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