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.op; 021 022import static java.lang.String.format; 023import static io.jenetics.ext.internal.util.Names.isIdentifier; 024 025import java.io.Serial; 026import java.io.Serializable; 027import java.util.HashMap; 028import java.util.Map; 029import java.util.Objects; 030import java.util.SortedSet; 031import java.util.TreeSet; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034import java.util.stream.Collectors; 035 036import io.jenetics.ext.util.Tree; 037import io.jenetics.ext.util.TreeNode; 038 039/** 040 * Represents the program variables. The {@code Var} operation is a 041 * <em>terminal</em> operation, which just returns the value with the defined 042 * index of the input variable array. It is essentially an orthogonal projection 043 * of the <em>n</em>-dimensional input space to the <em>1</em>-dimensional 044 * result space. 045 * 046 * {@snippet lang="java": 047 * final ISeq<? extends Op<Double>> operations = ISeq.of(null); // @replace substring='null' replacement="..." 048 * final ISeq<? extends Op<Double>> terminals = ISeq.of( 049 * Var.of("x", 0), Var.of("y", 1) 050 * ); 051 * } 052 * 053 * The example above shows how to define the terminal operations for a GP, which 054 * tries to optimize a 2-dimensional function. 055 * 056 * {@snippet lang="java": 057 * static double error(final ProgramChromosome<Double> program) { 058 * final double x = null; // @replace substring='null' replacement="..." 059 * final double y = null; // @replace substring='null' replacement="..." 060 * final double result = program.eval(x, y); 061 * // ... 062 * return null; // @replace substring='null' replacement="..." 063 * } 064 * } 065 * 066 * @implNote 067 * The {@code Var} object is comparable, according its name. 068 * 069 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 070 * @version 7.0 071 * @since 3.9 072 */ 073public final class Var<T> implements Op<T>, Comparable<Var<T>>, Serializable { 074 075 @Serial 076 private static final long serialVersionUID = 1L; 077 078 private final String _name; 079 private final int _index; 080 081 /** 082 * Create a new variable with the given {@code name} and projection 083 * {@code index}. 084 * 085 * @param name the variable name. Used when printing the operation tree 086 * (program) 087 * @param index the projection index 088 * @throws IllegalArgumentException if the given {@code name} is not a valid 089 * Java identifier 090 * @throws IndexOutOfBoundsException if the projection {@code index} is 091 * smaller than zero 092 * @throws NullPointerException if the given variable {@code name} is 093 * {@code null} 094 */ 095 private Var(final String name, final int index) { 096 if (!isIdentifier(name)) { 097 throw new IllegalArgumentException(format( 098 "'%s' is not a valid identifier.", name 099 )); 100 } 101 if (index < 0) { 102 throw new IndexOutOfBoundsException( 103 "Index smaller than zero: " + index 104 ); 105 } 106 107 _name = name; 108 _index = index; 109 } 110 111 /** 112 * The projection index of the variable. 113 * 114 * @return the projection index of the variable 115 */ 116 public int index() { 117 return _index; 118 } 119 120 @Override 121 public String name() { 122 return _name; 123 } 124 125 @Override 126 public int arity() { 127 return 0; 128 } 129 130 @Override 131 public T apply(final T[] variables) { 132 if (_index >= variables.length) { 133 throw new IllegalArgumentException(format( 134 "No value for variable '%s' given.", this 135 )); 136 } 137 return variables[_index]; 138 } 139 140 @Override 141 public int compareTo(final Var<T> o) { 142 return _name.compareTo(o._name); 143 } 144 145 @Override 146 public int hashCode() { 147 return Objects.hashCode(_name); 148 } 149 150 @Override 151 public boolean equals(final Object obj) { 152 return obj == this || 153 obj instanceof Var<?> other && 154 Objects.equals(other._name, _name); 155 } 156 157 @Override 158 public String toString() { 159 return _name; 160 } 161 162 /** 163 * Create a new variable with the given {@code name} and projection 164 * {@code index}. 165 * 166 * @see #parse(String) 167 * 168 * @param name the variable name. Used when printing the operation tree 169 * (program) 170 * @param index the projection index 171 * @param <T> the variable type 172 * @return a new variable with the given {@code name} and projection 173 * {@code index} 174 * @throws IllegalArgumentException if the given {@code name} is not a valid 175 * Java identifier 176 * @throws IndexOutOfBoundsException if the projection {@code index} is 177 * smaller than zero 178 * @throws NullPointerException if the given variable {@code name} is 179 * {@code null} 180 */ 181 public static <T> Var<T> of(final String name, final int index) { 182 return new Var<>(name, index); 183 } 184 185 /** 186 * Create a new variable with the given {@code name}. The projection index 187 * is set to zero. Always prefer to create new variables with 188 * {@link #of(String, int)}, especially when you define your terminal 189 * operation for your GP problem. 190 * 191 * @see #parse(String) 192 * 193 * @param name the variable name. Used when printing the operation tree 194 * (program) 195 * @param <T> the variable type 196 * @return a new variable with the given {@code name} and projection index 197 * zero 198 * @throws IllegalArgumentException if the given {@code name} is not a valid 199 * Java identifier 200 * @throws NullPointerException if the given variable {@code name} is 201 * {@code null} 202 */ 203 public static <T> Var<T> of(final String name) { 204 return new Var<>(name, 0); 205 } 206 207 private static final Pattern VAR_INDEX = Pattern.compile("(.+)\\[\\s*(\\d+)\\s*]"); 208 209 /** 210 * Parses the given variable string to its name and index. The expected 211 * format is <em>var_name</em>[<em>index</em>]. 212 * 213 * <pre> {@code 214 * x[0] 215 * y[3] 216 * my_var[4] 217 * } </pre> 218 * 219 * If no variable <em>index</em> is encoded in the name, a variable with 220 * index 0 is created. 221 * 222 * @see #of(String, int) 223 * 224 * @param name the variable name + index 225 * @param <T> the operation type 226 * @return a new variable parsed from the input string 227 * @throws IllegalArgumentException if the given variable couldn't be parsed 228 * and the given {@code name} is not a valid Java identifier 229 * @throws NullPointerException if the given variable {@code name} is 230 * {@code null} 231 */ 232 public static <T> Var<T> parse(final String name) { 233 final Matcher matcher = VAR_INDEX.matcher(name); 234 235 return matcher.matches() 236 ? of(matcher.group(1), Integer.parseInt(matcher.group(2))) 237 : of(name, 0); 238 } 239 240 /** 241 * Re-indexes the variables of the given operation {@code tree}. If the 242 * operation tree is created from its string representation, the indices 243 * of the variables ({@link Var}), are all set to zero, since it needs the 244 * whole tree for setting the indices correctly. The mapping from the node 245 * string to the {@link Op} object, on the other hand, is a <em>local</em> 246 * operation. This method gives you the possibility to fix the indices of 247 * the variables. The indices of the variables are assigned according it's 248 * <em>natural</em> order. 249 * 250 * {@snippet lang="java": 251 * final TreeNode<Op<Double>> tree = TreeNode.parse( 252 * "add(mul(x,y),sub(y,x))", 253 * MathOp::toMathOp 254 * ); 255 * 256 * assert Program.eval(tree, 10.0, 5.0) == 100.0; // wrong result 257 * Var.reindex(tree); 258 * assert Program.eval(tree, 10.0, 5.0) == 45.0; // correct result 259 * } 260 * The example above shows a use-case of this method. If you parse a tree 261 * string and convert it to an operation tree, you have to re-index the 262 * variables first. If not, you will get the wrong result when evaluating 263 * the tree. After the re-indexing, you will get the correct result of 45.0. 264 * 265 * @since 5.0 266 * 267 * @see MathOp#toMathOp(String) 268 * @see Program#eval(Tree, Object[]) 269 * 270 * @param tree the tree where the variable indices need to be fixed 271 * @param <V> the operation value type 272 */ 273 public static <V> void reindex(final TreeNode<Op<V>> tree) { 274 final SortedSet<Var<V>> vars = tree.stream() 275 .filter(node -> node.value() instanceof Var) 276 .map(node -> (Var<V>)node.value()) 277 .collect(Collectors.toCollection(TreeSet::new)); 278 279 int index = 0; 280 final Map<Var<V>, Integer> indexes = new HashMap<>(); 281 for (Var<V> var : vars) { 282 indexes.put(var, index++); 283 } 284 285 reindex(tree, indexes); 286 } 287 288 /** 289 * Re-indexes the variables of the given operation {@code tree}. If the 290 * operation tree is created from its string representation, the indices 291 * of the variables ({@link Var}), are all set to zero, since it needs the 292 * whole tree for setting the indices correctly. 293 * 294 * {@snippet lang="java": 295 * final TreeNode<Op<Double>> tree = TreeNode.parse( 296 * "add(mul(x,y),sub(y,x))", 297 * MathOp::toMathOp 298 * ); 299 * 300 * assert Program.eval(tree, 10.0, 5.0) == 100.0; // wrong result 301 * final Map<Var<Double>, Integer> indexes = new HashMap<>(); 302 * indexes.put(Var.of("x"), 0); 303 * indexes.put(Var.of("y"), 1); 304 * Var.reindex(tree, indexes); 305 * assert Program.eval(tree, 10.0, 5.0) == 45.0; // correct result 306 * } 307 * The example above shows a use-case of this method. If you parse a tree 308 * string and convert it to an operation tree, you have to re-index the 309 * variables first. If not, you will get the wrong result when evaluating 310 * the tree. After the re-indexing, you will get the correct result of 45.0. 311 * 312 * @since 5.0 313 * 314 * @see MathOp#toMathOp(String) 315 * @see BoolOp#toBoolOp(String) 316 * @see Program#eval(Tree, Object[]) 317 * 318 * @param tree the tree where the variable indices need to be fixed 319 * @param indexes the variable to index mapping 320 * @param <V> the operation value type 321 */ 322 public static <V> void reindex( 323 final TreeNode<Op<V>> tree, 324 final Map<Var<V>, Integer> indexes 325 ) { 326 for (TreeNode<Op<V>> node : tree) { 327 final Op<V> op = node.value(); 328 if (op instanceof Var) { 329 node.value(Var.of(op.name(), indexes.get(op))); 330 } 331 } 332 } 333 334}