Repository: aig-upf/tarski (language: python)

Issue:
Support for existential preconditions
Hi,
I am trying to parse a PDDL domain with existential preconditions (the _exists_ keyword). However, I get the error `tarski.errors.UndefinedVariable: Undefined element "?from" (error msg: None)`. Here is the PDDL domain:

```
(define (domain logistics)
(:requirements :strips :typing :existential-preconditions) 
(:types  city location thing - object
         package vehicle - thing
         truck airplane - vehicle  
         airport - location)
(:predicates  (in-city ?l - location ?c - city)
              (at ?obj - thing ?l - location)
              (in ?p - package ?veh - vehicle))
(:action drive
         :parameters    (?t - truck ?to - location)
         :precondition  (and 
                             (exists (?c - city ?from - location)
                                (and (at ?t ?from) (in-city ?from ?c) (in-city ?to ?c))
                              ))
         :effect        (and (not (at ?t ?from))
                             (at ?t ?to)))
;; Domain continues...
```

I have tested this PDDL domain (with the rest of the actions) with the FastDownward planner and it works. Therefore, my question is: **does Tarski support ADL requirements such as existential preconditions?** 

In case it doesn't, do you know of some **workaround to parse PDDL domains with existential preconditions on Tarski?** The reason why I am using existential preconditions is because I want to reduce the number of action parameters as much as possible (this is needed for my current application), so I can't simply add extra variables to the action parameters.


Testing a possible fix for pyrddl dependency



Discussion / hints:

Fix patch:
diff --git a/examples/data/fstrips/exists_precs/logistics-domain-exists.pddl b/examples/data/fstrips/exists_precs/logistics-domain-exists.pddl
new file mode 100644
index 00000000..c2c6ed7f
--- /dev/null
+++ b/examples/data/fstrips/exists_precs/logistics-domain-exists.pddl
@@ -0,0 +1,47 @@
+;; Adapted from: https://github.com/josej30/Planner-Sat-Constraints
+;; We use existential preconditions (exists) to remove as many parameters from the actions as possible
+
+(define (domain logistics)
+
+(:requirements :strips :typing :existential-preconditions) 
+
+(:types  city location thing - object
+         package vehicle - thing
+         truck airplane - vehicle  
+         airport - location)
+  
+(:predicates  (in-city ?l - location ?c - city)
+              (at ?obj - thing ?l - location)
+              (in ?p - package ?veh - vehicle))
+  
+(:action drive
+         :parameters    (?t - truck ?from ?to - location)
+         :precondition  (and 
+                             (exists (?c - city)
+                                (and (at ?t ?from) (in-city ?from ?c) (in-city ?to ?c))
+                              )
+                         )
+         :effect        (and (not (at ?t ?from))
+                             (at ?t ?to)))
+
+(:action fly
+         :parameters    (?a - airplane ?from ?to - airport)
+         :precondition  (at ?a ?from)
+         :effect        (and (not (at ?a ?from))
+                             (at ?a ?to)))
+
+(:action load
+         :parameters    (?v - vehicle ?p - package ?l - location)
+         :precondition  (and (at ?v ?l)
+                             (at ?p ?l))
+         :effect        (and (not (at ?p ?l))
+                             (in ?p ?v)))
+
+(:action unload
+         :parameters    (?v - vehicle ?p - package ?l - location)
+         :precondition  (and (at ?v ?l)
+                             (in ?p ?v))
+         :effect        (and (not (in ?p ?v))
+                             (at ?p ?l)))
+
+)
\ No newline at end of file
diff --git a/examples/data/fstrips/exists_precs/logistics-problem.pddl b/examples/data/fstrips/exists_precs/logistics-problem.pddl
new file mode 100644
index 00000000..df74a84b
--- /dev/null
+++ b/examples/data/fstrips/exists_precs/logistics-problem.pddl
@@ -0,0 +1,22 @@
+(define (problem bw_both_generative_policies_0)
+
+(:domain logistics)
+
+(:objects
+	c0 c1 - city
+     a0 a1 - airport
+     plane - airplane
+     p0 - package
+)
+
+(:init
+	(in-city a0 c0)
+	(in-city a1 c1)
+	(at plane a0)
+	(at p0 a1)
+)
+
+(:goal (and
+	(at p0 a0)
+))
+)
\ No newline at end of file
diff --git a/examples/fstrips_exists_precs/__init__.py b/examples/fstrips_exists_precs/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/fstrips_exists_precs/__main__.py b/examples/fstrips_exists_precs/__main__.py
new file mode 100644
index 00000000..9ad47dde
--- /dev/null
+++ b/examples/fstrips_exists_precs/__main__.py
@@ -0,0 +1,32 @@
+from tarski.errors import UndefinedSort, UndefinedPredicate
+from tarski.fstrips import AddEffect, FunctionalEffect
+from tarski.fstrips.errors import InvalidEffectError
+from tarski.io.fstrips import ParsingError, FstripsReader
+from tarski.syntax import Atom, CompoundFormula, Tautology
+from tarski.syntax.util import get_symbols
+from tarski.theories import Theory
+
+
+def reader(theories=None, strict_with_requirements=True, case_insensitive=False):
+    """ Return a reader configured to raise exceptions on syntax errors """
+    return FstripsReader(raise_on_error=True, theories=theories,
+                         strict_with_requirements=strict_with_requirements,
+                         case_insensitive=case_insensitive)
+
+
+def main():
+    # We create a `FstripsReader` object to parse the input PDDL
+    parser = reader()
+
+    instance_ast = None
+    # We locate the input pddl files for the domain and the instance data
+    domain_path = 'data/fstrips/exists_precs/logistics-domain-exists.pddl'
+    instance_path = 'data/fstrips/exists_precs/logistics-problem.pddl'
+    instance_ast = parser.read_problem(domain_path, instance_path)
+
+    if not instance_ast:
+        raise SystemExit("Parsing failed! Check expected PDDL paths ")
+
+
+if __name__ == '__main__':
+    main()
\ No newline at end of file


