def setUp(self): self.tree = BinaryTree() self.root = BinaryTreeNode('*') self.tree.root = self.root self.root.add_left('A') self.root.add_right('B') np.random.seed(42)
def test_crossover(self): np.random.seed(10) tree_1 = BinaryTree(BinaryTreeNode('*')) tree_1.root.add_left('A') tree_1.root.add_right('B') tree_2 = BinaryTree(BinaryTreeNode('+')) tree_2.root.add_left('C') tree_2.root.add_right('D') # tests bad type self.assertRaises(TypeError, SubtreeExchangeRecombinator.crossover, 'bad type') self.assertRaises(TypeError, SubtreeExchangeRecombinator.crossover, [tree_1, tree_2, 45]) parents = [tree_1, tree_2] recombinator = SubtreeExchangeRecombinator() result_1, result_2 = recombinator.crossover(parents) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree) self.assertEqual(result_1.root.value, '*') self.assertEqual(result_1.root.left.value, 'A') self.assertEqual(result_1.root.right.value, 'D') self.assertEqual(result_2.root.value, '+') self.assertEqual(result_2.root.left.value, 'C') self.assertEqual(result_2.root.right.value, 'B')
def test_structural_hamming_dist_complex_trees(self): # tree 1 # * # / \ # 10 20 # / # 40 root_1 = BinaryTreeNode('*') left = root_1.add_left(10) root_1.add_right(20) left.add_left(40) tree_1 = BinaryTree(root_1) # tree 2 # + # / \ # 10 20 # / \ # 50 40 root_2 = BinaryTreeNode('+') left = root_2.add_left(10) root_2.add_right(20) left.add_right(40) left.add_left(50) tree_2 = BinaryTree(root_2) result = structural_hamming_dist(tree_1, tree_2) self.assertEqual(2 / 3, result)
def test_crossover_roots(self): root = BinaryTreeNode('*') root.add_left('B') right = root.add_right('+') right.add_left('D') rr = right.add_right('*') rr.add_left('F') rr.add_right('G') tree_1 = BinaryTree(root) root = BinaryTreeNode('+') left = root.add_left('+') root.add_right('J') left.add_left('K') left.add_right('L') tree_2 = BinaryTree(root) parents = [tree_1, tree_2] result_1, result_2 = self.recombinator.crossover(parents) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree) self.assertEqual(result_1, tree_1) self.assertEqual(result_2, tree_2)
def test_swap_complex_trees(self): node_1 = BinaryTreeNode('*') node_1.add_left('A') right = node_1.add_right('B') right.add_right('R') tree_1 = BinaryTree(node_1) node_2 = BinaryTreeNode('+') left = node_2.add_left('C') node_2.add_right('D') left.add_left('L') tree_2 = BinaryTree(node_2) a = node_1.right b = node_2.left # should be # * + # / \ / \ # A C , B D # / \ # L R SubtreeExchangeRecombinatorBase._swap_subtrees(a, b, tree_1, tree_2) root_1 = tree_1.root self.check_root(root_1, '*', 'A', 'C') self.check_leaf(root_1.left, 'A', '*') self._check_node(root_1.right, 'C', 'L', None, '*') self.check_leaf(root_1.right.left, 'L', 'C') root_2 = tree_2.root self.check_root(root_2, '+', 'B', 'D') self._check_node(root_2.left, 'B', None, 'R', '+') self.check_leaf(root_2.right, 'D', '+') self.check_leaf(root_2.left.right, 'R', 'B')
def test_crossover(self): tree_1 = BinaryTree(BinaryTreeNode('*')) tree_1.root.add_left('A') tree_1.root.add_right('B') tree_2 = BinaryTree(BinaryTreeNode('+')) tree_2.root.add_left('C') tree_2.root.add_right('D') # tests bad type self.assertRaises(TypeError, SubtreeExchangeLeafBiasedRecombinator.crossover, 'bad type') self.assertRaises(TypeError, SubtreeExchangeLeafBiasedRecombinator.crossover, [tree_1, tree_2, 45]) parents = [tree_1, tree_2] recombinator = SubtreeExchangeLeafBiasedRecombinator(t_prob=0) result_1, result_2 = recombinator.crossover(parents) self.assertEqual(result_1, tree_1) self.assertEqual(result_2, tree_2) recombinator = SubtreeExchangeLeafBiasedRecombinator(t_prob=1) result_1, result_2 = recombinator.crossover(parents) self.assertEqual(result_1.root.value, '*') self.assertEqual(result_2.root.value, '+') recombinator = SubtreeExchangeLeafBiasedRecombinator() result_1, result_2 = recombinator.crossover(parents) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree) recombinator = SubtreeExchangeLeafBiasedRecombinator() stump = BinaryTree(BinaryTreeNode('C')) result_1, result_2 = recombinator.crossover([tree_1, stump]) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree)
def test_select_node_pair_only_operands(self): node_1 = BinaryTreeNode('A') node_2 = BinaryTreeNode('B') node_3 = BinaryTreeNode('C') node_4 = BinaryTreeNode('D') common_region = [(node_1, node_2), (node_3, node_4)] result = self.recombinator.select_node_pair(common_region) self.assertIn(result, common_region)
class TestSubTreeExchangeMutator(TestCase): def setUp(self): self.tree = BinaryTree() self.root = BinaryTreeNode('*') self.tree.root = self.root self.root.add_left('A') self.root.add_right('B') def test_max_depth(self): self.assertRaises(ValueError, SubTreeExchangeMutator, max_depth=-2, binary_tree_node_cls=BinaryTreeNode) def test__mutate_subtree_exchange(self): max_depth = 2 tree_gen = GrowGenerator(max_depth) result = SubTreeExchangeMutator._mutate_subtree_exchange(['+', '*'], [1, 2, 3], self.tree, tree_gen) self.assertIsInstance(result, BinaryTree) max_height = max_depth + 1 initial_height = self.tree.height() final_height = result.height() self.assertLessEqual(final_height, initial_height + max_height) def test__swap_mut_subtree(self): random_tree = BinaryTree() left = random_tree.root = BinaryTreeNode('*') ll = random_tree.root.add_left('C') lr = random_tree.root.add_right('D') r = 0 # A result = SubTreeExchangeMutator._swap_mut_subtree(self.tree, r, random_tree) self.assertIsInstance(result, BinaryTree) self.assertEqual(result.height(), 3) self.assertEqual(self.tree.root.left, left) self.assertEqual(self.tree.root.left.left, ll) self.assertEqual(self.tree.root.left.right, lr) def test_to_dict(self): mutator = SubTreeExchangeMutator(4, BinaryTreeNode) actual = mutator.to_dict() self.assertIsInstance(actual, dict) self.assertEqual("src.evalg.genprog.mutation", actual["__module__"]) self.assertEqual("SubTreeExchangeMutator", actual["__class__"]) self.assertEqual("src.evalg.encoding", actual["binary_tree_node_module_name"]) self.assertEqual("BinaryTreeNode", actual["binary_tree_node_cls_name"]) self.assertEqual(mutator.max_depth, actual["max_depth"]) def test_from_dict(self): test_cases = (SubTreeExchangeMutator, TreeMutator, Serializable) for cls in test_cases: with self.subTest(name=cls.__name__): mutator = SubTreeExchangeMutator(4, BinaryTreeNode) actual = cls.from_dict(mutator.to_dict()) self.assertIsInstance(actual, SubTreeExchangeMutator) self.assertEqual(BinaryTreeNode, actual.binary_tree_node_cls) self.assertEqual(mutator.max_depth, actual.max_depth)
def _hd(node_1: BinaryTreeNode, node_2: BinaryTreeNode) -> float: """Hamming distance between p and q 0 if p = q (Both terminal nodes of equal value) 1 otherwise (different terminal node type or internal node) """ if node_1.is_leaf() and node_2.is_leaf() and node_1.value == node_2.value: return 0 else: return 1
def test_structural_hamming_dist_stumps(self): tree_1 = BinaryTree(BinaryTreeNode('*')) tree_2 = BinaryTree(BinaryTreeNode('*')) result = structural_hamming_dist(tree_1, tree_2) self.assertEqual(0, result) tree_1 = BinaryTree(BinaryTreeNode('+')) tree_2 = BinaryTree(BinaryTreeNode('*')) result = structural_hamming_dist(tree_1, tree_2) self.assertEqual(1, result)
def test_crossover_trees_roots_selected(self): root_1 = BinaryTreeNode('*') root_1.add_left('B') right = root_1.add_right('+') right.add_left('D') rr = right.add_right('*') rr.add_left('F') rr.add_right('G') tree_1 = BinaryTree(root_1) root_2 = BinaryTreeNode('+') left = root_2.add_left('+') right = root_2.add_right('*') left.add_left('K') left.add_right('L') right.add_right('M') right.add_left('N') tree_2 = BinaryTree(root_2) parents = [tree_1, tree_2] self.recombinator.select_node_pair = MagicMock() self.recombinator.select_node_pair.return_value = (root_1, root_2) result_1, result_2 = self.recombinator.crossover(parents) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree) self.recombinator.select_node_pair.assert_called_once() self.assertEqual(result_1, tree_1) self.assertEqual(result_2, tree_2)
def test_crossover_stumps(self): tree_1 = BinaryTree(BinaryTreeNode('*')) tree_2 = BinaryTree(BinaryTreeNode('+')) parents = [tree_1, tree_2] result_1, result_2 = self.recombinator.crossover(parents) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree) self.assertEqual(result_1, tree_1) self.assertEqual(result_2, tree_2)
def test_postfix_tokens(self): tree = BinaryTree() root = BinaryTreeNode('*') tree.root = root left = root.add_left('+') right = root.add_right('+') left.add_left('A') left.add_right('B') right.add_left('C') right.add_right('D') tokens = ['A', 'B', 'C', tree.root.label, '+', 'D', '+'] result = tree.postfix_tokens() self.assertCountEqual(result, tokens) tree = BinaryTree() root = BinaryTreeNode('+') tree.root = root left = root.add_left('+') right = root.add_right('+') left.add_left('A') left.add_right('B') right.add_left('C') right.add_right('D') tokens = ['A', 'B', '+', 'C', '+', 'D', '+'] result = tree.postfix_tokens() self.assertCountEqual(result, tokens)
def test_contains(self): root = BinaryTreeNode('*') self.assertIn('*', root) left = root.add_left(10) self.assertIn('*', root) self.assertIn(10, root) self.assertIn(10, left) self.assertIn(10, root.left) right = root.add_right(20) self.assertIn('*', root) self.assertIn(20, right) self.assertIn(20, right) self.assertIn(20, root.right)
def test_crossover_stump_and_tree(self): tree_1 = BinaryTree(BinaryTreeNode('*')) tree_1.root.add_left('A') tree_1.root.add_right('B') tree_2 = BinaryTree(BinaryTreeNode('+')) parents = [tree_1, tree_2] result_1, result_2 = self.recombinator.crossover(parents) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree) self.assertEqual(result_1, tree_1) self.assertEqual(result_2, tree_2)
def _get_common_region( self, node_1: BinaryTreeNode, node_2: BinaryTreeNode, valid_pairs: Optional[List[NodePair]] = None) -> None: """Recursive helper to get common region.""" if valid_pairs is None: valid_pairs = [] if node_1 and node_2: both_leaves = node_1.is_leaf() and node_2.is_leaf() both_internals = not node_1.is_leaf() and not node_2.is_leaf() if both_leaves or both_internals: valid_pairs.append((node_1, node_2)) self._get_common_region(node_1.left, node_2.left, valid_pairs) self._get_common_region(node_1.right, node_2.right, valid_pairs)
def test_swap_same_node(self): node = BinaryTreeNode('*') tree = BinaryTree(node) a = b = node SubtreeExchangeRecombinatorBase._swap_subtrees(a, b, tree, tree) root = tree.root self._check_node(root, '*', None, None, None)
def _swap_subtrees(node_1: BinaryTreeNode, node_2: BinaryTreeNode, tree_1: BinaryTree, tree_2: BinaryTree) -> None: """Swap parents and children of nodes. :param node_1: :param node_2: :param tree_1: tree corresponding to node 1 :param tree_2: tree corresponding to node 2 :return: """ if node_1 == node_2: return if node_1 is None or node_2 is None: return if not node_1.has_parent() and not node_2.has_parent(): return if not node_1.has_parent(): tree_1.root = node_2 if node_2.is_left_child(): node_2.parent.left = node_1 else: node_2.parent.right = node_1 elif not node_2.has_parent(): tree_2.root = node_1 if node_1.is_left_child(): node_1.parent.left = node_2 else: node_1.parent.right = node_2 else: if node_1.is_left_child(): if node_2.is_left_child(): node_2.parent.left, node_1.parent.left = node_1, node_2 else: node_2.parent.right, node_1.parent.left = node_1, node_2 else: if node_2.is_left_child(): node_2.parent.left, node_1.parent.right = node_1, node_2 else: node_2.parent.right, node_1.parent.right = node_1, node_2 node_1.parent, node_2.parent = node_2.parent, node_1.parent
def test_swap_leaves(self): node_1 = BinaryTreeNode('A') tree_1 = BinaryTree(node_1) node_2 = BinaryTreeNode('B') tree_2 = BinaryTree(node_2) a = node_1 b = node_2 # should be # A B SubtreeExchangeRecombinatorBase._swap_subtrees(a, b, tree_1, tree_2) root_1 = tree_1.root self.check_stump(root_1, 'A') root_2 = tree_2.root self.check_stump(root_2, 'B')
def test_swap_none_node(self): node = BinaryTreeNode('*') tree = BinaryTree(node) a = b = node SubtreeExchangeRecombinatorBase._swap_subtrees(None, b, tree, tree) root = tree.root self.check_stump(root, '*') SubtreeExchangeRecombinatorBase._swap_subtrees(a, None, tree, tree) self.check_stump(root, '*')
def test_len(self): root = BinaryTreeNode('*') self.assertEqual(len(root), 1) left = root.add_left(10) self.assertEqual(len(root), 2) self.assertEqual(len(left), 1) right = root.add_right(20) self.assertEqual(len(root), 3) self.assertEqual(len(left), 1) self.assertEqual(len(right), 1) ll = left.add_left(40) self.assertEqual(len(root), 4) self.assertEqual(len(left), 2) self.assertEqual(len(right), 1) self.assertEqual(len(ll), 1) lr = left.add_right(50) self.assertEqual(len(root), 5) self.assertEqual(len(left), 3) self.assertEqual(len(right), 1) self.assertEqual(len(ll), 1) self.assertEqual(len(lr), 1) rl = right.add_left(60) self.assertEqual(len(root), 6) self.assertEqual(len(left), 3) self.assertEqual(len(right), 2) self.assertEqual(len(ll), 1) self.assertEqual(len(lr), 1) self.assertEqual(len(rl), 1) rr = right.add_right(70) self.assertEqual(len(root), 7) self.assertEqual(len(left), 3) self.assertEqual(len(right), 3) self.assertEqual(len(ll), 1) self.assertEqual(len(lr), 1) self.assertEqual(len(rl), 1) self.assertEqual(len(rr), 1)
def test_crossover_leaves(self): root_1 = BinaryTreeNode('*') root_1.add_left('B') right = root_1.add_right('+') right.add_left('D') rr = right.add_right('*') rr.add_left('F') rr.add_right('G') tree_1 = BinaryTree(root_1) root_2 = BinaryTreeNode('+') left = root_2.add_left('+') right = root_2.add_right('*') left.add_left('K') left.add_right('L') right.add_right('M') right.add_left('N') tree_2 = BinaryTree(root_2) parents = [tree_1, tree_2] self.recombinator.select_node_pair = MagicMock() self.recombinator.select_node_pair.return_value = (root_1.right.left, root_2.right.left) result_1, result_2 = self.recombinator.crossover(parents) self.assertIsInstance(result_1, BinaryTree) self.assertIsInstance(result_2, BinaryTree) self.recombinator.select_node_pair.assert_called_once() self.check_root(result_1.root, '*', 'B', '+') self.check_leaf(result_1.root.left, 'B', '*') self._check_node(result_1.root.right, '+', 'N', '*', '*') self.check_leaf(result_1.root.right.left, 'N', '+') self._check_node(result_1.root.right.right, '*', 'F', 'G', '+') self.check_leaf(result_1.root.right.right.left, 'F', '*') self.check_leaf(result_1.root.right.right.right, 'G', '*') self.check_root(result_2.root, '+', '+', '*') self._check_node(result_2.root.left, '+', 'K', 'L', '+') self._check_node(result_2.root.right, '*', 'D', 'M', '+') self.check_leaf(result_2.root.left.left, 'K', '+') self.check_leaf(result_2.root.left.right, 'L', '+') self.check_leaf(result_2.root.right.left, 'D', '*') self.check_leaf(result_2.root.right.right, 'M', '*')
def test__swap_mut_subtree(self): random_tree = BinaryTree() left = random_tree.root = BinaryTreeNode('*') ll = random_tree.root.add_left('C') lr = random_tree.root.add_right('D') r = 0 # A result = SubTreeExchangeMutator._swap_mut_subtree(self.tree, r, random_tree) self.assertIsInstance(result, BinaryTree) self.assertEqual(result.height(), 3) self.assertEqual(self.tree.root.left, left) self.assertEqual(self.tree.root.left.left, ll) self.assertEqual(self.tree.root.left.right, lr)
def shd(node_1: BinaryTreeNode, node_2: BinaryTreeNode, hd: Callable[[BinaryTreeNode, BinaryTreeNode], float]) -> float: """Structural Hamming distance (SHD) :param node_1: :param node_2: :param hd: :return: """ if node_1 is None or node_2 is None: return 1 # first get arity of each node arity_1 = 0 arity_2 = 0 if node_1.has_left_child(): arity_1 += 1 if node_1.has_right_child(): arity_1 += 1 if node_2.has_left_child(): arity_2 += 1 if node_2.has_right_child(): arity_2 += 1 if arity_1 != arity_2: return 1 else: if arity_1 == 0: # both are leaves return hd(node_1, node_2) else: m = arity_1 ham_dist = hd(node_1, node_2) children_dist_sum = sum([ shd(node_1.left, node_2.left, hd), shd(node_1.right, node_2.right, hd) ]) return (1 / (m + 1)) * (ham_dist + children_dist_sum)
class TestTreePointMutator(TestCase): def setUp(self): self.tree = BinaryTree() self.root = BinaryTreeNode('*') self.tree.root = self.root self.root.add_left('A') self.root.add_right('B') np.random.seed(42) def test_mutate(self): mutator = TreePointMutator() tree = mutator.mutate(['+', '*'], ['A', 'B', 'C', 'D'], self.tree) self.assertEqual(tree.root.label, '+') self.assertIsInstance(tree, BinaryTree) def test_to_dict(self): mutator = TreePointMutator(BinaryTreeNode) actual = mutator.to_dict() self.assertIsInstance(actual, dict) self.assertEqual("src.evalg.genprog.mutation", actual["__module__"]) self.assertEqual("TreePointMutator", actual["__class__"]) self.assertEqual("src.evalg.encoding", actual["binary_tree_node_module_name"]) self.assertEqual("BinaryTreeNode", actual["binary_tree_node_cls_name"]) def test_from_dict(self): test_cases = (TreePointMutator, TreeMutator, Serializable) for cls in test_cases: with self.subTest(name=cls.__name__): mutator = TreePointMutator(BinaryTreeNode) actual = cls.from_dict(mutator.to_dict()) self.assertIsInstance(actual, TreePointMutator) self.assertEqual(BinaryTreeNode, actual.binary_tree_node_cls) def tearDown(self): # reset random seed np.random.seed()
class TestHalfAndHalfMutator(TestCase): def setUp(self): self.tree = BinaryTree() self.root = BinaryTreeNode('*') self.tree.root = self.root self.root.add_left('A') self.root.add_right('B') def test_mutate(self): individual = self.tree operands = ['A', 'B', 'C'] mutator = HalfAndHalfMutator(max_depth=2) result = mutator.mutate(['+', '*'], operands, individual) self.assertIsInstance(result, BinaryTree) max_height = mutator.max_depth + 1 self.assertLessEqual(result.height(), self.tree.height() + max_height) def test_to_dict(self): mutator = HalfAndHalfMutator(4, BinaryTreeNode) actual = mutator.to_dict() self.assertIsInstance(actual, dict) self.assertEqual("src.evalg.genprog.mutation", actual["__module__"]) self.assertEqual("HalfAndHalfMutator", actual["__class__"]) self.assertEqual("src.evalg.encoding", actual["binary_tree_node_module_name"]) self.assertEqual("BinaryTreeNode", actual["binary_tree_node_cls_name"]) self.assertEqual(mutator.max_depth, actual["max_depth"]) def test_from_dict(self): test_cases = (HalfAndHalfMutator, SubTreeExchangeMutator, TreeMutator, Serializable) for cls in test_cases: with self.subTest(name=cls.__name__): mutator = HalfAndHalfMutator(4, BinaryTreeNode) actual = cls.from_dict(mutator.to_dict()) self.assertIsInstance(actual, HalfAndHalfMutator) self.assertEqual(BinaryTreeNode, actual.binary_tree_node_cls) self.assertEqual(mutator.max_depth, actual.max_depth)
def test_get_common_region(self): root_1 = BinaryTreeNode('*') root_1.add_left('B') right = root_1.add_right('+') right.add_left('D') rr = right.add_right('*') rr.add_left('F') rr.add_right('G') root_2 = BinaryTreeNode('+') left = root_2.add_left('+') right = root_2.add_right('*') left.add_left('K') left.add_right('L') right.add_right('M') right.add_left('N') result = self.recombinator.get_common_region(root_1, root_2) self.assertListEqual(result, [(root_1, root_2), (root_1.right, root_2.right), (root_1.right.left, root_2.right.left)])
def test_height(self): tree = BinaryTree() self.assertEqual(tree.height(), 0) tree.root = BinaryTreeNode('*') self.assertEqual(tree.height(), 1) left = tree.root.add_left(10) self.assertEqual(tree.height(), 2) right = tree.root.add_right(20) self.assertEqual(tree.height(), 2) ll = left.add_left(40) self.assertEqual(tree.height(), 3) left.add_right(50) self.assertEqual(tree.height(), 3) right.add_left(60) self.assertEqual(tree.height(), 3) right.add_right(70) self.assertEqual(tree.height(), 3) ll.add_left(80) self.assertEqual(tree.height(), 4)
def test_swap_right_and_stump(self): node_1 = BinaryTreeNode('*') node_1.add_left('A') node_1.add_right('B') tree_1 = BinaryTree(node_1) node_2 = BinaryTreeNode('C') tree_2 = BinaryTree(node_2) a = node_1.right b = node_2 # should be # * # / \ , B # A C SubtreeExchangeRecombinatorBase._swap_subtrees(a, b, tree_1, tree_2) root_1 = tree_1.root self.check_root(root_1, '*', 'A', 'C') self.check_leaf(root_1.left, 'A', '*') self.check_leaf(root_1.right, 'C', '*') root_2 = tree_2.root self.check_stump(root_2, 'B')
def setUp(self): self.root_val = 'Parent Value' self.root = BinaryTreeNode(self.root_val) self.left_child_val = 42 self.right_child_val = 13