CS 188 Spring 2023 Project 2: Logic and Classical Planning
Hello, dear friend, you can consult us at any time if you have any questions, add WeChat: daixieit
CS 188 Spring 2023
Project 2: Logic and Classical Planning
Due: Tuesday, February 14, 11:59 PM PT.
Logical Pacman,
Food is good AND ghosts are bad,
Spock would be so proud
• A note on conjoin and disjoin
• Prop Symbol Names (Important!)
• Rules
• Question 1 (2 points): Logic Warm-up
• Question 2 (2 points): Logic Workout
• Question 3 (4 points): Pacphysics and Satisfiability
• Question 4 (3 points): Path Planning with Logic
• Question 5 (3 points): Eating All the Food
• Helper Functions for the rest of the Project
• Add pacphysics, action, and percept information to KB
• Find possible pacman locations with updated KB
• Find provable wall locations with updated KB
• Question 6 (4 points): Localization
• Question 7 (3 points): Mapping
n this project, you will use/write simple Python functions that generate logical sentences describing Pacman physics, aka pacphysics. Then you will use a SAT solver, pycosat, to solve the logical inference tasks associated with planning (generating action sequences to reach goal locations and eat all the dots), localization (finding oneself in a map, given a local sensor model), mapping (building the map from scratch), and SLAM (simultaneous localization and mapping).
As in previous programming assignments, this assignment includes an autograder for you to grade your answers on your machine. This can be run with the command:
python autograder.py
Copy
The code for this project contains the following files, available as a zip archive.
Files you'll edit:
logicPlan.py |
Where you will put your code for the various logical agents. |
Files you might want to look at: |
|
logic.py |
Propsitional logic code originally from aima-python with modifications for our project. There are several useful utility functions for working with logic in here. |
logicAgents.py |
The file that defines in logical planning form the two specific problems that Pacman will encounter in this project. |
pycosat_test.py |
Quick test main function that checks that the pycosat module is installed correctly. |
game.py |
The internal simulator code for the Pacman world. The only thing you might want to look at in here is the Grid class. |
test_cases/ |
Directory containing the test cases for each question. |
The main file that runs Pacman games.
Utility functions for logic.py .
Utility functions primarily for other projects.
Project specific autograding test classes.
Graphics for Pacman.
Support for Pacman graphics.
ASCII graphics for Pacman.
Agents to control ghosts.
Keyboard interfaces to control Pacman.
Code for reading layout files and storing their contents.
Project autograder.
Parses autograder test and solution files.
General autograding test classes.
Files to Edit and Submit: You will fill in portions of logicPlan.py during the assignment. Once you have completed the assignment, you will submit these files to Gradescope (for instance, you can zip the entire project folder). Please do not change the other files in this distribution.
Evaluation: Your code will be autograded for technical correctness. Please do not change the names of any provided functions or classes within the code, or you will wreak havoc on the autograder. However, the correctness of your implementation – not the autograder’s judgements – will be the final judge of your score. If necessary, we will review and grade assignments individually to ensure that you receive due credit for your work.
Academic Dishonesty: We will be checking your code against other submissions in the class for logical redundancy. If you copy someone else’s code and submit it with minor changes, we will know. These cheat detectors are quite hard to fool, so please don’t try. We trust you all to submit your own work only; please don’t let us down. If you do, we will pursue the strongest consequences available to us.
Getting Help: You are not alone! If you find yourself stuck on something, contact the course staff for help. Office hours, section, and the discussion forum are there for your support; please use them. If you can’t make our office hours, let us know and we will schedule more. We want these projects to be rewarding and instructional, not frustrating and demoralizing. But, we don’t know when or how to help unless you ask.
Discussion: Please be careful not to post spoilers.
In the first part of this project, you will be working with the Expr class defined in
logic.py to build propositional logic sentences. An Expr object is implemented as a tree with logical operators (∧, V, ¬, →, ↔) at each node and with literals (A, B, C) at the leaves. Here is an example sentence and its representation:
(A ∧ B) ↔ (¬C V D)
To instantiate a symbol named 'A' , call the constructor like this:
A = Expr('A')
The Expr class allows you to use Python operators to build up these expressions. The following are the available Python operators and their meanings:
• ~A : ¬A
• A & B : A ∧ B
• A | B : A V B
• A >> B : A → B
• A % B : A ↔ B
So to build the expression A ∧ B, you would type this:
A = Expr('A') Copy
B = Expr('B')
a_and_b = A & B
(Note that A to the left of the assignment operator in that example is just a Python variable name, i.e. symbol1 = Expr('A') would have worked just as well.)
One last important thing to note is that you must use conjoin and disjoin operators wherever possible. conjoin creates a chained & (logical AND) expression, and disjoin creates a chained | (logical OR) expression. Let’s say you wanted to check whether conditions A, B, C, D, and E are all true. The naive way to achieve this is writing condition = A & B & C & D & E , but this actually translates to ((((A & B) & C) & D) & E) , which creates a very nested logic tree (see (1) in diagram below) and becomes a nightmare to debug. Instead, conjoin makes a flat tree (see (2) in diagram below).
Prop Symbol Names (Important!)
For the rest of the project, please use the following variable naming conventions:
• When we introduce variables, they must start with an upper-case character (including Expr ).
• Only these characters should appear in variable names: A-Z , a-z , 0-9 , _ , ^ , [ , ] .
• Logical connective characters ( & , | ) must not appear in variable names. So, Expr('A & B') is illegal because it attempts to create a single constant symbol named 'A & B' . We would use Expr('A') & Expr('B') to make a logical expression.
• PropSymbolExpr(pacman_str, x, y, time=t) : whether or not Pacman is at (x, y) at time t, writes P[x,y]_t .
• PropSymbolExpr(wall_str, x, y) : whether or not a wall is at (x, y), writes WALL[x,y] .
• PropSymbolExpr(action, time=t) : whether or not pacman takes action action at time t, where action is an element of DIRECTIONS , writes i.e. North_t .
• In general, PropSymbolExpr(str, a1, a2, a3, a4, time=a5) creates the expression str[a1,a2,a3,a4]_a5 where str is just a string.
There is additional, more detailed documentation for the Expr class in logic.py .
A SAT (satisfiability) solver takes a logic expression which encodes the rules of the world and returns a model (true and false assignments to logic symbols) that satisfies that expression if such a model exists. To efficiently find a possible model from an expression, we take advantage of the pycosat module, which is a Python wrapper around the picoSAT library.
Unfortunately, this requires installing this module/library on each machine. In the command line, run pip install pycosat , or pip3 install pycosat on some setups, or
conda install pycosat for conda .
On Windows, if you are getting an error message saying error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft Build Tools": ... , you will have to install a C/C++ compiler following that link; or, use conda install pycosat , for which you will need to have Anaconda installed (recommend uninstalling current Python before installing a new one) and run this from the Anaconda prompt.
Testing pycosat installation:
After unzipping the project code and changing to the project code directory, run:
python pycosat_test.py
Copy
This should output:
[1, -2, -3, -4, 5]
Copy
Please let us know if you have issues with this setup. This is critical to completing the project, and we don’t want you to spend your time fighting with this installation process.
Question 1 (2 points): Logic Warm-up
This question will give you practice working with the Expr data type used in the project
to represent propositional logic sentences. You will implement the following functions in logicPlan.py :
• sentence1() : Create one Expr instance that represents the proposition that the following three sentences are true. Do not do any logical simplification, just put them in a list in this order, and return the list conjoined. Each element of your list should correspond to each of the three sentences.
A V B
¬A ↔ (¬B V C)
¬A V ¬B V C
• sentence2() : Create one Expr instance that represents the proposition that the following four sentences are true. Again, do not do any logical simplification, just put them in a list in this order, and return the list conjoined.
C ↔ (B V D)
A → (¬B ^ ¬D)
¬ (B ^ ¬C) → A
¬D → C
• sentence3() : Using the PropSymbolExpr constructor, create the symbols 'PacmanAlive_0' , 'PacmanAlive_1' , 'PacmanBorn_0' , and 'PacmanKilled_0' (hint: recall that PropSymbolExpr(str, a1, a2, a3, a4, time=a5) creates the expression
str[a1,a2,a3,a4]_a5 where str is a string; you should make some strings for this problem to match these exactly). Then, create one Expr instance which encodes the following three English sentences as propositional logic in this order without any simplification:
1 Pacman is alive at time 1 if and only if he was alive at time 0 and he was not killed at time 0 or he was not alive at time 0 and he was born at time 0.
2 At time 0, Pacman cannot both be alive and be born.
3 Pacman is born at time 0.
• findModelUnderstandingCheck() :
1 Look at how the findModel(sentence) method works: it uses to_cnf to convert the input sentence into Conjunctive Normal Form (the form required by the SAT solver), and passes it to the SAT solver to find a satisfying assignment to the symbols in sentence , i.e., a model. A model is a dictionary of the symbols in your expression and a corresponding assignment of True or False . Test your sentence1() , sentence2() , and sentence3() with findModel by opening an interactive session in Python and running from logicPlan import * and findModel(sentence1()) and similar queries for the other two. Do they match what you were expecting?
2 Based on the above, fill in findModelUnderstandingCheck so that it returns what findModel(Expr('a')) would return if lower case variables were allowed. You should not use findModel or Expr beyond what’s already given; simply directly recreate the output.
• entails(premise, conclusion) : Return True if and only if the premise entails the conclusion . Hint: findModel is helpful here; think about what must be unsatisfiable in order for the entails to be True , and what it means for something to be unstatisfiable.
• plTrueInverse(assignments, inverse_statement) : Returns True if and only if the (not inverse_statement ) is True given assignments .
Before you continue, try instantiating a small sentence, e.g. A ∧ B → C, and call to_cnf on it. Inspect the output and make sure you understand it (refer to AIMA section 7.5.2 for details on the algorithm to_cnf implements).
To test and debug your code run:
python autograder.py -q q1
Question 2 (2 points): Logic Workout
Implement the following three functions in logicPlan.py (remembering to use conjoin and disjoin whenever possible):
• atLeastOne(literals) : Return a single expression ( Expr ) in CNF that is true only if at least one expression in the input list is true. Each input expression will be a literal.
• atMostOne(literals) : Return a single expression ( Expr ) in CNF that is true only if at most one expression in the input list is true. Each input expression will be a literal. Hint: Use itertools.combinations . If you have n literals, and at most one is true, your resulting CNF expression should be a conjunction of (2(n)) clauses.
• exactlyOne(literals) : Use atLeastOne and atMostOne to return a single expression ( Expr ) in CNF that is true only if exactly one expression in the input list is true. Each input expression will be a literal.
Each of these methods takes a list of Expr literals and returns a single Expr expression that represents the appropriate logical relationship between the expressions in the input list. An additional requirement is that the returned Expr must be in CNF (conjunctive normal form). You may NOT use the to_cnf function in your method implementations (or any of the helper functions logic.eliminate_implications , logic.move_not_inwards , and logic.distribute_and_over_or ).
Don’t run to_cnf on your knowledge base when implementing your planning agents in later questions. This is because to_cnf makes your logical expression much longer sometimes, so you want to minimize this effect; findModel does this as needed. In later questions, reuse your implementations for atLeastOne(.) , atMostOne(.) , and
exactlyOne(.) instead of re-engineering these functions from scratch. This avoids accidentally making unreasonably slow non-CNF-based implementations.
You may utilize the logic.pl_true function to test the output of your expressions.
pl_true takes an expression and a model and returns True if and only if the expression is true given the model.
To test and debug your code run:
python autograder.py -q q2
Question 3 (4 points): Pacphysics and Satisfiability
In this question, you will implement the basic pacphysics logical expressions, as well as learn how to prove where pacman is and isn’t by building an appropriate knowledge base (KB) of logical expressions.
Implement the following functions in logicPlan.py :
• pacmanSuccessorAxiomSingle – this generates an expression defining the sufficient and necessary conditions for Pacman to be at (x, y) at t:
• Read the construction of possible_causes provided.
• You need to fill out the return statement, which will be an Expr . Make sure to use disjoin and conjoin where appropriate. Looking at SLAMSuccessorAxiomSingle may be helpful, although note that the rules there are more complicated than in this function. The simpler side of the biconditional should be on the left for autograder purposes.
• pacphysicsAxioms – here, you will generate a bunch of physics axioms. For timestep t:
• Arguments:
• Required: t is time, all_coords and non_outer_wall_coords are lists of (x, y) tuples.
• Possibly- None : You will be using these to call functions, not much logic is required.
• walls_grid is only passed through to successorAxioms and describes (known) walls.
• sensorModel(t: int, non_outer_wall_coords) -> Expr returns a single Expr describing observation rules; you can take a look at sensorAxioms and SLAMSensorAxioms to see examples of this.
• successorAxioms(t: int, walls_grid, non_outer_wall_coords) -> Expr
describes transition rules, e.g. how previous locations and actions of Pacman affect the current location; we have seen this in the functions in
pacmanSuccessorAxiomSingle .
• Algorithm:
• For all (x, y) in all_coords , append the following implication (if-then form): if a wall is at (x, y), then Pacman is not at (x, y) at t.
• Pacman is at exactly one of the non_outer_wall_coords at timestep t.
• Pacman takes exactly one of the four actions in DIRECTIONS at timestep t.
• Sensors: append the result of sensorAxioms . All callers except for checkLocationSatisfiability make use of this; how to handle the case where we don’t want any sensor axioms added is up to you.
• Transitions: append the result of successorAxioms . All callers will use this.
• Add each of the sentences above to pacphysics_sentences . As you can see in the return statement, these will be conjoined and returned.
• Function passing syntax:
• Let def myFunction(x, y, t): return PropSymbolExpr('hello', x, y, time=t) be a function we want to use.
• Let def myCaller(func: Callable): ... be the caller that wants to use a function.
• We can pass the function in: myCaller(myFunction) . Note that myFunction is not called with () after it.
• We can use myFunction by having inside myCaller this: useful_return = func(0, 1, q) .
• checkLocationSatisfiability – given a transition (x0_y0, action0, x1_y1) , action1 , and a problem , you will write a function that will return a tuple of two models (model1, model2) :
• In model1 , Pacman is at (x1, y1) at time t = 1 given x0_y0 , action0 , action1 .
This model proves that it’s possible that Pacman there. Notably, if model1 is False , we know Pacman is guaranteed to NOT be there.
• In model2 , Pacman is NOT at (x1, y1) at time t = 1 given x0_y0 , action0 , action1 . This model proves that it’s possible that Pacman is not there. Notably, if model2 is False , we know Pacman is guaranteed to be there.
2023-02-20