From 1f272e43599c706f50f377a194a0b04796aa60b0 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Tue, 8 Oct 2024 14:38:12 +0000 Subject: [PATCH 01/40] add start of atomic mutations file --- src/social_norms_trees/atomic_mutations.py | 844 +++++++++++++++++++++ 1 file changed, 844 insertions(+) create mode 100644 src/social_norms_trees/atomic_mutations.py diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py new file mode 100644 index 0000000..8a9b744 --- /dev/null +++ b/src/social_norms_trees/atomic_mutations.py @@ -0,0 +1,844 @@ +from collections import namedtuple +import inspect +from functools import wraps +from itertools import islice +import logging +import sys +from types import GenericAlias +from typing import Callable, List, Tuple, TypeVar, Union, Dict + +import click +import py_trees +import typer + +_logger = logging.getLogger(__name__) + +# ============================================================================= +# Argument types +# ============================================================================= + +ExistingNode = TypeVar("ExistingNode", bound=py_trees.behaviour.Behaviour) +NewNode = TypeVar("NewNode", bound=py_trees.behaviour.Behaviour) +CompositeIndex = TypeVar( + "CompositeIndex", bound=Tuple[py_trees.composites.Composite, int] +) + +# ============================================================================= +# Atomic operations +# ============================================================================= + +# The very top line of each operation's docstring is used as the +# description of the operation in the UI, so it's required. +# The argument annotations are vital, because they tell the UI which prompt +# to use. + + +def remove(node: ExistingNode) -> ExistingNode: + """Remove a node. + Examples: + >>> tree = py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Success(), + ... failure := py_trees.behaviours.Failure()]) + >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + [-] + --> Success + --> Failure + >>> removed = remove(failure) + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE + [-] + --> Success + """ + if node.parent is None: + msg = ( + f"%s's parent is None, so we can't remove it. We can't remove the root node." + % (node) + ) + raise ValueError(msg) + elif isinstance(node.parent, py_trees.composites.Composite): + node.parent.remove_child(node) + else: + raise NotImplementedError() + return node + + +def insert(node: NewNode, where: CompositeIndex) -> None: + """Insert a new node. + Examples: + >>> tree = py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Success() + ... ]) + >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + [-] + --> Success + >>> insert(py_trees.behaviours.Failure(), (tree, 1)) + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE + [-] + --> Success + --> Failure + >>> insert(py_trees.behaviours.Dummy(), (tree, 0)) + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE + [-] + --> Dummy + --> Success + --> Failure + """ + parent, index = where + parent.insert_child(node, index) + return + + +def move( + node: ExistingNode, + where: CompositeIndex, +) -> None: + """Move a node. + Examples: + >>> tree = py_trees.composites.Sequence("", False, children=[ + ... failure_node := py_trees.behaviours.Failure(), + ... success_node := py_trees.behaviours.Success(), + ... ]) + >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + [-] + --> Failure + --> Success + >>> move(failure_node, (tree, 1)) + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE + [-] + --> Success + --> Failure + >>> move(failure_node, (tree, 1)) + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE + [-] + --> Success + --> Failure + """ + parent, index = where + insert(remove(node), (parent, index)) + return + + +def exchange( + node0: ExistingNode, + node1: ExistingNode, +) -> None: + """Exchange two nodes. + Examples: + >>> tree = py_trees.composites.Sequence("", False, children=[ + ... s := py_trees.behaviours.Success(), + ... f := py_trees.behaviours.Failure(), + ... ]) + >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + [-] + --> Success + --> Failure + >>> exchange(s, f) + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE + [-] + --> Failure + --> Success + >>> tree = py_trees.composites.Sequence("", False, children=[ + ... a:= py_trees.composites.Sequence("A", False, children=[ + ... py_trees.behaviours.Dummy() + ... ]), + ... py_trees.composites.Sequence("B", False, children=[ + ... py_trees.behaviours.Success(), + ... c := py_trees.composites.Sequence("C", False, children=[]) + ... ]) + ... ]) + >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + [-] + [-] A + --> Dummy + [-] B + --> Success + [-] C + >>> exchange(a, c) + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE + [-] + [-] C + [-] B + --> Success + [-] A + --> Dummy + >>> tree = py_trees.composites.Sequence("", False, children=[ + ... py_trees.composites.Sequence("1", False, children=[ + ... a := py_trees.behaviours.Dummy("A") + ... ]), + ... py_trees.composites.Sequence("2", False, children=[ + ... b := py_trees.behaviours.Dummy("B") + ... ]) + ... ]) + >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + [-] + [-] 1 + --> A + [-] 2 + --> B + >>> exchange(a, b) + >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + [-] + [-] 1 + --> B + [-] 2 + --> A + """ + + node0_parent, node0_index = node0.parent, node0.parent.children.index( + node0) + node1_parent, node1_index = node1.parent, node1.parent.children.index( + node1) + + move(node0, (node1_parent, node1_index)) + move(node1, (node0_parent, node0_index)) + + return + + +# ============================================================================= +# Node and Position Selectors +# ============================================================================= + + +def iterate_nodes(tree: py_trees.behaviour.Behaviour): + """ + Examples: + >>> list(iterate_nodes(py_trees.behaviours.Dummy())) # doctest: +ELLIPSIS + [] + >>> list(iterate_nodes( + ... py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Dummy(), + ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + [, + ] + >>> list(iterate_nodes( + ... py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Dummy(), + ... py_trees.behaviours.Dummy(), + ... py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Dummy(), + ... ]), + ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + [, + , + , + , + ] + """ + yield tree + for child in tree.children: + yield from iterate_nodes(child) + + +def enumerate_nodes(tree: py_trees.behaviour.Behaviour): + """ + Examples: + >>> list(enumerate_nodes(py_trees.behaviours.Dummy())) # doctest: +ELLIPSIS + [(0, )] + >>> list(enumerate_nodes( + ... py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Dummy(), + ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + [(0, ), + (1, )] + >>> list(enumerate_nodes( + ... py_trees.composites.Sequence("s1", False, children=[ + ... py_trees.behaviours.Dummy(), + ... py_trees.behaviours.Success(), + ... py_trees.composites.Sequence("s2", False, children=[ + ... py_trees.behaviours.Dummy(), + ... ]), + ... py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Failure(), + ... py_trees.behaviours.Periodic("p", n=1), + ... ]), + ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + [(0, ), + (1, ), + (2, ), + (3, ), + (4, ), + (5, ), + (6, ), + (7, )] + """ + return enumerate(iterate_nodes(tree)) + + +def label_tree_lines( + tree: py_trees.behaviour.Behaviour, + labels: List[str], + representation=py_trees.display.unicode_tree, +) -> str: + """Label the lines of a tree. + Examples: + >>> print(label_tree_lines(py_trees.behaviours.Dummy(), labels=["0"])) + 0: --> Dummy + >>> tree = py_trees.composites.Sequence( + ... "S1", + ... False, + ... children=[ + ... py_trees.behaviours.Dummy(), + ... py_trees.behaviours.Dummy()] + ... ) + >>> print(label_tree_lines(tree, labels=["A", "B", "C"])) + A: [-] S1 + B: --> Dummy + C: --> Dummy + >>> print(label_tree_lines(tree, labels=["AAA", "BB", "C"])) + AAA: [-] S1 + BB: --> Dummy + C: --> Dummy + """ + max_len = max([len(s) for s in labels]) + padded_labels = [s.rjust(max_len) for s in labels] + + tree_representation_lines = representation(tree).split("\n") + enumerated_tree_representation_lines = [ + f"{i}: {t}" for i, t in zip(padded_labels, tree_representation_lines) + ] + + output = "\n".join(enumerated_tree_representation_lines) + return output + + +# TODO: Split each of these functions into one which +# returns a labeled representation of the tree, +# a mapping of allowed values to nodes and +# a separate function which does the prompting. +# This should help testing. + + +def prompt_identify_node( + tree: py_trees.behaviour.Behaviour, + message: str = "Which node?", + display_nodes: bool = True, +) -> py_trees.behaviour.Behaviour: + + key_node_mapping = {str(i): n for i, n in enumerate_nodes(tree)} + labels = key_node_mapping.keys() + + if display_nodes: + text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" + else: + text = f"{message}" + + key = click.prompt(text=text, type=click.Choice(labels)) + + node = key_node_mapping[key] + + return node + + +def prompt_identify_position( + tree: py_trees.behaviour.Behaviour, + message: str = "Which position?", + display_nodes: bool = True, +) -> Tuple[py_trees.composites.Composite, int]: + """ + Example: + >>> s1 = py_trees.composites.Sequence("S1", False, children=[py_trees.behaviours.Dummy()]) + >>> prompt_identify_position(s1) + [-] S1 + 0: --> + --> Dummy + 1: --> + >>> s2 = py_trees.composites.Sequence("S2", False, children=[py_trees.behaviours.Failure()]) + >>> tree = py_trees.composites.Sequence("S0", False, children=[s1, s2]) + >>> prompt_identify_position(tree) + """ + """ + Better Options: + Option 0: + [-] S0 + 0: -----> + [-] S1 + 1: -------> + --> Dummy + 2: -------> + 3: -----> + [-] S2 + 4: -------> + --> Failure + 5: -------> + 6: -----> + Option 1: + [-] S0 + 0: -----> + [-] S1 + 0.0: -------> + --> Dummy + 0.1: -------> + 1: -----> + [-] S2 + 1.0: -------> + --> Failure + 1.1: -------> + 2: -----> + Option 2: + [-] S0 + --> {0} + [-] S1 + --> {0.0} + --> Dummy + --> {0.1} + --> {1} + [-] S2 + --> {1.0} + --> Failure + --> {1.1} + --> {2} + Option 3: + [-] S0 + --> {1} + [-] S1 + --> {2} + --> Dummy + --> {3} + --> {4} + [-] S2 + --> {5} + --> Failure + --> {6} + --> {7} + Option 4: + [-] S0 + --> {⚡️ 1} + [-] S1 + --> {⚡️ 2} + --> Dummy + --> {⚡️ 3} + --> {⚡️ 4} + [-] S2 + --> {⚡️ 5} + --> Failure + --> {⚡️ 6} + --> {⚡️ 7} + Option 5: + Where: [before/after] + Which: [user types display name] + Option 6: + [-] S0 + {⚡️ 1} <-- + [-] S1 + {⚡️ 2} <-- + --> Dummy + {⚡️ 3} <-- + {⚡️ 4} <-- + [-] S2 + {⚡️ 5} <-- + --> Failure + {⚡️ 6} <-- + {⚡️ 7} <-- + Option 7: + [-] S0 + {⚡️ 1} + [-] S1 + {⚡️ 2} + --> Dummy + {⚡️ 3} + {⚡️ 4} + [-] S2 + {⚡️ 5} + --> Failure + {⚡️ 6} + {⚡️ 7} + Option 8: + [-] S0 + --> _1 + [-] S1 + --> _2 + --> Dummy + --> _3 + --> _4 + [-] S2 + --> _5 + --> Failure + --> _6 + --> _7 + Option 9: + [-] S0 + 1: --> _ + [-] S1 + 2: --> _ + --> Dummy + 3: --> _ + 4: --> _ + [-] S2 + 5: --> _ + --> Failure + 6: --> _ + 7: --> _ + Option 10: + [-] S0 + 1: --> ______ + [-] S1 + 2: --> ______ + --> Dummy + 3: --> ______ + 4: --> ______ + [-] S2 + 5: --> ______ + --> Failure + 6: --> ______ + 7: --> ______ + Option 11: Preview + Chose a "Success" node: + [-] S0 + 1: --> *Success* + [-] S1 + 2: --> *Success* + --> Dummy + 3: --> *Success* + 4: --> *Success* + [-] S2 + 5: --> *Success* + --> Failure + 6: --> *Success* + 7: --> *Success* + """ + key_node_mapping = {str(i): n for i, n in enumerate_nodes(tree)} + labels = [] + for key, node in key_node_mapping.items(): + if isinstance(node, py_trees.composites.Composite): + labels.append(key) + else: + labels.append("_") + + if display_nodes: + text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" + else: + text = f"{message}" + + key = click.prompt(text=text, type=click.Choice(labels)) + + node = key_node_mapping[key] + + return node + + +def prompt_identify_composite( + tree: py_trees.behaviour.Behaviour, + message: str = "Which composite node?", + display_nodes: bool = True, + skip_label: str = "_" +) -> Tuple[py_trees.composites.Composite, int]: + """ + Example: + >>> s1 = py_trees.composites.Sequence("S1", False, children=[py_trees.behaviours.Dummy()]) + >>> prompt_identify_composite(s1) + [-] S1 + 0: --> + --> Dummy + 1: --> + >>> s2 = py_trees.composites.Sequence("S2", False, children=[py_trees.behaviours.Failure()]) + >>> tree = py_trees.composites.Sequence("S0", False, children=[s1, s2]) + >>> prompt_identify_position(tree) + """ + key_node_mapping = {str(i): n for i, n in enumerate_nodes(tree)} + labels = [] + for key, node in key_node_mapping.items(): + if isinstance(node, py_trees.composites.Composite): + labels.append(key) + else: + labels.append(skip_label) + if display_nodes: + text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" + else: + text = f"{message}" + allowed_labels = [l for l in labels if l != skip_label] + key = click.prompt(text=text, type=click.Choice(allowed_labels)) + node = key_node_mapping[key] + return node + + +def prompt_identify_child_index( + tree: py_trees.behaviour.Behaviour, + message: str = "Which position?", + display_nodes: bool = True, + skip_label: str = "_", +) -> int: + labels = [] + i = 0 + for node in iterate_nodes(tree): + if node in tree.children: + labels.append(str(i)) + i += 1 + else: + labels.append(skip_label) + labels.append(str(i)) + + if display_nodes: + text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" + else: + text = f"{message}" + allowed_labels = [l for l in labels if l != skip_label] + key = click.prompt(text=text, type=click.Choice(allowed_labels)) + node_index = int(key) + + return node_index + + +def format_children_with_indices(composite: py_trees.composites.Composite) -> str: + """ + Examples: + >>> tree = py_trees.composites.Sequence("s1", False, children=[ + ... py_trees.behaviours.Dummy(), + ... py_trees.behaviours.Success(), + ... s2 := py_trees.composites.Sequence("s2", False, children=[ + ... py_trees.behaviours.Dummy(), + ... ]), + ... s3 := py_trees.composites.Sequence("", False, children=[ + ... py_trees.behaviours.Failure(), + ... py_trees.behaviours.Periodic("p", n=1), + ... ]), + ... ]) + >>> print(format_children_with_indices(tree)) # doctest: +NORMALIZE_WHITESPACE + _: [-] s1 + 0: --> Dummy + 1: --> Success + 2: [-] s2 + _: --> Dummy + 3: [-] + _: --> Failure + _: --> p + >>> print(format_children_with_indices(s2)) # doctest: +NORMALIZE_WHITESPACE + _: [-] s2 + 0: --> Dummy + >>> print(format_children_with_indices(s3)) # doctest: +NORMALIZE_WHITESPACE + _: [-] + 0: --> Failure + 1: --> p + """ + index_strings = [] + i = 0 + for b in iterate_nodes(composite): + if b in composite.children: + index_strings.append(str(i)) + i += 1 + else: + index_strings.append("_") + + output = label_tree_lines(composite, index_strings) + return output + + +def prompt_identify_library_node( + library, message: str = "Which action from the library?", display_nodes: bool = True +) -> py_trees.behaviour.Behaviour: + key_node_mapping = {str(i): n for i, n in enumerate(library)} + + if display_nodes: + text = f"{format_library_with_indices(library)}\n{message}" + else: + text = f"{message}" + index = click.prompt(text=text, type=click.Choice(key_node_mapping.keys())) + + node = key_node_mapping[index] + + return node + + +def format_library_with_indices(library: List[py_trees.behaviour.Behaviour]): + return "\n".join([f"{i}: {n.name}" for i, n in enumerate(library)]) + + +# ============================================================================= +# User Interface +# ============================================================================= + + +# Wrapper functions for the atomic operations which give them a UI. +MutationResult = namedtuple( + "MutationResult", ["result", "tree", "function", "kwargs"]) + + +def mutate_chooser(*fs: Union[Callable], message="Which action?"): + """Prompt the user to choose one of the functions f. + Returns the wrapped version of the function. + """ + n_fs = len(fs) + docstring_summaries = [f_.__doc__.split("\n")[0] for f_ in fs] + text = ( + "\n".join( + [ + f"{i}: {docstring_summary}" + for i, docstring_summary in enumerate(docstring_summaries) + ] + ) + + f"\n{message}" + ) + i = click.prompt(text=text, type=click.IntRange(0, n_fs - 1)) + f = mutate_ui(fs[i]) + + return f + + +def mutate_ui( + f, +) -> Callable[[py_trees.behaviour.Behaviour, List[py_trees.behaviour.Behaviour]], Dict]: + """Factory function for a tree mutator UI. + This creates a version of the atomic function `f` + which prompts the user for the appropriate arguments + based on `f`'s type annotations. + """ + + signature = inspect.signature(f) + + @wraps(f) + def f_inner(tree, library): + kwargs = {} + for parameter_name in signature.parameters.keys(): + annotation = signature.parameters[parameter_name].annotation + _logger.debug(f"getting arguments for {annotation=}") + value = prompt_get_mutate_arguments(annotation, tree, library) + kwargs[parameter_name] = value + inner_result = f(**kwargs) + return_value = MutationResult( + result=inner_result, tree=tree, function=f, kwargs=kwargs + ) + return return_value + + return f_inner + + +def prompt_get_mutate_arguments(annotation: GenericAlias, tree, library): + """Prompt the user to specify nodes and positions in the tree.""" + annotation_ = str(annotation) + assert isinstance(annotation_, str) + + if annotation_ == str(inspect.Parameter.empty): + _logger.debug("in empty") + msg = "Can't work out what argument %s should be" % annotation + raise ValueError(msg) + elif annotation_ == str(ExistingNode): + _logger.debug("in ExistingNode") + node = prompt_identify_node(tree) + return node + elif annotation_ == str(CompositeIndex): + _logger.debug("in CompositeIndex") + composite_node = prompt_identify_composite( + tree, message="Which parent?") + index = prompt_identify_child_index(composite_node) + return composite_node, index + elif annotation_ == str(NewNode): + _logger.debug("in NewNode") + new_node = prompt_identify_library_node( + library, "Which node from the library?") + return new_node + else: + _logger.debug("in 'else'") + msg = "Can't work out what to do with %s" % annotation + raise NotImplementedError(msg) + + +# ============================================================================= +# Utility functions +# ============================================================================= + + +class QuitException(Exception): + pass + + +def quit(): + """Finish the experiment.""" + raise QuitException("User quit the experiment.") + + +# ============================================================================= +# Main Loop +# ============================================================================= + + +def load_experiment(): + """Placeholder function for loading a tree and library (should come from a file).""" + tree = py_trees.composites.Sequence( + "S0", + False, + children=[ + py_trees.behaviours.Dummy("A"), + py_trees.composites.Sequence( + "S1", + memory=False, + children=[ + py_trees.behaviours.Dummy("B"), + py_trees.behaviours.Dummy("C"), + py_trees.behaviours.Dummy("D"), + ], + ), + py_trees.composites.Selector( + "S2", + memory=False, + children=[ + py_trees.behaviours.Dummy("E"), + py_trees.behaviours.Dummy("F"), + py_trees.behaviours.Failure(), + ], + ), + py_trees.behaviours.Success(), + ], + ) + library = [py_trees.behaviours.Success(), py_trees.behaviours.Failure()] + return tree, library + + +def save_results(tree, protocol): + _logger.info("saving results") + print(f"protocol: {protocol}") + print(f"tree:\n{py_trees.display.unicode_tree(tree)}") + + +app = typer.app() + + +@app.command() +def main(): + logging.basicConfig(level=logging.DEBUG) + tree, library = load_experiment() + protocol = [] + exit_code = None + + try: + print(py_trees.display.ascii_tree(tree)) + + # The main loop of the experiment + while f := mutate_chooser(insert, move, exchange, remove, quit): + # try: + results = f(tree, library) + _logger.debug(results) + protocol.append(results) + print(py_trees.display.ascii_tree(tree)) + + # If we have any errors raised by the function, like wrong values, + # we don't want to crash. + # except (ValueError, IndexError, NotImplementedError) as e: + # _logger.error(f"\n{e}") + # continue + + # If the user calls the "quit" function, then we want to exit + except QuitException as e: + _logger.debug(e) + exit_code = 0 + + # If the user does a keyboard interrupt, then we want to exit + except click.exceptions.Abort: + print("\n") + _logger.error("Program aborted.") + exit_code = 1 + + # Save the results + finally: + _logger.debug("finally") + save_results(tree, protocol) + sys.exit(exit_code) + + +if __name__ == "__main__": + app() From dcc8480b2dbfc1e16e1e7c95ea8ee1e58a3151ac Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Tue, 8 Oct 2024 14:40:57 +0000 Subject: [PATCH 02/40] reformat --- norms/wsocket.py | 1 + src/social_norms_trees/atomic_mutations.py | 17 ++++++--------- src/social_norms_trees/ui_wrapper.py | 25 ++++++++++++---------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/norms/wsocket.py b/norms/wsocket.py index b339bbe..ad37930 100644 --- a/norms/wsocket.py +++ b/norms/wsocket.py @@ -14,5 +14,6 @@ async def main(): async with serve(echo, "localhost", 8765): await asyncio.Future() # run forever + if __name__ == "__main__": asyncio.run(main()) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 8a9b744..3b014b9 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -190,10 +190,8 @@ def exchange( --> A """ - node0_parent, node0_index = node0.parent, node0.parent.children.index( - node0) - node1_parent, node1_index = node1.parent, node1.parent.children.index( - node1) + node0_parent, node0_index = node0.parent, node0.parent.children.index(node0) + node1_parent, node1_index = node1.parent, node1.parent.children.index(node1) move(node0, (node1_parent, node1_index)) move(node1, (node0_parent, node0_index)) @@ -527,7 +525,7 @@ def prompt_identify_composite( tree: py_trees.behaviour.Behaviour, message: str = "Which composite node?", display_nodes: bool = True, - skip_label: str = "_" + skip_label: str = "_", ) -> Tuple[py_trees.composites.Composite, int]: """ Example: @@ -655,8 +653,7 @@ def format_library_with_indices(library: List[py_trees.behaviour.Behaviour]): # Wrapper functions for the atomic operations which give them a UI. -MutationResult = namedtuple( - "MutationResult", ["result", "tree", "function", "kwargs"]) +MutationResult = namedtuple("MutationResult", ["result", "tree", "function", "kwargs"]) def mutate_chooser(*fs: Union[Callable], message="Which action?"): @@ -723,14 +720,12 @@ def prompt_get_mutate_arguments(annotation: GenericAlias, tree, library): return node elif annotation_ == str(CompositeIndex): _logger.debug("in CompositeIndex") - composite_node = prompt_identify_composite( - tree, message="Which parent?") + composite_node = prompt_identify_composite(tree, message="Which parent?") index = prompt_identify_child_index(composite_node) return composite_node, index elif annotation_ == str(NewNode): _logger.debug("in NewNode") - new_node = prompt_identify_library_node( - library, "Which node from the library?") + new_node = prompt_identify_library_node(library, "Which node from the library?") return new_node else: _logger.debug("in 'else'") diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index de1f3ba..88c9e88 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -45,8 +45,7 @@ def experiment_setup(db, origin_tree): print("\n") participant_id = participant_login() - experiment_id = initialize_experiment_record( - db, participant_id, origin_tree) + experiment_id = initialize_experiment_record(db, participant_id, origin_tree) print("\nSetup Complete.\n") @@ -61,8 +60,7 @@ def participant_login(): def load_resources(file_path): try: - print( - f"\nLoading behavior tree and behavior library from {file_path}...\n") + print(f"\nLoading behavior tree and behavior library from {file_path}...\n") with open(file_path, "r") as file: resources = json.load(file) @@ -137,8 +135,7 @@ def run_experiment(db, origin_tree, experiment_id, behavior_library): db[experiment_id]["action_history"].append(action_log) elif action == 4: - origin_tree, action_log = add_node( - origin_tree, behavior_library) + origin_tree, action_log = add_node(origin_tree, behavior_library) db[experiment_id]["action_history"].append(action_log) else: @@ -167,9 +164,16 @@ def run_experiment(db, origin_tree, experiment_id, behavior_library): @app.command() def main( - resources_file: Annotated[pathlib.Path, typer.Argument(help="file with the experimental context, behavior tree, and behavior library")], - db_file: Annotated[pathlib.Path, typer.Option( - help="file where the experimental results will be written")] = "db.json", + resources_file: Annotated[ + pathlib.Path, + typer.Argument( + help="file with the experimental context, behavior tree, and behavior library" + ), + ], + db_file: Annotated[ + pathlib.Path, + typer.Option(help="file where the experimental results will be written"), + ] = "db.json", ): print("AIT Prototype #1 Simulator") @@ -179,8 +183,7 @@ def main( # load tree to run experiment on, and behavior library - original_tree, behavior_library, context_paragraph = load_resources( - resources_file) + original_tree, behavior_library, context_paragraph = load_resources(resources_file) print(f"\nContext of this experiment: {context_paragraph}") participant_id, experiment_id = experiment_setup(db, original_tree) From 843fc5b4a09bf7953f8ca24e525434e425692c7f Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Tue, 8 Oct 2024 14:43:29 +0000 Subject: [PATCH 03/40] fix typer --- src/social_norms_trees/atomic_mutations.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 3b014b9..b1e3f70 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -190,8 +190,10 @@ def exchange( --> A """ - node0_parent, node0_index = node0.parent, node0.parent.children.index(node0) - node1_parent, node1_index = node1.parent, node1.parent.children.index(node1) + node0_parent, node0_index = node0.parent, node0.parent.children.index( + node0) + node1_parent, node1_index = node1.parent, node1.parent.children.index( + node1) move(node0, (node1_parent, node1_index)) move(node1, (node0_parent, node0_index)) @@ -653,7 +655,8 @@ def format_library_with_indices(library: List[py_trees.behaviour.Behaviour]): # Wrapper functions for the atomic operations which give them a UI. -MutationResult = namedtuple("MutationResult", ["result", "tree", "function", "kwargs"]) +MutationResult = namedtuple( + "MutationResult", ["result", "tree", "function", "kwargs"]) def mutate_chooser(*fs: Union[Callable], message="Which action?"): @@ -720,12 +723,14 @@ def prompt_get_mutate_arguments(annotation: GenericAlias, tree, library): return node elif annotation_ == str(CompositeIndex): _logger.debug("in CompositeIndex") - composite_node = prompt_identify_composite(tree, message="Which parent?") + composite_node = prompt_identify_composite( + tree, message="Which parent?") index = prompt_identify_child_index(composite_node) return composite_node, index elif annotation_ == str(NewNode): _logger.debug("in NewNode") - new_node = prompt_identify_library_node(library, "Which node from the library?") + new_node = prompt_identify_library_node( + library, "Which node from the library?") return new_node else: _logger.debug("in 'else'") @@ -790,7 +795,7 @@ def save_results(tree, protocol): print(f"tree:\n{py_trees.display.unicode_tree(tree)}") -app = typer.app() +app = typer.Typer() @app.command() From 58abee7ab3213f02cd455be4b8bcc4996e27dece Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Tue, 8 Oct 2024 14:44:56 +0000 Subject: [PATCH 04/40] remove unused import --- src/social_norms_trees/atomic_mutations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index b1e3f70..b6d8c7b 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -1,7 +1,6 @@ from collections import namedtuple import inspect from functools import wraps -from itertools import islice import logging import sys from types import GenericAlias From 74377a3230b2fdbf7101ba71cff58a30e49cc6d0 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 10:39:11 +0000 Subject: [PATCH 05/40] add new typevars --- src/social_norms_trees/atomic_mutations.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index b6d8c7b..31bc937 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -4,7 +4,7 @@ import logging import sys from types import GenericAlias -from typing import Callable, List, Tuple, TypeVar, Union, Dict +from typing import Callable, List, Mapping, NamedTuple, Tuple, TypeVar, Union, Dict import click import py_trees @@ -21,6 +21,12 @@ CompositeIndex = TypeVar( "CompositeIndex", bound=Tuple[py_trees.composites.Composite, int] ) +BehaviorIdentifier = TypeVar( + "BehaviorIdentifier", bound=Union[ExistingNode, NewNode, CompositeIndex]) +BehaviorTreeNode = TypeVar( + "BehaviorTreeNode", bound=py_trees.behaviour.Behaviour) +BehaviorTree = TypeVar("BehaviorTree", bound=BehaviorTreeNode) +BehaviorLibrary = TypeVar("BehaviorLibrary", bound=List[BehaviorTreeNode]) # ============================================================================= # Atomic operations From 45999ba06a7204978d9645a8a68c5ed791a9a9fd Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 10:40:42 +0000 Subject: [PATCH 06/40] fix atomic doctests --- src/social_norms_trees/atomic_mutations.py | 41 +++++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 31bc937..31bf4e5 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -44,7 +44,8 @@ def remove(node: ExistingNode) -> ExistingNode: >>> tree = py_trees.composites.Sequence("", False, children=[ ... py_trees.behaviours.Success(), ... failure := py_trees.behaviours.Failure()]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE [-] --> Success --> Failure @@ -73,15 +74,18 @@ def insert(node: NewNode, where: CompositeIndex) -> None: >>> tree = py_trees.composites.Sequence("", False, children=[ ... py_trees.behaviours.Success() ... ]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE [-] --> Success + >>> insert(py_trees.behaviours.Failure(), (tree, 1)) >>> print(py_trees.display.ascii_tree(tree)) ... # doctest: +NORMALIZE_WHITESPACE [-] --> Success --> Failure + >>> insert(py_trees.behaviours.Dummy(), (tree, 0)) >>> print(py_trees.display.ascii_tree(tree)) ... # doctest: +NORMALIZE_WHITESPACE @@ -105,10 +109,13 @@ def move( ... failure_node := py_trees.behaviours.Failure(), ... success_node := py_trees.behaviours.Success(), ... ]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE [-] --> Failure --> Success + >>> move(failure_node, (tree, 1)) >>> print(py_trees.display.ascii_tree(tree)) ... # doctest: +NORMALIZE_WHITESPACE @@ -116,6 +123,7 @@ def move( --> Success --> Failure >>> move(failure_node, (tree, 1)) + >>> print(py_trees.display.ascii_tree(tree)) ... # doctest: +NORMALIZE_WHITESPACE [-] @@ -137,16 +145,20 @@ def exchange( ... s := py_trees.behaviours.Success(), ... f := py_trees.behaviours.Failure(), ... ]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE [-] --> Success --> Failure + >>> exchange(s, f) >>> print(py_trees.display.ascii_tree(tree)) ... # doctest: +NORMALIZE_WHITESPACE [-] --> Failure --> Success + >>> tree = py_trees.composites.Sequence("", False, children=[ ... a:= py_trees.composites.Sequence("A", False, children=[ ... py_trees.behaviours.Dummy() @@ -156,13 +168,16 @@ def exchange( ... c := py_trees.composites.Sequence("C", False, children=[]) ... ]) ... ]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE [-] [-] A --> Dummy [-] B --> Success [-] C + >>> exchange(a, c) >>> print(py_trees.display.ascii_tree(tree)) ... # doctest: +NORMALIZE_WHITESPACE @@ -172,6 +187,7 @@ def exchange( --> Success [-] A --> Dummy + >>> tree = py_trees.composites.Sequence("", False, children=[ ... py_trees.composites.Sequence("1", False, children=[ ... a := py_trees.behaviours.Dummy("A") @@ -180,14 +196,18 @@ def exchange( ... b := py_trees.behaviours.Dummy("B") ... ]) ... ]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE [-] [-] 1 --> A [-] 2 --> B + >>> exchange(a, b) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE + >>> print(py_trees.display.ascii_tree(tree)) + ... # doctest: +NORMALIZE_WHITESPACE [-] [-] 1 --> B @@ -214,7 +234,8 @@ def exchange( def iterate_nodes(tree: py_trees.behaviour.Behaviour): """ Examples: - >>> list(iterate_nodes(py_trees.behaviours.Dummy())) # doctest: +ELLIPSIS + >>> list(iterate_nodes(py_trees.behaviours.Dummy())) + ... # doctest: +ELLIPSIS [] >>> list(iterate_nodes( ... py_trees.composites.Sequence("", False, children=[ @@ -244,7 +265,9 @@ def iterate_nodes(tree: py_trees.behaviour.Behaviour): def enumerate_nodes(tree: py_trees.behaviour.Behaviour): """ Examples: - >>> list(enumerate_nodes(py_trees.behaviours.Dummy())) # doctest: +ELLIPSIS + + >>> list(enumerate_nodes(py_trees.behaviours.Dummy())) + ... # doctest: +ELLIPSIS [(0, )] >>> list(enumerate_nodes( ... py_trees.composites.Sequence("", False, children=[ From a8bd044f454853678f5d12a78a0c472797a14798 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 10:41:20 +0000 Subject: [PATCH 07/40] update tree labelling to not include a space after a label if there's no tree entry --- src/social_norms_trees/atomic_mutations.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 31bf4e5..4780f2d 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -323,13 +323,24 @@ def label_tree_lines( AAA: [-] S1 BB: --> Dummy C: --> Dummy + + If there are more labels than lines, then those are shown too: + >>> print(label_tree_lines(tree, labels=["AAA", "BB", "C", "O"])) + AAA: [-] S1 + BB: --> Dummy + C: --> Dummy + O: """ max_len = max([len(s) for s in labels]) padded_labels = [s.rjust(max_len) for s in labels] tree_representation_lines = representation(tree).split("\n") + enumerated_tree_representation_lines = [ - f"{i}: {t}" for i, t in zip(padded_labels, tree_representation_lines) + f"{i}: {t}".rstrip() # Make the line. If `t` is missing, + # then we don't want a trailing space + # so we strip that away + for i, t in zip(padded_labels, tree_representation_lines) ] output = "\n".join(enumerated_tree_representation_lines) From bf84cb9141ea9f1f285ce0afffff1768b7bfc9f1 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 10:47:52 +0000 Subject: [PATCH 08/40] reformat --- src/social_norms_trees/atomic_mutations.py | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 4780f2d..5f7c41c 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -22,9 +22,9 @@ "CompositeIndex", bound=Tuple[py_trees.composites.Composite, int] ) BehaviorIdentifier = TypeVar( - "BehaviorIdentifier", bound=Union[ExistingNode, NewNode, CompositeIndex]) -BehaviorTreeNode = TypeVar( - "BehaviorTreeNode", bound=py_trees.behaviour.Behaviour) + "BehaviorIdentifier", bound=Union[ExistingNode, NewNode, CompositeIndex] +) +BehaviorTreeNode = TypeVar("BehaviorTreeNode", bound=py_trees.behaviour.Behaviour) BehaviorTree = TypeVar("BehaviorTree", bound=BehaviorTreeNode) BehaviorLibrary = TypeVar("BehaviorLibrary", bound=List[BehaviorTreeNode]) @@ -169,7 +169,7 @@ def exchange( ... ]) ... ]) - >>> print(py_trees.display.ascii_tree(tree)) + >>> print(py_trees.display.ascii_tree(tree)) ... # doctest: +NORMALIZE_WHITESPACE [-] [-] A @@ -215,10 +215,10 @@ def exchange( --> A """ - node0_parent, node0_index = node0.parent, node0.parent.children.index( - node0) - node1_parent, node1_index = node1.parent, node1.parent.children.index( - node1) + node0_parent = node0.parent + node0_index = node0.parent.children.index(node0) + node1_parent = node1.parent + node1_index = node1.parent.children.index(node1) move(node0, (node1_parent, node1_index)) move(node1, (node0_parent, node0_index)) @@ -337,9 +337,11 @@ def label_tree_lines( tree_representation_lines = representation(tree).split("\n") enumerated_tree_representation_lines = [ - f"{i}: {t}".rstrip() # Make the line. If `t` is missing, - # then we don't want a trailing space - # so we strip that away + # Make the line. If `t` is missing, + # then we don't want a trailing space + # so we strip that away + f"{i}: {t}".rstrip() + for i, t in zip(padded_labels, tree_representation_lines) ] From ead8dcb281b42ce06a218afd9dd2ea8d255e5f41 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:04:38 +0000 Subject: [PATCH 09/40] replace specific prompt functions with one generic prompt and multiple plugins --- src/social_norms_trees/atomic_mutations.py | 372 +++++++++++---------- 1 file changed, 201 insertions(+), 171 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 5f7c41c..39eaec9 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -24,9 +24,13 @@ BehaviorIdentifier = TypeVar( "BehaviorIdentifier", bound=Union[ExistingNode, NewNode, CompositeIndex] ) -BehaviorTreeNode = TypeVar("BehaviorTreeNode", bound=py_trees.behaviour.Behaviour) +BehaviorTreeNode = TypeVar( + "BehaviorTreeNode", bound=py_trees.behaviour.Behaviour) BehaviorTree = TypeVar("BehaviorTree", bound=BehaviorTreeNode) BehaviorLibrary = TypeVar("BehaviorLibrary", bound=List[BehaviorTreeNode]) +TreeOrLibrary = TypeVar( + "TreeOrLibrary", bound=Union[BehaviorTree, BehaviorLibrary]) + # ============================================================================= # Atomic operations @@ -341,7 +345,6 @@ def label_tree_lines( # then we don't want a trailing space # so we strip that away f"{i}: {t}".rstrip() - for i, t in zip(padded_labels, tree_representation_lines) ] @@ -355,45 +358,215 @@ def label_tree_lines( # a separate function which does the prompting. # This should help testing. +# Edge cases: what happens if the name of a node is really long - does the ascii representation wrap around? -def prompt_identify_node( - tree: py_trees.behaviour.Behaviour, - message: str = "Which node?", + +class NodeMappingRepresentation(NamedTuple): + mapping: Dict[str, py_trees.behaviour.Behaviour] + labels: List[str] + representation: str + + +def prompt_identify( + tree: TreeOrLibrary, + function: Callable[ + [TreeOrLibrary], Tuple[Mapping[str, BehaviorIdentifier], List[str], str] + ], + message: str = "Which?", display_nodes: bool = True, -) -> py_trees.behaviour.Behaviour: +) -> BehaviorIdentifier: - key_node_mapping = {str(i): n for i, n in enumerate_nodes(tree)} - labels = key_node_mapping.keys() + mapping, labels, representation = function(tree) if display_nodes: - text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" + text = f"{representation}\n{message}" else: text = f"{message}" key = click.prompt(text=text, type=click.Choice(labels)) + node = mapping[key] + return node - node = key_node_mapping[key] - return node +def get_node_mapping(tree: BehaviorTree) -> NodeMappingRepresentation: + """ + Examples: + >>> a = get_node_mapping(py_trees.behaviours.Dummy()) + >>> a.mapping + {'0': } + >>> a.labels + ['0'] + + >>> print(a.representation) + 0: --> Dummy + + >>> b = get_node_mapping(py_trees.composites.Sequence("", False, children=[py_trees.behaviours.Dummy()])) + >>> b.mapping # doctest: +NORMALIZE_WHITESPACE + {'0': , + '1': } + + >>> b.labels + ['0', '1'] + + >>> print(b.representation) + 0: [-] + 1: --> Dummy -def prompt_identify_position( - tree: py_trees.behaviour.Behaviour, - message: str = "Which position?", - display_nodes: bool = True, -) -> Tuple[py_trees.composites.Composite, int]: """ - Example: - >>> s1 = py_trees.composites.Sequence("S1", False, children=[py_trees.behaviours.Dummy()]) - >>> prompt_identify_position(s1) - [-] S1 - 0: --> - --> Dummy - 1: --> - >>> s2 = py_trees.composites.Sequence("S2", False, children=[py_trees.behaviours.Failure()]) - >>> tree = py_trees.composites.Sequence("S0", False, children=[s1, s2]) - >>> prompt_identify_position(tree) + mapping = {str(i): n for i, n in enumerate_nodes(tree)} + labels = list(mapping.keys()) + representation = label_tree_lines(tree=tree, labels=labels) + return NodeMappingRepresentation(mapping, labels, representation) + + +prompt_identify_node = partial(prompt_identify, function=get_node_mapping) + + +def get_library_mapping(library: BehaviorLibrary) -> NodeMappingRepresentation: """ + Examples: + >>> from py_trees.behaviours import Success, Failure + >>> n = get_library_mapping([]) + >>> n.mapping + {} + + >>> n.labels + [] + + >>> print(n.representation) + + + >>> a = get_library_mapping([Success(), Failure()]) + >>> a.mapping # doctest: +NORMALIZE_WHITESPACE + {'0': , + '1': } + + >>> a.labels + ['0', '1'] + + >>> print(a.representation) + 0: Success + 1: Failure + """ + + mapping = {str(i): n for i, n in enumerate(library)} + labels = list(mapping.keys()) + representation = "\n".join( + [f"{i}: {n.name}" for i, n in enumerate(library)]) + return NodeMappingRepresentation(mapping, labels, representation) + + +prompt_identify_library_node = partial( + prompt_identify, + function=get_library_mapping +) + + +def get_composite_mapping(tree: BehaviorTree, skip_label="_"): + """ + Examples: + >>> a = get_composite_mapping(py_trees.behaviours.Dummy()) + >>> a.mapping + {} + + >>> a.labels + [] + + >>> print(a.representation) + _: --> Dummy + + >>> b = get_composite_mapping(py_trees.composites.Sequence("", False, children=[py_trees.behaviours.Dummy()])) + >>> b.mapping # doctest: +NORMALIZE_WHITESPACE + {'0': } + + >>> b.labels + ['0'] + + >>> print(b.representation) + 0: [-] + _: --> Dummy + """ + mapping = {} + display_labels, allowed_labels = [], [] + + for i, node in enumerate_nodes(tree): + label = str(i) + if isinstance(node, py_trees.composites.Composite): + mapping[label] = node + display_labels.append(label) + allowed_labels.append(label) + else: + display_labels.append(skip_label) + representation = label_tree_lines(tree=tree, labels=display_labels) + + return NodeMappingRepresentation(mapping, allowed_labels, representation) + + +prompt_identify_composite = partial( + prompt_identify, + function=get_composite_mapping +) + + +def get_child_index_mapping(tree: BehaviorTree, skip_label="_"): + """ + Examples: + >>> a = get_child_index_mapping(py_trees.behaviours.Dummy()) + >>> a.mapping + {'0': 0} + + >>> a.labels + ['0'] + + >>> print(a.representation) + _: --> Dummy + 0: + + >>> b = get_child_index_mapping(py_trees.composites.Sequence("", False, children=[py_trees.behaviours.Dummy()])) + >>> b.mapping # doctest: +NORMALIZE_WHITESPACE + {'0': 0, '1': 1} + + >>> b.labels + ['0', '1'] + + >>> print(b.representation) + _: [-] + 0: --> Dummy + 1: + """ + mapping = {} + display_labels, allowed_labels = [], [] + + for node in iterate_nodes(tree): + if node in tree.children: + index = tree.children.index(node) + label = str(index) + mapping[label] = index + allowed_labels.append(label) + display_labels.append(label) + else: + display_labels.append(skip_label) + + # Add the "after all the elements" label + post_list_index = len(tree.children) + post_list_label = str(post_list_index) + allowed_labels.append(post_list_label) + display_labels.append(post_list_label) + mapping[post_list_label] = post_list_index + + representation = label_tree_lines(tree=tree, labels=display_labels) + + return NodeMappingRepresentation(mapping, allowed_labels, representation) + + +prompt_identify_child_index = partial( + prompt_identify, + function=get_child_index_mapping +) + + +def get_position_mapping(tree): """ Better Options: Option 0: @@ -544,150 +717,7 @@ def prompt_identify_position( 6: --> *Success* 7: --> *Success* """ - key_node_mapping = {str(i): n for i, n in enumerate_nodes(tree)} - labels = [] - for key, node in key_node_mapping.items(): - if isinstance(node, py_trees.composites.Composite): - labels.append(key) - else: - labels.append("_") - - if display_nodes: - text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" - else: - text = f"{message}" - - key = click.prompt(text=text, type=click.Choice(labels)) - - node = key_node_mapping[key] - - return node - - -def prompt_identify_composite( - tree: py_trees.behaviour.Behaviour, - message: str = "Which composite node?", - display_nodes: bool = True, - skip_label: str = "_", -) -> Tuple[py_trees.composites.Composite, int]: - """ - Example: - >>> s1 = py_trees.composites.Sequence("S1", False, children=[py_trees.behaviours.Dummy()]) - >>> prompt_identify_composite(s1) - [-] S1 - 0: --> - --> Dummy - 1: --> - >>> s2 = py_trees.composites.Sequence("S2", False, children=[py_trees.behaviours.Failure()]) - >>> tree = py_trees.composites.Sequence("S0", False, children=[s1, s2]) - >>> prompt_identify_position(tree) - """ - key_node_mapping = {str(i): n for i, n in enumerate_nodes(tree)} - labels = [] - for key, node in key_node_mapping.items(): - if isinstance(node, py_trees.composites.Composite): - labels.append(key) - else: - labels.append(skip_label) - if display_nodes: - text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" - else: - text = f"{message}" - allowed_labels = [l for l in labels if l != skip_label] - key = click.prompt(text=text, type=click.Choice(allowed_labels)) - node = key_node_mapping[key] - return node - - -def prompt_identify_child_index( - tree: py_trees.behaviour.Behaviour, - message: str = "Which position?", - display_nodes: bool = True, - skip_label: str = "_", -) -> int: - labels = [] - i = 0 - for node in iterate_nodes(tree): - if node in tree.children: - labels.append(str(i)) - i += 1 - else: - labels.append(skip_label) - labels.append(str(i)) - - if display_nodes: - text = f"{(label_tree_lines(tree=tree, labels=labels))}\n{message}" - else: - text = f"{message}" - allowed_labels = [l for l in labels if l != skip_label] - key = click.prompt(text=text, type=click.Choice(allowed_labels)) - node_index = int(key) - - return node_index - - -def format_children_with_indices(composite: py_trees.composites.Composite) -> str: - """ - Examples: - >>> tree = py_trees.composites.Sequence("s1", False, children=[ - ... py_trees.behaviours.Dummy(), - ... py_trees.behaviours.Success(), - ... s2 := py_trees.composites.Sequence("s2", False, children=[ - ... py_trees.behaviours.Dummy(), - ... ]), - ... s3 := py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Failure(), - ... py_trees.behaviours.Periodic("p", n=1), - ... ]), - ... ]) - >>> print(format_children_with_indices(tree)) # doctest: +NORMALIZE_WHITESPACE - _: [-] s1 - 0: --> Dummy - 1: --> Success - 2: [-] s2 - _: --> Dummy - 3: [-] - _: --> Failure - _: --> p - >>> print(format_children_with_indices(s2)) # doctest: +NORMALIZE_WHITESPACE - _: [-] s2 - 0: --> Dummy - >>> print(format_children_with_indices(s3)) # doctest: +NORMALIZE_WHITESPACE - _: [-] - 0: --> Failure - 1: --> p - """ - index_strings = [] - i = 0 - for b in iterate_nodes(composite): - if b in composite.children: - index_strings.append(str(i)) - i += 1 - else: - index_strings.append("_") - - output = label_tree_lines(composite, index_strings) - return output - - -def prompt_identify_library_node( - library, message: str = "Which action from the library?", display_nodes: bool = True -) -> py_trees.behaviour.Behaviour: - key_node_mapping = {str(i): n for i, n in enumerate(library)} - - if display_nodes: - text = f"{format_library_with_indices(library)}\n{message}" - else: - text = f"{message}" - index = click.prompt(text=text, type=click.Choice(key_node_mapping.keys())) - - node = key_node_mapping[index] - - return node - - -def format_library_with_indices(library: List[py_trees.behaviour.Behaviour]): - return "\n".join([f"{i}: {n.name}" for i, n in enumerate(library)]) + pass # ============================================================================= @@ -771,7 +801,7 @@ def prompt_get_mutate_arguments(annotation: GenericAlias, tree, library): elif annotation_ == str(NewNode): _logger.debug("in NewNode") new_node = prompt_identify_library_node( - library, "Which node from the library?") + library, message="Which node from the library?") return new_node else: _logger.debug("in 'else'") From b71e41aaf570577875e8081557e051cc6c22566e Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:04:58 +0000 Subject: [PATCH 10/40] =?UTF-8?q?remove=20error=20checking=20=E2=80=93=20w?= =?UTF-8?q?e=20want=20this=20version=20just=20to=20crash=20if=20there's=20?= =?UTF-8?q?a=20problem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/social_norms_trees/atomic_mutations.py | 39 ++++------------------ 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 39eaec9..9cfabfb 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -874,42 +874,15 @@ def main(): logging.basicConfig(level=logging.DEBUG) tree, library = load_experiment() protocol = [] - exit_code = None - try: + # The main loop of the experiment + while f := mutate_chooser(insert, move, exchange, remove, quit): + # try: + results = f(tree, library) + _logger.debug(results) + protocol.append(results) print(py_trees.display.ascii_tree(tree)) - # The main loop of the experiment - while f := mutate_chooser(insert, move, exchange, remove, quit): - # try: - results = f(tree, library) - _logger.debug(results) - protocol.append(results) - print(py_trees.display.ascii_tree(tree)) - - # If we have any errors raised by the function, like wrong values, - # we don't want to crash. - # except (ValueError, IndexError, NotImplementedError) as e: - # _logger.error(f"\n{e}") - # continue - - # If the user calls the "quit" function, then we want to exit - except QuitException as e: - _logger.debug(e) - exit_code = 0 - - # If the user does a keyboard interrupt, then we want to exit - except click.exceptions.Abort: - print("\n") - _logger.error("Program aborted.") - exit_code = 1 - - # Save the results - finally: - _logger.debug("finally") - save_results(tree, protocol) - sys.exit(exit_code) - if __name__ == "__main__": app() From 82bce06adc477f925459d8b698a778aba64ab2ce Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:05:11 +0000 Subject: [PATCH 11/40] add partial import --- src/social_norms_trees/atomic_mutations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 9cfabfb..4c2cc84 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -1,6 +1,6 @@ from collections import namedtuple import inspect -from functools import wraps +from functools import partial, wraps import logging import sys from types import GenericAlias From 5d6ac9a6995d7c9d9fa149c3cc63689fa11e0183 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:05:22 +0000 Subject: [PATCH 12/40] remove unused sys import --- src/social_norms_trees/atomic_mutations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 4c2cc84..c462c4e 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -2,7 +2,6 @@ import inspect from functools import partial, wraps import logging -import sys from types import GenericAlias from typing import Callable, List, Mapping, NamedTuple, Tuple, TypeVar, Union, Dict From 43989d07f0cbca281362d0773485226da8ef62cf Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:05:45 +0000 Subject: [PATCH 13/40] use black version from environment --- .vscode/settings.json | 1 + merge.sh | 2 ++ 2 files changed, 3 insertions(+) create mode 100755 merge.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index 00a99d7..598b0d7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.defaultFormatter": "ms-python.black-formatter", "editor.formatOnSave": true, + "black-formatter.importStrategy": "fromEnvironment", "python.testing.pytestArgs": [ "." ], diff --git a/merge.sh b/merge.sh new file mode 100755 index 0000000..b864f99 --- /dev/null +++ b/merge.sh @@ -0,0 +1,2 @@ +git fetch --all +git merge origin/{main,add-black-checks,fix-pytests,update-paths} \ No newline at end of file From 0666aeb62c61e643a7c396e7985bd733025f72ad Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:19:25 +0000 Subject: [PATCH 14/40] delete unneeded merge shellscript --- merge.sh | 2 -- 1 file changed, 2 deletions(-) delete mode 100755 merge.sh diff --git a/merge.sh b/merge.sh deleted file mode 100755 index b864f99..0000000 --- a/merge.sh +++ /dev/null @@ -1,2 +0,0 @@ -git fetch --all -git merge origin/{main,add-black-checks,fix-pytests,update-paths} \ No newline at end of file From 5b677b5146aed169588e130fe93943bf52d7bdef Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:26:19 +0000 Subject: [PATCH 15/40] rename quit to end_experiment --- src/social_norms_trees/atomic_mutations.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index c462c4e..1bf67cc 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -817,9 +817,9 @@ class QuitException(Exception): pass -def quit(): - """Finish the experiment.""" - raise QuitException("User quit the experiment.") +def end_experiment(): + """I'm done, end the experiment.""" + raise QuitException("User ended the experiment.") # ============================================================================= @@ -875,8 +875,7 @@ def main(): protocol = [] # The main loop of the experiment - while f := mutate_chooser(insert, move, exchange, remove, quit): - # try: + while f := mutate_chooser(insert, move, exchange, remove, end_experiment): results = f(tree, library) _logger.debug(results) protocol.append(results) From a8a47a10000e7634bdd506b14a5e83ccb826442a Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 11:26:40 +0000 Subject: [PATCH 16/40] add logger --- src/social_norms_trees/ui_wrapper.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index e6188b3..d25b56d 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -21,6 +21,8 @@ from social_norms_trees.behavior_library import BehaviorLibrary +_logger = logging.getLogger(__name__) + def load_db(db_file): if os.path.exists(db_file): From e869c0757dec4d7cecac2cbf0185c4efc4f21f2d Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 17:26:21 +0000 Subject: [PATCH 17/40] update options for highlighting positions in the tree --- src/social_norms_trees/atomic_mutations.py | 202 +++++++++++++++------ 1 file changed, 143 insertions(+), 59 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 1bf67cc..e7bd3d9 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -568,7 +568,7 @@ def get_child_index_mapping(tree: BehaviorTree, skip_label="_"): def get_position_mapping(tree): """ Better Options: - Option 0: + Option 0: Numbers close to place is better [-] S0 0: -----> [-] S1 @@ -581,32 +581,8 @@ def get_position_mapping(tree): --> Failure 5: -------> 6: -----> - Option 1: - [-] S0 - 0: -----> - [-] S1 - 0.0: -------> - --> Dummy - 0.1: -------> - 1: -----> - [-] S2 - 1.0: -------> - --> Failure - 1.1: -------> - 2: -----> - Option 2: - [-] S0 - --> {0} - [-] S1 - --> {0.0} - --> Dummy - --> {0.1} - --> {1} - [-] S2 - --> {1.0} - --> Failure - --> {1.1} - --> {2} + + Option 3: [-] S0 --> {1} @@ -620,6 +596,7 @@ def get_position_mapping(tree): --> Failure --> {6} --> {7} + Option 4: [-] S0 --> {⚡️ 1} @@ -633,23 +610,9 @@ def get_position_mapping(tree): --> Failure --> {⚡️ 6} --> {⚡️ 7} - Option 5: - Where: [before/after] - Which: [user types display name] - Option 6: - [-] S0 - {⚡️ 1} <-- - [-] S1 - {⚡️ 2} <-- - --> Dummy - {⚡️ 3} <-- - {⚡️ 4} <-- - [-] S2 - {⚡️ 5} <-- - --> Failure - {⚡️ 6} <-- - {⚡️ 7} <-- - Option 7: + + + Option 7: But the arrows point to the wrong places – the places the user doesn't need to look at right now [-] S0 {⚡️ 1} [-] S1 @@ -662,20 +625,99 @@ def get_position_mapping(tree): --> Failure {⚡️ 6} {⚡️ 7} - Option 8: + + + "Choose a numbered position" + + Option 11: [-] S0 - --> _1 + {⭐️ 1} [-] S1 - --> _2 + {⭐️ 2} --> Dummy - --> _3 - --> _4 + {⭐️ 3} + {⭐️ 4} [-] S2 - --> _5 + {⭐️ 5} --> Failure - --> _6 - --> _7 - Option 9: + {⭐️ 6} + {⭐️ 7} + + Option 12: highlight the options in *gray* or make everything else gray and leave what they can choose as black + [-] S0 + *Position 1* + [-] S1 + *Position 2* + --> Dummy + *Position 3* + *Position 4* + [-] S2 + *Position 5* + --> Failure + *Position 6* + *Position 7* + + Option 12: highlight the options in *gray* or make everything else gray and leave what they can choose as black + [-] S0 + *1* + [-] S1 + *2* + --> Dummy + *3* + *4* + [-] S2 + *5* + --> Failure + *6* + *7* + + Option 13: make everything else gray and leave what they can choose as black + [-] S0 + --> {1} + [-] S1 + --> {2} + --> Dummy + --> {3} + --> {4} + [-] S2 + --> {5} + --> Failure + --> {6} + --> {7} + + + + Rejected options + + Option 1: Excluded – Whole numbers is easier than decimals + [-] S0 + 0: -----> + [-] S1 + 0.0: -------> + --> Dummy + 0.1: -------> + 1: -----> + [-] S2 + 1.0: -------> + --> Failure + 1.1: -------> + 2: -----> + + Option 2: Excluded – Whole numbers is easier than decimals + [-] S0 + --> {0} + [-] S1 + --> {0.0} + --> Dummy + --> {0.1} + --> {1} + [-] S2 + --> {1.0} + --> Failure + --> {1.1} + --> {2} + + Option 9: Looks like line numbers – numbers near the place [-] S0 1: --> _ [-] S1 @@ -688,7 +730,9 @@ def get_position_mapping(tree): --> Failure 6: --> _ 7: --> _ - Option 10: + + + Option 10: Looks like line numbers [-] S0 1: --> ______ [-] S1 @@ -701,8 +745,9 @@ def get_position_mapping(tree): --> Failure 6: --> ______ 7: --> ______ + Option 11: Preview - Chose a "Success" node: + Chose a "Success" node: Looks like line numbers: [-] S0 1: --> *Success* [-] S1 @@ -715,6 +760,46 @@ def get_position_mapping(tree): --> Failure 6: --> *Success* 7: --> *Success* + + Option 8: Too many lines and dashes + [-] S0 + --> _1 + [-] S1 + --> _2 + --> Dummy + --> _3 + --> _4 + [-] S2 + --> _5 + --> Failure + --> _6 + --> _7 + + Option 6: too many dashes, arrows going everywhere + [-] S0 + {⚡️ 1} <-- + [-] S1 + {⚡️ 2} <-- + --> Dummy + {⚡️ 3} <-- + {⚡️ 4} <-- + [-] S2 + {⚡️ 5} <-- + --> Failure + {⚡️ 6} <-- + {⚡️ 7} <-- + + + Option 5: Might be tricky with typos, caps, might feel more complicated. + Where: [before/after] + Which: [user types display name] + [-] S0 + [-] S1 + --> Dummy + [-] S2 + --> Failure + Legal options: "Before S1", "Before Dummy", "After Dummy", "Before Failure" + """ pass @@ -784,9 +869,8 @@ def prompt_get_mutate_arguments(annotation: GenericAlias, tree, library): assert isinstance(annotation_, str) if annotation_ == str(inspect.Parameter.empty): - _logger.debug("in empty") - msg = "Can't work out what argument %s should be" % annotation - raise ValueError(msg) + _logger.debug("No argument annotation, returning None") + return None elif annotation_ == str(ExistingNode): _logger.debug("in ExistingNode") node = prompt_identify_node(tree) @@ -817,7 +901,7 @@ class QuitException(Exception): pass -def end_experiment(): +def end_experiment(*args, **kwargs): """I'm done, end the experiment.""" raise QuitException("User ended the experiment.") From cfe390ecde4b68fe789cb5eb91009baf651e5a6d Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:11:51 +0000 Subject: [PATCH 18/40] delete rejected options --- src/social_norms_trees/atomic_mutations.py | 215 --------------------- 1 file changed, 215 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index e7bd3d9..311c091 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -567,111 +567,9 @@ def get_child_index_mapping(tree: BehaviorTree, skip_label="_"): def get_position_mapping(tree): """ - Better Options: - Option 0: Numbers close to place is better - [-] S0 - 0: -----> - [-] S1 - 1: -------> - --> Dummy - 2: -------> - 3: -----> - [-] S2 - 4: -------> - --> Failure - 5: -------> - 6: -----> - Option 3: - [-] S0 - --> {1} - [-] S1 - --> {2} - --> Dummy - --> {3} - --> {4} - [-] S2 - --> {5} - --> Failure - --> {6} - --> {7} - - Option 4: - [-] S0 - --> {⚡️ 1} - [-] S1 - --> {⚡️ 2} - --> Dummy - --> {⚡️ 3} - --> {⚡️ 4} - [-] S2 - --> {⚡️ 5} - --> Failure - --> {⚡️ 6} - --> {⚡️ 7} - - - Option 7: But the arrows point to the wrong places – the places the user doesn't need to look at right now - [-] S0 - {⚡️ 1} - [-] S1 - {⚡️ 2} - --> Dummy - {⚡️ 3} - {⚡️ 4} - [-] S2 - {⚡️ 5} - --> Failure - {⚡️ 6} - {⚡️ 7} - - - "Choose a numbered position" - - Option 11: - [-] S0 - {⭐️ 1} - [-] S1 - {⭐️ 2} - --> Dummy - {⭐️ 3} - {⭐️ 4} - [-] S2 - {⭐️ 5} - --> Failure - {⭐️ 6} - {⭐️ 7} - - Option 12: highlight the options in *gray* or make everything else gray and leave what they can choose as black - [-] S0 - *Position 1* - [-] S1 - *Position 2* - --> Dummy - *Position 3* - *Position 4* - [-] S2 - *Position 5* - --> Failure - *Position 6* - *Position 7* - - Option 12: highlight the options in *gray* or make everything else gray and leave what they can choose as black - [-] S0 - *1* - [-] S1 - *2* - --> Dummy - *3* - *4* - [-] S2 - *5* - --> Failure - *6* - *7* - Option 13: make everything else gray and leave what they can choose as black [-] S0 --> {1} [-] S1 @@ -687,119 +585,6 @@ def get_position_mapping(tree): - Rejected options - - Option 1: Excluded – Whole numbers is easier than decimals - [-] S0 - 0: -----> - [-] S1 - 0.0: -------> - --> Dummy - 0.1: -------> - 1: -----> - [-] S2 - 1.0: -------> - --> Failure - 1.1: -------> - 2: -----> - - Option 2: Excluded – Whole numbers is easier than decimals - [-] S0 - --> {0} - [-] S1 - --> {0.0} - --> Dummy - --> {0.1} - --> {1} - [-] S2 - --> {1.0} - --> Failure - --> {1.1} - --> {2} - - Option 9: Looks like line numbers – numbers near the place - [-] S0 - 1: --> _ - [-] S1 - 2: --> _ - --> Dummy - 3: --> _ - 4: --> _ - [-] S2 - 5: --> _ - --> Failure - 6: --> _ - 7: --> _ - - - Option 10: Looks like line numbers - [-] S0 - 1: --> ______ - [-] S1 - 2: --> ______ - --> Dummy - 3: --> ______ - 4: --> ______ - [-] S2 - 5: --> ______ - --> Failure - 6: --> ______ - 7: --> ______ - - Option 11: Preview - Chose a "Success" node: Looks like line numbers: - [-] S0 - 1: --> *Success* - [-] S1 - 2: --> *Success* - --> Dummy - 3: --> *Success* - 4: --> *Success* - [-] S2 - 5: --> *Success* - --> Failure - 6: --> *Success* - 7: --> *Success* - - Option 8: Too many lines and dashes - [-] S0 - --> _1 - [-] S1 - --> _2 - --> Dummy - --> _3 - --> _4 - [-] S2 - --> _5 - --> Failure - --> _6 - --> _7 - - Option 6: too many dashes, arrows going everywhere - [-] S0 - {⚡️ 1} <-- - [-] S1 - {⚡️ 2} <-- - --> Dummy - {⚡️ 3} <-- - {⚡️ 4} <-- - [-] S2 - {⚡️ 5} <-- - --> Failure - {⚡️ 6} <-- - {⚡️ 7} <-- - - - Option 5: Might be tricky with typos, caps, might feel more complicated. - Where: [before/after] - Which: [user types display name] - [-] S0 - [-] S1 - --> Dummy - [-] S2 - --> Failure - Legal options: "Before S1", "Before Dummy", "After Dummy", "Before Failure" - """ pass From ee5b96f9d03580f8c6be63396bde589b36e49692 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:25:52 +0000 Subject: [PATCH 19/40] udpate type annotation --- src/social_norms_trees/atomic_mutations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 311c091..655eebc 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -621,8 +621,8 @@ def mutate_chooser(*fs: Union[Callable], message="Which action?"): def mutate_ui( - f, -) -> Callable[[py_trees.behaviour.Behaviour, List[py_trees.behaviour.Behaviour]], Dict]: + f: Callable, +) -> Callable[[py_trees.behaviour.Behaviour, List[py_trees.behaviour.Behaviour]], MutationResult]: """Factory function for a tree mutator UI. This creates a version of the atomic function `f` which prompts the user for the appropriate arguments From 9c8e4825772c3409c527fc9dd7f2137b7fca2bcf Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:26:01 +0000 Subject: [PATCH 20/40] make behavior library iterable --- src/social_norms_trees/behavior_library.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/social_norms_trees/behavior_library.py b/src/social_norms_trees/behavior_library.py index 58c784f..6b703f2 100644 --- a/src/social_norms_trees/behavior_library.py +++ b/src/social_norms_trees/behavior_library.py @@ -4,4 +4,9 @@ def __init__(self, behavior_list): self.behavior_from_display_name = { behavior["display_name"]: behavior for behavior in behavior_list } - self.behavior_from_id = {behavior["id"]: behavior for behavior in behavior_list} + self.behavior_from_id = { + behavior["id"]: behavior for behavior in behavior_list} + + def __iter__(self): + for i in self.behaviors: + yield i From c195236a5067c8825c6df5ba732bc29ad69e2484 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:27:23 +0000 Subject: [PATCH 21/40] add logging config --- src/social_norms_trees/ui_wrapper.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index d25b56d..0d95a2d 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -1,3 +1,4 @@ +import logging import pathlib import time from typing import Annotated @@ -172,9 +173,21 @@ def main( ], db_file: Annotated[ pathlib.Path, - typer.Option(help="file where the experimental results will be written"), + typer.Option( + help="file where the experimental results will be written"), ] = "db.json", + verbose: Annotated[bool, typer.Option("--verbose")] = False, + debug: Annotated[bool, typer.Option("--debug")] = False, ): + if debug: + logging.basicConfig(level=logging.DEBUG) + _logger.debug("debug logging") + elif verbose: + logging.basicConfig(level=logging.INFO) + _logger.debug("verbose logging") + else: + logging.basicConfig() + print("AIT Prototype #1 Simulator") # TODO: write up some context, assumptions made in the README From 136a6f1cdf39f9baec62baad2eaf147807c7b941 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:28:49 +0000 Subject: [PATCH 22/40] use new functions for experiment --- src/social_norms_trees/ui_wrapper.py | 108 +++++++++++++-------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index 0d95a2d..fbfce8b 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -1,7 +1,7 @@ import logging import pathlib import time -from typing import Annotated +from typing import Annotated, List import click from datetime import datetime import json @@ -12,11 +12,15 @@ import typer -from social_norms_trees.mutate_tree import ( - move_node, - exchange_nodes, - remove_node, - add_node, +from social_norms_trees.atomic_mutations import ( + QuitException, + exchange, + insert, + move, + mutate_chooser, + remove, + end_experiment, + MutationResult, ) from social_norms_trees.serialize_tree import serialize_tree, deserialize_tree @@ -46,7 +50,8 @@ def experiment_setup(db, origin_tree): print("\n") participant_id = participant_login() - experiment_id = initialize_experiment_record(db, participant_id, origin_tree) + experiment_id = initialize_experiment_record( + db, participant_id, origin_tree) print("\nSetup Complete.\n") @@ -61,7 +66,8 @@ def participant_login(): def load_resources(file_path): try: - print(f"\nLoading behavior tree and behavior library from {file_path}...\n") + print( + f"\nLoading behavior tree and behavior library from {file_path}...\n") with open(file_path, "r") as file: resources = json.load(file) @@ -100,64 +106,53 @@ def initialize_experiment_record(db, participant_id, origin_tree): return experiment_id -def run_experiment(db, origin_tree, experiment_id, behavior_library): +def display_tree(tree): + print(py_trees.display.ascii_tree(tree)) + return + + +def run_experiment(origin_tree, behavior_library): # Loop for the actual experiment part, which takes user input to decide which action to take print("\nExperiment beginning...\n") + tree = origin_tree + library = behavior_library + protocol: List[MutationResult] = [] + action_log = [] + results_dict = {} + + results_dict["initial_behavior_tree"] = serialize_tree(tree) + results_dict["start_time"] = datetime.now().isoformat() + try: while True: - print(py_trees.display.ascii_tree(origin_tree)) - user_choice = click.prompt( - "Would you like to perform an action on the behavior tree?", - show_choices=True, - type=click.Choice(["y", "n"], case_sensitive=False), - ) - - if user_choice == "y": - action = click.prompt( - "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.IntRange(min=1, max=4), - show_choices=True, - ) - - if action == 1: - origin_tree, action_log = move_node(origin_tree) - db[experiment_id]["action_history"].append(action_log) - elif action == 2: - origin_tree, action_log = exchange_nodes(origin_tree) - db[experiment_id]["action_history"].append(action_log) - - elif action == 3: - 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, 3, or 4).\n" - ) - - # user_choice == "n", end simulation run - else: + display_tree(tree) + f = mutate_chooser(insert, move, exchange, remove, end_experiment) + if f is end_experiment: break + results = f(tree, library) + _logger.debug(results) + protocol.append(results) + action_log.append({ + "type": results.function.__name__, + "time": datetime.now().isoformat(), + }) + + except QuitException: + pass except Exception: print( "\nAn error has occured during the experiment, the experiment will now end." ) - db[experiment_id]["error_log"] = traceback.format_exc() + results_dict["error_log"] = traceback.format_exc() finally: - db[experiment_id]["final_behavior_tree"] = serialize_tree(origin_tree) - db[experiment_id]["end_date"] = datetime.now().isoformat() - return db + results_dict["final_behavior_tree"] = serialize_tree(tree) + results_dict["start_time"] = datetime.now().isoformat() + results_dict["action_log"] = action_log + + return results_dict app = typer.Typer() @@ -196,11 +191,14 @@ def main( # load tree to run experiment on, and behavior library - original_tree, behavior_library, context_paragraph = load_resources(resources_file) + original_tree, behavior_library, context_paragraph = load_resources( + resources_file) print(f"\nContext of this experiment: {context_paragraph}") participant_id, experiment_id = experiment_setup(db, original_tree) - db = run_experiment(db, original_tree, experiment_id, behavior_library) + results = run_experiment(original_tree, behavior_library) + db[experiment_id] = results + _logger.debug(db) save_db(db, db_file) # TODO: define export file, that will be where we export the results to From 623dc8cde0411e5153abbb8bc5d643b1990b7247 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:32:38 +0000 Subject: [PATCH 23/40] remove some extra variables --- src/social_norms_trees/ui_wrapper.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index fbfce8b..85a3532 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -117,9 +117,9 @@ def run_experiment(origin_tree, behavior_library): tree = origin_tree library = behavior_library - protocol: List[MutationResult] = [] - action_log = [] - results_dict = {} + results_dict = { + "action_log": [] + } results_dict["initial_behavior_tree"] = serialize_tree(tree) results_dict["start_time"] = datetime.now().isoformat() @@ -131,9 +131,7 @@ def run_experiment(origin_tree, behavior_library): if f is end_experiment: break results = f(tree, library) - _logger.debug(results) - protocol.append(results) - action_log.append({ + results_dict["action_log"].append({ "type": results.function.__name__, "time": datetime.now().isoformat(), }) @@ -150,7 +148,6 @@ def run_experiment(origin_tree, behavior_library): finally: results_dict["final_behavior_tree"] = serialize_tree(tree) results_dict["start_time"] = datetime.now().isoformat() - results_dict["action_log"] = action_log return results_dict From e1abe71e2207ba6ddb248bb56a65aab6967f1d7d Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:32:52 +0000 Subject: [PATCH 24/40] simplify variable names --- src/social_norms_trees/ui_wrapper.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index 85a3532..f047ec1 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -111,12 +111,10 @@ def display_tree(tree): return -def run_experiment(origin_tree, behavior_library): +def run_experiment(tree, library): # Loop for the actual experiment part, which takes user input to decide which action to take print("\nExperiment beginning...\n") - tree = origin_tree - library = behavior_library results_dict = { "action_log": [] } From 521c314680dde847365a265e230b4c5ca3c6229a Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 18:33:13 +0000 Subject: [PATCH 25/40] add debugging --- src/social_norms_trees/atomic_mutations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 655eebc..9bfa0dd 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -643,6 +643,7 @@ def f_inner(tree, library): return_value = MutationResult( result=inner_result, tree=tree, function=f, kwargs=kwargs ) + _logger.debug(return_value) return return_value return f_inner @@ -686,7 +687,7 @@ class QuitException(Exception): pass -def end_experiment(*args, **kwargs): +def end_experiment(): """I'm done, end the experiment.""" raise QuitException("User ended the experiment.") From bc4e835f52c2ddd2738c776f414b2ef32ec1b166 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 19:01:48 +0000 Subject: [PATCH 26/40] add some extra serialization options for the tree --- src/social_norms_trees/serialize_tree.py | 45 +++++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/social_norms_trees/serialize_tree.py b/src/social_norms_trees/serialize_tree.py index a3424f9..c5b8958 100644 --- a/src/social_norms_trees/serialize_tree.py +++ b/src/social_norms_trees/serialize_tree.py @@ -3,15 +3,42 @@ def serialize_tree(tree): - def serialize_node(node): - data = { - "type": node.__class__.__name__, - "name": node.name, - "children": [serialize_node(child) for child in node.children], - } - return data - - return serialize_node(tree) + """ + Examples: + >>> from py_trees.behaviours import Dummy, Success, Failure + >>> from py_trees.composites import Sequence, Selector + + >>> serialize_tree(Dummy()) + {'type': 'Dummy', 'name': 'Dummy'} + + >>> serialize_tree(Success()) + {'type': 'Success', 'name': 'Success'} + + >>> serialize_tree(Failure()) + {'type': 'Failure', 'name': 'Failure'} + + >>> serialize_tree(Sequence("root", True, children=[Dummy()])) + {'type': 'Sequence', 'name': 'root', 'children': [{'type': 'Dummy', 'name': 'Dummy'}]} + + >>> serialize_tree(CustomBehavior("behavior", "theid", "display behavior")) + {'type': 'CustomBehavior', 'name': 'behavior', 'display_name': 'display behavior', 'id_': 'theid'} + + >>> serialize_tree(CustomSequence("root", "theid", "display root", children=[Dummy()])) + {'type': 'CustomSequence', 'name': 'root', 'display_name': 'display root', 'id_': 'theid', 'children': [{'type': 'Dummy', 'name': 'Dummy'}]} + """ + + data = { + "type": tree.__class__.__name__, + "name": tree.name, + } + if hasattr(tree, "display_name"): + data["display_name"] = tree.display_name + if hasattr(tree, "id_"): + data["id_"] = tree.id_ + if tree.children: + data["children"] = [serialize_tree(child) for child in tree.children] + + return data def deserialize_tree(tree, behavior_library): From b36935bb4ab8f27c488673d6f3109acb59b928d0 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 19:02:02 +0000 Subject: [PATCH 27/40] serialize all function arguments --- src/social_norms_trees/ui_wrapper.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index f047ec1..bfe8270 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -111,17 +111,28 @@ def display_tree(tree): return +def serialize_function_arguments(kwargs_): + results = {} + for key, value in kwargs_.items(): + if isinstance(value, dict): + results[key] = serialize_function_arguments(value) + elif isinstance(value, py_trees.behaviour.Behaviour): + results[key] = serialize_tree(value) + else: + results[key] = value + return results + + def run_experiment(tree, library): # Loop for the actual experiment part, which takes user input to decide which action to take print("\nExperiment beginning...\n") results_dict = { + "start_time": datetime.now().isoformat(), + "initial_behavior_tree": serialize_tree(tree), "action_log": [] } - results_dict["initial_behavior_tree"] = serialize_tree(tree) - results_dict["start_time"] = datetime.now().isoformat() - try: while True: display_tree(tree) @@ -131,6 +142,7 @@ def run_experiment(tree, library): results = f(tree, library) results_dict["action_log"].append({ "type": results.function.__name__, + "kwargs": serialize_function_arguments(results.kwargs), "time": datetime.now().isoformat(), }) From 672d828fd88004305c72c6621dc94339e1d98e1f Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 19:10:48 +0000 Subject: [PATCH 28/40] remove unused import --- src/social_norms_trees/ui_wrapper.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index bfe8270..057ca12 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -20,7 +20,6 @@ mutate_chooser, remove, end_experiment, - MutationResult, ) from social_norms_trees.serialize_tree import serialize_tree, deserialize_tree @@ -149,15 +148,15 @@ def run_experiment(tree, library): except QuitException: pass - except Exception: - print( - "\nAn error has occured during the experiment, the experiment will now end." - ) - results_dict["error_log"] = traceback.format_exc() + # except Exception: + # print( + # "\nAn error has occured during the experiment, the experiment will now end." + # ) + # results_dict["error_log"] = traceback.format_exc() - finally: - results_dict["final_behavior_tree"] = serialize_tree(tree) - results_dict["start_time"] = datetime.now().isoformat() + # finally: + results_dict["final_behavior_tree"] = serialize_tree(tree) + results_dict["start_time"] = datetime.now().isoformat() return results_dict From 6b736c51c20c2b6c49ac2d95006f4dd98b8b36cf Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 19:11:04 +0000 Subject: [PATCH 29/40] remove unused import --- src/social_norms_trees/ui_wrapper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index 057ca12..1229445 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -1,6 +1,5 @@ import logging import pathlib -import time from typing import Annotated, List import click from datetime import datetime From 6431eb388b70d8f3a3dd5384abfcd78d30ade662 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 20:26:46 +0000 Subject: [PATCH 30/40] add option to include children in serialize tree --- src/social_norms_trees/serialize_tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/social_norms_trees/serialize_tree.py b/src/social_norms_trees/serialize_tree.py index c5b8958..84dcbba 100644 --- a/src/social_norms_trees/serialize_tree.py +++ b/src/social_norms_trees/serialize_tree.py @@ -2,7 +2,7 @@ from social_norms_trees.custom_node_library import CustomBehavior, CustomSequence -def serialize_tree(tree): +def serialize_tree(tree, include_children=True): """ Examples: >>> from py_trees.behaviours import Dummy, Success, Failure @@ -35,7 +35,7 @@ def serialize_tree(tree): data["display_name"] = tree.display_name if hasattr(tree, "id_"): data["id_"] = tree.id_ - if tree.children: + if include_children and tree.children: data["children"] = [serialize_tree(child) for child in tree.children] return data From 9324a4d068a6aed8f0aa2bc320d491f903e4a501 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 20:27:40 +0000 Subject: [PATCH 31/40] add function to deserialize the library into objects --- src/social_norms_trees/serialize_tree.py | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/social_norms_trees/serialize_tree.py b/src/social_norms_trees/serialize_tree.py index 84dcbba..f1f3070 100644 --- a/src/social_norms_trees/serialize_tree.py +++ b/src/social_norms_trees/serialize_tree.py @@ -41,6 +41,59 @@ def serialize_tree(tree, include_children=True): return data +def deserialize_library_element(description: dict): + """ + Examples: + >>> deserialize_library_element({"type": "Sequence"}) + + """ + assert isinstance(description["type"], str), ( + f"\nThere was an invalid configuration detected in the inputted behavior tree: " + f"Invalid type for node attribute 'type' found for node '{description['name']}'. " + f"Please ensure that the 'name' attribute is a string." + ) + + node_type = description["type"] + assert node_type in {"Sequence", "Selector", "Behavior"}, ( + f"\nThere was an invalid configuration detected in the inputted behavior tree: " + f"Invalid node type '{node_type}' found for node '{description['name']}'. " + f"Please ensure that all node types are correct and supported." + ) + + if node_type == "Sequence": + if "children" in description.keys(): + children = [deserialize_library_element( + child) for child in description["children"]] + else: + children = [] + + node = CustomSequence( + name=description["name"], + id_=description["id"], + display_name=description["name"], + children=children, + ) + + elif node_type == "Behavior": + assert "children" not in description or len(description["children"]) == 0, ( + f"\nThere was an invalid configuration detected in the inputted behavior tree: " + f"Children were detected for Behavior type node '{description['name']}': " + f"Behavior nodes should not have any children. Please check the structure of your behavior tree." + ) + + node = CustomBehavior( + name=description["name"], + id_=description["id"], + display_name=description["name"], + ) + + else: + msg = "node_type=%s is not implemented" % node_type + raise NotImplementedError(msg) + + return node + + def deserialize_tree(tree, behavior_library): def deserialize_node(node): assert type(node["type"] == str), ( From 5d2d43f3f9d0ddc04746dbd765d4699b6a35563d Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 20:28:07 +0000 Subject: [PATCH 32/40] remove duplicated display_name --- src/social_norms_trees/behavior_library.py | 2 +- src/social_norms_trees/serialize_tree.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/social_norms_trees/behavior_library.py b/src/social_norms_trees/behavior_library.py index 6b703f2..4704b64 100644 --- a/src/social_norms_trees/behavior_library.py +++ b/src/social_norms_trees/behavior_library.py @@ -2,7 +2,7 @@ class BehaviorLibrary: def __init__(self, behavior_list): self.behaviors = behavior_list self.behavior_from_display_name = { - behavior["display_name"]: behavior for behavior in behavior_list + behavior["name"]: behavior for behavior in behavior_list } self.behavior_from_id = { behavior["id"]: behavior for behavior in behavior_list} diff --git a/src/social_norms_trees/serialize_tree.py b/src/social_norms_trees/serialize_tree.py index f1f3070..2400b4d 100644 --- a/src/social_norms_trees/serialize_tree.py +++ b/src/social_norms_trees/serialize_tree.py @@ -121,9 +121,9 @@ def deserialize_node(node): if behavior: return CustomSequence( - name=behavior["display_name"], + name=behavior["name"], id_=behavior["id"], - display_name=behavior["display_name"], + display_name=behavior["name"], children=children, ) else: @@ -142,9 +142,9 @@ def deserialize_node(node): if behavior: return CustomBehavior( - name=behavior["display_name"], + name=behavior["name"], id_=behavior["id"], - display_name=behavior["display_name"], + display_name=behavior["name"], ) else: raise ValueError( From 1ee50d692052e75ef94697c67f92772c9da32263 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 20:28:22 +0000 Subject: [PATCH 33/40] get ui wrapper working with new objects --- src/social_norms_trees/ui_wrapper.py | 44 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index 1229445..f096cb0 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -20,7 +20,7 @@ remove, end_experiment, ) -from social_norms_trees.serialize_tree import serialize_tree, deserialize_tree +from social_norms_trees.serialize_tree import deserialize_library_element, serialize_tree, deserialize_tree from social_norms_trees.behavior_library import BehaviorLibrary @@ -40,8 +40,10 @@ def save_db(db, db_file): print(f"\nWriting results of simulation to {db_file}...") + json_representation = json.dumps(db, indent=4) + with open(db_file, "w") as f: - json.dump(db, f, indent=4) + f.write(json_representation) def experiment_setup(db, origin_tree): @@ -78,9 +80,10 @@ def load_resources(file_path): behavior_list = resources.get("behavior_library") context_paragraph = resources.get("context") - behavior_library = BehaviorLibrary(behavior_list) + behavior_tree = deserialize_tree( + behavior_tree, BehaviorLibrary(behavior_list)) - behavior_tree = deserialize_tree(behavior_tree, behavior_library) + behavior_library = [deserialize_library_element(e) for e in behavior_list] print("Loading success.") return behavior_tree, behavior_library, context_paragraph @@ -109,16 +112,23 @@ def display_tree(tree): return -def serialize_function_arguments(kwargs_): +def serialize_function_arguments(args): results = {} - for key, value in kwargs_.items(): - if isinstance(value, dict): + if isinstance(args, dict): + for key, value in args.items(): results[key] = serialize_function_arguments(value) - elif isinstance(value, py_trees.behaviour.Behaviour): - results[key] = serialize_tree(value) - else: - results[key] = value - return results + return results + elif isinstance(args, py_trees.behaviour.Behaviour): + value = serialize_tree(args, include_children=False) + return value + elif isinstance(args, tuple): + value = tuple(serialize_function_arguments(i) for i in args) + return value + elif isinstance(args, list): + value = [serialize_function_arguments(i) for i in args] + return value + else: + return args def run_experiment(tree, library): @@ -147,11 +157,11 @@ def run_experiment(tree, library): except QuitException: pass - # except Exception: - # print( - # "\nAn error has occured during the experiment, the experiment will now end." - # ) - # results_dict["error_log"] = traceback.format_exc() + except Exception: + print( + "\nAn error has occured during the experiment, the experiment will now end." + ) + results_dict["error_log"] = traceback.format_exc() # finally: results_dict["final_behavior_tree"] = serialize_tree(tree) From f84253f1aa0f402380af4dbd1283cbac3191c647 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 20:30:40 +0000 Subject: [PATCH 34/40] replace name with display name --- examples/entering-a-room.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/entering-a-room.json b/examples/entering-a-room.json index 0b9ee06..2c96146 100644 --- a/examples/entering-a-room.json +++ b/examples/entering-a-room.json @@ -21,61 +21,61 @@ "behavior_library": [ { "id": "anonymous-sequence", - "display_name": "", + "name": "", "type": "Sequence", "show": false }, { "id": "approach-door", - "display_name": "Approach the door", + "name": "Approach the door", "type": "Behavior", "show": true }, { "id": "open-door", - "display_name": "Open the door", + "name": "Open the door", "type": "Behavior", "show": true }, { "id": "go-through-doorway", - "display_name": "Go through the doorway", + "name": "Go through the doorway", "type": "Behavior", "show": true }, { "id": "knock-on-the-door", - "display_name": "Knock on the door", + "name": "Knock on the door", "type": "Behavior", "show": true }, { "id": "sing-a-song", - "display_name": "Sing a song", + "name": "Sing a song", "type": "Behavior", "show": true }, { "id": "announce-your-presence", - "display_name": "Announce your presence", + "name": "Announce your presence", "type": "Behavior", "show": true }, { "id": "contact-supervisor-guidance", - "display_name": "Contact supervisor for guidance", + "name": "Contact supervisor for guidance", "type": "Behavior", "show": true }, { "id": "stop", - "display_name": "Stop", + "name": "Stop", "type": "Behavior", "show": true }, { "id": "return-to-charging-station", - "display_name": "Return to charging station", + "name": "Return to charging station", "type": "Behavior", "show": false } From aaf3386daed885524496a9bdb65c3ffd362dbe1a Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Wed, 9 Oct 2024 20:40:04 +0000 Subject: [PATCH 35/40] delete overtaken mutate tree file --- src/social_norms_trees/mutate_tree.py | 563 -------------------------- 1 file changed, 563 deletions(-) delete mode 100644 src/social_norms_trees/mutate_tree.py diff --git a/src/social_norms_trees/mutate_tree.py b/src/social_norms_trees/mutate_tree.py deleted file mode 100644 index 3146f15..0000000 --- a/src/social_norms_trees/mutate_tree.py +++ /dev/null @@ -1,563 +0,0 @@ -"""Example of using worlds with just an integer for the state of the world""" - -import warnings -from functools import partial, wraps -from itertools import islice -from typing import TypeVar, Optional, List - -import click -import py_trees - -from datetime import datetime - -from social_norms_trees.custom_node_library import CustomBehavior, CustomSequence - - -T = TypeVar("T", bound=py_trees.behaviour.Behaviour) - - -def print_tree(tree: py_trees.behaviour.Behaviour): - tree_display = py_trees.display.unicode_tree(tree) - print(tree_display) - - -def iterate_nodes(tree: py_trees.behaviour.Behaviour): - """ - - Examples: - >>> list(iterate_nodes(py_trees.behaviours.Dummy())) # doctest: +ELLIPSIS - [] - - >>> list(iterate_nodes( - ... py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Dummy(), - ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - [, - ] - - >>> list(iterate_nodes( - ... py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Dummy(), - ... py_trees.behaviours.Dummy(), - ... py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Dummy(), - ... ]), - ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - [, - , - , - , - ] - - """ - yield tree - for child in tree.children: - yield from iterate_nodes(child) - - -def enumerate_nodes(tree: py_trees.behaviour.Behaviour): - """ - - Examples: - >>> list(enumerate_nodes(py_trees.behaviours.Dummy())) # doctest: +ELLIPSIS - [(0, )] - - >>> list(enumerate_nodes( - ... py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Dummy(), - ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - [(0, ), - (1, )] - - >>> list(enumerate_nodes( - ... py_trees.composites.Sequence("s1", False, children=[ - ... py_trees.behaviours.Dummy(), - ... py_trees.behaviours.Success(), - ... py_trees.composites.Sequence("s2", False, children=[ - ... py_trees.behaviours.Dummy(), - ... ]), - ... py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Failure(), - ... py_trees.behaviours.Periodic("p", n=1), - ... ]), - ... ]))) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - [(0, ), - (1, ), - (2, ), - (3, ), - (4, ), - (5, ), - (6, ), - (7, )] - - """ - return enumerate(iterate_nodes(tree)) - - -def label_tree_lines( - tree: py_trees.behaviour.Behaviour, - labels: List[str], - representation=py_trees.display.unicode_tree, -) -> str: - max_len = max([len(s) for s in labels]) - padded_labels = [s.rjust(max_len) for s in labels] - - tree_representation_lines = representation(tree).split("\n") - enumerated_tree_representation_lines = [ - f"{i}: {t}" for i, t in zip(padded_labels, tree_representation_lines) - ] - - output = "\n".join(enumerated_tree_representation_lines) - return output - - -def format_children_with_indices(composite: py_trees.composites.Composite) -> str: - """ - Examples: - >>> tree = py_trees.composites.Sequence("s1", False, children=[ - ... py_trees.behaviours.Dummy(), - ... py_trees.behaviours.Success(), - ... py_trees.composites.Sequence("s2", False, children=[ - ... py_trees.behaviours.Dummy(), - ... ]), - ... py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Failure(), - ... py_trees.behaviours.Periodic("p", n=1), - ... ]), - ... ]) - >>> print(format_children_with_indices(tree)) # doctest: +NORMALIZE_WHITESPACE - _: [-] s1 - 0: --> Dummy - 1: --> Success - 2: [-] s2 - _: --> Dummy - 3: [-] - _: --> Failure - _: --> p - """ - index_strings = [] - i = 0 - for b in iterate_nodes(composite): - if b in composite.children: - index_strings.append(str(i)) - i += 1 - else: - index_strings.append("_") - - index_strings.append(str(i)) - - output = label_tree_lines(composite, index_strings) - return output - - -def format_parents_with_indices(composite: py_trees.composites.Composite) -> str: - index_strings = [] - i = 0 - for b in iterate_nodes(composite): - if ( - b.__class__.__name__ == "CustomSequence" - or b.__class__.__name__ == "CustomSelector" - ): - index_strings.append(str(i)) - else: - index_strings.append("_") - i += 1 - - output = label_tree_lines(composite, index_strings) - return output - - -def format_tree_with_indices( - tree: py_trees.behaviour.Behaviour, - show_root: bool = False, -) -> tuple[str, List[str]]: - """ - Examples: - >>> print(format_tree_with_indices(py_trees.behaviours.Dummy())) - 0: --> Dummy - - >>> tree = py_trees.composites.Sequence("s1", False, children=[ - ... py_trees.behaviours.Dummy(), - ... py_trees.behaviours.Success(), - ... py_trees.composites.Sequence("s2", False, children=[ - ... py_trees.behaviours.Dummy(), - ... ]), - ... py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Failure(), - ... py_trees.behaviours.Periodic("p", n=1), - ... ]), - ... ]) - >>> print(format_tree_with_indices(tree)) # doctest: +NORMALIZE_WHITESPACE - 0: [-] s1 - 1: --> Dummy - 2: --> Success - 3: [-] s2 - 4: --> Dummy - 5: [-] - 6: --> Failure - 7: --> p - - """ - - index_strings = [] - index = 0 - for i, node in enumerate_nodes(tree): - if i == 0 and not show_root: - index_strings.append("_") - else: - index_strings.append(str(index)) - index += 1 - output = label_tree_lines(tree, index_strings) - - return output, index_strings[1:] - - -def say(message): - print(message) - - -def prompt_identify_node( - tree: py_trees.behaviour.Behaviour, - message: str = "Which node?", - display_nodes: bool = True, - show_root: bool = False, -) -> py_trees.behaviour.Behaviour: - node_index = prompt_identify_tree_iterator_index( - tree=tree, message=message, display_nodes=display_nodes, show_root=show_root - ) - node = next(islice(iterate_nodes(tree), node_index, node_index + 1)) - return node - - -def prompt_identify_parent_node( - tree: py_trees.behaviour.Behaviour, - message: str = "Which position?", - display_nodes: bool = True, -) -> int: - if display_nodes: - text = f"{format_parents_with_indices(tree)}\n{message}" - else: - text = f"{message}" - node_index = click.prompt( - text=text, - type=int, - ) - - node = next(islice(iterate_nodes(tree), node_index, node_index + 1)) - return node - - -def prompt_identify_tree_iterator_index( - tree: py_trees.behaviour.Behaviour, - message: str = "Which position?", - display_nodes: bool = True, - show_root: bool = False, -) -> int: - if display_nodes: - format_tree_text, index_options = format_tree_with_indices(tree, show_root) - text = f"{format_tree_text}\n{message}" - else: - _, index_options = format_tree_with_indices(tree, show_root) - text = f"{message}" - node_index = click.prompt( - text=text, - type=click.Choice(index_options, case_sensitive=False), - show_choices=False, - ) - return int(node_index) - - -def prompt_identify_child_index( - tree: py_trees.behaviour.Behaviour, - message: str = "Which position?", - display_nodes: bool = True, -) -> int: - if display_nodes: - text = f"{format_children_with_indices(tree)}\n{message}" - else: - text = f"{message}" - node_index = click.prompt( - text=text, - type=int, - ) - return node_index - - -def add_child( - tree: T, - parent: Optional[py_trees.composites.Composite] = None, - child: Optional[py_trees.behaviour.Behaviour] = None, -) -> T: - """Add a behaviour to the tree - - Examples: - >>> tree = py_trees.composites.Sequence("", False, children=[]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE - [-] - - >>> print(py_trees.display.ascii_tree(add_child(tree, py_trees.behaviours.Success()))) - ... # doctest: +NORMALIZE_WHITESPACE - [-] - --> Success - - """ - if parent is None: - parent = prompt_identify_node( - tree, f"Which parent node do you want to add the child to?" - ) - if child is None: - child_key = click.prompt( - text="What should the child do?", type=click.Choice(["say"]) - ) - match child_key: - case "say": - message = click.prompt(text="What should it say?", type=str) - - child_function = wraps(say)(partial(say, message)) - child_type = py_trees.meta.create_behaviour_from_function( - child_function - ) - child = child_type() - case _: - raise NotImplementedError() - parent.add_child(child) - return tree - - -def remove_node(tree: T, node: Optional[py_trees.behaviour.Behaviour] = None) -> T: - """Remove a behaviour from the tree - - Examples: - >>> tree = py_trees.composites.Sequence("", False, children=[ - ... py_trees.behaviours.Success(), - ... failure_node := py_trees.behaviours.Failure()]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE - [-] - --> Success - --> Failure - - >>> print(py_trees.display.ascii_tree(remove_node(tree, failure_node))) - ... # doctest: +NORMALIZE_WHITESPACE - [-] - --> Success - - """ - if node is None: - node = prompt_identify_node(tree, f"Which node do you want to remove?") - parent_node = node.parent - if parent_node is None: - warnings.warn( - f"{node}'s parent is None, so we can't remove it. You can't remove the root node." - ) - 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_, "display_name": node.display_name}, - ], - "timestamp": datetime.now().isoformat(), - } - else: - raise NotImplementedError() - - 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 - - Examples: - >>> tree = py_trees.composites.Sequence("", False, children=[]) - - """ - - if node is None: - node = prompt_identify_node(tree, f"Which node do you want to move?") - if new_parent is None: - new_parent = prompt_identify_parent_node( - tree, f"What should its parent be?", display_nodes=True - ) - if index is None: - index = prompt_identify_child_index(new_parent) - - 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_, - "display_name": node.display_name, - }, - ], - "timestamp": datetime.now().isoformat(), - } - return tree, action_log - - return tree - - -def exchange_nodes( - tree: T, - node0: Optional[py_trees.behaviour.Behaviour] = None, - node1: Optional[py_trees.behaviour.Behaviour] = None, -) -> T: - """Exchange two behaviours in the tree - - Examples: - >>> tree = py_trees.composites.Sequence("", False, children=[ - ... s:=py_trees.behaviours.Success(), - ... f:=py_trees.behaviours.Failure(), - ... ]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE - [-] - --> Success - --> Failure - - >>> print(py_trees.display.ascii_tree(exchange_nodes(tree, s, f))) - ... # doctest: +NORMALIZE_WHITESPACE - [-] - --> Failure - --> Success - - >>> tree = py_trees.composites.Sequence("", False, children=[ - ... a:= py_trees.composites.Sequence("A", False, children=[ - ... py_trees.behaviours.Dummy() - ... ]), - ... py_trees.composites.Sequence("B", False, children=[ - ... py_trees.behaviours.Success(), - ... c := py_trees.composites.Sequence("C", False, children=[]) - ... ]) - ... ]) - >>> print(py_trees.display.ascii_tree(tree)) # doctest: +NORMALIZE_WHITESPACE - [-] - [-] A - --> Dummy - [-] B - --> Success - [-] C - >>> print(py_trees.display.ascii_tree(exchange_nodes(tree, a, c))) - ... # doctest: +NORMALIZE_WHITESPACE - [-] - [-] C - [-] B - --> Success - [-] A - --> Dummy - """ - - if node0 is None: - node0 = prompt_identify_node(tree, f"Which node do you want to switch?") - if node1 is None: - node1 = prompt_identify_node( - tree, f"Which node do you want to switch?", display_nodes=False - ) - - 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, True) - tree = move_node(tree, node1, node0_parent, node0_index, True) - - nodes = [] - if node0.__class__.__name__ != "CustomBehavior": - nodes.append( - { - "display_name": node0.name, - } - ) - else: - nodes.append({"id": node0.id_, "display_name": node0.display_name}) - - if node1.__class__.__name__ != "CustomBehavior": - nodes.append( - { - "display_name": node1.display_name, - } - ) - else: - nodes.append({"id": node1.id_, "display_name": node1.display_name}) - - 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.behavior_from_display_name.keys(), 1 - ): - print(f"{idx}. {tree_name}") - - choices = [str(i + 1) for i in range(len(behavior_library.behaviors))] - node_index = click.prompt(text=text, type=click.Choice(choices), show_choices=False) - - node_key = list(behavior_library.behavior_from_display_name.keys())[node_index - 1] - - return behavior_library.behavior_from_display_name[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?" - ) - - if behavior["type"] == "Behavior": - new_node = CustomBehavior( - name=behavior["display_name"], - id_=behavior["id"], - display_name=behavior["display_name"], - ) - - elif behavior["type"] == "Sequence": - new_node = CustomSequence( - name=behavior["display_name"], - id_=behavior["id"], - display_name=behavior["display_name"], - ) - - 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_, "display_name": new_node.display_name}, - "timestamp": datetime.now().isoformat(), - } - - return tree, action_log From db32799bf0c2b3ac26d3ddc364f1dac913801d85 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Thu, 10 Oct 2024 15:20:23 +0000 Subject: [PATCH 36/40] remove unused import --- src/social_norms_trees/serialize_tree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/social_norms_trees/serialize_tree.py b/src/social_norms_trees/serialize_tree.py index 2400b4d..04d05c5 100644 --- a/src/social_norms_trees/serialize_tree.py +++ b/src/social_norms_trees/serialize_tree.py @@ -1,4 +1,3 @@ -import py_trees from social_norms_trees.custom_node_library import CustomBehavior, CustomSequence From 99cf31e1061531a36241ae774bac2161130db6e8 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Thu, 10 Oct 2024 15:20:55 +0000 Subject: [PATCH 37/40] reformat --- src/social_norms_trees/atomic_mutations.py | 61 +++++++++------------- src/social_norms_trees/behavior_library.py | 3 +- src/social_norms_trees/serialize_tree.py | 5 +- src/social_norms_trees/ui_wrapper.py | 35 +++++++------ 4 files changed, 47 insertions(+), 57 deletions(-) diff --git a/src/social_norms_trees/atomic_mutations.py b/src/social_norms_trees/atomic_mutations.py index 9bfa0dd..850c319 100644 --- a/src/social_norms_trees/atomic_mutations.py +++ b/src/social_norms_trees/atomic_mutations.py @@ -23,12 +23,10 @@ BehaviorIdentifier = TypeVar( "BehaviorIdentifier", bound=Union[ExistingNode, NewNode, CompositeIndex] ) -BehaviorTreeNode = TypeVar( - "BehaviorTreeNode", bound=py_trees.behaviour.Behaviour) +BehaviorTreeNode = TypeVar("BehaviorTreeNode", bound=py_trees.behaviour.Behaviour) BehaviorTree = TypeVar("BehaviorTree", bound=BehaviorTreeNode) BehaviorLibrary = TypeVar("BehaviorLibrary", bound=List[BehaviorTreeNode]) -TreeOrLibrary = TypeVar( - "TreeOrLibrary", bound=Union[BehaviorTree, BehaviorLibrary]) +TreeOrLibrary = TypeVar("TreeOrLibrary", bound=Union[BehaviorTree, BehaviorLibrary]) # ============================================================================= @@ -451,15 +449,11 @@ def get_library_mapping(library: BehaviorLibrary) -> NodeMappingRepresentation: mapping = {str(i): n for i, n in enumerate(library)} labels = list(mapping.keys()) - representation = "\n".join( - [f"{i}: {n.name}" for i, n in enumerate(library)]) + representation = "\n".join([f"{i}: {n.name}" for i, n in enumerate(library)]) return NodeMappingRepresentation(mapping, labels, representation) -prompt_identify_library_node = partial( - prompt_identify, - function=get_library_mapping -) +prompt_identify_library_node = partial(prompt_identify, function=get_library_mapping) def get_composite_mapping(tree: BehaviorTree, skip_label="_"): @@ -502,10 +496,7 @@ def get_composite_mapping(tree: BehaviorTree, skip_label="_"): return NodeMappingRepresentation(mapping, allowed_labels, representation) -prompt_identify_composite = partial( - prompt_identify, - function=get_composite_mapping -) +prompt_identify_composite = partial(prompt_identify, function=get_composite_mapping) def get_child_index_mapping(tree: BehaviorTree, skip_label="_"): @@ -559,10 +550,7 @@ def get_child_index_mapping(tree: BehaviorTree, skip_label="_"): return NodeMappingRepresentation(mapping, allowed_labels, representation) -prompt_identify_child_index = partial( - prompt_identify, - function=get_child_index_mapping -) +prompt_identify_child_index = partial(prompt_identify, function=get_child_index_mapping) def get_position_mapping(tree): @@ -570,18 +558,18 @@ def get_position_mapping(tree): - [-] S0 - --> {1} - [-] S1 - --> {2} - --> Dummy - --> {3} - --> {4} - [-] S2 - --> {5} - --> Failure - --> {6} - --> {7} + [-] S0 + --> {1} + [-] S1 + --> {2} + --> Dummy + --> {3} + --> {4} + [-] S2 + --> {5} + --> Failure + --> {6} + --> {7} @@ -595,8 +583,7 @@ def get_position_mapping(tree): # Wrapper functions for the atomic operations which give them a UI. -MutationResult = namedtuple( - "MutationResult", ["result", "tree", "function", "kwargs"]) +MutationResult = namedtuple("MutationResult", ["result", "tree", "function", "kwargs"]) def mutate_chooser(*fs: Union[Callable], message="Which action?"): @@ -622,7 +609,9 @@ def mutate_chooser(*fs: Union[Callable], message="Which action?"): def mutate_ui( f: Callable, -) -> Callable[[py_trees.behaviour.Behaviour, List[py_trees.behaviour.Behaviour]], MutationResult]: +) -> Callable[ + [py_trees.behaviour.Behaviour, List[py_trees.behaviour.Behaviour]], MutationResult +]: """Factory function for a tree mutator UI. This creates a version of the atomic function `f` which prompts the user for the appropriate arguments @@ -663,14 +652,14 @@ def prompt_get_mutate_arguments(annotation: GenericAlias, tree, library): return node elif annotation_ == str(CompositeIndex): _logger.debug("in CompositeIndex") - composite_node = prompt_identify_composite( - tree, message="Which parent?") + composite_node = prompt_identify_composite(tree, message="Which parent?") index = prompt_identify_child_index(composite_node) return composite_node, index elif annotation_ == str(NewNode): _logger.debug("in NewNode") new_node = prompt_identify_library_node( - library, message="Which node from the library?") + library, message="Which node from the library?" + ) return new_node else: _logger.debug("in 'else'") diff --git a/src/social_norms_trees/behavior_library.py b/src/social_norms_trees/behavior_library.py index 4704b64..4cbd710 100644 --- a/src/social_norms_trees/behavior_library.py +++ b/src/social_norms_trees/behavior_library.py @@ -4,8 +4,7 @@ def __init__(self, behavior_list): self.behavior_from_display_name = { behavior["name"]: behavior for behavior in behavior_list } - self.behavior_from_id = { - behavior["id"]: behavior for behavior in behavior_list} + self.behavior_from_id = {behavior["id"]: behavior for behavior in behavior_list} def __iter__(self): for i in self.behaviors: diff --git a/src/social_norms_trees/serialize_tree.py b/src/social_norms_trees/serialize_tree.py index 04d05c5..e12ba62 100644 --- a/src/social_norms_trees/serialize_tree.py +++ b/src/social_norms_trees/serialize_tree.py @@ -61,8 +61,9 @@ def deserialize_library_element(description: dict): if node_type == "Sequence": if "children" in description.keys(): - children = [deserialize_library_element( - child) for child in description["children"]] + children = [ + deserialize_library_element(child) for child in description["children"] + ] else: children = [] diff --git a/src/social_norms_trees/ui_wrapper.py b/src/social_norms_trees/ui_wrapper.py index f096cb0..a81fe6e 100644 --- a/src/social_norms_trees/ui_wrapper.py +++ b/src/social_norms_trees/ui_wrapper.py @@ -20,7 +20,11 @@ remove, end_experiment, ) -from social_norms_trees.serialize_tree import deserialize_library_element, serialize_tree, deserialize_tree +from social_norms_trees.serialize_tree import ( + deserialize_library_element, + serialize_tree, + deserialize_tree, +) from social_norms_trees.behavior_library import BehaviorLibrary @@ -50,8 +54,7 @@ def experiment_setup(db, origin_tree): print("\n") participant_id = participant_login() - experiment_id = initialize_experiment_record( - db, participant_id, origin_tree) + experiment_id = initialize_experiment_record(db, participant_id, origin_tree) print("\nSetup Complete.\n") @@ -66,8 +69,7 @@ def participant_login(): def load_resources(file_path): try: - print( - f"\nLoading behavior tree and behavior library from {file_path}...\n") + print(f"\nLoading behavior tree and behavior library from {file_path}...\n") with open(file_path, "r") as file: resources = json.load(file) @@ -80,8 +82,7 @@ def load_resources(file_path): behavior_list = resources.get("behavior_library") context_paragraph = resources.get("context") - behavior_tree = deserialize_tree( - behavior_tree, BehaviorLibrary(behavior_list)) + behavior_tree = deserialize_tree(behavior_tree, BehaviorLibrary(behavior_list)) behavior_library = [deserialize_library_element(e) for e in behavior_list] @@ -138,7 +139,7 @@ def run_experiment(tree, library): results_dict = { "start_time": datetime.now().isoformat(), "initial_behavior_tree": serialize_tree(tree), - "action_log": [] + "action_log": [], } try: @@ -148,11 +149,13 @@ def run_experiment(tree, library): if f is end_experiment: break results = f(tree, library) - results_dict["action_log"].append({ - "type": results.function.__name__, - "kwargs": serialize_function_arguments(results.kwargs), - "time": datetime.now().isoformat(), - }) + results_dict["action_log"].append( + { + "type": results.function.__name__, + "kwargs": serialize_function_arguments(results.kwargs), + "time": datetime.now().isoformat(), + } + ) except QuitException: pass @@ -183,8 +186,7 @@ def main( ], db_file: Annotated[ pathlib.Path, - typer.Option( - help="file where the experimental results will be written"), + typer.Option(help="file where the experimental results will be written"), ] = "db.json", verbose: Annotated[bool, typer.Option("--verbose")] = False, debug: Annotated[bool, typer.Option("--debug")] = False, @@ -206,8 +208,7 @@ def main( # load tree to run experiment on, and behavior library - original_tree, behavior_library, context_paragraph = load_resources( - resources_file) + original_tree, behavior_library, context_paragraph = load_resources(resources_file) print(f"\nContext of this experiment: {context_paragraph}") participant_id, experiment_id = experiment_setup(db, original_tree) From b282737628d4ba5b21776525fde5a35d48710521 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Thu, 10 Oct 2024 17:30:32 +0000 Subject: [PATCH 38/40] fix deserialize tests --- src/social_norms_trees/serialize_tree.py | 30 +++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/social_norms_trees/serialize_tree.py b/src/social_norms_trees/serialize_tree.py index e12ba62..930b107 100644 --- a/src/social_norms_trees/serialize_tree.py +++ b/src/social_norms_trees/serialize_tree.py @@ -43,7 +43,35 @@ def serialize_tree(tree, include_children=True): def deserialize_library_element(description: dict): """ Examples: - >>> deserialize_library_element({"type": "Sequence"}) + >>> s = deserialize_library_element({"type": "Sequence", "name": "Sequence 0", "id": "s0"}) + >>> s + + + >>> s.id_ + 's0' + + >>> s.name + 'Sequence 0' + + >>> s.children + [] + + TODO: Implement selectors + >>> deserialize_library_element({"type": "Selector", "name": "Selector 0", "id": "s0"}) + Traceback (most recent call last): + ... + NotImplementedError: node_type=Selector is not implemented + + >>> b = deserialize_library_element({"type": "Behavior", "name": "Behavior 0", "id": "b0"}) + >>> b + + + >>> b.id_ + 'b0' + + >>> b.name + 'Behavior 0' + """ assert isinstance(description["type"], str), ( From a0c6135e62a40c4fe6e6f3fcd1e04bf854e078eb Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Thu, 10 Oct 2024 17:38:28 +0000 Subject: [PATCH 39/40] fix setting black formatter as deafult for python --- .vscode/settings.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index a990b47..bf6870d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,12 @@ { - "editor.defaultFormatter": "ms-python.black-formatter", - "black-formatter.importStrategy": "fromEnvironment", "editor.formatOnSave": true, + "black-formatter.importStrategy": "fromEnvironment", "python.testing.pytestArgs": [ "." ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } } From b48da8412045033bff191b7bffc95f83e1450806 Mon Sep 17 00:00:00 2001 From: John Gerrard Holland Date: Thu, 10 Oct 2024 17:40:15 +0000 Subject: [PATCH 40/40] remove unused config for black --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4fc4e24..3ff2675 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,6 +123,3 @@ version.source = "vcs" [tool.pytest.ini_options] addopts = "--doctest-modules" - -[tool.black] -target-version = ['py311']