将 MATLAB 代码集成到 InfoSphere Streams 中

将 MATLAB 函数编译成 C++ 库并将其导入 SPL 以在 Streams 应用程序中执行它

MATLAB 是一种科学计算语言和平台,为矩阵操作和大量数学建模库提供了强大支持,是实现各种分析资产的最优选择。本文将讨论如何将 MATLAB 函数集成到 SPL 中,以便在 IBM® InfoSphere® Streams 应用程序内部执行 MATLAB 代码。这种集成无需更改 MATLAB 代码,它依靠 MATLAB 支持将 MATLAB 代码编译为 C++ 共享库,并依靠 SPL 支持与本机函数建立连接。

Bugra Dr. Gedik, 高级研究员, IBM

作者照片:Gedik 博士Buğra Gedik 博士供职于 IBM Thomas J. Watson 研究中心。目前,他的研究兴趣是大规模分布式流处理系统。他特别关注语言、编译器和运行时设计,以及配置、优化和调试问题。他是 IBM InfoSphere Streams 产品的主要开发人员之一。



2011 年 8 月 29 日

概述

本文讨论如何从 InfoSphere Streams 应用程序内部执行 MATLAB 函数。MATLAB 是一种科学计算语言和平台,为矩阵操作和大量数学建模库提供了强大支持,是实现各种分析资产的最优选择。用其他语言重新实现这些分析资产通常很麻烦,因为要需要寻找一些外部库来提供已经内置于 MATLAB 中的功能。正因如此,开发人员特别希望将用 MATLAB 编写的函数集成到 InfoSphere Streams 应用程序中,这样就不必以 C++ 或 Java™ 重写函数逻辑,这两种语言是 Streams 支持的一级语言。

MATLAB 支持将用 MATLAB 编程语言编写的函数(在 .m 文件中)编译为 C++ 共享库。它还提供一些 API 来编写与生成的库交互的 C++ 代码,比如提供矩阵数据类型的工具类。另外,MATLAB 还提供一个运行库来链接到使用编译为 C++ 的 MATLAB 例程的应用程序。将 MATLAB 函数集成到 Streams 的总方法如下:

  1. 开发您的 MATLAB 函数。
  2. 使用 MATLAB 编译器创建一个 C++ 共享库。
  3. 在 SPL 中创建一个本机函数来封装 C++ 共享库。
  4. 编写一个 SPL 应用程序来使用这些本机函数。

上述步骤将通过下面这个示例来演示。

开发您的 MATLAB 函数

您将编写一个简单的 MATLAB 函数来执行矩阵求逆(matrix inversion)运算。

创建一个名为 Matlab_inv 的目录,在其中放入一个名为 ml_inv.m 的文件,该文件包含以下内容:

  function out = ml_inv(in)
  % ML_INV Matrix inverse
  % Invert a given matrix
  out = inv(in);

您的目标是能够在 SPL 代码中轻松使用这个矩阵求逆函数。下面是一个样例 SPL 函数,它利用了包装来自 MATLAB 的 ml_inv 函数的矩阵求逆函数 ml::inv。本文余下部分将介绍创建这个 ml::inv 函数所需的步骤。

  void foo() 
  {   // Sample SPL code that uses the matrix inverse
      list<list<float64>> inM = [[1.0,3.0],[2.0,4.0]];
      mutable list<list<float64>> outM = [];
      ml::inv(outM, inM); // ml::inv is the wrapper for ml_inv
  }

使用 MATLAB 编译器创建一个 C++ 共享库

您将使用 MATLAB 编译器创建一个 C++ 共享库,该库将包含矩阵求逆函数。

  1. 在 MATLAB 命令行中,键入 deploytool
  2. 跟随图形界面在 Matlab_inv 目录下创建一个名为 libMatlab_inv 的部署项目。
  3. 单击 Add File 按钮将 ml_inv.m 文件添加到项目。
  4. 单击 Build 按钮创建共享库。

