/// <summary>Calculate the interception loss of water from the canopy</summary> private void CalculateInterception(ZoneMicroClimate ZoneMC) { double sumLAI = 0.0; double sumLAItot = 0.0; for (int i = 0; i <= ZoneMC.numLayers - 1; i++) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { sumLAI += ZoneMC.Canopies[j].LAI[i]; sumLAItot += ZoneMC.Canopies[j].LAItot[i]; } } double totalInterception = a_interception * Math.Pow(weather.Rain, b_interception) + c_interception * sumLAItot + d_interception; totalInterception = Math.Max(0.0, Math.Min(0.99 * weather.Rain, totalInterception)); for (int i = 0; i <= ZoneMC.numLayers - 1; i++) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Canopies[j].interception[i] = MathUtilities.Divide(ZoneMC.Canopies[j].LAI[i], sumLAI, 0.0) * totalInterception; } } }
/// <summary>Calculate the interception loss of water from the canopy</summary> private void CalculateInterception(ZoneMicroClimate ZoneMC) { double sumLAI = 0.0; double sumLAItot = 0.0; for (int i = 0; i <= ZoneMC.numLayers - 1; i++) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { sumLAI += ZoneMC.Canopies[j].LAI[i]; sumLAItot += ZoneMC.Canopies[j].LAItot[i]; } } double totalInterception = a_interception * Math.Pow(weather.Rain, b_interception) + c_interception * sumLAItot + d_interception; totalInterception = Math.Max(0.0, Math.Min(0.99 * weather.Rain, totalInterception)); for (int i = 0; i <= ZoneMC.numLayers - 1; i++) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Canopies[j].interception[i] = MathUtilities.Divide(ZoneMC.Canopies[j].LAI[i], sumLAI, 0.0) * totalInterception; } } ISoilWater zonesoilwater = Apsim.Find(ZoneMC.zone, typeof(ISoilWater)) as ISoilWater; if (zonesoilwater != null) { zonesoilwater.PotentialInfiltration = Math.Max(0, weather.Rain - totalInterception); } }
/// <summary>Calculate the amtospheric potential evaporation rate for each zone</summary> private void CalculateEo(ZoneMicroClimate ZoneMC) { ISoilWater zoneSoilWater = Apsim.Find(ZoneMC.zone, typeof(ISoilWater)) as ISoilWater; ISurfaceOrganicMatter zoneSurfaceOM = Apsim.Find(ZoneMC.zone, typeof(ISurfaceOrganicMatter)) as ISurfaceOrganicMatter; double CoverGreen = 0; for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { if (ZoneMC.Canopies[j].Canopy != null) { CoverGreen += (1 - CoverGreen) * ZoneMC.Canopies[j].Canopy.CoverGreen; } } if (weather != null && zoneSoilWater != null && zoneSurfaceOM != null) { zoneSoilWater.Eo = AtmosphericPotentialEvaporationRate(weather.Radn, weather.MaxT, weather.MinT, zoneSoilWater.Salb, zoneSurfaceOM.Cover, CoverGreen); } }
/// <summary> /// This model is for strip crops where there is verticle overlap of the shortest and tallest crops canopy but no horizontal overlap /// </summary> /// <param name="tallest"></param> /// <param name="shortest"></param> private void doStripCropShortWaveRadiation(ref ZoneMicroClimate tallest, ref ZoneMicroClimate shortest) { if (MathUtilities.Sum(tallest.DeltaZ) > 0) // Don't perform calculations if layers are empty { double Ht = MathUtilities.Sum(tallest.DeltaZ); // Height of tallest strip double Hs = MathUtilities.Sum(shortest.DeltaZ); // Height of shortest strip double Wt = (tallest.zone as Zones.RectangularZone).Width; // Width of tallest strip double Ws = (shortest.zone as Zones.RectangularZone).Width; // Width of shortest strip double Ft = Wt / (Wt + Ws); // Fraction of space in tallest strip double Fs = Ws / (Wt + Ws); // Fraction of space in the shortest strip double LAIt = MathUtilities.Sum(tallest.LAItotsum); // LAI of tallest strip double LAIs = MathUtilities.Sum(shortest.LAItotsum); // LAI of shortest strip double Kt = tallest.Canopies[0].Ktot; // Extinction Coefficient of the tallest strip double Ks = shortest.Canopies[0].Ktot; // Extinction Coefficient of the shortest strip double Httop = Ht - Hs; // Height of the top layer in tallest strip (ie distance from top of shortest to top of tallest) double LAIttop = Httop / Ht * LAIt; // LAI of the top layer of the tallest strip (ie LAI in tallest strip above height of shortest strip) double LAItbot = LAIt - LAIttop; // LAI of the bottom layer of the tallest strip (ie LAI in tallest strip below height of the shortest strip) double LAIttophomo = Ft * LAIttop; // LAI of top layer of tallest strip if spread homogeneously across all of the space double Ftblack = (Math.Sqrt(Math.Pow(Httop, 2) + Math.Pow(Wt, 2)) - Httop) / Wt; // View factor for top layer of tallest strip double Fsblack = (Math.Sqrt(Math.Pow(Httop, 2) + Math.Pow(Ws, 2)) - Httop) / Ws; // View factor for top layer of shortest strip double Tt = Ft * (Ftblack * Math.Exp(-Kt * LAIttop) + Ft * (1 - Ftblack) * Math.Exp(-Kt * LAIttophomo)) + Fs * Ft * (1 - Fsblack) * Math.Exp(-Kt * LAIttophomo); // Transmission of light to bottom of top layer in tallest strip double Ts = Fs * (Fsblack + Fs * (1 - Fsblack) * Math.Exp(-Kt * LAIttophomo)) + Ft * Fs * ((1 - Ftblack) * Math.Exp(-Kt * LAIttophomo)); // Transmission of light to bottom of top layer in shortest strip double Intttop = 1 - Tt - Ts; // Interception by the top layer of the tallest strip (ie light intercepted in tallest strip above height of shortest strip) double Inttbot = (Tt * (1 - Math.Exp(-Kt * LAItbot))); // Interception by the bottom layer of the tallest strip double Soilt = (Tt * (Math.Exp(-Kt * LAItbot))); // Transmission to the soil below tallest strip double Ints = Ts * (1 - Math.Exp(-Ks * LAIs)); // Interception by the shortest strip double Soils = Ts * (Math.Exp(-Ks * LAIs)); // Transmission to the soil below shortest strip double EnergyBalanceCheck = Intttop + Inttbot + Soilt + Ints + Soils; // Sum of all light fractions (should equal 1) if (Math.Abs(1 - EnergyBalanceCheck) > 0.001) { throw (new Exception("Energy Balance not maintained in strip crop light interception model")); } tallest.Canopies[0].Rs[0] = weather.Radn * (Intttop + Inttbot) / Ft; tallest.SurfaceRs = weather.Radn * Soilt / Ft; if (shortest.Canopies[0].Rs != null) { if (shortest.Canopies[0].Rs.Length > 0) { shortest.Canopies[0].Rs[0] = weather.Radn * Ints / Fs; } } shortest.SurfaceRs = weather.Radn * Soils / Fs; } else { //tallest.Canopies[0].Rs[0] =0; tallest.SurfaceRs = weather.Radn; //shortest.Canopies[0].Rs[0] = 0; shortest.SurfaceRs = weather.Radn; } }
/// <summary>Calculate the aerodynamic decoupling for system compartments</summary> private void CalculateOmega(ZoneMicroClimate MCZone) { for (int i = 0; i <= MCZone.numLayers - 1; i++) { for (int j = 0; j <= MCZone.Canopies.Count - 1; j++) { MCZone.Canopies[j].Omega[i] = CalcOmega(weather.MinT, weather.MaxT, weather.AirPressure, MCZone.Canopies[j].Ga[i], MCZone.Canopies[j].Gc[i]); } } }
/// <summary> /// Create a new MicroClimateZone for a given simulation zone /// </summary> /// <param name="newZone"></param> private void CreateZoneMicroClimate(Zone newZone) { ZoneMicroClimate myZoneMC = new ZoneMicroClimate(); myZoneMC.zone = newZone; myZoneMC.Reset(); foreach (ICanopy canopy in Apsim.ChildrenRecursively(newZone, typeof(ICanopy))) { myZoneMC.Canopies.Add(new CanopyType(canopy)); } zoneMicroClimates.Add(myZoneMC); }
/// <summary>Calculate the aerodynamic conductance for system compartments</summary> private void CalculateGa(ZoneMicroClimate ZoneMC) { double sumDeltaZ = MathUtilities.Sum(ZoneMC.DeltaZ); double sumLAI = MathUtilities.Sum(ZoneMC.LAItotsum); double totalGa = AerodynamicConductanceFAO(weather.Wind, ReferenceHeight, sumDeltaZ, sumLAI); for (int i = 0; i <= ZoneMC.numLayers - 1; i++) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Canopies[j].Ga[i] = totalGa * MathUtilities.Divide(ZoneMC.Canopies[j].Rs[i], ZoneMC.sumRs, 0.0); } } }
/// <summary> /// Calculate Radiation loss to soil heating /// </summary> private void CalculateSoilHeatRadiation(ZoneMicroClimate ZoneMC) { double radnint = ZoneMC.sumRs; // Intercepted SW radiation ZoneMC.SoilHeatFlux = CalculateSoilHeatFlux(weather.Radn, radnint, SoilHeatFluxFraction); // SoilHeat balance Proportional to Short Wave Balance // ==================================================== for (int i = ZoneMC.numLayers - 1; i >= 0; i += -1) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Canopies[j].Rsoil[i] = MathUtilities.Divide(ZoneMC.Canopies[j].Rs[i], weather.Radn, 0.0) * ZoneMC.SoilHeatFlux; } } }
/// <summary> /// Calculate Net Long Wave Radiation Balance /// </summary> private void CalculateLongWaveRadiation(ZoneMicroClimate ZoneMC) { double sunshineHours = CalcSunshineHours(weather.Radn, dayLengthLight, weather.Latitude, Clock.Today.DayOfYear); double fractionClearSky = MathUtilities.Divide(sunshineHours, dayLengthLight, 0.0); double averageT = CalcAverageT(weather.MinT, weather.MaxT); ZoneMC.NetLongWaveRadiation = LongWave(averageT, fractionClearSky, ZoneMC.Emissivity) * dayLengthEvap * hr2s / 1000000.0; // W to MJ // Long Wave Balance Proportional to Short Wave Balance // ==================================================== for (int i = ZoneMC.numLayers - 1; i >= 0; i += -1) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Canopies[j].Rl[i] = MathUtilities.Divide(ZoneMC.Canopies[j].Rs[i], weather.Radn, 0.0) * ZoneMC.NetLongWaveRadiation; } } }
/// <summary> /// Calculates interception of short wave by canopy compartments /// </summary> private void CalculateLayeredShortWaveRadiation(ZoneMicroClimate ZoneMC) { // Perform Top-Down Light Balance // ============================== double Rin = weather.Radn; double Rint = 0; for (int i = ZoneMC.numLayers - 1; i >= 0; i += -1) { Rint = Rin * (1.0 - Math.Exp(-ZoneMC.layerKtot[i] * ZoneMC.LAItotsum[i])); for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Canopies[j].Rs[i] = Rint * MathUtilities.Divide(ZoneMC.Canopies[j].Ftot[i] * ZoneMC.Canopies[j].Ktot, ZoneMC.layerKtot[i], 0.0); } Rin -= Rint; } ZoneMC.SurfaceRs = Rin; }
/// <summary>Calculate the canopy conductance for system compartments</summary> private void CalculateGc(ZoneMicroClimate ZoneMC) { double Rin = weather.Radn; for (int i = ZoneMC.numLayers - 1; i >= 0; i += -1) { double Rflux = Rin * 1000000.0 / (dayLengthEvap * hr2s) * (1.0 - ZoneMC.Albedo); double Rint = 0.0; for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Canopies[j].Gc[i] = CanopyConductance(ZoneMC.Canopies[j].Canopy.Gsmax, ZoneMC.Canopies[j].Canopy.R50, ZoneMC.Canopies[j].Fgreen[i], ZoneMC.layerKtot[i], ZoneMC.LAItotsum[i], Rflux); Rint += ZoneMC.Canopies[j].Rs[i]; } // Calculate Rin for the next layer down Rin -= Rint; } }
/// <summary> /// Calculate the overall system energy terms /// </summary> private void CalculateEnergyTerms(ZoneMicroClimate ZoneMC) { ZoneMC.sumRs = 0.0; ZoneMC.Albedo = 0.0; ZoneMC.Emissivity = 0.0; for (int i = ZoneMC.numLayers - 1; i >= 0; i += -1) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { ZoneMC.Albedo += MathUtilities.Divide(ZoneMC.Canopies[j].Rs[i], weather.Radn, 0.0) * ZoneMC.Canopies[j].Canopy.Albedo; ZoneMC.Emissivity += MathUtilities.Divide(ZoneMC.Canopies[j].Rs[i], weather.Radn, 0.0) * CanopyEmissivity; ZoneMC.sumRs += ZoneMC.Canopies[j].Rs[i]; } } ZoneMC.Albedo += (1.0 - MathUtilities.Divide(ZoneMC.sumRs, weather.Radn, 0.0)) * soil_albedo; ZoneMC.Emissivity += (1.0 - MathUtilities.Divide(ZoneMC.sumRs, weather.Radn, 0.0)) * SoilEmissivity; }
/// <summary>Calculate the Penman-Monteith water demand</summary> private void CalculatePM(ZoneMicroClimate ZoneMC) { // zero a few things, and sum a few others double sumRl = 0.0; double sumRsoil = 0.0; double sumInterception = 0.0; double freeEvapGa = 0.0; for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { sumRl += MathUtilities.Sum(ZoneMC.Canopies[j].Rl); sumRsoil += MathUtilities.Sum(ZoneMC.Canopies[j].Rsoil); sumInterception += MathUtilities.Sum(ZoneMC.Canopies[j].interception); freeEvapGa += MathUtilities.Sum(ZoneMC.Canopies[j].Ga); } double netRadiation = ((1.0 - ZoneMC.Albedo) * ZoneMC.sumRs + sumRl + sumRsoil) * 1000000.0; // MJ/J netRadiation = Math.Max(0.0, netRadiation); double freeEvapGc = freeEvapGa * 1000000.0; // infinite surface conductance double freeEvap = CalcPenmanMonteith(netRadiation, weather.MinT, weather.MaxT, weather.VP, weather.AirPressure, dayLengthEvap, freeEvapGa, freeEvapGc); ZoneMC.dryleaffraction = 1.0 - MathUtilities.Divide(sumInterception * (1.0 - NightInterceptionFraction), freeEvap, 0.0); ZoneMC.dryleaffraction = Math.Max(0.0, ZoneMC.dryleaffraction); for (int i = 0; i <= ZoneMC.numLayers - 1; i++) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { netRadiation = 1000000.0 * ((1.0 - ZoneMC.Albedo) * ZoneMC.Canopies[j].Rs[i] + ZoneMC.Canopies[j].Rl[i] + ZoneMC.Canopies[j].Rsoil[i]); netRadiation = Math.Max(0.0, netRadiation); ZoneMC.Canopies[j].PETr[i] = CalcPETr(netRadiation * ZoneMC.dryleaffraction, weather.MinT, weather.MaxT, weather.AirPressure, ZoneMC.Canopies[j].Ga[i], ZoneMC.Canopies[j].Gc[i]); ZoneMC.Canopies[j].PETa[i] = CalcPETa(weather.MinT, weather.MaxT, weather.VP, weather.AirPressure, dayLengthEvap * ZoneMC.dryleaffraction, ZoneMC.Canopies[j].Ga[i], ZoneMC.Canopies[j].Gc[i]); ZoneMC.Canopies[j].PET[i] = ZoneMC.Canopies[j].PETr[i] + ZoneMC.Canopies[j].PETa[i]; } } }
/// <summary>Send an energy balance event</summary> private void SetCanopyEnergyTerms(ZoneMicroClimate ZoneMC) { for (int j = 0; j <= ZoneMC.Canopies.Count - 1; j++) { if (ZoneMC.Canopies[j].Canopy != null) { CanopyEnergyBalanceInterceptionlayerType[] lightProfile = new CanopyEnergyBalanceInterceptionlayerType[ZoneMC.numLayers]; double totalPotentialEp = 0; double totalInterception = 0.0; for (int i = 0; i <= ZoneMC.numLayers - 1; i++) { lightProfile[i] = new CanopyEnergyBalanceInterceptionlayerType(); lightProfile[i].thickness = ZoneMC.DeltaZ[i]; lightProfile[i].amount = ZoneMC.Canopies[j].Rs[i] * ZoneMC.RadnGreenFraction(j); totalPotentialEp += ZoneMC.Canopies[j].PET[i]; totalInterception += ZoneMC.Canopies[j].interception[i]; } ZoneMC.Canopies[j].Canopy.PotentialEP = totalPotentialEp; ZoneMC.Canopies[j].Canopy.WaterDemand = totalPotentialEp; ZoneMC.Canopies[j].Canopy.LightProfile = lightProfile; } } }
/// <summary> /// This model is for tree crops where there is no vertical overlap of the shortest and tallest canopy but the tallest canopy can overlap the shortest horzontally /// </summary> /// <param name="tree"></param> /// <param name="alley"></param> private void doTreeRowCropShortWaveRadiation(ref ZoneMicroClimate tree, ref ZoneMicroClimate alley) { if (MathUtilities.Sum(tree.DeltaZ) > 0) // Don't perform calculations if layers are empty { double Ht = MathUtilities.Sum(tree.DeltaZ); // Height of tree canopy double CDt = tree.Canopies[0].Canopy.Depth / 1000; // Depth of tree canopy double CBHt = Ht - CDt; // Base hight of the tree canopy double Ha = MathUtilities.Sum(alley.DeltaZ); // Height of alley canopy if ((Ha > CBHt) & (tree.DeltaZ.Length > 1)) { throw (new Exception("Height of the alley canopy must not exceed the base height of the tree canopy")); } double Wt = (tree.zone as Zones.RectangularZone).Width; // Width of tree zone double Wa = (alley.zone as Zones.RectangularZone).Width; // Width of alley zone double CWt = Math.Min(tree.Canopies[0].Canopy.Width / 1000, (Wt + Wa)); // Width of the tree canopy double WaOl = Math.Min(CWt - Wt, Wa); // Width of tree canopy that overlap the alley zone double WaOp = Wa - WaOl; // Width of the open alley zone between tree canopies double Ft = CWt / (Wt + Wa); // Fraction of space in tree canopy double Fs = WaOp / (Wt + Wa); // Fraction of open space in the alley row double LAIt = MathUtilities.Sum(tree.LAItotsum); // LAI of trees double LAIa = MathUtilities.Sum(alley.LAItotsum); // LAI of alley crop double Kt = tree.Canopies[0].Ktot; // Extinction Coefficient of trees double Ka = alley.Canopies[0].Ktot; // Extinction Coefficient of alley crop double LAIthomo = Ft * LAIt; // LAI of trees if spread homogeneously across row and alley zones double Ftbla = (Math.Sqrt(Math.Pow(CDt, 2) + Math.Pow(CWt, 2)) - CDt) / CWt; // View factor for the tree canopy if a black body double Fabla = (Math.Sqrt(Math.Pow(CDt, 2) + Math.Pow(WaOp, 2)) - CDt) / WaOp; // View factor for the gap between trees in alley if trees a black body if (WaOp == 0) { Fabla = 0; } //All transmission and interception values below are a fraction of the total short wave radiation incident to both the tree and alley rows double Tt = Ft * (Ftbla * Math.Exp(-Kt * LAIt) + Ft * (1 - Ftbla) * Math.Exp(-Kt * LAIthomo)) + Fs * Ft * (1 - Fabla) * Math.Exp(-Kt * LAIthomo); // Transmission of light to the bottom of the tree canopy double Ta = Fs * (Fabla + Fs * (1 - Fabla) * Math.Exp(-Kt * LAIthomo)) + Ft * Fs * ((1 - Ftbla) * Math.Exp(-Kt * LAIthomo)); // Transmission of light to the bottom of the gap in the tree canopy double It = 1 - Tt - Ta; // Interception by the trees double St = Tt * Wt / CWt; // Transmission to the soil in the tree zone double IaOl = Tt * WaOl / CWt * (1 - Math.Exp(-Ka * LAIa)); // Interception by the alley canopy below the overlap of the trees double IaOp = Ta * (1 - Math.Exp(-Ka * LAIa)); // Interception by the alley canopy in the gaps between tree canopy double Ia = IaOl + IaOp; // Interception by the alley canopy double SaOl = Tt * WaOl / CWt * (Math.Exp(-Ka * LAIa)); // Transmission to the soil beneigth the alley canopy under the tree canopy double SaOp = Ta * (Math.Exp(-Ka * LAIa)); // Transmission to the soil beneigth the alley canopy in the open double Sa = SaOl + SaOp; // Transmission to the soil beneight the alley double EnergyBalanceCheck = It + St + Ia + Sa; // Sum of all light fractions (should equal 1) if (Math.Abs(1 - EnergyBalanceCheck) > 0.001) { throw (new Exception("Energy Balance not maintained in strip crop light interception model")); } Ft = (Wt) / (Wt + Wa); // Remove overlap so scaling back to zone ground area works Fs = (Wa) / (Wt + Wa); // Remove overlap so scaling back to zone ground area works tree.Canopies[0].Rs[1] = weather.Radn * It / Ft; tree.SurfaceRs = weather.Radn * St / Ft; if (alley.Canopies[0].Rs != null) { if (alley.Canopies[0].Rs.Length > 0) { alley.Canopies[0].Rs[0] = weather.Radn * Ia / Fs; } } alley.SurfaceRs = weather.Radn * Sa / Fs; } else { tree.SurfaceRs = weather.Radn; alley.SurfaceRs = weather.Radn; } }