-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Total-order planner #645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Total-order planner #645
Conversation
…-order planning. Also included air cargo, spare_tire, blocks world and sussman anomaly examples.
… from FolKB. Changed blocks_world function name to three_block_tower. Cleaned up imports and PlanningKB __repr__ function.
This looks really nice -- great work! |
There are too many conflicts for me to resolve -- can you have a look? |
One of the main issues is that the PlanningKB items are immutable (and therefore hashable and searchable) and the FOLKB items from logic.py aren't. I'll need some time to see if I can make these two object models work together, and probably a decision from you as to which way to go with the implementation. |
Hey @bcorfman, I know that |
Hi Dr. Norvig - I use the existing A* algorithm to solve the example planning problems. Recall that the set of explored items is maintained during the search to keep track of repeated states (line 270 in the current search.py). Any items in this set must be hashable and unique, or they will fail when they are added to the set. I use my PlanningKB as the state variable in the search Node, and it's hashable because the PlanningKB only uses frozenset variables instead of regular mutable sets. The FolKB database doesn't work as the Node.state because it's designed (because of its tell and ask methods) to be mutable -- that means its clauses variable is a mutable list, and its state can't be hashed. |
I got it. What about if you use |
Yep, a tuple should work for the FolKB clauses. (I remember using tuples with the Node setup for one of the Berkeley Pac-Man projects.) I guess my question is whether you want to a) just make the FolKB usable for the search algorithms, 2) combine the existing logic.py and my new planning.py code together, or 3) just pass the FolKB clause list into the PlanningKB init and then use the PlanningKB to conduct the search. I think number 3 is the easiest -- although there'd be two KBs for different purposes, my PlanningKB and Action classes use frozenset methods to quickly perform goal and precondition checks. I can make changes based on what you think is the best long-term approach. |
search.py
Outdated
@@ -181,7 +181,7 @@ def tree_search(problem, frontier): | |||
while frontier: | |||
node = frontier.pop() | |||
if problem.goal_test(node.state): | |||
return node | |||
yield node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, you can't change the search
algorithms to be generators instead of functions. You'll need to define a separate function if you want to yield all possible solutions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tip. I will revert this in my next push.
method can be used. Changed tokenize() to add whitespace around colons, so that actions parse correctly.
Working on problem file parsing.
move this to a subfolder eventually.
line/col info for error reporting. Didn't pan out because of carrying the line/col info in a separate data structure that makes it hard to keep in sync, plus it just seems like overkill.
Added shoes domain and problem.
new PDDL examples.
I merged my planning code with the current version that's in the AIMA repo. It's not the cleanest because it was hard for me to tell the state of the code at the moment -- there seems to be a lot of design ideas in progress, and I was hesitant to start melding too much without some evaluation on my own code first. I built a basic PDDL parser with lots of tests/examples in the pddl_files subdirectory in order to give my total order planner a thorough shakedown before submitting it. The examples are run by executing the test_planning_solutions function inside the planning module. Most of the tests are just translations of the ones that were already inside the existing planning code, but there are a couple additions too. The parser could be better about more specific error reporting when something goes wrong, but it does work solidly otherwise. Dr. Norvig, I did try a few of your suggestions above to try merging the FolKB class and my PlanningKB class, but these ideas quickly got snarled because my PlanningKB, PlanningProblem and PlanningAction classes are very interdependent and rely heavily on Python's set classes. Modifying the existing AIMA Problem, Node or Action code would have big changes that would affect other modules, so I didn't want to do that without feedback. Please let me know if you want to proceed any further. |
Fixed bug on build_expr_string to correctly capitalize negative Exprs. Created classmethods on PlanningProblem and PlanningAction to __init__ those classes from existing PDDL and Action objects. Fixed __repr__ method on PlanningAction.
I also added a couple of classmethods to my PlanningProblem and PlanningAction classes. These alternative constructors (PlanningProblem.from_PDDL_object and PlanningAction.from_action) take existing PDDL and Action objects as parameters in order to convert the KB and actions to be searchable. If you decide to use my PDDL parser too, I might suggest renaming the PDDL class to something more appropriate -- there's starting to be a lot of things named "PDDL" in the code, and it could create confusion. |
planning.py
Outdated
@@ -191,22 +221,22 @@ def air_cargo(): | |||
# Load | |||
precond = [expr('At(c, a)'), expr('At(p, a)'), expr('Cargo(c)'), expr('Plane(p)'), expr('Airport(a)')] | |||
effect = [expr('In(c, p)'), expr('~At(c, a)')] | |||
load = Action(expr('Load(c, p, a)'), precond, effect) | |||
load = PlanningAction(expr('Load(c, p, a)'), precond, effect) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better if PlanningAction
accepted strings or Exprs, and then this could be
load = PlanningAction('Load(c, p, a)',
'At(c, a), At(p, a), Cargo(c), Plane(p), Airport(a)',
'In(c, p), ~At(c, a)')
and similarly elsewhere.
I think this looks pretty good, but there's a lot here stretching over a long time. Can you, @bcorfman , check in with @ad71 and perhaps @samagra14 over in aima-java, who is also working on planning now, and together come to a consensus? |
After I checked in, I found a bug that was exposed by one of my test cases, and I realized that the existing Action and PDDL classes were doing some things I should incorporate to fix it. I will make the changes over the next couple days so we're more in sync, and try to come to consensus on the remainder! |
@bcorfman please also have a look at #927 and tell me if you want me to change something. My total order planner is a misnomer (and can probably be handled by something as simple as a topological sort on a planning graph) and I'll get rid of it in the next commit but apart from that, the PR heavily relies on the current Action and PDDL classes. It might be a problem if those classes are changed. Do inform me if you really want to though, we can find a way around it. You can also have a look at the planning notebook to see how things are handled now. It's quite different from the time this pull request was first opened. Thanks! |
@ad71 I will take a look at #927 and get my bearings ... the basic issue for using any of the search algorithms (A*) for the current KBs is that they must be frozen types (i.e., tuples, frozensets, etc.) to be hashable when stored in the frontier. If we get away from a search representation for the planners (i.e., like you did with Graphplan, correct?), we can stay with the tell() and retract() methods that modify the KB. But otherwise, it seems like we'd be stuck with two different KB representations: frozen and searchable, or mutable and unsearchable. We could move between them, but I don't think we can combine the KB types together in my opinion (at least without an unfortunate breaking of the Python object model, e.g., utils.hashabledict). I always could be missing something though. |
@bcorfman we faced similar problems trying to make |
whether action is valid (separate from one or more valid substitutions).
Sorry I'm so behind -- I fixed the bug I found with my TO planner. I will take a look at #927 over the next couple days and see if we can wrap this up ASAP. |
use strings or Exprs for a simplified API.
@ad71 I implemented what Dr. @norvig suggested for PlanningAction and PlanningKB ... both of their init methods take strings or Exprs now for a cleaner API. I agree with you on translating between representations ... your observation is a good reason why Python has both lists and tuples and sets and frozensets, and they can easily be moved between. Do you think we should try the classmethod approach for moving between the types, or use some other way like a standalone function? Probably my PlanningProblem should be renamed PlanningSearchProblem or something else to distinguish it from the mutable (PDDL) representations. Same for PlanningAction, so it isn't confused with the existing Action class ... I suggest STRIPSAction because of its negative and positive preconditions and add and remove effects that are implemented with set operations. I'm open to improved naming. As I mentioned previously, strongly think we should rename the PDDL class though (since it has little to do with the PDDL language) ... could we rename that to PlanningProblem instead? |
@bcorfman I agree that |
OK, I'll change the code to reflect the new names. Should I add a draft writeup on the new code in the Jupiter notebook, or hold off for now?
|
Editing notebooks will result in merge conflicts with the other PR. You can add the write-ups later. |
No problem; I can wait. |
During the interim, I'll work on some unit tests to add to test_planning.py as well. |
Added substitution in PlanningSearchProblem.actions() for valid actions. Added __eq__ special method for comparing STRIPSAction objects, both for correctness and simpler unit testing. Simplified print_solution() function since argument substitution went away above. Changed test_planning_solutions() function to run_planning_solutions() so it wouldn't get triggered by py.test.
since it was causing failures.
Submitting code for planner.