一、引言
M兼容工具:兼蓄经典,实现模型代码资产高效复用(上篇)科学计算与系统建模仿真是装备数字化的重要落地支撑,是避免受制于人的关键核心技术。近年来,同元软控围绕行业应用求和装备数字化需求,持续迭代完善MWORKS,今年发布的MWORKS 2024是全球第四个完整的科学计算与系统建模仿真一体化平台,为世界提供科学计算与系统建模仿真平台的中国选项。
依托多年的编译技术沉淀、复杂装备系统的软件研发与工程实践经验、自主开发的高质量系列模型库及函数库,MWORKS 2024全新推出M语言兼容工具和Simulink模型导入工具,无需安装MATLAB,即可实现M语言脚本的直接运行和Simulink模型的导入与转换,实现对用户MATLAB模型代码资产的高效复用。兼包并蓄,不止于替代。
M兼容工具分为上下两篇,上篇主要介绍M语言兼容工具,下篇主要介绍Simulink模型导入工具。
二、M语言兼容工具
2.1 概述
M语言兼容工具(即TyMLang),是同元软控新推出的一款实用工具,无需安装 MATLAB,原生支持MATLAB代码文件的解释与运行,并内置提供1260+ 常用M函数,覆盖基础、数学、图形、控制、信号等领域,帮您实现历史代码资产的高效复用。它运行在MWORKS.Syslab 上,并支持M语言与Julia语言的相互调用,无缝衔接MATLAB生态与Julia科学计算生态。
M语言兼容工具主要由三部分组成:一是IDE,支持M语言的编辑、运行与调试;二是M语言执行环境,系统性的原生支持MATLAB/M语言的核心语法、程序行为和特殊机制;三是M函数库,包括内置常用函数、外部语言函数(MEX机制)、以及MATLAB专业函数。
其中,M语言执行环境是整个M兼容工具的核心,即使是其中较为简单的解析器,其难度、工作量也不容小觑。MATLAB语法没有公开规范,并在40年的发展中积累大量边缘语法、上下文相关语法。因此, M语言解析器开发与一般编译解析的小步快速迭代过程不同,M语言解析器不仅要快速迭代,还需要随时推翻之前总结的规范重新来过。
在这种风险极大、重构极频繁的解析器工作中,同元软控充分研究了前沿的解析器维护技术,综合了解析器生成器 + 语法制导 + BNF静态类型检查 + 语法树领域建模,在底层保证每次推翻规范、重构代码后,可通过生成器快速生成成品,可通过静态检查和领域建模以确定性手段排查旧代码中的错误规范/假设,从而确保M语言解析器的高可用。实现M语言执行环境所面临的困难与挑战,从其冰山一角的M语言解析器上可见一斑。
2.2 亮点功能
1)无需安装MATLAB,直接在Syslab上运行
MWORKS.Syslab是多语言科学计算环境,以Julia为主语言,同时支持Python、M等编程语言。Syslab内置提供了M扩展插件(TyMLangIDE)、M编译器(mc.exe)和M函数库,因此无需安装MATLAB,M语言脚本即可直接在Syslab上打开、编辑和运行。
2)M语言及程序行为兼容度超过90%
MATLAB语法没有公开规范,多年来积累形成了大量边缘语法、上下文相关语法。通过大量资料的查阅与推理,以及在工程实践中反复锤炼,MWORKS.Syslab对M语言及程序行为兼容度超过90%。
3)支持代码调试
M语言兼容工具,支持M语言脚本的代码调试,包括设置断点、启动调试、单步调试、断点调试、查看调试变量等。
4)支持MEX机制
MEX是MATLAB内的一种扩展,用于在M语言中调用C/C++编写的函数。MEX 文件的源代码用C编写,并通过一组MEX API,提供了C与M语言的数组之间交互的功能。遵循以下的入口函数规范,将对应C文件编译成动态库后可直接由M语言调用。
#include "mex.h"
void mexFunction ( int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[] )
{
// ...
}
其中nlhs表示M语言在调用该函数时的实际参数个数,plhs表示指向参数列表的指针,nrhs 表示调用该函数时的返回值个数,prhs 表示指向返回值列表的指针。
M语言兼容工具支持将符合MEX接口规范的C文件编译成动态库,并能加载和调用该动态库。例如,创建一个c_cumsum.c文件,完整代码如下:
#include "mex.h"
#include "assert.h"
#include "stdio.h"
#include "math.h"
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[]) {
// 检查输入
if (nlhs == 0) {
return;
}
if (nlhs > 1) {
mexErrMsgTxt("Too many output arguments.");
}
if (nrhs != 1) {
mexErrMsgTxt("one input required.");
}
if (!mxIsDouble(prhs[0])) {
mexErrMsgTxt("Input must be of type double.");
}
// 提取指向输入数组的数据的指针
double *input1 = mxGetPr(prhs[0]);
int N = mxGetNumberOfElements(prhs[0]);
// 创建和输入相同大小的矩阵作为输出
int s1 = mxGetM(prhs[0]);
int s2 = mxGetN(prhs[0]);
mxArray *B = mxCreateDoubleMatrix(s1, s2, mxREAL);
double *out = mxGetPr(B);
double tmp = 0;
// cumsum,求累加和
for (int i = 0; i < N; ++i) {
tmp += input1[i];
out[i] = tmp;
}
// 将结果赋给 lhs
plhs[0] = B;
}
启动M语言兼容工具,输入以下命令,即可将c_cumsum.c文件编译成动态链接库c_cumsum.mexdll,然后加载动态库并调用其算法函数。
5)支持搜索路径
搜索路径是文件系统中所有文件夹的子集,使用搜索路径来高效地定位用于产品的文件。搜索路径上的文件夹顺序十分重要。当在搜索路径上的多个文件夹中出现同名文件时,将使用搜索路径中最靠前的文件夹中的文件。
M语言兼容工具支持查看或更改搜索路径,并提供设置路径对话框,方便用户使用。
6)支持Julia与M语言的互调用
M语言兼容工具,支持Julia与M的语言互调用,能够调用同元软控Julia高性能科学计算函数库,无缝衔接MATLAB生态与Julia生态。
①M语言调用Julia函数M语言兼容工具提供了一套API,支持完备的Julia调用,包括:
⦁获取Julia对象的原始引用
⦁Julia对象与M对象的类型转换
⦁调用Julia函数
⦁Julia对象操作
M调用Julia的API | 说明 |
jv = Julia(o) jv = Julia(o, jtype) | 将M类型对象转为Julia类型对象 |
o = fromJulia(jv) o = fromJulia(jv, mtype) | 将Julia类型对象转为M类型对象 |
jv = jeval(s) | 执行Julia代码,返回Julia对象 |
jv = jcall(expr, jv1, ..., jvn) jv = jcall(__, '-kwargs', kws) jv = jcall('-return', mtype, __) jv = jcall('-vector', __) jv = jcall('-noreturn', __) | 调用Julia函数,返回Julia对象 |
jv = jinterp(expr, opts) | jinterp比jeval开销更少 |
jvi = jindex(jv, {i1, ..., in}) jvi = jindex(jv1, {i1, ..., in}, jv2) | 获取Julia对象的索引,返回Julia对象 |
jisa(jv, jtype) | 判断类型,返回logical |
jfunc = jcallable(mfun, {rettypes}, {argtypes}) | 返回一个Julia的callable对象 |
不妨以中值滤波的噪声抑制为例,考虑到MWORKS.Syslab信号处理函数库已提供一维中值滤波函数medfilt1,此时用户M代码若需要调用该函数,可以基于上述API来实现,完整代码如下:
fs = 100;
t = 0:1/fs:1;
x = sin(2*pi*t*3)+0.25*sin(2*pi*t*40);
% 调用同元信号处理函数库的一维中值滤波medfilt1函数
jv_x = Julia(x);
jv_y = jcall("TySignalProcessing.medfilt1", jv_x, Julia(10, "Int"));
y = fromJulia(jv_y, "double")
plot(t,x,t,y)
legend('Original','Filtered')
在MWORKS.Syslab上直接运行上述M脚本,将得到如下运行结果:
②Julia语言调用M函数M语言兼容工具,除了支持M语言调用Julia函数,同样也支持Julia语言调用M函数,包括在Julia执行M代码、操作M语言的对象、类型转换以及调用M语言的函数。
Julia调用M的API | 说明 |
eval_mcode!(code) | 在 Julia 中直接运行 M 代码,返回值为 nothing |
to_mlang(x) | 将一个 Julia 对象转换为 MxArray 类型 |
from_mlang(pm::MxArray) from_mlang(T::Type, pm::MxArray) | MxArray 类型转换对应的 Julia 类型 |
mexCallMLang(funName::String, nargout::Integer, args::Vector{MxArray}) | funName 为调用的 M 函数,nargout 为 M 函数的返回参数个数,返回值为 Vector{MxArray} |
mexGetVar(varName::String) | 获取 MLang REPL 中的变量,返回值为 MxArray |
mxget_cell(pm::MxArray, i) | 获取元胞数组 pm 的第 i 个元素,返回值为 MxArray 类型 |
mxset_cell(pm::MxArray, i, value::MxArray) | 将元胞数组 pm 的第 i 个元素设置为 value |
mxget_field(pm::MxArray, i, fieldname) | 获取结构体 pm 的第 i 个元素的 fieldname 字段,返回值为 MxArray 类型 |
mxset_field(pm::MxArray, i, fieldname, value::MxArray) | 将结构体 pm 的第 i 个元素的 fieldname 字段设置为 value |
mxndims(pm::MxArray) | 获取 pm 的维度 |
nrows(pm::MxArray) | 获取 pm 的行数 |
ncols(pm::MxArray) | 获取 pm 的列数 |
nelems(pm::MxArray) | 获取 pm 的元素个数 |
size_vec(pm::MxArray) | 获取 pm 的 shape, 返回值为 Vector{Int} |
is_class(pm::MxArray, classname) | 判断 pm 是否为 classname 类的实例 |
is_empty(pm::MxArray) | 判断 pm 是否为空数组 |
is_scalar(pm::MxArray) | 判断 pm 是否为标量 |
is_numeric(pm::MxArray) | 判断 pm 是否为数值类型 |
is_char(pm::MxArray) | 判断 pm 是否为字符数组类型 |
is_logical(pm::MxArray) | 判断 pm 是否为 logical 类型 |
is_double(pm::MxArray) | 判断 pm 是否为 double 类型 |
is_complex(pm::MxArray) | 判断 pm 是否为 complex |
is_struct(pm::MxArray) | 判断 pm 是否为 MLang 结构体数组 |
is_cell(pm::MxArray) | 判断 pm 是否为 MLang 元胞数组 |
不妨以求输入向量的均值和标准差为例,假设已存在M统计函数stat,若Julia语言需要调用该算法函数,可以基于上述API来实现调用,完整代码如下:
import TyMLangCore
TyMLangCore.eval_mcode!(raw"""
function [m,s] = stat(x)
n = length(x);
m = sum(x)/n;
s = sqrt(sum((x-m).^2/n));
end
""")
mx = TyMLangCore.to_mlang([12.7, 45.4, 98.9, 26.6, 53.1])
mv = TyMLangCore.mexCallMLang("stat", 2, [mx])
ave = TyMLangCore.from_mlang(mv[1])
println("ave=$ave")
stdev = TyMLangCore.from_mlang(mv[2])
println("stdev=$stdev")
在MWORKS.Syslab中执行上述Julia代码,得到运行结果:
2.3 M函数列表
如果只有M语言解析器,常用的数学或专业算法函数都需要用户自己编写或提供,那么该产品将只是一个没有灵魂的空壳。为了让用户实现“开箱即用、专注业务”,M语言兼容工具目前已内置提供1260+ 常用M函数,覆盖基础、数学、图形、控制系统、信号处理、优化等领域常用函数。
打开MWORKS.Syslab帮助文档,点击左侧目录的M语言兼容工具/支持的M函数列表,可以查阅已支持的M函数列表。如果内置提供的M函数列表不满足用户使用场景,还可以基于上述的M与Julia语言互调用技术,调用同元Julia高性能科学计算函数库,支持用户自定义扩展M函数。
三、典型示例
不妨拿通信行业较为著名的Delta-Sigma开源工具箱为例。该工具箱支持Delta-Sigma模拟器的合成、模拟、实现和动态,能够用来计算Delta-Sigma 模拟器的NTF(噪声传输函数)和STF(信号传输函数),并进行调节。Delta-Sigma工具箱用到了基础、数学、图形、APP、控制系统、优化、信号处理的一些算法函数,具有一定的工程规模和代码复杂度。
得益于高兼容度的M语言解析器和丰富的M函数库,用户无需修改Delta-Sigma任何源码,在MWORKS.Syslab上可以直接打开、运行或调试,并能得到正确结果。
四、总结
目前,M语言兼容工具在工程实践上反复迭代,某芯片产业的77+应用场景案例,8万行代码,实现100% 精准对标。M语言兼容工具达到了一定的兼容度,但与拥有四十年以上积累的MATLAB相比,仍然面临着以下挑战:
⦁部分语言特性的差异:MATLAB为并行计算引入了 parfor、spmd 等内置语法,TyMLang正在计划逐步接入。
⦁边缘语法的差距:虽然MATLAB的语法相对简单,但在边缘情况却有大量细节需要注意。在这种情况下,只能通过不断地测试和修复,来逐步完善TyMLang的语法兼容性,这是一个长期的过程。
⦁内置函数的差距:MATLAB内置了许多工具箱,这些工具箱包含了大量函数。函数的缺失,导致用户在使用TyMLang时可能需要自行实现这些函数,或调用同元软控Julia科学计算函数库。但是,TyMLang将会源源不断地扩充常用M函数库。
兼蓄经典,不止于替代。在长期服务于重大工程的过程中,同元软控始终致力于做好新一代科学计算与系统建模仿真的底座,提供开放的接口,依托中国科学与工程的创新发展需求、高校院所3800万师生和工业企业4000万工程师,共同创建MWORKS的应用生态,立足创新,不断实现自我突破。