Skip to content

Move to method dispatch using act_on for CliffordState, Tableau and ChForm #3456

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

Merged
merged 8 commits into from
Oct 29, 2020
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
101 changes: 13 additions & 88 deletions cirq/sim/clifford/clifford_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@

import cirq
from cirq import circuits, study, ops, protocols, value
from cirq.ops import pauli_gates
from cirq.ops.clifford_gate import SingleQubitCliffordGate
from cirq.ops.dense_pauli_string import DensePauliString
from cirq.protocols import unitary
from cirq.protocols import act_on, unitary
from cirq.sim import simulator
from cirq.sim.clifford import clifford_tableau, stabilizer_state_ch_form
from cirq._compat import deprecated, deprecated_parameter
Expand Down Expand Up @@ -336,94 +335,20 @@ def wave_function(self):
return self.state_vector()

def apply_unitary(self, op: 'cirq.Operation'):
if len(op.qubits) == 1:
self.apply_single_qubit_unitary(op)
elif isinstance(op, GlobalPhaseOperation):
self.ch_form.omega *= op.coefficient
elif op.gate == cirq.CNOT:
self.tableau._CNOT(self.qubit_map[op.qubits[0]],
self.qubit_map[op.qubits[1]])
self.ch_form._CNOT(self.qubit_map[op.qubits[0]],
self.qubit_map[op.qubits[1]])
elif op.gate == cirq.CZ:
self.tableau._CZ(self.qubit_map[op.qubits[0]],
self.qubit_map[op.qubits[1]])
self.ch_form._CZ(self.qubit_map[op.qubits[0]],
self.qubit_map[op.qubits[1]])
else:
from cirq.sim.clifford import (ActOnCliffordTableauArgs,
ActOnStabilizerCHFormArgs)
tableau_args = ActOnCliffordTableauArgs(
self.tableau, [self.qubit_map[i] for i in op.qubits],
np.random.RandomState(), {})
ch_form_args = ActOnStabilizerCHFormArgs(
self.ch_form, [self.qubit_map[i] for i in op.qubits])
try:
act_on(op, tableau_args)
act_on(op, ch_form_args)
except TypeError:
raise ValueError('%s cannot be run with Clifford simulator.' %
str(op.gate)) # type: ignore

def apply_single_qubit_unitary(self, op: 'cirq.Operation'):
qubit = self.qubit_map[op.qubits[0]]
if op.gate == cirq.I:
return

if op.gate == cirq.X:
self._apply_X(qubit)
return

if op.gate == cirq.Y:
self._apply_Y(qubit)
return

if op.gate == cirq.Z:
self._apply_Z(qubit)
return

if op.gate == cirq.H:
self._apply_H(qubit)
return

u = unitary(op)
clifford_gate = SingleQubitCliffordGate.from_unitary(u)
if clifford_gate is None:
raise ValueError('%s cannot be run with Clifford simulator.' %
str(op.gate))

h = unitary(ops.H)
s = unitary(ops.S)
applied_unitary = np.eye(2)
for axis, quarter_turns in clifford_gate.decompose_rotation():
for _ in range(quarter_turns % 4):
if axis == pauli_gates.X:
self._apply_H(qubit)
self._apply_S(qubit)
self._apply_H(qubit)
applied_unitary = h @ s @ h @ applied_unitary
elif axis == pauli_gates.Y:
self._apply_S(qubit)
self._apply_S(qubit)
self._apply_H(qubit)
applied_unitary = h @ s @ s @ applied_unitary
else:
assert axis == pauli_gates.Z
self._apply_S(qubit)
applied_unitary = s @ applied_unitary

max_idx = max(np.ndindex(*u.shape), key=lambda t: abs(u[t]))
phase_shift = u[max_idx] / applied_unitary[max_idx]
self.ch_form.omega *= phase_shift

def _apply_H(self, qubit: int):
self.tableau._H(qubit)
self.ch_form._H(qubit)

def _apply_S(self, qubit: int):
self.tableau._S(qubit)
self.ch_form._S(qubit)

def _apply_X(self, qubit: int):
self.tableau._X(qubit)
self.ch_form._X(qubit)

def _apply_Z(self, qubit: int):
self.tableau._Z(qubit)
self.ch_form._Z(qubit)

def _apply_Y(self, qubit: int):
self.tableau._Y(qubit)
self.ch_form._Y(qubit)
return

