Zero-padding of `ifft` does not work as expected

avitase: <p>Hi:)</p>
<p>I want to calculate the inverse FFT of a truncated input vector,</p>
<pre><code class="lang-auto">Python 3.8.10 (default, May 19 2021, 18:05:58)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt; import torch
&gt;&gt;&gt; x = torch.rand(5)
&gt;&gt;&gt; fx = torch.fft.fft(x)
&gt;&gt;&gt; fx
tensor([3.2079+0.0000j, 0.3252+0.3854j, 0.1552+0.6282j, 0.1552-0.6282j, 0.3252-0.3854j])
&gt;&gt;&gt; fx[2] = 0.
&gt;&gt;&gt; fx[3] = 0.
&gt;&gt;&gt; fx
tensor([3.2079+0.0000j, 0.3252+0.3854j, 0.0000+0.0000j, 0.0000+0.0000j, 0.3252-0.3854j])
&gt;&gt;&gt; torch.fft.ifft(fx)
tensor([0.7717+0.j, 0.5352+0.j, 0.4457+0.j, 0.6269+0.j, 0.8284+0.j])
</code></pre>
<p>Note that the truncation affects the highest frequency modes and since both, <code>fx[2]</code> and <code>fx[3]</code>, are set to zero the result of <code>ifft</code> is real-valued as expected.</p>
<p>Naively, I would expect that <code>torch.fft.ifft</code> gives the very same result when the frequency components are truncated rather than set to zero,</p>
<pre><code class="lang-auto">&gt;&gt;&gt; torch.fft.ifft(torch.tensor([fx[0], fx[1], fx[4]]), n=5)
tensor([0.7717+0.0000j, 0.5811+0.1863j, 0.4905-0.1098j, 0.7277-0.0625j, 0.6370-0.0139j])
</code></pre>
<p>but the result is now different and complex.<br>
Obviously, I am missing something, probably, I misunderstand the parameter <code>n</code>?</p>
<p>Any ideas?</p>

KFrank: <p>Hi Avitase!</p>
<aside class="quote no-group quote-modified" data-username="avitase" data-post="1" data-topic="125710" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/a/94ad74/48.png" class="avatar"> avitase:</div>
<blockquote>
<pre><code class="lang-auto">&gt;&gt;&gt; fx
tensor([3.2079+0.0000j, 0.3252+0.3854j, 0.0000+0.0000j, 0.0000+0.0000j, 0.3252-0.3854j])
&gt;&gt;&gt; torch.fft.ifft(fx)
tensor([0.7717+0.j, 0.5352+0.j, 0.4457+0.j, 0.6269+0.j, 0.8284+0.j])
</code></pre>
<pre><code class="lang-auto">&gt;&gt;&gt; torch.fft.ifft(torch.tensor([fx[0], fx[1], fx[4]]), n=5)
tensor([0.7717+0.0000j, 0.5811+0.1863j, 0.4905-0.1098j, 0.7277-0.0625j, 0.6370-0.0139j])
</code></pre>
<p>but the result is now different and complex.</p>
</blockquote>
</aside>
<p>Pytorch’s <code>fft</code> functions, including <code>ifft()</code>, pad by simply appending zeros<br>
to the end of the input vector – they don’t insert zeros into the middle.</p>
<p>You can get the behavior I believe you want by using <code>rfft()</code> / <code>irfft()</code>.<br>
The following illustrates these two points:</p>
<pre><code class="lang-plaintext">&gt;&gt;&gt; import torch
&gt;&gt;&gt; torch.__version__
'1.9.0'
&gt;&gt;&gt; x = torch.arange (1, 6).float()**(1.0 / 3.0)
&gt;&gt;&gt; x
tensor([1.0000, 1.2599, 1.4422, 1.5874, 1.7100])
&gt;&gt;&gt; fx = torch.fft.fft (x)
&gt;&gt;&gt; fx
tensor([ 6.9995+0.0000j, -0.5333+0.5133j, -0.4665+0.1265j, -0.4665-0.1265j,
        -0.5333-0.5133j])
