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