diff --git a/.gitignore b/.gitignore
index 40f2a49386..cf080967fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,10 @@ Thumbs.db
*.dvi
*.toc
+# Folders created with unit/functional tests
+_build/
+_run/
+
# Old Files
*~
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000000..9760a39c1d
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,90 @@
+cmake_minimum_required(VERSION 3.4)
+
+list(APPEND CMAKE_MODULE_PATH ${CIME_CMAKE_MODULE_DIRECTORY})
+
+FIND_PATH(NETCDFC_FOUND libnetcdf.a ${NETCDF_C_DIR}/lib)
+FIND_PATH(NETCDFF_FOUND libnetcdff.a ${NETCDF_FORTRAN_DIR}/lib)
+MESSAGE(" NETCDFC_FOUND = ${NETCDFC_FOUND}")
+MESSAGE(" NETCDFF_FOUND = ${NETCDFF_FOUND}")
+
+string(APPEND LDFLAGS " -L${NETCDF_FORTRAN_DIR}/lib -lnetcdff")
+string(APPEND LDFLAGS " -L${NETCDF_C_DIR}/lib -lnetcdf")
+
+include(CIME_initial_setup)
+
+project(FATES_tests Fortran C)
+
+include(CIME_utils)
+
+set(HLM_ROOT "../../")
+
+# Add source directories from other share code (csm_share, etc.)
+add_subdirectory(${HLM_ROOT}/share/src csm_share)
+add_subdirectory(${HLM_ROOT}/share/unit_test_stubs/util csm_share_stubs)
+
+# Add FATES source directories
+add_subdirectory(${HLM_ROOT}/src/fates/main fates_main)
+add_subdirectory(${HLM_ROOT}/src/fates/biogeochem fates_biogeochem)
+add_subdirectory(${HLM_ROOT}/src/fates/biogeophys fates_biogeophys)
+add_subdirectory(${HLM_ROOT}/src/fates/parteh fates_parteh)
+add_subdirectory(${HLM_ROOT}/src/fates/fire fates_fire)
+add_subdirectory(${HLM_ROOT}/src/fates/radiation fates_radiation)
+add_subdirectory(${HLM_ROOT}/src/fates/testing/testing_shr test_share)
+
+# Remove shr_mpi_mod from share_sources.
+# This is needed because we want to use the mock shr_mpi_mod in place of the real one
+#
+# TODO: this should be moved into a general-purpose function in Sourcelist_utils.
+# Then this block of code could be replaced with a single call, like:
+# remove_source_file(${share_sources} "shr_mpi_mod.F90")
+foreach (sourcefile ${share_sources})
+ string(REGEX MATCH "shr_mpi_mod.F90" match_found ${sourcefile})
+ if(match_found)
+ list(REMOVE_ITEM share_sources ${sourcefile})
+ endif()
+endforeach()
+
+# Remove shr_cal_mod from share_sources.
+#
+# shr_cal_mod depends on ESMF (or the lightweight esmf wrf timemgr, at
+# least). Since CTSM doesn't currently use shr_cal_mod, we're avoiding
+# the extra overhead of including esmf_wrf_timemgr sources in this
+# build.
+#
+# TODO: like above, this should be moved into a general-purpose function
+# in Sourcelist_utils. Then this block of code could be replaced with a
+# single call, like: remove_source_file(${share_sources}
+# "shr_cal_mod.F90")
+foreach (sourcefile ${share_sources})
+ string(REGEX MATCH "shr_cal_mod.F90" match_found ${sourcefile})
+ if(match_found)
+ list(REMOVE_ITEM share_sources ${sourcefile})
+ endif()
+endforeach()
+
+# Build libraries containing stuff needed for the unit tests.
+# Eventually, these add_library calls should probably be distributed into the correct location, rather than being in this top-level CMakeLists.txt file.
+add_library(csm_share ${share_sources})
+declare_generated_dependencies(csm_share "${share_genf90_sources}")
+add_library(fates ${fates_sources})
+add_dependencies(fates csm_share)
+
+# We need to look for header files here, in order to pick up shr_assert.h
+include_directories(${HLM_ROOT}/share/include)
+
+# This needs to be something we add dynamically
+# via some calls using cime
+set(NETCDF_C_DIR ${NETCDF_C_PATH})
+set(NETCDF_FORTRAN_DIR ${NETCDF_F_PATH})
+
+include_directories(${NETCDF_C_DIR}/include
+ ${NETCDF_FORTRAN_DIR}/include)
+link_directories(${NETCDF_C_DIR}/lib
+ ${NETCDF_FORTRAN_DIR}/lib)
+
+# Tell cmake to look for libraries & mod files here, because this is where we built libraries
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+link_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+# Add the main test directory
+add_subdirectory(${HLM_ROOT}/src/fates/testing)
diff --git a/biogeochem/CMakeLists.txt b/biogeochem/CMakeLists.txt
new file mode 100644
index 0000000000..7a10c1fa9b
--- /dev/null
+++ b/biogeochem/CMakeLists.txt
@@ -0,0 +1,9 @@
+# This file is required for unit testing, but is not used for production runs
+list(APPEND fates_sources
+ FatesLitterMod.F90
+ FatesCohortMod.F90
+ FatesAllometryMod.F90
+ DamageMainMod.F90
+ FatesPatchMod.F90)
+
+sourcelist_to_parent(fates_sources)
diff --git a/biogeochem/EDLoggingMortalityMod.F90 b/biogeochem/EDLoggingMortalityMod.F90
index bf6ab7443c..6d373e995a 100644
--- a/biogeochem/EDLoggingMortalityMod.F90
+++ b/biogeochem/EDLoggingMortalityMod.F90
@@ -28,6 +28,7 @@ module EDLoggingMortalityMod
use FatesConstantsMod , only : dtype_ilog
use FatesConstantsMod , only : dtype_ifall
use FatesConstantsMod , only : dtype_ifire
+ use FatesConstantsMod , only : n_landuse_cats
use EDPftvarcon , only : EDPftvarcon_inst
use EDPftvarcon , only : GetDecompyFrac
use PRTParametersMod , only : prt_params
@@ -53,6 +54,7 @@ module EDLoggingMortalityMod
use FatesInterfaceTypesMod , only : hlm_num_lu_harvest_cats
use FatesInterfaceTypesMod , only : hlm_use_logging
use FatesInterfaceTypesMod , only : hlm_use_planthydro
+ use FatesInterfaceTypesMod , only : hlm_use_luh
use FatesConstantsMod , only : itrue,ifalse
use FatesGlobals , only : endrun => fates_endrun
use FatesGlobals , only : fates_log
@@ -69,7 +71,10 @@ module EDLoggingMortalityMod
use FatesConstantsMod , only : hlm_harvest_area_fraction
use FatesConstantsMod , only : hlm_harvest_carbon
use FatesConstantsMod, only : fates_check_param_set
-
+ use FatesInterfaceTypesMod , only : numpft
+ use FatesLandUseChangeMod, only : GetInitLanduseHarvestRate
+ use FatesLandUseChangeMod, only : GetLUHStatedata
+
implicit none
private
@@ -195,15 +200,17 @@ end subroutine IsItLoggingTime
! ======================================================================================
- subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, &
+ subroutine LoggingMortality_frac( currentSite, bc_in, pft_i, dbh, canopy_layer, lmort_direct, &
lmort_collateral,lmort_infra, l_degrad, &
hlm_harvest_rates, hlm_harvest_catnames, &
hlm_harvest_units, &
patch_land_use_label, secondary_age, &
- frac_site_primary, harvestable_forest_c, &
+ frac_site_primary, frac_site_secondary, harvestable_forest_c, &
harvest_tag)
- ! Arguments
+ ! Arguments
+ type(ed_site_type), intent(inout), target :: currentSite ! site structure
+ type(bc_in_type), intent(in) :: bc_in
integer, intent(in) :: pft_i ! pft index
real(r8), intent(in) :: dbh ! diameter at breast height (cm)
integer, intent(in) :: canopy_layer ! canopy layer of this cohort
@@ -215,6 +222,7 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, &
real(r8), intent(in) :: harvestable_forest_c(:) ! total harvestable forest carbon
! of all hlm harvest categories
real(r8), intent(in) :: frac_site_primary
+ real(r8), intent(in) :: frac_site_secondary
real(r8), intent(out) :: lmort_direct ! direct (harvestable) mortality fraction
real(r8), intent(out) :: lmort_collateral ! collateral damage mortality fraction
real(r8), intent(out) :: lmort_infra ! infrastructure mortality fraction
@@ -232,6 +240,9 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, &
! Local variables
integer :: cur_harvest_tag ! the harvest tag of the cohort today
real(r8) :: harvest_rate ! the final harvest rate to apply to this cohort today
+ real(r8) :: state_vector(n_landuse_cats)
+ logical :: site_secondaryland_first_exceeding_min
+ real(r8) :: secondary_young_fraction ! what fraction of secondary land is young secondary land
! todo: probably lower the dbhmin default value to 30 cm
! todo: change the default logging_event_code to 1 september (-244)
@@ -239,55 +250,92 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, &
! todo: check outputs against the LUH2 carbon data
! todo: eventually set up distinct harvest practices, each with a set of input paramaeters
! todo: implement harvested carbon inputs
-
- if (logging_time) then
- ! Pass logging rates to cohort level
-
- if (hlm_use_lu_harvest == ifalse) then
- ! 0=use fates logging parameters directly when logging_time == .true.
- ! this means harvest the whole cohort area
- harvest_rate = 1._r8
-
- else if (hlm_use_lu_harvest == itrue .and. hlm_harvest_units == hlm_harvest_area_fraction) then
- ! We are harvesting based on areal fraction, not carbon/biomass terms.
- ! 1=use area fraction from hlm
- ! combine forest and non-forest fracs and then apply:
- ! primary and secondary area fractions to the logging rates, which are fates parameters
-
- ! Definitions of the underlying harvest land category variables
- ! these are hardcoded to match the LUH input data via landuse.timseries file (see dynHarvestMod)
- ! these are fractions of vegetated area harvested, split into five land category variables
- ! HARVEST_VH1 = harvest from primary forest
- ! HARVEST_VH2 = harvest from primary non-forest
- ! HARVEST_SH1 = harvest from secondary mature forest
- ! HARVEST_SH2 = harvest from secondary young forest
- ! HARVEST_SH3 = harvest from secondary non-forest (assume this is young for biomass)
-
- ! Get the area-based harvest rates based on info passed to FATES from the boundary condition
- call get_harvest_rate_area (patch_land_use_label, hlm_harvest_catnames, &
- hlm_harvest_rates, frac_site_primary, secondary_age, harvest_rate)
+ ! The transition_landuse_from_off_to_on is for handling the special case of the first timestep after leaving potential
+ ! vegetation mode. In this case, all prior historical land-use, including harvest, needs to be applied on that first day.
+ ! So logging rates on that day are what is required to deforest exactly the amount of primary lands that will give the
+ ! amount of secondary lands dictated by the land use state vector for that year, rather than whatever the continuous
+ ! logging rate for that year is supposed to be according to the land use transition matrix.
+ if (.not. currentSite%transition_landuse_from_off_to_on) then
+
+ ! Check if the secondaryland exceeds the minimum if in landuse mode
+ site_secondaryland_first_exceeding_min = .false.
+ if (hlm_use_luh .eq. itrue) then
+ call GetLUHStatedata(bc_in, state_vector)
+ site_secondaryland_first_exceeding_min = (state_vector(secondaryland) .gt. currentSite%min_allowed_landuse_fraction) &
+ .and. (.not. currentSite%landuse_vector_gt_min(secondaryland))
+ end if
+
+ ! if the total intended area of secondary lands are less than what we can consider without having too-small patches,
+ ! or if that was the case until just now, then there is special logic
+ if (site_secondaryland_first_exceeding_min) then
+ if ( patch_land_use_label .eq. primaryland) then
+ harvest_rate = state_vector(secondaryland) / state_vector(primaryland)
+ write(fates_log(), *) 'applying state_vector(secondaryland) to plants.', pft_i
+ else
+ harvest_rate = 0._r8
+ endif
! For area-based harvest, harvest_tag shall always be 2 (not applicable).
harvest_tag = 2
cur_harvest_tag = 2
+ elseif (logging_time) then
+
+ ! Pass logging rates to cohort level
+
+ if (hlm_use_lu_harvest == ifalse) then
+ ! 0=use fates logging parameters directly when logging_time == .true.
+ ! this means harvest the whole cohort area
+ harvest_rate = 1._r8
+
+ else if (hlm_use_lu_harvest == itrue .and. hlm_harvest_units == hlm_harvest_area_fraction) then
+ ! We are harvesting based on areal fraction, not carbon/biomass terms.
+ ! 1=use area fraction from hlm
+ ! combine forest and non-forest fracs and then apply:
+ ! primary and secondary area fractions to the logging rates, which are fates parameters
+
+ ! Definitions of the underlying harvest land category variables
+ ! these are hardcoded to match the LUH input data via landuse.timseries file (see dynHarvestMod)
+ ! these are fractions of vegetated area harvested, split into five land category variables
+ ! HARVEST_VH1 = harvest from primary forest
+ ! HARVEST_VH2 = harvest from primary non-forest
+ ! HARVEST_SH1 = harvest from secondary mature forest
+ ! HARVEST_SH2 = harvest from secondary young forest
+ ! HARVEST_SH3 = harvest from secondary non-forest (assume this is young for biomass)
+
+ secondary_young_fraction = currentSite%get_secondary_young_fraction()
+
+ ! Get the area-based harvest rates based on info passed to FATES from the boundary condition
+ call get_harvest_rate_area (patch_land_use_label, hlm_harvest_catnames, &
+ hlm_harvest_rates, frac_site_primary, frac_site_secondary, secondary_young_fraction, secondary_age, harvest_rate)
+
+ ! For area-based harvest, harvest_tag shall always be 2 (not applicable).
+ harvest_tag = 2
+ cur_harvest_tag = 2
+
+ if (fates_global_verbose()) then
+ write(fates_log(), *) 'Successfully Read Harvest Rate from HLM.', hlm_harvest_rates(:), harvest_rate
+ end if
- if (fates_global_verbose()) then
- write(fates_log(), *) 'Successfully Read Harvest Rate from HLM.', hlm_harvest_rates(:), harvest_rate
- end if
+ else if (hlm_use_lu_harvest == itrue .and. hlm_harvest_units == hlm_harvest_carbon) then
+ ! 2=use carbon from hlm
+ ! shall call another subroutine, which transfers biomass/carbon into fraction
- else if (hlm_use_lu_harvest == itrue .and. hlm_harvest_units == hlm_harvest_carbon) then
- ! 2=use carbon from hlm
- ! shall call another subroutine, which transfers biomass/carbon into fraction
+ call get_harvest_rate_carbon (patch_land_use_label, hlm_harvest_catnames, &
+ hlm_harvest_rates, secondary_age, harvestable_forest_c, &
+ harvest_rate, harvest_tag, cur_harvest_tag)
- call get_harvest_rate_carbon (patch_land_use_label, hlm_harvest_catnames, &
- hlm_harvest_rates, secondary_age, harvestable_forest_c, &
- harvest_rate, harvest_tag, cur_harvest_tag)
+ if (fates_global_verbose()) then
+ write(fates_log(), *) 'Successfully Read Harvest Rate from HLM.', hlm_harvest_rates(:), harvest_rate, harvestable_forest_c
+ end if
- if (fates_global_verbose()) then
- write(fates_log(), *) 'Successfully Read Harvest Rate from HLM.', hlm_harvest_rates(:), harvest_rate, harvestable_forest_c
- end if
-
+ endif
+
+ else
+ harvest_rate = 0._r8
+ ! For area-based harvest, harvest_tag shall always be 2 (not applicable).
+ harvest_tag = 2
+ cur_harvest_tag = 2
endif
! transfer of area to secondary land is based on overall area affected, not just logged crown area
@@ -296,7 +344,7 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, &
if (cur_harvest_tag == 0) then
! direct logging rates, based on dbh min and max criteria
if (dbh >= logging_dbhmin .and. .not. &
- ((logging_dbhmax < fates_check_param_set) .and. (dbh >= logging_dbhmax )) ) then
+ ((logging_dbhmax < fates_check_param_set) .and. (dbh >= logging_dbhmax )) ) then
! the logic of the above line is a bit unintuitive but allows turning off the dbhmax comparison entirely.
! since there is an .and. .not. after the first conditional, the dbh:dbhmax comparison needs to be
! the opposite of what would otherwise be expected...
@@ -305,7 +353,7 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, &
lmort_direct = 0.0_r8
end if
else
- lmort_direct = 0.0_r8
+ lmort_direct = 0.0_r8
end if
! infrastructure (roads, skid trails, etc) mortality rates
@@ -335,13 +383,20 @@ subroutine LoggingMortality_frac( pft_i, dbh, canopy_layer, lmort_direct, &
else
l_degrad = 0._r8
endif
-
- else
- lmort_direct = 0.0_r8
+
+ else
+ call GetInitLanduseHarvestRate(bc_in, currentSite%min_allowed_landuse_fraction, &
+ harvest_rate, currentSite%landuse_vector_gt_min)
+ lmort_direct = 0.0_r8
lmort_collateral = 0.0_r8
lmort_infra = 0.0_r8
l_degrad = 0.0_r8
- end if
+ if(prt_params%woody(pft_i) == itrue)then
+ lmort_direct = harvest_rate
+ else if (canopy_layer .eq. 1) then
+ l_degrad = harvest_rate
+ endif
+ endif
end subroutine LoggingMortality_frac
@@ -349,13 +404,13 @@ end subroutine LoggingMortality_frac
! ============================================================================
subroutine get_harvest_rate_area (patch_land_use_label, hlm_harvest_catnames, hlm_harvest_rates, &
- frac_site_primary, secondary_age, harvest_rate)
+ frac_site_primary, frac_site_secondary, secondary_young_fraction, secondary_age, harvest_rate)
! -------------------------------------------------------------------------------------------
!
! DESCRIPTION:
- ! get the area-based harvest rates based on info passed to FATES from the bioundary conditions in.
+ ! get the area-based harvest rates based on info passed to FATES from the boundary conditions in.
! assumes logging_time == true
! Arguments
@@ -364,6 +419,8 @@ subroutine get_harvest_rate_area (patch_land_use_label, hlm_harvest_catnames, hl
integer, intent(in) :: patch_land_use_label ! patch level land_use_label
real(r8), intent(in) :: secondary_age ! patch level age_since_anthro_disturbance
real(r8), intent(in) :: frac_site_primary
+ real(r8), intent(in) :: frac_site_secondary
+ real(r8), intent(in) :: secondary_young_fraction ! what fraction of secondary land is young secondary land
real(r8), intent(out) :: harvest_rate
! Local Variables
@@ -396,19 +453,26 @@ subroutine get_harvest_rate_area (patch_land_use_label, hlm_harvest_catnames, hl
! Normalize by site-level primary or secondary forest fraction
! since harvest_rate is specified as a fraction of the gridcell
! also need to put a cap so as not to harvest more primary or secondary area than there is in a gridcell
+ ! For secondary, also need to normalize by the young/old fraction.
if (patch_land_use_label .eq. primaryland) then
if (frac_site_primary .gt. fates_tiny) then
- harvest_rate = min((harvest_rate / frac_site_primary),frac_site_primary)
+ harvest_rate = min((harvest_rate / frac_site_primary),1._r8)
else
harvest_rate = 0._r8
endif
- else
- if ((1._r8-frac_site_primary) .gt. fates_tiny) then
- harvest_rate = min((harvest_rate / (1._r8-frac_site_primary)),&
- (1._r8-frac_site_primary))
+ else if (patch_land_use_label .eq. secondaryland) then
+ ! the .gt. -0.5 in the next line is because frac_site_secondary returns -1 if no secondary area.
+ if (frac_site_secondary .gt. fates_tiny .and. frac_site_secondary .gt. -0.5_r8) then
+ if (secondary_age .lt. secondary_age_threshold) then
+ harvest_rate = min((harvest_rate / (frac_site_secondary * secondary_young_fraction)), 1._r8)
+ else
+ harvest_rate = min((harvest_rate / (frac_site_secondary * (1._r8 - secondary_young_fraction))), 1._r8)
+ endif
else
harvest_rate = 0._r8
endif
+ else
+ harvest_rate = 0._r8
endif
! calculate today's harvest rate
@@ -983,7 +1047,7 @@ subroutine logging_litter_fluxes(currentSite, currentPatch, newPatch, patch_site
ag_wood * logging_export_frac
! This is for checking the total mass balance [kg/site/day]
- site_mass%wood_product = site_mass%wood_product + &
+ site_mass%wood_product_harvest(pft) = site_mass%wood_product_harvest(pft) + &
ag_wood * logging_export_frac
new_litt%ag_cwd(ncwd) = new_litt%ag_cwd(ncwd) + ag_wood * &
@@ -1106,13 +1170,13 @@ subroutine UpdateHarvestC(currentSite,bc_out)
use PRTGenericMod , only : element_pos
use PRTGenericMod , only : carbon12_element
use FatesInterfaceTypesMod , only : bc_out_type
- use EDParamsMod , only : pprodharv10_forest_mean
! Arguments
type(ed_site_type), intent(inout), target :: currentSite ! site structure
type(bc_out_type), intent(inout) :: bc_out
integer :: icode
+ integer :: i_pft
real(r8) :: unit_trans_factor
@@ -1123,13 +1187,26 @@ subroutine UpdateHarvestC(currentSite,bc_out)
! Calculate the unit transfer factor (from kgC m-2 day-1 to gC m-2 s-1)
unit_trans_factor = g_per_kg * days_per_sec
- bc_out%hrv_deadstemc_to_prod10c = bc_out%hrv_deadstemc_to_prod10c + &
- currentSite%mass_balance(element_pos(carbon12_element))%wood_product * &
- AREA_INV * pprodharv10_forest_mean * unit_trans_factor
- bc_out%hrv_deadstemc_to_prod100c = bc_out%hrv_deadstemc_to_prod100c + &
- currentSite%mass_balance(element_pos(carbon12_element))%wood_product * &
- AREA_INV * (1._r8 - pprodharv10_forest_mean) * unit_trans_factor
-
+ ! harvest-associated wood product pools
+ do i_pft = 1,numpft
+ bc_out%hrv_deadstemc_to_prod10c = bc_out%hrv_deadstemc_to_prod10c + &
+ currentSite%mass_balance(element_pos(carbon12_element))%wood_product_harvest(i_pft) * &
+ AREA_INV * EDPftvarcon_inst%harvest_pprod10(i_pft) * unit_trans_factor
+ bc_out%hrv_deadstemc_to_prod100c = bc_out%hrv_deadstemc_to_prod100c + &
+ currentSite%mass_balance(element_pos(carbon12_element))%wood_product_harvest(i_pft) * &
+ AREA_INV * (1._r8 - EDPftvarcon_inst%harvest_pprod10(i_pft)) * unit_trans_factor
+ end do
+
+ ! land-use-change-associated wood product pools
+ do i_pft = 1,numpft
+ bc_out%hrv_deadstemc_to_prod10c = bc_out%hrv_deadstemc_to_prod10c + &
+ currentSite%mass_balance(element_pos(carbon12_element))%wood_product_landusechange(i_pft) * &
+ AREA_INV * EDPftvarcon_inst%landusechange_pprod10(i_pft) * unit_trans_factor
+ bc_out%hrv_deadstemc_to_prod100c = bc_out%hrv_deadstemc_to_prod100c + &
+ currentSite%mass_balance(element_pos(carbon12_element))%wood_product_landusechange(i_pft) * &
+ AREA_INV * (1._r8 - EDPftvarcon_inst%landusechange_pprod10(i_pft)) * unit_trans_factor
+ end do
+
return
end subroutine UpdateHarvestC
diff --git a/biogeochem/EDMortalityFunctionsMod.F90 b/biogeochem/EDMortalityFunctionsMod.F90
index 6abb19013b..2778f3c0b7 100644
--- a/biogeochem/EDMortalityFunctionsMod.F90
+++ b/biogeochem/EDMortalityFunctionsMod.F90
@@ -281,7 +281,7 @@ end subroutine mortality_rates
subroutine Mortality_Derivative( currentSite, currentCohort, bc_in, btran_ft, &
mean_temp, land_use_label, age_since_anthro_disturbance, &
- frac_site_primary, harvestable_forest_c, harvest_tag)
+ frac_site_primary, frac_site_secondary, harvestable_forest_c, harvest_tag)
!
! !DESCRIPTION:
@@ -301,6 +301,7 @@ subroutine Mortality_Derivative( currentSite, currentCohort, bc_in, btran_ft, &
integer, intent(in) :: land_use_label
real(r8), intent(in) :: age_since_anthro_disturbance
real(r8), intent(in) :: frac_site_primary
+ real(r8), intent(in) :: frac_site_secondary
real(r8), intent(in) :: harvestable_forest_c(:) ! total carbon available for logging, kgC site-1
integer, intent(out) :: harvest_tag(:) ! tag to record the harvest status
@@ -329,7 +330,7 @@ subroutine Mortality_Derivative( currentSite, currentCohort, bc_in, btran_ft, &
!if trees are in the canopy, then their death is 'disturbance'. This probably needs a different terminology
call mortality_rates(currentCohort,bc_in,btran_ft, mean_temp, &
cmort,hmort,bmort,frmort, smort, asmort, dgmort)
- call LoggingMortality_frac(ipft, currentCohort%dbh, currentCohort%canopy_layer, &
+ call LoggingMortality_frac(currentSite, bc_in, ipft, currentCohort%dbh, currentCohort%canopy_layer, &
currentCohort%lmort_direct, &
currentCohort%lmort_collateral, &
currentCohort%lmort_infra, &
@@ -339,7 +340,7 @@ subroutine Mortality_Derivative( currentSite, currentCohort, bc_in, btran_ft, &
bc_in%hlm_harvest_units, &
land_use_label, &
age_since_anthro_disturbance, &
- frac_site_primary, harvestable_forest_c, harvest_tag)
+ frac_site_primary, frac_site_secondary, harvestable_forest_c, harvest_tag)
if (currentCohort%canopy_layer > 1)then
! Include understory logging mortality rates not associated with disturbance
diff --git a/biogeochem/EDPatchDynamicsMod.F90 b/biogeochem/EDPatchDynamicsMod.F90
index 140c108d66..586c3a1e3b 100644
--- a/biogeochem/EDPatchDynamicsMod.F90
+++ b/biogeochem/EDPatchDynamicsMod.F90
@@ -1,4 +1,3 @@
-
module EDPatchDynamicsMod
! ============================================================================
! Controls formation, creation, fusing and termination of patch level processes.
@@ -43,6 +42,7 @@ module EDPatchDynamicsMod
use FatesLitterMod , only : dl_sf
use FatesConstantsMod , only : N_DIST_TYPES
use EDTypesMod , only : AREA_INV
+ use EDTypesMod , only : dump_site
use FatesConstantsMod , only : rsnbl_math_prec
use FatesConstantsMod , only : fates_tiny
use FatesConstantsMod , only : nocomp_bareground
@@ -71,6 +71,7 @@ module EDPatchDynamicsMod
use EDLoggingMortalityMod, only : get_harvest_rate_carbon
use EDLoggingMortalityMod, only : get_harvestable_carbon
use EDLoggingMortalityMod, only : get_harvest_debt
+ use FatesLandUseChangeMod, only : GetInitLanduseHarvestRate
use EDParamsMod , only : fates_mortality_disturbance_fraction
use FatesAllometryMod , only : carea_allom
use FatesAllometryMod , only : set_root_fraction
@@ -80,8 +81,11 @@ module EDPatchDynamicsMod
use FatesConstantsMod , only : years_per_day
use FatesConstantsMod , only : nearzero
use FatesConstantsMod , only : primaryland, secondaryland, pastureland, rangeland, cropland
+ use FatesConstantsMod , only : nocomp_bareground_land
use FatesConstantsMod , only : n_landuse_cats
- use FatesLandUseChangeMod, only : get_landuse_transition_rates
+ use FatesLandUseChangeMod, only : GetLanduseTransitionRates
+ use FatesLandUseChangeMod, only : GetInitLanduseTransitionRates
+ use FatesLandUseChangeMod, only : GetLUHStatedata
use FatesConstantsMod , only : fates_unset_r8
use FatesConstantsMod , only : fates_unset_int
use FatesConstantsMod , only : hlm_harvest_carbon
@@ -106,7 +110,7 @@ module EDPatchDynamicsMod
use FatesRunningMeanMod, only : ema_sdlng_emerg_h2o, ema_sdlng_mort_par, ema_sdlng2sap_par
use FatesRunningMeanMod, only : ema_24hr, fixed_24hr, ema_lpa, ema_longterm
use FatesRadiationMemMod, only : num_swb
-
+
! CIME globals
use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=)
use shr_log_mod , only : errMsg => shr_log_errMsg
@@ -123,7 +127,6 @@ module EDPatchDynamicsMod
public :: check_patch_area
public :: set_patchno
private:: fuse_2_patches
- public :: get_frac_site_primary
character(len=*), parameter, private :: sourcefile = &
__FILE__
@@ -204,21 +207,35 @@ subroutine disturbance_rates( site_in, bc_in)
integer :: threshold_sizeclass
integer :: i_dist
integer :: h_index
- real(r8) :: frac_site_primary
real(r8) :: harvest_rate
real(r8) :: tempsum
real(r8) :: mean_temp
real(r8) :: harvestable_forest_c(hlm_num_lu_harvest_cats)
integer :: harvest_tag(hlm_num_lu_harvest_cats)
- real(r8) :: landuse_transition_matrix(n_landuse_cats, n_landuse_cats) ! [m2/m2/day]
real(r8) :: current_fates_landuse_state_vector(n_landuse_cats) ! [m2/m2]
+ real(r8) :: state_vector(n_landuse_cats)
+ real(r8), parameter :: max_daily_disturbance_rate = 0.999_r8
+ logical :: site_secondaryland_first_exceeding_min
+ real(r8) :: secondary_young_fraction ! what fraction of secondary land is young secondary land
!----------------------------------------------------------------------------------------------
! Calculate Mortality Rates (these were previously calculated during growth derivatives)
! And the same rates in understory plants have already been applied to %dndt
!----------------------------------------------------------------------------------------------
! first calculate the fraction of the site that is primary land
- call get_frac_site_primary(site_in, frac_site_primary)
+ current_fates_landuse_state_vector = site_in%get_current_landuse_statevector()
+
+ ! and get the fraction of secondary land that is young secondary land
+ secondary_young_fraction = site_in%get_secondary_young_fraction()
+
+ ! check status of transition_landuse_from_off_to_on flag, and do some error checking on it
+ if(site_in%transition_landuse_from_off_to_on) then
+ if (sum(current_fates_landuse_state_vector(secondaryland:cropland)) .gt. nearzero) then
+ write(fates_log(),*) 'flag for transition_landuse_from_off_to_on is set to true but site is not entirely primaryland'
+ write(fates_log(), *) current_fates_landuse_state_vector
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ endif
+ endif
! get available biomass for harvest for all patches
call get_harvestable_carbon(site_in, bc_in%site_area, bc_in%hlm_harvest_catnames, harvestable_forest_c)
@@ -246,14 +263,16 @@ subroutine disturbance_rates( site_in, bc_in)
currentCohort%asmort = asmort
currentCohort%dgmort = dgmort
- call LoggingMortality_frac(currentCohort%pft, currentCohort%dbh, currentCohort%canopy_layer, &
+ call LoggingMortality_frac(site_in, bc_in, currentCohort%pft, &
+ currentCohort%dbh, currentCohort%canopy_layer, &
lmort_direct,lmort_collateral,lmort_infra,l_degrad,&
bc_in%hlm_harvest_rates, &
bc_in%hlm_harvest_catnames, &
bc_in%hlm_harvest_units, &
currentPatch%land_use_label, &
currentPatch%age_since_anthro_disturbance, &
- frac_site_primary, &
+ current_fates_landuse_state_vector(primaryland), &
+ current_fates_landuse_state_vector(secondaryland), &
harvestable_forest_c, &
harvest_tag)
@@ -271,21 +290,17 @@ subroutine disturbance_rates( site_in, bc_in)
call get_harvest_debt(site_in, bc_in, harvest_tag)
if ( hlm_use_luh .eq. itrue ) then
- call get_landuse_transition_rates(bc_in, landuse_transition_matrix)
+ if(.not. site_in%transition_landuse_from_off_to_on) then
+ call GetLanduseTransitionRates(bc_in, site_in%min_allowed_landuse_fraction, &
+ site_in%landuse_transition_matrix, site_in%landuse_vector_gt_min)
+ else
+ call GetInitLanduseTransitionRates(bc_in, site_in%min_allowed_landuse_fraction, &
+ site_in%landuse_transition_matrix, site_in%landuse_vector_gt_min)
+ endif
else
- landuse_transition_matrix(:,:) = 0._r8
+ site_in%landuse_transition_matrix(:,:) = 0._r8
endif
- ! calculate total area in each landuse category
- current_fates_landuse_state_vector(:) = 0._r8
- currentPatch => site_in%oldest_patch
- do while (associated(currentPatch))
- current_fates_landuse_state_vector(currentPatch%land_use_label) = &
- current_fates_landuse_state_vector(currentPatch%land_use_label) + &
- currentPatch%area/AREA
- currentPatch => currentPatch%younger
- end do
-
! ---------------------------------------------------------------------------------------------
! Calculate Disturbance Rates based on the mortality rates just calculated
! ---------------------------------------------------------------------------------------------
@@ -304,6 +319,16 @@ subroutine disturbance_rates( site_in, bc_in)
currentPatch => currentPatch%younger
end do
+ ! get some info needed to determine whether or not to apply land use change
+ site_secondaryland_first_exceeding_min = .false.
+ if (hlm_use_luh .eq. itrue) then
+ call GetLUHStatedata(bc_in, state_vector)
+ site_secondaryland_first_exceeding_min = (state_vector(secondaryland) .gt. site_in%min_allowed_landuse_fraction) &
+ .and. (.not. site_in%landuse_vector_gt_min(secondaryland))
+ else
+ state_vector = current_fates_landuse_state_vector
+ end if
+
currentPatch => site_in%oldest_patch
do while (associated(currentPatch))
@@ -313,10 +338,18 @@ subroutine disturbance_rates( site_in, bc_in)
dist_rate_ldist_notharvested = 0.0_r8
- ! Avoid this calculation to avoid NaN due to division by zero result if luh is not used
- if (hlm_use_luh .eq. itrue) then
+ ! transition matrix has units of area transitioned per unit area of the whole gridcell per time;
+ ! need to change to area transitioned per unit area of that land-use type per time;
+ ! because the land use state vector sums to one minus area bareground, need to also divide by that
+ ! (or rather, multiply since it is in the denominator of the denominator)
+ ! Avoid this calculation to avoid NaN due to division by zero result if luh is not used or applying
+ ! to bare ground note that an alternative here might be to use what LUH thinks the state vector
+ ! should be instead of what the FATES state vector is, in order to not amplify small deviations
+ ! between the two...
+ if (hlm_use_luh .eq. itrue .and. currentPatch%land_use_label .gt. nocomp_bareground_land) then
currentPatch%landuse_transition_rates(1:n_landuse_cats) = min(1._r8, &
- landuse_transition_matrix(currentPatch%land_use_label,1:n_landuse_cats) / &
+ site_in%landuse_transition_matrix(currentPatch%land_use_label,1:n_landuse_cats) &
+ * (1._r8 - site_in%area_bareground) / &
current_fates_landuse_state_vector(currentPatch%land_use_label))
else
currentPatch%landuse_transition_rates = 0.0_r8
@@ -357,18 +390,37 @@ subroutine disturbance_rates( site_in, bc_in)
! for non-closed-canopy areas subject to logging, add an additional increment of area disturbed
! equivalent to the fraction logged to account for transfer of interstitial ground area to new secondary lands
- if ( logging_time .and. &
+ if ( (logging_time .or. site_in%transition_landuse_from_off_to_on .or. site_secondaryland_first_exceeding_min) .and. &
(currentPatch%area - currentPatch%total_canopy_area) .gt. fates_tiny ) then
! The canopy is NOT closed.
- if(bc_in%hlm_harvest_units == hlm_harvest_carbon) then
- call get_harvest_rate_carbon (currentPatch%land_use_label, bc_in%hlm_harvest_catnames, &
- bc_in%hlm_harvest_rates, currentPatch%age_since_anthro_disturbance, harvestable_forest_c, &
- harvest_rate, harvest_tag)
+ if (.not. site_in%transition_landuse_from_off_to_on) then
+ if(bc_in%hlm_harvest_units == hlm_harvest_carbon) then
+ call get_harvest_rate_carbon (currentPatch%land_use_label, bc_in%hlm_harvest_catnames, &
+ bc_in%hlm_harvest_rates, currentPatch%age_since_anthro_disturbance, harvestable_forest_c, &
+ harvest_rate, harvest_tag)
+ else
+ call get_harvest_rate_area (currentPatch%land_use_label, bc_in%hlm_harvest_catnames, &
+ bc_in%hlm_harvest_rates, current_fates_landuse_state_vector(primaryland), &
+ current_fates_landuse_state_vector(secondaryland), secondary_young_fraction, &
+ currentPatch%age_since_anthro_disturbance, harvest_rate)
+ end if
+
+ ! if the total intended area of secondary lands are less than what we can consider
+ ! without having too-small patches, or if that was the case until just now,
+ ! then there is special logic
+ if (state_vector(secondaryland) .le. site_in%min_allowed_landuse_fraction) then
+ harvest_rate = 0._r8
+ else if (currentPatch%land_use_label .eq. primaryland .and. .not. &
+ site_in%landuse_vector_gt_min(secondaryland)) then
+ harvest_rate = state_vector(secondaryland) / sum(state_vector(:))
+ else
+ harvest_rate = 0._r8
+ end if
else
- call get_harvest_rate_area (currentPatch%land_use_label, bc_in%hlm_harvest_catnames, &
- bc_in%hlm_harvest_rates, frac_site_primary, currentPatch%age_since_anthro_disturbance, harvest_rate)
- end if
+ call GetInitLanduseHarvestRate(bc_in, site_in%min_allowed_landuse_fraction, &
+ harvest_rate, site_in%landuse_vector_gt_min)
+ endif
currentPatch%disturbance_rates(dtype_ilog) = currentPatch%disturbance_rates(dtype_ilog) + &
(currentPatch%area - currentPatch%total_canopy_area) * harvest_rate / currentPatch%area
@@ -399,20 +451,42 @@ subroutine disturbance_rates( site_in, bc_in)
call FatesWarn(msg,index=2)
endif
- ! if the sum of all disturbance rates is such that they will exceed total patch area on this day, then reduce them all proportionally.
- if ( (sum(currentPatch%disturbance_rates(:)) + sum(currentPatch%landuse_transition_rates(1:n_landuse_cats))) .gt. 1.0_r8 ) then
+ ! if the sum of all disturbance rates is such that they will exceed total patch area on this day,
+ ! then reduce them all proportionally.
+
+ if ( (sum(currentPatch%disturbance_rates(:)) + sum(currentPatch%landuse_transition_rates(1:n_landuse_cats))) .gt. &
+ max_daily_disturbance_rate ) then
tempsum = sum(currentPatch%disturbance_rates(:)) + sum(currentPatch%landuse_transition_rates(1:n_landuse_cats))
do i_dist = 1,N_DIST_TYPES
- currentPatch%disturbance_rates(i_dist) = currentPatch%disturbance_rates(i_dist) / tempsum
+ currentPatch%disturbance_rates(i_dist) = max_daily_disturbance_rate * currentPatch%disturbance_rates(i_dist) &
+ / tempsum
end do
do i_dist = 1,n_landuse_cats
- currentPatch%landuse_transition_rates(i_dist) = currentPatch%landuse_transition_rates(i_dist) / tempsum
+ currentPatch%landuse_transition_rates(i_dist) = max_daily_disturbance_rate * &
+ currentPatch%landuse_transition_rates(i_dist) / tempsum
end do
endif
currentPatch => currentPatch%younger
- enddo !patch loop
+ enddo !patch loop
+
+ ! if the area of secondary land has just exceeded the minimum below which we ignore things,
+ ! set the flag to keep track of that.
+ if ( (state_vector(secondaryland) .gt. site_in%min_allowed_landuse_fraction) .and. &
+ (.not. site_in%landuse_vector_gt_min(secondaryland)) ) then
+ site_in%landuse_vector_gt_min(secondaryland) = .true.
+ write(fates_log(),*) 'setting site_in%landuse_vector_gt_min(secondaryland) = .true.'
+ if (debug) then
+ currentPatch => site_in%oldest_patch
+ do while (associated(currentPatch))
+ write(fates_log(),*) 'cpatch area, LU, distrates(ilog): ', currentPatch%area, currentPatch%land_use_label, &
+ currentPatch%nocomp_pft_label, currentPatch%disturbance_rates(dtype_ilog), &
+ currentPatch%area - currentPatch%total_canopy_area
+ currentPatch => currentPatch%younger
+ end do
+ end if
+ end if
end subroutine disturbance_rates
@@ -440,8 +514,7 @@ subroutine spawn_patches( currentSite, bc_in)
use EDParamsMod , only : ED_val_understorey_death, logging_coll_under_frac
use EDCohortDynamicsMod , only : terminate_cohorts
use FatesConstantsMod , only : rsnbl_math_prec
- use FatesLandUseChangeMod, only : get_landuse_transition_rates
- use FatesLandUseChangeMod, only : get_landusechange_rules
+ use FatesLandUseChangeMod, only : GetLanduseChangeRules
!
! !ARGUMENTS:
type (ed_site_type), intent(inout) :: currentSite
@@ -482,7 +555,20 @@ subroutine spawn_patches( currentSite, bc_in)
real(r8) :: disturbance_rate ! rate of disturbance being resolved [fraction of patch area / day]
real(r8) :: oldarea ! old patch area prior to disturbance
logical :: clearing_matrix(n_landuse_cats,n_landuse_cats) ! do we clear vegetation when transferring from one LU type to another?
-
+ type (fates_patch_type) , pointer :: buffer_patch, temp_patch, copyPatch, previousPatch
+ real(r8) :: nocomp_pft_area_vector(numpft)
+ real(r8) :: nocomp_pft_area_vector_filled(numpft)
+ real(r8) :: nocomp_pft_area_vector_alt(numpft)
+ real(r8) :: newp_area_buffer_frac(numpft)
+ real(r8) :: newp_area_vector(numpft)
+ real(r8) :: max_val
+ integer :: i_land_use_label
+ integer :: i_pft
+ real(r8) :: newp_area, area_to_keep, fraction_to_keep
+ logical :: buffer_patch_in_linked_list
+ integer :: n_pfts_by_landuse
+ integer :: which_pft_allowed
+ logical :: buffer_patch_used
!---------------------------------------------------------------------
storesmallcohort => null() ! storage of the smallest cohort for insertion routine
@@ -500,8 +586,8 @@ subroutine spawn_patches( currentSite, bc_in)
currentSite%disturbance_rates(:,:,:) = 0._r8
! get rules for vegetation clearing during land use change
- call get_landusechange_rules(clearing_matrix)
-
+ call GetLanduseChangeRules(clearing_matrix)
+
! in the nocomp cases, since every patch has a PFT identity, it can only receive patch area from patches
! that have the same identity. In order to allow this, we have this very high level loop over nocomp PFTs
! and only do the disturbance for any patches that have that nocomp PFT identity.
@@ -511,7 +597,8 @@ subroutine spawn_patches( currentSite, bc_in)
! we want at the second-outermost loop to go through all disturbance types, because we resolve each of these separately
disturbance_type_loop: do i_disturbance_type = 1,N_DIST_TYPES
- ! the next loop level is to go through patches that have a specific land-use type. the reason to do this is because the combination of
+ ! the next loop level is to go through patches that have a specific land-use type. the reason to do this
+ ! is because the combination of
! disturbance type and donor land-use type uniquly define the land-use type of the receiver patch.
landuse_donortype_loop: do i_donorpatch_landuse_type = 1, n_landuse_cats
@@ -520,7 +607,8 @@ subroutine spawn_patches( currentSite, bc_in)
! for fire and treefall disturbance, receiver land-use type is whatever the donor land-use type is.
! for logging disturbance, receiver land-use type is always secondary lands
- ! for land-use-change disturbance, we need to loop over all possible transition types for land-use-change from the current land-use type.
+ ! for land-use-change disturbance, we need to loop over all possible transition types for land-use-change from
+ ! the current land-use type.
select case(i_disturbance_type)
case(dtype_ifire)
@@ -533,7 +621,8 @@ subroutine spawn_patches( currentSite, bc_in)
start_receiver_lulabel = secondaryland
end_receiver_lulabel = secondaryland
case(dtype_ilandusechange)
- start_receiver_lulabel = 1 ! this could actually maybe be 2, as primaryland column of matrix should all be zeros, but leave as 1 for now
+ start_receiver_lulabel = 1 ! this could actually maybe be 2, as primaryland column of matrix should
+ ! all be zeros, but leave as 1 for now
end_receiver_lulabel = n_landuse_cats
case default
write(fates_log(),*) 'unknown disturbance mode?'
@@ -543,19 +632,24 @@ subroutine spawn_patches( currentSite, bc_in)
! next loop level is the set of possible receiver patch land use types.
! for disturbance types other than land use change, this is sort of a dummy loop, per the above logic.
- landusechange_receiverpatchlabel_loop: do i_landusechange_receiverpatchlabel = start_receiver_lulabel, end_receiver_lulabel
+ landusechange_receiverpatchlabel_loop: do i_landusechange_receiverpatchlabel = start_receiver_lulabel, &
+ end_receiver_lulabel
! now we want to begin resolving all of the disturbance given the above categorical criteria of:
- ! nocomp-PFT, disturbance type, donor patch land use label, and receiver patch land use label. All of the disturbed area that meets these
- ! criteria (if any) will be put into a new patch whose area and properties are taken from one or more donor patches.
+ ! nocomp-PFT, disturbance type, donor patch land use label, and receiver patch land use label.
+ ! All of the disturbed area that meets these criteria (if any) will be put into a new patch whose area and
+ ! properties are taken from one or more donor patches.
- ! calculate area of disturbed land that meets the above criteria, in this timestep, by summing contributions from each existing patch.
+ ! calculate area of disturbed land that meets the above criteria, in this timestep, by summing contributions
+ ! from each existing patch.
currentPatch => currentSite%youngest_patch
- ! this variable site_areadis holds all the newly disturbed area from all patches for all disturbance being resolved now.
+ ! this variable site_areadis holds all the newly disturbed area from all patches for all disturbance being
+ ! resolved now.
site_areadis = 0.0_r8
- ! loop over all patches to figure out the total patch area generated as a result of all disturbance being resolved now.
+ ! loop over all patches to figure out the total patch area generated as a result of all disturbance being
+ ! resolved now.
patchloop_areadis: do while(associated(currentPatch))
cp_nocomp_matches_1_if: if ( hlm_use_nocomp .eq. ifalse .or. &
@@ -678,14 +772,21 @@ subroutine spawn_patches( currentSite, bc_in)
currentPatch%burnt_frac_litter(:) = 0._r8
end if
+ call CopyPatchMeansTimers(currentPatch, newPatch)
+
call TransLitterNewPatch( currentSite, currentPatch, newPatch, patch_site_areadis)
! Transfer in litter fluxes from plants in various contexts of death and destruction
-
select case(i_disturbance_type)
case (dtype_ilog)
call logging_litter_fluxes(currentSite, currentPatch, &
newPatch, patch_site_areadis,bc_in)
+
+ ! if transitioning from primary to secondary, then may need to change nocomp pft,
+ ! so tag as having transitioned LU
+ if ( i_disturbance_type .eq. dtype_ilog .and. i_donorpatch_landuse_type .eq. primaryland) then
+ newPatch%changed_landuse_this_ts = .true.
+ end if
case (dtype_ifire)
call fire_litter_fluxes(currentSite, currentPatch, &
newPatch, patch_site_areadis,bc_in)
@@ -696,33 +797,15 @@ subroutine spawn_patches( currentSite, bc_in)
call landusechange_litter_fluxes(currentSite, currentPatch, &
newPatch, patch_site_areadis,bc_in, &
clearing_matrix(i_donorpatch_landuse_type,i_landusechange_receiverpatchlabel))
+
+ ! if land use change, then may need to change nocomp pft, so tag as having transitioned LU
+ newPatch%changed_landuse_this_ts = .true.
case default
write(fates_log(),*) 'unknown disturbance mode?'
write(fates_log(),*) 'i_disturbance_type: ',i_disturbance_type
call endrun(msg=errMsg(sourcefile, __LINE__))
end select
-
- ! Copy any means or timers from the original patch to the new patch
- ! These values will inherit all info from the original patch
- ! --------------------------------------------------------------------------
- call newPatch%tveg24%CopyFromDonor(currentPatch%tveg24)
- call newPatch%tveg_lpa%CopyFromDonor(currentPatch%tveg_lpa)
- call newPatch%tveg_longterm%CopyFromDonor(currentPatch%tveg_longterm)
-
-
- if ( regeneration_model == TRS_regeneration ) then
- call newPatch%seedling_layer_par24%CopyFromDonor(currentPatch%seedling_layer_par24)
- call newPatch%sdlng_mort_par%CopyFromDonor(currentPatch%sdlng_mort_par)
- call newPatch%sdlng2sap_par%CopyFromDonor(currentPatch%sdlng2sap_par)
- do pft = 1,numpft
- call newPatch%sdlng_emerg_smp(pft)%p%CopyFromDonor(currentPatch%sdlng_emerg_smp(pft)%p)
- call newPatch%sdlng_mdd(pft)%p%CopyFromDonor(currentPatch%sdlng_mdd(pft)%p)
- enddo
- end if
-
- call newPatch%tveg_longterm%CopyFromDonor(currentPatch%tveg_longterm)
-
! --------------------------------------------------------------------------
! The newly formed patch from disturbance (newPatch), has now been given
! some litter from dead plants and pre-existing litter from the donor patches.
@@ -766,7 +849,8 @@ subroutine spawn_patches( currentSite, bc_in)
store_c = currentCohort%prt%GetState(store_organ, carbon12_element)
total_c = sapw_c + struct_c + leaf_c + fnrt_c + store_c
- ! survivorship of plants in both the disturbed and undisturbed cohorts depends on what type of disturbance is happening.
+ ! survivorship of plants in both the disturbed and undisturbed cohorts depends on what type of
+ ! disturbance is happening.
disttype_case: select case(i_disturbance_type)
@@ -1190,7 +1274,7 @@ subroutine spawn_patches( currentSite, bc_in)
newPatch%shortest => nc
nc%shorter => null()
endif
- !nc%patchptr => new_patch
+
call insert_cohort(newPatch, nc, newPatch%tallest, newPatch%shortest, &
tnull, snull, storebigcohort, storesmallcohort)
@@ -1281,6 +1365,296 @@ subroutine spawn_patches( currentSite, bc_in)
end do nocomp_pft_loop
+ nocomp_and_luh_if: if ( hlm_use_nocomp .eq. itrue .and. hlm_use_luh .eq. itrue ) then
+ ! disturbance has just happened, and now the nocomp PFT identities of the newly-disturbed patches
+ ! need to be remapped to those associated with the new land use type.
+
+ ! logic: loop over land use types. figure out the nocomp PFT fractions for all newly-disturbed patches that have become that land use type.
+ ! if the
+
+ lu_loop: do i_land_use_label = n_landuse_cats, 1, -1
+
+ nocomp_pft_area_vector(:) = 0._r8
+ nocomp_pft_area_vector_filled(:) = 0._r8
+
+ currentPatch => currentSite%oldest_patch
+ do while(associated(currentPatch))
+ if (currentPatch%changed_landuse_this_ts .and. currentPatch%land_use_label .eq. i_land_use_label) then
+ nocomp_pft_area_vector(currentPatch%nocomp_pft_label) = nocomp_pft_area_vector(currentPatch%nocomp_pft_label) + currentPatch%area
+ copyPatch => currentPatch
+ end if
+ currentPatch => currentPatch%younger
+ end do
+
+ ! figure out how may PFTs on each land use type. if only 1, then the next calculation is much simpler: we just need to know which PFT is allowed.
+ n_pfts_by_landuse = 0
+ do i_pft = 1,numpft
+ if ( currentSite%area_pft(i_pft,i_land_use_label) .gt. nearzero) then
+ n_pfts_by_landuse = n_pfts_by_landuse + 1
+ which_pft_allowed = i_pft
+ end if
+ end do
+ if ( n_pfts_by_landuse .ne. 1) then
+ which_pft_allowed = fates_unset_int
+ endif
+
+ patch_area_to_reallocate_if: if ( sum(nocomp_pft_area_vector(:)) .gt. nearzero ) then
+ more_than_1_pft_to_handle_if: if ( n_pfts_by_landuse .ne. 1 ) then
+ ! create buffer patch to put all of the pieces carved off of other patches
+ allocate(buffer_patch)
+
+ call buffer_patch%Create(0._r8, 0._r8, i_land_use_label, 0, &
+ num_swb, numpft, currentSite%nlevsoil, hlm_current_tod, &
+ regeneration_model)
+
+ ! Initialize the litter pools to zero
+ do el=1,num_elements
+ call buffer_patch%litter(el)%InitConditions(init_leaf_fines=0._r8, &
+ init_root_fines=0._r8, &
+ init_ag_cwd=0._r8, &
+ init_bg_cwd=0._r8, &
+ init_seed=0._r8, &
+ init_seed_germ=0._r8)
+ end do
+ buffer_patch%tallest => null()
+ buffer_patch%shortest => null()
+
+ call CopyPatchMeansTimers(copyPatch, buffer_patch)
+
+ ! make a note that this buffer patch has not been put into the linked list
+ buffer_patch_in_linked_list = .false.
+ buffer_patch_used = .false.
+
+ currentPatch => currentSite%oldest_patch
+ do while(associated(currentPatch))
+ if (currentPatch%changed_landuse_this_ts .and. currentPatch%land_use_label .eq. i_land_use_label) then
+
+ ! Calculate the areas to be given to potentially give to the buffer patch and those to keep in the current patch
+ area_to_keep = currentSite%area_pft(currentPatch%nocomp_pft_label,i_land_use_label) * sum(nocomp_pft_area_vector(:)) - &
+ nocomp_pft_area_vector_filled(currentPatch%nocomp_pft_label)
+ newp_area = currentPatch%area - area_to_keep
+ fraction_to_keep = area_to_keep / currentPatch%area
+
+ if (fraction_to_keep .le. nearzero .or. area_to_keep .lt. rsnbl_math_prec) then
+ ! we don't want any patch area with this PFT identity at all anymore. Fuse it into the buffer patch.
+
+ currentPatch%nocomp_pft_label = 0
+ if (associated(currentPatch%older)) then
+ previousPatch => currentPatch%older
+ else
+ previousPatch => currentPatch
+ endif
+
+ call fuse_2_patches(currentSite, currentPatch, buffer_patch)
+ currentPatch => previousPatch
+
+ buffer_patch_used = .true.
+
+ elseif ( area_to_keep .ge. rsnbl_math_prec .and. newp_area .ge. rsnbl_math_prec) then
+ ! we have more patch are of this PFT than we want, but we do want to keep some of it.
+ ! we want to split the patch into two here. leave one patch as-is, and put the rest into the buffer patch.
+
+ allocate(temp_patch)
+
+ call split_patch(currentSite, currentPatch, temp_patch, fraction_to_keep, newp_area)
+ !
+ temp_patch%nocomp_pft_label = 0
+
+ call fuse_2_patches(currentSite, temp_patch, buffer_patch)
+ !
+ nocomp_pft_area_vector_filled(currentPatch%nocomp_pft_label) = &
+ nocomp_pft_area_vector_filled(currentPatch%nocomp_pft_label) + currentPatch%area
+
+ currentPatch%changed_landuse_this_ts = .false.
+
+ buffer_patch_used = .true.
+ else
+ ! we want to keep all of this patch (and possibly more)
+
+ nocomp_pft_area_vector_filled(currentPatch%nocomp_pft_label) = &
+ nocomp_pft_area_vector_filled(currentPatch%nocomp_pft_label) + currentPatch%area
+ currentPatch%changed_landuse_this_ts = .false.
+
+ endif
+ end if
+
+ currentPatch => currentPatch%younger
+ end do
+
+ buffer_patch_used_if: if ( buffer_patch_used ) then
+ ! at this point, lets check that the total patch area remaining to be relabelled equals what we think that it is.
+ if (abs(sum(nocomp_pft_area_vector(:) - nocomp_pft_area_vector_filled(:)) - buffer_patch%area) .gt. rsnbl_math_prec) then
+ write(fates_log(),*) 'midway through patch reallocation and things are already not adding up.', i_land_use_label
+ write(fates_log(),*) currentSite%area_pft(:,i_land_use_label)
+ write(fates_log(),*) '-----'
+ write(fates_log(),*) nocomp_pft_area_vector_filled
+ write(fates_log(),*) '-----'
+ write(fates_log(),*) nocomp_pft_area_vector
+ write(fates_log(),*) '-----'
+ write(fates_log(),*) buffer_patch%area, buffer_patch%land_use_label, buffer_patch%nocomp_pft_label
+ write(fates_log(),*) sum(nocomp_pft_area_vector(:)), sum(nocomp_pft_area_vector_filled(:)), buffer_patch%area
+ currentPatch => currentSite%oldest_patch
+ do while(associated(currentPatch))
+ write(fates_log(),*) currentPatch%area, currentPatch%land_use_label, currentPatch%nocomp_pft_label
+ currentPatch => currentPatch%younger
+ end do
+ call dump_site(currentSite)
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+
+ ! It's possible that we only need to move all of the buffer into one patch, so first determine what the new patch areas look
+ ! like and compare to the buffer patch area
+ newp_area_vector(:)= (currentSite%area_pft(:,i_land_use_label) * sum(nocomp_pft_area_vector(:))) - nocomp_pft_area_vector_filled(:)
+ newp_area_buffer_frac(:) = newp_area_vector(:) / buffer_patch%area
+
+ ! Find the maximum value of the vector
+ max_val = maxval(newp_area_buffer_frac)
+
+ ! If the max value is the only value in the array then loop through the array to find the max value pft index and insert buffer
+ if (abs(sum(newp_area_buffer_frac(:)) - max_val) .le. nearzero) then
+ i_pft = 1
+ do while(.not. buffer_patch_in_linked_list)
+ if (abs(newp_area_buffer_frac(i_pft) - max_val) .le. nearzero) then
+
+ ! give the buffer patch the intended nocomp PFT label
+ buffer_patch%nocomp_pft_label = i_pft
+
+ ! track that we have added this patch area
+ nocomp_pft_area_vector_filled(i_pft) = nocomp_pft_area_vector_filled(i_pft) + buffer_patch%area
+
+ ! put the buffer patch directly into the linked list
+ call InsertPatch(currentSite, buffer_patch)
+
+ ! Set flag to skip the next pft loop
+ buffer_patch_in_linked_list = .true.
+ end if
+ i_pft = i_pft + 1
+ end do
+ end if
+
+ ! Now we need to loop through the nocomp PFTs, and split the buffer patch into a set of patches to put back in the linked list
+ ! if not already done so above
+ nocomp_pft_loop_2: do i_pft = 1, numpft
+
+ ! Check the area fraction to makes sure that this pft should have area. Also make sure that the buffer patch hasn't been
+ ! added to the linked list already
+ if ( currentSite%area_pft(i_pft,i_land_use_label) .gt. nearzero .and. .not. buffer_patch_in_linked_list) then
+
+ ! Slightly complicated way of making sure that the same pfts are subtracted from each other which may help to avoid precision
+ ! errors due to differencing between very large and very small areas
+ nocomp_pft_area_vector_alt(:) = nocomp_pft_area_vector(:)
+ nocomp_pft_area_vector_alt(i_pft) = 0._r8
+ newp_area = (currentSite%area_pft(i_pft,i_land_use_label) * nocomp_pft_area_vector(i_pft)) - nocomp_pft_area_vector_filled(i_pft)
+ newp_area = newp_area + sum(currentSite%area_pft(i_pft,i_land_use_label)*nocomp_pft_area_vector_alt(:))
+
+ ! Compute area and fraction to keep in buffer
+ area_to_keep = buffer_patch%area - newp_area
+ fraction_to_keep = area_to_keep / buffer_patch%area
+
+ ! only bother doing this if the new new patch area needed is greater than some tiny amount
+ if ( newp_area .gt. rsnbl_math_prec * 0.01_r8) then
+
+ if (area_to_keep .gt. rsnbl_math_prec) then
+
+ ! split buffer patch in two, keeping the smaller buffer patch to put into new patches
+ allocate(temp_patch)
+
+ call split_patch(currentSite, buffer_patch, temp_patch, fraction_to_keep, newp_area)
+
+ ! give the new patch the intended nocomp PFT label
+ temp_patch%nocomp_pft_label = i_pft
+
+ ! track that we have added this patch area
+ nocomp_pft_area_vector_filled(i_pft) = nocomp_pft_area_vector_filled(i_pft) + temp_patch%area
+
+ ! put the new patch into the linked list
+ call InsertPatch(currentSite, temp_patch)
+
+ else
+ ! give the buffer patch the intended nocomp PFT label
+ buffer_patch%nocomp_pft_label = i_pft
+
+ ! track that we have added this patch area
+ nocomp_pft_area_vector_filled(i_pft) = nocomp_pft_area_vector_filled(i_pft) + buffer_patch%area
+
+ ! put the buffer patch directly into the linked list
+ call InsertPatch(currentSite, buffer_patch)
+
+ buffer_patch_in_linked_list = .true.
+
+ end if
+ end if
+ end if
+ end do nocomp_pft_loop_2
+
+ ! now we want to make sure that either the buffer_patch has zero area (presumably it was never used),
+ ! in which case it should be deallocated, or else it does have area but it has been put into the site
+ ! linked list. if either of those, that means everything worked properly, if not, then something has gone wrong.
+ if ( .not. buffer_patch_in_linked_list) then
+ if (buffer_patch%area .lt. rsnbl_math_prec) then
+ ! here we need to deallocate the buffer patch so that we don't get a memory leak.
+ call buffer_patch%FreeMemory(regeneration_model, numpft)
+ deallocate(buffer_patch, stat=istat, errmsg=smsg)
+ if (istat/=0) then
+ write(fates_log(),*) 'dealloc: fail on deallocate(dp):'//trim(smsg)
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ endif
+ else
+ write(fates_log(),*) 'Buffer patch still has area and it wasnt put into the linked list'
+ write(fates_log(),*) 'buffer_patch%area', buffer_patch%area
+ write(fates_log(),*) sum(nocomp_pft_area_vector_filled(:)), sum(nocomp_pft_area_vector(:))
+ write(fates_log(),*) sum(nocomp_pft_area_vector_filled(:) - nocomp_pft_area_vector(:))
+
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+ end if
+ else
+ ! buffer patch was never even used. deallocate.
+ call buffer_patch%FreeMemory(regeneration_model, numpft)
+ deallocate(buffer_patch, stat=istat, errmsg=smsg)
+ if (istat/=0) then
+ write(fates_log(),*) 'dealloc: fail on deallocate(dp):'//trim(smsg)
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ endif
+ end if buffer_patch_used_if
+
+ ! check that the area we have added is the same as the area we have taken away. if not, crash.
+ if ( abs(sum(nocomp_pft_area_vector_filled(:) - nocomp_pft_area_vector(:))) .gt. rsnbl_math_prec) then
+ write(fates_log(),*) 'patch reallocation logic doesnt add up. difference is: ', sum(nocomp_pft_area_vector_filled(:) - nocomp_pft_area_vector(:))
+ write(fates_log(),*) nocomp_pft_area_vector_filled
+ write(fates_log(),*) nocomp_pft_area_vector
+ write(fates_log(),*) i_land_use_label
+ currentPatch => currentSite%oldest_patch
+ do while(associated(currentPatch))
+ write(fates_log(),*) currentPatch%area, currentPatch%land_use_label, currentPatch%nocomp_pft_label
+ currentPatch => currentPatch%younger
+ end do
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+ else
+ ! if there is only one PFT allowed on this land use type, then all we need to do is relabel all of the patches that just changed
+ ! land use type and let patch fusion take care of the rest.
+ currentPatch => currentSite%oldest_patch
+ do while(associated(currentPatch))
+ if (currentPatch%changed_landuse_this_ts .and. currentPatch%land_use_label .eq. i_land_use_label) then
+ currentPatch%nocomp_pft_label = which_pft_allowed
+ currentPatch%changed_landuse_this_ts = .false.
+ end if
+ currentPatch => currentPatch%younger
+ end do
+ endif more_than_1_pft_to_handle_if
+ end if patch_area_to_reallocate_if
+ call check_patch_area(currentSite)
+ end do lu_loop
+ else
+ ! if not using a configuration where the changed_landuse_this_ts is relevant, loop through all patches and reset it
+ currentPatch => currentSite%oldest_patch
+ do while(associated(currentPatch))
+ currentPatch%changed_landuse_this_ts = .false.
+ currentPatch => currentPatch%younger
+ end do
+ endif nocomp_and_luh_if
+
!zero disturbance rate trackers on all patches
currentPatch => currentSite%oldest_patch
do while(associated(currentPatch))
@@ -1292,6 +1666,130 @@ subroutine spawn_patches( currentSite, bc_in)
return
end subroutine spawn_patches
+ ! -----------------------------------------------------------------------------------------
+
+ subroutine split_patch(currentSite, currentPatch, new_patch, fraction_to_keep, area_to_remove)
+ !
+ ! !DESCRIPTION:
+ ! Split a patch into two patches that are identical except in their areas
+ !
+ ! !ARGUMENTS:
+ type(ed_site_type),intent(inout) :: currentSite
+ type(fates_patch_type) , intent(inout), pointer :: currentPatch ! Donor Patch
+ type(fates_patch_type) , intent(inout), pointer :: new_patch ! New Patch
+ real(r8), intent(in) :: fraction_to_keep ! fraction of currentPatch to keep, the rest goes to newpatch
+ real(r8), intent(in), optional :: area_to_remove ! area of currentPatch to remove, the rest goes to newpatch
+ !
+ ! !LOCAL VARIABLES:
+ integer :: el ! element loop index
+ type (fates_cohort_type), pointer :: nc
+ type (fates_cohort_type), pointer :: storesmallcohort
+ type (fates_cohort_type), pointer :: storebigcohort
+ type (fates_cohort_type), pointer :: currentCohort
+ integer :: tnull ! is there a tallest cohort?
+ integer :: snull ! is there a shortest cohort?
+ integer :: pft
+ real(r8) :: temp_area
+
+ temp_area = 0._r8
+ if (present(area_to_remove)) then
+ temp_area = area_to_remove
+ else
+ temp_area = currentPatch%area - (currentPatch%area * fraction_to_keep)
+ end if
+
+ ! first we need to make the new patch
+ call new_patch%Create(0._r8, temp_area, &
+ currentPatch%land_use_label, currentPatch%nocomp_pft_label, &
+ num_swb, numpft, currentSite%nlevsoil, hlm_current_tod, &
+ regeneration_model)
+
+ ! Initialize the litter pools to zero, these
+ ! pools will be populated shortly
+ do el=1,num_elements
+ call new_patch%litter(el)%InitConditions(init_leaf_fines=0._r8, &
+ init_root_fines=0._r8, &
+ init_ag_cwd=0._r8, &
+ init_bg_cwd=0._r8, &
+ init_seed=0._r8, &
+ init_seed_germ=0._r8)
+ end do
+ new_patch%tallest => null()
+ new_patch%shortest => null()
+
+ call CopyPatchMeansTimers(currentPatch, new_patch)
+
+ call TransLitterNewPatch( currentSite, currentPatch, new_patch, temp_area)
+
+ currentPatch%burnt_frac_litter(:) = 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.
+ ! -------------------------------------------------------------------------
+
+ currentCohort => currentPatch%shortest
+ do while(associated(currentCohort))
+
+ allocate(nc)
+ if(hlm_use_planthydro.eq.itrue) call InitHydrCohort(CurrentSite,nc)
+
+ ! Initialize the PARTEH object and point to the
+ ! correct boundary condition fields
+ nc%prt => null()
+ call InitPRTObject(nc%prt)
+ call nc%InitPRTBoundaryConditions()
+
+ ! (Keeping as an example)
+ ! Allocate running mean functions
+ !allocate(nc%tveg_lpa)
+ !call nc%tveg_lpa%InitRMean(ema_lpa,init_value=new_patch%tveg_lpa%GetMean())
+
+ call nc%ZeroValues()
+
+ ! nc is the new cohort that goes in the disturbed patch (new_patch)... currentCohort
+ ! is the curent cohort that stays in the donor patch (currentPatch)
+ call currentCohort%Copy(nc)
+
+ ! Number of members in the new patch
+ nc%n = currentCohort%n * (1._r8 - fraction_to_keep)
+
+ ! loss of individuals from source patch due to area shrinking
+ currentCohort%n = currentCohort%n * fraction_to_keep
+
+ storebigcohort => new_patch%tallest
+ storesmallcohort => new_patch%shortest
+ if(associated(new_patch%tallest))then
+ tnull = 0
+ else
+ tnull = 1
+ new_patch%tallest => nc
+ nc%taller => null()
+ endif
+
+ if(associated(new_patch%shortest))then
+ snull = 0
+ else
+ snull = 1
+ new_patch%shortest => nc
+ nc%shorter => null()
+ endif
+
+ call insert_cohort(new_patch, nc, new_patch%tallest, new_patch%shortest, &
+ tnull, snull, storebigcohort, storesmallcohort)
+
+ new_patch%tallest => storebigcohort
+ new_patch%shortest => storesmallcohort
+
+ currentCohort => currentCohort%taller
+ enddo ! currentCohort
+
+ call sort_cohorts(currentPatch)
+
+ !update area of donor patch
+ currentPatch%area = currentPatch%area - temp_area
+
+ end subroutine split_patch
+
! ============================================================================
subroutine check_patch_area( currentSite )
@@ -1337,6 +1835,13 @@ subroutine check_patch_area( currentSite )
if ( abs(areatot-area_site) > area_error_fail ) then
write(fates_log(),*) 'Patch areas do not sum to 10000 within tolerance'
write(fates_log(),*) 'Total area: ',areatot,'absolute error: ',areatot-area_site
+
+ currentPatch => currentSite%oldest_patch
+ do while(associated(currentPatch))
+ write(fates_log(),*) 'area, LU, PFT', currentPatch%area, currentPatch%land_use_label, currentPatch%nocomp_pft_label
+ currentPatch => currentPatch%younger
+ end do
+
call endrun(msg=errMsg(sourcefile, __LINE__))
end if
@@ -2184,10 +2689,6 @@ subroutine landusechange_litter_fluxes(currentSite, currentPatch, &
! (note we are accumulating over the patch, but scale is site level)
real(r8) :: woodproduct_mass ! mass that ends up in wood products [kg]
- ! the following two parameters are new to this logic.
- real(r8), parameter :: burn_frac_landusetransition = 0.5_r8 ! what fraction of plant fines are burned during a land use transition?
- real(r8), parameter :: woodproduct_frac_landusetransition = 0.5_r8 ! what fraction of trunk carbon is turned into wood products during a land use transition?
-
!---------------------------------------------------------------------
clear_veg_if: if (clearing_matrix_element) then
@@ -2276,10 +2777,10 @@ subroutine landusechange_litter_fluxes(currentSite, currentPatch, &
! Contribution of dead trees to leaf litter
donatable_mass = num_dead_trees * (leaf_m+repro_m) * &
- (1.0_r8-burn_frac_landusetransition)
+ (1.0_r8-EDPftvarcon_inst%landusechange_frac_burned(pft))
! Contribution of dead trees to leaf burn-flux
- burned_mass = num_dead_trees * (leaf_m+repro_m) * burn_frac_landusetransition
+ burned_mass = num_dead_trees * (leaf_m+repro_m) * EDPftvarcon_inst%landusechange_frac_burned(pft)
do dcmpy=1,ndcmpy
dcmpy_frac = GetDecompyFrac(pft,leaf_organ,dcmpy)
@@ -2309,7 +2810,7 @@ subroutine landusechange_litter_fluxes(currentSite, currentPatch, &
! Track as diagnostic fluxes
flux_diags%leaf_litter_input(pft) = &
flux_diags%leaf_litter_input(pft) + &
- num_dead_trees * (leaf_m+repro_m) * (1.0_r8-burn_frac_landusetransition)
+ num_dead_trees * (leaf_m+repro_m) * (1.0_r8-EDPftvarcon_inst%landusechange_frac_burned(pft))
flux_diags%root_litter_input(pft) = &
flux_diags%root_litter_input(pft) + &
@@ -2345,21 +2846,28 @@ subroutine landusechange_litter_fluxes(currentSite, currentPatch, &
do c = 1,ncwd
donatable_mass = num_dead_trees * SF_val_CWD_frac(c) * bstem
if (c == 1 .or. c == 2) then ! these pools can burn
- donatable_mass = donatable_mass * (1.0_r8-burn_frac_landusetransition)
+ donatable_mass = donatable_mass * (1.0_r8-EDPftvarcon_inst%landusechange_frac_burned(pft))
burned_mass = num_dead_trees * SF_val_CWD_frac(c) * bstem * &
- burn_frac_landusetransition
+ EDPftvarcon_inst%landusechange_frac_burned(pft)
site_mass%burn_flux_to_atm = site_mass%burn_flux_to_atm + burned_mass
- else ! all other pools can end up as timber products but not burn
- donatable_mass = donatable_mass * (1.0_r8-woodproduct_frac_landusetransition)
+ else ! all other pools can end up as timber products or burn or go to litter
+ donatable_mass = donatable_mass * (1.0_r8-EDPftvarcon_inst%landusechange_frac_exported(pft)) * &
+ (1.0_r8-EDPftvarcon_inst%landusechange_frac_burned(pft))
+
+ burned_mass = num_dead_trees * SF_val_CWD_frac(c) * bstem * &
+ (1.0_r8-EDPftvarcon_inst%landusechange_frac_exported(pft)) * &
+ EDPftvarcon_inst%landusechange_frac_burned(pft)
woodproduct_mass = num_dead_trees * SF_val_CWD_frac(c) * bstem * &
- woodproduct_frac_landusetransition
+ EDPftvarcon_inst%landusechange_frac_exported(pft)
+
+ site_mass%burn_flux_to_atm = site_mass%burn_flux_to_atm + burned_mass
trunk_product_site = trunk_product_site + &
woodproduct_mass
- site_mass%wood_product = site_mass%wood_product + &
+ site_mass%wood_product_landusechange(pft) = site_mass%wood_product_landusechange(pft) + &
woodproduct_mass
endif
new_litt%ag_cwd(c) = new_litt%ag_cwd(c) + donatable_mass * donate_m2
@@ -2386,6 +2894,7 @@ subroutine landusechange_litter_fluxes(currentSite, currentPatch, &
end subroutine landusechange_litter_fluxes
! ============================================================================
+
subroutine fuse_patches( csite, bc_in )
!
! !DESCRIPTION:
@@ -2413,6 +2922,8 @@ subroutine fuse_patches( csite, bc_in )
integer :: i_pftlabel !nocomp pft iterator
real(r8) :: primary_land_fraction_beforefusion,primary_land_fraction_afterfusion
integer :: pftlabelmin, pftlabelmax
+ integer :: num_bareground_patches
+ integer :: i
!
!---------------------------------------------------------------------
@@ -2424,20 +2935,29 @@ subroutine fuse_patches( csite, bc_in )
primary_land_fraction_afterfusion = 0._r8
nopatches(1:n_landuse_cats) = 0
-
+ num_bareground_patches = 0
+
currentPatch => currentSite%youngest_patch
do while(associated(currentPatch))
- nopatches(currentPatch%land_use_label) = &
- nopatches(currentPatch%land_use_label) + 1
+ if ( currentPatch%land_use_label .gt. nocomp_bareground_land) then
+ nopatches(currentPatch%land_use_label) = &
+ nopatches(currentPatch%land_use_label) + 1
- if (currentPatch%land_use_label .eq. primaryland) then
- primary_land_fraction_beforefusion = primary_land_fraction_beforefusion + &
- currentPatch%area * AREA_INV
+ if (currentPatch%land_use_label .eq. primaryland) then
+ primary_land_fraction_beforefusion = primary_land_fraction_beforefusion + &
+ currentPatch%area * AREA_INV
+ endif
+ else
+ num_bareground_patches = num_bareground_patches + 1
endif
-
currentPatch => currentPatch%older
enddo
+ if (num_bareground_patches .gt. 1 ) then
+ write(fates_log(),*) 'somehow there is more than one bare ground patch. this shouldnt have happened.'
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ endif
+
pftlabelmin = 0
if ( hlm_use_nocomp .eq. itrue ) then
pftlabelmax = numpft
@@ -2651,6 +3171,17 @@ subroutine fuse_patches( csite, bc_in )
write(fates_log(),*) 'profile tolerance is too big, this shouldnt happen.'
write(fates_log(),*) 'probably this means there are too many distinct categorical '
write(fates_log(),*) 'patch types for the maximum number of patches'
+ call dump_site(currentSite)
+ write(fates_log(),*) 'currentSite%area_bareground', currentSite%area_bareground
+ do i = 1, n_landuse_cats
+ write(fates_log(),*) 'i, currentSite%area_pft(:,i)',i, currentSite%area_pft(:,i)
+ end do
+ tmpptr => currentSite%youngest_patch
+ do while(associated(tmpptr))
+ write(fates_log(),*) tmpptr%area, tmpptr%nocomp_pft_label, tmpptr%land_use_label
+ tmpptr => tmpptr%older
+ end do
+
call endrun(msg=errMsg(sourcefile, __LINE__))
endif
else
@@ -2718,7 +3249,7 @@ subroutine fuse_2_patches(csite, dp, rp)
+ rp%age_since_anthro_disturbance * rp%area) * inv_sum_area
rp%age_class = get_age_class_index(rp%age)
-
+
do el = 1,num_elements
call rp%litter(el)%FuseLitter(rp%area,dp%area,dp%litter(el))
end do
@@ -2848,35 +3379,39 @@ subroutine fuse_2_patches(csite, dp, rp)
call endrun(msg=errMsg(sourcefile, __LINE__))
endif
- if(associated(youngerp))then
- ! Update the younger patch's new older patch (because it isn't dp anymore)
- youngerp%older => olderp
- else
- ! There was no younger patch than dp, so the head of the young order needs
- ! to be set, and it is set as the patch older than dp. That patch
- ! already knows it's older patch (so no need to set or change it)
- csite%youngest_patch => olderp
- olderp%younger => null()
- end if
+ ! if neither youngerp nor olderp are associated, that means that the patch we are fusing into
+ ! is not part of the linked-list structure, and so no further action needs to be taken.
+ if(associated(youngerp) .or. associated(olderp))then
+
+ if(associated(youngerp))then
+ ! Update the younger patch's new older patch (because it isn't dp anymore)
+ youngerp%older => olderp
+ else
+ ! There was no younger patch than dp, so the head of the young order needs
+ ! to be set, and it is set as the patch older than dp. That patch
+ ! already knows it's older patch (so no need to set or change it)
+ csite%youngest_patch => olderp
+ 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
- else
- ! There was no patch older than dp, so the head of the old patch order needs
- ! to be set, and it is set as the patch younger than dp. That patch already
- ! knows it's younger patch, no need to set
- csite%oldest_patch => youngerp
- youngerp%older => null()
- end if
+ if(associated(olderp))then
+ ! Update the older patch's new younger patch (becuase it isn't dp anymore)
+ olderp%younger => youngerp
+ else
+ ! There was no patch older than dp, so the head of the old patch order needs
+ ! to be set, and it is set as the patch younger than dp. That patch already
+ ! knows it's younger patch, no need to set
+ csite%oldest_patch => youngerp
+ youngerp%older => null()
+ end if
+ end if
end subroutine fuse_2_patches
! ============================================================================
- subroutine terminate_patches(currentSite)
+ subroutine terminate_patches(currentSite, bc_in)
!
! !DESCRIPTION:
! Terminate Patches if they are too small
@@ -2884,19 +3419,25 @@ subroutine terminate_patches(currentSite)
!
! !ARGUMENTS:
type(ed_site_type), target, intent(inout) :: currentSite
+ type(bc_in_type), intent(in) :: bc_in
!
! !LOCAL VARIABLES:
type(fates_patch_type), pointer :: currentPatch
type(fates_patch_type), pointer :: olderPatch
type(fates_patch_type), pointer :: youngerPatch
type(fates_patch_type), pointer :: patchpointer
+ type(fates_patch_type), pointer :: largest_patch
integer, parameter :: max_cycles = 10 ! After 10 loops through
! You should had fused
integer :: count_cycles
logical :: gotfused
logical :: current_patch_is_youngest_lutype
+ integer :: i_landuse, i_pft
+ integer :: land_use_type_to_remove
- real(r8) areatot ! variable for checking whether the total patch area is wrong.
+ real(r8) areatot ! variable for checking whether the total patch area is wrong.
+ real(r8) :: state_vector_driver(n_landuse_cats) ! [m2/m2]
+ real(r8) :: state_vector_internal(n_landuse_cats) ! [m2/m2]
!---------------------------------------------------------------------
! Initialize the count cycles
@@ -2928,7 +3469,7 @@ subroutine terminate_patches(currentSite)
if ( .not. gotfused ) then
!! somehow didn't find a patch to fuse with.
write(fates_log(),*) 'Warning. small nocomp patch wasnt able to find another patch to fuse with.', &
- currentPatch%nocomp_pft_label, currentPatch%land_use_label
+ currentPatch%nocomp_pft_label, currentPatch%land_use_label, currentPatch%area
endif
else nocomp_if
@@ -3037,14 +3578,88 @@ subroutine terminate_patches(currentSite)
write(fates_log(),*) 'is very very small. You can test your luck by'
write(fates_log(),*) 'disabling the endrun statement following this message.'
write(fates_log(),*) 'FATES may or may not continue to operate within error'
- write(fates_log(),*) 'tolerances, but will generate another fail if it does not.'
- call endrun(msg=errMsg(sourcefile, __LINE__))
+ write(fates_log(),*) 'tolerances, but will generate another fail if it does not.'
+ write(fates_log(),*) 'otherwise, dumping some diagnostics.'
+ write(fates_log(),*) currentPatch%area, currentPatch%nocomp_pft_label, currentPatch%land_use_label
+ call dump_site(currentSite)
+
+ write(fates_log(),*) 'currentSite%area_bareground', currentSite%area_bareground
+ write(fates_log(),*) 'currentSite%area_pft(:,:)', currentSite%area_pft(:,:)
+ patchpointer => currentSite%youngest_patch
+ do while(associated(patchpointer))
+ write(fates_log(),*) patchpointer%area, patchpointer%nocomp_pft_label, patchpointer%land_use_label
+ patchpointer => patchpointer%older
+ end do
+ state_vector_internal = currentSite%get_current_landuse_statevector()
+ write(fates_log(),*) 'current landuse state vector: ', state_vector_internal
+ write(fates_log(),*) 'current landuse state vector (not including bare gruond): ', state_vector_internal/(1._r8-currentSite%area_bareground)
+ call GetLUHStatedata(bc_in, state_vector_driver)
+ write(fates_log(),*) 'driver data landuse state vector: ', state_vector_driver
+ write(fates_log(),*) 'min_allowed_landuse_fraction: ', currentSite%min_allowed_landuse_fraction
+ write(fates_log(),*) 'landuse_vector_gt_min: ', currentSite%landuse_vector_gt_min
+ do i_landuse = 1, n_landuse_cats
+ write(fates_log(),*) 'trans matrix from: ', i_landuse, currentSite%landuse_transition_matrix(i_landuse,:)
+ end do
+
+ if ( (state_vector_driver(currentPatch%land_use_label) .lt. currentSite%min_allowed_landuse_fraction ) .or. &
+ (state_vector_internal(currentPatch%land_use_label) .lt. currentSite%min_allowed_landuse_fraction ) ) then
+
+ ! try fusing all of the patches with this land use label into the largest patch on the site.
+ land_use_type_to_remove = currentPatch%land_use_label
+
+ write(fates_log(),*) 'removing all patches with land use type ',land_use_type_to_remove
+
+ ! first find the largest patch on the site
+ patchpointer => currentSite%youngest_patch
+ largest_patch => currentSite%youngest_patch
+ do while(associated(patchpointer))
+ if (patchpointer%area .gt. largest_patch%area .and. patchpointer%nocomp_pft_label .ne. nocomp_bareground) then
+ largest_patch => patchpointer
+ endif
+ patchpointer => patchpointer%older
+ end do
+
+ ! now go and fuse all patches that have the land use type we are removing into that patch
+ patchpointer => currentSite%youngest_patch
+ do while(associated(patchpointer))
+ if ( patchpointer%land_use_label .eq. land_use_type_to_remove ) then
+
+ write(fates_log(),*) 'fusing into patch with types, age, and size of:', largest_patch%land_use_label, &
+ largest_patch%nocomp_pft_label, largest_patch%age, largest_patch%area
+
+ write(fates_log(),*) 'fusing away patch with types, age, and size of:', patchpointer%land_use_label, &
+ patchpointer%nocomp_pft_label, patchpointer%age, patchpointer%area
+
+ ! reset the categorical properties of the patch and fuse it into the largest patch
+ patchpointer%land_use_label = largest_patch%land_use_label
+ patchpointer%nocomp_pft_label = largest_patch%nocomp_pft_label
+ patchpointer%age_since_anthro_disturbance = largest_patch%age_since_anthro_disturbance
+ call fuse_2_patches(currentSite, patchpointer, largest_patch)
+
+ ! start over in the loop to make sure we are removing every patch with the targeted land use type
+ patchpointer => currentSite%youngest_patch
+
+ else
+ patchpointer => patchpointer%older
+ endif
+ end do
+
+ write(fates_log(),*) 'resetting currentSite%landuse_vector_gt_min(i) to .false.'
+ ! now reset the allowed land use vector element so that we don't make any more such patches unless they exceed the min area
+ currentSite%landuse_vector_gt_min(land_use_type_to_remove) = .false.
+ count_cycles = 0
+ currentPatch => currentSite%youngest_patch
+ else
+ write(fates_log(),*) 'this isnt because the land use was less than allowed'
+
+ call endrun(msg=errMsg(sourcefile, __LINE__))
- ! Note to user. If you DO decide to remove the end-run above this line
- ! Make sure that you keep the pointer below this line, or you will get
- ! an infinite loop.
- currentPatch => currentPatch%older
- count_cycles = 0
+ ! Note to user. If you DO decide to remove the end-run above this line
+ ! Make sure that you keep the pointer below this line, or you will get
+ ! an infinite loop.
+ currentPatch => currentPatch%older
+ count_cycles = 0
+ endif
end if !count cycles
enddo ! current patch loop
@@ -3175,35 +3790,6 @@ end function countPatches
! =====================================================================================
- subroutine get_frac_site_primary(site_in, frac_site_primary)
-
- !
- ! !DESCRIPTION:
- ! Calculate how much of a site is primary land
- !
- ! !USES:
- use EDTypesMod , only : ed_site_type
- !
- ! !ARGUMENTS:
- type(ed_site_type) , intent(in), target :: site_in
- real(r8) , intent(out) :: frac_site_primary
-
- ! !LOCAL VARIABLES:
- type (fates_patch_type), pointer :: currentPatch
-
- frac_site_primary = 0._r8
- currentPatch => site_in%oldest_patch
- do while (associated(currentPatch))
- if (currentPatch%land_use_label .eq. primaryland) then
- frac_site_primary = frac_site_primary + currentPatch%area * AREA_INV
- endif
- currentPatch => currentPatch%younger
- end do
-
- end subroutine get_frac_site_primary
-
- ! =====================================================================================
-
subroutine InsertPatch(currentSite, newPatch)
! !DESCRIPTION:
@@ -3305,7 +3891,7 @@ subroutine InsertPatch(currentSite, newPatch)
! In the case in which we get to the end of the list and haven't found
! a landuse label match.
- ! If the new patch is primarylands add it to the oldest end of the list
+ ! If the new patch is primaryland add it to the oldest end of the list
if (newPatch%land_use_label .eq. primaryland) then
newPatch%older => null()
newPatch%younger => currentSite%oldest_patch
@@ -3368,4 +3954,36 @@ subroutine InsertPatch(currentSite, newPatch)
end subroutine InsertPatch
+ ! =====================================================================================
+
+ subroutine CopyPatchMeansTimers(dp, rp)
+
+ ! !DESCRIPTION:
+ ! Copy any means or timers from the original patch to the new patch
+ ! These values will inherit all info from the original patch
+ ! --------------------------------------------------------------------------
+ !
+ ! !ARGUMENTS:
+ type (fates_patch_type), intent(in) :: dp ! Donor Patch
+ type (fates_patch_type), intent(inout) :: rp ! Recipient Patch
+
+ ! LOCAL:
+ integer :: ipft ! pft index
+
+ call rp%tveg24%CopyFromDonor(dp%tveg24)
+ call rp%tveg_lpa%CopyFromDonor(dp%tveg_lpa)
+ call rp%tveg_longterm%CopyFromDonor(dp%tveg_longterm)
+
+ if ( regeneration_model == TRS_regeneration ) then
+ call rp%seedling_layer_par24%CopyFromDonor(dp%seedling_layer_par24)
+ call rp%sdlng_mort_par%CopyFromDonor(dp%sdlng_mort_par)
+ call rp%sdlng2sap_par%CopyFromDonor(dp%sdlng2sap_par)
+ do ipft = 1,numpft
+ call rp%sdlng_emerg_smp(ipft)%p%CopyFromDonor(dp%sdlng_emerg_smp(ipft)%p)
+ call rp%sdlng_mdd(ipft)%p%CopyFromDonor(dp%sdlng_mdd(ipft)%p)
+ enddo
+ end if
+
+ end subroutine CopyPatchMeansTimers
+
end module EDPatchDynamicsMod
diff --git a/biogeochem/EDPhysiologyMod.F90 b/biogeochem/EDPhysiologyMod.F90
index e25a722f7a..0f080127c9 100644
--- a/biogeochem/EDPhysiologyMod.F90
+++ b/biogeochem/EDPhysiologyMod.F90
@@ -18,6 +18,7 @@ module EDPhysiologyMod
use FatesInterfaceTypesMod, only : hlm_parteh_mode
use FatesInterfaceTypesMod, only : hlm_use_fixed_biogeog
use FatesInterfaceTypesMod, only : hlm_use_nocomp
+ use EDParamsMod , only : crop_lu_pft_vector
use FatesInterfaceTypesMod, only : hlm_nitrogen_spec
use FatesInterfaceTypesMod, only : hlm_phosphorus_spec
use FatesInterfaceTypesMod, only : hlm_use_tree_damage
@@ -34,6 +35,8 @@ module EDPhysiologyMod
use FatesConstantsMod, only : g_per_kg
use FatesConstantsMod, only : ndays_per_year
use FatesConstantsMod, only : nocomp_bareground
+ use FatesConstantsMod, only : nocomp_bareground_land
+ use FatesConstantsMod, only : is_crop
use FatesConstantsMod, only : area_error_2
use EDPftvarcon , only : EDPftvarcon_inst
use PRTParametersMod , only : prt_params
@@ -140,7 +143,8 @@ module EDPhysiologyMod
use FatesParameterDerivedMod, only : param_derived
use FatesPlantHydraulicsMod, only : InitHydrCohort
use PRTInitParamsFatesMod, only : NewRecruitTotalStoichiometry
-
+ use FatesInterfaceTypesMod , only : hlm_use_luh
+
implicit none
private
@@ -2492,20 +2496,35 @@ subroutine recruitment(currentSite, currentPatch, bc_in)
real(r8) :: seedling_layer_smp ! soil matric potential at seedling rooting depth [mm H2O suction]
integer, parameter :: recruitstatus = 1 ! whether the newly created cohorts are recruited or initialized
integer :: ilayer_seedling_root ! the soil layer at seedling rooting depth
-
+ logical :: use_this_pft ! logical flag for whether or not to allow a given PFT to recruit
!---------------------------------------------------------------------------
do ft = 1, numpft
- ! The following if block is for the prescribed biogeography and/or nocomp modes.
- ! Since currentSite%use_this_pft is a site-level quantity and thus only limits whether a given PFT
- ! is permitted on a given gridcell or not, it applies to the prescribed biogeography case only.
- ! If nocomp is enabled, then we must determine whether a given PFT is allowed on a given patch or not.
+ ! The following if block is for the prescribed biogeography and/or nocomp modes and/or crop land use types
+ ! Since currentSite%use_this_pft is a site-level quantity and thus only limits whether a given PFT
+ ! is permitted on a given gridcell or not, it applies to the prescribed biogeography case only.
+ ! If nocomp is enabled, then we must determine whether a given PFT is allowed on a given patch or not.
+ ! Whether or not nocomp or prescribed biogeography is enabled, if land use change is enabled, then we only want to
+ ! allow crop PFTs on patches with crop land use types
+
+ use_this_pft = .false.
+ if(currentSite%use_this_pft(ft).eq.itrue &
+ .and. ((hlm_use_nocomp .eq. ifalse) .or. (ft .eq. currentPatch%nocomp_pft_label)))then
+ use_this_pft = .true.
+ end if
- if (currentSite%use_this_pft(ft) .eq. itrue .and. &
- ((hlm_use_nocomp .eq. ifalse) .or. &
- (ft .eq. currentPatch%nocomp_pft_label))) then
+ if ( currentPatch%land_use_label .ne. nocomp_bareground_land ) then ! cdk
+ if ((hlm_use_luh .eq. itrue) .and. (is_crop(currentPatch%land_use_label))) then
+ if ( crop_lu_pft_vector(currentPatch%land_use_label) .eq. ft ) then
+ use_this_pft = .true.
+ else
+ use_this_pft = .false.
+ end if
+ end if
+ endif
+ use_this_pft_if: if(use_this_pft) then
height = EDPftvarcon_inst%hgt_min(ft)
stem_drop_fraction = prt_params%phen_stem_drop_fraction(ft)
fnrt_drop_fraction = prt_params%phen_fnrt_drop_fraction(ft)
@@ -2749,11 +2768,11 @@ subroutine recruitment(currentSite, currentPatch, bc_in)
currentSite%recruitment_rate(ft) = currentSite%recruitment_rate(ft) + cohort_n
endif any_recruits
- endif !use_this_pft
+ endif use_this_pft_if
enddo !pft loop
end subroutine recruitment
- ! ======================================================================================
+ ! ======================================================================================
subroutine CWDInput( currentSite, currentPatch, litt, bc_in)
@@ -3022,7 +3041,7 @@ subroutine CWDInput( currentSite, currentPatch, litt, bc_in)
SF_val_CWD_frac_adj(c) * dead_n_dlogging * &
prt_params%allom_agb_frac(pft)
- site_mass%wood_product = site_mass%wood_product + &
+ site_mass%wood_product_harvest(pft) = site_mass%wood_product_harvest(pft) + &
trunk_wood * currentPatch%area * logging_export_frac
! Add AG wood to litter from the non-exported fraction of wood
diff --git a/biogeochem/FatesAllometryMod.F90 b/biogeochem/FatesAllometryMod.F90
index d0957ef190..2339df3b1a 100644
--- a/biogeochem/FatesAllometryMod.F90
+++ b/biogeochem/FatesAllometryMod.F90
@@ -1050,21 +1050,11 @@ subroutine bsap_allom(d,ipft,crowndamage,canopy_trim,elongf_stem, sapw_area,bsap
call bbgw_allom(d,ipft, elongf_stem,bbgw,dbbgwdd)
bsap = bagw + bbgw
- ! replicate the crown damage code
- ! Do we really need this for grass? I would think this can be helpful for
- ! grazing in the future. --XLG
- if(crowndamage > 1)then
- call GetCrownReduction(crowndamage, crown_reduction)
- bsap = elongf_stem * (bsap - (bsap * agb_frac * branch_frac * crown_reduction))
- if(present(dbsapdd))then
- dbsapdd = elongf_stem * &
- (dbagwdd + dbbgwdd - ((dbagwdd + dbbgwdd) * agb_frac * branch_frac * crown_reduction))
- end if
- else
- bsap = elongf_stem * bsap
- if (present(dbsapdd))then
- dbsapdd = elongf_stem * (dbagwdd + dbbgwdd)
- end if
+ ! This is a grass-only functionnal type, no need to run crown-damage effects
+
+ bsap = elongf_stem * bsap
+ if (present(dbsapdd))then
+ dbsapdd = elongf_stem * (dbagwdd + dbbgwdd)
end if
if(present(dbsapdd))then
diff --git a/biogeochem/FatesLandUseChangeMod.F90 b/biogeochem/FatesLandUseChangeMod.F90
index 1999cfabc7..5873da97eb 100644
--- a/biogeochem/FatesLandUseChangeMod.F90
+++ b/biogeochem/FatesLandUseChangeMod.F90
@@ -15,6 +15,7 @@ module FatesLandUseChangeMod
use FatesInterfaceTypesMod , only : hlm_use_luh
use FatesInterfaceTypesMod , only : hlm_num_luh2_states
use FatesInterfaceTypesMod , only : hlm_num_luh2_transitions
+ use FatesInterfaceTypesMod , only : hlm_use_potentialveg
use FatesUtilsMod , only : FindIndex
use EDTypesMod , only : area_site => area
@@ -27,10 +28,11 @@ module FatesLandUseChangeMod
character(len=*), parameter :: sourcefile = __FILE__
- public :: get_landuse_transition_rates
- public :: get_landusechange_rules
- public :: get_luh_statedata
-
+ public :: GetLanduseTransitionRates
+ public :: GetLanduseChangeRules
+ public :: GetLUHStatedata
+ public :: GetInitLanduseTransitionRates
+ public :: GetInitLanduseHarvestRate
! module data
integer, parameter :: max_luh2_types_per_fates_lu_type = 5
@@ -60,16 +62,19 @@ module FatesLandUseChangeMod
contains
! ============================================================================
- subroutine get_landuse_transition_rates(bc_in, landuse_transition_matrix)
+ subroutine GetLanduseTransitionRates(bc_in, min_allowed_landuse_fraction, landuse_transition_matrix, &
+ landuse_vector_gt_min)
- ! The purpose of this routine is to ingest the land use transition rate information that the host model has read in from a dataset,
- ! aggregate land use types to those being used in the simulation, and output a transition matrix that can be used to drive patch
- ! disturbance rates.
+ ! The purpose of this routine is to ingest the land use transition rate information that the host
+ ! model has read in from a dataset,aggregate land use types to those being used in the simulation,
+ ! and output a transition matrix that can be used to drive patch disturbance rates.
! !ARGUMENTS:
type(bc_in_type) , intent(in) :: bc_in
- real(r8), intent(inout) :: landuse_transition_matrix(n_landuse_cats, n_landuse_cats) ! [m2/m2/day]
+ real(r8), intent(in) :: min_allowed_landuse_fraction
+ real(r8), intent(inout) :: landuse_transition_matrix(n_landuse_cats, n_landuse_cats) ! [m2/m2/day]
+ logical, intent(inout) :: landuse_vector_gt_min(n_landuse_cats)
! !LOCAL VARIABLES:
type(luh2_fates_lutype_map) :: lumap
@@ -79,42 +84,67 @@ subroutine get_landuse_transition_rates(bc_in, landuse_transition_matrix)
real(r8) :: urban_fraction
real(r8) :: temp_vector(hlm_num_luh2_transitions)
logical :: modified_flag
+ real(r8) :: state_vector(n_landuse_cats) ! [m2/m2]
+ integer :: i_lu
! zero the transition matrix and the urban fraction
landuse_transition_matrix(:,:) = 0._r8
urban_fraction = 0._r8
- ! Check the LUH data incoming to see if any of the transitions are NaN
- temp_vector = bc_in%hlm_luh_transitions
- call CheckLUHData(temp_vector,modified_flag)
- if (.not. modified_flag) then
- ! identify urban fraction so that it can be factored into the land use state output
- urban_fraction = bc_in%hlm_luh_states(FindIndex(bc_in%hlm_luh_state_names,'urban'))
- end if
-
- !!TODO: may need some logic here to ask whether or not ot perform land use change on this timestep. current code occurs every day.
- !!If not doing transition every day, need to update units.
-
- transitions_loop: do i_luh2_transitions = 1, hlm_num_luh2_transitions
-
- ! transition names are written in form xxxxx_to_yyyyy where x and y are donor and receiver state names
- transition_name = bc_in%hlm_luh_transition_names(i_luh2_transitions)
- donor_name = transition_name(1:5)
- receiver_name = transition_name(10:14)
-
- ! Get the fates land use type index associated with the luh2 state types
- i_donor= lumap%GetIndex(donor_name)
- i_receiver = lumap%GetIndex(receiver_name)
-
- ! Avoid transitions with 'urban' as those are handled seperately
- if (.not.(i_donor .eq. fates_unset_int .or. i_receiver .eq. fates_unset_int)) then
- landuse_transition_matrix(i_donor,i_receiver) = &
- landuse_transition_matrix(i_donor,i_receiver) + temp_vector(i_luh2_transitions) * years_per_day / (1._r8 - urban_fraction)
+ ! if we are using potential veg only, then keep all transitions equal to zero.
+ if (hlm_use_potentialveg .eq. ifalse) then
+ ! Check the LUH data incoming to see if any of the transitions are NaN
+ temp_vector = bc_in%hlm_luh_transitions
+ call CheckLUHData(temp_vector,modified_flag)
+ if (.not. modified_flag) then
+ ! identify urban fraction so that it can be factored into the land use state output
+ urban_fraction = bc_in%hlm_luh_states(FindIndex(bc_in%hlm_luh_state_names,'urban'))
end if
- end do transitions_loop
- end subroutine get_landuse_transition_rates
+ !! TODO: may need some logic here to ask whether or not ot perform land use change on this timestep.
+ !! current code occurs every day. If not doing transition every day, need to update units.
+
+ transitions_loop: do i_luh2_transitions = 1, hlm_num_luh2_transitions
+
+ ! transition names are written in form xxxxx_to_yyyyy where x and y are donor and receiver state names
+ transition_name = bc_in%hlm_luh_transition_names(i_luh2_transitions)
+ donor_name = transition_name(1:5)
+ receiver_name = transition_name(10:14)
+
+ ! Get the fates land use type index associated with the luh2 state types
+ i_donor= lumap%GetIndex(donor_name)
+ i_receiver = lumap%GetIndex(receiver_name)
+
+ ! Avoid transitions with 'urban' as those are handled seperately
+ ! Also ignore diagonal elements of transition matrix.
+ if (.not.(i_donor .eq. fates_unset_int .or. i_receiver .eq. fates_unset_int .or. &
+ i_donor .eq. i_receiver)) then
+ landuse_transition_matrix(i_donor,i_receiver) = &
+ landuse_transition_matrix(i_donor,i_receiver) + temp_vector(i_luh2_transitions) &
+ * years_per_day / (1._r8 - urban_fraction)
+
+ end if
+ end do transitions_loop
+
+ ! zero all transitions where the receiving land use type state vector is less than the minimum allowed,
+ ! and otherwise if this is the first timestep where the minimum was exceeded,
+ ! then apply all transitions from primary to this type and reset the flag
+ ! note that the flag resetting should not happen for secondary lands, as this is handled in the
+ ! logging logic
+ call GetLUHStatedata(bc_in, state_vector)
+ do i_lu = secondaryland, n_landuse_cats
+ if ( state_vector(i_lu) .le. min_allowed_landuse_fraction ) then
+ landuse_transition_matrix(:,i_lu) = 0._r8
+ else if ((.not. landuse_vector_gt_min(i_lu)) .and. (i_lu .ne. secondaryland)) then
+ landuse_transition_matrix(:,i_lu) = 0._r8
+ landuse_transition_matrix(primaryland,i_lu) = state_vector(i_lu)
+ landuse_vector_gt_min(i_lu) = .true.
+ end if
+ end do
+ end if
+
+ end subroutine GetLanduseTransitionRates
!----------------------------------------------------------------------------------------------------
@@ -142,18 +172,20 @@ end function GetLUCategoryFromStateName
!----------------------------------------------------------------------------------------------------
- subroutine get_landusechange_rules(clearing_matrix)
+ subroutine GetLanduseChangeRules(clearing_matrix)
- ! the purpose of this is to define a ruleset for when to clear the vegetation in transitioning from one land use type to another
+ ! the purpose of this is to define a ruleset for when to clear the vegetation in transitioning
+ ! from one land use type to another
logical, intent(out) :: clearing_matrix(n_landuse_cats,n_landuse_cats)
- ! default value of ruleset 4 above means that plants are not cleared during land use change transitions to rangeland, whereas plants are
- ! cleared in transitions to pasturelands and croplands.
- integer, parameter :: ruleset = 4 ! ruleset to apply from table 1 of Ma et al (2020) https://doi.org/10.5194/gmd-13-3203-2020
+ ! default value of ruleset 4 above means that plants are not cleared during land use change
+ ! transitions to rangeland, whereas plants are cleared in transitions to pasturelands and croplands.
+ integer, parameter :: ruleset = 4 ! ruleset to apply from table 1 of Ma et al (2020)
+ ! https://doi.org/10.5194/gmd-13-3203-2020
- ! clearing matrix applies from the donor to the receiver land use type of the newly-transferred patch area
- ! values of clearing matrix: false => do not clear; true => clear
+ ! clearing matrix applies from the donor to the receiver land use type of the newly-transferred
+ ! patch area values of clearing matrix: false => do not clear; true => clear
clearing_matrix(:,:) = .false.
@@ -161,8 +193,10 @@ subroutine get_landusechange_rules(clearing_matrix)
case(1)
- ! note that this ruleset isnt exactly what is in Ma et al. rulesets 1 and 2, because FATES does not make the distinction
- ! between forested and non-forested lands from a land use/land cover perspective.
+ ! note that this ruleset isnt exactly what is in Ma et al. rulesets 1 and 2, because FATES
+ ! does not make the distinction between forested and non-forested lands from a land use/land
+ ! cover perspective.
+
clearing_matrix(:,cropland) = .true.
clearing_matrix(:,pastureland) = .true.
clearing_matrix(primaryland,rangeland) = .true.
@@ -227,11 +261,11 @@ subroutine get_landusechange_rules(clearing_matrix)
end select
- end subroutine get_landusechange_rules
+ end subroutine GetLanduseChangeRules
!----------------------------------------------------------------------------------------------------
- subroutine get_luh_statedata(bc_in, state_vector)
+ subroutine GetLUHStatedata(bc_in, state_vector)
type(bc_in_type) , intent(in) :: bc_in
real(r8), intent(out) :: state_vector(n_landuse_cats) ! [m2/m2]
@@ -249,36 +283,45 @@ subroutine get_luh_statedata(bc_in, state_vector)
state_vector(:) = 0._r8
urban_fraction = 0._r8
- ! Check to see if the incoming state vector is NaN.
- temp_vector = bc_in%hlm_luh_states
- call CheckLUHData(temp_vector,modified_flag)
- if (.not. modified_flag) then
- ! identify urban fraction so that it can be factored into the land use state output
- urban_fraction = bc_in%hlm_luh_states(FindIndex(bc_in%hlm_luh_state_names,'urban'))
- end if
-
- ! loop over all states and add up the ones that correspond to a given fates land use type
- do i_luh2_states = 1, hlm_num_luh2_states
-
- ! Get the luh2 state name and determine fates aggregated land use
- ! type index from the state to lutype map
- state_name = bc_in%hlm_luh_state_names(i_luh2_states)
- ii = lumap%GetIndex(state_name)
-
- ! Avoid 'urban' states whose indices have been given unset values
- if (ii .ne. fates_unset_int) then
- state_vector(ii) = state_vector(ii) + &
- temp_vector(i_luh2_states) / (1._r8 - urban_fraction)
+ if (hlm_use_potentialveg .eq. itrue) then
+ state_vector(primaryland) = 1._r8
+ else
+ ! Check to see if the incoming state vector is NaN.
+ temp_vector = bc_in%hlm_luh_states
+ call CheckLUHData(temp_vector,modified_flag)
+ if (.not. modified_flag) then
+ ! identify urban fraction so that it can be factored into the land use state output
+ urban_fraction = bc_in%hlm_luh_states(FindIndex(bc_in%hlm_luh_state_names,'urban'))
end if
- end do
- ! check to ensure total area == 1, and correct if not
- if ( abs(sum(state_vector(:)) - 1._r8) .gt. nearzero ) then
- write(fates_log(),*) 'warning: sum(state_vector) = ', sum(state_vector(:))
- state_vector = state_vector / sum(state_vector)
+ ! loop over all states and add up the ones that correspond to a given fates land use type
+ do i_luh2_states = 1, hlm_num_luh2_states
+
+ ! Get the luh2 state name and determine fates aggregated land use
+ ! type index from the state to lutype map
+ state_name = bc_in%hlm_luh_state_names(i_luh2_states)
+ ii = lumap%GetIndex(state_name)
+
+ ! Avoid 'urban' states whose indices have been given unset values
+ if (ii .ne. fates_unset_int) then
+ state_vector(ii) = state_vector(ii) + &
+ temp_vector(i_luh2_states) / (1._r8 - urban_fraction)
+ end if
+ end do
+
+ ! if all zeros, make all primary lands
+ if ( sum(state_vector(:)) .gt. nearzero ) then
+
+ ! check to ensure total area == 1, and correct if not
+ if ( abs(sum(state_vector(:)) - 1._r8) .gt. nearzero ) then
+ state_vector(:) = state_vector(:) / sum(state_vector(:))
+ end if
+ else
+ state_vector(primaryland) = 1._r8
+ endif
end if
- end subroutine get_luh_statedata
+ end subroutine GetLUHStatedata
!----------------------------------------------------------------------------------------------------
@@ -291,8 +334,8 @@ subroutine CheckLUHData(luh_vector,modified_flag)
! Check to see if the incoming luh2 vector is NaN.
! This suggests that there is a discepency where the HLM and LUH2 states
- ! there is vegetated ground. E.g. LUH2 data is missing for glacier-margin regions such as Antarctica.
- ! In this case, states should be Nan. If so,
+ ! there is vegetated ground. E.g. LUH2 data is missing for glacier-margin
+ ! regions such as Antarctica. In this case, states should be Nan. If so,
! set the current state to be all primary forest, and all transitions to be zero.
! If only a portion of the vector is NaN, there is something amiss with
! the data, so end the run.
@@ -305,7 +348,8 @@ subroutine CheckLUHData(luh_vector,modified_flag)
luh_vector(primaryland) = 1._r8
end if
modified_flag = .true.
- !write(fates_log(),*) 'WARNING: land use state is all NaN; setting state as all primary forest.' ! GL DIAG
+ !write(fates_log(),*) 'WARNING: land use state is all NaN;
+ !setting state as all primary forest.' ! GL DIAG
else if (any(isnan(luh_vector))) then
if (any(.not. isnan(luh_vector))) then
write(fates_log(),*) 'ERROR: land use vector has NaN'
@@ -315,4 +359,69 @@ subroutine CheckLUHData(luh_vector,modified_flag)
end subroutine CheckLUHData
+
+ subroutine GetInitLanduseHarvestRate(bc_in, min_allowed_landuse_fraction, harvest_rate, &
+ landuse_vector_gt_min)
+
+ ! the purpose of this subroutine is, only under the case where we are transitioning from a spinup
+ ! run that did not have land use to a run that does, to apply the land-use changes needed to get
+ ! to the state vector in a single daily instance. this is for the hrvest rate from primary lands,
+ ! i.e. the transition from primary to secondary lands. thus instead of using the harvest dataset
+ ! itself, it only uses the state vector for what land use compositoin we want to achieve, and log
+ ! the forests accordingly.
+
+ ! !ARGUMENTS:
+ type(bc_in_type) , intent(in) :: bc_in
+ real(r8), intent(in) :: min_allowed_landuse_fraction
+ real(r8), intent(out) :: harvest_rate ! [m2/ m2 / day]
+ logical, intent(inout) :: landuse_vector_gt_min(n_landuse_cats)
+
+ ! LOCALS
+ real(r8) :: state_vector(n_landuse_cats) ! [m2/m2]
+
+ call GetLUHStatedata(bc_in, state_vector)
+
+ ! only do this if the state vector exceeds the minimum viable patch size, and if so, note that in the
+ ! landuse_vector_gt_min flag (which will be coming in as .false. because of the use_potentialveg logic).
+ if ( state_vector(secondaryland) .gt. min_allowed_landuse_fraction) then
+ harvest_rate = state_vector(secondaryland)
+ landuse_vector_gt_min(secondaryland) = .true.
+ endif
+
+ end subroutine GetInitLanduseHarvestRate
+
+ subroutine GetInitLanduseTransitionRates(bc_in, min_allowed_landuse_fraction, &
+ landuse_transition_matrix, landuse_vector_gt_min)
+
+ ! The purpose of this subroutine is, only under the case where we are transitioning from a spinup
+ ! run that did not have land use to a run that does, to apply the land-use changes needed to get
+ ! to the state vector in a single daily instance. This is for the transitions other than harvest,
+ ! i.e. from primary lands to all other categories aside from secondary lands.
+
+ ! !ARGUMENTS:
+ type(bc_in_type) , intent(in) :: bc_in
+ real(r8), intent(in) :: min_allowed_landuse_fraction
+ real(r8), intent(inout) :: landuse_transition_matrix(n_landuse_cats, n_landuse_cats) ! [m2/m2/day]
+ logical, intent(inout) :: landuse_vector_gt_min(n_landuse_cats)
+
+ ! LOCALS
+ real(r8) :: state_vector(n_landuse_cats) ! [m2/m2]
+ integer :: i
+
+ landuse_transition_matrix(:,:) = 0._r8
+
+ call GetLUHStatedata(bc_in, state_vector)
+
+ ! only do this if the state vector exceeds the minimum viable patch size, and if so, note that
+ ! in the landuse_vector_gt_min flag (which will be coming in as .false. because of the
+ ! use_potentialveg logic).
+ do i = secondaryland+1,n_landuse_cats
+ if ( state_vector(i) .gt. min_allowed_landuse_fraction) then
+ landuse_transition_matrix(primaryland,i) = state_vector(i)
+ landuse_vector_gt_min(i) = .true.
+ end if
+ end do
+
+ end subroutine GetInitLanduseTransitionRates
+
end module FatesLandUseChangeMod
diff --git a/biogeochem/FatesPatchMod.F90 b/biogeochem/FatesPatchMod.F90
index d86e5c5d51..b5489f6e82 100644
--- a/biogeochem/FatesPatchMod.F90
+++ b/biogeochem/FatesPatchMod.F90
@@ -64,6 +64,7 @@ module FatesPatchMod
integer :: ncl_p ! number of occupied canopy layers
integer :: land_use_label ! patch label for land use classification (primaryland, secondaryland, etc)
real(r8) :: age_since_anthro_disturbance ! average age for secondary forest since last anthropogenic disturbance [years]
+ logical :: changed_landuse_this_ts ! logical flag to track patches that have just undergone land use change [only used with nocomp and land use change]
!---------------------------------------------------------------------------
@@ -558,7 +559,7 @@ end subroutine InitLitter
!===========================================================================
- subroutine Create(this, age, area, label, nocomp_pft, num_swb, num_pft, &
+ subroutine Create(this, age, area, land_use_label, nocomp_pft, num_swb, num_pft, &
num_levsoil, current_tod, regeneration_model)
!
! DESCRIPTION:
@@ -569,7 +570,7 @@ subroutine Create(this, age, area, label, nocomp_pft, num_swb, num_pft, &
class(fates_patch_type), intent(inout) :: this ! patch object
real(r8), intent(in) :: age ! notional age of this patch in years
real(r8), intent(in) :: area ! initial area of this patch in m2.
- integer, intent(in) :: label ! anthropogenic disturbance label
+ integer, intent(in) :: land_use_label ! land use label
integer, intent(in) :: nocomp_pft ! no-competition mode pft label
integer, intent(in) :: num_swb ! number of shortwave broad-bands to track
integer, intent(in) :: num_pft ! number of pfts to simulate
@@ -597,8 +598,8 @@ subroutine Create(this, age, area, label, nocomp_pft, num_swb, num_pft, &
this%area = area
! assign anthropgenic disturbance category and label
- this%land_use_label = label
- if (label .eq. secondaryland) then
+ this%land_use_label = land_use_label
+ if (land_use_label .eq. secondaryland) then
this%age_since_anthro_disturbance = age
else
this%age_since_anthro_disturbance = fates_unset_r8
@@ -609,6 +610,8 @@ subroutine Create(this, age, area, label, nocomp_pft, num_swb, num_pft, &
this%tr_soil_dif(:) = 1.0_r8
this%NCL_p = 1
+ this%changed_landuse_this_ts = .false.
+
end subroutine Create
!===========================================================================
diff --git a/biogeochem/FatesSoilBGCFluxMod.F90 b/biogeochem/FatesSoilBGCFluxMod.F90
index 9d813c32b3..2c5b7d9b18 100644
--- a/biogeochem/FatesSoilBGCFluxMod.F90
+++ b/biogeochem/FatesSoilBGCFluxMod.F90
@@ -69,6 +69,7 @@ module FatesSoilBGCFluxMod
use FatesConstantsMod, only : sec_per_day
use FatesConstantsMod, only : years_per_day
use FatesConstantsMod, only : itrue
+ use FatesConstantsMod, only : nocomp_bareground
use FatesLitterMod, only : litter_type
use FatesLitterMod , only : ncwd
use FatesLitterMod , only : ndcmpy
@@ -287,107 +288,107 @@ subroutine PrepCH4BCs(csite,bc_in,bc_out)
fp = 0
cpatch => csite%oldest_patch
do while (associated(cpatch))
-
- ! Patch ordering when passing boundary conditions
- ! always goes from oldest to youngest, following
- ! the convention of EDPatchDynamics::set_patchno()
-
- fp = fp + 1
-
- agnpp = 0._r8
- bgnpp = 0._r8
- woody_area = 0._r8
- plant_area = 0._r8
-
- ccohort => cpatch%tallest
- do while (associated(ccohort))
+ if_notbare: if(cpatch%nocomp_pft_label .ne. nocomp_bareground)then
+ ! Patch ordering when passing boundary conditions
+ ! always goes from oldest to youngest, following
+ ! the convention of EDPatchDynamics::set_patchno()
- ! For consistency, only apply calculations to non-new
- ! cohorts. New cohorts will not have respiration rates
- ! at this point in the call sequence.
+ fp = fp + 1
- if(.not.ccohort%isnew) then
-
- pft = ccohort%pft
-
- call set_root_fraction(csite%rootfrac_scr, pft, csite%zi_soil, &
- bc_in%max_rooting_depth_index_col )
-
- fnrt_c = ccohort%prt%GetState(fnrt_organ, carbon12_element)
-
- ! [kgC/day]
- sapw_net_alloc = ccohort%prt%GetNetAlloc(sapw_organ, carbon12_element) * days_per_sec
- store_net_alloc = ccohort%prt%GetNetAlloc(store_organ, carbon12_element) * days_per_sec
- leaf_net_alloc = ccohort%prt%GetNetAlloc(leaf_organ, carbon12_element) * days_per_sec
- fnrt_net_alloc = ccohort%prt%GetNetAlloc(fnrt_organ, carbon12_element) * days_per_sec
- struct_net_alloc = ccohort%prt%GetNetAlloc(struct_organ, carbon12_element) * days_per_sec
- repro_net_alloc = ccohort%prt%GetNetAlloc(repro_organ, carbon12_element) * days_per_sec
-
- ! [kgC/plant/day] -> [gC/m2/s]
- agnpp = agnpp + ccohort%n/cpatch%area * (leaf_net_alloc + repro_net_alloc + &
- prt_params%allom_agb_frac(pft)*(sapw_net_alloc+store_net_alloc+struct_net_alloc)) * g_per_kg
+ agnpp = 0._r8
+ bgnpp = 0._r8
+ woody_area = 0._r8
+ plant_area = 0._r8
+
+ ccohort => cpatch%tallest
+ do while (associated(ccohort))
- ! [kgC/plant/day] -> [gC/m2/s]
- bgnpp = bgnpp + ccohort%n/cpatch%area * (fnrt_net_alloc + &
- (1._r8-prt_params%allom_agb_frac(pft))*(sapw_net_alloc+store_net_alloc+struct_net_alloc)) * g_per_kg
+ ! For consistency, only apply calculations to non-new
+ ! cohorts. New cohorts will not have respiration rates
+ ! at this point in the call sequence.
- if(hlm_use_ch4==itrue)then
+ if(.not.ccohort%isnew) then
- ! Fine root fraction over depth
- bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) = &
- bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) + &
- csite%rootfrac_scr(1:bc_in%nlevsoil)
+ pft = ccohort%pft
+
+ call set_root_fraction(csite%rootfrac_scr, pft, csite%zi_soil, &
+ bc_in%max_rooting_depth_index_col )
+
+ fnrt_c = ccohort%prt%GetState(fnrt_organ, carbon12_element)
+
+ ! [kgC/day]
+ sapw_net_alloc = ccohort%prt%GetNetAlloc(sapw_organ, carbon12_element) * days_per_sec
+ store_net_alloc = ccohort%prt%GetNetAlloc(store_organ, carbon12_element) * days_per_sec
+ leaf_net_alloc = ccohort%prt%GetNetAlloc(leaf_organ, carbon12_element) * days_per_sec
+ fnrt_net_alloc = ccohort%prt%GetNetAlloc(fnrt_organ, carbon12_element) * days_per_sec
+ struct_net_alloc = ccohort%prt%GetNetAlloc(struct_organ, carbon12_element) * days_per_sec
+ repro_net_alloc = ccohort%prt%GetNetAlloc(repro_organ, carbon12_element) * days_per_sec
+
+ ! [kgC/plant/day] -> [gC/m2/s]
+ agnpp = agnpp + ccohort%n/cpatch%area * (leaf_net_alloc + repro_net_alloc + &
+ prt_params%allom_agb_frac(pft)*(sapw_net_alloc+store_net_alloc+struct_net_alloc)) * g_per_kg
+
+ ! [kgC/plant/day] -> [gC/m2/s]
+ bgnpp = bgnpp + ccohort%n/cpatch%area * (fnrt_net_alloc + &
+ (1._r8-prt_params%allom_agb_frac(pft))*(sapw_net_alloc+store_net_alloc+struct_net_alloc)) * g_per_kg
- ! Fine root carbon, convert [kg/plant] -> [g/m2]
- bc_out%frootc_pa(fp) = &
- bc_out%frootc_pa(fp) + &
- fnrt_c*ccohort%n/cpatch%area * g_per_kg
+ if(hlm_use_ch4==itrue)then
+
+ ! Fine root fraction over depth
+ bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) = &
+ bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) + &
+ csite%rootfrac_scr(1:bc_in%nlevsoil)
+
+ ! Fine root carbon, convert [kg/plant] -> [g/m2]
+ bc_out%frootc_pa(fp) = &
+ bc_out%frootc_pa(fp) + &
+ fnrt_c*ccohort%n/cpatch%area * g_per_kg
+
+ ! (gC/m2/s) root respiration (fine root MR + total root GR)
+ ! RGK: We do not save root respiration and average over the day. Until we do
+ ! this is a best (bad) guess at fine root MR + total root GR
+ ! (kgC/indiv/yr) -> gC/m2/s
+ bc_out%root_resp(1:bc_in%nlevsoil) = bc_out%root_resp(1:bc_in%nlevsoil) + &
+ ccohort%resp_acc_hold*years_per_day*g_per_kg*days_per_sec* &
+ ccohort%n*area_inv*(1._r8-prt_params%allom_agb_frac(pft)) * csite%rootfrac_scr(1:bc_in%nlevsoil)
+
+ end if
+
+ if( prt_params%woody(pft)==itrue ) then
+ woody_area = woody_area + ccohort%c_area
+ end if
+ plant_area = plant_area + ccohort%c_area
- ! (gC/m2/s) root respiration (fine root MR + total root GR)
- ! RGK: We do not save root respiration and average over the day. Until we do
- ! this is a best (bad) guess at fine root MR + total root GR
- ! (kgC/indiv/yr) -> gC/m2/s
- bc_out%root_resp(1:bc_in%nlevsoil) = bc_out%root_resp(1:bc_in%nlevsoil) + &
- ccohort%resp_acc_hold*years_per_day*g_per_kg*days_per_sec* &
- ccohort%n*area_inv*(1._r8-prt_params%allom_agb_frac(pft)) * csite%rootfrac_scr(1:bc_in%nlevsoil)
end if
- if( prt_params%woody(pft)==itrue ) then
- woody_area = woody_area + ccohort%c_area
+ ccohort => ccohort%shorter
+ end do
+
+ if(hlm_use_ch4==itrue)then
+ if( sum(bc_out%rootfr_pa(fp,1:bc_in%nlevsoil)) > nearzero) then
+ bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) = &
+ bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) / &
+ sum(bc_out%rootfr_pa(fp,1:bc_in%nlevsoil))
end if
- plant_area = plant_area + ccohort%c_area
+ ! RGK: These averages should switch to the new patch averaging methods
+ ! when available. Right now we are not doing any time averaging
+ ! because it would be mixing the memory of patches, which
+ ! would be arguably worse than just using the instantaneous value
- end if
-
- ccohort => ccohort%shorter
- end do
-
- if(hlm_use_ch4==itrue)then
- if( sum(bc_out%rootfr_pa(fp,1:bc_in%nlevsoil)) > nearzero) then
- bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) = &
- bc_out%rootfr_pa(fp,1:bc_in%nlevsoil) / &
- sum(bc_out%rootfr_pa(fp,1:bc_in%nlevsoil))
- end if
-
- ! RGK: These averages should switch to the new patch averaging methods
- ! when available. Right now we are not doing any time averaging
- ! because it would be mixing the memory of patches, which
- ! would be arguably worse than just using the instantaneous value
-
- ! gC/m2/s
- bc_out%annavg_agnpp_pa(fp) = agnpp
- bc_out%annavg_bgnpp_pa(fp) = bgnpp
- ! gc/m2/yr
- bc_out%annsum_npp_pa(fp) = (bgnpp+agnpp)*days_per_year*sec_per_day
-
- if(plant_area>nearzero) then
- bc_out%woody_frac_aere_pa(fp) = woody_area/plant_area
- end if
+ ! gC/m2/s
+ bc_out%annavg_agnpp_pa(fp) = agnpp
+ bc_out%annavg_bgnpp_pa(fp) = bgnpp
+ ! gc/m2/yr
+ bc_out%annsum_npp_pa(fp) = (bgnpp+agnpp)*days_per_year*sec_per_day
+
+ if(plant_area>nearzero) then
+ bc_out%woody_frac_aere_pa(fp) = woody_area/plant_area
+ end if
- end if
-
+ end if
+ end if if_notbare
cpatch => cpatch%younger
end do
diff --git a/biogeophys/CMakeLists.txt b/biogeophys/CMakeLists.txt
new file mode 100644
index 0000000000..c6048491b9
--- /dev/null
+++ b/biogeophys/CMakeLists.txt
@@ -0,0 +1,4 @@
+list(APPEND fates_sources
+ FatesHydroWTFMod.F90)
+
+sourcelist_to_parent(fates_sources)
\ No newline at end of file
diff --git a/biogeophys/FatesPlantRespPhotosynthMod.F90 b/biogeophys/FatesPlantRespPhotosynthMod.F90
index 0aad6eb977..4f3df22449 100644
--- a/biogeophys/FatesPlantRespPhotosynthMod.F90
+++ b/biogeophys/FatesPlantRespPhotosynthMod.F90
@@ -1301,6 +1301,13 @@ subroutine LeafLayerPhotosynthesis(f_sun_lsl, & ! in
! Fraction of light absorbed by non-photosynthetic pigments
real(r8),parameter :: fnps = 0.15_r8
+ ! term accounting that two photons are needed to fully transport a single
+ ! electron in photosystem 2
+ real(r8), parameter :: photon_to_e = 0.5_r8
+
+ ! Unit conversion of w/m2 to umol photons m-2 s-1
+ real(r8), parameter :: wm2_to_umolm2s = 4.6_r8
+
! For plants with no leaves, a miniscule amount of conductance
! can happen through the stems, at a partial rate of cuticular conductance
real(r8),parameter :: stem_cuticle_loss_frac = 0.1_r8
@@ -1364,7 +1371,7 @@ subroutine LeafLayerPhotosynthesis(f_sun_lsl, & ! in
do sunsha = 1,2
! Electron transport rate for C3 plants.
- ! Convert par from W/m2 to umol photons/m**2/s using the factor 4.6
+ ! Convert par from W/m2 to umol photons/m**2/s
! Convert from units of par absorbed per unit ground area to par
! absorbed per unit leaf area.
@@ -1372,7 +1379,7 @@ subroutine LeafLayerPhotosynthesis(f_sun_lsl, & ! in
if(( laisun_lsl * canopy_area_lsl) > min_la_to_solve)then
qabs = parsun_lsl / (laisun_lsl * canopy_area_lsl )
- qabs = qabs * 0.5_r8 * (1._r8 - fnps) * 4.6_r8
+ qabs = qabs * photon_to_e * (1._r8 - fnps) * wm2_to_umolm2s
else
qabs = 0.0_r8
@@ -1382,7 +1389,7 @@ subroutine LeafLayerPhotosynthesis(f_sun_lsl, & ! in
if( (parsha_lsl>nearzero) .and. (laisha_lsl * canopy_area_lsl) > min_la_to_solve ) then
qabs = parsha_lsl / (laisha_lsl * canopy_area_lsl)
- qabs = qabs * 0.5_r8 * (1._r8 - fnps) * 4.6_r8
+ qabs = qabs * photon_to_e * (1._r8 - fnps) * wm2_to_umolm2s
else
! The radiative transfer schemes are imperfect
! they can sometimes generate negative values here
@@ -1438,14 +1445,14 @@ subroutine LeafLayerPhotosynthesis(f_sun_lsl, & ! in
if(sunsha == 1)then !sunlit
!guard against /0's in the night.
if((laisun_lsl * canopy_area_lsl) > 0.0000000001_r8) then
- aj = quant_eff(c3c4_path_index) * parsun_lsl * 4.6_r8
+ aj = quant_eff(c3c4_path_index) * parsun_lsl * wm2_to_umolm2s
!convert from per cohort to per m2 of leaf)
aj = aj / (laisun_lsl * canopy_area_lsl)
else
aj = 0._r8
end if
else
- aj = quant_eff(c3c4_path_index) * parsha_lsl * 4.6_r8
+ aj = quant_eff(c3c4_path_index) * parsha_lsl * wm2_to_umolm2s
aj = aj / (laisha_lsl * canopy_area_lsl)
end if
@@ -1488,6 +1495,31 @@ subroutine LeafLayerPhotosynthesis(f_sun_lsl, & ! in
! With an <= 0, then gs_mol = stomatal_intercept_btran
leaf_co2_ppress = can_co2_ppress- h2o_co2_bl_diffuse_ratio/gb_mol * a_gs * can_press
leaf_co2_ppress = max(leaf_co2_ppress,1.e-06_r8)
+
+ ! A note about the use of the quadratic equations for calculating stomatal conductance
+ ! ------------------------------------------------------------------------------------
+ ! These two following models calculate the conductance between the intercellular leaf
+ ! space and the leaf surface, not the canopy air space. Transport between the leaf
+ ! surface and the canopy air space is governed by the leaf boundary layer conductance.
+ ! However, we need to estimate the properties at the surface of the leaf to solve for
+ ! the stomatal conductance. We do this by using Fick's law (gradient resistance
+ ! approximation of diffusion) to estimate the flux of water vapor across the
+ ! leaf boundary layer, and balancing that with the flux across the stomata. It
+ ! results in the following equation for leaf surface humidity:
+ !
+ ! e_s = (e_i g_s + e_c g_b)/(g_b + g_s)
+ !
+ ! The leaf surface humidity (e_s) becomes an expression of canopy humidity (e_c),
+ ! intercellular humidity (e_i, which is the saturation humidity at leaf temperature),
+ ! boundary layer conductance (g_b) (these are all known) and stomatal conductance
+ ! (g_s) (this is still unknown). This expression is substituted into the stomatal
+ ! conductance equation. The resulting form of these equations becomes a quadratic.
+ !
+ ! For a detailed explanation, see the FATES technical note, section
+ ! "1.11 Stomatal Conductance"
+ !
+ ! ------------------------------------------------------------------------------------
+
if ( stomatal_model == medlyn_model ) then
!stomatal conductance calculated from Medlyn et al. (2011), the numerical &
diff --git a/fire/CMakeLists.txt b/fire/CMakeLists.txt
new file mode 100644
index 0000000000..341065c3c3
--- /dev/null
+++ b/fire/CMakeLists.txt
@@ -0,0 +1,8 @@
+# This file is required for unit testing, but is not used for production runs
+list(APPEND fates_sources
+ SFParamsMod.F90
+ SFFireWeatherMod.F90
+ SFNesterovMod.F90
+ )
+
+sourcelist_to_parent(fates_sources)
diff --git a/fire/SFFireWeatherMod.F90 b/fire/SFFireWeatherMod.F90
new file mode 100644
index 0000000000..e39834ad1e
--- /dev/null
+++ b/fire/SFFireWeatherMod.F90
@@ -0,0 +1,32 @@
+module SFFireWeatherMod
+
+ use FatesConstantsMod, only : r8 => fates_r8
+
+ implicit none
+ private
+
+ type, abstract, public :: fire_weather
+ real(r8) :: fire_weather_index ! fire weather index
+ contains
+ procedure(initialize_fire_weather), public, deferred :: Init
+ procedure(update_fire_weather), public, deferred :: Update
+ end type fire_weather
+
+ abstract interface
+ subroutine initialize_fire_weather(this)
+ import :: fire_weather
+ class(fire_weather), intent(inout) :: this
+ end subroutine initialize_fire_weather
+
+ subroutine update_fire_weather(this, temp_C, precip, rh, wind)
+ use FatesConstantsMod, only : r8 => fates_r8
+ import :: fire_weather
+ class(fire_weather), intent(inout) :: this
+ real(r8), intent(in) :: temp_C
+ real(r8), intent(in) :: precip
+ real(r8), intent(in) :: rh
+ real(r8), intent(in) :: wind
+ end subroutine update_fire_weather
+ end interface
+
+end module SFFireWeatherMod
\ No newline at end of file
diff --git a/fire/SFMainMod.F90 b/fire/SFMainMod.F90
index 664e9f61ec..f1e7de14ea 100644
--- a/fire/SFMainMod.F90
+++ b/fire/SFMainMod.F90
@@ -1,7 +1,7 @@
module SFMainMod
! ============================================================================
- ! All subroutines realted to the SPITFIRE fire routine.
+ ! All subroutines related to the SPITFIRE fire routine.
! Code originally developed by Allan Spessa & Rosie Fisher as part of the NERC-QUEST project.
! ============================================================================
@@ -53,7 +53,6 @@ module SFMainMod
private
public :: fire_model
- public :: fire_danger_index
public :: charecteristics_of_fuel
public :: rate_of_spread
public :: ground_fuel_consumption
@@ -70,79 +69,71 @@ module SFMainMod
integer :: write_SF = ifalse ! for debugging
logical :: debug = .false. ! for debugging
- ! ============================================================================
- ! ============================================================================
+ ! ======================================================================================
contains
- ! ============================================================================
- ! Area of site burned by fire
- ! ============================================================================
- subroutine fire_model( currentSite, bc_in)
+ 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
- type(ed_site_type) , intent(inout), target :: currentSite
- type(bc_in_type) , intent(in) :: bc_in
-
-
- type (fates_patch_type), pointer :: currentPatch
-
- !zero fire things
+ ! zero fire things
currentPatch => currentSite%youngest_patch
do while(associated(currentPatch))
- currentPatch%frac_burnt = 0.0_r8
- currentPatch%fire = 0
- currentPatch => currentPatch%older
- enddo
-
- if(write_SF==itrue)then
- write(fates_log(),*) 'spitfire_mode', hlm_spitfire_mode
- endif
-
- if( hlm_spitfire_mode > hlm_sf_nofire_def )then
- call fire_danger_index(currentSite, bc_in)
- call wind_effect(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)
+ 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 wind_effect(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
- !*****************************************************************
- subroutine fire_danger_index ( currentSite, bc_in)
-
- !*****************************************************************
- ! currentSite%acc_NI is the accumulated Nesterov fire danger index
-
- 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
-
- type(ed_site_type) , intent(inout), target :: currentSite
- type(bc_in_type) , intent(in) :: bc_in
+ !---------------------------------------------------------------------------------------
+
+ subroutine UpdateFireWeather(currentSite, bc_in)
+ !
+ ! DESCRIPTION:
+ ! Updates the site's fire weather index
- type(fates_patch_type), pointer :: currentPatch
+ use FatesConstantsMod, only : tfrz => t_water_freeze_k_1atm
+ use FatesConstantsMod, only : sec_per_day
- real(r8) :: temp_in_C ! daily averaged temperature in celcius
- real(r8) :: rainfall ! daily precip in mm/day
- real(r8) :: rh ! daily rh
-
- real(r8) :: yipsolon !intermediate varable for dewpoint calculation
- real(r8) :: dewpoint !dewpoint in K
- real(r8) :: d_NI !daily change in Nesterov Index. C^2
- integer :: iofp ! index of oldest the fates patch
+ ! 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]
+ 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 inpact, unless we decide to ever calculated the NI for each patch.
+ ! which probably won't have much inpact, unless we decide to ever calculated fire weather for each patch.
currentPatch => currentSite%oldest_patch
@@ -153,30 +144,19 @@ subroutine fire_danger_index ( currentSite, bc_in)
endif
iofp = currentPatch%patchno
-
- temp_in_C = currentPatch%tveg24%GetMean() - tfrz
- rainfall = bc_in%precip24_pa(iofp)*sec_per_day
- rh = bc_in%relhumid24_pa(iofp)
-
- if (rainfall > 3.0_r8) then !rezero NI if it rains...
- d_NI = 0.0_r8
- currentSite%acc_NI = 0.0_r8
- else
- yipsolon = (SF_val_fdi_a* temp_in_C)/(SF_val_fdi_b+ temp_in_C)+log(max(1.0_r8,rh)/100.0_r8)
- dewpoint = (SF_val_fdi_b*yipsolon)/(SF_val_fdi_a-yipsolon) !Standard met. formula
- d_NI = ( temp_in_C-dewpoint)* temp_in_C !follows Nesterov 1968. Equation 5. Thonicke et al. 2010.
- if (d_NI < 0.0_r8) then !Change in NI cannot be negative.
- d_NI = 0.0_r8 !check
- endif
- endif
- currentSite%acc_NI = currentSite%acc_NI + d_NI !Accumulate Nesterov index over the fire season.
+ 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 subroutine fire_danger_index
+ ! update fire weather index
+ call currentSite%fireWeather%Update(temp_C, precip, rh, wind)
+ end subroutine UpdateFireWeather
+
+ !---------------------------------------------------------------------------------------
- !*****************************************************************
subroutine charecteristics_of_fuel ( currentSite )
- !*****************************************************************
use SFParamsMod, only : SF_val_drying_ratio, SF_val_SAV, SF_val_FBD
@@ -266,19 +246,21 @@ subroutine charecteristics_of_fuel ( currentSite )
! 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%acc_NI)
+ 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%acc_NI
+ 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%acc_NI))
+ 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))
@@ -746,7 +728,7 @@ subroutine area_burnt_intensity ( currentSite, bc_in )
! force ignition potential to be extreme
cloud_to_ground_strikes = 1.0_r8 ! cloud_to_ground = 1 = use 100% incoming observed ignitions
else ! USING LIGHTNING DATA
- currentSite%FDI = 1.0_r8 - exp(-SF_val_fdi_alpha*currentSite%acc_NI)
+ currentSite%FDI = 1.0_r8 - exp(-SF_val_fdi_alpha*currentSite%fireWeather%fire_weather_index)
cloud_to_ground_strikes = cg_strikes
end if
diff --git a/fire/SFNesterovMod.F90 b/fire/SFNesterovMod.F90
new file mode 100644
index 0000000000..85d1d94396
--- /dev/null
+++ b/fire/SFNesterovMod.F90
@@ -0,0 +1,104 @@
+module SFNesterovMod
+
+ use FatesConstantsMod, only : r8 => fates_r8
+ use SFFireWeatherMod, only : fire_weather
+
+ implicit none
+ private
+
+ type, public, extends(fire_weather) :: nesterov_index
+
+ contains
+
+ procedure, public :: Init => init_nesterov_fire_weather
+ procedure, public :: Update => 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]
+
+ contains
+
+ subroutine init_nesterov_fire_weather(this)
+ !
+ ! DESCRIPTION:
+ ! Initializes class attributes
+
+ ! ARGUMENTS
+ class(nesterov_index), intent(inout) :: this ! nesterov index extended class
+
+ ! initialize values to 0.0
+ this%fire_weather_index = 0.0_r8
+
+ end subroutine init_nesterov_fire_weather
+
+ !-------------------------------------------------------------------------------------
+
+ subroutine update_nesterov_index(this, temp_C, precip, rh, wind)
+ !
+ ! DESCRIPTION:
+ ! Updates Nesterov Index
+
+ ! ARGUMENTS
+ class(nesterov_index), intent(inout) :: this ! nesterov index extended class
+ real(r8), intent(in) :: temp_C ! daily averaged temperature [degrees C]
+ real(r8), intent(in) :: precip ! daily precipitation [mm]
+ real(r8), intent(in) :: rh ! daily relative humidity [%]
+ real(r8), intent(in) :: wind ! daily wind speed [m/min]
+
+ ! LOCALS:
+ real(r8) :: t_dew ! dewpoint temperature [degrees C]
+
+ if (precip > min_precip_thresh) then ! rezero NI if it rains
+ this%fire_weather_index = 0.0_r8
+ else
+
+ ! Calculate dewpoint temperature
+ t_dew = dewpoint(temp_c, rh)
+
+ ! Accumulate Nesterov index over fire season.
+ this%fire_weather_index = this%fire_weather_index + calc_nesterov_index(temp_C, t_dew)
+ end if
+
+ end subroutine update_nesterov_index
+
+ !-------------------------------------------------------------------------------------
+
+ real(r8) function calc_nesterov_index(temp_C, t_dew)
+ !
+ ! DESCRIPTION:
+ ! Calculates current day's Nesterov Index for a given input values
+
+ ! ARGUMENTS:
+ real(r8), intent(in) :: temp_C ! daily averaged temperature [degrees C]
+ real(r8), intent(in) :: t_dew ! daily dewpoint temperature [degrees C]
+
+ ! Nesterov 1968. Eq 5, Thonicke et al. 2010
+ calc_nesterov_index = (temp_C - t_dew)*temp_C
+ if (calc_nesterov_index < 0.0_r8) calc_nesterov_index = 0.0_r8 ! can't be negative
+
+ end function calc_nesterov_index
+
+ !-------------------------------------------------------------------------------------
+
+ real(r8) function dewpoint(temp_C, rh)
+ !
+ ! DESCRIPTION:
+ ! Calculates dewpoint from input air temperature and relative humidity
+ ! Uses Equation 8 from Lawrence 2005, https://doi.org/10.1175/BAMS-86-2-225
+
+ use FatesConstantsMod, only : dewpoint_a, dewpoint_b
+
+ ! ARGUMENTS
+ real(r8), intent(in) :: temp_C ! temperature [degrees C]
+ real(r8), intent(in) :: rh ! relative humidity [%]
+
+ ! LOCALS
+ real(r8) :: yipsolon ! intermediate value for dewpoint calculation
+
+ yipsolon = log(max(1.0_r8, rh)/100.0_r8) + (dewpoint_a*temp_C)/(dewpoint_b + temp_C)
+ 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 306034a804..c2dbc3fcd6 100644
--- a/fire/SFParamsMod.F90
+++ b/fire/SFParamsMod.F90
@@ -18,9 +18,6 @@ module SFParamsMod
!
! this is what the user can use for the actual values
!
-
- real(r8),protected, public :: SF_val_fdi_a
- real(r8),protected, public :: SF_val_fdi_b
real(r8),protected, public :: SF_val_fdi_alpha
real(r8),protected, public :: SF_val_miner_total
real(r8),protected, public :: SF_val_fuel_energy
@@ -41,8 +38,6 @@ module SFParamsMod
real(r8),protected, public :: SF_val_mid_moisture_Coeff(NFSC)
real(r8),protected, public :: SF_val_mid_moisture_Slope(NFSC)
- character(len=param_string_length),parameter :: SF_name_fdi_a = "fates_fire_fdi_a"
- character(len=param_string_length),parameter :: SF_name_fdi_b = "fates_fire_fdi_b"
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"
character(len=param_string_length),parameter :: SF_name_fuel_energy = "fates_fire_fuel_energy"
@@ -148,8 +143,6 @@ subroutine SpitFireParamsInit()
implicit none
- SF_val_fdi_a = nan
- SF_val_fdi_b = nan
SF_val_fdi_alpha = nan
SF_val_miner_total = nan
SF_val_fuel_energy = nan
@@ -214,12 +207,6 @@ subroutine SpitFireRegisterScalars(fates_params)
character(len=param_string_length), parameter :: dim_names_scalar(1) = (/dimension_name_scalar/)
- call fates_params%RegisterParameter(name=SF_name_fdi_a, dimension_shape=dimension_shape_scalar, &
- dimension_names=dim_names_scalar)
-
- call fates_params%RegisterParameter(name=SF_name_fdi_b, dimension_shape=dimension_shape_scalar, &
- dimension_names=dim_names_scalar)
-
call fates_params%RegisterParameter(name=SF_name_fdi_alpha, dimension_shape=dimension_shape_scalar, &
dimension_names=dim_names_scalar)
@@ -258,13 +245,6 @@ subroutine SpitFireReceiveScalars(fates_params)
class(fates_parameters_type), intent(inout) :: fates_params
real(r8) :: tmp_real
-
-
- call fates_params%RetrieveParameter(name=SF_name_fdi_a, &
- data=SF_val_fdi_a)
-
- call fates_params%RetrieveParameter(name=SF_name_fdi_b, &
- data=SF_val_fdi_b)
call fates_params%RetrieveParameter(name=SF_name_fdi_alpha, &
data=SF_val_fdi_alpha)
diff --git a/functional_unit_testing/allometry/AutoGenVarCon.py b/functional_unit_testing/allometry/AutoGenVarCon.py
deleted file mode 100644
index 7bf0f85a6b..0000000000
--- a/functional_unit_testing/allometry/AutoGenVarCon.py
+++ /dev/null
@@ -1,140 +0,0 @@
-
-
-# Walk through lines of a file, if a line contains
-# the string of interest (EDPftvarcon_inst), then
-# parse the string to find the variable name, and save that
-# to the list
-
-
-class ParamType:
-
- def __init__(self,var_sym,n_dims):
-
- self.var_sym = var_sym
- self.n_dims = n_dims
- self.var_name = ''
-
-
-
-
-def CheckFile(filename,check_str):
- file_ptr = open(filename,'r')
- var_list = []
- found = False
- for line in file_ptr:
- if check_str in line:
- line_split = line.split()
- # substr = [i for i in line_split if check_str in i][0]
- substr = line
- p1 = substr.find('%')+1
- if(p1>0):
- substr=substr[p1:]
- p2 = substr.find('(')
- p3 = substr.find(')')
- # Count the number of commas between p2 and p3
- n_dims = substr[p2:p3].count(',')+1
- if(p2>0):
- var_list.append(ParamType(substr[:p2],n_dims))
-
- unique_list = []
- for var in var_list:
- found = False
- for uvar in unique_list:
- if (var.var_sym == uvar.var_sym):
- found = True
- if(not found):
- unique_list.append(var)
-
- return(unique_list)
-
-
-
-check_str = 'EDPftvarcon_inst%'
-filename = '../../biogeochem/FatesAllometryMod.F90'
-
-var_list = CheckFile(filename,check_str)
-
-
-# Add symbols here
-
-var_list.append(ParamType('hgt_min',1))
-
-
-# Now look through EDPftvarcon.F90 to determine the variable name in file
-# that is associated with the variable pointer
-
-filename = '../../main/EDPftvarcon.F90'
-
-f = open(filename,"r")
-contents = f.readlines()
-
-
-var_name_list = []
-for var in var_list:
- for i,line in enumerate(contents):
- if (var.var_sym in line) and ('data' in line) and ('=' in line):
- var.var_name = contents[i-2].split()[-1].strip('\'')
- print("{} {} {}".format(var.var_sym,var.var_name,var.n_dims))
-
-
-f = open("f90src/AllomUnitWrap.F90_in", "r")
-contents = f.readlines()
-f.close()
-
-# Identify where we define the variables, and insert the variable definitions
-
-for i,str in enumerate(contents):
- if 'VARIABLE-DEFINITIONS-HERE' in str:
- index0=i
-
-index=index0+2
-for var in var_list:
- if(var.n_dims==1):
- contents.insert(index,' real(r8),pointer :: {}(:)\n'.format(var.var_sym))
- elif(var.n_dims==2):
- contents.insert(index,' real(r8),pointer :: {}(:,:)\n'.format(var.var_sym))
- else:
- print('Incorrect number of dims...')
- exit(-2)
- index=index+1
-
-# Identify where we do the pointer assignments, and insert the pointer assignments
-
-
-for i,str in enumerate(contents):
- if 'POINTER-SPECIFICATION-HERE' in str:
- index0=i
-
-index=index0+2
-for ivar,var in enumerate(var_list):
- if(var.n_dims==1):
- ins_l1='\t allocate(EDPftvarcon_inst%{}(1:numpft))\n'.format(var.var_sym)
- ins_l2='\t EDPftvarcon_inst%{}(:) = nan\n'.format(var.var_sym)
- ins_l3='\t iv1 = iv1 + 1\n'
- ins_l4='\t EDPftvarcon_ptr%var1d(iv1)%var_name = "{}"\n'.format(var.var_name)
- ins_l5='\t EDPftvarcon_ptr%var1d(iv1)%var_rp => EDPftvarcon_inst%{}\n'.format(var.var_sym)
- ins_l6='\t EDPftvarcon_ptr%var1d(iv1)%vtype = 1\n'
- ins_l7='\n'
- if(var.n_dims==2):
- ins_l1='\t allocate(EDPftvarcon_inst%{}(1:numpft,1))\n'.format(var.var_sym)
- ins_l2='\t EDPftvarcon_inst%{}(:,:) = nan\n'.format(var.var_sym)
- ins_l3='\t iv2 = iv2 + 1\n'
- ins_l4='\t EDPftvarcon_ptr%var2d(iv2)%var_name = "{}"\n'.format(var.var_name)
- ins_l5='\t EDPftvarcon_ptr%var2d(iv2)%var_rp => EDPftvarcon_inst%{}\n'.format(var.var_sym)
- ins_l6='\t EDPftvarcon_ptr%var2d(iv2)%vtype = 1\n'
- ins_l7='\n'
-
- contents.insert(index,ins_l1)
- contents.insert(index+1,ins_l2)
- contents.insert(index+2,ins_l3)
- contents.insert(index+3,ins_l4)
- contents.insert(index+4,ins_l5)
- contents.insert(index+5,ins_l6)
- contents.insert(index+6,ins_l7)
- index=index+7
-
-
-f = open("f90src/AllomUnitWrap.F90", "w+")
-contents = "".join(contents)
-f.write(contents)
-f.close()
diff --git a/functional_unit_testing/allometry/drive_allomtests.py b/functional_unit_testing/allometry/drive_allomtests.py
deleted file mode 100644
index d97da4ded3..0000000000
--- a/functional_unit_testing/allometry/drive_allomtests.py
+++ /dev/null
@@ -1,730 +0,0 @@
-import numpy as np
-import math
-import matplotlib.pyplot as plt
-import matplotlib as mp
-import ctypes
-import importlib
-from ctypes import * #byref, cdll, c_int, c_double, c_char_p, c_long
-import xml.etree.ElementTree as ET
-import argparse
-import re # This is a heftier string parser
-import code # For development: code.interact(local=dict(globals(), **locals()))
-import sys
-sys.path.append('../shared/py_src')
-from PyF90Utils import c8, ci, cchar, c8_arr, ci_arr
-
-# =======================================================================================
-# Set some constants. If they are used as constant arguments to the F90 routines,
-# define them with their ctype identifiers
-# =======================================================================================
-
-ndbh = 200
-maxdbh = 50
-ccanopy_trim = c_double(1.0) # Crown Trim (0=0% of target, 1=100% of targ)
-csite_spread = c_double(0.0) # Canopy spread (0=closed, 1=open)
-cnplant = c_double(1.0) # Number of plants (don't change)
-cilayer = c_int(1) # Index of the plant's canopy layer
-ccanopy_lai = (2 * c_double)(1.0,1.0) # The LAI of the different canopy layers
- # THIS VECTOR MUST MATCH ncanlayer
-cdo_reverse = c_bool(0) # DO NOT GET REVERSE CROWN AREA
-
-# =======================================================================================
-# Setup references to fortran shared libraries
-# =======================================================================================
-
-allom_const_object = "./include/FatesConstantsMod.o"
-allom_wrap_object = "./include/AllomUnitWrap.o"
-allom_lib_object = "./include/FatesAllometryMod.o"
-
-# ==============================================================================
-# Instantiate fortran allometry and other libraries
-# ==============================================================================
-
-f90constlib= ctypes.CDLL(allom_const_object,mode=ctypes.RTLD_GLOBAL)
-f90wraplib = ctypes.CDLL(allom_wrap_object,mode=ctypes.RTLD_GLOBAL)
-f90funclib = ctypes.CDLL(allom_lib_object,mode=ctypes.RTLD_GLOBAL)
-
-# =======================================================================================
-# Create aliases to all of the different routines, set return types for functions
-# =======================================================================================
-
-f90_pftalloc = f90wraplib.__edpftvarcon_MOD_edpftvarconalloc #(numpft)
-f90_pftset = f90wraplib.__edpftvarcon_MOD_edpftvarconpyset
-f90_pftset.argtypes = [POINTER(c_int),POINTER(c_double),POINTER(c_int),c_char_p,c_long]
-f90_h2d = f90funclib.__fatesallometrymod_MOD_h2d_allom #(h,ipft,d,dddh)
-f90_h = f90funclib.__fatesallometrymod_MOD_h_allom #(d,ipft,h,dhdd)
-f90_bagw = f90funclib.__fatesallometrymod_MOD_bagw_allom #(d,ipft,bagw,dbagwdd)
-f90_bleaf = f90funclib.__fatesallometrymod_MOD_bleaf #(d,ipft,canopy_trim,bl,dbldd)
-f90_bsap = f90funclib.__fatesallometrymod_MOD_bsap_allom #(d,ipft,canopy_trim,asapw,bsap,dbsapdd)
-f90_bstore = f90funclib.__fatesallometrymod_MOD_bstore_allom #(d,ipft,canopy_trim,bstore,dbstoredd)
-f90_bbgw = f90funclib.__fatesallometrymod_MOD_bbgw_allom #(d,ipft,canopy_trim,bbgw,dbbgwdd)
-f90_bfineroot = f90funclib.__fatesallometrymod_MOD_bfineroot #(d,ipft,canopy_trim,bfr,dbfrdd)
-f90_bdead = f90funclib.__fatesallometrymod_MOD_bdead_allom #(bagw,bbgw,bsap,ipft,bdead,dbagwdd,dbbgwdd,dbsapdd,dbdeaddd)
-f90_carea = f90funclib.__fatesallometrymod_MOD_carea_allom #(d,nplant,site_spread,ipft,c_area)(d,nplant,site_spread,ipft,c_area)
-f90_treelai = f90funclib.__fatesallometrymod_MOD_tree_lai #(leaf_c, pft, c_area, nplant, cl, canopy_lai, vcmax25top)
-f90_treelai.restype = c_double
-
-
-# This is the object type that holds our parameters
-# =======================================================================================
-class parameter:
-
- def __init__(self,symbol):
-
- self.dtype = -9
- self.symbol = symbol
- self.vals = []
-
- def setval(self,val,ipft):
-
- self.vals[ipft] = val
-
-# This is just a helper script that generates random colors
-# =======================================================================================
-def DiscreteCubeHelix(N):
-
- base = plt.cm.get_cmap('cubehelix')
- np.random.seed(2)
- color_list = base(np.random.randint(0,high=255,size=N))
- cmap_name = base.name + str(N)
- return base.from_list(cmap_name, color_list, N)
-
-
-# This will look through a CDL file for the provided parameter and determine
-# the parameter's type, as well as fill an array with the data
-# =======================================================================================
-def CDLParse(file_name,parm):
-
- fp = open(file_name,"r")
- contents = fp.readlines()
- fp.close()
-
- # Look in the file for the parameters
- # symbol/name, record the line number
- iline=-1
- isfirst = True
- for i,line in enumerate(contents):
- if(parm.symbol in line):
- iline=i
- if(isfirst):
- dtype = line.split()[0]
- if(dtype.strip()=="float" or (dtype.strip()=="double")):
- parm.dtype = 0
- elif(dtype.strip()=="char"):
- parm.dtype = 1
- isFirst=False
-
- if(iline==-1):
- print('Could not find symbol: {} in file: {}'.format(parm.symbol,file_name))
- exit(2)
- else:
- search_field=True
- line=""
- lcount=0
- while(search_field and (lcount<100)):
- line+=contents[iline]
- if(line.count(';')>0):
- search_field=False
- else:
- search_field=True
- lcount=lcount+1
- iline=iline+1
-
- # Parse the line
- line_split = re.split(',|=',line)
- # Remove the variable name entry
- del line_split[0]
-
- # This is for read numbers
- if(parm.dtype == 0):
- ival=0
- for str0 in line_split:
- str=""
- isnum=False
- for s in str0:
- if (s.isdigit() or s=='.'):
- str+=s
- isnum=True
- if(isnum):
- parm.vals.append(float(str))
-
- # This is a sting
- elif(parm.dtype == 1):
- for str0 in line_split:
- # Loop several times to trim stuff off
- for i in range(5):
- str0=str0.strip().strip('\"').strip(';').strip()
- parm.vals.append(str0)
-
- return(parm)
-
-
-
-# Read in the arguments
-# =======================================================================================
-
-parser = argparse.ArgumentParser(description='Parse command line arguments to this script.')
-parser.add_argument('--fin', '--input', dest='fnamein', type=str, help="Input CDL filename. Required.", required=True)
-args = parser.parse_args()
-
-
-# Read in the parameters of interest that are used in the fortran objects. These
-# parameters will be passed to the fortran allocation.
-# =======================================================================================
-
-parms = {}
-parms['dbh_maxheight'] = CDLParse(args.fnamein,parameter('fates_allom_dbh_maxheight'))
-parms['hmode'] = CDLParse(args.fnamein,parameter('fates_allom_hmode'))
-parms['amode'] = CDLParse(args.fnamein,parameter('fates_allom_amode'))
-parms['lmode'] = CDLParse(args.fnamein,parameter('fates_allom_lmode'))
-parms['smode'] = CDLParse(args.fnamein,parameter('fates_allom_smode'))
-parms['cmode'] = CDLParse(args.fnamein,parameter('fates_allom_cmode'))
-parms['fmode'] = CDLParse(args.fnamein,parameter('fates_allom_fmode'))
-parms['stmode'] = CDLParse(args.fnamein,parameter('fates_allom_stmode'))
-parms['cushion'] = CDLParse(args.fnamein,parameter('fates_alloc_storage_cushion'))
-parms['d2h1'] = CDLParse(args.fnamein,parameter('fates_allom_d2h1'))
-parms['d2h2'] = CDLParse(args.fnamein,parameter('fates_allom_d2h2'))
-parms['d2h3'] = CDLParse(args.fnamein,parameter('fates_allom_d2h3'))
-parms['agb1'] = CDLParse(args.fnamein,parameter('fates_allom_agb1'))
-parms['agb2'] = CDLParse(args.fnamein,parameter('fates_allom_agb2'))
-parms['agb3'] = CDLParse(args.fnamein,parameter('fates_allom_agb3'))
-parms['agb4'] = CDLParse(args.fnamein,parameter('fates_allom_agb4'))
-parms['d2bl1'] = CDLParse(args.fnamein,parameter('fates_allom_d2bl1'))
-parms['d2bl2'] = CDLParse(args.fnamein,parameter('fates_allom_d2bl2'))
-parms['d2bl3'] = CDLParse(args.fnamein,parameter('fates_allom_d2bl3'))
-parms['wood_density'] = CDLParse(args.fnamein,parameter('fates_wood_density'))
-parms['c2b'] = CDLParse(args.fnamein,parameter('fates_c2b'))
-parms['la_per_sa_int'] = CDLParse(args.fnamein,parameter('fates_allom_la_per_sa_int'))
-parms['la_per_sa_slp'] = CDLParse(args.fnamein,parameter('fates_allom_la_per_sa_slp'))
-parms['slatop'] = CDLParse(args.fnamein,parameter('fates_leaf_slatop'))
-parms['slamax'] = CDLParse(args.fnamein,parameter('fates_leaf_slamax'))
-parms['l2fr'] = CDLParse(args.fnamein,parameter('fates_allom_l2fr'))
-parms['agb_frac'] = CDLParse(args.fnamein,parameter('fates_allom_agb_frac'))
-parms['blca_expnt_diff'] = CDLParse(args.fnamein,parameter('fates_allom_blca_expnt_diff'))
-parms['d2ca_coeff_min'] = CDLParse(args.fnamein,parameter('fates_allom_d2ca_coefficient_min'))
-parms['d2ca_coeff_max'] = CDLParse(args.fnamein,parameter('fates_allom_d2ca_coefficient_max'))
-parms['sai_scaler'] = CDLParse(args.fnamein,parameter('fates_allom_sai_scaler'))
-
-# Read in the parameters that are not necessary for the F90 allometry algorithms,
-# but are useful for these scripts (e.g. the name of the parameter, and minimum height)
-# =======================================================================================
-
-eparms = {}
-eparms['recruit_hgt_min'] = CDLParse(args.fnamein,parameter('fates_recruit_hgt_min'))
-eparms['name'] = CDLParse(args.fnamein,parameter('fates_pftname'))
-eparms['vcmax25top'] = CDLParse(args.fnamein,parameter('fates_leaf_vcmax25top'))
-
-
-# Determine how many PFTs are here, also check to make sure that all parameters
-# have the same number
-# =======================================================================================
-numpft=-1
-for key, parm in parms.items():
- if( (len(parm.vals) == numpft) or (numpft==-1) ):
- numpft=len(parm.vals)
- else:
- print('Bad length in PFT parameter')
- print('parameter: {}, vals:'.format(parm.symbol),parm.vals)
-
-
-# ==============================================================================
-# Allocate fortran PFT arrays
-# ==============================================================================
-
-iret=f90_pftalloc(ci(numpft))
-
-# ==============================================================================
-# Populate the Fortran PFT structure
-# ==============================================================================
-
-for ipft in range(numpft):
- for key, parm in parms.items():
- print('{} {} '.format(parm.symbol,parm.vals[ipft]))
- iret=f90_pftset(c_int(ipft+1), \
- c_double(parm.vals[ipft]), \
- c_int(0), \
- c_char_p(parm.symbol.encode('utf-8')), \
- c_long(len(parm.symbol)))
-
-
-# =========================================================================
-# Initialize Output Arrays
-# =========================================================================
-
-blmaxi = np.zeros((numpft,ndbh))
-blmaxd = np.zeros((numpft,ndbh))
-bfrmax = np.zeros((numpft,ndbh))
-hi = np.zeros((numpft,ndbh))
-hd = np.zeros((numpft,ndbh))
-bagwi = np.zeros((numpft,ndbh))
-bagwd = np.zeros((numpft,ndbh))
-
-bagwr = np.zeros((numpft,ndbh))
-
-dbh = np.zeros((numpft,ndbh))
-bbgw = np.zeros((numpft,ndbh))
-bsapi = np.zeros((numpft,ndbh))
-bsapd = np.zeros((numpft,ndbh))
-asapd = np.zeros((numpft,ndbh))
-bstore = np.zeros((numpft,ndbh))
-bdead = np.zeros((numpft,ndbh))
-dbhe = np.zeros((numpft,ndbh))
-camin = np.zeros((numpft,ndbh))
-ldense = np.zeros((numpft,ndbh))
-treelai = np.zeros((numpft,ndbh))
-blmax_o_dbagwdh = np.zeros((numpft,ndbh))
-blmax_o_dbagwdd = np.zeros((numpft,ndbh))
-
-
-for ipft in range(numpft):
-
- print('py: Solving for pft: {}'.format(ipft+1))
-
- # Initialize Height #(d,ipft,h,dhdd)
- ch_min = c_double(eparms['recruit_hgt_min'].vals[ipft])
-
- cd = c_double(-9.0)
- cdddh = c_double(-9.0)
- cipft = c_int(ipft+1)
- cinit = c_int(0)
-
- # Calculate the minimum dbh
- iret=f90_h2d(byref(ch_min),byref(cipft),byref(cd),byref(cdddh))
-
- # Generate a vector of diameters (use dbh)
- dbh[ipft,:] = np.linspace(cd.value,maxdbh,num=ndbh)
-
- # Initialize various output vectors
- cd = c_double(dbh[ipft,0])
- ch = c_double(-9.0)
- cdhdd = c_double(-9.0)
- cbagw = c_double(-9.0)
- cdbagwdd = c_double(-9.0)
- cblmax = c_double(-9.0)
- cdblmaxdd = c_double(-9.0)
- cbfrmax = c_double(-9.0)
- cdbfrmaxdd = c_double(-9.0)
- cbbgw = c_double(-9.0)
- cdbbgwdd = c_double(-9.0)
- cbsap = c_double(-9.0)
- cdbsapdd = c_double(-9.0)
- cbdead = c_double(-9.0)
- cdbdeaddd = c_double(-9.0)
- ccamin = c_double(-9.0)
- casapw = c_double(-9.0) # Sapwood area
- cbstore = c_double(-9.0)
- cdbstoredd = c_double(-9.0)
-
- iret=f90_h(byref(cd),byref(cipft),byref(ch),byref(cdhdd))
- hi[ipft,0] = ch.value
- hd[ipft,0] = ch.value
- print('py: initialize h[{},0]={}'.format(ipft+1,ch.value))
-
- # Initialize AGB #(d,ipft,bagw,dbagwdd)
- iret=f90_bagw(byref(cd),byref(cipft),byref(cbagw),byref(cdbagwdd))
- bagwi[ipft,0] = cbagw.value
- print('py: initialize bagwi[{},0]={}'.format(ipft+1,cbagw.value))
-
- # Initialize bleaf #(d,ipft,canopy_trim,bl,dbldd)
- iret=f90_bleaf(byref(cd),byref(cipft),byref(ccanopy_trim),byref(cblmax),byref(cdblmaxdd))
- blmaxi[ipft,0] = cblmax.value
- blmaxd[ipft,0] = cblmax.value
- print('py: initialize blmaxi[{},0]={}'.format(ipft+1,cblmax.value))
-
- # Initialize bstore #(d,ipft,canopy_trim,bstore,dbstoredd)
- iret=f90_bstore(byref(cd),byref(cipft),byref(ccanopy_trim),byref(cbstore),byref(cdbstoredd))
- bstore[ipft,0] = cbstore.value
-
- # calculate crown area (d,nplant,site_spread,ipft,c_area) Using nplant = 1, generates units of m2
- # spread is likely 0.0, which is the value it tends towards when canopies close
- # (dbh, nplant, site_spread, ipft, c_area,inverse)
- iret= f90_carea(byref(cd),byref(cnplant),byref(csite_spread),byref(cipft),byref(ccamin),byref(cdo_reverse))
- camin[ipft,0] = ccamin.value
-
-
- ldense[ipft,0] = blmaxi[ipft,0]/camin[ipft,0]
- print('py: initialize careai[{},0]={}'.format(ipft+1,ccamin.value))
-
- #f90_treelai(leaf_c, pft, c_area, nplant, cl, canopy_lai, vcmax25top)
- cvcmax=c_double(eparms['vcmax25top'].vals[ipft])
- treelai[ipft,0]=f90_treelai(byref(cblmax),byref(cipft),byref(ccamin), \
- byref(cnplant),byref(cilayer),byref(ccanopy_lai),byref(cvcmax))
-
- # Initialize fine roots #(d,ipft,canopy_trim,bfr,dbfrdd)
- iret=f90_bfineroot(byref(cd),byref(cipft),byref(ccanopy_trim), \
- byref(cbfrmax),byref(cdbfrmaxdd))
- bfrmax[ipft,0] = cbfrmax.value
- print('py: initialize bfrmax[{},0]={}'.format(ipft+1,cbfrmax.value))
-
- # Initialize coarse roots #(d,ipft,bbgw,dbbgwdd)
- iret=f90_bbgw(byref(cd),byref(cipft),byref(c_double(1.0)), \
- byref(cbbgw),byref(cdbbgwdd))
- bbgw[ipft,0] = cbbgw.value
- print('py: initialize bbgw[{},0]={}'.format(ipft+1,cbbgw.value))
-
-
- # Initialize bsap (d,ipft,canopy_trim,asapw,bsap,dbsapdd)
- iret=f90_bsap(byref(cd),byref(cipft),byref(ccanopy_trim),byref(casapw),byref(cbsap),byref(cdbsapdd))
- bsapi[ipft,0] = cbsap.value
- bsapd[ipft,0] = cbsap.value
- asapd[ipft,0] = casapw.value
- print('py: initialize bsapi[{},0]={}'.format(ipft+1,cbsap.value))
-
- # bdead #(bagw,bbgw,bsap,ipft,bdead,dbagwdd,dbbgwdd,dbsapdd,dbdeaddd)
- iret=f90_bdead(byref(cbagw),byref(cbbgw),byref(cbsap),byref(cipft), \
- byref(cbdead),byref(cdbagwdd),byref(cdbbgwdd), \
- byref(cdbsapdd),byref(cdbdeaddd))
-
- bdead[ipft,0] = cbdead.value
- print('py: initialize bdead[{},0]={}'.format(ipft+1,cbdead.value))
-
- bagwr[ipft,0] = (bdead[ipft,0]) * 0.6
-
- # the metric that shan't be spoken
- blmax_o_dbagwdh[ipft,0] = blmaxi[ipft,0]/(cdbagwdd.value/cdhdd.value)
-
- # the metric that shan't be spoken
- blmax_o_dbagwdd[ipft,0] = blmaxi[ipft,0]/(cdbagwdd.value)
-
- for idi in range(1,ndbh):
-
- dp = dbh[ipft,idi-1] # previous position
- dc = dbh[ipft,idi] # current position
- dd = dc-dp
-
- cdp = c_double(dp)
- cdc = c_double(dc)
- cdbhe = c_double(-9.0)
- cddedh = c_double(-9.0)
-
- if(ipft==2):
- print("===")
-
- # integrate height #(d,ipft,h,dhdd)
- iret=f90_h(byref(cdc),byref(cipft),byref(ch),byref(cdhdd))
- hi[ipft,idi] = hi[ipft,idi-1] + cdhdd.value*dd
-
- # diagnosed height
- hd[ipft,idi] = ch.value
-
- # diagnose AGB #(d,h,ipft,bagw,dbagwdd)
- iret=f90_bagw(byref(cdc),byref(cipft),byref(cbagw),byref(cdbagwdd))
- bagwd[ipft,idi] = cbagw.value
-
- # integrate AGB #(d,h,ipft,bagw,dbagwdd)
- iret=f90_bagw(byref(cdp),byref(cipft),byref(cbagw),byref(cdbagwdd))
- bagwi[ipft,idi] = bagwi[ipft,idi-1] + cdbagwdd.value*dd
-
- # diagnose bleaf #(d,ipft,blmax,dblmaxdd)
- iret=f90_bleaf(byref(cdc),byref(cipft),byref(ccanopy_trim),byref(cblmax),byref(cdblmaxdd))
- blmaxd[ipft,idi] = cblmax.value
-
- # bstore #(d,ipft,canopy_trim,bstore,dbstoredd)
- iret=f90_bstore(byref(cdc),byref(cipft),byref(ccanopy_trim),byref(cbstore),byref(cdbstoredd))
- bstore[ipft,idi] = cbstore.value
-
- # calculate crown area (d,nplant,site_spread,ipft,c_area) Using nplant = 1, generates units of m2
- iret= f90_carea(byref(cdc),byref(cnplant),byref(csite_spread),byref(cipft),byref(ccamin),byref(cdo_reverse))
- camin[ipft,idi] = ccamin.value
-
- #f90_treelai(leaf_c, pft, c_area, nplant, cl, canopy_lai, vcmax25top)
- cvcmax=c_double(eparms['vcmax25top'].vals[ipft])
- treelai[ipft,idi]=f90_treelai(byref(cblmax),byref(cipft),byref(ccamin), \
- byref(cnplant),byref(cilayer),byref(ccanopy_lai),byref(cvcmax))
-
-
-
-
- # integrate bleaf #(d,ipft,blmax,dblmaxdd)
- iret=f90_bleaf(byref(cdp),byref(cipft),byref(c_double(1.0)),byref(cblmax),byref(cdblmaxdd))
- blmaxi[ipft,idi] = blmaxi[ipft,idi-1] + cdblmaxdd.value*dd
-
- # leaf mass per square meter of crown
- ldense[ipft,idi] = blmaxd[ipft,idi]/camin[ipft,idi]
-
- # integrate bfineroot #(d,ipft,canopy_trim,bfr,dbfrdd)
- iret=f90_bfineroot(byref(cdp),byref(cipft),byref(c_double(1.0)),byref(cbfrmax),byref(cdbfrmaxdd))
- bfrmax[ipft,idi] = bfrmax[ipft,idi-1] + cdbfrmaxdd.value*dd
-
- # integrate bbgw #(d,h,ipft,bbgw,dbbgwdd)
- iret=f90_bbgw(byref(cdp),byref(cipft),byref(cbbgw),byref(cdbbgwdd))
- bbgw[ipft,idi] = bbgw[ipft,idi-1] + cdbbgwdd.value*dd
-
- # diagnose bsap # (d,ipft,canopy_trim,asapw,bsap,dbsapdd)
- iret=f90_bsap(byref(cdc),byref(cipft),byref(ccanopy_trim),byref(casapw),byref(cbsap),byref(cdbsapdd))
- bsapd[ipft,idi] = cbsap.value # Biomass
- asapd[ipft,idi] = casapw.value # Area
-
- # integrate bsap
- iret=f90_bsap(byref(cdp),byref(cipft),byref(ccanopy_trim),byref(casapw),byref(cbsap),byref(cdbsapdd))
- bsapi[ipft,idi] = bsapi[ipft,idi-1] + cdbsapdd.value*dd
-
-
-
-
- # the metric that shan't be spoken
- # previous t-step derivatives are used for simplicity
- if cdhdd.value<0.000001:
- blmax_o_dbagwdh[ipft,idi] = None
- else:
- blmax_o_dbagwdh[ipft,idi] = blmaxi[ipft,idi-1]/(cdbagwdd.value/cdhdd.value)
-
- # the metric that shan't be spoken
- # previous t-step derivatives are used for simplicity
- blmax_o_dbagwdd[ipft,idi] = blmaxi[ipft,idi-1]/(cdbagwdd.value)
-
- # Diagnose bdead (bagw,bbgw,bsap,ipft,bdead,dbagwdd,dbbgwdd,dbsapdd,dbdeaddd)
-
- iret=f90_bdead(byref(c_double(bagwd[ipft,idi])), \
- byref(c_double(bbgw[ipft,idi])), \
- byref(c_double(bsapd[ipft,idi])), \
- byref(cipft), byref(cbdead), \
- byref(cdbagwdd),byref(cdbbgwdd), \
- byref(cdbsapdd),byref(cdbdeaddd))
- bdead[ipft,idi] = cbdead.value
-
-
- bagwr[ipft,idi] = (bdead[ipft,idi] + bsapd[ipft,idi]) * 0.6
-
-# Create the appropriate number of line-styles, colors and widths
-linestyles_base = ['-', '--', '-.', ':']
-linestyles=[]
-for i in range(int(math.floor(float(numpft)/float(len(linestyles_base))))):
- linestyles.extend(linestyles_base)
-for i in range(numpft-len(linestyles)):
- linestyles.append(linestyles_base[i])
-
-my_colors = DiscreteCubeHelix(numpft)
-
-
-mp.rcParams.update({'font.size': 14})
-mp.rcParams["savefig.directory"] = "" #os.chdir(os.path.dirname(__file__))
-
-legfs = 12
-lwidth = 2.0
-
-#code.interact(local=dict(globals(), **locals()))
-
-if(True):
- fig0 = plt.figure()
- figleg = plt.figure()
- ax = fig0.add_subplot(111)
- ax.axis("off")
- ax.set_axis_off()
- proxies = ()
- for ipft in range(numpft):
- proxies = proxies + (mp.lines.Line2D([],[], \
- linestyle=linestyles[ipft], \
- color=my_colors(ipft), \
- label=eparms['name'].vals[ipft], \
- linewidth=lwidth),)
- figleg.legend(handles=proxies,fontsize=12,frameon=False,labelspacing=0.25,loc='center')
- plt.show(block=False)
- plt.close(fig0)
-
-
-
-if(False):
- fig1_12 = plt.figure()
- for ipft in range(numpft):
- plt.plot(bagwd[ipft,:],bagwr[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('bagw [m]')
- plt.ylabel('bagr [m]')
- plt.title('')
- plt.grid(True)
- plt.savefig("plots/bagw_vs_bagwr.png")
-
-
-if(True):
- fig1 = plt.figure()
- figleg = plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,:],hi[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('height [m]')
- plt.title('Integrated Heights')
- plt.grid(True)
- plt.tight_layout()
-
-if(True):
- fig1_1 = plt.figure()
- for ipft in range(numpft):
- plt.plot(hd[ipft,:],hi[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('height (diagnosed) [m]')
- plt.ylabel('height (integrated) [m]')
- plt.title('Height')
- plt.grid(True)
- plt.savefig("plots/hdhi.png")
-
-if(False):
- fig2=plt.figure()
- for ipft in range(numpft):
- plt.plot(blmaxd[ipft,:],blmaxi[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diagnosed [kgC]')
- plt.ylabel('integrated [kgC]')
- plt.title('Maximum Leaf Biomass')
- plt.grid(True)
- plt.tight_layout()
-
-if(True):
- fig3=plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,:],blmaxi[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('mass [kgC]')
- plt.title('Maximum Leaf Biomass')
- plt.grid(True)
- plt.tight_layout()
-
-if(True):
- fig3_1=plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,1:15],blmaxi[ipft,1:15],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('mass [kgC]')
- plt.title('Maximum Leaf Biomass (saplings)')
- plt.grid(True)
- plt.tight_layout()
-
-
-if(True):
- fig4=plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,:],camin[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('[m2] (closed canopy)')
- plt.title('Crown Area')
- plt.grid(True)
- plt.tight_layout()
-
-if(True):
- fig4_1=plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,:],ldense[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('[kgC/m2] (closed canopy)')
- plt.title('Leaf Mass Per Crown Area')
- plt.grid(True)
- plt.tight_layout()
-
-
-if(True):
- fig6=plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,:],bagwi[ipft,:]/1000,linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('AGB [MgC]')
- plt.title('Above Ground Biomass')
- plt.grid(True)
- plt.tight_layout()
-
-if(False):
- fig6_1=plt.figure()
- for ipft in range(numpft):
- plt.plot(bagwd[ipft,:]/1000,bagwi[ipft,:]/1000,linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('AGBW deterministic [MgC]')
- plt.ylabel('AGBW integrated [MgC]')
- plt.title('Above Ground Biomass')
- plt.grid(True)
- plt.tight_layout()
-
-if(False):
- fig5=plt.figure()
- for ipft in range(numpft):
- gpmask = np.isfinite(blmax_o_dbagwdh[ipft,:])
- plt.plot(dbh[ipft,gpmask],blmax_o_dbagwdh[ipft,gpmask],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('growth potential: bl/(dAGB/dh) [m]')
- plt.title('Height Growth Potential')
- plt.grid(True)
- plt.tight_layout()
-
-if(False):
- fig6=plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,:],blmax_o_dbagwdd[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('diameter [cm]')
- plt.ylabel('growth potential: bl/(dAGB/dd) [cm]')
- plt.title('Diameter Growth Potential')
- plt.grid(True)
- plt.tight_layout()
-
-if(False):
- fig7=plt.figure()
- for ipft in range(numpft):
- plt.plot(bsapd[ipft,:],bsapi[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('deterministic [kgC]')
- plt.ylabel('integrated [kgC]')
- plt.title('Sapwood Biomass')
- plt.grid(True)
- plt.tight_layout()
-
-if(False):
- fig7_0=plt.figure()
- for ipft in range(numpft):
- plt.plot(dbh[ipft,:],bsapd[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- plt.xlabel('Diameter [cm]')
- plt.ylabel('[kgC]')
- plt.title('Sapwood Biomass')
- plt.grid(True)
- plt.tight_layout()
-
-if(True):
- fig7_2=plt.figure(figsize=(8,6))
- # Sapwood
- ax = fig7_2.add_subplot(221)
- for ipft in range(numpft):
- ax.plot(dbh[ipft,:],bsapd[ipft,:]/(bsapd[ipft,:]+blmaxi[ipft,:]+bfrmax[ipft,:]+bstore[ipft,:]), \
- linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- ax.set_xlabel('diameter [cm]')
- ax.set_ylabel('[kgC/kgC]')
- ax.set_title('Sapwood (fraction of live)')
- ax.grid(True)
- # Leaf
- ax = fig7_2.add_subplot(222)
- for ipft in range(numpft):
- ax.plot(dbh[ipft,:],blmaxi[ipft,:]/(bsapd[ipft,:]+blmaxi[ipft,:]+bfrmax[ipft,:]+bstore[ipft,:]), \
- linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- ax.set_xlabel('diameter [cm]')
- ax.set_ylabel('[kgC/kgC]')
- ax.set_title('Leaf (fraction of live)')
- ax.grid(True)
- # Fine Root
- ax = fig7_2.add_subplot(223)
- for ipft in range(numpft):
- ax.plot(dbh[ipft,:],bfrmax[ipft,:]/(bsapd[ipft,:]+blmaxi[ipft,:]+bfrmax[ipft,:]+bstore[ipft,:]), \
- linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- ax.set_xlabel('diameter [cm]')
- ax.set_ylabel('[kgC/kgC]')
- ax.set_title('Fine-Root (fraction of live)')
- ax.grid(True)
- # Storage
- ax = fig7_2.add_subplot(224)
- for ipft in range(numpft):
- ax.plot(dbh[ipft,:],bstore[ipft,:]/(bsapd[ipft,:]+blmaxi[ipft,:]+bfrmax[ipft,:]+bstore[ipft,:]), \
- linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- ax.set_xlabel('diameter [cm]')
- ax.set_ylabel('[kgC/kgC]')
- ax.set_title('Storage (fraction of live)')
- ax.grid(True)
-
- plt.tight_layout()
-
-
-
-if(True):
- fig8=plt.figure()
- ax = fig8.add_subplot(111)
- for ipft in range(numpft):
- ax.plot(dbh[ipft,:],treelai[ipft,:],linestyle=linestyles[ipft],color=my_colors(ipft),linewidth=lwidth)
- ax.ticklabel_format(style='plain')
- ax.set_xlabel('diameter [cm]')
- ax.set_ylabel('[m2/m2]')
- ax.set_title('Untrimmed In-Crown LAI')
- ax.grid(True)
- plt.tight_layout()
-
-
-
-
-plt.show()
diff --git a/functional_unit_testing/allometry/f90src/AllomUnitWrap.F90_in b/functional_unit_testing/allometry/f90src/AllomUnitWrap.F90_in
deleted file mode 100644
index 471314b0bf..0000000000
--- a/functional_unit_testing/allometry/f90src/AllomUnitWrap.F90_in
+++ /dev/null
@@ -1,218 +0,0 @@
-
-! =======================================================================================
-!
-! This file is an alternative to key files in the fates
-! filesystem. Noteably, we replace fates_r8 and fates_in
-! with types that work with "ctypes". This is
-! a key step in working with python
-!
-! We also wrap FatesGlobals to reduce the dependancy
-! cascade that it pulls in from shr_log_mod.
-!
-! =======================================================================================
-
-module shr_log_mod
-
- use iso_c_binding, only : c_char
- use iso_c_binding, only : c_int
-
- contains
-
- function shr_log_errMsg(source, line) result(ans)
- character(kind=c_char,len=*), intent(in) :: source
- integer(c_int), intent(in) :: line
- character(kind=c_char,len=128) :: ans
-
- ans = "source: " // trim(source) // " line: "
- end function shr_log_errMsg
-
-end module shr_log_mod
-
-
-module FatesGlobals
-
- contains
-
- integer function fates_log()
- fates_log = -1
- end function fates_log
-
- subroutine fates_endrun(msg)
-
- implicit none
- character(len=*), intent(in) :: msg ! string to be printed
-
- stop
-
- end subroutine fates_endrun
-
-end module FatesGlobals
-
-
-module EDTypesMod
-
- use iso_c_binding, only : r8 => c_double
-
- integer, parameter :: nclmax = 2
- integer, parameter :: nlevleaf = 30
- real(r8), parameter :: dinc_ed = 1.0_r8
-
-end module EDTypesMod
-
-
-module EDPftvarcon
-
- use iso_c_binding, only : r8 => c_double
- use iso_c_binding, only : i4 => c_int
- use iso_c_binding, only : c_char
-
- integer,parameter :: SHR_KIND_CS = 80 ! short char
-
- type, public :: EDPftvarcon_inst_type
-
- ! VARIABLE-DEFINITIONS-HERE (DO NOT REMOVE THIS LINE, OR MOVE IT)
-
- end type EDPftvarcon_inst_type
-
- type ptr_var1
- real(r8), dimension(:), pointer :: var_rp
- integer(i4), dimension(:), pointer :: var_ip
- character(len=shr_kind_cs) :: var_name
- integer :: vtype
- end type ptr_var1
-
- type ptr_var2
- real(r8), dimension(:,:), pointer :: var_rp
- integer(i4), dimension(:,:), pointer :: var_ip
- character(len=shr_kind_cs) :: var_name
- integer :: vtype
- end type ptr_var2
-
- type EDPftvarcon_ptr_type
- type(ptr_var1), allocatable :: var1d(:)
- type(ptr_var2), allocatable :: var2d(:)
- end type EDPftvarcon_ptr_type
-
-
- type(EDPftvarcon_inst_type), public :: EDPftvarcon_inst ! ED ecophysiological constants structure
- type(EDPftvarcon_ptr_type), public :: EDPftvarcon_ptr ! Pointer structure for obj-oriented id
-
- integer :: numparm1d ! Number of different PFT parameters
- integer :: numparm2d
- integer :: numpft
-
- logical, parameter :: debug = .true.
-
-contains
-
-
- subroutine EDPftvarconPySet(ipft,rval,ival,name)
-
- implicit none
- ! Arguments
- integer(i4),intent(in) :: ipft
- character(kind=c_char,len=*), intent(in) :: name
- real(r8),intent(in) :: rval
- integer(i4),intent(in) :: ival
- ! Locals
- logical :: npfound
- integer :: ip
- integer :: namelen
-
- namelen = len(trim(name))
-
- if(debug) print*,"F90: ARGS: ",trim(name)," IPFT: ",ipft," RVAL: ",rval," IVAL: ",ival
-
- ip=0
- npfound = .true.
- do ip=1,numparm1d
-
- if (trim(name) == trim(EDPftvarcon_ptr%var1d(ip)%var_name ) ) then
- print*,"F90: Found ",trim(name)," in lookup table"
- npfound = .false.
- if(EDPftvarcon_ptr%var1d(ip)%vtype == 1) then ! real
- EDPftvarcon_ptr%var1d(ip)%var_rp(ipft) = rval
- elseif(EDPftvarcon_ptr%var1d(ip)%vtype == 2) then ! integer
- EDPftvarcon_ptr%var1d(ip)%var_ip(ipft) = ival
- else
- print*,"F90: STRANGE TYPE"
- stop
- end if
- end if
- end do
-
- if(npfound)then
- print*,"F90: The parameter you loaded DNE: ",name(:)
- stop
- end if
-
- do ip=1,numparm2d
- if (trim(name) == trim(EDPftvarcon_ptr%var2d(ip)%var_name)) then
- print*,"F90: Found ",trim(name)," in lookup table"
- print*,"BUT... WE AVOID USING 2D VARIABLES FOR NOW..."
- print*,"REMOVE THIS TEST"
- stop
- end if
- end do
-
-
- ! Perform a check to see if the target array is being filled
- if (trim(name) == 'fates_allom_d2h1') then
- if (EDPftvarcon_inst%allom_d2h1(ipft) == rval) then
- print*,"F90: POINTER CHECK PASSES:",rval," = ",EDPftvarcon_inst%allom_d2h1(ipft)
- else
- print*,"F90: POINTER CHECK FAILS:",rval," != ",EDPftvarcon_inst%allom_d2h1(ipft)
- stop
- end if
- end if
-
- if (trim(name) == 'fates_wood_density' ) then
- if (EDPftvarcon_inst%wood_density(ipft) == rval) then
- print*,"F90: POINTER CHECK PASSES:",rval," = ",EDPftvarcon_inst%wood_density(ipft)
- else
- print*,"F90: POINTER CHECK FAILS:",rval," != ",EDPftvarcon_inst%wood_density(ipft)
- stop
- end if
- end if
-
- return
- end subroutine EDPftvarconPySet
-
-
- subroutine EDPftvarconAlloc(numpft_in)
- !
-
- ! !ARGUMENTS:
- integer(i4), intent(in) :: numpft_in
- ! LOCALS:
- integer :: iv1 ! The parameter incrementer
- integer :: iv2
- !------------------------------------------------------------------------
-
- numpft = numpft_in
-
- allocate( EDPftvarcon_ptr%var1d(100)) ! Make this plenty large
- allocate( EDPftvarcon_ptr%var2d(100))
- iv1=0
- iv2=0
-
- ! POINTER-SPECIFICATION-HERE (DO NOT REMOVE THIS LINE, OR MOVE IT)
-
-! allocate( EDPftvarcon_inst%allom_dbh_maxheight (1:numpft)); EDPftvarcon_inst%allom_dbh_maxheight (:) = nan
-! iv = iv + 1
-! EDPftvarcon_ptr%var1d(iv)%var_name = "fates_allom_dbh_maxheight"
-! EDPftvarcon_ptr%var1d(iv)%var_rp => EDPftvarcon_inst%allom_dbh_maxheight
-! EDPftvarcon_ptr%var1d(iv)%vtype = 1
-
-
- numparm1d = iv1
- numparm2d = iv2
-
-
- print*,"F90: ALLOCATED ",numparm1d," PARAMETERS, FOR ",numpft," PFTs"
-
-
- return
- end subroutine EDPftvarconAlloc
-
-end module EDPftvarcon
diff --git a/functional_unit_testing/allometry/include/README b/functional_unit_testing/allometry/include/README
deleted file mode 100644
index bfa612f78d..0000000000
--- a/functional_unit_testing/allometry/include/README
+++ /dev/null
@@ -1 +0,0 @@
-This holds the place of the include folder
\ No newline at end of file
diff --git a/functional_unit_testing/allometry/plots/README b/functional_unit_testing/allometry/plots/README
deleted file mode 100644
index c32df9df9a..0000000000
--- a/functional_unit_testing/allometry/plots/README
+++ /dev/null
@@ -1 +0,0 @@
-Placeholder for the folder
\ No newline at end of file
diff --git a/functional_unit_testing/allometry/simple_build.sh b/functional_unit_testing/allometry/simple_build.sh
deleted file mode 100755
index 114c82a15d..0000000000
--- a/functional_unit_testing/allometry/simple_build.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/bash
-
-FC='gfortran -g -shared -fPIC'
-
-# First copy over the FatesConstants file, but change the types of the fates_r8 and fates_int
-
-old_fates_r8_str=`grep -e integer ../../main/FatesConstantsMod.F90 | grep fates_r8 | sed 's/^[ \t]*//;s/[ \t]*$//'`
-new_fates_r8_str='use iso_c_binding, only: fates_r8 => c_double'
-
-old_fates_int_str=`grep -e integer ../../main/FatesConstantsMod.F90 | grep fates_int | sed 's/^[ \t]*//;s/[ \t]*$//'`
-new_fates_int_str='use iso_c_binding, only: fates_int => c_int'
-
-# Add the new lines (need position change, don't swap)
-
-sed "/implicit none/i $new_fates_r8_str" ../../main/FatesConstantsMod.F90 > f90src/FatesConstantsMod.F90
-sed -i "/implicit none/i $new_fates_int_str" f90src/FatesConstantsMod.F90
-
-# Delete the old lines
-
-sed -i "/$old_fates_r8_str/d" f90src/FatesConstantsMod.F90
-sed -i "/$old_fates_int_str/d" f90src/FatesConstantsMod.F90
-
-sed -i "/private/d" f90src/FatesConstantsMod.F90
-
-# This re-writes the wrapper so that it uses all the correct parameters
-# in FatesAllometryMod.F90
-python AutoGenVarCon.py
-
-
-# Procedure for auto-generating AllomUnitWrap
-# 1) scan FatesAllometry and create list of EDPftVarcon_inst variables
-# 2) scan EDpftVarcon and get the name of the in-file parameter names associated
-# with these variables
-
-
-
-
-rm -f include/*.o
-rm -f include/*.mod
-
-
-# Build the new file with constants
-
-${FC} -I include/ -J include/ -o include/FatesConstantsMod.o f90src/FatesConstantsMod.F90
-
-${FC} -I include/ -J include/ -o include/AllomUnitWrap.o f90src/AllomUnitWrap.F90
-
-${FC} -I include/ -J include/ -o include/FatesAllometryMod.o ../../biogeochem/FatesAllometryMod.F90
-
-
-#${FC} -g -o include/FatesConstantsMod.o ../main/FatesConstantsMod.F90
-
-#gfortran -shared -fPIC -g -o include/EDTypesMod.o ../main/EDTypesMod.F90
-
-
-
-
-#gfortran
diff --git a/functional_unit_testing/math_utils/MathUtilsDriver.py b/functional_unit_testing/math_utils/MathUtilsDriver.py
deleted file mode 100644
index 4add288126..0000000000
--- a/functional_unit_testing/math_utils/MathUtilsDriver.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# =======================================================================================
-#
-# For usage: $python HydroUTestDriver.py --help
-#
-# This script runs unit tests on the hydraulics functions.
-#
-#
-# =======================================================================================
-
-import matplotlib as mpl
-#mpl.use('Agg')
-import matplotlib.pyplot as plt
-from datetime import datetime
-import argparse
-#from matplotlib.backends.backend_pdf import PdfPages
-import platform
-import numpy as np
-import os
-import sys
-import getopt
-import code # For development: code.interact(local=dict(globals(), **locals()))
-import time
-import imp
-import ctypes
-from ctypes import *
-from operator import add
-
-
-#CDLParse = imp.load_source('CDLParse','../shared/py_src/CDLParse.py')
-#F90ParamParse = imp.load_source('F90ParamParse','../shared/py_src/F90ParamParse.py')
-PyF90Utils = imp.load_source('PyF90Utils','../shared/py_src/PyF90Utils.py')
-
-
-#from CDLParse import CDLParseDims, CDLParseParam, cdl_param_type
-#from F90ParamParse import f90_param_type, GetSymbolUsage, GetPFTParmFileSymbols, MakeListUnique
-
-from PyF90Utils import c8, ci, cchar, c8_arr, ci_arr
-
-# Load the fortran objects via CTYPES
-
-f90_unitwrap_obj = ctypes.CDLL('bld/UnitWrapMod.o',mode=ctypes.RTLD_GLOBAL)
-f90_constants_obj = ctypes.CDLL('bld/FatesConstantsMod.o',mode=ctypes.RTLD_GLOBAL)
-f90_fatesutils_obj = ctypes.CDLL('bld/FatesUtilsMod.o',mode=ctypes.RTLD_GLOBAL)
-
-# Alias the F90 functions, specify the return type
-# -----------------------------------------------------------------------------------
-
-neighbor_dist = f90_fatesutils_obj.__fatesutilsmod_MOD_getneighbordistance
-#quadratic_f = f90_fatesutils_obj.__fatesutilsmod_MOD_quadratic_f
-quadratic_roots = f90_fatesutils_obj.__fatesutilsmod_MOD_quadraticroots
-quadratic_sroots = f90_fatesutils_obj.__fatesutilsmod_MOD_quadraticrootssridharachary
-
-# Some constants
-rwcft = [1.0,0.958,0.958,0.958]
-rwccap = [1.0,0.947,0.947,0.947]
-pm_leaf = 1
-pm_stem = 2
-pm_troot = 3
-pm_aroot = 4
-pm_rhiz = 5
-
-# These parameters are matched with the indices in FATES-HYDRO
-vg_type = 1
-cch_type = 2
-tfs_type = 3
-
-isoil1 = 0 # Top soil layer parameters (@BCI)
-isoil2 = 1 # Bottom soil layer parameters
-
-# Constants for rhizosphere
-watsat = [0.567, 0.444]
-sucsat = [159.659, 256.094]
-bsw = [6.408, 9.27]
-
-unconstrained = True
-
-
-# ========================================================================================
-# ========================================================================================
-# Main
-# ========================================================================================
-# ========================================================================================
-
-def main(argv):
-
- # First check to make sure python 2.7 is being used
- version = platform.python_version()
- verlist = version.split('.')
-
- #if( not ((verlist[0] == '2') & (verlist[1] == '7') & (int(verlist[2])>=15) ) ):
- # print("The PARTEH driver mus be run with python 2.7")
- # print(" with tertiary version >=15.")
- # print(" your version is {}".format(version))
- # print(" exiting...")
- # sys.exit(2)
-
- # Read in the arguments
- # =======================================================================================
-
- # parser = argparse.ArgumentParser(description='Parse command line arguments to this script.')
- # parser.add_argument('--cdl-file', dest='cdlfile', type=str, \
- # help="Input CDL filename. Required.", required=True)
- # args = parser.parse_args()
-
- # Set number of analysis points
-
- # y = ax2 + bx + c
-
- a = [1,1,5,1.5]
- b = [-2,7,10,3.2]
- c = [1,12,3,1.1]
-
- cd_r1 = c_double(-9.0)
- cd_r2 = c_double(-9.0)
-
- r1 = np.zeros([3,1])
- r2 = np.zeros([3,1])
-
- for ic in range(len(a)):
-
- #iret = quadratic_f(c8(a[ic]),c8(b[ic]),c8(c[ic]),byref(cd_r1),byref(cd_r2))
- #r1[0] = cd_r1.value
- #r2[0] = cd_r2.value
-
- iret = quadratic_roots(c8(a[ic]),c8(b[ic]),c8(c[ic]),byref(cd_r1),byref(cd_r2))
- r1[1] = cd_r1.value
- r2[1] = cd_r2.value
-
- iret = quadratic_sroots(c8(a[ic]),c8(b[ic]),c8(c[ic]),byref(cd_r1),byref(cd_r2))
- r1[2] = cd_r2.value
- r2[2] = cd_r1.value
-
- print(a[ic],b[ic],c[ic])
- print(r1)
- print(r2)
-
- #PlotQuadAndRoots(a[ic],b[ic],c[ic],r1,r2)
-
-
-def PlotQuadAndRoots(a,b,c,d,r1,r2):
-
- fig, axs = plt.subplots(ncols=1,nrows=1,figsize=(8,8))
- ax1s = axs.reshape(-1)
- ic=0
-
- npts = 1000
-
- for i in range(npts):
- print(i)
-
-
-
-# code.interact(local=dict(globals(), **locals()))
-
-# Helper code to plot negative logs
-
-def semilogneg(x):
-
- y = np.sign(x)*np.log(abs(x))
- return(y)
-
-def semilog10net(x):
-
- y = np.sign(x)*np.log10(abs(x))
- return(y)
-
-
-# =======================================================================================
-# This is the actual call to main
-
-if __name__ == "__main__":
- main(sys.argv)
diff --git a/functional_unit_testing/math_utils/bld/README b/functional_unit_testing/math_utils/bld/README
deleted file mode 100644
index 4e67e5f091..0000000000
--- a/functional_unit_testing/math_utils/bld/README
+++ /dev/null
@@ -1 +0,0 @@
-PLACEHOLDER FOR DIR
\ No newline at end of file
diff --git a/functional_unit_testing/math_utils/build_math_objects.sh b/functional_unit_testing/math_utils/build_math_objects.sh
deleted file mode 100755
index 40ac3eb9d1..0000000000
--- a/functional_unit_testing/math_utils/build_math_objects.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/bin/bash
-
-# Path to FATES src
-
-FC='gfortran'
-
-F_OPTS="-shared -fPIC -g -ffpe-trap=zero,overflow,underflow -fbacktrace -fbounds-check"
-#F_OPTS="-shared -fPIC -O"
-
-
-MOD_FLAG="-J"
-
-rm -f bld/*.o
-rm -f bld/*.mod
-
-
-# First copy over the FatesConstants file, but change the types of the fates_r8 and fates_int
-
-old_fates_r8_str=`grep -e integer ../../main/FatesConstantsMod.F90 | grep fates_r8 | sed 's/^[ \t]*//;s/[ \t]*$//'`
-new_fates_r8_str='use iso_c_binding, only: fates_r8 => c_double'
-
-old_fates_int_str=`grep -e integer ../../main/FatesConstantsMod.F90 | grep fates_int | sed 's/^[ \t]*//;s/[ \t]*$//'`
-new_fates_int_str='use iso_c_binding, only: fates_int => c_int'
-
-# Add the new lines (need position change, don't swap)
-
-sed "/implicit none/i $new_fates_r8_str" ../../main/FatesConstantsMod.F90 > f90_src/FatesConstantsMod.F90
-sed -i "/implicit none/i $new_fates_int_str" f90_src/FatesConstantsMod.F90
-sed -i "/private /i public :: fates_r8" f90_src/FatesConstantsMod.F90
-sed -i "/private /i public :: fates_int" f90_src/FatesConstantsMod.F90
-
-# Delete the old lines
-
-sed -i "/$old_fates_r8_str/d" f90_src/FatesConstantsMod.F90
-sed -i "/$old_fates_int_str/d" f90_src/FatesConstantsMod.F90
-
-# Build the new file with constants
-
-${FC} ${F_OPTS} -I bld/ ${MOD_FLAG} bld/ -o bld/FatesConstantsMod.o f90_src/FatesConstantsMod.F90
-
-${FC} ${F_OPTS} -I bld/ ${MOD_FLAG} bld/ -o bld/UnitWrapMod.o f90_src/UnitWrapMod.F90
-
-${FC} ${F_OPTS} -I bld/ ${MOD_FLAG} bld/ -o bld/FatesUtilsMod.o ../../main/FatesUtilsMod.F90
-
-
-
-
diff --git a/functional_unit_testing/math_utils/f90_src/UnitWrapMod.F90 b/functional_unit_testing/math_utils/f90_src/UnitWrapMod.F90
deleted file mode 100644
index f12311655a..0000000000
--- a/functional_unit_testing/math_utils/f90_src/UnitWrapMod.F90
+++ /dev/null
@@ -1,49 +0,0 @@
-
-! =======================================================================================
-!
-! This file is an alternative to key files in the fates
-! filesystem. Noteably, we replace fates_r8 and fates_in
-! with types that work with "ctypes". This is
-! a key step in working with python
-!
-! We also wrap FatesGlobals to reduce the dependancy
-! cascade that it pulls in from shr_log_mod.
-!
-! =======================================================================================
-
-module shr_log_mod
-
- use iso_c_binding, only : c_char
- use iso_c_binding, only : c_int
-
- contains
-
- function shr_log_errMsg(source, line) result(ans)
- character(kind=c_char,len=*), intent(in) :: source
- integer(c_int), intent(in) :: line
- character(kind=c_char,len=128) :: ans
-
- ans = "source: " // trim(source) // " line: "
- end function shr_log_errMsg
-
-end module shr_log_mod
-
-
-module FatesGlobals
-
- contains
-
- integer function fates_log()
- fates_log = -1
- end function fates_log
-
- subroutine fates_endrun(msg)
-
- implicit none
- character(len=*), intent(in) :: msg ! string to be printed
-
- stop
-
- end subroutine fates_endrun
-
-end module FatesGlobals
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index dfd5eaba2a..17156c7e3e 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -10,5 +10,30 @@ list(APPEND clm_sources
FatesParametersInterface.F90
FatesUtilsMod.F90
)
+
+list(APPEND fates_sources
+ FatesConstantsMod.F90
+ FatesGlobals.F90
+ FatesParametersInterface.F90
+ )
+sourcelist_to_parent(fates_sources)
sourcelist_to_parent(clm_sources)
+
+list(APPEND fates_sources
+ FatesConstantsMod.F90
+ FatesGlobals.F90
+ FatesParametersInterface.F90
+ FatesInterfaceTypesMod.F90
+ EDPftvarcon.F90
+ EDParamsMod.F90
+ EDTypesMod.F90
+ FatesHydraulicsMemMod.F90
+ FatesRunningMeanMod.F90
+ FatesParameterDerivedMod.F90
+ FatesSizeAgeTypeIndicesMod.F90
+ FatesIntegratorsMod.F90
+ FatesUtilsMod.F90
+ FatesSynchronizedParamsMod.F90)
+
+sourcelist_to_parent(fates_sources)
diff --git a/main/EDInitMod.F90 b/main/EDInitMod.F90
index 4ab881c4f6..95da8cbfa8 100644
--- a/main/EDInitMod.F90
+++ b/main/EDInitMod.F90
@@ -10,7 +10,10 @@ module EDInitMod
use FatesConstantsMod , only : fates_unset_int
use FatesConstantsMod , only : primaryland
use FatesConstantsMod , only : nearzero
+ use FatesConstantsMod , only : rsnbl_math_prec
+ use EDTypesMod , only : min_patch_area_forced
use FatesConstantsMod , only : n_landuse_cats
+ use FatesConstantsMod , only : is_crop
use FatesConstantsMod , only : fates_unset_r8
use FatesConstantsMod , only : nearzero, area_error_4, area_error_3
use FatesGlobals , only : endrun => fates_endrun
@@ -62,7 +65,6 @@ module EDInitMod
use FatesInterfaceTypesMod , only : nlevdamage
use FatesInterfaceTypesMod , only : hlm_use_nocomp
use FatesInterfaceTypesMod , only : nlevage
-
use FatesAllometryMod , only : h2d_allom
use FatesAllometryMod , only : h_allom
use FatesAllometryMod , only : bagw_allom
@@ -91,10 +93,16 @@ module EDInitMod
use FatesSizeAgeTypeIndicesMod,only : get_age_class_index
use DamageMainMod, only : undamaged_class
use FatesConstantsMod, only : n_term_mort_types
- use FatesInterfaceTypesMod , only : hlm_num_luh2_transitions
+ use FatesInterfaceTypesMod, only : hlm_num_luh2_transitions
+ use FatesConstantsMod, only : nocomp_bareground_land, nocomp_bareground
+ use FatesConstantsMod, only : min_nocomp_pftfrac_perlanduse
+ use EdTypesMod, only : dump_site
+ use SFNesterovMod, only : nesterov_index
+
! CIME GLOBALS
use shr_log_mod , only : errMsg => shr_log_errMsg
+ use shr_infnan_mod , only : isnan => shr_infnan_isnan
implicit none
private
@@ -186,11 +194,8 @@ subroutine init_site_vars( site_in, bc_in, bc_out )
allocate(site_in%dz_soil(site_in%nlevsoil))
allocate(site_in%z_soil(site_in%nlevsoil))
- if (hlm_use_nocomp .eq. itrue .and. hlm_use_fixed_biogeog .eq. itrue) then
- allocate(site_in%area_pft(0:numpft))
- else ! SP and nocomp require a bare-ground patch.
- allocate(site_in%area_pft(1:numpft))
- endif
+ allocate(site_in%area_pft(1:numpft,1:n_landuse_cats))
+ allocate(site_in%landuse_vector_gt_min(1:n_landuse_cats))
allocate(site_in%use_this_pft(1:numpft))
allocate(site_in%area_by_age(1:nlevage))
@@ -220,6 +225,9 @@ subroutine init_site_vars( site_in, bc_in, bc_out )
allocate(site_in%seed_in(1:numpft))
allocate(site_in%seed_out(1:numpft))
+ allocate(nesterov_index :: site_in%fireWeather)
+ call site_in%fireWeather%Init()
+
end subroutine init_site_vars
! ============================================================================
@@ -268,9 +276,9 @@ subroutine zero_site( site_in )
! Disturbance rates tracking
site_in%primary_land_patchfusion_error = 0.0_r8
site_in%disturbance_rates(:,:,:) = 0.0_r8
+ site_in%landuse_transition_matrix(:,:) = 0.0_r8
! FIRE
- site_in%acc_ni = 0.0_r8 ! daily nesterov index accumulating over time. time unlimited theoretically.
site_in%FDI = 0.0_r8 ! daily fire danger index (0-1)
site_in%NF = 0.0_r8 ! daily lightning strikes per km2
site_in%NF_successful = 0.0_r8 ! daily successful iginitions per km2
@@ -341,14 +349,19 @@ subroutine zero_site( site_in )
! canopy spread
site_in%spread = 0._r8
+
+ site_in%area_pft(:,:) = 0._r8
+ site_in%area_bareground = 0._r8
+
! Seed dispersal
site_in%seed_in(:) = 0.0_r8
site_in%seed_out(:) = 0.0_r8
- site_in%area_pft(:) = 0._r8
site_in%use_this_pft(:) = fates_unset_int
site_in%area_by_age(:) = 0._r8
+ site_in%transition_landuse_from_off_to_on = .false.
+
end subroutine zero_site
! ============================================================================
@@ -357,6 +370,8 @@ subroutine set_site_properties( nsites, sites,bc_in )
! !DESCRIPTION:
!
! !USES:
+ use EDParamsMod, only : crop_lu_pft_vector
+ use EDParamsMod, only : max_nocomp_pfts_by_landuse
!
! !ARGUMENTS
@@ -369,7 +384,6 @@ subroutine set_site_properties( nsites, sites,bc_in )
integer :: cstat ! cold status phenology flag
real(r8) :: GDD
integer :: dstat ! drought status phenology flag
- real(r8) :: acc_NI
real(r8) :: liqvolmem
real(r8) :: smpmem
real(r8) :: elong_factor ! Elongation factor (0 - fully off; 1 - fully on)
@@ -385,6 +399,9 @@ subroutine set_site_properties( nsites, sites,bc_in )
real(r8) :: sumarea ! area of PFTs in nocomp mode.
integer :: hlm_pft ! used in fixed biogeog mode
integer :: fates_pft ! used in fixed biogeog mode
+ integer :: i_landusetype
+ real(r8) :: temp_vec(numpft) ! temporary vector
+ integer :: i_pftcount
!----------------------------------------------------------------------
@@ -401,7 +418,6 @@ subroutine set_site_properties( nsites, sites,bc_in )
cndleafon = 0
cndleafoff = 0
cstat = phen_cstat_notcold ! Leaves are on
- acc_NI = 0.0_r8
dstat = phen_dstat_moiston ! Leaves are on
dleafoff = 300
dleafon = 100
@@ -436,10 +452,9 @@ subroutine set_site_properties( nsites, sites,bc_in )
sites(s)%dstatus(1:numpft) = dstat
sites(s)%elong_factor(1:numpft) = elong_factor
- sites(s)%acc_NI = acc_NI
sites(s)%NF = 0.0_r8
sites(s)%NF_successful = 0.0_r8
- sites(s)%area_pft(:) = 0.0_r8
+ sites(s)%area_pft(:,:) = 0.0_r8
do ft = 1,numpft
sites(s)%rec_l2fr(ft,:) = prt_params%allom_l2fr(ft)
@@ -450,66 +465,121 @@ subroutine set_site_properties( nsites, sites,bc_in )
sites(s)%ema_npp = -9999._r8
if(hlm_use_fixed_biogeog.eq.itrue)then
- ! MAPPING OF FATES PFTs on to HLM_PFTs
- ! add up the area associated with each FATES PFT
- ! where pft_areafrac is the area of land in each HLM PFT and (from surface dataset)
- ! hlm_pft_map is the area of that land in each FATES PFT (from param file)
-
- do hlm_pft = 1,size( EDPftvarcon_inst%hlm_pft_map,2)
- do fates_pft = 1,numpft ! loop round all fates pfts for all hlm pfts
- sites(s)%area_pft(fates_pft) = sites(s)%area_pft(fates_pft) + &
- EDPftvarcon_inst%hlm_pft_map(fates_pft,hlm_pft) * bc_in(s)%pft_areafrac(hlm_pft)
- end do
- end do !hlm_pft
- do ft = 1,numpft
- if(sites(s)%area_pft(ft).lt.0.01_r8.and.sites(s)%area_pft(ft).gt.0.0_r8)then
- if(debug) write(fates_log(),*) 'removing small pft patches',s,ft,sites(s)%area_pft(ft)
- sites(s)%area_pft(ft)=0.0_r8
- ! remove tiny patches to prevent numerical errors in terminate patches
+ use_fates_luh_if: if (hlm_use_luh .eq. itrue) then
+ ! MAPPING OF FATES PFTs on to HLM_PFTs with land use
+ ! add up the area associated with each FATES PFT
+ ! where pft_areafrac_lu is the area of land in each HLM PFT and land use type (from surface dataset)
+ ! hlm_pft_map is the area of that land in each FATES PFT (from param file)
+
+ ! First check for NaNs in bc_in(s)%pft_areafrac_lu. If so, make everything bare ground.
+ if ( .not. (any( isnan( bc_in(s)%pft_areafrac_lu (:,:) )) .or. isnan( bc_in(s)%baregroundfrac))) then
+ do i_landusetype = 1, n_landuse_cats
+ if (.not. is_crop(i_landusetype)) then
+ do hlm_pft = 1,size( EDPftvarcon_inst%hlm_pft_map,2)
+ do fates_pft = 1,numpft ! loop round all fates pfts for all hlm pfts
+ sites(s)%area_pft(fates_pft,i_landusetype) = sites(s)%area_pft(fates_pft,i_landusetype) + &
+ EDPftvarcon_inst%hlm_pft_map(fates_pft,hlm_pft) * bc_in(s)%pft_areafrac_lu(hlm_pft,i_landusetype)
+ end do
+ end do !hlm_pft
+ else
+ ! for crops, we need to use different logic because the bc_in(s)%pft_areafrac_lu() information only exists for natural PFTs
+ sites(s)%area_pft(crop_lu_pft_vector(i_landusetype),i_landusetype) = 1._r8
+ endif
+ end do
+
+ sites(s)%area_bareground = bc_in(s)%baregroundfrac
+ else
+ !if ( all( isnan( bc_in(s)%pft_areafrac_lu (:,:))) .and. isnan(bc_in(s)%baregroundfrac)) then
+ ! if given all NaNs, then make everything bare ground
+ sites(s)%area_bareground = 1._r8
+ sites(s)%area_pft(:,:) = 0._r8
+ write(fates_log(),*) 'Nan values for pftareafrac. dumping site info.'
+ call dump_site(sites(s))
+ !else
+ ! ! if only some things are NaN but not all, then something terrible has probably happened. crash.
+ ! write(fates_log(),*) 'some but, not all, of the data in the PFT by LU matrix at this site is NaN.'
+ ! write(fates_log(),*) 'recommend checking the dataset to see what has happened.'
+ ! call endrun(msg=errMsg(sourcefile, __LINE__))
+ !endif
endif
- if(sites(s)%area_pft(ft).lt.0._r8)then
- write(fates_log(),*) 'negative area',s,ft,sites(s)%area_pft(ft)
- call endrun(msg=errMsg(sourcefile, __LINE__))
- end if
- sites(s)%area_pft(ft)= sites(s)%area_pft(ft) * AREA ! rescale units to m2.
+
+ else
+ ! MAPPING OF FATES PFTs on to HLM_PFTs
+ ! add up the area associated with each FATES PFT
+ ! where pft_areafrac is the area of land in each HLM PFT and (from surface dataset)
+ ! hlm_pft_map is the area of that land in each FATES PFT (from param file)
+
+ do hlm_pft = 1,size( EDPftvarcon_inst%hlm_pft_map,2)
+ do fates_pft = 1,numpft ! loop round all fates pfts for all hlm pfts
+ sites(s)%area_pft(fates_pft,primaryland) = sites(s)%area_pft(fates_pft,primaryland) + &
+ EDPftvarcon_inst%hlm_pft_map(fates_pft,hlm_pft) * bc_in(s)%pft_areafrac(hlm_pft)
+ end do
+ sites(s)%area_bareground = bc_in(s)%pft_areafrac(0)
+ end do !hlm_pft
+
+ endif use_fates_luh_if
+
+ ! handle some edge cases
+ do i_landusetype = 1, n_landuse_cats
+ do ft = 1,numpft
+
+ ! remove tiny patches to prevent numerical errors in terminate patches
+ if (sites(s)%area_pft(ft, i_landusetype) .lt. min_nocomp_pftfrac_perlanduse &
+ .and. sites(s)%area_pft(ft, i_landusetype) .gt. nearzero) then
+ if(debug) write(fates_log(),*) 'removing small numbers in site%area_pft',s,ft,i_landusetype,sites(s)%area_pft(ft, i_landusetype)
+ sites(s)%area_pft(ft, i_landusetype)=0.0_r8
+ endif
+
+ ! if any areas are negative, then end run
+ if(sites(s)%area_pft(ft, i_landusetype).lt.0._r8)then
+ write(fates_log(),*) 'negative area',s,ft,i_landusetype,sites(s)%area_pft(ft, i_landusetype)
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+ end do
end do
- ! re-normalize PFT area to ensure it sums to one.
- ! note that in areas of 'bare ground' (PFT 0 in CLM/ELM)
- ! the bare ground will no longer be proscribed and should emerge from FATES
- ! this may or may not be the right way to deal with this?
+ ! if in nocomp mode, and the number of nocomp PFTs of a given land use type is greater than the maximum number of patches
+ ! allowed to be allocated for that land use type, then only keep the number of PFTs correspondign to the number of patches
+ ! allowed on that land use type, starting with the PFTs with greatest area coverage and working down
+ if (hlm_use_nocomp .eq. itrue) then
+ do i_landusetype = 1, n_landuse_cats
+ ! count how many PFTs have areas greater than zero and compare to the number of patches allowed
+ if (COUNT(sites(s)%area_pft(:, i_landusetype) .gt. 0._r8) > max_nocomp_pfts_by_landuse(i_landusetype)) then
+ ! write current vector to log file
+ if(debug) write(fates_log(),*) 'too many PFTs for LU type ', i_landusetype, sites(s)%area_pft(:, i_landusetype)
+
+ ! start from largest area, put that PFT's area into a temp vector, and then work down to successively smaller-area PFTs,
+ ! at the end replace the original vector with the temp vector
+ temp_vec(:) = 0._r8
+ do i_pftcount = 1, max_nocomp_pfts_by_landuse(i_landusetype)
+ temp_vec(MAXLOC(sites(s)%area_pft(:, i_landusetype))) = &
+ sites(s)%area_pft(MAXLOC(sites(s)%area_pft(:, i_landusetype)), i_landusetype)
+ sites(s)%area_pft(MAXLOC(sites(s)%area_pft(:, i_landusetype)), i_landusetype) = 0._r8
+ end do
+ sites(s)%area_pft(:, i_landusetype) = temp_vec(:)
- if(hlm_use_nocomp.eq.ifalse)then ! when not in nocomp (i.e. or SP) mode,
- ! subsume bare ground evenly into the existing patches.
+ ! write adjusted vector to log file
+ if(debug) write(fates_log(),*) 'new PFT vector for LU type', i_landusetype, sites(s)%area_pft(:, i_landusetype)
+ endif
+ end do
+ end if
- sumarea = sum(sites(s)%area_pft(1:numpft))
- do ft = 1,numpft
- if(sumarea.gt.0._r8)then
- sites(s)%area_pft(ft) = area * sites(s)%area_pft(ft)/sumarea
- else
- sites(s)%area_pft(ft) = area/numpft
- ! in nocomp mode where there is only bare ground, we assign equal area to
- ! all pfts and let the model figure out whether land should be bare or not.
- end if
- end do !ft
- else ! for sp and nocomp mode, assert a bare ground patch if needed
- sumarea = sum(sites(s)%area_pft(1:numpft))
-
- ! In all the other FATES modes, bareground is the area in which plants
- ! do not grow of their own accord. In SP mode we assert that the canopy is full for
- ! each PFT patch. Thus, we also need to assert a bare ground area in
- ! order to not have all of the ground filled by leaves.
-
- ! Further to that, one could calculate bare ground as the remaining area when
- ! all fhe canopies are accounted for, but this means we don't pass balance checks
- ! on canopy are inside FATES, and so in SP mode, we define the bare groud
- ! patch as having a PFT identifier as zero.
-
- if(sumarea.lt.area)then !make some bare ground
- sites(s)%area_pft(0) = area - sumarea
+ ! re-normalize PFT area to ensure it sums to one for each (active) land use type
+ ! for nocomp cases, track bare ground area as a separate quantity
+ do i_landusetype = 1, n_landuse_cats
+ sumarea = sum(sites(s)%area_pft(:,i_landusetype))
+ if(sumarea.gt.nearzero)then
+ sites(s)%area_pft(:, i_landusetype) = sites(s)%area_pft(:, i_landusetype)/sumarea
+ else
+ ! if no PFT area in primary lands, set bare ground fraction to one.
+ if ( i_landusetype .eq. primaryland) then
+ sites(s)%area_bareground = 1._r8
+ sites(s)%area_pft(:, i_landusetype) = 0._r8
+ endif
end if
- end if !sp mode
+ end do
+
end if !fixed biogeog
do ft = 1,numpft
@@ -517,13 +587,27 @@ subroutine set_site_properties( nsites, sites,bc_in )
! are used for nocomp with no biogeog
sites(s)%use_this_pft(ft) = itrue
if(hlm_use_fixed_biogeog.eq.itrue)then
- if(sites(s)%area_pft(ft).gt.0.0_r8)then
+ if(any(sites(s)%area_pft(ft,:).gt.0.0_r8))then
sites(s)%use_this_pft(ft) = itrue
else
sites(s)%use_this_pft(ft) = ifalse
end if !area
end if !SBG
end do !ft
+
+ ! need to set the minimum amount of allowable land-use fraction on a given site. this is a function of the minimum allowable patch size,
+ ! and for nocomp simulations also the bare ground fraction and the minimum pft fraction for a given land-use type.
+ if (hlm_use_nocomp .eq. itrue ) then
+ if ( (1._r8 - sites(s)%area_bareground) .gt. nearzero) then
+ sites(s)%min_allowed_landuse_fraction = min_patch_area_forced / (AREA * min_nocomp_pftfrac_perlanduse * (1._r8 - sites(s)%area_bareground))
+ else
+ ! if all bare ground, shouldn't matter. but make it one anyway to really ignore land use (which should all be NaNs anyway)
+ sites(s)%min_allowed_landuse_fraction = 1._r8
+ endif
+ else
+ sites(s)%min_allowed_landuse_fraction = min_patch_area_forced / AREA
+ endif
+
end do !site loop
end if !restart
@@ -541,7 +625,7 @@ subroutine init_patches( nsites, sites, bc_in)
use FatesPlantHydraulicsMod, only : updateSizeDepRhizHydProps
use FatesInventoryInitMod, only : initialize_sites_by_inventory
- use FatesLandUseChangeMod, only : get_luh_statedata
+ use FatesLandUseChangeMod, only : GetLUHStatedata
!
! !ARGUMENTS
@@ -555,6 +639,7 @@ subroutine init_patches( nsites, sites, bc_in)
real(r8) :: age !notional age of this patch
integer :: ageclass
real(r8) :: area_diff
+ real(r8) :: area_error
! dummy locals
real(r8) :: biomass_stock
@@ -562,19 +647,19 @@ subroutine init_patches( nsites, sites, bc_in)
real(r8) :: seed_stock
integer :: n
integer :: start_patch
- integer :: num_new_patches
+ integer :: num_nocomp_pfts
integer :: nocomp_pft
real(r8) :: newparea, newparea_withlanduse
real(r8) :: total !check on area
real(r8) :: litt_init !invalid for satphen, 0 otherwise
real(r8) :: old_carea
- integer :: is_first_patch
+ logical :: is_first_patch
! integer :: n_luh_states
! integer :: luh_state_counter
real(r8) :: state_vector(n_landuse_cats) ! [m2/m2]
integer :: i_lu, i_lu_state
integer :: n_active_landuse_cats
-
+ integer :: end_landuse_idx
type(ed_site_type), pointer :: sitep
type(fates_patch_type), pointer :: newppft(:)
@@ -615,7 +700,11 @@ subroutine init_patches( nsites, sites, bc_in)
else
- ! state_vector(:) = 0._r8
+ if(hlm_use_nocomp.eq.itrue)then
+ num_nocomp_pfts = numpft
+ else !default
+ num_nocomp_pfts = 1
+ end if !nocomp
sites_loop: do s = 1, nsites
sites(s)%sp_tlai(:) = 0._r8
@@ -627,17 +716,6 @@ subroutine init_patches( nsites, sites, bc_in)
! have smaller spread factors than bare ground (they are crowded)
sites(s)%spread = init_spread_near_bare_ground
- start_patch = 1 ! start at the first vegetated patch
- if(hlm_use_nocomp.eq.itrue)then
- num_new_patches = numpft
- if( hlm_use_fixed_biogeog .eq.itrue )then
- start_patch = 0 ! start at the bare ground patch
- endif
- ! allocate(newppft(numpft))
- else !default
- num_new_patches = 1
- end if !nocomp
-
! read in luh state data to determine initial land use types
if (hlm_use_luh .eq. itrue) then
@@ -645,18 +723,18 @@ subroutine init_patches( nsites, sites, bc_in)
! This could be updated in the future to allow a variable number of
! categories based on which states are zero
n_active_landuse_cats = n_landuse_cats
- call get_luh_statedata(bc_in(s), state_vector)
- ! n_luh_states = 0
- ! do i_lu = 1, hlm_num_luh2_transitions
- ! if ( state_vector(i_lu) .gt. nearzero ) then
- ! n_luh_states = n_luh_states +1
- ! end if
- ! end do
-
- ! if (n_luh_states .eq. 0) then
- ! write(fates_log(),*) 'error. n_luh_states .eq. 0.'
- ! call endrun(msg=errMsg(sourcefile, __LINE__))
- ! endif
+ call GetLUHStatedata(bc_in(s), state_vector)
+
+ ! if the land use state vector is greater than the minimum value, set landuse_vector_gt_min flag to true
+ ! otherwise set to false.
+ do i_lu_state = 1, n_landuse_cats
+ if (state_vector(i_lu_state) .gt. sites(s)%min_allowed_landuse_fraction) then
+ sites(s)%landuse_vector_gt_min(i_lu_state) = .true.
+ else
+ sites(s)%landuse_vector_gt_min(i_lu_state) = .false.
+ end if
+ end do
+
else
! If LUH2 data is not being used, we initialize with primarylands,
! i.e. array index equals '1'
@@ -665,94 +743,163 @@ subroutine init_patches( nsites, sites, bc_in)
state_vector(primaryland) = 1._r8
endif
- is_first_patch = itrue
- ! luh_state_counter = 0
- new_patch_nocomp_loop: do n = start_patch, num_new_patches
+ ! confirm that state vector sums to 1.
+ if (abs(sum(state_vector(:))-1._r8) .gt. rsnbl_math_prec) then
+ write(fates_log(),*) 'error that the state vector must sum to 1, but doesnt'
+ write(fates_log(),*) 'sum(state_vector)', sum(state_vector)
+ write(fates_log(),*) state_vector
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ endif
- ! set the PFT index for patches if in nocomp mode.
- if(hlm_use_nocomp.eq.itrue)then
- nocomp_pft = n
- else
- nocomp_pft = fates_unset_int
- end if
+ is_first_patch = .true.
+
+ area_error = 0._r8
+ ! first make a bare-ground patch if one is needed.
+ make_bareground_patch_if: if (hlm_use_nocomp.eq.itrue .and. hlm_use_fixed_biogeog .eq.itrue) then
+
+ newparea = area * sites(s)%area_bareground
+ if (newparea .gt. min_patch_area_forced) then
+
+ allocate(newp)
+
+ call newp%Create(age, newparea, nocomp_bareground_land, nocomp_bareground, &
+ num_swb, numpft, sites(s)%nlevsoil, hlm_current_tod, &
+ regeneration_model)
+
+ ! set pointers for first patch (or only patch, if nocomp is false)
+ newp%patchno = 1
+ newp%younger => null()
+ newp%older => null()
+ sites(s)%youngest_patch => newp
+ sites(s)%oldest_patch => newp
+ is_first_patch = .false.
+
+ ! Initialize the litter pools to zero, these
+ ! pools will be populated by looping over the existing patches
+ ! and transfering in mass
+ if(hlm_use_sp.eq.itrue)then
+ litt_init = fates_unset_r8
+ else
+ litt_init = 0._r8
+ end if
+ do el=1,num_elements
+ call newp%litter(el)%InitConditions(init_leaf_fines=litt_init, &
+ init_root_fines=litt_init, &
+ init_ag_cwd=litt_init, &
+ init_bg_cwd=litt_init, &
+ init_seed=litt_init, &
+ init_seed_germ=litt_init)
+ end do
- if(hlm_use_nocomp.eq.itrue)then
- ! In no competition mode, if we are using the fixed_biogeog filter
- ! then each PFT has the area dictated by the surface dataset.
+ else
+ area_error = area_error + newparea
+ endif
+ endif make_bareground_patch_if
- ! If we are not using fixed biogeog model, each PFT gets the same area.
- ! i.e. each grid cell is divided exactly into the number of FATES PFTs.
+ if (hlm_use_luh .eq. itrue) then
+ end_landuse_idx = n_landuse_cats
+ else
+ end_landuse_idx = 1
+ endif
- if(hlm_use_fixed_biogeog.eq.itrue)then
- newparea = sites(s)%area_pft(nocomp_pft)
- else
- newparea = area / numpft
- end if
- else ! The default case is initialized w/ one patch with the area of the whole site.
- newparea = area
- end if !nocomp mode
-
- luh_state_loop: do i_lu_state = 1, n_active_landuse_cats
- lu_state_present_if: if ( state_vector(i_lu_state) .gt. nearzero ) then
-
- newparea_withlanduse = newparea * state_vector(i_lu_state)
-
- ! for now, spread nocomp PFTs evenly across land use types
- new_patch_area_gt_zero: if(newparea_withlanduse.gt.0._r8)then ! Stop patches being initilialized when PFT not present in nocomop mode
- allocate(newp)
-
- call newp%Create(age, newparea_withlanduse, i_lu_state, nocomp_pft, &
- num_swb, numpft, sites(s)%nlevsoil, hlm_current_tod, &
- regeneration_model)
-
- if(is_first_patch.eq.itrue)then !is this the first patch?
- ! set poointers for first patch (or only patch, if nocomp is false)
- newp%patchno = 1
- newp%younger => null()
- newp%older => null()
- sites(s)%youngest_patch => newp
- sites(s)%oldest_patch => newp
- is_first_patch = ifalse
- else
- ! Set pointers for N>1 patches. Note this only happens when nocomp mode s on.
- ! The new patch is the 'youngest' one, arbitrarily.
- newp%patchno = nocomp_pft + (i_lu_state-1) * numpft
- newp%older => sites(s)%youngest_patch
- newp%younger => null()
- sites(s)%youngest_patch%younger => newp
- sites(s)%youngest_patch => newp
- end if
- ! Initialize the litter pools to zero, these
- ! pools will be populated by looping over the existing patches
- ! and transfering in mass
- if(hlm_use_sp.eq.itrue)then
- litt_init = fates_unset_r8
+ ! Next, create the non-bareground patches. We do this for either of two scenarios:
+ ! If 1) we are not doing both nocomp & fixed-biogeo
+ ! 2) we are, but there is some non-zero bare-ground area
+ not_all_bare_if: if( ((1._r8 - sites(s)%area_bareground) > nearzero) .or. &
+ (.not.(hlm_use_nocomp.eq.itrue .and. hlm_use_fixed_biogeog.eq.itrue)) ) then
+
+ ! now make one or more vegetated patches based on nocomp and land use logic
+ luh_state_loop: do i_lu_state = 1, end_landuse_idx
+ lu_state_present_if: if (state_vector(i_lu_state) .gt. nearzero) then
+ new_patch_nocomp_loop: do n = 1, num_nocomp_pfts
+ ! set the PFT index for patches if in nocomp mode.
+ if(hlm_use_nocomp.eq.itrue)then
+ nocomp_pft = n
else
- litt_init = 0._r8
+ nocomp_pft = fates_unset_int
end if
- do el=1,num_elements
- call newp%litter(el)%InitConditions(init_leaf_fines=litt_init, &
- init_root_fines=litt_init, &
- init_ag_cwd=litt_init, &
- init_bg_cwd=litt_init, &
- init_seed=litt_init, &
- init_seed_germ=litt_init)
- end do
+
+ if(hlm_use_nocomp.eq.itrue)then
+ ! In no competition mode, if we are using the fixed_biogeog filter
+ ! then each PFT has the area dictated by the surface dataset.
+
+ ! If we are not using fixed biogeog model, each PFT gets the same area.
+ ! i.e. each grid cell is divided exactly into the number of FATES PFTs.
+
+ if(hlm_use_fixed_biogeog.eq.itrue)then
+ newparea = sites(s)%area_pft(nocomp_pft,i_lu_state) * area * state_vector(i_lu_state) &
+ * (1._r8 - sites(s)%area_bareground)
+ else
+ newparea = area * state_vector(i_lu_state) / numpft
+ end if
+ else ! The default case is initialized w/ one patch with the area of the whole site.
+ newparea = area * state_vector(i_lu_state)
+ end if !nocomp mode
+
+ ! Stop patches being initilialized when PFT not present in nocomop mode
+ new_patch_area_gt_zero: if(newparea .gt. min_patch_area_forced) then
+ allocate(newp)
+
+ call newp%Create(age, newparea, i_lu_state, nocomp_pft, &
+ num_swb, numpft, sites(s)%nlevsoil, hlm_current_tod, &
+ regeneration_model)
+
+ if (is_first_patch) then !is this the first patch?
+ ! set pointers for first patch (or only patch, if nocomp is false)
+ newp%patchno = 1
+ newp%younger => null()
+ newp%older => null()
+ sites(s)%youngest_patch => newp
+ sites(s)%oldest_patch => newp
+ is_first_patch = .false.
+ else
+ ! Set pointers for N>1 patches. Note this only happens when nocomp mode is on, or land use is on.
+ ! The new patch is the 'youngest' one, arbitrarily.
+ newp%patchno = nocomp_pft + (i_lu_state-1) * numpft
+ newp%older => sites(s)%youngest_patch
+ newp%younger => null()
+ sites(s)%youngest_patch%younger => newp
+ sites(s)%youngest_patch => newp
+ end if
- sitep => sites(s)
- if(hlm_use_sp.eq.itrue)then
- if(nocomp_pft.ne.0)then !don't initialize cohorts for SP bare ground patch
- call init_cohorts(sitep, newp, bc_in(s))
+ ! Initialize the litter pools to zero, these
+ ! pools will be populated by looping over the existing patches
+ ! and transfering in mass
+ if(hlm_use_sp.eq.itrue)then
+ litt_init = fates_unset_r8
+ else
+ litt_init = 0._r8
end if
- else ! normal non SP case always call init cohorts
+ do el=1,num_elements
+ call newp%litter(el)%InitConditions(init_leaf_fines=litt_init, &
+ init_root_fines=litt_init, &
+ init_ag_cwd=litt_init, &
+ init_bg_cwd=litt_init, &
+ init_seed=litt_init, &
+ init_seed_germ=litt_init)
+ end do
+
+ sitep => sites(s)
call init_cohorts(sitep, newp, bc_in(s))
- end if
- end if new_patch_area_gt_zero
+
+ else
+ area_error = area_error+ newparea
+ end if new_patch_area_gt_zero
+ end do new_patch_nocomp_loop
end if lu_state_present_if
end do luh_state_loop
- end do new_patch_nocomp_loop !no new patches
-
+ end if not_all_bare_if
+
+ ! if we had to skip small patches above, resize things accordingly
+ if ( area_error .gt. nearzero) then
+ newp => sites(s)%oldest_patch
+ do while (associated(newp))
+ newp%area = newp%area * area/ (area - area_error)
+ newp => newp%younger
+ end do
+ endif
+
!check if the total area adds to the same as site area
total = 0.0_r8
newp => sites(s)%oldest_patch
@@ -764,22 +911,34 @@ subroutine init_patches( nsites, sites, bc_in)
area_diff = total - area
if (abs(area_diff) > nearzero) then
if (abs(area_diff) < area_error_4) then ! this is a precision error
- if (sites(s)%oldest_patch%area > area_diff + nearzero) then
- ! remove or add extra area
- ! if the oldest patch has enough area, use that
- sites(s)%oldest_patch%area = sites(s)%oldest_patch%area - area_diff
- if (debug) write(fates_log(),*) 'fixing patch precision - oldest', s, area_diff
- else ! or otherwise take the area from the youngest patch.
- sites(s)%youngest_patch%area = sites(s)%youngest_patch%area - area_diff
- if (debug) write(fates_log(),*) 'fixing patch precision -youngest ', s, area_diff
- end if
+
+ ! adjust areas of all patches so that they add up to total area
+ newp => sites(s)%oldest_patch
+ do while (associated(newp))
+ newp%area = newp%area * (area / total)
+ newp => newp%younger
+ end do
+
else !this is a big error not just a precision error.
- write(fates_log(),*) 'issue with patch area in EDinit', area_diff, total
+ write(fates_log(),*) 'issue with patch area in EDinit', area_diff, total,sites(s)%lat,sites(s)%lon
+ write(fates_log(),*) 'hlm_use_nocomp: ',hlm_use_nocomp
+ write(fates_log(),*) 'hlm_use_fixed_biogeog: ',hlm_use_fixed_biogeog
+ newp => sites(s)%oldest_patch
+ do while (associated(newp))
+ write(fates_log(),*) newp%area, newp%nocomp_pft_label, newp%land_use_label
+ newp => newp%younger
+ end do
+ write(fates_log(),*) 'state_vector', state_vector
+ write(fates_log(),*) 'area_error', area_error
+ write(fates_log(),*) 'area_bareground', sites(s)%area_bareground
+ do i_lu_state = 1, end_landuse_idx
+ write(fates_log(),*) 'sites(s)%area_pft(:,i_lu_state)',i_lu_state, sites(s)%area_pft(:,i_lu_state)
+ end do
call endrun(msg=errMsg(sourcefile, __LINE__))
end if ! big error
end if ! too much patch area
- ! we might have messed up patch area now - need to correct if SP mode
+ ! we might have messed up crown areas now - need to correct if SP mode
if (hlm_use_sp .eq. itrue) then
newp => sites(s)%oldest_patch
do while (associated(newp))
@@ -847,6 +1006,18 @@ subroutine init_patches( nsites, sites, bc_in)
end do
end if
+ ! check to make sure there are no very tiny patches
+ do s = 1, nsites
+ currentPatch => sites(s)%youngest_patch
+ do while(associated(currentPatch))
+ if (currentPatch%area .lt. min_patch_area_forced) then
+ write(fates_log(),*) 'edinit somehow making tiny patches',currentPatch%land_use_label, currentPatch%nocomp_pft_label, currentPatch%area
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+ currentPatch => currentPatch%older
+ end do
+ end do
+
return
end subroutine init_patches
diff --git a/main/EDMainMod.F90 b/main/EDMainMod.F90
index 159e942c6a..06058b8f63 100644
--- a/main/EDMainMod.F90
+++ b/main/EDMainMod.F90
@@ -75,6 +75,7 @@ module EDMainMod
use EDTypesMod , only : phen_dstat_timeon
use FatesConstantsMod , only : itrue,ifalse
use FatesConstantsMod , only : primaryland, secondaryland
+ use FatesConstantsMod , only : n_landuse_cats
use FatesConstantsMod , only : nearzero
use FatesConstantsMod , only : m2_per_ha
use FatesConstantsMod , only : sec_per_day
@@ -88,7 +89,6 @@ module EDMainMod
use EDLoggingMortalityMod , only : IsItLoggingTime
use EDLoggingMortalityMod , only : get_harvestable_carbon
use DamageMainMod , only : IsItDamageTime
- use EDPatchDynamicsMod , only : get_frac_site_primary
use FatesGlobals , only : endrun => fates_endrun
use ChecksBalancesMod , only : SiteMassStock
use EDMortalityFunctionsMod , only : Mortality_Derivative
@@ -166,12 +166,6 @@ subroutine ed_ecosystem_dynamics(currentSite, bc_in, bc_out)
call currentSite%flux_diags(el)%ZeroFluxDiags()
end do
- ! zero dynamics (upfreq_in = 1) output history variables
- call fates_hist%zero_site_hvars(currentSite,upfreq_in=1)
-
- ! zero nutrient fluxes (upfreq_in=5) output hist variables
- call fates_hist%zero_site_hvars(currentSite,upfreq_in=5)
-
! Call a routine that simply identifies if logging should occur
! This is limited to a global event until more structured event handling is enabled
call IsItLoggingTime(hlm_masterproc,currentSite)
@@ -225,6 +219,11 @@ subroutine ed_ecosystem_dynamics(currentSite, bc_in, bc_out)
! Integrate state variables from annual rates to daily timestep
call ed_integrate_state_variables(currentSite, bc_in, bc_out )
+ ! at this point in the call sequence, if flag to transition_landuse_from_off_to_on was set, unset it as it is no longer needed
+ if(currentSite%transition_landuse_from_off_to_on) then
+ currentSite%transition_landuse_from_off_to_on = .false.
+ endif
+
else
! ed_intergrate_state_variables is where the new cohort flag
! is set. This flag designates wether a cohort has
@@ -289,6 +288,7 @@ subroutine ed_ecosystem_dynamics(currentSite, bc_in, bc_out)
! make new patches from disturbed land
if (do_patch_dynamics.eq.itrue ) then
+
call spawn_patches(currentSite, bc_in)
call TotalBalanceCheck(currentSite,3)
@@ -309,7 +309,7 @@ subroutine ed_ecosystem_dynamics(currentSite, bc_in, bc_out)
call TotalBalanceCheck(currentSite,4)
! kill patches that are too small
- call terminate_patches(currentSite)
+ call terminate_patches(currentSite, bc_in)
end if
call TotalBalanceCheck(currentSite,5)
@@ -372,7 +372,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out )
! a lowered damage state. This cohort should bypass several calculations
! because it inherited them (such as daily carbon balance)
real(r8) :: target_leaf_c
- real(r8) :: frac_site_primary
+ real(r8) :: current_fates_landuse_state_vector(n_landuse_cats)
real(r8) :: harvestable_forest_c(hlm_num_lu_harvest_cats)
integer :: harvest_tag(hlm_num_lu_harvest_cats)
@@ -408,7 +408,7 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out )
!-----------------------------------------------------------------------
- call get_frac_site_primary(currentSite, frac_site_primary)
+ current_fates_landuse_state_vector = currentSite%get_current_landuse_statevector()
! Clear site GPP and AR passing to HLM
bc_out%gpp_site = 0._r8
@@ -473,8 +473,8 @@ subroutine ed_integrate_state_variables(currentSite, bc_in, bc_out )
call Mortality_Derivative(currentSite, currentCohort, bc_in, &
currentPatch%btran_ft, mean_temp, &
currentPatch%land_use_label, &
- currentPatch%age_since_anthro_disturbance, frac_site_primary, &
- harvestable_forest_c, harvest_tag)
+ currentPatch%age_since_anthro_disturbance, current_fates_landuse_state_vector(primaryland), &
+ current_fates_landuse_state_vector(secondaryland), harvestable_forest_c, harvest_tag)
! -----------------------------------------------------------------------------
! Apply Plant Allocation and Reactive Transport
@@ -912,7 +912,8 @@ subroutine TotalBalanceCheck (currentSite, call_index )
site_mass%flux_generic_in + &
site_mass%patch_resize_err
- flux_out = site_mass%wood_product + &
+ flux_out = sum(site_mass%wood_product_harvest(:)) + &
+ sum(site_mass%wood_product_landusechange(:)) + &
site_mass%burn_flux_to_atm + &
site_mass%seed_out + &
site_mass%flux_generic_out + &
@@ -942,7 +943,8 @@ subroutine TotalBalanceCheck (currentSite, call_index )
write(fates_log(),*) 'net_root_uptake: ',site_mass%net_root_uptake
write(fates_log(),*) 'gpp_acc: ',site_mass%gpp_acc
write(fates_log(),*) 'flux_generic_in: ',site_mass%flux_generic_in
- write(fates_log(),*) 'wood_product: ',site_mass%wood_product
+ write(fates_log(),*) 'wood_product_harvest: ',site_mass%wood_product_harvest(:)
+ write(fates_log(),*) 'wood_product_landusechange: ',site_mass%wood_product_landusechange(:)
write(fates_log(),*) 'error from patch resizing: ',site_mass%patch_resize_err
write(fates_log(),*) 'burn_flux_to_atm: ',site_mass%burn_flux_to_atm
write(fates_log(),*) 'seed_out: ',site_mass%seed_out
diff --git a/main/EDParamsMod.F90 b/main/EDParamsMod.F90
index 1389e1c489..c3d8a76273 100644
--- a/main/EDParamsMod.F90
+++ b/main/EDParamsMod.F90
@@ -182,7 +182,9 @@ module EDParamsMod
character(len=param_string_length),parameter,public :: ED_name_history_height_bin_edges= "fates_history_height_bin_edges"
character(len=param_string_length),parameter,public :: ED_name_history_coageclass_bin_edges = "fates_history_coageclass_bin_edges"
character(len=param_string_length),parameter,public :: ED_name_history_damage_bin_edges = "fates_history_damage_bin_edges"
+ character(len=param_string_length),parameter,public :: ED_name_crop_lu_pft_vector = "fates_landuse_crop_lu_pft_vector"
character(len=param_string_length),parameter,public :: ED_name_maxpatches_by_landuse = "fates_maxpatches_by_landuse"
+ character(len=param_string_length),parameter,public :: ED_name_max_nocomp_pfts_by_landuse = "fates_max_nocomp_pfts_by_landuse"
! Hydraulics Control Parameters (ONLY RELEVANT WHEN USE_FATES_HYDR = TRUE)
! ----------------------------------------------------------------------------------------------
@@ -236,8 +238,12 @@ module EDParamsMod
! thus they are not protected here.
integer, public :: maxpatches_by_landuse(n_landuse_cats)
+ integer, public :: max_nocomp_pfts_by_landuse(n_landuse_cats)
integer, public :: maxpatch_total
+ ! which crops can be grown on a given crop land use type
+ integer,protected,public :: crop_lu_pft_vector(n_landuse_cats)
+
! Maximum allowable cohorts per patch
integer, protected, public :: max_cohort_per_patch
character(len=param_string_length), parameter, public :: maxcohort_name = "fates_maxcohort"
@@ -279,10 +285,6 @@ module EDParamsMod
! leftovers will be left onsite as large CWD
character(len=param_string_length),parameter,public :: logging_name_export_frac ="fates_landuse_logging_export_frac"
- real(r8),protected,public :: pprodharv10_forest_mean ! "mean harvest mortality proportion of deadstem to 10-yr
- ! product pool (pprodharv10) of all woody PFT types
- character(len=param_string_length),parameter,public :: logging_name_pprodharv10="fates_landuse_pprodharv10_forest_mean"
-
real(r8),protected,public :: eca_plant_escalar ! scaling factor for plant fine root biomass to
! calculate nutrient carrier enzyme abundance (ECA)
@@ -358,7 +360,6 @@ subroutine FatesParamsInit()
logging_event_code = nan
logging_dbhmax_infra = nan
logging_export_frac = nan
- pprodharv10_forest_mean = nan
eca_plant_escalar = nan
q10_mr = nan
q10_froz = nan
@@ -553,9 +554,6 @@ subroutine FatesRegisterParams(fates_params)
call fates_params%RegisterParameter(name=logging_name_export_frac, dimension_shape=dimension_shape_scalar, &
dimension_names=dim_names_scalar)
- call fates_params%RegisterParameter(name=logging_name_pprodharv10, dimension_shape=dimension_shape_scalar, &
- dimension_names=dim_names_scalar)
-
call fates_params%RegisterParameter(name=eca_name_plant_escalar, dimension_shape=dimension_shape_scalar, &
dimension_names=dim_names_scalar)
@@ -600,9 +598,15 @@ subroutine FatesRegisterParams(fates_params)
call fates_params%RegisterParameter(name=ED_name_history_damage_bin_edges, dimension_shape=dimension_shape_1d, &
dimension_names=dim_names_damageclass)
+ call fates_params%RegisterParameter(name=ED_name_crop_lu_pft_vector, dimension_shape=dimension_shape_1d, &
+ dimension_names=dim_names_landuse)
+
call fates_params%RegisterParameter(name=ED_name_maxpatches_by_landuse, dimension_shape=dimension_shape_1d, &
dimension_names=dim_names_landuse)
+ call fates_params%RegisterParameter(name=ED_name_max_nocomp_pfts_by_landuse, dimension_shape=dimension_shape_1d, &
+ dimension_names=dim_names_landuse)
+
end subroutine FatesRegisterParams
@@ -618,8 +622,10 @@ subroutine FatesReceiveParams(fates_params)
real(r8) :: tmpreal ! local real variable for changing type on read
real(r8), allocatable :: hydr_htftype_real(:)
- real(r8) :: tmp_vector_by_landuse(n_landuse_cats) ! local real vector for changing type on read
-
+ real(r8), allocatable :: tmp_vector_by_landuse1(:) ! local real vector for changing type on read
+ real(r8), allocatable :: tmp_vector_by_landuse2(:) ! local real vector for changing type on read
+ real(r8), allocatable :: tmp_vector_by_landuse3(:) ! local real vector for changing type on read
+
call fates_params%RetrieveParameter(name=ED_name_photo_temp_acclim_timescale, &
data=photo_temp_acclim_timescale)
@@ -780,9 +786,6 @@ subroutine FatesReceiveParams(fates_params)
call fates_params%RetrieveParameter(name=logging_name_export_frac, &
data=logging_export_frac)
- call fates_params%RetrieveParameter(name=logging_name_pprodharv10, &
- data=pprodharv10_forest_mean)
-
call fates_params%RetrieveParameter(name=eca_name_plant_escalar, &
data=eca_plant_escalar)
@@ -832,11 +835,24 @@ subroutine FatesReceiveParams(fates_params)
call fates_params%RetrieveParameterAllocate(name=ED_name_history_damage_bin_edges, &
data=ED_val_history_damage_bin_edges)
- call fates_params%RetrieveParameter(name=ED_name_maxpatches_by_landuse, &
- data=tmp_vector_by_landuse)
+ call fates_params%RetrieveParameterAllocate(name=ED_name_crop_lu_pft_vector, &
+ data=tmp_vector_by_landuse1)
+
+ crop_lu_pft_vector(:) = nint(tmp_vector_by_landuse1(:))
+ deallocate(tmp_vector_by_landuse1)
- maxpatches_by_landuse(:) = nint(tmp_vector_by_landuse(:))
+ call fates_params%RetrieveParameterAllocate(name=ED_name_maxpatches_by_landuse, &
+ data=tmp_vector_by_landuse2)
+
+ maxpatches_by_landuse(:) = nint(tmp_vector_by_landuse2(:))
maxpatch_total = sum(maxpatches_by_landuse(:))
+ deallocate(tmp_vector_by_landuse2)
+
+ call fates_params%RetrieveParameterAllocate(name=ED_name_max_nocomp_pfts_by_landuse, &
+ data=tmp_vector_by_landuse3)
+
+ max_nocomp_pfts_by_landuse(:) = nint(tmp_vector_by_landuse3(:))
+ deallocate(tmp_vector_by_landuse3)
call fates_params%RetrieveParameterAllocate(name=ED_name_hydr_htftype_node, &
data=hydr_htftype_real)
diff --git a/main/EDPftvarcon.F90 b/main/EDPftvarcon.F90
index 98367a30ff..3d91f2f1b9 100644
--- a/main/EDPftvarcon.F90
+++ b/main/EDPftvarcon.F90
@@ -277,9 +277,14 @@ module EDPftvarcon
real(r8), allocatable :: hydr_thetas_node(:,:) ! saturated water content (cm3/cm3)
! Table that maps HLM pfts to FATES pfts for fixed biogeography mode
- ! The values are area fractions (NOT IMPLEMENTED)
+ ! The values are area fractions
real(r8), allocatable :: hlm_pft_map(:,:)
+ ! Land-use and land-use change related PFT parameters
+ real(r8), allocatable :: harvest_pprod10(:) ! fraction of harvest wood product that goes to 10-year product pool (remainder goes to 100-year pool)
+ real(r8), allocatable :: landusechange_frac_burned(:) ! fraction of land use change-generated and not-exported material that is burned (the remainder goes to litter)
+ real(r8), allocatable :: landusechange_frac_exported(:) ! fraction of land use change-generated wood material that is exported to wood product (the remainder is either burned or goes to litter)
+ real(r8), allocatable :: landusechange_pprod10(:) ! fraction of land use change wood product that goes to 10-year product pool (remainder goes to 100-year pool)
contains
procedure, public :: Init => EDpftconInit
@@ -798,6 +803,22 @@ subroutine Register_PFT(this, fates_params)
call fates_params%RegisterParameter(name=name, dimension_shape=dimension_shape_1d, &
dimension_names=dim_names, lower_bounds=dim_lower_bound)
+ name = 'fates_landuse_harvest_pprod10'
+ call fates_params%RegisterParameter(name=name, dimension_shape=dimension_shape_1d, &
+ dimension_names=dim_names, lower_bounds=dim_lower_bound)
+
+ name = 'fates_landuse_luc_frac_burned'
+ call fates_params%RegisterParameter(name=name, dimension_shape=dimension_shape_1d, &
+ dimension_names=dim_names, lower_bounds=dim_lower_bound)
+
+ name = 'fates_landuse_luc_frac_exported'
+ call fates_params%RegisterParameter(name=name, dimension_shape=dimension_shape_1d, &
+ dimension_names=dim_names, lower_bounds=dim_lower_bound)
+
+ name = 'fates_landuse_luc_pprod10'
+ call fates_params%RegisterParameter(name=name, dimension_shape=dimension_shape_1d, &
+ dimension_names=dim_names, lower_bounds=dim_lower_bound)
+
name = 'fates_dev_arbitrary_pft'
call fates_params%RegisterParameter(name=name, dimension_shape=dimension_shape_1d, &
dimension_names=dim_names, lower_bounds=dim_lower_bound)
@@ -1266,6 +1287,22 @@ subroutine Receive_PFT(this, fates_params)
call fates_params%RetrieveParameterAllocate(name=name, &
data=this%hlm_pft_map)
+ name = 'fates_landuse_harvest_pprod10'
+ call fates_params%RetrieveParameterAllocate(name=name, &
+ data=this%harvest_pprod10)
+
+ name = 'fates_landuse_luc_frac_burned'
+ call fates_params%RetrieveParameterAllocate(name=name, &
+ data=this%landusechange_frac_burned)
+
+ name = 'fates_landuse_luc_frac_exported'
+ call fates_params%RetrieveParameterAllocate(name=name, &
+ data=this%landusechange_frac_exported)
+
+ name = 'fates_landuse_luc_pprod10'
+ call fates_params%RetrieveParameterAllocate(name=name, &
+ data=this%landusechange_pprod10)
+
end subroutine Receive_PFT
!-----------------------------------------------------------------------
@@ -1793,7 +1830,10 @@ subroutine FatesCheckParams(is_master)
use EDParamsMod , only : radiation_model, dayl_switch
use FatesInterfaceTypesMod, only : hlm_use_fixed_biogeog,hlm_use_sp, hlm_name
use FatesInterfaceTypesMod, only : hlm_use_inventory_init
-
+ use FatesInterfaceTypesMod, only : hlm_use_nocomp
+ use EDParamsMod , only : max_nocomp_pfts_by_landuse, maxpatches_by_landuse
+ use FatesConstantsMod , only : n_landuse_cats
+
! Argument
logical, intent(in) :: is_master ! Only log if this is the master proc
@@ -1806,6 +1846,7 @@ subroutine FatesCheckParams(is_master)
integer :: norgans ! size of the plant organ dimension
integer :: hlm_pft ! used in fixed biogeog mode
integer :: fates_pft ! used in fixed biogeog mode
+ integer :: i_lu ! land use index
real(r8) :: sumarea ! area of PFTs in nocomp mode.
real(r8) :: neg_lmr_temp ! temperature at which lmr would got negative
@@ -2142,6 +2183,19 @@ subroutine FatesCheckParams(is_master)
end if
+ ! Check to make sure that if a grass sapwood allometry is used, it is not
+ ! a woody plant.
+ if ( ( prt_params%allom_smode(ipft)==2 ) .and. (prt_params%woody(ipft)==itrue) ) then
+ write(fates_log(),*) 'Allometry mode 2 is a mode that is only appropriate'
+ write(fates_log(),*) 'for a grass functional type. Sapwood allometry is set with'
+ write(fates_log(),*) 'fates_allom_smode in the parameter file. Woody versus non woody'
+ write(fates_log(),*) 'plants are set via fates_woody in the parameter file.'
+ write(fates_log(),*) 'Current settings for pft number: ',ipft
+ write(fates_log(),*) 'fates_woody: true'
+ write(fates_log(),*) 'fates_allom_smode: ',prt_params%allom_smode(ipft)
+ write(fates_log(),*) 'Please correct this discrepancy before re-running. Aborting.'
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
! Check if fraction of storage to reproduction is between 0-1
! ----------------------------------------------------------------------------------
@@ -2195,6 +2249,25 @@ subroutine FatesCheckParams(is_master)
end do !ipft
+ ! if nocomp is enabled, check to make sure the max number of nocomp PFTs per land use is
+ ! less than or equal to the max number of patches per land use. (unless this is an
+ ! SP run, then all PFTS are tracked on the primary LU and the others are allocated
+ ! zero patch space
+
+ if ( hlm_use_nocomp .eq. itrue .and. hlm_use_sp.eq.ifalse) then
+ do i_lu = 1, n_landuse_cats
+ if (max_nocomp_pfts_by_landuse(i_lu) .gt. maxpatches_by_landuse(i_lu)) then
+ write(fates_log(),*) 'The max number of nocomp PFTs must all be less than or equal to the number of patches, for a given land use type'
+ write(fates_log(),*) 'land use index:',i_lu
+ write(fates_log(),*) 'max_nocomp_pfts_by_landuse(i_lu):', max_nocomp_pfts_by_landuse(i_lu)
+ write(fates_log(),*) 'maxpatches_by_landuse(i_lu):', maxpatches_by_landuse(i_lu)
+ write(fates_log(),*) 'Aborting'
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ end if
+ end do
+ endif
+
+
! Check the temperature at which Rdark would become negative for each PFT -
! given their parameters
!------------------------------------------------------------------------------------
diff --git a/main/EDTypesMod.F90 b/main/EDTypesMod.F90
index d310f0b84b..14865a56bd 100644
--- a/main/EDTypesMod.F90
+++ b/main/EDTypesMod.F90
@@ -4,6 +4,10 @@ module EDTypesMod
use FatesGlobals, only : endrun => fates_endrun
use FatesConstantsMod, only : ifalse
use FatesConstantsMod, only : itrue
+ use FatesConstantsMod, only : nocomp_bareground_land
+ use FatesConstantsMod, only : secondaryland
+ use FatesConstantsMod, only : secondary_age_threshold
+ use FatesConstantsMod, only : nearzero
use FatesGlobals, only : fates_log
use FatesHydraulicsMemMod, only : ed_cohort_hydr_type
use FatesHydraulicsMemMod, only : ed_site_hydr_type
@@ -31,6 +35,7 @@ module EDTypesMod
use EDParamsMod, only : nclmax, nlevleaf, maxpft
use FatesConstantsMod, only : n_dbh_bins, n_dist_types
use shr_log_mod, only : errMsg => shr_log_errMsg
+ use SFFireWeatherMod, only : fire_weather
implicit none
private ! By default everything is private
@@ -149,8 +154,13 @@ module EDTypesMod
type, public :: site_fluxdiags_type
! ----------------------------------------------------------------------------------
- ! Diagnostics for fluxes into the litter pool from plants
- ! these fluxes are the total from
+ ! Diagnostics of fluxes
+ ! These act as an intermediary to write fluxes to the history
+ ! file after number densities of plants have changed. They also
+ ! allow the history flux diagnostics to be rebuilt during restart
+ !
+ !
+ ! Litter fluxes are the total from
! (1) turnover from living plants
! (2) mass transfer from non-disturbance inducing mortality events
! (3) mass transfer from disturbance inducing mortality events
@@ -161,6 +171,13 @@ module EDTypesMod
real(r8) :: cwd_bg_input(1:ncwd)
real(r8),allocatable :: leaf_litter_input(:)
real(r8),allocatable :: root_litter_input(:)
+
+ ! This variable is slated as to-do, but the fluxdiags type needs
+ ! to be refactored first. Currently this type is allocated
+ ! by chemical species (ie C, N or P). GPP is C, but not N or P (RGK 0524)
+ ! Previous day GPP [kgC/m2/year], partitioned by size x pft
+ !real(r8),allocatable :: gpp_prev_scpf(:)
+
contains
@@ -204,7 +221,10 @@ module EDTypesMod
real(r8) :: frag_out ! Litter and coarse woody debris fragmentation flux [kg/site/day]
- real(r8) :: wood_product ! Total mass exported as wood product [kg/site/day]
+ real(r8) :: wood_product_harvest(maxpft) ! Total mass exported as wood product from wood harvest [kg/site/day]
+
+ real(r8) :: wood_product_landusechange(maxpft) ! Total mass exported as wood product from land use change [kg/site/day]
+
real(r8) :: burn_flux_to_atm ! Total mass burned and exported to the atmosphere [kg/site/day]
real(r8) :: flux_generic_in ! Used for prescribed or artificial input fluxes
@@ -249,7 +269,9 @@ module EDTypesMod
real(r8) :: lon ! longitude: degrees
! Fixed Biogeography mode inputs
- real(r8), allocatable :: area_PFT(:) ! Area allocated to individual PFTs
+ real(r8), allocatable :: area_PFT(:,:) ! Area allocated to individual PFTs, indexed by land use class [ha/ha of non-bareground area]
+ real(r8) :: area_bareground ! Area allocated to bare ground in nocomp configurations (corresponds to HLM PFT 0) [ha/ha]
+
integer, allocatable :: use_this_pft(:) ! Is area_PFT > 0 ? (1=yes, 0=no)
! Total area of patches in each age bin [m2]
@@ -335,10 +357,10 @@ module EDTypesMod
! FIRE
real(r8) :: wind ! daily wind in m/min for Spitfire units
- real(r8) :: acc_ni ! daily nesterov index accumulating over time.
real(r8) :: fdi ! daily probability an ignition event will start a fire
real(r8) :: NF ! daily ignitions in km2
real(r8) :: NF_successful ! daily ignitions in km2 that actually lead to fire
+ class(fire_weather), pointer :: fireWeather ! fire weather object
! PLANT HYDRAULICS
type(ed_site_hydr_type), pointer :: si_hydr
@@ -446,6 +468,15 @@ module EDTypesMod
real(r8) :: primary_land_patchfusion_error ! error term in total area of primary patches associated with patch fusion [m2/m2/day]
real(r8) :: landuse_transition_matrix(n_landuse_cats, n_landuse_cats) ! land use transition matrix as read in from HLM and aggregated to FATES land use types [m2/m2/year]
+ real(r8) :: min_allowed_landuse_fraction ! minimum amount of land-use type below which the resulting patches would be too small [m2/m2]
+ logical, allocatable :: landuse_vector_gt_min(:) ! is the land use state vector for each land use type greater than the minimum below which we ignore?
+ logical :: transition_landuse_from_off_to_on ! special flag to use only when reading restarts, which triggers procedure to initialize land use
+
+ contains
+
+ procedure, public :: get_current_landuse_statevector
+ procedure, public :: get_secondary_young_fraction
+
end type ed_site_type
! Make public necessary subroutines and functions
@@ -463,6 +494,11 @@ subroutine ZeroFluxDiags(this)
this%cwd_bg_input(:) = 0._r8
this%leaf_litter_input(:) = 0._r8
this%root_litter_input(:) = 0._r8
+
+ ! We don't zero gpp_prev_scpf because this is not
+ ! incremented like others, it is assigned at the end
+ ! of the daily history write process
+
return
end subroutine ZeroFluxDiags
@@ -489,7 +525,8 @@ subroutine ZeroMassBalFlux(this)
this%seed_in = 0._r8
this%seed_out = 0._r8
this%frag_out = 0._r8
- this%wood_product = 0._r8
+ this%wood_product_harvest(:) = 0._r8
+ this%wood_product_landusechange(:) = 0._r8
this%burn_flux_to_atm = 0._r8
this%flux_generic_in = 0._r8
this%flux_generic_out = 0._r8
@@ -515,7 +552,82 @@ subroutine dump_site(csite)
write(fates_log(),*) '----------------------------------------'
return
-end subroutine dump_site
+ end subroutine dump_site
+
+ ! =====================================================================================
+
+ function get_current_landuse_statevector(this) result(current_state_vector)
+
+ !
+ ! !DESCRIPTION:
+ ! Calculate how much of a site is each land use category.
+ ! this does not include bare ground when nocomp + fixed biogeography is on,
+ ! so will not sum to one in that case. otherwise it will sum to one.
+ !
+ ! !USES:
+ !
+ ! !ARGUMENTS:
+ class(ed_site_type) :: this
+ real(r8) :: current_state_vector(n_landuse_cats)
+
+ ! !LOCAL VARIABLES:
+ type(fates_patch_type), pointer :: currentPatch
+
+ current_state_vector(:) = 0._r8
+
+ currentPatch => this%oldest_patch
+ do while (associated(currentPatch))
+ if (currentPatch%land_use_label .gt. nocomp_bareground_land) then
+ current_state_vector(currentPatch%land_use_label) = &
+ current_state_vector(currentPatch%land_use_label) + &
+ currentPatch%area/AREA
+ end if
+ currentPatch => currentPatch%younger
+ end do
+
+ end function get_current_landuse_statevector
+
+ ! =====================================================================================
+
+ function get_secondary_young_fraction(this) result(secondary_young_fraction)
+
+ !
+ ! !DESCRIPTION:
+ ! Calculate how much of the secondary area is "young", i.e. below the age threshold.
+ ! If no seconday patch area at all, return -1.
+ !
+ ! !USES:
+ !
+ ! !ARGUMENTS:
+ class(ed_site_type) :: this
+ real(r8) :: secondary_young_fraction
+ real(r8) :: secondary_young_area
+ real(r8) :: secondary_old_area
+
+ ! !LOCAL VARIABLES:
+ type(fates_patch_type), pointer :: currentPatch
+
+ secondary_young_area = 0._r8
+ secondary_old_area = 0._r8
+
+ currentPatch => this%oldest_patch
+ do while (associated(currentPatch))
+ if (currentPatch%land_use_label .eq. secondaryland) then
+ if ( currentPatch%age .ge. secondary_age_threshold ) then
+ secondary_old_area = secondary_old_area + currentPatch%area
+ else
+ secondary_young_area = secondary_young_area + currentPatch%area
+ end if
+ end if
+ currentPatch => currentPatch%younger
+ end do
+
+ if ( (secondary_young_area + secondary_old_area) .gt. nearzero ) then
+ secondary_young_fraction = secondary_young_area / (secondary_young_area + secondary_old_area)
+ else
+ secondary_young_fraction = -1._r8
+ endif
+
+ end function get_secondary_young_fraction
-
end module EDTypesMod
diff --git a/main/FatesConstantsMod.F90 b/main/FatesConstantsMod.F90
index 350979b7c4..90033cb549 100644
--- a/main/FatesConstantsMod.F90
+++ b/main/FatesConstantsMod.F90
@@ -45,14 +45,21 @@ module FatesConstantsMod
integer , parameter, public :: dtype_ilog = 3 ! index for logging generated disturbance event
integer , parameter, public :: dtype_ilandusechange = 4 ! index for land use change disturbance (not including logging)
- ! Labels for patch disturbance history
+ ! Labels for patch land use type information
integer, parameter, public :: n_landuse_cats = 5
integer, parameter, public :: primaryland = 1
integer, parameter, public :: secondaryland = 2
integer, parameter, public :: rangeland = 3
integer, parameter, public :: pastureland = 4
integer, parameter, public :: cropland = 5
+ logical, parameter, dimension(n_landuse_cats), public :: is_crop = [.false., .false.,.false.,.false.,.true.]
+ integer, parameter, public :: n_crop_lu_types = 1
+ ! Bareground nocomp land use label
+ integer, parameter, public :: nocomp_bareground_land = 0 ! not a real land use type, only for labeling any bare-ground nocomp patches
+
+ ! Bareground nocomp PFT label for no competition mode
+ integer, parameter, public :: nocomp_bareground = 0
integer, parameter, public :: leaves_on = 2 ! Flag specifying that a deciduous plant has leaves
! and should be allocating to them as well
@@ -83,9 +90,6 @@ module FatesConstantsMod
integer, parameter, public :: ican_ustory = 2 ! nominal index for diagnostics that refer to understory layers
! (all layers that are not the top canopy layer)
- ! Bareground label for no competition mode
- integer, parameter, public :: nocomp_bareground = 0
-
! Flags specifying how phosphorous uptake and turnover interacts
! with the host model.
integer, public, parameter :: prescribed_p_uptake = 1
@@ -169,6 +173,9 @@ module FatesConstantsMod
! of magnitude of buffer error (ie instead of 1e-15)
real(fates_r8), parameter, public :: rsnbl_math_prec = 1.0e-12_fates_r8
+ ! in nocomp simulations, what is the minimum PFT fraction for any given land use type?
+ real(fates_r8), parameter, public :: min_nocomp_pftfrac_perlanduse = 0.01_fates_r8
+
! This is the precision of 8byte reals (~1e-308)
real(fates_r8), parameter, public :: tinyr8 = tiny(1.0_fates_r8)
@@ -277,6 +284,10 @@ module FatesConstantsMod
real(fates_r8), parameter, public :: J_per_kJ = 1000.0_fates_r8
! Physical constants
+
+ ! dewpoint calculation
+ real(fates_r8), parameter, public :: dewpoint_a = 17.62_fates_r8
+ real(fates_r8), parameter, public :: dewpoint_b = 243.12_fates_r8 ![degrees C]
! universal gas constant [J/K/kmol]
real(fates_r8), parameter, public :: rgas_J_K_kmol = 8314.4598_fates_r8
diff --git a/main/FatesHistoryInterfaceMod.F90 b/main/FatesHistoryInterfaceMod.F90
index c80186a58b..c5b48735f6 100644
--- a/main/FatesHistoryInterfaceMod.F90
+++ b/main/FatesHistoryInterfaceMod.F90
@@ -14,6 +14,7 @@ module FatesHistoryInterfaceMod
use FatesConstantsMod , only : i_term_mort_type_cstarv
use FatesConstantsMod , only : i_term_mort_type_canlev
use FatesConstantsMod , only : i_term_mort_type_numdens
+ use FatesConstantsMod , only : nocomp_bareground_land
use FatesGlobals , only : fates_log
use FatesGlobals , only : endrun => fates_endrun
use EDParamsMod , only : nclmax, maxpft
@@ -33,6 +34,7 @@ module FatesHistoryInterfaceMod
use FatesIOVariableKindMod , only : group_dyna_simple, group_dyna_complx
use FatesIOVariableKindMod , only : group_hifr_simple, group_hifr_complx
use FatesIOVariableKindMod , only : group_hydr_simple, group_hydr_complx
+ use FatesIOVariableKindMod , only : group_nflx_simple, group_nflx_complx
use FatesConstantsMod , only : N_DIST_TYPES
use FatesConstantsMod , only : dtype_ifall
use FatesConstantsMod , only : dtype_ifire
@@ -87,6 +89,7 @@ module FatesHistoryInterfaceMod
! CIME Globals
use shr_log_mod , only : errMsg => shr_log_errMsg
use shr_infnan_mod , only : isnan => shr_infnan_isnan
+
use FatesConstantsMod , only : g_per_kg
use FatesConstantsMod , only : kg_per_g
use FatesConstantsMod , only : ha_per_m2
@@ -131,8 +134,6 @@ module FatesHistoryInterfaceMod
use FatesSizeAgeTypeIndicesMod, only : get_cdamagesize_class_index
use FatesSizeAgeTypeIndicesMod, only : get_cdamagesizepft_class_index
use FatesSizeAgeTypeIndicesMod, only : coagetype_class_index
- use FatesInterfaceTypesMod , only : nlevdamage
-
implicit none
private ! By default everything is private
@@ -359,14 +360,16 @@ module FatesHistoryInterfaceMod
integer :: ih_area_si_landuse
integer :: ih_disturbance_rate_si_lulu
-
+ integer :: ih_transition_matrix_si_lulu
+
integer :: ih_fire_disturbance_rate_si
integer :: ih_logging_disturbance_rate_si
integer :: ih_fall_disturbance_rate_si
- integer :: ih_harvest_carbonflux_si
integer :: ih_harvest_debt_si
integer :: ih_harvest_debt_sec_si
-
+ integer :: ih_harvest_woodprod_carbonflux_si
+ integer :: ih_luchange_woodprod_carbonflux_si
+
! Indices to site by size-class by age variables
integer :: ih_nplant_si_scag
integer :: ih_nplant_canopy_si_scag
@@ -899,7 +902,7 @@ module FatesHistoryInterfaceMod
procedure, public :: flush_hvars
procedure, public :: zero_site_hvars
-
+ procedure, public :: flush_all_hvars
end type fates_history_interface_type
@@ -1811,6 +1814,38 @@ subroutine zero_site_hvars(this, currentSite, upfreq_in)
return
end subroutine zero_site_hvars
+
+ ! ======================================================================================
+
+ subroutine flush_all_hvars(this,nc)
+
+ ! A wrapper to flush all active history
+ ! groups to their flush value
+
+ class(fates_history_interface_type) :: this
+ integer,intent(in) :: nc
+
+ if(hlm_hist_level_hifrq>0) then
+ call this%flush_hvars(nc,upfreq_in=group_hifr_simple)
+ if (hlm_use_planthydro.eq.itrue) call this%flush_hvars(nc,upfreq_in=group_hydr_simple)
+ if(hlm_hist_level_hifrq>1) then
+ call this%flush_hvars(nc,upfreq_in=group_hifr_complx)
+ if (hlm_use_planthydro.eq.itrue) call this%flush_hvars(nc,upfreq_in=group_hydr_complx)
+ end if
+ end if
+
+ if(hlm_hist_level_dynam>0) then
+ call this%flush_hvars(nc,upfreq_in=group_dyna_simple)
+ call this%flush_hvars(nc,upfreq_in=group_nflx_simple)
+ if(hlm_hist_level_dynam>1) then
+ call this%flush_hvars(nc,upfreq_in=group_dyna_complx)
+ call this%flush_hvars(nc,upfreq_in=group_nflx_complx)
+ end if
+ end if
+
+ return
+ end subroutine flush_all_hvars
+
! ======================================================================================
subroutine flush_hvars(this,nc,upfreq_in)
@@ -2093,6 +2128,9 @@ subroutine update_history_nutrflux(this,csite)
! history site index
io_si = csite%h_gid
+ ! zero nutrient fluxes
+ call this%zero_site_hvars(csite,upfreq_in=group_nflx_simple)
+
cpatch => csite%youngest_patch
do while(associated(cpatch))
@@ -2161,7 +2199,9 @@ subroutine update_history_nutrflux(this,csite)
! Demand
this%hvars(ih_pdemand_si)%r81d(io_si) = &
+ this%hvars(ih_pdemand_si)%r81d(io_si) + &
ccohort%daily_p_demand*uconv
+
end select
end do
@@ -2178,10 +2218,11 @@ subroutine update_history_nutrflux(this,csite)
if_dynam2: if(hlm_hist_level_dynam>1) then
-
! history site index
io_si = csite%h_gid
+ call this%zero_site_hvars(csite,upfreq_in=group_nflx_complx)
+
cpatch => csite%youngest_patch
do while(associated(cpatch))
@@ -2391,7 +2432,6 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in)
hio_fire_disturbance_rate_si => this%hvars(ih_fire_disturbance_rate_si)%r81d, &
hio_logging_disturbance_rate_si => this%hvars(ih_logging_disturbance_rate_si)%r81d, &
hio_fall_disturbance_rate_si => this%hvars(ih_fall_disturbance_rate_si)%r81d, &
- hio_harvest_carbonflux_si => this%hvars(ih_harvest_carbonflux_si)%r81d, &
hio_harvest_debt_si => this%hvars(ih_harvest_debt_si)%r81d, &
hio_harvest_debt_sec_si => this%hvars(ih_harvest_debt_sec_si)%r81d, &
hio_npp_leaf_si => this%hvars(ih_npp_leaf_si)%r81d, &
@@ -2422,8 +2462,9 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in)
hio_tlongterm => this%hvars(ih_tlongterm_si)%r81d, &
hio_tgrowth => this%hvars(ih_tgrowth_si)%r81d, &
hio_lai_si => this%hvars(ih_lai_si)%r81d, &
- hio_elai_si => this%hvars(ih_elai_si)%r81d)
-
+ hio_elai_si => this%hvars(ih_elai_si)%r81d, &
+ hio_harvest_woodprod_carbonflux_si => this%hvars(ih_harvest_woodprod_carbonflux_si)%r81d, &
+ hio_luchange_woodprod_carbonflux_si => this%hvars(ih_luchange_woodprod_carbonflux_si)%r81d)
! ---------------------------------------------------------------------------------
! Loop through the FATES scale hierarchy and fill the history IO arrays
@@ -2436,8 +2477,7 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in)
site_ba = 0._r8
site_ca = 0._r8
- ! This should be removed from the interface and put here (RGK 04-24)
- ! call this%zero_site_hvars(sites(s),upfreq_in=group_dyna_simple)
+ call this%zero_site_hvars(sites(s),upfreq_in=group_dyna_simple)
! set the fates fraction to one, since it is zero on non-fates columns, &
! the average is the total gridcell fates fraction
@@ -2489,7 +2529,7 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in)
! site-level fire variables:
! Nesterov index (unitless)
- hio_nesterov_fire_danger_si(io_si) = sites(s)%acc_NI
+ hio_nesterov_fire_danger_si(io_si) = sites(s)%fireWeather%fire_weather_index
! number of ignitions [#/km2/day -> #/m2/s]
hio_fire_nignitions_si(io_si) = sites(s)%NF_successful / m2_per_km2 / &
@@ -2526,9 +2566,12 @@ subroutine update_history_dyn1(this,nc,nsites,sites,bc_in)
sum(sites(s)%disturbance_rates(dtype_ifall,1:n_landuse_cats,1:n_landuse_cats)) * &
days_per_year
- hio_harvest_carbonflux_si(io_si) = &
- sites(s)%mass_balance(element_pos(carbon12_element))%wood_product * AREA_INV
+ hio_harvest_woodprod_carbonflux_si(io_si) = AREA_INV * &
+ sum(sites(s)%mass_balance(element_pos(carbon12_element))%wood_product_harvest(1:numpft))
+ hio_luchange_woodprod_carbonflux_si(io_si) = AREA_INV * &
+ sum(sites(s)%mass_balance(element_pos(carbon12_element))%wood_product_landusechange(1:numpft))
+
! carbon flux associated with mortality of trees dying by fire
hio_canopy_mortality_carbonflux_si(io_si) = hio_canopy_mortality_carbonflux_si(io_si) + &
@@ -3031,8 +3074,9 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
integer :: iscagpft ! size-class x age x pft index
integer :: icdpf, icdsc, icdam ! iterators for the crown damage level
integer :: i_agefuel ! age x fuel size class index
- real(r8) :: gpp_cached ! variable used to cache gpp value in previous time step; for C13 discrimination
+ real(r8) :: gpp_cached ! gpp from previous timestep, for c13 discrimination
real(r8) :: crown_depth ! Depth of the crown [m]
+ real(r8) :: gpp_cached_scpf(numpft*nlevsclass) ! variable used to cache gpp value in previous time step; for C13 discrimination
real(r8) :: storen_canopy_scpf(numpft*nlevsclass)
real(r8) :: storen_understory_scpf(numpft*nlevsclass)
real(r8) :: storep_canopy_scpf(numpft*nlevsclass)
@@ -3261,7 +3305,8 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
hio_nplant_canopy_si_scag => this%hvars(ih_nplant_canopy_si_scag)%r82d, &
hio_nplant_understory_si_scag => this%hvars(ih_nplant_understory_si_scag)%r82d, &
hio_disturbance_rate_si_lulu => this%hvars(ih_disturbance_rate_si_lulu)%r82d, &
- hio_cstarvmortality_continuous_carbonflux_si_pft => this%hvars(ih_cstarvmortality_continuous_carbonflux_si_pft)%r82d)
+ hio_cstarvmortality_continuous_carbonflux_si_pft => this%hvars(ih_cstarvmortality_continuous_carbonflux_si_pft)%r82d, &
+ hio_transition_matrix_si_lulu => this%hvars(ih_transition_matrix_si_lulu)%r82d)
model_day_int = nint(hlm_model_day)
@@ -3269,10 +3314,18 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
! Loop through the FATES scale hierarchy and fill the history IO arrays
! ---------------------------------------------------------------------------------
+
siteloop: do s = 1,nsites
io_si = sites(s)%h_gid
+ ! C13 will not get b4b restarts on the first day because
+ ! there is no mechanism to remember the previous day's values
+ ! through a restart. This should be added with the next refactor
+ gpp_cached_scpf(:) = hio_gpp_si_scpf(io_si,:)
+
+ call this%zero_site_hvars(sites(s),upfreq_in=group_dyna_complx)
+
! These are weighting factors
storen_canopy_scpf(:) = 0._r8
storen_understory_scpf(:) = 0._r8
@@ -3280,17 +3333,22 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
storep_understory_scpf(:) = 0._r8
storec_canopy_scpf(:) = 0._r8
storec_understory_scpf(:) = 0._r8
-
- ! roll up disturbance rates in land-use x land-use array into a single dimension
+
do i_dist = 1, n_landuse_cats
do j_dist = 1, n_landuse_cats
+
+ ! roll up disturbance rates in land-use x land-use array into a single dimension
hio_disturbance_rate_si_lulu(io_si, i_dist+n_landuse_cats*(j_dist-1)) = &
sum(sites(s)%disturbance_rates(1:n_dist_types,i_dist, j_dist)) * &
days_per_year
+
+ ! get the land use transition matrix and output that to history.
+ ! (mainly a sanity check, can maybe remove before integration)
+ hio_transition_matrix_si_lulu(io_si, i_dist+n_landuse_cats*(j_dist-1)) = &
+ sites(s)%landuse_transition_matrix(i_dist, j_dist)
end do
end do
-
-
+
do el = 1, num_elements
! Total model error [kg/day -> kg/s] (all elements)
@@ -3328,7 +3386,6 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
end if
end do
-
! Loop through patches to sum up diagonistics
ipa = 0
cpatch => sites(s)%oldest_patch
@@ -3341,10 +3398,13 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
hio_area_si_age(io_si,cpatch%age_class) = hio_area_si_age(io_si,cpatch%age_class) &
+ cpatch%area * AREA_INV
- hio_area_si_landuse(io_si, cpatch%land_use_label) = &
- hio_area_si_landuse(io_si, cpatch%land_use_label) &
- + cpatch%area * AREA_INV
-
+ ! ignore land use info on nocomp bareground (where landuse label = 0)
+ if (cpatch%land_use_label .gt. nocomp_bareground_land) then
+ hio_area_si_landuse(io_si, cpatch%land_use_label) = &
+ hio_area_si_landuse(io_si, cpatch%land_use_label) &
+ + cpatch%area * AREA_INV
+ end if
+
! Increment some patch-age-resolved diagnostics
hio_lai_si_age(io_si,cpatch%age_class) = hio_lai_si_age(io_si,cpatch%age_class) &
+ sum(cpatch%tlai_profile(:,:,:) * cpatch%canopy_area_profile(:,:,:) ) * cpatch%total_canopy_area
@@ -3605,16 +3665,16 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
! update pft-resolved NPP and GPP fluxes
hio_gpp_si_pft(io_si, ft) = hio_gpp_si_pft(io_si, ft) + &
- ccohort%gpp_acc_hold * n_perm2 / days_per_year / sec_per_day
+ ccohort%gpp_acc_hold * n_perm2 / (days_per_year* sec_per_day)
hio_npp_si_pft(io_si, ft) = hio_npp_si_pft(io_si, ft) + &
- ccohort%npp_acc_hold * n_perm2 / days_per_year / sec_per_day
+ ccohort%npp_acc_hold * n_perm2 / (days_per_year*sec_per_day)
if ( cpatch%land_use_label .eq. secondaryland ) then
hio_gpp_sec_si_pft(io_si, ft) = hio_gpp_sec_si_pft(io_si, ft) + &
- ccohort%gpp_acc_hold * n_perm2 / days_per_year / sec_per_day
+ ccohort%gpp_acc_hold * n_perm2 / (days_per_year*sec_per_day)
hio_npp_sec_si_pft(io_si, ft) = hio_npp_sec_si_pft(io_si, ft) + &
- ccohort%npp_acc_hold * n_perm2 / days_per_year / sec_per_day
+ ccohort%npp_acc_hold * n_perm2 / (days_per_year*sec_per_day)
end if
! Turnover pools [kgC/day] * [day/yr] = [kgC/yr]
@@ -3640,35 +3700,40 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
capf => ccohort%coage_by_pft_class, &
cdam => ccohort%crowndamage)
- gpp_cached = (hio_gpp_si_scpf(io_si,scpf)) * &
- days_per_year * sec_per_day
-
- ! [kgC/m2/s]
+ ! convert [kgC/plant/year] -> [kgC/m2/s]
hio_gpp_si_scpf(io_si,scpf) = hio_gpp_si_scpf(io_si,scpf) + &
- n_perm2*ccohort%gpp_acc_hold / days_per_year / sec_per_day
+ n_perm2*ccohort%gpp_acc_hold / (days_per_year*sec_per_day)
+
hio_npp_totl_si_scpf(io_si,scpf) = hio_npp_totl_si_scpf(io_si,scpf) + &
- ccohort%npp_acc_hold * n_perm2 / days_per_year / sec_per_day
+ ccohort%npp_acc_hold * n_perm2 / (days_per_year*sec_per_day)
hio_npp_leaf_si_scpf(io_si,scpf) = hio_npp_leaf_si_scpf(io_si,scpf) + &
- leaf_m_net_alloc*n_perm2 / days_per_year / sec_per_day
+ leaf_m_net_alloc*n_perm2 / (days_per_year*sec_per_day)
+
hio_npp_fnrt_si_scpf(io_si,scpf) = hio_npp_fnrt_si_scpf(io_si,scpf) + &
- fnrt_m_net_alloc*n_perm2 / days_per_year / sec_per_day
+ fnrt_m_net_alloc*n_perm2 / (days_per_year*sec_per_day)
+
hio_npp_bgsw_si_scpf(io_si,scpf) = hio_npp_bgsw_si_scpf(io_si,scpf) + &
sapw_m_net_alloc*n_perm2*(1._r8-prt_params%allom_agb_frac(ccohort%pft)) / &
- days_per_year / sec_per_day
+ (days_per_year*sec_per_day)
+
hio_npp_agsw_si_scpf(io_si,scpf) = hio_npp_agsw_si_scpf(io_si,scpf) + &
sapw_m_net_alloc*n_perm2*prt_params%allom_agb_frac(ccohort%pft) / &
- days_per_year / sec_per_day
+ (days_per_year*sec_per_day)
+
hio_npp_bgdw_si_scpf(io_si,scpf) = hio_npp_bgdw_si_scpf(io_si,scpf) + &
struct_m_net_alloc*n_perm2*(1._r8-prt_params%allom_agb_frac(ccohort%pft)) / &
- days_per_year / sec_per_day
+ (days_per_year*sec_per_day)
+
hio_npp_agdw_si_scpf(io_si,scpf) = hio_npp_agdw_si_scpf(io_si,scpf) + &
struct_m_net_alloc*n_perm2*prt_params%allom_agb_frac(ccohort%pft) / &
- days_per_year / sec_per_day
+ (days_per_year*sec_per_day)
+
hio_npp_seed_si_scpf(io_si,scpf) = hio_npp_seed_si_scpf(io_si,scpf) + &
- repro_m_net_alloc*n_perm2 / days_per_year / sec_per_day
+ repro_m_net_alloc*n_perm2 / (days_per_year*sec_per_day)
+
hio_npp_stor_si_scpf(io_si,scpf) = hio_npp_stor_si_scpf(io_si,scpf) + &
- store_m_net_alloc*n_perm2 / days_per_year / sec_per_day
+ store_m_net_alloc*n_perm2 / (days_per_year*sec_per_day)
! Woody State Variables (basal area growth increment)
if ( prt_params%woody(ft) == itrue) then
@@ -3746,12 +3811,16 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
end if
!C13 discrimination
- if(gpp_cached + ccohort%gpp_acc_hold > 0.0_r8)then
+ if(abs(gpp_cached_scpf(scpf)-hlm_hio_ignore_val)>nearzero .and. &
+ (gpp_cached_scpf(scpf) + ccohort%gpp_acc_hold) > 0.0_r8) then
+
+ gpp_cached = gpp_cached_scpf(scpf)*days_per_year*sec_per_day
+
hio_c13disc_si_scpf(io_si,scpf) = ((hio_c13disc_si_scpf(io_si,scpf) * gpp_cached) + &
(ccohort%c13disc_acc * ccohort%gpp_acc_hold)) / (gpp_cached + ccohort%gpp_acc_hold)
else
hio_c13disc_si_scpf(io_si,scpf) = 0.0_r8
- endif
+ end if
! number density [/m2]
hio_nplant_si_scpf(io_si,scpf) = hio_nplant_si_scpf(io_si,scpf) + ccohort%n / m2_per_ha
@@ -4518,8 +4587,6 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
! Diagnostics discretized by element type
! ------------------------------------------------------------------------------
- hio_cwd_elcwd(io_si,:) = 0._r8
-
do el = 1, num_elements
flux_diags => sites(s)%flux_diags(el)
@@ -4529,17 +4596,6 @@ subroutine update_history_dyn2(this,nc,nsites,sites,bc_in)
sum(flux_diags%cwd_bg_input(:)) + sum(flux_diags%leaf_litter_input(:)) + &
sum(flux_diags%root_litter_input(:))) / m2_per_ha / sec_per_day
- hio_cwd_ag_elem(io_si,el) = 0._r8
- hio_cwd_bg_elem(io_si,el) = 0._r8
- hio_fines_ag_elem(io_si,el) = 0._r8
- hio_fines_bg_elem(io_si,el) = 0._r8
-
- hio_seed_bank_elem(io_si,el) = 0._r8
- hio_seed_germ_elem(io_si,el) = 0._r8
- hio_seed_decay_elem(io_si,el) = 0._r8
- hio_seeds_in_local_elem(io_si,el) = 0._r8
- hio_seed_in_extern_elem(io_si,el) = 0._r8
- hio_litter_out_elem(io_si,el) = 0._r8
! Plant multi-element states and fluxes
! Zero states, and set the fluxes
@@ -4867,10 +4923,6 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep)
type(fates_cohort_type),pointer :: ccohort
- ! This routine is only called for hlm_hist_level_hifrq >= 1
- if(hlm_hist_level_hifrq<1) return
-
-
associate( hio_gpp_si => this%hvars(ih_gpp_si)%r81d, &
hio_gpp_secondary_si => this%hvars(ih_gpp_secondary_si)%r81d, &
hio_npp_si => this%hvars(ih_npp_si)%r81d, &
@@ -4899,9 +4951,10 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep)
hio_tveg => this%hvars(ih_tveg_si)%r81d)
- ! Flush the relevant history variables
+ ! THIS CAN BE REMOVED WHEN BOTH CTSM AND E3SM CALL FLUSH_ALL_HVARS
+ ! THIS IS NOT A LIABILITY, IT IS JUST REDUNDANT
call this%flush_hvars(nc,upfreq_in=group_hifr_simple)
-
+
dt_tstep_inv = 1.0_r8/dt_tstep
allocate(age_area_rad(size(ED_val_history_ageclass_bin_edges,1)+1))
@@ -4962,14 +5015,17 @@ subroutine update_history_hifrq1(this,nc,nsites,sites,bc_in,bc_out,dt_tstep)
! Diagnostics that are only relevant if there is vegetation present on this site
! ie, non-zero canopy area
-
- site_area_veg_inv = 0._r8
- cpatch => sites(s)%oldest_patch
- do while(associated(cpatch))
- site_area_veg_inv = site_area_veg_inv + cpatch%total_canopy_area
- cpatch => cpatch%younger
- end do !patch loop
-
+ if (hlm_use_nocomp .eq. itrue .and. hlm_use_fixed_biogeog .eq. itrue) then
+ site_area_veg_inv = area - sites(s)%area_bareground * area
+ else
+ site_area_veg_inv = 0._r8
+ cpatch => sites(s)%oldest_patch
+ do while(associated(cpatch))
+ site_area_veg_inv = site_area_veg_inv + cpatch%total_canopy_area
+ cpatch => cpatch%younger
+ end do !patch loop
+ end if
+
if_veg_area: if(site_area_veg_inv < nearzero) then
hio_c_stomata_si(io_si) = hlm_hio_ignore_val
@@ -5142,9 +5198,6 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep)
type(fates_cohort_type),pointer :: ccohort
real(r8) :: dt_tstep_inv ! Time step in frequency units (/s)
- ! This routine is only called for hlm_hist_level_hifrq >= 1
- if(hlm_hist_level_hifrq<2) return
-
associate( hio_ar_si_scpf => this%hvars(ih_ar_si_scpf)%r82d, &
hio_ar_grow_si_scpf => this%hvars(ih_ar_grow_si_scpf)%r82d, &
hio_ar_maint_si_scpf => this%hvars(ih_ar_maint_si_scpf)%r82d, &
@@ -5187,13 +5240,17 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep)
hio_laisun_si_can => this%hvars(ih_laisun_si_can)%r82d, &
hio_laisha_si_can => this%hvars(ih_laisha_si_can)%r82d )
- ! Flush the relevant history variables
- call this%flush_hvars(nc,upfreq_in=group_hifr_complx)
+ ! THIS CAN BE REMOVED WHEN BOTH CTSM AND E3SM CALL FLUSH_ALL_HVARS
+ ! THIS IS NOT A LIABILITY, IT IS JUST REDUNDANT
+ call this%flush_hvars(nc,upfreq_in=group_hifr_complx)
+
dt_tstep_inv = 1.0_r8/dt_tstep
do_sites: do s = 1,nsites
+ call this%zero_site_hvars(sites(s), upfreq_in=group_hifr_complx)
+
site_area_veg_inv = 0._r8
cpatch => sites(s)%oldest_patch
do while(associated(cpatch))
@@ -5213,8 +5270,6 @@ subroutine update_history_hifrq2(this,nc,nsites,sites,bc_in,bc_out,dt_tstep)
patch_area_by_age(1:nlevage) = 0._r8
canopy_area_by_age(1:nlevage) = 0._r8
- call this%zero_site_hvars(sites(s), upfreq_in=group_hifr_complx)
-
cpatch => sites(s)%oldest_patch
do while(associated(cpatch))
@@ -5617,7 +5672,8 @@ subroutine update_history_hydraulics(this,nc,nsites,sites,bc_in,dt_tstep)
if_hifrq0: if(hlm_hist_level_hifrq>0) then
- ! Flush the relevant history variables
+ ! THIS CAN BE REMOVED WHEN BOTH CTSM AND E3SM CALL FLUSH_ALL_HVARS
+ ! THIS IS NOT A LIABILITY, IT IS JUST REDUNDANT
call this%flush_hvars(nc,upfreq_in=group_hydr_simple)
associate( hio_h2oveg_hydro_err_si => this%hvars(ih_h2oveg_hydro_err_si)%r81d, &
@@ -5724,6 +5780,8 @@ subroutine update_history_hydraulics(this,nc,nsites,sites,bc_in,dt_tstep)
hio_rootuptake50_scpf => this%hvars(ih_rootuptake50_scpf)%r82d, &
hio_rootuptake100_scpf => this%hvars(ih_rootuptake100_scpf)%r82d )
+ ! THIS CAN BE REMOVED WHEN BOTH CTSM AND E3SM CALL FLUSH_ALL_HVARS
+ ! THIS IS NOT A LIABILITY, IT IS JUST REDUNDANT
call this%flush_hvars(nc,upfreq_in=group_hydr_complx)
do s = 1,nsites
@@ -5810,8 +5868,6 @@ subroutine update_history_hydraulics(this,nc,nsites,sites,bc_in,dt_tstep)
hio_rootuptake10_scpf(io_si,iscpf) = site_hydr%rootuptake10_scpf(iscls,ipft) * ha_per_m2
hio_rootuptake50_scpf(io_si,iscpf) = site_hydr%rootuptake50_scpf(iscls,ipft) * ha_per_m2
hio_rootuptake100_scpf(io_si,iscpf) = site_hydr%rootuptake100_scpf(iscls,ipft) * ha_per_m2
- hio_iterh1_scpf(io_si,iscpf) = 0._r8
- hio_iterh2_scpf(io_si,iscpf) = 0._r8
end do
end do
@@ -6165,15 +6221,7 @@ subroutine define_history_vars(this, initialize_variables)
upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
index=ih_lai_secondary_si)
- call this%set_history_var(vname='FATES_PATCHAREA_LU', units='m2 m-2', &
- long='patch area by land use type', use_default='active', &
- avgflag='A', vtype=site_landuse_r8, hlms='CLM:ALM', upfreq=group_dyna_simple, ivar=ivar, &
- initialize=initialize_variables, index=ih_area_si_landuse)
-
- call this%set_history_var(vname='FATES_DISTURBANCE_RATE_MATRIX_LULU', units='m2 m-2 yr-1', &
- long='disturbance rates by land use type x land use type matrix', use_default='active', &
- avgflag='A', vtype=site_lulu_r8, hlms='CLM:ALM', upfreq=group_dyna_simple, ivar=ivar, &
- initialize=initialize_variables, index=ih_disturbance_rate_si_lulu)
+
! Secondary forest area and age diagnostics
@@ -6379,31 +6427,31 @@ subroutine define_history_vars(this, initialize_variables)
call this%set_history_var(vname='FATES_NH4UPTAKE', units='kg m-2 s-1', &
long='ammonium uptake rate by plants in kg NH4 per m2 per second', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_nh4uptake_si)
call this%set_history_var(vname='FATES_NO3UPTAKE', units='kg m-2 s-1', &
long='nitrate uptake rate by plants in kg NO3 per m2 per second', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_no3uptake_si)
call this%set_history_var(vname='FATES_NEFFLUX', units='kg m-2 s-1', &
long='nitrogen effluxed from plant in kg N per m2 per second (unused)', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_nefflux_si)
call this%set_history_var(vname='FATES_NDEMAND', units='kg m-2 s-1', &
long='plant nitrogen need (algorithm dependent) in kg N per m2 per second', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_ndemand_si)
call this%set_history_var(vname='FATES_NFIX_SYM', units='kg m-2 s-1', &
long='symbiotic dinitrogen fixation in kg N per m2 per second', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_nfix_si)
call this%set_history_var(vname='FATES_STOREN', units='kg m-2', &
@@ -6488,19 +6536,19 @@ subroutine define_history_vars(this, initialize_variables)
call this%set_history_var(vname='FATES_PUPTAKE', units='kg m-2 s-1', &
long='mineralized phosphorus uptake rate of plants in kg P per m2 per second', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_puptake_si)
call this%set_history_var(vname='FATES_PEFFLUX', units='kg m-2 s-1', &
long='phosphorus effluxed from plant in kg P per m2 per second (unused)', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_pefflux_si)
call this%set_history_var(vname='FATES_PDEMAND', units='kg m-2 s-1', &
long='plant phosphorus need (algorithm dependent) in kg P per m2 per second', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_pdemand_si)
end if phosphorus_active_if0
@@ -6560,14 +6608,21 @@ subroutine define_history_vars(this, initialize_variables)
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_fall_disturbance_rate_si)
-
- call this%set_history_var(vname='FATES_HARVEST_CARBON_FLUX', &
+
+ call this%set_history_var(vname='FATES_HARVEST_WOODPROD_C_FLUX', &
units='kg m-2 yr-1', &
- long='harvest carbon flux in kg carbon per m2 per year', &
+ long='harvest-associated wood product carbon flux in kg C per m2 per year', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
- index = ih_harvest_carbonflux_si)
-
+ index = ih_harvest_woodprod_carbonflux_si)
+
+ call this%set_history_var(vname='FATES_LUCHANGE_WOODPROD_C_FLUX', &
+ units='kg m-2 yr-1', &
+ long='land-use-change-associated wood product carbon flux in kg C per m2 per year', &
+ use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
+ upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ index = ih_luchange_woodprod_carbonflux_si)
+
call this%set_history_var(vname='FATES_TVEG24', units='degree_Celsius', &
long='fates 24-hr running mean vegetation temperature by site', &
use_default='active', &
@@ -6596,11 +6651,15 @@ subroutine define_history_vars(this, initialize_variables)
avgflag='A', vtype=site_r8, hlms='CLM:ALM', upfreq=group_dyna_simple, &
ivar=ivar, initialize=initialize_variables, index = ih_harvest_debt_sec_si )
+ ! Nutrient flux variables (dynamics call frequency)
+ ! ----------------------------------------------------
call this%set_history_var(vname='FATES_EXCESS_RESP', units='kg m-2 s-1', &
long='respiration of un-allocatable carbon gain', &
use_default='active', avgflag='A', vtype=site_r8, hlms='CLM:ALM', &
- upfreq=group_dyna_simple, ivar=ivar, initialize=initialize_variables, &
+ upfreq=group_nflx_simple, ivar=ivar, initialize=initialize_variables, &
index = ih_excess_resp_si)
+
+
! slow carbon fluxes associated with mortality from or transfer betweeen canopy and understory
call this%set_history_var(vname='FATES_DEMOTION_CARBONFLUX', &
@@ -6732,6 +6791,21 @@ subroutine define_history_vars(this, initialize_variables)
if_dyn1: if(hlm_hist_level_dynam>1) then
+ call this%set_history_var(vname='FATES_PATCHAREA_LU', units='m2 m-2', &
+ long='patch area by land use type', use_default='active', &
+ avgflag='A', vtype=site_landuse_r8, hlms='CLM:ALM', upfreq=group_dyna_complx, &
+ ivar=ivar, initialize=initialize_variables, index=ih_area_si_landuse)
+
+ call this%set_history_var(vname='FATES_TRANSITION_MATRIX_LULU', units='m2 m-2 yr-1', &
+ long='land use transition matrix', use_default='active', &
+ avgflag='A', vtype=site_lulu_r8, hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ initialize=initialize_variables, index=ih_transition_matrix_si_lulu)
+
+ call this%set_history_var(vname='FATES_DISTURBANCE_RATE_MATRIX_LULU', units='m2 m-2 yr-1', &
+ long='disturbance rates by land use type x land use type matrix', use_default='active', &
+ avgflag='A', vtype=site_lulu_r8, hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ initialize=initialize_variables, index=ih_disturbance_rate_si_lulu)
+
call this%set_history_var(vname='FATES_VEGC_PF', units='kg m-2', &
long='total PFT-level biomass in kg of carbon per land area', &
use_default='active', avgflag='A', vtype=site_pft_r8, hlms='CLM:ALM', &
@@ -7111,32 +7185,32 @@ subroutine define_history_vars(this, initialize_variables)
units='kg m-2 s-1', &
long='ammonium uptake rate by plants by size-class x pft in kg NH4 per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_nh4uptake_scpf)
call this%set_history_var(vname='FATES_NO3UPTAKE_SZPF', &
units='kg m-2 s-1', &
long='nitrate uptake rate by plants by size-class x pft in kg NO3 per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_no3uptake_scpf)
call this%set_history_var(vname='FATES_NEFFLUX_SZPF', units='kg m-2 s-1', &
long='nitrogen efflux, root to soil, by size-class x pft in kg N per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_nefflux_scpf)
call this%set_history_var(vname='FATES_NDEMAND_SZPF', units='kg m-2 s-1', &
long='plant N need (algorithm dependent), by size-class x pft in kg N per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_ndemand_scpf)
call this%set_history_var(vname='FATES_NFIX_SYM_SZPF', units='kg m-2 s-1', &
long='symbiotic dinitrogen fixation, by size-class x pft in kg N per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_nfix_scpf)
call this%set_history_var(vname='FATES_VEGN_SZPF', units='kg m-2', &
@@ -7248,20 +7322,20 @@ subroutine define_history_vars(this, initialize_variables)
units='kg m-2 s-1', &
long='phosphorus uptake rate by plants, by size-class x pft in kg P per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_puptake_scpf)
call this%set_history_var(vname='FATES_PEFFLUX_SZPF', &
units='kg m-2 s-1', &
long='phosphorus efflux, root to soil, by size-class x pft in kg P per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_pefflux_scpf)
call this%set_history_var(vname='FATES_PDEMAND_SZPF', units='kg m-2 s-1', &
long='plant P need (algorithm dependent), by size-class x pft in kg P per m2 per second', &
use_default='inactive', avgflag='A', vtype=site_size_pft_r8, &
- hlms='CLM:ALM', upfreq=group_dyna_complx, ivar=ivar, &
+ hlms='CLM:ALM', upfreq=group_nflx_complx, ivar=ivar, &
initialize=initialize_variables, index = ih_pdemand_scpf)
end if phosphorus_active_if1
diff --git a/main/FatesIOVariableKindMod.F90 b/main/FatesIOVariableKindMod.F90
index 61e2c93c9d..75ea7dbe57 100644
--- a/main/FatesIOVariableKindMod.F90
+++ b/main/FatesIOVariableKindMod.F90
@@ -51,16 +51,36 @@ module FatesIOVariableKindMod
character(*), parameter, public :: site_elcwd_r8 = 'SI_ELEMCWD_R8'
character(*), parameter, public :: site_elage_r8 = 'SI_ELEMAGE_R8'
-
+ ! ------------------------------------------------------------------
+ !
+ ! History Variable Groups
+ !
! These are group indices for output variables. We use
! these groups to do things like zero-ing and initializing
-
+ !
+ ! These groups are updated at the dynamics (daily) step
+ ! so they are turned on and off with dimlevel(2)
+ !
+ ! active when dimlevel(2)>0
integer, parameter, public :: group_dyna_simple = 1
+ integer, parameter, public :: group_nflx_simple = 7
+
+ ! active when dimlevel(2)>1
integer, parameter, public :: group_dyna_complx = 2
+ integer, parameter, public :: group_nflx_complx = 8
+
+ ! These groups are updated at the fast step
+ ! so they are turned on and off with dimlevel(1)
+ !
+ ! active when dimlevel(1)>0
integer, parameter, public :: group_hifr_simple = 3
- integer, parameter, public :: group_hifr_complx = 4
integer, parameter, public :: group_hydr_simple = 5
+
+ ! active when dimlevel(1)>1
+ integer, parameter, public :: group_hifr_complx = 4
integer, parameter, public :: group_hydr_complx = 6
+
+ ! -------------------------------------------------------------------
! NOTE(RGK, 2016) %active is not used yet. Was intended as a check on the HLM->FATES
! control parameter passing to ensure all active dimension types received all
diff --git a/main/FatesInterfaceMod.F90 b/main/FatesInterfaceMod.F90
index 99e13fa5f4..eea65eaa0a 100644
--- a/main/FatesInterfaceMod.F90
+++ b/main/FatesInterfaceMod.F90
@@ -41,6 +41,7 @@ module FatesInterfaceMod
use FatesConstantsMod , only : n_landuse_cats
use FatesConstantsMod , only : primaryland
use FatesConstantsMod , only : secondaryland
+ use FatesConstantsMod , only : n_crop_lu_types
use FatesConstantsMod , only : n_term_mort_types
use FatesGlobals , only : fates_global_verbose
use FatesGlobals , only : fates_log
@@ -417,7 +418,7 @@ end subroutine zero_bcs
! ===========================================================================
subroutine allocate_bcin(bc_in, nlevsoil_in, nlevdecomp_in, num_lu_harvest_cats, num_luh2_states, &
- num_luh2_transitions, natpft_lb,natpft_ub)
+ num_luh2_transitions, surfpft_lb,surfpft_ub)
! ---------------------------------------------------------------------------------
! Allocate and Initialze the FATES boundary condition vectors
@@ -430,7 +431,7 @@ subroutine allocate_bcin(bc_in, nlevsoil_in, nlevdecomp_in, num_lu_harvest_cats,
integer,intent(in) :: num_lu_harvest_cats
integer,intent(in) :: num_luh2_states
integer,intent(in) :: num_luh2_transitions
- integer,intent(in) :: natpft_lb,natpft_ub ! dimension bounds of the array holding surface file pft data
+ integer,intent(in) :: surfpft_lb,surfpft_ub ! dimension bounds of the array holding surface file pft data
! Allocate input boundaries
@@ -563,7 +564,13 @@ subroutine allocate_bcin(bc_in, nlevsoil_in, nlevdecomp_in, num_lu_harvest_cats,
allocate(bc_in%hlm_harvest_catnames(0))
end if
- allocate(bc_in%pft_areafrac(natpft_lb:natpft_ub))
+ if ( hlm_use_fixed_biogeog .eq. itrue) then
+ if (hlm_use_luh .eq. itrue ) then
+ allocate(bc_in%pft_areafrac_lu(size( EDPftvarcon_inst%hlm_pft_map,2),n_landuse_cats-n_crop_lu_types))
+ else
+ allocate(bc_in%pft_areafrac(surfpft_lb:surfpft_ub))
+ endif
+ endif
! LUH2 state and transition data
if (hlm_use_luh .eq. itrue) then
@@ -575,10 +582,11 @@ subroutine allocate_bcin(bc_in, nlevsoil_in, nlevdecomp_in, num_lu_harvest_cats,
! Variables for SP mode.
if(hlm_use_sp.eq.itrue) then
- allocate(bc_in%hlm_sp_tlai(natpft_lb:natpft_ub))
- allocate(bc_in%hlm_sp_tsai(natpft_lb:natpft_ub))
- allocate(bc_in%hlm_sp_htop(natpft_lb:natpft_ub))
- end if
+ allocate(bc_in%hlm_sp_tlai(surfpft_lb:surfpft_ub))
+ allocate(bc_in%hlm_sp_tsai(surfpft_lb:surfpft_ub))
+ allocate(bc_in%hlm_sp_htop(surfpft_lb:surfpft_ub))
+ end if
+
return
end subroutine allocate_bcin
@@ -611,14 +619,13 @@ subroutine allocate_bcout(bc_out, nlevsoil_in, nlevdecomp_in)
allocate(bc_out%rssha_pa(maxpatch_total))
! Canopy Radiation
- allocate(bc_out%albd_parb(maxpatch_total,num_swb))
- allocate(bc_out%albi_parb(maxpatch_total,num_swb))
- allocate(bc_out%fabd_parb(maxpatch_total,num_swb))
- allocate(bc_out%fabi_parb(maxpatch_total,num_swb))
- allocate(bc_out%ftdd_parb(maxpatch_total,num_swb))
- allocate(bc_out%ftid_parb(maxpatch_total,num_swb))
- allocate(bc_out%ftii_parb(maxpatch_total,num_swb))
-
+ allocate(bc_out%albd_parb(fates_maxPatchesPerSite,num_swb))
+ allocate(bc_out%albi_parb(fates_maxPatchesPerSite,num_swb))
+ allocate(bc_out%fabd_parb(fates_maxPatchesPerSite,num_swb))
+ allocate(bc_out%fabi_parb(fates_maxPatchesPerSite,num_swb))
+ allocate(bc_out%ftdd_parb(fates_maxPatchesPerSite,num_swb))
+ allocate(bc_out%ftid_parb(fates_maxPatchesPerSite,num_swb))
+ allocate(bc_out%ftii_parb(fates_maxPatchesPerSite,num_swb))
! We allocate the boundary conditions to the BGC
! model, regardless of what scheme we use. The BGC
@@ -1930,6 +1937,12 @@ subroutine set_fates_ctrlparms(tag,ival,rval,cval)
write(fates_log(),*) 'Transfering hlm_use_luh = ',ival,' to FATES'
end if
+ case('use_fates_potentialveg')
+ hlm_use_potentialveg = ival
+ if (fates_global_verbose()) then
+ write(fates_log(),*) 'Transfering hlm_use_potentialveg = ',ival,' to FATES'
+ end if
+
case('num_luh2_states')
hlm_num_luh2_states = ival
if (fates_global_verbose()) then
diff --git a/main/FatesInterfaceTypesMod.F90 b/main/FatesInterfaceTypesMod.F90
index a36d5195f4..75e64307a5 100644
--- a/main/FatesInterfaceTypesMod.F90
+++ b/main/FatesInterfaceTypesMod.F90
@@ -124,6 +124,8 @@ module FatesInterfaceTypesMod
! bc_in%hlm_harvest_rates and bc_in%hlm_harvest_catnames
integer, public :: hlm_use_luh ! flag to signal whether or not to use luh2 drivers
+ integer, public :: hlm_use_potentialveg ! flag to signal whether or not to use potential vegetation only
+ ! (i.e., no land use and instead force all lands to be primary)
integer, public :: hlm_num_luh2_states ! number of land use state types provided in LUH2 forcing dataset
integer, public :: hlm_num_luh2_transitions ! number of land use transition types provided in LUH2 forcing dataset
@@ -240,7 +242,7 @@ module FatesInterfaceTypesMod
! dataset than the number of PFTs in FATES, we have to allocate with
! the prior so that we can hold the LAI data
integer, public :: fates_maxPatchesPerSite
-
+
integer, public :: max_comp_per_site ! This is the maximum number of nutrient aquisition
! competitors that will be generated on each site
@@ -572,7 +574,12 @@ module FatesInterfaceTypesMod
real(r8) :: site_area ! Actual area of current site [m2], only used in carbon-based harvest
! Fixed biogeography mode
- real(r8), allocatable :: pft_areafrac(:) ! Fractional area of the FATES column occupied by each PFT
+ real(r8), allocatable :: pft_areafrac(:) ! Fractional area of the FATES column occupied by each PFT
+
+ ! Fixed biogeography mode with land use active
+ real(r8), allocatable :: pft_areafrac_lu(:,:) ! Fractional area occupied by each PFT on each land use type
+ real(r8) :: baregroundfrac ! fractional area held as bare-ground
+
! Satellite Phenology (SP) input variables. (where each patch only has one PFT)
! ---------------------------------------------------------------------------------
diff --git a/main/FatesInventoryInitMod.F90 b/main/FatesInventoryInitMod.F90
index 61f77387f4..22a48537b5 100644
--- a/main/FatesInventoryInitMod.F90
+++ b/main/FatesInventoryInitMod.F90
@@ -686,7 +686,7 @@ subroutine set_inventory_patch_type1(newpatch,pss_file_unit,ipa,ios,patch_name)
type(litter_type),pointer :: litt
integer :: el ! index for elements
real(r8) :: p_time ! Time patch was recorded
- real(r8) :: p_trk ! Land Use index (see above descriptions)
+ integer :: p_trk ! Land Use index (see above descriptions)
character(len=patchname_strlen) :: p_name ! unique string identifier of patch
real(r8) :: p_age ! Patch age [years]
real(r8) :: p_area ! Patch area [fraction]
@@ -694,9 +694,10 @@ subroutine set_inventory_patch_type1(newpatch,pss_file_unit,ipa,ios,patch_name)
integer :: ipft ! index for counting PFTs
real(r8) :: pftfrac ! the inverse of the total number of PFTs
- character(len=128),parameter :: wr_fmt = &
- '(F5.2,2X,A4,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2)'
-
+ character(len=30),parameter :: hd_fmt = &
+ '(A5,2X,A20,2X,A4,2X,A5,2X,A17)'
+ character(len=47),parameter :: wr_fmt = &
+ '(F5.2,2X,A20,2X,I4,2X,F5.2,2X,F17.14)'
read(pss_file_unit,fmt=*,iostat=ios) p_time, p_name, p_trk, p_age, p_area
@@ -705,6 +706,8 @@ subroutine set_inventory_patch_type1(newpatch,pss_file_unit,ipa,ios,patch_name)
patch_name = trim(p_name)
if( debug_inv) then
+ write(*,fmt=hd_fmt) &
+ ' time',' patch',' trk',' age',' area'
write(*,fmt=wr_fmt) &
p_time, p_name, p_trk, p_age, p_area
end if
@@ -828,8 +831,10 @@ subroutine set_inventory_cohort_type1(csite,bc_in,css_file_unit,npatches, &
real(r8) :: stem_drop_fraction ! Stem abscission fraction
integer :: i_pft, ncohorts_to_create
- character(len=128),parameter :: wr_fmt = &
- '(F7.1,2X,A20,2X,A20,2X,F5.2,2X,F5.2,2X,I4,2X,F5.2,2X,F5.2,2X,F5.2,2X,F5.2)'
+ character(len=35),parameter :: hd_fmt = &
+ '(A7,2X,A20,2X,A5,2X,A6,2X,A4,2X,A9)'
+ character(len=43),parameter :: wr_fmt = &
+ '(F7.1,2X,A20,2X,F5.2,2X,F6.2,2X,I4,2X,F9.6)'
real(r8), parameter :: abnormal_large_nplant = 1000.0_r8 ! Used to catch bad values
real(r8), parameter :: abnormal_large_dbh = 500.0_r8 ! I've never heard of a tree > 3m
@@ -858,6 +863,8 @@ subroutine set_inventory_cohort_type1(csite,bc_in,css_file_unit,npatches, &
if(.not.matched_patch)then
write(fates_log(), *) 'could not match a cohort with a patch'
+ write(fates_log(),fmt=hd_fmt) &
+ ' time',' patch',' dbh','height',' pft',' nplant'
write(fates_log(),fmt=wr_fmt) &
c_time, p_name, c_dbh, c_height, c_pft, c_nplant
call endrun(msg=errMsg(sourcefile, __LINE__))
diff --git a/main/FatesParametersInterface.F90 b/main/FatesParametersInterface.F90
index aa0ef85287..0559ec3eb6 100644
--- a/main/FatesParametersInterface.F90
+++ b/main/FatesParametersInterface.F90
@@ -135,7 +135,7 @@ subroutine Destroy(this)
integer :: n
do n = 1, this%num_parameters
- deallocate(this%parameters(n)%data)
+ deallocate(this%parameters(n)%data)
end do
end subroutine Destroy
diff --git a/main/FatesRestartInterfaceMod.F90 b/main/FatesRestartInterfaceMod.F90
index 90e282253b..3f7e8375fe 100644
--- a/main/FatesRestartInterfaceMod.F90
+++ b/main/FatesRestartInterfaceMod.F90
@@ -10,6 +10,7 @@ module FatesRestartInterfaceMod
use FatesConstantsMod, only : fates_unset_r8, fates_unset_int
use FatesConstantsMod, only : primaryland
use FatesConstantsMod, only : nearzero
+ use FatesConstantsMod, only : n_landuse_cats
use FatesConstantsMod, only : default_regeneration
use FatesConstantsMod, only : TRS_regeneration
use FatesGlobals, only : fates_log
@@ -24,6 +25,7 @@ module FatesRestartInterfaceMod
use FatesInterfaceTypesMod, only : hlm_parteh_mode
use FatesInterfaceTypesMod, only : hlm_use_sp
use FatesInterfaceTypesMod, only : hlm_use_nocomp, hlm_use_fixed_biogeog
+ use FatesInterfaceTypesMod, only : hlm_use_potentialveg
use FatesInterfaceTypesMod, only : fates_maxElementsPerSite
use FatesInterfaceTypesMod, only : hlm_use_tree_damage
use FatesHydraulicsMemMod, only : nshell
@@ -99,11 +101,14 @@ module FatesRestartInterfaceMod
integer :: ir_cndaysleafon_si
integer :: ir_cndaysleafoff_si
integer :: ir_phenmodeldate_si
- integer :: ir_acc_ni_si
+ integer :: ir_fireweather_index_si
integer :: ir_gdd_si
+ integer :: ir_min_allowed_landuse_fraction_si
+ integer :: ir_landuse_vector_gt_min_si
+ integer :: ir_area_bareground_si
integer :: ir_snow_depth_si
integer :: ir_trunk_product_si
-
+ integer :: ir_landuse_config_si
integer :: ir_ncohort_pa
integer :: ir_canopy_layer_co
integer :: ir_canopy_layer_yesterday_co
@@ -267,7 +272,8 @@ module FatesRestartInterfaceMod
integer :: ir_rootlittin_flxdg
integer :: ir_oldstock_mbal
integer :: ir_errfates_mbal
- integer :: ir_woodprod_mbal
+ integer :: ir_woodprod_harvest_mbal
+ integer :: ir_woodprod_landusechange_mbal
integer :: ir_prt_base ! Base index for all PRT variables
! site-level input seed from dispersal
@@ -704,12 +710,24 @@ subroutine define_restart_vars(this, initialize_variables)
call this%set_restart_var(vname='fates_acc_nesterov_id', vtype=site_r8, &
long_name='a nesterov index accumulator', units='unitless', flushval = flushzero, &
- hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_acc_ni_si )
+ hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_fireweather_index_si )
call this%set_restart_var(vname='fates_gdd_site', vtype=site_r8, &
long_name='growing degree days at each site', units='degC days', flushval = flushzero, &
hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_gdd_si )
+ call this%set_restart_var(vname='fates_min_allowed_landuse_fraction_site', vtype=site_r8, &
+ long_name='minimum allowed land use fraction at each site', units='fraction', flushval = flushzero, &
+ hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_min_allowed_landuse_fraction_si )
+
+ call this%set_restart_var(vname='fates_landuse_vector_gt_min_site', vtype=cohort_int, &
+ long_name='minimum allowed land use fraction at each site', units='logical', flushval = flushzero, &
+ hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_landuse_vector_gt_min_si )
+
+ call this%set_restart_var(vname='fates_area_bareground_site', vtype=site_r8, &
+ long_name='minimum allowed land use fraction at each site', units='fraction', flushval = flushzero, &
+ hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_area_bareground_si )
+
call this%set_restart_var(vname='fates_snow_depth_site', vtype=site_r8, &
long_name='average snow depth', units='m', flushval = flushzero, &
hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_snow_depth_si )
@@ -719,6 +737,11 @@ subroutine define_restart_vars(this, initialize_variables)
units='kgC/m2', flushval = flushzero, &
hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_trunk_product_si )
+ call this%set_restart_var(vname='fates_landuse_config_site', vtype=site_int, &
+ long_name='hlm_use_potentialveg status of run that created this restart file', &
+ units='kgC/m2', flushval = flushzero, &
+ hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_landuse_config_si )
+
! -----------------------------------------------------------------------------------
! Variables stored within cohort vectors
! Note: Some of these are multi-dimensional variables in the patch/site dimension
@@ -1134,10 +1157,15 @@ subroutine define_restart_vars(this, initialize_variables)
end if
- call this%RegisterCohortVector(symbol_base='fates_woodproduct', vtype=site_r8, &
- long_name_base='Current wood product flux', &
+ call this%RegisterCohortVector(symbol_base='fates_woodprod_harv', vtype=cohort_r8, &
+ long_name_base='Current wood product flux from harvest', &
+ units='kg/m2/day', veclength=num_elements, flushval = flushzero, &
+ hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_woodprod_harvest_mbal)
+
+ call this%RegisterCohortVector(symbol_base='fates_woodprod_luc', vtype=cohort_r8, &
+ long_name_base='Current wood product flux from land use change', &
units='kg/m2/day', veclength=num_elements, flushval = flushzero, &
- hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_woodprod_mbal)
+ hlms='CLM:ALM', initialize=initialize_variables, ivar=ivar, index = ir_woodprod_landusechange_mbal)
! Only register satellite phenology related restart variables if it is turned on!
@@ -1995,6 +2023,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
integer :: io_idx_si_scpf_term ! loop counter for scls, pft, and termination type
integer :: io_idx_si_pft_term ! loop counter for pft, and termination type
integer :: io_idx_si_luludi ! site-level lu x lu x ndist index
+ integer :: io_idx_si_lu ! site-level lu index
! Some counters (for checking mostly)
integer :: totalcohorts ! total cohort count on this thread (diagnostic)
@@ -2017,6 +2046,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
integer :: i_cdam ! loop counter for damage
integer :: icdi ! loop counter for damage
integer :: icdj ! loop counter for damage
+ integer :: i_landuse,i_pflu ! loop counter for land use class
integer :: i_term_type ! loop counter for termination type
integer :: i_lu_donor, i_lu_receiver, i_dist ! loop counters for land use and disturbance
@@ -2034,10 +2064,14 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
rio_cndaysleafon_si => this%rvars(ir_cndaysleafon_si)%int1d, &
rio_cndaysleafoff_si => this%rvars(ir_cndaysleafoff_si)%int1d, &
rio_phenmodeldate_si => this%rvars(ir_phenmodeldate_si)%int1d, &
- rio_acc_ni_si => this%rvars(ir_acc_ni_si)%r81d, &
+ rio_fireweather_index_si => this%rvars(ir_fireweather_index_si)%r81d, &
rio_gdd_si => this%rvars(ir_gdd_si)%r81d, &
+ rio_min_allowed_landuse_fraction_si => this%rvars(ir_min_allowed_landuse_fraction_si)%r81d, &
+ rio_landuse_vector_gt_min_si => this%rvars(ir_landuse_vector_gt_min_si)%int1d, &
+ rio_area_bareground_si => this%rvars(ir_area_bareground_si)%r81d, &
rio_snow_depth_si => this%rvars(ir_snow_depth_si)%r81d, &
rio_trunk_product_si => this%rvars(ir_trunk_product_si)%r81d, &
+ rio_landuse_config_s => this%rvars(ir_landuse_config_si)%int1d, &
rio_ncohort_pa => this%rvars(ir_ncohort_pa)%int1d, &
rio_fcansno_pa => this%rvars(ir_fcansno_pa)%r81d, &
rio_solar_zenith_flag_pa => this%rvars(ir_solar_zenith_flag_pa)%int1d, &
@@ -2130,6 +2164,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
rio_abg_fmort_flux_siscpf => this%rvars(ir_abg_fmort_flux_siscpf)%r81d, &
rio_abg_term_flux_siscpf => this%rvars(ir_abg_term_flux_siscpf)%r81d, &
rio_disturbance_rates_siluludi => this%rvars(ir_disturbance_rates_siluludi)%r81d, &
+ rio_landuse_config_si => this%rvars(ir_landuse_config_si)%int1d, &
rio_imortrate_sicdpf => this%rvars(ir_imortrate_sicdpf)%r81d, &
rio_imortcflux_sicdsc => this%rvars(ir_imortcflux_sicdsc)%r81d, &
@@ -2182,6 +2217,7 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
io_idx_si_scpf_term = io_idx_co_1st
io_idx_si_pft_term = io_idx_co_1st
io_idx_si_luludi = io_idx_co_1st
+ io_idx_si_lu = io_idx_co_1st
! recruitment rate
do i_pft = 1,numpft
@@ -2193,9 +2229,24 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
end do
do i_pft = 1,numpft
- rio_area_pft_sift(io_idx_co_1st+i_pft-1) = sites(s)%area_pft(i_pft)
+ do i_landuse = 1, n_landuse_cats
+ i_pflu = i_landuse + (i_pft - 1) * n_landuse_cats
+ rio_area_pft_sift(io_idx_co_1st+i_pflu-1) = sites(s)%area_pft(i_pft, i_landuse)
+ end do
end do
+ rio_min_allowed_landuse_fraction_si(io_idx_si) = sites(s)%min_allowed_landuse_fraction
+ do i_landuse = 1, n_landuse_cats
+ if ( sites(s)%landuse_vector_gt_min(i_landuse)) then
+ rio_landuse_vector_gt_min_si(io_idx_si_lu) = itrue
+ else
+ rio_landuse_vector_gt_min_si(io_idx_si_lu) = ifalse
+ endif
+ io_idx_si_lu = io_idx_si_lu + 1
+ end do
+
+ rio_area_bareground_si(io_idx_si) = sites(s)%area_bareground
+
do i_scls = 1, nlevsclass
do i_pft = 1, numpft
rio_fmortrate_cano_siscpf(io_idx_si_scpf) = sites(s)%fmort_rate_canopy(i_scls, i_pft)
@@ -2262,12 +2313,13 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
do i_pft=1,numpft
this%rvars(ir_leaflittin_flxdg+el-1)%r81d(io_idx_si_pft) = sites(s)%flux_diags(el)%leaf_litter_input(i_pft)
this%rvars(ir_rootlittin_flxdg+el-1)%r81d(io_idx_si_pft) = sites(s)%flux_diags(el)%root_litter_input(i_pft)
+ this%rvars(ir_woodprod_harvest_mbal+el-1)%r81d(io_idx_si_pft) = sites(s)%mass_balance(el)%wood_product_harvest(i_pft)
+ this%rvars(ir_woodprod_landusechange_mbal+el-1)%r81d(io_idx_si_pft) = sites(s)%mass_balance(el)%wood_product_landusechange(i_pft)
io_idx_si_pft = io_idx_si_pft + 1
end do
this%rvars(ir_oldstock_mbal+el-1)%r81d(io_idx_si) = sites(s)%mass_balance(el)%old_stock
this%rvars(ir_errfates_mbal+el-1)%r81d(io_idx_si) = sites(s)%mass_balance(el)%err_fates
- this%rvars(ir_woodprod_mbal+el-1)%r81d(io_idx_si) = sites(s)%mass_balance(el)%wood_product
end do
end if
@@ -2638,11 +2690,15 @@ subroutine set_restart_vectors(this,nc,nsites,sites)
- rio_acc_ni_si(io_idx_si) = sites(s)%acc_NI
+ rio_fireweather_index_si(io_idx_si) = sites(s)%fireWeather%fire_weather_index
rio_snow_depth_si(io_idx_si) = sites(s)%snow_depth
! Accumulated trunk product
rio_trunk_product_si(io_idx_si) = sites(s)%resources_management%trunk_product_site
+
+ ! land use flag
+ rio_landuse_config_si(io_idx_si) = hlm_use_potentialveg
+
! set numpatches for this column
rio_npatch_si(io_idx_si) = patchespersite
@@ -2970,6 +3026,7 @@ subroutine get_restart_vectors(this, nc, nsites, sites)
integer :: io_idx_si_scpf_term ! loop counter for scls, pft, and termination type
integer :: io_idx_si_pft_term ! loop counter for pft, and termination type
integer :: io_idx_si_luludi ! site-level lu x lu x ndist index
+ integer :: io_idx_si_lu ! site-level lu x lu x ndist index
! Some counters (for checking mostly)
integer :: totalcohorts ! total cohort count on this thread (diagnostic)
@@ -2989,8 +3046,9 @@ subroutine get_restart_vectors(this, nc, nsites, sites)
integer :: i_cdam ! loop counter for damage class
integer :: icdj ! loop counter for damage class
integer :: icdi ! loop counter for damage class
+ integer :: i_landuse,i_pflu ! loop counter for land use class
+ integer :: i_lu_donor, i_lu_receiver, i_dist ! loop counters for land use and disturbance
integer :: i_term_type ! loop counter for termination type
- integer :: i_lu_donor, i_lu_receiver, i_dist ! loop counters for land use and disturbance
associate( rio_npatch_si => this%rvars(ir_npatch_si)%int1d, &
rio_cd_status_si => this%rvars(ir_cd_status_si)%int1d, &
@@ -3001,10 +3059,14 @@ subroutine get_restart_vectors(this, nc, nsites, sites)
rio_cndaysleafon_si => this%rvars(ir_cndaysleafon_si)%int1d, &
rio_cndaysleafoff_si => this%rvars(ir_cndaysleafoff_si)%int1d, &
rio_phenmodeldate_si => this%rvars(ir_phenmodeldate_si)%int1d, &
- rio_acc_ni_si => this%rvars(ir_acc_ni_si)%r81d, &
+ rio_fireweather_index_si => this%rvars(ir_fireweather_index_si)%r81d, &
rio_gdd_si => this%rvars(ir_gdd_si)%r81d, &
+ rio_min_allowed_landuse_fraction_si => this%rvars(ir_min_allowed_landuse_fraction_si)%r81d, &
+ rio_landuse_vector_gt_min_si => this%rvars(ir_landuse_vector_gt_min_si)%int1d, &
+ rio_area_bareground_si => this%rvars(ir_area_bareground_si)%r81d, &
rio_snow_depth_si => this%rvars(ir_snow_depth_si)%r81d, &
rio_trunk_product_si => this%rvars(ir_trunk_product_si)%r81d, &
+ rio_landuse_config_si => this%rvars(ir_landuse_config_si)%int1d, &
rio_ncohort_pa => this%rvars(ir_ncohort_pa)%int1d, &
rio_fcansno_pa => this%rvars(ir_fcansno_pa)%r81d, &
rio_solar_zenith_flag_pa => this%rvars(ir_solar_zenith_flag_pa)%int1d, &
@@ -3140,6 +3202,7 @@ subroutine get_restart_vectors(this, nc, nsites, sites)
io_idx_si_pft_term = io_idx_co_1st
io_idx_si_luludi = io_idx_co_1st
+ io_idx_si_lu = io_idx_co_1st
! read seed_bank info(site-level, but PFT-resolved)
do i_pft = 1,numpft
@@ -3149,17 +3212,22 @@ subroutine get_restart_vectors(this, nc, nsites, sites)
! variables for fixed biogeography mode. These are currently used in restart even when this is off.
do i_pft = 1,numpft
sites(s)%use_this_pft(i_pft) = rio_use_this_pft_sift(io_idx_co_1st+i_pft-1)
- sites(s)%area_pft(i_pft) = rio_area_pft_sift(io_idx_co_1st+i_pft-1)
+ do i_landuse = 1, n_landuse_cats
+ i_pflu = i_landuse + (i_pft - 1) * n_landuse_cats
+ sites(s)%area_pft(i_pft, i_landuse) = rio_area_pft_sift(io_idx_co_1st+i_pflu-1)
+ end do
enddo
- ! calculate the bareground area for the pft in no competition + fixed biogeo modes
- if (hlm_use_nocomp .eq. itrue .and. hlm_use_fixed_biogeog .eq. itrue) then
- if (area-sum(sites(s)%area_pft(1:numpft)) .gt. nearzero) then
- sites(s)%area_pft(0) = area - sum(sites(s)%area_pft(1:numpft))
+ sites(s)%min_allowed_landuse_fraction = rio_min_allowed_landuse_fraction_si(io_idx_si)
+ do i_landuse = 1, n_landuse_cats
+ if ( rio_landuse_vector_gt_min_si(io_idx_si_lu) .eq. itrue ) then
+ sites(s)%landuse_vector_gt_min(i_landuse) = .true.
else
- sites(s)%area_pft(0) = 0.0_r8
+ sites(s)%landuse_vector_gt_min(i_landuse) = .false.
endif
- endif
+ io_idx_si_lu = io_idx_si_lu + 1
+ end do
+ sites(s)%area_bareground = rio_area_bareground_si(io_idx_si)
do i_scls = 1,nlevsclass
do i_pft = 1, numpft
@@ -3228,12 +3296,13 @@ subroutine get_restart_vectors(this, nc, nsites, sites)
do i_pft=1,numpft
sites(s)%flux_diags(el)%leaf_litter_input(i_pft) = this%rvars(ir_leaflittin_flxdg+el-1)%r81d(io_idx_si_pft)
sites(s)%flux_diags(el)%root_litter_input(i_pft) = this%rvars(ir_rootlittin_flxdg+el-1)%r81d(io_idx_si_pft)
+ sites(s)%mass_balance(el)%wood_product_harvest(i_pft) = this%rvars(ir_woodprod_harvest_mbal+el-1)%r81d(io_idx_si_pft)
+ sites(s)%mass_balance(el)%wood_product_landusechange(i_pft) = this%rvars(ir_woodprod_landusechange_mbal+el-1)%r81d(io_idx_si_pft)
io_idx_si_pft = io_idx_si_pft + 1
end do
sites(s)%mass_balance(el)%old_stock = this%rvars(ir_oldstock_mbal+el-1)%r81d(io_idx_si)
sites(s)%mass_balance(el)%err_fates = this%rvars(ir_errfates_mbal+el-1)%r81d(io_idx_si)
- sites(s)%mass_balance(el)%wood_product = this%rvars(ir_woodprod_mbal+el-1)%r81d(io_idx_si)
end do
end if
@@ -3643,10 +3712,22 @@ subroutine get_restart_vectors(this, nc, nsites, sites)
- sites(s)%acc_NI = rio_acc_ni_si(io_idx_si)
+ sites(s)%fireWeather%fire_weather_index = rio_fireweather_index_si(io_idx_si)
sites(s)%snow_depth = rio_snow_depth_si(io_idx_si)
sites(s)%resources_management%trunk_product_site = rio_trunk_product_si(io_idx_si)
+ ! if needed, trigger the special procedure to initialize land use structure from a
+ ! restart run that did not include land use.
+ if (rio_landuse_config_si(io_idx_si) .eq. itrue .and. hlm_use_potentialveg .eq. ifalse) then
+ sites(s)%transition_landuse_from_off_to_on = .true.
+ else if ( rio_landuse_config_si(io_idx_si) .ne. hlm_use_potentialveg ) then
+ ! can't go back into potential vegetation mode, it is a one-way thing.
+ write(fates_log(),*) 'this combination of rio_landuse_config_si(io_idx_si) and hlm_use_potentialveg is not permitted'
+ write(fates_log(),*) 'rio_landuse_config_si(io_idx_si)', rio_landuse_config_si(io_idx_si)
+ write(fates_log(),*) 'hlm_use_potentialveg', hlm_use_potentialveg
+ call endrun(msg=errMsg(sourcefile, __LINE__))
+ endif
+
end do
if ( debug ) then
diff --git a/main/FatesRunningMeanMod.F90 b/main/FatesRunningMeanMod.F90
index 7ef7866d62..721030b974 100644
--- a/main/FatesRunningMeanMod.F90
+++ b/main/FatesRunningMeanMod.F90
@@ -328,6 +328,9 @@ subroutine FuseRMean(this,donor,recip_wgt)
if (this%c_index .ne. donor%c_index) then
write(fates_log(), *) 'trying to fuse two fixed-window averages'
write(fates_log(), *) 'that are at different points in the window?'
+ write(fates_log(), *) 'c_mean', this%c_mean, donor%c_mean
+ write(fates_log(), *) 'l_mean', this%l_mean, donor%l_mean
+ write(fates_log(), *) 'c_index', this%c_index, donor%c_index
call endrun(msg=errMsg(sourcefile, __LINE__))
end if
end if
diff --git a/parameter_files/archive/api36.0.0_051724_params_default.cdl b/parameter_files/archive/api36.0.0_051724_params_default.cdl
new file mode 100644
index 0000000000..2a909ee340
--- /dev/null
+++ b/parameter_files/archive/api36.0.0_051724_params_default.cdl
@@ -0,0 +1,1776 @@
+netcdf tmp {
+dimensions:
+ fates_NCWD = 4 ;
+ fates_history_age_bins = 7 ;
+ fates_history_coage_bins = 2 ;
+ fates_history_damage_bins = 2 ;
+ fates_history_height_bins = 6 ;
+ fates_history_size_bins = 13 ;
+ fates_hlm_pftno = 14 ;
+ fates_hydr_organs = 4 ;
+ fates_landuseclass = 5 ;
+ fates_leafage_class = 1 ;
+ fates_litterclass = 6 ;
+ fates_pft = 12 ;
+ fates_plant_organs = 4 ;
+ fates_string_length = 60 ;
+variables:
+ double fates_history_ageclass_bin_edges(fates_history_age_bins) ;
+ fates_history_ageclass_bin_edges:units = "yr" ;
+ fates_history_ageclass_bin_edges:long_name = "Lower edges for age class bins used in age-resolved patch history output" ;
+ double fates_history_coageclass_bin_edges(fates_history_coage_bins) ;
+ fates_history_coageclass_bin_edges:units = "years" ;
+ fates_history_coageclass_bin_edges:long_name = "Lower edges for cohort age class bins used in cohort age resolved history output" ;
+ double fates_history_height_bin_edges(fates_history_height_bins) ;
+ fates_history_height_bin_edges:units = "m" ;
+ fates_history_height_bin_edges:long_name = "Lower edges for height bins used in height-resolved history output" ;
+ double fates_history_damage_bin_edges(fates_history_damage_bins) ;
+ fates_history_damage_bin_edges:units = "% crown loss" ;
+ fates_history_damage_bin_edges:long_name = "Lower edges for damage class bins used in cohort history output" ;
+ double fates_history_sizeclass_bin_edges(fates_history_size_bins) ;
+ fates_history_sizeclass_bin_edges:units = "cm" ;
+ fates_history_sizeclass_bin_edges:long_name = "Lower edges for DBH size class bins used in size-resolved cohort history output" ;
+ double fates_alloc_organ_id(fates_plant_organs) ;
+ fates_alloc_organ_id:units = "unitless" ;
+ fates_alloc_organ_id:long_name = "This is the global index that the organ in this file is associated with, values match those in parteh/PRTGenericMod.F90" ;
+ double fates_hydro_htftype_node(fates_hydr_organs) ;
+ fates_hydro_htftype_node:units = "unitless" ;
+ fates_hydro_htftype_node:long_name = "Switch that defines the hydraulic transfer functions for each organ." ;
+ char fates_pftname(fates_pft, fates_string_length) ;
+ fates_pftname:units = "unitless - string" ;
+ fates_pftname:long_name = "Description of plant type" ;
+ char fates_hydro_organ_name(fates_hydr_organs, fates_string_length) ;
+ fates_hydro_organ_name:units = "unitless - string" ;
+ fates_hydro_organ_name:long_name = "Name of plant hydraulics organs (DONT CHANGE, order matches media list in FatesHydraulicsMemMod.F90)" ;
+ char fates_alloc_organ_name(fates_plant_organs, fates_string_length) ;
+ fates_alloc_organ_name:units = "unitless - string" ;
+ fates_alloc_organ_name:long_name = "Name of plant organs (with alloc_organ_id, must match PRTGenericMod.F90)" ;
+ char fates_landuseclass_name(fates_landuseclass, fates_string_length) ;
+ fates_landuseclass_name:units = "unitless - string" ;
+ fates_landuseclass_name:long_name = "Name of the land use classes, for variables associated with dimension fates_landuseclass" ;
+ char fates_litterclass_name(fates_litterclass, fates_string_length) ;
+ fates_litterclass_name:units = "unitless - string" ;
+ fates_litterclass_name:long_name = "Name of the litter classes, for variables associated with dimension fates_litterclass" ;
+ double fates_alloc_organ_priority(fates_plant_organs, fates_pft) ;
+ fates_alloc_organ_priority:units = "index" ;
+ fates_alloc_organ_priority:long_name = "Priority level for allocation, 1: replaces turnover from storage, 2: same priority as storage use/replacement, 3: ascending in order of least importance" ;
+ double fates_alloc_storage_cushion(fates_pft) ;
+ fates_alloc_storage_cushion:units = "fraction" ;
+ fates_alloc_storage_cushion:long_name = "maximum size of storage C pool, relative to maximum size of leaf C pool" ;
+ double fates_alloc_store_priority_frac(fates_pft) ;
+ fates_alloc_store_priority_frac:units = "unitless" ;
+ fates_alloc_store_priority_frac:long_name = "for high-priority organs, the fraction of their turnover demand that is gauranteed to be replaced, and if need-be by storage" ;
+ double fates_allom_agb1(fates_pft) ;
+ fates_allom_agb1:units = "variable" ;
+ fates_allom_agb1:long_name = "Parameter 1 for agb allometry" ;
+ double fates_allom_agb2(fates_pft) ;
+ fates_allom_agb2:units = "variable" ;
+ fates_allom_agb2:long_name = "Parameter 2 for agb allometry" ;
+ double fates_allom_agb3(fates_pft) ;
+ fates_allom_agb3:units = "variable" ;
+ fates_allom_agb3:long_name = "Parameter 3 for agb allometry" ;
+ double fates_allom_agb4(fates_pft) ;
+ fates_allom_agb4:units = "variable" ;
+ fates_allom_agb4:long_name = "Parameter 4 for agb allometry" ;
+ double fates_allom_agb_frac(fates_pft) ;
+ fates_allom_agb_frac:units = "fraction" ;
+ fates_allom_agb_frac:long_name = "Fraction of woody biomass that is above ground" ;
+ double fates_allom_amode(fates_pft) ;
+ fates_allom_amode:units = "index" ;
+ fates_allom_amode:long_name = "AGB allometry function index." ;
+ double fates_allom_blca_expnt_diff(fates_pft) ;
+ fates_allom_blca_expnt_diff:units = "unitless" ;
+ fates_allom_blca_expnt_diff:long_name = "difference between allometric DBH:bleaf and DBH:crown area exponents" ;
+ double fates_allom_cmode(fates_pft) ;
+ fates_allom_cmode:units = "index" ;
+ fates_allom_cmode:long_name = "coarse root biomass allometry function index." ;
+ double fates_allom_d2bl1(fates_pft) ;
+ fates_allom_d2bl1:units = "variable" ;
+ fates_allom_d2bl1:long_name = "Parameter 1 for d2bl allometry" ;
+ double fates_allom_d2bl2(fates_pft) ;
+ fates_allom_d2bl2:units = "variable" ;
+ fates_allom_d2bl2:long_name = "Parameter 2 for d2bl allometry" ;
+ double fates_allom_d2bl3(fates_pft) ;
+ fates_allom_d2bl3:units = "unitless" ;
+ fates_allom_d2bl3:long_name = "Parameter 3 for d2bl allometry" ;
+ double fates_allom_d2ca_coefficient_max(fates_pft) ;
+ fates_allom_d2ca_coefficient_max:units = "m2 cm^(-1/beta)" ;
+ fates_allom_d2ca_coefficient_max:long_name = "max (savanna) dbh to area multiplier factor where: area = n*d2ca_coeff*dbh^beta" ;
+ double fates_allom_d2ca_coefficient_min(fates_pft) ;
+ fates_allom_d2ca_coefficient_min:units = "m2 cm^(-1/beta)" ;
+ fates_allom_d2ca_coefficient_min:long_name = "min (forest) dbh to area multiplier factor where: area = n*d2ca_coeff*dbh^beta" ;
+ double fates_allom_d2h1(fates_pft) ;
+ fates_allom_d2h1:units = "variable" ;
+ fates_allom_d2h1:long_name = "Parameter 1 for d2h allometry (intercept, or c)" ;
+ double fates_allom_d2h2(fates_pft) ;
+ fates_allom_d2h2:units = "variable" ;
+ fates_allom_d2h2:long_name = "Parameter 2 for d2h allometry (slope, or m)" ;
+ double fates_allom_d2h3(fates_pft) ;
+ fates_allom_d2h3:units = "variable" ;
+ fates_allom_d2h3:long_name = "Parameter 3 for d2h allometry (optional)" ;
+ double fates_allom_dbh_maxheight(fates_pft) ;
+ fates_allom_dbh_maxheight:units = "cm" ;
+ fates_allom_dbh_maxheight:long_name = "the diameter (if any) corresponding to maximum height, diameters may increase beyond this" ;
+ double fates_allom_dmode(fates_pft) ;
+ fates_allom_dmode:units = "index" ;
+ fates_allom_dmode:long_name = "crown depth allometry function index" ;
+ double fates_allom_fmode(fates_pft) ;
+ fates_allom_fmode:units = "index" ;
+ fates_allom_fmode:long_name = "fine root biomass allometry function index." ;
+ double fates_allom_fnrt_prof_a(fates_pft) ;
+ fates_allom_fnrt_prof_a:units = "unitless" ;
+ fates_allom_fnrt_prof_a:long_name = "Fine root profile function, parameter a" ;
+ double fates_allom_fnrt_prof_b(fates_pft) ;
+ fates_allom_fnrt_prof_b:units = "unitless" ;
+ fates_allom_fnrt_prof_b:long_name = "Fine root profile function, parameter b" ;
+ double fates_allom_fnrt_prof_mode(fates_pft) ;
+ fates_allom_fnrt_prof_mode:units = "index" ;
+ fates_allom_fnrt_prof_mode:long_name = "Index to select fine root profile function: 1) Jackson Beta, 2) 1-param exponential 3) 2-param exponential" ;
+ double fates_allom_frbstor_repro(fates_pft) ;
+ fates_allom_frbstor_repro:units = "fraction" ;
+ fates_allom_frbstor_repro:long_name = "fraction of bstore goes to reproduction after plant dies" ;
+ double fates_allom_h2cd1(fates_pft) ;
+ fates_allom_h2cd1:units = "variable" ;
+ fates_allom_h2cd1:long_name = "Parameter 1 for h2cd allometry (exp(log-intercept) or scaling). If allom_dmode=1; this is the same as former crown_depth_frac parameter" ;
+ double fates_allom_h2cd2(fates_pft) ;
+ fates_allom_h2cd2:units = "variable" ;
+ fates_allom_h2cd2:long_name = "Parameter 2 for h2cd allometry (log-slope or exponent). If allom_dmode=1; this is not needed (as exponent is assumed 1)" ;
+ double fates_allom_hmode(fates_pft) ;
+ fates_allom_hmode:units = "index" ;
+ fates_allom_hmode:long_name = "height allometry function index." ;
+ double fates_allom_l2fr(fates_pft) ;
+ fates_allom_l2fr:units = "gC/gC" ;
+ fates_allom_l2fr:long_name = "Allocation parameter: fine root C per leaf C" ;
+ double fates_allom_la_per_sa_int(fates_pft) ;
+ fates_allom_la_per_sa_int:units = "m2/cm2" ;
+ fates_allom_la_per_sa_int:long_name = "Leaf area per sapwood area, intercept" ;
+ double fates_allom_la_per_sa_slp(fates_pft) ;
+ fates_allom_la_per_sa_slp:units = "m2/cm2/m" ;
+ fates_allom_la_per_sa_slp:long_name = "Leaf area per sapwood area rate of change with height, slope (optional)" ;
+ double fates_allom_lmode(fates_pft) ;
+ fates_allom_lmode:units = "index" ;
+ fates_allom_lmode:long_name = "leaf biomass allometry function index." ;
+ double fates_allom_sai_scaler(fates_pft) ;
+ fates_allom_sai_scaler:units = "m2/m2" ;
+ fates_allom_sai_scaler:long_name = "allometric ratio of SAI per LAI" ;
+ double fates_allom_smode(fates_pft) ;
+ fates_allom_smode:units = "index" ;
+ fates_allom_smode:long_name = "sapwood allometry function index." ;
+ double fates_allom_stmode(fates_pft) ;
+ fates_allom_stmode:units = "index" ;
+ fates_allom_stmode:long_name = "storage allometry function index: 1) Storage proportional to leaf biomass (with trimming), 2) Storage proportional to maximum leaf biomass (not trimmed)" ;
+ double fates_allom_zroot_k(fates_pft) ;
+ fates_allom_zroot_k:units = "unitless" ;
+ fates_allom_zroot_k:long_name = "scale coefficient of logistic rooting depth model" ;
+ double fates_allom_zroot_max_dbh(fates_pft) ;
+ fates_allom_zroot_max_dbh:units = "cm" ;
+ fates_allom_zroot_max_dbh:long_name = "dbh at which a plant reaches the maximum value for its maximum rooting depth" ;
+ double fates_allom_zroot_max_z(fates_pft) ;
+ fates_allom_zroot_max_z:units = "m" ;
+ fates_allom_zroot_max_z:long_name = "the maximum rooting depth defined at dbh = fates_allom_zroot_max_dbh. note: max_z=min_z=large, sets rooting depth to soil depth" ;
+ double fates_allom_zroot_min_dbh(fates_pft) ;
+ fates_allom_zroot_min_dbh:units = "cm" ;
+ fates_allom_zroot_min_dbh:long_name = "dbh at which the maximum rooting depth for a recruit is defined" ;
+ double fates_allom_zroot_min_z(fates_pft) ;
+ fates_allom_zroot_min_z:units = "m" ;
+ fates_allom_zroot_min_z:long_name = "the maximum rooting depth defined at dbh = fates_allom_zroot_min_dbh. note: max_z=min_z=large, sets rooting depth to soil depth" ;
+ double fates_c2b(fates_pft) ;
+ fates_c2b:units = "ratio" ;
+ fates_c2b:long_name = "Carbon to biomass multiplier of bulk structural tissues" ;
+ double fates_cnp_eca_alpha_ptase(fates_pft) ;
+ fates_cnp_eca_alpha_ptase:units = "g/m3" ;
+ fates_cnp_eca_alpha_ptase:long_name = "(INACTIVE, KEEP AT 0) fraction of P from ptase activity sent directly to plant (ECA)" ;
+ double fates_cnp_eca_decompmicc(fates_pft) ;
+ fates_cnp_eca_decompmicc:units = "gC/m3" ;
+ fates_cnp_eca_decompmicc:long_name = "maximum soil microbial decomposer biomass found over depth (will be applied at a reference depth w/ exponential attenuation) (ECA)" ;
+ double fates_cnp_eca_km_nh4(fates_pft) ;
+ fates_cnp_eca_km_nh4:units = "gN/m3" ;
+ fates_cnp_eca_km_nh4:long_name = "half-saturation constant for plant nh4 uptake (ECA)" ;
+ double fates_cnp_eca_km_no3(fates_pft) ;
+ fates_cnp_eca_km_no3:units = "gN/m3" ;
+ fates_cnp_eca_km_no3:long_name = "half-saturation constant for plant no3 uptake (ECA)" ;
+ double fates_cnp_eca_km_p(fates_pft) ;
+ fates_cnp_eca_km_p:units = "gP/m3" ;
+ fates_cnp_eca_km_p:long_name = "half-saturation constant for plant p uptake (ECA)" ;
+ double fates_cnp_eca_km_ptase(fates_pft) ;
+ fates_cnp_eca_km_ptase:units = "gP/m3" ;
+ fates_cnp_eca_km_ptase:long_name = "half-saturation constant for biochemical P (ECA)" ;
+ double fates_cnp_eca_lambda_ptase(fates_pft) ;
+ fates_cnp_eca_lambda_ptase:units = "g/m3" ;
+ fates_cnp_eca_lambda_ptase:long_name = "(INACTIVE, KEEP AT 0) critical value for biochemical production (ECA)" ;
+ double fates_cnp_eca_vmax_ptase(fates_pft) ;
+ fates_cnp_eca_vmax_ptase:units = "gP/m2/s" ;
+ fates_cnp_eca_vmax_ptase:long_name = "maximum production rate for biochemical P (per m2) (ECA)" ;
+ double fates_cnp_nfix1(fates_pft) ;
+ fates_cnp_nfix1:units = "fraction" ;
+ fates_cnp_nfix1:long_name = "fractional surcharge added to maintenance respiration that drives symbiotic fixation" ;
+ double fates_cnp_nitr_store_ratio(fates_pft) ;
+ fates_cnp_nitr_store_ratio:units = "(gN/gN)" ;
+ fates_cnp_nitr_store_ratio:long_name = "storeable (labile) N, as a ratio compared to the N bound in cell structures of other organs (see code)" ;
+ double fates_cnp_phos_store_ratio(fates_pft) ;
+ fates_cnp_phos_store_ratio:units = "(gP/gP)" ;
+ fates_cnp_phos_store_ratio:long_name = "storeable (labile) P, as a ratio compared to the P bound in cell structures of other organs (see code)" ;
+ double fates_cnp_pid_kd(fates_pft) ;
+ fates_cnp_pid_kd:units = "unknown" ;
+ fates_cnp_pid_kd:long_name = "derivative constant of the PID controller on adaptive fine-root biomass" ;
+ double fates_cnp_pid_ki(fates_pft) ;
+ fates_cnp_pid_ki:units = "unknown" ;
+ fates_cnp_pid_ki:long_name = "integral constant of the PID controller on adaptive fine-root biomass" ;
+ double fates_cnp_pid_kp(fates_pft) ;
+ fates_cnp_pid_kp:units = "unknown" ;
+ fates_cnp_pid_kp:long_name = "proportional constant of the PID controller on adaptive fine-root biomass" ;
+ double fates_cnp_prescribed_nuptake(fates_pft) ;
+ fates_cnp_prescribed_nuptake:units = "fraction" ;
+ fates_cnp_prescribed_nuptake:long_name = "Prescribed N uptake flux. 0=fully coupled simulation >0=prescribed (experimental)" ;
+ double fates_cnp_prescribed_puptake(fates_pft) ;
+ fates_cnp_prescribed_puptake:units = "fraction" ;
+ fates_cnp_prescribed_puptake:long_name = "Prescribed P uptake flux. 0=fully coupled simulation, >0=prescribed (experimental)" ;
+ double fates_cnp_store_ovrflw_frac(fates_pft) ;
+ fates_cnp_store_ovrflw_frac:units = "fraction" ;
+ fates_cnp_store_ovrflw_frac:long_name = "size of overflow storage (for excess C,N or P) as a fraction of storage target" ;
+ double fates_cnp_turnover_nitr_retrans(fates_plant_organs, fates_pft) ;
+ fates_cnp_turnover_nitr_retrans:units = "fraction" ;
+ fates_cnp_turnover_nitr_retrans:long_name = "retranslocation (reabsorbtion) fraction of nitrogen in turnover of scenescing tissues" ;
+ double fates_cnp_turnover_phos_retrans(fates_plant_organs, fates_pft) ;
+ fates_cnp_turnover_phos_retrans:units = "fraction" ;
+ fates_cnp_turnover_phos_retrans:long_name = "retranslocation (reabsorbtion) fraction of phosphorus in turnover of scenescing tissues" ;
+ double fates_cnp_vmax_nh4(fates_pft) ;
+ fates_cnp_vmax_nh4:units = "gN/gC/s" ;
+ fates_cnp_vmax_nh4:long_name = "maximum (potential) uptake rate of NH4 per gC of fineroot biomass (see main/EDPftvarcon.F90 vmax_nh4 for usage)" ;
+ double fates_cnp_vmax_no3(fates_pft) ;
+ fates_cnp_vmax_no3:units = "gN/gC/s" ;
+ fates_cnp_vmax_no3:long_name = "maximum (potential) uptake rate of NO3 per gC of fineroot biomass (see main/EDPftvarcon.F90 vmax_no3 for usage)" ;
+ double fates_cnp_vmax_p(fates_pft) ;
+ fates_cnp_vmax_p:units = "gP/gC/s" ;
+ fates_cnp_vmax_p:long_name = "maximum production rate for phosphorus (ECA and RD)" ;
+ double fates_damage_frac(fates_pft) ;
+ fates_damage_frac:units = "fraction" ;
+ fates_damage_frac:long_name = "fraction of cohort damaged in each damage event (event frequency specified in the is_it_damage_time subroutine)" ;
+ double fates_damage_mort_p1(fates_pft) ;
+ fates_damage_mort_p1:units = "fraction" ;
+ fates_damage_mort_p1:long_name = "inflection point of damage mortality function, a value of 0.8 means 50% mortality with 80% loss of crown, turn off with a large number" ;
+ double fates_damage_mort_p2(fates_pft) ;
+ fates_damage_mort_p2:units = "unitless" ;
+ fates_damage_mort_p2:long_name = "rate of mortality increase with damage" ;
+ double fates_damage_recovery_scalar(fates_pft) ;
+ fates_damage_recovery_scalar:units = "unitless" ;
+ fates_damage_recovery_scalar:long_name = "fraction of the cohort that recovers from damage" ;
+ double fates_dev_arbitrary_pft(fates_pft) ;
+ fates_dev_arbitrary_pft:units = "unknown" ;
+ fates_dev_arbitrary_pft:long_name = "Unassociated pft dimensioned free parameter that developers can use for testing arbitrary new hypotheses" ;
+ double fates_fire_alpha_SH(fates_pft) ;
+ fates_fire_alpha_SH:units = "m / (kw/m)**(2/3)" ;
+ fates_fire_alpha_SH:long_name = "spitfire parameter, alpha scorch height, Equation 16 Thonicke et al 2010" ;
+ double fates_fire_bark_scaler(fates_pft) ;
+ fates_fire_bark_scaler:units = "fraction" ;
+ fates_fire_bark_scaler:long_name = "the thickness of a cohorts bark as a fraction of its dbh" ;
+ double fates_fire_crown_kill(fates_pft) ;
+ fates_fire_crown_kill:units = "NA" ;
+ fates_fire_crown_kill:long_name = "fire parameter, see equation 22 in Thonicke et al 2010" ;
+ double fates_frag_fnrt_fcel(fates_pft) ;
+ fates_frag_fnrt_fcel:units = "fraction" ;
+ fates_frag_fnrt_fcel:long_name = "Fine root litter cellulose fraction" ;
+ double fates_frag_fnrt_flab(fates_pft) ;
+ fates_frag_fnrt_flab:units = "fraction" ;
+ fates_frag_fnrt_flab:long_name = "Fine root litter labile fraction" ;
+ double fates_frag_fnrt_flig(fates_pft) ;
+ fates_frag_fnrt_flig:units = "fraction" ;
+ fates_frag_fnrt_flig:long_name = "Fine root litter lignin fraction" ;
+ double fates_frag_leaf_fcel(fates_pft) ;
+ fates_frag_leaf_fcel:units = "fraction" ;
+ fates_frag_leaf_fcel:long_name = "Leaf litter cellulose fraction" ;
+ double fates_frag_leaf_flab(fates_pft) ;
+ fates_frag_leaf_flab:units = "fraction" ;
+ fates_frag_leaf_flab:long_name = "Leaf litter labile fraction" ;
+ double fates_frag_leaf_flig(fates_pft) ;
+ fates_frag_leaf_flig:units = "fraction" ;
+ fates_frag_leaf_flig:long_name = "Leaf litter lignin fraction" ;
+ double fates_frag_seed_decay_rate(fates_pft) ;
+ fates_frag_seed_decay_rate:units = "yr-1" ;
+ fates_frag_seed_decay_rate:long_name = "fraction of seeds that decay per year" ;
+ double fates_grperc(fates_pft) ;
+ fates_grperc:units = "unitless" ;
+ fates_grperc:long_name = "Growth respiration factor" ;
+ double fates_hydro_avuln_gs(fates_pft) ;
+ fates_hydro_avuln_gs:units = "unitless" ;
+ fates_hydro_avuln_gs:long_name = "shape parameter for stomatal control of water vapor exiting leaf" ;
+ double fates_hydro_avuln_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_avuln_node:units = "unitless" ;
+ fates_hydro_avuln_node:long_name = "xylem vulnerability curve shape parameter" ;
+ double fates_hydro_epsil_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_epsil_node:units = "MPa" ;
+ fates_hydro_epsil_node:long_name = "bulk elastic modulus" ;
+ double fates_hydro_fcap_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_fcap_node:units = "unitless" ;
+ fates_hydro_fcap_node:long_name = "fraction of non-residual water that is capillary in source" ;
+ double fates_hydro_k_lwp(fates_pft) ;
+ fates_hydro_k_lwp:units = "unitless" ;
+ fates_hydro_k_lwp:long_name = "inner leaf humidity scaling coefficient" ;
+ double fates_hydro_kmax_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_kmax_node:units = "kg/MPa/m/s" ;
+ fates_hydro_kmax_node:long_name = "maximum xylem conductivity per unit conducting xylem area" ;
+ double fates_hydro_p50_gs(fates_pft) ;
+ fates_hydro_p50_gs:units = "MPa" ;
+ fates_hydro_p50_gs:long_name = "water potential at 50% loss of stomatal conductance" ;
+ double fates_hydro_p50_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_p50_node:units = "MPa" ;
+ fates_hydro_p50_node:long_name = "xylem water potential at 50% loss of conductivity" ;
+ double fates_hydro_p_taper(fates_pft) ;
+ fates_hydro_p_taper:units = "unitless" ;
+ fates_hydro_p_taper:long_name = "xylem taper exponent" ;
+ double fates_hydro_pinot_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_pinot_node:units = "MPa" ;
+ fates_hydro_pinot_node:long_name = "osmotic potential at full turgor" ;
+ double fates_hydro_pitlp_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_pitlp_node:units = "MPa" ;
+ fates_hydro_pitlp_node:long_name = "turgor loss point" ;
+ double fates_hydro_resid_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_resid_node:units = "cm3/cm3" ;
+ fates_hydro_resid_node:long_name = "residual water conent" ;
+ double fates_hydro_rfrac_stem(fates_pft) ;
+ fates_hydro_rfrac_stem:units = "fraction" ;
+ fates_hydro_rfrac_stem:long_name = "fraction of total tree resistance from troot to canopy" ;
+ double fates_hydro_rs2(fates_pft) ;
+ fates_hydro_rs2:units = "m" ;
+ fates_hydro_rs2:long_name = "absorbing root radius" ;
+ double fates_hydro_srl(fates_pft) ;
+ fates_hydro_srl:units = "m g-1" ;
+ fates_hydro_srl:long_name = "specific root length" ;
+ double fates_hydro_thetas_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_thetas_node:units = "cm3/cm3" ;
+ fates_hydro_thetas_node:long_name = "saturated water content" ;
+ double fates_hydro_vg_alpha_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_vg_alpha_node:units = "MPa-1" ;
+ fates_hydro_vg_alpha_node:long_name = "(used if hydr_htftype_node = 2), capillary length parameter in van Genuchten model" ;
+ double fates_hydro_vg_m_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_vg_m_node:units = "unitless" ;
+ fates_hydro_vg_m_node:long_name = "(used if hydr_htftype_node = 2),m in van Genuchten 1980 model, 2nd pore size distribution parameter" ;
+ double fates_hydro_vg_n_node(fates_hydr_organs, fates_pft) ;
+ fates_hydro_vg_n_node:units = "unitless" ;
+ fates_hydro_vg_n_node:long_name = "(used if hydr_htftype_node = 2),n in van Genuchten 1980 model, pore size distribution parameter" ;
+ double fates_leaf_c3psn(fates_pft) ;
+ fates_leaf_c3psn:units = "flag" ;
+ fates_leaf_c3psn:long_name = "Photosynthetic pathway (1=c3, 0=c4)" ;
+ double fates_leaf_jmaxha(fates_pft) ;
+ fates_leaf_jmaxha:units = "J/mol" ;
+ fates_leaf_jmaxha:long_name = "activation energy for jmax. NOTE: if fates_leaf_photo_tempsens_model=2 then these values are NOT USED" ;
+ double fates_leaf_jmaxhd(fates_pft) ;
+ fates_leaf_jmaxhd:units = "J/mol" ;
+ fates_leaf_jmaxhd:long_name = "deactivation energy for jmax. NOTE: if fates_leaf_photo_tempsens_model=2 then these values are NOT USED" ;
+ double fates_leaf_jmaxse(fates_pft) ;
+ fates_leaf_jmaxse:units = "J/mol/K" ;
+ fates_leaf_jmaxse:long_name = "entropy term for jmax. NOTE: if fates_leaf_photo_tempsens_model=2 then these values are NOT USED" ;
+ double fates_leaf_slamax(fates_pft) ;
+ fates_leaf_slamax:units = "m^2/gC" ;
+ fates_leaf_slamax:long_name = "Maximum Specific Leaf Area (SLA), even if under a dense canopy" ;
+ double fates_leaf_slatop(fates_pft) ;
+ fates_leaf_slatop:units = "m^2/gC" ;
+ fates_leaf_slatop:long_name = "Specific Leaf Area (SLA) at top of canopy, projected area basis" ;
+ double fates_leaf_stomatal_intercept(fates_pft) ;
+ fates_leaf_stomatal_intercept:units = "umol H2O/m**2/s" ;
+ fates_leaf_stomatal_intercept:long_name = "Minimum unstressed stomatal conductance for Ball-Berry model and Medlyn model" ;
+ double fates_leaf_stomatal_slope_ballberry(fates_pft) ;
+ fates_leaf_stomatal_slope_ballberry:units = "unitless" ;
+ fates_leaf_stomatal_slope_ballberry:long_name = "stomatal slope parameter, as per Ball-Berry" ;
+ double fates_leaf_stomatal_slope_medlyn(fates_pft) ;
+ fates_leaf_stomatal_slope_medlyn:units = "KPa**0.5" ;
+ fates_leaf_stomatal_slope_medlyn:long_name = "stomatal slope parameter, as per Medlyn" ;
+ double fates_leaf_vcmax25top(fates_leafage_class, fates_pft) ;
+ fates_leaf_vcmax25top:units = "umol CO2/m^2/s" ;
+ fates_leaf_vcmax25top:long_name = "maximum carboxylation rate of Rub. at 25C, canopy top" ;
+ double fates_leaf_vcmaxha(fates_pft) ;
+ fates_leaf_vcmaxha:units = "J/mol" ;
+ fates_leaf_vcmaxha:long_name = "activation energy for vcmax. NOTE: if fates_leaf_photo_tempsens_model=2 then these values are NOT USED" ;
+ double fates_leaf_vcmaxhd(fates_pft) ;
+ fates_leaf_vcmaxhd:units = "J/mol" ;
+ fates_leaf_vcmaxhd:long_name = "deactivation energy for vcmax. NOTE: if fates_leaf_photo_tempsens_model=2 then these values are NOT USED" ;
+ double fates_leaf_vcmaxse(fates_pft) ;
+ fates_leaf_vcmaxse:units = "J/mol/K" ;
+ fates_leaf_vcmaxse:long_name = "entropy term for vcmax. NOTE: if fates_leaf_photo_tempsens_model=2 then these values are NOT USED" ;
+ double fates_leafn_vert_scaler_coeff1(fates_pft) ;
+ fates_leafn_vert_scaler_coeff1:units = "unitless" ;
+ fates_leafn_vert_scaler_coeff1:long_name = "Coefficient one for decrease in leaf nitrogen through the canopy, from Lloyd et al. 2010." ;
+ double fates_leafn_vert_scaler_coeff2(fates_pft) ;
+ fates_leafn_vert_scaler_coeff2:units = "unitless" ;
+ fates_leafn_vert_scaler_coeff2:long_name = "Coefficient two for decrease in leaf nitrogen through the canopy, from Lloyd et al. 2010." ;
+ double fates_maintresp_leaf_atkin2017_baserate(fates_pft) ;
+ fates_maintresp_leaf_atkin2017_baserate:units = "umol CO2/m^2/s" ;
+ fates_maintresp_leaf_atkin2017_baserate:long_name = "Leaf maintenance respiration base rate parameter (r0) per Atkin et al 2017" ;
+ double fates_maintresp_leaf_ryan1991_baserate(fates_pft) ;
+ fates_maintresp_leaf_ryan1991_baserate:units = "gC/gN/s" ;
+ fates_maintresp_leaf_ryan1991_baserate:long_name = "Leaf maintenance respiration base rate per Ryan et al 1991" ;
+ double fates_maintresp_leaf_vert_scaler_coeff1(fates_pft) ;
+ fates_maintresp_leaf_vert_scaler_coeff1:units = "unitless" ;
+ fates_maintresp_leaf_vert_scaler_coeff1:long_name = "Leaf maintenance respiration decrease through the canopy. Only applies to Atkin et al. 2017. For proportionality between photosynthesis and respiration through the canopy, match with fates_leafn_vert_scaler_coeff1." ;
+ double fates_maintresp_leaf_vert_scaler_coeff2(fates_pft) ;
+ fates_maintresp_leaf_vert_scaler_coeff2:units = "unitless" ;
+ fates_maintresp_leaf_vert_scaler_coeff2:long_name = "Leaf maintenance respiration decrease through the canopy. Only applies to Atkin et al. 2017. For proportionality between photosynthesis and respiration through the canopy, match with fates_leafn_vert_scaler_coeff2." ;
+ double fates_maintresp_reduction_curvature(fates_pft) ;
+ fates_maintresp_reduction_curvature:units = "unitless (0-1)" ;
+ fates_maintresp_reduction_curvature:long_name = "curvature of MR reduction as f(carbon storage), 1=linear, 0=very curved" ;
+ double fates_maintresp_reduction_intercept(fates_pft) ;
+ fates_maintresp_reduction_intercept:units = "unitless (0-1)" ;
+ fates_maintresp_reduction_intercept:long_name = "intercept of MR reduction as f(carbon storage), 0=no throttling, 1=max throttling" ;
+ double fates_maintresp_reduction_upthresh(fates_pft) ;
+ fates_maintresp_reduction_upthresh:units = "unitless (0-1)" ;
+ fates_maintresp_reduction_upthresh:long_name = "upper threshold for storage biomass (relative to leaf biomass) above which MR is not reduced" ;
+ double fates_mort_bmort(fates_pft) ;
+ fates_mort_bmort:units = "1/yr" ;
+ fates_mort_bmort:long_name = "background mortality rate" ;
+ double fates_mort_freezetol(fates_pft) ;
+ fates_mort_freezetol:units = "degrees C" ;
+ fates_mort_freezetol:long_name = "minimum temperature tolerance" ;
+ double fates_mort_hf_flc_threshold(fates_pft) ;
+ fates_mort_hf_flc_threshold:units = "fraction" ;
+ fates_mort_hf_flc_threshold:long_name = "plant fractional loss of conductivity at which drought mortality begins for hydraulic model" ;
+ double fates_mort_hf_sm_threshold(fates_pft) ;
+ fates_mort_hf_sm_threshold:units = "unitless" ;
+ fates_mort_hf_sm_threshold:long_name = "soil moisture (btran units) at which drought mortality begins for non-hydraulic model" ;
+ double fates_mort_ip_age_senescence(fates_pft) ;
+ fates_mort_ip_age_senescence:units = "years" ;
+ fates_mort_ip_age_senescence:long_name = "Mortality cohort age senescence inflection point. If _ this mortality term is off. Setting this value turns on age dependent mortality. " ;
+ double fates_mort_ip_size_senescence(fates_pft) ;
+ fates_mort_ip_size_senescence:units = "dbh cm" ;
+ fates_mort_ip_size_senescence:long_name = "Mortality dbh senescence inflection point. If _ this mortality term is off. Setting this value turns on size dependent mortality" ;
+ double fates_mort_prescribed_canopy(fates_pft) ;
+ fates_mort_prescribed_canopy:units = "1/yr" ;
+ fates_mort_prescribed_canopy:long_name = "mortality rate of canopy trees for prescribed physiology mode" ;
+ double fates_mort_prescribed_understory(fates_pft) ;
+ fates_mort_prescribed_understory:units = "1/yr" ;
+ fates_mort_prescribed_understory:long_name = "mortality rate of understory trees for prescribed physiology mode" ;
+ double fates_mort_r_age_senescence(fates_pft) ;
+ fates_mort_r_age_senescence:units = "mortality rate year^-1" ;
+ fates_mort_r_age_senescence:long_name = "Mortality age senescence rate of change. Sensible range is around 0.03-0.06. Larger values givesteeper mortality curves." ;
+ double fates_mort_r_size_senescence(fates_pft) ;
+ fates_mort_r_size_senescence:units = "mortality rate dbh^-1" ;
+ fates_mort_r_size_senescence:long_name = "Mortality dbh senescence rate of change. Sensible range is around 0.03-0.06. Larger values give steeper mortality curves." ;
+ double fates_mort_scalar_coldstress(fates_pft) ;
+ fates_mort_scalar_coldstress:units = "1/yr" ;
+ fates_mort_scalar_coldstress:long_name = "maximum mortality rate from cold stress" ;
+ double fates_mort_scalar_cstarvation(fates_pft) ;
+ fates_mort_scalar_cstarvation:units = "1/yr" ;
+ fates_mort_scalar_cstarvation:long_name = "maximum mortality rate from carbon starvation" ;
+ double fates_mort_scalar_hydrfailure(fates_pft) ;
+ fates_mort_scalar_hydrfailure:units = "1/yr" ;
+ fates_mort_scalar_hydrfailure:long_name = "maximum mortality rate from hydraulic failure" ;
+ double fates_mort_upthresh_cstarvation(fates_pft) ;
+ fates_mort_upthresh_cstarvation:units = "unitless" ;
+ fates_mort_upthresh_cstarvation:long_name = "threshold for storage biomass (relative to target leaf biomass) above which carbon starvation is zero" ;
+ double fates_nonhydro_smpsc(fates_pft) ;
+ fates_nonhydro_smpsc:units = "mm" ;
+ fates_nonhydro_smpsc:long_name = "Soil water potential at full stomatal closure" ;
+ double fates_nonhydro_smpso(fates_pft) ;
+ fates_nonhydro_smpso:units = "mm" ;
+ fates_nonhydro_smpso:long_name = "Soil water potential at full stomatal opening" ;
+ double fates_phen_cold_size_threshold(fates_pft) ;
+ fates_phen_cold_size_threshold:units = "cm" ;
+ fates_phen_cold_size_threshold:long_name = "the dbh size above which will lead to phenology-related stem and leaf drop" ;
+ double fates_phen_drought_threshold(fates_pft) ;
+ fates_phen_drought_threshold:units = "m3/m3 or mm" ;
+ fates_phen_drought_threshold:long_name = "threshold for drought phenology (or lower threshold for semi-deciduous PFTs); the quantity depends on the sign: if positive, the threshold is volumetric soil moisture (m3/m3). If negative, the threshold is soil matric potentical (mm)" ;
+ double fates_phen_evergreen(fates_pft) ;
+ fates_phen_evergreen:units = "logical flag" ;
+ fates_phen_evergreen:long_name = "Binary flag for evergreen leaf habit" ;
+ double fates_phen_flush_fraction(fates_pft) ;
+ fates_phen_flush_fraction:units = "fraction" ;
+ fates_phen_flush_fraction:long_name = "Upon bud-burst, the maximum fraction of storage carbon used for flushing leaves" ;
+ double fates_phen_fnrt_drop_fraction(fates_pft) ;
+ fates_phen_fnrt_drop_fraction:units = "fraction" ;
+ fates_phen_fnrt_drop_fraction:long_name = "fraction of fine roots to drop during drought/cold" ;
+ double fates_phen_mindaysoff(fates_pft) ;
+ fates_phen_mindaysoff:units = "days" ;
+ fates_phen_mindaysoff:long_name = "day threshold compared against days since leaves abscised (shed)" ;
+ double fates_phen_moist_threshold(fates_pft) ;
+ fates_phen_moist_threshold:units = "m3/m3 or mm" ;
+ fates_phen_moist_threshold:long_name = "upper threshold for drought phenology (only for drought semi-deciduous PFTs); the quantity depends on the sign: if positive, the threshold is volumetric soil moisture (m3/m3). If negative, the threshold is soil matric potentical (mm)" ;
+ double fates_phen_season_decid(fates_pft) ;
+ fates_phen_season_decid:units = "logical flag" ;
+ fates_phen_season_decid:long_name = "Binary flag for seasonal-deciduous leaf habit" ;
+ double fates_phen_stem_drop_fraction(fates_pft) ;
+ fates_phen_stem_drop_fraction:units = "fraction" ;
+ fates_phen_stem_drop_fraction:long_name = "fraction of stems to drop for non-woody species during drought/cold" ;
+ double fates_phen_stress_decid(fates_pft) ;
+ fates_phen_stress_decid:units = "logical flag" ;
+ fates_phen_stress_decid:long_name = "Flag for stress/drought-deciduous leaf habit. 0 - not stress deciduous; 1 - default drought deciduous (two target states only, fully flushed or fully abscised); 2 - semi-deciduous" ;
+ double fates_prescribed_npp_canopy(fates_pft) ;
+ fates_prescribed_npp_canopy:units = "kgC / m^2 / yr" ;
+ fates_prescribed_npp_canopy:long_name = "NPP per unit crown area of canopy trees for prescribed physiology mode" ;
+ double fates_prescribed_npp_understory(fates_pft) ;
+ fates_prescribed_npp_understory:units = "kgC / m^2 / yr" ;
+ fates_prescribed_npp_understory:long_name = "NPP per unit crown area of understory trees for prescribed physiology mode" ;
+ double fates_rad_leaf_clumping_index(fates_pft) ;
+ fates_rad_leaf_clumping_index:units = "fraction (0-1)" ;
+ fates_rad_leaf_clumping_index:long_name = "factor describing how much self-occlusion of leaf scattering elements decreases light interception" ;
+ double fates_rad_leaf_rhonir(fates_pft) ;
+ fates_rad_leaf_rhonir:units = "fraction" ;
+ fates_rad_leaf_rhonir:long_name = "Leaf reflectance: near-IR" ;
+ double fates_rad_leaf_rhovis(fates_pft) ;
+ fates_rad_leaf_rhovis:units = "fraction" ;
+ fates_rad_leaf_rhovis:long_name = "Leaf reflectance: visible" ;
+ double fates_rad_leaf_taunir(fates_pft) ;
+ fates_rad_leaf_taunir:units = "fraction" ;
+ fates_rad_leaf_taunir:long_name = "Leaf transmittance: near-IR" ;
+ double fates_rad_leaf_tauvis(fates_pft) ;
+ fates_rad_leaf_tauvis:units = "fraction" ;
+ fates_rad_leaf_tauvis:long_name = "Leaf transmittance: visible" ;
+ double fates_rad_leaf_xl(fates_pft) ;
+ fates_rad_leaf_xl:units = "unitless" ;
+ fates_rad_leaf_xl:long_name = "Leaf/stem orientation index" ;
+ double fates_rad_stem_rhonir(fates_pft) ;
+ fates_rad_stem_rhonir:units = "fraction" ;
+ fates_rad_stem_rhonir:long_name = "Stem reflectance: near-IR" ;
+ double fates_rad_stem_rhovis(fates_pft) ;
+ fates_rad_stem_rhovis:units = "fraction" ;
+ fates_rad_stem_rhovis:long_name = "Stem reflectance: visible" ;
+ double fates_rad_stem_taunir(fates_pft) ;
+ fates_rad_stem_taunir:units = "fraction" ;
+ fates_rad_stem_taunir:long_name = "Stem transmittance: near-IR" ;
+ double fates_rad_stem_tauvis(fates_pft) ;
+ fates_rad_stem_tauvis:units = "fraction" ;
+ fates_rad_stem_tauvis:long_name = "Stem transmittance: visible" ;
+ double fates_recruit_height_min(fates_pft) ;
+ fates_recruit_height_min:units = "m" ;
+ fates_recruit_height_min:long_name = "the minimum height (ie starting height) of a newly recruited plant" ;
+ double fates_recruit_init_density(fates_pft) ;
+ fates_recruit_init_density:units = "stems/m2" ;
+ fates_recruit_init_density:long_name = "initial seedling density for a cold-start near-bare-ground simulation. If negative sets initial tree dbh - only to be used in nocomp mode" ;
+ double fates_recruit_prescribed_rate(fates_pft) ;
+ fates_recruit_prescribed_rate:units = "n/yr" ;
+ fates_recruit_prescribed_rate:long_name = "recruitment rate for prescribed physiology mode" ;
+ double fates_recruit_seed_alloc(fates_pft) ;
+ fates_recruit_seed_alloc:units = "fraction" ;
+ fates_recruit_seed_alloc:long_name = "fraction of available carbon balance allocated to seeds" ;
+ double fates_recruit_seed_alloc_mature(fates_pft) ;
+ fates_recruit_seed_alloc_mature:units = "fraction" ;
+ fates_recruit_seed_alloc_mature:long_name = "fraction of available carbon balance allocated to seeds in mature plants (adds to fates_seed_alloc)" ;
+ double fates_recruit_seed_dbh_repro_threshold(fates_pft) ;
+ fates_recruit_seed_dbh_repro_threshold:units = "cm" ;
+ fates_recruit_seed_dbh_repro_threshold:long_name = "the diameter where the plant will increase allocation to the seed pool by fraction: fates_recruit_seed_alloc_mature" ;
+ double fates_recruit_seed_germination_rate(fates_pft) ;
+ fates_recruit_seed_germination_rate:units = "yr-1" ;
+ fates_recruit_seed_germination_rate:long_name = "fraction of seeds that germinate per year" ;
+ double fates_recruit_seed_supplement(fates_pft) ;
+ fates_recruit_seed_supplement:units = "KgC/m2/yr" ;
+ fates_recruit_seed_supplement:long_name = "Supplemental external seed rain source term (non-mass conserving)" ;
+ double fates_seed_dispersal_fraction(fates_pft) ;
+ fates_seed_dispersal_fraction:units = "fraction" ;
+ fates_seed_dispersal_fraction:long_name = "fraction of seed rain to be dispersed to other grid cells" ;
+ double fates_seed_dispersal_max_dist(fates_pft) ;
+ fates_seed_dispersal_max_dist:units = "m" ;
+ fates_seed_dispersal_max_dist:long_name = "maximum seed dispersal distance for a given pft" ;
+ double fates_seed_dispersal_pdf_scale(fates_pft) ;
+ fates_seed_dispersal_pdf_scale:units = "unitless" ;
+ fates_seed_dispersal_pdf_scale:long_name = "seed dispersal probability density function scale parameter, A, Table 1 Bullock et al 2016" ;
+ double fates_seed_dispersal_pdf_shape(fates_pft) ;
+ fates_seed_dispersal_pdf_shape:units = "unitless" ;
+ fates_seed_dispersal_pdf_shape:long_name = "seed dispersal probability density function shape parameter, B, Table 1 Bullock et al 2016" ;
+ double fates_stoich_nitr(fates_plant_organs, fates_pft) ;
+ fates_stoich_nitr:units = "gN/gC" ;
+ fates_stoich_nitr:long_name = "target nitrogen concentration (ratio with carbon) of organs" ;
+ double fates_stoich_phos(fates_plant_organs, fates_pft) ;
+ fates_stoich_phos:units = "gP/gC" ;
+ fates_stoich_phos:long_name = "target phosphorus concentration (ratio with carbon) of organs" ;
+ double fates_trim_inc(fates_pft) ;
+ fates_trim_inc:units = "m2/m2" ;
+ fates_trim_inc:long_name = "Arbitrary incremental change in trimming function." ;
+ double fates_trim_limit(fates_pft) ;
+ fates_trim_limit:units = "m2/m2" ;
+ fates_trim_limit:long_name = "Arbitrary limit to reductions in leaf area with stress" ;
+ double fates_trs_repro_alloc_a(fates_pft) ;
+ fates_trs_repro_alloc_a:units = "fraction" ;
+ fates_trs_repro_alloc_a:long_name = "shape parameter for sigmoidal function relating dbh to reproductive allocation" ;
+ double fates_trs_repro_alloc_b(fates_pft) ;
+ fates_trs_repro_alloc_b:units = "fraction" ;
+ fates_trs_repro_alloc_b:long_name = "intercept parameter for sigmoidal function relating dbh to reproductive allocation" ;
+ double fates_trs_repro_frac_seed(fates_pft) ;
+ fates_trs_repro_frac_seed:units = "fraction" ;
+ fates_trs_repro_frac_seed:long_name = "fraction of reproductive mass that is seed" ;
+ double fates_trs_seedling_a_emerg(fates_pft) ;
+ fates_trs_seedling_a_emerg:units = "day -1" ;
+ fates_trs_seedling_a_emerg:long_name = "mean fraction of seed bank emerging" ;
+ double fates_trs_seedling_b_emerg(fates_pft) ;
+ fates_trs_seedling_b_emerg:units = "day -1" ;
+ fates_trs_seedling_b_emerg:long_name = "seedling emergence sensitivity to soil moisture" ;
+ double fates_trs_seedling_background_mort(fates_pft) ;
+ fates_trs_seedling_background_mort:units = "yr-1" ;
+ fates_trs_seedling_background_mort:long_name = "background seedling mortality rate" ;
+ double fates_trs_seedling_h2o_mort_a(fates_pft) ;
+ fates_trs_seedling_h2o_mort_a:units = "-" ;
+ fates_trs_seedling_h2o_mort_a:long_name = "coefficient in moisture-based seedling mortality" ;
+ double fates_trs_seedling_h2o_mort_b(fates_pft) ;
+ fates_trs_seedling_h2o_mort_b:units = "-" ;
+ fates_trs_seedling_h2o_mort_b:long_name = "coefficient in moisture-based seedling mortality" ;
+ double fates_trs_seedling_h2o_mort_c(fates_pft) ;
+ fates_trs_seedling_h2o_mort_c:units = "-" ;
+ fates_trs_seedling_h2o_mort_c:long_name = "coefficient in moisture-based seedling mortality" ;
+ double fates_trs_seedling_light_mort_a(fates_pft) ;
+ fates_trs_seedling_light_mort_a:units = "-" ;
+ fates_trs_seedling_light_mort_a:long_name = "light-based seedling mortality coefficient" ;
+ double fates_trs_seedling_light_mort_b(fates_pft) ;
+ fates_trs_seedling_light_mort_b:units = "-" ;
+ fates_trs_seedling_light_mort_b:long_name = "light-based seedling mortality coefficient" ;
+ double fates_trs_seedling_light_rec_a(fates_pft) ;
+ fates_trs_seedling_light_rec_a:units = "-" ;
+ fates_trs_seedling_light_rec_a:long_name = "coefficient in light-based seedling to sapling transition" ;
+ double fates_trs_seedling_light_rec_b(fates_pft) ;
+ fates_trs_seedling_light_rec_b:units = "-" ;
+ fates_trs_seedling_light_rec_b:long_name = "coefficient in light-based seedling to sapling transition" ;
+ double fates_trs_seedling_mdd_crit(fates_pft) ;
+ fates_trs_seedling_mdd_crit:units = "mm H2O day" ;
+ fates_trs_seedling_mdd_crit:long_name = "critical moisture deficit (suction) day accumulation for seedling moisture-based seedling mortality to begin" ;
+ double fates_trs_seedling_par_crit_germ(fates_pft) ;
+ fates_trs_seedling_par_crit_germ:units = "MJ m-2 day-1" ;
+ fates_trs_seedling_par_crit_germ:long_name = "critical light level for germination" ;
+ double fates_trs_seedling_psi_crit(fates_pft) ;
+ fates_trs_seedling_psi_crit:units = "mm H2O" ;
+ fates_trs_seedling_psi_crit:long_name = "critical soil moisture (suction) for seedling stress" ;
+ double fates_trs_seedling_psi_emerg(fates_pft) ;
+ fates_trs_seedling_psi_emerg:units = "mm h20 suction" ;
+ fates_trs_seedling_psi_emerg:long_name = "critical soil moisture for seedling emergence" ;
+ double fates_trs_seedling_root_depth(fates_pft) ;
+ fates_trs_seedling_root_depth:units = "m" ;
+ fates_trs_seedling_root_depth:long_name = "rooting depth of seedlings" ;
+ double fates_turb_displar(fates_pft) ;
+ fates_turb_displar:units = "unitless" ;
+ fates_turb_displar:long_name = "Ratio of displacement height to canopy top height" ;
+ double fates_turb_leaf_diameter(fates_pft) ;
+ fates_turb_leaf_diameter:units = "m" ;
+ fates_turb_leaf_diameter:long_name = "Characteristic leaf dimension" ;
+ double fates_turb_z0mr(fates_pft) ;
+ fates_turb_z0mr:units = "unitless" ;
+ fates_turb_z0mr:long_name = "Ratio of momentum roughness length to canopy top height" ;
+ double fates_turnover_branch(fates_pft) ;
+ fates_turnover_branch:units = "yr" ;
+ fates_turnover_branch:long_name = "turnover time of branches" ;
+ double fates_turnover_fnrt(fates_pft) ;
+ fates_turnover_fnrt:units = "yr" ;
+ fates_turnover_fnrt:long_name = "root longevity (alternatively, turnover time)" ;
+ double fates_turnover_leaf(fates_leafage_class, fates_pft) ;
+ fates_turnover_leaf:units = "yr" ;
+ fates_turnover_leaf:long_name = "Leaf longevity (ie turnover timescale). For drought-deciduous PFTs, this also indicates the maximum length of the growing (i.e., leaves on) season." ;
+ double fates_turnover_senleaf_fdrought(fates_pft) ;
+ fates_turnover_senleaf_fdrought:units = "unitless[0-1]" ;
+ fates_turnover_senleaf_fdrought:long_name = "multiplication factor for leaf longevity of senescent leaves during drought" ;
+ double fates_wood_density(fates_pft) ;
+ fates_wood_density:units = "g/cm3" ;
+ fates_wood_density:long_name = "mean density of woody tissue in plant" ;
+ double fates_woody(fates_pft) ;
+ fates_woody:units = "logical flag" ;
+ fates_woody:long_name = "Binary woody lifeform flag" ;
+ double fates_hlm_pft_map(fates_hlm_pftno, fates_pft) ;
+ fates_hlm_pft_map:units = "area fraction" ;
+ fates_hlm_pft_map:long_name = "In fixed biogeog mode, fraction of HLM area associated with each FATES PFT" ;
+ double fates_fire_FBD(fates_litterclass) ;
+ fates_fire_FBD:units = "kg Biomass/m3" ;
+ fates_fire_FBD:long_name = "fuel bulk density" ;
+ double fates_fire_low_moisture_Coeff(fates_litterclass) ;
+ fates_fire_low_moisture_Coeff:units = "NA" ;
+ fates_fire_low_moisture_Coeff:long_name = "spitfire parameter, equation B1 Thonicke et al 2010" ;
+ double fates_fire_low_moisture_Slope(fates_litterclass) ;
+ fates_fire_low_moisture_Slope:units = "NA" ;
+ fates_fire_low_moisture_Slope:long_name = "spitfire parameter, equation B1 Thonicke et al 2010" ;
+ double fates_fire_mid_moisture(fates_litterclass) ;
+ fates_fire_mid_moisture:units = "NA" ;
+ fates_fire_mid_moisture:long_name = "spitfire litter moisture threshold to be considered medium dry" ;
+ double fates_fire_mid_moisture_Coeff(fates_litterclass) ;
+ fates_fire_mid_moisture_Coeff:units = "NA" ;
+ fates_fire_mid_moisture_Coeff:long_name = "spitfire parameter, equation B1 Thonicke et al 2010" ;
+ double fates_fire_mid_moisture_Slope(fates_litterclass) ;
+ fates_fire_mid_moisture_Slope:units = "NA" ;
+ fates_fire_mid_moisture_Slope:long_name = "spitfire parameter, equation B1 Thonicke et al 2010" ;
+ double fates_fire_min_moisture(fates_litterclass) ;
+ fates_fire_min_moisture:units = "NA" ;
+ fates_fire_min_moisture:long_name = "spitfire litter moisture threshold to be considered very dry" ;
+ double fates_fire_SAV(fates_litterclass) ;
+ fates_fire_SAV:units = "cm-1" ;
+ fates_fire_SAV:long_name = "fuel surface area to volume ratio" ;
+ double fates_frag_maxdecomp(fates_litterclass) ;
+ fates_frag_maxdecomp:units = "yr-1" ;
+ fates_frag_maxdecomp:long_name = "maximum rate of litter & CWD transfer from non-decomposing class into decomposing class" ;
+ double fates_frag_cwd_frac(fates_NCWD) ;
+ fates_frag_cwd_frac:units = "fraction" ;
+ fates_frag_cwd_frac:long_name = "fraction of woody (bdead+bsw) biomass destined for CWD pool" ;
+ double fates_maxpatches_by_landuse(fates_landuseclass) ;
+ fates_maxpatches_by_landuse:units = "count" ;
+ fates_maxpatches_by_landuse:long_name = "maximum number of patches per site on each land use type" ;
+ double fates_canopy_closure_thresh ;
+ fates_canopy_closure_thresh:units = "unitless" ;
+ fates_canopy_closure_thresh:long_name = "tree canopy coverage at which crown area allometry changes from savanna to forest value" ;
+ double fates_cnp_eca_plant_escalar ;
+ fates_cnp_eca_plant_escalar:units = "" ;
+ fates_cnp_eca_plant_escalar:long_name = "scaling factor for plant fine root biomass to calculate nutrient carrier enzyme abundance (ECA)" ;
+ double fates_cohort_age_fusion_tol ;
+ fates_cohort_age_fusion_tol:units = "unitless" ;
+ fates_cohort_age_fusion_tol:long_name = "minimum fraction in differece in cohort age between cohorts." ;
+ double fates_cohort_size_fusion_tol ;
+ fates_cohort_size_fusion_tol:units = "unitless" ;
+ fates_cohort_size_fusion_tol:long_name = "minimum fraction in difference in dbh between cohorts" ;
+ double fates_comp_excln ;
+ fates_comp_excln:units = "none" ;
+ fates_comp_excln:long_name = "IF POSITIVE: weighting factor (exponent on dbh) for canopy layer exclusion and promotion, IF NEGATIVE: switch to use deterministic height sorting" ;
+ double fates_damage_canopy_layer_code ;
+ fates_damage_canopy_layer_code:units = "unitless" ;
+ fates_damage_canopy_layer_code:long_name = "Integer code that decides whether damage affects canopy trees (1), understory trees (2)" ;
+ double fates_damage_event_code ;
+ fates_damage_event_code:units = "unitless" ;
+ fates_damage_event_code:long_name = "Integer code that options how damage events are structured" ;
+ double fates_daylength_factor_switch ;
+ fates_daylength_factor_switch:units = "unitless" ;
+ fates_daylength_factor_switch:long_name = "user switch for turning on (1) or off (0) the day length factor scaling for photosynthetic parameters (ie scale vcmax and jmax)" ;
+ double fates_dev_arbitrary ;
+ fates_dev_arbitrary:units = "unknown" ;
+ fates_dev_arbitrary:long_name = "Unassociated free parameter that developers can use for testing arbitrary new hypotheses" ;
+ double fates_fire_active_crown_fire ;
+ fates_fire_active_crown_fire:units = "0 or 1" ;
+ fates_fire_active_crown_fire:long_name = "flag, 1=active crown fire 0=no active crown fire" ;
+ double fates_fire_cg_strikes ;
+ fates_fire_cg_strikes:units = "fraction (0-1)" ;
+ fates_fire_cg_strikes:long_name = "fraction of cloud to ground lightning strikes" ;
+ double fates_fire_drying_ratio ;
+ fates_fire_drying_ratio:units = "NA" ;
+ fates_fire_drying_ratio:long_name = "spitfire parameter, fire drying ratio for fuel moisture, alpha_FMC EQ 6 Thonicke et al 2010" ;
+ double fates_fire_durat_slope ;
+ fates_fire_durat_slope:units = "NA" ;
+ fates_fire_durat_slope:long_name = "spitfire parameter, fire max duration slope, Equation 14 Thonicke et al 2010" ;
+ double fates_fire_fdi_alpha ;
+ fates_fire_fdi_alpha:units = "NA" ;
+ fates_fire_fdi_alpha:long_name = "spitfire parameter, EQ 7 Venevsky et al. GCB 2002,(modified EQ 8 Thonicke et al. 2010) " ;
+ double fates_fire_fuel_energy ;
+ fates_fire_fuel_energy:units = "kJ/kg" ;
+ fates_fire_fuel_energy:long_name = "spitfire parameter, heat content of fuel" ;
+ double fates_fire_max_durat ;
+ fates_fire_max_durat:units = "minutes" ;
+ fates_fire_max_durat:long_name = "spitfire parameter, fire maximum duration, Equation 14 Thonicke et al 2010" ;
+ double fates_fire_miner_damp ;
+ fates_fire_miner_damp:units = "NA" ;
+ fates_fire_miner_damp:long_name = "spitfire parameter, mineral-dampening coefficient EQ A1 Thonicke et al 2010 " ;
+ double fates_fire_miner_total ;
+ fates_fire_miner_total:units = "fraction" ;
+ fates_fire_miner_total:long_name = "spitfire parameter, total mineral content, Table A1 Thonicke et al 2010" ;
+ double fates_fire_nignitions ;
+ fates_fire_nignitions:units = "ignitions per year per km2" ;
+ fates_fire_nignitions:long_name = "number of annual ignitions per square km" ;
+ double fates_fire_part_dens ;
+ fates_fire_part_dens:units = "kg/m2" ;
+ fates_fire_part_dens:long_name = "spitfire parameter, oven dry particle density, Table A1 Thonicke et al 2010" ;
+ double fates_fire_threshold ;
+ fates_fire_threshold:units = "kW/m" ;
+ fates_fire_threshold:long_name = "spitfire parameter, fire intensity threshold for tracking fires that spread" ;
+ double fates_frag_cwd_fcel ;
+ fates_frag_cwd_fcel:units = "unitless" ;
+ fates_frag_cwd_fcel:long_name = "Cellulose fraction for CWD" ;
+ double fates_frag_cwd_flig ;
+ fates_frag_cwd_flig:units = "unitless" ;
+ fates_frag_cwd_flig:long_name = "Lignin fraction of coarse woody debris" ;
+ double fates_hydro_kmax_rsurf1 ;
+ fates_hydro_kmax_rsurf1:units = "kg water/m2 root area/Mpa/s" ;
+ fates_hydro_kmax_rsurf1:long_name = "maximum conducitivity for unit root surface (into root)" ;
+ double fates_hydro_kmax_rsurf2 ;
+ fates_hydro_kmax_rsurf2:units = "kg water/m2 root area/Mpa/s" ;
+ fates_hydro_kmax_rsurf2:long_name = "maximum conducitivity for unit root surface (out of root)" ;
+ double fates_hydro_psi0 ;
+ fates_hydro_psi0:units = "MPa" ;
+ fates_hydro_psi0:long_name = "sapwood water potential at saturation" ;
+ double fates_hydro_psicap ;
+ fates_hydro_psicap:units = "MPa" ;
+ fates_hydro_psicap:long_name = "sapwood water potential at which capillary reserves exhausted" ;
+ double fates_hydro_solver ;
+ fates_hydro_solver:units = "unitless" ;
+ fates_hydro_solver:long_name = "switch designating which numerical solver for plant hydraulics, 1 = 1D taylor, 2 = 2D Picard, 3 = 2D Newton (deprecated)" ;
+ double fates_landuse_logging_coll_under_frac ;
+ fates_landuse_logging_coll_under_frac:units = "fraction" ;
+ fates_landuse_logging_coll_under_frac:long_name = "Fraction of stems killed in the understory when logging generates disturbance" ;
+ double fates_landuse_logging_collateral_frac ;
+ fates_landuse_logging_collateral_frac:units = "fraction" ;
+ fates_landuse_logging_collateral_frac:long_name = "Fraction of large stems in upperstory that die from logging collateral damage" ;
+ double fates_landuse_logging_dbhmax ;
+ fates_landuse_logging_dbhmax:units = "cm" ;
+ fates_landuse_logging_dbhmax:long_name = "Maximum dbh below which logging is applied (unset values flag this to be unused)" ;
+ double fates_landuse_logging_dbhmax_infra ;
+ fates_landuse_logging_dbhmax_infra:units = "cm" ;
+ fates_landuse_logging_dbhmax_infra:long_name = "Tree diameter, above which infrastructure from logging does not impact damage or mortality." ;
+ double fates_landuse_logging_dbhmin ;
+ fates_landuse_logging_dbhmin:units = "cm" ;
+ fates_landuse_logging_dbhmin:long_name = "Minimum dbh at which logging is applied" ;
+ double fates_landuse_logging_direct_frac ;
+ fates_landuse_logging_direct_frac:units = "fraction" ;
+ fates_landuse_logging_direct_frac:long_name = "Fraction of stems logged directly per event" ;
+ double fates_landuse_logging_event_code ;
+ fates_landuse_logging_event_code:units = "unitless" ;
+ fates_landuse_logging_event_code:long_name = "Integer code that options how logging events are structured" ;
+ double fates_landuse_logging_export_frac ;
+ fates_landuse_logging_export_frac:units = "fraction" ;
+ fates_landuse_logging_export_frac:long_name = "fraction of trunk product being shipped offsite, the leftovers will be left onsite as large CWD" ;
+ double fates_landuse_logging_mechanical_frac ;
+ fates_landuse_logging_mechanical_frac:units = "fraction" ;
+ fates_landuse_logging_mechanical_frac:long_name = "Fraction of stems killed due infrastructure an other mechanical means" ;
+ double fates_landuse_pprodharv10_forest_mean ;
+ fates_landuse_pprodharv10_forest_mean:units = "fraction" ;
+ fates_landuse_pprodharv10_forest_mean:long_name = "mean harvest mortality proportion of deadstem to 10-yr product (pprodharv10) of all woody PFT types" ;
+ double fates_leaf_photo_temp_acclim_thome_time ;
+ fates_leaf_photo_temp_acclim_thome_time:units = "years" ;
+ fates_leaf_photo_temp_acclim_thome_time:long_name = "Length of the window for the long-term (i.e. T_home in Kumarathunge et al 2019) exponential moving average (ema) of vegetation temperature used in photosynthesis temperature acclimation (used if fates_leaf_photo_tempsens_model = 2)" ;
+ double fates_leaf_photo_temp_acclim_timescale ;
+ fates_leaf_photo_temp_acclim_timescale:units = "days" ;
+ fates_leaf_photo_temp_acclim_timescale:long_name = "Length of the window for the exponential moving average (ema) of vegetation temperature used in photosynthesis temperature acclimation (used if fates_maintresp_leaf_model=2 or fates_leaf_photo_tempsens_model = 2)" ;
+ double fates_leaf_photo_tempsens_model ;
+ fates_leaf_photo_tempsens_model:units = "unitless" ;
+ fates_leaf_photo_tempsens_model:long_name = "switch for choosing the model that defines the temperature sensitivity of photosynthetic parameters (vcmax, jmax). 1=non-acclimating; 2=Kumarathunge et al 2019" ;
+ double fates_leaf_stomatal_assim_model ;
+ fates_leaf_stomatal_assim_model:units = "unitless" ;
+ fates_leaf_stomatal_assim_model:long_name = "a switch designating whether to use net (1) or gross (2) assimilation in the stomatal model" ;
+ double fates_leaf_stomatal_model ;
+ fates_leaf_stomatal_model:units = "unitless" ;
+ fates_leaf_stomatal_model:long_name = "switch for choosing between Ball-Berry (1) stomatal conductance model and Medlyn (2) model" ;
+ double fates_leaf_theta_cj_c3 ;
+ fates_leaf_theta_cj_c3:units = "unitless" ;
+ fates_leaf_theta_cj_c3:long_name = "Empirical curvature parameter for ac, aj photosynthesis co-limitation in c3 plants" ;
+ double fates_leaf_theta_cj_c4 ;
+ fates_leaf_theta_cj_c4:units = "unitless" ;
+ fates_leaf_theta_cj_c4:long_name = "Empirical curvature parameter for ac, aj photosynthesis co-limitation in c4 plants" ;
+ double fates_maintresp_leaf_model ;
+ fates_maintresp_leaf_model:units = "unitless" ;
+ fates_maintresp_leaf_model:long_name = "switch for choosing between maintenance respiration models. 1=Ryan (1991), 2=Atkin et al., (2017)" ;
+ double fates_maintresp_nonleaf_baserate ;
+ fates_maintresp_nonleaf_baserate:units = "gC/gN/s" ;
+ fates_maintresp_nonleaf_baserate:long_name = "Base maintenance respiration rate for plant tissues, using Ryan 1991" ;
+ double fates_maxcohort ;
+ fates_maxcohort:units = "count" ;
+ fates_maxcohort:long_name = "maximum number of cohorts per patch. Actual number of cohorts also depend on cohort fusion tolerances" ;
+ double fates_mort_cstarvation_model ;
+ fates_mort_cstarvation_model:units = "unitless" ;
+ fates_mort_cstarvation_model:long_name = "switch defining the carbon starvation model ( 1) Linear or 2) Exponential) in the mortality_rates function." ;
+ double fates_mort_disturb_frac ;
+ fates_mort_disturb_frac:units = "fraction" ;
+ fates_mort_disturb_frac:long_name = "fraction of canopy mortality that results in disturbance (i.e. transfer of area from new to old patch)" ;
+ double fates_mort_understorey_death ;
+ fates_mort_understorey_death:units = "fraction" ;
+ fates_mort_understorey_death:long_name = "fraction of plants in understorey cohort impacted by overstorey tree-fall" ;
+ double fates_patch_fusion_tol ;
+ fates_patch_fusion_tol:units = "unitless" ;
+ fates_patch_fusion_tol:long_name = "minimum fraction in difference in profiles between patches" ;
+ double fates_phen_chilltemp ;
+ fates_phen_chilltemp:units = "degrees C" ;
+ fates_phen_chilltemp:long_name = "chilling day counting threshold for vegetation" ;
+ double fates_phen_coldtemp ;
+ fates_phen_coldtemp:units = "degrees C" ;
+ fates_phen_coldtemp:long_name = "vegetation temperature exceedance that flags a cold-day for leaf-drop" ;
+ double fates_phen_gddthresh_a ;
+ fates_phen_gddthresh_a:units = "none" ;
+ fates_phen_gddthresh_a:long_name = "GDD accumulation function, intercept parameter: gdd_thesh = a + b exp(c*ncd)" ;
+ double fates_phen_gddthresh_b ;
+ fates_phen_gddthresh_b:units = "none" ;
+ fates_phen_gddthresh_b:long_name = "GDD accumulation function, multiplier parameter: gdd_thesh = a + b exp(c*ncd)" ;
+ double fates_phen_gddthresh_c ;
+ fates_phen_gddthresh_c:units = "none" ;
+ fates_phen_gddthresh_c:long_name = "GDD accumulation function, exponent parameter: gdd_thesh = a + b exp(c*ncd)" ;
+ double fates_phen_mindayson ;
+ fates_phen_mindayson:units = "days" ;
+ fates_phen_mindayson:long_name = "day threshold compared against days since leaves became on-allometry" ;
+ double fates_phen_ncolddayslim ;
+ fates_phen_ncolddayslim:units = "days" ;
+ fates_phen_ncolddayslim:long_name = "day threshold exceedance for temperature leaf-drop" ;
+ double fates_q10_froz ;
+ fates_q10_froz:units = "unitless" ;
+ fates_q10_froz:long_name = "Q10 for frozen-soil respiration rates" ;
+ double fates_q10_mr ;
+ fates_q10_mr:units = "unitless" ;
+ fates_q10_mr:long_name = "Q10 for maintenance respiration" ;
+ double fates_rad_model ;
+ fates_rad_model:units = "unitless" ;
+ fates_rad_model:long_name = "switch designating the model for canopy radiation, 1 = Norman, 2 = Two-stream (experimental)" ;
+ double fates_regeneration_model ;
+ fates_regeneration_model:units = "-" ;
+ fates_regeneration_model:long_name = "switch for choosing between FATES\'s: 1) default regeneration scheme , 2) the Tree Recruitment Scheme (Hanbury-Brown et al., 2022), or (3) the Tree Recruitment Scheme without seedling dynamics" ;
+ double fates_soil_salinity ;
+ fates_soil_salinity:units = "ppt" ;
+ fates_soil_salinity:long_name = "soil salinity used for model when not coupled to dynamic soil salinity" ;
+ double fates_trs_seedling2sap_par_timescale ;
+ fates_trs_seedling2sap_par_timescale:units = "days" ;
+ fates_trs_seedling2sap_par_timescale:long_name = "Length of the window for the exponential moving average of par at the seedling layer used to calculate seedling to sapling transition rates" ;
+ double fates_trs_seedling_emerg_h2o_timescale ;
+ fates_trs_seedling_emerg_h2o_timescale:units = "days" ;
+ fates_trs_seedling_emerg_h2o_timescale:long_name = "Length of the window for the exponential moving average of smp used to calculate seedling emergence" ;
+ double fates_trs_seedling_mdd_timescale ;
+ fates_trs_seedling_mdd_timescale:units = "days" ;
+ fates_trs_seedling_mdd_timescale:long_name = "Length of the window for the exponential moving average of moisture deficit days used to calculate seedling mortality" ;
+ double fates_trs_seedling_mort_par_timescale ;
+ fates_trs_seedling_mort_par_timescale:units = "days" ;
+ fates_trs_seedling_mort_par_timescale:long_name = "Length of the window for the exponential moving average of par at the seedling layer used to calculate seedling mortality" ;
+ double fates_vai_top_bin_width ;
+ fates_vai_top_bin_width:units = "m2/m2" ;
+ fates_vai_top_bin_width:long_name = "width in VAI units of uppermost leaf+stem layer scattering element in each canopy layer" ;
+ double fates_vai_width_increase_factor ;
+ fates_vai_width_increase_factor:units = "unitless" ;
+ fates_vai_width_increase_factor:long_name = "factor by which each leaf+stem scattering element increases in VAI width (1 = uniform spacing)" ;
+
+// global attributes:
+ :history = "This file was generated by BatchPatchParams.py:\nCDL Base File = archive/api24.1.0_101722_fates_params_default.cdl\nXML patch file = archive/api24.1.0_101722_patch_params.xml" ;
+data:
+
+ fates_history_ageclass_bin_edges = 0, 1, 2, 5, 10, 20, 50 ;
+
+ fates_history_coageclass_bin_edges = 0, 5 ;
+
+ fates_history_height_bin_edges = 0, 0.1, 0.3, 1, 3, 10 ;
+
+ fates_history_damage_bin_edges = 0, 80 ;
+
+ fates_history_sizeclass_bin_edges = 0, 5, 10, 15, 20, 30, 40, 50, 60, 70,
+ 80, 90, 100 ;
+
+ fates_alloc_organ_id = 1, 2, 3, 6 ;
+
+ fates_hydro_htftype_node = 1, 1, 1, 1 ;
+
+ fates_pftname =
+ "broadleaf_evergreen_tropical_tree ",
+ "needleleaf_evergreen_extratrop_tree ",
+ "needleleaf_colddecid_extratrop_tree ",
+ "broadleaf_evergreen_extratrop_tree ",
+ "broadleaf_hydrodecid_tropical_tree ",
+ "broadleaf_colddecid_extratrop_tree ",
+ "broadleaf_evergreen_extratrop_shrub ",
+ "broadleaf_hydrodecid_extratrop_shrub ",
+ "broadleaf_colddecid_extratrop_shrub ",
+ "arctic_c3_grass ",
+ "cool_c3_grass ",
+ "c4_grass " ;
+
+ fates_hydro_organ_name =
+ "leaf ",
+ "stem ",
+ "transporting root ",
+ "absorbing root " ;
+
+ fates_alloc_organ_name =
+ "leaf",
+ "fine root",
+ "sapwood",
+ "structure" ;
+
+ fates_landuseclass_name =
+ "primaryland",
+ "secondaryland",
+ "rangeland",
+ "pastureland",
+ "cropland" ;
+
+ fates_litterclass_name =
+ "twig ",
+ "small branch ",
+ "large branch ",
+ "trunk ",
+ "dead leaves ",
+ "live grass " ;
+
+ fates_alloc_organ_priority =
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ;
+
+ fates_alloc_storage_cushion = 1.2, 1.2, 1.2, 1.2, 2.4, 1.2, 1.2, 2.4, 1.2,
+ 1.2, 1.2, 1.2 ;
+
+ fates_alloc_store_priority_frac = 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8,
+ 0.8, 0.8, 0.8, 0.8 ;
+
+ fates_allom_agb1 = 0.0673, 0.1364012, 0.0393057, 0.2653695, 0.0673,
+ 0.0728698, 0.06896, 0.06896, 0.06896, 0.01, 0.01, 0.01 ;
+
+ fates_allom_agb2 = 0.976, 0.9449041, 1.087335, 0.8321321, 0.976, 1.0373211,
+ 0.572, 0.572, 0.572, 0.572, 0.572, 0.572 ;
+
+ fates_allom_agb3 = 1.94, 1.94, 1.94, 1.94, 1.94, 1.94, 1.94, 1.94, 1.94,
+ 1.94, 1.94, 1.94 ;
+
+ fates_allom_agb4 = 0.931, 0.931, 0.931, 0.931, 0.931, 0.931, 0.931, 0.931,
+ 0.931, 0.931, 0.931, 0.931 ;
+
+ fates_allom_agb_frac = 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6,
+ 0.6, 0.6 ;
+
+ fates_allom_amode = 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_blca_expnt_diff = -0.12, -0.34, -0.32, -0.22, -0.12, -0.35, 0,
+ 0, 0, 0, 0, 0 ;
+
+ fates_allom_cmode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_d2bl1 = 0.04, 0.07, 0.07, 0.01, 0.04, 0.07, 0.07, 0.07, 0.07,
+ 0.07, 0.07, 0.07 ;
+
+ fates_allom_d2bl2 = 1.6019679, 1.5234373, 1.3051237, 1.9621397, 1.6019679,
+ 1.3998939, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3 ;
+
+ fates_allom_d2bl3 = 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55,
+ 0.55, 0.55, 0.55 ;
+
+ fates_allom_d2ca_coefficient_max = 0.2715891, 0.3693718, 1.0787259,
+ 0.0579297, 0.2715891, 1.1553612, 0.6568464, 0.6568464, 0.6568464,
+ 0.6568464, 0.6568464, 0.6568464 ;
+
+ fates_allom_d2ca_coefficient_min = 0.2715891, 0.3693718, 1.0787259,
+ 0.0579297, 0.2715891, 1.1553612, 0.6568464, 0.6568464, 0.6568464,
+ 0.6568464, 0.6568464, 0.6568464 ;
+
+ fates_allom_d2h1 = 78.4087704, 306.842667, 106.8745821, 104.3586841,
+ 78.4087704, 31.4557047, 0.64, 0.64, 0.64, 0.64, 0.64, 0.64 ;
+
+ fates_allom_d2h2 = 0.8124383, 0.752377, 0.9471302, 1.1146973, 0.8124383,
+ 0.9734088, 0.37, 0.37, 0.37, 0.37, 0.37, 0.37 ;
+
+ fates_allom_d2h3 = 47.6666164, 196.6865691, 93.9790461, 160.6835089,
+ 47.6666164, 16.5928174, -999.9, -999.9, -999.9, -999.9, -999.9, -999.9 ;
+
+ fates_allom_dbh_maxheight = 1000, 1000, 1000, 1000, 1000, 1000, 3, 3, 2,
+ 0.35, 0.35, 0.35 ;
+
+ fates_allom_dmode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_fmode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_fnrt_prof_a = 7, 7, 7, 7, 6, 6, 7, 7, 7, 11, 11, 11 ;
+
+ fates_allom_fnrt_prof_b = 1, 2, 2, 1, 2, 2, 1.5, 1.5, 1.5, 2, 2, 2 ;
+
+ fates_allom_fnrt_prof_mode = 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 ;
+
+ fates_allom_frbstor_repro = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_allom_h2cd1 = 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.95, 0.95, 0.95, 1, 1, 1 ;
+
+ fates_allom_h2cd2 = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_hmode = 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_l2fr = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_la_per_sa_int = 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8,
+ 0.8, 0.8, 0.8 ;
+
+ fates_allom_la_per_sa_slp = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_allom_lmode = 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_sai_scaler = 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
+ 0.1, 0.1 ;
+
+ fates_allom_smode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_stmode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_allom_zroot_k = 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 ;
+
+ fates_allom_zroot_max_dbh = 100, 100, 100, 100, 100, 100, 2, 2, 2, 2, 2, 2 ;
+
+ fates_allom_zroot_max_z = 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100 ;
+
+ fates_allom_zroot_min_dbh = 1, 1, 1, 2.5, 2.5, 2.5, 0.1, 0.1, 0.1, 0.1, 0.1,
+ 0.1 ;
+
+ fates_allom_zroot_min_z = 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100 ;
+
+ fates_c2b = 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ;
+
+ fates_cnp_eca_alpha_ptase = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_cnp_eca_decompmicc = 280, 280, 280, 280, 280, 280, 280, 280, 280, 280,
+ 280, 280 ;
+
+ fates_cnp_eca_km_nh4 = 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14, 0.14,
+ 0.14, 0.14, 0.14 ;
+
+ fates_cnp_eca_km_no3 = 0.27, 0.27, 0.27, 0.27, 0.27, 0.27, 0.27, 0.27, 0.27,
+ 0.27, 0.27, 0.27 ;
+
+ fates_cnp_eca_km_p = 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
+ 0.1 ;
+
+ fates_cnp_eca_km_ptase = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_cnp_eca_lambda_ptase = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_cnp_eca_vmax_ptase = 5e-09, 5e-09, 5e-09, 5e-09, 5e-09, 5e-09, 5e-09,
+ 5e-09, 5e-09, 5e-09, 5e-09, 5e-09 ;
+
+ fates_cnp_nfix1 = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_cnp_nitr_store_ratio = 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5,
+ 1.5, 1.5, 1.5 ;
+
+ fates_cnp_phos_store_ratio = 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5,
+ 1.5, 1.5, 1.5 ;
+
+ fates_cnp_pid_kd = 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ;
+
+ fates_cnp_pid_ki = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_cnp_pid_kp = 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.0005,
+ 0.0005, 0.0005, 0.0005, 0.0005, 0.0005 ;
+
+ fates_cnp_prescribed_nuptake = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_cnp_prescribed_puptake = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_cnp_store_ovrflw_frac = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_cnp_turnover_nitr_retrans =
+ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_cnp_turnover_phos_retrans =
+ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_cnp_vmax_nh4 = 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09,
+ 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09 ;
+
+ fates_cnp_vmax_no3 = 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09,
+ 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09, 2.5e-09 ;
+
+ fates_cnp_vmax_p = 5e-10, 5e-10, 5e-10, 5e-10, 5e-10, 5e-10, 5e-10, 5e-10,
+ 5e-10, 5e-10, 5e-10, 5e-10 ;
+
+ fates_damage_frac = 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01,
+ 0.01, 0.01, 0.01 ;
+
+ fates_damage_mort_p1 = 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 ;
+
+ fates_damage_mort_p2 = 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5,
+ 5.5, 5.5 ;
+
+ fates_damage_recovery_scalar = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_dev_arbitrary_pft = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_fire_alpha_SH = 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2,
+ 0.2 ;
+
+ fates_fire_bark_scaler = 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, 0.07, 0.07,
+ 0.07, 0.07, 0.07, 0.07 ;
+
+ fates_fire_crown_kill = 0.775, 0.775, 0.775, 0.775, 0.775, 0.775, 0.775,
+ 0.775, 0.775, 0.775, 0.775, 0.775 ;
+
+ fates_frag_fnrt_fcel = 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5 ;
+
+ fates_frag_fnrt_flab = 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0.25, 0.25, 0.25 ;
+
+ fates_frag_fnrt_flig = 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0.25, 0.25, 0.25 ;
+
+ fates_frag_leaf_fcel = 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5 ;
+
+ fates_frag_leaf_flab = 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0.25, 0.25, 0.25 ;
+
+ fates_frag_leaf_flig = 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25,
+ 0.25, 0.25, 0.25 ;
+
+ fates_frag_seed_decay_rate = 0.51, 0.51, 0.51, 0.51, 0.51, 0.51, 0.51, 0.51,
+ 0.51, 0.51, 0.51, 0.51 ;
+
+ fates_grperc = 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11,
+ 0.11, 0.11 ;
+
+ fates_hydro_avuln_gs = 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5,
+ 2.5, 2.5 ;
+
+ fates_hydro_avuln_node =
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ;
+
+ fates_hydro_epsil_node =
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ;
+
+ fates_hydro_fcap_node =
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08,
+ 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08, 0.08,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_hydro_k_lwp = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_hydro_kmax_node =
+ -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999,
+ -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999, -999 ;
+
+ fates_hydro_p50_gs = -1.5, -1.5, -1.5, -1.5, -1.5, -1.5, -1.5, -1.5, -1.5,
+ -1.5, -1.5, -1.5 ;
+
+ fates_hydro_p50_node =
+ -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25,
+ -2.25, -2.25,
+ -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25,
+ -2.25, -2.25,
+ -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25,
+ -2.25, -2.25,
+ -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25, -2.25,
+ -2.25, -2.25 ;
+
+ fates_hydro_p_taper = 0.333, 0.333, 0.333, 0.333, 0.333, 0.333, 0.333,
+ 0.333, 0.333, 0.333, 0.333, 0.333 ;
+
+ fates_hydro_pinot_node =
+ -1.465984, -1.465984, -1.465984, -1.465984, -1.465984, -1.465984,
+ -1.465984, -1.465984, -1.465984, -1.465984, -1.465984, -1.465984,
+ -1.22807, -1.22807, -1.22807, -1.22807, -1.22807, -1.22807, -1.22807,
+ -1.22807, -1.22807, -1.22807, -1.22807, -1.22807,
+ -1.22807, -1.22807, -1.22807, -1.22807, -1.22807, -1.22807, -1.22807,
+ -1.22807, -1.22807, -1.22807, -1.22807, -1.22807,
+ -1.043478, -1.043478, -1.043478, -1.043478, -1.043478, -1.043478,
+ -1.043478, -1.043478, -1.043478, -1.043478, -1.043478, -1.043478 ;
+
+ fates_hydro_pitlp_node =
+ -1.67, -1.67, -1.67, -1.67, -1.67, -1.67, -1.67, -1.67, -1.67, -1.67,
+ -1.67, -1.67,
+ -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4,
+ -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4, -1.4,
+ -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2, -1.2 ;
+
+ fates_hydro_resid_node =
+ 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16, 0.16,
+ 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21,
+ 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21, 0.21,
+ 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11, 0.11 ;
+
+ fates_hydro_rfrac_stem = 0.625, 0.625, 0.625, 0.625, 0.625, 0.625, 0.625,
+ 0.625, 0.625, 0.625, 0.625, 0.625 ;
+
+ fates_hydro_rs2 = 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001,
+ 0.0001, 0.0001, 0.0001, 0.0001, 0.0001 ;
+
+ fates_hydro_srl = 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25 ;
+
+ fates_hydro_thetas_node =
+ 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65,
+ 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65,
+ 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65,
+ 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75 ;
+
+ fates_hydro_vg_alpha_node =
+ 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.005, 0.005, 0.005, 0.005, 0.005,
+ 0.005, 0.005,
+ 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.005, 0.005, 0.005, 0.005, 0.005,
+ 0.005, 0.005,
+ 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.005, 0.005, 0.005, 0.005, 0.005,
+ 0.005, 0.005,
+ 0.0005, 0.0005, 0.0005, 0.0005, 0.0005, 0.005, 0.005, 0.005, 0.005, 0.005,
+ 0.005, 0.005 ;
+
+ fates_hydro_vg_m_node =
+ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 ;
+
+ fates_hydro_vg_n_node =
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ;
+
+ fates_leaf_c3psn = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ;
+
+ fates_leaf_jmaxha = 43540, 43540, 43540, 43540, 43540, 43540, 43540, 43540,
+ 43540, 43540, 43540, 43540 ;
+
+ fates_leaf_jmaxhd = 152040, 152040, 152040, 152040, 152040, 152040, 152040,
+ 152040, 152040, 152040, 152040, 152040 ;
+
+ fates_leaf_jmaxse = 495, 495, 495, 495, 495, 495, 495, 495, 495, 495, 495,
+ 495 ;
+
+ fates_leaf_slamax = 0.0954, 0.0954, 0.0954, 0.0954, 0.0954, 0.0954, 0.012,
+ 0.03, 0.03, 0.03, 0.03, 0.03 ;
+
+ fates_leaf_slatop = 0.012, 0.005, 0.024, 0.009, 0.03, 0.03, 0.012, 0.03,
+ 0.03, 0.03, 0.03, 0.03 ;
+
+ fates_leaf_stomatal_intercept = 10000, 10000, 10000, 10000, 10000, 10000,
+ 10000, 10000, 10000, 10000, 10000, 40000 ;
+
+ fates_leaf_stomatal_slope_ballberry = 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 ;
+
+ fates_leaf_stomatal_slope_medlyn = 4.1, 2.3, 2.3, 4.1, 4.4, 4.4, 4.7, 4.7,
+ 4.7, 2.2, 5.3, 1.6 ;
+
+ fates_leaf_vcmax25top =
+ 50, 62, 39, 61, 58, 58, 62, 54, 54, 78, 78, 78 ;
+
+ fates_leaf_vcmaxha = 65330, 65330, 65330, 65330, 65330, 65330, 65330, 65330,
+ 65330, 65330, 65330, 65330 ;
+
+ fates_leaf_vcmaxhd = 149250, 149250, 149250, 149250, 149250, 149250, 149250,
+ 149250, 149250, 149250, 149250, 149250 ;
+
+ fates_leaf_vcmaxse = 485, 485, 485, 485, 485, 485, 485, 485, 485, 485, 485,
+ 485 ;
+
+ fates_leafn_vert_scaler_coeff1 = 0.00963, 0.00963, 0.00963, 0.00963,
+ 0.00963, 0.00963, 0.00963, 0.00963, 0.00963, 0.00963, 0.00963, 0.00963 ;
+
+ fates_leafn_vert_scaler_coeff2 = 2.43, 2.43, 2.43, 2.43, 2.43, 2.43, 2.43,
+ 2.43, 2.43, 2.43, 2.43, 2.43 ;
+
+ fates_maintresp_leaf_atkin2017_baserate = 1.756, 1.4995, 1.4995, 1.756,
+ 1.756, 1.756, 2.0749, 2.0749, 2.0749, 2.1956, 2.1956, 2.1956 ;
+
+ fates_maintresp_leaf_ryan1991_baserate = 2.525e-06, 2.525e-06, 2.525e-06,
+ 2.525e-06, 2.525e-06, 2.525e-06, 2.525e-06, 2.525e-06, 2.525e-06,
+ 2.525e-06, 2.525e-06, 2.525e-06 ;
+
+ fates_maintresp_leaf_vert_scaler_coeff1 = 0.00963, 0.00963, 0.00963,
+ 0.00963, 0.00963, 0.00963, 0.00963, 0.00963, 0.00963, 0.00963, 0.00963,
+ 0.00963 ;
+
+ fates_maintresp_leaf_vert_scaler_coeff2 = 2.43, 2.43, 2.43, 2.43, 2.43,
+ 2.43, 2.43, 2.43, 2.43, 2.43, 2.43, 2.43 ;
+
+ fates_maintresp_reduction_curvature = 0.01, 0.01, 0.01, 0.01, 0.01, 0.01,
+ 0.01, 0.01, 0.01, 0.01, 0.01, 0.01 ;
+
+ fates_maintresp_reduction_intercept = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_maintresp_reduction_upthresh = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_mort_bmort = 0.014, 0.014, 0.014, 0.014, 0.014, 0.014, 0.014, 0.014,
+ 0.014, 0.014, 0.014, 0.014 ;
+
+ fates_mort_freezetol = 2.5, -55, -80, -30, 2.5, -80, -60, -10, -80, -80,
+ -20, 2.5 ;
+
+ fates_mort_hf_flc_threshold = 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5 ;
+
+ fates_mort_hf_sm_threshold = 1e-06, 1e-06, 1e-06, 1e-06, 1e-06, 1e-06,
+ 1e-06, 1e-06, 1e-06, 1e-06, 1e-06, 1e-06 ;
+
+ fates_mort_ip_age_senescence = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_mort_ip_size_senescence = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_mort_prescribed_canopy = 0.0194, 0.0194, 0.0194, 0.0194, 0.0194,
+ 0.0194, 0.0194, 0.0194, 0.0194, 0.0194, 0.0194, 0.0194 ;
+
+ fates_mort_prescribed_understory = 0.025, 0.025, 0.025, 0.025, 0.025, 0.025,
+ 0.025, 0.025, 0.025, 0.025, 0.025, 0.025 ;
+
+ fates_mort_r_age_senescence = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_mort_r_size_senescence = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_mort_scalar_coldstress = 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 ;
+
+ fates_mort_scalar_cstarvation = 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6,
+ 0.6, 0.6, 0.6 ;
+
+ fates_mort_scalar_hydrfailure = 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6,
+ 0.6, 0.6, 0.6 ;
+
+ fates_mort_upthresh_cstarvation = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_nonhydro_smpsc = -255000, -255000, -255000, -255000, -255000, -255000,
+ -255000, -255000, -255000, -255000, -255000, -255000 ;
+
+ fates_nonhydro_smpso = -66000, -66000, -66000, -66000, -66000, -66000,
+ -66000, -66000, -66000, -66000, -66000, -66000 ;
+
+ fates_phen_cold_size_threshold = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_phen_drought_threshold = -152957.4, -152957.4, -152957.4, -152957.4,
+ -152957.4, -152957.4, -152957.4, -152957.4, -152957.4, -152957.4,
+ -152957.4, -152957.4 ;
+
+ fates_phen_evergreen = 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0 ;
+
+ fates_phen_flush_fraction = _, _, 0.5, _, 0.5, 0.5, _, 0.5, 0.5, 0.5, 0.5,
+ 0.5 ;
+
+ fates_phen_fnrt_drop_fraction = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_phen_mindaysoff = 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
+ 100, 100 ;
+
+ fates_phen_moist_threshold = -122365.9, -122365.9, -122365.9, -122365.9,
+ -122365.9, -122365.9, -122365.9, -122365.9, -122365.9, -122365.9,
+ -122365.9, -122365.9 ;
+
+ fates_phen_season_decid = 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0 ;
+
+ fates_phen_stem_drop_fraction = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_phen_stress_decid = 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1 ;
+
+ fates_prescribed_npp_canopy = 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4,
+ 0.4, 0.4, 0.4 ;
+
+ fates_prescribed_npp_understory = 0.03125, 0.03125, 0.03125, 0.03125,
+ 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125 ;
+
+ fates_rad_leaf_clumping_index = 0.85, 0.85, 0.8, 0.85, 0.85, 0.9, 0.85, 0.9,
+ 0.9, 0.75, 0.75, 0.75 ;
+
+ fates_rad_leaf_rhonir = 0.46, 0.41, 0.39, 0.46, 0.41, 0.41, 0.46, 0.41,
+ 0.41, 0.28, 0.28, 0.28 ;
+
+ fates_rad_leaf_rhovis = 0.11, 0.09, 0.08, 0.11, 0.08, 0.08, 0.11, 0.08,
+ 0.08, 0.05, 0.05, 0.05 ;
+
+ fates_rad_leaf_taunir = 0.33, 0.32, 0.42, 0.33, 0.43, 0.43, 0.33, 0.43,
+ 0.43, 0.4, 0.4, 0.4 ;
+
+ fates_rad_leaf_tauvis = 0.06, 0.04, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06,
+ 0.06, 0.05, 0.05, 0.05 ;
+
+ fates_rad_leaf_xl = 0.32, 0.01, 0.01, 0.32, 0.2, 0.59, 0.32, 0.59, 0.59,
+ -0.23, -0.23, -0.23 ;
+
+ fates_rad_stem_rhonir = 0.49, 0.36, 0.36, 0.49, 0.49, 0.49, 0.49, 0.49,
+ 0.49, 0.53, 0.53, 0.53 ;
+
+ fates_rad_stem_rhovis = 0.21, 0.12, 0.12, 0.21, 0.21, 0.21, 0.21, 0.21,
+ 0.21, 0.31, 0.31, 0.31 ;
+
+ fates_rad_stem_taunir = 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001,
+ 0.001, 0.001, 0.25, 0.25, 0.25 ;
+
+ fates_rad_stem_tauvis = 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001,
+ 0.001, 0.001, 0.12, 0.12, 0.12 ;
+
+ fates_recruit_height_min = 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 0.2, 0.2, 0.2,
+ 0.125, 0.125, 0.125 ;
+
+ fates_recruit_init_density = 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2,
+ 0.2, 0.2, 0.2 ;
+
+ fates_recruit_prescribed_rate = 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02,
+ 0.02, 0.02, 0.02, 0.02, 0.02 ;
+
+ fates_recruit_seed_alloc = 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
+ 0.1, 0.1 ;
+
+ fates_recruit_seed_alloc_mature = 0, 0, 0, 0, 0, 0, 0.9, 0.9, 0.9, 0.9, 0.9,
+ 0.9 ;
+
+ fates_recruit_seed_dbh_repro_threshold = 90, 80, 80, 80, 90, 80, 3, 3, 2,
+ 0.35, 0.35, 0.35 ;
+
+ fates_recruit_seed_germination_rate = 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5, 0.5, 0.5 ;
+
+ fates_recruit_seed_supplement = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
+
+ fates_seed_dispersal_fraction = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_seed_dispersal_max_dist = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_seed_dispersal_pdf_scale = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_seed_dispersal_pdf_shape = _, _, _, _, _, _, _, _, _, _, _, _ ;
+
+ fates_stoich_nitr =
+ 0.033, 0.029, 0.04, 0.033, 0.04, 0.04, 0.033, 0.04, 0.04, 0.04, 0.04, 0.04,
+ 0.024, 0.024, 0.024, 0.024, 0.024, 0.024, 0.024, 0.024, 0.024, 0.024,
+ 0.024, 0.024,
+ 1e-08, 1e-08, 1e-08, 1e-08, 1e-08, 1e-08, 1e-08, 1e-08, 1e-08, 1e-08,
+ 1e-08, 1e-08,
+ 0.0047, 0.0047, 0.0047, 0.0047, 0.0047, 0.0047, 0.0047, 0.0047, 0.0047,
+ 0.0047, 0.0047, 0.0047 ;
+
+ fates_stoich_phos =
+ 0.0033, 0.0029, 0.004, 0.0033, 0.004, 0.004, 0.0033, 0.004, 0.004, 0.004,
+ 0.004, 0.004,
+ 0.0024, 0.0024, 0.0024, 0.0024, 0.0024, 0.0024, 0.0024, 0.0024, 0.0024,
+ 0.0024, 0.0024, 0.0024,
+ 1e-09, 1e-09, 1e-09, 1e-09, 1e-09, 1e-09, 1e-09, 1e-09, 1e-09, 1e-09,
+ 1e-09, 1e-09,
+ 0.00047, 0.00047, 0.00047, 0.00047, 0.00047, 0.00047, 0.00047, 0.00047,
+ 0.00047, 0.00047, 0.00047, 0.00047 ;
+
+ fates_trim_inc = 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03, 0.03,
+ 0.03, 0.03 ;
+
+ fates_trim_limit = 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3 ;
+
+ fates_trs_repro_alloc_a = 0.0049, 0.0049, 0.0049, 0.0049, 0.0049, 0.0049,
+ 0.0049, 0.0049, 0.0049, 0.0049, 0.0049, 0.0049 ;
+
+ fates_trs_repro_alloc_b = -2.6171, -2.6171, -2.6171, -2.6171, -2.6171,
+ -2.6171, -2.6171, -2.6171, -2.6171, -2.6171, -2.6171, -2.6171 ;
+
+ fates_trs_repro_frac_seed = 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24,
+ 0.24, 0.24, 0.24, 0.24 ;
+
+ fates_trs_seedling_a_emerg = 0.0003, 0.0003, 0.0003, 0.0003, 0.0003, 0.0003,
+ 0.0003, 0.0003, 0.0003, 0.0003, 0.0003, 0.0003 ;
+
+ fates_trs_seedling_b_emerg = 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2, 1.2,
+ 1.2, 1.2, 1.2 ;
+
+ fates_trs_seedling_background_mort = 0.1085371, 0.1085371, 0.1085371,
+ 0.1085371, 0.1085371, 0.1085371, 0.1085371, 0.1085371, 0.1085371,
+ 0.1085371, 0.1085371, 0.1085371 ;
+
+ fates_trs_seedling_h2o_mort_a = 4.070565e-17, 4.070565e-17, 4.070565e-17,
+ 4.070565e-17, 4.070565e-17, 4.070565e-17, 4.070565e-17, 4.070565e-17,
+ 4.070565e-17, 4.070565e-17, 4.070565e-17, 4.070565e-17 ;
+
+ fates_trs_seedling_h2o_mort_b = -6.390757e-11, -6.390757e-11, -6.390757e-11,
+ -6.390757e-11, -6.390757e-11, -6.390757e-11, -6.390757e-11,
+ -6.390757e-11, -6.390757e-11, -6.390757e-11, -6.390757e-11, -6.390757e-11 ;
+
+ fates_trs_seedling_h2o_mort_c = 1.268992e-05, 1.268992e-05, 1.268992e-05,
+ 1.268992e-05, 1.268992e-05, 1.268992e-05, 1.268992e-05, 1.268992e-05,
+ 1.268992e-05, 1.268992e-05, 1.268992e-05, 1.268992e-05 ;
+
+ fates_trs_seedling_light_mort_a = -0.009897694, -0.009897694, -0.009897694,
+ -0.009897694, -0.009897694, -0.009897694, -0.009897694, -0.009897694,
+ -0.009897694, -0.009897694, -0.009897694, -0.009897694 ;
+
+ fates_trs_seedling_light_mort_b = -7.154063, -7.154063, -7.154063,
+ -7.154063, -7.154063, -7.154063, -7.154063, -7.154063, -7.154063,
+ -7.154063, -7.154063, -7.154063 ;
+
+ fates_trs_seedling_light_rec_a = 0.007, 0.007, 0.007, 0.007, 0.007, 0.007,
+ 0.007, 0.007, 0.007, 0.007, 0.007, 0.007 ;
+
+ fates_trs_seedling_light_rec_b = 0.8615, 0.8615, 0.8615, 0.8615, 0.8615,
+ 0.8615, 0.8615, 0.8615, 0.8615, 0.8615, 0.8615, 0.8615 ;
+
+ fates_trs_seedling_mdd_crit = 1400000, 1400000, 1400000, 1400000, 1400000,
+ 1400000, 1400000, 1400000, 1400000, 1400000, 1400000, 1400000 ;
+
+ fates_trs_seedling_par_crit_germ = 0.656, 0.656, 0.656, 0.656, 0.656, 0.656,
+ 0.656, 0.656, 0.656, 0.656, 0.656, 0.656 ;
+
+ fates_trs_seedling_psi_crit = -251995.7, -251995.7, -251995.7, -251995.7,
+ -251995.7, -251995.7, -251995.7, -251995.7, -251995.7, -251995.7,
+ -251995.7, -251995.7 ;
+
+ fates_trs_seedling_psi_emerg = -15744.65, -15744.65, -15744.65, -15744.65,
+ -15744.65, -15744.65, -15744.65, -15744.65, -15744.65, -15744.65,
+ -15744.65, -15744.65 ;
+
+ fates_trs_seedling_root_depth = 0.06, 0.06, 0.06, 0.06, 0.06, 0.06, 0.06,
+ 0.06, 0.06, 0.06, 0.06, 0.06 ;
+
+ fates_turb_displar = 0.67, 0.67, 0.67, 0.67, 0.67, 0.67, 0.67, 0.67, 0.67,
+ 0.67, 0.67, 0.67 ;
+
+ fates_turb_leaf_diameter = 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04, 0.04,
+ 0.04, 0.04, 0.04, 0.04 ;
+
+ fates_turb_z0mr = 0.055, 0.055, 0.055, 0.055, 0.055, 0.055, 0.055, 0.055,
+ 0.055, 0.055, 0.055, 0.055 ;
+
+ fates_turnover_branch = 150, 150, 150, 150, 150, 150, 150, 150, 150, 0, 0, 0 ;
+
+ fates_turnover_fnrt = 1, 2, 1, 1.5, 1, 1, 1.5, 1, 1, 1, 1, 1 ;
+
+ fates_turnover_leaf =
+ 1.5, 4, 1, 1.5, 1, 1, 1.5, 1, 1, 1, 1, 1 ;
+
+ fates_turnover_senleaf_fdrought = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
+
+ fates_wood_density = 0.548327, 0.44235, 0.454845, 0.754336, 0.548327,
+ 0.566452, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7 ;
+
+ fates_woody = 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 ;
+
+ fates_hlm_pft_map =
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
+ 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_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.8, 0.62, 0.62 ;
+
+ 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, 0.8, 3.2, 3.2 ;
+
+ 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, 0.24, 0.24 ;
+
+ fates_fire_SAV = 13, 3.58, 0.98, 0.2, 66, 66 ;
+
+ 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 ;
+
+ fates_maxpatches_by_landuse = 9, 4, 1, 1, 1 ;
+
+ fates_canopy_closure_thresh = 0.8 ;
+
+ fates_cnp_eca_plant_escalar = 1.25e-05 ;
+
+ fates_cohort_age_fusion_tol = 0.08 ;
+
+ fates_cohort_size_fusion_tol = 0.08 ;
+
+ fates_comp_excln = 3 ;
+
+ fates_damage_canopy_layer_code = 1 ;
+
+ fates_damage_event_code = 1 ;
+
+ fates_daylength_factor_switch = 1 ;
+
+ fates_dev_arbitrary = _ ;
+
+ fates_fire_active_crown_fire = 0 ;
+
+ fates_fire_cg_strikes = 0.2 ;
+
+ fates_fire_drying_ratio = 66000 ;
+
+ fates_fire_durat_slope = -11.06 ;
+
+ fates_fire_fdi_alpha = 0.00037 ;
+
+ fates_fire_fuel_energy = 18000 ;
+
+ fates_fire_max_durat = 240 ;
+
+ fates_fire_miner_damp = 0.41739 ;
+
+ fates_fire_miner_total = 0.055 ;
+
+ fates_fire_nignitions = 15 ;
+
+ fates_fire_part_dens = 513 ;
+
+ fates_fire_threshold = 50 ;
+
+ fates_frag_cwd_fcel = 0.76 ;
+
+ fates_frag_cwd_flig = 0.24 ;
+
+ fates_hydro_kmax_rsurf1 = 20 ;
+
+ fates_hydro_kmax_rsurf2 = 0.0001 ;
+
+ fates_hydro_psi0 = 0 ;
+
+ fates_hydro_psicap = -0.6 ;
+
+ fates_hydro_solver = 1 ;
+
+ fates_landuse_logging_coll_under_frac = 0.55983 ;
+
+ fates_landuse_logging_collateral_frac = 0.05 ;
+
+ fates_landuse_logging_dbhmax = _ ;
+
+ fates_landuse_logging_dbhmax_infra = 35 ;
+
+ fates_landuse_logging_dbhmin = 50 ;
+
+ fates_landuse_logging_direct_frac = 0.15 ;
+
+ fates_landuse_logging_event_code = -30 ;
+
+ fates_landuse_logging_export_frac = 0.8 ;
+
+ fates_landuse_logging_mechanical_frac = 0.05 ;
+
+ fates_landuse_pprodharv10_forest_mean = 0.8125 ;
+
+ fates_leaf_photo_temp_acclim_thome_time = 30 ;
+
+ fates_leaf_photo_temp_acclim_timescale = 30 ;
+
+ fates_leaf_photo_tempsens_model = 1 ;
+
+ fates_leaf_stomatal_assim_model = 1 ;
+
+ fates_leaf_stomatal_model = 1 ;
+
+ fates_leaf_theta_cj_c3 = 0.999 ;
+
+ fates_leaf_theta_cj_c4 = 0.999 ;
+
+ fates_maintresp_leaf_model = 1 ;
+
+ fates_maintresp_nonleaf_baserate = 2.525e-06 ;
+
+ fates_maxcohort = 100 ;
+
+ fates_mort_cstarvation_model = 1 ;
+
+ fates_mort_disturb_frac = 1 ;
+
+ fates_mort_understorey_death = 0.55983 ;
+
+ fates_patch_fusion_tol = 0.05 ;
+
+ fates_phen_chilltemp = 5 ;
+
+ fates_phen_coldtemp = 7.5 ;
+
+ fates_phen_gddthresh_a = -68 ;
+
+ fates_phen_gddthresh_b = 638 ;
+
+ fates_phen_gddthresh_c = -0.01 ;
+
+ fates_phen_mindayson = 90 ;
+
+ fates_phen_ncolddayslim = 5 ;
+
+ fates_q10_froz = 1.5 ;
+
+ fates_q10_mr = 1.5 ;
+
+ fates_rad_model = 1 ;
+
+ fates_regeneration_model = 1 ;
+
+ fates_soil_salinity = 0.4 ;
+
+ fates_trs_seedling2sap_par_timescale = 32 ;
+
+ fates_trs_seedling_emerg_h2o_timescale = 7 ;
+
+ fates_trs_seedling_mdd_timescale = 126 ;
+
+ fates_trs_seedling_mort_par_timescale = 32 ;
+
+ fates_vai_top_bin_width = 1 ;
+
+ fates_vai_width_increase_factor = 1 ;
+}
diff --git a/parameter_files/archive/api36.0.0_051724_patch_params.xml b/parameter_files/archive/api36.0.0_051724_patch_params.xml
new file mode 100644
index 0000000000..3af518bde2
--- /dev/null
+++ b/parameter_files/archive/api36.0.0_051724_patch_params.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ archive/api36.0.0_051724_params_default.xml
+ fates_params_default.cdl
+ 1,2,3,4,5,6,7,8,9,10,11,12
+
+
+ fates_landuse_harvest_pprod10
+ fates_pft
+ fraction
+ fraction of harvest wood product that goes to 10-year product pool (remainder goes to 100-year pool)
+ 1, 0.75, 0.75, 0.75, 1, 0.75, 1, 1, 1, 1, 1, 1
+
+
+ fates_landuse_luc_frac_burned
+ fates_pft
+ fraction
+ fraction of land use change-generated and not-exported material that is burned (the remainder goes to litter)
+ 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5
+
+
+ fates_landuse_luc_frac_exported
+ fates_pft
+ fraction
+ fraction of land use change-generated wood material that is exported to wood product (the remainder is either burned or goes to litter)
+ 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.2, 0.2, 0.2, 0, 0, 0
+
+
+ fates_landuse_luc_pprod10
+ fates_pft
+ fraction
+ fraction of land use change wood product that goes to 10-year product pool (remainder goes to 100-year pool)
+ 1, 0.75, 0.75, 0.75, 1, 0.75, 1, 1, 1, 1, 1, 1
+
+
+ fates_landuse_crop_lu_pft_vector
+ fates_landuseclass
+ NA
+ the FATES PFT index to use on a given crop land-use type (dummy value of -999 for non-crop types)
+ -999, -999, -999, -999, 11
+
+
+ fates_max_nocomp_pfts_by_landuse
+ fates_landuseclass
+ count
+ maximum number of nocomp PFTs on each land use type (only used in nocomp mode)
+ 4, 4, 1, 1, 1
+
+
+ fates_landuse_pprodharv10_forest_mean
+
+
+
diff --git a/parameter_files/fates_params_default.cdl b/parameter_files/fates_params_default.cdl
index 1d255a4aec..b66336bbf2 100644
--- a/parameter_files/fates_params_default.cdl
+++ b/parameter_files/fates_params_default.cdl
@@ -348,6 +348,18 @@ variables:
double fates_hydro_vg_n_node(fates_hydr_organs, fates_pft) ;
fates_hydro_vg_n_node:units = "unitless" ;
fates_hydro_vg_n_node:long_name = "(used if hydr_htftype_node = 2),n in van Genuchten 1980 model, pore size distribution parameter" ;
+ double fates_landuse_harvest_pprod10(fates_pft) ;
+ fates_landuse_harvest_pprod10:units = "fraction" ;
+ fates_landuse_harvest_pprod10:long_name = "fraction of harvest wood product that goes to 10-year product pool (remainder goes to 100-year pool)" ;
+ double fates_landuse_luc_frac_burned(fates_pft) ;
+ fates_landuse_luc_frac_burned:units = "fraction" ;
+ fates_landuse_luc_frac_burned:long_name = "fraction of land use change-generated and not-exported material that is burned (the remainder goes to litter)" ;
+ double fates_landuse_luc_frac_exported(fates_pft) ;
+ fates_landuse_luc_frac_exported:units = "fraction" ;
+ fates_landuse_luc_frac_exported:long_name = "fraction of land use change-generated wood material that is exported to wood product (the remainder is either burned or goes to litter)" ;
+ double fates_landuse_luc_pprod10(fates_pft) ;
+ fates_landuse_luc_pprod10:units = "fraction" ;
+ fates_landuse_luc_pprod10:long_name = "fraction of land use change wood product that goes to 10-year product pool (remainder goes to 100-year pool)" ;
double fates_leaf_c3psn(fates_pft) ;
fates_leaf_c3psn:units = "flag" ;
fates_leaf_c3psn:long_name = "Photosynthetic pathway (1=c3, 0=c4)" ;
@@ -690,6 +702,12 @@ variables:
double fates_frag_cwd_frac(fates_NCWD) ;
fates_frag_cwd_frac:units = "fraction" ;
fates_frag_cwd_frac:long_name = "fraction of woody (bdead+bsw) biomass destined for CWD pool" ;
+ double fates_landuse_crop_lu_pft_vector(fates_landuseclass) ;
+ fates_landuse_crop_lu_pft_vector:units = "NA" ;
+ fates_landuse_crop_lu_pft_vector:long_name = "the FATES PFT index to use on a given crop land-use type (dummy value of -999 for non-crop types)" ;
+ double fates_max_nocomp_pfts_by_landuse(fates_landuseclass) ;
+ fates_max_nocomp_pfts_by_landuse:units = "count" ;
+ fates_max_nocomp_pfts_by_landuse:long_name = "maximum number of nocomp PFTs on each land use type (only used in nocomp mode)" ;
double fates_maxpatches_by_landuse(fates_landuseclass) ;
fates_maxpatches_by_landuse:units = "count" ;
fates_maxpatches_by_landuse:long_name = "maximum number of patches per site on each land use type" ;
@@ -732,15 +750,9 @@ variables:
double fates_fire_durat_slope ;
fates_fire_durat_slope:units = "NA" ;
fates_fire_durat_slope:long_name = "spitfire parameter, fire max duration slope, Equation 14 Thonicke et al 2010" ;
- double fates_fire_fdi_a ;
- fates_fire_fdi_a:units = "NA" ;
- fates_fire_fdi_a:long_name = "spitfire parameter, fire danger index, EQ 5 Thonicke et al 2010" ;
double fates_fire_fdi_alpha ;
fates_fire_fdi_alpha:units = "NA" ;
fates_fire_fdi_alpha:long_name = "spitfire parameter, EQ 7 Venevsky et al. GCB 2002,(modified EQ 8 Thonicke et al. 2010) " ;
- double fates_fire_fdi_b ;
- fates_fire_fdi_b:units = "NA" ;
- fates_fire_fdi_b:long_name = "spitfire parameter, fire danger index, EQ 5 Thonicke et al 2010 " ;
double fates_fire_fuel_energy ;
fates_fire_fuel_energy:units = "kJ/kg" ;
fates_fire_fuel_energy:long_name = "spitfire parameter, heat content of fuel" ;
@@ -810,9 +822,6 @@ variables:
double fates_landuse_logging_mechanical_frac ;
fates_landuse_logging_mechanical_frac:units = "fraction" ;
fates_landuse_logging_mechanical_frac:long_name = "Fraction of stems killed due infrastructure an other mechanical means" ;
- double fates_landuse_pprodharv10_forest_mean ;
- fates_landuse_pprodharv10_forest_mean:units = "fraction" ;
- fates_landuse_pprodharv10_forest_mean:long_name = "mean harvest mortality proportion of deadstem to 10-yr product (pprodharv10) of all woody PFT types" ;
double fates_leaf_photo_temp_acclim_thome_time ;
fates_leaf_photo_temp_acclim_thome_time:units = "years" ;
fates_leaf_photo_temp_acclim_thome_time:long_name = "Length of the window for the long-term (i.e. T_home in Kumarathunge et al 2019) exponential moving average (ema) of vegetation temperature used in photosynthesis temperature acclimation (used if fates_leaf_photo_tempsens_model = 2)" ;
@@ -983,55 +992,55 @@ data:
0.8, 0.8, 0.8, 0.8 ;
fates_allom_agb1 = 0.0673, 0.1364012, 0.0393057, 0.2653695, 0.0673,
- 0.0728698, 0.06896, 0.06896, 0.06896, 0.001, 0.001, 0.003 ;
+ 0.0728698, 0.06896, 0.06896, 0.06896, 0.01, 0.01, 0.01 ;
fates_allom_agb2 = 0.976, 0.9449041, 1.087335, 0.8321321, 0.976, 1.0373211,
- 0.572, 0.572, 0.572, 1.6592, 1.6592, 1.3456 ;
+ 0.572, 0.572, 0.572, 0.572, 0.572, 0.572 ;
fates_allom_agb3 = 1.94, 1.94, 1.94, 1.94, 1.94, 1.94, 1.94, 1.94, 1.94,
- 1.248, 1.248, 1.869 ;
+ 1.94, 1.94, 1.94 ;
fates_allom_agb4 = 0.931, 0.931, 0.931, 0.931, 0.931, 0.931, 0.931, 0.931,
- 0.931, -999.9, -999.9, -999.9 ;
+ 0.931, 0.931, 0.931, 0.931 ;
- fates_allom_agb_frac = 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 1,
- 1, 1 ;
+ fates_allom_agb_frac = 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6,
+ 0.6, 0.6 ;
- fates_allom_amode = 3, 3, 3, 3, 3, 3, 1, 1, 1, 5, 5, 5 ;
+ fates_allom_amode = 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1 ;
fates_allom_blca_expnt_diff = -0.12, -0.34, -0.32, -0.22, -0.12, -0.35, 0,
- 0, 0, -0.487, -0.487, -0.259 ;
+ 0, 0, 0, 0, 0 ;
fates_allom_cmode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
fates_allom_d2bl1 = 0.04, 0.07, 0.07, 0.01, 0.04, 0.07, 0.07, 0.07, 0.07,
- 0.0004, 0.0004, 0.0012 ;
+ 0.07, 0.07, 0.07 ;
fates_allom_d2bl2 = 1.6019679, 1.5234373, 1.3051237, 1.9621397, 1.6019679,
- 1.3998939, 1.3, 1.3, 1.3, 1.7092, 1.7092, 1.5879 ;
+ 1.3998939, 1.3, 1.3, 1.3, 1.3, 1.3, 1.3 ;
fates_allom_d2bl3 = 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55,
- 0.3417, 0.3417, 0.9948 ;
+ 0.55, 0.55, 0.55 ;
fates_allom_d2ca_coefficient_max = 0.2715891, 0.3693718, 1.0787259,
0.0579297, 0.2715891, 1.1553612, 0.6568464, 0.6568464, 0.6568464,
- 0.0408, 0.0408, 0.0862 ;
+ 0.6568464, 0.6568464, 0.6568464 ;
fates_allom_d2ca_coefficient_min = 0.2715891, 0.3693718, 1.0787259,
0.0579297, 0.2715891, 1.1553612, 0.6568464, 0.6568464, 0.6568464,
- 0.6568464, 0.0408, 0.0862 ;
+ 0.6568464, 0.6568464, 0.6568464 ;
fates_allom_d2h1 = 78.4087704, 306.842667, 106.8745821, 104.3586841,
- 78.4087704, 31.4557047, 0.64, 0.64, 0.64, 0.1812, 0.1812, 0.3353 ;
+ 78.4087704, 31.4557047, 0.64, 0.64, 0.64, 0.64, 0.64, 0.64 ;
fates_allom_d2h2 = 0.8124383, 0.752377, 0.9471302, 1.1146973, 0.8124383,
- 0.9734088, 0.37, 0.37, 0.37, 0.6384, 0.6384, 0.4235 ;
+ 0.9734088, 0.37, 0.37, 0.37, 0.37, 0.37, 0.37 ;
fates_allom_d2h3 = 47.6666164, 196.6865691, 93.9790461, 160.6835089,
47.6666164, 16.5928174, -999.9, -999.9, -999.9, -999.9, -999.9, -999.9 ;
fates_allom_dbh_maxheight = 1000, 1000, 1000, 1000, 1000, 1000, 3, 3, 2,
- 20, 20, 30 ;
+ 0.35, 0.35, 0.35 ;
fates_allom_dmode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
@@ -1049,21 +1058,21 @@ data:
fates_allom_h2cd2 = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
- fates_allom_hmode = 5, 5, 5, 5, 5, 5, 1, 1, 1, 3, 3, 3 ;
+ fates_allom_hmode = 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1 ;
- fates_allom_l2fr = 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.67, 0.67, 1.41 ;
+ fates_allom_l2fr = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
fates_allom_la_per_sa_int = 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8,
0.8, 0.8, 0.8 ;
fates_allom_la_per_sa_slp = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ;
- fates_allom_lmode = 2, 2, 2, 2, 2, 2, 1, 1, 1, 5, 5, 5 ;
+ fates_allom_lmode = 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1 ;
fates_allom_sai_scaler = 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
0.1, 0.1 ;
- fates_allom_smode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2 ;
+ fates_allom_smode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
fates_allom_stmode = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ;
@@ -1294,6 +1303,16 @@ data:
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 ;
+ fates_landuse_harvest_pprod10 = 1, 0.75, 0.75, 0.75, 1, 0.75, 1, 1, 1, 1, 1, 1 ;
+
+ fates_landuse_luc_frac_burned = 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
+ 0.5, 0.5, 0.5 ;
+
+ fates_landuse_luc_frac_exported = 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.2, 0.2,
+ 0.2, 0, 0, 0 ;
+
+ fates_landuse_luc_pprod10 = 1, 0.75, 0.75, 0.75, 1, 0.75, 1, 1, 1, 1, 1, 1 ;
+
fates_leaf_c3psn = 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 ;
fates_leaf_jmaxha = 43540, 43540, 43540, 43540, 43540, 43540, 43540, 43540,
@@ -1306,10 +1325,10 @@ data:
495 ;
fates_leaf_slamax = 0.0954, 0.0954, 0.0954, 0.0954, 0.0954, 0.0954, 0.012,
- 0.03, 0.03, 0.05, 0.05, 0.05 ;
+ 0.03, 0.03, 0.03, 0.03, 0.03 ;
fates_leaf_slatop = 0.012, 0.005, 0.024, 0.009, 0.03, 0.03, 0.012, 0.03,
- 0.03, 0.05, 0.05, 0.05 ;
+ 0.03, 0.03, 0.03, 0.03 ;
fates_leaf_stomatal_intercept = 10000, 10000, 10000, 10000, 10000, 10000,
10000, 10000, 10000, 10000, 10000, 40000 ;
@@ -1463,7 +1482,7 @@ data:
0.001, 0.001, 0.12, 0.12, 0.12 ;
fates_recruit_height_min = 1.3, 1.3, 1.3, 1.3, 1.3, 1.3, 0.2, 0.2, 0.2,
- 0.2, 0.2, 0.2 ;
+ 0.125, 0.125, 0.125 ;
fates_recruit_init_density = 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2,
0.2, 0.2, 0.2 ;
@@ -1471,14 +1490,14 @@ data:
fates_recruit_prescribed_rate = 0.02, 0.02, 0.02, 0.02, 0.02, 0.02, 0.02,
0.02, 0.02, 0.02, 0.02, 0.02 ;
- fates_recruit_seed_alloc = 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0,
- 0, 0 ;
+ fates_recruit_seed_alloc = 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,
+ 0.1, 0.1 ;
- fates_recruit_seed_alloc_mature = 0, 0, 0, 0, 0, 0, 0.9, 0.9, 0.9, 0.25, 0.25,
- 0.2 ;
+ fates_recruit_seed_alloc_mature = 0, 0, 0, 0, 0, 0, 0.9, 0.9, 0.9, 0.9, 0.9,
+ 0.9 ;
fates_recruit_seed_dbh_repro_threshold = 90, 80, 80, 80, 90, 80, 3, 3, 2,
- 3, 3, 3 ;
+ 0.35, 0.35, 0.35 ;
fates_recruit_seed_germination_rate = 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
0.5, 0.5, 0.5, 0.5, 0.5 ;
@@ -1638,6 +1657,10 @@ data:
fates_frag_cwd_frac = 0.045, 0.075, 0.21, 0.67 ;
+ fates_landuse_crop_lu_pft_vector = -999, -999, -999, -999, 11 ;
+
+ fates_max_nocomp_pfts_by_landuse = 4, 4, 1, 1, 1 ;
+
fates_maxpatches_by_landuse = 9, 4, 1, 1, 1 ;
fates_canopy_closure_thresh = 0.8 ;
@@ -1666,12 +1689,8 @@ data:
fates_fire_durat_slope = -11.06 ;
- fates_fire_fdi_a = 17.62 ;
-
fates_fire_fdi_alpha = 0.00037 ;
- fates_fire_fdi_b = 243.12 ;
-
fates_fire_fuel_energy = 18000 ;
fates_fire_max_durat = 240 ;
@@ -1718,8 +1737,6 @@ data:
fates_landuse_logging_mechanical_frac = 0.05 ;
- fates_landuse_pprodharv10_forest_mean = 0.8125 ;
-
fates_leaf_photo_temp_acclim_thome_time = 30 ;
fates_leaf_photo_temp_acclim_timescale = 30 ;
diff --git a/parteh/CMakeLists.txt b/parteh/CMakeLists.txt
new file mode 100644
index 0000000000..67c8a175a2
--- /dev/null
+++ b/parteh/CMakeLists.txt
@@ -0,0 +1,8 @@
+list(APPEND fates_sources
+ PRTParametersMod.F90
+ PRTParamsFATESMod.F90
+ PRTGenericMod.F90
+ PRTAllometricCarbonMod.F90
+ PRTAllometricCNPMod.F90)
+
+sourcelist_to_parent(fates_sources)
\ No newline at end of file
diff --git a/radiation/CMakeLists.txt b/radiation/CMakeLists.txt
new file mode 100644
index 0000000000..74d625b12d
--- /dev/null
+++ b/radiation/CMakeLists.txt
@@ -0,0 +1,7 @@
+# This file is required for unit testing, but is not used for production runs
+list(APPEND fates_sources
+ TwoStreamMLPEMod.F90
+ FatesRadiationMemMod.F90
+ )
+
+sourcelist_to_parent(fates_sources)
diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt
new file mode 100644
index 0000000000..d59509c73d
--- /dev/null
+++ b/testing/CMakeLists.txt
@@ -0,0 +1,8 @@
+# This is where you add specific test directories
+
+## Functional tests
+add_subdirectory(functional_testing/allometry fates_allom_ftest)
+add_subdirectory(functional_testing/math_utils fates_math_ftest)
+
+## Unit tests
+add_subdirectory(unit_testing/fire_weather_test fates_fire_weather_utest)
\ No newline at end of file
diff --git a/testing/build_fortran_tests.py b/testing/build_fortran_tests.py
new file mode 100644
index 0000000000..b495f25020
--- /dev/null
+++ b/testing/build_fortran_tests.py
@@ -0,0 +1,249 @@
+"""
+Builds/compiles any tests within the FATES repository
+"""
+import os
+import shutil
+from utils import add_cime_lib_to_path
+
+add_cime_lib_to_path()
+
+from CIME.utils import get_src_root, run_cmd_no_fail, expect, stringify_bool # pylint: disable=wrong-import-position,import-error,wrong-import-order
+from CIME.build import CmakeTmpBuildDir # pylint: disable=wrong-import-position,import-error,wrong-import-order
+from CIME.XML.machines import Machines # pylint: disable=wrong-import-position,import-error,wrong-import-order
+from CIME.BuildTools.configure import configure, FakeCase # pylint: disable=wrong-import-position,import-error,wrong-import-order
+from CIME.XML.env_mach_specific import EnvMachSpecific # pylint: disable=wrong-import-position,import-error,wrong-import-order
+
+_CIMEROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../cime")
+
+def run_cmake(test_name, test_dir, pfunit_path, netcdf_c_path, netcdf_f_path, cmake_args):
+ """Run cmake for the fortran unit tests
+ Arguments:
+ test_name (str) - name for output messages
+ test_dir (str) - directory to run Cmake in
+ pfunit_path (str) - path to pfunit
+ netcdf_c_path (str) - path to netcdf
+ netcdf_f_path (str) - path to netcdff
+ clean (bool) - clean the build
+ """
+ if not os.path.isfile("CMakeCache.txt"):
+ print(f"Running cmake for {test_name}.")
+
+ # directory with cmake modules
+ cmake_module_dir = os.path.abspath(os.path.join(_CIMEROOT, "CIME", "non_py",
+ "src", "CMake"))
+
+ # directory with genf90
+ genf90_dir = os.path.join(_CIMEROOT, "CIME", "non_py", "externals", "genf90")
+
+ cmake_command = [
+ "cmake",
+ "-C Macros.cmake",
+ test_dir,
+ f"-DCIMEROOT={_CIMEROOT}",
+ f"-DSRC_ROOT={get_src_root()}",
+ f"-DCIME_CMAKE_MODULE_DIRECTORY={cmake_module_dir}",
+ "-DCMAKE_BUILD_TYPE=CESM_DEBUG",
+ f"-DCMAKE_PREFIX_PATH={pfunit_path}",
+ "-DUSE_MPI_SERIAL=ON",
+ "-DENABLE_GENF90=ON",
+ f"-DCMAKE_PROGRAM_PATH={genf90_dir}"
+ ]
+
+ if netcdf_c_path is not None:
+ cmake_command.append(f"-DNETCDF_C_PATH={netcdf_c_path}")
+
+ if netcdf_f_path is not None:
+ cmake_command.append(f"-DNETCDF_F_PATH={netcdf_f_path}")
+
+ cmake_command.extend(cmake_args.split(" "))
+
+ run_cmd_no_fail(" ".join(cmake_command), combine_output=True)
+
+def find_library(caseroot, cmake_args, lib_string):
+ """Find the library installation we'll be using, and print its path
+
+ Args:
+ caseroot (str): Directory with pfunit macros
+ cmake_args (str): The cmake args used to invoke cmake
+ (so that we get the correct makefile vars)
+ """
+ with CmakeTmpBuildDir(macroloc=caseroot) as cmaketmp:
+ all_vars = cmaketmp.get_makefile_vars(cmake_args=cmake_args)
+
+ all_vars_list = all_vars.splitlines()
+ for all_var in all_vars_list:
+ if ":=" in all_var:
+ expect(all_var.count(":=") == 1, f"Bad makefile: {all_var}")
+ varname, value = [item.strip() for item in all_var.split(":=")]
+ if varname == lib_string:
+ return value
+
+ expect(False, f"{lib_string} not found for this machine and compiler")
+
+ return None
+
+def prep_build_dir(build_dir, clean):
+ """Create (if necessary) build directory and clean contents (if asked to)
+
+ Args:
+ build_dir (str): build directory name
+ clean (bool): whether or not to clean contents
+ """
+
+ # create the build directory
+ build_dir_path = os.path.abspath(build_dir)
+ if not os.path.isdir(build_dir_path):
+ os.mkdir(build_dir_path)
+
+ # change into that directory
+ os.chdir(build_dir_path)
+
+ # clean up any files if we want to
+ if clean:
+ clean_cmake_files()
+
+ return build_dir_path
+
+def clean_cmake_files():
+ """Deletes all files related to build
+
+ """
+ if os.path.isfile("CMakeCache.txt"):
+ os.remove("CMakeCache.txt")
+ if os.path.isdir("CMakeFiles"):
+ shutil.rmtree("CMakeFiles")
+
+ cwd_contents = os.listdir(os.getcwd())
+
+ # Clear contents to do with cmake cache
+ for file in cwd_contents:
+ if (
+ file in ("Macros.cmake", "env_mach_specific.xml")
+ or file.startswith("Depends")
+ or file.startswith(".env_mach_specific")
+ ):
+ os.remove(file)
+
+def get_extra_cmake_args(build_dir, mpilib):
+ """Makes a fake case to grab the required cmake arguments
+ Args:
+ build_dir (str): build directory name
+ mpilib (str): MPI library name
+ """
+ # get the machine objects file
+ machobj = Machines() # this is different?
+
+ # get compiler
+ compiler = machobj.get_default_compiler()
+
+ # get operating system
+ os_ = machobj.get_value("OS")
+
+ # Create the environment, and the Macros.cmake file
+ #
+ #
+ configure(
+ machobj,
+ build_dir,
+ ["CMake"],
+ compiler,
+ mpilib,
+ True,
+ "nuopc",
+ os_,
+ unit_testing=True,
+ )
+ machspecific = EnvMachSpecific(build_dir, unit_testing=True)
+
+ # make a fake case
+ fake_case = FakeCase(compiler, mpilib, True, "nuopc", threading=False)
+ machspecific.load_env(fake_case)
+ cmake_args_list = [
+ f"-DOS={os_}",
+ f"-DMACH={machobj.get_machine_name()}",
+ f"-DCOMPILER={compiler}",
+ f"-DDEBUG={stringify_bool(True)}",
+ f"-DMPILIB={mpilib}",
+ f"-Dcompile_threaded={stringify_bool(False)}",
+ f"-DCASEROOT={build_dir}"
+ ]
+
+ cmake_args = " ".join(cmake_args_list)
+
+ return cmake_args
+
+def run_make(name, make_j, clean=False, verbose=False):
+ """Run make in current working directory
+
+ Args:
+ name (str): Name for output messages
+ make_j (int): number of processes to use for make
+ clean (bool, optional): whether or not to clean Defaults to False.
+ verbose (bool, optional): verbose error logging for make Defaults to False.
+ """
+
+ print(f"Running make for {name}.")
+
+ if clean:
+ run_cmd_no_fail("make clean")
+
+ make_command = ["make", "-j", str(make_j)]
+
+ if verbose:
+ make_command.append("VERBOSE=1")
+
+ run_cmd_no_fail(" ".join(make_command), combine_output=True)
+
+def build_exists(build_dir, test_dir, test_exe=None):
+ """Checks to see if the build directory and associated executables exist.
+
+ Args:
+ build_dir (str): build directory
+ test_dir (str): test directory
+ test_exe (str): test executable
+ """
+
+ build_path = os.path.abspath(build_dir)
+ if not os.path.isdir(build_path):
+ return False
+
+ if not os.path.isdir(os.path.join(build_path, test_dir)):
+ return False
+
+ if test_exe is not None:
+ if not os.path.isfile(os.path.join(build_path, test_dir, test_exe)):
+ return False
+
+ return True
+
+def build_unit_tests(build_dir, name, cmake_directory, make_j, clean=False, verbose=False):
+ """Build the unit test executables
+
+ Args:
+ build_dir (str): build directory
+ name (str): name for set of tests
+ cmake_directory (str): directory where the make CMakeLists.txt file is
+ make_j (int): number of processes to use for make
+ clean (bool, optional): whether or not to clean the build first. Defaults to False.
+ verbose (bool, optional): whether or not to run make with verbose output. Defaults to False.
+ """
+ # create the build directory
+ full_build_path = prep_build_dir(build_dir, clean=clean)
+
+ # get cmake args and the pfunit and netcdf paths
+ cmake_args = get_extra_cmake_args(full_build_path, "mpi-serial")
+ pfunit_path = find_library(full_build_path, cmake_args, "PFUNIT_PATH")
+
+ if not "NETCDF" in os.environ:
+ netcdf_c_path = find_library(full_build_path, cmake_args, "NETCDF_C_PATH")
+ netcdf_f_path = find_library(full_build_path, cmake_args, "NETCDF_FORTRAN_PATH")
+ else:
+ netcdf_c_path = None
+ netcdf_f_path = None
+
+ # change into the build dir
+ os.chdir(full_build_path)
+
+ # run cmake and make
+ run_cmake(name, cmake_directory, pfunit_path, netcdf_c_path, netcdf_f_path, cmake_args)
+ run_make(name, make_j, clean=clean, verbose=verbose)
diff --git a/testing/functional_testing/allometry/CMakeLists.txt b/testing/functional_testing/allometry/CMakeLists.txt
new file mode 100644
index 0000000000..c2c453e93a
--- /dev/null
+++ b/testing/functional_testing/allometry/CMakeLists.txt
@@ -0,0 +1,24 @@
+set(allom_sources FatesTestAllometry.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_allom_exe ${allom_sources})
+
+target_link_libraries(FATES_allom_exe
+ netcdf
+ netcdff
+ fates
+ csm_share
+ funit)
\ No newline at end of file
diff --git a/testing/functional_testing/allometry/FatesTestAllometry.F90 b/testing/functional_testing/allometry/FatesTestAllometry.F90
new file mode 100644
index 0000000000..91ee4df2cf
--- /dev/null
+++ b/testing/functional_testing/allometry/FatesTestAllometry.F90
@@ -0,0 +1,313 @@
+program FatesTestAllometry
+
+ use FatesConstantsMod, only : r8 => fates_r8
+ use FatesAllometryMod, only : h_allom, bagw_allom, blmax_allom
+ use FatesAllometryMod, only : carea_allom, bsap_allom, bbgw_allom
+ use FatesAllometryMod, only : bfineroot, bstore_allom, bdead_allom
+ use PRTParametersMod, only : prt_params
+ use FatesUnitTestParamReaderMod, only : fates_unit_test_param_reader
+
+ implicit none
+
+ ! LOCALS:
+ type(fates_unit_test_param_reader) :: param_reader ! param reader instance
+ character(len=:), allocatable :: param_file ! input parameter file
+ 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]
+ real(r8), allocatable :: blmax(:, :) ! plant leaf biomass [kgC]
+ real(r8), allocatable :: crown_area(:, :) ! crown area per cohort [m2]
+ real(r8), allocatable :: sapwood_area(:, :) ! cross sectional area of sapwood at reference height [m2]
+ real(r8), allocatable :: bsap(:, :) ! sapwood biomass [kgC]
+ real(r8), allocatable :: bbgw(:, :) ! belowground woody biomass [kgC]
+ real(r8), allocatable :: fineroot_biomass(:, :) ! belowground fineroot biomass [kgC]
+ real(r8), allocatable :: bstore(:, :) ! allometric target storage biomass [kgC]
+ real(r8), allocatable :: bdead(:, :) ! structural biomass (heartwood/struct) [kgC]
+ real(r8), allocatable :: total_biom_tissues(:,:) ! total biomass calculated as bleaf + bfineroot + bdead + bsap [kgC]
+ real(r8), allocatable :: total_biom_parts(:,:) ! total biomass calculated as bleaf + bfineroot + agbw + bgbw [kgC]
+
+ ! CONSTANTS:
+ character(len=*), parameter :: out_file = 'allometry_out.nc' ! output file
+ real(r8), parameter :: min_dbh = 0.5_r8 ! minimum DBH to calculate [cm]
+ real(r8), parameter :: max_dbh = 200.0_r8 ! maximum DBH to calculate [cm]
+ real(r8), parameter :: dbh_inc = 0.5_r8 ! DBH increment to use [cm]
+
+ integer, parameter :: crown_damage = 1 ! crown damage
+ real(r8), parameter :: elongation_factor = 1.0_r8 ! elongation factor for stem
+ real(r8), parameter :: elongation_factor_roots = 1.0_r8 ! elongation factor for roots
+ real(r8), parameter :: site_spread = 1.0_r8 ! site spread
+ real(r8), parameter :: canopy_trim = 1.0_r8 ! canopy trim
+ real(r8), parameter :: nplant = 1.0_r8 ! number of plants per cohort
+ real(r8), parameter :: leaf_to_fineroot = 1.0_r8 ! leaf to fineroot ratio
+
+ interface
+
+ subroutine WriteAllometryData(out_file, ndbh, numpft, dbh, height, bagw, blmax, &
+ crown_area, sapwood_area, bsap, bbgw, fineroot_biomass, bstore, bdead, &
+ total_biom_parts, total_biom_tissues)
+
+ use FatesUnitTestIOMod, only : OpenNCFile, RegisterNCDims, CloseNCFile
+ use FatesUnitTestIOMod, only : WriteVar, RegisterVar
+ use FatesUnitTestIOMod, only : type_double, type_int
+ use FatesConstantsMod, only : r8 => fates_r8
+ implicit none
+
+ character(len=*), intent(in) :: out_file
+ integer, intent(in) :: ndbh, numpft
+ real(r8), intent(in) :: dbh(:)
+ real(r8), intent(in) :: height(:,:)
+ real(r8), intent(in) :: bagw(:,:)
+ real(r8), intent(in) :: blmax(:, :)
+ real(r8), intent(in) :: crown_area(:, :)
+ real(r8), intent(in) :: sapwood_area(:, :)
+ real(r8), intent(in) :: bsap(:, :)
+ real(r8), intent(in) :: bbgw(:, :)
+ real(r8), intent(in) :: fineroot_biomass(:, :)
+ real(r8), intent(in) :: bstore(:, :)
+ real(r8), intent(in) :: bdead(:, :)
+ real(r8), intent(in) :: total_biom_parts(:, :)
+ real(r8), intent(in) :: total_biom_tissues(:, :)
+ 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
+ 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)
+
+ ! allocate arrays
+ allocate(dbh(numdbh))
+ allocate(height(numdbh, numpft))
+ allocate(bagw(numdbh, numpft))
+ allocate(blmax(numdbh, numpft))
+ allocate(crown_area(numdbh, numpft))
+ allocate(sapwood_area(numdbh, numpft))
+ allocate(bsap(numdbh, numpft))
+ allocate(bbgw(numdbh, numpft))
+ allocate(fineroot_biomass(numdbh, numpft))
+ allocate(bstore(numdbh, numpft))
+ allocate(bdead(numdbh, numpft))
+ allocate(total_biom_parts(numdbh, numpft))
+ allocate(total_biom_tissues(numdbh, numpft))
+
+ ! initialize dbh array
+ do i = 1, numdbh
+ dbh(i) = min_dbh + dbh_inc*(i-1)
+ end do
+
+ ! calculate allometries
+ do i = 1, numpft
+ do j = 1, numdbh
+ call h_allom(dbh(j), i, height(j, i))
+ call bagw_allom(dbh(j), i, crown_damage, elongation_factor, bagw(j, i))
+ call blmax_allom(dbh(j), i, blmax(j, i))
+ call carea_allom(dbh(j), nplant, site_spread, i, crown_damage, crown_area(j, i))
+ call bsap_allom(dbh(j), i, crown_damage, canopy_trim, elongation_factor, &
+ sapwood_area(j, i), bsap(j, i))
+ call bbgw_allom(dbh(j), i, elongation_factor, bbgw(j, i))
+ call bfineroot(dbh(j), i, canopy_trim, leaf_to_fineroot, elongation_factor_roots, &
+ fineroot_biomass(j, i))
+ call bstore_allom(dbh(j), i, crown_damage, canopy_trim, bstore(j, i))
+ call bdead_allom(bagw(j, i), bbgw(j, i), bsap(j, i), i, bdead(j, i))
+ total_biom_parts(j, i) = blmax(j, i) + fineroot_biomass(j, i) + bagw(j, i) + bbgw(j, i)
+ total_biom_tissues(j, i) = blmax(j, i) + fineroot_biomass(j, i) + bdead(j, i) + bsap(j, i)
+ end do
+ end do
+
+ ! write out data to netcdf file
+ call WriteAllometryData(out_file, numdbh, numpft, dbh, height, bagw, blmax, crown_area, &
+ sapwood_area, bsap, bbgw, fineroot_biomass, bstore, bdead, total_biom_parts, &
+ total_biom_tissues)
+
+end program FatesTestAllometry
+
+! ----------------------------------------------------------------------------------------
+
+subroutine WriteAllometryData(out_file, numdbh, numpft, dbh, height, bagw, blmax, &
+ crown_area, sapwood_area, bsap, bbgw, fineroot_biomass, bstore, bdead, total_biom_parts, &
+ total_biom_tissues)
+ !
+ ! DESCRIPTION:
+ ! Writes out data from the allometry test
+ !
+ use FatesConstantsMod, only : r8 => fates_r8
+ use FatesUnitTestIOMod, only : OpenNCFile, RegisterNCDims, CloseNCFile
+ use FatesUnitTestIOMod, only : WriteVar
+ use FatesUnitTestIOMod, only : RegisterVar
+ use FatesUnitTestIOMod, only : EndNCDef
+ use FatesUnitTestIOMod, only : type_double, type_int
+
+ implicit none
+
+ ! ARGUMENTS:
+ character(len=*), intent(in) :: out_file ! output file name
+ integer, intent(in) :: numdbh ! size of dbh array
+ integer, intent(in) :: numpft ! number of pfts
+ real(r8), intent(in) :: dbh(:) ! diameter at breast height [cm]
+ real(r8), intent(in) :: height(:,:) ! height [m]
+ real(r8), intent(in) :: bagw(:,:) ! aboveground biomass [kgC]
+ real(r8), intent(in) :: blmax(:, :) ! leaf biomass [kgC]
+ real(r8), intent(in) :: crown_area(:, :) ! crown area [m2]
+ real(r8), intent(in) :: sapwood_area(:, :) ! sapwood cross-sectional area [m2]
+ real(r8), intent(in) :: bsap(:, :) ! sapwood biomass [kgC]
+ real(r8), intent(in) :: bbgw(:, :) ! belowground biomass [kgC]
+ real(r8), intent(in) :: fineroot_biomass(:, :) ! fineroot biomass [kgC]
+ real(r8), intent(in) :: bstore(:, :) ! storage biomass [kgC]
+ real(r8), intent(in) :: bdead(:, :) ! deadwood biomass [kgC]
+ real(r8), intent(in) :: total_biom_parts(:, :) ! total biomass calculated from parts [kgC]
+ real(r8), intent(in) :: total_biom_tissues(:, :) ! total biomass calculated from tissues [kgC]
+
+ ! LOCALS:
+ integer, allocatable :: pft_indices(:) ! array of pft indices to write out
+ integer :: i ! looping index
+ integer :: ncid ! netcdf file id
+ character(len=8) :: dim_names(2) ! dimension names
+ integer :: dimIDs(2) ! dimension IDs
+ integer :: dbhID, pftID ! variable IDs for dimensions
+ integer :: heightID, bagwID
+ integer :: blmaxID, c_areaID
+ integer :: sapwoodareaID, bsapID
+ integer :: bbgwID, finerootID
+ integer :: bstoreID, bdeadID
+ integer :: totbiomID1, totbiomID2
+
+ ! create pft indices
+ allocate(pft_indices(numpft))
+ do i = 1, numpft
+ pft_indices(i) = i
+ end do
+
+ ! dimension names
+ dim_names = [character(len=12) :: 'dbh', 'pft']
+
+ ! open file
+ call OpenNCFile(trim(out_file), ncid, 'readwrite')
+
+ ! register dimensions
+ call RegisterNCDims(ncid, dim_names, (/numdbh, numpft/), 2, dimIDs)
+
+ ! register dbh
+ call RegisterVar(ncid, dim_names(1), dimIDs(1:1), type_double, &
+ [character(len=20) :: 'units', 'long_name'], &
+ [character(len=150) :: 'cm', 'diameter at breast height'], 2, dbhID)
+
+ ! register pft
+ call RegisterVar(ncid, dim_names(2), dimIDs(2:2), type_int, &
+ [character(len=20) :: 'units', 'long_name'], &
+ [character(len=150) :: '', 'plant functional type'], 2, pftID)
+
+ ! register height
+ call RegisterVar(ncid, 'height', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'm', 'plant height'], &
+ 3, heightID)
+
+ ! register aboveground biomass
+ call RegisterVar(ncid, 'bagw', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant aboveground woody biomass'], &
+ 3, bagwID)
+
+ ! register leaf biomass
+ call RegisterVar(ncid, 'blmax', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant maximum leaf biomass'], &
+ 3, blmaxID)
+
+ ! register crown area
+ call RegisterVar(ncid, 'crown_area', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'm2', 'plant crown area per cohort'], &
+ 3, c_areaID)
+
+ ! register sapwood area
+ call RegisterVar(ncid, 'sapwood_area', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'm2', 'plant cross section area sapwood at reference height'], &
+ 3, sapwoodareaID)
+
+ ! register sapwood biomass
+ call RegisterVar(ncid, 'bsap', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant sapwood biomass'], &
+ 3, bsapID)
+
+ ! register belowground woody biomass
+ call RegisterVar(ncid, 'bbgw', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant belowground woody biomass'], &
+ 3, bbgwID)
+
+ ! register fineroot biomass
+ call RegisterVar(ncid, 'fineroot_biomass', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant fineroot biomass'], &
+ 3, finerootID)
+
+ ! register storage biomass
+ call RegisterVar(ncid, 'bstore', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant storage biomass'], &
+ 3, bstoreID)
+
+ ! register structural biomass
+ call RegisterVar(ncid, 'bdead', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant deadwood (structural/heartwood) biomass'], &
+ 3, bdeadID)
+
+ ! register total biomass (parts)
+ call RegisterVar(ncid, 'total_biomass_parts', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant total biomass calculated from parts'], &
+ 3, totbiomID1)
+
+ ! register total biomass (tissues)
+ call RegisterVar(ncid, 'total_biomass_tissues', dimIDs(1:2), type_double, &
+ [character(len=20) :: 'coordinates', 'units', 'long_name'], &
+ [character(len=150) :: 'pft dbh', 'kgC', 'plant total biomass calculated from tissues'], &
+ 3, totbiomID2)
+
+ ! finish defining variables
+ call EndNCDef(ncid)
+
+ ! write out data
+ call WriteVar(ncid, dbhID, dbh(:))
+ call WriteVar(ncid, pftID, pft_indices(:))
+ call WriteVar(ncid, heightID, height(:,:))
+ call WriteVar(ncid, bagwID, bagw(:,:))
+ call WriteVar(ncid, blmaxID, blmax(:,:))
+ call WriteVar(ncid, c_areaID, crown_area(:,:))
+ call WriteVar(ncid, sapwoodareaID, sapwood_area(:,:))
+ call WriteVar(ncid, bsapID, bsap(:,:))
+ call WriteVar(ncid, bbgwID, bbgw(:,:))
+ call WriteVar(ncid, finerootID, fineroot_biomass(:,:))
+ call WriteVar(ncid, bstoreID, bstore(:,:))
+ call WriteVar(ncid, bdeadID, bdead(:,:))
+ call WriteVar(ncid, totbiomID1, total_biom_parts(:,:))
+ call WriteVar(ncid, totbiomID2, total_biom_tissues(:,:))
+
+ ! close the file
+ call CloseNCFile(ncid)
+
+end subroutine WriteAllometryData
\ No newline at end of file
diff --git a/testing/functional_testing/allometry/allometry_plotting.py b/testing/functional_testing/allometry/allometry_plotting.py
new file mode 100644
index 0000000000..caa6cc1069
--- /dev/null
+++ b/testing/functional_testing/allometry/allometry_plotting.py
@@ -0,0 +1,187 @@
+"""Utility functions for allometry 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
+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
+
+def plot_allometry_var(data, varname, units, save_fig, plot_dir=None):
+ """Plot an allometry variable
+
+ Args:
+ data (xarray DataArray): the data array of the variable to plot
+ var (str): variable name (for data structure)
+ varname (str): variable name for plot labels
+ units (str): variable units for plot labels
+ save_fig (bool): whether or not to write out plot
+ plot_dir (str): if saving figure, where to write to
+ """
+ data_frame = pd.DataFrame({'dbh': np.tile(data.dbh, len(data.pft)),
+ 'pft': np.repeat(data.pft, len(data.dbh)),
+ data.name: data.values.flatten()})
+
+ max_dbh = data_frame['dbh'].max()
+ max_var = round_up(data_frame[data.name].max())
+
+ blank_plot(max_dbh, 0.0, max_var, 0.0, draw_horizontal_lines=True)
+
+ pfts = np.unique(data_frame.pft.values)
+ colors = get_color_palette(len(pfts))
+ for rank, pft in enumerate(pfts):
+ dat = data_frame[data_frame.pft == pft]
+ plt.plot(dat.dbh.values, dat[data.name].values, lw=2, color=colors[rank],
+ label=pft)
+
+ plt.xlabel('DBH (cm)', fontsize=11)
+ plt.ylabel(f'{varname} ({units})', fontsize=11)
+ plt.title(f"Simulated {varname} for input parameter file", fontsize=11)
+ plt.legend(loc='upper left', title='PFT')
+
+ if save_fig:
+ fig_name = os.path.join(plot_dir, f"allometry_plot_{data.name}.png")
+ plt.savefig(fig_name)
+
+def plot_total_biomass(data, save_fig, plot_dir):
+ """Plot two calculations of total biomass against each other
+
+ Args:
+ data (xarray DataSet): the allometry dataset
+ """
+ data_frame = pd.DataFrame({'dbh': np.tile(data.dbh, len(data.pft)),
+ 'pft': np.repeat(data.pft, len(data.dbh)),
+ 'total_biomass_parts': data.total_biomass_parts.values.flatten(),
+ 'total_biomass_tissues': data.total_biomass_tissues.values.flatten()})
+
+ max_biomass = np.maximum(data_frame['total_biomass_parts'].max(),
+ data_frame['total_biomass_tissues'].max())
+
+ blank_plot(max_biomass, 0.0, max_biomass, 0.0, draw_horizontal_lines=False)
+
+ pfts = np.unique(data_frame.pft.values)
+ colors = get_color_palette(len(pfts))
+ for rank, pft in enumerate(pfts):
+ data = data_frame[data_frame.pft == pft]
+ plt.scatter(data.total_biomass_parts.values, data.total_biomass_parts.values,
+ color=colors[rank], label=pft)
+
+ plt.xlabel('Total biomass (kgC) from parts', fontsize=11)
+ plt.ylabel('Total biomass (kgC) from tissues', fontsize=11)
+ plt.title("Simulated total biomass for input parameter file", fontsize=11)
+ plt.legend(loc='upper left', title='PFT')
+
+ if save_fig:
+ fig_name = os.path.join(plot_dir, "allometry_plot_total_biomass_compare.png")
+ plt.savefig(fig_name)
+
+def plot_allometry_dat(run_dir, out_file, save_figs, plot_dir):
+ """Plots all allometry plots
+
+ Args:
+ run_dir (str): run directory
+ out_file (str): output file name
+ save_figs (bool): whether or not to save the figures
+ plot_dir (str): plot directory to save the figures to
+ """
+
+ # read in allometry data
+ allometry_dat = xr.open_dataset(os.path.join(run_dir, out_file))
+
+ plot_dict = {
+ 'height': {
+ 'varname': 'height',
+ 'units': 'm',
+ },
+ 'bagw': {
+ 'varname': 'aboveground biomass',
+ 'units': 'kgC',
+ },
+ 'blmax': {
+ 'varname': 'maximum leaf biomass',
+ 'units': 'kgC',
+ },
+ 'crown_area': {
+ 'varname': 'crown area',
+ 'units': 'm$^2$',
+ },
+ 'sapwood_area': {
+ 'varname': 'sapwood area',
+ 'units': 'm$^2$',
+ },
+ 'bsap': {
+ 'varname': 'sapwood biomass',
+ 'units': 'kgC',
+ },
+ 'bbgw': {
+ 'varname': 'belowground biomass',
+ 'units': 'kgC',
+ },
+ 'fineroot_biomass': {
+ 'varname': 'fineroot biomass',
+ 'units': 'kgC',
+ },
+ 'bstore': {
+ 'varname': 'storage biomass',
+ 'units': 'kgC',
+ },
+ 'bdead': {
+ 'varname': 'deadwood biomass',
+ 'units': 'kgC',
+ },
+ 'total_biomass_parts': {
+ 'varname': 'total biomass (calculated from parts)',
+ 'units': 'kgC',
+ },
+ 'total_biomass_tissues': {
+ 'varname': 'total biomass (calculated from tissues)',
+ 'units': 'kgC',
+ },
+
+ }
+ for plot, attributes in plot_dict.items():
+ plot_allometry_var(allometry_dat[plot], attributes['varname'],
+ attributes['units'], save_figs, plot_dir)
+
+ plot_total_biomass(allometry_dat, save_figs, plot_dir)
diff --git a/testing/functional_testing/math_utils/CMakeLists.txt b/testing/functional_testing/math_utils/CMakeLists.txt
new file mode 100644
index 0000000000..e23eac3dcf
--- /dev/null
+++ b/testing/functional_testing/math_utils/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(math_sources FatesTestMathUtils.F90)
+
+link_directories(${PFUNIT_TOP_DIR}/lib)
+
+add_executable(FATES_math_exe ${math_sources})
+
+target_link_libraries(FATES_math_exe
+ fates
+ csm_share
+ funit)
\ No newline at end of file
diff --git a/testing/functional_testing/math_utils/FatesTestMathUtils.F90 b/testing/functional_testing/math_utils/FatesTestMathUtils.F90
new file mode 100644
index 0000000000..627eb2713b
--- /dev/null
+++ b/testing/functional_testing/math_utils/FatesTestMathUtils.F90
@@ -0,0 +1,138 @@
+program FatesTestQuadSolvers
+
+ use FatesConstantsMod, only : r8 => fates_r8
+ use FatesUtilsMod, only : QuadraticRootsNSWC, QuadraticRootsSridharachary
+ use FatesUtilsMod, only : GetNeighborDistance
+
+ implicit none
+
+ ! CONSTANTS:
+ integer, parameter :: n = 4 ! number of points to test
+ character(len=*), parameter :: out_file = 'quad_out.nc' ! output file
+
+ ! LOCALS:
+ integer :: i ! looping index
+ real(r8) :: a(n), b(n), c(n) ! coefficients for quadratic solvers
+ real(r8) :: root1(n) ! real part of first root of quadratic solver
+ real(r8) :: root2(n) ! real part of second root of quadratic solver
+
+ interface
+
+ subroutine WriteQuadData(out_file, n, a, b, c, root1, root2)
+
+ use FatesUnitTestIOMod, only : OpenNCFile, RegisterNCDims, CloseNCFile
+ use FatesUnitTestIOMod, only : WriteVar, RegisterVar
+ use FatesUnitTestIOMod, only : type_double, type_int
+ use FatesConstantsMod, only : r8 => fates_r8
+ implicit none
+
+ character(len=*), intent(in) :: out_file
+ integer, intent(in) :: n
+ real(r8), intent(in) :: a(:)
+ real(r8), intent(in) :: b(:)
+ real(r8), intent(in) :: c(:)
+ real(r8), intent(in) :: root1(:)
+ real(r8), intent(in) :: root2(:)
+ end subroutine WriteQuadData
+
+ end interface
+
+ a = (/1.0_r8, 1.0_r8, 5.0_r8, 1.5_r8/)
+ b = (/-2.0_r8, 7.0_r8, 10.0_r8, 3.2_r8/)
+ c = (/1.0_r8, 12.0_r8, 3.0_r8, 1.1_r8/)
+
+ do i = 1, n
+ call QuadraticRootsNSWC(a(i), b(i), c(i), root1(i), root2(i))
+ end do
+
+ call WriteQuadData(out_file, n, a, b, c, root1, root2)
+
+end program FatesTestQuadSolvers
+
+! ----------------------------------------------------------------------------------------
+
+subroutine WriteQuadData(out_file, n, a, b, c, root1, root2)
+ !
+ ! DESCRIPTION:
+ ! Writes out data from the quadratic solver test
+ !
+ use FatesConstantsMod, only : r8 => fates_r8
+ use FatesUnitTestIOMod, only : OpenNCFile, RegisterNCDims, CloseNCFile
+ use FatesUnitTestIOMod, only : WriteVar
+ use FatesUnitTestIOMod, only : RegisterVar
+ use FatesUnitTestIOMod, only : EndNCDef
+ use FatesUnitTestIOMod, only : type_double, type_int
+
+ implicit none
+
+ ! ARGUMENTS:
+ character(len=*), intent(in) :: out_file ! output file name
+ integer, intent(in) :: n ! number of points to write out
+ real(r8), intent(in) :: a(:) ! coefficient a
+ real(r8), intent(in) :: b(:) ! coefficient b
+ real(r8), intent(in) :: c(:) ! coefficient c
+ real(r8), intent(in) :: root1(:) ! root1 from quadratic solver
+ real(r8), intent(in) :: root2(:) ! root2 from quadratic solver
+
+ ! LOCALS:
+ integer :: n_index(n) ! array of pft indices to write out
+ integer :: i ! looping index
+ integer :: ncid ! netcdf file id
+ character(len=8) :: dim_names(1) ! dimension names
+ integer :: dimIDs(1) ! dimension IDs
+ integer :: aID, bID, cID
+ integer :: root1ID, root2ID
+
+ ! make index
+ do i = 1, n
+ n_index(i) = i
+ end do
+
+ ! dimension names
+ dim_names = [character(len=12) :: 'n']
+
+ ! open file
+ call OpenNCFile(trim(out_file), ncid, 'readwrite')
+
+ ! register dimensions
+ call RegisterNCDims(ncid, dim_names, (/n/), 1, dimIDs)
+
+ ! register a
+ call RegisterVar(ncid, 'a', dimIDs(1:1), type_double, &
+ [character(len=20) :: 'units', 'long_name'], &
+ [character(len=150) :: '', 'coefficient a'], 2, aID)
+
+ ! register b
+ call RegisterVar(ncid, 'b', dimIDs(1:1), type_double, &
+ [character(len=20) :: 'units', 'long_name'], &
+ [character(len=150) :: '', 'coefficient b'], 2, bID)
+
+ ! register c
+ call RegisterVar(ncid, 'c', dimIDs(1:1), type_double, &
+ [character(len=20) :: 'units', 'long_name'], &
+ [character(len=150) :: '', 'coefficient c'], 2, cID)
+
+ ! register root1
+ call RegisterVar(ncid, 'root1', dimIDs(1:1), type_double, &
+ [character(len=20) :: 'units', 'long_name'], &
+ [character(len=150) :: '', 'root 1'], 2, root1ID)
+
+ ! register root2
+ call RegisterVar(ncid, 'root2', dimIDs(1:1), type_double, &
+ [character(len=20) :: 'units', 'long_name'], &
+ [character(len=150) :: '', 'root 2'], 2, root2ID)
+
+ ! finish defining variables
+ call EndNCDef(ncid)
+
+ ! write out data
+ call WriteVar(ncid, aID, a(:))
+ call WriteVar(ncid, bID, b(:))
+ call WriteVar(ncid, cID, c(:))
+ call WriteVar(ncid, root1ID, root1(:))
+ call WriteVar(ncid, root2ID, root2(:))
+
+ ! close the file
+ call CloseNCFile(ncid)
+
+end subroutine WriteQuadData
diff --git a/testing/functional_testing/math_utils/math_plotting.py b/testing/functional_testing/math_utils/math_plotting.py
new file mode 100644
index 0000000000..4e386dbe93
--- /dev/null
+++ b/testing/functional_testing/math_utils/math_plotting.py
@@ -0,0 +1,52 @@
+"""Utility functions for allometry functional unit tests
+"""
+import os
+import math
+import xarray as xr
+import numpy as np
+import matplotlib.pyplot as plt
+
+from utils import get_color_palette
+
+def plot_quadratic_dat(run_dir, out_file, save_figs, plot_dir):
+ """Reads in and plots quadratic formula test output
+
+ 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
+ """
+
+ # read in quadratic data
+ quadratic_dat = xr.open_dataset(os.path.join(run_dir, out_file))
+
+ # plot output
+ plot_quad_and_roots(quadratic_dat.a.values, quadratic_dat.b.values,
+ quadratic_dat.c.values, quadratic_dat.root1.values,
+ quadratic_dat.root2.values)
+ if save_figs:
+ fig_name = os.path.join(plot_dir, "quadratic_test.png")
+ plt.savefig(fig_name)
+
+def plot_quad_and_roots(a_coeff, b_coeff, c_coeff, root1, root2):
+ """Plots a set of quadratic formulas (ax**2 + bx + c) and their two roots
+
+ Args:
+ a_coeff (float array): set of a coefficients
+ b_coeff (float array): set of b coefficients
+ c_coeff (float array): set of b coefficients
+ root1 (float array): set of first real roots
+ root2 (float array): set of second real roots
+ """
+ num_equations = len(a_coeff)
+
+ plt.figure(figsize=(7, 5))
+ x_vals = np.linspace(-10.0, 10.0, num=20)
+
+ colors = get_color_palette(num_equations)
+ for i in range(num_equations):
+ y_vals = a_coeff[i]*x_vals**2 + b_coeff[i]*x_vals + c_coeff[i]
+ plt.plot(x_vals, y_vals, lw=2, color=colors[i])
+ plt.scatter(root1[i], root2[i], color=colors[i], s=50)
+ plt.axhline(y=0.0, color='k', linestyle='dotted')
diff --git a/testing/path_utils.py b/testing/path_utils.py
new file mode 100644
index 0000000000..85aea6085a
--- /dev/null
+++ b/testing/path_utils.py
@@ -0,0 +1,51 @@
+"""Utility functions related to getting paths to various important places
+"""
+
+import os
+import sys
+
+# Path to the root directory of FATES, based on the path of this file
+# Note: It's important that this NOT end with a trailing slash;
+_FATES_ROOT = os.path.normpath(
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
+)
+
+def path_to_fates_root():
+ """Returns the path to the root directory of FATES"""
+ return _FATES_ROOT
+
+def path_to_cime():
+ """Returns the path to cime, if it can be found
+
+ Raises a RuntimeError if it cannot be found
+
+ """
+ cime_path = os.path.join(path_to_fates_root(), "../../cime")
+ if os.path.isdir(cime_path):
+ return cime_path
+ raise RuntimeError("Cannot find cime.")
+
+def prepend_to_python_path(path):
+ """Adds the given path to python's sys.path if it isn't already in the path
+
+ The path is added near the beginning, so that it takes precedence over existing
+ entries in the path
+ """
+ if not path in sys.path:
+ # Insert at location 1 rather than 0, because 0 is special
+ sys.path.insert(1, path)
+
+def add_cime_lib_to_path():
+ """Adds the CIME python library to the python path, to allow importing
+ modules from that library
+
+ Returns the path to the top-level cime directory
+
+ For documentation on standalone_only: See documentation in
+ path_to_cime
+ """
+ cime_path = path_to_cime()
+ prepend_to_python_path(cime_path)
+ cime_lib_path = os.path.join(cime_path, "CIME", "Tools")
+ prepend_to_python_path(cime_lib_path)
+ return cime_path
diff --git a/testing/run_fates_tests.py b/testing/run_fates_tests.py
new file mode 100755
index 0000000000..ea2cf5b0b1
--- /dev/null
+++ b/testing/run_fates_tests.py
@@ -0,0 +1,457 @@
+#!/usr/bin/env python
+
+"""
+|------------------------------------------------------------------|
+|--------------------- Instructions -----------------------------|
+|------------------------------------------------------------------|
+To run this script the following python packages are required:
+ - numpy
+ - xarray
+ - matplotlib
+ - pandas
+
+Though this script does not require any host land model code, it does require some CIME
+and shr code, so you should still get these repositories as you normally would
+(i.e., manage_externals, etc.)
+
+Additionally, this requires netcdf and netcdff as well as a fortran compiler.
+
+You must also have a .cime folder in your home directory which specifies machine
+configurations for CIME.
+
+This script builds and runs various FATES unit and functional tests, and plots any
+relevant output from those tests.
+
+You can supply your own parameter file (either a .cdl or a .nc file), or if you do not
+specify anything, the script will use the default FATES parameter cdl file.
+
+"""
+import os
+import argparse
+import matplotlib.pyplot as plt
+from build_fortran_tests import build_unit_tests, build_exists
+from path_utils import add_cime_lib_to_path
+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
+
+add_cime_lib_to_path()
+
+from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order
+
+# Constants for this script
+_DEFAULT_CDL_PATH = os.path.abspath("../parameter_files/fates_params_default.cdl")
+_CMAKE_BASE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../")
+_TEST_NAME = "fates_tests"
+_TEST_SUB_DIR = "testing"
+
+# Dictionary with needed constants for running the executables and reading in the
+# output files - developers who add tests should add things here.
+
+# NOTE: if the functional test you write requires a parameter file read in as a
+# command-line argument, this should be the *first* (or only) argument in the
+# command-line argument list
+_ALL_TESTS_DICT = {
+ "allometry": {
+ "test_dir": "fates_allom_ftest",
+ "test_exe": "FATES_allom_exe",
+ "out_file": "allometry_out.nc",
+ "has_unit_test": False,
+ "use_param_file": True,
+ "other_args": [],
+ "plotting_function": plot_allometry_dat,
+ },
+ "quadratic": {
+ "test_dir": "fates_math_ftest",
+ "test_exe": "FATES_math_exe",
+ "out_file": "quad_out.nc",
+ "has_unit_test": False,
+ "use_param_file": False,
+ "other_args": [],
+ "plotting_function": plot_quadratic_dat,
+ },
+ "fire_weather":{
+ "test_dir": "fates_fire_weather_utest",
+ "test_exe": None,
+ "out_file": None,
+ "has_unit_test": True,
+ "use_param_file": False,
+ "other_args": [],
+ "plotting_function": None,
+ }
+ }
+
+def run_fortran_exectuables(build_dir, test_dir, test_exe, run_dir, args):
+ """Run the generated Fortran executables
+
+ Args:
+ build_dir (str): full path to build directory
+ run_dir (str): full path to run directory
+ test_dir (str): test directory within the run directory
+ test_exe (str): test executable to run
+ args ([str]): arguments for executable
+ """
+
+ # move executable to run directory
+ exe_path = os.path.join(build_dir, _TEST_SUB_DIR, test_dir, test_exe)
+ copy_file(exe_path, run_dir)
+
+ # run the executable
+ new_exe_path = os.path.join(run_dir, test_exe)
+ run_command = [new_exe_path]
+ run_command.extend(args)
+
+ os.chdir(run_dir)
+ out = run_cmd_no_fail(" ".join(run_command), combine_output=True)
+ print(out)
+
+def make_plotdirs(run_dir, test_dict):
+ """Create plotting directories if they don't already exist
+
+ Args:
+ run_dir (str): full path to run directory
+ test_dict (dict): dictionary of test to run
+ """
+ # make main plot directory
+ plot_dir = os.path.join(run_dir, 'plots')
+ if not os.path.isdir(plot_dir):
+ os.mkdir(plot_dir)
+
+ # make sub-plot directories
+ for test in dict(filter(lambda pair: pair[1]['plotting_function'] is not None,
+ test_dict.items())):
+ sub_dir = os.path.join(plot_dir, test)
+ if not os.path.isdir(sub_dir):
+ os.mkdir(sub_dir)
+
+def create_param_file(param_file, run_dir):
+ """Creates and/or move the default or input parameter file to the run directory
+ Creates a netcdf file from a cdl file if a cdl file is supplied
+
+ Args:
+ param_file (str): path to parmaeter file
+ run_dir (str): full path to run directory
+
+ Raises:
+ RuntimeError: Supplied parameter file is not netcdf (.cd) or cdl (.cdl)
+
+ Returns:
+ str: full path to new parameter file name/location
+ """
+ if param_file is None:
+ print("Using default parameter file.")
+ param_file = _DEFAULT_CDL_PATH
+ param_file_update = create_nc_file(param_file, run_dir)
+ else:
+ print(f"Using parameter file {param_file}.")
+ file_suffix = os.path.basename(param_file).split(".")[-1]
+ if file_suffix == 'cdl':
+ param_file_update = create_nc_file(param_file, run_dir)
+ elif file_suffix == "nc":
+ param_file_update = copy_file(param_file, run_dir)
+ else:
+ raise RuntimeError("Must supply parameter file with .cdl or .nc ending.")
+
+ return param_file_update
+
+def run_tests(clean, verbose_make, build_tests, run_executables, build_dir, run_dir,
+ make_j, param_file, save_figs, test_dict):
+ """Builds and runs the fates tests
+
+ Args:
+ clean (bool): whether or not to clean the build directory
+ verbose_make (bool): whether or not to run make with verbose output
+ build_tests (bool): whether or not to build the exectuables
+ run_executables (bool): whether or not to run the executables
+ build_dir (str): build directory
+ run_dir (str): run directory
+ make_j (int): number of processors for the build
+ param_file (str): input FATES parameter file
+ save_figs (bool): whether or not to write figures to file
+ test_dict (dict): dictionary of tests to run
+ """
+
+ # absolute path to desired build directory
+ build_dir_path = os.path.abspath(build_dir)
+
+ # absolute path to desired run directory
+ run_dir_path = os.path.abspath(run_dir)
+
+ # make run directory if it doesn't already exist
+ if not os.path.isdir(run_dir_path):
+ os.mkdir(run_dir_path)
+
+ # create plot directories if we need to
+ if save_figs:
+ make_plotdirs(os.path.abspath(run_dir), test_dict)
+
+ # move parameter file to correct location (creates nc file if cdl supplied)
+ param_file = create_param_file(param_file, run_dir)
+
+ # compile code
+ if build_tests:
+ build_unit_tests(build_dir, _TEST_NAME, _CMAKE_BASE_DIR, make_j, clean=clean,
+ verbose=verbose_make)
+
+ # run executables for each test in test list
+ if run_executables:
+ print("Running executables")
+ # we don't run executables for only pfunit tests
+ for attributes in dict(filter(lambda pair: pair[1]['test_exe'] is not None,
+ test_dict.items())).values():
+ # prepend parameter file (if required) to argument list
+ args = attributes['other_args']
+ if attributes['use_param_file']:
+ args.insert(0, param_file)
+ # run
+ run_fortran_exectuables(build_dir_path, attributes['test_dir'],
+ attributes['test_exe'], run_dir_path, args)
+
+ # run unit tests
+ for test, attributes in dict(filter(lambda pair: pair[1]['has_unit_test'],
+ test_dict.items())).items():
+ print(f"Running unit tests for {test}.")
+
+ test_dir = os.path.join(build_dir_path, _TEST_SUB_DIR, attributes['test_dir'])
+ ctest_command = ["ctest", "--output-on-failure"]
+ output = run_cmd_no_fail(" ".join(ctest_command), from_dir=test_dir,
+ combine_output=True)
+ print(output)
+
+ # plot output for relevant tests
+ for test, attributes in dict(filter(lambda pair: pair[1]['plotting_function'] is not None,
+ test_dict.items())).items():
+ attributes['plotting_function'](run_dir_path,
+ attributes['out_file'], save_figs,
+ os.path.join(run_dir_path, 'plots', test))
+ # show plots
+ plt.show()
+
+def out_file_exists(run_dir, out_file):
+ """Checks to see if the file out_file exists in the run_dir
+
+ Args:
+ run_dir (str): full path to run directory
+ out_file (str): output file name
+
+ Returns:
+ bool: yes/no file exists in correct location
+ """
+ return os.path.isfile(os.path.join(run_dir, out_file))
+
+def parse_test_list(test_string):
+ """Parses the input test list and checks for errors
+
+ Args:
+ test (str): user-supplied comma-separated list of test names
+
+ Returns:
+ dictionary: filtered dictionary of tests to run
+
+ Raises:
+ RuntimeError: Invalid test name supplied
+ """
+ valid_test_names = _ALL_TESTS_DICT.keys()
+
+ if test_string != "all":
+ test_list = test_string.split(',')
+ for test in test_list:
+ if test not in valid_test_names:
+ raise argparse.ArgumentTypeError("Invalid test supplied, \n"
+ "must supply one of:\n"
+ f"{', '.join(valid_test_names)}\n"
+ "or do not supply a test name to run all tests.")
+ test_dict = {key: _ALL_TESTS_DICT[key] for key in test_list}
+ else:
+ test_dict = _ALL_TESTS_DICT
+
+ return test_dict
+
+def commandline_args():
+ """Parse and return command-line arguments"""
+
+ description = """
+ Driver for running FATES unit and functional tests
+
+ Typical usage:
+
+ ./run_fates_tests -f parameter_file.nc
+
+ """
+ parser = argparse.ArgumentParser(
+ description=description, formatter_class=argparse.RawTextHelpFormatter
+ )
+
+ parser.add_argument(
+ "-f",
+ "--parameter-file",
+ type=str,
+ default=_DEFAULT_CDL_PATH,
+ help="Parameter file to run the FATES tests with.\n"
+ "Can be a netcdf (.nc) or cdl (.cdl) file.\n"
+ "If no file is specified the script will use the default .cdl file in the\n"
+ "parameter_files directory.\n",
+ )
+
+ parser.add_argument(
+ "-b",
+ "--build-dir",
+ type=str,
+ default="../_build",
+ help="Directory where tests are built.\n"
+ "Will be created if it does not exist.\n",
+ )
+
+ parser.add_argument(
+ "-r",
+ "--run-dir",
+ type=str,
+ default="../_run",
+ help="Directory where tests are run.\n"
+ "Will be created if it does not exist.\n",
+ )
+
+ parser.add_argument(
+ "--make-j",
+ type=int,
+ default=8,
+ help="Number of processes to use for build.",
+ )
+
+ parser.add_argument(
+ "-c",
+ "--clean",
+ action="store_true",
+ help="Clean build directory before building.\n"
+ "Removes CMake cache and runs 'make clean'.\n",
+ )
+
+ parser.add_argument(
+ "--skip-build",
+ action="store_true",
+ help="Skip building and compiling the test code.\n"
+ "Only do this if you already have run build.\n"
+ "Script will check to make sure executables are present.\n",
+ )
+
+ parser.add_argument(
+ "--skip-run-executables",
+ action="store_true",
+ help="Skip running test code executables.\n"
+ "Only do this if you already have run the code previously.\n"
+ "Script will check to make sure required output files are present.\n",
+ )
+
+ parser.add_argument(
+ "--save-figs",
+ action="store_true",
+ help="Write out generated figures to files.\n"
+ "Will be placed in run_dir/plots.\n"
+ "Should probably do this on remote machines.\n",
+ )
+
+ parser.add_argument(
+ "--verbose-make",
+ action="store_true",
+ help="Run make with verbose output."
+ )
+
+ parser.add_argument(
+ "-t",
+ "--test-list",
+ action="store",
+ dest="test_dict",
+ type=parse_test_list,
+ default="all",
+ help="Test(s) to run. Comma-separated list of test names, or 'all'\n"
+ "for all tests. If not supplied, will run all tests."
+ )
+
+ args = parser.parse_args()
+
+ check_arg_validity(args)
+
+ return args
+
+def check_param_file(param_file):
+ """Checks to see if param_file exists and is of the correct form (.nc or .cdl)
+
+ Args:
+ param_file (str): path to parameter file
+
+ Raises:
+ argparse.ArgumentError: Parameter file is not of the correct form (.nc or .cdl)
+ argparse.ArgumentError: Can't find parameter file
+ """
+ file_suffix = os.path.basename(param_file).split(".")[-1]
+ if not file_suffix in ['cdl', 'nc']:
+ raise argparse.ArgumentError(None, "Must supply parameter file with .cdl or .nc ending.")
+ if not os.path.isfile(param_file):
+ raise argparse.ArgumentError(None, f"Cannot find file {param_file}.")
+
+def check_build_dir(build_dir, test_dict):
+ """Checks to see if all required build directories and executables are present
+
+ Args:
+ build_dir (str): build directory
+ test_list (list, str): list of test names
+
+ Raises:
+ argparse.ArgumentError: Can't find a required build directory or executable
+ """
+ for attributes in test_dict.values():
+ if not build_exists(build_dir, attributes['test_dir'], attributes['test_exe']):
+ raise argparse.ArgumentError(None, "Build directory or executable does not exist.\n"
+ "Re-run script without --skip-build.")
+
+def check_out_files(run_dir, test_dict):
+ """Checks to see that required output files are present in the run directory
+
+ Args:
+ run_dir (str): run directory
+ test_dict (dict): dictionary of tests to run
+
+ Raises:
+ argparse.ArgumentError: Can't find a required output file
+ """
+ for test, attributes in dict(filter(lambda pair: pair[1]['out_file'] is not None,
+ test_dict.items())).items():
+ if not out_file_exists(os.path.abspath(run_dir), attributes['out_file']):
+ raise argparse.ArgumentError(None, f"Required file for {test} test does not exist.\n"
+ "Re-run script without --skip-run.")
+
+def check_arg_validity(args):
+ """Checks validity of input script arguments
+
+ Args:
+ args (parse_args): input arguments
+ """
+ # check to make sure parameter file exists and is one of the correct forms
+ check_param_file(args.parameter_file)
+
+ # make sure build directory exists
+ if args.skip_build:
+ if args.verbose_make:
+ raise argparse.ArgumentError(None, "Can't run verbose make and skip build.\n"
+ "Re-run script without --skip-build")
+ check_build_dir(args.build_dir, args.test_dict)
+
+ # make sure relevant output files exist:
+ if args.skip_run_executables:
+ check_out_files(args.run_dir, args.test_dict)
+
+def main():
+ """Main script
+ Reads in command-line arguments and then runs the tests.
+ """
+
+ args = commandline_args()
+
+ build = not args.skip_build
+ run = not args.skip_run_executables
+
+ run_tests(args.clean, args.verbose_make, build, run, args.build_dir, args.run_dir,
+ args.make_j, args.parameter_file, args.save_figs, args.test_dict)
+
+if __name__ == "__main__":
+ main()
diff --git a/testing/testing_shr/CMakeLists.txt b/testing/testing_shr/CMakeLists.txt
new file mode 100644
index 0000000000..8cf4eb69c0
--- /dev/null
+++ b/testing/testing_shr/CMakeLists.txt
@@ -0,0 +1,6 @@
+list(APPEND fates_sources
+ FatesUnitTestParamReaderMod.F90
+ FatesUnitTestIOMod.F90
+ )
+
+sourcelist_to_parent(fates_sources)
\ No newline at end of file
diff --git a/testing/testing_shr/FatesUnitTestIOMod.F90 b/testing/testing_shr/FatesUnitTestIOMod.F90
new file mode 100644
index 0000000000..8f6ea1141a
--- /dev/null
+++ b/testing/testing_shr/FatesUnitTestIOMod.F90
@@ -0,0 +1,574 @@
+module FatesUnitTestIOMod
+ use FatesConstantsMod, only : r8 => fates_r8
+ use FatesGlobals, only : fates_endrun
+ use shr_kind_mod, only : SHR_KIND_CL
+ use netcdf
+
+ implicit none
+ private
+
+ ! LOCALS
+ integer, public, parameter :: type_double = 1 ! type
+ integer, public, parameter :: type_int = 2 ! type
+
+ interface GetVar
+ module procedure GetVarScalarReal
+ module procedure GetVar1DReal
+ module procedure GetVar2DReal
+ module procedure GetVar3DReal
+ module procedure GetVar1DInt
+ module procedure GetVar2DInt
+ module procedure GetVar3DInt
+ end interface
+
+ interface WriteVar
+ module procedure WriteVar1DReal
+ module procedure WriteVar2DReal
+ module procedure WriteVar1DInt
+ module procedure WriteVar2DInt
+ end interface
+
+ public :: OpenNCFile
+ public :: CloseNCFile
+ public :: GetDimID
+ public :: GetDimLen
+ public :: GetVar
+ public :: RegisterNCDims
+ public :: RegisterVar
+ public :: WriteVar
+ public :: EndNCDef
+
+ contains
+
+ !=======================================================================================
+
+ logical function CheckFile(filename, fmode)
+ !
+ ! DESCRIPTION:
+ ! Checks to see if a file exists and checks against the mode
+ !
+
+ ! ARGUMENTS:
+ character(len=*), intent(in) :: filename ! Name of file to open
+ character(len=*), intent(in) :: fmode ! File mode
+
+ ! LOCALS:
+ character(len=len(filename)) :: fname ! Local filename (trimmed)
+ integer :: ios ! I/O status
+ logical :: file_exists ! Does the file exist?
+
+ ! trim filename of whitespace
+ fname = trim(adjustl(filename))
+
+ ! Does the file exist?
+ inquire(file=fname, exist=file_exists)
+
+ select case (fmode)
+ case('read')
+
+ if (.not. file_exists) then
+ write(*,'(a,a,a)') "File ", fname(1:len_trim(fname)), " does not exist. Can't read."
+ CheckFile = .false.
+ else
+ CheckFile = .true.
+ end if
+
+ case('readwrite')
+
+ CheckFile = .true.
+
+ case('write')
+ if (file_exists) then
+ write(*, '(a, a, a)') "File ", fname(1:len_trim(fname)), " exists. Cannot open write only."
+ else
+ CheckFile = .true.
+ CheckFile = .false.
+ end if
+ case default
+ write(*,'(a)') "Invalid file mode."
+ CheckFile = .false.
+ end select
+
+ end function CheckFile
+
+ !=======================================================================================
+
+ subroutine Check(status)
+ !
+ ! DESCRIPTION:
+ ! Checks status of netcdf operations
+
+ ! ARGUMENTS:
+ integer, intent(in) :: status ! return status code from a netcdf procedure
+
+ if (status /= nf90_noerr) then
+ write(*,*) trim(nf90_strerror(status))
+ stop
+ end if
+
+ end subroutine Check
+
+ ! =======================================================================================
+
+ subroutine OpenNCFile(nc_file, ncid, fmode)
+ !
+ ! DESCRIPTION:
+ ! Opens a netcdf file
+
+ ! ARGUMENTS:
+ character(len=*), intent(in) :: nc_file ! file name
+ integer, intent(out) :: ncid ! netcdf file unit number
+ character(len=*) :: fmode ! file mode
+
+ if (CheckFile(nc_file, fmode)) then
+ ! depending on mode
+ select case(fmode)
+ case ('read')
+ call Check(nf90_open(trim(nc_file), NF90_NOCLOBBER, ncid))
+ case ('write')
+ call Check(nf90_create(trim(nc_file), NF90_CLOBBER, ncid))
+ case ('readwrite')
+ call Check(nf90_create(trim(nc_file), NF90_CLOBBER, ncid))
+ case DEFAULT
+ write(*,*) 'Need to specify read, write, or readwrite'
+ stop
+ end select
+ else
+ write(*,*) 'Problem reading file'
+ stop
+ end if
+
+ end subroutine OpenNCFile
+
+ !=======================================================================================
+
+ subroutine CloseNCFile(ncid)
+ !
+ ! DESCRIPTION:
+ ! Closes a netcdf file
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit number
+
+ call Check(nf90_close(ncid))
+
+ end subroutine CloseNCFile
+
+ !=======================================================================================
+
+ subroutine GetDimID(ncid, var_name, dim_id)
+ !
+ ! DESCRIPTION:
+ ! Gets dimension IDs for a variable ID
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit number
+ character(len=*), intent(in) :: var_name ! variable name
+ integer, intent(out) :: dim_id ! dimension ID
+
+ call Check(nf90_inq_dimid(ncid, var_name, dim_id))
+
+ end subroutine GetDimID
+
+ !=======================================================================================
+
+ subroutine GetDimLen(ncid, dim_id, dim_len)
+ !
+ ! DESCRIPTION:
+ ! Gets dimension lengths given a dimension ID
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit number
+ integer, intent(in) :: dim_id ! dimension ID
+ integer, intent(out) :: dim_len ! dimension length
+
+ call Check(nf90_inquire_dimension(ncid, dim_id, len=dim_len))
+
+ end subroutine GetDimLen
+
+ !=======================================================================================
+
+ subroutine GetDims(ncid, varID, dim_lens)
+ !
+ ! DESCRIPTION:
+ ! Get dimensions for a netcdf variable
+ !
+
+ ! ARGUMENTS
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ integer, intent(in) :: varID ! variable ID
+ integer, allocatable, intent(out) :: dim_lens(:) ! dimension lengths
+
+ ! LOCALS:
+ integer :: numDims ! number of dimensions
+ integer, allocatable :: dimIDs(:) ! dimension IDs
+ integer :: i ! looping index
+
+ ! find dimensions of data
+ call Check(nf90_inquire_variable(ncid, varID, ndims=numDims))
+
+ ! allocate data to grab dimension information
+ allocate(dim_lens(numDims))
+ allocate(dimIDs(numDims))
+
+ ! get dimIDs
+ call Check(nf90_inquire_variable(ncid, varID, dimids=dimIDs))
+
+ ! grab these dimensions
+ do i = 1, numDims
+ call Check(nf90_inquire_dimension(ncid, dimIDs(i), len=dim_lens(i)))
+ end do
+
+ end subroutine GetDims
+
+ !=====================================================================================
+
+ subroutine GetVarScalarReal(ncid, var_name, data)
+ !
+ ! DESCRIPTION:
+ ! Read in variables for scalar real data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ character(len=*), intent(in) :: var_name ! variable name
+ real(r8), intent(out) :: data ! data value
+
+ ! LOCALS:
+ integer :: varID ! variable ID
+ integer, allocatable :: dim_lens(:) ! dimension lengths
+
+ ! find variable ID first
+ call Check(nf90_inq_varid(ncid, var_name, varID))
+
+ ! read data
+ call Check(nf90_get_var(ncid, varID, data))
+
+ end subroutine GetVarScalarReal
+
+ !=====================================================================================
+
+ subroutine GetVar1DReal(ncid, var_name, data)
+ !
+ ! DESCRIPTION:
+ ! Read in variables for 1D real data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ character(len=*), intent(in) :: var_name ! variable name
+ real(r8), allocatable, intent(out) :: data(:) ! data values
+
+ ! LOCALS:
+ integer :: varID ! variable ID
+ integer, allocatable :: dim_lens(:) ! dimension lengths
+
+ ! find variable ID first
+ call Check(nf90_inq_varid(ncid, var_name, varID))
+
+ ! get dimensions of data
+ call GetDims(ncid, varID, dim_lens)
+
+ ! read data
+ allocate(data(dim_lens(1)))
+ call Check(nf90_get_var(ncid, varID, data))
+
+ end subroutine GetVar1DReal
+
+ !=====================================================================================
+
+ subroutine GetVar1DInt(ncid, var_name, data)
+ !
+ ! DESCRIPTION:
+ ! Read in variables for 1D integer data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ character(len=*), intent(in) :: var_name ! variable name
+ integer, allocatable, intent(out) :: data(:) ! data values
+
+ ! LOCALS:
+ integer :: varID ! variable ID
+ integer, allocatable :: dim_lens(:) ! dimension lengths
+
+ ! find variable ID first
+ call Check(nf90_inq_varid(ncid, var_name, varID))
+
+ ! get dimensions of data
+ call GetDims(ncid, varID, dim_lens)
+
+ ! read data
+ allocate(data(dim_lens(1)))
+ call Check(nf90_get_var(ncid, varID, data))
+
+ end subroutine GetVar1DInt
+
+ !=====================================================================================
+
+ subroutine GetVar2DReal(ncid, var_name, data)
+ !
+ ! DESCRIPTION:
+ ! Read in variables for 2D real data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ character(len=*), intent(in) :: var_name ! variable name
+ real(r8), allocatable, intent(out) :: data(:,:) ! data values
+
+ ! LOCALS:
+ integer :: varID ! variable ID
+ integer, allocatable :: dim_lens(:) ! dimension lengths
+
+ ! find variable ID first
+ call Check(nf90_inq_varid(ncid, var_name, varID))
+
+ ! get dimensions of data
+ call GetDims(ncid, varID, dim_lens)
+
+ ! read data
+ allocate(data(dim_lens(1), dim_lens(2)))
+ call Check(nf90_get_var(ncid, varID, data))
+
+ end subroutine GetVar2DReal
+
+ !=====================================================================================
+
+ subroutine GetVar2DInt(ncid, var_name, data)
+ !
+ ! DESCRIPTION:
+ ! Read in variables for 2D integer data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ character(len=*), intent(in) :: var_name ! variable name
+ integer, allocatable, intent(out) :: data(:,:) ! data values
+
+ ! LOCALS:
+ integer :: varID ! variable ID
+ integer, allocatable :: dim_lens(:) ! dimension lengths
+
+ ! find variable ID first
+ call Check(nf90_inq_varid(ncid, var_name, varID))
+
+ ! get dimensions of data
+ call GetDims(ncid, varID, dim_lens)
+
+ ! read data
+ allocate(data(dim_lens(1), dim_lens(2)))
+ call Check(nf90_get_var(ncid, varID, data))
+
+ end subroutine GetVar2DInt
+
+ !=====================================================================================
+
+ subroutine GetVar3DReal(ncid, var_name, data)
+ !
+ ! DESCRIPTION:
+ ! Read in variables for 3D real data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ character(len=*), intent(in) :: var_name ! variable name
+ real(r8), allocatable, intent(out) :: data(:,:,:) ! data values
+
+ ! LOCALS:
+ integer :: varID ! variable ID
+ integer, allocatable :: dim_lens(:) ! dimension lengths
+
+ ! find variable ID first
+ call Check(nf90_inq_varid(ncid, var_name, varID))
+
+ ! get dimensions of data
+ call GetDims(ncid, varID, dim_lens)
+
+ ! read data
+ allocate(data(dim_lens(1), dim_lens(2), dim_lens(3)))
+ call Check(nf90_get_var(ncid, varID, data))
+
+ end subroutine GetVar3DReal
+
+ !=====================================================================================
+
+ subroutine GetVar3DInt(ncid, var_name, data)
+ !
+ ! DESCRIPTION:
+ ! Read in variables for 3D integer data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file unit ID
+ character(len=*), intent(in) :: var_name ! variable name
+ integer, allocatable, intent(out) :: data(:,:,:) ! data values
+
+ ! LOCALS:
+ integer :: varID ! variable ID
+ integer, allocatable :: dim_lens(:) ! dimension lengths
+
+ ! find variable ID first
+ call Check(nf90_inq_varid(ncid, var_name, varID))
+
+ ! get dimensions of data
+ call GetDims(ncid, varID, dim_lens)
+
+ ! read data
+ allocate(data(dim_lens(1), dim_lens(2), dim_lens(3)))
+ call Check(nf90_get_var(ncid, varID, data))
+
+ end subroutine GetVar3DInt
+
+ !=====================================================================================
+
+ subroutine RegisterNCDims(ncid, dim_names, dim_lens, num_dims, dim_IDs)
+ !
+ ! DESCRIPTION:
+ ! Defines variables and dimensions
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file id
+ character(len=*), intent(in) :: dim_names(num_dims) ! dimension names
+ integer, intent(in) :: dim_lens(num_dims) ! dimension lengths
+ integer, intent(in) :: num_dims ! number of dimensions
+ integer, intent(out) :: dim_IDs(num_dims) ! dimension IDs
+
+ ! LOCALS:
+ integer :: i ! looping index
+
+ do i = 1, num_dims
+ call Check(nf90_def_dim(ncid, dim_names(i), dim_lens(i), dim_IDs(i)))
+ end do
+
+ end subroutine RegisterNCDims
+
+ !=====================================================================================
+
+ subroutine RegisterVar(ncid, var_name, dimID, type, att_names, atts, num_atts, varID)
+ !
+ ! DESCRIPTION:
+ ! Defines variables and dimensions
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file id
+ character(len=*), intent(in) :: var_name ! variable name
+ integer, intent(in) :: dimID(:) ! dimension IDs
+ integer, intent(in) :: type ! type: int or double
+ character(len=*), intent(in) :: att_names(num_atts) ! attribute names
+ character(len=*), intent(in) :: atts(num_atts) ! attribute values
+ integer, intent(in) :: num_atts ! number of attributes
+ integer, intent(out) :: varID ! variable ID
+
+
+ ! LOCALS:
+ integer :: i ! looping index
+ integer :: nc_type ! netcdf type
+
+ if (type == type_double) then
+ nc_type = NF90_DOUBLE
+ else if (type == type_int) then
+ nc_type = NF90_INT
+ else
+ write(*, *) "Must pick correct type"
+ stop
+ end if
+
+ call Check(nf90_def_var(ncid, var_name, nc_type, dimID, varID))
+
+ do i = 1, num_atts
+ call Check(nf90_put_att(ncid, varID, att_names(i), atts(i)))
+ end do
+
+ end subroutine RegisterVar
+
+ ! =====================================================================================
+
+ subroutine EndNCDef(ncid)
+ !
+ ! DESCRIPTION:
+ ! End defining of netcdf dimensions and variables
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file id
+
+ call Check(nf90_enddef(ncid))
+
+ end subroutine EndNCDef
+
+ ! =====================================================================================
+
+ subroutine WriteVar1DReal(ncid, varID, data)
+ !
+ ! DESCRIPTION:
+ ! Write 1D real data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file id
+ integer, intent(in) :: varID ! variable ID
+ real(r8), intent(in) :: data(:) ! data to write
+
+ call Check(nf90_put_var(ncid, varID, data(:)))
+
+ end subroutine WriteVar1DReal
+
+ ! =====================================================================================
+
+ subroutine WriteVar2DReal(ncid, varID, data)
+ !
+ ! DESCRIPTION:
+ ! Write 2D real data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file id
+ integer, intent(in) :: varID ! variable ID
+ real(r8), intent(in) :: data(:,:) ! data to write
+
+ call Check(nf90_put_var(ncid, varID, data(:,:)))
+
+ end subroutine WriteVar2DReal
+
+ ! =====================================================================================
+
+ subroutine WriteVar1DInt(ncid, varID, data)
+ !
+ ! DESCRIPTION:
+ ! Write 1D integer data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file id
+ integer, intent(in) :: varID ! variable ID
+ integer, intent(in) :: data(:) ! data to write
+
+ call Check(nf90_put_var(ncid, varID, data(:)))
+
+ end subroutine WriteVar1DInt
+
+ ! =====================================================================================
+
+ subroutine WriteVar2DInt(ncid, varID, data)
+ !
+ ! DESCRIPTION:
+ ! Write 2D integer data
+ !
+
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file id
+ integer, intent(in) :: varID ! variable ID
+ integer, intent(in) :: data(:,:) ! data to write
+
+ call Check(nf90_put_var(ncid, varID, data(:,:)))
+
+ end subroutine WriteVar2DInt
+
+ ! =====================================================================================
+
+end module FatesUnitTestIOMod
\ No newline at end of file
diff --git a/testing/testing_shr/FatesUnitTestParamReaderMod.F90 b/testing/testing_shr/FatesUnitTestParamReaderMod.F90
new file mode 100644
index 0000000000..624c98ea68
--- /dev/null
+++ b/testing/testing_shr/FatesUnitTestParamReaderMod.F90
@@ -0,0 +1,200 @@
+module FatesUnitTestParamReaderMod
+
+ use FatesConstantsMod, only : r8 => fates_r8
+ use FatesParametersInterface, only : fates_param_reader_type
+ use FatesParametersInterface, only : fates_parameters_type
+ use FatesParametersInterface, only : param_string_length
+ use FatesParametersInterface, only : max_dimensions, max_used_dimensions
+ use FatesParametersInterface, only : dimension_shape_scalar, dimension_shape_1d, dimension_shape_2d
+ use EDParamsMod, only : FatesRegisterParams, FatesReceiveParams
+ use SFParamsMod, only : SpitFireRegisterParams, SpitFireReceiveParams
+ use PRTInitParamsFatesMod, only : PRTRegisterParams, PRTReceiveParams
+ use PRTParametersMod, only : prt_params
+ use FatesParameterDerivedMod, only : param_derived
+ use FatesSynchronizedParamsMod, only : FatesSynchronizedParamsInst
+ use EDPftvarcon, only : EDPftvarcon_inst
+ use FatesUnitTestIOMod, only : OpenNCFile, GetDimID, GetDimLen, GetVar, CloseNCFile
+
+ implicit none
+ private
+
+ type, public, extends(fates_param_reader_type) :: fates_unit_test_param_reader
+
+ character(:), allocatable :: filename ! local file name of parameter
+
+ contains
+ procedure, public :: Read => ReadParameters
+ procedure, public :: Init
+ procedure, public :: RetrieveParameters
+
+ end type fates_unit_test_param_reader
+
+ contains
+
+ subroutine Init(this, param_file)
+ !
+ ! DESCRIPTION:
+ ! Initialize the parameter reader class
+ !
+ ! ARGUMENTS:
+ class(fates_unit_test_param_reader) :: this
+ character(len=*) :: param_file
+
+ this%filename = trim(param_file)
+
+ end subroutine Init
+
+ ! --------------------------------------------------------------------------------------
+
+ subroutine ReadParameters(this, fates_params)
+ !
+ ! DESCRIPTION:
+ ! Read 'fates_params' parameters from storage
+ !
+ ! ARGUMENTS:
+ class(fates_unit_test_param_reader) :: this
+ class(fates_parameters_type), intent(inout) :: fates_params
+
+ ! LOCALS:
+ real(r8), allocatable :: data2d(:, :) ! data for 2D parameters
+ real(r8), allocatable :: data1d(:) ! data for 1D parameters
+ real(r8) :: data_scalar ! data for scalar parameters
+ integer :: ncid ! netcdf file ID
+ integer :: num_params ! total number of parameters
+ integer :: dimension_shape ! shape of parameter's dimension
+ integer :: i ! looping index
+ character(len=param_string_length) :: name ! parameter name
+ integer :: dimension_sizes(max_dimensions) ! sizes of dimensions from parameter file
+ character(len=param_string_length) :: dimension_names(max_dimensions) ! names of dimensions from parameter file
+ logical :: is_host_param
+
+ call OpenNCFile(this%filename, ncid, 'read')
+ call SetParameterDimensions(ncid, fates_params)
+
+ num_params = fates_params%num_params()
+ do i = 1, num_params
+ call fates_params%GetMetaData(i, name, dimension_shape, dimension_sizes, &
+ dimension_names, is_host_param)
+ select case(dimension_shape)
+ case(dimension_shape_scalar)
+ call GetVar(ncid, name, data_scalar)
+ call fates_params%SetData(i, data_scalar)
+ case(dimension_shape_1d)
+ call GetVar(ncid, name, data1d)
+ call fates_params%SetData(i, data1d)
+ case(dimension_shape_2d)
+ call GetVar(ncid, name, data2d)
+ call fates_params%SetData(i, data2d)
+ case default
+ write(*, '(a,a)') 'dimension shape:', dimension_shape
+ write(*, '(a)') 'unsupported number of dimensions reading parameters.'
+ stop
+ end select
+ end do
+
+ if (allocated(data1d)) deallocate(data1d)
+ if (allocated(data2d)) deallocate(data2d)
+
+ call CloseNCFile(ncid)
+
+ end subroutine ReadParameters
+
+ ! --------------------------------------------------------------------------------------
+
+ subroutine RetrieveParameters(this)
+ !
+ ! DESCRIPTION:
+ ! Read in fates parameters
+ !
+ ! ARGUMENTS:
+ class(fates_unit_test_param_reader), intent(in) :: this ! parameter reader class
+
+ ! LOCALS:
+ class(fates_parameters_type), allocatable :: fates_params ! fates parameters (for non-pft parameters)
+ class(fates_parameters_type), allocatable :: fates_pft_params ! fates parameters (for pft parameters)
+
+ ! allocate and read in parameters
+ allocate(fates_params)
+ allocate(fates_pft_params)
+ call fates_params%Init()
+ call fates_pft_params%Init()
+
+ call EDPftvarcon_inst%Init()
+
+ call FatesRegisterParams(fates_params)
+ call SpitFireRegisterParams(fates_params)
+ call PRTRegisterParams(fates_params)
+ call FatesSynchronizedParamsInst%RegisterParams(fates_params)
+ call EDPftvarcon_inst%Register(fates_pft_params)
+
+ call this%Read(fates_params)
+ call this%Read(fates_pft_params)
+
+ call FatesReceiveParams(fates_params)
+ call SpitFireReceiveParams(fates_params)
+ call PRTReceiveParams(fates_params)
+ call FatesSynchronizedParamsInst%ReceiveParams(fates_params)
+ call EDPftvarcon_inst%Receive(fates_pft_params)
+
+ call fates_params%Destroy()
+ call fates_pft_params%Destroy()
+ deallocate(fates_params)
+ deallocate(fates_pft_params)
+
+ ! initialize derived parameters
+ call param_derived%Init(size(prt_params%wood_density, dim=1))
+
+ end subroutine RetrieveParameters
+
+ ! --------------------------------------------------------------------------------------
+
+ subroutine SetParameterDimensions(ncid, fates_params)
+ !
+ ! DESCRIPTION:
+ ! Gets and sets the parameter dimensions for the fates parameters class
+ !
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file ID
+ class(fates_parameters_type), intent(inout) :: fates_params ! fates parameters class
+
+ ! LOCALS:
+ integer :: num_used_dimensions ! total number of dimensions
+ character(len=param_string_length) :: used_dimension_names(max_used_dimensions) ! dimension names
+ integer :: used_dimension_sizes(max_used_dimensions) ! dimension sizes
+
+ call fates_params%GetUsedDimensions(.false., num_used_dimensions, used_dimension_names)
+
+ call GetUsedDimensionSizes(ncid, num_used_dimensions, used_dimension_names, &
+ used_dimension_sizes)
+
+ call fates_params%SetDimensionSizes(.false., num_used_dimensions, &
+ used_dimension_names, used_dimension_sizes)
+
+ end subroutine SetParameterDimensions
+
+ ! --------------------------------------------------------------------------------------
+
+ subroutine GetUsedDimensionSizes(ncid, num_used_dimensions, dimension_names, dimension_sizes)
+ !
+ ! DESCRIPTION:
+ ! Gets dimension sizes for parameters
+ !
+ ! ARGUMENTS:
+ integer, intent(in) :: ncid ! netcdf file ID
+ integer, intent(in) :: num_used_dimensions ! number of dimensions
+ character(len=param_string_length), intent(in) :: dimension_names(:) ! dimension names
+ integer, intent(out) :: dimension_sizes(:) ! dimension sizes
+
+ ! LOCALS
+ integer :: d
+ integer :: dim_id
+
+ dimension_sizes(:) = 0
+ do d = 1, num_used_dimensions
+ call GetDimID(ncid, dimension_names(d), dim_id)
+ call GetDimLen(ncid, dim_id, dimension_sizes(d))
+ end do
+
+ end subroutine GetUsedDimensionSizes
+
+end module FatesUnitTestParamReaderMod
\ No newline at end of file
diff --git a/testing/unit_testing/fire_weather_test/CMakeLists.txt b/testing/unit_testing/fire_weather_test/CMakeLists.txt
new file mode 100644
index 0000000000..2a3554cd86
--- /dev/null
+++ b/testing/unit_testing/fire_weather_test/CMakeLists.txt
@@ -0,0 +1,5 @@
+set(pfunit_sources test_FireWeather.pf)
+
+add_pfunit_ctest(FireWeather
+ TEST_SOURCES "${pfunit_sources}"
+ LINK_LIBRARIES fates csm_share)
\ No newline at end of file
diff --git a/testing/unit_testing/fire_weather_test/test_FireWeather.pf b/testing/unit_testing/fire_weather_test/test_FireWeather.pf
new file mode 100644
index 0000000000..c5111394bc
--- /dev/null
+++ b/testing/unit_testing/fire_weather_test/test_FireWeather.pf
@@ -0,0 +1,67 @@
+module test_FireWeather
+ !
+ ! DESCRIPTION:
+ ! Test the FATES fire weather portion of the SPITFIRE model
+ !
+ use FatesConstantsMod, only : r8 => fates_r8
+ use SFFireWeatherMod, only : fire_weather
+ use SFNesterovMod, only : nesterov_index
+ use funit
+
+ implicit none
+
+ @TestCase
+ type, extends(TestCase) :: TestFireWeather
+
+ class(fire_weather), allocatable :: fireWeatherNesterov
+
+ contains
+ procedure :: setUp
+ procedure :: tearDown
+ end type TestFireWeather
+
+ real(r8), parameter :: tol = 1.e-13_r8
+
+ contains
+
+ subroutine setUp(this)
+ class(TestFireWeather), intent(inout) :: this
+ allocate(nesterov_index :: this%fireWeatherNesterov)
+ call this%fireWeatherNesterov%Init()
+ end subroutine setUp
+
+ subroutine tearDown(this)
+ class(TestFireWeather), intent(inout) :: this
+ if (allocated(this%fireWeatherNesterov)) deallocate(this%fireWeatherNesterov)
+ end subroutine tearDown
+
+ @Test
+ subroutine zero_NI_rain(this)
+ ! test that over 3 mm of rain is 0.0
+ class(TestFireWeather), intent(inout) :: this ! fire weather object
+
+ call this%fireWeatherNesterov%Update(25.0_r8, 3.1_r8, 10.0_r8, 0.0_r8)
+ @assertEqual(this%fireWeatherNesterov%fire_weather_index, 0.0_r8, tolerance=tol)
+ this%fireWeatherNesterov%fire_weather_index = 0.0_r8
+ end subroutine zero_NI_rain
+
+ @Test
+ subroutine NI_rain_min(this)
+ ! test that at 3 mm is over zero
+ class(TestFireWeather), intent(inout) :: this ! fire weather object
+
+ call this%fireWeatherNesterov%Update(25.0_r8, 3.0_r8, 10.0_r8, 0.0_r8)
+ @assertGreaterThan(this%fireWeatherNesterov%fire_weather_index, 0.0_r8, tolerance=tol)
+ this%fireWeatherNesterov%fire_weather_index = 0.0_r8
+ end subroutine NI_rain_min
+
+ @Test
+ subroutine NI_not_negative(this)
+ ! test that NI is not negative
+ class(TestFireWeather), intent(inout) :: this ! fire weather object
+
+ call this%fireWeatherNesterov%Update(-30.0_r8, 0.0_r8, 99.0_r8, 0.0_r8)
+ @assertEqual(this%fireWeatherNesterov%fire_weather_index, 0.0_r8, tolerance=tol)
+ end subroutine NI_not_negative
+
+ end module test_FireWeather
\ No newline at end of file
diff --git a/testing/utils.py b/testing/utils.py
new file mode 100644
index 0000000000..aa6079757d
--- /dev/null
+++ b/testing/utils.py
@@ -0,0 +1,94 @@
+"""Utility functions for plotting, file checking, math equations, etc.
+"""
+
+import math
+import os
+from path_utils import add_cime_lib_to_path
+
+add_cime_lib_to_path()
+
+from CIME.utils import run_cmd_no_fail # pylint: disable=wrong-import-position,import-error,wrong-import-order
+
+def round_up(num, decimals=0):
+ """Rounds a number up
+
+ Args:
+ num (float): number to round
+ decimals (int, optional): number of decimals to round to. Defaults to 0.
+
+ Returns:
+ float: input number rounded up
+ """
+ multiplier = 10**decimals
+ return math.ceil(num * multiplier)/multiplier
+
+def truncate(num, decimals=0):
+ """Rounds a number down
+
+ Args:
+ num (float): number to round
+ decimals (int, optional): Decimals to round down to. Defaults to 0.
+
+ Returns:
+ float: number rounded down
+ """
+ multiplier = 10**decimals
+ return int(num * multiplier)/multiplier
+
+def create_nc_file(cdl_path, run_dir):
+ """Creates a netcdf file from a cdl file
+
+ Args:
+ cdl_path (str): full path to desired cdl file
+ run_dir (str): where the file should be written to
+ """
+ file_basename = os.path.basename(cdl_path).split(".")[-2]
+ file_nc_name = f"{file_basename}.nc"
+
+ file_gen_command = [
+ "ncgen -o",
+ os.path.join(run_dir, file_nc_name),
+ cdl_path
+ ]
+ out = run_cmd_no_fail(" ".join(file_gen_command), combine_output=True)
+ print(out)
+
+ return file_nc_name
+
+def copy_file(file_path, directory):
+ """Copies a file file to a desired directory
+
+ Args:
+ file_path (str): full path to file
+ dir (str): where the file should be copied to
+ """
+ file_basename = os.path.basename(file_path)
+
+ file_copy_command = [
+ "cp",
+ os.path.abspath(file_path),
+ os.path.abspath(directory)
+ ]
+ run_cmd_no_fail(" ".join(file_copy_command), combine_output=True)
+
+ return file_basename
+
+def get_color_palette(number):
+ """Generate a color pallete
+ Args:
+ number: number of colors to get - must be <= 20
+ Returns:
+ float: array of colors to use in plotting
+ """
+ if number > 20:
+ raise RuntimeError("get_color_palette: number must be <=20")
+
+ colors = [(31, 119, 180), (174, 199, 232), (255, 127, 14), (255, 187, 120),
+ (44, 160, 44), (152, 223, 138), (214, 39, 40), (255, 152, 150),
+ (148, 103, 189), (197, 176, 213), (140, 86, 75), (196, 156, 148),
+ (227, 119, 194), (247, 182, 210), (127, 127, 127), (199, 199, 199),
+ (188, 189, 34), (219, 219, 141), (23, 190, 207), (158, 218, 229)]
+
+ colors = [(red/255.0, green/255.0, blue/255.0) for red, green, blue in colors]
+
+ return colors[:number]
diff --git a/tools/make_unstruct_grid/MakeUnstructGrid.py b/tools/make_unstruct_grid/MakeUnstructGrid.py
new file mode 100644
index 0000000000..dfde3d8e03
--- /dev/null
+++ b/tools/make_unstruct_grid/MakeUnstructGrid.py
@@ -0,0 +1,317 @@
+import numpy as np
+import xarray as xr
+import matplotlib.pyplot as plt
+import matplotlib
+import matplotlib.dates as mdates
+import sys
+import code # For development: code.interact(local=locals()) code.interact(local=dict(globals(), **locals()))
+import argparse
+import math
+from scipy.io import netcdf as nc
+import xml.etree.ElementTree as et
+
+# The user specifies a couplet of domain/surface files
+# from which they want to base their new unstructured grid. Then they provide
+# a list of geographic coordinates in latitude and longitude. These coordinates
+# are sampled from the base dataset, and written to an output dataset.
+#
+# This method is certainly useful for generating small sets of unstructured
+# grid-cells. It may not be the best method for generating large sets. One
+# may want to use "ncks" (ie Charlie Zender's NCO tools) for subsetting large
+# grids. This method will arrange the new grid cells in a 1D vector, and assumes
+# the input grids are based on a 2d array of cells.
+#
+# This method will assume that the grid-cell extents of the new unstructured grids
+# match the extents of the base files. If you want finer or coarser resolution,
+# just dig up a different base file.
+#
+# This method uses nearest neighbor.
+#
+# This may have trouble on newer surface files, particularly if they have topo
+# unit information. It won't be difficult to add that type of functionality
+# if it doesn't work, I (Ryan) just haven't tried it.
+#
+# All controls over this process can be found in the xml control file. See
+# andes7x7.xml for an example.
+
+# Usage MakeUnstructGrid.py --fin=xmlfile.xml
+
+def TransferData(da_key,ds_base,ds_unst,minis,minjs,dset_type):
+
+ print(' Transferring: {}'.format(da_key))
+
+ if(dset_type=='domain'):
+ xname = 'nj'
+ yname = 'ni'
+ ny = len(minis) #nj = len(minis)
+ nx = 1 #ni
+ nv = 4
+ elif(dset_type=='surface'):
+ xname = 'lsmlat'
+ yname = 'lsmlon'
+ ny = len(minis)
+ nx = 1
+
+ #numurbl = 3 ;
+ #nlevurb = 5 ;
+ #numrad = 2 ;
+ #nchar = 256 ;
+ #nlevsoi = 10 ;
+ #time = UNLIMITED ; // (12 currently)
+ #lsmpft = 17 ;
+ #natpft = 17 ;
+
+
+
+ # Determin the data type
+ if(ds_base[da_key].dtype == 'float64'):
+ dtype_out = np.float64
+ elif(ds_base[da_key].dtype == 'int32'):
+ dtype_out = np.int32
+ elif(ds_base[da_key].dtype == 'float32'):
+ dtype_out = np.float32
+ else:
+ print('unknown data type: {}.\n Exiting'.format(ds_base[da_key].dtype))
+ exit(2)
+
+
+
+ # The lat-lon is always the last two dimensions
+ # Time is always the first dimension
+
+ # Check to see if this has spatial dimensions
+ dimlist = list(ds_base[da_key].dims)
+
+ # 2D (nj,ni)
+ # (lsmlat,lsmlon)
+ # 3D (nj, ni, nv)
+ # (x, lsmlat, lsmlon)
+ # 4D (x,y,lsmlat, lsmlon)
+
+ if(any([dim==xname for dim in dimlist]) and (len(dimlist)==2)):
+
+ # This is 2D and they are or use geographic coordinates
+
+ ds_unst[da_key] = \
+ xr.DataArray(np.empty((nx,ny), dtype=dtype_out),dims=dimlist)
+
+ for k in range(len(minis)):
+ i = minis[k]
+ j = minjs[k]
+ ds_unst[da_key].loc[0,k] = ds_base[da_key].data[j,i]
+
+ elif(any([dim==xname for dim in dimlist]) and any([dim=='nv' for dim in dimlist]) ):
+
+ # This is the 3D coordinate in the domain file for vertices
+
+ ds_unst[da_key] = \
+ xr.DataArray(np.empty((nx,ny,nv), dtype=dtype_out),dims=dimlist)
+ for k in range(len(minis)):
+ i = minis[k]
+ j = minjs[k]
+ ds_unst[da_key].loc[0,k,:] = ds_base[da_key].data[j,i,:]
+
+ elif(any([dim==xname for dim in dimlist]) and len(dimlist)==3):
+
+ # This has dim==3, surface file and contains coordinates (x,lsmlat, lsmlon)
+
+ dim0 = ds_base.dims[dimlist[0]]
+ ds_unst[da_key] = \
+ xr.DataArray(np.empty((dim0,nx,ny), dtype=dtype_out),dims=dimlist)
+ for k in range(len(minis)):
+ i = minis[k]
+ j = minjs[k]
+ ds_unst[da_key].loc[:,0,k] = ds_base[da_key].data[:,j,i]
+
+ elif(any([dim==xname for dim in dimlist]) and len(dimlist)==4):
+
+ # This has dim==4, surface file and contains coordinates (x,y,lsmlat, lsmlon)
+
+ dim0 = ds_base.dims[dimlist[0]]
+ dim1 = ds_base.dims[dimlist[1]]
+ ds_unst[da_key] = \
+ xr.DataArray(np.empty((dim0,dim1,nx,ny), dtype=dtype_out),dims=dimlist)
+ for k in range(len(minis)):
+ i = minis[k]
+ j = minjs[k]
+ ds_unst[da_key].loc[:,:,0,k] = ds_base[da_key].data[:,:,j,i]
+
+ elif(len(dimlist)==1):
+ # If there is no spatial coordinate, then just copy over what is there
+ #dimsizes = tuple([ds_base.dims[txt] for txt in dimlist])
+ #ds_unst[da_key] = \
+ # xr.DataArray(np.empty(dimsizes,dtype=dtype_out),dims=dimlist)
+ #ds_unst[da_key].loc[:] = ds_base[da_key].data[:]
+ ds_unst[da_key] = ds_base[da_key]
+ else:
+ # If there is no spatial coordinate, then just copy over what is there
+
+ ds_unst[da_key] = ds_base[da_key]
+
+
+ # Once the new dataarray is created, transfer over metadata from original
+ ds_unst[da_key].attrs = ds_base[da_key].attrs
+
+ return(ds_unst)
+
+
+def main(argv):
+
+ parser = argparse.ArgumentParser(description='Parse command line arguments to this script.')
+ parser.add_argument('--fin', dest='xmlfile', type=str, help="path to the xml control file",required=True)
+ args = parser.parse_args()
+
+ xmlroot = et.parse(args.xmlfile).getroot()
+
+ print(' -------------------------------------------------------------- ')
+ print('\n Creating a new domain/surface couplet \n')
+ print(' --------------------------------------------------------------\n ')
+
+ # Get the domain base name
+ try:
+ domain_base = xmlroot.find('domain_base').text.strip()
+ domain_base_file = domain_base.split('/')[-1]
+
+ except:
+ print('Could not find xml entry: {}'.format('domain_base'))
+ exit(2)
+
+ # Get the new unstructured domain name (ie output)
+ try:
+ domain_unst = xmlroot.find('domain_unst').text.strip()
+ except:
+ print('Could not find xml entry: {}'.format('domain_unst'))
+ exit(2)
+
+
+ # Get the surface base name
+ try:
+ surface_base = xmlroot.find('surface_base').text.strip()
+ surface_base_file = surface_base.split('/')[-1]
+
+ except:
+ print('Could not find xml entry: {}'.format('surface_base'))
+ exit(2)
+
+ # Get the new unstructured surface name (ie output)
+ try:
+ surface_unst = xmlroot.find('surface_unst').text.strip()
+ except:
+ print('Could not find xml entry: {}'.format('surface_unst'))
+ exit(2)
+
+
+
+ # Get a list of lon coordinates (force them into 0-360 convention)
+ try:
+ lon_subset_text = xmlroot.find('lon_list').text.strip().split(',')
+ lon_subset = []
+ for txt in lon_subset_text:
+ lon = float(txt)
+ if(lon<0.0):
+ lon = 360.0+lon
+ lon_subset.append(lon)
+
+ except:
+ print('Could not find xml entry: {}'.format('lon_list'))
+ exit(2)
+
+ # Get a list of lat coordinates
+ try:
+ lat_subset_text = xmlroot.find('lat_list').text.strip().split(',')
+ lat_subset = [float(txt) for txt in lat_subset_text]
+
+ except:
+ print('Could not find xml entry: {}'.format('lat_list'))
+ exit(2)
+
+ # Check to make sure that the lat and lons are same length
+
+ if( len(lat_subset) != len(lon_subset) ):
+ print('number of latitude subset points must match number of longitude subsets')
+ exit(2)
+ else:
+ nj = len(lat_subset)
+ print(' Found N={} lat/lon coordinates'.format(nj))
+
+
+ #code.interact(local=dict(globals(), **locals()))
+ # ------------------------------------------------------------------------------
+ # >>> ds_domain_base.data_vars
+ # Data variables:
+ #xv (nj, ni, nv) float64 358.8 1.25 1.25 358.8 ... 358.7 358.7 356.2
+ #yv (nj, ni, nv) float64 -90.0 -90.0 -89.05 -89.05 ... 89.05 90.0 90.0
+ #mask (nj, ni) int32 1 1 1 1 1 1 1 1 1 1 1 1 ... 0 0 0 0 0 0 0 0 0 0 0 0
+ #area (nj, ni) float64 5.964e-06 5.964e-06 ... 5.964e-06 5.964e-06
+ #frac (nj, ni) float64 1.0 1.0 1.0 1.0 1.0 1.0 ... 0.0 0.0 0.0 0.0 0.0
+ # >>> ds_domain_base.coords
+ #Coordinates:
+ # xc (nj, ni) float64 0.0 2.5 5.0 7.5 10.0 ... 350.0 352.5 355.0 357.5
+ # yc (nj, ni) float64 -90.0 -90.0 -90.0 -90.0 ... 90.0 90.0 90.0 90.0
+ # ------------------------------------------------------------------------------
+
+ lon_subset_faux_2d = np.reshape(lon_subset, (-1, 1))
+ lat_subset_faux_2d = np.reshape(lat_subset, (-1, 1))
+
+ ds_domain_base = xr.open_dataset(domain_base)
+
+ # Lets find the indices for xc and yc that most closely match our coordinates
+ minis = []
+ minjs = []
+ for j in range(nj):
+ lat = lat_subset[j]
+ lon = lon_subset[j]
+ delt = (ds_domain_base['xc'].data-lon)**2.0 + (ds_domain_base['yc'].data-lat)**2.0
+ minj,mini = np.unravel_index(delt.argmin(), delt.shape)
+ minis.append(mini)
+ minjs.append(minj)
+
+ # Domain Processing
+ # ===========================================================================================
+ # Initialize the new dataset
+ ds_domain_unst = xr.Dataset(
+ attrs=ds_domain_base.attrs,
+ )
+ ds_domain_unst.attrs["modification"]="Modified with SurfToVec.py, based on {}.".format(domain_base_file)
+
+ #ode.interact(local=dict(globals(), **locals()))
+
+ # Loop through existing datasets, allocate new arrays
+ # and transfer over point data
+ for da_key in ds_domain_base.data_vars:
+ ds_domain_unst = TransferData(da_key,ds_domain_base,ds_domain_unst,minis,minjs,'domain')
+
+ for da_key in ds_domain_base.coords:
+ ds_domain_unst = TransferData(da_key,ds_domain_base,ds_domain_unst,minis,minjs,'domain')
+
+ print('\n Writing: {}'.format(domain_unst))
+ ds_domain_unst.to_netcdf(domain_unst) #,mode='a')
+
+ # Surface Processing
+ # ===========================================================================================
+
+ ds_surface_base = xr.open_dataset(surface_base)
+
+ # Initialize the new dataset
+ ds_surface_unst = xr.Dataset(
+ attrs=ds_surface_base.attrs,
+ )
+ ds_surface_unst.attrs["modification"]="Modified with SurfToVec.py, based on {}.".format(surface_base_file)
+
+
+ # Loop through existing datasets, allocate new arrays
+ # and transfer over point data
+ for da_key in ds_surface_base.data_vars:
+ ds_surface_unst = TransferData(da_key,ds_surface_base,ds_surface_unst,minis,minjs,'surface')
+
+ for da_key in ds_surface_base.coords:
+ ds_surface_unst = TransferData(da_key,ds_surface_base,ds_surface_unst,minis,minjs,'surface')
+
+ print('\n Writing: {}'.format(surface_unst))
+ ds_surface_unst.to_netcdf(surface_unst) #,mode='a')
+
+ print('\n')
+
+# This is the actual call to main
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/tools/make_unstruct_grid/andes7x7.xml b/tools/make_unstruct_grid/andes7x7.xml
new file mode 100644
index 0000000000..ae99882ccd
--- /dev/null
+++ b/tools/make_unstruct_grid/andes7x7.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+ Bases/domain.lnd.fv1.9x2.5_gx1v6.090206.nc
+ Bases/surfdata_1.9x2.5_simyr2000_c180306.nc
+
+
+ SAHydroStress/domain.lnd.fv1.9x2.5_gx1v6_SAHydroStress_c240320.nc
+ SAHydroStress/surfdata_1.9x2.5_simyr2000_SAHydroStress_c240320.nc
+
+
+
+ -19.8947368421, -19.8947368421, -19.8947368421,
+ -18, -18, -18, -18,
+ -16.1052631579, -16.1052631579, -16.1052631579, -16.1052631579, -16.1052631579,
+ -14.2105263158, -14.2105263158, -14.2105263158, -14.2105263158, -14.2105263158,
+ -12.3157894737, -12.3157894737, -12.3157894737, -12.3157894737, -12.3157894737, -12.3157894737,
+ -10.4210526316, -10.4210526316, -10.4210526316, -10.4210526316, -10.4210526316, -10.4210526316,
+ -8.52631578947, -8.52631578947, -8.52631578947, -8.52631578947, -8.52631578947, -8.52631578947, -8.52631578947
+
+
+
+
+
+ 290, 292.5, 295,
+ 287.5, 290, 292.5, 295,
+ 285, 287.5, 290, 292.5, 295,
+ 285, 287.5, 290, 292.5, 295,
+ 282.5, 285, 287.5, 290, 292.5, 295,
+ 282.5, 285, 287.5, 290, 292.5, 295,
+ 280, 282.5, 285, 287.5, 290, 292.5, 295
+
+
+