py_trees.composites module

Composites (multi-child) types for behaviour trees.

Composites are responsible for directing the path traced through the tree on a given tick (execution). They are the factories (Sequences and Parallels) and decision makers (Selectors) of a behaviour tree.

Composite behaviours typically manage children and apply some logic to the way they execute and return a result, but generally don’t do anything themselves. Perform the checks or actions you need to do in the non-composite behaviours.

Most any desired functionality can be authored with a combination of these three composites. In fact, it is precisely this feature that makes behaviour trees attractive - it breaks down complex decision making logic to just three primitive elements. It is possible and often desirable to extend this set with custom composites of your own, but think carefully before you do - in almost every case, a combination of the existing composites will serve and as a result, you will merely compound the complexity inherent in your tree logic. This this makes it confoundingly difficult to design, introspect and debug. As an example, design sessions often revolve around a sketched graph on a whiteboard. When these graphs are composed of just five elements (Selectors, Sequences, Parallels, Decorators and Behaviours), it is very easy to understand the logic at a glance. Double the number of fundamental elements and you may as well be back at the terminal parsing code.

Tip

You should never need to subclass or create new composites.

The basic operational modes of the three composites in this library are as follows:

  • Selector: execute a child based on cascading priorities

  • Sequence: execute children sequentially

  • Parallel: execute children concurrently

This library does provide some flexibility in how each composite is implemented without breaking the fundamental nature of each (as described above). Selectors and Sequences can be configured with or without memory (resumes or resets if children are RUNNING) and the results of a parallel can be configured to wait upon all children completing, succeed on one, all or a subset thereof.

Tip

Follow the links in each composite’s documentation to the relevant demo programs.

class py_trees.composites.Composite(name: str, children: Sequence[Behaviour] | None = None)

Bases: Behaviour, ABC

The parent class to all composite behaviours.

Args:

name (str): the composite behaviour name children ([Behaviour]): list of children to add

add_child(child: Behaviour) UUID

Add a child.

Args:

child: child to add

Raises:

TypeError: if the child is not an instance of Behaviour RuntimeError: if the child already has a parent

Returns:

unique id of the child

add_children(children: List[Behaviour]) Behaviour

Append a list of children to the current list.

Args:

children ([Behaviour]): list of children to add

insert_child(child: Behaviour, index: int) UUID

Insert child at the specified index.

This simply directly calls the python list’s insert method using the child and index arguments.

Args:

child (Behaviour): child to insert index (int): index to insert it at

Returns:

uuid.UUID: unique id of the child

prepend_child(child: Behaviour) UUID

Prepend the child before all other children.

Args:

child: child to insert

Returns:

uuid.UUID: unique id of the child

remove_all_children() None

Remove all children. Makes sure to stop each child if necessary.

remove_child(child: Behaviour) int

Remove the child behaviour from this composite.

Args:

child: child to delete

Returns:

index of the child that was removed

remove_child_by_id(child_id: UUID) None

Remove the child with the specified id.

Args:

child_id: unique id of the child

Raises:

IndexError: if the child was not found

replace_child(child: Behaviour, replacement: Behaviour) None

Replace the child behaviour with another.

Args:

child: child to delete replacement: child to insert

stop(new_status: Status = Status.INVALID) None

Provide common stop-level functionality for all composites.

The latter situation can arise for some composites, but more importantly, will always occur when high higher priority behaviour interrupts this one.

Args:

new_status: behaviour will transition to this new status

abstract tick() Iterator[Behaviour]

Tick the composite.

All composite subclasses require a re-implementation of the tick method to provide the logic for managing multiple children (tick() merely provides default logic for when there are no children).

tip() Behaviour | None

Recursive function to extract the last running node of the tree.

Returns:

the tip function of the current child of this composite or None

update() Status

Unused update method.

Composites should direct the flow, whilst behaviours do the real work.

Such flows are a consequence of how the composite interacts with it’s children. The success of behaviour trees depends on this logic being simple, well defined and limited to a few well established patterns - this is what ensures that visualising a tree enables a user to quickly grasp the decision making captured therein.

For the standard patterns, this logic is limited to the ordering of execution and logical inferences on the resulting status of the composite’s children.

This is a good guideline to adhere to (i.e. don’t reach inside children to inference on custom variables, nor reach out to the system your tree is attached to).

Implementation wise, this renders the update() method redundant as all customisation to create a simple, well defined composite happens in the tick() method.

Bottom line, composites do not make use of this method. Implementing it for subclasses of the core composites will not do anything.

class py_trees.composites.Parallel(name: str, policy: Base, children: Sequence[Behaviour] | None = None)

