From 75d7df65dc721083f19a92e801f883c6e4b5d8cb Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 31 May 2024 15:07:12 -0600 Subject: [PATCH 001/160] Functionize UpdateAccVars_CropGDDs(). --- src/biogeophys/TemperatureType.F90 | 121 ++++++++++++++++------------- 1 file changed, 69 insertions(+), 52 deletions(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index ab310650c8..1b28b2e1aa 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -129,6 +129,7 @@ module TemperatureType procedure, public :: InitAccBuffer procedure, public :: InitAccVars procedure, public :: UpdateAccVars + procedure, private :: UpdateAccVars_CropGDDs end type temperature_type @@ -1357,6 +1358,71 @@ subroutine InitAccVars(this, bounds) end subroutine InitAccVars + subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, dtime, nstep, basetemp_int, gddx_patch) + ! + ! USES + use shr_const_mod , only : SHR_CONST_CDAY, SHR_CONST_TKFRZ + use accumulMod , only : update_accum_field, extract_accum_field, accumResetVal + ! + ! !ARGUMENTS + class(temperature_type) :: this + real(r8), intent(inout), pointer, dimension(:) :: rbufslp ! temporary single level - pft level + integer, intent(in) :: begp, endp + integer, intent(in) :: month, day, secs, dtime, nstep + integer, intent(in) :: basetemp_int ! Crop base temperature. Integer to avoid possible float weirdness + real(r8), intent(inout), pointer, dimension(:) :: gddx_patch ! E.g., gdd0_patch + ! + ! !LOCAL VARIABLES + real(r8) :: basetemp_r8 ! real(r8) version of basetemp for arithmetic + real(r8) :: max_accum ! Maximum daily accumulation + character(8) :: field_name ! E.g., GDD0 + character(32) :: format_string + integer :: p, g + + basetemp_r8 = real(basetemp_int, r8) + + ! Get maximum daily accumulation + if (basetemp_int == 0) then + ! SSR 2024-05-31: I'm not sure why this was different for base temp 0, but I'm keeping it as I refactor into UpdateAccVars_CropGDDs() + max_accum = 26._r8 + else + max_accum = 30._r8 + end if + + do p = begp,endp + + ! Avoid unnecessary calculations over inactive points + if (.not. patch%active(p)) then + cycle + end if + + g = patch%gridcell(p) + if (month==1 .and. day==1 .and. secs==dtime) then + rbufslp(p) = accumResetVal ! reset gdd + else if (( month > 3 .and. month < 10 .and. grc%latdeg(g) >= 0._r8) .or. & + ((month > 9 .or. month < 4) .and. grc%latdeg(g) < 0._r8) ) then + rbufslp(p) = max(0._r8, min(max_accum, & + this%t_ref2m_patch(p)-(SHR_CONST_TKFRZ + basetemp_r8))) * dtime/SHR_CONST_CDAY + else + rbufslp(p) = 0._r8 ! keeps gdd unchanged at other times (eg, through Dec in NH) + end if + end do + + ! Get field name + if (basetemp_int < 10) then + format_string = "(A3,I1)" + else if (basetemp_int < 100) then + format_string = "(A3,I2)" + else + format_string = "(A3,I3)" + end if + write(field_name, format_string) "GDD",basetemp_int + + ! Save + call update_accum_field (trim(field_name), rbufslp, nstep) + call extract_accum_field (trim(field_name), gddx_patch, nstep) + end subroutine UpdateAccVars_CropGDDs + !----------------------------------------------------------------------- subroutine UpdateAccVars (this, bounds) ! @@ -1538,63 +1604,14 @@ subroutine UpdateAccVars (this, bounds) ! Accumulate and extract GDD0 - - do p = begp,endp - ! Avoid unnecessary calculations over inactive points - if (patch%active(p)) then - g = patch%gridcell(p) - if (month==1 .and. day==1 .and. secs==dtime) then - rbufslp(p) = accumResetVal ! reset gdd - else if (( month > 3 .and. month < 10 .and. grc%latdeg(g) >= 0._r8) .or. & - ((month > 9 .or. month < 4) .and. grc%latdeg(g) < 0._r8) ) then - rbufslp(p) = max(0._r8, min(26._r8, this%t_ref2m_patch(p)-SHR_CONST_TKFRZ)) * dtime/SHR_CONST_CDAY - else - rbufslp(p) = 0._r8 ! keeps gdd unchanged at other times (eg, through Dec in NH) - end if - end if - end do - call update_accum_field ('GDD0', rbufslp, nstep) - call extract_accum_field ('GDD0', this%gdd0_patch, nstep) + call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 0, this%gdd0_patch) ! Accumulate and extract GDD8 - - do p = begp,endp - ! Avoid unnecessary calculations over inactive points - if (patch%active(p)) then - g = patch%gridcell(p) - if (month==1 .and. day==1 .and. secs==dtime) then - rbufslp(p) = accumResetVal ! reset gdd - else if (( month > 3 .and. month < 10 .and. grc%latdeg(g) >= 0._r8) .or. & - ((month > 9 .or. month < 4) .and. grc%latdeg(g) < 0._r8) ) then - rbufslp(p) = max(0._r8, min(30._r8, & - this%t_ref2m_patch(p)-(SHR_CONST_TKFRZ + 8._r8))) * dtime/SHR_CONST_CDAY - else - rbufslp(p) = 0._r8 ! keeps gdd unchanged at other times (eg, through Dec in NH) - end if - end if - end do - call update_accum_field ('GDD8', rbufslp, nstep) - call extract_accum_field ('GDD8', this%gdd8_patch, nstep) + call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 8, this%gdd8_patch) ! Accumulate and extract GDD10 + call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 10, this%gdd10_patch) - do p = begp,endp - ! Avoid unnecessary calculations over inactive points - if (patch%active(p)) then - g = patch%gridcell(p) - if (month==1 .and. day==1 .and. secs==dtime) then - rbufslp(p) = accumResetVal ! reset gdd - else if (( month > 3 .and. month < 10 .and. grc%latdeg(g) >= 0._r8) .or. & - ((month > 9 .or. month < 4) .and. grc%latdeg(g) < 0._r8) ) then - rbufslp(p) = max(0._r8, min(30._r8, & - this%t_ref2m_patch(p)-(SHR_CONST_TKFRZ + 10._r8))) * dtime/SHR_CONST_CDAY - else - rbufslp(p) = 0._r8 ! keeps gdd unchanged at other times (eg, through Dec in NH) - end if - end if - end do - call update_accum_field ('GDD10', rbufslp, nstep) - call extract_accum_field ('GDD10', this%gdd10_patch, nstep) ! Accumulate and extract running 20-year means if (is_end_curr_year()) then From 45aff290b33baed02b48b089858de6f0f4a89a74 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 31 May 2024 17:08:01 -0600 Subject: [PATCH 002/160] Optionally read/use gdd20 accum seasons from stream files. --- bld/CLMBuildNamelist.pm | 15 ++ bld/namelist_files/namelist_defaults_ctsm.xml | 3 + .../namelist_definition_ctsm.xml | 15 ++ src/biogeochem/CropType.F90 | 4 + src/biogeophys/TemperatureType.F90 | 54 +++-- src/cpl/share_esmf/cropcalStreamMod.F90 | 198 ++++++++++++++++-- src/main/clm_driver.F90 | 2 +- 7 files changed, 265 insertions(+), 26 deletions(-) diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index 26725b7d96..dddd17ced9 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -4175,6 +4175,7 @@ sub setup_logic_cropcal_streams { # Set up other crop calendar parameters add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'cropcals_rx'); add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'cropcals_rx_adapt'); + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_gdd20_seasons'); add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'generate_crop_gdds'); add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'use_mxmat'); @@ -4185,6 +4186,20 @@ sub setup_logic_cropcal_streams { $log->fatal_error("cropcals_rx and cropcals_rx_adapt may not both be true" ); } + # Add defaults if reading gdd20 seasons from stream files + my $stream_gdd20_seasons = $nl->get_value('stream_gdd20_seasons') ; + my $gdd20_season_start_file = $nl->get_value('stream_fldFileName_gdd20_season_start') ; + my $gdd20_season_end_file = $nl->get_value('stream_fldFileName_gdd20_season_end') ; + if ( &value_is_true($stream_gdd20_seasons)) { + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_fldFileName_gdd20_season_start'); + add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_fldFileName_gdd20_season_end'); + + # Check + if ( &string_is_undef_or_empty($gdd20_season_start_file) or &string_is_undef_or_empty($gdd20_season_end_file) ) { + $log->fatal_error("If stream_gdd20_seasons is true, gdd20 season start and end files must be provided." ); + } + } + # Add defaults if using prescribed crop calendars if ( &value_is_true($cropcals_rx) or &value_is_true($cropcals_rx_adapt) ) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_fldFileName_swindow_start'); diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index da0a523adc..7e3ddb7c64 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -1689,6 +1689,7 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c .false. .false. +.false. 2000 2000 2000 @@ -1705,6 +1706,8 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.nc lnd/clm2/testdata/gdd20baseline.tmp_dontupload.nc +lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc +lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml index 50e645519c..036b9aca72 100644 --- a/bld/namelist_files/namelist_definition_ctsm.xml +++ b/bld/namelist_files/namelist_definition_ctsm.xml @@ -1838,6 +1838,21 @@ Filename of input stream data for cultivar growing degree-day targets Filename of input stream data for baseline GDD20 values + +Filename of input stream data for date (day of year) of start of gdd20 accumulation season. + + + +Set this to true to read gdd20 accumulation season start and end dates from stream files, rather than using hard-coded hemisphere-specific "warm seasons." + + + +Filename of input stream data for date (day of year) of end of gdd20 accumulation season. + + Filename of input stream data for crop calendar inputs diff --git a/src/biogeochem/CropType.F90 b/src/biogeochem/CropType.F90 index a7bccf1a73..04806f9349 100644 --- a/src/biogeochem/CropType.F90 +++ b/src/biogeochem/CropType.F90 @@ -53,6 +53,8 @@ module CropType integer , pointer :: rx_swindow_ends_thisyr_patch (:,:) ! all prescribed sowing window end dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: rx_cultivar_gdds_thisyr_patch (:,:) ! all cultivar GDD targets for this patch this year (ddays) [patch, mxsowings] real(r8), pointer :: gdd20_baseline_patch (:) ! GDD20 baseline for this patch (ddays) [patch] + integer , pointer :: gdd20_season_start_patch(:) ! gdd20 season start date for this patch (day of year) [patch] + integer , pointer :: gdd20_season_end_patch (:) ! gdd20 season end date for this patch (day of year) [patch] real(r8), pointer :: sdates_thisyr_patch (:,:) ! all actual sowing dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: swindow_starts_thisyr_patch(:,:) ! all sowing window start dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: swindow_ends_thisyr_patch (:,:) ! all sowing window end dates for this patch this year (day of year) [patch, mxsowings] @@ -237,6 +239,8 @@ subroutine InitAllocate(this, bounds) allocate(this%rx_swindow_ends_thisyr_patch(begp:endp,1:mxsowings)) ; this%rx_swindow_ends_thisyr_patch (:,:) = -1 allocate(this%rx_cultivar_gdds_thisyr_patch(begp:endp,1:mxsowings)) ; this%rx_cultivar_gdds_thisyr_patch(:,:) = spval allocate(this%gdd20_baseline_patch(begp:endp)) ; this%gdd20_baseline_patch(:) = spval + allocate(this%gdd20_season_start_patch(begp:endp)); this%gdd20_season_start_patch(:) = -1 + allocate(this%gdd20_season_end_patch(begp:endp)) ; this%gdd20_season_end_patch (:) = -1 allocate(this%sdates_thisyr_patch(begp:endp,1:mxsowings)) ; this%sdates_thisyr_patch(:,:) = spval allocate(this%swindow_starts_thisyr_patch(begp:endp,1:mxsowings)) ; this%swindow_starts_thisyr_patch(:,:) = spval allocate(this%swindow_ends_thisyr_patch (begp:endp,1:mxsowings)) ; this%swindow_ends_thisyr_patch (:,:) = spval diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 1b28b2e1aa..31fba16274 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1358,11 +1358,13 @@ subroutine InitAccVars(this, bounds) end subroutine InitAccVars - subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, dtime, nstep, basetemp_int, gddx_patch) + subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, dtime, nstep, basetemp_int, gddx_patch, crop_inst) ! ! USES use shr_const_mod , only : SHR_CONST_CDAY, SHR_CONST_TKFRZ use accumulMod , only : update_accum_field, extract_accum_field, accumResetVal + use clm_time_manager , only : is_doy_in_interval + use CropType, only : crop_type ! ! !ARGUMENTS class(temperature_type) :: this @@ -1371,13 +1373,22 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d integer, intent(in) :: month, day, secs, dtime, nstep integer, intent(in) :: basetemp_int ! Crop base temperature. Integer to avoid possible float weirdness real(r8), intent(inout), pointer, dimension(:) :: gddx_patch ! E.g., gdd0_patch + type(crop_type), intent(inout) :: crop_inst ! ! !LOCAL VARIABLES real(r8) :: basetemp_r8 ! real(r8) version of basetemp for arithmetic real(r8) :: max_accum ! Maximum daily accumulation character(8) :: field_name ! E.g., GDD0 character(32) :: format_string - integer :: p, g + integer :: p + logical :: in_accumulation_season + real(r8) :: lat ! latitude + integer :: gdd20_season_start, gdd20_season_end + + associate( & + gdd20_season_starts => crop_inst%gdd20_season_start_patch, & + gdd20_season_ends => crop_inst%gdd20_season_end_patch & + ) basetemp_r8 = real(basetemp_int, r8) @@ -1396,15 +1407,28 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d cycle end if - g = patch%gridcell(p) + ! Is this patch in its gdd20 accumulation season? + ! First, check based on latitude. This will be fallback if read-in gdd20 accumulation season is invalid. + lat = grc%latdeg(patch%gridcell(p)) + in_accumulation_season = & + ((month > 3 .and. month < 10) .and. lat >= 0._r8) .or. & + ((month > 9 .or. month < 4) .and. lat < 0._r8) + ! Replace with read-in gdd20 accumulation season, if valid + ! (If these aren't being read in or they're invalid, they'll be -1) + gdd20_season_start = crop_inst%gdd20_season_start_patch(p) + gdd20_season_end = crop_inst%gdd20_season_end_patch(p) + if (gdd20_season_start >= 1 .and. gdd20_season_end >= 1) then + in_accumulation_season = is_doy_in_interval( & + gdd20_season_starts(p), gdd20_season_ends(p), day) + end if + if (month==1 .and. day==1 .and. secs==dtime) then rbufslp(p) = accumResetVal ! reset gdd - else if (( month > 3 .and. month < 10 .and. grc%latdeg(g) >= 0._r8) .or. & - ((month > 9 .or. month < 4) .and. grc%latdeg(g) < 0._r8) ) then + else if (in_accumulation_season) then rbufslp(p) = max(0._r8, min(max_accum, & this%t_ref2m_patch(p)-(SHR_CONST_TKFRZ + basetemp_r8))) * dtime/SHR_CONST_CDAY else - rbufslp(p) = 0._r8 ! keeps gdd unchanged at other times (eg, through Dec in NH) + rbufslp(p) = 0._r8 ! keeps gdd unchanged outside accumulation season end if end do @@ -1421,24 +1445,28 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d ! Save call update_accum_field (trim(field_name), rbufslp, nstep) call extract_accum_field (trim(field_name), gddx_patch, nstep) + + end associate end subroutine UpdateAccVars_CropGDDs !----------------------------------------------------------------------- - subroutine UpdateAccVars (this, bounds) + subroutine UpdateAccVars (this, bounds, crop_inst) ! ! USES - use shr_const_mod , only : SHR_CONST_CDAY, SHR_CONST_TKFRZ + use shr_const_mod , only : SHR_CONST_TKFRZ use clm_time_manager , only : get_step_size, get_nstep, is_end_curr_day, get_curr_date, is_end_curr_year - use accumulMod , only : update_accum_field, extract_accum_field, accumResetVal + use accumulMod , only : update_accum_field, extract_accum_field use CNSharedParamsMod, only : upper_soil_layer + use CropType , only : crop_type ! ! !ARGUMENTS: class(temperature_type) :: this type(bounds_type) , intent(in) :: bounds + type(crop_type), intent(inout) :: crop_inst ! ! !LOCAL VARIABLES: - integer :: m,g,l,c,p ! indices + integer :: m,l,c,p ! indices integer :: ier ! error status integer :: dtime ! timestep size [seconds] integer :: nstep ! timestep number @@ -1604,13 +1632,13 @@ subroutine UpdateAccVars (this, bounds) ! Accumulate and extract GDD0 - call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 0, this%gdd0_patch) + call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 0, this%gdd0_patch, crop_inst) ! Accumulate and extract GDD8 - call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 8, this%gdd8_patch) + call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 8, this%gdd8_patch, crop_inst) ! Accumulate and extract GDD10 - call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 10, this%gdd10_patch) + call this%UpdateAccVars_CropGDDs(rbufslp, begp, endp, month, day, secs, dtime, nstep, 10, this%gdd10_patch, crop_inst) ! Accumulate and extract running 20-year means diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index 413ddbefce..8196ca1dfb 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -38,9 +38,12 @@ module cropcalStreamMod type(shr_strdata_type) :: sdat_cropcal_swindow_end ! sowing window end input data stream type(shr_strdata_type) :: sdat_cropcal_cultivar_gdds ! maturity requirement input data stream type(shr_strdata_type) :: sdat_cropcal_gdd20_baseline ! GDD20 baseline input data stream + type(shr_strdata_type) :: sdat_cropcal_gdd20_season_start ! gdd20 season start input data stream + type(shr_strdata_type) :: sdat_cropcal_gdd20_season_end ! gdd20 season end input data stream character(len=CS), allocatable :: stream_varnames_sdate(:) ! used for both start and end dates character(len=CS), allocatable :: stream_varnames_cultivar_gdds(:) character(len=CS), allocatable :: stream_varnames_gdd20_baseline(:) + character(len=CS), allocatable :: stream_varnames_gdd20_season_enddate(:) ! start uses stream_varnames_sdate integer :: ncft ! Number of crop functional types (excl. generic crops) logical :: allow_invalid_swindow_inputs ! Fall back on paramfile sowing windows in cases of invalid values in stream_fldFileName_swindow_start and _end? character(len=CL) :: stream_fldFileName_swindow_start ! sowing window start stream filename to read @@ -49,6 +52,10 @@ module cropcalStreamMod character(len=CL) :: stream_fldFileName_gdd20_baseline ! GDD20 baseline stream filename to read logical :: cropcals_rx ! Used only for setting input files in namelist; does nothing in code, but needs to be here so namelist read doesn't crash logical :: cropcals_rx_adapt ! Used only for setting input files in namelist; does nothing in code, but needs to be here so namelist read doesn't crash + logical :: stream_gdd20_seasons ! Read stream file for GDD20 accumulation seasons, instead of per-hemisphere periods + logical :: allow_invalid_gdd20_season_inputs ! Fall back on hemisphere "warm periods" in cases of invalid values in stream_fldFileName_gdd20_season_start and _end? + character(len=CL) :: stream_fldFileName_gdd20_season_start ! Stream filename to read for start of gdd20 season + character(len=CL) :: stream_fldFileName_gdd20_season_end ! Stream filename to read for end of gdd20 season character(len=*), parameter :: sourcefile = & __FILE__ @@ -105,7 +112,11 @@ subroutine cropcal_init(bounds) stream_fldFileName_gdd20_baseline, & stream_meshfile_cropcal, & cropcals_rx, & - cropcals_rx_adapt + cropcals_rx_adapt, & + stream_gdd20_seasons, & + allow_invalid_gdd20_season_inputs, & + stream_fldFileName_gdd20_season_start, & + stream_fldFileName_gdd20_season_end ! Default values for namelist stream_year_first_cropcal_swindows = 1 ! first year in sowing window streams to use @@ -120,16 +131,22 @@ subroutine cropcal_init(bounds) stream_fldFileName_swindow_end = '' stream_fldFileName_cultivar_gdds = '' stream_fldFileName_gdd20_baseline = '' + stream_gdd20_seasons = .false. + allow_invalid_gdd20_season_inputs = .false. + stream_fldFileName_gdd20_season_start = '' + stream_fldFileName_gdd20_season_end = '' ! Will need modification to work with mxsowings > 1 ncft = mxpft - npcropmin + 1 ! Ignores generic crops allocate(stream_varnames_sdate(ncft)) allocate(stream_varnames_cultivar_gdds(ncft)) allocate(stream_varnames_gdd20_baseline(ncft)) + allocate(stream_varnames_gdd20_season_enddate(ncft)) do n = 1,ncft ivt = npcropmin + n - 1 write(stream_varnames_sdate(n),'(a,i0)') "sdate1_",ivt write(stream_varnames_cultivar_gdds(n),'(a,i0)') "gdd1_",ivt write(stream_varnames_gdd20_baseline(n),'(a,i0)') "gdd20bl_",ivt + write(stream_varnames_gdd20_season_enddate(n),'(a,i0)') "hdate1_",ivt end do ! Read cropcal_streams namelist @@ -158,6 +175,10 @@ subroutine cropcal_init(bounds) call shr_mpi_bcast(stream_fldFileName_cultivar_gdds, mpicom) call shr_mpi_bcast(stream_fldFileName_gdd20_baseline, mpicom) call shr_mpi_bcast(stream_meshfile_cropcal , mpicom) + call shr_mpi_bcast(stream_gdd20_seasons, mpicom) + call shr_mpi_bcast(allow_invalid_gdd20_season_inputs, mpicom) + call shr_mpi_bcast(stream_fldFileName_gdd20_season_start, mpicom) + call shr_mpi_bcast(stream_fldFileName_gdd20_season_end, mpicom) if (masterproc) then write(iulog,*) @@ -174,9 +195,14 @@ subroutine cropcal_init(bounds) write(iulog,'(a,a)' ) ' stream_fldFileName_cultivar_gdds = ',trim(stream_fldFileName_cultivar_gdds) write(iulog,'(a,a)' ) ' stream_fldFileName_gdd20_baseline = ',trim(stream_fldFileName_gdd20_baseline) write(iulog,'(a,a)' ) ' stream_meshfile_cropcal = ',trim(stream_meshfile_cropcal) + write(iulog,'(a,l1)') ' stream_gdd20_seasons = ',stream_gdd20_seasons + write(iulog,'(a,l1)') ' allow_invalid_gdd20_season_inputs = ',allow_invalid_gdd20_season_inputs + write(iulog,'(a,a)' ) ' stream_fldFileName_gdd20_season_start = ',stream_fldFileName_gdd20_season_start + write(iulog,'(a,a)' ) ' stream_fldFileName_gdd20_season_end = ',stream_fldFileName_gdd20_season_end do n = 1,ncft write(iulog,'(a,a)' ) ' stream_varnames_sdate = ',trim(stream_varnames_sdate(n)) write(iulog,'(a,a)' ) ' stream_varnames_cultivar_gdds = ',trim(stream_varnames_cultivar_gdds(n)) + write(iulog,'(a,a)' ) ' stream_varnames_gdd20_season_enddate = ',trim(stream_varnames_gdd20_season_enddate(n)) write(iulog,'(a,a)' ) ' stream_varnames_gdd20_baseline = ',trim(stream_varnames_gdd20_baseline(n)) end do write(iulog,*) @@ -304,6 +330,65 @@ subroutine cropcal_init(bounds) end if end if + if (stream_gdd20_seasons) then + ! Initialize the cdeps data type sdat_cropcal_gdd20_season_start + ! NOTE: Hard-coded to one particular year because it should NOT vary over time. Note that the + ! particular year chosen doesn't matter. + call shr_strdata_init_from_inline(sdat_cropcal_gdd20_season_start, & + my_task = iam, & + logunit = iulog, & + compname = 'LND', & + model_clock = model_clock, & + model_mesh = mesh, & + stream_meshfile = trim(stream_meshfile_cropcal), & + stream_lev_dimname = 'null', & + stream_mapalgo = trim(cropcal_mapalgo), & + stream_filenames = (/trim(stream_fldFileName_gdd20_season_start)/), & + stream_fldlistFile = stream_varnames_sdate, & + stream_fldListModel = stream_varnames_sdate, & + stream_yearFirst = 2000, & + stream_yearLast = 2000, & + stream_yearAlign = 2000, & + stream_offset = cropcal_offset, & + stream_taxmode = 'extend', & + stream_dtlimit = 1.0e30_r8, & + stream_tintalgo = cropcal_tintalgo, & + stream_name = 'gdd20 season start data', & + rc = rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + + ! Initialize the cdeps data type sdat_cropcal_gdd20_season_end + ! NOTE: Hard-coded to one particular year because it should NOT vary over time. Note that the + ! particular year chosen doesn't matter. + call shr_strdata_init_from_inline(sdat_cropcal_gdd20_season_end, & + my_task = iam, & + logunit = iulog, & + compname = 'LND', & + model_clock = model_clock, & + model_mesh = mesh, & + stream_meshfile = trim(stream_meshfile_cropcal), & + stream_lev_dimname = 'null', & + stream_mapalgo = trim(cropcal_mapalgo), & + stream_filenames = (/trim(stream_fldFileName_gdd20_season_end)/), & + stream_fldlistFile = stream_varnames_gdd20_season_enddate, & + stream_fldListModel = stream_varnames_gdd20_season_enddate, & + stream_yearFirst = 2000, & + stream_yearLast = 2000, & + stream_yearAlign = 2000, & + stream_offset = cropcal_offset, & + stream_taxmode = 'extend', & + stream_dtlimit = 1.0e30_r8, & + stream_tintalgo = cropcal_tintalgo, & + stream_name = 'gdd20 season start data', & + rc = rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + + end if + end subroutine cropcal_init !================================================================ @@ -347,7 +432,10 @@ subroutine cropcal_advance( bounds ) end if end if - ! GDD20 baseline values do not have an associated time axis and thus will not be advanced here + ! The following should not have an associated time axis and thus will not be advanced here: + ! - GDD20 baseline values + ! - GDD20 season start dates + ! - GDD20 season end dates if ( .not. allocated(g_to_ig) )then allocate (g_to_ig(bounds%begg:bounds%endg) ) @@ -393,15 +481,21 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) real(r8), pointer :: dataptr1d_swindow_end (:) real(r8), pointer :: dataptr1d_cultivar_gdds(:) real(r8), pointer :: dataptr1d_gdd20_baseline(:) + real(r8), pointer :: dataptr1d_gdd20_season_start(:) + real(r8), pointer :: dataptr1d_gdd20_season_end (:) real(r8), pointer :: dataptr2d_swindow_start(:,:) real(r8), pointer :: dataptr2d_swindow_end (:,:) real(r8), pointer :: dataptr2d_cultivar_gdds(:,:) real(r8), pointer :: dataptr2d_gdd20_baseline(:,:) + real(r8), pointer :: dataptr2d_gdd20_season_start(:,:) + real(r8), pointer :: dataptr2d_gdd20_season_end (:,:) !----------------------------------------------------------------------- associate( & - starts => crop_inst%rx_swindow_starts_thisyr_patch, & - ends => crop_inst%rx_swindow_ends_thisyr_patch & + swindow_starts => crop_inst%rx_swindow_starts_thisyr_patch, & + swindow_ends => crop_inst%rx_swindow_ends_thisyr_patch, & + gdd20_season_starts => crop_inst%gdd20_season_start_patch, & + gdd20_season_ends => crop_inst%gdd20_season_end_patch & ) SHR_ASSERT_FL( (lbound(g_to_ig,1) <= bounds%begg ), sourcefile, __LINE__) @@ -459,8 +553,8 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) n = ivt - npcropmin + 1 ! vegetated pft ig = g_to_ig(patch%gridcell(p)) - starts(p,1) = dataptr2d_swindow_start(ig,n) - ends(p,1) = dataptr2d_swindow_end (ig,n) + swindow_starts(p,1) = dataptr2d_swindow_start(ig,n) + swindow_ends(p,1) = dataptr2d_swindow_end (ig,n) else write(iulog,'(a,i0)') 'cropcal_interp(), prescribed sowing windows: Crop patch has ivt ',ivt call ESMF_Finalize(endflag=ESMF_END_ABORT) @@ -469,23 +563,23 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) ! Ensure that, if mxsowings > 1, sowing windows are ordered such that ENDS are monotonically increasing. This is necessary because of how get_swindow() works. if (mxsowings > 1) then - if (any(ends(begp:endp,2:mxsowings) <= ends(begp:endp,1:mxsowings-1) .and. & - ends(begp:endp,2:mxsowings) >= 1)) then + if (any(swindow_ends(begp:endp,2:mxsowings) <= swindow_ends(begp:endp,1:mxsowings-1) .and. & + swindow_ends(begp:endp,2:mxsowings) >= 1)) then write(iulog, *) 'Sowing window inputs must be ordered such that end dates are monotonically increasing.' call ESMF_Finalize(endflag=ESMF_END_ABORT) end if end if ! Handle invalid sowing window values - if (any(starts(begp:endp,:) < 1 .or. ends(begp:endp,:) < 1)) then + if (any(swindow_starts(begp:endp,:) < 1 .or. swindow_ends(begp:endp,:) < 1)) then ! Fail if not allowing fallback to paramfile sowing windows - if ((.not. allow_invalid_swindow_inputs) .and. any(all(starts(begp:endp,:) < 1, dim=2) .and. patch%wtgcell > 0._r8 .and. patch%itype >= npcropmin)) then + if ((.not. allow_invalid_swindow_inputs) .and. any(all(swindow_starts(begp:endp,:) < 1, dim=2) .and. patch%wtgcell > 0._r8 .and. patch%itype >= npcropmin)) then write(iulog, *) 'At least one crop in one gridcell has invalid prescribed sowing window start date(s). To ignore and fall back to paramfile sowing windows, set allow_invalid_swindow_inputs to .true.' write(iulog, *) 'Affected crops:' do ivt = npcropmin, mxpft do fp = 1, num_pcropp p = filter_pcropp(fp) - if (ivt == patch%itype(p) .and. patch%wtgcell(p) > 0._r8 .and. all(starts(p,:) < 1)) then + if (ivt == patch%itype(p) .and. patch%wtgcell(p) > 0._r8 .and. all(swindow_starts(p,:) < 1)) then write(iulog, *) ' ',pftname(ivt),' (',ivt,')' exit ! Stop looking for patches of this type end if @@ -494,7 +588,7 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) call ESMF_Finalize(endflag=ESMF_END_ABORT) ! Fail if a sowing window start date is prescribed without an end date (or vice versa) - else if (any((starts(begp:endp,:) >= 1 .and. ends(begp:endp,:) < 1) .or. (starts(begp:endp,:) < 1 .and. ends(begp:endp,:) >= 1))) then + else if (any((swindow_starts(begp:endp,:) >= 1 .and. swindow_ends(begp:endp,:) < 1) .or. (swindow_starts(begp:endp,:) < 1 .and. swindow_ends(begp:endp,:) >= 1))) then write(iulog, *) 'Every prescribed sowing window start date must have a corresponding end date.' call ESMF_Finalize(endflag=ESMF_END_ABORT) end if @@ -613,6 +707,86 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) deallocate(dataptr2d_gdd20_baseline) + ! Read prescribed gdd20 season start dates from input files + allocate(dataptr2d_gdd20_season_start(lsize, ncft)) + dataptr2d_gdd20_season_start(:,:) = -1._r8 + allocate(dataptr2d_gdd20_season_end (lsize, ncft)) + dataptr2d_gdd20_season_end(:,:) = -1._r8 + if (stream_gdd20_seasons .and. init) then + ! Starting with npcropmin will skip generic crops + do n = 1, ncft + call dshr_fldbun_getFldPtr(sdat_cropcal_gdd20_season_start%pstrm(1)%fldbun_model, trim(stream_varnames_sdate(n)), & + fldptr1=dataptr1d_gdd20_season_start, rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + call dshr_fldbun_getFldPtr(sdat_cropcal_gdd20_season_end%pstrm(1)%fldbun_model, trim(stream_varnames_gdd20_season_enddate(n)), & + fldptr1=dataptr1d_gdd20_season_end, rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + ! Note that the size of dataptr1d includes ocean points so it will be around 3x larger than lsize + ! So an explicit loop is required here + do g = 1,lsize + + ! If read-in value is invalid, set to -1. Will be handled later in this subroutine. + if (dataptr1d_gdd20_season_start(g) <= 0 .or. dataptr1d_gdd20_season_start(g) > 366 & + .or. dataptr1d_gdd20_season_end(g) <= 0 .or. dataptr1d_gdd20_season_end(g) > 366) then + dataptr1d_gdd20_season_start(g) = -1 + dataptr1d_gdd20_season_end (g) = -1 + end if + + dataptr2d_gdd20_season_start(g,n) = dataptr1d_gdd20_season_start(g) + dataptr2d_gdd20_season_end (g,n) = dataptr1d_gdd20_season_end (g) + end do + end do + + ! Set gdd20 season for each gridcell/patch combination + do fp = 1, num_pcropp + p = filter_pcropp(fp) + ivt = patch%itype(p) + ! Will skip generic crops + if (ivt >= npcropmin) then + n = ivt - npcropmin + 1 + ! vegetated pft + ig = g_to_ig(patch%gridcell(p)) + gdd20_season_starts(p) = dataptr2d_gdd20_season_start(ig,n) + gdd20_season_ends(p) = dataptr2d_gdd20_season_end (ig,n) + else + write(iulog,'(a,i0)') 'cropcal_interp(), gdd20 seasons: Crop patch has ivt ',ivt + call ESMF_Finalize(endflag=ESMF_END_ABORT) + endif + end do + + ! Handle invalid gdd20 season values + if (any(gdd20_season_starts(begp:endp) < 1 .or. gdd20_season_ends(begp:endp) < 1)) then + ! Fail if not allowing fallback to paramfile sowing windows + if ((.not. allow_invalid_gdd20_season_inputs) .and. any(gdd20_season_starts(begp:endp) < 1 .and. patch%wtgcell(begp:endp) > 0._r8 .and. patch%itype(begp:endp) >= npcropmin)) then + write(iulog, *) 'At least one crop in one gridcell has invalid gdd20 season start date(s). To ignore and fall back to paramfile sowing windows, set allow_invalid_gdd20_season_inputs to .true.' + write(iulog, *) 'Affected crops:' + do ivt = npcropmin, mxpft + do fp = 1, num_pcropp + p = filter_pcropp(fp) + if (ivt == patch%itype(p) .and. patch%wtgcell(p) > 0._r8 .and. gdd20_season_starts(p) < 1) then + write(iulog, *) ' ',pftname(ivt),' (',ivt,')' + exit ! Stop looking for patches of this type + end if + end do + end do + call ESMF_Finalize(endflag=ESMF_END_ABORT) + + ! Fail if a gdd20 season start date is given without an end date (or vice versa) + else if (any((gdd20_season_starts(begp:endp) >= 1 .and. gdd20_season_ends(begp:endp) < 1) .or. (gdd20_season_starts(begp:endp) < 1 .and. gdd20_season_ends(begp:endp) >= 1))) then + write(iulog, *) 'Every gdd20 season start date must have a corresponding end date.' + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + end if + + end if ! stream_gdd20_seasons and init + deallocate(dataptr2d_gdd20_season_start) + deallocate(dataptr2d_gdd20_season_end) + + end associate end subroutine cropcal_interp diff --git a/src/main/clm_driver.F90 b/src/main/clm_driver.F90 index 2a3cef4b8b..e660ab9d8d 100644 --- a/src/main/clm_driver.F90 +++ b/src/main/clm_driver.F90 @@ -1374,7 +1374,7 @@ subroutine clm_drv(doalb, nextsw_cday, declinp1, declin, rstwr, nlend, rdate, ro call atm2lnd_inst%UpdateAccVars(bounds_proc) - call temperature_inst%UpdateAccVars(bounds_proc) + call temperature_inst%UpdateAccVars(bounds_proc, crop_inst) call canopystate_inst%UpdateAccVars(bounds_proc) From 0feff1f3d641ec47c616d41d8c6fe3e4b9208db1 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 31 May 2024 18:26:51 -0600 Subject: [PATCH 003/160] Fix check of invalid swindow inputs. --- src/cpl/share_esmf/cropcalStreamMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index 8196ca1dfb..4c4925f88e 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -573,7 +573,7 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) ! Handle invalid sowing window values if (any(swindow_starts(begp:endp,:) < 1 .or. swindow_ends(begp:endp,:) < 1)) then ! Fail if not allowing fallback to paramfile sowing windows - if ((.not. allow_invalid_swindow_inputs) .and. any(all(swindow_starts(begp:endp,:) < 1, dim=2) .and. patch%wtgcell > 0._r8 .and. patch%itype >= npcropmin)) then + if ((.not. allow_invalid_swindow_inputs) .and. any(all(swindow_starts(begp:endp,:) < 1, dim=2) .and. patch%wtgcell(begp:endp) > 0._r8 .and. patch%itype(begp:endp) >= npcropmin)) then write(iulog, *) 'At least one crop in one gridcell has invalid prescribed sowing window start date(s). To ignore and fall back to paramfile sowing windows, set allow_invalid_swindow_inputs to .true.' write(iulog, *) 'Affected crops:' do ivt = npcropmin, mxpft From 3729b6db9f2139399618e39c4c48084144eecf12 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 31 May 2024 22:35:35 -0600 Subject: [PATCH 004/160] Add RxCropCalsAdaptGGCMI testmod and test. --- cime_config/testdefs/testlist_clm.xml | 9 +++++++++ .../clm/RxCropCalsAdaptGGCMI/include_user_mods | 1 + .../testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/include_user_mods create mode 100644 cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index bf763c4775..edb932d24e 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3737,5 +3737,14 @@ + + + + + + + + + diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/include_user_mods new file mode 100644 index 0000000000..af5fe8591e --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/include_user_mods @@ -0,0 +1 @@ +../RxCropCalsAdapt diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm new file mode 100644 index 0000000000..fa0e4ec663 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm @@ -0,0 +1,2 @@ + +stream_gdd20_seasons = .true. From d020eada257e99055b2b90600f78c630286492c1 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 09:10:40 -0600 Subject: [PATCH 005/160] Fix check that gdd20 season files are provided. --- bld/CLMBuildNamelist.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index dddd17ced9..d5c2ffd54f 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -4188,14 +4188,17 @@ sub setup_logic_cropcal_streams { # Add defaults if reading gdd20 seasons from stream files my $stream_gdd20_seasons = $nl->get_value('stream_gdd20_seasons') ; - my $gdd20_season_start_file = $nl->get_value('stream_fldFileName_gdd20_season_start') ; - my $gdd20_season_end_file = $nl->get_value('stream_fldFileName_gdd20_season_end') ; if ( &value_is_true($stream_gdd20_seasons)) { add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_fldFileName_gdd20_season_start'); add_default($opts, $nl_flags->{'inputdata_rootdir'}, $definition, $defaults, $nl, 'stream_fldFileName_gdd20_season_end'); # Check + my $gdd20_season_start_file = $nl->get_value('stream_fldFileName_gdd20_season_start') ; + my $gdd20_season_end_file = $nl->get_value('stream_fldFileName_gdd20_season_end') ; if ( &string_is_undef_or_empty($gdd20_season_start_file) or &string_is_undef_or_empty($gdd20_season_end_file) ) { + $log->message($gdd20_season_start_file); + $log->message('abcd'); + $log->message($gdd20_season_end_file); $log->fatal_error("If stream_gdd20_seasons is true, gdd20 season start and end files must be provided." ); } } From 42b0dbb9f7e02ab2138710904af129704867e466 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 09:11:09 -0600 Subject: [PATCH 006/160] Minor rearrangement in cropcal namelist definition. --- bld/namelist_files/namelist_definition_ctsm.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml index 036b9aca72..5b6dcef87e 100644 --- a/bld/namelist_files/namelist_definition_ctsm.xml +++ b/bld/namelist_files/namelist_definition_ctsm.xml @@ -1838,16 +1838,16 @@ Filename of input stream data for cultivar growing degree-day targets Filename of input stream data for baseline GDD20 values - -Filename of input stream data for date (day of year) of start of gdd20 accumulation season. - - Set this to true to read gdd20 accumulation season start and end dates from stream files, rather than using hard-coded hemisphere-specific "warm seasons." + +Filename of input stream data for date (day of year) of start of gdd20 accumulation season. + + Filename of input stream data for date (day of year) of end of gdd20 accumulation season. From 92555b9c394a4542ff7412335a459dfab57a8979 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 12:13:20 -0600 Subject: [PATCH 007/160] generate_gdd20_baseline.py now saves NETCDF3_CLASSIC. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index d28bfda0e7..7b5fd625d6 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -226,7 +226,7 @@ def generate_gdd20_baseline(input_files, output_file, author): ds_out[var_out] = this_da # Save - ds_out.to_netcdf(output_file) + ds_out.to_netcdf(output_file, format="NETCDF3_CLASSIC") print("Done!") From 30eb31ce8e92a05bd104268e01e3d62b964ee486 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 12:16:49 -0600 Subject: [PATCH 008/160] Update default to point to classic version. --- bld/namelist_files/namelist_defaults_ctsm.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index 7e3ddb7c64..2683c2e535 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -1705,7 +1705,7 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.nc -lnd/clm2/testdata/gdd20baseline.tmp_dontupload.nc +lnd/clm2/testdata/gdd20baseline.tmp_dontupload.classic.nc lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc From a6e2c1e23964b6e9c3bf35d1778a14187a0b62e9 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 12:59:29 -0600 Subject: [PATCH 009/160] Add allow_invalid_gdd20_season_inputs to namelist XML. --- bld/namelist_files/namelist_definition_ctsm.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml index 5b6dcef87e..675c912900 100644 --- a/bld/namelist_files/namelist_definition_ctsm.xml +++ b/bld/namelist_files/namelist_definition_ctsm.xml @@ -1843,6 +1843,11 @@ Filename of input stream data for baseline GDD20 values Set this to true to read gdd20 accumulation season start and end dates from stream files, rather than using hard-coded hemisphere-specific "warm seasons." + +By default, a value in stream_fldFileName_gdd20_season_start or _end outside the range [1, 365] (or 366 in leap years) will cause the run to fail. Set this to .true. to instead have such cells fall back to the hard-coded hemisphere-specific "warm seasons." + + Filename of input stream data for date (day of year) of start of gdd20 accumulation season. From 9f04d71e0ac7f5e4f0bcadd195281dfcc2134f81 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 13:22:59 -0600 Subject: [PATCH 010/160] Update missing values from generate_gdd20_baseline.py. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 7b5fd625d6..fd56d61550 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -20,7 +20,8 @@ VAR_LIST_IN = ["GDD0", "GDD8", "GDD10"] VAR_LIST_IN = [x + "20" for x in VAR_LIST_IN] # TODO: Delete this once using the right variables -MISSING_FILL = -1 # Something negative to ensure that gddmaturity never changes (see PlantCrop) +MISSING_FILL = -1 # Something impossible to ensure that you can mark it as a missing value, to be +# bilinear-interpolated STREAM_YEAR = 2000 # The year specified for stream_yearFirst and stream_yearLast in the call of # shr_strdata_init_from_inline() for sdat_cropcal_gdd20_baseline @@ -211,7 +212,8 @@ def generate_gdd20_baseline(input_files, output_file, author): long_name = "Dummy GDD20" print(" dummy GDD20") else: - this_da = ds_in[gddn].fillna(MISSING_FILL) + # this_da = ds_in[gddn].fillna(MISSING_FILL) + this_da = ds_in[gddn] this_da = _add_time_axis(this_da) long_name = gddn print(f" {gddn}") @@ -219,6 +221,7 @@ def generate_gdd20_baseline(input_files, output_file, author): # Add attributes this_da.attrs["long_name"] = long_name + f" baseline for {cft_str}" this_da.attrs["units"] = "°C days" + # this_da.attrs["_FillValue"] = MISSING_FILL # Copy that to ds_out var_out = _get_output_varname(cft_str) From 625fd5e7b9eebc61bf8e93824e92a3079bf937cd Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 13:23:16 -0600 Subject: [PATCH 011/160] RxCropCalsAdaptGGCMI now uses allow_invalid_gdd20_season_inputs. --- .../testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm index fa0e4ec663..42e57a675c 100644 --- a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm @@ -1,2 +1,4 @@ stream_gdd20_seasons = .true. +!TODO SSR: Try without this once you have half-degree inputs +allow_invalid_gdd20_season_inputs = .true. From 0156d1aa928f5bc002c1eb8508962138c76f4a1d Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 13:25:02 -0600 Subject: [PATCH 012/160] Move hist_addfld1d() for GDD0 to be with its siblings. --- src/biogeophys/TemperatureType.F90 | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 31fba16274..89dfd11074 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -597,9 +597,7 @@ subroutine InitHistory(this, bounds, is_simple_buildtemp, is_prog_buildtemp ) call hist_addfld1d (fname='GDD0', units='ddays', & avgflag='A', long_name='Growing degree days base 0C from planting', & ptr_patch=this%gdd0_patch, default='inactive') - end if - if (use_crop) then this%gdd8_patch(begp:endp) = spval call hist_addfld1d (fname='GDD8', units='ddays', & avgflag='A', long_name='Growing degree days base 8C from planting', & From b7c34ffcd7b4421f5266051d3bb7aeef2f790744 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 13:34:16 -0600 Subject: [PATCH 013/160] Add max versions of GDD season accum outputs. --- src/biogeophys/TemperatureType.F90 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 89dfd11074..f8d5206a1e 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -608,6 +608,21 @@ subroutine InitHistory(this, bounds, is_simple_buildtemp, is_prog_buildtemp ) avgflag='A', long_name='Growing degree days base 10C from planting', & ptr_patch=this%gdd10_patch, default='inactive') + this%gdd0_patch(begp:endp) = spval + call hist_addfld1d (fname='GDD0X', units='ddays', & + avgflag='X', long_name='Growing degree days base 0C from planting, max', & + ptr_patch=this%gdd0_patch, default='inactive') + + this%gdd8_patch(begp:endp) = spval + call hist_addfld1d (fname='GDD8X', units='ddays', & + avgflag='X', long_name='Growing degree days base 8C from planting, max', & + ptr_patch=this%gdd8_patch, default='inactive') + + this%gdd10_patch(begp:endp) = spval + call hist_addfld1d (fname='GDD10X', units='ddays', & + avgflag='X', long_name='Growing degree days base 10C from planting, max', & + ptr_patch=this%gdd10_patch, default='inactive') + this%gdd020_patch(begp:endp) = spval call hist_addfld1d (fname='GDD020', units='ddays', & avgflag='A', long_name='Twenty year average of growing degree days base 0C from planting', & From 535e2c5a43f309ea3bbe8eb851c963ee7cb33dcf Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 17:01:44 -0600 Subject: [PATCH 014/160] Even gdd20 files need to be advance()d. --- src/cpl/share_esmf/cropcalStreamMod.F90 | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index 4c4925f88e..faa13b5fda 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -432,10 +432,26 @@ subroutine cropcal_advance( bounds ) end if end if - ! The following should not have an associated time axis and thus will not be advanced here: + ! The following should not have an associated time axis, but still need to be here ! - GDD20 baseline values ! - GDD20 season start dates ! - GDD20 season end dates + if (adapt_cropcal_rx_cultivar_gdds) then + call shr_strdata_advance(sdat_cropcal_gdd20_baseline, ymd=mcdate, tod=sec, logunit=iulog, istr='cropcaldyn', rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + end if + if (stream_gdd20_seasons) then + call shr_strdata_advance(sdat_cropcal_gdd20_season_start, ymd=mcdate, tod=sec, logunit=iulog, istr='cropcaldyn', rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + call shr_strdata_advance(sdat_cropcal_gdd20_season_end, ymd=mcdate, tod=sec, logunit=iulog, istr='cropcaldyn', rc=rc) + if (ESMF_LogFoundError(rcToCheck=rc, msg=ESMF_LOGERR_PASSTHRU, line=__LINE__, file=__FILE__)) then + call ESMF_Finalize(endflag=ESMF_END_ABORT) + end if + end if if ( .not. allocated(g_to_ig) )then allocate (g_to_ig(bounds%begg:bounds%endg) ) From 3e524db1aa4f34a70cd08c48c740c8c35033db9d Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 17:03:43 -0600 Subject: [PATCH 015/160] generate_gdd20_baseline.py now saves to double instead of float. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index fd56d61550..8cf30bf44d 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -197,6 +197,7 @@ def generate_gdd20_baseline(input_files, output_file, author): "created": dt.datetime.now().astimezone().isoformat(), }, ) + encoding_dict = {} for cft_str in utils.define_mgdcrop_list(): cft_int = utils.vegtype_str2int(cft_str)[0] print(f"{cft_str} ({cft_int})") @@ -227,9 +228,10 @@ def generate_gdd20_baseline(input_files, output_file, author): var_out = _get_output_varname(cft_str) print(f" Output variable {var_out}") ds_out[var_out] = this_da + encoding_dict[var_out] = {"dtype": "float64"} # Save - ds_out.to_netcdf(output_file, format="NETCDF3_CLASSIC") + ds_out.to_netcdf(output_file, format="NETCDF3_CLASSIC", encoding=encoding_dict) print("Done!") From 18df6b4a04ac901737ccb5d9b806619e66ebe532 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sat, 1 Jun 2024 17:06:19 -0600 Subject: [PATCH 016/160] stream_fldFileName_gdd20_baseline now defaults to half-deg fake file. --- bld/namelist_files/namelist_defaults_ctsm.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index 2683c2e535..bd5f7132ca 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -1705,7 +1705,7 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.nc -lnd/clm2/testdata/gdd20baseline.tmp_dontupload.classic.nc +lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.nc lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc From a3feed96de079cdc04f42e60a5cfc4ff22959606 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 2 Jun 2024 08:25:06 -0600 Subject: [PATCH 017/160] Use fixed gdd20 baseline file. --- bld/namelist_files/namelist_defaults_ctsm.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index bd5f7132ca..8ed75be01a 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -1705,7 +1705,7 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.nc -lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.nc +lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.v2.nc lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc From 5144b36a7993ae6363bd8bb16b41219ed3e032ed Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 10:58:43 -0600 Subject: [PATCH 018/160] gdd20 baseline now interpolated w/ nearest-neighbor. Revert this! --- src/cpl/share_esmf/cropcalStreamMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index faa13b5fda..ce595c0d70 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -312,7 +312,7 @@ subroutine cropcal_init(bounds) model_mesh = mesh, & stream_meshfile = trim(stream_meshfile_cropcal), & stream_lev_dimname = 'null', & - stream_mapalgo = 'bilinear', & + stream_mapalgo = 'nn', & stream_filenames = (/trim(stream_fldFileName_gdd20_baseline)/), & stream_fldlistFile = stream_varnames_gdd20_baseline, & stream_fldListModel = stream_varnames_gdd20_baseline, & From 90b938b5bbf8bc3e13d86e1211bb9519409b477d Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 10:59:37 -0600 Subject: [PATCH 019/160] generate_gdd20_nbaseline: Use GDDNX. --- .../ctsm/crop_calendars/generate_gdd20_baseline.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 8cf30bf44d..7f60fb6a68 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -18,8 +18,7 @@ from ctsm.crop_calendars.import_ds import import_ds import ctsm.crop_calendars.cropcal_utils as utils -VAR_LIST_IN = ["GDD0", "GDD8", "GDD10"] -VAR_LIST_IN = [x + "20" for x in VAR_LIST_IN] # TODO: Delete this once using the right variables +VAR_LIST_IN = ["GDD0X", "GDD8X", "GDD10X"] MISSING_FILL = -1 # Something impossible to ensure that you can mark it as a missing value, to be # bilinear-interpolated STREAM_YEAR = 2000 # The year specified for stream_yearFirst and stream_yearLast in the call of @@ -111,26 +110,25 @@ def _get_gddn_for_cft(cft_str): cft_str (str): E.g., "irrigated_temperate_corn" Returns: - str or None: Name of variable to use (e.g., "GDD8"). If crop isn't yet handled, return None. + str or None: Name of variable to use (e.g., "GDD8X"). If crop isn't yet handled, return None. """ gddn = None gdd0_list_str = ["wheat", "cotton", "rice"] if cft_str in _get_cft_list(gdd0_list_str): - gddn = "GDD0" + gddn = 0 gdd8_list_str = ["corn", "sugarcane", "miscanthus", "switchgrass"] if cft_str in _get_cft_list(gdd8_list_str): - gddn = "GDD8" + gddn = 8 gdd10_list_str = ["soybean"] if cft_str in _get_cft_list(gdd10_list_str): - gddn = "GDD10" + gddn = 10 - # TODO: Delete this once using the right variables if gddn is not None: - gddn += "20" + gddn = f"GDD{gddn}X" return gddn From e82679df56e1a514f28ee4a38ae4d9c9b581b120 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 11:25:37 -0600 Subject: [PATCH 020/160] generate_gdd20_baseline: Grid, if needed. --- .../ctsm/crop_calendars/generate_gdd20_baseline.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 7f60fb6a68..71125e332d 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -17,8 +17,10 @@ # pylint: disable=wrong-import-position from ctsm.crop_calendars.import_ds import import_ds import ctsm.crop_calendars.cropcal_utils as utils +from ctsm.crop_calendars.grid_one_variable import grid_one_variable VAR_LIST_IN = ["GDD0X", "GDD8X", "GDD10X"] +GRIDDING_VAR_LIST = ["patches1d_ixy", "patches1d_jxy", "lat", "lon"] MISSING_FILL = -1 # Something impossible to ensure that you can mark it as a missing value, to be # bilinear-interpolated STREAM_YEAR = 2000 # The year specified for stream_yearFirst and stream_yearLast in the call of @@ -171,7 +173,7 @@ def generate_gdd20_baseline(input_files, output_file, author): input_files.sort() # Import history files and ensure they have lat/lon dims - ds_in = import_ds(input_files, VAR_LIST_IN) + ds_in = import_ds(input_files, VAR_LIST_IN + GRIDDING_VAR_LIST) if not all(x in ds_in.dims for x in ["lat", "lon"]): raise RuntimeError("Input files must have lat and lon dimensions") @@ -188,8 +190,11 @@ def generate_gdd20_baseline(input_files, output_file, author): dummy_da = _add_time_axis(dummy_da) # Process all crops + data_var_dict = {} + for v in GRIDDING_VAR_LIST: + data_var_dict[v] = ds_in[v] ds_out = xr.Dataset( - data_vars=None, + data_vars=data_var_dict, attrs={ "author": author, "created": dt.datetime.now().astimezone().isoformat(), @@ -228,6 +233,10 @@ def generate_gdd20_baseline(input_files, output_file, author): ds_out[var_out] = this_da encoding_dict[var_out] = {"dtype": "float64"} + # Grid, if needed + if any(x not in this_da.dims for x in ["lat", "lon"]): + ds_out[var_out] = grid_one_variable(ds_out, var_out) + # Save ds_out.to_netcdf(output_file, format="NETCDF3_CLASSIC", encoding=encoding_dict) From 8b43fec5168d56df1b7c5ac6e22635d70024c3b0 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 11:28:32 -0600 Subject: [PATCH 021/160] generate_gdd20_baseline: Improve long names. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 71125e332d..89a38a0a08 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -219,7 +219,7 @@ def generate_gdd20_baseline(input_files, output_file, author): # this_da = ds_in[gddn].fillna(MISSING_FILL) this_da = ds_in[gddn] this_da = _add_time_axis(this_da) - long_name = gddn + long_name = gddn.replace("X", "20") print(f" {gddn}") # Add attributes From dcacbd4003585edf1a8608bc9d70232285f01202 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 11:47:38 -0600 Subject: [PATCH 022/160] Minor cleanup in UpdateAccVars_CropGDDs. --- src/biogeophys/TemperatureType.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index f8d5206a1e..5ae0b7b20c 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1428,11 +1428,11 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d ((month > 9 .or. month < 4) .and. lat < 0._r8) ! Replace with read-in gdd20 accumulation season, if valid ! (If these aren't being read in or they're invalid, they'll be -1) - gdd20_season_start = crop_inst%gdd20_season_start_patch(p) - gdd20_season_end = crop_inst%gdd20_season_end_patch(p) + gdd20_season_start = gdd20_season_starts(p) + gdd20_season_end = gdd20_season_ends(p) if (gdd20_season_start >= 1 .and. gdd20_season_end >= 1) then in_accumulation_season = is_doy_in_interval( & - gdd20_season_starts(p), gdd20_season_ends(p), day) + gdd20_season_start, gdd20_season_end, day) end if if (month==1 .and. day==1 .and. secs==dtime) then From 2cf491da0127f1caeec5ae6d7152d637f2975599 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 11:53:44 -0600 Subject: [PATCH 023/160] Add outputs: GDD20_BASELINE, GDD20_SEASON_START/END. --- src/biogeochem/CropType.F90 | 28 +++++++++++++++++++++---- src/biogeophys/TemperatureType.F90 | 5 +++-- src/cpl/share_esmf/cropcalStreamMod.F90 | 18 ++++++++++------ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/biogeochem/CropType.F90 b/src/biogeochem/CropType.F90 index 04806f9349..fdfdaa05aa 100644 --- a/src/biogeochem/CropType.F90 +++ b/src/biogeochem/CropType.F90 @@ -53,8 +53,11 @@ module CropType integer , pointer :: rx_swindow_ends_thisyr_patch (:,:) ! all prescribed sowing window end dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: rx_cultivar_gdds_thisyr_patch (:,:) ! all cultivar GDD targets for this patch this year (ddays) [patch, mxsowings] real(r8), pointer :: gdd20_baseline_patch (:) ! GDD20 baseline for this patch (ddays) [patch] - integer , pointer :: gdd20_season_start_patch(:) ! gdd20 season start date for this patch (day of year) [patch] - integer , pointer :: gdd20_season_end_patch (:) ! gdd20 season end date for this patch (day of year) [patch] + + ! REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + real(r8), pointer :: gdd20_season_start_patch(:) ! gdd20 season start date for this patch (day of year) [patch] + real(r8), pointer :: gdd20_season_end_patch (:) ! gdd20 season end date for this patch (day of year) [patch] + real(r8), pointer :: sdates_thisyr_patch (:,:) ! all actual sowing dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: swindow_starts_thisyr_patch(:,:) ! all sowing window start dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: swindow_ends_thisyr_patch (:,:) ! all sowing window end dates for this patch this year (day of year) [patch, mxsowings] @@ -239,8 +242,11 @@ subroutine InitAllocate(this, bounds) allocate(this%rx_swindow_ends_thisyr_patch(begp:endp,1:mxsowings)) ; this%rx_swindow_ends_thisyr_patch (:,:) = -1 allocate(this%rx_cultivar_gdds_thisyr_patch(begp:endp,1:mxsowings)) ; this%rx_cultivar_gdds_thisyr_patch(:,:) = spval allocate(this%gdd20_baseline_patch(begp:endp)) ; this%gdd20_baseline_patch(:) = spval - allocate(this%gdd20_season_start_patch(begp:endp)); this%gdd20_season_start_patch(:) = -1 - allocate(this%gdd20_season_end_patch(begp:endp)) ; this%gdd20_season_end_patch (:) = -1 + + ! REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + allocate(this%gdd20_season_start_patch(begp:endp)); this%gdd20_season_start_patch(:) = spval + allocate(this%gdd20_season_end_patch(begp:endp)) ; this%gdd20_season_end_patch (:) = spval + allocate(this%sdates_thisyr_patch(begp:endp,1:mxsowings)) ; this%sdates_thisyr_patch(:,:) = spval allocate(this%swindow_starts_thisyr_patch(begp:endp,1:mxsowings)) ; this%swindow_starts_thisyr_patch(:,:) = spval allocate(this%swindow_ends_thisyr_patch (begp:endp,1:mxsowings)) ; this%swindow_ends_thisyr_patch (:,:) = spval @@ -364,6 +370,20 @@ subroutine InitHistory(this, bounds) avgflag='I', long_name='Reason for each crop harvest; should only be output annually', & ptr_patch=this%harvest_reason_thisyr_patch, default='inactive') + ! DEVELOPMENT ONLY; DELETE BEFORE MERGE + this%gdd20_baseline_patch(begp:endp) = spval + call hist_addfld1d (fname='GDD20_BASELINE', units='ddays', & + avgflag='A', long_name='Baseline mean growing-degree days accumulated during accumulation period (from input)', & + ptr_patch=this%gdd20_baseline_patch, default='inactive') + this%gdd20_season_start_patch(begp:endp) = spval + call hist_addfld1d (fname='GDD20_SEASON_START', units='day of year', & + avgflag='A', long_name='Start of the GDD20 accumulation season (from input)', & + ptr_patch=this%gdd20_season_start_patch, default='inactive') + this%gdd20_season_end_patch(begp:endp) = spval + call hist_addfld1d (fname='GDD20_SEASON_END', units='day of year', & + avgflag='A', long_name='End of the GDD20 accumulation season (from input)', & + ptr_patch=this%gdd20_season_end_patch, default='inactive') + end subroutine InitHistory subroutine InitCold(this, bounds) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 5ae0b7b20c..ae5703237e 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1428,8 +1428,9 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d ((month > 9 .or. month < 4) .and. lat < 0._r8) ! Replace with read-in gdd20 accumulation season, if valid ! (If these aren't being read in or they're invalid, they'll be -1) - gdd20_season_start = gdd20_season_starts(p) - gdd20_season_end = gdd20_season_ends(p) + ! REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + gdd20_season_start = int(gdd20_season_starts(p)) + gdd20_season_end = int(gdd20_season_ends(p)) if (gdd20_season_start >= 1 .and. gdd20_season_end >= 1) then in_accumulation_season = is_doy_in_interval( & gdd20_season_start, gdd20_season_end, day) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index ce595c0d70..802b669905 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -766,8 +766,10 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) n = ivt - npcropmin + 1 ! vegetated pft ig = g_to_ig(patch%gridcell(p)) - gdd20_season_starts(p) = dataptr2d_gdd20_season_start(ig,n) - gdd20_season_ends(p) = dataptr2d_gdd20_season_end (ig,n) + + ! REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + gdd20_season_starts(p) = real(dataptr2d_gdd20_season_start(ig,n), r8) + gdd20_season_ends(p) = real(dataptr2d_gdd20_season_end (ig,n), r8) else write(iulog,'(a,i0)') 'cropcal_interp(), gdd20 seasons: Crop patch has ivt ',ivt call ESMF_Finalize(endflag=ESMF_END_ABORT) @@ -775,15 +777,18 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) end do ! Handle invalid gdd20 season values - if (any(gdd20_season_starts(begp:endp) < 1 .or. gdd20_season_ends(begp:endp) < 1)) then + ! gdd20_season_starts and gdd20_season_ends REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + if (any(gdd20_season_starts(begp:endp) < 1._r8 .or. gdd20_season_ends(begp:endp) < 1._r8)) then ! Fail if not allowing fallback to paramfile sowing windows - if ((.not. allow_invalid_gdd20_season_inputs) .and. any(gdd20_season_starts(begp:endp) < 1 .and. patch%wtgcell(begp:endp) > 0._r8 .and. patch%itype(begp:endp) >= npcropmin)) then + ! gdd20_season_starts REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + if ((.not. allow_invalid_gdd20_season_inputs) .and. any(gdd20_season_starts(begp:endp) < 1._r8 .and. patch%wtgcell(begp:endp) > 0._r8 .and. patch%itype(begp:endp) >= npcropmin)) then write(iulog, *) 'At least one crop in one gridcell has invalid gdd20 season start date(s). To ignore and fall back to paramfile sowing windows, set allow_invalid_gdd20_season_inputs to .true.' write(iulog, *) 'Affected crops:' do ivt = npcropmin, mxpft do fp = 1, num_pcropp p = filter_pcropp(fp) - if (ivt == patch%itype(p) .and. patch%wtgcell(p) > 0._r8 .and. gdd20_season_starts(p) < 1) then + ! gdd20_season_starts REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + if (ivt == patch%itype(p) .and. patch%wtgcell(p) > 0._r8 .and. gdd20_season_starts(p) < 1._r8) then write(iulog, *) ' ',pftname(ivt),' (',ivt,')' exit ! Stop looking for patches of this type end if @@ -792,7 +797,8 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) call ESMF_Finalize(endflag=ESMF_END_ABORT) ! Fail if a gdd20 season start date is given without an end date (or vice versa) - else if (any((gdd20_season_starts(begp:endp) >= 1 .and. gdd20_season_ends(begp:endp) < 1) .or. (gdd20_season_starts(begp:endp) < 1 .and. gdd20_season_ends(begp:endp) >= 1))) then + ! gdd20_season_starts and gdd20_season_ends REAL FOR DEVELOPMENT ONLY; REVERT TO INTEGER BEFORE MERGE + else if (any((gdd20_season_starts(begp:endp) >= 1._r8 .and. gdd20_season_ends(begp:endp) < 1._r8) .or. (gdd20_season_starts(begp:endp) < 1._r8 .and. gdd20_season_ends(begp:endp) >= 1._r8))) then write(iulog, *) 'Every gdd20 season start date must have a corresponding end date.' call ESMF_Finalize(endflag=ESMF_END_ABORT) end if From 0083a08432859948707cf4e8e7b4937dc7f1d92c Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 12:03:15 -0600 Subject: [PATCH 024/160] rxcropmaturity.py is now parent rxcropmaturityshared.py. --- ...ropmaturity.py => rxcropmaturityshared.py} | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) rename cime_config/SystemTests/{rxcropmaturity.py => rxcropmaturityshared.py} (97%) diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturityshared.py similarity index 97% rename from cime_config/SystemTests/rxcropmaturity.py rename to cime_config/SystemTests/rxcropmaturityshared.py index 75fff8a0e0..f8e4b1c9bb 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturityshared.py @@ -11,6 +11,10 @@ code do the interpolation. However, that wouldn't act on harvest dates (which are needed for generate_gdds.py). I could have Python interpolate those, but this would cause a potential inconsistency. + +Note that this is just a parent class. The actual tests are RXCROPMATURITY and +RXCROPMATURITY_SKIPRUN, the latter of which does everything except perform and +check the CTSM runs. """ import os @@ -25,7 +29,7 @@ logger = logging.getLogger(__name__) -class RXCROPMATURITY(SystemTestsCommon): +class RXCROPMATURITYSHARED(SystemTestsCommon): def __init__(self, case): # initialize an object interface to the SMS system test SystemTestsCommon.__init__(self, case) @@ -84,7 +88,7 @@ def __init__(self, case): # Which conda environment should we use? self._get_conda_env() - def run_phase(self): + def _run_phase(self, skip_run=False): # Modeling this after the SSP test, we create a clone to be the case whose outputs we don't # want to be saved as baseline. @@ -146,9 +150,10 @@ def run_phase(self): # "No history files expected, set suffix=None to avoid compare error" # We *do* expect history files here, but anyway. This works. self._skip_pnl = False - self.run_indv(suffix=None, st_archive=True) - self._run_generate_gdds(case_gddgen) + if not skip_run: + self.run_indv(suffix=None, st_archive=True) + self._run_generate_gdds(case_gddgen) # ------------------------------------------------------------------- # (3) Set up and perform Prescribed Calendars run @@ -168,13 +173,15 @@ def run_phase(self): ] ) - self.run_indv() + if not skip_run: + self.run_indv() # ------------------------------------------------------------------- # (4) Check Prescribed Calendars run # ------------------------------------------------------------------- logger.info("RXCROPMATURITY log: output check: Prescribed Calendars") - self._run_check_rxboth_run() + if not skip_run: + self._run_check_rxboth_run() # Get sowing and harvest dates for this resolution. def _get_rx_dates(self): From f728f9676e5103298dd255fcec2a6796d22e0144 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 13:44:07 -0600 Subject: [PATCH 025/160] Added back RXCROPMATURITY. --- cime_config/SystemTests/rxcropmaturity.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 cime_config/SystemTests/rxcropmaturity.py diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py new file mode 100644 index 0000000000..3eadccfeb3 --- /dev/null +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -0,0 +1,5 @@ +from RXCROPMATURITYSHARED import RXCROPMATURITYSHARED + +class RXCROPMATURITY(RXCROPMATURITYSHARED): + def run_phase(self): + self._run_phase() \ No newline at end of file From e4b97a233cd722ab915a9b0be27f5ece509c8ca9 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 13:44:51 -0600 Subject: [PATCH 026/160] Add RXCROPMATURITYSKIPRUN. --- cime_config/SystemTests/rxcropmaturityskiprun.py | 6 ++++++ cime_config/config_tests.xml | 10 ++++++++++ 2 files changed, 16 insertions(+) create mode 100644 cime_config/SystemTests/rxcropmaturityskiprun.py diff --git a/cime_config/SystemTests/rxcropmaturityskiprun.py b/cime_config/SystemTests/rxcropmaturityskiprun.py new file mode 100644 index 0000000000..d52742f95a --- /dev/null +++ b/cime_config/SystemTests/rxcropmaturityskiprun.py @@ -0,0 +1,6 @@ +print("pre-import") +from RXCROPMATURITYSHARED import RXCROPMATURITYSHARED + +class RXCROPMATURITYSKIPRUN(RXCROPMATURITYSHARED): + def run_phase(self): + self._run_phase(skip_run=True) \ No newline at end of file diff --git a/cime_config/config_tests.xml b/cime_config/config_tests.xml index c0b6afed9d..98434da10e 100644 --- a/cime_config/config_tests.xml +++ b/cime_config/config_tests.xml @@ -133,6 +133,16 @@ This defines various CTSM-specific system tests $STOP_N + + As RXCROPMATURITY but don't actually run or postprocess; just setup + 1 + FALSE + FALSE + never + $STOP_OPTION + $STOP_N + + lnd/clm2/surfdata_esmf/ctsm5.2.0/landuse.timeseries_1x1_smallvilleIA_SSP2-4.5_1850-1855_78pfts_c240221.nc + + + diff --git a/bld/namelist_files/namelist_defaults_overall.xml b/bld/namelist_files/namelist_defaults_overall.xml index 479b2a02b7..5b7ae1bdd9 100644 --- a/bld/namelist_files/namelist_defaults_overall.xml +++ b/bld/namelist_files/namelist_defaults_overall.xml @@ -62,6 +62,7 @@ determine default values for namelists. 1x1_urbanc_alpha 1x1_numaIA 1x1_smallvilleIA +1x1_cidadinhoBR 2000 @@ -110,6 +111,7 @@ determine default values for namelists. test navy test +test gx1v7 diff --git a/bld/unit_testers/build-namelist_test.pl b/bld/unit_testers/build-namelist_test.pl index b0ddb1e448..5c8aa3468d 100755 --- a/bld/unit_testers/build-namelist_test.pl +++ b/bld/unit_testers/build-namelist_test.pl @@ -1590,21 +1590,23 @@ sub cat_and_create_namelistinfile { print "==================================================\n"; # Check for crop resolutions -my $crop1850_res = "1x1_smallvilleIA"; -$options = "-bgc bgc -crop -res $crop1850_res -use_case 1850_control -envxml_dir ."; -&make_env_run(); -eval{ system( "$bldnml $options > $tempfile 2>&1 " ); }; -is( $@, '', "$options" ); -$cfiles->checkfilesexist( "$options", $mode ); -$cfiles->shownmldiff( "default", "standard" ); -if ( defined($opts{'compare'}) ) { - $cfiles->doNOTdodiffonfile( "$tempfile", "$options", $mode ); - $cfiles->comparefiles( "$options", $mode, $opts{'compare'} ); -} -if ( defined($opts{'generate'}) ) { - $cfiles->copyfiles( "$options", $mode ); +my @crop1850_res = ( "1x1_smallvilleIA", "1x1_cidadinhoBR" ); +foreach my $res ( @crop1850_res ) { + $options = "-bgc bgc -crop -res $res -use_case 1850_control -envxml_dir ."; + &make_env_run(); + eval{ system( "$bldnml $options > $tempfile 2>&1 " ); }; + is( $@, '', "$options" ); + $cfiles->checkfilesexist( "$options", $mode ); + $cfiles->shownmldiff( "default", "standard" ); + if ( defined($opts{'compare'}) ) { + $cfiles->doNOTdodiffonfile( "$tempfile", "$options", $mode ); + $cfiles->comparefiles( "$options", $mode, $opts{'compare'} ); + } + if ( defined($opts{'generate'}) ) { + $cfiles->copyfiles( "$options", $mode ); + } + &cleanup(); } -&cleanup(); my @crop_res = ( "1x1_numaIA", "4x5", "10x15", "0.9x1.25", "1.9x2.5", "ne3np4.pg3", "ne30np4", "ne30np4.pg3", "C96", "mpasa120" ); foreach my $res ( @crop_res ) { diff --git a/tools/mksurfdata_esmf/Makefile b/tools/mksurfdata_esmf/Makefile index d8bacdc5dd..c344843d06 100644 --- a/tools/mksurfdata_esmf/Makefile +++ b/tools/mksurfdata_esmf/Makefile @@ -54,7 +54,10 @@ SUBSETDATA_POINT_URBAN = $(SUBSETDATA_POINT) --include-nonveg # Subset data sites... SUBSETDATA_1X1_BRAZIL := --lat -7 --lon -55 --site 1x1_brazil SUBSETDATA_1X1_NUMAIA := --lat 40.6878 --lon 267.0228 --site 1x1_numaIA -SUBSETDATA_1X1_SMALL := --lat 40.6878 --lon 267.0228 --site 1x1_smallvilleIA \ +SUBSETDATA_1X1_SMALL_IA := --lat 40.6878 --lon 267.0228 --site 1x1_smallvilleIA \ + --dompft 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 \ + --pctpft 6.5 1.5 1.6 1.7 1.8 1.9 1.5 1.6 1.7 1.8 1.9 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 +SUBSETDATA_1X1_SMALL_BR := --lat -12.9952 --lon 305.3233 --site 1x1_cidadinhoBR \ --dompft 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 \ --pctpft 6.5 1.5 1.6 1.7 1.8 1.9 1.5 1.6 1.7 1.8 1.9 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 1.5 # NOTE: The 1850 smallvilleIA site is constructed to start with 100% natural vegetation, so we can test transition to crops @@ -112,6 +115,7 @@ all-subset : \ 1x1-smallville-present \ 1x1-smallville-1850 \ 1x1-smallville-transient \ + 1x1-cidadinho-present \ urban DEBUG: @@ -239,7 +243,7 @@ crop-global-hist-ne30 : FORCE $(SUBSETDATA_POINT_ALLLU) --create-surface $(SUBSETDATA_1X1_NUMAIA) 1x1-smallville-present : FORCE - $(SUBSETDATA_POINT) --create-surface $(SUBSETDATA_1X1_SMALL) + $(SUBSETDATA_POINT) --create-surface $(SUBSETDATA_1X1_SMALL_IA) # Note that the smallville 1850 dataset is entirely natural vegetation. This # facilitates testing a transient case that starts with no crop, and then later @@ -254,6 +258,9 @@ crop-global-hist-ne30 : FORCE $(SUBSETDATA_POINT) --create-landuse $(SUBSETDATA_1X1_SMALLTRANSIENT) ../modify_input_files/modify_smallville.sh +1x1-cidadinho-present : FORCE + $(SUBSETDATA_POINT) --create-surface $(SUBSETDATA_1X1_SMALL_BR) + # # Crop with future scenarios # From d33ef3e5b3e695a627fa23dd4d56e85686794c2c Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 3 Jun 2024 12:03:15 -0600 Subject: [PATCH 058/160] Add partial versions of RXCROPMATURITY test. Useful for troubleshooting: * RXCROPMATURITYSKIPRUN skips the build and run phases. * RXCROPMATURITYSKIPBUILD just skips the build phase. It still does the run phase so as to test everything before actual run submission. --- cime_config/SystemTests/rxcropmaturity.py | 444 +---------------- .../SystemTests/rxcropmaturityshared.py | 451 ++++++++++++++++++ .../SystemTests/rxcropmaturityskipbuild.py | 9 + .../SystemTests/rxcropmaturityskiprun.py | 9 + cime_config/config_tests.xml | 20 + 5 files changed, 492 insertions(+), 441 deletions(-) create mode 100644 cime_config/SystemTests/rxcropmaturityshared.py create mode 100644 cime_config/SystemTests/rxcropmaturityskipbuild.py create mode 100644 cime_config/SystemTests/rxcropmaturityskiprun.py diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index acb63bb000..12ec608898 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -1,444 +1,6 @@ -""" -CTSM-specific test that first performs a GDD-generating run, then calls -Python code to generate the maturity requirement file. This is then used -in a sowing+maturity forced run, which finally is tested to ensure -correct behavior. +from rxcropmaturityshared import RXCROPMATURITYSHARED -Currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions. Eventually, -this test should be able to generate its own files at whatever resolution it's -called at. Well, really, the ultimate goal would be to give CLM the files -at the original resolution (for GGCMI phase 3, 0.5°) and have the stream -code do the interpolation. However, that wouldn't act on harvest dates -(which are needed for generate_gdds.py). I could have Python interpolate -those, but this would cause a potential inconsistency. -""" - -import os -import re -import systemtest_utils as stu -import subprocess -from CIME.SystemTests.system_tests_common import SystemTestsCommon -from CIME.XML.standard_module_setup import * -from CIME.SystemTests.test_utils.user_nl_utils import append_to_user_nl_files -import shutil, glob - -logger = logging.getLogger(__name__) - - -class RXCROPMATURITY(SystemTestsCommon): - def __init__(self, case): - # initialize an object interface to the SMS system test - SystemTestsCommon.__init__(self, case) - - # Ensure run length is at least 5 years. Minimum to produce one complete growing season (i.e., two complete calendar years) actually 4 years, but that only gets you 1 season usable for GDD generation, so you can't check for season-to-season consistency. - stop_n = self._case.get_value("STOP_N") - stop_option = self._case.get_value("STOP_OPTION") - stop_n_orig = stop_n - stop_option_orig = stop_option - if "nsecond" in stop_option: - stop_n /= 60 - stop_option = "nminutes" - if "nminute" in stop_option: - stop_n /= 60 - stop_option = "nhours" - if "nhour" in stop_option: - stop_n /= 24 - stop_option = "ndays" - if "nday" in stop_option: - stop_n /= 365 - stop_option = "nyears" - if "nmonth" in stop_option: - stop_n /= 12 - stop_option = "nyears" - error_message = None - if "nyear" not in stop_option: - error_message = ( - f"STOP_OPTION ({stop_option_orig}) must be nsecond(s), nminute(s), " - + "nhour(s), nday(s), nmonth(s), or nyear(s)" - ) - elif stop_n < 5: - error_message = ( - "RXCROPMATURITY must be run for at least 5 years; you requested " - + f"{stop_n_orig} {stop_option_orig[1:]}" - ) - if error_message is not None: - logger.error(error_message) - raise RuntimeError(error_message) - - # Get the number of complete years that will be run - self._run_Nyears = int(stop_n) - - # Only allow RXCROPMATURITY to be called with test cropMonthOutput - casebaseid = self._case.get_value("CASEBASEID") - if casebaseid.split("-")[-1] != "cropMonthOutput": - error_message = ( - "Only call RXCROPMATURITY with test cropMonthOutput " - + "to avoid potentially huge sets of daily outputs." - ) - logger.error(error_message) - raise RuntimeError(error_message) - - # Get files with prescribed sowing and harvest dates - self._get_rx_dates() - - # Which conda environment should we use? - self._get_conda_env() +class RXCROPMATURITY(RXCROPMATURITYSHARED): def run_phase(self): - # Modeling this after the SSP test, we create a clone to be the case whose outputs we don't - # want to be saved as baseline. - - # ------------------------------------------------------------------- - # (1) Set up GDD-generating run - # ------------------------------------------------------------------- - # Create clone to be GDD-Generating case - logger.info("RXCROPMATURITY log: cloning setup") - case_rxboth = self._case - caseroot = self._case.get_value("CASEROOT") - clone_path = f"{caseroot}.gddgen" - self._path_gddgen = clone_path - if os.path.exists(self._path_gddgen): - shutil.rmtree(self._path_gddgen) - logger.info("RXCROPMATURITY log: cloning") - case_gddgen = self._case.create_clone(clone_path, keepexe=True) - logger.info("RXCROPMATURITY log: done cloning") - - os.chdir(self._path_gddgen) - self._set_active_case(case_gddgen) - - # Set up stuff that applies to both tests - self._setup_all() - - # Add stuff specific to GDD-Generating run - logger.info("RXCROPMATURITY log: modify user_nl files: generate GDDs") - self._append_to_user_nl_clm( - [ - "generate_crop_gdds = .true.", - "use_mxmat = .false.", - " ", - "! (h2) Daily outputs for GDD generation and figure-making", - "hist_fincl3 = 'GDDACCUM', 'GDDHARV'", - "hist_nhtfrq(3) = -24", - "hist_mfilt(3) = 365", - "hist_type1d_pertape(3) = 'PFTS'", - "hist_dov2xy(3) = .false.", - ] - ) - - # If flanduse_timeseries is defined, we need to make a static version for this test. This - # should have every crop in most of the world. - self._get_flanduse_timeseries_in(case_gddgen) - if self._flanduse_timeseries_in is not None: - - # Download files from the server, if needed - case_gddgen.check_all_input_data() - - # Make custom version of surface file - logger.info("RXCROPMATURITY log: run fsurdat_modifier") - self._run_fsurdat_modifier() - - # ------------------------------------------------------------------- - # (2) Perform GDD-generating run and generate prescribed GDDs file - # ------------------------------------------------------------------- - logger.info("RXCROPMATURITY log: Start GDD-Generating run") - - # As per SSP test: - # "No history files expected, set suffix=None to avoid compare error" - # We *do* expect history files here, but anyway. This works. - self._skip_pnl = False - self.run_indv(suffix=None, st_archive=True) - - self._run_generate_gdds(case_gddgen) - - # ------------------------------------------------------------------- - # (3) Set up and perform Prescribed Calendars run - # ------------------------------------------------------------------- - os.chdir(caseroot) - self._set_active_case(case_rxboth) - - # Set up stuff that applies to both tests - self._setup_all() - - # Add stuff specific to Prescribed Calendars run - logger.info("RXCROPMATURITY log: modify user_nl files: Prescribed Calendars") - self._append_to_user_nl_clm( - [ - "generate_crop_gdds = .false.", - f"stream_fldFileName_cultivar_gdds = '{self._gdds_file}'", - ] - ) - - self.run_indv() - - # ------------------------------------------------------------------- - # (4) Check Prescribed Calendars run - # ------------------------------------------------------------------- - logger.info("RXCROPMATURITY log: output check: Prescribed Calendars") - self._run_check_rxboth_run() - - # Get sowing and harvest dates for this resolution. - def _get_rx_dates(self): - # Eventually, I want to remove these hard-coded resolutions so that this test can generate - # its own sowing and harvest date files at whatever resolution is requested. - lnd_grid = self._case.get_value("LND_GRID") - input_data_root = self._case.get_value("DIN_LOC_ROOT") - processed_crop_dates_dir = f"{input_data_root}/lnd/clm2/cropdata/calendars/processed" - if lnd_grid == "10x15": - self._sdatefile = os.path.join( - processed_crop_dates_dir, - "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", - ) - self._hdatefile = os.path.join( - processed_crop_dates_dir, - "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", - ) - elif lnd_grid == "1.9x2.5": - self._sdatefile = os.path.join( - processed_crop_dates_dir, - "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", - ) - self._hdatefile = os.path.join( - processed_crop_dates_dir, - "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", - ) - elif lnd_grid == "0.9x1.25": - self._sdatefile = os.path.join( - processed_crop_dates_dir, - "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134417.nc", - ) - self._hdatefile = os.path.join( - processed_crop_dates_dir, - "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134418.nc", - ) - else: - error_message = "ERROR: RXCROPMATURITY currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions" - logger.error(error_message) - raise RuntimeError(error_message) - - # Ensure files exist - error_message = None - if not os.path.exists(self._sdatefile): - error_message = f"ERROR: Sowing date file not found: {self._sdatefile}" - elif not os.path.exists(self._hdatefile): - error_message = f"ERROR: Harvest date file not found: {self._sdatefile}" - if error_message is not None: - logger.error(error_message) - raise RuntimeError(error_message) - - def _setup_all(self): - logger.info("RXCROPMATURITY log: _setup_all start") - - # Get some info - self._ctsm_root = self._case.get_value("COMP_ROOT_DIR_LND") - run_startdate = self._case.get_value("RUN_STARTDATE") - self._run_startyear = int(run_startdate.split("-")[0]) - - # Set sowing dates file (and other crop calendar settings) for all runs - logger.info("RXCROPMATURITY log: modify user_nl files: all tests") - self._modify_user_nl_allruns() - logger.info("RXCROPMATURITY log: _setup_all done") - - # Make a surface dataset that has every crop in every gridcell - def _run_fsurdat_modifier(self): - - # fsurdat should be defined. Where is it? - self._fsurdat_in = None - with open(self._lnd_in_path, "r") as lnd_in: - for line in lnd_in: - fsurdat_in = re.match(r" *fsurdat *= *'(.*)'", line) - if fsurdat_in: - self._fsurdat_in = fsurdat_in.group(1) - break - if self._fsurdat_in is None: - error_message = "fsurdat not defined" - logger.error(error_message) - raise RuntimeError(error_message) - - # Where we will save the fsurdat version for this test - path, ext = os.path.splitext(self._fsurdat_in) - dir_in, filename_in_noext = os.path.split(path) - self._fsurdat_out = os.path.join( - self._path_gddgen, f"{filename_in_noext}.all_crops_everywhere{ext}" - ) - - # Make fsurdat for this test, if not already done - if not os.path.exists(self._fsurdat_out): - tool_path = os.path.join( - self._ctsm_root, - "tools", - "modify_input_files", - "fsurdat_modifier", - ) - - # Create configuration file for fsurdat_modifier - self._cfg_path = os.path.join( - self._path_gddgen, - "modify_fsurdat_allcropseverywhere.cfg", - ) - self._create_config_file_evenlysplitcrop() - - command = f"python3 {tool_path} {self._cfg_path} " - stu.run_python_script( - self._get_caseroot(), - self._this_conda_env, - command, - tool_path, - ) - - # Modify namelist - logger.info("RXCROPMATURITY log: modify user_nl files: new fsurdat") - self._append_to_user_nl_clm( - [ - "fsurdat = '{}'".format(self._fsurdat_out), - "do_transient_crops = .false.", - "flanduse_timeseries = ''", - "use_init_interp = .true.", - ] - ) - - def _create_config_file_evenlysplitcrop(self): - """ - Open the new and the template .cfg files - Loop line by line through the template .cfg file - When string matches, replace that line's content - """ - cfg_template_path = os.path.join( - self._ctsm_root, "tools/modify_input_files/modify_fsurdat_template.cfg" - ) - - with open(self._cfg_path, "w", encoding="utf-8") as cfg_out: - # Copy template, replacing some lines - with open(cfg_template_path, "r", encoding="utf-8") as cfg_in: - for line in cfg_in: - if re.match(r" *evenly_split_cropland *=", line): - line = f"evenly_split_cropland = True" - elif re.match(r" *fsurdat_in *=", line): - line = f"fsurdat_in = {self._fsurdat_in}" - elif re.match(r" *fsurdat_out *=", line): - line = f"fsurdat_out = {self._fsurdat_out}" - elif re.match(r" *process_subgrid_section *=", line): - line = f"process_subgrid_section = True" - cfg_out.write(line) - - # Add new lines - cfg_out.write("\n") - cfg_out.write("[modify_fsurdat_subgrid_fractions]\n") - cfg_out.write("PCT_CROP = 100.0\n") - cfg_out.write("PCT_NATVEG = 0.0\n") - cfg_out.write("PCT_GLACIER = 0.0\n") - cfg_out.write("PCT_WETLAND = 0.0\n") - cfg_out.write("PCT_LAKE = 0.0\n") - cfg_out.write("PCT_OCEAN = 0.0\n") - cfg_out.write("PCT_URBAN = 0.0 0.0 0.0\n") - - def _run_check_rxboth_run(self): - - output_dir = os.path.join(self._get_caseroot(), "run") - first_usable_year = self._run_startyear + 2 - last_usable_year = self._run_startyear + self._run_Nyears - 2 - - tool_path = os.path.join( - self._ctsm_root, "python", "ctsm", "crop_calendars", "check_rxboth_run.py" - ) - command = ( - f"python3 {tool_path} " - + f"--directory {output_dir} " - + f"-y1 {first_usable_year} " - + f"-yN {last_usable_year} " - + f"--rx-sdates-file {self._sdatefile} " - + f"--rx-gdds-file {self._gdds_file} " - ) - stu.run_python_script( - self._get_caseroot(), - self._this_conda_env, - command, - tool_path, - ) - - def _modify_user_nl_allruns(self): - nl_additions = [ - "stream_meshfile_cropcal = '{}'".format(self._case.get_value("LND_DOMAIN_MESH")), - "stream_fldFileName_swindow_start = '{}'".format(self._sdatefile), - "stream_fldFileName_swindow_end = '{}'".format(self._sdatefile), - "stream_year_first_cropcal = 2000", - "stream_year_last_cropcal = 2000", - "model_year_align_cropcal = 2000", - " ", - "! (h1) Annual outputs on sowing or harvest axis", - "hist_fincl2 = 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV'", - "hist_nhtfrq(2) = 17520", - "hist_mfilt(2) = 999", - "hist_type1d_pertape(2) = 'PFTS'", - "hist_dov2xy(2) = .false.", - ] - self._append_to_user_nl_clm(nl_additions) - - def _run_generate_gdds(self, case_gddgen): - self._generate_gdds_dir = os.path.join(self._path_gddgen, "generate_gdds_out") - os.makedirs(self._generate_gdds_dir) - - # Get arguments to generate_gdds.py - dout_sr = case_gddgen.get_value("DOUT_S_ROOT") - input_dir = os.path.join(dout_sr, "lnd", "hist") - first_season = self._run_startyear + 2 - last_season = self._run_startyear + self._run_Nyears - 2 - sdates_file = self._sdatefile - hdates_file = self._hdatefile - - # It'd be much nicer to call generate_gdds.main(), but I can't import generate_gdds. - tool_path = os.path.join( - self._ctsm_root, "python", "ctsm", "crop_calendars", "generate_gdds.py" - ) - command = " ".join( - [ - f"python3 {tool_path}", - f"--input-dir {input_dir}", - f"--first-season {first_season}", - f"--last-season {last_season}", - f"--sdates-file {sdates_file}", - f"--hdates-file {hdates_file}", - f"--output-dir generate_gdds_out", - f"--skip-crops miscanthus,irrigated_miscanthus", - ] - ) - stu.run_python_script( - self._get_caseroot(), - self._this_conda_env, - command, - tool_path, - ) - - # Where were the prescribed maturity requirements saved? - generated_gdd_files = glob.glob(os.path.join(self._generate_gdds_dir, "gdds_*.nc")) - if len(generated_gdd_files) != 1: - error_message = f"ERROR: Expected one matching prescribed maturity requirements file; found {len(generated_gdd_files)}: {generated_gdd_files}" - logger.error(error_message) - raise RuntimeError(error_message) - self._gdds_file = generated_gdd_files[0] - - def _get_conda_env(self): - conda_setup_commands = stu.cmds_to_setup_conda(self._get_caseroot()) - - # If npl conda environment is available, use that (It has dask, which - # enables chunking, which makes reading daily 1-degree netCDF files - # much more efficient. - if "npl " in os.popen(conda_setup_commands + "conda env list").read(): - self._this_conda_env = "npl" - else: - self._this_conda_env = "ctsm_pylib" - - def _append_to_user_nl_clm(self, additions): - caseroot = self._get_caseroot() - append_to_user_nl_files(caseroot=caseroot, component="clm", contents=additions) - - # Is flanduse_timeseries defined? If so, where is it? - def _get_flanduse_timeseries_in(self, case): - case.create_namelists(component="lnd") - self._lnd_in_path = os.path.join(self._path_gddgen, "CaseDocs", "lnd_in") - self._flanduse_timeseries_in = None - with open(self._lnd_in_path, "r") as lnd_in: - for line in lnd_in: - flanduse_timeseries_in = re.match(r" *flanduse_timeseries *= *'(.*)'", line) - if flanduse_timeseries_in: - self._flanduse_timeseries_in = flanduse_timeseries_in.group(1) - break + self._run_phase() diff --git a/cime_config/SystemTests/rxcropmaturityshared.py b/cime_config/SystemTests/rxcropmaturityshared.py new file mode 100644 index 0000000000..6b3a3191de --- /dev/null +++ b/cime_config/SystemTests/rxcropmaturityshared.py @@ -0,0 +1,451 @@ +""" +CTSM-specific test that first performs a GDD-generating run, then calls +Python code to generate the maturity requirement file. This is then used +in a sowing+maturity forced run, which finally is tested to ensure +correct behavior. + +Currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions. Eventually, +this test should be able to generate its own files at whatever resolution it's +called at. Well, really, the ultimate goal would be to give CLM the files +at the original resolution (for GGCMI phase 3, 0.5°) and have the stream +code do the interpolation. However, that wouldn't act on harvest dates +(which are needed for generate_gdds.py). I could have Python interpolate +those, but this would cause a potential inconsistency. + +Note that this is just a parent class. The actual tests are RXCROPMATURITY and +RXCROPMATURITY_SKIPRUN, the latter of which does everything except perform and +check the CTSM runs. +""" + +import os +import re +import systemtest_utils as stu +import subprocess +from CIME.SystemTests.system_tests_common import SystemTestsCommon +from CIME.XML.standard_module_setup import * +from CIME.SystemTests.test_utils.user_nl_utils import append_to_user_nl_files +import shutil, glob + +logger = logging.getLogger(__name__) + + +class RXCROPMATURITYSHARED(SystemTestsCommon): + def __init__(self, case): + # initialize an object interface to the SMS system test + SystemTestsCommon.__init__(self, case) + + # Ensure run length is at least 5 years. Minimum to produce one complete growing season (i.e., two complete calendar years) actually 4 years, but that only gets you 1 season usable for GDD generation, so you can't check for season-to-season consistency. + stop_n = self._case.get_value("STOP_N") + stop_option = self._case.get_value("STOP_OPTION") + stop_n_orig = stop_n + stop_option_orig = stop_option + if "nsecond" in stop_option: + stop_n /= 60 + stop_option = "nminutes" + if "nminute" in stop_option: + stop_n /= 60 + stop_option = "nhours" + if "nhour" in stop_option: + stop_n /= 24 + stop_option = "ndays" + if "nday" in stop_option: + stop_n /= 365 + stop_option = "nyears" + if "nmonth" in stop_option: + stop_n /= 12 + stop_option = "nyears" + error_message = None + if "nyear" not in stop_option: + error_message = ( + f"STOP_OPTION ({stop_option_orig}) must be nsecond(s), nminute(s), " + + "nhour(s), nday(s), nmonth(s), or nyear(s)" + ) + elif stop_n < 5: + error_message = ( + "RXCROPMATURITY must be run for at least 5 years; you requested " + + f"{stop_n_orig} {stop_option_orig[1:]}" + ) + if error_message is not None: + logger.error(error_message) + raise RuntimeError(error_message) + + # Get the number of complete years that will be run + self._run_Nyears = int(stop_n) + + # Only allow RXCROPMATURITY to be called with test cropMonthOutput + casebaseid = self._case.get_value("CASEBASEID") + if casebaseid.split("-")[-1] != "cropMonthOutput": + error_message = ( + "Only call RXCROPMATURITY with test cropMonthOutput " + + "to avoid potentially huge sets of daily outputs." + ) + logger.error(error_message) + raise RuntimeError(error_message) + + # Get files with prescribed sowing and harvest dates + self._get_rx_dates() + + # Which conda environment should we use? + self._get_conda_env() + + def _run_phase(self, skip_run=False): + # Modeling this after the SSP test, we create a clone to be the case whose outputs we don't + # want to be saved as baseline. + + # ------------------------------------------------------------------- + # (1) Set up GDD-generating run + # ------------------------------------------------------------------- + # Create clone to be GDD-Generating case + logger.info("RXCROPMATURITY log: cloning setup") + case_rxboth = self._case + caseroot = self._case.get_value("CASEROOT") + clone_path = f"{caseroot}.gddgen" + self._path_gddgen = clone_path + if os.path.exists(self._path_gddgen): + shutil.rmtree(self._path_gddgen) + logger.info("RXCROPMATURITY log: cloning") + case_gddgen = self._case.create_clone(clone_path, keepexe=True) + logger.info("RXCROPMATURITY log: done cloning") + + os.chdir(self._path_gddgen) + self._set_active_case(case_gddgen) + + # Set up stuff that applies to both tests + self._setup_all() + + # Add stuff specific to GDD-Generating run + logger.info("RXCROPMATURITY log: modify user_nl files: generate GDDs") + self._append_to_user_nl_clm( + [ + "generate_crop_gdds = .true.", + "use_mxmat = .false.", + " ", + "! (h2) Daily outputs for GDD generation and figure-making", + "hist_fincl3 = 'GDDACCUM', 'GDDHARV'", + "hist_nhtfrq(3) = -24", + "hist_mfilt(3) = 365", + "hist_type1d_pertape(3) = 'PFTS'", + "hist_dov2xy(3) = .false.", + ] + ) + + # If flanduse_timeseries is defined, we need to make a static version for this test. This + # should have every crop in most of the world. + self._get_flanduse_timeseries_in(case_gddgen) + if self._flanduse_timeseries_in is not None: + + # Download files from the server, if needed + case_gddgen.check_all_input_data() + + # Make custom version of surface file + logger.info("RXCROPMATURITY log: run fsurdat_modifier") + self._run_fsurdat_modifier() + + # ------------------------------------------------------------------- + # (2) Perform GDD-generating run and generate prescribed GDDs file + # ------------------------------------------------------------------- + logger.info("RXCROPMATURITY log: Start GDD-Generating run") + + # As per SSP test: + # "No history files expected, set suffix=None to avoid compare error" + # We *do* expect history files here, but anyway. This works. + self._skip_pnl = False + + if not skip_run: + self.run_indv(suffix=None, st_archive=True) + self._run_generate_gdds(case_gddgen) + + # ------------------------------------------------------------------- + # (3) Set up and perform Prescribed Calendars run + # ------------------------------------------------------------------- + os.chdir(caseroot) + self._set_active_case(case_rxboth) + + # Set up stuff that applies to both tests + self._setup_all() + + # Add stuff specific to Prescribed Calendars run + logger.info("RXCROPMATURITY log: modify user_nl files: Prescribed Calendars") + self._append_to_user_nl_clm( + [ + "generate_crop_gdds = .false.", + f"stream_fldFileName_cultivar_gdds = '{self._gdds_file}'", + ] + ) + + if not skip_run: + self.run_indv() + + # ------------------------------------------------------------------- + # (4) Check Prescribed Calendars run + # ------------------------------------------------------------------- + logger.info("RXCROPMATURITY log: output check: Prescribed Calendars") + if not skip_run: + self._run_check_rxboth_run() + + # Get sowing and harvest dates for this resolution. + def _get_rx_dates(self): + # Eventually, I want to remove these hard-coded resolutions so that this test can generate + # its own sowing and harvest date files at whatever resolution is requested. + lnd_grid = self._case.get_value("LND_GRID") + input_data_root = self._case.get_value("DIN_LOC_ROOT") + processed_crop_dates_dir = f"{input_data_root}/lnd/clm2/cropdata/calendars/processed" + if lnd_grid == "10x15": + self._sdatefile = os.path.join( + processed_crop_dates_dir, + "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", + ) + self._hdatefile = os.path.join( + processed_crop_dates_dir, + "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", + ) + elif lnd_grid == "1.9x2.5": + self._sdatefile = os.path.join( + processed_crop_dates_dir, + "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", + ) + self._hdatefile = os.path.join( + processed_crop_dates_dir, + "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", + ) + elif lnd_grid == "0.9x1.25": + self._sdatefile = os.path.join( + processed_crop_dates_dir, + "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134417.nc", + ) + self._hdatefile = os.path.join( + processed_crop_dates_dir, + "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134418.nc", + ) + else: + error_message = "ERROR: RXCROPMATURITY currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions" + logger.error(error_message) + raise RuntimeError(error_message) + + # Ensure files exist + error_message = None + if not os.path.exists(self._sdatefile): + error_message = f"ERROR: Sowing date file not found: {self._sdatefile}" + elif not os.path.exists(self._hdatefile): + error_message = f"ERROR: Harvest date file not found: {self._sdatefile}" + if error_message is not None: + logger.error(error_message) + raise RuntimeError(error_message) + + def _setup_all(self): + logger.info("RXCROPMATURITY log: _setup_all start") + + # Get some info + self._ctsm_root = self._case.get_value("COMP_ROOT_DIR_LND") + run_startdate = self._case.get_value("RUN_STARTDATE") + self._run_startyear = int(run_startdate.split("-")[0]) + + # Set sowing dates file (and other crop calendar settings) for all runs + logger.info("RXCROPMATURITY log: modify user_nl files: all tests") + self._modify_user_nl_allruns() + logger.info("RXCROPMATURITY log: _setup_all done") + + # Make a surface dataset that has every crop in every gridcell + def _run_fsurdat_modifier(self): + + # fsurdat should be defined. Where is it? + self._fsurdat_in = None + with open(self._lnd_in_path, "r") as lnd_in: + for line in lnd_in: + fsurdat_in = re.match(r" *fsurdat *= *'(.*)'", line) + if fsurdat_in: + self._fsurdat_in = fsurdat_in.group(1) + break + if self._fsurdat_in is None: + error_message = "fsurdat not defined" + logger.error(error_message) + raise RuntimeError(error_message) + + # Where we will save the fsurdat version for this test + path, ext = os.path.splitext(self._fsurdat_in) + dir_in, filename_in_noext = os.path.split(path) + self._fsurdat_out = os.path.join( + self._path_gddgen, f"{filename_in_noext}.all_crops_everywhere{ext}" + ) + + # Make fsurdat for this test, if not already done + if not os.path.exists(self._fsurdat_out): + tool_path = os.path.join( + self._ctsm_root, + "tools", + "modify_input_files", + "fsurdat_modifier", + ) + + # Create configuration file for fsurdat_modifier + self._cfg_path = os.path.join( + self._path_gddgen, + "modify_fsurdat_allcropseverywhere.cfg", + ) + self._create_config_file_evenlysplitcrop() + + command = f"python3 {tool_path} {self._cfg_path} " + stu.run_python_script( + self._get_caseroot(), + self._this_conda_env, + command, + tool_path, + ) + + # Modify namelist + logger.info("RXCROPMATURITY log: modify user_nl files: new fsurdat") + self._append_to_user_nl_clm( + [ + "fsurdat = '{}'".format(self._fsurdat_out), + "do_transient_crops = .false.", + "flanduse_timeseries = ''", + "use_init_interp = .true.", + ] + ) + + def _create_config_file_evenlysplitcrop(self): + """ + Open the new and the template .cfg files + Loop line by line through the template .cfg file + When string matches, replace that line's content + """ + cfg_template_path = os.path.join( + self._ctsm_root, "tools/modify_input_files/modify_fsurdat_template.cfg" + ) + + with open(self._cfg_path, "w", encoding="utf-8") as cfg_out: + # Copy template, replacing some lines + with open(cfg_template_path, "r", encoding="utf-8") as cfg_in: + for line in cfg_in: + if re.match(r" *evenly_split_cropland *=", line): + line = f"evenly_split_cropland = True" + elif re.match(r" *fsurdat_in *=", line): + line = f"fsurdat_in = {self._fsurdat_in}" + elif re.match(r" *fsurdat_out *=", line): + line = f"fsurdat_out = {self._fsurdat_out}" + elif re.match(r" *process_subgrid_section *=", line): + line = f"process_subgrid_section = True" + cfg_out.write(line) + + # Add new lines + cfg_out.write("\n") + cfg_out.write("[modify_fsurdat_subgrid_fractions]\n") + cfg_out.write("PCT_CROP = 100.0\n") + cfg_out.write("PCT_NATVEG = 0.0\n") + cfg_out.write("PCT_GLACIER = 0.0\n") + cfg_out.write("PCT_WETLAND = 0.0\n") + cfg_out.write("PCT_LAKE = 0.0\n") + cfg_out.write("PCT_OCEAN = 0.0\n") + cfg_out.write("PCT_URBAN = 0.0 0.0 0.0\n") + + def _run_check_rxboth_run(self): + + output_dir = os.path.join(self._get_caseroot(), "run") + first_usable_year = self._run_startyear + 2 + last_usable_year = self._run_startyear + self._run_Nyears - 2 + + tool_path = os.path.join( + self._ctsm_root, "python", "ctsm", "crop_calendars", "check_rxboth_run.py" + ) + command = ( + f"python3 {tool_path} " + + f"--directory {output_dir} " + + f"-y1 {first_usable_year} " + + f"-yN {last_usable_year} " + + f"--rx-sdates-file {self._sdatefile} " + + f"--rx-gdds-file {self._gdds_file} " + ) + stu.run_python_script( + self._get_caseroot(), + self._this_conda_env, + command, + tool_path, + ) + + def _modify_user_nl_allruns(self): + nl_additions = [ + "stream_meshfile_cropcal = '{}'".format(self._case.get_value("LND_DOMAIN_MESH")), + "stream_fldFileName_swindow_start = '{}'".format(self._sdatefile), + "stream_fldFileName_swindow_end = '{}'".format(self._sdatefile), + "stream_year_first_cropcal = 2000", + "stream_year_last_cropcal = 2000", + "model_year_align_cropcal = 2000", + " ", + "! (h1) Annual outputs on sowing or harvest axis", + "hist_fincl2 = 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV'", + "hist_nhtfrq(2) = 17520", + "hist_mfilt(2) = 999", + "hist_type1d_pertape(2) = 'PFTS'", + "hist_dov2xy(2) = .false.", + ] + self._append_to_user_nl_clm(nl_additions) + + def _run_generate_gdds(self, case_gddgen): + self._generate_gdds_dir = os.path.join(self._path_gddgen, "generate_gdds_out") + os.makedirs(self._generate_gdds_dir) + + # Get arguments to generate_gdds.py + dout_sr = case_gddgen.get_value("DOUT_S_ROOT") + input_dir = os.path.join(dout_sr, "lnd", "hist") + first_season = self._run_startyear + 2 + last_season = self._run_startyear + self._run_Nyears - 2 + sdates_file = self._sdatefile + hdates_file = self._hdatefile + + # It'd be much nicer to call generate_gdds.main(), but I can't import generate_gdds. + tool_path = os.path.join( + self._ctsm_root, "python", "ctsm", "crop_calendars", "generate_gdds.py" + ) + command = " ".join( + [ + f"python3 {tool_path}", + f"--input-dir {input_dir}", + f"--first-season {first_season}", + f"--last-season {last_season}", + f"--sdates-file {sdates_file}", + f"--hdates-file {hdates_file}", + f"--output-dir generate_gdds_out", + f"--skip-crops miscanthus,irrigated_miscanthus", + ] + ) + stu.run_python_script( + self._get_caseroot(), + self._this_conda_env, + command, + tool_path, + ) + + # Where were the prescribed maturity requirements saved? + generated_gdd_files = glob.glob(os.path.join(self._generate_gdds_dir, "gdds_*.nc")) + if len(generated_gdd_files) != 1: + error_message = f"ERROR: Expected one matching prescribed maturity requirements file; found {len(generated_gdd_files)}: {generated_gdd_files}" + logger.error(error_message) + raise RuntimeError(error_message) + self._gdds_file = generated_gdd_files[0] + + def _get_conda_env(self): + conda_setup_commands = stu.cmds_to_setup_conda(self._get_caseroot()) + + # If npl conda environment is available, use that (It has dask, which + # enables chunking, which makes reading daily 1-degree netCDF files + # much more efficient. + if "npl " in os.popen(conda_setup_commands + "conda env list").read(): + self._this_conda_env = "npl" + else: + self._this_conda_env = "ctsm_pylib" + + def _append_to_user_nl_clm(self, additions): + caseroot = self._get_caseroot() + append_to_user_nl_files(caseroot=caseroot, component="clm", contents=additions) + + # Is flanduse_timeseries defined? If so, where is it? + def _get_flanduse_timeseries_in(self, case): + case.create_namelists(component="lnd") + self._lnd_in_path = os.path.join(self._path_gddgen, "CaseDocs", "lnd_in") + self._flanduse_timeseries_in = None + with open(self._lnd_in_path, "r") as lnd_in: + for line in lnd_in: + flanduse_timeseries_in = re.match(r" *flanduse_timeseries *= *'(.*)'", line) + if flanduse_timeseries_in: + self._flanduse_timeseries_in = flanduse_timeseries_in.group(1) + break diff --git a/cime_config/SystemTests/rxcropmaturityskipbuild.py b/cime_config/SystemTests/rxcropmaturityskipbuild.py new file mode 100644 index 0000000000..0e16c757fc --- /dev/null +++ b/cime_config/SystemTests/rxcropmaturityskipbuild.py @@ -0,0 +1,9 @@ +from rxcropmaturityshared import RXCROPMATURITYSHARED + + +class RXCROPMATURITYSKIPBUILD(RXCROPMATURITYSHARED): + def build_indv(self, sharedlib_only=False, model_only=False): + self._case.set_value("BUILD_COMPLETE", "TRUE") + + def run_phase(self): + self._run_phase(skip_run=False) diff --git a/cime_config/SystemTests/rxcropmaturityskiprun.py b/cime_config/SystemTests/rxcropmaturityskiprun.py new file mode 100644 index 0000000000..24fc288373 --- /dev/null +++ b/cime_config/SystemTests/rxcropmaturityskiprun.py @@ -0,0 +1,9 @@ +from rxcropmaturityshared import RXCROPMATURITYSHARED + + +class RXCROPMATURITYSKIPRUN(RXCROPMATURITYSHARED): + def build_indv(self, sharedlib_only=False, model_only=False): + self._case.set_value("BUILD_COMPLETE", "TRUE") + + def run_phase(self): + self._run_phase(skip_run=True) diff --git a/cime_config/config_tests.xml b/cime_config/config_tests.xml index c0b6afed9d..115f4c0e89 100644 --- a/cime_config/config_tests.xml +++ b/cime_config/config_tests.xml @@ -133,6 +133,26 @@ This defines various CTSM-specific system tests $STOP_N + + As RXCROPMATURITY but don't actually run or postprocess; just setup + 1 + FALSE + FALSE + never + $STOP_OPTION + $STOP_N + + + + As RXCROPMATURITY but don't actually build; just setup and then start "run." Will fail when run start is requested, but should successfully test preprocessing. + 1 + FALSE + FALSE + never + $STOP_OPTION + $STOP_N + + - - - PEND - #2460 - - - FAIL From dd6e0cb0b8f722c74a6819b48ff4ec534fbc7a87 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 14 Jun 2024 15:50:47 -0600 Subject: [PATCH 070/160] Move RXCROPMATURITYSHARED into rxcropmaturity.py. --- cime_config/SystemTests/rxcropmaturity.py | 511 ++++++++++++++++- .../SystemTests/rxcropmaturityshared.py | 514 ------------------ .../SystemTests/rxcropmaturityskipgen.py | 2 +- 3 files changed, 511 insertions(+), 516 deletions(-) delete mode 100644 cime_config/SystemTests/rxcropmaturityshared.py diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index 12ec608898..e0f6f4df21 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -1,4 +1,513 @@ -from rxcropmaturityshared import RXCROPMATURITYSHARED +""" +CTSM-specific test that first performs a GDD-generating run, then calls +Python code to generate the maturity requirement file. This is then used +in a sowing+maturity forced run, which finally is tested to ensure +correct behavior. + +Currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions. Eventually, +this test should be able to generate its own files at whatever resolution it's +called at. Well, really, the ultimate goal would be to give CLM the files +at the original resolution (for GGCMI phase 3, 0.5°) and have the stream +code do the interpolation. However, that wouldn't act on harvest dates +(which are needed for generate_gdds.py). I could have Python interpolate +those, but this would cause a potential inconsistency. +""" + +import os +import re +import systemtest_utils as stu +import subprocess +from CIME.SystemTests.system_tests_common import SystemTestsCommon +from CIME.XML.standard_module_setup import * +from CIME.SystemTests.test_utils.user_nl_utils import append_to_user_nl_files +from CIME.case import Case +import shutil, glob + +logger = logging.getLogger(__name__) + + +class RXCROPMATURITYSHARED(SystemTestsCommon): + def __init__(self, case): + # initialize an object interface to the SMS system test + SystemTestsCommon.__init__(self, case) + + # Is this a real RXCROPMATURITY test or not? + casebaseid = self._case.get_value("CASEBASEID") + full_test = "RXCROPMATURITY_" in casebaseid + skipgen_test = "RXCROPMATURITYSKIPGEN_" in casebaseid + + # Ensure run length is at least 5 years. Minimum to produce one complete growing season (i.e., two complete calendar years) actually 4 years, but that only gets you 1 season usable for GDD generation, so you can't check for season-to-season consistency. + stop_n = self._case.get_value("STOP_N") + stop_option = self._case.get_value("STOP_OPTION") + stop_n_orig = stop_n + stop_option_orig = stop_option + if "nsecond" in stop_option: + stop_n /= 60 + stop_option = "nminutes" + if "nminute" in stop_option: + stop_n /= 60 + stop_option = "nhours" + if "nhour" in stop_option: + stop_n /= 24 + stop_option = "ndays" + if "nday" in stop_option: + stop_n /= 365 + stop_option = "nyears" + if "nmonth" in stop_option: + stop_n /= 12 + stop_option = "nyears" + error_message = None + if "nyear" not in stop_option: + error_message = ( + f"STOP_OPTION ({stop_option_orig}) must be nsecond(s), nminute(s), " + + "nhour(s), nday(s), nmonth(s), or nyear(s)" + ) + elif full_test and stop_n < 5: + error_message = ( + "RXCROPMATURITY must be run for at least 5 years; you requested " + + f"{stop_n_orig} {stop_option_orig[1:]}" + ) + elif skipgen_test and stop_n < 3: + # First year is discarded because crops are already in the ground at restart, and those aren't affected by the new crop calendar inputs. The second year is useable, but we need a third year so that all crops planted in the second year have a chance to finish. + error_message = ( + "RXCROPMATURITYSKIPGEN (both-forced part) must be run for at least 3 years; you requested " + + f"{stop_n_orig} {stop_option_orig[1:]}" + ) + if error_message is not None: + logger.error(error_message) + raise RuntimeError(error_message) + + # Get the number of complete years that will be run + self._run_Nyears = int(stop_n) + + # Only allow RXCROPMATURITY to be called with test cropMonthOutput + if casebaseid.split("-")[-1] != "cropMonthOutput": + error_message = ( + "Only call RXCROPMATURITY with test cropMonthOutput " + + "to avoid potentially huge sets of daily outputs." + ) + logger.error(error_message) + raise RuntimeError(error_message) + + # Get files with prescribed sowing and harvest dates + self._get_rx_dates() + + # Get cultivar maturity requirement file to fall back on if not generating it here + self._gdds_file = None + self._fallback_gdds_file = os.path.join( + os.path.dirname(self._sdatefile), + "gdds_20230829_161011.nc" + ) + + # Which conda environment should we use? + self._get_conda_env() + + def _run_phase(self, skip_run=False, skip_gen=False): + # Modeling this after the SSP test, we create a clone to be the case whose outputs we don't + # want to be saved as baseline. + + # ------------------------------------------------------------------- + # (1) Set up GDD-generating run + # ------------------------------------------------------------------- + # Create clone to be GDD-Generating case + logger.info("RXCROPMATURITY log: cloning setup") + case_rxboth = self._case + caseroot = self._case.get_value("CASEROOT") + clone_path = f"{caseroot}.gddgen" + self._path_gddgen = clone_path + if os.path.exists(self._path_gddgen): + shutil.rmtree(self._path_gddgen) + logger.info("RXCROPMATURITY log: cloning") + case_gddgen = self._case.create_clone(clone_path, keepexe=True) + logger.info("RXCROPMATURITY log: done cloning") + + os.chdir(self._path_gddgen) + self._set_active_case(case_gddgen) + + # Set up stuff that applies to both tests + self._setup_all() + + # Add stuff specific to GDD-Generating run + logger.info("RXCROPMATURITY log: modify user_nl files: generate GDDs") + self._append_to_user_nl_clm( + [ + "generate_crop_gdds = .true.", + "use_mxmat = .false.", + " ", + "! (h2) Daily outputs for GDD generation and figure-making", + "hist_fincl3 = 'GDDACCUM', 'GDDHARV'", + "hist_nhtfrq(3) = -24", + "hist_mfilt(3) = 365", + "hist_type1d_pertape(3) = 'PFTS'", + "hist_dov2xy(3) = .false.", + ] + ) + + # If flanduse_timeseries is defined, we need to make a static version for this test. This + # should have every crop in most of the world. + self._get_flanduse_timeseries_in(case_gddgen) + if self._flanduse_timeseries_in is not None: + + # Download files from the server, if needed + case_gddgen.check_all_input_data() + + # Copy needed file from original to gddgen directory + shutil.copyfile( + os.path.join(caseroot, ".env_mach_specific.sh"), + os.path.join(self._path_gddgen, ".env_mach_specific.sh"), + ) + + # Make custom version of surface file + logger.info("RXCROPMATURITY log: run fsurdat_modifier") + self._run_fsurdat_modifier() + + # ------------------------------------------------------------------- + # (2) Perform GDD-generating run and generate prescribed GDDs file + # ------------------------------------------------------------------- + logger.info("RXCROPMATURITY log: Start GDD-Generating run") + + # As per SSP test: + # "No history files expected, set suffix=None to avoid compare error" + # We *do* expect history files here, but anyway. This works. + self._skip_pnl = False + + # If not generating GDDs, only run a few days of this. + if skip_gen: + with Case(self._path_gddgen, read_only=False) as case: + case.set_value("STOP_N", 5) + case.set_value("STOP_OPTION", "ndays") + + if not skip_run: + self.run_indv(suffix=None, st_archive=True) + if skip_gen: + # Interpolate an existing GDD file. Needed to check obedience to GDD inputs. + self._run_interpolate_gdds() + else: + self._run_generate_gdds(case_gddgen) + + # ------------------------------------------------------------------- + # (3) Set up and perform Prescribed Calendars run + # ------------------------------------------------------------------- + os.chdir(caseroot) + self._set_active_case(case_rxboth) + + # Set up stuff that applies to both tests + self._setup_all() + + # Add stuff specific to Prescribed Calendars run + logger.info("RXCROPMATURITY log: modify user_nl files: Prescribed Calendars") + self._append_to_user_nl_clm( + [ + "generate_crop_gdds = .false.", + f"stream_fldFileName_cultivar_gdds = '{self._gdds_file}'", + ] + ) + + if not skip_run: + self.run_indv() + + # ------------------------------------------------------------------- + # (4) Check Prescribed Calendars run + # ------------------------------------------------------------------- + logger.info("RXCROPMATURITY log: output check: Prescribed Calendars") + if not skip_run: + self._run_check_rxboth_run(skip_gen) + + # Get sowing and harvest dates for this resolution. + def _get_rx_dates(self): + # Eventually, I want to remove these hard-coded resolutions so that this test can generate + # its own sowing and harvest date files at whatever resolution is requested. + lnd_grid = self._case.get_value("LND_GRID") + input_data_root = self._case.get_value("DIN_LOC_ROOT") + processed_crop_dates_dir = f"{input_data_root}/lnd/clm2/cropdata/calendars/processed" + if lnd_grid == "10x15": + self._sdatefile = os.path.join( + processed_crop_dates_dir, + "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", + ) + self._hdatefile = os.path.join( + processed_crop_dates_dir, + "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", + ) + elif lnd_grid == "1.9x2.5": + self._sdatefile = os.path.join( + processed_crop_dates_dir, + "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", + ) + self._hdatefile = os.path.join( + processed_crop_dates_dir, + "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", + ) + elif lnd_grid == "0.9x1.25": + self._sdatefile = os.path.join( + processed_crop_dates_dir, + "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134417.nc", + ) + self._hdatefile = os.path.join( + processed_crop_dates_dir, + "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134418.nc", + ) + else: + error_message = "ERROR: RXCROPMATURITY currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions" + logger.error(error_message) + raise RuntimeError(error_message) + + # Ensure files exist + error_message = None + if not os.path.exists(self._sdatefile): + error_message = f"ERROR: Sowing date file not found: {self._sdatefile}" + elif not os.path.exists(self._hdatefile): + error_message = f"ERROR: Harvest date file not found: {self._sdatefile}" + if error_message is not None: + logger.error(error_message) + raise RuntimeError(error_message) + + def _setup_all(self): + logger.info("RXCROPMATURITY log: _setup_all start") + + # Get some info + self._ctsm_root = self._case.get_value("COMP_ROOT_DIR_LND") + run_startdate = self._case.get_value("RUN_STARTDATE") + self._run_startyear = int(run_startdate.split("-")[0]) + + # Set sowing dates file (and other crop calendar settings) for all runs + logger.info("RXCROPMATURITY log: modify user_nl files: all tests") + self._modify_user_nl_allruns() + logger.info("RXCROPMATURITY log: _setup_all done") + + # Make a surface dataset that has every crop in every gridcell + def _run_fsurdat_modifier(self): + + # fsurdat should be defined. Where is it? + self._fsurdat_in = None + with open(self._lnd_in_path, "r") as lnd_in: + for line in lnd_in: + fsurdat_in = re.match(r" *fsurdat *= *'(.*)'", line) + if fsurdat_in: + self._fsurdat_in = fsurdat_in.group(1) + break + if self._fsurdat_in is None: + error_message = "fsurdat not defined" + logger.error(error_message) + raise RuntimeError(error_message) + + # Where we will save the fsurdat version for this test + path, ext = os.path.splitext(self._fsurdat_in) + dir_in, filename_in_noext = os.path.split(path) + self._fsurdat_out = os.path.join( + self._path_gddgen, f"{filename_in_noext}.all_crops_everywhere{ext}" + ) + + # Make fsurdat for this test, if not already done + if not os.path.exists(self._fsurdat_out): + tool_path = os.path.join( + self._ctsm_root, + "tools", + "modify_input_files", + "fsurdat_modifier", + ) + + # Create configuration file for fsurdat_modifier + self._cfg_path = os.path.join( + self._path_gddgen, + "modify_fsurdat_allcropseverywhere.cfg", + ) + self._create_config_file_evenlysplitcrop() + + command = f"python3 {tool_path} {self._cfg_path} " + stu.run_python_script( + self._get_caseroot(), + self._this_conda_env, + command, + tool_path, + ) + + # Modify namelist + logger.info("RXCROPMATURITY log: modify user_nl files: new fsurdat") + self._append_to_user_nl_clm( + [ + "fsurdat = '{}'".format(self._fsurdat_out), + "do_transient_crops = .false.", + "flanduse_timeseries = ''", + "use_init_interp = .true.", + ] + ) + + def _create_config_file_evenlysplitcrop(self): + """ + Open the new and the template .cfg files + Loop line by line through the template .cfg file + When string matches, replace that line's content + """ + cfg_template_path = os.path.join( + self._ctsm_root, "tools/modify_input_files/modify_fsurdat_template.cfg" + ) + + with open(self._cfg_path, "w", encoding="utf-8") as cfg_out: + # Copy template, replacing some lines + with open(cfg_template_path, "r", encoding="utf-8") as cfg_in: + for line in cfg_in: + if re.match(r" *evenly_split_cropland *=", line): + line = f"evenly_split_cropland = True" + elif re.match(r" *fsurdat_in *=", line): + line = f"fsurdat_in = {self._fsurdat_in}" + elif re.match(r" *fsurdat_out *=", line): + line = f"fsurdat_out = {self._fsurdat_out}" + elif re.match(r" *process_subgrid_section *=", line): + line = f"process_subgrid_section = True" + cfg_out.write(line) + + # Add new lines + cfg_out.write("\n") + cfg_out.write("[modify_fsurdat_subgrid_fractions]\n") + cfg_out.write("PCT_CROP = 100.0\n") + cfg_out.write("PCT_NATVEG = 0.0\n") + cfg_out.write("PCT_GLACIER = 0.0\n") + cfg_out.write("PCT_WETLAND = 0.0\n") + cfg_out.write("PCT_LAKE = 0.0\n") + cfg_out.write("PCT_OCEAN = 0.0\n") + cfg_out.write("PCT_URBAN = 0.0 0.0 0.0\n") + + def _run_check_rxboth_run(self, skip_gen): + + output_dir = os.path.join(self._get_caseroot(), "run") + + if skip_gen: + first_usable_year = self._run_startyear + 1 + last_usable_year = first_usable_year + else: + first_usable_year = self._run_startyear + 2 + last_usable_year = self._run_startyear + self._run_Nyears - 2 + + tool_path = os.path.join( + self._ctsm_root, "python", "ctsm", "crop_calendars", "check_rxboth_run.py" + ) + command = ( + f"python3 {tool_path} " + + f"--directory {output_dir} " + + f"-y1 {first_usable_year} " + + f"-yN {last_usable_year} " + + f"--rx-sdates-file {self._sdatefile} " + + f"--rx-gdds-file {self._gdds_file} " + ) + stu.run_python_script( + self._get_caseroot(), + self._this_conda_env, + command, + tool_path, + ) + + def _modify_user_nl_allruns(self): + nl_additions = [ + "stream_meshfile_cropcal = '{}'".format(self._case.get_value("LND_DOMAIN_MESH")), + "stream_fldFileName_swindow_start = '{}'".format(self._sdatefile), + "stream_fldFileName_swindow_end = '{}'".format(self._sdatefile), + "stream_year_first_cropcal = 2000", + "stream_year_last_cropcal = 2000", + "model_year_align_cropcal = 2000", + " ", + "! (h1) Annual outputs on sowing or harvest axis", + "hist_fincl2 = 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV'", + "hist_nhtfrq(2) = 17520", + "hist_mfilt(2) = 999", + "hist_type1d_pertape(2) = 'PFTS'", + "hist_dov2xy(2) = .false.", + ] + self._append_to_user_nl_clm(nl_additions) + + def _run_generate_gdds(self, case_gddgen): + self._generate_gdds_dir = os.path.join(self._path_gddgen, "generate_gdds_out") + os.makedirs(self._generate_gdds_dir) + + # Get arguments to generate_gdds.py + dout_sr = case_gddgen.get_value("DOUT_S_ROOT") + input_dir = os.path.join(dout_sr, "lnd", "hist") + first_season = self._run_startyear + 2 + last_season = self._run_startyear + self._run_Nyears - 2 + sdates_file = self._sdatefile + hdates_file = self._hdatefile + + # It'd be much nicer to call generate_gdds.main(), but I can't import generate_gdds. + tool_path = os.path.join( + self._ctsm_root, "python", "ctsm", "crop_calendars", "generate_gdds.py" + ) + command = " ".join( + [ + f"python3 {tool_path}", + f"--input-dir {input_dir}", + f"--first-season {first_season}", + f"--last-season {last_season}", + f"--sdates-file {sdates_file}", + f"--hdates-file {hdates_file}", + f"--output-dir generate_gdds_out", + f"--skip-crops miscanthus,irrigated_miscanthus", + ] + ) + stu.run_python_script( + self._get_caseroot(), + self._this_conda_env, + command, + tool_path, + ) + + # Where were the prescribed maturity requirements saved? + generated_gdd_files = glob.glob(os.path.join(self._generate_gdds_dir, "gdds_*.nc")) + if len(generated_gdd_files) != 1: + error_message = f"ERROR: Expected one matching prescribed maturity requirements file; found {len(generated_gdd_files)}: {generated_gdd_files}" + logger.error(error_message) + raise RuntimeError(error_message) + self._gdds_file = generated_gdd_files[0] + + def _run_interpolate_gdds(self): + # Save where? + self._gdds_file = os.path.join(self._get_caseroot(), "interpolated_gdds.nc") + + # It'd be much nicer to call interpolate_gdds.main(), but I can't import interpolate_gdds. + tool_path = os.path.join( + self._ctsm_root, "python", "ctsm", "crop_calendars", "interpolate_gdds.py" + ) + command = " ".join( + [ + f"python3 {tool_path}", + f"--input-file {self._fallback_gdds_file}", + f"--target-file {self._sdatefile}", + f"--output-file {self._gdds_file}", + "--overwrite", + ] + ) + stu.run_python_script( + self._get_caseroot(), + self._this_conda_env, + command, + tool_path, + ) + + def _get_conda_env(self): + conda_setup_commands = stu.cmds_to_setup_conda(self._get_caseroot()) + + # If npl conda environment is available, use that (It has dask, which + # enables chunking, which makes reading daily 1-degree netCDF files + # much more efficient. + if "npl " in os.popen(conda_setup_commands + "conda env list").read(): + self._this_conda_env = "npl" + else: + self._this_conda_env = "ctsm_pylib" + + def _append_to_user_nl_clm(self, additions): + caseroot = self._get_caseroot() + append_to_user_nl_files(caseroot=caseroot, component="clm", contents=additions) + + # Is flanduse_timeseries defined? If so, where is it? + def _get_flanduse_timeseries_in(self, case): + case.create_namelists(component="lnd") + self._lnd_in_path = os.path.join(self._path_gddgen, "CaseDocs", "lnd_in") + self._flanduse_timeseries_in = None + with open(self._lnd_in_path, "r") as lnd_in: + for line in lnd_in: + flanduse_timeseries_in = re.match(r" *flanduse_timeseries *= *'(.*)'", line) + if flanduse_timeseries_in: + self._flanduse_timeseries_in = flanduse_timeseries_in.group(1) + break class RXCROPMATURITY(RXCROPMATURITYSHARED): diff --git a/cime_config/SystemTests/rxcropmaturityshared.py b/cime_config/SystemTests/rxcropmaturityshared.py deleted file mode 100644 index 991fe687b5..0000000000 --- a/cime_config/SystemTests/rxcropmaturityshared.py +++ /dev/null @@ -1,514 +0,0 @@ -""" -CTSM-specific test that first performs a GDD-generating run, then calls -Python code to generate the maturity requirement file. This is then used -in a sowing+maturity forced run, which finally is tested to ensure -correct behavior. - -Currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions. Eventually, -this test should be able to generate its own files at whatever resolution it's -called at. Well, really, the ultimate goal would be to give CLM the files -at the original resolution (for GGCMI phase 3, 0.5°) and have the stream -code do the interpolation. However, that wouldn't act on harvest dates -(which are needed for generate_gdds.py). I could have Python interpolate -those, but this would cause a potential inconsistency. - -Note that this is just a parent class. The actual tests are RXCROPMATURITY and -RXCROPMATURITY_SKIPRUN, the latter of which does everything except perform and -check the CTSM runs. -""" - -import os -import re -import systemtest_utils as stu -import subprocess -from CIME.SystemTests.system_tests_common import SystemTestsCommon -from CIME.XML.standard_module_setup import * -from CIME.SystemTests.test_utils.user_nl_utils import append_to_user_nl_files -from CIME.case import Case -import shutil, glob - -logger = logging.getLogger(__name__) - - -class RXCROPMATURITYSHARED(SystemTestsCommon): - def __init__(self, case): - # initialize an object interface to the SMS system test - SystemTestsCommon.__init__(self, case) - - # Is this a real RXCROPMATURITY test or not? - casebaseid = self._case.get_value("CASEBASEID") - full_test = "RXCROPMATURITY_" in casebaseid - skipgen_test = "RXCROPMATURITYSKIPGEN_" in casebaseid - - # Ensure run length is at least 5 years. Minimum to produce one complete growing season (i.e., two complete calendar years) actually 4 years, but that only gets you 1 season usable for GDD generation, so you can't check for season-to-season consistency. - stop_n = self._case.get_value("STOP_N") - stop_option = self._case.get_value("STOP_OPTION") - stop_n_orig = stop_n - stop_option_orig = stop_option - if "nsecond" in stop_option: - stop_n /= 60 - stop_option = "nminutes" - if "nminute" in stop_option: - stop_n /= 60 - stop_option = "nhours" - if "nhour" in stop_option: - stop_n /= 24 - stop_option = "ndays" - if "nday" in stop_option: - stop_n /= 365 - stop_option = "nyears" - if "nmonth" in stop_option: - stop_n /= 12 - stop_option = "nyears" - error_message = None - if "nyear" not in stop_option: - error_message = ( - f"STOP_OPTION ({stop_option_orig}) must be nsecond(s), nminute(s), " - + "nhour(s), nday(s), nmonth(s), or nyear(s)" - ) - elif full_test and stop_n < 5: - error_message = ( - "RXCROPMATURITY must be run for at least 5 years; you requested " - + f"{stop_n_orig} {stop_option_orig[1:]}" - ) - elif skipgen_test and stop_n < 3: - # First year is discarded because crops are already in the ground at restart, and those aren't affected by the new crop calendar inputs. The second year is useable, but we need a third year so that all crops planted in the second year have a chance to finish. - error_message = ( - "RXCROPMATURITYSKIPGEN (both-forced part) must be run for at least 3 years; you requested " - + f"{stop_n_orig} {stop_option_orig[1:]}" - ) - if error_message is not None: - logger.error(error_message) - raise RuntimeError(error_message) - - # Get the number of complete years that will be run - self._run_Nyears = int(stop_n) - - # Only allow RXCROPMATURITY to be called with test cropMonthOutput - if casebaseid.split("-")[-1] != "cropMonthOutput": - error_message = ( - "Only call RXCROPMATURITY with test cropMonthOutput " - + "to avoid potentially huge sets of daily outputs." - ) - logger.error(error_message) - raise RuntimeError(error_message) - - # Get files with prescribed sowing and harvest dates - self._get_rx_dates() - - # Get cultivar maturity requirement file to fall back on if not generating it here - self._gdds_file = None - self._fallback_gdds_file = os.path.join( - os.path.dirname(self._sdatefile), - "gdds_20230829_161011.nc" - ) - - # Which conda environment should we use? - self._get_conda_env() - - def _run_phase(self, skip_run=False, skip_gen=False): - # Modeling this after the SSP test, we create a clone to be the case whose outputs we don't - # want to be saved as baseline. - - # ------------------------------------------------------------------- - # (1) Set up GDD-generating run - # ------------------------------------------------------------------- - # Create clone to be GDD-Generating case - logger.info("RXCROPMATURITY log: cloning setup") - case_rxboth = self._case - caseroot = self._case.get_value("CASEROOT") - clone_path = f"{caseroot}.gddgen" - self._path_gddgen = clone_path - if os.path.exists(self._path_gddgen): - shutil.rmtree(self._path_gddgen) - logger.info("RXCROPMATURITY log: cloning") - case_gddgen = self._case.create_clone(clone_path, keepexe=True) - logger.info("RXCROPMATURITY log: done cloning") - - os.chdir(self._path_gddgen) - self._set_active_case(case_gddgen) - - # Set up stuff that applies to both tests - self._setup_all() - - # Add stuff specific to GDD-Generating run - logger.info("RXCROPMATURITY log: modify user_nl files: generate GDDs") - self._append_to_user_nl_clm( - [ - "generate_crop_gdds = .true.", - "use_mxmat = .false.", - " ", - "! (h2) Daily outputs for GDD generation and figure-making", - "hist_fincl3 = 'GDDACCUM', 'GDDHARV'", - "hist_nhtfrq(3) = -24", - "hist_mfilt(3) = 365", - "hist_type1d_pertape(3) = 'PFTS'", - "hist_dov2xy(3) = .false.", - ] - ) - - # If flanduse_timeseries is defined, we need to make a static version for this test. This - # should have every crop in most of the world. - self._get_flanduse_timeseries_in(case_gddgen) - if self._flanduse_timeseries_in is not None: - - # Download files from the server, if needed - case_gddgen.check_all_input_data() - - # Copy needed file from original to gddgen directory - shutil.copyfile( - os.path.join(caseroot, ".env_mach_specific.sh"), - os.path.join(self._path_gddgen, ".env_mach_specific.sh"), - ) - - # Make custom version of surface file - logger.info("RXCROPMATURITY log: run fsurdat_modifier") - self._run_fsurdat_modifier() - - # ------------------------------------------------------------------- - # (2) Perform GDD-generating run and generate prescribed GDDs file - # ------------------------------------------------------------------- - logger.info("RXCROPMATURITY log: Start GDD-Generating run") - - # As per SSP test: - # "No history files expected, set suffix=None to avoid compare error" - # We *do* expect history files here, but anyway. This works. - self._skip_pnl = False - - # If not generating GDDs, only run a few days of this. - if skip_gen: - with Case(self._path_gddgen, read_only=False) as case: - case.set_value("STOP_N", 5) - case.set_value("STOP_OPTION", "ndays") - - if not skip_run: - self.run_indv(suffix=None, st_archive=True) - if skip_gen: - # Interpolate an existing GDD file. Needed to check obedience to GDD inputs. - self._run_interpolate_gdds() - else: - self._run_generate_gdds(case_gddgen) - - # ------------------------------------------------------------------- - # (3) Set up and perform Prescribed Calendars run - # ------------------------------------------------------------------- - os.chdir(caseroot) - self._set_active_case(case_rxboth) - - # Set up stuff that applies to both tests - self._setup_all() - - # Add stuff specific to Prescribed Calendars run - logger.info("RXCROPMATURITY log: modify user_nl files: Prescribed Calendars") - self._append_to_user_nl_clm( - [ - "generate_crop_gdds = .false.", - f"stream_fldFileName_cultivar_gdds = '{self._gdds_file}'", - ] - ) - - if not skip_run: - self.run_indv() - - # ------------------------------------------------------------------- - # (4) Check Prescribed Calendars run - # ------------------------------------------------------------------- - logger.info("RXCROPMATURITY log: output check: Prescribed Calendars") - if not skip_run: - self._run_check_rxboth_run(skip_gen) - - # Get sowing and harvest dates for this resolution. - def _get_rx_dates(self): - # Eventually, I want to remove these hard-coded resolutions so that this test can generate - # its own sowing and harvest date files at whatever resolution is requested. - lnd_grid = self._case.get_value("LND_GRID") - input_data_root = self._case.get_value("DIN_LOC_ROOT") - processed_crop_dates_dir = f"{input_data_root}/lnd/clm2/cropdata/calendars/processed" - if lnd_grid == "10x15": - self._sdatefile = os.path.join( - processed_crop_dates_dir, - "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", - ) - self._hdatefile = os.path.join( - processed_crop_dates_dir, - "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc", - ) - elif lnd_grid == "1.9x2.5": - self._sdatefile = os.path.join( - processed_crop_dates_dir, - "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", - ) - self._hdatefile = os.path.join( - processed_crop_dates_dir, - "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f19_g17.2000-2000.20230102_175625.nc", - ) - elif lnd_grid == "0.9x1.25": - self._sdatefile = os.path.join( - processed_crop_dates_dir, - "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134417.nc", - ) - self._hdatefile = os.path.join( - processed_crop_dates_dir, - "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f09_g17.2000-2000.20230520_134418.nc", - ) - else: - error_message = "ERROR: RXCROPMATURITY currently only supports 0.9x1.25, 1.9x2.5, and 10x15 resolutions" - logger.error(error_message) - raise RuntimeError(error_message) - - # Ensure files exist - error_message = None - if not os.path.exists(self._sdatefile): - error_message = f"ERROR: Sowing date file not found: {self._sdatefile}" - elif not os.path.exists(self._hdatefile): - error_message = f"ERROR: Harvest date file not found: {self._sdatefile}" - if error_message is not None: - logger.error(error_message) - raise RuntimeError(error_message) - - def _setup_all(self): - logger.info("RXCROPMATURITY log: _setup_all start") - - # Get some info - self._ctsm_root = self._case.get_value("COMP_ROOT_DIR_LND") - run_startdate = self._case.get_value("RUN_STARTDATE") - self._run_startyear = int(run_startdate.split("-")[0]) - - # Set sowing dates file (and other crop calendar settings) for all runs - logger.info("RXCROPMATURITY log: modify user_nl files: all tests") - self._modify_user_nl_allruns() - logger.info("RXCROPMATURITY log: _setup_all done") - - # Make a surface dataset that has every crop in every gridcell - def _run_fsurdat_modifier(self): - - # fsurdat should be defined. Where is it? - self._fsurdat_in = None - with open(self._lnd_in_path, "r") as lnd_in: - for line in lnd_in: - fsurdat_in = re.match(r" *fsurdat *= *'(.*)'", line) - if fsurdat_in: - self._fsurdat_in = fsurdat_in.group(1) - break - if self._fsurdat_in is None: - error_message = "fsurdat not defined" - logger.error(error_message) - raise RuntimeError(error_message) - - # Where we will save the fsurdat version for this test - path, ext = os.path.splitext(self._fsurdat_in) - dir_in, filename_in_noext = os.path.split(path) - self._fsurdat_out = os.path.join( - self._path_gddgen, f"{filename_in_noext}.all_crops_everywhere{ext}" - ) - - # Make fsurdat for this test, if not already done - if not os.path.exists(self._fsurdat_out): - tool_path = os.path.join( - self._ctsm_root, - "tools", - "modify_input_files", - "fsurdat_modifier", - ) - - # Create configuration file for fsurdat_modifier - self._cfg_path = os.path.join( - self._path_gddgen, - "modify_fsurdat_allcropseverywhere.cfg", - ) - self._create_config_file_evenlysplitcrop() - - command = f"python3 {tool_path} {self._cfg_path} " - stu.run_python_script( - self._get_caseroot(), - self._this_conda_env, - command, - tool_path, - ) - - # Modify namelist - logger.info("RXCROPMATURITY log: modify user_nl files: new fsurdat") - self._append_to_user_nl_clm( - [ - "fsurdat = '{}'".format(self._fsurdat_out), - "do_transient_crops = .false.", - "flanduse_timeseries = ''", - "use_init_interp = .true.", - ] - ) - - def _create_config_file_evenlysplitcrop(self): - """ - Open the new and the template .cfg files - Loop line by line through the template .cfg file - When string matches, replace that line's content - """ - cfg_template_path = os.path.join( - self._ctsm_root, "tools/modify_input_files/modify_fsurdat_template.cfg" - ) - - with open(self._cfg_path, "w", encoding="utf-8") as cfg_out: - # Copy template, replacing some lines - with open(cfg_template_path, "r", encoding="utf-8") as cfg_in: - for line in cfg_in: - if re.match(r" *evenly_split_cropland *=", line): - line = f"evenly_split_cropland = True" - elif re.match(r" *fsurdat_in *=", line): - line = f"fsurdat_in = {self._fsurdat_in}" - elif re.match(r" *fsurdat_out *=", line): - line = f"fsurdat_out = {self._fsurdat_out}" - elif re.match(r" *process_subgrid_section *=", line): - line = f"process_subgrid_section = True" - cfg_out.write(line) - - # Add new lines - cfg_out.write("\n") - cfg_out.write("[modify_fsurdat_subgrid_fractions]\n") - cfg_out.write("PCT_CROP = 100.0\n") - cfg_out.write("PCT_NATVEG = 0.0\n") - cfg_out.write("PCT_GLACIER = 0.0\n") - cfg_out.write("PCT_WETLAND = 0.0\n") - cfg_out.write("PCT_LAKE = 0.0\n") - cfg_out.write("PCT_OCEAN = 0.0\n") - cfg_out.write("PCT_URBAN = 0.0 0.0 0.0\n") - - def _run_check_rxboth_run(self, skip_gen): - - output_dir = os.path.join(self._get_caseroot(), "run") - - if skip_gen: - first_usable_year = self._run_startyear + 1 - last_usable_year = first_usable_year - else: - first_usable_year = self._run_startyear + 2 - last_usable_year = self._run_startyear + self._run_Nyears - 2 - - tool_path = os.path.join( - self._ctsm_root, "python", "ctsm", "crop_calendars", "check_rxboth_run.py" - ) - command = ( - f"python3 {tool_path} " - + f"--directory {output_dir} " - + f"-y1 {first_usable_year} " - + f"-yN {last_usable_year} " - + f"--rx-sdates-file {self._sdatefile} " - + f"--rx-gdds-file {self._gdds_file} " - ) - stu.run_python_script( - self._get_caseroot(), - self._this_conda_env, - command, - tool_path, - ) - - def _modify_user_nl_allruns(self): - nl_additions = [ - "stream_meshfile_cropcal = '{}'".format(self._case.get_value("LND_DOMAIN_MESH")), - "stream_fldFileName_swindow_start = '{}'".format(self._sdatefile), - "stream_fldFileName_swindow_end = '{}'".format(self._sdatefile), - "stream_year_first_cropcal = 2000", - "stream_year_last_cropcal = 2000", - "model_year_align_cropcal = 2000", - " ", - "! (h1) Annual outputs on sowing or harvest axis", - "hist_fincl2 = 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV'", - "hist_nhtfrq(2) = 17520", - "hist_mfilt(2) = 999", - "hist_type1d_pertape(2) = 'PFTS'", - "hist_dov2xy(2) = .false.", - ] - self._append_to_user_nl_clm(nl_additions) - - def _run_generate_gdds(self, case_gddgen): - self._generate_gdds_dir = os.path.join(self._path_gddgen, "generate_gdds_out") - os.makedirs(self._generate_gdds_dir) - - # Get arguments to generate_gdds.py - dout_sr = case_gddgen.get_value("DOUT_S_ROOT") - input_dir = os.path.join(dout_sr, "lnd", "hist") - first_season = self._run_startyear + 2 - last_season = self._run_startyear + self._run_Nyears - 2 - sdates_file = self._sdatefile - hdates_file = self._hdatefile - - # It'd be much nicer to call generate_gdds.main(), but I can't import generate_gdds. - tool_path = os.path.join( - self._ctsm_root, "python", "ctsm", "crop_calendars", "generate_gdds.py" - ) - command = " ".join( - [ - f"python3 {tool_path}", - f"--input-dir {input_dir}", - f"--first-season {first_season}", - f"--last-season {last_season}", - f"--sdates-file {sdates_file}", - f"--hdates-file {hdates_file}", - f"--output-dir generate_gdds_out", - f"--skip-crops miscanthus,irrigated_miscanthus", - ] - ) - stu.run_python_script( - self._get_caseroot(), - self._this_conda_env, - command, - tool_path, - ) - - # Where were the prescribed maturity requirements saved? - generated_gdd_files = glob.glob(os.path.join(self._generate_gdds_dir, "gdds_*.nc")) - if len(generated_gdd_files) != 1: - error_message = f"ERROR: Expected one matching prescribed maturity requirements file; found {len(generated_gdd_files)}: {generated_gdd_files}" - logger.error(error_message) - raise RuntimeError(error_message) - self._gdds_file = generated_gdd_files[0] - - def _run_interpolate_gdds(self): - # Save where? - self._gdds_file = os.path.join(self._get_caseroot(), "interpolated_gdds.nc") - - # It'd be much nicer to call interpolate_gdds.main(), but I can't import interpolate_gdds. - tool_path = os.path.join( - self._ctsm_root, "python", "ctsm", "crop_calendars", "interpolate_gdds.py" - ) - command = " ".join( - [ - f"python3 {tool_path}", - f"--input-file {self._fallback_gdds_file}", - f"--target-file {self._sdatefile}", - f"--output-file {self._gdds_file}", - "--overwrite", - ] - ) - stu.run_python_script( - self._get_caseroot(), - self._this_conda_env, - command, - tool_path, - ) - - def _get_conda_env(self): - conda_setup_commands = stu.cmds_to_setup_conda(self._get_caseroot()) - - # If npl conda environment is available, use that (It has dask, which - # enables chunking, which makes reading daily 1-degree netCDF files - # much more efficient. - if "npl " in os.popen(conda_setup_commands + "conda env list").read(): - self._this_conda_env = "npl" - else: - self._this_conda_env = "ctsm_pylib" - - def _append_to_user_nl_clm(self, additions): - caseroot = self._get_caseroot() - append_to_user_nl_files(caseroot=caseroot, component="clm", contents=additions) - - # Is flanduse_timeseries defined? If so, where is it? - def _get_flanduse_timeseries_in(self, case): - case.create_namelists(component="lnd") - self._lnd_in_path = os.path.join(self._path_gddgen, "CaseDocs", "lnd_in") - self._flanduse_timeseries_in = None - with open(self._lnd_in_path, "r") as lnd_in: - for line in lnd_in: - flanduse_timeseries_in = re.match(r" *flanduse_timeseries *= *'(.*)'", line) - if flanduse_timeseries_in: - self._flanduse_timeseries_in = flanduse_timeseries_in.group(1) - break diff --git a/cime_config/SystemTests/rxcropmaturityskipgen.py b/cime_config/SystemTests/rxcropmaturityskipgen.py index 4fb5257967..7b836a7b17 100644 --- a/cime_config/SystemTests/rxcropmaturityskipgen.py +++ b/cime_config/SystemTests/rxcropmaturityskipgen.py @@ -1,4 +1,4 @@ -from rxcropmaturityshared import RXCROPMATURITYSHARED +from rxcropmaturity import RXCROPMATURITYSHARED class RXCROPMATURITYSKIPGEN(RXCROPMATURITYSHARED): From 6c5fdb1d735531f8aaf129420448b2ac1d635a4c Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 14 Jun 2024 15:56:50 -0600 Subject: [PATCH 071/160] Remove refs to skip_run. --- cime_config/SystemTests/rxcropmaturity.py | 11 ++++------- cime_config/SystemTests/rxcropmaturityskipgen.py | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index e0f6f4df21..cffb832e8d 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -102,7 +102,7 @@ def __init__(self, case): # Which conda environment should we use? self._get_conda_env() - def _run_phase(self, skip_run=False, skip_gen=False): + def _run_phase(self, skip_gen=False): # Modeling this after the SSP test, we create a clone to be the case whose outputs we don't # want to be saved as baseline. @@ -177,8 +177,7 @@ def _run_phase(self, skip_run=False, skip_gen=False): case.set_value("STOP_N", 5) case.set_value("STOP_OPTION", "ndays") - if not skip_run: - self.run_indv(suffix=None, st_archive=True) + self.run_indv(suffix=None, st_archive=True) if skip_gen: # Interpolate an existing GDD file. Needed to check obedience to GDD inputs. self._run_interpolate_gdds() @@ -203,15 +202,13 @@ def _run_phase(self, skip_run=False, skip_gen=False): ] ) - if not skip_run: - self.run_indv() + self.run_indv() # ------------------------------------------------------------------- # (4) Check Prescribed Calendars run # ------------------------------------------------------------------- logger.info("RXCROPMATURITY log: output check: Prescribed Calendars") - if not skip_run: - self._run_check_rxboth_run(skip_gen) + self._run_check_rxboth_run(skip_gen) # Get sowing and harvest dates for this resolution. def _get_rx_dates(self): diff --git a/cime_config/SystemTests/rxcropmaturityskipgen.py b/cime_config/SystemTests/rxcropmaturityskipgen.py index 7b836a7b17..4feb661037 100644 --- a/cime_config/SystemTests/rxcropmaturityskipgen.py +++ b/cime_config/SystemTests/rxcropmaturityskipgen.py @@ -4,4 +4,4 @@ class RXCROPMATURITYSKIPGEN(RXCROPMATURITYSHARED): def run_phase(self): - self._run_phase(skip_run=False, skip_gen=True) + self._run_phase(skip_gen=True) From 3efa49aec741b01d324b2236ca347fa4d4bf349f Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 14 Jun 2024 17:02:16 -0600 Subject: [PATCH 072/160] Unexpected behavior in rxboth run now errors check_rxboth_run.py. --- .../crop_calendars/check_constant_vars.py | 2 +- python/ctsm/crop_calendars/check_rx_obeyed.py | 5 +++++ .../ctsm/crop_calendars/check_rxboth_run.py | 19 +++++++++++++++---- python/ctsm/crop_calendars/cropcal_module.py | 10 ++++++++-- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/python/ctsm/crop_calendars/check_constant_vars.py b/python/ctsm/crop_calendars/check_constant_vars.py index aa25a412fe..be7718256a 100644 --- a/python/ctsm/crop_calendars/check_constant_vars.py +++ b/python/ctsm/crop_calendars/check_constant_vars.py @@ -382,4 +382,4 @@ def check_constant_vars( raise RuntimeError("Stopping due to failed check_constant_vars().") bad_patches = np.unique(bad_patches) - return [int(p) for p in bad_patches] + return [int(p) for p in bad_patches], any_bad diff --git a/python/ctsm/crop_calendars/check_rx_obeyed.py b/python/ctsm/crop_calendars/check_rx_obeyed.py index 99b8d80bde..8525f3e890 100644 --- a/python/ctsm/crop_calendars/check_rx_obeyed.py +++ b/python/ctsm/crop_calendars/check_rx_obeyed.py @@ -203,9 +203,12 @@ def check_rx_obeyed( else: break + bad = True if all_ok == 2: + bad = False print(f"✅ {which_ds}: Prescribed {output_var} always obeyed") elif all_ok == 1: + bad = False # print(f"🟨 {which_ds}: Prescribed {output_var} *not* always obeyed, but acceptable:") # for x in diff_str_list: print(x) print( @@ -214,3 +217,5 @@ def check_rx_obeyed( ) elif not verbose: print(f"❌ {which_ds}: Prescribed {output_var} *not* always obeyed. E.g., {diffs_eg_txt}") + + return bad diff --git a/python/ctsm/crop_calendars/check_rxboth_run.py b/python/ctsm/crop_calendars/check_rxboth_run.py index ae4decde30..8f416e9f8e 100644 --- a/python/ctsm/crop_calendars/check_rxboth_run.py +++ b/python/ctsm/crop_calendars/check_rxboth_run.py @@ -75,6 +75,8 @@ def main(argv): "HARVEST_REASON_PERHARV", ] + any_bad = False + annual_outfiles = glob.glob(os.path.join(args.directory, "*.clm2.h1.*.nc")) # These should be constant in a Prescribed Calendars (rxboth) run, as long as the inputs were @@ -85,13 +87,17 @@ def main(argv): "rx_gdds_file": args.rx_gdds_file, } - case["ds"] = cc.import_output( + case["ds"], any_bad_import_output = cc.import_output( annual_outfiles, my_vars=my_vars, year_1=args.first_usable_year, year_n=args.last_usable_year, + throw_errors=False, ) - check_constant_vars(case["ds"], case, ignore_nan=True, verbose=True, throw_error=True) + any_bad = any_bad or any_bad_import_output + + _, any_bad_check_const_vars = check_constant_vars(case["ds"], case, ignore_nan=True, verbose=True, throw_error=True) + any_bad = any_bad or any_bad_check_const_vars # Import GGCMI sowing and harvest dates, and check sims casename = "Prescribed Calendars" @@ -128,15 +134,16 @@ def main(argv): # Check if case["rx_sdates_file"]: - check_rx_obeyed( + sdate_not_obeyed = check_rx_obeyed( case["ds"].vegtype_str.values, case["rx_sdates_ds"].isel(time=0), case["ds"], casename, "SDATES", ) + any_bad = any_bad or sdate_not_obeyed if case["rx_gdds_file"]: - check_rx_obeyed( + gdds_not_obeyed = check_rx_obeyed( case["ds"].vegtype_str.values, case["rx_gdds_ds"].isel(time=0), case["ds"], @@ -144,6 +151,10 @@ def main(argv): "GDDHARV", gdd_min=gdd_min, ) + any_bad = any_bad or gdds_not_obeyed + + if any_bad: + raise RuntimeError("Unexpected behavior in rxboth run") if __name__ == "__main__": diff --git a/python/ctsm/crop_calendars/cropcal_module.py b/python/ctsm/crop_calendars/cropcal_module.py index d25e16397e..fa1cc30e7c 100644 --- a/python/ctsm/crop_calendars/cropcal_module.py +++ b/python/ctsm/crop_calendars/cropcal_module.py @@ -170,8 +170,10 @@ def check_v0_le_v1(this_ds, var_list, msg_txt=" ", both_nan_ok=False, throw_erro if both_nan_ok: gdd_lt_hui = gdd_lt_hui | (np.isnan(this_ds[var0]) & np.isnan(this_ds[var1])) if np.all(gdd_lt_hui): + any_bad = False print(f"✅{msg_txt}{var0} always <= {var1}") else: + any_bad = True msg = f"❌{msg_txt}{var0} *not* always <= {var1}" gdd_lt_hui_vals = gdd_lt_hui.values patch_index = np.where(~gdd_lt_hui_vals)[0][0] @@ -187,6 +189,7 @@ def check_v0_le_v1(this_ds, var_list, msg_txt=" ", both_nan_ok=False, throw_erro print(msg) else: raise RuntimeError(msg) + return any_bad def get_gs_len_da(this_da): @@ -346,10 +349,13 @@ def import_output( sdates_rx_ds=None, gdds_rx_ds=None, verbose=False, + throw_errors=True, ): """ Import CLM output """ + any_bad = False + # Import this_ds = import_ds(filename, my_vars=my_vars, my_vegtypes=my_vegtypes) @@ -419,7 +425,7 @@ def import_output( # Check that e.g., GDDACCUM <= HUI for var_list in [["GDDACCUM", "HUI"], ["SYEARS", "HYEARS"]]: if all(v in this_ds_gs for v in var_list): - check_v0_le_v1(this_ds_gs, var_list, both_nan_ok=True, throw_error=True) + any_bad = check_v0_le_v1(this_ds_gs, var_list, both_nan_ok=True, throw_error=throw_errors) # Check that prescribed calendars were obeyed if sdates_rx_ds: @@ -447,7 +453,7 @@ def import_output( raise RuntimeError("How to get NHARVEST_DISCREP for NHARVESTS > 2?") this_ds_gs["NHARVEST_DISCREP"] = (this_ds_gs["NHARVESTS"] == 2).astype(int) - return this_ds_gs + return this_ds_gs, any_bad def handle_zombie_crops(this_ds): From 045d90f1d80f713eb3ae0ac58f6c2352937f1eb0 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 14 Jun 2024 17:08:21 -0600 Subject: [PATCH 073/160] Reformat with black. --- cime_config/SystemTests/rxcropmaturity.py | 3 +-- cime_config/SystemTests/rxcropmaturityskipgen.py | 1 - python/ctsm/crop_calendars/check_rxboth_run.py | 4 +++- python/ctsm/crop_calendars/cropcal_module.py | 4 +++- python/ctsm/crop_calendars/interpolate_gdds.py | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index cffb832e8d..a583432efe 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -95,8 +95,7 @@ def __init__(self, case): # Get cultivar maturity requirement file to fall back on if not generating it here self._gdds_file = None self._fallback_gdds_file = os.path.join( - os.path.dirname(self._sdatefile), - "gdds_20230829_161011.nc" + os.path.dirname(self._sdatefile), "gdds_20230829_161011.nc" ) # Which conda environment should we use? diff --git a/cime_config/SystemTests/rxcropmaturityskipgen.py b/cime_config/SystemTests/rxcropmaturityskipgen.py index 4feb661037..409f2b9847 100644 --- a/cime_config/SystemTests/rxcropmaturityskipgen.py +++ b/cime_config/SystemTests/rxcropmaturityskipgen.py @@ -2,6 +2,5 @@ class RXCROPMATURITYSKIPGEN(RXCROPMATURITYSHARED): - def run_phase(self): self._run_phase(skip_gen=True) diff --git a/python/ctsm/crop_calendars/check_rxboth_run.py b/python/ctsm/crop_calendars/check_rxboth_run.py index 8f416e9f8e..a1014b5e66 100644 --- a/python/ctsm/crop_calendars/check_rxboth_run.py +++ b/python/ctsm/crop_calendars/check_rxboth_run.py @@ -96,7 +96,9 @@ def main(argv): ) any_bad = any_bad or any_bad_import_output - _, any_bad_check_const_vars = check_constant_vars(case["ds"], case, ignore_nan=True, verbose=True, throw_error=True) + _, any_bad_check_const_vars = check_constant_vars( + case["ds"], case, ignore_nan=True, verbose=True, throw_error=True + ) any_bad = any_bad or any_bad_check_const_vars # Import GGCMI sowing and harvest dates, and check sims diff --git a/python/ctsm/crop_calendars/cropcal_module.py b/python/ctsm/crop_calendars/cropcal_module.py index fa1cc30e7c..b87d26816f 100644 --- a/python/ctsm/crop_calendars/cropcal_module.py +++ b/python/ctsm/crop_calendars/cropcal_module.py @@ -425,7 +425,9 @@ def import_output( # Check that e.g., GDDACCUM <= HUI for var_list in [["GDDACCUM", "HUI"], ["SYEARS", "HYEARS"]]: if all(v in this_ds_gs for v in var_list): - any_bad = check_v0_le_v1(this_ds_gs, var_list, both_nan_ok=True, throw_error=throw_errors) + any_bad = check_v0_le_v1( + this_ds_gs, var_list, both_nan_ok=True, throw_error=throw_errors + ) # Check that prescribed calendars were obeyed if sdates_rx_ds: diff --git a/python/ctsm/crop_calendars/interpolate_gdds.py b/python/ctsm/crop_calendars/interpolate_gdds.py index e9491480e9..2aa0b79997 100755 --- a/python/ctsm/crop_calendars/interpolate_gdds.py +++ b/python/ctsm/crop_calendars/interpolate_gdds.py @@ -13,7 +13,7 @@ sys.path.insert(1, _CTSM_PYTHON) from ctsm import ctsm_logging # pylint: disable=wrong-import-position -from ctsm.crop_calendars.cropcal_module import ( # pylint: disable=wrong-import-position +from ctsm.crop_calendars.cropcal_module import ( # pylint: disable=wrong-import-position unexpected_negative_rx_gdd, ) From 6af704f3c6d3d55aa9f288f736db2977792ef45a Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 14 Jun 2024 17:09:16 -0600 Subject: [PATCH 074/160] Add previous commit to .git-blame-ignore-revs. --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index f86a330408..dc56c6b7e2 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -45,3 +45,4 @@ aa04d1f7d86cc2503b98b7e2b2d84dbfff6c316b 6c6f57e948bfa31e60b383536cc21663fedb8b70 9660667b1267dcd4150889f5f39db540158be74a 665cf86102e09b4c4c5a140700676dca23bc55a9 +045d90f1d80f713eb3ae0ac58f6c2352937f1eb0 From 76b7df7d9fb8be59118ac7b260acd46852e196f8 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 14 Jun 2024 17:13:10 -0600 Subject: [PATCH 075/160] check_rx_obeyed.py: Satisfy pylint. --- python/ctsm/crop_calendars/check_rx_obeyed.py | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/python/ctsm/crop_calendars/check_rx_obeyed.py b/python/ctsm/crop_calendars/check_rx_obeyed.py index 8525f3e890..a38b946ec5 100644 --- a/python/ctsm/crop_calendars/check_rx_obeyed.py +++ b/python/ctsm/crop_calendars/check_rx_obeyed.py @@ -104,6 +104,27 @@ def get_extreme_info(diff_array, rx_array, mxn, dims, gs_da, patches1d_lon, patc return round(themxn, 3), round(this_lon, 3), round(this_lat, 3), this_gs, round(this_rx) +def summarize_results(which_ds, output_var, verbose, all_ok, gdd_tolerance, diffs_eg_txt): + """ + Summarize results + """ + bad = True + if all_ok == 2: + bad = False + print(f"✅ {which_ds}: Prescribed {output_var} always obeyed") + elif all_ok == 1: + bad = False + # print(f"🟨 {which_ds}: Prescribed {output_var} *not* always obeyed, but acceptable:") + # for x in diff_str_list: print(x) + print( + f"🟨 {which_ds}: Prescribed {output_var} *not* always obeyed, but acceptable (diffs <= " + + f"{gdd_tolerance})" + ) + elif not verbose: + print(f"❌ {which_ds}: Prescribed {output_var} *not* always obeyed. E.g., {diffs_eg_txt}") + return bad + + def check_rx_obeyed( vegtype_list, rx_ds, dates_ds, which_ds, output_var, gdd_min=None, verbose=False ): @@ -203,19 +224,6 @@ def check_rx_obeyed( else: break - bad = True - if all_ok == 2: - bad = False - print(f"✅ {which_ds}: Prescribed {output_var} always obeyed") - elif all_ok == 1: - bad = False - # print(f"🟨 {which_ds}: Prescribed {output_var} *not* always obeyed, but acceptable:") - # for x in diff_str_list: print(x) - print( - f"🟨 {which_ds}: Prescribed {output_var} *not* always obeyed, but acceptable (diffs <= " - + f"{gdd_tolerance})" - ) - elif not verbose: - print(f"❌ {which_ds}: Prescribed {output_var} *not* always obeyed. E.g., {diffs_eg_txt}") + bad = summarize_results(which_ds, output_var, verbose, all_ok, gdd_tolerance, diffs_eg_txt) return bad From fda1ae2b57993f84dd96953538bba08b8c5d74fe Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 16 Jun 2024 08:48:17 -0600 Subject: [PATCH 076/160] check_rxboth_run: Handle diffs_eg_txt never assigned. --- python/ctsm/crop_calendars/check_rx_obeyed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/ctsm/crop_calendars/check_rx_obeyed.py b/python/ctsm/crop_calendars/check_rx_obeyed.py index a38b946ec5..383ebbf1e0 100644 --- a/python/ctsm/crop_calendars/check_rx_obeyed.py +++ b/python/ctsm/crop_calendars/check_rx_obeyed.py @@ -135,6 +135,7 @@ def check_rx_obeyed( dates_ds, which_ds, output_var, verbose ) + diffs_eg_txt = None for vegtype_str in vegtype_list: thisveg_patches = np.where(dates_ds.patches1d_itype_veg_str == vegtype_str)[0] if thisveg_patches.size == 0: From ca15115f512a8dafbf54d1f441dbe2227f96b482 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 16 Jun 2024 08:57:43 -0600 Subject: [PATCH 077/160] RXCROPMATURITY: Fix gdd-gen namelist. --- cime_config/SystemTests/rxcropmaturity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index 0dbbcad291..0b9c5e8ee0 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -130,6 +130,8 @@ def _run_phase(self, skip_gen=False): logger.info("RXCROPMATURITY log: modify user_nl files: generate GDDs") self._append_to_user_nl_clm( [ + "cropcals_rx = .true.", + "stream_fldFileName_cultivar_gdds = ''", "generate_crop_gdds = .true.", "use_mxmat = .false.", " ", From 23f6fa295dd6df50770c8737a38297000533d4d9 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 16 Jun 2024 09:17:48 -0600 Subject: [PATCH 078/160] PlantCrop: Error if gdd20 <= 0. --- src/biogeochem/CNPhenologyMod.F90 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/biogeochem/CNPhenologyMod.F90 b/src/biogeochem/CNPhenologyMod.F90 index b346cb119f..c3eff1538a 100644 --- a/src/biogeochem/CNPhenologyMod.F90 +++ b/src/biogeochem/CNPhenologyMod.F90 @@ -2704,6 +2704,10 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & else if (ivt(p) == nwwheat .or. ivt(p) == nirrig_wwheat) then gddmaturity(p) = hybgdd(ivt(p)) else + if (gdd20 <= 0._r8) then + write(iulog, *) 'ERROR: gdd20 ',gdd20,' for ivt ',ivt(p) + call endrun(msg="Stopping") + end if if (ivt(p) == ntmp_soybean .or. ivt(p) == nirrig_tmp_soybean .or. & ivt(p) == ntrp_soybean .or. ivt(p) == nirrig_trp_soybean .or. & ivt(p) == nswheat .or. ivt(p) == nirrig_swheat .or. & @@ -2716,10 +2720,11 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & ivt(p) == nmiscanthus .or. ivt(p) == nirrig_miscanthus .or. & ivt(p) == nswitchgrass .or. ivt(p) == nirrig_switchgrass) then gddmaturity(p) = max(950._r8, min(gdd20*0.85_r8, hybgdd(ivt(p)))) - if (do_plant_normal) then + if (do_plant_normal) then ! TODO SSR: Add ".and. .not. do_plant_prescribed"? gddmaturity(p) = max(950._r8, min(gddmaturity(p)+150._r8, 1850._r8)) end if else + ! TODO SSR: Add more descriptive error message call endrun(msg="Stopping") end if From 78c5d989e8664d968a156cdac9d2120b17256618 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Mon, 17 Jun 2024 16:21:08 -0600 Subject: [PATCH 079/160] Handle gdd20 < minimum if generate_crop_gdds. --- src/biogeochem/CNPhenologyMod.F90 | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/biogeochem/CNPhenologyMod.F90 b/src/biogeochem/CNPhenologyMod.F90 index c3eff1538a..d4f486acb7 100644 --- a/src/biogeochem/CNPhenologyMod.F90 +++ b/src/biogeochem/CNPhenologyMod.F90 @@ -2704,10 +2704,6 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & else if (ivt(p) == nwwheat .or. ivt(p) == nirrig_wwheat) then gddmaturity(p) = hybgdd(ivt(p)) else - if (gdd20 <= 0._r8) then - write(iulog, *) 'ERROR: gdd20 ',gdd20,' for ivt ',ivt(p) - call endrun(msg="Stopping") - end if if (ivt(p) == ntmp_soybean .or. ivt(p) == nirrig_tmp_soybean .or. & ivt(p) == ntrp_soybean .or. ivt(p) == nirrig_trp_soybean .or. & ivt(p) == nswheat .or. ivt(p) == nirrig_swheat .or. & @@ -2731,7 +2727,7 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & endif if (gddmaturity(p) < min_gddmaturity) then - if (use_cropcal_rx_cultivar_gdds) then + if (use_cropcal_rx_cultivar_gdds .or. generate_crop_gdds) then if (did_rx_gdds) then write(iulog,*) 'Some patch with ivt ',ivt(p),' has rx gddmaturity',gddmaturity(p),'; using min_gddmaturity instead (',min_gddmaturity,')' end if From f618893aa8598727e37e6ae8064f317fa3f13182 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 08:46:47 -0600 Subject: [PATCH 080/160] Set cropcal_rx true for both parts of RXCROPMATURITY*. --- cime_config/SystemTests/rxcropmaturity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index 0b9c5e8ee0..46ed8f8be3 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -130,7 +130,6 @@ def _run_phase(self, skip_gen=False): logger.info("RXCROPMATURITY log: modify user_nl files: generate GDDs") self._append_to_user_nl_clm( [ - "cropcals_rx = .true.", "stream_fldFileName_cultivar_gdds = ''", "generate_crop_gdds = .true.", "use_mxmat = .false.", @@ -397,6 +396,7 @@ def _run_check_rxboth_run(self, skip_gen): def _modify_user_nl_allruns(self): nl_additions = [ + "cropcals_rx = .true.", "stream_meshfile_cropcal = '{}'".format(self._case.get_value("LND_DOMAIN_MESH")), "stream_fldFileName_swindow_start = '{}'".format(self._sdatefile), "stream_fldFileName_swindow_end = '{}'".format(self._sdatefile), From 7578787061226c463828f80fbc6612cdd2388ddc Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 10:04:17 -0600 Subject: [PATCH 081/160] Add GddGen(OnlyOutputs(Surf)) testmods. --- .../testmods_dirs/clm/GddGen/include_user_mods | 1 + .../testdefs/testmods_dirs/clm/GddGen/user_nl_clm | 11 +++++++++++ .../clm/GddGenOnlyOutputs/include_user_mods | 1 + .../clm/GddGenOnlyOutputs/user_nl_clm | 14 ++++++++++++++ .../clm/GddGenOnlyOutputsSurf/include_user_mods | 1 + .../clm/GddGenOnlyOutputsSurf/user_nl_clm | 5 +++++ 6 files changed, 33 insertions(+) create mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods create mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm create mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods create mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm create mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods create mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods new file mode 100644 index 0000000000..6cc92bc8b3 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods @@ -0,0 +1 @@ +../GddGenOnlyOutputsSurf diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm new file mode 100644 index 0000000000..f36ffd26b9 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm @@ -0,0 +1,11 @@ +cropcals_rx = .true. +stream_meshfile_cropcal = '/glade/campaign/cesm/cesmdata/inputdata/share/meshes/10x15_nomask_c110308_ESMFmesh.nc' +stream_fldFileName_swindow_start = '/glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc' +stream_fldFileName_swindow_end = '/glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc' +stream_year_first_cropcal_swindows = 2000 +stream_year_last_cropcal_swindows = 2000 +model_year_align_cropcal_swindows = 2000 + +stream_fldFileName_cultivar_gdds = '' +generate_crop_gdds = .true. +use_mxmat = .false. diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods new file mode 100644 index 0000000000..02ec13743f --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods @@ -0,0 +1 @@ +../cropMonthOutput diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm new file mode 100644 index 0000000000..8fe842624d --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm @@ -0,0 +1,14 @@ +! (h1) Annual outputs on sowing or harvest axis +hist_fincl2 = 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV' +hist_nhtfrq(2) = 17520 +hist_mfilt(2) = 999 +hist_type1d_pertape(2) = 'PFTS' +hist_dov2xy(2) = .false. + +! (h2) Daily outputs for GDD generation and figure-making +hist_fincl3 = 'GDDACCUM', 'GDDHARV' +hist_nhtfrq(3) = -24 +hist_mfilt(3) = 365 +hist_type1d_pertape(3) = 'PFTS' +hist_dov2xy(3) = .false. + diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods new file mode 100644 index 0000000000..8c6e50384c --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods @@ -0,0 +1 @@ +../GddGenOnlyOutputs diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm new file mode 100644 index 0000000000..184043f013 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm @@ -0,0 +1,5 @@ +fsurdat = '/glade/derecho/scratch/samrabin/tests_0618-093950de/RXCROPMATURITY_Lm61.f10_f10_mg37.IHistClm50BgcCrop.derecho_intel.clm-cropMonthOutput.0618-093950de.gddgen/surfdata_10x15_hist_1850_78pfts_c240216.all_crops_everywhere.nc' +do_transient_crops = .false. +flanduse_timeseries = '' +use_init_interp = .true. + From 6fdcf4cd198cd53733437136a8e90d59e4935369 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 15:50:59 -0600 Subject: [PATCH 082/160] UpdateAccVars_CropGDDs: Remove check that patch is a crop. --- src/biogeophys/TemperatureType.F90 | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index afe0948e6d..79bdce64ef 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1400,7 +1400,6 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d use shr_const_mod , only : SHR_CONST_CDAY, SHR_CONST_TKFRZ use accumulMod , only : update_accum_field, extract_accum_field, markreset_accum_field use clm_time_manager , only : is_doy_in_interval, get_curr_calday - use pftconMod , only : npcropmin use CropType, only : crop_type ! ! !ARGUMENTS @@ -1418,7 +1417,6 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d character(8) :: field_name ! E.g., GDD0 character(32) :: format_string integer :: p - integer :: ivt ! vegetation type logical :: in_accumulation_season real(r8) :: lat ! latitude integer :: gdd20_season_start, gdd20_season_end @@ -1456,9 +1454,8 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d do p = begp,endp - ! Avoid unnecessary calculations over inactive points and non-crops - ivt = patch%itype(p) - if (ivt < npcropmin .or. .not. patch%active(p)) then + ! Avoid unnecessary calculations over inactive points + if (.not. patch%active(p)) then cycle end if From c6ae96bd6dd557b6baf734431799b78351fd1910 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 16:05:11 -0600 Subject: [PATCH 083/160] Add StreamGdd20Seasons testmod. --- .../testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm diff --git a/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm new file mode 100644 index 0000000000..dd6cd3451d --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm @@ -0,0 +1,2 @@ +stream_gdd20_seasons = .true. + From 9c5b2792c38a572e8b297701ebfcf1234e13043d Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 16:22:24 -0600 Subject: [PATCH 084/160] StreamGdd20Seasons: Set allow_invalid_gdd20_season_inputs .true. --- .../testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm | 1 + 1 file changed, 1 insertion(+) diff --git a/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm index dd6cd3451d..86cdffe96a 100644 --- a/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm @@ -1,2 +1,3 @@ stream_gdd20_seasons = .true. +allow_invalid_gdd20_season_inputs = .true. From 70b8fe32f6f437d3458013b99b2ae5bb136a6255 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 16:38:16 -0600 Subject: [PATCH 085/160] Don't check gdd20_season_starts/ends if not crop. --- src/biogeophys/TemperatureType.F90 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 79bdce64ef..4212bcdd0f 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1400,6 +1400,7 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d use shr_const_mod , only : SHR_CONST_CDAY, SHR_CONST_TKFRZ use accumulMod , only : update_accum_field, extract_accum_field, markreset_accum_field use clm_time_manager , only : is_doy_in_interval, get_curr_calday + use pftconMod , only : npcropmin use CropType, only : crop_type ! ! !ARGUMENTS @@ -1467,7 +1468,7 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d ((month > 9 .or. month < 4) .and. lat < 0._r8) ! Replace with read-in gdd20 accumulation season, if needed and valid ! (If these aren't being read in or they're invalid, they'll be -1) - if (stream_gdd20_seasons) then + if (stream_gdd20_seasons .and. patch%itype(p) >= npcropmin) then gdd20_season_start = int(gdd20_season_starts(p)) gdd20_season_end = int(gdd20_season_ends(p)) if (gdd20_season_start >= 1 .and. gdd20_season_end >= 1) then From 41e30bd412045e74d33bb53137a4aa08bb401739 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 16:58:33 -0600 Subject: [PATCH 086/160] Correct comment in crop testmod user_nl_clm. --- cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm index 67042ea01a..c9598772a2 100644 --- a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm @@ -9,7 +9,7 @@ hist_fincl2 += 'DYN_COL_SOIL_ADJUSTMENTS_C' -! Annual crop variables on per-sowing/per-harvest axes, per PFT. +! Annual crop variables (including per-sowing/per-harvest axes), per PFT. hist_fincl3 = 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'GRAINN_TO_FOOD_PERHARV', 'GRAINN_TO_FOOD_ANN', 'GRAINC_TO_SEED_PERHARV', 'GRAINC_TO_SEED_ANN', 'GRAINN_TO_SEED_PERHARV', 'GRAINN_TO_SEED_ANN', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV', 'SWINDOW_STARTS', 'SWINDOW_ENDS' hist_nhtfrq(3) = 17520 hist_mfilt(3) = 1 From 30597ce4b917d88d3f31d850eee7615bfd06d830 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 17:10:47 -0600 Subject: [PATCH 087/160] Add four tests to crop_calendars suite: * SMS_Ld65.f10_f10_mg37.IHistClm50BgcCrop.derecho_intel.clm-GddGen.0618-170953de_int * SMS_Ld65.f10_f10_mg37.IHistClm50BgcCrop.derecho_intel.clm-GddGen--clm-StreamGdd20Seasons * SMS_Ld65.f10_f10_mg37.IHistClm60BgcCrop.derecho_intel.clm-GddGen * SMS_Ld65.f10_f10_mg37.IHistClm60BgcCrop.derecho_intel.clm-GddGen--clm-StreamGdd20Seasons --- cime_config/testdefs/testlist_clm.xml | 36 +++++++++++++++++++ .../clm/StreamGdd20Seasons/user_nl_clm | 2 ++ 2 files changed, 38 insertions(+) diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 254d60c74b..a51e1f92ac 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3767,5 +3767,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm index 86cdffe96a..7f140eb719 100644 --- a/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm @@ -1,3 +1,5 @@ stream_gdd20_seasons = .true. allow_invalid_gdd20_season_inputs = .true. +! Annual crop variables (including per-sowing/per-harvest axes), per PFT. +hist_fincl3 += 'GDD20_BASELINE', 'GDD20_SEASON_START', 'GDD20_SEASON_END' \ No newline at end of file From 2573c34721f9dd74f4b82fbbb062edd9b1b48c70 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 18 Jun 2024 18:46:57 -0600 Subject: [PATCH 088/160] Fix walltime for GddGen tests. --- cime_config/testdefs/testlist_clm.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index a51e1f92ac..c5aa12387c 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3772,7 +3772,7 @@ - + @@ -3781,7 +3781,7 @@ - + @@ -3790,7 +3790,7 @@ - + @@ -3799,7 +3799,7 @@ - + From 418f6bed1d757d8aada4a0ce932dc501c37d47df Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 19 Jun 2024 11:53:55 -0600 Subject: [PATCH 089/160] GDD20_BASELINE and GDD20_SEASON_START/END now instantaneous. --- src/biogeochem/CropType.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/biogeochem/CropType.F90 b/src/biogeochem/CropType.F90 index b1e0e4571d..77da895135 100644 --- a/src/biogeochem/CropType.F90 +++ b/src/biogeochem/CropType.F90 @@ -366,15 +366,15 @@ subroutine InitHistory(this, bounds) this%gdd20_baseline_patch(begp:endp) = spval call hist_addfld1d (fname='GDD20_BASELINE', units='ddays', & - avgflag='A', long_name='Baseline mean growing-degree days accumulated during accumulation period (from input)', & + avgflag='I', long_name='Baseline mean growing-degree days accumulated during accumulation period (from input)', & ptr_patch=this%gdd20_baseline_patch, default='inactive') this%gdd20_season_start_patch(begp:endp) = spval call hist_addfld1d (fname='GDD20_SEASON_START', units='day of year', & - avgflag='A', long_name='Start of the GDD20 accumulation season (from input)', & + avgflag='I', long_name='Start of the GDD20 accumulation season (from input)', & ptr_patch=this%gdd20_season_start_patch, default='inactive') this%gdd20_season_end_patch(begp:endp) = spval call hist_addfld1d (fname='GDD20_SEASON_END', units='day of year', & - avgflag='A', long_name='End of the GDD20 accumulation season (from input)', & + avgflag='I', long_name='End of the GDD20 accumulation season (from input)', & ptr_patch=this%gdd20_season_end_patch, default='inactive') end subroutine InitHistory From e3a00f84b3b3b276659673ac766b0d24bfc0d881 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 19 Jun 2024 11:55:57 -0600 Subject: [PATCH 090/160] Save GDD20_BASELINE and GDD20_SEASON_START/END in crop testmod. --- cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm index c9598772a2..a368e97593 100644 --- a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm @@ -9,8 +9,8 @@ hist_fincl2 += 'DYN_COL_SOIL_ADJUSTMENTS_C' -! Annual crop variables (including per-sowing/per-harvest axes), per PFT. -hist_fincl3 = 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'GRAINN_TO_FOOD_PERHARV', 'GRAINN_TO_FOOD_ANN', 'GRAINC_TO_SEED_PERHARV', 'GRAINC_TO_SEED_ANN', 'GRAINN_TO_SEED_PERHARV', 'GRAINN_TO_SEED_ANN', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV', 'SWINDOW_STARTS', 'SWINDOW_ENDS' +! Annual instantaneous crop variables (including per-sowing/per-harvest axes), per PFT. +hist_fincl3 = 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'GRAINN_TO_FOOD_PERHARV', 'GRAINN_TO_FOOD_ANN', 'GRAINC_TO_SEED_PERHARV', 'GRAINC_TO_SEED_ANN', 'GRAINN_TO_SEED_PERHARV', 'GRAINN_TO_SEED_ANN', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV', 'SWINDOW_STARTS', 'SWINDOW_ENDS', 'GDD20_BASELINE', 'GDD20_SEASON_START', 'GDD20_SEASON_END' hist_nhtfrq(3) = 17520 hist_mfilt(3) = 1 hist_type1d_pertape(3) = 'PFTS' From 3b915fdd953bfcf24fd7a0e3c29722041ca119c1 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 19 Jun 2024 12:10:14 -0600 Subject: [PATCH 091/160] Simplify crop calendar tests. --- cime_config/testdefs/testlist_clm.xml | 22 ++----------------- .../testdefs/testmods_dirs/clm/GddGen/README | 5 +++++ .../clm/GddGen/include_user_mods | 2 +- .../testmods_dirs/clm/GddGen/user_nl_clm | 14 ++++++------ .../clm/GddGenOnlyOutputs/include_user_mods | 1 - .../clm/GddGenOnlyOutputs/user_nl_clm | 14 ------------ .../GddGenOnlyOutputsSurf/include_user_mods | 1 - .../clm/GddGenOnlyOutputsSurf/user_nl_clm | 5 ----- .../clm/StreamGdd20Seasons/user_nl_clm | 3 --- .../clm/cropAnnOutputMonthly/user_nl_clm | 3 +++ 10 files changed, 18 insertions(+), 52 deletions(-) create mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGen/README delete mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods delete mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm delete mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods delete mode 100644 cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm create mode 100644 cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index c5aa12387c..1a09ce4e80 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3767,7 +3767,7 @@ - + @@ -3776,25 +3776,7 @@ - - - - - - - - - - - - - - - - - - - + diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGen/README b/cime_config/testdefs/testmods_dirs/clm/GddGen/README new file mode 100644 index 0000000000..3236ca609a --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/GddGen/README @@ -0,0 +1,5 @@ +The GddGen test is set up just like a GDD-Generating run, with two differences: +1) It doesn't include an all-crops-everywhere surface dataset, +2) it doesn't actually run the GDD-generating script, +and +3) it includes some extra outputs. diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods index 6cc92bc8b3..02ec13743f 100644 --- a/cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods +++ b/cime_config/testdefs/testmods_dirs/clm/GddGen/include_user_mods @@ -1 +1 @@ -../GddGenOnlyOutputsSurf +../cropMonthOutput diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm index f36ffd26b9..87cdd5d5b5 100644 --- a/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm @@ -1,11 +1,11 @@ cropcals_rx = .true. -stream_meshfile_cropcal = '/glade/campaign/cesm/cesmdata/inputdata/share/meshes/10x15_nomask_c110308_ESMFmesh.nc' -stream_fldFileName_swindow_start = '/glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc' -stream_fldFileName_swindow_end = '/glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-f10_f10_mg37.2000-2000.20230330_165301.nc' -stream_year_first_cropcal_swindows = 2000 -stream_year_last_cropcal_swindows = 2000 -model_year_align_cropcal_swindows = 2000 - stream_fldFileName_cultivar_gdds = '' generate_crop_gdds = .true. use_mxmat = .false. + +! (h3) Daily outputs for GDD generation and figure-making +hist_fincl4 = 'GDDACCUM', 'GDDHARV' +hist_nhtfrq(4) = -24 +hist_mfilt(4) = 365 +hist_type1d_pertape(4) = 'PFTS' +hist_dov2xy(4) = .false. diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods deleted file mode 100644 index 02ec13743f..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/include_user_mods +++ /dev/null @@ -1 +0,0 @@ -../cropMonthOutput diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm deleted file mode 100644 index 8fe842624d..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputs/user_nl_clm +++ /dev/null @@ -1,14 +0,0 @@ -! (h1) Annual outputs on sowing or harvest axis -hist_fincl2 = 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV' -hist_nhtfrq(2) = 17520 -hist_mfilt(2) = 999 -hist_type1d_pertape(2) = 'PFTS' -hist_dov2xy(2) = .false. - -! (h2) Daily outputs for GDD generation and figure-making -hist_fincl3 = 'GDDACCUM', 'GDDHARV' -hist_nhtfrq(3) = -24 -hist_mfilt(3) = 365 -hist_type1d_pertape(3) = 'PFTS' -hist_dov2xy(3) = .false. - diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods deleted file mode 100644 index 8c6e50384c..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/include_user_mods +++ /dev/null @@ -1 +0,0 @@ -../GddGenOnlyOutputs diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm deleted file mode 100644 index 184043f013..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/GddGenOnlyOutputsSurf/user_nl_clm +++ /dev/null @@ -1,5 +0,0 @@ -fsurdat = '/glade/derecho/scratch/samrabin/tests_0618-093950de/RXCROPMATURITY_Lm61.f10_f10_mg37.IHistClm50BgcCrop.derecho_intel.clm-cropMonthOutput.0618-093950de.gddgen/surfdata_10x15_hist_1850_78pfts_c240216.all_crops_everywhere.nc' -do_transient_crops = .false. -flanduse_timeseries = '' -use_init_interp = .true. - diff --git a/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm index 7f140eb719..8b040d9d43 100644 --- a/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/StreamGdd20Seasons/user_nl_clm @@ -1,5 +1,2 @@ stream_gdd20_seasons = .true. allow_invalid_gdd20_season_inputs = .true. - -! Annual crop variables (including per-sowing/per-harvest axes), per PFT. -hist_fincl3 += 'GDD20_BASELINE', 'GDD20_SEASON_START', 'GDD20_SEASON_END' \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm new file mode 100644 index 0000000000..1c47a2ebd1 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm @@ -0,0 +1,3 @@ +! These variables SHOULD only be saved annually in real runs, but for testing purposes it's fine to have them monthly. +! Modifies h2 history file defined in crop testmod. +hist_nhtfrq(3) = 0 From 501ba25c34e9f727442d77c0d3d43d7f2f7c9133 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 19 Jun 2024 13:19:18 -0600 Subject: [PATCH 092/160] Add tricky ERP test of RxCropCalsAdaptGGCMI. --- cime_config/testdefs/testlist_clm.xml | 10 ++++++++++ .../testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm | 1 + .../testmods_dirs/clm/midDecStart/include_user_mods | 1 + .../testmods_dirs/clm/midDecStart/shell_commands | 2 ++ 4 files changed, 14 insertions(+) create mode 100644 cime_config/testdefs/testmods_dirs/clm/midDecStart/include_user_mods create mode 100755 cime_config/testdefs/testmods_dirs/clm/midDecStart/shell_commands diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 1a09ce4e80..40f351ab4a 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3767,6 +3767,16 @@ + + + + + + + + + + diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm index 42e57a675c..dd4ac3117c 100644 --- a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptGGCMI/user_nl_clm @@ -1,4 +1,5 @@ stream_gdd20_seasons = .true. +flush_gdd20 = .true. !TODO SSR: Try without this once you have half-degree inputs allow_invalid_gdd20_season_inputs = .true. diff --git a/cime_config/testdefs/testmods_dirs/clm/midDecStart/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/midDecStart/include_user_mods new file mode 100644 index 0000000000..fe0e18cf88 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/midDecStart/include_user_mods @@ -0,0 +1 @@ +../default diff --git a/cime_config/testdefs/testmods_dirs/clm/midDecStart/shell_commands b/cime_config/testdefs/testmods_dirs/clm/midDecStart/shell_commands new file mode 100755 index 0000000000..d044ab8c3b --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/midDecStart/shell_commands @@ -0,0 +1,2 @@ +./xmlchange RUN_STARTDATE=2001-12-15 +./xmlchange CLM_BLDNML_OPTS=-ignore_warnings --append From 8d754533f0bf796fd5ee365d4fd1eedaee53ab20 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 20 Jun 2024 13:03:36 -0600 Subject: [PATCH 093/160] TemperatureType: Save flush_gdd20 regardless of stream_gdd20_seasons. --- src/biogeophys/TemperatureType.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 4212bcdd0f..bb579b8031 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1136,7 +1136,7 @@ subroutine Restart(this, bounds, ncid, flag, is_simple_buildtemp, is_prog_buildt end if end if - if (use_crop .and. stream_gdd20_seasons) then + if (use_crop) then if (flag == 'write') then if (this%flush_gdd20) then idata = 1 From 6810090b7258c2d4648204e3fe12d2c999cb55f7 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 20 Jun 2024 13:27:05 -0600 Subject: [PATCH 094/160] cropcal_interp: Ignore init when considering stream_gdd20_seasons. --- src/cpl/share_esmf/cropcalStreamMod.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index ef406a7e60..4034f550d4 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -741,7 +741,7 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) dataptr2d_gdd20_season_start(:,:) = -1._r8 allocate(dataptr2d_gdd20_season_end (lsize, ncft)) dataptr2d_gdd20_season_end(:,:) = -1._r8 - if (stream_gdd20_seasons .and. init) then + if (stream_gdd20_seasons) then ! Starting with npcropmin will skip generic crops do n = 1, ncft call dshr_fldbun_getFldPtr(sdat_cropcal_gdd20_season_start%pstrm(1)%fldbun_model, trim(stream_varnames_sdate(n)), & @@ -815,7 +815,7 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) end if end if - end if ! stream_gdd20_seasons and init + end if ! stream_gdd20_seasons deallocate(dataptr2d_gdd20_season_start) deallocate(dataptr2d_gdd20_season_end) From 24845d2ad8e5f235053bebdcfb845fa0513d42c7 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 21 Jun 2024 08:59:56 -0600 Subject: [PATCH 095/160] cropcal_interp: Ignore init when considering adapt_cropcal_rx_cultivar_gdds. --- src/cpl/share_esmf/cropcalStreamMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index 4034f550d4..ef3bab03c5 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -685,7 +685,7 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) deallocate(dataptr2d_cultivar_gdds) allocate(dataptr2d_gdd20_baseline(lsize, ncft)) - if (adapt_cropcal_rx_cultivar_gdds .and. init) then + if (adapt_cropcal_rx_cultivar_gdds) then ! Read GDD20 baselines from input files ! Starting with npcropmin will skip generic crops do n = 1, ncft From 5b49d91ace8161c2a2c14b85b73dac994b1b6413 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 21 Jun 2024 09:41:46 -0600 Subject: [PATCH 096/160] Forgot to commit RxCropCalsAdaptFlush testmod. --- .../testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods | 1 + .../testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods create mode 100644 cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods new file mode 100644 index 0000000000..af5fe8591e --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods @@ -0,0 +1 @@ +../RxCropCalsAdapt diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm new file mode 100644 index 0000000000..4c6af0f6d2 --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm @@ -0,0 +1,2 @@ + +flush_gdd20 = .true. From 4f702c33657ace2b931a0f33559a2ab676fd4bd4 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 25 Jun 2024 11:51:39 -0600 Subject: [PATCH 097/160] Add ERP test of RxCropCalsAdaptGGCMI to expected failure list. --- cime_config/testdefs/ExpectedTestFails.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cime_config/testdefs/ExpectedTestFails.xml b/cime_config/testdefs/ExpectedTestFails.xml index 03eb6a157d..0fac518374 100644 --- a/cime_config/testdefs/ExpectedTestFails.xml +++ b/cime_config/testdefs/ExpectedTestFails.xml @@ -170,6 +170,13 @@ + + + FAIL + #2593 + + + From d13508bef6c0b31e8569daa758c44c379f9309b8 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 25 Jun 2024 12:05:45 -0600 Subject: [PATCH 098/160] run_sys_tests: Check Python env for RXCROPMATURITY. --- python/ctsm/run_sys_tests.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/python/ctsm/run_sys_tests.py b/python/ctsm/run_sys_tests.py index de93081504..9961fd325d 100644 --- a/python/ctsm/run_sys_tests.py +++ b/python/ctsm/run_sys_tests.py @@ -736,13 +736,29 @@ def _check_py_env(test_attributes): # whether import is possible. # pylint: disable=import-error disable - # Check requirements for FSURDATMODIFYCTSM, if needed - if any("FSURDATMODIFYCTSM" in t for t in test_attributes): + # Check requirements for using modify_fsurdat, if needed + modify_fsurdat_users = ["FSURDATMODIFYCTSM", "RXCROPMATURITY"] + if any(any(u in t for u in modify_fsurdat_users) for t in test_attributes): try: import ctsm.modify_input_files.modify_fsurdat except ModuleNotFoundError as err: raise ModuleNotFoundError("modify_fsurdat" + err_msg) from err + # Check requirements for RXCROPMATURITY, if needed + if any("RXCROPMATURITY" in t for t in test_attributes): + try: + import ctsm.crop_calendars.check_rxboth_run + except ModuleNotFoundError as err: + raise ModuleNotFoundError("check_rxboth_run" + err_msg) from err + try: + import ctsm.crop_calendars.generate_gdds + except ModuleNotFoundError as err: + raise ModuleNotFoundError("generate_gdds" + err_msg) from err + try: + import ctsm.crop_calendars.interpolate_gdds + except ModuleNotFoundError as err: + raise ModuleNotFoundError("interpolate_gdds" + err_msg) from err + # Check that list for any testmods that use modify_fates_paramfile.py testmods_to_check = ["clm-FatesColdTwoStream", "clm-FatesColdTwoStreamNoCompFixedBioGeo"] testmods = _get_testmod_list(test_attributes) From a5da93ec5d016aabc11452870d8c3fab1677ef25 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 26 Jun 2024 12:31:05 -0600 Subject: [PATCH 099/160] generate_gdd20_baseline: Add optional -y1 and -yN args. --- .../crop_calendars/generate_gdd20_baseline.py | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 89a38a0a08..4357bd6dd1 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -70,6 +70,24 @@ def _parse_args(): help="Overwrite existing output file, if any", action="store_true", ) + parser.add_argument( + "-y1", + "--first-year", + help=( + "First calendar year to include" + ), + type=int, + required=False, + ) + parser.add_argument( + "-yN", + "--last-year", + help=( + "Last calendar year to include" + ), + type=int, + required=False, + ) # Get arguments args = parser.parse_args(sys.argv[1:]) @@ -78,7 +96,19 @@ def _parse_args(): if os.path.exists(args.output_file) and not args.overwrite: raise FileExistsError("Output file exists but --overwrite is not specified") - return args + # Process time slice + # Assumes CESM behavior where data for e.g. 1987 is saved as 1988-01-01 + if args.first_year is not None: + date_1 = f"{args.first_year+1}-01-01" + else: + date_1 = "0000-01-01" + if args.last_year is not None: + date_n = f"{args.last_year+1}-01-01" + else: + date_n = "9999-12-31" + time_slice = slice(date_1, date_n) + + return args, time_slice def _get_cft_list(crop_list): @@ -161,7 +191,7 @@ def _add_time_axis(da_in): return da_out -def generate_gdd20_baseline(input_files, output_file, author): +def generate_gdd20_baseline(input_files, output_file, author, time_slice): """ Generate stream_fldFileName_gdd20_baseline file from CTSM outputs """ @@ -173,7 +203,7 @@ def generate_gdd20_baseline(input_files, output_file, author): input_files.sort() # Import history files and ensure they have lat/lon dims - ds_in = import_ds(input_files, VAR_LIST_IN + GRIDDING_VAR_LIST) + ds_in = import_ds(input_files, VAR_LIST_IN + GRIDDING_VAR_LIST, time_slice=time_slice) if not all(x in ds_in.dims for x in ["lat", "lon"]): raise RuntimeError("Input files must have lat and lon dimensions") @@ -247,9 +277,10 @@ def main(): """ main() function for calling generate_gdd20_baseline.py from command line. """ - args = _parse_args() + args, time_slice = _parse_args() generate_gdd20_baseline( args.input_files, args.output_file, args.author, + time_slice ) From 23c8743ae67e8b550ee3ba89c10f2a2d17a51c3b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 26 Jun 2024 14:27:22 -0600 Subject: [PATCH 100/160] generate_gdds: Map sdate in/out diffs. --- .../crop_calendars/generate_gdd20_baseline.py | 5 +++- python/ctsm/crop_calendars/generate_gdds.py | 1 + .../crop_calendars/generate_gdds_functions.py | 28 +++++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 4357bd6dd1..476717b887 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -97,7 +97,10 @@ def _parse_args(): raise FileExistsError("Output file exists but --overwrite is not specified") # Process time slice - # Assumes CESM behavior where data for e.g. 1987 is saved as 1988-01-01 + # Assumes CESM behavior where data for e.g. 1987 is saved as 1988-01-01. + # It would be more robust, accounting for upcoming behavior (where timestamp for a year is the + # middle of that year), to do slice("YEAR1-01-03", "YEARN-01-02"), but that's not compatible + # with ctsm_pylib as of the version using python 3.7.9. See safer_timeslice() in cropcal_utils. if args.first_year is not None: date_1 = f"{args.first_year+1}-01-01" else: diff --git a/python/ctsm/crop_calendars/generate_gdds.py b/python/ctsm/crop_calendars/generate_gdds.py index 156ebfb20e..49226e6f75 100644 --- a/python/ctsm/crop_calendars/generate_gdds.py +++ b/python/ctsm/crop_calendars/generate_gdds.py @@ -178,6 +178,7 @@ def main( mxmats, cc.get_gs_len_da, skip_crops, + outdir_figs, logger, ) diff --git a/python/ctsm/crop_calendars/generate_gdds_functions.py b/python/ctsm/crop_calendars/generate_gdds_functions.py index 38c5d44384..2658e1de87 100644 --- a/python/ctsm/crop_calendars/generate_gdds_functions.py +++ b/python/ctsm/crop_calendars/generate_gdds_functions.py @@ -22,6 +22,7 @@ # pylint: disable=import-error from ctsm.crop_calendars.cropcal_figs_module import * from matplotlib.transforms import Bbox + import matplotlib.pyplot as plt warnings.filterwarnings( "ignore", @@ -63,7 +64,7 @@ def error(logger, string): raise RuntimeError(string) -def check_sdates(dates_ds, sdates_rx, logger, verbose=False): +def check_sdates(dates_ds, sdates_rx, outdir_figs, logger, verbose=False): """ Checking that input and output sdates match """ @@ -106,8 +107,28 @@ def check_sdates(dates_ds, sdates_rx, logger, verbose=False): log(logger, out_map_notnan[here][0:4]) log(logger, "diff:") log(logger, diff_map_notnan[here][0:4]) + first_diff = all_ok all_ok = False + if CAN_PLOT: + sdate_diffs_dir = os.path.join(outdir_figs, "sdate_diffs") + if first_diff: + log(logger, f"Saving sdate difference figures to {sdate_diffs_dir}") + if not os.path.exists(sdate_diffs_dir): + os.makedirs(sdate_diffs_dir) + in_map.where(~np.isnan(out_map)).plot() + plt.title(f"{vegtype_str} sdates in (masked)") + plt.savefig(os.path.join(sdate_diffs_dir, f"{vegtype_str}.in.png")) + plt.close() + out_map.plot() + plt.title(f"{vegtype_str} sdates out") + plt.savefig(os.path.join(sdate_diffs_dir, f"{vegtype_str}.out.png")) + plt.close() + diff_map.plot() + plt.title(f"{vegtype_str} sdates diff (out - in)") + plt.savefig(os.path.join(sdate_diffs_dir, f"{vegtype_str}.diff.png")) + plt.close() + if not any_found: error(logger, "No matching variables found in sdates_rx!") @@ -234,6 +255,7 @@ def import_and_process_1yr( mxmats, get_gs_len_da, skip_crops, + outdir_figs, logger, ): """ @@ -272,6 +294,8 @@ def import_and_process_1yr( time_slice=slice(f"{this_year}-01-01", f"{this_year}-12-31"), chunks=chunks, ) + for timestep in dates_ds["time"].values: + print(timestep) if dates_ds.dims["time"] > 1: if dates_ds.dims["time"] == 365: @@ -466,7 +490,7 @@ def import_and_process_1yr( # Import expected sowing dates. This will also be used as our template output file. imported_sdates = isinstance(sdates_rx, str) sdates_rx = import_rx_dates("s", sdates_rx, incl_patches1d_itype_veg, mxsowings, logger) - check_sdates(dates_incl_ds, sdates_rx, logger) + check_sdates(dates_incl_ds, sdates_rx, outdir_figs, logger) # Import hdates, if needed imported_hdates = isinstance(hdates_rx, str) From 7994c82a4427860494c79611fc32fa0efecadeb3 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 11 Jul 2024 16:29:12 -0600 Subject: [PATCH 101/160] Save crop date variables in testmods more frequently. --- cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm | 7 +++++-- .../testdefs/testmods_dirs/clm/cropMonthOutput/user_nl_clm | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm index a368e97593..aea8e39e6c 100644 --- a/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm @@ -9,9 +9,12 @@ hist_fincl2 += 'DYN_COL_SOIL_ADJUSTMENTS_C' -! Annual instantaneous crop variables (including per-sowing/per-harvest axes), per PFT. +! Instantaneous crop variables (including per-sowing/per-harvest axes), per PFT. +! Note that, under normal circumstances, these should only be saved annually. +! That's needed for the mxsowings and mxharvests axes to make sense. +! However, for testing purposes, it makes sense to save more frequently. hist_fincl3 = 'SDATES', 'SDATES_PERHARV', 'SYEARS_PERHARV', 'HDATES', 'GRAINC_TO_FOOD_PERHARV', 'GRAINC_TO_FOOD_ANN', 'GRAINN_TO_FOOD_PERHARV', 'GRAINN_TO_FOOD_ANN', 'GRAINC_TO_SEED_PERHARV', 'GRAINC_TO_SEED_ANN', 'GRAINN_TO_SEED_PERHARV', 'GRAINN_TO_SEED_ANN', 'HDATES', 'GDDHARV_PERHARV', 'GDDACCUM_PERHARV', 'HUI_PERHARV', 'SOWING_REASON_PERHARV', 'HARVEST_REASON_PERHARV', 'SWINDOW_STARTS', 'SWINDOW_ENDS', 'GDD20_BASELINE', 'GDD20_SEASON_START', 'GDD20_SEASON_END' -hist_nhtfrq(3) = 17520 +hist_nhtfrq(3) = -24 hist_mfilt(3) = 1 hist_type1d_pertape(3) = 'PFTS' hist_dov2xy(3) = .false. diff --git a/cime_config/testdefs/testmods_dirs/clm/cropMonthOutput/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/cropMonthOutput/user_nl_clm index 8f779ed011..18220de5ef 100644 --- a/cime_config/testdefs/testmods_dirs/clm/cropMonthOutput/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/cropMonthOutput/user_nl_clm @@ -1,4 +1,4 @@ - hist_nhtfrq = 0,-240,17520 + hist_nhtfrq = 0,-240,0 hist_mfilt = 1,1,1 ! NOTE slevis (2024/2/23) Adding option for tests to pass. In the long term From 014800df4825a909f3a0297951fcd4f8f05f232b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 11 Jul 2024 16:32:25 -0600 Subject: [PATCH 102/160] Add tweak_latlons.py. --- python/ctsm/crop_calendars/tweak_latlons.py | 168 ++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 python/ctsm/crop_calendars/tweak_latlons.py diff --git a/python/ctsm/crop_calendars/tweak_latlons.py b/python/ctsm/crop_calendars/tweak_latlons.py new file mode 100644 index 0000000000..de35ced0d1 --- /dev/null +++ b/python/ctsm/crop_calendars/tweak_latlons.py @@ -0,0 +1,168 @@ +# %% +import numpy as np +import xarray as xr +import os +import sys +from netCDF4 import Dataset +import contextlib + +# -- add python/ctsm to path (needed if we want to run this stand-alone) +_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir) +sys.path.insert(1, _CTSM_PYTHON) +# pylint: disable=wrong-import-position +from ctsm.mesh_maker import main as mesh_maker + +topdir = "/glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/cropdata/calendars/processed/" +file_list_in = [ + "swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc", + "swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc", + "gdds_20230829_161011.nc", +] +file_mesh_in = ( + "/glade/campaign/cesm/cesmdata/inputdata/share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc" +) + +file_list_out = [] +coord_list = ["lat", "lon"] + +# %% + + +def apply_tweak(ds, coord_str, tweak=1e-6): + # Apply tweak + da = ds[coord_str] + coord2 = da.values + coord2 += tweak + while np.any(coord2 == da.values): + tweak *= 10 + coord2 = da.values + coord2 += tweak + coord_tweak = np.full_like(coord2, tweak) + + # Ensure that no value is above maximum in input data. This is needed for mesh_maker to work. + max_coord = np.max(da.values) + where_toohigh = np.where(coord2 > max_coord) + Ntoohigh = len(where_toohigh[0]) + if Ntoohigh != 1: + raise RuntimeError(f"Expected 1 coordinate value too high; got {Ntoohigh}") + coord2[where_toohigh] = max_coord + coord_tweak[where_toohigh] = max_coord + + # Convert to DataArray + new_coords_dict = {coord_str: coord2} + da2 = xr.DataArray( + data=coord2, + coords=new_coords_dict, + dims=da.dims, + attrs=da.attrs, + ) + + # Replace coordinate in dataset + ds[coord_str] = da2 + + # Add a variable with the amount of the tweak + tweak_attrs = {} + if "standard_name" in da: + coord_name = da.attrs["standard_name"] + else: + coord_name = da.attrs["long_name"].replace("coordinate_", "") + tweak_attrs["standard_name"] = coord_name + "_tweak" + tweak_attrs[ + "long_name" + ] = f"Amount {coord_name} was shifted to avoid ambiguous nearest neighbors" + tweak_attrs["units"] = da.attrs["units"] + da_tweak = xr.DataArray( + data=coord_tweak, + coords=new_coords_dict, + dims=da.dims, + attrs=tweak_attrs, + ) + tweak_name = coord_str + "_tweak" + ds[tweak_name] = da_tweak + + return ds + + +# %% Apply tweak to all files + +for filename in file_list_in: + file_in = os.path.join(topdir, filename) + ds = xr.open_dataset(file_in) + + for coord in coord_list: + ds = apply_tweak(ds, coord, tweak=1e-6) + + # Set up for save + file_out = file_in.replace(".nc", ".tweaked_latlons.nc") + with Dataset(file_in, "r") as netcdf_file: + netcdf_format = netcdf_file.data_model + + # Save + print(f"Saving {file_out}") + ds.to_netcdf(file_out, format=netcdf_format) + file_list_out.append(file_out) +print("Done") + + +# %% Ensure all files got the same tweaks + +ds = xr.open_dataset(file_list_out[0]) + +for filename in file_list_out[1:]: + ds2 = xr.open_dataset(filename) + for coord in coord_list: + # Ensure that coordinates are the same + var = coord + assert np.array_equal(ds[var].values, ds2[var].values) + # Ensure that tweaks were the same + var = coord + "_tweak" + assert np.array_equal(ds[var].values, ds2[var].values) +print("All good!") + + +# %% Save new mesh file + +outfile_name = os.path.basename(file_mesh_in) +outfile_name = outfile_name.replace(".nc", ".tweaked_latlons.nc") +outdir = os.path.dirname(file_list_out[0]) +file_mesh_out = os.path.join(outdir, outfile_name) + +@contextlib.contextmanager +def redirect_argv(arglist): + argv_tmp = sys.argv[:] + sys.argv=arglist + yield + sys.argv = argv_tmp + + +mesh_maker_args = [ + "mesh_maker", + "--input", + file_list_out[0], + "--output", + file_mesh_out, + "--lat", + "lat", + "--lon", + "lon", + "--overwrite", +] +print(f"Saving {file_mesh_out}...") +with redirect_argv(mesh_maker_args): + mesh_maker() + +# Change format, if needed +with Dataset(file_mesh_in, "r") as netcdf_file: + netcdf_format_in = netcdf_file.data_model +with Dataset(file_mesh_out, "r") as netcdf_file: + netcdf_format_out = netcdf_file.data_model +if netcdf_format_in != netcdf_format_out: + file_mesh_out_tmp = file_mesh_out + ".tmp" + os.rename(file_mesh_out, file_mesh_out_tmp) + ds = xr.open_dataset(file_mesh_out_tmp) + ds.to_netcdf(file_mesh_out, format=netcdf_format_in) + os.remove(file_mesh_out_tmp) + + +print("Done") +# %% From 4c1d66f88985340b2b95dc909b55587475d75514 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 11 Jul 2024 16:37:20 -0600 Subject: [PATCH 103/160] mesh_type.py: Handle a broadcast error. --- python/ctsm/site_and_regional/mesh_type.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/python/ctsm/site_and_regional/mesh_type.py b/python/ctsm/site_and_regional/mesh_type.py index be785e745d..0260f1c816 100644 --- a/python/ctsm/site_and_regional/mesh_type.py +++ b/python/ctsm/site_and_regional/mesh_type.py @@ -235,8 +235,16 @@ def create_2d_coords(self): lons_size = self.center_lons.size # -- convert center points from 1d to 2d - self.center_lat2d = np.broadcast_to(self.center_lats[:], (lons_size, lats_size)) - self.center_lon2d = np.broadcast_to(self.center_lons[:], (lons_size, lats_size)) + try: + self.center_lat2d = np.broadcast_to(self.center_lats[:], (lons_size, lats_size)) + self.center_lon2d = np.broadcast_to(self.center_lons[:], (lons_size, lats_size)) + except ValueError: + self.center_lat2d = np.broadcast_to( + np.expand_dims(self.center_lats[:], 0), (lons_size, lats_size) + ) + self.center_lon2d = np.broadcast_to( + np.expand_dims(self.center_lons[:], 1), (lons_size, lats_size) + ) elif self.lat_dims == 2: # -- 2D lats and lons dims = self.center_lons.shape From f11003ba4e751e7749ebf2ddfb7a26a73ebb04f7 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 11 Jul 2024 16:37:46 -0600 Subject: [PATCH 104/160] mesh_type.py: Improve error for lon > 360. --- python/ctsm/site_and_regional/mesh_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctsm/site_and_regional/mesh_type.py b/python/ctsm/site_and_regional/mesh_type.py index 0260f1c816..47c0295593 100644 --- a/python/ctsm/site_and_regional/mesh_type.py +++ b/python/ctsm/site_and_regional/mesh_type.py @@ -359,7 +359,7 @@ def calculate_corners(self, unit="degrees"): ) # Longitudes should stay within 0 to 360 if np.any(self.corner_lons > 360.0): - abort("Corners have longitudes greater than 360") + abort(f"Corners have longitudes greater than 360 (max: {np.max(self.corner_lons)})") if np.any(self.corner_lons < 0.0): logger.warning( "Corners have longitudes less than zero -- %s %s", From 231b4ac4372421a714106ec10178416dcb3cb2c6 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 11 Jul 2024 16:44:31 -0600 Subject: [PATCH 105/160] tweak_latlons.py: Add more files. --- python/ctsm/crop_calendars/tweak_latlons.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/ctsm/crop_calendars/tweak_latlons.py b/python/ctsm/crop_calendars/tweak_latlons.py index de35ced0d1..89144a80ae 100644 --- a/python/ctsm/crop_calendars/tweak_latlons.py +++ b/python/ctsm/crop_calendars/tweak_latlons.py @@ -17,6 +17,9 @@ "swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc", "swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc", "gdds_20230829_161011.nc", + "gdd20bl.copied_from.gdds_20230829_161011.v2.nc", + "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc", + "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc", ] file_mesh_in = ( "/glade/campaign/cesm/cesmdata/inputdata/share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc" From 9f186d489d9ce2d3b982cd367756dd43102262a8 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 11 Jul 2024 16:45:50 -0600 Subject: [PATCH 106/160] Use tweaked_latlon cropcal inputs. This avoids different "nearest neighbors" being chosen when using a different number of processors. See https://github.com/orgs/esmf-org/discussions/261 --- bld/namelist_files/namelist_defaults_ctsm.xml | 22 +++++++++---------- .../clm/sowingWindows/user_nl_clm | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index ae9f9dee6a..d1f9d8545c 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -1704,17 +1704,17 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c 2000 -lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc -lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc -lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.nc -share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc -lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc -lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc -lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.nc -lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.v2.nc -lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc -lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc -share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc +lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.v2.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc diff --git a/cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm index 545e5f6ebd..7024e99b96 100644 --- a/cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm @@ -1,5 +1,5 @@ -stream_fldFileName_swindow_start = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc' -stream_fldFileName_swindow_end = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc' -stream_meshfile_cropcal = '$DIN_LOC_ROOT/share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc' +stream_fldFileName_swindow_start = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc' +stream_fldFileName_swindow_end = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc' +stream_meshfile_cropcal = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc' stream_year_first_cropcal_swindows = 2000 stream_year_last_cropcal_swindows = 2000 From 1377d48022c5db5a7352744b7356aa4cce1e8413 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 12 Jul 2024 10:12:26 -0600 Subject: [PATCH 107/160] Fix default stream_meshfile_cropcal. --- bld/namelist_files/namelist_defaults_ctsm.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index d1f9d8545c..3f8bc1f382 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -1707,14 +1707,14 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.tweaked_latlons.nc -lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/360x720_120830_ESMFmesh_c20210507_cdf5.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.v2.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc -lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/360x720_120830_ESMFmesh_c20210507_cdf5.tweaked_latlons.nc From d60dc80d74609096b63c942ca41e0ca38177683b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 12 Jul 2024 10:13:06 -0600 Subject: [PATCH 108/160] Fix reset of sowing_reason_perharv_patch. --- src/biogeochem/CNPhenologyMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biogeochem/CNPhenologyMod.F90 b/src/biogeochem/CNPhenologyMod.F90 index d4f486acb7..d1d3c85b21 100644 --- a/src/biogeochem/CNPhenologyMod.F90 +++ b/src/biogeochem/CNPhenologyMod.F90 @@ -1976,7 +1976,7 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , & cnveg_state_inst%gddmaturity_thisyr(p,s) = -1._r8 crop_inst%gddaccum_thisyr_patch(p,s) = -1._r8 crop_inst%hui_thisyr_patch(p,s) = -1._r8 - crop_inst%sowing_reason_perharv_patch = -1._r8 + crop_inst%sowing_reason_perharv_patch(p,s) = -1._r8 crop_inst%harvest_reason_thisyr_patch(p,s) = -1._r8 do k = repr_grain_min, repr_grain_max cnveg_carbonflux_inst%repr_grainc_to_food_perharv_patch(p,s,k) = 0._r8 From 5c3e71fe608c73c47f8f3e9b8ac6ee4102ce63a5 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 12 Jul 2024 11:46:58 -0600 Subject: [PATCH 109/160] Remove midDecStart-RxCropCalsAdaptGGCMI test from expected fails. --- cime_config/testdefs/ExpectedTestFails.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/cime_config/testdefs/ExpectedTestFails.xml b/cime_config/testdefs/ExpectedTestFails.xml index 0fac518374..03eb6a157d 100644 --- a/cime_config/testdefs/ExpectedTestFails.xml +++ b/cime_config/testdefs/ExpectedTestFails.xml @@ -170,13 +170,6 @@ - - - FAIL - #2593 - - - From dc488c77a715bba7115c92c43948b668c6c38bc0 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 12 Jul 2024 14:46:59 -0600 Subject: [PATCH 110/160] Improve interpolate_gdds.py. --- python/ctsm/crop_calendars/interpolate_gdds.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/python/ctsm/crop_calendars/interpolate_gdds.py b/python/ctsm/crop_calendars/interpolate_gdds.py index 2aa0b79997..809be98826 100755 --- a/python/ctsm/crop_calendars/interpolate_gdds.py +++ b/python/ctsm/crop_calendars/interpolate_gdds.py @@ -64,6 +64,14 @@ def _setup_process_args(): type=str, required=True, ) + parser.add_argument( + "-p", + "--variable-prefix", + help="Interpolate variables whose names start with this string", + type=str, + required=False, + default="gdd1_" + ) parser.add_argument( "--overwrite", help="If output file exists, overwrite it.", @@ -110,8 +118,12 @@ def interpolate_gdds(args): for var in ds_in: # Check variable - if "gdd1_" not in var: - raise RuntimeError(f"Unexpected variable {var} on input file") + if "lat" not in ds_in[var].dims and "lon" not in ds_in[var].dims: + print(f"Skipping variable {var} with dimensions {ds_in[var].dims}") + continue + elif args.variable_prefix not in var: + print(f"Unexpected variable {var} on input file. Skipping.") + continue if args.dry_run: continue From 3db9bf2788141d9eaea0d9f8dd331c96f7d01165 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 12 Jul 2024 15:08:03 -0600 Subject: [PATCH 111/160] tweak_latlons: Include gdd20 files. --- python/ctsm/crop_calendars/tweak_latlons.py | 105 +++++++++++++++----- 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/python/ctsm/crop_calendars/tweak_latlons.py b/python/ctsm/crop_calendars/tweak_latlons.py index 89144a80ae..0031d492e0 100644 --- a/python/ctsm/crop_calendars/tweak_latlons.py +++ b/python/ctsm/crop_calendars/tweak_latlons.py @@ -20,6 +20,8 @@ "gdd20bl.copied_from.gdds_20230829_161011.v2.nc", "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc", "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc", + "/glade/work/samrabin/cropCals_testing_20240626/gdds_20240712_114642_10x15_interpd_halfdeg.nc", + "/glade/work/samrabin/gdd20_baselines/gswp3.10x15_interpd_halfdeg.1980-2009.nc", ] file_mesh_in = ( "/glade/campaign/cesm/cesmdata/inputdata/share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc" @@ -27,19 +29,43 @@ file_list_out = [] coord_list = ["lat", "lon"] +COORD_DATATYPE = np.float64 -# %% - +# %% Define functions -def apply_tweak(ds, coord_str, tweak=1e-6): - # Apply tweak - da = ds[coord_str] - coord2 = da.values +def get_ds(topdir, file_in): + if not os.path.exists(file_in): + file_in = os.path.join(topdir, file_in) + ds = xr.open_dataset(file_in) + return file_in, ds + +def get_tweak(ds_in, coord_str, init_tweak): + """ + Get the tweak that will be applied to all datasets' lat/lon coordinates + """ + da = ds_in[coord_str] + coord2_orig = da.values.astype(COORD_DATATYPE) + coord2 = coord2_orig + tweak = init_tweak coord2 += tweak + + # This is necessary if precision is lower than float64 + max_tweak = 1e-2 while np.any(coord2 == da.values): tweak *= 10 - coord2 = da.values + if tweak > max_tweak: + raise RuntimeError(f"Tweaking by +{max_tweak} failed to 'take'") + coord2 = coord2_orig coord2 += tweak + return tweak + +def apply_tweak(ds_in, coord_str, tweak): + # Apply tweak + da = ds_in[coord_str] + coord2 = da.values.astype(COORD_DATATYPE) + coord2 += tweak + if np.any(coord2 == da.values): + raise RuntimeError('Tweak didn''t "take"') coord_tweak = np.full_like(coord2, tweak) # Ensure that no value is above maximum in input data. This is needed for mesh_maker to work. @@ -61,19 +87,22 @@ def apply_tweak(ds, coord_str, tweak=1e-6): ) # Replace coordinate in dataset - ds[coord_str] = da2 + ds_in[coord_str] = da2 # Add a variable with the amount of the tweak tweak_attrs = {} - if "standard_name" in da: + if "standard_name" in da.attrs: coord_name = da.attrs["standard_name"] - else: + elif "long_name" in da.attrs: coord_name = da.attrs["long_name"].replace("coordinate_", "") + else: + coord_name = coord_str tweak_attrs["standard_name"] = coord_name + "_tweak" tweak_attrs[ "long_name" ] = f"Amount {coord_name} was shifted to avoid ambiguous nearest neighbors" - tweak_attrs["units"] = da.attrs["units"] + if "units" in da.attrs: + tweak_attrs["units"] = da.attrs["units"] da_tweak = xr.DataArray( data=coord_tweak, coords=new_coords_dict, @@ -81,19 +110,48 @@ def apply_tweak(ds, coord_str, tweak=1e-6): attrs=tweak_attrs, ) tweak_name = coord_str + "_tweak" - ds[tweak_name] = da_tweak + ds_in[tweak_name] = da_tweak + + return ds_in + +def check(ds, f0_base, ds2, f_base, var): + if not np.array_equal(ds[var].values, ds2[var].values): + if not np.array_equal(ds[var].shape, ds2[var].shape): + msg = f"{var} shapes differ b/w {f0_base} ({ds[var].shape}) and {f_base} ({ds2[var].shape})" + raise RuntimeError(msg) + max_diff = np.max(np.abs(ds[var].values - ds2[var].values)) + msg = f"{var}s differ between {f0_base} and {f_base}; max = {max_diff}" + type0 = type(ds[var].values[0]) + type2 = type(ds2[var].values[0]) + if type0 != type2: + msg += f"\nTypes also differ: {type0} vs. {type2}" + raise RuntimeError(msg) - return ds +# %% Apply tweak to all files +# Set up empty dicts +tweak_dict = {} +coord_type_dict = {} +for coord in coord_list: + tweak_dict[coord] = -np.inf -# %% Apply tweak to all files +# Get tweaks +for file_in in file_list_in: + file_in, ds = get_ds(topdir, file_in) + for coord in coord_list: + this_tweak = get_tweak(ds, coord, init_tweak=1e-6) + if this_tweak > tweak_dict[coord]: + tweak_dict[coord] = this_tweak +for coord in coord_list: + print(f"Tweaking {coord} by {tweak_dict[coord]}") +print(" ") -for filename in file_list_in: - file_in = os.path.join(topdir, filename) - ds = xr.open_dataset(file_in) +# Apply tweaks +for file_in in file_list_in: + file_in, ds = get_ds(topdir, file_in) for coord in coord_list: - ds = apply_tweak(ds, coord, tweak=1e-6) + ds = apply_tweak(ds, coord, tweak_dict[coord]) # Set up for save file_out = file_in.replace(".nc", ".tweaked_latlons.nc") @@ -110,16 +168,14 @@ def apply_tweak(ds, coord_str, tweak=1e-6): # %% Ensure all files got the same tweaks ds = xr.open_dataset(file_list_out[0]) +f0_base = os.path.basename(file_list_out[0]) for filename in file_list_out[1:]: ds2 = xr.open_dataset(filename) + f_base = os.path.basename(filename) for coord in coord_list: - # Ensure that coordinates are the same - var = coord - assert np.array_equal(ds[var].values, ds2[var].values) - # Ensure that tweaks were the same - var = coord + "_tweak" - assert np.array_equal(ds[var].values, ds2[var].values) + check(ds, f0_base, ds2, f_base, coord) + check(ds, f0_base, ds2, f_base, coord + "_tweak") print("All good!") @@ -168,4 +224,3 @@ def redirect_argv(arglist): print("Done") -# %% From 64302ad0c48b2afd7a074659c6509ae3344a4ac3 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Fri, 12 Jul 2024 16:54:46 -0600 Subject: [PATCH 112/160] Additions/tweaks to CLM testlist. --- cime_config/testdefs/testlist_clm.xml | 44 +++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 40f351ab4a..3fde176440 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3663,6 +3663,8 @@ + + @@ -3673,6 +3675,7 @@ + @@ -3684,6 +3687,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3754,13 +3793,14 @@ - + + @@ -3773,7 +3813,7 @@ - + From 0da6eb5c706217606c1323b2b1a57eb136e32f1a Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 14 Jul 2024 12:01:58 -0600 Subject: [PATCH 113/160] Remove Clm50 RXCROPMATURITY tests. --- cime_config/testdefs/testlist_clm.xml | 37 +-------------------------- 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 3fde176440..86210256ff 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3660,42 +3660,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + From 87d1fa1a4b2a69c2d01898be9d8f9e661af8d163 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 14 Jul 2024 12:43:31 -0600 Subject: [PATCH 114/160] Move tweak_latlons.py to tools/contrib/. This is hopefully not something that will be needed permanently or even in the medium term, so it's not worth cleaning up and unit-/system-testing. --- {python/ctsm/crop_calendars => tools/contrib}/tweak_latlons.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {python/ctsm/crop_calendars => tools/contrib}/tweak_latlons.py (100%) diff --git a/python/ctsm/crop_calendars/tweak_latlons.py b/tools/contrib/tweak_latlons.py similarity index 100% rename from python/ctsm/crop_calendars/tweak_latlons.py rename to tools/contrib/tweak_latlons.py From f6ff13409d58a50299d0d62207e99a12b2c426a6 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 14 Jul 2024 14:07:51 -0600 Subject: [PATCH 115/160] Rework tweak_latlons.py to run from command line. --- tools/contrib/tweak_latlons.py | 285 +++++++++++++++++++-------------- 1 file changed, 164 insertions(+), 121 deletions(-) diff --git a/tools/contrib/tweak_latlons.py b/tools/contrib/tweak_latlons.py index 0031d492e0..2bae06d229 100644 --- a/tools/contrib/tweak_latlons.py +++ b/tools/contrib/tweak_latlons.py @@ -1,44 +1,23 @@ -# %% -import numpy as np -import xarray as xr +""" +'Tweak' the latitude and longitude coordinates to avoid ambiguous nearest neighbors +""" import os import sys -from netCDF4 import Dataset import contextlib +import argparse +import numpy as np +import xarray as xr +from netCDF4 import Dataset # pylint: disable=no-name-in-module # -- add python/ctsm to path (needed if we want to run this stand-alone) -_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir) +_CTSM_PYTHON = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir, "python") sys.path.insert(1, _CTSM_PYTHON) # pylint: disable=wrong-import-position from ctsm.mesh_maker import main as mesh_maker -topdir = "/glade/campaign/cesm/cesmdata/inputdata/lnd/clm2/cropdata/calendars/processed/" -file_list_in = [ - "swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc", - "swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.nc", - "gdds_20230829_161011.nc", - "gdd20bl.copied_from.gdds_20230829_161011.v2.nc", - "sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc", - "hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.nc", - "/glade/work/samrabin/cropCals_testing_20240626/gdds_20240712_114642_10x15_interpd_halfdeg.nc", - "/glade/work/samrabin/gdd20_baselines/gswp3.10x15_interpd_halfdeg.1980-2009.nc", -] -file_mesh_in = ( - "/glade/campaign/cesm/cesmdata/inputdata/share/meshes/360x720_120830_ESMFmesh_c20210507_cdf5.nc" -) - -file_list_out = [] -coord_list = ["lat", "lon"] +COORD_LIST = ["lat", "lon"] COORD_DATATYPE = np.float64 -# %% Define functions - -def get_ds(topdir, file_in): - if not os.path.exists(file_in): - file_in = os.path.join(topdir, file_in) - ds = xr.open_dataset(file_in) - return file_in, ds - def get_tweak(ds_in, coord_str, init_tweak): """ Get the tweak that will be applied to all datasets' lat/lon coordinates @@ -73,7 +52,9 @@ def apply_tweak(ds_in, coord_str, tweak): where_toohigh = np.where(coord2 > max_coord) Ntoohigh = len(where_toohigh[0]) if Ntoohigh != 1: - raise RuntimeError(f"Expected 1 coordinate value too high; got {Ntoohigh}") + raise RuntimeError( + f"Expected 1 coordinate value too high; got {Ntoohigh}" + ) coord2[where_toohigh] = max_coord coord_tweak[where_toohigh] = max_coord @@ -127,100 +108,162 @@ def check(ds, f0_base, ds2, f_base, var): msg += f"\nTypes also differ: {type0} vs. {type2}" raise RuntimeError(msg) -# %% Apply tweak to all files - -# Set up empty dicts -tweak_dict = {} -coord_type_dict = {} -for coord in coord_list: - tweak_dict[coord] = -np.inf - -# Get tweaks -for file_in in file_list_in: - file_in, ds = get_ds(topdir, file_in) - for coord in coord_list: - this_tweak = get_tweak(ds, coord, init_tweak=1e-6) - if this_tweak > tweak_dict[coord]: - tweak_dict[coord] = this_tweak -for coord in coord_list: - print(f"Tweaking {coord} by {tweak_dict[coord]}") -print(" ") - -# Apply tweaks -for file_in in file_list_in: - file_in, ds = get_ds(topdir, file_in) - - for coord in coord_list: - ds = apply_tweak(ds, coord, tweak_dict[coord]) - - # Set up for save - file_out = file_in.replace(".nc", ".tweaked_latlons.nc") - with Dataset(file_in, "r") as netcdf_file: - netcdf_format = netcdf_file.data_model - - # Save - print(f"Saving {file_out}") - ds.to_netcdf(file_out, format=netcdf_format) - file_list_out.append(file_out) -print("Done") - - -# %% Ensure all files got the same tweaks - -ds = xr.open_dataset(file_list_out[0]) -f0_base = os.path.basename(file_list_out[0]) - -for filename in file_list_out[1:]: - ds2 = xr.open_dataset(filename) - f_base = os.path.basename(filename) - for coord in coord_list: - check(ds, f0_base, ds2, f_base, coord) - check(ds, f0_base, ds2, f_base, coord + "_tweak") -print("All good!") - - -# %% Save new mesh file - -outfile_name = os.path.basename(file_mesh_in) -outfile_name = outfile_name.replace(".nc", ".tweaked_latlons.nc") -outdir = os.path.dirname(file_list_out[0]) -file_mesh_out = os.path.join(outdir, outfile_name) - @contextlib.contextmanager def redirect_argv(arglist): + """ + Preserve actual arg list while giving a new one to mesh_maker + """ argv_tmp = sys.argv[:] - sys.argv=arglist + sys.argv = arglist yield sys.argv = argv_tmp +def main(input_files, mesh_file_in, output_files): + """ + Apply tweak to all files + """ + + # Set up + tweak_dict = {} + for coord in COORD_LIST: + tweak_dict[coord] = -np.inf + mesh_file_out = output_files[-1] + output_files = output_files[:-1] + + # Get tweaks + for file_in in input_files: + ds = xr.open_dataset(file_in) + for coord in COORD_LIST: + this_tweak = get_tweak(ds, coord, init_tweak=1e-6) + if this_tweak > tweak_dict[coord]: + tweak_dict[coord] = this_tweak + for coord in COORD_LIST: + print(f"Tweaking {coord} by {tweak_dict[coord]}") + print(" ") + + # Apply tweaks + for i, file_in in enumerate(input_files): + ds = xr.open_dataset(file_in) + + for coord in COORD_LIST: + ds = apply_tweak(ds, coord, tweak_dict[coord]) + + # Set up for save + file_out = output_files[i] + with Dataset(file_in, "r") as netcdf_file: + netcdf_format = netcdf_file.data_model + + # Make output dir, if needed + output_dir = os.path.dirname(file_out) + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Save + print(f"Saving {file_out}") + ds.to_netcdf(file_out, format=netcdf_format) + print("Done") + + + # Ensure all files got the same tweaks + ds = xr.open_dataset(output_files[0]) + f0_base = os.path.basename(output_files[0]) + for file_out in output_files[1:]: + ds2 = xr.open_dataset(file_out) + f_base = os.path.basename(file_out) + for coord in COORD_LIST: + check(ds, f0_base, ds2, f_base, coord) + check(ds, f0_base, ds2, f_base, coord + "_tweak") + + + # Save new mesh file + mesh_maker_args = [ + "mesh_maker", + "--input", + output_files[0], + "--output", + mesh_file_out, + "--lat", + "lat", + "--lon", + "lon", + "--overwrite", + ] + print(f"Saving {mesh_file_out}...") + with redirect_argv(mesh_maker_args): + mesh_maker() + + # Change format, if needed + with Dataset(mesh_file_in, "r") as netcdf_file: + netcdf_format_in = netcdf_file.data_model + with Dataset(mesh_file_out, "r") as netcdf_file: + netcdf_format_out = netcdf_file.data_model + if netcdf_format_in != netcdf_format_out: + mesh_file_out_tmp = mesh_file_out + ".tmp" + os.rename(mesh_file_out, mesh_file_out_tmp) + ds = xr.open_dataset(mesh_file_out_tmp) + ds.to_netcdf(mesh_file_out, format=netcdf_format_in) + os.remove(mesh_file_out_tmp) + + print("Done") + + + + +if __name__ == "__main__": + ############################### + ### Process input arguments ### + ############################### + parser = argparse.ArgumentParser( + description="'Tweak' the latitude and longitude coordinates to avoid ambiguous nearest neighbors", + ) + + # Required + parser.add_argument( + "-i", + "--input-files", + help="Comma-separated stream files whose coordinates need tweaking", + required=True, + ) + parser.add_argument( + "-m", + "--mesh-file", + help="Mesh file associated with input files", + required=True, + ) + + # Optional + parser.add_argument( + "--overwrite", + help="Overwrite any existing output files", + action="store_true", + default=False, + ) + default_output_dir = os.getcwd() + parser.add_argument( + "-o", + "--output-dir", + help=f"Directory where output files should be saved. Default is current working directory: {default_output_dir}", + default=default_output_dir, + ) -mesh_maker_args = [ - "mesh_maker", - "--input", - file_list_out[0], - "--output", - file_mesh_out, - "--lat", - "lat", - "--lon", - "lon", - "--overwrite", -] -print(f"Saving {file_mesh_out}...") -with redirect_argv(mesh_maker_args): - mesh_maker() - -# Change format, if needed -with Dataset(file_mesh_in, "r") as netcdf_file: - netcdf_format_in = netcdf_file.data_model -with Dataset(file_mesh_out, "r") as netcdf_file: - netcdf_format_out = netcdf_file.data_model -if netcdf_format_in != netcdf_format_out: - file_mesh_out_tmp = file_mesh_out + ".tmp" - os.rename(file_mesh_out, file_mesh_out_tmp) - ds = xr.open_dataset(file_mesh_out_tmp) - ds.to_netcdf(file_mesh_out, format=netcdf_format_in) - os.remove(file_mesh_out_tmp) - - -print("Done") + # Get arguments + args = parser.parse_args(sys.argv[1:]) + + # Check/process input and output files + _input_files = args.input_files.split(",") + _output_files = [] + for file in _input_files + [args.mesh_file]: + if not os.path.exists(file): + raise FileNotFoundError(f"File not found: {file}") + + filename, ext = os.path.splitext(os.path.basename(file)) + output_file = os.path.join( + args.output_dir, filename + ".tweaked_latlons" + ext + ) + if os.path.exists(output_file) and not args.overwrite: + raise FileExistsError( + f"Output file exists but --overwrite not specified: {output_file}" + ) + _output_files.append(output_file) + + main(_input_files, args.mesh_file, _output_files) From 227426345fcd0bd552e755b5c4cceb503cea94c6 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Sun, 14 Jul 2024 14:28:50 -0600 Subject: [PATCH 116/160] Refine RxCropCals* testlist. --- cime_config/testdefs/testlist_clm.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index 86210256ff..c2926a25e7 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3755,7 +3755,7 @@ - + @@ -3765,7 +3765,8 @@ - + + @@ -3782,6 +3783,17 @@ + + + + + + + + + + + From e14313858df6e9e6f2488951af2551349e349c65 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 10:11:33 -0600 Subject: [PATCH 117/160] generate_gdd20_baseline: Clarity rearrangement. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 476717b887..5d4fac3aff 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -246,16 +246,18 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice): if gddn is None: # Crop not handled yet? Fill it entirely with missing value this_da = dummy_da - long_name = "Dummy GDD20" print(" dummy GDD20") else: # this_da = ds_in[gddn].fillna(MISSING_FILL) this_da = ds_in[gddn] this_da = _add_time_axis(this_da) - long_name = gddn.replace("X", "20") print(f" {gddn}") - # Add attributes + # Add attributes of output file + if gddn is None: + long_name = "Dummy GDD20" + else: + long_name = gddn.replace("X", "20") this_da.attrs["long_name"] = long_name + f" baseline for {cft_str}" this_da.attrs["units"] = "°C days" # this_da.attrs["_FillValue"] = MISSING_FILL From 647fc52df1eb32b10646eb54ed4240d4cc9cfc00 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 11:07:00 -0600 Subject: [PATCH 118/160] import_ds: Workaround to import multiple files without dask. --- python/ctsm/crop_calendars/import_ds.py | 53 +++++++++++++++++-------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/python/ctsm/crop_calendars/import_ds.py b/python/ctsm/crop_calendars/import_ds.py index 77a22b626b..0526d3c720 100644 --- a/python/ctsm/crop_calendars/import_ds.py +++ b/python/ctsm/crop_calendars/import_ds.py @@ -41,6 +41,29 @@ def compute_derived_vars(ds_in, var): return ds_in +def manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice): + """ + Opening a list of files with Xarray's open_mfdataset requires dask. This function is a + workaround for Python environments that don't have dask. + """ + ds_out = None + for filename in filelist: + ds_in = xr.open_dataset(filename) + ds_in = mfdataset_preproc(ds_in, my_vars, my_vegtypes, time_slice) + if ds_out is None: + ds_out = ds_in + else: + ds_out = xr.concat( + [ds_out, ds_in], + data_vars="minimal", + compat="override", + coords="all", + dim="time", + # combine="nested", + ) + return ds_out + + def mfdataset_preproc(ds_in, vars_to_import, vegtypes_to_import, time_slice): """ Function to drop unwanted variables in preprocessing of open_mfdataset(). @@ -221,22 +244,20 @@ def import_ds( if isinstance(filelist, list): with warnings.catch_warnings(): warnings.filterwarnings(action="ignore", category=DeprecationWarning) - if find_spec("dask") is None: - raise ModuleNotFoundError( - "You have asked xarray to import a list of files as a single Dataset using" - " open_mfdataset(), but this requires dask, which is not available.\nFile" - f" list: {filelist}" - ) - this_ds = xr.open_mfdataset( - sorted(filelist), - data_vars="minimal", - preprocess=mfdataset_preproc_closure, - compat="override", - coords="all", - concat_dim="time", - combine="nested", - chunks=chunks, - ) + dask_unavailable = find_spec("dask") is None + if dask_unavailable: + this_ds = manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice) + else: + this_ds = xr.open_mfdataset( + sorted(filelist), + data_vars="minimal", + preprocess=mfdataset_preproc_closure, + compat="override", + coords="all", + concat_dim="time", + combine="nested", + chunks=chunks, + ) elif isinstance(filelist, str): this_ds = xr.open_dataset(filelist, chunks=chunks) this_ds = mfdataset_preproc(this_ds, my_vars, my_vegtypes, time_slice) From 96015dacbc7a22d57bcd242e358356f5c0cb028b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 11:11:52 -0600 Subject: [PATCH 119/160] generate_gdd20_baseline: Can now specify -v/--variable GDDBX or GDDB20. --- .../crop_calendars/generate_gdd20_baseline.py | 64 +++++++++++++------ 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 5d4fac3aff..4b8e68e8f9 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -19,7 +19,6 @@ import ctsm.crop_calendars.cropcal_utils as utils from ctsm.crop_calendars.grid_one_variable import grid_one_variable -VAR_LIST_IN = ["GDD0X", "GDD8X", "GDD10X"] GRIDDING_VAR_LIST = ["patches1d_ixy", "patches1d_jxy", "lat", "lon"] MISSING_FILL = -1 # Something impossible to ensure that you can mark it as a missing value, to be # bilinear-interpolated @@ -88,6 +87,16 @@ def _parse_args(): type=int, required=False, ) + parser.add_argument( + "-v", + "--variable", + help=( + "Which type of variable should be processed?" + ), + required=False, + default="GDDBX", + choices=["GDDBX", "GDDB20"], + ) # Get arguments args = parser.parse_args(sys.argv[1:]) @@ -96,6 +105,12 @@ def _parse_args(): if os.path.exists(args.output_file) and not args.overwrite: raise FileExistsError("Output file exists but --overwrite is not specified") + # Get and check input files + args.input_files = args.input_files.split(" ") + for filename in args.input_files: + if not os.path.exists(filename): + raise FileNotFoundError(f"Input file not found: {filename}") + # Process time slice # Assumes CESM behavior where data for e.g. 1987 is saved as 1988-01-01. # It would be more robust, accounting for upcoming behavior (where timestamp for a year is the @@ -137,7 +152,7 @@ def _get_cft_list(crop_list): return cft_str_list -def _get_gddn_for_cft(cft_str): +def _get_gddn_for_cft(cft_str, variable): """ Given a CFT name, return the GDDN variable it uses. @@ -149,6 +164,7 @@ def _get_gddn_for_cft(cft_str): """ gddn = None + gddn_str = None gdd0_list_str = ["wheat", "cotton", "rice"] if cft_str in _get_cft_list(gdd0_list_str): @@ -163,9 +179,9 @@ def _get_gddn_for_cft(cft_str): gddn = 10 if gddn is not None: - gddn = f"GDD{gddn}X" + gddn_str = variable.replace("B", str(gddn)) - return gddn + return gddn, gddn_str def _get_output_varname(cft_str): @@ -194,19 +210,28 @@ def _add_time_axis(da_in): return da_out -def generate_gdd20_baseline(input_files, output_file, author, time_slice): +def generate_gdd20_baseline(input_files, output_file, author, time_slice, variable): """ Generate stream_fldFileName_gdd20_baseline file from CTSM outputs """ - # Get input file list - input_files = input_files.split(sep=" ") + # Define variables to process + if variable == "GDDBX": + suffix = "X" + elif variable == "GDDB20": + suffix = "20" + else: + raise ValueError(f"-v/--variable {variable} not recoginzed") + var_list_in = [] + for base_temp in [0, 8, 10]: + var_list_in.append(f"GDD{base_temp}{suffix}") + # Get unique values and sort input_files = list(set(input_files)) input_files.sort() # Import history files and ensure they have lat/lon dims - ds_in = import_ds(input_files, VAR_LIST_IN + GRIDDING_VAR_LIST, time_slice=time_slice) + ds_in = import_ds(input_files, var_list_in + GRIDDING_VAR_LIST, time_slice=time_slice) if not all(x in ds_in.dims for x in ["lat", "lon"]): raise RuntimeError("Input files must have lat and lon dimensions") @@ -216,9 +241,9 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice): # Set up a dummy DataArray to use for crops without an assigned GDDN variable dummy_da = xr.DataArray( - data=MISSING_FILL * np.ones_like(ds_in[VAR_LIST_IN[0]].values), - dims=ds_in[VAR_LIST_IN[0]].dims, - coords=ds_in[VAR_LIST_IN[0]].coords, + data=MISSING_FILL * np.ones_like(ds_in[var_list_in[0]].values), + dims=ds_in[var_list_in[0]].dims, + coords=ds_in[var_list_in[0]].coords, ) dummy_da = _add_time_axis(dummy_da) @@ -239,25 +264,27 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice): print(f"{cft_str} ({cft_int})") # Which GDDN history variable does this crop use? E.g., GDD0, GDD10 - gddn = _get_gddn_for_cft(cft_str) + gddn, gddn_str = _get_gddn_for_cft(cft_str, variable) # Fill any missing values with MISSING_FILL. This will mean that gddmaturity in these cells # never changes. - if gddn is None: + if gddn_str is None: # Crop not handled yet? Fill it entirely with missing value this_da = dummy_da print(" dummy GDD20") else: # this_da = ds_in[gddn].fillna(MISSING_FILL) - this_da = ds_in[gddn] + this_da = ds_in[gddn_str] this_da = _add_time_axis(this_da) - print(f" {gddn}") + print(f" {gddn_str}") # Add attributes of output file - if gddn is None: + if (gddn is None) != (gddn_str is None): + raise RuntimeError("gddn and gddn_str must either both be None or both be not None") + if gddn_str is None: long_name = "Dummy GDD20" else: - long_name = gddn.replace("X", "20") + long_name = f"GDD{gddn}20" this_da.attrs["long_name"] = long_name + f" baseline for {cft_str}" this_da.attrs["units"] = "°C days" # this_da.attrs["_FillValue"] = MISSING_FILL @@ -287,5 +314,6 @@ def main(): args.input_files, args.output_file, args.author, - time_slice + time_slice, + args.variable, ) From 3cc1d0fafb3290c1b249d22f828429c92a3719c4 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 11:14:33 -0600 Subject: [PATCH 120/160] generate_gdd20_baseline: Satisfy pylint. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 4b8e68e8f9..d2f96965b7 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -160,7 +160,7 @@ def _get_gddn_for_cft(cft_str, variable): cft_str (str): E.g., "irrigated_temperate_corn" Returns: - str or None: Name of variable to use (e.g., "GDD8X"). If crop isn't yet handled, return None. + str or None: Name of variable to use (e.g., "GDD8X"). If crop not yet handled, return None. """ gddn = None @@ -249,8 +249,8 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab # Process all crops data_var_dict = {} - for v in GRIDDING_VAR_LIST: - data_var_dict[v] = ds_in[v] + for gridding_var in GRIDDING_VAR_LIST: + data_var_dict[gridding_var] = ds_in[gridding_var] ds_out = xr.Dataset( data_vars=data_var_dict, attrs={ From b1bbe15eb03a57a26eb010a850a285fd600dfe3b Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 11:27:10 -0600 Subject: [PATCH 121/160] generate_gdd20_baseline: Save input file list as attribute(s). --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index d2f96965b7..b03d1fd658 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -247,7 +247,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab ) dummy_da = _add_time_axis(dummy_da) - # Process all crops + # Set up output Dataset data_var_dict = {} for gridding_var in GRIDDING_VAR_LIST: data_var_dict[gridding_var] = ds_in[gridding_var] @@ -258,6 +258,14 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab "created": dt.datetime.now().astimezone().isoformat(), }, ) + all_files_in_same_dir = len(np.unique([os.path.dirname(file) for file in input_files])) == 1 + if all_files_in_same_dir: + ds_out.attrs["input_files_dir"] = os.path.dirname(input_files[0]) + ds_out.attrs["input_files"] = ", ".join([os.path.basename(file) for file in input_files]) + else: + ds_out.attrs["input_files"] = ", ".join(input_files) + + # Process all crops encoding_dict = {} for cft_str in utils.define_mgdcrop_list(): cft_int = utils.vegtype_str2int(cft_str)[0] From a703cb6549576c63cedde68756292fab630543a5 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 11:31:46 -0600 Subject: [PATCH 122/160] generate_gdd20_baseline: Save input year range as attribute. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index b03d1fd658..14e2d5974f 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -210,7 +210,7 @@ def _add_time_axis(da_in): return da_out -def generate_gdd20_baseline(input_files, output_file, author, time_slice, variable): +def generate_gdd20_baseline(input_files, output_file, author, time_slice, variable, year_args): """ Generate stream_fldFileName_gdd20_baseline file from CTSM outputs """ @@ -256,6 +256,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab attrs={ "author": author, "created": dt.datetime.now().astimezone().isoformat(), + "input_year_range": f"{year_args[0]}-{year_args[1]}", }, ) all_files_in_same_dir = len(np.unique([os.path.dirname(file) for file in input_files])) == 1 @@ -324,4 +325,5 @@ def main(): args.author, time_slice, args.variable, + [args.first_year, args.last_year], ) From 7a5e067dd09970d74708ed9c678f1de7d8f29af6 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 11:32:33 -0600 Subject: [PATCH 123/160] generate_gdd20_baseline: Save input variable type as attribute. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 14e2d5974f..a87a2edb79 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -257,6 +257,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab "author": author, "created": dt.datetime.now().astimezone().isoformat(), "input_year_range": f"{year_args[0]}-{year_args[1]}", + "input_variable": variable, }, ) all_files_in_same_dir = len(np.unique([os.path.dirname(file) for file in input_files])) == 1 From 25896e460ac556a5cef78201f55b8f04fc6ce154 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 11:34:19 -0600 Subject: [PATCH 124/160] generate_gdd20_baseline: Functionize setup_output_dataset(). --- .../crop_calendars/generate_gdd20_baseline.py | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index a87a2edb79..5d6809f7ff 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -210,6 +210,31 @@ def _add_time_axis(da_in): return da_out +def setup_output_dataset(input_files, author, variable, year_args, ds_in): + """ + Set up output Dataset + """ + data_var_dict = {} + for gridding_var in GRIDDING_VAR_LIST: + data_var_dict[gridding_var] = ds_in[gridding_var] + ds_out = xr.Dataset( + data_vars=data_var_dict, + attrs={ + "author": author, + "created": dt.datetime.now().astimezone().isoformat(), + "input_year_range": f"{year_args[0]}-{year_args[1]}", + "input_variable": variable, + }, + ) + all_files_in_same_dir = len(np.unique([os.path.dirname(file) for file in input_files])) == 1 + if all_files_in_same_dir: + ds_out.attrs["input_files_dir"] = os.path.dirname(input_files[0]) + ds_out.attrs["input_files"] = ", ".join([os.path.basename(file) for file in input_files]) + else: + ds_out.attrs["input_files"] = ", ".join(input_files) + return ds_out + + def generate_gdd20_baseline(input_files, output_file, author, time_slice, variable, year_args): """ Generate stream_fldFileName_gdd20_baseline file from CTSM outputs @@ -248,24 +273,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab dummy_da = _add_time_axis(dummy_da) # Set up output Dataset - data_var_dict = {} - for gridding_var in GRIDDING_VAR_LIST: - data_var_dict[gridding_var] = ds_in[gridding_var] - ds_out = xr.Dataset( - data_vars=data_var_dict, - attrs={ - "author": author, - "created": dt.datetime.now().astimezone().isoformat(), - "input_year_range": f"{year_args[0]}-{year_args[1]}", - "input_variable": variable, - }, - ) - all_files_in_same_dir = len(np.unique([os.path.dirname(file) for file in input_files])) == 1 - if all_files_in_same_dir: - ds_out.attrs["input_files_dir"] = os.path.dirname(input_files[0]) - ds_out.attrs["input_files"] = ", ".join([os.path.basename(file) for file in input_files]) - else: - ds_out.attrs["input_files"] = ", ".join(input_files) + ds_out = setup_output_dataset(input_files, author, variable, year_args, ds_in) # Process all crops encoding_dict = {} From f909d2da504328459dd8129df708e289dfe33de3 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 13:15:49 -0600 Subject: [PATCH 125/160] Fill all land cells with MISSING_RX_GDD_VAL. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 5d6809f7ff..c0c9adb30b 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -18,10 +18,9 @@ from ctsm.crop_calendars.import_ds import import_ds import ctsm.crop_calendars.cropcal_utils as utils from ctsm.crop_calendars.grid_one_variable import grid_one_variable +from ctsm.crop_calendars.cropcal_module import MISSING_RX_GDD_VAL GRIDDING_VAR_LIST = ["patches1d_ixy", "patches1d_jxy", "lat", "lon"] -MISSING_FILL = -1 # Something impossible to ensure that you can mark it as a missing value, to be -# bilinear-interpolated STREAM_YEAR = 2000 # The year specified for stream_yearFirst and stream_yearLast in the call of # shr_strdata_init_from_inline() for sdat_cropcal_gdd20_baseline @@ -266,7 +265,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab # Set up a dummy DataArray to use for crops without an assigned GDDN variable dummy_da = xr.DataArray( - data=MISSING_FILL * np.ones_like(ds_in[var_list_in[0]].values), + data=np.full_like(ds_in[var_list_in[0]].values, MISSING_RX_GDD_VAL), dims=ds_in[var_list_in[0]].dims, coords=ds_in[var_list_in[0]].coords, ) @@ -284,10 +283,10 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab # Which GDDN history variable does this crop use? E.g., GDD0, GDD10 gddn, gddn_str = _get_gddn_for_cft(cft_str, variable) - # Fill any missing values with MISSING_FILL. This will mean that gddmaturity in these cells + # Fill any missing values with MISSING_RX_GDD_VAL. This will mean that gddmaturity there # never changes. if gddn_str is None: - # Crop not handled yet? Fill it entirely with missing value + # Crop not handled yet? It's already filled with missing value this_da = dummy_da print(" dummy GDD20") else: @@ -295,6 +294,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab this_da = ds_in[gddn_str] this_da = _add_time_axis(this_da) print(f" {gddn_str}") + this_da = this_da.fillna(MISSING_RX_GDD_VAL) # Add attributes of output file if (gddn is None) != (gddn_str is None): @@ -305,7 +305,6 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab long_name = f"GDD{gddn}20" this_da.attrs["long_name"] = long_name + f" baseline for {cft_str}" this_da.attrs["units"] = "°C days" - # this_da.attrs["_FillValue"] = MISSING_FILL # Copy that to ds_out var_out = _get_output_varname(cft_str) From 8fa8506ceb753e6e1571f4b93adb4c43ea51d8d1 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 13:39:15 -0600 Subject: [PATCH 126/160] Set different gdd20 baseline file if using default gdd20 baseline seasons. --- bld/namelist_files/namelist_defaults_ctsm.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bld/namelist_files/namelist_defaults_ctsm.xml b/bld/namelist_files/namelist_defaults_ctsm.xml index 3f8bc1f382..70ff048aa7 100644 --- a/bld/namelist_files/namelist_defaults_ctsm.xml +++ b/bld/namelist_files/namelist_defaults_ctsm.xml @@ -1711,7 +1711,8 @@ lnd/clm2/surfdata_esmf/NEON/surfdata_1x1_NEON_TOOL_hist_78pfts_CMIP6_simyr2000_c lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/gdds_20230829_161011.tweaked_latlons.nc -lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.v2.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/20230714_cropcals_pr2_1deg.actually2deg.1980-2009.from_GDDB20.interpd_halfdeg.tweaked_latlons.nc +lnd/clm2/cropdata/calendars/processed/gdd20bl.copied_from.gdds_20230829_161011.v2.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/hdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc lnd/clm2/cropdata/calendars/processed/360x720_120830_ESMFmesh_c20210507_cdf5.tweaked_latlons.nc From 945c8bbd142d0c528cbddbafd529a790a88c2dd8 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 14:34:03 -0600 Subject: [PATCH 127/160] Add utility function to get list of managed crops WITH grasses. define_mgdcrop_list() had excluded foddergrass and switchgrass. New function define_mgdcrop_list_withgrasses includes them. Renamed the existing function to define_mgdcrop_list_nograsses for clarity. --- python/ctsm/crop_calendars/cropcal_module.py | 2 +- python/ctsm/crop_calendars/cropcal_utils.py | 11 ++++++++++- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 4 ++-- python/ctsm/crop_calendars/generate_gdds_functions.py | 4 ++-- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/python/ctsm/crop_calendars/cropcal_module.py b/python/ctsm/crop_calendars/cropcal_module.py index b87d26816f..08754f8823 100644 --- a/python/ctsm/crop_calendars/cropcal_module.py +++ b/python/ctsm/crop_calendars/cropcal_module.py @@ -345,7 +345,7 @@ def import_output( my_vars, year_1=None, year_n=None, - my_vegtypes=utils.define_mgdcrop_list(), + my_vegtypes=utils.define_mgdcrop_list_nograsses(), sdates_rx_ds=None, gdds_rx_ds=None, verbose=False, diff --git a/python/ctsm/crop_calendars/cropcal_utils.py b/python/ctsm/crop_calendars/cropcal_utils.py index 00ed2413d2..2157dc87f4 100644 --- a/python/ctsm/crop_calendars/cropcal_utils.py +++ b/python/ctsm/crop_calendars/cropcal_utils.py @@ -207,7 +207,7 @@ def is_each_vegtype(this_vegtypelist, this_filter, this_method): return [is_this_vegtype(x, this_filter, this_method) for x in this_vegtypelist] -def define_mgdcrop_list(): +def define_mgdcrop_list_nograsses(): """ List (strings) of managed crops in CLM. """ @@ -216,6 +216,15 @@ def define_mgdcrop_list(): is_crop = is_each_vegtype(defined_pftlist, notcrop_list, "notok_contains") return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] +def define_mgdcrop_list_withgrasses(): + """ + List (strings) of managed crops in CLM. + """ + notcrop_list = ["tree", "c3_arctic_grass", "c3_non-arctic_grass", "c4_grass", "shrub", "unmanaged", "not_vegetated"] + defined_pftlist = define_pftlist() + is_crop = is_each_vegtype(defined_pftlist, notcrop_list, "notok_contains") + return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] + def vegtype_str2int(vegtype_str, vegtype_mainlist=None): """ diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index c0c9adb30b..64cfd804fa 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -144,7 +144,7 @@ def _get_cft_list(crop_list): "cotton", "irrigated_cotton"] """ - mgdcrop_list = utils.define_mgdcrop_list() + mgdcrop_list = utils.define_mgdcrop_list_nograsses() cft_str_list = [] for crop_str in crop_list: cft_str_list += [x for x in mgdcrop_list if crop_str in x] @@ -276,7 +276,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab # Process all crops encoding_dict = {} - for cft_str in utils.define_mgdcrop_list(): + for cft_str in utils.define_mgdcrop_list_nograsses(): cft_int = utils.vegtype_str2int(cft_str)[0] print(f"{cft_str} ({cft_int})") diff --git a/python/ctsm/crop_calendars/generate_gdds_functions.py b/python/ctsm/crop_calendars/generate_gdds_functions.py index 2658e1de87..83c167af00 100644 --- a/python/ctsm/crop_calendars/generate_gdds_functions.py +++ b/python/ctsm/crop_calendars/generate_gdds_functions.py @@ -282,9 +282,9 @@ def import_and_process_1yr( # Get list of crops to include if skip_crops is not None: - crops_to_read = [c for c in utils.define_mgdcrop_list() if c not in skip_crops] + crops_to_read = [c for c in utils.define_mgdcrop_list_nograsses() if c not in skip_crops] else: - crops_to_read = utils.define_mgdcrop_list() + crops_to_read = utils.define_mgdcrop_list_nograsses() print(h1_filelist) dates_ds = import_ds( From ec9f07f2a6cc5f79e6c207e8050db74ff2d857ea Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 14:36:41 -0600 Subject: [PATCH 128/160] generate_gdd20_baseline: Use MGDCROP_LIST constant. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 64cfd804fa..1de6b5a739 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -23,6 +23,7 @@ GRIDDING_VAR_LIST = ["patches1d_ixy", "patches1d_jxy", "lat", "lon"] STREAM_YEAR = 2000 # The year specified for stream_yearFirst and stream_yearLast in the call of # shr_strdata_init_from_inline() for sdat_cropcal_gdd20_baseline +MGDCROP_LIST = utils.define_mgdcrop_list_nograsses() def _parse_args(): @@ -144,10 +145,9 @@ def _get_cft_list(crop_list): "cotton", "irrigated_cotton"] """ - mgdcrop_list = utils.define_mgdcrop_list_nograsses() cft_str_list = [] for crop_str in crop_list: - cft_str_list += [x for x in mgdcrop_list if crop_str in x] + cft_str_list += [x for x in MGDCROP_LIST if crop_str in x] return cft_str_list @@ -276,7 +276,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab # Process all crops encoding_dict = {} - for cft_str in utils.define_mgdcrop_list_nograsses(): + for cft_str in MGDCROP_LIST: cft_int = utils.vegtype_str2int(cft_str)[0] print(f"{cft_str} ({cft_int})") From 669f227921e1bdf59157cf1da1c9b4e5176851bc Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 14:37:05 -0600 Subject: [PATCH 129/160] generate_gdd20_baseline: Include mgd grasses. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 1de6b5a739..effc8e54ec 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -23,7 +23,7 @@ GRIDDING_VAR_LIST = ["patches1d_ixy", "patches1d_jxy", "lat", "lon"] STREAM_YEAR = 2000 # The year specified for stream_yearFirst and stream_yearLast in the call of # shr_strdata_init_from_inline() for sdat_cropcal_gdd20_baseline -MGDCROP_LIST = utils.define_mgdcrop_list_nograsses() +MGDCROP_LIST = utils.define_mgdcrop_list_withgrasses() def _parse_args(): From a62460642403d827b58807cfe5961df0db026c71 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 18 Jul 2024 14:41:46 -0600 Subject: [PATCH 130/160] generate_gdd20_baseline: Include unmanaged crops. --- python/ctsm/crop_calendars/cropcal_utils.py | 8 ++++++++ python/ctsm/crop_calendars/generate_gdd20_baseline.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/cropcal_utils.py b/python/ctsm/crop_calendars/cropcal_utils.py index 2157dc87f4..1a80448c1b 100644 --- a/python/ctsm/crop_calendars/cropcal_utils.py +++ b/python/ctsm/crop_calendars/cropcal_utils.py @@ -206,6 +206,14 @@ def is_each_vegtype(this_vegtypelist, this_filter, this_method): return [is_this_vegtype(x, this_filter, this_method) for x in this_vegtypelist] +def define_crop_list(): + """ + List (strings) of managed crops in CLM. + """ + notcrop_list = ["tree", "c3_arctic_grass", "c3_non-arctic_grass", "c4_grass", "shrub", "not_vegetated"] + defined_pftlist = define_pftlist() + is_crop = is_each_vegtype(defined_pftlist, notcrop_list, "notok_contains") + return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] def define_mgdcrop_list_nograsses(): """ diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index effc8e54ec..3d082d0fde 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -23,7 +23,7 @@ GRIDDING_VAR_LIST = ["patches1d_ixy", "patches1d_jxy", "lat", "lon"] STREAM_YEAR = 2000 # The year specified for stream_yearFirst and stream_yearLast in the call of # shr_strdata_init_from_inline() for sdat_cropcal_gdd20_baseline -MGDCROP_LIST = utils.define_mgdcrop_list_withgrasses() +MGDCROP_LIST = utils.define_crop_list() def _parse_args(): From 753fda3ff0147837231a73c9c728dd9ce47b5997 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 23 Jul 2024 09:27:15 -0600 Subject: [PATCH 131/160] Format with black. --- python/ctsm/crop_calendars/cropcal_utils.py | 22 +++++++++++++++++-- .../crop_calendars/generate_gdd20_baseline.py | 12 +++------- .../crop_calendars/generate_gdds_functions.py | 17 ++++++++------ .../ctsm/crop_calendars/interpolate_gdds.py | 2 +- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/python/ctsm/crop_calendars/cropcal_utils.py b/python/ctsm/crop_calendars/cropcal_utils.py index 1a80448c1b..584046edee 100644 --- a/python/ctsm/crop_calendars/cropcal_utils.py +++ b/python/ctsm/crop_calendars/cropcal_utils.py @@ -206,15 +206,24 @@ def is_each_vegtype(this_vegtypelist, this_filter, this_method): return [is_this_vegtype(x, this_filter, this_method) for x in this_vegtypelist] + def define_crop_list(): """ List (strings) of managed crops in CLM. """ - notcrop_list = ["tree", "c3_arctic_grass", "c3_non-arctic_grass", "c4_grass", "shrub", "not_vegetated"] + notcrop_list = [ + "tree", + "c3_arctic_grass", + "c3_non-arctic_grass", + "c4_grass", + "shrub", + "not_vegetated", + ] defined_pftlist = define_pftlist() is_crop = is_each_vegtype(defined_pftlist, notcrop_list, "notok_contains") return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] + def define_mgdcrop_list_nograsses(): """ List (strings) of managed crops in CLM. @@ -224,11 +233,20 @@ def define_mgdcrop_list_nograsses(): is_crop = is_each_vegtype(defined_pftlist, notcrop_list, "notok_contains") return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] + def define_mgdcrop_list_withgrasses(): """ List (strings) of managed crops in CLM. """ - notcrop_list = ["tree", "c3_arctic_grass", "c3_non-arctic_grass", "c4_grass", "shrub", "unmanaged", "not_vegetated"] + notcrop_list = [ + "tree", + "c3_arctic_grass", + "c3_non-arctic_grass", + "c4_grass", + "shrub", + "unmanaged", + "not_vegetated", + ] defined_pftlist = define_pftlist() is_crop = is_each_vegtype(defined_pftlist, notcrop_list, "notok_contains") return [defined_pftlist[i] for i, x in enumerate(is_crop) if x] diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 3d082d0fde..06d3a9cd21 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -72,27 +72,21 @@ def _parse_args(): parser.add_argument( "-y1", "--first-year", - help=( - "First calendar year to include" - ), + help=("First calendar year to include"), type=int, required=False, ) parser.add_argument( "-yN", "--last-year", - help=( - "Last calendar year to include" - ), + help=("Last calendar year to include"), type=int, required=False, ) parser.add_argument( "-v", "--variable", - help=( - "Which type of variable should be processed?" - ), + help=("Which type of variable should be processed?"), required=False, default="GDDBX", choices=["GDDBX", "GDDB20"], diff --git a/python/ctsm/crop_calendars/generate_gdds_functions.py b/python/ctsm/crop_calendars/generate_gdds_functions.py index 83c167af00..50e2ac3d00 100644 --- a/python/ctsm/crop_calendars/generate_gdds_functions.py +++ b/python/ctsm/crop_calendars/generate_gdds_functions.py @@ -651,10 +651,7 @@ def import_and_process_1yr( ) if save_figs and np.any(np.isnan(gddharv_atharv_p)): if np.all(np.isnan(gddharv_atharv_p)): - log( - logger, - " ❗ All GDDHARV are NaN; should only affect figure" - ) + log(logger, " ❗ All GDDHARV are NaN; should only affect figure") check_gddharv = False else: log( @@ -744,9 +741,15 @@ def import_and_process_1yr( ) else: error(logger, "Unexpected NaN for last season's GDD accumulation.") - if save_figs and check_gddharv and np.any( - np.isnan( - gddharv_yp_list[var][year_index - 1, active_this_year_where_gs_lastyr_indices] + if ( + save_figs + and check_gddharv + and np.any( + np.isnan( + gddharv_yp_list[var][ + year_index - 1, active_this_year_where_gs_lastyr_indices + ] + ) ) ): if incorrectly_daily: diff --git a/python/ctsm/crop_calendars/interpolate_gdds.py b/python/ctsm/crop_calendars/interpolate_gdds.py index 809be98826..830df18c73 100755 --- a/python/ctsm/crop_calendars/interpolate_gdds.py +++ b/python/ctsm/crop_calendars/interpolate_gdds.py @@ -70,7 +70,7 @@ def _setup_process_args(): help="Interpolate variables whose names start with this string", type=str, required=False, - default="gdd1_" + default="gdd1_", ) parser.add_argument( "--overwrite", From c0a3556886e31c5c4bfc55ceccec0925e407b1f4 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Tue, 23 Jul 2024 09:27:53 -0600 Subject: [PATCH 132/160] Add previous commit to .git-blame-ignore-revs. --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 44a697c343..31f63ef8ae 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -48,3 +48,4 @@ aa04d1f7d86cc2503b98b7e2b2d84dbfff6c316b 9660667b1267dcd4150889f5f39db540158be74a 665cf86102e09b4c4c5a140700676dca23bc55a9 045d90f1d80f713eb3ae0ac58f6c2352937f1eb0 +753fda3ff0147837231a73c9c728dd9ce47b5997 From 71ca96d2d8e14fbbe9bc7686c2d905907b4d43d5 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:29:29 -0600 Subject: [PATCH 133/160] Delete an unused ncd_log. --- src/biogeophys/TemperatureType.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index bb579b8031..5ba64f9b1e 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -897,7 +897,7 @@ subroutine Restart(this, bounds, ncid, flag, is_simple_buildtemp, is_prog_buildt use shr_log_mod , only : errMsg => shr_log_errMsg use spmdMod , only : masterproc use abortutils , only : endrun - use ncdio_pio , only : file_desc_t, ncd_double, ncd_int, ncd_log + use ncdio_pio , only : file_desc_t, ncd_double, ncd_int use restUtilMod ! ! !ARGUMENTS: From ad2c7f3af389ad2b2b6464987a06d1200049a813 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:30:31 -0600 Subject: [PATCH 134/160] Delete a troubleshooting log message. --- bld/CLMBuildNamelist.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/bld/CLMBuildNamelist.pm b/bld/CLMBuildNamelist.pm index ad110b017e..5202717d74 100755 --- a/bld/CLMBuildNamelist.pm +++ b/bld/CLMBuildNamelist.pm @@ -4200,7 +4200,6 @@ sub setup_logic_cropcal_streams { my $gdd20_season_end_file = $nl->get_value('stream_fldFileName_gdd20_season_end') ; if ( &string_is_undef_or_empty($gdd20_season_start_file) or &string_is_undef_or_empty($gdd20_season_end_file) ) { $log->message($gdd20_season_start_file); - $log->message('abcd'); $log->message($gdd20_season_end_file); $log->fatal_error("If stream_gdd20_seasons is true, gdd20 season start and end files must be provided." ); } From 592476f7c6ae5751b6360c372ed70ad4f94af0c7 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:34:29 -0600 Subject: [PATCH 135/160] Improve comments/error at "Handle invalid gdd20 season values." --- src/cpl/share_esmf/cropcalStreamMod.F90 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index ef3bab03c5..d17ce1c259 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -793,9 +793,10 @@ subroutine cropcal_interp(bounds, num_pcropp, filter_pcropp, init, crop_inst) ! Handle invalid gdd20 season values if (any(gdd20_season_starts(begp:endp) < 1._r8 .or. gdd20_season_ends(begp:endp) < 1._r8)) then - ! Fail if not allowing fallback to paramfile sowing windows + ! Fail if not allowing fallback to paramfile sowing windows. Only need to check for + ! values < 1 because values outside [1, 366] are set to -1 above. if ((.not. allow_invalid_gdd20_season_inputs) .and. any(gdd20_season_starts(begp:endp) < 1._r8 .and. patch%wtgcell(begp:endp) > 0._r8 .and. patch%itype(begp:endp) >= npcropmin)) then - write(iulog, *) 'At least one crop in one gridcell has invalid gdd20 season start date(s). To ignore and fall back to paramfile sowing windows, set allow_invalid_gdd20_season_inputs to .true.' + write(iulog, *) 'At least one crop in one gridcell has invalid gdd20 season start and/or end date(s). To ignore and fall back to paramfile sowing windows for such crop-gridcells, set allow_invalid_gdd20_season_inputs to .true.' write(iulog, *) 'Affected crops:' do ivt = npcropmin, mxpft do fp = 1, num_pcropp From 013028c4687f564953ef5443e159b02f00220297 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:35:49 -0600 Subject: [PATCH 136/160] Replace SSR with Sam Rabin. --- src/biogeochem/CNPhenologyMod.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/biogeochem/CNPhenologyMod.F90 b/src/biogeochem/CNPhenologyMod.F90 index d1d3c85b21..159e688dbd 100644 --- a/src/biogeochem/CNPhenologyMod.F90 +++ b/src/biogeochem/CNPhenologyMod.F90 @@ -2699,7 +2699,7 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & did_rx_gdds = .true. if (adapt_cropcal_rx_cultivar_gdds .and. crop_inst%gdd20_baseline_patch(p) > min_gdd20_baseline) then gddmaturity(p) = gddmaturity(p) * gdd20 / crop_inst%gdd20_baseline_patch(p) - !TODO SSR: Set maximum and minimum gddmaturity + !TODO Sam Rabin: Set maximum and minimum gddmaturity end if else if (ivt(p) == nwwheat .or. ivt(p) == nirrig_wwheat) then gddmaturity(p) = hybgdd(ivt(p)) @@ -2716,11 +2716,11 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & ivt(p) == nmiscanthus .or. ivt(p) == nirrig_miscanthus .or. & ivt(p) == nswitchgrass .or. ivt(p) == nirrig_switchgrass) then gddmaturity(p) = max(950._r8, min(gdd20*0.85_r8, hybgdd(ivt(p)))) - if (do_plant_normal) then ! TODO SSR: Add ".and. .not. do_plant_prescribed"? + if (do_plant_normal) then ! TODO Sam Rabin: Add ".and. .not. do_plant_prescribed"? gddmaturity(p) = max(950._r8, min(gddmaturity(p)+150._r8, 1850._r8)) end if else - ! TODO SSR: Add more descriptive error message + ! TODO Sam Rabin: Add more descriptive error message call endrun(msg="Stopping") end if From 96c7c2d96cf39347cd6e259366f5d321971e75eb Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:37:30 -0600 Subject: [PATCH 137/160] import_ds.py: Delete a commented-out line. --- python/ctsm/crop_calendars/import_ds.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/ctsm/crop_calendars/import_ds.py b/python/ctsm/crop_calendars/import_ds.py index 0526d3c720..486757492f 100644 --- a/python/ctsm/crop_calendars/import_ds.py +++ b/python/ctsm/crop_calendars/import_ds.py @@ -59,7 +59,6 @@ def manual_mfdataset(filelist, my_vars, my_vegtypes, time_slice): compat="override", coords="all", dim="time", - # combine="nested", ) return ds_out From 1d9e93989c656c91b44389a307bf07602727bcfd Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:38:29 -0600 Subject: [PATCH 138/160] Improve a comment in run_sys_tests.py. --- python/ctsm/run_sys_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctsm/run_sys_tests.py b/python/ctsm/run_sys_tests.py index 9961fd325d..b3a3b78379 100644 --- a/python/ctsm/run_sys_tests.py +++ b/python/ctsm/run_sys_tests.py @@ -736,7 +736,7 @@ def _check_py_env(test_attributes): # whether import is possible. # pylint: disable=import-error disable - # Check requirements for using modify_fsurdat, if needed + # Check requirements for using modify_fsurdat Python module, if needed modify_fsurdat_users = ["FSURDATMODIFYCTSM", "RXCROPMATURITY"] if any(any(u in t for u in modify_fsurdat_users) for t in test_attributes): try: From 78d9f98060c62ec486b32ef20dd2b14718d3c428 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:40:49 -0600 Subject: [PATCH 139/160] Improve error messages in PlantCrop(). --- src/biogeochem/CNPhenologyMod.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/biogeochem/CNPhenologyMod.F90 b/src/biogeochem/CNPhenologyMod.F90 index 159e688dbd..855a1476f8 100644 --- a/src/biogeochem/CNPhenologyMod.F90 +++ b/src/biogeochem/CNPhenologyMod.F90 @@ -2720,7 +2720,7 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & gddmaturity(p) = max(950._r8, min(gddmaturity(p)+150._r8, 1850._r8)) end if else - ! TODO Sam Rabin: Add more descriptive error message + write(iulog, *) 'ERROR: PlantCrop(): unrecognized ivt for GDD target: ',ivt(p) call endrun(msg="Stopping") end if @@ -2729,7 +2729,7 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & if (gddmaturity(p) < min_gddmaturity) then if (use_cropcal_rx_cultivar_gdds .or. generate_crop_gdds) then if (did_rx_gdds) then - write(iulog,*) 'Some patch with ivt ',ivt(p),' has rx gddmaturity',gddmaturity(p),'; using min_gddmaturity instead (',min_gddmaturity,')' + write(iulog,*) 'Some patch with ivt ',ivt(p),' has rx gddmaturity ',gddmaturity(p),'; using min_gddmaturity instead (',min_gddmaturity,')' end if gddmaturity(p) = min_gddmaturity else From d3e6cbf13c4347ad2e61bbd5841b1bb9a7363fb8 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:41:54 -0600 Subject: [PATCH 140/160] CropType: Fix a comment. --- src/biogeochem/CropType.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biogeochem/CropType.F90 b/src/biogeochem/CropType.F90 index 77da895135..0f650a4a9f 100644 --- a/src/biogeochem/CropType.F90 +++ b/src/biogeochem/CropType.F90 @@ -54,7 +54,7 @@ module CropType real(r8), pointer :: rx_cultivar_gdds_thisyr_patch (:,:) ! all cultivar GDD targets for this patch this year (ddays) [patch, mxsowings] real(r8), pointer :: gdd20_baseline_patch (:) ! GDD20 baseline for this patch (ddays) [patch] real(r8), pointer :: gdd20_season_start_patch(:) ! gdd20 season start date for this patch (day of year) [patch]. Real to enable history field. - real(r8), pointer :: gdd20_season_end_patch (:) ! gdd20 season end date for this patch (day of year) [patch]/ Real to enable history field. + real(r8), pointer :: gdd20_season_end_patch (:) ! gdd20 season end date for this patch (day of year) [patch]. Real to enable history field. real(r8), pointer :: sdates_thisyr_patch (:,:) ! all actual sowing dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: swindow_starts_thisyr_patch(:,:) ! all sowing window start dates for this patch this year (day of year) [patch, mxsowings] real(r8), pointer :: swindow_ends_thisyr_patch (:,:) ! all sowing window end dates for this patch this year (day of year) [patch, mxsowings] From 6546a681b92f736e460a106b33b4523b290f1cbc Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:42:57 -0600 Subject: [PATCH 141/160] TemperatureType Restart(): Delete unused p. --- src/biogeophys/TemperatureType.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 5ba64f9b1e..787efa81a0 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -909,7 +909,7 @@ subroutine Restart(this, bounds, ncid, flag, is_simple_buildtemp, is_prog_buildt logical , intent(in) :: is_prog_buildtemp ! Prognostic building temp is being used ! ! !LOCAL VARIABLES: - integer :: j,c,p ! indices + integer :: j,c ! indices logical :: readvar ! determine if variable is on initial file integer :: idata !----------------------------------------------------------------------- From c96ce350ed85600c8525b655f3acbfd2f6831059 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:49:19 -0600 Subject: [PATCH 142/160] Delete unused sowingWindows testmod. --- .../testmods_dirs/clm/sowingWindows/include_user_mods | 1 - .../testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm | 5 ----- 2 files changed, 6 deletions(-) delete mode 100644 cime_config/testdefs/testmods_dirs/clm/sowingWindows/include_user_mods delete mode 100644 cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm diff --git a/cime_config/testdefs/testmods_dirs/clm/sowingWindows/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/sowingWindows/include_user_mods deleted file mode 100644 index fe0e18cf88..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/sowingWindows/include_user_mods +++ /dev/null @@ -1 +0,0 @@ -../default diff --git a/cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm deleted file mode 100644 index 7024e99b96..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/sowingWindows/user_nl_clm +++ /dev/null @@ -1,5 +0,0 @@ -stream_fldFileName_swindow_start = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc' -stream_fldFileName_swindow_end = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_ends_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc' -stream_meshfile_cropcal = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/swindow_starts_ggcmi_crop_calendar_phase3_v1.01.2000-2000.20231005_145103.tweaked_latlons.nc' -stream_year_first_cropcal_swindows = 2000 -stream_year_last_cropcal_swindows = 2000 From b96ec9107fe8e5b02260689190cd79799f589938 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:52:49 -0600 Subject: [PATCH 143/160] check_rxboth_run.py: Improve error message. --- python/ctsm/crop_calendars/check_rxboth_run.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/check_rxboth_run.py b/python/ctsm/crop_calendars/check_rxboth_run.py index a1014b5e66..fa4affd220 100644 --- a/python/ctsm/crop_calendars/check_rxboth_run.py +++ b/python/ctsm/crop_calendars/check_rxboth_run.py @@ -156,7 +156,16 @@ def main(argv): any_bad = any_bad or gdds_not_obeyed if any_bad: - raise RuntimeError("Unexpected behavior in rxboth run") + msg = "\n ".join( + [ + "Unexpected behavior in rxboth run:", + f"any_bad_import_output: {any_bad_import_output}", + f"any_bad_check_const_vars: {any_bad_check_const_vars}", + f"sdate_not_obeyed: {sdate_not_obeyed}", + f"gdds_not_obeyed: {gdds_not_obeyed}", + ] + ) + raise RuntimeError(msg) if __name__ == "__main__": From 876e23bb4fe4c6274e5f328212b4377ae1dd9c1d Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 15:59:46 -0600 Subject: [PATCH 144/160] interpolate_gdds.py: Strict check for prefix match. --- python/ctsm/crop_calendars/interpolate_gdds.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/interpolate_gdds.py b/python/ctsm/crop_calendars/interpolate_gdds.py index 830df18c73..92bb112e3f 100755 --- a/python/ctsm/crop_calendars/interpolate_gdds.py +++ b/python/ctsm/crop_calendars/interpolate_gdds.py @@ -6,6 +6,7 @@ import sys import argparse import logging +import re import xarray as xr # -- add python/ctsm to path (needed if we want to run this stand-alone) @@ -121,7 +122,7 @@ def interpolate_gdds(args): if "lat" not in ds_in[var].dims and "lon" not in ds_in[var].dims: print(f"Skipping variable {var} with dimensions {ds_in[var].dims}") continue - elif args.variable_prefix not in var: + if not re.compile("^" + args.variable_prefix).match(var) print(f"Unexpected variable {var} on input file. Skipping.") continue if args.dry_run: From fecad299757de00f235217add67f70e60f7cda33 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:06:22 -0600 Subject: [PATCH 145/160] Delete unneeded cropAnnOutputMonthly testmod. --- cime_config/testdefs/testlist_clm.xml | 4 ++-- .../testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index c2926a25e7..e5a1ae7ef4 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3794,7 +3794,7 @@ - + @@ -3803,7 +3803,7 @@ - + diff --git a/cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm deleted file mode 100644 index 1c47a2ebd1..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/cropAnnOutputMonthly/user_nl_clm +++ /dev/null @@ -1,3 +0,0 @@ -! These variables SHOULD only be saved annually in real runs, but for testing purposes it's fine to have them monthly. -! Modifies h2 history file defined in crop testmod. -hist_nhtfrq(3) = 0 From c94aeeab68a6f98188f0f94127b3685d2c89dd49 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:09:35 -0600 Subject: [PATCH 146/160] Add READMEs to testmods decStart and midDecStart. --- cime_config/testdefs/testmods_dirs/clm/decStart/README | 1 + cime_config/testdefs/testmods_dirs/clm/midDecStart/README | 1 + 2 files changed, 2 insertions(+) create mode 100644 cime_config/testdefs/testmods_dirs/clm/decStart/README create mode 100644 cime_config/testdefs/testmods_dirs/clm/midDecStart/README diff --git a/cime_config/testdefs/testmods_dirs/clm/decStart/README b/cime_config/testdefs/testmods_dirs/clm/decStart/README new file mode 100644 index 0000000000..7cdab6abfd --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/decStart/README @@ -0,0 +1 @@ +Use midDecStart instead of decStart if you want ERP/ERS/etc. tests longer than 2 days to be able to have the split in December instead of January (i.e., before rather than after new year). \ No newline at end of file diff --git a/cime_config/testdefs/testmods_dirs/clm/midDecStart/README b/cime_config/testdefs/testmods_dirs/clm/midDecStart/README new file mode 100644 index 0000000000..7cdab6abfd --- /dev/null +++ b/cime_config/testdefs/testmods_dirs/clm/midDecStart/README @@ -0,0 +1 @@ +Use midDecStart instead of decStart if you want ERP/ERS/etc. tests longer than 2 days to be able to have the split in December instead of January (i.e., before rather than after new year). \ No newline at end of file From 833d4f67e888c747cfb2c35d8ae8354974987061 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:11:48 -0600 Subject: [PATCH 147/160] import_output(): Bugfix in call of check_v0_le_v1(). --- python/ctsm/crop_calendars/cropcal_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/cropcal_module.py b/python/ctsm/crop_calendars/cropcal_module.py index 08754f8823..927bdc9100 100644 --- a/python/ctsm/crop_calendars/cropcal_module.py +++ b/python/ctsm/crop_calendars/cropcal_module.py @@ -425,7 +425,7 @@ def import_output( # Check that e.g., GDDACCUM <= HUI for var_list in [["GDDACCUM", "HUI"], ["SYEARS", "HYEARS"]]: if all(v in this_ds_gs for v in var_list): - any_bad = check_v0_le_v1( + any_bad = any_bad or check_v0_le_v1( this_ds_gs, var_list, both_nan_ok=True, throw_error=throw_errors ) From 56523e4cbb4969e9648adb2601c40d847cb9d408 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:15:28 -0600 Subject: [PATCH 148/160] Remove 1-degree RXCROPMATURITY test from rxcropmaturity, crop_calendars suites. --- cime_config/testdefs/testlist_clm.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/cime_config/testdefs/testlist_clm.xml b/cime_config/testdefs/testlist_clm.xml index e5a1ae7ef4..5d3fbeb11a 100644 --- a/cime_config/testdefs/testlist_clm.xml +++ b/cime_config/testdefs/testlist_clm.xml @@ -3663,8 +3663,6 @@ - - From d06e8642a180190fea9f31c0a6af042758c31b81 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:18:38 -0600 Subject: [PATCH 149/160] Remove unneeded RxCropCalsAdaptFlush testmod. --- .../testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods | 1 - .../testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods delete mode 100644 cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods deleted file mode 100644 index af5fe8591e..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/include_user_mods +++ /dev/null @@ -1 +0,0 @@ -../RxCropCalsAdapt diff --git a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm deleted file mode 100644 index 4c6af0f6d2..0000000000 --- a/cime_config/testdefs/testmods_dirs/clm/RxCropCalsAdaptFlush/user_nl_clm +++ /dev/null @@ -1,2 +0,0 @@ - -flush_gdd20 = .true. From a2bf12483a8fd1f33488b6df09ffe2b291aa2f43 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:21:18 -0600 Subject: [PATCH 150/160] generate_gdd20_baseline.py: Add a clarifying comment. --- python/ctsm/crop_calendars/generate_gdd20_baseline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/ctsm/crop_calendars/generate_gdd20_baseline.py b/python/ctsm/crop_calendars/generate_gdd20_baseline.py index 06d3a9cd21..13668fc850 100644 --- a/python/ctsm/crop_calendars/generate_gdd20_baseline.py +++ b/python/ctsm/crop_calendars/generate_gdd20_baseline.py @@ -284,8 +284,7 @@ def generate_gdd20_baseline(input_files, output_file, author, time_slice, variab this_da = dummy_da print(" dummy GDD20") else: - # this_da = ds_in[gddn].fillna(MISSING_FILL) - this_da = ds_in[gddn_str] + this_da = ds_in[gddn_str] # Already did ds_in.mean(dim="time") above this_da = _add_time_axis(this_da) print(f" {gddn_str}") this_da = this_da.fillna(MISSING_RX_GDD_VAL) From c9ff0ee33410b82462fbf4fc876b4130811e13cc Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:24:14 -0600 Subject: [PATCH 151/160] PlantCrop(): Resolve (dismiss) a TODO. --- src/biogeochem/CNPhenologyMod.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/biogeochem/CNPhenologyMod.F90 b/src/biogeochem/CNPhenologyMod.F90 index 855a1476f8..1fb13d32eb 100644 --- a/src/biogeochem/CNPhenologyMod.F90 +++ b/src/biogeochem/CNPhenologyMod.F90 @@ -2716,7 +2716,7 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, & ivt(p) == nmiscanthus .or. ivt(p) == nirrig_miscanthus .or. & ivt(p) == nswitchgrass .or. ivt(p) == nirrig_switchgrass) then gddmaturity(p) = max(950._r8, min(gdd20*0.85_r8, hybgdd(ivt(p)))) - if (do_plant_normal) then ! TODO Sam Rabin: Add ".and. .not. do_plant_prescribed"? + if (do_plant_normal) then gddmaturity(p) = max(950._r8, min(gddmaturity(p)+150._r8, 1850._r8)) end if else From bc055919bd68719b53d6563d1f7a37976ec11c44 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:47:49 -0600 Subject: [PATCH 152/160] Improve flush_gdd20 restart logic. Previously, TemperatureType%flush_gdd20 would get set to true upon restart unless it was read and false. This change makes it so that: - If TemperatureType%flush_gdd20 isn't read from the restart file, it falls back to the flush_gdd20 from clm_varctl. - If TemperatureType%flush_gdd20 IS read from the restart file, it is set to true if it's true on the restart file OR if the flush_gdd20 from clm_varctl is true. --- src/biogeophys/TemperatureType.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 787efa81a0..98f6d0f638 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -1148,10 +1148,10 @@ subroutine Restart(this, bounds, ncid, flag, is_simple_buildtemp, is_prog_buildt long_name='Flag indicating that GDD20 values need to be flushed', & units='none', interpinic_flag='copy', readvar=readvar, data=idata) if (flag == 'read') then - if (readvar .and. idata == 0) then - this%flush_gdd20 = .false. + if (readvar) then + this%flush_gdd20 = flush_gdd20 .or. idata == 1 else - this%flush_gdd20 = .true. + this%flush_gdd20 = flush_gdd20 end if end if end if From d298aef9438d0b13bf37ccd162e1f5a303f6e4b4 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 16:53:54 -0600 Subject: [PATCH 153/160] Do not use flush_gdd20 from clm_varctl in flush decision. Not directly, at least. Instead, rely on TemperatureType%flush_gdd20 having been set correctly. --- src/biogeophys/TemperatureType.F90 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 98f6d0f638..4929b24307 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -169,6 +169,9 @@ subroutine Init(this, bounds, & em_perroad_lun(bounds%begl:bounds%endl), & is_simple_buildtemp, is_prog_buildtemp) + ! Finish up + this%flush_gdd20 = flush_gdd20 + end subroutine Init !------------------------------------------------------------------------ @@ -1693,13 +1696,13 @@ subroutine UpdateAccVars (this, bounds, crop_inst) ! Accumulate and extract running 20-year means if (is_end_curr_year()) then ! Flush, if needed - if (flush_gdd20 .or. this%flush_gdd20) then + if (this%flush_gdd20) then write(iulog, *) 'Flushing GDD20 variables' call markreset_accum_field('GDD020') call markreset_accum_field('GDD820') call markreset_accum_field('GDD1020') this%flush_gdd20 = .false. - flush_gdd20 = .false. + flush_gdd20 = .false. ! Shouldn't be necessary, because flush_gdd20 shouldn't be considered after Init and Restart. But just in case... end if call update_accum_field ('GDD020', this%gdd0_patch, nstep) call extract_accum_field ('GDD020', this%gdd020_patch, nstep) From 827632e12b1f55253c8c75b999f4d8abd6f87b7a Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 18:08:09 -0600 Subject: [PATCH 154/160] Actually don't use TemperatureType%flush_gdd20 at all. Having that alongside flush_gdd20 from clm_varctl means that they can disagree when restarting, and it's not obvious which should win. Note that the user being able to request a flush at all is dangerous. If they start a run with it true, they might continue that run without setting it to false. Instead, the restart decision should be made by the model---if a gridcell has different prescribed gdd20 season from what its restart file says, then that gridcell should be flushed. --- src/biogeophys/TemperatureType.F90 | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 4929b24307..71722d84fb 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -95,7 +95,6 @@ module TemperatureType real(r8), pointer :: gdd020_patch (:) ! patch 20-year average of gdd0 (ddays) real(r8), pointer :: gdd820_patch (:) ! patch 20-year average of gdd8 (ddays) real(r8), pointer :: gdd1020_patch (:) ! patch 20-year average of gdd10 (ddays) - logical :: flush_gdd20 = .false. ! whether accumulated GDD20s need to be flushed ! Heat content real(r8), pointer :: beta_col (:) ! coefficient of convective velocity [-] @@ -169,9 +168,6 @@ subroutine Init(this, bounds, & em_perroad_lun(bounds%begl:bounds%endl), & is_simple_buildtemp, is_prog_buildtemp) - ! Finish up - this%flush_gdd20 = flush_gdd20 - end subroutine Init !------------------------------------------------------------------------ @@ -1139,26 +1135,6 @@ subroutine Restart(this, bounds, ncid, flag, is_simple_buildtemp, is_prog_buildt end if end if - if (use_crop) then - if (flag == 'write') then - if (this%flush_gdd20) then - idata = 1 - else - idata = 0 - end if - end if - call restartvar(ncid=ncid, flag=flag, varname='flush_gdd20', xtype=ncd_int, & - long_name='Flag indicating that GDD20 values need to be flushed', & - units='none', interpinic_flag='copy', readvar=readvar, data=idata) - if (flag == 'read') then - if (readvar) then - this%flush_gdd20 = flush_gdd20 .or. idata == 1 - else - this%flush_gdd20 = flush_gdd20 - end if - end if - end if - end subroutine Restart @@ -1696,13 +1672,12 @@ subroutine UpdateAccVars (this, bounds, crop_inst) ! Accumulate and extract running 20-year means if (is_end_curr_year()) then ! Flush, if needed - if (this%flush_gdd20) then + if (flush_gdd20) then write(iulog, *) 'Flushing GDD20 variables' call markreset_accum_field('GDD020') call markreset_accum_field('GDD820') call markreset_accum_field('GDD1020') - this%flush_gdd20 = .false. - flush_gdd20 = .false. ! Shouldn't be necessary, because flush_gdd20 shouldn't be considered after Init and Restart. But just in case... + flush_gdd20 = .false. end if call update_accum_field ('GDD020', this%gdd0_patch, nstep) call extract_accum_field ('GDD020', this%gdd020_patch, nstep) From 4ab30a5ddb9ac54cba659abc8682f1152e8eb844 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 18:20:21 -0600 Subject: [PATCH 155/160] crop_calendars Python stuff now includes crop grasses. --- python/ctsm/crop_calendars/cropcal_module.py | 2 +- python/ctsm/crop_calendars/generate_gdds_functions.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ctsm/crop_calendars/cropcal_module.py b/python/ctsm/crop_calendars/cropcal_module.py index 927bdc9100..719d352665 100644 --- a/python/ctsm/crop_calendars/cropcal_module.py +++ b/python/ctsm/crop_calendars/cropcal_module.py @@ -345,7 +345,7 @@ def import_output( my_vars, year_1=None, year_n=None, - my_vegtypes=utils.define_mgdcrop_list_nograsses(), + my_vegtypes=utils.define_mgdcrop_list_withgrasses(), sdates_rx_ds=None, gdds_rx_ds=None, verbose=False, diff --git a/python/ctsm/crop_calendars/generate_gdds_functions.py b/python/ctsm/crop_calendars/generate_gdds_functions.py index 50e2ac3d00..14bd6b2e40 100644 --- a/python/ctsm/crop_calendars/generate_gdds_functions.py +++ b/python/ctsm/crop_calendars/generate_gdds_functions.py @@ -282,9 +282,9 @@ def import_and_process_1yr( # Get list of crops to include if skip_crops is not None: - crops_to_read = [c for c in utils.define_mgdcrop_list_nograsses() if c not in skip_crops] + crops_to_read = [c for c in utils.define_mgdcrop_list_withgrasses() if c not in skip_crops] else: - crops_to_read = utils.define_mgdcrop_list_nograsses() + crops_to_read = utils.define_mgdcrop_list_withgrasses() print(h1_filelist) dates_ds = import_ds( From aeb4182302dc63bc88a3c8a5186233901f310f37 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 18:22:58 -0600 Subject: [PATCH 156/160] interpolate_gdds.py: Syntax fix. --- python/ctsm/crop_calendars/interpolate_gdds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ctsm/crop_calendars/interpolate_gdds.py b/python/ctsm/crop_calendars/interpolate_gdds.py index 92bb112e3f..123d40af38 100755 --- a/python/ctsm/crop_calendars/interpolate_gdds.py +++ b/python/ctsm/crop_calendars/interpolate_gdds.py @@ -122,7 +122,7 @@ def interpolate_gdds(args): if "lat" not in ds_in[var].dims and "lon" not in ds_in[var].dims: print(f"Skipping variable {var} with dimensions {ds_in[var].dims}") continue - if not re.compile("^" + args.variable_prefix).match(var) + if not re.compile("^" + args.variable_prefix).match(var): print(f"Unexpected variable {var} on input file. Skipping.") continue if args.dry_run: From 929ec5d6c43a4234e35b060b02223888466554fc Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 23:08:27 -0600 Subject: [PATCH 157/160] Skip switchgrass in RXCROPMATURITY. --- cime_config/SystemTests/rxcropmaturity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cime_config/SystemTests/rxcropmaturity.py b/cime_config/SystemTests/rxcropmaturity.py index 46ed8f8be3..965398821f 100644 --- a/cime_config/SystemTests/rxcropmaturity.py +++ b/cime_config/SystemTests/rxcropmaturity.py @@ -438,7 +438,7 @@ def _run_generate_gdds(self, case_gddgen): f"--sdates-file {sdates_file}", f"--hdates-file {hdates_file}", f"--output-dir generate_gdds_out", - f"--skip-crops miscanthus,irrigated_miscanthus", + f"--skip-crops miscanthus,irrigated_miscanthus,switchgrass,irrigated_switchgrass", ] ) stu.run_python_script( From b70c156fed30f835a8b32cbca4d82dcbfe4888b2 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Wed, 24 Jul 2024 23:16:00 -0600 Subject: [PATCH 158/160] Manually specify sowing date file for GddGen testmod. --- cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm b/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm index 87cdd5d5b5..cfde517fd9 100644 --- a/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm +++ b/cime_config/testdefs/testmods_dirs/clm/GddGen/user_nl_clm @@ -1,4 +1,6 @@ cropcals_rx = .true. +stream_fldFileName_swindow_start = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc' +stream_fldFileName_swindow_end = '$DIN_LOC_ROOT/lnd/clm2/cropdata/calendars/processed/sdates_ggcmi_crop_calendar_phase3_v1.01_nninterp-hcru_hcru_mt13.2000-2000.20230728_165845.tweaked_latlons.nc' stream_fldFileName_cultivar_gdds = '' generate_crop_gdds = .true. use_mxmat = .false. From 7400d5bb153bd64be3eeb7f560e85128d736ed5c Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 25 Jul 2024 05:14:01 -0600 Subject: [PATCH 159/160] Add izumi_nag.clm-RxCropCalsAdaptGGCMI test to expected fails. SMS_P128x1_Lm25.f10_f10_mg37.IHistClm60BgcCrop.izumi_nag.clm-RxCropCalsAdaptGGCMI --- cime_config/testdefs/ExpectedTestFails.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cime_config/testdefs/ExpectedTestFails.xml b/cime_config/testdefs/ExpectedTestFails.xml index 03eb6a157d..e5e1410d1e 100644 --- a/cime_config/testdefs/ExpectedTestFails.xml +++ b/cime_config/testdefs/ExpectedTestFails.xml @@ -170,6 +170,13 @@ + + + FAIL + #2659 + + + From 388c7ed45e9561206a88efeecdb6faec054522d1 Mon Sep 17 00:00:00 2001 From: Sam Rabin Date: Thu, 25 Jul 2024 05:55:45 -0600 Subject: [PATCH 160/160] Move stream_gdd20_seasons to cropCalStreamMod. --- bld/namelist_files/namelist_definition_ctsm.xml | 2 +- src/biogeophys/TemperatureType.F90 | 7 +++++-- src/cpl/share_esmf/cropcalStreamMod.F90 | 5 ++++- src/main/clm_varctl.F90 | 1 - src/main/controlMod.F90 | 4 +--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bld/namelist_files/namelist_definition_ctsm.xml b/bld/namelist_files/namelist_definition_ctsm.xml index 31ce220d9c..a24f59339a 100644 --- a/bld/namelist_files/namelist_definition_ctsm.xml +++ b/bld/namelist_files/namelist_definition_ctsm.xml @@ -1839,7 +1839,7 @@ Filename of input stream data for baseline GDD20 values + group="cropcal_streams" valid_values="" > Set this to true to read gdd20 accumulation season start and end dates from stream files, rather than using hard-coded hemisphere-specific "warm seasons." diff --git a/src/biogeophys/TemperatureType.F90 b/src/biogeophys/TemperatureType.F90 index 71722d84fb..707218cc27 100644 --- a/src/biogeophys/TemperatureType.F90 +++ b/src/biogeophys/TemperatureType.F90 @@ -8,7 +8,7 @@ module TemperatureType use decompMod , only : bounds_type use abortutils , only : endrun use clm_varctl , only : use_cndv, iulog, use_luna, use_crop, use_biomass_heat_storage - use clm_varctl , only : stream_gdd20_seasons, flush_gdd20 + use clm_varctl , only : flush_gdd20 use clm_varpar , only : nlevsno, nlevgrnd, nlevlak, nlevurb, nlevmaxurbgrnd use clm_varcon , only : spval, ispval use GridcellType , only : grc @@ -1401,6 +1401,7 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d real(r8) :: lat ! latitude integer :: gdd20_season_start, gdd20_season_end integer :: jday ! Julian day of year (1, ..., 366) + logical :: stream_gdd20_seasons_tt ! Local derivation of this to avoid circular dependency associate( & gdd20_season_starts => crop_inst%gdd20_season_start_patch, & @@ -1432,6 +1433,8 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d end if write(field_name, format_string) "GDD",basetemp_int + stream_gdd20_seasons_tt = any(gdd20_season_starts(begp:endp) > 0.5_r8) .and. any(gdd20_season_starts(begp:endp) < 366.5_r8) + do p = begp,endp ! Avoid unnecessary calculations over inactive points @@ -1447,7 +1450,7 @@ subroutine UpdateAccVars_CropGDDs(this, rbufslp, begp, endp, month, day, secs, d ((month > 9 .or. month < 4) .and. lat < 0._r8) ! Replace with read-in gdd20 accumulation season, if needed and valid ! (If these aren't being read in or they're invalid, they'll be -1) - if (stream_gdd20_seasons .and. patch%itype(p) >= npcropmin) then + if (stream_gdd20_seasons_tt .and. patch%itype(p) >= npcropmin) then gdd20_season_start = int(gdd20_season_starts(p)) gdd20_season_end = int(gdd20_season_ends(p)) if (gdd20_season_start >= 1 .and. gdd20_season_end >= 1) then diff --git a/src/cpl/share_esmf/cropcalStreamMod.F90 b/src/cpl/share_esmf/cropcalStreamMod.F90 index d17ce1c259..85218a0e1c 100644 --- a/src/cpl/share_esmf/cropcalStreamMod.F90 +++ b/src/cpl/share_esmf/cropcalStreamMod.F90 @@ -16,7 +16,6 @@ module cropcalStreamMod use clm_varctl , only : iulog use clm_varctl , only : use_cropcal_rx_swindows, use_cropcal_rx_cultivar_gdds, use_cropcal_streams use clm_varctl , only : adapt_cropcal_rx_cultivar_gdds - use clm_varctl , only : stream_gdd20_seasons use clm_varpar , only : mxpft use clm_varpar , only : mxsowings use perf_mod , only : t_startf, t_stopf @@ -53,6 +52,7 @@ module cropcalStreamMod character(len=CL) :: stream_fldFileName_gdd20_baseline ! GDD20 baseline stream filename to read logical :: cropcals_rx ! Used only for setting input files in namelist; does nothing in code, but needs to be here so namelist read doesn't crash logical :: cropcals_rx_adapt ! Used only for setting input files in namelist; does nothing in code, but needs to be here so namelist read doesn't crash + logical :: stream_gdd20_seasons ! Read start and end dates for gdd20 seasons from streams instead of using hemisphere-specific values logical :: allow_invalid_gdd20_season_inputs ! Fall back on hemisphere "warm periods" in cases of invalid values in stream_fldFileName_gdd20_season_start and _end? character(len=CL) :: stream_fldFileName_gdd20_season_start ! Stream filename to read for start of gdd20 season character(len=CL) :: stream_fldFileName_gdd20_season_end ! Stream filename to read for end of gdd20 season @@ -113,6 +113,7 @@ subroutine cropcal_init(bounds) stream_meshfile_cropcal, & cropcals_rx, & cropcals_rx_adapt, & + stream_gdd20_seasons, & allow_invalid_gdd20_season_inputs, & stream_fldFileName_gdd20_season_start, & stream_fldFileName_gdd20_season_end @@ -130,6 +131,7 @@ subroutine cropcal_init(bounds) stream_fldFileName_swindow_end = '' stream_fldFileName_cultivar_gdds = '' stream_fldFileName_gdd20_baseline = '' + stream_gdd20_seasons = .false. allow_invalid_gdd20_season_inputs = .false. stream_fldFileName_gdd20_season_start = '' stream_fldFileName_gdd20_season_end = '' @@ -173,6 +175,7 @@ subroutine cropcal_init(bounds) call shr_mpi_bcast(stream_fldFileName_cultivar_gdds, mpicom) call shr_mpi_bcast(stream_fldFileName_gdd20_baseline, mpicom) call shr_mpi_bcast(stream_meshfile_cropcal , mpicom) + call shr_mpi_bcast(stream_gdd20_seasons, mpicom) call shr_mpi_bcast(allow_invalid_gdd20_season_inputs, mpicom) call shr_mpi_bcast(stream_fldFileName_gdd20_season_start, mpicom) call shr_mpi_bcast(stream_fldFileName_gdd20_season_end, mpicom) diff --git a/src/main/clm_varctl.F90 b/src/main/clm_varctl.F90 index a1ca2fd3f7..678f386f23 100644 --- a/src/main/clm_varctl.F90 +++ b/src/main/clm_varctl.F90 @@ -392,7 +392,6 @@ module clm_varctl logical, public :: use_cropcal_rx_swindows = .false. logical, public :: use_cropcal_rx_cultivar_gdds = .false. logical, public :: adapt_cropcal_rx_cultivar_gdds = .false. - logical, public :: stream_gdd20_seasons = .false. logical, public :: flush_gdd20 = .false. !---------------------------------------------------------- diff --git a/src/main/controlMod.F90 b/src/main/controlMod.F90 index 98e0d1afac..634128798b 100644 --- a/src/main/controlMod.F90 +++ b/src/main/controlMod.F90 @@ -302,7 +302,7 @@ subroutine control_init(dtime) use_lch4, use_nitrif_denitrif, use_extralakelayers, & use_vichydro, use_cn, use_cndv, use_crop, use_fertilizer, & use_grainproduct, use_snicar_frc, use_vancouver, use_mexicocity, use_noio, & - use_nguardrail, crop_residue_removal_frac, stream_gdd20_seasons, flush_gdd20 + use_nguardrail, crop_residue_removal_frac, flush_gdd20 ! SNICAR namelist /clm_inparm/ & @@ -711,7 +711,6 @@ subroutine control_spmd() call mpi_bcast (use_cndv, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_nguardrail, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_crop, 1, MPI_LOGICAL, 0, mpicom, ier) - call mpi_bcast (stream_gdd20_seasons, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (flush_gdd20, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_fertilizer, 1, MPI_LOGICAL, 0, mpicom, ier) call mpi_bcast (use_grainproduct, 1, MPI_LOGICAL, 0, mpicom, ier) @@ -985,7 +984,6 @@ subroutine control_print () write(iulog,*) ' use_cn = ', use_cn write(iulog,*) ' use_cndv = ', use_cndv write(iulog,*) ' use_crop = ', use_crop - write(iulog,*) ' stream_gdd20_seasons = ', stream_gdd20_seasons write(iulog,*) ' flush_gdd20 = ', flush_gdd20 write(iulog,*) ' use_fertilizer = ', use_fertilizer write(iulog,*) ' use_grainproduct = ', use_grainproduct