private void OnNitrogenChanged(NitrogenChangedType NitrogenChanges) { // Note: // Send deltas to each patch, if delta comes from soil or plant then the values are modified (partioned) // based on N content. If sender is any other module then values are passed to patches as they come string module = NitrogenChanges.SenderType.ToLower(); if ((Patch.Count > 1) && (module == "WaterModule".ToLower()) || (module == "Plant".ToLower())) { // values supplied by a module from which a different treatment for each patch is required, // they will be partitioned according to the N content in each patch, following: // - If module is Plant (uptake): partition is based on the relative concentration, at each layer, of all patches // - If module is WaterModule (leaching): // . if is removal (negative): partition is equal to a plant uptake // . if is incoming leaching: partition is based on relative concentration on the layer and above // 1- consider urea: if (hasValues(NitrogenChanges.DeltaUrea, EPSILON)) { // 1.1-send incoming dlt to be partitioned amongst patches double[][] newDelta = partitionDelta(NitrogenChanges.DeltaUrea, "urea", NPartitionApproach.ToLower()); // 1.2- send dlt's to each patch for (int k = 0; k < Patch.Count; k++) { Patch[k].dlt_urea = newDelta[k]; } } // 2- consider nh4: if (hasValues(NitrogenChanges.DeltaNH4, EPSILON)) { // 2.1- send incoming dlt to be partitioned amongst patches double[][] newDelta = partitionDelta(NitrogenChanges.DeltaNH4, "NH4", NPartitionApproach.ToLower()); // 2.2- send dlt's to each patch for (int k = 0; k < Patch.Count; k++) { Patch[k].dlt_nh4 = newDelta[k]; } } // 3- consider no3: if (hasValues(NitrogenChanges.DeltaNO3, EPSILON)) { // 3.1- send incoming dlt to be partitioned amongst patches double[][] newDelta = partitionDelta(NitrogenChanges.DeltaNO3, "NO3", NPartitionApproach.ToLower()); // 3.2- send dlt's to each patch for (int k = 0; k < Patch.Count; k++) { Patch[k].dlt_no3 = newDelta[k]; } } } else { // values will passed to patches as they come for (int k = 0; k < Patch.Count; k++) { Patch[k].dlt_urea = NitrogenChanges.DeltaUrea; Patch[k].dlt_nh4 = NitrogenChanges.DeltaNH4; Patch[k].dlt_no3 = NitrogenChanges.DeltaNO3; } } }
/// <summary>Gets the changes in mineral N made by other modules</summary> /// <param name="NitrogenChanges">The nitrogen changes.</param> public void SetNitrogenChanged(NitrogenChangedType NitrogenChanges) { OnNitrogenChanged(NitrogenChanges); }
private void OnNitrogenChanged(NitrogenChangedType NitrogenChanges) { // Note: // Send deltas to each patch, if delta comes from soil or plant then the values are modified (partioned) // based on N content. If sender is any other module then values are passed to patches as they come string module = NitrogenChanges.SenderType.ToLower(); if ((Patch.Count > 1) && (module == "WaterModule".ToLower()) || (module == "Plant".ToLower())) { // values supplied by a module from which a different treatment for each patch is required, // they will be partitioned according to the N content in each patch, following: // - If module is Plant (uptake): partition is based on the relative concentration, at each layer, of all patches // - If module is WaterModule (leaching): // . if is removal (negative): partition is equal to a plant uptake // . if is incoming leaching: partition is based on relative concentration on the layer and above // 1- consider urea: if (hasValues(NitrogenChanges.DeltaUrea, EPSILON)) { // 1.1-send incoming dlt to be partitioned amongst patches double[][] newDelta = partitionDelta(NitrogenChanges.DeltaUrea, "urea", NPartitionApproach.ToLower()); // 1.2- send dlt's to each patch for (int k = 0; k < Patch.Count; k++) Patch[k].dlt_urea = newDelta[k]; } // 2- consider nh4: if (hasValues(NitrogenChanges.DeltaNH4, EPSILON)) { // 2.1- send incoming dlt to be partitioned amongst patches double[][] newDelta = partitionDelta(NitrogenChanges.DeltaNH4, "NH4", NPartitionApproach.ToLower()); // 2.2- send dlt's to each patch for (int k = 0; k < Patch.Count; k++) Patch[k].dlt_nh4 = newDelta[k]; } // 3- consider no3: if (hasValues(NitrogenChanges.DeltaNO3, EPSILON)) { // 3.1- send incoming dlt to be partitioned amongst patches double[][] newDelta = partitionDelta(NitrogenChanges.DeltaNO3, "NO3", NPartitionApproach.ToLower()); // 3.2- send dlt's to each patch for (int k = 0; k < Patch.Count; k++) Patch[k].dlt_no3 = newDelta[k]; } } else { // values will passed to patches as they come for (int k = 0; k < Patch.Count; k++) { Patch[k].dlt_urea = NitrogenChanges.DeltaUrea; Patch[k].dlt_nh4 = NitrogenChanges.DeltaNH4; Patch[k].dlt_no3 = NitrogenChanges.DeltaNO3; } } }
/// <summary> /// Computes the distribution of N uptake over the soil profile and send the delta to soil module /// </summary> /// <exception cref="System.Exception"> /// Error on computing N uptake /// or /// N uptake source was not recognised. Please specify it as either \"sward\" or \"species\". /// </exception> private void DoSoilNitrogenUptake() { if (nUptakeSource.ToLower() == "sward") { if (soilAvailableN.Sum() > 0.0 && swardSoilNuptake > 0.0) { // there is N in the soil and there is plant uptake Soils.NitrogenChangedType NTakenUp = new Soils.NitrogenChangedType(); NTakenUp.Sender = Name; NTakenUp.SenderType = "Plant"; NTakenUp.DeltaNO3 = new double[nLayers]; NTakenUp.DeltaNH4 = new double[nLayers]; double uptakeFraction = Math.Min(1.0, swardSoilNuptake / soilAvailableN.Sum()); double speciesFraction = 0.0; if (useAltNUptake == "no") { // calc the amount of each N form taken up for (int layer = 0; layer <= RootFrontier; layer++) { NTakenUp.DeltaNH4[layer] = -Soil.NH4N[layer] * uptakeFraction; NTakenUp.DeltaNO3[layer] = -Soil.NO3N[layer] * uptakeFraction; } // partition the amount taken up between species, considering amount actually taken up foreach (PastureSpecies mySpecies in mySward) { mySpecies.mySoilNitrogenTakenUp = new double[nLayers]; if (swardSoilNuptake > 0.0) { speciesFraction = mySpecies.mySoilNuptake / swardSoilNuptake; for (int layer = 0; layer <= RootFrontier; layer++) mySpecies.mySoilNitrogenTakenUp[layer] = -(NTakenUp.DeltaNH4[layer] + NTakenUp.DeltaNO3[layer]) * speciesFraction; } } } else { // Method implemented by RCichota, // Uptake is distributed over the profile according to N availability, // this means that N and water status as well as root distribution have been taken into account double[] adjustedNH4Available; double[] adjustedNO3Available; double[] sumNH4Available = new double[nLayers]; double[] sumNO3Available = new double[nLayers]; for (int layer = 0; layer < RootFrontier; layer++) { sumNH4Available[layer] = mySward.Sum(mySpecies => mySpecies.SoilAvailableWater[layer]); sumNO3Available[layer] = mySward.Sum(mySpecies => mySpecies.SoilAvailableWater[layer]); } foreach (PastureSpecies mySpecies in mySward) { // get adjusted N available adjustedNH4Available = new double[nLayers]; adjustedNO3Available = new double[nLayers]; for (int layer = 0; layer <= mySpecies.RootFrontier; layer++) { adjustedNH4Available[layer] = soilNH4Available[layer] * mySpecies.mySoilNH4available[layer] / sumNH4Available[layer]; adjustedNO3Available[layer] = soilNO3Available[layer] * mySpecies.mySoilNO3available[layer] / sumNO3Available[layer]; } // get fraction of demand supplied by the soil uptakeFraction = Math.Min(1.0, mySpecies.mySoilNuptake / (adjustedNH4Available.Sum() + adjustedNO3Available.Sum())); // get the actual amounts taken up from each layer mySpecies.mySoilNitrogenTakenUp = new double[nLayers]; for (int layer = 0; layer <= mySpecies.RootFrontier; layer++) { mySpecies.mySoilNitrogenTakenUp[layer] = (adjustedNH4Available[layer] + adjustedNO3Available[layer]) * uptakeFraction; NTakenUp.DeltaNH4[layer] -= Soil.NH4N[layer] * uptakeFraction; NTakenUp.DeltaNO3[layer] -= Soil.NO3N[layer] * uptakeFraction; } } } double totalUptake = mySward.Sum(mySpecies => mySpecies.UptakeN.Sum()); if ((Math.Abs(swardSoilNuptake - totalUptake) > 0.0001) || (Math.Abs(NTakenUp.DeltaNH4.Sum() + NTakenUp.DeltaNO3.Sum() + totalUptake) > 0.0001)) throw new Exception("Error on computing N uptake"); // do the actual N changes NitrogenChanged.Invoke(NTakenUp); } else { // No uptake, just zero out the arrays foreach (PastureSpecies mySpecies in mySward) mySpecies.mySoilNitrogenTakenUp = new double[nLayers]; } } else { // N uptake calculated by other modules (e.g., SWIM) - not actually implemented string msg = "N uptake source was not recognised. Please specify it as either \"sward\" or \"species\"."; throw new Exception(msg); } }
/// <summary> /// Sends the nitrogen changed event. /// </summary> private void SendNitrogenChangedEvent() { NitrogenChangedType NitrogenDeltas = new NitrogenChangedType(); NitrogenDeltas.Sender = "SoilWater"; NitrogenDeltas.SenderType = "WaterModule"; NitrogenDeltas.DeltaUrea = SoilObject.GetDeltaArrayForASolute("urea"); NitrogenDeltas.DeltaNH4 = SoilObject.GetDeltaArrayForASolute("NH4"); NitrogenDeltas.DeltaNO3 = SoilObject.GetDeltaArrayForASolute("NO3"); if (NitrogenChanged != null) NitrogenChanged.Invoke(NitrogenDeltas); }
/// <summary>Old_s the on do nutrient arbitration.</summary> /// <exception cref="System.Exception">Invalid potential uptake method selected</exception> private void Old_OnDoNutrientArbitration() { // use i for the plant loop and j for the layer loop NitrogenChangedType NUptakeType = new NitrogenChangedType(); NUptakeType.Sender = Name; NUptakeType.SenderType = "Plant"; NUptakeType.DeltaNO3 = new double[Soil.Thickness.Length]; NUptakeType.DeltaNH4 = new double[Soil.Thickness.Length]; double tempSupply = 0.0; // this zeros the variable for each crop - calculates the potentialSupply for the crop for all layers - will be used to compare against demand // calculate the potentially available water and sum the demand for (int i = 0; i < plants.Count; i++) { if (NutrientUptakeMethod == 2) { demandNitrogen[i] = Math.Min(plants[i].demandNitrogen, plants[i].RootProperties.MaximumDailyNUptake); //HEB added in Maximum daily uptake constraint } else { demandNitrogen[i] = plants[i].demandNitrogen; } tempSupply = 0.0; // this is the sum of potentialSupplyNitrogenPlantLayer[i, j] across j for each plant - used in the limitation to demand for (int j = 0; j < Soil.Thickness.Length; j++) { if (NutrientUptakeMethod == 1) // use the soil KL with no water effect { potentialSupplyNitrogenPlantLayer[i, j] = MathUtilities.Divide(plants[i].RootProperties.KL[j], plants.Count, 0.0) * (Soil.NO3N[j] + Soil.NH4N[j]) * plants[i].RootProperties.RootExplorationByLayer[j]; tempSupply += potentialSupplyNitrogenPlantLayer[i, j]; // temporary add up the supply of water across all layers for this crop, then scale back if needed below double tempNO3OnlySupply = MathUtilities.Divide(plants[i].RootProperties.KL[j], plants.Count, 0.0) * Soil.NO3N[j] * plants[i].RootProperties.RootExplorationByLayer[j]; potentialSupplyPropNO3PlantLayer[i, j] = MathUtilities.Divide(tempNO3OnlySupply, potentialSupplyNitrogenPlantLayer[i, j], 0.0); } else if (NutrientUptakeMethod == 2) { //I blocked the N arbitration out to get water working correctly with the arbitrator first. /* //Fixme. The PMF implementation was not adjusting N Uptake in partially rooted layer. I have left it as this for testing but will need to introduce it soon. //method from PMF - based on concentration //HEB. I have rewritten this section of code to make it function identically to current PMF implementation for purposes of testing double relativeSoilWaterContent = 0; if (Plants[i].RootProperties.RootLengthDensityByVolume[j] > 0.0) // Test to see if there are roots in this layer. { relativeSoilWaterContent = (Soil.SoilWater.sw_dep[j] - Soil.SoilWater.ll15_dep[j]) / (Soil.SoilWater.dul_dep[j] - Soil.SoilWater.ll15_dep[j]); relativeSoilWaterContent = MathUtilities.Constrain(relativeSoilWaterContent, 0, 1.0); potentialSupplyNitrogenPlantLayer[i, j] = Soil.NO3Nppm[j] * Soil.NO3N[j] * plants[i].RootProperties.KNO3 * relativeSoilWaterContent; } else { potentialSupplyNitrogenPlantLayer[i, j] = 0; } double tempNO3OnlySupply = 0; tempNO3OnlySupply = potentialSupplyNitrogenPlantLayer[i, j]; potentialSupplyNitrogenPlantLayer[i, j] += Soil.NH4Nppm[j] * Soil.NH4N[j] * plants[i].RootProperties.KNH4 * relativeSoilWaterContent; tempSupply += potentialSupplyNitrogenPlantLayer[i, j]; // temporary add up the supply of water across all layers for this crop, then scale back if needed below if (potentialSupplyNitrogenPlantLayer[i, j] == 0) potentialSupplyPropNO3PlantLayer[i, j] = 1; else potentialSupplyPropNO3PlantLayer[i, j] = tempNO3OnlySupply / potentialSupplyNitrogenPlantLayer[i, j]; */ } else if (NutrientUptakeMethod == 3) { //method from OilPlam - based on amount double relativeSoilWaterContent = 0; relativeSoilWaterContent = MathUtilities.Constrain(MathUtilities.Divide((Soil.Water[j] - Soil.SoilWater.LL15mm[j]), (Soil.SoilWater.DULmm[j] - Soil.SoilWater.LL15mm[j]), 0.0), 0.0, 1.0); potentialSupplyNitrogenPlantLayer[i, j] = Math.Max(0.0, plants[i].RootProperties.RootExplorationByLayer[j] * (MathUtilities.Divide(plants[i].RootProperties.KNO3, plants.Count, 0.0) * Soil.NO3N[j] + MathUtilities.Divide(plants[i].RootProperties.KNH4, plants.Count, 0.0) * Soil.NH4N[j]) * relativeSoilWaterContent); tempSupply += potentialSupplyNitrogenPlantLayer[i, j]; // temporary add up the supply of water across all layers for this crop, then scale back if needed below double tempNO3OnlySupply = 0; tempNO3OnlySupply = Math.Max(0.0, plants[i].RootProperties.RootExplorationByLayer[j] * (MathUtilities.Divide(plants[i].RootProperties.KNO3, plants.Count, 0.0) * Soil.NO3N[j]) * relativeSoilWaterContent); potentialSupplyPropNO3PlantLayer[i, j] = MathUtilities.Divide(tempNO3OnlySupply, potentialSupplyNitrogenPlantLayer[i, j], 0.0); } else { throw new Exception("Invalid potential uptake method selected"); } } for (int j = 0; j < Soil.Thickness.Length; j++) { // if the potential supply calculated above is greater than demand then scale it back - note that this is still a potential supply as a solo crop potentialSupplyNitrogenPlantLayer[i, j] = potentialSupplyNitrogenPlantLayer[i, j] * Math.Min(1.0, MathUtilities.Divide(demandNitrogen[i], tempSupply, 0.0)); } } // close the plants loop - by here have the potentialSupply by plant and layer. This is the sole plant demand - no competition yet // calculate the maximum amount of nitrogen available in each layer double[] totalAvailableNitrogen; totalAvailableNitrogen = new double[Soil.Thickness.Length]; for (int j = 0; j < Soil.Thickness.Length; j++) { totalAvailableNitrogen[j] = Soil.NO3N[j] + Soil.NH4N[j]; } // compare the potential nitrogen supply against the total available nitrogen // if supply exceeds demand then satisfy all demands, otherwise scale back by relative demand for (int j = 0; j < Soil.Thickness.Length; j++) // loop through the layers in the outer loop { for (int i = 0; i < plants.Count; i++) { uptakeNitrogenPlantLayer[i, j] = potentialSupplyNitrogenPlantLayer[i, j] * MathUtilities.Constrain(MathUtilities.Divide(MathUtilities.Sum(totalAvailableNitrogen), MathUtilities.Sum(demandNitrogen), 0.0), 0.0, 1.0); uptakeNitrogenPropNO3PlantLayer[i, j] = 0.0; NUptakeType.DeltaNO3[j] += -1.0 * uptakeNitrogenPlantLayer[i, j] * potentialSupplyPropNO3PlantLayer[i, j]; // -ve to reduce water content in the soil NUptakeType.DeltaNH4[j] += -1.0 * uptakeNitrogenPlantLayer[i, j] * (1.0 - potentialSupplyPropNO3PlantLayer[i, j]); // -ve to reduce water content in the soil if ((i == plants.Count - 1) && (j == 0)) { //Summary.WriteMessage(this, potentialSupplyNitrogenPlantLayer[0, j] + " " + potentialSupplyNitrogenPlantLayer[i, j] + " " + NUptakeType.DeltaNO3[j]); } } } // close the layer loop for (int i = 0; i < plants.Count; i++) { double[] dummyArray1 = new double[Soil.Thickness.Length]; // have to create a new array for each plant to avoid the .NET pointer thing double[] dummyArray2 = new double[Soil.Thickness.Length]; // have to create a new array for each plant to avoid the .NET pointer thing for (int j = 0; j < Soil.Thickness.Length; j++) // cannot set a particular dimension from a 2D arrary into a 1D array directly so need a temporary variable { dummyArray1[j] = uptakeNitrogenPlantLayer[i, j]; dummyArray2[j] = uptakeNitrogenPropNO3PlantLayer[i, j]; } double testingvalue = MathUtilities.Sum(dummyArray1); plants[i].uptakeNitrogen = dummyArray1; plants[i].uptakeNitrogenPropNO3 = dummyArray2; // debugging into SummaryFile //Summary.WriteMessage(FullPath, "Arbitrator is setting the value of plants[" + i.ToString() + "].supplyWater(3) to " + plants[i].supplyWater[3].ToString()); } // send the change in soil soil nitrate and ammonium to the soil nitrogen module if (NitrogenChanged != null) NitrogenChanged.Invoke(NUptakeType); }
/// <summary>Nitrogen uptake process</summary> /// <returns></returns> /// <exception cref="System.Exception"></exception> private double SNUptakeProcess() { //Uptake from the root_zone Soils.NitrogenChangedType NUptake = new Soils.NitrogenChangedType(); NUptake.Sender = Name; NUptake.SenderType = "Plant"; NUptake.DeltaNO3 = new double[Soil.Thickness.Length]; NUptake.DeltaNH4 = new double[Soil.Thickness.Length]; double Fraction = 0; if (p_soilNavailable > 0) { Fraction = Math.Min(1.0, p_soilNuptake / p_soilNavailable); } double n_uptake = 0; if (alt_N_uptake == "yes") { double uptake_multiplier = double.MaxValue, totSWUptake = SWUptake.Sum(); double[] availableNH4_bylayer = new double[Soil.Thickness.Length], availableNO3_bylayer = new double[Soil.Thickness.Length], diffNH4_bylayer = new double[Soil.Thickness.Length], diffNO3_bylayer = new double[Soil.Thickness.Length]; for (int layer = 0; layer < Soil.Thickness.Length; layer++) { double totN = Soil.NH4N[layer] + Soil.NO3N[layer], fracH2O = SWUptake[layer] / totSWUptake; if (totN > 0) { availableNH4_bylayer[layer] = fracH2O * Soil.NH4N[layer] / totN; availableNO3_bylayer[layer] = fracH2O * Soil.NO3N[layer] / totN; //if we have no3 and nh4 in this layer then calculate our uptake multiplier, otherwise set it to 0 //the idea behind the multiplier is that it allows us to calculate the max amount of N we can extract //without forcing any of the layers below 0 AND STILL MAINTAINING THE RATIO as calculated with fracH2O //NOTE: it doesn't matter whether we use nh4 or no3 for this calculation, we will get the same answer regardless uptake_multiplier = Soil.NH4N[layer] * Soil.NO3N[layer] > 0 ? Math.Min(uptake_multiplier, Soil.NH4N[layer] / availableNH4_bylayer[layer]) : 0; } else { availableNH4_bylayer[layer] = 0; availableNO3_bylayer[layer] = 0; } } //adjust availability values with the multiplier we just calculated availableNH4_bylayer = availableNH4_bylayer.Select(x => x * uptake_multiplier).ToArray(); availableNO3_bylayer = availableNO3_bylayer.Select(x => x * uptake_multiplier).ToArray(); //calculate how much no3/nh4 will be left in the soil layers (diff_nxx[layer] = nxx[layer] - availableNH4_bylayer[layer]) diffNH4_bylayer = Soil.NH4N.Select((x, layer) => Math.Max(0, x - availableNH4_bylayer[layer])).ToArray(); diffNO3_bylayer = Soil.NO3N.Select((x, layer) => Math.Max(0, x - availableNO3_bylayer[layer])).ToArray(); //adjust this by the sum of all leftover so we get a ratio we can use later double sum_diff = diffNH4_bylayer.Sum() + diffNO3_bylayer.Sum(); diffNH4_bylayer = diffNH4_bylayer.Select(x => x / sum_diff).ToArray(); diffNO3_bylayer = diffNO3_bylayer.Select(x => x / sum_diff).ToArray(); double //available N from our 'withwater' calcs (still some left in the 'diff' arrays if this isn't enough) avail_withwater = availableNH4_bylayer.Sum() + availableNO3_bylayer.Sum(), //if not enough N was available via the 'withwater' calcs this will be positive and will require more from the 'diffs' we calculated shortfall_withwater = p_soilNuptake - avail_withwater; if (shortfall_withwater > 0) { //this cap should not be needed because shortfall is already capped via the math.min in the scaled_demand calcs (leave it here though) double scaled_diff = Math.Min(shortfall_withwater / avail_withwater, 1); availableNH4_bylayer = availableNH4_bylayer.Select((x, layer) => x + shortfall_withwater * diffNH4_bylayer[layer]).ToArray(); availableNO3_bylayer = availableNO3_bylayer.Select((x, layer) => x + shortfall_withwater * diffNO3_bylayer[layer]).ToArray(); } NUptake.DeltaNH4 = availableNH4_bylayer.Select(x => x * -1).ToArray(); NUptake.DeltaNO3 = availableNO3_bylayer.Select(x => x * -1).ToArray(); for (int layer = 0; layer < p_bottomRootLayer; layer++) n_uptake += SNUptake[layer] = (NUptake.DeltaNH4[layer] + NUptake.DeltaNO3[layer]) * -1; double[] diffs = NUptake.DeltaNO3.Select((x, i) => Math.Max(Soil.NO3N[i] + x + 0.00000001, 0)).ToArray(); if (diffs.Any(x => x == 0)) throw new Exception(); } /*if (ValsMode == "withwater") { NUptake.DeltaNO3 = SP[0].availableNO3_bylayer.Select(x => x * -1).ToArray(); NUptake.DeltaNH4 = SP[0].availableNH4_bylayer.Select(x => x * -1).ToArray(); for (int layer = 0; layer < p_bottomRootLayer; layer++) SNUptake[layer] = SP[0].availableNO3_bylayer[layer] + SP[0].availableNH4_bylayer[layer]; n_uptake = SNUptake.Sum(); }*/ else { for (int layer = 0; layer < p_bottomRootLayer; layer++) { //N are taken up only in top layers that root can reach (including buffer Zone). n_uptake += (Soil.NO3N[layer] + Soil.NH4N[layer]) * Fraction; SNUptake[layer] = (Soil.NO3N[layer] + Soil.NH4N[layer]) * Fraction; NUptake.DeltaNO3[layer] = -Soil.NO3N[layer] * Fraction; NUptake.DeltaNH4[layer] = -Soil.NH4N[layer] * Fraction; } } if (NitrogenChanged != null) NitrogenChanged.Invoke(NUptake); return n_uptake; }
//called at end of OnProcess() Event Handler. /// <summary> /// Sugar_set_other_variableses the specified i_dlt_no3gsm. /// </summary> /// <param name="i_dlt_no3gsm">The i_dlt_no3gsm.</param> /// <param name="i_dlt_nh4gsm">The i_dlt_nh4gsm.</param> /// <param name="i_dlt_sw_dep">The i_dlt_sw_dep.</param> void sugar_set_other_variables(double[] i_dlt_no3gsm, double[] i_dlt_nh4gsm, double[] i_dlt_sw_dep) { //*+ Purpose //* Set the value of a variable or array in other module/s. //*+ Mission Statement //* Set value of variable or array in other module/s //*+ Notes //* a flag is set if any of the totals is requested. The totals are //* reset during the next process phase when this happens. double[] l_dlt_NO3 = new double[max_layer]; //! soil NO3 change (kg/ha) double[] l_dlt_NH4 = new double[max_layer]; //! soil NO3 change (kg/ha) int num_layers; //! number of layers if (uptake_source == "calc") { num_layers = count_of_real_vals(dlayer, max_layer); //sv- I added this resize, from max_layer to num_layer. // Done so the NitrogenChanges.DeltaNO3 etc. gets an array of a sensible size. Array.Resize(ref l_dlt_NO3, num_layers); Array.Resize(ref l_dlt_NH4, num_layers); Array.Resize(ref i_dlt_sw_dep, num_layers); for (int layer = 0; layer < num_layers; layer++) { l_dlt_NO3[layer] = i_dlt_no3gsm[layer] * gm2kg / sm2ha; l_dlt_NH4[layer] = i_dlt_nh4gsm[layer] * gm2kg / sm2ha; } NitrogenChangedType NitrogenChanges = new NitrogenChangedType(); NitrogenChanges.Sender = "Sugarcane"; NitrogenChanges.DeltaNO3 = l_dlt_NO3; NitrogenChanges.DeltaNH4 = l_dlt_NH4; NitrogenChanged.Invoke(NitrogenChanges); //trigger/invoke the Nitrogen Changed Event WaterChangedType WaterChanges = new WaterChangedType(); WaterChanges.DeltaWater = i_dlt_sw_dep; WaterChanged.Invoke(WaterChanges); //trigger/invoke the Water Changed Event } else if (uptake_source == "swim3") { num_layers = count_of_real_vals(dlayer, max_layer); //sv- I added this resize, from max_layer to num_layer // Done so the NitrogenChanges.DeltaNO3 etc. gets an array of the sensible size. Array.Resize(ref l_dlt_NO3, num_layers); Array.Resize(ref l_dlt_NH4, num_layers); for (int layer = 0; layer < num_layers; layer++) { l_dlt_NO3[layer] = i_dlt_no3gsm[layer] * gm2kg / sm2ha; l_dlt_NH4[layer] = i_dlt_nh4gsm[layer] * gm2kg / sm2ha; } NitrogenChangedType NitrogenChanges = new NitrogenChangedType(); NitrogenChanges.Sender = "Sugarcane"; NitrogenChanges.DeltaNO3 = l_dlt_NO3; NitrogenChanges.DeltaNH4 = l_dlt_NH4; NitrogenChanged.Invoke(NitrogenChanges); //trigger/invoke the Nitrogen Changed Event } else { //! assume that the module that calculated uptake has also updated these pools. } }
/// <summary>Does the n balance.</summary> /// <exception cref="System.Exception">Error in N Allocation</exception> private void DoNBalance() { NitrogenChangedType NUptakeType = new NitrogenChangedType(); NUptakeType.Sender = Name; NUptakeType.SenderType = "Plant"; NUptakeType.DeltaNO3 = new double[Soil.Thickness.Length]; NUptakeType.DeltaNH4 = new double[Soil.Thickness.Length]; double StartN = PlantN; double StemNDemand = StemGrowth * StemNConcentration.Value / 100.0 * 10.0; // factor of 10 to convert g/m2 to kg/ha double RootNDemand = Math.Max(0.0, (RootMass * RootNConcentration.Value / 100.0 - RootN)) * 10.0; // kg/ha double FrondNDemand = Math.Max(0.0, (FrondMass * FrondMaximumNConcentration.Value / 100.0 - FrondN)) * 10.0; // kg/ha double BunchNDemand = Math.Max(0.0, (BunchMass * BunchNConcentration.Value / 100.0 - BunchN)) * 10.0; // kg/ha Ndemand = StemNDemand + FrondNDemand + RootNDemand + BunchNDemand; //kg/ha for (int j = 0; j < Soil.SoilWater.LL15mm.Length; j++) { double swaf = 0; swaf = (Soil.Water[j] - Soil.SoilWater.LL15mm[j]) / (Soil.SoilWater.DULmm[j] - Soil.SoilWater.LL15mm[j]); swaf = Math.Max(0.0, Math.Min(swaf, 1.0)); double no3ppm = Soil.NO3N[j] * (100.0 / (Soil.BD[j] * Soil.Thickness[j])); PotNUptake[j] = Math.Max(0.0, RootProportion(j, RootDepth) * KNO3.Value * Soil.NO3N[j] * swaf); } double TotPotNUptake = MathUtilities.Sum(PotNUptake); double Fr = Math.Min(1.0, Ndemand / TotPotNUptake); for (int j = 0; j < Soil.SoilWater.LL15mm.Length; j++) { NUptake[j] = PotNUptake[j] * Fr; NUptakeType.DeltaNO3[j] = -NUptake[j]; } if (NitrogenChanged != null) NitrogenChanged.Invoke(NUptakeType); Fr = Math.Min(1.0, Math.Max(0, MathUtilities.Sum(NUptake) / BunchNDemand)); double DeltaBunchN = BunchNDemand * Fr; double Tot = 0; foreach (BunchType B in Bunches) { Tot += Math.Max(0.0, B.Mass * BunchNConcentration.Value / 100.0 - B.N) * Fr / SowingData.Population; B.N += Math.Max(0.0, B.Mass * BunchNConcentration.Value / 100.0 - B.N) * Fr; } // Calculate fraction of N demand for Vegetative Parts if ((Ndemand - DeltaBunchN) > 0) Fr = Math.Max(0.0, ((MathUtilities.Sum(NUptake) - DeltaBunchN) / (Ndemand - DeltaBunchN))); else Fr = 0.0; StemN += StemNDemand / 10 * Fr; double[] RootNDef = new double[Soil.SoilWater.LL15mm.Length]; double TotNDef = 1e-20; for (int j = 0; j < Soil.SoilWater.LL15mm.Length; j++) { RootNDef[j] = Math.Max(0.0, Roots[j].Mass * RootNConcentration.Value / 100.0 - Roots[j].N); TotNDef += RootNDef[j]; } for (int j = 0; j < Soil.SoilWater.LL15mm.Length; j++) Roots[j].N += RootNDemand / 10 * Fr * RootNDef[j] / TotNDef; foreach (FrondType F in Fronds) F.N += Math.Max(0.0, F.Mass * FrondMaximumNConcentration.Value / 100.0 - F.N) * Fr; double EndN = PlantN; double Change = EndN - StartN; double Uptake = MathUtilities.Sum(NUptake) / 10.0; if (Math.Abs(Change - Uptake) > 0.001) throw new Exception("Error in N Allocation"); double Nact = FrondNConc; double Ncrit = FrondCriticalNConcentration.Value; double Nmin = FrondMinimumNConcentration.Value; Fn = Math.Min(Math.Max(0.0, (Nact - Nmin) / (Ncrit - Nmin)), 1.0); }
/// <summary>Update the water and N balance.</summary> private void UpdateWaterAndNBalance() { NitrogenChangedType NitrogenUptake = new NitrogenChangedType(); NitrogenUptake.Sender = "Plant"; NitrogenUptake.SenderType = "Plant"; NitrogenUptake.DeltaNO3 = MathUtilities.Multiply_Value(dlt_no3gsm, Conversions.gm2kg / Conversions.sm2ha); NitrogenUptake.DeltaNH4 = MathUtilities.Multiply_Value(dlt_nh4gsm, Conversions.gm2kg / Conversions.sm2ha); Util.Debug("Root.NitrogenUptake.DeltaNO3=%f", MathUtilities.Sum(NitrogenUptake.DeltaNO3)); Util.Debug("Root.NitrogenUptake.DeltaNH4=%f", MathUtilities.Sum(NitrogenUptake.DeltaNH4)); NitrogenChanged.Invoke(NitrogenUptake); // Send back delta water and nitrogen back to APSIM. if (!SwimIsPresent) { WaterChangedType WaterUptake = new WaterChangedType(); WaterUptake.DeltaWater = dlt_sw_dep; Util.Debug("Root.WaterUptake=%f", MathUtilities.Sum(WaterUptake.DeltaWater)); WaterChanged.Invoke(WaterUptake); } }
/// <summary> /// Set the n uptake for today /// </summary> public void SetNUptake(List<ZoneWaterAndN> info) { NitrogenChangedType NUptakeType = new NitrogenChangedType(); NUptakeType.Sender = Name; NUptakeType.SenderType = "Plant"; NUptakeType.DeltaNO3 = new double[Soil.Thickness.Length]; NUptakeType.DeltaNH4 = new double[Soil.Thickness.Length]; NO3Uptake = info[0].NO3N; NH4Uptake = info[0].NH4N; for (int j = 0; j < Soil.SoilWater.LL15mm.Length; j++) { NUptakeType.DeltaNO3[j] = -NO3Uptake[j]; NUptakeType.DeltaNH4[j] = -NH4Uptake[j]; } if (NitrogenChanged != null) NitrogenChanged.Invoke(NUptakeType); }
/// <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.Name); if (zone == null) throw new Exception("Cannot find a zone called " + thisZone.Name); // 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); } }
/// <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>Apply fertiliser.</summary> /// <param name="Amount">The amount.</param> /// <param name="Type">The type.</param> /// <param name="Depth">The depth.</param> /// <exception cref="ApsimXException">Cannot find fertiliser type ' + Type + '</exception> public void Apply(double Amount, Types Type, double Depth = 0.0) { if (Amount > 0 && NitrogenChanged != null) { // find the layer that the fertilizer is to be added to. int layer = GetLayerDepth(Depth, Soil.Thickness); FertiliserType fertiliserType = Definitions.FirstOrDefault(f => f.Name == Type.ToString()); if (fertiliserType == null) throw new ApsimXException(this, "Cannot find fertiliser type '" + Type + "'"); NitrogenChangedType NitrogenChanges = new NitrogenChangedType(); NitrogenChanges.Sender = Apsim.FullPath(this); if (fertiliserType.FractionNO3 != 0) { NitrogenChanges.DeltaNO3 = new double[Soil.Thickness.Length]; NitrogenChanges.DeltaNO3[layer] = Amount * fertiliserType.FractionNO3; NitrogenApplied += Amount * fertiliserType.FractionNO3; } if (fertiliserType.FractionNH4 != 0) { NitrogenChanges.DeltaNH4 = new double[Soil.Thickness.Length]; NitrogenChanges.DeltaNH4[layer] = Amount * fertiliserType.FractionNH4; NitrogenApplied += Amount * fertiliserType.FractionNH4; } if (fertiliserType.FractionUrea != 0) { NitrogenChanges.DeltaUrea = new double[Soil.Thickness.Length]; NitrogenChanges.DeltaUrea[layer] = Amount * fertiliserType.FractionUrea; NitrogenApplied += Amount * fertiliserType.FractionUrea; } NitrogenChanged.Invoke(NitrogenChanges); Summary.WriteMessage(this, string.Format("{0} kg/ha of {1} added at depth {2} layer {3}", Amount, Type, Depth, layer + 1)); } }
/// <summary> /// Computes the distribution of N uptake over the soil profile and send the delta to soil module /// </summary> /// <exception cref="System.Exception"> /// Error on computing N uptake /// or /// N uptake source was not recognised. Please specify it as either \"sward\" or \"species\". /// </exception> private void DoSoilNitrogenUptake() { if (myNitrogenUptakeSource.ToLower() == "species") { // check whether there is any uptake if (mySoilAvailableN.Sum() > 0.0 && mySoilNuptake > 0.0) { Soils.NitrogenChangedType NUptake = new Soils.NitrogenChangedType(); NUptake.Sender = Name; NUptake.SenderType = "Plant"; NUptake.DeltaNO3 = new double[nLayers]; NUptake.DeltaNH4 = new double[nLayers]; mySoilNitrogenTakenUp = new double[nLayers]; double uptakeFraction = 0; if (useAltNUptake == "no") { if (mySoilAvailableN.Sum() > 0.0) uptakeFraction = Math.Min(1.0, MathUtilities.Divide(mySoilNuptake, mySoilAvailableN.Sum(), 0.0)); for (int layer = 0; layer <= myRootFrontier; layer++) { NUptake.DeltaNH4[layer] = -Soil.NH4N[layer] * uptakeFraction; NUptake.DeltaNO3[layer] = -Soil.NO3N[layer] * uptakeFraction; mySoilNitrogenTakenUp[layer] = -(NUptake.DeltaNH4[layer] + NUptake.DeltaNO3[layer]); } } else { // Method implemented by RCichota, // N uptake is distributed considering water uptake and N availability double[] fNH4Avail = new double[nLayers]; double[] fNO3Avail = new double[nLayers]; double[] fWUptake = new double[nLayers]; double totNH4Available = mySoilAvailableN.Sum(); double totNO3Available = mySoilAvailableN.Sum(); double totWuptake = mySoilWaterTakenUp.Sum(); for (int layer = 0; layer < nLayers; layer++) { fNH4Avail[layer] = Math.Min(1.0, MathUtilities.Divide(mySoilAvailableN[layer], totNH4Available, 0.0)); fNO3Avail[layer] = Math.Min(1.0, MathUtilities.Divide(mySoilAvailableN[layer], totNO3Available, 0.0)); fWUptake[layer] = Math.Min(1.0, MathUtilities.Divide(mySoilWaterTakenUp[layer], totWuptake, 0.0)); } double totFacNH4 = fNH4Avail.Sum() + fWUptake.Sum(); double totFacNO3 = fNO3Avail.Sum() + fWUptake.Sum(); for (int layer = 0; layer < nLayers; layer++) { uptakeFraction = Math.Min(1.0, MathUtilities.Divide(fNH4Avail[layer] + fWUptake[layer], totFacNH4, 0.0)); NUptake.DeltaNH4[layer] = -Soil.NH4N[layer] * uptakeFraction; uptakeFraction = Math.Min(1.0, MathUtilities.Divide(fNO3Avail[layer] + fWUptake[layer], totFacNO3, 0.0)); NUptake.DeltaNO3[layer] = -Soil.NO3N[layer] * uptakeFraction; mySoilNitrogenTakenUp[layer] = NUptake.DeltaNH4[layer] + NUptake.DeltaNO3[layer]; } } //mySoilUptakeN.Sum() 2.2427998752781684 double if (Math.Abs(mySoilNuptake - mySoilNitrogenTakenUp.Sum()) > 0.0001) throw new Exception("Error on computing N uptake"); // do the actual N changes NitrogenChanged.Invoke(NUptake); } else { // no uptake, just zero out the array mySoilNitrogenTakenUp = new double[nLayers]; } } else { // N uptake calculated by other modules (e.g., SWIM) string msg = "N uptake source was not recognised. Please specify it as either \"sward\" or \"species\"."; throw new Exception(msg); } }
/// <summary> /// Sets the values of solute variables from other modules /// </summary> /// <exception cref="System.Exception"> /// -ve concentration in apswim_set_solute_variables + Environment.NewLine + mess /// or /// -ve value for solute concentration + Environment.NewLine + mess /// </exception> private void SetSoluteVariables() { //+ Purpose // Set the values of solute variables from other modules //+ Changes // 21-6-96 NIH - Changed set_double_array to post construct // RCichota - 26/01/2010 - Add test to make sure SWIM will not send a -ve value // RCichota - 12/Jul/2010 - add simple test for -ve solution concentration NitrogenChangedType ndata = new NitrogenChangedType(); double[] solute_n = new double[n + 1]; // solute concn in layers(kg/ha) double[] dlt_solute_s = new double[n + 1]; // solute concn in layers(kg/ha) // initialise the NitrogenChanged data to zero ndata.DeltaUrea = new double[n + 1]; ndata.DeltaNH4 = new double[n + 1]; ndata.DeltaNO3 = new double[n + 1]; for (int solnum = 0; solnum < num_solutes; solnum++) { for (int node = 0; node <= n; node++) { // Step One - calculate total solute in node from solute in // water and Freundlich isotherm. if (csl[solnum][node] < 0.0) { string mess = String.Format(" solution {0}({1,3}) = {2,12:G6}", solute_names[solnum], node, csl[solnum][node]); throw new Exception("-ve concentration in apswim_set_solute_variables" + Environment.NewLine + mess); } double Ctot, dCtot; Freundlich(node, solnum, ref csl[solnum][node], out Ctot, out dCtot); // Note:- Sometimes small numerical errors can leave // -ve concentrations. Test if values are within limits. if (Math.Abs(Ctot) < 1e-100) { // Ctot is REALLY small, its value can be disregarded // set to zero to avoid underflow with reals Ctot = 0.0; } else if (Ctot < 0.0) { // Ctot is negative and a fatal error is thrown. Should not happen as it has been tested on apswim_freundlich string mess = String.Format(" Total {0}({1,3}) = {2,12:G6}", solute_names[solnum], node, Ctot); throw new Exception("-ve value for solute concentration" + Environment.NewLine + mess); // Ctot = 0.0; } //else Ctot is positive // convert solute ug/cc soil to kg/ha for node // // kg ug cc soil kg // -- = -------- p%x -------- p%x -- // ha cc soil ha ug Ctot = Ctot // ug/cc soil * (dx[node] * 1.0e8) // cc soil/ha * 1e-9; // kg/ug // finished testing - assign value to array element solute_n[node] = Ctot; dlt_solute_s[node] = Ctot - cslstart[solnum][node]; } // Added by RCichota - using NitrogenChanged event to modify dlt_N's if (solute_names[solnum] == "urea") Array.Copy(dlt_solute_s, ndata.DeltaUrea, n + 1); else if (solute_names[solnum] == "nh4") Array.Copy(dlt_solute_s, ndata.DeltaNH4, n + 1); else if (solute_names[solnum] == "no3") Array.Copy(dlt_solute_s, ndata.DeltaNO3, n + 1); else { ///// TODO: ///// string compName = paddock.SiblingNameFromId(solute_owners[solnum]); ///// My.Set(compName + ".dlt_" + solute_names[solnum], dlt_solute_s); } } // Send a NitrogenChanged event to the system ndata.Sender = "SWIM"; ndata.SenderType = "WaterModule"; NitrogenChanged.Invoke(ndata); }
/// <summary>Sends the delta nitrogen to the soil module.</summary> private void DoSoilNitrogenUptake() { if ((mySoilNH4Uptake.Sum() + mySoilNO3Uptake.Sum()) > Epsilon) { NitrogenChangedType nitrogenTakenUp = new NitrogenChangedType(); nitrogenTakenUp.Sender = Name; nitrogenTakenUp.SenderType = "Plant"; nitrogenTakenUp.DeltaNO3 = new double[nLayers]; nitrogenTakenUp.DeltaNH4 = new double[nLayers]; for (int layer = 0; layer <= roots.BottomLayer; layer++) { nitrogenTakenUp.DeltaNH4[layer] = -mySoilNH4Uptake[layer]; nitrogenTakenUp.DeltaNO3[layer] = -mySoilNO3Uptake[layer]; } if (NitrogenChanged != null) NitrogenChanged.Invoke(nitrogenTakenUp); } }
/// <summary> /// Send the nitrogen uptake arrays back to the plants and send the change in nitrogen back to the soil /// </summary> /// <param name="resourceToArbitrate">The resource to arbitrate.</param> private void SetNitrogenUptake(string resourceToArbitrate) { NitrogenChangedType NUptakeType = new NitrogenChangedType(); NUptakeType.Sender = Name; NUptakeType.SenderType = "Plant"; NUptakeType.DeltaNO3 = new double[Soil.Thickness.Length]; NUptakeType.DeltaNH4 = new double[Soil.Thickness.Length]; for (int p = 0; p < plants.Count; p++) { double[] dummyArray1 = new double[Soil.Thickness.Length]; // have to create a new array for each plant to avoid the .NET pointer thing - will have to re-think this with when zones come in double[] dummyArray2 = new double[Soil.Thickness.Length]; // have to create a new array for each plant to avoid the .NET pointer thing - will have to re-think this with when zones come in for (int l = 0; l < Soil.Thickness.Length; l++) { for (int z = 0; z < zones; z++) // for now set zones is to 1 { for (int b = 0; b < bounds; b++) { for (int f = 0; f < forms; f++) { dummyArray1[l] += uptake[p, l, z, b, f]; // add the forms together to give the total nitrogen uptake if (f == 0) { NUptakeType.DeltaNO3[l] += -1.0 * uptake[p, l, z, b, f]; dummyArray2[l] += uptake[p, l, z, b, f]; // nitrate only uptake so can do the proportion before sending to the plant } else { NUptakeType.DeltaNH4[l] += -1.0 * uptake[p, l, z, b, f]; } } } } } // set uptakes in each plant plants[p].uptakeNitrogen = dummyArray1; for (int l = 0; l < Soil.Thickness.Length; l++) // don't forget to deal with zones at some point { dummyArray2[l] = MathUtilities.Divide(dummyArray2[l], dummyArray1[l], 0.0); } plants[p].uptakeNitrogenPropNO3 = dummyArray2; } // and finally set the changed soil resources if (NitrogenChanged != null) NitrogenChanged.Invoke(NUptakeType); }
/// <summary> /// Send the nitrogen uptake arrays back to the plants and send the change in nitrogen back to the soil /// </summary> /// <param name="resourceToArbitrate"></param> private void SetNitrogenUptake(string resourceToArbitrate) { zones = -1; int maxPlants = 0; int maxLayers = 0; foreach (Zone zone in Apsim.FindAll(Simulation, typeof(Zone))) //foreach (Zone zone in Simulation.FindAll(typeof(Zone))) { zones += 1; // Find plants in paddock. List<ICrop2> plants = zone.Plants; maxPlants = Math.Max(plants.Count, maxPlants); // Find soil in paddock. Soil Soil = (Soil)Apsim.Find(zone, typeof(Soil)); maxLayers = Math.Max(Soil.Thickness.Length, maxLayers); double[] dummyArray1 = new double[Soil.Thickness.Length]; // have to create a new array for each plant to avoid the .NET pointer thing - will have to re-think this with when zones come in double[] dummyArray2 = new double[Soil.Thickness.Length]; // have to create a new array for each plant to avoid the .NET pointer thing - will have to re-think this with when zones come in // move this inside the zone loop - needs to get zeroed for each seperate zone NitrogenChangedType NUptakeType = new NitrogenChangedType(); NUptakeType.Sender = Name; NUptakeType.SenderType = "Plant"; NUptakeType.DeltaNO3 = new double[Soil.Thickness.Length]; NUptakeType.DeltaNH4 = new double[Soil.Thickness.Length]; for (int p = 0; p < maxPlants; p++) { for (int l = 0; l < maxLayers; l++) { for (int b = 0; b < bounds; b++) { for (int f = 0; f < forms; f++) { dummyArray1[l] += uptake[p, l, z, b, f]; // add the forms together to give the total nitrogen uptake if (f == 0) { NUptakeType.DeltaNO3[l] += -1.0 * uptake[p, l, z, b, f]; dummyArray2[l] += uptake[p, l, z, b, f]; // nitrate only uptake so can do the proportion before sending to the plant } else { NUptakeType.DeltaNH4[l] += -1.0 * uptake[p, l, z, b, f]; } } } } // set uptakes in each plant plants[p].uptakeNitrogen = dummyArray1; for (int l = 0; l < Soil.Thickness.Length; l++) // don't forget to deal with zones at some point { dummyArray2[l] = MathUtilities.Divide(dummyArray2[l], dummyArray1[l], 0.0); // would be nice to have a utility for this } plants[p].uptakeNitrogenPropNO3 = dummyArray2; } // and finally set the changed soil resources if (NitrogenChanged != null) NitrogenChanged.Invoke(NUptakeType); } }