下面的结果文件将生成于 Matlab_inv/libMatlab_inv/distrib 目录下。

  • libMatlab_inv.h:这是一个接口文件,用于声明从 MATLAB 代码生成的 C++ 函数。
  • libMatlab_inv.so:这是共享库,包含从 MATLAB 代码生成的 C++ 函数实现。
  • libMatlab_inv.exports:这是一个文本文件,列出了库导出的函数的名称。库操作不需要这个文件。
  • libMatlab_inv.ctflibMatlab_inv_mcr:前者是一个归档文件,MATLAB 编译器从该文件创建后面的目录。它们包含额外的支持库,应该与 .so 共享库位于相同的位置。

在一个独立 C++ 应用程序中试用共享库

在 Streams 应用程序中使用共享库之前,先在一个独立 C++ 应用程序中试用它。

创建一个名为 sample_c++ 目录,与 Matlab_inv 位于同一级别。在这个目录中,创建一个名为 sample.cpp 的文件并填充以下内容:

  #include "libMatlab_inv.h"
  int main()
  {
      libMatlab_invInitialize();
      double data[] = {1.0, 2.0, 3.0, 4.0};
      mwArray in(2,2,mxDOUBLE_CLASS);
      mwArray out(2,2,mxDOUBLE_CLASS);
      in.SetData(data, 4);
      ml_inv(1, out, in);
      std::cerr << "[" << out(1,1) << ", " << out(1,2) << "; "
                       << out(2,1) << ", " << out(2,2) << "]\n";
      libMatlab_invTerminate();
      return 0;
  }

在上面的代码中,您调用 libMatlab_invInitialize 函数来初始化 MATLAB 运行库。这是 MATLAB 编译器为您生成的函数之一。接下来,您创建了以下两个矩阵:inoutmwArray 是 MATLAB C++ APIs 提供的一个 C++ 类,用于处理矩阵。您可以使用 ml_inv 函数对 in 矩阵进行求逆运算,并将结果赋予 out 矩阵。这是 MATLAB 编译器根据 ml_inv.m 文件中的同名 MATLAB 函数为您生成的核心函数。最后,您可以调用 libMatlab_invTerminate 函数来终止 MATLAB 运行库。同样,这也是 MATLAB 编译器为您生成的函数。

要编译这个程序,请按以下方式创建一个 Makefile

.PHONY: all clean
MATLAB_LIBRARY_LOCATION := /nfs/hny/apps01/matlab/bin/glnxa64/
MATLAB_INCLUDE_LOCATION := /nfs/hny/apps01/matlab/extern/include/
all: 
	g++ -o sample sample.cpp                           \
            -I ../Matlab_inv/libMatlab_inv/distrib         \
            -I $(MATLAB_INCLUDE_LOCATION)                  \
            -L ../Matlab_inv/libMatlab_inv/distrib         \
	    -Wl,-rpath,../Matlab_inv/libMatlab_inv/distrib \
            -L $(MATLAB_LIBRARY_LOCATION)                  \
	    -Wl,-rpath,/nfs/hny/apps01/matlab/bin/glnxa64/ \
            -lMatlab_inv -lmwmclmcrrt 

clean:
	rm sample

应该注意以下几点:

  • 路径 -I ../Matlab_inv/libMatlab_inv/distrib 用于指定 MATLAB 编译器生成的接口文件(libMatlab_inv.h)的位置。
  • 路径 -I $(MATLAB_INCLUDE_LOCATION) 用于指定 MATLAB C++ APIs 的接口文件的位置。这个位置特定于您的环境,取决于 MATLAB 安装的位置。应该对变量 $(MATLAB_INCLUDE_LOCATION) 进行相应的定义。
  • 库路径 -L ../Matlab_inv/libMatlab_inv/distrib 用于指定 MATLAB 编译器生成的库(libMatlab_inv.so)的位置。
  • 库路径 -L $(MATLAB_LIBRARY_LOCATION) 用于指定 MATLAB 运行库的位置。这个位置特定于您的环境,取决于 MATLAB 安装的位置和您的系统架构。变量 $(MATLAB_LIBRARY_LOCATION) 应该被相应定义。
  • -lMatlab_inv 用于指定 MATLAB 编译器生成的库的名称(libMatlab_inv.so)。可以使用相同路径指定 RPATH,以便获得该库的运行时位置。
  • -lmwmclmcrrt 用于指定 MATLAB 运行库的名称(libmwmclmcrrt.so)。

