View Javadoc
1   package net.bmahe.genetics4j.gp.combination;
2   
3   import java.util.ArrayDeque;
4   import java.util.ArrayList;
5   import java.util.Collections;
6   import java.util.Deque;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  import java.util.random.RandomGenerator;
13  import java.util.stream.Collectors;
14  
15  import org.apache.commons.lang3.Validate;
16  
17  import net.bmahe.genetics4j.core.chromosomes.Chromosome;
18  import net.bmahe.genetics4j.core.chromosomes.TreeChromosome;
19  import net.bmahe.genetics4j.core.chromosomes.TreeNode;
20  import net.bmahe.genetics4j.core.combination.ChromosomeCombinator;
21  import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;
22  import net.bmahe.genetics4j.gp.Operation;
23  
24  final class ProgramChromosomeCombinator<T extends Comparable<T>> implements ChromosomeCombinator<T> {
25  
26  	private final RandomGenerator randomGenerator;
27  
28  	public ProgramChromosomeCombinator(final RandomGenerator _randomGenerator) {
29  		Validate.notNull(_randomGenerator);
30  
31  		this.randomGenerator = _randomGenerator;
32  	}
33  
34  	@SuppressWarnings("rawtypes")
35  	protected Map<Class, List<TreeNode<Operation<?>>>> returnedTypeToNode(final TreeNode<Operation<?>> root) {
36  		Validate.notNull(root);
37  
38  		final Map<Class, List<TreeNode<Operation<?>>>> returnedTypeIndex = new HashMap<>();
39  
40  		final Deque<TreeNode<Operation<?>>> nodes = new ArrayDeque<>();
41  		nodes.add(root);
42  
43  		while (nodes.isEmpty() == false) {
44  			final TreeNode<Operation<?>> node = nodes.remove();
45  
46  			final Operation<?> operation = node.getData();
47  			final Class returnedType = operation.returnedType();
48  
49  			returnedTypeIndex.computeIfAbsent(returnedType, k -> new ArrayList<>());
50  			returnedTypeIndex.get(returnedType)
51  					.add(node);
52  
53  			if (node.getChildren() != null && node.getChildren()
54  					.isEmpty() == false) {
55  				nodes.addAll(node.getChildren());
56  			}
57  		}
58  
59  		return returnedTypeIndex;
60  	}
61  
62  	protected TreeNode<Operation<?>> copyAndReplace(final TreeNode<Operation<?>> root,
63  			final TreeNode<Operation<?>> replaced, final TreeNode<Operation<?>> replacement) {
64  		Validate.notNull(root);
65  		Validate.notNull(replaced);
66  		Validate.notNull(replacement);
67  
68  		if (root == replaced) {
69  			return copyAndReplace(replacement, replaced, replacement);
70  		}
71  
72  		final Operation<?> data = root.getData();
73  		final List<TreeNode<Operation<?>>> children = root.getChildren();
74  
75  		final List<TreeNode<Operation<?>>> copiedChildren = children == null ? null
76  				: children.stream()
77  						.map(child -> copyAndReplace(child, replaced, replacement))
78  						.collect(Collectors.toList());
79  
80  		final TreeNode<Operation<?>> copy = new TreeNode<>(data);
81  		if (children.isEmpty() == false) {
82  			copy.addChildren(copiedChildren);
83  		}
84  
85  		return copy;
86  	}
87  
88  	@SuppressWarnings("rawtypes")
89  	private final TreeNode<Operation<?>> mix(final TreeNode<Operation<?>> rootA, final TreeNode<Operation<?>> rootB,
90  			final Set<Class> acceptableClasses, final Map<Class, List<TreeNode<Operation<?>>>> returnedTypeToNodeA,
91  			final Map<Class, List<TreeNode<Operation<?>>>> returnedTypeToNodeB) {
92  		Validate.notNull(rootA);
93  		Validate.notNull(rootB);
94  		Validate.notNull(acceptableClasses);
95  		Validate.isTrue(acceptableClasses.isEmpty() == false);
96  		Validate.notNull(returnedTypeToNodeA);
97  		Validate.notNull(returnedTypeToNodeB);
98  
99  		final int targetClassIndex = randomGenerator.nextInt(acceptableClasses.size());
100 		final Class targetClass = acceptableClasses.stream()
101 				.skip(targetClassIndex)
102 				.findFirst()
103 				.get();
104 
105 		final List<TreeNode<Operation<?>>> candidateReplacedNodes = returnedTypeToNodeA.get(targetClass);
106 		final TreeNode<Operation<?>> replacedNode = candidateReplacedNodes
107 				.get(randomGenerator.nextInt(candidateReplacedNodes.size()));
108 
109 		final List<TreeNode<Operation<?>>> candidateReplacementNodes = returnedTypeToNodeB.get(targetClass);
110 		final TreeNode<Operation<?>> replacementNode = candidateReplacementNodes
111 				.get(randomGenerator.nextInt(candidateReplacementNodes.size()));
112 
113 		return copyAndReplace(rootA, replacedNode, replacementNode);
114 	}
115 
116 	@SuppressWarnings({ "rawtypes", "unchecked" })
117 	@Override
118 	public List<Chromosome> combine(final AbstractEAConfiguration<T> eaConfiguration, final Chromosome chromosome1,
119 			final T firstParentFitness, final Chromosome chromosome2, final T secondParentFitness) {
120 		Validate.notNull(chromosome1);
121 		Validate.notNull(chromosome2);
122 
123 		if (chromosome1 instanceof TreeChromosome<?> == false) {
124 			throw new IllegalArgumentException("This mutator does not support chromosome of type " + chromosome1.getClass()
125 					.getSimpleName());
126 		}
127 
128 		if (chromosome2 instanceof TreeChromosome<?> == false) {
129 			throw new IllegalArgumentException("This mutator does not support chromosome of type " + chromosome2.getClass()
130 					.getSimpleName());
131 		}
132 
133 		if (chromosome1 == chromosome2) {
134 			return Collections.emptyList();
135 		}
136 
137 		final TreeChromosome<Operation<?>> treeChromosome1 = (TreeChromosome<Operation<?>>) chromosome1;
138 		final TreeNode<Operation<?>> root1 = treeChromosome1.getRoot();
139 		final Map<Class, List<TreeNode<Operation<?>>>> returnedTypeToNode1 = returnedTypeToNode(root1);
140 
141 		final TreeChromosome<Operation<?>> treeChromosome2 = (TreeChromosome<Operation<?>>) chromosome2;
142 		final TreeNode<Operation<?>> root2 = treeChromosome2.getRoot();
143 		final Map<Class, List<TreeNode<Operation<?>>>> returnedTypeToNode2 = returnedTypeToNode(root2);
144 
145 		final Set<Class> acceptableClasses = new HashSet<>();
146 		acceptableClasses.addAll(returnedTypeToNode1.keySet());
147 		acceptableClasses.retainAll(returnedTypeToNode2.keySet());
148 
149 		final List<Chromosome> children = new ArrayList<>();
150 
151 		if (acceptableClasses.isEmpty() == false) {
152 
153 			final TreeNode<Operation<?>> child1 = mix(root1,
154 					root2,
155 					acceptableClasses,
156 					returnedTypeToNode1,
157 					returnedTypeToNode2);
158 			final TreeChromosome<Operation<?>> child1Chromosome = new TreeChromosome<Operation<?>>(child1);
159 
160 			final TreeNode<Operation<?>> child2 = mix(root2,
161 					root1,
162 					acceptableClasses,
163 					returnedTypeToNode2,
164 					returnedTypeToNode1);
165 			final TreeChromosome<Operation<?>> child2Chromosome = new TreeChromosome<Operation<?>>(child2);
166 
167 			children.add(child1Chromosome);
168 			children.add(child2Chromosome);
169 		}
170 
171 		return children;
172 	}
173 }