Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add standalone test file for Clifford synthesis functions #12347

Merged
merged 6 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions qiskit/circuit/library/generalized_gates/linear_function.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2021.
# (C) Copyright IBM 2017, 2024.
#
# 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
Expand All @@ -16,7 +16,6 @@
import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate
from qiskit.circuit.exceptions import CircuitError
from qiskit.synthesis.linear import check_invertible_binary_matrix
from qiskit.circuit.library.generalized_gates.permutation import PermutationGate

# pylint: disable=cyclic-import
Expand Down Expand Up @@ -115,6 +114,8 @@ def __init__(

# Optionally, check that the matrix is invertible
if validate_input:
from qiskit.synthesis.linear import check_invertible_binary_matrix

if not check_invertible_binary_matrix(linear):
raise CircuitError(
"A linear function must be represented by an invertible matrix."
Expand Down
2 changes: 1 addition & 1 deletion qiskit/circuit/library/generalized_gates/uc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
# (C) Copyright IBM 2020, 2024.
#
# 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
Expand Down
18 changes: 10 additions & 8 deletions qiskit/circuit/library/generalized_gates/unitary.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
# (C) Copyright IBM 2017, 2024.
#
# 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
Expand Down Expand Up @@ -30,14 +30,8 @@
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit.quantum_info.operators.predicates import is_unitary_matrix

# pylint: disable=cyclic-import
from qiskit.synthesis.one_qubit.one_qubit_decompose import OneQubitEulerDecomposer
from qiskit.synthesis.two_qubit.two_qubit_decompose import two_qubit_cnot_decompose

from .isometry import Isometry

_DECOMPOSER1Q = OneQubitEulerDecomposer("U")

if typing.TYPE_CHECKING:
from qiskit.quantum_info.operators.base_operator import BaseOperator

Expand Down Expand Up @@ -143,13 +137,21 @@ def transpose(self):
def _define(self):
"""Calculate a subcircuit that implements this unitary."""
if self.num_qubits == 1:
from qiskit.synthesis.one_qubit.one_qubit_decompose import OneQubitEulerDecomposer

q = QuantumRegister(1, "q")
qc = QuantumCircuit(q, name=self.name)
theta, phi, lam, global_phase = _DECOMPOSER1Q.angles_and_phase(self.to_matrix())
theta, phi, lam, global_phase = OneQubitEulerDecomposer("U").angles_and_phase(
self.to_matrix()
)
qc._append(UGate(theta, phi, lam), [q[0]], [])
qc.global_phase = global_phase
self.definition = qc
elif self.num_qubits == 2:
from qiskit.synthesis.two_qubit.two_qubit_decompose import ( # pylint: disable=cyclic-import
two_qubit_cnot_decompose,
)

self.definition = two_qubit_cnot_decompose(self.to_matrix())
else:
from qiskit.synthesis.unitary.qsd import ( # pylint: disable=cyclic-import
Expand Down
5 changes: 3 additions & 2 deletions qiskit/circuit/library/n_local/evolved_operator_ansatz.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
# (C) Copyright IBM 2021, 2024.
#
# 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
Expand All @@ -22,7 +22,6 @@
from qiskit.circuit.quantumregister import QuantumRegister
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.quantum_info import Operator, Pauli, SparsePauliOp
from qiskit.synthesis.evolution import LieTrotter

from .n_local import NLocal

Expand Down Expand Up @@ -185,6 +184,8 @@ def _evolve_operator(self, operator, time):
gate = HamiltonianGate(operator, time)
# otherwise, use the PauliEvolutionGate
else:
from qiskit.synthesis.evolution import LieTrotter

evolution = LieTrotter() if self._evolution is None else self._evolution
gate = PauliEvolutionGate(operator, time, synthesis=evolution)

Expand Down
10 changes: 7 additions & 3 deletions qiskit/circuit/library/pauli_evolution.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021, 2023.
# (C) Copyright IBM 2021, 2024.
#
# 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
Expand All @@ -14,14 +14,16 @@

from __future__ import annotations

from typing import Union, Optional
from typing import Union, Optional, TYPE_CHECKING
import numpy as np

from qiskit.circuit.gate import Gate
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.synthesis.evolution import EvolutionSynthesis, LieTrotter
from qiskit.quantum_info import Pauli, SparsePauliOp

if TYPE_CHECKING:
from qiskit.synthesis.evolution import EvolutionSynthesis


class PauliEvolutionGate(Gate):
r"""Time-evolution of an operator consisting of Paulis.
Expand Down Expand Up @@ -107,6 +109,8 @@ class docstring for an example.
operator = _to_sparse_pauli_op(operator)

if synthesis is None:
from qiskit.synthesis.evolution import LieTrotter

synthesis = LieTrotter()

if label is None:
Expand Down
6 changes: 4 additions & 2 deletions qiskit/quantum_info/operators/dihedral/random.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2019, 2021.
# (C) Copyright IBM 2019, 2024.
#
# 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
Expand Down Expand Up @@ -49,7 +49,9 @@ def random_cnotdihedral(num_qubits, seed=None):

# Random affine function
# Random invertible binary matrix
from qiskit.synthesis.linear import random_invertible_binary_matrix
from qiskit.synthesis.linear import ( # pylint: disable=cyclic-import
random_invertible_binary_matrix,
)

linear = random_invertible_binary_matrix(num_qubits, seed=rng)
elem.linear = linear
Expand Down
6 changes: 3 additions & 3 deletions qiskit/synthesis/clifford/clifford_decompose_bm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021, 2022.
# (C) Copyright IBM 2021, 2024.
#
# 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
Expand Down Expand Up @@ -76,11 +76,11 @@ def synth_clifford_bm(clifford: Clifford) -> QuantumCircuit:
pos = [qubit, qubit + num_qubits]
circ = _decompose_clifford_1q(clifford.tableau[pos][:, pos + [-1]])
if len(circ) > 0:
ret_circ.append(circ, [qubit])
ret_circ.append(circ, [qubit], copy=False)

# Add the inverse of the 2-qubit reductions circuit
if len(inv_circuit) > 0:
ret_circ.append(inv_circuit.inverse(), range(num_qubits))
ret_circ.append(inv_circuit.inverse(), range(num_qubits), copy=False)

return ret_circ.decompose()

Expand Down
18 changes: 9 additions & 9 deletions qiskit/synthesis/clifford/clifford_decompose_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,32 +137,32 @@ def synth_clifford_layers(
cz_func_reverse_qubits=cz_func_reverse_qubits,
)

layeredCircuit.append(S2_circ, qubit_list)
layeredCircuit.append(S2_circ, qubit_list, copy=False)

if cx_cz_synth_func is None:
layeredCircuit.append(CZ2_circ, qubit_list)
layeredCircuit.append(CZ2_circ, qubit_list, copy=False)

CXinv = CX_circ.copy().inverse()
layeredCircuit.append(CXinv, qubit_list)
layeredCircuit.append(CXinv, qubit_list, copy=False)

else:
# note that CZ2_circ is None and built into the CX_circ when
# cx_cz_synth_func is not None
layeredCircuit.append(CX_circ, qubit_list)
layeredCircuit.append(CX_circ, qubit_list, copy=False)

layeredCircuit.append(H2_circ, qubit_list)
layeredCircuit.append(S1_circ, qubit_list)
layeredCircuit.append(CZ1_circ, qubit_list)
layeredCircuit.append(H2_circ, qubit_list, copy=False)
layeredCircuit.append(S1_circ, qubit_list, copy=False)
layeredCircuit.append(CZ1_circ, qubit_list, copy=False)

if cz_func_reverse_qubits:
H1_circ = H1_circ.reverse_bits()
layeredCircuit.append(H1_circ, qubit_list)
layeredCircuit.append(H1_circ, qubit_list, copy=False)

# Add Pauli layer to fix the Clifford phase signs

clifford_target = Clifford(layeredCircuit)
pauli_circ = _calc_pauli_diff(cliff, clifford_target)
layeredCircuit.append(pauli_circ, qubit_list)
layeredCircuit.append(pauli_circ, qubit_list, copy=False)

return layeredCircuit

Expand Down
94 changes: 1 addition & 93 deletions test/python/quantum_info/operators/symplectic/test_clifford.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2023.
# (C) Copyright IBM 2017, 2024.
#
# 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
Expand Down Expand Up @@ -57,12 +57,6 @@
from qiskit.quantum_info.operators import Clifford, Operator
from qiskit.quantum_info.operators.predicates import matrix_equal
from qiskit.quantum_info.operators.symplectic.clifford_circuits import _append_operation
from qiskit.synthesis.clifford import (
synth_clifford_full,
synth_clifford_ag,
synth_clifford_bm,
synth_clifford_greedy,
)
from qiskit.synthesis.linear import random_invertible_binary_matrix
from test import QiskitTestCase # pylint: disable=wrong-import-order
from test import combine # pylint: disable=wrong-import-order
Expand Down Expand Up @@ -588,92 +582,6 @@ def test_from_circuit_with_all_types(self):
self.assertEqual(combined_clifford, expected_clifford)


@ddt
class TestCliffordSynthesis(QiskitTestCase):
"""Test Clifford synthesis methods."""

@staticmethod
def _cliffords_1q():
clifford_dicts = [
{"stabilizer": ["+Z"], "destabilizer": ["-X"]},
{"stabilizer": ["-Z"], "destabilizer": ["+X"]},
{"stabilizer": ["-Z"], "destabilizer": ["-X"]},
{"stabilizer": ["+Z"], "destabilizer": ["+Y"]},
{"stabilizer": ["+Z"], "destabilizer": ["-Y"]},
{"stabilizer": ["-Z"], "destabilizer": ["+Y"]},
{"stabilizer": ["-Z"], "destabilizer": ["-Y"]},
{"stabilizer": ["+X"], "destabilizer": ["+Z"]},
{"stabilizer": ["+X"], "destabilizer": ["-Z"]},
{"stabilizer": ["-X"], "destabilizer": ["+Z"]},
{"stabilizer": ["-X"], "destabilizer": ["-Z"]},
{"stabilizer": ["+X"], "destabilizer": ["+Y"]},
{"stabilizer": ["+X"], "destabilizer": ["-Y"]},
{"stabilizer": ["-X"], "destabilizer": ["+Y"]},
{"stabilizer": ["-X"], "destabilizer": ["-Y"]},
{"stabilizer": ["+Y"], "destabilizer": ["+X"]},
{"stabilizer": ["+Y"], "destabilizer": ["-X"]},
{"stabilizer": ["-Y"], "destabilizer": ["+X"]},
{"stabilizer": ["-Y"], "destabilizer": ["-X"]},
{"stabilizer": ["+Y"], "destabilizer": ["+Z"]},
{"stabilizer": ["+Y"], "destabilizer": ["-Z"]},
{"stabilizer": ["-Y"], "destabilizer": ["+Z"]},
{"stabilizer": ["-Y"], "destabilizer": ["-Z"]},
]
return [Clifford.from_dict(i) for i in clifford_dicts]

def test_decompose_1q(self):
"""Test synthesis for all 1-qubit Cliffords"""
for cliff in self._cliffords_1q():
with self.subTest(msg=f"Test circuit {cliff}"):
target = cliff
value = Clifford(cliff.to_circuit())
self.assertEqual(target, value)

@combine(num_qubits=[2, 3])
def test_synth_bm(self, num_qubits):
"""Test B&M synthesis for set of {num_qubits}-qubit Cliffords"""
rng = np.random.default_rng(1234)
samples = 50
for _ in range(samples):
circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng)
target = Clifford(circ)
value = Clifford(synth_clifford_bm(target))
self.assertEqual(value, target)

@combine(num_qubits=[2, 3, 4, 5])
def test_synth_ag(self, num_qubits):
"""Test A&G synthesis for set of {num_qubits}-qubit Cliffords"""
rng = np.random.default_rng(1234)
samples = 50
for _ in range(samples):
circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng)
target = Clifford(circ)
value = Clifford(synth_clifford_ag(target))
self.assertEqual(value, target)

@combine(num_qubits=[1, 2, 3, 4, 5])
def test_synth_greedy(self, num_qubits):
"""Test greedy synthesis for set of {num_qubits}-qubit Cliffords"""
rng = np.random.default_rng(1234)
samples = 50
for _ in range(samples):
circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng)
target = Clifford(circ)
value = Clifford(synth_clifford_greedy(target))
self.assertEqual(value, target)

@combine(num_qubits=[1, 2, 3, 4, 5])
def test_synth_full(self, num_qubits):
"""Test synthesis for set of {num_qubits}-qubit Cliffords"""
rng = np.random.default_rng(1234)
samples = 50
for _ in range(samples):
circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng)
target = Clifford(circ)
value = Clifford(synth_clifford_full(target))
self.assertEqual(value, target)


@ddt
class TestCliffordDecomposition(QiskitTestCase):
"""Test Clifford decompositions."""
Expand Down
Loading
Loading