Skip to content

Commit

Permalink
Merge a8c7e4f into cc1e72d
Browse files Browse the repository at this point in the history
  • Loading branch information
cmaclell committed Feb 3, 2019
2 parents cc1e72d + a8c7e4f commit 849f6ce
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 77 deletions.
30 changes: 30 additions & 0 deletions .travis.yml
@@ -0,0 +1,30 @@
language: python
sudo: required
dist: xenial
python:
- '3.7'
- '2.7'
- pypy2.7-6.0
- pypy3.5-6.0
install:
- pip install tox-travis
- pip install coveralls
script:
- tox
- coveralls
deploy:
matrix:
- provider: pypi
user: cmaclell
skip_existing: true
distributions: sdist bdist_wheel
password:
secure: a7vgMdw8CPIWeJslKxAB7yeuqz4j7GfEhc5EmN4OxHu88JSGgIfBzAQwUt1mel2D+2+Snz8Rv9dTxo7pRwvwEKbAJHCsJprEizTwBrMONZV0zF+lv+TtVYpLB7yku/la8cYJfDz8DaCPCopSWtRrTAeEM5Tr0E3hIbO14pInYCJMkGXDNG11suh0pbhoQ9Wjz51vIwXi0mNBP2I+/59HVlzOQn8/VOSRYBvMeXKiadA/VAS76kfOEfpemhEfxl9+x8oCnoAKkka4YmCt+s1NBy2Sl7wfT18PwYuxkRci2NmsSxBydMyrGu1lsJw6yKaT8wwCUBsdw6+QssktL2tE0pziWyZXbnCVCi6ly7CS3KQKpKycT4nWLxXrdtHlSnzJzEdpIBA8/jlkODSwnJe9SpGLPE8yRpOfQTQcLsmo5Lb64Z41mFggSz4ITb1RjL+YV/eQSsiVNA8nHaqCn4MLyLQxtKw3YUDj5kl5w15c5Tp7BsZdDqrVk+fjlhfz9si8n0bYBjuSW7vKZvH5NbMMnC92L2nGCSnhk2L1aeg055gP36PplOKrlB2ivQMLO9zzDPZR+2oIjmxAZTjOGUy4ayWcYSNhOh4oSnE+vbDSFZ8bsO9v7Jn13Rb0cxMgullO6L1SY+zcm91BSvyB86mgWt+LGmn1ncnoVukryE2VdwY=
- provider: pypi
user: cmaclell
skip_existing: true
distributions: sdist bdist_wheel
password:
secure: a7vgMdw8CPIWeJslKxAB7yeuqz4j7GfEhc5EmN4OxHu88JSGgIfBzAQwUt1mel2D+2+Snz8Rv9dTxo7pRwvwEKbAJHCsJprEizTwBrMONZV0zF+lv+TtVYpLB7yku/la8cYJfDz8DaCPCopSWtRrTAeEM5Tr0E3hIbO14pInYCJMkGXDNG11suh0pbhoQ9Wjz51vIwXi0mNBP2I+/59HVlzOQn8/VOSRYBvMeXKiadA/VAS76kfOEfpemhEfxl9+x8oCnoAKkka4YmCt+s1NBy2Sl7wfT18PwYuxkRci2NmsSxBydMyrGu1lsJw6yKaT8wwCUBsdw6+QssktL2tE0pziWyZXbnCVCi6ly7CS3KQKpKycT4nWLxXrdtHlSnzJzEdpIBA8/jlkODSwnJe9SpGLPE8yRpOfQTQcLsmo5Lb64Z41mFggSz4ITb1RjL+YV/eQSsiVNA8nHaqCn4MLyLQxtKw3YUDj5kl5w15c5Tp7BsZdDqrVk+fjlhfz9si8n0bYBjuSW7vKZvH5NbMMnC92L2nGCSnhk2L1aeg055gP36PplOKrlB2ivQMLO9zzDPZR+2oIjmxAZTjOGUy4ayWcYSNhOh4oSnE+vbDSFZ8bsO9v7Jn13Rb0cxMgullO6L1SY+zcm91BSvyB86mgWt+LGmn1ncnoVukryE2VdwY=
on:
tags: true
5 changes: 5 additions & 0 deletions README.rst
@@ -1,3 +1,8 @@
.. image:: https://travis-ci.com/cmaclell/py_plan.svg?branch=master
:target: https://travis-ci.com/cmaclell/py_plan
.. image:: https://coveralls.io/repos/github/cmaclell/py_plan/badge.svg?branch=examples
:target: https://coveralls.io/github/cmaclell/py_plan?branch=examples

