diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index 408ff9b573..c3f1866bf4 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -4071,7 +4071,16 @@ sub setup_logic_fire_emis { if ( &value_is_true( $nl_flags->{'use_fates'} ) ) { $log->warning("Fire emission option $var can NOT be on when FATES is also on.\n" . " DON'T use the '--fire_emis' option when '--bgc fates' is activated"); - } + } elsif ( ! &value_is_true( $nl_flags->{'use_cn'} ) ) { + $log->fatal_error("Fire emission option $var can NOT be on when BGC SP (i.e. Satellite Phenology) is also on.\n" . + " DON'T use the '--fire_emis' option when '--bgc sp' is activated"); + } elsif ( &value_is_true( $nl_flags->{'use_cn'}) ) { + my $fire_method = remove_leading_and_trailing_quotes( $nl->get_value('fire_method') ); + if ( $fire_method eq "nofire" ) { + $log->fatal_error("Fire emission option $var can NOT be on with BGC and fire_method=='nofire'.\n" . + " DON'T use the '--fire_emis' option when fire_method is nofire"); + } + } } } } @@ -4235,7 +4244,7 @@ sub setup_logic_lai_streams { if ( &value_is_true($nl_flags->{'use_crop'}) && &value_is_true($nl->get_value('use_lai_streams')) ) { $log->fatal_error("turning use_lai_streams on is incompatable with use_crop set to true."); } - if ( $nl_flags->{'bgc_mode'} eq "sp" || ($nl_flags->{'bgc_mode'} eq "fates" && &value_is_true($nl->get_value('use_fates_sp')) )) { + if ( $nl_flags->{'bgc_mode'} eq "sp" || ($nl_flags->{'bgc_mode'} eq "fates" && &value_is_true($nl_flags->{'use_fates_sp'}) )) { if ( &value_is_true($nl->get_value('use_lai_streams')) ) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_lai_streams'); add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'lai_mapalgo', @@ -4745,29 +4754,26 @@ sub setup_logic_fates { # For FATES SP mode make sure no-competetiion, and fixed-biogeography are also set # And also check for other settings that can't be trigged on as well # - my $var = "use_fates_sp"; - if ( defined($nl->get_value($var)) ) { - if ( &value_is_true($nl->get_value($var)) ) { - my @list = ( "use_fates_nocomp", "use_fates_fixed_biogeog" ); - foreach my $var ( @list ) { - if ( ! &value_is_true($nl->get_value($var)) ) { - $log->fatal_error("$var is required when FATES SP is on (use_fates_sp)" ); - } - } - # spit-fire can't be on with FATES SP mode is active - if ( $nl->get_value('fates_spitfire_mode') > 0 ) { - $log->fatal_error('fates_spitfire_mode can NOT be set to greater than 0 when use_fates_sp is true'); - } + if ( &value_is_true($nl_flags->{'use_fates_sp'}) ) { + my @list = ( "use_fates_nocomp", "use_fates_fixed_biogeog" ); + foreach my $var ( @list ) { + if ( ! &value_is_true($nl->get_value($var)) ) { + $log->fatal_error("$var is required when FATES SP is on (use_fates_sp)" ); + } + } + # spit-fire can't be on with FATES SP mode is active + if ( $nl->get_value('fates_spitfire_mode') > 0 ) { + $log->fatal_error('fates_spitfire_mode can NOT be set to greater than 0 when use_fates_sp is true'); + } - # fates landuse can't be on with FATES SP mode is active - if ( &value_is_true($nl->get_value('use_fates_luh')) ) { - $log->fatal_error('use_fates_luh can NOT be true when use_fates_sp is true'); - } + # fates landuse can't be on with FATES SP mode is active + if ( &value_is_true($nl->get_value('use_fates_luh')) ) { + $log->fatal_error('use_fates_luh can NOT be true when use_fates_sp is true'); + } - # hydro isn't currently supported to work when FATES SP mode is active - if (&value_is_true( $nl->get_value('use_fates_planthydro') )) { - $log->fatal_error('fates sp mode is currently not supported to work with fates hydro'); - } + # hydro isn't currently supported to work when FATES SP mode is active + if (&value_is_true( $nl->get_value('use_fates_planthydro') )) { + $log->fatal_error('fates sp mode is currently not supported to work with fates hydro'); } } my $var = "use_fates_inventory_init"; @@ -4792,6 +4798,13 @@ sub setup_logic_fates { } } } + # Check that both FaTES-SP and FATES ST3 aren't both on + my $var = "use_fates_ed_st3"; + if ( defined($nl->get_value($var)) ) { + if ( &value_is_true($nl->get_value($var)) && &value_is_true($nl_flags->{'use_fates_sp'}) ) { + $log->fatal_error("$var can NOT also be true with use_fates_sp true" ); + } + } # check that fates landuse change mode has the necessary luh2 landuse timeseries data # and add the default if not defined. Do not add default if use_fates_potentialveg is true. # If fixed biogeography is on, make sure that flandusepftdat is avilable. diff --git a/bld/unit_testers/build-namelist_test.pl b/bld/unit_testers/build-namelist_test.pl index 19e7af11c1..6892638a21 100755 --- a/bld/unit_testers/build-namelist_test.pl +++ b/bld/unit_testers/build-namelist_test.pl @@ -163,10 +163,10 @@ sub cat_and_create_namelistinfile { # # Figure out number of tests that will run # -my $ntests = 3276; +my $ntests = 3285; if ( defined($opts{'compare'}) ) { - $ntests += 1987; + $ntests += 2161; } plan( tests=>$ntests ); @@ -288,7 +288,7 @@ sub cat_and_create_namelistinfile { &make_config_cache($phys); my @mfiles = ( "lnd_in", "drv_flds_in", $tempfile ); my $mfiles = NMLTest::CompFiles->new( $cwd, @mfiles ); -foreach my $options ( "-drydep", "-megan", "-drydep -megan", "-fire_emis", "-drydep -megan -fire_emis" ) { +foreach my $options ( "-drydep --bgc sp", "-megan --bgc sp", "-drydep -megan --bgc bgc", "-fire_emis --bgc bgc", "-drydep -megan -fire_emis --bgc bgc" ) { &make_env_run(); eval{ system( "$bldnml -envxml_dir . $options > $tempfile 2>&1 " ); }; is( $@, '', "options: $options" ); @@ -576,8 +576,8 @@ sub cat_and_create_namelistinfile { "--res 1.9x2.5 --bgc bgc --use_case 1850-2100_SSP2-4.5_transient --namelist '&a start_ymd=19101023/'", "-namelist \"&a dust_emis_method='Zender_2003', zender_soil_erod_source='lnd' /'\"", "-bgc bgc -use_case 2000_control -namelist \"&a fire_method='nofire'/\" -crop", - "-res 0.9x1.25 -bgc sp -use_case 1850_noanthro_control -drydep -fire_emis", - "-res 0.9x1.25 -bgc bgc -use_case 1850_noanthro_control -drydep -fire_emis -light_res 360x720", + "-res 0.9x1.25 -bgc sp -use_case 1850_noanthro_control -drydep", + "-res 0.9x1.25 -bgc bgc -use_case 1850_noanthro_control -drydep -fire_emis -megan -light_res 360x720", "--bgc bgc --light_res none --namelist \"&a fire_method='nofire'/\"", "--bgc fates --light_res 360x720 --no-megan --namelist \"&a fates_spitfire_mode=2/\"", "--bgc fates --light_res none --no-megan --namelist \"&a fates_spitfire_mode=1/\"", @@ -1093,6 +1093,10 @@ sub cat_and_create_namelistinfile { namelst=>"suplnitro='NONE'", phys=>"clm6_0", }, + "FATESwBothSpST3" =>{ options=>"--bgc fates --envxml_dir . --no-megan", + namelst=>"use_fates_sp = TRUE, use_fates_ed_st3 = TRUE", + phys=>"clm6_0", + }, "FireNoneButBGCfireon" =>{ options=>"-bgc bgc -envxml_dir . -light_res none", namelst=>"fire_method='li2021gswpfrc'", phys=>"clm6_0", @@ -1145,6 +1149,14 @@ sub cat_and_create_namelistinfile { namelst=>"", phys=>"clm4_5", }, + "useFIREEMISwithNOFIRE" =>{ options=>"--bgc bgc --envxml_dir . --fire_emis", + namelst=>"fire_method='nofire'", + phys=>"clm6_0", + }, + "useFIREEMISwithSP" =>{ options=>"--bgc sp --envxml_dir . --fire_emis", + namelst=>"", + phys=>"clm6_0", + }, "useDRYDEPwithFATES" =>{ options=>"--bgc fates --envxml_dir . --no-megan --drydep", namelst=>"", phys=>"clm4_5", diff --git a/cime_config/config_component.xml b/cime_config/config_component.xml index 689fbcde0d..f869d0e362 100644 --- a/cime_config/config_component.xml +++ b/cime_config/config_component.xml @@ -241,12 +241,24 @@ char - - -bgc sp - -bgc bgc - -bgc bgc -crop - -bgc fates -no-megan - -bgc fates -no-megan + + -bgc sp + -bgc bgc + -bgc bgc -crop + + + --bgc fates --no-megan --no-drydep --no-fire_emis + + + --bgc sp --no-megan --no-drydep --no-fire_emis + --bgc bgc --no-megan --no-drydep --no-fire_emis + --bgc bgc --crop --no-megan --no-drydep --no-fire_emis + -bgc bgc -dynamic_vegetation diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 70a9ba32fb..968025e244 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -219,7 +219,7 @@ - + @@ -228,7 +228,7 @@ - + @@ -406,7 +406,7 @@ - + @@ -416,7 +416,7 @@ - + @@ -425,7 +425,7 @@ - + @@ -434,7 +434,7 @@ - + @@ -443,7 +443,7 @@ - + @@ -453,7 +453,7 @@ - + @@ -462,7 +462,7 @@ - + @@ -471,7 +471,7 @@ - + @@ -480,7 +480,7 @@ - + @@ -546,7 +546,7 @@ - + @@ -1261,7 +1261,7 @@ - + @@ -1289,7 +1289,7 @@ - + @@ -1298,7 +1298,7 @@ - + @@ -1758,7 +1758,7 @@ - + @@ -1776,7 +1776,7 @@ - + @@ -1827,7 +1827,7 @@ - + @@ -1847,7 +1847,7 @@ - + @@ -1856,7 +1856,7 @@ - + @@ -2318,7 +2318,7 @@ - + @@ -2865,7 +2865,7 @@ - + @@ -3597,7 +3597,7 @@ - + @@ -3607,7 +3607,7 @@ - + @@ -3618,7 +3618,7 @@ - + diff --git a/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods index 1e4ddf5337..bc8c80f140 100644 --- a/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/ExcessIceStreams/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods index 1e4ddf5337..bc8c80f140 100644 --- a/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/SNICARFRC/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods index acdaa462fc..821b73c2e0 100644 --- a/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/collapse_pfts_78_to_16_decStart_f10/include_user_mods @@ -1 +1,2 @@ +../nofireemis ../decStart diff --git a/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/o3lombardozzi2015/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/pauseResume/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods index 1e4ddf5337..bc8c80f140 100644 --- a/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/prescribed/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/pts/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods index 1e4ddf5337..d3df58a6b3 100644 --- a/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/waccmx_offline/include_user_mods @@ -1,2 +1,2 @@ -../default ../nofireemis +../default diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9707af4f0b..2682775ca5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -45,6 +45,8 @@ add_subdirectory(${CLM_ROOT}/share/unit_test_stubs/util csm_share_stubs) list ( APPEND drv_sources_needed ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/glc_elevclass_mod.F90 ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/shr_dust_emis_mod.F90 + ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/shr_expr_parser_mod.F90 + ${CLM_ROOT}/components/cmeps/cesm/nuopc_cap_share/shr_fire_emis_mod.F90 ) # Add CLM source directories diff --git a/src/biogeochem/CMakeLists.txt b/src/biogeochem/CMakeLists.txt index 270e85838b..3da0a2eab6 100644 --- a/src/biogeochem/CMakeLists.txt +++ b/src/biogeochem/CMakeLists.txt @@ -2,6 +2,7 @@ # source files that are currently used in unit tests list(APPEND clm_sources + ch4varcon.F90 CNSharedParamsMod.F90 CNPhenologyMod.F90 CNSpeciesMod.F90 @@ -12,6 +13,14 @@ list(APPEND clm_sources DustEmisFactory.F90 CropReprPoolsMod.F90 CropType.F90 + CNFireBaseMod.F90 + CNFireNoFireMod.F90 + CNFireFactoryMod.F90 + CNFireLi2014Mod.F90 + CNFireLi2016Mod.F90 + CNFireLi2021Mod.F90 + CNFireLi2024Mod.F90 + CNVegMatrixMod.F90 CNVegStateType.F90 CNVegCarbonStateType.F90 CNVegCarbonFluxType.F90 @@ -20,6 +29,11 @@ list(APPEND clm_sources CNVegNitrogenFluxType.F90 CNCIsoAtmTimeSeriesReadMod.F90 CNVegComputeSeedMod.F90 + FATESFireBase.F90 + FATESFireDataMod.F90 + FATESFireFactoryMod.F90 + FATESFireNoDataMod.F90 + SatellitePhenologyMod.F90 SpeciesBaseType.F90 SpeciesIsotopeType.F90 SpeciesNonIsotopeType.F90 diff --git a/src/biogeochem/CNDriverMod.F90 b/src/biogeochem/CNDriverMod.F90 index b407e07ad5..9a51d9f616 100644 --- a/src/biogeochem/CNDriverMod.F90 +++ b/src/biogeochem/CNDriverMod.F90 @@ -60,7 +60,7 @@ module CNDriverMod contains !----------------------------------------------------------------------- - subroutine CNDriverInit(bounds, NLFilename, cnfire_method) + subroutine CNDriverInit(bounds, NLFilename) ! ! !DESCRIPTION: ! Initialzation of the CN Ecosystem dynamics. @@ -68,18 +68,15 @@ subroutine CNDriverInit(bounds, NLFilename, cnfire_method) ! !USES: use CNSharedParamsMod , only : use_fun use CNPhenologyMod , only : CNPhenologyInit - use FireMethodType , only : fire_method_type use SoilBiogeochemCompetitionMod, only : SoilBiogeochemCompetitionInit ! ! !ARGUMENTS: type(bounds_type) , intent(in) :: bounds character(len=*) , intent(in) :: NLFilename ! Namelist filename - class(fire_method_type) , intent(inout) :: cnfire_method !----------------------------------------------------------------------- call SoilBiogeochemCompetitionInit(bounds) if(use_cn)then call CNPhenologyInit(bounds) - call cnfire_method%FireInit(bounds, NLFilename) end if end subroutine CNDriverInit diff --git a/src/biogeochem/CNFireBaseMod.F90 b/src/biogeochem/CNFireBaseMod.F90 index 2f9e99ea44..42a054b44c 100644 --- a/src/biogeochem/CNFireBaseMod.F90 +++ b/src/biogeochem/CNFireBaseMod.F90 @@ -85,13 +85,17 @@ module CNFireBaseMod private ! !PRIVATE MEMBER DATA: ! !PUBLIC MEMBER DATA (used by extensions of the base class): - real(r8), public, pointer :: btran2_patch (:) ! patch root zone soil wetness factor (0 to 1) + real(r8), public, pointer :: btran2_patch (:) => NULL() ! patch root zone soil wetness factor (0 to 1) contains ! ! !PUBLIC MEMBER FUNCTIONS: + procedure, public :: CNFireInit ! Initialization of Fire procedure, public :: FireInit => CNFireInit ! Initialization of Fire - procedure, public :: FireReadNML ! Read in namelist for CNFire + procedure, public :: CNFireCleanBase ! Deallocate fire data + procedure, public :: FireClean => CNFireCleanBase ! Deallocate fire data + procedure, public :: CNFireReadNML ! Read in namelist for CNFire + procedure, public :: FireReadNML => CNFireReadNML ! Read in namelist for CNFire procedure, public :: CNFireReadParams ! Read in constant parameters from the paramsfile procedure, public :: CNFireFluxes ! Calculate fire fluxes procedure, public :: CNFire_calc_fire_root_wetness_Li2014 ! Calculate CN-fire specific root wetness: original version @@ -129,17 +133,16 @@ end function need_lightning_and_popdens_interface contains !----------------------------------------------------------------------- - subroutine CNFireInit( this, bounds, NLFilename ) + subroutine CNFireInit( this, bounds ) ! ! !DESCRIPTION: ! Initialize CN Fire module ! !ARGUMENTS: class(cnfire_base_type) :: this type(bounds_type), intent(in) :: bounds - character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- ! Call the base-class Initialization method - call this%BaseFireInit( bounds, NLFilename ) + call this%BaseFireInit( bounds ) ! Allocate memory call this%InitAllocate( bounds ) @@ -185,6 +188,24 @@ subroutine InitHistory( this, bounds ) ptr_patch=this%btran2_patch, l2g_scale_type='veg') end subroutine InitHistory + !---------------------------------------------------------------------- + + subroutine CNFireCleanBase( this ) + ! + ! Deallocate data + ! + ! !ARGUMENTS: + class(cnfire_base_type) :: this + !----------------------------------------------------------------------- + ! Call the base class clean method + !call this%BaseFireClean() + + if ( associated(this%btran2_patch) )then + deallocate(this%btran2_patch) + end if + this%btran2_patch => NULL() + end subroutine CNFireCleanBase + !---------------------------------------------------------------------- subroutine CNFire_calc_fire_root_wetness_Li2014( this, bounds, & num_exposedvegp, filter_exposedvegp, num_noexposedvegp, filter_noexposedvegp, & @@ -321,7 +342,7 @@ end subroutine CNFire_calc_fire_root_wetness_Li2021 !---------------------------------------------------------------------- !---------------------------------------------------------------------- - subroutine FireReadNML( this, NLFilename ) + subroutine CNFireReadNML( this, bounds, NLFilename ) ! ! !DESCRIPTION: ! Read the namelist for CNFire @@ -331,17 +352,17 @@ subroutine FireReadNML( this, NLFilename ) use shr_nl_mod , only : shr_nl_find_group_name use spmdMod , only : masterproc, mpicom use shr_mpi_mod , only : shr_mpi_bcast - use clm_varctl , only : iulog ! ! !ARGUMENTS: class(cnfire_base_type) :: this + type(bounds_type), intent(in):: bounds !bounds character(len=*), intent(in) :: NLFilename ! Namelist filename ! ! !LOCAL VARIABLES: integer :: ierr ! error code integer :: unitn ! unit for namelist file - character(len=*), parameter :: subname = 'FireReadNML' + character(len=*), parameter :: subname = 'CNFireReadNML' character(len=*), parameter :: nmlname = 'lifire_inparm' !----------------------------------------------------------------------- real(r8) :: cli_scale, boreal_peatfire_c, pot_hmn_ign_counts_alpha @@ -361,6 +382,9 @@ subroutine FireReadNML( this, NLFilename ) borpeat_fire_soilmoist_denom, nonborpeat_fire_precip_denom if ( this%need_lightning_and_popdens() ) then + ! Read the base namelist + call this%BaseFireReadNML( bounds, NLFilename ) + cli_scale = cnfire_const%cli_scale boreal_peatfire_c = cnfire_const%boreal_peatfire_c non_boreal_peatfire_c = cnfire_const%non_boreal_peatfire_c @@ -392,9 +416,11 @@ subroutine FireReadNML( this, NLFilename ) read(unitn, nml=lifire_inparm, iostat=ierr) if (ierr /= 0) then call endrun(msg="ERROR reading "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if else call endrun(msg="ERROR could NOT find "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if call relavu( unitn ) end if @@ -447,7 +473,7 @@ subroutine FireReadNML( this, NLFilename ) end if end if - end subroutine FireReadNML + end subroutine CNFireReadNML !----------------------------------------------------------------------- subroutine CNFireFluxes (this, bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & diff --git a/src/biogeochem/CNFireFactoryMod.F90 b/src/biogeochem/CNFireFactoryMod.F90 index 44407da927..9d1962d4ff 100644 --- a/src/biogeochem/CNFireFactoryMod.F90 +++ b/src/biogeochem/CNFireFactoryMod.F90 @@ -9,17 +9,20 @@ module CNFireFactoryMod use abortutils , only : endrun use shr_log_mod , only : errMsg => shr_log_errMsg use clm_varctl , only : iulog + use shr_kind_mod , only : CS => SHR_KIND_CS implicit none save private ! ! !PUBLIC ROUTINES: - public :: CNFireReadNML ! read the fire namelist + public :: CNFireReadNML ! read the fire factory namelist to get the CN fire_method to use public :: create_cnfire_method ! create an object of class fire_method_type + ! For Unit Testing: + public :: CNFireSetFireMethod ! Set the fire_method ! !PRIVATE DATA MEMBERS: - character(len=80), private :: fire_method = "li2014qianfrc" + character(len=CS), private :: fire_method = "UNSET" character(len=*), parameter, private :: sourcefile = & __FILE__ @@ -63,9 +66,11 @@ subroutine CNFireReadNML( NLFilename ) read(unitn, nml=cnfire_inparm, iostat=ierr) if (ierr /= 0) then call endrun(msg="ERROR reading "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if else call endrun(msg="ERROR finding "//nmlname//"namelist"//errmsg(sourcefile, __LINE__)) + return end if call relavu( unitn ) end if @@ -82,7 +87,7 @@ end subroutine CNFireReadNML !----------------------------------------------------------------------- !----------------------------------------------------------------------- - subroutine create_cnfire_method( NLFilename, cnfire_method ) + subroutine create_cnfire_method( cnfire_method ) ! ! !DESCRIPTION: ! Create and return an object of fire_method_type. The particular type @@ -98,11 +103,9 @@ subroutine create_cnfire_method( NLFilename, cnfire_method ) use decompMod , only : bounds_type ! ! !ARGUMENTS: - character(len=*), intent(in) :: NLFilename ! Namelist filename class(fire_method_type), allocatable, intent(inout) :: cnfire_method ! ! !LOCAL VARIABLES: - character(len=*), parameter :: subname = 'create_cnfire_method' !----------------------------------------------------------------------- select case (trim(fire_method)) @@ -119,13 +122,29 @@ subroutine create_cnfire_method( NLFilename, cnfire_method ) allocate(cnfire_li2024_type :: cnfire_method) case default - write(iulog,*) subname//' ERROR: unknown method: ', fire_method - call endrun(msg=errMsg(sourcefile, __LINE__)) + write(iulog,*) 'Unrecognized fire_method ' // errMsg(sourcefile, __LINE__) + call endrun( msg='Unknown option for namelist item fire_method: ' // trim(fire_method) ) + ! For unit-testing, make sure a valid cnfire_method is set and return, otherwise it fails with a seg-fault + allocate(cnfire_nofire_type :: cnfire_method) + return end select - call cnfire_method%FireReadNML( NLFilename ) end subroutine create_cnfire_method !----------------------------------------------------------------------- + subroutine CNFireSetFireMethod( fire_method_in ) + ! + ! !DESCRIPTION: + ! Set the fire_method (to be used in unit testing) + ! + ! !USES: + ! !ARGUMENTS: + character(len=*), intent(IN) :: fire_method_in + + fire_method = trim(fire_method_in) + + end subroutine CNFireSetFireMethod + !----------------------------------------------------------------------- + end module CNFireFactoryMod diff --git a/src/biogeochem/CNFireNoFireMod.F90 b/src/biogeochem/CNFireNoFireMod.F90 index e0605585e9..da6f28cd0d 100644 --- a/src/biogeochem/CNFireNoFireMod.F90 +++ b/src/biogeochem/CNFireNoFireMod.F90 @@ -8,6 +8,8 @@ module CNFireNoFireMod ! ! !USES: use shr_kind_mod , only : r8 => shr_kind_r8 + use abortutils , only : endrun + use clm_varctl , only : iulog use decompMod , only : bounds_type use atm2lndType , only : atm2lnd_type use CNVegStateType , only : cnveg_state_type @@ -36,10 +38,15 @@ module CNFireNoFireMod contains ! ! !PUBLIC MEMBER FUNCTIONS: - procedure, public :: need_lightning_and_popdens - procedure, public :: CNFireArea ! Calculate fire area + procedure, public :: need_lightning_and_popdens ! If need lightning and/or population density (always .false. here) + procedure, public :: NoFireInit ! Initiialization + procedure, public :: FireInit => NoFireInit ! Initiialization + procedure, public :: CNFireArea ! Calculate fire area end type cnfire_nofire_type + character(len=*), parameter, private :: sourcefile = & + __FILE__ + contains !----------------------------------------------------------------------- @@ -56,6 +63,28 @@ function need_lightning_and_popdens(this) need_lightning_and_popdens = .false. end function need_lightning_and_popdens + !----------------------------------------------------------------------- + subroutine NoFireInit( this, bounds ) + ! + ! !DESCRIPTION: + ! Initialize No Fire module + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n + use shr_log_mod , only : errMsg => shr_log_errMsg + ! !ARGUMENTS: + class(cnfire_nofire_type) :: this + type(bounds_type), intent(in) :: bounds + + if ( shr_fire_emis_mechcomps_n > 0) then + write(iulog,*) "Fire emissions can NOT be active for fire_method=nofire" // & + errMsg(sourcefile, __LINE__) + call endrun(msg="Having fire emissions on requires fire_method to be something besides nofire" ) + return + end if + call this%CNFireInit( bounds ) + + end subroutine NoFireInit + !----------------------------------------------------------------------- + !----------------------------------------------------------------------- subroutine CNFireArea (this, bounds, num_soilc, filter_soilc, num_soilp, filter_soilp, & num_exposedvegp, filter_exposedvegp, num_noexposedvegp, filter_noexposedvegp, & diff --git a/src/biogeochem/CNVegetationFacade.F90 b/src/biogeochem/CNVegetationFacade.F90 index 47099708f4..b47237690d 100644 --- a/src/biogeochem/CNVegetationFacade.F90 +++ b/src/biogeochem/CNVegetationFacade.F90 @@ -204,10 +204,12 @@ subroutine Init(this, bounds, NLFilename, nskip_steps, params_ncid) ! ! !USES: use CNFireFactoryMod , only : create_cnfire_method + use CNFireNoFireMod , only : cnfire_nofire_type use clm_varcon , only : c13ratio, c14ratio use ncdio_pio , only : file_desc_t use filterMod , only : filter use decompMod , only : get_proc_clumps + ! ! !ARGUMENTS: class(cn_vegetation_type), intent(inout) :: this @@ -302,10 +304,21 @@ subroutine Init(this, bounds, NLFilename, nskip_steps, params_ncid) ! use_cndv is true so that it can be used in associate statements (nag compiler ! complains otherwise) call this%dgvs_inst%Init(bounds) - end if - call create_cnfire_method(NLFilename, this%cnfire_method) - call this%cnfire_method%CNFireReadParams( params_ncid ) + call create_cnfire_method( this%cnfire_method ) + call this%cnfire_method%FireInit( bounds ) + call this%cnfire_method%FireReadNML( bounds, NLFilename ) + call this%cnfire_method%CNFireReadParams( params_ncid ) + end if + + ! + ! For FATES we HAVE to allocate a cnfire_method even through it won't be used + ! cnfire_method is passed down to CN routines that are used for FATES + ! so there has to be something allocated that is passed down + ! + if ( use_fates_bgc )then + allocate(cnfire_nofire_type :: this%cnfire_method) + end if end subroutine Init @@ -584,7 +597,7 @@ subroutine Init2(this, bounds, NLFilename) character(len=*), parameter :: subname = 'Init2' !----------------------------------------------------------------------- - call CNDriverInit(bounds, NLFilename, this%cnfire_method) + call CNDriverInit(bounds, NLFilename) if (use_cndv) then call dynCNDV_init(bounds, this%dgvs_inst) diff --git a/src/biogeochem/FATESFireFactoryMod.F90 b/src/biogeochem/FATESFireFactoryMod.F90 index 0352994e5f..94e3eee4c3 100644 --- a/src/biogeochem/FATESFireFactoryMod.F90 +++ b/src/biogeochem/FATESFireFactoryMod.F90 @@ -42,7 +42,8 @@ subroutine create_fates_fire_data_method( fates_fire_data_method ) ! The particular type is determined based on a namelist parameter. ! ! !USES: - use clm_varctl, only: fates_spitfire_mode + use clm_varctl, only: fates_spitfire_mode, use_fates_sp, use_fates_ed_st3 + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n use FATESFireBase, only: fates_fire_base_type use FATESFireNoDataMod, only: fates_fire_no_data_type use FATESFireDataMod, only: fates_fire_data_type @@ -51,25 +52,69 @@ subroutine create_fates_fire_data_method( fates_fire_data_method ) class(fates_fire_base_type), allocatable, intent(inout) :: fates_fire_data_method ! function result ! ! !LOCAL VARIABLES: - integer :: current_case - - character(len=*), parameter :: subname = 'create_fates_fire_data_method' !----------------------------------------------------------------------- - current_case = fates_spitfire_mode - - select case (current_case) + ! + ! For FATES options that bypass fire... + ! + if ( use_fates_sp .or. use_fates_ed_st3 )then + ! + ! Make sure fire-emissions is NOT on + ! + if ( shr_fire_emis_mechcomps_n > 0 )then + if ( use_fates_sp )then + write(iulog,*) "Fire emissions can NOT be on with FATES-SP mode: ", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission with FATES requires FATES to NOT be in Satellite Phenology (SP) mode" ) + else if ( use_fates_ed_st3 )then + write(iulog,*) "Fire emissions can NOT be on with FATES ST3 mode: ", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission with FATES requires FATES to NOT be in Static Stand Structure mode" ) + end if + ! For unit-testing return with a FATESFireData type, so there isn't a run-time error + ! Also do the FATESFireData type, as using FATESFireNoData type will fail with an error + allocate(fates_fire_data_type :: fates_fire_data_method) + return + end if + allocate(fates_fire_no_data_type :: fates_fire_data_method) + else + ! + ! For regular FATES options that include fire + ! + select case (fates_spitfire_mode) - case (no_fire:scalar_lightning) - allocate(fates_fire_no_data_type :: fates_fire_data_method) - case (lightning_from_data:anthro_suppression) - allocate(fates_fire_data_type :: fates_fire_data_method) + ! No-fire, scalar-lightning and successful_ignitions ALL do NOT need input data from the base class + case (no_fire:scalar_lightning) + allocate(fates_fire_no_data_type :: fates_fire_data_method) + case (successful_ignitions) + allocate(fates_fire_no_data_type :: fates_fire_data_method) + ! Lightning from data, and the anthro types (ignition and suppression) need lightning data from the base class + case (lightning_from_data) + allocate(fates_fire_data_type :: fates_fire_data_method) + case (anthro_ignitions:anthro_suppression) + allocate(fates_fire_data_type :: fates_fire_data_method) - case default - write(iulog,*) subname//' ERROR: unknown method: ', fates_spitfire_mode - call endrun(msg=errMsg(sourcefile, __LINE__)) + case default + write(iulog,*) 'Unrecognized fates_spitfire_mode option = ', fates_spitfire_mode, ' in: ', & + errMsg(sourcefile, __LINE__) + call endrun(msg="Unknown option for namelist item fates_spitfire_mode:") + ! For unit-testing, make sure a valid fates_fire_data_method is set and return, otherwise it fails with a seg-fault + allocate(fates_fire_no_data_type :: fates_fire_data_method) - end select + end select + ! ------------------------------------------------------------------------------------------------------- + ! For now we die with a error whenever fire-emissions are turned on -- because this isn't setup in FATES + ! + if ( fates_spitfire_mode /= no_fire ) then + if ( shr_fire_emis_mechcomps_n > 0 )then + write(iulog,*) "Fire emissions can NOT be on with FATES currently: ", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission with FATES can NOT currently be turned on (see issue #1045)" ) + return + end if + end if + ! ------------------------------------------------------------------------------------------------------- + end if end subroutine create_fates_fire_data_method diff --git a/src/biogeochem/FATESFireNoDataMod.F90 b/src/biogeochem/FATESFireNoDataMod.F90 index 4034b68e97..65b7bae5af 100644 --- a/src/biogeochem/FATESFireNoDataMod.F90 +++ b/src/biogeochem/FATESFireNoDataMod.F90 @@ -27,6 +27,8 @@ module FATESFireNoDataMod contains ! !PUBLIC MEMBER FUNCTIONS: + procedure, public :: FATESNoFireInit! Initialization + procedure, public :: FireInit => FATESNoFireInit procedure, public :: need_lightning_and_popdens procedure, public :: GetLight24 ! Return the 24-hour averaged lightning data procedure, public :: GetGDP ! Return the global gdp data @@ -40,6 +42,28 @@ module FATESFireNoDataMod contains + !----------------------------------------------------------------------- + subroutine FATESNoFireInit( this, bounds ) + ! + ! !DESCRIPTION: + ! Initialize No Fire data module for FATES + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n + use shr_log_mod , only : errMsg => shr_log_errMsg + use clm_varctl , only : fates_spitfire_mode + ! !ARGUMENTS: + class(fates_fire_no_data_type) :: this + type(bounds_type), intent(in) :: bounds + + if ( (shr_fire_emis_mechcomps_n > 0) .and. (fates_spitfire_mode == 0) ) then + write(iulog,*) "Fire emissions can NOT be active for fates_spitfire_mode=0 (no_fire)", & + errMsg(sourcefile, __LINE__) + call endrun(msg="Having fire emissions on requires fates_spitfire_mode to be something besides no_fire (0)" ) + return + end if + call this%CNFireInit( bounds ) + + end subroutine FATESNoFireInit + !------------------------------------------------------------------------ function need_lightning_and_popdens(this) ! !ARGUMENTS: diff --git a/src/biogeochem/SatellitePhenologyMod.F90 b/src/biogeochem/SatellitePhenologyMod.F90 index ffce605e88..7747aad5fa 100644 --- a/src/biogeochem/SatellitePhenologyMod.F90 +++ b/src/biogeochem/SatellitePhenologyMod.F90 @@ -18,7 +18,8 @@ module SatellitePhenologyMod use spmdMod , only : masterproc, mpicom, iam use laiStreamMod , only : lai_init, lai_advance, lai_interp use clm_varctl , only : use_fates - use ncdio_pio + use ncdio_pio , only : ncd_pio_openfile, ncd_inqfdims, check_dim_size, ncd_io + use ncdio_pio , only : ncd_pio_closefile, file_desc_t ! ! !PUBLIC TYPES: implicit none @@ -56,6 +57,9 @@ subroutine SatellitePhenologyInit (bounds) ! ! !USES: use shr_infnan_mod, only : nan => shr_infnan_nan, assignment(=) + use shr_fire_emis_mod, only : shr_fire_emis_mechcomps_n + use shr_log_mod, only : errMsg => shr_log_errMsg + use clm_varctl, only : use_cn ! ! !ARGUMENTS: type(bounds_type), intent(in) :: bounds @@ -63,6 +67,12 @@ subroutine SatellitePhenologyInit (bounds) ! !LOCAL VARIABLES: integer :: ier ! error code !----------------------------------------------------------------------- + if ( (shr_fire_emis_mechcomps_n > 0) .and. (.not. use_cn) ) then + write(iulog,*) "Fire emissions can NOT be active for Satellite Phenology mode (SP)" // & + errMsg(sourcefile, __LINE__) + call endrun(msg="Fire emission requires BGC to be on rather than a Satelitte Pheonology (SP) case") + return + end if InterpMonths1 = -999 ! saved month index diff --git a/src/biogeochem/test/CMakeLists.txt b/src/biogeochem/test/CMakeLists.txt index e22a720523..2ebe27c76f 100644 --- a/src/biogeochem/test/CMakeLists.txt +++ b/src/biogeochem/test/CMakeLists.txt @@ -3,3 +3,5 @@ add_subdirectory(CNVegComputeSeed_test) add_subdirectory(CNPhenology_test) add_subdirectory(Latbaset_test) add_subdirectory(DustEmis_test) +add_subdirectory(CNFireFactory_test) +add_subdirectory(FATESFireFactory_test) diff --git a/src/biogeochem/test/CNFireFactory_test/CMakeLists.txt b/src/biogeochem/test/CNFireFactory_test/CMakeLists.txt new file mode 100644 index 0000000000..032e0fa953 --- /dev/null +++ b/src/biogeochem/test/CNFireFactory_test/CMakeLists.txt @@ -0,0 +1,7 @@ +set (pfunit_sources + test_CNFireFactory.pf +) + +add_pfunit_ctest(CNFireFActory + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share esmf) diff --git a/src/biogeochem/test/CNFireFactory_test/test_CNFireFactory.pf b/src/biogeochem/test/CNFireFactory_test/test_CNFireFactory.pf new file mode 100644 index 0000000000..5b0f52c8d4 --- /dev/null +++ b/src/biogeochem/test/CNFireFactory_test/test_CNFireFactory.pf @@ -0,0 +1,240 @@ +module test_CNFireFactory + + ! Tests of CNFireFactory + + use funit + use unittestSubgridMod, only : bounds + use FireMethodType , only : fire_method_type + use CNFireFactoryMod + use ESMF, only : ESMF_SUCCESS + use shr_kind_mod , only : r8 => shr_kind_r8 + use clm_varctl, only : use_cn, iulog + + implicit none + + @TestCase + type, extends(TestCase) :: TestCNFireFactory + logical :: initialized = .false. + class(fire_method_type), allocatable :: cnfire_method + contains + procedure :: setUp + procedure :: tearDown + procedure :: FireFactInit + procedure :: turn_fire_emis_on + end type TestCNFireFactory + + contains + + !----------------------------------------------------------------------- + + subroutine setUp(this) + use shr_log_mod, only : shr_log_setLogUnit + use ESMF, only : ESMF_Initialize, ESMF_IsInitialized + use shr_sys_mod, only : shr_sys_system + class(TestCNFireFactory), intent(inout) :: this + + integer :: rc + logical :: esmf_initialized + + esmf_initialized = ESMF_IsInitialized( rc=rc ) + if (rc /= ESMF_SUCCESS) then + stop 'Error in ESMF_IsInitialized' + end if + if ( .not. esmf_initialized )then + call ESMF_Initialize( rc=rc ) + if (rc /= ESMF_SUCCESS) then + stop 'Error in ESMF_Initialize' + end if + end if + use_cn = .true. + iulog = 6 + call shr_log_setLogUnit(iulog) + this%initialized = .false. + + end subroutine setUp + !----------------------------------------------------------------------- + + subroutine tearDown(this) + use shr_sys_mod, only : shr_sys_system + use shr_log_mod, only : shr_log_setLogUnit + class(TestCNFireFactory), intent(inout) :: this + + integer :: rc + + ! A clean method should be added to the fire method class structures + if ( this%initialized )then + call this%cnfire_method%FireClean() + deallocate( this%cnfire_method ) + end if + ! IMPORTANT NOTE: DO NOT CALL ESMF_Finalize HERE! + ! Calling ESMF_Finalize here, with full ESMF, means you couldn't call ESMF_Initialize again + this%initialized = .false. + + end subroutine tearDown + + !----------------------------------------------------------------------- + + subroutine FireFactInit(this, fire_method) + class(TestCNFireFactory), intent(inout) :: this + character(len=*), intent(in) :: fire_method + + if ( trim(fire_method) /= "DO_NOT_SET") then + call CNFireSetFireMethod( fire_method_in=fire_method ) + end if + call create_cnfire_method(this%cnfire_method) + call this%cnfire_method%FireInit(bounds) + this%initialized = .true. + + end subroutine FireFactInit + + !----------------------------------------------------------------------- + + subroutine turn_fire_emis_on(this) + use shr_fire_emis_mod, only : shr_fire_emis_readnl, shr_fire_emis_mechcomps_n + use shr_sys_mod, only : shr_sys_system + class(TestCNFireFactory), intent(inout) :: this + + ! NOTE!: This is bad that this can be done directly without having it done through a namelist, or setter! + shr_fire_emis_mechcomps_n = 2 + end subroutine turn_fire_emis_on + + !----------------------------------------------------------------------- + + @Test + subroutine fire_method_not_set_fails(this) + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%FireFactInit( fire_method = "DO_NOT_SET") + expected_msg = "ABORTED: Unknown option for namelist item fire_method: UNSET" + @assertExceptionRaised(expected_msg) + + end subroutine fire_method_not_set_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fire_method_bad_fails(this) + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%FireFactInit( fire_method = "ZZTOP") ! Set to an invalid option + expected_msg = "ABORTED: Unknown option for namelist item fire_method: ZZTOP" + @assertExceptionRaised(expected_msg) + + end subroutine fire_method_bad_fails + + !----------------------------------------------------------------------- + + @Test + subroutine nofire_with_fire_emis_fails(this) + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%turn_fire_emis_on() + call this%FireFactInit( fire_method = "nofire") + expected_msg = "ABORTED: Having fire emissions on requires fire_method to be something besides nofire" + @assertExceptionRaised(expected_msg) + + end subroutine nofire_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine spcase_with_fire_emis_fails(this) + use SatellitePhenologyMod, only : SatellitePhenologyInit + class(TestCNFireFactory), intent(inout) :: this + character(100) :: expected_msg + + use_cn = .false. + call this%turn_fire_emis_on() + call SatellitePhenologyInit( bounds ) + expected_msg = "ABORTED: Fire emission requires BGC to be on rather than a Satelitte Pheonology (SP) case" + @assertExceptionRaised(expected_msg) + + end subroutine spcase_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine li2014_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2014qianfrc") + + end subroutine li2014_works + + !----------------------------------------------------------------------- + + ! + ! Test that default settings with ALL of the Li Fire options work by default + ! (These tests are done one by one which makes them dead simple, but take up more code + ! see the looping option below) + ! + @Test + subroutine li2016_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2016crufrc") + + end subroutine li2016_works + + !----------------------------------------------------------------------- + + @Test + subroutine li2021_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2021gswpfrc") + + end subroutine li2021_works + + !----------------------------------------------------------------------- + + @Test + subroutine li2024_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2024gswpfrc") + + end subroutine li2024_works + + !----------------------------------------------------------------------- + + @Test + subroutine li2024crujra_works(this) + class(TestCNFireFactory), intent(inout) :: this + + call this%FireFactInit( fire_method = "li2024crujra") + + end subroutine li2024crujra_works + + !----------------------------------------------------------------------- + + ! + ! Test that default settings with ALL of the Li Fire options work when fire emissions + ! are turned on. This test is done with a loop rather than one by one as above. + ! This cuts down on the total test code, but also means that setUp and tearDown have + ! to be explicitly called for example. Setup is always called before a test, and tearDown + ! after each test) + ! + + @Test + subroutine all_li_options_with_fire_emis_works(this) + class(TestCNFireFactory), intent(inout) :: this + integer, parameter :: noptions = 5 + integer :: i + character(len=*), parameter :: fire_method_options(noptions) = (/ 'li2014qianfrc', 'li2016crufrc ', 'li2021gswpfrc', 'li2024gswpfrc', 'li2024crujra '/) + + do i = 1, noptions + call this%setUp() ! This is needed because of the loop over all options + call this%turn_fire_emis_on() + call this%FireFactInit( fire_method = fire_method_options(i) ) + call this%tearDown() ! This is needed because of the loop over all options + end do + + end subroutine all_li_options_with_fire_emis_works + + !----------------------------------------------------------------------- + +end module test_CNFireFactory diff --git a/src/biogeochem/test/FATESFireFactory_test/CMakeLists.txt b/src/biogeochem/test/FATESFireFactory_test/CMakeLists.txt new file mode 100644 index 0000000000..80ac4114e7 --- /dev/null +++ b/src/biogeochem/test/FATESFireFactory_test/CMakeLists.txt @@ -0,0 +1,7 @@ +set (pfunit_sources + test_FATESFireFactory.pf +) + +add_pfunit_ctest(FATESFireFActory + TEST_SOURCES "${pfunit_sources}" + LINK_LIBRARIES clm csm_share esmf) diff --git a/src/biogeochem/test/FATESFireFactory_test/test_FATESFireFactory.pf b/src/biogeochem/test/FATESFireFactory_test/test_FATESFireFactory.pf new file mode 100644 index 0000000000..fba39098a8 --- /dev/null +++ b/src/biogeochem/test/FATESFireFactory_test/test_FATESFireFactory.pf @@ -0,0 +1,167 @@ +module test_FATESFireFactory + + ! Tests of FATESFireFactory + + use funit + use unittestSubgridMod, only : bounds + use FATESFireBase, only : fates_fire_base_type + use FATESFireFactoryMod + use shr_kind_mod , only : r8 => shr_kind_r8, CS => shr_kind_CS + use clm_varctl, only : iulog, fates_spitfire_mode, use_fates, use_fates_sp, use_fates_ed_st3 + + implicit none + + @TestCase + type, extends(TestCase) :: TestFATESFireFactory + logical :: initialized = .false. + class(fates_fire_base_type), allocatable :: fates_fire_method + contains + procedure :: setUp + procedure :: tearDown + procedure :: FireFactInit + procedure :: turn_fire_emis_on + end type TestFATESFireFactory + + contains + + !----------------------------------------------------------------------- + + subroutine setUp(this) + use shr_log_mod, only : shr_log_setLogUnit + use ESMF, only : ESMF_Initialize + use shr_sys_mod, only : shr_sys_system + class(TestFATESFireFactory), intent(inout) :: this + + call ESMF_Initialize() + use_fates = .true. + use_fates_sp = .false. + use_fates_ed_st3 = .false. + fates_spitfire_mode = no_fire + iulog = 6 + call shr_log_setLogUnit(iulog) + this%initialized = .false. + + end subroutine setUp + !----------------------------------------------------------------------- + + subroutine tearDown(this) + use shr_sys_mod, only : shr_sys_system + use shr_log_mod, only : shr_log_setLogUnit + class(TestFATESFireFactory), intent(inout) :: this + + if ( this%initialized )then + call this%fates_fire_method%FireClean() + deallocate( this%fates_fire_method ) + end if + this%initialized = .false. + + end subroutine tearDown + + !----------------------------------------------------------------------- + + subroutine FireFactInit(this) + class(TestFATESFireFactory), intent(inout) :: this + + call create_fates_fire_data_method(this%fates_fire_method) + call this%fates_fire_method%FireInit(bounds) + this%initialized = .true. + + end subroutine FireFactInit + + !----------------------------------------------------------------------- + + subroutine turn_fire_emis_on(this) + use shr_fire_emis_mod, only : shr_fire_emis_readnl, shr_fire_emis_mechcomps_n + use shr_sys_mod, only : shr_sys_system + class(TestFATESFireFactory), intent(inout) :: this + + ! NOTE!: This is bad that this can be done directly without having it done through a namelist, or setter! + shr_fire_emis_mechcomps_n = 2 + end subroutine turn_fire_emis_on + + !----------------------------------------------------------------------- + + @Test + subroutine fates_spitfire_mode_bad_fails(this) + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + fates_spitfire_mode = -1 + call this%FireFactInit( ) + expected_msg = "ABORTED: Unknown option for namelist item fates_spitfire_mode:" + @assertExceptionRaised(expected_msg) + + end subroutine fates_spitfire_mode_bad_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fates_sp_case_with_fire_emis_fails(this) + use clm_varctl, only : use_fates_sp + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + use_fates_sp = .true. + call this%turn_fire_emis_on() + call this%FireFactInit( ) + expected_msg = "ABORTED: Fire emission with FATES requires FATES to NOT be in Satellite Phenology (SP) mode" + @assertExceptionRaised(expected_msg) + + end subroutine fates_sp_case_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fates_st3_case_with_fire_emis_fails(this) + use clm_varctl, only : use_fates_ed_st3 + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + use_fates_ed_st3 = .true. + call this%turn_fire_emis_on() + call this%FireFactInit( ) + expected_msg = "ABORTED: Fire emission with FATES requires FATES to NOT be in Static Stand Structure mode" + @assertExceptionRaised(expected_msg) + + end subroutine fates_st3_case_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine fates_no_spitfire_case_with_fire_emis_fails(this) + class(TestFATESFireFactory), intent(inout) :: this + character(100) :: expected_msg + + call this%turn_fire_emis_on() + fates_spitfire_mode = no_fire + call this%FireFactInit( ) + expected_msg = "ABORTED: Having fire emissions on requires fates_spitfire_mode to be something besides no_fire (0)" + @assertExceptionRaised(expected_msg) + + end subroutine fates_no_spitfire_case_with_fire_emis_fails + + !----------------------------------------------------------------------- + + @Test + subroutine all_fates_spitfire_options_with_fire_emis_fails(this) + class(TestFATESFireFactory), intent(inout) :: this + integer, parameter :: noptions = anthro_suppression + integer :: i + character(100) :: expected_msg + + do i = scalar_lightning, noptions + call this%setUp() + call this%turn_fire_emis_on() + fates_spitfire_mode = i + use_fates_sp = .false. + call this%FireFactInit( ) + expected_msg = "ABORTED: Fire emission with FATES can NOT currently be turned on (see issue #1045)" + @assertExceptionRaised(expected_msg) + call this%tearDown() + end do + + end subroutine all_fates_spitfire_options_with_fire_emis_fails + + !----------------------------------------------------------------------- + +end module test_FATESFireFactory diff --git a/src/cpl/share_esmf/FireDataBaseType.F90 b/src/cpl/share_esmf/FireDataBaseType.F90 index b84e3bfa33..13323cd924 100644 --- a/src/cpl/share_esmf/FireDataBaseType.F90 +++ b/src/cpl/share_esmf/FireDataBaseType.F90 @@ -26,23 +26,25 @@ module FireDataBaseType type, abstract, extends(fire_method_type) :: fire_base_type private ! !PRIVATE MEMBER DATA: - real(r8), public, pointer :: forc_hdm(:) ! Human population density - type(shr_strdata_type) :: sdat_hdm ! Human population density input data stream - real(r8), public, pointer :: forc_lnfm(:) ! Lightning frequency - type(shr_strdata_type) :: sdat_lnfm ! Lightning frequency input data stream + real(r8), public, pointer :: forc_hdm(:) => NULL() ! Human population density + type(shr_strdata_type) :: sdat_hdm ! Human population density input data stream + real(r8), public, pointer :: forc_lnfm(:) => NULL() ! Lightning frequency + type(shr_strdata_type) :: sdat_lnfm ! Lightning frequency input data stream - real(r8), public, pointer :: gdp_lf_col(:) ! col global real gdp data (k US$/capita) - real(r8), public, pointer :: peatf_lf_col(:) ! col global peatland fraction data (0-1) - integer , public, pointer :: abm_lf_col(:) ! col global peak month of crop fire emissions + real(r8), public, pointer :: gdp_lf_col(:) => NULL() ! col global real gdp data (k US$/capita) + real(r8), public, pointer :: peatf_lf_col(:) => NULL() ! col global peatland fraction data (0-1) + integer , public, pointer :: abm_lf_col(:) => NULL() ! col global peak month of crop fire emissions contains ! ! !PUBLIC MEMBER FUNCTIONS: - procedure, public :: FireInit => BaseFireInit ! Initialization of Fire procedure, public :: BaseFireInit ! Initialization of Fire + procedure, public :: FireInit => BaseFireInit ! Initialization of Fire + procedure, public :: BaseFireClean ! Clean up data and deallocate data + procedure, public :: FireClean => BaseFireClean ! Clean up data and deallocate data procedure, public :: FireInterp ! Interpolate fire data - procedure(FireReadNML_interface), public, deferred :: & - FireReadNML ! Read in namelist for Fire + procedure, public :: BaseFireReadNML ! Read in the namelist for fire + procedure, public :: ReadFireNML => BaseFireReadNML ! Read in the namelist for fire procedure(need_lightning_and_popdens_interface), public, deferred :: & need_lightning_and_popdens ! Returns true if need lightning & popdens ! @@ -78,7 +80,7 @@ end function need_lightning_and_popdens_interface contains !============================================================================== - subroutine FireReadNML_interface( this, NLFilename ) + subroutine BaseFireReadNML( this, bounds, NLFilename ) ! ! !DESCRIPTION: ! Read the namelist for Fire @@ -87,11 +89,21 @@ subroutine FireReadNML_interface( this, NLFilename ) ! ! !ARGUMENTS: class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds character(len=*), intent(in) :: NLFilename ! Namelist filename - end subroutine FireReadNML_interface + + ! Read the namelists for the fire data and do the preparation needed on them + if ( this%need_lightning_and_popdens() ) then + call this%hdm_init(bounds, NLFilename) + call this%hdm_interp(bounds) + call this%lnfm_init(bounds, NLFilename) + call this%lnfm_interp(bounds) + call this%surfdataread(bounds) + end if + end subroutine BaseFireReadNML !================================================================ - subroutine BaseFireInit( this, bounds, NLFilename ) + subroutine BaseFireInit( this, bounds ) ! ! !DESCRIPTION: ! Initialize CN Fire module @@ -101,9 +113,7 @@ subroutine BaseFireInit( this, bounds, NLFilename ) ! !ARGUMENTS: class(fire_base_type) :: this type(bounds_type), intent(in) :: bounds - character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- - if ( this%need_lightning_and_popdens() ) then ! Allocate lightning forcing data allocate( this%forc_lnfm(bounds%begg:bounds%endg) ) @@ -118,16 +128,36 @@ subroutine BaseFireInit( this, bounds, NLFilename ) allocate(this%peatf_lf_col(bounds%begc:bounds%endc)) ! Allocates peak month of crop fire emissions allocate(this%abm_lf_col(bounds%begc:bounds%endc)) - - call this%hdm_init(bounds, NLFilename) - call this%hdm_interp(bounds) - call this%lnfm_init(bounds, NLFilename) - call this%lnfm_interp(bounds) - call this%surfdataread(bounds) end if end subroutine BaseFireInit + !================================================================ + subroutine BaseFireClean( this ) + ! + ! !DESCRIPTION: + ! Clean fire data + ! !USES: + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + !----------------------------------------------------------------------- + + if ( this%need_lightning_and_popdens() ) then + deallocate( this%forc_lnfm ) + deallocate( this%forc_hdm ) + deallocate( this%gdp_lf_col ) + deallocate( this%peatf_lf_col ) + deallocate( this%abm_lf_col ) + this%forc_lnfm => NULL() + this%forc_hdm => NULL() + this%gdp_lf_col => NULL() + this%peatf_lf_col => NULL() + this%abm_lf_col => NULL() + end if + + end subroutine BaseFireClean + !================================================================ subroutine FireInterp(this,bounds) ! diff --git a/src/main/CMakeLists.txt b/src/main/CMakeLists.txt index 53a6edb8a5..fc324efeb9 100644 --- a/src/main/CMakeLists.txt +++ b/src/main/CMakeLists.txt @@ -20,6 +20,7 @@ list(APPEND clm_sources column_varcon.F90 decompMod.F90 filterColMod.F90 + FireMethodType.F90 glc2lndMod.F90 glcBehaviorMod.F90 initSubgridMod.F90 diff --git a/src/main/FireMethodType.F90 b/src/main/FireMethodType.F90 index 978450e65f..5f90dea893 100644 --- a/src/main/FireMethodType.F90 +++ b/src/main/FireMethodType.F90 @@ -34,6 +34,9 @@ module FireMethodType ! Figure out the fire fluxes procedure(CNFireFluxes_interface) , public, deferred :: CNFireFluxes + ! Deallocate the fire datasets + procedure(FireClean_interface) , public, deferred :: FireClean + end type fire_method_type abstract interface @@ -52,7 +55,7 @@ module FireMethodType ! consistent between different implementations. ! !--------------------------------------------------------------------------- - subroutine FireInit_interface(this, bounds, NLFilename ) + subroutine FireInit_interface(this, bounds ) ! ! !DESCRIPTION: ! Initialize Fire datasets @@ -63,20 +66,21 @@ subroutine FireInit_interface(this, bounds, NLFilename ) ! !ARGUMENTS: class(fire_method_type) :: this type(bounds_type), intent(in) :: bounds - character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- end subroutine FireInit_interface - subroutine FireReadNML_interface(this, NLFilename ) + subroutine FireReadNML_interface(this, bounds, NLFilename ) ! ! !DESCRIPTION: ! Read general fire namelist ! ! USES + use decompMod , only : bounds_type import :: fire_method_type ! !ARGUMENTS: class(fire_method_type) :: this + type(bounds_type), intent(in) :: bounds character(len=*), intent(in) :: NLFilename !----------------------------------------------------------------------- @@ -97,6 +101,20 @@ subroutine FireInterp_interface(this, bounds) end subroutine FireInterp_interface + !----------------------------------------------------------------------- + subroutine FireClean_interface(this) + ! + ! !DESCRIPTION: + ! Deallocate Fire datasets + ! + ! USES + import :: fire_method_type + ! !ARGUMENTS: + class(fire_method_type) :: this + !----------------------------------------------------------------------- + + end subroutine FireClean_interface + !----------------------------------------------------------------------- subroutine CNFireReadParams_interface( this, ncid ) ! diff --git a/src/soilbiogeochem/CMakeLists.txt b/src/soilbiogeochem/CMakeLists.txt index e2baa2d1b2..ac467c3e5f 100644 --- a/src/soilbiogeochem/CMakeLists.txt +++ b/src/soilbiogeochem/CMakeLists.txt @@ -2,6 +2,7 @@ # source files that are currently used in unit tests list(APPEND clm_sources + SoilBiogeochemCarbonFluxType.F90 SoilBiogeochemStateType.F90 SoilBiogeochemDecompCascadeConType.F90 SoilBiogeochemStateType.F90 diff --git a/src/unit_test_stubs/main/ncdio_pio_fake.F90.in b/src/unit_test_stubs/main/ncdio_pio_fake.F90.in index e8ef14e457..7f38565e90 100644 --- a/src/unit_test_stubs/main/ncdio_pio_fake.F90.in +++ b/src/unit_test_stubs/main/ncdio_pio_fake.F90.in @@ -48,6 +48,7 @@ module ncdio_pio public :: check_var ! determine if variable is on netcdf file public :: check_dim ! determine if dimension is on netcdf file public :: check_var_or_dim ! determine if variable or dimension is on netcdf file + public :: check_dim_size ! validity check on dimension public :: ncd_io ! do fake i/o (currently only set up to read) public :: ncd_inqvid ! inquire on a variable id public :: ncd_set_var ! set data on "file" for one variable @@ -340,6 +341,25 @@ contains end subroutine check_var_or_dim + !----------------------------------------------------------------------- + subroutine check_dim_size(ncid, dimname, value, msg) + ! + ! !DESCRIPTION: + ! Validity check on dimension + ! + ! !ARGUMENTS: + class(file_desc_t),intent(in) :: ncid ! PIO file handle + character(len=*) , intent(in) :: dimname ! Dimension name + integer, intent(in) :: value ! Expected dimension size + + character(len=*), intent(in), optional :: msg ! Optional additional message printed upon error + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + ! Does nothing assumes the dim size is as expected + + end subroutine check_dim_size !----------------------------------------------------------------------- subroutine ncd_inqdid(ncid, name, dimid, dimexist) diff --git a/src/unit_test_stubs/share_esmf/CMakeLists.txt b/src/unit_test_stubs/share_esmf/CMakeLists.txt index 1d767543ea..368601dcc8 100644 --- a/src/unit_test_stubs/share_esmf/CMakeLists.txt +++ b/src/unit_test_stubs/share_esmf/CMakeLists.txt @@ -1,5 +1,7 @@ list(APPEND clm_sources ExcessIceStreamType.F90 + FireDataBaseType.F90 + laiStreamMod.F90 PrigentRoughnessStreamType.F90 ZenderSoilErodStreamType.F90 ) diff --git a/src/unit_test_stubs/share_esmf/FireDataBaseType.F90 b/src/unit_test_stubs/share_esmf/FireDataBaseType.F90 new file mode 100644 index 0000000000..63046188a3 --- /dev/null +++ b/src/unit_test_stubs/share_esmf/FireDataBaseType.F90 @@ -0,0 +1,123 @@ +module FireDataBaseType + +#include "shr_assert.h" + + !----------------------------------------------------------------------- + ! !DESCRIPTION: + ! module for handling of fire data + ! UNIT-TEST STUB for fire data Streams + ! This just allows the fire code to be tested without + ! reading in the streams data, by faking it and setting it to a + ! constant value. + ! + ! !USES: + use shr_kind_mod , only : r8 => shr_kind_r8, CL => shr_kind_CL + use shr_log_mod , only : errMsg => shr_log_errMsg + use clm_varctl , only : iulog + use spmdMod , only : masterproc, mpicom, iam + use abortutils , only : endrun + use decompMod , only : bounds_type + use FireMethodType , only : fire_method_type + ! + implicit none + private + ! + ! !PUBLIC TYPES: + public :: fire_base_type + ! + type, abstract, extends(fire_method_type) :: fire_base_type + private + ! !PRIVATE MEMBER DATA: + real(r8), public, pointer :: forc_hdm(:) ! Human population density + real(r8), public, pointer :: forc_lnfm(:) ! Lightning frequency + real(r8), public, pointer :: gdp_lf_col(:) ! col global real gdp data (k US$/capita) + real(r8), public, pointer :: peatf_lf_col(:) ! col global peatland fraction data (0-1) + integer , public, pointer :: abm_lf_col(:) ! col global peak month of crop fire emissions + + contains + ! + ! !PUBLIC MEMBER FUNCTIONS: + procedure, public :: BaseFireInit ! Initialization of Fire + procedure, public :: FireInit => BaseFireInit ! Initialization of Fire + procedure, public :: FireInterp ! Interpolate fire data + procedure, public :: BaseFireReadNML ! Read in the namelist + procedure, public :: FireReadNML => BaseFireReadNML ! Read in the namelist + procedure(need_lightning_and_popdens_interface), public, deferred :: & + need_lightning_and_popdens ! Returns true if need lightning & popdens + + end type fire_base_type + + abstract interface + !----------------------------------------------------------------------- + function need_lightning_and_popdens_interface(this) result(need_lightning_and_popdens) + ! + ! !DESCRIPTION: + ! Returns true if need lightning and popdens, false otherwise + ! + ! USES + import :: fire_base_type + ! + ! !ARGUMENTS: + class(fire_base_type), intent(in) :: this + logical :: need_lightning_and_popdens ! function result + !----------------------------------------------------------------------- + end function need_lightning_and_popdens_interface + end interface + + character(len=*), parameter, private :: sourcefile = & + __FILE__ + +!============================================================================== +contains +!============================================================================== + + subroutine BaseFireReadNML( this, bounds, NLFilename ) + ! + ! !DESCRIPTION: + ! Read the namelist for Fire + ! + ! !USES: + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds + character(len=*), intent(in) :: NLFilename ! Namelist filename + end subroutine BaseFireReadNML + + !================================================================ + subroutine BaseFireInit( this, bounds ) + ! + ! !DESCRIPTION: + ! Initialize CN Fire module + ! !USES: + use shr_infnan_mod , only : nan => shr_infnan_nan, assignment(=) + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds + !----------------------------------------------------------------------- + + if ( this%need_lightning_and_popdens() ) then + + end if + + end subroutine BaseFireInit + + !================================================================ + subroutine FireInterp(this,bounds) + ! + ! !DESCRIPTION: + ! Interpolate CN Fire datasets + ! + ! !ARGUMENTS: + class(fire_base_type) :: this + type(bounds_type), intent(in) :: bounds + !----------------------------------------------------------------------- + + if ( this%need_lightning_and_popdens() ) then + + end if + + end subroutine FireInterp + +end module FireDataBaseType diff --git a/src/unit_test_stubs/share_esmf/laiStreamMod.F90 b/src/unit_test_stubs/share_esmf/laiStreamMod.F90 new file mode 100644 index 0000000000..a39a3eb053 --- /dev/null +++ b/src/unit_test_stubs/share_esmf/laiStreamMod.F90 @@ -0,0 +1,74 @@ +module laiStreamMod + + !----------------------------------------------------------------------- + ! !DESCRIPTION: + ! Read LAI from stream + ! + ! !USES: + use decompMod , only : bounds_type + use abortutils , only : endrun + use clm_varctl , only : iulog + ! + ! !PUBLIC TYPES: + implicit none + private + + ! !PUBLIC MEMBER FUNCTIONS: + public :: lai_init ! position datasets for LAI + public :: lai_advance ! Advance the LAI streams (outside of a Open-MP threading loop) + public :: lai_interp ! interpolates between two years of LAI data (when LAI streams + + character(len=*), parameter :: sourcefile = & + __FILE__ + +!============================================================================== +contains +!============================================================================== + + subroutine lai_init(bounds) + ! + ! Initialize data stream information for LAI. + ! + ! !USES: + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds ! bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + end subroutine lai_init + + !================================================================ + subroutine lai_advance( bounds ) + ! + ! Advance LAI streams + ! + ! !USES: + ! + ! !ARGUMENTS: + type(bounds_type), intent(in) :: bounds + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + end subroutine lai_advance + + !================================================================ + subroutine lai_interp(bounds, canopystate_inst) + ! + ! Interpolate data stream information for Lai. + ! + ! !USES: + use CanopyStateType , only : canopystate_type + ! + ! !ARGUMENTS: + type(bounds_type) , intent(in) :: bounds + type(canopystate_type) , intent(inout) :: canopystate_inst + ! + ! !LOCAL VARIABLES: + !----------------------------------------------------------------------- + + end subroutine lai_interp + +end module LaiStreamMod diff --git a/src/utils/clmfates_interfaceMod.F90 b/src/utils/clmfates_interfaceMod.F90 index 2effb561dd..289244ae89 100644 --- a/src/utils/clmfates_interfaceMod.F90 +++ b/src/utils/clmfates_interfaceMod.F90 @@ -3231,7 +3231,8 @@ subroutine Init2(this, bounds, NLFilename) call t_startf('fates_init2') - call this%fates_fire_data_method%FireInit(bounds, NLFilename) + call this%fates_fire_data_method%FireInit(bounds) + call this%fates_fire_data_method%FireReadNML(bounds, NLFilename) call t_stopf('fates_init2')