001/*
002 * Java Genetic Algorithm Library (jenetics-7.0.0).
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 */
020package io.jenetics.prog.op;
021
022import static java.lang.String.format;
023import static java.util.Objects.requireNonNull;
024import static io.jenetics.internal.util.Hashes.hash;
025
026import java.io.Serial;
027import java.io.Serializable;
028import java.lang.reflect.Array;
029import java.util.Objects;
030import java.util.random.RandomGenerator;
031
032import io.jenetics.util.ISeq;
033import io.jenetics.util.RandomRegistry;
034
035import io.jenetics.ext.util.FlatTree;
036import io.jenetics.ext.util.Tree;
037import 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 */
049public 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.
150         *
151         * @param <T> the argument type
152         * @param tree the operation tree
153         * @param variables the input variables
154         * @return the result of the operation tree evaluation
155         * @throws NullPointerException if one of the arguments is {@code null}
156         * @throws IllegalArgumentException if the length of the variable array
157         *         is smaller than the program arity
158         */
159        @SafeVarargs
160        public static <T> T eval(
161                final Tree<? extends Op<T>, ?> tree,
162                final T... variables
163        ) {
164                requireNonNull(tree);
165                requireNonNull(variables);
166
167                final Op<T> op = tree.value();
168                return op.isTerminal()
169                        ? evalOp(op, variables)
170                        : evalOp(op, evalChildren(tree, variables));
171        }
172
173        @SafeVarargs
174        private static <T> T evalOp(final Op<T> op, final T... variables) {
175                if (op instanceof Var && ((Var)op).index() >= variables.length) {
176                        throw new IllegalArgumentException(format(
177                                "No value for variable '%s' given.", op
178                        ));
179                }
180
181                return op.apply(variables);
182        }
183
184        @SafeVarargs
185        private static <T> T[] evalChildren(
186                final Tree<? extends Op<T>, ?> node,
187                final T... variables
188        ) {
189                final T[] result = newArray(variables.getClass(), node.childCount());
190                for (int i = 0; i < node.childCount(); ++i) {
191                        result[i] = eval(node.childAt(i), variables);
192                }
193                return result;
194        }
195
196        @SuppressWarnings("unchecked")
197        private static <T> T[] newArray(final Class<?> arrayType, final int size) {
198                return (T[])Array.newInstance(arrayType.getComponentType(), size);
199        }
200
201        /**
202         * Validates the given program tree.
203         *
204         * @param program the program to validate
205         * @throws NullPointerException if the given {@code program} is {@code null}
206         * @throws IllegalArgumentException if the given operation tree is invalid,
207         *         which means there is at least one node where the operation arity
208         *         and the node child count differ.
209         */
210        public static void check(final Tree<? extends Op<?>, ?> program) {
211                program.forEach(Program::checkArity);
212        }
213
214        private static void checkArity(final Tree<? extends Op<?>, ?> node) {
215                if (node.value() != null &&
216                        node.value().arity() != node.childCount())
217                {
218                        throw new IllegalArgumentException(format(
219                                "Op arity != child count: %d != %d",
220                                node.value().arity(), node.childCount()
221                        ));
222                }
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 <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        ) {
248                return new Program<>(name, of(depth, operations, terminals));
249        }
250
251        /**
252         * Create a new, random program from the given (non) terminal operations
253         * with the desired depth. The created program tree is a <em>full</em> tree.
254         *
255         * @since 4.1
256         *
257         * @param name the program name
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 random the random engine used for creating the program
262         * @param <A> the operational type
263         * @return a new program
264         * @throws NullPointerException if one of the given operations is
265         *        {@code null}
266         * @throws IllegalArgumentException if the given tree depth is smaller than
267         *         zero
268         */
269        public static <A> Program<A> of(
270                final String name,
271                final int depth,
272                final ISeq<? extends Op<A>> operations,
273                final ISeq<? extends Op<A>> terminals,
274                final RandomGenerator random
275        ) {
276                return new Program<>(name, of(depth, operations, terminals, random));
277        }
278
279        /**
280         * Create a new, random program tree from the given (non) terminal
281         * operations with the desired depth. The created program tree is a
282         * <em>full</em> tree.
283         *
284         * @param depth the desired depth of the program tree
285         * @param operations the list of <em>non</em>-terminal operations
286         * @param terminals the list of terminal operations
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        ) {
299                return of(depth, operations, terminals, RandomRegistry.random());
300        }
301
302        /**
303         * Create a new, random program tree from the given (non) terminal
304         * operations with the desired depth. The created program tree is a
305         * <em>full</em> tree.
306         *
307         * @since 4.1
308         *
309         * @param depth the desired depth of the program tree
310         * @param operations the list of <em>non</em>-terminal operations
311         * @param terminals the list of terminal operations
312         * @param random the random engine used for creating the program
313         * @param <A> the operational type
314         * @return a new program tree
315         * @throws NullPointerException if one of the given operations is
316         *        {@code null}
317         * @throws IllegalArgumentException if the given tree depth is smaller than
318         *         zero
319         */
320        public static <A> TreeNode<Op<A>> of(
321                final int depth,
322                final ISeq<? extends Op<A>> operations,
323                final ISeq<? extends Op<A>> terminals,
324                final RandomGenerator random
325        ) {
326                if (depth < 0) {
327                        throw new IllegalArgumentException(
328                                "Tree depth is smaller than zero: " + depth
329                        );
330                }
331                if (!operations.forAll(o -> !o.isTerminal())) {
332                        throw new IllegalArgumentException(
333                                "Operation list contains terminal op."
334                        );
335                }
336                if (!terminals.forAll(Op::isTerminal)) {
337                        throw new IllegalArgumentException(
338                                "Terminal list contains non-terminal op."
339                        );
340                }
341
342                final TreeNode<Op<A>> root = TreeNode.of();
343                fill(depth, root, operations, terminals, random);
344                return root;
345        }
346
347        private static <A> void fill(
348                final int level,
349                final TreeNode<Op<A>> tree,
350                final ISeq<? extends Op<A>> operations,
351                final ISeq<? extends Op<A>> terminals,
352                final RandomGenerator random
353        ) {
354                final Op<A> op = level == 0
355                        ? terminals.get(random.nextInt(terminals.size()))
356                        : operations.get(random.nextInt(operations.size()));
357
358                tree.value(op);
359
360                if (level > 1) {
361                        for (int i = 0; i < op.arity(); ++i) {
362                                final TreeNode<Op<A>> node = TreeNode.of();
363                                fill(level - 1, node, operations, terminals, random);
364                                tree.attach(node);
365                        }
366                } else {
367                        for (int i = 0; i < op.arity(); ++i) {
368                                final Op<A> term = terminals.get(random.nextInt(terminals.size()));
369                                tree.attach(TreeNode.of(term));
370                        }
371                }
372        }
373
374        /**
375         * Creates a valid program tree from the given flattened sequence of
376         * op nodes. The given {@code operations} and {@code termination} nodes are
377         * used for <em>repairing</em> the program tree, if necessary.
378         *
379         * @param nodes the flattened, possible corrupt, program tree
380         * @param terminals the usable non-terminal operation nodes to use for
381         *        reparation
382         * @param <A> the operation argument type
383         * @return a new valid program tree build from the flattened program tree
384         * @throws NullPointerException if one of the arguments is {@code null}
385         * @throws IllegalArgumentException if the {@code nodes} sequence is empty
386         */
387        public static <A> TreeNode<Op<A>> toTree(
388                final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
389                final ISeq<? extends Op<A>> terminals
390        ) {
391                if (nodes.isEmpty()) {
392                        throw new IllegalArgumentException("Tree nodes must not be empty.");
393                }
394
395                final Op<A> op = requireNonNull(nodes.get(0).value());
396                final TreeNode<Op<A>> tree = TreeNode.of(op);
397                return toTree(
398                        tree,
399                        0,
400                        nodes,
401                        offsets(nodes),
402                        terminals,
403                        RandomRegistry.random()
404                );
405        }
406
407        private static <A> TreeNode<Op<A>> toTree(
408                final TreeNode<Op<A>> root,
409                final int index,
410                final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
411                final int[] offsets,
412                final ISeq<? extends Op<A>> terminals,
413                final RandomGenerator random
414        ) {
415                if (index < nodes.size()) {
416                        final FlatTree<? extends Op<A>, ?> node = nodes.get(index);
417                        final Op<A> op = node.value();
418
419                        for (int i  = 0; i < op.arity(); ++i) {
420                                assert offsets[index] != -1;
421
422                                final TreeNode<Op<A>> treeNode = TreeNode.of();
423                                if (offsets[index] + i < nodes.size()) {
424                                        treeNode.value(nodes.get(offsets[index] + i).value());
425                                } else {
426                                        treeNode.value(terminals.get(random.nextInt(terminals.size())));
427                                }
428
429                                toTree(
430                                        treeNode,
431                                        offsets[index] + i,
432                                        nodes,
433                                        offsets,
434                                        terminals,
435                                        random
436                                );
437                                root.attach(treeNode);
438                        }
439                }
440
441                return root;
442        }
443
444        /**
445         * Create the offset array for the given nodes. The offsets are calculated
446         * using the arity of the stored operations.
447         *
448         * @param nodes the flattened tree nodes
449         * @return the offset array for the given nodes
450         */
451        static int[]
452        offsets(final ISeq<? extends FlatTree<? extends Op<?>, ?>> nodes) {
453                final int[] offsets = new int[nodes.size()];
454
455                int offset = 1;
456                for (int i = 0; i < offsets.length; ++i) {
457                        final Op<?> op = nodes.get(i).value();
458
459                        offsets[i] = op.isTerminal() ? -1 : offset;
460                        offset += op.arity();
461                }
462
463                return offsets;
464        }
465
466}