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}