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 + + +