Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a1090c0
Update to include test code, realize issues, and clean up unnecessary…
carwyn987 Sep 25, 2025
f023860
Update, identified location of bug that exists within linearize, but …
carwyn987 Sep 27, 2025
31349ba
Add initial layer interference test, recursive goal testing, and appr…
carwyn987 Sep 27, 2025
e2f48d3
Add action application to verify results
carwyn987 Sep 27, 2025
0fb3902
Add changes, stuff doesn't work
carwyn987 Sep 29, 2025
7a3ad7c
I have made the formulation understandable now. Each level is grouped…
carwyn987 Sep 29, 2025
5234d7b
Cake problem works now. On to debugging shopping problem );
carwyn987 Sep 29, 2025
5fbbc85
If I ever see GraphPlan in a dark alley it's not leaving alive. ... b…
carwyn987 Sep 30, 2025
60d33ea
Now I'm confused about some of the tests. ... tennis problem also fai…
carwyn987 Sep 30, 2025
cb71b64
Improved extract_solution
carwyn987 Sep 30, 2025
256863c
Most tests consistently passing
carwyn987 Oct 1, 2025
204b19b
Working! Needed to update the extract_solution to attempt every power…
carwyn987 Oct 4, 2025
ea6ca75
Cleanup
carwyn987 Oct 4, 2025
c6ce67c
Cleanup and pytest configuration work
carwyn987 Oct 5, 2025
a2265d7
Merge pull request #1 from carwyn987/experimental/revert_ordering_and…
carwyn987 Oct 5, 2025
82e061b
More cleanup, removed unnecessary functions
carwyn987 Oct 5, 2025
4a24a2e
Further cleanup
carwyn987 Oct 5, 2025
497414c
Issue identified. Leveloff condition failing to check for mutex equiv…
carwyn987 Oct 5, 2025
b16e862
Differentiated between state and action mutexes. Fixed leveloff funct…
carwyn987 Oct 5, 2025
dabd9bd
Remove lingering print statement
carwyn987 Oct 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Cleanup and pytest configuration work
  • Loading branch information
