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。”

依据:Fast Downward PDDL 支持范围。

评分检查

不能直接使用该 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 支持并不等于本系统支持。

口头回答: “我先对齐真正的评价指标,再决定用成本模型还是执行模拟。动作少不保证走得少,更不保证多人更快完成。”

依据:Fast Downward PDDL 支持范围。

评分检查

区分动作数、成本与实际完成时间;固定动作代价未必准确描述动态路径和并行 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。