Giter Site home page Giter Site logo

async-btree's Introduction

Async Behaviour Tree for Python

Unix Build Status Coverage Status Codacy Badge Scrutinizer Code Quality PyPI Version PyPI License

Versions following Semantic Versioning

See documentation.

Overview

What's a behavior tree ?

Unlike a Finite State Machine, a Behaviour Tree is a tree of hierarchical nodes that controls the flow of decision and the execution of "tasks" or, as we will call them further, "Actions". -- behaviortree

If your new (or not) about behavior tree, you could spend some time on this few links:

Few implementation libraries:

  • task_behavior_engine A behavior tree based task engine written in Python
  • pi_trees a Python/ROS library for implementing Behavior Trees
  • pr_behavior_tree A simple python behavior tree library based on coroutines
  • btsk Behavior Tree Starter Kit
  • behave A behavior tree implementation in Python

Why another library so ?

SIMPLICITY

When you study behavior tree implementation, reactive node, dynamic change, runtime execution, etc ... At a moment you're build more or less something that mimic an evaluator 'eval/apply' or a compilator, with a complex hierachical set of class.

All complexity came with internal state management, using tree of blackboard to avoid global variable, multithreading issue, maybe few callback etc ...

This break the simplicity and beauty of your initial design.

What I find usefull with behavior tree:

  • clarity of expression
  • node tree representation
  • possibility to reuse behavior
  • add external measure to dynamicaly change a behavior, a first step on observable pattern...

As I've used OOP for years (very long time), I will try to avoid class tree and prefer using the power of functionnal programming to obtain what I want: add metadata on a sematic construction, deal with closure, use function in parameters or in return value...

And a last reason, more personal, it that i would explore python expressivity.

SO HOW ?

In this module, I purpose you to use the concept of coroutines, and their mecanisms to manage the execution flow. By this way:

  • we reuse simple language idiom to manage state, parameter, etc
  • no design constraint on action implementation
  • most of language build block could be reused

You could build expression like this:

async def a_func():
    """A great function"""
    return "a"

async def b_decorator(child_value, other=""):
    """A great decorator..."""
    return f"b{child_value}{other}"

assert run(decorate(a_func, b_decorator)) == "ba"

This expression apply b_decorator on function a_func. Note that decorate(a_func, b_decorator) is not an async function, only action, or condition are async function.

Few guidelines of this implementation:

  • In order to mimic all NodeStatus (success, failure, running), I replace this by truthy/falsy meaning of evaluation value. A special dedicated exception decorate standard exception in order to give them a Falsy meaning (ControlFlowException). By default, exception are raised like happen usually until you catch them.
  • Blackboard pattern, act as a manager of context variable for behavior tree. With python 3, please... simply use contextvars !
  • In order to be able to build a sematic tree, I've introduce a metadata tuple added on function implementation.

The rest is just implementation details..

A little note:

You should not use this until you're ready to think about what you're doing :)

Note about 'async' framework

