/// <summary> /// return a list of components available given the specified types /// </summary> /// <param name="typesToFind">the list of types to locate</param> /// <returns>A list of names of components</returns> public IEnumerable <string> GetResourcesAvailableByName(object[] typesToFind) { List <string> results = new List <string>(); Zone zone = this.FindAncestor <Zone>(); if (!(zone is null)) { ResourcesHolder resources = zone.FindChild <ResourcesHolder>(); if (!(resources is null)) { foreach (object type in typesToFind) { if (type is string) { results.Add(type as string); } else if (type is Type) { var list = resources.FindResource(type as Type)?.FindAllChildren <IResourceType>().Select(a => (a as CLEMModel).NameWithParent); if (list != null) { results.AddRange(resources.FindResource(type as Type).FindAllChildren <IResourceType>().Select(a => (a as CLEMModel).NameWithParent)); } } } } } return(results.AsEnumerable()); }
/// <summary> /// Validate this object /// </summary> /// <param name="validationContext"></param> /// <returns></returns> public IEnumerable <ValidationResult> Validate(ValidationContext validationContext) { var results = new List <ValidationResult>(); if (TransmuteResourceTypeName != null && TransmuteResourceTypeName != "") { if (!TransmuteResourceTypeName.Contains(".")) { string[] memberNames = new string[] { "ResourceTypeName" }; results.Add(new ValidationResult("Invalid resource type entry. Please select resource type from the drop down list provided or ensure the value is formatted as ResourceGroup.ResourceType", memberNames)); } else { object result = resources.FindResource <ResourceBaseWithTransactions>(TransmuteResourceTypeName.Split('.').First()); if (result == null) { Summary.WriteWarning(this, $"Could not find resource group [r={TransmuteResourceTypeName.Split('.').First()}] in transmute [{this.Name}]{Environment.NewLine}The parent transmutation [{(this.Parent as CLEMModel).NameWithParent}] will not suceed without this resource and will not be performed"); } else { object resultType = resources.FindResourceType <ResourceBaseWithTransactions, IResourceType>(this, TransmuteResourceTypeName, OnMissingResourceActionTypes.Ignore, OnMissingResourceActionTypes.Ignore); if (resultType is null) { string[] memberNames = new string[] { "ResourceType" }; results.Add(new ValidationResult($"Could not find resource [r={TransmuteResourceTypeName.Split('.').First()}][r={TransmuteResourceTypeName.Split('.').Last()}] for [{this.Name}]{Environment.NewLine}The parent transmutation [{(this.Parent as CLEMModel).NameWithParent}] will not suceed without this resource and will not be performed", memberNames)); } } } } // get pricing if available IResourceType parentResource = FindAncestor <CLEMResourceTypeBase>() as IResourceType; if (TransmuteStyle == TransmuteStyle.UsePricing) { if (shortfallPricing is null) { string[] memberNames = new string[] { "Shortfall resource pricing" }; results.Add(new ValidationResult($"No resource pricing was found for [r={(parentResource as CLEMModel).NameWithParent}] required for a price based transmute [{this.Name}]{Environment.NewLine}Provide a pricing for the shortfall resource or use Direct transmute style", memberNames)); } if (!(TransmuteResourceType is FinanceType)) { if (transmutePricing is null) { string[] memberNames = new string[] { "Transmute resource pricing" }; results.Add(new ValidationResult($"No resource pricing was found for [r={(TransmuteResourceType as CLEMModel).NameWithParent}] required for a price based transmute [{this.Name}]Provide a pricing for the transmute resource or use Direct transmute style", memberNames)); } } } return(results); }
/// <summary> /// Validate this object /// </summary> /// <param name="validationContext"></param> /// <returns></returns> public IEnumerable <ValidationResult> Validate(ValidationContext validationContext) { var results = new List <ValidationResult>(); // ensure labour resource added Labour lab = Resources.FindResource <Labour>(); if (lab == null) { Summary.WriteMessage(this, "[a=" + this.Parent.Name + "][f=" + this.Name + "] No labour resorces in simulation. Labour requirement will be ignored.", MessageType.Warning); } else if (lab.Children.Count <= 0) { Summary.WriteMessage(this, "[a=" + this.Parent.Name + "][f=" + this.Name + "] No labour resorce types are provided in the labour resource. Labour requirement will be ignored.", MessageType.Warning); } // check filter groups present if (this.Children.OfType <LabourFilterGroup>().Count() == 0) { Summary.WriteMessage(this, "No LabourFilterGroup is supplied with the LabourRequirement for [a=" + this.Parent.Name + "]. No labour will be used for this activity.", MessageType.Warning); } // check for individual nesting. foreach (LabourFilterGroup fg in this.FindAllChildren <LabourFilterGroup>()) { LabourFilterGroup currentfg = fg; while (currentfg != null && currentfg.FindAllChildren <LabourFilterGroup>().Any()) { if (currentfg.FindAllChildren <LabourFilterGroup>().Count() > 1) { string[] memberNames = new string[] { "Labour requirement" }; results.Add(new ValidationResult(String.Format("Invalid nested labour filter groups in [f={0}] for [a={1}]. Only one nested filter group is permitted each branch. Additional filtering will be ignored.", currentfg.Name, this.Name), memberNames)); } currentfg = currentfg.FindAllChildren <LabourFilterGroup>().FirstOrDefault(); } } return(results); }
private void OnCommencing(object sender, EventArgs e) { int lossModifier = 1; if (ReportLossesAsNegative) { lossModifier = -1; } // check if running from a CLEM.Market bool market = (FindAncestor <Zone>().GetType() == typeof(Market)); List <string> variableNames = new List <string> { "[Clock].Today as Date" }; if (IncludeFinancialYear) { Finance financeStore = resources.FindResourceGroup <Finance>(); if (financeStore != null) { variableNames.Add($"[Resources].{financeStore.Name}.FinancialYear as FY"); } } List <string> eventNames = new List <string>(); if (ResourceGroupsToReport != null && ResourceGroupsToReport.Trim() != "") { // check it is a ResourceGroup CLEMModel model = resources.FindResource <ResourceBaseWithTransactions>(ResourceGroupsToReport); if (model == null) { summary.WriteMessage(this, String.Format("Invalid resource group [{0}] in ReportResourceBalances [{1}]\r\nEntry has been ignored", this.ResourceGroupsToReport, this.Name), MessageType.Warning); } else { bool pricingIncluded = false; if (model.GetType() == typeof(RuminantHerd)) { pricingIncluded = model.FindAllDescendants <AnimalPricing>().Where(a => a.Enabled).Count() > 0; variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.ID as uID"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Breed as Breed"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Sex as Sex"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Age as Age"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Weight as Weight"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.AdultEquivalent as AE"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.SaleFlag as Category"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.Class as Class"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.HerdName as RelatesTo"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastIndividualChanged.PopulationChangeDirection as Change"); // ToDo: add pricing for ruminants including buy and sell pricing // Needs update in CLEMResourceTypeBase and link between ResourcePricing and AnimalPricing. } else { pricingIncluded = model.FindAllDescendants <ResourcePricing>().Where(a => a.Enabled).Count() > 0; if (ReportStyle == ReportTransactionStyle.GainAndLossColumns) { variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Gain as Gain"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.Loss * {lossModifier} as Loss"); } else { variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.TransactionType as Type"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.AmountModifiedForLoss({ReportLossesAsNegative}) as Amount"); } // get all converters for this type of resource if (IncludeConversions) { var converterList = model.FindAllDescendants <ResourceUnitsConverter>().Select(a => a.Name).Distinct(); if (converterList != null) { foreach (var item in converterList) { if (ReportStyle == ReportTransactionStyle.GainAndLossColumns) { variableNames.Add($"[Resources].{this.ResourceGroupsToReport}.LastTransaction.ConvertTo(\"{item}\",\"gain\",{ReportLossesAsNegative}) as {item}_Gain"); variableNames.Add($"[Resources].{this.ResourceGroupsToReport}.LastTransaction.ConvertTo(\"{item}\",\"loss\",{ReportLossesAsNegative}) as {item}_Loss"); } else { variableNames.Add($"[Resources].{this.ResourceGroupsToReport}.LastTransaction.ConvertTo(\"{item}\", {ReportLossesAsNegative}) as {item}_Amount"); } } } } // add pricing if (IncludePrice && pricingIncluded) { if (ReportStyle == ReportTransactionStyle.GainAndLossColumns) { variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.ConvertTo(\"$gain\",\"gain\", {ReportLossesAsNegative}) as Price_Gain"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.ConvertTo(\"$loss\",\"loss\", {ReportLossesAsNegative}) as Price_Loss"); } else { variableNames.Add("[Resources]." + this.ResourceGroupsToReport + $".LastTransaction.ConvertTo(\"$gain\", {ReportLossesAsNegative}) as Price_Amount"); } } variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.ResourceType.Name as Resource"); // if this is a multi CLEM model simulation then add a new column with the parent Zone name if (FindAncestor <Simulation>().FindChild <Market>() != null) { variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Activity.CLEMParentName as Source"); } variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Activity.Name as Activity"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.RelatesToResource as RelatesTo"); variableNames.Add("[Resources]." + this.ResourceGroupsToReport + ".LastTransaction.Category as Category"); } } eventNames.Add("[Resources]." + this.ResourceGroupsToReport + ".TransactionOccurred"); } VariableNames = variableNames.ToArray(); EventNames = eventNames.ToArray(); SubscribeToEvents(); }
[EventSubscribe("FinalInitialise")] // "Commencing" private void OnCommencing(object sender, EventArgs e) { if (ResourceGroupsToReport is null || !ResourceGroupsToReport.Any()) { return; } timers = FindAllChildren <IActivityTimer>(); List <string> variableNames = new List <string>(); if (ResourceGroupsToReport.Where(a => a.Contains("[Clock].Today")).Any() is false) { variableNames.Add("[Clock].Today as Date"); } if (ResourceGroupsToReport != null) { for (int i = 0; i < this.ResourceGroupsToReport.Length; i++) { // each variable name is now a ResourceGroup bool isDuplicate = StringUtilities.IndexOfCaseInsensitive(variableNames, this.ResourceGroupsToReport[i].Trim()) != -1; if (!isDuplicate && this.ResourceGroupsToReport[i] != string.Empty) { if (this.ResourceGroupsToReport[i].StartsWith("[")) { variableNames.Add(this.ResourceGroupsToReport[i]); } else { // check it is a ResourceGroup CLEMModel model = resources.FindResource <ResourceBaseWithTransactions>(this.ResourceGroupsToReport[i]); if (model == null) { summary.WriteMessage(this, $"Invalid resource group [r={this.ResourceGroupsToReport[i]}] in ReportResourceBalances [{this.Name}]{Environment.NewLine}Entry has been ignored", MessageType.Warning); } else { if (model is Labour) { string amountStr = "Amount"; if (ReportLabourIndividuals) { amountStr = "Individuals"; } for (int j = 0; j < (model as Labour).Items.Count; j++) { if (ReportAmount) { variableNames.Add("[Resources]." + this.ResourceGroupsToReport[i] + ".Items[" + (j + 1).ToString() + $"].{amountStr} as " + (model as Labour).Items[j].Name); } //TODO: what economic metric is needed for labour //TODO: add ability to report labour value if required } } else { foreach (CLEMModel item in model.FindAllChildren <CLEMModel>()) { string amountStr = "Amount"; switch (item) { case FinanceType ftype: amountStr = "Balance"; break; case LandType ltype: if (ReportLandEntire) { amountStr = "LandArea"; } break; default: break; } if (item is RuminantType) { // add each variable needed foreach (var category in (model as RuminantHerd).GetReportingGroups(item as RuminantType)) { if (ReportAmount) { variableNames.Add($"[Resources].{this.ResourceGroupsToReport[i]}.GetRuminantReportGroup(\"{(item as IModel).Name}\",\"{category}\").Count as {item.Name.Replace(" ", "_")}{(((model as RuminantHerd).TransactionStyle != RuminantTransactionsGroupingStyle.Combined) ? $".{category.Replace(" ", "_")}" : "")}.Count"); } if (ReportAnimalEquivalents) { variableNames.Add($"[Resources].{this.ResourceGroupsToReport[i]}.GetRuminantReportGroup(\"{(item as IModel).Name}\",\"{category}\").TotalAdultEquivalent as {item.Name.Replace(" ", "_")}{(((model as RuminantHerd).TransactionStyle != RuminantTransactionsGroupingStyle.Combined) ? $".{category.Replace(" ", "_")}" : "")}.TotalAdultEquivalent"); } if (ReportAnimalWeight) { variableNames.Add($"[Resources].{this.ResourceGroupsToReport[i]}.GetRuminantReportGroup(\"{(item as IModel).Name}\",\"{category}\").TotalWeight as {item.Name.Replace(" ", "_")}{(((model as RuminantHerd).TransactionStyle != RuminantTransactionsGroupingStyle.Combined) ? $".{category.Replace(" ", "_")}" : "")}.TotalWeight"); } if (ReportValue) { variableNames.Add($"[Resources].{this.ResourceGroupsToReport[i]}.GetRuminantReportGroup(\"{(item as IModel).Name}\",\"{category}\").TotalPrice as {item.Name.Replace(" ", "_")}{(((model as RuminantHerd).TransactionStyle != RuminantTransactionsGroupingStyle.Combined) ? $".{category.Replace(" ", "_")}" : "")}.TotalPrice"); } } } else { if (ReportAmount) { variableNames.Add($"[Resources].{this.ResourceGroupsToReport[i]}.{ item.Name}.{ amountStr } as { item.Name.Replace(" ", "_") }_Amount"); } if (ReportValue & item.GetType() != typeof(FinanceType)) { variableNames.Add($"[Resources].{this.ResourceGroupsToReport[i]}.{item.Name}.Value as { item.Name.Replace(" ", "_") }_DollarValue"); } } } } } } } } } VariableNames = variableNames.ToArray(); // Subscribe to events. if (EventNames == null || !EventNames.Where(a => a.Trim() != "").Any()) { EventNames = new string[] { "[Clock].CLEMFinalizeTimeStep" } } ; SubscribeToEvents(); }
/// <summary> /// Determine resources available and perform transmutation if needed. /// </summary> /// <param name="resourceRequests">List of requests</param> /// <param name="uniqueActivityID">Unique id for the activity</param> public void CheckResources(IEnumerable<ResourceRequest> resourceRequests, Guid uniqueActivityID) { if (resourceRequests is null || !resourceRequests.Any()) { this.Status = ActivityStatus.Success; return; } foreach (ResourceRequest request in resourceRequests) { request.ActivityID = uniqueActivityID; request.Available = 0; // If resource group does not exist then provide required. // This means when resource is not added to model it will not limit simulations if (request.ResourceType == null || Resources.FindResource(request.ResourceType) == null) { request.Available = request.Required; request.Provided = request.Required; } else { if (request.ResourceType == typeof(Labour)) // get available labour based on rules. request.Available = TakeLabour(request, false, this, Resources, this.OnPartialResourcesAvailableAction); else request.Available = TakeNonLabour(request, false); } } // are all resources available IEnumerable<ResourceRequest> shortfallRequests = resourceRequests.Where(a => a.Required > a.Available); if (shortfallRequests.Any()) // check what transmutations can occur Resources.TransmutateShortfall(shortfallRequests); // check if need to do transmutations int countTransmutationsSuccessful = shortfallRequests.Where(a => a.TransmutationPossible == true && a.AllowTransmutation).Count(); bool allTransmutationsSuccessful = (shortfallRequests.Where(a => a.TransmutationPossible == false && a.AllowTransmutation).Count() == 0); // OR at least one transmutation successful and PerformWithPartialResources if ((shortfallRequests.Any() && (shortfallRequests.Count() == countTransmutationsSuccessful)) || (countTransmutationsSuccessful > 0 && OnPartialResourcesAvailableAction == OnPartialResourcesAvailableActionTypes.UseResourcesAvailable)) { // do transmutations. // this uses the current zone resources, but will find markets if needed in the process Resources.TransmutateShortfall(shortfallRequests, false); // recheck resource amounts now that resources have been topped up foreach (ResourceRequest request in resourceRequests) { // get resource request.Available = 0; if (request.Resource != null) // get amount available request.Available = Math.Min(request.Resource.Amount, request.Required); } } bool deficitFound = false; // report any resource defecits here foreach (var item in resourceRequests.Where(a => (a.Required - a.Available) > 0.000001)) { ResourceRequestEventArgs rrEventArgs = new ResourceRequestEventArgs() { Request = item }; if (item.Resource != null && (item.Resource as Model).FindAncestor<Market>() != null) { ActivitiesHolder marketActivities = Resources.FoundMarket.FindChild<ActivitiesHolder>(); if(marketActivities != null) marketActivities.ActivitiesHolder_ResourceShortfallOccurred(this, rrEventArgs); } else OnShortfallOccurred(rrEventArgs); Status = ActivityStatus.Partial; deficitFound = true; } if(!deficitFound) this.Status = ActivityStatus.Success; }
/// <summary> /// Method to determine available labour based on filters and take it if requested. /// </summary> /// <param name="request">Resource request details</param> /// <param name="removeFromResource">Determines if only calculating available labour or labour removed</param> /// <param name="callingModel">Model calling this method</param> /// <param name="resourceHolder">Location of resource holder</param> /// <param name="partialAction">Action on partial resources available</param> /// <returns></returns> public static double TakeLabour(ResourceRequest request, bool removeFromResource, IModel callingModel, ResourcesHolder resourceHolder, OnPartialResourcesAvailableActionTypes partialAction ) { double amountProvided = 0; double amountNeeded = request.Required; LabourFilterGroup current = request.FilterDetails.OfType<LabourFilterGroup>().FirstOrDefault(); LabourRequirement lr; if (current!=null) { if (current.Parent is LabourRequirement) lr = current.Parent as LabourRequirement; else // coming from Transmutation request lr = new LabourRequirement() { ApplyToAll = false, MaximumPerPerson = 1000, MinimumPerPerson = 0 }; } else lr = callingModel.FindAllChildren<LabourRequirement>().FirstOrDefault(); int currentIndex = 0; if (current==null) // no filtergroup provided so assume any labour current = new LabourFilterGroup(); request.ResourceTypeName = "Labour"; ResourceRequest removeRequest = new ResourceRequest() { ActivityID = request.ActivityID, ActivityModel = request.ActivityModel, AdditionalDetails = request.AdditionalDetails, AllowTransmutation = request.AllowTransmutation, Available = request.Available, FilterDetails = request.FilterDetails, Provided = request.Provided, Category = request.Category, RelatesToResource = request.RelatesToResource, Required = request.Required, Resource = request.Resource, ResourceType = request.ResourceType, ResourceTypeName = (request.Resource is null? "":(request.Resource as CLEMModel).NameWithParent) }; // start with top most LabourFilterGroup while (current != null && amountProvided < amountNeeded) { IEnumerable<LabourType> items = resourceHolder.FindResource<Labour>().Items; items = items.Where(a => (a.LastActivityRequestID != request.ActivityID) || (a.LastActivityRequestID == request.ActivityID && a.LastActivityRequestAmount < lr.MaximumPerPerson)); items = current.Filter(items); // search for people who can do whole task first while (amountProvided < amountNeeded && items.Where(a => a.LabourCurrentlyAvailableForActivity(request.ActivityID, lr.MaximumPerPerson) >= request.Required).Count() > 0) { // get labour least available but with the amount needed LabourType lt = items.Where(a => a.LabourCurrentlyAvailableForActivity(request.ActivityID, lr.MaximumPerPerson) >= request.Required).OrderBy(a => a.LabourCurrentlyAvailableForActivity(request.ActivityID, lr.MaximumPerPerson)).FirstOrDefault(); double amount = Math.Min(amountNeeded - amountProvided, lt.LabourCurrentlyAvailableForActivity(request.ActivityID, lr.MaximumPerPerson)); // limit to max allowed per person amount = Math.Min(amount, lr.MaximumPerPerson); // limit to min per person to do activity if (amount < lr.MinimumPerPerson) { request.Category = "Min labour limit"; return amountProvided; } amountProvided += amount; removeRequest.Required = amount; if (removeFromResource) { lt.LastActivityRequestID = request.ActivityID; lt.LastActivityRequestAmount = amount; lt.Remove(removeRequest); request.Provided += removeRequest.Provided; request.Value += request.Provided * lt.PayRate(); } } // if still needed and allow partial resource use. if (partialAction == OnPartialResourcesAvailableActionTypes.UseResourcesAvailable) { if (amountProvided < amountNeeded) { // then search for those that meet criteria and can do part of task foreach (LabourType item in items.Where(a => a.LabourCurrentlyAvailableForActivity(request.ActivityID, lr.MaximumPerPerson) >= 0).OrderByDescending(a => a.LabourCurrentlyAvailableForActivity(request.ActivityID, lr.MaximumPerPerson))) { if (amountProvided >= amountNeeded) break; double amount = Math.Min(amountNeeded - amountProvided, item.LabourCurrentlyAvailableForActivity(request.ActivityID, lr.MaximumPerPerson)); // limit to max allowed per person amount = Math.Min(amount, lr.MaximumPerPerson); // limit to min per person to do activity if (amount >= lr.MinimumPerPerson) { amountProvided += amount; removeRequest.Required = amount; if (removeFromResource) { if(item.LastActivityRequestID != request.ActivityID) item.LastActivityRequestAmount = 0; item.LastActivityRequestID = request.ActivityID; item.LastActivityRequestAmount += amount; item.Remove(removeRequest); request.Provided += removeRequest.Provided; request.Value += request.Provided * item.PayRate(); } } else currentIndex = request.FilterDetails.Count; } } } currentIndex++; var currentFilterGroups = current.FindAllChildren<LabourFilterGroup>(); if (currentFilterGroups.Any()) current = currentFilterGroups.FirstOrDefault(); else current = null; } // report amount gained. return amountProvided; }