private void OnStartOfSimulation(object sender, EventArgs e) { // determine resource type from name TransmuteResourceType = resources.FindResourceType <ResourceBaseWithTransactions, IResourceType>(this, TransmuteResourceTypeName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore); if (TransmuteResourceType != null) { ResourceGroup = (TransmuteResourceType as IModel).Parent as ResourceBaseWithTransactions; var shortfallResourceType = (TransmuteResourceType as IModel).FindAncestor <IResourceType>(); shortfallPacketSize = (Parent as Transmutation).TransmutationPacketSize; shortfallWholePackets = (Parent as Transmutation).UseWholePackets; // get pricing of shortfall resource if (shortfallResourceType != null && TransmuteStyle == TransmuteStyle.UsePricing) { shortfallPricing = shortfallResourceType.Price(PurchaseOrSalePricingStyleType.Purchase); shortfallPacketSize = shortfallPricing.PacketSize; shortfallWholePackets = shortfallPricing.UseWholePackets; // get pricing of transmute resource if (!(TransmuteResourceType is FinanceType)) { transmutePricing = TransmuteResourceType.Price(PurchaseOrSalePricingStyleType.Sale); if (FinanceTypeForTransactionsName != "No transactions") { // link to first bank account financeType = resources.FindResourceType <Finance, FinanceType>(this, FinanceTypeForTransactionsName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.ReportWarning); } } } } }
private void OnStartOfSimulation(object sender, EventArgs e) { ResourceType = typeof(Finance); // get pricing if available pricing = Apsim.Children(Apsim.Parent(this, typeof(IResourceType)), typeof(ResourcePricing)).FirstOrDefault() as ResourcePricing; }
private void OnCLEMInitialiseActivity(object sender, EventArgs e) { // get bank account object to use bankAccount = Resources.FindResourceType <Finance, FinanceType>(this, AccountName, OnMissingResourceActionTypes.ReportWarning, OnMissingResourceActionTypes.ReportErrorAndStop); // get resource type to sell resourceToSell = Resources.FindResourceType <ResourceBaseWithTransactions, IResourceType>(this, ResourceTypeName, OnMissingResourceActionTypes.ReportErrorAndStop, OnMissingResourceActionTypes.ReportErrorAndStop); // find market if present Market market = Resources.FoundMarket; // find a suitable store to place resource if (market != null) { resourceToPlace = market.Resources.LinkToMarketResourceType(resourceToSell as CLEMResourceTypeBase) as IResourceType; } if (resourceToPlace != null) { price = resourceToPlace.Price(PurchaseOrSalePricingStyleType.Purchase); } if (price is null && resourceToSell.Price(PurchaseOrSalePricingStyleType.Sale) != null) { price = resourceToSell.Price(PurchaseOrSalePricingStyleType.Sale); } }
/// <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() { // get pricing price = resourceToSell.Price; unitsAvailable = unitsAvailableForSale; return(null); }
/// <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() { // get pricing. price = resourceToBuy.Price; // calculate units units = Units; if (price.UseWholePackets) { units = Math.Truncate(Units); } if (units > 0) { return(new List <ResourceRequest>() { new ResourceRequest() { AllowTransmutation = false, Required = units * price.PricePerPacket, ResourceType = typeof(Finance), ResourceTypeName = bankAccount.Name, Reason = "Purchase " + (resourceToBuy as Model).Name, ActivityModel = this } }); } else { return(null); } }
private void OnCLEMInitialiseActivity(object sender, EventArgs e) { // get bank account object to use bankAccount = Resources.FindResourceType<Finance, FinanceType>(this, AccountName, OnMissingResourceActionTypes.ReportWarning, OnMissingResourceActionTypes.ReportErrorAndStop); // get resource type to buy resourceToBuy = Resources.FindResourceType<ResourceBaseWithTransactions, IResourceType>(this, ResourceTypeName, OnMissingResourceActionTypes.ReportErrorAndStop, OnMissingResourceActionTypes.ReportErrorAndStop); // get pricing if((resourceToBuy as CLEMResourceTypeBase).MarketStoreExists) if ((resourceToBuy as CLEMResourceTypeBase).EquivalentMarketStore.PricingExists(PurchaseOrSalePricingStyleType.Sale)) price = (resourceToBuy as CLEMResourceTypeBase).EquivalentMarketStore.Price(PurchaseOrSalePricingStyleType.Sale); // no market price found... look in local resources and allow 0 price if not found if(price is null) price = resourceToBuy.Price(PurchaseOrSalePricingStyleType.Purchase); }
/// <summary> /// Validate this object /// </summary> /// <param name="validationContext"></param> /// <returns></returns> public IEnumerable <ValidationResult> Validate(ValidationContext validationContext) { var results = new List <ValidationResult>(); // get pricing if available IResourceType parentResource = FindAncestor <CLEMResourceTypeBase>() as IResourceType; if (parentResource != null) { pricing = parentResource.Price(PurchaseOrSalePricingStyleType.Purchase); } if (pricing is null) { string[] memberNames = new string[] { "Resource pricing" }; results.Add(new ValidationResult($"No resource pricing was found for [r={(parentResource as IModel).Name}] required for a price based transmutation [{this.Name}]", memberNames)); } return(results); }
private void OnStartOfSimulation(object sender, EventArgs e) { // get herd for transmutating ResourceGroup = resources.FindResourceGroup <RuminantHerd>(); TransmuteResourceTypeName = ResourceGroup.Name; shortfallPacketSize = (Parent as Transmutation).TransmutationPacketSize; shortfallWholePackets = (Parent as Transmutation).UseWholePackets; groupings = ResourceGroup.FindAllChildren <RuminantGroup>(); var shortfallResourceType = this.FindAncestor <IResourceType>(); if (shortfallResourceType != null && TransmuteStyle == TransmuteStyle.UsePricing) { shortfallPricing = shortfallResourceType.Price(PurchaseOrSalePricingStyleType.Purchase); if (FinanceTypeForTransactionsName != "No transactions") { // link to first bank account financeType = resources.FindResourceType <Finance, FinanceType>(this, FinanceTypeForTransactionsName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.ReportWarning); } } }
/// <summary> /// Provides the description of the model settings for summary (GetFullSummary) /// </summary> /// <param name="formatForParentControl">Use full verbose description</param> /// <returns></returns> public override string ModelSummary(bool formatForParentControl) { string html = ""; // get the pricing ResourcePricing price = Apsim.Children(Apsim.Parent(this, typeof(IResourceType)), typeof(ResourcePricing)).FirstOrDefault() as ResourcePricing; if (price != null) { html += "<div class=\"activityentry\">Use "; html += (ResourceTypeName != null && ResourceTypeName != "") ? "<span class=\"resourcelink\">" + ResourceTypeName + "</span>" : "<span class=\"errorlink\">Account not set</span>"; html += " based upon the <span class=\"resourcelink\">" + price.Name + "</span> packet size and price for this resource</div>"; } else { html += "<div class=\"errorlink\">"; html += "Invalid transmutation cost. Cannot find a [r=ResourcePricing] for this resource."; html += "</div>"; } return(html); }
/// <summary> /// Provides the description of the model settings for summary (GetFullSummary) /// </summary> /// <param name="formatForParentControl">Use full verbose description</param> /// <returns></returns> public override string ModelSummary(bool formatForParentControl) { string html = ""; // get the pricing var w = FindAncestor <CLEMResourceTypeBase>() as IResourceType; bool multiPrice = (w as IModel).FindAllChildren <ResourcePricing>().Count() > 1; ResourcePricing price = w.Price(PurchaseOrSalePricingStyleType.Purchase); if (price != null) { html += "<div class=\"activityentry\">Use "; html += (ResourceTypeName != null && ResourceTypeName != "") ? "<span class=\"resourcelink\">" + ResourceTypeName + "</span>" : "<span class=\"errorlink\">Account not set</span>"; html += " based upon the " + (multiPrice ? "most suitable" : "<span class=\"resourcelink\"> " + price.Name + "</span>") + " packet size and price for this resource</div>"; } else { html += "<div class=\"errorlink\">"; html += "Invalid transmutation cost. Cannot find a [r=ResourcePricing] for this resource."; html += "</div>"; } return(html); }
/// <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 used to perform activity if it can occur as soon as resources are available. /// </summary> public override void DoActivity() { this.Status = ActivityStatus.NotNeeded; // get labour shortfall double labourLimit = this.LabourLimitProportion; // get finance purchase shortfall double financeLimit = (spent - earned > 0) ? Math.Min(bankAccount.Amount, spent - earned) / (spent - earned) : 1; if (resourceList.Count() == 0) { if (currentEntries.Count > 0) { this.Status = ActivityStatus.Warning; } return; } else { if (financeLimit < 1) { ResourceRequest purchaseRequest = new ResourceRequest { ActivityModel = this, Required = spent - earned, Available = bankAccount.Amount, Provided = bankAccount.Amount, AllowTransmutation = false, ResourceType = typeof(Finance), ResourceTypeName = AccountName, Category = "External purchases" }; ResourceRequestEventArgs rre = new ResourceRequestEventArgs() { Request = purchaseRequest }; OnShortfallOccurred(rre); } if (financeLimit < 1 || labourLimit < 1) { switch (OnPartialResourcesAvailableAction) { case OnPartialResourcesAvailableActionTypes.ReportErrorAndStop: Summary.WriteWarning(this, $"Ensure resources are available or change OnPartialResourcesAvailableAction setting for activity [a={this.NameWithParent}]"); Status = ActivityStatus.Critical; throw new ApsimXException(this, $"Insufficient resources [r={AccountName}] for activity [a={this.NameWithParent}]"); case OnPartialResourcesAvailableActionTypes.SkipActivity: this.Status = ActivityStatus.Ignored; return; default: break; } this.Status = ActivityStatus.Partial; } else { this.Status = ActivityStatus.Success; } // loop through all resources to exchange and make transactions for (int i = 0; i < currentEntries.Count; i++) { if (resourceList[i] is null) { this.Status = ActivityStatus.Warning; } else { // matching resource was found double amount = Convert.ToDouble(currentEntries[i][fileResource.AmountColumnName], CultureInfo.InvariantCulture); bool isSale = (amount < 0); amount = Math.Abs(amount); ResourcePricing price = null; if (bankAccount != null && !(resourceList[i] is FinanceType)) { price = resourceList[i].Price((amount > 0 ? PurchaseOrSalePricingStyleType.Purchase : PurchaseOrSalePricingStyleType.Sale)); } // transactions if (isSale) { // sell, so limit to labour and amount available amount = Math.Min(amount * labourLimit, resourceList[i].Amount); if (amount > 0) { if (price != null) { double packets = amount / price.PacketSize; if (price.UseWholePackets) { packets = Math.Truncate(packets); amount = packets * price.PacketSize; } bankAccount.Add(packets * price.PricePerPacket, this, (resourceList[i] as CLEMModel).NameWithParent, "External output"); } ResourceRequest sellRequest = new ResourceRequest { ActivityModel = this, Required = amount, AllowTransmutation = false, Category = "External output", RelatesToResource = (resourceList[i] as CLEMModel).NameWithParent }; resourceList[i].Remove(sellRequest); } } else { // limit to labour and financial constraints as this is a purchase amount *= Math.Min(labourLimit, financeLimit); if (amount > 0) { if (price != null) { // need to limit amount by financial constraints double packets = amount / price.PacketSize; if (price.UseWholePackets) { packets = Math.Truncate(packets); } amount = packets * price.PacketSize; ResourceRequest sellRequestDollars = new ResourceRequest { ActivityModel = this, Required = packets * price.PacketSize, AllowTransmutation = false, Category = "External input", RelatesToResource = (resourceList[i] as CLEMModel).NameWithParent }; bankAccount.Remove(sellRequestDollars); } resourceList[i].Add(amount, this, (resourceList[i] as CLEMModel).NameWithParent, "External input"); } } } } } }
/// <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); }
/// <inheritdoc/> public override List <ResourceRequest> GetResourcesNeededForActivity() { List <ResourceRequest> resourcesNeeded = new List <ResourceRequest>(); double amountToProcess = resourceTypeProcessModel.Amount; if (Reserve > 0) { amountToProcess = Math.Min(amountToProcess, Reserve); } // get finances required. foreach (ResourceActivityFee item in this.FindAllChildren <ResourceActivityFee>()) { if (ResourceRequestList == null) { ResourceRequestList = new List <ResourceRequest>(); } double sumneeded = 0; switch (item.PaymentStyle) { case ResourcePaymentStyleType.Fixed: sumneeded = item.Amount; break; case ResourcePaymentStyleType.perUnit: sumneeded = amountToProcess * item.Amount; break; case ResourcePaymentStyleType.perBlock: ResourcePricing price = resourceTypeProcessModel.Price(PurchaseOrSalePricingStyleType.Both); double blocks = amountToProcess / price.PacketSize; if (price.UseWholePackets) { blocks = Math.Truncate(blocks); } sumneeded = blocks * item.Amount; break; default: throw new Exception(String.Format("PaymentStyle [{0}] is not supported for [{1}] in [a={2}]", item.PaymentStyle, item.Name, this.Name)); } if (sumneeded > 0) { ResourceRequestList.Add(new ResourceRequest() { AllowTransmutation = false, Required = sumneeded, Resource = item.BankAccount, ResourceType = typeof(Finance), ResourceTypeName = item.BankAccount.Name, ActivityModel = this, FilterDetails = null, Category = TransactionCategory, RelatesToResource = (resourceTypeCreatedModel as CLEMModel).NameWithParent } ); } } // get process resource required if (amountToProcess > 0) { resourcesNeeded.Add( new ResourceRequest() { AllowTransmutation = true, Required = amountToProcess, Resource = resourceTypeProcessModel, ResourceType = (resourceTypeProcessModel as Model).Parent.GetType(), ResourceTypeName = (resourceTypeProcessModel as Model).Name, ActivityModel = this, Category = TransactionCategory, RelatesToResource = (resourceTypeCreatedModel as CLEMModel).NameWithParent } ); } return(resourcesNeeded); }
/// <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); }
private void OnCLEMAnimalSell(object sender, EventArgs e) { // Sell excess above store reserve level calculated from AE and daily target of first feed target // Performed here so all activities have access to human food stores before being sold. if (SellExcess && TimingOK) { // only uses the first target metric as measure double[] stored = new double[MonthsStorage + 1]; double[] target = new double[MonthsStorage + 1]; int[] daysInMonth = new int[MonthsStorage + 1]; // determine AEs to be fed - NOTE does not account ofr aging in reserve calcualtion double aE = people.Items.Where(a => IncludeHiredLabour || a.Hired == false).Sum(a => a.AdultEquivalent); LabourActivityFeedTarget feedTarget = this.FindAllChildren <LabourActivityFeedTarget>().FirstOrDefault() as LabourActivityFeedTarget; for (int i = 1; i <= MonthsStorage; i++) { DateTime month = Clock.Today.AddMonths(i); daysInMonth[i] = DateTime.DaysInMonth(month.Year, month.Month); target[i] = daysInMonth[i] * aE * feedTarget.TargetValue; } foreach (HumanFoodStoreType foodStore in food.FindAllChildren <HumanFoodStoreType>().Cast <HumanFoodStoreType>().ToList()) { double amountStored = 0; double amountAvailable = foodStore.Pools.Sum(a => a.Amount); if (amountAvailable > 0) { foreach (HumanFoodStorePool pool in foodStore.Pools.OrderBy(a => ((foodStore.UseByAge == 0) ? MonthsStorage : a.Age))) { if (foodStore.UseByAge != 0 && pool.Age == foodStore.UseByAge) { // don't sell food expiring this month as spoiled amountStored += pool.Amount; } else { int currentMonth = ((foodStore.UseByAge == 0) ? MonthsStorage : foodStore.UseByAge - pool.Age + 1); double poolRemaining = pool.Amount; while (currentMonth > 0) { if (stored[currentMonth] < target[currentMonth]) { // place amount in store double amountNeeded = target[currentMonth] - stored[currentMonth]; double towardTarget = pool.Amount * foodStore.EdibleProportion * foodStore.ConversionFactor(feedTarget.Metric); double amountSupplied = Math.Min(towardTarget, amountNeeded); double proportionProvided = amountSupplied / towardTarget; amountStored += pool.Amount * proportionProvided; poolRemaining -= pool.Amount * proportionProvided; stored[currentMonth] += amountSupplied; if (poolRemaining <= 0) { break; } } currentMonth--; } } } double amountSold = amountAvailable - amountStored; if (amountSold > 0) { ResourcePricing priceToUse = new ResourcePricing() { PacketSize = 1 }; if (foodStore.PricingExists(PurchaseOrSalePricingStyleType.Purchase)) { priceToUse = foodStore.Price(PurchaseOrSalePricingStyleType.Purchase); } double units = amountSold / priceToUse.PacketSize; if (priceToUse.UseWholePackets) { units = Math.Truncate(units); } // remove resource ResourceRequest purchaseRequest = new ResourceRequest { ActivityModel = this, Required = units * priceToUse.PacketSize, AllowTransmutation = false, Category = "Sell excess", RelatesToResource = foodStore.NameWithParent, MarketTransactionMultiplier = this.FarmMultiplier }; foodStore.Remove(purchaseRequest); // transfer money earned if (bankAccount != null) { ResourceRequest purchaseFinance = new ResourceRequest { ActivityModel = this, Required = units * priceToUse.PacketSize, AllowTransmutation = false, Category = $"Sales", RelatesToResource = foodStore.NameWithParent, MarketTransactionMultiplier = this.FarmMultiplier }; bankAccount.Add(purchaseFinance, this, foodStore.NameWithParent, "Sales"); } } } } } }
/// <summary> /// Resources needed for Activity /// </summary> /// <returns></returns> public override List <ResourceRequest> GetResourcesNeededForActivity() { List <ResourceRequest> resourcesNeeded = new List <ResourceRequest>(); double amountToProcess = resourceTypeProcessModel.Amount; if (Reserve > 0) { amountToProcess = Math.Min(amountToProcess, Reserve); } // get finances required. foreach (ResourceActivityFee item in Apsim.Children(this, typeof(ResourceActivityFee))) { if (ResourceRequestList == null) { ResourceRequestList = new List <ResourceRequest>(); } double sumneeded = 0; switch (item.PaymentStyle) { case ResourcePaymentStyleType.Fixed: sumneeded = item.Amount; break; case ResourcePaymentStyleType.perUnit: sumneeded = amountToProcess * item.Amount; break; case ResourcePaymentStyleType.perBlock: ResourcePricing price = resourceTypeProcessModel.Price; double blocks = amountToProcess / price.PacketSize; if (price.UseWholePackets) { blocks = Math.Truncate(blocks); } sumneeded = blocks * item.Amount; break; default: throw new Exception(String.Format("PaymentStyle ({0}) is not supported for ({1}) in ({2})", item.PaymentStyle, item.Name, this.Name)); } ResourceRequestList.Add(new ResourceRequest() { AllowTransmutation = false, Required = sumneeded, ResourceType = typeof(Finance), ResourceTypeName = item.BankAccount.Name, ActivityModel = this, FilterDetails = null, Reason = item.Name } ); } // get process resource required if (amountToProcess > 0) { resourcesNeeded.Add( new ResourceRequest() { AllowTransmutation = true, Required = amountToProcess, ResourceType = (resourceTypeProcessModel as Model).Parent.GetType(), ResourceTypeName = (resourceTypeProcessModel as Model).Name, ActivityModel = this, Reason = "Process " + (resourceTypeProcessModel as Model).Name } ); } return(resourcesNeeded); }