001/*
002 * Java Genetic Algorithm Library (jenetics-7.0.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 */
020package io.jenetics.prog.op;
021
022import static java.nio.charset.StandardCharsets.UTF_8;
023import static java.util.Comparator.comparing;
024import static java.util.Objects.requireNonNull;
025import static java.util.stream.Collectors.toCollection;
026import static io.jenetics.internal.util.SerialIO.readInt;
027import static io.jenetics.internal.util.SerialIO.writeInt;
028import static io.jenetics.prog.op.Numbers.box;
029
030import java.io.DataInput;
031import java.io.DataOutput;
032import java.io.IOException;
033import java.io.InvalidObjectException;
034import java.io.ObjectInputStream;
035import java.io.Serial;
036import java.io.Serializable;
037import java.util.TreeSet;
038import java.util.function.Function;
039
040import io.jenetics.internal.util.Lazy;
041import io.jenetics.util.ISeq;
042
043import io.jenetics.ext.rewriting.TreeRewriteRule;
044import io.jenetics.ext.rewriting.TreeRewriter;
045import io.jenetics.ext.util.FlatTreeNode;
046import io.jenetics.ext.util.Tree;
047import io.jenetics.ext.util.TreeNode;
048
049/**
050 * This class allows you to create a math operation tree from an expression
051 * string. The expression string may only contain functions/operations defined
052 * in {@link MathOp}.
053 *
054 * <pre>{@code
055 * final MathExpr expr = MathExpr.parse("5 + 6*x + sin(x)^34 + (1 + sin(x*5)/4)/6");
056 * final double result = expr.eval(4.32);
057 * assert result == 31.170600453465315;
058 *
059 * assert 12.0 == MathExpr.eval("3*4");
060 * assert 24.0 == MathExpr.eval("3*4*x", 2);
061 * assert 28.0 == MathExpr.eval("3*4*x + y", 2, 4);
062 * }</pre>
063 *
064 * @see MathOp
065 *
066 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
067 * @version 5.0
068 * @since 4.1
069 */
070public final class MathExpr
071        implements
072                Function<Double[], Double>,
073                Serializable
074{
075
076        @Serial
077        private static final long serialVersionUID = 1L;
078
079        /**
080         * This tree-rewriter rewrites constant expressions to its single value.
081         *
082         * <pre>{@code
083         * final TreeNode<Op<Double>> tree = MathExpr.parseTree("1 + 2*(6 + 7)");
084         * MathExpr.CONST_REWRITER.rewrite(tree);
085         * assertEquals(tree.getValue(), Const.of(27.0));
086         * }</pre>
087         *
088         * @since 5.0
089         */
090        public static final TreeRewriter<Op<Double>> CONST_REWRITER =
091                ConstRewriter.DOUBLE;
092
093        /**
094         * This rewriter implements some common arithmetic identities, in exactly
095         * this order.
096         * <pre> {@code
097         *     sub($x,$x) ->  0
098         *     sub($x,0)  ->  $x
099         *     add($x,0)  ->  $x
100         *     add(0,$x)  ->  $x
101         *     add($x,$x) ->  mul(2,$x)
102         *     div($x,$x) ->  1
103         *     div(0,$x)  ->  0
104         *     mul($x,0)  ->  0
105         *     mul(0,$x)  ->  0
106         *     mul($x,1)  ->  $x
107         *     mul(1,$x)  ->  $x
108         *     mul($x,$x) ->  pow($x,2)
109         *     pow($x,0)  ->  1
110         *     pow(0,$x)  ->  0
111         *     pow($x,1)  ->  $x
112         *     pow(1,$x)  ->  1
113         * }</pre>
114         *
115         * @since 5.0
116         */
117        public static final TreeRewriter<Op<Double>> ARITHMETIC_REWRITER =
118                TreeRewriter.concat(
119                        compile("sub($x,$x) -> 0"),
120                        compile("sub($x,0) -> $x"),
121                        compile("add($x,0) -> $x"),
122                        compile("add(0,$x) -> $x"),
123                        compile("add($x,$x) -> mul(2,$x)"),
124                        compile("div($x,$x) -> 1"),
125                        compile("div(0,$x) -> 0"),
126                        compile("mul($x,0) -> 0"),
127                        compile("mul(0,$x) -> 0"),
128                        compile("mul($x,1) -> $x"),
129                        compile("mul(1,$x) -> $x"),
130                        compile("mul($x,$x) -> pow($x,2)"),
131                        compile("pow($x,0) -> 1"),
132                        compile("pow(0,$x) -> 0"),
133                        compile("pow($x,1) -> $x"),
134                        compile("pow(1,$x) -> 1")
135                );
136
137        private static TreeRewriter<Op<Double>> compile(final String rule) {
138                return TreeRewriteRule.parse(rule, MathOp::toMathOp);
139        }
140
141        /**
142         * Combination of the {@link #ARITHMETIC_REWRITER} and the
143         * {@link #CONST_REWRITER}, in this specific order.
144         *
145         * @since 5.0
146         */
147        public static final TreeRewriter<Op<Double>> REWRITER = TreeRewriter.concat(
148                ARITHMETIC_REWRITER,
149                CONST_REWRITER
150        );
151
152        private final Tree<? extends Op<Double>, ?> _tree;
153
154        private final Lazy<ISeq<Var<Double>>> _vars;
155
156        // Primary constructor.
157        private MathExpr(final Tree<? extends Op<Double>, ?> tree, boolean primary) {
158                _tree = requireNonNull(tree);
159                _vars = Lazy.of(() -> ISeq.of(
160                        _tree.stream()
161                                .filter(node -> node.value() instanceof Var)
162                                .map(node -> (Var<Double>)node.value())
163                                .collect(toCollection(() -> new TreeSet<>(comparing(Var::name))))
164                ));
165        }
166
167        /**
168         * Create a new {@code MathExpr} object from the given operation tree.
169         *
170         * @param tree the underlying operation tree
171         * @throws NullPointerException if the given {@code program} is {@code null}
172         * @throws IllegalArgumentException if the given operation tree is invalid,
173         *         which means there is at least one node where the operation arity
174         *         and the node child count differ.
175         */
176        public MathExpr(final Tree<? extends Op<Double>, ?> tree) {
177                this(FlatTreeNode.ofTree(tree), true);
178                Program.check(tree);
179        }
180
181        /**
182         * Return the variable list of this <em>math</em> expression.
183         *
184         * @return the variable list of this <em>math</em> expression
185         */
186        public ISeq<Var<Double>> vars() {
187                return _vars.get();
188        }
189
190        /**
191         * Return the math expression as operation tree.
192         *
193         * @return a new expression tree
194         */
195        public TreeNode<Op<Double>> toTree() {
196                return TreeNode.ofTree(_tree);
197        }
198
199        /**
200         * @see #eval(double...)
201         * @see #eval(String, double...)
202         */
203        @Override
204        public Double apply(final Double[] args) {
205                return Program.eval(_tree, args);
206        }
207
208        /**
209         * Convenient method, which lets you apply the program function without
210         * explicitly create a wrapper array.
211         *
212         * <pre>{@code
213         *  final double result = MathExpr.parse("2*z + 3*x - y").eval(3, 2, 1);
214         *  assert result == 9.0;
215         * }</pre>
216         *
217         * @see #apply(Double[])
218         * @see #eval(String, double...)
219         *
220         * @param args the function arguments
221         * @return the evaluated value
222         * @throws NullPointerException if the given variable array is {@code null}
223         * @throws IllegalArgumentException if the length of the arguments array
224         *         is smaller than the program arity
225         */
226        public double eval(final double... args) {
227                final double val = apply(box(args));
228                return val == -0.0 ? 0.0 : val;
229        }
230
231        @Override
232        public int hashCode() {
233                return Tree.hashCode(_tree);
234        }
235
236        @Override
237        public boolean equals(final Object obj) {
238                return obj == this ||
239                        obj instanceof MathExpr other &&
240                        Tree.equals(other._tree, _tree);
241        }
242
243        /**
244         * Return the string representation of this {@code MathExpr} object. The
245         * string returned by this method can be parsed again and will result in the
246         * same expression object.
247         * <pre>{@code
248         *  final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
249         *  final MathExpr tree = MathExpr.parse(expr);
250         *  assert tree.toString().equals(expr);
251         * }</pre>
252         *
253         * @return the expression string
254         */
255        @Override
256        public String toString() {
257                return format(_tree);
258        }
259
260        /**
261         * Simplifying {@code this} expression by applying the given {@code rewriter}
262         * and the given rewrite {@code limit}.
263         *
264         * @param rewriter the rewriter used for simplifying {@code this} expression
265         * @param limit the rewrite limit
266         * @return a newly created math expression object
267         * @throws NullPointerException if the {@code rewriter} is {@code null}
268         * @throws IllegalArgumentException if the {@code limit} is smaller than
269         *         zero
270         */
271        public MathExpr simplify(
272                final TreeRewriter<Op<Double>> rewriter,
273                final int limit
274        ) {
275                final TreeNode<Op<Double>> tree = toTree();
276                rewriter.rewrite(tree, limit);
277                return new MathExpr(FlatTreeNode.ofTree(tree), true);
278        }
279
280        /**
281         * Simplifying {@code this} expression by applying the given {@code rewriter}.
282         *
283         * @param rewriter the rewriter used for simplifying {@code this} expression
284         * @return a newly created math expression object
285         * @throws NullPointerException if the {@code rewriter} is {@code null}
286         */
287        public MathExpr simplify(final TreeRewriter<Op<Double>> rewriter) {
288                return simplify(rewriter, Integer.MAX_VALUE);
289        }
290
291        /**
292         * Simplifies {@code this} expression by applying the default
293         * {@link #REWRITER}.
294         *
295         * @return a newly created math expression object
296         */
297        public MathExpr simplify() {
298                return simplify(REWRITER);
299        }
300
301
302        /* *************************************************************************
303         *  Java object serialization
304         * ************************************************************************/
305
306        @Serial
307        private Object writeReplace() {
308                return new SerialProxy(SerialProxy.MATH_EXPR, this);
309        }
310
311        @Serial
312        private void readObject(final ObjectInputStream stream)
313                throws InvalidObjectException
314        {
315                throw new InvalidObjectException("Serialization proxy required.");
316        }
317
318        void write(final DataOutput out) throws IOException {
319                final byte[] data = toString().getBytes(UTF_8);
320                writeInt(data.length, out);
321                out.write(data);
322        }
323
324        static MathExpr read(final DataInput in) throws IOException {
325                final byte[] data = new byte[readInt(in)];
326                in.readFully(data);
327                return parse(new String(data, UTF_8));
328        }
329
330
331        /* *************************************************************************
332         * Static helper methods.
333         * ************************************************************************/
334
335        /**
336         * Return the string representation of the given {@code tree} object. The
337         * string returned by this method can be parsed again and will result in the
338         * same expression object.
339         * <pre>{@code
340         *  final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
341         *  final MathExpr tree = MathExpr.parse(expr);
342         *  assert MathExpr.format(tree.tree()).equals(expr);
343         * }</pre>
344         *
345         * @since 4.3
346         *
347         * @param tree the tree object to convert to a string
348         * @return a new expression string
349         * @throws NullPointerException if the given {@code tree} is {@code null}
350         */
351        public static String format(final Tree<? extends Op<Double>, ?> tree) {
352                return MathExprFormatter.format(tree);
353        }
354
355        /**
356         * Parses the given {@code expression} into a AST tree.
357         *
358         * @param expression the expression string
359         * @return the tree representation of the given {@code expression}
360         * @throws NullPointerException if the given {@code expression} is {@code null}
361         * @throws IllegalArgumentException if the given expression is invalid or
362         *         can't be parsed.
363         */
364        public static MathExpr parse(final String expression) {
365                final Tree<? extends Op<Double>, ?> tree = parseTree(expression);
366                Program.check(tree);
367                return new MathExpr(tree, true);
368        }
369
370        /**
371         * Parses the given mathematical expression string and returns the
372         * mathematical expression tree. The expression may contain all functions
373         * defined in {@link MathOp}.
374         * <pre>{@code
375         * final Tree<? extends Op<Double>, ?> tree = MathExpr
376         *     .parseTree("5 + 6*x + sin(x)^34 + (1 + sin(x*5)/4)/6");
377         * }</pre>
378         * The example above will lead to the following tree:
379         * <pre> {@code
380         *  add
381         *  ├── add
382         *  │   ├── add
383         *  │   │   ├── 5.0
384         *  │   │   └── mul
385         *  │   │       ├── 6.0
386         *  │   │       └── x
387         *  │   └── pow
388         *  │       ├── sin
389         *  │       │   └── x
390         *  │       └── 34.0
391         *  └── div
392         *      ├── add
393         *      │   ├── 1.0
394         *      │   └── div
395         *      │       ├── sin
396         *      │       │   └── mul
397         *      │       │       ├── x
398         *      │       │       └── 5.0
399         *      │       └── 4.0
400         *      └── 6.0
401         * }</pre>
402         *
403         * @param expression the expression string
404         * @return the parsed expression tree
405         * @throws NullPointerException if the given {@code expression} is {@code null}
406         * @throws IllegalArgumentException if the given expression is invalid or
407         *         can't be parsed.
408         */
409        public static TreeNode<Op<Double>> parseTree(final String expression) {
410                return MathExprParser.parse(expression);
411        }
412
413        /**
414         * Evaluates the given {@code expression} with the given arguments.
415         *
416         * <pre>{@code
417         *  final double result = MathExpr.eval("2*z + 3*x - y", 3, 2, 1);
418         *  assert result == 9.0;
419         * }</pre>
420         *
421         * @see #apply(Double[])
422         * @see #eval(double...)
423         *
424         * @param expression the expression to evaluate
425         * @param args the expression arguments, in alphabetical order
426         * @return the evaluation result
427         * @throws NullPointerException if the given {@code expression} is
428         *         {@code null}
429         * @throws IllegalArgumentException if the given operation tree is invalid,
430         *         which means there is at least one node where the operation arity
431         *         and the node child count differ.
432         */
433        public static double eval(final String expression, final double... args) {
434                return parse(expression).eval(args);
435        }
436
437        /**
438         * Evaluates the given {@code expression} with the given arguments.
439         *
440         * @see #apply(Double[])
441         * @see #eval(double...)
442         * @see #eval(String, double...)
443         *
444         * @since 4.4
445         *
446         * @param expression the expression to evaluate
447         * @param args the expression arguments, in alphabetical order
448         * @return the evaluation result
449         * @throws NullPointerException if the given {@code expression} is
450         *         {@code null}
451         */
452        public static double eval(
453                final Tree<? extends Op<Double>, ?> expression,
454                final double... args
455        ) {
456                return new MathExpr(expression, true).eval(args);
457        }
458
459        /**
460         * Applies the {@link #REWRITER} to the given (mutable) {@code tree}. The
461         * tree rewrite is done in place.
462         *
463         * @see TreeRewriter#rewrite(TreeNode, int)
464         *
465         * @since 5.0
466         *
467         * @param tree the tree to be rewritten
468         * @param limit the maximal number this rewrite rule is applied to the given
469         *        tree. This guarantees the termination of the rewrite method.
470         * @return the number of rewrites applied to the input {@code tree}
471         * @throws NullPointerException if the given {@code tree} is {@code null}
472         * @throws IllegalArgumentException if the {@code limit} is smaller than
473         *         one
474         */
475        public static int rewrite(final TreeNode<Op<Double>> tree, final int limit) {
476                return REWRITER.rewrite(tree, limit);
477        }
478
479        /**
480         * Applies the {@link #REWRITER} to the given (mutable) {@code tree}. The
481         * tree rewrite is done in place. The limit of the applied rewrites is set
482         * unlimited ({@link Integer#MAX_VALUE}).
483         *
484         * @see #rewrite(TreeNode, int)
485         * @see TreeRewriter#rewrite(TreeNode)
486         *
487         * @since 5.0
488         *
489         * @param tree the tree to be rewritten
490         * @return {@code true} if the tree has been changed (rewritten) by this
491         *         method, {@code false} if the tree hasn't been changed
492         * @throws NullPointerException if the given {@code tree} is {@code null}
493         */
494        public static int rewrite(final TreeNode<Op<Double>> tree) {
495                return rewrite(tree, Integer.MAX_VALUE);
496        }
497
498}