Skip to content

Commit

Permalink
Add standalone test file for Clifford synthesis functions (#12347)
Browse files Browse the repository at this point in the history
* Convert synthesis imports to runtime imports to avoid cyclic import errors

* Set copy=False in append

* Move clifford synthesis tests to separate file in test/synthesis.

* Add random_clifford_circuit to qiskit.circuit.random

* Remove pylint disable

* Fix reno
  • Loading branch information
ElePT authored May 7, 2024
1 parent c062dd6 commit 6ff0eb5
Show file tree
Hide file tree
Showing 13 changed files with 248 additions and 214 deletions.
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
2 changes: 1 addition & 1 deletion qiskit/circuit/random/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@

"""Method for generating random circuits."""

from .utils import random_circuit
from .utils import random_circuit, random_clifford_circuit
71 changes: 70 additions & 1 deletion qiskit/circuit/random/utils.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.
# (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 @@ -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
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
14 changes: 14 additions & 0 deletions releasenotes/notes/add-random-clifford-util-5358041208729988.yaml
Original file line number Diff line number Diff line change
@@ -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')
Loading

0 comments on commit 6ff0eb5

Please sign in to comment.