Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I prevent Matlab from dynamically resizing a pre-allocated array?

Tags:

matlab

For example, in this simple/stupid example:

n = 3;
x = zeros(n, 1);
for ix=1:4
    x(ix) = ix;
end

the array is pre-allocated, but dynamically resized in the loop. Is there a setting in Matlab that will throw an error when dynamic resizing like this occurs? In this example I could trivially rewrite it:

n = 3;
x = zeros(n, 1);
for ix=1:4
    if ix > n
        error('Size:Dynamic', 'Dynamic resizing will occur.')
    end
    x(ix) = ix;
end

but I'm hoping to use this as a check to make sure I've pre-allocated my matrices properly.

like image 417
Michael A Avatar asked Sep 26 '13 20:09

Michael A


4 Answers

You can create a subclass of double and restrict the assignment in subsasgn method:

classdef dbl < double
    methods
        function obj = dbl(d)
            obj = obj@double(d);
        end

        function obj = subsasgn(obj,s,val)
            if strcmp(s.type, '()')
                mx = cellfun(@max, s.subs).*~strcmp(s.subs, ':');
                sz = size(obj);
                nx = numel(mx);
                if nx < numel(sz)
                    sz = [sz(1:nx-1) prod(sz(nx:end))];
                end
                assert(all( mx <= sz), ...
                    'Index exceeds matrix dimensions.');
            end
            obj = subsasgn@double(obj, s, val);
        end

    end
end

So now when you are preallocating use dbl

>> z = dbl(zeros(3))
z = 
  dbl

  double data:
     0     0     0
     0     0     0
     0     0     0
  Methods, Superclasses

All methods for double are now inherited by dbl and you can use it as usual until you assign something to z

>> z(1:2,2:3) = 6
z = 
  dbl

  double data:
     0     6     6
     0     6     6
     0     0     0
  Methods, Superclasses

>> z(1:2,2:5) = 6
Error using dbl/subsasgn (line 9)
Index exceeds matrix dimensions.

I haven't benchmarked it but I expect this to have insignificant performance impact.

If you want the display of the values look normal you can overload the display method as well:

function display(obj)
    display(double(obj));
end

Then

>> z = dbl(zeros(3))
ans =
     0     0     0
     0     0     0
     0     0     0
>> z(1:2,2:3) = 6
ans =
     0     6     6
     0     6     6
     0     0     0
>> z(1:2,2:5) = 6
Error using dbl/subsasgn (line 9)
Index exceeds matrix dimensions.
>> class(z)
ans =
dbl
like image 171
Mohsen Nosratinia Avatar answered Nov 07 '22 23:11

Mohsen Nosratinia


The simplest, most straightforward and robust way I can think of to do this is just by accessing the index before assigning to it. Unfortunately, you cannot overload subsasgn for fundamental types (and it'd be a major headache to do correctly in any case).

for ix=1:4
    x(ix); x(ix) = ix;
end
% Error: 'Attempted to access x(4); index out of bounds because numel(x)=3.'

Alternatively, you could try to be clever and do something with the end keyword... but no matter what you do you'll end up with some sort of nonsensical error message (which the above nicely provides).

for ix=1:4
    x(ix*(ix<=end)) = ix;
end
% Error: 'Attempted to access x(0); index must be a positive integer or logical.'

Or you could do that check in a function, which gains you your nice error message but is still terribly verbose and obfuscated:

for ix=1:4
    x(idxchk(ix,end)) = ix;
end
function idx = idxchk(idx,e)
    assert(idx <= e, 'Size:Dynamic', 'Dynamic resizing will occur.')
end
like image 40
mbauman Avatar answered Nov 08 '22 01:11

mbauman


This is not a fully worked example (see disclaimer after the code!) but it shows one idea...

You could (at least while debugging your code), use the following class in place of zeros to allocate your original variable.

Subsequent use of the data outside of the bounds of the originally allocated size would result in an 'Index exceeds matrix dimensions.' error.

For example:

>> n = 3;
>> x = zeros_debug(n, 1)

x = 

     0
     0
     0

>> x(2) = 32

x = 

     0
    32
     0

>> x(5) = 3
Error using zeros_debug/subsasgn (line 42)
Index exceeds matrix dimensions.

>> 

The class code:

classdef zeros_debug < handle    
    properties (Hidden)
       Data
    end

    methods       
      function obj = zeros_debug(M,N)
          if nargin < 2
              N = M;
          end
          obj.Data = zeros(M,N);
      end

        function sref = subsref(obj,s)
           switch s(1).type
              case '()'
                 if length(s)<2
                 % Note that obj.Data is passed to subsref
                    sref = builtin('subsref',obj.Data,s);
                    return
                 else
                    sref = builtin('subsref',obj,s);
                 end              
               otherwise,
                 error('zeros_debug:subsref',...
                   'Not a supported subscripted reference')
           end 
        end        
        function obj = subsasgn(obj,s,val)
           if isempty(s) && strcmp(class(val),'zeros_debug')
              obj = zeros_debug(val.Data);
           end
           switch s(1).type
               case '.'
                    obj = builtin('subsasgn',obj,s,val);
              case '()'
                    if strcmp(class(val),'double')                        
                        switch length(s(1).subs{1}) 
                            case 1,
                               if s(1).subs{1} > length(obj.Data)
                                   error('zeros_debug:subsasgn','Index exceeds matrix dimensions.');
                               end
                            case 2,                            
                               if s(1).subs{1} > size(obj.Data,1) || ...
                                       s(1).subs{2} > size(obj.Data,2) 
                                   error('zeros_debug:subsasgn','Index exceeds matrix dimensions.');
                               end                            
                        end
                        snew = substruct('.','Data','()',s(1).subs(:));
                             obj = subsasgn(obj,snew,val);
                    end
               otherwise,
                 error('zeros_debug:subsasgn',...
                    'Not a supported subscripted assignment')
           end     
        end        
        function disp( obj )
            disp(obj.Data);
        end        
    end   
end

There would be considerable performance implications (and problems stemming from using a class inheriting from handle) but it seemed like an interesting solution to the original problem.

like image 21
grantnz Avatar answered Nov 08 '22 00:11

grantnz


Allowing assignment to indices outside of an array's bounds and filling the gaps with zeros is indeed one of Matlab's ugly parts. I am not aware of any simple tricks without an explicit check to avoid that, other than implementing your own storage class. I would stick to adding a simple assert(i <= n) to your loop and forget about it. I have never been bitten by hard-to-find bugs due to assigning something out of bounds.

In case of a forgotten or too small preallocation, in the 'ideal' case your code gets really slow due to quadratic behavior, after which you find the bug and fix it. But these days, Matlab's JIT is sometimes smart enough to not cause any slowdowns (maybe it dynamically grows arrays in some cases, like python's list), so it might not even be an issue anymore. So it actually allows for some sloppier coding ...

like image 25
Bas Swinckels Avatar answered Nov 07 '22 23:11

Bas Swinckels