65.9K
CodeProject 正在变化。 阅读更多。
Home

Java 函数编译器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2014 年 3 月 5 日

CPOL

7分钟阅读

viewsIcon

27267

downloadIcon

1310

JEL 表达式库的快速演示。


引言

这是一个演示,展示了 GNU Java 表达式库 (JEL) 如何在您的项目中使用,通过编译 (而不是解释) 代数表达式的字符串表示。使用 JEL 在运行时编译表达式比解析/解释方法效率极高,并且其应用远远超出了此处演示的代数求值任务。

假设您希望用户能够以字符串形式输入一个代数函数,然后在运行时由您的应用程序求值。一种常见的方法是解析表达式并解释它。另一种避免解释器性能损失的方法是表达式编译成 Java 字节码。这时就轮到 GNU 项目提供的 Java 表达式库 (JEL) 了。

演示应用程序提供了一个带有文本字段的窗口,用户可以在其中以字符串形式输入函数。它还有用于输入要计算的域值最小值和最大值的框,以及用于输入合成域值集合的增量的框。只需点击绘图按钮即可显示图形。菜单中提供了五十五个示例函数。

该示例故意很简单。为了给编码人员提供一个不分散注意力的示例,我避免了用辅助功能和审慎的错误处理来使代码复杂化(它不会优雅地处理拼写错误)。对于无需额外功能的绘图,我使用了一个简单的绘图类 (Plot.java),该类由 Weber State University 的 Daniel Schroeder 博士提供。如果您在项目中使用此绘图类,它有版权,带有非商业用途限制,并要求正确引用。其他三个类和 JEL 库是免费、开源、GNU 许可的软件。整个应用程序继承了本网站的 CPOL 许可证。

背景

GNU Java 表达式库是 Java 程序员的一项重要资源。它最后一次更新是在 2007 年,但仍然是一个非常有用的解决方案,其功能远远超出了我示例的范围。它由 Konstantin Metlov 博士开发,他目前是顿涅茨克物理技术研究所的高级科学家。在浏览了源代码并使用了该库之后,我确信 JEL 比我可能费力写出的任何代码都更高效,并且拥有它在我的工具箱中,让我能够专注于我的主要兴趣。它得到了很好的文档记录,既包括 Java 类本身,也包括项目上的 手册API 页面。

尝试演示

您需要更新的 Java 运行时环境才能运行该应用程序。如果您还没有将 Java 更新到 1.7,请进行更新。下载演示并解压缩。这将在您的下载目录中创建一个名为runnableDemo的文件夹,其中包含 Java 可执行文件jelDemo.jar以及必要的库。从命令行,切换到包含jelDemo.jar的文件夹(runnableDemo 文件夹)并运行

java -jar jelDemo.jar 

应用程序启动时,参数框中已填充了一个示例。只需点击绘图即可显示图形。

The main application window

在尝试您自己的表达式之前,请尝试菜单中的一些示例,以了解表达式的语法。

默认函数是Morlet 小波的实部。



为了一个更简单的例子,选择菜单 -> 笛卡尔 -> 直线。这会自动在框中加载相应的示例参数。

我最喜欢的是星形线 (1) 示例。

Hypocycloid

代码

解压缩源代码下载文件将在您的下载目录中创建一个CompilePlotSrc文件夹。它包含一个LicenseInfo.txt文件,一个EclipseHowTo.txt文件,源代码文件夹 (src),以及一个方便的 jel-2.0.1 库副本,位于一个子文件夹中。

在编译源代码之前,您需要链接到 jel-2.0.11 库。EclipseHowTo.txt文件简要说明了如何使用 Eclipse IDE 执行此操作。

MainForm.java 类

应用程序的入口点是 MainForm.java 类中的 main() 方法。启动时,MainForm 类会显示一个 jFrame,其中包含文本字段、复选框、菜单和绘图按钮。该类有一些全局作用域的属性。

public String function = ""; // Non-parametric expression for f(x), y = f(x)
public String functionTx = ""; // Parametric expression for f(t), x = f(t)
public String functionTy = ""; // Parametric expression for f'(t), y = f'(t)
public String label = "";
public double xMin; // lower domain
public double xMax; // upper domain
public double dx; // domain increment
public boolean isPolar = false; // True when we wish to plot a function using polar coordinates. 
public boolean isParametric = false; // True when we are plotting a parametrically defined function.

例如,假设我们有一个函数 y = sin(x),我们想从 x = -10 到 x = 10 求值,步长为 0.01。在这种情况下,

  • 函数 = sin(x)
  • xMin = -10
  • xMax = 10
  • dx = 0.01
  • isPolar = false (尽管我们可以将函数理解为极坐标,并且图形将是一个完美的圆。)
  • y = sin(x) 不是参数方程,因此isParametric = false。因此,functionTxfunctionTy 未被使用。
  • label 可以是您选择的任何字符串。
MainForm 的主要 jFrame 是frmExpressionCompilerDemo,它包含控件 (jTextFields 和 jCheckBoxes),用于输入上述值。当点击绘图按钮时,getText() 方法从控件中收集值,并将它们作为参数调用下一个类(我们将讨论)的相应方法:Helper.java 类。
Helper.java 类

Helper 类只暴露两个公共方法:Plot() 和 PlotParametric()。在本文的其余部分,我将重点关注非参数情况。参数情况遵循类似的逻辑。Plot() 接受以下参数:函数的字符串表示、域的最小值、最大值和增量值、标签以及 isPolar 布尔值。

这是 Helper 类中的 Plot() 方法。

