Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
496ee06
Add MAX_TLAI_PERHARV history output.
samsrabin Nov 25, 2025
7f9ef13
PlantCrop: Add namelist option to suppress low gddmaturity warning.
samsrabin Nov 2, 2025
e9219a2
Fix a comment
samsrabin Nov 25, 2025
4ee9e6e
Add crop phase transition biomass outputs.
samsrabin Nov 25, 2025
291a158
Rename GRAINC_AT hist vars to HARVORGANC_AT to fix LREPRSTRUCT tests.
samsrabin Nov 26, 2025
7c89b24
Rename HARVORGANC_AT history vars to REPRC_AT.
samsrabin Dec 1, 2025
a263a92
Rename grainc_at vars to reprc_at.
samsrabin Dec 1, 2025
319be76
CropPhaseTransitionBiomass() bugfix for reproductive C.
samsrabin Dec 1, 2025
a7e66aa
PlantCrop: Fix setting max_tlai_patch.
samsrabin Jan 8, 2026
eb3237a
CropPhenology(): New variable is_mature.
samsrabin Jan 9, 2026
533383f
Only save crop maturity biomass if mature. Always save new harvest bi…
samsrabin Jan 9, 2026
7e480b9
CropPhenology(): Add a comment.
samsrabin Jan 12, 2026
71bc137
Fix restart of _harvest_ biomass variables.
samsrabin Jan 12, 2026
af1ed71
CropPhaseTransitionBiomass(): Delete an unused 'use'.
samsrabin Jan 12, 2026
365fda3
CropPhaseTransitionBiomass(): Require is_mature arg if cphase is harv…
samsrabin Jan 12, 2026
df3c1cd
Move some var inits from PlantCrop() to new CropType%InitPlantCrop().
samsrabin Jan 12, 2026
cf7d3fe
Add tests for CropType%CropPhaseTransitionBiomass(). 2 fail.
samsrabin Jan 12, 2026
f26b8e2
CropPhaseTransitionBiomass: Fix behavior if skipping phases.
samsrabin Jan 12, 2026
08b93e7
CropPhaseTransitionBiomass(): Assert cphase in expected range.
samsrabin Jan 12, 2026
3c7eac6
Merge branch 'b4b-dev' into crop-harvest-biomass-history
samsrabin Jan 13, 2026
db86d05
Merge branch 'b4b-dev' into crop-harvest-biomass-history
samsrabin Jan 17, 2026
c087862
Merge branch 'b4b-dev' into crop-harvest-biomass-history
samsrabin Jan 28, 2026
71123fa
Move CropPhaseTransitionBiomass from CropType to CNPhenology.
samsrabin Jan 28, 2026
9af60cb
test_CropPhaseTransitionBiomass: Add two failing tests.
samsrabin Jan 28, 2026
d792a2f
CropPhaseTransitionBiomass(): Consider whether crop actually reached …
samsrabin Jan 29, 2026
e8c5710
Delete an extraneous space.
samsrabin Jan 29, 2026
29bfcf2
Rename leafout to gddtsoi.
samsrabin Jan 29, 2026
485e85e
Docs: Update history_fields_nofates.rst.
samsrabin Jan 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bld/namelist_files/namelist_defaults_ctsm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ attributes from the config_cache.xml file (with keys converted to upper-case).

<generate_crop_gdds >.false.</generate_crop_gdds>
<use_mxmat >.true.</use_mxmat>
<suppress_gddmaturity_warning >.false.</suppress_gddmaturity_warning>

<!-- use additional stress deciduous onset trigger -->
<constrain_stress_deciduous_onset >.true.</constrain_stress_deciduous_onset>
Expand Down
5 changes: 5 additions & 0 deletions bld/namelist_files/namelist_definition_ctsm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 +1472,11 @@ Set to .true. in order to override crop harvesting logic and to instead harvest
Set to .false. in order to ignore crop PFT parameter for maximum growing season length (mxmat). Must be set to .false. when generate_crop_gdds is .true.
</entry>

<entry id="suppress_gddmaturity_warning" type="logical" category="physics"
group="cnphenology" valid_values="" value=".false.">
If set to .true., suppress the warning message when a prescribed cultivar GDD requirement is below the minimum allowed value and is replaced with min_gddmaturity. Useful when using prescribed crop calendars with intentionally low GDD requirements.
</entry>

