diff --git a/.github/workflows/pyapp.yml b/.github/workflows/build.yml similarity index 83% rename from .github/workflows/pyapp.yml rename to .github/workflows/build.yml index 16488e4..efce7d5 100644 --- a/.github/workflows/pyapp.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,9 @@ -name: Python application +name: Build on: [push] jobs: build: - runs-on: ubuntu-latest steps: @@ -33,11 +32,4 @@ jobs: 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 - - name: Test with pytest - run: | - pip install pytest - pytest - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 - with: - name: codecov-umbrella + diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..09e45c5 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,71 @@ +name: Run tests + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - uses: actions/checkout@v1.0.0 + - uses: julia-actions/setup-julia@v1 + with: + version: '1.4.1' + - name: Install Julia dependencies + run: | + julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(path="https://github.com/APLA-Toolbox/PDDL.jl"))' + julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(path="https://github.com/JuliaPy/PyCall.jl"))' + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install julia + python -m pip install pycall + - 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: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - uses: actions/checkout@v1.0.0 + - uses: julia-actions/setup-julia@v1 + with: + version: '1.4.1' + - name: Install Julia dependencies + run: | + julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(path="https://github.com/APLA-Toolbox/PDDL.jl"))' + julia --color=yes -e 'using Pkg; Pkg.add(Pkg.PackageSpec(path="https://github.com/JuliaPy/PyCall.jl"))' + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install julia + python -m pip install pycall + - 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 + - name: Test with pytest + run: | + pip install pytest + pytest + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + name: codecov-umbrella diff --git a/README.md b/README.md index 152f90f..e9f4e74 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +![tests](https://github.com/APLA-Toolbox/pyjulia-pddl/workflows/Run%20tests/badge.svg?branch=main&event=push) +![build](https://github.com/APLA-Toolbox/pyjulia-pddl/workflows/Build/badge.svg?branch=main&event=push) + + # Pyjulia PDDL A Python wrapper using JuliaPy for the PDDL.jl package. It implements Planners (Best-First, Breadth-First, Depth-First) as class methods. Easy to use even in REFL mode. The AutomatedPlanner class is clear and understandable, easy to contribute to. diff --git a/src/astar.py b/src/astar.py index 3796a42..74c92f2 100644 --- a/src/astar.py +++ b/src/astar.py @@ -1,7 +1,7 @@ -class AStarBestFirstSearch(): +class AStarBestFirstSearch: def __init__(self, automated_planner): self.automated_planner = automated_planner - + def search(self): print("-/!\- No path found -/!\-") return [] diff --git a/src/automated_planner.py b/src/automated_planner.py index c1aa9a1..cf35914 100644 --- a/src/automated_planner.py +++ b/src/automated_planner.py @@ -16,6 +16,7 @@ from julia import PDDL from time import time as now + class AutomatedPlanner: def __init__(self, domain_path, problem_path): self.pddl = PDDL @@ -28,7 +29,7 @@ def __execute_action(self, action, state): def transition(self, state, action): return self.pddl.transition(self.domain, state, action, check=False) - + def available_actions(self, state): return self.pddl.available(state, self.domain) @@ -66,7 +67,7 @@ def get_state_def_from_path(self, path): trimmed_path = [] for node in path: trimmed_path.append(node.state) - return trimmed_path + return trimmed_path def breadth_first_search(self, time_it=False): if time_it: diff --git a/src/bfs.py b/src/bfs.py index bd8f71c..26c9f96 100644 --- a/src/bfs.py +++ b/src/bfs.py @@ -1,24 +1,34 @@ from .node import Node -class BreadthFirstSearch(): + +class BreadthFirstSearch: def __init__(self, automated_planner): self.visited = [] self.automated_planner = automated_planner self.init = Node(self.automated_planner.initial_state, automated_planner) self.queue = [self.init] - + def search(self): while self.queue: current_node = self.queue.pop(0) if current_node not in self.visited: self.visited.append(current_node) - if self.automated_planner.satisfies(self.automated_planner.problem.goal, current_node.state): + if self.automated_planner.satisfies( + self.automated_planner.problem.goal, current_node.state + ): return current_node - + actions = self.automated_planner.available_actions(current_node.state) for act in actions: - child = Node(state=self.automated_planner.transition(current_node.state, act), automated_planner=self.automated_planner, parent_action=act, parent=current_node) + child = Node( + state=self.automated_planner.transition( + current_node.state, act + ), + automated_planner=self.automated_planner, + parent_action=act, + parent=current_node, + ) if child in self.visited: continue self.queue.append(child) diff --git a/src/dfs.py b/src/dfs.py index fde6f98..60cb086 100644 --- a/src/dfs.py +++ b/src/dfs.py @@ -1,7 +1,7 @@ -class DepthFirstSearch(): +class DepthFirstSearch: def __init__(self, automated_planner): self.automated_planner = automated_planner - + def search(self): print("-/!\- No path found -/!\-") return [] diff --git a/src/dijkstra.py b/src/dijkstra.py index 76ee354..8506600 100644 --- a/src/dijkstra.py +++ b/src/dijkstra.py @@ -2,10 +2,16 @@ import math from .heuristics import zero_heuristic -class DijkstraBestFirstSearch(): + +class DijkstraBestFirstSearch: def __init__(self, automated_planner): self.automated_planner = automated_planner - self.init = Node(self.automated_planner.initial_state, automated_planner, is_closed=False, is_open=True) + self.init = Node( + self.automated_planner.initial_state, + automated_planner, + is_closed=False, + is_open=True, + ) self.open_nodes_n = 1 self.nodes = dict() self.nodes[self.__hash(self.init)] = self.init @@ -17,19 +23,32 @@ def __hash(self, node): def search(self): while self.open_nodes_n > 0: - current_key = min([n for n in self.nodes if self.nodes[n].is_open], key=(lambda k: self.nodes[k].f_cost)) + current_key = min( + [n for n in self.nodes if self.nodes[n].is_open], + key=(lambda k: self.nodes[k].f_cost), + ) current_node = self.nodes[current_key] - if self.automated_planner.satisfies(self.automated_planner.problem.goal, current_node.state): - return current_node + if self.automated_planner.satisfies( + self.automated_planner.problem.goal, current_node.state + ): + return current_node current_node.is_closed = True current_node.is_open = False self.open_nodes_n -= 1 - + actions = self.automated_planner.available_actions(current_node.state) for act in actions: - child = Node(state=self.automated_planner.transition(current_node.state, act), automated_planner=self.automated_planner, parent_action=act, parent=current_node, heuristic=zero_heuristic, is_closed=False, is_open=True) + child = Node( + state=self.automated_planner.transition(current_node.state, act), + automated_planner=self.automated_planner, + parent_action=act, + parent=current_node, + heuristic=zero_heuristic, + is_closed=False, + is_open=True, + ) child_hash = self.__hash(child) if child_hash in self.nodes: if self.nodes[child_hash].is_closed: diff --git a/src/node.py b/src/node.py index c797f14..95c5aba 100644 --- a/src/node.py +++ b/src/node.py @@ -1,5 +1,15 @@ -class Node(): - def __init__(self, state, automated_planner, is_closed=None, is_open=None, parent_action=None, parent=None, g_cost=0, heuristic=None): +class Node: + def __init__( + self, + state, + automated_planner, + is_closed=None, + is_open=None, + parent_action=None, + parent=None, + g_cost=0, + heuristic=None, + ): self.state = state self.parent_action = parent_action self.parent = parent @@ -19,7 +29,6 @@ def __init__(self, state, automated_planner, is_closed=None, is_open=None, paren self.h_cost = 0 self.f_cost = self.g_cost + self.h_cost - self.is_closed = is_closed self.is_open = is_open diff --git a/tests/test_automated_planner.py b/tests/test_automated_planner.py index 7338b63..abb11ab 100644 --- a/tests/test_automated_planner.py +++ b/tests/test_automated_planner.py @@ -18,7 +18,8 @@ def test_available_actions(): apla = AutomatedPlanner("data/domain.pddl", "data/problem.pddl") actions = apla.available_actions(apla.initial_state) - assert(len(actions) > 0) + assert len(actions) > 0 + def test_execute_action(): from src.automated_planner import AutomatedPlanner @@ -26,7 +27,8 @@ def test_execute_action(): apla = AutomatedPlanner("data/domain.pddl", "data/problem.pddl") actions = apla.available_actions(apla.initial_state) new_state = apla.transition(apla.initial_state, actions[0]) - assert(str(new_state) != str(apla.initial_state)) + assert str(new_state) != str(apla.initial_state) + def test_state_assertion(): from src.automated_planner import AutomatedPlanner