-
Notifications
You must be signed in to change notification settings - Fork 1
Revealables
Revealables are a feature in PDDLSIM that problems can use via the :revealables requirement. Revealables allow you to change the state of a problem once some condition holds true. Revealables are one of two features in PDDLSIM (the other being fallible actions) that are considered "hidden information", i.e., the agent will know they are used, but not in which actual way (by default). Typically, revealables are used to implement "secrets", such as hidden treasure in a maze, a secret opening in a dungeon, etc.
To illustrate the usage of revealables, let's implement a dungeon with a secret door. To begin, we will create a dungeon PDDL domain:
(define (domain dungeon)
(:requirements :equality :typing :negative-preconditions :disjunctive-preconditions)
(:types locationed room - object person switch - locationed)
(:predicates (connected ?a ?b - room ?s - switch)
(at ?l - locationed ?r - room)
(on ?s - switch))
(:action move
:parameters (?p - person ?from ?to - room ?s - switch)
:precondition (and (at ?p ?from)
(not (= ?from ?to))
(or (connected ?from ?to ?s)
(connected ?to ?from ?s))
(on ?s))
:effect (and (at ?p ?to)
(not (at ?p ?from))))
(:action turn-on
:parameters (?p - person ?s - switch ?r - room)
:precondition (and (at ?p ?r)
(at ?s ?r)
(not (on ?s)))
:effect (on ?s)))Essentially, the dungeon domain consists of three parts:
- The dungeon is made of rooms, which are connected by doors, locked by switches
- To open doors, their switch, existing in some room, must be turned on
- People, can turn on switches and navigate between rooms
To model doors with no switch, problems can make a dummy switch, which starts the problem in the "on" state.
Using the dungeon domain, we will now create a simple dungeon problem, and then extend it with revealables:
(define (problem darkest-dungeon)
(:domain dungeon)
(:objects start-room
room-right
room-top
room-left
goal-room - room
dummy-switch
switch-right
goal-switch - switch
bob - person)
(:init (at bob start-room)
(on dummy-switch)
(connected start-room room-right dummy-switch)
(at switch-right room-right)
(connected start-room room-top switch-right)
(connected start-room room-left switch-right)
(at goal-switch room-top)
(connected start-room goal-room goal-switch))
(:goal (at bob goal-room)))This is a dungeon with four rooms: a starting room, and four rooms connected to in each direction. To its bottom, the goal room. That room however, is locked behind a switch found in the top room, which is itself also locked, by a switch found in the right room, which is unlocked. At the moment, completing this problem is trivial, and can be done with the following series of actions:
(move bob start-room room-right dummy-switch)
(turn-on bob switch-right room-right) ; Unlock top room
(move bob room-right start-room dummy-switch)
(move bob start-room room-top switch-right)
(turn-on bob goal-switch room-top) ; Unlock goal room
(move bob room-top start-room switch-right)
(move bob start-room goal-room goal-switch) ; Finish the dungeon
Of course, the main reason this problem is so simple to solve, beyond its small size, is the fact we know all of the information in it perfectly! One can take a look at the problem description, and easily find a good route between all of the rooms to solve the problem. As hinted to above, we are going to solve this issue using revealables, by making it so the contents of some rooms aren't known until they are reached:
(define (problem darkest-dungeon)
(:domain dungeon)
(:requirements :revealables) ; Adding this requirement is necessary
(:objects start-room
room-right
room-top
room-left
goal-room - room
dummy-switch
switch-right
goal-switch - switch)
(:reveals (when (at bob room-right) (at switch-right room-right)) ; Moved from `:init`
(when (at bob room-top) (at goal-switch room-top))) ; Moved from `:init`
(:init (at bob start-room)
(on dummy-switch)
(connected start-room room-right dummy-switch)
(connected start-room room-top switch-right)
(connected start-room room-left switch-right)
(connected start-room goal-room goal-switch))
(:goal (at bob goal-room)))First, we added the :revealables requirement to the problem, as we use when statements. Then, we moved the parts of :init that reveal information on the contents of certain rooms (room-right and room-top), to the :reveals section. Once their condition is activated, the predicates are added to the state, and become like any other predicate in the state.
This means that (at goal-switch room-top) for example, only becomes true when (at bob room-top). Furthermore, even once (at bob room-top) no longer holds, (at goal-switch room-top) is still true.
Note
In older, now unsupported versions of PDDLSIM (pre 0.2.0), the semantics around revealables used to be different: the revealables would only hold as long as their when condition holds. To simplify the implementation, and allow for more interactions with revealables, we have since changed this behavior, to the one described above.
Let's now take a look at this problem from the perspective of an agent. First, the agent would see the following problem:
(define (problem darkest-dungeon)
(:domain dungeon)
(:requirements :revealables)
(:objects start-room
room-right
room-top
room-left
goal-room - room
dummy-switch
switch-right
goal-switch - switch
bob - person)
(:init (at bob start-room)
(on dummy-switch)
(connected start-room room-right dummy-switch)
(connected start-room room-top switch-right)
(connected start-room room-left switch-right)
(connected start-room goal-room goal-switch))
(:goal (at bob goal-room)))In other words, while the agent will know the problem has a :reveals section (see the requirement in the above text), it will not know what precise reveals are used. Let's now show the beginning of a solution to the problem from the agent's perspective, by successively performing actions and running get_perceived_state using a hypothetical SimulationClient.
With our first get_perceived_state, we will see the following state:
(at bob start-room)
(on dummy-switch)
(connected start-room room-right dummy-switch)
(connected start-room room-top switch-right)
(connected start-room room-left switch-right)
(connected start-room goal-room goal-switch)
as expected. As the hypothetical agent, with only one real option, we will perform (move bob start-room room-right dummy-switch). Now, looking at the definition of move from the dungeon domain, we would expect the next state to look like:
(at bob room-right)
(on dummy-switch)
; Not showing the `(connected ...)` predicates for brevity
But after running get_perceived_state again, we actually see:
(at bob room-right)
(at switch-right room-right)
(on dummy-switch)
; Not showing the `(connected ...)` predicates for brevity
Given this discrepancy, the agent must conclude a revealable has been activated, as is indeed the case. With this, we can now perform (turn-on bob switch-right room-right), activating the switch. Let's now move back to the start room, with (move bob room-right start-room dummy-switch). If we run get_perceived_state here again, notice how the state becomes:
(at bob start-room)
(at switch-right room-right)
(on switch-right)
(on dummy-switch)
; Not showing the `(connected ...)` predicates for brevity
While the revealable's condition no longer holds, its predicate is still in the state, as nothing has removed it. Likewise, predicates added to the state thanks to the revealable are also still present (on switch-right)
Naturally, we can keep going like before, eventually solving the problem.
In PDDLSIM, the full form of a revealable statement is:
(when [PROBABILITY] <CONDITION> <EFFECT>)
where <CONDITION> is a grounded condition, [PROBABILITY] an optional decimal number representing the revealable's probability and defaulting to 1.0, and <EFFECT> is a grounded effect. Semantically, the statement means "when <CONDITION> with probability [PROBABILITY], apply <EFFECT> and mark revealable as activated". When the effect is partially probabilistic (see Probabilistic Effects), the revealable's result will naturally be random. Finally, conditions are tested only:
- At the beginning of simulation
- On every successful action by the agent (meaning the action didn't fail due to fallible actions)