From 3156c2f47b51ba6cc7c91f32475694c6f0bcdf99 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Tue, 17 Sep 2024 19:26:23 +1200 Subject: [PATCH 01/39] Ignore output folder in prototypes --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a1f7b706fe..0b5a6b6937 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ Prototypes/SCRUM/.ipynb_checkpoints/ParameterListBuilder-checkpoint.ipynb Prototypes/SCRUM/Crop.xml Prototypes/SCRUM/SCRUM.apsimx.orig Prototypes/Grapevine/Observations/PhenoDataObservation - Copy.xlsx +Prototypes/**/Outputs*/ lib/ .vs/ .Rproj.user From c463ff4413cfa5f4b71028a9ec047552c2146539 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 00:15:30 +1200 Subject: [PATCH 02/39] Update summary tags, fixe typos and clarified/standardised a few descriptions. Plus added a few Unit tags --- Models/Climate/Weather.cs | 254 +++++++++++++++++++------------------- 1 file changed, 124 insertions(+), 130 deletions(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 69f27fc654..b7135ec7ff 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -12,7 +12,7 @@ namespace Models.Climate { /// - /// Reads in weather data and makes it available to other models. + /// Reads in weather data from a met file and makes it available to other models /// [Serializable] [ViewName("UserInterface.Views.TabbedMetDataView")] @@ -22,13 +22,13 @@ namespace Models.Climate public class Weather : Model, IWeather, IReferenceExternalFiles { /// - /// A link to the clock model. + /// A link to the clock model /// [Link] private IClock clock = null; /// - /// A link to the the summary + /// A link to the summary (log file) /// [Link] private ISummary summary = null; @@ -70,12 +70,12 @@ public class Weather : Model, IWeather, IReferenceExternalFiles private int rainfallHoursIndex; /// - /// The index of the vapor pressure column in the weather file + /// The index of the vapour pressure column in the weather file /// private int vapourPressureIndex; /// - /// The index of the wind column in the weather file + /// The index of the wind speed column in the weather file /// private int windIndex; @@ -86,32 +86,32 @@ public class Weather : Model, IWeather, IReferenceExternalFiles private int co2Index; /// - /// The index of the DiffuseFraction column in the weather file + /// The index of the diffuse radiation fraction column in the weather file /// private int DiffuseFractionIndex; /// - /// The index of the DayLength column in the weather file + /// The index of the day length column in the weather file /// private int dayLengthIndex; /// - /// This event will be invoked immediately before models get their weather data. - /// models and scripts an opportunity to change the weather data before other models - /// reads it. + /// Event that will be invoked immediately before the daily weather data is updated. + /// This provides models and scripts an opportunity to change the weather data before + /// other models access them /// public event EventHandler PreparingNewWeatherData; /// - /// Optional constants file name. This should only be accessed via + /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between - /// relative/absolute paths. + /// relative/absolute paths /// private string constantsFile; /// - /// A LinkedList of weather that has previously been read. - /// Stored in order of date from newest to oldest as most recent weather is most common to search for + /// A LinkedList of weather that has previously been read. The data is stored in order of + /// date, from newest to oldest, as most recent weather is most likely to be searched for /// private LinkedList weatherCache = new LinkedList(); @@ -123,8 +123,9 @@ public class Weather : Model, IWeather, IReferenceExternalFiles private double co2Value { get; set; } /// - /// Allows to specify a second file which contains constants such as lat, long, - /// tav, amp, etc. Really only used when the actual met data is in a .csv file. + /// Gets or sets the optional constants file name. Allows to specify a second file which + /// contains constants such as latitude, longitude, tav, amp, etc.; really only used when + /// the actual met data is in a .csv file /// [Description("Constants file")] public string ConstantsFile @@ -154,14 +155,14 @@ public string ConstantsFile } /// - /// Gets or sets the file name. Should be relative filename where possible. + /// Gets or sets the weather file name. Should be relative file path where possible /// [Summary] [Description("Weather file name")] public string FileName { get; set; } /// - /// Gets or sets the full file name (with path). The user interface uses this. + /// Gets or sets the full file name (with path). Needed for the user interface /// [JsonIgnore] public string FullFileName @@ -191,7 +192,7 @@ public string FullFileName } /// - /// Used to hold the WorkSheet Name if data retrieved from an Excel file + /// Gets or sets the WorkSheet name with weather data, if data is supplied as an Excel file /// public string ExcelWorkSheetName { get; set; } @@ -224,28 +225,28 @@ public DateTime EndDate } /// - /// Gets or sets the maximum temperature (oC) + /// Gets or sets the maximum air temperature (oC) /// [Units("°C")] [JsonIgnore] public double MaxT { get; set; } /// - /// Gets or sets the minimum temperature (oC) + /// Gets or sets the minimum air temperature (oC) /// [JsonIgnore] [Units("°C")] public double MinT { get; set; } /// - /// Daily Mean temperature (oC) + /// Daily mean air temperature (oC) /// [Units("°C")] [JsonIgnore] public double MeanT { get { return (MaxT + MinT) / 2; } } /// - /// Daily mean VPD (hPa) + /// Gets the daily mean vapour pressure deficit (hPa) /// [Units("hPa")] [JsonIgnore] @@ -265,7 +266,7 @@ public double VPD } /// - /// days since winter solstice (day) + /// Gets the day for the winter solstice (day) /// [Units("day")] [JsonIgnore] @@ -292,75 +293,79 @@ public int WinterSolsticeDOY private bool First = true; /// - /// Number of days lapsed since the winter solstice + /// Gets the number of days since the winter solstice /// [Units("d")] [JsonIgnore] public int DaysSinceWinterSolstice { get; set; } /// - /// Maximum clear sky radiation (MJ/m2) + /// Gets or sets the maximum clear sky radiation (MJ/m2) /// - [Units("MJ/M2")] + [Units("MJ/m2")] [JsonIgnore] public double Qmax { get; set; } /// - /// Gets or sets the rainfall (mm) + /// Gets or sets the rainfall amount (mm) /// [Units("mm")] [JsonIgnore] public double Rain { get; set; } /// - /// Gets or sets the solar radiation. MJ/m2/day + /// Gets or sets the solar radiation (MJ/m2) /// - [Units("MJ/m^2/d")] + [Units("MJ/m2")] [JsonIgnore] public double Radn { get; set; } /// - /// Gets or sets the Pan Evaporation (mm) (Class A pan) + /// Gets or sets the class A pan evaporation (mm) /// [Units("mm")] [JsonIgnore] public double PanEvap { get; set; } /// - /// Gets or sets the number of hours rainfall occured in + /// Gets or sets the number duration of rainfall within a day (h) /// + [Units("h")] [JsonIgnore] public double RainfallHours { get; set; } /// - /// Gets or sets the vapor pressure (hPa) + /// Gets or sets the air vapour pressure (hPa) /// [Units("hPa")] [JsonIgnore] public double VP { get; set; } /// - /// Gets or sets the wind value found in weather file or zero if not specified. (code says 3.0 not zero) + /// Gets or sets the average wind speed (m/s) /// + [Units("m/s")] [JsonIgnore] public double Wind { get; set; } /// - /// Gets or sets the DF value found in weather file or zero if not specified + /// Gets or sets the diffuse radiation fraction (0-1) /// [Units("0-1")] [JsonIgnore] public double DiffuseFraction { get; set; } /// - /// Gets or sets the Daylength value found in weather file or zero if not specified + /// Gets or sets the day length, period with light (h) /// + [Units("h")] [JsonIgnore] public double DayLength { get; set; } /// - /// Gets or sets the CO2 level. If not specified in the weather file the default is 350. + /// Gets or sets the CO2 level in the atmosphere (ppm) /// + [Units("ppm")] [JsonIgnore] public double CO2 { get @@ -377,15 +382,16 @@ public double CO2 { } /// - /// Gets or sets the atmospheric air pressure. If not specified in the weather file the default is 1010 hPa. + /// Gets or sets the mean atmospheric air pressure /// [Units("hPa")] [JsonIgnore] public double AirPressure { get; set; } /// - /// Gets the latitude + /// Gets the latitude (decimal degrees) /// + [Units("degrees")] public double Latitude { get @@ -398,8 +404,9 @@ public double Latitude } /// - /// Gets the longitude + /// Gets the longitude (decimal degrees) /// + [Units("degrees")] public double Longitude { get @@ -412,9 +419,9 @@ public double Longitude } /// - /// Gets the average temperature + /// Gets the long-term average air temperature (oC) /// - [Units("°C")] + [Units("oC")] public double Tav { get @@ -429,7 +436,7 @@ public double Tav } /// - /// Gets the temperature amplitude. + /// Gets the long-term average temperature amplitude (oC) /// public double Amp { @@ -444,33 +451,33 @@ public double Amp } } - /// Met Data from yesterday + /// Met data from yesterday [JsonIgnore] public DailyMetDataFromFile YesterdaysMetData { get { return GetAdjacentMetData(-1); } } - /// Met Data for Today + /// Met data for today [JsonIgnore] public DailyMetDataFromFile TodaysMetData { get; set; } - /// Met Data from yesterday + /// Met data for tomorrow [JsonIgnore] public DailyMetDataFromFile TomorrowsMetData { get { return GetAdjacentMetData(1); } } - /// First date of summer. + /// Gets or sets the first date of summer (dd-mmm) [JsonIgnore] - public string FirstDateOfSummer { get; set; } = "1-dec"; + public string FirstDateOfSummer { get; set; } = "1-Dec"; - /// First date of autumn / fall. + /// Gets or sets the first date of autumn (dd-mmm) [JsonIgnore] - public string FirstDateOfAutumn { get; set; } = "1-mar"; + public string FirstDateOfAutumn { get; set; } = "1-Mar"; - /// First date of winter. + /// Gets or sets the first date of winter (dd-mmm) [JsonIgnore] - public string FirstDateOfWinter { get; set; } = "1-jun"; + public string FirstDateOfWinter { get; set; } = "1-Jun"; - /// First date of spring. + /// Gets or sets the first date of spring (dd-mmm) [JsonIgnore] - public string FirstDateOfSpring { get; set; } = "1-sep"; + public string FirstDateOfSpring { get; set; } = "1-Sep"; /// /// Temporarily stores which tab is currently displayed. @@ -488,31 +495,31 @@ public double Amp /// [JsonIgnore] public int ShowYears = 1; - /// Start of spring event. + /// Event that will be invoked at the start of spring public event EventHandler StartOfSpring; - /// Start of summer event. + /// Event that will be invoked at the start of summer public event EventHandler StartOfSummer; - /// Start of autumn/fall event. + /// Event that will be invoked at the start of autumn public event EventHandler StartOfAutumn; - /// Start of winter event. + /// Event that will be invoked at the start of winter public event EventHandler StartOfWinter; - /// End of spring event. + /// Event that will be invoked at the end of spring public event EventHandler EndOfSpring; - /// End of summer event. + /// Event that will be invoked at the end of summer public event EventHandler EndOfSummer; - /// End of autumn/fall event. + /// Event that will be invoked at the end of autumn public event EventHandler EndOfAutumn; - /// End of winter event. + /// Event that will be invoked at the end of winter public event EventHandler EndOfWinter; - /// Name of current season. + /// Name of current season public string Season { get @@ -528,21 +535,21 @@ public string Season } } - /// Return our input filenames + /// Returns our input file names public IEnumerable GetReferencedFileNames() { return new string[] { FullFileName }; } - /// Remove all paths from referenced filenames. + /// Remove all paths from referenced file names public void RemovePathsFromReferencedFileNames() { FileName = Path.GetFileName(FileName); } - /// - /// Gets the duration of the day in hours. - /// + /// Computes the duration of the day, with light (hours) + /// The angle to measure time for twilight (degrees) + /// The length of day light public double CalculateDayLength(double Twilight) { if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant @@ -551,14 +558,14 @@ public double CalculateDayLength(double Twilight) return this.DayLength; } - /// calculate the time of sun rise + /// Computes the time of sun rise (h) /// Sun rise time public double CalculateSunRise() { return 12 - CalculateDayLength(-6) / 2; } - /// calculate the time of sun set + /// Computes the time of sun set (h) /// Sun set time public double CalculateSunSet() { @@ -566,7 +573,7 @@ public double CalculateSunSet() } /// - /// Check values in weather and return a collection of warnings. + /// Check values in weather and return a collection of warnings /// public IEnumerable Validate() { @@ -576,9 +583,9 @@ public IEnumerable Validate() } } - /// - /// Overrides the base class method to allow for initialization. - /// + /// Overrides the base class method to allow for initialization of this model + /// The sender of the event + /// The arguments of the event [EventSubscribe("Commencing")] private void OnSimulationCommencing(object sender, EventArgs e) { @@ -621,7 +628,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) } /// - /// Perform the necessary initialisation at the start of simulation. + /// Performs the necessary initialisation at the start of simulation /// [EventSubscribe("StartOfSimulation")] private void OnStartOfSimulation(object sender, EventArgs e) @@ -629,9 +636,9 @@ private void OnStartOfSimulation(object sender, EventArgs e) First = true; } - /// - /// Overrides the base class method to allow for clean up - /// + /// Overrides the base class method to allow for clean up task + /// The sender of the event + /// The arguments of the event [EventSubscribe("Completed")] private void OnSimulationCompleted(object sender, EventArgs e) { @@ -640,9 +647,7 @@ private void OnSimulationCompleted(object sender, EventArgs e) this.reader = null; } - /// - /// Get the DataTable view of this data - /// + /// Get the DataTable view of the weather data /// The DataTable public DataTable GetAllData() { @@ -664,9 +669,7 @@ public DataTable GetAllData() return null; } - /// - /// An event handler for the daily DoWeather event. - /// + /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event [EventSubscribe("DoWeather")] @@ -716,12 +719,12 @@ private void OnDoWeather(object sender, EventArgs e) } /// - /// Method to read one days met data in from file + /// Reads the weather data for one day from file /// - /// the date to read met data + /// The date to read met data public DailyMetDataFromFile GetMetData(DateTime date) { - //check if we've looked at this date before + // check if we've looked at this date before WeatherRecordEntry previousEntry = null; foreach (WeatherRecordEntry entry in this.weatherCache) { @@ -740,7 +743,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) if (!this.OpenDataFile()) throw new ApsimXException(this, "Cannot find weather file '" + this.FileName + "'"); - //get weather for that date + // get the weather data for that date DailyMetDataFromFile readMetData = new DailyMetDataFromFile(); try { @@ -755,8 +758,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) if (date != this.reader.GetDateFromValues(readMetData.Raw)) throw new Exception("Non consecutive dates found in file: " + this.FileName + "."); - //since this data was valid, store in our cache for next time - + // since this data was valid, store in our cache for next time WeatherRecordEntry record = new WeatherRecordEntry(); record.Date = date; record.MetData = readMetData; @@ -765,10 +767,12 @@ public DailyMetDataFromFile GetMetData(DateTime date) else this.weatherCache.AddFirst(record); - return CheckDailyMetData(readMetData); } + /// Checks the values for weather data, uses either daily values or a constant + /// The weather data structure with values for one line + /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { @@ -819,7 +823,7 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) if (this.DiffuseFractionIndex == -1) { - // Estimate Diffuse Fraction using the Approach of Bristow and Campbell + // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -831,7 +835,7 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) else readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[this.DiffuseFractionIndex], CultureInfo.InvariantCulture); - if (this.dayLengthIndex == -1) // Daylength is not a column - check for a constant + if (this.dayLengthIndex == -1) // DayLength is not a column - check for a constant { if (this.reader.Constant("daylength") != null) readMetData.DayLength = this.reader.ConstantAsDouble("daylength"); @@ -844,9 +848,7 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) return readMetData; } - /// - /// An event handler for the start of day event. - /// + /// Performs tasks at the start of the day /// The sender of the event /// The arguments of the event [EventSubscribe("StartOfDay")] @@ -865,9 +867,7 @@ private void OnStartOfDay(object sender, EventArgs e) StartOfSpring.Invoke(this, e); } - /// - /// An event handler for the end of day event. - /// + /// Performs the tasks for the end of the day /// The sender of the event /// The arguments of the event [EventSubscribe("EndOfDay")] @@ -886,9 +886,7 @@ private void OnEndOfDay(object sender, EventArgs e) EndOfSpring.Invoke(this, e); } - /// - /// Open the weather data file. - /// + /// Opens the weather data file /// True if the file was successfully opened public bool OpenDataFile() { @@ -966,7 +964,7 @@ public bool OpenDataFile() } } - /// Close the datafile. + /// Closes the data file public void CloseDataFile() { if (reader != null) @@ -974,10 +972,8 @@ public void CloseDataFile() reader = null; } - /// - /// Read a user-defined value from today's weather data. - /// - /// Name of the column. + /// Read a user-defined variable from today's weather data + /// Name of the column/variable to retrieve public double GetValue(string columnName) { int columnIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, columnName); @@ -987,8 +983,8 @@ public double GetValue(string columnName) } /// - /// Calculate the amp and tav 'constant' values for this weather file - /// and store the values into the File constants. + /// Calculates the values for the constants Tav and Amp for this weather file + /// and store the values into the reader constants list. /// private void CalcTAVAMP() { @@ -1010,7 +1006,7 @@ private void CalcTAVAMP() } /// - /// Calculate the amp and tav 'constant' values for this weather file. + /// Calculates the values for the constants Tav and Amp for this weather file /// /// The calculated tav value /// The calculated amp value @@ -1097,55 +1093,53 @@ private void ProcessMonthlyTAVAMP(out double tav, out double amp) reader.SeekToPosition(savedPosition); } - /// - /// Does a santiy check on this weather data to check that temperatures, - /// VP, radition and rain are potentially valid numbers. - /// Also checks that every day has weather. - /// + /// Checks the weather data to ensure values are valid/sensible + /// + /// This will send an error message if: + /// - MinT is less than MaxT + /// - Radn is greater than 0.0 or greater than 40.0 + /// - Rain is less than 0.0 + /// - VP is less or equal to 0.0 + /// Also checks that every day has weather + /// /// The clock /// The weather private void SensibilityCheck(Clock clock, Weather weatherToday) { - //things to check: - //Mint > MaxtT - //VP(if present) <= 0 - //Radn < 0 or Radn > 40 - //Rain < 0 if (weatherToday.MinT > weatherToday.MaxT) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has higher minimum temperature (" + weatherToday.MinT + ") than maximum (" + weatherToday.MaxT + ")", MessageType.Warning); } if (weatherToday.VP <= 0) { - summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has Vapor Pressure (" + weatherToday.VP + ") which is below 0", MessageType.Warning); + summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0", MessageType.Warning); } if (weatherToday.Radn < 0) { - summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative solar raditation (" + weatherToday.Radn + ")", MessageType.Warning); + summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative solar radiation (" + weatherToday.Radn + ")", MessageType.Warning); } if (weatherToday.Radn > 40) { - summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has solar raditation (" + weatherToday.Radn + ") which is above 40", MessageType.Warning); + summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has solar radiation (" + weatherToday.Radn + ") which is above 40", MessageType.Warning); } if (weatherToday.Rain < 0) { - summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative ranfaill (" + weatherToday.Radn + ")", MessageType.Warning); + summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative rainfall (" + weatherToday.Radn + ")", MessageType.Warning); } return; } - /// - /// Returns an adjacent day's weather data as defined by the offset. - /// Will return today's data if yesterday is not a valid entry in the weather file. - /// + /// Returns the weather data for a date defined by an offset from today + /// + /// Will return today's data if the offset given would make the date sit outside those + /// available in the weather file + /// /// The number of days away from today private DailyMetDataFromFile GetAdjacentMetData(int offset) { if ((this.clock.Today.Equals(this.reader.FirstDate) && offset < 0) || this.clock.Today.Equals(this.reader.LastDate) && offset > 0) { - //in the case that we try to get yesterdays/tomorrows weather and today is the same as the start or end of the weather file - //we should instead return today's weather summary.WriteMessage(this, "Warning: Weather on " + this.clock.Today.AddDays(offset).ToString("d") + " does not exist. Today's weather on " + this.clock.Today.ToString("d") + " was used instead.", MessageType.Warning); return GetMetData(this.clock.Today); } From dbf5b71b0be0bd8e05161edd3e700314c99fc8e6 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 00:31:20 +1200 Subject: [PATCH 03/39] organise the variables a bit so that similar types are together, Few more typos and trailing spaces --- Models/Climate/Weather.cs | 215 +++++++++++++++----------------------- 1 file changed, 86 insertions(+), 129 deletions(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index b7135ec7ff..a8093120ea 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -34,11 +34,48 @@ public class Weather : Model, IWeather, IReferenceExternalFiles private ISummary summary = null; /// - /// A reference to the text file reader object + /// Event that will be invoked immediately before the daily weather data is updated /// + /// + /// This provides models and scripts an opportunity to change the weather data before + /// other models access them + /// + public event EventHandler PreparingNewWeatherData; + + /// Event that will be invoked at the start of spring + public event EventHandler StartOfSpring; + + /// Event that will be invoked at the start of summer + public event EventHandler StartOfSummer; + + /// Event that will be invoked at the start of autumn + public event EventHandler StartOfAutumn; + + /// Event that will be invoked at the start of winter + public event EventHandler StartOfWinter; + + /// Event that will be invoked at the end of spring + public event EventHandler EndOfSpring; + + /// Event that will be invoked at the end of summer + public event EventHandler EndOfSummer; + + /// Event that will be invoked at the end of autumn + public event EventHandler EndOfAutumn; + + /// Event that will be invoked at the end of winter + public event EventHandler EndOfWinter; + + /// A reference to the text file reader object [NonSerialized] private ApsimTextFile reader = null; + /// + /// A LinkedList of weather that has previously been read. The data is stored in order of + /// date, from newest to oldest, as most recent weather is most likely to be searched for + /// + private LinkedList weatherCache = new LinkedList(); + /// /// The index of the maximum temperature column in the weather file /// @@ -96,32 +133,19 @@ public class Weather : Model, IWeather, IReferenceExternalFiles private int dayLengthIndex; /// - /// Event that will be invoked immediately before the daily weather data is updated. - /// This provides models and scripts an opportunity to change the weather data before - /// other models access them + /// Stores the CO2 value from either the default 350 or from a column in met file. Public property can then also check + /// if this value was supplied by a constant /// - public event EventHandler PreparingNewWeatherData; + [JsonIgnore] + private double co2Value { get; set; } /// /// Stores the optional constants file name. This should only be accessed via - /// , which handles conversion between + /// , which handles conversion between /// relative/absolute paths /// private string constantsFile; - /// - /// A LinkedList of weather that has previously been read. The data is stored in order of - /// date, from newest to oldest, as most recent weather is most likely to be searched for - /// - private LinkedList weatherCache = new LinkedList(); - - /// - /// Stores the CO2 value from either the default 350 or from a column in met file. Public property can then also check - /// if this value was supplied by a constant - /// - [JsonIgnore] - private double co2Value { get; set; } - /// /// Gets or sets the optional constants file name. Allows to specify a second file which /// contains constants such as latitude, longitude, tav, amp, etc.; really only used when @@ -196,9 +220,7 @@ public string FullFileName /// public string ExcelWorkSheetName { get; set; } - /// - /// Gets the start date of the weather file - /// + /// Gets the start date of the weather file public DateTime StartDate { get @@ -210,9 +232,7 @@ public DateTime StartDate } } - /// - /// Gets the end date of the weather file - /// + /// Gets the end date of the weather file public DateTime EndDate { get @@ -224,30 +244,22 @@ public DateTime EndDate } } - /// - /// Gets or sets the maximum air temperature (oC) - /// + /// Gets or sets the maximum air temperature (oC) [Units("°C")] [JsonIgnore] public double MaxT { get; set; } - /// - /// Gets or sets the minimum air temperature (oC) - /// + /// Gets or sets the minimum air temperature (oC) [JsonIgnore] [Units("°C")] public double MinT { get; set; } - /// - /// Daily mean air temperature (oC) - /// + /// Daily mean air temperature (oC) [Units("°C")] [JsonIgnore] public double MeanT { get { return (MaxT + MinT) / 2; } } - /// - /// Gets the daily mean vapour pressure deficit (hPa) - /// + /// Gets the daily mean vapour pressure deficit (hPa) [Units("hPa")] [JsonIgnore] public double VPD @@ -265,9 +277,7 @@ public double VPD } } - /// - /// Gets the day for the winter solstice (day) - /// + /// Gets the day for the winter solstice (day) [Units("day")] [JsonIgnore] public int WinterSolsticeDOY @@ -291,83 +301,64 @@ public int WinterSolsticeDOY } } + /// Flag whether we're one the first day of simulation private bool First = true; - /// - /// Gets the number of days since the winter solstice - /// + + /// Gets the number of days since the winter solstice [Units("d")] [JsonIgnore] public int DaysSinceWinterSolstice { get; set; } - /// - /// Gets or sets the maximum clear sky radiation (MJ/m2) - /// + /// Gets or sets the maximum clear sky radiation (MJ/m2) [Units("MJ/m2")] [JsonIgnore] public double Qmax { get; set; } - /// - /// Gets or sets the rainfall amount (mm) - /// + /// Gets or sets the rainfall amount (mm) [Units("mm")] [JsonIgnore] public double Rain { get; set; } - /// - /// Gets or sets the solar radiation (MJ/m2) - /// + /// Gets or sets the solar radiation (MJ/m2) [Units("MJ/m2")] [JsonIgnore] public double Radn { get; set; } - /// - /// Gets or sets the class A pan evaporation (mm) - /// + /// Gets or sets the class A pan evaporation (mm) [Units("mm")] [JsonIgnore] public double PanEvap { get; set; } - /// - /// Gets or sets the number duration of rainfall within a day (h) - /// + /// Gets or sets the number duration of rainfall within a day (h) [Units("h")] [JsonIgnore] public double RainfallHours { get; set; } - /// - /// Gets or sets the air vapour pressure (hPa) - /// + /// Gets or sets the air vapour pressure (hPa)/// [Units("hPa")] [JsonIgnore] public double VP { get; set; } - /// - /// Gets or sets the average wind speed (m/s) - /// + /// Gets or sets the average wind speed (m/s) [Units("m/s")] [JsonIgnore] public double Wind { get; set; } - /// - /// Gets or sets the diffuse radiation fraction (0-1) - /// + /// Gets or sets the diffuse radiation fraction (0-1) [Units("0-1")] [JsonIgnore] public double DiffuseFraction { get; set; } - /// - /// Gets or sets the day length, period with light (h) - /// + /// Gets or sets the day length, period with light (h) [Units("h")] [JsonIgnore] public double DayLength { get; set; } - /// - /// Gets or sets the CO2 level in the atmosphere (ppm) - /// + /// Gets or sets the CO2 level in the atmosphere (ppm) [Units("ppm")] [JsonIgnore] - public double CO2 { + public double CO2 + { get { if (this.reader == null || this.reader.Constant("co2") == null) @@ -375,22 +366,18 @@ public double CO2 { else return this.reader.ConstantAsDouble("co2"); } - set + set { co2Value = value; } } - /// - /// Gets or sets the mean atmospheric air pressure - /// + /// Gets or sets the mean atmospheric air pressure [Units("hPa")] [JsonIgnore] public double AirPressure { get; set; } - /// - /// Gets the latitude (decimal degrees) - /// + /// Gets the latitude (decimal degrees) [Units("degrees")] public double Latitude { @@ -403,9 +390,7 @@ public double Latitude } } - /// - /// Gets the longitude (decimal degrees) - /// + /// Gets the longitude (decimal degrees) [Units("degrees")] public double Longitude { @@ -418,9 +403,7 @@ public double Longitude } } - /// - /// Gets the long-term average air temperature (oC) - /// + /// Gets the long-term average air temperature (oC) [Units("oC")] public double Tav { @@ -435,9 +418,7 @@ public double Tav } } - /// - /// Gets the long-term average temperature amplitude (oC) - /// + /// Gets the long-term average temperature amplitude (oC) public double Amp { get @@ -451,18 +432,6 @@ public double Amp } } - /// Met data from yesterday - [JsonIgnore] - public DailyMetDataFromFile YesterdaysMetData { get { return GetAdjacentMetData(-1); } } - - /// Met data for today - [JsonIgnore] - public DailyMetDataFromFile TodaysMetData { get; set; } - - /// Met data for tomorrow - [JsonIgnore] - public DailyMetDataFromFile TomorrowsMetData { get { return GetAdjacentMetData(1); } } - /// Gets or sets the first date of summer (dd-mmm) [JsonIgnore] public string FirstDateOfSummer { get; set; } = "1-Dec"; @@ -479,6 +448,18 @@ public double Amp [JsonIgnore] public string FirstDateOfSpring { get; set; } = "1-Sep"; + /// Met data from yesterday + [JsonIgnore] + public DailyMetDataFromFile YesterdaysMetData { get { return GetAdjacentMetData(-1); } } + + /// Met data for today + [JsonIgnore] + public DailyMetDataFromFile TodaysMetData { get; set; } + + /// Met data for tomorrow + [JsonIgnore] + public DailyMetDataFromFile TomorrowsMetData { get { return GetAdjacentMetData(1); } } + /// /// Temporarily stores which tab is currently displayed. /// Meaningful only within the GUI @@ -495,30 +476,6 @@ public double Amp /// [JsonIgnore] public int ShowYears = 1; - /// Event that will be invoked at the start of spring - public event EventHandler StartOfSpring; - - /// Event that will be invoked at the start of summer - public event EventHandler StartOfSummer; - - /// Event that will be invoked at the start of autumn - public event EventHandler StartOfAutumn; - - /// Event that will be invoked at the start of winter - public event EventHandler StartOfWinter; - - /// Event that will be invoked at the end of spring - public event EventHandler EndOfSpring; - - /// Event that will be invoked at the end of summer - public event EventHandler EndOfSummer; - - /// Event that will be invoked at the end of autumn - public event EventHandler EndOfAutumn; - - /// Event that will be invoked at the end of winter - public event EventHandler EndOfWinter; - /// Name of current season public string Season { @@ -738,7 +695,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) break; } } - + if (this.reader == null) if (!this.OpenDataFile()) throw new ApsimXException(this, "Cannot find weather file '" + this.FileName + "'"); @@ -766,7 +723,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) this.weatherCache.AddBefore(this.weatherCache.Find(previousEntry), record); else this.weatherCache.AddFirst(record); - + return CheckDailyMetData(readMetData); } @@ -1147,4 +1104,4 @@ private DailyMetDataFromFile GetAdjacentMetData(int offset) return GetMetData(this.clock.Today.AddDays(offset)); } } -} \ No newline at end of file +} From 3dc27e042b50005cbb458f225d36d9ca5104aeb7 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 00:38:45 +1200 Subject: [PATCH 04/39] Changes variable DiffuseRadiationIndex to diffuseRadiationIndex (as is private), deleted variable "First' (and related lines) as not needed (replaced with test for StartDate) --- Models/Climate/Weather.cs | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index a8093120ea..8ee906ce0a 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -125,7 +125,7 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// /// The index of the diffuse radiation fraction column in the weather file /// - private int DiffuseFractionIndex; + private int diffuseFractionIndex; /// /// The index of the day length column in the weather file @@ -301,9 +301,6 @@ public int WinterSolsticeDOY } } - /// Flag whether we're one the first day of simulation - private bool First = true; - /// Gets the number of days since the winter solstice [Units("d")] [JsonIgnore] @@ -555,7 +552,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) this.vapourPressureIndex = 0; this.windIndex = 0; this.co2Index = -1; - this.DiffuseFractionIndex = 0; + this.diffuseFractionIndex = 0; this.dayLengthIndex = 0; if (AirPressure == 0) this.AirPressure = 1010; @@ -584,15 +581,6 @@ private void OnSimulationCommencing(object sender, EventArgs e) summary.WriteMessage(this, message, MessageType.Warning); } - /// - /// Performs the necessary initialisation at the start of simulation - /// - [EventSubscribe("StartOfSimulation")] - private void OnStartOfSimulation(object sender, EventArgs e) - { - First = true; - } - /// Overrides the base class method to allow for clean up task /// The sender of the event /// The arguments of the event @@ -649,7 +637,7 @@ private void OnDoWeather(object sender, EventArgs e) if (this.PreparingNewWeatherData != null) this.PreparingNewWeatherData.Invoke(this, new EventArgs()); - if (First) + if (clock.Today.Date == clock.StartDate.Date) { //StartDAWS = met.DaysSinceWinterSolstice; if (clock.Today.DayOfYear < WinterSolsticeDOY) @@ -661,13 +649,14 @@ private void OnDoWeather(object sender, EventArgs e) } else DaysSinceWinterSolstice = clock.Today.DayOfYear - WinterSolsticeDOY; - - First = false; } - - if (clock.Today.DayOfYear == WinterSolsticeDOY & First == false) - DaysSinceWinterSolstice = 0; - else DaysSinceWinterSolstice += 1; + else + { + if (clock.Today.DayOfYear == WinterSolsticeDOY) + DaysSinceWinterSolstice = 0; + else + DaysSinceWinterSolstice += 1; + } Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); @@ -778,7 +767,7 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) else readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - if (this.DiffuseFractionIndex == -1) + if (this.diffuseFractionIndex == -1) { // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) @@ -790,7 +779,7 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) if (Tt > 0.5 && readMetData.DiffuseFraction < 0.1) readMetData.DiffuseFraction = 0.1; } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[this.DiffuseFractionIndex], CultureInfo.InvariantCulture); + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[this.diffuseFractionIndex], CultureInfo.InvariantCulture); if (this.dayLengthIndex == -1) // DayLength is not a column - check for a constant { @@ -878,7 +867,7 @@ public bool OpenDataFile() this.rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "RainHours"); this.vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "VP"); this.windIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Wind"); - this.DiffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DifFr"); + this.diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DifFr"); this.dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DayLength"); this.co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); From 83c2c47ce5ef66b263a358075944a874ae53b1e2 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 18:25:54 +1200 Subject: [PATCH 05/39] few more code changes to variable doco and extending tidying up to SimpleWeather --- Models/Climate/SimpleWeather.cs | 356 ++++++++++++++------------------ Models/Climate/Weather.cs | 181 ++++++++-------- 2 files changed, 240 insertions(+), 297 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 19ad43796f..3d00568cb5 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -12,7 +12,7 @@ namespace Models.Climate { /// - /// Reads in weather data and makes it available to other models. + /// Reads in weather data from a met file and makes it available to other models /// [Serializable] [ViewName("UserInterface.Views.PropertyView")] @@ -22,20 +22,27 @@ namespace Models.Climate public class SimpleWeather : Model, IWeather, IReferenceExternalFiles { /// - /// A link to the clock model. + /// A link to the clock model /// [Link] private IClock clock = null; /// - /// A link to the the summary + /// A link to the summary (log file) /// [Link] private ISummary summary = null; /// - /// A reference to the text file reader object + /// Event that will be invoked immediately before the daily weather data is updated /// + /// + /// This provides models and scripts an opportunity to change the weather data before + /// other models access them + /// + public event EventHandler PreparingNewWeatherData; + + /// A reference to the text file reader object [NonSerialized] private ApsimTextFile reader = null; @@ -65,17 +72,17 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int evaporationIndex; /// - /// The index of the evaporation column in the weather file + /// The index of the rainfall duration column in the weather file /// private int rainfallHoursIndex; /// - /// The index of the vapor pressure column in the weather file + /// The index of the vapour pressure column in the weather file /// private int vapourPressureIndex; /// - /// The index of the wind column in the weather file + /// The index of the wind speed column in the weather file /// private int windIndex; @@ -86,32 +93,26 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int co2Index; /// - /// The index of the DiffuseFraction column in the weather file + /// The index of the diffuse radiation fraction column in the weather file /// - private int DiffuseFractionIndex; + private int diffuseFractionIndex; /// - /// The index of the DayLength column in the weather file + /// The index of the day length column in the weather file /// private int dayLengthIndex; /// - /// This event will be invoked immediately before models get their weather data. - /// models and scripts an opportunity to change the weather data before other models - /// reads it. - /// - public event EventHandler PreparingNewWeatherData; - - /// - /// Optional constants file name. This should only be accessed via - /// , which handles conversion between - /// relative/absolute paths. + /// Stores the optional constants file name. This should only be accessed via + /// , which handles conversion between + /// relative/absolute paths /// private string constantsFile; /// - /// Allows to specify a second file which contains constants such as lat, long, - /// tav, amp, etc. Really only used when the actual met data is in a .csv file. + /// Gets or sets the optional constants file name. Allows to specify a second file which + /// contains constants such as latitude, longitude, tav, amp, etc.; really only used when + /// the actual met data is in a .csv file /// [Description("Constants file")] public string ConstantsFile @@ -141,14 +142,14 @@ public string ConstantsFile } /// - /// Gets or sets the file name. Should be relative filename where possible. + /// Gets or sets the weather file name. Should be relative file path where possible /// [Summary] [Description("Weather file name")] public string _fileName { get; set; } /// - /// Gets or sets the full file name (with path). + /// Gets or sets the full file name (with path). Needed for the user interface /// [JsonIgnore] public string FileName @@ -175,19 +176,17 @@ public string FileName else this._fileName = value; if (this.reader != null) - this.reader.Close(); + this.reader.Close(); this.reader = null; } } /// - /// Used to hold the WorkSheet Name if data retrieved from an Excel file + /// Gets or sets the WorkSheet name with weather data, if data is supplied as an Excel file /// public string ExcelWorkSheetName { get; set; } - /// - /// Gets the start date of the weather file - /// + /// Gets the start date of the weather file public DateTime StartDate { get @@ -199,9 +198,7 @@ public DateTime StartDate } } - /// - /// Gets the end date of the weather file - /// + /// Gets the end date of the weather file public DateTime EndDate { get @@ -213,124 +210,96 @@ public DateTime EndDate } } - /// - /// Gets or sets the maximum temperature (oC) - /// + /// Gets or sets the maximum air temperature (oC) [Units("°C")] [JsonIgnore] public double MaxT { get; set; } - /// - /// Gets or sets the minimum temperature (oC) - /// + /// Gets or sets the minimum air temperature (oC) [JsonIgnore] [Units("°C")] public double MinT { get; set; } - /// - /// Daily Mean temperature (oC) - /// + /// Daily mean air temperature (oC) [Units("°C")] [JsonIgnore] public double MeanT { get { return (MaxT + MinT) / 2; } } - /// - /// Daily mean VPD (hPa) - /// - [Units("hPa")] + /// Gets or sets the solar radiation (MJ/m2) + [Units("MJ/m2")] [JsonIgnore] - public double VPD - { - get - { - const double SVPfrac = 0.66; - double VPDmint = MetUtilities.svp((float)MinT) - VP; - VPDmint = Math.Max(VPDmint, 0.0); - - double VPDmaxt = MetUtilities.svp((float)MaxT) - VP; - VPDmaxt = Math.Max(VPDmaxt, 0.0); - - return SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; - } - } + public double Radn { get; set; } - /// - /// Maximum clear sky radiation (MJ/m2) - /// - [Units("MJ/M2")] + /// Gets or sets the maximum clear sky radiation (MJ/m2) + [Units("MJ/m2")] [JsonIgnore] public double Qmax { get; set; } - /// - /// Gets or sets the rainfall (mm) - /// - [Units("mm")] + /// Gets or sets the diffuse radiation fraction (0-1) + [Units("0-1")] [JsonIgnore] - public double Rain { get; set; } + public double DiffuseFraction { get; set; } - /// - /// Gets or sets the solar radiation. MJ/m2/day - /// - [Units("MJ/m^2/d")] + /// Gets or sets the rainfall amount (mm) + [Units("mm")] [JsonIgnore] - public double Radn { get; set; } + public double Rain { get; set; } - /// - /// Gets or sets the Pan Evaporation (mm) (Class A pan) - /// + /// Gets or sets the class A pan evaporation (mm) [Units("mm")] [JsonIgnore] public double PanEvap { get; set; } - /// - /// Gets or sets the number of hours rainfall occured in - /// + /// Gets or sets the number duration of rainfall within a day (h) + [Units("h")] [JsonIgnore] public double RainfallHours { get; set; } - /// - /// Gets or sets the vapor pressure (hPa) - /// + /// Gets or sets the air vapour pressure (hPa)/// [Units("hPa")] [JsonIgnore] public double VP { get; set; } - /// - /// Gets or sets the wind value found in weather file or zero if not specified. (code says 3.0 not zero) - /// + /// Gets the daily mean vapour pressure deficit (hPa) + [Units("hPa")] [JsonIgnore] - public double Wind { get; set; } + public double VPD + { + get + { + const double SVPfrac = 0.66; + double VPDmint = MetUtilities.svp((float)MinT) - VP; + VPDmint = Math.Max(VPDmint, 0.0); - /// - /// Gets or sets the DF value found in weather file or zero if not specified - /// - [Units("0-1")] + double VPDmaxt = MetUtilities.svp((float)MaxT) - VP; + VPDmaxt = Math.Max(VPDmaxt, 0.0); + + return SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; + } + } + + /// Gets or sets the average wind speed (m/s) + [Units("m/s")] [JsonIgnore] - public double DiffuseFraction { get; set; } + public double Wind { get; set; } - /// - /// Gets or sets the Daylength value found in weather file or zero if not specified - /// + /// Gets or sets the day length, period with light (h) + [Units("h")] [JsonIgnore] public double DayLength { get; set; } - - /// - /// Gets or sets the CO2 level. If not specified in the weather file the default is 350. - /// + /// Gets or sets the CO2 level in the atmosphere (ppm) + [Units("ppm")] [JsonIgnore] public double CO2 { get; set; } - /// - /// Gets or sets the atmospheric air pressure. If not specified in the weather file the default is 1010 hPa. - /// + /// Gets or sets the mean atmospheric air pressure [Units("hPa")] [JsonIgnore] public double AirPressure { get; set; } - /// - /// Gets the latitude - /// + /// Gets the latitude (decimal degrees) + [Units("degrees")] public double Latitude { get @@ -342,9 +311,8 @@ public double Latitude } } - /// - /// Gets the longitude - /// + /// Gets the longitude (decimal degrees) + [Units("degrees")] public double Longitude { get @@ -356,9 +324,7 @@ public double Longitude } } - /// - /// Gets the average temperature - /// + /// Gets the long-term average air temperature (oC) [Units("°C")] public double Tav { @@ -370,9 +336,8 @@ public double Tav } } - /// - /// Gets the temperature amplitude. - /// + /// Gets the long-term average temperature amplitude (oC) + [Units("°C")] public double Amp { get @@ -383,33 +348,33 @@ public double Amp } } - /// Met Data from yesterday + /// Met data from yesterday [JsonIgnore] - public DailyMetDataFromFile YesterdaysMetData { get; set; } + public DailyMetDataFromFile YesterdaysMetData { get; set; } - /// Met Data for Today + /// Met data for today [JsonIgnore] public DailyMetDataFromFile TodaysMetData { get; set; } - /// Met Data from yesterday + /// Met data for tomorrow [JsonIgnore] - public DailyMetDataFromFile TomorrowsMetData { get ; set; } + public DailyMetDataFromFile TomorrowsMetData { get; set; } - /// Return our input filenames + /// Returns our input file names public IEnumerable GetReferencedFileNames() { return new string[] { FileName }; } - /// Remove all paths from referenced filenames. + /// Remove all paths from referenced file names public void RemovePathsFromReferencedFileNames() { _fileName = Path.GetFileName(_fileName); } - /// - /// Gets the duration of the day in hours. - /// + /// Computes the duration of the day, with light (hours) + /// The angle to measure time for twilight (degrees) + /// The length of day light public double CalculateDayLength(double Twilight) { if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant @@ -418,14 +383,14 @@ public double CalculateDayLength(double Twilight) return this.DayLength; } - /// calculate the time of sun rise + /// Computes the time of sun rise (h) /// Sun rise time public double CalculateSunRise() { return 12 - CalculateDayLength(-6) / 2; } - /// calculate the time of sun set + /// Computes the time of sun set (h) /// Sun set time public double CalculateSunSet() { @@ -433,7 +398,7 @@ public double CalculateSunSet() } /// - /// Check values in weather and return a collection of warnings. + /// Check values in weather and return a collection of warnings /// public IEnumerable Validate() { @@ -443,9 +408,9 @@ public IEnumerable Validate() } } - /// - /// Overrides the base class method to allow for initialization. - /// + /// Overrides the base class method to allow for initialization of this model + /// The sender of the event + /// The arguments of the event [EventSubscribe("Commencing")] private void OnSimulationCommencing(object sender, EventArgs e) { @@ -458,7 +423,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) this.vapourPressureIndex = 0; this.windIndex = 0; this.co2Index = -1; - this.DiffuseFractionIndex = 0; + this.diffuseFractionIndex = 0; this.dayLengthIndex = 0; if (CO2 == 0) this.CO2 = 350; @@ -476,30 +441,33 @@ private void OnSimulationCommencing(object sender, EventArgs e) summary.WriteMessage(this, message, MessageType.Warning); } - /// - /// Perform the necessary initialisation at the start of simulation. - /// + /// Overrides the base class method to perform the necessary initialisation + /// The sender of the event + /// The arguments of the event [EventSubscribe("StartOfSimulation")] private void OnStartOfSimulation(object sender, EventArgs e) { bool hasYesterday = true; - try { - YesterdaysMetData = GetMetData(clock.Today.AddDays(-1)); - } catch (Exception) { - hasYesterday = false; + try + { + YesterdaysMetData = GetMetData(clock.Today.AddDays(-1)); + } + catch (Exception) + { + hasYesterday = false; } TodaysMetData = GetMetData(clock.Today); - if (!hasYesterday) - YesterdaysMetData = TodaysMetData; + if (!hasYesterday) + YesterdaysMetData = TodaysMetData; TomorrowsMetData = GetMetData(clock.Today.AddDays(1)); } - /// - /// Overrides the base class method to allow for clean up - /// + /// Overrides the base class method to allow for clean up task + /// The sender of the event + /// The arguments of the event [EventSubscribe("Completed")] private void OnSimulationCompleted(object sender, EventArgs e) { @@ -508,9 +476,7 @@ private void OnSimulationCompleted(object sender, EventArgs e) this.reader = null; } - /// - /// An event handler for the daily DoWeather event. - /// + /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event [EventSubscribe("DoWeather")] @@ -518,11 +484,14 @@ private void OnDoWeather(object sender, EventArgs e) { YesterdaysMetData = TodaysMetData; TodaysMetData = TomorrowsMetData; - try { - TomorrowsMetData = GetMetData(this.clock.Today.AddDays(1)); - } catch (Exception) { - // If this fails, we've run out of met data - TomorrowsMetData = GetMetData(this.clock.Today); + try + { + TomorrowsMetData = GetMetData(this.clock.Today.AddDays(1)); + } + catch (Exception) + { + // If this fails, we've run out of met data + TomorrowsMetData = GetMetData(this.clock.Today); } this.Radn = TodaysMetData.Radn; @@ -543,21 +512,19 @@ private void OnDoWeather(object sender, EventArgs e) Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); - //do sanity check on weather + // do sanity check on weather SensibilityCheck(clock as Clock, this); } - /// - /// Method to read one days met data in from file - /// - /// the date to read met data + /// Reads the weather data for one day from file + /// The date to read met data public DailyMetDataFromFile GetMetData(DateTime date) { if (this.reader == null) if (!this.OpenDataFile()) throw new ApsimXException(this, "Cannot find weather file '" + this._fileName + "'"); - //get weather for that date + // get the weather data for that date DailyMetDataFromFile readMetData = new DailyMetDataFromFile(); try { @@ -575,9 +542,11 @@ public DailyMetDataFromFile GetMetData(DateTime date) return CheckDailyMetData(readMetData); } + /// Checks the values for weather data, uses either daily values or a constant + /// The weather data structure with values for one line + /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (this.radiationIndex != -1) readMetData.Radn = Convert.ToSingle(readMetData.Raw[this.radiationIndex], CultureInfo.InvariantCulture); else @@ -621,9 +590,9 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) if (co2Index != -1) readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - if (this.DiffuseFractionIndex == -1) + if (this.diffuseFractionIndex == -1) { - // Estimate Diffuse Fraction using the Approach of Bristow and Campbell + // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -633,9 +602,9 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) if (Tt > 0.5 && readMetData.DiffuseFraction < 0.1) readMetData.DiffuseFraction = 0.1; } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[this.DiffuseFractionIndex], CultureInfo.InvariantCulture); + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[this.diffuseFractionIndex], CultureInfo.InvariantCulture); - if (this.dayLengthIndex == -1) // Daylength is not a column - check for a constant + if (this.dayLengthIndex == -1) // DayLength is not a column - check for a constant { if (this.reader.Constant("daylength") != null) readMetData.DayLength = this.reader.ConstantAsDouble("daylength"); @@ -648,29 +617,7 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) return readMetData; } - /// - /// An event handler for the start of day event. - /// - /// The sender of the event - /// The arguments of the event - [EventSubscribe("StartOfDay")] - private void OnStartOfDay(object sender, EventArgs e) - { - } - - /// - /// An event handler for the end of day event. - /// - /// The sender of the event - /// The arguments of the event - [EventSubscribe("EndOfDay")] - private void OnEndOfDay(object sender, EventArgs e) - { - } - - /// - /// Open the weather data file. - /// + /// Opens the weather data file /// True if the file was successfully opened public bool OpenDataFile() { @@ -701,7 +648,7 @@ public bool OpenDataFile() this.rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "RainHours"); this.vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "VP"); this.windIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Wind"); - this.DiffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DifFr"); + this.diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DifFr"); this.dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DayLength"); co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); @@ -744,10 +691,8 @@ public bool OpenDataFile() } } - /// - /// Read a user-defined value from today's weather data. - /// - /// Name of the column. + /// Read a user-defined variable from today's weather data + /// Name of the column/variable to retrieve public double GetValue(string columnName) { int columnIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, columnName); @@ -756,40 +701,39 @@ public double GetValue(string columnName) return Convert.ToDouble(TodaysMetData.Raw[columnIndex], CultureInfo.InvariantCulture); } - /// - /// Does a santiy check on this weather data to check that temperatures, - /// VP, radition and rain are potentially valid numbers. - /// Also checks that every day has weather. - /// + /// Checks the weather data to ensure values are valid/sensible + /// + /// This will send an error message if: + /// - MinT is less than MaxT + /// - Radn is greater than 0.0 or greater than 40.0 + /// - Rain is less than 0.0 + /// - VP is less or equal to 0.0 + /// Also checks that every day has weather + /// /// The clock /// The weather private void SensibilityCheck(Clock clock, SimpleWeather weatherToday) { - //things to check: - //Mint > MaxtT - //VP(if present) <= 0 - //Radn < 0 or Radn > 40 - //Rain < 0 if (weatherToday.MinT > weatherToday.MaxT) { - throw new Exception( "Error: Weather on " + clock.Today.ToString() + " has higher minimum temperature (" + weatherToday.MinT + ") than maximum (" + weatherToday.MaxT + ")"); + throw new Exception("Error: Weather on " + clock.Today.ToString() + " has higher minimum temperature (" + weatherToday.MinT + ") than maximum (" + weatherToday.MaxT + ")"); } if (weatherToday.VP <= 0) { - throw new Exception( "Error: Weather on " + clock.Today.ToString() + " has Vapor Pressure (" + weatherToday.VP + ") which is below 0"); + throw new Exception("Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0"); } - if (weatherToday.Radn <= 0) + if (weatherToday.Radn < 0) { - throw new Exception( "Error: Weather on " + clock.Today.ToString() + " has negative solar raditation (" + weatherToday.Radn + ")"); + throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative solar radiation (" + weatherToday.Radn + ")"); } if (weatherToday.Radn > 40) { - throw new Exception( "Error: Weather on " + clock.Today.ToString() + " has solar radiation (" + weatherToday.Radn + ") which is above 40"); + throw new Exception("Error: Weather on " + clock.Today.ToString() + " has solar radiation (" + weatherToday.Radn + ") which is above 40"); } if (weatherToday.Rain < 0) { - throw new Exception( "Error: Weather on " + clock.Today.ToString() + " has negative ranfaill (" + weatherToday.Radn + ")"); + throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative ranfaill (" + weatherToday.Radn + ")"); } } } -} \ No newline at end of file +} diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 8ee906ce0a..b6b8fdca81 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -102,7 +102,7 @@ public class Weather : Model, IWeather, IReferenceExternalFiles private int evaporationIndex; /// - /// The index of the evaporation column in the weather file + /// The index of the rainfall duration column in the weather file /// private int rainfallHoursIndex; @@ -259,68 +259,26 @@ public DateTime EndDate [JsonIgnore] public double MeanT { get { return (MaxT + MinT) / 2; } } - /// Gets the daily mean vapour pressure deficit (hPa) - [Units("hPa")] - [JsonIgnore] - public double VPD - { - get - { - const double SVPfrac = 0.66; - double VPDmint = MetUtilities.svp((float)MinT) - VP; - VPDmint = Math.Max(VPDmint, 0.0); - - double VPDmaxt = MetUtilities.svp((float)MaxT) - VP; - VPDmaxt = Math.Max(VPDmaxt, 0.0); - - return SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; - } - } - - /// Gets the day for the winter solstice (day) - [Units("day")] - [JsonIgnore] - public int WinterSolsticeDOY - { - get - { - if (Latitude <= 0) - { - if (DateTime.IsLeapYear(clock.Today.Year)) - return 173; - else - return 172; - } - else - { - if (DateTime.IsLeapYear(clock.Today.Year)) - return 356; - else - return 355; - } - } - } - - /// Gets the number of days since the winter solstice - [Units("d")] + /// Gets or sets the solar radiation (MJ/m2) + [Units("MJ/m2")] [JsonIgnore] - public int DaysSinceWinterSolstice { get; set; } + public double Radn { get; set; } /// Gets or sets the maximum clear sky radiation (MJ/m2) [Units("MJ/m2")] [JsonIgnore] public double Qmax { get; set; } + /// Gets or sets the diffuse radiation fraction (0-1) + [Units("0-1")] + [JsonIgnore] + public double DiffuseFraction { get; set; } + /// Gets or sets the rainfall amount (mm) [Units("mm")] [JsonIgnore] public double Rain { get; set; } - /// Gets or sets the solar radiation (MJ/m2) - [Units("MJ/m2")] - [JsonIgnore] - public double Radn { get; set; } - /// Gets or sets the class A pan evaporation (mm) [Units("mm")] [JsonIgnore] @@ -336,16 +294,29 @@ public int WinterSolsticeDOY [JsonIgnore] public double VP { get; set; } + /// Gets the daily mean vapour pressure deficit (hPa) + [Units("hPa")] + [JsonIgnore] + public double VPD + { + get + { + const double SVPfrac = 0.66; + double VPDmint = MetUtilities.svp((float)MinT) - VP; + VPDmint = Math.Max(VPDmint, 0.0); + + double VPDmaxt = MetUtilities.svp((float)MaxT) - VP; + VPDmaxt = Math.Max(VPDmaxt, 0.0); + + return SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; + } + } + /// Gets or sets the average wind speed (m/s) [Units("m/s")] [JsonIgnore] public double Wind { get; set; } - /// Gets or sets the diffuse radiation fraction (0-1) - [Units("0-1")] - [JsonIgnore] - public double DiffuseFraction { get; set; } - /// Gets or sets the day length, period with light (h) [Units("h")] [JsonIgnore] @@ -401,7 +372,7 @@ public double Longitude } /// Gets the long-term average air temperature (oC) - [Units("oC")] + [Units("°C")] public double Tav { get @@ -409,13 +380,14 @@ public double Tav if (this.reader == null) return 0; else if (this.reader.Constant("tav") == null) - this.CalcTAVAMP(); + this.calculateTAVAMP(); return this.reader.ConstantAsDouble("tav"); } } /// Gets the long-term average temperature amplitude (oC) + [Units("°C")] public double Amp { get @@ -423,12 +395,41 @@ public double Amp if (this.reader == null) return 0; else if (this.reader.Constant("amp") == null) - this.CalcTAVAMP(); + this.calculateTAVAMP(); return this.reader.ConstantAsDouble("amp"); } } + /// Gets the day for the winter solstice (day) + [Units("day")] + [JsonIgnore] + public int WinterSolsticeDOY + { + get + { + if (Latitude <= 0) + { + if (DateTime.IsLeapYear(clock.Today.Year)) + return 173; + else + return 172; + } + else + { + if (DateTime.IsLeapYear(clock.Today.Year)) + return 356; + else + return 355; + } + } + } + + /// Gets the number of days since the winter solstice + [Units("d")] + [JsonIgnore] + public int DaysSinceWinterSolstice { get; set; } + /// Gets or sets the first date of summer (dd-mmm) [JsonIgnore] public string FirstDateOfSummer { get; set; } = "1-Dec"; @@ -462,11 +463,13 @@ public double Amp /// Meaningful only within the GUI /// [JsonIgnore] public int ActiveTabIndex = 0; + /// /// Temporarily stores the starting date for charts. /// Meaningful only within the GUI /// [JsonIgnore] public int StartYear = -1; + /// /// Temporarily stores the years to show in charts. /// Meaningful only within the GUI @@ -592,29 +595,7 @@ private void OnSimulationCompleted(object sender, EventArgs e) this.reader = null; } - /// Get the DataTable view of the weather data - /// The DataTable - public DataTable GetAllData() - { - this.reader = null; - - if (this.OpenDataFile()) - { - List metProps = new List(); - metProps.Add("mint"); - metProps.Add("maxt"); - metProps.Add("radn"); - metProps.Add("rain"); - metProps.Add("wind"); - metProps.Add("diffr"); - - return this.reader.ToTable(metProps); - } - else - return null; - } - - /// Performs the tasks to update the weather data + /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event [EventSubscribe("DoWeather")] @@ -639,7 +620,6 @@ private void OnDoWeather(object sender, EventArgs e) if (clock.Today.Date == clock.StartDate.Date) { - //StartDAWS = met.DaysSinceWinterSolstice; if (clock.Today.DayOfYear < WinterSolsticeDOY) { if (DateTime.IsLeapYear(clock.Today.Year - 1)) @@ -660,13 +640,33 @@ private void OnDoWeather(object sender, EventArgs e) Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); - //do sanity check on weather + // do sanity check on weather SensibilityCheck(clock as Clock, this); } - /// - /// Reads the weather data for one day from file - /// + /// Get the DataTable view of the weather data + /// The DataTable + public DataTable GetAllData() + { + this.reader = null; + + if (this.OpenDataFile()) + { + List metProps = new List(); + metProps.Add("mint"); + metProps.Add("maxt"); + metProps.Add("radn"); + metProps.Add("rain"); + metProps.Add("wind"); + metProps.Add("diffr"); + + return this.reader.ToTable(metProps); + } + else + return null; + } + + /// Reads the weather data for one day from file /// The date to read met data public DailyMetDataFromFile GetMetData(DateTime date) { @@ -721,7 +721,6 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (this.radiationIndex != -1) readMetData.Radn = Convert.ToSingle(readMetData.Raw[this.radiationIndex], CultureInfo.InvariantCulture); else @@ -932,13 +931,13 @@ public double GetValue(string columnName) /// Calculates the values for the constants Tav and Amp for this weather file /// and store the values into the reader constants list. /// - private void CalcTAVAMP() + private void calculateTAVAMP() { double tav = 0; double amp = 0; // do the calculations - this.ProcessMonthlyTAVAMP(out tav, out amp); + this.processMonthlyTAVAMP(out tav, out amp); if (this.reader.Constant("tav") == null) this.reader.AddConstant("tav", tav.ToString(CultureInfo.InvariantCulture), string.Empty, string.Empty); // add a new constant @@ -956,7 +955,7 @@ private void CalcTAVAMP() /// /// The calculated tav value /// The calculated amp value - private void ProcessMonthlyTAVAMP(out double tav, out double amp) + private void processMonthlyTAVAMP(out double tav, out double amp) { long savedPosition = reader.GetCurrentPosition(); From d402a398e2d666be485baa0e679697b72fbb823f Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 19:36:50 +1200 Subject: [PATCH 06/39] removing the 'this' qualification --- Models/Climate/SimpleWeather.cs | 240 ++++++++++++------------- Models/Climate/Weather.cs | 302 ++++++++++++++++---------------- 2 files changed, 271 insertions(+), 271 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 3d00568cb5..f26a4e8ec2 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -121,23 +121,23 @@ public string ConstantsFile { Simulation simulation = FindAncestor(); if (simulation != null) - return PathUtilities.GetAbsolutePath(this.constantsFile, simulation.FileName); + return PathUtilities.GetAbsolutePath(constantsFile, simulation.FileName); else { Simulations simulations = FindAncestor(); if (simulations != null) - return PathUtilities.GetAbsolutePath(this.constantsFile, simulations.FileName); + return PathUtilities.GetAbsolutePath(constantsFile, simulations.FileName); else - return this.constantsFile; + return constantsFile; } } set { Simulations simulations = FindAncestor(); if (simulations != null) - this.constantsFile = PathUtilities.GetRelativePath(value, simulations.FileName); + constantsFile = PathUtilities.GetRelativePath(value, simulations.FileName); else - this.constantsFile = value; + constantsFile = value; } } @@ -158,26 +158,26 @@ public string FileName { Simulation simulation = FindAncestor(); if (simulation != null) - return PathUtilities.GetAbsolutePath(this._fileName, simulation.FileName); + return PathUtilities.GetAbsolutePath(_fileName, simulation.FileName); else { Simulations simulations = FindAncestor(); if (simulations != null) - return PathUtilities.GetAbsolutePath(this._fileName, simulations.FileName); + return PathUtilities.GetAbsolutePath(_fileName, simulations.FileName); else - return this._fileName; + return _fileName; } } set { Simulations simulations = FindAncestor(); if (simulations != null) - this._fileName = PathUtilities.GetRelativePath(value, simulations.FileName); + _fileName = PathUtilities.GetRelativePath(value, simulations.FileName); else - this._fileName = value; - if (this.reader != null) - this.reader.Close(); - this.reader = null; + _fileName = value; + if (reader != null) + reader.Close(); + reader = null; } } @@ -191,10 +191,10 @@ public DateTime StartDate { get { - if (this.reader == null && !this.OpenDataFile()) + if (reader == null && !OpenDataFile()) return new DateTime(0); - return this.reader.FirstDate; + return reader.FirstDate; } } @@ -203,10 +203,10 @@ public DateTime EndDate { get { - if (this.reader == null && !this.OpenDataFile()) + if (reader == null && !OpenDataFile()) return new DateTime(0); - return this.reader.LastDate; + return reader.LastDate; } } @@ -304,10 +304,10 @@ public double Latitude { get { - if (this.reader == null && !this.OpenDataFile()) + if (reader == null && !OpenDataFile()) return 0; - return this.reader.ConstantAsDouble("Latitude"); + return reader.ConstantAsDouble("Latitude"); } } @@ -317,10 +317,10 @@ public double Longitude { get { - if (this.reader == null || this.reader.Constant("Longitude") == null) + if (reader == null || reader.Constant("Longitude") == null) return 0; else - return this.reader.ConstantAsDouble("Longitude"); + return reader.ConstantAsDouble("Longitude"); } } @@ -330,9 +330,9 @@ public double Tav { get { - if (this.reader == null) + if (reader == null) return 0; - return this.reader.ConstantAsDouble("tav"); + return reader.ConstantAsDouble("tav"); } } @@ -342,9 +342,9 @@ public double Amp { get { - if (this.reader == null) + if (reader == null) return 0; - return this.reader.ConstantAsDouble("amp"); + return reader.ConstantAsDouble("amp"); } } @@ -378,9 +378,9 @@ public void RemovePathsFromReferencedFileNames() public double CalculateDayLength(double Twilight) { if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant - return MathUtilities.DayLength(this.clock.Today.DayOfYear, Twilight, this.Latitude); + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); else - return this.DayLength; + return DayLength; } /// Computes the time of sun rise (h) @@ -414,23 +414,23 @@ public IEnumerable Validate() [EventSubscribe("Commencing")] private void OnSimulationCommencing(object sender, EventArgs e) { - this.maximumTemperatureIndex = 0; - this.minimumTemperatureIndex = 0; - this.radiationIndex = 0; - this.rainIndex = 0; - this.evaporationIndex = 0; - this.rainfallHoursIndex = 0; - this.vapourPressureIndex = 0; - this.windIndex = 0; - this.co2Index = -1; - this.diffuseFractionIndex = 0; - this.dayLengthIndex = 0; + maximumTemperatureIndex = 0; + minimumTemperatureIndex = 0; + radiationIndex = 0; + rainIndex = 0; + evaporationIndex = 0; + rainfallHoursIndex = 0; + vapourPressureIndex = 0; + windIndex = 0; + co2Index = -1; + diffuseFractionIndex = 0; + dayLengthIndex = 0; if (CO2 == 0) - this.CO2 = 350; + CO2 = 350; if (AirPressure == 0) - this.AirPressure = 1010; + AirPressure = 1010; if (DiffuseFraction == 0) - this.DiffuseFraction = -1; + DiffuseFraction = -1; if (reader != null) { reader.Close(); @@ -471,9 +471,9 @@ private void OnStartOfSimulation(object sender, EventArgs e) [EventSubscribe("Completed")] private void OnSimulationCompleted(object sender, EventArgs e) { - if (this.reader != null) - this.reader.Close(); - this.reader = null; + if (reader != null) + reader.Close(); + reader = null; } /// Performs the tasks to update the weather data @@ -486,29 +486,29 @@ private void OnDoWeather(object sender, EventArgs e) TodaysMetData = TomorrowsMetData; try { - TomorrowsMetData = GetMetData(this.clock.Today.AddDays(1)); + TomorrowsMetData = GetMetData(clock.Today.AddDays(1)); } catch (Exception) { // If this fails, we've run out of met data - TomorrowsMetData = GetMetData(this.clock.Today); + TomorrowsMetData = GetMetData(clock.Today); } - this.Radn = TodaysMetData.Radn; - this.MaxT = TodaysMetData.MaxT; - this.MinT = TodaysMetData.MinT; - this.Rain = TodaysMetData.Rain; - this.PanEvap = TodaysMetData.PanEvap; - this.RainfallHours = TodaysMetData.RainfallHours; - this.VP = TodaysMetData.VP; - this.Wind = TodaysMetData.Wind; - this.DiffuseFraction = TodaysMetData.DiffuseFraction; - this.DayLength = TodaysMetData.DayLength; + Radn = TodaysMetData.Radn; + MaxT = TodaysMetData.MaxT; + MinT = TodaysMetData.MinT; + Rain = TodaysMetData.Rain; + PanEvap = TodaysMetData.PanEvap; + RainfallHours = TodaysMetData.RainfallHours; + VP = TodaysMetData.VP; + Wind = TodaysMetData.Wind; + DiffuseFraction = TodaysMetData.DiffuseFraction; + DayLength = TodaysMetData.DayLength; if (co2Index != -1) CO2 = TodaysMetData.CO2; - if (this.PreparingNewWeatherData != null) - this.PreparingNewWeatherData.Invoke(this, new EventArgs()); + if (PreparingNewWeatherData != null) + PreparingNewWeatherData.Invoke(this, new EventArgs()); Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); @@ -520,15 +520,15 @@ private void OnDoWeather(object sender, EventArgs e) /// The date to read met data public DailyMetDataFromFile GetMetData(DateTime date) { - if (this.reader == null) - if (!this.OpenDataFile()) - throw new ApsimXException(this, "Cannot find weather file '" + this._fileName + "'"); + if (reader == null) + if (!OpenDataFile()) + throw new ApsimXException(this, "Cannot find weather file '" + _fileName + "'"); // get the weather data for that date DailyMetDataFromFile readMetData = new DailyMetDataFromFile(); try { - this.reader.SeekToDate(date); + reader.SeekToDate(date); readMetData.Raw = reader.GetNextLineOfData(); } catch (IndexOutOfRangeException err) @@ -536,8 +536,8 @@ public DailyMetDataFromFile GetMetData(DateTime date) throw new Exception($"Unable to retrieve weather data on {date.ToString("yyy-MM-dd")} in file {_fileName}", err); } - if (date != this.reader.GetDateFromValues(readMetData.Raw)) - throw new Exception("Non consecutive dates found in file: " + this._fileName + "."); + if (date != reader.GetDateFromValues(readMetData.Raw)) + throw new Exception("Non consecutive dates found in file: " + _fileName + "."); return CheckDailyMetData(readMetData); } @@ -547,50 +547,50 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (this.radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[this.radiationIndex], CultureInfo.InvariantCulture); + if (radiationIndex != -1) + readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); else - readMetData.Radn = this.reader.ConstantAsDouble("radn"); + readMetData.Radn = reader.ConstantAsDouble("radn"); - if (this.maximumTemperatureIndex != -1) - readMetData.MaxT = Convert.ToSingle(readMetData.Raw[this.maximumTemperatureIndex], CultureInfo.InvariantCulture); + if (maximumTemperatureIndex != -1) + readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); else - readMetData.MaxT = this.reader.ConstantAsDouble("maxt"); + readMetData.MaxT = reader.ConstantAsDouble("maxt"); - if (this.minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[this.minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex != -1) + readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); else - readMetData.MinT = this.reader.ConstantAsDouble("mint"); + readMetData.MinT = reader.ConstantAsDouble("mint"); - if (this.rainIndex != -1) - readMetData.Rain = Convert.ToSingle(readMetData.Raw[this.rainIndex], CultureInfo.InvariantCulture); + if (rainIndex != -1) + readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); else - readMetData.Rain = this.reader.ConstantAsDouble("rain"); + readMetData.Rain = reader.ConstantAsDouble("rain"); - if (this.evaporationIndex == -1) + if (evaporationIndex == -1) readMetData.PanEvap = double.NaN; else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[this.evaporationIndex], CultureInfo.InvariantCulture); + readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[evaporationIndex], CultureInfo.InvariantCulture); - if (this.rainfallHoursIndex == -1) + if (rainfallHoursIndex == -1) readMetData.RainfallHours = double.NaN; else - readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[this.rainfallHoursIndex], CultureInfo.InvariantCulture); + readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); - if (this.vapourPressureIndex == -1) + if (vapourPressureIndex == -1) readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); else - readMetData.VP = Convert.ToSingle(readMetData.Raw[this.vapourPressureIndex], CultureInfo.InvariantCulture); + readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); - if (this.windIndex == -1) + if (windIndex == -1) readMetData.Wind = 3.0; else - readMetData.Wind = Convert.ToSingle(readMetData.Raw[this.windIndex], CultureInfo.InvariantCulture); + readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); if (co2Index != -1) readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - if (this.diffuseFractionIndex == -1) + if (diffuseFractionIndex == -1) { // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) @@ -602,17 +602,17 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) if (Tt > 0.5 && readMetData.DiffuseFraction < 0.1) readMetData.DiffuseFraction = 0.1; } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[this.diffuseFractionIndex], CultureInfo.InvariantCulture); + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - if (this.dayLengthIndex == -1) // DayLength is not a column - check for a constant + if (dayLengthIndex == -1) // DayLength is not a column - check for a constant { - if (this.reader.Constant("daylength") != null) - readMetData.DayLength = this.reader.ConstantAsDouble("daylength"); + if (reader.Constant("daylength") != null) + readMetData.DayLength = reader.ConstantAsDouble("daylength"); else readMetData.DayLength = -1; } else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[this.dayLengthIndex], CultureInfo.InvariantCulture); + readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); return readMetData; } @@ -621,17 +621,17 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) /// True if the file was successfully opened public bool OpenDataFile() { - if (System.IO.File.Exists(this.FileName)) + if (System.IO.File.Exists(FileName)) { - if (this.reader == null) + if (reader == null) { if (ExcelUtilities.IsExcelFile(FileName) && string.IsNullOrEmpty(ExcelWorkSheetName)) throw new Exception($"Unable to open excel file {FileName}: no sheet name is specified"); - this.reader = new ApsimTextFile(); - this.reader.Open(this.FileName, this.ExcelWorkSheetName); + reader = new ApsimTextFile(); + reader.Open(FileName, ExcelWorkSheetName); - if (this.reader.Headings == null) + if (reader.Headings == null) { string message = "Cannot find the expected header in "; if (ExcelUtilities.IsExcelFile(FileName)) @@ -640,16 +640,16 @@ public bool OpenDataFile() throw new Exception(message); } - this.maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Maxt"); - this.minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Mint"); - this.radiationIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Radn"); - this.rainIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Rain"); - this.evaporationIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Evap"); - this.rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "RainHours"); - this.vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "VP"); - this.windIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Wind"); - this.diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DifFr"); - this.dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DayLength"); + maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); + minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); + radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); + rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); + evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); + rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); + vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); + windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); + diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); + dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); if (!string.IsNullOrEmpty(ConstantsFile)) @@ -658,29 +658,29 @@ public bool OpenDataFile() constantsReader.Open(ConstantsFile); if (constantsReader.Constants != null) foreach (ApsimConstant constant in constantsReader.Constants) - this.reader.AddConstant(constant.Name, constant.Value, constant.Units, constant.Comment); + reader.AddConstant(constant.Name, constant.Value, constant.Units, constant.Comment); } - if (this.maximumTemperatureIndex == -1) - if (this.reader == null || this.reader.Constant("maxt") == null) - throw new Exception("Cannot find MaxT in weather file: " + this.FileName); + if (maximumTemperatureIndex == -1) + if (reader == null || reader.Constant("maxt") == null) + throw new Exception("Cannot find MaxT in weather file: " + FileName); - if (this.minimumTemperatureIndex == -1) - if (this.reader == null || this.reader.Constant("mint") == null) - throw new Exception("Cannot find MinT in weather file: " + this.FileName); + if (minimumTemperatureIndex == -1) + if (reader == null || reader.Constant("mint") == null) + throw new Exception("Cannot find MinT in weather file: " + FileName); - if (this.radiationIndex == -1) - if (this.reader == null || this.reader.Constant("radn") == null) - throw new Exception("Cannot find Radn in weather file: " + this.FileName); + if (radiationIndex == -1) + if (reader == null || reader.Constant("radn") == null) + throw new Exception("Cannot find Radn in weather file: " + FileName); - if (this.rainIndex == -1) - if (this.reader == null || this.reader.Constant("rain") == null) - throw new Exception("Cannot find Rain in weather file: " + this.FileName); + if (rainIndex == -1) + if (reader == null || reader.Constant("rain") == null) + throw new Exception("Cannot find Rain in weather file: " + FileName); } else { - if (this.reader.IsExcelFile != true) - this.reader.SeekToDate(this.reader.FirstDate); + if (reader.IsExcelFile != true) + reader.SeekToDate(reader.FirstDate); } return true; diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index b6b8fdca81..6412fc35d1 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -158,23 +158,23 @@ public string ConstantsFile { Simulation simulation = FindAncestor(); if (simulation != null) - return PathUtilities.GetAbsolutePath(this.constantsFile, simulation.FileName); + return PathUtilities.GetAbsolutePath(constantsFile, simulation.FileName); else { Simulations simulations = FindAncestor(); if (simulations != null) - return PathUtilities.GetAbsolutePath(this.constantsFile, simulations.FileName); + return PathUtilities.GetAbsolutePath(constantsFile, simulations.FileName); else - return this.constantsFile; + return constantsFile; } } set { Simulations simulations = FindAncestor(); if (simulations != null) - this.constantsFile = PathUtilities.GetRelativePath(value, simulations.FileName); + constantsFile = PathUtilities.GetRelativePath(value, simulations.FileName); else - this.constantsFile = value; + constantsFile = value; } } @@ -195,23 +195,23 @@ public string FullFileName { Simulation simulation = FindAncestor(); if (simulation != null && simulation.FileName != null) - return PathUtilities.GetAbsolutePath(this.FileName, simulation.FileName); + return PathUtilities.GetAbsolutePath(FileName, simulation.FileName); else { Simulations simulations = FindAncestor(); if (simulations != null) - return PathUtilities.GetAbsolutePath(this.FileName, simulations.FileName); + return PathUtilities.GetAbsolutePath(FileName, simulations.FileName); else - return PathUtilities.GetAbsolutePath(this.FileName, ""); + return PathUtilities.GetAbsolutePath(FileName, ""); } } set { Simulations simulations = FindAncestor(); if (simulations != null) - this.FileName = PathUtilities.GetRelativePath(value, simulations.FileName); + FileName = PathUtilities.GetRelativePath(value, simulations.FileName); else - this.FileName = value; + FileName = value; } } @@ -225,10 +225,10 @@ public DateTime StartDate { get { - if (this.reader == null && !this.OpenDataFile()) + if (reader == null && !OpenDataFile()) return new DateTime(0); - return this.reader.FirstDate; + return reader.FirstDate; } } @@ -237,10 +237,10 @@ public DateTime EndDate { get { - if (this.reader == null && !this.OpenDataFile()) + if (reader == null && !OpenDataFile()) return new DateTime(0); - return this.reader.LastDate; + return reader.LastDate; } } @@ -329,10 +329,10 @@ public double CO2 { get { - if (this.reader == null || this.reader.Constant("co2") == null) + if (reader == null || reader.Constant("co2") == null) return co2Value; else - return this.reader.ConstantAsDouble("co2"); + return reader.ConstantAsDouble("co2"); } set { @@ -351,10 +351,10 @@ public double Latitude { get { - if (this.reader == null && !this.OpenDataFile()) + if (reader == null && !OpenDataFile()) return 0; - return this.reader.ConstantAsDouble("Latitude"); + return reader.ConstantAsDouble("Latitude"); } } @@ -364,10 +364,10 @@ public double Longitude { get { - if (this.reader == null || this.reader.Constant("Longitude") == null) + if (reader == null || reader.Constant("Longitude") == null) return 0; else - return this.reader.ConstantAsDouble("Longitude"); + return reader.ConstantAsDouble("Longitude"); } } @@ -377,12 +377,12 @@ public double Tav { get { - if (this.reader == null) + if (reader == null) return 0; - else if (this.reader.Constant("tav") == null) - this.calculateTAVAMP(); + else if (reader.Constant("tav") == null) + calculateTAVAMP(); - return this.reader.ConstantAsDouble("tav"); + return reader.ConstantAsDouble("tav"); } } @@ -392,12 +392,12 @@ public double Amp { get { - if (this.reader == null) + if (reader == null) return 0; - else if (this.reader.Constant("amp") == null) - this.calculateTAVAMP(); + else if (reader.Constant("amp") == null) + calculateTAVAMP(); - return this.reader.ConstantAsDouble("amp"); + return reader.ConstantAsDouble("amp"); } } @@ -510,9 +510,9 @@ public void RemovePathsFromReferencedFileNames() public double CalculateDayLength(double Twilight) { if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant - return MathUtilities.DayLength(this.clock.Today.DayOfYear, Twilight, this.Latitude); + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); else - return this.DayLength; + return DayLength; } /// Computes the time of sun rise (h) @@ -546,21 +546,21 @@ public IEnumerable Validate() [EventSubscribe("Commencing")] private void OnSimulationCommencing(object sender, EventArgs e) { - this.maximumTemperatureIndex = 0; - this.minimumTemperatureIndex = 0; - this.radiationIndex = 0; - this.rainIndex = 0; - this.evaporationIndex = 0; - this.rainfallHoursIndex = 0; - this.vapourPressureIndex = 0; - this.windIndex = 0; - this.co2Index = -1; - this.diffuseFractionIndex = 0; - this.dayLengthIndex = 0; + maximumTemperatureIndex = 0; + minimumTemperatureIndex = 0; + radiationIndex = 0; + rainIndex = 0; + evaporationIndex = 0; + rainfallHoursIndex = 0; + vapourPressureIndex = 0; + windIndex = 0; + co2Index = -1; + diffuseFractionIndex = 0; + dayLengthIndex = 0; if (AirPressure == 0) - this.AirPressure = 1010; + AirPressure = 1010; if (DiffuseFraction == 0) - this.DiffuseFraction = -1; + DiffuseFraction = -1; if (reader != null) { reader.Close(); @@ -590,9 +590,9 @@ private void OnSimulationCommencing(object sender, EventArgs e) [EventSubscribe("Completed")] private void OnSimulationCompleted(object sender, EventArgs e) { - if (this.reader != null) - this.reader.Close(); - this.reader = null; + if (reader != null) + reader.Close(); + reader = null; } /// Performs the tasks to update the weather data @@ -601,22 +601,22 @@ private void OnSimulationCompleted(object sender, EventArgs e) [EventSubscribe("DoWeather")] private void OnDoWeather(object sender, EventArgs e) { - TodaysMetData = GetMetData(this.clock.Today); //Read first date to get todays data - - this.Radn = TodaysMetData.Radn; - this.MaxT = TodaysMetData.MaxT; - this.MinT = TodaysMetData.MinT; - this.Rain = TodaysMetData.Rain; - this.PanEvap = TodaysMetData.PanEvap; - this.RainfallHours = TodaysMetData.RainfallHours; - this.VP = TodaysMetData.VP; - this.Wind = TodaysMetData.Wind; - this.DiffuseFraction = TodaysMetData.DiffuseFraction; - this.DayLength = TodaysMetData.DayLength; - this.CO2 = TodaysMetData.CO2; - - if (this.PreparingNewWeatherData != null) - this.PreparingNewWeatherData.Invoke(this, new EventArgs()); + TodaysMetData = GetMetData(clock.Today); //Read first date to get todays data + + Radn = TodaysMetData.Radn; + MaxT = TodaysMetData.MaxT; + MinT = TodaysMetData.MinT; + Rain = TodaysMetData.Rain; + PanEvap = TodaysMetData.PanEvap; + RainfallHours = TodaysMetData.RainfallHours; + VP = TodaysMetData.VP; + Wind = TodaysMetData.Wind; + DiffuseFraction = TodaysMetData.DiffuseFraction; + DayLength = TodaysMetData.DayLength; + CO2 = TodaysMetData.CO2; + + if (PreparingNewWeatherData != null) + PreparingNewWeatherData.Invoke(this, new EventArgs()); if (clock.Today.Date == clock.StartDate.Date) { @@ -648,9 +648,9 @@ private void OnDoWeather(object sender, EventArgs e) /// The DataTable public DataTable GetAllData() { - this.reader = null; + reader = null; - if (this.OpenDataFile()) + if (OpenDataFile()) { List metProps = new List(); metProps.Add("mint"); @@ -660,7 +660,7 @@ public DataTable GetAllData() metProps.Add("wind"); metProps.Add("diffr"); - return this.reader.ToTable(metProps); + return reader.ToTable(metProps); } else return null; @@ -672,7 +672,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) { // check if we've looked at this date before WeatherRecordEntry previousEntry = null; - foreach (WeatherRecordEntry entry in this.weatherCache) + foreach (WeatherRecordEntry entry in weatherCache) { if (entry.Date.Equals(date)) { @@ -685,15 +685,15 @@ public DailyMetDataFromFile GetMetData(DateTime date) } } - if (this.reader == null) - if (!this.OpenDataFile()) - throw new ApsimXException(this, "Cannot find weather file '" + this.FileName + "'"); + if (reader == null) + if (!OpenDataFile()) + throw new ApsimXException(this, "Cannot find weather file '" + FileName + "'"); // get the weather data for that date DailyMetDataFromFile readMetData = new DailyMetDataFromFile(); try { - this.reader.SeekToDate(date); + reader.SeekToDate(date); readMetData.Raw = reader.GetNextLineOfData(); } catch (IndexOutOfRangeException err) @@ -701,17 +701,17 @@ public DailyMetDataFromFile GetMetData(DateTime date) throw new Exception($"Unable to retrieve weather data on {date.ToString("yyy-MM-dd")} in file {FileName}", err); } - if (date != this.reader.GetDateFromValues(readMetData.Raw)) - throw new Exception("Non consecutive dates found in file: " + this.FileName + "."); + if (date != reader.GetDateFromValues(readMetData.Raw)) + throw new Exception("Non consecutive dates found in file: " + FileName + "."); // since this data was valid, store in our cache for next time WeatherRecordEntry record = new WeatherRecordEntry(); record.Date = date; record.MetData = readMetData; if (previousEntry != null) - this.weatherCache.AddBefore(this.weatherCache.Find(previousEntry), record); + weatherCache.AddBefore(weatherCache.Find(previousEntry), record); else - this.weatherCache.AddFirst(record); + weatherCache.AddFirst(record); return CheckDailyMetData(readMetData); } @@ -721,52 +721,52 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (this.radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[this.radiationIndex], CultureInfo.InvariantCulture); + if (radiationIndex != -1) + readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); else - readMetData.Radn = this.reader.ConstantAsDouble("radn"); + readMetData.Radn = reader.ConstantAsDouble("radn"); - if (this.maximumTemperatureIndex != -1) - readMetData.MaxT = Convert.ToSingle(readMetData.Raw[this.maximumTemperatureIndex], CultureInfo.InvariantCulture); + if (maximumTemperatureIndex != -1) + readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); else - readMetData.MaxT = this.reader.ConstantAsDouble("maxt"); + readMetData.MaxT = reader.ConstantAsDouble("maxt"); - if (this.minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[this.minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex != -1) + readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); else - readMetData.MinT = this.reader.ConstantAsDouble("mint"); + readMetData.MinT = reader.ConstantAsDouble("mint"); - if (this.rainIndex != -1) - readMetData.Rain = Convert.ToSingle(readMetData.Raw[this.rainIndex], CultureInfo.InvariantCulture); + if (rainIndex != -1) + readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); else - readMetData.Rain = this.reader.ConstantAsDouble("rain"); + readMetData.Rain = reader.ConstantAsDouble("rain"); - if (this.evaporationIndex == -1) + if (evaporationIndex == -1) readMetData.PanEvap = double.NaN; else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[this.evaporationIndex], CultureInfo.InvariantCulture); + readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[evaporationIndex], CultureInfo.InvariantCulture); - if (this.rainfallHoursIndex == -1) + if (rainfallHoursIndex == -1) readMetData.RainfallHours = double.NaN; else - readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[this.rainfallHoursIndex], CultureInfo.InvariantCulture); + readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); - if (this.vapourPressureIndex == -1) + if (vapourPressureIndex == -1) readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); else - readMetData.VP = Convert.ToSingle(readMetData.Raw[this.vapourPressureIndex], CultureInfo.InvariantCulture); + readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); - if (this.windIndex == -1) + if (windIndex == -1) readMetData.Wind = 3.0; else - readMetData.Wind = Convert.ToSingle(readMetData.Raw[this.windIndex], CultureInfo.InvariantCulture); + readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); if (co2Index == -1) readMetData.CO2 = 350; else readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - if (this.diffuseFractionIndex == -1) + if (diffuseFractionIndex == -1) { // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) @@ -778,17 +778,17 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) if (Tt > 0.5 && readMetData.DiffuseFraction < 0.1) readMetData.DiffuseFraction = 0.1; } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[this.diffuseFractionIndex], CultureInfo.InvariantCulture); + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - if (this.dayLengthIndex == -1) // DayLength is not a column - check for a constant + if (dayLengthIndex == -1) // DayLength is not a column - check for a constant { - if (this.reader.Constant("daylength") != null) - readMetData.DayLength = this.reader.ConstantAsDouble("daylength"); + if (reader.Constant("daylength") != null) + readMetData.DayLength = reader.ConstantAsDouble("daylength"); else readMetData.DayLength = -1; } else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[this.dayLengthIndex], CultureInfo.InvariantCulture); + readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); return readMetData; } @@ -835,21 +835,21 @@ private void OnEndOfDay(object sender, EventArgs e) /// True if the file was successfully opened public bool OpenDataFile() { - if (!System.IO.File.Exists(this.FullFileName) && + if (!System.IO.File.Exists(FullFileName) && System.IO.Path.GetExtension(FullFileName) == string.Empty) FileName += ".met"; - if (System.IO.File.Exists(this.FullFileName)) + if (System.IO.File.Exists(FullFileName)) { - if (this.reader == null) + if (reader == null) { if (ExcelUtilities.IsExcelFile(FullFileName) && string.IsNullOrEmpty(ExcelWorkSheetName)) throw new Exception($"Unable to open excel file {FullFileName}: no sheet name is specified"); - this.reader = new ApsimTextFile(); - this.reader.Open(this.FullFileName, this.ExcelWorkSheetName); + reader = new ApsimTextFile(); + reader.Open(FullFileName, ExcelWorkSheetName); - if (this.reader.Headings == null) + if (reader.Headings == null) { string message = "Cannot find the expected header in "; if (ExcelUtilities.IsExcelFile(FullFileName)) @@ -858,17 +858,17 @@ public bool OpenDataFile() throw new Exception(message); } - this.maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Maxt"); - this.minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Mint"); - this.radiationIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Radn"); - this.rainIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Rain"); - this.evaporationIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Evap"); - this.rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "RainHours"); - this.vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "VP"); - this.windIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "Wind"); - this.diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DifFr"); - this.dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(this.reader.Headings, "DayLength"); - this.co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); + maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); + minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); + radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); + rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); + evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); + rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); + vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); + windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); + diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); + dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); + co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); if (!string.IsNullOrEmpty(ConstantsFile)) { @@ -876,29 +876,29 @@ public bool OpenDataFile() constantsReader.Open(ConstantsFile); if (constantsReader.Constants != null) foreach (ApsimConstant constant in constantsReader.Constants) - this.reader.AddConstant(constant.Name, constant.Value, constant.Units, constant.Comment); + reader.AddConstant(constant.Name, constant.Value, constant.Units, constant.Comment); } - if (this.maximumTemperatureIndex == -1) - if (this.reader == null || this.reader.Constant("maxt") == null) - throw new Exception("Cannot find MaxT in weather file: " + this.FullFileName); + if (maximumTemperatureIndex == -1) + if (reader == null || reader.Constant("maxt") == null) + throw new Exception("Cannot find MaxT in weather file: " + FullFileName); - if (this.minimumTemperatureIndex == -1) - if (this.reader == null || this.reader.Constant("mint") == null) - throw new Exception("Cannot find MinT in weather file: " + this.FullFileName); + if (minimumTemperatureIndex == -1) + if (reader == null || reader.Constant("mint") == null) + throw new Exception("Cannot find MinT in weather file: " + FullFileName); - if (this.radiationIndex == -1) - if (this.reader == null || this.reader.Constant("radn") == null) - throw new Exception("Cannot find Radn in weather file: " + this.FullFileName); + if (radiationIndex == -1) + if (reader == null || reader.Constant("radn") == null) + throw new Exception("Cannot find Radn in weather file: " + FullFileName); - if (this.rainIndex == -1) - if (this.reader == null || this.reader.Constant("rain") == null) - throw new Exception("Cannot find Rain in weather file: " + this.FullFileName); + if (rainIndex == -1) + if (reader == null || reader.Constant("rain") == null) + throw new Exception("Cannot find Rain in weather file: " + FullFileName); } else { - if (this.reader.IsExcelFile != true) - this.reader.SeekToDate(this.reader.FirstDate); + if (reader.IsExcelFile != true) + reader.SeekToDate(reader.FirstDate); } return true; @@ -937,17 +937,17 @@ private void calculateTAVAMP() double amp = 0; // do the calculations - this.processMonthlyTAVAMP(out tav, out amp); + processMonthlyTAVAMP(out tav, out amp); - if (this.reader.Constant("tav") == null) - this.reader.AddConstant("tav", tav.ToString(CultureInfo.InvariantCulture), string.Empty, string.Empty); // add a new constant + if (reader.Constant("tav") == null) + reader.AddConstant("tav", tav.ToString(CultureInfo.InvariantCulture), string.Empty, string.Empty); // add a new constant else - this.reader.SetConstant("tav", tav.ToString(CultureInfo.InvariantCulture)); + reader.SetConstant("tav", tav.ToString(CultureInfo.InvariantCulture)); - if (this.reader.Constant("amp") == null) - this.reader.AddConstant("amp", amp.ToString(CultureInfo.InvariantCulture), string.Empty, string.Empty); // add a new constant + if (reader.Constant("amp") == null) + reader.AddConstant("amp", amp.ToString(CultureInfo.InvariantCulture), string.Empty, string.Empty); // add a new constant else - this.reader.SetConstant("amp", amp.ToString(CultureInfo.InvariantCulture)); + reader.SetConstant("amp", amp.ToString(CultureInfo.InvariantCulture)); } /// @@ -965,8 +965,8 @@ private void processMonthlyTAVAMP(out double tav, out double amp) double maxt, mint; // get dataset size - DateTime start = this.reader.FirstDate; - DateTime last = this.reader.LastDate; + DateTime start = reader.FirstDate; + DateTime last = reader.LastDate; int nyears = last.Year - start.Year + 1; // temp storage arrays @@ -974,7 +974,7 @@ private void processMonthlyTAVAMP(out double tav, out double amp) double[,] monthlySums = new double[12, nyears]; int[,] monthlyDays = new int[12, nyears]; - this.reader.SeekToDate(start); // goto start of data set + reader.SeekToDate(start); // goto start of data set // read the daily data from the met file object[] values; @@ -983,11 +983,11 @@ private void processMonthlyTAVAMP(out double tav, out double amp) bool moreData = true; while (moreData) { - values = this.reader.GetNextLineOfData(); - curDate = this.reader.GetDateFromValues(values); + values = reader.GetNextLineOfData(); + curDate = reader.GetDateFromValues(values); int yearIndex = curDate.Year - start.Year; - maxt = Convert.ToDouble(values[this.maximumTemperatureIndex], System.Globalization.CultureInfo.InvariantCulture); - mint = Convert.ToDouble(values[this.minimumTemperatureIndex], System.Globalization.CultureInfo.InvariantCulture); + maxt = Convert.ToDouble(values[maximumTemperatureIndex], System.Globalization.CultureInfo.InvariantCulture); + mint = Convert.ToDouble(values[minimumTemperatureIndex], System.Globalization.CultureInfo.InvariantCulture); // accumulate the daily mean for each month if (curMonth != curDate.Month) @@ -1082,14 +1082,14 @@ private void SensibilityCheck(Clock clock, Weather weatherToday) /// The number of days away from today private DailyMetDataFromFile GetAdjacentMetData(int offset) { - if ((this.clock.Today.Equals(this.reader.FirstDate) && offset < 0) || - this.clock.Today.Equals(this.reader.LastDate) && offset > 0) + if ((clock.Today.Equals(reader.FirstDate) && offset < 0) || + clock.Today.Equals(reader.LastDate) && offset > 0) { - summary.WriteMessage(this, "Warning: Weather on " + this.clock.Today.AddDays(offset).ToString("d") + " does not exist. Today's weather on " + this.clock.Today.ToString("d") + " was used instead.", MessageType.Warning); - return GetMetData(this.clock.Today); + summary.WriteMessage(this, "Warning: Weather on " + clock.Today.AddDays(offset).ToString("d") + " does not exist. Today's weather on " + clock.Today.ToString("d") + " was used instead.", MessageType.Warning); + return GetMetData(clock.Today); } else - return GetMetData(this.clock.Today.AddDays(offset)); + return GetMetData(clock.Today.AddDays(offset)); } } } From e15317ec4c6858050df84bf25a418627229a2346 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 20:26:25 +1200 Subject: [PATCH 07/39] Making some variables settable, move a few calculations to functions --- Models/Climate/SimpleWeather.cs | 117 +++++++++++++---------- Models/Climate/Weather.cs | 164 ++++++++++++++++++-------------- 2 files changed, 162 insertions(+), 119 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index f26a4e8ec2..056e44e524 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -210,20 +210,20 @@ public DateTime EndDate } } - /// Gets or sets the maximum air temperature (oC) - [Units("°C")] - [JsonIgnore] - public double MaxT { get; set; } - - /// Gets or sets the minimum air temperature (oC) + /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] [Units("°C")] public double MinT { get; set; } - /// Daily mean air temperature (oC) + /// Gets or sets the daily maximum air temperature (oC) + [Units("°C")] + [JsonIgnore] + public double MaxT { get; set; } + + /// Gets or sets the daily mean air temperature (oC) [Units("°C")] [JsonIgnore] - public double MeanT { get { return (MaxT + MinT) / 2; } } + public double MeanT { get; set; } /// Gets or sets the solar radiation (MJ/m2) [Units("MJ/m2")] @@ -260,23 +260,10 @@ public DateTime EndDate [JsonIgnore] public double VP { get; set; } - /// Gets the daily mean vapour pressure deficit (hPa) + /// Gets or sets the daily mean vapour pressure deficit (hPa) [Units("hPa")] [JsonIgnore] - public double VPD - { - get - { - const double SVPfrac = 0.66; - double VPDmint = MetUtilities.svp((float)MinT) - VP; - VPDmint = Math.Max(VPDmint, 0.0); - - double VPDmaxt = MetUtilities.svp((float)MaxT) - VP; - VPDmaxt = Math.Max(VPDmaxt, 0.0); - - return SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; - } - } + public double VPD { get; set; } /// Gets or sets the average wind speed (m/s) [Units("m/s")] @@ -372,31 +359,6 @@ public void RemovePathsFromReferencedFileNames() _fileName = Path.GetFileName(_fileName); } - /// Computes the duration of the day, with light (hours) - /// The angle to measure time for twilight (degrees) - /// The length of day light - public double CalculateDayLength(double Twilight) - { - if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); - else - return DayLength; - } - - /// Computes the time of sun rise (h) - /// Sun rise time - public double CalculateSunRise() - { - return 12 - CalculateDayLength(-6) / 2; - } - - /// Computes the time of sun set (h) - /// Sun set time - public double CalculateSunSet() - { - return 12 + CalculateDayLength(-6) / 2; - } - /// /// Check values in weather and return a collection of warnings /// @@ -482,6 +444,7 @@ private void OnSimulationCompleted(object sender, EventArgs e) [EventSubscribe("DoWeather")] private void OnDoWeather(object sender, EventArgs e) { + // read weather data (for yesterday, today, and tomorrow) YesterdaysMetData = TodaysMetData; TodaysMetData = TomorrowsMetData; try @@ -490,10 +453,11 @@ private void OnDoWeather(object sender, EventArgs e) } catch (Exception) { - // If this fails, we've run out of met data + // if this fails, we've run out of met data TomorrowsMetData = GetMetData(clock.Today); } + // assign values to output variables Radn = TodaysMetData.Radn; MaxT = TodaysMetData.MaxT; MinT = TodaysMetData.MinT; @@ -507,9 +471,12 @@ private void OnDoWeather(object sender, EventArgs e) if (co2Index != -1) CO2 = TodaysMetData.CO2; + // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) PreparingNewWeatherData.Invoke(this, new EventArgs()); + // compute a series of values derived from weather data + MeanT = (MaxT + MinT) / 2.0; Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); // do sanity check on weather @@ -691,6 +658,58 @@ public bool OpenDataFile() } } + /// Closes the data file + public void CloseDataFile() + { + if (reader != null) + reader.Close(); + reader = null; + } + + /// Computes the duration of the day, with light (hours) + /// The angle to measure time for twilight (degrees) + /// The length of day light + public double CalculateDayLength(double Twilight) + { + if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + else + return DayLength; + } + + /// Computes the time of sun rise (h) + /// Sun rise time + public double CalculateSunRise() + { + return 12 - CalculateDayLength(-6) / 2; + } + + /// Computes the time of sun set (h) + /// Sun set time + public double CalculateSunSet() + { + return 12 + CalculateDayLength(-6) / 2; + } + + /// Computes today's atmospheric vapour pressure deficit (hPa) + /// Today's minimum temperature (oC) + /// Today's maximum temperature (oC) + /// Today's vapour pressure (hPa) + /// The vapour pressure deficit (hPa) + private double calculateVapourPressureDefict(double minTemp, double maxTemp, double vapourPressure) + { + const double SVPfrac = 0.66; + double result; + double VPDmint = MetUtilities.svp(minTemp) - vapourPressure; + VPDmint = Math.Max(VPDmint, 0.0); + + double VPDmaxt = MetUtilities.svp(MaxT) - vapourPressure; + VPDmaxt = Math.Max(VPDmaxt, 0.0); + + result = SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; + return result; + } + /// Read a user-defined variable from today's weather data /// Name of the column/variable to retrieve public double GetValue(string columnName) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 6412fc35d1..ada4073401 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -1,5 +1,7 @@ using APSIM.Shared.Utilities; +using DocumentFormat.OpenXml.Packaging; using Models.Core; +using Models.Functions; using Models.Interfaces; using Newtonsoft.Json; using System; @@ -244,20 +246,20 @@ public DateTime EndDate } } - /// Gets or sets the maximum air temperature (oC) - [Units("°C")] - [JsonIgnore] - public double MaxT { get; set; } - - /// Gets or sets the minimum air temperature (oC) + /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] [Units("°C")] public double MinT { get; set; } - /// Daily mean air temperature (oC) + /// Gets or sets the daily maximum air temperature (oC) + [Units("°C")] + [JsonIgnore] + public double MaxT { get; set; } + + /// Gets or sets the daily mean air temperature (oC) [Units("°C")] [JsonIgnore] - public double MeanT { get { return (MaxT + MinT) / 2; } } + public double MeanT { get; set; } /// Gets or sets the solar radiation (MJ/m2) [Units("MJ/m2")] @@ -294,23 +296,10 @@ public DateTime EndDate [JsonIgnore] public double VP { get; set; } - /// Gets the daily mean vapour pressure deficit (hPa) + /// Gets or sets the daily mean vapour pressure deficit (hPa) [Units("hPa")] [JsonIgnore] - public double VPD - { - get - { - const double SVPfrac = 0.66; - double VPDmint = MetUtilities.svp((float)MinT) - VP; - VPDmint = Math.Max(VPDmint, 0.0); - - double VPDmaxt = MetUtilities.svp((float)MaxT) - VP; - VPDmaxt = Math.Max(VPDmaxt, 0.0); - - return SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; - } - } + public double VPD { get; set; } /// Gets or sets the average wind speed (m/s) [Units("m/s")] @@ -425,7 +414,7 @@ public int WinterSolsticeDOY } } - /// Gets the number of days since the winter solstice + /// Gets or sets the number of days since the winter solstice [Units("d")] [JsonIgnore] public int DaysSinceWinterSolstice { get; set; } @@ -504,31 +493,6 @@ public void RemovePathsFromReferencedFileNames() FileName = Path.GetFileName(FileName); } - /// Computes the duration of the day, with light (hours) - /// The angle to measure time for twilight (degrees) - /// The length of day light - public double CalculateDayLength(double Twilight) - { - if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); - else - return DayLength; - } - - /// Computes the time of sun rise (h) - /// Sun rise time - public double CalculateSunRise() - { - return 12 - CalculateDayLength(-6) / 2; - } - - /// Computes the time of sun set (h) - /// Sun set time - public double CalculateSunSet() - { - return 12 + CalculateDayLength(-6) / 2; - } - /// /// Check values in weather and return a collection of warnings /// @@ -601,8 +565,10 @@ private void OnSimulationCompleted(object sender, EventArgs e) [EventSubscribe("DoWeather")] private void OnDoWeather(object sender, EventArgs e) { - TodaysMetData = GetMetData(clock.Today); //Read first date to get todays data + // read today's weather data + TodaysMetData = GetMetData(clock.Today); + // assign values to output variables Radn = TodaysMetData.Radn; MaxT = TodaysMetData.MaxT; MinT = TodaysMetData.MinT; @@ -615,30 +581,15 @@ private void OnDoWeather(object sender, EventArgs e) DayLength = TodaysMetData.DayLength; CO2 = TodaysMetData.CO2; + // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) PreparingNewWeatherData.Invoke(this, new EventArgs()); - if (clock.Today.Date == clock.StartDate.Date) - { - if (clock.Today.DayOfYear < WinterSolsticeDOY) - { - if (DateTime.IsLeapYear(clock.Today.Year - 1)) - DaysSinceWinterSolstice = 366 - WinterSolsticeDOY + clock.Today.DayOfYear; - else - DaysSinceWinterSolstice = 365 - WinterSolsticeDOY + clock.Today.DayOfYear; - } - else - DaysSinceWinterSolstice = clock.Today.DayOfYear - WinterSolsticeDOY; - } - else - { - if (clock.Today.DayOfYear == WinterSolsticeDOY) - DaysSinceWinterSolstice = 0; - else - DaysSinceWinterSolstice += 1; - } - + // compute a series of values derived from weather data + MeanT = (MaxT + MinT) / 2.0; Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); + VPD = calculateVapourPressureDefict(MinT, MaxT, VP); + DaysSinceWinterSolstice = calculateDaysSinceSolstice(DaysSinceWinterSolstice); // do sanity check on weather SensibilityCheck(clock as Clock, this); @@ -917,6 +868,79 @@ public void CloseDataFile() reader = null; } + /// Computes the duration of the day, with light (hours) + /// The angle to measure time for twilight (degrees) + /// The length of day light + public double CalculateDayLength(double Twilight) + { + if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + else + return DayLength; + } + + /// Computes the time of sun rise (h) + /// Sun rise time + public double CalculateSunRise() + { + return 12 - CalculateDayLength(-6) / 2; + } + + /// Computes the time of sun set (h) + /// Sun set time + public double CalculateSunSet() + { + return 12 + CalculateDayLength(-6) / 2; + } + + /// Computes today's atmospheric vapour pressure deficit (hPa) + /// Today's minimum temperature (oC) + /// Today's maximum temperature (oC) + /// Today's vapour pressure (hPa) + /// The vapour pressure deficit (hPa) + private double calculateVapourPressureDefict(double minTemp, double maxTemp, double vapourPressure) + { + const double SVPfrac = 0.66; + double result; + double VPDmint = MetUtilities.svp(minTemp) - vapourPressure; + VPDmint = Math.Max(VPDmint, 0.0); + + double VPDmaxt = MetUtilities.svp(MaxT) - vapourPressure; + VPDmaxt = Math.Max(VPDmaxt, 0.0); + + result = SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; + return result; + } + + /// Computes the number of days since winter solstice + /// The current number of days since solstice + /// Updated number of days since winter solstice + private int calculateDaysSinceSolstice(int currentDaysSinceSolstice) + { + int daysSinceSolstice = 0; + if (clock.Today.Date == clock.StartDate.Date) + { + if (clock.Today.DayOfYear < WinterSolsticeDOY) + { + if (DateTime.IsLeapYear(clock.Today.Year - 1)) + daysSinceSolstice = 366 - WinterSolsticeDOY + clock.Today.DayOfYear; + else + daysSinceSolstice = 365 - WinterSolsticeDOY + clock.Today.DayOfYear; + } + else + DaysSinceWinterSolstice = clock.Today.DayOfYear - WinterSolsticeDOY; + } + else + { + if (clock.Today.DayOfYear == WinterSolsticeDOY) + daysSinceSolstice = 0; + else + daysSinceSolstice = currentDaysSinceSolstice + 1; + } + + return daysSinceSolstice; + } + /// Read a user-defined variable from today's weather data /// Name of the column/variable to retrieve public double GetValue(string columnName) From 1fec9f228181bf8a5078923b856344955c3c992c Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 21:25:45 +1200 Subject: [PATCH 08/39] Make latitude and longitude settable --- Models/Climate/SimpleWeather.cs | 14 ++++++++++++-- Models/Climate/Weather.cs | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 056e44e524..63d40fdbe9 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -285,7 +285,7 @@ public DateTime EndDate [JsonIgnore] public double AirPressure { get; set; } - /// Gets the latitude (decimal degrees) + /// Gets or sets the latitude (decimal degrees) [Units("degrees")] public double Latitude { @@ -296,9 +296,14 @@ public double Latitude return reader.ConstantAsDouble("Latitude"); } + set + { + if (this.reader != null) + reader.Constant("Latitude").Value = value.ToString(); + } } - /// Gets the longitude (decimal degrees) + /// Gets or sets the longitude (decimal degrees) [Units("degrees")] public double Longitude { @@ -309,6 +314,11 @@ public double Longitude else return reader.ConstantAsDouble("Longitude"); } + set + { + if (reader != null) + reader.Constant("Longitude").Value = value.ToString(); + } } /// Gets the long-term average air temperature (oC) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index ada4073401..3ee1095a7e 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -334,7 +334,7 @@ public double CO2 [JsonIgnore] public double AirPressure { get; set; } - /// Gets the latitude (decimal degrees) + /// Gets or sets the latitude (decimal degrees) [Units("degrees")] public double Latitude { @@ -345,9 +345,14 @@ public double Latitude return reader.ConstantAsDouble("Latitude"); } + set + { + if (this.reader != null) + reader.Constant("Latitude").Value = value.ToString(); + } } - /// Gets the longitude (decimal degrees) + /// Gets or sets the longitude (decimal degrees) [Units("degrees")] public double Longitude { @@ -358,6 +363,11 @@ public double Longitude else return reader.ConstantAsDouble("Longitude"); } + set + { + if (reader != null) + reader.Constant("Longitude").Value = value.ToString(); + } } /// Gets the long-term average air temperature (oC) @@ -901,6 +911,7 @@ public double CalculateSunSet() private double calculateVapourPressureDefict(double minTemp, double maxTemp, double vapourPressure) { const double SVPfrac = 0.66; + double result; double VPDmint = MetUtilities.svp(minTemp) - vapourPressure; VPDmint = Math.Max(VPDmint, 0.0); From b04256204e123d5555d9af12cfb4ef3472d77582 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 18 Sep 2024 23:53:55 +1200 Subject: [PATCH 09/39] Adding calculation for AirPressure (from soilTemperature) --- Models/Climate/SimpleWeather.cs | 19 ++++++++++++++++++- Models/Climate/Weather.cs | 19 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 63d40fdbe9..f808933c18 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -400,7 +400,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) if (CO2 == 0) CO2 = 350; if (AirPressure == 0) - AirPressure = 1010; + AirPressure = calculateAirPressure(27.09); // to default 1010; if (DiffuseFraction == 0) DiffuseFraction = -1; if (reader != null) @@ -720,6 +720,23 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou return result; } + /// Computes the air pressure for a given location + /// The altitude (m) + /// The air pressure (hPa) + private double calculateAirPressure(double localAltitude) + { + const double baseTemperature = 288.15; // (K) + const double basePressure = 101325.0; // (Pa) + const double lapseRate = 0.0065; // (K/m) + const double gravitationalAcceleration = 9.80665; // (m/s) + const double molarMassOfAir = 0.0289644; // (kg/mol) + const double universalGasConstant = 8.3144598; // (J/mol/K) + double result; + result = basePressure * Math.Pow(1.0 - localAltitude * lapseRate / baseTemperature, + gravitationalAcceleration * molarMassOfAir / (universalGasConstant * lapseRate)); + return result / 100.0; // to hPa + } + /// Read a user-defined variable from today's weather data /// Name of the column/variable to retrieve public double GetValue(string columnName) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 3ee1095a7e..ee6dac1952 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -532,7 +532,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) diffuseFractionIndex = 0; dayLengthIndex = 0; if (AirPressure == 0) - AirPressure = 1010; + AirPressure = calculateAirPressure(27.09); // to default 1010; if (DiffuseFraction == 0) DiffuseFraction = -1; if (reader != null) @@ -923,6 +923,23 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou return result; } + /// Computes the air pressure for a given location + /// The altitude (m) + /// The air pressure (hPa) + private double calculateAirPressure(double localAltitude) + { + const double baseTemperature = 288.15; // (K) + const double basePressure = 101325.0; // (Pa) + const double lapseRate = 0.0065; // (K/m) + const double gravitationalAcceleration = 9.80665; // (m/s) + const double molarMassOfAir = 0.0289644; // (kg/mol) + const double universalGasConstant = 8.3144598; // (J/mol/K) + double result; + result = basePressure * Math.Pow(1.0 - localAltitude * lapseRate / baseTemperature, + gravitationalAcceleration * molarMassOfAir / (universalGasConstant * lapseRate)); + return result / 100.0; // to hPa + } + /// Computes the number of days since winter solstice /// The current number of days since solstice /// Updated number of days since winter solstice From aa6a77d28c3bd17b22457531f75549baf8f9ab8e Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 01:20:08 +1200 Subject: [PATCH 10/39] Increase pressicion of altitude (for airPressure) plus minor shuffle of code (keep an order to outputs) --- Models/Climate/SimpleWeather.cs | 42 ++++++++++++++-------------- Models/Climate/Weather.cs | 49 ++++++++++++++++----------------- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index f808933c18..93213982ab 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -386,8 +386,8 @@ public IEnumerable Validate() [EventSubscribe("Commencing")] private void OnSimulationCommencing(object sender, EventArgs e) { - maximumTemperatureIndex = 0; minimumTemperatureIndex = 0; + maximumTemperatureIndex = 0; radiationIndex = 0; rainIndex = 0; evaporationIndex = 0; @@ -399,10 +399,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) dayLengthIndex = 0; if (CO2 == 0) CO2 = 350; - if (AirPressure == 0) - AirPressure = calculateAirPressure(27.09); // to default 1010; - if (DiffuseFraction == 0) - DiffuseFraction = -1; + if (reader != null) { reader.Close(); @@ -468,9 +465,9 @@ private void OnDoWeather(object sender, EventArgs e) } // assign values to output variables - Radn = TodaysMetData.Radn; - MaxT = TodaysMetData.MaxT; MinT = TodaysMetData.MinT; + MaxT = TodaysMetData.MaxT; + Radn = TodaysMetData.Radn; Rain = TodaysMetData.Rain; PanEvap = TodaysMetData.PanEvap; RainfallHours = TodaysMetData.RainfallHours; @@ -480,6 +477,7 @@ private void OnDoWeather(object sender, EventArgs e) DayLength = TodaysMetData.DayLength; if (co2Index != -1) CO2 = TodaysMetData.CO2; + AirPressure = calculateAirPressure(27.08889); // to default 1010; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -524,20 +522,20 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex != -1) + readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); else - readMetData.Radn = reader.ConstantAsDouble("radn"); + readMetData.MinT = reader.ConstantAsDouble("mint"); if (maximumTemperatureIndex != -1) readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); else readMetData.MaxT = reader.ConstantAsDouble("maxt"); - if (minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (radiationIndex != -1) + readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); else - readMetData.MinT = reader.ConstantAsDouble("mint"); + readMetData.Radn = reader.ConstantAsDouble("radn"); if (rainIndex != -1) readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); @@ -617,8 +615,8 @@ public bool OpenDataFile() throw new Exception(message); } - maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); + maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); @@ -638,14 +636,14 @@ public bool OpenDataFile() reader.AddConstant(constant.Name, constant.Value, constant.Units, constant.Comment); } - if (maximumTemperatureIndex == -1) - if (reader == null || reader.Constant("maxt") == null) - throw new Exception("Cannot find MaxT in weather file: " + FileName); - if (minimumTemperatureIndex == -1) if (reader == null || reader.Constant("mint") == null) throw new Exception("Cannot find MinT in weather file: " + FileName); + if (maximumTemperatureIndex == -1) + if (reader == null || reader.Constant("maxt") == null) + throw new Exception("Cannot find MaxT in weather file: " + FileName); + if (radiationIndex == -1) if (reader == null || reader.Constant("radn") == null) throw new Exception("Cannot find Radn in weather file: " + FileName); @@ -764,10 +762,6 @@ private void SensibilityCheck(Clock clock, SimpleWeather weatherToday) { throw new Exception("Error: Weather on " + clock.Today.ToString() + " has higher minimum temperature (" + weatherToday.MinT + ") than maximum (" + weatherToday.MaxT + ")"); } - if (weatherToday.VP <= 0) - { - throw new Exception("Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0"); - } if (weatherToday.Radn < 0) { throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative solar radiation (" + weatherToday.Radn + ")"); @@ -780,6 +774,10 @@ private void SensibilityCheck(Clock clock, SimpleWeather weatherToday) { throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative ranfaill (" + weatherToday.Radn + ")"); } + if (weatherToday.VP <= 0) + { + throw new Exception("Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0"); + } } } } diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index ee6dac1952..98dcc31aaa 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -520,8 +520,8 @@ public IEnumerable Validate() [EventSubscribe("Commencing")] private void OnSimulationCommencing(object sender, EventArgs e) { - maximumTemperatureIndex = 0; minimumTemperatureIndex = 0; + maximumTemperatureIndex = 0; radiationIndex = 0; rainIndex = 0; evaporationIndex = 0; @@ -531,10 +531,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) co2Index = -1; diffuseFractionIndex = 0; dayLengthIndex = 0; - if (AirPressure == 0) - AirPressure = calculateAirPressure(27.09); // to default 1010; - if (DiffuseFraction == 0) - DiffuseFraction = -1; + if (reader != null) { reader.Close(); @@ -579,17 +576,18 @@ private void OnDoWeather(object sender, EventArgs e) TodaysMetData = GetMetData(clock.Today); // assign values to output variables - Radn = TodaysMetData.Radn; - MaxT = TodaysMetData.MaxT; MinT = TodaysMetData.MinT; + MaxT = TodaysMetData.MaxT; + Radn = TodaysMetData.Radn; + DiffuseFraction = TodaysMetData.DiffuseFraction; Rain = TodaysMetData.Rain; PanEvap = TodaysMetData.PanEvap; RainfallHours = TodaysMetData.RainfallHours; VP = TodaysMetData.VP; Wind = TodaysMetData.Wind; - DiffuseFraction = TodaysMetData.DiffuseFraction; DayLength = TodaysMetData.DayLength; CO2 = TodaysMetData.CO2; + AirPressure = calculateAirPressure(27.08889); // to default 1010; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -618,8 +616,8 @@ public DataTable GetAllData() metProps.Add("maxt"); metProps.Add("radn"); metProps.Add("rain"); - metProps.Add("wind"); metProps.Add("diffr"); + metProps.Add("wind"); return reader.ToTable(metProps); } @@ -682,20 +680,20 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex != -1) + readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); else - readMetData.Radn = reader.ConstantAsDouble("radn"); + readMetData.MinT = reader.ConstantAsDouble("mint"); if (maximumTemperatureIndex != -1) readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); else readMetData.MaxT = reader.ConstantAsDouble("maxt"); - if (minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (radiationIndex != -1) + readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); else - readMetData.MinT = reader.ConstantAsDouble("mint"); + readMetData.Radn = reader.ConstantAsDouble("radn"); if (rainIndex != -1) readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); @@ -819,15 +817,15 @@ public bool OpenDataFile() throw new Exception(message); } - maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); + maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); + diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); - diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); @@ -840,14 +838,14 @@ public bool OpenDataFile() reader.AddConstant(constant.Name, constant.Value, constant.Units, constant.Comment); } - if (maximumTemperatureIndex == -1) - if (reader == null || reader.Constant("maxt") == null) - throw new Exception("Cannot find MaxT in weather file: " + FullFileName); - if (minimumTemperatureIndex == -1) if (reader == null || reader.Constant("mint") == null) throw new Exception("Cannot find MinT in weather file: " + FullFileName); + if (maximumTemperatureIndex == -1) + if (reader == null || reader.Constant("maxt") == null) + throw new Exception("Cannot find MaxT in weather file: " + FullFileName); + if (radiationIndex == -1) if (reader == null || reader.Constant("radn") == null) throw new Exception("Cannot find Radn in weather file: " + FullFileName); @@ -1107,10 +1105,6 @@ private void SensibilityCheck(Clock clock, Weather weatherToday) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has higher minimum temperature (" + weatherToday.MinT + ") than maximum (" + weatherToday.MaxT + ")", MessageType.Warning); } - if (weatherToday.VP <= 0) - { - summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0", MessageType.Warning); - } if (weatherToday.Radn < 0) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative solar radiation (" + weatherToday.Radn + ")", MessageType.Warning); @@ -1123,6 +1117,11 @@ private void SensibilityCheck(Clock clock, Weather weatherToday) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative rainfall (" + weatherToday.Radn + ")", MessageType.Warning); } + if (weatherToday.VP <= 0) + { + summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0", MessageType.Warning); + } + return; } From d4f9a4f0e2a793bc2727697992d50b5bdaaea5d9 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 02:05:41 +1200 Subject: [PATCH 11/39] Standardised check for DiffuseFraction and CO2 (this partialy) --- Models/Climate/SimpleWeather.cs | 82 +++++++++++++++++++-------------- Models/Climate/Weather.cs | 59 +++++++++++++++--------- 2 files changed, 83 insertions(+), 58 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 93213982ab..173845c21e 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -61,6 +61,16 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles /// private int radiationIndex; + /// + /// The index of the day length column in the weather file + /// + private int dayLengthIndex; + + /// + /// The index of the diffuse radiation fraction column in the weather file + /// + private int diffuseFractionIndex; + /// /// The index of the rainfall column in the weather file /// @@ -92,16 +102,6 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles /// private int co2Index; - /// - /// The index of the diffuse radiation fraction column in the weather file - /// - private int diffuseFractionIndex; - - /// - /// The index of the day length column in the weather file - /// - private int dayLengthIndex; - /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -235,6 +235,11 @@ public DateTime EndDate [JsonIgnore] public double Qmax { get; set; } + /// Gets or sets the day length, period with light (h) + [Units("h")] + [JsonIgnore] + public double DayLength { get; set; } + /// Gets or sets the diffuse radiation fraction (0-1) [Units("0-1")] [JsonIgnore] @@ -270,11 +275,6 @@ public DateTime EndDate [JsonIgnore] public double Wind { get; set; } - /// Gets or sets the day length, period with light (h) - [Units("h")] - [JsonIgnore] - public double DayLength { get; set; } - /// Gets or sets the CO2 level in the atmosphere (ppm) [Units("ppm")] [JsonIgnore] @@ -389,16 +389,14 @@ private void OnSimulationCommencing(object sender, EventArgs e) minimumTemperatureIndex = 0; maximumTemperatureIndex = 0; radiationIndex = 0; + dayLengthIndex = 0; + diffuseFractionIndex = 0; rainIndex = 0; evaporationIndex = 0; rainfallHoursIndex = 0; vapourPressureIndex = 0; windIndex = 0; - co2Index = -1; - diffuseFractionIndex = 0; - dayLengthIndex = 0; - if (CO2 == 0) - CO2 = 350; + co2Index = 0; if (reader != null) { @@ -468,15 +466,14 @@ private void OnDoWeather(object sender, EventArgs e) MinT = TodaysMetData.MinT; MaxT = TodaysMetData.MaxT; Radn = TodaysMetData.Radn; + DayLength = TodaysMetData.DayLength; + DiffuseFraction = TodaysMetData.DiffuseFraction; Rain = TodaysMetData.Rain; PanEvap = TodaysMetData.PanEvap; RainfallHours = TodaysMetData.RainfallHours; VP = TodaysMetData.VP; Wind = TodaysMetData.Wind; - DiffuseFraction = TodaysMetData.DiffuseFraction; - DayLength = TodaysMetData.DayLength; - if (co2Index != -1) - CO2 = TodaysMetData.CO2; + CO2 = TodaysMetData.CO2; AirPressure = calculateAirPressure(27.08889); // to default 1010; // invoke event that allows other models to modify weather data @@ -562,20 +559,18 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) else readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); - if (co2Index != -1) + if (co2Index == -1) + { // CO2 is not a column - check for a constant + if (reader.Constant("co2") != null) + readMetData.DayLength = reader.ConstantAsDouble("co2"); + else + readMetData.CO2 = 350; + } + else readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (diffuseFractionIndex == -1) - { - // estimate diffuse fraction using the approach of Bristow and Campbell - double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) - double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); - double B = Qmax / Q0; - double Tt = MathUtilities.Bound(readMetData.Radn / Q0, 0, 1); - if (Tt > B) Tt = B; - readMetData.DiffuseFraction = (1 - Math.Exp(0.6 * (1 - B / Tt) / (B - 0.4))); - if (Tt > 0.5 && readMetData.DiffuseFraction < 0.1) readMetData.DiffuseFraction = 0.1; - } + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); else readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); @@ -699,6 +694,23 @@ public double CalculateSunSet() return 12 + CalculateDayLength(-6) / 2; } + /// Estimate diffuse radiation fraction (0-1) + /// Uses the approach of Bristow and Campbell (0000) + /// The diffuse radiation fraction + private double calculateDiffuseRadiationFraction(double todaysRadiation) + { + // estimate diffuse fraction using the approach of Bristow and Campbell + double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) + double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); + double B = Qmax / Q0; + double Tt = MathUtilities.Bound(todaysRadiation / Q0, 0, 1); + if (Tt > B) Tt = B; + double result = (1 - Math.Exp(0.6 * (1 - B / Tt) / (B - 0.4))); + if (Tt > 0.5 && result < 0.1) + result = 0.1; + return result; + } + /// Computes today's atmospheric vapour pressure deficit (hPa) /// Today's minimum temperature (oC) /// Today's maximum temperature (oC) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 98dcc31aaa..94f379830e 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -271,6 +271,11 @@ public DateTime EndDate [JsonIgnore] public double Qmax { get; set; } + /// Gets or sets the day length, period with light (h) + [Units("h")] + [JsonIgnore] + public double DayLength { get; set; } + /// Gets or sets the diffuse radiation fraction (0-1) [Units("0-1")] [JsonIgnore] @@ -306,11 +311,6 @@ public DateTime EndDate [JsonIgnore] public double Wind { get; set; } - /// Gets or sets the day length, period with light (h) - [Units("h")] - [JsonIgnore] - public double DayLength { get; set; } - /// Gets or sets the CO2 level in the atmosphere (ppm) [Units("ppm")] [JsonIgnore] @@ -523,14 +523,14 @@ private void OnSimulationCommencing(object sender, EventArgs e) minimumTemperatureIndex = 0; maximumTemperatureIndex = 0; radiationIndex = 0; + dayLengthIndex = 0; + diffuseFractionIndex = 0; rainIndex = 0; evaporationIndex = 0; rainfallHoursIndex = 0; vapourPressureIndex = 0; windIndex = 0; - co2Index = -1; - diffuseFractionIndex = 0; - dayLengthIndex = 0; + co2Index = 0; if (reader != null) { @@ -538,7 +538,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) reader = null; } - if (Latitude > 0) + if (Latitude > 0.0) { // Swap summer and winter dates. var temp = FirstDateOfSummer; @@ -579,13 +579,13 @@ private void OnDoWeather(object sender, EventArgs e) MinT = TodaysMetData.MinT; MaxT = TodaysMetData.MaxT; Radn = TodaysMetData.Radn; + DayLength = TodaysMetData.DayLength; DiffuseFraction = TodaysMetData.DiffuseFraction; Rain = TodaysMetData.Rain; PanEvap = TodaysMetData.PanEvap; RainfallHours = TodaysMetData.RainfallHours; VP = TodaysMetData.VP; Wind = TodaysMetData.Wind; - DayLength = TodaysMetData.DayLength; CO2 = TodaysMetData.CO2; AirPressure = calculateAirPressure(27.08889); // to default 1010; @@ -721,26 +721,22 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); if (co2Index == -1) - readMetData.CO2 = 350; + { // CO2 is not a column - check for a constant + if (reader.Constant("co2") != null) + readMetData.DayLength = reader.ConstantAsDouble("co2"); + else + readMetData.CO2 = 350; + } else readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (diffuseFractionIndex == -1) - { - // estimate diffuse fraction using the approach of Bristow and Campbell - double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) - double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); - double B = Qmax / Q0; - double Tt = MathUtilities.Bound(readMetData.Radn / Q0, 0, 1); - if (Tt > B) Tt = B; - readMetData.DiffuseFraction = (1 - Math.Exp(0.6 * (1 - B / Tt) / (B - 0.4))); - if (Tt > 0.5 && readMetData.DiffuseFraction < 0.1) readMetData.DiffuseFraction = 0.1; - } + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); else readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - if (dayLengthIndex == -1) // DayLength is not a column - check for a constant - { + if (dayLengthIndex == -1) + { // DayLength is not a column - check for a constant if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else @@ -901,6 +897,23 @@ public double CalculateSunSet() return 12 + CalculateDayLength(-6) / 2; } + /// Estimate diffuse radiation fraction (0-1) + /// Uses the approach of Bristow and Campbell (0000) + /// The diffuse radiation fraction + private double calculateDiffuseRadiationFraction(double todaysRadiation) + { + // estimate diffuse fraction using the approach of Bristow and Campbell + double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) + double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); + double B = Qmax / Q0; + double Tt = MathUtilities.Bound(todaysRadiation / Q0, 0, 1); + if (Tt > B) Tt = B; + double result = (1 - Math.Exp(0.6 * (1 - B / Tt) / (B - 0.4))); + if (Tt > 0.5 && result < 0.1) + result = 0.1; + return result; + } + /// Computes today's atmospheric vapour pressure deficit (hPa) /// Today's minimum temperature (oC) /// Today's maximum temperature (oC) From e2830561ff3df40c0d26f7813ce558976eb8f327 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 03:31:52 +1200 Subject: [PATCH 12/39] Enable airPressure to be read from met file, added defaults for Wind and CO2, standardised co2 to be read from met or set via manager, etc. --- Models/Climate/SimpleWeather.cs | 59 ++++++++++++----- Models/Climate/Weather.cs | 109 ++++++++++++++++++-------------- 2 files changed, 106 insertions(+), 62 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 173845c21e..5e2e65eb51 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -102,6 +102,21 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles /// private int co2Index; + /// + /// The index of the air pressure column in the weather file + /// + private int airPressureIndex; + + /// + /// Default value for wind speed (m/s) + /// + private const double defaultWind = 3.0; + + /// + /// Default value for atmospheric CO2 concentration (ppm) + /// + private const double defaultCO2 = 350.0; + /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -397,6 +412,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) vapourPressureIndex = 0; windIndex = 0; co2Index = 0; + airPressureIndex = 0; if (reader != null) { @@ -534,6 +550,21 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) else readMetData.Radn = reader.ConstantAsDouble("radn"); + if (dayLengthIndex == -1) // DayLength is not a column - check for a constant + { + if (reader.Constant("daylength") != null) + readMetData.DayLength = reader.ConstantAsDouble("daylength"); + else + readMetData.DayLength = -1; + } + else + readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); + + if (diffuseFractionIndex == -1) + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + else + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + if (rainIndex != -1) readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); else @@ -555,34 +586,31 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); if (windIndex == -1) - readMetData.Wind = 3.0; + readMetData.Wind = defaultWind; else readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); if (co2Index == -1) { // CO2 is not a column - check for a constant if (reader.Constant("co2") != null) - readMetData.DayLength = reader.ConstantAsDouble("co2"); + readMetData.CO2 = reader.ConstantAsDouble("co2"); else - readMetData.CO2 = 350; + readMetData.CO2 = defaultCO2; } else readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - if (diffuseFractionIndex == -1) - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + if (airPressureIndex >= 0) + { + readMetData.AirPressure = Convert.ToDouble(readMetData.Raw[airPressureIndex], CultureInfo.InvariantCulture); + } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - - if (dayLengthIndex == -1) // DayLength is not a column - check for a constant { - if (reader.Constant("daylength") != null) - readMetData.DayLength = reader.ConstantAsDouble("daylength"); + if (reader.Constant("airpressure") != null) + readMetData.AirPressure = reader.ConstantAsDouble("airpressure"); else - readMetData.DayLength = -1; + readMetData.AirPressure = calculateAirPressure(27.08889); // returns default 1010; } - else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); return readMetData; } @@ -613,14 +641,15 @@ public bool OpenDataFile() minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); + dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); + diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); - diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); - dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); + airPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "AirPressure"); if (!string.IsNullOrEmpty(ConstantsFile)) { diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 94f379830e..557f1bf13f 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -93,15 +93,25 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private int radiationIndex; + /// + /// The index of the day length column in the weather file + /// + private int dayLengthIndex; + + /// + /// The index of the diffuse radiation fraction column in the weather file + /// + private int diffuseFractionIndex; + /// /// The index of the rainfall column in the weather file /// private int rainIndex; /// - /// The index of the evaporation column in the weather file + /// The index of the pan evaporation column in the weather file /// - private int evaporationIndex; + private int panEvaporationIndex; /// /// The index of the rainfall duration column in the weather file @@ -119,27 +129,24 @@ public class Weather : Model, IWeather, IReferenceExternalFiles private int windIndex; /// - /// The index of the co2 column in the weather file, or -1 - /// if the weather file doesn't contain co2. + /// The index of the co2 column in the weather file /// private int co2Index; /// - /// The index of the diffuse radiation fraction column in the weather file + /// The index of the air pressure column in the weather file /// - private int diffuseFractionIndex; + private int airPressureIndex; /// - /// The index of the day length column in the weather file + /// Default value for wind speed (m/s) /// - private int dayLengthIndex; + private const double defaultWind = 3.0; /// - /// Stores the CO2 value from either the default 350 or from a column in met file. Public property can then also check - /// if this value was supplied by a constant + /// Default value for atmospheric CO2 concentration (ppm) /// - [JsonIgnore] - private double co2Value { get; set; } + private const double defaultCO2 = 350.0; /// /// Stores the optional constants file name. This should only be accessed via @@ -314,20 +321,7 @@ public DateTime EndDate /// Gets or sets the CO2 level in the atmosphere (ppm) [Units("ppm")] [JsonIgnore] - public double CO2 - { - get - { - if (reader == null || reader.Constant("co2") == null) - return co2Value; - else - return reader.ConstantAsDouble("co2"); - } - set - { - co2Value = value; - } - } + public double CO2 { get; set; } /// Gets or sets the mean atmospheric air pressure [Units("hPa")] @@ -526,11 +520,12 @@ private void OnSimulationCommencing(object sender, EventArgs e) dayLengthIndex = 0; diffuseFractionIndex = 0; rainIndex = 0; - evaporationIndex = 0; + panEvaporationIndex = 0; rainfallHoursIndex = 0; vapourPressureIndex = 0; windIndex = 0; co2Index = 0; + airPressureIndex = 0; if (reader != null) { @@ -587,7 +582,7 @@ private void OnDoWeather(object sender, EventArgs e) VP = TodaysMetData.VP; Wind = TodaysMetData.Wind; CO2 = TodaysMetData.CO2; - AirPressure = calculateAirPressure(27.08889); // to default 1010; + AirPressure = TodaysMetData.AirPressure; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -676,6 +671,13 @@ public DailyMetDataFromFile GetMetData(DateTime date) } /// Checks the values for weather data, uses either daily values or a constant + /// + /// For each variable handled by Weather, this method will firstly check whether there is a column + /// with daily data in the met file (i.e. there is an index equal or greater than zero), if not, it + /// will check whether a constant value was given (a single value in the met file, like latitude or + /// TAV). If that fails, either a default value is supplied or 'null' is returned (which results in + /// an exception being thrown later on) + /// /// The weather data structure with values for one line /// The weather data structure with values checked private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) @@ -695,15 +697,30 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) else readMetData.Radn = reader.ConstantAsDouble("radn"); + if (dayLengthIndex == -1) + { + if (reader.Constant("daylength") != null) + readMetData.DayLength = reader.ConstantAsDouble("daylength"); + else + readMetData.DayLength = -1; + } + else + readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); + + if (diffuseFractionIndex == -1) + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + else + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + if (rainIndex != -1) readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); else readMetData.Rain = reader.ConstantAsDouble("rain"); - if (evaporationIndex == -1) + if (panEvaporationIndex == -1) readMetData.PanEvap = double.NaN; else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[evaporationIndex], CultureInfo.InvariantCulture); + readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); if (rainfallHoursIndex == -1) readMetData.RainfallHours = double.NaN; @@ -716,34 +733,31 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); if (windIndex == -1) - readMetData.Wind = 3.0; + readMetData.Wind = defaultWind; else readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); if (co2Index == -1) - { // CO2 is not a column - check for a constant + { if (reader.Constant("co2") != null) - readMetData.DayLength = reader.ConstantAsDouble("co2"); + readMetData.CO2 = reader.ConstantAsDouble("co2"); else - readMetData.CO2 = 350; + readMetData.CO2 = defaultCO2; } else readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - if (diffuseFractionIndex == -1) - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + if (airPressureIndex >= 0) + { + readMetData.AirPressure = Convert.ToDouble(readMetData.Raw[airPressureIndex], CultureInfo.InvariantCulture); + } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - - if (dayLengthIndex == -1) - { // DayLength is not a column - check for a constant - if (reader.Constant("daylength") != null) - readMetData.DayLength = reader.ConstantAsDouble("daylength"); + { + if (reader.Constant("airpressure") != null) + readMetData.AirPressure = reader.ConstantAsDouble("airpressure"); else - readMetData.DayLength = -1; + readMetData.AirPressure = calculateAirPressure(27.08889); // returns default 1010; } - else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); return readMetData; } @@ -816,14 +830,15 @@ public bool OpenDataFile() minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); + dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); - evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); + panEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); - dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); + airPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "AirPressure"); if (!string.IsNullOrEmpty(ConstantsFile)) { From 241d1af9c9cd82c30599fc8204705c9e9648b5fa Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 04:45:09 +1200 Subject: [PATCH 13/39] Further standardisation of tests and behaviur for setting weather variables --- Models/Climate/SimpleWeather.cs | 172 ++++++++++++++++++++++---------- Models/Climate/Weather.cs | 150 +++++++++++++++++++--------- 2 files changed, 224 insertions(+), 98 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 5e2e65eb51..802a9e10e3 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -77,9 +77,9 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int rainIndex; /// - /// The index of the evaporation column in the weather file + /// The index of the pan evaporation column in the weather file /// - private int evaporationIndex; + private int panEvaporationIndex; /// /// The index of the rainfall duration column in the weather file @@ -97,8 +97,7 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int windIndex; /// - /// The index of the co2 column in the weather file, or -1 - /// if the weather file doesn't contain co2. + /// The index of the co2 column in the weather file /// private int co2Index; @@ -117,6 +116,11 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles /// private const double defaultCO2 = 350.0; + /// + /// Default value for solar angle for computing twilight (degrees) + /// + private const double defaultTwilight = 6.0; + /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -227,16 +231,16 @@ public DateTime EndDate /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] - [Units("°C")] + [Units("oC")] public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MeanT { get; set; } @@ -337,7 +341,7 @@ public double Longitude } /// Gets the long-term average air temperature (oC) - [Units("°C")] + [Units("oC")] public double Tav { get @@ -349,7 +353,7 @@ public double Tav } /// Gets the long-term average temperature amplitude (oC) - [Units("°C")] + [Units("oC")] public double Amp { get @@ -407,7 +411,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) dayLengthIndex = 0; diffuseFractionIndex = 0; rainIndex = 0; - evaporationIndex = 0; + panEvaporationIndex = 0; rainfallHoursIndex = 0; vapourPressureIndex = 0; windIndex = 0; @@ -490,7 +494,7 @@ private void OnDoWeather(object sender, EventArgs e) VP = TodaysMetData.VP; Wind = TodaysMetData.Wind; CO2 = TodaysMetData.CO2; - AirPressure = calculateAirPressure(27.08889); // to default 1010; + AirPressure = TodaysMetData.AirPressure; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -499,6 +503,7 @@ private void OnDoWeather(object sender, EventArgs e) // compute a series of values derived from weather data MeanT = (MaxT + MinT) / 2.0; Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); + VPD = calculateVapourPressureDefict(MinT, MaxT, VP); // do sanity check on weather SensibilityCheck(clock as Clock, this); @@ -527,78 +532,140 @@ public DailyMetDataFromFile GetMetData(DateTime date) if (date != reader.GetDateFromValues(readMetData.Raw)) throw new Exception("Non consecutive dates found in file: " + _fileName + "."); - return CheckDailyMetData(readMetData); + return checkDailyMetData(readMetData); } /// Checks the values for weather data, uses either daily values or a constant + /// + /// For each variable handled by Weather, this method will firstly check whether there is a column + /// with daily data in the met file (i.e. there is an index equal or greater than zero), if not, it + /// will check whether a constant value was given (a single value in the met file, like latitude or + /// TAV). If that fails, either a default value is supplied or 'null' is returned (which results in + /// an exception being thrown later on) + /// /// The weather data structure with values for one line /// The weather data structure with values checked - private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) + private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) { - if (minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex >= 0) + { + readMetData.MinT = Convert.ToDouble(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MinT = reader.ConstantAsDouble("mint"); + } - if (maximumTemperatureIndex != -1) - readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + if (maximumTemperatureIndex >= 0) + { + readMetData.MaxT = Convert.ToDouble(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MaxT = reader.ConstantAsDouble("maxt"); + } - if (radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + if (radiationIndex >= 0) + { + readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Radn = reader.ConstantAsDouble("radn"); + } - if (dayLengthIndex == -1) // DayLength is not a column - check for a constant + if (dayLengthIndex >= 0) + { + readMetData.DayLength = Convert.ToDouble(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); + } + else { if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = -1; + readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); } - else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - if (diffuseFractionIndex == -1) - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + if (diffuseFractionIndex >= 0) + { + readMetData.DiffuseFraction = Convert.ToDouble(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("diffr") != null) + readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); + else + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + } - if (rainIndex != -1) - readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + if (rainIndex >= 0) + { + readMetData.Rain = Convert.ToDouble(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Rain = reader.ConstantAsDouble("rain"); + } - if (evaporationIndex == -1) - readMetData.PanEvap = double.NaN; + if (panEvaporationIndex >= 0) + { + readMetData.PanEvap = Convert.ToDouble(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); + } else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[evaporationIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("evap") != null) + readMetData.PanEvap = reader.ConstantAsDouble("evap"); + else + readMetData.PanEvap = double.NaN; + } - if (rainfallHoursIndex == -1) - readMetData.RainfallHours = double.NaN; + if (rainfallHoursIndex >= 0) + { + readMetData.RainfallHours = Convert.ToDouble(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + } else - readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("rainhours") != null) + readMetData.RainfallHours = reader.ConstantAsDouble("rainhours"); + else + readMetData.RainfallHours = double.NaN; + } - if (vapourPressureIndex == -1) - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + if (vapourPressureIndex >= 0) + { + readMetData.VP = Convert.ToDouble(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + } else - readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("vp") != null) + readMetData.VP = reader.ConstantAsDouble("vp"); + else + readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + } - if (windIndex == -1) - readMetData.Wind = defaultWind; + if (windIndex >= 0) + { + readMetData.Wind = Convert.ToDouble(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + } else - readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("wind") != null) + readMetData.Wind = reader.ConstantAsDouble("wind"); + else + readMetData.Wind = defaultWind; + } - if (co2Index == -1) - { // CO2 is not a column - check for a constant + if (co2Index >= 0) + { + readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); + } + else + { if (reader.Constant("co2") != null) readMetData.CO2 = reader.ConstantAsDouble("co2"); else readMetData.CO2 = defaultCO2; } - else - readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (airPressureIndex >= 0) { @@ -644,7 +711,7 @@ public bool OpenDataFile() dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); - evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); + panEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); @@ -700,13 +767,10 @@ public void CloseDataFile() /// Computes the duration of the day, with light (hours) /// The angle to measure time for twilight (degrees) - /// The length of day light + /// The number of hours of daylight public double CalculateDayLength(double Twilight) { - if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); - else - return DayLength; + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); } /// Computes the time of sun rise (h) @@ -724,11 +788,13 @@ public double CalculateSunSet() } /// Estimate diffuse radiation fraction (0-1) - /// Uses the approach of Bristow and Campbell (0000) + /// + /// Uses the approach of Bristow and Campbell (1984). On the relationship between incoming solar + /// radiation and daily maximum and minimum temperature. Agricultural and Forest Meteorology + /// /// The diffuse radiation fraction private double calculateDiffuseRadiationFraction(double todaysRadiation) { - // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -748,6 +814,7 @@ private double calculateDiffuseRadiationFraction(double todaysRadiation) private double calculateVapourPressureDefict(double minTemp, double maxTemp, double vapourPressure) { const double SVPfrac = 0.66; + double result; double VPDmint = MetUtilities.svp(minTemp) - vapourPressure; VPDmint = Math.Max(VPDmint, 0.0); @@ -760,6 +827,7 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou } /// Computes the air pressure for a given location + /// From Jacobson (2005). Fundamentals of atmospheric modeling /// The altitude (m) /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 557f1bf13f..0c868f8f8f 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -1,7 +1,5 @@ using APSIM.Shared.Utilities; -using DocumentFormat.OpenXml.Packaging; using Models.Core; -using Models.Functions; using Models.Interfaces; using Newtonsoft.Json; using System; @@ -148,6 +146,11 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private const double defaultCO2 = 350.0; + /// + /// Default value for solar angle for computing twilight (degrees) + /// + private const double defaultTwilight = 6.0; + /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -255,16 +258,16 @@ public DateTime EndDate /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] - [Units("°C")] + [Units("oC")] public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MeanT { get; set; } @@ -365,7 +368,7 @@ public double Longitude } /// Gets the long-term average air temperature (oC) - [Units("°C")] + [Units("oC")] public double Tav { get @@ -380,7 +383,7 @@ public double Tav } /// Gets the long-term average temperature amplitude (oC) - [Units("°C")] + [Units("oC")] public double Amp { get @@ -667,7 +670,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) else weatherCache.AddFirst(record); - return CheckDailyMetData(readMetData); + return checkDailyMetData(readMetData); } /// Checks the values for weather data, uses either daily values or a constant @@ -680,72 +683,127 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// /// The weather data structure with values for one line /// The weather data structure with values checked - private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) + private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) { - if (minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex >= 0) + { + readMetData.MinT = Convert.ToDouble(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MinT = reader.ConstantAsDouble("mint"); + } - if (maximumTemperatureIndex != -1) - readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + if (maximumTemperatureIndex >= 0) + { + readMetData.MaxT = Convert.ToDouble(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MaxT = reader.ConstantAsDouble("maxt"); + } - if (radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + if (radiationIndex >= 0) + { + readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Radn = reader.ConstantAsDouble("radn"); + } - if (dayLengthIndex == -1) + if (dayLengthIndex >= 0) + { + readMetData.DayLength = Convert.ToDouble(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); + } + else { if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = -1; + readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); } - else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - if (diffuseFractionIndex == -1) - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + if (diffuseFractionIndex >= 0) + { + readMetData.DiffuseFraction = Convert.ToDouble(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("diffr") != null) + readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); + else + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + } - if (rainIndex != -1) - readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + if (rainIndex >= 0) + { + readMetData.Rain = Convert.ToDouble(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Rain = reader.ConstantAsDouble("rain"); + } - if (panEvaporationIndex == -1) - readMetData.PanEvap = double.NaN; + if (panEvaporationIndex >= 0) + { + readMetData.PanEvap = Convert.ToDouble(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); + } else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("evap") != null) + readMetData.PanEvap = reader.ConstantAsDouble("evap"); + else + readMetData.PanEvap = double.NaN; + } - if (rainfallHoursIndex == -1) - readMetData.RainfallHours = double.NaN; + if (rainfallHoursIndex >= 0) + { + readMetData.RainfallHours = Convert.ToDouble(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + } else - readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("rainhours") != null) + readMetData.RainfallHours = reader.ConstantAsDouble("rainhours"); + else + readMetData.RainfallHours = double.NaN; + } - if (vapourPressureIndex == -1) - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + if (vapourPressureIndex >= 0) + { + readMetData.VP = Convert.ToDouble(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + } else - readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("vp") != null) + readMetData.VP = reader.ConstantAsDouble("vp"); + else + readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + } - if (windIndex == -1) - readMetData.Wind = defaultWind; + if (windIndex >= 0) + { + readMetData.Wind = Convert.ToDouble(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + } else - readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("wind") != null) + readMetData.Wind = reader.ConstantAsDouble("wind"); + else + readMetData.Wind = defaultWind; + } - if (co2Index == -1) + if (co2Index >= 0) + { + readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); + } + else { if (reader.Constant("co2") != null) readMetData.CO2 = reader.ConstantAsDouble("co2"); else readMetData.CO2 = defaultCO2; } - else - readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (airPressureIndex >= 0) { @@ -889,13 +947,10 @@ public void CloseDataFile() /// Computes the duration of the day, with light (hours) /// The angle to measure time for twilight (degrees) - /// The length of day light + /// The number of hours of daylight public double CalculateDayLength(double Twilight) { - if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); - else - return DayLength; + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); } /// Computes the time of sun rise (h) @@ -913,11 +968,13 @@ public double CalculateSunSet() } /// Estimate diffuse radiation fraction (0-1) - /// Uses the approach of Bristow and Campbell (0000) + /// + /// Uses the approach of Bristow and Campbell (1984). On the relationship between incoming solar + /// radiation and daily maximum and minimum temperature. Agricultural and Forest Meteorology + /// /// The diffuse radiation fraction private double calculateDiffuseRadiationFraction(double todaysRadiation) { - // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -950,6 +1007,7 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou } /// Computes the air pressure for a given location + /// From Jacobson (2005). Fundamentals of atmospheric modeling /// The altitude (m) /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) From c2eebc5e64a3eb150f7d1c0e4ee946226a52d3f9 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 07:15:32 +1200 Subject: [PATCH 14/39] Revert "Further standardisation of tests and behaviur for setting weather variables" This reverts commit 241d1af9c9cd82c30599fc8204705c9e9648b5fa. --- Models/Climate/SimpleWeather.cs | 172 ++++++++++---------------------- Models/Climate/Weather.cs | 150 +++++++++------------------- 2 files changed, 98 insertions(+), 224 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 802a9e10e3..5e2e65eb51 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -77,9 +77,9 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int rainIndex; /// - /// The index of the pan evaporation column in the weather file + /// The index of the evaporation column in the weather file /// - private int panEvaporationIndex; + private int evaporationIndex; /// /// The index of the rainfall duration column in the weather file @@ -97,7 +97,8 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int windIndex; /// - /// The index of the co2 column in the weather file + /// The index of the co2 column in the weather file, or -1 + /// if the weather file doesn't contain co2. /// private int co2Index; @@ -116,11 +117,6 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles /// private const double defaultCO2 = 350.0; - /// - /// Default value for solar angle for computing twilight (degrees) - /// - private const double defaultTwilight = 6.0; - /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -231,16 +227,16 @@ public DateTime EndDate /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] - [Units("oC")] + [Units("°C")] public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("oC")] + [Units("°C")] [JsonIgnore] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("oC")] + [Units("°C")] [JsonIgnore] public double MeanT { get; set; } @@ -341,7 +337,7 @@ public double Longitude } /// Gets the long-term average air temperature (oC) - [Units("oC")] + [Units("°C")] public double Tav { get @@ -353,7 +349,7 @@ public double Tav } /// Gets the long-term average temperature amplitude (oC) - [Units("oC")] + [Units("°C")] public double Amp { get @@ -411,7 +407,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) dayLengthIndex = 0; diffuseFractionIndex = 0; rainIndex = 0; - panEvaporationIndex = 0; + evaporationIndex = 0; rainfallHoursIndex = 0; vapourPressureIndex = 0; windIndex = 0; @@ -494,7 +490,7 @@ private void OnDoWeather(object sender, EventArgs e) VP = TodaysMetData.VP; Wind = TodaysMetData.Wind; CO2 = TodaysMetData.CO2; - AirPressure = TodaysMetData.AirPressure; + AirPressure = calculateAirPressure(27.08889); // to default 1010; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -503,7 +499,6 @@ private void OnDoWeather(object sender, EventArgs e) // compute a series of values derived from weather data MeanT = (MaxT + MinT) / 2.0; Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); - VPD = calculateVapourPressureDefict(MinT, MaxT, VP); // do sanity check on weather SensibilityCheck(clock as Clock, this); @@ -532,140 +527,78 @@ public DailyMetDataFromFile GetMetData(DateTime date) if (date != reader.GetDateFromValues(readMetData.Raw)) throw new Exception("Non consecutive dates found in file: " + _fileName + "."); - return checkDailyMetData(readMetData); + return CheckDailyMetData(readMetData); } /// Checks the values for weather data, uses either daily values or a constant - /// - /// For each variable handled by Weather, this method will firstly check whether there is a column - /// with daily data in the met file (i.e. there is an index equal or greater than zero), if not, it - /// will check whether a constant value was given (a single value in the met file, like latitude or - /// TAV). If that fails, either a default value is supplied or 'null' is returned (which results in - /// an exception being thrown later on) - /// /// The weather data structure with values for one line /// The weather data structure with values checked - private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) + private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (minimumTemperatureIndex >= 0) - { - readMetData.MinT = Convert.ToDouble(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); - } + if (minimumTemperatureIndex != -1) + readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); else - { readMetData.MinT = reader.ConstantAsDouble("mint"); - } - if (maximumTemperatureIndex >= 0) - { - readMetData.MaxT = Convert.ToDouble(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); - } + if (maximumTemperatureIndex != -1) + readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); else - { readMetData.MaxT = reader.ConstantAsDouble("maxt"); - } - if (radiationIndex >= 0) - { - readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); - } + if (radiationIndex != -1) + readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); else - { readMetData.Radn = reader.ConstantAsDouble("radn"); - } - if (dayLengthIndex >= 0) - { - readMetData.DayLength = Convert.ToDouble(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - } - else + if (dayLengthIndex == -1) // DayLength is not a column - check for a constant { if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); + readMetData.DayLength = -1; } + else + readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - if (diffuseFractionIndex >= 0) - { - readMetData.DiffuseFraction = Convert.ToDouble(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - } + if (diffuseFractionIndex == -1) + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); else - { - if (reader.Constant("diffr") != null) - readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); - else - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); - } + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - if (rainIndex >= 0) - { - readMetData.Rain = Convert.ToDouble(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); - } + if (rainIndex != -1) + readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); else - { readMetData.Rain = reader.ConstantAsDouble("rain"); - } - if (panEvaporationIndex >= 0) - { - readMetData.PanEvap = Convert.ToDouble(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); - } + if (evaporationIndex == -1) + readMetData.PanEvap = double.NaN; else - { - if (reader.Constant("evap") != null) - readMetData.PanEvap = reader.ConstantAsDouble("evap"); - else - readMetData.PanEvap = double.NaN; - } + readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[evaporationIndex], CultureInfo.InvariantCulture); - if (rainfallHoursIndex >= 0) - { - readMetData.RainfallHours = Convert.ToDouble(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); - } + if (rainfallHoursIndex == -1) + readMetData.RainfallHours = double.NaN; else - { - if (reader.Constant("rainhours") != null) - readMetData.RainfallHours = reader.ConstantAsDouble("rainhours"); - else - readMetData.RainfallHours = double.NaN; - } + readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); - if (vapourPressureIndex >= 0) - { - readMetData.VP = Convert.ToDouble(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); - } + if (vapourPressureIndex == -1) + readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); else - { - if (reader.Constant("vp") != null) - readMetData.VP = reader.ConstantAsDouble("vp"); - else - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); - } + readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); - if (windIndex >= 0) - { - readMetData.Wind = Convert.ToDouble(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); - } + if (windIndex == -1) + readMetData.Wind = defaultWind; else - { - if (reader.Constant("wind") != null) - readMetData.Wind = reader.ConstantAsDouble("wind"); - else - readMetData.Wind = defaultWind; - } + readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); - if (co2Index >= 0) - { - readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - } - else - { + if (co2Index == -1) + { // CO2 is not a column - check for a constant if (reader.Constant("co2") != null) readMetData.CO2 = reader.ConstantAsDouble("co2"); else readMetData.CO2 = defaultCO2; } + else + readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (airPressureIndex >= 0) { @@ -711,7 +644,7 @@ public bool OpenDataFile() dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); - panEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); + evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); @@ -767,10 +700,13 @@ public void CloseDataFile() /// Computes the duration of the day, with light (hours) /// The angle to measure time for twilight (degrees) - /// The number of hours of daylight + /// The length of day light public double CalculateDayLength(double Twilight) { - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + else + return DayLength; } /// Computes the time of sun rise (h) @@ -788,13 +724,11 @@ public double CalculateSunSet() } /// Estimate diffuse radiation fraction (0-1) - /// - /// Uses the approach of Bristow and Campbell (1984). On the relationship between incoming solar - /// radiation and daily maximum and minimum temperature. Agricultural and Forest Meteorology - /// + /// Uses the approach of Bristow and Campbell (0000) /// The diffuse radiation fraction private double calculateDiffuseRadiationFraction(double todaysRadiation) { + // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -814,7 +748,6 @@ private double calculateDiffuseRadiationFraction(double todaysRadiation) private double calculateVapourPressureDefict(double minTemp, double maxTemp, double vapourPressure) { const double SVPfrac = 0.66; - double result; double VPDmint = MetUtilities.svp(minTemp) - vapourPressure; VPDmint = Math.Max(VPDmint, 0.0); @@ -827,7 +760,6 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou } /// Computes the air pressure for a given location - /// From Jacobson (2005). Fundamentals of atmospheric modeling /// The altitude (m) /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 0c868f8f8f..557f1bf13f 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -1,5 +1,7 @@ using APSIM.Shared.Utilities; +using DocumentFormat.OpenXml.Packaging; using Models.Core; +using Models.Functions; using Models.Interfaces; using Newtonsoft.Json; using System; @@ -146,11 +148,6 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private const double defaultCO2 = 350.0; - /// - /// Default value for solar angle for computing twilight (degrees) - /// - private const double defaultTwilight = 6.0; - /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -258,16 +255,16 @@ public DateTime EndDate /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] - [Units("oC")] + [Units("°C")] public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("oC")] + [Units("°C")] [JsonIgnore] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("oC")] + [Units("°C")] [JsonIgnore] public double MeanT { get; set; } @@ -368,7 +365,7 @@ public double Longitude } /// Gets the long-term average air temperature (oC) - [Units("oC")] + [Units("°C")] public double Tav { get @@ -383,7 +380,7 @@ public double Tav } /// Gets the long-term average temperature amplitude (oC) - [Units("oC")] + [Units("°C")] public double Amp { get @@ -670,7 +667,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) else weatherCache.AddFirst(record); - return checkDailyMetData(readMetData); + return CheckDailyMetData(readMetData); } /// Checks the values for weather data, uses either daily values or a constant @@ -683,127 +680,72 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// /// The weather data structure with values for one line /// The weather data structure with values checked - private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) + private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) { - if (minimumTemperatureIndex >= 0) - { - readMetData.MinT = Convert.ToDouble(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); - } + if (minimumTemperatureIndex != -1) + readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); else - { readMetData.MinT = reader.ConstantAsDouble("mint"); - } - if (maximumTemperatureIndex >= 0) - { - readMetData.MaxT = Convert.ToDouble(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); - } + if (maximumTemperatureIndex != -1) + readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); else - { readMetData.MaxT = reader.ConstantAsDouble("maxt"); - } - if (radiationIndex >= 0) - { - readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); - } + if (radiationIndex != -1) + readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); else - { readMetData.Radn = reader.ConstantAsDouble("radn"); - } - if (dayLengthIndex >= 0) - { - readMetData.DayLength = Convert.ToDouble(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - } - else + if (dayLengthIndex == -1) { if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); + readMetData.DayLength = -1; } + else + readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - if (diffuseFractionIndex >= 0) - { - readMetData.DiffuseFraction = Convert.ToDouble(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - } + if (diffuseFractionIndex == -1) + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); else - { - if (reader.Constant("diffr") != null) - readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); - else - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); - } + readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); - if (rainIndex >= 0) - { - readMetData.Rain = Convert.ToDouble(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); - } + if (rainIndex != -1) + readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); else - { readMetData.Rain = reader.ConstantAsDouble("rain"); - } - if (panEvaporationIndex >= 0) - { - readMetData.PanEvap = Convert.ToDouble(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); - } + if (panEvaporationIndex == -1) + readMetData.PanEvap = double.NaN; else - { - if (reader.Constant("evap") != null) - readMetData.PanEvap = reader.ConstantAsDouble("evap"); - else - readMetData.PanEvap = double.NaN; - } + readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); - if (rainfallHoursIndex >= 0) - { - readMetData.RainfallHours = Convert.ToDouble(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); - } + if (rainfallHoursIndex == -1) + readMetData.RainfallHours = double.NaN; else - { - if (reader.Constant("rainhours") != null) - readMetData.RainfallHours = reader.ConstantAsDouble("rainhours"); - else - readMetData.RainfallHours = double.NaN; - } + readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); - if (vapourPressureIndex >= 0) - { - readMetData.VP = Convert.ToDouble(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); - } + if (vapourPressureIndex == -1) + readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); else - { - if (reader.Constant("vp") != null) - readMetData.VP = reader.ConstantAsDouble("vp"); - else - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); - } + readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); - if (windIndex >= 0) - { - readMetData.Wind = Convert.ToDouble(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); - } + if (windIndex == -1) + readMetData.Wind = defaultWind; else - { - if (reader.Constant("wind") != null) - readMetData.Wind = reader.ConstantAsDouble("wind"); - else - readMetData.Wind = defaultWind; - } + readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); - if (co2Index >= 0) - { - readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); - } - else + if (co2Index == -1) { if (reader.Constant("co2") != null) readMetData.CO2 = reader.ConstantAsDouble("co2"); else readMetData.CO2 = defaultCO2; } + else + readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (airPressureIndex >= 0) { @@ -947,10 +889,13 @@ public void CloseDataFile() /// Computes the duration of the day, with light (hours) /// The angle to measure time for twilight (degrees) - /// The number of hours of daylight + /// The length of day light public double CalculateDayLength(double Twilight) { - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + else + return DayLength; } /// Computes the time of sun rise (h) @@ -968,13 +913,11 @@ public double CalculateSunSet() } /// Estimate diffuse radiation fraction (0-1) - /// - /// Uses the approach of Bristow and Campbell (1984). On the relationship between incoming solar - /// radiation and daily maximum and minimum temperature. Agricultural and Forest Meteorology - /// + /// Uses the approach of Bristow and Campbell (0000) /// The diffuse radiation fraction private double calculateDiffuseRadiationFraction(double todaysRadiation) { + // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -1007,7 +950,6 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou } /// Computes the air pressure for a given location - /// From Jacobson (2005). Fundamentals of atmospheric modeling /// The altitude (m) /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) From c0080844090d1a11c2f1fb0f94eaa75d2e98d9e3 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 07:20:54 +1200 Subject: [PATCH 15/39] redo-standardisation, in steps. only doco --- Models/Climate/SimpleWeather.cs | 54 +++++++++++++++++++++------------ Models/Climate/Weather.cs | 30 ++++++++++-------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 5e2e65eb51..48f68706f0 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -77,9 +77,9 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int rainIndex; /// - /// The index of the evaporation column in the weather file + /// The index of the pan evaporation column in the weather file /// - private int evaporationIndex; + private int panEvaporationIndex; /// /// The index of the rainfall duration column in the weather file @@ -97,8 +97,7 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles private int windIndex; /// - /// The index of the co2 column in the weather file, or -1 - /// if the weather file doesn't contain co2. + /// The index of the co2 column in the weather file /// private int co2Index; @@ -117,6 +116,11 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles /// private const double defaultCO2 = 350.0; + /// + /// Default value for solar angle for computing twilight (degrees) + /// + private const double defaultTwilight = 6.0; + /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -227,16 +231,16 @@ public DateTime EndDate /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] - [Units("°C")] + [Units("oC")] public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MeanT { get; set; } @@ -337,7 +341,7 @@ public double Longitude } /// Gets the long-term average air temperature (oC) - [Units("°C")] + [Units("oC")] public double Tav { get @@ -349,7 +353,7 @@ public double Tav } /// Gets the long-term average temperature amplitude (oC) - [Units("°C")] + [Units("oC")] public double Amp { get @@ -407,7 +411,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) dayLengthIndex = 0; diffuseFractionIndex = 0; rainIndex = 0; - evaporationIndex = 0; + panEvaporationIndex = 0; rainfallHoursIndex = 0; vapourPressureIndex = 0; windIndex = 0; @@ -490,7 +494,7 @@ private void OnDoWeather(object sender, EventArgs e) VP = TodaysMetData.VP; Wind = TodaysMetData.Wind; CO2 = TodaysMetData.CO2; - AirPressure = calculateAirPressure(27.08889); // to default 1010; + AirPressure = TodaysMetData.AirPressure; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -499,6 +503,7 @@ private void OnDoWeather(object sender, EventArgs e) // compute a series of values derived from weather data MeanT = (MaxT + MinT) / 2.0; Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); + VPD = calculateVapourPressureDefict(MinT, MaxT, VP); // do sanity check on weather SensibilityCheck(clock as Clock, this); @@ -527,13 +532,20 @@ public DailyMetDataFromFile GetMetData(DateTime date) if (date != reader.GetDateFromValues(readMetData.Raw)) throw new Exception("Non consecutive dates found in file: " + _fileName + "."); - return CheckDailyMetData(readMetData); + return checkDailyMetData(readMetData); } /// Checks the values for weather data, uses either daily values or a constant + /// + /// For each variable handled by Weather, this method will firstly check whether there is a column + /// with daily data in the met file (i.e. there is an index equal or greater than zero), if not, it + /// will check whether a constant value was given (a single value in the met file, like latitude or + /// TAV). If that fails, either a default value is supplied or 'null' is returned (which results in + /// an exception being thrown later on) + /// /// The weather data structure with values for one line /// The weather data structure with values checked - private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) + private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) { if (minimumTemperatureIndex != -1) readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); @@ -570,10 +582,10 @@ private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) else readMetData.Rain = reader.ConstantAsDouble("rain"); - if (evaporationIndex == -1) + if (panEvaporationIndex == -1) readMetData.PanEvap = double.NaN; else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[evaporationIndex], CultureInfo.InvariantCulture); + readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); if (rainfallHoursIndex == -1) readMetData.RainfallHours = double.NaN; @@ -644,7 +656,7 @@ public bool OpenDataFile() dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); rainIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Rain"); - evaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); + panEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Evap"); rainfallHoursIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "RainHours"); vapourPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "VP"); windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); @@ -700,7 +712,7 @@ public void CloseDataFile() /// Computes the duration of the day, with light (hours) /// The angle to measure time for twilight (degrees) - /// The length of day light + /// The number of hours of daylight public double CalculateDayLength(double Twilight) { if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant @@ -724,11 +736,13 @@ public double CalculateSunSet() } /// Estimate diffuse radiation fraction (0-1) - /// Uses the approach of Bristow and Campbell (0000) + /// + /// Uses the approach of Bristow and Campbell (1984). On the relationship between incoming solar + /// radiation and daily maximum and minimum temperature. Agricultural and Forest Meteorology + /// /// The diffuse radiation fraction private double calculateDiffuseRadiationFraction(double todaysRadiation) { - // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -748,6 +762,7 @@ private double calculateDiffuseRadiationFraction(double todaysRadiation) private double calculateVapourPressureDefict(double minTemp, double maxTemp, double vapourPressure) { const double SVPfrac = 0.66; + double result; double VPDmint = MetUtilities.svp(minTemp) - vapourPressure; VPDmint = Math.Max(VPDmint, 0.0); @@ -760,6 +775,7 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou } /// Computes the air pressure for a given location + /// From Jacobson (2005). Fundamentals of atmospheric modeling /// The altitude (m) /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 557f1bf13f..308d4c5cac 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -1,7 +1,5 @@ using APSIM.Shared.Utilities; -using DocumentFormat.OpenXml.Packaging; using Models.Core; -using Models.Functions; using Models.Interfaces; using Newtonsoft.Json; using System; @@ -148,6 +146,11 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private const double defaultCO2 = 350.0; + /// + /// Default value for solar angle for computing twilight (degrees) + /// + private const double defaultTwilight = 6.0; + /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -255,16 +258,16 @@ public DateTime EndDate /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] - [Units("°C")] + [Units("oC")] public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("°C")] + [Units("oC")] [JsonIgnore] public double MeanT { get; set; } @@ -365,7 +368,7 @@ public double Longitude } /// Gets the long-term average air temperature (oC) - [Units("°C")] + [Units("oC")] public double Tav { get @@ -380,7 +383,7 @@ public double Tav } /// Gets the long-term average temperature amplitude (oC) - [Units("°C")] + [Units("oC")] public double Amp { get @@ -667,7 +670,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) else weatherCache.AddFirst(record); - return CheckDailyMetData(readMetData); + return checkDailyMetData(readMetData); } /// Checks the values for weather data, uses either daily values or a constant @@ -680,7 +683,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// /// The weather data structure with values for one line /// The weather data structure with values checked - private DailyMetDataFromFile CheckDailyMetData(DailyMetDataFromFile readMetData) + private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) { if (minimumTemperatureIndex != -1) readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); @@ -889,7 +892,7 @@ public void CloseDataFile() /// Computes the duration of the day, with light (hours) /// The angle to measure time for twilight (degrees) - /// The length of day light + /// The number of hours of daylight public double CalculateDayLength(double Twilight) { if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant @@ -913,11 +916,13 @@ public double CalculateSunSet() } /// Estimate diffuse radiation fraction (0-1) - /// Uses the approach of Bristow and Campbell (0000) + /// + /// Uses the approach of Bristow and Campbell (1984). On the relationship between incoming solar + /// radiation and daily maximum and minimum temperature. Agricultural and Forest Meteorology + /// /// The diffuse radiation fraction private double calculateDiffuseRadiationFraction(double todaysRadiation) { - // estimate diffuse fraction using the approach of Bristow and Campbell double Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, 0.0); // Radiation for clear and dry sky (ie low humidity) double Q0 = MetUtilities.Q0(clock.Today.DayOfYear + 1, Latitude); double B = Qmax / Q0; @@ -950,6 +955,7 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou } /// Computes the air pressure for a given location + /// From Jacobson (2005). Fundamentals of atmospheric modeling /// The altitude (m) /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) From 3a0ff3cefbaccb5c10460fce6b453f79347eb510 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 07:56:10 +1200 Subject: [PATCH 16/39] second stage of standardising how weather variables are set --- Models/Climate/SimpleWeather.cs | 109 ++++++++++++++++++++++++-------- Models/Climate/Weather.cs | 105 ++++++++++++++++++++++-------- 2 files changed, 160 insertions(+), 54 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 48f68706f0..82c67e8fc6 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -547,22 +547,34 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// The weather data structure with values checked private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) { - if (minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex >= 0) + { + readMetData.MinT = Convert.ToDouble(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MinT = reader.ConstantAsDouble("mint"); + } - if (maximumTemperatureIndex != -1) - readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + if (maximumTemperatureIndex >= 0) + { + readMetData.MaxT = Convert.ToDouble(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MaxT = reader.ConstantAsDouble("maxt"); + } - if (radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + if (radiationIndex >= 0) + { + readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Radn = reader.ConstantAsDouble("radn"); + } - if (dayLengthIndex == -1) // DayLength is not a column - check for a constant + if (dayLengthIndex == -1) { if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); @@ -572,45 +584,86 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) else readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - if (diffuseFractionIndex == -1) - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + if (diffuseFractionIndex >= 0) + { + readMetData.DiffuseFraction = Convert.ToDouble(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("diffr") != null) + readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); + else + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + } - if (rainIndex != -1) - readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + if (rainIndex >= 0) + { + readMetData.Rain = Convert.ToDouble(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Rain = reader.ConstantAsDouble("rain"); + } - if (panEvaporationIndex == -1) - readMetData.PanEvap = double.NaN; + if (panEvaporationIndex >= 0) + { + readMetData.PanEvap = Convert.ToDouble(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); + } else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("evap") != null) + readMetData.PanEvap = reader.ConstantAsDouble("evap"); + else + readMetData.PanEvap = double.NaN; + } - if (rainfallHoursIndex == -1) - readMetData.RainfallHours = double.NaN; + if (rainfallHoursIndex >= 0) + { + readMetData.RainfallHours = Convert.ToDouble(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + } else - readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("rainhours") != null) + readMetData.RainfallHours = reader.ConstantAsDouble("rainhours"); + else + readMetData.RainfallHours = double.NaN; + } - if (vapourPressureIndex == -1) - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + if (vapourPressureIndex >= 0) + { + readMetData.VP = Convert.ToDouble(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + } else - readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("vp") != null) + readMetData.VP = reader.ConstantAsDouble("vp"); + else + readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + } - if (windIndex == -1) - readMetData.Wind = defaultWind; + if (windIndex >= 0) + { + readMetData.Wind = Convert.ToDouble(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + } else - readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("wind") != null) + readMetData.Wind = reader.ConstantAsDouble("wind"); + else + readMetData.Wind = defaultWind; + } - if (co2Index == -1) - { // CO2 is not a column - check for a constant + if (co2Index >= 0) + { + readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); + } + else + { if (reader.Constant("co2") != null) readMetData.CO2 = reader.ConstantAsDouble("co2"); else readMetData.CO2 = defaultCO2; } - else - readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (airPressureIndex >= 0) { diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 308d4c5cac..6721b0be1a 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -685,20 +685,32 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// The weather data structure with values checked private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) { - if (minimumTemperatureIndex != -1) - readMetData.MinT = Convert.ToSingle(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + if (minimumTemperatureIndex >= 0) + { + readMetData.MinT = Convert.ToDouble(readMetData.Raw[minimumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MinT = reader.ConstantAsDouble("mint"); + } - if (maximumTemperatureIndex != -1) - readMetData.MaxT = Convert.ToSingle(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + if (maximumTemperatureIndex >= 0) + { + readMetData.MaxT = Convert.ToDouble(readMetData.Raw[maximumTemperatureIndex], CultureInfo.InvariantCulture); + } else + { readMetData.MaxT = reader.ConstantAsDouble("maxt"); + } - if (radiationIndex != -1) - readMetData.Radn = Convert.ToSingle(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + if (radiationIndex >= 0) + { + readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Radn = reader.ConstantAsDouble("radn"); + } if (dayLengthIndex == -1) { @@ -710,45 +722,86 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) else readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); - if (diffuseFractionIndex == -1) - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + if (diffuseFractionIndex >= 0) + { + readMetData.DiffuseFraction = Convert.ToDouble(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + } else - readMetData.DiffuseFraction = Convert.ToSingle(readMetData.Raw[diffuseFractionIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("diffr") != null) + readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); + else + readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + } - if (rainIndex != -1) - readMetData.Rain = Convert.ToSingle(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + if (rainIndex >= 0) + { + readMetData.Rain = Convert.ToDouble(readMetData.Raw[rainIndex], CultureInfo.InvariantCulture); + } else + { readMetData.Rain = reader.ConstantAsDouble("rain"); + } - if (panEvaporationIndex == -1) - readMetData.PanEvap = double.NaN; + if (panEvaporationIndex >= 0) + { + readMetData.PanEvap = Convert.ToDouble(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); + } else - readMetData.PanEvap = Convert.ToSingle(readMetData.Raw[panEvaporationIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("evap") != null) + readMetData.PanEvap = reader.ConstantAsDouble("evap"); + else + readMetData.PanEvap = double.NaN; + } - if (rainfallHoursIndex == -1) - readMetData.RainfallHours = double.NaN; + if (rainfallHoursIndex >= 0) + { + readMetData.RainfallHours = Convert.ToDouble(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + } else - readMetData.RainfallHours = Convert.ToSingle(readMetData.Raw[rainfallHoursIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("rainhours") != null) + readMetData.RainfallHours = reader.ConstantAsDouble("rainhours"); + else + readMetData.RainfallHours = double.NaN; + } - if (vapourPressureIndex == -1) - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + if (vapourPressureIndex >= 0) + { + readMetData.VP = Convert.ToDouble(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + } else - readMetData.VP = Convert.ToSingle(readMetData.Raw[vapourPressureIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("vp") != null) + readMetData.VP = reader.ConstantAsDouble("vp"); + else + readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + } - if (windIndex == -1) - readMetData.Wind = defaultWind; + if (windIndex >= 0) + { + readMetData.Wind = Convert.ToDouble(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + } else - readMetData.Wind = Convert.ToSingle(readMetData.Raw[windIndex], CultureInfo.InvariantCulture); + { + if (reader.Constant("wind") != null) + readMetData.Wind = reader.ConstantAsDouble("wind"); + else + readMetData.Wind = defaultWind; + } - if (co2Index == -1) + if (co2Index >= 0) + { + readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); + } + else { if (reader.Constant("co2") != null) readMetData.CO2 = reader.ConstantAsDouble("co2"); else readMetData.CO2 = defaultCO2; } - else - readMetData.CO2 = Convert.ToDouble(readMetData.Raw[co2Index], CultureInfo.InvariantCulture); if (airPressureIndex >= 0) { From 1d2fc625bacaf4ffc1c26bd0956b0f81d212760b Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 08:12:25 +1200 Subject: [PATCH 17/39] thrid step of standardisation, consider day length --- Models/Climate/SimpleWeather.cs | 16 +++++++++++----- Models/Climate/Weather.cs | 16 +++++++++++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 82c67e8fc6..fae51a0ded 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -574,15 +574,17 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) readMetData.Radn = reader.ConstantAsDouble("radn"); } - if (dayLengthIndex == -1) + if (dayLengthIndex >= 0) + { + readMetData.DayLength = Convert.ToDouble(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); + } + else { if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = -1; + readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); } - else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); if (diffuseFractionIndex >= 0) { @@ -768,10 +770,14 @@ public void CloseDataFile() /// The number of hours of daylight public double CalculateDayLength(double Twilight) { - if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant + if (dayLengthIndex == -1 && (reader.Constant("daylength") == null)) + { // day length was not given as column or set as a constant return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + } else + { return DayLength; + } } /// Computes the time of sun rise (h) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 6721b0be1a..202badf93d 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -712,15 +712,17 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) readMetData.Radn = reader.ConstantAsDouble("radn"); } - if (dayLengthIndex == -1) + if (dayLengthIndex >= 0) + { + readMetData.DayLength = Convert.ToDouble(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); + } + else { if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = -1; + readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); } - else - readMetData.DayLength = Convert.ToSingle(readMetData.Raw[dayLengthIndex], CultureInfo.InvariantCulture); if (diffuseFractionIndex >= 0) { @@ -948,10 +950,14 @@ public void CloseDataFile() /// The number of hours of daylight public double CalculateDayLength(double Twilight) { - if (dayLengthIndex == -1 && DayLength == -1) // daylength is not set as column or constant + if (dayLengthIndex == -1 && (reader.Constant("daylength") == null)) + { // day length was not given as column or set as a constant return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + } else + { return DayLength; + } } /// Computes the time of sun rise (h) From 95be138a8fea8918367dded5501a10e83869e5a2 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 09:05:24 +1200 Subject: [PATCH 18/39] Make MeaT readble from met file as well as settable. Defaults to (MinT+MaXT)/2 --- Models/Climate/SimpleWeather.cs | 25 ++++++- Models/Climate/Weather.cs | 27 ++++++-- Models/Interfaces/IWeather.cs | 111 ++++++++++++++++---------------- 3 files changed, 100 insertions(+), 63 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index fae51a0ded..df63705bdf 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -46,15 +46,20 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles [NonSerialized] private ApsimTextFile reader = null; + /// + /// The index of the minimum temperature column in the weather file + /// + private int minimumTemperatureIndex; + /// /// The index of the maximum temperature column in the weather file /// private int maximumTemperatureIndex; /// - /// The index of the minimum temperature column in the weather file + /// The index of the mean temperature column in the weather file /// - private int minimumTemperatureIndex; + private int meanTemperatureIndex; /// /// The index of the solar radiation column in the weather file @@ -407,6 +412,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) { minimumTemperatureIndex = 0; maximumTemperatureIndex = 0; + meanTemperatureIndex = 0; radiationIndex = 0; dayLengthIndex = 0; diffuseFractionIndex = 0; @@ -485,6 +491,7 @@ private void OnDoWeather(object sender, EventArgs e) // assign values to output variables MinT = TodaysMetData.MinT; MaxT = TodaysMetData.MaxT; + MeanT = TodaysMetData.MeanT; Radn = TodaysMetData.Radn; DayLength = TodaysMetData.DayLength; DiffuseFraction = TodaysMetData.DiffuseFraction; @@ -501,7 +508,6 @@ private void OnDoWeather(object sender, EventArgs e) PreparingNewWeatherData.Invoke(this, new EventArgs()); // compute a series of values derived from weather data - MeanT = (MaxT + MinT) / 2.0; Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); VPD = calculateVapourPressureDefict(MinT, MaxT, VP); @@ -565,6 +571,18 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) readMetData.MaxT = reader.ConstantAsDouble("maxt"); } + if (meanTemperatureIndex >= 0) + { + readMetData.MeanT = Convert.ToDouble(readMetData.Raw[meanTemperatureIndex], CultureInfo.InvariantCulture); + } + else + { + if (reader.Constant("meant") != null) + readMetData.MeanT = reader.ConstantAsDouble("meant"); + else + readMetData.MeanT = (readMetData.MinT + readMetData.MaxT) / 2.0; + } + if (radiationIndex >= 0) { readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); @@ -707,6 +725,7 @@ public bool OpenDataFile() minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); + meanTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Meant"); radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 202badf93d..3e8c57ea6a 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -76,16 +76,21 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private LinkedList weatherCache = new LinkedList(); + /// + /// The index of the minimum temperature column in the weather file + /// + private int minimumTemperatureIndex; + /// /// The index of the maximum temperature column in the weather file /// private int maximumTemperatureIndex; /// - /// The index of the minimum temperature column in the weather file + /// The index of the mean temperature column in the weather file /// - private int minimumTemperatureIndex; - + private int meanTemperatureIndex; + /// /// The index of the solar radiation column in the weather file /// @@ -519,6 +524,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) { minimumTemperatureIndex = 0; maximumTemperatureIndex = 0; + meanTemperatureIndex = 0; radiationIndex = 0; dayLengthIndex = 0; diffuseFractionIndex = 0; @@ -576,6 +582,7 @@ private void OnDoWeather(object sender, EventArgs e) // assign values to output variables MinT = TodaysMetData.MinT; MaxT = TodaysMetData.MaxT; + MeanT = TodaysMetData.MeanT; Radn = TodaysMetData.Radn; DayLength = TodaysMetData.DayLength; DiffuseFraction = TodaysMetData.DiffuseFraction; @@ -592,7 +599,6 @@ private void OnDoWeather(object sender, EventArgs e) PreparingNewWeatherData.Invoke(this, new EventArgs()); // compute a series of values derived from weather data - MeanT = (MaxT + MinT) / 2.0; Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); VPD = calculateVapourPressureDefict(MinT, MaxT, VP); DaysSinceWinterSolstice = calculateDaysSinceSolstice(DaysSinceWinterSolstice); @@ -703,6 +709,18 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) readMetData.MaxT = reader.ConstantAsDouble("maxt"); } + if (meanTemperatureIndex >= 0) + { + readMetData.MeanT = Convert.ToDouble(readMetData.Raw[meanTemperatureIndex], CultureInfo.InvariantCulture); + } + else + { + if (reader.Constant("meant") != null) + readMetData.MeanT = reader.ConstantAsDouble("meant"); + else + readMetData.MeanT = (readMetData.MinT + readMetData.MaxT) / 2.0; + } + if (radiationIndex >= 0) { readMetData.Radn = Convert.ToDouble(readMetData.Raw[radiationIndex], CultureInfo.InvariantCulture); @@ -887,6 +905,7 @@ public bool OpenDataFile() minimumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Mint"); maximumTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Maxt"); + meanTemperatureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Meant"); radiationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Radn"); dayLengthIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DayLength"); diffuseFractionIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "DifFr"); diff --git a/Models/Interfaces/IWeather.cs b/Models/Interfaces/IWeather.cs index 957ea2a5ad..c8c7bb1568 100644 --- a/Models/Interfaces/IWeather.cs +++ b/Models/Interfaces/IWeather.cs @@ -4,77 +4,75 @@ namespace Models.Interfaces { - /// A weather interface. + /// A weather data interface public interface IWeather { - /// Gets the start date of the weather file. + /// Gets the start date of the weather file DateTime StartDate { get; } - /// Gets the end date of the weather file. + /// Gets the end date of the weather file DateTime EndDate { get; } - /// Gets or sets the maximum temperature (oc) - double MaxT { get; set; } - - /// Gets or sets the minimum temperature (oc) + /// Gets or sets the minimum temperature (oC) double MinT { get; set; } - /// Mean temperature /// + /// Gets or sets the maximum temperature (oC) + double MaxT { get; set; } + + /// Gets or sets the mean temperature (oC) double MeanT { get; } - /// Daily mean VPD (hPa) /// - double VPD { get; } + /// Gets or sets the solar radiation (MJ/m2) + double Radn { get; set; } + + /// Gets or sets the diffuse radiation fraction (0-1) + double DiffuseFraction { get; set; } /// Gets or sets the rainfall (mm) double Rain { get; set; } - /// Pan evaporation + /// Class A pan evaporation (mm) public double PanEvap { get; set; } - /// Gets or sets the solar radiation. MJ/m2/day - double Radn { get; set; } - - /// Gets or sets the vapor pressure + /// Gets or sets the vapour pressure (hPa) double VP { get; set; } - /// Gets or sets the wind value found in weather file or zero if not specified. + /// Daily mean vapour pressure deficit (hPa) + double VPD { get; } + + /// Gets or sets the mean wind speed (m/s) double Wind { get; set; } - /// Gets or sets the CO2 level. If not specified in the weather file the default is 350. + /// Gets or sets the atmospheric CO2 level (ppm) double CO2 { get; set; } - /// Gets or sets the atmospheric air pressure. If not specified in the weather file the default is 1010 hPa. + /// Gets or sets the atmospheric air pressure (hPa) double AirPressure { get; set; } - /// Gets or sets the diffuse radiation fraction. If not specified in the weather file the default is 1. - double DiffuseFraction { get; set; } - - /// Gets the latitude + /// Gets the latitude (degrees) double Latitude { get; } - /// Gets the longitude + /// Gets the longitude (degrees) double Longitude { get; } - /// Gets the average temperature + /// Gets the long-term average temperature (oC) double Tav { get; } - /// Gets the temperature amplitude. + /// Gets the long-term temperature amplitude (oC) double Amp { get; } - /// Gets the average temperature + /// Gets the weather file name string FileName { get; } - /// Gets the duration of the day in hours. + /// Gets the duration of the daylight (h) double CalculateDayLength(double Twilight); - /// Gets the time the sun came up. + /// Gets the time the sun raises (h) double CalculateSunRise(); - /// Gets the time the sun went down. + /// Gets the time the sun sets (h) double CalculateSunSet(); - - /// MetData for tomorrow DailyMetDataFromFile TomorrowsMetData { get; } @@ -82,48 +80,49 @@ public interface IWeather DailyMetDataFromFile YesterdaysMetData { get; } } - /// - /// Structure containing daily met data variables - /// + /// Structure containing daily weather data variables [Serializable] public class DailyMetDataFromFile : Model { - /// Gets or sets the maximum temperature (oc) + /// Gets or sets the minimum temperature (oC) + public double MinT { get; set; } + + /// Gets or sets the maximum temperature (oC) public double MaxT { get; set; } - /// Gets or sets the minimum temperature (oc) - public double MinT { get; set; } + /// Gets or sets the mean temperature (oC) + public double MeanT { get; set; } - /// Daily evap /// - public double PanEvap { get; set; } + /// Gets or sets the solar radiation (MJ/m2) + public double Radn { get; set; } + + /// Gets or sets the daylight length (h) + public double DayLength { get; set; } + + /// Gets or sets the diffuse radiation fraction(0-1) + public double DiffuseFraction { get; set; } /// Gets or sets the rainfall (mm) public double Rain { get; set; } - /// Gets or sets the solar radiation. MJ/m2/day - public double Radn { get; set; } + /// Daily class A pan evaporation (mm) + public double PanEvap { get; set; } + + /// Gets or sets the duration of rain within a day (h) + public double RainfallHours { get; set; } - /// Gets or sets the vapor pressure + /// Gets or sets the vapour pressure (hPa) public double VP { get; set; } - /// Gets or sets the wind value found in weather file or zero if not specified. /// + /// Gets or sets the mean wind speed (m/s) public double Wind { get; set; } - /// Gets or sets the CO2 level. If not specified in the weather file the default is 350. - public double RainfallHours { get; set; } + /// Gets or sets the atmospheric CO2 level (ppm) + public double CO2 { get; set; } - /// Gets or sets the atmospheric air pressure. If not specified in the weather file the default is 1010 hPa. + /// Gets or sets the atmospheric air pressure (hPa) public double AirPressure { get; set; } - /// Gets or sets the diffuse radiation fraction. If not specified in the weather file the default is 1010 hPa. - public double DiffuseFraction { get; set; } - - /// Gets or sets the diffuse radiation fraction. If not specified in the weather file the default is 1010 hPa. - public double DayLength { get; set; } - - /// Daily co2 level. - public double CO2 { get; set; } - /// /// Raw data straight from the met file. This can be used to access /// non-standard variables which aren't auto-mapped to properties. @@ -132,7 +131,7 @@ public class DailyMetDataFromFile : Model } /// - /// Stores a weather data file with a datetime it was read from + /// Stores a weather data file with the date-time it was read from /// [Serializable] public class WeatherRecordEntry From a261c9daa41c716f12ea119298353350a43c89cc Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 19:06:40 +1200 Subject: [PATCH 19/39] reverting providing a value for daylength by default (models can only get via CalculateDayLength) --- Models/Climate/SimpleWeather.cs | 4 ++-- Models/Climate/Weather.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index df63705bdf..dfc9b60953 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -601,7 +601,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); + readMetData.DayLength = -1; } if (diffuseFractionIndex >= 0) @@ -789,7 +789,7 @@ public void CloseDataFile() /// The number of hours of daylight public double CalculateDayLength(double Twilight) { - if (dayLengthIndex == -1 && (reader.Constant("daylength") == null)) + if (dayLengthIndex == -1 && DayLength == -1) { // day length was not given as column or set as a constant return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); } diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 3e8c57ea6a..9cae759cec 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -739,7 +739,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("daylength") != null) readMetData.DayLength = reader.ConstantAsDouble("daylength"); else - readMetData.DayLength = MathUtilities.DayLength(clock.Today.DayOfYear, defaultTwilight, Latitude); + readMetData.DayLength = -1; } if (diffuseFractionIndex >= 0) @@ -969,7 +969,7 @@ public void CloseDataFile() /// The number of hours of daylight public double CalculateDayLength(double Twilight) { - if (dayLengthIndex == -1 && (reader.Constant("daylength") == null)) + if (dayLengthIndex == -1 && DayLength == -1) { // day length was not given as column or set as a constant return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); } From 8e3b398311fd1f2122a0456f068d7b8a1a5f8f92 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 19:40:11 +1200 Subject: [PATCH 20/39] improving resilience to change in latitude --- Models/Climate/Weather.cs | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 9cae759cec..57d4a4faae 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -349,8 +349,11 @@ public double Latitude } set { - if (this.reader != null) + if (reader != null) + { reader.Constant("Latitude").Value = value.ToString(); + checkSeasonDates(); + } } } @@ -542,18 +545,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) reader = null; } - if (Latitude > 0.0) - { - // Swap summer and winter dates. - var temp = FirstDateOfSummer; - FirstDateOfSummer = FirstDateOfWinter; - FirstDateOfWinter = temp; - - // Swap spring and autumn dates. - temp = FirstDateOfSpring; - FirstDateOfSpring = FirstDateOfAutumn; - FirstDateOfAutumn = temp; - } + checkSeasonDates(); foreach (var message in Validate()) summary.WriteMessage(this, message, MessageType.Warning); @@ -964,6 +956,25 @@ public void CloseDataFile() reader = null; } + /// + /// Checks whether the dates defining the seasons should be swapped if in the northern hemisphere + /// + private void checkSeasonDates() + { + if (Latitude > 0.0) + { + // Swap summer and winter dates. + var temp = FirstDateOfSummer; + FirstDateOfSummer = FirstDateOfWinter; + FirstDateOfWinter = temp; + + // Swap spring and autumn dates. + temp = FirstDateOfSpring; + FirstDateOfSpring = FirstDateOfAutumn; + FirstDateOfAutumn = temp; + } + } + /// Computes the duration of the day, with light (hours) /// The angle to measure time for twilight (degrees) /// The number of hours of daylight From b8090d6098f05a89b6c843c3af81911d42dc3b22 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 20:50:44 +1200 Subject: [PATCH 21/39] Ensure variables that could be filled in with 'default' calculation are tested after OnPreparingNewWeatherData, to make code more robust to changes --- Models/Climate/SimpleWeather.cs | 18 ++++++++++++++---- Models/Climate/Weather.cs | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index dfc9b60953..93d2906f0a 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -507,6 +507,16 @@ private void OnDoWeather(object sender, EventArgs e) if (PreparingNewWeatherData != null) PreparingNewWeatherData.Invoke(this, new EventArgs()); + // check whether some variables need to be set with 'default' values (functions) + if (MeanT == double.NaN) + MeanT = (MinT + MaxT) / 2.0; + if (DiffuseFraction == double.NaN) + DiffuseFraction = calculateDiffuseRadiationFraction(Radn); + if (VP == double.NaN) + VP = Math.Max(0, MetUtilities.svp(MinT)); + if (AirPressure == double.NaN) + AirPressure = calculateAirPressure(27.08889); // returns default 1010; + // compute a series of values derived from weather data Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); VPD = calculateVapourPressureDefict(MinT, MaxT, VP); @@ -580,7 +590,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("meant") != null) readMetData.MeanT = reader.ConstantAsDouble("meant"); else - readMetData.MeanT = (readMetData.MinT + readMetData.MaxT) / 2.0; + readMetData.MeanT = double.NaN; } if (radiationIndex >= 0) @@ -613,7 +623,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("diffr") != null) readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); else - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + readMetData.DiffuseFraction = double.NaN; } if (rainIndex >= 0) @@ -658,7 +668,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("vp") != null) readMetData.VP = reader.ConstantAsDouble("vp"); else - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + readMetData.VP = double.NaN; } if (windIndex >= 0) @@ -694,7 +704,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("airpressure") != null) readMetData.AirPressure = reader.ConstantAsDouble("airpressure"); else - readMetData.AirPressure = calculateAirPressure(27.08889); // returns default 1010; + readMetData.AirPressure = double.NaN; } return readMetData; diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 57d4a4faae..82486351d6 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -590,6 +590,16 @@ private void OnDoWeather(object sender, EventArgs e) if (PreparingNewWeatherData != null) PreparingNewWeatherData.Invoke(this, new EventArgs()); + // check whether some variables need to be set with 'default' values (functions) + if (MeanT == double.NaN) + MeanT = (MinT + MaxT) / 2.0; + if (DiffuseFraction == double.NaN) + DiffuseFraction = calculateDiffuseRadiationFraction(Radn); + if (VP == double.NaN) + VP = Math.Max(0, MetUtilities.svp(MinT)); + if (AirPressure == double.NaN) + AirPressure = calculateAirPressure(27.08889); // returns default 1010; + // compute a series of values derived from weather data Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); VPD = calculateVapourPressureDefict(MinT, MaxT, VP); @@ -710,7 +720,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("meant") != null) readMetData.MeanT = reader.ConstantAsDouble("meant"); else - readMetData.MeanT = (readMetData.MinT + readMetData.MaxT) / 2.0; + readMetData.MeanT = double.NaN; } if (radiationIndex >= 0) @@ -743,7 +753,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("diffr") != null) readMetData.DiffuseFraction = reader.ConstantAsDouble("diffr"); else - readMetData.DiffuseFraction = calculateDiffuseRadiationFraction(readMetData.Radn); + readMetData.DiffuseFraction = double.NaN; } if (rainIndex >= 0) @@ -788,7 +798,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("vp") != null) readMetData.VP = reader.ConstantAsDouble("vp"); else - readMetData.VP = Math.Max(0, MetUtilities.svp(readMetData.MinT)); + readMetData.VP = double.NaN; } if (windIndex >= 0) @@ -824,7 +834,7 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) if (reader.Constant("airpressure") != null) readMetData.AirPressure = reader.ConstantAsDouble("airpressure"); else - readMetData.AirPressure = calculateAirPressure(27.08889); // returns default 1010; + readMetData.AirPressure = double.NaN; } return readMetData; From 9d95e3293a9908e494775ccadeee447aef01aa0c Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 21:16:07 +1200 Subject: [PATCH 22/39] Ensure sanity check on weather is done before derived outputs are computed (increase robustness) --- Models/Climate/SimpleWeather.cs | 12 ++++++------ Models/Climate/Weather.cs | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 93d2906f0a..7a83c100b4 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -507,6 +507,9 @@ private void OnDoWeather(object sender, EventArgs e) if (PreparingNewWeatherData != null) PreparingNewWeatherData.Invoke(this, new EventArgs()); + // do basic sanity checks on weather data + sensibilityCheck(clock as Clock, this); + // check whether some variables need to be set with 'default' values (functions) if (MeanT == double.NaN) MeanT = (MinT + MaxT) / 2.0; @@ -517,12 +520,9 @@ private void OnDoWeather(object sender, EventArgs e) if (AirPressure == double.NaN) AirPressure = calculateAirPressure(27.08889); // returns default 1010; - // compute a series of values derived from weather data + // compute additional outputs derived from weather data Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); VPD = calculateVapourPressureDefict(MinT, MaxT, VP); - - // do sanity check on weather - SensibilityCheck(clock as Clock, this); } /// Reads the weather data for one day from file @@ -901,7 +901,7 @@ public double GetValue(string columnName) /// /// The clock /// The weather - private void SensibilityCheck(Clock clock, SimpleWeather weatherToday) + private void sensibilityCheck(Clock clock, SimpleWeather weatherToday) { if (weatherToday.MinT > weatherToday.MaxT) { @@ -919,7 +919,7 @@ private void SensibilityCheck(Clock clock, SimpleWeather weatherToday) { throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative ranfaill (" + weatherToday.Radn + ")"); } - if (weatherToday.VP <= 0) + if (weatherToday.VP != double.NaN && weatherToday.VP <= 0) { throw new Exception("Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0"); } diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 82486351d6..85dc1c67ea 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -590,23 +590,23 @@ private void OnDoWeather(object sender, EventArgs e) if (PreparingNewWeatherData != null) PreparingNewWeatherData.Invoke(this, new EventArgs()); + // do basic sanity checks on weather data + sensibilityCheck(clock as Clock, this); + // check whether some variables need to be set with 'default' values (functions) if (MeanT == double.NaN) MeanT = (MinT + MaxT) / 2.0; if (DiffuseFraction == double.NaN) DiffuseFraction = calculateDiffuseRadiationFraction(Radn); if (VP == double.NaN) - VP = Math.Max(0, MetUtilities.svp(MinT)); + VP = Math.Max(0.0, MetUtilities.svp(MinT)); if (AirPressure == double.NaN) AirPressure = calculateAirPressure(27.08889); // returns default 1010; - // compute a series of values derived from weather data + // compute additional outputs derived from weather data Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); VPD = calculateVapourPressureDefict(MinT, MaxT, VP); DaysSinceWinterSolstice = calculateDaysSinceSolstice(DaysSinceWinterSolstice); - - // do sanity check on weather - SensibilityCheck(clock as Clock, this); } /// Get the DataTable view of the weather data @@ -1232,7 +1232,7 @@ private void processMonthlyTAVAMP(out double tav, out double amp) /// /// The clock /// The weather - private void SensibilityCheck(Clock clock, Weather weatherToday) + private void sensibilityCheck(Clock clock, Weather weatherToday) { if (weatherToday.MinT > weatherToday.MaxT) { @@ -1250,7 +1250,7 @@ private void SensibilityCheck(Clock clock, Weather weatherToday) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative rainfall (" + weatherToday.Radn + ")", MessageType.Warning); } - if (weatherToday.VP <= 0) + if (weatherToday.VP != double.NaN && weatherToday.VP <= 0) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0", MessageType.Warning); } From 62d6f45f2c452a3d0232659cdc4a8e056ae75471 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 21:35:26 +1200 Subject: [PATCH 23/39] Fix test for NaN values --- Models/Climate/SimpleWeather.cs | 12 ++++++------ Models/Climate/Weather.cs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 7a83c100b4..253bb8efc4 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -511,13 +511,13 @@ private void OnDoWeather(object sender, EventArgs e) sensibilityCheck(clock as Clock, this); // check whether some variables need to be set with 'default' values (functions) - if (MeanT == double.NaN) + if (double.IsNaN(MeanT)) MeanT = (MinT + MaxT) / 2.0; - if (DiffuseFraction == double.NaN) + if (double.IsNaN(DiffuseFraction)) DiffuseFraction = calculateDiffuseRadiationFraction(Radn); - if (VP == double.NaN) - VP = Math.Max(0, MetUtilities.svp(MinT)); - if (AirPressure == double.NaN) + if (double.IsNaN(VP)) + VP = Math.Max(0.0, MetUtilities.svp(MinT)); + if (double.IsNaN(AirPressure)) AirPressure = calculateAirPressure(27.08889); // returns default 1010; // compute additional outputs derived from weather data @@ -919,7 +919,7 @@ private void sensibilityCheck(Clock clock, SimpleWeather weatherToday) { throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative ranfaill (" + weatherToday.Radn + ")"); } - if (weatherToday.VP != double.NaN && weatherToday.VP <= 0) + if (!double.IsNaN(weatherToday.VP) && weatherToday.VP <= 0) { throw new Exception("Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0"); } diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 85dc1c67ea..f9eccd8ddb 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -591,16 +591,16 @@ private void OnDoWeather(object sender, EventArgs e) PreparingNewWeatherData.Invoke(this, new EventArgs()); // do basic sanity checks on weather data - sensibilityCheck(clock as Clock, this); + sensibilityCheck(clock as Clock, this); // check whether some variables need to be set with 'default' values (functions) - if (MeanT == double.NaN) + if (double.IsNaN(MeanT)) MeanT = (MinT + MaxT) / 2.0; - if (DiffuseFraction == double.NaN) + if (double.IsNaN(DiffuseFraction)) DiffuseFraction = calculateDiffuseRadiationFraction(Radn); - if (VP == double.NaN) + if (double.IsNaN(VP)) VP = Math.Max(0.0, MetUtilities.svp(MinT)); - if (AirPressure == double.NaN) + if (double.IsNaN(AirPressure)) AirPressure = calculateAirPressure(27.08889); // returns default 1010; // compute additional outputs derived from weather data @@ -1250,7 +1250,7 @@ private void sensibilityCheck(Clock clock, Weather weatherToday) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has negative rainfall (" + weatherToday.Radn + ")", MessageType.Warning); } - if (weatherToday.VP != double.NaN && weatherToday.VP <= 0) + if (!double.IsNaN(weatherToday.VP) && weatherToday.VP <= 0) { summary.WriteMessage(weatherToday, "Error: Weather on " + clock.Today.ToString() + " has vapour pressure (" + weatherToday.VP + ") which is below 0", MessageType.Warning); } From 1df9195d89e8eab84c961180c1653c4e11f27032 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Thu, 19 Sep 2024 23:13:09 +1200 Subject: [PATCH 24/39] rename file name variables in SimpleWeather to align with Weather --- Models/Climate/SimpleWeather.cs | 59 +++++++++++++++++---------------- Models/Climate/Weather.cs | 4 +-- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 253bb8efc4..259c05eed6 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -170,38 +170,35 @@ public string ConstantsFile /// [Summary] [Description("Weather file name")] - public string _fileName { get; set; } + public string FileName { get; set; } /// /// Gets or sets the full file name (with path). Needed for the user interface /// [JsonIgnore] - public string FileName + public string FullFileName { get { Simulation simulation = FindAncestor(); - if (simulation != null) - return PathUtilities.GetAbsolutePath(_fileName, simulation.FileName); + if (simulation != null && simulation.FileName != null) + return PathUtilities.GetAbsolutePath(FileName, simulation.FileName); else { Simulations simulations = FindAncestor(); if (simulations != null) - return PathUtilities.GetAbsolutePath(_fileName, simulations.FileName); + return PathUtilities.GetAbsolutePath(FileName, simulations.FileName); else - return _fileName; + return PathUtilities.GetAbsolutePath(FileName, ""); } } set { Simulations simulations = FindAncestor(); if (simulations != null) - _fileName = PathUtilities.GetRelativePath(value, simulations.FileName); + FileName = PathUtilities.GetRelativePath(value, simulations.FileName); else - _fileName = value; - if (reader != null) - reader.Close(); - reader = null; + FileName = value; } } @@ -322,8 +319,10 @@ public double Latitude } set { - if (this.reader != null) + if (reader != null) + { reader.Constant("Latitude").Value = value.ToString(); + } } } @@ -384,13 +383,13 @@ public double Amp /// Returns our input file names public IEnumerable GetReferencedFileNames() { - return new string[] { FileName }; + return new string[] { FullFileName }; } /// Remove all paths from referenced file names public void RemovePathsFromReferencedFileNames() { - _fileName = Path.GetFileName(_fileName); + FileName = Path.GetFileName(FileName); } /// @@ -531,7 +530,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) { if (reader == null) if (!OpenDataFile()) - throw new ApsimXException(this, "Cannot find weather file '" + _fileName + "'"); + throw new ApsimXException(this, "Cannot find weather file '" + FileName + "'"); // get the weather data for that date DailyMetDataFromFile readMetData = new DailyMetDataFromFile(); @@ -542,11 +541,11 @@ public DailyMetDataFromFile GetMetData(DateTime date) } catch (IndexOutOfRangeException err) { - throw new Exception($"Unable to retrieve weather data on {date.ToString("yyy-MM-dd")} in file {_fileName}", err); + throw new Exception($"Unable to retrieve weather data on {date.ToString("yyy-MM-dd")} in file {FileName}", err); } if (date != reader.GetDateFromValues(readMetData.Raw)) - throw new Exception("Non consecutive dates found in file: " + _fileName + "."); + throw new Exception("Non consecutive dates found in file: " + FileName + "."); return checkDailyMetData(readMetData); } @@ -714,22 +713,26 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) /// True if the file was successfully opened public bool OpenDataFile() { - if (System.IO.File.Exists(FileName)) + if (!System.IO.File.Exists(FullFileName) && + System.IO.Path.GetExtension(FullFileName) == string.Empty) + FileName += ".met"; + + if (System.IO.File.Exists(FullFileName)) { if (reader == null) { - if (ExcelUtilities.IsExcelFile(FileName) && string.IsNullOrEmpty(ExcelWorkSheetName)) - throw new Exception($"Unable to open excel file {FileName}: no sheet name is specified"); + if (ExcelUtilities.IsExcelFile(FullFileName) && string.IsNullOrEmpty(ExcelWorkSheetName)) + throw new Exception($"Unable to open excel file {FullFileName}: no sheet name is specified"); reader = new ApsimTextFile(); - reader.Open(FileName, ExcelWorkSheetName); + reader.Open(FullFileName, ExcelWorkSheetName); if (reader.Headings == null) { string message = "Cannot find the expected header in "; - if (ExcelUtilities.IsExcelFile(FileName)) + if (ExcelUtilities.IsExcelFile(FullFileName)) message += $"sheet '{ExcelWorkSheetName}' of "; - message += $"weather file: {FileName}"; + message += $"weather file: {FullFileName}"; throw new Exception(message); } @@ -758,19 +761,19 @@ public bool OpenDataFile() if (minimumTemperatureIndex == -1) if (reader == null || reader.Constant("mint") == null) - throw new Exception("Cannot find MinT in weather file: " + FileName); + throw new Exception("Cannot find MinT in weather file: " + FullFileName); if (maximumTemperatureIndex == -1) if (reader == null || reader.Constant("maxt") == null) - throw new Exception("Cannot find MaxT in weather file: " + FileName); + throw new Exception("Cannot find MaxT in weather file: " + FullFileName); if (radiationIndex == -1) if (reader == null || reader.Constant("radn") == null) - throw new Exception("Cannot find Radn in weather file: " + FileName); + throw new Exception("Cannot find Radn in weather file: " + FullFileName); if (rainIndex == -1) if (reader == null || reader.Constant("rain") == null) - throw new Exception("Cannot find Rain in weather file: " + FileName); + throw new Exception("Cannot find Rain in weather file: " + FullFileName); } else { @@ -886,7 +889,7 @@ public double GetValue(string columnName) { int columnIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, columnName); if (columnIndex == -1) - throw new InvalidOperationException($"Column {columnName} does not exist in {_fileName}"); + throw new InvalidOperationException($"Column {columnName} does not exist in {FileName}"); return Convert.ToDouble(TodaysMetData.Raw[columnIndex], CultureInfo.InvariantCulture); } diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index f9eccd8ddb..bde9f45553 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -90,7 +90,7 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// The index of the mean temperature column in the weather file /// private int meanTemperatureIndex; - + /// /// The index of the solar radiation column in the weather file /// @@ -591,7 +591,7 @@ private void OnDoWeather(object sender, EventArgs e) PreparingNewWeatherData.Invoke(this, new EventArgs()); // do basic sanity checks on weather data - sensibilityCheck(clock as Clock, this); + sensibilityCheck(clock as Clock, this); // check whether some variables need to be set with 'default' values (functions) if (double.IsNaN(MeanT)) From 7842be9a6ebd402905989dc6418455ce7c5d5fca Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Fri, 20 Sep 2024 00:14:25 +1200 Subject: [PATCH 25/39] Add variables to read in value for PET and evaporation (broadcasted, not replacing SoilWater's) --- Models/Climate/Weather.cs | 72 +++++++++++++++++++++++++ Models/Interfaces/IWeather.cs | 9 ++++ Tests/UnitTests/Weather/WeatherTests.cs | 4 +- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index bde9f45553..8d38abc253 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -141,6 +141,21 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private int airPressureIndex; + /// + /// The index of the potential ET column in the weather file + /// + private int potentialEvapotranspirationIndex; + + /// + /// The index of the potential evaporation column in the weather file + /// + private int potentialEvaporationIndex; + + /// + /// The index of the actual evaporation column in the weather file + /// + private int actualEvaporationIndex; + /// /// Default value for wind speed (m/s) /// @@ -336,6 +351,21 @@ public DateTime EndDate [JsonIgnore] public double AirPressure { get; set; } + /// Gets or sets the potential evapotranspiration + [Units("mm")] + [JsonIgnore] + public double GivenPotentialEvapotranspiration { get; set; } + + /// Gets or sets the potential soil evaporation + [Units("mm")] + [JsonIgnore] + public double GivenPotentialSoilEvaporation { get; set; } + + /// Gets or sets the actual soil evaporation + [Units("mm")] + [JsonIgnore] + public double GivenActualSoilEvaporation { get; set; } + /// Gets or sets the latitude (decimal degrees) [Units("degrees")] public double Latitude @@ -538,6 +568,9 @@ private void OnSimulationCommencing(object sender, EventArgs e) windIndex = 0; co2Index = 0; airPressureIndex = 0; + potentialEvapotranspirationIndex = 0; + potentialEvaporationIndex = 0; + actualEvaporationIndex = 0; if (reader != null) { @@ -837,6 +870,42 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) readMetData.AirPressure = double.NaN; } + if (potentialEvapotranspirationIndex >= 0) + { + readMetData.PotentialEvapotranspiration = Convert.ToDouble(readMetData.Raw[potentialEvapotranspirationIndex], CultureInfo.InvariantCulture); + } + else + { + if (reader.Constant("pet") != null) + readMetData.PotentialEvapotranspiration = reader.ConstantAsDouble("pet"); + else + readMetData.PotentialEvapotranspiration = double.NaN; + } + + if (potentialEvaporationIndex >= 0) + { + readMetData.PotentialSoilEvaporation = Convert.ToDouble(readMetData.Raw[potentialEvaporationIndex], CultureInfo.InvariantCulture); + } + else + { + if (reader.Constant("potevap") != null) + readMetData.PotentialSoilEvaporation = reader.ConstantAsDouble("potevap"); + else + readMetData.PotentialSoilEvaporation = double.NaN; + } + + if (actualEvaporationIndex >= 0) + { + readMetData.ActualSoilEvaporation = Convert.ToDouble(readMetData.Raw[actualEvaporationIndex], CultureInfo.InvariantCulture); + } + else + { + if (reader.Constant("actualevap") != null) + readMetData.ActualSoilEvaporation = reader.ConstantAsDouble("actualevap"); + else + readMetData.ActualSoilEvaporation = double.NaN; + } + return readMetData; } @@ -918,6 +987,9 @@ public bool OpenDataFile() windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); airPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "AirPressure"); + potentialEvapotranspirationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "PET"); + potentialEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "PotEvap"); + actualEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "ActaulEvap"); if (!string.IsNullOrEmpty(ConstantsFile)) { diff --git a/Models/Interfaces/IWeather.cs b/Models/Interfaces/IWeather.cs index c8c7bb1568..726bfac472 100644 --- a/Models/Interfaces/IWeather.cs +++ b/Models/Interfaces/IWeather.cs @@ -123,6 +123,15 @@ public class DailyMetDataFromFile : Model /// Gets or sets the atmospheric air pressure (hPa) public double AirPressure { get; set; } + /// Gets or sets the potential evapotranspiration (mm) + public double PotentialEvapotranspiration { get; set; } + + /// Gets or sets the potential evaporation (mm) + public double PotentialSoilEvaporation { get; set; } + + /// Gets or sets the actual soil evaporation (mm) + public double ActualSoilEvaporation { get; set; } + /// /// Raw data straight from the met file. This can be used to access /// non-standard variables which aren't auto-mapped to properties. diff --git a/Tests/UnitTests/Weather/WeatherTests.cs b/Tests/UnitTests/Weather/WeatherTests.cs index d5dc5bc382..427a377bcf 100644 --- a/Tests/UnitTests/Weather/WeatherTests.cs +++ b/Tests/UnitTests/Weather/WeatherTests.cs @@ -301,7 +301,7 @@ public void TestGetAnyDayMetData() new Models.Climate.SimpleWeather() { Name = "Weather", - FileName = weatherFilePath, + FullFileName = weatherFilePath, ExcelWorkSheetName = "" }, new Clock() @@ -315,7 +315,7 @@ public void TestGetAnyDayMetData() Models.Climate.SimpleWeather weather = baseSim.Children[0] as Models.Climate.SimpleWeather; Clock clock = baseSim.Children[1] as Clock; - weather.FileName = weatherFilePath2; + weather.FullFileName = weatherFilePath2; clock.StartDate = DateTime.ParseExact("1990-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture); clock.EndDate = DateTime.ParseExact("1990-01-02", "yyyy-MM-dd", CultureInfo.InvariantCulture); baseSim.Prepare(); From 415885503adc9e4bbf309d4a59739b1481778b6a Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Fri, 20 Sep 2024 00:25:04 +1200 Subject: [PATCH 26/39] update few variable names --- Models/Climate/SimpleWeather.cs | 16 ++++++++-------- Models/Climate/Weather.cs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index 259c05eed6..c5535044f2 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -871,15 +871,15 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) { - const double baseTemperature = 288.15; // (K) - const double basePressure = 101325.0; // (Pa) - const double lapseRate = 0.0065; // (K/m) - const double gravitationalAcceleration = 9.80665; // (m/s) - const double molarMassOfAir = 0.0289644; // (kg/mol) - const double universalGasConstant = 8.3144598; // (J/mol/K) + const double standardAtmosphericTemperature = 288.15; // (K) + const double standardAtmosphericPressure = 101325.0; // (Pa) + const double StandardTemperatureLapseRate = 0.0065; // (K/m) + const double standardGravitationalAcceleration = 9.80665; // (m/s) + const double standardAtmosphericMolarMass = 0.0289644; // (kg/mol) + const double universalGasConstant = 8.3144598; // (J/mol/K) double result; - result = basePressure * Math.Pow(1.0 - localAltitude * lapseRate / baseTemperature, - gravitationalAcceleration * molarMassOfAir / (universalGasConstant * lapseRate)); + result = standardAtmosphericPressure * Math.Pow(1.0 - localAltitude * StandardTemperatureLapseRate / standardAtmosphericTemperature, + standardGravitationalAcceleration * standardAtmosphericMolarMass / (universalGasConstant * StandardTemperatureLapseRate)); return result / 100.0; // to hPa } diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 8d38abc253..1cfae58dcb 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -1131,15 +1131,15 @@ private double calculateVapourPressureDefict(double minTemp, double maxTemp, dou /// The air pressure (hPa) private double calculateAirPressure(double localAltitude) { - const double baseTemperature = 288.15; // (K) - const double basePressure = 101325.0; // (Pa) - const double lapseRate = 0.0065; // (K/m) - const double gravitationalAcceleration = 9.80665; // (m/s) - const double molarMassOfAir = 0.0289644; // (kg/mol) - const double universalGasConstant = 8.3144598; // (J/mol/K) + const double standardAtmosphericTemperature = 288.15; // (K) + const double standardAtmosphericPressure = 101325.0; // (Pa) + const double StandardTemperatureLapseRate = 0.0065; // (K/m) + const double standardGravitationalAcceleration = 9.80665; // (m/s) + const double standardAtmosphericMolarMass = 0.0289644; // (kg/mol) + const double universalGasConstant = 8.3144598; // (J/mol/K) double result; - result = basePressure * Math.Pow(1.0 - localAltitude * lapseRate / baseTemperature, - gravitationalAcceleration * molarMassOfAir / (universalGasConstant * lapseRate)); + result = standardAtmosphericPressure * Math.Pow(1.0 - localAltitude * StandardTemperatureLapseRate / standardAtmosphericTemperature, + standardGravitationalAcceleration * standardAtmosphericMolarMass / (universalGasConstant * StandardTemperatureLapseRate)); return result / 100.0; // to hPa } From 17bc66cd1697544c9c8b2d2a725a41b6534cf336 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Fri, 20 Sep 2024 00:32:46 +1200 Subject: [PATCH 27/39] Test making altitude an input to Weather (used to compute AirPressure) --- Models/Climate/Weather.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 1cfae58dcb..9d08b0ae2c 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -27,6 +27,12 @@ public class Weather : Model, IWeather, IReferenceExternalFiles [Link] private IClock clock = null; + /// + /// A link to the zone model + /// + [Link] + private Zone zone = null; + /// /// A link to the summary (log file) /// @@ -634,7 +640,7 @@ private void OnDoWeather(object sender, EventArgs e) if (double.IsNaN(VP)) VP = Math.Max(0.0, MetUtilities.svp(MinT)); if (double.IsNaN(AirPressure)) - AirPressure = calculateAirPressure(27.08889); // returns default 1010; + AirPressure = calculateAirPressure(zone.Altitude); // compute additional outputs derived from weather data Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); From 2bcfb56bf0414bf5815af185767753cbacade4ff Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Fri, 20 Sep 2024 01:46:02 +1200 Subject: [PATCH 28/39] Adding a test for the existence of 'zone' (to supply Altitude), not all sims have one --- Models/Climate/Weather.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 9d08b0ae2c..552a19b53f 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -640,7 +640,12 @@ private void OnDoWeather(object sender, EventArgs e) if (double.IsNaN(VP)) VP = Math.Max(0.0, MetUtilities.svp(MinT)); if (double.IsNaN(AirPressure)) - AirPressure = calculateAirPressure(zone.Altitude); + { + if (zone != null) + AirPressure = calculateAirPressure(zone.Altitude); + else + AirPressure = calculateAirPressure(27.08889); + } // compute additional outputs derived from weather data Qmax = MetUtilities.QMax(clock.Today.DayOfYear + 1, Latitude, MetUtilities.Taz, MetUtilities.Alpha, VP); From 1f38bca2989c7433020b87f9b41fde3e65a49fe7 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Fri, 20 Sep 2024 02:22:40 +1200 Subject: [PATCH 29/39] Make Link to 'zone' optional as not all simulation have one... --- Models/Climate/Weather.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 552a19b53f..a339a4b621 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -30,7 +30,7 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// /// A link to the zone model /// - [Link] + [Link(IsOptional = true)] private Zone zone = null; /// From 8d804af2ee849acb480e509a8ad171b15da7984a Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Fri, 20 Sep 2024 06:47:33 +1200 Subject: [PATCH 30/39] Add constant to explicitly test AMP, Exposing evaporation variables --- Models/Climate/SimpleWeather.cs | 17 +++++++++++------ Models/Climate/Weather.cs | 18 +++++++++++++----- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index c5535044f2..d14f75df94 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -126,6 +126,11 @@ public class SimpleWeather : Model, IWeather, IReferenceExternalFiles /// private const double defaultTwilight = 6.0; + /// + /// Maximum value expected for mean long-term temperature amplitude (oC) + /// + private const double maximumAMP = 25.0; + /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -397,9 +402,9 @@ public void RemovePathsFromReferencedFileNames() /// public IEnumerable Validate() { - if (Amp > 20) + if (Amp > maximumAMP) { - yield return $"The value of Weather.AMP ({Amp}) is > 20 oC. Please check the value."; + yield return $"The value of Weather.AMP ({Amp}) is > {maximumAMP} oC. Please check the value."; } } @@ -554,7 +559,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// /// For each variable handled by Weather, this method will firstly check whether there is a column /// with daily data in the met file (i.e. there is an index equal or greater than zero), if not, it - /// will check whether a constant value was given (a single value in the met file, like latitude or + /// will check whether a constant value was given (a single value in the met file, like Latitude or /// TAV). If that fails, either a default value is supplied or 'null' is returned (which results in /// an exception being thrown later on) /// @@ -896,8 +901,8 @@ public double GetValue(string columnName) /// Checks the weather data to ensure values are valid/sensible /// /// This will send an error message if: - /// - MinT is less than MaxT - /// - Radn is greater than 0.0 or greater than 40.0 + /// - MinT is greater than MaxT + /// - Radn is less than 0.0 or greater than 40.0 /// - Rain is less than 0.0 /// - VP is less or equal to 0.0 /// Also checks that every day has weather @@ -920,7 +925,7 @@ private void sensibilityCheck(Clock clock, SimpleWeather weatherToday) } if (weatherToday.Rain < 0) { - throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative ranfaill (" + weatherToday.Radn + ")"); + throw new Exception("Error: Weather on " + clock.Today.ToString() + " has negative rainfall (" + weatherToday.Radn + ")"); } if (!double.IsNaN(weatherToday.VP) && weatherToday.VP <= 0) { diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index a339a4b621..04bf533740 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -177,6 +177,11 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private const double defaultTwilight = 6.0; + /// + /// Maximum value expected for mean long-term temperature amplitude (oC) + /// + private const double maximumAMP = 25.0; + /// /// Stores the optional constants file name. This should only be accessed via /// , which handles conversion between @@ -549,9 +554,9 @@ public void RemovePathsFromReferencedFileNames() /// public IEnumerable Validate() { - if (Amp > 20) + if (Amp > maximumAMP) { - yield return $"The value of Weather.AMP ({Amp}) is > 20 oC. Please check the value."; + yield return $"The value of Weather.AMP ({Amp}) is > {maximumAMP} oC. Please check the value."; } } @@ -624,6 +629,9 @@ private void OnDoWeather(object sender, EventArgs e) Wind = TodaysMetData.Wind; CO2 = TodaysMetData.CO2; AirPressure = TodaysMetData.AirPressure; + GivenPotentialEvapotranspiration = TodaysMetData.PotentialEvapotranspiration; + GivenPotentialSoilEvaporation = TodaysMetData.PotentialSoilEvaporation; + GivenActualSoilEvaporation = TodaysMetData.ActualSoilEvaporation; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -729,7 +737,7 @@ public DailyMetDataFromFile GetMetData(DateTime date) /// /// For each variable handled by Weather, this method will firstly check whether there is a column /// with daily data in the met file (i.e. there is an index equal or greater than zero), if not, it - /// will check whether a constant value was given (a single value in the met file, like latitude or + /// will check whether a constant value was given (a single value in the met file, like Latitude or /// TAV). If that fails, either a default value is supplied or 'null' is returned (which results in /// an exception being thrown later on) /// @@ -1307,8 +1315,8 @@ private void processMonthlyTAVAMP(out double tav, out double amp) /// Checks the weather data to ensure values are valid/sensible /// /// This will send an error message if: - /// - MinT is less than MaxT - /// - Radn is greater than 0.0 or greater than 40.0 + /// - MinT is greater than MaxT + /// - Radn is less than 0.0 or greater than 40.0 /// - Rain is less than 0.0 /// - VP is less or equal to 0.0 /// Also checks that every day has weather From a23640d92245bcdfc0245469d4cc94adb4e56c05 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Sat, 21 Sep 2024 18:12:21 +1200 Subject: [PATCH 31/39] Removing the added PET variables (need more thinking on this) --- Models/Climate/Weather.cs | 75 --------------------------------------- 1 file changed, 75 deletions(-) diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 04bf533740..26fd7180f3 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -147,21 +147,6 @@ public class Weather : Model, IWeather, IReferenceExternalFiles /// private int airPressureIndex; - /// - /// The index of the potential ET column in the weather file - /// - private int potentialEvapotranspirationIndex; - - /// - /// The index of the potential evaporation column in the weather file - /// - private int potentialEvaporationIndex; - - /// - /// The index of the actual evaporation column in the weather file - /// - private int actualEvaporationIndex; - /// /// Default value for wind speed (m/s) /// @@ -362,21 +347,6 @@ public DateTime EndDate [JsonIgnore] public double AirPressure { get; set; } - /// Gets or sets the potential evapotranspiration - [Units("mm")] - [JsonIgnore] - public double GivenPotentialEvapotranspiration { get; set; } - - /// Gets or sets the potential soil evaporation - [Units("mm")] - [JsonIgnore] - public double GivenPotentialSoilEvaporation { get; set; } - - /// Gets or sets the actual soil evaporation - [Units("mm")] - [JsonIgnore] - public double GivenActualSoilEvaporation { get; set; } - /// Gets or sets the latitude (decimal degrees) [Units("degrees")] public double Latitude @@ -579,9 +549,6 @@ private void OnSimulationCommencing(object sender, EventArgs e) windIndex = 0; co2Index = 0; airPressureIndex = 0; - potentialEvapotranspirationIndex = 0; - potentialEvaporationIndex = 0; - actualEvaporationIndex = 0; if (reader != null) { @@ -629,9 +596,6 @@ private void OnDoWeather(object sender, EventArgs e) Wind = TodaysMetData.Wind; CO2 = TodaysMetData.CO2; AirPressure = TodaysMetData.AirPressure; - GivenPotentialEvapotranspiration = TodaysMetData.PotentialEvapotranspiration; - GivenPotentialSoilEvaporation = TodaysMetData.PotentialSoilEvaporation; - GivenActualSoilEvaporation = TodaysMetData.ActualSoilEvaporation; // invoke event that allows other models to modify weather data if (PreparingNewWeatherData != null) @@ -889,42 +853,6 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) readMetData.AirPressure = double.NaN; } - if (potentialEvapotranspirationIndex >= 0) - { - readMetData.PotentialEvapotranspiration = Convert.ToDouble(readMetData.Raw[potentialEvapotranspirationIndex], CultureInfo.InvariantCulture); - } - else - { - if (reader.Constant("pet") != null) - readMetData.PotentialEvapotranspiration = reader.ConstantAsDouble("pet"); - else - readMetData.PotentialEvapotranspiration = double.NaN; - } - - if (potentialEvaporationIndex >= 0) - { - readMetData.PotentialSoilEvaporation = Convert.ToDouble(readMetData.Raw[potentialEvaporationIndex], CultureInfo.InvariantCulture); - } - else - { - if (reader.Constant("potevap") != null) - readMetData.PotentialSoilEvaporation = reader.ConstantAsDouble("potevap"); - else - readMetData.PotentialSoilEvaporation = double.NaN; - } - - if (actualEvaporationIndex >= 0) - { - readMetData.ActualSoilEvaporation = Convert.ToDouble(readMetData.Raw[actualEvaporationIndex], CultureInfo.InvariantCulture); - } - else - { - if (reader.Constant("actualevap") != null) - readMetData.ActualSoilEvaporation = reader.ConstantAsDouble("actualevap"); - else - readMetData.ActualSoilEvaporation = double.NaN; - } - return readMetData; } @@ -1006,9 +934,6 @@ public bool OpenDataFile() windIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "Wind"); co2Index = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "CO2"); airPressureIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "AirPressure"); - potentialEvapotranspirationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "PET"); - potentialEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "PotEvap"); - actualEvaporationIndex = StringUtilities.IndexOfCaseInsensitive(reader.Headings, "ActaulEvap"); if (!string.IsNullOrEmpty(ConstantsFile)) { From 06ffd95129d020dc0635faf13d49529a1b25f058 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Sat, 21 Sep 2024 19:26:06 +1200 Subject: [PATCH 32/39] Minor update and code suffle to make ControlledEnvironment more comparable to Weather --- Models/Climate/ControlledEnvironment.cs | 279 +++++++++++------------- Models/Climate/SimpleWeather.cs | 4 +- Models/Climate/Weather.cs | 4 +- 3 files changed, 126 insertions(+), 161 deletions(-) diff --git a/Models/Climate/ControlledEnvironment.cs b/Models/Climate/ControlledEnvironment.cs index f03e96a3d6..6a34513790 100644 --- a/Models/Climate/ControlledEnvironment.cs +++ b/Models/Climate/ControlledEnvironment.cs @@ -6,9 +6,8 @@ namespace Models.Climate { - /// - /// Reads in controlled environment weather data and makes it available to models. + /// Reads in controlled environment weather data and makes it available to models /// [Serializable] [ViewName("UserInterface.Views.PropertyView")] @@ -17,214 +16,146 @@ namespace Models.Climate public class ControlledEnvironment : Model, IWeather { /// - /// A link to the clock model. + /// A link to the clock model /// [Link] private IClock clock = null; - /// - /// Gets the start date of the weather file - /// - public DateTime StartDate { get { return clock.StartDate; } } /// - /// Gets the end date of the weather file + /// A link to the summary (log file) /// - public DateTime EndDate { get { return clock.EndDate; } } + [Link] + private ISummary summary = null; /// - /// This event will be invoked immediately before models get their weather data. - /// models and scripts an opportunity to change the weather data before other models - /// reads it. + /// Event that will be invoked immediately before the daily weather data is updated /// + /// + /// This provides models and scripts an opportunity to change the weather data before + /// other models access them + /// public event EventHandler PreparingNewWeatherData; /// - /// Gets or sets the maximum temperature (oC) + /// Gets the weather file name. Should be relative file path where possible /// - [Description("Maximum Air Temperature")] - [Units("°C")] - public double MaxT { get; set; } + public string FileName { get { return "Controlled Environment does not read from a file"; } } - /// - /// Gets or sets the minimum temperature (oC) - /// - [Description("Minimum Air Temperature")] - [Units("°C")] + /// Gets the start date of the weather file + public DateTime StartDate { get { return clock.StartDate; } } + + /// Gets the end date of the weather file + public DateTime EndDate { get { return clock.EndDate; } } + + /// Gets or sets the daily minimum air temperature (oC) + [Description("Minimum air temperature")] + [Units("oC")] public double MinT { get; set; } - /// - /// Daily Mean temperature (oC) - /// - [Units("°C")] - [JsonIgnore] - public double MeanT { get { return (MaxT + MinT) / 2; } } + /// Gets or sets the daily maximum air temperature (oC) + [Description("Maximum air temperature")] + [Units("oC")] + public double MaxT { get; set; } - /// - /// Daily mean VPD (hPa) - /// - [Units("hPa")] + /// Gets the daily mean air temperature (oC) + [Units("oC")] [JsonIgnore] - public double VPD - { - get - { - const double SVPfrac = 0.66; - double VPDmint = MetUtilities.svp((float)MinT) - VP; - VPDmint = Math.Max(VPDmint, 0.0); + public double MeanT { get { return (MaxT + MinT) / 2.0; } } - double VPDmaxt = MetUtilities.svp((float)MaxT) - VP; - VPDmaxt = Math.Max(VPDmaxt, 0.0); + /// Gets or sets the solar radiation (MJ/m2) + [Description("Solar radiation")] + [Units("MJ/m2")] + public double Radn { get; set; } - return SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; - } - } + /// Gets or sets the day length, period with light (h) + [Description("Day length")] + [Units("h")] + public double DayLength { get; set; } - /// - /// Gets or sets the rainfall (mm) - /// + /// Gets or sets the diffuse radiation fraction (0-1) + [Description("Diffuse radiation fraction")] + [Units("0-1")] + public double DiffuseFraction { get; set; } + + /// Gets or sets the rainfall amount (mm) [Description("Rainfall")] [Units("mm")] public double Rain { get; set; } - /// - /// Gets or sets the solar radiation. MJ/m2/day - /// - [Description("Solar Radiation")] - [Units("MJ/m2/d")] - public double Radn { get; set; } - - /// - /// Gets or sets the Pan Evaporation (mm) (Class A pan) - /// - [Description("Pan Evaporation")] + /// Gets or sets the class A pan evaporation (mm) + [Description("Class A pan evaporation")] [Units("mm")] public double PanEvap { get; set; } - /// - /// Gets or sets the vapor pressure (hPa) - /// - [Description("Vapour Pressure")] + /// Gets or sets the air vapour pressure (hPa) + [Description("Air vapour pressure")] [Units("hPa")] public double VP { get; set; } - /// - /// Gets or sets the wind value found in weather file or zero if not specified. (code says 3.0 not zero) - /// - [Description("Wind Speed")] + /// Gets the daily mean vapour pressure deficit (hPa) + [Units("hPa")] + [JsonIgnore] + public double VPD { get { return calculateVapourPressureDefict(MinT, MaxT, VP); } } + + /// Gets or sets the average wind speed (m/s) + [Description("Wind speed")] [Units("m/s")] public double Wind { get; set; } - /// - /// Gets or sets the CO2 level. If not specified in the weather file the default is 350. - /// - [Description("CO2 concentration of the air")] + /// Gets or sets the CO2 level in the atmosphere (ppm) + [Description("Atmospheric CO2 concentration")] [Units("ppm")] public double CO2 { get; set; } - /// - /// Gets or sets the atmospheric air pressure. If not specified in the weather file the default is 1010 hPa. - /// - [Description("Air Pressure")] + /// Gets or sets the mean atmospheric air pressure + [Description("Atmospheric air pressure")] [Units("hPa")] - public double AirPressure { get; set; } - - /// - /// Gets or sets the diffuse radiation fraction. If not specified in the weather file the default is 1. - /// - [Description("Diffuse Fraction")] - [Units("0-1")] - public double DiffuseFraction { get; set; } + public double AirPressure { get; set; } = 1010; - /// - /// Gets the latitude - /// + /// Gets or sets the latitude (decimal degrees) [Description("Latitude")] - [Units("°")] + [Units("degrees")] public double Latitude { get; set; } - /// Gets the longitude + /// Gets or sets the longitude (decimal degrees) [Description("Longitude")] - [Units("°")] + [Units("degrees")] public double Longitude { get; set; } - /// - /// Gets the average temperature - /// - public double Tav { get { return (this.MinT + this.MaxT) / 2; } } + /// Gets the long-term average air temperature (oC) + [Units("oC")] + public double Tav { get { return (MinT + MaxT) / 2.0; } } - /// - /// Gets the temperature amplitude. - /// + /// Gets the long-term average temperature amplitude (oC) + [Units("oC")] public double Amp { get { return 0; } } - /// - /// Gets the temperature amplitude. - /// - public string FileName { get { return "Controlled Environment does not read from a file"; } } - - /// - /// Gets the duration of the day in hours. - /// - [Description("Day Length")] - [Units("h")] - public double DayLength { get; set; } - - /// - /// Calculate daylength using a given twilight angle - /// - /// - /// - public double CalculateDayLength(double twilight) - { - return DayLength; - } - - - /// - /// Gets the duration of the day in hours. - /// + /// Gets or sets time of the day for sunrise (h) [Description("The hour of the day for sunrise")] + [Units("h")] public double SunRise { get; set; } - /// - /// Calculate daylength using a given twilight angle - /// - public double CalculateSunRise() - { - return SunRise; - } - - /// - /// Gets the duration of the day in hours. - /// + /// Gets or sets time of the day for sunset (h) [Description("The hour of the day for sunset")] + [Units("h")] public double SunSet { get; set; } - /// - /// Calculate daylength using a given twilight angle - /// - public double CalculateSunSet() - { - return SunSet; - } - /// - /// Constructor - /// - public ControlledEnvironment() - { - AirPressure = 1010; - } + /// Met Data from yesterday + [JsonIgnore] + public DailyMetDataFromFile YesterdaysMetData { get; set; } - /// - /// An event handler for the daily DoWeather event. - /// + /// Met Data for tomorrow + [JsonIgnore] + public DailyMetDataFromFile TomorrowsMetData { get; set; } + + /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event [EventSubscribe("DoWeather")] private void OnDoWeather(object sender, EventArgs e) { - if (this.PreparingNewWeatherData != null) - this.PreparingNewWeatherData.Invoke(this, new EventArgs()); + if (PreparingNewWeatherData != null) + PreparingNewWeatherData.Invoke(this, new EventArgs()); YesterdaysMetData = new DailyMetDataFromFile(); YesterdaysMetData.Radn = Radn; YesterdaysMetData.Rain = Rain; @@ -235,12 +166,46 @@ private void OnDoWeather(object sender, EventArgs e) TomorrowsMetData = YesterdaysMetData; } - /// Met Data from yesterday - [JsonIgnore] - public DailyMetDataFromFile YesterdaysMetData { get; set; } + /// Computes the duration of the day, with light (hours) + /// The angle to measure time for twilight (degrees) + /// The number of hours of daylight + public double CalculateDayLength(double Twilight) + { + return DayLength; + } - /// Met Data from yesterday - [JsonIgnore] - public DailyMetDataFromFile TomorrowsMetData { get; set; } + /// Computes the time of sun rise (h) + /// Sun set time + public double CalculateSunRise() + { + return SunRise; + } + + /// Computes the time of sun set (h) + /// Sun set time + public double CalculateSunSet() + { + return SunSet; + } + + /// Computes today's atmospheric vapour pressure deficit (hPa) + /// Today's minimum temperature (oC) + /// Today's maximum temperature (oC) + /// Today's vapour pressure (hPa) + /// The vapour pressure deficit (hPa) + private double calculateVapourPressureDefict(double minTemp, double maxTemp, double vapourPressure) + { + const double SVPfrac = 0.66; + + double result; + double VPDmint = MetUtilities.svp(minTemp) - vapourPressure; + VPDmint = Math.Max(VPDmint, 0.0); + + double VPDmaxt = MetUtilities.svp(MaxT) - vapourPressure; + VPDmaxt = Math.Max(VPDmaxt, 0.0); + + result = SVPfrac * VPDmaxt + (1 - SVPfrac) * VPDmint; + return result; + } } -} \ No newline at end of file +} diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index d14f75df94..addaf95785 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -286,7 +286,7 @@ public DateTime EndDate [JsonIgnore] public double RainfallHours { get; set; } - /// Gets or sets the air vapour pressure (hPa)/// + /// Gets or sets the air vapour pressure (hPa) [Units("hPa")] [JsonIgnore] public double VP { get; set; } @@ -473,7 +473,7 @@ private void OnSimulationCompleted(object sender, EventArgs e) reader = null; } - /// Performs the tasks to update the weather data + /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event [EventSubscribe("DoWeather")] diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 26fd7180f3..5fd21947e5 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -322,7 +322,7 @@ public DateTime EndDate [JsonIgnore] public double RainfallHours { get; set; } - /// Gets or sets the air vapour pressure (hPa)/// + /// Gets or sets the air vapour pressure (hPa) [Units("hPa")] [JsonIgnore] public double VP { get; set; } @@ -573,7 +573,7 @@ private void OnSimulationCompleted(object sender, EventArgs e) reader = null; } - /// Performs the tasks to update the weather data + /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event [EventSubscribe("DoWeather")] From 76bc9c53469344c580f7ac0834c68422149c4d1a Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Sat, 21 Sep 2024 20:18:08 +1200 Subject: [PATCH 33/39] remove link to un-used Summary --- Models/Climate/ControlledEnvironment.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Models/Climate/ControlledEnvironment.cs b/Models/Climate/ControlledEnvironment.cs index 6a34513790..339c58d2c4 100644 --- a/Models/Climate/ControlledEnvironment.cs +++ b/Models/Climate/ControlledEnvironment.cs @@ -21,12 +21,6 @@ public class ControlledEnvironment : Model, IWeather [Link] private IClock clock = null; - /// - /// A link to the summary (log file) - /// - [Link] - private ISummary summary = null; - /// /// Event that will be invoked immediately before the daily weather data is updated /// From 392c9cac2ed13ae7f28d8f4ae77b6a6ad75d243e Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Sun, 22 Sep 2024 03:49:06 +1200 Subject: [PATCH 34/39] Minor shuffle of code, cosmetic --- Models/Climate/ControlledEnvironment.cs | 2 +- Models/Climate/SimpleWeather.cs | 53 +++++----- Models/Climate/Weather.cs | 123 ++++++++++++------------ 3 files changed, 90 insertions(+), 88 deletions(-) diff --git a/Models/Climate/ControlledEnvironment.cs b/Models/Climate/ControlledEnvironment.cs index 339c58d2c4..89028a2842 100644 --- a/Models/Climate/ControlledEnvironment.cs +++ b/Models/Climate/ControlledEnvironment.cs @@ -87,8 +87,8 @@ public class ControlledEnvironment : Model, IWeather public double VP { get; set; } /// Gets the daily mean vapour pressure deficit (hPa) - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double VPD { get { return calculateVapourPressureDefict(MinT, MaxT, VP); } } /// Gets or sets the average wind speed (m/s) diff --git a/Models/Climate/SimpleWeather.cs b/Models/Climate/SimpleWeather.cs index addaf95785..c737ac59c3 100644 --- a/Models/Climate/SimpleWeather.cs +++ b/Models/Climate/SimpleWeather.cs @@ -242,73 +242,73 @@ public DateTime EndDate public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("oC")] [JsonIgnore] + [Units("oC")] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("oC")] [JsonIgnore] + [Units("oC")] public double MeanT { get; set; } /// Gets or sets the solar radiation (MJ/m2) - [Units("MJ/m2")] [JsonIgnore] + [Units("MJ/m2")] public double Radn { get; set; } /// Gets or sets the maximum clear sky radiation (MJ/m2) - [Units("MJ/m2")] [JsonIgnore] + [Units("MJ/m2")] public double Qmax { get; set; } /// Gets or sets the day length, period with light (h) - [Units("h")] [JsonIgnore] + [Units("h")] public double DayLength { get; set; } /// Gets or sets the diffuse radiation fraction (0-1) - [Units("0-1")] [JsonIgnore] + [Units("0-1")] public double DiffuseFraction { get; set; } /// Gets or sets the rainfall amount (mm) - [Units("mm")] [JsonIgnore] + [Units("mm")] public double Rain { get; set; } /// Gets or sets the class A pan evaporation (mm) - [Units("mm")] [JsonIgnore] + [Units("mm")] public double PanEvap { get; set; } /// Gets or sets the number duration of rainfall within a day (h) - [Units("h")] [JsonIgnore] + [Units("h")] public double RainfallHours { get; set; } /// Gets or sets the air vapour pressure (hPa) - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double VP { get; set; } /// Gets or sets the daily mean vapour pressure deficit (hPa) - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double VPD { get; set; } /// Gets or sets the average wind speed (m/s) - [Units("m/s")] [JsonIgnore] + [Units("m/s")] public double Wind { get; set; } /// Gets or sets the CO2 level in the atmosphere (ppm) - [Units("ppm")] [JsonIgnore] + [Units("ppm")] public double CO2 { get; set; } /// Gets or sets the mean atmospheric air pressure - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double AirPressure { get; set; } /// Gets or sets the latitude (decimal degrees) @@ -462,17 +462,6 @@ private void OnStartOfSimulation(object sender, EventArgs e) TomorrowsMetData = GetMetData(clock.Today.AddDays(1)); } - /// Overrides the base class method to allow for clean up task - /// The sender of the event - /// The arguments of the event - [EventSubscribe("Completed")] - private void OnSimulationCompleted(object sender, EventArgs e) - { - if (reader != null) - reader.Close(); - reader = null; - } - /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event @@ -529,7 +518,19 @@ private void OnDoWeather(object sender, EventArgs e) VPD = calculateVapourPressureDefict(MinT, MaxT, VP); } - /// Reads the weather data for one day from file + /// Overrides the base class method to allow for clean up task + /// The sender of the event + /// The arguments of the event + [EventSubscribe("Completed")] + private void OnSimulationCompleted(object sender, EventArgs e) + { + if (reader != null) + reader.Close(); + reader = null; + } + + /// Reads the weather data for a given date from file + /// Will throw an exception if date is not found /// The date to read met data public DailyMetDataFromFile GetMetData(DateTime date) { diff --git a/Models/Climate/Weather.cs b/Models/Climate/Weather.cs index 5fd21947e5..22b6662869 100644 --- a/Models/Climate/Weather.cs +++ b/Models/Climate/Weather.cs @@ -278,73 +278,73 @@ public DateTime EndDate public double MinT { get; set; } /// Gets or sets the daily maximum air temperature (oC) - [Units("oC")] [JsonIgnore] + [Units("oC")] public double MaxT { get; set; } /// Gets or sets the daily mean air temperature (oC) - [Units("oC")] [JsonIgnore] + [Units("oC")] public double MeanT { get; set; } /// Gets or sets the solar radiation (MJ/m2) - [Units("MJ/m2")] [JsonIgnore] + [Units("MJ/m2")] public double Radn { get; set; } /// Gets or sets the maximum clear sky radiation (MJ/m2) - [Units("MJ/m2")] [JsonIgnore] + [Units("MJ/m2")] public double Qmax { get; set; } /// Gets or sets the day length, period with light (h) - [Units("h")] [JsonIgnore] + [Units("h")] public double DayLength { get; set; } /// Gets or sets the diffuse radiation fraction (0-1) - [Units("0-1")] [JsonIgnore] + [Units("0-1")] public double DiffuseFraction { get; set; } /// Gets or sets the rainfall amount (mm) - [Units("mm")] [JsonIgnore] + [Units("mm")] public double Rain { get; set; } /// Gets or sets the class A pan evaporation (mm) - [Units("mm")] [JsonIgnore] + [Units("mm")] public double PanEvap { get; set; } /// Gets or sets the number duration of rainfall within a day (h) - [Units("h")] [JsonIgnore] + [Units("h")] public double RainfallHours { get; set; } /// Gets or sets the air vapour pressure (hPa) - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double VP { get; set; } /// Gets or sets the daily mean vapour pressure deficit (hPa) - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double VPD { get; set; } /// Gets or sets the average wind speed (m/s) - [Units("m/s")] [JsonIgnore] + [Units("m/s")] public double Wind { get; set; } /// Gets or sets the CO2 level in the atmosphere (ppm) - [Units("ppm")] [JsonIgnore] + [Units("ppm")] public double CO2 { get; set; } /// Gets or sets the mean atmospheric air pressure - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double AirPressure { get; set; } /// Gets or sets the latitude (decimal degrees) @@ -417,8 +417,8 @@ public double Amp } /// Gets the day for the winter solstice (day) - [Units("day")] [JsonIgnore] + [Units("day")] public int WinterSolsticeDOY { get @@ -441,8 +441,8 @@ public int WinterSolsticeDOY } /// Gets or sets the number of days since the winter solstice - [Units("d")] [JsonIgnore] + [Units("d")] public int DaysSinceWinterSolstice { get; set; } /// Gets or sets the first date of summer (dd-mmm) @@ -562,15 +562,42 @@ private void OnSimulationCommencing(object sender, EventArgs e) summary.WriteMessage(this, message, MessageType.Warning); } - /// Overrides the base class method to allow for clean up task + /// Performs tasks at the start of the day /// The sender of the event /// The arguments of the event - [EventSubscribe("Completed")] - private void OnSimulationCompleted(object sender, EventArgs e) + [EventSubscribe("StartOfDay")] + private void OnStartOfDay(object sender, EventArgs e) { - if (reader != null) - reader.Close(); - reader = null; + if (StartOfSummer != null && DateUtilities.DayMonthIsEqual(FirstDateOfSummer, clock.Today)) + StartOfSummer.Invoke(this, e); + + if (StartOfAutumn != null && DateUtilities.DayMonthIsEqual(FirstDateOfAutumn, clock.Today)) + StartOfAutumn.Invoke(this, e); + + if (StartOfWinter != null && DateUtilities.DayMonthIsEqual(FirstDateOfWinter, clock.Today)) + StartOfWinter.Invoke(this, e); + + if (StartOfSpring != null && DateUtilities.DayMonthIsEqual(FirstDateOfSpring, clock.Today)) + StartOfSpring.Invoke(this, e); + } + + /// Performs the tasks for the end of the day + /// The sender of the event + /// The arguments of the event + [EventSubscribe("EndOfDay")] + private void OnEndOfDay(object sender, EventArgs e) + { + if (EndOfSummer != null && DateUtilities.DayMonthIsEqual(FirstDateOfAutumn, clock.Today.AddDays(1))) + EndOfSummer.Invoke(this, e); + + if (EndOfAutumn != null && DateUtilities.DayMonthIsEqual(FirstDateOfWinter, clock.Today.AddDays(1))) + EndOfAutumn.Invoke(this, e); + + if (EndOfWinter != null && DateUtilities.DayMonthIsEqual(FirstDateOfSpring, clock.Today.AddDays(1))) + EndOfWinter.Invoke(this, e); + + if (EndOfSpring != null && DateUtilities.DayMonthIsEqual(FirstDateOfSummer, clock.Today.AddDays(1))) + EndOfSpring.Invoke(this, e); } /// Performs the tasks to update the weather data @@ -625,6 +652,17 @@ private void OnDoWeather(object sender, EventArgs e) DaysSinceWinterSolstice = calculateDaysSinceSolstice(DaysSinceWinterSolstice); } + /// Overrides the base class method to allow for clean up task + /// The sender of the event + /// The arguments of the event + [EventSubscribe("Completed")] + private void OnSimulationCompleted(object sender, EventArgs e) + { + if (reader != null) + reader.Close(); + reader = null; + } + /// Get the DataTable view of the weather data /// The DataTable public DataTable GetAllData() @@ -647,7 +685,8 @@ public DataTable GetAllData() return null; } - /// Reads the weather data for one day from file + /// Reads the weather data for a given date from file + /// Will throw an exception if date is not found /// The date to read met data public DailyMetDataFromFile GetMetData(DateTime date) { @@ -856,44 +895,6 @@ private DailyMetDataFromFile checkDailyMetData(DailyMetDataFromFile readMetData) return readMetData; } - /// Performs tasks at the start of the day - /// The sender of the event - /// The arguments of the event - [EventSubscribe("StartOfDay")] - private void OnStartOfDay(object sender, EventArgs e) - { - if (StartOfSummer != null && DateUtilities.DayMonthIsEqual(FirstDateOfSummer, clock.Today)) - StartOfSummer.Invoke(this, e); - - if (StartOfAutumn != null && DateUtilities.DayMonthIsEqual(FirstDateOfAutumn, clock.Today)) - StartOfAutumn.Invoke(this, e); - - if (StartOfWinter != null && DateUtilities.DayMonthIsEqual(FirstDateOfWinter, clock.Today)) - StartOfWinter.Invoke(this, e); - - if (StartOfSpring != null && DateUtilities.DayMonthIsEqual(FirstDateOfSpring, clock.Today)) - StartOfSpring.Invoke(this, e); - } - - /// Performs the tasks for the end of the day - /// The sender of the event - /// The arguments of the event - [EventSubscribe("EndOfDay")] - private void OnEndOfDay(object sender, EventArgs e) - { - if (EndOfSummer != null && DateUtilities.DayMonthIsEqual(FirstDateOfAutumn, clock.Today.AddDays(1))) - EndOfSummer.Invoke(this, e); - - if (EndOfAutumn != null && DateUtilities.DayMonthIsEqual(FirstDateOfWinter, clock.Today.AddDays(1))) - EndOfAutumn.Invoke(this, e); - - if (EndOfWinter != null && DateUtilities.DayMonthIsEqual(FirstDateOfSpring, clock.Today.AddDays(1))) - EndOfWinter.Invoke(this, e); - - if (EndOfSpring != null && DateUtilities.DayMonthIsEqual(FirstDateOfSummer, clock.Today.AddDays(1))) - EndOfSpring.Invoke(this, e); - } - /// Opens the weather data file /// True if the file was successfully opened public bool OpenDataFile() From 6a8f51c5a14cc7bb58f1fbaf5e5e19e71076578a Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Sun, 22 Sep 2024 03:51:13 +1200 Subject: [PATCH 35/39] Updates to WeatherSample summary tags and some code shuffling to align with changes is Weather --- Models/Climate/WeatherSampler.cs | 280 +++++++++--------- .../UnitTests/Weather/WeatherSamplerTests.cs | 4 +- 2 files changed, 148 insertions(+), 136 deletions(-) diff --git a/Models/Climate/WeatherSampler.cs b/Models/Climate/WeatherSampler.cs index ccb8112783..563d535e8c 100644 --- a/Models/Climate/WeatherSampler.cs +++ b/Models/Climate/WeatherSampler.cs @@ -9,9 +9,8 @@ namespace Models.Climate { - /// - /// Allow random sampling of whole years of weather data from a weather file. + /// Generates random sampling for whole years of weather data from a weather file /// [Serializable] [ViewName("UserInterface.Views.PropertyView")] @@ -27,51 +26,67 @@ namespace Models.Climate public class WeatherSampler : Model, IWeather { - /// A data table of all weather data. + /// + /// A link to the clock model + /// + [Link] + private IClock clock = null; + + /// + /// A link to access the simulation level models + /// + [Link] + private Simulation simulation = null; + + /// + /// Event that will be invoked immediately before the daily weather data is updated + /// + /// + /// This provides models and scripts an opportunity to change the weather data before + /// other models access them + /// + public event EventHandler PreparingNewWeatherData; + + /// A data table of all weather data private DataTable data; - /// The current row into the weather data table. + /// The current row into the weather data table private int currentRowIndex; - /// The current index into the Years array. + /// The current index into the years array private int currentYearIndex; - /// The first date in the weather file. + /// The first date in the weather file private DateTime firstDateInFile; - /// The last date in the weather file. + /// The last date in the weather file private DateTime lastDateInFile; - [Link] - private IClock clock = null; - - [Link] - private Simulation simulation = null; - - /// Type of randomiser enum for drop down. + /// Options defining year sampling type public enum RandomiserTypeEnum { - /// Specify years manually. + /// Specify years manually SpecificYears, - /// Random sampler. + /// Fully random sampler RandomSample, - /// Random choose the first year to draw weather data from. + /// Random choose the first year to draw weather data from RandomChooseFirstYear } - - /// The weather file name. + /// + /// Gets or sets the weather file name. Should be relative file path where possible + /// [Summary] [Description("Weather file to sample from")] public string FileName { get; set; } - /// Type of year sampling. + /// Choice of year sampling type [Description("Type of sampling")] public RandomiserTypeEnum TypeOfSampling { get; set; } - /// The sample years. + /// The seed for sampling years [Summary] [Description("Seed to pass to random number generator. Leave blank for fully random")] [Display(VisibleCallback = "IsRandomEnabled")] @@ -79,148 +94,123 @@ public enum RandomiserTypeEnum /// The sample years. [Summary] - [Description("Years to sample from the weather file.")] + [Description("Years from which to sample the weather file")] [Display(VisibleCallback = "IsSpecifyYearsEnabled")] - public int[] Years { get; set; } + public int[] SampleYears { get; set; } - /// Is random enabled? + /// Flag whether random sampling is enabled public bool IsRandomEnabled { get { return TypeOfSampling == RandomiserTypeEnum.RandomSample || TypeOfSampling == RandomiserTypeEnum.RandomChooseFirstYear; } } - /// Is 'specify years' enabled? + /// Flag whether 'specify years' is enabled public bool IsSpecifyYearsEnabled { get { return TypeOfSampling == RandomiserTypeEnum.SpecificYears; } } - /// The date when years tick over. + /// The date marking when years tick over [Summary] - [Description("The date marking the start of sampling years (d-mmm). Leave blank for 1-Jan")] + [Description("Date marking the start of a sampling year (d-mmm). Leave blank for 1-Jan")] public string SplitDate { get; set; } - - - /// Met Data from yesterday - [JsonIgnore] - public DailyMetDataFromFile YesterdaysMetData { get; set; } - - /// Met Data from yesterday - [JsonIgnore] - public DailyMetDataFromFile TomorrowsMetData { get; set; } - - /// The start date of the weather file. + /// Gets the start date of the weather file public DateTime StartDate => clock.StartDate; - /// The end date of the weather file. + /// Gets the end date of the weather file public DateTime EndDate => clock.EndDate; - /// The maximum temperature (oc). - [Units("°C")] + /// Gets or sets the daily minimum air temperature (oC) [JsonIgnore] - public double MaxT { get; set; } + [Units("oC")] + public double MinT { get; set; } - /// Gets or sets the minimum temperature (oc). - [Units("°C")] + /// Gets or sets the daily maximum air temperature (oC) [JsonIgnore] - public double MinT { get; set; } + [Units("oC")] + public double MaxT { get; set; } - /// Mean temperature. - [Units("°C")] + /// Gets the daily mean air temperature (oC) + [Units("oC")] [JsonIgnore] public double MeanT { get { return (MaxT + MinT) / 2; } } - /// Daily mean VPD. - [Units("hPa")] + /// Gets or sets the solar radiation (MJ/m2) + [Units("MJ/m2")] [JsonIgnore] - public double VPD { get; set; } + public double Radn { get; set; } - /// Daily Pan evaporation. - [Units("mm")] + /// Gets or sets the diffuse radiation fraction (0-1) + [Units("0-1")] [JsonIgnore] - public double PanEvap { get; set; } + public double DiffuseFraction { get; set; } - /// Rainfall (mm). + /// Gets or sets the rainfall amount (mm) [Units("mm")] [JsonIgnore] public double Rain { get; set; } - /// Solar radiation (MJ/m2/day). - [Units("MJ/m^2/d")] + /// Gets or sets the class A pan evaporation (mm) + [Units("mm")] [JsonIgnore] - public double Radn { get; set; } + public double PanEvap { get; set; } - /// Vapor pressure. + /// Gets or sets the air vapour pressure (hPa) [Units("hPa")] [JsonIgnore] public double VP { get; set; } - /// Wind. + /// Gets or sets the daily mean vapour pressure deficit (hPa) + [Units("hPa")] + [JsonIgnore] + public double VPD { get; set; } + + /// Gets or sets the average wind speed (m/s) + [Units("m/s")] [JsonIgnore] public double Wind { get; set; } - /// CO2 level. If not specified in the weather file the default is 350. + /// Gets or sets the CO2 level in the atmosphere (ppm) [Units("ppm")] [JsonIgnore] public double CO2 { get; set; } - /// Atmospheric air pressure. If not specified in the weather file the default is 1010 hPa. + /// Gets or sets the mean atmospheric air pressure [Units("hPa")] [JsonIgnore] public double AirPressure { get; set; } - /// Diffuse radiation fraction. If not specified in the weather file the default is 1. - [Units("0-1")] - [JsonIgnore] - public double DiffuseFraction { get; set; } - - /// Latitude. + /// Gets or sets the latitude (decimal degrees) + [Units("degrees")] [JsonIgnore] public double Latitude { get; set; } - /// Gets the longitude + /// Gets or sets the longitude (decimal degrees) + [Units("degrees")] [JsonIgnore] public double Longitude { get; set; } - /// Average temperature. - [Units("°C")] + /// Gets the long-term average air temperature (oC) + [Units("oC")] [JsonIgnore] public double Tav { get; set; } - /// Temperature amplitude. - [Units("°C")] + /// Gets the long-term average temperature amplitude (oC) + [Units("oC")] [JsonIgnore] public double Amp { get; set; } - /// - /// This event will be invoked immediately before models get their weather data. - /// models and scripts an opportunity to change the weather data before other models - /// reads it. - /// - public event EventHandler PreparingNewWeatherData; - - /// Duration of the day in hours. - /// The twilight angle. - public double CalculateDayLength(double Twilight) - { - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, this.Latitude); - } - - /// calculate the time of sun rise - /// Sun rise time - public double CalculateSunRise() - { - return 12 - CalculateDayLength(-6) / 2; - } + /// Met Data from yesterday + [JsonIgnore] + public DailyMetDataFromFile YesterdaysMetData { get; set; } - /// calculate the time of sun set - /// Sun set time - public double CalculateSunSet() - { - return 12 + CalculateDayLength(-6) / 2; - } + /// Met Data for tomorrow + [JsonIgnore] + public DailyMetDataFromFile TomorrowsMetData { get; set; } - /// Called at the beginning of a simulation. + /// Overrides the base class method to allow for initialization of this model + /// The sender of the event + /// The arguments of the event [EventSubscribe("Commencing")] - private void OnStartOfSimulation(object sender, EventArgs e) + private void OnSimulationCommencing(object sender, EventArgs e) { - // Open the weather file and read its contents. + // open the weather file and read its contents. var file = new ApsimTextFile(); try { @@ -245,17 +235,17 @@ private void OnStartOfSimulation(object sender, EventArgs e) file.Close(); } - // Make sure some data was read. + // check that some data was read if (data.Rows.Count == 0) throw new Exception($"No weather data found in file {FileName}"); - // If sampling method is random then create an array of random years. + // if sampling method is random then create an array of random years if (IsRandomEnabled) { - // Determine the number of years to extract out of the weather file. + // number of years to extract out of the weather file int numYears = clock.EndDate.Year - clock.StartDate.Year + 1; - // Determine the year range to sample from. Only sample from years that have a full record i.e. 1-jan to 31-dec + // year range to sample from. Only sample from years that have a full annual record var firstYearToSampleFrom = firstDateInFile.Year; var lastYearToSampleFrom = lastDateInFile.Year; if (firstDateInFile.DayOfYear > 1) @@ -271,38 +261,38 @@ private void OnStartOfSimulation(object sender, EventArgs e) if (TypeOfSampling == RandomiserTypeEnum.RandomSample) { - // Randomly sample from the weather record for the required number of years. - Years = new int[numYears]; + // randomly sample from the weather record for the required number of years + SampleYears = new int[numYears]; for (int i = 0; i < numYears; i++) - Years[i] = random.Next(firstYearToSampleFrom, lastYearToSampleFrom); + SampleYears[i] = random.Next(firstYearToSampleFrom, lastYearToSampleFrom); } else if (TypeOfSampling == RandomiserTypeEnum.RandomChooseFirstYear) { - // Randomly choose a year to draw weather data from. - // The year chosen can't be at the end of the weather record because there needs to be sufficient years of - // consecutive weather data after the chosen year for the length of simulation. Limit the random number generator to - // allow for this. + // randomly choose a year from which to start drawing weather data + // The year chosen can't be to close to end of the weather record. As this is the year + // to start the sampling, there needs to at least as many year after this as the length + // of the simulation. Here the random number generator is limited to account for this var lastYearForRandomNumberGenerator = lastYearToSampleFrom - numYears + 1; if (lastYearForRandomNumberGenerator < firstYearToSampleFrom) throw new Exception("There is insufficient weather data for the length of simulation (clock enddate-startdate). Cannot randomly sample the start date."); firstYearToSampleFrom = random.Next(firstYearToSampleFrom, lastYearForRandomNumberGenerator); - Years = Enumerable.Range(firstYearToSampleFrom, numYears).ToArray(); + SampleYears = Enumerable.Range(firstYearToSampleFrom, numYears).ToArray(); } } - if (Years == null || Years.Length == 0) + if (SampleYears == null || SampleYears.Length == 0) throw new Exception("No years specified in WeatherRandomiser"); if (string.IsNullOrEmpty(SplitDate)) { - SplitDate = "1-jan"; + SplitDate = "1-Jan"; } currentYearIndex = 0; - currentRowIndex = FindRowForDate(new DateTime(Years[currentYearIndex], clock.StartDate.Month, clock.StartDate.Day)); + currentRowIndex = FindRowForDate(new DateTime(SampleYears[currentYearIndex], clock.StartDate.Month, clock.StartDate.Day)); } - /// An event handler for the daily DoWeather event. + /// Performs the tasks to update the weather data /// The sender of the event /// The arguments of the event [EventSubscribe("DoWeather")] @@ -310,32 +300,33 @@ private void OnDoWeather(object sender, EventArgs e) { if (clock.Today == DateUtilities.GetDate(SplitDate, clock.Today.Year)) { - // Need to change years to next one in sequence. + // need to change years to next one in sequence currentYearIndex++; - if (currentYearIndex == Years.Length) + if (currentYearIndex == SampleYears.Length) currentYearIndex = 0; - var dateToFind = DateUtilities.GetDate(SplitDate, Years[currentYearIndex]); + var dateToFind = DateUtilities.GetDate(SplitDate, SampleYears[currentYearIndex]); currentRowIndex = FindRowForDate(dateToFind); } var dateInFile = DataTableUtilities.GetDateFromRow(data.Rows[currentRowIndex]); if (dateInFile.Day == 29 && dateInFile.Month == 2 && clock.Today.Day == 1 && clock.Today.Month == 3) { - // Leap day in weather data but not clock - skip the leap day. + // leap day in weather data but not clock - skip the leap day currentRowIndex++; } else if (dateInFile.Day == 1 && dateInFile.Month == 3 && clock.Today.Day == 29 && clock.Today.Month == 2) { - // Leap day in clock but not weather data. + // leap day in clock but not weather data - repeat last day currentRowIndex--; } else { - // Make sure date in file matches the clock. + // Make sure date in file matches the clock if (clock.Today.Day != dateInFile.Day || clock.Today.Month != dateInFile.Month) throw new Exception($"Non contiguous weather data found at date {dateInFile}"); } + MaxT = Convert.ToDouble(data.Rows[currentRowIndex]["MaxT"]); MinT = Convert.ToDouble(data.Rows[currentRowIndex]["MinT"]); Radn = Convert.ToDouble(data.Rows[currentRowIndex]["Radn"]); @@ -345,29 +336,50 @@ private void OnDoWeather(object sender, EventArgs e) if (data.Columns.Contains("Wind")) Wind = Convert.ToDouble(data.Rows[currentRowIndex]["Wind"]); if (AirPressure == 0) - this.AirPressure = 1010; + AirPressure = 1010; currentRowIndex++; PreparingNewWeatherData?.Invoke(this, new EventArgs()); } - /// - /// Find a row in the data table that matches a date. Will throw if not found. - /// - /// The date to find. - /// The index of the found row. - private int FindRowForDate(DateTime dateToFind) + /// Finds a row in the data table that matches a given date + /// Will throw an exception if date not found + /// The date to find + /// The index of the row found + private int FindRowForDate(DateTime date) { var firstDateInFile = DataTableUtilities.GetDateFromRow(data.Rows[0]); - var rowIndex = (dateToFind - firstDateInFile).Days; + var rowIndex = (date - firstDateInFile).Days; - // check to make sure dates are ok. + // check to make sure dates are ok if (rowIndex < 0) - throw new Exception($"Cannot find year in weather file. Year = {Years[currentYearIndex]}"); - if (DataTableUtilities.GetDateFromRow(data.Rows[rowIndex]) != dateToFind) + throw new Exception($"Cannot find year in weather file. Year = {SampleYears[currentYearIndex]}"); + if (DataTableUtilities.GetDateFromRow(data.Rows[rowIndex]) != date) throw new Exception($"Non consecutive dates found in file {FileName}"); return rowIndex; } + + /// Computes the duration of the day, with light (hours) + /// The angle to measure time for twilight (degrees) + /// The number of hours of daylight + public double CalculateDayLength(double Twilight) + { + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, this.Latitude); + } + + /// Computes the time of sun rise (h) + /// Sun rise time + public double CalculateSunRise() + { + return 12 - CalculateDayLength(-6) / 2; + } + + /// Computes the time of sun set (h) + /// Sun set time + public double CalculateSunSet() + { + return 12 + CalculateDayLength(-6) / 2; + } } } diff --git a/Tests/UnitTests/Weather/WeatherSamplerTests.cs b/Tests/UnitTests/Weather/WeatherSamplerTests.cs index 7f2e2136f8..d02a84be73 100644 --- a/Tests/UnitTests/Weather/WeatherSamplerTests.cs +++ b/Tests/UnitTests/Weather/WeatherSamplerTests.cs @@ -32,7 +32,7 @@ public void ThreeYearTest() new WeatherSampler() { FileName = weatherFilePath, - Years = new int[] { 1995, 1952, 1993 } + SampleYears = new int[] { 1995, 1952, 1993 } }, new MockSummary() } @@ -139,7 +139,7 @@ public void WaterYearTest() new WeatherSampler() { FileName = weatherFilePath, - Years = new int[] { 1995, 1952, 1993 }, + SampleYears = new int[] { 1995, 1952, 1993 }, SplitDate = "2-jun" }, new MockSummary() From a11fa18dd3beb0a54028f7f2d274604653b02c08 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Mon, 30 Sep 2024 14:34:20 +1300 Subject: [PATCH 36/39] Add variables for deafult values and FullFileName in WeatherSampler, align with Weather --- Models/Climate/WeatherSampler.cs | 81 +++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/Models/Climate/WeatherSampler.cs b/Models/Climate/WeatherSampler.cs index 563d535e8c..2d788aa341 100644 --- a/Models/Climate/WeatherSampler.cs +++ b/Models/Climate/WeatherSampler.cs @@ -75,6 +75,26 @@ public enum RandomiserTypeEnum RandomChooseFirstYear } + /// + /// Default value for wind speed (m/s) + /// + private const double defaultWind = 3.0; + + /// + /// Default value for atmospheric CO2 concentration (ppm) + /// + private const double defaultCO2 = 350.0; + + /// + /// Default value for solar angle for computing twilight (degrees) + /// + private const double defaultTwilight = 6.0; + + /// + /// Maximum value expected for mean long-term temperature amplitude (oC) + /// + private const double maximumAMP = 25.0; + /// /// Gets or sets the weather file name. Should be relative file path where possible /// @@ -82,6 +102,36 @@ public enum RandomiserTypeEnum [Description("Weather file to sample from")] public string FileName { get; set; } + /// + /// Gets or sets the full file name (with path) + /// + [JsonIgnore] + public string FullFileName + { + get + { + Simulation simulation = FindAncestor(); + if (simulation != null && simulation.FileName != null) + return PathUtilities.GetAbsolutePath(FileName, simulation.FileName); + else + { + Simulations simulations = FindAncestor(); + if (simulations != null) + return PathUtilities.GetAbsolutePath(FileName, simulations.FileName); + else + return PathUtilities.GetAbsolutePath(FileName, ""); + } + } + set + { + Simulations simulations = FindAncestor(); + if (simulations != null) + FileName = PathUtilities.GetRelativePath(value, simulations.FileName); + else + FileName = value; + } + } + /// Choice of year sampling type [Description("Type of sampling")] public RandomiserTypeEnum TypeOfSampling { get; set; } @@ -98,7 +148,6 @@ public enum RandomiserTypeEnum [Display(VisibleCallback = "IsSpecifyYearsEnabled")] public int[] SampleYears { get; set; } - /// Flag whether random sampling is enabled public bool IsRandomEnabled { get { return TypeOfSampling == RandomiserTypeEnum.RandomSample || TypeOfSampling == RandomiserTypeEnum.RandomChooseFirstYear; } } @@ -127,73 +176,73 @@ public enum RandomiserTypeEnum public double MaxT { get; set; } /// Gets the daily mean air temperature (oC) - [Units("oC")] [JsonIgnore] + [Units("oC")] public double MeanT { get { return (MaxT + MinT) / 2; } } /// Gets or sets the solar radiation (MJ/m2) - [Units("MJ/m2")] [JsonIgnore] + [Units("MJ/m2")] public double Radn { get; set; } /// Gets or sets the diffuse radiation fraction (0-1) - [Units("0-1")] [JsonIgnore] + [Units("0-1")] public double DiffuseFraction { get; set; } /// Gets or sets the rainfall amount (mm) - [Units("mm")] [JsonIgnore] + [Units("mm")] public double Rain { get; set; } /// Gets or sets the class A pan evaporation (mm) - [Units("mm")] [JsonIgnore] + [Units("mm")] public double PanEvap { get; set; } /// Gets or sets the air vapour pressure (hPa) - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double VP { get; set; } /// Gets or sets the daily mean vapour pressure deficit (hPa) - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double VPD { get; set; } /// Gets or sets the average wind speed (m/s) - [Units("m/s")] [JsonIgnore] + [Units("m/s")] public double Wind { get; set; } /// Gets or sets the CO2 level in the atmosphere (ppm) - [Units("ppm")] [JsonIgnore] + [Units("ppm")] public double CO2 { get; set; } /// Gets or sets the mean atmospheric air pressure - [Units("hPa")] [JsonIgnore] + [Units("hPa")] public double AirPressure { get; set; } /// Gets or sets the latitude (decimal degrees) - [Units("degrees")] [JsonIgnore] + [Units("degrees")] public double Latitude { get; set; } /// Gets or sets the longitude (decimal degrees) - [Units("degrees")] [JsonIgnore] + [Units("degrees")] public double Longitude { get; set; } /// Gets the long-term average air temperature (oC) - [Units("oC")] [JsonIgnore] + [Units("oC")] public double Tav { get; set; } /// Gets the long-term average temperature amplitude (oC) - [Units("oC")] [JsonIgnore] + [Units("oC")] public double Amp { get; set; } /// Met Data from yesterday @@ -228,7 +277,7 @@ private void OnSimulationCommencing(object sender, EventArgs e) if (file.Constant("CO2") != null) CO2 = Convert.ToDouble(file.Constant("CO2").Value); else - CO2 = 350; + CO2 = defaultCO2; } finally { From aebb87b8c53ba6d238283ad9e130d0dcedb55024 Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Sun, 6 Oct 2024 15:59:00 +1300 Subject: [PATCH 37/39] Add all the basic output variables to WeatherSampler (align with Weather) --- Models/Climate/WeatherSampler.cs | 47 ++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/Models/Climate/WeatherSampler.cs b/Models/Climate/WeatherSampler.cs index 2d788aa341..aa4c1ae831 100644 --- a/Models/Climate/WeatherSampler.cs +++ b/Models/Climate/WeatherSampler.cs @@ -38,6 +38,12 @@ public class WeatherSampler : Model, IWeather [Link] private Simulation simulation = null; + /// + /// A link to the summary (log file) + /// + [Link] + private ISummary summary = null; + /// /// Event that will be invoked immediately before the daily weather data is updated /// @@ -185,6 +191,16 @@ public string FullFileName [Units("MJ/m2")] public double Radn { get; set; } + /// Gets or sets the maximum clear sky radiation (MJ/m2) + [JsonIgnore] + [Units("MJ/m2")] + public double Qmax { get; set; } + + /// Gets or sets the day length, period with light (h) + [JsonIgnore] + [Units("h")] + public double DayLength { get; set; } + /// Gets or sets the diffuse radiation fraction (0-1) [JsonIgnore] [Units("0-1")] @@ -195,6 +211,11 @@ public string FullFileName [Units("mm")] public double Rain { get; set; } + /// Gets or sets the number duration of rainfall within a day (h) + [JsonIgnore] + [Units("h")] + public double RainfallHours { get; set; } + /// Gets or sets the class A pan evaporation (mm) [JsonIgnore] [Units("mm")] @@ -253,6 +274,17 @@ public string FullFileName [JsonIgnore] public DailyMetDataFromFile TomorrowsMetData { get; set; } + /// + /// Check values in weather and return a collection of warnings + /// + public IEnumerable Validate() + { + if (Amp > maximumAMP) + { + yield return $"The value of Weather.AMP ({Amp}) is > {maximumAMP} oC. Please check the value."; + } + } + /// Overrides the base class method to allow for initialization of this model /// The sender of the event /// The arguments of the event @@ -333,9 +365,11 @@ private void OnSimulationCommencing(object sender, EventArgs e) if (SampleYears == null || SampleYears.Length == 0) throw new Exception("No years specified in WeatherRandomiser"); - if (string.IsNullOrEmpty(SplitDate)) { + if (string.IsNullOrEmpty(SplitDate)) SplitDate = "1-Jan"; - } + + foreach (var message in Validate()) + summary.WriteMessage(this, message, MessageType.Warning); currentYearIndex = 0; currentRowIndex = FindRowForDate(new DateTime(SampleYears[currentYearIndex], clock.StartDate.Month, clock.StartDate.Day)); @@ -414,7 +448,14 @@ private int FindRowForDate(DateTime date) /// The number of hours of daylight public double CalculateDayLength(double Twilight) { - return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, this.Latitude); + if (DayLength <= -1) + { // day length was not given as column or set as a constant + return MathUtilities.DayLength(clock.Today.DayOfYear, Twilight, Latitude); + } + else + { + return DayLength; + } } /// Computes the time of sun rise (h) From 2843a4af00bb650aebd3af31bea6a55bb8ec573c Mon Sep 17 00:00:00 2001 From: Dean Holzworth Date: Tue, 8 Oct 2024 11:22:41 +1000 Subject: [PATCH 38/39] Fixed unit test --- .../UnitTests/Weather/WeatherSamplerTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Tests/UnitTests/Weather/WeatherSamplerTests.cs b/Tests/UnitTests/Weather/WeatherSamplerTests.cs index d02a84be73..50ec840ddd 100644 --- a/Tests/UnitTests/Weather/WeatherSamplerTests.cs +++ b/Tests/UnitTests/Weather/WeatherSamplerTests.cs @@ -25,7 +25,7 @@ public void ThreeYearTest() { Children = new List() { - new Clock() + new Clock() { StartDate = new DateTime(3000, 6, 1) }, @@ -45,11 +45,11 @@ public void ThreeYearTest() Utilities.InjectLink(weatherSampler, "clock", clock); var weather = baseSim.Children[1] as WeatherSampler; - Utilities.CallEvent(weather, "StartOfSimulation"); + Utilities.CallEvent(weather, "SimulationCommencing"); // ################## YEAR 1 - year before a leap year // 1st Jun 1995 in weather file - // year day radn maxt mint rain pan vp + // year day radn maxt mint rain pan vp // 1995 152 14.0 22.5 10.0 4.3 2.2 15.0 Utilities.CallEvent(weather, "DoWeather"); Assert.That(weather.Radn, Is.EqualTo(14).Within(delta)); @@ -63,7 +63,7 @@ public void ThreeYearTest() AdvanceOneDay(weather, clock); // 31st May 1952 in weather file - // year day radn maxt mint rain pan vp + // year day radn maxt mint rain pan vp // 1952 152 16.0 21.7 6.1 0.0 2.8 9.4 Assert.That(weather.Radn, Is.EqualTo(16).Within(delta)); Assert.That(weather.MaxT, Is.EqualTo(21.7).Within(delta)); @@ -73,7 +73,7 @@ public void ThreeYearTest() // ################## YEAR 2 - leap year // 1st June 1952 in weather file - // year day radn maxt mint rain pan vp + // year day radn maxt mint rain pan vp // 1952 153 16.0 20.6 6.1 0.0 2.8 9.7 AdvanceOneDay(weather, clock); Assert.That(weather.Radn, Is.EqualTo(16).Within(delta)); @@ -87,7 +87,7 @@ public void ThreeYearTest() AdvanceOneDay(weather, clock); // 31st May 1993 in weather file - // year day radn maxt mint rain pan vp + // year day radn maxt mint rain pan vp // 1993 151 14.0 24.5 9.5 0.0 2.6 15.0 Assert.That(weather.Radn, Is.EqualTo(14).Within(delta)); Assert.That(weather.MaxT, Is.EqualTo(24.5).Within(delta)); @@ -97,7 +97,7 @@ public void ThreeYearTest() // ################## YEAR 3 - year after leap year // 1st June 1993 in weather file - // year day radn maxt mint rain pan vp + // year day radn maxt mint rain pan vp // 1993 152 9.0 24.0 14.0 0.0 2.6 16.0 AdvanceOneDay(weather, clock); Assert.That(weather.Radn, Is.EqualTo(9).Within(delta)); @@ -112,7 +112,7 @@ public void ThreeYearTest() // ***** BACK TO FIRST YEAR (1995) // 1st Jun 1995 in weather file - // year day radn maxt mint rain pan vp + // year day radn maxt mint rain pan vp // 1995 152 14.0 22.5 10.0 4.3 2.2 15.0 Assert.That(weather.Radn, Is.EqualTo(14).Within(delta)); Assert.That(weather.MaxT, Is.EqualTo(22.5).Within(delta)); @@ -132,7 +132,7 @@ public void WaterYearTest() { Children = new List() { - new Clock() + new Clock() { StartDate = new DateTime(3000, 6, 1) }, @@ -153,11 +153,11 @@ public void WaterYearTest() Utilities.InjectLink(weatherSampler, "clock", clock); var weather = baseSim.Children[1] as WeatherSampler; - Utilities.CallEvent(weather, "StartOfSimulation"); + Utilities.CallEvent(weather, "SimulationCommencing"); - // ################## YEAR 1 + // ################## YEAR 1 // 1st Jun 1995 in weather file - // year day radn maxt mint rain pan vp + // year day radn maxt mint rain pan vp // 1995 152 14.0 22.5 10.0 4.3 2.2 15.0 Utilities.CallEvent(weather, "DoWeather"); Assert.That(weather.Radn, Is.EqualTo(14).Within(delta)); From 02fd45cb83483deb0759b9e51f01b4288fbe59fc Mon Sep 17 00:00:00 2001 From: "rogerio.cichota@plantandfood.co.nz" Date: Wed, 9 Oct 2024 10:05:19 +1300 Subject: [PATCH 39/39] Add winter solstice day to WeatherSampler --- Models/Climate/WeatherSampler.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Models/Climate/WeatherSampler.cs b/Models/Climate/WeatherSampler.cs index aa4c1ae831..b1c5445adf 100644 --- a/Models/Climate/WeatherSampler.cs +++ b/Models/Climate/WeatherSampler.cs @@ -266,6 +266,35 @@ public string FullFileName [Units("oC")] public double Amp { get; set; } + /// Gets the day for the winter solstice (day) + [JsonIgnore] + [Units("day")] + public int WinterSolsticeDOY + { + get + { + if (Latitude <= 0) + { + if (DateTime.IsLeapYear(clock.Today.Year)) + return 173; + else + return 172; + } + else + { + if (DateTime.IsLeapYear(clock.Today.Year)) + return 356; + else + return 355; + } + } + } + + /// Gets or sets the number of days since the winter solstice + [JsonIgnore] + [Units("d")] + public int DaysSinceWinterSolstice { get; set; } + /// Met Data from yesterday [JsonIgnore] public DailyMetDataFromFile YesterdaysMetData { get; set; }