<p dir="auto">the Fourier transform have many properties.<br>
one is the following equation, regarding complex conjugation:<br>
$$ \widetilde{\overline{f(t)}} = \overline{\widetilde{f}(-\omega)} $$<br>
(where the overline indicates the complex conjugation and the tilde the Fourier transform)<br>
and another one is this other equation, regarding time reversal:<br>
$$ F(x(-t)) = (F(x))(-\omega) $$<br>
(with the Fourier transform indicated by F)</p>
<p dir="auto">let's start talking about the first equation.<br>
when checking this analytical property numerically, I find that it is verified only for sequences with an odd number of samples, while with even numbers the two terms (left and right) are shifted by one sample. this seems a bug. if you do <code class="notranslate">numpy.roll(right, 1)</code> you obtain back the <code class="notranslate">left</code> term (within negligible numerical errors).</p>
<p dir="auto">the second properties is instead never correctly verified, since with both odd and even numbers the two results appear to be shifted by one sample one with respect to the other. this seems a bug to me.</p>
<p dir="auto">I'm reporting those two bugs together, since I believe they are strictly related.</p>
<p dir="auto">with the following code you can reproduce those bugs.</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import numpy

import matplotlib
#matplotlib.use('Qt5Agg')
from matplotlib import pyplot

&quot;&quot;&quot;
check complex conjugation
$\widetilde{\overline{f(t)}} = \overline{\widetilde{f}(-\omega)}$
&quot;&quot;&quot;

length = 1024

complex_data = numpy.random.normal(size=length) + 1j*numpy.random.normal(size=length)

left = numpy.fft.fftshift(numpy.fft.fft(numpy.conjugate(complex_data)))
right = numpy.conjugate(numpy.flip(numpy.fft.fftshift(numpy.fft.fft(complex_data))))

print(numpy.isclose(left, right, atol=0.00001).all())

pyplot.plot(abs(left), label='abs(left)')
pyplot.plot(abs(right), label='abs(right)')
pyplot.legend()
pyplot.show()

# with length=1023 everything is ok, but with length=1024 there is a shift of one sample. if you zoom the plot you can clearly see it. it can be resolved with numpy.roll(abs(right), 1)

##############################

&quot;&quot;&quot;
check time reversal
$F(x(-t)) = (F(x))(-\omega)$
&quot;&quot;&quot;

left = numpy.fft.fftshift(numpy.fft.fft(numpy.flip(complex_data)))
right = numpy.flip(numpy.fft.fftshift(numpy.fft.fft(complex_data)))

print(numpy.isclose(left, right, atol=0.00001).all())

pyplot.plot(abs(left), label='abs(left)')
pyplot.plot(abs(right), label='abs(right)')
pyplot.legend()
pyplot.show()

# both with length=1023 and length=1024 there is a shift of one sample. if you zoom the plot you can clearly see it. it can be resolved with numpy.roll(abs(right), 1)"><pre class="notranslate"><code class="notranslate">import numpy

import matplotlib
#matplotlib.use('Qt5Agg')
from matplotlib import pyplot

"""
check complex conjugation
$\widetilde{\overline{f(t)}} = \overline{\widetilde{f}(-\omega)}$
"""

length = 1024

complex_data = numpy.random.normal(size=length) + 1j*numpy.random.normal(size=length)

left = numpy.fft.fftshift(numpy.fft.fft(numpy.conjugate(complex_data)))
right = numpy.conjugate(numpy.flip(numpy.fft.fftshift(numpy.fft.fft(complex_data))))

print(numpy.isclose(left, right, atol=0.00001).all())

pyplot.plot(abs(left), label='abs(left)')
pyplot.plot(abs(right), label='abs(right)')
pyplot.legend()
pyplot.show()

# with length=1023 everything is ok, but with length=1024 there is a shift of one sample. if you zoom the plot you can clearly see it. it can be resolved with numpy.roll(abs(right), 1)

##############################

"""
check time reversal
$F(x(-t)) = (F(x))(-\omega)$
"""

left = numpy.fft.fftshift(numpy.fft.fft(numpy.flip(complex_data)))
right = numpy.flip(numpy.fft.fftshift(numpy.fft.fft(complex_data)))

print(numpy.isclose(left, right, atol=0.00001).all())

pyplot.plot(abs(left), label='abs(left)')
pyplot.plot(abs(right), label='abs(right)')
pyplot.legend()
pyplot.show()

