001/* 002 * Java Genetic Algorithm Library (jenetics-9.0.0). 003 * Copyright (c) 2007-2026 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.ext.internal.util.FormulaParser.TokenType.FUNCTION; 027import static io.jenetics.ext.internal.util.FormulaParser.TokenType.UNARY_OPERATOR; 028import static io.jenetics.internal.util.SerialIO.readInt; 029import static io.jenetics.internal.util.SerialIO.writeInt; 030import static io.jenetics.prog.op.MathTokenType.COMMA; 031import static io.jenetics.prog.op.MathTokenType.DIV; 032import static io.jenetics.prog.op.MathTokenType.IDENTIFIER; 033import static io.jenetics.prog.op.MathTokenType.LPAREN; 034import static io.jenetics.prog.op.MathTokenType.MINUS; 035import static io.jenetics.prog.op.MathTokenType.MOD; 036import static io.jenetics.prog.op.MathTokenType.NUMBER; 037import static io.jenetics.prog.op.MathTokenType.PLUS; 038import static io.jenetics.prog.op.MathTokenType.POW; 039import static io.jenetics.prog.op.MathTokenType.RPAREN; 040import static io.jenetics.prog.op.MathTokenType.TIMES; 041 042import java.io.DataInput; 043import java.io.DataOutput; 044import java.io.IOException; 045import java.io.InvalidObjectException; 046import java.io.ObjectInputStream; 047import java.io.Serial; 048import java.io.Serializable; 049import java.util.TreeSet; 050import java.util.function.Function; 051import java.util.function.Supplier; 052 053import io.jenetics.internal.util.Lazy; 054import io.jenetics.util.ISeq; 055 056import io.jenetics.ext.internal.parser.ParsingException; 057import io.jenetics.ext.internal.parser.Token; 058import io.jenetics.ext.internal.util.FormulaParser; 059import io.jenetics.ext.internal.util.FormulaParser.TokenType; 060import io.jenetics.ext.rewriting.TreeRewriteRule; 061import io.jenetics.ext.rewriting.TreeRewriter; 062import io.jenetics.ext.util.FlatTreeNode; 063import io.jenetics.ext.util.Tree; 064import 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 */ 073public 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 * {@snippet lang="java": 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 * } 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 * @see #eval(double...) 219 * @see #eval(String, double...) 220 */ 221 @Override 222 public Double apply(final Double[] args) { 223 return Program.eval(_tree, args); 224 } 225 226 /** 227 * Convenient method, which lets you apply the program function without 228 * explicitly create a wrapper array. 229 * 230 * {@snippet lang="java": 231 * final double result = MathExpr.parse("2*z + 3*x - y").eval(3, 2, 1); 232 * assert result == 9.0; 233 * } 234 * 235 * @see #apply(Double[]) 236 * @see #eval(String, double...) 237 * 238 * @param args the function arguments 239 * @return the evaluated value 240 * @throws NullPointerException if the given variable array is {@code null} 241 * @throws IllegalArgumentException if the length of the argument array 242 * is smaller than the program arity 243 */ 244 public double eval(final double... args) { 245 final double val = apply(box(args)); 246 return val == -0.0 ? 0.0 : val; 247 } 248 249 @Override 250 public int hashCode() { 251 return Tree.hashCode(_tree); 252 } 253 254 @Override 255 public boolean equals(final Object obj) { 256 return obj instanceof MathExpr expr && 257 _tree.equals(expr._tree); 258 } 259 260 /** 261 * Return the string representation of this {@code MathExpr} object. The 262 * string returned by this method can be parsed again and will result in the 263 * same expression object. 264 * {@snippet lang="java": 265 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5"; 266 * final MathExpr tree = MathExpr.parse(expr); 267 * assert tree.toString().equals(expr); 268 * } 269 * 270 * @return the expression string 271 */ 272 @Override 273 public String toString() { 274 return format(_tree); 275 } 276 277 /** 278 * Simplifying {@code this} expression by applying the given {@code rewriter} 279 * and the given rewrite {@code limit}. 280 * 281 * @param rewriter the rewriter used for simplifying {@code this} expression 282 * @param limit the rewrite limit 283 * @return a newly created math expression object 284 * @throws NullPointerException if the {@code rewriter} is {@code null} 285 * @throws IllegalArgumentException if the {@code limit} is smaller than 286 * zero 287 */ 288 public MathExpr simplify( 289 final TreeRewriter<Op<Double>> rewriter, 290 final int limit 291 ) { 292 final TreeNode<Op<Double>> tree = TreeNode.ofTree(tree()); 293 rewriter.rewrite(tree, limit); 294 return new MathExpr(FlatTreeNode.ofTree(tree)); 295 } 296 297 /** 298 * Simplifying {@code this} expression by applying the given {@code rewriter}. 299 * 300 * @param rewriter the rewriter used for simplifying {@code this} expression 301 * @return a newly created math expression object 302 * @throws NullPointerException if the {@code rewriter} is {@code null} 303 */ 304 public MathExpr simplify(final TreeRewriter<Op<Double>> rewriter) { 305 return simplify(rewriter, Integer.MAX_VALUE); 306 } 307 308 /** 309 * Simplifies {@code this} expression by applying the default 310 * {@link #REWRITER}. 311 * 312 * @return a newly created math expression object 313 */ 314 public MathExpr simplify() { 315 return simplify(REWRITER); 316 } 317 318 private static Double[] box(final double... values) { 319 final Double[] result = new Double[values.length]; 320 for (int i = values.length; --i >= 0;) { 321 result[i] = values[i]; 322 } 323 return result; 324 } 325 326 private static Op<Double> toOp( 327 final Token<String> token, 328 final TokenType type 329 ) { 330 return switch ((MathTokenType)token.type()) { 331 case PLUS -> type == UNARY_OPERATOR ? MathOp.ID : MathOp.ADD; 332 case MINUS -> type == UNARY_OPERATOR ? MathOp.NEG : MathOp.SUB; 333 case TIMES -> MathOp.MUL; 334 case DIV -> MathOp.DIV; 335 case MOD -> MathOp.MOD; 336 case POW -> MathOp.POW; 337 case NUMBER -> Const.of(Double.parseDouble(token.value())); 338 case IDENTIFIER -> { 339 if (type == FUNCTION) { 340 yield MathOp.toMathOp(token.value()); 341 } else { 342 yield switch (token.value()) { 343 case "π", "PI" -> MathOp.PI; 344 default -> Var.of(token.value()); 345 }; 346 } 347 } 348 default -> throw new ParsingException("Unknown token: " + token); 349 }; 350 } 351 352 353 /* ************************************************************************* 354 * Java object serialization 355 * ************************************************************************/ 356 357 @Serial 358 private Object writeReplace() { 359 return new SerialProxy(SerialProxy.MATH_EXPR, this); 360 } 361 362 @Serial 363 private void readObject(final ObjectInputStream stream) 364 throws InvalidObjectException 365 { 366 throw new InvalidObjectException("Serialization proxy required."); 367 } 368 369 void write(final DataOutput out) throws IOException { 370 final byte[] data = toString().getBytes(UTF_8); 371 writeInt(data.length, out); 372 out.write(data); 373 } 374 375 static MathExpr read(final DataInput in) throws IOException { 376 final byte[] data = new byte[readInt(in)]; 377 in.readFully(data); 378 return parse(new String(data, UTF_8)); 379 } 380 381 /* ************************************************************************* 382 * Static helper methods. 383 * ************************************************************************/ 384 385 /** 386 * Return the string representation of the given {@code tree} object. The 387 * string returned by this method can be parsed again and will result in the 388 * same expression object. 389 * {@snippet lang="java": 390 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5"; 391 * final MathExpr tree = MathExpr.parse(expr); 392 * assert MathExpr.format(tree.tree()).equals(expr); 393 * } 394 * 395 * @since 4.3 396 * 397 * @param tree the tree object to convert to a string 398 * @return a new expression string 399 * @throws NullPointerException if the given {@code tree} is {@code null} 400 */ 401 public static String format(final Tree<? extends Op<Double>, ?> tree) { 402 return MathExprFormatter.format(tree); 403 } 404 405 /** 406 * Parses the given {@code expression} into an AST tree. 407 * 408 * @param expression the expression string 409 * @return the tree representation of the given {@code expression} 410 * @throws NullPointerException if the given {@code expression} is {@code null} 411 * @throws IllegalArgumentException if the given expression is invalid or 412 * can't be parsed. 413 */ 414 public static MathExpr parse(final String expression) { 415 return new MathExpr(FlatTreeNode.ofTree(parseTree(expression))); 416 } 417 418 /** 419 * Parses the given mathematical expression string and returns the 420 * mathematical expression tree. The expression may contain all functions 421 * defined in {@link MathOp}. 422 * {@snippet lang="java": 423 * final Tree<? extends Op<Double>, ?> tree = MathExpr 424 * .parseTree("5 + 6*x + sin(x)^34 + (1 + sin(x*5)/4)/6"); 425 * } 426 * The example above will lead to the following tree: 427 * <pre> {@code 428 * add 429 * ├── add 430 * │ ├── add 431 * │ │ ├── 5.0 432 * │ │ └── mul 433 * │ │ ├── 6.0 434 * │ │ └── x 435 * │ └── pow 436 * │ ├── sin 437 * │ │ └── x 438 * │ └── 34.0 439 * └── div 440 * ├── add 441 * │ ├── 1.0 442 * │ └── div 443 * │ ├── sin 444 * │ │ └── mul 445 * │ │ ├── x 446 * │ │ └── 5.0 447 * │ └── 4.0 448 * └── 6.0 449 * } </pre> 450 * 451 * @param expression the expression string 452 * @return the parsed expression tree 453 * @throws NullPointerException if the given {@code expression} is {@code null} 454 * @throws IllegalArgumentException if the given expression is invalid or 455 * can't be parsed. 456 */ 457 public static Tree<Op<Double>, ?> parseTree(final String expression) { 458 final var tokenizer = new MathStringTokenizer(expression); 459 return parseTree(tokenizer::next); 460 } 461 462 private static <V> Tree<Op<Double>, ?> 463 parseTree(final Supplier<Token<String>> tokens) { 464 final TreeNode<Op<Double>> tree = FORMULA_PARSER.parse(tokens, MathExpr::toOp); 465 Var.reindex(tree); 466 return FlatTreeNode.ofTree(tree); 467 } 468 469 /** 470 * Evaluates the given {@code expression} with the given arguments. 471 * 472 * {@snippet lang="java": 473 * final double result = MathExpr.eval("2*z + 3*x - y", 3, 2, 1); 474 * assert result == 9.0; 475 * } 476 * 477 * @see #apply(Double[]) 478 * @see #eval(double...) 479 * 480 * @param expression the expression to evaluate 481 * @param args the expression arguments, in alphabetical order 482 * @return the evaluation result 483 * @throws NullPointerException if the given {@code expression} is 484 * {@code null} 485 * @throws IllegalArgumentException if the given operation tree is invalid, 486 * which means there is at least one node where the operation arity 487 * and the node child count differ. 488 */ 489 public static double eval(final String expression, final double... args) { 490 return parse(expression).eval(args); 491 } 492 493 /** 494 * Evaluates the given {@code expression} with the given arguments. 495 * 496 * @see #apply(Double[]) 497 * @see #eval(double...) 498 * @see #eval(String, double...) 499 * 500 * @since 4.4 501 * 502 * @param expression the expression to evaluate 503 * @param args the expression arguments, in alphabetical order 504 * @return the evaluation result 505 * @throws NullPointerException if the given {@code expression} is 506 * {@code null} 507 */ 508 public static double eval( 509 final Tree<? extends Op<Double>, ?> expression, 510 final double... args 511 ) { 512 return Program.eval(expression, box(args)); 513 } 514 515 /** 516 * Applies the {@link #REWRITER} to the given (mutable) {@code tree}. The 517 * tree rewrite is done in place. 518 * 519 * @see TreeRewriter#rewrite(TreeNode, int) 520 * 521 * @since 5.0 522 * 523 * @param tree the tree to be rewritten 524 * @param limit the maximal number this rewrite rule is applied to the given 525 * tree. This guarantees the termination of the rewrite method. 526 * @return the number of rewrites applied to the input {@code tree} 527 * @throws NullPointerException if the given {@code tree} is {@code null} 528 * @throws IllegalArgumentException if the {@code limit} is smaller than 529 * one 530 */ 531 public static int rewrite(final TreeNode<Op<Double>> tree, final int limit) { 532 return REWRITER.rewrite(tree, limit); 533 } 534 535 /** 536 * Applies the {@link #REWRITER} to the given (mutable) {@code tree}. The 537 * tree rewrite is done in place. The limit of the applied rewrites is set 538 * unlimited ({@link Integer#MAX_VALUE}). 539 * 540 * @see #rewrite(TreeNode, int) 541 * @see TreeRewriter#rewrite(TreeNode) 542 * 543 * @since 5.0 544 * 545 * @param tree the tree to be rewritten 546 * @return {@code true} if the tree has been changed (rewritten) by this 547 * method, {@code false} if the tree hasn't been changed 548 * @throws NullPointerException if the given {@code tree} is {@code null} 549 */ 550 public static int rewrite(final TreeNode<Op<Double>> tree) { 551 return rewrite(tree, Integer.MAX_VALUE); 552 } 553 554}