Issue 1:
the Fourier transform have many properties.

one is the following equation, regarding complex conjugation:

$$ \widetilde{\overline{f(t)}} = \overline{\widetilde{f}(-\omega)} $$

(where the overline indicates the complex conjugation and the tilde the Fourier transform)

and another one is this other equation, regarding time reversal:

$$ F(x(-t)) = (F(x))(-\omega) $$

(with the Fourier transform indicated by F)

let's start talking about the first equation.

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 numpy.roll(right, 1) you obtain back the left term (within negligible numerical errors).

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.

I'm reporting those two bugs together, since I believe they are strictly related.

with the following code you can reproduce those bugs.

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)

Numpy/Python version information:

import sys, numpy
print(numpy.__version__)
print(sys.version)

>>> 1.15.3
>>> 3.6.7 |Anaconda custom (64-bit)| (default, Oct 23 2018, 19:16:44) [GCC 7.3.0]

Issue 2:
Background: Quite frequently I would like (and others, it seems, too) to multiply a 1D array by an array of other arrays, say:

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

Clearly, one option is to use comprehensions. Another is to expand c to have a suitable shape (3,1,1) by expand_dims or by c[:, np.newaxis, np.newaxis].

Enh: allow expand_dims accept a tuple that shows where insert new dimensions.

If an int is passed, then do the same it does now.

If a tuple (p, q, r, s, ...) of length d is passed, then increase ndim by d, fill shape elements at the indices p, q, r, s ... with 1, fill the rest with the original array's dimensions.

Some examples that illustrate the idea:

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)

Numpy/Python version information:

1.15.3 3.7.1 (default, Oct 22 2018, 10:41:28)

[GCC 8.2.1 20180831]