Skip to content

Commit

Permalink
Merge pull request #16 from brown-ccv/integrate-action-library
Browse files Browse the repository at this point in the history
initial action_library integration
  • Loading branch information
hollandjg authored Sep 24, 2024
2 parents 7ae2b11 + d8a8290 commit b836c23
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 71 deletions.
12 changes: 12 additions & 0 deletions src/social_norms_trees/behavior_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class BehaviorLibrary:
def __init__(self, behaviors):
self.behaviors = behaviors

def get_behavior_by_nickname(self, nickname):
return self.behaviors.get(nickname)

def get_behavior_by_id(self, id_):
for behavior in self.behaviors.values():
if behavior.id == id_:
return behavior
return None
15 changes: 15 additions & 0 deletions src/social_norms_trees/custom_node_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import py_trees

class CustomBehavior(py_trees.behaviours.Dummy):
def __init__(self, name, id_, nickname):
super().__init__(name)
self.id_ = id_
self.nickname = nickname



# id of the behavior within the behavior library (persists)
# but also the unique id for the behavior within the tree (in case there are multiple instances of
# the behavior in one tree)


134 changes: 129 additions & 5 deletions src/social_norms_trees/mutate_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
import click
import py_trees

from datetime import datetime

from social_norms_trees.custom_node_library import CustomBehavior


T = TypeVar("T", bound=py_trees.behaviour.Behaviour)

Expand Down Expand Up @@ -337,19 +341,32 @@ def remove_node(tree: T, node: Optional[py_trees.behaviour.Behaviour] = None) ->
warnings.warn(
f"{node}'s parent is None, so we can't remove it. You can't remove the root node."
)
return tree
action_log = {}
return tree,
elif isinstance(parent_node, py_trees.composites.Composite):
parent_node.remove_child(node)
action_log = {
"type": "remove_node",
"nodes": [
{
"id_": node.id_,
"nickname": node.nickname
},
],
"timestamp": datetime.now().isoformat(),
}
else:
raise NotImplementedError()
return tree

return tree, action_log


