HTN领域设计实践指南:从理论到落地

2026-08-28 Updated 2026-09-11 36 min read 0 views

系列第七篇,实践篇开篇。理论讲完了,该教怎么用了。本文将系统介绍HTN领域设计的核心方法、常见模式和最佳实践。


一、开篇:为什么领域设计是"拦路虎"?

在前面六篇文章中,我们系统学习了HTN规划的理论基础:
- 什么是HTN?——形式化定义与核心机制
- HTN与经典规划的区别——表达能力更强,但不可判定
- TFD算法详解——如何分解任务、搜索解

然而,当你真正动手设计一个HTN领域时,可能会遇到这些困惑:

❓ 我的任务层次结构怎么划分才合理?
❓ 一个compound task应该有多少个method?
❓ method的precondition写太细会怎样?写太粗又怎样?
❓ 为什么我的规划器跑不出来?是领域设计问题还是算法问题?

这些问题指向一个核心事实:HTN领域设计是一门"艺术"

本文将从实践角度,系统回答这些问题,帮助你从"懂HTN理论"进阶到"会设计HTN领域"。


二、识别任务的自然层次结构

2.1 什么是"自然层次"?

HTN的核心是任务分解,而任务分解的前提是任务本身具有层次结构。一个好的层次结构应该:

  1. 符合人类认知习惯:专家是如何思考这个问题的?
  2. 反映问题本身的结构:任务之间的逻辑关系是什么?
  3. 便于复用和扩展:子任务能否被多个上层任务共享?

反例:强行构造的层次结构

任务:去北京
分解为:[买机票, 预订酒店, 打包行李, 去机场, ...]

这个分解看似合理,但实际上混合了多个层次:买机票是"计划"层次,打包行李是"准备"层次,去机场是"执行"层次。

正例:自然的层次结构

任务:出差北京
分解为:[规划行程, 准备出发, 执行行程]
  - 规划行程 → [订机票, 订酒店, 安排会议]
  - 准备出发 → [打包行李, 处理工作交接]
  - 执行行程 → [去机场, 登机, 到达后活动]

2.2 识别层次结构的三步法

Step 1:枚举原子动作

先不考虑层次,列出所有可能的原子动作:

航天器领域:slew, takePhoto, download, charge, ...
物流领域:load, unload, drive, fly, ...

Step 2:按"目的"分组

原子动作很少单独出现,通常是为了完成某个"小目标":

[定位目标, 调整姿态, 拍照, 存储数据] → 观测任务
[加载数据, 建立链路, 传输数据, 确认接收] → 下传任务

Step 3:构建任务树

从底层向上归纳,形成任务树:

执行观测任务
├── 观测准备
│   ├── 定位目标
│   └── 调整姿态
├── 执行观测
│   └── 拍照
└── 数据处理
    ├── 存储
    └── 下传

2.3 判断层次结构好坏的标准

标准 好的层次结构 差的层次结构 示例
粒度一致性 同层任务粒度相近 有的很粗,有的很细 ✅ [观测、下传] vs ❌ [观测、买咖啡]
逻辑清晰性 分解理由显而易见 需要大量解释才能理解 ✅ [规划→准备→执行] vs ❌ 随意混合
复用性 子任务可被多处调用 每个任务只在一个地方用 ✅ "充电"可被多任务调用
可扩展性 新增任务容易插入 新增任务需要大改结构 ✅ 新增目标类型只需加method

三、设计有效的方法(Method)

3.1 Method的核心要素

一个method由三部分组成:

(:method 方法名
  :precondition (前提条件)
  :tasks (子任务序列)
)

设计原则
- Precondition:精确描述何时适用这个方法
- Tasks:清晰定义分解后的执行顺序

3.2 Precondition设计的两难

问题:precondition应该写多细?

太粗的后果

(:method 观测目标
  :precondition ()  ; 无条件适用
  :tasks (定位 调整姿态 拍照)
)

→ 可能生成不可行的计划(如目标不可见时仍尝试观测)

太细的后果

(:method 观测目标
  :precondition (and 
    (可见 ?target)
    (姿态正确 ?target)
    (电量充足)
    (存储空间充足)
    (无更高优先级任务)
    ...
  )
  :tasks (拍照)
)

→ 限制了规划器的灵活性,可能错过可行解

平衡策略

核心约束 → 写进precondition
辅助约束 → 在子任务中处理

