From 4c9df00b467a30690eb8d87cd9bf9157b601e66d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 17 Feb 2022 14:39:49 +0000 Subject: [PATCH] Fix phase management in `StabilizerState.expectation_value` (#7460) (#7675) * fix a bug in expectation value. Handle Pauli phases. * extend the test to include Pauli expectation vals with phases. * add release notes * add more 1-qubit and 2-qubit tests for exp_val=-1,i,-i * more ddt * Fix typo in error message * Add comment on phase Co-authored-by: Ikko Hamamura Co-authored-by: Luciano Bello Co-authored-by: Jake Lishman Co-authored-by: Jake Lishman (cherry picked from commit 6da136c3c8c56c886c50cf903637375459d60217) Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> --- qiskit/quantum_info/states/stabilizerstate.py | 32 +- ...abilizerstate_expval-2556c5ee916f5327.yaml | 5 + .../states/test_stabilizerstate.py | 493 ++++++++++-------- 3 files changed, 296 insertions(+), 234 deletions(-) create mode 100644 releasenotes/notes/fix_stabilizerstate_expval-2556c5ee916f5327.yaml diff --git a/qiskit/quantum_info/states/stabilizerstate.py b/qiskit/quantum_info/states/stabilizerstate.py index cb9fd39d62ae..114d8b6693e6 100644 --- a/qiskit/quantum_info/states/stabilizerstate.py +++ b/qiskit/quantum_info/states/stabilizerstate.py @@ -196,15 +196,21 @@ def evolve(self, other, qargs=None): return ret def expectation_value(self, oper, qargs=None): - """Compute the expectation value of an operator. + """Compute the expectation value of a Pauli operator. Args: - oper (BaseOperator): an operator to evaluate expval. + oper (Pauli): a Pauli operator to evaluate expval. qargs (None or list): subsystems to apply the operator on. Returns: - complex: the expectation value (only 0 or 1 or -1). + complex: the expectation value (only 0 or 1 or -1 or i or -i). + + Raises: + QiskitError: if oper is not a Pauli operator. """ + if not isinstance(oper, Pauli): + raise QiskitError("Operator for expectation value is not a Pauli operator.") + num_qubits = self.clifford.num_qubits if qargs is None: qubits = range(num_qubits) @@ -214,19 +220,12 @@ def expectation_value(self, oper, qargs=None): # Construct Pauli on num_qubits pauli = Pauli(num_qubits * "I") phase = 0 + pauli_phase = (-1j) ** oper.phase if oper.phase else 1 for pos, qubit in enumerate(qubits): - pauli_pos = (oper.to_label())[len(oper) - 1 - pos] - if pauli_pos == "X": - pauli.x[qubit] = 1 - elif pauli_pos == "Y": - pauli.x[qubit] = 1 - pauli.z[qubit] = 1 - phase += 1 - elif pauli_pos == "Z": - pauli.z[qubit] = 1 - else: - pass + pauli.x[qubit] = oper.x[pos] + pauli.z[qubit] = oper.z[pos] + phase += pauli.x[qubit] & pauli.z[qubit] # Check if there is a stabilizer that anti-commutes with an odd number of qubits # If so the expectation value is 0 @@ -258,10 +257,11 @@ def expectation_value(self, oper, qargs=None): phase += 2 * np.count_nonzero(pauli_z & stab.X[p]) pauli_z = pauli_z ^ stab.Z[p] + # For valid stabilizers, `phase` can only be 0 (= 1) or 2 (= -1) at this point. if phase % 4 != 0: - return -1 + return -pauli_phase - return 1 + return pauli_phase def probabilities(self, qargs=None, decimals=None): """Return the subsystem measurement probability vector. diff --git a/releasenotes/notes/fix_stabilizerstate_expval-2556c5ee916f5327.yaml b/releasenotes/notes/fix_stabilizerstate_expval-2556c5ee916f5327.yaml new file mode 100644 index 000000000000..2280a546f63d --- /dev/null +++ b/releasenotes/notes/fix_stabilizerstate_expval-2556c5ee916f5327.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix a bug in :class:`~qiskit.quantum_info.StabilizerState` expectation value calculation, + that occured when the Pauli operator has a non-trivial phase. diff --git a/test/python/quantum_info/states/test_stabilizerstate.py b/test/python/quantum_info/states/test_stabilizerstate.py index 10bcba46335c..80ab7f2c5ffb 100644 --- a/test/python/quantum_info/states/test_stabilizerstate.py +++ b/test/python/quantum_info/states/test_stabilizerstate.py @@ -16,7 +16,7 @@ import unittest from test import combine import logging -from ddt import ddt +from ddt import ddt, data, unpack import numpy as np @@ -525,228 +525,21 @@ def test_probs_random_subsystem(self, num_qubits): self.assertTrue(np.allclose(probs, target)) self.assertDictAlmostEqual(probs_dict, target_dict) - def test_expval_single_qubit(self): - """Test expectation_value method of a single qubit""" - - num_qubits = 1 - - qc = QuantumCircuit(num_qubits) - stab = StabilizerState(qc) - pairs = [("Z", 1), ("X", 0), ("Y", 0), ("I", 1)] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(num_qubits) - qc.x(0) - stab = StabilizerState(qc) - pairs = [("Z", -1), ("X", 0), ("Y", 0), ("I", 1)] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(num_qubits) - qc.h(0) - stab = StabilizerState(qc) - pairs = [("Z", 0), ("X", 1), ("Y", 0), ("I", 1)] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - def test_expval_two_qubits(self): - """Test expectation_value method of two qubits""" - - num_qubits = 2 - - qc = QuantumCircuit(num_qubits) - stab = StabilizerState(qc) - pairs = [ - ("II", 1), - ("XX", 0), - ("YY", 0), - ("ZZ", 1), - ("IX", 0), - ("IY", 0), - ("IZ", 1), - ("XY", 0), - ("XZ", 0), - ("YZ", 0), - ] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(num_qubits) - qc.x(0) - qc.x(1) - stab = StabilizerState(qc) - pairs = [ - ("II", 1), - ("XX", 0), - ("YY", 0), - ("ZZ", 1), - ("IX", 0), - ("IY", 0), - ("IZ", -1), - ("XY", 0), - ("XZ", 0), - ("YZ", 0), - ] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(num_qubits) - qc.h(0) - qc.h(1) - stab = StabilizerState(qc) - pairs = [ - ("II", 1), - ("XX", 1), - ("YY", 0), - ("ZZ", 0), - ("IX", 1), - ("IY", 0), - ("IZ", 0), - ("XY", 0), - ("XZ", 0), - ("YZ", 0), - ] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(num_qubits) - qc.x(0) - qc.h(1) - stab = StabilizerState(qc) - pairs = [ - ("II", 1), - ("XX", 0), - ("YY", 0), - ("ZZ", 0), - ("IX", 0), - ("IY", 0), - ("IZ", -1), - ("XY", 0), - ("XZ", -1), - ("YZ", 0), - ] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(num_qubits) - qc.h(0) - qc.cx(0, 1) - stab = StabilizerState(qc) - pairs = [ - ("II", 1), - ("XX", 1), - ("YY", -1), - ("ZZ", 1), - ("IX", 0), - ("IY", 0), - ("IZ", 0), - ("XY", 0), - ("XZ", 0), - ("YZ", 0), - ] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(num_qubits) - qc.h(0) - qc.x(1) - qc.cx(0, 1) - stab = StabilizerState(qc) - pairs = [ - ("II", 1), - ("XX", 1), - ("YY", 1), - ("ZZ", -1), - ("IX", 0), - ("IY", 0), - ("IZ", 0), - ("XY", 0), - ("XZ", 0), - ("YZ", 0), - ] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - - qc = QuantumCircuit(2) - qc.h(0) - qc.cx(0, 1) - qc.sdg(0) - qc.sdg(1) - qc.h(0) - qc.h(1) - stab = StabilizerState(qc) - pairs = [ - ("II", 1), - ("XX", 1), - ("YY", 1), - ("ZZ", -1), - ("IX", 0), - ("IY", 0), - ("IZ", 0), - ("XY", 0), - ("XZ", 0), - ("YZ", 0), - ] - for label, target in pairs: - with self.subTest(msg=f"<{label}>"): - op = Pauli(label) - expval = stab.expectation_value(op) - self.assertEqual(expval, target) - @combine(num_qubits=[2, 3, 4, 5]) - def test_expval_random(self, num_qubits): - """Test expectation_value method of random Cliffords""" + def test_expval_from_random_clifford(self, num_qubits): + """Test that the expectation values for a random Clifford, + where the Pauli operators are all its stabilizers, + are equal to 1.""" for _ in range(self.samples): cliff = random_clifford(num_qubits, seed=self.rng) - op = random_pauli(num_qubits, seed=self.rng) qc = cliff.to_circuit() - stab = StabilizerState(cliff) - exp_val = stab.expectation_value(op) - target = Statevector(qc).expectation_value(op) - self.assertAlmostEqual(exp_val, target) - - @combine(num_qubits=[2, 3, 4, 5]) - def test_expval_random_subsystem(self, num_qubits): - """Test expectation_value method of random Cliffords and a subsystem""" - - for _ in range(self.samples): - cliff = random_clifford(num_qubits, seed=self.rng) - op = random_pauli(2, seed=self.rng) - qargs = np.random.choice(num_qubits, size=2, replace=False) - qc = cliff.to_circuit() - stab = StabilizerState(cliff) - exp_val = stab.expectation_value(op, qargs) - target = Statevector(qc).expectation_value(op, qargs) - self.assertAlmostEqual(exp_val, target) + stab = StabilizerState(qc) + stab_gen = stab.clifford.to_dict()["stabilizer"] + for i in range(num_qubits): + op = Pauli(stab_gen[i]) + exp_val = stab.expectation_value(op) + self.assertEqual(exp_val, 1) def test_sample_counts_reset_bell(self): """Test sample_counts after reset for Bell state""" @@ -880,5 +673,269 @@ def test_sample_counts_memory_superposition(self): self.assertEqual(set(memory), set(target)) +@ddt +class TestStabilizerStateExpectationValue(QiskitTestCase): + """Tests for StabilizerState.expectation_value method.""" + + rng = np.random.default_rng(12345) + samples = 10 + shots = 1000 + threshold = 0.1 * shots + + @data(("Z", 1), ("X", 0), ("Y", 0), ("I", 1), ("Z", 1), ("-Z", -1), ("iZ", 1j), ("-iZ", -1j)) + @unpack + def test_expval_single_qubit_0(self, label, target): + """Test expectation_value method of a single qubit on |0>""" + qc = QuantumCircuit(1) + stab = StabilizerState(qc) + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data(("Z", -1), ("X", 0), ("Y", 0), ("I", 1)) + @unpack + def test_expval_single_qubit_1(self, label, target): + """Test expectation_value method of a single qubit on |1>""" + qc = QuantumCircuit(1) + qc.x(0) + stab = StabilizerState(qc) + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data(("Z", 0), ("X", 1), ("Y", 0), ("I", 1), ("X", 1), ("-X", -1), ("iX", 1j), ("-iX", -1j)) + @unpack + def test_expval_single_qubit_plus(self, label, target): + """Test expectation_value method of a single qubit on |+>""" + qc = QuantumCircuit(1) + qc.h(0) + stab = StabilizerState(qc) + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data( + ("II", 1), + ("XX", 0), + ("YY", 0), + ("ZZ", 1), + ("IX", 0), + ("IY", 0), + ("IZ", 1), + ("XY", 0), + ("XZ", 0), + ("YZ", 0), + ("-ZZ", -1), + ("iZZ", 1j), + ("-iZZ", -1j), + ) + @unpack + def test_expval_two_qubits_00(self, label, target): + """Test expectation_value method of two qubits in |00>""" + + num_qubits = 2 + + qc = QuantumCircuit(num_qubits) + stab = StabilizerState(qc) + + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data( + ("II", 1), + ("XX", 0), + ("YY", 0), + ("ZZ", 1), + ("IX", 0), + ("IY", 0), + ("IZ", -1), + ("XY", 0), + ("XZ", 0), + ("YZ", 0), + ) + @unpack + def test_expval_two_qubits_11(self, label, target): + """Test expectation_value method of two qubits in |11>""" + qc = QuantumCircuit(2) + qc.x(0) + qc.x(1) + stab = StabilizerState(qc) + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data( + ("II", 1), + ("XX", 1), + ("YY", 0), + ("ZZ", 0), + ("IX", 1), + ("IY", 0), + ("IZ", 0), + ("XY", 0), + ("XZ", 0), + ("YZ", 0), + ("-XX", -1), + ("iXX", 1j), + ("-iXX", -1j), + ) + @unpack + def test_expval_two_qubits_plusplus(self, label, target): + """Test expectation_value method of two qubits in |++>""" + qc = QuantumCircuit(2) + qc.h(0) + qc.h(1) + stab = StabilizerState(qc) + + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data( + ("II", 1), + ("XX", 0), + ("YY", 0), + ("ZZ", 0), + ("IX", 0), + ("IY", 0), + ("IZ", -1), + ("XY", 0), + ("XZ", -1), + ("YZ", 0), + ) + @unpack + def test_expval_two_qubits_plus1(self, label, target): + """Test expectation_value method of two qubits in |+1>""" + qc = QuantumCircuit(2) + qc.x(0) + qc.h(1) + stab = StabilizerState(qc) + + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data( + ("II", 1), + ("XX", 1), + ("YY", -1), + ("ZZ", 1), + ("IX", 0), + ("IY", 0), + ("IZ", 0), + ("XY", 0), + ("XZ", 0), + ("YZ", 0), + ("-YY", 1), + ("iYY", -1j), + ("-iYY", 1j), + ) + @unpack + def test_expval_two_qubits_bell_phi_plus(self, label, target): + """Test expectation_value method of two qubits in bell phi plus""" + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + stab = StabilizerState(qc) + + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data( + ("II", 1), + ("XX", 1), + ("YY", 1), + ("ZZ", -1), + ("IX", 0), + ("IY", 0), + ("IZ", 0), + ("XY", 0), + ("XZ", 0), + ("YZ", 0), + ("-XX", -1), + ("-YY", -1), + ("iXX", 1j), + ("iYY", 1j), + ("-iXX", -1j), + ("-iYY", -1j), + ) + @unpack + def test_expval_two_qubits_bell_phi_minus(self, label, target): + """Test expectation_value method of two qubits in bell phi minus""" + + qc = QuantumCircuit(2) + qc.h(0) + qc.x(1) + qc.cx(0, 1) + stab = StabilizerState(qc) + + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @data( + ("II", 1), + ("XX", 1), + ("YY", 1), + ("ZZ", -1), + ("IX", 0), + ("IY", 0), + ("IZ", 0), + ("XY", 0), + ("XZ", 0), + ("YZ", 0), + ("-XX", -1), + ("-YY", -1), + ("iXX", 1j), + ("iYY", 1j), + ("-iXX", -1j), + ("-iYY", -1j), + ) + @unpack + def test_expval_two_qubits_bell_sdg_h(self, label, target): + """Test expectation_value method of two qubits in bell followed by sdg and h""" + qc = QuantumCircuit(2) + qc.h(0) + qc.cx(0, 1) + qc.sdg(0) + qc.sdg(1) + qc.h(0) + qc.h(1) + stab = StabilizerState(qc) + + op = Pauli(label) + expval = stab.expectation_value(op) + self.assertEqual(expval, target) + + @combine(num_qubits=[2, 3, 4, 5]) + def test_expval_random(self, num_qubits): + """Test expectation_value method of random Cliffords""" + + for _ in range(self.samples): + cliff = random_clifford(num_qubits, seed=self.rng) + op = random_pauli(num_qubits, group_phase=True, seed=self.rng) + qc = cliff.to_circuit() + stab = StabilizerState(cliff) + exp_val = stab.expectation_value(op) + target = Statevector(qc).expectation_value(op) + self.assertAlmostEqual(exp_val, target) + + @combine(num_qubits=[2, 3, 4, 5]) + def test_expval_random_subsystem(self, num_qubits): + """Test expectation_value method of random Cliffords and a subsystem""" + + for _ in range(self.samples): + cliff = random_clifford(num_qubits, seed=self.rng) + op = random_pauli(2, group_phase=True, seed=self.rng) + qargs = np.random.choice(num_qubits, size=2, replace=False) + qc = cliff.to_circuit() + stab = StabilizerState(cliff) + exp_val = stab.expectation_value(op, qargs) + target = Statevector(qc).expectation_value(op, qargs) + self.assertAlmostEqual(exp_val, target) + + if __name__ == "__main__": unittest.main()