=========
Py Plan
=========
Expand Down
10 changes: 8 additions & 2 deletions py_plan/base.py
Expand Up @@ -19,7 +19,7 @@ def gen_skolem():

class Operator:

def __init__(self, name, conditions, effects, cost=1):
def __init__(self, name, conditions, effects, cost=1, reverse_sub=None):
# make the name just a descriptive string / annotation.
self.name = name
self.conditions = set(conditions)
Expand All @@ -31,6 +31,11 @@ def __init__(self, name, conditions, effects, cost=1):
self.add_effects = set()
self.del_effects = set()

if reverse_sub is None:
self.reverse_sub = {}
else:
self.reverse_sub = reverse_sub

for c in self.conditions:
if is_negated_term(c):
self.pos_cond.add(c[1])
Expand Down Expand Up @@ -59,11 +64,12 @@ def standardized_copy(self):
args = set(e for term in self.conditions.union(self.effects) for e in
extract_strings(term) if is_variable(e))
sub = {a: gen_skolem() for a in args}
reverse_sub = {sk: sub[sk] for sk in sub}

conditions = set(subst(sub, c) for c in self.conditions)
effects = set(subst(sub, e) for e in self.effects)

return Operator(self.name, conditions, effects, self.cost)
return Operator(self.name, conditions, effects, self.cost, reverse_sub)

def __str__(self):
s = "Name: %s" % self.name + "\n"
Expand Down
13 changes: 12 additions & 1 deletion py_plan/pattern_matching.py
Expand Up @@ -325,6 +325,13 @@ def identify_determined_vars(term):


def identify_necessary_vars(term, determined_vars, neg=False, fun=False):
"""
>>> det = {'?b', '?other', '?other2', '?y'}
>>> identify_necessary_vars(('not', ('on', '?other', '?b')), det)
{'?other', '?b'}
"""
# print(term, determined_vars, neg, fun)

if isinstance(term, tuple) and len(term) > 0:
if term[0] == 'not':
return identify_necessary_vars(term[1], determined_vars, neg=True,
Expand All @@ -333,7 +340,8 @@ def identify_necessary_vars(term, determined_vars, neg=False, fun=False):
return set.union(*[identify_necessary_vars(ele, determined_vars,
neg, fun=True)
for ele in term[1:]])
return set.union(*[identify_necessary_vars(ele, determined_vars)
return set.union(*[identify_necessary_vars(ele, determined_vars, neg,
fun)
for ele in term[1:]])

if not is_variable(term):
Expand Down Expand Up @@ -362,6 +370,7 @@ def pattern_match(pattern, index, substitution=None, partial=False):

determined_vars = set(v for t in pattern
for v in identify_determined_vars(t))
# print('DETERMINED', determined_vars)

terms = {}
for t in pattern:
Expand All @@ -370,6 +379,8 @@ def pattern_match(pattern, index, substitution=None, partial=False):
terms[necessary] = []
terms[necessary].append(t)

# print(terms)

f_terms = set(t for t in pattern if is_functional_term(t))

terms = update_terms(terms, f_terms, substitution, index, partial)
Expand Down
11 changes: 9 additions & 2 deletions py_plan/problems/air_cargo.py
Expand Up @@ -49,7 +49,8 @@
('Airport', 'SFO')]

goal = [('At', 'C1', 'JFK'),
('At', 'C2', 'SFO')]
# ('At', 'C2', 'SFO')
]


def progression(x):
Expand All @@ -66,4 +67,10 @@ def bidirectional(x):

p = StateSpacePlanningProblem(start, goal, [load, unload, fly])

compare_searches([p], [progression, regression, bidirectional])
compare_searches([p], [progression, regression,
bidirectional])

print(next(progression(p)).path())
path = next(regression(p)).path()

print(path[0][0])
38 changes: 27 additions & 11 deletions py_plan/problems/blocksworld.py
Expand Up @@ -14,35 +14,47 @@
('block', '?b'),
('block', '?x'),
('block', '?y'),
('clear', '?b'),
('clear', '?y'),
('block', '?other'),
('block', '?other2'),
('not', ('on', '?other', '?b')),
('not', ('on', '?other2', '?y')),
# ('clear', '?b'),
# ('clear', '?y'),
(ne, '?b', '?x'),
(ne, '?b', '?y'),
(ne, '?x', '?y')],
[('on', '?b', '?y'),
('clear', '?x'),
# ('clear', '?x'),
('not', ('on', '?b', '?x')),
('not', ('clear', '?y'))])
# ('not', ('clear', '?y'))
])