<entry id="min_critical_dayl_method" type="char*25" category="physics"
group="cnphenology" valid_values="Constant,DependsOnLat,DependsOnVeg,DependsOnLatAndVeg">
Method for determining what the minimum critical day length for seasonal decidious leaf offset depends on
Expand Down
7 changes: 6 additions & 1 deletion cime_config/testdefs/testmods_dirs/clm/crop/user_nl_clm
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ hist_fincl2 += 'DYN_COL_SOIL_ADJUSTMENTS_C'
! 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_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', 'MAX_TLAI_PERHARV'
hist_fincl3 += 'FROOTC_AT_EMERGENCE_PERHARV', 'FROOTC_AT_ANTHESIS_PERHARV', 'FROOTC_AT_MATURITY_PERHARV'
hist_fincl3 += 'LIVECROOTC_AT_EMERGENCE_PERHARV', 'LIVECROOTC_AT_ANTHESIS_PERHARV', 'LIVECROOTC_AT_MATURITY_PERHARV'
hist_fincl3 += 'LIVESTEMC_AT_EMERGENCE_PERHARV', 'LIVESTEMC_AT_ANTHESIS_PERHARV', 'LIVESTEMC_AT_MATURITY_PERHARV'
hist_fincl3 += 'LEAFC_AT_EMERGENCE_PERHARV', 'LEAFC_AT_ANTHESIS_PERHARV', 'LEAFC_AT_MATURITY_PERHARV'
hist_fincl3 += 'REPRC_AT_EMERGENCE_PERHARV', 'REPRC_AT_ANTHESIS_PERHARV', 'REPRC_AT_MATURITY_PERHARV'
hist_nhtfrq = -24,-8,-24
hist_mfilt = 1,1,1
hist_type1d_pertape(3) = 'PFTS'
Expand Down
73 changes: 70 additions & 3 deletions src/biogeochem/CNPhenologyMod.F90
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ module CNPhenologyMod
real(r8) :: min_gddmaturity = 1._r8 ! Weird things can happen if gddmaturity is tiny
logical, public :: generate_crop_gdds = .false. ! If true, harvest the day before next sowing
logical, public :: use_mxmat = .true. ! If true, ignore crop maximum growing season length
logical, private :: suppress_gddmaturity_warning = .false. ! If true, suppress warning when using min_gddmaturity

! For use with adapt_cropcal_rx_cultivar_gdds .true.
real(r8), parameter :: min_gdd20_baseline = 0._r8 ! If gdd20_baseline_patch is ≤ this, do not consider baseline.
Expand Down Expand Up @@ -200,7 +201,7 @@ subroutine CNPhenologyReadNML( NLFilename )
!-----------------------------------------------------------------------
namelist /cnphenology/ initial_seed_at_planting, onset_thresh_depends_on_veg, &
min_critical_dayl_method, generate_crop_gdds, &
use_mxmat
use_mxmat, suppress_gddmaturity_warning

! Initialize options to default values, in case they are not specified in
! the namelist
Expand All @@ -226,6 +227,7 @@ subroutine CNPhenologyReadNML( NLFilename )
call shr_mpi_bcast (min_critical_dayl_method, mpicom)
call shr_mpi_bcast (generate_crop_gdds, mpicom)
call shr_mpi_bcast (use_mxmat, mpicom)
call shr_mpi_bcast (suppress_gddmaturity_warning, mpicom)

if ( min_critical_dayl_method == "DependsOnLat" )then
critical_daylight_method = critical_daylight_depends_on_lat
Expand Down Expand Up @@ -2054,6 +2056,7 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , &
real(r8) avg_dayspyr ! average number of days per year
real(r8) crmcorn ! comparitive relative maturity for corn
real(r8) ndays_on ! number of days to fertilize
real(r8) cphase_orig ! crop phase before updating
logical has_rx_sowing_date ! does the crop have a single sowing date instead of a window?
logical is_in_sowing_window ! is the crop in its sowing window?
logical is_end_sowing_window ! is it the last day of the crop's sowing window?
Expand Down Expand Up @@ -2092,6 +2095,7 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , &

fertnitro => crop_inst%fertnitro_patch , & ! Input: [real(r8) (:) ] fertilizer nitrogen
hui => crop_inst%hui_patch , & ! Input: [real(r8) (:) ] crop patch heat unit index (growing degree-days); set to 0 at sowing and accumulated until harvest
max_tlai => crop_inst%max_tlai_patch , & ! Input: [real(r8) (:) ] maximum total projected leaf area seen this season (m2/m2); set to 0 at sowing and tracked until harvest
leafout => crop_inst%gddtsoi_patch , & ! Input: [real(r8) (:) ] gdd from top soil layer temperature
harvdate => crop_inst%harvdate_patch , & ! Output: [integer (:) ] harvest date
croplive => crop_inst%croplive_patch , & ! Output: [logical (:) ] Flag, true if planted, not harvested
Expand Down Expand Up @@ -2154,6 +2158,8 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , &
! Should never be saved as zero, but including this so it's initialized just in case
harvest_reason = 0._r8