def move_node(
tree: T,
node: Optional[py_trees.behaviour.Behaviour] = None,
new_parent: Optional[py_trees.behaviour.Behaviour] = None,
index: int = None,
internal_call: bool = False,
) -> T:
"""Exchange two behaviours in the tree
Expand All @@ -370,9 +387,24 @@ def move_node(
assert isinstance(new_parent, py_trees.composites.Composite)
assert isinstance(node.parent, py_trees.composites.Composite)

# old_parent = node.parent.name
node.parent.remove_child(node)
new_parent.insert_child(node, index)


if not internal_call:
action_log = {
"type": "move_node",
"nodes": [
{
"id": node.id_,
"nickname": node.nickname,
},
],
"timestamp": datetime.now().isoformat(),
}
return tree, action_log

return tree


Expand Down Expand Up @@ -435,7 +467,99 @@ def exchange_nodes(
node0_parent, node0_index = node0.parent, node0.parent.children.index(node0)
node1_parent, node1_index = node1.parent, node1.parent.children.index(node1)

tree = move_node(tree, node0, node1_parent, node1_index)
tree = move_node(tree, node1, node0_parent, node0_index)
tree = move_node(tree, node0, node1_parent, node1_index, True)
tree = move_node(tree, node1, node0_parent, node0_index, True)

nodes = []
if node0.__class__.__name__ != "CustomBehavior":
nodes.append(
{
"nickname": node0.name,
}
)
else:
nodes.append(
{
"id": node0.id_,
"nickname": node0.nickname
}
)

if node1.__class__.__name__ != "CustomBehavior":
nodes.append(
{
"nickname": node1.name,
}
)
else:
nodes.append(
{
"id": node1.id_,
"nickname": node1.nickname
}
)

action_log = {
"type": "exchange_nodes",
"nodes": nodes,
"timestamp": datetime.now().isoformat(),
}
return tree, action_log


def prompt_select_node(behavior_library, text):

for idx, tree_name in enumerate(behavior_library.behaviors.keys(), 1):
print(f"{idx}. {tree_name}")


node_index = click.prompt(
text=text,
type=int,
)

node_key = list(behavior_library.behaviors.keys())[node_index-1]

return behavior_library.behaviors[node_key]


def add_node(
tree: T,
behavior_library: object,
) -> T:
"""Exchange two behaviours in the tree
Examples:
>>> tree = py_trees.composites.Sequence("", False, children=[])
"""


behavior = prompt_select_node(behavior_library, f"Which behavior do you want to add?")

new_node = CustomBehavior(
name=behavior['nickname'],
id_=behavior['id'],
nickname=behavior['nickname']
)

new_parent = prompt_identify_parent_node(
tree, f"What should its parent be?", display_nodes=True
)

index = prompt_identify_child_index(new_parent)

assert isinstance(new_parent, py_trees.composites.Composite)

return tree
new_parent.insert_child(new_node, index)

action_log = {
"type": "add_node",
"node": {
"id": new_node.id_,
"nickname": new_node.nickname
},
"timestamp": datetime.now().isoformat(),
}

return tree, action_log
104 changes: 104 additions & 0 deletions src/social_norms_trees/resources.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"behavior_tree": {
"name": "Sequence A",
"type": "Sequence",
"children": [
{
"name": "Behavior A",
"type": "Behavior",
"children": []
},
{
"name": "Behavior B",
"type": "Behavior",
"children": []
},
{
"name": "Sequence B",
"type": "Sequence",
"children": [
{
"name": "Behavior C",
"type": "Behavior",
"children": []
},
{
"name": "Behavior D",
"type": "Behavior",
"children": []
}
]
},
{
"name": "Sequence C",
"type": "Sequence",
"children": [
{
"name": "Behavior E",
"type": "Behavior",
"children": []
},
{
"name": "Behavior F",
"type": "Behavior",
"children": []
},
{
"name": "Behavior G",
"type": "Behavior",
"children": []
},
{
"name": "Behavior H",
"type": "Behavior",
"children": []
}
]
}
]
},
"behavior_library":
{
"Behavior A": {
"id": "001",
"nickname": "Behavior A",
"type": "Dummy"
},
"Behavior B": {
"id": "002",
"nickname": "Behavior B",
"type": "Dummy"
},
"Behavior C": {
"id": "003",
"nickname": "Behavior C",
"type": "Dummy"
},
"Behavior D": {
"id": "004",
"nickname": "Behavior D",
"type": "Dummy"
},
"Behavior E": {
"id": "005",
"nickname": "Behavior E",
"type": "Dummy"
},
"Behavior F": {
"id": "006",
"nickname": "Behavior F",
"type": "Dummy"
},
"Behavior G": {
"id": "007",
"nickname": "Behavior G",
"type": "Dummy"
},
"Behavior H": {
"id": "008",
"nickname": "Behavior H",
"type": "Dummy"
}

}
}
23 changes: 13 additions & 10 deletions src/social_norms_trees/serialize_tree.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import py_trees
from social_norms_trees.custom_node_library import CustomBehavior


def serialize_tree(tree):
Expand All @@ -12,20 +13,22 @@ def serialize_node(node):

return serialize_node(tree)

def deserialize_tree(tree):
def deserialize_tree(tree, behavior_library):
def deserialize_node(node):
node_type = node['type']
children = [deserialize_node(child) for child in node['children']]

if node_type == 'Sequence':
return py_trees.composites.Sequence(node['name'], False, children=children)
elif node_type == 'Dummy':
return py_trees.behaviours.Dummy(node['name'])
elif node_type == 'Success':
return py_trees.behaviours.Success(node['name'])
elif node_type == 'Failure':
return py_trees.behaviours.Failure(node['name'])
elif node_type == 'Running':
return py_trees.behaviours.Running(node['name'])

elif node_type == 'Behavior':
behavior = behavior_library.get_behavior_by_nickname(node['name'])
if behavior:
return CustomBehavior(
name=behavior['nickname'],
id_=behavior['id'],
nickname=behavior['nickname']
)
else:
raise ValueError(f"Behavior {node['name']} not found in behavior library")

return deserialize_node(tree)
Loading

0 comments on commit b836c23

Please sign in to comment.