/// <summary>Estimates the amount of plant available water in each soil layer of the root zone.</summary> /// <remarks> /// This is an alternative method, which does not use kl. A factor based on Ksat is used instead. This is further modified /// by soil water content and a plant related factor, defined based on root length density. All three factors are normalised /// (using ReferenceKSat and ReferenceRLD for KSat and root and DUL for soil water content). The effect of all factors are /// assumed to vary between zero and one following exponential functions, such that the effect is 90% at the reference value. /// </remarks> /// <param name="myZone">The soil information</param> /// <returns>The amount of available water in each layer (mm)</returns> internal double[] PlantAvailableSoilWaterAlternativeKS(ZoneWaterAndN myZone) { double[] result = new double[nLayers]; SoilCrop soilCropData = (SoilCrop)mySoil.Crop(mySpeciesName); for (int layer = 0; layer <= BottomLayer; layer++) { double condFac = 1.0 - Math.Pow(10.0, -mySoil.KS[layer] / myReferenceKSuptake); double rldFac = 1.0 - Math.Pow(10.0, -RootLengthDensity[layer] / myReferenceRLD); double swFac; if (mySoil.SoilWater.SWmm[layer] >= mySoil.DULmm[layer]) { swFac = 1.0; } else if (mySoil.SoilWater.SWmm[layer] <= mySoil.LL15mm[layer]) { swFac = 0.0; } else { double waterRatio = (myZone.Water[layer] - mySoil.LL15mm[layer]) / (mySoil.DULmm[layer] - mySoil.LL15mm[layer]); swFac = 1.0 - Math.Pow(1.0 - waterRatio, myExponentSoilMoisture); } // Total available water result[layer] = Math.Max(0.0, myZone.Water[layer] - soilCropData.LL[layer]) * mySoil.Thickness[layer]; // Actual plant available water result[layer] *= FractionLayerWithRoots(layer) * Math.Min(1.0, rldFac * condFac * swFac); } return(result); }
/// <summary> /// Growth depth of roots in this zone /// </summary> public void GrowRootDepth() { // Do Root Front Advance int RootLayer = Soil.LayerIndexOfDepth(Depth, soil.Thickness); SoilCrop crop = soil.Crop(plant.Name) as SoilCrop; Depth = Depth + root.RootFrontVelocity.Value * crop.XF[RootLayer]; // Limit root depth for impeded layers double MaxDepth = 0; for (int i = 0; i < soil.Thickness.Length; i++) { if (crop.XF[i] > 0) { MaxDepth += soil.Thickness[i]; } } // Limit root depth for the crop specific maximum depth MaxDepth = Math.Min(root.MaximumRootDepth.Value, MaxDepth); Depth = Math.Min(Depth, MaxDepth); }
//----------------------- Public methods ----------------------- /// <summary>Initialise this root instance (and tissues).</summary> /// <param name="zone">The zone the roots belong to.</param> /// <param name="minimumLiveWt">Minimum live DM biomass for this organ (kg/ha).</param> public void Initialise(Zone zone, double minimumLiveWt) { // link to soil models parameters soil = zone.FindInScope <Soil>(); if (soil == null) { throw new Exception($"Cannot find soil in zone {zone.Name}"); } soilPhysical = soil.FindInScope <IPhysical>(); if (soilPhysical == null) { throw new Exception($"Cannot find soil physical in soil {soil.Name}"); } waterBalance = soil.FindInScope <ISoilWater>(); if (waterBalance == null) { throw new Exception($"Cannot find a water balance model in soil {soil.Name}"); } soilCropData = soil.FindDescendant <SoilCrop>(species.Name + "Soil"); if (soilCropData == null) { throw new Exception($"Cannot find a soil crop parameterisation called {species.Name + "Soil"}"); } nutrient = zone.FindInScope <INutrient>(); if (nutrient == null) { throw new Exception($"Cannot find SoilNitrogen in zone {zone.Name}"); } no3 = zone.FindInScope("NO3") as ISolute; if (no3 == null) { throw new Exception($"Cannot find NO3 solute in zone {zone.Name}"); } nh4 = zone.FindInScope("NH4") as ISolute; if (nh4 == null) { throw new Exception($"Cannot find NH4 solute in zone {zone.Name}"); } // initialise soil related variables zoneName = soil.Parent.Name; nLayers = soilPhysical.Thickness.Length; mySoilNH4Available = new double[nLayers]; mySoilNO3Available = new double[nLayers]; mySoilWaterAvailable = new double[nLayers]; // save minimum DM and get target root distribution MinimumLiveDM = minimumLiveWt; TargetDistribution = RootDistributionTarget(); // initialise tissues Live.Initialise(); Dead.Initialise(); }
/// <summary> /// Formats the GridView. Sets colours, spacing, locks the /// depth column, etc. /// </summary> private void FormatGrid() { for (int i = 0; i < properties.Count; i++) { // fixme - ugly hack to work around last column being very wide if (i != properties.Count - 1) { grid.GetColumn(i).LeftJustification = false; } grid.GetColumn(i).HeaderLeftJustification = false; VariableProperty property = properties[i] as VariableProperty; if (!(property.Object is SoilCrop)) { continue; } SoilCrop crop = property.Object as SoilCrop; int index = Apsim.Children(crop.Parent, typeof(SoilCrop)).IndexOf(crop); Color foreground = ColourUtilities.ChooseColour(index); if (property.IsReadOnly) { foreground = Color.Red; } grid.GetColumn(i).ForegroundColour = foreground; grid.GetColumn(i).MinimumWidth = 70; grid.GetColumn(i).ReadOnly = property.IsReadOnly; } grid.LockLeftMostColumns(1); }
public void ImporterTests_SoilImports() { string oldXml = ReflectionUtilities.GetResourceAsString("UnitTests.ImporterTestsSoilImports.xml"); APSIMImporter importer = new APSIMImporter(); Simulations sims = importer.CreateSimulationsFromXml(oldXml); Soil s = sims.Children[0].Children[0] as Soil; Assert.AreEqual(s.Name, "Soil"); InitialWater initWater = s.Children[0] as InitialWater; Assert.AreEqual(initWater.FractionFull, 0.5); Assert.AreEqual(initWater.PercentMethod, InitialWater.PercentMethodEnum.FilledFromTop); Water w = s.Children[1] as Water; Assert.AreEqual(w.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(w.BD, new double[] { 1.02, 1.03, 1.02, 1.02 }); Assert.AreEqual(w.LL15, new double[] { 0.29, 0.29, 0.29, 0.29 }); SoilWater sw = s.Children[2] as SoilWater; Assert.AreEqual(sw.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(sw.SWCON, new double[] { 0.3, 0.3, 0.3, 0.3 }); Assert.AreEqual(sw.SummerCona, 3.5); Assert.AreEqual(sw.SummerU, 6); Assert.AreEqual(sw.WinterCona, 2); Assert.AreEqual(sw.WinterU, 2); Assert.IsTrue(s.Children[3] is SoilNitrogen); SoilOrganicMatter som = s.Children[4] as SoilOrganicMatter; Assert.AreEqual(som.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(som.OC, new double[] { 1.04, 0.89, 0.89, 0.89 }); Assert.AreEqual(som.FBiom, new double[] { 0.025, 0.02, 0.015, 0.01 }); Analysis a = s.Children[5] as Analysis; Assert.AreEqual(a.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(a.EC, new double[] { 0.2, 0.25, 0.31, 0.40 }); Assert.AreEqual(a.PH, new double[] { 8.4, 8.8, 9.0, 9.2 }); Sample sam = s.Children[6] as Sample; Assert.AreEqual(sam.Thickness, new double[] { 150, 150, 300 }); Assert.AreEqual(sam.NO3, new double[] { 6.5, 2.1, 2.1 }); Assert.AreEqual(sam.NH4, new double[] { 0.5, 0.1, 0.1 }); SoilCrop crop = s.Children[1].Children[0] as SoilCrop; Assert.AreEqual(crop.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(crop.LL, new double[] { 0.29, 0.29, 0.32, 0.38 }); Assert.AreEqual(crop.KL, new double[] { 0.1, 0.1, 0.08, 0.06 }); Assert.AreEqual(crop.XF, new double[] { 1, 1, 1, 1 }); }
private void OnSimulationCommencing(object sender, EventArgs e) { soilCrop = this.Soil.Crop(this.Plant.Name) as SoilCrop; if (soilCrop == null) { throw new ApsimXException(this, "Cannot find a soil crop parameterisation for " + Name); } Clear(); }
/// <summary> /// Setup the grid as a water grid. /// </summary> private void AddCropColumns() { Color[] CropColors = { Color.FromArgb(173, 221, 142), Color.FromArgb(247, 252, 185) }; Color[] PredictedCropColors = { Color.FromArgb(233, 191, 255), Color.FromArgb(244, 226, 255) }; // DataGridViewColumn SAT = Grid.Columns["SAT\r\n(mm/mm)"]; // SAT.Frozen = true; Grid.Columns[Grid.ColumnCount - 1].Frozen = true; int CropIndex = 0; int PredictedCropIndex = 0; foreach (string CropName in Soil.CropNames.Union(Soil.PredictedCropNames, StringComparer.OrdinalIgnoreCase)) { SoilCrop Crop = Soil.Crop(CropName); bool IsReadonly; Color CropColour; Color ForeColour = Color.Black; if (Crop.LLMetadata != null && Crop.LLMetadata.First() == "Estimated") { CropColour = PredictedCropColors[PredictedCropIndex]; ForeColour = Color.Gray; IsReadonly = true; PredictedCropIndex++; if (PredictedCropIndex >= PredictedCropColors.Length) { PredictedCropIndex = 0; } } else { CropColour = CropColors[CropIndex]; IsReadonly = false; CropIndex++; if (CropIndex >= CropColors.Length) { CropIndex = 0; } } double[] PAWCmm = MathUtility.Multiply(Soil.PAWCCropAtWaterThickness(CropName), Soil.Water.Thickness); DataGridViewColumn LL = GridUtility.AddColumn(Grid, CropName + " LL\r\n(mm/mm)", Crop.LL, "f3", CropColour, ForeColour, ToolTips: Crop.LLMetadata, ReadOnly: IsReadonly); DataGridViewColumn PAWC = GridUtility.AddColumn(Grid, CropName + " PAWC\r\n", PAWCmm, "f1", CropColour, Color.Gray, ReadOnly: true, ToolTips: StringManip.CreateStringArray("Calculated from crop LL and DUL", PAWCmm.Length)); DataGridViewColumn KL = GridUtility.AddColumn(Grid, CropName + " KL\r\n(/day)", Crop.KL, "f2", CropColour, ForeColour, ToolTips: Crop.KLMetadata, ReadOnly: IsReadonly); DataGridViewColumn XF = GridUtility.AddColumn(Grid, CropName + " XF\r\n(0-1)", Crop.XF, "f1", CropColour, ForeColour, ToolTips: Crop.XFMetadata, ReadOnly: IsReadonly); PAWC.ToolTipText = "Calculated from crop LL and DUL"; PAWC.ReadOnly = true; UpdateTotal(PAWC); } }
/// <summary>Convert the crop to the specified thickness. Ensures LL is between AirDry and DUL.</summary> /// <param name="crop">The crop to convert</param> /// <param name="thickness">The thicknesses to convert the crop to.</param> /// <param name="physical">The soil the crop belongs to.</param> private static void SetCropThickness(SoilCrop crop, double[] thickness, IPhysical physical) { if (!MathUtilities.AreEqual(thickness, physical.Thickness)) { crop.LL = MapConcentration(crop.LL, physical.Thickness, thickness, MathUtilities.LastValue(crop.LL)); crop.KL = MapConcentration(crop.KL, physical.Thickness, thickness, MathUtilities.LastValue(crop.KL)); crop.XF = MapConcentration(crop.XF, physical.Thickness, thickness, MathUtilities.LastValue(crop.XF)); crop.LL = MathUtilities.Constrain(crop.LL, AirDryMapped(physical, thickness), DULMapped(physical, thickness)); } }
private void OnSimulationCommencing(object sender, EventArgs e) { Uptakes = new List <ZoneWaterAndN>(); EP = 0; soilCrop = Soil.FindDescendant <SoilCrop>(Name + "Soil"); if (soilCrop == null) { throw new Exception($"Cannot find a soil crop parameterisation called {Name}Soil"); } }
public void ImporterTests_SoilImports() { string oldXml = ReflectionUtilities.GetResourceAsString("UnitTests.ImporterTestsSoilImports.xml"); var importer = new Importer(); Simulations sims = importer.CreateSimulationsFromXml(oldXml); Soil s = sims.Children[0].Children[0] as Soil; Assert.AreEqual(s.Name, "Soil"); InitialWater initWater = s.Children[0] as InitialWater; Assert.AreEqual(initWater.FractionFull, 0.5); Assert.AreEqual(initWater.PercentMethod, InitialWater.PercentMethodEnum.FilledFromTop); Physical w = s.Children[1] as Physical; Assert.AreEqual(w.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(w.BD, new double[] { 1.02, 1.03, 1.02, 1.02 }); Assert.AreEqual(w.LL15, new double[] { 0.29, 0.29, 0.29, 0.29 }); ISoilWater sw = s.Children[2] as ISoilWater; Assert.AreEqual(sw.Thickness, new double[] { 150, 150, 300, 300 }); Assert.IsTrue(s.Children[3] is SoilNitrogen); Assert.IsTrue(s.Children[4] is CERESSoilTemperature); Organic som = s.Children[5] as Organic; Assert.AreEqual(som.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(som.Carbon, new double[] { 1.04, 0.89, 0.89, 0.89 }); Assert.AreEqual(som.FBiom, new double[] { 0.025, 0.02, 0.015, 0.01 }); Chemical a = s.Children[6] as Chemical; Assert.AreEqual(a.Thickness, new double[] { 150, 150, 300, 300 }); Assert.AreEqual(a.NO3N, new double[] { 6.5, 2.1, 2.1, 1.0 }); Assert.AreEqual(a.NH4N, new double[] { 0.5, 0.1, 0.1, 0.2 }); Assert.AreEqual(a.EC, new double[] { 0.2, 0.25, 0.31, 0.40 }); Assert.AreEqual(a.PH, new double[] { 8.4, 8.8, 9.0, 9.2 }); Sample sam = s.Children[7] as Sample; Assert.AreEqual(sam.Thickness, new double[] { 150, 150, 300 }); SoilCrop crop = s.Children[1].Children[0] as SoilCrop; Assert.AreEqual(crop.LL, new double[] { 0.29, 0.29, 0.32, 0.38 }); Assert.AreEqual(crop.KL, new double[] { 0.1, 0.1, 0.08, 0.06 }); Assert.AreEqual(crop.XF, new double[] { 1, 1, 1, 1 }); }
/// <summary>Estimates the amount of plant available water in each soil layer of the root zone.</summary> /// <remarks>This is the default APSIM method, with kl representing the daily rate for water extraction</remarks> /// <param name="myZone">The soil information</param> /// <returns>The amount of available water in each layer (mm)</returns> internal double[] PlantAvailableSoilWaterDefault(ZoneWaterAndN myZone) { double[] result = new double[nLayers]; SoilCrop soilCropData = (SoilCrop)mySoil.Crop(mySpeciesName); for (int layer = 0; layer <= BottomLayer; layer++) { result[layer] = Math.Max(0.0, myZone.Water[layer] - (soilCropData.LL[layer] * mySoil.Thickness[layer])); result[layer] *= FractionLayerWithRoots(layer) * soilCropData.KL[layer] * KLModiferDueToDamage(layer); } return(result); }
/// <summary>Computes the target (or ideal) distribution of roots in the soil profile.</summary> /// <remarks> /// This distribution is solely based on root parameters (maximum depth and distribution parameters) /// These values will be used to allocate initial rootDM as well as any growth over the profile /// </remarks> /// <returns>A weighting factor for each soil layer (mm equivalent)</returns> public double[] RootDistributionTarget() { // 1. Base distribution calculated using a combination of linear and power functions: // It considers homogeneous distribution from surface down to a fraction of root depth (DepthForConstantRootProportion), // below this depth the proportion of root decrease following a power function (with exponent ExponentRootDistribution), // it reaches zero slightly below the MaximumRootDepth (defined by rootBottomDistributionFactor), but the function is // truncated at MaximumRootDepth. The values are not normalised. // The values are further adjusted using the values of XF (so there will be less roots in those layers) double[] result = new double[nLayers]; SoilCrop soilCropData = (SoilCrop)mySoil.Crop(mySpeciesName); double depthTop = 0.0; double depthBottom = 0.0; double depthFirstStage = Math.Min(myRootDepthMaximum, myRootDistributionDepthParam); for (int layer = 0; layer < nLayers; layer++) { depthBottom += mySoil.Thickness[layer]; if (depthTop >= myRootDepthMaximum) { // totally out of root zone result[layer] = 0.0; } else if (depthBottom <= depthFirstStage) { // totally in the first stage result[layer] = mySoil.Thickness[layer] * soilCropData.XF[layer]; } else { // at least partially on second stage double maxRootDepth = myRootDepthMaximum * myRootBottomDistributionFactor; result[layer] = Math.Pow(maxRootDepth - Math.Max(depthTop, depthFirstStage), myRootDistributionExponent + 1) - Math.Pow(maxRootDepth - Math.Min(depthBottom, myRootDepthMaximum), myRootDistributionExponent + 1); result[layer] /= (myRootDistributionExponent + 1) * Math.Pow(maxRootDepth - depthFirstStage, myRootDistributionExponent); if (depthTop < depthFirstStage) { // partially in first stage result[layer] += depthFirstStage - depthTop; } result[layer] *= soilCropData.XF[layer]; } depthTop += mySoil.Thickness[layer]; } return(result); }
/// <summary> /// Save all crop columns. /// </summary> private void SaveCropColumns() { for (int Col = 7; Col < Grid.Columns.Count; Col += 4) { string CropName = CropNameFromColumn(Col); SoilCrop Crop = Soil.Crop(CropName); if (Crop.LLMetadata == null || Crop.LLMetadata.First() != "Estimated") { Crop.Thickness = Soil.ToThickness(GridUtility.GetColumnAsStrings(Grid, 0)); Crop.LL = GridUtility.GetColumnAsDoubles(Grid, Col); Crop.LLMetadata = GridUtility.GetColumnOfToolTips(Grid, Col); Crop.KL = GridUtility.GetColumnAsDoubles(Grid, Col + 2); Crop.KLMetadata = GridUtility.GetColumnOfToolTips(Grid, Col + 2); Crop.XF = GridUtility.GetColumnAsDoubles(Grid, Col + 3); Crop.XFMetadata = GridUtility.GetColumnOfToolTips(Grid, Col + 3); } } }
/// <summary>Test setup routine.</summary> /// <returns>A soil that can be used for testing.</returns> private static Soil Setup() { Soil soil = new Soil(); soil.Water = new Water(); soil.Water.Thickness = new double[] { 100, 300, 300, 300, 300, 300 }; soil.Water.BD = new double[] { 1.36, 1.216, 1.24, 1.32, 1.372, 1.368 }; soil.Water.AirDry = new double[] { 0.135, 0.214, 0.261, 0.261, 0.261, 0.261 }; soil.Water.LL15 = new double[] { 0.27, 0.267, 0.261, 0.261, 0.261, 0.261 }; soil.Water.DUL = new double[] { 0.365, 0.461, 0.43, 0.412, 0.402, 0.404 }; // Add a wheat crop. SoilCrop crop = new SoilCrop(); crop.Thickness = soil.Water.Thickness; crop.Name = "Wheat"; crop.KL = new double[] { 0.06, 0.060, 0.060, 0.060, 0.060, 0.060 }; crop.LL = new double[] { 0.27, 0.267, 0.261, 0.315, 0.402, 0.402 }; soil.Water.Crops = new List <SoilCrop>(); soil.Water.Crops.Add(crop); // Add OC values into SoilOrganicMatter. soil.SoilOrganicMatter = new SoilOrganicMatter(); soil.SoilOrganicMatter.Thickness = soil.Water.Thickness; soil.SoilOrganicMatter.OC = new double[] { 2, 1, 0.5, 0.4, 0.3, 0.2 }; // Add in CL into analysis. soil.Analysis = new Analysis(); soil.Analysis.Thickness = soil.Water.Thickness; soil.Analysis.CL = new double[] { 38, double.NaN, 500, 490, 500, 500 }; // Add a sample. Sample sample = new Sample(); sample.Thickness = new double[] { 100, 300, 300, 300 }; sample.SW = new double[] { 0.103, 0.238, 0.253, 0.247 }; sample.NO3 = new double[] { 23, 7, 2, 1 }; sample.OC = new double[] { 1.35, double.NaN, double.NaN, double.NaN }; sample.SWUnits = Sample.SWUnitsEnum.Gravimetric; soil.Samples = new List <Sample>(); soil.Samples.Add(sample); return(soil); }
/// <summary>Calculate the potential sw uptake for today</summary> /// <param name="soilstate"></param> /// <returns>list of uptakes</returns> /// <exception cref="ApsimXException">Could not find root zone in Zone + this.Parent.Name + for SimpleTree</exception> public List <ZoneWaterAndN> GetWaterUptakeEstimates(SoilState soilstate) { ZoneWaterAndN MyZone = new ZoneWaterAndN(this.Parent as Zone); foreach (ZoneWaterAndN Z in soilstate.Zones) { if (Z.Zone.Name == this.Parent.Name) { MyZone = Z; } } double[] PotSWUptake = new double[Soil.LL15.Length]; SWUptake = new double[Soil.LL15.Length]; SoilCrop soilCrop = Soil.Crop(this.Name) as SoilCrop; for (int j = 0; j < Soil.LL15mm.Length; j++) { PotSWUptake[j] = Math.Max(0.0, RootProportion(j, RootDepth) * soilCrop.KL[j] * (MyZone.Water[j] - Soil.LL15mm[j])); } double TotPotSWUptake = MathUtilities.Sum(PotSWUptake); for (int j = 0; j < Soil.LL15mm.Length; j++) { SWUptake[j] = PotSWUptake[j] * Math.Min(1.0, PotentialEP / TotPotSWUptake); } List <ZoneWaterAndN> Uptakes = new List <ZoneWaterAndN>(); ZoneWaterAndN Uptake = new ZoneWaterAndN(this.Parent as Zone); Uptake.Water = SWUptake; Uptake.NO3N = new double[SWUptake.Length]; Uptake.NH4N = new double[SWUptake.Length]; Uptake.NH4N = new double[SWUptake.Length]; Uptake.PlantAvailableNO3N = new double[SWUptake.Length]; Uptake.PlantAvailableNH4N = new double[SWUptake.Length]; Uptakes.Add(Uptake); return(Uptakes); }
/// <summary>Checks the crop for missing values.</summary> /// <param name="crop">The crop.</param> /// <param name="soil">The soil.</param> private static void CheckCropForMissingValues(SoilCrop crop, Soil soil) { var water = Apsim.Child(soil, typeof(Water)) as Water; for (int i = 0; i < crop.Thickness.Length; i++) { if (crop.LL != null && double.IsNaN(crop.LL[i])) { crop.LL[i] = water.LL15[i]; } if (crop.KL != null && double.IsNaN(crop.KL[i])) { crop.KL[i] = 0.06; } if (crop.XF != null && double.IsNaN(crop.XF[i])) { crop.XF[i] = 1; } } }
/// <summary>Checks the crop for missing values.</summary> /// <param name="crop">The crop.</param> /// <param name="soil">The soil.</param> private static void CheckCropForMissingValues(SoilCrop crop, Soil soil) { var water = soil.FindChild <Physical>(); for (int i = 0; i < water.Thickness.Length; i++) { if (crop.LL != null && double.IsNaN(crop.LL[i])) { crop.LL[i] = water.LL15[i]; } if (crop.KL != null && double.IsNaN(crop.KL[i])) { crop.KL[i] = 0.06; } if (crop.XF != null && double.IsNaN(crop.XF[i])) { crop.XF[i] = 1; } } }
/// <summary>Placeholder for SoilArbitrator</summary> /// <param name="soilstate">soil state</param> /// <returns></returns> public List <ZoneWaterAndN> GetNUptakes(SoilState soilstate) { ZoneWaterAndN MyZone = new ZoneWaterAndN(); foreach (ZoneWaterAndN Z in soilstate.Zones) { if (Z.Name == this.Parent.Name) { MyZone = Z; } } double[] PotNO3Uptake = new double[Soil.NO3N.Length]; double[] PotNH4Uptake = new double[Soil.NH4N.Length]; NO3Uptake = new double[Soil.NO3N.Length]; NH4Uptake = new double[Soil.NH4N.Length]; SoilCrop soilCrop = Soil.Crop(this.Name) as SoilCrop; for (int j = 0; j < Soil.SoilWater.LL15mm.Length; j++) { PotNO3Uptake[j] = Math.Max(0.0, RootProportion(j, RootDepth) * soilCrop.KL[j] * MyZone.NO3N[j]); PotNH4Uptake[j] = Math.Max(0.0, RootProportion(j, RootDepth) * soilCrop.KL[j] * MyZone.NH4N[j]); } double TotPotNUptake = MathUtilities.Sum(PotNO3Uptake) + MathUtilities.Sum(PotNH4Uptake); for (int j = 0; j < Soil.NO3N.Length; j++) { NO3Uptake[j] = PotNO3Uptake[j] * Math.Min(1.0, NDemand / TotPotNUptake); NH4Uptake[j] = PotNH4Uptake[j] * Math.Min(1.0, NDemand / TotPotNUptake); } List <ZoneWaterAndN> Uptakes = new List <ZoneWaterAndN>(); ZoneWaterAndN Uptake = new ZoneWaterAndN(); Uptake.Name = this.Parent.Name; Uptake.NO3N = NO3Uptake; Uptake.NH4N = NH4Uptake; Uptake.Water = new double[NO3Uptake.Length]; Uptakes.Add(Uptake); return(Uptakes); }
/// <summary>Fills in KL for crop.</summary> /// <param name="crop">The crop.</param> private static void FillInKLForCrop(SoilCrop crop) { if (crop.Name == null) { throw new Exception("Crop has no name"); } int i = StringUtilities.IndexOfCaseInsensitive(cropNames, crop.Name); if (i != -1) { double[] KLs = GetRowOfArray(defaultKLs, i); double[] cumThickness = APSIM.Shared.APSoil.SoilUtilities.ToCumThickness(crop.Thickness); crop.KL = new double[crop.Thickness.Length]; for (int l = 0; l < crop.Thickness.Length; l++) { bool didInterpolate; crop.KL[l] = MathUtilities.LinearInterpReal(cumThickness[l], defaultKLThickness, KLs, out didInterpolate); } } }
/// <summary> /// Modify the KL values for subsoil constraints. /// </summary> /// <remarks> /// From: /// Hochman, Z., Dang, Y.P., Schwenke, G.D., Dalgliesh, N.P., Routley, R., McDonald, M., /// Daniells, I.G., Manning, W., Poulton, P.L., 2007. /// Simulating the effects of saline and sodic subsoils on wheat crops /// growing on Vertosols. Australian Journal of Agricultural Research 58, 802–810. doi:10.1071/ar06365 /// </remarks> /// <param name="crop"></param> /// <param name="soil">The soil the crop belongs to.</param> private static void ModifyKLForSubSoilConstraints(SoilCrop crop, Soil soil) { var soilPhysical = soil.FindChild <IPhysical>(); var initialConditions = soil.Children.Find(child => child is Sample) as Sample; double[] cl = initialConditions.CL; if (MathUtilities.ValuesInArray(cl)) { crop.KL = Layers.MapConcentration(StandardKL, StandardThickness, soilPhysical.Thickness, StandardKL.Last()); for (int i = 0; i < soilPhysical.Thickness.Length; i++) { crop.KL[i] *= Math.Min(1.0, 4.0 * Math.Exp(-0.005 * cl[i])); } } else { double[] esp = initialConditions.ESP; if (MathUtilities.ValuesInArray(esp)) { crop.KL = Layers.MapConcentration(StandardKL, StandardThickness, soilPhysical.Thickness, StandardKL.Last()); for (int i = 0; i < soilPhysical.Thickness.Length; i++) { crop.KL[i] *= Math.Min(1.0, 10.0 * Math.Exp(-0.15 * esp[i])); } } else { double[] ec = initialConditions.EC; if (MathUtilities.ValuesInArray(ec)) { crop.KL = Layers.MapConcentration(StandardKL, StandardThickness, soilPhysical.Thickness, StandardKL.Last()); for (int i = 0; i < soilPhysical.Thickness.Length; i++) { crop.KL[i] *= Math.Min(1.0, 3.0 * Math.Exp(-1.3 * ec[i])); } } } } }
/// <summary>Gets or sets the water supply.</summary> /// <param name="zone">The zone.</param> public double[] CalculateWaterSupply(ZoneWaterAndN zone) { ZoneState myZone = Zones.Find(z => z.Name == zone.Zone.Name); if (myZone == null) { return(null); } SoilCrop crop = myZone.soil.Crop(Plant.Name) as SoilCrop; double[] supply = new double[myZone.soil.Thickness.Length]; double[] layerMidPoints = Soil.ToMidPoints(myZone.soil.Thickness); for (int layer = 0; layer < myZone.soil.Thickness.Length; layer++) { if (layer <= Soil.LayerIndexOfDepth(myZone.Depth, myZone.soil.Thickness)) { supply[layer] = Math.Max(0.0, crop.KL[layer] * KLModifier.ValueForX(layerMidPoints[layer]) * (zone.Water[layer] - crop.LL[layer] * myZone.soil.Thickness[layer]) * Soil.ProportionThroughLayer(layer, myZone.Depth, myZone.soil.Thickness)); } } return(supply); }
/// <summary>Do all soil related settings.</summary> /// <param name="simulation">The specification to use</param> /// <param name="workingFolder">The folder where files shoud be created.</param> private static void DoSoil(APSIMSpecification simulation, string workingFolder) { Soil soil; if (simulation.Soil == null) { if (simulation.SoilPath.StartsWith("<Soil")) { // Soil and Landscape grid XmlDocument doc = new XmlDocument(); doc.LoadXml(simulation.SoilPath); soil = XmlUtilities.Deserialise(doc.DocumentElement, typeof(Soil)) as Soil; } else if (simulation.SoilPath.StartsWith("http")) { // Soil and Landscape grid string xml; using (var client = new WebClient()) { xml = client.DownloadString(simulation.SoilPath); } XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); List <XmlNode> soils = XmlUtilities.ChildNodes(doc.DocumentElement, "Soil"); if (soils.Count == 0) { throw new Exception("Cannot find soil in Soil and Landscape Grid"); } soil = XmlUtilities.Deserialise(soils[0], typeof(Soil)) as Soil; } else { // Apsoil web service. APSOIL.Service apsoilService = new APSOIL.Service(); string soilXml = apsoilService.SoilXML(simulation.SoilPath.Replace("\r\n", "")); if (soilXml == string.Empty) { throw new Exception("Cannot find soil: " + simulation.SoilPath); } soil = SoilUtilities.FromXML(soilXml); } } else { // Just use the soil we already have soil = simulation.Soil; } // Make sure we have a soil crop parameterisation. If not then try creating one // based on wheat. Sow sowing = YieldProphetUtility.GetCropBeingSown(simulation.Management); string[] cropNames = soil.Water.Crops.Select(c => c.Name).ToArray(); if (cropNames.Length == 0) { throw new Exception("Cannot find any crop parameterisations in soil: " + simulation.SoilPath); } if (sowing != null && !StringUtilities.Contains(cropNames, sowing.Crop)) { SoilCrop wheat = soil.Water.Crops.Find(c => c.Name.Equals("wheat", StringComparison.InvariantCultureIgnoreCase)); if (wheat == null) { // Use the first crop instead. wheat = soil.Water.Crops[0]; } SoilCrop newSoilCrop = new SoilCrop(); newSoilCrop.Name = sowing.Crop; newSoilCrop.Thickness = wheat.Thickness; newSoilCrop.LL = wheat.LL; newSoilCrop.KL = wheat.KL; newSoilCrop.XF = wheat.XF; soil.Water.Crops.Add(newSoilCrop); } // Remove any initwater nodes. soil.InitialWater = null; // Transfer the simulation samples to the soil if (simulation.Samples != null) { soil.Samples = simulation.Samples; } if (simulation.InitTotalWater != 0) { soil.InitialWater = new InitialWater(); soil.InitialWater.PercentMethod = InitialWater.PercentMethodEnum.FilledFromTop; double pawc; if (sowing == null || sowing.Crop == null) { pawc = MathUtilities.Sum(PAWC.OfSoilmm(soil)); soil.InitialWater.RelativeTo = "LL15"; } else { SoilCrop crop = soil.Water.Crops.Find(c => c.Name.Equals(sowing.Crop, StringComparison.InvariantCultureIgnoreCase)); pawc = MathUtilities.Sum(PAWC.OfCropmm(soil, crop)); soil.InitialWater.RelativeTo = crop.Name; } soil.InitialWater.FractionFull = Convert.ToDouble(simulation.InitTotalWater) / pawc; } if (simulation.InitTotalNitrogen != 0) { // Add in a sample. Sample nitrogenSample = new Sample(); nitrogenSample.Name = "NitrogenSample"; soil.Samples.Add(nitrogenSample); nitrogenSample.Thickness = new double[] { 150, 150, 3000 }; nitrogenSample.NO3Units = Nitrogen.NUnitsEnum.kgha; nitrogenSample.NH4Units = Nitrogen.NUnitsEnum.kgha; nitrogenSample.NO3 = new double[] { 6.0, 2.1, 0.1 }; nitrogenSample.NH4 = new double[] { 0.5, 0.1, 0.1 }; nitrogenSample.OC = new double[] { double.NaN, double.NaN, double.NaN }; nitrogenSample.EC = new double[] { double.NaN, double.NaN, double.NaN }; nitrogenSample.PH = new double[] { double.NaN, double.NaN, double.NaN }; double Scale = Convert.ToDouble(simulation.InitTotalNitrogen) / MathUtilities.Sum(nitrogenSample.NO3); nitrogenSample.NO3 = MathUtilities.Multiply_Value(nitrogenSample.NO3, Scale); } // Add in soil temperature. Needed for Aflatoxin risk. soil.SoilTemperature = new SoilTemperature(); soil.SoilTemperature.BoundaryLayerConductance = 15; soil.SoilTemperature.Thickness = new double[] { 2000 }; soil.SoilTemperature.InitialSoilTemperature = new double[] { 22 }; if (soil.Analysis.ParticleSizeClay == null) { soil.Analysis.ParticleSizeClay = MathUtilities.CreateArrayOfValues(60, soil.Analysis.Thickness.Length); } InFillMissingValues(soil.Analysis.ParticleSizeClay); foreach (Sample sample in soil.Samples) { CheckSample(soil, sample, sowing); } Defaults.FillInMissingValues(soil); // Correct the CONA / U parameters depending on LAT/LONG CorrectCONAU(simulation, soil, workingFolder); // get rid of <soiltype> from the soil // this is necessary because NPD uses this field and puts in really long // descriptive classifications. Soiln2 bombs with an FString internal error. soil.SoilType = null; // Set the soil name to 'soil' soil.Name = "Soil"; simulation.Soil = soil; }
/// <summary>Constructor, initialise tissues for the roots.</summary> /// <param name="zone">The zone the roots belong in.</param> /// <param name="initialDM">Initial dry matter weight</param> /// <param name="initialDepth">Initial root depth</param> /// <param name="minLiveDM">The minimum biomass for this organ</param> public void Initialise(Zone zone, double initialDM, double initialDepth, double minLiveDM) { soil = zone.FindInScope <Soil>(); if (soil == null) { throw new Exception($"Cannot find soil in zone {zone.Name}"); } soilPhysical = soil.FindInScope <IPhysical>(); if (soilPhysical == null) { throw new Exception($"Cannot find soil physical in soil {soil.Name}"); } waterBalance = soil.FindInScope <ISoilWater>(); if (waterBalance == null) { throw new Exception($"Cannot find a water balance model in soil {soil.Name}"); } soilCropData = soil.FindDescendant <SoilCrop>(species.Name + "Soil"); if (soilCropData == null) { throw new Exception($"Cannot find a soil crop parameterisation called {species.Name + "Soil"}"); } nutrient = zone.FindInScope <INutrient>(); if (nutrient == null) { throw new Exception($"Cannot find SoilNitrogen in zone {zone.Name}"); } no3 = zone.FindInScope("NO3") as ISolute; if (no3 == null) { throw new Exception($"Cannot find NO3 solute in zone {zone.Name}"); } nh4 = zone.FindInScope("NH4") as ISolute; if (nh4 == null) { throw new Exception($"Cannot find NH4 solute in zone {zone.Name}"); } // link to soil and initialise related variables zoneName = soil.Parent.Name; nLayers = soilPhysical.Thickness.Length; dulMM = soilPhysical.DULmm; ll15MM = soilPhysical.LL15mm; mySoilNH4Available = new double[nLayers]; mySoilNO3Available = new double[nLayers]; // save minimum DM and get target root distribution Depth = initialDepth; minimumLiveDM = minLiveDM; CalculateRootZoneBottomLayer(); TargetDistribution = RootDistributionTarget(); // initialise tissues double[] initialDMByLayer = MathUtilities.Multiply_Value(CurrentRootDistributionTarget(), initialDM); double[] initialNByLayer = MathUtilities.Multiply_Value(initialDMByLayer, NConcOptimum); Live = tissue[0]; Dead = tissue[1]; Live.Initialise(initialDMByLayer, initialNByLayer); Dead.Initialise(null, null); }
private void OnSimulationCommencing(object sender, EventArgs e) { CropType = "Slurp"; uptakeWater = new double[Soil.Thickness.Length]; uptakeNitrogen = new double[Soil.Thickness.Length]; uptakeNitrogenPropNO3 = new double[Soil.Thickness.Length]; // set the canopy and root properties here - no need to capture the sets from any Managers as they directly set the properties CanopyProperties.Name = "Slurp"; CanopyProperties.CoverGreen = 1.0 - Math.Exp(-1 * localLightExtinction * localLAIGreen); CanopyProperties.CoverTot = 1.0 - Math.Exp(-1 * localLightExtinction * localLAItot); CanopyProperties.CanopyDepth = localCanopyDepth; CanopyProperties.CanopyHeight = localCanopyHeight; CanopyProperties.LAIGreen = localLAIGreen; CanopyProperties.LAItot = localLAItot; CanopyProperties.MaximumStomatalConductance = localMaximumStomatalConductance; CanopyProperties.HalfSatStomatalConductance = 200.0; // should this be on the UI? CanopyProperties.CanopyEmissivity = 0.96; CanopyProperties.Frgr = localFrgr; SoilCrop soilCrop = this.Soil.Crop(Name) as SoilCrop; RootProperties.KL = soilCrop.KL; RootProperties.MinNO3ConcForUptake = new double[Soil.Thickness.Length]; RootProperties.MinNH4ConcForUptake = new double[Soil.Thickness.Length]; RootProperties.LowerLimitDep = new double[Soil.Thickness.Length]; for (int j = 0; j < Soil.Thickness.Length; j++) { RootProperties.LowerLimitDep[j] = soilCrop.LL[j] * Soil.Thickness[j]; RootProperties.MinNO3ConcForUptake[j] = 0.0; RootProperties.MinNH4ConcForUptake[j] = 0.0; } RootProperties.RootDepth = localRootDepth; RootProperties.KNO3 = localKNO3; RootProperties.KNH4 = localKNH4; RootProperties.UptakePreferenceByLayer = new double[Soil.Thickness.Length]; for (int j = 0; j < Soil.Thickness.Length; j++) { RootProperties.UptakePreferenceByLayer[j] = 1.0; } localRootExplorationByLayer = new double[Soil.Thickness.Length]; localRootLengthDensityByVolume = new double[Soil.Thickness.Length]; uptakeWater = new double[Soil.Thickness.Length]; tempDepthUpper = 0.0; tempDepthMiddle = 0.0; tempDepthLower = 0.0; demandWater = localDemandWater; // calculate root exploration (proprotion of the layer occupied by the roots) for each layer for (int j = 0; j < Soil.Thickness.Length; j++) { tempDepthLower += Soil.Thickness[j]; // increment soil depth thorugh the layers tempDepthMiddle = tempDepthLower - Soil.Thickness[j] * 0.5; tempDepthUpper = tempDepthLower - Soil.Thickness[j]; if (tempDepthUpper < localRootDepth) // set the root exploration { localRootExplorationByLayer[j] = 1.0; } else if (tempDepthLower <= localRootDepth) { localRootExplorationByLayer[j] = MathUtilities.Divide(localRootDepth - tempDepthUpper, Soil.Thickness[j], 0.0); } else { localRootExplorationByLayer[j] = 0.0; } // set a triangular root length density by scaling layer depth against maximum rooting depth, constrain the multiplier between 0 and 1 localRootLengthDensityByVolume[j] = localSurfaceRootLengthDensity * localRootExplorationByLayer[j] * (1.0 - MathUtilities.Constrain(MathUtilities.Divide(tempDepthMiddle, localRootDepth, 0.0), 0.0, 1.0)); } RootProperties.RootExplorationByLayer = localRootExplorationByLayer; RootProperties.RootLengthDensityByVolume = localRootLengthDensityByVolume; }
/// <summary>Crop lower limit - mapped to the specified layer structure. Units: mm/mm /// </summary> /// <param name="crop">The crop.</param> /// <param name="ToThickness">To thickness.</param> /// <returns></returns> private static double[] LLMapped(SoilCrop crop, double[] ToThickness) { var waterNode = crop.Parent as Physical; return(MapConcentration(crop.LL, waterNode.Thickness, ToThickness, MathUtilities.LastValue(crop.LL))); }
/// <summary>Crop lower limit - mapped to the specified layer structure. Units: mm/mm /// </summary> /// <param name="crop">The crop.</param> /// <param name="ToThickness">To thickness.</param> /// <returns></returns> private static double[] LLMapped(SoilCrop crop, double[] ToThickness) { return(MapConcentration(crop.LL, crop.Thickness, ToThickness, MathUtilities.LastValue(crop.LL))); }
/// This alternative approach for obtaining ISRIC soil data need a little bit more work, but is largely complete /// There are still bits of the soil organic matter initialisation that should be enhanced. /// We probably don't really need two different ways to get to ISRIC data, but it may be interesting to see how the /// two compare. The initial motiviation was what appears to be an order-of-magnitude problem with soil carbon /// in the World Modellers version. /// <summary> /// Gets and ISRIC soil description directly from SoilGrids /// </summary> /// <returns>True if successful</returns> private IEnumerable <SoilFromDataSource> GetISRICSoils() { var soils = new List <SoilFromDataSource>(); string url = "https://rest.soilgrids.org/query?lon=" + longitudeEditBox.Text + "&lat=" + latitudeEditBox.Text; try { double[] bd = new double[7]; double[] coarse = new double[7]; double[] clay = new double[7]; double[] silt = new double[7]; double[] sand = new double[7]; double[] thetaSat = new double[7]; double[] awc20 = new double[7]; double[] awc23 = new double[7]; double[] awc25 = new double[7]; double[] thetaWwp = new double[7]; double[] ocdrc = new double[7]; double[] phWater = new double[7]; double[] cationEC = new double[7]; double[] texture = new double[7]; string soilType = String.Empty; double maxTemp = 0.0; double minTemp = 0.0; double ppt = 0.0; double bedrock = 2500.0; string[] textureClasses = new string[] { "Clay", "Silty Clay", "Sandy Clay", "Clay Loam", "Silty Clay Loam", "Sandy Clay Loam", "Loam", "Silty Loam", "Sandy Loam", "Silt", "Loamy Sand", "Sand", "NO DATA" }; double[] textureToAlb = new double[] { 0.12, 0.12, 0.13, 0.13, 0.12, 0.13, 0.13, 0.14, 0.13, 0.13, 0.16, 0.19, 0.13 }; double[] textureToCN2 = new double[] { 73.0, 73.0, 73.0, 73.0, 73.0, 73.0, 73.0, 73.0, 68.0, 73.0, 68.0, 68.0, 73.0 }; double[] textureToSwcon = new double[] { 0.25, 0.3, 0.3, 0.4, 0.5, 0.5, 0.5, 0.5, 0.6, 0.5, 0.6, 0.75, 0.5 }; MemoryStream stream = WebUtilities.ExtractDataFromURL(url); stream.Position = 0; JsonTextReader reader = new JsonTextReader(new StreamReader(stream)); while (reader.Read()) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("properties") && reader.Depth == 1) { reader.Read(); // Read the "start object" token while (reader.Read()) { if (reader.TokenType == JsonToken.PropertyName) { string propName = reader.Value.ToString(); double[] dest = null; double multiplier = 1.0; if (propName == "TAXNWRBMajor") { soilType = reader.ReadAsString(); } else if (propName == "TMDMOD_2011") { maxTemp = 0.0; reader.Read(); while (reader.Read() && reader.TokenType != JsonToken.EndObject) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M")) { reader.Read(); // Read start of object token for (int i = 0; i < 12; i++) { reader.Read(); // Read a month name maxTemp += (double)reader.ReadAsDouble(); } maxTemp /= 12.0; } } } else if (propName == "TMNMOD_2011") { minTemp = 0.0; reader.Read(); while (reader.Read() && reader.TokenType != JsonToken.EndObject) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M")) { reader.Read(); // Read start of object token for (int i = 0; i < 12; i++) { reader.Read(); // Read a month name minTemp += (double)reader.ReadAsDouble(); } minTemp /= 12.0; } } } else if (propName == "PREMRG") { ppt = 0.0; reader.Read(); while (reader.Read() && reader.TokenType != JsonToken.EndObject) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M")) { reader.Read(); // Read start of object token for (int i = 0; i < 12; i++) { reader.Read(); // Read a month name ppt += (double)reader.ReadAsDouble(); } } } } else if (propName == "BDTICM") // Is this the best metric to use for find the "bottom" of the soil? { reader.Read(); while (reader.Read() && reader.TokenType != JsonToken.EndObject) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M")) { reader.Read(); // Read start of object token reader.Read(); // Read property name (which ought to be BDTICM_M) bedrock = 10.0 * (double)reader.ReadAsDouble(); reader.Skip(); } } } else if (propName == "AWCh1") { dest = awc20; multiplier = 0.01; } else if (propName == "AWCh2") { dest = awc23; multiplier = 0.01; } else if (propName == "AWCh3") { dest = awc25; multiplier = 0.01; } else if (propName == "AWCtS") { dest = thetaSat; multiplier = 0.01; } else if (propName == "BLDFIE") { dest = bd; multiplier = 0.001; } else if (propName == "CECSOL") { dest = cationEC; multiplier = 1.0; } else if (propName == "CLYPPT") { dest = clay; multiplier = 1.0; } else if (propName == "CRFVOL") { dest = coarse; multiplier = 1.0; } else if (propName == "ORCDRC") { dest = ocdrc; multiplier = 0.1; } else if (propName == "PHIHOX") { dest = phWater; multiplier = 0.1; } else if (propName == "SLTPPT") { dest = silt; multiplier = 1.0; } else if (propName == "SNDPPT") { dest = sand; multiplier = 1.0; } else if (propName == "TEXMHT") { dest = texture; multiplier = 1.0; } else if (propName == "WWP") { dest = thetaWwp; multiplier = 0.01; } if (dest != null) { reader.Read(); while (reader.Read() && reader.TokenType != JsonToken.EndObject) { if (reader.TokenType == JsonToken.PropertyName && reader.Value.Equals("M")) { while (reader.Read() && reader.TokenType != JsonToken.EndObject) { if (reader.TokenType == JsonToken.PropertyName) { string tokenName = reader.Value.ToString(); if (tokenName.StartsWith("sl")) { int index = Int32.Parse(tokenName.Substring(2)) - 1; dest[index] = (double)reader.ReadAsDouble() * multiplier; } } } } } } else { reader.Skip(); } } } } } var newSoil = new Soil(); Chemical analysis = new Chemical(); Physical waterNode = new Physical(); Organic organicMatter = new Organic(); WaterBalance soilWater = new WaterBalance(); InitialWater initialWater = new InitialWater(); Sample initialNitrogen = new Sample(); SoilNitrogen soilN = new SoilNitrogen(); SoilCrop wheat = new SoilCrop(); waterNode.Children.Add(wheat); wheat.Name = "WheatSoil"; waterNode.ParentAllDescendants(); Model nh4 = new SoilNitrogenNH4(); nh4.Name = "NH4"; soilN.Children.Add(nh4); Model no3 = new SoilNitrogenNO3(); no3.Name = "NO3"; soilN.Children.Add(no3); Model urea = new SoilNitrogenUrea(); urea.Name = "Urea"; soilN.Children.Add(urea); Model plantAvailNH4 = new SoilNitrogenPlantAvailableNH4(); plantAvailNH4.Name = "PlantAvailableNH4"; soilN.Children.Add(plantAvailNH4); Model plantAvailNO3 = new SoilNitrogenPlantAvailableNO3(); plantAvailNO3.Name = "PlantAvailableNO3"; soilN.Children.Add(plantAvailNO3); soilN.ParentAllDescendants(); newSoil.Children.Add(waterNode); newSoil.Children.Add(soilWater); newSoil.Children.Add(soilN); newSoil.Children.Add(organicMatter); newSoil.Children.Add(analysis); newSoil.Children.Add(initialWater); newSoil.Children.Add(initialNitrogen); newSoil.Children.Add(new CERESSoilTemperature()); newSoil.ParentAllDescendants(); newSoil.OnCreated(); newSoil.Name = "Synthetic soil derived from ISRIC SoilGrids REST API"; newSoil.DataSource = "ISRIC SoilGrids"; newSoil.SoilType = soilType; newSoil.Latitude = Convert.ToDouble(latitudeEditBox.Text, System.Globalization.CultureInfo.InvariantCulture); newSoil.Longitude = Convert.ToDouble(longitudeEditBox.Text, System.Globalization.CultureInfo.InvariantCulture); // ISRIC values are for "levels", not "intervals", so we need to convert to layers // Following Andrew Moore's lead on layer thickness and weightings. double[] thickness = new double[] { 150.0, 150.0, 150.0, 150.0, 200.0, 200.0, 200.0, 200.0, 300.0, 300.0 }; double[] depth = new double[thickness.Length]; int layerCount = thickness.Length; for (int i = 0; i < thickness.Length; i++) { depth[i] = thickness[i] + (i > 0 ? depth[i - 1] : 0.0); if ((i > 0) && (layerCount == thickness.Length) && (bedrock < depth[i] + 20.0)) { layerCount = i + 1; thickness[i] = Math.Min(thickness[i], Math.Max(0.0, bedrock - (depth[i] - thickness[i]))); if (i == 1) { thickness[i] = Math.Max(50.0, thickness[i]); } Array.Resize(ref thickness, layerCount); } } analysis.Thickness = thickness; waterNode.Thickness = thickness; soilWater.Thickness = thickness; organicMatter.Thickness = thickness; initialWater.Name = "Initial water"; initialWater.PercentMethod = InitialWater.PercentMethodEnum.FilledFromTop; initialWater.FractionFull = 0.0; // Initialise nitrogen to 0.0 initialNitrogen.Name = "Initial nitrogen"; initialNitrogen.NH4 = new double[layerCount]; initialNitrogen.NO3 = new double[layerCount]; double tAvg = (maxTemp + minTemp) / 2.0; soilWater.CNCov = 0.0; soilWater.CNRed = 20.0; soilWater.SummerDate = newSoil.Latitude <= 0.0 ? "1-nov" : "1-may"; soilWater.WinterDate = newSoil.Latitude <= 0.0 ? "1-apr" : "1-oct"; soilWater.SummerCona = 6.0; soilWater.SummerU = 6.0; soilWater.WinterCona = tAvg < 21.0 ? 2.5 : 6.0; soilWater.WinterU = tAvg < 21.0 ? 4.0 : 6.0; soilWater.Salb = textureToAlb[(int)Math.Round(texture[0] - 1)]; soilWater.CN2Bare = textureToCN2[(int)Math.Round(texture[0] - 1)]; double[] swcon = new double[7]; for (int i = 0; i < 7; i++) { swcon[i] = textureToSwcon[(int)Math.Round(texture[i] - 1)]; } soilWater.SWCON = ConvertLayers(swcon, layerCount); waterNode.BD = ConvertLayers(bd, layerCount); waterNode.LL15 = ConvertLayers(thetaWwp, layerCount); waterNode.SAT = ConvertLayers(thetaSat, layerCount); waterNode.AirDry = ConvertLayers(MathUtilities.Divide_Value(thetaWwp, 3.0), layerCount); double[] dul = new double[7]; for (int i = 0; i < 7; i++) { dul[i] = thetaWwp[i] + awc20[i]; // This could be made Moore complex } waterNode.DUL = ConvertLayers(dul, layerCount); waterNode.ParticleSizeSand = ConvertLayers(sand, layerCount); waterNode.ParticleSizeSilt = ConvertLayers(silt, layerCount); waterNode.ParticleSizeClay = ConvertLayers(clay, layerCount); // waterNode.Rocks = ConvertLayers(coarse, layerCount); analysis.PH = ConvertLayers(phWater, layerCount); // Obviously using the averaging in "ConvertLayers" for texture classes is not really correct, but should be OK as a first pass if we don't have sharply contrasting layers double[] classes = ConvertLayers(texture, layerCount); string[] textures = new string[layerCount]; for (int i = 0; i < layerCount; i++) { textures[i] = textureClasses[(int)Math.Round(classes[i]) - 1]; } double[] xf = new double[layerCount]; double[] kl = new double[layerCount]; double[] ll = new double[layerCount]; double p1 = 1.4; double p2 = 1.60 - p1; double p3 = 1.80 - p1; double topEffDepth = 0.0; double klMax = 0.06; double depthKl = 900.0; double depthRoot = 1900.0; for (int i = 0; i < layerCount; i++) { xf[i] = 1.0 - (waterNode.BD[i] - (p1 + p2 * 0.01 * waterNode.ParticleSizeSand[i])) / p3; xf[i] = Math.Max(0.1, Math.Min(1.0, xf[i])); double effectiveThickness = thickness[i] * xf[i]; double bottomEffDepth = topEffDepth + effectiveThickness; double propMaxKl = Math.Max(0.0, Math.Min(bottomEffDepth, depthKl) - topEffDepth) / effectiveThickness; double propDecrKl = Math.Max(Math.Max(0.0, Math.Min(bottomEffDepth, depthRoot) - topEffDepth) / effectiveThickness - propMaxKl, 0.0); double propZeroKl = 1.0 - propMaxKl - propDecrKl; double ratioTopDepth = Math.Max(0.0, Math.Min((depthRoot - topEffDepth) / (depthRoot - depthKl), 1.0)); double ratioBottomDepth = Math.Max(0.0, Math.Min((depthRoot - bottomEffDepth) / (depthRoot - depthKl), 1.0)); double meanDecrRatio = 0.5 * (ratioTopDepth + ratioBottomDepth); double weightedRatio = propMaxKl * 1.0 + propDecrKl * meanDecrRatio + propZeroKl * 0.0; kl[i] = klMax * weightedRatio; ll[i] = waterNode.LL15[i] + (waterNode.DUL[i] - waterNode.LL15[i]) * (1.0 - weightedRatio); if (kl[i] <= 0.0) { xf[i] = 0.0; } topEffDepth = bottomEffDepth; } wheat.XF = xf; wheat.KL = kl; wheat.LL = ll; organicMatter.Carbon = ConvertLayers(ocdrc, layerCount); double rootWt = Math.Max(0.0, Math.Min(3000.0, 2.5 * (ppt - 100.0))); // For AosimX, root wt needs to be distributed across layers. This conversion logic is adapted from that used in UpgradeToVersion52 double[] rootWtFraction = new double[layerCount]; double profileDepth = depth[layerCount - 1]; double cumDepth = 0.0; for (int layer = 0; layer < layerCount; layer++) { double fracLayer = Math.Min(1.0, MathUtilities.Divide(profileDepth - cumDepth, thickness[layer], 0.0)); cumDepth += thickness[layer]; rootWtFraction[layer] = fracLayer * Math.Exp(-3.0 * Math.Min(1.0, MathUtilities.Divide(cumDepth, profileDepth, 0.0))); } // get the actuall FOM distribution through layers (adds up to one) double totFOMfraction = MathUtilities.Sum(rootWtFraction); for (int layer = 0; layer < thickness.Length; layer++) { rootWtFraction[layer] /= totFOMfraction; } organicMatter.FOM = MathUtilities.Multiply_Value(rootWtFraction, rootWt); double[] fBiom = { 0.04, 0.04 - 0.03 * (225.0 - 150.0) / (400.0 - 150.0), (400.0 - 300.0) / (450.0 - 300.0) * (0.04 - 0.03 * (350.0 - 150.0) / (400.0 - 150.0)) + (450.0 - 400.0) / (450.0 - 300.0) * 0.01, 0.01, 0.01,0.01, 0.01, 0.01, 0.01, 0.01 }; Array.Resize(ref fBiom, layerCount); double inert_c = 0.95 * ocdrc[4]; double[] fInert = new double[7]; for (int layer = 0; layer < 7; layer++) { fInert[layer] = Math.Min(0.99, inert_c / ocdrc[layer]); } organicMatter.FInert = ConvertLayers(fInert, layerCount); // Not perfect, but should be good enough organicMatter.FBiom = fBiom; organicMatter.FOMCNRatio = 40.0; organicMatter.SoilCNRatio = Enumerable.Repeat(11.0, layerCount).ToArray(); // Is there any good way to estimate this? ISRIC provides no N data newSoil.Children.Add(new CERESSoilTemperature()); newSoil.OnCreated(); soils.Add(new SoilFromDataSource() { DataSource = "ISRIC", Soil = newSoil }); } catch (Exception) { } return(soils); }