/// <summary> /// Update the forage data for this crop/agpasture object /// </summary> /// <param name="forageObj">The crop/pasture object</param> public void UpdateForages(ModelWithDigestibleBiomass 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> /// 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(ModelWithDigestibleBiomass 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 (var live in forageObj.Material.Where(m => m.IsLive)) { // 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.Consumable.Wt > 0 || dead.Consumable.Wt > 0) { result.TotalGreen += (greenPropn * live.Consumable.Wt); // g/m^2 result.TotalDead += dead.Consumable.Wt; // we can find the dmd of structural, assume storage and metabolic are 100% digestible dmd = (live.Digestibility * greenPropn * live.Consumable.StructuralWt) + (1 * greenPropn * live.Consumable.StorageWt) + (1 * greenPropn * live.Consumable.MetabolicWt); // storage and metab are 100% dmd dmd += ((dead.Digestibility * dead.Consumable.StructuralWt) + (1 * dead.Consumable.StorageWt) + (1 * dead.Consumable.MetabolicWt)); totalDMD += dmd; totalN += (greenPropn * live.Consumable.N) + (dead.Consumable.Wt > 0 ? dead.Consumable.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> /// Add a forage provider component /// </summary> /// <param name="paddock">The paddock info</param> /// <param name="paddName">The paddock name</param> /// <param name="forageName">The forage name</param> /// <param name="hostID">Component ID</param> /// <param name="driverID">Driver ID</param> /// <param name="forageObj">The forage object</param> public void AddProvider(PaddockInfo paddock, string paddName, string forageName, int hostID, int driverID, ModelWithDigestibleBiomass forageObj) { ForageProvider forageProvider; // this is a forage provider // this provider can host a number of forages/species forageProvider = new ForageProvider(); forageProvider.PaddockOwnerName = paddName; // owning paddock forageProvider.ForageHostName = forageName; // host pasture/plant component name forageProvider.HostID = hostID; // plant/pasture comp forageProvider.ForageObj = forageObj; // setting property ID // keep a ptr to the paddock owned by the model so the forages can be assigned there as they become available forageProvider.OwningPaddock = paddock; this.forageProviderList.Add(forageProvider); }