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