Downsampling and Convolution: When Can one Swap Between Them? (Code snippet with example below).

In MATLAB, assume I have a long signal vector y (length N) that I wish to convolve with a square wave h (consisting of H 1's). This would give a convolution result of length N+H-1. 

Since I have to perform this operation quite often, I thought that I could speed up my code by downsampling the signal y (and also the square wave h) and then perform the convolution. The downsampled signals are referred to as y_ds and h_ds.

However, the result is shifted when compared to the downsampled version of the convolution result (if corrected for scaling differences). So in general I found that:


  conv(y_ds,h_ds) $\neq$ downsample(conv(y,h))


However, the shift is between the two results is not constant, i.e. shifting such that the maxima align, causes the edges to be different.

My questions


Where does this shift originate from? 
How can this code be modified such that the downsampled convolution result equals the convolution result of the downsampled signals?


Kind regards

N = 100;
dsfactor = 3;
H = dsfactor*3;
x = linspace(0,10,N);
y = sind(18*x);
h = ones(H,1);

convyh = conv(y,h);

convyh_ds = downsample(convyh,dsfactor)./H;
convyh_ds3 = conv(downsample(y,dsfactor),downsample(h,dsfactor))./(ceil(H/dsfactor));

figure;
plot(convyh_ds,'DisplayName','downsample(conv)','LineWidth',1);
hold on;
plot(convyh_ds3,'DisplayName','conv(downsample)','LineWidth',1);
grid minor
legend('show');
line([0 length(convyh_ds2)],[0 0],'LineStyle','--');
I thought this was because you were getting aliasing in your signal during one of the downsampling operations. However, I re-wrote your code in R (included below) and I did not see the same effect  I suspect conv is doing something odd. You may want to look at using the shape parameter and see if that makes a difference.

Below are four plots of different variants of yours:


Top left: Yours.
Top right: a variation that uses slightly different x values.
Bottom left: Yours at twice the frequency.
Bottom right: a variation that uses slightly different x values at twice the frequency.


As you can see, in all four examples the red and the black overlay exactly.



Yes, in general, these will not be the same. Below is an example with a sawtooth wave, and the two do not line up.



Here is the specific example you ask about with the triangle wave.





R Code Only Below

#43794
#N = 100;
N &lt;- 100
#dsfactor = 3;
dsfactor &lt;- 3
#H = dsfactor*3;
H &lt;- dsfactor * 3
#x = linspace(0,10,N);
x1 &lt;- seq(0,10,10/(N-1))
x2 &lt;- seq(0,10*(N-1)/N,10/N)
#y = sind(18*x);
y1 &lt;- sin(18*x1/180*pi)
y2 &lt;- sin(18*x2/180*pi)
y3 &lt;- sin(36*x1/180*pi)
y4 &lt;- sin(36*x2/180*pi)
#h = ones(H,1);
h &lt;- rep(1,H)

#convyh = conv(y,h);
convy1h &lt;- filter(y1,h)
convy2h &lt;- filter(y2,h)
convy3h &lt;- filter(y3,h)
convy4h &lt;- filter(y4,h)

downsample &lt;- function(signal, factor) 
{
  return(signal[seq(1,length(signal), factor)])
}

#convyh_ds = downsample(convyh,dsfactor)./H;
convy1h_ds &lt;- downsample(convy1h, dsfactor)/H
convy2h_ds &lt;- downsample(convy2h, dsfactor)/H
convy3h_ds &lt;- downsample(convy3h, dsfactor)/H
convy4h_ds &lt;- downsample(convy4h, dsfactor)/H
#convyh_ds3 = conv(downsample(y,dsfactor),downsample(h,dsfactor))./(ceil(H/dsfactor));
convy1h_ds3 &lt;- filter(downsample(y1,dsfactor), downsample(h,dsfactor))/ceiling(H/dsfactor)
convy2h_ds3 &lt;- filter(downsample(y2,dsfactor), downsample(h,dsfactor))/ceiling(H/dsfactor)
convy3h_ds3 &lt;- filter(downsample(y3,dsfactor), downsample(h,dsfactor))/ceiling(H/dsfactor)
convy4h_ds3 &lt;- filter(downsample(y4,dsfactor), downsample(h,dsfactor))/ceiling(H/dsfactor)
#figure;
#plot(convyh_ds,'DisplayName','downsample(conv)','LineWidth',1);
#hold on;
#plot(convyh_ds3,'DisplayName','conv(downsample)','LineWidth',1);
#grid minor
#legend('show');
#line([0 length(convyh_ds2)],[0 0],'LineStyle','--');

par(mfrow=c(2,2))
plot(convy1h_ds)
lines(convy1h_ds3, col='red')

plot(convy2h_ds)
lines(convy2h_ds3, col='red')

plot(convy3h_ds)
lines(convy3h_ds3, col='red')

plot(convy4h_ds)
lines(convy4h_ds3, col='red')