/// <summary> /// Determines whether [is same as] [the specified other supp]. /// </summary> /// <param name="otherSupp">The other supp.</param> /// <returns></returns> public bool isSameAs(TSupplement otherSupp) { return (sName == otherSupp.sName) && (IsRoughage == otherSupp.IsRoughage) && (DM_Propn == otherSupp.DM_Propn) && (DM_Digestibility == otherSupp.DM_Digestibility) && (ME_2_DM == otherSupp.ME_2_DM) && (EtherExtract == otherSupp.EtherExtract) && (CrudeProt == otherSupp.CrudeProt) && (DgProt == otherSupp.DgProt) && (ADIP_2_CP == otherSupp.ADIP_2_CP) && (Phosphorus == otherSupp.Phosphorus) && (Sulphur == otherSupp.Sulphur) && (AshAlkalinity == otherSupp.AshAlkalinity) && (MaxPassage == otherSupp.MaxPassage); }
/// <summary> /// Mix two supplements together and store in Self /// Will work if Supp1=this or Supp2=this /// This method is only exact if the passage rates of the two supplements are equal /// </summary> /// <param name="Supp1">The supp1.</param> /// <param name="Supp2">The supp2.</param> /// <param name="propn1">The propn1.</param> public void Mix(TSupplement Supp1, TSupplement Supp2, double propn1) { if (propn1 >= 0.50) IsRoughage = Supp1.IsRoughage; else IsRoughage = Supp2.IsRoughage; double propn2 = 1.0 - propn1; // Proportion of suppt 2 on a FW basis double DMpropn1 = MathUtilities.Divide(propn1 * Supp1.DM_Propn, // Proportion of suppt 1 on a DM basis propn1 * Supp1.DM_Propn + propn2 * Supp2.DM_Propn, 0.0); double DMpropn2 = 1.0 - DMpropn1; // Proportion of suppt 2 on a DM basis double CPpropn1; // Proportion of suppt 1 on a total CP basis if (propn1 * Supp1.DM_Propn * Supp1.CrudeProt + propn2 * Supp2.DM_Propn * Supp2.CrudeProt > 0.0) CPpropn1 = propn1 * Supp1.DM_Propn * Supp1.CrudeProt / (propn1 * Supp1.DM_Propn * Supp1.CrudeProt + propn2 * Supp2.DM_Propn * Supp2.CrudeProt); else CPpropn1 = propn1; double CPpropn2 = 1.0 - CPpropn1; // Proportion of suppt 1 on a total CP basis DM_Propn = propn1 * Supp1.DM_Propn + propn2 * Supp2.DM_Propn; DM_Digestibility = DMpropn1 * Supp1.DM_Digestibility + DMpropn2 * Supp2.DM_Digestibility; ME_2_DM = DMpropn1 * Supp1.ME_2_DM + DMpropn2 * Supp2.ME_2_DM; EtherExtract = DMpropn1 * Supp1.EtherExtract + DMpropn2 * Supp2.EtherExtract; CrudeProt = DMpropn1 * Supp1.CrudeProt + DMpropn2 * Supp2.CrudeProt; DgProt = CPpropn1 * Supp1.DgProt + CPpropn2 * Supp2.DgProt; ADIP_2_CP = CPpropn1 * Supp1.ADIP_2_CP + CPpropn2 * Supp2.ADIP_2_CP; Phosphorus = DMpropn1 * Supp1.Phosphorus + DMpropn2 * Supp2.Phosphorus; Sulphur = DMpropn1 * Supp1.Sulphur + DMpropn2 * Supp2.Sulphur; AshAlkalinity = DMpropn1 * Supp1.AshAlkalinity + DMpropn2 * Supp2.AshAlkalinity; MaxPassage = DMpropn1 * Supp1.MaxPassage + DMpropn2 * Supp2.MaxPassage; }
/// <summary> /// copy consructor /// </summary> /// <param name="src">The source.</param> public TSupplement(TSupplement src) { Assign(src); }
/// <summary> /// Assigns the specified source supp. /// </summary> /// <param name="srcSupp">The source supp.</param> public void Assign(TSupplement srcSupp) { if (srcSupp != null) { sName = srcSupp.sName; IsRoughage = srcSupp.IsRoughage; DM_Propn = srcSupp.DM_Propn; DM_Digestibility = srcSupp.DM_Digestibility; ME_2_DM = srcSupp.ME_2_DM; EtherExtract = srcSupp.EtherExtract; CrudeProt = srcSupp.CrudeProt; DgProt = srcSupp.DgProt; ADIP_2_CP = srcSupp.ADIP_2_CP; Phosphorus = srcSupp.Phosphorus; Sulphur = srcSupp.Sulphur; AshAlkalinity = srcSupp.AshAlkalinity; MaxPassage = srcSupp.MaxPassage; Array.Resize(ref FTranslations, srcSupp.FTranslations.Length); Array.Copy(srcSupp.FTranslations, FTranslations, srcSupp.FTranslations.Length); } }
/// <summary> /// Inserts the specified index. /// </summary> /// <param name="idx">The index.</param> /// <param name="supp">The supp.</param> /// <param name="amt">The amt.</param> /// <param name="cost">The cost.</param> public void Insert(int idx, TSupplement supp, double amt = 0.0, double cost = 0.0) { Array.Resize(ref fSuppts, fSuppts.Length + 1); for (int jdx = fSuppts.Length - 1; jdx > idx; jdx--) fSuppts[jdx] = fSuppts[jdx - 1]; fSuppts[idx] = new TSupplementItem(supp, amt, cost); }
/// <summary> /// Scales the attributes of the members of the supplement so that the weighted /// average attributes match those of aveSupp. Ensures that fractional values /// remain within the range 0-1 /// * Assumes that all values are non-negative /// </summary> /// <param name="scaleToSupp">The scale to supp.</param> /// <param name="attrs">The attrs.</param> public void RescaleRation(TSupplement scaleToSupp, IList<TSupplement.TSuppAttribute> attrs) { Array attribs = Enum.GetValues(typeof(TSupplement.TSuppAttribute)); foreach (TSupplement.TSuppAttribute attr in attribs) // NB this only works becuase of the way the supplement attributes are ordered, i.e. DM proportion first and CP before dg and ADIP:CP } { // i.e. DM proportion first and CP before dg and ADIP:CP if (attrs.Contains(attr)) { double newWtMean = scaleToSupp[attr]; if (fSuppts.Length == 1) fSuppts[0][attr] = newWtMean; else { double oldWtMean = 0.0; double totalWeight = 0.0; double fWeight = 0.0; for (int idx = 0; idx < fSuppts.Length; idx++) { switch (attr) { case TSupplement.TSuppAttribute.spaDMP: fWeight = getFWFract(idx); break; case TSupplement.TSuppAttribute.spaDG: case TSupplement.TSuppAttribute.spaADIP: fWeight = getFWFract(idx) * fSuppts[idx].DM_Propn * fSuppts[idx].CrudeProt; break; default: fWeight = getFWFract(idx) * fSuppts[idx].DM_Propn; break; } oldWtMean += fWeight * fSuppts[idx][attr]; totalWeight += fWeight; } if (totalWeight > 0.0) oldWtMean /= totalWeight; for (int idx = 0; idx < fSuppts.Length; idx++) { if (totalWeight == 0.0) fSuppts[idx][attr] = newWtMean; else if ((newWtMean < oldWtMean) || (!PROPN_ATTRS.Contains(attr))) fSuppts[idx][attr] *= newWtMean / oldWtMean; else fSuppts[idx][attr] += (1.0 - fSuppts[idx][attr]) * (newWtMean - oldWtMean) / (1.0 - oldWtMean); } } } } }
/// <summary> /// Adds the specified supp. /// </summary> /// <param name="supp">The supp.</param> /// <param name="amt">The amt.</param> /// <param name="cost">The cost.</param> /// <returns></returns> public int Add(TSupplement supp, double amt = 0.0, double cost = 0.0) { int idx = fSuppts.Length; Insert(idx, supp, amt, cost); return idx; }
/// <summary> /// Blends the specified source store. /// </summary> /// <param name="srcStore">The source store.</param> /// <param name="transferKg">The transfer kg.</param> /// <param name="destStore">The dest store.</param> /// <exception cref="System.Exception">Supplement \ + srcStore + \ not recognised</exception> public void Blend(string srcStore, double transferKg, string destStore) { int iSrc = IndexOf(srcStore); if (iSrc < 0) throw new Exception("Supplement \"" + srcStore + "\" not recognised"); transferKg = System.Math.Min(transferKg, this[iSrc].Amount); if (transferKg > 0.0) { int iDest = IndexOf(destStore); if (iDest < 0) { TSupplement newSupp = new TSupplement(); newSupp.Assign(this[iSrc]); newSupp.sName = destStore; iDest = AddToStore(0.0, newSupp); } Transfer(this, iSrc, this, iDest, transferKg); } }
/// <summary> /// Adds an amount of a supplement to a store. /// * If the store name already exists in the FStores array, the method adds /// the supplement to that store. Otherwise a new store is created. /// * The DMP, DMD, MEDM, CP, DG, EE and ADIP2CP parameters may be set to zero, /// in which case the default values for the supplement name are used. /// Defaults are taken from the current store if the name is already defined, /// and from grazSUPP.PAS otherwise. If defaults cannot be found for a name, /// wheat is used as the default composition. /// </summary> /// <param name="suppKg">Amount (kg fresh weight) of the supplement to be included in the store.</param> /// <param name="suppName">Name of the supplement.</param> /// <param name="roughage">The roughage.</param> /// <param name="DMP">Proportion of the fresh weight which is dry matter kg/kg FW</param> /// <param name="DMD">Dry matter digestibility of the supplement kg/kg DM</param> /// <param name="MEDM">Metabolisable energy content of dry matter MJ/kg DM</param> /// <param name="CP">Crude protein content kg/kg DM</param> /// <param name="DG">Degradability of the crude protein kg/kg CP</param> /// <param name="EE">Ether-extractable content kg/kg DM</param> /// <param name="ADIP2CP">Ratio of acid detergent insoluble protein to CP kg/kg CP</param> /// <param name="phos">Phosphorus content kg/kg DM</param> /// <param name="sulf">Sulphur content kg/kg DM</param> /// <param name="ashAlk">Ash alkalinity mol/kg DM</param> /// <param name="maxPass">Maximum passage rate 0-1</param> /// <returns> /// Index of the supplement in the store /// </returns> public int AddToStore(double suppKg, string suppName, int roughage = DEFAULT, double DMP = 0.0, double DMD = 0.0, double MEDM = 0.0, double CP = 0.0, double DG = 0.0, double EE = 0.0, double ADIP2CP = 0.0, double phos = 0.0, double sulf = 0.0, double ashAlk = 0.0, double maxPass = 0.0) { int idx = IndexOf(suppName); TSupplement addSupp = new TSupplement(suppName); if (idx >= 0) // Work out the composition of the supplement being added addSupp.Assign(this[idx]); else addSupp.DefaultFromName(); addSupp.sName = suppName.ToLower(); if (roughage == ROUGHAGE) // Override the default composition as required addSupp.IsRoughage = true; else if (roughage != DEFAULT) addSupp.IsRoughage = false; if (DMP > 0.0) addSupp.DM_Propn = DMP; if (DMD > 0.0) addSupp.DM_Digestibility = DMD; if (MEDM > 0.0) addSupp.ME_2_DM = MEDM; if (CP > 0.0) addSupp.CrudeProt = CP; if (DG > 0.0) addSupp.DgProt = DG; if (EE > 0.0) addSupp.EtherExtract = EE; if (ADIP2CP > 0.0) addSupp.ADIP_2_CP = ADIP2CP; if (phos > 0.0) addSupp.Phosphorus = phos; if (sulf > 0.0) addSupp.Sulphur = sulf; if (ashAlk > 0.0) addSupp.AshAlkalinity = ashAlk; if (maxPass > 0.0) addSupp.MaxPassage = maxPass; if (DMD > 0.0 && MEDM == 0.0) addSupp.ME_2_DM = addSupp.DefaultME2DM(); else if (DMD == 0.0 && MEDM > 0.0) addSupp.DM_Digestibility = addSupp.DefaultDMD(); return AddToStore(suppKg, addSupp); }
/// <summary> /// Reads from strings. /// </summary> /// <param name="locale">The locale.</param> /// <param name="strings">The strings.</param> /// <exception cref="System.Exception"> /// Error reading supplement library - must contain a header line /// or /// Error reading supplement library - header line is invalid /// or /// Error reading supplement library - line for + sNameStr + is invalid /// </exception> public void ReadFromStrings(string locale, string[] strings) { if (strings == null || strings.Length == 0) throw new Exception("Error reading supplement library - must contain a header line"); int iAttrPosn = strings[0].IndexOf('|'); // Every line must have a | character in // this column string sHdrStr = "Name"; sHdrStr = sHdrStr.PadRight(iAttrPosn, ' ') + "|" + sATTR_HEADER; if (strings[0] != sHdrStr) throw new Exception("Error reading supplement library - header line is invalid"); Clear(); string sNameStr = ""; try { for (int idx = 1; idx < strings.Length; idx++) { if (strings[idx].Length < iAttrPosn) continue; sNameStr = strings[idx].Substring(0, iAttrPosn - 1).Trim(); string sAttrStr = strings[idx].Substring(iAttrPosn + 1); TSupplement newSupp = new TSupplement(); newSupp.sName = sNameStr; newSupp.IsRoughage = sAttrStr[0] == 'Y'; sAttrStr = sAttrStr.Remove(0, 1); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._DM_Propn); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._DM_Digestibility); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._ME_2_DM); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._EtherExtract); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._CrudeProt); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._DgProt); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._ADIP_2_CP); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._Phosphorus); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._Sulphur); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._AshAlkalinity); StringUtilities.TokenDouble(ref sAttrStr, ref newSupp._MaxPassage); sAttrStr = sAttrStr.Trim(); string sTransStr; string sTransName; string sLocStr; string sLang; int iBlank = sAttrStr.IndexOf(' '); if (iBlank < 0) { sLocStr = sAttrStr; sTransStr = ""; } else { sLocStr = sAttrStr.Substring(0, iBlank); sTransStr = sAttrStr.Substring(iBlank + 1).Trim(); } while (sTransStr != "") { StringUtilities.TextToken(ref sTransStr, out sLang); if (sTransStr[0] == ':') { sTransStr = sTransStr.Substring(1); StringUtilities.TextToken(ref sTransStr, out sTransName, true); newSupp.AddTranslation(sLang, sTransName); } if (sTransStr.Length > 0 && sTransStr[0] == ';') sTransStr = sTransStr.Substring(1); } if (sLocStr == "" || GrazParam.InLocale(locale, sLocStr)) Add(newSupp, 0.0, 0.0); } } catch (Exception) { throw new Exception("Error reading supplement library - line for " + sNameStr + " is invalid"); } }
/// <summary> /// Initializes a new instance of the <see cref="TSupplementModel"/> class. /// </summary> public TSupplementModel() : base() { AddToStore(0.0, FODDER, ROUGHAGE); fSuppts[0].DM_Propn = 0.85; FCurrPaddSupp = new TSupplement(); }
/// <summary> /// Transfers the specified source. /// </summary> /// <param name="src">The source.</param> /// <param name="srcIdx">Index of the source.</param> /// <param name="dest">The dest.</param> /// <param name="destIdx">Index of the dest.</param> /// <param name="amount">The amount.</param> /// <exception cref="System.Exception">Invalid transfer of feed</exception> private void Transfer(TSupplementRation src, int srcIdx, TSupplementRation dest, int destIdx, double amount) { if (srcIdx < 0 || srcIdx >= src.Count || destIdx < 0 || destIdx > dest.Count) throw new Exception("Invalid transfer of feed"); if (amount > 0.0) { if (destIdx < dest.Count) SuppIntoRation(dest, destIdx, src[srcIdx], amount); else { TSupplement copy = new TSupplement(); copy.Assign(src[srcIdx]); dest.Add(copy, amount); } src[srcIdx].Amount -= amount; } }
/// <summary> /// Supps the into ration. /// </summary> /// <param name="ration">The ration.</param> /// <param name="idx">The index.</param> /// <param name="supp">The supp.</param> /// <param name="amount">The amount.</param> private void SuppIntoRation(TSupplementRation ration, int idx, TSupplement supp, double amount) { if (amount > 0.0) { double propn = amount / (amount + ration[idx].Amount); ration[idx].Mix(supp, ration[idx], propn); ration[idx].Amount += amount; } }
/// <summary> /// Mixes the many. /// </summary> /// <param name="supps">The supps.</param> /// <param name="amounts">The amounts.</param> public void MixMany(TSupplement[] supps, double[] amounts) { double amountSum = 0.0; for (int idx = 0; idx < supps.Length; idx++) { if (idx < amounts.Length && amounts[idx] > 0.0) { Mix(supps[idx], this, amounts[idx] / (amountSum + amounts[idx])); amountSum += amounts[idx]; } } }
/// <summary> /// Computes a weighted average supplement composition /// </summary> /// <param name="aveSupp">receives the average supplement composition</param> public void AverageSuppt(out TSupplement aveSupp) { aveSupp = new TSupplement(); if (TotalAmount > 0.0) aveSupp.MixMany(fSuppts); }
/// <summary> /// Constructor /// Note that it makes a copy of the TSupplement /// </summary> /// <param name="src">The source.</param> /// <param name="amt">The amt.</param> /// <param name="cst">The CST.</param> public TSupplementItem(TSupplement src, double amt = 0.0, double cst = 0.0) : base(src) { Amount = amt; Cost = cst; }
/// <summary> /// Adds to store. /// </summary> /// <param name="suppKg">The supp kg.</param> /// <param name="suppComp">The supp comp.</param> /// <returns></returns> /// <exception cref="System.Exception">Supplement submodel: cannot combine roughage and concentrate, both named + suppComp.sName</exception> public int AddToStore(double suppKg, TSupplement suppComp) { int suppIdx = IndexOf(suppComp.sName); if (suppIdx < 0) suppIdx = Add(suppComp, suppKg); else if (suppKg > 0.0) { if (suppComp.IsRoughage != fSuppts[suppIdx].IsRoughage) throw new Exception("Supplement submodel: cannot combine roughage and concentrate, both named " + suppComp.sName); SuppIntoRation(this, suppIdx, suppComp, suppKg); } return suppIdx; }