Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
142 changes: 76 additions & 66 deletions cirq-core/cirq/ops/clifford_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from cirq import _compat, protocols, value, linalg, qis
from cirq._import import LazyLoader
from cirq.ops import common_gates, identity, named_qubit, raw_types, pauli_gates, phased_x_z_gate
from cirq.ops import common_gates, named_qubit, raw_types, pauli_gates, phased_x_z_gate
from cirq.ops.pauli_gates import Pauli
from cirq.type_workarounds import NotImplementedType

Expand Down Expand Up @@ -158,121 +158,131 @@ class CommonCliffordGateMetaClass(value.ABCMetaImplementAnyOneOf):
# Note that in python 3.9+ @classmethod can be used with @property, so these
# can be moved to CommonCliffordGates.

@property
def all_single_qubit_cliffords(cls) -> Sequence['cirq.SingleQubitCliffordGate']:
"""All 24 single-qubit Clifford gates."""
if not hasattr(cls, '_all_single_qubit_cliffords'):
pX = (pauli_gates.X, False)
mX = (pauli_gates.X, True)
pY = (pauli_gates.Y, False)
mY = (pauli_gates.Y, True)
pZ = (pauli_gates.Z, False)
mZ = (pauli_gates.Z, True)

def from_xz(x_to, z_to):
return SingleQubitCliffordGate.from_clifford_tableau(
_to_clifford_tableau(x_to=x_to, z_to=z_to)
)

# Order in is relied on in properties that retrieve a specific Clifford below.
cls._all_single_qubit_cliffords = (
# 0: Identity
from_xz(x_to=pX, z_to=pZ), # I
# 1..3: Paulis
from_xz(x_to=pX, z_to=mZ), # X
from_xz(x_to=mX, z_to=mZ), # Y
from_xz(x_to=mX, z_to=pZ), # Z
# 4..6: Square roots of Paulis
from_xz(x_to=pX, z_to=mY), # I-iX
from_xz(x_to=mZ, z_to=pX), # I-iY
from_xz(x_to=pY, z_to=pZ), # I-iZ aka S
# 7..9: Negative square roots of Paulis
from_xz(x_to=pX, z_to=pY), # I+iX
from_xz(x_to=pZ, z_to=mX), # I+iY
from_xz(x_to=mY, z_to=pZ), # I+iZ
# 10..15: Hadamards
from_xz(x_to=pZ, z_to=pX), # Z+X aka H
from_xz(x_to=pY, z_to=mZ), # X+Y
from_xz(x_to=mX, z_to=pY), # Y+Z
from_xz(x_to=mZ, z_to=mX), # Z-X
from_xz(x_to=mY, z_to=mZ), # X-Y
from_xz(x_to=mX, z_to=mY), # Y-Z
# 16..23: Order-3 Cliffords
from_xz(x_to=pY, z_to=pX), # I-i(+X+Y+Z)
from_xz(x_to=mZ, z_to=mY), # I-i(+X+Y-Z)
from_xz(x_to=pZ, z_to=mY), # I-i(+X-Y+Z)
from_xz(x_to=mY, z_to=mX), # I-i(+X-Y-Z)
from_xz(x_to=mZ, z_to=pY), # I-i(-X+Y+Z)
from_xz(x_to=mY, z_to=pX), # I-i(-X+Y-Z)
from_xz(x_to=pY, z_to=mX), # I-i(-X-Y+Z)
from_xz(x_to=pZ, z_to=pY), # I-i(-X-Y-Z)
)
return cls._all_single_qubit_cliffords