As we use async function as underlaying mechanism to manage the execution flow, the standard library asyncio is pretty fine. But, (always a but somewhere isn't it...), you should read this amazing blog post by Nathaniel J. Smith. And next study curio framework in deep.

As curio say:

Don't Use Curio if You're Allergic to Curio

Personaly, after few time of testing and reading curio code, I'm pretty addict.

If curio is not present, we default to asyncio.

Installation

Install this library directly into an activated virtual environment with pip or Poetry :

  • python -m pip install async-btree or 'poetry add async-btree
  • with with curio extention: python -m pip install async-btree[curio] or 'poetry add async-btree[curio]

Usage

After installation, the package can imported:

$ python
>>> import async_btree
>>> async_btree.__version__

See API Reference documentation.

With this framework, you didn't find any configuration file, no Xml, no json, no yaml.

The main reason (oriented and personal point of view) is that you did not need to introduce an extra level of abtraction to declare a composition of functions. I think it's true for most of main use case (except using an editor to wrote behaviour tree for example).

So "If you wrote your function with python, wrote composition in python"... (remember that you did not need XML to do SQL, just write good sql...)

So, the goal is to:

  • define your business function wich implements actions or conditions, with all test case that you wish/need
  • compose them using those provided by this framework like sequence, selector, ...
  • use them as it is or create a well define python module to reuse them

Wanna style have an abtract tree of our behaviour tree ?

Functions from async-btree build an abstract tree for you. If you lookup in code, you should see an annotation "node_metadata" on internal implementation. This decorator add basic information like function name, parameters, and children relation ship.

This abstract tree can be retreived and stringified with analyze and stringify_analyze. Here the profile:

  def analyze(target: CallableFunction) -> Node: # here we have our "abtract tree code"
    ...

For example:

# your behaviour tree, or a sub tree:
my_func = alias(child=repeat_until(child=action(hello), condition=success_until_zero), name="btree_1")

# retrieve meta information and build a Node tree
abstract_tree_tree_1 = analyze(my_func) 

# output the tree:
print(stringify_analyze(abstract_tree_tree_1))

This should print:

 --> btree_1:
     --(child)--> repeat_until:
         --(condition)--> success_until_zero:
         --(child)--> action:
                      target: hello

Note about action and condition method:

  • you could use sync or async function
  • you could specify a return value with SUCCESS or FAILURE
  • function with no return value will be evaluated as FAILURE until you decorate them with a always_successor always_failure

See this example/tutorial_1.py for more information.

async-btree's People

Contributors

dependabot[bot] avatar geronimo-iia avatar oliverguhr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

async-btree's Issues

TODO Code

  • module common definition (typing)
  • module of meta manipulation
  • define submodule for decorator, leaf etc
  • add more example in docs
  • ensure tests and coverage

decision nodes success tree is not evaluted

Describe the bug
I am sure I missed something... I tried to extend the example and add a decision node together with the context. The data in the context is correct and the condition is evaluated - however, the success (or failiure) function is not executed.

Code to Reproduce

import curio
import async_btree as bt
import contextvars


def some_action():
    print("continue here...")

async def say_hello(name: str):
    print(f"Hello: {name}")

def is_name_set():
    print("current name is " + name.get())
    return name.get() != ""

name = contextvars.ContextVar('name',default="")

greet_with_name = bt.decision(is_name_set, 
        bt.always_success(child=bt.action(target=say_hello, name=name.get()))        
        )

b_tree = bt.sequence(
    children=[
        greet_with_name,        
        bt.always_success(child=bt.action(target=some_action)),        
    ]
)

if __name__ == '__main__':
       
    name = contextvars.ContextVar('name')
    name.set("Hans")    
  
    curio.run(b_tree)

    abstract_tree_tree_1 = bt.analyze(b_tree) 
    # output the tree:
    print(bt.stringify_analyze(abstract_tree_tree_1))

Expected behavior
The decision node should evaluate the success_tree / failure_tree methods.

Thank you in advance.

Feature Request: Abstract Tree code from source

This may be implemented or contrary to your design goals, so feel free to ignore it.

Behavior Tree is a well known Behavior Tree package. That project uses XML to define the tree. The source c++ relies on extending base classes. This allows easy re-use of the logic.

From the async-btree documentation is appears the python functions are decorated directly with the tree. If so, it may be practical to abstract them from each other.

Fix poetry 1.0.2 make

Describe the bug
Travis error on make install

Expected behavior
aim a successful travis make install

Additional context
Setting settings.virtualenvs.in-project does not exist
-> remove use of this config entry

How could it raise error rather than catch?

code

import curio
import async_btree as bt
def div_zero():
    1/0

result = curio.run(bt.action(div_zero))
assert result is not None

question

curio run normaly and return None, but I except it panic !

expect

The code panic and report trackstack rather than normal running.

Documentation Request

When appropriate, I suggest you could re-create the first tutorial on BehaviorTree using async-btree. I am far too new to the package, but I have included my attempt here.

import async_btree as bt

print("Btreeeeeeee!!!")

async def approach_object():
    """
    A basic node that seems to always accomplish what it sets out to do.
    :return: async_btree.SUCCESS
    """
    return bt.SUCCESS

#async def approach_tree(child_value, other=""):
async def approach_tree():
    """
    Create the tree within Python using existing functions
    :return: bt.sequence()
    """
    status = approach_object()
    await status
    return bt.sequence(children=[approach_object()], succes_threshold=2)


if __name__=="__main__":
    print("Attempting approach")
    seq = approach_tree()

I know this is wrong and I will attempt to fix it. I do think a good example on the doc site would be useful.

Thank you!

update dependencies

  • curio has been released in version > 1
  • we can use pytest-curio to execute test unit

Design question: should a decision with no failure tree fail?

I am running a bunch of decisions in a sequence. Each decision only checks a condition and executes a function if the condition is true. If the decision has no failure tree and the condition fails, the decision will be return failure and stop the execution of the sequence.

I solved this issue for me with this "hack". This way the decision will be successful if the condition fails.

some_decision  = bt.decision(condition=i_fail,
        success_tree=bt.always_success(some_action),         
        failure_tree=bt.always_success(lambda x: True)         
        )

The question is: Should a missing failure_tree result in a failing decision? Since there is no action that actually failed, the default could be also "success".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.