Repository navigation
Fix wrong answers in the task model and heuristics - #165
Merged
Merged
Conversation
Write down where and why we mean to publish jupyddl as citable research software (.docs/PUBLISHING.md): a Zenodo DOI per release, a JOSS paper, ICAPS workshops for the research results, with a readiness checklist, the paper outline, the AI-usage disclosure JOSS requires, and how contributors can be considered for authorship. The README states the project's aim and its Cite section now says which version the 2021 preprint describes. Add the community files an outside contributor expects: a security policy, a pull-request template, a "Propose an algorithm" issue template, and an "Adding an algorithm" section in CONTRIBUTING.md. Run tests and build on pull requests. They were `on: [push]`, which never fires for a fork, so a contributor's PR showed no checks at all. Fix AGENTS.md drift: the Python floor is 3.10 and Mergify is retired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RbGtRHF8XhkiPQCDVHtV2
A package can now add planners, heuristics and generators by declaring `jupyddl.planners`, `jupyddl.heuristics` or `jupyddl.generators` entry points. Once installed, they work from the CLI, the benchmark harness and `jupyddl.solve` by name, without anyone importing the package. Discovery is lazy and defensive (jupyddl/plugins.py): the CLI runs it before building its parser, the library only on a registry miss, so importing jupyddl never scans installed packages. A built-in name always wins, a plugin that fails to import is skipped with a warning, and JUPYDDL_NO_PLUGINS=1 turns it off. `jupyddl plugins` lists what was found. The CLI now reads `requires_heuristic` from the registry at call time, since INFORMED_PLANNERS is fixed at import and cannot know about plugins. .docs/EXTENDING.md walks through a heuristic, a planner from a priority function, a planner from scratch with the budget/observer contract, a generator, testing the guarantees you claim, and shipping as a plugin. tests/test_extending.py runs every Python block in it, so it cannot drift. best_first now accepts a tuple priority for tie-breaking; observers get its first element as `f`, so traces stay numeric. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RbGtRHF8XhkiPQCDVHtV2
# Conflicts: # .docs/PUBLISHING.md # AGENTS.md # CHANGELOG.md
Each of these returned a plan, a cost or a value without complaint, and validate_plan accepted the plans because it replays them through the same Task.apply. tests/test_semantics.py now pins them on tiny hand-written tasks; 16 of its cases fail on the previous code. - Derived facts were never retracted, and (not <derived>) held from the start. close() now recomputes axioms from scratch and maintains negated derived facts; negation inside a rule body (needs stratification) is refused. The relaxation gets the complement for free, so it never calls a solvable state a dead end. - An atom both added and deleted left its complement true too; a `when` re-adding a deleted atom did the same. Complements: the delete wins. - Under a (total-cost) metric an action without a cost effect is free, as in IPC planners (pegsol/sokoban optimised the wrong objective). Cost inside `when` was charged unconditionally: refused, as are negative costs. - A never-defined numeric fluent read as 0; conditions reading one fail. - blind skipped zero-cost actions and overestimated. - h1/h2/hm crashed on numeric states; LM-cut crashed on an empty goal. - lmcut and h2 clear `admissible` on tasks with conditional effects. - Optimal planners default to lmcut, not hff; `-H none` means blind. - make_heuristic builds a heuristic class or partial instead of passing it through unbuilt. - Fact ids and id sets are built in sorted order, so LM-cut tie-breaks, expansions and plans no longer change with PYTHONHASHSEED. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014RbGtRHF8XhkiPQCDVHtV2
guilyx
marked this pull request as ready for review
November 3, 2026 08:00
guilyx
pushed a commit
that referenced
this pull request
Nov 3, 2026
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The full code review found a set of bugs where jupyddl returns a wrong answer without complaint: a plan that shouldn't exist, an "optimal" cost that isn't, a heuristic that overestimates or crashes.
validate_plandidn't catch any of them, because it replays plans through the sameTask.applythat has the bug.offdeletes(on)but(light)stays true;(not (light))is true from the start, sobfs"solves" an unsolvable taskmove(a,a)leaves(at a)and(not (at a))true, so a 2-step plan "beats" the real 3-step optimum. Same for(and (not p) (when q p))(total-cost)metric(increase (total-cost) 10)insidewhendijkstraspins until the budget runs out(distance a c)blindastar+blindreturned cost 3 where 2 is optimalh1/h2/hmsolve()lmcut/h2admissibleflagTrue, even with conditional effectssolve_task(task, "astar")hff(not admissible): cost 6 where 5 is optimallmcut.-H nonemeanthff; it now meansblindmake_heuristic(LMCutHeuristic, task)PYTHONHASHSEEDastar/lmcutexpanded 1725, 1758 or 1760 nodes and returned different plansThe requirements registry, the README PDDL table and AGENTS.md now describe the new semantics.
How it was checked
tests/test_semantics.py(33 cases): tiny hand-written tasks whose right answer is obvious by inspection. 16 of them fail on the previous code (I ran them againstmainto confirm) and all pass here. The seed test runs the same search under threePYTHONHASHSEEDs.flake8andblackare clean, andweb/distis rebuilt.Guarantees changed
blindis now actually admissible on tasks with free actions.lmcutandh2reportadmissible=Falseon tasks with conditional effects; they were already inadmissible there.OPTIMAL_PLANNERSin this PR. A follow-up handlesbfs/iddfs, which claim cost-optimality but only minimise plan length.Checklist
flake8 jupyddl tests toolsandpytestpass locallypython tools/build_web.pyre-run;web/distcommittedCHANGELOG.mdupdated (Unreleased → Fixed)The history has a
merge -s oursof the already-merged #164 tip so the push fast-forwards. The PR diff againstmaincontains only this change. Squash-merge.Generated by Claude Code