0

0

解决Python PDDL框架中的RecursionError:正确定义动作效果

霞舞

霞舞

发布时间:2025-11-29 13:54:24

|

689人浏览过

|

来源于php中文网

原创

解决python pddl框架中的recursionerror:正确定义动作效果

本文深入探讨了在使用Python PDDL框架实现旅行商问题时遇到的`RecursionError`,并提供了解决方案。核心在于如何正确利用`pddl.logic`模块提供的逻辑运算符来定义动作效果,而非简单的字符串拼接。通过一个旅行商问题的实例,文章详细阐述了PDDL域和问题的构建过程,强调了逻辑表达式的正确使用,以避免递归深度超限错误,确保规划域的有效性和可解析性。

1. Python PDDL框架简介

pddl是一个Python库,旨在简化PDDL(Planning Domain Definition Language)域和问题的创建。它允许开发者使用Python代码定义PDDL的谓词、动作、类型、常量等元素,然后将其转换为标准的PDDL文件,供像Fast-Downward这样的规划器执行。这种方式极大地提高了PDDL建模的效率和可维护性。

2. 旅行商问题 (TSP) 的PDDL建模

旅行商问题(Travelling Salesman Problem, TSP)是一个经典的组合优化问题:给定一系列城市和每对城市之间的距离,找到访问每个城市一次并返回起点的最短路径。在PDDL中,我们可以将TSP建模为一个规划问题。

2.1 定义问题实体与谓词

首先,我们需要定义问题的基本元素:

立即学习Python免费学习笔记(深入)”;

  • 类型 (Types): 城市可以被定义为position类型。
  • 谓词 (Predicates):
    • at(city): 表示当前规划器位于city。
    • connected(city1, city2): 表示city1和city2之间存在连接。
    • visited(city): 表示city已经被访问过。

在tsp_basic.py中,这些元素被这样定义:

from pddl.logic import Predicate, variables
from pddl.core import Domain, Problem
from pddl.action import Action
from pddl.formatter import domain_to_string, problem_to_string
from pddl.requirements import Requirements

class TSPBasic:
    def __init__(self, connections, start_city):
        self.connections = connections
        self.start_city = start_city

        unique_cities = set()
        for start, end in connections:
            unique_cities.update([start, end])
        # 将所有城市定义为position类型的变量
        self.cities = variables(" ".join(unique_cities), types=["position"])

        # 定义动作中使用的单个城市变量
        self.start = variables("start", types=["position"])[0]
        self.finish = variables("finish", types=["position"])[0]

        # 定义谓词
        self.at = Predicate("at", self.start)
        self.connected = Predicate("connected", self.start, self.finish)
        self.visited = Predicate("visited", self.finish)

        self.domain = self.create_domain()
        self.problem = self.create_problem()

2.2 定义动作:move

TSP的核心动作是move,它描述了从一个城市移动到另一个城市的过程。

  • 动作名称: move
  • 参数: start (起始城市), finish (目标城市),两者都属于position类型。
  • 前置条件 (Precondition):
    • 当前位于start城市 (at(start))。
    • start和finish之间有连接 (connected(start, finish))。
    • finish城市尚未被访问 (~self.visited(self.finish))。
  • 效果 (Effect):
    • 当前位置变为finish城市 (at(finish))。
    • finish城市被标记为已访问 (visited(finish))。
    • 不再位于start城市 (~at(start))。

3. RecursionError 的根源与解决方案

在原始代码中,move动作的效果定义如下:

# 错误的代码示例
effect="&".join([
    str(self.at(self.finish)),
    str(self.visited(self.finish)),
    "~" + str(self.at(self.start))
])

这段代码尝试通过字符串拼接来构建动作效果。然而,pddl库在内部进行类型检查和逻辑表达式解析时,期望的是pddl.logic模块提供的逻辑表达式对象(如And, Not, Predicate等),而不是简单的字符串。当传递字符串时,库的验证机制会尝试将其解析为逻辑对象,导致无限递归,最终触发RecursionError: maximum recursion depth exceeded while calling a Python object。

Programming Helper
Programming Helper

AI代码自动生成器,在AI的帮助下更快地编程

下载

正确的做法是使用pddl.logic模块提供的逻辑运算符(&表示逻辑与,~表示逻辑非)直接构建逻辑表达式。 这些运算符被重载以创建相应的PDDL逻辑结构。

# 正确的代码示例
def create_actions(self):
    move = Action(
        "move",
        parameters=[self.start, self.finish],
        precondition=self.at(self.start) &
                     self.connected(self.start, self.finish) &
                     ~self.visited(self.finish),
        effect=self.at(self.finish) & self.visited(self.finish) & ~self.at(self.start)
    )
    return [move]

通过这种方式,effect参数接收的是一个由pddl.logic对象组成的复合逻辑表达式,而不是一个字符串,从而避免了类型不匹配和递归错误。

4. 构建完整的域 (Domain) 和问题 (Problem)

4.1 构建域 (Domain)