@property
def I(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_I'):
cls._I = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, identity.I))
return cls._I
return cls.all_single_qubit_cliffords[0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not stoked about these indices which could get out of sync with the tuple of all gates, but I don't have a great alternative. At very least, we should document that the order of the all gates tuple should not be changed without updating these indices.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not a big fan of the constants. An alternative I considered was to use a Dict[str, ...], but it involves making up names for all Clifford gates most of which would be highly non-standard.

I imagine that anyone wanting access to a specific gate would use a property (e.g. CliffordGate.X_sqrt) and folks would use all_single_qubit_cliffords if they really want to get all 24. Here's to wishful thinking :-)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a namedtuple could help here. You could use property names for the items that should be accessible as properties and a say repeated _12 name with the rename=True argument for all other gates for which you don't care about the name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT, the primary (only?) use-case for all_single_qubit_cliffords is the situation where someone wants to iterate over all single-qubit Cliffords, e.g. to execute a test-case on all of them. I think using namedtuple adds unnecessary clutter to output and is potentially confusing.


@property
def X(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_X'):
cls._X = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, pauli_gates.X))
return cls._X
return cls.all_single_qubit_cliffords[1]

@property
def Y(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_Y'):
cls._Y = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, pauli_gates.Y))
return cls._Y
return cls.all_single_qubit_cliffords[2]

@property
def Z(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_Z'):
cls._Z = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, pauli_gates.Z))
return cls._Z
return cls.all_single_qubit_cliffords[3]

@property
def H(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_H'):
cls._H = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, common_gates.H))
return cls._H
return cls.all_single_qubit_cliffords[10]