move_from_table = Operator('move_from_table',
[('on', '?b', 'Table'),
('clear', '?b'),
('clear', '?y'),
('block', '?other'),
('block', '?other2'),
('not', ('on', '?other', '?b')),
('not', ('on', '?other2', '?y')),
# ('clear', '?b'),
# ('clear', '?y'),
('block', '?b'),
('block', '?y'),
(ne, '?b', '?y')],
[('on', '?b', '?y'),
('not', ('on', '?b', 'Table')),
('not', ('clear', '?y'))])
# ('not', ('clear', '?y'))
])

move_to_table = Operator('move_to_table',
[('on', '?b', '?x'),
('block', '?b'),
('block', '?x'),
('clear', '?b'),
('block', '?other'),
('not', ('on', '?other', '?b')),
# ('clear', '?b'),
(ne, '?b', '?x')],
[('on', '?b', 'Table'),
('clear', '?x'),
# ('clear', '?x'),
('not', ('on', '?b', '?x'))])


Expand All @@ -54,8 +66,9 @@
('block', 'A'),
('block', 'B'),
('block', 'C'),
('clear', 'B'),
('clear', 'C')]
# ('clear', 'B'),
# ('clear', 'C')
]

goal = [('on', 'A', 'B'),
('on', 'B', 'C'),
Expand Down Expand Up @@ -89,3 +102,6 @@ def bidirectional(x):
regression, bidirectional,
# iterative_deepening_search
])

print(next(progression(p)).path())
print(next(regression(p)).path())
6 changes: 5 additions & 1 deletion py_plan/problems/book_ordering.py
Expand Up @@ -34,7 +34,11 @@ def progression(problem):
def regression(problem):
return partial(depth_first_search, forward=False, backward=True)(problem)

def bidirectional(problem):
return partial(depth_first_search, forward=True, backward=True)(problem)


compare_searches([p], [progression,
# regression
regression,
bidirectional
])
4 changes: 4 additions & 0 deletions py_plan/problems/spare_tire.py
Expand Up @@ -48,3 +48,7 @@ def bidirectional(x):


compare_searches([p], [progression, regression, bidirectional])


print(next(progression(p)).path())
print(next(regression(p)).path())
75 changes: 52 additions & 23 deletions py_plan/total_order.py
Expand Up @@ -3,7 +3,6 @@
from __future__ import absolute_import
from __future__ import division

from pprint import pprint
from itertools import chain
from itertools import combinations
from operator import ne
Expand All @@ -18,7 +17,6 @@
from py_search.base import GoalNode
from py_search.utils import compare_searches

from py_plan.pattern_matching import index_key
from py_plan.pattern_matching import build_index
from py_plan.pattern_matching import pattern_match
from py_plan.pattern_matching import is_functional_term
Expand Down Expand Up @@ -56,6 +54,19 @@ def generate_regression_constraints(del_effects, goal_index):
del_effects for match in pattern_match([e], goal_index, {})]


def generate_del_constraints(del_effects, positive_goals):
# print(del_effects, list(positive_goals))
return [or_constraints([(ne, match[v], v) for v in match]) for e in
positive_goals for match in
pattern_match([e], build_index(del_effects), {})]


