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}