Fleet: the blocked corridor
Two survey robots share a depot. One corridor leads out of it, and it may be blocked by debris. Only a robot standing at the junction can see down it, and a robot watching another one look learns that it looked, not what it saw. Both robots route around the corridor, so the mission is not finished until both know whether it is passable.
Nothing in this scenario changes the corridor. What changes is who knows about it, so a classical planner has nothing to work with here, and the solution is a policy and not a sequence.
The smallest scenario of the fleet family, and the one to read first: one corridor, one sensing action, one branch.
The mission
Agents |
|
The unknown |
whether the corridor is blocked; nobody has looked |
Actions |
drive to the junction, look down the corridor, broadcast what was found |
Goal |
both robots know whether the corridor is blocked |
The goal is a knowing-whether goal, and deliberately so. A mission that only succeeds when the corridor turns out to be blocked is not a mission, and a plan for it would be unexecutable half the time.
Formalization
The domain is robot-fleet, in
plansys2_epddl_grounder/examples/robot-fleet-domain.epddl. Two predicates
carry the whole scenario:
(:predicates
(blocked) ; the corridor is obstructed
(at-junction ?i - agent) ; ?i is where the corridor can be seen
)
The three mechanisms are one action each, and the difference between them is entirely in who observes what.
(:action goto-junction
:parameters (?i - agent)
:action-type (public-ontic (e-goto-junction ?i))
:observability-conditions (default Fully))
(:action inspect
:parameters (?i - agent)
:action-type (semi-private-sensing (e-inspect-blocked ?i) (e-inspect-clear ?i))
:observability-conditions (:and (?i Fully) (default Partially)))
(:action report-blocked
:parameters (?i - agent)
:action-type (public-announcement (e-knows-blocked ?i) (e-not-knows-blocked ?i))
:observability-conditions (default Fully))
report-clear is the mirror of report-blocked: a robot can only report
what it found, so the domain has two announcements in place of one action with
a parameter.
Fig. 3 What each action type does to the fleet’s knowledge
Semi-private sensing is what makes the fleet talk. If looking were public, one robot looking would inform the other and no announcement would be needed; if it were fully private, the other robot would not even know a look had happened. A camera on one robot is neither: the fleet sees the behaviour, not the image.
The initial theory
The problem file states what is common knowledge at the depot and, just as importantly, what it leaves open:
(:agents r1 r2)
(:init
(:and
(:forall (?i - agent) ([C. All] (not (at-junction ?i))))
(:forall (?i - agent) ([C. All] (<Kw. ?i> (blocked))))))
(:goal
(and ([Kw. r1] (blocked)) ([Kw. r2] (blocked))))
It never says whether the corridor is blocked. The theory is therefore satisfied by two worlds, both designated: the model is multi-pointed, and the corridor is one way or the other without anyone (including the planner) knowing which. That is the property that makes the solution branch, and the reason this scenario exists.
Fig. 4 The model through one execution: sensing removes r1’s uncertainty, the announcement removes r2’s
A double circle is a designated world, one the fleet cannot rule out as the
actual one. An edge labelled r1 is r1 being unable to tell the two apart.
The mission is finished when no agent has an edge left, the condition Kw
expresses.
Grounding
plank turns the EPDDL pair into the IePC JSON the planner searches over:
ros2 run plansys2_epddl_grounder ground_epddl \
-d <share>/plansys2_epddl_grounder/examples/robot-fleet-domain.epddl \
-p <share>/plansys2_epddl_grounder/examples/robot-fleet-problem.epddl \
-o /tmp/robot-fleet.json
The result is the checked-in
plansys2_epistemic_planner/test/tasks/robot-fleet.json: 2 agents, 3 atoms,
8 grounded actions, and 2 initial worlds, both designated. Grounding also
happens automatically on first use when the solver is given epddl_domain
and epddl_problem instead of a task_file.
The policy
AO* solves it at depth 3 and returns four policy nodes: drive out, look, and one announcement per outcome.
Fig. 5 The corridor policy. The branch is at the sensing action; each leaf is one way the corridor can turn out.
Two things in that tree are worth reading carefully.
The knowledge requirement on each announcement, (K r1 blocked) and
(K r1 (not blocked)), is not a PDDL precondition and cannot be checked
against the problem expert, because no predicate records it. It is checked
against the epistemic state by CheckKnowledge, and it is what stops the
fleet from broadcasting a guess.
The branch is the planner declining to commit. Both continuations are in the tree; which one runs is decided while the robot is standing at the junction with its camera on. A flattened version of this plan would be correct half the time.
Note also what the planner worked out: both robots could have driven out and looked, at four actions; one robot going, looking and broadcasting costs three. It found the cheaper one, which is to say it worked out that talking beats everybody going to see for themselves.
Running it
The planner. Point the solver at the task and its action mapping, and ask for the branches to be kept:
planner:
ros__parameters:
plan_solver_plugins: ["EPISTEMIC"]
EPISTEMIC:
plugin: "plansys2/EpistemicPlanSolver"
task_file: "<repo>/plansys2_epistemic_planner/test/tasks/robot-fleet.json"
action_mapping: "<share>/plansys2_epistemic_planner/examples/mappings/robot-fleet.json"
strategy: "aostar"
conditional_plan: "policy"
conditional_plan: "policy" is the setting that matters. The default,
"flatten", follows one branch and warns; it exists for a stock PlanSys2
executor, which can only run a sequence.
The mapping. The planner searches over grounded epistemic names and the executor drives PDDL actions, so the two vocabularies are related by a file:
{
"goto-junction_r1": {"action": "(goto_junction r1)", "duration": 30.0},
"inspect_r1": {"action": "(inspect_corridor r1)", "duration": 5.0},
"report-blocked_r1": {"action": "(report_blocked r1)", "duration": 1.0},
"report-clear_r1": {"action": "(report_clear r1)", "duration": 1.0}
}
The durations belong to the robot’s implementation of each action, not to the plan: the epistemic planner is untimed, and these are what the executor schedules and times out against.
The executor. Two parameters select the epistemic path:
executor:
ros__parameters:
bt_builder_plugin: "EpistemicBTBuilder"
bt_node_plugins: ["libplansys2_epistemic_bt_nodes.so"]
The state. epistemic_state must be loaded from the same task the
planner solved, or the policy’s knowledge requirements would be checked in a
vocabulary it does not speak:
ros2 run plansys2_epistemic_executor epistemic_state_node --ros-args \
-p task_file:=<repo>/plansys2_epistemic_planner/test/tasks/robot-fleet.json
The observation. With two designated worlds the epistemic state cannot
determine which outcome the sensing produced, so the answer must come from the
robot that performed it. The inspect_corridor performer supplies it on
finishing, in the outcome field of ActionExecution:
// in the performer's do_work, once the sensor has answered
finish(true, 1.0, "Corridor inspected", clear ? "e-inspect-clear"
: "e-inspect-blocked");
The token must name an outcome of the action’s event model, since the policy
branches on it. status remains the human-readable message. The packaged
action template then carries the value across without further configuration:
ExecuteAction writes it to the blackboard and ApplyEpistemicUpdate
reads it.
An observation originating elsewhere, such as an independently operating
perception node, is supplied by binding ApplyEpistemicUpdate’s observed
port in your own action template. See
Executing a policy end to end for a worked binding.
Performance
Measured with the packaged grounded task, aostar with the heuristic the
selection policy chooses (ks), on a release build:
Quantity |
Value |
Note |
|---|---|---|
Solution depth |
3 |
drive, look, report |
Nodes expanded |
17 |
the search barely searches |
Nodes generated |
19 |
|
Policy nodes |
4 |
one per action, both announcements included |
Leaves |
2 |
one per way the corridor can turn out |
Wall clock |
0.5 s |
dominated by process start-up, not by search |
Peak resident memory |
28 MB |
essentially the ROS process itself |
This scenario is small enough to run in a test suite, and it does:
plansys2_tests/test_5 executes it end to end over a live node graph, twice,
with the corridor blocked in one run and clear in the other.
Next
Fleet: the two-route depot is the same three mechanisms with two corridors and three robots, where the planner has to divide the labour and the policy branches twice.