开发者

Can this Matlab function be vectorized (or made to run faster by other another method)?

The main problem is finding values at a fixed offset from the current value. My current method is very slow when the Value vector is large (typically 100000's of elements).

function [ AverageValue ] = CalculateAverageValueOverAngle( Value, Angle )
% function [ AverageValue ] = CalculateAverageValueOverAngle( Value, Angle )
%   Calculate average value from instantaneous valu开发者_如何学Ce and angle
%   Average value is calculated over +- 90 degrees from current angle

   AverageValue = zeros( size( Value ) );
   UnwrappedRadians = unwrap( Angle ./ 180 * pi );

   for i=1:length(UnwrappedRadians)
       mid = UnwrappedRadians(i);
       start = find( UnwrappedRadians(1:i) < (mid - pi/2), 1, 'Last');
       finish = find( UnwrappedRadians(i:end) > (mid + pi/2), 1, 'First');
       if isempty(start) | isempty(finish)
           AverageValue(i) = Value(i); 
        else
           AverageValue(i) = mean(Value(start:finish+i-1));  % nanmean
        end
    end    
end


A minor refactoring will save the second find in instances where you don't find results, and preallocating with AverageValue with Value saves the else part.

UnwrappedRadians = unwrap( Angle ./ 180 * pi);
AverageValue = Value;

for i=1:length(UnwrappedRadians)
    mid = UnwrappedRadians(i);
    start = find( UnwrappedRadians(1:i) < (mid - pi/2), 1, 'Last');
    if ~isempty(start)
        finish = find( UnwrappedRadians(i:end) > (mid + pi/2), 1, 'First');
        if ~isempty(finish)
            AverageValue(i) = mean(Value(start:finish+i-1));  % nanmean
        end
    end
end

If you find that the calculation of finish is empty more often than the calculation of start, you can switch their order so that the finish check is done first.

It's not clear if UnwrappedRadians will always be sorted. If so, you can reuse the results from earlier finds to reduce the size of the subvector you search over. For instance, if the you look for the last 1 between 11 and the end of the vector and it is element 23, when you search for 1.1, you can reduce the search to between 24 and the end of the vector. I've found that this technique can yield really big increases in speed.

Vectorization is difficult in cases like this, because you are using the indexing variable (i) as an index again (in the find statements). It is possible to rig something up using arrayfun, but it is highly unlikely to be faster (I would guess slower, actually) and will definitely be less readable than what you have.


MatlabSorter has provided a refactoring which makes sense to me, so if your code really does what you want, his refactoring is the way forward :-). Note that in my tests with numel(Angle)=50000, his refactoring did not save much (probably because my sample data assumed that the find()s will almost never fail except at the very beginning and at the end of your data trace).

However, while looking at your code, I was wondering: Are you sure you absolutely want to average all values from the first time the angle gets into the mid-pi/2...mid+pi/2 range, until the last time it leaves that range? If your unwrapped Angles are non-monotonous (for example if backwards movements are allowed, if the sampling rate is too low to avoid aliasing, or simply due to measurement noise in the angle) then you will also be averaging over some values outside (and possibly well outside) the 180° range.

Note that in any case the first measurement (Value(start)) you average over is always more than pi/2 before your "mid" angle (you start with the last angle before the interval), while your last measurement (Value(finish+i-1)) is always more than pi/2 behind mid. So your effective range you average over is always larger than pi, even if data values at exactly mid-pi/2 and mid+pi/2 are available... is that really intended?

So in case you are really interested in averaging only Values where Angle is less than pi/2 from mid, here is my code suggestion, which sadly has only marginally quicker runtime than what you currently use. Note that this is NOT a refactoring, because it acts differently from your code in the two ways described above.

UnwrappedRadians = unwrap( Angle ./ 180 * pi);
AverageValue = Value;
avgstart=find( UnwrappedRadians > (UnwrappedRadians(1) + pi/2), 2, 'First');
avgend=find( UnwrappedRadians < (UnwrappedRadians(end) - pi/2), 1, 'Last');
for i=avgstart:avgend
    AverageValue(i) = mean(Value(abs(UnwrappedRadians-UnwrappedRadians(i)) <= pi/2));  % nanmean
end
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