This repository provides a Python tool for specifying box rearrangement planning problems
in a structured JSON format, compiling them into PDDL problem instances for the
BOX-WORLD domain, and optionally solving them using an external planner
(e.g., Fast Downward).
The tool supports two modes:
- convert — JSON → PDDL problem file
- solve — JSON → PDDL → planner → JSON plan output
./json-to-pddl.py convert ./examples/tiny-test.json -o tiny-test.pddl
./json-to-pddl.py solve ./examples/tiny-test.json --plan-json-out tiny-test.plan.jsonThe solve mode of this tool requires an external PDDL planner.
It has been tested with Fast Downward 24.06.1.
Fast Downward is not included in this repository and must be installed separately.
Download the latest Fast Downward release tarball from:
https://www.fast-downward.org/latest/releases/
For example:
wget https://www.fast-downward.org/latest/files/release24.06/fast-downward-24.06.1.tar.gztar -xzf fast-downward-24.06.1.tar.gz
cd fast-downward-24.06.1This will create a directory containing the Fast Downward source code.
python3 build.pyThis compiles Fast Downward and creates the launcher script:
fast-downward.py
./fast-downward.py --helpYou should see the Fast Downward help message.
By default, the solver assumes the planner is available at:
./fast-downward-24.06.1/fast-downward.py
You can override the planner path explicitly:
./json-to-pddl.py solve problem.json \
--planner /path/to/fast-downward.py \
--plan-json-out plan.json- Fast Downward 24.06.1
- Python 3.9+
- Linux / WSL environments
./json-to-pddl.py convert problem.json -o problem.pddlIf -o is omitted, the generated PDDL problem is printed to stdout.
./json-to-pddl.py solve problem.json \
--domain box-world-domain.pddl \
--planner ./fast-downward.py \
--plan-json-out plan.jsonIf --plan-json-out is omitted, the plan JSON is printed to stdout.
The planner is expected to write multiple plan files of the form:
plan.1, plan.2, ..., plan.N
Larger numeric suffixes correspond to better plans.
The tool automatically selects the best plan.
{
"problem_name": "...",
"locations": [... or {...}],
"boxes": [... or {...}],
"initial_state": { ... },
"forbidden_stack": [...], // optional
"goal": { ... }
}Required fields:
problem_namelocationsboxesinitial_stategoal
Optional fields:
forbidden_stack
Locations may be specified in either minimal or property-annotated form.
"locations": ["L1", "L2", "L3"]"locations": {
"L1": { "color": "white" },
"L2": { "color": "black" },
"L3": {}
}Supported property:
color:"black"|"white"
These map to PDDL predicates:
(black L)(white L)
Unknown properties are ignored by the generator.
Boxes follow the same structure and semantics as locations.
"boxes": ["B1", "B2", "B3"]"boxes": {
"B1": { "color": "black" },
"B2": {},
"B3": { "color": "white" }
}Properties are optional and handled identically to location properties.
The entire initial world state is specified under initial_state.
"initial_state": {
"robot_at": "L1",
"holding": null,
"stacks": {
"L1": ["B1", "B2", "B3"]
}
}Fields:
-
robot_at(required)
Starting location of the robot. -
holding(optional)
Name of a box the robot starts holding, ornull.
If omitted ornull, the robot starts with empty hands. -
stacks(required)
Mapping from non-empty locations to stacks of boxes.
Stacks are listed from top to bottom.
"L1": ["B1", "B2", "B3"]represents:
B1 (top)
B2
B3 (bottom)
L1 (location)
Locations omitted from stacks are assumed empty.
For a stack at location L with boxes [t0, t1, ..., tk] (top → bottom), the
following PDDL facts are generated:
(on t0 t1)(on t{i} t{i+1})fori = 0..k-1(on tk L)(clear t0)(box-at ti L)for all boxes in the stack
For locations not listed in stacks:
(clear L)
Robot hand state:
- If
holding = B:(holding B) - Otherwise:
(hands-empty)
Invariant:
Each box must appear exactly once across {holding} ∪ stacks.
"forbidden_stack": [
["B2", "B1"],
["B3", "B2"]
]Each pair [top, bottom] generates:
(forbidden-stack top bottom)
If omitted, no stacking constraints are imposed.
Goals describe the desired final configuration of the world.
Supported structured predicates:
on— box on box or box on locationbox-at— box is in a stack at a locationclear— object (box or location) is clear
All structured goal predicates are implicitly conjoined.
"goal": {
"on": [
["B2", "B3"],
["B3", "L2"]
],
"clear": ["B2"]
}Equivalent PDDL goal:
(and
(on B2 B3)
(on B3 L2)
(clear B2)
)Advanced users may include raw PDDL formulas using the pddl field.
"goal": {
"pddl": [
"(robot-at L2)",
"(exists (?x - box) (and (clear ?x) (not (holding ?x))))"
]
}Notes:
- Each formula must be a string
- All formulas are conjoined with structured goals
- Verbatim formulas are parsed and validated before PDDL is emitted
Supported formula forms:
- Predicate atoms using
holding,hands-empty,robot-at,box-at,forbidden-stack,on,clear,black, orwhite - Logical forms:
and,or,not - Quantifiers:
existsandforallwith typed variables - Equality:
=
Validation rules:
- Constants must refer to declared boxes or locations.
- Variables must be bound by an enclosing
existsorforall. - Predicate arity and argument types must match the
BOX-WORLDdomain. - Supported types are
box,location, andobject. - Full PDDL declarations, actions, effects, numeric expressions, and durative constructs are not supported in
goal.pddl.
Example validation error:
Invalid goal.pddl[0]: predicate 'robot-at' expects location, got box term 'B1'
When run in solve mode, the tool outputs a JSON plan file.
{
"plan": [
{"unstack": ["b1", "b2", "l1"]},
{"locomotion": ["l1", "l2"]},
{"putdown": ["b1", "l2"]},
{"locomotion": ["l2", "l1"]},
{"pickup": ["b2", "l1"]},
{"locomotion": ["l1", "l2"]},
{"stack": ["b2", "b1", "l2"]}
],
"cost": 12
}Fields:
plan: ordered list of action strings (as produced by the planner)cost: integer plan cost, ornullif unavailable
Plan Actions:
{"locomotion": ["l1", "l2"]}: robot moves from location l1 to location l2{"pickup": ["b2", "l1"]}: pickup box b that is directly on location l{"putdown": ["b1", "l2"]}: putdown box b (currently held) directly onto location l (currently clear){"unstack": ["b1", "b2", "l1"]}: unstack box b1 that is currently on box b2 at location l (b2 may be on l or another box){"stack": ["b2", "b1", "l2"]}: stack box b1 (currently held) onto box b2 (currently clear) at location l (b2 may be on l or another box)
If --plan-json-out is not specified, this JSON object is printed to stdout.
{
"problem_name": "tiny",
"locations": ["L1", "L2"],
"boxes": ["B1"],
"initial_state": {
"robot_at": "L1",
"stacks": {
"L1": ["B1"]
}
},
"goal": {
"on": [["B1", "L2"]]
}
}- This document describes v1 of the Box-World JSON format.
- Structured goals are limited to conjunctions.
- Future versions may add:
- disjunction (
or) - quantifiers (
exists,forall) - richer plan metadata and validation
- disjunction (