x.y.z...
x.y.z...

Reputation: 269

Compute double sum in matlab efficiently?

I am looking for an optimal way to program this summation ratio. As input I have two vectors v_mn and x_mn with (M*N)x1 elements each.

The ratio is of the form:

formula

The vector x_mn is 0-1 vector so when x_mn=1, the ration is r given above and when x_mn=0 the ratio is 0.

The vector v_mn is a vector which contain real numbers.

I did the denominator like this but it takes a lot of times.

function r_ij = denominator(v_mn, M, N, i, j)
%here x_ij=1, to get r_ij.
S = [];
for m = 1:M
  for n = 1:N
    if (m ~= i)
      if (n ~= j)
        S = [S v_mn(i, n)];
      else
        S = [S 0];
      end
    else
      S = [S 0];
    end
  end
end
r_ij = 1+S;
end

Can you give a good way to do it in matlab. You can ignore the ratio and give me the denominator which is more complicated.

EDIT: I am sorry I did not write it very good. The i and j are some numbers between 1..M and 1..N respectively. As you can see, the ratio r is many values (M*N values). So I calculated only the value i and j. More precisely, I supposed x_ij=1. Also, I convert the vectors v_mn into a matrix that's why I use double index.

Upvotes: 4

Views: 1109

Answers (4)

mad_scientist_kyouma
mad_scientist_kyouma

Reputation: 71

You can vectorize from within Matlab to speed up your calculations. Every time you use an operation like ".^" or ".*" or any matrix operation for that matter, Matlab will do them in parallel, which is much, much faster than iterating over each item.

In this case, look at what you are doing in terms of matrices. First, in your loop you are only dealing with the mth row of $V_{nm}$, which we can use as a vector for itself.

If you look at your formula carefully, you can figure out that you almost get there if you just write this row vector as a column vector and multiply the matrix $X_{nm}$ to it from the left, using standard matrix multiplication. The resulting vector contains the sums over all n. To get the final result, just sum up this vector.

function result = denominator_vectorized(V,X,m,n)

% get the part of V with the first index m
Vm = V(m,:)';
% remove the parts of X you don't want to iterate over. Note that, since I
% am inside the function, I am only editing the value of X within the scope
% of this function.
X(m,:) = 0;
X(:,n) = 0;

%do the matrix multiplication and the summation at once
result = 1-sum(X*Vm);

To show you how this optimizes your operation, I will compare it to the code proposed by another commenter:

function result = denominator(V,X,m,n)

% use the size of V to determine M and N
[M,N] = size(V);

% initialize the summed value to one (to account for one at the end)
result = 1;

% outer loop
for i=1:M
% ignore the case where m==i
if i~=m
    for j=1:N
        % ignore the case where n==j
        if j~=n
            result = result + V(m,j)*X(i,j);
        end
     end
 end
end

The test:

V=rand(10000,10000);
X=rand(10000,10000);
disp('looped version')
tic
denominator(V,X,1,1)
toc
disp('matrix operation')
tic
denominator_vectorized(V,X,1,1)
toc

The result:

looped version

ans =

   2.5197e+07

Elapsed time is 4.648021 seconds.
matrix operation

ans =

   2.5197e+07

Elapsed time is 0.563072 seconds.

That is almost ten times the speed of the loop iteration. So, always look out for possible matrix operations in your code. If you have the Parallel Computing Toolbox installed and a CUDA-enabled graphics card installed, Matlab will even perform these operations on your graphics card without any further effort on your part!

EDIT: That last bit is not entirely true. You still need to take a few steps to do operations on CUDA hardware, but they aren't a lot. See Matlab documentation.

Upvotes: 3

Rody Oldenhuis
Rody Oldenhuis

Reputation: 38032

If you reshape your data, your summation is just a repeated matrix/vector multiplication.

Here's an implementation for a single m and n, along with a simple speed/equality test:

clc