def generate_add_constraints(add_effects, negative_goals):
return [or_constraints([(ne, match[v], v) for v in match]) for e in
negative_goals for match in
pattern_match([e], build_index(add_effects), {})]


def replace_functionals(ele, sub):
"""
Return the element with all functionals replaced,
Expand Down Expand Up @@ -121,6 +132,8 @@ def __init__(self, state, goals, operators):
def successors(self, node):
index = build_index(node.state)
for o in self.operators:
# TODO check that operators cannot have unbound variables in
# effects.
for m in pattern_match(o.conditions, index):
dels = frozenset(execute_functions(e, m) if
is_functional_term(e) else subst(m, e) for e
Expand All @@ -132,34 +145,55 @@ def successors(self, node):
yield Node(new_state, node, (o, m), node.cost() + o.cost)

def predecessors(self, node):
# print()
# pprint(node.state)
# if node.parent is not None:
# return
for o in self.operators:
# Rename variables to prevent collisions.
# TODO figure out how to reverse this for printing plans
o = o.standardized_copy()

# convert constants into equality constraints.
# Convert constants into equality constraints, so that goals with
# constants can be matched in reverse.
constant_mapping = {}
var_state = frozenset(replace_constants(e, constant_mapping) for e
in node.state)
equality_constraints = set((eq, e, constant_mapping[e]) for e in
constant_mapping)

# Generate constraints that prevent operator inconsistency
# prevent delete effects that match positive goals
del_constraints = generate_del_constraints(o.del_effects,
[e for e in var_state if
not
is_functional_term(e)
and not
is_negated_term(e)])
# prevent positive effects that produce negated goals
add_constraints = generate_add_constraints(o.add_effects,
[e[1] for e in var_state
if not
is_functional_term(e)
and
is_negated_term(e)])

# TODO figure out how to add any applicable constraints to var
# state in order to prevent variables bindings that are
# inconsistent this requires comparing the vars in the state with
# those in the constraints and only adds constraints that have var
# subsets.
var_state = var_state.union(equality_constraints)

# print()
# pprint(var_state)
# print(o)
for m in pattern_match(var_state,
build_index(o.add_effects),
partial=True):
# pprint(m)

new_state = frozenset(subst(m, e) for e in var_state)
new_state = new_state.difference(o.add_effects)
new_state = new_state.union(o.conditions)

cons = set(subst(m, e) for e in new_state if
is_functional_term(e))
cons.update(subst(m, e) for e in del_constraints)
cons.update(subst(m, e) for e in add_constraints)

new_state = frozenset(e for e in new_state if not
is_functional_term(e))
# print("Constraints", cons)
Expand Down Expand Up @@ -188,7 +222,9 @@ def predecessors(self, node):
new_state = frozenset(subst(assignment_mapping, e)
for e in new_state)

# check for any other functional constraints.
# Check for any other functional constraints.
# Terminate branches with false functions
# Eliminate constraints that are satisfied
new_cons = set()
for c in cons:
try:
Expand All @@ -204,9 +240,6 @@ def predecessors(self, node):
# REACHABILITY ANALYSIS, check if there are any
# new_state elements that cannot be generated by an
# operator and do not exist in the state
# print()
# pprint(new_state)
# print(build_index(self.achievable))
for e in new_state:
if is_negated_term(e):
continue
Expand All @@ -217,27 +250,23 @@ def predecessors(self, node):
try:
next(pattern_match(p, self.achievable,
partial=True))
except Exception as e:
# print(e)
# print("ACHIEVABLE")
# pprint(self.achievable)
# print("HUH?", p)
# print('OP', o, m)
# print(new_state)
except Exception:
invalid = True
break

if invalid:
continue

# Add any surviving constraints back into the state
new_state = new_state.union(new_cons)
# pprint(new_state)

yield GoalNode(new_state, node, (o, m), node.cost() + o.cost)

def goal_test(self, node, goal):
index = build_index(node.state)
for m in pattern_match(goal.state, index, {}):
for v in m:
goal.action[1][v] = m[v]
return True
return False

Expand Down

0 comments on commit 849f6ce

Please sign in to comment.