Java调用Matlab中自定义函数

引言

近期一个工作中使用Java进行编程,涉及到一个数学规划/优化问题的求解,本想着通过Java调用Gurobi来求解,但约束中含有指数函数,在尝试后发现Gurobi求解速度较慢,并且在问题规模增大后求解过程没有终止的迹象。

后来发现Matlab中的CVX适合求解这样的问题,最终决定通过Java调用Matlab来进行求解。主要的方案有两种:一是将Matlab中的.m文件打包成JAR包,再导入到Java工程中调用;二是在Java中创建MatlabEngine对象来执行Matlab程序。这两种方案我都进行了尝试,第一种方案没有走通,第二种方案走通了,在此记录一下。

方案一

首先我尝试了方案一,打包和导入过程网上都有较多案例,我也顺利地完成了。之后耗费时间较长的一个问题是函数的调用。后来发现这种打包会生成详细的说明文档(在打包生成的doc文件夹中可以找到),如下图所示。
JAR包文档说明以RS_KL_max为例,RS_KL_max是我在Matlab中写的一个函数,JAR包里针对这一个函数生成了三个方法,目前我还不太明白后两个方法的作用,根据说明第一个方法是“标准接口”,大多数情况下应该是使用这一方法,点击方法名可以跳转到详细说明,如下(图中的乱码在Matlab中是中文,应该是打包过程存在编码问题,可忽略)。
示例方法标准接口说明这一说明非常详细(写这篇博文的时候才注意到有这个文档,emmm,提示有时先不要急着在网上找解答,尽量看看有没有官方的说明),有下面几个关键点。一是第一个参数是返回参数的个数,这个等于是额外出现的一个参数(我在Matlab中写的RS_KL_max函数是三个输入参数,一个输出参数),需要根据实际情况设置;二是提到输入参数应该由3个逗号分隔(3 comma-seperated),3个逗号正好是4个参数,等于是原本的3个加上表示返回参数个数的那一个;三是提到了输入参数的类型;四是提到了返回值的类型。

   public static void Java_cvx_1(ArrayList<Double> H_list,double eta) throws MWException{
       // 有可能cvx的代码并未编译
       int n = H_list.size();
       int[] dim = {1,n};
       double[] h = new double[n];
       for (int i = 0; i < n; i++) {
           h[i] = H_list.get(i);
       }
       Object[] x = {h};
       Object[] outputs = null;
       RS_KL_cvx RS = null;
       MWNumericArray y = new MWNumericArray(x,MWClassID.DOUBLE);
       MWNumericArray mwn = new MWNumericArray(n,MWClassID.INT32);
       MWNumericArray mweta = new MWNumericArray(eta,MWClassID.DOUBLE);
//        for (int i = 0; i < n; i++){
//            x[i] = new MWNumericArray(Double.valueOf(H_list.get(i)), MWClassID.DOUBLE);
//        }
       //MWNumericArray H = MWNumericArray.newInstance(dim,h,MWClassID.DOUBLE);
       RS = new RS_KL_cvx();
       outputs = RS.RS_KL_max(1,mwn,y,mweta);
       System.out.println(outputs[0].toString());
   }

我的代码如上所示,运行后会出现下面的错误。由于报错中出现了cvx,我推测方法是正常调用了,参数传递正常,但在方法内调用cvx时出现了问题。在网上搜索“PostVMInit failed to initialize com.mathworks.mwswing.MJStartup”和“此类型的变量不支持使用点进行索引”,没太看到非常相关的内容。发现论坛上一个提问是说想把simulink的某部分打包成JAR报这个错误,下面有员工回答simulink不支持这样打包,并表示这个报错提示得不明确。想一想也有道理,如果Matlab所有部分都能这样打包,那它作为付费软件不就没有意义了……又注意到之前看到的成功的例子都没有涉及调用其他toolbox,综合下来推测可能是cvx不支持这样的打包。

PostVMInit failed to initialize com.mathworks.mwswing.MJStartup
此类型的变量不支持使用点进行索引。

出错 cvx_global (第 76 行)

出错 cvxprob (第 4 行)

出错 cvx_begin (第 41 行)

出错 RS_KL_max (第 4 行)

