diff --git a/src/social_norms_trees/action_library.py b/src/social_norms_trees/action_library.py deleted file mode 100644 index 3e1f29b6..00000000 --- a/src/social_norms_trees/action_library.py +++ /dev/null @@ -1,73 +0,0 @@ -class BehaviorLibrary: - def __init__(self): - self.behaviors = { - "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", - }, - - # these will be the new behaviors that will be possible to "add" - # as part of new action4 , "add node" - # TODO: for now, show all actions, and user will just choose the new acton to add - - "Behavior I": { - "id": "009", - "nickname": "Behavior I", - "type": "Dummy", - }, - "Behavior J": { - "id": "010", - "nickname": "Behavior J", - "type": "Dummy", - }, - "Behavior K": { - "id": "011", - "nickname": "Behavior K", - "type": "Dummy", - }, - } - - 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 \ No newline at end of file diff --git a/src/social_norms_trees/behavior_library.py b/src/social_norms_trees/behavior_library.py new file mode 100644 index 00000000..9ba37d24 --- /dev/null +++ b/src/social_norms_trees/behavior_library.py @@ -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 \ No newline at end of file diff --git a/src/social_norms_trees/custom_node_library.py b/src/social_norms_trees/custom_node_library.py index 78ea6b29..9194cebd 100644 --- a/src/social_norms_trees/custom_node_library.py +++ b/src/social_norms_trees/custom_node_library.py @@ -1,9 +1,9 @@ import py_trees class CustomBehavior(py_trees.behaviours.Dummy): - def __init__(self, name, id, nickname): + def __init__(self, name, id_, nickname): super().__init__(name) - self.id_ = id + self.id_ = id_ self.nickname = nickname diff --git a/src/social_norms_trees/mutate_tree.py b/src/social_norms_trees/mutate_tree.py index 7f652be2..163daba5 100644 --- a/src/social_norms_trees/mutate_tree.py +++ b/src/social_norms_trees/mutate_tree.py @@ -10,6 +10,8 @@ from datetime import datetime +from social_norms_trees.custom_node_library import CustomBehavior + T = TypeVar("T", bound=py_trees.behaviour.Behaviour) @@ -502,4 +504,62 @@ def exchange_nodes( "nodes": nodes, "timestamp": datetime.now().isoformat(), } - return tree, action_log \ No newline at end of file + 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) + + 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 diff --git a/src/social_norms_trees/resources.json b/src/social_norms_trees/resources.json new file mode 100644 index 00000000..78363361 --- /dev/null +++ b/src/social_norms_trees/resources.json @@ -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" + } + + } +} diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index 1410c0dd..ebd745d4 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -6,10 +6,10 @@ import uuid import py_trees -from social_norms_trees.mutate_tree import move_node, exchange_nodes, remove_node +from social_norms_trees.mutate_tree import move_node, exchange_nodes, remove_node, add_node from social_norms_trees.serialize_tree import serialize_tree, deserialize_tree -from social_norms_trees.action_library import BehaviorLibrary +from social_norms_trees.behavior_library import BehaviorLibrary DB_FILE = "db.json" @@ -29,19 +29,16 @@ def save_db(db, db_file): with open(db_file, "w") as f: json.dump(db, f, indent=4) -def experiment_setup(db, behavior_library): +def experiment_setup(db, origin_tree): print("\n") participant_id = participant_login() - print("\n") - origin_tree = load_behavior_tree(behavior_library) - experiment_id = initialize_experiment_record(db, participant_id, origin_tree) print("\nSetup Complete.\n") - return participant_id, origin_tree, experiment_id + return participant_id, experiment_id def participant_login(): @@ -51,81 +48,27 @@ def participant_login(): return participant_id +def load_resources(file_path): + try: + print(f"\nLoading behavior tree and behavior library from {file_path}...\n") + with open(file_path, 'r') as file: + resources = json.load(file) + + except json.JSONDecodeError: + raise ValueError("Error") + except Exception: + raise RuntimeError("Error") -behavior_trees = { - "tree_1": { - "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": [] - } - ] - } - ] - }, -} - - -def load_behavior_tree(behavior_library): - for idx, tree_name in enumerate(behavior_trees.keys(), 1): - print(f"{idx}. {tree_name}") - - selected_index = click.prompt("Please select a behavior tree to load for the experiment (enter the number)", type=int) - - tree_config = behavior_trees[f"tree_{selected_index}"] - - experiment_ready_tree = deserialize_tree(tree_config, behavior_library) - - return experiment_ready_tree + behavior_tree = resources.get('behavior_tree') + behaviors = resources.get('behavior_library') + + behavior_library = BehaviorLibrary(behaviors) + + behavior_tree = deserialize_tree(behavior_tree, behavior_library) + + print("Loading success.") + return behavior_tree, behavior_library def initialize_experiment_record(db, participant_id, origin_tree): @@ -146,7 +89,7 @@ def initialize_experiment_record(db, participant_id, origin_tree): return experiment_id -def run_experiment(db, participant_id, origin_tree, experiment_id): +def run_experiment(db, origin_tree, experiment_id, behavior_library): # Loop for the actual experiment part, which takes user input to decide which action to take print("\nExperiment beginning...\n") @@ -165,8 +108,9 @@ def run_experiment(db, participant_id, origin_tree, experiment_id): "1. move node\n" + "2. exchange node\n" + "3. remove node\n" + + "4. add node\n" + "Please select an action to perform on the behavior tree", - type=click.Choice(['1', '2', '3'], case_sensitive=False), + type=click.Choice(['1', '2', '3', '4'], case_sensitive=False), show_choices=True ) @@ -181,6 +125,10 @@ def run_experiment(db, participant_id, origin_tree, experiment_id): origin_tree, action_log = remove_node(origin_tree) db[experiment_id]["action_history"].append(action_log) + elif action == "4": + origin_tree, action_log = add_node(origin_tree, behavior_library) + db[experiment_id]["action_history"].append(action_log) + else: print("Invalid choice, please select a valid number (1, 2, or 3).\n") @@ -196,7 +144,6 @@ def run_experiment(db, participant_id, origin_tree, experiment_id): def main(): print("AIT Prototype #1 Simulator") - behavior_library = BehaviorLibrary() #TODO: define a input file, that will have the original tree and also the behavior library #TODO: write up some context, assumptions made in the README @@ -204,9 +151,13 @@ def main(): DB_FILE = "db.json" db = load_db(DB_FILE) + #load tree to run experiment on, and behavior library + + RESOURCES_FILE = "resources.json" + original_tree, behavior_library = load_resources(RESOURCES_FILE) - participant_id, origin_tree, experiment_id = experiment_setup(db, behavior_library) - db = run_experiment(db, participant_id, origin_tree, experiment_id) + participant_id, experiment_id = experiment_setup(db, original_tree) + db = run_experiment(db, original_tree, experiment_id, behavior_library) save_db(db, DB_FILE)