001/*
002 * Java Genetic Algorithm Library (jenetics-8.3.0).
003 * Copyright (c) 2007-2025 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;
021
022import static java.lang.String.format;
023import static java.util.Objects.requireNonNull;
024import static io.jenetics.internal.util.SerialIO.readInt;
025import static io.jenetics.internal.util.SerialIO.writeInt;
026
027import java.io.IOException;
028import java.io.InvalidObjectException;
029import java.io.ObjectInput;
030import java.io.ObjectInputStream;
031import java.io.ObjectOutput;
032import java.io.Serial;
033import java.io.Serializable;
034import java.util.function.Function;
035import java.util.function.Predicate;
036
037import io.jenetics.util.ISeq;
038import io.jenetics.util.MSeq;
039
040import io.jenetics.ext.AbstractTreeChromosome;
041import io.jenetics.ext.util.FlatTreeNode;
042import io.jenetics.ext.util.Tree;
043import io.jenetics.ext.util.TreeNode;
044
045import io.jenetics.prog.op.Op;
046import io.jenetics.prog.op.Program;
047
048/**
049 * Holds the nodes of the operation tree.
050 * {@snippet lang="java":
051 * final int depth = 6;
052 * final ISeq<Op<Double>> operations = ISeq.of(null); // @replace substring='null' replacement="..."
053 * final ISeq<Op<Double>> terminals = ISeq.of(null); // @replace substring='null' replacement="..."
054 * final ProgramChromosome<Double> ch = ProgramChromosome.of(
055 *     depth,
056 *     // If the program has more than 200 nodes, it is marked as "invalid".
057 *     ch -> ch.length() <= 200,
058 *     operations,
059 *     terminals
060 * );
061 * }
062 *
063 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
064 * @version 4.1
065 * @since 3.9
066 */
067public class ProgramChromosome<A>
068        extends AbstractTreeChromosome<Op<A>, ProgramGene<A>>
069        implements Function<A[], A>, Serializable
070{
071
072        @Serial
073        private static final long serialVersionUID = 2L;
074
075        private final Predicate<? super ProgramChromosome<A>> _validator;
076        private final ISeq<Op<A>> _operations;
077        private final ISeq<Op<A>> _terminals;
078
079        /**
080         * Create a new program chromosome from the given program genes. This
081         * constructor assumes that the given {@code program} is valid. Since the
082         * program validation is quite expensive, the validity check is skipped in
083         * this constructor.
084         *
085         * @param program the program. During the program evolution, newly created
086         *        program trees have the same <em>depth</em> than this tree.
087         * @param validator the chromosome validator. A typical validator would
088         *        check the size of the tree and if the tree is too large, mark it
089         *        at <em>invalid</em>. The <em>validator</em> may be {@code null}.
090         * @param operations the allowed non-terminal operations
091         * @param terminals the allowed terminal operations
092         * @throws NullPointerException if one of the given arguments is {@code null}
093         * @throws IllegalArgumentException if either the {@code operations} or
094         *         {@code terminals} sequence is empty
095         */
096        protected ProgramChromosome(
097                final ISeq<ProgramGene<A>> program,
098                final Predicate<? super ProgramChromosome<A>> validator,
099                final ISeq<? extends Op<A>> operations,
100                final ISeq<? extends Op<A>> terminals
101        ) {
102                super(program);
103                _validator = requireNonNull(validator);
104                _operations = requireNonNull(ISeq.upcast(operations));
105                _terminals = requireNonNull(ISeq.upcast(terminals));
106
107                if (operations.isEmpty()) {
108                        throw new IllegalArgumentException("No operations given.");
109                }
110                if (terminals.isEmpty()) {
111                        throw new IllegalArgumentException("No terminals given");
112                }
113        }
114
115        /**
116         * Return the allowed operations.
117         *
118         * @since 5.0
119         *
120         * @return the allowed operations
121         */
122        public ISeq<Op<A>> operations() {
123                return _operations;
124        }
125
126        /**
127         * Return the allowed terminal operations.
128         *
129         * @since 5.0
130         *
131         * @return the allowed terminal operations
132         */
133        public ISeq<Op<A>> terminals() {
134                return _terminals;
135        }
136
137        @Override
138        public boolean isValid() {
139                if (_valid == null) {
140                        _valid = _validator.test(this);
141                }
142
143                return _valid;
144        }
145
146        private boolean isSuperValid() {
147                return super.isValid();
148        }
149
150        /**
151         * Evaluates the root node of this chromosome.
152         *
153         * @see ProgramGene#apply(Object[])
154         * @see ProgramChromosome#eval(Object[])
155         *
156         * @param args the input variables
157         * @return the evaluated value
158         * @throws NullPointerException if the given variable array is {@code null}
159         */
160        @Override
161        public A apply(final A[] args) {
162                return root().apply(args);
163        }
164
165        /**
166         * Evaluates the root node of this chromosome.
167         *
168         * @see ProgramGene#eval(Object[])
169         * @see ProgramChromosome#apply(Object[])
170         *
171         * @param args the function arguments
172         * @return the evaluated value
173         * @throws NullPointerException if the given variable array is {@code null}
174         */
175        @SafeVarargs
176        public final A eval(final A... args) {
177                return root().eval(args);
178        }
179
180        @Override
181        public ProgramChromosome<A> newInstance(final ISeq<ProgramGene<A>> genes) {
182                return create(genes, _validator, _operations, _terminals);
183        }
184
185        @Override
186        public ProgramChromosome<A> newInstance() {
187                return create(root().depth(), _validator, _operations, _terminals);
188        }
189
190        /**
191         * Create a new chromosome from the given operation tree (program).
192         *
193         * @param program the operation tree
194         * @param validator the chromosome validator. A typical validator would
195         *        check the size of the tree and if the tree is too large, mark it
196         *        at <em>invalid</em>. The <em>validator</em> may be {@code null}.
197         * @param operations the allowed non-terminal operations
198         * @param terminals the allowed terminal operations
199         * @param <A> the operation type
200         * @return a new chromosome from the given operation tree
201         * @throws NullPointerException if one of the given arguments is {@code null}
202         * @throws IllegalArgumentException if the given operation tree is invalid,
203         *         which means there is at least one node where the operation arity
204         *         and the node child count differ.
205         */
206        public static <A> ProgramChromosome<A> of(
207                final Tree<? extends Op<A>, ?> program,
208                final Predicate<? super ProgramChromosome<A>> validator,
209                final ISeq<? extends Op<A>> operations,
210                final ISeq<? extends Op<A>> terminals
211        ) {
212                Program.check(program);
213                checkOperations(operations);
214                checkTerminals(terminals);
215
216                return create(program, validator, operations, terminals);
217        }
218
219        // Create the chromosomes without checks.
220        private static <A> ProgramChromosome<A> create(
221                final Tree<? extends Op<A>, ?> program,
222                final Predicate<? super ProgramChromosome<A>> validator,
223                final ISeq<? extends Op<A>> operations,
224                final ISeq<? extends Op<A>> terminals
225        ) {
226                final ISeq<ProgramGene<A>> genes = FlatTreeNode.ofTree(program).stream()
227                        .map(n -> new ProgramGene<>(
228                                n.value(), n.childOffset(), operations, terminals))
229                        .collect(ISeq.toISeq());
230
231                return new ProgramChromosome<>(genes, validator, operations, terminals);
232        }
233
234        private static void checkOperations(final ISeq<? extends Op<?>> operations) {
235                final ISeq<?> terminals = operations.stream()
236                        .filter(Op::isTerminal)
237                        .collect(ISeq.toISeq());
238
239                if (!terminals.isEmpty()) {
240                        throw new IllegalArgumentException(format(
241                                "Operations must not contain terminals: %s",
242                                terminals.toString(",")
243                        ));
244                }
245        }
246
247        private static void checkTerminals(final ISeq<? extends Op<?>> terminals) {
248                final ISeq<?> operations = terminals.stream()
249                        .filter(op -> !op.isTerminal())
250                        .collect(ISeq.toISeq());
251
252                if (!operations.isEmpty()) {
253                        throw new IllegalArgumentException(format(
254                                "Terminals must not contain operations: %s",
255                                operations.toString(",")
256                        ));
257                }
258        }
259
260        /**
261         * Create a new chromosome from the given operation tree (program).
262         *
263         * @param program the operation tree
264         * @param operations the allowed non-terminal operations
265         * @param terminals the allowed terminal operations
266         * @param <A> the operation type
267         * @return a new chromosome from the given operation tree
268         * @throws NullPointerException if one of the given arguments is {@code null}
269         * @throws IllegalArgumentException if the given operation tree is invalid,
270         *         which means there is at least one node where the operation arity
271         *         and the node child count differ.
272         */
273        public static <A> ProgramChromosome<A> of(
274                final Tree<? extends Op<A>, ?> program,
275                final ISeq<? extends Op<A>> operations,
276                final ISeq<? extends Op<A>> terminals
277        ) {
278                return of(
279                        program,
280                        (Predicate<? super ProgramChromosome<A>> & Serializable)ProgramChromosome::isSuperValid,
281                        operations,
282                        terminals
283                );
284        }
285
286        /**
287         * Create a new program chromosome with the defined depth. This method will
288         * create a <em>full</em> program tree.
289         *
290         * @param depth the depth of the created program tree
291         * @param validator the chromosome validator. A typical validator would
292         *        check the size of the tree and if the tree is too large, mark it
293         *        at <em>invalid</em>. The <em>validator</em> may be {@code null}.
294         * @param operations the allowed non-terminal operations
295         * @param terminals the allowed terminal operations
296         * @param <A> the operation type
297         * @return a new program chromosome from the given (flattened) program tree
298         * @throws NullPointerException if one of the parameters is {@code null}
299         * @throws IllegalArgumentException if the {@code depth} is smaller than zero
300         */
301        public static <A> ProgramChromosome<A> of(
302                final int depth,
303                final Predicate<? super ProgramChromosome<A>> validator,
304                final ISeq<? extends Op<A>> operations,
305                final ISeq<? extends Op<A>> terminals
306        ) {
307                checkOperations(operations);
308                checkTerminals(terminals);
309
310                return create(depth, validator, operations, terminals);
311        }
312
313        private static <A> ProgramChromosome<A> create(
314                final int depth,
315                final Predicate<? super ProgramChromosome<A>> validator,
316                final ISeq<? extends Op<A>> operations,
317                final ISeq<? extends Op<A>> terminals
318        ) {
319                return create(
320                        Program.of(depth, operations, terminals),
321                        validator,
322                        operations,
323                        terminals
324                );
325        }
326
327        /**
328         * Create a new program chromosome with the defined depth. This method will
329         * create a <em>full</em> program tree.
330         *
331         * @param depth the depth of the created (full) program tree
332         * @param operations the allowed non-terminal operations
333         * @param terminals the allowed terminal operations
334         * @param <A> the operation type
335         * @return a new program chromosome from the given (flattened) program tree
336         * @throws NullPointerException if one of the parameters is {@code null}
337         * @throws IllegalArgumentException if the {@code depth} is smaller than zero
338         */
339        public static <A> ProgramChromosome<A> of(
340                final int depth,
341                final ISeq<? extends Op<A>> operations,
342                final ISeq<? extends Op<A>> terminals
343        ) {
344                return of(
345                        depth,
346                        (Predicate<? super ProgramChromosome<A>> & Serializable)
347                                ProgramChromosome::isSuperValid,
348                        operations,
349                        terminals
350                );
351        }
352
353        /**
354         * Create a new program chromosome from the given (flattened) program tree.
355         * This method doesn't make any assumption about the validity of the given
356         * operation tree. If the tree is not valid, it will repair it. This
357         * behavior allows the <em>safe</em> usage of all existing alterers.
358         * {@snippet lang="java":
359         * final ProgramChromosome<Double> ch = ProgramChromosome.of(
360         *     genes,
361         *     // If the program has more that 200 nodes, it is marked as "invalid".
362         *     ch -> ch.length() <= 200,
363         *     operations,
364         *     terminals
365         * );
366         * }
367         *
368         * @param genes the program genes
369         * @param validator the chromosome validator to use
370         * @param operations the allowed non-terminal operations
371         * @param terminals the allowed terminal operations
372         * @param <A> the operation type
373         * @return a new program chromosome from the given (flattened) program tree
374         * @throws NullPointerException if one of the parameters is {@code null}
375         */
376        public static <A> ProgramChromosome<A> of(
377                final ISeq<ProgramGene<A>> genes,
378                final Predicate<? super ProgramChromosome<A>> validator,
379                final ISeq<? extends Op<A>> operations,
380                final ISeq<? extends Op<A>> terminals
381        ) {
382                final TreeNode<Op<A>> program = Program.toTree(genes, terminals);
383                return of(program, validator, operations, terminals);
384        }
385
386        private static <A> ProgramChromosome<A> create(
387                final ISeq<ProgramGene<A>> genes,
388                final Predicate<? super ProgramChromosome<A>> validator,
389                final ISeq<? extends Op<A>> operations,
390                final ISeq<? extends Op<A>> terminals
391        ) {
392                final TreeNode<Op<A>> program = Program.toTree(genes, terminals);
393                return create(program, validator, operations, terminals);
394        }
395
396        public static <A> ProgramChromosome<A> of(
397                final ISeq<ProgramGene<A>> genes,
398                final ISeq<? extends Op<A>> operations,
399                final ISeq<? extends Op<A>> terminals
400        ) {
401                return of(genes, ProgramChromosome::isSuperValid, operations, terminals);
402        }
403
404
405        /* *************************************************************************
406         *  Java object serialization
407         * ************************************************************************/
408
409        @Serial
410        private Object writeReplace() {
411                return new SerialProxy(SerialProxy.PROGRAM_CHROMOSOME, this);
412        }
413
414        @Serial
415        private void readObject(final ObjectInputStream stream)
416                throws InvalidObjectException
417        {
418                throw new InvalidObjectException("Serialization proxy required.");
419        }
420
421        void write(final ObjectOutput out) throws IOException {
422                writeInt(length(), out);
423                out.writeObject(_operations);
424                out.writeObject(_terminals);
425
426                for (ProgramGene<A> gene : _genes) {
427                        out.writeObject(gene.allele());
428                        writeInt(gene.childOffset(), out);
429                }
430        }
431
432        @SuppressWarnings({"unchecked", "rawtypes"})
433        static ProgramChromosome read(final ObjectInput in)
434                throws IOException, ClassNotFoundException
435        {
436                final var length = readInt(in);
437                final var operations = (ISeq)in.readObject();
438                final var terminals = (ISeq)in.readObject();
439
440                final MSeq genes = MSeq.ofLength(length);
441                for (int i = 0; i < genes.length(); ++i) {
442                        final Op op = (Op)in.readObject();
443                        final int childOffset = readInt(in);
444                        genes.set(i, new ProgramGene(op, childOffset, operations, terminals));
445                }
446
447                return ProgramChromosome.of(genes.toISeq(), operations, terminals);
448        }
449
450}