CS188 Project 2 Logic and Classical Planning
# logicPlan.py
# ————
# Licensing Information: You are free to use or extend these projects for
# educational purposes provided that (1) you do not distribute or publish
# solutions, (2) you retain this notice, and (3) you provide clear
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
# The core projects and autograders were primarily created by John DeNero
# and Dan Klein
# Student side autograding was added by Brad Miller, Nick Hay, and
# Pieter Abbeel
In logicPlan.py, you will implement logic planning methods which are called by
Pacman agents (in logicAgents.py).
from typing import Dict, List, Tuple, Callable, Generator, Any
import util
import sys
import logic
import game
from logic import conjoin, disjoin
from logic import PropSymbolExpr, Expr, to_cnf, pycoSAT, parseExpr, pl_true
import itertools
import copy
pacman_str = ‘P’
food_str = ‘FOOD’
wall_str = ‘WALL’
pacman_wall_str = pacman_str + wall_str
DIRECTIONS = [‘North’, ‘South’, ‘East’, ‘West’]
blocked_str_map = dict([(direction, (direction + “_blocked”).upper()) for direction in DIRECTIONS])
geq_num_adj_wall_str_map = dict([(num, “GEQ_{}_adj_walls”.format(num)) for num in range(1, 4)])
DIR_TO_DXDY_MAP = {‘North’:(0, 1), ‘South’:(0, -1), ‘East’:(1, 0), ‘West’:(-1, 0)}
#______________________________________________________________________________
# QUESTION 1
def sentence1() -> Expr:
“””Returns a Expr instance that encodes that the following expressions are all true.
(not A) if and only if ((not B) or C)
(not A) or (not B) or C
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def sentence2() -> Expr:
“””Returns a Expr instance that encodes that the following expressions are all true.
C if and only if (B or D)
A implies ((not B) and (not D))
(not (B and (not C))) implies A
(not D) implies C
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def sentence3() -> Expr:
“””Using the symbols PacmanAlive_1 PacmanAlive_0, PacmanBorn_0, and PacmanKilled_0,
created using the PropSymbolExpr constructor, return a PropSymbolExpr
instance that encodes the following English sentences (in this order):
Pacman is alive at time 1 if and only if Pacman was alive at time 0 and it was
not killed at time 0 or it was not alive at time 0 and it was born at time 0.
Pacman cannot both be alive at time 0 and be born at time 0.
Pacman is born at time 0.
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def findModel(sentence: Expr) -> Dict[Expr, bool]:
“””Given a propositional logic sentence (i.e. a Expr instance), returns a satisfying
model if one exists. Otherwise, returns False.
cnf_sentence = to_cnf(sentence)
return pycoSAT(cnf_sentence)
def findModelUnderstandingCheck() -> Dict[Expr, bool]:
“””Returns the result of findModel(Expr(‘a’)) if lower cased expressions were allowed.
You should not use findModel or Expr in this method.
a = Expr(‘A’)
“*** BEGIN YOUR CODE HERE ***”
print(“a.__dict__ is:”, a.__dict__) # might be helpful for getting ideas
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def entails(premise: Expr, conclusion: Expr) -> bool:
“””Returns True if the premise entails the conclusion and False otherwise.
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def plTrueInverse(assignments: Dict[Expr, bool], inverse_statement: Expr) -> bool:
“””Returns True if the (not inverse_statement) is True given assignments and False otherwise.
pl_true may be useful here; see logic.py for its description.
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
#______________________________________________________________________________
# QUESTION 2
def atLeastOne(literals: List[Expr]) -> Expr:
Given a list of Expr literals (i.e. in the form A or ~A), return a single
Expr instance in CNF (conjunctive normal form) that represents the logic
that at least one of the literals ist is true.
>>> A = PropSymbolExpr(‘A’);
>>> B = PropSymbolExpr(‘B’);
>>> symbols = [A, B]
>>> atleast1 = atLeastOne(symbols)
>>> model1 = {A:False, B:False}
>>> print(pl_true(atleast1,model1))
>>> model2 = {A:False, B:True}
>>> print(pl_true(atleast1,model2))
>>> model3 = {A:True, B:True}
>>> print(pl_true(atleast1,model2))
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def atMostOne(literals: List[Expr]) -> Expr:
Given a list of Expr literals, return a single Expr instance in
CNF (conjunctive normal form) that represents the logic that at most one of
the expressions in the list is true.
itertools.combinations may be useful here.
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def exactlyOne(literals: List[Expr]) -> Expr:
Given a list of Expr literals, return a single Expr instance in
CNF (conjunctive normal form)that represents the logic that exactly one of
the expressions in the list is true.
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
#______________________________________________________________________________
# QUESTION 3
def pacmanSuccessorAxiomSingle(x: int, y: int, time: int, walls_grid: List[List[bool]]=None) -> Expr:
Successor state axiom for state (x,y,t) (from t-1), given the board (as a
grid representing the wall locations).
Current <==> (previous position at time t-1) & (took action to move to x, y)
Available actions are [‘North’, ‘East’, ‘South’, ‘West’]
Note that STOP is not an available action.
now, last = time, time – 1
possible_causes: List[Expr] = [] # enumerate all possible causes for P[x,y]_t
# the if statements give a small performance boost and are required for q4 and q5 correctness
if walls_grid[x][y+1] != 1:
possible_causes.append( PropSymbolExpr(pacman_str, x, y+1, time=last)
& PropSymbolExpr(‘South’, time=last))
if walls_grid[x][y-1] != 1:
possible_causes.append( PropSymbolExpr(pacman_str, x, y-1, time=last)
& PropSymbolExpr(‘North’, time=last))
if walls_grid[x+1][y] != 1:
possible_causes.append( PropSymbolExpr(pacman_str, x+1, y, time=last)
& PropSymbolExpr(‘West’, time=last))
if walls_grid[x-1][y] != 1:
possible_causes.append( PropSymbolExpr(pacman_str, x-1, y, time=last)
& PropSymbolExpr(‘East’, time=last))
if not possible_causes:
return None
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
def SLAMSuccessorAxiomSingle(x: int, y: int, time: int, walls_grid: List[List[bool]]) -> Expr:
Similar to `pacmanSuccessorStateAxioms` but accounts for illegal actions
where the pacman might not move timestep to timestep.
Available actions are [‘North’, ‘East’, ‘South’, ‘West’]
now, last = time, time – 1
moved_causes: List[Expr] = [] # enumerate all possible causes for P[x,y]_t, assuming moved to having moved
if walls_grid[x][y+1] != 1:
moved_causes.append( PropSymbolExpr(pacman_str, x, y+1, time=last)
& PropSymbolExpr(‘South’, time=last))
if walls_grid[x][y-1] != 1:
moved_causes.append( PropSymbolExpr(pacman_str, x, y-1, time=last)
& PropSymbolExpr(‘North’, time=last))
if walls_grid[x+1][y] != 1:
moved_causes.append( PropSymbolExpr(pacman_str, x+1, y, time=last)
& PropSymbolExpr(‘West’, time=last))
if walls_grid[x-1][y] != 1:
moved_causes.append( PropSymbolExpr(pacman_str, x-1, y, time=last)
& PropSymbolExpr(‘East’, time=last))
if not moved_causes:
return None
moved_causes_sent: Expr = conjoin([~PropSymbolExpr(pacman_str, x, y, time=last) , ~PropSymbolExpr(wall_str, x, y), disjoin(moved_causes)])
failed_move_causes: List[Expr] = [] # using merged variables, improves speed significantly
auxilary_expression_definitions: List[Expr] = []
for direction in DIRECTIONS:
dx, dy = DIR_TO_DXDY_MAP[direction]
wall_dir_clause = PropSymbolExpr(wall_str, x + dx, y + dy) & PropSymbolExpr(direction, time=last)
wall_dir_combined_literal = PropSymbolExpr(wall_str + direction, x + dx, y + dy, time=last)
failed_move_causes.append(wall_dir_combined_literal)
auxilary_expression_definitions.append(wall_dir_combined_literal % wall_dir_clause)
failed_move_causes_sent: Expr = conjoin([
PropSymbolExpr(pacman_str, x, y, time=last),
disjoin(failed_move_causes)])
return conjoin([PropSymbolExpr(pacman_str, x, y, time=now) % disjoin([moved_causes_sent, failed_move_causes_sent])] + auxilary_expression_definitions)
def pacphysicsAxioms(t: int, all_coords: List[Tuple], non_outer_wall_coords: List[Tuple], walls_grid: List[List] = None, sensorModel: Callable = None, successorAxioms: Callable = None) -> Expr:
t: timestep
all_coords: list of (x, y) coordinates of the entire problem
non_outer_wall_coords: list of (x, y) coordinates of the entire problem,
excluding the outer border (these are the actual squares pacman can
possibly be in)
walls_grid: 2D array of either -1/0/1 or T/F. Used only for successorAxioms.
Do NOT use this when making possible locations for pacman to be in.
sensorModel(t, non_outer_wall_coords) -> Expr: function that generates
the sensor model axioms. If None, it’s not provided, so shouldn’t be run.
successorAxioms(t, walls_grid, non_outer_wall_coords) -> Expr: function that generates
the sensor model axioms. If None, it’s not provided, so shouldn’t be run.
Return a logic sentence containing all of the following:
– for all (x, y) in all_coords:
If a wall is at (x, y) –> Pacman is not at (x, y)
– Pacman is at exactly one of the squares at timestep t.
– Pacman takes exactly one action at timestep t.
– Results of calling sensorModel(…), unless None.
– Results of calling successorAxioms(…), describing how Pacman can end in various
locations on this time step. Consider edge cases. Don’t call if None.
pacphysics_sentences = []
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
return conjoin(pacphysics_sentences)
def checkLocationSatisfiability(x1_y1: Tuple[int, int], x0_y0: Tuple[int, int], action0, action1, problem):
– x1_y1 = (x1, y1), a potential location at time t = 1
– x0_y0 = (x0, y0), Pacman’s location at time t = 0
– action0 = one of the four items in DIRECTIONS, Pacman’s action at time t = 0
– action1 = to ensure match with autograder solution
– problem = an instance of logicAgents.LocMapProblem
– there’s no sensorModel because we know everything about the world
– the successorAxioms should be allLegalSuccessorAxioms where needed
– a model where Pacman is at (x1, y1) at time t = 1
– a model where Pacman is not at (x1, y1) at time t = 1
walls_grid = problem.walls
walls_list = walls_grid.asList()
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
x0, y0 = x0_y0
x1, y1 = x1_y1
# We know which coords are walls:
map_sent = [PropSymbolExpr(wall_str, x, y) for x, y in walls_list]
KB.append(conjoin(map_sent))
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
#______________________________________________________________________________
# QUESTION 4
def positionLogicPlan(problem) -> List:
Given an instance of a PositionPlanningProblem, return a list of actions that lead to the goal.
Available actions are [‘North’, ‘East’, ‘South’, ‘West’]
Note that STOP is not an available action.
Overview: add knowledge incrementally, and query for a model each timestep. Do NOT use pacphysicsAxioms.
walls_grid = problem.walls
width, height = problem.getWidth(), problem.getHeight()
walls_list = walls_grid.asList()
x0, y0 = problem.startState
xg, yg = problem.goal
# Get lists of possible locations (i.e. without walls) and possible actions
all_coords = list(itertools.product(range(width + 2),
range(height + 2)))
non_wall_coords = [loc for loc in all_coords if loc not in walls_list]
actions = [ ‘North’, ‘South’, ‘East’, ‘West’ ]
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
#______________________________________________________________________________
# QUESTION 5
def foodLogicPlan(problem) -> List:
Given an instance of a FoodPlanningProblem, return a list of actions that help Pacman
eat all of the food.
Available actions are [‘North’, ‘East’, ‘South’, ‘West’]
Note that STOP is not an available action.
Overview: add knowledge incrementally, and query for a model each timestep. Do NOT use pacphysicsAxioms.
walls = problem.walls
width, height = problem.getWidth(), problem.getHeight()
walls_list = walls.asList()
(x0, y0), food = problem.start
food = food.asList()
# Get lists of possible locations (i.e. without walls) and possible actions
all_coords = list(itertools.product(range(width + 2), range(height + 2)))
non_wall_coords = [loc for loc in all_coords if loc not in walls_list]
actions = [ ‘North’, ‘South’, ‘East’, ‘West’ ]
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
“*** END YOUR CODE HERE ***”
#______________________________________________________________________________
# QUESTION 6
def localization(problem, agent) -> Generator:
problem: a LocalizationProblem instance
agent: a LocalizationLogicAgent instance
walls_grid = problem.walls
walls_list = walls_grid.asList()
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
for t in range(agent.num_timesteps):
“*** END YOUR CODE HERE ***”
yield possible_locations
#______________________________________________________________________________
# QUESTION 7
def mapping(problem, agent) -> Generator:
problem: a MappingProblem instance
agent: a MappingLogicAgent instance
pac_x_0, pac_y_0 = problem.startState
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
# map describes what we know, for GUI rendering purposes. -1 is unknown, 0 is open, 1 is wall
known_map = [[-1 for y in range(problem.getHeight()+2)] for x in range(problem.getWidth()+2)]
# Pacman knows that the outer border of squares are all walls
outer_wall_sent = []
for x, y in all_coords:
if ((x == 0 or x == problem.getWidth() + 1)
or (y == 0 or y == problem.getHeight() + 1)):
known_map[x][y] = 1
outer_wall_sent.append(PropSymbolExpr(wall_str, x, y))
KB.append(conjoin(outer_wall_sent))
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
for t in range(agent.num_timesteps):
“*** END YOUR CODE HERE ***”
yield known_map
#______________________________________________________________________________
# QUESTION 8
def slam(problem, agent) -> Generator:
problem: a SLAMProblem instance
agent: a SLAMLogicAgent instance
pac_x_0, pac_y_0 = problem.startState
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
# map describes what we know, for GUI rendering purposes. -1 is unknown, 0 is open, 1 is wall
known_map = [[-1 for y in range(problem.getHeight()+2)] for x in range(problem.getWidth()+2)]
# We know that the outer_coords are all walls.
outer_wall_sent = []
for x, y in all_coords:
if ((x == 0 or x == problem.getWidth() + 1)
or (y == 0 or y == problem.getHeight() + 1)):
known_map[x][y] = 1
outer_wall_sent.append(PropSymbolExpr(wall_str, x, y))
KB.append(conjoin(outer_wall_sent))
“*** BEGIN YOUR CODE HERE ***”
util.raiseNotDefined()
for t in range(agent.num_timesteps):
“*** END YOUR CODE HERE ***”
yield (known_map, possible_locations)
# Abbreviations
plp = positionLogicPlan
loc = localization
mp = mapping
flp = foodLogicPlan
# Sometimes the logic module uses pretty deep recursion on long expressions
sys.setrecursionlimit(100000)
#______________________________________________________________________________
# Important expression generating functions, useful to read for understanding of this project.
def sensorAxioms(t: int, non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
all_percept_exprs = []
combo_var_def_exprs = []
for direction in DIRECTIONS:
percept_exprs = []
dx, dy = DIR_TO_DXDY_MAP[direction]
for x, y in non_outer_wall_coords:
combo_var = PropSymbolExpr(pacman_wall_str, x, y, x + dx, y + dy, time=t)
percept_exprs.append(combo_var)
combo_var_def_exprs.append(combo_var % (
PropSymbolExpr(pacman_str, x, y, time=t) & PropSymbolExpr(wall_str, x + dx, y + dy)))
percept_unit_clause = PropSymbolExpr(blocked_str_map[direction], time = t)
all_percept_exprs.append(percept_unit_clause % disjoin(percept_exprs))
return conjoin(all_percept_exprs + combo_var_def_exprs)
def fourBitPerceptRules(t: int, percepts: List) -> Expr:
Localization and Mapping both use the 4 bit sensor, which tells us True/False whether
a wall is to pacman’s north, south, east, and west.
assert isinstance(percepts, list), “Percepts must be a list.”
assert len(percepts) == 4, “Percepts must be a length 4 list.”
percept_unit_clauses = []
for wall_present, direction in zip(percepts, DIRECTIONS):
percept_unit_clause = PropSymbolExpr(blocked_str_map[direction], time=t)
if not wall_present:
percept_unit_clause = ~PropSymbolExpr(blocked_str_map[direction], time=t)
percept_unit_clauses.append(percept_unit_clause) # The actual sensor readings
return conjoin(percept_unit_clauses)
def numAdjWallsPerceptRules(t: int, percepts: List) -> Expr:
SLAM uses a weaker numAdjWallsPerceptRules sensor, which tells us how many walls pacman is adjacent to
in its four directions.
000 = 0 adj walls.
100 = 1 adj wall.
110 = 2 adj walls.
111 = 3 adj walls.
assert isinstance(percepts, list), “Percepts must be a list.”
assert len(percepts) == 3, “Percepts must be a length 3 list.”
percept_unit_clauses = []
for i, percept in enumerate(percepts):
percept_literal_n = PropSymbolExpr(geq_num_adj_wall_str_map[n], time=t)
if not percept:
percept_literal_n = ~percept_literal_n
percept_unit_clauses.append(percept_literal_n)
return conjoin(percept_unit_clauses)
def SLAMSensorAxioms(t: int, non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
all_percept_exprs = []
combo_var_def_exprs = []
for direction in DIRECTIONS:
percept_exprs = []
dx, dy = DIR_TO_DXDY_MAP[direction]
for x, y in non_outer_wall_coords:
combo_var = PropSymbolExpr(pacman_wall_str, x, y, x + dx, y + dy, time=t)
percept_exprs.append(combo_var)
combo_var_def_exprs.append(combo_var % (PropSymbolExpr(pacman_str, x, y, time=t) & PropSymbolExpr(wall_str, x + dx, y + dy)))
blocked_dir_clause = PropSymbolExpr(blocked_str_map[direction], time=t)
all_percept_exprs.append(blocked_dir_clause % disjoin(percept_exprs))
percept_to_blocked_sent = []
for n in range(1, 4):
wall_combos_size_n = itertools.combinations(blocked_str_map.values(), n)
n_walls_blocked_sent = disjoin([
conjoin([PropSymbolExpr(blocked_str, time=t) for blocked_str in wall_combo])
for wall_combo in wall_combos_size_n])
# n_walls_blocked_sent is of form: (N & S) | (N & E) | …
percept_to_blocked_sent.append(
PropSymbolExpr(geq_num_adj_wall_str_map[n], time=t) % n_walls_blocked_sent)
return conjoin(all_percept_exprs + combo_var_def_exprs + percept_to_blocked_sent)
def allLegalSuccessorAxioms(t: int, walls_grid: List[List], non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
“””walls_grid can be a 2D array of ints or bools.”””
all_xy_succ_axioms = []
for x, y in non_outer_wall_coords:
xy_succ_axiom = pacmanSuccessorAxiomSingle(
x, y, t, walls_grid)
if xy_succ_axiom:
all_xy_succ_axioms.append(xy_succ_axiom)
return conjoin(all_xy_succ_axioms)
def SLAMSuccessorAxioms(t: int, walls_grid: List[List], non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
“””walls_grid can be a 2D array of ints or bools.”””
all_xy_succ_axioms = []
for x, y in non_outer_wall_coords:
xy_succ_axiom = SLAMSuccessorAxiomSingle(
x, y, t, walls_grid)
if xy_succ_axiom:
all_xy_succ_axioms.append(xy_succ_axiom)
return conjoin(all_xy_succ_axioms)
#______________________________________________________________________________
# Various useful functions, are not needed for completing the project but may be useful for debugging
def modelToString(model: Dict[Expr, bool]) -> str:
“””Converts the model to a string for printing purposes. The keys of a model are
sorted before converting the model to a string.
model: Either a boolean False or a dictionary of Expr symbols (keys)
and a corresponding assignment of True or False (values). This model is the output of
a call to pycoSAT.
if model == False:
return “False”
# Dictionary
modelList = sorted(model.items(), key=lambda item: str(item[0]))
return str(modelList)
def extractActionSequence(model: Dict[Expr, bool], actions: List) -> List:
Convert a model in to an ordered list of actions.
model: Propositional logic model stored as a dictionary with keys being
the symbol strings and values being Boolean: True or False
>>> model = {“North[2]”:True, “P[3,4,0]”:True, “P[3,3,0]”:False, “West[0]”:True, “GhostScary”:True, “West[2]”:False, “South[1]”:True, “East[0]”:False}
>>> actions = [‘North’, ‘South’, ‘East’, ‘West’]
>>> plan = extractActionSequence(model, actions)
>>> print(plan)
[‘West’, ‘South’, ‘North’]
plan = [None for _ in range(len(model))]
for sym, val in model.items():
parsed = parseExpr(sym)
if type(parsed) == tuple and parsed[0] in actions and val:
action, _, time = parsed
plan[time] = action
#return list(filter(lambda x: x is not None, plan))
return [x for x in plan if x is not None]
# Helpful Debug Method
def visualizeCoords(coords_list, problem) -> None:
wallGrid = game.Grid(problem.walls.width, problem.walls.height, initialValue=False)
for (x, y) in itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)):
if (x, y) in coords_list:
wallGrid.data[x][y] = True
print(wallGrid)
# Helpful Debug Method
def visualizeBoolArray(bool_arr, problem) -> None:
wallGrid = game.Grid(problem.walls.width, problem.walls.height, initialValue=False)
wallGrid.data = copy.deepcopy(bool_arr)
print(wallGrid)
class PlanningProblem:
This class outlines the structure of a planning problem, but doesn’t implement
any of the methods (in object-oriented terminology: an abstract class).
You do not need to change anything in this class, ever.
def getStartState(self):
Returns the start state for the planning problem.
util.raiseNotDefined()
def getGhostStartStates(self):
Returns a list containing the start state for each ghost.
Only used in problems that use ghosts (FoodGhostPlanningProblem)
util.raiseNotDefined()
def getGoalState(self):
Returns goal state for problem. Note only defined for problems that have
a unique goal state such as PositionPlanningProblem
util.raiseNotDefined()