Skip to content

Commit e861bea

Browse files
authored
Merge pull request #158 from aquacropos/gdd_switch_type
Gdd switch type
2 parents 3066a2e + 695f117 commit e861bea

20 files changed

+261
-116
lines changed

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -573,3 +573,11 @@ v7_final_tidying.pptx
573573
outputs/
574574

575575
test_test.py
576+
577+
*.png
578+
579+
no-stress-testing.py
580+
581+
temp-delete.py
582+
583+
gdd_compute_crop_cal_testing_200524.py

aquacrop/entities/crop.py

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def __init__(self, c_name, planting_date, harvest_date=None, **kwargs):
6767
self.bface = (
6868
0.001165 # WP co2 adjustment parameter given by FACE experiments
6969
)
70+
self.SwitchGDDType = 'mean' # calculate GDD phenology based on mean of CD phenology across entire simulation period (mean/median)
7071

7172
if c_name == "custom":
7273

@@ -110,6 +111,7 @@ def __init__(self, c_name, planting_date, harvest_date=None, **kwargs):
110111
"PlantMethod",
111112
"CalendarType",
112113
"SwitchGDD",
114+
"SwitchGDDType",
113115
"planting_date",
114116
"harvest_date",
115117
"Emergence",
@@ -258,6 +260,7 @@ def calculate_additional_params(
258260
("PlantMethod", int64),
259261
("CalendarType", int64),
260262
("SwitchGDD", int64),
263+
("SwitchGDDType", str),
261264
("EmergenceCD", int64),
262265
("Canopy10PctCD", int64),
263266
("MaxRootingCD", int64),
@@ -377,6 +380,7 @@ def __init__(
377380
2 # Calendar Type (1 = Calendar days, 2 = Growing degree days)
378381
)
379382
self.SwitchGDD = 0 # Convert calendar to gdd mode if inputs are given in calendar days (0 = No; 1 = Yes)
383+
self.SwitchGDDType = 'mean' # calculate GDD phenology based on mean of CD phenology across entire simulation period (mean/median)
380384

381385
self.EmergenceCD = 0
382386
self.Canopy10PctCD = 0

aquacrop/entities/crops/crop_params.py

+70
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,76 @@
14711471
'p_up2': 0.65,
14721472
'p_up3': 0.75,
14731473
'p_up4': 0.8},
1474+
'SugarBeetGDD_UK': {'Aer': 5.0,
1475+
'CCx': 0.98,
1476+
'CDC': 0.003857,
1477+
'CDC_CD': 0.07128,
1478+
'CGC': 0.010541,
1479+
'CGC_CD': 0.13227,
1480+
'CalendarType': 2,
1481+
'CropType': 2,
1482+
'Determinant': 0.0,
1483+
'ETadj': 1.0,
1484+
'Emergence': 23.0,
1485+
'EmergenceCD': 5.0,
1486+
'Flowering': 0.0,
1487+
'FloweringCD': 0.0,
1488+
'GDD_lo': 0,
1489+
'GDD_up': 9.0,
1490+
'GDDmethod': 3,
1491+
'HI0': 0.75,
1492+
'HIstart': 865.0,
1493+
'HIstartCD': 71.0,
1494+
'Kcb': 1.15,
1495+
'Maturity': 2203.0,
1496+
'MaturityCD': 142.0,
1497+
'MaxRooting': 408.0,
1498+
'MaxRootingCD': 43.0,
1499+
'Name': 'SugarBeetGDD_UK',
1500+
'PlantMethod': 1.0,
1501+
'PlantPop': 100000.0,
1502+
'PolColdStress': 1,
1503+
'PolHeatStress': 1,
1504+
'SeedSize': 1.0,
1505+
'Senescence': 1704.0,
1506+
'SenescenceCD': 116.0,
1507+
'SwitchGDD': 0,
1508+
'SxBotQ': 0.012,
1509+
'SxTopQ': 0.048,
1510+
'Tbase': 3.0,
1511+
'Tmax_lo': 45.0,
1512+
'Tmax_up': 40.0,
1513+
'Tmin_lo': 3.0,
1514+
'Tmin_up': 8.0,
1515+
'TrColdStress': 1,
1516+
'Tupp': 25.0,
1517+
'WP': 18.0,
1518+
'WPy': 100.0,
1519+
'YldForm': 1301.0,
1520+
'YldFormCD': 70.0,
1521+
'YldWC': 20,
1522+
'Zmax': 1.0,
1523+
'Zmin': 0.3,
1524+
'a_HI': 6.0,
1525+
'b_HI': -9.0,
1526+
'dHI0': 20.0,
1527+
'dHI_pre': 0.0,
1528+
'exc': -9.0,
1529+
'fage': 0.15,
1530+
'fshape_r': 1.5,
1531+
'fshape_w1': 3.0,
1532+
'fshape_w2': 3.0,
1533+
'fshape_w3': 3.0,
1534+
'fshape_w4': 1,
1535+
'fsink': 0.5,
1536+
'p_lo1': 0.6,
1537+
'p_lo2': 1,
1538+
'p_lo3': 1,
1539+
'p_lo4': 1,
1540+
'p_up1': 0.2,
1541+
'p_up2': 0.65,
1542+
'p_up3': 0.75,
1543+
'p_up4': 0.8},
14741544
'SugarCane': {'Aer': 5.0,
14751545
'CCx': 0.95,
14761546
'CDC': -9.0,

aquacrop/initialize/compute_crop_calendar.py

+9-32
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pandas as pd
33

44
from ..entities.modelConstants import ModelConstants
5+
from ..utils.prepare_gdd import prepare_gdd
56
from typing import TYPE_CHECKING
67

78
if TYPE_CHECKING:
@@ -14,6 +15,7 @@ def compute_crop_calendar(
1415
crop: "Crop",
1516
clock_struct_planting_dates: "DatetimeIndex",
1617
clock_struct_simulation_start_date: str,
18+
clock_struct_simulation_end_date: str,
1719
clock_struct_time_span: "DatetimeIndex",
1820
weather_df: "DataFrame",
1921
) -> "Crop":
@@ -148,36 +150,11 @@ def compute_crop_calendar(
148150
Tmean = (temp_max + temp_min) / 2
149151
Tmean = Tmean.clip(lower=crop.Tbase)
150152
gdd = Tmean - crop.Tbase
151-
152-
gdd_cum = np.cumsum(gdd)
153-
# Find gdd equivalent for each crop calendar variable
154-
# 1. gdd's from sowing to emergence
155-
crop.Emergence = gdd_cum.iloc[int(crop.EmergenceCD)]
156-
# 2. gdd's from sowing to 10# canopy cover
157-
crop.Canopy10Pct = gdd_cum.iloc[int(crop.Canopy10PctCD)]
158-
# 3. gdd's from sowing to maximum rooting
159-
crop.MaxRooting = gdd_cum.iloc[int(crop.MaxRootingCD)]
160-
# 4. gdd's from sowing to maximum canopy cover
161-
crop.MaxCanopy = gdd_cum.iloc[int(crop.MaxCanopyCD)]
162-
# 5. gdd's from sowing to end of vegetative growth
163-
crop.CanopyDevEnd = gdd_cum.iloc[int(crop.CanopyDevEndCD)]
164-
# 6. gdd's from sowing to senescence
165-
crop.Senescence = gdd_cum.iloc[int(crop.SenescenceCD)]
166-
# 7. gdd's from sowing to maturity
167-
crop.Maturity = gdd_cum.iloc[int(crop.MaturityCD)]
168-
# 8. gdd's from sowing to start of yield_ formation
169-
crop.HIstart = gdd_cum.iloc[int(crop.HIstartCD)]
170-
# 9. gdd's from sowing to start of yield_ formation
171-
crop.HIend = gdd_cum.iloc[int(crop.HIendCD)]
172-
# 10. Duration of yield_ formation (gdd's)
173-
crop.YldForm = crop.HIend - crop.HIstart
174-
175-
# 11. Duration of flowering (gdd's) - (fruit/grain crops only)
176-
if crop.CropType == 3:
177-
# gdd's from sowing to end of flowering
178-
crop.FloweringEnd = gdd_cum.iloc[int(crop.FloweringEndCD)]
179-
# Duration of flowering (gdd's)
180-
crop.Flowering = crop.FloweringEnd - crop.HIstart
153+
154+
crop = prepare_gdd(weather_df,
155+
clock_struct_simulation_start_date,
156+
clock_struct_simulation_end_date,
157+
gdd, crop, crop.SwitchGDDType)
181158

182159
# Convert CGC to gdd mode
183160
# crop.CGC_CD = crop.CGC
@@ -194,15 +171,15 @@ def compute_crop_calendar(
194171
if tCD <= 0:
195172
tCD = 1
196173

197-
CCi = crop.CCx * (1 - 0.05 * (np.exp((crop.CDC_CD / crop.CCx) * tCD) - 1))
174+
CCi = crop.CCx * (1 - 0.05 * (np.exp(((3.33 * crop.CDC_CD) / (crop.CCx + 2.29)) * tCD) - 1))
198175
if CCi < 0:
199176
CCi = 0
200177

201178
tGDD = crop.Maturity - crop.Senescence
202179
if tGDD <= 0:
203180
tGDD = 5
204181

205-
crop.CDC = (crop.CCx / tGDD) * np.log(1 + ((1 - CCi / crop.CCx) / 0.05))
182+
crop.CDC = ((crop.CCx + 2.29) * np.log((((CCi/crop.CCx) - 1) / -0.05) + 1)) / (3.33 * tGDD)
206183
# Set calendar type to gdd mode
207184
crop.CalendarType = 2
208185

aquacrop/initialize/compute_variables.py

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def compute_variables(
8989
crop,
9090
clock_struct.planting_dates,
9191
clock_struct.simulation_start_date,
92+
clock_struct.simulation_end_date,
9293
clock_struct.time_span,
9394
weather_df,
9495
)

aquacrop/initialize/read_model_parameters.py

+1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def read_model_parameters(
110110
crop,
111111
clock_struct.planting_dates,
112112
clock_struct.simulation_start_date,
113+
clock_struct.simulation_end_date,
113114
clock_struct.time_span,
114115
weather_df,
115116
)

aquacrop/utils/prepare_gdd.py

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import numpy as np
2+
import pandas as pd
3+
4+
def prepare_gdd(weather_df, sim_start, sim_end, gdd, crop, sum_fun):
5+
"""
6+
function to read in GDD and crop data to return
7+
mean or median GDD crop growth stages based on all seasons
8+
in the simulation period
9+
Arguments:
10+
weather_df(DataFrame): weather data across entire simulation timespan
11+
12+
sim_start (str): simulation start date
13+
14+
sim_end (str): simulation end date
15+
16+
gdd (numeric vector): daily gdd values across entire simulation time
17+
18+
crop (Crop object): Crop object containing crop parameters
19+
20+
sum_fun (str): 'mean' or 'median', select method of summarising
21+
multiple years of GDD growth stages calculations
22+
Returns:
23+
crop (Crop object): updated crop object with new GDD growth stages
24+
calculated based on entire simulation period
25+
"""
26+
# convert str to datetime
27+
sim_start_date=pd.to_datetime(sim_start)
28+
sim_end_date=pd.to_datetime(sim_end)
29+
30+
# add gdd as column
31+
assert len(gdd) == len(weather_df), "The length of 'gdd' does not match the number of rows in 'weather_df', check planting date is on or after simulation start date in first year."
32+
weather_df['gdd']=gdd
33+
34+
# Convert mm/dd formatted dates to datetime objects
35+
def parse_mmdd_to_datetime(mmdd_date, year):
36+
mm, dd = map(int, mmdd_date.split('/'))
37+
return pd.to_datetime(f'{year}-{mm:02d}-{dd:02d}')
38+
39+
# Initialize the season counter
40+
season_counter = 1
41+
42+
current_year = sim_start_date.year
43+
planting_date=crop.planting_date
44+
45+
# Determine the first planting date
46+
first_planting_date = parse_mmdd_to_datetime(planting_date, current_year)
47+
if first_planting_date < sim_start_date:
48+
current_year += 1
49+
first_planting_date = parse_mmdd_to_datetime(planting_date, current_year)
50+
51+
# Loop through each year to assign seasons
52+
while first_planting_date <= sim_end_date:
53+
# Determine the end date of the season (a day before the next planting date)
54+
next_planting_date = parse_mmdd_to_datetime(planting_date, current_year + 1)
55+
season_end_date = next_planting_date - pd.Timedelta(days=1)
56+
57+
# If the season end date is beyond the simulation end date, truncate it
58+
if season_end_date > sim_end_date:
59+
season_end_date = sim_end_date
60+
61+
# Update the 'season' column for the current season
62+
weather_df.loc[(weather_df['Date'] >= first_planting_date) & (weather_df['Date'] <= season_end_date), 'season'] = season_counter
63+
64+
# Increment the season counter
65+
season_counter += 1
66+
67+
# Move to the next year
68+
current_year += 1
69+
first_planting_date = next_planting_date
70+
71+
# List of growth stages
72+
growth_stages = [
73+
'Emergence', 'Canopy10Pct', 'MaxRooting', 'MaxCanopy', 'CanopyDevEnd',
74+
'Senescence', 'Maturity', 'HIstart', 'HIend', 'YieldFormation'
75+
]
76+
if crop.CropType == 3:
77+
growth_stages.extend(['FloweringEnd', 'FloweringDuration'])
78+
79+
# Create dictionary of lists to store yearly GDD values for each growth stage
80+
# i.e. create as many lists as there are elements in 'growth_stages',
81+
# named with the values in 'growth_stages' in a dictionary
82+
gdd_lists = {f'{stage}': [] for stage in growth_stages}
83+
84+
# get list of season numbers to iterate across
85+
seasons=weather_df['season'].unique()
86+
87+
# iterate across seasons, calculating GDD to each CD growth stage
88+
# per season and storing in the gdd_lists lists
89+
for season in seasons:
90+
# filter to current season
91+
season_data = weather_df[weather_df['season']==season]
92+
93+
# get cumulative GDD for current season
94+
gdd_cum=np.cumsum(season_data['gdd'])
95+
96+
# Find GDD equivalent for each crop calendar day growth stage
97+
gdd_lists['Emergence'].append(gdd_cum.iloc[int(crop.EmergenceCD)])
98+
gdd_lists['Canopy10Pct'].append(gdd_cum.iloc[int(crop.Canopy10PctCD)])
99+
gdd_lists['MaxRooting'].append(gdd_cum.iloc[int(crop.MaxRootingCD)])
100+
gdd_lists['MaxCanopy'].append(gdd_cum.iloc[int(crop.MaxCanopyCD)])
101+
gdd_lists['CanopyDevEnd'].append(gdd_cum.iloc[int(crop.CanopyDevEndCD)])
102+
gdd_lists['Senescence'].append(gdd_cum.iloc[int(crop.SenescenceCD)])
103+
gdd_lists['Maturity'].append(gdd_cum.iloc[int(crop.MaturityCD)])
104+
gdd_lists['HIstart'].append(gdd_cum.iloc[int(crop.HIstartCD)])
105+
gdd_lists['HIend'].append(gdd_cum.iloc[int(crop.HIendCD)])
106+
gdd_lists['YieldFormation'].append(crop.HIend - crop.HIstart)
107+
108+
# Duration of flowering (gdd's) - (fruit/grain crops only)
109+
if crop.CropType == 3:
110+
flowering_end=gdd_cum.iloc[int(crop.FloweringEndCD)]
111+
# gdd's from sowing to end of flowering
112+
gdd_lists['FloweringEnd'].append(flowering_end)
113+
# Duration of flowering (gdd's)
114+
gdd_lists['FloweringDuration'].append(flowering_end - crop.HIstart)
115+
116+
# calculate mean/median of GDD growth stages using dictionary logic,
117+
# set the attribute to update the crop object
118+
if sum_fun == 'mean':
119+
for stage, values in gdd_lists.items():
120+
setattr(crop, stage, np.mean(values))
121+
elif sum_fun == 'median':
122+
for stage, values in gdd_lists.items():
123+
setattr(crop, stage, np.median(values))
124+
125+
return crop

setup.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = aquacrop
3-
version = 3.0.7
3+
version = 3.0.8
44
author = Tim Foster
55
author_email = [email protected]
66
description = Soil-Crop-Water model based on AquaCrop-OS.

tests/test_bug.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"""
22
This test is for bug testing
33
"""
4+
import os
5+
os.environ['DEVELOPMENT'] = 'True'
46
import unittest
57

68
from aquacrop import AquaCropModel, Soil, Crop, InitialWaterContent, GroundWater
79
from aquacrop.utils import prepare_weather, get_filepath
810

9-
import os
10-
os.environ['DEVELOPMENT'] = 'True'
11+
1112

1213

1314
class TestModelExceptions(unittest.TestCase):

tests/test_co2_timeseries.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
"""
22
Test exeptions in the model.
33
"""
4+
import os
5+
os.environ['DEVELOPMENT'] = 'True'
46
import unittest
57
import pandas as pd
8+
9+
610
from aquacrop import AquaCropModel, Soil, Crop, InitialWaterContent, GroundWater, CO2
711
from aquacrop.utils import prepare_weather, get_filepath
812

9-
import os
10-
os.environ['DEVELOPMENT'] = 'True'
13+
1114

1215
class TestModelExceptions(unittest.TestCase):
1316
"""

0 commit comments

Comments
 (0)