Skip to content

Commit fbd1e3e

Browse files
committed
fix hirerchey
1 parent a6bfcb5 commit fbd1e3e

6 files changed

Lines changed: 112 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [S
66

77
## [Unreleased]
88

9+
## [0.1.2] — 2026-04-11
10+
11+
### Fixed
12+
- Plan structure validation now honors PDDL type hierarchies. Objects whose
13+
type is a transitive subtype of an action parameter's declared type are
14+
accepted (e.g., an object typed `depot` satisfies a `place` parameter when
15+
the domain declares `depot - place`). Previously the `is_compatible` check
16+
was called with swapped arguments, rejecting every typed IPC benchmark
17+
(depots, driverlog, rovers, ...) at the structure phase.
18+
19+
### Added
20+
- Regression tests (`tests/test_validator.py`) with a small `vhcl` domain
21+
exercising both positive (subtype accepted) and negative (sibling branch
22+
rejected) cases, plus matching fixtures in `tests/conftest.py`.
23+
924
## [0.1.1] — 2026-04-09
1025

1126
### Added

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pddl-pyvalidator"
7-
version = "0.1.1"
7+
version = "0.1.2"
88
description = "Pure Python PDDL plan validator"
99
license = "MIT"
1010
requires-python = ">=3.10"

pyval/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""PyVAL — Pure Python PDDL plan validator."""
22

3-
__version__ = "0.1.1"
3+
__version__ = "0.1.2"
44

55
from pyval.models import (
66
GoalResult,

pyval/validator.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ def _validate_plan_structure(
170170
original_names[obj.name] = param_name_raw
171171

172172
expected_type = action_schema.parameters[i].type
173-
if not obj.type.is_compatible(expected_type):
173+
# `Type.is_compatible(self, other)` returns True iff an object of
174+
# type `other` can be assigned to a slot of type `self`. The
175+
# parameter type is the target, so it must be on the left.
176+
if not expected_type.is_compatible(obj.type):
174177
errors.append(
175178
f"Step {step_idx}: Action '{action_name_raw}' — "
176179
f"parameter {i + 1} expects type '{expected_type}', "

tests/conftest.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,60 @@
171171
"""
172172

173173

174+
# ---------------------------------------------------------------------------
175+
# Typed hierarchy: vehicles + cargo (for subtype compatibility checks)
176+
# ---------------------------------------------------------------------------
177+
178+
TYPED_HIERARCHY_DOMAIN = """\
179+
(define (domain vhcl)
180+
(:requirements :strips :typing)
181+
(:types
182+
vehicle cargo - object
183+
truck plane - vehicle)
184+
(:predicates
185+
(parked ?v - vehicle)
186+
(stored ?c - cargo)
187+
)
188+
189+
(:action start
190+
:parameters (?v - vehicle)
191+
:precondition (parked ?v)
192+
:effect (not (parked ?v))
193+
)
194+
)
195+
"""
196+
197+
TYPED_HIERARCHY_PROBLEM = """\
198+
(define (problem vhcl-p1)
199+
(:domain vhcl)
200+
(:objects
201+
truck1 - truck
202+
plane1 - plane
203+
box1 - cargo
204+
)
205+
(:init
206+
(parked truck1)
207+
(parked plane1)
208+
(stored box1)
209+
)
210+
(:goal (and (not (parked truck1)) (not (parked plane1))))
211+
)
212+
"""
213+
214+
# Subtype plan: (start truck1) passes a `truck` where `vehicle` is expected.
215+
# Must be accepted — PDDL typing is hierarchical.
216+
TYPED_HIERARCHY_SUBTYPE_PLAN = """\
217+
(start truck1)
218+
(start plane1)
219+
"""
220+
221+
# Sibling-branch plan: (start box1) passes a `cargo` where `vehicle` is
222+
# expected. Must be rejected — cargo is not in vehicle's subtree.
223+
TYPED_HIERARCHY_SIBLING_PLAN = """\
224+
(start box1)
225+
"""
226+
227+
174228
# ---------------------------------------------------------------------------
175229
# Helpers
176230
# ---------------------------------------------------------------------------

tests/test_validator.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
BLOCKSWORLD_VALID_PLAN,
77
LOGISTICS_FUEL_DOMAIN,
88
LOGISTICS_FUEL_PROBLEM,
9+
TYPED_HIERARCHY_DOMAIN,
10+
TYPED_HIERARCHY_PROBLEM,
11+
TYPED_HIERARCHY_SIBLING_PLAN,
12+
TYPED_HIERARCHY_SUBTYPE_PLAN,
913
write_pddl_files,
1014
)
1115

@@ -86,6 +90,39 @@ def test_empty_plan(tmp_path):
8690
assert result.phases["structure"]["status"] == "PASS"
8791

8892

93+
def test_subtype_object_accepted_for_supertype_parameter(tmp_path):
94+
"""Action parameter typed `vehicle` must accept a `truck` (subtype)."""
95+
paths = write_pddl_files(
96+
tmp_path,
97+
TYPED_HIERARCHY_DOMAIN,
98+
TYPED_HIERARCHY_PROBLEM,
99+
TYPED_HIERARCHY_SUBTYPE_PLAN,
100+
)
101+
v = PDDLValidator()
102+
result = v.validate(paths["domain"], paths["problem"], paths["plan"])
103+
assert result.phases["structure"]["status"] == "PASS", (
104+
result.phases["structure"]["errors"]
105+
)
106+
107+
108+
def test_sibling_type_rejected_for_parameter(tmp_path):
109+
"""An object from a sibling branch (cargo) must NOT satisfy a vehicle parameter."""
110+
paths = write_pddl_files(
111+
tmp_path,
112+
TYPED_HIERARCHY_DOMAIN,
113+
TYPED_HIERARCHY_PROBLEM,
114+
TYPED_HIERARCHY_SIBLING_PLAN,
115+
)
116+
v = PDDLValidator()
117+
result = v.validate(paths["domain"], paths["problem"], paths["plan"])
118+
assert not result.is_valid
119+
assert result.status == "STRUCTURE_ERROR"
120+
assert any(
121+
"expects type 'vehicle'" in e and "box1" in e
122+
for e in result.phases["structure"]["errors"]
123+
)
124+
125+
89126
def test_comment_and_cost_lines_skipped(tmp_path):
90127
plan = """\
91128
; This is a comment

0 commit comments

Comments
 (0)