py_trees快速实践 (Python Behavior Tree)

最近的项目中涉及机器人任务执行,考虑到直接写if-else虽然前期效率很高,但是随着逻辑复杂度的增加,最后的可读性和可维护性会较差。由于项目是基于Python的,因此研究了一下py_trees这个开源项目。

Why Behavior Tree?

个人总结BT的优势如下:

1.  Model-Based. 基于一种模型的编程范式,在规则的约束下,不容易发生歧义,不会快速地随着问题的复杂度增加而陷入if-else的灾难中。

2. Reactive. 基于tick令牌的轮询运行机制,允许高优先级的事件打断正在运行的action。

3. Visualisation. 在设计上支持事件流的图形化,提高了逻辑的可读性和可维护性。

基本概念

基本概念不过多讲了,可以参看以下几篇文章:

py_trees快速实践

感觉pt_trees的Github教程对初学者不是很友好,没有给出马上就能上手的代码片段。本文的快速实践路线如下:

1. 环境搭建

由于py_trees已经在PyPi上,可直接激活一个虚拟环境,然后下载即可:

:~$ conda activate base
:~$ pip install py_trees

2. 拷贝demo程序到本地文件,或者直接到github demo目录下载。

3. 先运行demo程序,看一看demo打印的介绍,回到相应的章节看下对应模块的介绍。

4. 仔细阅读demo程序,看一下随着tick的流转,终端打印的流转过程。

5. 遇到疑惑再回过头看相应章节的模块介绍,分析清楚为什么终端打印出行为树的状态是这样流转的。

6. 尝试改下运行的tick数,函数形参如memory等配置,再看看事件是怎样流转的.。

按照上述的流程,运行demo,通过命令行日志输出观察事件的流转,相信你会加深对Behavior Tree的理解。个人跑下来的感觉是这个范式设计的还是很巧妙的,难怪出了好多书,但是去看py_trees的代码,代码量又不是很大。另外,运行demo,分析日志的过程也是蛮有趣的。

py-trees-demo-selector

最后以py-trees-demo-selector这个demo为例演示下这个过程:

#!/usr/bin/env python
# py-trees-demo-selector.py
# coding: utf-8
#
# License: BSD
#   https://raw.githubusercontent.com/splintered-reality/py_trees/devel/LICENSE
#
##############################################################################
# Documentation
##############################################################################

"""
A py_trees demo.

.. argparse::
   :module: py_trees.demos.selector
   :func: command_line_argument_parser
   :prog: py-trees-demo-selector

.. graphviz:: dot/demo-selector.dot

.. image:: images/selector.gif

"""
##############################################################################
# Imports
##############################################################################

import argparse
import sys
import time
import typing

import py_trees
import py_trees.console as console

##############################################################################
# Classes
##############################################################################


def description() -> str:
    """
    Print description and usage information about the program.

    Returns:
       the program description string
    """
    content = (
        "Higher priority switching and interruption in the children of a selector.\n"
    )
    content += "\n"
    content += "In this example the higher priority child is setup to fail initially,\n"
    content += "falling back to the continually running second child. On the third\n"
    content += (
        "tick, the first child succeeds and cancels the hitherto running child.\n"
    )
    if py_trees.console.has_colours:
        banner_line = console.green + "*" * 79 + "\n" + console.reset
        s = banner_line
        s += console.bold_white + "Selectors".center(79) + "\n" + console.reset
        s += banner_line
        s += "\n"
        s += content
        s += "\n"
        s += banner_line
    else:
        s = content
    return s


def epilog() -> typing.Optional[str]:
    """
    Print a noodly epilog for --help.

    Returns:
       the noodly message
    """
    if py_trees.console.has_colours:
        return (
            console.cyan
            + "And his noodly appendage reached forth to tickle the blessed...\n"
            + console.reset
        )
    else:
        return None


def command_line_argument_parser() -> argparse.ArgumentParser:
    """
    Process command line arguments.

    Returns:
        the argument parser
    """
    parser = argparse.ArgumentParser(
        description=description(),
        epilog=epilog(),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "-r", "--render", action="store_true", help="render dot tree to file"
    )
    return parser


def create_root() -> py_trees.behaviour.Behaviour:
    """
    Create the root behaviour and it's subtree.

    Returns:
        the root behaviour
    """
    root = py_trees.composites.Selector(name="Selector", memory=False)
    ffs = py_trees.behaviours.StatusQueue(
        name="FFS",
        queue=[
            py_trees.common.Status.FAILURE,
            py_trees.common.Status.FAILURE,
            py_trees.common.Status.SUCCESS,
        ],
        eventually=py_trees.common.Status.SUCCESS,
    )
    always_running = py_trees.behaviours.Running(name="Running")
    root.add_children([ffs, always_running])
    return root


##############################################################################
# Main
##############################################################################


def main() -> None:
    """Entry point for the demo script."""
    args = command_line_argument_parser().parse_args()
    print(description())
    py_trees.logging.level = py_trees.logging.Level.DEBUG

    root = create_root()

    ####################
    # Rendering
    ####################
    if args.render:
        py_trees.display.render_dot_tree(root)
        sys.exit()

    ####################
    # Execute
    ####################
    root.setup_with_descendants()
    for i in range(1, 4):
        try:
            print("\n--------- Tick {0} ---------\n".format(i))
            root.tick_once()
            print("\n")
            print(py_trees.display.unicode_tree(root=root, show_status=True))
            time.sleep(1.0)
        except KeyboardInterrupt:
            break
    print("\n")
main()

运行程序,有如下打印:

可以看到,第一个FFS action的成功打断了第二个Running action的运行。如果尝试将py_trees.composites.Selector(name="Selector", memory=False)代码中的memory改成True,会发现RUNING状态的action将不会再被打断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值