def mutate_gene(genome, p_add, p_delete): """ Mutate only a single gene. """ if p_add < 0 or p_delete < 0: raise Exception("Mutation parameters must not be negative.") if p_add + p_delete > 1: raise Exception("Sum of the mutation probabilities must be less than 1.") mutated_individual = behavior_tree.BT([]) max_attempts = 100 attempts = 0 while (not mutated_individual.is_valid() or mutated_individual.bt == genome) and attempts < max_attempts: mutated_individual.set(genome) index = random.randint(0, len(genome) - 1) mutation = random.random() if mutation < p_delete: mutated_individual.delete_node(index) elif mutation < p_delete + p_add: mutated_individual.add_node(index) else: mutated_individual.change_node(index) #Close and trim bt accordingly to the change mutated_individual.close() mutated_individual.trim() attempts += 1 if attempts >= max_attempts and (not mutated_individual.is_valid() or mutated_individual.bt == genome): mutated_individual = behavior_tree.BT([]) return mutated_individual.bt
def crossover_genome(genome1, genome2, replace=True): # pylint: disable=too-many-branches """ Do crossover between genomes at random points """ bt1 = behavior_tree.BT(genome1) bt2 = behavior_tree.BT(genome2) offspring1 = behavior_tree.BT([]) offspring2 = behavior_tree.BT([]) if bt1.is_valid() and bt2.is_valid(): max_attempts = 100 attempts = 0 found = False while not found and attempts < max_attempts: offspring1.set(bt1.bt) offspring2.set(bt2.bt) cop1 = -1 cop2 = -1 if len(genome1) == 1: cop1 = 0 #Change whole tree else: while not offspring1.is_subtree(cop1): cop1 = random.randint(1, len(genome1) - 1) if len(genome2) == 1: cop2 = 0 #Change whole tree else: while not offspring2.is_subtree(cop2): cop2 = random.randint(1, len(genome2) - 1) if replace: offspring1.swap_subtrees(offspring2, cop1, cop2) else: subtree1 = offspring1.get_subtree(cop1) subtree2 = offspring2.get_subtree(cop2) if len(genome1) == 1: index1 = random.randint(0, 1) else: index1 = random.randint(1, len(genome1) - 1) if len(genome2) == 1: index2 = random.randint(0, 1) else: index2 = random.randint(1, len(genome2) - 1) offspring1.insert_subtree(subtree2, index1) offspring2.insert_subtree(subtree1, index2) attempts += 1 if offspring1.is_valid() and offspring2.is_valid(): found = True if not found: offspring1.set([]) offspring2.set([]) return offspring1.bt, offspring2.bt
def get_bt_from_root(self): """ Returns bt string (actually a list) from py tree root by cleaning the ascii tree from py trees Not complete or beautiful by any means but works for many trees """ string = pt.display.ascii_tree(self.root) print(string) string = string.replace('[o] ', '') string = string.replace('\t', '') string = string.replace('-->', '') string = string.replace('Fallback', 'f(') string = string.replace('Sequence', 's(') bt = string.split('\n') bt = bt[:-1] #Remove empty element because of final newline prev_leading_spaces = 999999 for i in range(len(bt) - 1, -1, -1): leading_spaces = len(bt[i]) - len(bt[i].lstrip(' ')) bt[i] = bt[i].lstrip(' ') if leading_spaces > prev_leading_spaces: for _ in range( round((leading_spaces - prev_leading_spaces) / 4)): bt.insert(i + 1, ')') prev_leading_spaces = leading_spaces bt_obj = behavior_tree.BT(bt) bt_obj.close() return bt_obj.bt
def test_close(): """ Tests close function """ bt = behavior_tree.BT([]) bt.close() assert bt.bt == [] #Correct tree with just one action bt.set(['a0']).close() assert bt.bt == ['a0'] #Correct tree bt.set(['s(', 's(', 'a0', ')', ')']).close() assert bt.bt == ['s(', 's(', 'a0', ')', ')'] #Missing up at end bt.set(['s(', 's(', 'a0', ')', 's(', 'a0', 's(', 'a0']).close() assert bt.bt == [ 's(', 's(', 'a0', ')', 's(', 'a0', 's(', 'a0', ')', ')', ')' ] #Too many up at end bt.set(['s(', 'a0', ')', ')', ')']).close() assert bt.bt == ['s(', 'a0', ')'] #Too many up but not at the end bt.set(['s(', 's(', 'a0', ')', ')', ')', 'a1', ')']).close() assert bt.bt == ['s(', 's(', 'a0', ')', 'a1', ')']
def test_is_subtree(): """ Tests is_subtree function """ bt = behavior_tree.BT(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']) assert bt.is_subtree(0) assert bt.is_subtree(1) assert not bt.is_subtree(5)
def test_delete_node(): """ Tests delete_node function """ bt = behavior_tree.BT([]) bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).delete_node(0) assert bt.bt == [] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 's(', 'a0', ')', ')']).delete_node(0) assert bt.bt == [] bt.set( ['s(', 'a0', 'f(', 'a0', 's(', 'a0', ')', ')', 's(', 'a0', ')', ')']).delete_node(0) assert bt.bt == [] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).delete_node(1) assert bt.bt == ['s(', 'f(', 'a0', 'a0', ')', 'a0', ')'] bt.set(['s(', 'a0', 'f(', 'a1', 'a2', ')', 'a3', ')']).delete_node(2) assert bt.bt == ['s(', 'a0', 'a3', ')'] bt.set(['s(', 'a0', 'f(', 'a0', ')', 'a0', ')']).delete_node(3) assert bt.bt == ['s(', 'a0', 'f(', ')', 'a0', ')'] bt.set(['s(', 'a0', ')']).delete_node(2) assert bt.bt == ['s(', 'a0', ')']
def test_add_node(): """ Tests add_node function """ bt = behavior_tree.BT([]) random.seed(1337) bt.set(['a0']).add_node(0, 's(') assert bt.bt == ['s(', 'a0', ')'] bt.set(['s(', 'a0', 'a0', ')']).add_node(2) assert bt.bt == ['s(', 'a0', 'a3', 'a0', ')'] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).add_node(2, 'a0') assert bt.bt == ['s(', 'a0', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')'] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).add_node(3, 'a0') assert bt.bt == ['s(', 'a0', 'f(', 'a0', 'a0', 'a0', ')', 'a0', ')'] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).add_node(0, 'f(') assert bt.bt == ['f(', 's(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')', ')'] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).add_node(4, 's(') assert bt.is_valid() bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).add_node(2, 'f(') assert bt.is_valid() bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).add_node(1, 'f(') assert bt.is_valid() bt.set(['s(', 'a0', 'f(', 'c1', 'a0', ')', ')']).add_node(2, 'f(') assert bt.is_valid()
def __init__(self, string, behaviors, world_interface=None, root=None, verbose=False): # pylint: disable=too-many-arguments if root is not None: self.root = root string = self.get_bt_from_root() self.bt = behavior_tree.BT(string) self.depth = self.bt.depth() self.length = self.bt.length() self.world_interface = world_interface self.verbose = verbose self.behaviors = behaviors self.failed = False self.timeout = False if root is None: self.root, has_children = behaviors.get_node_from_string( string[0], world_interface, self.verbose) string.pop(0) else: has_children = False super().__init__(root=self.root) if has_children: self.create_from_string(string, self.root)
def test_random(): """ Tests random function """ bt = behavior_tree.BT([]) random.seed(1337) for length in range(1, 11): bt.random(length) assert bt.length() == length assert bt.is_valid()
def test_find_parent(): """ Tests find_parent function """ bt = behavior_tree.BT([]) bt.set(['s(', 'a0', 'f(', 'a0', ')', 'a0', ')']) assert bt.find_parent(0) is None assert bt.find_parent(1) == 0 assert bt.find_parent(2) == 0 assert bt.find_parent(3) == 2 assert bt.find_parent(4) == 2 assert bt.find_parent(5) == 0
def test_find_children(): """ Tests find_children function """ bt = behavior_tree.BT([]) bt.set(['s(', 'a0', 'f(', 'a0', ')', 'a0', ')']) assert bt.find_children(0) == [1, 2, 5] assert bt.find_children(1) == [] assert bt.find_children(2) == [3] assert bt.find_children(3) == [] assert bt.find_children(4) == [] assert bt.find_children(5) == []
def test_swap_subtrees(): """ Tests swap_subtrees function """ bt1 = behavior_tree.BT(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']) bt2 = behavior_tree.BT( ['s(', 'a0', 'f(', 'a0', 'a0', ')', 's(', 'a0', 'a0', ')', ')']) bt1.swap_subtrees(bt2, 6, 6) assert bt1.bt == [ 's(', 'a0', 'f(', 'a0', 'a0', ')', 's(', 'a0', 'a0', ')', ')' ] assert bt2.bt == ['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')'] #Invalid subtree because it's an up node, no swap bt1.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']) bt2.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 's(', 'a0', 'a0', ')', ')']) bt1.swap_subtrees(bt2, 5, 6) assert bt1.bt == ['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')'] assert bt2.bt == [ 's(', 'a0', 'f(', 'a0', 'a0', ')', 's(', 'a0', 'a0', ')', ')' ]
def test_crossover_genome(): """ Tests crossover_genome function """ behavior_tree.load_settings_from_file( 'behavior_tree_learning/tests/BT_TEST_SETTINGS.yaml') genome1 = ['s(', 'c0', 'f(', 'c0', 'a0', ')', 'a0', ')'] genome2 = ['f(', 'c1', 's(', 'c1', 'a1', ')', 'a1', ')'] offspring1, offspring2 = gp_bt_interface.crossover_genome(genome1, genome2) assert offspring1 != [] assert offspring2 != [] assert offspring1 != genome1 assert offspring1 != genome2 assert offspring2 != genome1 assert offspring2 != genome2 bt1 = behavior_tree.BT(offspring1) assert bt1.is_valid() bt1 = bt1.set(offspring2) assert bt1.is_valid() genome1 = ['a0'] genome2 = ['a1'] offspring1, offspring2 = gp_bt_interface.crossover_genome(genome1, genome2) assert offspring1 == genome2 assert offspring2 == genome1 genome1 = [] offspring1, offspring2 = gp_bt_interface.crossover_genome(genome1, genome2) assert offspring1 == [] assert offspring2 == [] for i in range(10): random.seed(i) offspring1, offspring2 = gp_bt_interface.crossover_genome(\ gp_bt_interface.random_genome(10), gp_bt_interface.random_genome(10)) bt1 = bt1.set(offspring1) assert bt1.is_valid() bt1 = bt1.set(offspring2) assert bt1.is_valid() genome1 = ['s(', 'f(', 'c0', 'a0', ')', 'a0', ')'] genome2 = ['f(', 's(', 'c1', 'a1', ')', 'a1', ')'] offspring1, offspring2 = gp_bt_interface.crossover_genome(genome1, genome2, replace=False) assert offspring1 != genome1 assert offspring2 != genome2 for gene in genome1: assert gene in offspring1 for gene in genome2: assert gene in offspring2
def test_is_valid(): """ Tests is_valid function """ bt = behavior_tree.BT([]) assert not bt.is_valid() #Valid tree bt.set(['s(', 'c0', 'f(', 'c0', 'a0', ')', 'a0', ')']) assert bt.is_valid() #Minimal valid tree - just an action node bt.set(['a0']) assert bt.is_valid() #Two control nodes at root level - not valid bt.set(['s(', 'c0', 'f(', 'c0', 'a0', ')', 'a0', ')', 's(', 'a0', ')']) assert not bt.is_valid() #Action node at root level - not valid bt.set(['s(', 'c0', 'f(', 'c0', 'a0', ')', ')', 'a0', ')']) assert not bt.is_valid() #Too few up nodes - not valid bt.set(['s(', 'c0', 'f(', 'c0', 'a0', ')', 'a0']) assert not bt.is_valid() #Too few up nodes - not valid bt.set(['s(', 'c0', 'f(', 'c0', 'a0', ')']) assert not bt.is_valid() #No control nodes, but more than one action - not valid bt.set(['a0', 'a0']) assert not bt.is_valid() #Starts with an up node - not valid bt.set([')', 'f(', 'c0', 'a0', ')']) assert not bt.is_valid() #Just a control node - not valid bt.set(['s(', ')']) assert not bt.is_valid() #Just a control node - not valid bt.set(['s(', 's(']) assert not bt.is_valid() #Up just after control node bt.set(['s(', 'f(', ')', 'a0', ')']) assert not bt.is_valid() #Unknown characters bt.set(['s(', 'c0', 'x', 'y', 'z', ')']) assert not bt.is_valid()
def test_insert_subtree(): """ Tests insert_subtree function """ bt1 = behavior_tree.BT(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']) bt1.insert_subtree(['f(', 'a1', ')'], 1) assert bt1.bt == [ 's(', 'f(', 'a1', ')', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')' ] bt1.insert_subtree(['f(', 'a1', ')'], 6) assert bt1.bt == [ 's(', 'f(', 'a1', ')', 'a0', 'f(', 'f(', 'a1', ')', 'a0', 'a0', ')', 'a0', ')' ]
def test_length(): """ Tests bt_length function """ bt = behavior_tree.BT([]) bt.set(['s(', 'a0', 'a1', ')']) assert bt.length() == 3 bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']) assert bt.length() == 6 bt.set(['s(', ')']) assert bt.length() == 1 bt.set(['a0']) assert bt.length() == 1
def test_get_subtree(): """ Tests get_subtree function """ bt = behavior_tree.BT(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']) subtree = bt.get_subtree(1) assert subtree == ['a0'] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 's(', 'a0', 'a0', ')', ')']) subtree = bt.get_subtree(6) assert subtree == ['s(', 'a0', 'a0', ')'] bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', ')']) subtree = bt.get_subtree(2) assert subtree == ['f(', 'a0', 'a0', ')'] subtree = bt.get_subtree(5) assert subtree == []
def test_subtree_is_valid(): """ Tests subtree_is_valid function """ bt = behavior_tree.BT([]) assert bt.is_subtree_valid(['s(', 'f(', 'a0', ')', ')', ')'], True, True) assert not bt.is_subtree_valid(['s(', 'f(', 'a0', ')', ')', ')'], True, False) assert not bt.is_subtree_valid(['f(', 's(', 'a0', ')', ')', ')'], False, True) assert not bt.is_subtree_valid(['f(', 'f(', 'a0', ')', ')', ')'], True, True) assert not bt.is_subtree_valid(['s(', 's(', 'a0', ')', ')', ')'], True, True) assert not bt.is_subtree_valid(['s(', 'f(', 'a0', ')', ')'], True, True) assert bt.is_subtree_valid(['s(', 'f(', 'c0', ')', ')', ')'], True, True)
def test_change_node(): """ Tests change_node function """ bt = behavior_tree.BT([]) random.seed(1337) #No new node given, change to random node bt.set(['s(', 'a0', 'a0', ')']).change_node(2) assert bt.bt[2] != 'a0' #Change control node to action node bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).change_node(2, 'a0') assert bt.bt == ['s(', 'a0', 'a0', 'a0', ')'] #Change control node to action node - correct up must be removed too bt.set(['s(', 'a0', 'f(', 's(', 'a0', ')', 'a0', ')', 'a0', ')']).change_node(2, 'a0') assert bt.bt == ['s(', 'a0', 'a0', 'a0', ')'] bt.set(['s(', 'a0', 'f(', 's(', 'a0', ')', 'a1', ')', 'a0', ')']).change_node(3, 'a0') assert bt.bt == ['s(', 'a0', 'f(', 'a0', 'a1', ')', 'a0', ')'] #Change action node to control node bt.set(['s(', 'a0', 'a0', ')']).change_node(1, 'f(') assert bt.bt == ['s(', 'f(', 'a0', 'a0', ')', 'a0', ')'] #Change action node to action node bt.set(['s(', 'a0', 'a0', ')']).change_node(1, 'a1') assert bt.bt == ['s(', 'a1', 'a0', ')'] #Change control node to control node bt.set(['s(', 'a0', 'a0', ')']).change_node(0, 'f(') assert bt.bt == ['f(', 'a0', 'a0', ')'] #Change up node, not possible bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']).change_node(5, 'a0') assert bt.bt == ['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']
def test_trim(): """ Tests trim function """ bt = behavior_tree.BT([]) bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', 's(', 'a0', ')', ')']) bt.trim() assert bt.bt == ['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', 'a0', ')'] bt.set(['s(', 'a0', 'f(', ')', 'a0', 's(', 'a0', ')', ')']) bt.trim() assert bt.bt == ['s(', 'a0', 'a0', 'a0', ')'] bt.set(['s(', 'a0', 'f(', 'a1', 's(', 'a2', ')', 'a3', ')', 'a4', ')']) bt.trim() assert bt.bt == ['s(', 'a0', 'f(', 'a1', 'a2', 'a3', ')', 'a4', ')'] bt.set(['s(', 'a0', 'f(', 's(', 'a2', 'a3', ')', ')', 'a4', ')']) bt.trim() assert bt.bt == ['s(', 'a0', 'a2', 'a3', 'a4', ')'] bt.set(['s(', 'a0', ')']) bt.trim() assert bt.bt == ['s(', 'a0', ')']
def test_depth(): """ Tests bt_depth function """ bt = behavior_tree.BT([]) #Normal correct tree bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')']) assert bt.depth() == 2 #Goes to 0 before last node - invalid bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0', ')', 's(', 'a0', ')']) assert bt.depth() == -1 #Goes to 0 before last node - invalid bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', ')', 'a0', ')']) assert bt.depth() == -1 #Goes to 0 before last node - invalid bt.set(['s(', 'a0', 'f(', 'a0', 'a0', ')', 'a0']) assert bt.depth() == -1 #Just an action node - no depth bt.set(['a0']) assert bt.depth() == 0
def test_mutate_gene(): """ Tests mutate_gene function """ behavior_tree.load_settings_from_file( 'behavior_tree_learning/tests/BT_TEST_SETTINGS.yaml') genome = ['s(', 'a0', ')'] with pytest.raises(Exception): _ = gp_bt_interface.mutate_gene(genome, p_add=-1, p_delete=1) with pytest.raises(Exception): _ = gp_bt_interface.mutate_gene(genome, p_add=1, p_delete=1) for _ in range(10): #Loop many times to catch random errors mutated_genome = gp_bt_interface.mutate_gene(genome, p_add=1, p_delete=0) assert len(mutated_genome) >= len(genome) mutated_genome = gp_bt_interface.mutate_gene(genome, p_add=0, p_delete=1) assert len(mutated_genome) <= len(genome) mutated_genome = gp_bt_interface.mutate_gene(genome, p_add=0, p_delete=0) bt = behavior_tree.BT(mutated_genome) assert mutated_genome != genome assert bt.is_valid() mutated_genome = gp_bt_interface.mutate_gene(genome, p_add=0.3, p_delete=0.3) bt.set(mutated_genome) assert mutated_genome != genome assert bt.is_valid()
def test_find_up_node(): """ Tests find_up_node function """ bt = behavior_tree.BT([]) bt.set(['s(', 'a0', 'f(', 'a0', ')', 'a0', ')']) assert bt.find_up_node(0) == 6 bt.set(['s(', 'a0', 'f(', 'a0', ')', 'a0', ')']) assert bt.find_up_node(2) == 4 bt.set(['s(', 'a0', 'f(', 's(', 'a0', ')', 'a0', ')']) assert bt.find_up_node(2) == 7 bt.set(['s(', 'a0', 'f(', 'a0', ')', 'a0', ')']) with pytest.raises(Exception): _ = bt.find_up_node(1) bt.set(['s(', 'a0', 'f(', 'a0', ')', 'a0']) with pytest.raises(Exception): _ = bt.find_up_node(0) bt.set(['s(', 's(', 'a0', 'f(', 'a0', ')', 'a0']) with pytest.raises(Exception): _ = bt.find_up_node(1)
def random_genome(length): """ Returns a random genome """ bt = behavior_tree.BT([]) return bt.random(length)
def test_init(): """ Tests init function """ _ = behavior_tree.BT([])