Skip to content
Open
3 changes: 2 additions & 1 deletion qiskit/quantum_info/states/statevector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Fixed :meth:`.Statevector.evolve` modifying the input statevector in-place when the
instruction has a global phase.
See `#15750 <https://github.com/Qiskit/qiskit/issues/15750>`__.
111 changes: 110 additions & 1 deletion test/python/quantum_info/states/test_statevector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down