| import numpy as np
|
| import pandas as pd
|
|
|
| from ..entities.modelConstants import ModelConstants
|
| from ..utils.prepare_gdd import prepare_gdd
|
| from typing import TYPE_CHECKING
|
|
|
| if TYPE_CHECKING:
|
|
|
| from aquacrop.entities.crop import Crop
|
| from pandas import DatetimeIndex, DataFrame
|
|
|
|
|
| def compute_crop_calendar(
|
| crop: "Crop",
|
| clock_struct_planting_dates: "DatetimeIndex",
|
| clock_struct_simulation_start_date: str,
|
| clock_struct_simulation_end_date: str,
|
| clock_struct_time_span: "DatetimeIndex",
|
| weather_df: "DataFrame",
|
| ) -> "Crop":
|
| """
|
| Function to compute additional parameters needed to define crop phenological calendar
|
|
|
| <a href="https://www.fao.org/3/BR248E/br248e.pdf#page=28" target="_blank">Reference Manual</a> (pg. 19-20)
|
|
|
|
|
| Arguments:
|
|
|
| crop (Crop): Crop object containing crop paramaters
|
|
|
| clock_struct_planting_dates (DatetimeIndex): list of planting dates
|
|
|
| clock_struct_simulation_start_date (str): sim start date
|
|
|
| clock_struct_time_span (DatetimeIndex): all dates between sim start and end dates
|
|
|
| weather_df (DataFrame): weather data for simulation period
|
|
|
|
|
| Returns:
|
|
|
| crop (Crop): updated Crop object
|
|
|
|
|
|
|
| """
|
|
|
| if len(clock_struct_planting_dates) == 0:
|
| plant_year = pd.DatetimeIndex([clock_struct_simulation_start_date]).year[0]
|
| if (
|
| pd.to_datetime(str(plant_year) + "/" + crop.planting_date)
|
| < clock_struct_simulation_start_date
|
| ):
|
| pl_date = str(plant_year + 1) + "/" + crop.planting_date
|
| else:
|
| pl_date = str(plant_year) + "/" + crop.planting_date
|
| else:
|
| pl_date = clock_struct_planting_dates[0]
|
|
|
|
|
| Mode = crop.CalendarType
|
|
|
|
|
| if Mode == 1:
|
|
|
|
|
| if crop.Determinant == 1:
|
| crop.CanopyDevEndCD = round(crop.HIstartCD + (crop.FloweringCD / 2))
|
| else:
|
| crop.CanopyDevEndCD = crop.SenescenceCD
|
|
|
|
|
| crop.Canopy10PctCD = round(
|
| crop.EmergenceCD + (np.log(0.1 / crop.CC0) / crop.CGC_CD)
|
| )
|
|
|
|
|
| crop.MaxCanopyCD = round(
|
| crop.EmergenceCD
|
| + (
|
| np.log(
|
| (0.25 * crop.CCx * crop.CCx / crop.CC0)
|
| / (crop.CCx - (0.98 * crop.CCx))
|
| )
|
| / crop.CGC_CD
|
| )
|
| )
|
|
|
|
|
| crop.HIendCD = crop.HIstartCD + crop.YldFormCD
|
|
|
|
|
|
|
| crop.Emergence = crop.EmergenceCD
|
| crop.Canopy10Pct = crop.Canopy10PctCD
|
| crop.MaxRooting = crop.MaxRootingCD
|
| crop.Senescence = crop.SenescenceCD
|
| crop.Maturity = crop.MaturityCD
|
| crop.MaxCanopy = crop.MaxCanopyCD
|
| crop.CanopyDevEnd = crop.CanopyDevEndCD
|
| crop.HIstart = crop.HIstartCD
|
| crop.HIend = crop.HIendCD
|
| crop.YldForm = crop.YldFormCD
|
| if crop.CropType == 3:
|
| crop.FloweringEndCD = crop.HIstartCD + crop.FloweringCD
|
|
|
|
|
| else:
|
| crop.FloweringEnd = ModelConstants.NO_VALUE
|
| crop.FloweringEndCD = ModelConstants.NO_VALUE
|
| crop.FloweringCD = ModelConstants.NO_VALUE
|
|
|
|
|
| if crop.SwitchGDD == 1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| date_range = pd.date_range(pl_date, clock_struct_time_span[-1])
|
| weather_df = weather_df.copy()
|
| weather_df.index = weather_df.Date
|
| weather_df = weather_df.loc[date_range]
|
| temp_min = weather_df.MinTemp
|
| temp_max = weather_df.MaxTemp
|
|
|
|
|
| if crop.GDDmethod == 1:
|
|
|
| Tmean = (temp_max + temp_min) / 2
|
| Tmean = Tmean.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| gdd = Tmean - crop.Tbase
|
|
|
| elif crop.GDDmethod == 2:
|
|
|
| temp_max = temp_max.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| temp_min = temp_min.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| Tmean = (temp_max + temp_min) / 2
|
| gdd = Tmean - crop.Tbase
|
|
|
| elif crop.GDDmethod == 3:
|
|
|
| temp_max = temp_max.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| temp_min = temp_min.clip(upper=crop.Tupp)
|
| Tmean = (temp_max + temp_min) / 2
|
| Tmean = Tmean.clip(lower=crop.Tbase)
|
| gdd = Tmean - crop.Tbase
|
|
|
| crop = prepare_gdd(weather_df,
|
| clock_struct_simulation_start_date,
|
| clock_struct_simulation_end_date,
|
| gdd, crop, crop.SwitchGDDType)
|
|
|
|
|
|
|
| crop.CGC = (
|
| np.log(
|
| (((0.98 * crop.CCx) - crop.CCx) * crop.CC0)
|
| / (-0.25 * (crop.CCx**2))
|
| )
|
| ) / (-(crop.MaxCanopy - crop.Emergence))
|
|
|
|
|
|
|
| tCD = crop.MaturityCD - crop.SenescenceCD
|
| if tCD <= 0:
|
| tCD = 1
|
|
|
| CCi = crop.CCx * (1 - 0.05 * (np.exp(((3.33 * crop.CDC_CD) / (crop.CCx + 2.29)) * tCD) - 1))
|
| if CCi < 0:
|
| CCi = 0
|
|
|
| tGDD = crop.Maturity - crop.Senescence
|
| if tGDD <= 0:
|
| tGDD = 5
|
|
|
| crop.CDC = ((crop.CCx + 2.29) * np.log((((CCi/crop.CCx) - 1) / -0.05) + 1)) / (3.33 * tGDD)
|
|
|
| crop.CalendarType = 2
|
|
|
| else:
|
| crop.CDC = crop.CDC_CD
|
| crop.CGC = crop.CGC_CD
|
|
|
| elif Mode == 2:
|
|
|
|
|
| if crop.Determinant == 1:
|
| crop.CanopyDevEnd = round(crop.HIstart + (crop.Flowering / 2))
|
| else:
|
| crop.CanopyDevEnd = crop.Senescence
|
|
|
|
|
| crop.Canopy10Pct = round(crop.Emergence + (np.log(0.1 / crop.CC0) / crop.CGC))
|
|
|
|
|
| crop.MaxCanopy = round(
|
| crop.Emergence
|
| + (
|
| np.log(
|
| (0.25 * crop.CCx * crop.CCx / crop.CC0)
|
| / (crop.CCx - (0.98 * crop.CCx))
|
| )
|
| / crop.CGC
|
| )
|
| )
|
|
|
|
|
| crop.HIend = crop.HIstart + crop.YldForm
|
|
|
|
|
| if crop.CropType == 3:
|
| crop.FloweringEnd = crop.HIstart + crop.Flowering
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| date_range = pd.date_range(pl_date, clock_struct_time_span[-1])
|
| weather_df = weather_df.copy()
|
| weather_df.index = weather_df.Date
|
|
|
| weather_df = weather_df.loc[date_range]
|
| temp_min = weather_df.MinTemp
|
| temp_max = weather_df.MaxTemp
|
|
|
|
|
| if crop.GDDmethod == 1:
|
|
|
| Tmean = (temp_max + temp_min) / 2
|
| Tmean = Tmean.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| gdd = Tmean - crop.Tbase
|
|
|
| elif crop.GDDmethod == 2:
|
|
|
| temp_max = temp_max.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| temp_min = temp_min.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| Tmean = (temp_max + temp_min) / 2
|
| gdd = Tmean - crop.Tbase
|
|
|
| elif crop.GDDmethod == 3:
|
|
|
| temp_max = temp_max.clip(lower=crop.Tbase, upper=crop.Tupp)
|
| temp_min = temp_min.clip(upper=crop.Tupp)
|
| Tmean = (temp_max + temp_min) / 2
|
| Tmean = Tmean.clip(lower=crop.Tbase)
|
| gdd = Tmean - crop.Tbase
|
|
|
| gdd_cum = np.cumsum(gdd).reset_index(drop=True)
|
|
|
| assert (
|
| gdd_cum.values[-1] > crop.Maturity
|
| ), f"not enough growing degree days in simulation ({gdd_cum.values[-1]}) to reach maturity ({crop.Maturity})"
|
|
|
| crop.MaturityCD = (gdd_cum > crop.Maturity).idxmax() + 1
|
|
|
| assert crop.MaturityCD < 365, "crop will take longer than 1 year to mature"
|
|
|
|
|
| crop.MaxCanopyCD = (gdd_cum > crop.MaxCanopy).idxmax() + 1
|
|
|
| crop.CanopyDevEndCD = (gdd_cum > crop.CanopyDevEnd).idxmax() + 1
|
|
|
| crop.HIstartCD = (gdd_cum > crop.HIstart).idxmax() + 1
|
|
|
| crop.HIendCD = (gdd_cum > crop.HIend).idxmax() + 1
|
|
|
| crop.YldFormCD = crop.HIendCD - crop.HIstartCD
|
| if crop.CropType == 3:
|
|
|
| FloweringEnd = (gdd_cum > crop.FloweringEnd).idxmax() + 1
|
|
|
| crop.FloweringCD = FloweringEnd - crop.HIstartCD
|
| else:
|
| crop.FloweringCD = ModelConstants.NO_VALUE
|
|
|
| return crop
|
|
|