the framework moves chefs; the model keeps only can_reach
PDDL has no clock; I judge timing by running the plan
what each chef holds · what is on each tile · what is inside each station · which orders are open
Recipes: food changes only at the right station. Level 3 adds:
Goal (served ?order) for the orders I choose
(:action put_in_station
:parameters (?chef - chef ?food - food
?location - location)
:precondition (and
(can_reach ?chef ?location)
(holding ?chef ?food)
(station_empty ?location)
(accepts ?location ?food)
(safe_station ?location))
:effect (and
(hand_empty ?chef)
(in_station ?food ?location)
(not (holding ?chef ?food))
(not (station_empty ?location))))
can_reach · the chef can stand next to it; walking is gone
holding · what is in the chef's hand
station_empty · accepts · the recipe structure
safe_station · a Level 3 rule, written as a fact
effects: the food moves from the hand into the station
empty_plate · dirty_plate · plated_<dish>
A tile holds one item, so this is enough, and the model stays small.
take_from_storage · take_plate · pick_up · place_* · mount_tool · lift_station
put_in_station · pour_into_station · cook · grill_onto_plate · …
add_to_plate · station_to_plate · storage_to_plate · …
wash_plate · bin_food · bin_from_plate · clear_station
deliver
right now
pick the orders, write the kitchen as a problem file, in a few versions
one plan per version
2 s each
run each plan in a copy of the simulator, no screen
share the best plan out between the chefs, turn it into simulator commands
FilterCheck each recipe against the food that is really there. One impossible goal would leave no plan at all.
CapEach delivery takes a plate away until the next replan, so plan at most as many orders as plates.
ChooseNo dispenser: the plates are the budget for the whole level. most_valuable tries the combinations the food can make together, keeps the one that pays most.
(:objects … order_0 order_1 order_2 order_end - order) (:init … (order_dish order_0 plated_catalog_food_black_forest_cake) (order_turn order_0) (order_next order_0 order_end) (may_serve chef_chef_1_2 order_0) …) (:goal (and (served order_0) (served order_1) (served order_2)))
To the planner these cost the same.
So I write the kitchen several ways, and let the simulator judge.
Cut the plan into jobs, empty hands to empty hands. Each goes to the chef who can start it soonest. Six ways; the rollout picks.
An empty pan before someone's turn looks the same as after it. A step that puts something into a place waits for the earlier step there.
Action dependency graph, after Hönig et al. (2019)
elif step.name == "grill_onto_plate":
put = Combine(chef, expected_held_food=meat)
pick = PickUp(chef, expected_input_name=PLATE_NAME)
scoop = Combine(chef,
expected_target_equipment_contents_name=grilled_meat)
return [put @ pan, pick @ counter, scoop @ pan]The maximum is each order's reward plus its tips, worked out from the rules. Full marks means every available point was collected.
The last 1.5 marks of Level 3 came from fixing three kitchens, marked on the chart.
Four cakes. Three went out; the fourth always just missed its deadline.
The plans were valid, but wasteful. The first plan found did this:
served = run(domain, facts, steps) # orders this plan serves
index = 0
while index < len(steps):
kept = shorter({index}) # replay without step index
if kept is not None: # every order still served?
steps, index = kept, 0 # keep it out, start again
else:
index += 1
Six orders, three plates, no dispenser. Three went out, then it stopped at 212 of 248.
The plans were fine, but the goal was not: most-urgent-first used the last cherries on cheaper orders.
def visit(left, stock, chosen, total):
if (total, len(chosen)) > best_key: best = chosen # pays most
if len(chosen) == plates: return # budget spent
for name in dishes_left_by_deadline:
trial = dict(stock)
if _make(name, trial): # food left after this dish?
visit(rest, trial, chosen + [name], total + reward)
Four cakes, none delivered. The planner found no plan.
a place (station)
still a place
a thing to carry
Nothing could lift the emptied bowl back onto the mixer, so no goal was reachable.
(:action lift_station
:parameters (?chef - chef ?tool - tool ?location - location)
:precondition (and (can_reach ?chef ?location) (hand_empty ?chef)
(lifts ?location ?tool)
(station_empty ?location))
:effect (and (holding ?chef ?tool) (counter_empty ?location)
(not (lifts ?location ?tool)) …))
The approach worked: plain STRIPS for the kitchen, and simulator rollouts to judge timing.
Full marks on every level, within the time limit.
Each failure came from a gap in my model, not from the planner.
The planner counts steps, so a far station looks as good as a near one. I work around it with several problem versions and rollouts; on the 3-1 layout they ranged from 127 to 248 points. With walking distance as the action cost, the planner would prefer nearby work itself.
Plates already count down through count objects. Cherries could do the same, so the planner itself would refuse to spend the last one.
It turned valid plans into fast ones: 3-1 went from three cakes to four.
Thank you
Helmert, M. (2006). The Fast Downward planning system. JAIR 26 · Hönig, W., Kiesel, S., Tinka, A., Durham, J. W., Ayanian, N. (2019). Persistent and robust execution of MAPF schedules in warehouses (action dependency graphs). IEEE RA-L · Topcuoglu, H., Hariri, S., Wu, M.-Y. (2002). HEFT list scheduling. IEEE TPDS · Too Many Chefs simulator and kitchen guide, FIT5222, Monash University.