示例:

(:method 观测目标
  :precondition (可见 ?target)  ; 核心约束
  :tasks (调整姿态 拍照)        ; 调整姿态会检查姿态正确性
)

3.3 多Method设计的"覆盖"问题

一个compound task通常有多个method,它们共同构成"解决方案空间"。

设计要点
1. 互斥性:不同method的适用条件应尽量不重叠
2. 完备性:所有可能的情况都应该有对应的method
3. 偏好性:如果希望优先选择某个method,可以在precondition中设置"偏好条件"

示例:到达目的地的多method设计

(:method 到达目的地
  :precondition (距离近 ?dest)
  :tasks (步行 ?dest)
)

(:method 到达目的地
  :precondition (and (距离远 ?dest) (有车))
  :tasks (开车 ?dest)
)

(:method 到达目的地
  :precondition (and (距离远 ?dest) (无车) (有公交站 ?dest))
  :tasks (坐公交 ?dest)
)

这三个method几乎覆盖了所有情况,但边界情况可能需要额外处理。

⚠️ 关于互斥性的重要说明

上述示例的method并非严格互斥。如果 (距离近 ?dest)(有车) 同时成立,第一个和第二个method都可能被选择。

HTN规划器通常按定义顺序尝试——先定义的method优先级更高。如果第一个method适用,规划器会优先使用它,不会再尝试后面的method。

严格互斥的写法

(:method 到达目的地  ; Method 1
  :precondition (距离近 ?dest)
  :tasks (步行 ?dest))

(:method 到达目的地  ; Method 2
  :precondition (and (距离远 ?dest) (有车))
  :tasks (开车 ?dest))

(:method 到达目的地  ; Method 3 - 兜底
  :precondition (and (距离远 ?dest) (not (有车)) (有公交站 ?dest))
  :tasks (坐公交 ?dest))

这样三个method的条件互斥,确保每种情况只有一个method适用。严格互斥的好处是行为更可预测,坏处是条件写起来更复杂。实践中,根据需求选择:如果顺序优先已满足需求,不必强求严格互斥。

3.4 Task排序的学问

Task的排序影响:
1. 执行效率:并行任务可以无序
2. 可行性:某些任务必须先执行(如先开机再操作)
3. 规划器性能:有序任务减少搜索空间

排序规则
- 因果依赖:A的effect是B的precondition → A必须在B前
- 资源竞争:共享资源 → 根据资源状态决定顺序
- 逻辑顺序:业务逻辑要求 → 按业务规则排序


四、常见设计模式与反模式

4.1 设计模式(推荐)

Pattern 1:顺序分解模式

适用场景:任务有明确的执行顺序

(:method 执行观测
  :tasks (准备 → 执行 → 清理)
)

优点:简单直观,规划器负担小


Pattern 2:选择分解模式

适用场景:同一任务有多种实现方式

(:method 数据传输
  :precondition (有地面站)
  :tasks (通过地面站传输)
)

(:method 数据传输
  :precondition (无地面站)
  :tasks (通过中继卫星传输)
)

优点:增强鲁棒性,处理多种场景


Pattern 3:迭代分解模式

适用场景:需要多次执行相似任务

(:method 观测多个目标
  :precondition (目标列表非空)
  :tasks (观测第一个目标 观测剩余目标)
)

(:method 观测多个目标
  :precondition (目标列表为空)
  :tasks ()  ; 空任务,结束递归
)

优点:处理动态数量的任务

⚠️ 关键注意:迭代分解必须有明确的终止条件

上面的例子中,(目标列表为空) 是终止条件,返回空任务 ()。没有终止条件,规划器会陷入无限递归。

常见错误:忘记写终止method

;; 错误!只有递归,没有终止
(:method 观测多个目标
  :tasks (观测第一个目标 观测剩余目标))  ; 永远不会停止

Pattern 4:条件执行模式

适用场景:根据状态选择是否执行某步骤

(:method 完成任务
  :tasks (检查条件 (条件分支))
)

(:method 条件分支
  :precondition (条件满足)
  :tasks (执行动作)
)

(:method 条件分支
  :precondition (not (条件满足))
  :tasks ()  ; 跳过
)

优点:灵活处理不确定性


4.2 反模式(避免)

Anti-Pattern 1:过度分解

问题:把原子动作也当作compound task分解

(:method 拍照
  :tasks (打开相机 对准目标 按下快门 关闭相机)
)

