private void OnStartOfSimulation(object sender, EventArgs e) { //if the SQLite Database can't be opened throw an exception. var validationContext = new ValidationContext(this, null, null); validationResults = new List <ValidationResult>(); Validator.TryValidateObject(this, validationContext, validationResults, true); if (OpenSQLiteDB() == false) { throw new Exception(ErrorMessage); } else { // create warning if database is not optimised for CLEM var result = SQLiteReader.ExecuteQuery("SELECT count(*) FROM sqlite_master WHERE type='index' and name='CLEM_next_growth';"); if (result.Rows.Count >= 0 && Convert.ToInt32(result.Rows[0][0]) != 1) { // add warning string warn = $"The database [x={this.FileName.Replace("_", "\\_")}] specified in [x={this.Name}] has not been optimised for best performance in CLEM. Add the following index to your database using your chosen database management software (e.g. DB Browser) to significantly improve your simulation speed:\r\nCREATE INDEX CLEM\\_next\\_growth ON Native\\_Inputs (Region, Soil, GrassBA, LandCon, StkRate, Year, Month);\r\nThis index must be named CLEM\\_next\\_growth and should include the table and column names appropriate to your database."; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } } // get list of distinct stocking rates available in database // database has already been opened and checked in Validate() this.distinctStkRates = GetCategories(StkRateColumnName); this.distinctGBAs = GetCategories(GrassBAColumnName); this.distinctLandConditions = GetCategories(LandConColumnName); }
/// <summary> /// Get value of a specific individual /// </summary> /// <returns>value</returns> public double ValueofIndividual(Ruminant ind, PurchaseOrSalePricingStyleType purchaseStyle) { if (PricingAvailable()) { List <Ruminant> animalList = new List <Ruminant>() { ind }; // search through RuminantPriceGroups for first match with desired purchase or sale flag foreach (AnimalPriceGroup item in priceGroups.Where(a => a.PurchaseOrSale == purchaseStyle || a.PurchaseOrSale == PurchaseOrSalePricingStyleType.Both)) { if (animalList.Filter(item).Count() == 1) { return(item.Value * ((item.PricingStyle == PricingStyleType.perKg) ? ind.Weight : 1.0)); } } // no price match found. string warningString = $"No [{purchaseStyle.ToString()}] price entry was found for [r={ind.Breed}] meeting the required criteria [f=age: {ind.Age}] [f=gender: {ind.GenderAsString}] [f=weight: {ind.Weight.ToString("##0")}]"; if (!Warnings.Exists(warningString)) { Warnings.Add(warningString); Summary.WriteWarning(this, warningString); } } return(0); }
/// <summary> /// Get value of a specific individual /// </summary> /// <returns>value</returns> public double ValueofIndividual(Ruminant ind, PurchaseOrSalePricingStyleType purchaseStyle) { if (PricingAvailable()) { List <Ruminant> animalList = new List <Ruminant>() { ind }; // search through RuminantPriceGroups for first match with desired purchase or sale flag foreach (AnimalPriceGroup item in Apsim.Children(PriceList, typeof(AnimalPriceGroup)).Cast <AnimalPriceGroup>().Where(a => a.PurchaseOrSale == purchaseStyle || a.PurchaseOrSale == PurchaseOrSalePricingStyleType.Both)) { if (animalList.Filter(item).Count() == 1) { return(item.Value * ((item.PricingStyle == PricingStyleType.perKg) ? ind.Weight : 1.0)); } } // no price match found. string warning = "No " + purchaseStyle.ToString() + " price entry was found for an indiviudal with details ([f=age: " + ind.Age + "] [f=herd: " + ind.HerdName + "] [f=gender: " + ind.GenderAsString + "] [f=weight: " + ind.Weight.ToString("##0") + "])"; if (!Warnings.Exists(warning)) { Warnings.Add(warning); Summary.WriteWarning(this, warning); } } return(0); }
/// <summary> /// Resource price /// </summary> public ResourcePricing Price(PurchaseOrSalePricingStyleType priceType) { // find pricing that is ok; ResourcePricing price = Apsim.Children(this, typeof(ResourcePricing)).Where(a => a.Enabled & ((a as ResourcePricing).PurchaseOrSale == PurchaseOrSalePricingStyleType.Both | (a as ResourcePricing).PurchaseOrSale == priceType) && (a as ResourcePricing).TimingOK).FirstOrDefault() as ResourcePricing; // does simulation have finance ResourcesHolder resources = Apsim.Parent(this, typeof(ResourcesHolder)) as ResourcesHolder; bool financesPresent = (resources.FinanceResource() != null); if (price == null) { if (financesPresent) { string warn = "No pricing is available for [r=" + this.Parent.Name + "." + this.Name + "]"; if (Clock != null & Apsim.Children(this, typeof(ResourcePricing)).Count > 0) { warn += " in month [" + Clock.Today.ToString("MM yyyy") + "]"; } warn += "\nAdd [r=ResourcePricing] component to [r=" + this.Parent.Name + "." + this.Name + "] to include financial transactions for purchases and sales."; if (!Warnings.Exists(warn) & Summary != null) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } return(new ResourcePricing() { PricePerPacket = 0, PacketSize = 1, UseWholePackets = true }); } return(price); }
/// <summary> /// Returns the link to the matching resource in the market place if found or creates a new clone copy for future transactions /// This allows this action to be performed once to store the link rather than at every transaction /// This functionality allows resources not in the market at the start of the simulation to be traded. /// </summary> /// <param name="resourceType">The resource type to trade</param> /// <returns>Whether the search was successful</returns> public IResourceWithTransactionType LinkToMarketResourceType(CLEMResourceTypeBase resourceType) { if (!(this.Parent is Market)) { throw new ApsimXException(this, $"Logic error in code. Trying to link a resource type [r={resourceType.Name}] from the market with the same market./nThis is a coding issue. Please contact the developers"); } // find parent group type ResourceBaseWithTransactions parent = (resourceType as Model).Parent as ResourceBaseWithTransactions; ResourceBaseWithTransactions resGroup = GetResourceGroupByType(parent.GetType()) as ResourceBaseWithTransactions; if (resGroup is null) { // add warning the market is not currently trading in this resource string zoneName = FindAncestor <Zone>().Name; string warn = $"[{zoneName}] is currently not accepting resources of type [r={parent.GetType().ToString()}]\nOnly resources groups provided in the [r=ResourceHolder] in the simulation tree will be traded."; if (!Warnings.Exists(warn) & Summary != null) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } return(null); } // TODO: do some group checks. land units, currency // TODO: if market and looking for finance only return or create "Bank" // find resource type in group object resType = resGroup.GetByName((resourceType as IModel).Name) as IResourceWithTransactionType; if (resType is null) { // clone resource // too many problems with linked events to clone these objects and setup again // it will be the responsibility of the user to ensure the resources and details are in the market // resType = Apsim.Clone(resourceType); if (resType is null) { // add warning the market does not have the resource string zoneName = FindAncestor <Zone>().Name; string warn = $"The resource [r={resourceType.Parent.Name}.{resourceType.Name}] does not exist in [m={this.Parent.Name}].\nAdd resource and associated components to the market to permit trading."; if (!Warnings.Exists(warn) & Summary != null) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } return(null); } else { (resType as IModel).Parent = resGroup; (resType as CLEMModel).CLEMParentName = resGroup.CLEMParentName; // add new resource type resGroup.AddNewResourceType(resType as IResourceWithTransactionType); } } return(resType as IResourceWithTransactionType); }
/// <summary> /// Method to perform destocking /// </summary> /// <param name="animalEquivalentsForSale"></param> /// <param name="paddockName"></param> /// <returns>The AE that were not handled</returns> private double HandleDestocking(double animalEquivalentsForSale, string paddockName) { if (animalEquivalentsForSale <= 0) { return(0); } // move to underutilised paddocks // TODO: This can be added later as an activity including spelling // remove all potential purchases from list as they can't be supported. // This does not change the shortfall AE as they were not counted in TotalAE pressure. RuminantHerd ruminantHerd = Resources.RuminantHerd(); ruminantHerd.PurchaseIndividuals.RemoveAll(a => a.Location == paddockName); var destockGroups = FindAllChildren <RuminantGroup>().Where(a => a.Reason == RuminantStockGroupStyle.Destock); if (destockGroups.Count() == 0) { string warn = $"No [f=FilterGroup]s with a [Destock] Reason were provided in [a={this.Name}]\r\nNo destocking will be performed."; this.Status = ActivityStatus.Warning; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } // remove individuals to sale as specified by destock groups foreach (RuminantGroup item in destockGroups) { // works with current filtered herd to obey filtering. var herd = CurrentHerd(false) .Where(a => a.Location == paddockName && !a.ReadyForSale) .FilterRuminants(item).FilterRuminants(item).FilterRuminants(item) .ToList(); int cnt = 0; while (cnt < herd.Count() && animalEquivalentsForSale > 0) { if (herd[cnt].SaleFlag != HerdChangeReason.DestockSale) { animalEquivalentsForSale -= herd[cnt].AdultEquivalent; herd[cnt].SaleFlag = HerdChangeReason.DestockSale; } cnt++; } if (animalEquivalentsForSale <= 0) { return(0); } } return(animalEquivalentsForSale); // handling of sucklings with sold female is in RuminantActivityBuySell // buy or sell is handled by the buy sell activity }
private double HandleRestocking(double animalEquivalentsToBuy, string paddockName, Ruminant exampleRuminant) { if (animalEquivalentsToBuy <= 0) { return(0); } GrazeFoodStoreType foodStore = Resources.GetResourceItem(this, typeof(GrazeFoodStore), paddockName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.ReportErrorAndStop) as GrazeFoodStoreType; // ensure min pasture for restocking if ((foodStore == null) || ((foodStore.TonnesPerHectare * 1000) > MinimumFeedBeforeRestock)) { var specifyComponents = FindAllChildren <SpecifyRuminant>(); if (specifyComponents.Count() == 0) { string warn = $"No [f=SpecifyRuminant]s were provided in [a={this.Name}]\r\nNo restocking will be performed."; this.Status = ActivityStatus.Warning; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } // buy animals specified in restock ruminant groups foreach (SpecifyRuminant item in specifyComponents) { double sumAE = 0; double limitAE = animalEquivalentsToBuy * item.Proportion; while (sumAE < limitAE && animalEquivalentsToBuy > 0) { Ruminant newIndividual = item.Details.CreateIndividuals(1, null).FirstOrDefault(); newIndividual.Location = paddockName; newIndividual.BreedParams = item.BreedParams; newIndividual.HerdName = item.BreedParams.Name; newIndividual.PurchaseAge = newIndividual.Age; newIndividual.SaleFlag = HerdChangeReason.RestockPurchase; if (newIndividual.Weight == 0) { throw new ApsimXException(this, $"Specified individual added during restock cannot have no weight in [{this.Name}]"); } Resources.RuminantHerd().PurchaseIndividuals.Add(newIndividual); double indAE = newIndividual.AdultEquivalent; animalEquivalentsToBuy -= indAE; sumAE += indAE; } } return(Math.Max(0, animalEquivalentsToBuy)); } return(animalEquivalentsToBuy); }
/// <summary> /// Check whether an individual has all mandotory attributes /// </summary> /// <param name="ind">Individual ruminant to check</param> /// <param name="model">Model adding individuals</param> public void CheckMandatoryAttributes(Ruminant ind, IModel model) { foreach (var attribute in mandatoryAttributes) { if (!ind.AttributeExists(attribute)) { string warningString = $"No mandatory attribute [{attribute.ToUpper()}] present for individual added by [a={model.Name}]"; if (!Warnings.Exists(warningString)) { Warnings.Add(warningString); Summary.WriteWarning(this, warningString); } } } }
/// <summary> /// Do simple error checking to make sure the data retrieved is usable /// </summary> /// <param name="filtered"></param> /// <param name="startDate"></param> /// <param name="endDate"></param> /// <param name="region"></param> /// <param name="soil"></param> /// <param name="grassBasalArea"></param> /// <param name="landCondition"></param> /// <param name="stockingRate"></param> private void CheckAllMonthsWereRetrieved(List <PastureDataType> filtered, DateTime startDate, DateTime endDate, int region, string soil, double grassBasalArea, double landCondition, double stockingRate) { string errormessageStart = "Problem with pasture input file." + System.Environment.NewLine + $"For Region: [{region}], Soil: [{soil}], GrassBA: [{grassBasalArea}], LandCon: [{landCondition}], StkRate: [{stockingRate}]\n"; if (clock.EndDate == clock.Today) { return; } //Check no gaps in the months DateTime tempdate = startDate; if (filtered.Count() > 0 && MissingDataAction != OnMissingResourceActionTypes.Ignore) { foreach (PastureDataType month in filtered) { if ((tempdate.Year != month.Year) || (tempdate.Month != month.Month)) { // missing month entry string warn = $"Missing pasture production entry for [{tempdate.Month}/{tempdate.Year}] in [x={this.Name}]"; string warnfull = warn + $"\nGiven Region id: [{region}], Land id: [{soil}], Grass Basal Area: [{grassBasalArea}], Land Condition: [{landCondition}] & Stocking Rate: [{stockingRate}]\nAssume [0] for pasture production and all associated values such as rainfall"; if (MissingDataAction == OnMissingResourceActionTypes.ReportWarning) { if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warnfull); Warnings.Add(warn); } } else if (MissingDataAction == OnMissingResourceActionTypes.ReportErrorAndStop) { throw new ApsimXException(this, warnfull); } } tempdate = tempdate.AddMonths(1); } } }
/// <summary> /// Finds the closest Value of categorised lookup values form the database /// This applies to Stocking rates, Grass Basal Area (or use GBA) and Land Condition /// The Pasture database does not have every stocking rate, grass basal area or land condition. /// It will find the category with the next largest value to the actual value supplied. /// So if the value is 0 the category with the next largest value will normally be the first entry /// </summary> /// <param name="category">The name of the distict categories to use</param> /// <param name="value">The value to search for</param> /// <returns></returns> private double FindClosestCategory(string category, double value) { double[] valuesToUse; switch (category) { case "StockingRate": valuesToUse = distinctStkRates; if (valuesToUse.Max() > 100 | valuesToUse.Min() < 0) { // add warning string warn = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]"; if (!Warnings.Exists(warn)) { string warnfull = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]\nValues in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\nExpecting values between 1 and 100"; Summary.WriteWarning(this, warnfull); Warnings.Add(warn); } } break; case "GrassBasalArea": case "GBA": valuesToUse = distinctGBAs; if (valuesToUse.Max() > 10 | valuesToUse.Min() < 0) { // add warning string warn = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]"; if (!Warnings.Exists(warn)) { string warnfull = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]\nValues in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\nExpecting values between 0 and 10"; Summary.WriteWarning(this, warnfull); Warnings.Add(warn); } } break; case "LandCondition": valuesToUse = distinctLandConditions; if (valuesToUse.Max() > 11 | valuesToUse.Min() < 0) { // add warning string warn = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]"; if (!Warnings.Exists(warn)) { string warnfull = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]\nValues in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\nExpecting values between 1 and 11"; Summary.WriteWarning(this, warnfull); Warnings.Add(warn); } } break; default: throw new ApsimXException(this, $"Unknown pasture data category [{category}] used in code behind [x={this.Name}]"); } if (valuesToUse.Count() == 0) { throw new ApsimXException(this, $"Unable to find any values for [{category}] in [x={this.Name}]"); } int index = Array.BinarySearch(valuesToUse, value); if (~index >= valuesToUse.Count()) { // add warning string warn = $"Unable to find a [{category}] value greater than the specified value in pasture database [x={this.Name}]"; if (!Warnings.Exists(warn)) { string warnfull = $"Unable to find a [{category}] value greater than the specified [{value:0.##}] in pasture database [x={this.Name}]\nKnown values in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\nUsed: [{valuesToUse.Last()}]\nFix: Ensure the pasture database includes a [{category}] greater than values produced in this simulation for optimal results."; Summary.WriteWarning(this, warnfull); Warnings.Add(warn); } index = valuesToUse.Count() - 1; } return((index < 0) ? valuesToUse[~index] : valuesToUse[index]); }
private void OnCLEMAnimalStock(object sender, EventArgs e) { AeToDestock = 0; AeDestocked = 0; AeToRestock = 0; AeRestocked = 0; // this event happens after management has marked individuals for purchase or sale. if (this.TimingOK) { // Get ENSO forcase for current time ENSOState forecastEnsoState = GetENSOMeasure(); this.Status = ActivityStatus.NotNeeded; // calculate dry season pasture available for each managed paddock holding stock RuminantHerd ruminantHerd = Resources.RuminantHerd(); foreach (var newgroup in ruminantHerd.Herd.Where(a => a.Location != "").GroupBy(a => a.Location)) { double aELocationNeeded = 0; // total adult equivalents of all breeds on pasture for utilisation double totalAE = newgroup.Sum(a => a.AdultEquivalent); // determine AE marked for sale and purchase of managed herd double markedForSaleAE = newgroup.Where(a => a.ReadyForSale).Sum(a => a.AdultEquivalent); double purchaseAE = ruminantHerd.PurchaseIndividuals.Where(a => a.Location == newgroup.Key).Sum(a => a.AdultEquivalent); double herdChange = 1.0; bool relationshipFound = false; switch (forecastEnsoState) { case ENSOState.Neutral: break; case ENSOState.ElNino: if (!(pastureToStockingChangeElNino is null)) { GrazeFoodStoreType pasture = Resources.GetResourceItem(this, typeof(GrazeFoodStoreType), newgroup.Key, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore) as GrazeFoodStoreType; double kgha = pasture.TonnesPerHectare * 1000; herdChange = pastureToStockingChangeElNino.SolveY(kgha); relationshipFound = true; } break; case ENSOState.LaNina: if (!(pastureToStockingChangeLaNina is null)) { GrazeFoodStoreType pasture = Resources.GetResourceItem(this, typeof(GrazeFoodStoreType), newgroup.Key, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore) as GrazeFoodStoreType; double kgha = pasture.TonnesPerHectare * 1000; herdChange = pastureToStockingChangeLaNina.SolveY(kgha); relationshipFound = true; } break; default: break; } if (!relationshipFound) { string warn = $"No pasture biomass to herd change proportion [Relationship] provided for {((forecastEnsoState== ENSOState.ElNino)? "El Niño":"La Niña")} phase in [a={this.Name}]\r\nNo stock management will be performed in this phase."; this.Status = ActivityStatus.Warning; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } if (herdChange > 1.0) { aELocationNeeded = Math.Max(0, (totalAE * herdChange) - purchaseAE); AeToRestock += aELocationNeeded; double notHandled = HandleRestocking(aELocationNeeded, newgroup.Key, newgroup.FirstOrDefault()); AeRestocked += (aELocationNeeded - notHandled); } else if (herdChange < 1.0) { aELocationNeeded = Math.Max(0, (totalAE * (1 - herdChange)) - markedForSaleAE); AeToDestock += aELocationNeeded; double notHandled = HandleDestocking(AeToDestock, newgroup.Key); AeDestocked += (aELocationNeeded - notHandled); } } if (this.Status != ActivityStatus.Warning & AeToDestock + AeToRestock > 0) { if (Math.Max(0, AeToRestock - AeRestocked) + Math.Max(0, AeToDestock - AeDestocked) == 0) { this.Status = ActivityStatus.Success; } else { this.Status = ActivityStatus.Partial; } } } }
/// <summary> /// Method to determine resources required for this activity in the current month /// </summary> /// <returns>List of required resource requests</returns> public override List <ResourceRequest> GetResourcesNeededForActivity() { List <ResourceRequest> requests = new List <ResourceRequest>(); earned = 0; spent = 0; // get data currentEntries = fileResource.GetCurrentResourceData(Clock.Today.Month, Clock.Today.Year); resourceList = new List <IResourceType>(); if (currentEntries.Count > 0) { IResourceType resource = null; foreach (DataRowView item in currentEntries) { // find resource string resName = item[fileResource.ResourceNameColumnName].ToString(); if (resName.Contains(".")) { resource = Resources.GetResourceItem(this, resName, OnMissingResourceActionTypes.ReportErrorAndStop, OnMissingResourceActionTypes.ReportErrorAndStop) as IResourceType; } else { var found = Resources.FindAllDescendants <IResourceType>(resName); if (found.Count() == 1) { resource = found.FirstOrDefault(); // highlight unsupported resource types // TODO: add ability to include labour (days) and ruminants (number added/removed) switch (resource.GetType().ToString()) { case "Models.CLEM.Resources.LandType": case "Models.CLEM.Resources.RuminantType": case "Models.CLEM.Resources.LabourType": case "Models.CLEM.Resources.GrazeFoodStoreType": case "Models.CLEM.Resources.OtherAnimalsType": string warn = $"[a={this.Name}] does not support [r={resource.GetType()}]\r\nThis resource will be ignored. Contact developers for more information"; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } resource = null; break; default: break; } // if finances if (resource != null && bankAccount != null) { double amount = Convert.ToDouble(item[fileResource.AmountColumnName], CultureInfo.InvariantCulture); // get price of resource ResourcePricing price = resource.Price((amount > 0 ? PurchaseOrSalePricingStyleType.Purchase : PurchaseOrSalePricingStyleType.Sale)); double amountAvailable = (amount < 0) ? Math.Min(Math.Abs(amount), resource.Amount) : amount; double packets = amountAvailable / price.PacketSize; if (price.UseWholePackets) { packets = Math.Truncate(packets); } if (amount < 0) { earned += packets * price.PricePerPacket; } else { spent += packets * price.PricePerPacket; } } } else { string warn = ""; if (found.Count() == 0) { warn = $"[a={this.Name}] could not find a resource [r={resName}] provided by [x={fileResource.Name}] in the local [r=ResourcesHolder]\r\nExternal transactions with this resource will be ignored\r\nYou can either add this resource to your simulation or remove it from the input file to avoid this warning"; } else { warn = $"[a={this.Name}] could not distinguish between multiple occurences of resource [r={resName}] provided by [x={fileResource.Name}] in the local [r=ResourcesHolder]\r\nEnsure all resource names are unique across stores, or use ResourceStore.ResourceType notation to specify resources in the input file"; } if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } } if (resource != null) { resourceList.Add(resource); } } } return(requests); }
private void OnCLEMAnimalManage(object sender, EventArgs e) { RuminantHerd ruminantHerd = Resources.RuminantHerd(); // remove only the individuals that are affected by this activity. // these are old purchases that were not made. This list will be regenerated in this method. ruminantHerd.PurchaseIndividuals.RemoveAll(a => a.Breed == this.PredictedHerdBreed); List <Ruminant> herd = this.CurrentHerd(true); // can sell off males any month as per NABSA // if we don't need this monthly, then it goes into next if statement with herd declaration // NABSA MALES - weaners, 1-2, 2-3 and 3-4 yo, we check for any male weaned and not a breeding sire. // check for sell age/weight of young males // if SellYoungFemalesLikeMales then all apply to both sexes else only males. // SellFemalesLikeMales will grow out excess heifers until age/weight rather than sell immediately. if (this.TimingOK || ContinuousMaleSales) { foreach (var ind in herd.Where(a => a.Weaned && (SellFemalesLikeMales ? true : (a.Gender == Sex.Male)) && (a.Age >= MaleSellingAge || a.Weight >= MaleSellingWeight))) { bool sell = true; if (ind.GetType() == typeof(RuminantMale)) { // don't sell breeding sires. sell = !((ind as RuminantMale).BreedingSire); } else { // only sell females that were marked as excess sell = ind.Tags.Contains("GrowHeifer"); } if (sell) { ind.SaleFlag = HerdChangeReason.AgeWeightSale; } } } // if management month if (this.TimingOK) { // ensure pasture limits are ok before purchases bool sufficientFood = true; if (foodStore != null) { sufficientFood = (foodStore.TonnesPerHectare * 1000) >= MinimumPastureBeforeRestock; } // check for maximum age (females and males have different cutoffs) foreach (var ind in herd.Where(a => a.Age >= ((a.Gender == Sex.Female) ? MaximumBreederAge : MaximumBullAge))) { ind.SaleFlag = HerdChangeReason.MaxAgeSale; // ensure females are not pregnant and add warning if pregnant old females found. if (ind.Gender == Sex.Female && (ind as RuminantFemale).IsPregnant) { string warning = "Some females sold at maximum age in [a=" + this.Name + "] were pregnant.\nConsider changing the MaximumBreederAge in [a=RuminantActivityManage] or ensure [r=RuminantType.MaxAgeMating] is less than or equal to the MaximumBreederAge to avoid selling pregnant individuals."; if (!Warnings.Exists(warning)) { Warnings.Add(warning); Summary.WriteWarning(this, warning); } } } // MALES // check for breeder bulls after sale of old individuals and buy/sell int numberMaleSiresInHerd = herd.Where(a => a.Gender == Sex.Male && a.SaleFlag == HerdChangeReason.None).Cast <RuminantMale>().Where(a => a.BreedingSire).Count(); // Number of females int numberFemaleBreedingInHerd = herd.Where(a => a.Gender == Sex.Female && a.Age >= a.BreedParams.MinimumAge1stMating && a.SaleFlag == HerdChangeReason.None).Count(); int numberFemaleTotalInHerd = herd.Where(a => a.Gender == Sex.Female && a.SaleFlag == HerdChangeReason.None).Count(); // these are females that will exceed max age and be sold in next 12 months int numberFemaleOldInHerd = herd.Where(a => a.Gender == Sex.Female && (a.Age + 12 >= MaximumBreederAge) && a.SaleFlag == HerdChangeReason.None).Count(); // defined heifers here as weaned and will be a breeder in the next year int numberFemaleHeifersInHerd = herd.Where(a => a.Gender == Sex.Female && a.Weaned && ((a.Age - a.BreedParams.MinimumAge1stMating < 0) && (a.Age - a.BreedParams.MinimumAge1stMating > -12)) && a.SaleFlag == HerdChangeReason.None).Count(); if (numberMaleSiresInHerd > SiresKept) { // sell bulls // What rule? oldest first as they may be lost soonest? int numberToRemove = numberMaleSiresInHerd - SiresKept; if (numberToRemove > 0) { foreach (var male in herd.Where(a => a.Gender == Sex.Male).Cast <RuminantMale>().Where(a => a.BreedingSire).OrderByDescending(a => a.Age).Take(numberToRemove)) { male.SaleFlag = HerdChangeReason.ExcessBullSale; numberToRemove--; if (numberToRemove == 0) { break; } } } } else if (numberMaleSiresInHerd < SiresKept) { if ((foodStore == null) || (sufficientFood)) { if (AllowSireReplacement) { // remove young bulls from sale herd to replace breed bulls (not those sold because too old) foreach (RuminantMale male in herd.Where(a => a.Gender == Sex.Male && a.SaleFlag == HerdChangeReason.AgeWeightSale).OrderByDescending(a => a.Weight)) { male.SaleFlag = HerdChangeReason.None; male.BreedingSire = true; numberMaleSiresInHerd++; if (numberMaleSiresInHerd >= SiresKept) { break; } } // if still insufficent, look into current herd for replacement // remaining males assumed to be too small, so await next time-step } // if still insufficient buy bulls. if (numberMaleSiresInHerd < SiresKept && (MaximumSiresPerPurchase > 0)) { // limit by breeders as proportion of max breeders so we don't spend alot on sires when building the herd and females more valuable double propOfBreeders = (double)numberFemaleBreedingInHerd / (double)MaximumBreedersKept; propOfBreeders = 1; int sires = Convert.ToInt32(Math.Ceiling(Math.Ceiling(SiresKept * propOfBreeders))); int numberToBuy = Math.Min(MaximumSiresPerPurchase, Math.Max(0, sires - numberMaleSiresInHerd)); for (int i = 0; i < numberToBuy; i++) { if (i < MaximumSiresPerPurchase) { RuminantMale newbull = new RuminantMale(48, Sex.Male, 450, breedParams) { Location = grazeStore, Breed = this.PredictedHerdBreed, HerdName = this.PredictedHerdName, BreedingSire = true, Gender = Sex.Male, ID = 0, // Next unique ide will be assigned when added PreviousWeight = 450, SaleFlag = HerdChangeReason.SirePurchase }; // add to purchase request list and await purchase in Buy/Sell ruminantHerd.PurchaseIndividuals.Add(newbull); } } } } } // FEMALES // Breeding herd sold as heifers only, purchased as breeders (>= minAge1stMating) int excessBreeders = 0; // get the mortality rate for the herd if available or assume zero double mortalityRate = breedParams.MortalityBase; // shortfall between actual and desired numbers of breeders (-ve for shortfall) excessBreeders = numberFemaleBreedingInHerd - MaximumBreedersKept; // IAT-NABSA removes adjusts to account for the old animals that will be sold in the next year // This is not required in CLEM as they have been sold in this method, and it wont be until this method is called again that the next lot are sold. // Like IAT-NABSA we will account for mortality losses in the next year in our breeder purchases // Account for whole individuals only. int numberDyingInNextYear = Convert.ToInt32(Math.Floor(numberFemaleBreedingInHerd * mortalityRate), CultureInfo.InvariantCulture); // adjust for future mortality excessBreeders -= numberDyingInNextYear; // account for heifers already in the herd // These are the next cohort that will become breeders in the next 12 months (before this method is called again) excessBreeders += numberFemaleHeifersInHerd; if (excessBreeders > 0) // surplus heifers to sell { foreach (var female in herd.Where(a => a.Gender == Sex.Female && (a as RuminantFemale).IsHeifer).Take(excessBreeders)) { // if sell like males tag for grow out otherwise mark for sale if (SellFemalesLikeMales) { if (!female.Tags.Contains("GrowHeifer")) { female.Tags.Add("GrowHeifer"); } } else { // tag for sale. female.SaleFlag = HerdChangeReason.ExcessHeiferSale; } excessBreeders--; if (excessBreeders == 0) { break; } } } else if (excessBreeders < 0) // shortfall heifers to buy { double minBreedAge = breedParams.MinimumAge1stMating; excessBreeders *= -1; if ((foodStore == null) || (sufficientFood)) { // remove grow out heifers from grow out herd to replace breeders if (SellFemalesLikeMales) { foreach (Ruminant female in herd.Where(a => a.Tags.Contains("GrowHeifer")).OrderByDescending(a => a.Age)) { female.Tags.Remove("GrowHeifer"); excessBreeders--; if (excessBreeders == 0) { break; } } } // remove young females from sale herd to replace breeders (not those sold because too old) foreach (RuminantFemale female in herd.Where(a => a.Gender == Sex.Female && (a.SaleFlag == HerdChangeReason.AgeWeightSale || a.SaleFlag == HerdChangeReason.ExcessHeiferSale)).OrderByDescending(a => a.Age)) { female.SaleFlag = HerdChangeReason.None; excessBreeders--; if (excessBreeders == 0) { break; } } // if still insufficient buy breeders. if (excessBreeders > 0 && (MaximumProportionBreedersPerPurchase > 0)) { int ageOfBreeder = 0; // IAT-NABSA had buy mortality base% more to account for deaths before these individuals grow to breeding age // These individuals are already of breeding age so we will ignore this in CLEM // minimum of (max kept x prop in single purchase) and (the number needed + annual mortality) int numberToBuy = Math.Min(excessBreeders, Convert.ToInt32(Math.Ceiling(MaximumProportionBreedersPerPurchase * MaximumBreedersKept), CultureInfo.InvariantCulture)); int numberPerPurchaseCohort = Convert.ToInt32(Math.Ceiling(numberToBuy / Convert.ToDouble(NumberOfBreederPurchaseAgeClasses, CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture); int numberBought = 0; while (numberBought < numberToBuy) { int breederClass = Convert.ToInt32(numberBought / numberPerPurchaseCohort, CultureInfo.InvariantCulture); ageOfBreeder = Convert.ToInt32(minBreedAge + (breederClass * 12), CultureInfo.InvariantCulture); RuminantFemale newBreeder = new RuminantFemale(ageOfBreeder, Sex.Female, 0, breedParams) { Location = grazeStore, Breed = this.PredictedHerdBreed, HerdName = this.PredictedHerdName, BreedParams = breedParams, Gender = Sex.Female, ID = 0, SaleFlag = HerdChangeReason.BreederPurchase }; // weight will be set to normalised weight as it was assigned 0 at initialisation newBreeder.PreviousWeight = newBreeder.Weight; // this individual must be weaned to be permitted to start breeding. newBreeder.Wean(false, "Initial"); // add to purchase request list and await purchase in Buy/Sell ruminantHerd.PurchaseIndividuals.Add(newBreeder); numberBought++; } } } } // Breeders themselves don't get sold. Trading is with Heifers // Breeders can be sold in seasonal and ENSO destocking. // sell breeders // What rule? oldest first as they may be lost soonest // should keep pregnant females... and young... // this will currently remove pregnant females and females with suckling calf } }
/// <summary> /// Queries the the Pasture SQLite database using the specified parameters. /// </summary> /// <param name="region"></param> /// <param name="soil"></param> /// <param name="grassBasalArea"></param> /// <param name="landCondition"></param> /// <param name="stockingRate"></param> /// <param name="ecolCalculationDate"></param> /// <param name="ecolCalculationInterval"></param> /// <returns></returns> public List <PastureDataType> GetIntervalsPastureData(int region, string soil, double grassBasalArea, double landCondition, double stockingRate, DateTime ecolCalculationDate, int ecolCalculationInterval) { List <PastureDataType> pastureDetails = new List <PastureDataType>(); if (validationResults.Count > 0 | ecolCalculationDate > clock.EndDate) { return(pastureDetails); } int startYear = ecolCalculationDate.Year; int startMonth = ecolCalculationDate.Month; DateTime endDate = ecolCalculationDate.AddMonths(ecolCalculationInterval + 1); if (endDate > clock.EndDate) { endDate = clock.EndDate; } int endYear = endDate.Year; int endMonth = endDate.Month; double stkRateCategory = FindClosestCategory("StockingRate", stockingRate); double grassBasalAreaCategory = FindClosestCategory("GBA", grassBasalArea); double landConditionCategory = FindClosestCategory("LandCondition", landCondition); string sqlQuery = "SELECT " + YearColumnName + ", " + MonthColumnName + "," + GrowthColumnName; if (ErosionColumnName != null && ErosionColumnName != "") { sqlQuery += "," + ErosionColumnName; } if (RunoffColumnName != null || RunoffColumnName != "") { sqlQuery += "," + RunoffColumnName; } if (RainfallColumnName != null || RainfallColumnName != "") { sqlQuery += "," + RainfallColumnName; } if (CoverColumnName != null || CoverColumnName != "") { sqlQuery += "," + CoverColumnName; } if (TBAColumnName != null || TBAColumnName != "") { sqlQuery += "," + TBAColumnName; } sqlQuery += " FROM " + TableName + " WHERE " + RegionColumnName + " = " + region + " AND " + LandIdColumnName + " = " + soil + " AND " + GrassBAColumnName + " = " + grassBasalAreaCategory + " AND " + LandConColumnName + " = " + landConditionCategory + " AND " + StkRateColumnName + " = " + stkRateCategory; if (shuffler != null) { int shuffleStartYear = shuffler.ShuffledYears.Where(a => a.Year == startYear).FirstOrDefault().RandomYear; int shuffleEndYear = shuffler.ShuffledYears.Where(a => a.Year == endYear).FirstOrDefault().RandomYear; // first year sqlQuery += " AND (( " + YearColumnName + " = " + shuffleStartYear + " AND " + MonthColumnName + " >= " + startMonth + ")"; // any middle years for (int i = startYear + 1; i < endYear; i++) { sqlQuery += " OR ( " + YearColumnName + " = " + shuffler.ShuffledYears[i] + ")"; } //last year sqlQuery += " OR ( " + YearColumnName + " = " + shuffleEndYear + " AND " + MonthColumnName + " <= " + endMonth + "))"; } else { if (startYear == endYear) { sqlQuery += " AND (( " + YearColumnName + " = " + startYear + " AND " + MonthColumnName + " >= " + startMonth + " AND " + MonthColumnName + " < " + endMonth + ")" + ")"; } else { sqlQuery += " AND (( " + YearColumnName + " = " + startYear + " AND " + MonthColumnName + " >= " + startMonth + ")" + " OR ( " + YearColumnName + " > " + startYear + " AND " + YearColumnName + " < " + endYear + ")" + " OR ( " + YearColumnName + " = " + endYear + " AND " + MonthColumnName + " < " + endMonth + ")" + ")"; } } DataTable results = SQLiteReader.ExecuteQuery(sqlQuery); if (results.Rows.Count == 0) { switch (MissingDataAction) { case OnMissingResourceActionTypes.ReportWarning: // this is no longer an error to allow situations where there is no pasture production reported in a given period string warn = $"No pasture production for was found for [{startMonth}/{startYear}] by [x={this.Name}]"; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn + $"\nGiven Region id: [{region}], Land id: [{soil}], Grass Basal Area: [{grassBasalAreaCategory}], Land Condition: [{landConditionCategory}] & Stocking Rate: [{stkRateCategory}]"); Warnings.Add(warn); } break; default: break; } return(null); } // re-label shuffled years if (shuffler != null) { foreach (DataRow row in results.Rows) { row["Year"] = shuffler.ShuffledYears.Where(a => a.RandomYear == Convert.ToInt32(row["Year"])).FirstOrDefault().Year; } } results.DefaultView.Sort = YearColumnName + ", " + MonthColumnName; foreach (DataRowView row in results.DefaultView) { pastureDetails.Add(DataRow2PastureDataType(row)); } CheckAllMonthsWereRetrieved(pastureDetails, ecolCalculationDate, endDate, region, soil, grassBasalAreaCategory, landConditionCategory, stkRateCategory); return(pastureDetails); }
private void OnCLEMAnimalWeightGain(object sender, EventArgs e) { RuminantHerd ruminantHerd = Resources.RuminantHerd(); List <Ruminant> herd = ruminantHerd.Herd; int cmonth = Clock.Today.Month; // grow individuals List <string> breeds = herd.Select(a => a.BreedParams.Name).Distinct().ToList(); this.Status = ActivityStatus.NotNeeded; foreach (string breed in breeds) { int unfed = 0; int unfedcalves = 0; double totalMethane = 0; foreach (Ruminant ind in herd.Where(a => a.BreedParams.Name == breed).OrderByDescending(a => a.Age)) { ind.MetabolicIntake = ind.Intake; this.Status = ActivityStatus.Success; if (ind.Weaned) { // check that they had some food if (ind.Intake == 0) { unfed++; } // calculate protein concentration // Calculate diet dry matter digestibilty from the %N of the current diet intake. // Reference: Ash and McIvor // ind.DietDryMatterDigestibility = 36.7 + 9.36 * ind.PercentNOfIntake / 62.5; // Now tracked via incoming food DMD values // TODO: NABSA restricts Diet_DMD to 75% before supplements. Why? // Our flow has already taken in supplements by this stage and cannot be distinguished // Maybe this limit should be placed on some feed to limit DMD to 75% for non supp feeds // A Ash stated that the 75% limit is no longer required and DMD above 75% is possible even if unlikely. // TODO: Check equation. NABSA doesn't include the 0.9 // Crude protein required generally 130g per kg of digestable feed. double crudeProteinRequired = ind.BreedParams.ProteinCoefficient * ind.DietDryMatterDigestibility / 100; // adjust for efficiency of use of protein, (default 90%) degradable. now user param. double crudeProteinSupply = (ind.PercentNOfIntake * 62.5) * ind.BreedParams.ProteinDegradability; // This was proteinconcentration * 0.9 // prevent future divide by zero issues. if (crudeProteinSupply == 0.0) { crudeProteinSupply = 0.001; } if (crudeProteinSupply < crudeProteinRequired) { double ratioSupplyRequired = (crudeProteinSupply + crudeProteinRequired) / (2 * crudeProteinRequired); //TODO: add min protein to parameters ratioSupplyRequired = Math.Max(ratioSupplyRequired, 0.3); ind.MetabolicIntake *= ratioSupplyRequired; } // old. I think IAT //double ratioSupplyRequired = Math.Max(0.3, Math.Min(1.3, crudeProteinSupply / crudeProteinRequired)); // TODO: check if we still need to apply modification to only the non-supplemented component of intake // Used to be 1.2 * Potential ind.Intake = Math.Min(ind.Intake, ind.PotentialIntake); ind.MetabolicIntake = Math.Min(ind.MetabolicIntake, ind.Intake); } else { if (ind.Intake == 0) { unfedcalves++; } // no potential * 1.2 as potential has been fixed based on suckling individuals. ind.Intake = Math.Min(ind.Intake, ind.PotentialIntake); ind.MetabolicIntake = Math.Min(ind.MetabolicIntake, ind.Intake); } // TODO: nabsa adjusts potential intake for digestability of fodder here. // This is now done in RuminantActivityGrazePasture // calculate energy CalculateEnergy(ind, out double methane); // Sum and produce one event for breed at end of loop totalMethane += methane; // grow wool and cashmere ind.Wool += ind.BreedParams.WoolCoefficient * ind.Intake; ind.Cashmere += ind.BreedParams.CashmereCoefficient * ind.Intake; } // alert user to unfed animals in the month as this should not happen if (unfed > 0) { string warn = $"individuals of [r={breed}] not fed"; if (!Warnings.Exists(warn)) { string warnfull = $"Some individuals of [r={breed}] were not fed in some months (e.g. [{unfed}] in [{Clock.Today.Month}/{Clock.Today.Year}])\nFix: Check feeding strategy and ensure animals are mustered to pasture or fed in yards"; Summary.WriteWarning(this, warnfull); Warnings.Add(warn); } } if (unfedcalves > 0) { string warn = $"calves of [r={breed}] not fed"; if (!Warnings.Exists(warn)) { string warnfull = $"Some calves of [r={breed}] were not fed in some months (e.g. [{unfedcalves}] in [{Clock.Today.Month}/{Clock.Today.Year}])\nFix: Check calves are are fed, or have access to pasture (mustered with mothers or separately) when no milk is available from mother"; Summary.WriteWarning(this, warnfull); Warnings.Add(warn); } } if (methaneEmissions != null) { // g per day -> total kg methaneEmissions.Add(totalMethane * 30.4 / 1000, this, breed); } } }
private void OnCLEMAnimalBreeding(object sender, EventArgs e) { this.Status = ActivityStatus.NotNeeded; NumberConceived = 0; // get list of all pregnant females List <RuminantFemale> pregnantherd = CurrentHerd(true).Where(a => a.Gender == Sex.Female).Cast <RuminantFemale>().Where(a => a.IsPregnant).ToList(); // determine all fetus and newborn mortality of all pregnant females. foreach (RuminantFemale female in pregnantherd) { // calculate fetus and newborn mortality // total mortality / (gestation months + 1) to get monthly mortality // done here before births to account for post birth motality as well.. // IsPregnant status does not change until births occur in next section so will include mortality in month of birth // needs to be calculated for each offspring carried. for (int i = 0; i < female.CarryingCount; i++) { if (RandomNumberGenerator.Generator.NextDouble() < (female.BreedParams.PrenatalMortality / (female.BreedParams.GestationLength + 1))) { female.OneOffspringDies(); if (female.NumberOfOffspring == 0) { // report conception status changed when last multiple birth dies. female.BreedParams.OnConceptionStatusChanged(new Reporting.ConceptionStatusChangedEventArgs(Reporting.ConceptionStatus.Failed, female, Clock.Today)); } } } if (female.BirthDue) { int numberOfNewborn = female.CarryingCount; for (int i = 0; i < numberOfNewborn; i++) { object newCalf = null; bool isMale = (RandomNumberGenerator.Generator.NextDouble() <= female.BreedParams.ProportionOffspringMale); double weight = female.BreedParams.SRWBirth * female.StandardReferenceWeight * (1 - 0.33 * (1 - female.Weight / female.StandardReferenceWeight)); if (isMale) { newCalf = new RuminantMale(0, Sex.Male, weight, female.BreedParams); } else { newCalf = new RuminantFemale(0, Sex.Female, weight, female.BreedParams); } Ruminant newCalfRuminant = newCalf as Ruminant; newCalfRuminant.HerdName = female.HerdName; newCalfRuminant.Breed = female.BreedParams.Breed; newCalfRuminant.ID = Resources.RuminantHerd().NextUniqueID; newCalfRuminant.Location = female.Location; newCalfRuminant.Mother = female; newCalfRuminant.Number = 1; newCalfRuminant.SetUnweaned(); // calf weight from Freer newCalfRuminant.PreviousWeight = newCalfRuminant.Weight; newCalfRuminant.SaleFlag = HerdChangeReason.Born; // add attributes inherited from mother foreach (var attribute in female.Attributes) { newCalfRuminant.AddAttribute(attribute.Key, attribute.Value.GetInheritedAttribute() as ICLEMAttribute); } Resources.RuminantHerd().AddRuminant(newCalfRuminant, this); // add to sucklings female.SucklingOffspringList.Add(newCalfRuminant); // this now reports for each individual born not a birth event as individual wean events are reported female.BreedParams.OnConceptionStatusChanged(new Reporting.ConceptionStatusChangedEventArgs(Reporting.ConceptionStatus.Birth, female, Clock.Today)); } female.UpdateBirthDetails(); this.Status = ActivityStatus.Success; } } // Perform breeding IEnumerable <Ruminant> herd = null; if (useControlledMating && controlledMating.TimingOK) { // determined by controlled mating and subsequent timer (e.g. smart milking) herd = controlledMating.BreedersToMate(); this.TriggerOnActivityPerformed(); } else if (!useControlledMating && TimingOK) { // whole herd for activity herd = CurrentHerd(true); // report that this activity was performed as it does not use base GetResourcesRequired this.TriggerOnActivityPerformed(); } if (herd != null && herd.Count() > 0) { // group by location var breeders = from ind in herd where ind.IsAbleToBreed group ind by ind.Location into grp select grp; int breedersCount = breeders.Count(); int numberPossible = breedersCount; int numberServiced = 1; List <Ruminant> maleBreeders = new List <Ruminant>(); // for each location where parts of this herd are located foreach (var location in breeders) { numberPossible = -1; if (useControlledMating) { numberPossible = Convert.ToInt32(location.Where(a => a.Gender == Sex.Female).Count(), CultureInfo.InvariantCulture); } else { numberPossible = 0; // uncontrolled conception if (location.GroupBy(a => a.Gender).Count() == 2) { int maleCount = location.Where(a => a.Gender == Sex.Male).Count(); // get a list of males to provide attributes when incontrolled mating. if (maleCount > 0 && location.FirstOrDefault().BreedParams.IncludedAttributeInheritanceWhenMating) { maleBreeders = location.Where(a => a.Gender == Sex.Male).ToList(); } int femaleCount = location.Where(a => a.Gender == Sex.Female).Count(); numberPossible = Convert.ToInt32(Math.Ceiling(maleCount * location.FirstOrDefault().BreedParams.MaximumMaleMatingsPerDay * 30), CultureInfo.InvariantCulture); } } numberServiced = 1; foreach (RuminantFemale female in location.Where(a => a.Gender == Sex.Female).Cast <RuminantFemale>().Where(a => !a.IsPregnant & a.Age <= a.BreedParams.MaximumAgeMating).ToList()) { Reporting.ConceptionStatus status = Reporting.ConceptionStatus.NotMated; if (numberServiced <= numberPossible) { // calculate conception double conceptionRate = ConceptionRate(female, out status); if (conceptionRate > 0) { if (RandomNumberGenerator.Generator.NextDouble() <= conceptionRate) { female.UpdateConceptionDetails(female.CalulateNumberOfOffspringThisPregnancy(), conceptionRate, 0); // if mandatory attributes are present in the herd, save male value with female details. if (female.BreedParams.IncludedAttributeInheritanceWhenMating) { if (useControlledMating) { // save all male attributes AddMalesAttributeDetails(female, controlledMating.SireAttributes); } else { // randomly select male AddMalesAttributeDetails(female, maleBreeders[RandomNumberGenerator.Generator.Next(0, maleBreeders.Count() - 1)]); } } status = Reporting.ConceptionStatus.Conceived; NumberConceived++; } } numberServiced++; this.Status = ActivityStatus.Success; } // report change in breeding status // do not report for -1 (controlled mating outside timing) if (numberPossible >= 0 && status != Reporting.ConceptionStatus.NotAvailable) { female.BreedParams.OnConceptionStatusChanged(new Reporting.ConceptionStatusChangedEventArgs(status, female, Clock.Today)); } } // report a natural mating locations for transparency via a message if (numberServiced > 0 & !useControlledMating) { string warning = "Natural (uncontrolled) mating ocurred in [r=" + location.Key + "]"; if (!Warnings.Exists(warning)) { Warnings.Add(warning); Summary.WriteMessage(this, warning); } } } } }
private void OnCLEMAnimalBreeding(object sender, EventArgs e) { List <Ruminant> herd = CurrentHerd(true); int aDay = Clock.Today.Year; // get list of all individuals of breeding age and condition // grouped by location var breeders = from ind in herd where ind.IsBreedingCondition group ind by ind.Location into grp select grp; // calculate labour and finance limitations if needed when doing AI int breedersCount = breeders.Count(); int numberPossible = breedersCount; int numberServiced = 1; double limiter = 1; if (UseAI && TimingOK) { // attempt to get required resources List <ResourceRequest> resourcesneeded = GetResourcesNeededForActivityLocal(); CheckResources(resourcesneeded, Guid.NewGuid()); bool tookRequestedResources = TakeResources(resourcesneeded, true); // get all shortfalls if (tookRequestedResources && (ResourceRequestList != null)) { //TODO: fix this to account for perHead payments and labour and not fixed expenses double amountCashNeeded = resourcesneeded.Where(a => a.ResourceType == typeof(Finance)).Sum(a => a.Required); double amountCashProvided = resourcesneeded.Where(a => a.ResourceType == typeof(Finance)).Sum(a => a.Provided); double amountLabourNeeded = resourcesneeded.Where(a => a.ResourceType == typeof(Labour)).Sum(a => a.Required); double amountLabourProvided = resourcesneeded.Where(a => a.ResourceType == typeof(Labour)).Sum(a => a.Provided); double cashlimit = 1; if (amountCashNeeded > 0) { cashlimit = amountCashProvided == 0 ? 0 : amountCashNeeded / amountCashProvided; } double labourlimit = 1; if (amountLabourNeeded > 0) { labourlimit = amountLabourProvided == 0 ? 0 : amountLabourNeeded / amountLabourProvided; } limiter = Math.Min(cashlimit, labourlimit); // TODO: determine if fixed payments were not possible // TODO: determine limits by insufficient labour or cash for per head payments } // report that this activity was performed as it does not use base GetResourcesRequired this.TriggerOnActivityPerformed(); } if (!UseAI) { // report that this activity was performed as it does not use base GetResourcesRequired this.TriggerOnActivityPerformed(); this.Status = ActivityStatus.NotNeeded; } // for each location where parts of this herd are located foreach (var location in breeders) { // determine all fetus and newborn mortality of all pregnant females. foreach (RuminantFemale female in location.Where(a => a.Gender == Sex.Female).Cast <RuminantFemale>().Where(a => a.IsPregnant).ToList()) { // calculate fetus and newborn mortality // total mortality / (gestation months + 1) to get monthly mortality // done here before births to account for post birth motality as well.. // IsPregnant status does not change until births occur in next section so will include mortality in month of birth // needs to be caclulated for each offspring carried. for (int i = 0; i < female.CarryingCount; i++) { if (RandomNumberGenerator.Generator.NextDouble() < (female.BreedParams.PrenatalMortality / (female.BreedParams.GestationLength + 1))) { female.OneOffspringDies(); if (female.NumberOfOffspring == 0) { // report conception status changed when last multiple birth dies. female.BreedParams.OnConceptionStatusChanged(new Reporting.ConceptionStatusChangedEventArgs(Reporting.ConceptionStatus.Failed, female, Clock.Today)); } } } } // check for births of all pregnant females. int month = Clock.Today.Month; foreach (RuminantFemale female in location.Where(a => a.Gender == Sex.Female).Cast <RuminantFemale>().ToList()) { if (female.BirthDue) { int numberOfNewborn = female.CarryingCount; for (int i = 0; i < numberOfNewborn; i++) { // Foetal mortality is now performed each timestep at base of this method object newCalf = null; bool isMale = (RandomNumberGenerator.Generator.NextDouble() <= female.BreedParams.ProportionOffspringMale); double weight = female.BreedParams.SRWBirth * female.StandardReferenceWeight * (1 - 0.33 * (1 - female.Weight / female.StandardReferenceWeight)); if (isMale) { newCalf = new RuminantMale(0, Sex.Male, weight, female.BreedParams); } else { newCalf = new RuminantFemale(0, Sex.Female, weight, female.BreedParams); } Ruminant newCalfRuminant = newCalf as Ruminant; newCalfRuminant.HerdName = female.HerdName; newCalfRuminant.Breed = female.BreedParams.Breed; newCalfRuminant.ID = Resources.RuminantHerd().NextUniqueID; newCalfRuminant.Location = female.Location; newCalfRuminant.Mother = female; newCalfRuminant.Number = 1; newCalfRuminant.SetUnweaned(); // calf weight from Freer newCalfRuminant.PreviousWeight = newCalfRuminant.Weight; newCalfRuminant.SaleFlag = HerdChangeReason.Born; Resources.RuminantHerd().AddRuminant(newCalfRuminant, this); // add to sucklings female.SucklingOffspringList.Add(newCalfRuminant); // this now reports for each individual born not a birth event as individual wean events are reported female.BreedParams.OnConceptionStatusChanged(new Reporting.ConceptionStatusChangedEventArgs(Reporting.ConceptionStatus.Birth, female, Clock.Today)); } female.UpdateBirthDetails(); this.Status = ActivityStatus.Success; } } numberPossible = -1; if (!UseAI) { numberPossible = 0; // uncontrolled conception if (location.GroupBy(a => a.Gender).Count() == 2) { int maleCount = location.Where(a => a.Gender == Sex.Male).Count(); int femaleCount = location.Where(a => a.Gender == Sex.Female).Count(); numberPossible = Convert.ToInt32(Math.Ceiling(maleCount * location.FirstOrDefault().BreedParams.MaximumMaleMatingsPerDay * 30), CultureInfo.InvariantCulture); } } else { // controlled mating (AI) if (this.TimingOK) { numberPossible = Convert.ToInt32(limiter * location.Where(a => a.Gender == Sex.Female).Count(), CultureInfo.InvariantCulture); } } numberServiced = 1; foreach (RuminantFemale female in location.Where(a => a.Gender == Sex.Female).Cast <RuminantFemale>().Where(a => !a.IsPregnant & a.Age <= a.BreedParams.MaximumAgeMating).ToList()) { Reporting.ConceptionStatus status = Reporting.ConceptionStatus.NotMated; if (numberServiced <= numberPossible) { // calculate conception double conceptionRate = ConceptionRate(female, out status); if (conceptionRate > 0) { if (RandomNumberGenerator.Generator.NextDouble() <= conceptionRate) { female.UpdateConceptionDetails(female.CalulateNumberOfOffspringThisPregnancy(), conceptionRate, 0); status = Reporting.ConceptionStatus.Conceived; } } numberServiced++; this.Status = ActivityStatus.Success; } // report change in breeding status // do not report for -1 (controlled mating outside timing) if (numberPossible >= 0 && status != Reporting.ConceptionStatus.NotAvailable) { female.BreedParams.OnConceptionStatusChanged(new Reporting.ConceptionStatusChangedEventArgs(status, female, Clock.Today)); } } // report a natural mating locations for transparency via a message if (this.Status == ActivityStatus.Success && !UseAI) { string warning = "Natural (uncontrolled) mating ocurred in [r=" + location.Key + "]"; if (!Warnings.Exists(warning)) { Warnings.Add(warning); Summary.WriteMessage(this, warning); } } } }
private void OnCLEMStartOfTimeStep(object sender, EventArgs e) { // if harvest tags provided for this crop then they will be used to define previous, next etc // while date month > harvest record look for previous and delete past events if (HarvestData.Count() > 0) { int clockYrMth = CalculateYearMonth(Clock.Today); int position; // passed -1, current 0, future 1 do { int harvestYrMth = CalculateYearMonth(HarvestData.First().HarvestDate); position = (clockYrMth > harvestYrMth) ? -1 : ((clockYrMth == harvestYrMth) ? 0 : 1); // check for valid sequence if (HarvestTagsUsed && HarvestData.FirstOrDefault().HarvestType != "") { if (previousTag == HarvestData.FirstOrDefault().HarvestType) { string warn = $"Invalid sequence of HarvetTags detected in [a={this.Name}]\r\nEnsure tags are ordered first, last in sequence."; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } previousTag = HarvestData.FirstOrDefault().HarvestType; } switch (position) { case -1: if (HarvestTagsUsed) { switch (HarvestData.FirstOrDefault().HarvestType) { case "first": if (!performedHarvest) { InsideMultiHarvestSequence = true; StartCurrentSequenceHarvest = HarvestData.FirstOrDefault(); EndCurrentSequenceHarvest = HarvestData.Where(a => a.HarvestType == "last").FirstOrDefault(); } break; case "last": // hit tagged last to delete as we've passed this date so out of multi harvest sequence InsideMultiHarvestSequence = false; PreviousHarvest = HarvestData.FirstOrDefault(); break; default: break; } } else { PreviousHarvest = HarvestData.FirstOrDefault(); } HarvestData.RemoveAt(0); break; case 0: performedHarvest = true; if (HarvestTagsUsed) { switch (HarvestData.FirstOrDefault().HarvestType) { case "first": // hit tagged first for current time step InsideMultiHarvestSequence = true; StartCurrentSequenceHarvest = HarvestData.FirstOrDefault(); PreviousHarvest = null; EndCurrentSequenceHarvest = HarvestData.Where(a => a.HarvestType == "last").FirstOrDefault(); break; default: NextHarvest = HarvestData.FirstOrDefault(); PreviousHarvest = null; break; } } else { NextHarvest = HarvestData.FirstOrDefault(); PreviousHarvest = null; } break; case 1: if (HarvestTagsUsed) { switch (HarvestData.FirstOrDefault().HarvestType) { case "first": // hit tagged first for next harvest NextHarvest = HarvestData.FirstOrDefault(); break; default: NextHarvest = HarvestData.FirstOrDefault(); break; } } else { NextHarvest = HarvestData.FirstOrDefault(); } break; default: break; } } while (HarvestData.Count > 0 && position == -1); } }
private void OnCLEMInitialiseActivity(object sender, EventArgs e) { // create local version of max breeders so we can modify without affecting user set value maxBreeders = Math.Max(this.MaximumBreedersKept, this.MinimumBreedersKept); this.InitialiseHerd(false, true); breedParams = Resources.GetResourceItem(this, typeof(RuminantHerd), this.PredictedHerdName, OnMissingResourceActionTypes.ReportErrorAndStop, OnMissingResourceActionTypes.ReportErrorAndStop) as RuminantType; decimal breederHerdSize = 0; if (AdjustBreedingFemalesAtStartup) { RuminantHerd herd = Resources.RuminantHerd(); List <Ruminant> rumHerd = this.CurrentHerd(false); if (rumHerd != null && rumHerd.Count() > 0) { int numberAdded = 0; RuminantType breedParams = rumHerd.FirstOrDefault().BreedParams; RuminantInitialCohorts cohorts = Apsim.Children(rumHerd.FirstOrDefault().BreedParams, typeof(RuminantInitialCohorts)).FirstOrDefault() as RuminantInitialCohorts; if (cohorts != null) { List <RuminantTypeCohort> cohortList = Apsim.Children(cohorts, typeof(RuminantTypeCohort)).Cast <RuminantTypeCohort>().Where(a => a.Gender == Sex.Female && (a.Age >= breedParams.MinimumAge1stMating & a.Age <= this.MaximumBreederAge)).ToList(); int initialBreeders = Convert.ToInt32(cohortList.Sum(a => a.Number)); if (initialBreeders < this.MinimumBreedersKept) { double scaleFactor = this.MinimumBreedersKept / Convert.ToDouble(initialBreeders); // add new individuals foreach (var item in cohortList) { int numberToAdd = Convert.ToInt32(Math.Round(item.Number * scaleFactor) - item.Number); foreach (var newind in item.CreateIndividuals(numberToAdd)) { newind.SaleFlag = HerdChangeReason.FillInitialHerd; herd.AddRuminant(newind, this); numberAdded++; } } if (numberAdded == 0) { throw new ApsimXException(this, $"Unable to scale breeding female population up to the maximum breeders kept at startup\nNo cohorts representing breeders were found in the initial herd structure [r=InitialCohorts] for [r={breedParams.Name}]\nAdd at least one initial cohort that meets the breeder criteria of age at first mating and max age kept"); } breederHerdSize = initialBreeders + numberAdded; } else if (initialBreeders > maxBreeders) { int reduceBy = Math.Max(0, initialBreeders - maxBreeders); // reduce initial herd size // randomly select the individuals to remove form the breeder herd List <Ruminant> breeders = rumHerd.Where(a => a.Gender == Sex.Female && a.Age > breedParams.MinimumAge1stMating && a.Age < this.MaximumBreederAge).OrderBy(x => Guid.NewGuid()).Take(reduceBy).ToList(); foreach (var item in breeders) { item.SaleFlag = HerdChangeReason.ReduceInitialHerd; herd.RemoveRuminant(item, this); reduceBy--; } if (reduceBy > 0) { // add warning string warn = $"Unable to reduce breeders at the start of the simulation to number required [{maxBreeders}] using [a={this.Name}]"; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } breederHerdSize = maxBreeders; } } else { throw new ApsimXException(this, $"Unable to adjust breeding female population to the maximum breeders kept at startup\nNo initial herd structure [r=InitialCohorts] has been provided in [r={breedParams.Name}]"); } } } // max sires if (MaximumSiresKept < 1 & MaximumSiresKept > 0) { SiresKept = Convert.ToInt32(Math.Ceiling(maxBreeders * breederHerdSize), CultureInfo.InvariantCulture); } else { SiresKept = Convert.ToInt32(Math.Truncate(MaximumSiresKept), CultureInfo.InvariantCulture); } if (AdjustBreedingMalesAtStartup) { RuminantHerd herd = Resources.RuminantHerd(); if (herd != null) { // get number in herd List <Ruminant> rumHerd = this.CurrentHerd(false); int numberPresent = rumHerd.Where(a => a.Gender == Sex.Male).Cast <RuminantMale>().Where(a => a.BreedingSire).Count(); if (numberPresent < SiresKept) { // fill to number needed for (int i = numberPresent; i < SiresKept; i++) { RuminantMale newSire = new RuminantMale(SireAgeAtPurchase, Sex.Male, 0, breedParams) { Breed = this.PredictedHerdBreed, HerdName = this.PredictedHerdName, BreedingSire = true, ID = herd.NextUniqueID, SaleFlag = HerdChangeReason.FillInitialHerd }; herd.AddRuminant(newSire, this); } } else if (numberPresent > SiresKept) { // reduce initial herd. int reduceBy = Math.Max(0, numberPresent - SiresKept); // reduce initial sire herd size // randomly select the individuals to remove form the breeder herd List <RuminantMale> sires = rumHerd.Where(a => a.Gender == Sex.Male).Cast <RuminantMale>().Where(a => a.BreedingSire).OrderBy(x => Guid.NewGuid()).Take(reduceBy).ToList(); foreach (var item in sires) { item.SaleFlag = HerdChangeReason.ReduceInitialHerd; herd.RemoveRuminant(item, this); reduceBy--; } if (reduceBy > 0) { // add warning string warn = $"Unable to reduce breeding sires at the start of the simulation to number required [{SiresKept}] using [a={this.Name}]"; if (!Warnings.Exists(warn)) { Summary.WriteWarning(this, warn); Warnings.Add(warn); } } } } } // check GrazeFoodStoreExists for breeders grazeStoreBreeders = ""; if (GrazeFoodStoreNameBreeders != null && !GrazeFoodStoreNameBreeders.StartsWith("Not specified")) { grazeStoreBreeders = GrazeFoodStoreNameBreeders.Split('.').Last(); foodStoreBreeders = Resources.GetResourceItem(this, GrazeFoodStoreNameBreeders, OnMissingResourceActionTypes.ReportErrorAndStop, OnMissingResourceActionTypes.ReportErrorAndStop) as GrazeFoodStoreType; } // check for managed paddocks and warn if breeders placed in yards. if (grazeStoreBreeders == "" && this.MaximumProportionBreedersPerPurchase > 0) { var ah = Apsim.Find(this, typeof(ActivitiesHolder)); if (Apsim.ChildrenRecursively(ah, typeof(PastureActivityManage)).Count() != 0) { Summary.WriteWarning(this, String.Format("Breeders purchased by [a={0}] are currently placed in [Not specified - general yards] while a managed pasture is available. These animals will not graze until mustered and will require feeding while in yards.\nSolution: Set the [GrazeFoodStore to place purchase in] located in the properties [General].[PastureDetails]", this.Name)); } } // check GrazeFoodStoreExists for sires grazeStoreSires = ""; if (GrazeFoodStoreNameSires != null && !GrazeFoodStoreNameSires.StartsWith("Not specified")) { grazeStoreSires = GrazeFoodStoreNameSires.Split('.').Last(); foodStoreSires = Resources.GetResourceItem(this, GrazeFoodStoreNameSires, OnMissingResourceActionTypes.ReportErrorAndStop, OnMissingResourceActionTypes.ReportErrorAndStop) as GrazeFoodStoreType; } // check for managed paddocks and warn if sires placed in yards. if (grazeStoreBreeders == "" && this.SiresKept > 0) { var ah = Apsim.Find(this, typeof(ActivitiesHolder)); if (Apsim.ChildrenRecursively(ah, typeof(PastureActivityManage)).Count() != 0) { Summary.WriteWarning(this, String.Format("Sires purchased by [a={0}] are currently placed in [Not specified - general yards] while a managed pasture is available. These animals will not graze until mustered and will require feeding while in yards.\nSolution: Set the [GrazeFoodStore to place purchase in] located in the properties [General].[PastureDetails]", this.Name)); } } }