Test patch:
diff --git a/tests/io/test_fstrips_parsing.py b/tests/io/test_fstrips_parsing.py
index 5484b346..dfd9fc1f 100644
--- a/tests/io/test_fstrips_parsing.py
+++ b/tests/io/test_fstrips_parsing.py
@@ -1,5 +1,6 @@
-
 import pytest
+
+
 from tarski.errors import UndefinedSort, UndefinedPredicate
 from tarski.fstrips import AddEffect, FunctionalEffect
 from tarski.fstrips.errors import InvalidEffectError
@@ -40,6 +41,13 @@ def _test_inputs(inputs, r=None):
     return [_test_input(string, rule, r or reader()) for string, rule in inputs]
 
 
+def create_reader(theories=None, strict_with_requirements=True, case_insensitive=False):
+    """ Return a reader configured to raise exceptions on syntax errors """
+    return FstripsReader(raise_on_error=True, theories=theories,
+                         strict_with_requirements=strict_with_requirements,
+                         case_insensitive=case_insensitive)
+
+
 def test_pddl_type_declaration():
     r = reader()
     _test_inputs([
@@ -77,7 +85,7 @@ def test_symbol_declarations():
     lang = problem.language
     f = lang.get_function("loc1")
     assert f.codomain == lang.get_sort('object')
-    assert f.domain == (lang.get_sort('object'), )
+    assert f.domain == (lang.get_sort('object'),)
 
 
 def test_init():
@@ -321,3 +329,30 @@ def test_increase_effects():
     increase = output[1][0]
     assert isinstance(increase, FunctionalEffect) and isinstance(increase.condition, Tautology)
     assert str(increase.rhs) == '+(total-cost(), 1.0)'
+
+
+EXISTENTIAL_PRECS_TEST = """
+(define (domain logistics)
+(:requirements :strips :typing :existential-preconditions) 
+(:types  city location thing - object
+         package vehicle - thing
+         truck airplane - vehicle  
+         airport - location)
+(:predicates  (in-city ?l - location ?c - city)
+              (at ?obj - thing ?l - location)
+              (in ?p - package ?veh - vehicle))
+(:action drive
+         :parameters    (?t - truck ?to - location)
+         :precondition  (and 
+                             (exists (?c - city ?from - location)
+                                (and (at ?t ?from) (in-city ?from ?c) (in-city ?to ?c))
+                              ))
+         :effect        (and (not (at ?t ?from))
+                             (at ?t ?to)))
+)
+"""
+
+
+def test_preconditions_with_existential_formulas():
+    with pytest.raises(ParsingError):
+        output = _test_input(EXISTENTIAL_PRECS_TEST, 'domain', create_reader())
diff --git a/tests/io/test_pddl_parsing.py b/tests/io/test_pddl_parsing.py
index 5f0a4f0d..95d4c59f 100644
--- a/tests/io/test_pddl_parsing.py
+++ b/tests/io/test_pddl_parsing.py
@@ -4,6 +4,8 @@
 import tempfile
 
 import pytest
+
+import tarski.io.pddl.errors
 from tarski.io.pddl.lexer import PDDLlex
 from tarski.io.pddl import Features
 from tarski.io.pddl.parser import PDDLparser, UnsupportedFeature
@@ -221,4 +223,42 @@ def test_temporal_numeric():
         #assert len(instance.durative) == 1
         #assert len(instance.derived) == 1
         #assert len(instance.init) == 2
-        #assert len(goal_atoms) == 2
\ No newline at end of file
+        #assert len(goal_atoms) == 2
+
+
+@pytest.mark.pddl
+def test_preconditions_with_existential_effects():
+
+    pddl_data = """\
+(define (domain logistics)
+(:requirements :strips :typing :existential-preconditions) 
+(:types  city location thing - object
+         package vehicle - thing
+         truck airplane - vehicle  
+         airport - location)
+(:predicates  (in-city ?l - location ?c - city)
+              (at ?obj - thing ?l - location)
+              (in ?p - package ?veh - vehicle))
+(:action drive
+         :parameters    (?t - truck ?to - location)
+         :precondition  (and 
+                             (exists (?c - city ?from - location)
+                                (and (at ?t ?from) (in-city ?from ?c) (in-city ?to ?c))
+                              ))
+         :effect        (and (not (at ?t ?from))
+                             (at ?t ?to)))
+)
+"""
+    parser = PDDLparser(debug=True)
+
+    with tempfile.NamedTemporaryFile() as f:
+        parser.build(logfile=f.name)
+
+        with pytest.raises(tarski.io.pddl.errors.UnsupportedFeature):
+            parser.parse(pddl_data)
+
+        assert parser.domain_name == 'logistics'
+        assert Features.TYPING in parser.required_features
+        assert Features.EXISTENTIAL_PRECONDITIONS in parser.required_features
+
+