【问题标题】:Most Efficient Way of Using mexCallMATLAB in Converting Double* to mxArray*使用 mexCallMATLAB 将 Double* 转换为 mxArray* 的最有效方法
【发布时间】:2014-11-06 00:09:42
【问题描述】:

我正在编写一个MEX 代码,我需要在其中使用pinv 函数。我正在尝试以最有效的方式使用mexCallMATLABdouble 类型的数组传递给pinv。举个例子,假设数组名为G,其大小为100。

double *G = (double*) mxMalloc( 100 * sizeof(double) );

在哪里

G[0] = G11; G[1] = G12;
G[2] = G21; G[3] = G22;

这意味着 G 的每四个连续元素是一个2×2 矩阵。 G 存储 25 这个 2×2 矩阵的不同值。

我应该注意到这些2×2 矩阵不是很好的条件,它们的元素中可能包含全零。如何使用pinv 函数计算G 元素中的伪逆?例如,如何将数组传递给mexCallMATLAB 以计算G 中第一个2×2 矩阵的伪逆?

我想到了以下方法:

mxArray *G_PINV_input     = mxCreateDoubleMatrix(2, 2, mxREAL);
mxArray *G_PINV_output    = mxCreateDoubleMatrix(2, 2, mxREAL);
double  *G_PINV_input_ptr = mxGetPr(G_PINV_input);

memcpy( G_PINV_input_ptr, &G[0], 4 * sizeof(double));
mexCallMATLAB(1, G_PINV_output, 1, G_PINV_input, "pinv");

我不确定这种方法有多好。复制这些值根本不经济,因为在我的实际应用程序中G 中的元素总数很大。反正有没有跳过这个复制?

【问题讨论】:

  • 如果你真的关心性能,你甚至不需要调用MATLAB; 2x2 可逆矩阵的逆矩阵很容易定义:mathworld.wolfram.com/MatrixInverse.html
  • 对于 2x2 矩阵的伪逆矩阵仍然必须有一个封闭的解析形式。您可以使用 Symbolic Math Toolbox 来帮助您找到答案:syms a b c d real; pinv([a b; c d])
  • 或者自己在 C 中为 2×2 矩阵实现 pinv。在命令窗口中输入edit pinv 以查看代码。您只需要实现一个 2×2 SVD,其中有 also analytical forms available
  • @Amro:不幸的是,这不适用于较低秩矩阵(正如 OP 建议他/sh 可能有的那样),例如,[2 0;0 0] 如果以符号方式评估,则返回除以零错误或[NaN NaN;NaN Inf] 如果以数字方式评估结果。 pinv 返回[1/3 0;0 0]
  • 事实是,使用行列式来判断矩阵是否奇异并不是最好的方法(当然是由于数值问题):stackoverflow.com/a/13146750/97160。但是要计算矩阵秩或条件数,我们又不得不计算 SVD!

标签: matlab mex matrix-inverse


【解决方案1】:

这是我对 MEX 函数的实现:

my_pinv.cpp

#include "mex.h"

void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{
    // validate arguments
    if (nrhs!=1 || nlhs>1)
        mexErrMsgIdAndTxt("mex:error", "Wrong number of arguments");
    if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) || mxIsSparse(prhs[0]))
        mexErrMsgIdAndTxt("mex:error", "Input isnt real dense double array");
    if (mxGetNumberOfElements(prhs[0]) != 100)
        mexErrMsgIdAndTxt("mex:error", "numel() != 100");

    // create necessary arrays
    mxArray *rhs[1], *lhs[1];
    plhs[0] = mxCreateDoubleMatrix(100, 1, mxREAL);
    rhs[0] = mxCreateDoubleMatrix(2, 2, mxREAL);
    double *in = mxGetPr(prhs[0]);
    double *out = mxGetPr(plhs[0]);
    double *x = mxGetPr(rhs[0]), *y;

    // for each 2x2 matrix
    for (mwIndex i=0; i<100; i+=4) {
        // copy 2x2 matrix into rhs
        x[0] = in[i+0];
        x[2] = in[i+1];
        x[1] = in[i+2];
        x[3] = in[i+3];

        // lhs = pinv(rhs)
        mexCallMATLAB(1, lhs, 1, rhs, "pinv");

        // copy 2x2 matrix from lhs
        y = mxGetPr(lhs[0]);
        out[i+0] = y[0];
        out[i+1] = y[1];
        out[i+2] = y[2];
        out[i+3] = y[3];

        // free array
        mxDestroyArray(lhs[0]);
    }

    // cleanup
    mxDestroyArray(rhs[0]);
}

这是 MATLAB 中的基线实现,以便我们验证结果是否正确:

my_pinv0.m