键入 make 构建库并键入 ./sample 运行它。输出应该如下所示: [-2, 1.5000; 1, -0.5000]

在 SPL 中创建了一个本机函数来封装 C++ 共享库

您将在 SPL 中创建一些本机函数,以便利用 MATLAB 编译器生成的函数。方法有两种:创建一个工具包来封装所有本机函数;或者将本机函数直接包含到应用程序中。如果有多个应用程序要利用这些函数,则第一种方法更适合。为简便起见,我们在这里将使用第二种方法。

  1. 创建一个名为 sample_spl 的目录,它与 Matlab_invsample_c++ 位于同一级别。该目录将是您的应用程序目录。
  2. 创建一个名为 ml 的子目录。这将作为您的名称空间目录。
  3. ml 下创建一个名为 native.function 的子目录,用于存放函数模型。
  4. 在此目录下,添加名为 function.xml 的函数模型文件,该文件包含以下内容:
<functionModel
   xmlns="http://www.ibm.com/xmlns/prod/streams/spl/function"
   xmlns:cmn="http://www.ibm.com/xmlns/prod/streams/spl/common"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.ibm.com/xmlns/prod/streams/spl/function 
                       functionModel.xsd">
  <functionSet>
    <headerFileName>spl_ml_inv.h</headerFileName>
    <functions>
      <function>
        <description>Take inverse of a matrix</description>
        <prototype><![CDATA[ public void inv(mutable list<list<float64>> r,
                             list<list<float64>> s) ]]></prototype>
      </function>
      <function>
        <description>Initialize Matlab runtime</description>
        <prototype><![CDATA[ public void initialize() ]]></prototype>
      </function>
      <function>
        <description>Finalize Matlab runtime</description>
        <prototype><![CDATA[ public void terminate() ]]></prototype>
      </function>
    </functions>
    <dependencies>
      <library>
        <cmn:description>Matrix inverse</cmn:description>
        <cmn:managedLibrary>
          <cmn:lib>Matlab_inv</cmn:lib>
          <cmn:lib>mwmclmcrrt</cmn:lib>
          <cmn:libPath>../../impl/lib</cmn:libPath>
          <cmn:libPath>/nfs/hny/apps01/matlab/bin/glnxa64/</cmn:libPath>
          <cmn:includePath>../../impl/include</cmn:includePath>
          <cmn:includePath>/nfs/hny/apps01/matlab/extern/include/</cmn:includePath>
        </cmn:managedLibrary>
      </library>
    </dependencies>
  </functionSet>
</functionModel>

在上面的函数模型文件中,有几点需要注意:

  • 文件 spl_ml_inv.h 是头文件,包含将封装 MATLAB 生成的函数的 C++ 函数。
  • 三个 SPL 本机函数 invinitializeterminate 分别对应 MATLAB 编译器生成的三个函数 ml_invlibMatlab_invInitializelibMatlab_invTerminate。请注意,inv 函数使用 SPL 嵌套列表来表示矩阵,因为 SPL 中没有矩阵类型。您将看到,inv 函数还将执行 SPL C++ 类型和 MATLAB C++ 类型之间的转换。
  • Matlab_invmwmclmcrrt 被指定为依赖项,它包含这些库的库路径和包含路径 (include path)。请注意,Matlab_inv 库的包含路径和库路径是相对于模型文件进行指定的。

