001/* 002 * Java Genetic Algorithm Library (jenetics-8.1.0). 003 * Copyright (c) 2007-2024 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 * 051 * {@snippet lang="java": 052 * final int depth = 6; 053 * final ISeq<Op<Double>> operations = ISeq.of(null); // @replace substring='null' replacement="..." 054 * final ISeq<Op<Double>> terminals = ISeq.of(null); // @replace substring='null' replacement="..." 055 * final ProgramChromosome<Double> ch = ProgramChromosome.of( 056 * depth, 057 * // If the program has more than 200 nodes, it is marked as "invalid". 058 * ch -> ch.length() <= 200, 059 * operations, 060 * terminals 061 * ); 062 * } 063 * 064 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 065 * @version 4.1 066 * @since 3.9 067 */ 068public 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 have 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 alterers. 359 * 360 * {@snippet lang="java": 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 * } 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}