2

I am writing a MEX code in which I need to use pinv function. I am trying to find a way to pass the array of type double to pinv using mexCallMATLAB in the most efficient way. Let's for the sake of example say the array is named G and its size is 100.

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

where

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

Which means every four consecutive elements of G is a 2×2 matrix. G stores 25 different values of this 2×2 matrix.

enter image description here

I should note that these 2×2 matrices are not well-conditioned and they may contain all zero in their element. How can I use pinv function to calculate the pseudoinverse in the elements of G? For example, how can I pass the array to mexCallMATLAB in order to calculate the pseudoinverse of the first 2×2 matrix in G?

I thought of the following approach:

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");

I am not sure how good this approach is. Copying the values is not economical at all because the total number of elements in G in my actual application is large. Is there anyway to skip this copying?

afp_2008
  • 1,940
  • 1
  • 19
  • 46
  • 2
    if you really care about performance, you don't even need to call MATLAB; the inverse of a 2x2 invertible matrix is easily defined: http://mathworld.wolfram.com/MatrixInverse.html – Amro Nov 06 '14 at 00:35
  • I should note that these `2×2` matrices are not well-conditioned and they may contain all zero in their element. – afp_2008 Nov 06 '14 at 00:38
  • 2
    still there's gotta be a closed analytical form to the pseudo-inverse of a 2x2 matrix. You could use Symbolic Math Toolbox to help you find the answer: `syms a b c d real; pinv([a b; c d])` – Amro Nov 06 '14 at 00:40
  • 1
    Or just implement `pinv` in C for 2-by-2 matrices yourself. Type `edit pinv` in the command window to see the code. You'd just need implement a 2-by-2 SVD, for which there are [also analytical forms available](http://en.wikipedia.org/wiki/Singular_value_decomposition#Analytic_result_of_2_.C3.97_2_SVD). – horchler Nov 06 '14 at 02:59
  • 1
    @Amro: Unfortunately, that doesn't work for a lower rank matrix (as the OP suggests he/sh might have), e.g., `[2 0;0 0]` returns a division by zero error if evaluated symbolically or `[NaN NaN;NaN Inf]` if the result is evaluated numerically. `pinv` returns `[1/3 0;0 0]`. – horchler Nov 06 '14 at 03:06
  • 1
    @A2009: ...or use [`coder`](http://www.mathworks.com/products/matlab-coder/)/[`codegen`](http://www.mathworks.com/help/coder/ref/codegen.html) to just compile `pinv` directly to C and be done with it. – horchler Nov 06 '14 at 03:13
  • @horchler: I added a new version. We do the manual computation if the matrix is invertible, otherwise we fallback to calling PINV. – Amro Nov 06 '14 at 03:35
  • What would be the best threshold to say the matrix is singular? – afp_2008 Nov 06 '14 at 03:40
  • 2
    the truth is that using the determinant to tell if a matrix is singular is not the best way (due to numerical issues of course): http://stackoverflow.com/a/13146750/97160. But to compute matrix rank or conditioning number, we go back to having to compute the SVD! – Amro Nov 06 '14 at 03:43
  • 1
    So I say stick with calling PINV using `mexCallMATLAB`, or call LAPACK routines yourself from the C code. – Amro Nov 06 '14 at 03:50
  • @Amro: Using LAPACK is a great idea. I had thought about it at first but my understanding was I should install other libraries to be able to get the pseudoinverse and I prefer not to. Could you please give me some directions or links how I can do that using LAPACK? I would really appreciate it. – afp_2008 Nov 06 '14 at 03:58
  • 1
    @A2009: PINV is basically doing SVD decomposition (see the code yourself `edit pinv.m`). With that, check out another answer of mine showing how to do SVD using LAPACK: http://stackoverflow.com/a/6440839/97160 (the second part of the post) – Amro Nov 06 '14 at 04:01

2 Answers2

2

Here is my implementation of the MEX-function:

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]);
}

Here is a baseline implementation in MATLAB so that we can verify the results are correct:

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

Now we test the MEX-function:

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

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

% compare
assert(isequal(y,yy))

EDIT:

Here is an another implementation:

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]);
        }
    }
}

This time we compute the determinant of the 2x2 matrix, if is non-zero, we calculate the inverse ourselves according to:

2x2_matrix_inverse

Otherwise we fallback to invoking PINV from MATLAB for the pseudo-inverse.

Here is quick benchmark:

% 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}))

I get the following timings:

>> 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)

The error is acceptable and within machine precision:

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

You could also test the worst case, when many of the 2x2 matrices are singular:

x = randi([0 1], [100 1]);
Amro
  • 123,847
  • 25
  • 243
  • 454
  • Would it be possible to skip the copying and somehow directly call `mexCallMATLAB`? This copying is actually a bottleneck. – afp_2008 Nov 06 '14 at 01:36
  • I think it's somewhat unavoidable. Perhaps you can reuse existing data for the RHS input of `mexCallMATLAB` (by using `mxSetPr` to point `rhs` towards `&in[i]`), but the LHS output will still be allocated in each call, and you'd have to copy data out of it... – Amro Nov 06 '14 at 03:11
  • maybe we can use a "hybrid" approach, where we compute the inverse ourselves using the simple formula if the matrix is invertible, and fallback to calling PINV from MATLAB if the matrix is singular... Give me a minute, I'll post some new code. – Amro Nov 06 '14 at 03:12
0

You don't need to allocate the output. Just make the pointer and let pinv create the mxArray automatically.

mxArray *lhs;

Then just use & like,

mexCallMATLAB(1, &lhs, 1, &rhs, "pinv");
chappjc
  • 30,359
  • 6
  • 75
  • 132