%# some arbitrary test parameters
M = 250;
N = 1000;
v = rand(M,N);   %# (you call it v_mn)
x = rand(M,N);   %# (you call it x_mn)

m0 = randi(M,1); %# m of interest
n0 = randi(N,1); %# n of interest 


%# "Naive" version
tic
S1 = 0;
for mm = 1:M %# (you call this m')
    if mm == m0, continue; end
    for nn = 1:N %# (you call this n')
        if nn == n0, continue; end
        S1 = S1 + v(m0,nn) * x(mm,nn);
    end
end
r1 = v(m0,n0)*x(m0,n0) / (1+S1);
toc


%# MATLAB version: use matrix multiplication!
tic

ninds = [1:m0-1 m0+1:M];
minds = [1:n0-1 n0+1:N];
S2 = sum( x(minds, ninds) * v(m0, ninds).' );
r2 = v(m0,n0)*x(m0,n0) / (1+S2);

toc


%# Test if values are equal
abs(r1-r2) < 1e-12

Outputs on my machine:

Elapsed time is 0.327004 seconds.   %# loop-version
Elapsed time is 0.002455 seconds.   %# version with matrix multiplication
ans =  
     1                              %# and yes, both are equal

So the speedup is ~133×

Now that's for a single value of m and n. To do this for all values of m and n, you can use an (optimized) double loop around it:

r = zeros(M,N);
for m0 = 1:M   
    xx = x([1:m0-1 m0+1:M], :);
    vv = v(m0,:).';
    for n0 = 1:N
        ninds    = [1:n0-1 n0+1:N];        
        denom    = 1 + sum( xx(:,ninds) * vv(ninds) );
        r(m0,n0) = v(m0,n0)*x(m0,n0)/denom;        
    end
end

which completes in ~15 seconds on my PC for M = 250, N= 1000 (R2010a).

EDIT: actually, with a little more thought, I was able to reduce it all down to this:

denom = zeros(M,N);
for mm = 1:M    
    xx = x([1:mm-1 mm+1:M],:);
    denom(mm,:) = sum( xx*v(mm,:).' ) - sum( bsxfun(@times, xx, v(mm,:)) );    
end
denom = denom + 1;

r_mn = x.*v./denom;

which completes in less than 1 second for N = 250 and M = 1000 :)

Upvotes: 5

Geoff
Geoff

Reputation: 1603

Rather than first jumping into vectorization of the double loop, you may want modify the above to make sure that it does what you want. In this code, there is no summing of the data, instead a vector S is being resized at each iteration. As well, the signature could include the matrices V and X so that the multiplication occurs as in the formula (rather than just relying on the value of X to be zero or one, let us pass that matrix in).

The function could look more like the following (I've replaced the i,j inputs with m,n to be more like the equation):

function result = denominator(V,X,m,n)

% use the size of V to determine M and N
[M,N] = size(V);

% initialize the summed value to one (to account for one at the end)
result = 1;

% outer loop
for i=1:M
    % ignore the case where m==i
    if i~=m
        for j=1:N
            % ignore the case where n==j
            if j~=n
                result = result + V(m,j)*X(i,j);
            end
         end
     end
 end

Note how the first if is outside of the inner for loop since it does not depend on j. Try the above and see what happens!

Upvotes: 3

Fantastic Mr Fox
Fantastic Mr Fox

Reputation: 33864

For a start you need to pre-alocate your S matrix. It changes size every loop so put

S = zeros(m*n, 1) 

at the start of your function. This will also allow you to do away with your else conditional statements, ie they will reduce to this:

if (m ~= i)
   if (n ~= j)
      S(m*M + n) = v_mn(i, n);

Otherwise since you have to visit every element im afraid it may not be able to get much faster.

If you desperately need more speed you can look into doing some mex coding which is code in c/c++ but run in matlab.

http://www.mathworks.com.au/help/matlab/matlab_external/introducing-mex-files.html

Upvotes: 3

Related Questions