@deprecated_parameter(
deadline='v0.10.0',
Expand Down
29 changes: 0 additions & 29 deletions cirq/sim/clifford/clifford_tableau.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,35 +136,6 @@ def _str_full_(self) -> str:

return string

def _CZ(self, q, r):
self._H(r)
self._CNOT(q, r)
self._H(r)

def _X(self, q):
self.rs[:] ^= self.zs[:, q]

def _Y(self, q):
self.rs[:] ^= self.xs[:, q] ^ self.zs[:, q]

def _Z(self, q):
self.rs[:] ^= self.xs[:, q]

def _S(self, q):
self.rs[:] ^= (self.xs[:, q] & self.zs[:, q])
self.zs[:, q] ^= self.xs[:, q]

def _H(self, q):
(self.xs[:, q], self.zs[:, q]) = (self.zs[:, q].copy(),
self.xs[:, q].copy())
self.rs[:] ^= (self.xs[:, q] & self.zs[:, q])

def _CNOT(self, q1, q2):
self.rs[:] ^= self.xs[:,q1] & self.zs[:,q2] & \
(~(self.xs[:,q2] ^ self.zs[:,q1]))
self.xs[:, q2] ^= self.xs[:, q1]
self.zs[:, q1] ^= self.zs[:, q2]

def _rowsum(self, q1, q2):
"""Implements the "rowsum" routine defined by
Aaronson and Gottesman.
Expand Down
48 changes: 4 additions & 44 deletions cirq/sim/clifford/stabilizer_state_ch_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import cirq
from cirq import protocols, value
from cirq.ops import pauli_gates
from cirq.value import big_endian_int_to_digits
from cirq._compat import deprecated

Expand Down Expand Up @@ -60,7 +61,9 @@ def __init__(self, num_qubits: int, initial_state: int = 0) -> None:
digit_count=num_qubits,
base=2)):
if val:
self._X(i)
from cirq.sim.clifford import ActOnStabilizerCHFormArgs
protocols.act_on(pauli_gates.X,
ActOnStabilizerCHFormArgs(self, [i]))

def _json_dict_(self) -> Dict[str, Any]:
return protocols.obj_to_dict_helper(
Expand Down Expand Up @@ -133,66 +136,23 @@ def state_vector(self) -> np.ndarray:
def wave_function(self) -> np.ndarray:
return self.state_vector()

def _S(self, q):
self.M[q, :] ^= self.G[q, :]
self.gamma[q] = (self.gamma[q] - 1) % 4

def _S_right(self, q):
r"""Right multiplication version of S gate."""
self.M[:, q] ^= self.F[:, q]
self.gamma[:] = (self.gamma[:] - self.F[:, q]) % 4

def _Z(self, q):
self._S(q)
self._S(q)

def _X(self, q):
self._H(q)
self._Z(q)
self._H(q)

def _Y(self, q):
self._Z(q)
self._X(q)
self.omega *= 1j

def _CZ(self, q, r):
self.M[q, :] ^= self.G[r, :]
self.M[r, :] ^= self.G[q, :]

def _CZ_right(self, q, r):
r"""Right multiplication version of CZ gate."""
self.M[:, q] ^= self.F[:, r]
self.M[:, r] ^= self.F[:, q]
self.gamma[:] = (self.gamma[:] + 2 * self.F[:, q] * self.F[:, r]) % 4

def _CNOT(self, q, r):
self.gamma[q] = (self.gamma[q] + self.gamma[r] + 2 *
(sum(self.M[q, :] & self.F[r, :]) % 2)) % 4
self.G[r, :] ^= self.G[q, :]
self.F[q, :] ^= self.F[r, :]
self.M[q, :] ^= self.M[r, :]

def _CNOT_right(self, q, r):
r"""Right multiplication version of CNOT gate."""
self.G[:, q] ^= self.G[:, r]
self.F[:, r] ^= self.F[:, q]
self.M[:, q] ^= self.M[:, r]

def _H(self, p):
t = self.s ^ (self.G[p, :] & self.v)
u = self.s ^ (self.F[p, :] & (~self.v)) ^ (self.M[p, :] & self.v)

alpha = sum(self.G[p, :] & (~self.v) & self.s) % 2
beta = sum(self.M[p, :] & (~self.v) & self.s)
beta += sum(self.F[p, :] & self.v & self.M[p, :])
beta += sum(self.F[p, :] & self.v & self.s)
beta %= 2

delta = (self.gamma[p] + 2 * (alpha + beta)) % 4

self.update_sum(t, u, delta=delta, alpha=alpha)

def update_sum(self, t, u, delta=0, alpha=0):
""" Implements the transformation (Proposition 4 in Bravyi et al)

Expand Down