/// <summary> /// Get value of a specific individual /// </summary> /// <returns>value</returns> public AnimalPriceGroup ValueofIndividual(Ruminant ind, PurchaseOrSalePricingStyleType purchaseStyle, string warningMessage = "") { if (PricingAvailable()) { if (ind.CurrentPrice == null || !ind.CurrentPrice.Filter(ind)) { // search through RuminantPriceGroups for first match with desired purchase or sale flag foreach (AnimalPriceGroup priceGroup in priceGroups.Where(a => a.PurchaseOrSale == purchaseStyle || a.PurchaseOrSale == PurchaseOrSalePricingStyleType.Both)) { if (priceGroup.Filter(ind)) { ind.CurrentPrice = priceGroup; return(priceGroup); } } // no price match found. string warningString = warningMessage; if (warningString == "") { warningString = $"No [{purchaseStyle}] price entry was found for [r={ind.Breed}] meeting the required criteria [f=age: {ind.Age}] [f=sex: {ind.Sex}] [f=weight: {ind.Weight:##0}]"; } Warnings.CheckAndWrite(warningString, Summary, this); } return(ind.CurrentPrice); } return(null); }
/// <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) { 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 + $"\r\nGiven Region id: [{region}], Land id: [{soil}], Grass Basal Area: [{grassBasalArea}], Land Condition: [{landCondition}] & Stocking Rate: [{stockingRate}]\r\nAssume [0] for pasture production and all associated values such as rainfall"; if (MissingDataAction == OnMissingResourceActionTypes.ReportWarning) { Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning, warnfull); } else if (MissingDataAction == OnMissingResourceActionTypes.ReportErrorAndStop) { throw new ApsimXException(this, warnfull); } } tempdate = tempdate.AddMonths(1); } } }
private void OnCLEMInitialiseActivity(object sender, EventArgs e) { this.InitialiseHerd(false, true); IEnumerable <Ruminant> testherd = this.CurrentHerd(true); // check if finance is available and warn if not supplying bank account. if (Resources.ResourceItemsExist <Finance>()) { if (BankAccountName == "") { Summary.WriteMessage(this, $"No bank account has been specified in [a={this.Name}] while Finances are available in the simulation. No financial transactions will be recorded for the purchase and sale of animals.", MessageType.Warning); } } if (BankAccountName != "") { bankAccount = Resources.FindResourceType <Finance, FinanceType>(this, BankAccountName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.ReportErrorAndStop); } // get trucking settings trucking = this.FindAllChildren <TruckingSettings>().FirstOrDefault() as TruckingSettings; // check if pricing is present if (bankAccount != null) { var breeds = HerdResource.Herd.Where(a => a.BreedParams.Breed == this.PredictedHerdBreed).GroupBy(a => a.HerdName); foreach (var herd in breeds) { if (!herd.FirstOrDefault().BreedParams.PricingAvailable()) { string warn = $"No pricing schedule has been provided for herd [r={herd.Key}]. No financial transactions will be recorded for activity [a={this.Name}]"; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } } } }
private void UpdatePricingToDate(int year, int month) { DateTime checkDate = new DateTime(year, month, DateTime.DaysInMonth(year, month)); // work through to start date while (priceFileAsRows.Count > 0 && (DateTime.Parse(priceFileAsRows[0][DateColumnName].ToString()) <= checkDate)) { int cnt = 0; foreach (var column in priceFileAsRows[0].Table.Columns) { if (!column.ToString().Equals(DateColumnName, StringComparison.OrdinalIgnoreCase) && double.TryParse(priceFileAsRows[0][cnt].ToString(), out double res)) { // update var components = pricingComonentsFound.Where(a => (a as IModel).Name == column.ToString()); if (components.Count() > 1) { string warn = $"Multiple resource [r=PricingComponents] named [{column}] were found when applying pricing by [a={this.Name}]. \r\n Ensure input price applies to all these components or provide unique component names"; Warnings.CheckAndWrite(warn, Summary, this); } foreach (IResourcePricing resourcePricing in components) { resourcePricing.SetPrice(res, this); } } cnt++; } // remove row priceFileAsRows.RemoveAt(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}]"; string warnfull = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]\r\nValues in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\r\nExpecting values between 1 and 100"; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning, warnfull); } 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}]"; string warnfull = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]\r\nValues in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\r\nExpecting values between 0 and 10"; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning, warnfull); } 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}]"; string warnfull = $"Suspicious values for [{category}] found in pasture database [x={this.Name}]\r\nValues in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\r\nExpecting values between 1 and 11"; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning, warnfull); } 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}]"; string warnfull = $"Unable to find a [{category}] value greater than the specified [{value:0.##}] in pasture database [x={this.Name}]\r\nKnown values in database: [{string.Join("],[", valuesToUse.Select(a => a.ToString()).ToArray())}]\r\nUsed: [{valuesToUse.Last()}]\r\nFix: Ensure the pasture database includes a [{category}] greater than values produced in this simulation for optimal results."; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Error, warnfull); index = valuesToUse.Count() - 1; } return((index < 0) ? valuesToUse[~index] : valuesToUse[index]); }
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."; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } } // get list of distinct stocking rates available in database // database has already been opened and checked in Validate() this.distinctStkRates = GetCategories(StkRateColumnName); this.distinctGBAs = GetCategories(GrassBAColumnName); this.distinctLandConditions = GetCategories(LandConColumnName); }
/// <summary> /// 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.Attributes.Exists(attribute)) { string warningString = $"No mandatory attribute [{attribute.ToUpper()}] present for individual added by [a={model.Name}]"; Warnings.CheckAndWrite(warningString, Summary, this, MessageType.Error); } } }
private double HandleRestocking(double animalEquivalentsToBuy, string paddockName, Ruminant exampleRuminant) { if (animalEquivalentsToBuy <= 0) { return(0); } GrazeFoodStoreType foodStore = Resources.FindResourceType <GrazeFoodStore, GrazeFoodStoreType>(this, paddockName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.ReportErrorAndStop); // 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; Warnings.CheckAndWrite(warn, Summary, this); } // 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}]"); } HerdResource.PurchaseIndividuals.Add(newIndividual); double indAE = newIndividual.AdultEquivalent; animalEquivalentsToBuy -= indAE; sumAE += indAE; } } return(Math.Max(0, animalEquivalentsToBuy)); } return(animalEquivalentsToBuy); }
/// <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. HerdResource.PurchaseIndividuals.RemoveAll(a => a.Location == paddockName); var destockGroups = FindAllChildren <RuminantGroup>().Where(a => a.Reason == RuminantStockGroupStyle.Destock); if (!destockGroups.Any()) { 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; Warnings.CheckAndWrite(warn, Summary, this); } foreach (var item in destockGroups) { // works with current filtered herd to obey filtering. var herd = item.Filter(CurrentHerd(false)) .Where(a => a.Location == paddockName && !a.ReadyForSale); foreach (Ruminant ruminant in herd) { if (ruminant.SaleFlag != HerdChangeReason.DestockSale) { animalEquivalentsForSale -= ruminant.AdultEquivalent; ruminant.SaleFlag = HerdChangeReason.DestockSale; } if (animalEquivalentsForSale <= 0) { this.Status = ActivityStatus.Success; return(0); } } } return(animalEquivalentsForSale); // handling of sucklings with sold female is in RuminantActivityBuySell // buy or sell is handled by the buy sell activity }
/// <inheritdoc/> public override void DoActivity() { Status = ActivityStatus.NotNeeded; double labourlimit = this.LabourLimitProportion; double units = 0; if (labourlimit == 1 || this.OnPartialResourcesAvailableAction == OnPartialResourcesAvailableActionTypes.UseResourcesAvailable) { units = unitsAvailableForSale * labourlimit; if (price.UseWholePackets) { units = Math.Truncate(units); } } if (units > 0) { // remove resource ResourceRequest purchaseRequest = new ResourceRequest { ActivityModel = this, Required = units * price.PacketSize, AllowTransmutation = true, Category = TransactionCategory, RelatesToResource = (resourceToSell as CLEMModel).NameWithParent }; resourceToSell.Remove(purchaseRequest); // transfer money earned if (bankAccount != null) { if (price.PricePerPacket == 0) { string warn = $"No price set [0] for [r={resourceToSell.Name}] at time of transaction for [a={this.Name}]{Environment.NewLine}No financial transactions will occur.{Environment.NewLine}Ensure price is set or resource pricing file contains entries before this transaction or start of simulation."; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } bankAccount.Add(units * price.PricePerPacket, this, (resourceToSell as CLEMModel).NameWithParent, TransactionCategory); if (bankAccount.EquivalentMarketStore != null) { purchaseRequest.Required = units * price.PricePerPacket; purchaseRequest.Category = TransactionCategory; purchaseRequest.RelatesToResource = (resourceToSell as CLEMModel).NameWithParent; (bankAccount.EquivalentMarketStore as FinanceType).Remove(purchaseRequest); } } SetStatusSuccess(); } }
/// <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; if (!(FindResource(parent.GetType()) is ResourceBaseWithTransactions resourceGroupInMarket)) { // 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().Name}]\r\nOnly resources groups provided in the [r=ResourceHolder] in the simulation tree will be traded."; Warnings.CheckAndWrite(warn, Summary, this); 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 = resourceGroupInMarket.FindChild <IResourceWithTransactionType>(resourceType.Name); // 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 if (resType is null) { // add warning the market does not have the resource string warn = $"The resource [r={resourceType.Parent.Name}.{resourceType.Name}] does not exist in [m={this.Parent.Name}].\r\nAdd resource and associated components to the market to permit trading."; Warnings.CheckAndWrite(warn, Summary, this); return(null); } // TODO: create a clone of the resource and put it in the market return(resType as IResourceWithTransactionType); }
/// <inheritdoc/> public override List <ResourceRequest> GetResourcesNeededForActivity() { if (people is null | food is null) { return(null); } List <LabourType> peopleList = people.Items.Where(a => IncludeHiredLabour || a.Hired == false).ToList(); peopleList.Select(a => { a.FeedToTargetIntake = 0; return(a); }).ToList(); // determine AEs to be fed double aE = peopleList.Sum(a => a.TotalAdultEquivalents); if (aE <= 0) { return(null); } int daysInMonth = DateTime.DaysInMonth(clock.Today.Year, clock.Today.Month); // determine feed limits (max kg per AE per day * AEs * days) double intakeLimit = DailyIntakeLimit * aE * daysInMonth; // remove previous consumption double otherIntake = this.DailyIntakeOtherSources * aE * daysInMonth; otherIntake += peopleList.Sum(a => a.GetAmountConsumed()); List <LabourActivityFeedTarget> labourActivityFeedTargets = this.FindAllChildren <LabourActivityFeedTarget>().ToList(); // determine targets foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { // calculate target target.Target = target.TargetValue * aE * daysInMonth; // calculate target maximum target.TargetMaximum = target.TargetMaximumValue * aE * daysInMonth; // set initial level based on off store inputs target.CurrentAchieved = target.OtherSourcesValue * aE * daysInMonth; // calculate current level from previous intake this month (LabourActivityFeed) target.CurrentAchieved += people.GetDietaryValue(target.Metric, IncludeHiredLabour, false); // * aE; // * daysInMonth; // add sources outside of this activity to peoples' diets if (target.OtherSourcesValue > 0) { foreach (var person in peopleList) { LabourDietComponent outsideEat = new LabourDietComponent(); // TODO: might need to add consumed here outsideEat.AmountConsumed = this.DailyIntakeOtherSources * person.TotalAdultEquivalents * daysInMonth; outsideEat.AddOtherSource(target.Metric, target.OtherSourcesValue * person.TotalAdultEquivalents * daysInMonth); // track this consumption by people here. person.AddIntake(outsideEat); person.FeedToTargetIntake += outsideEat.AmountConsumed; } } } // get max months before spoiling of all food stored (will be zero for non perishable food) int maxFoodAge = food.FindAllChildren <HumanFoodStoreType>().Max(a => a.Pools.Select(b => a.UseByAge - b.Age).DefaultIfEmpty(0).Max()); // create list of all food parcels List <HumanFoodParcel> foodParcels = new List <HumanFoodParcel>(); foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>().ToList()) { foreach (HumanFoodStorePool pool in foodStore.Pools.Where(a => a.Amount > 0)) { foodParcels.Add(new HumanFoodParcel() { FoodStore = foodStore, Pool = pool, Expires = ((foodStore.UseByAge == 0) ? maxFoodAge + 1: foodStore.UseByAge - pool.Age) }); } } foodParcels = foodParcels.OrderBy(a => a.Expires).ToList(); // if a market exists add the available market produce to the list below that ordered above. // order market food by price ascending // this will include market available food in the decisions. // will need to purchase this food before taking it if cost associated. // We can check if the parent of the human food store used is a market and charge accordingly. // for each market List <HumanFoodParcel> marketFoodParcels = new List <HumanFoodParcel>(); ResourcesHolder resources = Market?.Resources; if (resources != null) { HumanFoodStore food = resources.FindResourceGroup <HumanFoodStore>(); if (food != null) { foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>()) { foreach (HumanFoodStorePool pool in foodStore.Pools.Where(a => a.Amount > 0)) { marketFoodParcels.Add(new HumanFoodParcel() { FoodStore = foodStore, Pool = pool, Expires = ((foodStore.UseByAge == 0) ? maxFoodAge + 1 : foodStore.UseByAge - pool.Age) }); } } } } foodParcels.AddRange(marketFoodParcels.OrderBy(a => a.FoodStore.Price(PurchaseOrSalePricingStyleType.Purchase).PricePerPacket)); double fundsAvailable = double.PositiveInfinity; if (bankAccount != null) { fundsAvailable = bankAccount.FundsAvailable; } int parcelIndex = 0; double metricneeded = 0; double intake = otherIntake; // start eating food from list from that about to expire first // food from household can be eaten up to target maximum // food from market can only be eaten up to target while (parcelIndex < foodParcels.Count) { foodParcels[parcelIndex].Proportion = 0; var isHousehold = foodParcels[parcelIndex].FoodStore.CLEMParentName == this.CLEMParentName; if (intake < intakeLimit & (labourActivityFeedTargets.Where(a => ((isHousehold)? !a.TargetMaximumAchieved: !a.TargetAchieved)).Count() > 0 | foodParcels[parcelIndex].Expires == 0)) { // still able to eat and target not met or food about to expire this timestep // reduce by amout that can be eaten double propCanBeEaten = Math.Min(1, (intakeLimit - intake) / (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount)); // reduce to target limits double propToTarget = 1; if (foodParcels[parcelIndex].Expires != 0) { // if the food is not going to spoil // then adjust what can be eaten up to target otherwise allow over target consumption to avoid waste LabourActivityFeedTarget targetUnfilled = labourActivityFeedTargets.Where(a => ((isHousehold) ? !a.TargetMaximumAchieved : !a.TargetAchieved)).FirstOrDefault(); if (targetUnfilled != null) { // calculate reduction to metric target metricneeded = Math.Max(0, (isHousehold ? targetUnfilled.TargetMaximum : targetUnfilled.Target) - targetUnfilled.CurrentAchieved); double amountneeded = metricneeded / foodParcels[parcelIndex].FoodStore.ConversionFactor(targetUnfilled.Metric); propToTarget = Math.Min(1, amountneeded / (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount)); } } foodParcels[parcelIndex].Proportion = Math.Min(propCanBeEaten, propToTarget); // work out if there will be a cost limitation, only if a price structure exists for the resource // no charge for household consumption double propToPrice = 1; if (!isHousehold && foodParcels[parcelIndex].FoodStore.PricingExists(PurchaseOrSalePricingStyleType.Purchase)) { ResourcePricing price = foodParcels[parcelIndex].FoodStore.Price(PurchaseOrSalePricingStyleType.Purchase); double cost = (foodParcels[parcelIndex].Pool.Amount * foodParcels[parcelIndex].Proportion) / price.PacketSize * price.PricePerPacket; // TODO: sell cattle based on selling groups till run out of cattle or meet shortfall // adjust fundsAvailable with new money // if cost > 0 and cost > funds available if (cost > 0) { propToPrice = Math.Min(1, fundsAvailable / cost); // remove cost from running check tally fundsAvailable = Math.Max(0, fundsAvailable - (cost * propToPrice)); // real finance transactions will happen in the do activity as stuff is allocated // there should not be shortfall as all the checks and reductions have happened here } } foodParcels[parcelIndex].Proportion *= propToPrice; // update intake double newIntake = (foodParcels[parcelIndex].FoodStore.EdibleProportion * foodParcels[parcelIndex].Pool.Amount * foodParcels[parcelIndex].Proportion); intake += newIntake; // update metrics foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { target.CurrentAchieved += newIntake * foodParcels[parcelIndex].FoodStore.ConversionFactor(target.Metric); } } else if (intake >= intakeLimit && labourActivityFeedTargets.Where(a => ((isHousehold) ? !a.TargetMaximumAchieved : !a.TargetAchieved)).Count() > 1) { // full but could still reach target with some substitution // but can substitute to remove a previous target // does the current parcel have better target values than any previous non age 0 pool of a different food type } else { break; } parcelIndex++; } // fill resource requests List <ResourceRequest> requests = new List <ResourceRequest>(); foreach (var item in foodParcels.GroupBy(a => a.FoodStore)) { double amount = item.Sum(a => a.Pool.Amount * a.Proportion); if (amount > 0) { double financeLimit = 1; // if obtained from the market make financial transaction before taking ResourcePricing price = item.Key.Price(PurchaseOrSalePricingStyleType.Sale); bool marketIsSource = item.Key.Parent.Parent.Parent == Market; if (bankAccount != null && marketIsSource && price.PricePerPacket > 0) { // finance transaction to buy food from market ResourceRequest marketRequest = new ResourceRequest { ActivityModel = this, Required = amount / price.PacketSize * price.PricePerPacket, AllowTransmutation = false, Category = $"{TransactionCategory}.PurchaseFood", MarketTransactionMultiplier = 1, RelatesToResource = item.Key.NameWithParent }; bankAccount.Remove(marketRequest); } // is this a market requests.Add(new ResourceRequest() { Resource = item.Key, ResourceType = typeof(HumanFoodStore), AllowTransmutation = false, Required = amount * financeLimit, ResourceTypeName = item.Key.NameWithParent, ActivityModel = this, Category = $"{TransactionCategory}{(marketIsSource?".FromMarket":".FromHousehold")}" }); } } // if still hungry and funds available, try buy food in excess of what stores (private or market) offered using transmutation if present. // This will force the market or private sources to purchase more food to meet demand if transmutation available. // if no market is present it will look to transmutating from its own stores if possible. // this means that other than a purchase from market (above) this activity doesn't need to worry about financial tranactions. int testType = 0; // test is limited to 1 for now so only to metric target NOT intake limit as we use maximum and target values now while (testType < 1 && intake < intakeLimit && (labourActivityFeedTargets.Where(a => !a.TargetAchieved).Any()) && fundsAvailable > 0) { // don't worry about money anymore. The over request will be handled by the transmutation. // move through specified purchase list // 1. to assign based on energy // 2. if still need food assign based on intake still needed metricneeded = 0; LabourActivityFeedTarget targetUnfilled = labourActivityFeedTargets.Where(a => !a.TargetAchieved).FirstOrDefault(); if (targetUnfilled != null) { metricneeded = Math.Max(0, (targetUnfilled.Target - targetUnfilled.CurrentAchieved)); double amountToFull = intakeLimit - intake; foreach (LabourActivityFeedTargetPurchase purchase in this.FindAllChildren <LabourActivityFeedTargetPurchase>()) { HumanFoodStoreType foodtype = purchase.FoodStore; if (purchase.ProportionToPurchase > 0 && foodtype != null && (foodtype.TransmutationDefined & intake < intakeLimit)) { double amountEaten = 0; if (testType == 0) { // metric target based on purchase proportion amountEaten = metricneeded / foodtype.ConversionFactor(targetUnfilled.Metric) * purchase.ProportionToPurchase; } else { // amount to satisfy limited by proportion of purchases amountEaten = amountToFull * purchase.ProportionToPurchase; } if (intake + amountEaten > intakeLimit) { amountEaten = intakeLimit - intake; } if (amountEaten > 0) { targetUnfilled.CurrentAchieved += amountEaten * foodtype.ConversionFactor(targetUnfilled.Metric); double amountPurchased = amountEaten / foodtype.EdibleProportion; // update intake.. needed is the amount edible, not the amount purchased. intake += amountEaten; // add financial transactions to purchase from market // if obtained from the market make financial transaction before taking ResourcePricing price = foodtype.Price(PurchaseOrSalePricingStyleType.Sale); if (bankAccount != null) { if (price.PricePerPacket > 0) { ResourceRequest marketRequest = new ResourceRequest { ActivityModel = this, Required = amountPurchased / price.PacketSize * price.PricePerPacket, AllowTransmutation = false, Category = "Import", MarketTransactionMultiplier = 1, RelatesToResource = foodtype.NameWithParent }; bankAccount.Remove(marketRequest); } else { string warn = $"No price set [{price.PricePerPacket}] for [r={foodtype.Name}] at time of transaction for [a={this.Name}]{Environment.NewLine}No financial transactions will occur.{Environment.NewLine}Ensure price is set or resource pricing file contains entries before this transaction or start of simulation."; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } } // find in requests or create a new one ResourceRequest foodRequestFound = requests.Find(a => a.Resource == foodtype); if (foodRequestFound is null) { requests.Add(new ResourceRequest() { Resource = foodtype, ResourceType = typeof(HumanFoodStore), AllowTransmutation = true, Required = amountPurchased, ResourceTypeName = purchase.FoodStoreName, ActivityModel = this, Category = $"{TransactionCategory}.FromImports" }); } else { foodRequestFound.Required += amountPurchased; foodRequestFound.AllowTransmutation = true; } } } } testType++; } } return(requests); }
private void OnCLEMInitialiseActivity(object sender, EventArgs e) { people = Resources.FindResourceGroup <Labour>(); food = Resources.FindResourceGroup <HumanFoodStore>(); bankAccount = Resources.FindResourceType <Finance, FinanceType>(this, AccountName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore); Market = food.FindAncestor <ResourcesHolder>().FoundMarket; resourcesHolder = base.Resources; // if market is present point to market to find the resource if (Market != null) { resourcesHolder = Market.FindChild <ResourcesHolder>(); } // set the food store linked in any TargetPurchase if target proportion set > 0 // check that all purchase resources have transmutation or recalulate the proportion var targetPurchases = this.FindAllChildren <LabourActivityFeedTargetPurchase>().Where(a => a.TargetProportion > 0).ToList(); if (targetPurchases.Any()) { double checkPropAvailable = 0; double totPropAvailable = 0; bool adjusted = false; foreach (var item in targetPurchases) { checkPropAvailable += item.TargetProportion; item.FoodStore = resourcesHolder.FindResourceType <HumanFoodStore, HumanFoodStoreType>(this, item.FoodStoreName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore); if (item.FoodStore.TransmutationDefined) { totPropAvailable += item.TargetProportion; item.ProportionToPurchase = item.TargetProportion; } else { string warn = $"The HumanFoodStoreType [r={item.FoodStore.FullPath}] does not have a Transmutation required to be a LabourActivityFeedTargetPurchase [a={item.FullPath}] of [a={this.FullPath}]{Environment.NewLine}This HumanFoodStore will not be allocated and the remaining purchase proportions have been adjusted"; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); adjusted = true; item.ProportionToPurchase = 0; } } if (Math.Abs(1 - checkPropAvailable) < 0.0001) { if (!adjusted) { string warn = $"The TargetProportions provided for [a=LabourActivityFeedTargetPurchase] provided for [a={this.FullPath}] do not sum to 1.{Environment.NewLine}These purchase proportions have been adjusted"; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } adjusted = true; } // recalculate proportions to buy based on transmuation of resource allowed if (adjusted) { foreach (var item in targetPurchases.Where(a => a.FoodStore.TransmutationDefined)) { item.ProportionToPurchase = item.TargetProportion / totPropAvailable; } } } }
/// <inheritdoc/> 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.FindResourceType <ResourceBaseWithTransactions, IResourceType>(this, resName, OnMissingResourceActionTypes.ReportErrorAndStop, OnMissingResourceActionTypes.ReportErrorAndStop); } 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"; Warnings.CheckAndWrite(warn, Summary, this); 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"; } Warnings.CheckAndWrite(warn, Summary, this); } } if (resource != null) { resourceList.Add(resource); } } } return(requests); }
/// <summary> /// Create the individual ruminant animals using the Cohort parameterisations. /// </summary> /// <param name="number">The number of individuals to create</param> /// <param name="initialAttributes">The initial attributes found from parent and this cohort</param> /// <param name="ruminantType">The breed parameters if overwritten</param> /// <returns>List of ruminants</returns> public List <Ruminant> CreateIndividuals(int number, List <ISetAttribute> initialAttributes, RuminantType ruminantType = null) { List <Ruminant> individuals = new List <Ruminant>(); if (number > 0) { RuminantType parent = ruminantType; if (parent is null) { parent = FindAncestor <RuminantType>(); } // get Ruminant Herd resource for unique ids RuminantHerd ruminantHerd = parent.Parent as RuminantHerd; // Resources.FindResourceGroup<RuminantHerd>(); for (int i = 1; i <= number; i++) { double weight = 0; if (Weight > 0) { // avoid accidental small weight if SD provided but weight is 0 // if weight is 0 then the normalised weight will be applied in Ruminant constructor. double u1 = RandomNumberGenerator.Generator.NextDouble(); double u2 = RandomNumberGenerator.Generator.NextDouble(); double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); weight = Weight + WeightSD * randStdNormal; } Ruminant ruminant = Ruminant.Create(Sex, parent, Age, weight); ruminant.ID = ruminantHerd.NextUniqueID; ruminant.Breed = parent.Breed; ruminant.HerdName = parent.Name; ruminant.SaleFlag = HerdChangeReason.None; if (Suckling) { if (Age >= ((parent.NaturalWeaningAge == 0)?parent.GestationLength: parent.NaturalWeaningAge)) { string limitstring = (parent.NaturalWeaningAge == 0) ? $"gestation length [{parent.GestationLength}]" : $"natural weaning age [{parent.NaturalWeaningAge}]"; string warn = $"Individuals older than {limitstring} cannot be assigned as suckling [r={parent.Name}][r={this.Parent.Name}][r={this.Name}]{Environment.NewLine}These individuals have not been assigned suckling."; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } else { ruminant.SetUnweaned(); } } if (Sire) { if (this.Sex == Sex.Male) { RuminantMale ruminantMale = ruminant as RuminantMale; ruminantMale.Attributes.Add("Sire"); } else { string warn = $"Breeding sire switch is not valid for individual females [r={parent.Name}][r={this.Parent.Name}][r={this.Name}]{Environment.NewLine}These individuals have not been assigned sires. Change Sex to Male to create sires in initial herd."; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); } } // if weight not provided use normalised weight ruminant.PreviousWeight = ruminant.Weight; if (this.Sex == Sex.Female) { RuminantFemale ruminantFemale = ruminant as RuminantFemale; ruminantFemale.WeightAtConception = ruminant.Weight; ruminantFemale.NumberOfBirths = 0; if (setPreviousConception != null) { setPreviousConception.SetConceptionDetails(ruminantFemale); } } // initialise attributes foreach (ISetAttribute item in initialAttributes) { ruminant.Attributes.Add(item.AttributeName, item.GetAttribute(true)); } individuals.Add(ruminant); } // add any mandatory attributes to the list on the ruminant type foreach (var mattrib in initialAttributes.Where(a => a.Mandatory)) { parent.AddMandatoryAttribute(mattrib.AttributeName); } } return(individuals); }
/// <summary> /// Find a resource type from type of resources and name of resource type component /// </summary> /// <typeparam name="T">Type of ResourceType to return</typeparam> /// <typeparam name="R">Type of Resource group containing resource type</typeparam> /// <param name="requestingModel">The model requesting this resource</param> /// <param name="resourceName">The name identifier NameOfResource.NameOfResourceType or simply NameOfResourceType</param> /// <param name="missingResourceAction">Action if resource group missing</param> /// <param name="missingResourceTypeAction">Action if resource type is missing</param> /// <returns>A resource type component</returns> public T FindResourceType <R, T>(IModel requestingModel, string resourceName, OnMissingResourceActionTypes missingResourceAction = OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes missingResourceTypeAction = OnMissingResourceActionTypes.Ignore) where T : IResourceType where R : ResourceBaseWithTransactions { string[] nameParts = new string[] { "", resourceName }; if (nameParts.Last().Contains('.')) { nameParts = nameParts.Last().Split('.'); if (nameParts.Length > 2) { throw new ApsimXException(requestingModel, $"Invalid resource name identifier for [{requestingModel.Name}], expecting 'ResourceName.ResourceTypeName' or 'ResourceTypeName'. Value provided [{resourceName}]"); } } // not sure it's quickets to find the resource then look at it's children // or look through all descendents for the type and name // if we find children then we use R as a double check bool searchForAllIresourceType = false; bool resGroupNameMatch = true; T resType = default(T); ResourceBaseWithTransactions resGroup = null; if (!typeof(R).IsSubclassOf(typeof(ResourceBaseWithTransactions))) { if (nameParts.First() == "") { searchForAllIresourceType = true; } else { // find resource by name resGroup = FindChild <R>(nameParts.First()); } } else { resGroup = (nameParts.First() != "") ? FindResource <R>(nameParts.First()) : FindResource <R>(); if (resGroup == null && nameParts.First() != "") { // no resource name match so try with just the type resGroupNameMatch = false; resGroup = FindResource <R>(); } } if (searchForAllIresourceType) { resType = FindAllDescendants <T>(nameParts.Last()).FirstOrDefault(); } else { if (resGroup != null) { resType = (resGroup as IModel).FindChild <T>(nameParts.Last()); } } string errorMsg; if (resGroup == null) { errorMsg = $"Unable to locate resource group [r={typeof(R).Name}] for [a={requestingModel.Name}]"; switch (missingResourceAction) { case OnMissingResourceActionTypes.ReportErrorAndStop: throw new ApsimXException(this, errorMsg); case OnMissingResourceActionTypes.ReportWarning: Warnings.CheckAndWrite(errorMsg, Summary, this); break; default: break; } return(default(T)); } else { if (!resGroupNameMatch) { errorMsg = $"Unable to locate resource named [r={nameParts.First()}] for [a={requestingModel.Name}] but a [{typeof(R).Name}] resource was found and will be used."; Warnings.CheckAndWrite(errorMsg, Summary, this); } } if (resType as IModel is null) { errorMsg = $"Unable to locate resource type [r={nameParts.Last()}] in [r={resGroup.Name}] for [a={requestingModel.Name}]"; switch (missingResourceTypeAction) { case OnMissingResourceActionTypes.ReportErrorAndStop: throw new ApsimXException(this, errorMsg); case OnMissingResourceActionTypes.ReportWarning: Warnings.CheckAndWrite(errorMsg, Summary, this); break; default: break; } } return(resType); }
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 foreach (var newgroup in HerdResource.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 = HerdResource.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.FindResourceType <GrazeFoodStore, GrazeFoodStoreType>(this, newgroup.Key, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore); double kgha = pasture.TonnesPerHectare * 1000; herdChange = pastureToStockingChangeElNino.SolveY(kgha); relationshipFound = true; } break; case ENSOState.LaNina: if (!(pastureToStockingChangeLaNina is null)) { GrazeFoodStoreType pasture = Resources.FindResourceType <GrazeFoodStore, GrazeFoodStoreType>(this, newgroup.Key, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore); 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; Warnings.CheckAndWrite(warn, Summary, this); } 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> /// 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}]"; Warnings.CheckAndWrite(warn, Summary, this, MessageType.Warning); 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"], CultureInfo.InvariantCulture)).FirstOrDefault().Year; } } results.DefaultView.Sort = YearColumnName + ", " + MonthColumnName; foreach (DataRowView row in results.DefaultView) { pastureDetails.Add(DataRow2PastureDataType(row)); } CheckAllMonthsWereRetrieved(pastureDetails, ecolCalculationDate, endDate, region, soil, grassBasalAreaCategory, landConditionCategory, stkRateCategory); return(pastureDetails); }
private void OnCLEMAnimalBreeding(object sender, EventArgs e) { this.Status = ActivityStatus.NotNeeded; NumberConceived = 0; // get list of all pregnant females List <RuminantFemale> pregnantherd = CurrentHerd(true).OfType <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++) { var rnd = RandomNumberGenerator.Generator.NextDouble(); if (rnd < (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++) { bool isMale = RandomNumberGenerator.Generator.NextDouble() <= female.BreedParams.ProportionOffspringMale; Sex sex = isMale ? Sex.Male : Sex.Female; double weight = female.BreedParams.SRWBirth * female.StandardReferenceWeight * (1 - 0.33 * (1 - female.Weight / female.StandardReferenceWeight)); Ruminant newSucklingRuminant = Ruminant.Create(sex, female.BreedParams, 0, weight); newSucklingRuminant.HerdName = female.HerdName; newSucklingRuminant.Breed = female.BreedParams.Breed; newSucklingRuminant.ID = HerdResource.NextUniqueID; newSucklingRuminant.Location = female.Location; newSucklingRuminant.Mother = female; newSucklingRuminant.Number = 1; newSucklingRuminant.SetUnweaned(); // suckling/calf weight from Freer newSucklingRuminant.PreviousWeight = newSucklingRuminant.Weight; newSucklingRuminant.SaleFlag = HerdChangeReason.Born; // add attributes inherited from mother foreach (var attribute in female.Attributes.Items) { if (attribute.Value != null) { newSucklingRuminant.Attributes.Add(attribute.Key, attribute.Value.GetInheritedAttribute() as IIndividualAttribute); } } HerdResource.AddRuminant(newSucklingRuminant, this); // add to sucklings female.SucklingOffspringList.Add(newSucklingRuminant); // 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(); } else if (!useControlledMating && TimingOK) { // whole herd for activity including males herd = CurrentHerd(true); } if (herd != null && herd.Any()) { // group by location var breeders = from ind in herd where ind.IsAbleToBreed group ind by ind.Location into grp select grp; // identify not ready for reporting and tracking var notReadyBreeders = herd.Where(a => a.Sex == Sex.Female).Cast <RuminantFemale>().Where(a => a.IsBreeder && !a.IsAbleToBreed && !a.IsPregnant); foreach (RuminantFemale female in notReadyBreeders) { female.BreedParams.OnConceptionStatusChanged(new Reporting.ConceptionStatusChangedEventArgs(Reporting.ConceptionStatus.NotReady, female, clock.Today)); } int numberPossible = breeders.Sum(a => a.Count()); 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.OfType <RuminantFemale>().Count(), CultureInfo.InvariantCulture); } else { numberPossible = 0; // uncontrolled conception if (location.GroupBy(a => a.Sex).Count() == 2) { int maleCount = location.OfType <RuminantMale>().Count(); // get a list of males to provide attributes when incontrolled mating. if (maleCount > 0 && location.FirstOrDefault().BreedParams.IncludedAttributeInheritanceWhenMating) { maleBreeders = location.Where(a => a.Sex == Sex.Male).ToList(); } int femaleCount = location.Where(a => a.Sex == Sex.Female).Count(); numberPossible = Convert.ToInt32(Math.Ceiling(maleCount * location.FirstOrDefault().BreedParams.MaximumMaleMatingsPerDay * 30), CultureInfo.InvariantCulture); } } numberServiced = 0; lastJoinIndex = -1; int cnt = 0; // shuffle the not pregnant females when obtained to avoid any inherant order by creation of individuals affecting which individuals are available first var notPregnantFemales = location.OfType <RuminantFemale>().Where(a => !a.IsPregnant).OrderBy(a => RandomNumberGenerator.Generator.Next()).ToList(); int totalToBreed = notPregnantFemales.Count; while (cnt < totalToBreed) { RuminantFemale female = notPregnantFemales.ElementAt(cnt); Reporting.ConceptionStatus status = Reporting.ConceptionStatus.NotMated; if (numberServiced < numberPossible) { // calculate conception double conceptionRate = ConceptionRate(female, out status); // if mandatory attributes are present in the herd, save male value with female details. // update male for both successful and failed matings (next if statement if (female.BreedParams.IncludedAttributeInheritanceWhenMating) { object male = null; if (useControlledMating) { bool newJoining = needsNewJoiningMale(controlledMating.JoiningsPerMale, numberServiced); // save all male attributes AddMalesAttributeDetails(female, controlledMating.SireAttributes, newJoining); } else { male = maleBreeders[RandomNumberGenerator.Generator.Next(0, maleBreeders.Count() - 1)]; female.LastMatingStyle = ((male as RuminantMale).IsWildBreeder ? MatingStyle.WildBreeder : MatingStyle.Natural); // randomly select male AddMalesAttributeDetails(female, male as Ruminant); } } if (conceptionRate > 0) { if (RandomNumberGenerator.Generator.NextDouble() <= conceptionRate) { female.UpdateConceptionDetails(female.CalulateNumberOfOffspringThisPregnancy(), conceptionRate, 0); if (useControlledMating) { female.LastMatingStyle = MatingStyle.Controlled; } status = Reporting.ConceptionStatus.Conceived; NumberConceived++; } else { status = Reporting.ConceptionStatus.Unsuccessful; } } 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)); } cnt++; } // report a natural mating locations for transparency via a message if (numberServiced > 0 & !useControlledMating) { string warning = $"Natural (uncontrolled) mating ocurred in [r={(location.Key ?? "Not specified - general yards")}]"; Warnings.CheckAndWrite(warning, Summary, this, MessageType.Information); } } } // report that this activity was performed as it does not use base GetResourcesRequired this.TriggerOnActivityPerformed(); }
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."; Warnings.CheckAndWrite(warn, Summary, this); } 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); } }