예제 #1
0
        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);
        }
예제 #2
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 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);
        }
예제 #3
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);
        }
예제 #4
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);
        }
예제 #5
0
        /// <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);
        }
예제 #8
0
 /// <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);
             }
         }
     }
 }
예제 #9
0
        /// <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);
                }
            }
        }
예제 #10
0
        /// <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);
        }
예제 #13
0
        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
            }
        }
예제 #14
0
        /// <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);
        }
예제 #15
0
        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);
                }
            }
        }
예제 #16
0
        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);
                        }
                    }
                }
            }
        }
예제 #17
0
        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);
                    }
                }
            }
        }
예제 #18
0
        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));
                }
            }
        }