From db426cd8de43b603c4dfb79f4acf157da55dc71f Mon Sep 17 00:00:00 2001 From: Gabi Roeger Date: Thu, 1 Feb 2024 16:11:11 +0100 Subject: [PATCH] [issue913] Handle actions with uninitialized numeric expressions in instantiation. If an action increases total-cost by the value of a numeric fluent that has not been defined in the initial state, the action should not be instantiated. We adapted the relaxed reachability analysis of the grounding component to prevent such instantiations from the beginning (instead of failing with an AssertionError). --------- Co-authored-by: remochristen --- src/translate/normalize.py | 39 +++++++++++++++++++++++++++++++-- src/translate/pddl_to_prolog.py | 4 ++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/translate/normalize.py b/src/translate/normalize.py index 375dc67e9c..4b5df01dda 100755 --- a/src/translate/normalize.py +++ b/src/translate/normalize.py @@ -1,6 +1,7 @@ #! /usr/bin/env python3 import copy +from typing import Sequence import pddl @@ -23,7 +24,19 @@ def delete_owner(self, task): def build_rules(self, rules): action = self.owner rule_head = get_action_predicate(action) - rule_body = condition_to_rule_body(action.parameters, self.condition) + + # If the action cost is based on a primitive numeric expression, + # we need to require that it has a value defined in the initial state. + # We hand it over to condition_to_rule_body to include this in the rule + # body. + pne = None + if (isinstance(action.cost, pddl.Increase) and + isinstance(action.cost.expression, + pddl.PrimitiveNumericExpression)): + pne = action.cost.expression + + rule_body = condition_to_rule_body(action.parameters, self.condition, + pne) rules.append((rule_body, rule_head)) def get_type_map(self): return self.owner.type_map @@ -117,6 +130,9 @@ def get_axiom_predicate(axiom): variables += [par.name for par in axiom.condition.parameters] return pddl.Atom(name, variables) +def get_pne_definition_predicate(pne: pddl.PrimitiveNumericExpression): + return pddl.Atom(f"@def-{pne.symbol}", pne.args) + def all_conditions(task): for action in task.actions: yield PreconditionProxy(action) @@ -366,10 +382,24 @@ def build_exploration_rules(task): proxy.build_rules(result) return result -def condition_to_rule_body(parameters, condition): +def condition_to_rule_body(parameters: Sequence[pddl.TypedObject], + condition: pddl.conditions.Condition, + pne: pddl.PrimitiveNumericExpression = None): + """The rule body requires that + - all parameters (including existentially quantified variables in the + condition) are instantiated with objecst of the right type, + - all positive atoms in the condition (which must be normalized) are + true in the Prolog model, and + - the primitive numeric expression (from the action cost) has a defined + value (in the initial state).""" result = [] + # Require parameters to be instantiated with objects of the right type. for par in parameters: result.append(par.get_atom()) + + # Require each positive literal in the condition to be reached and + # existentially quantified variables of the condition to be instantiated + # with objects of the right type. if not isinstance(condition, pddl.Truth): if isinstance(condition, pddl.ExistentialCondition): for par in condition.parameters: @@ -388,6 +418,11 @@ def condition_to_rule_body(parameters, condition): assert isinstance(part, pddl.Literal), "Condition not normalized: %r" % part if not part.negated: result.append(part) + + # Require the primitive numeric expression (from the action cost) to be + # defined. + if pne is not None: + result.append(get_pne_definition_predicate(pne)) return result if __name__ == "__main__": diff --git a/src/translate/pddl_to_prolog.py b/src/translate/pddl_to_prolog.py index fee70f7c3b..7950451bec 100755 --- a/src/translate/pddl_to_prolog.py +++ b/src/translate/pddl_to_prolog.py @@ -155,6 +155,10 @@ def translate_facts(prog, task): assert isinstance(fact, pddl.Atom) or isinstance(fact, pddl.Assign) if isinstance(fact, pddl.Atom): prog.add_fact(fact) + else: + # Add a fact to indicate that the primitive numeric expression in + # fact.fluent has been defined. + prog.add_fact(normalize.get_pne_definition_predicate(fact.fluent)) def translate(task): # Note: The function requires that the task has been normalized.