/// <summary>Perform the movement of water.</summary> public void Calculate() { // Lateral flow does not move solutes. We should add this feature one day. if (InFlow != null) { if (OutFlow.Length != InFlow.Length) { OutFlow = new double[InFlow.Length]; } double[] SW = MathUtilities.Add(soil.Water, InFlow); double[] DUL = MathUtilities.Multiply(soilPhysical.DUL, soilPhysical.Thickness); double[] SAT = MathUtilities.Multiply(soilPhysical.SAT, soilPhysical.Thickness); for (int layer = 0; layer < soilPhysical.Thickness.Length; layer++) { // Calculate depth of water table (m) double depthWaterTable = soilPhysical.Thickness[layer] * MathUtilities.Divide((SW[layer] - DUL[layer]), (SAT[layer] - DUL[layer]), 0.0); depthWaterTable = Math.Max(0.0, depthWaterTable); // water table depth in layer must be +ve // Calculate out flow (mm) double i, j; i = soil.KLAT[layer] * depthWaterTable * (soil.DischargeWidth / UnitConversion.mm2m) * soil.Slope; j = (soil.CatchmentArea * UnitConversion.sm2smm) * (Math.Pow((1.0 + Math.Pow(soil.Slope, 2)), 0.5)); OutFlow[layer] = MathUtilities.Divide(i, j, 0.0); // Bound out flow to max flow double max_flow = Math.Max(0.0, (SW[layer] - DUL[layer])); OutFlow[layer] = MathUtilities.Bound(OutFlow[layer], 0.0, max_flow); } } }
/// <summary> /// Scale the water and n values if the total uptake exceeds the amounts available. /// </summary> /// <param name="zones">List of zones to check.</param> /// <param name="uptakes">List of all potential uptakes</param> private static void ScaleWaterAndNIfNecessary(List <ZoneWaterAndN> zones, List <ZoneWaterAndN> uptakes) { foreach (ZoneWaterAndN uniqueZone in zones) { double[] totalWaterUptake = new double[uniqueZone.Water.Length]; double[] totalNO3Uptake = new double[uniqueZone.Water.Length]; double[] totalNH4Uptake = new double[uniqueZone.Water.Length]; foreach (ZoneWaterAndN zone in uptakes) { if (zone.Zone == uniqueZone.Zone) { totalWaterUptake = MathUtilities.Add(totalWaterUptake, zone.Water); totalNO3Uptake = MathUtilities.Add(totalNO3Uptake, zone.NO3N); totalNH4Uptake = MathUtilities.Add(totalNH4Uptake, zone.NH4N); } } for (int i = 0; i < uniqueZone.Water.Length; i++) { if (uniqueZone.Water[i] < totalWaterUptake[i] && totalWaterUptake[i] > 0) { // Scale water double scale = uniqueZone.Water[i] / totalWaterUptake[i]; foreach (ZoneWaterAndN zone in uptakes) { if (zone.Zone == uniqueZone.Zone) { zone.Water[i] *= scale; } } } if (uniqueZone.NO3N[i] < totalNO3Uptake[i] && totalNO3Uptake[i] > 0) { // Scale NO3 double scale = uniqueZone.NO3N[i] / totalNO3Uptake[i]; foreach (ZoneWaterAndN zone in uptakes) { if (zone.Zone == uniqueZone.Zone) { zone.NO3N[i] *= scale; } } } if (uniqueZone.NH4N[i] < totalNH4Uptake[i] && totalNH4Uptake[i] > 0) { // Scale NH4 double scale = uniqueZone.NH4N[i] / totalNH4Uptake[i]; foreach (ZoneWaterAndN zone in uptakes) { if (zone.Zone == uniqueZone.Zone) { zone.NH4N[i] *= scale; } } } } } }
/// <summary> /// Set the n uptake for today /// </summary> public void SetActualNitrogenUptakes(List <ZoneWaterAndN> info) { NO3Uptake = info[0].NO3N; NH4Uptake = info[0].NH4N; NitrogenUptake = MathUtilities.Add(NO3Uptake, NH4Uptake); NO3.SetKgHa(SoluteSetterType.Plant, MathUtilities.Subtract(NO3.kgha, NO3Uptake)); NH4.SetKgHa(SoluteSetterType.Plant, MathUtilities.Subtract(NH4.kgha, NH4Uptake)); }
public void FailingTest() { int a = 2; int b = 2; int expectedSum = 5; int actualSum = MathUtilities.Add(a, b); Assert.AreEqual(expectedSum, actualSum); }
/// <summary>Calculate Nitrogen UptakeEstimates</summary> public List <ZoneWaterAndN> GetUptakeEstimates(SoilState soilstate, IArbitration[] Organs) { var N = Arbitrator.N; double NSupply = 0;//NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. for (int i = 0; i < Organs.Count(); i++) { N.UptakeSupply[i] = 0; } List <ZoneWaterAndN> zones = new List <ZoneWaterAndN>(); foreach (ZoneWaterAndN zone in soilstate.Zones) { ZoneWaterAndN UptakeDemands = new ZoneWaterAndN(zone); UptakeDemands.NO3N = new double[zone.NO3N.Length]; UptakeDemands.NH4N = new double[zone.NH4N.Length]; UptakeDemands.Water = new double[UptakeDemands.NO3N.Length]; //Get Nuptake supply from each organ and set the PotentialUptake parameters that are passed to the soil arbitrator for (int i = 0; i < Organs.Count(); i++) { if (Organs[i] is IWaterNitrogenUptake) { double[] organNO3Supply = new double[zone.NO3N.Length]; double[] organNH4Supply = new double[zone.NH4N.Length]; (Organs[i] as IWaterNitrogenUptake).CalculateNitrogenSupply(zone, ref organNO3Supply, ref organNH4Supply); UptakeDemands.NO3N = MathUtilities.Add(UptakeDemands.NO3N, organNO3Supply); //Add uptake supply from each organ to the plants total to tell the Soil arbitrator UptakeDemands.NH4N = MathUtilities.Add(UptakeDemands.NH4N, organNH4Supply); double organSupply = organNH4Supply.Sum() + organNO3Supply.Sum(); N.UptakeSupply[i] += organSupply * kgha2gsm * zone.Zone.Area / this.zone.Area; NSupply += organSupply * zone.Zone.Area; } } zones.Add(UptakeDemands); } double NDemand = (N.TotalPlantDemand - N.TotalReallocation) / kgha2gsm * zone.Area; //NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. if (NDemand < 0) { NDemand = 0; //NSupply should be zero if Reallocation can meet all demand (including small rounding errors which can make this -ve) } if (NSupply > NDemand) { //Reduce the PotentialUptakes that we pass to the soil arbitrator double ratio = Math.Min(1.0, NDemand / NSupply); foreach (ZoneWaterAndN UptakeDemands in zones) { UptakeDemands.NO3N = MathUtilities.Multiply_Value(UptakeDemands.NO3N, ratio); UptakeDemands.NH4N = MathUtilities.Multiply_Value(UptakeDemands.NH4N, ratio); } } return(zones); }
/// <summary> /// Set the value of a solute by specifying a delta. Will throw if solute not found. /// </summary> /// <param name="name">Name of solute</param> /// <param name="delta">Delta values to be added to solute</param> public void Add(string name, double[] delta) { Solute foundSolute = solutes.Find(solute => solute.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (foundSolute == null) { throw new Exception("Cannot find solute: " + name); } foundSolute.Value = MathUtilities.Add(foundSolute.Value, delta); }
/// <summary> /// Calculate the potential N uptake for today. Should return null if crop is not in the ground. /// </summary> public virtual List <Soils.Arbitrator.ZoneWaterAndN> GetNitrogenUptakeEstimates(SoilState soilstate) { if (Plant.IsEmerged) { double NSupply = 0;//NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. for (int i = 0; i < Organs.Count; i++) { N.UptakeSupply[i] = 0; } List <ZoneWaterAndN> zones = new List <ZoneWaterAndN>(); foreach (ZoneWaterAndN zone in soilstate.Zones) { ZoneWaterAndN UptakeDemands = new ZoneWaterAndN(zone); UptakeDemands.NO3N = new double[zone.NO3N.Length]; UptakeDemands.NH4N = new double[zone.NH4N.Length]; UptakeDemands.PlantAvailableNO3N = new double[zone.NO3N.Length]; UptakeDemands.PlantAvailableNH4N = new double[zone.NO3N.Length]; UptakeDemands.Water = new double[UptakeDemands.NO3N.Length]; //Get Nuptake supply from each organ and set the PotentialUptake parameters that are passed to the soil arbitrator for (int i = 0; i < Organs.Count; i++) { if (Organs[i] is IWaterNitrogenUptake) { double[] organNO3Supply = new double[zone.NO3N.Length]; double[] organNH4Supply = new double[zone.NH4N.Length]; (Organs[i] as IWaterNitrogenUptake).CalculateNitrogenSupply(zone, ref organNO3Supply, ref organNH4Supply); UptakeDemands.NO3N = MathUtilities.Add(UptakeDemands.NO3N, organNO3Supply); //Add uptake supply from each organ to the plants total to tell the Soil arbitrator UptakeDemands.NH4N = MathUtilities.Add(UptakeDemands.NH4N, organNH4Supply); N.UptakeSupply[i] += (MathUtilities.Sum(organNH4Supply) + MathUtilities.Sum(organNO3Supply)) * kgha2gsm * zone.Zone.Area / Plant.Zone.Area; NSupply += (MathUtilities.Sum(organNH4Supply) + MathUtilities.Sum(organNO3Supply)) * zone.Zone.Area; } } zones.Add(UptakeDemands); } double NDemand = (N.TotalPlantDemand - N.TotalReallocation) / kgha2gsm * Plant.Zone.Area; //NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. if (NSupply > NDemand) { //Reduce the PotentialUptakes that we pass to the soil arbitrator double ratio = Math.Min(1.0, NDemand / NSupply); foreach (ZoneWaterAndN UptakeDemands in zones) { UptakeDemands.NO3N = MathUtilities.Multiply_Value(UptakeDemands.NO3N, ratio); UptakeDemands.NH4N = MathUtilities.Multiply_Value(UptakeDemands.NH4N, ratio); } } return(zones); } return(null); }
/// <summary>Implements the operator +.</summary> /// <param name="ZWN1">Zone 1</param> /// <param name="ZWN2">Zone 2</param> /// <returns>The result of the operator.</returns> /// <exception cref="System.Exception">Cannot add zones with different names</exception> public static ZoneWaterAndN operator +(ZoneWaterAndN ZWN1, ZoneWaterAndN ZWN2) { if (ZWN1.Zone.Name != ZWN2.Zone.Name) { throw new Exception("Cannot add zones with different names"); } ZoneWaterAndN NewZ = new ZoneWaterAndN(ZWN1.Zone); NewZ.Water = MathUtilities.Add(ZWN1.Water, ZWN2.Water); NewZ.NO3N = MathUtilities.Add(ZWN1.NO3N, ZWN2.NO3N); NewZ.NH4N = MathUtilities.Add(ZWN1.NH4N, ZWN2.NH4N); return(NewZ); }
/// <summary>Does the Nitrogen uptake.</summary> /// <param name="zonesFromSoilArbitrator">List of zones from soil arbitrator</param> public void DoNitrogenUptake(List <ZoneWaterAndN> zonesFromSoilArbitrator) { foreach (ZoneWaterAndN thisZone in zonesFromSoilArbitrator) { ZoneState zone = Zones.Find(z => z.Name == thisZone.Zone.Name); if (zone != null) { zone.solutes.Subtract("NO3", SoluteManager.SoluteSetterType.Plant, thisZone.NO3N); zone.solutes.Subtract("NH4", SoluteManager.SoluteSetterType.Plant, thisZone.NH4N); zone.NitUptake = MathUtilities.Multiply_Value(MathUtilities.Add(thisZone.NO3N, thisZone.NH4N), -1); } } }
/// <summary>Does the Nitrogen uptake.</summary> /// <param name="NO3NAmount">The NO3NAmount.</param> /// <param name="NH4NAmount">The NH4NAmount.</param> public override void DoNitrogenUptake(double[] NO3NAmount, double[] NH4NAmount) { // Send the delta water back to SoilN that we're going to uptake. NitrogenChangedType NitrogenUptake = new NitrogenChangedType(); NitrogenUptake.DeltaNO3 = MathUtilities.Multiply_Value(NO3NAmount, -1.0); NitrogenUptake.DeltaNH4 = MathUtilities.Multiply_Value(NH4NAmount, -1.0); NitUptake = MathUtilities.Add(NitrogenUptake.DeltaNO3, NitrogenUptake.DeltaNH4); if (NitrogenChanged != null) { NitrogenChanged.Invoke(NitrogenUptake); } }
/// <summary>Implements the operator +.</summary> /// <param name="zone1">Zone 1</param> /// <param name="zone2">Zone 2</param> /// <returns>The result of the operator.</returns> /// <exception cref="System.Exception">Cannot add zones with different names</exception> public static ZoneWaterAndN operator +(ZoneWaterAndN zone1, ZoneWaterAndN zone2) { if (zone1.Name != zone2.Name) { throw new Exception("Cannot add zones with different names"); } ZoneWaterAndN NewZ = new ZoneWaterAndN(); NewZ.Name = zone1.Name; NewZ.Water = MathUtilities.Add(zone1.Water, zone2.Water); NewZ.NO3N = MathUtilities.Add(zone1.NO3N, zone2.NO3N); NewZ.NH4N = MathUtilities.Add(zone1.NH4N, zone2.NH4N); return(NewZ); }
// get curvature at interior points of (x,y) private static double[] curv(int n, double[] x, double[] y) { double[] s = new double[n - 1]; double[] yl = new double[n - 1]; double[] ySub = MathUtilities.Subtract(y.Slice(3, n), y.Slice(1, n - 2)); double[] xSub = MathUtilities.Subtract(x.Slice(3, n), x.Slice(1, n - 2)); s = MathUtilities.Divide(ySub, xSub); yl = MathUtilities.Add(y.Slice(1, n - 2), MathUtilities.Multiply(MathUtilities.Subtract(x.Slice(2, n - 1), x.Slice(1, n - 2)), s)); double[] ySlice = y.Slice(2, n - 1); return(MathUtilities.Subtract_Value(MathUtilities.Divide(ySlice, yl), 1)); }
// get curvature at interior points of (x,y) private static double[] curv(int n, double[] x, double[] y) { double[] c = new double[n - 2]; double[] s = new double[n - 2]; double[] yl = new double[n - 2]; Vector <double> xV = Vector <double> .Build.DenseOfArray(x); Vector <double> yV = Vector <double> .Build.DenseOfArray(y); s = MathUtilities.Divide(MathUtilities.Subtract(yV.SubVector(2, n - 1).ToArray(), yV.SubVector(0, n - 3).ToArray()), MathUtilities.Subtract(xV.SubVector(2, n - 1).ToArray(), xV.SubVector(0, n - 3).ToArray())); yl = MathUtilities.Add(yV.SubVector(0, n - 3).ToArray(), MathUtilities.Multiply(MathUtilities.Subtract(xV.SubVector(1, n - 2).ToArray(), xV.SubVector(0, n - 3).ToArray()), s)); return(MathUtilities.Subtract_Value(MathUtilities.Divide(yV.SubVector(1, n - 2).ToArray(), yl), 1)); }
/// <summary> /// Called by solutes to get the value of a solute. /// </summary> /// <param name="name">The name of the solute to get.</param> /// <returns></returns> internal double[] GetSoluteKgha(string name) { double[] values = null; foreach (var patch in patches) { if (values == null) { values = patch.GetSoluteKgHaRelativeArea(name); } else { values = MathUtilities.Add(values, patch.GetSoluteKgHaRelativeArea(name)); } } return(values); }
/// <summary>Does the Nitrogen uptake.</summary> /// <param name="zonesFromSoilArbitrator">List of zones from soil arbitrator</param> public void DoNitrogenUptake(List <ZoneWaterAndN> zonesFromSoilArbitrator) { foreach (ZoneWaterAndN thisZone in zonesFromSoilArbitrator) { ZoneState zone = Zones.Find(z => z.Name == thisZone.Zone.Name); if (zone != null) { // Send the delta water back to SoilN that we're going to uptake. NitrogenChangedType NitrogenUptake = new NitrogenChangedType(); NitrogenUptake.DeltaNO3 = MathUtilities.Multiply_Value(thisZone.NO3N, -1.0); NitrogenUptake.DeltaNH4 = MathUtilities.Multiply_Value(thisZone.NH4N, -1.0); zone.NitUptake = MathUtilities.Add(NitrogenUptake.DeltaNO3, NitrogenUptake.DeltaNH4); zone.soil.SoilNitrogen.SetNitrogenChanged(NitrogenUptake); } } }
private static double[] odef(int n1, int n2, double[] aK, double[] hpK) { double[] u = new double[3]; int np; double[] da = new double[n2 - n1 + 2]; double[] db = new double[n2 - n1 + 1]; // Get z and dz/dq for flux q and phi from aphi(n1) to aphi(n2). // q is global to subroutine fluxtbl. np = n2 - n1 + 1; double[] daTemp = MathUtilities.Subtract_Value(aK.Slice(n1, n2), q); double[] dbTemp = MathUtilities.Subtract_Value(hpK.Slice(n1, n2 - 1), q); diags.AppendLine("n1: " + n1 + Environment.NewLine + "n2: " + n2); for (int idx = 1; idx < da.Length; idx++) { da[idx] = 1.0 / daTemp[idx]; } for (int idx = 1; idx < db.Length; idx++) { db[idx] = 1.0 / dbTemp[idx]; } diags.Append("da: "); for (int i = 1; i < da.Length; i++) { diags.Append(" " + da[i]); } diags.AppendLine(); // apply Simpson's rule // u[] = sum((aphi(n1+1:n2)-aphi(n1:n2-1))*(da(1:np-1)+4*db+da(2:np))/6). Love the C# implementation. Note aphi is now sp.phic u[1] = MathUtilities.Sum(MathUtilities.Divide_Value(MathUtilities.Multiply (MathUtilities.Subtract(sp.phic.Slice(n1 + 1, n2), sp.phic.Slice(n1, n2 - 1)), MathUtilities.Add(MathUtilities.Add(MathUtilities.Multiply_Value(db, 4), da.Slice(1, np - 1)), da.Slice(2, np))), 6)); // this is madness! da = MathUtilities.Multiply(da, da); db = MathUtilities.Multiply(db, db); u[2] = MathUtilities.Sum(MathUtilities.Divide_Value(MathUtilities.Multiply (MathUtilities.Subtract(sp.phic.Slice(n1 + 1, n2), sp.phic.Slice(n1, n2 - 1)), MathUtilities.Add(MathUtilities.Add(MathUtilities.Multiply_Value(db, 4), da.Slice(1, np - 1)), da.Slice(2, np))), 6)); return(u); }
private static double[] odef(int n1, int n2, double[] aK, double[] hpK) { double[] u = new double[2]; int np; double[] da = new double[n2 - n1 + 1]; double[] db = new double[n2 - n1]; // Get z and dz/dq for flux q and phi from aphi(n1) to aphi(n2). // q is global to subroutine fluxtbl. np = n2 - n1 + 1; Vector <double> akV = Vector <double> .Build.DenseOfArray(aK); Vector <double> hpKV = Vector <double> .Build.DenseOfArray(hpK); double[] daTemp = MathUtilities.Subtract_Value(akV.SubVector(n1, n2 - n1 + 1).ToArray(), q); double[] dbTemp = MathUtilities.Subtract_Value(akV.SubVector(n1, n2 - n1).ToArray(), q); for (int idx = 0; idx < da.Length; idx++) { da[idx] = 1.0 / daTemp[idx]; if (idx < db.Length) { db[idx] = 1.0 / dbTemp[idx]; } } // apply Simpson's rule Vector <double> aphiV = Vector <double> .Build.DenseOfArray(aphi); Vector <double> daV = Vector <double> .Build.DenseOfArray(da); // sum((aphi(n1+1:n2)-aphi(n1:n2-1))*(da(1:np-1)+4*db+da(2:np))/6) for both of these stupid lines... find something better. Vector <double> t1 = aphiV.SubVector(n1, n2 - n1 + 1); Vector <double> t2 = aphiV.SubVector(n1 - 1, n2 - n1 + 1); Vector <double> t3 = daV.SubVector(0, np); Vector <double> t4 = daV.SubVector(1, np - 1); //u[0] = MathUtilities.Sum(MathUtilities.Divide_Value(MathUtilities.Multiply(aphiV.SubVector(n1, n2 - n1 + 1).Subtract(aphiV.SubVector(n1 - 1, n2 - n1)).ToArray(), MathUtilities.Add(MathUtilities.Multiply(daV.SubVector(0, np).Add(4).ToArray(), db), daV.SubVector(1, np + 1).ToArray())), 6)); // this is madness! u[0] = MathUtilities.Sum(MathUtilities.Divide_Value(MathUtilities.Multiply(aphiV.SubVector(n1, n2 - n1 + 1).Subtract(aphiV.SubVector(n1 - 1, n2 - n1)).ToArray(), MathUtilities.Add(MathUtilities.Multiply(daV.SubVector(0, np).Add(4).ToArray(), db), daV.SubVector(1, np + 1).ToArray())), 6)); // this is madness! da = MathUtilities.Multiply(da, da); db = MathUtilities.Multiply(db, db); u[1] = MathUtilities.Sum(MathUtilities.Divide_Value(MathUtilities.Multiply(aphiV.SubVector(n1, n2 - n1 + 1).Subtract(aphiV.SubVector(n1 - 1, n2 - n1)).ToArray(), MathUtilities.Add(MathUtilities.Multiply(daV.SubVector(0, np).Add(4).ToArray(), db), daV.SubVector(1, np + 1).ToArray())), 6)); // this is madness! return(u); }
/// <summary> /// Calculate the potential N uptake for today. Should return null if crop is not in the ground. /// </summary> public override List <Soils.Arbitrator.ZoneWaterAndN> GetNitrogenUptakeEstimates(SoilState soilstate) { if (Plant.IsEmerged) { var nSupply = 0.0;//NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. //this function is called 4 times as part of estimates //shouldn't set public variables in here var grainIndex = 0; var rootIndex = 1; var leafIndex = 2; var stemIndex = 4; var nGreen = stem.Live.N; var dmGreen = stem.Live.Wt; var dltDM = stem.potentialDMAllocation.Structural; var rootDemand = N.StructuralDemand[rootIndex] + N.MetabolicDemand[rootIndex]; var stemDemand = N.StructuralDemand[stemIndex] + N.MetabolicDemand[stemIndex]; var leafDemand = N.MetabolicDemand[leafIndex]; var grainDemand = N.StructuralDemand[grainIndex] + N.MetabolicDemand[grainIndex]; //have to correct the leaf demand calculation var leaf = Organs[leafIndex] as SorghumLeaf; var leafAdjustment = leaf.calculateClassicDemandDelta(); //double NDemand = (N.TotalPlantDemand - N.TotalReallocation) / kgha2gsm * Plant.Zone.Area; //NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. //old sorghum uses g/m^2 - need to convert after it is used to calculate actual diffusion // leaf adjustment is not needed here because it is an adjustment for structural demand - we only look at metabolic here. var nDemand = Math.Max(0, N.TotalMetabolicDemand - grainDemand); // to replicate calcNDemand in old sorghum for (int i = 0; i < Organs.Count; i++) { N.UptakeSupply[i] = 0; } List <ZoneWaterAndN> zones = new List <ZoneWaterAndN>(); foreach (ZoneWaterAndN zone in soilstate.Zones) { ZoneWaterAndN UptakeDemands = new ZoneWaterAndN(zone.Zone); UptakeDemands.NO3N = new double[zone.NO3N.Length]; UptakeDemands.NH4N = new double[zone.NH4N.Length]; UptakeDemands.PlantAvailableNO3N = new double[zone.NO3N.Length]; UptakeDemands.PlantAvailableNH4N = new double[zone.NO3N.Length]; UptakeDemands.Water = new double[UptakeDemands.NO3N.Length]; //only using Root to get Nitrogen from - temporary code for sorghum var root = Organs[rootIndex] as Root; //Get Nuptake supply from each organ and set the PotentialUptake parameters that are passed to the soil arbitrator //at present these 2arrays arenot being used within the CalculateNitrogenSupply function //sorghum uses Diffusion & Massflow variables currently double[] organNO3Supply = new double[zone.NO3N.Length]; //kg/ha double[] organNH4Supply = new double[zone.NH4N.Length]; ZoneState myZone = root.Zones.Find(z => z.Name == zone.Zone.Name); if (myZone != null) { CalculateNitrogenSupply(myZone, zone); //new code double[] diffnAvailable = new double[myZone.Diffusion.Length]; for (var i = 0; i < myZone.Diffusion.Length; ++i) { diffnAvailable[i] = myZone.Diffusion[i] - myZone.MassFlow[i]; } var totalMassFlow = MathUtilities.Sum(myZone.MassFlow); //g/m^2 var totalDiffusion = MathUtilities.Sum(diffnAvailable); //g/m^2 var potentialSupply = totalMassFlow + totalDiffusion; var dltt = root.DltThermalTime.Value(); var actualDiffusion = 0.0; var actualMassFlow = dltt > 0 ? totalMassFlow : 0.0; var maxDiffusionConst = root.MaxDiffusion.Value(); if (totalMassFlow < nDemand && dltt > 0.0) { actualDiffusion = MathUtilities.Bound(nDemand - totalMassFlow, 0.0, totalDiffusion); actualDiffusion = MathUtilities.Divide(actualDiffusion, maxDiffusionConst, 0.0); var nsupplyFraction = root.NSupplyFraction.Value(); var maxRate = root.MaxNUptakeRate.Value(); var maxUptakeRateFrac = Math.Min(1.0, (potentialSupply / root.NSupplyFraction.Value())) * root.MaxNUptakeRate.Value(); var maxUptake = Math.Max(0, maxUptakeRateFrac * dltt - actualMassFlow); actualDiffusion = Math.Min(actualDiffusion, maxUptake); } //adjust diffusion values proportionally //make sure organNO3Supply is in kg/ha for (int layer = 0; layer < organNO3Supply.Length; layer++) { var massFlowLayerFraction = MathUtilities.Divide(myZone.MassFlow[layer], totalMassFlow, 0.0); var diffusionLayerFraction = MathUtilities.Divide(diffnAvailable[layer], totalDiffusion, 0.0); //organNH4Supply[layer] = massFlowLayerFraction * root.MassFlow[layer]; organNO3Supply[layer] = (massFlowLayerFraction * actualMassFlow + diffusionLayerFraction * actualDiffusion) / kgha2gsm; //convert to kg/ha } } //originalcode UptakeDemands.NO3N = MathUtilities.Add(UptakeDemands.NO3N, organNO3Supply); //Add uptake supply from each organ to the plants total to tell the Soil arbitrator if (UptakeDemands.NO3N.Any(n => MathUtilities.IsNegative(n))) { throw new Exception("-ve no3 uptake demand"); } UptakeDemands.NH4N = MathUtilities.Add(UptakeDemands.NH4N, organNH4Supply); N.UptakeSupply[rootIndex] += MathUtilities.Sum(organNO3Supply) * kgha2gsm * zone.Zone.Area / Plant.Zone.Area; //g/^m if (MathUtilities.IsNegative(N.UptakeSupply[rootIndex])) { throw new Exception($"-ve uptake supply for organ {(Organs[rootIndex] as IModel).Name}"); } nSupply += MathUtilities.Sum(organNO3Supply) * zone.Zone.Area; zones.Add(UptakeDemands); } var nDemandInKg = nDemand / kgha2gsm * Plant.Zone.Area; //NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. if (nSupply > nDemandInKg) { //Reduce the PotentialUptakes that we pass to the soil arbitrator double ratio = Math.Min(1.0, nDemandInKg / nSupply); foreach (ZoneWaterAndN UptakeDemands in zones) { UptakeDemands.NO3N = MathUtilities.Multiply_Value(UptakeDemands.NO3N, ratio); UptakeDemands.NH4N = MathUtilities.Multiply_Value(UptakeDemands.NH4N, ratio); } } return(zones); } return(null); }
/// <summary> /// Solves the equation of continuity from time ts to tfin. /// </summary> /// <param name="sol">solute properties.</param> /// <param name="sdata">soil data.</param> /// <param name="ts">start time (h).</param> /// <param name="tfin">finish time.</param> /// <param name="qprec">precipitation (or water input) rate (fluxes are in cm/h).</param> /// <param name="qevap">potl evaporation rate from soil surface.</param> /// <param name="nsol">no. of solutes.</param> /// <param name="nex">no. of water extraction streams.</param> /// <param name="h0">surface head, equal to depth of surface pond.</param> /// <param name="S">degree of saturation ("effective satn") of layers.</param> /// <param name="evap">cumulative evaporation from soil surface (cm, not initialised).</param> /// <param name="runoff">cumulative runoff.</param> /// <param name="infil">cumulative net infiltration (time integral of flux across surface).</param> /// <param name="drn">cumulative net drainage (time integral of flux across bottom).</param> /// <param name="nsteps">cumulative no. of time steps for RE soln.</param> /// <param name="jt">layer soil type numbers for solute.</param> /// <param name="cin">solute concns in water input (user's units/cc).</param> /// <param name="c0">solute concns in surface pond.</param> /// <param name="sm">solute (mass) concns in layers.</param> /// <param name="soff">cumulative solute runoff (user's units).</param> /// <param name="sinfil">cumulative solute infiltration.</param> /// <param name="sdrn">cumulative solute drainage.</param> /// <param name="nssteps">cumulative no. of time steps for ADE soln.</param> /// <param name="wex">cumulative water extractions from layers.</param> /// <param name="sex">cumulative solute extractions from layers.</param> public static void Solve(SolProps sol, SoilData sdata, double ts, double tfin, double qprec, double qevap, int nsol, int nex, ref double h0, ref double[] S, ref double evap, ref double runoff, ref double infil, ref double drn, ref int nsteps, int[] jt, double[] cin, ref double[] c0, ref double[,] sm, ref double[] soff, ref double[] sinfil, ref double[] sdrn, ref int[] nssteps, ref double[,] wex, ref double[,,] sex) { // Since S.Length is one greater in Fortran to account for the 0 based index, this line is included // so that the +1 nomenclature can be kept below to avoid (further) confusion. int sLength = S.Length - 1; bool again, extraction, initpond, maxpond; int i, iflux, ih0, iok, isatbot, itmp, ns, nsat, nsatlast, nsteps0; int[] isat = new int[sLength + 1]; double accel, dmax, dt, dwinfil, dwoff, fac, infili, qpme, qprec1, rsig, rsigdt, sig, t, ti, win, xtblbot; double[] dSdt = new double[sLength + 1]; double[] h = new double[sLength + 1]; double[] xtbl = new double[sLength + 1]; double[] thi = new double[sLength + 1]; double[] thf = new double[sLength + 1]; double[] qwex = new double[sLength + 1]; double[] qwexd = new double[sLength + 1]; double[,] dwexs = new double[sLength + 1, nex + 1]; double[,] qwexs = new double[sLength + 1, nex + 1]; double[,] qwexsd = new double[sLength + 1, nex + 1]; double[] aa = new double[sLength + 1]; // these are 0 based double[] bb = new double[sLength + 1]; double[] cc = new double[sLength + 1]; double[] dd = new double[sLength + 1]; double[] dy = new double[sLength + 1]; double[] ee = new double[sLength + 1]; double[] q = new double[sLength + 1]; double[] qya = new double[sLength + 1]; double[] qyb = new double[sLength + 1]; double[] cav = new double[nsol + 1]; double[] sinfili = new double[nsol + 1]; double[,] c; sd = sdata; c = new double[nsol + 1, sd.n + 1]; ISink sink = new SinkDripperDrain(); // Sink type can be changed here. /* * ! The saturation status of a layer is stored as 0 or 1 in isat since S may be * ! >1 (because of previous overshoot) when a layer desaturates. Fluxes at the * ! beginning of a time step and their partial derivs wrt S or h of upper and * ! lower layers or boundaries are stored in q, qya and qyb. */ isatbot = 0; xtblbot = 0; dt = 0.0; fac = 0.0; initpond = false; extraction = false; dwoff = 0.0; ti = 0.0; infili = 0.0; if (nex > 0) { extraction = true; } if (sLength != sd.n) { Console.WriteLine("solve: Size of S differs from table data."); Environment.Exit(1); } //-----set up for boundary conditions if (botbc == "constant head") // !h at bottom bdry specified { if (hbot < sd.he[sd.n]) { isatbot = 0; xtblbot = Sbot; } else { isatbot = 1; xtblbot = hbot - sd.he[sd.n]; } } //-----end set up for boundary conditions //-----initialise t = ts; nsteps0 = nsteps; nsat = 0; //initialise saturated regions for (int x = 1; x < sLength; x++) { if (S[x] >= 1.0) { isat[x] = 1; h[x] = sd.he[x]; } else { isat[x] = 0; h[x] = sd.he[x] - 1.0; } } if (nsol > 0) { //set solute info thi = MathUtilities.Multiply(sd.ths, S); //initial th dwexs.Populate2D(0); //initial water extracted from layers ti = t; infili = infil; sinfili = sinfil; double c0Temp = c0[1]; if (h0 > 0 && cin.Any(x => x != c0Temp)) // count(c0 /= cin) > 0) { initpond = true; //initial pond with different solute concn } else { initpond = false; } c.Populate2D(0); //temp storage for soln concns } //-----end initialise //-----solve until tfin while (t < tfin) { //-----take next time step for (iflux = 1; iflux <= 2; iflux++) //sometimes need twice to adjust h at satn { nsatlast = nsat; // for detecting onset of profile saturation nsat = isat.Sum(); sig = 0.5; if (nsat != 0) { sig = 1.0; //time weighting sigma } rsig = 1.0 / sig; //-----get fluxes and derivs // get table entries for (int x = 1; x < isat.Length; x++) { if (isat[x] == 0) { xtbl[x] = S[x]; } else { xtbl[x] = h[x] - sd.he[x]; } } //get surface flux qpme = qprec - qevap; //input rate at saturation qprec1 = qprec; //may change qprec1 to maintain pond if required if (h[1] <= 0 && h0 <= 0 && nsat < sd.n) //no ponding { ns = 1; //start index for eqns sd.GetQ(0, new [] { 0, 0, isat[1] }, new [] { 0, 0, xtbl[1] }, out q[0], out qya[0], out qyb[0]); if (q[0] < qpme) { q[0] = qpme; qyb[0] = 0; } maxpond = false; } else //ponding { ns = 0; sd.GetQ(0, new [] { 0, 1, isat[1] }, new [] { 0, h0 - sd.he[1], xtbl[1] }, out q[0], out qya[0], out qyb[0]); if (h0 >= h0max && qpme > q[0]) { maxpond = true; ns = 1; } else { maxpond = false; } } //get profile fluxes for (i = 1; i <= sd.n - 1; i++) { sd.GetQ(i, new [] { 0, isat[i], isat[i + 1] }, new [] { 0, xtbl[i], xtbl[i + 1] }, out q[i], out qya[i], out qyb[i]); } //get bottom flux switch (botbc) { case "constant head": sd.GetQ(sd.n, new [] { 0, isat[sd.n], isatbot }, new [] { 0, xtbl[sd.n], xtblbot }, out q[sd.n], out qya[sd.n], out qyb[sd.n]); break; case "0.0 flux": q[sd.n] = 0; qya[sd.n] = 0; break; case "free drainage": sd.GetK(sd.n, isat[sd.n], xtbl[sd.n], out q[sd.n], out qya[sd.n]); break; case "seepage": if (h[sd.n] <= -0.5 * sd.dx[sd.n]) { q[sd.n] = 0; qya[sd.n] = 0; } else { sd.GetQ(sd.n, new int[] { 0, isat[sd.n], 1 }, new double[] { 0, xtbl[sd.n], -sd.he[sd.n] }, out q[sd.n], out qya[sd.n], out qyb[sd.n]); } break; default: Console.Out.WriteLine("solve: illegal bottom boundary condn"); Environment.Exit(1); break; } if (extraction) //get rate of extraction { sink.Wsinks(t, isat, xtbl, sd.he, ref qwexs, ref qwexsd); for (int x = 1; x < qwexs.GetLength(1); x++) { qwex[x] = Matrix <double> .Build.DenseOfArray(qwexs).Column(x).Sum(); qwexd[x] = Matrix <double> .Build.DenseOfArray(qwexsd).Column(x).Sum(); } } again = false; //flag for recalcn of fluxes //-----end get fluxes and derivs //----estimate time step dt dmax = 0; dSdt = MathUtilities.CreateArrayOfValues(0, dSdt.Length); for (int x = 1; x <= sd.n; x++) { if (isat[x] == 0) { dSdt[x] = Math.Abs(q[x] - q[x - 1] + (extraction ? qwex[x] : 0)) / (sd.ths[x] * sd.dx[x]); } } dmax = MathUtilities.Max(dSdt); //Max derivative | dS / dt | if (dmax > 0) { dt = dSmax / dmax; // if pond going adjust dt if (h0 > 0 && (q[0] - qpme) * dt > h0) { dt = (h0 - 0.5 * h0min) / (q[0] - qpme); } } else //steady state flow { if (qpme >= q[sd.n]) //step to finish -but what if extraction varies with time ??? { dt = tfin - t; } else { dt = -(h0 - 0.5 * h0min) / (qpme - q[sd.n]); //pond going so adjust dt } } if (dt > dtmax) { dt = dtmax; //user's limit } // if initial step, improve h where S>= 1 if (nsteps == nsteps0 && nsat > 0 && iflux == 1) { again = true; dt = 1.0e-20 * (tfin - ts); } if (nsat == sd.n && nsatlast < sd.n && iflux == 1) { //profile has just become saturated so adjust h values again = true; dt = 1.0e-20 * (tfin - ts); } if (t + 1.1 * dt > tfin) //step to finish { dt = tfin - t; t = tfin; } else { t = t + dt; //tentative update } //-----end estimate time step dt //-----get and solve eqns rsigdt = 1.0 / (sig * dt); //aa, bb, cc and dd hold coeffs and rhs of tridiag eqn set for (int x = ns; x <= sd.n - 1; x++) { aa[x + 1] = qya[x]; cc[x] = -qyb[x]; } if (extraction) { for (int x = 1; x <= sd.n; x++) { dd[x] = -(q[x - 1] - q[x] - qwex[x]) * rsig; } } else { for (int x = 1; x <= sd.n; x++) { dd[x] = -(q[x - 1] - q[x]) * rsig; } } iok = 0; //flag for time step test itmp = 0; //counter to abort if not getting solution while (iok == 0) //keep reducing time step until all ok { itmp = itmp + 1; accel = 1.0 - 0.05 * Math.Min(10, Math.Max(0, itmp - 4)); //acceleration if (itmp > 20) { Console.Out.WriteLine("solve: too many iterations of equation solution"); Environment.Exit(1); } if (ns < 1) { bb[0] = -qya[0] - rsigdt; dd[0] = -(qpme - q[0]) * rsig; } for (int x = 1; x <= sd.n; x++) { bb[x] = qyb[x - 1] - qya[x]; if (isat[x] == 0) { bb[x] -= sd.ths[x] * sd.dx[x] * rsigdt; } if (extraction) { bb[x] -= qwexd[x]; } } Tri(ns, sd.n, aa, ref bb, cc, dd, ref ee, ref dy); //dy contains dS or, for sat layers, h values iok = 1; if (!again) { //check if time step ok, if not then set fac to make it less iok = 1; for (i = 1; i <= sd.n; i++) { if (isat[i] == 0) //check change in S { if (Math.Abs(dy[i]) > dSfac * dSmax) { fac = Math.Max(0.5, accel * Math.Abs(dSmax / dy[i])); iok = 0; break; } if (-dy[i] > dSmaxr * S[i]) { fac = Math.Max(0.5, accel * dSmaxr * S[i] / (-dSfac * dy[i])); iok = 0; break; } if (S[i] < 1.0 && S[i] + dy[i] > Smax) { fac = accel * (0.5 * (1.0 + Smax) - S[i]) / dy[i]; iok = 0; break; } if (S[i] >= 1.0 && dy[i] > 0.5 * (Smax - 1.0)) { fac = 0.25 * (Smax - 1.0) / dy[i]; iok = 0; break; } } } if (iok == 1 && ns < 1 && h0 < h0max && h0 + dy[0] > h0max + dh0max) { //start of runoff fac = (h0max + 0.5 * dh0max - h0) / dy[0]; iok = 0; } if (iok == 1 && ns < 1 && h0 > 0.0 && h0 + dy[0] < h0min) { //pond going fac = -(h0 - 0.5 * h0min) / dy[0]; iok = 0; } if (iok == 0) //reduce time step { t = t - dt; dt = fac * dt; t = t + dt; rsigdt = 1.0 / (sig * dt); nless = nless + 1; //count step size reductions } if (isat[1] != 0 && iflux == 1 && h[1] < 0.0 && h[1] + dy[1] > 0.0) { //incipient ponding - adjust state of saturated regions t = t - dt; dt = 1.0e-20 * (tfin - ts); rsigdt = 1.0 / (sig * dt); again = true; iok = 0; } } } //end while //-----end get and solve eqns //-----update unknowns ih0 = 0; if (!again) { dwoff = 0.0; if (ns < 1) { h0 = h0 + dy[0]; if (h0 < 0.0 && dy[0] < 0.0) { ih0 = 1; //pond g1.0 } evap = evap + qevap * dt; //note that fluxes required are q at sigma of time step dwinfil = (q[0] + sig * (qya[0] * dy[0] + qyb[0] * dy[1])) * dt; } else { dwinfil = (q[0] + sig * qyb[0] * dy[1]) * dt; if (maxpond) { evap = evap + qevap * dt; if (qprec > qprecmax) // set input to maintain pond { qpme = q[0] + sig * qyb[0] * dy[1]; qprec1 = qpme + qevap; dwoff = 0.0; } else { dwoff = qpme * dt - dwinfil; } runoff = runoff + dwoff; } else { evap = evap + qprec1 * dt - dwinfil; } } infil = infil + dwinfil; if (nsol > 0) //get surface solute balance { if (initpond) //pond concn != cin { if (h0 > 0.0) { if (ns == 1) // if max pond depth { dy[0] = 0.0; } for (int x = 1; x < cav.Length; x++) { cav[x] = ((2.0 * h0 - dy[0]) * c0[x] + qprec1 * dt * cin[x]) / (2.0 * h0 + dwoff + dwinfil); } for (int x = 1; x < c0.Length; x++) { c0[x] = 2.0 * cav[x] - c0[x]; //This needs to be tested from FORTRAN; no example in original code. } } else { for (int x = 1; x < cav.Length; x++) { cav[x] = ((h0 - dy[0]) * c0[x] + qprec1 * dt * cin[x]) / (dwoff + dwinfil); } initpond = false; //pond gone c0 = cin; // for output if any pond at end } soff = MathUtilities.Add(soff, MathUtilities.Multiply_Value(cav, dwoff)); sinfil = MathUtilities.Add(sinfil, MathUtilities.Multiply_Value(cav, dwinfil)); } else { soff = MathUtilities.Add(soff, MathUtilities.Multiply_Value(cav, dwoff)); sinfil = MathUtilities.Add(sinfil, MathUtilities.Multiply_Value(cin, qprec1 * dt - dwoff)); } } // There's a condition based on botbc in the FORTRAN here, but both paths // resolve to the same equation. drn = drn + (q[sd.n] + sig * qya[sd.n] * dy[sd.n]) * dt; if (extraction) { Matrix <double> dwexsM = Matrix <double> .Build.DenseOfArray(dwexs); Matrix <double> qwexsM = Matrix <double> .Build.DenseOfArray(qwexs); Matrix <double> qwexsdM = Matrix <double> .Build.DenseOfArray(qwexsd); Matrix <double> wexM = Matrix <double> .Build.DenseOfArray(wex); Vector <double> wexV; Vector <double> dwexsV; Vector <double> qwexsV; Vector <double> qwexsdV; if (nsol > 0) { //dwexs = dwexs + (qwexs + sig * qwexsd * spread(dy(1:n), 2, nex)) * dt for (i = 1; i <= nex; i++) { dwexsV = dwexsM.Column(i); qwexsV = qwexsM.Column(i); qwexsdV = qwexsdM.Column(i); dwexsV = dwexsV + (qwexsV + sig * qwexsdV * Vector <double> .Build.DenseOfArray(dy.Slice(1, sd.n))) * dt; dwexsM.Column(i, dwexsV); } dwexs = dwexsM.ToArray(); } //wex = wex + (qwexs + sig * qwexsd * spread(dy(1:n), 2, nex)) * dt if (wex.GetLength(0) > 1) // analog for if (present(wex)) { for (i = 1; i <= nex; i++) { qwexsV = qwexsM.Column(i); qwexsdV = qwexsdM.Column(i); wexV = wexM.Column(i); wexV = wexV + (qwexsV + sig * qwexsdV * Vector <double> .Build.DenseOfArray(dy.Slice(1, sd.n))) * dt; wexM.Column(i, wexV); } wex = wexM.ToArray(); } } } for (i = 1; i <= sd.n; i++) { if (isat[i] == 0) { if (!again) { S[i] = S[i] + dy[i]; if (S[i] > 1.0 && dy[i] > 0.0) //saturation of layer { isat[i] = 1; h[i] = sd.he[i]; } } } else { h[i] = h[i] + dy[i]; if (i == 1 && ih0 != 0 && h[i] >= sd.he[i]) { h[i] = sd.he[i] - 1.0; //pond gone } if (h[i] < sd.he[i]) //desaturation of layer { isat[i] = 0; h[i] = sd.he[i]; } } } //-----end update unknowns if (!again) { break; } } if (dt <= dtmin) { Console.WriteLine("solve: time step = " + dt); Environment.Exit(1); } //-----end take next time step //remove negative h0 (optional) if (h0 < 0.0 && isat[1] == 0) { infil = infil + h0; S[1] = S[1] + h0 / (sd.ths[1] * sd.dx[1]); h0 = 0.0; } nsteps = nsteps + 1; //solve for solute transport if required if (nwsteps * (nsteps / nwsteps) == nsteps) { // This is function getsolute() in FORTRAN. Inline here as it uses a huge number of vars and is only used once. if (nsol > 0 && t > ti) { thf = MathUtilities.Multiply(sd.ths, S); //final th before call win = infil - infili; //water in at top over time interval cav = MathUtilities.Divide_Value(MathUtilities.Subtract(sinfil, sinfili), win); //average concn in win Solute(ti, t, thi, thf, dwexs, win, cav, sd.n, nsol, nex, sd.dx, jt, dsmmax, ref sm, ref sdrn, ref nssteps, ref c, ref sex, extraction, sol); ti = t; thi = thf; dwexs.Populate2D(0); infili = infil; sinfili = sinfil; // for next interval } } } //-----end solve until tfin //finalise solute transport if required }
/// <summary>Evaluates the specified sym1.</summary> /// <param name="sym1">The sym1.</param> /// <param name="opr">The opr.</param> /// <param name="sym2">The sym2.</param> /// <returns></returns> protected Symbol Evaluate(Symbol sym1, Symbol opr, Symbol sym2) { Symbol result; result.m_name = sym1.m_name + opr.m_name + sym2.m_name; result.m_type = ExpressionType.Result; result.m_value = 0; result.m_values = null; switch (opr.m_name) { case "^": if (sym1.m_values != null) { result.m_values = new double[sym1.m_values.Length]; for (int i = 0; i < sym1.m_values.Length; i++) { result.m_values[i] = Math.Pow(sym1.m_values[i], sym2.m_value); } } else { result.m_value = System.Math.Pow(sym1.m_value, sym2.m_value); } break; case "/": { if (sym1.m_values != null && sym2.m_values != null) { result.m_values = MathUtilities.Divide(sym1.m_values, sym2.m_values); } else if (sym1.m_values != null) { result.m_values = MathUtilities.Divide_Value(sym1.m_values, sym2.m_value); } else if (sym2.m_values != null) { result.m_values = new double[sym2.m_values.Length]; for (int i = 0; i < result.m_values.Length; i++) { result.m_values[i] = MathUtilities.Divide(sym1.m_value, sym2.m_values[i], 0); } } else { if (sym2.m_value > 0) { result.m_value = sym1.m_value / sym2.m_value; } else { result.m_name = "Divide by Zero."; result.m_type = ExpressionType.Error; } } break; } case "*": if (sym1.m_values != null && sym2.m_values != null) { result.m_values = MathUtilities.Multiply(sym1.m_values, sym2.m_values); } else if (sym1.m_values != null) { result.m_values = MathUtilities.Multiply_Value(sym1.m_values, sym2.m_value); } else if (sym2.m_values != null) { result.m_values = MathUtilities.Multiply_Value(sym2.m_values, sym1.m_value); } else { result.m_value = sym1.m_value * sym2.m_value; } break; case "%": result.m_value = sym1.m_value % sym2.m_value; break; case "+": if (sym1.m_values != null && sym2.m_values != null) { result.m_values = MathUtilities.Add(sym1.m_values, sym2.m_values); } else if (sym1.m_values != null) { result.m_values = MathUtilities.AddValue(sym1.m_values, sym2.m_value); } else if (sym2.m_values != null) { result.m_values = MathUtilities.AddValue(sym2.m_values, sym1.m_value); } else { result.m_value = sym1.m_value + sym2.m_value; } break; case "-": if (sym1.m_values != null && sym2.m_values != null) { result.m_values = MathUtilities.Subtract(sym1.m_values, sym2.m_values); } else if (sym1.m_values != null) { result.m_values = MathUtilities.Subtract_Value(sym1.m_values, sym2.m_value); } else if (sym2.m_values != null) { result.m_values = new double[sym2.m_values.Length]; for (int i = 0; i < result.m_values.Length; i++) { result.m_values[i] = sym1.m_value - sym2.m_values[i]; } } else { result.m_value = sym1.m_value - sym2.m_value; } break; default: result.m_type = ExpressionType.Error; result.m_name = "Undefine operator: " + opr.m_name + "."; break; } return(result); }
public void AddKgHaDelta(SoluteSetterType callingModelType, double[] delta) { kgha = MathUtilities.Add(kgha, delta); }
/// <summary>The method used to arbitrate N allocations</summary> public List <ZoneWaterAndN> GetUptakeEstimates(SoilState soilstate, IArbitration[] Organs) { var N = Arbitrator.N; var nSupply = 0.0;//NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. //this function is called 4 times as part of estimates //shouldn't set public variables in here var grainIndex = 0; //Organs.ToList().FindIndex(o => (o as IModel).Name == "Grain") var rootIndex = 1; var leafIndex = 2; var stemIndex = 4; var rootDemand = N.StructuralDemand[rootIndex] + N.MetabolicDemand[rootIndex]; var stemDemand = /*N.StructuralDemand[stemIndex] + */ N.MetabolicDemand[stemIndex]; var leafDemand = N.MetabolicDemand[leafIndex]; var grainDemand = N.StructuralDemand[grainIndex] + N.MetabolicDemand[grainIndex]; //have to correct the leaf demand calculation var leaf = Organs[leafIndex] as SorghumLeaf; var leafAdjustment = leaf.calculateClassicDemandDelta(); //double NDemand = (N.TotalPlantDemand - N.TotalReallocation) / kgha2gsm * Plant.Zone.Area; //NOTE: This is in kg, not kg/ha, to arbitrate N demands for spatial simulations. //old sorghum uses g/m^2 - need to convert after it is used to calculate actual diffusion // leaf adjustment is not needed here because it is an adjustment for structural demand - we only look at metabolic here. // dh - In old sorghum, root only has one type of NDemand - it doesn't have a structural/metabolic division. // In new apsim, root only uses structural, metabolic is always 0. Therefore, we have to include root's structural // NDemand in this calculation. // dh - In old sorghum, totalDemand is metabolic demand for all organs. However in new apsim, grain has no metabolic // demand, so we must include its structural demand in this calculation. double totalDemand = N.TotalMetabolicDemand + N.StructuralDemand[rootIndex] + N.StructuralDemand[grainIndex]; double nDemand = Math.Max(0, totalDemand - grainDemand); // to replicate calcNDemand in old sorghum List <ZoneWaterAndN> zones = new List <ZoneWaterAndN>(); foreach (ZoneWaterAndN zone in soilstate.Zones) { ZoneWaterAndN UptakeDemands = new ZoneWaterAndN(zone.Zone); UptakeDemands.NO3N = new double[zone.NO3N.Length]; UptakeDemands.NH4N = new double[zone.NH4N.Length]; UptakeDemands.PlantAvailableNO3N = new double[zone.NO3N.Length]; UptakeDemands.PlantAvailableNH4N = new double[zone.NO3N.Length]; UptakeDemands.Water = new double[UptakeDemands.NO3N.Length]; //only using Root to get Nitrogen from - temporary code for sorghum var root = Organs[rootIndex] as Root; //Get Nuptake supply from each organ and set the PotentialUptake parameters that are passed to the soil arbitrator //at present these 2arrays arenot being used within the CalculateNitrogenSupply function //sorghum uses Diffusion & Massflow variables currently double[] organNO3Supply = new double[zone.NO3N.Length]; //kg/ha - dltNo3 in old apsim double[] organNH4Supply = new double[zone.NH4N.Length]; ZoneState myZone = root.Zones.Find(z => z.Name == zone.Zone.Name); if (myZone != null) { CalculateNitrogenSupply(myZone, zone); //new code double[] diffnAvailable = new double[myZone.Diffusion.Length]; for (var i = 0; i < myZone.Diffusion.Length; ++i) { diffnAvailable[i] = myZone.Diffusion[i] - myZone.MassFlow[i]; } var totalMassFlow = MathUtilities.Sum(myZone.MassFlow); //g/m^2 var totalDiffusion = MathUtilities.Sum(diffnAvailable); //g/m^2 var potentialSupply = totalMassFlow + totalDiffusion; var actualDiffusion = 0.0; //var actualMassFlow = DltTT > 0 ? totalMassFlow : 0.0; var maxDiffusionConst = root.MaxDiffusion.Value(); double nUptakeCease = NUptakeCease.Value(); if (TTFMFromFlowering.Value() > NUptakeCease.Value()) { totalMassFlow = 0; } var actualMassFlow = totalMassFlow; if (totalMassFlow < nDemand && TTFMFromFlowering.Value() < nUptakeCease) // fixme && ttElapsed < nUptakeCease { actualDiffusion = MathUtilities.Bound(nDemand - totalMassFlow, 0.0, totalDiffusion); actualDiffusion = MathUtilities.Divide(actualDiffusion, maxDiffusionConst, 0.0); var nsupplyFraction = root.NSupplyFraction.Value(); var maxRate = root.MaxNUptakeRate.Value(); var maxUptakeRateFrac = Math.Min(1.0, (potentialSupply / root.NSupplyFraction.Value())) * root.MaxNUptakeRate.Value(); var maxUptake = Math.Max(0, maxUptakeRateFrac * DltTT.Value() - actualMassFlow); actualDiffusion = Math.Min(actualDiffusion, maxUptake); } NDiffusionSupply = actualDiffusion; NMassFlowSupply = actualMassFlow; //adjust diffusion values proportionally //make sure organNO3Supply is in kg/ha for (int layer = 0; layer < organNO3Supply.Length; layer++) { var massFlowLayerFraction = MathUtilities.Divide(myZone.MassFlow[layer], totalMassFlow, 0.0); var diffusionLayerFraction = MathUtilities.Divide(diffnAvailable[layer], totalDiffusion, 0.0); //organNH4Supply[layer] = massFlowLayerFraction * root.MassFlow[layer]; organNO3Supply[layer] = (massFlowLayerFraction * actualMassFlow + diffusionLayerFraction * actualDiffusion) / kgha2gsm; //convert to kg/ha } } //originalcode UptakeDemands.NO3N = MathUtilities.Add(UptakeDemands.NO3N, organNO3Supply); //Add uptake supply from each organ to the plants total to tell the Soil arbitrator if (UptakeDemands.NO3N.Any(n => MathUtilities.IsNegative(n))) { throw new Exception("-ve no3 uptake demand"); } UptakeDemands.NH4N = MathUtilities.Add(UptakeDemands.NH4N, organNH4Supply); N.UptakeSupply[rootIndex] += MathUtilities.Sum(organNO3Supply) * kgha2gsm * zone.Zone.Area / plant.Zone.Area; //g/m2 if (MathUtilities.IsNegative(N.UptakeSupply[rootIndex])) { throw new Exception($"-ve uptake supply for organ {(Organs[rootIndex] as IModel).Name}"); } nSupply += MathUtilities.Sum(organNO3Supply) * zone.Zone.Area; zones.Add(UptakeDemands); } return(zones); }