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