Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]
max-line-length = 100
extend-ignore = E203, W503
exclude = .venv, pddl-examples, build, dist, .git, __pycache__
28 changes: 6 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
# Python 3.8 is the newest interpreter for which the pinned
# dependencies (matplotlib==3.5.1, pyjulia==0.5.7) resolve a
# compatible NumPy 1.x. Newer interpreters pull NumPy 2.x and break
# matplotlib 3.5.1's ABI.
python-version: ['3.8']
python-version: ['3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand All @@ -22,24 +19,11 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: julia-actions/setup-julia@v2
with:
version: '1.5.2'
- name: Install Python dependencies
- name: Install package (dev extras)
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Install Julia dependencies
# Build PyCall against *this* Python interpreter (via pyjulia's
# installer) so the in-process bridge used by jupyddl works, then add
# the PDDL.jl parser fork.
run: |
python -c "import julia; julia.install()"
julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(url="https://github.com/APLA-Toolbox/PDDL.jl"))'
python -m pip install -e ".[dev]"
- name: Lint with flake8
run: |
python -m pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
flake8 jupyddl tests --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 jupyddl tests --count --statistics
13 changes: 6 additions & 7 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,22 @@ jobs:
format:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- uses: actions/checkout@v2
- name: Set up Python 3.7
uses: actions/setup-python@v3
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.14
python-version: '3.12'
- name: Install formatter dependencies
run: |
python -m pip install --upgrade pip
python -m pip install black
- name: Format with black
run: |
black .
black jupyddl tests
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v4.14.0
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Apply formatting changes
branch: main
60 changes: 8 additions & 52 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,14 @@ name: tests
on: [push]

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
python-version: ['3.8']

steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: julia-actions/setup-julia@v2
with:
version: '1.5.2'
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Install Julia dependencies
run: |
python -c "import julia; julia.install()"
julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(url="https://github.com/APLA-Toolbox/PDDL.jl"))'
- name: Lint with flake8
run: |
python -m pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ['3.8']
python-version: ['3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v4
with:
Expand All @@ -51,29 +19,17 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: julia-actions/setup-julia@v2
with:
version: '1.5.2'
- name: Install Python dependencies
- name: Install package (dev extras)
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install flake8 pytest pytest-cov
- name: Install Julia dependencies
# Build PyCall against this Python (needed so pytest can import the
# jupyddl -> PDDL.jl bridge), then add the PDDL.jl parser fork.
run: |
python -c "import julia; julia.install()"
julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(url="https://github.com/APLA-Toolbox/PDDL.jl"))'
python -m pip install -e ".[dev]"
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
flake8 jupyddl tests --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 jupyddl tests --count --statistics
- name: Test with pytest
run: |
pytest --cov=./
pytest --cov=jupyddl --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
Expand Down
73 changes: 31 additions & 42 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,37 @@

## Cursor Cloud specific instructions

`jupyddl` (PythonPDDL) is a Python library + CLI for PDDL automated planning. It is a
thin wrapper around the Julia `PDDL.jl` parser, bridged through `pyjulia`/`PyCall.jl`.
There are no servers/databases — "running the app" means parsing PDDL domain/problem
files and running the planners, either via the library or the `scripts/ipc.py` CLI.
`jupyddl` is a **pure-Python** PDDL planning framework (parser, grounder,
planners, heuristics, benchmarking). The Julia/`PDDL.jl`/PyCall integration has
been removed — there is no Julia, no native build step, and the core has zero
runtime dependencies.

### Environment layout (baked into the VM snapshot)
- Julia 1.5.2 in `/opt/julia-1.5.2` (symlinked at `/usr/local/bin/julia`).
- Python 3.8 venv at `.venv`, built from the **deadsnakes** `/usr/bin/python3.8`.
- Julia packages `PyCall.jl` + the `APLA-Toolbox/PDDL.jl` fork live in `~/.julia`.
`PyCall` is built against `.venv/bin/python`.
- `.venv`, `logs/*`, and the `pddl-examples` submodule contents are git-ignored.
### Environment
- Python ≥ 3.9; a `.venv` (created with `uv`, Python 3.12) with an editable
install: `uv pip install -e ".[dev]"` (add `viz` for matplotlib-based
benchmark plots). `.venv` and `pddl-examples/` contents are git-ignored.
- The `pddl-examples` git submodule supplies the domains/problems the tests use;
it must be initialised (`git submodule update --init`).

### Running / testing / linting
Always use the venv interpreter and run from the repo root (relative `pddl-examples/...`
paths and the auto-created `logs/` dir depend on CWD):
- Library / hello-world: `.venv/bin/python -c "from jupyddl import AutomatedPlanner; ..."`
- CLI: `cd scripts && ../.venv/bin/python ipc.py <domain.pddl> <problem.pddl> <output>`
- Tests: `.venv/bin/python -m pytest --cov=./` (from repo root).
- Lint (as CI): `.venv/bin/python -m flake8 . --select=E9,F63,F7,F82` is the build-gating
check; the second CI `flake8` pass uses `--exit-zero` (style warnings only, non-blocking).
### Running / testing / linting (use the venv interpreter)
- Tests: `.venv/bin/python -m pytest` (add `--cov=jupyddl`).
- Lint (as CI): `flake8 jupyddl tests` (config in `.flake8`, max-line 100).
- CLI: `.venv/bin/python -m jupyddl.cli solve <domain> <problem> -s astar -H lmcut`
or `... benchmark pddl-examples --csv out.csv`. Installed as `jupyddl` too.

