pellierd / pddl4j

PDDL4J is an open source library under LGPL license whose purpose of PDDL4J is to facilitate the development of JAVA tools for Automated Planning based on PDDL language (Planning Domain Description Language).
https://lig-membres.imag.fr/pellier/
GNU Lesser General Public License v3.0
147 stars 68 forks source link

NPE when instantiating an unsolvable problem #88

Closed o-fir closed 2 years ago

o-fir commented 2 years ago

Getting a null pointer exception when trying to instantiate a problem that's clearly not satisfiable

Exception in thread "main" java.lang.NullPointerException
        at fr.uga.pddl4j.problem.FinalizedProblem.finalizeCondition(FinalizedProblem.java:825)
        at fr.uga.pddl4j.problem.FinalizedProblem.finalizeCondition(FinalizedProblem.java:832)
        at fr.uga.pddl4j.problem.FinalizedProblem.finalizeGoal(FinalizedProblem.java:781)
        at fr.uga.pddl4j.problem.HTNProblem.finalization(HTNProblem.java:224)
        at fr.uga.pddl4j.problem.AbstractProblem.instantiate(AbstractProblem.java:438)

If my goal state contains (task_performed task1), but I don't declare an (is_child task1 SOME_ACTIVITY) predicate, which is required to perform the task, I get a NPE.

To reproduce this behaviour, you can use the domain and problem below. Adding (is_child task1 a_no_children) (is_child task2 a_no_children) to the initial state fixes the problem.

My domain:

(define (domain tmp)

(:requirements :hierarchy
      :typing
      :method-preconditions
  :negative-preconditions
)

(:types study activity task resource place timeslot)

(:predicates (activity_has_tasks ?a - activity) (activity_primitive ?a - activity) (is_child ?t - task ?a - activity) (task_performed ?t - task)(activity_performed ?a - activity)

)

(:task do_tasks
  :parameters (?a - activity))

; m0_do_tasks is a recursive method that will try to perform some existing task for activity a, and then call itself again
; we need this recursivity because domain doesn't know how many tasks are there in an activity
(:method m0_do_tasks
  :parameters (?a - activity ?t - task ?s - timeslot)
  :task (do_tasks ?a)
  :precondition (and (is_child ?t ?a))
  :ordered-subtasks(and (t1 (schedule_task ?t ?s)) (t2 (do_tasks ?a)) )
)
; m1_do_tasks serves as termination for the recursive m0_do_tasks
(:method m1_do_tasks
  :parameters (?a - activity)
  :task (do_tasks ?a)
  :ordered-subtasks(and (t1 (nop)))
)

(:action schedule_task
  :parameters (?t - task ?s - timeslot)
  :precondition (and (not(task_performed ?t)))
  :effect(and (task_performed ?t))
)

(:action nop
  :parameters ()
  :precondition ()
  :effect ()
)
)

My problem:

(define (problem test)
(:domain tmp)

;---------------- Facts -----------------------
(:objects
a_no_children a_w_children - activity
task1 task2 task3 task4 - task
t1 t2 t3 t4 t5 - timeslot
bob anna - resource
)

;--------------- Initial State -----------------
    (:htn
        :parameters ()
        :subtasks (and
         (task0 (do_tasks a_no_children))
        )
    )
(:init

)

(:goal
    (and (task_performed task1)
    (task_performed task2)
    )
)

)
pellierd commented 2 years ago

The problem came from the fact that the goal was not simplified after method instantiation. Indeed, method instantiation removes actions that are not reachable by decomposition from the initial task network. Thus, fluents could become no more reachable. It is the case in your example for both fluents of the goal. The bug is fixed.