Closed MathisFederico closed 7 months ago
To be able to reproduce it, could you post the code generating the model ?
Here is an even smaller example with code:
from typing import Dict
import unified_planning.shortcuts as ups
from unified_planning.model.htn import HierarchicalProblem, Method, Task
from unified_planning.engines.results import PlanGenerationResult
PLAYER_ITEM_TYPE = ups.UserType("player_item")
def generate_hproblem() -> HierarchicalProblem:
hproblem = HierarchicalProblem("Small Hierarchical example")
# Types and objects
items_obj: Dict[str, "ups.Object"] = {
"wood": ups.Object("wood", PLAYER_ITEM_TYPE),
"plank": ups.Object("plank", PLAYER_ITEM_TYPE),
}
hproblem.add_objects(items_obj.values())
# Numeric fluents
amount = ups.Fluent("amount", ups.IntType(), item=PLAYER_ITEM_TYPE)
hproblem.add_fluent(amount, default_initial_value=0)
# Actions
actions = []
## action 1_search_wood
action_1 = ups.InstantaneousAction("1_search_wood")
action_1.add_increase_effect(amount(items_obj["wood"]), 1)
actions.append(action_1)
## action 2_craft_plank
action_2 = ups.InstantaneousAction("2_craft_plank")
action_2.add_precondition(ups.GE(amount(items_obj["wood"]), 1))
action_2.add_decrease_effect(amount(items_obj["wood"]), 1)
action_2.add_increase_effect(amount(items_obj["plank"]), 4)
actions.append(action_2)
hproblem.add_actions(actions)
# Tasks and methods
get_enough_of_item_task: Dict["str", "Task"] = {}
## get_enough_wood
task = hproblem.add_task("get-enough-of-wood", quantity=ups.IntType())
get_enough_of_item_task["wood"] = task
### noop method
noop_method = Method("has-enough-of-wood", quantity=ups.IntType())
noop_method.add_precondition(
ups.GE(amount(items_obj["wood"]), noop_method.quantity)
)
noop_method.set_task(task)
hproblem.add_method(noop_method)
### with action 1_search_wood
method_execute_1 = Method("execute-search-wood", quantity=ups.IntType())
quantity = method_execute_1.parameter("quantity")
stack_quantity = 1
get_nearly_enough_wood = method_execute_1.add_subtask(
get_enough_of_item_task["wood"],
quantity - stack_quantity,
)
execute_search_wood = method_execute_1.add_subtask(action_1)
method_execute_1.set_ordered([get_nearly_enough_wood, execute_search_wood])
method_execute_1.set_task(task, quantity)
hproblem.add_method(method_execute_1)
## get_enough_planks
task = hproblem.add_task("get-enough-of-plank", quantity=ups.IntType())
get_enough_of_item_task["plank"] = task
### noop method
noop_method = Method("has-enough-of-plank", quantity=ups.IntType())
quantity = noop_method.parameter("quantity")
noop_method.add_precondition(ups.GE(amount(items_obj["plank"]), quantity))
noop_method.set_task(task)
hproblem.add_method(noop_method)
### with action 2_craft_plank
method_execute_2 = Method("execute-search-plank", quantity=ups.IntType())
quantity = method_execute_2.parameter("quantity")
stack_quantity = 4
get_nearly_enough_plank = method_execute_2.add_subtask(
get_enough_of_item_task["plank"],
quantity - stack_quantity,
)
execute_search_plank = method_execute_2.add_subtask(action_2)
method_execute_2.set_ordered([get_nearly_enough_plank, execute_search_plank])
method_execute_2.set_task(task, quantity)
hproblem.add_method(method_execute_2)
hproblem.task_network.add_subtask(
get_enough_of_item_task["plank"], 5, ident="Get 5 planks"
)
return hproblem
def main():
hproblem = generate_hproblem()
print(hproblem, end="\n\n\n")
with ups.OneshotPlanner(name="aries") as planner:
results: "PlanGenerationResult" = planner.solve(hproblem)
if results.plan is None:
raise ValueError(
"Not plan could be found for this problem.\n"
f"Planner status: {results.status}\n"
f"Logs: {[log.message for log in results.log_messages]}"
)
print(f"Plan found: {results.plan}")
if __name__ == "__main__":
main()
Will generate output:
problem name = Small Hierarchical example
types = [player_item]
fluents = [
integer amount[item=player_item]
]
actions = [
action 1_search_wood {
preconditions = [
]
effects = [
amount(wood) += 1
]
}
action 2_craft_plank {
preconditions = [
(1 <= amount(wood))
]
effects = [
amount(wood) -= 1
amount(plank) += 4
]
}
]
objects = [
player_item: [wood, plank]
]
initial fluents default = [
integer amount[item=player_item] := 0
]
initial values = [
]
goals = [
]
abstract tasks = [
get-enough-of-wood[quantity=integer]
get-enough-of-plank[quantity=integer]
]
methods = [
method has-enough-of-wood(integer quantity) {
task = get-enough-of-wood(integer quantity)
preconditions = [
(quantity <= amount(wood))
]
}
method execute-search-wood(integer quantity) {
task = get-enough-of-wood(integer quantity)
subtasks = [
_t1: get-enough-of-wood((quantity - 1))
_t2: 1_search_wood()
]
}
method has-enough-of-plank(integer quantity) {
task = get-enough-of-plank(integer quantity)
preconditions = [
(quantity <= amount(plank))
]
}
method execute-search-plank(integer quantity) {
task = get-enough-of-plank(integer quantity)
subtasks = [
_t3: get-enough-of-plank((quantity - 4))
_t4: 2_craft_plank()
]
}
]
task network {
subtasks = [
Get 5 planks: get-enough-of-plank(5)
]
}
Traceback (most recent call last):
File "d:\Data\Projects\Github\Recherche\HierarchyCraft\small_hproblem.py", line 117, in <module>
main()
File "d:\Data\Projects\Github\Recherche\HierarchyCraft\small_hproblem.py", line 108, in main
raise ValueError(
ValueError: Not plan could be found for this problem.
Planner status: PlanGenerationResultStatus.INTERNAL_ERROR
Logs: ['type error\n Context: In problem Small Hierarchical example_domain/Small Hierarchical example']
Indeed, the Aries engine supports numeric parameters but our conversion from UP conservatively rejected it. It should work in the latest development version (see https://github.com/plaans/aries/tree/master/planning/unified/plugin )
By the way, it seems that your model has a few quirks :
Subtask
instances (a proper linter should have warned you about this, but it might be considered a bug that UP does not raise an exception).Below is the model with these flaws fixed. I will close this issue as it is not actually related to unified-planning. For any follow up bugs on Aries or its integration, you can open them in the aries
repository directly.
def generate_hproblem() -> HierarchicalProblem:
hproblem = HierarchicalProblem("Small Hierarchical example")
# Types and objects
items_obj: Dict[str, "ups.Object"] = {
"wood": ups.Object("wood", PLAYER_ITEM_TYPE),
"plank": ups.Object("plank", PLAYER_ITEM_TYPE),
}
hproblem.add_objects(items_obj.values())
# Numeric fluents
amount = ups.Fluent("amount", ups.IntType(), item=PLAYER_ITEM_TYPE)
hproblem.add_fluent(amount, default_initial_value=0)
# Actions
actions = []
## action 1_search_wood
action_1 = ups.InstantaneousAction("1_search_wood")
action_1.add_increase_effect(amount(items_obj["wood"]), 1)
actions.append(action_1)
## action 2_craft_plank
action_2 = ups.InstantaneousAction("2_craft_plank")
action_2.add_precondition(ups.GE(amount(items_obj["wood"]), 1))
action_2.add_decrease_effect(amount(items_obj["wood"]), 1)
action_2.add_increase_effect(amount(items_obj["plank"]), 4)
actions.append(action_2)
hproblem.add_actions(actions)
# Tasks and methods
get_enough_of_item_task: Dict["str", "Task"] = {}
## get_enough_wood
task = hproblem.add_task("get-enough-of-wood", quantity=ups.IntType())
get_enough_of_item_task["wood"] = task
### noop method
noop_method = Method("has-enough-of-wood", quantity=ups.IntType())
noop_method.add_precondition(
ups.GE(amount(items_obj["wood"]), noop_method.quantity)
)
noop_method.set_task(task)
hproblem.add_method(noop_method)
### with action 1_search_wood
method_execute_1 = Method("execute-search-wood", quantity=ups.IntType())
quantity = method_execute_1.parameter("quantity")
stack_quantity = 1
get_nearly_enough_wood = method_execute_1.add_subtask(
get_enough_of_item_task["wood"],
quantity - stack_quantity,
)
execute_search_wood = method_execute_1.add_subtask(action_1)
method_execute_1.set_ordered(get_nearly_enough_wood, execute_search_wood) # enforce order
method_execute_1.set_task(task, quantity)
hproblem.add_method(method_execute_1)
## get_enough_planks
task = hproblem.add_task("get-enough-of-plank", quantity=ups.IntType())
get_enough_of_item_task["plank"] = task
### noop method
noop_method = Method("has-enough-of-plank", quantity=ups.IntType())
quantity = noop_method.parameter("quantity")
noop_method.add_precondition(ups.GE(amount(items_obj["plank"]), quantity))
noop_method.set_task(task)
hproblem.add_method(noop_method)
### with action 2_craft_plank
method_execute_2 = Method("execute-search-plank", quantity=ups.IntType())
quantity = method_execute_2.parameter("quantity")
stack_quantity = 4
# do not overproduce
method_execute_2.add_precondition(ups.LT(amount(items_obj["plank"]), quantity))
get_nearly_enough_plank = method_execute_2.add_subtask(
get_enough_of_item_task["plank"],
quantity - stack_quantity,
)
get_nearly_enough_wood = method_execute_2.add_subtask( # allow collecting wood
get_enough_of_item_task["wood"],
1,
)
execute_search_plank = method_execute_2.add_subtask(action_2)
method_execute_2.set_ordered(get_nearly_enough_plank, execute_search_plank) # enforce order
method_execute_2.set_task(task, quantity)
hproblem.add_method(method_execute_2)
hproblem.task_network.add_subtask(
get_enough_of_item_task["plank"], 13, ident="Get 5 planks"
)
return hproblem
Ok thanks a lot !
User Story
As a user discovering HTN I want to use IntType parameters in hierarchical tasks to solve my problem/domain.
When using aries, the only available hierarchical planner in the unified planning framework, it raises a typerror.
Acceptance Criteria
Additional Material
Attention Points