后果
- 增加不必要的搜索空间
- 违反原子动作的语义

正确做法:拍照是原子动作,直接在method中引用


Anti-Pattern 2:循环依赖

问题:任务A分解出任务B,任务B又分解出任务A

(:method 任务A
  :tasks (任务B)
)

(:method 任务B
  :tasks (任务A)
)

后果:规划器陷入无限循环

正确做法:确保分解是有向无环的


Anti-Pattern 3:Precondition遗漏

问题:method的前提条件不完整

(:method 开车去
  :tasks (启动汽车 行驶)
)

遗漏:没检查有车、有驾照、油量充足...

后果:生成不可行计划

正确做法:系统性检查所有必要条件


Anti-Pattern 4:任务粒度不一致

问题:同一层级的任务粒度差异巨大

(:method 完成项目
  :tasks (写代码 买咖啡)  ; 前者是核心任务,后者是细节
)

后果
- 逻辑混乱
- 难以理解和维护

正确做法:买咖啡应该在"休息"或"日常"层次


五、HTN领域设计的最佳实践

5.1 从简单到复杂

推荐流程
1. 先实现一个最小可行领域
- 只包含最核心的任务和方法
- 确保能跑通一个简单场景

  1. 逐步扩展
  2. 添加新的method处理更多情况
  3. 添加新的compound task处理更复杂的任务

  4. 持续测试

  5. 每次扩展后测试是否破坏已有功能
  6. 使用测试用例覆盖各种场景

5.2 领域文档化

推荐格式

# HTN领域说明文档

## 1. 领域概述
- 应用场景:航天器任务规划
- 主要任务:观测、通信、维护

## 2. 任务层次图
(可视化任务树)

## 3. Method说明表
| Compound Task | Method | Precondition | Subtasks |
|--------------|--------|--------------|----------|
| 观测 | 标准观测 | 目标可见 | [定位, 调姿, 拍照] |

## 4. 原子动作说明
| 动作 | 参数 | Precondition | Effect |
|------|------|--------------|--------|
| takePhoto | target | 对准目标 | 存储图像 |

好处
- 自己回顾时能快速理解
- 与他人协作时降低沟通成本
- 调试时快速定位问题

5.3 调试技巧

问题:规划器跑不出来,怎么定位?

调试步骤

  1. 检查任务网络
    lisp ;; SHOP2中查看分解过程 (shop2:find-plans 'domain 'problem :verbose 2)
  2. 分解是否到达原子动作?
  3. 是否存在循环依赖?
  4. 使用 (trace-methods) 追踪method调用

  5. 检查约束冲突

  6. 前提条件是否矛盾?
  7. 时间约束是否可满足?
  8. 失败时检查当前状态与precondition的差异

  9. 简化问题——最小复现
    lisp ;; 创建最小测试用例 (defproblem minimal-test (:init (visible target1)) (:task (observe target1)))

  10. 减少任务数量
  11. 简化前提条件
  12. 找到最小复现案例

  13. 使用规划器日志

  14. SHOP2等规划器有详细日志
  15. 查看分解路径、失败原因

常见失败模式速查表

现象 可能原因 检查方法
无限循环/超时 递归method无终止条件 检查递归出口
找不到解 precondition过严 逐步放宽条件测试
解太慢 method数量过多 合并相似method
生成的计划不可执行 precondition遗漏关键约束 检查原子动作前提
分解过早终止 缺少某个method 检查是否覆盖所有情况

5.4 性能优化

影响性能的因素
- Method数量:越多搜索空间越大
- Precondition复杂度:复杂的条件判断耗时
- 任务网络深度:越深递归层数越多

优化策略
1. 合并相似method:用参数区分而非多个method
2. 简化precondition:移除冗余条件
3. 调整分解策略:优先分解约束最紧的任务


六、实例演练:航天器观测领域

6.1 问题定义

场景:卫星对多个地面目标进行观测并下传数据

约束
- 目标有可见性窗口
- 电量有限
- 存储空间有限
- 地面站有通信窗口

6.2 任务层次设计

主任务:执行观测计划
├── 观测任务
│   ├── 定位目标
│   ├── 调整姿态
│   └── 拍照
├── 数据管理
│   ├── 存储数据
│   └── 下传数据
└── 资源管理
    ├── 充电
    └── 清理存储

6.3 Method设计

