001/*
002 * Java Genetic Algorithm Library (jenetics-9.1.0).
003 * Copyright (c) 2007-2026 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;
024
025import java.io.Serial;
026import java.io.Serializable;
027import java.util.Objects;
028import java.util.function.BiFunction;
029import java.util.function.Function;
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 argument 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 Objects.hash(_name, _tree);
128        }
129
130        @Override
131        public boolean equals(final Object obj) {
132                return obj instanceof Program<?> other &&
133                        Objects.equals(other._name, _name) &&
134                        Objects.equals(other._tree, _tree);
135        }
136
137        @Override
138        public String toString() {
139                return _name;
140        }
141
142
143        /* *************************************************************************
144         * Static helper methods.
145         * ************************************************************************/
146
147        /**
148         * Evaluates the given operation tree with the given variables. This method
149         * is equivalent to
150         * {@snippet lang="java":
151         * final T result = tree.reduce(variables, Op::apply);
152         * }
153         * but handles the variable sized {@code variables} array more conveniently.
154         *
155         * @see Tree#reduce(Object[], BiFunction)
156         *
157         * @param <T> the argument type
158         * @param tree the operation tree
159         * @param variables the input variables
160         * @return the result of the operation tree evaluation
161         * @throws NullPointerException if one of the arguments is {@code null}
162         * @throws IllegalArgumentException if the length of the variable array
163         *         is smaller than the program arity
164         */
165        @SafeVarargs
166        public static <T> T eval(
167                final Tree<? extends Op<T>, ?> tree,
168                final T... variables
169        ) {
170                return tree.reduce(variables, Function::apply);
171        }
172
173        /**
174         * Validates the given program tree.
175         *
176         * @param program the program to validate
177         * @throws NullPointerException if the given {@code program} is {@code null}
178         * @throws IllegalArgumentException if the given operation tree is invalid,
179         *         which means there is at least one node where the operation arity
180         *         and the node child count differ.
181         */
182        public static void check(final Tree<? extends Op<?>, ?> program) {
183                program.forEach(Program::checkArity);
184        }
185
186        private static void checkArity(final Tree<? extends Op<?>, ?> node) {
187                if (node.value() != null &&
188                        node.value().arity() != node.childCount())
189                {
190                        throw new IllegalArgumentException(format(
191                                "Op arity != child count: %d != %d",
192                                node.value().arity(), node.childCount()
193                        ));
194                }
195        }
196
197        /**
198         * Create a new, random program from the given (non) terminal operations
199         * with the desired depth. The created program tree is a <em>full</em> tree.
200         *
201         * @since 4.1
202         *
203         * @param name the program name
204         * @param depth the desired depth of the program tree
205         * @param operations the list of <em>non</em>-terminal operations
206         * @param terminals the list of terminal operations
207         * @param <A> the operational type
208         * @return a new program
209         * @throws NullPointerException if one of the given operations is
210         *        {@code null}
211         * @throws IllegalArgumentException if the given tree depth is smaller than
212         *         zero
213         */
214        public static <A> Program<A> of(
215                final String name,
216                final int depth,
217                final ISeq<? extends Op<A>> operations,
218                final ISeq<? extends Op<A>> terminals
219        ) {
220                return new Program<>(name, of(depth, operations, terminals));
221        }
222
223        /**
224         * Create a new, random program from the given (non) terminal operations
225         * with the desired depth. The created program tree is a <em>full</em> tree.
226         *
227         * @since 4.1
228         *
229         * @param name the program name
230         * @param depth the desired depth of the program tree
231         * @param operations the list of <em>non</em>-terminal operations
232         * @param terminals the list of terminal operations
233         * @param random the random engine used for creating the program
234         * @param <A> the operational type
235         * @return a new program
236         * @throws NullPointerException if one of the given operations is
237         *        {@code null}
238         * @throws IllegalArgumentException if the given tree depth is smaller than
239         *         zero
240         */
241        public static <A> Program<A> of(
242                final String name,
243                final int depth,
244                final ISeq<? extends Op<A>> operations,
245                final ISeq<? extends Op<A>> terminals,
246                final RandomGenerator random
247        ) {
248                return new Program<>(name, of(depth, operations, terminals, random));
249        }
250
251        /**
252         * Create a new, random program tree from the given (non) terminal
253         * operations with the desired depth. The created program tree is a
254         * <em>full</em> tree.
255         *
256         * @param depth the desired depth of the program tree
257         * @param operations the list of <em>non</em>-terminal operations
258         * @param terminals the list of terminal operations
259         * @param <A> the operational type
260         * @return a new program tree
261         * @throws NullPointerException if one of the given operations is
262         *        {@code null}
263         * @throws IllegalArgumentException if the given tree depth is smaller than
264         *         zero
265         */
266        public static <A> TreeNode<Op<A>> of(
267                final int depth,
268                final ISeq<? extends Op<A>> operations,
269                final ISeq<? extends Op<A>> terminals
270        ) {
271                return of(depth, operations, terminals, RandomRegistry.random());
272        }
273
274        /**
275         * Create a new, random program tree from the given (non) terminal
276         * operations with the desired depth. The created program tree is a
277         * <em>full</em> tree.
278         *
279         * @since 4.1
280         *
281         * @param depth the desired depth of the program tree
282         * @param operations the list of <em>non</em>-terminal operations
283         * @param terminals the list of terminal operations
284         * @param random the random engine used for creating the program
285         * @param <A> the operational type
286         * @return a new program tree
287         * @throws NullPointerException if one of the given operations is
288         *        {@code null}
289         * @throws IllegalArgumentException if the given tree depth is smaller than
290         *         zero
291         */
292        public static <A> TreeNode<Op<A>> of(
293                final int depth,
294                final ISeq<? extends Op<A>> operations,
295                final ISeq<? extends Op<A>> terminals,
296                final RandomGenerator random
297        ) {
298                if (depth < 0) {
299                        throw new IllegalArgumentException(
300                                "Tree depth is smaller than zero: " + depth
301                        );
302                }
303                if (!operations.forAll(o -> !o.isTerminal())) {
304                        throw new IllegalArgumentException(
305                                "Operation list contains terminal op."
306                        );
307                }
308                if (!terminals.forAll(Op::isTerminal)) {
309                        throw new IllegalArgumentException(
310                                "Terminal list contains non-terminal op."
311                        );
312                }
313
314                final TreeNode<Op<A>> root = TreeNode.of();
315                fill(depth, root, operations, terminals, random);
316                return root;
317        }
318
319        private static <A> void fill(
320                final int level,
321                final TreeNode<Op<A>> tree,
322                final ISeq<? extends Op<A>> operations,
323                final ISeq<? extends Op<A>> terminals,
324                final RandomGenerator random
325        ) {
326                final Op<A> op = level == 0
327                        ? terminals.get(random.nextInt(terminals.size()))
328                        : operations.get(random.nextInt(operations.size()));
329
330                tree.value(op);
331
332                if (level > 1) {
333                        for (int i = 0; i < op.arity(); ++i) {
334                                final TreeNode<Op<A>> node = TreeNode.of();
335                                fill(level - 1, node, operations, terminals, random);
336                                tree.attach(node);
337                        }
338                } else {
339                        for (int i = 0; i < op.arity(); ++i) {
340                                final Op<A> term = terminals.get(random.nextInt(terminals.size()));
341                                tree.attach(TreeNode.of(term));
342                        }
343                }
344        }
345
346        /**
347         * Creates a valid program tree from the given flattened sequence of
348         * op nodes. The given {@code operations} and {@code termination} nodes are
349         * used for <em>repairing</em> the program tree, if necessary.
350         *
351         * @param nodes the flattened, possible corrupt, program tree
352         * @param terminals the usable non-terminal operation nodes to use for
353         *        reparation
354         * @param <A> the operation argument type
355         * @return a new valid program tree build from the flattened program tree
356         * @throws NullPointerException if one of the arguments is {@code null}
357         * @throws IllegalArgumentException if the {@code nodes} sequence is empty
358         */
359        public static <A> TreeNode<Op<A>> toTree(
360                final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
361                final ISeq<? extends Op<A>> terminals
362        ) {
363                if (nodes.isEmpty()) {
364                        throw new IllegalArgumentException("Tree nodes must not be empty.");
365                }
366
367                final Op<A> op = requireNonNull(nodes.get(0).value());
368                final TreeNode<Op<A>> tree = TreeNode.of(op);
369                return toTree(
370                        tree,
371                        0,
372                        nodes,
373                        offsets(nodes),
374                        terminals,
375                        RandomRegistry.random()
376                );
377        }
378
379        private static <A> TreeNode<Op<A>> toTree(
380                final TreeNode<Op<A>> root,
381                final int index,
382                final ISeq<? extends FlatTree<? extends Op<A>, ?>> nodes,
383                final int[] offsets,
384                final ISeq<? extends Op<A>> terminals,
385                final RandomGenerator random
386        ) {
387                if (index < nodes.size()) {
388                        final FlatTree<? extends Op<A>, ?> node = nodes.get(index);
389                        final Op<A> op = node.value();
390
391                        for (int i  = 0; i < op.arity(); ++i) {
392                                assert offsets[index] != -1;
393
394                                final TreeNode<Op<A>> treeNode = TreeNode.of();
395                                if (offsets[index] + i < nodes.size()) {
396                                        treeNode.value(nodes.get(offsets[index] + i).value());
397                                } else {
398                                        treeNode.value(terminals.get(random.nextInt(terminals.size())));
399                                }
400
401                                toTree(
402                                        treeNode,
403                                        offsets[index] + i,
404                                        nodes,
405                                        offsets,
406                                        terminals,
407                                        random
408                                );
409                                root.attach(treeNode);
410                        }
411                }
412
413                return root;
414        }
415
416        /**
417         * Create the offset array for the given nodes. The offsets are calculated
418         * using the arity of the stored operations.
419         *
420         * @param nodes the flattened tree nodes
421         * @return the offset array for the given nodes
422         */
423        static int[]
424        offsets(final ISeq<? extends FlatTree<? extends Op<?>, ?>> nodes) {
425                final int[] offsets = new int[nodes.size()];
426
427                int offset = 1;
428                for (int i = 0; i < offsets.length; ++i) {
429                        final Op<?> op = nodes.get(i).value();
430
431                        offsets[i] = op.isTerminal() ? -1 : offset;
432                        offset += op.arity();
433                }
434
435                return offsets;
436        }
437
438}