现在,您将相关文件复制到应用程序目录 sample_spl 下的 impl 子目录中,使应用程序成为自含应用程序。

  1. 在应用程序目录 sample_spl 下创建目录 impl
  2. impl 目录下创建两个子目录:includelib。将 libMatlab_inv.h 文件从 Matlab_inv/libMatlab_inv/distrib 目录复制到 include 目录中。
  3. 现在,将 libMatlab_inv.solibMatlab_inv.ctf 文件以及 libMatlab_inv_mcr 目录复制到 lib 目录中。
  4. 最后,在 include 目录下创建封装器 spl_ml_inv.h 头部文件,文件内容如下:
  #include "libMatlab_inv.h"
  namespace ml {
      void initialize() {
          libMatlab_invInitialize();
      }
      void terminate() {
          libMatlab_invTerminate();
      }
      void inv(SPL::list<SPL::list<SPL::float64> > & lhs,
               SPL::list<SPL::list<SPL::float64> > const & rhs)
      {
          size_t nr = rhs.size(), nc = rhs[0].size();
          mwArray in(nr, nc, mxDOUBLE_CLASS);
          mwArray out(nr, nc, mxDOUBLE_CLASS);
          for(size_t r=0; r<nr; ++r)
              for(size_t c=0; c<nc; ++c)
                  in(r+1,c+1) = rhs[r][c];
          ml_inv(1, out, in);
          lhs.resize(nr);
          for(size_t r=0; r<nr; ++r) {
              lhs[r].resize(nc);
              for(size_t c=0; c<nc; ++c)
                  lhs[r][c] = out(r+1,c+1);  
          }      
      }
  }

在上面的代码中,inv 函数执行 SPL list 类型和 MATLAB mwArray 类型之间的转换,以便封装 MATLAB 编译器生成的 ml_inv 函数。注意,所有的封装器函数都放置到名称空间 ml 中,因为 native.function 目录位于名称空间目录 ml 下。

编写一个使用本机函数的 SPL 应用程序

您将编写一个使用本机函数的 SPL 应用程序,这些本机函数封装 MATLAB 编译器生成的函数。

sample_spl 目录下创建一个名为 Main.spl 的文件,文件内容如下:

  composite Main {
      graph
          stream<int8 dummy> Beat = Beacon() { 
              param iterations : 1u; 
          }
          () as Sink = Custom(Beat) {
              logic
                  onTuple Beat: {
                      ml::initialize();
                      list<list<float64>> inM = [[1.0,3.0],[2.0,4.0]];
                      mutable list<list<float64>> outM = [];
                      ml::inv(outM, inM);
                      println(outM);
                      ml::terminate();
                  }
          }
  }

该样例 SPL 应用程序将初始化 MATLAB 运行库,通过 ml::inv 函数执行一个矩阵求逆运算,然后终止 MATLAB 运行库。

  1. sample_spl 目录中键入 sc -m -M Main,为该应用程序创建一个 Makefile。
  2. 然后键入 make standalone 构建它。
  3. 您将看到一些警告。忽略这些多余的警告(Streams 的未来版本可能会包含抑制这些警告的支持),键入 ./output/bin/standalone 来运行该应用程序。您应该能看到以下结果:[[-2,1.5],[1,-0.5]]

结束语

本文介绍了如何将 MATLAB 函数集成到 Streams 应用程序中,而无需以另一种语言重写函数逻辑。这种方法依靠 MATLAB 编译器将 MATLAB 代码转换为 C++ 共享库,并编写一个封装这个共享库的 C++ 本机函数,然后使用一个函数模型将封装器函数导入 SPL 中。本文还提供了我们讨论的示例的样例源代码。


下载

描述名字大小
本文样例代码MatlabStreamsIntegration.zip322KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=754373
ArticleTitle=将 MATLAB 代码集成到 InfoSphere Streams 中
publish-date=08292011