Bases: Composite

Parallels enable a kind of spooky at-a-distance concurrency.

A parallel ticks every child every time the parallel is itself ticked. The parallelism however, is merely conceptual. The children have actually been sequentially ticked, but from both the tree and the parallel’s purview, all children have been ticked at once.

The parallelism too, is not true in the sense that it kicks off multiple threads or processes to do work. Some behaviours may kick off threads or processes in the background, or connect to existing threads/processes. The behaviour itself however, merely monitors these and is itself encosced in a py_tree which only ever ticks in a single-threaded operation.

Policies SuccessOnAll and SuccessOnSelected may be configured to be synchronised in which case children that tick with SUCCESS will be skipped on subsequent ticks until the policy criteria is met, or one of the children returns status FAILURE.

Parallels with policy SuccessOnSelected will check in both the setup() and tick() methods to to verify the selected set of children is actually a subset of the children of this parallel.

See also

  • Context Switching Demo

setup(**kwargs: int) None

Detect before ticking whether the policy configuration is invalid.

Args:
**kwargs (dict): distribute arguments to this

behaviour and in turn, all of it’s children

Raises:

RuntimeError: if the parallel’s policy configuration is invalid Exception: be ready to catch if any of the children raise an exception

stop(new_status: Status = Status.INVALID) None

Ensure that any running children are stopped.

Args:

new_status : the composite is transitioning to this new status

tick() Iterator[Behaviour]

Tick over the children.

Yields:

Behaviour: a reference to itself or one of its children

Raises:

RuntimeError: if the policy configuration was invalid

validate_policy_configuration() None

Validate the currently stored policy.

Policy configuration can be invalid if: * Policy is SuccessOnSelected and no behaviours have been specified * Policy is SuccessOnSelected and behaviours that are not children exist

Raises:

RuntimeError: if policy configuration was invalid

class py_trees.composites.Selector(name: str, memory: bool, children: Sequence[Behaviour] | None = None)

Bases: Composite

Selectors are the decision makers.

A selector executes each of its child behaviours in turn until one of them succeeds (at which point it itself returns RUNNING or SUCCESS, or it runs out of children at which point it itself returns FAILURE. We usually refer to selecting children as a means of choosing between priorities. Each child and its subtree represent a decreasingly lower priority path.

Note

Switching from a low -> high priority branch causes a stop(INVALID) signal to be sent to the previously executing low priority branch. This signal will percolate down that child’s own subtree. Behaviours should make sure that they catch this and destruct appropriately.

Note

If configured with memory, higher priority checks will be skipped when a child returned with running on the previous tick. i.e. once a priority is locked in, it will run to completion and can only be interrupted if the selector is interrupted by higher priorities elsewhere in the tree.

See also

The py-trees-demo-selector-program program demos higher priority switching under a selector.

Args:
memory (bool): if RUNNING on the previous tick,

resume with the RUNNING child

name (str): the composite behaviour name children ([Behaviour]): list of children to add

stop(new_status: Status = Status.INVALID) None

Ensure that children are appropriately stopped and update status.

Args:

new_status : the composite is transitioning to this new status

tick() Iterator[Behaviour]

Customise the tick behaviour for a selector.

This implements priority-interrupt style handling amongst the selector’s children. The selector’s status is always a reflection of it’s children’s status.

Yields:

Behaviour: a reference to itself or one of its children

class py_trees.composites.Sequence(name: str, memory: bool, children: Sequence[Behaviour] | None = None)

Bases: Composite

Sequences are the factory lines of behaviour trees.

A sequence will progressively tick over each of its children so long as each child returns SUCCESS. If any child returns FAILURE or RUNNING the sequence will halt and the parent will adopt the result of this child. If it reaches the last child, it returns with that result regardless.

Note

The sequence halts once it engages with a child is RUNNING, remaining behaviours are not ticked.

Note

If configured with memory and a child returned with running on the previous tick, it will proceed directly to the running behaviour, skipping any and all preceding behaviours. With memory is useful for moving through a long running series of tasks. Without memory is useful if you want conditional guards in place preceding the work that you always want checked off.

See also

The py-trees-demo-sequence-program program demos a simple sequence in action.

Args:

name: the composite behaviour name memory: if RUNNING on the previous tick,

resume with the RUNNING child

children: list of children to add

stop(new_status: Status = Status.INVALID) None

Ensure that children are appropriately stopped and update status.

Args:

new_status : the composite is transitioning to this new status

tick() Iterator[Behaviour]

Tick over the children.

Yields:

Behaviour: a reference to itself or one of its children