Program.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-7.1.1).
003  * Copyright (c) 2007-2022 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 import static io.jenetics.internal.util.Hashes.hash;
025 
026 import java.io.Serial;
027 import java.io.Serializable;
028 import java.util.Objects;
029 import java.util.function.BiFunction;
030 import java.util.random.RandomGenerator;
031 
032 import io.jenetics.util.ISeq;
033 import io.jenetics.util.RandomRegistry;
034 
035 import io.jenetics.ext.util.FlatTree;
036 import io.jenetics.ext.util.Tree;
037 import io.jenetics.ext.util.TreeNode;
038 
039 /**
040  * This class composes a given operation tree to a new operation. which can
041  * serve as a sub <em>program</em> in another operation tree.
042  *
043  @param <T> the argument type of the operation
044  *
045  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
046  @version 4.1
047  @since 3.9
048  */
049 public class Program<T> implements Op<T>, Serializable {
050 
051     @Serial
052     private static final long serialVersionUID = 1L;
053 
054     private final String _name;
055     private final Tree<? extends Op<T>, ?> _tree;
056 
057     /**
058      * Create a new program with the given name and the given operation tree.
059      * The arity of the program is calculated from the given operation tree and
060      * set to the maximal arity of the operations of the tree.
061      *
062      @param name the program name
063      @param tree the operation tree
064      @throws NullPointerException if one of the given arguments is {@code null}
065      @throws IllegalArgumentException if the given operation tree is invalid,
066      *         which means there is at least one node where the operation arity
067      *         and the node child count differ.
068      */
069     public Program(final String name, final Tree<? extends Op<T>, ?> tree) {
070         _name = requireNonNull(name);
071         _tree = requireNonNull(tree);
072         check(tree);
073     }
074 
075     @Override
076     public String name() {
077         return _name;
078     }
079 
080     @Override
081     public int arity() {
082         return 0;
083     }
084 
085     /**
086      * Return the underlying expression tree.
087      *
088      @since 4.1
089      *
090      @return the underlying expression tree
091      */
092     public Tree<Op<T>, ?> tree() {
093         return TreeNode.ofTree(_tree);
094     }
095 
096     @Override
097     public T apply(final T[] args) {
098         if (args.length < arity()) {
099             throw new IllegalArgumentException(format(
100                 "Arguments length is smaller than program arity: %d < %d",
101                 args.length, arity()
102             ));
103         }
104 
105         return eval(_tree, args);
106     }
107 
108     /**
109      * Convenient method, which lets you apply the program function without
110      * explicitly create a wrapper array.
111      *
112      @see #apply(Object[])
113      *
114      @param args the function arguments
115      @return the evaluated value
116      @throws NullPointerException if the given variable array is {@code null}
117      @throws IllegalArgumentException if the length of the arguments array
118      *         is smaller than the program arity
119      */
120     @SafeVarargs
121     public final T eval(final T... args) {
122         return apply(args);
123     }
124 
125     @Override
126     public int hashCode() {
127         return hash(_name, hash(_tree));
128     }
129 
130     @Override
131     public boolean equals(final Object obj) {
132         return obj == this ||
133             obj instanceof Program<?> other &&
134             Objects.equals(other._name, _name&&
135             Objects.equals(other._tree, _tree);
136     }
137 
138     @Override
139     public String toString() {
140         return _name;
141     }
142 
143 
144     /* *************************************************************************
145      * Static helper methods.
146      * ************************************************************************/
147 
148     /**
149      * Evaluates the given operation tree with the given variables. This method
150      * is equivalent to
151      <pre>{@code
152      * final T result = tree.reduce(variables, Op::apply);
153      * }</pre>
154      * but handles the variable sized {@code variables} array more conveniently.
155      *
156      @see Tree#reduce(Object[], BiFunction)
157      *
158      @param <T> the argument type
159      @param tree the operation tree
160      @param variables the input variables
161      @return the result of the operation tree evaluation
162      @throws NullPointerException if one of the arguments is {@code null}
163      @throws IllegalArgumentException if the length of the variable array
164      *         is smaller than the program arity
165      */
166     @SafeVarargs
167     public static <T> T eval(
168         final Tree<? extends Op<T>, ?> tree,
169         final T... variables
170     ) {
171         return tree.reduce(variables, Op::apply);
172     }
173 
174     /**
175      * Validates the given program tree.
176      *
177      @param program the program to validate
178      @throws NullPointerException if the given {@code program} is {@code null}
179      @throws IllegalArgumentException if the given operation tree is invalid,
180      *         which means there is at least one node where the operation arity
181      *         and the node child count differ.
182      */
183     public static void check(final Tree<? extends Op<?>, ?> program) {
184         program.forEach(Program::checkArity);
185     }
186 
187     private static void checkArity(final Tree<? extends Op<?>, ?> node) {
188         if (node.value() != null &&
189             node.value().arity() != node.childCount())
190         {
191             throw new IllegalArgumentException(format(
192                 "Op arity != child count: %d != %d",
193                 node.value().arity(), node.childCount()
194             ));
195         }
196     }
197 
198     /**
199      * Create a new, random program from the given (non) terminal operations
200      * with the desired depth. The created program tree is a <em>full</em> tree.
201      *
202      @since 4.1
203      *
204      @param name the program name
205      @param depth the desired depth of the program tree
206      @param operations the list of <em>non</em>-terminal operations
207      @param terminals the list of terminal operations
208      @param <A> the operational type
209      @return a new program
210      @throws NullPointerException if one of the given operations is
211      *        {@code null}
212      @throws IllegalArgumentException if the given tree depth is smaller than
213      *         zero
214      */
215     public static <A> Program<A> of(
216         final String name,
217         final int depth,
218         final ISeq<? extends Op<A>> operations,
219         final ISeq<? extends Op<A>> terminals
220     ) {
221         return new Program<>(name, of(depth, operations, terminals));
222     }
223 
224     /**
225      * Create a new, random program from the given (non) terminal operations
226      * with the desired depth. The created program tree is a <em>full</em> tree.
227      *
228      @since 4.1
229      *
230      @param name the program name
231      @param depth the desired depth of the program tree
232      @param operations the list of <em>non</em>-terminal operations
233      @param terminals the list of terminal operations
234      @param random the random engine used for creating the program
235      @param <A> the operational type
236      @return a new program
237      @throws NullPointerException if one of the given operations is
238      *        {@code null}
239      @throws IllegalArgumentException if the given tree depth is smaller than
240      *         zero
241      */
242     public static <A> Program<A> of(
243         final String name,
244         final int depth,
245         final ISeq<? extends Op<A>> operations,
246         final ISeq<? extends Op<A>> terminals,
247         final RandomGenerator random
248     ) {
249         return new Program<>(name, of(depth, operations, terminals, random));
250     }
251 
252     /**
253      * Create a new, random program tree from the given (non) terminal
254      * operations with the desired depth. The created program tree is a
255      <em>full</em> tree.
256      *
257      @param depth the desired depth of the program tree
258      @param operations the list of <em>non</em>-terminal operations
259      @param terminals the list of terminal operations
260      @param <A> the operational type
261      @return a new program tree
262      @throws NullPointerException if one of the given operations is
263      *        {@code null}
264      @throws IllegalArgumentException if the given tree depth is smaller than
265      *         zero
266      */
267     public static <A> TreeNode<Op<A>> of(
268         final int depth,
269         final ISeq<? extends Op<A>> operations,
270         final ISeq<? extends Op<A>> terminals
271     ) {
272         return of(depth, operations, terminals, RandomRegistry.random());
273     }
274 
275     /**
276      * Create a new, random program tree from the given (non) terminal
277      * operations with the desired depth. The created program tree is a
278      <em>full</em> tree.
279      *
280      @since 4.1
281      *
282      @param depth the desired depth of the program tree
283      @param operations the list of <em>non</em>-terminal operations
284      @param terminals the list of terminal operations
285      @param random the random engine used for creating the program
286      @param <A> the operational type
287      @return a new program tree
288      @throws NullPointerException if one of the given operations is
289      *        {@code null}
290      @throws IllegalArgumentException if the given tree depth is smaller than
291      *         zero
292      */
293     public static <A> TreeNode<Op<A>> of(
294         final int depth,
295         final ISeq<? extends Op<A>> operations,
296         final ISeq<? extends Op<A>> terminals,
297         final RandomGenerator random
298     ) {
299         if (depth < 0) {
300             throw new IllegalArgumentException(
301                 "Tree depth is smaller than zero: " + depth
302             );
303         }
304         if (!operations.forAll(o -> !o.isTerminal())) {
305             throw new IllegalArgumentException(
306                 "Operation list contains terminal op."
307             );
308         }
309         if (!terminals.forAll(Op::isTerminal)) {
310             throw new IllegalArgumentException(
311                 "Terminal list contains non-terminal op."
312             );
313         }
314 
315         final TreeNode<Op<A>> root = TreeNode.of();
316         fill(depth, root, operations, terminals, random);
317         return root;
318     }
319 
320     private static <A> void fill(
321         final int level,
322         final TreeNode<Op<A>> tree,
323         final ISeq<? extends Op<A>> operations,
324         final ISeq<? extends Op<A>> terminals,
325         final RandomGenerator random
326     ) {
327         final Op<A> op = level == 0
328             ? terminals.get(random.nextInt(terminals.size()))
329             : operations.get(random.nextInt(operations.size()));
330 
331         tree.value(op);
332 
333         if (level > 1) {
334             for (int i = 0; i < op.arity(); ++i) {
335                 final TreeNode<Op<A>> node = TreeNode.of();
336                 fill(level - 1, node, operations, terminals, random);
337                 tree.attach(node);
338             }
339         else {
340             for (int i = 0; i < op.arity(); ++i) {
341                 final Op<A> term = terminals.get(random.nextInt(terminals.size()));
342                 tree.attach(TreeNode.of(term));
343             }
344         }
345     }
346 
347     /**
348      * Creates a valid program tree from the given flattened sequence of
349      * op nodes. The given {@code operations} and {@code termination} nodes are
350      * used for <em>repairing</em> the program tree, if necessary.
351      *
352      @param nodes the flattened, possible corrupt, program tree
353      @param terminals the usable non-terminal operation nodes to use for
354      *        reparation
355      @param <A> the operation argument type
356      @return a new valid program tree build from the flattened program tree
357      @throws NullPointerException if one of the arguments is {@code null}
358      @throws IllegalArgumentException if the {@code nodes} sequence is empty
359      */
360     public static <A> TreeNode<Op<A>> toTree(
361         final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
362         final ISeq<? extends Op<A>> terminals
363     ) {
364         if (nodes.isEmpty()) {
365             throw new IllegalArgumentException("Tree nodes must not be empty.");
366         }
367 
368         final Op<A> op = requireNonNull(nodes.get(0).value());
369         final TreeNode<Op<A>> tree = TreeNode.of(op);
370         return toTree(
371             tree,
372             0,
373             nodes,
374             offsets(nodes),
375             terminals,
376             RandomRegistry.random()
377         );
378     }
379 
380     private static <A> TreeNode<Op<A>> toTree(
381         final TreeNode<Op<A>> root,
382         final int index,
383         final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
384         final int[] offsets,
385         final ISeq<? extends Op<A>> terminals,
386         final RandomGenerator random
387     ) {
388         if (index < nodes.size()) {
389             final FlatTree<? extends Op<A>, ?> node = nodes.get(index);
390             final Op<A> op = node.value();
391 
392             for (int i  = 0; i < op.arity(); ++i) {
393                 assert offsets[index!= -1;
394 
395                 final TreeNode<Op<A>> treeNode = TreeNode.of();
396                 if (offsets[index+ i < nodes.size()) {
397                     treeNode.value(nodes.get(offsets[index+ i).value());
398                 else {
399                     treeNode.value(terminals.get(random.nextInt(terminals.size())));
400                 }
401 
402                 toTree(
403                     treeNode,
404                     offsets[index+ i,
405                     nodes,
406                     offsets,
407                     terminals,
408                     random
409                 );
410                 root.attach(treeNode);
411             }
412         }
413 
414         return root;
415     }
416 
417     /**
418      * Create the offset array for the given nodes. The offsets are calculated
419      * using the arity of the stored operations.
420      *
421      @param nodes the flattened tree nodes
422      @return the offset array for the given nodes
423      */
424     static int[]
425     offsets(final ISeq<? extends FlatTree<? extends Op<?>, ?>> nodes) {
426         final int[] offsets = new int[nodes.size()];
427 
428         int offset = 1;
429         for (int i = 0; i < offsets.length; ++i) {
430             final Op<?> op = nodes.get(i).value();
431 
432             offsets[i= op.isTerminal() ? -: offset;
433             offset += op.arity();
434         }
435 
436         return offsets;
437     }
438 
439 }