/// <summary> /// Check to see if the add list of the action can potentially be used to fulfill a goal /// </summary> /// <param name="goal">The goal to be fulfilled</param> /// <param name="currentBeliefs">The current beliefs that we have at the moment</param> /// <returns>Returns a fact that can be found in the add list that has the same name as the end goal/fact in the goal /// and where an assignment can be found that fulfills the goal and does not violate the constraints of the action.</returns> internal virtual Fact IsApplicableForAdd(SimpleGoal goal, List <Fact> currentBeliefs) { //only facts that have the same name as the goal are considered but also only those that are more general (or equally general) than the goal //this means that a substitution (or empty substitution) exists that transforms the fact of the addlist into the fact of the goal var possibeAdds = AddList.Where(y => y.Name.Equals(goal.Fact.Name) && y.IsMoreGeneralThanOrEqualTo(goal.Fact)); //if no such facts can be found, quickly return null if (!possibeAdds.Any()) { return(null); } //if there are no constraints, quickly return the first fact that can fulfill the goal if (!Constraints.Any()) { return(possibeAdds.FirstOrDefault()); } //return the first fact of the possible facts of the addlist where an assignment can be found that also //does not violate the constraints of the action foreach (var possibleGoal in possibeAdds) { var tuples = GetAssignment(possibleGoal, goal, currentBeliefs); if (CheckConstraints(tuples)) { return(possibleGoal); } } return(null); }
/// <summary> /// Instantiate an action so an instantiated goal is fulfilled /// </summary> /// <param name="goal">The goal to be fulfilled</param> /// <param name="currentBeliefs">The current set of beliefs</param> /// <returns>The instantiated action (based on the given action) that completely fulfills the given goal</returns> internal Action InstantiateFor(SimpleGoal goal, List <Fact> currentBeliefs) { var cloned = Clone(); var fact = cloned.IsApplicableForAdd(goal, currentBeliefs); var tuples = GetAssignment(fact, goal, currentBeliefs); cloned.FillParameters(tuples); return(cloned); }
/// <summary> /// Returns a potential assignment/substitution in the form of a list of keyvalue pair of names and values that /// fulfills the goal based on the given fact of the addlist /// </summary> /// <param name="possibleAdd">The fact of the addlist that can be instantiated to fulfill the goal.</param> /// <param name="goal">The goal we wish to see fulfilled</param> /// <param name="currentBeliefs">The current beliefs that we have at the moment</param> /// <returns>A potential assignment for the fact of the addlist to fulfill the given goal.</returns> public virtual Dictionary <string, object> GetAssignment(Fact possibleAdd, SimpleGoal goal, List <Fact> currentBeliefs) { var tuples = new Dictionary <string, object>(); for (var i = 0; i < Math.Min(goal.Fact.Parameters.Count, possibleAdd.Parameters.Count); i++) { if (possibleAdd.Parameters[i] is NamedParameter namedParameter) { tuples.Add(namedParameter.Name, (goal.Fact.Parameters[i] as ValueParameter).Value); } } return(tuples); }
/// <summary> /// Finds a substitution/assigment for a fact of a goal so that the instantiated fact exists in the current beliefset /// </summary> /// <param name="goal">The goal to be fulfilled</param> /// <param name="currentBeliefs">The current beliefset</param> /// <returns>An assignment/substitution for the fact of the goal such that the instantiated fact exists in the current beliefset</returns> private static Dictionary <string, object> FindGoodSubstitution(SimpleGoal goal, List <Fact> currentBeliefs) { var dict = new Dictionary <string, object>(); //first look for candidate facts in the current beliefset that bear the same name as the fact in the goal var candidateFacts = currentBeliefs.Where(x => x.Name.Equals(goal.Fact.Name)); var lstOk = new List <Fact>(); //keep a list of those facts in the reduced list that have the same value in their parameters as the goal has when it has //filled in valueparameters instead of named parameters foreach (var candidateFact in candidateFacts) { var ok = true; for (var i = 0; i < candidateFact.Parameters.Count && ok; i++) { var canParam = candidateFact.Parameters[i] as ValueParameter; if (goal.Fact.Parameters[i] is ValueParameter valParam && !((valParam.Value == null && canParam.Value == null) || (valParam.Value != null && (valParam.Value == canParam.Value || valParam.Value.Equals(canParam.Value))))) { ok = false; } } if (ok) { lstOk.Add(candidateFact); } } //get the first in this list (or null) :=> REMARK : possibly change this as this is inefficient var firstOk = lstOk.FirstOrDefault(); if (firstOk == null) { return(null); } //for the found fact, calculate the values for the remaining named parameters and fill the subsitution with this information for (var i = 0; i < firstOk.Parameters.Count; i++) { if (goal.Fact.Parameters[i] is NamedParameter namedParam) { dict.Add(namedParam.Name, ((ValueParameter)firstOk.Parameters[i]).Value); } } return(dict); }
/// <summary> /// Perform and check the subsitution but only when needed /// </summary> /// <param name="simpleGoal">The goal to perform substitution on</param> /// <param name="currentBeliefs">The current set of beliefs</param> /// <param name="preconditions">The current list of preconditions used when performing loop detection</param> /// <returns>True if no loops detected and substitution is possible when it is required, false otherwise</returns> private static bool PerformAndCheckSubstitionIfNeeded(SimpleGoal simpleGoal, List <Fact> currentBeliefs, List <Fact> preconditions) { if (simpleGoal.NeedsSubstitution()) { var toSubstitute = FindGoodSubstitution(simpleGoal, currentBeliefs); //no substitution found so no plan possible for the current choice of actions if (toSubstitute == null) { return(false); } simpleGoal.Action.FillParameters(toSubstitute); //simple loop detection if (IsAlreadyACondition(simpleGoal.Fact, preconditions)) { return(false); } } return(true); }
/// <summary> /// Fulfill a simple goal when needed. This is done by finding an action that has a more general version of the goal in its add list. /// If we find an action, we instantiate it and add it along with its preconditions to the stack (preconditions first). /// Some loopdetection is performed to see if we haven't already added a precondition of the action as this indicates a loop. /// Multiple actions are possible so if choosing one action ends in an unplannable situation (due to a loop or no choice of actions further down the line) /// we simply backtrack to the previous choice of actions and choose another. When looking for an optimal path, this backtracking is done for every choice so /// we can guarantee that we make the most optimal choice at any point of the execution. /// </summary> /// <param name="simpleGoal">The goal we wish to see fulfilled by choosing an action</param> /// <param name="plan">The current plan</param> /// <param name="optimal">A flag indicating if we're looking for the optimal plan or not</param> /// <param name="itemsToProcess">The stack of items to process containing goals and actions</param> /// <param name="previousStates">A list of previous states to be used in loop detection</param> /// <param name="currentBeliefs">The current set of beliefs</param> /// <param name="preconditions">A list of preconditions used in loop detection</param> /// <param name="actions">A list of executable actions of the agent</param> /// <returns>True if we find a plan that fulfills the goal and all remaining items in the stack, false otherwise</returns> private static bool FulFillSimpleGoal <TAction>(SimpleGoal simpleGoal, Plan <TAction> plan, bool optimal, Stack <IStackItem> itemsToProcess, List <State> previousStates, List <Fact> currentBeliefs, List <Fact> preconditions, List <Action> actions) where TAction : Action { //only look for an action when the goal hasn't been achieved yet if (!simpleGoal.IsFulFilled(currentBeliefs)) { //add the fact of the goal in the list of visited preconditions preconditions.Add(simpleGoal.Fact); //look for possible actions to execute var possibleActions = actions.Where(x => x.IsApplicableFor(simpleGoal, currentBeliefs)); var iter = possibleActions.GetEnumerator(); //keep a list of plans and beliefsets for each action that is possible (for optimization later) var subPlansForAction = new List <Tuple <Plan <TAction>, List <Fact> > >(); while (iter.MoveNext()) { var action = iter.Current; //instantiate the action as much as possible action = action.InstantiateFor(simpleGoal, currentBeliefs); //perform loopdetection var lstPre = action.Preconditions.ToList(); lstPre.Reverse(); var faulty = lstPre.Any(x => IsAlreadyACondition(x, preconditions)); if (!faulty) { //create the complex goal with the preconditions of the action var complex = new ComplexGoal(); foreach (var precondition in lstPre) { complex.Goals.Add(new SimpleGoal(precondition, action)); } //create a new stack but with action and complex goal added var newStack = new Stack <IStackItem>(itemsToProcess.Reverse()); newStack.Push(action); newStack.Push(complex); //perform create plan for the new stack (Plan <TAction> subPlanForAction, List <Fact> lstBeliefs) = CreatePlan <TAction>(newStack, currentBeliefs.ToList(), preconditions.ToList(), previousStates.ToList(), actions, optimal); //the chosen action gives a valid plan so add the plan (and beliefset) to the possible plans we can choose from if (subPlanForAction != null) { subPlansForAction.Add(Tuple.Create(subPlanForAction, lstBeliefs)); if (optimal) { break; } } } } //no plan found for this goal and current beliefset so return null if (!subPlansForAction.Any()) { return(false); } else { //we look for the minimal path (minimal number of steps) asuming that each step has an equal 'cost' var minSteps = subPlansForAction.Min(x => x.Item1.Steps.Count()); var best = subPlansForAction.First(x => x.Item1.Steps.Count() == minSteps); //add the steps of the optimal subplan to this plan and update the current set of beliefs plan.Steps.AddRange(best.Item1.Steps); currentBeliefs = best.Item2; //clear the stack as we already processed all items in the stack when looking for all subplans itemsToProcess.Clear(); } } return(true); }
/// <summary> /// Check to see if an action is applicable to fulfill a certain goal /// </summary> /// <param name="goal">The goal to be tested for potential fulfillment</param> /// <param name="currentBeliefs">The current set of beliefs</param> /// <returns>True if the action is applicable to fulfill the goal, false otherwise</returns> internal bool IsApplicableFor(SimpleGoal goal, List <Fact> currentBeliefs) => IsApplicableForAdd(goal, currentBeliefs) != null;