@property
def S(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_S'):
cls._S = SingleQubitCliffordGate.from_clifford_tableau(_gate_tableau(1, common_gates.S))
return cls._S
return cls.all_single_qubit_cliffords[6]

@property
def CNOT(cls) -> 'cirq.CliffordGate':
if not hasattr(cls, '_CNOT'):
cls._CNOT = CliffordGate.from_clifford_tableau(_gate_tableau(2, common_gates.CNOT))
t = qis.CliffordTableau(num_qubits=2)
t.xs = [[1, 1], [0, 1], [0, 0], [0, 0]]
t.zs = [[0, 0], [0, 0], [1, 0], [1, 1]]
cls._CNOT = CliffordGate.from_clifford_tableau(t)
return cls._CNOT

@property
def CZ(cls) -> 'cirq.CliffordGate':
if not hasattr(cls, '_CZ'):
cls._CZ = CliffordGate.from_clifford_tableau(_gate_tableau(2, common_gates.CZ))
t = qis.CliffordTableau(num_qubits=2)
t.xs = [[1, 0], [0, 1], [0, 0], [0, 0]]
t.zs = [[0, 1], [1, 0], [1, 0], [0, 1]]
cls._CZ = CliffordGate.from_clifford_tableau(t)
return cls._CZ

@property
def SWAP(cls) -> 'cirq.CliffordGate':
if not hasattr(cls, '_SWAP'):
cls._SWAP = CliffordGate.from_clifford_tableau(_gate_tableau(2, common_gates.SWAP))
t = qis.CliffordTableau(num_qubits=2)
t.xs = [[0, 1], [1, 0], [0, 0], [0, 0]]
t.zs = [[0, 0], [0, 0], [0, 1], [1, 0]]
cls._SWAP = CliffordGate.from_clifford_tableau(t)
return cls._SWAP

@property
def X_sqrt(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_X_sqrt'):
# Unfortunately, due the code style, the matrix should be viewed transposed.
# Note xs, zs, and rs are column vector.
# Transformation: X -> X, Z -> -Y
clifford_tableau = qis.CliffordTableau._from_json_dict_(
n=1, rs=[0, 1], xs=[[1], [1]], zs=[[0], [1]]
)
cls._X_sqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau)
return cls._X_sqrt
return cls.all_single_qubit_cliffords[4]

@property
def X_nsqrt(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_X_nsqrt'):
# Transformation: X->X, Z->Y
clifford_tableau = qis.CliffordTableau._from_json_dict_(
n=1, rs=[0, 0], xs=[[1], [1]], zs=[[0], [1]]
)
cls._X_nsqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau)
return cls._X_nsqrt
return cls.all_single_qubit_cliffords[7]

@property
def Y_sqrt(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_Y_sqrt'):
# Transformation: X -> -Z, Z -> X
clifford_tableau = qis.CliffordTableau._from_json_dict_(
n=1, rs=[1, 0], xs=[[0], [1]], zs=[[1], [0]]
)
cls._Y_sqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau)
return cls._Y_sqrt
return cls.all_single_qubit_cliffords[5]

@property
def Y_nsqrt(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_Y_nsqrt'):
# Transformation: X -> Z, Z -> -X
clifford_tableau = qis.CliffordTableau._from_json_dict_(
n=1, rs=[0, 1], xs=[[0], [1]], zs=[[1], [0]]
)
cls._Y_nsqrt = SingleQubitCliffordGate.from_clifford_tableau(clifford_tableau)
return cls._Y_nsqrt
return cls.all_single_qubit_cliffords[8]

@property
def Z_sqrt(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_Z_sqrt'):
# Transformation: X -> Y, Z -> Z
_clifford_tableau = qis.CliffordTableau._from_json_dict_(
n=1, rs=[0, 0], xs=[[1], [0]], zs=[[1], [1]]
)
cls._Z_sqrt = SingleQubitCliffordGate.from_clifford_tableau(_clifford_tableau)
return cls._Z_sqrt
return cls.all_single_qubit_cliffords[6]
Copy link
Collaborator

@pavoljuhas pavoljuhas Jun 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same as S on line 234. Not sure which one is a typo.

Can you perhaps add an independent test that class-property gates have correct values?
If you keep the current property implementation with indexed lookup, such test will fail when
all_single_qubit_cliffords go out of order.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The S gate and Z_sqrt are the same gate. Square the S gate to convince yourself of that.

The PR does include independent test (test_all_single_qubit_clifford_unitaries) that checks all the gates have correct values. It is already order-sensitive.


@property
def Z_nsqrt(cls) -> 'cirq.SingleQubitCliffordGate':
if not hasattr(cls, '_Z_nsqrt'):
# Transformation: X -> -Y, Z -> Z
_clifford_tableau = qis.CliffordTableau._from_json_dict_(
n=1, rs=[1, 0], xs=[[1], [0]], zs=[[1], [1]]
)
cls._Z_nsqrt = SingleQubitCliffordGate.from_clifford_tableau(_clifford_tableau)
return cls._Z_nsqrt
return cls.all_single_qubit_cliffords[9]


class CommonCliffordGates(metaclass=CommonCliffordGateMetaClass):
Expand Down
82 changes: 46 additions & 36 deletions cirq-core/cirq/ops/clifford_gate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,22 +535,7 @@ def test_text_diagram_info(gate, sym, exp):
)


@pytest.mark.parametrize(
"clifford_gate",
(
cirq.SingleQubitCliffordGate.I,
cirq.SingleQubitCliffordGate.H,
cirq.SingleQubitCliffordGate.X,
cirq.SingleQubitCliffordGate.Y,
cirq.SingleQubitCliffordGate.Z,
cirq.SingleQubitCliffordGate.X_sqrt,
cirq.SingleQubitCliffordGate.Y_sqrt,
cirq.SingleQubitCliffordGate.Z_sqrt,
cirq.SingleQubitCliffordGate.X_nsqrt,
cirq.SingleQubitCliffordGate.Y_nsqrt,
cirq.SingleQubitCliffordGate.Z_nsqrt,
),
)
@pytest.mark.parametrize("clifford_gate", cirq.SingleQubitCliffordGate.all_single_qubit_cliffords)
def test_from_unitary(clifford_gate):
u = cirq.unitary(clifford_gate)
result_gate = cirq.SingleQubitCliffordGate.from_unitary(u)
Expand Down Expand Up @@ -589,22 +574,7 @@ def test_from_unitary_not_clifford():
assert cirq.SingleQubitCliffordGate.from_unitary_with_global_phase(u) is None


@pytest.mark.parametrize(
"clifford_gate",
(
cirq.SingleQubitCliffordGate.I,
cirq.SingleQubitCliffordGate.H,
cirq.SingleQubitCliffordGate.X,
cirq.SingleQubitCliffordGate.Y,
cirq.SingleQubitCliffordGate.Z,
cirq.SingleQubitCliffordGate.X_sqrt,
cirq.SingleQubitCliffordGate.Y_sqrt,
cirq.SingleQubitCliffordGate.Z_sqrt,
cirq.SingleQubitCliffordGate.X_nsqrt,
cirq.SingleQubitCliffordGate.Y_nsqrt,
cirq.SingleQubitCliffordGate.Z_nsqrt,
),
)
@pytest.mark.parametrize("clifford_gate", cirq.SingleQubitCliffordGate.all_single_qubit_cliffords)
def test_decompose_gate(clifford_gate):
gates = clifford_gate.decompose_gate()
u = functools.reduce(np.dot, [np.eye(2), *(cirq.unitary(gate) for gate in reversed(gates))])
Expand Down Expand Up @@ -659,12 +629,12 @@ def test_common_clifford_gate(clifford_gate, standard_gate):
cirq.testing.assert_allclose_up_to_global_phase(u_c, u_s, atol=1e-8)


@pytest.mark.parametrize('clifford_gate_name', ("I", "X", "Y", "Z", "H", "S", "CNOT", "CZ", "SWAP"))
def test_common_clifford_gate_caching(clifford_gate_name):
cache_name = f"_{clifford_gate_name}"
@pytest.mark.parametrize('property_name', ("all_single_qubit_cliffords", "CNOT", "CZ", "SWAP"))
def test_common_clifford_gate_caching(property_name):
cache_name = f"_{property_name}"
delattr(cirq.CliffordGate, cache_name)
assert not hasattr(cirq.CliffordGate, cache_name)
_ = getattr(cirq.CliffordGate, clifford_gate_name)
_ = getattr(cirq.CliffordGate, property_name)
assert hasattr(cirq.CliffordGate, cache_name)


Expand Down Expand Up @@ -912,3 +882,43 @@ def test_clifford_gate_act_on_ch_form():
def test_clifford_gate_act_on_fail():
with pytest.raises(TypeError, match="Failed to act"):
cirq.act_on(cirq.CliffordGate.X, DummySimulationState(), qubits=())


def test_all_single_qubit_clifford_unitaries():
i = np.eye(2)
x = np.array([[0, 1], [1, 0]])
y = np.array([[0, -1j], [1j, 0]])
z = np.diag([1, -1])

cs = [cirq.unitary(c) for c in cirq.CliffordGate.all_single_qubit_cliffords]

# Identity
assert cirq.equal_up_to_global_phase(cs[0], i)
# Paulis
assert cirq.equal_up_to_global_phase(cs[1], x)
assert cirq.equal_up_to_global_phase(cs[2], y)
assert cirq.equal_up_to_global_phase(cs[3], z)
# Square roots of Paulis
assert cirq.equal_up_to_global_phase(cs[4], (i - 1j * x) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[5], (i - 1j * y) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[6], (i - 1j * z) / np.sqrt(2))
# Negative square roots of Paulis
assert cirq.equal_up_to_global_phase(cs[7], (i + 1j * x) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[8], (i + 1j * y) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[9], (i + 1j * z) / np.sqrt(2))
# Hadamards
assert cirq.equal_up_to_global_phase(cs[10], (z + x) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[11], (x + y) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[12], (y + z) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[13], (z - x) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[14], (x - y) / np.sqrt(2))
assert cirq.equal_up_to_global_phase(cs[15], (y - z) / np.sqrt(2))
# Order-3 Cliffords
assert cirq.equal_up_to_global_phase(cs[16], (i - 1j * (x + y + z)) / 2)
assert cirq.equal_up_to_global_phase(cs[17], (i - 1j * (x + y - z)) / 2)
assert cirq.equal_up_to_global_phase(cs[18], (i - 1j * (x - y + z)) / 2)
assert cirq.equal_up_to_global_phase(cs[19], (i - 1j * (x - y - z)) / 2)
assert cirq.equal_up_to_global_phase(cs[20], (i - 1j * (-x + y + z)) / 2)
assert cirq.equal_up_to_global_phase(cs[21], (i - 1j * (-x + y - z)) / 2)
assert cirq.equal_up_to_global_phase(cs[22], (i - 1j * (-x - y + z)) / 2)
assert cirq.equal_up_to_global_phase(cs[23], (i - 1j * (-x - y - z)) / 2)