# both with length=1023 and length=1024 there is a shift of one sample. if you zoom the plot you can clearly see it. it can be resolved with numpy.roll(abs(right), 1)
</code></pre></div>
<p dir="auto">Numpy/Python version information:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="import sys, numpy
print(numpy.__version__)
print(sys.version)

&gt;&gt;&gt; 1.15.3
&gt;&gt;&gt; 3.6.7 |Anaconda custom (64-bit)| (default, Oct 23 2018, 19:16:44) [GCC 7.3.0]"><pre class="notranslate"><code class="notranslate">import sys, numpy
print(numpy.__version__)
print(sys.version)

&gt;&gt;&gt; 1.15.3
&gt;&gt;&gt; 3.6.7 |Anaconda custom (64-bit)| (default, Oct 23 2018, 19:16:44) [GCC 7.3.0]
</code></pre></div>
<p dir="auto"><em>Background</em>: Quite frequently I would like (<a href="https://stackoverflow.com/questions/30031828/multiply-numpy-ndarray-with-1d-array-along-a-given-axis" rel="nofollow">and others</a>, it seems, too) to multiply a 1D array by an array of other arrays, say:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import numpy as np
c = np.array([1, 2, 3])
E = np.ones((3, 3))
Ev = np.array([E, E, E])
# and I'd like to do this using NumPy broadcasting possibilities: 
c * Ev"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-s1">numpy</span> <span class="pl-k">as</span> <span class="pl-s1">np</span>
<span class="pl-s1">c</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-c1">1</span>, <span class="pl-c1">2</span>, <span class="pl-c1">3</span>])
<span class="pl-v">E</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">ones</span>((<span class="pl-c1">3</span>, <span class="pl-c1">3</span>))
<span class="pl-v">Ev</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-v">E</span>, <span class="pl-v">E</span>, <span class="pl-v">E</span>])
<span class="pl-c"># and I'd like to do this using NumPy broadcasting possibilities: </span>
<span class="pl-s1">c</span> <span class="pl-c1">*</span> <span class="pl-v">Ev</span></pre></div>
<p dir="auto">Clearly, one option is to use comprehensions. Another is to expand <code class="notranslate">c</code> to have a suitable shape <code class="notranslate">(3,1,1)</code> by <code class="notranslate">expand_dims</code> or by <code class="notranslate">c[:, np.newaxis, np.newaxis]</code>.</p>
<p dir="auto"><em>Enh</em>: allow <code class="notranslate">expand_dims</code> accept a tuple that shows where insert new dimensions.<br>
If an int is passed, then do the same it does now.<br>
If a tuple <code class="notranslate">(p, q, r, s, ...)</code> of length <code class="notranslate">d</code> is passed, then increase <code class="notranslate">ndim</code> by <code class="notranslate">d</code>, fill <code class="notranslate">shape</code> elements at the indices <code class="notranslate">p, q, r, s ...</code> with 1, fill the rest with the original array's dimensions.</p>
<p dir="auto">Some examples that illustrate the idea:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="def new_expand_dims(a, axes):
    # if int is passed, retain the same behaviour
    if type(axes) == int:
        return np.expand_dims(a, axes)
    # insert axes to given indices
    for ax in sorted(axes):
        a = np.expand_dims(a, ax)
    return a


c = np.array([1, 3, 3])
csize = c.size

M = np.random.rand(8, 3, 4)


def test_int():
    assert new_expand_dims(c, 0).shape == (1, csize)


def test_consecutive_tpl():
    assert new_expand_dims(c, (0, 1)).shape == (1, 1, csize)


def test_non_consecutive_tpl():
    assert new_expand_dims(c, (0, 2)).shape == (1, csize, 1)


def test_302_tpl():
    assert new_expand_dims(c, (3, 0, 2)).shape == (1, csize, 1, 1)


def test_123_tpl():
    assert new_expand_dims(c, (1, 2, 3)).shape == (csize, 1, 1, 1)


def test_int_nd():
    assert new_expand_dims(M, 0).shape == (1, *M.shape)


def test_302_tpl_nd():
    assert new_expand_dims(M, (3, 0, 2)).shape == (1, 8, 1, 1, 3, 4)"><pre class="notranslate"><span class="pl-k">def</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-s1">a</span>, <span class="pl-s1">axes</span>):
    <span class="pl-c"># if int is passed, retain the same behaviour</span>
    <span class="pl-k">if</span> <span class="pl-en">type</span>(<span class="pl-s1">axes</span>) <span class="pl-c1">==</span> <span class="pl-s1">int</span>:
        <span class="pl-k">return</span> <span class="pl-s1">np</span>.<span class="pl-en">expand_dims</span>(<span class="pl-s1">a</span>, <span class="pl-s1">axes</span>)
    <span class="pl-c"># insert axes to given indices</span>
    <span class="pl-k">for</span> <span class="pl-s1">ax</span> <span class="pl-c1">in</span> <span class="pl-en">sorted</span>(<span class="pl-s1">axes</span>):
        <span class="pl-s1">a</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">expand_dims</span>(<span class="pl-s1">a</span>, <span class="pl-s1">ax</span>)
    <span class="pl-k">return</span> <span class="pl-s1">a</span>


