为实现一些相对复杂的系统开发或建模仿真,可以借助MathWorks® Simulink在图形化的工程界面中编辑和测试模型,或者使用MathWorks提供的现成实例与模块以零基础快速搭建所需的仿真环境。但若要让他人运行自己搭建的模型,并根据需要自行修改部分模型参数,或是发布为可供他人使用的仿真工具,则需要将模型打包为独立的可执行文件,并设计前端图形化界面以查看仿真输出结果、修改仿真参数。
模型打包的官方推荐做法是使用MATLAB®自带的Simulink Compiler工具,将编译好的 Simulink模型和用于设置、运行和分析仿真的MATLAB代码一起打包,且可以附带由MATLAB App设计工具设计的UI。该方法理论上实现不难,打包出的程序只需Runtime环境即可运行。但MATLAB App设计工具并非主流前端设计工具,需要学习成本,且制作的UI界面较为简单,不满足对前端界面要求较高的场景;此外,打包生成的代码可读性较差,网上现有的案例资源等又极少,一旦报错,很难定位错误,效率不高。
为解决该问题,就需要抛弃官方提供的MATLAB App设计工具和Simulink Compiler打包工具,自行设计前端界面、获取Simulink运行数据并打包发布。一种可行的方案是通过主流前端设计工具制作前端GUI界面,并通过外部会话访问MATLAB,以此控制Simulink模型运行并获取运行时的数据,如可以采用官方提供的用于Python的MATLAB Engine API(https://ww2.mathworks.cn/help/matlab/matlab_external/install-the-matlab-engine-for-python.html),通过Python会话在后台调用MATLAB,将仿真实时状态和模型参数等显示在以PyQt制作的前端UI界面中,最后通过Pyinstaller打包发布。
该方案可以实现复杂的前端软件设计与Simulink模型仿真相结合,且体积不大、兼容性较好,但需安装有MATLAB环境而非Runtime,即发布给他人时除软件本体外还需附带完整MATLAB安装包或对方已安装MATLAB(若未安装则Simulink仿真部分不可用,但不影响软件其他功能)。本质上Simulink模型并没有被编译发布,只是调用了相关api接口使模型在后台仿真运行。官方文档(https://ww2.mathworks.cn/help/matlab/matlab-engine-for-python.html?s_tid=CRUX_lftnav)中也指出了:
“MATLAB Engine API for Python 可提供一个包,供 Python 将 MATLAB 作为计算引擎来调用。引擎应用程序需要已安装版本的 MATLAB;您无法在只有 MATLAB Runtime 的机器上运行 MATLAB Engine。”
目前,该方案在网上可供参考的资源不多,文中可能存在错误和可优化的部分。我使用的Simulink模型为纯电动汽车建模(EvReferenceApplication),模型细节可参考的我的另一篇文章。前端GUI界面使用PyQt5设计,制作一个小型仿真系统软件界面来控制电动汽车模型仿真的启停以及部分模型参数的修改,同时实时读取并显示仿真输出数据。软件界面设计需在保证功能完整的前提下尽量高效美观。
安装用于 Python 的 MATLAB Engine API
安装前需要确认电脑中的Python和MATLAB版本是否兼容,可以查看官方文档页面。(https://ww2.mathworks.cn/support/requirements/python-compatibility.html)
在MATLAB根目录\extern\engines\python文件夹中找到setup.py,即自动安装脚本文件;控制台进入该目录(若Python安装在虚拟环境则要在对应虚拟环境终端进入),输入指令python setup.py install,安装会自动执行。安装完成后MATLAB Engine会以Python包的形式存在,可以通过pip list查询。
除此之外,MATLAB R2022b及以上也可以通过pip安装,详情可参考官方文档。(https://ww2.mathworks.cn/help/matlab/matlab_external/install-the-matlab-engine-for-python.html)
MATLAB Engine需要读取_arch.txt文件中的地址信息来调用电脑中的MATLAB,该文件默认存放于Python包文件夹\matlab\engine中,且仅在安装时自动生成,记录本机MATLAB地址。若本机MATLAB地址改变、_arch.txt文件丢失等都会导致寻址失败。因此不难想到,若要在打包发布时将MATLAB Engine以python包的形式包含,移植到他人电脑中运行,则必须手动修改_arch.txt文件中的地址信息为对方电脑中的MATLAB地址,否则无法正常运行。
MATLAB Engine需要读取的文件地址信息(即_arch.txt文件)如下:
序号(行数) | 名称 | 内容 | 备注 |
1 | _arch | 系统类型(win64, glnxa64, maci64) | Windows系统为win64 |
2 | _bin_dir | 本机MATLAB主程序所在地址 | MATLAB.exe文件所在地址 |
3 | _engine_dir | 本机MATLAB Engine文件所在地址 | matlabengineforpython版本号.pyd文件所在地址 |
4 | _extern_bin_dir | 本机MATLAB Engine 附属文件所在地址 | matlabmultidimarrayforpython.pyd等文件所在地址 |
修改python包代码
为保证MATLAB Engine在打包移植后仍能正常读取配置文件,获取目标系统环境信息及MATLAB地址,需对包中部分代码进行修改。
在本机Python包文件夹\matlab\engine\__init__.py中找到调用_arch.txt文件所在代码
_arch_filename = os.path.join(_module_folder, "_arch.txt")
将其改为
_arch_filename = os.path.join(os.path.abspath('.'), "xxxxx.txt")
其中“xxxxx.txt”可以是任意文本文件,需自行编写代码,在程序运行时根据当前系统环境在程序运行根目录下生成该文件,从而实现MATLAB Engine包寻址。
在python中调用MATLAB Engine
官方文档(https://ww2.mathworks.cn/help/matlab/matlab_external/start-the-matlab-engine-for-python.html)中给出了在Python中调用MATLAB Engine的代码示例和说明可供参考,但大多较为简略,且实际编程时还需结合以编程方式运行仿真(https://ww2.mathworks.cn/help/simulink/ug/using-the-sim-command.html),用代码代替Simulink® UI 与仿真进行交互,如果以前从未接触过Simulink的代码形式,可能遇到不少问题。本仿真系统中用到的部分代码见下表。
代码 | 说明 | 备注 |
import matlab.engine | 将matlab.engine包导入Python 会话中。 | 为防止移植后MATLAB Engine读取配置文件错误而引发崩溃,可以使用try….except语句捕捉异常并重新生成配置文件。 |
self.eng = matlab.engine.start_matlab(‘-nosplash’) | 启动新的MATLAB®进程。之后可以通过对象eng传递数据和调用由 MATLAB® 执行的函数。 | 输入参数option=’-nosplash’,表示不在启动时显示启动画面,详见官方文档(https://ww2.mathworks.cn/help/matlab/ref/matlabwindows.html) |
self.eng.addpath(filePath) | 将指定的文件夹添加到当前MATLAB®会话的搜索路径顶层。 | 详见官方文档(https://ww2.mathworks.cn/help/matlab/ref/addpath.html) |
self.eng.openProject(‘EV’) | 将工程加载到指定的文件或文件夹中。 | 如果当前有任何工程处于打开状态MATLAB®会在加载指定工程之前关闭它们。详见官方文档(https://ww2.mathworks.cn/help/matlab/ref/openproject.html) |
self.eng.load_system(env_name) | 将模型env_name加载到内存中,而无需在 Simulink® 编辑器中打开模型。 | 将模型加载到内存后,可以使用 Simulink API 命令对其进行处理。使用 save_system 保存对模型的更改。详见官方文档(https://ww2.mathworks.cn/help/simulink/slref/load_system.html) |
self.eng.start(nargout=0) | 开始仿真 | 参数nargout=0不能省略,表示无需返回值,否则将会报错。 |
self.eng.xxx(nargout=0) | 调用指定m文件或MATLAB函数 | 详见官方文档(https://ww2.mathworks.cn/help/matlab/matlab_external/call-matlab-functions-from-python.html) |
self.eng.workspace[‘xx’] self.eng.workspace[‘xx’] = y | 读取或修改MATLAB 工作区中指定变量的值。若该变量不存在则创建该变量。 | 其返回值为MATLAB工作区中对应变量值转换为python变量;若对其赋值则为创建MATLAB变量或修改MATLAB 工作区中指定变量的值。详见官方文档(https://ww2.mathworks.cn/help/matlab/matlab_external/use-the-matlab-engine-workspace-in-python.html) |
self.eng.set_param(object,parameter1,value1,…,parameterN,valueN,nargout=0) | 将指定的 Simulink® 参数 parameter 设置为由 object 指定的目标对象(模型)的指定值 value。 | 用于设置Simulink指定模型object中的参数,最后一个参数nargout=0不能省略且在官方文档中未特别注明,表示无需返回值,否则将会报错。object、parameter、value参数均为字符串。详见官方文档(https://ww2.mathworks.cn/help/simulink/slref/set_param.html) |
其中,本仿真系统用到的部分set_param中的parameter参数与对应的value值见下表。需要注意表中所有参数均为Python String。
parameter参数 | value值 | 说明 | 备注 |
fileVar | 文件地址 | Drive Cycle Source所需文件地址 | 如xxx/xxx.mat |
StartTime | Float数值 | 模型开始时间 | |
StopTime | Float数值 | 模型停止时间 | |
ExtrapolationOrder | Int数值 | 求解器外插阶数 | 仅部分求解器(如ode14x)有该参数 |
FixedStep | Float数值 | 求解器步长(基础采样时间) | 仅定步长求解器有该参数 |
SolverName | 求解器名 | 求解器名 | 如ode14x、ode1be |
NumberNewtonIterations | Int数值 | 牛顿迭代次数 | 仅部分求解器(如ode14x)有该参数 |
SimulationCommand | 指定字符串(如start、pause、continue、stop、update、WriteDataLogs) | 设置Simulink控制指令 | 控制Simulink仿真启停、更新等 |
另外需要注意的是,在Python中调用MATLAB Engine相当于访问外部会话,属于耗时操作,若写在主程序线程中将会导致线程阻塞,影响前端GUI界面运行,需要利用多线程通信来解决该问题。
打包发布
使用pyinstaller打包发布Python程序,但pyinstaller无法正确识别MATLAB Engine包,此时需要手动复制该包;若需要打包为单一可执行程序(.exe),则需要配置hooks来添加指定包。
在主程序.py目录下新建文件夹,命名为hooks,其内新建py文件,内容如下:
from PyInstaller.utils.hooks import collect_all
datas, binaries, hiddenimports = collect_all('matlab')
打包时额外添加指令:
--additional-hooks-dir=hooks
如:
pyinstaller -F -w --additional-hooks-dir=hooks 主程序.py
即可正确打包为单一可执行程序。
暂无评论