001 /*
002 * Java Genetic Algorithm Library (jenetics-4.3.0).
003 * Copyright (c) 2007-2018 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 return apply(DoubleStream.of(args).boxed().toArray(Double[]::new));
150 }
151
152 @Override
153 public int hashCode() {
154 return _tree.hashCode();
155 }
156
157 @Override
158 public boolean equals(final Object obj) {
159 return obj == this ||
160 obj instanceof MathExpr &&
161 Objects.equals(((MathExpr)obj)._tree, _tree);
162 }
163
164 /**
165 * Return the string representation of this {@code MathExpr} object. The
166 * string returned by this method can be parsed again and will result in the
167 * same expression object.
168 * <pre>{@code
169 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
170 * final MathExpr tree = MathExpr.parse(expr);
171 * assert tree.toString().equals(expr);
172 * }</pre>
173 *
174 * @return the expression string
175 */
176 @Override
177 public String toString() {
178 return format(_tree);
179 }
180
181 /**
182 * Tries to simplify {@code this} math expression.
183 *
184 * <pre>{@code
185 * final MathExpr expr = MathExpr.parse("4.0 + 4.0 + x*(5.0 + 13.0)");
186 * final MathExpr simplified = expr.simplify()
187 * System.out.println(simplified);
188 * }</pre>
189 * The simplified expression will be look like this: {@code 8.0 + (x*18.0)}.
190 *
191 * @see #prune(TreeNode)
192 * @see #simplify(Tree)
193 *
194 * @return a new simplified math expression
195 */
196 public MathExpr simplify() {
197 return new MathExpr(simplify(_tree));
198 }
199
200
201 /* *************************************************************************
202 * Java object serialization
203 * ************************************************************************/
204
205 private Object writeReplace() {
206 return new Serial(Serial.MATH_EXPR, this);
207 }
208
209 private void readObject(final ObjectInputStream stream)
210 throws InvalidObjectException
211 {
212 throw new InvalidObjectException("Serialization proxy required.");
213 }
214
215 void write(final DataOutput out) throws IOException {
216 final byte[] data = toString().getBytes(UTF_8);
217 out.writeInt(data.length);
218 out.write(data);
219 }
220
221 static MathExpr read(final DataInput in) throws IOException {
222 final byte[] data = new byte[in.readInt()];
223 in.readFully(data);
224 return parse(new String(data, UTF_8));
225 }
226
227
228 /* *************************************************************************
229 * Static helper methods.
230 * ************************************************************************/
231
232 /**
233 * Return the string representation of the given {@code tree} object. The
234 * string returned by this method can be parsed again and will result in the
235 * same expression object.
236 * <pre>{@code
237 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
238 * final MathExpr tree = MathExpr.parse(expr);
239 * assert MathExpr.toString(tree.tree()).equals(expr);
240 * }</pre>
241 *
242 * @param tree the tree object to convert to a string
243 * @return a new expression string
244 * @throws NullPointerException if the given {@code tree} is {@code null}
245 *
246 * @deprecated Use {@link #format(Tree)} instead
247 */
248 @Deprecated
249 public static String toString(final Tree<? extends Op<Double>, ?> tree) {
250 return format(tree);
251 }
252
253 /**
254 * Return the string representation of the given {@code tree} object. The
255 * string returned by this method can be parsed again and will result in the
256 * same expression object.
257 * <pre>{@code
258 * final String expr = "5.0 + 6.0*x + sin(x)^34.0 + (1.0 + sin(x*5.0)/4.0) + 6.5";
259 * final MathExpr tree = MathExpr.parse(expr);
260 * assert MathExpr.format(tree.tree()).equals(expr);
261 * }</pre>
262 *
263 * @since 4.3
264 *
265 * @param tree the tree object to convert to a string
266 * @return a new expression string
267 * @throws NullPointerException if the given {@code tree} is {@code null}
268 */
269 public static String format(final Tree<? extends Op<Double>, ?> tree) {
270 return MathExprFormatter.format(tree);
271 }
272
273 /**
274 * Parses the given {@code expression} into a AST tree.
275 *
276 * @param expression the expression string
277 * @return the tree representation of the given {@code expression}
278 * @throws NullPointerException if the given {@code expression} is {@code null}
279 * @throws IllegalArgumentException if the given expression is invalid or
280 * can't be parsed.
281 */
282 public static MathExpr parse(final String expression) {
283 final Tree<? extends Op<Double>, ?> tree = parseTree(expression);
284 Program.check(tree);
285 return new MathExpr(tree, true);
286 }
287
288 /**
289 * Parses the given mathematical expression string and returns the
290 * mathematical expression tree. The expression may contain all functions
291 * defined in {@link MathOp}.
292 * <pre>{@code
293 * final Tree<? extends Op<Double>, ?> tree = MathExpr
294 * .parseTree("5 + 6*x + sin(x)^34 + (1 + sin(x*5)/4)/6");
295 * }</pre>
296 * The example above will lead to the following tree:
297 * <pre> {@code
298 * add
299 * ├── add
300 * │ ├── add
301 * │ │ ├── 5.0
302 * │ │ └── mul
303 * │ │ ├── 6.0
304 * │ │ └── x
305 * │ └── pow
306 * │ ├── sin
307 * │ │ └── x
308 * │ └── 34.0
309 * └── div
310 * ├── add
311 * │ ├── 1.0
312 * │ └── div
313 * │ ├── sin
314 * │ │ └── mul
315 * │ │ ├── x
316 * │ │ └── 5.0
317 * │ └── 4.0
318 * └── 6.0
319 * }</pre>
320 *
321 * @param expression the expression string
322 * @return the parsed expression tree
323 * @throws NullPointerException if the given {@code expression} is {@code null}
324 * @throws IllegalArgumentException if the given expression is invalid or
325 * can't be parsed.
326 */
327 public static Tree<? extends Op<Double>, ?>
328 parseTree(final String expression) {
329 return MathExprParser.parse(expression);
330 }
331
332 /**
333 * Evaluates the given {@code expression} with the given arguments.
334 *
335 * <pre>{@code
336 * final double result = MathExpr.eval("2*z + 3*x - y", 3, 2, 1);
337 * assert result == 9.0;
338 * }</pre>
339 *
340 * @see #apply(Double[])
341 * @see #eval(double...)
342 *
343 * @param expression the expression to evaluate
344 * @param args the expression arguments, in alphabetical order
345 * @return the evaluation result
346 * @throws NullPointerException if the given {@code program} is {@code null}
347 * @throws IllegalArgumentException if the given operation tree is invalid,
348 * which means there is at least one node where the operation arity
349 * and the node child count differ.
350 */
351 public static double eval(final String expression, final double... args) {
352 return parse(expression).eval(args);
353 }
354
355 /**
356 * Tries to simplify the given math tree.
357 *
358 * <pre>{@code
359 * final Tree<? extends Op<Double>, ?> tree =
360 * MathExpr.parseTree("4.0 + 4.0 + x*(5.0 + 13.0)");
361 * final Tree<? extends Op<Double>, ?> simplified = MathExpr.simplify(tree)
362 * System.out.println(simplified);
363 * }</pre>
364 * The simplified tree will be look like this:
365 * <pre> {@code
366 * add
367 * ├── 8.0
368 * └── mul
369 * ├── x
370 * └── 18.0
371 * }</pre>
372 *
373 * @see #prune(TreeNode)
374 * @see #simplify()
375 *
376 * @param tree the math tree to simplify
377 * @return the new simplified tree
378 * @throws NullPointerException if the given {@code tree} is {@code null}
379 */
380 public static Tree<? extends Op<Double>, ?>
381 simplify(final Tree<? extends Op<Double>, ?> tree) {
382 return MathExprSimplifier.prune(TreeNode.ofTree(tree));
383 }
384
385 /**
386 * Tries to simplify the given math tree in place.
387 *
388 * @see #simplify(Tree)
389 * @see #simplify()
390 *
391 * @param tree the math tree to simplify
392 * @throws NullPointerException if the given {@code tree} is {@code null}
393 */
394 public static void prune(final TreeNode<Op<Double>> tree) {
395 MathExprSimplifier.prune(tree);
396 }
397
398 }
|