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 }); }
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> /// Attach the view to the model. /// </summary> /// <param name="model">The initial water model</param> /// <param name="view">The initial water view</param> /// <param name="explorerPresenter">The parent explorer presenter</param> public void Attach(object model, object view, ExplorerPresenter explorerPresenter) { this.initialWater = model as InitialWater; this.initialWaterView = view as IInitialWaterView; this.explorerPresenter = explorerPresenter as ExplorerPresenter; this.initialWaterView.RelativeToCrops = this.initialWater.RelativeToCrops; this.ConnectViewEvents(); this.PopulateView(); this.explorerPresenter.CommandHistory.ModelChanged += this.OnModelChanged; // Populate the graph. this.graph = Utility.Graph.CreateGraphFromResource("ApsimNG.Resources.InitialWaterGraph.xml"); this.graph.Parent = this.initialWater.Parent; this.graphPresenter = new GraphPresenter(); this.graphPresenter.Attach(this.graph, this.initialWaterView.Graph, this.explorerPresenter); }
/// 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); }