From 009f647433dab84c2e841ffde4c39ef7054c8558 Mon Sep 17 00:00:00 2001 From: Simon Condon Date: Sat, 7 Jan 2023 14:52:16 +0000 Subject: [PATCH] Breaking changes to "search" planners. Folded action cost calculation into the same interface as the one that contains EstimateCost - IHeuristic. Renamed it to IStrategy since its no longer just about the heuristic. This has enabled StateSpaceSearch and GoalSpaceSearch to be a little less ugly. --- .../Search/GoalSpaceSearch_LiftedWithKB.cs | 30 ++++-------- .../Search/GoalSpaceSearch_LiftedWithoutKB.cs | 30 ++++-------- .../GoalSpaceSearch_PropositionalWithKB.cs | 30 ++++-------- .../GoalSpaceSearch_PropositionalWithoutKB.cs | 30 ++++-------- .../Planning/PlannerBenchmarks.cs | 20 ++++---- .../wwwroot/md/getting-started.md | 26 +++++------ .../wwwroot/md/library-overview.md | 2 +- .../Planning/Search/GoalSpaceSearchTests.cs | 27 ++++++----- .../Heuristics/GoalInvariantCheckTests.cs | 5 +- .../IgnorePreconditionsGreedySetCoverTests.cs | 2 +- .../Heuristics/PlanningGraphLevelSumTests.cs | 2 +- .../Heuristics/PlanningGraphMaxLevelTests.cs | 2 +- .../Heuristics/PlanningGraphSetLevelTests.cs | 2 +- .../Planning/Search/StateSpaceSearchTests.cs | 6 +-- .../Planning/Search/GoalSpaceSearch.cs | 34 +++++--------- .../Planning/Search/IHeuristic.cs | 31 ------------- .../Planning/Search/IStrategy.cs | 46 +++++++++++++++++++ .../Planning/Search/StateSpaceSearch.cs | 34 +++++--------- .../DelegateStrategy.cs} | 21 ++++++--- .../GoalInvariantCheck.cs | 25 +++++----- .../IgnorePreconditionsGreedySetCover.cs | 22 ++++----- .../PlanningGraphLevelSum.cs | 10 ++-- .../PlanningGraphMaxLevel.cs | 10 ++-- .../PlanningGraphSetLevel.cs | 14 ++++-- .../UnsatisfiedGoalCount.cs | 15 ++++-- 25 files changed, 222 insertions(+), 254 deletions(-) delete mode 100644 src/SCClassicalPlanning/Planning/Search/IHeuristic.cs create mode 100644 src/SCClassicalPlanning/Planning/Search/IStrategy.cs rename src/SCClassicalPlanning/Planning/Search/{Heuristics/DelegateHeuristic.cs => Strategies/DelegateStrategy.cs} (53%) rename src/SCClassicalPlanning/Planning/Search/{Heuristics => Strategies}/GoalInvariantCheck.cs (78%) rename src/SCClassicalPlanning/Planning/Search/{Heuristics => Strategies}/IgnorePreconditionsGreedySetCover.cs (91%) rename src/SCClassicalPlanning/Planning/Search/{Heuristics => Strategies}/PlanningGraphLevelSum.cs (84%) rename src/SCClassicalPlanning/Planning/Search/{Heuristics => Strategies}/PlanningGraphMaxLevel.cs (84%) rename src/SCClassicalPlanning/Planning/Search/{Heuristics => Strategies}/PlanningGraphSetLevel.cs (78%) rename src/SCClassicalPlanning/Planning/Search/{Heuristics => Strategies}/UnsatisfiedGoalCount.cs (72%) diff --git a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithKB.cs b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithKB.cs index a08f297..4e2b138 100644 --- a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithKB.cs +++ b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithKB.cs @@ -25,28 +25,16 @@ namespace SCClassicalPlanning.Planning.Search /// public class GoalSpaceSearch_LiftedWithKB : IPlanner { - private readonly IHeuristic heuristic; + private readonly IStrategy strategy; private readonly InvariantInspector? invariantInspector; - private readonly Func getActionCost; /// - /// Initializes a new instance of the class that attempts to minimise the number of actions in the resulting plan. + /// Initializes a new instance of the class. /// - /// The heuristic to use - the returned cost will be interpreted as the estimated number of actions that need to be performed. - public GoalSpaceSearch_LiftedWithKB(IHeuristic heuristic, IKnowledgeBase? invariantsKB = null) - : this(heuristic, a => 1f, invariantsKB) + /// The strategy to use. + public GoalSpaceSearch_LiftedWithKB(IStrategy strategy, IKnowledgeBase? invariantsKB = null) { - } - - /// - /// Initializes a new instance of the class that attempts to minimise the total "cost" of actions in the resulting plan. - /// - /// The heuristic to use - with the returned cost will be interpreted as the estimated total cost of the actions that need to be performed. - /// A delegate to retrieve the cost of an action. - public GoalSpaceSearch_LiftedWithKB(IHeuristic heuristic, Func getActionCost, IKnowledgeBase? invariantsKB = null) - { - this.heuristic = heuristic; - this.getActionCost = getActionCost; + this.strategy = strategy; this.invariantInspector = invariantsKB != null ? new InvariantInspector(invariantsKB) : null; } @@ -55,7 +43,7 @@ public GoalSpaceSearch_LiftedWithKB(IHeuristic heuristic, Func ge /// /// The problem to create a plan for. /// - public PlanningTask CreatePlanningTask(Problem problem) => new(problem, heuristic, getActionCost, invariantInspector); + public PlanningTask CreatePlanningTask(Problem problem) => new(problem, strategy, invariantInspector); /// IPlanningTask IPlanner.CreatePlanningTask(Problem problem) => CreatePlanningTask(problem); @@ -70,7 +58,7 @@ public class PlanningTask : SteppablePlanningTask<(Goal, Action, Goal)> private bool isComplete; private Plan? result; - internal PlanningTask(Problem problem, IHeuristic heuristic, Func getActionCost, InvariantInspector? invariantInspector) + internal PlanningTask(Problem problem, IStrategy strategy, InvariantInspector? invariantInspector) { Domain = problem.Domain; InvariantInspector = invariantInspector; @@ -78,8 +66,8 @@ internal PlanningTask(Problem problem, IHeuristic heuristic, Func search = new AStarSearch( source: new GoalSpaceNode(this, problem.Goal), isTarget: n => problem.InitialState.GetSatisfyingSubstitutions(n.Goal).Any(), - getEdgeCost: e => getActionCost(e.Action), - getEstimatedCostToTarget: n => heuristic.EstimateCost(problem.InitialState, n.Goal)); + getEdgeCost: e => strategy.GetCost(e.Action), + getEstimatedCostToTarget: n => strategy.EstimateCost(problem.InitialState, n.Goal)); CheckForSearchCompletion(); } diff --git a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithoutKB.cs b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithoutKB.cs index 8ccffd0..c3e615d 100644 --- a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithoutKB.cs +++ b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_LiftedWithoutKB.cs @@ -26,27 +26,15 @@ namespace SCClassicalPlanning.Planning.Search /// public class GoalSpaceSearch_LiftedWithoutKB : IPlanner { - private readonly IHeuristic heuristic; - private readonly Func getActionCost; + private readonly IStrategy strategy; /// - /// Initializes a new instance of the class that attempts to minimise the number of actions in the resulting plan. + /// Initializes a new instance of the class. /// - /// The heuristic to use - the returned cost will be interpreted as the estimated number of actions that need to be performed. - public GoalSpaceSearch_LiftedWithoutKB(IHeuristic heuristic) - : this(heuristic, a => 1f) + /// The strategy to use. + public GoalSpaceSearch_LiftedWithoutKB(IStrategy strategy) { - } - - /// - /// Initializes a new instance of the class that attempts to minimise the total "cost" of actions in the resulting plan. - /// - /// The heuristic to use - with the returned cost will be interpreted as the estimated total cost of the actions that need to be performed. - /// A delegate to retrieve the cost of an action. - public GoalSpaceSearch_LiftedWithoutKB(IHeuristic heuristic, Func getActionCost) - { - this.heuristic = heuristic; - this.getActionCost = getActionCost; + this.strategy = strategy; } /// @@ -54,7 +42,7 @@ public GoalSpaceSearch_LiftedWithoutKB(IHeuristic heuristic, Func /// /// The problem to create a plan for. /// - public PlanningTask CreatePlanningTask(Problem problem) => new(problem, heuristic, getActionCost); + public PlanningTask CreatePlanningTask(Problem problem) => new(problem, strategy); /// IPlanningTask IPlanner.CreatePlanningTask(Problem problem) => CreatePlanningTask(problem); @@ -69,13 +57,13 @@ public class PlanningTask : SteppablePlanningTask<(Goal, Action, Goal)> private bool isComplete; private Plan? result; - internal PlanningTask(Problem problem, IHeuristic heuristic, Func getActionCost) + internal PlanningTask(Problem problem, IStrategy strategy) { search = new AStarSearch( source: new GoalSpaceNode(problem.Domain, problem.Goal), isTarget: n => problem.InitialState.GetSatisfyingSubstitutions(n.Goal).Any(), - getEdgeCost: e => getActionCost(e.Action), - getEstimatedCostToTarget: n => heuristic.EstimateCost(problem.InitialState, n.Goal)); + getEdgeCost: e => strategy.GetCost(e.Action), + getEstimatedCostToTarget: n => strategy.EstimateCost(problem.InitialState, n.Goal)); CheckForSearchCompletion(); } diff --git a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithKB.cs b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithKB.cs index e51ab51..5686430 100644 --- a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithKB.cs +++ b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithKB.cs @@ -27,28 +27,16 @@ namespace SCClassicalPlanning.Planning.Search /// public class GoalSpaceSearch_PropositionalWithKB : IPlanner { - private readonly IHeuristic heuristic; + private readonly IStrategy strategy; private readonly InvariantInspector? invariantInspector; - private readonly Func getActionCost; /// - /// Initializes a new instance of the class that attempts to minimise the number of actions in the resulting plan. + /// Initializes a new instance of the class. /// - /// The heuristic to use - the returned cost will be interpreted as the estimated number of actions that need to be performed. - public GoalSpaceSearch_PropositionalWithKB(IHeuristic heuristic, IKnowledgeBase? invariantsKB = null) - : this(heuristic, a => 1f, invariantsKB) + /// The strategy to use. + public GoalSpaceSearch_PropositionalWithKB(IStrategy strategy, IKnowledgeBase? invariantsKB = null) { - } - - /// - /// Initializes a new instance of the class that attempts to minimise the total "cost" of actions in the resulting plan. - /// - /// The heuristic to use - with the returned cost will be interpreted as the estimated total cost of the actions that need to be performed. - /// A delegate to retrieve the cost of an action. - public GoalSpaceSearch_PropositionalWithKB(IHeuristic heuristic, Func getActionCost, IKnowledgeBase? invariantsKB = null) - { - this.heuristic = heuristic; - this.getActionCost = getActionCost; + this.strategy = strategy; this.invariantInspector = invariantsKB != null ? new InvariantInspector(invariantsKB) : null; } @@ -57,7 +45,7 @@ public GoalSpaceSearch_PropositionalWithKB(IHeuristic heuristic, Func /// The problem to create a plan for. /// - public PlanningTask CreatePlanningTask(Problem problem) => new(problem, heuristic, getActionCost, invariantInspector); + public PlanningTask CreatePlanningTask(Problem problem) => new(problem, strategy, invariantInspector); /// IPlanningTask IPlanner.CreatePlanningTask(Problem problem) => CreatePlanningTask(problem); @@ -72,7 +60,7 @@ public class PlanningTask : SteppablePlanningTask<(Goal, Action, Goal)> private bool isComplete; private Plan? result; - internal PlanningTask(Problem problem, IHeuristic heuristic, Func getActionCost, InvariantInspector? invariantInspector) + internal PlanningTask(Problem problem, IStrategy strategy, InvariantInspector? invariantInspector) { Problem = problem; InvariantInspector = invariantInspector; @@ -80,8 +68,8 @@ internal PlanningTask(Problem problem, IHeuristic heuristic, Func search = new AStarSearch( source: new GoalSpaceNode(this, problem.Goal), isTarget: n => n.Goal.IsSatisfiedBy(problem.InitialState), - getEdgeCost: e => getActionCost(e.Action), - getEstimatedCostToTarget: n => heuristic.EstimateCost(problem.InitialState, n.Goal)); + getEdgeCost: e => strategy.GetCost(e.Action), + getEstimatedCostToTarget: n => strategy.EstimateCost(problem.InitialState, n.Goal)); CheckForSearchCompletion(); } diff --git a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithoutKB.cs b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithoutKB.cs index 42c0126..1c0358d 100644 --- a/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithoutKB.cs +++ b/src/SCClassicalPlanning.Alternatives/Planning/Search/GoalSpaceSearch_PropositionalWithoutKB.cs @@ -29,27 +29,15 @@ namespace SCClassicalPlanning.Planning.Search /// public class GoalSpaceSearch_PropositionalWithoutKB : IPlanner { - private readonly IHeuristic heuristic; - private readonly Func getActionCost; - - /// - /// Initializes a new instance of the class that attempts to minimise the number of actions in the resulting plan. - /// - /// The heuristic to use - the returned cost will be interpreted as the estimated number of actions that need to be performed. - public GoalSpaceSearch_PropositionalWithoutKB(IHeuristic heuristic) - : this(heuristic, a => 1f) - { - } + private readonly IStrategy strategy; /// - /// Initializes a new instance of the class that attempts to minimise the total "cost" of actions in the resulting plan. + /// Initializes a new instance of the class. /// - /// The heuristic to use - with the returned cost will be interpreted as the estimated total cost of the actions that need to be performed. - /// A delegate to retrieve the cost of an action. - public GoalSpaceSearch_PropositionalWithoutKB(IHeuristic heuristic, Func getActionCost) + /// The strategy to use. + public GoalSpaceSearch_PropositionalWithoutKB(IStrategy strategy) { - this.heuristic = heuristic; - this.getActionCost = getActionCost; + this.strategy = strategy; } /// @@ -57,7 +45,7 @@ public GoalSpaceSearch_PropositionalWithoutKB(IHeuristic heuristic, Func /// The problem to create a plan for. /// - public PlanningTask CreatePlanningTask(Problem problem) => new(problem, heuristic, getActionCost); + public PlanningTask CreatePlanningTask(Problem problem) => new(problem, strategy); /// IPlanningTask IPlanner.CreatePlanningTask(Problem problem) => CreatePlanningTask(problem); @@ -72,13 +60,13 @@ public class PlanningTask : SteppablePlanningTask<(Goal, Action, Goal)> private bool isComplete; private Plan? result; - internal PlanningTask(Problem problem, IHeuristic heuristic, Func getActionCost) + internal PlanningTask(Problem problem, IStrategy strategy) { search = new AStarSearch( source: new GoalSpaceNode(problem, problem.Goal), isTarget: n => n.Goal.IsSatisfiedBy(problem.InitialState), - getEdgeCost: e => getActionCost(e.Action), - getEstimatedCostToTarget: n => heuristic.EstimateCost(problem.InitialState, n.Goal)); + getEdgeCost: e => strategy.GetCost(e.Action), + getEstimatedCostToTarget: n => strategy.EstimateCost(problem.InitialState, n.Goal)); CheckForSearchCompletion(); } diff --git a/src/SCClassicalPlanning.Benchmarks/Planning/PlannerBenchmarks.cs b/src/SCClassicalPlanning.Benchmarks/Planning/PlannerBenchmarks.cs index fb6fa28..02cc384 100644 --- a/src/SCClassicalPlanning.Benchmarks/Planning/PlannerBenchmarks.cs +++ b/src/SCClassicalPlanning.Benchmarks/Planning/PlannerBenchmarks.cs @@ -2,12 +2,12 @@ using SCClassicalPlanning.ExampleDomains.FromAIaMA; using SCClassicalPlanning.Planning; using SCClassicalPlanning.Planning.Search; -using SCClassicalPlanning.Planning.Search.Heuristics; using SCFirstOrderLogic; using SCFirstOrderLogic.Inference; using static SCFirstOrderLogic.SentenceCreation.OperableSentenceFactory; using static SCClassicalPlanning.ExampleDomains.FromAIaMA.BlocksWorld; using SCFirstOrderLogic.Inference.Resolution; +using SCClassicalPlanning.Planning.Search.Strategies; namespace SCClassicalPlanning.Benchmarks.Planning { @@ -15,7 +15,7 @@ namespace SCClassicalPlanning.Benchmarks.Planning [InProcess] public class PlannerBenchmarks { - public record TestCase(string Label, Problem Problem, IHeuristic Heuristic, IKnowledgeBase InvariantsKB) + public record TestCase(string Label, Problem Problem, IStrategy Strategy, IKnowledgeBase InvariantsKB) { public override string ToString() => Label; } @@ -25,13 +25,13 @@ public record TestCase(string Label, Problem Problem, IHeuristic Heuristic, IKno new( Label: "Air Cargo", Problem: AirCargo.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(AirCargo.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(AirCargo.Domain), InvariantsKB: MakeInvariantsKB(Array.Empty())), new( Label: "Blocks - Small", Problem: BlocksWorld.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), InvariantsKB: MakeInvariantsKB(new Sentence[] { // TODO: slicker support for unique names assumption worth looking into at some point.. @@ -53,13 +53,13 @@ public record TestCase(string Label, Problem Problem, IHeuristic Heuristic, IKno new( Label: "Spare Tire", Problem: SpareTire.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(SpareTire.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(SpareTire.Domain), InvariantsKB: MakeInvariantsKB(Array.Empty())), ////new( //// Label: "Blocks - Large", //// Problem: BlocksWorld.LargeExampleProblem, - //// Heuristic: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), + //// Strategy: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), //// InvariantsKB: MakeInvariantsKB(new Sentence[] //// { //// Block(new Constant("blockA")), @@ -102,25 +102,25 @@ public record TestCase(string Label, Problem Problem, IHeuristic Heuristic, IKno [Benchmark] public Plan StateSpaceSearch() { - return new StateSpaceSearch(CurrentTestCase!.Heuristic).CreatePlan(CurrentTestCase.Problem); + return new StateSpaceSearch(CurrentTestCase!.Strategy).CreatePlan(CurrentTestCase.Problem); } [Benchmark] public Plan GoalSpaceSearch() { - return new GoalSpaceSearch(CurrentTestCase!.Heuristic).CreatePlan(CurrentTestCase.Problem); + return new GoalSpaceSearch(CurrentTestCase!.Strategy).CreatePlan(CurrentTestCase.Problem); } [Benchmark] public Plan GoalSpaceSearch_PropositionalWithoutKB() { - return new GoalSpaceSearch_PropositionalWithoutKB(CurrentTestCase!.Heuristic).CreatePlan(CurrentTestCase.Problem); + return new GoalSpaceSearch_PropositionalWithoutKB(CurrentTestCase!.Strategy).CreatePlan(CurrentTestCase.Problem); } [Benchmark] public Plan GoalSpaceSearch_PropositionalWithKB() { - return new GoalSpaceSearch_PropositionalWithKB(CurrentTestCase!.Heuristic, CurrentTestCase.InvariantsKB).CreatePlan(CurrentTestCase.Problem); + return new GoalSpaceSearch_PropositionalWithKB(CurrentTestCase!.Strategy, CurrentTestCase.InvariantsKB).CreatePlan(CurrentTestCase.Problem); } private static IKnowledgeBase MakeInvariantsKB(IEnumerable invariants) diff --git a/src/SCClassicalPlanning.Documentation/wwwroot/md/getting-started.md b/src/SCClassicalPlanning.Documentation/wwwroot/md/getting-started.md index 5319a02..26680e4 100644 --- a/src/SCClassicalPlanning.Documentation/wwwroot/md/getting-started.md +++ b/src/SCClassicalPlanning.Documentation/wwwroot/md/getting-started.md @@ -124,20 +124,17 @@ Once you have a problem, the types in the `SCClassicalPlanning.Planning` namespa ``` using SCClassicalPlanning.Planning; // for PlanFormatter and CreatePlan extension method (plan creation is async by default) using SCClassicalPlanning.Planning.Search; // for StateSpaceSearch -using SCClassicalPlanning.Planning.Search.Heuristics; // for UnsatisfiedGoalCount +using SCClassicalPlanning.Planning.Search.Strategies; // for UnsatisfiedGoalCount -// First instantiate a StateSpaceSearch, specifying a heuristic to use (an object that -// estimates the number of actions it will take to get from a given state to a state that satisfies -// a given goal). You can create a heuristic specific to the domain, or you can use a +// First instantiate a StateSpaceSearch, specifying a strategy to use (an object that gives +// the cost of actions, and estimates the total cost of getting from a given state to a state +// that satisfies a given goal). You can create a strategy specific to the domain, or you can use a // generic one provided by the library. Here we use one provided by the library. -// NB #1: both the state and goal space search classes have constructor overloads -// that also include a parameter for a delegate to compute the "cost" of an action. Potentially useful if -// it makes sense for a custom heuristic to consider some actions more costly than others. -// NB #2: here we use the UnsatisfiedGoalCount heuristic - which just counts the number of elements of the -// goal that are not satisfied in the current state. It's totally useless for goal space searches (because -// it can't rule out unsatisfiable goals) and isn't that great for most state space searches either -// (note it's not admissable, so can give non-optimal plans). However, it suffices for the very simple -// problem we are trying to solve here: +// NB: here we use the UnsatisfiedGoalCount strategy - which (gives all actions a cost of 1 and) just +// counts the number of elements of the goal that are not satisfied in the current state. It's totally +// useless for goal space searches (because it can't rule out unsatisfiable goals) and isn't that great +// for most state space searches either (note it's not admissable, so can give non-optimal plans). +// However, it suffices for the very simple problem we are trying to solve here: var planner = new StateSpaceSearch(new UnsatisfiedGoalCount()); // Tell the planner to create a plan for our problem: @@ -165,10 +162,9 @@ Console.WriteLine($"Goal satisfied: {state.Satisfies(problem.Goal)}!"); ### Using Goal Space Search -As above, but using `var planner = new GoalSpaceSearch(heuristic);`, where heuristic is an `IHeuristic`. +As above, but using `var planner = new GoalSpaceSearch(strategy);`, where strategy is an `IStrategy`. As mentioned above, UnsatisfiedGoalCount is useless for this. Either create one specifically for your -domain, or wait until I implement some more generic ones (working on a precondition-ignoring greedy set cover at the time -of writing). +domain, or wait until I implement some better generic ones. ### Using Graphplan diff --git a/src/SCClassicalPlanning.Documentation/wwwroot/md/library-overview.md b/src/SCClassicalPlanning.Documentation/wwwroot/md/library-overview.md index f87c8da..6c33b76 100644 --- a/src/SCClassicalPlanning.Documentation/wwwroot/md/library-overview.md +++ b/src/SCClassicalPlanning.Documentation/wwwroot/md/library-overview.md @@ -6,7 +6,7 @@ Here is a quick overview of the namespaces found within this library. Reading th * **`Planning`:** intended as the top-level namespace for actual planning algorithms. Directly contains `IPlanner`, an interface for types capable of creating plans to solve problems. * **`GraphPlan`:** Contains an implementation of `IPlanner` that uses the GraphPlan algorithm - as well as some supporting types (notably, a class for planning graphs). * **`Search`:** contains some very simple `IPlanner` implementations that search state- or goal-space to create plans. - * **`Heuristics`:** contains heuristic implementations for use by search planners. + * **`Strategies`:** contains strategy implementations for use by search planners. * **`ProblemCreation`:** contains types that assist with the creation of problems and their constituent elements. * **`ProblemManipulation`:** contains types to assist with the manipulation of problems and their consituent elements. diff --git a/src/SCClassicalPlanning.Tests/Planning/Search/GoalSpaceSearchTests.cs b/src/SCClassicalPlanning.Tests/Planning/Search/GoalSpaceSearchTests.cs index 0f5fbbb..f12136e 100644 --- a/src/SCClassicalPlanning.Tests/Planning/Search/GoalSpaceSearchTests.cs +++ b/src/SCClassicalPlanning.Tests/Planning/Search/GoalSpaceSearchTests.cs @@ -1,11 +1,10 @@ using FluentAssertions; using FlUnit; using SCClassicalPlanning.ExampleDomains.FromAIaMA; -using SCClassicalPlanning.Planning.Search.Heuristics; +using SCClassicalPlanning.Planning.Search.Strategies; using SCFirstOrderLogic; using SCFirstOrderLogic.Inference; using SCFirstOrderLogic.Inference.Resolution; -using System.Numerics; using static SCClassicalPlanning.ExampleDomains.FromAIaMA.AirCargo; using static SCClassicalPlanning.ExampleDomains.FromAIaMA.BlocksWorld; using static SCFirstOrderLogic.SentenceCreation.OperableSentenceFactory; @@ -14,7 +13,7 @@ namespace SCClassicalPlanning.Planning.Search { public static class GoalSpaceSearchTests { - private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase InvariantsKB); + private record TestCase(Problem Problem, IStrategy Strategy, IKnowledgeBase InvariantsKB); public static Test CreatedPlanValidity => TestThat .GivenTestContext() @@ -22,7 +21,7 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In { new( Problem: AirCargo.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(AirCargo.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(AirCargo.Domain), InvariantsKB: MakeInvariantsKB(new Sentence[] { Cargo(new Constant("cargo1")), @@ -41,7 +40,7 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In new( Problem: BlocksWorld.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), InvariantsKB: MakeInvariantsKB(new Sentence[] { Block(new Constant("blockA")), @@ -53,12 +52,12 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In new( Problem: SpareTire.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(SpareTire.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(SpareTire.Domain), InvariantsKB: MakeInvariantsKB(Array.Empty())), new( Problem: BlocksWorld.LargeExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), InvariantsKB: MakeInvariantsKB(new Sentence[] { Block(new Constant("blockA")), @@ -72,7 +71,7 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In }) .When((_, tc) => { - var planner = new GoalSpaceSearch(tc.Heuristic); + var planner = new GoalSpaceSearch(tc.Strategy); return planner.CreatePlanAsync(tc.Problem).GetAwaiter().GetResult(); }) .ThenReturns() @@ -81,7 +80,7 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In public static Test CreatedPlanValidity_AlternativeImplementations => TestThat .GivenTestContext() - .AndEachOf(() => new Func[] + .AndEachOf(() => new Func[] { //(h, kb) => new BackwardStateSpaceSearch_LiftedWithKB(h, kb), // doesnt work yet //(h, kb) => new BackwardStateSpaceSearch_LiftedWithoutKB(h), // doesnt work yet @@ -92,7 +91,7 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In { new( Problem: AirCargo.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(AirCargo.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(AirCargo.Domain), InvariantsKB: MakeInvariantsKB(new Sentence[] { Cargo(new Constant("cargo1")), @@ -111,7 +110,7 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In new( Problem: BlocksWorld.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), InvariantsKB: MakeInvariantsKB(new Sentence[] { Block(new Constant("blockA")), @@ -123,12 +122,12 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In new( Problem: SpareTire.ExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(SpareTire.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(SpareTire.Domain), InvariantsKB: MakeInvariantsKB(Array.Empty())), new( Problem: BlocksWorld.LargeExampleProblem, - Heuristic: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), + Strategy: new IgnorePreconditionsGreedySetCover(BlocksWorld.Domain), InvariantsKB: MakeInvariantsKB(new Sentence[] { Block(new Constant("blockA")), @@ -140,7 +139,7 @@ private record TestCase(Problem Problem, IHeuristic Heuristic, IKnowledgeBase In ForAll(A, B, If(On(A, B), !Clear(B))), })), }) - .When((_, makePlanner, tc) => makePlanner(tc.Heuristic, tc.InvariantsKB).CreatePlan(tc.Problem)) + .When((_, makePlanner, tc) => makePlanner(tc.Strategy, tc.InvariantsKB).CreatePlan(tc.Problem)) .ThenReturns() .And((_, _, tc, pl) => pl.ApplyTo(tc.Problem.InitialState).Satisfies(tc.Problem.Goal).Should().BeTrue()) .And((cxt, _, tc, pl) => cxt.WriteOutputLine(new PlanFormatter(tc.Problem.Domain).Format(pl))); diff --git a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/GoalInvariantCheckTests.cs b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/GoalInvariantCheckTests.cs index b80822f..ebc42ce 100644 --- a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/GoalInvariantCheckTests.cs +++ b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/GoalInvariantCheckTests.cs @@ -8,7 +8,7 @@ using static SCClassicalPlanning.ProblemCreation.OperableProblemFactory; using static SCFirstOrderLogic.SentenceCreation.OperableSentenceFactory; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { public static class GoalInvariantCheckTests { @@ -58,7 +58,8 @@ private record TestCase(IEnumerable Invariants, OperableState State, O SimpleResolutionKnowledgeBase.PriorityComparisons.None); // No point in unitpreference, 'cos query is *all* unit clauses.. kb.Tell(tc.Invariants); - return new GoalInvariantCheck(kb, new DelegateHeuristic((s, g) => 0)).EstimateCost(tc.State, tc.Goal); + var nullStrategy = new DelegateStrategy(a => 0f, (s, g) => 0f); + return new GoalInvariantCheck(kb, nullStrategy).EstimateCost(tc.State, tc.Goal); }) .ThenReturns() .And((tc, rv) => rv.Should().Be(tc.ExpectedCost)); diff --git a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/IgnorePreconditionsGreedySetCoverTests.cs b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/IgnorePreconditionsGreedySetCoverTests.cs index 214cbe6..1d82dc7 100644 --- a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/IgnorePreconditionsGreedySetCoverTests.cs +++ b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/IgnorePreconditionsGreedySetCoverTests.cs @@ -5,7 +5,7 @@ using static SCClassicalPlanning.ExampleDomains.FromAIaMA.AirCargo; using static SCClassicalPlanning.ProblemCreation.OperableProblemFactory; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { public static class IgnorePreconditionsGreedySetCoverTests { diff --git a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphLevelSumTests.cs b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphLevelSumTests.cs index 6984751..71cceb5 100644 --- a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphLevelSumTests.cs +++ b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphLevelSumTests.cs @@ -2,7 +2,7 @@ using FlUnit; using SCClassicalPlanning.ExampleDomains.FromAIaMA; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { public class PlanningGraphLevelSumTesrs { diff --git a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphMaxLevelTests.cs b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphMaxLevelTests.cs index a53544c..0d2e55e 100644 --- a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphMaxLevelTests.cs +++ b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphMaxLevelTests.cs @@ -2,7 +2,7 @@ using FlUnit; using SCClassicalPlanning.ExampleDomains.FromAIaMA; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { public static class PlanningGraphMaxLevelTests { diff --git a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphSetLevelTests.cs b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphSetLevelTests.cs index 56a343c..e42472a 100644 --- a/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphSetLevelTests.cs +++ b/src/SCClassicalPlanning.Tests/Planning/Search/Heuristics/PlanningGraphSetLevelTests.cs @@ -3,7 +3,7 @@ using SCClassicalPlanning.ExampleDomains.FromAIaMA; using static SCClassicalPlanning.ProblemCreation.OperableProblemFactory; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { public class PlanningGraphSetLevelTests { diff --git a/src/SCClassicalPlanning.Tests/Planning/Search/StateSpaceSearchTests.cs b/src/SCClassicalPlanning.Tests/Planning/Search/StateSpaceSearchTests.cs index f1cea77..a07184b 100644 --- a/src/SCClassicalPlanning.Tests/Planning/Search/StateSpaceSearchTests.cs +++ b/src/SCClassicalPlanning.Tests/Planning/Search/StateSpaceSearchTests.cs @@ -1,7 +1,7 @@ using FluentAssertions; using FlUnit; using SCClassicalPlanning.ExampleDomains.FromAIaMA; -using SCClassicalPlanning.Planning.Search.Heuristics; +using SCClassicalPlanning.Planning.Search.Strategies; namespace SCClassicalPlanning.Planning.Search { @@ -18,8 +18,8 @@ public static class StateSpaceSearchTests }) .When((_, problem) => { - var heuristic = new IgnorePreconditionsGreedySetCover(problem.Domain); - var planner = new StateSpaceSearch(heuristic); + var strategy = new IgnorePreconditionsGreedySetCover(problem.Domain); + var planner = new StateSpaceSearch(strategy); return planner.CreatePlan(problem); }) .ThenReturns() diff --git a/src/SCClassicalPlanning/Planning/Search/GoalSpaceSearch.cs b/src/SCClassicalPlanning/Planning/Search/GoalSpaceSearch.cs index fe6a7cc..2ff0add 100644 --- a/src/SCClassicalPlanning/Planning/Search/GoalSpaceSearch.cs +++ b/src/SCClassicalPlanning/Planning/Search/GoalSpaceSearch.cs @@ -24,35 +24,23 @@ namespace SCClassicalPlanning.Planning.Search /// public class GoalSpaceSearch : IPlanner { - private readonly IHeuristic heuristic; - private readonly Func getActionCost; - - /// - /// Initializes a new instance of the class that attempts to minimise the number of actions in the resulting plan. - /// - /// The heuristic to use - the returned cost will be interpreted as the estimated number of actions that need to be performed. - public GoalSpaceSearch(IHeuristic heuristic) - : this(heuristic, a => 1f) - { - } + private readonly IStrategy strategy; /// - /// Initializes a new instance of the class that attempts to minimise the total "cost" of actions in the resulting plan. + /// Initializes a new instance of the class. /// - /// The heuristic to use - with the returned cost will be interpreted as the estimated total cost of the actions that need to be performed. - /// A delegate to retrieve the cost of an action. - public GoalSpaceSearch(IHeuristic heuristic, Func getActionCost) - { - this.heuristic = heuristic; - this.getActionCost = getActionCost; - } + /// + /// The strategy to use - which provides the cost of actions as well as estimates of the total cost of + /// getting from a given state, to a state that satisfies a given goal. + /// + public GoalSpaceSearch(IStrategy strategy) => this.strategy = strategy; /// /// Creates a (concretely-typed) planning task to work on solving a given problem. /// /// The problem to create a plan for. /// A new instance. - public PlanningTask CreatePlanningTask(Problem problem) => new(problem, heuristic, getActionCost); + public PlanningTask CreatePlanningTask(Problem problem) => new(problem, strategy); /// IPlanningTask IPlanner.CreatePlanningTask(Problem problem) => CreatePlanningTask(problem); @@ -67,13 +55,13 @@ public class PlanningTask : SteppablePlanningTask<(Goal, Action, Goal)> private bool isComplete; private Plan? result; - internal PlanningTask(Problem problem, IHeuristic heuristic, Func getActionCost) + internal PlanningTask(Problem problem, IStrategy strategy) { search = new AStarSearch( source: new GoalSpaceNode(problem, problem.Goal), isTarget: n => problem.InitialState.Satisfies(n.Goal), - getEdgeCost: e => getActionCost(e.Action), - getEstimatedCostToTarget: n => heuristic.EstimateCost(problem.InitialState, n.Goal)); + getEdgeCost: e => strategy.GetCost(e.Action), + getEstimatedCostToTarget: n => strategy.EstimateCost(problem.InitialState, n.Goal)); CheckForSearchCompletion(); } diff --git a/src/SCClassicalPlanning/Planning/Search/IHeuristic.cs b/src/SCClassicalPlanning/Planning/Search/IHeuristic.cs deleted file mode 100644 index 7833a5d..0000000 --- a/src/SCClassicalPlanning/Planning/Search/IHeuristic.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 Simon Condon -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -namespace SCClassicalPlanning.Planning.Search -{ - /// - /// Interface for state/goal space search heuristic implementations. That is, types that can estimate the - /// "cost" (whatever that means in the context of the problem being solved) of getting from a given - /// state to a state that satisfies a given goal. - /// - public interface IHeuristic - { - /// - /// Estimates the cost of getting from a given state to a state that satisfies a given goal. - /// - /// The start state. - /// The goal to be satisfied. - /// The estimated cost. - float EstimateCost(State state, Goal goal); - } -} diff --git a/src/SCClassicalPlanning/Planning/Search/IStrategy.cs b/src/SCClassicalPlanning/Planning/Search/IStrategy.cs new file mode 100644 index 0000000..d0cf044 --- /dev/null +++ b/src/SCClassicalPlanning/Planning/Search/IStrategy.cs @@ -0,0 +1,46 @@ +// Copyright 2022 Simon Condon +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using SCGraphTheory.Search.Classic; + +namespace SCClassicalPlanning.Planning.Search +{ + /// + /// Interface for state/goal space search strategy implementations. That is, types that provide + /// the "cost" (whatever that means in the context of the problem being solved) of any given action, + /// and can also estimate the total cost of getting from a given state to a state that satisfies a given goal. + /// + /// + /// NB: Instead of looking like this, this interface *could* have a single method that returns an + /// . More flexible but less easy to use (and would require + /// our search graph types to be public).. Just worth bearing in mind should greater flexibility + /// be required in the future. + /// + public interface IStrategy + { + /// + /// Gets the cost of a given action. + /// + /// The action to get the cost of. + /// The cost of the given action. + float GetCost(Action action); + + /// + /// Estimates the total cost of getting from a given state to a state that satisfies a given goal. + /// + /// The start state. + /// The goal to be satisfied. + /// The estimated cost. + float EstimateCost(State state, Goal goal); + } +} diff --git a/src/SCClassicalPlanning/Planning/Search/StateSpaceSearch.cs b/src/SCClassicalPlanning/Planning/Search/StateSpaceSearch.cs index 0ddddd3..4294ac3 100644 --- a/src/SCClassicalPlanning/Planning/Search/StateSpaceSearch.cs +++ b/src/SCClassicalPlanning/Planning/Search/StateSpaceSearch.cs @@ -26,35 +26,23 @@ namespace SCClassicalPlanning.Planning.Search /// public class StateSpaceSearch : IPlanner { - private readonly IHeuristic heuristic; - private readonly Func getActionCost; + private readonly IStrategy strategy; /// - /// Initializes a new instance of the class that attempts to minimise the number of actions in the resulting plan. + /// Initializes a new instance of the class. /// - /// The heuristic to use - with the "cost" being interpreted as the estimated number of actions that need to be performed. - public StateSpaceSearch(IHeuristic heuristic) - : this(heuristic, e => 1f) - { - } - - /// - /// Initializes a new instance of the class that attempts to minimise the total "cost" of actions in the resulting plan. - /// - /// A delegate to retrieve the cost of an action. - /// The heuristic to use - with the "cost" being interpreted as the estimated total cost of the actions that need to be performed. - public StateSpaceSearch(IHeuristic heuristic, Func getActionCost) - { - this.heuristic = heuristic; - this.getActionCost = getActionCost; - } + /// + /// The strategy to use - which provides the cost of actions as well as estimates of the total cost of + /// getting from a given state, to a state that satisfies a given goal. + /// + public StateSpaceSearch(IStrategy strategy) => this.strategy = strategy; /// /// Creates a (concretely-typed) planning task to work on solving a given problem. /// /// The problem to create a plan for. /// A new instance. - public PlanningTask CreatePlanningTask(Problem problem) => new(problem, heuristic, getActionCost); + public PlanningTask CreatePlanningTask(Problem problem) => new(problem, strategy); /// IPlanningTask IPlanner.CreatePlanningTask(Problem problem) => CreatePlanningTask(problem); @@ -69,13 +57,13 @@ public class PlanningTask : SteppablePlanningTask<(State, Action, State)> private bool isComplete; private Plan? result; - internal PlanningTask(Problem problem, IHeuristic heuristic, Func getActionCost) + internal PlanningTask(Problem problem, IStrategy strategy) { search = new AStarSearch( source: new StateSpaceNode(problem, problem.InitialState), isTarget: n => n.State.Satisfies(problem.Goal), - getEdgeCost: e => getActionCost(e.Action), - getEstimatedCostToTarget: n => heuristic.EstimateCost(n.State, problem.Goal)); + getEdgeCost: e => strategy.GetCost(e.Action), + getEstimatedCostToTarget: n => strategy.EstimateCost(n.State, problem.Goal)); CheckForSearchCompletion(); } diff --git a/src/SCClassicalPlanning/Planning/Search/Heuristics/DelegateHeuristic.cs b/src/SCClassicalPlanning/Planning/Search/Strategies/DelegateStrategy.cs similarity index 53% rename from src/SCClassicalPlanning/Planning/Search/Heuristics/DelegateHeuristic.cs rename to src/SCClassicalPlanning/Planning/Search/Strategies/DelegateStrategy.cs index ad69b41..5ff3eee 100644 --- a/src/SCClassicalPlanning/Planning/Search/Heuristics/DelegateHeuristic.cs +++ b/src/SCClassicalPlanning/Planning/Search/Strategies/DelegateStrategy.cs @@ -11,20 +11,29 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { /// - /// Heuristic implementation that just invokes a given delegate. + /// Implementation of that just invokes given delegates. /// - public class DelegateHeuristic : IHeuristic + public class DelegateStrategy : IStrategy { + private readonly Func getCost; private readonly Func estimateCost; /// - /// Initialises a new instance of the class. + /// Initialises a new instance of the class. /// - /// The delegate to invoke. - public DelegateHeuristic(Func estimateCost) => this.estimateCost = estimateCost; + /// The delegate to invoke to get the cost of an action. + /// The delegate to invoke to estimate the total cost of getting from a given state to a state that satisfies a given goal. + public DelegateStrategy(Func getCost, Func estimateCost) + { + this.getCost = getCost; + this.estimateCost = estimateCost; + } + + /// + public float GetCost(Action action) => getCost(action); /// public float EstimateCost(State state, Goal goal) => estimateCost(state, goal); diff --git a/src/SCClassicalPlanning/Planning/Search/Heuristics/GoalInvariantCheck.cs b/src/SCClassicalPlanning/Planning/Search/Strategies/GoalInvariantCheck.cs similarity index 78% rename from src/SCClassicalPlanning/Planning/Search/Heuristics/GoalInvariantCheck.cs rename to src/SCClassicalPlanning/Planning/Search/Strategies/GoalInvariantCheck.cs index 1b56fb9..980bce4 100644 --- a/src/SCClassicalPlanning/Planning/Search/Heuristics/GoalInvariantCheck.cs +++ b/src/SCClassicalPlanning/Planning/Search/Strategies/GoalInvariantCheck.cs @@ -14,14 +14,14 @@ using SCClassicalPlanning.Planning.Utilities; using SCFirstOrderLogic.Inference; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { /// - /// A decorator heuristic that checks whether the goal violates any known invariants - /// before invoking the inner heuristic. If any invariants are violated, returns . + /// A decorator strategy that checks whether the goal violates any known invariants + /// before invoking the inner strategy. If any invariants are violated, returns . /// Intended to be of use for early pruning of unreachable goals when backward searching. /// - /// NB #1: This heuristic isn't driven by any particular source material, but given that it's a fairly + /// NB #1: This strategy isn't driven by any particular source material, but given that it's a fairly /// obvious idea, there could well be some terminology that I'm not using - I may rename/refactor it as and when. /// /// NB #2: Checking invariants obviously comes at a performance cost (though fact that goals consist only of unit @@ -40,28 +40,31 @@ namespace SCClassicalPlanning.Planning.Search.Heuristics /// are entailed by the invariants (e.g. IsBlock(BlockA)) - as we don't need to worry about things that /// will always be true. /// - public class GoalInvariantCheck : IHeuristic + public class GoalInvariantCheck : IStrategy { private readonly InvariantInspector invariantInspector; - private readonly IHeuristic innerHeuristic; + private readonly IStrategy innerStrategy; /// /// Initializes a new instance of the . /// /// A knowledge base containing all of the invariants of the problem. - /// The inner heuristic to invoke if no invariants are violated by the goal. - public GoalInvariantCheck(IKnowledgeBase invariantsKB, IHeuristic innerHeuristic) + /// The inner strategy to invoke if no invariants are violated by the goal. + public GoalInvariantCheck(IKnowledgeBase invariantsKB, IStrategy innerStrategy) { this.invariantInspector = new InvariantInspector(invariantsKB); - this.innerHeuristic = innerHeuristic; + this.innerStrategy = innerStrategy; } + /// + public float GetCost(Action action) => innerStrategy.GetCost(action); + /// /// Estimates the cost of getting from the given state to a state that satisfies the given goal. /// /// The state. /// The goal. - /// if any invariants are violated by the goal. Otherwise, the cost estimated by the inner heuristic. + /// if any invariants are violated by the goal. Otherwise, the cost estimated by the inner strategy. public float EstimateCost(State state, Goal goal) { if (invariantInspector.IsGoalPrecludedByInvariants(goal)) @@ -69,7 +72,7 @@ public float EstimateCost(State state, Goal goal) return float.PositiveInfinity; } - return innerHeuristic.EstimateCost(state, goal); + return innerStrategy.EstimateCost(state, goal); } } } diff --git a/src/SCClassicalPlanning/Planning/Search/Heuristics/IgnorePreconditionsGreedySetCover.cs b/src/SCClassicalPlanning/Planning/Search/Strategies/IgnorePreconditionsGreedySetCover.cs similarity index 91% rename from src/SCClassicalPlanning/Planning/Search/Heuristics/IgnorePreconditionsGreedySetCover.cs rename to src/SCClassicalPlanning/Planning/Search/Strategies/IgnorePreconditionsGreedySetCover.cs index cc46193..6b03b9d 100644 --- a/src/SCClassicalPlanning/Planning/Search/Heuristics/IgnorePreconditionsGreedySetCover.cs +++ b/src/SCClassicalPlanning/Planning/Search/Strategies/IgnorePreconditionsGreedySetCover.cs @@ -17,17 +17,17 @@ using SCFirstOrderLogic.SentenceManipulation.Unification; using System.Diagnostics; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { /// - /// State space search heuristic that ignores preconditions and uses a greedy set cover algorithm - /// to provide its estimate. + /// State space search strategy that (gives all actions a cost of 1 and) ignores preconditions + /// and uses a greedy set cover algorithm to provide cost estimates. /// /// Not "admissable" (mostly because greedy set cover can overestimate) - /// so the plans discovered using it won't necessarily be optimal, but better than heuristics /// that don't examine the available actions at all.. /// - public class IgnorePreconditionsGreedySetCover : IHeuristic + public class IgnorePreconditionsGreedySetCover : IStrategy { private readonly Domain domain; @@ -37,12 +37,10 @@ public class IgnorePreconditionsGreedySetCover : IHeuristic /// The domain of the problem being solved. public IgnorePreconditionsGreedySetCover(Domain domain) => this.domain = domain; - /// - /// Estimates the cost of getting from the given state to a state that satisfies the given goal. - /// - /// The state. - /// The goal. - /// An estimate of the cost of getting from the given state to a state that satisfies the given goal. + /// + public float GetCost(Action action) => 1f; + + /// public float EstimateCost(State state, Goal goal) { var unsatisfiedGoalElements = GetUnsatisfiedGoalElements(state, goal); @@ -158,7 +156,9 @@ private static int GetCoveringActionCount(IEnumerable target, IEnumerab } } - return coveringActionCount; + // NB: We *could* allow non-unitary action costs (either by delegate or otherwise), + // and return the sum. But don't bother at least for now. + return coveringActionCount; } /// diff --git a/src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphLevelSum.cs b/src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphLevelSum.cs similarity index 84% rename from src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphLevelSum.cs rename to src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphLevelSum.cs index 989649f..315c21a 100644 --- a/src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphLevelSum.cs +++ b/src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphLevelSum.cs @@ -13,15 +13,16 @@ // limitations under the License. using SCClassicalPlanning.Planning.GraphPlan; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { /// - /// Heuristic that uses a "max level" planning graph heuristic. + /// Strategy that (gives all actions a cost of 1 and) uses a "max level" planning graph heuristic + /// to provide cost estimates. /// /// To give an estimate, it first constructs a planning graph starting from the current state. /// The cost estimate is the sum of the level costs of all of the goal's elements. /// - public class PlanningGraphLevelSum : IHeuristic + public class PlanningGraphLevelSum : IStrategy { private readonly Domain domain; @@ -31,6 +32,9 @@ public class PlanningGraphLevelSum : IHeuristic /// The relevant domain. public PlanningGraphLevelSum(Domain domain) => this.domain = domain; + /// + public float GetCost(Action action) => 1f; + /// public float EstimateCost(State state, Goal goal) { diff --git a/src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphMaxLevel.cs b/src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphMaxLevel.cs similarity index 84% rename from src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphMaxLevel.cs rename to src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphMaxLevel.cs index c80ccf0..415fd62 100644 --- a/src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphMaxLevel.cs +++ b/src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphMaxLevel.cs @@ -13,16 +13,17 @@ // limitations under the License. using SCClassicalPlanning.Planning.GraphPlan; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { /// - /// Heuristic that uses a "max level" planning graph heuristic. + /// Strategy that (gives all actions a cost of 1 and) uses a "max level" planning graph heuristic + /// to provide cost estimates. /// /// To give an estimate, it first constructs a planning graph (yup, this is rather expensive..) /// starting from the current state. The cost estimate is the maximum level cost of any of the goal's /// elements. /// - public class PlanningGraphMaxLevel : IHeuristic + public class PlanningGraphMaxLevel : IStrategy { private readonly Domain domain; @@ -32,6 +33,9 @@ public class PlanningGraphMaxLevel : IHeuristic /// The relevant domain. public PlanningGraphMaxLevel(Domain domain) => this.domain = domain; + /// + public float GetCost(Action action) => 1f; + /// public float EstimateCost(State state, Goal goal) { diff --git a/src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphSetLevel.cs b/src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphSetLevel.cs similarity index 78% rename from src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphSetLevel.cs rename to src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphSetLevel.cs index 3315f3a..ca06d9c 100644 --- a/src/SCClassicalPlanning/Planning/Search/Heuristics/PlanningGraphSetLevel.cs +++ b/src/SCClassicalPlanning/Planning/Search/Strategies/PlanningGraphSetLevel.cs @@ -13,16 +13,17 @@ // limitations under the License. using SCClassicalPlanning.Planning.GraphPlan; -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { /// - /// Heuristic that uses a "max level" planning graph heuristic. + /// Strategy that (gives all actions a cost of 1 and) uses a "set level" planning graph heuristic + /// to provide cost estimates. /// /// To give an estimate, it first constructs a planning graph (yup, this is rather expensive..) - /// starting from the current state. The cost estimate is the maximum level cost of any of the goal's - /// elements. + /// starting from the current state. The cost estimate is the level at which each of the goals + /// elements appear and are all not mutually-exclusive with one another. /// - public class PlanningGraphSetLevel : IHeuristic + public class PlanningGraphSetLevel : IStrategy { private readonly Domain domain; @@ -32,6 +33,9 @@ public class PlanningGraphSetLevel : IHeuristic /// The relevant domain. public PlanningGraphSetLevel(Domain domain) => this.domain = domain; + /// + public float GetCost(Action action) => 1f; + /// public float EstimateCost(State state, Goal goal) { diff --git a/src/SCClassicalPlanning/Planning/Search/Heuristics/UnsatisfiedGoalCount.cs b/src/SCClassicalPlanning/Planning/Search/Strategies/UnsatisfiedGoalCount.cs similarity index 72% rename from src/SCClassicalPlanning/Planning/Search/Heuristics/UnsatisfiedGoalCount.cs rename to src/SCClassicalPlanning/Planning/Search/Strategies/UnsatisfiedGoalCount.cs index a0f3381..8105e94 100644 --- a/src/SCClassicalPlanning/Planning/Search/Heuristics/UnsatisfiedGoalCount.cs +++ b/src/SCClassicalPlanning/Planning/Search/Strategies/UnsatisfiedGoalCount.cs @@ -11,20 +11,25 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -namespace SCClassicalPlanning.Planning.Search.Heuristics +namespace SCClassicalPlanning.Planning.Search.Strategies { /// - /// Very simplistic heuristic that just counts the number of unsatisfied goals. That is, it adds the number of positive elements - /// of the goal that do not occur in the state to the number of negative elements that do (that is, it assumes that we need to - /// carry out one action per element of the goal that isn't currently satisfied). + /// Very simplistic strategy that just (gives each action a cost of 1 and) counts the number of + /// unsatisfied goals to provide cost estimates. That is, it adds the number of positive elements + /// of the goal that do not occur in the state to the number of negative elements that do (that is, + /// it assumes that we need to carry out one action per element of the goal that isn't currently + /// satisfied). /// /// This is generally a pretty terrible heuristic - as are all heuristics that don't take into account the details of the /// problem being solved. Consider using something else. DEFINITELY use something else for backward state space searches. /// One of the things that this heuristic simply can't do is tell when preconditions are unsatisfiable - which is very bad /// news for a backward state space search, because you rely on being able to do this to prune branches. /// - public class UnsatisfiedGoalCount : IHeuristic + public class UnsatisfiedGoalCount : IStrategy { + /// + public float GetCost(Action action) => 1f; + /// public float EstimateCost(State state, Goal goal) {