<span class="pl-s1">c</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-c1">1</span>, <span class="pl-c1">3</span>, <span class="pl-c1">3</span>])
<span class="pl-s1">csize</span> <span class="pl-c1">=</span> <span class="pl-s1">c</span>.<span class="pl-s1">size</span>

<span class="pl-v">M</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-s1">random</span>.<span class="pl-en">rand</span>(<span class="pl-c1">8</span>, <span class="pl-c1">3</span>, <span class="pl-c1">4</span>)


<span class="pl-k">def</span> <span class="pl-en">test_int</span>():
    <span class="pl-k">assert</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-s1">c</span>, <span class="pl-c1">0</span>).<span class="pl-s1">shape</span> <span class="pl-c1">==</span> (<span class="pl-c1">1</span>, <span class="pl-s1">csize</span>)


<span class="pl-k">def</span> <span class="pl-en">test_consecutive_tpl</span>():
    <span class="pl-k">assert</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-s1">c</span>, (<span class="pl-c1">0</span>, <span class="pl-c1">1</span>)).<span class="pl-s1">shape</span> <span class="pl-c1">==</span> (<span class="pl-c1">1</span>, <span class="pl-c1">1</span>, <span class="pl-s1">csize</span>)


<span class="pl-k">def</span> <span class="pl-en">test_non_consecutive_tpl</span>():
    <span class="pl-k">assert</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-s1">c</span>, (<span class="pl-c1">0</span>, <span class="pl-c1">2</span>)).<span class="pl-s1">shape</span> <span class="pl-c1">==</span> (<span class="pl-c1">1</span>, <span class="pl-s1">csize</span>, <span class="pl-c1">1</span>)


<span class="pl-k">def</span> <span class="pl-en">test_302_tpl</span>():
    <span class="pl-k">assert</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-s1">c</span>, (<span class="pl-c1">3</span>, <span class="pl-c1">0</span>, <span class="pl-c1">2</span>)).<span class="pl-s1">shape</span> <span class="pl-c1">==</span> (<span class="pl-c1">1</span>, <span class="pl-s1">csize</span>, <span class="pl-c1">1</span>, <span class="pl-c1">1</span>)


<span class="pl-k">def</span> <span class="pl-en">test_123_tpl</span>():
    <span class="pl-k">assert</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-s1">c</span>, (<span class="pl-c1">1</span>, <span class="pl-c1">2</span>, <span class="pl-c1">3</span>)).<span class="pl-s1">shape</span> <span class="pl-c1">==</span> (<span class="pl-s1">csize</span>, <span class="pl-c1">1</span>, <span class="pl-c1">1</span>, <span class="pl-c1">1</span>)


<span class="pl-k">def</span> <span class="pl-en">test_int_nd</span>():
    <span class="pl-k">assert</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-v">M</span>, <span class="pl-c1">0</span>).<span class="pl-s1">shape</span> <span class="pl-c1">==</span> (<span class="pl-c1">1</span>, <span class="pl-c1">*</span><span class="pl-v">M</span>.<span class="pl-s1">shape</span>)


<span class="pl-k">def</span> <span class="pl-en">test_302_tpl_nd</span>():
    <span class="pl-k">assert</span> <span class="pl-en">new_expand_dims</span>(<span class="pl-v">M</span>, (<span class="pl-c1">3</span>, <span class="pl-c1">0</span>, <span class="pl-c1">2</span>)).<span class="pl-s1">shape</span> <span class="pl-c1">==</span> (<span class="pl-c1">1</span>, <span class="pl-c1">8</span>, <span class="pl-c1">1</span>, <span class="pl-c1">1</span>, <span class="pl-c1">3</span>, <span class="pl-c1">4</span>)</pre></div>
<h3 dir="auto">Numpy/Python version information:</h3>
<p dir="auto">1.15.3 3.7.1 (default, Oct 22 2018, 10:41:28)<br>
[GCC 8.2.1 20180831]</p>