Repository: scipy/scipy

Issue:
Shape bug in scipy.optimize.least_squares (trf) with custom loss function and jac as linear operator
I believe there is a shape/broadcasting bug in the scipy.optimize._lsq.common::left_multiplied_operator's matmat implementation, which causes a crash when using scipy.optimize.least_squares with a jacobian passed as a linearoperator and a custom loss function

### Reproducing code example:
```
import numpy as np
import scipy.optimize
from scipy.optimize._numdiff import approx_derivative
from scipy.sparse.linalg import aslinearoperator

X = np.linspace(1,10)
Y = 2*X + 1 + 0.01 * np.random.RandomState(0).randn(len(X))

def fun(p):
    return X*p[0] + p[1] - Y

def jac(x):
    return aslinearoperator(approx_derivative(fun, x))

r = scipy.optimize.least_squares(
    fun=lambda p: X*p[0] + p[1] - Y,
    jac=jac,
    x0=[0, 0],
    loss='huber',
)
```

### Error message:
```
Traceback (most recent call last):
  File "example.py", line 19, in <module>
    loss='huber,
  File "/prefix/lib/python2.7/site-packages/scipy/optimize/_lsq/least_squares.py", line 908, in least_squares
    tr_options.copy(), verbose)
  File "/prefix/lib/python2.7/site-packages/scipy/optimize/_lsq/trf.py", line 124, in trf
    loss_function, tr_solver, tr_options, verbose)
  File "/prefix/lib/python2.7/site-packages/scipy/optimize/_lsq/trf.py", line 491, in trf_no_bounds
    JS = J_h.dot(S)
  File "/prefix/lib/python2.7/site-packages/scipy/sparse/linalg/interface.py", line 364, in dot
    return self.matmat(x)
  File "/prefix/lib/python2.7/site-packages/scipy/sparse/linalg/interface.py", line 326, in matmat
    Y = self._matmat(X)
  File "/prefix/lib/python2.7/site-packages/scipy/sparse/linalg/interface.py", line 466, in _matmat
    return self.__matmat_impl(X)
  File "/prefix/lib/python2.7/site-packages/scipy/optimize/_lsq/common.py", line 642, in matmat
    return J.matmat(X * d[:, np.newaxis])
  File "/prefix/lib/python2.7/site-packages/scipy/sparse/linalg/interface.py", line 326, in matmat
    Y = self._matmat(X)
  File "/prefix/lib/python2.7/site-packages/scipy/sparse/linalg/interface.py", line 466, in _matmat
    return self.__matmat_impl(X)
  File "/prefix/lib/python2.7/site-packages/scipy/optimize/_lsq/common.py", line 625, in matmat
    return d * J.matmat(X)
ValueError: operands could not be broadcast together with shapes (50,) (50,2)
```

### Scipy/Numpy/Python version information:
```
$ python -c 'import sys, scipy, numpy; print(scipy.__version__, numpy.__version__, sys.version_info)'
('0.19.1', '1.13.1', sys.version_info(major=2, minor=7, micro=14, releaselevel='final', serial=0))
```



Discussion:
I believe that the fix is to change [this line](https://github.com/scipy/scipy/blob/master/scipy/optimize/_lsq/common.py#L625) from `return d * J.matmat(X)` to `return d[:, np.newaxis] * J.matmat(X)`.

Resolving patch:
diff --git a/scipy/optimize/_lsq/common.py b/scipy/optimize/_lsq/common.py
--- a/scipy/optimize/_lsq/common.py
+++ b/scipy/optimize/_lsq/common.py
@@ -622,7 +622,7 @@ def matvec(x):
         return d * J.matvec(x)
 
     def matmat(X):
-        return d * J.matmat(X)
+        return d[:, np.newaxis] * J.matmat(X)
 
     def rmatvec(x):
         return J.rmatvec(x.ravel() * d)


Test patch:
diff --git a/scipy/optimize/tests/test_lsq_common.py b/scipy/optimize/tests/test_lsq_common.py
--- a/scipy/optimize/tests/test_lsq_common.py
+++ b/scipy/optimize/tests/test_lsq_common.py
@@ -4,10 +4,12 @@
 from pytest import raises as assert_raises
 import numpy as np
 
+from scipy.sparse.linalg import LinearOperator
 from scipy.optimize._lsq.common import (
     step_size_to_bound, find_active_constraints, make_strictly_feasible,
     CL_scaling_vector, intersect_trust_region, build_quadratic_1d,
-    minimize_quadratic_1d, evaluate_quadratic, reflective_transformation)
+    minimize_quadratic_1d, evaluate_quadratic, reflective_transformation,
+    left_multiplied_operator, right_multiplied_operator)
 
 
 class TestBounds(object):
@@ -248,3 +250,26 @@ def test_reflective_transformation():
     assert_equal(x, [0, 10])
     assert_equal(g, [-1, 1])
 
+
+def test_linear_operators():
+    A = np.arange(6).reshape((3, 2))
+
+    d_left = np.array([-1, 2, 5])
+    DA = np.diag(d_left).dot(A)
+    J_left = left_multiplied_operator(A, d_left)
+
+    d_right = np.array([5, 10])
+    AD = A.dot(np.diag(d_right))
+    J_right = right_multiplied_operator(A, d_right)
+
+    x = np.array([-2, 3])
+    X = -2 * np.arange(2, 8).reshape((2, 3))
+    xt = np.array([0, -2, 15])
+
+    assert_allclose(DA.dot(x), J_left.matvec(x))
+    assert_allclose(DA.dot(X), J_left.matmat(X))
+    assert_allclose(DA.T.dot(xt), J_left.rmatvec(xt))
+
+    assert_allclose(AD.dot(x), J_right.matvec(x))
+    assert_allclose(AD.dot(X), J_right.matmat(X))
+    assert_allclose(AD.T.dot(xt), J_right.rmatvec(xt))