&gt;&gt;&gt; torch.fft.ifft (torch.tensor ([fx[0], fx[1], 0.0, 0.0, fx[4]]))
tensor([1.1866+0.j, 1.1387+0.j, 1.4518+0.j, 1.6932+0.j, 1.5293+0.j])
&gt;&gt;&gt; torch.fft.ifft (torch.tensor ([fx[0], fx[1], fx[4], 0.0, 0.0]))
tensor([1.1866+0.0000j, 1.4159-0.0493j, 1.2952-0.0760j, 1.6112-0.1535j,
        1.4905+0.2789j])
&gt;&gt;&gt; torch.fft.ifft (torch.tensor ([fx[0], fx[1], fx[4]]), n = 5)
tensor([1.1866+0.0000j, 1.4159-0.0493j, 1.2952-0.0760j, 1.6112-0.1535j,
        1.4905+0.2789j])
&gt;&gt;&gt; rfx = torch.fft.rfft (x)
&gt;&gt;&gt; rfx
tensor([ 6.9995+0.0000j, -0.5333+0.5133j, -0.4665+0.1265j])
&gt;&gt;&gt; torch.fft.irfft (torch.tensor ([rfx[0], rfx[1]]), n = 5)
tensor([1.1866, 1.1387, 1.4518, 1.6932, 1.5293])
&gt;&gt;&gt; torch.fft.irfft (torch.tensor ([rfx[0], rfx[1], 0.0]), n = 5)
tensor([1.1866, 1.1387, 1.4518, 1.6932, 1.5293])
</code></pre>
<p>Best.</p>
<p>K. Frank</p>

avitase: <aside class="quote no-group" data-username="KFrank" data-post="2" data-topic="125710">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/k/ecb155/48.png" class="avatar"> KFrank:</div>
<blockquote>
<p>Pytorch’s <code>fft</code> functions, including <code>ifft()</code>, pad by simply appending zeros<br>
to the end of the input vector – they don’t insert zeros into the middle.</p>
</blockquote>
</aside>
<p>Thanks for the help. I actually do need the complex version and just used the <code>torch.arange</code> as an example. I understand your point that <code>fft</code> and <code>ifft</code> pad the zeros to the end but I am now wondering when this would ever be useful for an inverse Fourier transform? Are there good reasons / is the behaviour that I expected worth making a feature request?</p>

KFrank: <p>Hi Avitase!</p>
<aside class="quote no-group" data-username="avitase" data-post="3" data-topic="125710" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/a/94ad74/48.png" class="avatar"> avitase:</div>
<blockquote>
<p>I actually do need the complex version</p>
</blockquote>
</aside>
<p>Okay, I think I understand your use case now.  Indeed, for the complex<br>
case, <code>rfft()</code> won’t work, and, as you noted, naive padding also doesn’t<br>
work.</p>
<p>I suppose you could use <code>fftshift()</code>, zero out the ends of the vector,<br>
and restore the required order with <code>ifftshift()</code> before applying <code>ifft()</code>.</p>
<p>But that would be a little verbose for my taste.  I would just do the<br>
necessary index arithmetic to zero out the high-frequency components:</p>
<pre><code class="lang-plaintext">&gt;&gt;&gt; fx
tensor([ 6.9995+0.0000j, -0.5333+0.5133j, -0.4665+0.1265j, -0.4665-0.1265j,
        -0.5333-0.5133j])
&gt;&gt;&gt; nZero = 2
&gt;&gt;&gt; fxc = fx.clone()
&gt;&gt;&gt; l = len (fxc)
&gt;&gt;&gt; fxc[(l - nZero + 1) // 2 : (l + nZero + 1) // 2] = 0.0
&gt;&gt;&gt; fxc
tensor([ 6.9995+0.0000j, -0.5333+0.5133j,  0.0000+0.0000j,  0.0000+0.0000j,
        -0.5333-0.5133j])
&gt;&gt;&gt; torch.fft.ifft (fxc)
tensor([1.1866+0.j, 1.1387+0.j, 1.4518+0.j, 1.6932+0.j, 1.5293+0.j])
</code></pre>
<p>Best.</p>
<p>K. Frank</p>

avitase: <p>Thanking you again for the help. I already did a similar thing with <code>torch.nn.functional.pad</code> (I need this to work in N dimensions). Maybe I will write a feature request later today <img src="https://discuss.pytorch.org/images/emoji/apple/see_no_evil.png?v=9" title=":see_no_evil:" class="emoji" alt=":see_no_evil:"></p>