PDDL 模拟面试题
代码改动对比的是我们自己提交里的 overcooked.pddl。绿色是新增的行,红色是删掉的行,没有颜色的是原样保留的行。
01.完整 problem
给你一个厨房:厨师空手,空盘在 A,准备好的果汁在 B,C 是交付点。请写出 objects、init、goal,并给出一条有效计划。
查看答案
先声明:本题只考符号实例,沿用当前 overcooked domain,移动由执行层处理。厨师能操作 A、B、C,目标是一张果汁订单。题设“成品在台面”按给定初态处理,不从这个例子推出所有加工食物都允许执行 Place 放到裸台面。
下面是可与当前 domain 配对的完整 problem,不是修改 domain:
(define (problem juice_delivery)
(:domain overcooked)
(:objects
chef_a - chef
counter_a counter_b delivery_c - location
juice - food
empty_plate plated_juice - plate
order_0 order_end - order
)
(:init
(hand_empty chef_a)
(can_reach chef_a counter_a)
(can_reach chef_a counter_b)
(can_reach chef_a delivery_c)
(item_at empty_plate counter_a)
(item_at juice counter_b)
(is_empty_plate empty_plate)
(plating empty_plate juice plated_juice)
(delivery_location delivery_c)
(order_turn order_0)
(order_next order_0 order_end)
(order_dish order_0 plated_juice)
(may_serve chef_a order_0)
)
(:goal (and (served order_0)))
)
有效计划:
(pick_up chef_a empty_plate counter_a) (add_to_plate chef_a empty_plate juice counter_b plated_juice) (deliver chef_a plated_juice delivery_c order_0 order_end)
第一步取盘,第二步将持有状态由空盘替换为果汁盘并消耗台面果汁,第三步消耗所持盘并完成订单。没有给两个初始占用台面添加 counter_empty,否则初态自相矛盾。
反例检查: 删掉 plating 事实,第二步不能执行;删掉 order_dish,第三步不能执行。holding chef_a plated_juice 不是交付完成目标。
口头回答: “Domain 定义通用规则;这个 problem 给出具体对象、当前事实和一张订单的交付目标。Planner 需要先拿盘,再装盘,最后交付。”
评分检查
区分食物种类与当前位置;补齐装盘关系、可达关系和交付位置。目标表达交付完成,而不是拿到成品。使用当前 domain 时,还需声明 order 对象及交付动作依赖的订单事实。
当前版本需要改哪里
不需要修改 D。纸面练习写一个独立 problem 即可;在应用中运行通常准备对应关卡,由 G 生成 problem。只有生成遗漏时才改 G。手写 problem 文件不会自动替代项目的动态生成流程。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
02.修复动作
pick_up 只增加 holding,没有删除 item_at 和 hand_empty。模型会允许什么错误行为?现场修复。
查看答案
正确修复如下,保持当前参数接口不变:
(:action pick_up :parameters (?chef - chef ?item - item ?location - location) :precondition (and (can_reach ?chef ?location) (hand_empty ?chef) (item_at ?item ?location) ) :effect (and (holding ?chef ?item) (counter_empty ?location) (not (hand_empty ?chef)) (not (item_at ?item ?location)) ) )
假设初态含 hand_empty(chef_a) 与 item_at(apple, counter_a)。若原动作只增加 holding,执行后厨师既空手又持有 apple,apple 还留在原台面;接着取另一件东西也可能被错误允许。
修复后,取走会消耗“手空”和“物品在原位”,并释放台面。保持的不变量是:每名厨师最多持有一件东西;同一份资源不能既被拿走又留在原位。
反例检查: 已持有 apple 的 chef_a 不能再取另一件物品,因为 hand_empty 不成立。另一个厨师也不能从同一台面再次拿走 apple,因为对应 item_at 已被删除。
口头回答: “STRIPS 没写删除效果的事实会继续成立,所以必须显式删除旧位置和手空状态。”
评分检查
用具体状态展示物品仍留在原处、厨师仍被认为空手的问题,补齐删除效果,并保持台面空闲状态一致。
当前版本需要改哪里
当前 pick_up 已包含这些删除效果。若考官人为删掉它们,本题是典型的只改 D 即可修复的练习,前提是不改变动作参数和执行语义。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
08.有限库存
原来食材无限供应,现在某个储物柜只剩两份。请用适合当前 planner 的方式建模。
查看答案
用离散计数,不依赖通用数值 fluent。沿用现有 count 类型、count_next、storage_of、holding、hand_empty,新增库存谓词:
+(stock ?s - location ?n - count)
本动作替换无限取料动作;不能保留另一个绕过库存的取料入口。
+(:action take_limited+ :parameters (?c - chef ?f - food ?s - location+ ?n - count ?left - count)+ :precondition (and+ (can_reach ?c ?s)+ (hand_empty ?c)+ (storage_of ?s ?f)+ (stock ?s ?n)+ (count_next ?left ?n)+ )+ :effect (and+ (holding ?c ?f)+ (not (hand_empty ?c))+ (stock ?s ?left)+ (not (stock ?s ?n))+ )+)
小实例使用三名厨师,避免第二次取料受到“第一人仍持有食材”的干扰:
(:objects chef_a chef_b chef_c - chef storage_a - location tomato - food n0 n1 n2 - count ) (:init (hand_empty chef_a) (hand_empty chef_b) (hand_empty chef_c) (can_reach chef_a storage_a) (can_reach chef_b storage_a) (can_reach chef_c storage_a) (storage_of storage_a tomato) (stock storage_a n2) (count_next n0 n1) (count_next n1 n2) ) (:goal (and (holding chef_a tomato) (holding chef_b tomato)))
(take_limited chef_a tomato storage_a n2 n1) (take_limited chef_b tomato storage_a n1 n0)
第三名厨师手仍空、可达储物柜,但没有任何 count_next ?left n0,所以不能再取。这里 tomato 是种类,份数由 stock 控制,不是声称两个厨师持有同一个物理 ID。
重要补充: 初始每个储物柜必须恰好有一个 stock 值,动作删除旧值。所有取料/直接装盘宏动作都要扣库存;重规划从真实剩余库存生成 init,不能重新写 stock n2。
口头回答: “把库存当作有限状态机 n2→n1→n0,零没有后继取料转移。”
评分检查
独立资源对象或离散计数及后继关系;取一次减一份;零库存不可取;重规划后不能重置成两份。
当前版本需要改哪里
食材 Storage 没有两份库存的计数状态,当前取食材操作不递减库存;盘子 dispenser 已有计数,但不等于食材库存已实现。建模至少 D + G;若动作参数变化需 T,并检查所有取料宏动作。要让世界真实遵守新规则,还需相应 E 支持,或明确实现并持久跟踪 agent 自限配额;两者不是同一种语义。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
12.验证计划
给你 init 和一串动作,其中某一步不适用。请找出第一处错误,并判断最终 goal 是否满足。
查看答案
用第 01 题的完整 problem 和当前动作定义,检查下面这条错误计划:
(pick_up chef_a empty_plate counter_a) (pick_up chef_a juice counter_b) (deliver chef_a plated_juice delivery_c order_0 order_end)
正前提 STRIPS 的逐步检查规则是:先要求 Pre(a) ⊆ S;满足后才计算 S' = (S − Del(a)) ∪ Add(a)。不适用动作没有合法后继,不能继续假装执行其效果。
| 步骤 | 检查 | 结论 |
|---|---|---|
| 1 | 手空、盘在 A、A 可达 | 适用;增加 holding(empty_plate) 和 counter_empty(A),删除 hand_empty 与盘在 A |
| 2 | 想再 pick_up juice | 首次失败:hand_empty 已不成立 |
| 3 | 前一步已非法 | 不能将其当作有效计划的一部分;第一步后也没有 holding(plated_juice) |
修复不是允许厨师同时拿盘和食物,而是把第二步替换为:
(add_to_plate chef_a empty_plate juice counter_b plated_juice)
然后原第三步可以执行,最终 served(order_0) 为真。
反例检查: 即使每一步都适用,提前在装盘后停止仍不满足交付目标。计划适用性与目标满足要分别检查。
口头回答: “第一个错误在第二步,因为第一步消耗了手空。用装盘动作替换第二次取物,才能保持容量约束。”
评分检查
每步先检查前提,再应用 add/delete effects;停在第一处不适用动作;区分计划可执行与目标达成。
当前版本需要改哪里
这是分析题,无需改生产代码。可手推一个独立 problem;当前 P 提供有限语法下的状态推进和检查,但不能据此当作完整 PDDL validator。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
13.厨师能力限制
只有 chef A 能使用烤箱,其他厨师仍能拿食材、装盘和交付。怎样限制?
查看答案
新增一个静态能力谓词,限制加工工位,而不是限制所有拿取、装盘和交付:
+(can_operate ?c - chef ?s - location)
以当前 cook 为基础的完整修改:
(:action cook :parameters (?c - chef ?input - food ?s - location ?output - food) :precondition (and (can_reach ?c ?s)+ (can_operate ?c ?s) (in_station ?input ?s) (cookable ?input ?s ?output) ) :effect (and (in_station ?output ?s) (not (in_station ?input ?s)) ) )
实例片段:
(:objects chef_a chef_b - chef oven_a board_a - location dough cake - food ) (:init (can_reach chef_a oven_a) (can_reach chef_b oven_a) (can_operate chef_a oven_a) (can_operate chef_a board_a) (can_operate chef_b board_a) (in_station dough oven_a) (cookable dough oven_a cake) ) (:goal (and (in_station cake oven_a)))
A 能 cook,B 即使站得很近也不能。其他无限制工位要为所有合法厨师初始化 can_operate,否则全局给 cook 加这个前提会误封其他加工。
假设必须说清: 以上把“使用烤箱”定义为执行 cook。如果题目还禁止 B 装入或取出烤箱食物,相关动作和宏动作也必须检查能力;如果自动烹饪不调用 cook,只限制 cook 就不充分。
当前工程关键追问: 调度器会重新分配厨师。能力事实不仅用于求解,还要用于候选厨师过滤,或者固定相关任务的厨师,不能让重分配绕过限制。
口头回答: “可达不等于有操作权限;静态能力关系限定合法厨师,执行分配也必须保留这一条件。”
评分检查
静态能力关系,仅限制相应加工动作;不得误限其他工作;执行分工必须保留能力条件。
当前版本需要改哪里
至少 D + G + S。当前 S 会重新分配厨师,其候选筛选主要考虑忙闲和可达性,不会自动执行新增 PDDL 能力谓词。因此只加 domain 前提仍可能在后处理中被破坏。若模拟器也要强制能力限制,需另有 E 支持。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
14.工位清洁
每次加工后工位变脏,必须清洁后才能加工下一份。请增加规则。
查看答案
先声明规则:加工开始前工位必须干净;加工结束后变脏;脏工位仍可取出成品;仅当工位空且厨师空手时才能清洁。清洁无需额外工具。本例是独立新增机制,不是假设当前模拟器已有该操作。
新增谓词:
+(station_clean ?s - location)+(station_dirty ?s - location)+(cleanable ?s - location)
修改 cook,保留原输入输出关系:
(:action cook :parameters (?c - chef ?input - food ?s - location ?output - food) :precondition (and (can_reach ?c ?s) (in_station ?input ?s) (cookable ?input ?s ?output)+ (station_clean ?s) ) :effect (and (in_station ?output ?s) (not (in_station ?input ?s))+ (station_dirty ?s)+ (not (station_clean ?s)) ) )+(:action clean_station+ :parameters (?c - chef ?s - location)+ :precondition (and+ (can_reach ?c ?s)+ (hand_empty ?c)+ (cleanable ?s)+ (station_empty ?s)+ (station_dirty ?s)+ )+ :effect (and+ (station_clean ?s)+ (not (station_dirty ?s))+ )+)
单次加工小实例的 problem 片段:
(:objects chef_a - chef board - location raw_food cooked_food - food) (:init (can_reach chef_a board) (hand_empty chef_a) (cleanable board) (station_clean board) (in_station raw_food board) (cookable raw_food board cooked_food) ) (:goal (and (in_station cooked_food board) (station_dirty board)))
执行 cook 达到该目标。若要继续使用工位,必须先取走成品、按规则放下所持物品或由另一空手厨师清洁,再 clean_station。取出动作不能要求 station_clean,否则加工后会把成品锁死。
反例检查: 工位仍有成品时,即使 station_dirty 成立也不能清洁;清洁后 dirty 必须删除。初态 clean/dirty 恰好选一项,不能都真。
口头回答: “容量与卫生是两个独立状态;空不等于干净,脏也不应该阻止把成品拿出来。”
评分检查
clean/dirty 转换;加工消耗 clean;清洁恢复 clean;明确清洁时工位是否必须为空。
当前版本需要改哪里
属于新规则,不能只增加一个谓词。需要 D + G;可执行的新清洁动作需要 T、S 的相关处理,以及真实状态/操作 E 支持。纸面练习只需完整 domain/problem,无需改模拟器。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
15.把移动纳入 PDDL
原模型省略位置与移动。现在必须显式规划移动,需要增加什么?
查看答案
把 floor cell 与设备 location 分开:厨师站在地板格,设备占相邻工位。为精简本题,方向不建模,假设站在允许操作的格子就能交互;如要求精确模拟器行为,还要表示朝向或解释转向编译。
新增类型和谓词(合并到原声明):
(:types cell - object) (:predicates+ (at_cell ?c - chef ?p - cell)+ (adjacent ?from - cell ?to - cell)+ (free_cell ?p - cell)+ (access ?p - cell ?s - location) )
完整移动动作:
+(:action move+ :parameters (?c - chef ?from - cell ?to - cell)+ :precondition (and+ (at_cell ?c ?from)+ (adjacent ?from ?to)+ (free_cell ?to)+ )+ :effect (and+ (at_cell ?c ?to)+ (free_cell ?from)+ (not (at_cell ?c ?from))+ (not (free_cell ?to))+ )+)
adjacent 只为不同、相邻且可通行的格子生成,双向道路需两条关系。初始每名厨师恰好一个 at_cell,只有无人占用的格子标记 free_cell。
(:objects chef_a - chef p1 p2 - cell) (:init (at_cell chef_a p1) (free_cell p2) (adjacent p1 p2) (adjacent p2 p1) ) (:goal (and (at_cell chef_a p2)))
计划为 (move chef_a p1 p2)。第二次从 p1 再移动不适用,因为旧位置已经删除。
所有交互动作还必须加入一个当前位置参数 ?p - cell,并检查 (at_cell ?c ?p) 与 (access ?p ?location);保留全局 can_reach 不能替代当前站位检查。
反例检查: 目标格有另一名厨师时没有 free_cell,不能进入。这个串行模型不允许直接交换两人占据的格子;不能擅自声称支持无空格交换。
当前工程边界: 显式 move 与自动导航不能各自修改实际位置。必须明确谁负责移动,并确保翻译、调度和符号位置一致。
口头回答: “可达性只表示能去那里;位置谓词才表示现在在那里。把移动放进模型后,操作也必须受位置约束。”
评分检查
厨师位置、邻接、move 动作;删除旧位置、加入新位置;操作检查站位;多厨师还需考虑占位规则。
当前版本需要改哪里
D + G + T,并重新检查 S 与控制器导航的分工。当前控制器自动移动和转向;只添加 move 会导致符号位置与自动导航不同步。不能把这题当作孤立增加一个 action。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
16.两步能否并行
A 放下食材,B 取走食材,能否同时执行?两人加工不同工位又如何?
查看答案
先约定并行语义:同一个时间步的各动作前提,都在该步开始状态检查,效果在步结束生效。
在此语义下,A 把食材放到原本空的台面,B 从那里取走食材,不能同一步执行。B 所需的 item_at 是 A 在步结束才产生的,属于因果依赖:
place_food(A, tomato, shared) → pick_up(B, tomato, shared)
如果一个模拟器在同一 tick 内按顺序应用动作,它可能允许先放后取;那是该 tick 内存在串行顺序,不能反过来证明两动作在上述并行语义下独立。
对于两名厨师加工不同工位,若两者起始前提都成立、厨师和工位不同、无共享食材/容器/路径等冲突,可以并行。不同动作名称或不同厨师本身并不是充分条件。
正前提 STRIPS 下,常用的保守独立性检查是:
Pre(a) ⊆ S,Pre(b) ⊆ S Del(a) ∩ (Pre(b) ∪ Add(b)) = ∅ Del(b) ∩ (Pre(a) ∪ Add(a)) = ∅
它保证两步不删除对方的前提或新增事实;还需要所有实际容量/互斥约束确实被模型表达。有负前提时,还要检查对方是否增加了被要求为假的事实。
反例检查: 两人都从同一台面拿唯一食材,即使起始时各自前提都成立,也不能同时拿;每个 pick_up 删除对方依赖的 item_at。
口头回答: “能同时开始、没有因果先后、没有资源冲突,才有并行依据。不同厨师只是必要考虑之一。”
评分检查
区分因果依赖和资源冲突;按厨师拆分串行计划不足以保证正确;说明所采用的时间步语义。
当前版本需要改哪里
口头分析不需要改文件。若要求改变运行时并行策略,重点是 S 和 T 中依赖构造;D 负责表达的资源状态也需一致。当前已存在调度与 guarded 操作。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
17.三步后熟、两步后烧焦
如何表达这一规则?当前 Fast Downward 能否直接处理 durative-action?
查看答案
先回答工具边界:Fast Downward 不直接支持 durative-action。本题可给出执行层计时方案,或明确的离散时间编译;不能只换成时间 PDDL 语法却继续使用不支持它的 planner。
为了消除边界歧义,假设食材在 t=0 开始加热,t=3 变熟,t=5 变焦;在 t=3 或 t=4 的决策时刻可取出,在 t=5 不可交付。实际边界必须与模拟器“先更新计时还是先执行动作”一致。
| 已经过时间 | 工位中状态 | 可否当成成品取出 |
|---|---|---|
| 0 | cooking_3 | 否 |
| 1 | cooking_2 | 否 |
| 2 | cooking_1 | 否 |
| 3 | ready_2 | 是 |
| 4 | ready_1 | 是 |
| 5 | burnt | 否 |
面试首选答案:分层执行。 高层用加工动作表达从原料到成品的任务关系;执行层启动烹饪、等待真实 ready 状态、及时取出,并检查 burnt/订单失效。调度估算行走和等待时间;无法赶到时改分工、调整开工时机或重规划。明确说明这不是 PDDL 内保证截止时间的完整证明。
若要求全部用经典 PDDL 表达: 用有限阶段谓词表示上表,通过统一时间推进把每个占用工位的阶段一起更新;加入动作选择阶段和强制 tick 阶段,禁止连续执行物理操作而不推进时间。多厨师一个 tick 内的动作选择不应分别把全局时钟重复推进。对多个工位,可逐工位处理该 tick,并用 processed 标记保证每个工位恰好更新一次,全部更新完才进入下一步。
这是编译方案说明,不是可直接粘贴的完整 domain;完整编译还需涵盖放入/取出时刻、所有计时资源和 tick 边界。只写一个可选 wait 会让 planner 任意冻结时间,模型不正确。
反例检查: 开火后让另一个厨师连续工作五步,烹饪计时仍必须前进;不能因为负责烹饪的厨师没动就不烧焦。已取出的食物也不应继续按“仍在炉上”的阶段烧焦。
口头回答: “时间是整个环境前进,不是只在我执行 wait 时前进。当前工具适合把计时交给执行层;若要纯规划保证,就必须完整编译全局 tick。”
评分检查
不能直接使用该 planner 不支持的时间规划;可离散时间编译,或由执行层计时、监测和重规划;必须解释时间如何推进及其他厨师动作如何影响它。
当前版本需要改哪里
已有定时烹饪、烧焦相关处理,但不是显式时间 PDDL。若只是调整现有计时参数,先查配置是否能表达;若改为时间展开模型,需 D + G + T + S/P 的一致支持,并与 E 对齐,不能只写几个时间谓词。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
18.截止时间与选单
两张订单无法全部按时完成,应如何修改目标选择?
查看答案
先明确优化目标。若目标是最大化按时交付收益,不能只按最近截止时间选单;若目标是完成订单数,策略也可能不同。普通经典 planner 针对给定 goal 找计划,不会自动把合取目标中的某一单当成可放弃项。
一个可手算的小例子:当前 t=0,单一不可并行的瓶颈资源,没有切换时间,且在截止时刻完成算有效。
| 订单 | 处理时长 | 截止时间 | 收益 |
|---|---|---|---|
| O1 | 4 | 5 | 10 |
| O2 | 4 | 6 | 30 |
每单单独都能完成,但一起需要 8 步,无论先做哪张,第二张都过期。若目标是收益,选 O2;若先按 earliest deadline 做 O1,只赚 10。
生成器选择 O2 后,可在保留其他必要对象和初态的前提下生成:
(:goal (and (served o2)))
可采用的最小流程:枚举少量候选订单或顺序 → 检查食材、盘子、可达性 → 估计/模拟完成时间 → 排除超时组合 → 按目标评分 → 为选中的目标生成 problem。候选很少时可穷举,规模大时用启发式,但必须承认启发式不保证最优。
重要限制: 如果游戏要求严格按顺序交付,O2 未必能跳过 O1;此时这个独立选单例子不适用,必须在可行策略集合中比较。生成器不能通过删掉顺序事实伪造可行性。
反例检查: 没有足够盘子/共享食材时,单独可行不代表联合可行。静态规划找到一条计划也不证明能赶上真实截止时间。
口头回答: “先决定值得且来得及完成哪些订单,再让 planner 为这些目标找计划;选单的目标函数和游戏顺序约束必须明确。”
评分检查
区分选目标与求计划;普通合取目标不会自动放弃低收益或超时订单;说明选择依据与不足。
当前版本需要改哪里
主要是 G 的 planned_orders、目标生成与重规划,必要时调整 rollout/调度评估;一般不必改 D。当前已有按截止时间排序、食材可行性筛选、盘子限制,以及部分场景的收益选单,但不能宣称保证全局最优或所有截止时间可行。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
19.最短计划与最快完成
计划 A 的 PDDL 动作更少,却走路更多;计划 B 动作更多但更快。怎么改进?
查看答案
先区分评价量:PDDL 动作数、高层动作成本、真实模拟器时间、多人完成时间 makespan,不是同一个指标。
例子:计划 A 有 4 个高层动作、需要走 20 格;计划 B 有 6 个高层动作、只走 4 格。若单人、每个动作一 tick、无等待,A 约 24 tick,B 约 10 tick。按动作数选 A,按执行时间选 B。
与当前系统最接近的答案: 保持高层动作抽象,比较候选计划经相同翻译/调度后的 rollout;若目标是收益优先,则先比实际得分,再用完成时间打破平局。只比较翻译前的高层长度不够。
若题目要求 action costs: 以下是独立教学片段,展示完整的成本相关修改,不是当前 domain 的可直接替换动作。把动作参数中的状态与成本实例具体化,以真实路线或合法静态估计决定代价。
(:requirements :strips :typing :action-costs) (:functions (total-cost)) +(:action deliver_costed+ :parameters (?c - chef ?p - plate ?l - location ?o - order)+ :precondition (and+ (can_reach ?c ?l)+ (holding ?c ?p)+ (delivery_location ?l)+ (order_dish ?o ?p)+ (may_serve ?c ?o)+ (order_turn ?o)+ )+ :effect (and+ (served ?o)+ (hand_empty ?c)+ (not (holding ?c ?p))+ (not (order_turn ?o))+ (increase (total-cost) 1)+ )+)
这个例子限定无顺序后继的单次交付教学模型;真实项目须保留原 deliver 的 order_next 逻辑。problem 的 init 增加:
(= (total-cost) 0)
problem 再增加:
(:metric minimize (total-cost))
所有有真实代价的动作都应按所选度量计费,而非只给交付计费。常数成本 1 只是在展示语法,不会凭空解决步行时间问题。还要使用能处理成本且符合所需最优性保证的搜索配置;仅写 metric 不保证某个 satisficing 搜索返回最优解。
反例检查: 两个各 10 tick 的独立任务并行完成可能只需 10 tick,而相加成本为 20。sum of costs 不等于 makespan。固定宏动作成本也未必能表示随厨师当前位置变化的行走距离。
当前工程边界: progress.py 的布尔效果推进不直接支持 increase 效果,需同步处理,否则 planner 支持并不等于本系统支持。
口头回答: “我先对齐真正的评价指标,再决定用成本模型还是执行模拟。动作少不保证走得少,更不保证多人更快完成。”
评分检查
区分动作数、成本与实际完成时间;固定动作代价未必准确描述动态路径和并行 makespan。
当前版本需要改哪里
现有方案已用 rollout 比较候选,并在 S 中估计时间。改善该方案主要涉及 G/T/S 与评估。若改为 :action-costs,须同时处理 D、problem 的初始化/metric、求解器搜索配置,以及 P 对数值效果的支持边界;只加 increase(total-cost) 不够。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
20.重复实例与状态抽象
两个位置各有一份番茄,都用同一个 tomato 符号表示,是否一定错误?什么时候会丢失信息?
查看答案
不一定错,先问 tomato 表示“食物种类”还是“某一份物理食物”。当前模型主要按种类和持有位置表达资源,不应自动套用“一个对象符号必须是一件物品”的解释。
如果表示种类:
(item_at tomato counter_a) (item_at tomato counter_b)
可以表示 A、B 各有一份番茄,前提是每个位置容量一且每个关系槽最多一份。取走 A 只删除 (item_at tomato counter_a),B 的事实保留;厨师 holding 的 chef 参数同样能区分不同持有者。容量和状态转移必须保持正确。
什么时候丢信息?
- 同一容器里两份相同番茄不能靠重复同一个原子表示;集合会合并重复事实。
- 两份番茄有不同新鲜度、归属或处理时间时,仅靠类型无法追踪差异。
- 需要跨位置追踪同一份物品身份时,仅表示“此处有番茄”可能不够。
如果需要个体身份,可使用下面的独立扩展,保留 food 类型作种类对象,新增 portion 表示具体份:
(:types portion - object) (:predicates+ (kind ?x - portion ?k - food)+ (portion_at ?x - portion ?l - location)+ (holding_portion ?c - chef ?x - portion) )
(:objects tomato_1 tomato_2 - portion tomato - food counter_a counter_b - location chef_a - chef ) (:init (kind tomato_1 tomato) (kind tomato_2 tomato) (portion_at tomato_1 counter_a) (portion_at tomato_2 counter_b) (hand_empty chef_a) (can_reach chef_a counter_a) ) (:goal (and (holding_portion chef_a tomato_1)))
完整取物动作:
+(:action pick_portion+ :parameters (?c - chef ?x - portion ?l - location)+ :precondition (and+ (hand_empty ?c)+ (can_reach ?c ?l)+ (portion_at ?x ?l)+ )+ :effect (and+ (holding_portion ?c ?x)+ (counter_empty ?l)+ (not (hand_empty ?c))+ (not (portion_at ?x ?l))+ )+)
后续配方需要通过 kind 匹配类型,并明确输入个体的消耗与结果个体/状态产生方式。经典 PDDL 的对象集合通常预先声明,不能把“运行时随便新建 output object”当成免费操作。
反例检查: 类型复用方案在两个不同位置可用,但同一位置重复两条 item_at 不表示两份;个体方案则不能再把 tomato_1 当成食物类型直接传给原有配方关系。
口头回答: “抽象是否正确取决于保留的信息是否足以表达规则;需要计数或个体属性时再细化,不应为所有场景无条件增加身份对象。”
评分检查
先明确符号代表种类还是个体。不同位置可通过位置参数区别;数量、个体属性和身份追踪可能仍需要独立对象或计数。不能不加条件地断言复用符号一定错或一定正确。
当前版本需要改哪里
当前食物以种类、盘子以内容状态抽象。解释题无需修改;若改为具体 item IDs,则要整体核对 D + G + T,以及依赖当前符号约定的 S/P、订单匹配和评估,不能只给 objects 换名字。
实现范围对应作业仓库提交 1acafd9;这些示例是面试答案,不是已集成到模拟器的补丁。完整文件与片段的区别、代码缩写及公共来源见 README。
代码改动对比的是课程模板域 D0。绿色是新增的行,红色是删掉的行,没有颜色的是原样保留的行。
本套说明
这一套是重新出的,没有参考第一套。所有答案都写成了可以运行的 PDDL,并用 Fast Downward 实际解过。2026-11-04 去重时补了 A11 到 A13 和 B11 四道,覆盖厨房手册里原来没人出题的规则:烧焦、按序上菜、奖励不同、做不出的单。
这一套按什么思路出
Q&A 的评分标准在 Presentation_rubric.pdf 第 2 页。两道题各 16 分,只看 PDDL 写得对不对:动作缺参数、前提或效果之一,或者问题缺对象表、初始状态、目标之一,就是 0 分;少一个谓词、少一个前提或效果、类型写错、引用无关的东西,一两处是 8 分;语法错误不算小错。
所以这一套假设两道题分别是:
- A 类,写动作。 在课程模板域上加一条新规则,写出动作的参数、前提和效果。模板对每个人都一样,最适合作为统一的出题起点。
- B 类,写问题实例。 给一个小厨房,写出对象表、初始状态和目标。
这只是根据评分标准做的推测,课程没有公开过往年题目。
两个基础域
- D0 是课程模板域,也就是
submission_ori/overcooked.pddl,只有pick_up、add_to_plate、deliver三个动作。A 类全部题目和 B01、B02 都从它出发。完整文本见pddl/d0.pddl。 - D1 是我为 B 类写的一个小型多厨师教学域,见
pddl/d1.pddl。它有厨师类型和can_reach,有放下、储藏柜、砧板、垃圾桶和订单对象,和我们自己的域思路一致,但小得多。B03 到 B11 都用它。
D1 的谓词和动作如下,B 类题目前先读一遍:
; D1: a small multi-chef teaching domain used by part B, questions B3 to B10.
(define (domain kitchen)
(:requirements :strips :typing)
(:types
chef location item order - object
food plate - item
)
(:predicates
(hand_empty ?c - chef)
(holding ?c - chef ?i - item)
(can_reach ?c - chef ?l - location)
(item_at ?i - item ?l - location)
(counter_empty ?l - location)
(storage ?l - location ?f - food)
(cutboard ?l - location)
(chops ?raw - food ?chopped - food)
(plating ?p - plate ?f - food ?r - plate)
(delivery ?l - location)
(bin ?l - location)
(wants ?o - order ?p - plate)
(pending ?o - order)
(served ?o - order)
)
(:action pick_up
:parameters (?c - chef ?i - item ?l - location)
:precondition (and (can_reach ?c ?l) (hand_empty ?c) (item_at ?i ?l))
:effect (and (holding ?c ?i) (counter_empty ?l) (not (hand_empty ?c)) (not (item_at ?i ?l)))
)
(:action place
:parameters (?c - chef ?i - item ?l - location)
:precondition (and (can_reach ?c ?l) (holding ?c ?i) (counter_empty ?l))
:effect (and (item_at ?i ?l) (hand_empty ?c) (not (holding ?c ?i)) (not (counter_empty ?l)))
)
(:action take_from_storage
:parameters (?c - chef ?f - food ?l - location)
:precondition (and (can_reach ?c ?l) (hand_empty ?c) (storage ?l ?f))
:effect (and (holding ?c ?f) (not (hand_empty ?c)))
)
(:action chop
:parameters (?c - chef ?raw - food ?chopped - food ?l - location)
:precondition (and (can_reach ?c ?l) (hand_empty ?c) (cutboard ?l) (item_at ?raw ?l) (chops ?raw ?chopped))
:effect (and (item_at ?chopped ?l) (not (item_at ?raw ?l)))
)
(:action add_to_plate
:parameters (?c - chef ?p - plate ?f - food ?l - location ?r - plate)
:precondition (and (can_reach ?c ?l) (holding ?c ?p) (item_at ?f ?l) (plating ?p ?f ?r))
:effect (and (holding ?c ?r) (counter_empty ?l) (not (holding ?c ?p)) (not (item_at ?f ?l)))
)
(:action bin_food
:parameters (?c - chef ?f - food ?l - location)
:precondition (and (can_reach ?c ?l) (holding ?c ?f) (bin ?l))
:effect (and (hand_empty ?c) (not (holding ?c ?f)))
)
(:action deliver
:parameters (?c - chef ?p - plate ?l - location ?o - order)
:precondition (and (can_reach ?c ?l) (holding ?c ?p) (delivery ?l) (wants ?o ?p) (pending ?o))
:effect (and (served ?o) (hand_empty ?c) (not (pending ?o)) (not (holding ?c ?p)))
)
)
答题检查清单
写动作时:
- 参数表里的每个变量,前提或效果里都用到了吗?前提和效果里用到的每个变量,都在参数表里吗?
- 前提里有没有写“手是空的”或者“手上拿着什么”?
- 效果是不是成对的?加了
holding就要删hand_empty;东西从 A 挪到 B,就要删 A 处的事实。 - 新谓词是否都是肯定形式?要表达“没有”,就定义一个肯定的谓词。
- 加了新谓词以后,已有的动作要不要跟着改?
写问题实例时:
- 对象表:每个出现在初始状态和目标里的名字都声明了吗?类型对吗?
- 初始状态按“厨师、台面、设备、储藏柜和出菜口、菜谱关系、订单、可达性”的顺序写,不容易漏。
- 只有真正空着的台面才写“台面为空”。
- 目标写的是“送达”,不是“拿在手上”。
建议每题限时 5 分钟,写完再对照答案和评分要点自己打分。
怎么自己验证
cd ../too-many-chefs-a-1-exsun-da21 PYTHONPATH=. .venv/bin/python ../mock-quiz/set2/pddl/verify.py
它会把每道题的完整域和问题写进 pddl/build/,交给 Fast Downward 去解,并检查计划里是否用到了题目要求的动作。改了 pddl/cases.py 里的答案之后,再运行 python3 pddl/gen_readme.py 重新生成这份文档。
A 类:写动作(基于模板域 D0)
A01.加一个“放下”动作
模板域只能拿起东西,不能放下。请加一个动作 place:厨师把手上的东西放到一个空台面上。
要求:
- 不能用否定前提,所以需要一个表示“台面是空的”的新谓词。
- 写完
place之后,说明模板里已有的哪些动作也要跟着改,并写出改后的版本。
查看答案
; 在 :predicates 里加+ (counter_empty ?location - location) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location) ) :effect (and (holding ?item)+ (counter_empty ?location); new: the counter is free again (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and (holding ?result)+ (counter_empty ?location); new: the food has left the counter (not (holding ?plate)) (not (item_at ?food ?location)) ) )
评分要点
- 最常见的失分点是只写了
place,忘了改pick_up和add_to_plate。这两个动作都会让台面上的东西离开,台面重新变空,所以效果里要加上(counter_empty ?location)。漏掉的话,台面只要被用过一次就永远不能再放东西。漏一处算小错,两处都漏也还在 8 分档。 place的前提要写“拿着这个东西”,而不是hand_empty。写反了整个动作就不对,属于大错。- 效果要成对:加了
item_at和hand_empty,就要删掉holding和counter_empty。 - 问题文件里,只有真正的空台面才写
counter_empty。出菜口、储藏柜不能写,否则规划器会把东西放到出菜口上。
验证用的测试问题和规划器给出的计划
(define (problem a01) (:domain overcooked)
(:objects tomato - food empty_plate plated_tomato - plate loc_a loc_b loc_c - location)
(:init (holding tomato) (item_at empty_plate loc_a) (counter_empty loc_b)
(delivery_location loc_c) (plating empty_plate tomato plated_tomato))
(:goal (delivered plated_tomato)))
1. place tomato loc_b 2. pick_up empty_plate loc_a 3. add_to_plate empty_plate tomato loc_b plated_tomato 4. deliver plated_tomato loc_c
A02.从储藏柜拿食材
在 A01 的基础上,厨房里多了储藏柜。储藏柜放在某个位置上,里面的某种食材可以无限地拿。请写动作 take_from_storage,以及它需要的谓词。
查看答案
; 在 :predicates 里加+ (counter_empty ?location - location)+ (storage ?location - location ?food - food) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location) ) :effect (and (holding ?item)+ (counter_empty ?location); new: the counter is free again (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and (holding ?result)+ (counter_empty ?location); new: the food has left the counter (not (holding ?plate)) (not (item_at ?food ?location)) ) ) + (:action take_from_storage+ :parameters (?food - food ?location - location)+ :precondition (and+ (hand_empty)+ (storage ?location ?food)+ )+ :effect (and+ (holding ?food)+ (not (hand_empty))+ )+ )
评分要点
- 储藏柜是“无限的”,所以效果里不能删掉
(storage ?location ?food)。删了的话,每个储藏柜只能拿一次。 - 不要用
item_at来表示储藏柜里的食物。pick_up会把它删掉,效果和上一条一样。 - 参数类型写
food而不是item。写成item会让规划器以为储藏柜也能拿出盘子,算小错。 - 前提要有
hand_empty,效果要删掉hand_empty。
验证用的测试问题和规划器给出的计划
(define (problem a02) (:domain overcooked)
(:objects tomato - food empty_plate plated_tomato - plate loc_s loc_a loc_b loc_c - location)
(:init (hand_empty) (storage loc_s tomato) (item_at empty_plate loc_a) (counter_empty loc_b)
(delivery_location loc_c) (plating empty_plate tomato plated_tomato))
(:goal (delivered plated_tomato)))
1. take_from_storage tomato loc_s 2. place tomato loc_b 3. pick_up empty_plate loc_a 4. add_to_plate empty_plate tomato loc_b plated_tomato 5. deliver plated_tomato loc_c
A03.在砧板上切菜
在 A01 的基础上,厨房里有砧板。砧板本身也是一个台面:生的食物用 place 放上去,切好后用 pick_up 拿走。请写一个动作 chop:厨师空着手,把砧板上的生食物切成切好的食物。
“哪种食物切了变成哪种”由问题文件给出,不要写死在域里。
查看答案
; 在 :predicates 里加+ (counter_empty ?location - location)+ (cutboard ?location - location)+ (chops ?raw - food ?chopped - food) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location) ) :effect (and (holding ?item)+ (counter_empty ?location); new: the counter is free again (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and (holding ?result)+ (counter_empty ?location); new: the food has left the counter (not (holding ?plate)) (not (item_at ?food ?location)) ) ) + (:action chop+ :parameters (?raw - food ?chopped - food ?location - location)+ :precondition (and+ (hand_empty)+ (cutboard ?location)+ (item_at ?raw ?location)+ (chops ?raw ?chopped)+ )+ :effect (and+ (item_at ?chopped ?location)+ (not (item_at ?raw ?location))+ )+ )
评分要点
- 切菜时厨师空着手,所以前提是
hand_empty,而且效果里不改变手的状态。如果写成“拿着食物切”,就和题目规则不符。 - 效果必须同时加上切好的食物、删掉生的食物。只加不删的话,砧板上会同时有生的和切好的,算小错。
- 一定要有
(cutboard ?location)这个前提,否则任何台面都能切菜。 - “生的变成什么”用
(chops ?raw ?chopped)放在问题文件里,不要在域里写lettuce这种具体名字。在域里写具体对象名属于大错。
验证用的测试问题和规划器给出的计划
(define (problem a03) (:domain overcooked)
(:objects lettuce chopped_lettuce - food empty_plate plated_chopped_lettuce - plate
loc_board loc_a loc_c - location)
(:init (hand_empty) (cutboard loc_board) (item_at lettuce loc_board) (item_at empty_plate loc_a)
(chops lettuce chopped_lettuce) (delivery_location loc_c)
(plating empty_plate chopped_lettuce plated_chopped_lettuce))
(:goal (delivered plated_chopped_lettuce)))
1. chop lettuce chopped_lettuce loc_board 2. pick_up empty_plate loc_a 3. add_to_plate empty_plate chopped_lettuce loc_board plated_chopped_lettuce 4. deliver plated_chopped_lettuce loc_c
A04.把东西倒进垃圾桶
厨房里有垃圾桶。请写两个动作:
bin_food:把手上的食物扔掉,手变空。bin_from_plate:厨师拿着一个装了菜的盘子,把菜倒掉,手里变成空盘子。
提示:不需要新的“盘子—食物”关系,模板里已有的 plating 可以反过来用。
查看答案
; 在 :predicates 里加+ (bin ?location - location) + (:action bin_food+ :parameters (?food - food ?location - location)+ :precondition (and+ (holding ?food)+ (bin ?location)+ )+ :effect (and+ (hand_empty)+ (not (holding ?food))+ )+ ) + (:action bin_from_plate+ :parameters (?full - plate ?empty - plate ?food - food ?location - location)+ :precondition (and+ (holding ?full)+ (plating ?empty ?food ?full)+ (bin ?location)+ )+ :effect (and+ (holding ?empty)+ (not (holding ?full))+ )+ )
评分要点
bin_from_plate的关键是反过来使用(plating ?empty ?food ?full):它说“空盘加这个食物等于这盘菜”,倒掉食物就是从“这盘菜”回到“空盘”。重新定义一个谓词也能拿分,但要在问题文件里多写一遍事实。bin_from_plate的结果是拿着空盘,不是hand_empty。写成手变空,就等于把盘子也扔了,属于一处小错。bin_food的参数类型是food。写成item的话,盘子也能扔掉,算小错。- 垃圾桶不是台面,问题文件里不要给它写
counter_empty。
验证用的测试问题和规划器给出的计划
(define (problem a04) (:domain overcooked)
(:objects tomato lettuce - food empty_plate plated_tomato plated_lettuce - plate
loc_a loc_x loc_c - location)
(:init (holding plated_tomato) (item_at lettuce loc_a) (bin loc_x) (delivery_location loc_c)
(plating empty_plate tomato plated_tomato) (plating empty_plate lettuce plated_lettuce))
(:goal (delivered plated_lettuce)))
1. bin_from_plate plated_tomato empty_plate tomato loc_x 2. add_to_plate empty_plate lettuce loc_a plated_lettuce 3. deliver plated_lettuce loc_c
A05.从一个厨师改成多个厨师
模板域默认只有一个厨师,所以 hand_empty 没有参数。现在厨房里有两个厨师,每个厨师只能在自己能走到的位置干活。请改写类型、谓词,以及 pick_up、add_to_plate、deliver 三个动作。
查看答案
; 在 :types 里加+ chef - object ; :predicates 整段换成- (hand_empty)+ (hand_empty ?chef - chef)- (holding ?item - item)+ (holding ?chef - chef ?item - item)+ (can_reach ?chef - chef ?location - location) (item_at ?item - item ?location - location) (delivery_location ?location - location) (plating ?plate - plate ?food - food ?result - plate) (delivered ?plate - plate) (:action pick_up- :parameters (?item - item ?location - location)+ :parameters (?chef - chef ?item - item ?location - location) :precondition (and- (hand_empty)+ (can_reach ?chef ?location)+ (hand_empty ?chef) (item_at ?item ?location) ) :effect (and- (holding ?item)- (not (hand_empty))+ (holding ?chef ?item)+ (not (hand_empty ?chef)) (not (item_at ?item ?location)) ) ) (:action add_to_plate- :parameters (?plate - plate ?food - food ?location - location ?result - plate)+ :parameters (?chef - chef ?plate - plate ?food - food ?location - location ?result - plate) :precondition (and- (holding ?plate)+ (can_reach ?chef ?location)+ (holding ?chef ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and- (holding ?result)- (not (holding ?plate))+ (holding ?chef ?result)+ (not (holding ?chef ?plate)) (not (item_at ?food ?location)) ) ) (:action deliver- :parameters (?plate - plate ?location - location)+ :parameters (?chef - chef ?plate - plate ?location - location) :precondition (and- (holding ?plate)+ (can_reach ?chef ?location)+ (holding ?chef ?plate) (delivery_location ?location) ) :effect (and (delivered ?plate)- (hand_empty)- (not (holding ?plate))+ (hand_empty ?chef)+ (not (holding ?chef ?plate)) ) )
评分要点
- 要新增类型
chef,并给hand_empty、holding都加上?chef参数。只改动作不改谓词,属于大错,因为参数个数对不上。 - 每个动作都要加
(can_reach ?chef ?location)。漏掉一个动作算小错。 item_at、plating、delivered描述的是厨房,不属于某个厨师,不要给它们加厨师参数。加了属于引用无关参数,算小错。- 问题文件里,每个厨师都要单独写一条
hand_empty,每个厨师能到的每个位置都要单独写一条can_reach。
验证用的测试问题和规划器给出的计划
(define (problem a05) (:domain overcooked)
(:objects chef_1 chef_2 - chef juice - food empty_plate plated_juice - plate loc_a loc_b loc_c - location)
(:init (hand_empty chef_1) (hand_empty chef_2)
(can_reach chef_1 loc_a)
(can_reach chef_2 loc_a) (can_reach chef_2 loc_b) (can_reach chef_2 loc_c)
(item_at juice loc_a) (item_at empty_plate loc_b) (delivery_location loc_c)
(plating empty_plate juice plated_juice))
(:goal (delivered plated_juice)))
1. pick_up chef_2 empty_plate loc_b 2. add_to_plate chef_2 empty_plate juice loc_a plated_juice 3. deliver chef_2 plated_juice loc_c
A06.锅里做汤
在 A01 的基础上,厨房里有一口锅。做汤的规则是:先把一种食材放进空锅,再把第二种食材加进去,两种合起来变成汤。两种食材先放哪个都可以。
请写 put_in_pot 和 add_to_pot 两个动作和它们需要的谓词。盛汤的动作 ladle_to_plate 已经给出,见答案最后一个动作。
查看答案
; 在 :predicates 里加+ (counter_empty ?location - location)+ (pot_empty ?location - location)+ (in_pot ?food - food ?location - location)+ (mix ?first - food ?second - food ?result - food) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location) ) :effect (and (holding ?item)+ (counter_empty ?location); new: the counter is free again (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and (holding ?result)+ (counter_empty ?location); new: the food has left the counter (not (holding ?plate)) (not (item_at ?food ?location)) ) ) + (:action put_in_pot+ :parameters (?food - food ?location - location)+ :precondition (and+ (holding ?food)+ (pot_empty ?location)+ )+ :effect (and+ (in_pot ?food ?location)+ (hand_empty)+ (not (holding ?food))+ (not (pot_empty ?location))+ )+ ) + (:action add_to_pot+ :parameters (?food - food ?inside - food ?result - food ?location - location)+ :precondition (and+ (holding ?food)+ (in_pot ?inside ?location)+ (mix ?inside ?food ?result)+ )+ :effect (and+ (in_pot ?result ?location)+ (hand_empty)+ (not (in_pot ?inside ?location))+ (not (holding ?food))+ )+ ) + (:action ladle_to_plate+ :parameters (?plate - plate ?soup - food ?location - location ?result - plate)+ :precondition (and+ (holding ?plate)+ (in_pot ?soup ?location)+ (plating ?plate ?soup ?result)+ )+ :effect (and+ (holding ?result)+ (pot_empty ?location)+ (not (holding ?plate))+ (not (in_pot ?soup ?location))+ )+ )
评分要点
- 要区分“锅是空的”和“锅里有东西”。
put_in_pot需要pot_empty,并在效果里删掉它。add_to_pot不能再要求pot_empty,否则永远加不进第二样东西。 add_to_pot的效果要把锅里原来的东西换成结果:加上(in_pot ?result ?location),删掉(in_pot ?inside ?location)。- “两种先放哪个都行”不需要两个动作,只需要在问题文件里把
mix写两条:(mix onion tomato soup)和(mix tomato onion soup)。只写一条不算错,但计划会被限定成一种顺序,可以口头说明。 - 两个动作都要让手变空。
验证用的测试问题和规划器给出的计划
(define (problem a06) (:domain overcooked)
(:objects onion tomato soup - food empty_plate plated_soup - plate loc_a loc_b loc_c loc_pot loc_d - location)
(:init (hand_empty) (item_at onion loc_a) (item_at tomato loc_b) (item_at empty_plate loc_c)
(pot_empty loc_pot) (delivery_location loc_d)
(mix onion tomato soup) (mix tomato onion soup)
(plating empty_plate soup plated_soup))
(:goal (delivered plated_soup)))
1. pick_up onion loc_a 2. put_in_pot onion loc_pot 3. pick_up tomato loc_b 4. add_to_pot tomato onion soup loc_pot 5. pick_up empty_plate loc_c 6. ladle_to_plate empty_plate soup loc_pot plated_soup 7. deliver plated_soup loc_d
A07.用订单对象来表示目标
模板的目标写成 (delivered plated_juice)。如果同时来了两张椰子汁订单,这个写法就分不出“送了一杯”和“送了两杯”。
请改写 deliver:引入 order 类型,每张订单要一种菜,送出一盘菜就完成一张订单。写出新增的类型、谓词和新的 deliver,并说明目标应该怎么写。
查看答案
; 在 :types 里加+ order - object ; 在 :predicates 里加+ (wants ?order - order ?plate - plate)+ (pending ?order - order)+ (served ?order - order) (:action deliver- :parameters (?plate - plate ?location - location)+ :parameters (?plate - plate ?location - location ?order - order) :precondition (and (holding ?plate) (delivery_location ?location)+ (wants ?order ?plate)+ (pending ?order) ) :effect (and- (delivered ?plate)+ (served ?order) (hand_empty)+ (not (pending ?order)) (not (holding ?plate)) ) )
评分要点
- 目标要改成每张订单一个
(served order_n),用and连起来。目标还写(delivered ...)就没有解决题目的问题,属于“目标不恰当”的大错。 deliver的前提要检查(wants ?order ?plate),否则送什么菜都能完成订单。pending的作用是保证每张订单只被完成一次。不写它,计划通常也能找到,但模型会允许“同一张单送两次”这种没有意义的动作,口头说清楚即可。- 两个空盘是同一个对象
empty_plate,放在两个不同的位置上,这在 PDDL 里是允许的。
验证用的测试问题和规划器给出的计划
(define (problem a07) (:domain overcooked)
(:objects juice - food empty_plate plated_juice - plate loc_a loc_b loc_c loc_d loc_e - location
order_1 order_2 - order)
(:init (hand_empty) (item_at juice loc_a) (item_at juice loc_b)
(item_at empty_plate loc_c) (item_at empty_plate loc_d) (delivery_location loc_e)
(plating empty_plate juice plated_juice)
(wants order_1 plated_juice) (wants order_2 plated_juice)
(pending order_1) (pending order_2))
(:goal (and (served order_1) (served order_2))))
1. pick_up empty_plate loc_c 2. add_to_plate empty_plate juice loc_b plated_juice 3. deliver plated_juice loc_e order_1 4. pick_up empty_plate loc_d 5. add_to_plate empty_plate juice loc_a plated_juice 6. deliver plated_juice loc_e order_2
A08.台面着火
在 A01 的基础上,某些台面会着火。着火的台面上不能拿东西,也不能从那里取食物装盘。厨师拿着灭火器对着着火的台面使用,火就灭了。灭火器本身是一件可以拿起和放下的东西。
请写 extinguish 动作,并改写受影响的已有动作。
查看答案
; 在 :predicates 里加+ (counter_empty ?location - location)+ (on_fire ?location - location)+ (no_fire ?location - location)+ (extinguisher ?item - item) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location)+ (no_fire ?location); new ) :effect (and (holding ?item)+ (counter_empty ?location) (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result)+ (no_fire ?location); new ) :effect (and (holding ?result)+ (counter_empty ?location) (not (holding ?plate)) (not (item_at ?food ?location)) ) ) + (:action extinguish+ :parameters (?tool - item ?location - location)+ :precondition (and+ (holding ?tool)+ (extinguisher ?tool)+ (on_fire ?location)+ )+ :effect (and+ (no_fire ?location)+ (not (on_fire ?location))+ )+ )
评分要点
- 不能写
(not (on_fire ?location))作前提,所以要定义一个肯定形式的no_fire,给pick_up和add_to_plate当前提。只改了一个动作算小错。 - 最容易漏的是问题文件:每个没着火的位置都要写一条
(no_fire ...)。漏掉的话,规划器在这些位置上什么也拿不了,整个问题无解。 extinguish的效果要同时加no_fire、删on_fire。- 灭火器用完还在手上,计划里需要先
place放下它,手空了才能去拿盘子。这一点不用写进动作,规划器会自己找到。
验证用的测试问题和规划器给出的计划
(define (problem a08) (:domain overcooked)
(:objects juice - food empty_plate plated_juice - plate fire_extinguisher - item
loc_a loc_b loc_e loc_f loc_c - location)
(:init (hand_empty) (item_at empty_plate loc_a) (on_fire loc_a)
(item_at fire_extinguisher loc_e) (extinguisher fire_extinguisher)
(item_at juice loc_b) (counter_empty loc_f) (delivery_location loc_c)
(no_fire loc_b) (no_fire loc_e) (no_fire loc_f) (no_fire loc_c)
(plating empty_plate juice plated_juice))
(:goal (delivered plated_juice)))
1. pick_up fire_extinguisher loc_e 2. extinguish fire_extinguisher loc_a 3. place fire_extinguisher loc_f 4. pick_up empty_plate loc_a 5. add_to_plate empty_plate juice loc_b plated_juice 6. deliver plated_juice loc_c
A09.直接把食物放进台面上的盘子
模板的 add_to_plate 要求厨师拿着盘子,食物放在台面上。现在要加一个反过来的动作 add_to_plate_on_counter:厨师拿着食物,盘子放在台面上,直接把食物放进盘子里。
查看答案
+(:action add_to_plate_on_counter+ :parameters (?food - food ?plate - plate ?location - location ?result - plate)+ :precondition (and+ (holding ?food)+ (item_at ?plate ?location)+ (plating ?plate ?food ?result)+ )+ :effect (and+ (item_at ?result ?location)+ (hand_empty)+ (not (item_at ?plate ?location))+ (not (holding ?food))+ )+ )
评分要点
- 结果盘子还留在台面上:效果是加
(item_at ?result ?location),删(item_at ?plate ?location)。写成厨师拿着结果盘子,就和规则不符,算小错。 - 厨师手上的食物没了,所以要加
hand_empty、删holding。 - 台面一直有盘子占着,所以如果域里有
counter_empty,这个动作不能改它。 plating事实的用法和原来的add_to_plate完全一样,不需要新谓词。
验证用的测试问题和规划器给出的计划
(define (problem a09) (:domain overcooked)
(:objects juice - food empty_plate plated_juice - plate loc_a loc_c - location)
(:init (holding juice) (item_at empty_plate loc_a) (delivery_location loc_c)
(plating empty_plate juice plated_juice))
(:goal (delivered plated_juice)))
1. add_to_plate_on_counter juice empty_plate loc_a plated_juice 2. pick_up plated_juice loc_a 3. deliver plated_juice loc_c
A10.用碗把面糊倒进烤箱
在 A01 的基础上,厨房里有烤箱和碗。碗可以拿在手上,碗里可以装面糊。厨师拿着装了面糊的碗,把面糊倒进空烤箱,手里剩一个空碗。然后烤箱把面糊烤成蛋糕。
请写 pour_into_oven 和 bake 两个动作,以及需要的类型和谓词。把蛋糕盛到盘子里的 oven_to_plate 已经给出,见答案最后一个动作。
查看答案
; 在 :types 里加+ bowl - item ; 在 :predicates 里加+ (counter_empty ?location - location)+ (filled ?empty - bowl ?food - food ?full - bowl)+ (oven ?location - location)+ (oven_empty ?location - location)+ (in_oven ?food - food ?location - location)+ (bakes ?food - food ?baked - food) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location) ) :effect (and (holding ?item)+ (counter_empty ?location); new: the counter is free again (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and (holding ?result)+ (counter_empty ?location); new: the food has left the counter (not (holding ?plate)) (not (item_at ?food ?location)) ) ) + (:action pour_into_oven+ :parameters (?full - bowl ?empty - bowl ?food - food ?location - location)+ :precondition (and+ (holding ?full)+ (filled ?empty ?food ?full)+ (oven ?location)+ (oven_empty ?location)+ )+ :effect (and+ (holding ?empty)+ (in_oven ?food ?location)+ (not (holding ?full))+ (not (oven_empty ?location))+ )+ ) + (:action bake+ :parameters (?food - food ?baked - food ?location - location)+ :precondition (and+ (in_oven ?food ?location)+ (bakes ?food ?baked)+ )+ :effect (and+ (in_oven ?baked ?location)+ (not (in_oven ?food ?location))+ )+ ) + (:action oven_to_plate+ :parameters (?plate - plate ?food - food ?location - location ?result - plate)+ :precondition (and+ (holding ?plate)+ (in_oven ?food ?location)+ (plating ?plate ?food ?result)+ )+ :effect (and+ (holding ?result)+ (oven_empty ?location)+ (not (holding ?plate))+ (not (in_oven ?food ?location))+ )+ )
评分要点
- 碗和盘子一样,用“状态即对象”来表示:
empty_bowl和bowl_of_batter是两个对象,(filled empty_bowl batter bowl_of_batter)说明它们的关系。所以bowl要声明成item的子类型,否则pick_up和place不能用在碗上。 pour_into_oven之后厨师手里是空碗,不是hand_empty。- 烤箱要有
oven_empty这样的肯定谓词,倒进去时删掉,盛出来时加回去。 bake是否要求厨师空手,取决于你对规则的理解。题目没说,所以不写也对,但要能口头说明理由。
验证用的测试问题和规划器给出的计划
(define (problem a10) (:domain overcooked)
(:objects batter cake - food empty_plate plated_cake - plate empty_bowl bowl_of_batter - bowl
loc_oven loc_a loc_b loc_c - location)
(:init (holding bowl_of_batter) (filled empty_bowl batter bowl_of_batter)
(oven loc_oven) (oven_empty loc_oven) (bakes batter cake)
(item_at empty_plate loc_a) (counter_empty loc_b) (delivery_location loc_c)
(plating empty_plate cake plated_cake))
(:goal (delivered plated_cake)))
1. pour_into_oven bowl_of_batter empty_bowl batter loc_oven 2. bake batter cake loc_oven 3. place empty_bowl loc_b 4. pick_up empty_plate loc_a 5. oven_to_plate empty_plate cake loc_oven plated_cake 6. deliver plated_cake loc_c
A11.锅里的菜烧焦了
在 A01 的基础上,厨房里有一口锅。生肉饼放进锅里煎,煎好后要及时取出来;放久了会烧焦,烧焦的东西不能装盘,只能把锅清空。put_in_pot 和 cook 已经给出(见答案前两个动作)。
- 请写
take_from_pot(把煎好的肉饼拿到手上)和clear_pot(把烧焦的东西清掉)。 - “烧焦”这个事件在模型里怎么表示?规划器会不会自己选择让它烧焦?
查看答案
; 在 :predicates 里加+ (counter_empty ?location - location)+ (pot_empty ?location - location)+ (in_pot ?food - food ?location - location)+ (cooks ?raw - food ?cooked - food)+ (cooked ?food - food)+ (burnt ?food - food) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location) ) :effect (and (holding ?item)+ (counter_empty ?location); new: the counter is free again (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and (holding ?result)+ (counter_empty ?location); new: the food has left the counter (not (holding ?plate)) (not (item_at ?food ?location)) ) ) + (:action put_in_pot+ :parameters (?food - food ?location - location)+ :precondition (and+ (holding ?food)+ (pot_empty ?location)+ )+ :effect (and+ (in_pot ?food ?location)+ (hand_empty)+ (not (holding ?food))+ (not (pot_empty ?location))+ )+ ) + (:action cook+ :parameters (?raw - food ?cooked - food ?location - location)+ :precondition (and+ (in_pot ?raw ?location)+ (cooks ?raw ?cooked)+ )+ :effect (and+ (in_pot ?cooked ?location)+ (not (in_pot ?raw ?location))+ )+ ) + (:action take_from_pot+ :parameters (?food - food ?location - location)+ :precondition (and+ (hand_empty)+ (in_pot ?food ?location)+ (cooked ?food)+ )+ :effect (and+ (holding ?food)+ (pot_empty ?location)+ (not (hand_empty))+ (not (in_pot ?food ?location))+ )+ ) + (:action clear_pot+ :parameters (?food - food ?location - location)+ :precondition (and+ (hand_empty)+ (in_pot ?food ?location)+ (burnt ?food)+ )+ :effect (and+ (pot_empty ?location)+ (not (in_pot ?food ?location))+ )+ )
评分要点
- 经典 PDDL 没有时间,“放久了会烧焦”不是一个动作,而是环境自己发生的事。它在模型里只出现在初始状态:重新规划时如果锅里已经是烧焦的东西,就写
(in_pot burnt_patty loc_pot)和(burnt burnt_patty)。规划器永远不会主动选择烧焦,因为没有哪个动作的效果是burnt。这一点口头说清楚就能拿分;写了一个burn动作不算错,但要能解释规划器不会用它。 take_from_pot的前提要有(cooked ?food),否则生的、烧焦的都能拿出来装盘。不能写(not (burnt ?food)),要用肯定形式的谓词。clear_pot的前提要有(burnt ?food),不然规划器会把煎好的肉饼当垃圾清掉。效果要把pot_empty加回来,否则锅永远不能再用。- 两个动作都要求
hand_empty。take_from_pot之后手里是肉饼,要删hand_empty;clear_pot之后手还是空的,不改手的状态。 - 真实控制器里的做法:发现锅里的东西快烧焦了就触发重新规划,给“先取出来”的计划加分。这是执行层的事,不写进域。
验证用的测试问题和规划器给出的计划
(define (problem a11) (:domain overcooked)
(:objects raw_patty cooked_patty burnt_patty - food empty_plate plated_burger - plate
loc_a loc_pot loc_b loc_c loc_d - location)
(:init (hand_empty) (item_at raw_patty loc_a) (item_at empty_plate loc_b) (counter_empty loc_c)
(in_pot burnt_patty loc_pot) (burnt burnt_patty)
(cooks raw_patty cooked_patty) (cooked cooked_patty)
(delivery_location loc_d) (plating empty_plate cooked_patty plated_burger))
(:goal (delivered plated_burger)))
1. clear_pot burnt_patty loc_pot 2. pick_up raw_patty loc_a 3. put_in_pot raw_patty loc_pot 4. cook raw_patty cooked_patty loc_pot 5. take_from_pot cooked_patty loc_pot 6. place cooked_patty loc_a 7. pick_up empty_plate loc_b 8. add_to_plate empty_plate cooked_patty loc_a plated_burger 9. deliver plated_burger loc_d
A12.必须按顺序上菜
在 A07 的基础上,新规则是:订单必须按它们到达的顺序交付,前一张没送出去,后一张做好了也不能送。请改写 deliver,写出需要的谓词,并说明问题文件里多了什么。
追问:如果订单是一张一张揭开的,前一张送出去下一张才出现,这个模型还能用吗?
查看答案
; 在 :types 里加+ order - object ; 在 :predicates 里加+ (wants ?order - order ?plate - plate)+ (served ?order - order)+ (order_turn ?order - order)+ (order_next ?order - order ?next - order) (:action deliver- :parameters (?plate - plate ?location - location)+ :parameters (?plate - plate ?location - location ?order - order ?next - order) :precondition (and (holding ?plate) (delivery_location ?location)+ (wants ?order ?plate)+ (order_turn ?order)+ (order_next ?order ?next) ) :effect (and- (delivered ?plate)+ (served ?order)+ (order_turn ?next) (hand_empty)+ (not (order_turn ?order)) (not (holding ?plate)) ) )
评分要点
- 关键是用两个谓词表示队列:
(order_turn ?order)说“现在轮到这张”,(order_next ?order ?next)说“它后面是哪张”。deliver的前提要有order_turn,效果要把order_turn从这张挪到下一张。只加前提不挪order_turn,第二张永远送不出去,是大错。 - 队列的最后一张需要一个哨兵对象
order_end,它没有wants,所以永远不会被送。漏掉哨兵,最后一张订单的order_next没地方写,规划器找不到计划。 - 用了
order_turn以后,A07 的pending就不需要了:一张订单送过之后order_turn已经不在它身上,不可能送第二次。两个都写不算错,但属于多余的谓词。 - 问题文件里要写
(order_turn order_1),以及一条链(order_next order_1 order_2)、(order_next order_2 order_end)。 - 追问的答案:模型本身能用,因为
order_next链描述的就是揭开的顺序。区别在于问题文件:还没揭开的订单要不要写进去?写进去,规划器就能提前准备食材,这是我们控制器里planned_orders的做法;不写,就只能等揭开再重新规划。
验证用的测试问题和规划器给出的计划
(define (problem a12) (:domain overcooked)
(:objects juice salad - food empty_plate plated_juice plated_salad - plate
loc_a loc_b loc_c loc_d loc_e - location order_1 order_2 order_end - order)
(:init (hand_empty) (item_at juice loc_a) (item_at salad loc_b)
(item_at empty_plate loc_c) (item_at empty_plate loc_d) (delivery_location loc_e)
(plating empty_plate juice plated_juice) (plating empty_plate salad plated_salad)
(wants order_1 plated_salad) (wants order_2 plated_juice)
(order_turn order_1) (order_next order_1 order_2) (order_next order_2 order_end))
(:goal (and (served order_1) (served order_2))))
1. pick_up empty_plate loc_c 2. add_to_plate empty_plate salad loc_b plated_salad 3. deliver plated_salad loc_e order_1 order_2 4. pick_up empty_plate loc_d 5. add_to_plate empty_plate juice loc_a plated_juice 6. deliver plated_juice loc_e order_2 order_end
A13.奖励不同,做不完时放弃哪张
在 A07 的基础上:每张订单的奖励不一样,而厨房里只有一个盘子,两张订单只能做一张。目标还是“每张订单都处理完”,但允许放弃一张。请用 action costs 写一个 skip_order 动作,改写目标和 deliver,让规划器放弃奖励低的那张。
查看答案
; 在 :requirements 里加 :action-costs ; 在 :types 里加+ order - object ; 在 :predicates 里加+ (wants ?order - order ?plate - plate)+ (pending ?order - order)+ (served ?order - order)+ (done ?order - order) ; 新增 :functions 段+ (:functions+ (total-cost)+ (reward ?order - order)+ ) (:action deliver- :parameters (?plate - plate ?location - location)+ :parameters (?plate - plate ?location - location ?order - order) :precondition (and (holding ?plate) (delivery_location ?location)+ (wants ?order ?plate)+ (pending ?order) ) :effect (and- (delivered ?plate)+ (served ?order)+ (done ?order) (hand_empty)+ (not (pending ?order)) (not (holding ?plate)) ) ) + (:action skip_order+ :parameters (?order - order)+ :precondition (and+ (pending ?order)+ )+ :effect (and+ (done ?order)+ (not (pending ?order))+ (increase (total-cost) (reward ?order))+ )+ )
评分要点
- 思路是把“放弃”也做成一个动作,代价等于这张订单的奖励,目标改成“每张订单要么送出、要么放弃”。为了不用
or,定义一个(done ?order)谓词,deliver和skip_order的效果都加上它,目标写(and (done order_1) (done order_2))。 - 需要
:action-costs、(:functions (total-cost) (reward ?order - order))、问题文件里的(= (total-cost) 0)、每张订单的(= (reward ...) n),以及(:metric minimize (total-cost))。漏掉 metric,规划器不会去比较代价,可能放弃错的那张。 skip_order的前提是pending,效果要删掉pending,否则同一张单可以反复放弃。- 要能说出:课程的 Fast Downward 默认用 lama-first,它是满意规划,不保证代价最小;这道题的验证用的是最优配置
seq-opt-lmcut。真实控制器里我们没走这条路,而是在生成问题之前用most_valuable先选好订单,PDDL 里只写选中的那几张。
验证用的测试问题和规划器给出的计划
(define (problem a13) (:domain overcooked)
(:objects juice salad - food empty_plate plated_juice plated_salad - plate
loc_a loc_b loc_c loc_d - location order_1 order_2 - order)
(:init (hand_empty) (item_at juice loc_a) (item_at salad loc_b)
(item_at empty_plate loc_c) (delivery_location loc_d)
(plating empty_plate juice plated_juice) (plating empty_plate salad plated_salad)
(wants order_1 plated_juice) (wants order_2 plated_salad)
(pending order_1) (pending order_2)
(= (total-cost) 0) (= (reward order_1) 10) (= (reward order_2) 30))
(:goal (and (done order_1) (done order_2)))
(:metric minimize (total-cost)))
1. pick_up empty_plate loc_c 2. skip_order order_1 3. add_to_plate empty_plate salad loc_b plated_salad 4. deliver plated_salad loc_d order_2
B 类:写问题实例
B01.模板域:最简单的实例
用模板域 D0,为下面这个厨房写一个完整的问题文件,包括对象、初始状态和目标。订单是一盘椰子汁。
y=0 | 椰子汁 | 空盘 | 出菜口 |
x=0 x=1 x=2
厨师站在 (1,1),手是空的
查看答案
(define (problem b01)
(:domain overcooked)
(:objects
coconut_juice - food
empty_plate plated_coconut_juice - plate
loc_0_0 loc_1_0 loc_2_0 - location
)
(:init
(hand_empty)
(item_at coconut_juice loc_0_0)
(item_at empty_plate loc_1_0)
(delivery_location loc_2_0)
(plating empty_plate coconut_juice plated_coconut_juice)
)
(:goal (delivered plated_coconut_juice))
)
评分要点
- 厨师站在哪里不写进初始状态。模板域不建模走路,也没有表示位置的谓词。写了就是引用无关谓词。
plating事实不能漏,否则规划器不知道空盘加椰子汁会变成什么,问题无解。empty_plate和plated_coconut_juice都要在对象表里声明,虽然一开始只有空盘存在。- 初始状态不要写
delivered,目标也不要写成holding。
规划器给出的计划
1. pick_up empty_plate loc_1_0 2. add_to_plate empty_plate coconut_juice loc_0_0 plated_coconut_juice 3. deliver plated_coconut_juice loc_2_0
B02.模板域:两张不同的订单
还是用 D0。现在有两张订单:一盘椰子汁、一盘水果沙拉。台面上有两个空盘。请写完整的问题文件。
追问:如果两张订单都要椰子汁,这个域还能写对吗?
y=0 | 椰子汁 | 水果沙拉 | 空盘 | 空盘 | 出菜口 |
x=0 x=1 x=2 x=3 x=4
厨师手是空的
查看答案
(define (problem b02)
(:domain overcooked)
(:objects
coconut_juice fruit_salad - food
empty_plate plated_coconut_juice plated_fruit_salad - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 loc_4_0 - location
)
(:init
(hand_empty)
(item_at coconut_juice loc_0_0)
(item_at fruit_salad loc_1_0)
(item_at empty_plate loc_2_0)
(item_at empty_plate loc_3_0)
(delivery_location loc_4_0)
(plating empty_plate coconut_juice plated_coconut_juice)
(plating empty_plate fruit_salad plated_fruit_salad)
)
(:goal (and
(delivered plated_coconut_juice)
(delivered plated_fruit_salad)
))
)
评分要点
- 两个空盘都写成
empty_plate,只是位置不同:(item_at empty_plate loc_2_0)和(item_at empty_plate loc_3_0)。不需要plate_1、plate_2两个对象,写成两个也能拿分,但要配两套plating事实。 - 目标要用
and把两张订单都包进去。只写一个属于“目标不完整”的大错。 - 每种菜都要有一条
plating。 - 追问的答案:不能。
(delivered plated_coconut_juice)在送出第一杯后就已经成立,规划器只会送一杯。要解决这个问题,就要像 A07 那样把订单变成对象。
规划器给出的计划
1. pick_up empty_plate loc_2_0 2. add_to_plate empty_plate coconut_juice loc_0_0 plated_coconut_juice 3. deliver plated_coconut_juice loc_4_0 4. pick_up empty_plate loc_3_0 5. add_to_plate empty_plate fruit_salad loc_1_0 plated_fruit_salad 6. deliver plated_fruit_salad loc_4_0
B03.教学域:一个厨师,从储藏柜做到出菜
用教学域 D1,为下面的厨房写问题文件。订单是一盘切好的生菜。
y=0 | 生菜储藏柜 | 砧板(空) | 空盘 | 出菜口 |
x=0 x=1 x=2 x=3
厨师 chef_1 站在 (1,1),手是空的,四个位置都走得到
查看答案
(define (problem b03)
(:domain kitchen)
(:objects
chef_1 - chef
lettuce chopped_lettuce - food
empty_plate plated_chopped_lettuce - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 - order
)
(:init
(hand_empty chef_1)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(storage loc_0_0 lettuce)
(cutboard loc_1_0)
(counter_empty loc_1_0)
(item_at empty_plate loc_2_0)
(delivery loc_3_0)
(chops lettuce chopped_lettuce)
(plating empty_plate chopped_lettuce plated_chopped_lettuce)
(wants order_1 plated_chopped_lettuce)
(pending order_1)
)
(:goal (served order_1))
)
评分要点
- 砧板也是台面,所以要同时写
(cutboard loc_1_0)和(counter_empty loc_1_0)。漏掉counter_empty,生菜就放不上砧板,问题无解。 - 储藏柜、出菜口不能写
counter_empty。放着盘子的loc_2_0也不能写,因为它不是空的。 - 每张订单要写
wants和pending两条事实。漏pending,deliver的前提永远不成立。 can_reach要覆盖厨师会用到的每一个位置,包括出菜口。- 目标是
(served order_1),不是delivered,因为 D1 里没有delivered这个谓词。
规划器给出的计划
1. take_from_storage chef_1 lettuce loc_0_0 2. place chef_1 lettuce loc_1_0 3. chop chef_1 lettuce chopped_lettuce loc_1_0 4. pick_up chef_1 empty_plate loc_2_0 5. add_to_plate chef_1 empty_plate chopped_lettuce loc_1_0 plated_chopped_lettuce 6. deliver chef_1 plated_chopped_lettuce loc_3_0 order_1
B04.教学域:两个厨师,两张订单
用 D1 写问题文件。订单 1 要一盘切好的番茄,订单 2 要一盘切好的生菜。
y=0 | 番茄储藏柜 | 砧板(空) | 砧板(空) | 生菜储藏柜 |
x=0 x=1 x=2 x=3
y=1 | chef_1 | | | chef_2 |
y=2 | 空盘 | 出菜口 | (地板) | 空盘 |
两个厨师手都是空的,七个位置都走得到
查看答案
(define (problem b04)
(:domain kitchen)
(:objects
chef_1 chef_2 - chef
tomato chopped_tomato lettuce chopped_lettuce - food
empty_plate plated_chopped_tomato plated_chopped_lettuce - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 loc_0_2 loc_1_2 loc_3_2 - location
order_1 order_2 - order
)
(:init
(hand_empty chef_1)
(hand_empty chef_2)
(can_reach chef_1 loc_0_0) (can_reach chef_1 loc_1_0) (can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0) (can_reach chef_1 loc_0_2) (can_reach chef_1 loc_1_2)
(can_reach chef_1 loc_3_2)
(can_reach chef_2 loc_0_0) (can_reach chef_2 loc_1_0) (can_reach chef_2 loc_2_0)
(can_reach chef_2 loc_3_0) (can_reach chef_2 loc_0_2) (can_reach chef_2 loc_1_2)
(can_reach chef_2 loc_3_2)
(storage loc_0_0 tomato)
(storage loc_3_0 lettuce)
(cutboard loc_1_0) (counter_empty loc_1_0)
(cutboard loc_2_0) (counter_empty loc_2_0)
(item_at empty_plate loc_0_2)
(item_at empty_plate loc_3_2)
(delivery loc_1_2)
(chops tomato chopped_tomato)
(chops lettuce chopped_lettuce)
(plating empty_plate chopped_tomato plated_chopped_tomato)
(plating empty_plate chopped_lettuce plated_chopped_lettuce)
(wants order_1 plated_chopped_tomato)
(wants order_2 plated_chopped_lettuce)
(pending order_1)
(pending order_2)
)
(:goal (and (served order_1) (served order_2)))
)
评分要点
- 每个厨师都要有自己的
hand_empty,每个厨师能到的每个位置都要有自己的can_reach。这一题一共要写 14 条can_reach,在纸上可以写出前几条后注明“其余同理”,但要先问考官是否接受。 - 两块砧板各自要有
cutboard和counter_empty。 - 两张订单各自要有
wants和pending,目标用and连起来。 - 对象表里不需要声明“厨师 1 的盘子”这种东西,盘子对象只表示状态。
规划器给出的计划
1. pick_up chef_1 empty_plate loc_0_2 2. take_from_storage chef_2 lettuce loc_3_0 3. place chef_2 lettuce loc_2_0 4. chop chef_2 lettuce chopped_lettuce loc_2_0 5. take_from_storage chef_2 tomato loc_0_0 6. add_to_plate chef_1 empty_plate chopped_lettuce loc_2_0 plated_chopped_lettuce 7. deliver chef_1 plated_chopped_lettuce loc_1_2 order_2 8. pick_up chef_1 empty_plate loc_3_2 9. place chef_2 tomato loc_2_0 10. chop chef_2 tomato chopped_tomato loc_2_0 11. add_to_plate chef_1 empty_plate chopped_tomato loc_2_0 plated_chopped_tomato 12. deliver chef_1 plated_chopped_tomato loc_1_2 order_1
B05.教学域:中间有墙,要靠传菜台交接
用 D1 写问题文件。厨房被墙分成左右两半,中间的传菜台两边的厨师都能够到。订单是一盘切好的生菜。
y=0 | 生菜储藏柜 | 砧板(空) | 传菜台(空) | 空盘 | 出菜口 |
x=0 x=1 x=2 x=3 x=4
\________ chef_1 ________/
\______ chef_2 ______/
chef_1 只能到 x=0,1,2;chef_2 只能到 x=2,3,4;两人手都是空的
查看答案
(define (problem b05)
(:domain kitchen)
(:objects
chef_1 chef_2 - chef
lettuce chopped_lettuce - food
empty_plate plated_chopped_lettuce - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 loc_4_0 - location
order_1 - order
)
(:init
(hand_empty chef_1)
(hand_empty chef_2)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_2 loc_2_0)
(can_reach chef_2 loc_3_0)
(can_reach chef_2 loc_4_0)
(storage loc_0_0 lettuce)
(cutboard loc_1_0)
(counter_empty loc_1_0)
(counter_empty loc_2_0)
(item_at empty_plate loc_3_0)
(delivery loc_4_0)
(chops lettuce chopped_lettuce)
(plating empty_plate chopped_lettuce plated_chopped_lettuce)
(wants order_1 plated_chopped_lettuce)
(pending order_1)
)
(:goal (served order_1))
)
评分要点
- 这道题的关键全在
can_reach:chef_1 只写 x=0、1、2 三个位置,chef_2 只写 x=2、3、4 三个位置,传菜台loc_2_0两个人都要写。 - 传菜台要写
counter_empty,否则 chef_1 没法把切好的生菜放上去交接。 - 不需要新的“交接”动作:chef_1 用
place放下,chef_2 直接用add_to_plate从传菜台上取。计划里能看到这个过程。 - 如果给两个厨师都写了全部位置,计划仍然能找到,但没有体现墙,属于初始状态错误。
规划器给出的计划
1. pick_up chef_2 empty_plate loc_3_0 2. take_from_storage chef_1 lettuce loc_0_0 3. place chef_1 lettuce loc_1_0 4. chop chef_1 lettuce chopped_lettuce loc_1_0 5. pick_up chef_1 chopped_lettuce loc_1_0 6. place chef_1 chopped_lettuce loc_2_0 7. add_to_plate chef_2 empty_plate chopped_lettuce loc_2_0 plated_chopped_lettuce 8. deliver chef_2 plated_chopped_lettuce loc_4_0 order_1
B06.教学域:厨师手里拿着没用的东西
用 D1 写问题文件。开局时厨师手里拿着一条鱼,订单不需要鱼,厨房里也没有空台面可以放。订单是一盘椰子汁。
y=0 | 椰子汁 | 空盘 | 垃圾桶 | 出菜口 |
x=0 x=1 x=2 x=3
厨师手里拿着一条鱼,订单不需要鱼;厨房里没有空台面
查看答案
(define (problem b06)
(:domain kitchen)
(:objects
chef_1 - chef
fish coconut_juice - food
empty_plate plated_coconut_juice - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 - order
)
(:init
(holding chef_1 fish)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(item_at coconut_juice loc_0_0)
(item_at empty_plate loc_1_0)
(bin loc_2_0)
(delivery loc_3_0)
(plating empty_plate coconut_juice plated_coconut_juice)
(wants order_1 plated_coconut_juice)
(pending order_1)
)
(:goal (served order_1))
)
评分要点
- 厨师一开始拿着鱼,所以初始状态写
(holding chef_1 fish),不能再写(hand_empty chef_1)。两条同时写,等于说厨师一边拿着鱼、手又是空的,规划器会直接去拿盘子,计划和实际不符。 fish虽然不在订单里,也要在对象表里声明,因为初始状态用到了它。- 垃圾桶写
(bin loc_2_0),不写counter_empty。 - 没有空台面,所以整个初始状态里一条
counter_empty都没有。
规划器给出的计划
1. bin_food chef_1 fish loc_2_0 2. pick_up chef_1 empty_plate loc_1_0 3. add_to_plate chef_1 empty_plate coconut_juice loc_0_0 plated_coconut_juice 4. deliver chef_1 plated_coconut_juice loc_3_0 order_1
B07.教学域:做了一半的厨房
用 D1 写问题文件。这是重新规划时的一个中间状态:砧板上已经放着一颗没切的生菜,另一个台面上的盘子里已经装了番茄。订单要番茄生菜沙拉。
y=0 | 生菜储藏柜 | 砧板(上面有一颗生菜) | 盘子(已装番茄) | 出菜口 |
x=0 x=1 x=2 x=3
厨师手是空的;订单要“番茄生菜沙拉”,做法是在装了番茄的盘子里加切好的生菜
查看答案
(define (problem b07)
(:domain kitchen)
(:objects
chef_1 - chef
lettuce chopped_lettuce - food
plated_tomato plated_salad - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 - order
)
(:init
(hand_empty chef_1)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(storage loc_0_0 lettuce)
(cutboard loc_1_0)
(item_at lettuce loc_1_0)
(item_at plated_tomato loc_2_0)
(delivery loc_3_0)
(chops lettuce chopped_lettuce)
(plating plated_tomato chopped_lettuce plated_salad)
(wants order_1 plated_salad)
(pending order_1)
)
(:goal (served order_1))
)
评分要点
- 装了番茄的盘子是一个单独的对象
plated_tomato,初始状态写(item_at plated_tomato loc_2_0)。不能写成empty_plate加一条“盘子里有番茄”。 - 砧板上已经有生菜,所以写
(item_at lettuce loc_1_0),而且不能写(counter_empty loc_1_0)。 - 需要的
plating只有一条:plated_tomato加切好的生菜等于plated_salad。再写“空盘加番茄”这条路线不算错,但属于用不到的事实。 - 储藏柜照样要写,因为它在厨房里。计划不一定用到它。
规划器给出的计划
1. chop chef_1 lettuce chopped_lettuce loc_1_0 2. pick_up chef_1 plated_tomato loc_2_0 3. add_to_plate chef_1 plated_tomato chopped_lettuce loc_1_0 plated_salad 4. deliver chef_1 plated_salad loc_3_0 order_1
B08.教学域:两张一样的订单
用 D1 写问题文件。两张订单都要一盘椰子汁。厨房里只有一个储藏柜、一块砧板,台面上有两个空盘。
y=0 | 椰子储藏柜 | 砧板(空) | 空盘 | 空盘 | 出菜口 |
x=0 x=1 x=2 x=3 x=4
厨师手是空的;两张订单,都要一盘椰子汁(椰子切了就是椰子汁)
查看答案
(define (problem b08)
(:domain kitchen)
(:objects
chef_1 - chef
coconut coconut_juice - food
empty_plate plated_coconut_juice - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 loc_4_0 - location
order_1 order_2 - order
)
(:init
(hand_empty chef_1)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(can_reach chef_1 loc_4_0)
(storage loc_0_0 coconut)
(cutboard loc_1_0)
(counter_empty loc_1_0)
(item_at empty_plate loc_2_0)
(item_at empty_plate loc_3_0)
(delivery loc_4_0)
(chops coconut coconut_juice)
(plating empty_plate coconut_juice plated_coconut_juice)
(wants order_1 plated_coconut_juice)
(wants order_2 plated_coconut_juice)
(pending order_1)
(pending order_2)
)
(:goal (and (served order_1) (served order_2)))
)
评分要点
- 两张订单是两个对象
order_1和order_2,都写(wants ... plated_coconut_juice),都写pending。目标用and连起来。 - 两个空盘还是同一个对象
empty_plate,在两个位置上各写一条item_at。 - 椰子在储藏柜里,储藏柜无限,所以不需要声明“两个椰子”。
- 计划会切两次椰子、送两次。同一块砧板能用两次,是因为
pick_up和add_to_plate都会把counter_empty加回来。
规划器给出的计划
1. take_from_storage chef_1 coconut loc_0_0 2. place chef_1 coconut loc_1_0 3. chop chef_1 coconut coconut_juice loc_1_0 4. pick_up chef_1 empty_plate loc_3_0 5. add_to_plate chef_1 empty_plate coconut_juice loc_1_0 plated_coconut_juice 6. deliver chef_1 plated_coconut_juice loc_4_0 order_1 7. take_from_storage chef_1 coconut loc_0_0 8. place chef_1 coconut loc_1_0 9. chop chef_1 coconut coconut_juice loc_1_0 10. pick_up chef_1 empty_plate loc_2_0 11. add_to_plate chef_1 empty_plate coconut_juice loc_1_0 plated_coconut_juice 12. deliver chef_1 plated_coconut_juice loc_4_0 order_2
B09.教学域:两种配料的沙拉
用 D1 写问题文件。订单要沙拉:一个盘子里装切好的番茄和切好的生菜。两种配料先放哪个都可以。两种配料已经切好放在台面上。
y=0 | 切好的番茄 | 切好的生菜 | 空盘 | 出菜口 |
x=0 x=1 x=2 x=3
厨师手是空的;订单要沙拉 = 盘子 + 切好的番茄 + 切好的生菜,两样先放哪个都行
查看答案
(define (problem b09)
(:domain kitchen)
(:objects
chef_1 - chef
chopped_tomato chopped_lettuce - food
empty_plate plated_tomato plated_lettuce plated_salad - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 - order
)
(:init
(hand_empty chef_1)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(item_at chopped_tomato loc_0_0)
(item_at chopped_lettuce loc_1_0)
(item_at empty_plate loc_2_0)
(delivery loc_3_0)
(plating empty_plate chopped_tomato plated_tomato)
(plating empty_plate chopped_lettuce plated_lettuce)
(plating plated_tomato chopped_lettuce plated_salad)
(plating plated_lettuce chopped_tomato plated_salad)
(wants order_1 plated_salad)
(pending order_1)
)
(:goal (served order_1))
)
评分要点
- 关键是
plating事实要写成一条链:空盘加番茄得到plated_tomato,再加生菜得到plated_salad。 - “先放哪个都可以”要写成两条链,一共四条
plating,两条链最后得到同一个对象plated_salad。只写一条链不算错,因为计划仍然存在,但没有体现题目的规则,可能被追问。 - 中间状态
plated_tomato和plated_lettuce都要在对象表里声明。 - 订单只要
plated_salad。
规划器给出的计划
1. pick_up chef_1 empty_plate loc_2_0 2. add_to_plate chef_1 empty_plate chopped_tomato loc_0_0 plated_tomato 3. add_to_plate chef_1 plated_tomato chopped_lettuce loc_1_0 plated_salad 4. deliver chef_1 plated_salad loc_3_0 order_1
B10.找错题:改正一个有问题的实例
下面是某位同学为这个厨房写的问题文件,用的是 D1,订单是一盘切好的生菜。规划器找不到计划。请找出其中的错误,至少五处,并写出改正后的版本。
y=0 | 生菜储藏柜 | 砧板(空) | 空盘 | 出菜口 |
x=0 x=1 x=2 x=3
和 B03 是同一个厨房:一个厨师,四个位置都走得到
有问题的版本:
(define (problem b10)
(:domain kitchen)
(:objects
chef_1 - chef
lettuce - plate
chopped_lettuce - food
empty_plate plated_chopped_lettuce - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 - order
)
(:init
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(storage loc_0_0 lettuce)
(counter_empty loc_0_0)
(cutboard loc_1_0)
(counter_empty loc_1_0)
(item_at empty_plate loc_2_0)
(delivery loc_3_0)
(chops lettuce chopped_lettuce)
(plating empty_plate lettuce plated_chopped_lettuce)
(wants order_1 plated_chopped_lettuce)
)
(:goal (holding chef_1 plated_chopped_lettuce))
)
查看答案
(define (problem b10)
(:domain kitchen)
(:objects
chef_1 - chef
lettuce chopped_lettuce - food
empty_plate plated_chopped_lettuce - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 - order
)
(:init
(hand_empty chef_1)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(storage loc_0_0 lettuce)
(cutboard loc_1_0)
(counter_empty loc_1_0)
(item_at empty_plate loc_2_0)
(delivery loc_3_0)
(chops lettuce chopped_lettuce)
(plating empty_plate chopped_lettuce plated_chopped_lettuce)
(wants order_1 plated_chopped_lettuce)
(pending order_1)
)
(:goal (served order_1))
)
评分要点
这份文件一共有七处错误:
lettuce - plate:类型错了,生菜是food。- 缺少
(hand_empty chef_1):厨师什么都拿不起来。 (counter_empty loc_0_0):储藏柜不是台面,不该写。这一条不会让问题无解,但会让规划器把东西放到储藏柜上。- 缺少
(can_reach chef_1 loc_3_0):厨师走不到出菜口。 (plating empty_plate lettuce plated_chopped_lettuce):装盘的应该是切好的生菜chopped_lettuce。- 缺少
(pending order_1):deliver的前提永远不成立。 - 目标写成了
(holding chef_1 plated_chopped_lettuce):拿在手上不等于送达,目标应该是(served order_1)。
答题时按“对象表、初始状态、目标”三段逐一检查,比从头读到尾更不容易漏。
规划器给出的计划
1. take_from_storage chef_1 lettuce loc_0_0 2. place chef_1 lettuce loc_1_0 3. chop chef_1 lettuce chopped_lettuce loc_1_0 4. pick_up chef_1 empty_plate loc_2_0 5. add_to_plate chef_1 empty_plate chopped_lettuce loc_1_0 plated_chopped_lettuce 6. deliver chef_1 plated_chopped_lettuce loc_3_0 order_1
B11.做不出的单:目标该写什么
用 D1 写问题文件。来了两张订单:order_1 要一盘切好的生菜,order_2 要一盘切好的番茄。厨房里有生菜储藏柜,但哪里都没有番茄。
- 两张订单都写进目标会怎样?
- 写出一个规划器能解的问题文件,并说明 order_2 要不要出现在里面。
y=0 | 生菜储藏柜 | 砧板(空) | 空盘 | 出菜口 |
x=0 x=1 x=2 x=3
和 B03 是同一个厨房。两张订单:order_1 要一盘切好的生菜,order_2 要一盘切好的番茄。厨房里没有番茄。
两张单都写进目标的版本,规划器报告无解:
(define (problem b11)
(:domain kitchen)
(:objects
chef_1 - chef
lettuce chopped_lettuce tomato chopped_tomato - food
empty_plate plated_chopped_lettuce plated_chopped_tomato - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 order_2 - order
)
(:init
(hand_empty chef_1)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(storage loc_0_0 lettuce)
(cutboard loc_1_0)
(counter_empty loc_1_0)
(item_at empty_plate loc_2_0)
(delivery loc_3_0)
(chops lettuce chopped_lettuce)
(chops tomato chopped_tomato)
(plating empty_plate chopped_lettuce plated_chopped_lettuce)
(plating empty_plate chopped_tomato plated_chopped_tomato)
(wants order_1 plated_chopped_lettuce)
(pending order_1)
(wants order_2 plated_chopped_tomato)
(pending order_2)
)
(:goal (and (served order_1) (served order_2)))
)
查看答案
(define (problem b11)
(:domain kitchen)
(:objects
chef_1 - chef
lettuce chopped_lettuce tomato chopped_tomato - food
empty_plate plated_chopped_lettuce plated_chopped_tomato - plate
loc_0_0 loc_1_0 loc_2_0 loc_3_0 - location
order_1 order_2 - order
)
(:init
(hand_empty chef_1)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_1_0)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_3_0)
(storage loc_0_0 lettuce)
(cutboard loc_1_0)
(counter_empty loc_1_0)
(item_at empty_plate loc_2_0)
(delivery loc_3_0)
(chops lettuce chopped_lettuce)
(chops tomato chopped_tomato)
(plating empty_plate chopped_lettuce plated_chopped_lettuce)
(plating empty_plate chopped_tomato plated_chopped_tomato)
(wants order_1 plated_chopped_lettuce)
(pending order_1)
(wants order_2 plated_chopped_tomato)
(pending order_2)
)
(:goal (served order_1))
)
评分要点
- 两张都写进目标,问题无解,规划器什么计划都不给,连能做的 order_1 也送不出去。这是“目标不恰当”的大错:目标里只能放做得出来的订单。
- 做不出的单可以留在对象表和初始状态里(
wants、pending照写),只是不进目标。这样不算引用无关对象,因为它确实在厨房里;但完全不写它也对。 - 判断“做不做得出”不是 PDDL 的事,要在生成问题之前做:我们的控制器里
makeable_orders会顺着菜谱往下查食材,查不到的订单就不写进目标。 - 另一种写法是 A13 的
skip_order,让规划器自己放弃。两种都对,但 A13 需要 action costs,纸上写起来更长。
规划器给出的计划
1. take_from_storage chef_1 lettuce loc_0_0 2. place chef_1 lettuce loc_1_0 3. chop chef_1 lettuce chopped_lettuce loc_1_0 4. pick_up chef_1 empty_plate loc_2_0 5. add_to_plate chef_1 empty_plate chopped_lettuce loc_1_0 plated_chopped_lettuce 6. deliver chef_1 plated_chopped_lettuce loc_3_0 order_1
代码改动对比的是课程模板域 D0。绿色是新增的行,红色是删掉的行,没有颜色的是原样保留的行。
本套说明
这一套题全部围绕课程给的初始模板 controllers/pddl/submission_ori/:域文件 overcooked.pddl、问题生成器 problem_generator.py、翻译器 from_pddl.py。模板对每个学生都一样,所以它最可能是面试出题的共同起点。
题目分成四组
| 组 | 题号 | 考什么 |
|---|---|---|
| 第一组 | T01、T02、T03、T05 | 给一个关卡,写出模板会生成的问题文件,判断能不能解 |
| 第二组 | T06 到 T14 | 把模板扩展到 Level 1 的其他关卡:写出域的改动和生成器要新增的事实 |
| 第三组 | T15 到 T17 | 模板的翻译器怎样把计划变成模拟器动作 |
| 第四组 | T19、T20 | 模板跑得起来、结果却不对的两个厨房,以及改法 |
和前两套一样,评分按 rubric 的 Q&A 部分来:动作缺参数、前提、效果之一,或问题缺对象、初始状态、目标之一,是大错;少一个谓词或事实、类型写错之类,是小错。
答案是怎么验证的
- 第一组的问题文件,是直接调用模板的
build_problem生成的。 - 第二组的扩展写在
pddl/ext.py里。它继承了模板的生成器和翻译器,只新增了函数和动作分支。用它在模拟器里跑 Level 1 的全部 12 关,每一关都送达了订单。 - 第三组的答案是直接调用模板的
DefaultFromPDDL得到的。 - 第四组的修改只用 Fast Downward 验证过,因为要在模拟器里跑,还得改翻译器。
- 每道题末尾的“验证”写出了实际结果。题里用到的四个变体关卡在
set3/levels/里。
重新生成这份文档:
cd ../too-many-chefs-a-1-exsun-da21 PYTHONPATH=.:../mock-quiz/set3/pddl .venv/bin/python ../mock-quiz/set3/pddl/sim_all.py PYTHONPATH=.:../mock-quiz/set3/pddl .venv/bin/python ../mock-quiz/set3/pddl/build.py
题号不连续,是因为 T04 和 T18 与第二套的 A05、A07、B08 重复,去重时删掉了。
模板的三个关键写法
做题前先记住这三点,大部分题都从这里来:
- 每种食物生成两个对象。 一个是食物本身,一个是“装着它的盘子”,再加一条
plating事实。不管订单要不要它。 - 只描述看得懂的东西。 台面上有东西才写,空台面、砧板、储藏柜、水槽、垃圾桶、脏盘子都不写。
- 模型里没有厨师。
hand_empty没有参数,计划全部交给第一个厨师。同一道菜的多张订单只生成一个目标。
第一组:读懂模板会生成什么
这一组题给出一个关卡,请你按模板 problem_generator.py 的写法,手写出它生成的问题文件,并判断规划器能不能解。答案就是模板代码真正生成的文本。
T01.第一关会生成什么问题
关卡是 levels/0_i_can_cook/0_0_coconut_juice_finished_on_counter.yaml。图中 -j 是放着椰子汁的台面,-p 是放着空盘的台面,* 是出菜口,X 是垃圾桶,1 是厨师。
| | -j| -p| * | | | 1 | | | | | | | X |
请写出模板生成的完整问题文件,并写出规划器给出的计划。
查看答案
(define (problem kitchen)
(:domain overcooked)
(:objects
empty_plate - plate
catalog_food_coconut_juice - food
plated_catalog_food_coconut_juice - plate
loc_1_0 - location
loc_2_0 - location
loc_3_0 - location
)
(:init
(plating empty_plate catalog_food_coconut_juice plated_catalog_food_coconut_juice)
(hand_empty)
(item_at catalog_food_coconut_juice loc_1_0)
(item_at empty_plate loc_2_0)
(delivery_location loc_3_0)
)
(:goal
(and
(delivered plated_catalog_food_coconut_juice)
)
)
)
计划:
1. pick_up empty_plate loc_2_0 2. add_to_plate empty_plate catalog_food_coconut_juice loc_1_0 plated_catalog_food_coconut_juice 3. deliver plated_catalog_food_coconut_juice loc_3_0
评分要点
describe_food会给关卡里定义的每一种食物生成两个对象:一个食物对象,一个“装着它的盘子”对象,再加一条plating事实。这一关只定义了椰子汁,所以只有一组。describe_counters只写上面有东西的台面。这一关两个台面都有东西,所以loc_1_0和loc_2_0都在。- 垃圾桶没有对应的
describe_*函数,所以loc_3_2不出现。厨师站在哪里也不写,因为域里不建模走路。 - 目标来自
describe_orders,每张可见订单一条(delivered ...)。 - 名字的规则是
pddl_name:小写,把/换成_。catalog/food/coconut_juice变成catalog_food_coconut_juice。名字写错算小错,比如写成coconut_juice。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 50 分,用了 7 步。
T02.椰子还没切的厨房
关卡是 0_1_coconut_juice_ingredient_on_counter.yaml。-c 是放着整个椰子的台面,-/ 是放着砧板的台面。菜谱是:椰子放在砧板上切,变成椰子汁。
| | -c| -/| -p| * | | | 1 | | | | | | | | | X |
- 写出模板生成的问题文件。
- 规划器能找到计划吗?从问题文件里指出原因。
查看答案
(define (problem kitchen)
(:domain overcooked)
(:objects
empty_plate - plate
catalog_food_coconut - food
plated_catalog_food_coconut - plate
catalog_food_coconut_juice - food
plated_catalog_food_coconut_juice - plate
loc_1_0 - location
loc_3_0 - location
loc_4_0 - location
)
(:init
(plating empty_plate catalog_food_coconut plated_catalog_food_coconut)
(plating empty_plate catalog_food_coconut_juice plated_catalog_food_coconut_juice)
(hand_empty)
(item_at catalog_food_coconut loc_1_0)
(item_at empty_plate loc_3_0)
(delivery_location loc_4_0)
)
(:goal
(and
(delivered plated_catalog_food_coconut_juice)
)
)
)
规划器找不到计划。原因有两层:
- 域里没有加工动作。 模板只有拿起、装盘、上菜三个动作,没有任何动作能把椰子变成椰子汁。
- 问题里根本没有砧板。 砧板台面
loc_2_0不在对象表里。这是因为describe_counters用item_symbol去描述台面上的东西,而砧板是设备,item_symbol对它返回 None,这个台面就被跳过了。菜谱cook_recipes也没有被任何describe_*函数读取。
评分要点
- 两层原因都要答出来。只说“缺动作”,说明没有读懂问题文件,因为就算加了动作,问题里也没有砧板的位置和菜谱。
- 注意
describe_food仍然生成了plated_catalog_food_coconut(装着整个椰子的盘子)和对应的plating。这条事实在模型里允许“把整个椰子装盘”,但它和订单无关,所以不影响结果。
验证
在模拟器里实际跑一局的结果:结束原因 gave_up,送出 0 单,剩 1 单,得 0 分,用了 0 步。
T03.水果芭菲:缺的是哪两样
关卡是 1_0_fruit_parfait_salad_on_counter.yaml。-f 是放着水果沙拉的台面,Y 是酸奶储藏柜。组合菜谱是:水果沙拉加酸奶等于水果芭菲。
| | -f| Y | -p| * | | | 1 | | | | | | | | | X |
写出模板生成的问题文件,并说明为什么规划器找不到计划。
查看答案
(define (problem kitchen)
(:domain overcooked)
(:objects
empty_plate - plate
catalog_food_fruit_salad - food
plated_catalog_food_fruit_salad - plate
catalog_food_yoghurt - food
plated_catalog_food_yoghurt - plate
catalog_food_fruit_parfait - food
plated_catalog_food_fruit_parfait - plate
loc_1_0 - location
loc_3_0 - location
loc_4_0 - location
)
(:init
(plating empty_plate catalog_food_fruit_salad plated_catalog_food_fruit_salad)
(plating empty_plate catalog_food_yoghurt plated_catalog_food_yoghurt)
(plating empty_plate catalog_food_fruit_parfait plated_catalog_food_fruit_parfait)
(hand_empty)
(item_at catalog_food_fruit_salad loc_1_0)
(item_at empty_plate loc_3_0)
(delivery_location loc_4_0)
)
(:goal
(and
(delivered plated_catalog_food_fruit_parfait)
)
)
)
找不到计划,因为问题里缺了两样东西:
- 酸奶从哪里来。 储藏柜
loc_2_0不在问题里,模板没有描述储藏柜的函数。 - 沙拉加酸奶会变成什么。 模板只为“空盘加一种食物”写
plating,没有“装着沙拉的盘子加酸奶等于装着芭菲的盘子”这样的事实。combine_recipes没有被读取。
所以目标 (delivered plated_catalog_food_fruit_parfait) 里的芭菲盘子,在模型里根本做不出来。
评分要点
- 题目问的是问题文件里缺什么,所以答案要落到“缺哪种事实”,而不只是说“模板太简单”。
- 对象表里已经有
plated_catalog_food_fruit_parfait,因为describe_food会为每种食物都生成一个盘子对象。缺的是能“到达”它的plating事实。
验证
在模拟器里实际跑一局的结果:结束原因 gave_up,送出 0 单,剩 1 单,得 0 分,用了 0 步。
T05.唯一的盘子是脏的
这是第一关的变体:唯一的盘子 -d 是脏的,旁边有一个水槽 S。关卡文件在 set3/levels/v3_dirty_plate.yaml。
| | -j| -d| * | | | 1 | | S | | | | | X |
写出模板生成的问题文件。规划器能解吗?为什么?
查看答案
(define (problem kitchen)
(:domain overcooked)
(:objects
empty_plate - plate
catalog_food_coconut_juice - food
plated_catalog_food_coconut_juice - plate
loc_1_0 - location
loc_3_0 - location
)
(:init
(plating empty_plate catalog_food_coconut_juice plated_catalog_food_coconut_juice)
(hand_empty)
(item_at catalog_food_coconut_juice loc_1_0)
(delivery_location loc_3_0)
)
(:goal
(and
(delivered plated_catalog_food_coconut_juice)
)
)
)
不能解。脏盘子所在的台面 loc_2_0 整个消失了:item_symbol 对脏盘子返回 None,describe_counters 就跳过了这个台面。水槽也没有对应的 describe_* 函数。问题里没有任何盘子,目标的“装着椰子汁的盘子”就做不出来。
评分要点
- 很多人会以为问题里有
(item_at empty_plate loc_2_0)。其实没有,模板会把脏盘子当成“看不懂的东西”直接丢掉。 - 注意
empty_plate仍然出现在对象表里,因为describe_food总是先声明它。对象存在,不等于它在厨房的某个位置上。
验证
在模拟器里实际跑一局的结果:结束原因 gave_up,送出 0 单,剩 1 单,得 0 分,用了 0 步。
第二组:把模板扩展到更多关卡
T06.让模板会用砧板
回到 T02 的厨房。请扩展模板,让它能完成这一关:
- 在域里加上需要的谓词和动作。砧板这类设备统称“工作台”,一次只能放一样东西。
- 写出生成器要为这一关新增的对象和事实。
查看答案
; 在 :predicates 里加+ (station_empty ?location - location)+ (in_station ?food - food ?location - location)+ (cooks ?raw - food ?location - location ?result - food) + (:action put_in_station+ :parameters (?food - food ?location - location)+ :precondition (and+ (holding ?food)+ (station_empty ?location)+ )+ :effect (and+ (in_station ?food ?location)+ (hand_empty)+ (not (holding ?food))+ (not (station_empty ?location))+ )+ ) + (:action cook+ :parameters (?raw - food ?result - food ?location - location)+ :precondition (and+ (hand_empty)+ (in_station ?raw ?location)+ (cooks ?raw ?location ?result)+ )+ :effect (and+ (in_station ?result ?location)+ (not (in_station ?raw ?location))+ )+ ) + (:action station_to_plate+ :parameters (?plate - plate ?food - food ?location - location ?result - plate)+ :precondition (and+ (holding ?plate)+ (in_station ?food ?location)+ (plating ?plate ?food ?result)+ )+ :effect (and+ (holding ?result)+ (station_empty ?location)+ (not (holding ?plate))+ (not (in_station ?food ?location))+ )+ )
生成器为这一关新增的内容:
loc_2_0 - location (station_empty loc_2_0) (cooks catalog_food_coconut loc_2_0 catalog_food_coconut_juice)
规划器给出的计划:
1. pick_up catalog_food_coconut loc_1_0 2. put_in_station catalog_food_coconut loc_2_0 3. cook catalog_food_coconut catalog_food_coconut_juice loc_2_0 4. pick_up empty_plate loc_3_0 5. station_to_plate empty_plate catalog_food_coconut_juice loc_2_0 plated_catalog_food_coconut_juice 6. deliver plated_catalog_food_coconut_juice loc_4_0
评分要点
- 三个动作缺一不可:放进工作台、加工、从工作台装盘。少了“装盘”,切好的椰子汁就一直留在砧板上。
cooks带着位置参数,表示“这个位置的工作台能把这种食物做成那种”。这样同一张菜谱表里,砧板的菜谱不会用到锅上。- 工作台用
station_empty表示空,不能用counter_empty,因为砧板下面的台面不是空台面。T14 会专门考这一点。 - 生成器要做两件事:把砧板的位置声明成对象,再根据
cook_recipes里with等于这件设备名字的菜谱写cooks。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 51 分,用了 11 步。
T07.椰子在储藏柜里
关卡是 0_2_coconut_juice_ingredient_storage.yaml。C 是椰子储藏柜,可以无限地拿。
| | C | -/| -p| * | | | 1 | | | | | | | | | X |
在 T06 的基础上,再加上储藏柜。写出新的谓词、动作,以及这一关新增的事实。
查看答案
; 在 :predicates 里加+ (storage ?location - location ?food - food) + (:action take_from_storage+ :parameters (?food - food ?location - location)+ :precondition (and+ (hand_empty)+ (storage ?location ?food)+ )+ :effect (and+ (holding ?food)+ (not (hand_empty))+ )+ )
生成器为这一关新增的内容(包括 T06 的部分):
loc_2_0 - location loc_1_0 - location (station_empty loc_2_0) (cooks catalog_food_coconut loc_2_0 catalog_food_coconut_juice) (storage loc_1_0 catalog_food_coconut)
规划器给出的计划:
1. take_from_storage catalog_food_coconut loc_1_0 2. put_in_station catalog_food_coconut loc_2_0 3. cook catalog_food_coconut catalog_food_coconut_juice loc_2_0 4. pick_up empty_plate loc_3_0 5. station_to_plate empty_plate catalog_food_coconut_juice loc_2_0 plated_catalog_food_coconut_juice 6. deliver plated_catalog_food_coconut_juice loc_4_0
评分要点
- 储藏柜无限,所以
take_from_storage的效果里不能删掉storage事实。 - 储藏柜里的椰子不要写成
(item_at catalog_food_coconut loc_1_0),否则拿一次就没了。 - 生成器要遍历厨房里所有的
Storage实体,读它的food_name。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 52 分,用了 11 步。
T08.储藏柜很难走到
关卡是 0_3_coconut_juice_hard_to_reach_storage.yaml。储藏柜在左上角,厨师要绕一大圈才能走到。
| C | - | - | - | - | | | | | | - | | - | - | - | | -/| | | 1 | | | -p| | X | | | | * |
和 T07 相比,域要不要改?问题文件有什么不同?
查看答案
域完全不用改。问题文件里只是位置的名字不同:
loc_4_2 - location loc_0_0 - location (station_empty loc_4_2) (cooks catalog_food_coconut loc_4_2 catalog_food_coconut_juice) (storage loc_0_0 catalog_food_coconut)
规划器给出的计划和 T07 一样是 6 步:
1. take_from_storage catalog_food_coconut loc_0_0 2. put_in_station catalog_food_coconut loc_4_2 3. cook catalog_food_coconut catalog_food_coconut_juice loc_4_2 4. pick_up empty_plate loc_4_3 5. station_to_plate empty_plate catalog_food_coconut_juice loc_4_2 plated_catalog_food_coconut_juice 6. deliver plated_catalog_food_coconut_juice loc_4_4
原因是模板的域不建模走路。走多远、怎么绕过台面,都交给 navigation 去处理。规划器只关心“在哪个位置做什么”。
评分要点
- 这道题考的是对“走路不进模型”这个设计的理解。答“要加 move 动作”是错的。
- 可以顺带说出代价:规划器不知道走路要花多少步,所以它找到的计划步数最少,但不一定是用时最短的。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 52 分,用了 21 步。
T09.水果芭菲:拿着盘子去储藏柜
回到 T03 的厨房。模板没有“放下”动作,所以厨师拿着盘子时,手就一直不空。
- 为什么只加 T07 的
take_from_storage不够? - 写出一个能解决这一关的动作,以及这一关新增的事实。
提示:在这个游戏里,厨师拿着盘子去储藏柜取食材,食材会直接进盘子。
查看答案
take_from_storage 要求手是空的,它拿出来的酸奶在手上。可是模板的 add_to_plate 要求“拿着盘子、食物在台面上”,没有“放下”动作,酸奶永远到不了台面。所以要一个“拿着盘子直接从储藏柜装盘”的动作:
; 在 :predicates 里加+ (storage ?location - location ?food - food) + (:action storage_to_plate+ :parameters (?plate - plate ?food - food ?location - location ?result - plate)+ :precondition (and+ (holding ?plate)+ (storage ?location ?food)+ (plating ?plate ?food ?result)+ )+ :effect (and+ (holding ?result)+ (not (holding ?plate))+ )+ )
生成器为这一关新增的内容:
loc_2_0 - location (plating plated_catalog_food_fruit_salad catalog_food_yoghurt plated_catalog_food_fruit_parfait) (plating plated_catalog_food_yoghurt catalog_food_fruit_salad plated_catalog_food_fruit_parfait) (storage loc_2_0 catalog_food_yoghurt)
规划器给出的计划:
1. pick_up empty_plate loc_3_0 2. add_to_plate empty_plate catalog_food_fruit_salad loc_1_0 plated_catalog_food_fruit_salad 3. storage_to_plate plated_catalog_food_fruit_salad catalog_food_yoghurt loc_2_0 plated_catalog_food_fruit_parfait 4. deliver plated_catalog_food_fruit_parfait loc_4_0
评分要点
- 关键的两条
plating事实,是从组合菜谱“沙拉加酸奶等于芭菲”来的。两种食材先放哪个都行,所以写两条。少写一条不影响这一关,但另一种顺序就做不了。 storage_to_plate复用了plating,不需要新的菜谱谓词。- 效果里不要加
hand_empty,厨师手上还拿着那盘菜。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 52 分,用了 11 步。
T10.两层的组合菜谱
关卡是 1_1_fruit_parfait_chopped_on_counter.yaml。台面上有切好的西瓜 -w 和切好的菠萝 -a。菜谱有两条:西瓜加菠萝等于水果沙拉;水果沙拉加酸奶等于水果芭菲。
| | -w| -a| Y | -p| * | | | 1 | | | | | | | | | | | X |
沿用 T09 的域,写出生成器要新增的全部事实。
查看答案
生成器为这一关新增的内容:
loc_3_0 - location (plating plated_catalog_food_chopped_pineapple catalog_food_chopped_watermelon plated_catalog_food_fruit_salad) (plating plated_catalog_food_chopped_watermelon catalog_food_chopped_pineapple plated_catalog_food_fruit_salad) (plating plated_catalog_food_fruit_salad catalog_food_yoghurt plated_catalog_food_fruit_parfait) (plating plated_catalog_food_yoghurt catalog_food_fruit_salad plated_catalog_food_fruit_parfait) (storage loc_3_0 catalog_food_yoghurt)
规划器给出的计划:
1. pick_up empty_plate loc_4_0 2. add_to_plate empty_plate catalog_food_chopped_pineapple loc_2_0 plated_catalog_food_chopped_pineapple 3. add_to_plate plated_catalog_food_chopped_pineapple catalog_food_chopped_watermelon loc_1_0 plated_catalog_food_fruit_salad 4. storage_to_plate plated_catalog_food_fruit_salad catalog_food_yoghurt loc_3_0 plated_catalog_food_fruit_parfait 5. deliver plated_catalog_food_fruit_parfait loc_5_0
评分要点
- 每条组合菜谱生成两条
plating,两条菜谱一共四条。 - 第一条菜谱的结果是“装着沙拉的盘子”,它又是第二条菜谱的起点。这就是“盘子状态即对象”的好处:多层菜谱只是一条
plating的链,不需要新动作。 - 域不用改。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 53 分,用了 15 步。
T11.芝士汉堡:一个动作都不加
关卡是 2_0_cheeseburger_processed.yaml。台面上有煎好的肉饼、面包和芝士片。三样东西两两组合,一共有六条组合菜谱,见关卡文件。
| | -g| -b| -s| -p| * | | | 1 | | | | | | | | | | | X |
只用模板原来的域,不加任何动作,能完成这一关吗?如果能,生成器要新增哪些事实?
查看答案
能。三样东西都在台面上,模板的 add_to_plate 就够了,只缺组合菜谱对应的 plating。六条菜谱各写两条,一共 12 条:
(plating plated_catalog_food_bun catalog_food_sliced_cheese plated_catalog_food_bun_cheese) (plating plated_catalog_food_sliced_cheese catalog_food_bun plated_catalog_food_bun_cheese) (plating plated_catalog_food_bun catalog_food_grilled_meat plated_catalog_food_hamburger) (plating plated_catalog_food_grilled_meat catalog_food_bun plated_catalog_food_hamburger) (plating plated_catalog_food_grilled_meat catalog_food_sliced_cheese plated_catalog_food_patty_cheese) (plating plated_catalog_food_sliced_cheese catalog_food_grilled_meat plated_catalog_food_patty_cheese) (plating plated_catalog_food_bun_cheese catalog_food_grilled_meat plated_catalog_food_cheeseburger) (plating plated_catalog_food_grilled_meat catalog_food_bun_cheese plated_catalog_food_cheeseburger) (plating plated_catalog_food_hamburger catalog_food_sliced_cheese plated_catalog_food_cheeseburger) (plating plated_catalog_food_sliced_cheese catalog_food_hamburger plated_catalog_food_cheeseburger) (plating plated_catalog_food_bun catalog_food_patty_cheese plated_catalog_food_cheeseburger) (plating plated_catalog_food_patty_cheese catalog_food_bun plated_catalog_food_cheeseburger)
只用模板原来的域,规划器给出的计划:
1. pick_up empty_plate loc_4_0 2. add_to_plate empty_plate catalog_food_grilled_meat loc_1_0 plated_catalog_food_grilled_meat 3. add_to_plate plated_catalog_food_grilled_meat catalog_food_bun loc_2_0 plated_catalog_food_hamburger 4. add_to_plate plated_catalog_food_hamburger catalog_food_sliced_cheese loc_3_0 plated_catalog_food_cheeseburger 5. deliver plated_catalog_food_cheeseburger loc_5_0
评分要点
- 这一题考的是分清“缺动作”和“缺事实”。很多关卡跑不通,并不需要改域,只要生成器多写几条事实。
- 12 条事实可以先写一条,说明规律:“菜谱 a 加 b 等于 c,就写 a 的盘子加 b 等于 c 的盘子,以及 b 的盘子加 a 等于 c 的盘子”。在纸上这样答,要先问考官是否接受。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 52 分,用了 15 步。
T12.砧板和锅用同一套动作
关卡是 2_2_cheeseburger_from_scratch.yaml。面包、生肉、芝士都在储藏柜里。芝士要在砧板 -/ 上切,生肉要在灶台上的锅 -VR 里煎。
| | B | M | C | -/|-VR| -p| * | | | 1 | | | | | | | | | | | | | | | X |
T06 的三个工作台动作能不能同时用在砧板和锅上?写出这一关关于工作台和储藏柜的新增事实。
查看答案
能。工作台动作里没有写死设备种类,哪个位置能把什么做成什么,全由 cooks 事实决定:
(station_empty loc_4_0) (cooks catalog_food_cheese loc_4_0 catalog_food_sliced_cheese) (station_empty loc_5_0) (cooks catalog_food_meat loc_5_0 catalog_food_grilled_meat) (storage loc_1_0 catalog_food_bun) (storage loc_2_0 catalog_food_meat) (storage loc_3_0 catalog_food_cheese)
另外还有六条组合菜谱的 12 条 plating,和 T11 一样。用 T06、T07、T09 合起来的域,规划器给出的计划一共 11 步:
1. take_from_storage catalog_food_meat loc_2_0 2. put_in_station catalog_food_meat loc_5_0 3. take_from_storage catalog_food_cheese loc_3_0 4. put_in_station catalog_food_cheese loc_4_0 5. cook catalog_food_cheese catalog_food_sliced_cheese loc_4_0 6. cook catalog_food_meat catalog_food_grilled_meat loc_5_0 7. pick_up empty_plate loc_6_0 8. storage_to_plate empty_plate catalog_food_bun loc_1_0 plated_catalog_food_bun 9. station_to_plate plated_catalog_food_bun catalog_food_sliced_cheese loc_4_0 plated_catalog_food_bun_cheese 10. station_to_plate plated_catalog_food_bun_cheese catalog_food_grilled_meat loc_5_0 plated_catalog_food_cheeseburger 11. deliver plated_catalog_food_cheeseburger loc_7_0
评分要点
- 锅放在灶台上,模拟器里这个位置有两件设备。生成器只为“有菜谱用到它”的那件写事实,也就是锅;灶台本身没有菜谱,跳过。
cooks的第二个参数是位置,所以芝士只能在loc_4_0切,肉只能在loc_5_0煎。把位置参数去掉,规划器就可能“在锅里切芝士”。
验证
在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 57 分,用了 31 步。
T13.只有一块砧板,却要切两样水果
关卡是 1_3_fruit_parfait_ingredients_on_counter.yaml。台面上有整个西瓜、整个菠萝和酸奶,只有一块砧板。
| | -w| -a| -y| -/| -p| * | | | 1 | | | | | | | | | | | | | X |
用 T06 到 T12 的全部扩展,这一关仍然找不到计划。
- 说出原因。
- 写出最少的改动,让它能解。
查看答案
原因。 切完西瓜,要先把它从砧板上移走,砧板才能切菠萝。唯一的移法是 station_to_plate,于是厨师手里拿着一盘西瓜。这时手不空,拿不起菠萝;又没有“放下”动作,盘子永远放不下。这是一个死局。
最少的改动。 加一个 place 动作和 counter_empty 谓词。同时,pick_up 和 add_to_plate 都会让台面变空,效果里要补上 counter_empty:
; 在 :predicates 里加+ (counter_empty ?location - location) + (:action place+ :parameters (?item - item ?location - location)+ :precondition (and+ (holding ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty)+ (not (holding ?item))+ (not (counter_empty ?location))+ )+ ) (:action pick_up :parameters (?item - item ?location - location) :precondition (and (hand_empty) (item_at ?item ?location) ) :effect (and (holding ?item)+ (counter_empty ?location) (not (hand_empty)) (not (item_at ?item ?location)) ) ) (:action add_to_plate :parameters (?plate - plate ?food - food ?location - location ?result - plate) :precondition (and (holding ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and (holding ?result)+ (counter_empty ?location) (not (holding ?plate)) (not (item_at ?food ?location)) ) )
不加 place 时,规划器的结果:
(规划器没有找到计划)
加了之后的计划,第 6 步把那盘西瓜放下:
1. pick_up catalog_food_watermelon loc_1_0 2. put_in_station catalog_food_watermelon loc_4_0 3. cook catalog_food_watermelon catalog_food_chopped_watermelon loc_4_0 4. pick_up empty_plate loc_5_0 5. station_to_plate empty_plate catalog_food_chopped_watermelon loc_4_0 plated_catalog_food_chopped_watermelon 6. place plated_catalog_food_chopped_watermelon loc_1_0 7. pick_up catalog_food_pineapple loc_2_0 8. put_in_station catalog_food_pineapple loc_4_0 9. cook catalog_food_pineapple catalog_food_chopped_pineapple loc_4_0 10. pick_up plated_catalog_food_chopped_watermelon loc_1_0 11. station_to_plate plated_catalog_food_chopped_watermelon catalog_food_chopped_pineapple loc_4_0 plated_catalog_food_fruit_salad 12. add_to_plate plated_catalog_food_fruit_salad catalog_food_yoghurt loc_3_0 plated_catalog_food_fruit_parfait 13. deliver plated_catalog_food_fruit_parfait loc_6_0
评分要点
- 只写
place而不改pick_up和add_to_plate,这一关仍然无解:开局时没有一个空台面,只有拿走东西之后台面才会变空。 - 生成器要为空台面写
counter_empty。这一关开局时所有台面都有东西,所以一条都没有,见 T14。
验证
在模拟器里实际跑一局的结果:结束原因 gave_up,送出 0 单,剩 1 单,得 0 分,用了 0 步。
加了 place 之后:在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 54 分,用了 34 步。
T14.砧板下面的台面算不算空台面
关卡是 1_4_fruit_parfait_ingredients_in_storage.yaml。三样水果都在储藏柜里,台面上只有砧板和一个盘子。
| | W | P | Y | -/| -p| * | | | 1 | | | | | | | | | | | | | X |
加了 T13 的 place 之后,describe_counters 要为“台面是空的”写事实。有人这样写:
for counter in environment.get_entities_of_type(Counter):
if counter.held_item_id is None:
problem.add_fact(f"(counter_empty {location_symbol(counter)})")
- 这一关开局时,正确的
counter_empty事实应该有哪些? - 上面的写法会多写出哪一条?会有什么后果?
查看答案
- 一条都没有。 两个台面一个放着盘子,一个放着砧板。正确的结果是:
[]。 - 会多写出
(counter_empty loc_4_0)。 在模拟器里,砧板是放在台面上的另一个实体,台面自己的held_item_id仍然是空的,所以上面的代码会把砧板下面的台面当成空台面。
后果是规划器会把盘子 place 到 loc_4_0 上,然后又往同一个位置的砧板里放西瓜。模型里两件事互不冲突,游戏里却不可能同时发生。出这套题时,我的第一版扩展就犯了这个错:1_4 的计划能找到,但在模拟器里执行到第 10 步就卡住了,直到时间用完。
修正方法是跳过有设备的位置:
stations = {(e.x, e.y) for e in environment.get_entities_of_type(Equipment)}
...
if counter.held_item_id is None and (counter.x, counter.y) not in stations:
评分要点
- 这道题考的是“生成的事实必须和游戏一致”。规划器只会相信你写的事实,事实写错了,它找到的计划也执行不了。
- 也可以答“在域里让 place 要求不是工作台”,但这需要一个新的肯定谓词,还是要在生成器里写对。
验证
修正之后:在模拟器里实际跑一局的结果:结束原因 orders_delivered,送出 1 单,剩 0 单,得 57 分,用了 32 步。
第三组:读懂模板的翻译器
T15.三步计划翻译成什么
T01 的计划有三步。请写出模板的 from_pddl 会把每一步翻译成哪种模拟器动作、作用在哪块砖上,以及它检查哪些条件。
查看答案
| 计划步骤 | 模拟器动作 | 作用的砖 | 检查的条件 |
|---|---|---|---|
pick_up empty_plate loc_2_0 |
PickUp | (2, 0) | agent_id=chef_1_1, expected_held_equipment_name=catalog/equipment/plate |
add_to_plate empty_plate catalog_food_coconut_juice loc_1_0 plated_catalog_food_coconut_juice |
Combine | (1, 0) | agent_id=chef_1_1, expected_output_name=catalog/food/coconut_juice, expected_held_equipment_name=catalog/equipment/plate, expected_target_food_name=catalog/food/coconut_juice |
deliver plated_catalog_food_coconut_juice loc_3_0 |
Deliver | (3, 0) | agent_id=chef_1_1, expected_held_food=catalog/food/coconut_juice, expected_held_equipment_name=catalog/equipment/plate |
几点说明:
- 作用的砖是厨师要面对的那块砖,不是厨师站的砖。比如第二步作用在 (1,0) 的台面上,厨师实际站在它下面的 (1,1),由 navigation 决定。
- 每一步只翻译成一个模拟器动作,走路和转向都不在里面。
- 所有步骤都交给厨房里的第一个厨师。
评分要点
- 模拟器动作名写对是基本要求:拿起是
PickUp,装盘是Combine,上菜是Deliver。把装盘写成PickUp是常见错误。 - 能说出
expected_*字段的作用就更好:执行时如果厨房和预期不一样,比如盘子被别人拿走了,这个动作就不合法,厨师会原地等待。
T16.改了参数顺序却没改翻译器
有同学觉得 add_to_plate 的参数顺序不顺眼,在域文件里把它改成了:
:parameters (?food - food ?plate - plate ?location - location ?result - plate)
前提和效果都相应改好了,规划器也能找到计划。但是 from_pddl.py 没改。运行时会发生什么?为什么?
查看答案
运行时会报错,整局直接中止。规划器现在返回的步骤是:
add_to_plate catalog_food_coconut_juice empty_plate loc_1_0 plated_catalog_food_coconut_juice
而 translate 还是按老顺序拆参数:plate, food, location, result = step.parameters。于是它把椰子汁当成了盘子,去调用 food_on_plate,得到:
ValueError: Unknown plate in plan: catalog_food_coconut_juice
模板在域文件里专门提醒过:from_pddl.py 按 :parameters 的顺序读取参数,改了顺序就要同时改翻译器。
评分要点
- 答出“参数是按位置读的,不是按名字读的”就抓住了要点。
- 这类错误规划器发现不了,因为规划器只看域,不看翻译器。只有真正运行时才会暴露。
验证
验证方法:脚本直接用改过顺序的步骤调用模板的 translate,捕获到了上面的错误。
T17.新动作对应哪种模拟器动作
第二组给模板加了六个动作。请为每个动作写出:from_pddl 的 translate 应该把它翻译成哪种模拟器动作,作用在哪个参数表示的位置上,要检查什么。
查看答案
| PDDL 动作 | 模拟器动作 | 作用的位置 | 检查的条件 |
|---|---|---|---|
put_in_station ?food ?location |
Combine | ?location |
手上的食物等于 ?food |
cook ?raw ?result ?location |
Cook | ?location |
工作台里的食物等于 ?raw,结果等于 ?result |
station_to_plate ?plate ?food ?location ?result |
Combine | ?location |
手上是盘子,盘子里是 ?plate 对应的食物,工作台里是 ?food,结果是 ?result |
take_from_storage ?food ?location |
TakeFromStorage | ?location |
储藏柜给的是 ?food |
storage_to_plate ?plate ?food ?location ?result |
TakeFromStorage | ?location |
储藏柜给的是 ?food,装进手上的盘子后是 ?result |
place ?item ?location |
Place | ?location |
手上是 ?item |
有两点值得注意:
- 放进工作台和从工作台装盘都是
Combine。 模拟器的Combine会根据“手上拿着什么、面前是什么”决定做什么。翻译器只要把expected_*字段填对,区分这两种情况。 storage_to_plate用的是TakeFromStorage。 模拟器里,拿着盘子对着储藏柜取料,食材会直接进盘子。所以不需要先拿再放两步。
评分要点
- 位置几乎总是动作里那个
location参数,但要说清楚是哪一个。动作里有两个位置时,比如我们自己域里的storage_to_plate,就要拆成两个模拟器动作。 - 这道题不是 PDDL 题,口头答出对应关系即可。
验证
这六个分支就是 pddl/ext.py 里 ExtFromPDDL.translate 的实现。用它在模拟器里跑 Level 1 的 12 关,全部送达,结果见第二组各题的验证。
第四组:模板的局限和改法
T19.1 号厨师走不到出菜口
这是第一关的变体:中间一排台面把厨房隔成左右两半。1 号厨师在左边,能够到椰子汁和盘子;2 号厨师在右边,能够到出菜口;中间的台面两个人都能够到。关卡文件在 set3/levels/v4_divided.yaml。
| -j| 1 | - | 2 | * | | -p| | - | | X |
- 模板能找到计划吗?执行时会怎样?
- 写出修改后的域和这一关的问题文件。
查看答案
1. 能找到计划,还是那三步,见 T01。但是三步都交给 1 号厨师,他走不到出菜口,最后一步永远执行不了。模板默认不重新规划,所以他会一直等到时间用完。
2. 要做三件事:加厨师类型和 can_reach,让每个动作带上厨师;加 place 和 counter_empty,这样才能在中间的台面交接;问题里为每个厨师写出他能到的位置。
完整的域:
(define (domain overcooked) (:requirements :strips :typing) (:types location item chef - object food plate - item ) (:predicates- (hand_empty)+ (hand_empty ?chef - chef)- (holding ?item - item)+ (holding ?chef - chef ?item - item)+ (can_reach ?chef - chef ?location - location) (item_at ?item - item ?location - location)+ (counter_empty ?location - location) (delivery_location ?location - location) (plating ?plate - plate ?food - food ?result - plate) (delivered ?plate - plate) ) (:action pick_up- :parameters (?item - item ?location - location)+ :parameters (?chef - chef ?item - item ?location - location) :precondition (and- (hand_empty)+ (can_reach ?chef ?location)+ (hand_empty ?chef) (item_at ?item ?location) ) :effect (and- (holding ?item)- (not (hand_empty))+ (holding ?chef ?item)+ (counter_empty ?location)+ (not (hand_empty ?chef)) (not (item_at ?item ?location)) ) )+ (:action place+ :parameters (?chef - chef ?item - item ?location - location)+ :precondition (and+ (can_reach ?chef ?location)+ (holding ?chef ?item)+ (counter_empty ?location)+ )+ :effect (and+ (item_at ?item ?location)+ (hand_empty ?chef)+ (not (holding ?chef ?item))+ (not (counter_empty ?location))+ )+ ) (:action add_to_plate- :parameters (?plate - plate ?food - food ?location - location ?result - plate)+ :parameters (?chef - chef ?plate - plate ?food - food ?location - location ?result - plate) :precondition (and- (holding ?plate)+ (can_reach ?chef ?location)+ (holding ?chef ?plate) (item_at ?food ?location) (plating ?plate ?food ?result) ) :effect (and- (holding ?result)- (not (holding ?plate))+ (holding ?chef ?result)+ (counter_empty ?location)+ (not (holding ?chef ?plate)) (not (item_at ?food ?location)) ) ) (:action deliver- :parameters (?plate - plate ?location - location)+ :parameters (?chef - chef ?plate - plate ?location - location) :precondition (and- (holding ?plate)+ (can_reach ?chef ?location)+ (holding ?chef ?plate) (delivery_location ?location) ) :effect (and (delivered ?plate)- (hand_empty)- (not (holding ?plate))+ (hand_empty ?chef)+ (not (holding ?chef ?plate)) ) ) )
这一关的问题文件:
(define (problem kitchen)
(:domain overcooked)
(:objects
empty_plate - plate
catalog_food_coconut_juice - food
plated_catalog_food_coconut_juice - plate
chef_1 chef_2 - chef
loc_0_0 loc_0_1 loc_2_0 loc_2_1 loc_4_0 - location
)
(:init
(plating empty_plate catalog_food_coconut_juice plated_catalog_food_coconut_juice)
(hand_empty chef_1)
(hand_empty chef_2)
(item_at catalog_food_coconut_juice loc_0_0)
(item_at empty_plate loc_0_1)
(counter_empty loc_2_0)
(counter_empty loc_2_1)
(delivery_location loc_4_0)
(can_reach chef_1 loc_0_0)
(can_reach chef_1 loc_0_1)
(can_reach chef_1 loc_2_0)
(can_reach chef_1 loc_2_1)
(can_reach chef_2 loc_2_0)
(can_reach chef_2 loc_2_1)
(can_reach chef_2 loc_4_0)
)
(:goal
(and
(delivered plated_catalog_food_coconut_juice)
)
)
)
规划器给出的计划:
1. pick_up chef_1 empty_plate loc_0_1 2. add_to_plate chef_1 empty_plate catalog_food_coconut_juice loc_0_0 plated_catalog_food_coconut_juice 3. place chef_1 plated_catalog_food_coconut_juice loc_2_0 4. pick_up chef_2 plated_catalog_food_coconut_juice loc_2_0 5. deliver chef_2 plated_catalog_food_coconut_juice loc_4_0
评分要点
- 这道题的关键在
can_reach。两个厨师都写全部位置,计划能找到,但会让 1 号厨师去上菜,执行时同样卡住。 - 只加厨师参数不加
place,问题无解,因为没有办法把盘子交给 2 号厨师。 - 中间两个台面要写
counter_empty,否则也放不上去。 from_pddl.py的chef_id要改成从步骤的第一个参数读出是哪个厨师。
验证
在模拟器里实际跑一局的结果:结束原因 time_limit,送出 0 单,剩 1 单,得 0 分,用了 4 步。
修改后的版本只用规划器验证过。
T20.把脏盘子洗干净
回到 T05 的厨房。修改模板,让厨师能先洗盘子再装椰子汁。写出域的改动、问题文件里新增的内容,并说明生成器哪里要改。
查看答案
; 在 :predicates 里加+ (sink ?location - location)+ (washes ?dirty - plate ?clean - plate) + (:action wash_plate+ :parameters (?dirty - plate ?clean - plate ?location - location)+ :precondition (and+ (holding ?dirty)+ (washes ?dirty ?clean)+ (sink ?location)+ )+ :effect (and+ (holding ?clean)+ (not (holding ?dirty))+ )+ )
问题里新增的内容:
dirty_plate - plate loc_2_0 - location loc_3_1 - location (item_at dirty_plate loc_2_0) (sink loc_3_1) (washes dirty_plate empty_plate)
规划器给出的计划:
1. pick_up dirty_plate loc_2_0 2. wash_plate dirty_plate empty_plate loc_3_1 3. add_to_plate empty_plate catalog_food_coconut_juice loc_1_0 plated_catalog_food_coconut_juice 4. deliver plated_catalog_food_coconut_juice loc_3_0
生成器要改两处:
item_symbol对脏盘子返回dirty_plate,而不是 None。这样describe_counters就会写出脏盘子在哪。- 新增一个描述水槽的函数,写
sink和washes事实。
评分要点
- 脏盘子是盘子的又一种状态,所以是一个新的
plate对象dirty_plate,washes说明洗干净后变成哪个对象。这和“装着某种食物的盘子”是同一个思路。 - 洗盘子之后厨师手上是空盘,不是
hand_empty。 - 只改域不改生成器,问题里还是没有脏盘子,照样无解。这是最容易漏的一点。
验证
修改后的版本只用规划器验证过,没有在模拟器里跑。
Domain 文件骨架
一个动作必须有三部分:参数、前提、效果,缺一部分就是大错。STRIPS 不能写否定前提,要表达“没有”,就定义一个肯定的谓词,比如 hand_empty、counter_empty。
(define (domain overcooked)
(:requirements :strips :typing)
(:types
location item - object
food plate - item
)
(:predicates
(hand_empty)
(holding ?item - item)
(item_at ?item - item ?location - location)
; 新规则需要的谓词写在这里
)
(:action action_name
:parameters (?x - type1 ?y - type2)
:precondition (and
; 执行前必须成立的事实
)
:effect (and
; 新成立的事实
; (not ...) 不再成立的事实,和上面成对写
)
)
)
写完检查: 参数表里每个变量都用到了吗?用到的变量都在参数表里吗?加了 holding 是否删了 hand_empty?东西从 A 挪到 B,是否删了 A 处的事实?新谓词会不会影响已有动作?
Problem 文件骨架
对象表、初始状态、目标缺一部分就是大错。初始状态只写成立的事实,没写的都当作不成立。
(define (problem problem_name)
(:domain overcooked)
(:objects
loc_a loc_b - location
tomato - food
empty_plate plated_tomato - plate
)
(:init
; 厨师状态
; 台面上有什么;只有真正空着的台面才写 counter_empty
; 设备、储藏柜、出菜口
; 菜谱关系(plating 等静态事实)
; 订单
; 可达性(多厨师时的 can_reach)
)
(:goal (and
; 写“送达”,不是“拿在手上”
))
)
写完检查: 初始状态和目标里出现的每个名字,对象表里都声明了吗?类型对吗?