/// <summary> /// Test the Removal to determine if there is any quantity of forage removed. /// </summary> /// <returns>True if some herbage has been removed</returns> public bool SomethingRemoved() { bool result = false; // get the removal for each forage int forageIdx = 0; ForageInfo forageInf = this.ForageByIndex(forageIdx); while ((forageInf != null) && (!result)) { result = forageInf.SomethingRemoved(); forageIdx++; forageInf = this.ForageByIndex(forageIdx); } return(result); }
/// <summary> /// Update the forage data for this crop/agpasture object /// </summary> /// <param name="forageObj">The crop/pasture object</param> public void UpdateForages(IPlantDamage forageObj) { // ensure this forage is in the list // the forage key in this case is component name ForageInfo forage = this.forages.ByName(this.ForageHostName); if (forage == null) { // if this cohort doesn't exist in the forage list forage = new ForageInfo(); forage.Name = this.ForageHostName.ToLower(); this.owningPaddock.AssignForage(forage); // the paddock in the model can access this forage this.forages.Add(forage); // create a new forage for this cohort } // TODO: just assuming one forage cohort in this component (expand here?) this.PassGrazingInputs(forage, this.Crop2GrazingInputs(forageObj), "g/m^2"); // then update it's value }
/// <summary> /// Assign a forage to this paddock /// </summary> /// <param name="forage">The forage object to assign to this paddock</param> public void AssignForage(ForageInfo forage) { this.Forages.Add(forage); forage.InPaddock = this; }
/// <summary> /// Copies a Plant/AgPasture object biomass organs into GrazingInputs object /// This object may then get scaled to kg/ha /// </summary> /// <param name="forageObj">The forage object - a Plant/AgPasture component</param> /// <returns>The grazing inputs</returns> private GrazType.GrazingInputs Crop2GrazingInputs(IPlantDamage forageObj) { GrazType.GrazingInputs result = new GrazType.GrazingInputs(); GrazType.zeroGrazingInputs(ref result); result.TotalGreen = 0; result.TotalDead = 0; double totalDMD = 0; double totalN = 0; double nConc; double meanDMD; double dmd; // calculate the green available based on the total green in this paddock double greenPropn = 0; // ** should really take into account the height ratio here e.g. Params.HeightRatio if (this.PastureGreenDM > GrazType.Ungrazeable) { greenPropn = 1.0 - (GrazType.Ungrazeable / this.PastureGreenDM); } // calculate the total live and dead biomass foreach (IOrganDamage biomass in forageObj.Organs) { if (biomass.IsAboveGround) { if (biomass.Live.Wt > 0 || biomass.Dead.Wt > 0) { result.TotalGreen += (greenPropn * biomass.Live.Wt); // g/m^2 result.TotalDead += biomass.Dead.Wt; // we can find the dmd of structural, assume storage and metabolic are 100% digestible dmd = (biomass.Live.DMDOfStructural * greenPropn * biomass.Live.StructuralWt) + (1 * greenPropn * biomass.Live.StorageWt) + (1 * greenPropn * biomass.Live.MetabolicWt); // storage and metab are 100% dmd dmd += ((biomass.Dead.DMDOfStructural * biomass.Dead.StructuralWt) + (1 * biomass.Dead.StorageWt) + (1 * biomass.Dead.MetabolicWt)); totalDMD += dmd; totalN += (greenPropn * biomass.Live.N) + (biomass.Dead.Wt > 0 ? biomass.Dead.N : 0); // g/m^2 } } } // TODO: Improve this routine double availDM = result.TotalGreen + result.TotalDead; if (availDM > 0) { meanDMD = totalDMD / availDM; // calc the average dmd for the plant nConc = totalN / availDM; // N conc // get the dmd distribution double[] dmdPropns; // = new double[GrazType.DigClassNo + 1]; // green 0.85-0.45, dead 0.70-0.30 dmdPropns = ForageInfo.CalcDMDDistribution(meanDMD, 0.85, 0.45); // FIX ME: the DMD ranges should be organ- and development-specific values for (int idx = 1; idx <= GrazType.DigClassNo; idx++) { result.Herbage[idx].Biomass = dmdPropns[idx] * availDM; result.Herbage[idx].CrudeProtein = nConc * GrazType.N2Protein; result.Herbage[idx].Digestibility = GrazType.ClassDig[idx]; result.Herbage[idx].Degradability = Math.Min(0.90, result.Herbage[idx].Digestibility + 0.10); result.Herbage[idx].HeightRatio = 1; result.Herbage[idx].PhosContent = 0; // N * 0.05? result.Herbage[idx].SulfContent = 0; // N * 0.07? result.Herbage[idx].AshAlkalinity = 0.70; // TODO: use a modelled value } if (forageObj is IPlant plant) { switch (plant.PlantType) { case "AGPLucerne": case "AGPRedClover": case "AGPWhiteClover": result.LegumePropn = 1; break; default: result.LegumePropn = 0; break; } } result.SelectFactor = 0; // TODO: set from Plant model value // TODO: Store any seed pools } return(result); }
/// <summary> /// The herbage is removed from the plant/agpasture /// </summary> public void RemoveHerbageFromPlant() { string chemType = string.Empty; int forageIdx = 0; ForageInfo forage = this.ForageByIndex(forageIdx); while (forage != null) { double area = forage.InPaddock.Area; GrazType.GrazingOutputs removed = forage.RemovalKG; // total the amount removed kg/ha double totalRemoved = 0.0; for (int i = 0; i < removed.Herbage.Length; i++) { totalRemoved += removed.Herbage[i]; } double propnRemoved = Math.Min(1.0, (totalRemoved / area) / (forage.TotalLive + forage.TotalDead + GrazType.Ungrazeable * 10.0)); // calculations in kg /ha, needs more checking, would be good to use a variable for the unit conversion on ungrazeable // calculations of proportions each organ of the total plant removed (in the native units) double totalDM = ForageObj.Material.Sum(m => m.Total.Wt); double consumableDM = ForageObj.Material.Sum(m => m.Consumable.Wt); double amountRemoved = 0; double amountToRemove = propnRemoved * consumableDM; var liveMaterial = ForageObj.Material.Where(m => m.IsLive).ToList(); foreach (var live in liveMaterial) { // Find corresponding dead material var dead = ForageObj.Material.FirstOrDefault(m => !m.IsLive && m.Name == live.Name); if (dead == null) { throw new Exception($"Cannot find dead material for {live.Name}."); } if (live.Total.Wt + dead.Total.Wt > 0) { double propnOfPlantDM = (live.Total.Wt + dead.Total.Wt) / totalDM; double amountOfOrganToRemove = propnOfPlantDM * amountToRemove; double prpnOfOrganToRemove = amountOfOrganToRemove / (live.Total.Wt + dead.Total.Wt); prpnOfOrganToRemove = Math.Min(prpnOfOrganToRemove, 1.0); PMF.OrganBiomassRemovalType removal = new PMF.OrganBiomassRemovalType(); removal.FractionDeadToRemove = prpnOfOrganToRemove; removal.FractionLiveToRemove = prpnOfOrganToRemove; ForageObj.RemoveBiomass(live.Name, removal); amountRemoved += amountOfOrganToRemove; } } if (liveMaterial.Count == 0) { var deadMaterial = ForageObj.Material.Where(m => !m.IsLive).ToList(); foreach (var dead in deadMaterial) { // This can happen for surface organic matter which only has dead material. double propnOfPlantDM = dead.Total.Wt / totalDM; double amountOfOrganToRemove = propnOfPlantDM * amountToRemove; double prpnOfOrganToRemove = MathUtilities.Divide(amountOfOrganToRemove, dead.Total.Wt, 0); prpnOfOrganToRemove = Math.Min(prpnOfOrganToRemove, 1.0); PMF.OrganBiomassRemovalType removal = new PMF.OrganBiomassRemovalType(); removal.FractionDeadToRemove = prpnOfOrganToRemove; ForageObj.RemoveBiomass(dead.Name, removal); amountRemoved += amountOfOrganToRemove; } } if (!APSIM.Shared.Utilities.MathUtilities.FloatsAreEqual(amountRemoved, amountToRemove)) { throw new Exception("Mass balance check fail in Stock. The amount of biomass removed from the plant does not equal the amount of forage the animals consumed."); } forageIdx++; forage = this.ForageByIndex(forageIdx); } }