cphase_orig = cphase(p)

! ---------------------------------
! from AgroIBIS subroutine planting
! ---------------------------------
Expand All @@ -2180,6 +2186,22 @@ 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%max_tlai_thisyr_patch(p,s) = -1._r8
crop_inst%frootc_emergence_thisyr_patch(p,s) = -1._r8
crop_inst%frootc_anthesis_thisyr_patch(p,s) = -1._r8
crop_inst%frootc_maturity_thisyr_patch(p,s) = -1._r8
crop_inst%livecrootc_emergence_thisyr_patch(p,s) = -1._r8
crop_inst%livecrootc_anthesis_thisyr_patch(p,s) = -1._r8
crop_inst%livecrootc_maturity_thisyr_patch(p,s) = -1._r8
crop_inst%livestemc_emergence_thisyr_patch(p,s) = -1._r8
crop_inst%livestemc_anthesis_thisyr_patch(p,s) = -1._r8
crop_inst%livestemc_maturity_thisyr_patch(p,s) = -1._r8
crop_inst%leafc_emergence_thisyr_patch(p,s) = -1._r8
crop_inst%leafc_anthesis_thisyr_patch(p,s) = -1._r8
crop_inst%leafc_maturity_thisyr_patch(p,s) = -1._r8
crop_inst%reprc_emergence_thisyr_patch(p,s) = -1._r8
crop_inst%reprc_anthesis_thisyr_patch(p,s) = -1._r8
crop_inst%reprc_maturity_thisyr_patch(p,s) = -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
Expand Down Expand Up @@ -2507,6 +2529,9 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , &
! similar changes in CropPhase.
if ((.not. do_harvest) .and. leafout(p) >= huileaf(p) .and. hui(p) < huigrain(p) .and. idpp < mxmat) then
cphase(p) = cphase_leafemerge
if (cphase(p) /= cphase_orig) then
call crop_inst%CropPhaseTransitionBiomass(p, cnveg_carbonstate_inst)
end if
if (abs(onset_counter(p)) > 1.e-6_r8) then
onset_flag(p) = 1._r8
onset_counter(p) = dt
Expand All @@ -2532,6 +2557,9 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , &
! changes to the offset subroutine below

else if (do_harvest) then
cphase(p) = cphase_harvest
call crop_inst%CropPhaseTransitionBiomass(p, cnveg_carbonstate_inst)

! Don't update these if you're just harvesting because of incorrect Dec.
! 31 planting
if (.not. fake_harvest) then
Expand All @@ -2546,10 +2574,27 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , &
crop_inst%sowing_reason_perharv_patch(p, harvest_count(p)) = real(crop_inst%sowing_reason_patch(p), r8)
crop_inst%sowing_reason_patch(p) = -1 ! "Reason for most recent sowing of this patch." So in the line above we save, and here we reset.
crop_inst%harvest_reason_thisyr_patch(p, harvest_count(p)) = harvest_reason
crop_inst%max_tlai_thisyr_patch(p, harvest_count(p)) = crop_inst%max_tlai_patch(p)

! Crop phase transition biomass sizes
crop_inst%frootc_emergence_thisyr_patch(p, harvest_count(p)) = crop_inst%frootc_emergence_patch(p)
crop_inst%frootc_anthesis_thisyr_patch(p, harvest_count(p)) = crop_inst%frootc_anthesis_patch(p)
crop_inst%frootc_maturity_thisyr_patch(p, harvest_count(p)) = crop_inst%frootc_maturity_patch(p)
crop_inst%livecrootc_emergence_thisyr_patch(p, harvest_count(p)) = crop_inst%livecrootc_emergence_patch(p)
crop_inst%livecrootc_anthesis_thisyr_patch(p, harvest_count(p)) = crop_inst%livecrootc_anthesis_patch(p)
crop_inst%livecrootc_maturity_thisyr_patch(p, harvest_count(p)) = crop_inst%livecrootc_maturity_patch(p)
crop_inst%livestemc_emergence_thisyr_patch(p, harvest_count(p)) = crop_inst%livestemc_emergence_patch(p)
crop_inst%livestemc_anthesis_thisyr_patch(p, harvest_count(p)) = crop_inst%livestemc_anthesis_patch(p)
crop_inst%livestemc_maturity_thisyr_patch(p, harvest_count(p)) = crop_inst%livestemc_maturity_patch(p)
crop_inst%leafc_emergence_thisyr_patch(p, harvest_count(p)) = crop_inst%leafc_emergence_patch(p)
crop_inst%leafc_anthesis_thisyr_patch(p, harvest_count(p)) = crop_inst%leafc_anthesis_patch(p)
crop_inst%leafc_maturity_thisyr_patch(p, harvest_count(p)) = crop_inst%leafc_maturity_patch(p)
crop_inst%reprc_emergence_thisyr_patch(p, harvest_count(p)) = crop_inst%reprc_emergence_patch(p)
crop_inst%reprc_anthesis_thisyr_patch(p, harvest_count(p)) = crop_inst%reprc_anthesis_patch(p)
crop_inst%reprc_maturity_thisyr_patch(p, harvest_count(p)) = crop_inst%reprc_maturity_patch(p)
endif

