Repository: scipy/scipy

Issue:
Array shape mismatch in scipy.optimize
<!-- 
Thank you for taking the time to file a bug report. 
Please fill in the fields below, deleting the sections that 
don't apply to your issue. You can view the final output
by clicking the preview button above.
Note: This is a comment, and won't appear in the output.
-->

Shape mismatch of some np.matrix class
The issue can be solved easily by inserting
f0 = np.array(f0)[0]
below the line
f0 = fun.f
in _constraints.py

#### Reproducing code example:
<!-- 
    -->

```
def opt(x):
    return x[0]**2+x[1]**2

import scipy.optimize
A = np.matrix(np.diag([1,1]))
lin_cons = scipy.optimize.LinearConstraint(A, -1, np.inf)
    
params = scipy.optimize.minimize(opt, 2*[1], constraints = lin_cons)
```

#### Error message:
<!-- If any, paste the *full* error message inside a code block
as above (starting from line Traceback)
-->

```

  File "C:\Users\Rapha\Desktop\Sigma\scratchbook.py", line 18, in <module>
    params = scipy.optimize.minimize(opt, 2*[1], constraints = lin_cons)

  File "c:\users\rapha\appdata\local\programs\python\python37\lib\site-packages\scipy\optimize\_minimize.py", line 595, in minimize
    constraints = standardize_constraints(constraints, x0, meth)

  File "c:\users\rapha\appdata\local\programs\python\python37\lib\site-packages\scipy\optimize\_minimize.py", line 825, in standardize_constraints
    old_constraints = new_constraint_to_old(con, x0)

  File "c:\users\rapha\appdata\local\programs\python\python37\lib\site-packages\scipy\optimize\_constraints.py", line 355, in new_constraint_to_old
    pcon = PreparedConstraint(con, x0)

  File "c:\users\rapha\appdata\local\programs\python\python37\lib\site-packages\scipy\optimize\_constraints.py", line 247, in __init__
    if np.any(f0[mask] < lb[mask]) or np.any(f0[mask] > ub[mask]):

  File "c:\users\rapha\appdata\local\programs\python\python37\lib\site-packages\numpy\matrixlib\defmatrix.py", line 195, in __getitem__
    out = N.ndarray.__getitem__(self, index)

IndexError: boolean index did not match indexed array along dimension 0; dimension is 1 but corresponding boolean dimension is 2  ...
```

#### Scipy/Numpy/Python version information:
<!-- You can simply run the following and paste the result in a code block
```
1.4.1 1.18.2 sys.version_info(major=3, minor=7, micro=7, releaselevel='final', serial=0)
```
-->


Discussion:
The issue here is that `A` is a `np.matrix`, your example works ok if `A` is simply an array, `np.diag([1, 1])`. The use of `np.matrix` is [no longer recommended](https://numpy.org/doc/stable/reference/generated/numpy.matrix.html#numpy.matrix).

With your example `f0` in `Prepared_Constraint` is a `(1, 2)` matrix (this calculation is done in [LinearVectorFunction](https://github.com/scipy/scipy/blob/master/scipy/optimize/_differentiable_functions.py#L487)). This is something akin to:
```
>>> A = np.matrix(np.diag([1,1]))
>>> B = np.diag([1, 1])
>>> A.dot([1, 1.]), B.dot([1, 1.])
(matrix([[1., 1.]]), array([1., 1.]))
```
The trouble is that the matrix is not subscriptable by booleans:
```
>>> dp = A.dot([1, 1.])
>>> dp
matrix([[1., 1.]])
>>> idx = np.array([False, False])
>>> dp[idx]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-180-4876f18bf5d6> in <module>
----> 1 dp[idx]

~/miniconda3/envs/dev38/lib/python3.8/site-packages/numpy/matrixlib/defmatrix.py in __getitem__(self, index)
    193 
    194         try:
--> 195             out = N.ndarray.__getitem__(self, index)
    196         finally:
    197             self._getitem = False

IndexError: boolean index did not match indexed array along dimension 0; dimension is 1 but corresponding boolean dimension is 2
```

A decision needs to be made as to where to go from here, using one of the following:

1. because `matrix` shouldn't be used for new code, we're not going to make any changes.
2. in `_differentiable_functions` test if `A` is a `np.ndarray`, and if it's not make sure it is.


Any change made would be made to `LinearVectorFunction`:

```
# use of extra np.array call is in case A is a matrix
self.J = np.atleast_2d(np.array(A, copy=False)))
```

@mdhaber, any thoughts?
I agree that this doesn't _have_ to be patched, but if it's a straightforward fix like you suggested `self.J = np.atleast_2d...` then I would be happy to review and merge.

Resolving patch:
diff --git a/scipy/optimize/_differentiable_functions.py b/scipy/optimize/_differentiable_functions.py
--- a/scipy/optimize/_differentiable_functions.py
+++ b/scipy/optimize/_differentiable_functions.py
@@ -478,7 +478,8 @@ def __init__(self, A, x0, sparse_jacobian):
             self.J = A.toarray()
             self.sparse_jacobian = False
         else:
-            self.J = np.atleast_2d(A)
+            # np.asarray makes sure A is ndarray and not matrix
+            self.J = np.atleast_2d(np.asarray(A))
             self.sparse_jacobian = False
 
         self.m, self.n = self.J.shape


Test patch:
diff --git a/scipy/optimize/tests/test_minimize_constrained.py b/scipy/optimize/tests/test_minimize_constrained.py
--- a/scipy/optimize/tests/test_minimize_constrained.py
+++ b/scipy/optimize/tests/test_minimize_constrained.py
@@ -1,3 +1,4 @@
+import warnings
 import numpy as np
 import pytest
 from scipy.linalg import block_diag
@@ -665,3 +666,14 @@ def constraintlcoh(x, v):
         )
 
         assert_array_almost_equal(abs(result.x), np.array([1, 0]), decimal=4)
+
+
+def test_bug_11886():
+    def opt(x):
+        return x[0]**2+x[1]**2
+
+    with np.testing.suppress_warnings() as sup:
+        sup.filter(PendingDeprecationWarning)
+        A = np.matrix(np.diag([1, 1]))
+    lin_cons = LinearConstraint(A, -1, np.inf)
+    minimize(opt, 2*[1], constraints = lin_cons)  # just checking that there are no errors