public void TestUnsaturatedFlow() { SoilModel soil = new SoilModel(); APSIM.Shared.Soils.Soil soilProperties = Setup(); APSIMReadySoil.Create(soilProperties); UnsaturatedFlowModel unsaturatedFlow = new UnsaturatedFlowModel(); SetLink(soil, "properties", soilProperties); SetLink(unsaturatedFlow, "soil", soil); unsaturatedFlow.DiffusConst = 88; unsaturatedFlow.DiffusSlope = 35.4; // Profile at DUL. soil.Water = MathUtilities.Multiply(soilProperties.Water.DUL, soilProperties.Water.Thickness); double[] flow = unsaturatedFlow.Values; Assert.IsTrue(MathUtilities.AreEqual(flow, new double[] { 0, 0, 0, 0, 0, 0 })); // Profile at SAT. soil.Water = MathUtilities.Multiply(soilProperties.Water.SAT, soilProperties.Water.Thickness); flow = unsaturatedFlow.Values; Assert.IsTrue(MathUtilities.AreEqual(flow, new double[] { 0, 0, 0, 0, 0, 0 })); // Force some unsaturated flow by reducing the water to 0.8 of SAT. soil.Water = MathUtilities.Multiply_Value(soil.Water, 0.8); flow = unsaturatedFlow.Values; Assert.IsTrue(MathUtilities.AreEqual(flow, new double[] { 0.52148, -0.38359, -0.16771, -0.07481, 0, 0 })); }
public void TestPredictedCrops() { APSIM.Shared.Soils.Soil soil = Setup(); soil.SoilType = "Black vertosol"; APSIMReadySoil.Create(soil); // Make sure that predicted crops have been added. Assert.AreEqual(soil.Water.Crops.Count, 3); Assert.AreEqual(soil.Water.Crops[0].Name, "Wheat"); Assert.AreEqual(soil.Water.Crops[1].Name, "Sorghum"); Assert.AreEqual(soil.Water.Crops[2].Name, "Cotton"); // Change soil type to a grey vertosol and examine the predicted crops. soil.SoilType = "Grey vertosol"; soil.Water.Crops.Clear(); APSIMReadySoil.Create(soil); Assert.AreEqual(soil.Water.Crops.Count, 7); Assert.AreEqual(soil.Water.Crops[0].Name, "Wheat"); Assert.AreEqual(soil.Water.Crops[1].Name, "Sorghum"); Assert.AreEqual(soil.Water.Crops[2].Name, "Cotton"); Assert.AreEqual(soil.Water.Crops[3].Name, "Barley"); Assert.AreEqual(soil.Water.Crops[4].Name, "Chickpea"); Assert.AreEqual(soil.Water.Crops[5].Name, "Fababean"); Assert.AreEqual(soil.Water.Crops[6].Name, "Mungbean"); }
public void TestLayerStructure() { APSIM.Shared.Soils.Soil soil = Setup(); // convert sw from gravimetric to volumetric. APSIMReadySoil.Create(soil); // Make sure the samples have been removed. Assert.AreEqual(soil.Samples.Count, 0); // Check the SW values have been converted and that the bottom two layers are at CLL (0.402) MathUtilities.AreEqual(soil.Water.SW, new double[] { 0.140, 0.289, 0.314, 0.326, 0.402, 0.402 }); // Check the NO3 values haven't been converted and that the bottom layers have default values MathUtilities.AreEqual(soil.Nitrogen.NO3, new double[] { 23, 7, 2, 1, 0.01, 0.01 }); // Check that the OC sample values have been put on top of the SoilOrganicMatter OC values. MathUtilities.AreEqual(soil.SoilOrganicMatter.OC, new double[] { 1.35, 1, 0.5, 0.4, 0.3, 0.2 }); // Make sure the analysis missing value has been replaced with zero. MathUtilities.AreEqual(soil.Analysis.CL, new double[] { 38, 0.0, 500, 490, 500, 500 }); // Make sure that no predicted crops have been added. string[] cropNames = soil.Water.Crops.Select(c => c.Name).ToArray(); Assert.AreEqual(cropNames.Length, 1); }
public void TestWaterTable() { SoilModel soil = new SoilModel(); APSIM.Shared.Soils.Soil soilProperties = Setup(); APSIMReadySoil.Create(soilProperties); WaterTableModel waterTable = new WaterTableModel(); SetLink(soil, "properties", soilProperties); SetLink(waterTable, "soil", soil); double[] DUL = MathUtilities.Multiply(soilProperties.Water.DUL, soilProperties.Water.Thickness); double[] SAT = MathUtilities.Multiply(soilProperties.Water.SAT, soilProperties.Water.Thickness); // Profile at DUL. Essentially water table is below profile. soil.Water = DUL; Assert.AreEqual(waterTable.Value(), 1600); // Put a saturated layer at index 3. soil.Water[3] = SAT[3]; Assert.AreEqual(waterTable.Value(), 700); // Put a saturated layer at index 3 and a drainable layer at index 2. soil.Water[2] = (DUL[2] + SAT[2]) / 2; soil.Water[3] = SAT[3]; Assert.AreEqual(waterTable.Value(), 250); }
/// <summary>Calculate a layered soil water. Units: mm/mm</summary> public double[] SW(Soil soil) { string[] cropNames = soil.Water.Crops.Select(c => c.Name).ToArray(); // Get the correct LL and XF int cropIndex = -1; if (RelativeTo != null) cropIndex = StringUtilities.IndexOfCaseInsensitive(cropNames, RelativeTo); double[] ll; double[] xf = null; double[] PAWCmm; if (cropIndex == -1) { ll = soil.Water.LL15; PAWCmm = PAWC.OfSoilmm(soil); } else { SoilCrop crop = soil.Water.Crops[cropIndex]; ll = crop.LL; xf = crop.XF; PAWCmm = PAWC.OfCropmm(soil, crop); } if (double.IsNaN(DepthWetSoil)) { if (PercentMethod == InitialWater.PercentMethodEnum.FilledFromTop) return SWFilledFromTop(PAWCmm, ll, soil.Water.DUL, xf); else return SWEvenlyDistributed(ll, soil.Water.DUL); } else return SWDepthWetSoil(soil.Water.Thickness, ll, soil.Water.DUL); }
/// <summary>Sets the water thickness.</summary> /// <param name="water">The water.</param> /// <param name="toThickness">To thickness.</param> /// <param name="soil">Soil</param> private static void SetWaterThickness(Water water, double[] toThickness, Soil soil) { if (!MathUtilities.AreEqual(toThickness, water.Thickness)) { water.BD = MapConcentration(water.BD, water.Thickness, toThickness, MathUtilities.LastValue(water.BD)); water.SW = MapSW(water.SW, water.Thickness, toThickness, soil); water.AirDry = MapConcentration(water.AirDry, water.Thickness, toThickness, MathUtilities.LastValue(water.AirDry)); water.LL15 = MapConcentration(water.LL15, water.Thickness, toThickness, MathUtilities.LastValue(water.LL15)); water.DUL = MapConcentration(water.DUL, water.Thickness, toThickness, MathUtilities.LastValue(water.DUL)); water.SAT = MapConcentration(water.SAT, water.Thickness, toThickness, MathUtilities.LastValue(water.SAT)); water.KS = MapConcentration(water.KS, water.Thickness, toThickness, MathUtilities.LastValue(water.KS)); water.Thickness = toThickness; } if (water.Crops != null) { foreach (SoilCrop crop in water.Crops) { if (!MathUtilities.AreEqual(toThickness, crop.Thickness)) { crop.LL = MapConcentration(crop.LL, crop.Thickness, toThickness, MathUtilities.LastValue(crop.LL)); crop.KL = MapConcentration(crop.KL, crop.Thickness, toThickness, MathUtilities.LastValue(crop.KL)); crop.XF = MapConcentration(crop.XF, crop.Thickness, toThickness, MathUtilities.LastValue(crop.XF)); crop.Thickness = toThickness; // Ensure crop LL are between Airdry and DUL. for (int i = 0; i < crop.LL.Length; i++) crop.LL = MathUtilities.Constrain(crop.LL, soil.Water.AirDry, soil.Water.DUL); } } } }
public void TestInitialWater() { APSIM.Shared.Soils.Soil soil = Setup(); soil.Samples[0].SW = null; soil.InitialWater = new InitialWater(); soil.InitialWater.PercentMethod = InitialWater.PercentMethodEnum.FilledFromTop; soil.InitialWater.RelativeTo = "LL15"; soil.InitialWater.FractionFull = 0.5; APSIMReadySoil.Create(soil); // Make sure the initial water has been removed. Assert.IsNull(soil.InitialWater); // Check the SW values. MathUtilities.AreEqual(soil.Water.SW, new double[] { 0.365, 0.461, 0.43, 0.281, 0.261, 0.261 }); // check evenly distributed method. soil.InitialWater = new InitialWater(); soil.InitialWater.PercentMethod = InitialWater.PercentMethodEnum.EvenlyDistributed; soil.InitialWater.RelativeTo = "LL15"; soil.InitialWater.FractionFull = 0.5; APSIMReadySoil.Create(soil); MathUtilities.AreEqual(soil.Water.SW, new double[] { 0.318, 0.364, 0.345, 0.336, 0.332, 0.333 }); }
public void TestLateralFlow() { APSIM.Shared.Soils.Soil soilProperties = Setup(); soilProperties.Samples.Clear(); // Setup our objects with links. SoilModel soil = new SoilModel(); LateralFlowModel lateralFlow = new LateralFlowModel(); SetLink(soil, "properties", soilProperties); SetLink(soil, "lateralFlowModel", lateralFlow); SetLink(lateralFlow, "soil", soil); // Set initial water to full. soilProperties.InitialWater = new InitialWater(); soilProperties.InitialWater.FractionFull = 1.0; APSIMReadySoil.Create(soilProperties); soil.Water = MathUtilities.Multiply(soilProperties.Water.SW, soilProperties.Water.Thickness); // No inflow, so there should be no outflow. lateralFlow.InFlow = null; Assert.AreEqual(lateralFlow.Values.Length, 0); // Profile is full so adding in flow will produce out flow. lateralFlow.InFlow = new double[] { 9, 9, 9, 9, 9, 9 }; lateralFlow.KLAT = MathUtilities.CreateArrayOfValues(8.0, soilProperties.Water.Thickness.Length); Assert.IsTrue(MathUtilities.AreEqual(lateralFlow.Values, new double[] { 0.45999, 0.80498, 0.80498, 0.80498, 0.80498, 0.80498 })); // Set initial water to empty. Out flow should be zeros. soilProperties.InitialWater = new InitialWater(); soilProperties.InitialWater.FractionFull = 0.0; APSIMReadySoil.Create(soilProperties); soil.Water = MathUtilities.Multiply(soilProperties.Water.SW, soilProperties.Water.Thickness); Assert.IsTrue(MathUtilities.AreEqual(lateralFlow.Values, new double[] { 0, 0, 0, 0, 0, 0 })); }
/// <summary>Creates an apsim ready soil.</summary> /// <param name="soil">The soil.</param> /// <returns>A newly created soil ready to be run in APSIM.</returns> public static Soil Create(Soil soil) { Unit.Convert(soil); RemoveInitialWater(soil); LayerStructure.Standardise(soil); Defaults.FillInMissingValues(soil); RemoveSamples(soil); return soil; }
public void CheckUserLayerStructure() { APSIM.Shared.Soils.Soil soil = Setup(); soil.LayerStructure = new LayerStructure(); soil.LayerStructure.Thickness = new double[] { 200, 200, 200, 200 }; APSIMReadySoil.Create(soil); MathUtilities.AreEqual(soil.Water.Thickness, new double[] { 200, 200, 200, 200 }); }
/// <summary>Standardise the specified soil with a uniform thickness.</summary> /// <param name="soil">The soil.</param> /// <returns>A standardised soil.</returns> public static void Standardise(Soil soil) { double[] toThickness = soil.Water.Thickness; if (soil.LayerStructure != null) toThickness = soil.LayerStructure.Thickness; SetWaterThickness(soil.Water, toThickness, soil); SetSoilWaterThickness(soil.SoilWater, toThickness); SetAnalysisThickness(soil.Analysis, toThickness); SetSoilOrganicMatterThickness(soil.SoilOrganicMatter, toThickness); SetPhosphorus(soil.Phosphorus, toThickness); SetSWIM(soil.Swim, toThickness); SetSoilTemperature(soil.SoilTemperature, toThickness); foreach (Sample sample in soil.Samples) SetSampleThickness(sample, toThickness, soil); }
/// <summary>Sets the sample thickness.</summary> /// <param name="sample">The sample.</param> /// <param name="thickness">The thickness to change the sample to.</param> /// <param name="soil">The soil</param> private static void SetSampleThickness(Sample sample, double[] thickness, Soil soil) { if (!MathUtilities.AreEqual(thickness, sample.Thickness)) { sample.Name = sample.Name; sample.Date = sample.Date; if (sample.SW != null) { sample.SW = MapSW(sample.SW, sample.Thickness, thickness, soil); } if (sample.NH4 != null) { sample.NH4 = MapConcentration(sample.NH4, sample.Thickness, thickness, 0.01); } if (sample.NO3 != null) { sample.NO3 = MapConcentration(sample.NO3, sample.Thickness, thickness, 0.01); } // The elements below will be overlaid over other arrays of values so we want // to have missing values (double.NaN) used at the bottom of the profile. if (sample.CL != null) { sample.CL = MapConcentration(sample.CL, sample.Thickness, thickness, double.NaN, allowMissingValues: true); } if (sample.EC != null) { sample.EC = MapConcentration(sample.EC, sample.Thickness, thickness, double.NaN, allowMissingValues: true); } if (sample.ESP != null) { sample.ESP = MapConcentration(sample.ESP, sample.Thickness, thickness, double.NaN, allowMissingValues: true); } if (sample.OC != null) { sample.OC = MapConcentration(sample.OC, sample.Thickness, thickness, double.NaN, allowMissingValues: true); } if (sample.PH != null) { sample.PH = MapConcentration(sample.PH, sample.Thickness, thickness, double.NaN, allowMissingValues: true); } sample.Thickness = thickness; } }
/// <summary>Write soil to XML</summary> /// <param name="soil">The soil.</param> /// <returns></returns> public static string ToXML(Soil soil) { XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", ""); XmlSerializer x = new XmlSerializer(typeof(Soil)); StringWriter Out = new StringWriter(); x.Serialize(Out, soil, ns); string st = Out.ToString(); if (st.Length > 5 && st.Substring(0, 5) == "<?xml") { // remove the first line: <?xml version="1.0"?>/n int posEol = st.IndexOf("\n"); if (posEol != -1) return st.Substring(posEol + 1); } return st; }
/// <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) { for (int i = 0; i < crop.Thickness.Length; i++) { if (crop.LL != null && double.IsNaN(crop.LL[i])) { crop.LL[i] = soil.Water.LL15[i]; } if (crop.KL != null && double.IsNaN(crop.KL[i])) { crop.KL[i] = 0; } if (crop.XF != null && double.IsNaN(crop.XF[i])) { crop.XF[i] = 0; } } }
/// <summary>Test setup routine. Returns a soil properties that can be used for testing.</summary> public static APSIM.Shared.Soils.Soil Setup() { APSIM.Shared.Soils.Soil soil = new APSIM.Shared.Soils.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 }; soil.Water.SAT = new double[] { 0.400, 0.481, 0.45, 0.432, 0.422, 0.424 }; // 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 a layered soil water. Units: mm/mm</summary> public double[] SW(Soil soil) { string[] cropNames = soil.Water.Crops.Select(c => c.Name).ToArray(); // Get the correct LL and XF int cropIndex = -1; if (RelativeTo != null) { cropIndex = StringUtilities.IndexOfCaseInsensitive(cropNames, RelativeTo); } double[] ll; double[] xf = null; double[] PAWCmm; if (cropIndex == -1) { ll = soil.Water.LL15; PAWCmm = PAWC.OfSoilmm(soil); } else { SoilCrop crop = soil.Water.Crops[cropIndex]; ll = crop.LL; xf = crop.XF; PAWCmm = PAWC.OfCropmm(soil, crop); } if (double.IsNaN(DepthWetSoil)) { if (PercentMethod == InitialWater.PercentMethodEnum.FilledFromTop) { return(SWFilledFromTop(PAWCmm, ll, soil.Water.DUL, xf)); } else { return(SWEvenlyDistributed(ll, soil.Water.DUL)); } } else { return(SWDepthWetSoil(soil.Water.Thickness, ll, soil.Water.DUL)); } }
/// <summary>Test setup routine. Returns a soil properties that can be used for testing.</summary> public static APSIM.Shared.Soils.Soil Setup() { APSIM.Shared.Soils.Soil soil = new APSIM.Shared.Soils.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 }; soil.Water.SAT = new double[] { 0.400, 0.481, 0.45, 0.432, 0.422, 0.424 }; // 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>Fills in missing values where possible.</summary> /// <param name="soil">The soil.</param> public static void FillInMissingValues(Soil soil) { AddPredictedCrops(soil); CheckAnalysisForMissingValues(soil); foreach (SoilCrop crop in soil.Water.Crops) { if (crop.XF == null) { crop.XF = MathUtilities.CreateArrayOfValues(1.0, crop.Thickness.Length); crop.XFMetadata = StringUtilities.CreateStringArray("Estimated", crop.Thickness.Length); } if (crop.KL == null) FillInKLForCrop(crop); CheckCropForMissingValues(crop, soil); } foreach (Sample sample in soil.Samples) CheckSampleForMissingValues(sample, soil); }
/// <summary>Standardise the specified soil with a uniform thickness.</summary> /// <param name="soil">The soil.</param> /// <returns>A standardised soil.</returns> public static void Standardise(Soil soil) { double[] toThickness = soil.Water.Thickness; if (soil.LayerStructure != null) { toThickness = soil.LayerStructure.Thickness; } SetWaterThickness(soil.Water, toThickness, soil); SetSoilWaterThickness(soil.SoilWater, toThickness); SetAnalysisThickness(soil.Analysis, toThickness); SetSoilOrganicMatterThickness(soil.SoilOrganicMatter, toThickness); SetPhosphorus(soil.Phosphorus, toThickness); SetSWIM(soil.Swim, toThickness); SetSoilTemperature(soil.SoilTemperature, toThickness); foreach (Sample sample in soil.Samples) { SetSampleThickness(sample, toThickness, soil); } }
/// <summary>Calculates volumetric soil water for the given sample.</summary> /// <param name="sample">The sample.</param> /// <param name="soil">The soil.</param> /// <returns>Volumetric water (mm/mm)</returns> private static double[] SWVolumetric(Sample sample, Soil soil) { if (sample.SWUnits == Sample.SWUnitsEnum.Volumetric || sample.SW == null) { return(sample.SW); } else { // convert the numbers if (sample.SWUnits == Sample.SWUnitsEnum.Gravimetric) { double[] bd = LayerStructure.BDMapped(soil, sample.Thickness); return(MathUtilities.Multiply(sample.SW, bd)); } else { return(MathUtilities.Divide(sample.SW, sample.Thickness)); // from mm to mm/mm } } }
/// <summary>Map soil variables (using BD) from one layer structure to another.</summary> /// <param name="fromValues">The from values.</param> /// <param name="fromThickness">The from thickness.</param> /// <param name="toThickness">To thickness.</param> /// <param name="soil">The soil.</param> /// <param name="defaultValueForBelowProfile">The default value for below profile.</param> /// <returns></returns> private static double[] MapUsingBD(double[] fromValues, double[] fromThickness, double[] toThickness, Soil soil, double defaultValueForBelowProfile) { if (fromValues == null || fromThickness == null) { return(null); } // create an array of values with a dummy bottom layer. List <double> values = new List <double>(); values.AddRange(fromValues); values.Add(defaultValueForBelowProfile); List <double> thickness = new List <double>(); thickness.AddRange(fromThickness); thickness.Add(3000); // convert fromValues to a mass basis double[] BD = BDMapped(soil, fromThickness); for (int Layer = 0; Layer < values.Count; Layer++) { values[Layer] = values[Layer] * BD[Layer] * fromThickness[Layer] / 100; } // change layer structure double[] newValues = MapMass(values.ToArray(), thickness.ToArray(), toThickness); // convert newValues back to original units and return BD = BDMapped(soil, toThickness); for (int Layer = 0; Layer < newValues.Length; Layer++) { newValues[Layer] = newValues[Layer] * 100.0 / BD[Layer] / toThickness[Layer]; } return(newValues); }
/// <summary>Write soil to XML</summary> /// <param name="soil">The soil.</param> /// <returns></returns> public static string ToXML(Soil soil) { XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); ns.Add("", ""); XmlSerializer x = new XmlSerializer(typeof(Soil)); StringWriter Out = new StringWriter(); x.Serialize(Out, soil, ns); string st = Out.ToString(); if (st.Length > 5 && st.Substring(0, 5) == "<?xml") { // remove the first line: <?xml version="1.0"?>/n int posEol = st.IndexOf("\n"); if (posEol != -1) { return(st.Substring(posEol + 1)); } } return(st); }
public void TestSaturatedFlow() { SoilModel soil = new SoilModel(); APSIM.Shared.Soils.Soil soilProperties = Setup(); APSIMReadySoil.Create(soilProperties); SaturatedFlowModel saturatedFlow = new SaturatedFlowModel(); SetLink(soil, "properties", soilProperties); SetLink(saturatedFlow, "soil", soil); saturatedFlow.SWCON = new double[] { 0.3, 0.3, 0.3, 0.3, 0.3, 0.3 }; // Profile at DUL. soil.Water = MathUtilities.Multiply(soilProperties.Water.DUL, soilProperties.Water.Thickness); double[] flux = saturatedFlow.Values; Assert.IsTrue(MathUtilities.AreEqual(flux, new double[] { 0, 0, 0, 0, 0, 0 })); // Profile at SAT. soil.Water = MathUtilities.Multiply(soilProperties.Water.SAT, soilProperties.Water.Thickness); flux = saturatedFlow.Values; Assert.IsTrue(MathUtilities.AreEqual(flux, new double[] { 1.05, 2.85, 4.64999, 6.45, 8.25, 10.05 })); // Use the KS method soilProperties.Water.KS = new double[] { 1000, 300, 20, 100, 100, 100 }; flux = saturatedFlow.Values; Assert.IsTrue(MathUtilities.AreEqual(flux, new double[] { 1.05, 1.8000, 1.8000, 1.8000, 1.8000, 1.8000 })); Assert.AreEqual(saturatedFlow.backedUpSurface, 0); // Use the KS method, water above SAT. soilProperties.Water.KS = new double[] { 1000, 300, 20, 100, 100, 100 }; MathUtilities.AddValue(soil.Water, 10); // add 5 mm of water into each layer. flux = saturatedFlow.Values; Assert.IsTrue(MathUtilities.AreEqual(flux, new double[] { 1.05, 1.8000, 1.8000, 1.8000, 1.8000, 1.8000 })); }
/// <summary> /// Set the soilwat, soiln, surfaceom components /// </summary> /// <param name="defaultPaddock"></param> /// <param name="soilConfig"></param> /// <param name="paddocknode"></param> private void SetSoilComponents(FarmPaddockType defaultPaddock, Soil soilConfig, XmlNode paddocknode) { XmlNode apsimCompNode; XmlNode translatorNode; //set soilwat apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"SoilWat\"]"); initSoilWat(apsimCompNode, soilConfig); if (apsimCompNode != null) { translatorNode = apsimCompNode.ParentNode; initSoilWat_Trans(translatorNode, soilConfig, defaultPaddock.SoilType); } //set soiln apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"SoilN\"]"); initSoilN(apsimCompNode, soilConfig, defaultPaddock); if (apsimCompNode != null) { translatorNode = apsimCompNode.ParentNode; initSoilN_Trans(translatorNode, soilConfig); } //set surfaceOM apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"SurfaceOM\"]"); initSOM(apsimCompNode, soilConfig); if (apsimCompNode != null) { translatorNode = apsimCompNode.ParentNode; initSOM_Trans(translatorNode, soilConfig, defaultPaddock.SoilType); //if there is a paddock with init residue data and new crops in the rotation then set here setSurfaceOMPaddockInits(apsimCompNode, defaultPaddock); } }
/// <summary>Drained upper limit - mapped to the specified layer structure. Units: mm/mm /// </summary> /// <param name="soil">The soil.</param> /// <param name="ToThickness">To thickness.</param> /// <returns></returns> internal static double[] DULMapped(Soil soil, double[] ToThickness) { return MapConcentration(soil.Water.DUL, soil.Water.Thickness, ToThickness, soil.Water.DUL.Last()); }
/// <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="soil">The soil the crop belongs to.</param> private static void SetCropThickness(SoilCrop crop, double[] thickness, Soil soil) { if (!MathUtilities.AreEqual(thickness, crop.Thickness)) { crop.LL = MapConcentration(crop.LL, crop.Thickness, thickness, MathUtilities.LastValue(crop.LL)); crop.KL = MapConcentration(crop.KL, crop.Thickness, thickness, MathUtilities.LastValue(crop.KL)); crop.XF = MapConcentration(crop.XF, crop.Thickness, thickness, MathUtilities.LastValue(crop.XF)); crop.Thickness = thickness; crop.LL = MathUtilities.Constrain(crop.LL, AirDryMapped(soil, thickness), DULMapped(soil, thickness)); } }
/// <summary>Map soil variables (using BD) from one layer structure to another.</summary> /// <param name="fromValues">The from values.</param> /// <param name="fromThickness">The from thickness.</param> /// <param name="toThickness">To thickness.</param> /// <param name="soil">The soil.</param> /// <param name="defaultValueForBelowProfile">The default value for below profile.</param> /// <returns></returns> private static double[] MapUsingBD(double[] fromValues, double[] fromThickness, double[] toThickness, Soil soil, double defaultValueForBelowProfile) { if (fromValues == null || fromThickness == null) return null; // create an array of values with a dummy bottom layer. List<double> values = new List<double>(); values.AddRange(fromValues); values.Add(defaultValueForBelowProfile); List<double> thickness = new List<double>(); thickness.AddRange(fromThickness); thickness.Add(3000); // convert fromValues to a mass basis double[] BD = BDMapped(soil, fromThickness); for (int Layer = 0; Layer < values.Count; Layer++) values[Layer] = values[Layer] * BD[Layer] * fromThickness[Layer] / 100; // change layer structure double[] newValues = MapMass(values.ToArray(), thickness.ToArray(), toThickness); // convert newValues back to original units and return BD = BDMapped(soil, toThickness); for (int Layer = 0; Layer < newValues.Length; Layer++) newValues[Layer] = newValues[Layer] * 100.0 / BD[Layer] / toThickness[Layer]; return newValues; }
/// <summary>Return the plant available water CAPACITY of the soil. Units: mm/mm</summary> /// <param name="soil">The soil to calculate PAWC for.</param> public static double[] OfSoil(Soil soil) { return(PAWCInternal(soil.Water.Thickness, soil.Water.LL15, soil.Water.DUL, null)); }
/// <summary>Return the index of the layer that contains the specified depth.</summary> /// <param name="soil">The soil</param> /// <param name="depth">The depth to search for.</param> /// <returns></returns> static public int FindLayerIndex(Soil soil, double depth) { return(Array.FindIndex(ToCumThickness(soil.Water.Thickness), d => d > depth)); }
private void initSoilN_Trans(XmlNode compNode, Soil aSoil) { TSDMLValue init = GetTypedInit(compNode, "excrete_params"); if (init.count() < 3) { init.setElementCount(3); init.item(1).setValue(0.3); //depositied in camp areas init.item(2).setValue(0.25); //urine that volatizes init.item(3).setValue(0.035); //surface faecal breakdown SetTypedInit(compNode, "excrete_params", init); } //may not need to do this in an already configured simulation init = GetTypedInit(compNode, "published_events"); if (init.count() < 1) { init.setElementCount(2); init.item(1).member("name").setValue(".model.new_solute"); init.item(1).member("connects").setElementCount(1); init.item(1).member("connects").item(1).setValue("..water.model.new_solute"); init.item(2).member("name").setValue(".model.actualresiduedecompositioncalculated"); init.item(2).member("connects").setElementCount(1); init.item(2).member("connects").item(1).setValue("..surfaceom.model.actualresiduedecompositioncalculated"); SetTypedInit(compNode, "published_events", init); } }
/// <summary>Checks the sample for missing values.</summary> /// <param name="sample">The sample.</param> /// <param name="soil">The soil.</param> private static void CheckSampleForMissingValues(Sample sample, Soil soil) { if (!MathUtilities.ValuesInArray(sample.SW)) { sample.SW = null; } if (!MathUtilities.ValuesInArray(sample.NO3)) { sample.NO3 = null; } if (!MathUtilities.ValuesInArray(sample.CL)) { sample.CL = null; } if (!MathUtilities.ValuesInArray(sample.EC)) { sample.EC = null; } if (!MathUtilities.ValuesInArray(sample.ESP)) { sample.ESP = null; } if (!MathUtilities.ValuesInArray(sample.PH)) { sample.PH = null; } if (!MathUtilities.ValuesInArray(sample.OC)) { sample.OC = null; } if (sample.SW != null) { sample.SW = MathUtilities.FixArrayLength(sample.SW, sample.Thickness.Length); } if (sample.NO3 != null) { sample.NO3 = MathUtilities.FixArrayLength(sample.NO3, sample.Thickness.Length); } if (sample.NH4 != null) { sample.NH4 = MathUtilities.FixArrayLength(sample.NH4, sample.Thickness.Length); } if (sample.CL != null) { sample.CL = MathUtilities.FixArrayLength(sample.CL, sample.Thickness.Length); } if (sample.EC != null) { sample.EC = MathUtilities.FixArrayLength(sample.EC, sample.Thickness.Length); } if (sample.ESP != null) { sample.ESP = MathUtilities.FixArrayLength(sample.ESP, sample.Thickness.Length); } if (sample.PH != null) { sample.PH = MathUtilities.FixArrayLength(sample.PH, sample.Thickness.Length); } if (sample.OC != null) { sample.OC = MathUtilities.FixArrayLength(sample.OC, sample.Thickness.Length); } double[] ll15 = LayerStructure.LL15Mapped(soil, sample.Thickness); for (int i = 0; i < sample.Thickness.Length; i++) { if (sample.SW != null && double.IsNaN(sample.SW[i])) { sample.SW[i] = ll15[i]; } if (sample.NO3 != null && double.IsNaN(sample.NO3[i])) { sample.NO3[i] = 1.0; } if (sample.NH4 != null && double.IsNaN(sample.NH4[i])) { sample.NH4[i] = 0.1; } if (sample.CL != null && double.IsNaN(sample.CL[i])) { sample.CL[i] = 0; } if (sample.EC != null && double.IsNaN(sample.EC[i])) { sample.EC[i] = 0; } if (sample.ESP != null && double.IsNaN(sample.ESP[i])) { sample.ESP[i] = 0; } if (sample.PH != null && (double.IsNaN(sample.PH[i]) || sample.PH[i] == 0.0)) { sample.PH[i] = 7.0; } if (sample.OC != null && (double.IsNaN(sample.OC[i]) || sample.OC[i] == 0.0)) { sample.OC[i] = 0.5; } } }
private static string ReplaceSoilMacros(XmlNode SoilNode, string ApsimToSimContents) { // Get rid of nodes under <soil> that are disabled. RemoveDisabledNodes(SoilNode); APSIM.Shared.Soils.Soil mySoil = APSIM.Shared.Soils.SoilUtilities.FromXML(SoilNode.OuterXml); mySoil = APSIM.Shared.Soils.APSIMReadySoil.Create(mySoil); if (mySoil.Name == null) { mySoil.Name = "Soil"; } // Loop through all soil macros. int PosMacro = 0; PosMacro = ApsimToSimContents.IndexOf("[soil.", PosMacro); while (PosMacro != -1) { int PosEndMacro = ApsimToSimContents.IndexOf(']', PosMacro); if (PosEndMacro == -1) { throw new Exception("Invalid soil macro found: " + ApsimToSimContents.Substring(PosMacro)); } // Get macro name e.g. soil.thickness string MacroName = ApsimToSimContents.Substring(PosMacro + 1, PosEndMacro - PosMacro - 1); //if (MacroName.Contains("soil.")) { // remove the soil. prefix from the MacroName. MacroName = MacroName.Substring(MacroName.IndexOf('.') + 1); // Is it a crop macro i.e. wheat ll object Obj = null; if (MacroName.Contains(" ")) { string CropName = MacroName.Substring(0, MacroName.IndexOf(' ')); string VariableName = MacroName.Substring(MacroName.IndexOf(' ') + 1); APSIM.Shared.Soils.SoilCrop crop = mySoil.Water.Crops.Find(c => c.Name.Equals(CropName, StringComparison.InvariantCultureIgnoreCase)); if (crop == null) { throw new Exception("Cannot find soil water information for crop '" + CropName + "'. " + "You likely need to \"Manage Crops\" in the Water node of your soil, and set the water properties of this crop"); } if (VariableName.Equals("ll", StringComparison.CurrentCultureIgnoreCase)) { Obj = crop.LL; } else if (VariableName.Equals("kl", StringComparison.CurrentCultureIgnoreCase)) { Obj = crop.KL; } else if (VariableName.Equals("xf", StringComparison.CurrentCultureIgnoreCase)) { Obj = crop.XF; } } else { Obj = Utility.GetValueOfFieldOrProperty(MacroName, mySoil); } if (Obj != null) { string MacroValue = ""; if (Obj is IList) { foreach (object I in Obj as IList) { if (I is double) { MacroValue += ((double)I).ToString("f3", CultureInfo.CreateSpecificCulture("en-AU")) + " "; } else { MacroValue += I.ToString() + " "; } } } else if (Obj is double) { if (double.IsNaN(Convert.ToDouble(Obj))) { MacroValue = ""; } else { MacroValue = ((Double)Obj).ToString("f3", CultureInfo.CreateSpecificCulture("en-AU")); } } else { MacroValue = Obj.ToString(); } ApsimToSimContents = ApsimToSimContents.Remove(PosMacro, PosEndMacro - PosMacro + 1); ApsimToSimContents = ApsimToSimContents.Insert(PosMacro, MacroValue); } PosMacro = ApsimToSimContents.IndexOf("[soil.", PosMacro + 1); } } return(ApsimToSimContents); }
/// <summary>AirDry - mapped to the specified layer structure. Units: mm/mm /// </summary> /// <param name="soil">The soil.</param> /// <param name="ToThickness">To thickness.</param> /// <returns></returns> private static double[] AirDryMapped(Soil soil, double[] ToThickness) { return(MapConcentration(soil.Water.AirDry, soil.Water.Thickness, ToThickness, soil.Water.AirDry.Last())); }
/// <summary>Checks the sample for missing values.</summary> /// <param name="sample">The sample.</param> /// <param name="soil">The soil.</param> private static void CheckSampleForMissingValues(Sample sample, Soil soil) { if (!MathUtilities.ValuesInArray(sample.SW)) sample.SW = null; if (!MathUtilities.ValuesInArray(sample.NO3)) sample.NO3 = null; if (!MathUtilities.ValuesInArray(sample.CL)) sample.CL = null; if (!MathUtilities.ValuesInArray(sample.EC)) sample.EC = null; if (!MathUtilities.ValuesInArray(sample.ESP)) sample.ESP = null; if (!MathUtilities.ValuesInArray(sample.PH)) sample.PH = null; if (!MathUtilities.ValuesInArray(sample.OC)) sample.OC = null; if (sample.SW != null) sample.SW = MathUtilities.FixArrayLength(sample.SW, sample.Thickness.Length); if (sample.NO3 != null) sample.NO3 = MathUtilities.FixArrayLength(sample.NO3, sample.Thickness.Length); if (sample.NH4 != null) sample.NH4 = MathUtilities.FixArrayLength(sample.NH4, sample.Thickness.Length); if (sample.CL != null) sample.CL = MathUtilities.FixArrayLength(sample.CL, sample.Thickness.Length); if (sample.EC != null) sample.EC = MathUtilities.FixArrayLength(sample.EC, sample.Thickness.Length); if (sample.ESP != null) sample.ESP = MathUtilities.FixArrayLength(sample.ESP, sample.Thickness.Length); if (sample.PH != null) sample.PH = MathUtilities.FixArrayLength(sample.PH, sample.Thickness.Length); if (sample.OC != null) sample.OC = MathUtilities.FixArrayLength(sample.OC, sample.Thickness.Length); double[] ll15 = LayerStructure.LL15Mapped(soil, sample.Thickness); for (int i = 0; i < sample.Thickness.Length; i++) { if (sample.SW != null && double.IsNaN(sample.SW[i])) sample.SW[i] = ll15[i]; if (sample.NO3 != null && double.IsNaN(sample.NO3[i])) sample.NO3[i] = 1.0; if (sample.NH4 != null && double.IsNaN(sample.NH4[i])) sample.NH4[i] = 0.1; if (sample.CL != null && double.IsNaN(sample.CL[i])) sample.CL[i] = 0; if (sample.EC != null && double.IsNaN(sample.EC[i])) sample.EC[i] = 0; if (sample.ESP != null && double.IsNaN(sample.ESP[i])) sample.ESP[i] = 0; if (sample.PH != null && (double.IsNaN(sample.PH[i]) || sample.PH[i] == 0.0)) sample.PH[i] = 7.0; if (sample.OC != null && (double.IsNaN(sample.OC[i]) || sample.OC[i] == 0.0)) sample.OC[i] = 0.5; } }
/// <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) { for (int i = 0; i < crop.Thickness.Length; i++) { if (crop.LL != null && double.IsNaN(crop.LL[i])) crop.LL[i] = soil.Water.LL15[i]; if (crop.KL != null && double.IsNaN(crop.KL[i])) crop.KL[i] = 0; if (crop.XF != null && double.IsNaN(crop.XF[i])) crop.XF[i] = 0; } }
/// <summary>Checks the analysis for missing values.</summary> /// <param name="soil">The soil.</param> private static void CheckAnalysisForMissingValues(Soil soil) { for (int i = 0; i < soil.Analysis.Thickness.Length; i++) { if (soil.Analysis.CL != null && double.IsNaN(soil.Analysis.CL[i])) soil.Analysis.CL[i] = 0; if (soil.Analysis.EC != null && double.IsNaN(soil.Analysis.EC[i])) soil.Analysis.EC[i] = 0; if (soil.Analysis.ESP != null && double.IsNaN(soil.Analysis.ESP[i])) soil.Analysis.ESP[i] = 0; if (soil.Analysis.PH != null && double.IsNaN(soil.Analysis.PH[i])) soil.Analysis.PH[i] = 7; } }
/// <summary>Convert soil units to APSIM standard.</summary> /// <param name="soil">The soil.</param> public static void Convert(Soil soil) { // Convert soil organic matter OC to total % if (soil.SoilOrganicMatter != null) { soil.SoilOrganicMatter.OC = OCTotalPercent(soil.SoilOrganicMatter.OC, soil.SoilOrganicMatter.OCUnits); soil.SoilOrganicMatter.OCUnits = SoilOrganicMatter.OCUnitsEnum.Total; } // Convert nitrogen to ppm. if (soil.Nitrogen != null) { double[] bd = LayerStructure.BDMapped(soil, soil.Nitrogen.Thickness); soil.Nitrogen.NO3 = Nppm(soil.Nitrogen.NO3, soil.Nitrogen.Thickness, soil.Nitrogen.NO3Units, bd); soil.Nitrogen.NO3Units = Nitrogen.NUnitsEnum.ppm; soil.Nitrogen.NH4 = Nppm(soil.Nitrogen.NH4, soil.Nitrogen.Thickness, soil.Nitrogen.NH4Units, bd); soil.Nitrogen.NH4Units = Nitrogen.NUnitsEnum.ppm; } // Convert analysis. if (soil.Analysis != null) { soil.Analysis.PH = PHWater(soil.Analysis.PH, soil.Analysis.PHUnits); soil.Analysis.PHUnits = Analysis.PHUnitsEnum.Water; } // Convert all samples. if (soil.Samples != null) { foreach (Sample sample in soil.Samples) { // Convert sw units to volumetric. if (sample.SW != null) sample.SW = SWVolumetric(sample, soil); sample.SWUnits = Sample.SWUnitsEnum.Volumetric; // Convert no3 units to ppm. if (sample.NO3 != null) { double[] bd = LayerStructure.BDMapped(soil, sample.Thickness); sample.NO3 = Nppm(sample.NO3, sample.Thickness, sample.NO3Units, bd); } sample.NO3Units = Nitrogen.NUnitsEnum.ppm; // Convert nh4 units to ppm. if (sample.NH4 != null) { double[] bd = LayerStructure.BDMapped(soil, sample.Thickness); sample.NH4 = Nppm(sample.NH4, sample.Thickness, sample.NH4Units, bd); } sample.NH4Units = Nitrogen.NUnitsEnum.ppm; // Convert OC to total (%) if (sample.OC != null) sample.OC = OCTotalPercent(sample.OC, sample.OCUnits); sample.OCUnits = SoilOrganicMatter.OCUnitsEnum.Total; // Convert PH to water. if (sample.PH != null) sample.PH = PHWater(sample.PH, sample.PHUnits); sample.PHUnits = Analysis.PHUnitsEnum.Water; } } }
/// <summary> /// Calculate and return a predicted LL from the specified A and B values. /// </summary> /// <param name="soil">The soil.</param> /// <param name="A">a.</param> /// <param name="B">The b.</param> /// <returns></returns> private static double[] PredictedLL(Soil soil, double[] A, double B) { double[] LL15 = LayerStructure.LL15Mapped(soil, PredictedThickness); double[] DUL = LayerStructure.DULMapped(soil, PredictedThickness); double[] LL = new double[PredictedThickness.Length]; for (int i = 0; i != PredictedThickness.Length; i++) { double DULPercent = DUL[i] * 100.0; LL[i] = DULPercent * (A[i] + B * DULPercent); LL[i] /= 100.0; // Bound the predicted LL values. LL[i] = Math.Max(LL[i], LL15[i]); LL[i] = Math.Min(LL[i], DUL[i]); } // make the top 3 layers the same as the top 3 layers of LL15 if (LL.Length >= 3) { LL[0] = LL15[0]; LL[1] = LL15[1]; LL[2] = LL15[2]; } return LL; }
/// <summary>Return the plant available water CAPACITY of the soil. Units: mm</summary> /// <param name="soil">The soil to calculate PAWC for.</param> public static double[] OfSoilmm(Soil soil) { double[] pawc = OfSoil(soil); return(MathUtilities.Multiply(pawc, soil.Water.Thickness)); }
/// <summary>Removes the samples from the specified soil, copying data to other soil components.</summary> /// <param name="soil">The soil.</param> /// <exception cref="System.Exception">Cannot fold a sample into the Water component. Thicknesses are different.</exception> private static void RemoveSamples(Soil soil) { foreach (Sample sample in soil.Samples) { // Make sure the thicknesses are the same. if (!MathUtilities.AreEqual(sample.Thickness, soil.Water.Thickness)) throw new Exception("Cannot fold a sample into the Water component. Thicknesses are different."); if (sample.SW != null) soil.Water.SW = sample.SW; if (sample.NO3 != null) { if (soil.Nitrogen == null) { soil.Nitrogen = new Nitrogen(); soil.Nitrogen.Thickness = sample.Thickness; } soil.Nitrogen.NO3 = sample.NO3; MathUtilities.ReplaceMissingValues(soil.Nitrogen.NO3, 0.01); } if (sample.NH4 != null) { if (soil.Nitrogen == null) { soil.Nitrogen = new Nitrogen(); soil.Nitrogen.Thickness = sample.Thickness; } soil.Nitrogen.NH4 = sample.NH4; MathUtilities.ReplaceMissingValues(soil.Nitrogen.NH4, 0.01); } if (sample.OC != null) { double[] values = soil.SoilOrganicMatter.OC; double[] thickness = soil.SoilOrganicMatter.Thickness; OverlaySampleOnTo(sample.OC, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.OC)); soil.SoilOrganicMatter.OC = values; soil.SoilOrganicMatter.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.SoilOrganicMatter.OC, 0.01); } if (sample.PH != null) { double[] values = soil.Analysis.PH; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.PH, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.PH)); soil.Analysis.PH = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.PH, 7); } if (sample.ESP != null) { double[] values = soil.Analysis.ESP; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.ESP, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.ESP)); soil.Analysis.ESP = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.ESP, 0); } if (sample.EC != null) { double[] values = soil.Analysis.EC; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.EC, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.EC)); soil.Analysis.EC = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.EC, 0); } if (sample.CL != null) { double[] values = soil.Analysis.CL; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.CL, sample.Thickness, ref values, ref thickness, 0.0); soil.Analysis.CL = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.CL, 0); } } soil.Samples.Clear(); }
/// <summary> /// Return a predicted SoilCrop for the specified crop name or null if not found. /// </summary> /// <param name="soil">The soil.</param> /// <param name="CropName">Name of the crop.</param> /// <returns></returns> private static SoilCrop PredictedCrop(Soil soil, string CropName) { double[] A = null; double B = double.NaN; double[] KL = null; if (soil.SoilType == null) { return(null); } if (soil.SoilType.Equals("Black Vertosol", StringComparison.CurrentCultureIgnoreCase)) { if (CropName.Equals("Cotton", StringComparison.CurrentCultureIgnoreCase)) { A = BlackVertosol.CottonA; B = BlackVertosol.CottonB; KL = CottonKL; } else if (CropName.Equals("Sorghum", StringComparison.CurrentCultureIgnoreCase)) { A = BlackVertosol.SorghumA; B = BlackVertosol.SorghumB; KL = SorghumKL; } else if (CropName.Equals("Wheat", StringComparison.CurrentCultureIgnoreCase)) { A = BlackVertosol.WheatA; B = BlackVertosol.WheatB; KL = WheatKL; } } else if (soil.SoilType.Equals("Grey Vertosol", StringComparison.CurrentCultureIgnoreCase)) { if (CropName.Equals("Cotton", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.CottonA; B = GreyVertosol.CottonB; KL = CottonKL; } else if (CropName.Equals("Sorghum", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.SorghumA; B = GreyVertosol.SorghumB; KL = SorghumKL; } else if (CropName.Equals("Wheat", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.WheatA; B = GreyVertosol.WheatB; KL = WheatKL; } else if (CropName.Equals("Barley", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.BarleyA; B = GreyVertosol.BarleyB; KL = BarleyKL; } else if (CropName.Equals("Chickpea", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.ChickpeaA; B = GreyVertosol.ChickpeaB; KL = ChickpeaKL; } else if (CropName.Equals("Fababean", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.FababeanA; B = GreyVertosol.FababeanB; KL = FababeanKL; } else if (CropName.Equals("Mungbean", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.MungbeanA; B = GreyVertosol.MungbeanB; KL = MungbeanKL; } } if (A == null) { return(null); } double[] LL = PredictedLL(soil, A, B); LL = LayerStructure.MapConcentration(LL, PredictedThickness, soil.Water.Thickness, LL.Last()); KL = LayerStructure.MapConcentration(KL, PredictedThickness, soil.Water.Thickness, KL.Last()); double[] XF = LayerStructure.MapConcentration(PredictedXF, PredictedThickness, soil.Water.Thickness, PredictedXF.Last()); string[] Metadata = StringUtilities.CreateStringArray("Estimated", soil.Water.Thickness.Length); return(new SoilCrop() { Name = CropName, Thickness = soil.Water.Thickness, LL = LL, LLMetadata = Metadata, KL = KL, KLMetadata = Metadata, XF = XF, XFMetadata = Metadata }); }
/// <summary>Return the index of the layer that contains the specified depth.</summary> /// <param name="soil">The soil</param> /// <param name="depth">The depth to search for.</param> /// <returns></returns> static public int FindLayerIndex(Soil soil, double depth) { return Array.FindIndex(ToCumThickness(soil.Water.Thickness), d => d > depth); }
public void TestRunoff() { SoilModel soil = new SoilModel(); APSIM.Shared.Soils.Soil soilProperties = Setup(); APSIMReadySoil.Create(soilProperties); MockWeather weather = new MockWeather(); weather.Rain = 100; MockIrrigation irrigation = new MockIrrigation(); irrigation.IrrigationApplied = 0; MockSurfaceOrganicMatter surfaceOrganicMatter = new MockSurfaceOrganicMatter(); surfaceOrganicMatter.Cover = 0.1; CNReductionForCover reductionForCover = new CNReductionForCover(); List <ICanopy> canopies = new List <ICanopy>(); CNReductionForTillage reductionForTillage = new CNReductionForTillage(); RunoffModel runoff = new RunoffModel(); runoff.CN2Bare = 70; // setup links SetLink(soil, "properties", soilProperties); SetLink(soil, "runoffModel", runoff); SetLink(soil, "weather", weather); SetLink(soil, "irrigation", irrigation); SetLink(runoff, "soil", soil); SetLink(runoff, "reductionForCover", reductionForCover); SetLink(runoff, "reductionForTillage", reductionForTillage); SetLink(reductionForCover, "surfaceOrganicMatter", surfaceOrganicMatter); SetLink(reductionForCover, "canopies", canopies); SetLink(reductionForTillage, "weather", weather); // Empty profile. soil.Water = MathUtilities.Multiply(soilProperties.Water.LL15, soilProperties.Water.Thickness); // Profile is empty - should be small amount of runoff. Assert.IsTrue(MathUtilities.FloatsAreEqual(runoff.Value(), 5.60815)); // Full profile - should be a lot more runoff. soil.Water = MathUtilities.Multiply(soilProperties.Water.DUL, soilProperties.Water.Thickness); Assert.IsTrue(MathUtilities.FloatsAreEqual(runoff.Value(), 58.23552)); // Test CN reduction due to canopy. Tests the Curve Number vs Cover graph. // Cover is 10%, reduction is 2.5 surfaceOrganicMatter.Cover = 0.1; Assert.IsTrue(MathUtilities.FloatsAreEqual(reductionForCover.Value(), 2.49999)); // Cover is 80%, reduction is 20 surfaceOrganicMatter.Cover = 0.8; Assert.IsTrue(MathUtilities.FloatsAreEqual(reductionForCover.Value(), 20.0)); // Test Runoff vs Rainfall graph i.e. effect of different curve numbers. surfaceOrganicMatter.Cover = 0.0; runoff.CN2Bare = 60; Assert.IsTrue(MathUtilities.FloatsAreEqual(runoff.Value(), 48.18584)); runoff.CN2Bare = 75; Assert.IsTrue(MathUtilities.FloatsAreEqual(runoff.Value(), 68.16430)); runoff.CN2Bare = 85; Assert.IsTrue(MathUtilities.FloatsAreEqual(runoff.Value(), 81.15006)); }
/// <summary>Removes the initial water.</summary> /// <param name="soil">The soil.</param> private static void RemoveInitialWater(Soil soil) { if (soil.InitialWater != null) { soil.Water.SW = soil.InitialWater.SW(soil); // If any sample also has SW then get rid of it. Initial Water takes // priority. foreach (Sample sample in soil.Samples) sample.SW = null; } soil.InitialWater = null; }
/// <summary>Sets the sample thickness.</summary> /// <param name="sample">The sample.</param> /// <param name="thickness">The thickness to change the sample to.</param> /// <param name="soil">The soil</param> private static void SetSampleThickness(Sample sample, double[] thickness, Soil soil) { if (!MathUtilities.AreEqual(thickness, sample.Thickness)) { sample.Name = sample.Name; sample.Date = sample.Date; if (sample.SW != null) sample.SW = MapSW(sample.SW, sample.Thickness, thickness, soil); if (sample.NH4 != null) sample.NH4 = MapConcentration(sample.NH4, sample.Thickness, thickness, 0.01); if (sample.NO3 != null) sample.NO3 = MapConcentration(sample.NO3, sample.Thickness, thickness, 0.01); // The elements below will be overlaid over other arrays of values so we want // to have missing values (double.NaN) used at the bottom of the profile. if (sample.CL != null) sample.CL = MapConcentration(sample.CL, sample.Thickness, thickness, double.NaN, allowMissingValues:true); if (sample.EC != null) sample.EC = MapConcentration(sample.EC, sample.Thickness, thickness, double.NaN, allowMissingValues: true); if (sample.ESP != null) sample.ESP = MapConcentration(sample.ESP, sample.Thickness, thickness, double.NaN, allowMissingValues: true); if (sample.OC != null) sample.OC = MapConcentration(sample.OC, sample.Thickness, thickness, double.NaN, allowMissingValues: true); if (sample.PH != null) sample.PH = MapConcentration(sample.PH, sample.Thickness, thickness, double.NaN, allowMissingValues: true); sample.Thickness = thickness; } }
/// <summary>Convert soil units to APSIM standard.</summary> /// <param name="soil">The soil.</param> public static void Convert(Soil soil) { // Convert soil organic matter OC to total % if (soil.SoilOrganicMatter != null) { soil.SoilOrganicMatter.OC = OCTotalPercent(soil.SoilOrganicMatter.OC, soil.SoilOrganicMatter.OCUnits); soil.SoilOrganicMatter.OCUnits = SoilOrganicMatter.OCUnitsEnum.Total; } // Convert nitrogen to ppm. if (soil.Nitrogen != null) { double[] bd = LayerStructure.BDMapped(soil, soil.Nitrogen.Thickness); soil.Nitrogen.NO3 = Nppm(soil.Nitrogen.NO3, soil.Nitrogen.Thickness, soil.Nitrogen.NO3Units, bd); soil.Nitrogen.NO3Units = Nitrogen.NUnitsEnum.ppm; soil.Nitrogen.NH4 = Nppm(soil.Nitrogen.NH4, soil.Nitrogen.Thickness, soil.Nitrogen.NH4Units, bd); soil.Nitrogen.NH4Units = Nitrogen.NUnitsEnum.ppm; } // Convert analysis. if (soil.Analysis != null) { soil.Analysis.PH = PHWater(soil.Analysis.PH, soil.Analysis.PHUnits); soil.Analysis.PHUnits = Analysis.PHUnitsEnum.Water; } // Convert all samples. if (soil.Samples != null) { foreach (Sample sample in soil.Samples) { // Convert sw units to volumetric. if (sample.SW != null) { sample.SW = SWVolumetric(sample, soil); } sample.SWUnits = Sample.SWUnitsEnum.Volumetric; // Convert no3 units to ppm. if (sample.NO3 != null) { double[] bd = LayerStructure.BDMapped(soil, sample.Thickness); sample.NO3 = Nppm(sample.NO3, sample.Thickness, sample.NO3Units, bd); } sample.NO3Units = Nitrogen.NUnitsEnum.ppm; // Convert nh4 units to ppm. if (sample.NH4 != null) { double[] bd = LayerStructure.BDMapped(soil, sample.Thickness); sample.NH4 = Nppm(sample.NH4, sample.Thickness, sample.NH4Units, bd); } sample.NH4Units = Nitrogen.NUnitsEnum.ppm; // Convert OC to total (%) if (sample.OC != null) { sample.OC = OCTotalPercent(sample.OC, sample.OCUnits); } sample.OCUnits = SoilOrganicMatter.OCUnitsEnum.Total; // Convert PH to water. if (sample.PH != null) { sample.PH = PHWater(sample.PH, sample.PHUnits); } sample.PHUnits = Analysis.PHUnitsEnum.Water; } } }
/// <summary> /// Sets the soil parameters for the crop types in this paddock /// </summary> /// <param name="soilConfig">Soil crop parameters</param> /// <param name="paddocknode">The paddock node for the paddock</param> /// <param name="maxRootDepth">The maximum root depth for any plant allowed on this soil type. mm</param> /// <returns></returns> private void SetSoilCrops(Soil soilConfig, XmlNode paddocknode, FarmSoilType soilArea) { XmlNode apsimCompNode; CropSpec crop; //set the ll, kl, xf values of the crop apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Wheat\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("wheat", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "wheat", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Barley\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("barley", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "barley", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Canola\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("canola", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "canola", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Oats\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("oats", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "oats", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.ChickPea\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("chickpea", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "chickpea", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.FieldPea\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("fieldpea", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "fieldpea", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Fababean\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("fababean", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "fababean", soilConfig, crop); apsimCompNode = paddocknode.SelectSingleNode("system/component[@class=\"Plant.Lupin\"]"); crop = soilArea.CropRotationList.Find(c => c.Name.Equals("lupin", StringComparison.InvariantCultureIgnoreCase)); setSoilCrop(apsimCompNode, "lupin", soilConfig, crop); //sorghum }
/// <summary> /// Initialise the SOM translator /// </summary> /// <param name="compNode"></param> /// <param name="aSoil"></param> private void initSOM_Trans(XmlNode compNode, Soil aSoil, FarmSoilType farmSoil) { TSDMLValue init = GetTypedInit(compNode, "surfaceom_types"); bool found; // for each crop type in the rotation list there should be a residue item in the translator for (int res = 0; res < farmSoil.CropRotationList.Count; res++) { string cropName = CropPhases.CropFromLanduse(farmSoil.CropRotationList[res].Name); if (CropPhases.IsValidCropName(cropName)) { uint i = 1; found = false; while (!found && (i <= init.count())) { if (init.item(i).asStr() == cropName) found = true; i++; } if (!found) { init.setElementCount(init.count() + 1); init.item(init.count()).setValue(cropName); } } } SetTypedInit(compNode, "surfaceom_types", init); //may not need to do this in an already configured simulation init = GetTypedInit(compNode, "published_events"); if (init.count() < 1) { init.setElementCount(2); init.item(1).member("name").setValue(".model.potentialresiduedecompositioncalculated"); init.item(1).member("connects").setElementCount(1); init.item(1).member("connects").item(1).setValue("..nitrogen.model.potentialresiduedecompositioncalculated"); init.item(2).member("name").setValue(".model.incorpfompool"); init.item(2).member("connects").setElementCount(1); init.item(2).member("connects").item(1).setValue("..nitrogen.model.incorpfompool"); SetTypedInit(compNode, "published_events", init); } }
/// <summary>Map soil water from one layer structure to another.</summary> /// <param name="fromValues">The from values.</param> /// <param name="fromThickness">The from thickness.</param> /// <param name="toThickness">To thickness.</param> /// <param name="soil">The soil.</param> /// <returns></returns> private static double[] MapSW(double[] fromValues, double[] fromThickness, double[] toThickness, Soil soil) { if (fromValues == null || fromThickness == null) return null; // convert from values to a mass basis with a dummy bottom layer. List<double> values = new List<double>(); values.AddRange(fromValues); values.Add(MathUtilities.LastValue(fromValues) * 0.8); values.Add(MathUtilities.LastValue(fromValues) * 0.4); values.Add(0.0); List<double> thickness = new List<double>(); thickness.AddRange(fromThickness); thickness.Add(MathUtilities.LastValue(fromThickness)); thickness.Add(MathUtilities.LastValue(fromThickness)); thickness.Add(3000); // Get the first crop ll or ll15. double[] LowerBound; if (soil.Water.Crops.Count > 0) LowerBound = LLMapped(soil.Water.Crops[0], thickness.ToArray()); else LowerBound = LL15Mapped(soil, thickness.ToArray()); if (LowerBound == null) throw new Exception("Cannot find crop lower limit or LL15 in soil"); // Make sure all SW values below LastIndex don't go below CLL. int bottomLayer = fromThickness.Length - 1; for (int i = bottomLayer + 1; i < thickness.Count; i++) values[i] = Math.Max(values[i], LowerBound[i]); double[] massValues = MathUtilities.Multiply(values.ToArray(), thickness.ToArray()); // Convert mass back to concentration and return double[] newValues = MathUtilities.Divide(MapMass(massValues, thickness.ToArray(), toThickness), toThickness); return newValues; }
public void TestEvaporation() { MockSoil soil = new MockSoil(); APSIM.Shared.Soils.Soil soilProperties = Setup(); APSIMReadySoil.Create(soilProperties); MockClock clock = new MockClock(); clock.Today = new DateTime(2015, 6, 1); MockWeather weather = new MockWeather(); weather.MaxT = 30; weather.MinT = 10; weather.Rain = 100; weather.Radn = 25; MockSurfaceOrganicMatter surfaceOrganicMatter = new MockSurfaceOrganicMatter(); surfaceOrganicMatter.Cover = 0.8; List <ICanopy> canopies = new List <ICanopy>(); EvaporationModel evaporation = new EvaporationModel(); SetLink(soil, "properties", soilProperties); SetLink(evaporation, "soil", soil); SetLink(evaporation, "clock", clock); SetLink(evaporation, "weather", weather); SetLink(evaporation, "canopies", canopies); SetLink(evaporation, "surfaceOrganicMatter", surfaceOrganicMatter); // Empty profile. soil.Water = MathUtilities.Multiply(soilProperties.Water.LL15, soilProperties.Water.Thickness); evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 3.00359)); soil.Infiltration = 0; evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 2.20072)); soil.Infiltration = 0; evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 1.57064)); soil.Infiltration = 0; evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 0.96006)); soil.Infiltration = 0; evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 0.75946)); soil.Infiltration = 0; evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 0.64851)); soil.Infiltration = 100; evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 3.00359)); soil.Infiltration = 0; evaporation.Calculate(); Assert.IsTrue(MathUtilities.FloatsAreEqual(evaporation.Es, 2.20072)); }
/// <summary>AirDry - mapped to the specified layer structure. Units: mm/mm /// </summary> /// <param name="soil">The soil.</param> /// <param name="ToThickness">To thickness.</param> /// <returns></returns> private static double[] AirDryMapped(Soil soil, double[] ToThickness) { return MapConcentration(soil.Water.AirDry, soil.Water.Thickness, ToThickness, soil.Water.AirDry.Last()); }
/// <summary>Sets the soil.</summary> /// <param name="soil">The soil.</param> public void SetSoil(Soil soil) { XmlDocument soilDoc = new XmlDocument(); soilDoc.LoadXml(SoilUtilities.ToXML(soil)); XmlNode paddockNode = XmlUtilities.Find(simulationXML, "Paddock"); paddockNode.AppendChild(paddockNode.OwnerDocument.ImportNode(soilDoc.DocumentElement, true)); }
/// <summary>Removes the samples from the specified soil, copying data to other soil components.</summary> /// <param name="soil">The soil.</param> /// <exception cref="System.Exception">Cannot fold a sample into the Water component. Thicknesses are different.</exception> private static void RemoveSamples(Soil soil) { foreach (Sample sample in soil.Samples) { // Make sure the thicknesses are the same. if (!MathUtilities.AreEqual(sample.Thickness, soil.Water.Thickness)) { throw new Exception("Cannot fold a sample into the Water component. Thicknesses are different."); } if (sample.SW != null) { soil.Water.SW = sample.SW; } if (sample.NO3 != null) { if (soil.Nitrogen == null) { soil.Nitrogen = new Nitrogen(); soil.Nitrogen.Thickness = sample.Thickness; } soil.Nitrogen.NO3 = sample.NO3; MathUtilities.ReplaceMissingValues(soil.Nitrogen.NO3, 0.01); } if (sample.NH4 != null) { if (soil.Nitrogen == null) { soil.Nitrogen = new Nitrogen(); soil.Nitrogen.Thickness = sample.Thickness; } soil.Nitrogen.NH4 = sample.NH4; MathUtilities.ReplaceMissingValues(soil.Nitrogen.NH4, 0.01); } if (sample.OC != null) { double[] values = soil.SoilOrganicMatter.OC; double[] thickness = soil.SoilOrganicMatter.Thickness; OverlaySampleOnTo(sample.OC, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.OC)); soil.SoilOrganicMatter.OC = values; soil.SoilOrganicMatter.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.SoilOrganicMatter.OC, 0.01); } if (sample.PH != null) { double[] values = soil.Analysis.PH; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.PH, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.PH)); soil.Analysis.PH = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.PH, 7); } if (sample.ESP != null) { double[] values = soil.Analysis.ESP; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.ESP, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.ESP)); soil.Analysis.ESP = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.ESP, 0); } if (sample.EC != null) { double[] values = soil.Analysis.EC; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.EC, sample.Thickness, ref values, ref thickness, MathUtilities.LastValue(sample.EC)); soil.Analysis.EC = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.EC, 0); } if (sample.CL != null) { double[] values = soil.Analysis.CL; double[] thickness = soil.Analysis.Thickness; OverlaySampleOnTo(sample.CL, sample.Thickness, ref values, ref thickness, 0.0); soil.Analysis.CL = values; soil.Analysis.Thickness = thickness; MathUtilities.ReplaceMissingValues(soil.Analysis.CL, 0); } } soil.Samples.Clear(); }
/// <summary>Calculates volumetric soil water for the given sample.</summary> /// <param name="sample">The sample.</param> /// <param name="soil">The soil.</param> /// <returns>Volumetric water (mm/mm)</returns> private static double[] SWVolumetric(Sample sample, Soil soil) { if (sample.SWUnits == Sample.SWUnitsEnum.Volumetric || sample.SW == null) return sample.SW; else { // convert the numbers if (sample.SWUnits == Sample.SWUnitsEnum.Gravimetric) { double[] bd = LayerStructure.BDMapped(soil, sample.Thickness); return MathUtilities.Multiply(sample.SW, bd); } else return MathUtilities.Divide(sample.SW, sample.Thickness); // from mm to mm/mm } }
/// <summary> /// Return a list of predicted crop names or an empty string[] if none found. /// </summary> /// <param name="soil">The soil.</param> /// <returns></returns> private static void AddPredictedCrops(Soil soil) { if (soil.SoilType != null) { string[] predictedCropNames = null; if (soil.SoilType.Equals("Black Vertosol", StringComparison.CurrentCultureIgnoreCase)) predictedCropNames = BlackVertosolCropList; else if (soil.SoilType.Equals("Grey Vertosol", StringComparison.CurrentCultureIgnoreCase)) predictedCropNames = GreyVertosolCropList; if (predictedCropNames != null) { foreach (string cropName in predictedCropNames) { // if a crop parameterisation already exists for this crop then don't add a predicted one. if (soil.Water.Crops.Find(c => c.Name.Equals(cropName, StringComparison.InvariantCultureIgnoreCase)) == null) soil.Water.Crops.Add(PredictedCrop(soil, cropName)); } } } }
/// <summary>Return the plant available water CAPACITY for the specified crop. Units: mm</summary> /// <param name="soil">The soil to calculate PAWC for.</param> /// <param name="crop">The crop.</param> /// <returns></returns> public static double[] OfCropmm(Soil soil, SoilCrop crop) { double[] pawc = PAWC.OfCrop(soil, crop); return(MathUtilities.Multiply(pawc, soil.Water.Thickness)); }
/// <summary>Map soil water from one layer structure to another.</summary> /// <param name="fromValues">The from values.</param> /// <param name="fromThickness">The from thickness.</param> /// <param name="toThickness">To thickness.</param> /// <param name="soil">The soil.</param> /// <returns></returns> private static double[] MapSW(double[] fromValues, double[] fromThickness, double[] toThickness, Soil soil) { if (fromValues == null || fromThickness == null) { return(null); } // convert from values to a mass basis with a dummy bottom layer. List <double> values = new List <double>(); values.AddRange(fromValues); values.Add(MathUtilities.LastValue(fromValues) * 0.8); values.Add(MathUtilities.LastValue(fromValues) * 0.4); values.Add(0.0); List <double> thickness = new List <double>(); thickness.AddRange(fromThickness); thickness.Add(MathUtilities.LastValue(fromThickness)); thickness.Add(MathUtilities.LastValue(fromThickness)); thickness.Add(3000); // Get the first crop ll or ll15. double[] LowerBound; if (soil.Water.Crops.Count > 0) { LowerBound = LLMapped(soil.Water.Crops[0], thickness.ToArray()); } else { LowerBound = LL15Mapped(soil, thickness.ToArray()); } if (LowerBound == null) { throw new Exception("Cannot find crop lower limit or LL15 in soil"); } // Make sure all SW values below LastIndex don't go below CLL. int bottomLayer = fromThickness.Length - 1; for (int i = bottomLayer + 1; i < thickness.Count; i++) { values[i] = Math.Max(values[i], LowerBound[i]); } double[] massValues = MathUtilities.Multiply(values.ToArray(), thickness.ToArray()); // Convert mass back to concentration and return double[] newValues = MathUtilities.Divide(MapMass(massValues, thickness.ToArray(), toThickness), toThickness); return(newValues); }
/// <summary> /// Return a predicted SoilCrop for the specified crop name or null if not found. /// </summary> /// <param name="soil">The soil.</param> /// <param name="CropName">Name of the crop.</param> /// <returns></returns> private static SoilCrop PredictedCrop(Soil soil, string CropName) { double[] A = null; double B = double.NaN; double[] KL = null; if (soil.SoilType == null) return null; if (soil.SoilType.Equals("Black Vertosol", StringComparison.CurrentCultureIgnoreCase)) { if (CropName.Equals("Cotton", StringComparison.CurrentCultureIgnoreCase)) { A = BlackVertosol.CottonA; B = BlackVertosol.CottonB; KL = CottonKL; } else if (CropName.Equals("Sorghum", StringComparison.CurrentCultureIgnoreCase)) { A = BlackVertosol.SorghumA; B = BlackVertosol.SorghumB; KL = SorghumKL; } else if (CropName.Equals("Wheat", StringComparison.CurrentCultureIgnoreCase)) { A = BlackVertosol.WheatA; B = BlackVertosol.WheatB; KL = WheatKL; } } else if (soil.SoilType.Equals("Grey Vertosol", StringComparison.CurrentCultureIgnoreCase)) { if (CropName.Equals("Cotton", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.CottonA; B = GreyVertosol.CottonB; KL = CottonKL; } else if (CropName.Equals("Sorghum", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.SorghumA; B = GreyVertosol.SorghumB; KL = SorghumKL; } else if (CropName.Equals("Wheat", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.WheatA; B = GreyVertosol.WheatB; KL = WheatKL; } else if (CropName.Equals("Barley", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.BarleyA; B = GreyVertosol.BarleyB; KL = BarleyKL; } else if (CropName.Equals("Chickpea", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.ChickpeaA; B = GreyVertosol.ChickpeaB; KL = ChickpeaKL; } else if (CropName.Equals("Fababean", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.FababeanA; B = GreyVertosol.FababeanB; KL = FababeanKL; } else if (CropName.Equals("Mungbean", StringComparison.CurrentCultureIgnoreCase)) { A = GreyVertosol.MungbeanA; B = GreyVertosol.MungbeanB; KL = MungbeanKL; } } if (A == null) return null; double[] LL = PredictedLL(soil, A, B); LL = LayerStructure.MapConcentration(LL, PredictedThickness, soil.Water.Thickness, LL.Last()); KL = LayerStructure.MapConcentration(KL, PredictedThickness, soil.Water.Thickness, KL.Last()); double[] XF = LayerStructure.MapConcentration(PredictedXF, PredictedThickness, soil.Water.Thickness, PredictedXF.Last()); string[] Metadata = StringUtilities.CreateStringArray("Estimated", soil.Water.Thickness.Length); return new SoilCrop() { Name = CropName, Thickness = soil.Water.Thickness, LL = LL, LLMetadata = Metadata, KL = KL, KLMetadata = Metadata, XF = XF, XFMetadata = Metadata }; }
/// <summary>Drained upper limit - mapped to the specified layer structure. Units: mm/mm /// </summary> /// <param name="soil">The soil.</param> /// <param name="ToThickness">To thickness.</param> /// <returns></returns> internal static double[] DULMapped(Soil soil, double[] ToThickness) { return(MapConcentration(soil.Water.DUL, soil.Water.Thickness, ToThickness, soil.Water.DUL.Last())); }
/// <summary> /// Initialise the Soil water translator /// </summary> /// <param name="compNode"></param> /// <param name="aSoil"></param> private void initSoilWat_Trans(XmlNode compNode, Soil aSoil, FarmSoilType farmSoil) { TSDMLValue init = GetTypedInit(compNode, "psd"); init.member("sand").setElementCount((uint)aSoil.SoilWater.Thickness.Length); init.member("clay").setElementCount((uint)aSoil.SoilWater.Thickness.Length); double value; for (uint x = 0; x <= aSoil.SoilWater.Thickness.Length - 1; x++) { if ((aSoil.Analysis.ParticleSizeSand != null) && (aSoil.Analysis.ParticleSizeSand.Length > x)) { value = aSoil.Analysis.ParticleSizeSand[x]; if (value.Equals(double.NaN)) value = 0.0; // probably should be interpolated from any existing values init.member("sand").item(x + 1).setValue(value * 0.01); value = aSoil.Analysis.ParticleSizeClay[x]; if (value.Equals(double.NaN)) value = 0.0; init.member("clay").item(x + 1).setValue(value * 0.01); } else { // ## not suitable but it will allow the simulation to run init.member("sand").item(x + 1).setValue(0.0); init.member("clay").item(x + 1).setValue(0.0); } } SetTypedInit(compNode, "psd", init); // configure the published events for this translator so each crop component in the rotation is supported init = GetTypedInit(compNode, "published_events"); // if not the correct number of published events if (init.count() < 2) { init.setElementCount(2); init.item(1).member("name").setValue(".model.new_profile"); init.item(1).member("connects").setElementCount(1); init.item(1).member("connects").item(1).setValue("..nitrogen.model.new_profile"); init.item(2).member("name").setValue(".model.nitrogenchanged"); init.item(2).member("connects").setElementCount(1); init.item(2).member("connects").item(1).setValue("..nitrogen.model.nitrogenchanged"); } // now ensure that the connections go to each crop component // there should be a connection item for every plant component in the paddock TTypedValue connects = null; uint idx = 1; // find the correct item in the published events array while ((connects == null) && idx <= 2) { if (init.item(idx).member("name").asStr() == ".model.new_profile") connects = init.item(idx).member("connects"); idx++; } string connectionName; // for each item in the crop rotation list for (int crop = 0; crop < farmSoil.CropRotationList.Count; crop++) { string landuse = farmSoil.CropRotationList[crop].Name; string cropName = CropPhases.CropFromLanduse(landuse); if (CropPhases.IsValidCropName(cropName)) { idx = 1; bool found = false; while (!found && (idx <= connects.count())) { connectionName = connects.item(idx).asStr().ToLower(); if (connectionName.Contains(cropName.ToLower())) found = true; idx++; } //if not found int the connections then add it to the list if (!found) { connects.setElementCount(connects.count() + 1); connects.item(connects.count()).setValue(".." + cropName + ".model.new_profile"); } } } SetTypedInit(compNode, "published_events", init); }