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
6 changes: 3 additions & 3 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, ubuntu-22.04]
python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12', '3.13']
os: [ubuntu-22.04, ubuntu-24.04]
python-version: [3.8, 3.9, '3.10', '3.11', '3.12', '3.13', '3.14']

steps:
- uses: actions/checkout@v3
Expand All @@ -39,7 +39,7 @@ jobs:
sudo apt-get -y install minisat python3-pip tox

- name: Check style
if: matrix.os == 'ubuntu-22.04' && matrix.python-version == '3.12'
if: matrix.os == 'ubuntu-24.04' && matrix.python-version == '3.14'
run: |
tox -e style

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
*~
*.py[cod]
*.soln
uv.lock
.tox/
build/
dist/
Expand Down
17 changes: 5 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,15 @@ is published under the terms of the GNU General Public License 3
Pyperplan supports the following PDDL fragment: STRIPS without action
costs.

# Requirements

Pyperplan requires [Python](https://python.org) >= 3.7.

# Installation

From the Python package index (PyPI):

pip install pyperplan
From the Python package index (PyPI) via [uv](https://docs.astral.sh/uv/):

From inside a repository clone:
uv tool install pyperplan

pip install --editable .
From inside a repository clone via [uv](https://docs.astral.sh/uv/):

This makes the `pyperplan` command available globally or in your [virtual
environment](https://docs.python.org/3/tutorial/venv.html) (recommended).
uv pip install --editable .

# Usage

Expand All @@ -45,7 +38,7 @@ By default, the planner performs a blind breadth-first search, which
does not scale very well. Heuristic search algorithms are available. For
example, to use greedy-best-first search with the FF heuristic, run

pyperplan -H hff -s gbf DOMAIN PROBLEM
pyperplan -H hff -s gbfs DOMAIN PROBLEM

For a list of available search algorithms and heuristics, run

Expand Down
8 changes: 4 additions & 4 deletions dev/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ CHANGES="/tmp/pyperplan-$VERSION-changes"

function set_version {
local version="$1"
sed -i -e "s/VERSION = \".*\"/VERSION = \"$version\"/" setup.py
sed -i -e "s/^version = \".*\"/version = \"$version\"/" pyproject.toml
}

cd $(dirname "$0")/../
Expand Down Expand Up @@ -36,9 +36,9 @@ git tag -a "v$VERSION" -m "v$VERSION" HEAD

# Requirements:
# pipx install twine
# pip install --user wheel
python3 setup.py sdist bdist_wheel --universal
twine upload dist/pyperplan-${VERSION}.tar.gz dist/pyperplan-${VERSION}-py2.py3-none-any.whl
# pipx install uv
uv build
twine upload dist/pyperplan-${VERSION}.tar.gz dist/pyperplan-${VERSION}-py3-none-any.whl

git push
git push --tags
Expand Down
15 changes: 12 additions & 3 deletions pyperplan/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@
import os
import sys

from pyperplan import tools
from pyperplan.planner import (
find_domain,
HEURISTICS,
search_plan,
SEARCHES,
find_domain,
search_plan,
validate_solution,
write_solution,
)
Expand Down Expand Up @@ -74,8 +75,9 @@ def get_callable_names(callables, omit_string):
format="%(asctime)s %(levelname)-8s %(message)s",
stream=sys.stdout,
)
logging.info(f"Python version: {sys.version}")

hffpo_searches = ["gbf", "wastar", "ehs"]
hffpo_searches = ["gbfs", "wastar", "ehs"]
if args.heuristic == "hffpo" and args.search not in hffpo_searches:
print(
"ERROR: hffpo can currently only be used with %s\n" % hffpo_searches,
Expand Down Expand Up @@ -115,6 +117,13 @@ def get_callable_names(callables, omit_string):
write_solution(solution, solution_file)
validate_solution(args.domain, args.problem, solution_file)

try:
peak_memory = tools.get_peak_memory_in_kb()
except Warning as warning:
logging.warning(warning)
else:
logging.info("Peak memory: %d KB" % peak_memory)


if __name__ == "__main__":
main()
5 changes: 2 additions & 3 deletions pyperplan/grounding.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@
task.
"""

from collections import defaultdict
import itertools
import logging
import re
from collections import defaultdict

from .task import Operator, Task


# controls mass log output
verbose_logging = False

Expand Down Expand Up @@ -159,7 +158,7 @@ def _relevance_analysis(operators, goals):
if debug:
logging.info("Relevance analysis removed %d facts" % len(debug_pruned_op))
# remove completely irrelevant operators
return [op for op in operators if not op in del_operators]
return [op for op in operators if op not in del_operators]


def _get_statics(predicates, actions):
Expand Down
2 changes: 1 addition & 1 deletion pyperplan/heuristics/landmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
Landmarks Heuristic
"""

from collections import defaultdict
import copy
from collections import defaultdict

from .heuristic_base import Heuristic

Expand Down
16 changes: 8 additions & 8 deletions pyperplan/heuristics/lm_cut.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
Implementation of LM-cut heuristic.
"""

from heapq import *
import logging
from heapq import heappop, heappush

from .heuristic_base import Heuristic

Expand Down Expand Up @@ -165,7 +165,7 @@ def link_op_to_effect(relaxed_op, factname):
self.relaxed_facts[fact] = RelaxedFact(fact)

for op in task.operators:
assert not op.name in self.relaxed_ops
assert op.name not in self.relaxed_ops
# build new relaxed operator from the task operator
relaxed_op = RelaxedOp(op.name)
# insert all preconditions into relaxed_op and
Expand All @@ -175,7 +175,7 @@ def link_op_to_effect(relaxed_op, factname):
# insert one fact that is always true if not already defined
# --> this fact will be used for all operators with empty
# preconditions
if not self.always_true in self.relaxed_facts:
if self.always_true not in self.relaxed_facts:
self.relaxed_facts[self.always_true] = RelaxedFact(self.always_true)
link_op_to_precondition(relaxed_op, self.always_true)
else:
Expand Down Expand Up @@ -235,7 +235,7 @@ def compute_hmax(self, state, clear_op_cost=True):
# --> if this is not the case then precond_fulfilled might
# still contain facts from a previous heuristic computation
# hence we need to clear it first!
if not op in op_cleared:
if op not in op_cleared:
op.clear(clear_op_cost)
op_cleared.add(op)
op.preconditions_unsat -= 1
Expand All @@ -251,13 +251,13 @@ def compute_hmax(self, state, clear_op_cost=True):
op.hmax_value = hmax_value + op.cost
hmax_next = op.hmax_supporter.hmax_value + op.cost
for eff in op.effects:
if not eff in fact_cleared:
if eff not in fact_cleared:
# clear fact if necessary
eff.clear()
fact_cleared.add(eff)
if hmax_next < eff.hmax_value:
eff.hmax_value = hmax_next
if not eff in facts_seen:
if eff not in facts_seen:
# enqueue effect if not already explored
facts_seen.add(eff)
heappush(unexpanded, eff)
Expand Down Expand Up @@ -305,7 +305,7 @@ def compute_goal_plateau(self, fact_name):
fact_in_plateau = self.relaxed_facts[fact_name]
if (
fact_in_plateau in self.reachable
and not fact_in_plateau in self.goal_plateau
and fact_in_plateau not in self.goal_plateau
):
# add this fact to the goal plateau
self.goal_plateau.add(fact_in_plateau)
Expand All @@ -332,7 +332,7 @@ def find_cut(self, state):
while unexpanded:
fact_obj = heappop(unexpanded)
for relaxed_op in fact_obj.precondition_of:
if not relaxed_op in op_cleared:
if relaxed_op not in op_cleared:
relaxed_op.precond_unsat = len(relaxed_op.precondition)
op_cleared.add(relaxed_op)
relaxed_op.precond_unsat -= 1
Expand Down
17 changes: 7 additions & 10 deletions pyperplan/heuristics/relaxation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@
#

import heapq
import logging

from ..task import Operator, Task
from .heuristic_base import Heuristic


""" This module contains the relaxation heuristics hAdd, hMax, hSA and hFF. """


Expand Down Expand Up @@ -326,14 +323,14 @@ def get_cost(self, operator, pre):

if operator.preconditions:
# Collect the sa-sets from all preconditions in a list.
l = [
sa_sets = [
self.facts[pre].sa_set
for pre in operator.preconditions
if self.facts[pre].sa_set is not None
]
if l:
if sa_sets:
# Union all these sets.
unioned_sets = set.union(*l)
unioned_sets = set.union(*sa_sets)
# The heuristic value equals the cardinality of the unioned
# sets.
cost = len(unioned_sets)
Expand All @@ -353,16 +350,16 @@ def calc_goal_h(self):
"""
if self.goals:
# Collect the sa-sets of all facts that are part of the goal.
l = [
sa_sets = [
self.facts[fact].sa_set
for fact in self.goals
if self.facts[fact].sa_set is not None
]
# Check whether all subgoals are fulfilled.
if len(l) == len(self.goals):
if len(sa_sets) == len(self.goals):
# Union all these sets and take the length of the union as
# heuristic value.
h_value = len(set.union(*l))
h_value = len(set.union(*sa_sets))
else:
# Ff not, return infinty.
h_value = float("inf")
Expand Down Expand Up @@ -438,7 +435,7 @@ def calc_goal_h(self, return_relaxed_plan=False):
# is not already expanded
if (
fact.cheapest_achiever is not None
and not fact.cheapest_achiever in relaxed_plan
and fact.cheapest_achiever not in relaxed_plan
):
# Add all preconditions of the cheapest achiever to the
# queue.
Expand Down
2 changes: 1 addition & 1 deletion pyperplan/pddl/lisp_iterators.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def is_structure(self):

def empty(self):
self._raise_if(self.is_word(), "cannot call empty on word")
return self.peek() == None
return self.peek() is None

def get_word(self):
"""If called on a word, return the word as a string.
Expand Down
1 change: 0 additions & 1 deletion pyperplan/pddl/lisp_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

"""Basic functions for parsing simple Lisp files."""


from .errors import ParseError
from .lisp_iterators import LispIterator

Expand Down
Loading