张志波,童中翔,王超哲,李建勋,李 彬
(1.空军工程大学 航空航天工程学院,陕西 西安710038;2.山东交通职业学院 机电工程系,山东 潍坊261206)
Matlab作为一款优秀的数学计算工具软件,已经广泛应用于各个领域,但其也存在着代码效率低,执行速度慢,难以生成脱离Matlab环境的应用程序等诸多缺点。因此,广大学者对于如何在其它编程环境下利用Matlab的数学工具进行了广泛研究探讨。在所见的方法中,利用Matcom来实现在VC等编译环境中直接利用Matlab函数最为方便简单,很多文献和技术书籍都做了大量的介绍。中国地质大学的刘迎等利用Matcom的函数对合成数据和实测超声波信号进行了可视化分析处理[1];上海交通大学的倪静静利用Matcomh函数实现了卡尔曼滤波[2];刘维在其编著的书籍 《精通Matlab与C/C++混合程序设计》中用一章的篇幅对其进行详细梳理讲解[3]。但是长期以来,这些方法都局限于利用Matcom提供的基本函数,对于需要将自定义函数作为参数的高级函数却鲜有人知道如何自由地利用。例如:非线性拟合、数值积分、常微分方程求解、函数极值求解等函数。
目前对于需要传入自定义函数作为参数的高级函数调用,一般采取的是通过Matcom将对应的m文件转换为C/C++文件。如首都师范大学的李宝方、中国工程物理研究院机械制造工艺研究所的郭超、空军工程大学的陈慕春、山东理工大学的侯运鑫、海军工程大学的黄晓颖等通过转换的C/C++文件,实现了函数拟合、神经网络运算等等复杂的数值计算,开发了相应的功能程序[4-9]。这种应用Matcom将m文件转换为C/C++的方法,通用性较差,每个函数都必须单独转换。而且实践中发现此种方法,只允许在程序中存在一个自定义函数,对于有多个自定义函数的情况,此种方法无法解决。通过对Matcom调用自定义函数的内部机理和其提供的编译头文件matlab.h进行详细分析,本文提出了一种直接在编译环境中编写函数,然后将其作为参数输入的方法。这种方法可以自由地输入自定义函数,而且允许一个程序中有多个不同的自定义函数,极大地提高了工作效率,降低了数值计算方面的难度,圆满解决了上诉诸多问题。
利用Matcom动态链接库编程,需要安装Matcom和在编译环境中配置相关文件,参考文献 [3]中有详细的说明,这里不再详述。这样配置后可以实现许多无需传入自定义函数作为参数的函数应用,比如矩阵计算、矩阵分解、矩阵求逆、多项式拟合、多项式方程根的求解等,关于这方面的基本应用可查阅Matcom提供的函数手册。如果此时按照Matcom函数手册的说明,调用高级函数,例如数值积分,编译无法通过,提示出错 “feval was not linked with the program”。分析该出错提示,很容易发现是因为函数feval没有成功连接到工程中,导致编译无法通过。
通过文献 [10]提供的方法,可以在软件的安装目录下找到文件feval.h和feval.cpp文件,其中就包含了feval函数的定义。但是将此文件添加进工程中后,仍不能自由地传入自定义函数。要解决这个问题,必须分析Matcom调用自定义函数的内部机理,然后重新设计程序流程。
通过分析Matcom翻译m文件得到的feval函数文件,结合Matlib.h头文件,可知Matcom在调用需要传入自定义函数作为参数的高级函数时,有一套内置的程序流程,如图1所示。
在用户调用ode45、quad等高级函数时,在调用用户自定义函数之前,会隐式的调用feval函数。通过调用feval函数来隐式的调用用户自定义函数。因为,feval函数实际上通过翻译已有的Matlab函数文件.m文件得到的,其在翻译过程中会将用户自定义的函数包含进feval.cpp文件中。在feval.cpp文件中表现为代码语句 “#include"myfun.h"” 和 ”y1 = myfun (varargin.safebr (1),varar-gin.safebr(2))”(由于在工程中翻译而成的自定义函数为myfun,所以此处为此语句,该语句并不具有唯一性)。而“myfun.h”文件实际上就是用户自定义函数的头文件。通过包含头文件myfun.h,vc等编译器在编译代码时也就知道了自定义函数的地址,也就是本工程中的自定义全局函数myfun,从而调用用户自定义函数。
图1 原始计算流程
通过分析feval.cpp文件,可以得到如下结论:feval文件内嵌了自定义函数的头文件,将自定义函数与feval函数绑定在一个文件中,其能够调用的函数并不是真正意义上的自定义函数。要解决这一问题,就必须在feval文件中预留一个自定义函数的输入接口,让用户能自由的将自定义函数地址传入feval函数中,从而解决自由传入自定义函数这一问题。
通过分析Matcom调用自定义函数的内部机理,可以按照如下的方法来实现利用VC++等编译环境中直接编写的自定义函数作为输入参数进行数值积分、常微分方程求解等多种高级应用。
在feval函数中增加各种类型的函数指针,包括一元函数、二元函数等常用的自定义函数类型,从而增加一个自定义函数的外部输入接口。在feval函数调用函数指针过程中,通过ode45、quad的高级函数传入的参数来区别使用的函数指针类型。通过修改feval函数文件形成新的程序流程,如图2所示,也就能实现所期望的功能。具体使用方法为,在调用ode45、quad等高级函数前,首先显式的将自定义函数地址传给feval中对应类型的函数指针,然后再按照自定义函数的类型,调用Matcom提供的高级函数。
通过上文的流程设计,可以很容易地修改原有“feval.cpp”文件中的代码,实现需要的功能。具体的实现步骤为:
(1)按照文献 [10]提供的方法,生成原始文件 “feval.h”和 “feval.cpp”。
(2)修改 “feval.cpp”文件,增加函数指针变量两个,改写部分代码。
图2 本文设计的计算流程
1)删除语句 ‘#include"feval_myfun.h"’和 ‘#include"myfun.h"’,也就是删除feval函数绑定的自定义函数。
2)在文件起始处添加一个头文件、两个函数指针变量和一个函数类型的标记变量,功能为提供自定义函数的外部接口,内容如下:
3)替换部分语句。
将语句:
替换为:
替换的目的为通过传入的函数参数来区别自定义函数的类型。
将语句:
替换为:
替换的目的为将原始的直接调用自定义函数的方式改为通过函数指针来调用的方式。
4)将所有的语句 “#line”替换为 “//#line”,即注释掉该行语句,这些为Matcom翻译过程中留下的原始matlab语句。
说明一下,由于文件目录不同,替换的代码会有所差别。通过以上操作,我们需要的文件feval.h和feval.cpp就准备完毕。使用时需要在工程中添加这两个文件,并且在需要调用的地方,声明外部函数指针变量myfun1和myfun2。声明代码如下:
调用高级函数时,首先显式的将函数地址赋值给类型相对应的函数指针,然后正常调用ode45、quad等高级函数。这里要特别强调,此时输入高级函数中的函数名并不是自定义函数名,而是使用的函数指针的名称,用于feval函数内部识别正确的函数指针。具体的使用可参见下文的算例说明。
通过将自定义函数作为参数,可以利用Matcom提供的动态链接库实现许多复杂的数值计算功能,例如非线性函数拟合、数值积分、常微分方程求解、函数极值求解等,其调用的方法和需要输入的参数和Matlab中基本一样,只是根据C++语言的特性在形式上做了些改变。通过本文的方法,这些函数的调用方法也基本相同,只是在调用之前需要将函数地址赋值给feval.cpp文件中的函数指针变量。为了更加简单地说明该方法如何使用,现以常见的数值积分和解常微分方程为例,详细说明自定义的一元函数、二元函数的调用方法。
以求解式y=cosx+xex在区间[-π,π]上的积分为例进行说明。
首先需要编写自定义函数。编写自定义函数时需要特别注意,自定义函数必须为静态的成员函数,或者是全局函数,只有这样才能将函数地址赋值给函数指针。
此函数的声明代码如下:
函数具体实现代码如下:
调用Matcom动态链接库中的函数quad实现数值积分,具体代码和说明如下:
myfun1=&fun1;//将自定义函数地址赋值给feval.cpp中的一元函数指针变量
Mm y=quad (TM ("myfun1"),-pi,pi);//调用函数quad,参数TM ("myfun1")表示传入为一元函数,用于feval函数内部识别自定义函数类型
函数运行后,输出结果如图3所示。通过积分函数的原函数y=sinx+xex-ex可求得精确解为49.736911787753602,比较可知计算结果精确度很高。
图3 数值积分结果
以Matlab中提供的解常微分方程的例子为例,说明使用方法。微分方程如下
需要求取时间范围 [0,12],初值分别为0,1,1时的解。同一元函数的使用方法一样,首先需要编写自定义函数。函数声明如下:
函数具体实现代码如下:
调用Matcom动态链接库中的函数ode45,实现利用四阶、五阶Runge-Kutta单步算法求解常微分方程。调用代码和说明如下:
代码运行的结果如图4所示。将其与Matlab帮助文件提供的输出结果 (图5)比较,可见在VC中完美的实现了解常微分方程。
使用Matcom提供的动态链接库解决数值计算问题具有很大的灵活性和简单性,但是由于Matcom的所有计算几乎都是来自于Matlab,也就是基于矩阵运算的思想。因此在编写自定义函数时,需要特别注意,必须按照Matcom的规范进行书写。这里将一些需要注意的问题和使用中的一些技巧进行说明,以便大家更容易使用。
自定义的函数,函数返回值和形参必须是Matcom可以识别的内部数据格式,也就是Mm的数据格式,而不能使用int、float、double等类型的数据格式。比如名为“functionExample”的一元函数就应该写成如下的形式:
在函数的具体实现部分,肯定少不了乘法和除法运算。在进行编写时,必须注意乘法和除法的代码书写,区别一般乘法和点乘,一般除法和点除。例如求解函数x·sin(x)的积分,按照C++语言的规则和Matcom对于自定义函数的要求,代码应该如下:
但是,按照这种方法编写的自定义函数,利用上文提供的求解积分方法会提示出错,并不能得到正确的积分结果。这就是因为这里的乘法应该是点乘运算,而不是一般意义的乘法。在Matcom提供的函数库中,利用times()函数来实现点乘运算。所以此处自定义函数的代码应该如下:
乘法如此,除法也不例外,在编写代码时也必须分清一般除法和点除的区别,Matcom函数库中的函数rdivide()来实现点除运算。
这种利用Matcom提供的动态链接库进行快速数值计算的方法是通过改变Matcom调用自定义函数的流程来实现的,因此在使用过程中可以利用Matcom软件提供很多帮助,降低难度,减少一些错误的发生。
(1)利用Matcom将Matlab写成的函数文件,也就是.m文件,直接翻译成C++文件,生产成自定义函数,能有效的降低错误。
(2)通过分析Matcom翻译M文件而成的C++文件,能快速掌握Matcom提供的函数库的使用方法。
(3)将Matlab和 Matcom有效的结合。Matcom作为一款插件性质的软件,自带的函数帮助文件有限,可以充分利用Matlab强大的帮助文件,快速掌握相关函数的功能和使用方法。利用Matcom将已有的M文件翻译成C++文件能将Matlab强大的数值计算能力快速高效的移植到C++平台,实现脱离Matlab平台的快速数值计算。
本文设计的计算方法,实现了在VC++等编译环境中直接编写自定义函数作为Matcom内置函数的输入参数,极大限度地利用Matcom提供的动态链接库。该方法与目前的方法相比,具有以下优势:
(1)函数直接在编译环境中编写,无需 Matcom转化m文件,代码简洁高效,可读性更强。
(2)摆脱了目前使用方法中一个程序内只能使用一个自定义函数的局限性,工程应用范围更广。
(3)该方法只需要 Matcom提供的动态链接库及相应的头文件,可以完全脱离Matcom软件,独立性更强,对于C++builder等C++编译环境同样适用。
研究人员只要熟悉Matlab的函数调用,就可以利用该方法快速地解决实际的数值计算问题,为脱离Matlab环境的数值计算领域提供了一种快速高效的解决方案。
[1]LIU Ying,SHI Xueming,CHEN Xiaoling,et al.Application of mixed programming with MATCOM and VC++in data visualization processing [J].Chinese Journal of Engineering Geophysics,2007,4 (5):455-459 (in Chinese). [刘迎,师学明,陈晓玲,等.VC与MATCOM联合编程在数据可视化处理中的应用 [J].工程地球物理学报,2007,4 (5):455-459.]
[2]NI Jingjing,WANG Junpu,JIN Zhihua,et al.New method For KALMAN filter implementation using matcom and visual C++ [J].Computer Applications and Software,2008,25(5):175-176 (in Chinese). [倪静静,王俊璞,金志华,等.利用Matcom和Visual C++实现卡尔曼滤波的新方法 [J].计算机应用与软件,2008,25 (5):175-176.]
[3]LIU Wei.Specialize in hybrid programming bacede on Matlab and C++ [M].2nd ed.Beijing:Beihang Univercity Press,2007:166-224 (in Chinese).[刘维.精通 Matlab与C/C++混合程序设计 [M].2版.北京:北京航空航天大学出版社,2007:166-224.]
[4]LI Baofang,GUAN Yong,SHEN Xiaoben,et al.Determination of optimal fitting to function based on VC++ and Matcom[J].Computer Engineering and Design,2007,28 (12):2980-2982(in Chinese).[李宝方,关永,沈孝本,等.基于VC++和Matcom混合编程的函数最佳拟合的确定 [J].计算机工程与设计,2007,28 (12):2980-2982.]
[5]GUO Chao,ZHOU Danchen.Man-hour quota system based on genetic neural network [J].Computer Applications and Software,2010,27 (8):205-208 (in Chinese).[郭超,周丹晨.基于遗传神经网络的工时定额系统 [J],计算机应用与软件,2010,27 (8):205-208.]
[6]CHEN Muchun,WANG Xu,DONG Xiaolong.Application of VC++ and MATCOM in airplane performance program development [J].Fire Control and Command Control,2008,33(3):44-45 (in Chinese). [陈慕春,王旭,董小龙.VC++和MATCOM结合在飞机基本飞行性能程序开发中的应用[J].火力与指挥控制,2008,33 (3):44-45.]
[7]HOU Yunxin,ZHANG Guixiang,SHAO Mei,et al.Dealing with trend item of collected signals based on Matlab and VC++[J].Journal of Shandong University of Technology (Natural Science Edition),2009,23 (1):53-59 (in Chinese). [侯运鑫,张桂香,邵梅,等.基于Matcom与VC++的信号趋势项处理 [J].山东理工大学学报 (自然的科学版),2009,23(1):53-59.]
[8]HUANG Xiaoying,TONG Yude,BIAN Shaofeng.Implementation for simulation of ICCP based on matcom [J].Journal of Geomatics Science and Technolog,2011,28 (3):186-189(in Chinese).[黄晓颖,童余德,边少锋.基于 Matcom混合编程的ICCP算法仿真实现 [J].测绘科学技术学报,2011,28 (3):186-189.]
[9]HU Shaoquan,ZHANG Chao.Development of vibration signal analysis module based on VC&MATLAB [J].Computer&Digital Engineering,2011,39 (7):161-164 (in Chinese). [胡绍泉,张超.基于VC和MATLAB振动信号分析模块开发 [J].计算机与数字工程,2011,39 (7):161-164.]
[10]GU Shefeng,CUI Ruihua.The method of calling the user defined function as the input argument in mixed programming with MATCOM and VC+ + [J].Computer Programming Skills & Maintenance,2009 (22):21-23 (in Chinese). [谷社峰,崔瑞华.MATCOM与VC++混合编程中自定义函数作为输入参数的调用方法 [J].计算机编程技巧与维护,2009 (22):21-23.]