Skip to content

Commit

Permalink
A few things:
Browse files Browse the repository at this point in the history
* Renamed InvariantCheck heuristic to GoalInvariantCheck, for clarity
* Some tweaks to (Goal)InvariantCheck
* Re-enabled BackStateSpaceSearchTests
  • Loading branch information
sdcondon committed Oct 9, 2022
1 parent a5011e5 commit d984d0e
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using SCClassicalPlanning.ExampleDomains.FromAIaMA;
using SCClassicalPlanning.Planning.StateSpaceSearch.Heuristics;
using SCFirstOrderLogic;
using SCFirstOrderLogic.Inference;
using SCFirstOrderLogic.Inference.Resolution;
using static SCClassicalPlanning.ExampleDomains.FromAIaMA.AirCargo;
using static SCClassicalPlanning.ExampleDomains.FromAIaMA.BlocksWorld;
using static SCClassicalPlanning.ExampleDomains.FromAIaMA.SpareTire;
using static SCFirstOrderLogic.SentenceCreation.OperableSentenceFactory;

namespace SCClassicalPlanning.Planning.StateSpaceSearch
{
#if false
public static class BackwardStateSpaceSearchTests
{
public static Test AirCargoScenario => TestThat
Expand All @@ -25,6 +27,7 @@ public static class BackwardStateSpaceSearchTests
return new TestCase(
Domain: AirCargo.Domain,
Invariants: Array.Empty<Sentence>(),
InitialState: new(
Cargo(cargo1)
& Cargo(cargo2)
Expand Down Expand Up @@ -55,6 +58,7 @@ public static class BackwardStateSpaceSearchTests
return new TestCase(
Domain: BlocksWorld.Domain,
Invariants: new Sentence[] { ForAll(A, B, If(On(A, B), !Clear(B))), },
InitialState: new(
Block(blockA)
& Equal(blockA, blockA)
Expand Down Expand Up @@ -88,6 +92,7 @@ public static class BackwardStateSpaceSearchTests
return new TestCase(
Domain: BlocksWorld.Domain,
Invariants: new Sentence[] { ForAll(A, B, If(On(A, B), !Clear(B))), },
InitialState: new(
Block(blockA)
& Equal(blockA, blockA)
Expand Down Expand Up @@ -124,6 +129,7 @@ public static class BackwardStateSpaceSearchTests
{
return new TestCase(
Domain: SpareTire.Domain,
Invariants: Array.Empty<Sentence>(),
InitialState: new(
SpareTire.ImplicitState
& IsAt(Flat, Axle)
Expand All @@ -136,16 +142,25 @@ public static class BackwardStateSpaceSearchTests
.And((_, tc, p) => tc.Goal.IsSatisfiedBy(p.ApplyTo(tc.InitialState)).Should().BeTrue())
.And((cxt, _, p) => cxt.WriteOutputLine(new PlanFormatter(SpareTire.Domain).Format(p)));

private record TestCase(Domain Domain, State InitialState, Goal Goal)
private record TestCase(Domain Domain, IEnumerable<Sentence> Invariants, State InitialState, Goal Goal)
{
public Plan Execute()
{
var problem = new Problem(Domain, InitialState, Goal);
var heuristic = new IgnorePreconditionsGreedySetCover(problem);

var invariantKb = new SimpleResolutionKnowledgeBase(
new SimpleClauseStore(),
SimpleResolutionKnowledgeBase.Filters.None,
SimpleResolutionKnowledgeBase.PriorityComparisons.UnitPreference);
invariantKb.Tell(Invariants);

var innerHeuristic = new IgnorePreconditionsGreedySetCover(problem).EstimateCost;
//var innerHeuristic = ElementDifferenceCount.EstimateCost;

var heuristic = new GoalInvariantCheck(invariantKb, innerHeuristic);
var planner = new BackwardStateSpaceSearch(heuristic.EstimateCost);
return planner.CreatePlanAsync(problem).GetAwaiter().GetResult();
}
}
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

namespace SCClassicalPlanning.Planning.StateSpaceSearch
{
public static class InvariantCheckTests
public static class GoalInvariantCheckTests
{
private static readonly Constant blockA = new(nameof(blockA));
private static readonly Constant blockB = new(nameof(blockB));
Expand All @@ -31,7 +31,7 @@ public static class InvariantCheckTests
& Clear(blockB)
& Clear(blockC));

private record TestCase(IEnumerable<Sentence> Invariants, State State, OperableGoal Goal, float ExpectedCost);
private record TestCase(IEnumerable<Sentence> Invariants, OperableState State, OperableGoal Goal, float ExpectedCost);

public static Test EstimateCostBehaviour => TestThat
.GivenTestContext()
Expand All @@ -40,36 +40,42 @@ private record TestCase(IEnumerable<Sentence> Invariants, State State, OperableG
new TestCase(
Invariants: new Sentence[] { Block(blockA), ForAll(A, B, If(On(A, B), !Clear(B))) },
State: BlocksWorldInitialState,
Goal: Block(Table), // Fine - invariants don't rule this out
Goal: Goal.Empty, // Fine
ExpectedCost: 0),
new TestCase(
Invariants: new Sentence[] { Block(blockA), ForAll(A, B, If(On(A, B), !Clear(B))) },
State: BlocksWorldInitialState,
Goal: !Block(blockA), // Contradicts Block(blockA)
Goal: Block(Table), // Fine
ExpectedCost: 0),
new TestCase(
Invariants: new Sentence[] { Block(blockA), ForAll(A, B, If(On(A, B), !Clear(B))) },
State: BlocksWorldInitialState,
Goal: !Block(blockA), // Violates Block(blockA)
ExpectedCost: float.PositiveInfinity),
new TestCase(
Invariants: new Sentence[] { Block(blockA), ForAll(A, B, If(On(A, B), !Clear(B))) },
State: BlocksWorldInitialState,
Goal: On(blockA, blockB) & Clear(blockB), // Nope - violates on/clear relationship
Goal: On(blockA, blockB) & Clear(blockB), // Violates on/clear relationship
ExpectedCost: float.PositiveInfinity),
new TestCase(
Invariants: new Sentence[] { Block(blockA), ForAll(A, B, If(On(A, B), !Clear(B))) },
State: BlocksWorldInitialState,
Goal: On(blockB, blockA) & Clear(blockB), // Fine
ExpectedCost: 0)
ExpectedCost: 0),
})
.When((_, tc) =>
{
var kb = new SimpleResolutionKnowledgeBase(
new SimpleClauseStore(),
SimpleResolutionKnowledgeBase.Filters.None,
SimpleResolutionKnowledgeBase.PriorityComparisons.UnitPreference);
SimpleResolutionKnowledgeBase.PriorityComparisons.None); // No point in unitpref, 'cos query is all unit clauses..
kb.Tell(tc.Invariants);
return new InvariantCheck(kb, (s, g) => 0).EstimateCost(tc.State, tc.Goal);
return new GoalInvariantCheck(kb, (s, g) => 0).EstimateCost(tc.State, tc.Goal);
})
.ThenReturns()
.And((_, tc, rv) => rv.Should().Be(tc.ExpectedCost));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using SCClassicalPlanning.ProblemManipulation;
using SCFirstOrderLogic;
using SCFirstOrderLogic.Inference;
using SCFirstOrderLogic.SentenceManipulation;

namespace SCClassicalPlanning.Planning.StateSpaceSearch.Heuristics
{
/// <summary>
/// A decorator heuristic that checks whether the goal violates any known invariants
/// before invoking the inner heuristic. If any invariants are violated, returns <see cref="float.PositiveInfinity"/>.
/// Intended to be of use for early pruning of unreachable goals when backward searching.
/// <para/>
/// NB #1: This heuristic 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.
/// <para/>
/// NB #2: Checking invariants obviously comes at a performance cost (though fact that goals consist only of unit
/// clauses likely mitigates this quite a lot - because it means that the negation of the query we ask our KB it
/// consists only of unit clauses).
/// The question is whether the benefit it provides outweighs the cost. I do wonder if we can somehow check
/// only the stuff that has changed.
/// <para/>
/// NB #3: Ultimately it should be possible to derive the invariants by examining the problem.
/// The simplest example of this is if a predicate doesn't appear in any effects. If this is true, the
/// the occurences of this predicate in the initial state must persist throughout the problem.
/// Might research / play with this idea at some point.
/// </summary>
public class GoalInvariantCheck
{
private readonly Func<State, Goal, float> innerHeuristic;
private readonly IKnowledgeBase knowledgeBase;

/// <summary>
/// Initializes a new instance of the <see cref="GoalInvariantCheck"/>.
/// </summary>
/// <param name="invariantsKnowledgeBase">A knowledge base containing all of the invariants of the problem.</param>
/// <param name="innerHeuristic">The inner heuristic to invoke if no invariants are violated by the goal.</param>
public GoalInvariantCheck(IKnowledgeBase invariantsKnowledgeBase, Func<State, Goal, float> innerHeuristic)
{
this.innerHeuristic = innerHeuristic;
this.knowledgeBase = invariantsKnowledgeBase;
}

/// <summary>
/// Estimates the cost of getting from the given state to a state that satisfies the given goal.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="goal">The goal.</param>
/// <returns><see cref="float.PositiveInfinity"/> if any invariants are violated by the goal. Otherwise, the cost estimated by the inner heuristic.</returns>
public float EstimateCost(State state, Goal goal)
{
// One would assume that the inner heuristic would return 0 if there are no elements
// in the goal - but its not our business to shortcut that
if (goal.Elements.Count > 0)
{
var variables = new HashSet<VariableDeclaration>();
GoalVariableFinder.Instance.Visit(goal, variables);

// Annoying performance hit - goals are essentially already in CNF, but our knowledge bases want to do the conversion themselves.. Meh, never mind.
// TODO: Perhaps a ToSentence in Goal? (and others..)
var goalSentence = goal.Elements.Skip(1).Aggregate(goal.Elements.First().ToSentence(), (c, e) => new Conjunction(c, e.ToSentence()));

foreach (var variable in variables)
{
goalSentence = new ExistentialQuantification(variable, goalSentence);
}

// Note the negation here. We're not asking if the invariants mean that the goal MUST
// be true (that will of course generally not be the case!), we're asking if the goal
// CANNOT be true - that is, if its NEGATION must be true.
if (knowledgeBase.Ask(new Negation(goalSentence)))
{
return float.PositiveInfinity;
}
}

return innerHeuristic(state, goal);
}

/// <summary>
/// Utility class to find <see cref="Constant"/> instances within the elements of a <see cref="SCClassicalPlanning.Goal"/>, and add them to a given <see cref="HashSet{T}"/>.
/// </summary>
private class GoalVariableFinder : RecursiveGoalVisitor<HashSet<VariableDeclaration>>
{
/// <summary>
/// Gets a singleton instance of the <see cref="GoalVariableFinder"/> class.
/// </summary>
public static GoalVariableFinder Instance { get; } = new();

/// <inheritdoc/>
public override void Visit(VariableDeclaration variable, HashSet<VariableDeclaration> variables) => variables.Add(variable);
}
}
}

This file was deleted.

0 comments on commit d984d0e

Please sign in to comment.