diff --git a/qiskit/circuit/library/generalized_gates/linear_function.py b/qiskit/circuit/library/generalized_gates/linear_function.py index 68deaddd732..519a306c357 100644 --- a/qiskit/circuit/library/generalized_gates/linear_function.py +++ b/qiskit/circuit/library/generalized_gates/linear_function.py @@ -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 @@ -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 @@ -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." diff --git a/qiskit/circuit/library/generalized_gates/uc.py b/qiskit/circuit/library/generalized_gates/uc.py index f54567123e0..6e6a1db95ca 100644 --- a/qiskit/circuit/library/generalized_gates/uc.py +++ b/qiskit/circuit/library/generalized_gates/uc.py @@ -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 diff --git a/qiskit/circuit/library/generalized_gates/unitary.py b/qiskit/circuit/library/generalized_gates/unitary.py index 1fd36e52e0c..6a6623ffce5 100644 --- a/qiskit/circuit/library/generalized_gates/unitary.py +++ b/qiskit/circuit/library/generalized_gates/unitary.py @@ -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 @@ -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 @@ -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 diff --git a/qiskit/circuit/library/n_local/evolved_operator_ansatz.py b/qiskit/circuit/library/n_local/evolved_operator_ansatz.py index a50b48ce488..4bc6bcc58a1 100644 --- a/qiskit/circuit/library/n_local/evolved_operator_ansatz.py +++ b/qiskit/circuit/library/n_local/evolved_operator_ansatz.py @@ -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 @@ -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 @@ -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) diff --git a/qiskit/circuit/library/pauli_evolution.py b/qiskit/circuit/library/pauli_evolution.py index c6d69789bae..b0af3fbe416 100644 --- a/qiskit/circuit/library/pauli_evolution.py +++ b/qiskit/circuit/library/pauli_evolution.py @@ -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 @@ -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. @@ -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: diff --git a/qiskit/circuit/random/__init__.py b/qiskit/circuit/random/__init__.py index 3e3dc752d5a..06e817bb4de 100644 --- a/qiskit/circuit/random/__init__.py +++ b/qiskit/circuit/random/__init__.py @@ -12,4 +12,4 @@ """Method for generating random circuits.""" -from .utils import random_circuit +from .utils import random_circuit, random_clifford_circuit diff --git a/qiskit/circuit/random/utils.py b/qiskit/circuit/random/utils.py index 71809735aa8..fc497a300cb 100644 --- a/qiskit/circuit/random/utils.py +++ b/qiskit/circuit/random/utils.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017. +# (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 @@ -207,3 +207,72 @@ def random_circuit( qc.measure(qc.qubits, cr) return qc + + +def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None): + """Generate a pseudo-random Clifford circuit. + + This function will generate a Clifford circuit by randomly selecting the chosen amount of Clifford + gates from the set of standard gates in :mod:`qiskit.circuit.library.standard_gates`. For example: + + .. plot:: + :include-source: + + from qiskit.circuit.random import random_clifford_circuit + + circ = random_clifford_circuit(num_qubits=2, num_gates=6) + circ.draw(output='mpl') + + Args: + num_qubits (int): number of quantum wires. + num_gates (int): number of gates in the circuit. + gates (list[str]): optional list of Clifford gate names to randomly sample from. + If ``"all"`` (default), use all Clifford gates in the standard library. + seed (int | np.random.Generator): sets random seed/generator (optional). + + Returns: + QuantumCircuit: constructed circuit + """ + + gates_1q = ["i", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg"] + gates_2q = ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"] + if gates == "all": + if num_qubits == 1: + gates = gates_1q + else: + gates = gates_1q + gates_2q + + instructions = { + "i": (standard_gates.IGate(), 1), + "x": (standard_gates.XGate(), 1), + "y": (standard_gates.YGate(), 1), + "z": (standard_gates.ZGate(), 1), + "h": (standard_gates.HGate(), 1), + "s": (standard_gates.SGate(), 1), + "sdg": (standard_gates.SdgGate(), 1), + "sx": (standard_gates.SXGate(), 1), + "sxdg": (standard_gates.SXdgGate(), 1), + "cx": (standard_gates.CXGate(), 2), + "cy": (standard_gates.CYGate(), 2), + "cz": (standard_gates.CZGate(), 2), + "swap": (standard_gates.SwapGate(), 2), + "iswap": (standard_gates.iSwapGate(), 2), + "ecr": (standard_gates.ECRGate(), 2), + "dcx": (standard_gates.DCXGate(), 2), + } + + if isinstance(seed, np.random.Generator): + rng = seed + else: + rng = np.random.default_rng(seed) + + samples = rng.choice(gates, num_gates) + + circ = QuantumCircuit(num_qubits) + + for name in samples: + gate, nqargs = instructions[name] + qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() + circ.append(gate, qargs, copy=False) + + return circ diff --git a/qiskit/quantum_info/operators/dihedral/random.py b/qiskit/quantum_info/operators/dihedral/random.py index f339cf98377..4331d618d73 100644 --- a/qiskit/quantum_info/operators/dihedral/random.py +++ b/qiskit/quantum_info/operators/dihedral/random.py @@ -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 @@ -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 diff --git a/qiskit/synthesis/clifford/clifford_decompose_bm.py b/qiskit/synthesis/clifford/clifford_decompose_bm.py index cbc54f16bb0..50ffcb74316 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_bm.py +++ b/qiskit/synthesis/clifford/clifford_decompose_bm.py @@ -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 @@ -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() diff --git a/qiskit/synthesis/clifford/clifford_decompose_layers.py b/qiskit/synthesis/clifford/clifford_decompose_layers.py index f1a7c5cce13..2fc9ca5bdb2 100644 --- a/qiskit/synthesis/clifford/clifford_decompose_layers.py +++ b/qiskit/synthesis/clifford/clifford_decompose_layers.py @@ -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 diff --git a/releasenotes/notes/add-random-clifford-util-5358041208729988.yaml b/releasenotes/notes/add-random-clifford-util-5358041208729988.yaml new file mode 100644 index 00000000000..7f2e20db652 --- /dev/null +++ b/releasenotes/notes/add-random-clifford-util-5358041208729988.yaml @@ -0,0 +1,14 @@ +--- +features_circuits: + - | + Added a new function to ``qiskit.circuit.random`` that allows to generate a pseudo-random + Clifford circuit with gates from the standard library: :func:`.random_clifford_circuit`. + Example usage: + + .. plot:: + :include-source: + + from qiskit.circuit.random import random_clifford_circuit + + circ = random_clifford_circuit(num_qubits=2, num_gates=6) + circ.draw(output='mpl') diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py index f23c0155bc6..3585efb9f64 100644 --- a/test/python/quantum_info/operators/symplectic/test_clifford.py +++ b/test/python/quantum_info/operators/symplectic/test_clifford.py @@ -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 @@ -17,7 +17,9 @@ import numpy as np from ddt import ddt -from qiskit.circuit import Gate, QuantumCircuit, QuantumRegister +from qiskit.circuit import Gate, QuantumCircuit +from qiskit.circuit.random import random_clifford_circuit + from qiskit.circuit.library import ( CPhaseGate, CRXGate, @@ -26,7 +28,6 @@ CXGate, CYGate, CZGate, - DCXGate, ECRGate, HGate, IGate, @@ -37,10 +38,7 @@ RYYGate, RZZGate, RZXGate, - SdgGate, SGate, - SXGate, - SXdgGate, SwapGate, XGate, XXMinusYYGate, @@ -57,98 +55,11 @@ 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 -class VGate(Gate): - """V Gate used in Clifford synthesis.""" - - def __init__(self): - """Create new V Gate.""" - super().__init__("v", 1, []) - - def _define(self): - """V Gate definition.""" - q = QuantumRegister(1, "q") - qc = QuantumCircuit(q) - qc.sdg(0) - qc.h(0) - self.definition = qc - - -class WGate(Gate): - """W Gate used in Clifford synthesis.""" - - def __init__(self): - """Create new W Gate.""" - super().__init__("w", 1, []) - - def _define(self): - """W Gate definition.""" - q = QuantumRegister(1, "q") - qc = QuantumCircuit(q) - qc.append(VGate(), [q[0]], []) - qc.append(VGate(), [q[0]], []) - self.definition = qc - - -def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None): - """Generate a pseudo random Clifford circuit.""" - - qubits_1_gates = ["i", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg", "v", "w"] - qubits_2_gates = ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"] - if gates == "all": - if num_qubits == 1: - gates = qubits_1_gates - else: - gates = qubits_1_gates + qubits_2_gates - - instructions = { - "i": (IGate(), 1), - "x": (XGate(), 1), - "y": (YGate(), 1), - "z": (ZGate(), 1), - "h": (HGate(), 1), - "s": (SGate(), 1), - "sdg": (SdgGate(), 1), - "sx": (SXGate(), 1), - "sxdg": (SXdgGate(), 1), - "v": (VGate(), 1), - "w": (WGate(), 1), - "cx": (CXGate(), 2), - "cy": (CYGate(), 2), - "cz": (CZGate(), 2), - "swap": (SwapGate(), 2), - "iswap": (iSwapGate(), 2), - "ecr": (ECRGate(), 2), - "dcx": (DCXGate(), 2), - } - - if isinstance(seed, np.random.Generator): - rng = seed - else: - rng = np.random.default_rng(seed) - - samples = rng.choice(gates, num_gates) - - circ = QuantumCircuit(num_qubits) - - for name in samples: - gate, nqargs = instructions[name] - qargs = rng.choice(range(num_qubits), nqargs, replace=False).tolist() - circ.append(gate, qargs) - - return circ - - @ddt class TestCliffordGates(QiskitTestCase): """Tests for clifford append gate functions.""" @@ -588,92 +499,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.""" @@ -683,11 +508,9 @@ class TestCliffordDecomposition(QiskitTestCase): ["h", "s"], ["h", "s", "i", "x", "y", "z"], ["h", "s", "sdg"], - ["h", "s", "v"], - ["h", "s", "w"], ["h", "sx", "sxdg"], ["s", "sx", "sxdg"], - ["h", "s", "sdg", "i", "x", "y", "z", "v", "w", "sx", "sxdg"], + ["h", "s", "sdg", "i", "x", "y", "z", "sx", "sxdg"], ] ) def test_to_operator_1qubit_gates(self, gates): diff --git a/test/python/synthesis/test_clifford_sythesis.py b/test/python/synthesis/test_clifford_sythesis.py new file mode 100644 index 00000000000..887f1af5ad9 --- /dev/null +++ b/test/python/synthesis/test_clifford_sythesis.py @@ -0,0 +1,118 @@ +# This code is part of Qiskit. +# +# (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 +# 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. + +# pylint: disable=invalid-name +"""Tests for Clifford synthesis functions.""" + +import numpy as np +from ddt import ddt +from qiskit.circuit.random import random_clifford_circuit +from qiskit.quantum_info.operators import Clifford +from qiskit.synthesis.clifford import ( + synth_clifford_full, + synth_clifford_ag, + synth_clifford_bm, + synth_clifford_greedy, +) + +from test import QiskitTestCase # pylint: disable=wrong-import-order +from test import combine # pylint: disable=wrong-import-order + + +@ddt +class TestCliffordSynthesis(QiskitTestCase): + """Tests for clifford synthesis functions.""" + + @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) + synth_circ = synth_clifford_bm(target) + value = Clifford(synth_circ) + 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 = 1 + for _ in range(samples): + circ = random_clifford_circuit(num_qubits, 5 * num_qubits, seed=rng) + target = Clifford(circ) + synth_circ = synth_clifford_ag(target) + value = Clifford(synth_circ) + 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) + synth_circ = synth_clifford_greedy(target) + value = Clifford(synth_circ) + 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) + synth_circ = synth_clifford_full(target) + value = Clifford(synth_circ) + self.assertEqual(value, target)