Repository: scipy/scipy

Issue:
finish in scipy.optimize.brute suddenly changes shape
In the first part of `scipy.optimize.brute`, where `func` is evaluated on the grid `float`s are returned. Then in the final stage `func` is fed to a minimized and suddenly a `(1,)` numpy array is returned. This can lead to problems as I show in an example here.

See the manifestation of the behavior in the following code
```python
import scipy.optimize
import numpy as np

def test(x):
    print(x, x.dtype, x.shape)
    return x**2

res = scipy.optimize.brute(test, ranges=[(-1, 1)], Ns=3)
```

it prints:
```
-1.0 float64 ()
-1.0 float64 ()
0.0 float64 ()
1.0 float64 ()
[ 0.] float64 (1,)
```

This gave me an annoying issue when the function was using `np.diag`, because:
```python
> np.diag([1,1])

array([[1, 0],
       [0, 1]])
```
is different than:
```python
> np.diag([np.array([1]), np.array([1])])

array([1])
```

A simple example that would work with `finish=None` but fails when not:
```python
A = np.random.rand(2, 2)
def test(x):
    y = np.diag([x**2, x**2])
    return np.min(y @ A)

res = scipy.optimize.brute(test, ranges=[(-1, 1)], Ns=3)
```


Discussion:
If someone can confirm that this behavior is unwanted I can submit a PR to fix it! 😄 
All the minimizers in `scipy.optimize` indicate that `func`:

> Must be in the form f(x, *args), where x is the argument in the form of a 1-D array

i.e. the first argument supplied to `func` is a 1D array of parameters. `func` is supposed to return a single float value. Thus, the objective function in your examples should expect to receive a 1D array. 

I think the reason your function is not receiving an array to start with is due to the [_scalarfunc](https://github.com/scipy/scipy/blob/v1.0.0/scipy/optimize/optimize.py#L2818) wrapper. That wrapper squeezes the parameter array.

I'd say if anything the bug is that brute is supplying the test function with a single float instead of a 1D array. This issue will only occur for a 1D problem.

As I suspected - `_scalarfunc` is supplied with tuples: `(-1.0,)`, `(0.0,)`, `(1.0,)` and the squeezing is supplying `func` with scalar floats.
Perhaps [L2818](https://github.com/scipy/scipy/blob/v1.0.0/scipy/optimize/optimize.py#L2818) needs to be changed to:
```
params = atleast_1d(squeeze(asarray(params)))
```
<edit>
or even better:
```
params = asarray(params).flatten()
```

Resolving patch:
diff --git a/scipy/optimize/optimize.py b/scipy/optimize/optimize.py
--- a/scipy/optimize/optimize.py
+++ b/scipy/optimize/optimize.py
@@ -2835,7 +2835,7 @@ def brute(func, ranges, args=(), Ns=20, full_output=0, finish=fmin,
         lrange = lrange[0]
 
     def _scalarfunc(*params):
-        params = squeeze(asarray(params))
+        params = asarray(params).flatten()
         return func(params, *args)
 
     vecfunc = vectorize(_scalarfunc)


Test patch:
diff --git a/scipy/optimize/tests/test_optimize.py b/scipy/optimize/tests/test_optimize.py
--- a/scipy/optimize/tests/test_optimize.py
+++ b/scipy/optimize/tests/test_optimize.py
@@ -1190,6 +1190,17 @@ def test_brute(self):
         assert_allclose(resbrute[1], self.func(self.solution, *self.params),
                         atol=1e-3)
 
+    def test_1D(self):
+        # test that for a 1D problem the test function is passed an array,
+        # not a scalar.
+        def f(x):
+            assert_(len(x.shape) == 1)
+            assert_(x.shape[0] == 1)
+            return x ** 2
+
+        optimize.brute(f, [(-1, 1)], Ns=3, finish=None)
+
+
 class TestIterationLimits(object):
     # Tests that optimisation does not give up before trying requested
     # number of iterations or evaluations. And that it does not succeed