/* * Copyright 2015 S. Webber * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.oakgp.evolve.crossover; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.oakgp.TestUtils.assertNodeEquals; import static org.oakgp.TestUtils.readFunctionNode; import static org.oakgp.util.DummyRandom.random; import static org.oakgp.util.DummyRandom.GetIntExpectation.nextInt; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import org.junit.Test; import org.oakgp.evolve.GeneticOperator; import org.oakgp.node.FunctionNode; import org.oakgp.node.Node; import org.oakgp.select.DummyNodeSelector; import org.oakgp.util.DummyRandom; public class SubtreeCrossoverTest { private static final int DEFAULT_DEPTH = 9; @Test public void testFunctionNodes() { DummyRandom dummyRandom = random().nextInt(2).returns(1).nextInt(5).returns(3).build(); DummyNodeSelector dummySelector = new DummyNodeSelector("(+ 9 5)", "(* 7 (- 8 v5))"); GeneticOperator c = new SubtreeCrossover(dummyRandom, DEFAULT_DEPTH); Node result = c.evolve(dummySelector); assertNodeEquals("(+ 9 (- 8 v5))", result); dummyRandom.assertEmpty(); dummySelector.assertEmpty(); } @Test public void testConstantNodes() { DummyRandom dummyRandom = nextInt(1).returns(0); DummyNodeSelector dummySelector = new DummyNodeSelector("1", "2"); GeneticOperator c = new SubtreeCrossover(dummyRandom, DEFAULT_DEPTH); Node result = c.evolve(dummySelector); assertNodeEquals("2", result); dummySelector.assertEmpty(); } /** Test crossover using trees that use a mix of types (booleans and integers) */ @Test public void testMixedTypes() { String input = "(+ 4 5)"; String output = "(if (< 6 7) 8 9)"; DummyRandom dummyRandom = random().nextInt(2).returns(0, 1, 1, 1, 1, 1).nextInt(5).returns(0, 0, 1, 2, 3, 4).build(); DummyNodeSelector dummySelector = new DummyNodeSelector(input, output, input, output, input, output, input, output, input, output, input, output); GeneticOperator c = new SubtreeCrossover(dummyRandom, DEFAULT_DEPTH); assertNodeEquals("(+ 6 5)", c.evolve(dummySelector)); assertNodeEquals("(+ 4 6)", c.evolve(dummySelector)); assertNodeEquals("(+ 4 7)", c.evolve(dummySelector)); assertNodeEquals("(+ 4 8)", c.evolve(dummySelector)); assertNodeEquals("(+ 4 9)", c.evolve(dummySelector)); assertNodeEquals("(+ 4 " + output + ")", c.evolve(dummySelector)); dummyRandom.assertEmpty(); dummySelector.assertEmpty(); } /** Test attempted crossover when selected node in first parent has a type that is not present in the second parent */ @Test public void testNoMatchingTypes() { String input = "(+ (if (< 6 7) 8 9) 5)"; String output = "(+ 1 2)"; DummyRandom dummyRandom = nextInt(7).returns(2); DummyNodeSelector dummySelector = new DummyNodeSelector(input, output); GeneticOperator c = new SubtreeCrossover(dummyRandom, DEFAULT_DEPTH); assertNodeEquals(input, c.evolve(dummySelector)); dummyRandom.assertEmpty(); dummySelector.assertEmpty(); } /** Test max depth limit not exceeded */ @Test public void testMaxDepthLimit() { assertCrossoverPossibilities(7); assertCrossoverPossibilities(6); assertCrossoverPossibilities(5); assertCrossoverPossibilities(4); } private void assertCrossoverPossibilities(int maxDepth) { String[] possibilities = { "(+ (+ 9 (+ 3 4)) 5)", "(+ (+ 7 (+ 3 4)) 5)", "(+ (+ 8 (+ 3 4)) 5)", "(+ (+ (* 7 8) (+ 3 4)) 5)", "(+ (+ (* 9 (* 7 8)) (+ 3 4)) 5)", "(+ (+ 6 (+ 3 4)) 5)", "(+ (+ (* (* 9 (* 7 8)) 6) (+ 3 4)) 5)" }; List<FunctionNode> f = Arrays.stream(possibilities).map(s -> readFunctionNode(s)).filter(n -> n.getHeight() <= maxDepth).collect(Collectors.toList()); int numPossibilities = f.size(); assertTrue(numPossibilities > 0); for (int i = 0; i < numPossibilities; i++) { Node result = subtreeCrossover(maxDepth, 6, 0, numPossibilities, i); assertEquals(f.get(i), result); assertTrue(result.getHeight() <= maxDepth); } } private Node subtreeCrossover(int maxDepth, int first, int second, int third, int fourth) { String input = "(+ (+ 2 (+ 3 4)) 5)"; String output = "(* (* 9 (* 7 8)) 6)"; DummyNodeSelector dummySelector = new DummyNodeSelector(input, output); DummyRandom dummyRandom; if (first == third) { dummyRandom = random().nextInt(first).returns(second, fourth).build(); } else { dummyRandom = random().nextInt(first).returns(second).nextInt(third).returns(fourth).build(); } GeneticOperator c = new SubtreeCrossover(dummyRandom, maxDepth); Node result = c.evolve(dummySelector); dummyRandom.assertEmpty(); dummySelector.assertEmpty(); return result; } }