/// <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); }
/// <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() { 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); // determine AEs to be fed double aE = peopleList.Sum(a => a.AdultEquivalent); 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>().Cast <LabourActivityFeedTarget>().ToList(); // determine targets foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { // calculate target target.Target = target.TargetValue * 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, true) * aE * daysInMonth; // add sources outside of this activity to peoples' diets if (target.OtherSourcesValue > 0) { foreach (var person in peopleList) { LabourDietComponent outsideEat = new LabourDietComponent(); outsideEat.AddOtherSource(target.Metric, target.OtherSourcesValue * person.AdultEquivalent * daysInMonth); person.AddIntake(outsideEat); } } } // get max months before spoiling of all food stored (will be zero for non perishable food) int maxFoodAge = food.FindAllChildren <HumanFoodStoreType>().Cast <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>().Cast <HumanFoodStoreType>().ToList()) { foreach (HumanFoodStorePool pool in foodStore.Pools) { 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 = Resources.FoundMarket.Resources; if (resources != null) { HumanFoodStore food = resources.HumanFoodStore(); if (food != null) { foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>()) { foreach (HumanFoodStorePool pool in foodStore.Pools) { 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 intake = otherIntake; // start eating food from list from that about to expire first while (parcelIndex < foodParcels.Count) { foodParcels[parcelIndex].Proportion = 0; if (intake < intakeLimit & (labourActivityFeedTargets.Where(a => !a.TargetMet).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 => !a.TargetMet).FirstOrDefault(); if (targetUnfilled != null) { // calculate reduction to metric target double metricneeded = Math.Max(0, 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 double propToPrice = 1; if (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; 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 => !a.TargetMet).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); if (bankAccount != null && item.Key.Parent.Parent.Parent == Market && price.PricePerPacket > 0) { // if shortfall reduce purchase ResourceRequest marketRequest = new ResourceRequest { ActivityModel = this, Required = amount / price.PacketSize * price.PricePerPacket, AllowTransmutation = false, Category = "Food purchase", MarketTransactionMultiplier = 1 }; bankAccount.Remove(marketRequest); } requests.Add(new ResourceRequest() { Resource = item.Key, ResourceType = typeof(HumanFoodStore), AllowTransmutation = false, Required = amount * financeLimit, ResourceTypeName = item.Key.Name, ActivityModel = this, Category = "Consumption" }); } } // 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. if (intake < intakeLimit && (labourActivityFeedTargets.Where(a => !a.TargetMet).Count() > 0) && fundsAvailable > 0) { ResourcesHolder resourcesHolder = Resources; // if market is present point to market to find the resource if (Market != null) { resourcesHolder = Market.FindChild <ResourcesHolder>(); } // don't worry about money anymore. The over request will be handled by the transmutation. // move through specified purchase list foreach (LabourActivityFeedTargetPurchase purchase in this.FindAllChildren <LabourActivityFeedTargetPurchase>().Cast <LabourActivityFeedTargetPurchase>().ToList()) { HumanFoodStoreType foodtype = resourcesHolder.GetResourceItem(this, purchase.FoodStoreName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore) as HumanFoodStoreType; if (foodtype != null && (foodtype.TransmutationDefined & intake < intakeLimit)) { LabourActivityFeedTarget targetUnfilled = labourActivityFeedTargets.Where(a => !a.TargetMet).FirstOrDefault(); if (targetUnfilled != null) { // calculate reduction to metric target double metricneeded = Math.Max(0, (targetUnfilled.Target - targetUnfilled.CurrentAchieved)); double amountneeded = metricneeded / foodtype.ConversionFactor(targetUnfilled.Metric); if (intake + amountneeded > intakeLimit) { amountneeded = intakeLimit - intake; } double amountfood = amountneeded / foodtype.EdibleProportion; // update intake intake += amountfood; // find in requests or create a new one ResourceRequest foodRequestFound = requests.Find(a => a.Resource == foodtype) as ResourceRequest; if (foodRequestFound is null) { requests.Add(new ResourceRequest() { Resource = foodtype, ResourceType = typeof(HumanFoodStore), AllowTransmutation = true, Required = amountfood, ResourceTypeName = purchase.FoodStoreName.Split('.')[1], ActivityModel = this, Category = "Consumption" }); } else { foodRequestFound.Required += amountneeded; foodRequestFound.AllowTransmutation = true; } } } } // NOTE: proportions of purchased food are not modified if the sum does not add up to 1 or some of the food types are not available. } return(requests); }
/// <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() { if (people is null | food is null) { return(null); } List <LabourType> peopleList = people.Items.Where(a => IncludeHiredLabour || a.Hired == true).ToList(); peopleList.Select(a => a.FeedToTargetIntake == 0); List <ResourceRequest> requests = new List <ResourceRequest>(); // determine AEs to be fed double aE = peopleList.Sum(a => a.AdultEquivalent); // determine feed limits (max kg per AE per day * AEs * days) double intakeLimit = DailyIntakeLimit * aE * 30.4; // remove previous consumption intakeLimit -= this.DailyIntakeOtherSources * aE * 30.4; intakeLimit -= peopleList.Sum(a => a.GetAmountConsumed()); List <LabourActivityFeedTarget> labourActivityFeedTargets = this.FindAllChildren <LabourActivityFeedTarget>().Cast <LabourActivityFeedTarget>().ToList(); int feedTargetIndex = 0; // determine targets foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { // calculate target target.Target = target.TargetValue * aE * 30.4; // set initial level based on off store inputs target.CurrentAchieved = target.OtherSourcesValue * aE * 30.4; // calculate current level from previous intake this month (LabourActivityFeed) target.CurrentAchieved += people.GetDietaryValue(target.Metric, IncludeHiredLabour, true) * aE * 30.4; } // order food to achieve best returns for first criteria conversion factor decreasing List <HumanFoodStoreType> foodStoreTypes = food.FindAllChildren <HumanFoodStoreType>().Cast <HumanFoodStoreType>().OrderBy(a => a.ConversionFactor(labourActivityFeedTargets[feedTargetIndex].Metric)).ToList(); // check availability to take food based on order in simulation tree while (foodStoreTypes.Count() > 0 & intakeLimit > 0) { // get next food store type HumanFoodStoreType foodtype = foodStoreTypes[0]; // get amount people can still eat based on limits and previous consumption double amountNeededRaw = 0; foreach (LabourType labourType in peopleList) { double indLimit = (labourType.AdultEquivalent * DailyIntakeLimit * 30.4); double alreadyEatenThis = labourType.GetAmountConsumed(foodtype.Name); double alreadyEaten = labourType.GetAmountConsumed() + labourType.FeedToTargetIntake; double canStillEat = Math.Max(0, indLimit - alreadyEaten); double amountOfThisFood = canStillEat; amountNeededRaw += amountOfThisFood / foodtype.EdibleProportion; } // update targets based on amount available (will update excess if transmutated later) double amountNeededEdible = Math.Min(amountNeededRaw, foodtype.Amount) * foodtype.EdibleProportion; foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { target.CurrentAchieved += amountNeededEdible * foodtype.ConversionFactor(target.Metric); } if (amountNeededRaw > 0) { // create request requests.Add(new ResourceRequest() { AllowTransmutation = false, Required = amountNeededRaw, Available = foodtype.Amount, ResourceType = typeof(HumanFoodStore), ResourceTypeName = foodtype.Name, ActivityModel = this, Category = "Consumption" } ); } foodStoreTypes.RemoveAt(0); // check if target has been met (allows slight overrun) if (labourActivityFeedTargets[feedTargetIndex].CurrentAchieved >= labourActivityFeedTargets[feedTargetIndex].Target) { feedTargetIndex++; if (feedTargetIndex > labourActivityFeedTargets.Count()) { // all feed targets have been met. Preserve remaining food for next time. //TODO: eat food that will go off if not eaten and still below limits. break; } // reorder remaining food types to next feed target if available foodStoreTypes = foodStoreTypes.OrderBy(a => a.ConversionFactor(labourActivityFeedTargets[feedTargetIndex].Metric)).ToList(); } } // We have now been through all food types or all targets have been achieved. // Any unused food will not be consumed even if it is about to spoil. // The food requests ready to send contain excesses that may need to be purchased but haven't been accounted for towards targets yet // Next we go through and check all requests that exceed available to see if we can and there is need to buy resources. foreach (ResourceRequest request in ResourceRequestList.Where(a => a.Required > a.Available)) { // all targets have not been met if (feedTargetIndex <= labourActivityFeedTargets.Count()) { // allow if transmutation possible if ((request.Resource as HumanFoodStoreType).TransmutationDefined) { // allow if still below threshold if (labourActivityFeedTargets[feedTargetIndex].CurrentAchieved < labourActivityFeedTargets[feedTargetIndex].Target) { HumanFoodStoreType foodStore = request.Resource as HumanFoodStoreType; // if this food type provides towards the target if (foodStore.ConversionFactor(labourActivityFeedTargets[feedTargetIndex].Metric) > 0) { // work out what the extra is worth double excess = request.Required - request.Available; // get target needed double remainingToTarget = labourActivityFeedTargets[feedTargetIndex].Target - labourActivityFeedTargets[feedTargetIndex].CurrentAchieved; double excessConverted = excess * foodStore.EdibleProportion * foodStore.ConversionFactor(labourActivityFeedTargets[feedTargetIndex].Metric); // reduce if less than needed double prop = Math.Max(excessConverted / remainingToTarget, 1.0); double newExcess = excess * prop; request.Required = request.Available + newExcess; request.AllowTransmutation = true; // update targets based on new amount eaten foreach (LabourActivityFeedTarget target in labourActivityFeedTargets) { target.CurrentAchieved += newExcess * foodStore.EdibleProportion * foodStore.ConversionFactor(target.Metric); } // move to next target if achieved. if (labourActivityFeedTargets[feedTargetIndex].CurrentAchieved >= labourActivityFeedTargets[feedTargetIndex].Target) { feedTargetIndex++; } } } } } // transmutation not allowed so only get what was available. if (request.AllowTransmutation == false) { request.Required = request.Available; } } return(requests); }