/// <summary> /// Process and check the complex goal for loop detection /// </summary> /// <param name="currentGoal">The complex goal to be checked and processed</param> /// <param name="currentBeliefs">The current set of beliefs</param> /// <param name="previousStates">The list of previous states</param> /// <param name="itemsToProcess">The stack of items to process</param> /// <returns>True if the complex goal does not contain loops and is processed, false otherwise</returns> private static bool DecomposeAndCheckComplexGoal(ComplexGoal currentGoal, List <Fact> currentBeliefs, List <State> previousStates, Stack <IStackItem> itemsToProcess) { //only if the complex goal is not fulfilled, do we need to process and check it if (!currentGoal.IsFulFilled(currentBeliefs)) { //if a loop is detected, we end execution, else we add this state to the list of states if (CheckForLoop(currentBeliefs, currentGoal, previousStates)) { return(false); } else { previousStates.Add(new State { CurrentComplexGoal = currentGoal, CurrentBeliefs = currentBeliefs }); } //we repush the complex goal in the items to process so it can be checked again after all subgoals have been fulfilled //this is done to avoid that the solution to a subgoal 'destroys' another fulfilled subgoal itemsToProcess.Push(currentGoal); //all subgoals are pushed on the stack foreach (var simpleGoal in currentGoal.Goals) { itemsToProcess.Push(simpleGoal); } } return(true); }
/// <summary> /// Check for a loop in the execution of our planner so that we do not introduce infinite loops. /// We test this by testing if we encountered the same current beliefs and the same complex goal somewhere before. /// If so, this indicated that we're stuck in a loop and we need to exit the current choice of actions to find a proper path /// of steps/actions that lead to the fulfillment of all given goals. /// </summary> /// <param name="currentBeliefs">The set of facts we current believe to be true</param> /// <param name="goal">The complex goal currently being processed</param> /// <param name="states">The list of previous states we encountered</param> /// <returns>True if a state can be found that contains the same given goal and where the set of beliefs of that state /// completely matches the current set of beliefs.</returns> private static bool CheckForLoop(List <Fact> currentBeliefs, ComplexGoal goal, List <State> states) { foreach (var state in states) { if (goal.IsSame(state.CurrentComplexGoal) && state.CurrentBeliefs.Count() == currentBeliefs.Count() && state.CurrentBeliefs.All(x => currentBeliefs.Any(y => y.IsSameAs(x)))) { return(true); } } return(false); }
/// <summary> /// Check to see if a complex goal is the same as another /// </summary> /// <param name="goal">The other complex goal to test for equality</param> /// <returns>True if they have the same amount of subgoals and if every subgoal /// in its ordered list is the same goal in the ordered list of the other complex goal</returns> internal bool IsSame(ComplexGoal goal) { //not same amount of goals so can't be equal if (goal.Goals.Count() != Goals.Count()) { return(false); } //test if all subgoals (ordered) match (return false if one set does not match) for (var i = 0; i < goal.Goals.Count(); i++) { if (!goal.Goals[i].Fact.IsSameAs(Goals[i].Fact)) { 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); }