carwyn987 committed Oct 5, 2025
commit c6ce67c413a159e51d0d892a708944d0837dc0ee
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ git submodule init
git submodule update
```

Wait for the datasets to download, it may take a while. Once they are downloaded, you need to install `pytest`, so that you can run the test suite:

`pip install pytest`
Wait for the datasets to download, it may take a while.

Then to run the tests:

Expand Down
41 changes: 2 additions & 39 deletions planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,6 @@ def __init__(self, kb, is_first_layer=False):
def __call__(self, actions, objects):
self.build(actions, objects)
self.find_mutex()
#self.deduplify()

def __str__(self):
state_str = ", ".join(str(s) for s in self.current_state)
Expand Down Expand Up @@ -794,7 +793,7 @@ def __str__(self):
__repr__ = __str__

def check_leveloff(self):
"""Checks if the graph has levelled off"""
"""Checks if the graph has leveled off"""

check = (set(self.graph.levels[-1].current_state) == set(self.graph.levels[-2].current_state)) and \
self.graph.levels[-1].mutex == self.graph.levels[-2].mutex
Expand Down Expand Up @@ -866,7 +865,6 @@ def _extract_solution_recursive(self, goals, level_index):
#We must check if they exist in the initial state. No further recursion is needed.

if level_index == 0:
#breakpoint()
initial_state = set(self.graph.levels[0].current_state)
if goals.issubset(initial_state):
return [] # Success! Return the empty plan to be built upon.
Expand Down Expand Up @@ -926,7 +924,7 @@ def execute(self):

class Linearize:
"""

Problem wrapper / coordinator that linearizes partially ordered solutions generated by GraphPlan object.
"""

def __init__(self, planning_problem):
Expand Down Expand Up @@ -1314,41 +1312,6 @@ def execute(self, display=True):
return self.constraints, self.causal_links


def spare_tire_graphPlan():
"""Solves the spare tire problem using GraphPlan"""
return GraphPlan(spare_tire()).execute()


def three_block_tower_graphPlan():
"""Solves the Sussman Anomaly problem using GraphPlan"""
return GraphPlan(three_block_tower()).execute()


def air_cargo_graphPlan():
"""Solves the air cargo problem using GraphPlan"""
return GraphPlan(air_cargo()).execute()


def have_cake_and_eat_cake_too_graphPlan():
"""Solves the cake problem using GraphPlan"""
return [GraphPlan(have_cake_and_eat_cake_too()).execute()[1]]


def shopping_graphPlan():
"""Solves the shopping problem using GraphPlan"""
return GraphPlan(shopping_problem()).execute()


def socks_and_shoes_graphPlan():
"""Solves the socks and shoes problem using GraphPlan"""
return GraphPlan(socks_and_shoes()).execute()


def simple_blocks_world_graphPlan():
"""Solves the simple blocks world problem"""
return GraphPlan(simple_blocks_world()).execute()


class HLA(Action):
"""
Define Actions for the real-world (that may be refined further), and satisfy resource
Expand Down
241 changes: 234 additions & 7 deletions planning_envs.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def spare_tire():
>>> st.goal_test()
False
>>> st.act(expr('PutOn(Spare, Axle)'))
>>> st.goal_test()
>>> st.goal_test(
True
>>>
"""
Expand Down Expand Up @@ -169,6 +169,52 @@ def simple_blocks_world():
Action('FromTable(y, x)',
precond='OnTable(y) & Clear(y) & Clear(x)',
effect='~OnTable(y) & ~Clear(x) & On(y, x)')])


def blocks_world(initial, goals, blocks):
"""
GENERALIZED-BLOCKS-WORLD-PROBLEM

A flexible constructor for creating blocks-world planning problems.
You can specify any initial and goal configuration for a given set of blocks.

Example:
>>> from planning import *
>>> # Let's define the classic Sussman Anomaly
>>> initial_state = 'On(C, A) & On(A, Table) & On(B, Table) & Clear(C) & Clear(B)'
>>> goal_state = 'On(A, B) & On(B, C)'
>>> block_names = ['A', 'B', 'C']
>>> sussman_anomaly = blocks_world(initial_state, goal_state, block_names)
>>>
>>> sussman_anomaly.goal_test()
False
>>> # A sequence of moves to solve it
>>> sussman_anomaly.act(expr('MoveToTable(C, A)'))
>>> sussman_anomaly.act(expr('Move(B, Table, C)'))
>>> sussman_anomaly.act(expr('Move(A, Table, B)'))
>>> sussman_anomaly.goal_test()
True
>>>
"""
# Dynamically generate the domain knowledge based on the list of blocks
domain_knowledge = ' & '.join([f'Block({b})' for b in blocks])

# Define the fundamental actions for moving blocks
actions = [
Action('Move(b, x, y)',
precond='On(b, x) & Clear(b) & Clear(y)',
effect='On(b, y) & Clear(x) & ~On(b, x) & ~Clear(y)',
domain='Block(b) & Block(y)'), # 'x' can be another block or 'Table'
Action('MoveToTable(b, x)',
precond='On(b, x) & Clear(b)',
effect='On(b, Table) & Clear(x) & ~On(b, x)',
domain='Block(b) & Block(x)') # 'x' must be a block
]

return PlanningProblem(initial=initial,
goals=goals,
actions=actions,
domain=domain_knowledge)


def have_cake_and_eat_cake_too():
Expand Down Expand Up @@ -226,19 +272,18 @@ def shopping_problem():
>>>
"""

return PlanningProblem(initial='At(Home) & Sells(SM, Milk) & Sells(HW, Drill)',
goals='Have(Milk) & Have(Drill)',
return PlanningProblem(initial='At(Home) & Sells(SM, Milk) & Sells(SM, Banana) & Sells(HW, Drill)',
goals='Have(Milk) & Have(Banana) & Have(Drill)',
actions=[Action('Buy(x, store)',
precond='At(store) & Sells(store, x)',
effect='Have(x)',
domain='Store(store) & Item(x)'),
Action('Go(x, y)',
precond='At(x)',
effect='At(y) & ~At(x)',
domain='Rplace(x) & Rplace(y)')],
domain='Rplace(Home) & Rplace(SM) & Rplace(HW) & Store(SM) & Store(HW) & '
'Item(Milk) & Item(Drill)')

domain='Place(x) & Place(y)')],
domain='Place(Home) & Place(SM) & Place(HW) & Store(SM) & Store(HW) & '
'Item(Milk) & Item(Banana) & Item(Drill)')

def socks_and_shoes():
"""
Expand Down Expand Up @@ -346,3 +391,185 @@ def double_tennis_problem():
effect='At(actor, to) & ~At(actor, loc)')],
domain="Loc(LeftBaseline)")


def rush_hour():
"""
RUSH-HOUR-PROBLEM (Non-Numeric Version)

A planning problem for the Rush Hour sliding block puzzle. The goal is to
maneuver the RedCar to the exit. This version uses non-numeric symbols for
grid positions (e.g., R1, C1) instead of integers.

This specific instance uses:
- RedCar (2x1, horizontal) starting at (R3, C1)
- GreenTruck (3x1, vertical) starting at (R1, C4)
- BlueCar (2x1, vertical) starting at (R5, C2)
"""
# Initial state: Define vehicle locations and clear spots using non-numeric identifiers.
initial_state = 'At(RedCar, R3, C1) & At(GreenTruck, R1, C4) & At(BlueCar, R5, C2) & ' \
'IsHorizontal(RedCar) & IsVertical(GreenTruck) & IsVertical(BlueCar) & ' \
'Clear(R1, C1) & Clear(R1, C2) & Clear(R1, C3) & Clear(R1, C5) & Clear(R1, C6) & ' \
'Clear(R2, C1) & Clear(R2, C2) & Clear(R2, C3) & Clear(R2, C5) & Clear(R2, C6) & ' \
'Clear(R3, C3) & Clear(R3, C4) & Clear(R3, C5) & Clear(R3, C6) & ' \
'Clear(R4, C1) & Clear(R4, C2) & Clear(R4, C3) & Clear(R4, C4) & Clear(R4, C5) & Clear(R4, C6) & ' \
'Clear(R5, C1) & Clear(R5, C3) & Clear(R5, C4) & Clear(R5, C5) & Clear(R5, C6) & ' \
'Clear(R6, C1) & Clear(R6, C3) & Clear(R6, C4) & Clear(R6, C5) & Clear(R6, C6)'

# Goal state: The RedCar's left-most part is at column C5.
goal_state = 'At(RedCar, R3, C5)'

# Domain: Define objects, types (Row, Col), and adjacency relationships.
domain_knowledge = 'Vehicle(RedCar) & Vehicle(GreenTruck) & Vehicle(BlueCar) & ' \
'Car(RedCar) & Truck(GreenTruck) & Car(BlueCar) & ' \
'Row(R1) & Row(R2) & Row(R3) & Row(R4) & Row(R5) & Row(R6) & ' \
'Col(C1) & Col(C2) & Col(C3) & Col(C4) & Col(C5) & Col(C6) & ' \
'NextTo(R1, R2) & NextTo(R2, R3) & NextTo(R3, R4) & NextTo(R4, R5) & NextTo(R5, R6) & ' \
'NextTo(C1, C2) & NextTo(C2, C3) & NextTo(C3, C4) & NextTo(C4, C5) & NextTo(C5, C6)'

actions = [
# --- CAR ACTIONS (length 2) ---
Action('MoveRightCar(v, r, c1, c2, c3)',
precond='At(v, r, c1) & Car(v) & IsHorizontal(v) & NextTo(c1, c2) & NextTo(c2, c3) & Clear(r, c3)',
effect='At(v, r, c2) & ~At(v, r, c1) & Clear(r, c1) & ~Clear(r, c3)',
domain='Vehicle(v) & Row(r) & Col(c1) & Col(c2) & Col(c3)'),
Action('MoveLeftCar(v, r, c1, c2, c3)',
precond='At(v, r, c2) & Car(v) & IsHorizontal(v) & NextTo(c1, c2) & NextTo(c2, c3) & Clear(r, c1)',
effect='At(v, r, c1) & ~At(v, r, c2) & Clear(r, c3) & ~Clear(r, c1)',
domain='Vehicle(v) & Row(r) & Col(c1) & Col(c2) & Col(c3)'),
Action('MoveDownCar(v, r1, r2, r3, c)',
precond='At(v, r1, c) & Car(v) & IsVertical(v) & NextTo(r1, r2) & NextTo(r2, r3) & Clear(r3, c)',
effect='At(v, r2, c) & ~At(v, r1, c) & Clear(r1, c) & ~Clear(r3, c)',
domain='Vehicle(v) & Row(r1) & Row(r2) & Row(r3) & Col(c)'),
Action('MoveUpCar(v, r1, r2, r3, c)',
precond='At(v, r2, c) & Car(v) & IsVertical(v) & NextTo(r1, r2) & NextTo(r2, r3) & Clear(r1, c)',
effect='At(v, r1, c) & ~At(v, r2, c) & Clear(r3, c) & ~Clear(r1, c)',
domain='Vehicle(v) & Row(r1) & Row(r2) & Row(r3) & Col(c)'),

# --- TRUCK ACTIONS (length 3) ---
Action('MoveRightTruck(v, r, c1, c2, c3, c4)',
precond='At(v, r, c1) & Truck(v) & IsHorizontal(v) & NextTo(c1, c2) & NextTo(c2, c3) & NextTo(c3, c4) & Clear(r, c4)',
effect='At(v, r, c2) & ~At(v, r, c1) & Clear(r, c1) & ~Clear(r, c4)',
domain='Vehicle(v) & Row(r) & Col(c1) & Col(c2) & Col(c3) & Col(c4)'),
Action('MoveLeftTruck(v, r, c1, c2, c3, c4)',
precond='At(v, r, c2) & Truck(v) & IsHorizontal(v) & NextTo(c1, c2) & NextTo(c2, c3) & NextTo(c3, c4) & Clear(r, c1)',
effect='At(v, r, c1) & ~At(v, r, c2) & Clear(r, c4) & ~Clear(r, c1)',
domain='Vehicle(v) & Row(r) & Col(c1) & Col(c2) & Col(c3) & Col(c4)'),
Action('MoveDownTruck(v, r1, r2, r3, r4, c)',
precond='At(v, r1, c) & Truck(v) & IsVertical(v) & NextTo(r1, r2) & NextTo(r2, r3) & NextTo(r3, r4) & Clear(r4, c)',
effect='At(v, r2, c) & ~At(v, r1, c) & Clear(r1, c) & ~Clear(r4, c)',
domain='Vehicle(v) & Row(r1) & Row(r2) & Row(r3) & Row(r4) & Col(c)'),
Action('MoveUpTruck(v, r1, r2, r3, r4, c)',
precond='At(v, r2, c) & Truck(v) & IsVertical(v) & NextTo(r1, r2) & NextTo(r2, r3) & NextTo(r3, r4) & Clear(r1, c)',
effect='At(v, r1, c) & ~At(v, r2, c) & Clear(r4, c) & ~Clear(r1, c)',
domain='Vehicle(v) & Row(r1) & Row(r2) & Row(r3) & Row(r4) & Col(c)')
]

return PlanningProblem(initial=initial_state,
goals=goal_state,
actions=actions,
domain=domain_knowledge)


def rush_hour_optimized():
"""
RUSH-HOUR-PROBLEM (Optimized Version)

This version optimizes the planning problem by creating vehicle-specific
actions. Since each vehicle's orientation is fixed, we can remove generic
predicates like `IsHorizontal` and create actions that only apply to the
correct vehicle on its fixed axis of movement. This drastically reduces
the number of permutations the planner needs to generate and check.
"""
# Initial state is simpler as orientation is now baked into the actions.
initial_state = 'At(RedCar, R3, C1) & At(GreenTruck, R1, C4) & At(BlueCar, R5, C2) & ' \
'Clear(R1, C1) & Clear(R1, C2) & Clear(R1, C3) & Clear(R1, C5) & Clear(R1, C6) & ' \
'Clear(R2, C1) & Clear(R2, C2) & Clear(R2, C3) & Clear(R2, C5) & Clear(R2, C6) & ' \
'Clear(R3, C3) & Clear(R3, C4) & Clear(R3, C5) & Clear(R3, C6) & ' \
'Clear(R4, C1) & Clear(R4, C2) & Clear(R4, C3) & Clear(R4, C4) & Clear(R4, C5) & Clear(R4, C6) & ' \
'Clear(R5, C1) & Clear(R5, C3) & Clear(R5, C4) & Clear(R5, C5) & Clear(R5, C6) & ' \
'Clear(R6, C1) & Clear(R6, C3) & Clear(R6, C4) & Clear(R6, C5) & Clear(R6, C6)'

# Goal state remains the same.
goal_state = 'At(RedCar, R3, C5)'

# Domain knowledge defines the grid and vehicles.
domain_knowledge = 'Vehicle(RedCar) & Vehicle(GreenTruck) & Vehicle(BlueCar) & ' \
'Row(R1) & Row(R2) & Row(R3) & Row(R4) & Row(R5) & Row(R6) & ' \
'Col(C1) & Col(C2) & Col(C3) & Col(C4) & Col(C5) & Col(C6) & ' \
'NextTo(R1, R2) & NextTo(R2, R3) & NextTo(R3, R4) & NextTo(R4, R5) & NextTo(R5, R6) & ' \
'NextTo(C1, C2) & NextTo(C2, C3) & NextTo(C3, C4) & NextTo(C4, C5) & NextTo(C5, C6)'

# Optimized Actions: Specific to each vehicle and its fixed orientation.
actions = [
# RedCar is horizontal on Row 3, length 2
Action('MoveRedCarRight(c1, c2, c3)',
precond='At(RedCar, R3, c1) & NextTo(c1, c2) & NextTo(c2, c3) & Clear(R3, c3)',
effect='At(RedCar, R3, c2) & ~At(RedCar, R3, c1) & Clear(R3, c1) & ~Clear(R3, c3)',
domain='Col(c1) & Col(c2) & Col(c3)'),
Action('MoveRedCarLeft(c1, c2, c3)',
precond='At(RedCar, R3, c2) & NextTo(c1, c2) & NextTo(c2, c3) & Clear(R3, c1)',
effect='At(RedCar, R3, c1) & ~At(RedCar, R3, c2) & Clear(R3, c3) & ~Clear(R3, c1)',
domain='Col(c1) & Col(c2) & Col(c3)'),

# GreenTruck is vertical on Column 4, length 3
Action('MoveGreenTruckDown(r1, r2, r3, r4)',
precond='At(GreenTruck, r1, C4) & NextTo(r1, r2) & NextTo(r2, r3) & NextTo(r3, r4) & Clear(r4, C4)',
effect='At(GreenTruck, r2, C4) & ~At(GreenTruck, r1, C4) & Clear(r1, C4) & ~Clear(r4, C4)',
domain='Row(r1) & Row(r2) & Row(r3) & Row(r4)'),
Action('MoveGreenTruckUp(r1, r2, r3, r4)',
precond='At(GreenTruck, r2, C4) & NextTo(r1, r2) & NextTo(r2, r3) & NextTo(r3, r4) & Clear(r1, C4)',
effect='At(GreenTruck, r1, C4) & ~At(GreenTruck, r2, C4) & Clear(r4, C4) & ~Clear(r1, C4)',
domain='Row(r1) & Row(r2) & Row(r3) & Row(r4)'),

# BlueCar is vertical on Column 2, length 2
Action('MoveBlueCarDown(r1, r2, r3)',
precond='At(BlueCar, r1, C2) & NextTo(r1, r2) & NextTo(r2, r3) & Clear(r3, C2)',
effect='At(BlueCar, r2, C2) & ~At(BlueCar, r1, C2) & Clear(r1, C2) & ~Clear(r3, C2)',
domain='Row(r1) & Row(r2) & Row(r3)'),
Action('MoveBlueCarUp(r1, r2, r3)',
precond='At(BlueCar, r2, C2) & NextTo(r1, r2) & NextTo(r2, r3) & Clear(r1, C2)',
effect='At(BlueCar, r1, C2) & ~At(BlueCar, r2, C2) & Clear(r3, C2) & ~Clear(r1, C2)',
domain='Row(r1) & Row(r2) & Row(r3)'),
]

return PlanningProblem(initial=initial_state,
goals=goal_state,
actions=actions,
domain=domain_knowledge)


#### For pytests

def spare_tire_graphPlan():
"""Solves the spare tire problem using GraphPlan"""
return GraphPlan(spare_tire()).execute()


def three_block_tower_graphPlan():
"""Solves the Sussman Anomaly problem using GraphPlan"""
return GraphPlan(three_block_tower()).execute()


def air_cargo_graphPlan():
"""Solves the air cargo problem using GraphPlan"""
return GraphPlan(air_cargo()).execute()


def have_cake_and_eat_cake_too_graphPlan():
"""Solves the cake problem using GraphPlan"""
return GraphPlan(have_cake_and_eat_cake_too()).execute()


def shopping_graphPlan():
"""Solves the shopping problem using GraphPlan"""
return GraphPlan(shopping_problem()).execute()


def socks_and_shoes_graphPlan():
"""Solves the socks and shoes problem using GraphPlan"""
return GraphPlan(socks_and_shoes()).execute()


def simple_blocks_world_graphPlan():
"""Solves the simple blocks world problem"""
return GraphPlan(simple_blocks_world()).execute()
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ numpy
opencv-python
pandas
pillow
pytest-cov
qpsolvers
scipy
sortedcontainers
tensorflow
tensorflow
pytest
pytest-cov
Loading