# Copyright (C) 2019 DLR
#
# All rights reserved. This program and the accompanying materials are made
# available under the terms of the 3-Clause BSD License which accompanies this
# distribution, and is available at
# https://opensource.org/licenses/BSD-3-Clause
#
# Contributors:
# Christoph Suerig <christoph.suerig@dlr.de>
# Don't connect with the Copyright comment above!
# Version 31.05.2019
import re
from rafcon.utils import log
logger = log.get_logger(__name__)
[docs]class PddlRequirementFinder():
"""
The PddlRequirementFiner, tries to figure out requirments from a given action definition.
It's doing this according to PDDL 2.1. It's not completed yet.
"""
def __init__(self, action_string):
"""
:param action_string: An string, containing a pddl action.
"""
self.action = action_string
if not self.action or len(self.action) == 0:
logger.error("Can't find requirements in None or empty action string.")
raise ValueError("Can't find requirements in None or empty action string.")
[docs] def strips(self):
"""
Returns always True, since
"If a domain stipulates no requirements, it is assumed to declare a requirement for :strips"
(PDDL2.1: An Extension to PDDL for Expressing Temporal Planning Domains, Fox and Long, 12/2003)
:return: Boolean: Returns always True.
"""
return True
[docs] def typing(self):
"""
returns true, if the action is typed.
:return: Boolean: True if the action is typed, False otherwise.
"""
return self.action.find(' - ') > -1
[docs] def negative_preconditions(self):
"""
Negative preconditions allows for the "NOT" keyword in the preconditions.
:return: Boolean: True if preconditions contain a 'NOT', False otherwise.
"""
not_pattern = re.compile(':precondition.*\(\s*not.*:effect', re.IGNORECASE | re.MULTILINE | re.DOTALL)
return not_pattern.search(self.action) is not None
[docs] def disjunctive_preconditions(self):
"""
Allows the key words "imply" and "or" in the preconditions.
:return: Boolean: True if the key words "or", or "imply" are part of the preconditions. False otherwise.
"""
or_pattern = re.compile(':precondition.*\(\s*or.*:effect', re.IGNORECASE | re.MULTILINE | re.DOTALL)
imply_pattern = re.compile(':precondition.*\(\s*imply.*:effect', re.IGNORECASE | re.MULTILINE | re.DOTALL)
return imply_pattern.search(self.action) is not None or or_pattern.search(self.action) is not None
[docs] def equality(self):
"""
Equality allows for a built-in predicate named "=".
:return: Boolean: True, if "=" is present in the action. False otherwise.
"""
return self.action.find('=') > -1
[docs] def existential_preconditions(self):
"""
Allows for the "exists" keyword.
:return: Boolean: True, if "exists" is present in the preconditions. False otherwise.
"""
pattern = re.compile(':precondition.*\(\s*exists.*:effect', re.IGNORECASE | re.MULTILINE | re.DOTALL)
return bool(pattern.search(self.action))
[docs] def universal_preconditions(self):
"""
this requirement allows forall-loops inside of the preconditions.
:return: Boolean: True, if forall loop is present in the preconditions. False otherwise.
"""
pattern = re.compile(':precondition.*\(\s*forall.*:effect', re.IGNORECASE | re.MULTILINE | re.DOTALL)
return bool(pattern.search(self.action))
[docs] def quantified_preconditions(self):
"""
This is the abbreviation of the requirement set, consisting of existential, and universal-preconditions.
:return: Boolean: True, if existential and universal-preconditions are met. False otherwise.
"""
return self.existential_preconditions() and self.universal_preconditions()
[docs] def conditional_effects(self):
"""
This requirement allows for "when", and "forall" in the effects of an action.
:return: Boolean: True, if "forall" or "when" is present in the effects of an action. False otherwise.
"""
when_pattern = re.compile('effect.*\(\s*when[\s+|\(]', re.IGNORECASE | re.MULTILINE | re.DOTALL)
forall_pattern = re.compile('effect.*\(\s*forall[\s+|\(]', re.IGNORECASE | re.MULTILINE | re.DOTALL)
return when_pattern.search(self.action) is not None or forall_pattern.search(self.action) is not None
[docs] def action_expansions(self):
if self.dag_expansions():
return True
if self.foreach_expansions():
return True
expansions_pattern = re.compile(':expansion', re.IGNORECASE)
if re.search(expansions_pattern, self.action):
return True
return False
[docs] def foreach_expansions(self):
requires = False
expansions_pattern = re.compile(':expansion', re.IGNORECASE)
if expansions_pattern.search(self.action):
expansion = self.action[re.search(expansions_pattern, self.action).start():]
foreach_pattern = re.compile('foreach', re.IGNORECASE)
requires = re.search(foreach_pattern, expansion) is not None
return requires
[docs] def dag_expansions(self):
requires = False
expansions_pattern = re.compile(':expansion', re.IGNORECASE)
if expansions_pattern.search(self.action):
expansion = self.action[re.search(expansions_pattern, self.action).start():]
foreach_pattern = re.compile('constrained', re.IGNORECASE)
requires = re.search(foreach_pattern, expansion) is not None
return requires
[docs] def fluents(self):
requires = False
in_dec_pattern = re.compile('effect.*\(\s*(increase|decrease)[\s+|\(]',
re.IGNORECASE | re.MULTILINE | re.DOTALL)
if re.search(in_dec_pattern, self.action):
requires = True
return requires
[docs] def expression_evaluation(self):
if self.fluents():
return True
return False
[docs] def domain_axioms(self):
if self.expression_evaluation():
return True
return False
[docs] def adl(self):
if not self.strips():
return False
if not self.typing():
return False
if not self.disjunctive_preconditions():
return False
if not self.equality():
return False
if not self.quantified_preconditions():
return False
if not self.conditional_effects():
return False
return True