-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Synthesis of a stabilizer state for LNN connectivity (#9734)
* minor * add synthesis of a stabilizer state into layers * add a test of StabilizerState decomposition * add stabilizer state synthesis for LNN connectivity and tests * updates following review comments * add documentation * minor update * add release notes * minor update * add a test for a reduced inverse Clifford * improved stabilizer synthesis. Co-authored-by: alexi@il.ibm.com * minor updates following review * combining tests
- Loading branch information
1 parent
2ebf81e
commit 93ab1f9
Showing
7 changed files
with
327 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
"""Module containing stabilizer state preparation circuit synthesis.""" | ||
|
||
from .stabilizer_decompose import synth_stabilizer_layers, synth_stabilizer_depth_lnn |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
""" | ||
Circuit synthesis for a stabilizer state preparation circuit. | ||
""" | ||
# pylint: disable=invalid-name | ||
|
||
import numpy as np | ||
from qiskit.circuit import QuantumCircuit | ||
from qiskit.exceptions import QiskitError | ||
from qiskit.quantum_info.states import StabilizerState | ||
from qiskit.synthesis.linear.linear_matrix_utils import ( | ||
calc_inverse_matrix, | ||
) | ||
from qiskit.synthesis.linear_phase import synth_cz_depth_line_mr | ||
from qiskit.synthesis.clifford.clifford_decompose_layers import ( | ||
_default_cz_synth_func, | ||
_reverse_clifford, | ||
_create_graph_state, | ||
_decompose_graph_state, | ||
) | ||
|
||
|
||
def synth_stabilizer_layers( | ||
stab, | ||
cz_synth_func=_default_cz_synth_func, | ||
cz_func_reverse_qubits=False, | ||
validate=False, | ||
): | ||
"""Synthesis of a stabilizer state into layers. | ||
It provides a similar decomposition to the synthesis described in Lemma 8 of Bravyi and Maslov, | ||
without the initial Hadamard-free sub-circuit which do not affect the stabilizer state. | ||
For example, a 5-qubit stabilizer state is decomposed into the following layers: | ||
.. parsed-literal:: | ||
┌─────┐┌─────┐┌─────┐┌─────┐┌────────┐ | ||
q_0: ┤0 ├┤0 ├┤0 ├┤0 ├┤0 ├ | ||
│ ││ ││ ││ ││ │ | ||
q_1: ┤1 ├┤1 ├┤1 ├┤1 ├┤1 ├ | ||
│ ││ ││ ││ ││ │ | ||
q_2: ┤2 H2 ├┤2 S1 ├┤2 CZ ├┤2 H1 ├┤2 Pauli ├ | ||
│ ││ ││ ││ ││ │ | ||
q_3: ┤3 ├┤3 ├┤3 ├┤3 ├┤3 ├ | ||
│ ││ ││ ││ ││ │ | ||
q_4: ┤4 ├┤4 ├┤4 ├┤4 ├┤4 ├ | ||
└─────┘└─────┘└─────┘└─────┘└────────┘ | ||
Args: | ||
stab (StabilizerState): a stabilizer state. | ||
cz_synth_func (Callable): a function to decompose the CZ sub-circuit. | ||
It gets as input a boolean symmetric matrix, and outputs a QuantumCircuit. | ||
validate (Boolean): if True, validates the synthesis process. | ||
cz_func_reverse_qubits (Boolean): True only if cz_synth_func is synth_cz_depth_line_mr, | ||
since this function returns a circuit that reverts the order of qubits. | ||
Return: | ||
QuantumCircuit: a circuit implementation of the stabilizer state. | ||
Raises: | ||
QiskitError: if the input is not a StabilizerState. | ||
Reference: | ||
1. S. Bravyi, D. Maslov, *Hadamard-free circuits expose the | ||
structure of the Clifford group*, | ||
`arXiv:2003.09412 [quant-ph] <https://arxiv.org/abs/2003.09412>`_ | ||
""" | ||
|
||
if not isinstance(stab, StabilizerState): | ||
raise QiskitError("The input is not a StabilizerState.") | ||
|
||
cliff = stab.clifford | ||
num_qubits = cliff.num_qubits | ||
|
||
if cz_func_reverse_qubits: | ||
cliff0 = _reverse_clifford(cliff) | ||
else: | ||
cliff0 = cliff | ||
|
||
H1_circ, cliff1 = _create_graph_state(cliff0, validate=validate) | ||
|
||
H2_circ, CZ1_circ, S1_circ, _ = _decompose_graph_state( | ||
cliff1, validate=validate, cz_synth_func=cz_synth_func | ||
) | ||
|
||
qubit_list = list(range(num_qubits)) | ||
layeredCircuit = QuantumCircuit(num_qubits) | ||
|
||
layeredCircuit.append(H2_circ, qubit_list) | ||
layeredCircuit.append(S1_circ, qubit_list) | ||
layeredCircuit.append(CZ1_circ, qubit_list) | ||
|
||
if cz_func_reverse_qubits: | ||
H1_circ = H1_circ.reverse_bits() | ||
layeredCircuit.append(H1_circ, qubit_list) | ||
|
||
# Add Pauli layer to fix the Clifford phase signs | ||
# pylint: disable=cyclic-import | ||
from qiskit.quantum_info.operators.symplectic import Clifford | ||
|
||
clifford_target = Clifford(layeredCircuit) | ||
pauli_circ = _calc_pauli_diff_stabilizer(cliff, clifford_target) | ||
layeredCircuit.append(pauli_circ, qubit_list) | ||
|
||
return layeredCircuit | ||
|
||
|
||
def _calc_pauli_diff_stabilizer(cliff, cliff_target): | ||
"""Given two Cliffords whose stabilizers differ by a Pauli, we find this Pauli.""" | ||
|
||
# pylint: disable=cyclic-import | ||
from qiskit.quantum_info.operators.symplectic import Pauli | ||
|
||
num_qubits = cliff.num_qubits | ||
if cliff.num_qubits != cliff_target.num_qubits: | ||
raise QiskitError("num_qubits is not the same for the original clifford and the target.") | ||
|
||
# stabilizer generators of the original clifford | ||
stab_gen = StabilizerState(cliff).clifford.to_dict()["stabilizer"] | ||
|
||
# stabilizer state of the target clifford | ||
ts = StabilizerState(cliff_target) | ||
|
||
phase_destab = [False] * num_qubits | ||
phase_stab = [ts.expectation_value(Pauli(stab_gen[i])) == -1 for i in range(num_qubits)] | ||
|
||
phase = [] | ||
phase.extend(phase_destab) | ||
phase.extend(phase_stab) | ||
phase = np.array(phase, dtype=int) | ||
|
||
A = cliff.symplectic_matrix.astype(int) | ||
Ainv = calc_inverse_matrix(A) | ||
|
||
# By carefully writing how X, Y, Z gates affect each qubit, all we need to compute | ||
# is A^{-1} * (phase) | ||
C = np.matmul(Ainv, phase) % 2 | ||
|
||
# Create the Pauli | ||
pauli_circ = QuantumCircuit(num_qubits, name="Pauli") | ||
for k in range(num_qubits): | ||
destab = C[k] | ||
stab = C[k + num_qubits] | ||
if stab and destab: | ||
pauli_circ.y(k) | ||
elif stab: | ||
pauli_circ.x(k) | ||
elif destab: | ||
pauli_circ.z(k) | ||
|
||
return pauli_circ | ||
|
||
|
||
def synth_stabilizer_depth_lnn(stab): | ||
"""Synthesis of an n-qubit stabilizer state for linear-nearest neighbour connectivity, | ||
in 2-qubit depth 2*n+2 and two distinct CX layers, using CX and phase gates (S, Sdg or Z). | ||
Args: | ||
stab (StabilizerState): a stabilizer state. | ||
Return: | ||
QuantumCircuit: a circuit implementation of the stabilizer state. | ||
Reference: | ||
1. S. Bravyi, D. Maslov, *Hadamard-free circuits expose the | ||
structure of the Clifford group*, | ||
`arXiv:2003.09412 [quant-ph] <https://arxiv.org/abs/2003.09412>`_ | ||
2. Dmitri Maslov, Martin Roetteler, | ||
*Shorter stabilizer circuits via Bruhat decomposition and quantum circuit transformations*, | ||
`arXiv:1705.09176 <https://arxiv.org/abs/1705.09176>`_. | ||
""" | ||
|
||
circ = synth_stabilizer_layers( | ||
stab, | ||
cz_synth_func=synth_cz_depth_line_mr, | ||
cz_func_reverse_qubits=True, | ||
) | ||
return circ |
11 changes: 11 additions & 0 deletions
11
releasenotes/notes/stabilizer_state_synthesis-c48c0389941715a6.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
--- | ||
features: | ||
- | | ||
Add a new synthesis method :class:`.synth_stabilizer_layers` of a stabilizer state into layers. | ||
It provides a similar decomposition to the synthesis described in Lemma 8 of Bravyi and Maslov, | ||
(arxiv:2003.09412) without the initial Hadamard-free sub-circuit which does not affect the stabilizer state. | ||
- | | ||
Add a new synthesis method :class:`.synth_stabilizer_lnn` of a stabilizer state | ||
for linear nearest neighbor connectivity in 2-qubit depth of 2n+2 and two distinct CX layers, | ||
using CX and phase gates (S, Sdg or Z). | ||
The synthesis algorithm is based on the paper of Maslov and Roetteler (https://arxiv.org/abs/1705.09176). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
"""Tests for stabilizer state synthesis methods.""" | ||
|
||
|
||
import unittest | ||
from test import combine | ||
from ddt import ddt | ||
|
||
import numpy as np | ||
|
||
from qiskit.test import QiskitTestCase | ||
from qiskit.quantum_info.states import StabilizerState | ||
from qiskit.quantum_info import random_clifford | ||
from qiskit.synthesis.stabilizer import synth_stabilizer_layers, synth_stabilizer_depth_lnn | ||
from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity | ||
|
||
|
||
@ddt | ||
class TestStabDecomposeLayers(QiskitTestCase): | ||
"""Tests for stabilizer state decomposition functions.""" | ||
|
||
@combine(num_qubits=[4, 5, 6, 7]) | ||
def test_decompose_stab(self, num_qubits): | ||
"""Create layer decomposition for a stabilizer state, and check that it | ||
results in an equivalent stabilizer state.""" | ||
rng = np.random.default_rng(1234) | ||
samples = 10 | ||
for _ in range(samples): | ||
cliff = random_clifford(num_qubits, seed=rng) | ||
stab = StabilizerState(cliff) | ||
circ = synth_stabilizer_layers(stab, validate=True) | ||
stab_target = StabilizerState(circ) | ||
# Verify that the two stabilizers generate the same state | ||
self.assertTrue(stab.equiv(stab_target)) | ||
# Verify that the two stabilizers produce the same probabilities | ||
self.assertEqual(stab.probabilities_dict(), stab_target.probabilities_dict()) | ||
# Verify the layered structure | ||
self.assertEqual(circ.data[0].operation.name, "H2") | ||
self.assertEqual(circ.data[1].operation.name, "S1") | ||
self.assertEqual(circ.data[2].operation.name, "CZ") | ||
self.assertEqual(circ.data[3].operation.name, "H1") | ||
self.assertEqual(circ.data[4].operation.name, "Pauli") | ||
|
||
@combine(num_qubits=[4, 5, 6, 7]) | ||
def test_decompose_lnn_depth(self, num_qubits): | ||
"""Test stabilizer state decomposition for linear-nearest-neighbour (LNN) connectivity.""" | ||
rng = np.random.default_rng(1234) | ||
samples = 10 | ||
for _ in range(samples): | ||
cliff = random_clifford(num_qubits, seed=rng) | ||
stab = StabilizerState(cliff) | ||
circ = synth_stabilizer_depth_lnn(stab) | ||
# Check that the stabilizer state circuit 2-qubit depth equals 2*n+2 | ||
depth2q = (circ.decompose()).depth( | ||
filter_function=lambda x: x.operation.num_qubits == 2 | ||
) | ||
self.assertTrue(depth2q == 2 * num_qubits + 2) | ||
# Check that the stabilizer state circuit has linear nearest neighbour connectivity | ||
self.assertTrue(check_lnn_connectivity(circ.decompose())) | ||
stab_target = StabilizerState(circ) | ||
# Verify that the two stabilizers generate the same state | ||
self.assertTrue(stab.equiv(stab_target)) | ||
# Verify that the two stabilizers produce the same probabilities | ||
self.assertEqual(stab.probabilities_dict(), stab_target.probabilities_dict()) | ||
|
||
@combine(num_qubits=[4, 5], method_lnn=[True, False]) | ||
def test_reduced_inverse_clifford(self, num_qubits, method_lnn): | ||
"""Test that one can use this stabilizer state synthesis method to calculate an inverse Clifford | ||
that preserves the ground state |0...0>, with a reduced circuit depth. | ||
This is useful for multi-qubit Randomized Benchmarking.""" | ||
rng = np.random.default_rng(5678) | ||
samples = 5 | ||
for _ in range(samples): | ||
cliff = random_clifford(num_qubits, seed=rng) | ||
circ_orig = cliff.to_circuit() | ||
stab = StabilizerState(cliff) | ||
# Calculate the reduced-depth inverse Clifford | ||
if method_lnn: | ||
circ_inv = synth_stabilizer_depth_lnn(stab).inverse() | ||
else: | ||
circ_inv = synth_stabilizer_layers(stab, validate=True).inverse() | ||
circ = circ_orig.compose(circ_inv) | ||
stab = StabilizerState(circ) | ||
# Verify that we get back the ground state |0...0> with probability 1 | ||
target_probs = {"0" * num_qubits: 1} | ||
self.assertEqual(stab.probabilities_dict(), target_probs) | ||
|
||
|
||
if __name__ == "__main__": | ||
unittest.main() |