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}