croplive(p) = .false. ! no re-entry in greater if-block
cphase(p) = cphase_harvest
if (tlai(p) > 0._r8) then ! plant had emerged before harvest
offset_flag(p) = 1._r8
offset_counter(p) = dt
Expand Down Expand Up @@ -2580,6 +2625,9 @@ subroutine CropPhenology(num_pcropp, filter_pcropp , &

else if (hui(p) >= huigrain(p)) then
cphase(p) = cphase_grainfill
if (cphase(p) /= cphase_orig) then
call crop_inst%CropPhaseTransitionBiomass(p, cnveg_carbonstate_inst)
end if
bglfr(p) = 1._r8/(leaf_long(ivt(p))*avg_dayspyr*secspday)
end if

Expand Down Expand Up @@ -2809,6 +2857,7 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, &
harvdate => crop_inst%harvdate_patch , & ! Output: [integer (:) ] harvest date
sowing_count => crop_inst%sowing_count , & ! Inout: [integer (:) ] number of sowing events this year for this patch
sowing_reason => crop_inst%sowing_reason_thisyr_patch , & ! Output: [real(r8) (:) ] reason for each sowing this year for this patch
max_tlai => crop_inst%max_tlai_patch , & ! Output: [real(r8) (:) ] maximum total projected leaf area seen this season
gddmaturity => cnveg_state_inst%gddmaturity_patch , & ! Output: [real(r8) (:) ] gdd needed to harvest
idop => cnveg_state_inst%idop_patch , & ! Output: [integer (:) ] date of planting
iyop => cnveg_state_inst%iyop_patch , & ! Output: [integer (:) ] year of planting
Expand Down Expand Up @@ -2932,7 +2981,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
if (did_rx_gdds .and. .not. suppress_gddmaturity_warning) then
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
Expand All @@ -2955,6 +3004,24 @@ subroutine PlantCrop(p, leafcn_in, jday, kyr, do_plant_normal, &
arepr(p,k) = 0._r8
end do

! Initialize other stuff
max_tlai(p) = 0._r8
crop_inst%frootc_emergence_patch(p) = -1._r8
crop_inst%frootc_anthesis_patch(p) = -1._r8
crop_inst%frootc_maturity_patch(p) = -1._r8
crop_inst%livecrootc_emergence_patch(p) = -1._r8
crop_inst%livecrootc_anthesis_patch(p) = -1._r8
crop_inst%livecrootc_maturity_patch(p) = -1._r8
crop_inst%livestemc_emergence_patch(p) = -1._r8
crop_inst%livestemc_anthesis_patch(p) = -1._r8
crop_inst%livestemc_maturity_patch(p) = -1._r8
crop_inst%leafc_emergence_patch(p) = -1._r8
crop_inst%leafc_anthesis_patch(p) = -1._r8
crop_inst%leafc_maturity_patch(p) = -1._r8
crop_inst%reprc_emergence_patch(p) = -1._r8
crop_inst%reprc_anthesis_patch(p) = -1._r8
crop_inst%reprc_maturity_patch(p) = -1._r8

end associate

end subroutine PlantCrop
Expand Down
4 changes: 4 additions & 0 deletions src/biogeochem/CNVegStructUpdateMod.F90
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ subroutine CNVegStructUpdate(bounds,num_soilp, filter_soilp, &
peaklai => cnveg_state_inst%peaklai_patch , & ! Output: [integer (:) ] 1: max allowed lai; 0: not at max

harvdate => crop_inst%harvdate_patch , & ! Input: [integer (:) ] harvest date
max_tlai => crop_inst%max_tlai_patch , & ! Output: [real(r8) (:) ] maximum total projected leaf area seen this season

! *** Key Output from CN***
tlai => canopystate_inst%tlai_patch , & ! Output: [real(r8) (:) ] one-sided leaf area index, no burying by snow
Expand Down Expand Up @@ -260,6 +261,9 @@ subroutine CNVegStructUpdate(bounds,num_soilp, filter_soilp, &
htop(p) = max(0.05_r8, max(htmx(p),htop(p)))
hbot(p) = 0.02_r8

! Maximum LAI seen this season
max_tlai(p) = max(max_tlai(p), tlai(p))

else ! generic crops and ...

! grasses
Expand Down
Loading