/// <summary> /// * The providing forage component is specifying a list of quantities of /// forage, each of which has a known mean DMD (denoted by passing DDM and IDM /// sub-pools). That is, the providing module is taking responsibility for /// providing a DMD distribution for each cohort, organ and age class /// * The Stock component will place each quantity of forage into (usually 2) /// adjacent DMD classes, so as to preserve the provided mean DMD /// </summary> /// <param name="dAvailPropn"></param> /// <param name="dBulkDensity"></param> /// <returns></returns> public GrazType.TGrazingInputs convertChemistry_VarDMDClasses(double dAvailPropn, double dBulkDensity) { double dPoolDM; // Mass of each sub-pool of forage double dPoolDMD; // Digestibility of sub-pool of forage int iDMDClass; double dClassPropn; int iDMD; // DMD classes (regularly spaced) in TGrazingInputs int iPool; // DMD classes (irregularly spaced) in the chemistry type int iChemDDM; int iChemIDM; GrazType.TGrazingInputs Result = new GrazType.TGrazingInputs(); for (iDMD = 1; iDMD <= GrazType.DigClassNo; iDMD++) Result.Herbage[iDMD].HeightRatio = 1.0; this.dHerbageDMDFract = new double[GrazType.DigClassNo + 1]; this.dSeedRipeFract = new double[3]; // Leaf & stem pools if (FSeedType == NOT_SEED) { for (iPool = 1; iPool <= MAX_DDM_CLASSES; iPool++) { iChemDDM = 2 * iPool; iChemIDM = 2 * iPool + 1; dPoolDM = FChemData[iChemDDM].dMass_KgHa + FChemData[iChemIDM].dMass_KgHa; if (dPoolDM > 0.0) { dPoolDMD = FChemData[iChemDDM].dMass_KgHa / dPoolDM; if (dPoolDMD >= GrazType.ClassDig[1]) { iDMDClass = 1; dClassPropn = 1.0; } else if (dPoolDMD <= GrazType.ClassDig[GrazType.DigClassNo]) { iDMDClass = GrazType.DigClassNo; dClassPropn = 1.0; } else { iDMDClass = Convert.ToInt32(Math.Max(1, Math.Min(1 + Math.Truncate((GrazType.ClassDig[1] - dPoolDMD) / CLASSWIDTH), GrazType.DigClassNo - 1))); dClassPropn = Math.Max(0.0, Math.Min((dPoolDMD - GrazType.ClassDig[iDMDClass + 1]) / CLASSWIDTH, 1.0)); } if (dClassPropn > 0.0) populateIntakeRecord(ref Result.Herbage[iDMDClass], iDMDClass, (dClassPropn == 1.0), dPoolDM, dPoolDMD, dAvailPropn * dClassPropn, FChemData[iChemDDM].dNitrogen_KgHa, FChemData[iChemIDM].dNitrogen_KgHa, FChemData[iChemDDM].dPhosphorus_KgHa, FChemData[iChemIDM].dPhosphorus_KgHa, FChemData[iChemDDM].dSulphur_KgHa, FChemData[iChemIDM].dSulphur_KgHa, FChemData[iChemDDM].dAshAlk_MolHa, FChemData[iChemIDM].dAshAlk_MolHa, dBulkDensity); if (dClassPropn < 1.0) populateIntakeRecord(ref Result.Herbage[iDMDClass + 1], iDMDClass + 1, false, dPoolDM, dPoolDMD, dAvailPropn * (1.0 - dClassPropn), FChemData[iChemDDM].dNitrogen_KgHa, FChemData[iChemIDM].dNitrogen_KgHa, FChemData[iChemDDM].dPhosphorus_KgHa, FChemData[iChemIDM].dPhosphorus_KgHa, FChemData[iChemDDM].dSulphur_KgHa, FChemData[iChemIDM].dSulphur_KgHa, FChemData[iChemDDM].dAshAlk_MolHa, FChemData[iChemIDM].dAshAlk_MolHa, dBulkDensity); } } populateHerbageType(ref Result); } // Seed pools: not permitted else if ((FSeedType == GrazType.UNRIPE) || (FSeedType == GrazType.RIPE)) throw new Exception("Chemistry \"DDMnn\"/\"IDMnn\" may not be used when the organ is seeds, heads or ears"); return Result; }
/// <summary> /// Calculates the TGrazingInputs values from the values stored during addForageData() /// </summary> /// <param name="fMaxGH"></param> /// <param name="fCurvature"></param> /// <param name="fSlope"></param> /// <returns></returns> public GrazType.TGrazingInputs availForage(double fMaxGH, double fCurvature, double fSlope) { GrazType.TGrazingInputs Result = new GrazType.TGrazingInputs(); double fGreenHeight; double[] fAvailPropn = new double[2]; // TRUE = green forage, FALSE = dry forage double dBulkDensity; GrazType.TGrazingInputs fractionData; int iDMD; int iRipe; if (FUseForageData) Result.CopyFrom(FForageData); else { if (FGreenMass > 0.0) { fGreenHeight = 1.0E-4 * InPaddock.FSummedGreenMass / FGreenBulkDensity; // Height is in metres here, FSummedGreenMass in kg/ha and BD in kg/m^3 fAvailPropn[1] = Math.Max(0.0, 1.0 - GrazType.fGrazingHeight(fGreenHeight, fMaxGH, fCurvature, fSlope) / fGreenHeight); // green } else fAvailPropn[1] = 0.0; fAvailPropn[0] = 1.0; GrazType.zeroGrazingInputs(ref Result); dBulkDensity = this.dHerbageBulkDensity(); switch (FChemistryType) { case TForageChemistry.fcDigInDig: fractionData = this.convertChemistry_DigInDig(fAvailPropn[(FIsGreen ? 1 : 0)], dBulkDensity); break; case TForageChemistry.fcMeanDMD: fractionData = this.convertChemistry_MeanDMD(fAvailPropn[(FIsGreen ? 1 : 0)], dBulkDensity); break; case TForageChemistry.fcDMDClasses6: fractionData = this.convertChemistry_DMDClasses6(fAvailPropn[(FIsGreen ? 1 : 0)], dBulkDensity); break; case TForageChemistry.fcVarDMDClasses: fractionData = this.convertChemistry_VarDMDClasses(fAvailPropn[(FIsGreen ? 1 : 0)], dBulkDensity); break; default: throw new Exception("Cannot translate the forage chemistry inputs"); } GrazType.addGrazingInputs(1, fractionData, ref Result); // Finish computing the proportion of DMD class that is contributed by each herbage fraction // This is used to disaggregate the computed intakes back to the source forages for (iDMD = 1; iDMD <= GrazType.DigClassNo; iDMD++) { if (Result.Herbage[iDMD].Biomass > 0.0) this.dHerbageDMDFract[iDMD] = this.dHerbageDMDFract[iDMD] / Result.Herbage[iDMD].Biomass; for (iRipe = GrazType.UNRIPE; iRipe <= GrazType.RIPE; iRipe++) if (Result.Seeds[1, iRipe].Biomass > 0.0) this.dSeedRipeFract[iRipe] = this.dSeedRipeFract[iRipe] / Result.Seeds[1, iRipe].Biomass; } } return Result; }
/// <summary> /// The providing forage component is specifying a list of quantities of forage, /// each of which is assumed to have DMD uniformly distributed across one of /// the TAnimalGroup DMD pools /// </summary> /// <param name="dAvailPropn"></param> /// <param name="dBulkDensity"></param> /// <returns></returns> public GrazType.TGrazingInputs convertChemistry_DMDClasses6(double dAvailPropn, double dBulkDensity) { int iChem; int iDMD; GrazType.TGrazingInputs Result = new GrazType.TGrazingInputs(); for (iDMD = 1; iDMD <= GrazType.DigClassNo; iDMD++) Result.Herbage[iDMD].HeightRatio = 1.0; this.dHerbageDMDFract = new double[GrazType.DigClassNo + 1]; this.dSeedRipeFract = new double[3]; // Leaf & stem pools if (FSeedType == NOT_SEED) { for (iChem = 0; iChem <= this.CHEM_COUNT[(int)TForageChemistry.fcDMDClasses6] - 1; iChem++) { iDMD = iChem + 1; Result.Herbage[iDMD].Biomass = dAvailPropn * FChemData[iChem].dMass_KgHa; Result.Herbage[iDMD].Digestibility = GrazType.ClassDig[iDMD]; Result.Herbage[iDMD].Degradability = Math.Min(0.90, Result.Herbage[iDMD].Digestibility + 0.10); if (Result.Herbage[iDMD].Biomass > 0.0) { Result.Herbage[iDMD].CrudeProtein = FChemData[iChem].dNitrogen_KgHa / FChemData[iChem].dMass_KgHa * GrazType.N2Protein; Result.Herbage[iDMD].PhosContent = FChemData[iChem].dPhosphorus_KgHa / FChemData[iChem].dMass_KgHa; Result.Herbage[iDMD].SulfContent = FChemData[iChem].dSulphur_KgHa / FChemData[iChem].dMass_KgHa; Result.Herbage[iDMD].AshAlkalinity = FChemData[iChem].dAshAlk_MolHa / FChemData[iChem].dMass_KgHa; } Result.Herbage[iDMD].HeightRatio = GrazType.REF_HERBAGE_BD / dBulkDensity; if (FIsGreen) Result.TotalGreen = Result.TotalGreen + Result.Herbage[iDMD].Biomass; else Result.TotalDead = Result.TotalDead + Result.Herbage[iDMD].Biomass; this.dHerbageDMDFract[iDMD] = Result.Herbage[iDMD].Biomass; // for later division by the total for the DMD class } populateHerbageType(ref Result); } // Seed pools: not permitted else if ((FSeedType == GrazType.UNRIPE) || (FSeedType == GrazType.RIPE)) throw new Exception("Chemistry \"DMDnn\" may not be used when the organ is seeds, heads or ears"); return Result; }
/// <summary> /// The providing forage component is specifying a quantity of forage with /// a known average digestibility, and is signalling that *this* component /// should distribute the forage across the TAnimalGroup DMD pools /// </summary> /// <param name="dAvailPropn"></param> /// <param name="dBulkDensity"></param> /// <returns></returns> public GrazType.TGrazingInputs convertChemistry_MeanDMD(double dAvailPropn, double dBulkDensity) { const int DDM_MEAN = 0; const int IDM_MEAN = 1; double dTotalDM; double dMeanDMD; double[] dDMDPropns = new double[GrazType.DigClassNo + 1]; int iDMD; GrazType.TGrazingInputs Result = new GrazType.TGrazingInputs(); for (iDMD = 1; iDMD <= GrazType.DigClassNo; iDMD++) Result.Herbage[iDMD].HeightRatio = 1.0; this.dHerbageDMDFract = new double[GrazType.DigClassNo + 1]; this.dSeedRipeFract = new double[3]; dTotalDM = FChemData[DDM_MEAN].dMass_KgHa + FChemData[IDM_MEAN].dMass_KgHa; // Leaf & stem pools if ((FSeedType == NOT_SEED) && (dTotalDM > 0.0)) { dMeanDMD = FChemData[DDM_MEAN].dMass_KgHa / dTotalDM; if (FIsGreen) dDMDPropns = this.calcDMDDistribution(dMeanDMD, 0.85, 0.45); // FIX ME: the DMD ranges should be organ- and development-specific values else dDMDPropns = this.calcDMDDistribution(dMeanDMD, 0.70, 0.30); for (iDMD = 1; iDMD <= GrazType.DigClassNo; iDMD++) { populateIntakeRecord(ref Result.Herbage[iDMD], iDMD, (dDMDPropns[iDMD] == 1.0), dTotalDM, dMeanDMD, dAvailPropn * dDMDPropns[iDMD], FChemData[DDM_MEAN].dNitrogen_KgHa, FChemData[IDM_MEAN].dNitrogen_KgHa, FChemData[DDM_MEAN].dPhosphorus_KgHa, FChemData[IDM_MEAN].dPhosphorus_KgHa, FChemData[DDM_MEAN].dSulphur_KgHa, FChemData[IDM_MEAN].dSulphur_KgHa, FChemData[DDM_MEAN].dAshAlk_MolHa, FChemData[IDM_MEAN].dAshAlk_MolHa, dBulkDensity); if (FIsGreen) Result.TotalGreen = Result.TotalGreen + Result.Herbage[iDMD].Biomass; else Result.TotalDead = Result.TotalDead + Result.Herbage[iDMD].Biomass; this.dHerbageDMDFract[iDMD] = Result.Herbage[iDMD].Biomass; // for later division by the total for the DMD class } // for iDMD = 1 to DigClassNo populateHerbageType(ref Result); } // Seed pools else if (((FSeedType == GrazType.UNRIPE) || (FSeedType == GrazType.RIPE)) && (dTotalDM > 0.0)) { populateSeedRecord(ref Result, dAvailPropn, DDM_MEAN, DDM_MEAN); this.dSeedRipeFract[FSeedType] = Result.Seeds[1, FSeedType].Biomass; // for later division by the total for the seed ripeness class } return Result; }
/// <summary> /// * The providing forage component is specifying a quantity of forage with /// a single digestibility, as part of a distribution of digestibilities /// *provided by the source component*. /// * We therefore calculate the DMD and then place all foage mass in the /// TAnimalGroup DMD class that contains that DMD value /// </summary> /// <param name="dAvailPropn"></param> /// <param name="dBulkDensity"></param> /// <returns></returns> public GrazType.TGrazingInputs convertChemistry_DigInDig(double dAvailPropn, double dBulkDensity) { const int DDM = 0; const int IDM = 1; double dTotalDM; double dMeanDMD; int iDMD; GrazType.TGrazingInputs Result = new GrazType.TGrazingInputs(); GrazType.zeroGrazingInputs(ref Result); for (iDMD = 1; iDMD <= GrazType.DigClassNo; iDMD++) Result.Herbage[iDMD].HeightRatio = 1.0; this.dHerbageDMDFract = new double[GrazType.DigClassNo + 1]; this.dSeedRipeFract = new double[3]; dTotalDM = this.FChemData[DDM].dMass_KgHa + this.FChemData[IDM].dMass_KgHa; // Leaf & stem pools if ((FSeedType == NOT_SEED) && (dTotalDM > 0.0)) { dMeanDMD = this.FChemData[DDM].dMass_KgHa / dTotalDM; iDMD = Convert.ToInt32(Math.Min(1, Math.Max(1 + Math.Truncate((HIGHEST_DMD - dMeanDMD) / CLASSWIDTH), GrazType.DigClassNo))); // TODO: may need testing populateIntakeRecord(ref Result.Herbage[iDMD], iDMD, true, dTotalDM, dMeanDMD, dAvailPropn, this.FChemData[DDM].dNitrogen_KgHa, this.FChemData[IDM].dNitrogen_KgHa, this.FChemData[DDM].dPhosphorus_KgHa, this.FChemData[IDM].dPhosphorus_KgHa, this.FChemData[DDM].dSulphur_KgHa, this.FChemData[IDM].dSulphur_KgHa, this.FChemData[DDM].dAshAlk_MolHa, this.FChemData[IDM].dAshAlk_MolHa, dBulkDensity); if (FIsGreen) Result.TotalGreen = Result.Herbage[iDMD].Biomass; else Result.TotalDead = Result.Herbage[iDMD].Biomass; populateHerbageType(ref Result); this.dHerbageDMDFract[iDMD] = Result.Herbage[iDMD].Biomass; // for later division by the total for the DMD class } // Seed pools else if (((FSeedType == GrazType.UNRIPE) || (FSeedType == GrazType.RIPE)) && (dTotalDM > 0.0)) { populateSeedRecord(ref Result, dAvailPropn, DDM, IDM); this.dSeedRipeFract[FSeedType] = Result.Seeds[1, FSeedType].Biomass; // for later division by the total for the seed ripeness class } return Result; }
/// <summary> /// /// </summary> /// <param name="aValue"></param> /// <returns></returns> private GrazType.TGrazingInputs Value2GrazingInputs(TTypedValue aValue) { double fTotalDM; int Idx; GrazType.TGrazingInputs Result = new GrazType.TGrazingInputs(); GrazType.zeroGrazingInputs(ref Result); for (Idx = 1; Idx <= Math.Min(GrazType.DigClassNo, aValue.item(1).count()); Idx++) // Item[1]="herbage" Value2IntakeRecord(aValue.item(1).item((uint)Idx), ref Result.Herbage[Idx]); fTotalDM = 0.0; for (Idx = 1; Idx <= GrazType.DigClassNo; Idx++) fTotalDM = fTotalDM + Result.Herbage[Idx].Biomass; Result.TotalGreen = fTotalDM * aValue.item(2).asDouble(); // Item[2]="propn_green" Result.TotalDead = fTotalDM - Result.TotalGreen; Result.LegumePropn = aValue.item(3).asDouble(); // Item[3]="legume" Result.SelectFactor = aValue.item(4).asDouble(); // Item[4]="select_factor" for (Idx = 1; Idx <= Math.Min(2, aValue.item(5).count()); Idx++) // Item[5]="seed" { Value2IntakeRecord(aValue.item(5).item((uint)Idx), ref Result.Seeds[1, Idx]); Result.SeedClass[1, Idx] = aValue.item(6).item((uint)Idx).asInteger(); // Item[6]="seed_class" } return Result; }
private double getPaddockRank(TPaddockInfo aPaddock, TAnimalGroup aGroup) { double Result; GrazType.TGrazingInputs forageInputs; GrazType.TGrazingInputs paddockInputs; double[] fHerbageRI = new double[GrazType.DigClassNo + 1]; double[,] fSeedRI = new double[GrazType.MaxPlantSpp + 1, GrazType.RIPE + 1]; double fDummy = 0.0; int Jdx; int iClass; aGroup.PaddSteep = aPaddock.Steepness; aGroup.WaterLogging = aPaddock.fWaterlog; aGroup.RationFed.Assign(aPaddock.SuppInPadd); aGroup.RationFed.TotalAmount = 0.0; // No supplementary feed here paddockInputs = new GrazType.TGrazingInputs(); for (Jdx = 0; Jdx <= aPaddock.Forages.Count() - 1; Jdx++) { forageInputs = aPaddock.Forages.byIndex(Jdx).availForage(aGroup.Genotype.GrazeC[17], aGroup.Genotype.GrazeC[18], aGroup.Genotype.GrazeC[19]); GrazType.addGrazingInputs(Jdx + 1, forageInputs, ref paddockInputs); } aGroup.Herbage = paddockInputs; aGroup.CalculateRelIntake(aGroup, 1.0, false, 1.0, ref fHerbageRI, ref fSeedRI, ref fDummy); Result = 0.0; for (iClass = 1; iClass <= GrazType.DigClassNo; iClass++) // Function result is DMDI/pot. intake Result = Result + fHerbageRI[iClass] * GrazType.ClassDig[iClass]; return Result; }