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