diff --git a/qiskit/opflow/list_ops/composed_op.py b/qiskit/opflow/list_ops/composed_op.py index 089934e7dc80..a6cf2609bba0 100644 --- 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 index ce5178436abb..8a3d9fecb026 100644 --- 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]]: diff --git a/releasenotes/notes/fix-composedop-08e14db184c637c8.yaml b/releasenotes/notes/fix-composedop-08e14db184c637c8.yaml new file mode 100644 index 000000000000..99e9d99d8f1f --- /dev/null +++ b/releasenotes/notes/fix-composedop-08e14db184c637c8.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed two bugs in the :class:`.ComposedOp` where the :meth:`.ComposedOp.to_matrix` + method did not provide the correct results for compositions with :class:`.StateFn` + and for compositions with a global coefficients. + See also `#9283 _`. diff --git a/test/python/opflow/test_op_construction.py b/test/python/opflow/test_op_construction.py index 26aad8260c6a..75a1b625c109 100644 --- a/test/python/opflow/test_op_construction.py +++ b/test/python/opflow/test_op_construction.py @@ -1086,6 +1086,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."""