Skip to content

Commit

Permalink
Add behavior tree code (#21)
Browse files Browse the repository at this point in the history
* copy in code

* copy in code

* fix copyright header comment

* __init__ files don't need main()

* mark broken test as broken

* add docs

* fix imports and add docstrings

* add todos

* update docstrings

* update docstrings

* add bt doc page

* add pytest stanza

* refine pacman demo and add to installer

* continue to refine pacman demo and docs

* move global vars into class vars and blackboard

* refactor blackboard into its own module

* minor refactor

* add type hints

* fix some warnings

* docs refinement

* add robot demo to docs

* add type hint

* add diagram and sample output

* roll in more unit tests

* add message simulation

* rename blackboard class

* add vultron simulator bot

* fix unit test

* add cvd proto demo

* use dataclass for blackboard

* make report to others a fuzzer

* add context manager to help unwind where exceptions are coming from

* fix test

* working out bugs in cvd_proto simulator

* eliminate separate discovery_capability attribute

* add cvd role

* move fuzzer things to fuzzer module

* fix unit tests

* turn on other simulator features

* add ADR folder, exclude from site publication

* add unit tests for basic CS conditions

* ignore node.js stuff

* make conditions test cases work

* make transition test cases work

* clean up obsoleted code

* add some docs

* add demo output

* record decisions to build a bt tree python module

* update requirements.txt

* add report management test module

* test basic conditions

* update docstring

* add tests for compound nodes

* add tests for rm states.py

* add tests for rm transitions.py

* replace ActorState with mock class in test

* format with black

* update docstrings

* add to_graph method to BtNode

* add node graph printer

rename module internal classes with _ prefix
update docstrings

* add tests for message conditions

* add log msg test

* add cs message tests

* clean up tests

* remove old experimental code

* rename internal classes

* add factory methods to consistently generate common node types

* start using factory methods instead of subclasses

* add adr with explanation of factory method usage

* more factory stuff

* fix string format

* fix type hint

* improve factory methods

* update demos to use factory

* add state_in factory

* use condition_check factory

* update use of state_in factory

* update use of factories

* update use of factories

* update use of factories

* use factories

* use factories

* use factories

* use factories

* unit tests for factories

* keep track of q_cs history

* change demo names

* catch potential attribute error when no dm function set

* markdownlint -fix

* markdownlint --fix

* markdown lint fixes

* add vultron.bt docs to nav

* add example

* unify command line demo

* unify command line demo

* reformat with black

* markdownlint
  • Loading branch information
ahouseholder authored Nov 17, 2023
1 parent 6282a3b commit c8a90e1
Show file tree
Hide file tree
Showing 145 changed files with 13,649 additions and 172 deletions.
62 changes: 62 additions & 0 deletions docs/adr/0002-model-processes-with-behavior-trees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
status: accepted
date: 2023-10-23
deciders: adh
---
# Model Processes with Behavior Trees

## Context and Problem Statement

The Vultron protocol is defined as a set of three interacting state machines for each agent:

- Report Management
- Embargo Management
- Case State Management

We have a need to simulate the behavior of these state machines in order to test the protocol.
However, the interactions between them can be complex, so we need a way to model those interactions.

## Decision Drivers

- Need to simulate complex interactions between state machines
- Need to explore and exercise state space of the protocol even if some states are rarely reached in practice

## Considered Options

- Object-oriented Agent implementation
- Behavior Tree Agent implementation

## Decision Outcome

Chosen option: "Behavior Tree Agent implementation", because behavior trees allow us to model complex interactions
between state machines. By building in stochastic behaviors, they also allow us to explore and exercise the state space
of the protocol even if some states are rarely reached in practice.

### Consequences

- Behavior trees are a new technology for the team, so there will be a learning curve.
- Simulating changes to process logic should be easier with behavior trees than with object-oriented code.

## Pros and Cons of the Options

### Object-oriented implementation

Good, because:

- Standard OO python approach understood by the team
- State management could be implemented as a set of classes for each state machine

Bad, because:

- Complex interactions between state machines would be difficult to model and maintain
- Reactive behaviors (e.g., based on state changes in outside world) would be difficult to model

## More Information

- Michele Colledanchise, Petter Ögren: Behavior Trees in Robotics and AI
- [book @ Amazon](https://www.amazon.com/Behavior-Trees-Robotics-Introduction-Intelligence/dp/1138593737)
- [pdf @ arXiv](https://arxiv.org/abs/1709.00084)
- Petter Ögren's YouTube channel has a number of good videos on Behavior Trees
- <https://www.youtube.com/@petterogren7535>
- Wikipedia
- [Behavior Tree (artificial intelligence, robotics and control)](https://en.wikipedia.org/wiki/Behavior_tree_(artificial_intelligence,_robotics_and_control))
85 changes: 85 additions & 0 deletions docs/adr/0003-build-custom-python-bt-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
status: accepted
date: 2023-10-23
deciders: adh
---
# Build our own Behavior Tree engine in Python

## Context and Problem Statement

We need a Behavior Tree engine to support our Behavior Tree Agent implementation.

## Decision Drivers

- Need to support stochastic behaviors
- Team is most familiar with Python
- Need to support quick prototyping and experimentation

## Considered Options

- BehaviorTree.CPP
- py_trees
- Build our own

## Decision Outcome

Chosen option: "Build our own", because

- It's a good opportunity to learn about Behavior Trees
- py_trees is closely tied to robot operating system (ROS), so it might not be a good fit for our use case
- BehaviorTree.CPP is written in C++, so it would be difficult to integrate with the rest of our Python codebase

This decision can/should be revisited if

- the complexity of building our own Behavior Tree engine becomes too high
- we discover that existing Behavior Tree engines already support features that would be difficult for us to build

In order to mitigate the risk of building our own Behavior Tree engine, we will maintain a modular design
that allows us to swap out our engine for an existing engine if necessary.

### Consequences

Good, because:

- Provides an opportunity to learn more about Behavior Trees
- We can build it in Python, so it should be easy to integrate with the rest of our codebase
- We can build it in a way that supports stochastic behaviors
- We can customize it to our needs

Neutral, because:

- We might discover that we need features that are already supported by existing Behavior Tree engines
- We might find that other Behavior Tree engines are better suited to our needs

Bad, because:

- We have to build and maintain our own Behavior Tree engine
- It's another dependency we have to own and maintain

## Pros and Cons of the Options

### BehaviorTree.CPP

Good, because:

- It's a mature, well-tested Behavior Tree engine

Bad, because:

- It's written in C++, so it would be difficult to integrate with the rest of our Python codebase

### py_trees

Good, because:

- It's written in Python, so it should be easy to integrate with the rest of our codebase
- It seems to be a mature, well-tested Behavior Tree engine

Neutral-to-bad, because:

- It's closely tied to robot operating system (ROS), so it might not be a good fit for our use case

## More Information

- [BehaviorTree.CPP](https://www.behaviortree.dev/) [GitHub](https://github.com/BehaviorTree/BehaviorTree.CPP)
- [py_trees](https://py-trees.readthedocs.io/en/devel/) [GitHub](https://github.com/splintered-reality/py_trees)
70 changes: 70 additions & 0 deletions docs/adr/0004-use-factory-methods-for-common-bt-node-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
# These are optional elements. Feel free to remove any of them.
status: accepted
date: 2023-10-24
deciders: adh
---
# Use factory methods for common BT node types

## Context and Problem Statement

We have a number of common BT node types that are used in multiple trees. We want to be able to create these nodes
in a consistent way, and we want to be able to easily change the implementation of these nodes without having to
change the code that creates them.

## Decision Drivers

* We want to be able to create these nodes in a consistent way
* Preserve future flexibility to change the underlying BT node implementation without having to change the code that creates them

## Considered Options

* No factory methods, directly subclass the BT node types
* Use factory methods

## Decision Outcome

Chosen option: "Use factory methods", because it allows us to create the nodes in a consistent way, and it allows us to
change the underlying implementation without having to change the code that creates them.

### Consequences

Good because:

* retains flexibility to change the underlying implementation without having to change the code that creates them
* allows us to create the nodes in a consistent way
* allows us to keep the `vultron.bt.base` module clean and focused on the base classes
* allows us to keep the rest of the `vultron.bt` module focused on Vultron-specific needs

Neutral because:

* Adds a central place to maintain the factory methods

Bad because:

* less pythonic than just subclassing the BT node types

## Pros and Cons of the Options

### No factory methods

Good because:

* more pythonic
*

Neutral because:

Bad because:

* Harder to enforce consistency in how the nodes are created

## More Information

This decision was inspired in part by the `py_trees` [documentation](https://py-trees.readthedocs.io/en/devel/the_crazy_hospital.html)
(in the context of composite nodes):

> Don’t subclass merely to auto-populate it, build a create_<xyz>_subtree() library instead
Which got us thinking about using factory methods to help maintain a clean separation between the `vultron.bt.base`
module and the things that live above it.
15 changes: 15 additions & 0 deletions docs/reference/code/bt/behavior_trees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Behavior Trees

::: vultron.bt

::: vultron.bt.base

::: vultron.bt.behaviors

::: vultron.bt.fuzzer

::: vultron.bt.states

::: vultron.bt.errors

::: vultron.bt.common
13 changes: 13 additions & 0 deletions docs/reference/code/bt/bt_base.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Behavior Tree Base

The Vultron behavior tree base module defines the basic building blocks of any behavior tree.
This page covers the basic Tree and Blackboard classes, as well as the basic error classes.
Detailed descriptions of Node types are provided in [Behavior Tree Basic Node Types](bt_base_nodes.md).

::: vultron.bt.base

::: vultron.bt.base.bt

::: vultron.bt.base.blackboard

::: vultron.bt.base.errors
15 changes: 15 additions & 0 deletions docs/reference/code/bt/bt_base_nodes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Behavior Tree Basic Node Types

We have defined a number of basic node types.
These are the building blocks of any behavior tree.
They are defined in the `vultron.bt.base` module.

::: vultron.bt.base.node_status

::: vultron.bt.base.bt_node

::: vultron.bt.base.composites

::: vultron.bt.base.decorators

::: vultron.bt.base.fuzzer
5 changes: 5 additions & 0 deletions docs/reference/code/bt/case_states.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Vultron Case State Behaviors

::: vultron.bt.case_state.conditions

::: vultron.bt.case_state.transitions
80 changes: 80 additions & 0 deletions docs/reference/code/bt/pacman_demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Pacman Bot Behavior Tree Demo

This is a demo of a behavior tree for a game playing bot.
It has no practical use for Vultron, rather it's an introduction to how to use the behavior tree
framework we've built.

We're providing this as an example to show how to build a behavior tree that can be used
to implement some context-aware behavior.

It implements a simplified Pacman game with a bot that eats pills and avoids ghosts.

## Behaviors

If no ghosts are nearby, Pacman eats one pill per tick.
But if a ghost is nearby, Pacman will chase it if it is scared, otherwise he will avoid it.
If he successfully avoids a ghost, he will eat a pill.
The game ends when Pacman has eaten all the pills or has been eaten by a ghost.

## Scoring

Scoring is as follows:

- 10 points per pill
- 200 points for the first ghost, 400 for the second, 800 for the third, 1600 for the fourth

There are 240 pills on the board. The max score is

$$(240 \times 10) + (200 + 400 + 800 + 1600) = 5400$$

## Differences from the real thing

If the game exceeds 1000 ticks, Pacman gets bored and quits (but statistically that should never happen).

We did not model power pellets, fruit, or the maze. Ghosts just randomly get scared and then randomly
stop being scared. Ghosts and pills are just counters. Ghost location is just a random "nearby" check.

## The Behavior Tree

The tree structure is shown below.

{% include-markdown './pacman_tree_diagram.md' %}

Legend:

| Symbol | Meaning |
|----------| ------- |
| ? | FallbackNode |
| &rarr; | SequenceNode |
| &#8645; | Invert |
| &#9648; | ActionNode |
| &#11052; | ConditionNode |
| &#127922; | Fuzzer node (randomly succeeds or fails some percentage of the time) |

## Demo Output

!!! example

```shell
# if vultron package is installed
# run the demo
$ vultrabot --pacman

# print tree and exit
$ vultrabot --pacman --print-tree

# if vultron package is not installed
$ python -m vultron.bt.base.demo.pacman
```

When the tree is run, it will look something like this:

```text
{% include-markdown './pacman_tree_example.txt' %}
```

## Demo Code

::: vultron.bt.base.demo.pacman
options:
heading_level: 3
36 changes: 36 additions & 0 deletions docs/reference/code/bt/pacman_tree_diagram.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
```mermaid
graph TD
MaybeEatPills_1["&rarr; MaybeEatPills"]
MaybeChaseOrAvoidGhost_2["? MaybeChaseOrAvoidGhost"]
MaybeEatPills_1 --> MaybeChaseOrAvoidGhost_2
NoMoreGhosts_3["#8645; NoMoreGhosts"]
MaybeChaseOrAvoidGhost_2 --> NoMoreGhosts_3
GhostsRemain_4["#11052; GhostsRemain"]
NoMoreGhosts_3 --> GhostsRemain_4
NoGhostClose_5["#8645; NoGhostClose"]
MaybeChaseOrAvoidGhost_2 --> NoGhostClose_5
GhostClose_6["#127922; GhostClose"]
NoGhostClose_5 --> GhostClose_6
ChaseOrAvoidGhost_7["? ChaseOrAvoidGhost"]
MaybeChaseOrAvoidGhost_2 --> ChaseOrAvoidGhost_7
ChaseIfScared_8["&rarr; ChaseIfScared"]
ChaseOrAvoidGhost_7 --> ChaseIfScared_8
GhostsScared_9["#11052; GhostsScared"]
ChaseIfScared_8 --> GhostsScared_9
ChaseGhost_10["#127922; ChaseGhost"]
ChaseIfScared_8 --> ChaseGhost_10
CaughtGhost_11["&rarr; CaughtGhost"]
ChaseIfScared_8 --> CaughtGhost_11
DecrGhostCount_12["#9648; DecrGhostCount"]
CaughtGhost_11 --> DecrGhostCount_12
ScoreGhost_13["#9648; ScoreGhost"]
CaughtGhost_11 --> ScoreGhost_13
IncrGhostScore_14["#9648; IncrGhostScore"]
CaughtGhost_11 --> IncrGhostScore_14
GhostsScared_15["#11052; GhostsScared"]
ChaseOrAvoidGhost_7 --> GhostsScared_15
AvoidGhost_16["#127922; AvoidGhost"]
ChaseOrAvoidGhost_7 --> AvoidGhost_16
EatPill_17["#9648; EatPill"]
MaybeEatPills_1 --> EatPill_17
```
Loading

0 comments on commit c8a90e1

Please sign in to comment.