-
Notifications
You must be signed in to change notification settings - Fork 2
Multi robot plan repair #25
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: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| (define (domain region-object-rearrangement-domain-multirobot-fd-constraints) | ||
| (:requirements :derived-predicates :typing :adl) | ||
| (:types | ||
| region point-of-interest - object | ||
| place dsg_object - point-of-interest | ||
| robot - object | ||
| ) | ||
|
|
||
| (:predicates | ||
| (at-poi ?r - robot ?p - point-of-interest) | ||
| (connected ?s - point-of-interest ?t - point-of-interest) | ||
| (suspicious ?o - dsg_object) | ||
| (at-object ?r - robot ?o - dsg_object) | ||
| (at-place ?r - robot ?p - place) | ||
| (in-region ?r - robot ?reg - region) | ||
|
|
||
| (holding ?r - robot ?o - dsg_object) | ||
| (hand-full ?r - robot) | ||
| (object-in-place ?o - dsg_object ?p - place) | ||
| (place-in-region ?p - place ?reg - region) | ||
|
|
||
| (visited-poi ?p - point-of-interest) | ||
| (visited-place ?p - place) | ||
| (visited-object ?o - dsg_object) | ||
| (visited-region ?reg - region) | ||
|
|
||
| (safe ?o - dsg_object) | ||
| (explored-region ?reg - region) | ||
|
|
||
| (forbidden-poi ?p - point-of-interest) | ||
| (forbidden-edge ?s - point-of-interest ?t - point-of-interest) | ||
| ) | ||
|
|
||
| (:functions | ||
| (distance ?s - point-of-interest ?t - point-of-interest) | ||
| (total-cost) | ||
| ) | ||
|
|
||
| (:derived (at-object ?r - robot ?o - dsg_object) | ||
| (at-poi ?r ?o)) | ||
|
|
||
| (:derived (at-place ?r - robot ?p - place) | ||
| (at-poi ?r ?p)) | ||
|
|
||
| (:derived (visited-place ?p - place) | ||
| (visited-poi ?p)) | ||
|
|
||
| (:derived (visited-object ?o - dsg_object) | ||
| (visited-poi ?o)) | ||
|
|
||
| (:derived (safe ?o - dsg_object) | ||
| (not (suspicious ?o))) | ||
|
|
||
| (:derived (in-region ?r - robot ?reg - region) | ||
| (exists (?p - place) (and (at-place ?r ?p) (place-in-region ?p ?reg)))) | ||
|
|
||
| (:derived (visited-region ?reg - region) | ||
| (exists (?p - place) (and (visited-place ?p) (place-in-region ?p ?reg)))) | ||
|
|
||
| (:derived (explored-region ?reg - region) | ||
| (forall (?p - place) | ||
| (or (not (place-in-region ?p ?reg)) | ||
| (visited-place ?p)))) | ||
|
|
||
| (:action goto-poi | ||
| :parameters (?r - robot ?s - point-of-interest ?t - point-of-interest) | ||
| :precondition (and (at-poi ?r ?s) | ||
| (or (connected ?s ?t) (connected ?t ?s)) | ||
| (not (forbidden-poi ?t)) | ||
| (not (forbidden-edge ?s ?t)) | ||
| (not (forbidden-edge ?t ?s))) | ||
| :effect (and (not (at-poi ?r ?s)) | ||
| (at-poi ?r ?t) | ||
| (visited-poi ?t) | ||
| (increase (total-cost) (distance ?s ?t)) | ||
| ) | ||
| ) | ||
|
|
||
| (:action inspect | ||
| :parameters (?r - robot ?o - dsg_object) | ||
| :precondition (at-object ?r ?o) | ||
| :effect (and (not (suspicious ?o)) | ||
| (increase (total-cost) 1) | ||
| ) | ||
| ) | ||
|
|
||
| (:action pick-object | ||
| :parameters (?r - robot ?o - dsg_object ?p - place) | ||
| :precondition (and (not (hand-full ?r)) | ||
| (safe ?o) | ||
| (at-object ?r ?o) | ||
| (object-in-place ?o ?p)) | ||
| :effect (and (holding ?r ?o) | ||
| (hand-full ?r) | ||
| (not (object-in-place ?o ?p)))) | ||
|
|
||
| (:action place-object | ||
| :parameters (?r - robot ?o - dsg_object ?p - place) | ||
| :precondition (and (holding ?r ?o) (at-place ?r ?p)) | ||
| :effect (and (not (holding ?r ?o)) | ||
| (not (hand-full ?r)) | ||
| (object-in-place ?o ?p))) | ||
|
|
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -480,6 +480,7 @@ def ground_problem( | |
| robot_states: dict, | ||
| goal: PddlGoal, | ||
| feedback: Any = None, | ||
| constraints: list = [], | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interface to |
||
| ) -> RobotWrapper[GroundedPddlProblem]: | ||
| logger.info(f"Grounding PDDL Problem {domain.domain_name}") | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,7 @@ | ||
| import logging | ||
| import os | ||
| import subprocess | ||
| import tempfile | ||
| from dataclasses import dataclass | ||
| from typing import Any, Dict, List | ||
|
|
||
|
|
@@ -7,7 +10,7 @@ | |
| from plum import dispatch | ||
|
|
||
| from dsg_pddl.dsg_pddl_grounding import GroundedPddlProblem, PddlDomain, PddlSymbol | ||
| from dsg_pddl.pddl_planning import solve_pddl | ||
| from dsg_pddl.pddl_utils import lisp_string_to_ast | ||
| from omniplanner.tsp import LayerPlanner | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
@@ -21,6 +24,90 @@ class PddlPlan: | |
| symbols: Dict[str, PddlSymbol] | ||
|
|
||
|
|
||
| DEFAULT_FD_SEARCH = ( | ||
| "let(hff, ff(), let(hcea, cea(), lazy_greedy([hff, hcea], preferred=[hff, hcea])))" | ||
| ) | ||
|
|
||
|
|
||
| def solve_pddl(problem: GroundedPddlProblem): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We really don't want a full duplicate implementation of the solve_pddl function that does almost the same thing as the existing one. Any functionality that was missing with the existing version of solve_pddl should be added there as a refactor if necessary. |
||
| """Use fast-downward to solve the given pddl problem. | ||
|
|
||
| The Fast Downward search can be overridden via environment variables: | ||
| ADT4_FD_ALIAS — use a Fast Downward alias (e.g. "seq-sat-lama-2011"). | ||
| Takes precedence over ADT4_FD_SEARCH when set. | ||
| ADT4_FD_SEARCH — raw value passed after `--search`. | ||
| If neither is set, the historical lazy-greedy(ff+cea) search is used. | ||
| """ | ||
| with tempfile.TemporaryDirectory() as tmpdirname: | ||
| problem_fn = os.path.join(tmpdirname, "problem.pddl") | ||
| domain_fn = os.path.join(tmpdirname, "domain.pddl") | ||
| plan_fn = os.path.join(tmpdirname, "plan.txt") | ||
|
|
||
| with open(problem_fn, "w") as fo: | ||
| fo.write(problem.problem_str) | ||
|
|
||
| with open(domain_fn, "w") as fo: | ||
| fo.write(problem.domain.to_string()) | ||
|
|
||
| alias = os.getenv("ADT4_FD_ALIAS", "").strip() | ||
| search = os.getenv("ADT4_FD_SEARCH", "").strip() | ||
| time_limit = os.getenv("ADT4_FD_OVERALL_TIME_LIMIT", "").strip() | ||
|
|
||
| command = ["fast-downward"] | ||
| command += ["--plan-file", plan_fn] | ||
| if time_limit: | ||
| command += ["--overall-time-limit", time_limit] | ||
| if alias: | ||
| command += ["--alias", alias] | ||
| command += [domain_fn, problem_fn] | ||
| else: | ||
| command += [domain_fn, problem_fn] | ||
| command += ["--search", search or DEFAULT_FD_SEARCH] | ||
|
|
||
| logger.warning(f"Calling: {command}") | ||
| return_code = subprocess.run(command) | ||
| logger.warning(f"Return code: {return_code}") | ||
|
|
||
| # Anytime aliases (e.g. seq-sat-lama-2011) write plan.txt.1, plan.txt.2, | ||
| # ... and do NOT write plan.txt. Pick the highest-numbered (best) one. | ||
| import glob | ||
|
|
||
| numbered = sorted( | ||
| glob.glob(plan_fn + ".*"), | ||
| key=lambda p: int(p.rsplit(".", 1)[-1]) | ||
| if p.rsplit(".", 1)[-1].isdigit() | ||
| else -1, | ||
| ) | ||
| if numbered and numbered[-1].rsplit(".", 1)[-1].isdigit(): | ||
| best_plan = numbered[-1] | ||
| elif os.path.exists(plan_fn): | ||
| best_plan = plan_fn | ||
| else: | ||
| best_plan = None | ||
|
|
||
| if best_plan is not None: | ||
| logger.warning(f"Reading plan from {best_plan}") | ||
| with open(best_plan, "r") as fo: | ||
| lines = fo.readlines() | ||
| else: | ||
| output_dir = os.getenv("ADT4_OUTPUT_DIR", "") | ||
| debug_fn = os.path.join(output_dir, "pddl_problem_debugging.pddl") | ||
| logger.warning( | ||
| f"Planning failed. Please see {debug_fn} for the failed problem file." | ||
| ) | ||
| with open(debug_fn, "w") as fo: | ||
| fo.write(problem.problem_str) | ||
| raise Exception( | ||
| f"Planning failed, please see {debug_fn} for failed problem file." | ||
| ) | ||
|
|
||
| plan = [lisp_string_to_ast(line) for line in lines[:-1]] | ||
| plan = [ | ||
| ("goto-poi",) + p[1:] if p and p[0] == "goto-poi-revisit" else p for p in plan | ||
| ] | ||
| return plan | ||
|
|
||
|
|
||
| def drop_index(t, k): | ||
| return t[:k] + t[k + 1 :] | ||
|
|
||
|
|
@@ -76,7 +163,7 @@ def parameterize_place_object_multirobot(layer_planner, symbols, action, last_po | |
| def make_plan(grounded_problem: GroundedPddlProblem, map_context: Any) -> PddlPlan: | ||
| plan = solve_pddl(grounded_problem) | ||
|
|
||
| logger.warning(f"Made plan {plan}") | ||
| # logger.warning(f"Made plan {plan}") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should keep this, although we can change from warning to info or debug |
||
|
|
||
| parameterized_plan = [] | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,7 @@ def ground_problem( | |
| robot_states: dict, | ||
| goal: GotoPointsGoal, | ||
| feedback: Any = None, | ||
| constraints: list = [], | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't want to change the interface to ground_problem. These constraints should be included in the goal class (for any kind of domain where we support constraints)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (applies to all instances of ground_problem and make_plan) |
||
| ) -> RobotWrapper[GroundedGotoPointsProblem]: | ||
| start = robot_states[goal.robot_id] | ||
|
|
||
|
|
@@ -78,6 +79,7 @@ def ground_problem( | |
| start: np.ndarray, | ||
| goal: list, | ||
| feedback: Any = None, | ||
| constraints: list = [], | ||
| ) -> GroundedGotoPointsProblem: | ||
| point_sequence = map_context[goal] | ||
| return GroundedGotoPointsProblem(start, point_sequence) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| string robot_id | ||
| string pddl_goal | ||
| string constraints # JSON list of constraint facts, e.g. '[["forbidden-poi","p123"]]'. Empty = no change. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two comments here -- 1) I think we should still have a simple pddl goal message that doesn't have the constraints. The right way of building the constraints message you want is probably making a ConstrainedPddlGoalMsg (or something like that) with two fields, a PddlGoalMsg and your constraints. 2) It's probably worth having the constraints field be a list of constraint messages, where each constraint message has a predicate and a list of symbol strings. The PDDL goal is currently a string because representing it with ROS message types would be quite a pain, but the list of constraints has a much simpler structure. |
||
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.
Do we actually need this in the planning domain vs. just excluding the place/edge when we construct the pddl problem instance?