Exception in thread "main" ... Matlab M-code Stack Trace ...
file C:\...\RS_KL_0\Software\cvx\cvx-w64\cvx\lib\cvx_global.m, name cvx_global, line 76.
file C:\...\RS_KL_0\Software\cvx\cvx-w64\cvx\lib\@cvxprob\cvxprob.m, name cvxprob, line 4.
file C:\...\RS_KL_0\Software\cvx\cvx-w64\cvx\commands\cvx_begin.m, name cvx_begin, line 41.
file C:\...\RS_KL_0\RS_KL_cvx\RS_KL_max.m, name RS_KL_max, line 4.

方案二

方案二是在Java中启动一个Matlab engine,之后就可以和Matlab进行交互,现在来看,这个engine应该是一个运行Matlab终端的子进程(这一思路和Java与GAMS交互非常类似,参考GAMS相关的博文GAMS使用小记(二))。网上相关的教程也比较多,但最后一步还是自己摸索出一个有些生硬但有效的方法解决了问题。相关代码如下。

public static void Java_cvx_2(ArrayList<Double> H_list,double eta) throws MWException{
       int n = H_list.size();
       int[] dim = {1,n};
       double[] h = new double[n];
       String h_s = new String("[");
       for (int i = 0; i < n; i++) {
           h_s = h_s + H_list.get(i) + ",";
       }
       h_s = h_s + "]";
       try {
           // 启动Matlab引擎
           MatlabEngine eng = MatlabEngine.startMatlab();

           eng.eval("cd D:\\Programming\\Matlab\\");
           //double upper= eng.feval("add",n*1.0,eta); 可以调用
           //eng.eval("cvx_setup");

           eng.eval("RS_KL_max("+n+","+eta+","+h_s+")");
           //eng.feval("yourFunction", param1, param2, ...);
           // 添加Matlab函数所在的路径
           //eng.feval("D:\\Programming\\Matlab");
           // 调用Matlab函数
           //double upper= eng.feval("RS_KL_max",n,eta,h);//需指定返回值数量,默认为1
           //System.out.println(upper);
           //Object[] Z = eng.getVariable("Z");//获得输出值
           // 关闭Matlab引擎
           eng.close();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

首先碰到的一个问题是出现“MatlabConversionError”这一错误,并且提示出错代码是“MatlabEngine eng = MatlabEngine.startMatlab();”这一句,等于是Matlab无法正常启动。在网上搜索会发现少有匹配的内容,说明这一错误不太常见(可能每个人遇到的错误点都不太一样……),最常提到的是要按照官网的步骤添加环境变量,我也添加了,有些奇怪的时候看到一个回答说“重启让环境变量生效”,突然意识到就像在terminal里写命令一样,更新环境变量后需要重新开一个terminal来验证。于是重启了IDEA,果然MatlabEngine可以正常启动了。

随后遇到了新的问题,我在网上以及官方的文档中都看到用feval来执行有返回值的命令(eval执行没有返回值的命令),于是我使用了上面注释掉的“double upper= eng.feval(“RS_KL_max”,n,eta,h);”想执行这一函数,会出现下面的报错,非常奇怪的是有很多小字,仔细看是很多“SUB”,不知道是什么情况。
SUB报错
正当有些沮丧时,我意识到eval这个函数其实就相当于在Matlab终端输入命令,那只要是Matlab终端能执行的,在Java中一样可以把这条命令拼接出来,于是根据Matlab中的调用方式(如:RS_KL_max(6,1.5,[2.6,2.7,2.8,2.9,3.0])),有了下面的代码。

String h_s = new String("[");
        for (int i = 0; i < n; i++) {
            h_s = h_s + H_list.get(i) + ",";
        }
        h_s = h_s + "]";
eng.eval("RS_KL_max("+n+","+eta+","+h_s+")");

成功了!以往总觉得这样拼接命令有些生硬,现在看来这是一种很直接有效的方法。进一步如果想获得返回值怎么做?其实关键点在于完全当成在终端执行命令,稍微修改上面的代码即可在Java中获得返回值。

String h_s = new String("[");
        for (int i = 0; i < n; i++) {
            h_s = h_s + H_list.get(i) + ",";
        }
        h_s = h_s + "]";
eng.eval("Z=RS_KL_max("+n+","+eta+","+h_s+")");//将返回值赋给变量Z
double Z = eng.getVariable("Z");//获得返回值
System.out.println(Z);

感想

  • 混合编程一般是不得已的选择,但有时也能拓宽思路,充分利用不同语言/软件的优点。
  • 官网的文档和论坛是质量很高的。
  • 类似问题的解决方案往往是有些类似的,争取触类旁通。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值