Program.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-4.0.0).
003  * Copyright (c) 2007-2017 Franz Wilhelmstötter
004  *
005  * Licensed under the Apache License, Version 2.0 (the "License");
006  * you may not use this file except in compliance with the License.
007  * You may obtain a copy of the License at
008  *
009  *      http://www.apache.org/licenses/LICENSE-2.0
010  *
011  * Unless required by applicable law or agreed to in writing, software
012  * distributed under the License is distributed on an "AS IS" BASIS,
013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014  * See the License for the specific language governing permissions and
015  * limitations under the License.
016  *
017  * Author:
018  *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019  */
020 package io.jenetics.prog.op;
021 
022 import static java.lang.String.format;
023 import static java.util.Objects.requireNonNull;
024 
025 import java.lang.reflect.Array;
026 import java.util.Random;
027 
028 import io.jenetics.ext.util.FlatTree;
029 import io.jenetics.ext.util.Tree;
030 import io.jenetics.ext.util.TreeNode;
031 import io.jenetics.util.ISeq;
032 import io.jenetics.util.RandomRegistry;
033 
034 /**
035  * This class composes a given operation tree to a new operation. which can
036  * serve as a sub <em>program</em> in an other operation tree.
037  *
038  @param <T> the argument type of the operation
039  *
040  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
041  @version 3.9
042  @since 3.9
043  */
044 public class Program<T> implements Op<T> {
045 
046     private final String _name;
047     private final int _arity;
048     private final Tree<? extends Op<T>, ?> _tree;
049 
050     /**
051      * Create a new program with the given name and the given operation tree.
052      * The arity of the program is calculated from the given operation tree and
053      * set to the maximal arity of the operations of the tree.
054      *
055      @param name the program name
056      @param tree the operation tree
057      @throws NullPointerException if one of the given arguments is {@code null}
058      @throws IllegalArgumentException if the given operation tree is invalid,
059      *         which means there is at least one node where the operation arity
060      *         and the node child count differ.
061      */
062     public Program(final String name, final Tree<? extends Op<T>, ?> tree) {
063         _name = requireNonNull(name);
064         _tree = requireNonNull(tree);
065         check(tree);
066         _arity = tree.breadthFirstStream()
067             .filter(t -> t.getValue() instanceof Var<?>)
068             .mapToInt(v -> ((Var<?>)v.getValue()).index() 1)
069             .max()
070             .orElse(0);
071     }
072 
073     @Override
074     public String name() {
075         return _name;
076     }
077 
078     @Override
079     public int arity() {
080         return _arity;
081     }
082 
083     @Override
084     public T apply(final T[] args) {
085         if (args.length < arity()) {
086             throw new IllegalArgumentException(format(
087                 "Arguments length is smaller than program arity: %d < %d",
088                 args.length, arity()
089             ));
090         }
091 
092         return eval(_tree, args);
093     }
094 
095     /**
096      * Convenient method, which lets you apply the program function without
097      * explicitly create a wrapper array.
098      *
099      @see #apply(Object[])
100      *
101      @param args the function arguments
102      @return the evaluated value
103      @throws NullPointerException if the given variable array is {@code null}
104      @throws IllegalArgumentException if the length of the arguments array
105      *         is smaller than the program arity
106      */
107     @SafeVarargs
108     public final T eval(final T... args) {
109         return apply(args);
110     }
111 
112     @Override
113     public String toString() {
114         return _name;
115     }
116 
117 
118     /* *************************************************************************
119      * Static helper methods.
120      * ************************************************************************/
121 
122     /**
123      * Evaluates the given operation tree with the given variables.
124      *
125      @param <T> the argument type
126      @param tree the operation tree
127      @param variables the input variables
128      @return the result of the operation tree evaluation
129      @throws NullPointerException if one of the arguments is {@code null}
130      @throws IllegalArgumentException if the length of the variable array
131      *         is smaller than the program arity
132      */
133     @SafeVarargs
134     public static <T> T eval(
135         final Tree<? extends Op<T>, ?> tree,
136         final T... variables
137     ) {
138         requireNonNull(tree);
139         requireNonNull(variables);
140 
141         final Op<T> op = tree.getValue();
142         return op.isTerminal()
143             ? op.apply(variables)
144             : op.apply(
145                     tree.childStream()
146                         .map(child -> eval(child, variables))
147                         .toArray(size -> newArray(variables.getClass(), size))
148                 );
149     }
150 
151     @SuppressWarnings("unchecked")
152     private static <T> T[] newArray(final Class<?> arrayType, final int size) {
153         return (T[])Array.newInstance(arrayType.getComponentType(), size);
154     }
155 
156     /**
157      * Validates the given program tree.
158      *
159      @param program the program to validate
160      @throws NullPointerException if the given {@code program} is {@code null}
161      @throws IllegalArgumentException if the given operation tree is invalid,
162      *         which means there is at least one node where the operation arity
163      *         and the node child count differ.
164      */
165     public static void check(final Tree<? extends Op<?>, ?> program) {
166         requireNonNull(program);
167         program.forEach(Program::checkArity);
168     }
169 
170     private static void checkArity(final Tree<? extends Op<?>, ?> node) {
171         if (node.getValue() != null &&
172             node.getValue().arity() != node.childCount())
173         {
174             throw new IllegalArgumentException(format(
175                 "Op arity != child count: %d != %d",
176                 node.getValue().arity(), node.childCount()
177             ));
178         }
179     }
180 
181     /**
182      * Create a new program tree from the given (non) terminal operations with
183      * the desired depth. The created program tree is a <em>full</em> tree.
184      *
185      @param depth the desired depth of the program tree
186      @param operations the list of <em>non</em>-terminal operations
187      @param terminals the list of terminal operations
188      @param <A> the operational type
189      @return a new program tree
190      @throws NullPointerException if one of the given operations is
191      *        {@code null}
192      @throws IllegalArgumentException if the given tree depth is smaller than
193      *         zero
194      */
195     public static <A> TreeNode<Op<A>> of(
196         final int depth,
197         final ISeq<? extends Op<A>> operations,
198         final ISeq<? extends Op<A>> terminals
199     ) {
200         if (depth < 0) {
201             throw new IllegalArgumentException(
202                 "Tree depth is smaller than zero: " + depth
203             );
204         }
205         if (!operations.forAll(o -> !o.isTerminal())) {
206             throw new IllegalArgumentException(
207                 "Operation list contains terminal op."
208             );
209         }
210         if (!terminals.forAll(o -> o.isTerminal())) {
211             throw new IllegalArgumentException(
212                 "Terminal list contains non-terminal op."
213             );
214         }
215 
216         final TreeNode<Op<A>> root = TreeNode.of();
217         fill(depth, root, operations, terminals, RandomRegistry.getRandom());
218         return root;
219     }
220 
221     private static <A> void fill(
222         final int level,
223         final TreeNode<Op<A>> tree,
224         final ISeq<? extends Op<A>> operations,
225         final ISeq<? extends Op<A>> terminals,
226         final Random random
227     ) {
228         final Op<A> op = level == 0
229             ? terminals.get(random.nextInt(terminals.size()))
230             : operations.get(random.nextInt(operations.size()));
231 
232         tree.setValue(op);
233 
234         if (level > 1) {
235             for (int i = 0; i < op.arity(); ++i) {
236                 final TreeNode<Op<A>> node = TreeNode.of();
237                 fill(level - 1, node, operations, terminals, random);
238                 tree.attach(node);
239             }
240         else {
241             for (int i = 0; i < op.arity(); ++i) {
242                 final Op<A> term = terminals.get(random.nextInt(terminals.size()));
243                 tree.attach(TreeNode.of(term));
244             }
245         }
246     }
247 
248     /**
249      * Creates a valid program tree from the given flattened sequence of
250      * op nodes. The given {@code operations} and {@code termination} nodes are
251      * used for <em>repairing</em> the program tree, if necessary.
252      *
253      @param nodes the flattened, possible corrupt, program tree
254      @param terminals the usable non-terminal operation nodes to use for
255      *        reparation
256      @param <A> the operation argument type
257      @return a new valid program tree build from the flattened program tree
258      @throws NullPointerException if one of the arguments is {@code null}
259      @throws IllegalArgumentException if the {@code nodes} sequence is empty
260      */
261     public static <A> TreeNode<Op<A>> toTree(
262         final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
263         final ISeq<? extends Op<A>> terminals
264     ) {
265         if (nodes.isEmpty()) {
266             throw new IllegalArgumentException("Tree nodes must not be empty.");
267         }
268 
269         final Op<A> op = requireNonNull(nodes.get(0).getValue());
270         final TreeNode<Op<A>> tree = TreeNode.of(op);
271         return toTree(
272             tree,
273             0,
274             nodes,
275             offsets(nodes),
276             terminals,
277             RandomRegistry.getRandom()
278         );
279     }
280 
281     private static <A> TreeNode<Op<A>> toTree(
282         final TreeNode<Op<A>> root,
283         final int index,
284         final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
285         final int[] offsets,
286         final ISeq<? extends Op<A>> terminals,
287         final Random random
288     ) {
289         if (index < nodes.size()) {
290             final FlatTree<? extends Op<A>, ?> node = nodes.get(index);
291             final Op<A> op = node.getValue();
292 
293             for (int i  = 0; i < op.arity(); ++i) {
294                 assert offsets[index!= -1;
295 
296                 final TreeNode<Op<A>> treeNode = TreeNode.of();
297                 if (offsets[index+ i < nodes.size()) {
298                     treeNode.setValue(nodes.get(offsets[index+ i).getValue());
299                 else {
300                     treeNode.setValue(terminals.get(random.nextInt(terminals.size())));
301                 }
302 
303                 toTree(
304                     treeNode,
305                     offsets[index+ i,
306                     nodes,
307                     offsets,
308                     terminals,
309                     random
310                 );
311                 root.attach(treeNode);
312             }
313         }
314 
315         return root;
316     }
317 
318     /**
319      * Create the offset array for the given nodes. The offsets are calculated
320      * using the arity of the stored operations.
321      *
322      @param nodes the flattened tree nodes
323      @return the offset array for the given nodes
324      */
325     static int[]
326     offsets(final ISeq<? extends FlatTree<? extends Op<?>, ?>> nodes) {
327         final int[] offsets = new int[nodes.size()];
328 
329         int offset = 1;
330         for (int i = 0; i < offsets.length; ++i) {
331             final Op<?> op = nodes.get(i).getValue();
332 
333             offsets[i= op.isTerminal() ? -: offset;
334             offset += op.arity();
335         }
336 
337         return offsets;
338     }
339 
340 }