### Non-obvious gotchas
- **pyjulia needs a dynamically-linked Python.** The `.venv` intentionally uses the
deadsnakes `python3.8` (dynamically linked). Do **not** rebuild the venv from a
`uv`-managed / python-build-standalone interpreter — those are statically linked to
libpython and break the in-process Julia bridge.
- **If the venv is recreated at a different path, rebuild PyCall** so it points at the new
interpreter: `PYTHON=/workspace/.venv/bin/python .venv/bin/python -c "import julia; julia.install()"`.
- **matplotlib backend.** `jupyddl/data_analyst.py` picks `TkAgg` when `DISPLAY` is set and
`Agg` otherwise. The VM has a virtual display (`DISPLAY=:1`) and `python3.8-tk` is
installed, so the default import works. For a purely headless run, invoke with
`env -u DISPLAY ...` (or `MPLBACKEND=Agg` when the import path allows it) to force `Agg`.
- **`DISPLAY` changes the test outcome.** `DataAnalyst.__get_all_pddl_from_data` only walks
the whole `pddl-examples/` folder when `DISPLAY` is set; otherwise it returns a hardcoded
`dinner`-only list. Headless (no `DISPLAY`, as in CI) the full suite is green — run tests
with `env -u DISPLAY .venv/bin/python -m pytest` to reproduce CI (all 77 pass). With this
VM's `DISPLAY=:1`, 19 `DataAnalyst` tests fail with `PyCall.jlwrap ... 'domain' keyword is
missing`: the folder walk pairs files from an unsorted `os.walk` assuming `domain.pddl`
precedes `problem.pddl`, but the `pallet` example is ordered the other way, so a problem
file is parsed as a domain. That is a pre-existing code bug, not an environment issue.
- **CI (`.github/workflows/*.yml`) is pinned to Python 3.8 on `ubuntu-latest`.** 3.8 is the
newest interpreter whose resolved deps keep NumPy at 1.x (matplotlib 3.5.1 breaks on NumPy
2.x). macOS runners were dropped: they are arm64, which has no Julia 1.5.2 build and no EOL
Python build. CI builds PyCall against the runner Python with `python -c "import julia;
julia.install()"` before adding `PDDL.jl`.
### Non-obvious notes
- **Example data quirks (external submodule, do not "fix" in this repo):**
`grid` uses numeric fluents and is intentionally unsupported (raises
`UnsupportedFeatureError`); `vehicle` has typos in its problem file
(`struck`/`truck`, `acessible`) so its goal is unreachable and it is correctly
reported unsolvable. Tests treat both as expected.
- **Conditional effects (`flip`)**: the delete-relaxation heuristics (`hadd`,
`hff`, `lmcut`, `h^m`) are *not guaranteed admissible* on domains with
conditional effects because each conditional effect is relaxed into its own
operator. For guaranteed-optimal plans there use `bfs`, `dijkstra`, or
`astar`/`idastar` with the `blind` heuristic. Optimality tests use these.
- **Matplotlib is optional**: only `jupyddl.benchmark.plot_summary` (and
`jupyddl solve/benchmark --plot`) need it; run headless with `MPLBACKEND=Agg`
if no display. The test suite does not require it.
- Extend via the registries: `jupyddl.search.PLANNERS` and
`jupyddl.heuristics.HEURISTICS`.
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Changelog

All notable changes to this project are documented in this file. The format is
based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-07-01

Complete rewrite: **the Julia dependency is removed** and the project is now a
pure-Python planning framework.

### Added
- Hand-written PDDL tokenizer, AST and recursive-descent parser
(`jupyddl.parser`).
- Grounder (`jupyddl.grounding`) with type hierarchies, static-predicate
pruning, positive-normal-form compilation of negative preconditions/goals,
object harvesting for undeclared constants, and `forall`/`when` conditional
effect expansion.
- Grounded task representation with conditional effects (`jupyddl.task`).
- Planners (`jupyddl.search`): BFS, DFS, Iterative Deepening, Dijkstra
(uniform cost), Greedy Best-First, A*, Weighted A*, IDA*, and Enforced Hill
Climbing, plus a shared best-first engine and a planner registry.
- Heuristics (`jupyddl.heuristics`): blind, goal-count, `h_max`, `h_add`, FF,
critical-path `h^m` (`h1`/`h2`), and LM-cut, plus a heuristic registry.
- Benchmarking harness (`jupyddl.benchmark`) with CSV export and comparison
plots, and a CLI (`jupyddl solve` / `jupyddl benchmark`).
- High-level API: `solve`, `build_task`, `solve_task`, `validate_plan`.
- Comprehensive pytest suite covering parsing, grounding, search optimality,
heuristic admissibility, the API, the benchmark harness and the CLI.

### Changed
- Packaging migrated from `setup.py`/`requirements.txt` to `pyproject.toml`
(hatchling); the core has **zero runtime dependencies** (matplotlib is an
optional `viz` extra).
- CI reworked to run on modern Python without Julia.

### Removed
- The Julia / `PDDL.jl` / PyCall / pyjulia integration and the old
`AutomatedPlanner` / `DataAnalyst` API.
Loading
Loading