diff --git a/qiskit/quantum_info/states/statevector.py b/qiskit/quantum_info/states/statevector.py index 3fc58ee4f9a6..f8aaf4c8432c 100644 --- a/qiskit/quantum_info/states/statevector.py +++ b/qiskit/quantum_info/states/statevector.py @@ -1027,7 +1027,8 @@ def _evolve_instruction(statevec, obj, qargs=None): ) if obj.definition.global_phase: - statevec._data *= np.exp(1j * float(obj.definition.global_phase)) + # We do not apply this in-place, just in-case we have a shallow copy of _data. + statevec._data = statevec._data * np.exp(1j * float(obj.definition.global_phase)) qubits = {qubit: i for i, qubit in enumerate(obj.definition.qubits)} for instruction in obj.definition: if instruction.clbits: diff --git a/releasenotes/notes/fix-statevector-evolve-inplace-modification-be2f31976d5a2e1d.yaml b/releasenotes/notes/fix-statevector-evolve-inplace-modification-be2f31976d5a2e1d.yaml new file mode 100644 index 000000000000..2672766a0d1e --- /dev/null +++ b/releasenotes/notes/fix-statevector-evolve-inplace-modification-be2f31976d5a2e1d.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed :meth:`.Statevector.evolve` modifying the input statevector in-place when the + instruction has a global phase. + See `#15750 `__. diff --git a/test/python/quantum_info/states/test_statevector.py b/test/python/quantum_info/states/test_statevector.py index a72a327dd994..b4c8df7691f8 100644 --- a/test/python/quantum_info/states/test_statevector.py +++ b/test/python/quantum_info/states/test_statevector.py @@ -23,7 +23,7 @@ from qiskit import QiskitError from qiskit import QuantumRegister, QuantumCircuit from qiskit import transpile -from qiskit.circuit.library import HGate, QFTGate, GlobalPhaseGate +from qiskit.circuit.library import HGate, QFTGate, GlobalPhaseGate, DiagonalGate, CXGate, XGate from qiskit.providers.basic_provider import BasicSimulator from qiskit.utils import optionals from qiskit.quantum_info.random import random_unitary, random_statevector, random_pauli @@ -336,6 +336,115 @@ def test_evolve(self): evolved = Statevector(vec).evolve(op) self.assertEqual(target, evolved) + def test_evolve_no_modification_with_xgate(self): + """Test evolve by XGate doesn't modify original vector""" + + # |00> is sensitive to IX + original_vec = Statevector([1.0, 0.0, 0.0, 0.0]) + + # Copy so we can compare to original_vec + new_vec = original_vec.copy() + evolved_vec = new_vec.evolve(XGate(), [0]) + + # Check that the evolved state is different to the original state. If they aren't, then + # either our initial state is an eigenstate of the operator or evolve() with the given + # operator is broken. + self.assertNotEqual( + evolved_vec, + original_vec, + "The evolved state is unchanged. Either the state is an eigenvector or evolve() is broken.", + ) + self.assertEqual(original_vec, new_vec, "Evolving by XGate modified original Statevector") + + def test_evolve_no_modification_with_cxgate(self): + """Test evolve by CXGate doesn't modify original vector""" + + # |01> is sensitive to CX + original_vec = Statevector([0.0, 1.0, 0.0, 0.0]) + + # Copy so we can compare to original_vec + new_vec = original_vec.copy() + evolved_vec = new_vec.evolve(CXGate(), [0, 1]) + + # Check that the evolved state is different to the original state. If they aren't, then + # either our initial state is an eigenstate of the operator or evolve() with the given + # operator is broken. + self.assertNotEqual( + evolved_vec, + original_vec, + "The evolved state is unchanged. Either the state is an eigenvector or evolve() is broken.", + ) + self.assertEqual(original_vec, new_vec, "Evolving by CXGate modified original Statevector") + + def test_evolve_no_modification_with_diagonalgate(self): + """Test evolve by DiagonalGate doesn't modify original vector""" + + # All superposition is sensitive to the given DiagonalGate + original_vec = Statevector(np.ones(4) / np.sqrt(4)) + + # Copy so we can compare to original_vec + new_vec = original_vec.copy() + evolved_vec = new_vec.evolve(DiagonalGate([1.0, -1.0, -1.0, 1.0]), [0, 1]) + + # Check that the evolved state is different to the original state. If they aren't, then + # either our initial state is an eigenstate of the operator or evolve() with the given + # operator is broken. + self.assertNotEqual( + evolved_vec, + original_vec, + "The evolved state is unchanged. Either the state is an eigenvector or evolve() is broken.", + ) + self.assertEqual( + original_vec, new_vec, "Evolving by DiagonalGate modified original Statevector" + ) + + def test_evolve_no_modification_with_operator(self): + """Test evolve by Operator doesn't modify original vector""" + + # |0> is sensitive to X + original_vec = Statevector([1.0, 0.0, 0.0, 0.0]) + # Copy so we can compare to original_vec + new_vec = original_vec.copy() + evolved_vec = new_vec.evolve(Operator(XGate()), [0]) + + # Check that the evolved state is different to the original state. If they aren't, then + # either our initial state is an eigenstate of the operator or evolve() with the given + # operator is broken. + self.assertNotEqual( + evolved_vec, + original_vec, + "The evolved state is unchanged. Either the state is an eigenvector or evolve() is broken.", + ) + self.assertEqual( + original_vec, new_vec, "Evolving by Operator modified original Statevector" + ) + + def test_evolve_no_modification_with_quantumcircuit(self): + """Test by QuantumCircuit method doesn't modify original vector""" + + # |00> is sensitive to a Bell-state preparation circuit + original_vec = Statevector([1.0, 0.0, 0.0, 0.0]) + + # Copy so we can compare to original_vec + new_vec = original_vec.copy() + circ = QuantumCircuit(2) + circ.h(0) + circ.cx(0, 1) + evolved_vec = new_vec.evolve(circ, [0, 1]) + + # Check that the evolved state is different to the original state. If they aren't, then + # either our initial state is an eigenstate of the operator or evolve() with the given + # operator is broken. + + self.assertNotEqual( + evolved_vec, + original_vec, + "The evolved state is unchanged. Either the state is an eigenvector or evolve() is broken.", + ) + self.assertEqual( + original_vec, new_vec, "Evolving by QuantumCircuit modified original Statevector" + ) + def test_evolve_operator_overload_dimensions(self): """Test that the @ operator returns a Statevector of correct dimension, type and value.""" op = random_unitary(4) # 4x4 unitary