001 /*
002 * Java Genetic Algorithm Library (jenetics-7.1.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 */
020 package io.jenetics.prog.op;
021
022 import static java.lang.String.format;
023 import static io.jenetics.ext.internal.util.Names.isIdentifier;
024
025 import java.io.Serial;
026 import java.io.Serializable;
027 import java.util.HashMap;
028 import java.util.Map;
029 import java.util.Objects;
030 import java.util.SortedSet;
031 import java.util.TreeSet;
032 import java.util.regex.Matcher;
033 import java.util.regex.Pattern;
034 import java.util.stream.Collectors;
035
036 import io.jenetics.ext.util.Tree;
037 import 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 * <pre>{@code
047 * final ISeq<? extends Op<Double>> operations = ISeq.of(...);
048 * final ISeq<? extends Op<Double>> terminals = ISeq.of(
049 * Var.of("x", 0), Var.of("y", 1)
050 * );
051 * }</pre>
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 * <pre>{@code
057 * static double error(final ProgramChromosome<Double> program) {
058 * final double x = ...;
059 * final double y = ...;
060 * final double result = program.eval(x, y);
061 * ...
062 * return ...;
063 * }
064 * }</pre>
065 *
066 * @implNote
067 * The {@code Var} object is comparable according it's name.
068 *
069 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
070 * @version 7.0
071 * @since 3.9
072 */
073 public 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 it's 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 * <pre>{@code
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 * }</pre>
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 needs 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 it's 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 * <pre>{@code
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 * }</pre>
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 needs 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 }
|