function y = my_pinv0(x)
    y = zeros(size(x));
    for i=1:4:numel(x)
        y(i:i+3) = pinv(x([0 1; 2 3]+i));
    end
end

现在我们测试 MEX 函数:

% some vector
x = randn(100,1);

% MEX vs. MATLAB function
y = my_pinv0(x);
yy = my_pinv(x);

% compare
assert(isequal(y,yy))

编辑:

这是另一个实现:

my_pinv2.cpp

#include "mex.h"

inline void call_pinv(const double &a, const double &b, const double &c,
                      const double &d, double *out)
{
    mxArray *rhs[1], *lhs[1];

    // create input matrix [a b; c d]
    rhs[0] = mxCreateDoubleMatrix(2, 2, mxREAL);
    double *x = mxGetPr(rhs[0]);
    x[0] = a;
    x[1] = c;
    x[2] = b;
    x[3] = d;

    // lhs = pinv(rhs)
    mexCallMATLAB(1, lhs, 1, rhs, "pinv");

    // get values from output matrix
    const double *y = mxGetPr(lhs[0]);
    out[0] = y[0];
    out[1] = y[1];
    out[2] = y[2];
    out[3] = y[3];

    // cleanup
    mxDestroyArray(lhs[0]);
    mxDestroyArray(rhs[0]);
}

void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[])
{
    // validate arguments
    if (nrhs!=1 || nlhs>1)
        mexErrMsgIdAndTxt("mex:error", "Wrong number of arguments");
    if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) || mxIsSparse(prhs[0]))
        mexErrMsgIdAndTxt("mex:error", "Input isnt real dense double array");
    if (mxGetNumberOfElements(prhs[0]) != 100)
        mexErrMsgIdAndTxt("mex:error", "numel() != 100");

    // allocate output
    plhs[0] = mxCreateDoubleMatrix(100, 1, mxREAL);
    double *out = mxGetPr(plhs[0]);
    const double *in = mxGetPr(prhs[0]);

    // for each 2x2 matrix
    for (mwIndex i=0; i<100; i+=4) {
        // 2x2 input matrix [a b; c d], and its determinant
        const double a = in[i+0];
        const double b = in[i+1];
        const double c = in[i+2];
        const double d = in[i+3];
        const double det = (a*d - b*c);

        if (det != 0) {
            // inverse of 2x2 matrix [d -b; -c a]/det
            out[i+0] =  d/det;
            out[i+1] = -c/det;
            out[i+2] = -b/det;
            out[i+3] =  a/det;
        }
        else {
            // singular matrix, fallback to pseudo-inverse
            call_pinv(a, b, c, d, &out[i]);
        }
    }
}

这次我们计算2x2矩阵的行列式,如果不为零,我们自己计算逆:

否则我们会退回到从 MATLAB 为 pseudo-inverse 调用 PINV。

这是快速基准测试:

% 100x1 vector
x = randn(100,1);           % average case, with normal 2x2 matrices

% running time
funcs = {@my_pinv0, @my_pinv1, @my_pinv2};
t = cellfun(@(f) timeit(@() f(x)), funcs, 'Uniform',true);

% compare results
y = cellfun(@(f) f(x), funcs, 'Uniform',false);
assert(isequal(y{1},y{2}))

我得到以下时间:

>> fprintf('%.6f\n', t);
0.002111   % MATLAB function
0.001498   % first MEX-file with mexCallMATLAB
0.000010   % second MEX-file with "unrolled" matrix inverse (+ PINV as fallback)

误差在可接受范围内且在机器精度范围内:

>> norm(y{1}-y{3})
ans =
   2.1198e-14

您还可以测试最坏的情况,即许多 2x2 矩阵都是奇异的:

x = randi([0 1], [100 1]);

【讨论】:

  • 是否可以跳过复制并以某种方式直接调用mexCallMATLAB?这种复制其实是个瓶颈。
  • 我认为这有点不可避免。也许您可以将现有数据重用于mexCallMATLAB 的 RHS 输入(通过使用 mxSetPrrhs 指向 &amp;in[i]),但 LHS 输出仍将在每次调用中分配,您必须复制从中提取数据...
  • 也许我们可以使用“混合”方法,如果矩阵是可逆的,我们使用简单的公式自己计算逆矩阵,如果矩阵是奇异矩阵,则回退到从 MATLAB 调用 PINV...等一下,我会发布一些新代码。
【解决方案2】:

您不需要分配输出。只需制作指针,让pinv 自动创建mxArray

mxArray *lhs;

那就用&amp;like,

mexCallMATLAB(1, &lhs, 1, &rhs, "pinv");

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-14
    • 1970-01-01
    • 2014-08-15
    • 2013-05-02
    • 2010-09-18
    • 1970-01-01
    • 2011-04-02
    • 2013-04-15
    相关资源
    最近更新 更多