001 /*
002 * Java Genetic Algorithm Library (jenetics-4.4.0).
003 * Copyright (c) 2007-2019 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.Objects.requireNonNull;
024
025 import java.io.DataInput;
026 import java.io.DataOutput;
027 import java.io.IOException;
028 import java.io.InvalidObjectException;
029 import java.io.ObjectInputStream;
030 import java.io.Serializable;
031 import java.util.Comparator;
032 import java.util.Objects;
033 import java.util.TreeSet;
034 import java.util.function.Function;
035 import java.util.stream.Collectors;
036 import java.util.stream.DoubleStream;
037
038 import io.jenetics.internal.util.Lazy;
039 import io.jenetics.util.ISeq;
040
041 import io.jenetics.ext.util.Tree;
042 import io.jenetics.ext.util.TreeNode;
043
044 /**
045 * This class allows you to create a math operation tree from an expression
046 * string. The expression string may only contain functions/operations defined
047 * in {@link MathOp}.
048 *
049 * <pre>{@code
050 * final MathExpr expr = MathExpr.parse("5 + 6*x + sin(x)^34 + (1 + sin(x*5)/4)/6");
051 * final double result = expr.eval(4.32);
052 * assert result == 31.170600453465315;
053 *
054 * assert 12.0 == MathExpr.eval("3*4");
055 * assert 24.0 == MathExpr.eval("3*4*x", 2);
056 * assert 28.0 == MathExpr.eval("3*4*x + y", 2, 4);
057 * }</pre>
058 *
059 * @see MathOp
060 *
061 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
062 * @version 4.3
063 * @since 4.1
064 */
065 public final class MathExpr
066 implements
067 Function<Double[], Double>,
068 Serializable
069 {
070
071 private static final long serialVersionUID = 1L;
072
073 private final Tree<? extends Op<Double>, ?> _tree;
074
075 private final Lazy<ISeq<Var<Double>>> _vars;
076
077 // Primary constructor.
078 private MathExpr(final Tree<? extends Op<Double>, ?> tree, boolean primary) {
079 _tree = requireNonNull(tree);
080 _vars = Lazy.of(() -> ISeq.of(
081 _tree.stream()
082 .filter(node -> node.getValue() instanceof Var)
083 .map(node -> (Var<Double>)node.getValue())
084 .collect(Collectors.toCollection(() ->
085 new TreeSet<>(Comparator.comparing(Var::name))))
086 ));
087 }
088
089 /**
090 * Create a new {@code MathExpr} object from the given operation tree.
091 *
092 * @param tree the underlying operation tree
093 * @throws NullPointerException if the given {@code program} is {@code null}
094 * @throws IllegalArgumentException if the given operation tree is invalid,
095 * which means there is at least one node where the operation arity
096 * and the node child count differ.
097 */
098 public MathExpr(final Tree<? extends Op<Double>, ?> tree) {
099 this(TreeNode.ofTree(tree), true);
100 Program.check(tree);
101 }
102
103 /**
104 * Return the variable list of this <em>math</em> expression.
105 *
106 * @return the variable list of this <em>math</em> expression
107 */
108 public ISeq<Var<Double>> vars() {
109 return _vars.get();
110 }
111
112 /**
113 * Return the math expression as operation tree.
114 *
115 * @return a new expression tree
116 */
117 public Tree<? extends Op<Double>, ?> toTree() {
118 return TreeNode.ofTree(_tree);
119 }
120
121 /**
122 * @see #eval(double...)
123 * @see #eval(String, double...)
124 */
125 @Override
126 public Double apply(final Double[] args) {
127 return Program.eval(_tree, args);
128 }
129
130 /**
131 * Convenient method, which lets you apply the program function without
132 * explicitly create a wrapper array.
133 *
134 * <pre>{@code
135 * final double result = MathExpr.parse("2*z + 3*x - y").eval(3, 2, 1);
136 * assert result == 9.0;
137 * }</pre>
138 *
139 * @see #apply(Double[])
140 * @see #eval(String, double...)
141 *
142 * @param args the function arguments
143 * @return the evaluated value
144 * @throws NullPointerException if the given variable array is {@code null}
145 * @throws IllegalArgumentException if the length of the arguments array
146 * is smaller than the program arity
147 */
148 public double eval(final double... args) {
149 final double val = apply(
150 DoubleStream.of(args)
151 .boxed()
152 .toArray(Double[]::new)
153 );
154 return val == -0.0 ? 0.0 : val;
155 }
156
157 @Override
158 public int hashCode() {
159 return _tree.hashCode();
160 }
161
162 @Override
163 public boolean equals(final Object obj) {
164 return obj == this ||
165 obj instanceof MathExpr &&
166 Objects.equals(((MathExpr)obj)._tree, _tree);
167 }
168
169 /**
170 * Return the string representation of this {@code MathExpr} object. The
171 * string returned by this method can be parsed again and will result in the
172 * same expression object.
173 * <pre>{@code
174 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
175 * final MathExpr tree = MathExpr.parse(expr);
176 * assert tree.toString().equals(expr);
177 * }</pre>
178 *
179 * @return the expression string
180 */
181 @Override
182 public String toString() {
183 return format(_tree);
184 }
185
186 /**
187 * Tries to simplify {@code this} math expression.
188 *
189 * <pre>{@code
190 * final MathExpr expr = MathExpr.parse("4.0 + 4.0 + x*(5.0 + 13.0)");
191 * final MathExpr simplified = expr.simplify()
192 * System.out.println(simplified);
193 * }</pre>
194 * The simplified expression will be look like this: {@code 8.0 + (x*18.0)}.
195 *
196 * @see #prune(TreeNode)
197 * @see #simplify(Tree)
198 *
199 * @return a new simplified math expression
200 */
201 public MathExpr simplify() {
202 return new MathExpr(simplify(_tree));
203 }
204
205
206 /* *************************************************************************
207 * Java object serialization
208 * ************************************************************************/
209
210 private Object writeReplace() {
211 return new Serial(Serial.MATH_EXPR, this);
212 }
213
214 private void readObject(final ObjectInputStream stream)
215 throws InvalidObjectException
216 {
217 throw new InvalidObjectException("Serialization proxy required.");
218 }
219
220 void write(final DataOutput out) throws IOException {
221 final byte[] data = toString().getBytes(UTF_8);
222 out.writeInt(data.length);
223 out.write(data);
224 }
225
226 static MathExpr read(final DataInput in) throws IOException {
227 final byte[] data = new byte[in.readInt()];
228 in.readFully(data);
229 return parse(new String(data, UTF_8));
230 }
231
232
233 /* *************************************************************************
234 * Static helper methods.
235 * ************************************************************************/
236
237 /**
238 * Return the string representation of the given {@code tree} object. The
239 * string returned by this method can be parsed again and will result in the
240 * same expression object.
241 * <pre>{@code
242 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
243 * final MathExpr tree = MathExpr.parse(expr);
244 * assert MathExpr.toString(tree.tree()).equals(expr);
245 * }</pre>
246 *
247 * @param tree the tree object to convert to a string
248 * @return a new expression string
249 * @throws NullPointerException if the given {@code tree} is {@code null}
250 *
251 * @deprecated Use {@link #format(Tree)} instead
252 */
253 @Deprecated
254 public static String toString(final Tree<? extends Op<Double>, ?> tree) {
255 return format(tree);
256 }
257
258 /**
259 * Return the string representation of the given {@code tree} object. The
260 * string returned by this method can be parsed again and will result in the
261 * same expression object.
262 * <pre>{@code
263 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
264 * final MathExpr tree = MathExpr.parse(expr);
265 * assert MathExpr.format(tree.tree()).equals(expr);
266 * }</pre>
267 *
268 * @since 4.3
269 *
270 * @param tree the tree object to convert to a string
271 * @return a new expression string
272 * @throws NullPointerException if the given {@code tree} is {@code null}
273 */
274 public static String format(final Tree<? extends Op<Double>, ?> tree) {
275 return MathExprFormatter.format(tree);
276 }
277
278 /**
279 * Parses the given {@code expression} into a AST tree.
280 *
281 * @param expression the expression string
282 * @return the tree representation of the given {@code expression}
283 * @throws NullPointerException if the given {@code expression} is {@code null}
284 * @throws IllegalArgumentException if the given expression is invalid or
285 * can't be parsed.
286 */
287 public static MathExpr parse(final String expression) {
288 final Tree<? extends Op<Double>, ?> tree = parseTree(expression);
289 Program.check(tree);
290 return new MathExpr(tree, true);
291 }
292
293 /**
294 * Parses the given mathematical expression string and returns the
295 * mathematical expression tree. The expression may contain all functions
296 * defined in {@link MathOp}.
297 * <pre>{@code
298 * final Tree<? extends Op<Double>, ?> tree = MathExpr
299 * .parseTree("5 + 6*x + sin(x)^34 + (1 + sin(x*5)/4)/6");
300 * }</pre>
301 * The example above will lead to the following tree:
302 * <pre> {@code
303 * add
304 * ├── add
305 * │ ├── add
306 * │ │ ├── 5.0
307 * │ │ └── mul
308 * │ │ ├── 6.0
309 * │ │ └── x
310 * │ └── pow
311 * │ ├── sin
312 * │ │ └── x
313 * │ └── 34.0
314 * └── div
315 * ├── add
316 * │ ├── 1.0
317 * │ └── div
318 * │ ├── sin
319 * │ │ └── mul
320 * │ │ ├── x
321 * │ │ └── 5.0
322 * │ └── 4.0
323 * └── 6.0
324 * }</pre>
325 *
326 * @param expression the expression string
327 * @return the parsed expression tree
328 * @throws NullPointerException if the given {@code expression} is {@code null}
329 * @throws IllegalArgumentException if the given expression is invalid or
330 * can't be parsed.
331 */
332 public static Tree<? extends Op<Double>, ?>
333 parseTree(final String expression) {
334 return MathExprParser.parse(expression);
335 }
336
337 /**
338 * Evaluates the given {@code expression} with the given arguments.
339 *
340 * <pre>{@code
341 * final double result = MathExpr.eval("2*z + 3*x - y", 3, 2, 1);
342 * assert result == 9.0;
343 * }</pre>
344 *
345 * @see #apply(Double[])
346 * @see #eval(double...)
347 *
348 * @param expression the expression to evaluate
349 * @param args the expression arguments, in alphabetical order
350 * @return the evaluation result
351 * @throws NullPointerException if the given {@code expression} is
352 * {@code null}
353 * @throws IllegalArgumentException if the given operation tree is invalid,
354 * which means there is at least one node where the operation arity
355 * and the node child count differ.
356 */
357 public static double eval(final String expression, final double... args) {
358 return parse(expression).eval(args);
359 }
360
361 /**
362 * Evaluates the given {@code expression} with the given arguments.
363 *
364 * @see #apply(Double[])
365 * @see #eval(double...)
366 * @see #eval(String, double...)
367 *
368 * @since 4.4
369 *
370 * @param expression the expression to evaluate
371 * @param args the expression arguments, in alphabetical order
372 * @return the evaluation result
373 * @throws NullPointerException if the given {@code expression} is
374 * {@code null}
375 */
376 public static double eval(
377 final Tree<? extends Op<Double>, ?> expression,
378 final double... args
379 ) {
380 return new MathExpr(expression, true).eval(args);
381 }
382
383 /**
384 * Tries to simplify the given math tree.
385 *
386 * <pre>{@code
387 * final Tree<? extends Op<Double>, ?> tree =
388 * MathExpr.parseTree("4.0 + 4.0 + x*(5.0 + 13.0)");
389 * final Tree<? extends Op<Double>, ?> simplified = MathExpr.simplify(tree)
390 * System.out.println(simplified);
391 * }</pre>
392 * The simplified tree will be look like this:
393 * <pre> {@code
394 * add
395 * ├── 8.0
396 * └── mul
397 * ├── x
398 * └── 18.0
399 * }</pre>
400 *
401 * @see #prune(TreeNode)
402 * @see #simplify()
403 *
404 * @param tree the math tree to simplify
405 * @return the new simplified tree
406 * @throws NullPointerException if the given {@code tree} is {@code null}
407 */
408 public static Tree<? extends Op<Double>, ?>
409 simplify(final Tree<? extends Op<Double>, ?> tree) {
410 return MathExprRewriter.prune(TreeNode.ofTree(tree));
411 }
412
413 /**
414 * Tries to simplify the given math tree in place.
415 *
416 * @see #simplify(Tree)
417 * @see #simplify()
418 *
419 * @param tree the math tree to simplify
420 * @throws NullPointerException if the given {@code tree} is {@code null}
421 */
422 public static void prune(final TreeNode<Op<Double>> tree) {
423 MathExprRewriter.prune(tree);
424 }
425
426 }
|