Getting Started¶
Installation¶
See the GitHub page for installation instructions.
Loading PDDL¶
PDDL specifications get loaded into a Pddl
object. From here, you can list
the environment objects, apply actions to states, perform basic planning, etc.
C++¶
#include <symbolic/symbolic>
int main(int argc, char* argv[]) {
// Load PDDL.
symbolic::Pddl pddl("resources/domain.pddl", "resources/problem.pddl");
// Validate PDDL.
pddl.IsValid(/*verbose=*/true);
return 0;
}
See the C++ symbolic::Pddl reference for more details.
Python¶
import symbolic
# Load PDDL.
pddl = symbolic.Pddl("resources/domain.pddl", "resources/problem.pddl")
# Validate PDDL.
pddl.is_valid(verbose=True)
See the Python symbolic.Pddl
reference for more details.
States¶
Symbolic states are represented as sets of propositions that are true using the closed-world assumption. This means that propositions not included in the set are assumed to be false.
C++¶
The State
class can be instantiated in numerous ways. The easiest is to treat
it as a set of proposition strings.
symbolic::State s(pddl, {"on(box, table)", "inworkspace(table)"});
Or using explicit types:
symbolic::Object box(pddl, "box");
symbolic::Object table(pddl, "table");
symbolic::Proposition on_box_table("on", {box, table});
symbolic::Proposition inworkspace_table("inworkspace", {table});
symbolic::State s = {on_box_table, inworkspace_table};
See the C++ symbolic::State reference for more details.
Python¶
In Python, states are simply represented as sets of proposition strings.
s = {"on(box, table)", "inworkspace(table)"}
Actions¶
Actions whose pre-conditions are satisfied can modify states according to their post-conditions. Action calls (actions whose parameters are instantiated with actual objects) are represented as strings.
C++¶
symbolic::State s(pddl, {"on(box, table)", "inworkspace(box)", "inworkspace(table)"});
std::string action_call = "pick(box, table)";
// Check pre-conditions.
if (pddl.IsValidAction(s, action_call)) {
// Apply post-conditions.
symbolic::State s_next = pddl.NextState(s, action_call);
}
Or using explicit types:
// Set up state.
symbolic::Object box(pddl, "box");
symbolic::Object table(pddl, "table");
symbolic::Proposition on_box_table("on", {box, table});
symbolic::Proposition inworkspace_table("inworkspace", {table});
symbolic::State s = {on_box_table, inworkspace_table};
// Set up action.
symbolic::Action action(pddl, "pick");
std::vector<symbolic::Object> args = {box, table};
// Check pre-conditions.
if (action.IsValid(s, args)) {
// Apply post-conditions.
symbolic::State s_next = action.Apply(s, args);
}
See the C++ symbolic::Pddl::NextState() and symbolic::Action references for more details.
Python¶
s = {"on(box, table)", "inworkspace(box)", "inworkspace(table)"}
action_call = "pick(box, table)"
# Check pre-conditions.
if pddl.is_valid_action(s, action_call):
# Apply post-conditions.
s_next = pddl.next_state(s, action_call)
See the Python symbolic.Pddl.next_state()
and symbolic.Action
references for more details.
Goals¶
PDDL goals are specified as first-order logic formulas. Checking if a state satisfies the goal condition is simple:
C++¶
symbolic::State s = pddl.initial_state();
if (pddl.IsGoalSatisfied(s)) {
std::cout << "Done!" << std::endl;
}
See the C++ symbolic::Pddl reference for more details.
Python¶
s = pddl.initial_state
if pddl.is_goal_satisfied(s):
print("Done!")
See the Python symbolic.Pddl
reference for more details.
Planning¶
symbolic
provides a breadth first search planner for planning in simple
domains.
C++¶
#include <symbolic/symbolic>
int main(int argc, char* argv[]) {
// Load PDDL.
symbolic::Pddl pddl("resources/domain.pddl", "resources/problem.pddl");
// Initialize basic forward planner from the PDDL initial state.
symbolic::Planner planner(pddl);
symbolic::BreadthFirstSearch bfs(planner.root(), args.depth, /*verbose=*/false);
// Perform BFS until the first valid plan.
std::cout << "Planning..." << std::endl;
std::vector<symbolic::Planner::Node> plan = *bfs.begin();
// Extract list of actions to execute.
// The first nodes in plans returned by BFS just contain the initial state (no action).
std::vector<std::string> action_skeleton;
action_skeleton.reserve(plan.size() - 1);
for (size_t i = 1; i < plan.size(); i++) {
action_skeleton.push_back(plan[i].action());
}
// Execute plan.
std::cout << "Executing plan..." << std::endl;
symbolic::State s = pddl.initial_state();
for (const std::string& a : action_skeleton) {
s = pddl.NextState(s, a);
}
std::cout << "Final state: " << s << std::endl;
std::cout << "Is goal satisfied? " << pddl.IsGoalSatisfied(s) << std::endl;
// Find all valid plans.
std::cout << "Planning..." << std::endl;
size_t idx_plan = 0;
for (const std::vector<symbolic::Planner::Node>& plan : bfs) {
std::cout << "Solution " << idx_plan << std::endl
<< "===========" << std::endl;
// Iterate over all nodes in the plan.
for (const symbolic::Planner::Node& node : plan) {
std::cout << node << std::endl;
}
idx_plan++;
}
return 0;
}
See the C++ symbolic::Planner and symbolic::BreadthFirstSearch references for more details.
Python¶
import symbolic
# Load PDDL.
pddl = symbolic.Pddl("resources/domain.pddl", "resources/problem.pddl")
# Initialize basic forward planner from the PDDL initial state..
planner = symbolic.Planner(pddl)
bfs = symbolic.BreadthFirstSearch(planner.root, max_depth=5, verbose=False)
# Perform BFS until the first valid plan.
print("Planning...")
plan = next(iter(bfs))
# Extract list of actions to execute.
# The first nodes in plans returned by BFS just contain the initial state (no action).
action_skeleton = [node.action for node in plan[1:]]
# Execute plan.
print("Executing plan...")
s = pddl.initial_state
for a in action_skeleton:
s = pddl.next_state(s, a)
print(f"Final state: {s}\n")
print(f"Is goal satisfied? {pddl.is_goal_satisfied(s)}\n")
# Find all valid plans.
print("Planning...")
for idx_plan, plan in enumerate(bfs):
print(f"Solution {idx_plan}")
print("===========")
# Iterate over all nodes in the plan.
for node in plan:
print(node)
See the Python symbolic.Planner
and
symbolic.BreadthFirstSearch
references for more details.