;; 主任务分解
(:method 执行观测计划
  :precondition (有目标任务)
  :tasks (观测任务 数据管理)
)

;; 观测任务分解
(:method 观测任务
  :precondition (可见 ?target)
  :tasks (定位 ?target 调姿 ?target 拍照 ?target)
)

;; 数据下传分解
(:method 下传数据
  :precondition (and (有地面站窗口) (存储非空))
  :tasks (建立链路 传输数据 确认接收)
)

;; 无地面站时的备选方案
(:method 下传数据
  :precondition (and (无地面站窗口) (有中继卫星))
  :tasks (通过中继传输)
)

6.4 完整性检查

检查清单
- [ ] 所有compound task都有对应的method
- [ ] 所有method的precondition覆盖了可能的场景
- [ ] 分解最终都能到达原子动作
- [ ] 没有循环依赖
- [ ] 关键约束(电量、存储、窗口)都有处理

6.5 常见错误示例

学习正确的设计很重要,但避免错误同样关键。以下是一些典型的踩坑案例:

错误1:Precondition遗漏关键约束

;; 错误!没有检查目标可见性
(:method 观测任务
  :tasks (定位 ?target 调姿 ?target 拍照 ?target))
;; 后果:目标不可见时仍会尝试规划,生成不可执行的计划

;; 正确做法
(:method 观测任务
  :precondition (可见 ?target)  ; 关键约束
  :tasks (定位 ?target 调姿 ?target 拍照 ?target))

错误2:任务粒度混乱

;; 错误!"充电"是资源管理任务,不应与"观测任务"同级
(:method 执行观测计划
  :tasks (观测任务 充电))

;; 正确做法:分层处理
(:method 执行观测计划
  :tasks (观测任务 资源管理))
(:method 资源管理
  :precondition (电量不足)
  :tasks (充电))

错误3:Method条件不完整

;; 错误!缺少"无车且无公交站"的情况
(:method 到达目的地
  :precondition (距离近 ?dest)
  :tasks (步行 ?dest))
(:method 到达目的地
  :precondition (距离远 ?dest)
  :tasks (开车 ?dest))  ; 如果没车怎么办?

;; 后果:当"距离远且无车"时,规划器无解

;; 正确做法:覆盖所有情况,或提供兜底method
(:method 到达目的地  ; 兜底
  :precondition (and (距离远 ?dest) (not (有车)) (not (有公交站 ?dest)))
  :tasks (打车 ?dest))

错误4:递归无终止条件

;; 错误!无限递归
(:method 处理所有任务
  :tasks (处理第一个任务 处理所有任务))

;; 正确做法:添加终止条件
(:method 处理所有任务
  :precondition (任务列表非空)
  :tasks (处理第一个任务 处理所有任务))
(:method 处理所有任务
  :precondition (任务列表为空)
  :tasks ())  ; 终止

经验总结:每写完一个compound task的所有method,都应该问自己:
- 是否覆盖了所有可能的情况?
- 是否有遗漏的关键约束?
- 递归是否有出口?
- 任务粒度是否一致?


七、总结与展望

7.1 核心要点回顾

主题 核心要点
层次结构 自然、清晰、一致、可复用
Method设计 Precondition平衡、多method覆盖、合理排序
设计模式 顺序、选择、迭代、条件执行
反模式 过度分解、循环依赖、条件遗漏、粒度不一致
最佳实践 从简到繁、文档化、系统调试、性能优化

7.2 下一步

设计好领域后,需要用规划器来执行。下一篇我们将介绍:
- 主流HTN规划器对比:SHOP2、PyHOP、SIADEX
- 如何选择合适的规划器
- 规划器配置与调优


参考文献

  1. Nau, D. S., Au, T. C., Ilghami, O., Kuter, U., Murdock, J. W., Wu, D., & Yaman, F. (2003). SHOP2: An HTN planning system. Journal of Artificial Intelligence Research, 20, 379-404.
  2. Erol, K., Hendler, J., & Nau, D. S. (1994). UMCP: A Sound and Complete Procedure for Hierarchical Task-Network Planning. AIPS.
  3. Ghallab, M., Nau, D., & Traverso, P. (2004). Automated Planning: Theory and Practice. Morgan Kaufmann. (Chapter 11)
  4. Bercher, P., Alford, R., & Höller, D. (2019). A survey on hierarchical planning. Artificial Intelligence, 277, 103-165.