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; 024 025import java.io.Serial; 026import java.util.function.Function; 027 028import io.jenetics.Gene; 029import io.jenetics.util.ISeq; 030import io.jenetics.util.RandomRegistry; 031 032import io.jenetics.ext.AbstractTreeGene; 033import io.jenetics.ext.util.TreeNode; 034 035import io.jenetics.prog.op.Op; 036import io.jenetics.prog.op.Program; 037 038/** 039 * This gene represents a program, build upon an AST of {@link Op} functions. 040 * Because of the tight coupling with the {@link ProgramChromosome}, a 041 * {@code ProgramGene} can't be created directly. This reduces the possible 042 * <em>error space</em>. Since the {@code ProgramGene} also is a {@code Tree}, 043 * it can be easily used as a result. 044 * 045 * {@snippet lang="java": 046 * final ProgramGene<Double> program = engine.stream() 047 * .limit(300) 048 * .collect(EvolutionResult.toBestGenotype()) 049 * .getGene(); 050 * 051 * final double result = program.eval(3.4); 052 * } 053 * 054 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 055 * @version 5.2 056 * @since 3.9 057 */ 058public final class ProgramGene<A> 059 extends AbstractTreeGene<Op<A>, ProgramGene<A>> 060 implements Gene<Op<A>, ProgramGene<A>>, Function<A[], A> 061{ 062 063 @Serial 064 private static final long serialVersionUID = 1L; 065 066 private final ISeq<? extends Op<A>> _operations; 067 private final ISeq<? extends Op<A>> _terminals; 068 069 ProgramGene( 070 final Op<A> op, 071 final int childOffset, 072 final ISeq<? extends Op<A>> operations, 073 final ISeq<? extends Op<A>> terminals 074 ) { 075 super(requireNonNull(get(op)), childOffset, op.arity()); 076 _operations = requireNonNull(operations); 077 _terminals = requireNonNull(terminals); 078 } 079 080 private static <A> Op<A> get(final Op<A> op) { 081 final Op<A> instance = op.get(); 082 if (instance != op && instance.arity() != op.arity()) { 083 throw new IllegalArgumentException(format( 084 "Original op and created op have different arity: %d != %d,", 085 instance.arity(), op.arity() 086 )); 087 } 088 return instance; 089 } 090 091 /** 092 * Evaluates this program gene (recursively) with the given variable values. 093 * 094 * @see ProgramGene#eval(Object[]) 095 * @see ProgramChromosome#eval(Object[]) 096 * 097 * @param args the input variables 098 * @return the evaluated value 099 * @throws NullPointerException if the given variable array is {@code null} 100 */ 101 @Override 102 public A apply(final A[] args) { 103 checkTreeState(); 104 return Program.eval(this, args); 105 } 106 107 /** 108 * Convenient method, which lets you apply the program function without 109 * explicitly create a wrapper array. 110 * 111 * @see ProgramGene#apply(Object[]) 112 * @see ProgramChromosome#eval(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 */ 118 @SafeVarargs 119 public final A eval(final A... args) { 120 return apply(args); 121 } 122 123 /** 124 * Return the allowed operations. 125 * 126 * @return the allowed operations 127 */ 128 public ISeq<Op<A>> operations() { 129 return ISeq.upcast(_operations); 130 } 131 132 /** 133 * Return the allowed terminal operations. 134 * 135 * @return the allowed terminal operations 136 */ 137 public ISeq<Op<A>> terminals() { 138 return ISeq.upcast(_terminals); 139 } 140 141 /** 142 * Creates a new {@link TreeNode} from this program gene. 143 * 144 * @since 5.0 145 * 146 * @return a new tree node value build from this program gene 147 */ 148 public TreeNode<Op<A>> toTreeNode() { 149 return TreeNode.ofTree(this); 150 } 151 152 @Override 153 public ProgramGene<A> newInstance() { 154 final var random = RandomRegistry.random(); 155 156 Op<A> operation = value(); 157 if (isLeaf()) { 158 operation = _terminals.get(random.nextInt(_terminals.length())); 159 } else { 160 final ISeq<Op<A>> operations = _operations.stream() 161 .filter(op -> op.arity() == value().arity()) 162 .collect(ISeq.toISeq()); 163 164 if (operations.length() > 1) { 165 operation = operations.get(random.nextInt(operations.length())); 166 } 167 } 168 169 return newInstance(operation); 170 } 171 172 /** 173 * Create a new program gene with the given operation. 174 * 175 * @param op the operation of the new program gene 176 * @return a new program gene with the given operation 177 * @throws NullPointerException if the given {@code op} is {@code null} 178 * @throws IllegalArgumentException if the arity of the given operation is 179 * different from the arity of the current operation. This restriction 180 * ensures that only valid program genes are created by this method. 181 */ 182 @Override 183 public ProgramGene<A> newInstance(final Op<A> op) { 184 if (value().arity() != op.arity()) { 185 throw new IllegalArgumentException(format( 186 "New operation must have same arity: %s[%d] != %s[%d]", 187 value().name(), value().arity(), op.name(), op.arity() 188 )); 189 } 190 return new ProgramGene<>(op, childOffset(), _operations, _terminals); 191 } 192 193 /** 194 * Return a new program gene with the given operation and the <em>local</em> 195 * tree structure. 196 * 197 * @param op the new operation 198 * @param childOffset the offset of the first node child within the 199 * chromosome 200 * @param childCount the number of children of the new tree gene 201 * @return a new tree gene with the given parameters 202 * @throws IllegalArgumentException if the {@code childCount} is smaller 203 * than zero 204 * @throws IllegalArgumentException if the operation arity is different from 205 * the {@code childCount}. 206 * @throws NullPointerException if the given {@code op} is {@code null} 207 */ 208 @Override 209 public ProgramGene<A> newInstance( 210 final Op<A> op, 211 final int childOffset, 212 final int childCount 213 ) { 214 if (op.arity() != childCount) { 215 throw new IllegalArgumentException(format( 216 "Operation arity and child count are different: %d, != %d", 217 op.arity(), childCount 218 )); 219 } 220 221 return new ProgramGene<>(op, childOffset, _operations, _terminals); 222 } 223 224}