2

I have a matrix named l having size 20X3. What I wanted to do was this : Suppose I have this limits:

l1_max=20; l1_min=0.5;
l2_max=20; l2_min=0.5;
mu_max=20; mu_min=0.5;

I wanted to force all the elements of the matrix l within the limits. The values of 1st column within l1_max & l1_min. The values of 2nd column within l2_max & l2_min. The values of 3rd column within mu_max & mu_min.

What I did was like this:

for k=1:20
    if l(k,1)>l1_max 
        l(k,1) = l1_max;
    elseif l(k,1)<l1_min
        l(k,1) = l1_min;
    end

    if l(k,2)>l2_max 
        l(k,2) = l2_max;
    elseif l(k,2)<l2_min
        l(k,2) = l2_min;
    end

    if l(k,3)>mu_max 
        l(k,3) = mu_max;
    elseif l(k,3)<mu_min
        l(k,3) = mu_min;
    end
end

Can it be done in a better way ?

Amro
  • 123,847
  • 25
  • 243
  • 454
roni
  • 1,443
  • 3
  • 28
  • 49

3 Answers3

4

You don't have to loop over rows, use vectorized operations on entire columns:

l(l(:, 1) > l1_max, 1) = l1_max;
l(l(:, 1) < l1_min, 1) = l1_min;

Similarily:

l(l(:, 2) > l2_max, 2) = l2_max;
l(l(:, 2) < l2_min, 2) = l2_min;
l(l(:, 3) > l2_max, 3) = mu_max;
l(l(:, 3) < l2_min, 3) = mu_min;

An alternative method, which resembles to Bas' idea, is to apply min and max as follows:

l(:, 1) = max(min(l(:, 1), l1_max), l1_min);
l(:, 2) = max(min(l(:, 2), l2_max), l2_min);
l(:, 3) = max(min(l(:, 3), mu_max), mu_min);

It appears that both approaches have comparable performance.