域定义了问题的规则、类型、谓词和动作。

# tsp_basic.py 中的 create_domain 方法
def create_domain(self):
    # 明确声明所需的需求,例如STRIPS, TYPING, NEG_PRECONDITION
    requirements = [Requirements.STRIPS, Requirements.TYPING, Requirements.NEG_PRECONDITION]

    domain = Domain(
        "tsp_basic_domain",
        requirements=requirements,
        types={"position": None}, # 定义position类型
        constants=[], # 本例中没有全局常量
        predicates=[self.at, self.connected, self.visited], # 注册所有谓词
        actions=self.create_actions() # 注册所有动作
    )
    return domain

4.2 构建问题 (Problem)

问题定义了具体实例的对象、初始状态和目标状态。

# tsp_basic.py 中的 create_problem 方法
def create_problem(self):
    requirements = [Requirements.STRIPS, Requirements.TYPING]

    # 将所有唯一城市作为问题对象
    objects = self.cities

    # 定义初始状态
    init = [self.at(variables(self.start_city, types=["position"])[0])] # 初始位于起始城市
    for start, finish in self.connections:
        # 添加所有城市连接关系
        init.append(self.connected(variables(start, types=["position"])[0],
                                   variables(finish, types=["position"])[0]))

    # 定义目标状态:所有城市被访问且最终回到起始城市
    goal_conditions = [self.visited(city) for city in self.cities if city.name != self.start_city]
    goal_conditions.append(self.at(variables(self.start_city, types=["position"])[0]))
    # 同样,这里也需要使用逻辑运算符,而非字符串拼接
    goal = goal_conditions[0]
    for cond in goal_conditions[1:]:
        goal &= cond

    problem = Problem(
        "tsp_basic_problem",
        domain=self.domain,
        requirements=requirements,
        objects=objects,
        init=init,
        goal=goal
    )
    return problem

注意: 在create_problem方法中,goal的定义也需要使用pddl.logic的逻辑运算符(&)来组合多个目标条件,而非字符串拼接。上述代码已修正此问题。

5. 整合与执行

main.py负责将上述PDDL模型实例化,生成PDDL文件,并调用外部规划器(如Fast-Downward)来求解。

# main.py
from tsp_basic import TSPBasic
import subprocess

# 定义城市连接和起始城市
connections = [("Boston", "NewYork"), ("NewYork", "Boston"), ...]
start_city = "NewYork"

# 实例化TSPBasic类,它会自动构建Domain和Problem对象
tsp = TSPBasic(connections, start_city)

# 获取PDDL域和问题的字符串表示
domain_pddl_str = tsp.get_domain()
problem_pddl_str = tsp.get_problem()

# 将PDDL字符串保存到文件
with open('domain.pddl', 'w') as domain_file:
    domain_file.write(domain_pddl_str)
with open('problem.pddl', 'w') as problem_file:
    problem_file.write(problem_pddl_str)

# 定义并执行Fast-Downward命令
command = ["/home/mihai/tools/downward/fast-downward.py", "./domain.pddl", "./problem.pddl",
           "--heuristic", "h=ff()", "--search", "astar(h)"]

process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

# 检查执行结果
if process.returncode == 0:
    print("Command executed successfully!")
    print(process.stdout)
else:
    print("Error in command execution.")
    print("Error Output:\n", process.stderr)

6. 注意事项与最佳实践

  1. 始终使用pddl.logic运算符: 在定义前置条件、效果和目标时,务必使用pddl.logic模块提供的&、|、~等逻辑运算符来组合表达式,而不是手动进行字符串拼接。这是避免RecursionError的关键。
  2. 明确声明需求 (Requirements): 根据PDDL域和问题中使用的特性(如负前置条件NEG_PRECONDITION、类型TYPING等),在Domain和Problem的构造函数中明确声明相应的Requirements。这有助于规划器正确解析PDDL文件。
  3. PDDL文件检查: 如果遇到规划器错误或意外行为,首先检查生成的domain.pddl和problem.pddl文件,确保它们符合PDDL语法且表达了预期的逻辑。
  4. 变量与常量: 区分variables和constants。variables通常用于动作参数和谓词定义,而constants用于在域级别定义全局不变的对象。在问题定义中,实际的对象(如城市名称)通常通过objects参数传入,它们在PDDL中会被视为常量。
  5. 错误信息分析: 当Fast-Downward或其他规划器返回错误时,仔细阅读其标准错误输出。这些信息通常会指出PDDL语法或逻辑上的具体问题。

总结

通过Python PDDL框架构建规划域和问题能够显著提高开发效率。然而,理解其内部机制,特别是如何正确构造逻辑表达式至关重要。本文通过解决旅行商问题中的RecursionError,强调了使用pddl.logic模块提供的运算符而非字符串拼接来定义动作效果和目标的重要性。遵循这些最佳实践,可以有效地利用Python PDDL框架,构建健壮且可解析的规划模型。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

241

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

150

2025.10.17

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

107

2023.09.25

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 4.9万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号