Wiki

Clone wiki

mikulas / mexpr / index

Mexpr

Mexpr is a library supporting writing mathematical expressions and their evaluation.

Scientific applications sometimes need to enter some formulas and evaluate them or show them in a graph. The formula is not a part of the application but user usually wishes to enter own formula and evaluate it.

ExpressionParser

Simplest expression

Main class to use is ExpressionParser Example of use:

ExpressionParser expressionParser = new ExpressionParser("100 * 10 + 25");
double result = expressionParser.evaluateDouble();

Variables

More complex use may contain variables (which can be considered to be some constants):

Easy Variables

Passing a value into variable, like 'x':

ExpressionParser expressionParser = new ExpressionParser("100 * x + 25", Map.of("x", 20));
double result = expressionParser.evaluateDouble();
Note: the number 20 in this case can't be changed so it's in fact a constant unless using settable instance like DoubleHolder

Settable Variables

Variables can be set without need to recompile expression which would be slow for scientific calculations, like using in equations or drawing a graph.

DoubleHolder xHolder = new DoubleHolder(); // DoubleHolder is a Supplier<Double> with possibility to dynamically change the value
ExpressionParser expressionParser = new ExpressionParser("x + 1", Map.of("x", xHolder));
xHolder.accept(10);
expressionParser.evaluateDouble(); // 11
xHolder.accept(20);
expressionParser.evaluateDouble(); // 21

Typical Use Case

An exception should be expected for the parsing. Depending on exception the input should be changed:

ExpressionParser expressionParser = null;
try {
    expressionParser = new ExpressionParser("100 * x + 25");
} catch (ParseExpressionException e) {
    if (e instanceof UndefinedVariableException) {
        expressionParser = new ExpressionParser("100 * x + 25", Map.of(e.getVariableName(), 10)); // setting value for missing x variable
    }
}
...
double result = expressionParser.evaluateDouble();

Constants

Also constants like pi are supported:

ExpressionParser expressionParser = new ExpressionParser("pi * r ^ 2", Map.of("r", 20));
double result = expressionParser.evaluateDouble();

Functions

Expressed Functions

They can be made easily directly by user using a declaring and defining string present in the map.

new ExpressionParser("sin(0.5) + quadr(11)", Map.of("quadr(x)", "11 * x ^ 2 + 21 * x + 31"));

Invalid example:

new ExpressionParser("f(11)", Map.of("f(x)", "x + a"));
Variable from outside of function is valid:
new ExpressionParser("f(11)", Map.of("f(x)", "x + a", "a", 20));
This is valid but argument variable wins:
new ExpressionParser("f(10, 20)", Map.of("f(x, a)", "x + a",
                                         "a", 40)); // 30

Java Functions

Functions can be made by a java class annotated with @FunctionalSupplier. All such classes with such annotation are ready to use in expression. There are some prepared basic math functions, like sin, log, ... Prepared functions are all functions found on classpath and their package starts with cz.jmare.mexpr.func but another custom functions with own package may be also added. Prepared functions can be disabled by new Config().withPredefinedClasses(false);, config is passed as an argument int ExpressionParser constructor.

List of available java functions:

SupplierInfos supplierInfos = SuppliersClassesProvider.getSupplierInfosEmbedded(true);
System.out.println(supplierInfos.funcInfos);

Example of use:

ExpressionParser expressionParser = new ExpressionParser("sin(2 *  pi)");
double result = expressionParser.evaluateDouble();

They can be used, for example, in graphs.

DoubleHolder xHolder = new DoubleHolder();
ExpressionParser expressionParser = new ExpressionParser("sin(x * 2 * pi)", Map.of("x", xHolder));
for (double x = 0; x < 10; x += 0.01) {
    xHolder.accept(x);
    double y = expressionParser.evaluateDouble();
    plot(x, y);
}

Note: We are creating a DoubleHolder instance which is a Supplier providing a value and it's also a Consumer to set the value. The expression needn't be compiled for each evaluation but rather we only set the value and get a new result.

Useful functions

I often use functions, like sin, rand. For example to generate a sine wave with standard deviation of 0.05 such formula may be used:

sin(x * 2 * pi) + rand(-0.1, 0.1)

Or to generate one curve with two frequencies where x = 1 is a border:

sin(x * 2 * pi) * less(x, 1) + sin(x * 2 * pi * 2) * greater(x, 1)
This was an example of use discrete functions like less or greater. Inclusion (less or equals, greater or equals) of the second parameter can be enabled by third parameter with value 1, otherwise the second parameter means exclusion (less or greater). There is also the between function to define a range.

Also functions like log are often used. Note they can contain various number of parameters and some functions like log may contain one or two parameters. The second parameter is the base. For base=10 the log10 function can be also used as shortcut.

Application configuration

Use following Maven configuration before start:

<dependency>
    <groupId>io.bitbucket.janmaren</groupId>
    <artifactId>mexpr</artifactId>
    <version>1.1</version>
</dependency>

See also expression syntax

Updated