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