出于一些奇怪的原因,尝试了一把MATLAB混合编程。
Java等语言更适合搭建Web Server,处理文件,而MATLAB有许多优秀的内置函数,用起来十分方便。
两者结合,就能写出提供一些计算函数的后端服务,配合上前端,就能在网页上用MATLAB了。
简介
使用其他编程语言调用MATLAB,有两种办法,一种是使用MATLAB ENGINE的API,另一种是MATLAB COMPILER将MATLAB函数打包成其他语言的形式。
我主要使用第二种,原因是在部署机上没有MATLAB的LICENSE,而且也不想在每个Server上都运行一个完整的MATLAB。第二种方式,最终执行函数的机器,
只需要安装了MCR(MATLAB COMPILER RUNTIME)就可以了。
MATLAB COMPILER RUNTIME
在官网上下载安装包,按照步骤安装即可,但要注意版本保证和MATLAB的版本一致。
MATLAB COMPILER
安装MATLAB的时候勾选这类产品即可。
使用时,在命令行输入deploytools
即可调出界面,在里面配置好编译的选项。
对于JAVA Library,需要勾选输入的.m文件,设定Java类名,检查方法名。
JAVA_HOME一定要设置对,COMPILER会使用到javac进行编译。
最后打包出来就是一个jar包,在项目中导入使用即可。
Java应用还需要导入javabuiler.jar
,这个包在MATLAB或者MCR的安装目录下都会有。
以Linux上的v95版本MCR为例(对应MATLAB 2018b),路径为
/usr/local/MATLAB/MATLAB_Runtime/v95/toolbox/javabuilder/jar/javabuiler.jar
Call MATLAB Function From JAVA
1 2 3 4 5
| function [outputArg1,outputArg2, outputArg3] = usersolve(arg) options = optimoptions('fsolve','Display','off'); [outputArg1, outputArg2, outputArg3] = fsolve(@InputSolve, arg, options); end
|
打包成usersolve.jar
,放到java项目的lib目录下,添加maven引用。
1 2 3 4 5 6 7
| <dependency> <groupId>algo</groupId> <artifactId>usersolve</artifactId> <version>1.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/usersolve.jar</systemPath> </dependency>
|
Controller代码。这里我犯了一个错误,我以为MCR会如同MATLAB一样,从Documents/MATLAB目录下加载.m文件。但是其实,MCR只能执行编译过的文件,而且会为这些具体函数建立一个cache,目录为$USER_HOME/.mcrCache9.5/userso2/
。
这个目录里可以看到,所有的函数都是二进制格式。
所以我虽然接收并存储了用户的函数代码,但实际上执行的还是之前打包进来的InputSolve函数,并不能动态加载函数,这是个失败例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @RestController public class UserSolve { @Autowired private UserSolveService userSolveServicej;
@PostMapping("/usersolve") synchronized public UserSolveResponseBody userSolve(@RequestBody UserSolveBody userSolveBody) throws IOException { UserSolveResponseBody userSolveResponseBody = new UserSolveResponseBody(); String funcFile = System.getProperty("juser.home") + "/Documents/MATLAB/InputSolve.m"; OutputStream f = new FileOutputStream(funcFile); f.write(userSolveBody.getFunction().getBytes()); try { Object[] res = userSolveService.solve(userSolveBody.getSolution()); userSolveResponseBody.setFlag(((MWNumericArray)res[2]).getInt());
List<List<Double>> x = new ArrayList<>(); Object[] xObj = ((MWNumericArray) res[0]).toArray(); for (int i = 0; i < xObj.length; i++) { x.add(DoubleStream.of((double[]) xObj[i]).boxed().collect(Collectors.toCollection(ArrayList::new))); } userSolveResponseBody.setX(x); List<List<Double>> fval = new ArrayList<>(); Object[] fvalObj = ((MWNumericArray) res[1]).toArray(); for (int i = 0; i < fvalObj.length; i++) { fval.add(DoubleStream.of((double[]) fvalObj[i]).boxed().collect(Collectors.toCollection(ArrayList::new))); } userSolveResponseBody.setFval(fval); } catch (MWException e) { e.printStackTrace(); userSolveResponseBody.setErrorInfo(e.getMessage()); } return userSolveResponseBody; } }
|
Service代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Service public class UserSolveService { private UserSolve userSolve;
public UserSolveService() throws MWException { this.userSolve = new UserSolve(); }
public Object[] solve(List<List<Double>> solution) throws MWException { Object[] objects = new Object[solution.size()];
for (int i = 0; i < solution.size(); i++) { objects[i] = solution.get(i).toArray(); } Object[] result = {null, null, null}; Object[] input = {objects}; userSolve.usersolve(result, input); return result; } }
|
有意思的事情是,给MATLAB函数传参数,统统都是用Object[]。
并且对象数组可以嵌套,而可用的基本类型有double、int、long。
matlab里的矩阵就可以用double[][]
表示,虽然先声明Object[]
,
在逐层向里面加入数据更为灵活。
MATLAB返回的参数,要么会用NumericArray包装,要么就是裸的数据类型。
只是,不同的内置函数的返回类型究竟对应什么,我也没有弄很清楚。
函数画图可以采用webfigure,java这里就对应jsp。
springboot里要启用jsp,添加如下参数。
1 2 3 4
|
spring.mvc.view.prefix=/WEB-INF/jsp/ spring.mvc.view.suffix=.jsp
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Controller @SessionAttributes(value = {"MyFigure"}) public class Weibull { @Autowired private WeibullService weibullService;
@PostMapping("/weibull") public String weibull(@RequestBody WeibullInputBody weibullInputBody, Model model) { try { MWNumericArray numericArray = (MWNumericArray) weibullService.evaluate(weibullInputBody.getData()); model.addAttribute("a1", numericArray.getDouble(1)); model.addAttribute("a2", numericArray.getDouble(2)); model.addAttribute("MyFigure", weibullService.plot(numericArray)); } catch (MWException e) { e.printStackTrace(); } return "weibull"; } }
|
jsp
1 2
| <%@ taglib prefix="wf" uri="http://www.mathworks.com/builderja/webfigures.tld" %> <wf:web-figure name="MyFigure" scope="session"/>
|
但是,这个jsp里的web-figure会被替换成一段iframe,然后指向了地址$HOST/WebFigures/$name
。也就是说,还会有二次请求,那么显然这个地址会404,因为我们没有配置关于这个地址的处理函数。
实际上它的处理函数应该对应MATLAB提供的一个Servlet:
com.mathworks.toolbox.javabuilder.webfigures.WebFiguresServlet
。
那么springboot里怎么简单地加入一个别人写的servlet呢,
因为我只找到了一个WebServlet
注解,然后它只能加在某个类上,
又因为这个类不是我写的,我只能使了个小技巧,继承了这个类。
或者退回到使用mvc的方式,在web.xml里写servlet标签。
1 2 3
| @WebServlet(name = "MyServlet",urlPatterns = "/WebFigures/*") public class Webfigure extends WebFiguresServlet { }
|
App处加上扫描Servlet的注解。
1 2 3 4 5 6 7
| @SpringBootApplication @ServletComponentScan public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
|
MATLAB 函数
1 2 3 4 5
| function df = getWebFigure() f=figure('Visible', 'off'); df = webfigure(f) end
|
JAVA接收webfigure。
1 2 3
| ((MWJavaObjectRef)result[0]).get()
|