Eitan T
  • 32,660
  • 14
  • 72
  • 109
  • 1
    Than what, a loop? Probably. MATLAB is optimized especially for such operations. – Eitan T Aug 13 '13 at 06:15
  • Thanks. I too wanted to use logical indexing. – roni Aug 13 '13 at 06:16
  • 1
    My benchmarking shows that the OP's solution is 5 times faster see https://gist.github.com/tuix/6218612 – Mohsen Nosratinia Aug 13 '13 at 07:18
  • 1
    @MohsenNosratinia When I run your benchmark it shows that the loop is 10 times slower – Eitan T Aug 13 '13 at 07:32
  • 1
    On mine (2011b) - loop is faster by about 5 times when using L = randn(20,3); faster by only about 2 when using L = randn(100,3); only slightly faster for L = randn(2000,3), and loop becomes slower at L = randn(20000,3). – nkjt Aug 13 '13 at 09:36
  • @nkjt I ran this on R2013a (Windows Server on x64, 24 cores). I wonder if that has anything to do with it. – Eitan T Aug 13 '13 at 09:43
  • Could you try to also benchmark the version with `bsxfun` I posted below? Thanks – Bas Swinckels Aug 13 '13 at 12:03
  • @MohsenNosratinia, @ nkjt I can only speculate that your JIT underperformed in the vectorized version, and branch prediction accelerated the loop by a factor of 10 (might be related to [this](http://stackoverflow.com/questions/13382155/is-indexing-vectors-in-matlab-inefficient/)). You do agree though that it is absurd that MATLAB explicit loops outperform logical indexing, right? – Eitan T Aug 14 '13 at 06:39
  • Thanks for your answer. I need to use the bsxfun in slightly different way. Could you check the connected question at : http://stackoverflow.com/questions/18296486/forcing-limits-on-a-vector-in-matlab. I need to use the bsxfun to force different set of limits on different parts of the column matrix or vector. Which method would be the faster in this regard? – roni Aug 19 '13 at 05:41
2

You don't even have to loop over all columns, the operation on the whole matrix can be done in 2 calls to bsxfun, independent of the number of columns:

column_max = [l1_max, l2_max, mu_max];
column_min = [l1_min, l2_min, mu_min];

M = bsxfun(@min, M, column_max); %clip to maximum
M = bsxfun(@max, M, column_min); %clip to minimum

This uses two tricks: to clip a value between min_val and max_val, you can do clipped_x = min(max(x, min_val), max_val). The other trick is to use the somewhat obscure bsxfun, which applies a function after doing singleton expansion. When you use it on two matrices, it 'extrudes' the smallest one to the same size as the largest one before applying the function, so the example above is equivalent to M = min(M, repmat(column_max, size(M, 1), 1)), but hopefully calculated in a more efficient way.

Bas Swinckels
  • 18,095
  • 3
  • 45
  • 62
  • 1
    Did some quick benchmarking myself. For 20 rows, roni's original solution is the fastest. Between 200 and 2000 rows, Eitan's solution seems faster, while above 10000 rows or so, my solution is the fastest. Searching around a bit, more people are complaining that `bsxfun` is a bit slow. – Bas Swinckels Aug 13 '13 at 14:28
  • 1
    @EitanT: I posted a benchmark you can test yourself, `bsxfun` is indeed the fastest – Amro Aug 18 '13 at 08:48
  • @Amro thanks! Indeed this can make one wonder what redundant operations logical indexing does that `bsxfun` doesn't... – Eitan T Aug 18 '13 at 09:28
  • @EitanT Logical indexing creates intermediate logical arrays. I think this array creation is what costs time, especially when your matrices are big. – Bas Swinckels Aug 18 '13 at 15:27
  • Thanks for your answer. I need to use the bsxfun in slightly different way. Could you check the connected question at : http://stackoverflow.com/questions/18296486/forcing-limits-on-a-vector-in-matlab. I need to use the bsxfun to force different set of limits on different parts of the column matrix or vector. Which method would be the faster in this regard? – roni Aug 19 '13 at 05:41
1

Below is a benchmark to test the various methods discussed so far. I'm using the TIMEIT function found on the File Exchange.

function [t,v] = testClampColumns()
    % data and limits ranges for each column
    r = 10000; c = 500;
    M = randn(r,c);
    mn = -1.1 * ones(1,c);
    mx = +1.1 * ones(1,c);

    % functions
    f = { ...
        @() clamp1(M,mn,mx) ;
        @() clamp2(M,mn,mx) ;
        @() clamp3(M,mn,mx) ;
        @() clamp4(M,mn,mx) ;
        @() clamp5(M,mn,mx) ;
    };

    % timeit and check results
    t = cellfun(@timeit, f, 'UniformOutput',true);
    v = cellfun(@feval, f, 'UniformOutput',false);
    assert(isequal(v{:}))
end

Given the following implementations:

1) loop over all values and compare against min/max

function M = clamp1(M, mn, mx)
    for j=1:size(M,2)
        for i=1:size(M,1)
            if M(i,j) > mx(j)
                M(i,j) = mx(j);
            elseif M(i,j) < mn(j)
                M(i,j) = mn(j);
            end
        end
    end
end

2) compare each column against min/max

function M = clamp2(M, mn, mx)
    for j=1:size(M,2)
        M(M(:,j) < mn(j), j) = mn(j);
        M(M(:,j) > mx(j), j) = mx(j);
    end
end

3) truncate each columns to limits

function M = clamp3(M, mn, mx)
    for j=1:size(M,2)
        M(:,j) = min(max(M(:,j), mn(j)), mx(j));
    end
end

4) vectorized version of truncation in (3)

function M = clamp4(M, mn, mx)
    M = bsxfun(@min, bsxfun(@max, M, mn), mx);
end

5) absolute value comparison: -a < x < a <==> |x| < a

(Note: this is not applicable to your case, since it requires a symmetric limits range. I only included this for completeness. Besides it turns out to be the slowest method.)

function M = clamp5(M, mn, mx)
    assert(isequal(-mn,mx), 'Only works when -mn==mx')
    idx = bsxfun(@gt, abs(M), mx);
    v = bsxfun(@times, sign(M), mx);
    M(idx) = v(idx);
end

The timing I get on my machine with an input matrix of size 10000x500:

>> t = testClampColumns
t =
    0.2424
    0.1267
    0.0569
    0.0409
    0.2868

I would say that all the above methods are acceptably fast enough, with the bsxfun solution being the fastest :)

Amro
  • 123,847
  • 25
  • 243
  • 454
  • Thanks for your answer. I need to use the bsxfun in slightly different way. Could you check the connected question at : http://stackoverflow.com/questions/18296486/forcing-limits-on-a-vector-in-matlab. I need to use the bsxfun to force different set of limits on different parts of the column matrix or vector. Which method would be the faster in this regard? – roni Aug 19 '13 at 05:39