Var.java
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 }