Repository: Qiskit/qiskit

Issue:
`ComposedOp`'s `to_matrix()` returns a wrong answer.
### Environment

- **Qiskit Terra version**: 
   qiskit-terra==0.22.3
- **Python version**: 
   3.10.4
- **Operating system**: 
   Ubuntu 20.04.5 LTS


### What is happening?

`ComposedOp`'s `to_matrix()` returns a wrong answer.
In addition,  `ComposedOp`'s `to_matrix()` raises an exception if it contains a state vector.

### How can we reproduce the issue?

Run the following code:
```
import numpy as np
from qiskit.opflow import MatrixOp, X, Zero

x = MatrixOp(X.to_matrix())
print((0.5*(x @ X)).to_matrix())
```

Then, the following will be printed:
```
[[0.25+0.j 0.  +0.j]
 [0.  +0.j 0.25+0.j]]
```

In addition to that, the following code is also buggy:
```
(x @ Zero).to_matrix()
```

This code raises the following exception:
```
ValueError                                Traceback (most recent call last)
Cell In[4], line 1
----> 1 (x @ Zero).to_matrix()

File ~/quantum/venv/lib/python3.10/site-packages/qiskit/opflow/list_ops/list_op.py:364, in ListOp.to_matrix(self, massive)
    357 OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive)
    359 # Combination function must be able to handle classical values.
    360 # Note: this can end up, when we have list operators containing other list operators, as a
    361 #       ragged array and numpy 1.19 raises a deprecation warning unless this is explicitly
    362 #       done as object type now - was implicit before.
    363 mat = self.combo_fn(  # pylint: disable=not-callable
--> 364     np.asarray(
    365         [op.to_matrix(massive=massive) * self.coeff for op in self.oplist], dtype=object
    366     )
    367 )
    368 # Note: As ComposedOp has a combo function of inner product we can end up here not with
    369 # a matrix (array) but a scalar. In which case we make a single element array of it.
    370 if isinstance(mat, Number):

ValueError: could not broadcast input array from shape (2,2) into shape (2,)

```

### What should happen?

The former result should be:
```
[[0.5+0.j, 0. +0.j],
 [0. +0.j, 0.5+0.j]]
```

The latter result should be:
```
[0.+0.j, 1.+0.j]
```


### Any suggestions?

This bug is caused by the lack of implementation of `to_matrix()` for `ComposedOp`.
`ComposedOp` doesn't have its own `to_matrix()`, so it calls `to_matrix()` of the superclass `ListOp`.
However, `ListOp` assumes it is distributive w.r.t. `coeff`, so the former problem is caused.

The second problem is also caused by the lack of implementation of `to_matrix()` for `ComposedOp`.
`ListOp` assumes all elements of oplist will be converted into the same size matrix; the latter problem happens.

I used the following patch to address this. But I am not sure my patch is correct:

```
from numbers import Number

import numpy as np
from qiskit.opflow import ComposedOp


def custom_to_matrix():
    def to_matrix(self, massive: bool = False):
        ans = None
        for i, op in enumerate(self.oplist):
            if ans is None:
                ans = op.to_matrix(massive=massive)
            else:
                ans = np.dot(ans, op.to_matrix(massive=massive))

        ans = ans * self.coeff
        if isinstance(ans, Number):
            ans = [ans]

        return np.asarray(ans, dtype=complex)

    ComposedOp.to_matrix = to_matrix
```


Discussion:

Resolving patch:
diff --git a/qiskit/opflow/list_ops/composed_op.py b/qiskit/opflow/list_ops/composed_op.py
--- a/qiskit/opflow/list_ops/composed_op.py
+++ b/qiskit/opflow/list_ops/composed_op.py
@@ -15,6 +15,7 @@
 from functools import partial, reduce
 from typing import List, Optional, Union, cast, Dict
 
+from numbers import Number
 import numpy as np
 
 from qiskit import QuantumCircuit
@@ -64,6 +65,20 @@ def settings(self) -> Dict:
     #     """ Tensor product with Self Multiple Times """
     #     raise NotImplementedError
 
+    def to_matrix(self, massive: bool = False) -> np.ndarray:
+        OperatorBase._check_massive("to_matrix", True, self.num_qubits, massive)
+
+        mat = self.coeff * reduce(
+            np.dot, [np.asarray(op.to_matrix(massive=massive)) for op in self.oplist]
+        )
+
+        # Note: As ComposedOp has a combo function of inner product we can end up here not with
+        # a matrix (array) but a scalar. In which case we make a single element array of it.
+        if isinstance(mat, Number):
+            mat = [mat]
+
+        return np.asarray(mat, dtype=complex)
+
     def to_circuit(self) -> QuantumCircuit:
         """Returns the quantum circuit, representing the composed operator.
 
diff --git a/qiskit/opflow/list_ops/list_op.py b/qiskit/opflow/list_ops/list_op.py
--- a/qiskit/opflow/list_ops/list_op.py
+++ b/qiskit/opflow/list_ops/list_op.py
@@ -13,7 +13,6 @@
 """ ListOp Operator Class """
 
 from functools import reduce
-from numbers import Number
 from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Sequence, Union, cast
 
 import numpy as np
@@ -365,10 +364,6 @@ def to_matrix(self, massive: bool = False) -> np.ndarray:
                 [op.to_matrix(massive=massive) * self.coeff for op in self.oplist], dtype=object
             )
         )
-        # Note: As ComposedOp has a combo function of inner product we can end up here not with
-        # a matrix (array) but a scalar. In which case we make a single element array of it.
-        if isinstance(mat, Number):
-            mat = [mat]
         return np.asarray(mat, dtype=complex)
 
     def to_spmatrix(self) -> Union[spmatrix, List[spmatrix]]:


Test patch:
diff --git a/test/python/opflow/test_op_construction.py b/test/python/opflow/test_op_construction.py
--- a/test/python/opflow/test_op_construction.py
+++ b/test/python/opflow/test_op_construction.py
@@ -1084,6 +1084,30 @@ def test_empty_listops(self):
         with self.subTest("eval empty TensoredOp "):
             self.assertEqual(TensoredOp([]).eval(), 0.0)
 
+    def test_composed_op_to_matrix_with_coeff(self):
+        """Test coefficients are properly handled.
+
+        Regression test of Qiskit/qiskit-terra#9283.
+        """
+        x = MatrixOp(X.to_matrix())
+        composed = 0.5 * (x @ X)
+
+        expected = 0.5 * np.eye(2)
+
+        np.testing.assert_almost_equal(composed.to_matrix(), expected)
+
+    def test_composed_op_to_matrix_with_vector(self):
+        """Test a matrix-vector composed op can be cast to matrix.
+
+        Regression test of Qiskit/qiskit-terra#9283.
+        """
+        x = MatrixOp(X.to_matrix())
+        composed = x @ Zero
+
+        expected = np.array([0, 1])
+
+        np.testing.assert_almost_equal(composed.to_matrix(), expected)
+
 
 class TestOpMethods(QiskitOpflowTestCase):
     """Basic method tests."""