/**        
 *         
 * @param function        
 *            : an expression, e.g. sin(2*pi*16*x)        
 * @param xMin        
 *            Lower domain value        
 * @param xMax        
 *            Upper domain value        
 * @param dx        
 *            Delta x (sampling increment)        
 * @param isPolar        
 *            True when the function represents a polar equation, else        
 *            false.        
 * @param label Any label you choose including "         
 */        
public static void Plot(String function, double xMin, double xMax,        
        double dx, boolean isPolar, String label) {
        
    double[] x = SamplingFn(xMin, xMax, dx);    
    double[][] xy = EvaluateFunction(function, x, isPolar);    
    Plot(xy[0], xy[1], label);    
        
} 

方法中有三件事发生。

  1. MainForm 从用户那里收集 x 值的规范,该规范仅包括 x 的最小值、x 的最大值以及各个值之间的增量差。拥有一个包含所有 x 值集合的数组对于处理更方便。通过调用 SamplingFn() 方法,可以获得一个包含指定 x 值的 double[]。
  2. 实际的表达式求值工作由 EvaluateFn() 方法执行,该方法返回一个二维数组:double[][] xy,其中xy[0]是 x 值,xy[1]是 y 值。
  3. 最后,将 xy 数组提交给 Plot() 方法,该方法创建一个 PlotForm 类的实例来呈现函数的图形。

重要的一步是第 2 步,我们在 x 值数组中包含的域点上评估表达式。EvaluateFn() 调用 Compile() 方法来获取 y = f(x) 值的数组,然后将结果与原始 x 值打包在一个 double[][] 中。Compile() 方法实现了与 JEL 的接口。以下是 Compile() 方法的代码,这是对 JEL 文档中的示例代码的一个小改动。

// "x" is used as the variable here but it may refer to "t" from the calling
// function (Or one that you add to your VariableProvider method)
/**
 * @see "JEL Documentation at GNU Project"
 * @param x
 *            vector containing x values
 * @param expr
 *            Sting representation of function e.g. sin(2*pi*16*x)
 * @return a vector containing the f(x) values
 * @throws CompilationException
 */
public static double[] Compile(double[] x, String expr)
        throws CompilationException {
    int n = x.length;
    double[] fX = new double[n];
 
    // Set up class for Java Math namespace        
    Class[] staticLib = new Class[1];
    try {
        staticLib[0] = Class.forName("java.lang.Math");
    } catch (ClassNotFoundException e) {
        System.out.print("1");
    }
 
    // Setup class for variables
    Class[] dynamicLib = new Class[1];
    VariableProvider variables = new VariableProvider();
    Object[] context = new Object[1];
    context[0] = variables;
    dynamicLib[0] = variables.getClass();
 
    Library lib = new Library(staticLib, dynamicLib, null, null, null);
 
    CompiledExpression expr_c = null;
 
    try {
        expr_c = Evaluator.compile(expr, lib);
    } catch (CompilationException ce) {
        System.err.print("--- COMPILATION ERROR :");
        System.err.println(ce.getMessage());
        System.err.print("                       ");
        System.err.println(expr);
        int column = ce.getColumn(); // Column, where error was found
        for (int i = 0; i < column + 23 - 1; i++)
            System.err.print(' ');
            System.err.println('^');
    }
 
    if (expr != null) {
 
        try {
            for (int i = 0; i < n; i++) {
                variables.xvar = x[i]; // <- Value of the variable
                fX[i] = (double) expr_c.evaluate(context);
            }
        } catch (Throwable e) {
                System.err.println("Exception emerged from JEL compiled"
                    + " code (IT'S OK) :"); //Konstantin's message
                System.err.print(e);
        }
    }
        return fX;
}



第四个类 VariableProvider.java 是什么?它是使我们能够将数组值 x[i] 传递给编译器,以便它们可以替代表达式字符串中“x”的出现。这是 VariableProvider 的相关部分。

// Add variables as needed. In the demo I only use x or t for my independent
// variable.
public class VariableProvider {
    public double xvar;
    public double tvar;
    public double picon = Math.PI;
    
    //Let the user pass either t or x as the independent variable.
    public double x() {
        return xvar;
    };
    
    public double t() {
        return xvar;
    };
    
    // Enables a lower case pi    
    public double pi() {
        return picon;
    }; 
} 

关注点

Java 表达式库具有许多超出本文范围的强大功能。它可以用于各种用例,基本上是当您希望程序使用对象、类型、运算符等字符串表示形式在运行时执行编译后的指令时。(参见其他功能)。选择 JEL 作为代数表达式字符串表示的编程访问的主要好处是,JEL 的功能不仅限于该任务本身。因为它是一个通用的表达式求值器,它不仅提供了一个工具,而是一整套工具。

图形能够以非凡的清晰度呈现函数,常常能提供仅通过查看其文本表示无法获得的洞察。通常,当函数以极坐标和/或参数形式表示时,可以更好地理解(也更容易表达)它们。Helper.java 类中的 PlotParametric() 和 PolarToCart() 方法负责处理从极坐标和参数解集创建(基于笛卡尔坐标)数据数组的计算。虽然我们不必局限于笛卡尔坐标的思维模式,但将所有函数简化为笛卡尔坐标的等效形式对于创建可视化非常方便。

我年轻时记得在现在已成为收藏品的《CRC 化学与物理手册》中看到各种函数(笛卡尔、极坐标和参数)的图形。如今,有许多在线函数绘图集,常常会附带针对各种商业数学软件的特定语法编程示例(示例)。可以肯定的是,其中一些拥有复杂的、闭源的表达式编译器。

历史








在此处保持您所做的任何更改或改进的实时更新。
© . All rights reserved.