Repository: scipy/scipy

Issue:
Complex conjugates in correlate()
I don't know when this was introduced, but there's a discrepancy in handling of complex conjugates in scipy 0.17.0:

```
    scipy.signal.correlate(1j, 2j)              (-2+0j)
    scipy.signal.correlate([1j], [2j])          array([ 2.+0.j])
    scipy.signal.correlate([[1j]], [[2j]])      array([[ 2.+0.j]])
    scipy.signal.correlate2d([[1j]], [[2j]])    array([[-2.+0.j]]) 
    numpy.correlate([1j], [2j])                 array([ 2.+0.j])
```

Wikipedia's definition of cross-correlation is:

![(f \star g)[n]\ \stackrel{\mathrm{def}}{=} \sum_{m=-\infty}^{\infty} f^*[m]\ g[m+n]](https://cloud.githubusercontent.com/assets/58611/13658199/d65369d4-e642-11e5-8276-145f57f8bba9.png)

which matches Wolfram's:

![[f*g](t)=int_(-infty)^inftyf^_(-tau)g(t-tau)dtau](https://cloud.githubusercontent.com/assets/58611/13658313/ed83fa96-e643-11e5-826b-f969c5a0ac2e.gif)

so this would mean conjugating the 1j into -1j and then multiplying it to get +2 as the correct output.

but then correlate(1j, 2) should equal 1j.conj() \* 2 = -1j \* 2 = -2j, which none of these agree with:

```
    scipy.signal.correlate(1j, 2)           2j
    scipy.signal.correlate([1j], [2])       array([ 0.+2.j])
    scipy.signal.correlate([[1j]], [[2]])   array([[ 0.+2.j]])
    scipy.signal.correlate2d([[1j]], [[2]]) array([[ 0.+2.j]])
    numpy.correlate([1j], [2])              array([ 0.+2.j])
```



Discussion:
Can someone run similar things in matlab and see which way it calculates? 

conv and xcorr on
1j, 2
1j, 2j
1, 2j

Etc

Octave says it conjugates the second argument:

> The cross-correlation estimate between vectors "x" and "y" (of length N) for lag "k" is given by
> 
> ```
>            N
>  R_xy(k) = sum x_{i+k} conj(y_i),
>            i=1
> ```

```
octave:7> xcorr([1j, 0], [2j, 0])
ans =

   0   2   0

octave:8> xcorr([1j, 0], [2, 0])
ans =

   0 + 0i   0 + 2i   0 + 0i

octave:9> xcorr([1, 0], [2j, 0])
ans =

   0 + 0i   0 - 2i   0 + 0i
```

MATLAB r2015b

```
> xcorr([1j, 0], [2j, 0])

ans =
     0     2     0

> xcorr([1j, 0], [2, 0])

ans =
  Column 1
                          0 +                     0i
  Column 2
                          0 +                     2i
  Column 3
                          0 +                     0i

> xcorr([1, 0], [2j, 0])

ans =
  Column 1
                          0 +                     0i
  Column 2
                          0 -                     2i
  Column 3
                          0 +                     0i
```

```
>> conv([1j, 0], [2j, 0])

ans =
    -2     0     0

>> conv([1j, 0], [2, 0])

ans =
  Column 1
                          0 +                     2i
  Column 2
                          0 +                     0i
  Column 3
                          0 +                     0i

>> conv([1, 0], [2j, 0])

ans =
  Column 1
                          0 +                     2i
  Column 2

                          0 +                     0i
  Column 3
                          0 +                     0i

```

So we can either change everything to match book definitions and become inconsistent with other software, or just change correlate 0d and correlate2d to make them consistent internally, and add Notes explaining the discrepancy

Any explanation from Octave or MATLAB docs about their convention?

They both define what they're doing, but don't give a reason why they swapped which input gets conjugated.

http://www.mathworks.com/help/signal/ref/xcorr.html#bubr0in
http://www.mathworks.com/help/signal/ref/xcorr2.html#buwgc15

I looked for refs and found one source that conjugates the second input:
https://web.eecs.umich.edu/~fessler/course/451/l/pdf/c2.pdf#page=5

And another that conjugates first:
http://www.ee.ic.ac.uk/hp/staff/dmb/courses/E1Fourier/00800_Correlation.pdf#page=2

but most book refs I found don't mention the conjugation at all, they deal only with real signals.

So I think it's fine to leave it as is, but fix scipy.signal.correlate 0d and correlate2d to be consistent, and explain the convention in the docstrings.

Agreed, it seems like they should at least be internally consistent.

Actually now that I look at this again, more carefully, all of the definitions I can find are equivalent, they just have minus or plus signs making them look different.  So correlate is correct for ND inputs, incorrect for 0d inputs, and correlate2d is incorrect. 

The complex conjugate exists to make sure the output is positive when the inputs have the same sign and the same imaginary sign.  So `correlate(3, 4)` should be positive, and `correlate(3j, 4j)` should also be positive.

Resolving patch:
diff --git a/scipy/signal/signaltools.py b/scipy/signal/signaltools.py
--- a/scipy/signal/signaltools.py
+++ b/scipy/signal/signaltools.py
@@ -212,7 +212,7 @@ def correlate(in1, in2, mode='full', method='auto'):
     in2 = asarray(in2)
 
     if in1.ndim == in2.ndim == 0:
-        return in1 * in2
+        return in1 * in2.conj()
     elif in1.ndim != in2.ndim:
         raise ValueError("in1 and in2 should have the same dimensionality")
 
@@ -1112,7 +1112,7 @@ def correlate2d(in1, in2, mode='full', boundary='fill', fillvalue=0):
 
     val = _valfrommode(mode)
     bval = _bvalfromboundary(boundary)
-    out = sigtools._convolve2d(in1, in2, 0, val, bval, fillvalue)
+    out = sigtools._convolve2d(in1, in2.conj(), 0, val, bval, fillvalue)
 
     if swapped_inputs:
         out = out[::-1, ::-1]


Test patch:
diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py
--- a/scipy/signal/tests/test_signaltools.py
+++ b/scipy/signal/tests/test_signaltools.py
@@ -1385,6 +1385,24 @@ def test_rank3(self, dt):
         assert_array_almost_equal(y, y_r, decimal=self.decimal(dt) - 1)
         assert_equal(y.dtype, dt)
 
+    def test_rank0(self, dt):
+        a = np.array(np.random.randn()).astype(dt)
+        a += 1j * np.array(np.random.randn()).astype(dt)
+        b = np.array(np.random.randn()).astype(dt)
+        b += 1j * np.array(np.random.randn()).astype(dt)
+
+        y_r = (correlate(a.real, b.real)
+               + correlate(a.imag, b.imag)).astype(dt)
+        y_r += 1j * (-correlate(a.real, b.imag) + correlate(a.imag, b.real))
+
+        y = correlate(a, b, 'full')
+        assert_array_almost_equal(y, y_r, decimal=self.decimal(dt) - 1)
+        assert_equal(y.dtype, dt)
+
+        assert_equal(correlate([1], [2j]), correlate(1, 2j))
+        assert_equal(correlate([2j], [3j]), correlate(2j, 3j))
+        assert_equal(correlate([3j], [4]), correlate(3j, 4))
+
 
 class TestCorrelate2d(object):
 
@@ -1419,6 +1437,11 @@ def test_invalid_shapes(self):
         assert_raises(ValueError, signal.correlate2d, *(a, b), **{'mode': 'valid'})
         assert_raises(ValueError, signal.correlate2d, *(b, a), **{'mode': 'valid'})
 
+    def test_complex_input(self):
+        assert_equal(signal.correlate2d([[1]], [[2j]]), -2j)
+        assert_equal(signal.correlate2d([[2j]], [[3j]]), 6)
+        assert_equal(signal.correlate2d([[3j]], [[4]]), 12j)
+
 
 class TestLFilterZI(object):