From ea95c98abc11351f20c02736da1aeceb9fe97597 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Thu, 11 Nov 2021 15:30:45 -0800 Subject: [PATCH 01/41] Change act_on to apply_to --- cirq-core/cirq/ops/common_channels.py | 36 +- cirq-core/cirq/ops/common_gates.py | 451 +++++++++--------- cirq-core/cirq/ops/global_phase_op.py | 25 +- cirq-core/cirq/ops/identity.py | 10 + cirq-core/cirq/ops/random_gate_channel.py | 20 +- cirq-core/cirq/ops/swap_gates.py | 40 +- cirq-core/cirq/protocols/__init__.py | 6 + .../cirq/protocols/clifford_protocols.py | 64 +++ .../clifford/act_on_clifford_tableau_args.py | 22 +- .../act_on_stabilizer_ch_form_args.py | 15 +- 10 files changed, 391 insertions(+), 298 deletions(-) create mode 100644 cirq-core/cirq/protocols/clifford_protocols.py diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index e43e42c41be..9df6d55fd09 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -316,15 +316,13 @@ def __str__(self) -> str: return f"depolarize(p={self._p})" return f"depolarize(p={self._p},n_qubits={self._n_qubits})" - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if args.prng.random() < self._p: - gate = args.prng.choice([pauli_gates.X, pauli_gates.Y, pauli_gates.Z]) - protocols.act_on(gate, args, qubits) - return True - return NotImplemented + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + if prng.random() < self._p: + gate = prng.choice([pauli_gates.X, pauli_gates.Y, pauli_gates.Z]) + protocols.apply_to_tableau(gate, tableau, axes, prng) + return True def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: result: Tuple[str, ...] @@ -722,14 +720,20 @@ def _qasm_(self, args: 'cirq.QasmArgs', qubits: Tuple['cirq.Qid', ...]) -> Optio def _qid_shape_(self): return (self._dimension,) - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq import sim, ops + def _apply_to_ch_form_( + self, + ch_form: 'cirq.StabilizerStateChForm', + axes: Sequence[int], + prng: np.random.RandomState, + ): + from cirq import ops + + if ch_form._measure(axes[0], prng): + protocols.apply_to_ch_form(ops.X, ch_form, axes, prng) + return True - if isinstance(args, sim.ActOnStabilizerCHFormArgs): - axe = args.qubit_map[qubits[0]] - if args.state._measure(axe, args.prng): - ops.X._act_on_(args, qubits) - return True + def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): + from cirq import sim if isinstance(args, sim.ActOnStateVectorArgs): # Do a silent measurement. diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index d6e50418c70..a1b9c90256b 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -62,12 +62,6 @@ """ -def _act_with_gates(args, qubits, *gates: 'cirq.SupportsActOnQubits') -> None: - """Act on the given args with the given gates in order.""" - for gate in gates: - assert gate._act_on_(args, qubits) - - def _pi(rads): return sympy.pi if protocols.is_parameterized(rads) else np.pi @@ -108,34 +102,34 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - tableau = args.tableau - q = args.qubit_map[qubits[0]] - effective_exponent = self._exponent % 2 - if effective_exponent == 0.5: - tableau.xs[:, q] ^= tableau.zs[:, q] - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.zs[:, q] - elif effective_exponent == 1.5: - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - tableau.xs[:, q] ^= tableau.zs[:, q] - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - _act_with_gates(args, qubits, H, ZPowGate(exponent=self._exponent), H) - # Adjust the global phase based on the global_shift parameter. - args.state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q = axes[0] + effective_exponent = self._exponent % 2 + if effective_exponent == 0.5: + tableau.xs[:, q] ^= tableau.zs[:, q] + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.zs[:, q] + elif effective_exponent == 1.5: + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + tableau.xs[:, q] ^= tableau.zs[:, q] + return True - return NotImplemented + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + protocols.apply_to_ch_form(H, state, axes, prng) + protocols.apply_to_ch_form(ZPowGate(exponent=self._exponent), state, axes, prng) + protocols.apply_to_ch_form(H, state, axes, prng) + # Adjust the global phase based on the global_shift parameter. + state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) + return True def in_su2(self) -> 'Rx': """Returns an equal-up-global-phase gate from the group SU2.""" @@ -361,50 +355,53 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - tableau = args.tableau - q = args.qubit_map[qubits[0]] - effective_exponent = self._exponent % 2 - if effective_exponent == 0.5: - tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), - ) - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.xs[:, q] ^ tableau.zs[:, q] - elif effective_exponent == 1.5: - tableau.rs[:] ^= ~(tableau.xs[:, q]) & tableau.zs[:, q] - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), - ) - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - effective_exponent = self._exponent % 2 - state = args.state - Z = ZPowGate() - if effective_exponent == 0.5: - _act_with_gates(args, qubits, Z, H) - state.omega *= (1 + 1j) / (2 ** 0.5) - elif effective_exponent == 1: - _act_with_gates(args, qubits, Z, H, Z, H) - state.omega *= 1j - elif effective_exponent == 1.5: - _act_with_gates(args, qubits, H, Z) - state.omega *= (1 - 1j) / (2 ** 0.5) - # Adjust the global phase based on the global_shift parameter. - args.state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True - return NotImplemented + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q = axes[0] + effective_exponent = self._exponent % 2 + if effective_exponent == 0.5: + tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) + (tableau.xs[:, q], tableau.zs[:, q]) = ( + tableau.zs[:, q].copy(), + tableau.xs[:, q].copy(), + ) + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.xs[:, q] ^ tableau.zs[:, q] + elif effective_exponent == 1.5: + tableau.rs[:] ^= ~(tableau.xs[:, q]) & tableau.zs[:, q] + (tableau.xs[:, q], tableau.zs[:, q]) = ( + tableau.zs[:, q].copy(), + tableau.xs[:, q].copy(), + ) + return True + + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + effective_exponent = self._exponent % 2 + Z = ZPowGate() + if effective_exponent == 0.5: + protocols.apply_to_ch_form(Z, state, axes, prng) + protocols.apply_to_ch_form(H, state, axes, prng) + state.omega *= (1 + 1j) / (2 ** 0.5) + elif effective_exponent == 1: + protocols.apply_to_ch_form(Z, state, axes, prng) + protocols.apply_to_ch_form(H, state, axes, prng) + protocols.apply_to_ch_form(Z, state, axes, prng) + protocols.apply_to_ch_form(H, state, axes, prng) + state.omega *= 1j + elif effective_exponent == 1.5: + protocols.apply_to_ch_form(H, state, axes, prng) + protocols.apply_to_ch_form(Z, state, axes, prng) + state.omega *= (1 - 1j) / (2 ** 0.5) + # Adjust the global phase based on the global_shift parameter. + state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) + return True def in_su2(self) -> 'Ry': """Returns an equal-up-global-phase gate from the group SU2.""" @@ -580,41 +577,38 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - tableau = args.tableau - q = args.qubit_map[qubits[0]] - effective_exponent = self._exponent % 2 - if effective_exponent == 0.5: - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - tableau.zs[:, q] ^= tableau.xs[:, q] - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.xs[:, q] - elif effective_exponent == 1.5: - tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) - tableau.zs[:, q] ^= tableau.xs[:, q] - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = args.qubit_map[qubits[0]] - effective_exponent = self._exponent % 2 - state = args.state - for _ in range(int(effective_exponent * 2)): - # Prescription for S left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.M[q, :] ^= state.G[q, :] - state.gamma[q] = (state.gamma[q] - 1) % 4 - # Adjust the global phase based on the global_shift parameter. - args.state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q = axes[0] + effective_exponent = self._exponent % 2 + if effective_exponent == 0.5: + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + tableau.zs[:, q] ^= tableau.xs[:, q] + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.xs[:, q] + elif effective_exponent == 1.5: + tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) + tableau.zs[:, q] ^= tableau.xs[:, q] + return True - return NotImplemented + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q = axes[0] + effective_exponent = self._exponent % 2 + for _ in range(int(effective_exponent * 2)): + # Prescription for S left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.M[q, :] ^= state.G[q, :] + state.gamma[q] = (state.gamma[q] - 1) % 4 + # Adjust the global phase based on the global_shift parameter. + state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) + return True def _decompose_into_clifford_with_qubits_(self, qubits): from cirq.ops.clifford_gate import SingleQubitCliffordGate @@ -900,48 +894,45 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= np.sqrt(2) * p return args.target_tensor - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - tableau = args.tableau - q = args.qubit_map[qubits[0]] - if self._exponent % 2 == 1: - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = args.qubit_map[qubits[0]] - state = args.state - if self._exponent % 2 == 1: - # Prescription for H left multiplication - # Reference: https://arxiv.org/abs/1808.00128 - # Equations 48, 49 and Proposition 4 - t = state.s ^ (state.G[q, :] & state.v) - u = state.s ^ (state.F[q, :] & (~state.v)) ^ (state.M[q, :] & state.v) - - alpha = sum(state.G[q, :] & (~state.v) & state.s) % 2 - beta = sum(state.M[q, :] & (~state.v) & state.s) - beta += sum(state.F[q, :] & state.v & state.M[q, :]) - beta += sum(state.F[q, :] & state.v & state.s) - beta %= 2 - - delta = (state.gamma[q] + 2 * (alpha + beta)) % 4 - - state.update_sum(t, u, delta=delta, alpha=alpha) - # Adjust the global phase based on the global_shift parameter. - args.state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q = axes[0] + if self._exponent % 2 == 1: + (tableau.xs[:, q], tableau.zs[:, q]) = ( + tableau.zs[:, q].copy(), + tableau.xs[:, q].copy(), + ) + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + return True - return NotImplemented + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q = axes[0] + if self._exponent % 2 == 1: + # Prescription for H left multiplication + # Reference: https://arxiv.org/abs/1808.00128 + # Equations 48, 49 and Proposition 4 + t = state.s ^ (state.G[q, :] & state.v) + u = state.s ^ (state.F[q, :] & (~state.v)) ^ (state.M[q, :] & state.v) + + alpha = sum(state.G[q, :] & (~state.v) & state.s) % 2 + beta = sum(state.M[q, :] & (~state.v) & state.s) + beta += sum(state.F[q, :] & state.v & state.M[q, :]) + beta += sum(state.F[q, :] & state.v & state.s) + beta %= 2 + + delta = (state.gamma[q] + 2 * (alpha + beta)) % 4 + + state.update_sum(t, u, delta=delta, alpha=alpha) + # Adjust the global phase based on the global_shift parameter. + state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) + return True def _decompose_(self, qubits): q = qubits[0] @@ -1064,51 +1055,46 @@ def _apply_unitary_( args.target_tensor *= p return args.target_tensor - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - tableau = args.tableau - q1 = args.qubit_map[qubits[0]] - q2 = args.qubit_map[qubits[1]] - if self._exponent % 2 == 1: - (tableau.xs[:, q2], tableau.zs[:, q2]) = ( - tableau.zs[:, q2].copy(), - tableau.xs[:, q2].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, q2] & tableau.zs[:, q2] - tableau.rs[:] ^= ( - tableau.xs[:, q1] - & tableau.zs[:, q2] - & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) - ) - tableau.xs[:, q2] ^= tableau.xs[:, q1] - tableau.zs[:, q1] ^= tableau.zs[:, q2] - (tableau.xs[:, q2], tableau.zs[:, q2]) = ( - tableau.zs[:, q2].copy(), - tableau.xs[:, q2].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, q2] & tableau.zs[:, q2] - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q1 = args.qubit_map[qubits[0]] - q2 = args.qubit_map[qubits[1]] - state = args.state - if self._exponent % 2 == 1: - # Prescription for CZ left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.M[q1, :] ^= state.G[q2, :] - state.M[q2, :] ^= state.G[q1, :] - # Adjust the global phase based on the global_shift parameter. - args.state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q1 = axes[0] + q2 = axes[1] + if self._exponent % 2 == 1: + (tableau.xs[:, q2], tableau.zs[:, q2]) = ( + tableau.zs[:, q2].copy(), + tableau.xs[:, q2].copy(), + ) + tableau.rs[:] ^= tableau.xs[:, q2] & tableau.zs[:, q2] + tableau.rs[:] ^= ( + tableau.xs[:, q1] & tableau.zs[:, q2] & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) + ) + tableau.xs[:, q2] ^= tableau.xs[:, q1] + tableau.zs[:, q1] ^= tableau.zs[:, q2] + (tableau.xs[:, q2], tableau.zs[:, q2]) = ( + tableau.zs[:, q2].copy(), + tableau.xs[:, q2].copy(), + ) + tableau.rs[:] ^= tableau.xs[:, q2] & tableau.zs[:, q2] + return True - return NotImplemented + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q1 = axes[0] + q2 = axes[1] + if self._exponent % 2 == 1: + # Prescription for CZ left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.M[q1, :] ^= state.G[q2, :] + state.M[q2, :] ^= state.G[q1, :] + # Adjust the global phase based on the global_shift parameter. + state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) + return True def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): @@ -1292,47 +1278,40 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - tableau = args.tableau - q1 = args.qubit_map[qubits[0]] - q2 = args.qubit_map[qubits[1]] - if self._exponent % 2 == 1: - tableau.rs[:] ^= ( - tableau.xs[:, q1] - & tableau.zs[:, q2] - & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) - ) - tableau.xs[:, q2] ^= tableau.xs[:, q1] - tableau.zs[:, q1] ^= tableau.zs[:, q2] - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q1 = args.qubit_map[qubits[0]] - q2 = args.qubit_map[qubits[1]] - state = args.state - if self._exponent % 2 == 1: - # Prescription for CX left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.gamma[q1] = ( - state.gamma[q1] - + state.gamma[q2] - + 2 * (sum(state.M[q1, :] & state.F[q2, :]) % 2) - ) % 4 - state.G[q2, :] ^= state.G[q1, :] - state.F[q1, :] ^= state.F[q2, :] - state.M[q1, :] ^= state.M[q2, :] - # Adjust the global phase based on the global_shift parameter. - args.state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q1 = axes[0] + q2 = axes[1] + if self._exponent % 2 == 1: + tableau.rs[:] ^= ( + tableau.xs[:, q1] & tableau.zs[:, q2] & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) + ) + tableau.xs[:, q2] ^= tableau.xs[:, q1] + tableau.zs[:, q1] ^= tableau.zs[:, q2] + return True - return NotImplemented + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + q1 = axes[0] + q2 = axes[1] + if self._exponent % 2 == 1: + # Prescription for CX left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.gamma[q1] = ( + state.gamma[q1] + state.gamma[q2] + 2 * (sum(state.M[q1, :] & state.F[q2, :]) % 2) + ) % 4 + state.G[q2, :] ^= state.G[q1, :] + state.F[q1, :] ^= state.F[q2, :] + state.M[q1, :] ^= state.M[q2, :] + # Adjust the global phase based on the global_shift parameter. + state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) + return True def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): diff --git a/cirq-core/cirq/ops/global_phase_op.py b/cirq-core/cirq/ops/global_phase_op.py index ce5a249fce1..6d1e670e6d8 100644 --- a/cirq-core/cirq/ops/global_phase_op.py +++ b/cirq-core/cirq/ops/global_phase_op.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """A no-qubit global phase operation.""" -from typing import Any, Dict, Tuple, TYPE_CHECKING +from typing import Any, Dict, Tuple, TYPE_CHECKING, Sequence import numpy as np @@ -60,19 +60,18 @@ def _apply_unitary_(self, args) -> np.ndarray: def _has_stabilizer_effect_(self) -> bool: return True - def _act_on_(self, args: 'cirq.ActOnArgs'): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - # Since CliffordTableau does not keep track of the global phase, - # it's safe to just ignore it here. - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - args.state.omega *= self.coefficient - return True + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + # Since CliffordTableau does not keep track of the global phase, + # it's safe to just ignore it here. + return True - return NotImplemented + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + state.omega *= self.coefficient + return True def __str__(self) -> str: return str(self.coefficient) diff --git a/cirq-core/cirq/ops/identity.py b/cirq-core/cirq/ops/identity.py index 122185db378..c8bdeec8111 100644 --- a/cirq-core/cirq/ops/identity.py +++ b/cirq-core/cirq/ops/identity.py @@ -65,6 +65,16 @@ def __init__( def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): return True + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + return True + + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + return True + def _qid_shape_(self) -> Tuple[int, ...]: return self._qid_shape diff --git a/cirq-core/cirq/ops/random_gate_channel.py b/cirq-core/cirq/ops/random_gate_channel.py index fbb0a6d66ae..e4dc495be39 100644 --- a/cirq-core/cirq/ops/random_gate_channel.py +++ b/cirq-core/cirq/ops/random_gate_channel.py @@ -123,19 +123,17 @@ def _trace_distance_bound_(self) -> float: result *= float(self.probability) return result - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): if self._is_parameterized_(): return NotImplemented - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if args.prng.random() < self.probability: - # Note: because we're doing this probabilistically, it's not - # safe to fallback to other strategies if act_on fails. Those - # strategies could double-count the probability. - protocols.act_on(self.sub_gate, args, qubits) - return True - return NotImplemented + if prng.random() < self.probability: + # Note: because we're doing this probabilistically, it's not + # safe to fallback to other strategies if act_on fails. Those + # strategies could double-count the probability. + protocols.apply_to_tableau(self.sub_gate, tableau, axes, prng) + return True def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['sub_gate', 'probability']) diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index fd464d6cd6b..9f48ebc61e8 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -94,24 +94,36 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: return None return self.exponent % 1 == 0 - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq import ops, sim, protocols + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ): + from cirq import ops - if isinstance(args, (sim.ActOnStabilizerCHFormArgs, sim.ActOnCliffordTableauArgs)): - if not self._has_stabilizer_effect_(): - return NotImplemented - if isinstance(args, sim.ActOnStabilizerCHFormArgs): - args.state.omega *= 1j ** (2 * self.global_shift * self._exponent) + if not self._has_stabilizer_effect_(): + return NotImplemented + + if self._exponent % 2 == 1: + protocols.apply_to_tableau(ops.CNOT, tableau, axes, prng) + protocols.apply_to_tableau(ops.CNOT, tableau, tuple(reversed(axes)), prng) + protocols.apply_to_tableau(ops.CNOT, tableau, axes, prng) + return True - if self._exponent % 2 == 1: - protocols.act_on(ops.CNOT, args, qubits) - protocols.act_on(ops.CNOT, args, tuple(reversed(qubits))) - protocols.act_on(ops.CNOT, args, qubits) + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ): + from cirq import ops + + if not self._has_stabilizer_effect_(): + return NotImplemented + state.omega *= 1j ** (2 * self.global_shift * self._exponent) - # An even exponent does not change anything except the global phase above. - return True + if self._exponent % 2 == 1: + protocols.apply_to_ch_form(ops.CNOT, state, axes, prng) + protocols.apply_to_ch_form(ops.CNOT, state, tuple(reversed(axes)), prng) + protocols.apply_to_ch_form(ops.CNOT, state, axes, prng) - return NotImplemented + # An even exponent does not change anything except the global phase above. + return True def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.ndarray]: if self._exponent != 1: diff --git a/cirq-core/cirq/protocols/__init__.py b/cirq-core/cirq/protocols/__init__.py index 8cd88ad897e..460aa005240 100644 --- a/cirq-core/cirq/protocols/__init__.py +++ b/cirq-core/cirq/protocols/__init__.py @@ -44,6 +44,12 @@ has_kraus, SupportsKraus, ) +from cirq.protocols.clifford_protocols import ( + apply_to_ch_form, + apply_to_tableau, + SupportsApplyToChForm, + SupportsApplyToTableau, +) from cirq.protocols.commutes_protocol import ( commutes, definitely_commutes, diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py new file mode 100644 index 00000000000..73169937ae0 --- /dev/null +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -0,0 +1,64 @@ +# Copyright 2018 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import ( + Union, + Sequence, + TYPE_CHECKING, +) + +import numpy as np +from typing_extensions import Protocol + +from cirq._doc import doc_private +from cirq.type_workarounds import NotImplementedType + +if TYPE_CHECKING: + import cirq + + +class SupportsApplyToTableau(Protocol): + @doc_private + def _apply_to_tableau_( + self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState + ) -> Union[bool, NotImplementedType]: + """Write me!""" + + +def apply_to_tableau( + val: 'cirq.Gate', + tableau: 'cirq.CliffordTableau', + axes: Sequence[int], + prng: np.random.RandomState, +) -> Union[bool, NotImplementedType]: + getter = getattr(val, '_apply_to_tableau_', None) + return NotImplemented if getter is None else getter(tableau, axes, prng) + + +class SupportsApplyToChForm(Protocol): + @doc_private + def _apply_to_ch_form_( + self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState + ) -> Union[bool, NotImplementedType]: + """Write me!""" + + +def apply_to_ch_form( + val: 'cirq.Gate', + state: 'cirq.StabilizerStateChForm', + axes: Sequence[int], + prng: np.random.RandomState, +) -> Union[bool, NotImplementedType]: + getter = getattr(val, '_apply_to_ch_form_', None) + return NotImplemented if getter is None else getter(state, axes, prng) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 4f5bcce2619..d4c3aa6a855 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -18,6 +18,7 @@ import numpy as np +from cirq import protocols, ops from cirq.ops import common_gates from cirq.ops import pauli_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate @@ -66,7 +67,7 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [] + strats = [_strat_apply_to_tableau] if allow_decompose: strats.append(_strat_act_on_clifford_tableau_from_single_qubit_decompose) for strat in strats: @@ -96,6 +97,13 @@ def sample( raise NotImplementedError() +def _strat_apply_to_tableau( + val: Any, args: 'cirq.ActOnCliffordTableauArgs', qubits: Sequence['cirq.Qid'] +) -> bool: + gate = val.gate if isinstance(val, ops.Operation) else val + return protocols.apply_to_tableau(gate, args.tableau, args.get_axes(qubits), args.prng) + + def _strat_act_on_clifford_tableau_from_single_qubit_decompose( val: Any, args: 'cirq.ActOnCliffordTableauArgs', qubits: Sequence['cirq.Qid'] ) -> bool: @@ -107,12 +115,18 @@ def _strat_act_on_clifford_tableau_from_single_qubit_decompose( if clifford_gate is not None: for axis, quarter_turns in clifford_gate.decompose_rotation(): if axis == pauli_gates.X: - common_gates.XPowGate(exponent=quarter_turns / 2)._act_on_(args, qubits) + protocols.act_on( + common_gates.XPowGate(exponent=quarter_turns / 2), args, qubits + ) elif axis == pauli_gates.Y: - common_gates.YPowGate(exponent=quarter_turns / 2)._act_on_(args, qubits) + protocols.act_on( + common_gates.YPowGate(exponent=quarter_turns / 2), args, qubits + ) else: assert axis == pauli_gates.Z - common_gates.ZPowGate(exponent=quarter_turns / 2)._act_on_(args, qubits) + protocols.act_on( + common_gates.ZPowGate(exponent=quarter_turns / 2), args, qubits + ) return True return NotImplemented diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 62154568052..7fcc4f6e024 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -64,7 +64,7 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [] + strats = [_strat_apply_to_ch_form] if allow_decompose: strats.append(_strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) for strat in strats: @@ -98,6 +98,13 @@ def sample( return np.array(list(measurements.values()), dtype=bool) +def _strat_apply_to_ch_form( + val: Any, args: 'cirq.ActOnStabilizerCHFormArgs', qubits: Sequence['cirq.Qid'] +) -> bool: + gate = val.gate if isinstance(val, ops.Operation) else val + return protocols.apply_to_ch_form(gate, args.state, args.get_axes(qubits), args.prng) + + def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( val: Any, args: 'cirq.ActOnStabilizerCHFormArgs', qubits: Sequence['cirq.Qid'] ) -> bool: @@ -114,14 +121,14 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( gate = None # type: Optional[cirq.Gate] if axis == pauli_gates.X: gate = common_gates.XPowGate(exponent=quarter_turns / 2) - assert gate._act_on_(args, qubits) + protocols.act_on(gate, args, qubits) elif axis == pauli_gates.Y: gate = common_gates.YPowGate(exponent=quarter_turns / 2) - assert gate._act_on_(args, qubits) + protocols.act_on(gate, args, qubits) else: assert axis == pauli_gates.Z gate = common_gates.ZPowGate(exponent=quarter_turns / 2) - assert gate._act_on_(args, qubits) + protocols.act_on(gate, args, qubits) final_unitary = np.matmul(unitary(gate), final_unitary) From 8dd48aeef2f0f3f51e0fa61b20d4372fd9ed509a Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Thu, 11 Nov 2021 15:41:42 -0800 Subject: [PATCH 02/41] revert global phase --- cirq-core/cirq/ops/global_phase_op.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/cirq-core/cirq/ops/global_phase_op.py b/cirq-core/cirq/ops/global_phase_op.py index 6d1e670e6d8..b09a45814e3 100644 --- a/cirq-core/cirq/ops/global_phase_op.py +++ b/cirq-core/cirq/ops/global_phase_op.py @@ -60,18 +60,19 @@ def _apply_unitary_(self, args) -> np.ndarray: def _has_stabilizer_effect_(self) -> bool: return True - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - # Since CliffordTableau does not keep track of the global phase, - # it's safe to just ignore it here. - return True + def _act_on_(self, args: 'cirq.ActOnArgs'): + from cirq.sim import clifford - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - state.omega *= self.coefficient - return True + if isinstance(args, clifford.ActOnCliffordTableauArgs): + # Since CliffordTableau does not keep track of the global phase, + # it's safe to just ignore it here. + return True + + if isinstance(args, clifford.ActOnStabilizerCHFormArgs): + args.state.omega *= self.coefficient + return True + + return NotImplemented def __str__(self) -> str: return str(self.coefficient) From 992c4f6cb96c367a98d80680f42ffc5ca86cc2ae Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Thu, 11 Nov 2021 16:01:34 -0800 Subject: [PATCH 03/41] revert random gate --- cirq-core/cirq/ops/random_gate_channel.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/cirq-core/cirq/ops/random_gate_channel.py b/cirq-core/cirq/ops/random_gate_channel.py index e4dc495be39..fbb0a6d66ae 100644 --- a/cirq-core/cirq/ops/random_gate_channel.py +++ b/cirq-core/cirq/ops/random_gate_channel.py @@ -123,17 +123,19 @@ def _trace_distance_bound_(self) -> float: result *= float(self.probability) return result - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): + def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): + from cirq.sim import clifford + if self._is_parameterized_(): return NotImplemented - if prng.random() < self.probability: - # Note: because we're doing this probabilistically, it's not - # safe to fallback to other strategies if act_on fails. Those - # strategies could double-count the probability. - protocols.apply_to_tableau(self.sub_gate, tableau, axes, prng) - return True + if isinstance(args, clifford.ActOnCliffordTableauArgs): + if args.prng.random() < self.probability: + # Note: because we're doing this probabilistically, it's not + # safe to fallback to other strategies if act_on fails. Those + # strategies could double-count the probability. + protocols.act_on(self.sub_gate, args, qubits) + return True + return NotImplemented def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['sub_gate', 'probability']) From d4366ea1a40c07c13e60bb16c8cf2a0eea6c6357 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 10:15:15 -0800 Subject: [PATCH 04/41] docs --- .../cirq/protocols/clifford_protocols.py | 65 +++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index 73169937ae0..e63215a62ff 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -13,8 +13,9 @@ # limitations under the License. from typing import ( - Union, + Any, Sequence, + Union, TYPE_CHECKING, ) @@ -29,19 +30,47 @@ class SupportsApplyToTableau(Protocol): + """An object that can apply a transformation to a Clifford tableau.""" + @doc_private def _apply_to_tableau_( self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState ) -> Union[bool, NotImplementedType]: - """Write me!""" + """Applies an transform to the given Clifford tableau. + + Args: + tableau: A Clifford tableau that is the target of the transform. + axes: The axes to which the transform should be applied. + prng: A random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ def apply_to_tableau( - val: 'cirq.Gate', + val: Any, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState, ) -> Union[bool, NotImplementedType]: + """Applies an transform to the given Clifford tableau. + + Args: + val: The object (typically a gate) that contains a transform to apply. + tableau: A Clifford tableau that is the target of the transform. + axes: The axes to which the transform should be applied. + prng: The random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ getter = getattr(val, '_apply_to_tableau_', None) return NotImplemented if getter is None else getter(tableau, axes, prng) @@ -51,14 +80,40 @@ class SupportsApplyToChForm(Protocol): def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState ) -> Union[bool, NotImplementedType]: - """Write me!""" + """Applies an transform to the given Clifford CH-form. + + Args: + state: A Clifford CH-form that is the target of the transform. + axes: The axes to which the transform should be applied. + prng: The random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ def apply_to_ch_form( - val: 'cirq.Gate', + val: Any, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState, ) -> Union[bool, NotImplementedType]: + """Applies an transform to the given Clifford CH-form. + + Args: + val: The object (typically a gate) that contains a transform to apply. + state: A Clifford CH-form that is the target of the transform. + axes: The axes to which the transform should be applied. + prng: A random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ getter = getattr(val, '_apply_to_ch_form_', None) return NotImplemented if getter is None else getter(state, axes, prng) From 4f768e80d2ab3c901d9debac41af82879a05d2cc Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 10:29:24 -0800 Subject: [PATCH 05/41] Change act-on calls to apply where possible --- .../sim/clifford/act_on_clifford_tableau_args.py | 15 +++++++++------ .../clifford/act_on_stabilizer_ch_form_args.py | 10 ++++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index d4c3aa6a855..608c9c8840b 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -113,19 +113,22 @@ def _strat_act_on_clifford_tableau_from_single_qubit_decompose( u = unitary(val) clifford_gate = SingleQubitCliffordGate.from_unitary(u) if clifford_gate is not None: + axes = args.get_axes(qubits) + tableau = args.tableau + rng = args.prng for axis, quarter_turns in clifford_gate.decompose_rotation(): if axis == pauli_gates.X: - protocols.act_on( - common_gates.XPowGate(exponent=quarter_turns / 2), args, qubits + protocols.apply_to_tableau( + common_gates.XPowGate(exponent=quarter_turns / 2), tableau, axes, rng ) elif axis == pauli_gates.Y: - protocols.act_on( - common_gates.YPowGate(exponent=quarter_turns / 2), args, qubits + protocols.apply_to_tableau( + common_gates.YPowGate(exponent=quarter_turns / 2), tableau, axes, rng ) else: assert axis == pauli_gates.Z - protocols.act_on( - common_gates.ZPowGate(exponent=quarter_turns / 2), args, qubits + protocols.apply_to_tableau( + common_gates.ZPowGate(exponent=quarter_turns / 2), tableau, axes, rng ) return True diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 7fcc4f6e024..a4062dc71be 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -117,18 +117,20 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( # Gather the effective unitary applied so as to correct for the # global phase later. final_unitary = np.eye(2) + axes = args.get_axes(qubits) + state = args.state + rng = args.prng for axis, quarter_turns in clifford_gate.decompose_rotation(): - gate = None # type: Optional[cirq.Gate] if axis == pauli_gates.X: gate = common_gates.XPowGate(exponent=quarter_turns / 2) - protocols.act_on(gate, args, qubits) + protocols.apply_to_ch_form(gate, state, axes, rng) elif axis == pauli_gates.Y: gate = common_gates.YPowGate(exponent=quarter_turns / 2) - protocols.act_on(gate, args, qubits) + protocols.apply_to_ch_form(gate, state, axes, rng) else: assert axis == pauli_gates.Z gate = common_gates.ZPowGate(exponent=quarter_turns / 2) - protocols.act_on(gate, args, qubits) + protocols.apply_to_ch_form(gate, state, axes, rng) final_unitary = np.matmul(unitary(gate), final_unitary) From d6dc94783ac481a480610f55f91d3ff4be9634f8 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 11:00:02 -0800 Subject: [PATCH 06/41] tests --- cirq-core/cirq/protocols/clifford_protocols.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index e63215a62ff..4fcb48007e4 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -1,4 +1,4 @@ -# Copyright 2018 The Cirq Developers +# Copyright 2021 The Cirq Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ class SupportsApplyToTableau(Protocol): def _apply_to_tableau_( self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState ) -> Union[bool, NotImplementedType]: - """Applies an transform to the given Clifford tableau. + """Applies a transform to the given Clifford tableau. Args: tableau: A Clifford tableau that is the target of the transform. @@ -57,7 +57,7 @@ def apply_to_tableau( axes: Sequence[int], prng: np.random.RandomState, ) -> Union[bool, NotImplementedType]: - """Applies an transform to the given Clifford tableau. + """Applies a transform to the given Clifford tableau. Args: val: The object (typically a gate) that contains a transform to apply. @@ -80,7 +80,7 @@ class SupportsApplyToChForm(Protocol): def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState ) -> Union[bool, NotImplementedType]: - """Applies an transform to the given Clifford CH-form. + """Applies a transform to the given Clifford CH-form. Args: state: A Clifford CH-form that is the target of the transform. @@ -101,7 +101,7 @@ def apply_to_ch_form( axes: Sequence[int], prng: np.random.RandomState, ) -> Union[bool, NotImplementedType]: - """Applies an transform to the given Clifford CH-form. + """Applies a transform to the given Clifford CH-form. Args: val: The object (typically a gate) that contains a transform to apply. From 2ad141fe1028299fdabc05f906e88bf9416affcd Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 11:00:18 -0800 Subject: [PATCH 07/41] tests --- cirq-core/cirq/__init__.py | 4 + .../cirq/protocols/clifford_protocols_test.py | 81 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 cirq-core/cirq/protocols/clifford_protocols_test.py diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index dd66f312590..b00dfb286b3 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -493,6 +493,8 @@ act_on, apply_channel, apply_mixture, + apply_to_ch_form, + apply_to_tableau, apply_unitaries, apply_unitary, ApplyChannelArgs, @@ -547,6 +549,8 @@ SupportsActOnQubits, SupportsApplyChannel, SupportsApplyMixture, + SupportsApplyToChForm, + SupportsApplyToTableau, SupportsApproximateEquality, SupportsConsistentApplyUnitary, SupportsCircuitDiagramInfo, diff --git a/cirq-core/cirq/protocols/clifford_protocols_test.py b/cirq-core/cirq/protocols/clifford_protocols_test.py new file mode 100644 index 00000000000..db3f0d0246c --- /dev/null +++ b/cirq-core/cirq/protocols/clifford_protocols_test.py @@ -0,0 +1,81 @@ +# Copyright 2020 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from typing import Any, Tuple, Union, Sequence + +import numpy as np +import pytest + +import cirq +from cirq.ops.raw_types import TSelf + + +class CountingGate(cirq.SingleQubitGate): + def __init__(self, implemented: bool = True): + self._implemented = implemented + + def _apply_to_tableau_(self, tableau: cirq.CliffordTableau, axes, prng): + if self._implemented: + tableau.n += sum(axes) + return True + return NotImplemented + + def _apply_to_ch_form_(self, state: cirq.StabilizerStateChForm, axes, prng): + if self._implemented: + state.n += sum(axes) + return True + return NotImplemented + + +def test_apply_to_tableau_succeeds(): + gate = CountingGate() + tableau = cirq.CliffordTableau(1) + result = cirq.apply_to_tableau(gate, tableau, [2, 3], np.random.RandomState()) + assert result is True + assert tableau.n == 6 + + +def test_apply_to_ch_form_succeeds(): + gate = CountingGate() + state = cirq.StabilizerStateChForm(1) + result = cirq.apply_to_ch_form(gate, state, [2, 3], np.random.RandomState()) + assert result is True + assert state.n == 6 + + +def test_apply_to_tableau_not_implemented_explicitly(): + gate = CountingGate(implemented=False) + tableau = cirq.CliffordTableau(1) + result = cirq.apply_to_tableau(gate, tableau, [2, 3], np.random.RandomState()) + assert result is NotImplemented + + +def test_apply_to_ch_form_not_implemented_explicitly(): + gate = CountingGate(implemented=False) + state = cirq.StabilizerStateChForm(1) + result = cirq.apply_to_ch_form(gate, state, [2, 3], np.random.RandomState()) + assert result is NotImplemented + + +def test_apply_to_tableau_not_implemented_implicitly(): + gate = cirq.SingleQubitGate() + tableau = cirq.CliffordTableau(1) + result = cirq.apply_to_tableau(gate, tableau, [2, 3], np.random.RandomState()) + assert result is NotImplemented + + +def test_apply_to_ch_form_not_implemented_implicitly(): + gate = cirq.SingleQubitGate() + state = cirq.StabilizerStateChForm(1) + result = cirq.apply_to_ch_form(gate, state, [2, 3], np.random.RandomState()) + assert result is NotImplemented From 7f13f4d04ee524ec130f7c4293a5455d3b9966bf Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 11:00:36 -0800 Subject: [PATCH 08/41] tests --- cirq-core/cirq/protocols/clifford_protocols_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/protocols/clifford_protocols_test.py b/cirq-core/cirq/protocols/clifford_protocols_test.py index db3f0d0246c..cf509bfd130 100644 --- a/cirq-core/cirq/protocols/clifford_protocols_test.py +++ b/cirq-core/cirq/protocols/clifford_protocols_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 The Cirq Developers +# Copyright 2021 The Cirq Developers # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 14ad01a085d70b983f5348a34bdbb88ead5ab109 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 11:29:45 -0800 Subject: [PATCH 09/41] imports --- cirq-core/cirq/protocols/clifford_protocols_test.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cirq-core/cirq/protocols/clifford_protocols_test.py b/cirq-core/cirq/protocols/clifford_protocols_test.py index cf509bfd130..6a31b0d2c48 100644 --- a/cirq-core/cirq/protocols/clifford_protocols_test.py +++ b/cirq-core/cirq/protocols/clifford_protocols_test.py @@ -11,13 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Tuple, Union, Sequence import numpy as np -import pytest import cirq -from cirq.ops.raw_types import TSelf class CountingGate(cirq.SingleQubitGate): From 208e9d3b911de5f5a96b42dfa12a417210936ee5 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 12:20:30 -0800 Subject: [PATCH 10/41] tests --- cirq-core/cirq/protocols/json_test_data/spec.py | 2 ++ cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py | 1 + 2 files changed, 3 insertions(+) diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index 09c3780f3bb..0902a0cd3b8 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -134,6 +134,8 @@ 'SupportsActOnQubits', 'SupportsApplyChannel', 'SupportsApplyMixture', + 'SupportsApplyToChForm', + 'SupportsApplyToTableau', 'SupportsApproximateEquality', 'SupportsCircuitDiagramInfo', 'SupportsCommutes', diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index a4062dc71be..1543c31c403 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -121,6 +121,7 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( state = args.state rng = args.prng for axis, quarter_turns in clifford_gate.decompose_rotation(): + gate = None # type: Optional[cirq.Gate] if axis == pauli_gates.X: gate = common_gates.XPowGate(exponent=quarter_turns / 2) protocols.apply_to_ch_form(gate, state, axes, rng) From 7012fffe19ac236bec7f80dbc83601b2bb408722 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 12 Nov 2021 12:49:10 -0800 Subject: [PATCH 11/41] coverage --- cirq-core/cirq/ops/global_phase_op.py | 2 +- cirq-core/cirq/ops/identity_test.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/global_phase_op.py b/cirq-core/cirq/ops/global_phase_op.py index b09a45814e3..ce5a249fce1 100644 --- a/cirq-core/cirq/ops/global_phase_op.py +++ b/cirq-core/cirq/ops/global_phase_op.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """A no-qubit global phase operation.""" -from typing import Any, Dict, Tuple, TYPE_CHECKING, Sequence +from typing import Any, Dict, Tuple, TYPE_CHECKING import numpy as np diff --git a/cirq-core/cirq/ops/identity_test.py b/cirq-core/cirq/ops/identity_test.py index e8f8f409f1b..4779257df0f 100644 --- a/cirq-core/cirq/ops/identity_test.py +++ b/cirq-core/cirq/ops/identity_test.py @@ -126,6 +126,24 @@ def test_identity_apply_unitary(): assert result is v +def test_identity_apply_to_tableau(): + gate = cirq.I + tableau = cirq.CliffordTableau(1) + copy = tableau.copy() + result = cirq.apply_to_tableau(gate, tableau, [0], np.random.RandomState()) + assert result is True + assert tableau == copy + + +def test_identity_apply_to_ch_form(): + gate = cirq.I + state = cirq.StabilizerStateChForm(1) + copy = state.copy() + result = cirq.apply_to_ch_form(gate, state, [0], np.random.RandomState()) + assert result is True + assert state == copy + + def test_identity_eq(): equals_tester = cirq.testing.EqualsTester() equals_tester.make_equality_group( From d3abe6d4756f5ce8147d05406b0a8a26e0d63541 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 08:17:36 -0800 Subject: [PATCH 12/41] basic paulis --- cirq-core/cirq/__init__.py | 2 + cirq-core/cirq/ops/common_channels.py | 12 +- cirq-core/cirq/ops/common_gates.py | 78 ++++------- cirq-core/cirq/protocols/__init__.py | 2 + .../cirq/protocols/clifford_protocols.py | 40 ++++++ .../clifford/act_on_clifford_tableau_args.py | 130 ++++++++++++------ 6 files changed, 164 insertions(+), 100 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index b00dfb286b3..0b9bb46d0ff 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -501,6 +501,7 @@ ApplyMixtureArgs, ApplyUnitaryArgs, approx_eq, + as_paulis, circuit_diagram_info, CircuitDiagramInfo, CircuitDiagramInfoArgs, @@ -552,6 +553,7 @@ SupportsApplyToChForm, SupportsApplyToTableau, SupportsApproximateEquality, + SupportsAsPaulis, SupportsConsistentApplyUnitary, SupportsCircuitDiagramInfo, SupportsCommutes, diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index 9df6d55fd09..13d64b4ed08 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -316,13 +316,11 @@ def __str__(self) -> str: return f"depolarize(p={self._p})" return f"depolarize(p={self._p},n_qubits={self._n_qubits})" - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - if prng.random() < self._p: - gate = prng.choice([pauli_gates.X, pauli_gates.Y, pauli_gates.Z]) - protocols.apply_to_tableau(gate, tableau, axes, prng) - return True + def _as_paulis_(self, prng: np.random.RandomState): + if prng.random() > self._p: + return [] + gate = prng.choice([pauli_gates.X, pauli_gates.Y, pauli_gates.Z]) + return [(gate, 1, 0)] def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: result: Tuple[str, ...] diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index a1b9c90256b..1dacc7b3778 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -102,22 +102,14 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = axes[0] - effective_exponent = self._exponent % 2 - if effective_exponent == 0.5: - tableau.xs[:, q] ^= tableau.zs[:, q] - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.zs[:, q] - elif effective_exponent == 1.5: - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - tableau.xs[:, q] ^= tableau.zs[:, q] - return True + def _as_paulis_(self, prng: np.random.RandomState): + from cirq.ops import pauli_gates + + if self.exponent % 2 == 0: + return [] + if self.exponent % 0.5 == 0: + return [(pauli_gates.X, self.exponent % 2, 0)] + return NotImplemented def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState @@ -355,28 +347,14 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = axes[0] - effective_exponent = self._exponent % 2 - if effective_exponent == 0.5: - tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), - ) - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.xs[:, q] ^ tableau.zs[:, q] - elif effective_exponent == 1.5: - tableau.rs[:] ^= ~(tableau.xs[:, q]) & tableau.zs[:, q] - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), - ) - return True + def _as_paulis_(self, prng: np.random.RandomState): + from cirq.ops import pauli_gates + + if self.exponent % 2 == 0: + return [] + if self.exponent % 0.5 == 0: + return [(pauli_gates.Y, self.exponent % 2, 0)] + return NotImplemented def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState @@ -577,22 +555,14 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = axes[0] - effective_exponent = self._exponent % 2 - if effective_exponent == 0.5: - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - tableau.zs[:, q] ^= tableau.xs[:, q] - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.xs[:, q] - elif effective_exponent == 1.5: - tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) - tableau.zs[:, q] ^= tableau.xs[:, q] - return True + def _as_paulis_(self, prng: np.random.RandomState): + from cirq.ops import pauli_gates + + if self.exponent % 2 == 0: + return [] + if self.exponent % 0.5 == 0: + return [(pauli_gates.Z, self.exponent % 2, 0)] + return NotImplemented def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState diff --git a/cirq-core/cirq/protocols/__init__.py b/cirq-core/cirq/protocols/__init__.py index 460aa005240..75ac7c8b299 100644 --- a/cirq-core/cirq/protocols/__init__.py +++ b/cirq-core/cirq/protocols/__init__.py @@ -47,8 +47,10 @@ from cirq.protocols.clifford_protocols import ( apply_to_ch_form, apply_to_tableau, + as_paulis, SupportsApplyToChForm, SupportsApplyToTableau, + SupportsAsPaulis, ) from cirq.protocols.commutes_protocol import ( commutes, diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index 4fcb48007e4..453b35417e3 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -17,6 +17,7 @@ Sequence, Union, TYPE_CHECKING, + Tuple, ) import numpy as np @@ -117,3 +118,42 @@ def apply_to_ch_form( """ getter = getattr(val, '_apply_to_ch_form_', None) return NotImplemented if getter is None else getter(state, axes, prng) + + +class SupportsAsPaulis(Protocol): + @doc_private + def _as_paulis_( + self, prng: np.random.RandomState + ) -> Union[Sequence[Tuple['cirq.Pauli', float, int]], NotImplementedType]: + """Transforms the gate to paulis. + + Args: + prng: The random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ + + +def as_paulis( + gate: 'cirq.Gate', prng: np.random.RandomState +) -> Union[Sequence[Tuple['cirq.Pauli', float, int]], NotImplementedType]: + """Applies a transform to the given Clifford CH-form. + + Args: + gate: The object (typically a gate) that contains a transform to apply. + state: A Clifford CH-form that is the target of the transform. + axes: The axes to which the transform should be applied. + prng: A random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ + getter = getattr(gate, '_as_paulis_', None) + return NotImplemented if getter is None else getter(prng) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 608c9c8840b..3546855b1a7 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -67,11 +67,11 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [_strat_apply_to_tableau] + strats = [self._strat_apply_to_tableau] if allow_decompose: - strats.append(_strat_act_on_clifford_tableau_from_single_qubit_decompose) + strats.append(self._strat_act_on_clifford_tableau_from_single_qubit_decompose) for strat in strats: - result = strat(action, self, qubits) + result = strat(action, qubits) if result is False: break # coverage: ignore if result is True: @@ -95,41 +95,93 @@ def sample( ) -> np.ndarray: # Unnecessary for now but can be added later if there is a use case. raise NotImplementedError() - - -def _strat_apply_to_tableau( - val: Any, args: 'cirq.ActOnCliffordTableauArgs', qubits: Sequence['cirq.Qid'] -) -> bool: - gate = val.gate if isinstance(val, ops.Operation) else val - return protocols.apply_to_tableau(gate, args.tableau, args.get_axes(qubits), args.prng) - - -def _strat_act_on_clifford_tableau_from_single_qubit_decompose( - val: Any, args: 'cirq.ActOnCliffordTableauArgs', qubits: Sequence['cirq.Qid'] -) -> bool: - if num_qubits(val) == 1: - if not has_unitary(val): - return NotImplemented - u = unitary(val) - clifford_gate = SingleQubitCliffordGate.from_unitary(u) - if clifford_gate is not None: - axes = args.get_axes(qubits) - tableau = args.tableau - rng = args.prng - for axis, quarter_turns in clifford_gate.decompose_rotation(): - if axis == pauli_gates.X: - protocols.apply_to_tableau( - common_gates.XPowGate(exponent=quarter_turns / 2), tableau, axes, rng - ) - elif axis == pauli_gates.Y: - protocols.apply_to_tableau( - common_gates.YPowGate(exponent=quarter_turns / 2), tableau, axes, rng - ) + + def _x(self, q, exponent): + assert exponent % 0.5 == 0.0 + tableau = self.tableau + effective_exponent = exponent % 2 + if effective_exponent == 0.5: + tableau.xs[:, q] ^= tableau.zs[:, q] + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.zs[:, q] + elif effective_exponent == 1.5: + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + tableau.xs[:, q] ^= tableau.zs[:, q] + + def _y(self, q, exponent): + assert exponent % 0.5 == 0.0 + tableau = self.tableau + effective_exponent = exponent % 2 + if effective_exponent == 0.5: + tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) + (tableau.xs[:, q], tableau.zs[:, q]) = ( + tableau.zs[:, q].copy(), + tableau.xs[:, q].copy(), + ) + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.xs[:, q] ^ tableau.zs[:, q] + elif effective_exponent == 1.5: + tableau.rs[:] ^= ~(tableau.xs[:, q]) & tableau.zs[:, q] + (tableau.xs[:, q], tableau.zs[:, q]) = ( + tableau.zs[:, q].copy(), + tableau.xs[:, q].copy(), + ) + + def _z(self, q, exponent): + assert exponent % 0.5 == 0.0 + tableau = self.tableau + effective_exponent = exponent % 2 + if effective_exponent == 0.5: + tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + tableau.zs[:, q] ^= tableau.xs[:, q] + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.xs[:, q] + elif effective_exponent == 1.5: + tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) + tableau.zs[:, q] ^= tableau.xs[:, q] + + def _strat_apply_to_tableau( + self, val: Any, qubits: Sequence['cirq.Qid'] + ) -> bool: + val = val.gate if isinstance(val, ops.Operation) else val + paulis = protocols.as_paulis(val, self.prng) + if paulis is not NotImplemented: + for pauli, exponent, axis in paulis: + q = self.qubit_map[qubits[axis]] + if pauli is pauli_gates.X: + self._x(q, exponent) + elif pauli is pauli_gates.Y: + self._y(q, exponent) + elif pauli is pauli_gates.Z: + self._z(q, exponent) else: - assert axis == pauli_gates.Z - protocols.apply_to_tableau( - common_gates.ZPowGate(exponent=quarter_turns / 2), tableau, axes, rng - ) + assert False return True - - return NotImplemented + else: + gate = val.gate if isinstance(val, ops.Operation) else val + return protocols.apply_to_tableau(gate, self.tableau, self.get_axes(qubits), self.prng) + + def _strat_act_on_clifford_tableau_from_single_qubit_decompose( + self, val: Any, qubits: Sequence['cirq.Qid'] + ) -> bool: + if num_qubits(val) == 1: + if not has_unitary(val): + return NotImplemented + u = unitary(val) + clifford_gate = SingleQubitCliffordGate.from_unitary(u) + if clifford_gate is not None: + axes = self.get_axes(qubits) + tableau = self.tableau + rng = self.prng + for axis, quarter_turns in clifford_gate.decompose_rotation(): + if axis == pauli_gates.X: + self._x(axes[0], quarter_turns / 2) + elif axis == pauli_gates.Y: + self._y(axes[0], quarter_turns / 2) + else: + assert axis == pauli_gates.Z + self._z(axes[0], quarter_turns / 2) + return True + + return NotImplemented From d6b62c9811a20c3321674c19dcf7649ceb187aed Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 10:46:41 -0800 Subject: [PATCH 13/41] cz --- cirq-core/cirq/ops/common_channels.py | 4 +- cirq-core/cirq/ops/common_gates.py | 66 +++------- .../cirq/protocols/clifford_protocols.py | 4 +- .../clifford/act_on_clifford_tableau_args.py | 114 ++++++++++-------- 4 files changed, 90 insertions(+), 98 deletions(-) diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index 13d64b4ed08..0b8c42205f1 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -319,8 +319,8 @@ def __str__(self) -> str: def _as_paulis_(self, prng: np.random.RandomState): if prng.random() > self._p: return [] - gate = prng.choice([pauli_gates.X, pauli_gates.Y, pauli_gates.Z]) - return [(gate, 1, 0)] + gate = prng.choice(['X', 'Y', 'Z']) + return [(gate, 1, [0])] def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: result: Tuple[str, ...] diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index 1dacc7b3778..3e2feea27d4 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -103,12 +103,10 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda return args.available_buffer def _as_paulis_(self, prng: np.random.RandomState): - from cirq.ops import pauli_gates - if self.exponent % 2 == 0: return [] if self.exponent % 0.5 == 0: - return [(pauli_gates.X, self.exponent % 2, 0)] + return [('X', self.exponent % 2, [0])] return NotImplemented def _apply_to_ch_form_( @@ -348,12 +346,10 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda return args.available_buffer def _as_paulis_(self, prng: np.random.RandomState): - from cirq.ops import pauli_gates - if self.exponent % 2 == 0: return [] if self.exponent % 0.5 == 0: - return [(pauli_gates.Y, self.exponent % 2, 0)] + return [('Y', self.exponent % 2, [0])] return NotImplemented def _apply_to_ch_form_( @@ -556,12 +552,10 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda return args.target_tensor def _as_paulis_(self, prng: np.random.RandomState): - from cirq.ops import pauli_gates - if self.exponent % 2 == 0: return [] if self.exponent % 0.5 == 0: - return [(pauli_gates.Z, self.exponent % 2, 0)] + return [('Z', self.exponent % 2, [0])] return NotImplemented def _apply_to_ch_form_( @@ -842,6 +836,16 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: } ) + def _as_paulis_(self, prng: np.random.RandomState): + if self.exponent % 2 == 0: + return [] + if self.exponent % 2 == 1: + return [ + ('Y', 0.5, [0]), + ('X', 1, [0]), + ] + return NotImplemented + def _decompose_into_clifford_with_qubits_(self, qubits): from cirq.ops.clifford_gate import SingleQubitCliffordGate @@ -864,20 +868,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= np.sqrt(2) * p return args.target_tensor - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = axes[0] - if self._exponent % 2 == 1: - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - return True - def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState ): @@ -1025,30 +1015,12 @@ def _apply_unitary_( args.target_tensor *= p return args.target_tensor - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q1 = axes[0] - q2 = axes[1] - if self._exponent % 2 == 1: - (tableau.xs[:, q2], tableau.zs[:, q2]) = ( - tableau.zs[:, q2].copy(), - tableau.xs[:, q2].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, q2] & tableau.zs[:, q2] - tableau.rs[:] ^= ( - tableau.xs[:, q1] & tableau.zs[:, q2] & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) - ) - tableau.xs[:, q2] ^= tableau.xs[:, q1] - tableau.zs[:, q1] ^= tableau.zs[:, q2] - (tableau.xs[:, q2], tableau.zs[:, q2]) = ( - tableau.zs[:, q2].copy(), - tableau.xs[:, q2].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, q2] & tableau.zs[:, q2] - return True + def _as_paulis_(self, prng: np.random.RandomState): + if self.exponent % 2 == 0: + return [] + if self.exponent % 2 == 1: + return [('CZ', self.exponent % 2, [0, 1])] + return NotImplemented def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index 453b35417e3..ad5bb7df30b 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -124,7 +124,7 @@ class SupportsAsPaulis(Protocol): @doc_private def _as_paulis_( self, prng: np.random.RandomState - ) -> Union[Sequence[Tuple['cirq.Pauli', float, int]], NotImplementedType]: + ) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: """Transforms the gate to paulis. Args: @@ -140,7 +140,7 @@ def _as_paulis_( def as_paulis( gate: 'cirq.Gate', prng: np.random.RandomState -) -> Union[Sequence[Tuple['cirq.Pauli', float, int]], NotImplementedType]: +) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: """Applies a transform to the given Clifford CH-form. Args: diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 3546855b1a7..acbf2cf39ca 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -95,73 +95,95 @@ def sample( ) -> np.ndarray: # Unnecessary for now but can be added later if there is a use case. raise NotImplementedError() - - def _x(self, q, exponent): + + def _x(self, exponent, axis): assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 if effective_exponent == 0.5: - tableau.xs[:, q] ^= tableau.zs[:, q] - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] + tableau.xs[:, axis] ^= tableau.zs[:, axis] + tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] elif effective_exponent == 1: - tableau.rs[:] ^= tableau.zs[:, q] + tableau.rs[:] ^= tableau.zs[:, axis] elif effective_exponent == 1.5: - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - tableau.xs[:, q] ^= tableau.zs[:, q] - - def _y(self, q, exponent): + tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] + tableau.xs[:, axis] ^= tableau.zs[:, axis] + + def _y(self, exponent, axis): assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 if effective_exponent == 0.5: - tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), + tableau.rs[:] ^= tableau.xs[:, axis] & (~tableau.zs[:, axis]) + (tableau.xs[:, axis], tableau.zs[:, axis]) = ( + tableau.zs[:, axis].copy(), + tableau.xs[:, axis].copy(), ) elif effective_exponent == 1: - tableau.rs[:] ^= tableau.xs[:, q] ^ tableau.zs[:, q] + tableau.rs[:] ^= tableau.xs[:, axis] ^ tableau.zs[:, axis] elif effective_exponent == 1.5: - tableau.rs[:] ^= ~(tableau.xs[:, q]) & tableau.zs[:, q] - (tableau.xs[:, q], tableau.zs[:, q]) = ( - tableau.zs[:, q].copy(), - tableau.xs[:, q].copy(), + tableau.rs[:] ^= ~(tableau.xs[:, axis]) & tableau.zs[:, axis] + (tableau.xs[:, axis], tableau.zs[:, axis]) = ( + tableau.zs[:, axis].copy(), + tableau.xs[:, axis].copy(), ) - def _z(self, q, exponent): + def _z(self, exponent, axis): assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 if effective_exponent == 0.5: - tableau.rs[:] ^= tableau.xs[:, q] & tableau.zs[:, q] - tableau.zs[:, q] ^= tableau.xs[:, q] + tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] + tableau.zs[:, axis] ^= tableau.xs[:, axis] elif effective_exponent == 1: - tableau.rs[:] ^= tableau.xs[:, q] + tableau.rs[:] ^= tableau.xs[:, axis] elif effective_exponent == 1.5: - tableau.rs[:] ^= tableau.xs[:, q] & (~tableau.zs[:, q]) - tableau.zs[:, q] ^= tableau.xs[:, q] + tableau.rs[:] ^= tableau.xs[:, axis] & (~tableau.zs[:, axis]) + tableau.zs[:, axis] ^= tableau.xs[:, axis] - def _strat_apply_to_tableau( - self, val: Any, qubits: Sequence['cirq.Qid'] - ) -> bool: + def _cz(self, exponent, axis1, axis2): + assert exponent % 2 == 1 + tableau = self.tableau + (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( + tableau.zs[:, axis2].copy(), + tableau.xs[:, axis2].copy(), + ) + tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + tableau.rs[:] ^= ( + tableau.xs[:, axis1] + & tableau.zs[:, axis2] + & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) + ) + tableau.xs[:, axis2] ^= tableau.xs[:, axis1] + tableau.zs[:, axis1] ^= tableau.zs[:, axis2] + (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( + tableau.zs[:, axis2].copy(), + tableau.xs[:, axis2].copy(), + ) + tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + + def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: val = val.gate if isinstance(val, ops.Operation) else val paulis = protocols.as_paulis(val, self.prng) if paulis is not NotImplemented: - for pauli, exponent, axis in paulis: - q = self.qubit_map[qubits[axis]] - if pauli is pauli_gates.X: - self._x(q, exponent) - elif pauli is pauli_gates.Y: - self._y(q, exponent) - elif pauli is pauli_gates.Z: - self._z(q, exponent) + for pauli, exponent, raw_axes in paulis: + qubits = [qubits[i] for i in raw_axes] + axes = self.get_axes(qubits) + if pauli == 'X': + self._x(exponent, axes[0]) + elif pauli == 'Y': + self._y(exponent, axes[0]) + elif pauli == 'Z': + self._z(exponent, axes[0]) + elif pauli == 'CZ': + self._cz(exponent, axes[0], axes[1]) else: assert False return True else: gate = val.gate if isinstance(val, ops.Operation) else val return protocols.apply_to_tableau(gate, self.tableau, self.get_axes(qubits), self.prng) - + def _strat_act_on_clifford_tableau_from_single_qubit_decompose( self, val: Any, qubits: Sequence['cirq.Qid'] ) -> bool: @@ -171,17 +193,15 @@ def _strat_act_on_clifford_tableau_from_single_qubit_decompose( u = unitary(val) clifford_gate = SingleQubitCliffordGate.from_unitary(u) if clifford_gate is not None: - axes = self.get_axes(qubits) - tableau = self.tableau - rng = self.prng - for axis, quarter_turns in clifford_gate.decompose_rotation(): - if axis == pauli_gates.X: - self._x(axes[0], quarter_turns / 2) - elif axis == pauli_gates.Y: - self._y(axes[0], quarter_turns / 2) + axis = self.qubit_map[qubits[0]] + for gate, quarter_turns in clifford_gate.decompose_rotation(): + if gate == pauli_gates.X: + self._x(quarter_turns / 2, axis) + elif gate == pauli_gates.Y: + self._y(quarter_turns / 2, axis) else: - assert axis == pauli_gates.Z - self._z(axes[0], quarter_turns / 2) + assert gate == pauli_gates.Z + self._z(quarter_turns / 2, axis) return True - + return NotImplemented From d1c733867fba54449d190c105d6aea5ea296daaf Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 10:51:51 -0800 Subject: [PATCH 14/41] cx --- cirq-core/cirq/ops/common_gates.py | 20 ++++++------------ .../clifford/act_on_clifford_tableau_args.py | 21 +++++++++++++++---- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index 3e2feea27d4..658a0cf93f6 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -1220,20 +1220,12 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q1 = axes[0] - q2 = axes[1] - if self._exponent % 2 == 1: - tableau.rs[:] ^= ( - tableau.xs[:, q1] & tableau.zs[:, q2] & (~(tableau.xs[:, q2] ^ tableau.zs[:, q1])) - ) - tableau.xs[:, q2] ^= tableau.xs[:, q1] - tableau.zs[:, q1] ^= tableau.zs[:, q2] - return True + def _as_paulis_(self, prng: np.random.RandomState): + if self.exponent % 2 == 0: + return [] + if self.exponent % 2 == 1: + return [('CX', self.exponent % 2, [0, 1])] + return NotImplemented def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index acbf2cf39ca..aed7e41ee31 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -96,7 +96,7 @@ def sample( # Unnecessary for now but can be added later if there is a use case. raise NotImplementedError() - def _x(self, exponent, axis): + def _x(self, exponent: float, axis: int): assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 @@ -109,7 +109,7 @@ def _x(self, exponent, axis): tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] tableau.xs[:, axis] ^= tableau.zs[:, axis] - def _y(self, exponent, axis): + def _y(self, exponent: float, axis: int): assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 @@ -128,7 +128,7 @@ def _y(self, exponent, axis): tableau.xs[:, axis].copy(), ) - def _z(self, exponent, axis): + def _z(self, exponent: float, axis: int): assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 @@ -141,7 +141,7 @@ def _z(self, exponent, axis): tableau.rs[:] ^= tableau.xs[:, axis] & (~tableau.zs[:, axis]) tableau.zs[:, axis] ^= tableau.xs[:, axis] - def _cz(self, exponent, axis1, axis2): + def _cz(self, exponent: float, axis1: int, axis2: int): assert exponent % 2 == 1 tableau = self.tableau (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( @@ -162,6 +162,17 @@ def _cz(self, exponent, axis1, axis2): ) tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + def _cx(self, exponent: float, axis1: int, axis2: int): + assert exponent % 2 == 1 + tableau = self.tableau + tableau.rs[:] ^= ( + tableau.xs[:, axis1] + & tableau.zs[:, axis2] + & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) + ) + tableau.xs[:, axis2] ^= tableau.xs[:, axis1] + tableau.zs[:, axis1] ^= tableau.zs[:, axis2] + def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: val = val.gate if isinstance(val, ops.Operation) else val paulis = protocols.as_paulis(val, self.prng) @@ -177,6 +188,8 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo self._z(exponent, axes[0]) elif pauli == 'CZ': self._cz(exponent, axes[0], axes[1]) + elif pauli == 'CX': + self._cx(exponent, axes[0], axes[1]) else: assert False return True From 91a904bc5faabf935a968ea27ee9f3d1cb748311 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 11:17:17 -0800 Subject: [PATCH 15/41] swap --- cirq-core/cirq/ops/identity.py | 6 ++---- cirq-core/cirq/ops/identity_test.py | 9 --------- cirq-core/cirq/ops/swap_gates.py | 23 ++++++++++------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/cirq-core/cirq/ops/identity.py b/cirq-core/cirq/ops/identity.py index c8bdeec8111..0b148be9d26 100644 --- a/cirq-core/cirq/ops/identity.py +++ b/cirq-core/cirq/ops/identity.py @@ -65,10 +65,8 @@ def __init__( def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): return True - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - return True + def _as_paulis_(self, prng: np.random.RandomState): + return [] def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState diff --git a/cirq-core/cirq/ops/identity_test.py b/cirq-core/cirq/ops/identity_test.py index 4779257df0f..a974bd79026 100644 --- a/cirq-core/cirq/ops/identity_test.py +++ b/cirq-core/cirq/ops/identity_test.py @@ -126,15 +126,6 @@ def test_identity_apply_unitary(): assert result is v -def test_identity_apply_to_tableau(): - gate = cirq.I - tableau = cirq.CliffordTableau(1) - copy = tableau.copy() - result = cirq.apply_to_tableau(gate, tableau, [0], np.random.RandomState()) - assert result is True - assert tableau == copy - - def test_identity_apply_to_ch_form(): gate = cirq.I state = cirq.StabilizerStateChForm(1) diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index 9f48ebc61e8..d48554d0743 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -94,19 +94,16 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: return None return self.exponent % 1 == 0 - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ): - from cirq import ops - - if not self._has_stabilizer_effect_(): - return NotImplemented - - if self._exponent % 2 == 1: - protocols.apply_to_tableau(ops.CNOT, tableau, axes, prng) - protocols.apply_to_tableau(ops.CNOT, tableau, tuple(reversed(axes)), prng) - protocols.apply_to_tableau(ops.CNOT, tableau, axes, prng) - return True + def _as_paulis_(self, prng: np.random.RandomState): + if self.exponent % 2 == 0: + return [] + if self.exponent % 2 == 1: + return [ + ('CX', 1, [0, 1]), + ('CX', 1, [1, 0]), + ('CX', 1, [0, 1]), + ] + return NotImplemented def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState From bf13385f590e6a95376d288094fc28db6507435c Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 11:50:38 -0800 Subject: [PATCH 16/41] fix --- cirq-core/cirq/ops/common_gates.py | 4 ++-- cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index 658a0cf93f6..e4fc8777b4f 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -1019,7 +1019,7 @@ def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: return [] if self.exponent % 2 == 1: - return [('CZ', self.exponent % 2, [0, 1])] + return [('CZ', 1, [0, 1])] return NotImplemented def _apply_to_ch_form_( @@ -1224,7 +1224,7 @@ def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: return [] if self.exponent % 2 == 1: - return [('CX', self.exponent % 2, [0, 1])] + return [('CX', 1, [0, 1])] return NotImplemented def _apply_to_ch_form_( diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index aed7e41ee31..55d52891483 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -177,9 +177,9 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo val = val.gate if isinstance(val, ops.Operation) else val paulis = protocols.as_paulis(val, self.prng) if paulis is not NotImplemented: - for pauli, exponent, raw_axes in paulis: - qubits = [qubits[i] for i in raw_axes] - axes = self.get_axes(qubits) + for pauli, exponent, indexes in paulis: + affected_qubits = [qubits[i] for i in indexes] + axes = self.get_axes(affected_qubits) if pauli == 'X': self._x(exponent, axes[0]) elif pauli == 'Y': From 1417b14976baed853d0149958ca60bf923d0b69f Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 11:56:50 -0800 Subject: [PATCH 17/41] remove apply_to_tableau --- cirq-core/cirq/__init__.py | 2 - cirq-core/cirq/protocols/__init__.py | 2 - .../cirq/protocols/clifford_protocols.py | 46 ------------------- .../cirq/protocols/clifford_protocols_test.py | 28 ----------- .../cirq/protocols/json_test_data/spec.py | 2 +- .../clifford/act_on_clifford_tableau_args.py | 38 ++++++++------- 6 files changed, 19 insertions(+), 99 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 0b9bb46d0ff..919ccdb0478 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -494,7 +494,6 @@ apply_channel, apply_mixture, apply_to_ch_form, - apply_to_tableau, apply_unitaries, apply_unitary, ApplyChannelArgs, @@ -551,7 +550,6 @@ SupportsApplyChannel, SupportsApplyMixture, SupportsApplyToChForm, - SupportsApplyToTableau, SupportsApproximateEquality, SupportsAsPaulis, SupportsConsistentApplyUnitary, diff --git a/cirq-core/cirq/protocols/__init__.py b/cirq-core/cirq/protocols/__init__.py index 75ac7c8b299..8b58ce117f0 100644 --- a/cirq-core/cirq/protocols/__init__.py +++ b/cirq-core/cirq/protocols/__init__.py @@ -46,10 +46,8 @@ ) from cirq.protocols.clifford_protocols import ( apply_to_ch_form, - apply_to_tableau, as_paulis, SupportsApplyToChForm, - SupportsApplyToTableau, SupportsAsPaulis, ) from cirq.protocols.commutes_protocol import ( diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index ad5bb7df30b..a7dd0db0ee9 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -30,52 +30,6 @@ import cirq -class SupportsApplyToTableau(Protocol): - """An object that can apply a transformation to a Clifford tableau.""" - - @doc_private - def _apply_to_tableau_( - self, tableau: 'cirq.CliffordTableau', axes: Sequence[int], prng: np.random.RandomState - ) -> Union[bool, NotImplementedType]: - """Applies a transform to the given Clifford tableau. - - Args: - tableau: A Clifford tableau that is the target of the transform. - axes: The axes to which the transform should be applied. - prng: A random number generator to use if necessary. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - - -def apply_to_tableau( - val: Any, - tableau: 'cirq.CliffordTableau', - axes: Sequence[int], - prng: np.random.RandomState, -) -> Union[bool, NotImplementedType]: - """Applies a transform to the given Clifford tableau. - - Args: - val: The object (typically a gate) that contains a transform to apply. - tableau: A Clifford tableau that is the target of the transform. - axes: The axes to which the transform should be applied. - prng: The random number generator to use if necessary. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - getter = getattr(val, '_apply_to_tableau_', None) - return NotImplemented if getter is None else getter(tableau, axes, prng) - - class SupportsApplyToChForm(Protocol): @doc_private def _apply_to_ch_form_( diff --git a/cirq-core/cirq/protocols/clifford_protocols_test.py b/cirq-core/cirq/protocols/clifford_protocols_test.py index 6a31b0d2c48..4a839079e3a 100644 --- a/cirq-core/cirq/protocols/clifford_protocols_test.py +++ b/cirq-core/cirq/protocols/clifford_protocols_test.py @@ -21,12 +21,6 @@ class CountingGate(cirq.SingleQubitGate): def __init__(self, implemented: bool = True): self._implemented = implemented - def _apply_to_tableau_(self, tableau: cirq.CliffordTableau, axes, prng): - if self._implemented: - tableau.n += sum(axes) - return True - return NotImplemented - def _apply_to_ch_form_(self, state: cirq.StabilizerStateChForm, axes, prng): if self._implemented: state.n += sum(axes) @@ -34,14 +28,6 @@ def _apply_to_ch_form_(self, state: cirq.StabilizerStateChForm, axes, prng): return NotImplemented -def test_apply_to_tableau_succeeds(): - gate = CountingGate() - tableau = cirq.CliffordTableau(1) - result = cirq.apply_to_tableau(gate, tableau, [2, 3], np.random.RandomState()) - assert result is True - assert tableau.n == 6 - - def test_apply_to_ch_form_succeeds(): gate = CountingGate() state = cirq.StabilizerStateChForm(1) @@ -50,13 +36,6 @@ def test_apply_to_ch_form_succeeds(): assert state.n == 6 -def test_apply_to_tableau_not_implemented_explicitly(): - gate = CountingGate(implemented=False) - tableau = cirq.CliffordTableau(1) - result = cirq.apply_to_tableau(gate, tableau, [2, 3], np.random.RandomState()) - assert result is NotImplemented - - def test_apply_to_ch_form_not_implemented_explicitly(): gate = CountingGate(implemented=False) state = cirq.StabilizerStateChForm(1) @@ -64,13 +43,6 @@ def test_apply_to_ch_form_not_implemented_explicitly(): assert result is NotImplemented -def test_apply_to_tableau_not_implemented_implicitly(): - gate = cirq.SingleQubitGate() - tableau = cirq.CliffordTableau(1) - result = cirq.apply_to_tableau(gate, tableau, [2, 3], np.random.RandomState()) - assert result is NotImplemented - - def test_apply_to_ch_form_not_implemented_implicitly(): gate = cirq.SingleQubitGate() state = cirq.StabilizerStateChForm(1) diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index 0902a0cd3b8..fab8bae9f11 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -135,8 +135,8 @@ 'SupportsApplyChannel', 'SupportsApplyMixture', 'SupportsApplyToChForm', - 'SupportsApplyToTableau', 'SupportsApproximateEquality', + 'SupportsAsPaulis', 'SupportsCircuitDiagramInfo', 'SupportsCommutes', 'SupportsConsistentApplyUnitary', diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 55d52891483..db2fe88d951 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -176,26 +176,24 @@ def _cx(self, exponent: float, axis1: int, axis2: int): def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: val = val.gate if isinstance(val, ops.Operation) else val paulis = protocols.as_paulis(val, self.prng) - if paulis is not NotImplemented: - for pauli, exponent, indexes in paulis: - affected_qubits = [qubits[i] for i in indexes] - axes = self.get_axes(affected_qubits) - if pauli == 'X': - self._x(exponent, axes[0]) - elif pauli == 'Y': - self._y(exponent, axes[0]) - elif pauli == 'Z': - self._z(exponent, axes[0]) - elif pauli == 'CZ': - self._cz(exponent, axes[0], axes[1]) - elif pauli == 'CX': - self._cx(exponent, axes[0], axes[1]) - else: - assert False - return True - else: - gate = val.gate if isinstance(val, ops.Operation) else val - return protocols.apply_to_tableau(gate, self.tableau, self.get_axes(qubits), self.prng) + if paulis is NotImplemented: + return NotImplemented + for pauli, exponent, indexes in paulis: + affected_qubits = [qubits[i] for i in indexes] + axes = self.get_axes(affected_qubits) + if pauli == 'X': + self._x(exponent, axes[0]) + elif pauli == 'Y': + self._y(exponent, axes[0]) + elif pauli == 'Z': + self._z(exponent, axes[0]) + elif pauli == 'CZ': + self._cz(exponent, axes[0], axes[1]) + elif pauli == 'CX': + self._cx(exponent, axes[0], axes[1]) + else: + assert False + return True def _strat_act_on_clifford_tableau_from_single_qubit_decompose( self, val: Any, qubits: Sequence['cirq.Qid'] From 6d142f9db7023f364152c8431f369c5c7a243ab2 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 12:45:06 -0800 Subject: [PATCH 18/41] ch --- cirq-core/cirq/ops/common_channels.py | 24 +-- cirq-core/cirq/ops/common_gates.py | 26 +-- cirq-core/cirq/ops/identity.py | 2 +- cirq-core/cirq/ops/swap_gates.py | 4 +- .../cirq/protocols/clifford_protocols.py | 4 +- .../clifford/act_on_clifford_tableau_args.py | 2 +- .../act_on_stabilizer_ch_form_args.py | 194 +++++++++++++----- 7 files changed, 175 insertions(+), 81 deletions(-) diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index 0b8c42205f1..38d34784bda 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -318,9 +318,9 @@ def __str__(self) -> str: def _as_paulis_(self, prng: np.random.RandomState): if prng.random() > self._p: - return [] + return [], 1 gate = prng.choice(['X', 'Y', 'Z']) - return [(gate, 1, [0])] + return [(gate, 1, [0])], 1 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: result: Tuple[str, ...] @@ -718,20 +718,14 @@ def _qasm_(self, args: 'cirq.QasmArgs', qubits: Tuple['cirq.Qid', ...]) -> Optio def _qid_shape_(self): return (self._dimension,) - def _apply_to_ch_form_( - self, - ch_form: 'cirq.StabilizerStateChForm', - axes: Sequence[int], - prng: np.random.RandomState, - ): - from cirq import ops - - if ch_form._measure(axes[0], prng): - protocols.apply_to_ch_form(ops.X, ch_form, axes, prng) - return True - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq import sim + from cirq import sim, ops + + if isinstance(args, sim.ActOnStabilizerCHFormArgs): + axe = args.qubit_map[qubits[0]] + if args.state._measure(axe, args.prng): + protocols.act_on(ops.X, args, qubits) + return True if isinstance(args, sim.ActOnStateVectorArgs): # Do a silent measurement. diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index e4fc8777b4f..bdbd04ad60d 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -104,9 +104,9 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [] + return [], 1 if self.exponent % 0.5 == 0: - return [('X', self.exponent % 2, [0])] + return [('X', self.exponent % 2, [0])], 1 return NotImplemented def _apply_to_ch_form_( @@ -347,9 +347,9 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [] + return [], 1 if self.exponent % 0.5 == 0: - return [('Y', self.exponent % 2, [0])] + return [('Y', self.exponent % 2, [0])], 1 return NotImplemented def _apply_to_ch_form_( @@ -553,9 +553,11 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [] + return [], 1 if self.exponent % 0.5 == 0: - return [('Z', self.exponent % 2, [0])] + return [('Z', self.exponent % 2, [0])], np.exp( + 1j * np.pi * self.global_shift * self.exponent + ) return NotImplemented def _apply_to_ch_form_( @@ -838,12 +840,12 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [] + return [], 1 if self.exponent % 2 == 1: return [ ('Y', 0.5, [0]), ('X', 1, [0]), - ] + ], 1 return NotImplemented def _decompose_into_clifford_with_qubits_(self, qubits): @@ -1017,9 +1019,9 @@ def _apply_unitary_( def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [] + return [], 1 if self.exponent % 2 == 1: - return [('CZ', 1, [0, 1])] + return [('CZ', 1, [0, 1])], 1 return NotImplemented def _apply_to_ch_form_( @@ -1222,9 +1224,9 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [] + return [], 1 if self.exponent % 2 == 1: - return [('CX', 1, [0, 1])] + return [('CX', 1, [0, 1])], 1 return NotImplemented def _apply_to_ch_form_( diff --git a/cirq-core/cirq/ops/identity.py b/cirq-core/cirq/ops/identity.py index 0b148be9d26..712e50f8bd7 100644 --- a/cirq-core/cirq/ops/identity.py +++ b/cirq-core/cirq/ops/identity.py @@ -66,7 +66,7 @@ def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): return True def _as_paulis_(self, prng: np.random.RandomState): - return [] + return [], 1 def _apply_to_ch_form_( self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index d48554d0743..b4e4119c439 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -96,13 +96,13 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [] + return [], 1 if self.exponent % 2 == 1: return [ ('CX', 1, [0, 1]), ('CX', 1, [1, 0]), ('CX', 1, [0, 1]), - ] + ], 1 return NotImplemented def _apply_to_ch_form_( diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index a7dd0db0ee9..a8dbe5d42da 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -78,7 +78,7 @@ class SupportsAsPaulis(Protocol): @doc_private def _as_paulis_( self, prng: np.random.RandomState - ) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: + ) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: """Transforms the gate to paulis. Args: @@ -94,7 +94,7 @@ def _as_paulis_( def as_paulis( gate: 'cirq.Gate', prng: np.random.RandomState -) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: +) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: """Applies a transform to the given Clifford CH-form. Args: diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index db2fe88d951..be1d4253d12 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -19,7 +19,6 @@ import numpy as np from cirq import protocols, ops -from cirq.ops import common_gates from cirq.ops import pauli_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary @@ -178,6 +177,7 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo paulis = protocols.as_paulis(val, self.prng) if paulis is NotImplemented: return NotImplemented + paulis, _ = paulis for pauli, exponent, indexes in paulis: affected_qubits = [qubits[i] for i in indexes] axes = self.get_axes(affected_qubits) diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 1543c31c403..11bc5be6abd 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -64,11 +64,11 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [_strat_apply_to_ch_form] + strats = [self._strat_apply_to_ch_form, self._strat_apply_to_ch_form2] if allow_decompose: - strats.append(_strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) + strats.append(self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) for strat in strats: - result = strat(action, self, qubits) + result = strat(action, qubits) if result is True: return True assert result is NotImplemented, str(result) @@ -97,49 +97,147 @@ def sample( protocols.act_on(op, ch_form_args) return np.array(list(measurements.values()), dtype=bool) - -def _strat_apply_to_ch_form( - val: Any, args: 'cirq.ActOnStabilizerCHFormArgs', qubits: Sequence['cirq.Qid'] -) -> bool: - gate = val.gate if isinstance(val, ops.Operation) else val - return protocols.apply_to_ch_form(gate, args.state, args.get_axes(qubits), args.prng) - - -def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( - val: Any, args: 'cirq.ActOnStabilizerCHFormArgs', qubits: Sequence['cirq.Qid'] -) -> bool: - if num_qubits(val) == 1: - if not has_unitary(val): + def _x(self, exponent: float, axis: int): + assert exponent % 0.5 == 0.0 + tableau = self.tableau + effective_exponent = exponent % 2 + if effective_exponent == 0.5: + tableau.xs[:, axis] ^= tableau.zs[:, axis] + tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.zs[:, axis] + elif effective_exponent == 1.5: + tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] + tableau.xs[:, axis] ^= tableau.zs[:, axis] + + def _y(self, exponent: float, axis: int): + assert exponent % 0.5 == 0.0 + tableau = self.tableau + effective_exponent = exponent % 2 + if effective_exponent == 0.5: + tableau.rs[:] ^= tableau.xs[:, axis] & (~tableau.zs[:, axis]) + (tableau.xs[:, axis], tableau.zs[:, axis]) = ( + tableau.zs[:, axis].copy(), + tableau.xs[:, axis].copy(), + ) + elif effective_exponent == 1: + tableau.rs[:] ^= tableau.xs[:, axis] ^ tableau.zs[:, axis] + elif effective_exponent == 1.5: + tableau.rs[:] ^= ~(tableau.xs[:, axis]) & tableau.zs[:, axis] + (tableau.xs[:, axis], tableau.zs[:, axis]) = ( + tableau.zs[:, axis].copy(), + tableau.xs[:, axis].copy(), + ) + + def _z(self, exponent: float, axis: int): + effective_exponent = exponent % 2 + state = self.state + for _ in range(int(effective_exponent * 2)): + # Prescription for S left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.M[axis, :] ^= state.G[axis, :] + state.gamma[axis] = (state.gamma[axis] - 1) % 4 + + def _cz(self, exponent: float, axis1: int, axis2: int): + assert exponent % 2 == 1 + tableau = self.tableau + (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( + tableau.zs[:, axis2].copy(), + tableau.xs[:, axis2].copy(), + ) + tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + tableau.rs[:] ^= ( + tableau.xs[:, axis1] + & tableau.zs[:, axis2] + & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) + ) + tableau.xs[:, axis2] ^= tableau.xs[:, axis1] + tableau.zs[:, axis1] ^= tableau.zs[:, axis2] + (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( + tableau.zs[:, axis2].copy(), + tableau.xs[:, axis2].copy(), + ) + tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + + def _cx(self, exponent: float, axis1: int, axis2: int): + assert exponent % 2 == 1 + tableau = self.tableau + tableau.rs[:] ^= ( + tableau.xs[:, axis1] + & tableau.zs[:, axis2] + & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) + ) + tableau.xs[:, axis2] ^= tableau.xs[:, axis1] + tableau.zs[:, axis1] ^= tableau.zs[:, axis2] + + def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: + val = val.gate if isinstance(val, ops.Operation) else val + paulis = protocols.as_paulis(val, self.prng) + if paulis is NotImplemented: return NotImplemented - u = unitary(val) - clifford_gate = SingleQubitCliffordGate.from_unitary(u) - if clifford_gate is not None: - # Gather the effective unitary applied so as to correct for the - # global phase later. - final_unitary = np.eye(2) - axes = args.get_axes(qubits) - state = args.state - rng = args.prng - for axis, quarter_turns in clifford_gate.decompose_rotation(): - gate = None # type: Optional[cirq.Gate] - if axis == pauli_gates.X: - gate = common_gates.XPowGate(exponent=quarter_turns / 2) - protocols.apply_to_ch_form(gate, state, axes, rng) - elif axis == pauli_gates.Y: - gate = common_gates.YPowGate(exponent=quarter_turns / 2) - protocols.apply_to_ch_form(gate, state, axes, rng) - else: - assert axis == pauli_gates.Z - gate = common_gates.ZPowGate(exponent=quarter_turns / 2) - protocols.apply_to_ch_form(gate, state, axes, rng) - - final_unitary = np.matmul(unitary(gate), final_unitary) - - # Find the entry with the largest magnitude in the input unitary. - k = max(np.ndindex(*u.shape), key=lambda t: abs(u[t])) - # Correct the global phase that wasn't conserved in the above - # decomposition. - args.state.omega *= u[k] / final_unitary[k] - return True - - return NotImplemented + print(paulis) + paulis, phase = paulis + for pauli, exponent, indexes in paulis: + affected_qubits = [qubits[i] for i in indexes] + axes = self.get_axes(affected_qubits) + if pauli == 'X': + return NotImplemented + self._x(exponent, axes[0]) + elif pauli == 'Y': + return NotImplemented + self._y(exponent, axes[0]) + elif pauli == 'Z': + self._z(exponent, axes[0]) + elif pauli == 'CZ': + return NotImplemented + self._cz(exponent, axes[0], axes[1]) + elif pauli == 'CX': + return NotImplemented + self._cx(exponent, axes[0], axes[1]) + else: + assert False + self.state.omega *= phase + return True + + def _strat_apply_to_ch_form2(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: + gate = val.gate if isinstance(val, ops.Operation) else val + return protocols.apply_to_ch_form(gate, self.state, self.get_axes(qubits), self.prng) + + def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( + self, val: Any, qubits: Sequence['cirq.Qid'] + ) -> bool: + if num_qubits(val) == 1: + if not has_unitary(val): + return NotImplemented + u = unitary(val) + clifford_gate = SingleQubitCliffordGate.from_unitary(u) + if clifford_gate is not None: + # Gather the effective unitary applied so as to correct for the + # global phase later. + final_unitary = np.eye(2) + axes = self.get_axes(qubits) + state = self.state + rng = self.prng + for axis, quarter_turns in clifford_gate.decompose_rotation(): + gate = None # type: Optional[cirq.Gate] + if axis == pauli_gates.X: + gate = common_gates.XPowGate(exponent=quarter_turns / 2) + protocols.apply_to_ch_form(gate, state, axes, rng) + elif axis == pauli_gates.Y: + gate = common_gates.YPowGate(exponent=quarter_turns / 2) + protocols.apply_to_ch_form(gate, state, axes, rng) + else: + assert axis == pauli_gates.Z + gate = common_gates.ZPowGate(exponent=quarter_turns / 2) + protocols.apply_to_ch_form(gate, state, axes, rng) + + final_unitary = np.matmul(unitary(gate), final_unitary) + + # Find the entry with the largest magnitude in the input unitary. + k = max(np.ndindex(*u.shape), key=lambda t: abs(u[t])) + # Correct the global phase that wasn't conserved in the above + # decomposition. + self.state.omega *= u[k] / final_unitary[k] + return True + + return NotImplemented From 5a513ddc4e834a0448f1f6e8fe906289285235ea Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 15:41:42 -0800 Subject: [PATCH 19/41] ch --- cirq-core/cirq/__init__.py | 2 + cirq-core/cirq/ops/common_channels.py | 4 +- cirq-core/cirq/ops/common_gates.py | 106 +++++++++++++++--- cirq-core/cirq/ops/identity.py | 8 +- cirq-core/cirq/ops/identity_test.py | 9 -- cirq-core/cirq/ops/swap_gates.py | 4 +- cirq-core/cirq/protocols/__init__.py | 2 + .../cirq/protocols/clifford_protocols.py | 43 ++++++- .../cirq/protocols/json_test_data/spec.py | 1 + .../clifford/act_on_clifford_tableau_args.py | 2 +- .../act_on_stabilizer_ch_form_args.py | 70 ++++++------ 11 files changed, 180 insertions(+), 71 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 919ccdb0478..c644dbd23eb 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -500,6 +500,7 @@ ApplyMixtureArgs, ApplyUnitaryArgs, approx_eq, + as_ch, as_paulis, circuit_diagram_info, CircuitDiagramInfo, @@ -551,6 +552,7 @@ SupportsApplyMixture, SupportsApplyToChForm, SupportsApproximateEquality, + SupportsAsCH, SupportsAsPaulis, SupportsConsistentApplyUnitary, SupportsCircuitDiagramInfo, diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index 38d34784bda..f3602206f18 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -318,9 +318,9 @@ def __str__(self) -> str: def _as_paulis_(self, prng: np.random.RandomState): if prng.random() > self._p: - return [], 1 + return [] gate = prng.choice(['X', 'Y', 'Z']) - return [(gate, 1, [0])], 1 + return [(gate, 1, [0])] def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: result: Tuple[str, ...] diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index bdbd04ad60d..785fe39fb72 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -104,9 +104,23 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [], 1 + return [] if self.exponent % 0.5 == 0: - return [('X', self.exponent % 2, [0])], 1 + return [('X', self.exponent % 2, [0])] + return NotImplemented + + def _as_ch_(self, prng: np.random.RandomState): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + phase = np.exp(1j * np.pi * self.global_shift * self.exponent) + if self.exponent % 2 == 0: + return [], phase + if self.exponent % 2 == 1: + return [ + ('H', 1, [0]), + ('Z', self.exponent, [0]), + ('H', 1, [0]), + ], phase return NotImplemented def _apply_to_ch_form_( @@ -347,9 +361,35 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [], 1 + return [] if self.exponent % 0.5 == 0: - return [('Y', self.exponent % 2, [0])], 1 + return [('Y', self.exponent % 2, [0])] + return NotImplemented + + def _as_ch_(self, prng: np.random.RandomState): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + phase = np.exp(1j * np.pi * self.global_shift * self.exponent) + effective_exponent = self._exponent % 2 + if effective_exponent == 0: + return [], phase + elif effective_exponent == 0.5: + return [ + ('Z', 1, [0]), + ('H', 1, [0]), + ], phase * (1 + 1j) / (2 ** 0.5) + elif effective_exponent == 1: + return [ + ('Z', 1, [0]), + ('H', 1, [0]), + ('Z', 1, [0]), + ('H', 1, [0]), + ], phase * 1j + elif effective_exponent == 1.5: + return [ + ('H', 1, [0]), + ('Z', 1, [0]), + ], phase * (1 - 1j) / (2 ** 0.5) return NotImplemented def _apply_to_ch_form_( @@ -553,11 +593,19 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [], 1 + return [] if self.exponent % 0.5 == 0: - return [('Z', self.exponent % 2, [0])], np.exp( - 1j * np.pi * self.global_shift * self.exponent - ) + return [('Z', self.exponent % 2, [0])] + return NotImplemented + + def _as_ch_(self, prng: np.random.RandomState): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + phase = np.exp(1j * np.pi * self.global_shift * self.exponent) + if self.exponent % 2 == 0: + return [], phase + if self.exponent % 0.5 == 0: + return [('Z', self.exponent % 2, [0])], phase return NotImplemented def _apply_to_ch_form_( @@ -840,12 +888,22 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [], 1 + return [] if self.exponent % 2 == 1: return [ ('Y', 0.5, [0]), ('X', 1, [0]), - ], 1 + ] + return NotImplemented + + def _as_ch_(self, prng: np.random.RandomState): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + phase = np.exp(1j * np.pi * self.global_shift * self.exponent) + if self.exponent % 2 == 0: + return [], phase + if self.exponent % 2 == 1: + return [('H', 1, [0])], phase return NotImplemented def _decompose_into_clifford_with_qubits_(self, qubits): @@ -1019,9 +1077,19 @@ def _apply_unitary_( def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [], 1 + return [] + if self.exponent % 2 == 1: + return [('CZ', 1, [0, 1])] + return NotImplemented + + def _as_ch_(self, prng: np.random.RandomState): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + phase = np.exp(1j * np.pi * self.global_shift * self.exponent) + if self.exponent % 2 == 0: + return [], phase if self.exponent % 2 == 1: - return [('CZ', 1, [0, 1])], 1 + return [('CZ', 1, [0, 1])], phase return NotImplemented def _apply_to_ch_form_( @@ -1224,9 +1292,19 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [], 1 + return [] + if self.exponent % 2 == 1: + return [('CX', 1, [0, 1])] + return NotImplemented + + def _as_ch_(self, prng: np.random.RandomState): + if not protocols.has_stabilizer_effect(self): + return NotImplemented + phase = np.exp(1j * np.pi * self.global_shift * self.exponent) + if self.exponent % 2 == 0: + return [], phase if self.exponent % 2 == 1: - return [('CX', 1, [0, 1])], 1 + return [('CX', 1, [0, 1])], phase return NotImplemented def _apply_to_ch_form_( diff --git a/cirq-core/cirq/ops/identity.py b/cirq-core/cirq/ops/identity.py index 712e50f8bd7..a1f252bdfab 100644 --- a/cirq-core/cirq/ops/identity.py +++ b/cirq-core/cirq/ops/identity.py @@ -66,12 +66,10 @@ def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): return True def _as_paulis_(self, prng: np.random.RandomState): - return [], 1 + return [] - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - return True + def _as_ch_(self, prng: np.random.RandomState): + return [], 1 def _qid_shape_(self) -> Tuple[int, ...]: return self._qid_shape diff --git a/cirq-core/cirq/ops/identity_test.py b/cirq-core/cirq/ops/identity_test.py index a974bd79026..e8f8f409f1b 100644 --- a/cirq-core/cirq/ops/identity_test.py +++ b/cirq-core/cirq/ops/identity_test.py @@ -126,15 +126,6 @@ def test_identity_apply_unitary(): assert result is v -def test_identity_apply_to_ch_form(): - gate = cirq.I - state = cirq.StabilizerStateChForm(1) - copy = state.copy() - result = cirq.apply_to_ch_form(gate, state, [0], np.random.RandomState()) - assert result is True - assert state == copy - - def test_identity_eq(): equals_tester = cirq.testing.EqualsTester() equals_tester.make_equality_group( diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index b4e4119c439..d48554d0743 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -96,13 +96,13 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: def _as_paulis_(self, prng: np.random.RandomState): if self.exponent % 2 == 0: - return [], 1 + return [] if self.exponent % 2 == 1: return [ ('CX', 1, [0, 1]), ('CX', 1, [1, 0]), ('CX', 1, [0, 1]), - ], 1 + ] return NotImplemented def _apply_to_ch_form_( diff --git a/cirq-core/cirq/protocols/__init__.py b/cirq-core/cirq/protocols/__init__.py index 8b58ce117f0..50c7742c701 100644 --- a/cirq-core/cirq/protocols/__init__.py +++ b/cirq-core/cirq/protocols/__init__.py @@ -46,8 +46,10 @@ ) from cirq.protocols.clifford_protocols import ( apply_to_ch_form, + as_ch, as_paulis, SupportsApplyToChForm, + SupportsAsCH, SupportsAsPaulis, ) from cirq.protocols.commutes_protocol import ( diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index a8dbe5d42da..05204526d4f 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -78,7 +78,7 @@ class SupportsAsPaulis(Protocol): @doc_private def _as_paulis_( self, prng: np.random.RandomState - ) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: + ) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: """Transforms the gate to paulis. Args: @@ -94,7 +94,7 @@ def _as_paulis_( def as_paulis( gate: 'cirq.Gate', prng: np.random.RandomState -) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: +) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: """Applies a transform to the given Clifford CH-form. Args: @@ -111,3 +111,42 @@ def as_paulis( """ getter = getattr(gate, '_as_paulis_', None) return NotImplemented if getter is None else getter(prng) + + +class SupportsAsCH(Protocol): + @doc_private + def _as_ch_( + self, prng: np.random.RandomState + ) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: + """Transforms the gate to ch. + + Args: + prng: The random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ + + +def as_ch( + gate: 'cirq.Gate', prng: np.random.RandomState +) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: + """Applies a transform to the given Clifford CH-form. + + Args: + gate: The object (typically a gate) that contains a transform to apply. + state: A Clifford CH-form that is the target of the transform. + axes: The axes to which the transform should be applied. + prng: A random number generator to use if necessary. + + Returns: + True: The receiving object (`self`) could apply a transform. + NotImplemented: The receiving object cannot apply a transform. + + All other return values are considered to be errors. + """ + getter = getattr(gate, '_as_ch_', None) + return NotImplemented if getter is None else getter(prng) diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index fab8bae9f11..d7cf58d7cae 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -136,6 +136,7 @@ 'SupportsApplyMixture', 'SupportsApplyToChForm', 'SupportsApproximateEquality', + 'SupportsAsCH', 'SupportsAsPaulis', 'SupportsCircuitDiagramInfo', 'SupportsCommutes', diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index be1d4253d12..db2fe88d951 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -19,6 +19,7 @@ import numpy as np from cirq import protocols, ops +from cirq.ops import common_gates from cirq.ops import pauli_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary @@ -177,7 +178,6 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo paulis = protocols.as_paulis(val, self.prng) if paulis is NotImplemented: return NotImplemented - paulis, _ = paulis for pauli, exponent, indexes in paulis: affected_qubits = [qubits[i] for i in indexes] axes = self.get_axes(affected_qubits) diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 11bc5be6abd..dc254ddf3e0 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -138,41 +138,45 @@ def _z(self, exponent: float, axis: int): state.M[axis, :] ^= state.G[axis, :] state.gamma[axis] = (state.gamma[axis] - 1) % 4 + def _h(self, exponent: float, axis: int): + state = self.state + + # Prescription for H left multiplication + # Reference: https://arxiv.org/abs/1808.00128 + # Equations 48, 49 and Proposition 4 + t = state.s ^ (state.G[axis, :] & state.v) + u = state.s ^ (state.F[axis, :] & (~state.v)) ^ (state.M[axis, :] & state.v) + alpha = sum(state.G[axis, :] & (~state.v) & state.s) % 2 + beta = sum(state.M[axis, :] & (~state.v) & state.s) + beta += sum(state.F[axis, :] & state.v & state.M[axis, :]) + beta += sum(state.F[axis, :] & state.v & state.s) + beta %= 2 + delta = (state.gamma[axis] + 2 * (alpha + beta)) % 4 + state.update_sum(t, u, delta=delta, alpha=alpha) + def _cz(self, exponent: float, axis1: int, axis2: int): assert exponent % 2 == 1 - tableau = self.tableau - (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( - tableau.zs[:, axis2].copy(), - tableau.xs[:, axis2].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] - tableau.rs[:] ^= ( - tableau.xs[:, axis1] - & tableau.zs[:, axis2] - & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) - ) - tableau.xs[:, axis2] ^= tableau.xs[:, axis1] - tableau.zs[:, axis1] ^= tableau.zs[:, axis2] - (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( - tableau.zs[:, axis2].copy(), - tableau.xs[:, axis2].copy(), - ) - tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + state = self.state + # Prescription for CZ left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.M[axis1, :] ^= state.G[axis2, :] + state.M[axis2, :] ^= state.G[axis1, :] def _cx(self, exponent: float, axis1: int, axis2: int): assert exponent % 2 == 1 - tableau = self.tableau - tableau.rs[:] ^= ( - tableau.xs[:, axis1] - & tableau.zs[:, axis2] - & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) - ) - tableau.xs[:, axis2] ^= tableau.xs[:, axis1] - tableau.zs[:, axis1] ^= tableau.zs[:, axis2] + state = self.state + # Prescription for CX left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.gamma[axis1] = ( + state.gamma[axis1] + state.gamma[axis2] + 2 * (sum(state.M[axis1, :] & state.F[axis2, :]) % 2) + ) % 4 + state.G[axis2, :] ^= state.G[axis1, :] + state.F[axis1, :] ^= state.F[axis2, :] + state.M[axis1, :] ^= state.M[axis2, :] def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: val = val.gate if isinstance(val, ops.Operation) else val - paulis = protocols.as_paulis(val, self.prng) + paulis = protocols.as_ch(val, self.prng) if paulis is NotImplemented: return NotImplemented print(paulis) @@ -180,19 +184,13 @@ def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo for pauli, exponent, indexes in paulis: affected_qubits = [qubits[i] for i in indexes] axes = self.get_axes(affected_qubits) - if pauli == 'X': - return NotImplemented - self._x(exponent, axes[0]) - elif pauli == 'Y': - return NotImplemented - self._y(exponent, axes[0]) - elif pauli == 'Z': + if pauli == 'Z': self._z(exponent, axes[0]) + elif pauli == 'H': + self._h(exponent, axes[0]) elif pauli == 'CZ': - return NotImplemented self._cz(exponent, axes[0], axes[1]) elif pauli == 'CX': - return NotImplemented self._cx(exponent, axes[0], axes[1]) else: assert False From 3473a4c6e1a45b03cbad81dbd4df402ba8f7eff9 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Fri, 19 Nov 2021 16:10:15 -0800 Subject: [PATCH 20/41] remove apply_to_ch --- cirq-core/cirq/__init__.py | 2 - cirq-core/cirq/ops/common_gates.py | 135 ++---------------- cirq-core/cirq/ops/swap_gates.py | 27 ++-- cirq-core/cirq/protocols/__init__.py | 2 - .../cirq/protocols/clifford_protocols.py | 44 ------ .../cirq/protocols/clifford_protocols_test.py | 28 ---- .../cirq/protocols/json_test_data/spec.py | 1 - .../act_on_stabilizer_ch_form_args.py | 55 +------ 8 files changed, 27 insertions(+), 267 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index c644dbd23eb..e2e3e3c3604 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -493,7 +493,6 @@ act_on, apply_channel, apply_mixture, - apply_to_ch_form, apply_unitaries, apply_unitary, ApplyChannelArgs, @@ -550,7 +549,6 @@ SupportsActOnQubits, SupportsApplyChannel, SupportsApplyMixture, - SupportsApplyToChForm, SupportsApproximateEquality, SupportsAsCH, SupportsAsPaulis, diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index 785fe39fb72..34887bf9372 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -113,27 +113,11 @@ def _as_ch_(self, prng: np.random.RandomState): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 2 == 1: - return [ - ('H', 1, [0]), - ('Z', self.exponent, [0]), - ('H', 1, [0]), - ], phase - return NotImplemented - - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - protocols.apply_to_ch_form(H, state, axes, prng) - protocols.apply_to_ch_form(ZPowGate(exponent=self._exponent), state, axes, prng) - protocols.apply_to_ch_form(H, state, axes, prng) - # Adjust the global phase based on the global_shift parameter. - state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True + return [ + ('H', 1, [0]), + ('Z', self._exponent, [0]), + ('H', 1, [0]), + ], phase def in_su2(self) -> 'Rx': """Returns an equal-up-global-phase gate from the group SU2.""" @@ -374,10 +358,11 @@ def _as_ch_(self, prng: np.random.RandomState): if effective_exponent == 0: return [], phase elif effective_exponent == 0.5: + phase *= (1 + 1j) / (2 ** 0.5) return [ ('Z', 1, [0]), ('H', 1, [0]), - ], phase * (1 + 1j) / (2 ** 0.5) + ], phase elif effective_exponent == 1: return [ ('Z', 1, [0]), @@ -386,37 +371,13 @@ def _as_ch_(self, prng: np.random.RandomState): ('H', 1, [0]), ], phase * 1j elif effective_exponent == 1.5: + phase *= (1 - 1j) / (2 ** 0.5) return [ ('H', 1, [0]), ('Z', 1, [0]), - ], phase * (1 - 1j) / (2 ** 0.5) + ], phase return NotImplemented - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - effective_exponent = self._exponent % 2 - Z = ZPowGate() - if effective_exponent == 0.5: - protocols.apply_to_ch_form(Z, state, axes, prng) - protocols.apply_to_ch_form(H, state, axes, prng) - state.omega *= (1 + 1j) / (2 ** 0.5) - elif effective_exponent == 1: - protocols.apply_to_ch_form(Z, state, axes, prng) - protocols.apply_to_ch_form(H, state, axes, prng) - protocols.apply_to_ch_form(Z, state, axes, prng) - protocols.apply_to_ch_form(H, state, axes, prng) - state.omega *= 1j - elif effective_exponent == 1.5: - protocols.apply_to_ch_form(H, state, axes, prng) - protocols.apply_to_ch_form(Z, state, axes, prng) - state.omega *= (1 - 1j) / (2 ** 0.5) - # Adjust the global phase based on the global_shift parameter. - state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True - def in_su2(self) -> 'Ry': """Returns an equal-up-global-phase gate from the group SU2.""" return Ry(rads=self._exponent * _pi(self._exponent)) @@ -608,22 +569,6 @@ def _as_ch_(self, prng: np.random.RandomState): return [('Z', self.exponent % 2, [0])], phase return NotImplemented - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = axes[0] - effective_exponent = self._exponent % 2 - for _ in range(int(effective_exponent * 2)): - # Prescription for S left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.M[q, :] ^= state.G[q, :] - state.gamma[q] = (state.gamma[q] - 1) % 4 - # Adjust the global phase based on the global_shift parameter. - state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True - def _decompose_into_clifford_with_qubits_(self, qubits): from cirq.ops.clifford_gate import SingleQubitCliffordGate @@ -928,32 +873,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= np.sqrt(2) * p return args.target_tensor - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q = axes[0] - if self._exponent % 2 == 1: - # Prescription for H left multiplication - # Reference: https://arxiv.org/abs/1808.00128 - # Equations 48, 49 and Proposition 4 - t = state.s ^ (state.G[q, :] & state.v) - u = state.s ^ (state.F[q, :] & (~state.v)) ^ (state.M[q, :] & state.v) - - alpha = sum(state.G[q, :] & (~state.v) & state.s) % 2 - beta = sum(state.M[q, :] & (~state.v) & state.s) - beta += sum(state.F[q, :] & state.v & state.M[q, :]) - beta += sum(state.F[q, :] & state.v & state.s) - beta %= 2 - - delta = (state.gamma[q] + 2 * (alpha + beta)) % 4 - - state.update_sum(t, u, delta=delta, alpha=alpha) - # Adjust the global phase based on the global_shift parameter. - state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True - def _decompose_(self, qubits): q = qubits[0] @@ -1092,22 +1011,6 @@ def _as_ch_(self, prng: np.random.RandomState): return [('CZ', 1, [0, 1])], phase return NotImplemented - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q1 = axes[0] - q2 = axes[1] - if self._exponent % 2 == 1: - # Prescription for CZ left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.M[q1, :] ^= state.G[q2, :] - state.M[q2, :] ^= state.G[q1, :] - # Adjust the global phase based on the global_shift parameter. - state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True - def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): return NotImplemented @@ -1307,26 +1210,6 @@ def _as_ch_(self, prng: np.random.RandomState): return [('CX', 1, [0, 1])], phase return NotImplemented - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - q1 = axes[0] - q2 = axes[1] - if self._exponent % 2 == 1: - # Prescription for CX left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.gamma[q1] = ( - state.gamma[q1] + state.gamma[q2] + 2 * (sum(state.M[q1, :] & state.F[q2, :]) % 2) - ) % 4 - state.G[q2, :] ^= state.G[q1, :] - state.F[q1, :] ^= state.F[q2, :] - state.M[q1, :] ^= state.M[q2, :] - # Adjust the global phase based on the global_shift parameter. - state.omega *= np.exp(1j * np.pi * self.global_shift * self.exponent) - return True - def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): return NotImplemented diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index d48554d0743..4f8631200e6 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -105,22 +105,17 @@ def _as_paulis_(self, prng: np.random.RandomState): ] return NotImplemented - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ): - from cirq import ops - - if not self._has_stabilizer_effect_(): - return NotImplemented - state.omega *= 1j ** (2 * self.global_shift * self._exponent) - - if self._exponent % 2 == 1: - protocols.apply_to_ch_form(ops.CNOT, state, axes, prng) - protocols.apply_to_ch_form(ops.CNOT, state, tuple(reversed(axes)), prng) - protocols.apply_to_ch_form(ops.CNOT, state, axes, prng) - - # An even exponent does not change anything except the global phase above. - return True + def _as_ch_(self, prng: np.random.RandomState): + phase = np.exp(1j * np.pi * self.global_shift * self.exponent) + if self.exponent % 2 == 0: + return [], phase + if self.exponent % 2 == 1: + return [ + ('CX', 1, [0, 1]), + ('CX', 1, [1, 0]), + ('CX', 1, [0, 1]), + ], phase + return NotImplemented def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.ndarray]: if self._exponent != 1: diff --git a/cirq-core/cirq/protocols/__init__.py b/cirq-core/cirq/protocols/__init__.py index 50c7742c701..16f8f1241f3 100644 --- a/cirq-core/cirq/protocols/__init__.py +++ b/cirq-core/cirq/protocols/__init__.py @@ -45,10 +45,8 @@ SupportsKraus, ) from cirq.protocols.clifford_protocols import ( - apply_to_ch_form, as_ch, as_paulis, - SupportsApplyToChForm, SupportsAsCH, SupportsAsPaulis, ) diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index 05204526d4f..896677a139b 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -30,50 +30,6 @@ import cirq -class SupportsApplyToChForm(Protocol): - @doc_private - def _apply_to_ch_form_( - self, state: 'cirq.StabilizerStateChForm', axes: Sequence[int], prng: np.random.RandomState - ) -> Union[bool, NotImplementedType]: - """Applies a transform to the given Clifford CH-form. - - Args: - state: A Clifford CH-form that is the target of the transform. - axes: The axes to which the transform should be applied. - prng: The random number generator to use if necessary. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - - -def apply_to_ch_form( - val: Any, - state: 'cirq.StabilizerStateChForm', - axes: Sequence[int], - prng: np.random.RandomState, -) -> Union[bool, NotImplementedType]: - """Applies a transform to the given Clifford CH-form. - - Args: - val: The object (typically a gate) that contains a transform to apply. - state: A Clifford CH-form that is the target of the transform. - axes: The axes to which the transform should be applied. - prng: A random number generator to use if necessary. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - getter = getattr(val, '_apply_to_ch_form_', None) - return NotImplemented if getter is None else getter(state, axes, prng) - - class SupportsAsPaulis(Protocol): @doc_private def _as_paulis_( diff --git a/cirq-core/cirq/protocols/clifford_protocols_test.py b/cirq-core/cirq/protocols/clifford_protocols_test.py index 4a839079e3a..051db264ec0 100644 --- a/cirq-core/cirq/protocols/clifford_protocols_test.py +++ b/cirq-core/cirq/protocols/clifford_protocols_test.py @@ -20,31 +20,3 @@ class CountingGate(cirq.SingleQubitGate): def __init__(self, implemented: bool = True): self._implemented = implemented - - def _apply_to_ch_form_(self, state: cirq.StabilizerStateChForm, axes, prng): - if self._implemented: - state.n += sum(axes) - return True - return NotImplemented - - -def test_apply_to_ch_form_succeeds(): - gate = CountingGate() - state = cirq.StabilizerStateChForm(1) - result = cirq.apply_to_ch_form(gate, state, [2, 3], np.random.RandomState()) - assert result is True - assert state.n == 6 - - -def test_apply_to_ch_form_not_implemented_explicitly(): - gate = CountingGate(implemented=False) - state = cirq.StabilizerStateChForm(1) - result = cirq.apply_to_ch_form(gate, state, [2, 3], np.random.RandomState()) - assert result is NotImplemented - - -def test_apply_to_ch_form_not_implemented_implicitly(): - gate = cirq.SingleQubitGate() - state = cirq.StabilizerStateChForm(1) - result = cirq.apply_to_ch_form(gate, state, [2, 3], np.random.RandomState()) - assert result is NotImplemented diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index d7cf58d7cae..1c667fcf952 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -134,7 +134,6 @@ 'SupportsActOnQubits', 'SupportsApplyChannel', 'SupportsApplyMixture', - 'SupportsApplyToChForm', 'SupportsApproximateEquality', 'SupportsAsCH', 'SupportsAsPaulis', diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index dc254ddf3e0..73a548e13ad 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -64,7 +64,7 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [self._strat_apply_to_ch_form, self._strat_apply_to_ch_form2] + strats = [self._strat_apply_to_ch_form] if allow_decompose: strats.append(self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) for strat in strats: @@ -97,38 +97,6 @@ def sample( protocols.act_on(op, ch_form_args) return np.array(list(measurements.values()), dtype=bool) - def _x(self, exponent: float, axis: int): - assert exponent % 0.5 == 0.0 - tableau = self.tableau - effective_exponent = exponent % 2 - if effective_exponent == 0.5: - tableau.xs[:, axis] ^= tableau.zs[:, axis] - tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.zs[:, axis] - elif effective_exponent == 1.5: - tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] - tableau.xs[:, axis] ^= tableau.zs[:, axis] - - def _y(self, exponent: float, axis: int): - assert exponent % 0.5 == 0.0 - tableau = self.tableau - effective_exponent = exponent % 2 - if effective_exponent == 0.5: - tableau.rs[:] ^= tableau.xs[:, axis] & (~tableau.zs[:, axis]) - (tableau.xs[:, axis], tableau.zs[:, axis]) = ( - tableau.zs[:, axis].copy(), - tableau.xs[:, axis].copy(), - ) - elif effective_exponent == 1: - tableau.rs[:] ^= tableau.xs[:, axis] ^ tableau.zs[:, axis] - elif effective_exponent == 1.5: - tableau.rs[:] ^= ~(tableau.xs[:, axis]) & tableau.zs[:, axis] - (tableau.xs[:, axis], tableau.zs[:, axis]) = ( - tableau.zs[:, axis].copy(), - tableau.xs[:, axis].copy(), - ) - def _z(self, exponent: float, axis: int): effective_exponent = exponent % 2 state = self.state @@ -140,7 +108,6 @@ def _z(self, exponent: float, axis: int): def _h(self, exponent: float, axis: int): state = self.state - # Prescription for H left multiplication # Reference: https://arxiv.org/abs/1808.00128 # Equations 48, 49 and Proposition 4 @@ -168,7 +135,9 @@ def _cx(self, exponent: float, axis1: int, axis2: int): # Prescription for CX left multiplication. # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end state.gamma[axis1] = ( - state.gamma[axis1] + state.gamma[axis2] + 2 * (sum(state.M[axis1, :] & state.F[axis2, :]) % 2) + state.gamma[axis1] + + state.gamma[axis2] + + 2 * (sum(state.M[axis1, :] & state.F[axis2, :]) % 2) ) % 4 state.G[axis2, :] ^= state.G[axis1, :] state.F[axis1, :] ^= state.F[axis2, :] @@ -179,7 +148,6 @@ def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo paulis = protocols.as_ch(val, self.prng) if paulis is NotImplemented: return NotImplemented - print(paulis) paulis, phase = paulis for pauli, exponent, indexes in paulis: affected_qubits = [qubits[i] for i in indexes] @@ -197,10 +165,6 @@ def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo self.state.omega *= phase return True - def _strat_apply_to_ch_form2(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - gate = val.gate if isinstance(val, ops.Operation) else val - return protocols.apply_to_ch_form(gate, self.state, self.get_axes(qubits), self.prng) - def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( self, val: Any, qubits: Sequence['cirq.Qid'] ) -> bool: @@ -213,21 +177,16 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( # Gather the effective unitary applied so as to correct for the # global phase later. final_unitary = np.eye(2) - axes = self.get_axes(qubits) - state = self.state - rng = self.prng for axis, quarter_turns in clifford_gate.decompose_rotation(): gate = None # type: Optional[cirq.Gate] if axis == pauli_gates.X: gate = common_gates.XPowGate(exponent=quarter_turns / 2) - protocols.apply_to_ch_form(gate, state, axes, rng) elif axis == pauli_gates.Y: gate = common_gates.YPowGate(exponent=quarter_turns / 2) - protocols.apply_to_ch_form(gate, state, axes, rng) - else: - assert axis == pauli_gates.Z + elif axis == pauli_gates.Z: gate = common_gates.ZPowGate(exponent=quarter_turns / 2) - protocols.apply_to_ch_form(gate, state, axes, rng) + assert gate is not None + self._strat_apply_to_ch_form(gate, qubits) final_unitary = np.matmul(unitary(gate), final_unitary) From 9039a1e844ed5b1476310fe6969b83c2bf5b7e0d Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sat, 20 Nov 2021 20:29:36 -0800 Subject: [PATCH 21/41] have both simulators accept all common gates --- cirq-core/cirq/ops/common_channels.py | 4 +- cirq-core/cirq/ops/common_gates.py | 79 ++----------------- cirq-core/cirq/ops/identity.py | 3 - cirq-core/cirq/ops/swap_gates.py | 13 +-- .../cirq/protocols/clifford_protocols.py | 3 +- .../clifford/act_on_clifford_tableau_args.py | 9 ++- .../act_on_stabilizer_ch_form_args.py | 54 ++++++++++--- 7 files changed, 64 insertions(+), 101 deletions(-) diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index f3602206f18..38d34784bda 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -318,9 +318,9 @@ def __str__(self) -> str: def _as_paulis_(self, prng: np.random.RandomState): if prng.random() > self._p: - return [] + return [], 1 gate = prng.choice(['X', 'Y', 'Z']) - return [(gate, 1, [0])] + return [(gate, 1, [0])], 1 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: result: Tuple[str, ...] diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index 34887bf9372..da765feebe9 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -103,21 +103,14 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda return args.available_buffer def _as_paulis_(self, prng: np.random.RandomState): - if self.exponent % 2 == 0: - return [] - if self.exponent % 0.5 == 0: - return [('X', self.exponent % 2, [0])] - return NotImplemented - - def _as_ch_(self, prng: np.random.RandomState): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - return [ - ('H', 1, [0]), - ('Z', self._exponent, [0]), - ('H', 1, [0]), - ], phase + if self.exponent % 2 == 0: + return [], phase + if self.exponent % 0.5 == 0: + return [('X', self.exponent % 2, [0])], phase + return NotImplemented def in_su2(self) -> 'Rx': """Returns an equal-up-global-phase gate from the group SU2.""" @@ -344,38 +337,13 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda return args.available_buffer def _as_paulis_(self, prng: np.random.RandomState): - if self.exponent % 2 == 0: - return [] - if self.exponent % 0.5 == 0: - return [('Y', self.exponent % 2, [0])] - return NotImplemented - - def _as_ch_(self, prng: np.random.RandomState): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - effective_exponent = self._exponent % 2 - if effective_exponent == 0: + if self.exponent % 2 == 0: return [], phase - elif effective_exponent == 0.5: - phase *= (1 + 1j) / (2 ** 0.5) - return [ - ('Z', 1, [0]), - ('H', 1, [0]), - ], phase - elif effective_exponent == 1: - return [ - ('Z', 1, [0]), - ('H', 1, [0]), - ('Z', 1, [0]), - ('H', 1, [0]), - ], phase * 1j - elif effective_exponent == 1.5: - phase *= (1 - 1j) / (2 ** 0.5) - return [ - ('H', 1, [0]), - ('Z', 1, [0]), - ], phase + if self.exponent % 0.5 == 0: + return [('Y', self.exponent % 2, [0])], phase return NotImplemented def in_su2(self) -> 'Ry': @@ -553,13 +521,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda return args.target_tensor def _as_paulis_(self, prng: np.random.RandomState): - if self.exponent % 2 == 0: - return [] - if self.exponent % 0.5 == 0: - return [('Z', self.exponent % 2, [0])] - return NotImplemented - - def _as_ch_(self, prng: np.random.RandomState): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -832,16 +793,6 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: ) def _as_paulis_(self, prng: np.random.RandomState): - if self.exponent % 2 == 0: - return [] - if self.exponent % 2 == 1: - return [ - ('Y', 0.5, [0]), - ('X', 1, [0]), - ] - return NotImplemented - - def _as_ch_(self, prng: np.random.RandomState): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -995,13 +946,6 @@ def _apply_unitary_( return args.target_tensor def _as_paulis_(self, prng: np.random.RandomState): - if self.exponent % 2 == 0: - return [] - if self.exponent % 2 == 1: - return [('CZ', 1, [0, 1])] - return NotImplemented - - def _as_ch_(self, prng: np.random.RandomState): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -1194,13 +1138,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda return args.target_tensor def _as_paulis_(self, prng: np.random.RandomState): - if self.exponent % 2 == 0: - return [] - if self.exponent % 2 == 1: - return [('CX', 1, [0, 1])] - return NotImplemented - - def _as_ch_(self, prng: np.random.RandomState): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) diff --git a/cirq-core/cirq/ops/identity.py b/cirq-core/cirq/ops/identity.py index a1f252bdfab..fc02bf49ac3 100644 --- a/cirq-core/cirq/ops/identity.py +++ b/cirq-core/cirq/ops/identity.py @@ -66,9 +66,6 @@ def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): return True def _as_paulis_(self, prng: np.random.RandomState): - return [] - - def _as_ch_(self, prng: np.random.RandomState): return [], 1 def _qid_shape_(self) -> Tuple[int, ...]: diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index 4f8631200e6..d694d25db95 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -95,17 +95,8 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: return self.exponent % 1 == 0 def _as_paulis_(self, prng: np.random.RandomState): - if self.exponent % 2 == 0: - return [] - if self.exponent % 2 == 1: - return [ - ('CX', 1, [0, 1]), - ('CX', 1, [1, 0]), - ('CX', 1, [0, 1]), - ] - return NotImplemented - - def _as_ch_(self, prng: np.random.RandomState): + if not protocols.has_stabilizer_effect(self): + return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) if self.exponent % 2 == 0: return [], phase diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index 896677a139b..4ef9736f6c9 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -13,7 +13,6 @@ # limitations under the License. from typing import ( - Any, Sequence, Union, TYPE_CHECKING, @@ -50,7 +49,7 @@ def _as_paulis_( def as_paulis( gate: 'cirq.Gate', prng: np.random.RandomState -) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: +) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: """Applies a transform to the given Clifford CH-form. Args: diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index db2fe88d951..b24ce131de4 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -19,7 +19,6 @@ import numpy as np from cirq import protocols, ops -from cirq.ops import common_gates from cirq.ops import pauli_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary @@ -141,6 +140,11 @@ def _z(self, exponent: float, axis: int): tableau.rs[:] ^= tableau.xs[:, axis] & (~tableau.zs[:, axis]) tableau.zs[:, axis] ^= tableau.xs[:, axis] + def _h(self, exponent: float, axis: int): + assert exponent % 2 == 1 + self._y(0.5, axis) + self._x(1, axis) + def _cz(self, exponent: float, axis1: int, axis2: int): assert exponent % 2 == 1 tableau = self.tableau @@ -178,6 +182,7 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo paulis = protocols.as_paulis(val, self.prng) if paulis is NotImplemented: return NotImplemented + paulis, phase = paulis for pauli, exponent, indexes in paulis: affected_qubits = [qubits[i] for i in indexes] axes = self.get_axes(affected_qubits) @@ -187,6 +192,8 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo self._y(exponent, axes[0]) elif pauli == 'Z': self._z(exponent, axes[0]) + elif pauli == 'H': + self._h(exponent, axes[0]) elif pauli == 'CZ': self._cz(exponent, axes[0], axes[1]) elif pauli == 'CX': diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 73a548e13ad..3cbc996f9e4 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -97,7 +97,32 @@ def sample( protocols.act_on(op, ch_form_args) return np.array(list(measurements.values()), dtype=bool) - def _z(self, exponent: float, axis: int): + def _x(self, exponent: float, axis: int, phase: complex): + self._h(1, axis, 1) + self._z(exponent, axis, 1) + self._h(1, axis, 1) + self.state.omega *= phase + + def _y(self, exponent: float, axis: int, phase: complex): + if exponent == 0.5: + phase *= (1 + 1j) / (2 ** 0.5) + self._z(1, axis, 1) + self._h(1, axis, 1) + self.state.omega *= phase + elif exponent == 1: + phase *= 1j + self._z(1, axis, 1) + self._h(1, axis, 1) + self._z(1, axis, 1) + self._h(1, axis, 1) + self.state.omega *= phase + if exponent == 1.5: + phase *= (1 - 1j) / (2 ** 0.5) + self._h(1, axis, 1) + self._z(1, axis, 1) + self.state.omega *= phase + + def _z(self, exponent: float, axis: int, phase: complex): effective_exponent = exponent % 2 state = self.state for _ in range(int(effective_exponent * 2)): @@ -105,8 +130,9 @@ def _z(self, exponent: float, axis: int): # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end state.M[axis, :] ^= state.G[axis, :] state.gamma[axis] = (state.gamma[axis] - 1) % 4 + state.omega *= phase - def _h(self, exponent: float, axis: int): + def _h(self, exponent: float, axis: int, phase: complex): state = self.state # Prescription for H left multiplication # Reference: https://arxiv.org/abs/1808.00128 @@ -120,16 +146,18 @@ def _h(self, exponent: float, axis: int): beta %= 2 delta = (state.gamma[axis] + 2 * (alpha + beta)) % 4 state.update_sum(t, u, delta=delta, alpha=alpha) + state.omega *= phase - def _cz(self, exponent: float, axis1: int, axis2: int): + def _cz(self, exponent: float, axis1: int, axis2: int, phase: complex): assert exponent % 2 == 1 state = self.state # Prescription for CZ left multiplication. # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end state.M[axis1, :] ^= state.G[axis2, :] state.M[axis2, :] ^= state.G[axis1, :] + state.omega *= phase - def _cx(self, exponent: float, axis1: int, axis2: int): + def _cx(self, exponent: float, axis1: int, axis2: int, phase: complex): assert exponent % 2 == 1 state = self.state # Prescription for CX left multiplication. @@ -142,27 +170,31 @@ def _cx(self, exponent: float, axis1: int, axis2: int): state.G[axis2, :] ^= state.G[axis1, :] state.F[axis1, :] ^= state.F[axis2, :] state.M[axis1, :] ^= state.M[axis2, :] + state.omega *= phase def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: val = val.gate if isinstance(val, ops.Operation) else val - paulis = protocols.as_ch(val, self.prng) + paulis = protocols.as_paulis(val, self.prng) if paulis is NotImplemented: return NotImplemented paulis, phase = paulis for pauli, exponent, indexes in paulis: affected_qubits = [qubits[i] for i in indexes] axes = self.get_axes(affected_qubits) - if pauli == 'Z': - self._z(exponent, axes[0]) + if pauli == 'X': + self._x(exponent, axes[0], phase) + elif pauli == 'Y': + self._y(exponent, axes[0], phase) + elif pauli == 'Z': + self._z(exponent, axes[0], phase) elif pauli == 'H': - self._h(exponent, axes[0]) + self._h(exponent, axes[0], phase) elif pauli == 'CZ': - self._cz(exponent, axes[0], axes[1]) + self._cz(exponent, axes[0], axes[1], phase) elif pauli == 'CX': - self._cx(exponent, axes[0], axes[1]) + self._cx(exponent, axes[0], axes[1], phase) else: assert False - self.state.omega *= phase return True def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( From 2a800cb4eb8679ee0485c694baa440713515ed50 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sat, 20 Nov 2021 20:52:32 -0800 Subject: [PATCH 22/41] remove prng in most cases --- cirq-core/cirq/__init__.py | 3 +- cirq-core/cirq/ops/common_channels.py | 2 +- cirq-core/cirq/ops/common_gates.py | 12 ++--- cirq-core/cirq/ops/identity.py | 2 +- cirq-core/cirq/ops/swap_gates.py | 2 +- cirq-core/cirq/protocols/__init__.py | 3 +- .../cirq/protocols/clifford_protocols.py | 47 +++++-------------- .../cirq/protocols/json_test_data/spec.py | 1 + 8 files changed, 25 insertions(+), 47 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index e2e3e3c3604..cd084eeceb3 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -499,7 +499,6 @@ ApplyMixtureArgs, ApplyUnitaryArgs, approx_eq, - as_ch, as_paulis, circuit_diagram_info, CircuitDiagramInfo, @@ -550,8 +549,8 @@ SupportsApplyChannel, SupportsApplyMixture, SupportsApproximateEquality, - SupportsAsCH, SupportsAsPaulis, + SupportsAsPaulisWithNondeterminism, SupportsConsistentApplyUnitary, SupportsCircuitDiagramInfo, SupportsCommutes, diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index 38d34784bda..e1c35b38ad1 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -316,7 +316,7 @@ def __str__(self) -> str: return f"depolarize(p={self._p})" return f"depolarize(p={self._p},n_qubits={self._n_qubits})" - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_with_nondeterminism_(self, prng: np.random.RandomState): if prng.random() > self._p: return [], 1 gate = prng.choice(['X', 'Y', 'Z']) diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index da765feebe9..c53b1906503 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -102,7 +102,7 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -336,7 +336,7 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -520,7 +520,7 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -792,7 +792,7 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: } ) - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -945,7 +945,7 @@ def _apply_unitary_( args.target_tensor *= p return args.target_tensor - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) @@ -1137,7 +1137,7 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) diff --git a/cirq-core/cirq/ops/identity.py b/cirq-core/cirq/ops/identity.py index fc02bf49ac3..ddc50ea0cd9 100644 --- a/cirq-core/cirq/ops/identity.py +++ b/cirq-core/cirq/ops/identity.py @@ -65,7 +65,7 @@ def __init__( def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): return True - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): return [], 1 def _qid_shape_(self) -> Tuple[int, ...]: diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index d694d25db95..24acf2925d8 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -94,7 +94,7 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: return None return self.exponent % 1 == 0 - def _as_paulis_(self, prng: np.random.RandomState): + def _as_paulis_(self): if not protocols.has_stabilizer_effect(self): return NotImplemented phase = np.exp(1j * np.pi * self.global_shift * self.exponent) diff --git a/cirq-core/cirq/protocols/__init__.py b/cirq-core/cirq/protocols/__init__.py index 16f8f1241f3..7fa948a86c8 100644 --- a/cirq-core/cirq/protocols/__init__.py +++ b/cirq-core/cirq/protocols/__init__.py @@ -45,10 +45,9 @@ SupportsKraus, ) from cirq.protocols.clifford_protocols import ( - as_ch, as_paulis, - SupportsAsCH, SupportsAsPaulis, + SupportsAsPaulisWithNondeterminism, ) from cirq.protocols.commutes_protocol import ( commutes, diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py index 4ef9736f6c9..2d577a13562 100644 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ b/cirq-core/cirq/protocols/clifford_protocols.py @@ -17,6 +17,7 @@ Union, TYPE_CHECKING, Tuple, + Optional, ) import numpy as np @@ -31,14 +32,9 @@ class SupportsAsPaulis(Protocol): @doc_private - def _as_paulis_( - self, prng: np.random.RandomState - ) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: + def _as_paulis_(self) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: """Transforms the gate to paulis. - Args: - prng: The random number generator to use if necessary. - Returns: True: The receiving object (`self`) could apply a transform. NotImplemented: The receiving object cannot apply a transform. @@ -47,33 +43,12 @@ def _as_paulis_( """ -def as_paulis( - gate: 'cirq.Gate', prng: np.random.RandomState -) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: - """Applies a transform to the given Clifford CH-form. - - Args: - gate: The object (typically a gate) that contains a transform to apply. - state: A Clifford CH-form that is the target of the transform. - axes: The axes to which the transform should be applied. - prng: A random number generator to use if necessary. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - getter = getattr(gate, '_as_paulis_', None) - return NotImplemented if getter is None else getter(prng) - - -class SupportsAsCH(Protocol): +class SupportsAsPaulisWithNondeterminism(Protocol): @doc_private - def _as_ch_( + def _as_paulis_with_nondeterminism_( self, prng: np.random.RandomState - ) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: - """Transforms the gate to ch. + ) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: + """Transforms the gate to paulis. Args: prng: The random number generator to use if necessary. @@ -86,8 +61,8 @@ def _as_ch_( """ -def as_ch( - gate: 'cirq.Gate', prng: np.random.RandomState +def as_paulis( + gate: 'cirq.Gate', prng: Optional[np.random.RandomState] ) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: """Applies a transform to the given Clifford CH-form. @@ -103,5 +78,9 @@ def as_ch( All other return values are considered to be errors. """ - getter = getattr(gate, '_as_ch_', None) + getter = getattr(gate, '_as_paulis_', None) + return_val = NotImplemented if getter is None else getter() + if return_val is not NotImplemented: + return return_val + getter = getattr(gate, '_as_paulis_with_nondeterminism_', None) return NotImplemented if getter is None else getter(prng) diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index 1c667fcf952..66a7f395d66 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -137,6 +137,7 @@ 'SupportsApproximateEquality', 'SupportsAsCH', 'SupportsAsPaulis', + 'SupportsAsPaulisWithNondeterminism', 'SupportsCircuitDiagramInfo', 'SupportsCommutes', 'SupportsConsistentApplyUnitary', From 82d13d1f7bebde0ca6b8f025cad26d4447ba4b09 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sat, 20 Nov 2021 21:04:29 -0800 Subject: [PATCH 23/41] fix swap phase --- cirq-core/cirq/ops/swap_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index 24acf2925d8..132c733e16a 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -105,7 +105,7 @@ def _as_paulis_(self): ('CX', 1, [0, 1]), ('CX', 1, [1, 0]), ('CX', 1, [0, 1]), - ], phase + ], phase ** (1 / 3) return NotImplemented def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.ndarray]: From 5d54724b5f1bc86a947a663c7a6fc25e8b6738a7 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 17:27:28 -0800 Subject: [PATCH 24/41] decompose --- .../clifford/act_on_clifford_tableau_args.py | 99 ++++---- .../act_on_stabilizer_ch_form_args.py | 219 ++++++++++-------- 2 files changed, 178 insertions(+), 140 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index b24ce131de4..a99e2ff4c58 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -19,7 +19,7 @@ import numpy as np from cirq import protocols, ops -from cirq.ops import pauli_gates +from cirq.ops import common_gates, matrix_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary from cirq.qis.clifford_tableau import CliffordTableau @@ -66,7 +66,7 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [self._strat_apply_to_tableau] + strats = [self._strat_apply_to_tableau, self._strat_apply_mixture_to_tableau] if allow_decompose: strats.append(self._strat_act_on_clifford_tableau_from_single_qubit_decompose) for strat in strats: @@ -95,7 +95,10 @@ def sample( # Unnecessary for now but can be added later if there is a use case. raise NotImplementedError() - def _x(self, exponent: float, axis: int): + def _x(self, g: common_gates.XPowGate, axis: int): + exponent = g.exponent + if exponent % 2 == 0: + return assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 @@ -108,7 +111,10 @@ def _x(self, exponent: float, axis: int): tableau.rs[:] ^= tableau.xs[:, axis] & tableau.zs[:, axis] tableau.xs[:, axis] ^= tableau.zs[:, axis] - def _y(self, exponent: float, axis: int): + def _y(self, g: common_gates.YPowGate, axis: int): + exponent = g.exponent + if exponent % 2 == 0: + return assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 @@ -127,7 +133,10 @@ def _y(self, exponent: float, axis: int): tableau.xs[:, axis].copy(), ) - def _z(self, exponent: float, axis: int): + def _z(self, g: common_gates.ZPowGate, axis: int): + exponent = g.exponent + if exponent % 2 == 0: + return assert exponent % 0.5 == 0.0 tableau = self.tableau effective_exponent = exponent % 2 @@ -140,12 +149,18 @@ def _z(self, exponent: float, axis: int): tableau.rs[:] ^= tableau.xs[:, axis] & (~tableau.zs[:, axis]) tableau.zs[:, axis] ^= tableau.xs[:, axis] - def _h(self, exponent: float, axis: int): + def _h(self, g: common_gates.HPowGate, axis: int): + exponent = g.exponent + if exponent % 2 == 0: + return assert exponent % 2 == 1 - self._y(0.5, axis) - self._x(1, axis) + self._y(common_gates.YPowGate(exponent=0.5), axis) + self._x(common_gates.XPowGate(), axis) - def _cz(self, exponent: float, axis1: int, axis2: int): + def _cz(self, g: common_gates.CZPowGate, axis1: int, axis2: int): + exponent = g.exponent + if exponent % 2 == 0: + return assert exponent % 2 == 1 tableau = self.tableau (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( @@ -166,7 +181,10 @@ def _cz(self, exponent: float, axis1: int, axis2: int): ) tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] - def _cx(self, exponent: float, axis1: int, axis2: int): + def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): + exponent = g.exponent + if exponent % 2 == 0: + return assert exponent % 2 == 1 tableau = self.tableau tableau.rs[:] ^= ( @@ -178,30 +196,40 @@ def _cx(self, exponent: float, axis1: int, axis2: int): tableau.zs[:, axis1] ^= tableau.zs[:, axis2] def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - val = val.gate if isinstance(val, ops.Operation) else val - paulis = protocols.as_paulis(val, self.prng) - if paulis is NotImplemented: + if not protocols.has_stabilizer_effect(val): + return NotImplemented + gate = val.gate if isinstance(val, ops.Operation) else val + axes = self.get_axes(qubits) + if isinstance(gate, common_gates.XPowGate): + self._x(gate, axes[0]) + elif isinstance(gate, common_gates.YPowGate): + self._y(gate, axes[0]) + elif isinstance(gate, common_gates.ZPowGate): + self._z(gate, axes[0]) + elif isinstance(gate, common_gates.HPowGate): + self._h(gate, axes[0]) + elif isinstance(gate, common_gates.CXPowGate): + self._cx(gate, axes[0], axes[1]) + elif isinstance(gate, common_gates.CZPowGate): + self._cz(gate, axes[0], axes[1]) + else: return NotImplemented - paulis, phase = paulis - for pauli, exponent, indexes in paulis: - affected_qubits = [qubits[i] for i in indexes] - axes = self.get_axes(affected_qubits) - if pauli == 'X': - self._x(exponent, axes[0]) - elif pauli == 'Y': - self._y(exponent, axes[0]) - elif pauli == 'Z': - self._z(exponent, axes[0]) - elif pauli == 'H': - self._h(exponent, axes[0]) - elif pauli == 'CZ': - self._cz(exponent, axes[0], axes[1]) - elif pauli == 'CX': - self._cx(exponent, axes[0], axes[1]) - else: - assert False return True + def _strat_apply_mixture_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: + mixture = protocols.mixture(val, None) + if mixture is None: + return False + rand = self.prng.random() + psum = 0.0 + for p, mix in mixture: + psum += p + if psum >= rand: + return self._strat_act_on_clifford_tableau_from_single_qubit_decompose( + matrix_gates.MatrixGate(mix), qubits + ) + raise AssertionError("Probablities don't add to 1") + def _strat_act_on_clifford_tableau_from_single_qubit_decompose( self, val: Any, qubits: Sequence['cirq.Qid'] ) -> bool: @@ -211,15 +239,8 @@ def _strat_act_on_clifford_tableau_from_single_qubit_decompose( u = unitary(val) clifford_gate = SingleQubitCliffordGate.from_unitary(u) if clifford_gate is not None: - axis = self.qubit_map[qubits[0]] for gate, quarter_turns in clifford_gate.decompose_rotation(): - if gate == pauli_gates.X: - self._x(quarter_turns / 2, axis) - elif gate == pauli_gates.Y: - self._y(quarter_turns / 2, axis) - else: - assert gate == pauli_gates.Z - self._z(quarter_turns / 2, axis) + self._strat_apply_to_tableau(gate ** (quarter_turns / 2), qubits) return True return NotImplemented diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 3cbc996f9e4..078dc66548e 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -17,7 +17,7 @@ import numpy as np from cirq import value, ops, protocols -from cirq.ops import common_gates, pauli_gates +from cirq.ops import common_gates, pauli_gates, matrix_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary from cirq.sim.act_on_args import ActOnArgs @@ -64,7 +64,7 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [self._strat_apply_to_ch_form] + strats = [self._strat_apply_to_ch_form, self._strat_apply_mixture_to_ch_form] if allow_decompose: strats.append(self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) for strat in strats: @@ -97,106 +97,126 @@ def sample( protocols.act_on(op, ch_form_args) return np.array(list(measurements.values()), dtype=bool) - def _x(self, exponent: float, axis: int, phase: complex): - self._h(1, axis, 1) - self._z(exponent, axis, 1) - self._h(1, axis, 1) - self.state.omega *= phase - - def _y(self, exponent: float, axis: int, phase: complex): - if exponent == 0.5: - phase *= (1 + 1j) / (2 ** 0.5) - self._z(1, axis, 1) - self._h(1, axis, 1) - self.state.omega *= phase - elif exponent == 1: - phase *= 1j - self._z(1, axis, 1) - self._h(1, axis, 1) - self._z(1, axis, 1) - self._h(1, axis, 1) - self.state.omega *= phase - if exponent == 1.5: - phase *= (1 - 1j) / (2 ** 0.5) - self._h(1, axis, 1) - self._z(1, axis, 1) - self.state.omega *= phase - - def _z(self, exponent: float, axis: int, phase: complex): - effective_exponent = exponent % 2 + def _x(self, g: common_gates.XPowGate, axis: int): + exponent = g.exponent + if exponent % 2 != 0: + self._h(common_gates.H, axis) + self._z(common_gates.ZPowGate(exponent=exponent), axis) + self._h(common_gates.H, axis) + self.state.omega *= _phase(g) + + def _y(self, g: common_gates.YPowGate, axis: int): + exponent = g.exponent + if exponent % 2 == 0: + self.state.omega *= _phase(g) + elif exponent % 2 == 0.5: + self._z(pauli_gates.Z, axis) + self._h(common_gates.H, axis) + self.state.omega *= _phase(g) * (1 + 1j) / (2 ** 0.5) + elif exponent % 2 == 1: + self._z(pauli_gates.Z, axis) + self._h(common_gates.H, axis) + self._z(pauli_gates.Z, axis) + self._h(common_gates.H, axis) + self.state.omega *= _phase(g) * 1j + elif exponent % 2 == 1.5: + self._h(common_gates.H, axis) + self._z(pauli_gates.Z, axis) + self.state.omega *= _phase(g) * (1 - 1j) / (2 ** 0.5) + + def _z(self, g: common_gates.ZPowGate, axis: int): + exponent = g.exponent state = self.state - for _ in range(int(effective_exponent * 2)): - # Prescription for S left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.M[axis, :] ^= state.G[axis, :] - state.gamma[axis] = (state.gamma[axis] - 1) % 4 - state.omega *= phase - - def _h(self, exponent: float, axis: int, phase: complex): + if exponent % 2 != 0: + effective_exponent = exponent % 2 + for _ in range(int(effective_exponent * 2)): + # Prescription for S left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.M[axis, :] ^= state.G[axis, :] + state.gamma[axis] = (state.gamma[axis] - 1) % 4 + state.omega *= _phase(g) + + def _h(self, g: common_gates.HPowGate, axis: int): + exponent = g.exponent state = self.state - # Prescription for H left multiplication - # Reference: https://arxiv.org/abs/1808.00128 - # Equations 48, 49 and Proposition 4 - t = state.s ^ (state.G[axis, :] & state.v) - u = state.s ^ (state.F[axis, :] & (~state.v)) ^ (state.M[axis, :] & state.v) - alpha = sum(state.G[axis, :] & (~state.v) & state.s) % 2 - beta = sum(state.M[axis, :] & (~state.v) & state.s) - beta += sum(state.F[axis, :] & state.v & state.M[axis, :]) - beta += sum(state.F[axis, :] & state.v & state.s) - beta %= 2 - delta = (state.gamma[axis] + 2 * (alpha + beta)) % 4 - state.update_sum(t, u, delta=delta, alpha=alpha) - state.omega *= phase - - def _cz(self, exponent: float, axis1: int, axis2: int, phase: complex): - assert exponent % 2 == 1 + if exponent % 2 != 0: + # Prescription for H left multiplication + # Reference: https://arxiv.org/abs/1808.00128 + # Equations 48, 49 and Proposition 4 + t = state.s ^ (state.G[axis, :] & state.v) + u = state.s ^ (state.F[axis, :] & (~state.v)) ^ (state.M[axis, :] & state.v) + alpha = sum(state.G[axis, :] & (~state.v) & state.s) % 2 + beta = sum(state.M[axis, :] & (~state.v) & state.s) + beta += sum(state.F[axis, :] & state.v & state.M[axis, :]) + beta += sum(state.F[axis, :] & state.v & state.s) + beta %= 2 + delta = (state.gamma[axis] + 2 * (alpha + beta)) % 4 + state.update_sum(t, u, delta=delta, alpha=alpha) + state.omega *= _phase(g) + + def _cz(self, g: common_gates.CZPowGate, axis1: int, axis2: int): + exponent = g.exponent state = self.state - # Prescription for CZ left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.M[axis1, :] ^= state.G[axis2, :] - state.M[axis2, :] ^= state.G[axis1, :] - state.omega *= phase - - def _cx(self, exponent: float, axis1: int, axis2: int, phase: complex): - assert exponent % 2 == 1 + if exponent % 2 != 0: + assert exponent % 2 == 1 + # Prescription for CZ left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.M[axis1, :] ^= state.G[axis2, :] + state.M[axis2, :] ^= state.G[axis1, :] + state.omega *= _phase(g) + + def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): + exponent = g.exponent state = self.state - # Prescription for CX left multiplication. - # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.gamma[axis1] = ( - state.gamma[axis1] - + state.gamma[axis2] - + 2 * (sum(state.M[axis1, :] & state.F[axis2, :]) % 2) - ) % 4 - state.G[axis2, :] ^= state.G[axis1, :] - state.F[axis1, :] ^= state.F[axis2, :] - state.M[axis1, :] ^= state.M[axis2, :] - state.omega *= phase + if exponent % 2 != 0: + assert exponent % 2 == 1 + # Prescription for CX left multiplication. + # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end + state.gamma[axis1] = ( + state.gamma[axis1] + + state.gamma[axis2] + + 2 * (sum(state.M[axis1, :] & state.F[axis2, :]) % 2) + ) % 4 + state.G[axis2, :] ^= state.G[axis1, :] + state.F[axis1, :] ^= state.F[axis2, :] + state.M[axis1, :] ^= state.M[axis2, :] + state.omega *= _phase(g) def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - val = val.gate if isinstance(val, ops.Operation) else val - paulis = protocols.as_paulis(val, self.prng) - if paulis is NotImplemented: + if not protocols.has_stabilizer_effect(val): + return NotImplemented + gate = val.gate if isinstance(val, ops.Operation) else val + axes = self.get_axes(qubits) + if isinstance(gate, common_gates.XPowGate): + self._x(gate, axes[0]) + elif isinstance(gate, common_gates.YPowGate): + self._y(gate, axes[0]) + elif isinstance(gate, common_gates.ZPowGate): + self._z(gate, axes[0]) + elif isinstance(gate, common_gates.HPowGate): + self._h(gate, axes[0]) + elif isinstance(gate, common_gates.CXPowGate): + self._cx(gate, axes[0], axes[1]) + elif isinstance(gate, common_gates.CZPowGate): + self._cz(gate, axes[0], axes[1]) + else: return NotImplemented - paulis, phase = paulis - for pauli, exponent, indexes in paulis: - affected_qubits = [qubits[i] for i in indexes] - axes = self.get_axes(affected_qubits) - if pauli == 'X': - self._x(exponent, axes[0], phase) - elif pauli == 'Y': - self._y(exponent, axes[0], phase) - elif pauli == 'Z': - self._z(exponent, axes[0], phase) - elif pauli == 'H': - self._h(exponent, axes[0], phase) - elif pauli == 'CZ': - self._cz(exponent, axes[0], axes[1], phase) - elif pauli == 'CX': - self._cx(exponent, axes[0], axes[1], phase) - else: - assert False return True + def _strat_apply_mixture_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: + mixture = protocols.mixture(val, None) + if mixture is None: + return NotImplemented + rand = self.prng.random() + psum = 0.0 + for p, mix in mixture: + psum += p + if psum >= rand: + return self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( + matrix_gates.MatrixGate(mix), qubits + ) + raise AssertionError("Probablities don't add to 1") + def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( self, val: Any, qubits: Sequence['cirq.Qid'] ) -> bool: @@ -210,14 +230,7 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( # global phase later. final_unitary = np.eye(2) for axis, quarter_turns in clifford_gate.decompose_rotation(): - gate = None # type: Optional[cirq.Gate] - if axis == pauli_gates.X: - gate = common_gates.XPowGate(exponent=quarter_turns / 2) - elif axis == pauli_gates.Y: - gate = common_gates.YPowGate(exponent=quarter_turns / 2) - elif axis == pauli_gates.Z: - gate = common_gates.ZPowGate(exponent=quarter_turns / 2) - assert gate is not None + gate = axis ** (quarter_turns / 2) self._strat_apply_to_ch_form(gate, qubits) final_unitary = np.matmul(unitary(gate), final_unitary) @@ -230,3 +243,7 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( return True return NotImplemented + + +def _phase(gate): + return np.exp(1j * np.pi * gate.global_shift * gate.exponent) \ No newline at end of file From 10ea2357531c34b915f7082ed0a91fa8cdd62847 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 17:31:46 -0800 Subject: [PATCH 25/41] dead code --- cirq-core/cirq/ops/common_channels.py | 6 -- cirq-core/cirq/ops/common_gates.py | 60 ------------- cirq-core/cirq/ops/identity.py | 3 - cirq-core/cirq/ops/swap_gates.py | 16 +--- .../cirq/protocols/clifford_protocols.py | 86 ------------------- .../cirq/protocols/clifford_protocols_test.py | 22 ----- 6 files changed, 1 insertion(+), 192 deletions(-) delete mode 100644 cirq-core/cirq/protocols/clifford_protocols.py delete mode 100644 cirq-core/cirq/protocols/clifford_protocols_test.py diff --git a/cirq-core/cirq/ops/common_channels.py b/cirq-core/cirq/ops/common_channels.py index e1c35b38ad1..34ec17cdbe8 100644 --- a/cirq-core/cirq/ops/common_channels.py +++ b/cirq-core/cirq/ops/common_channels.py @@ -316,12 +316,6 @@ def __str__(self) -> str: return f"depolarize(p={self._p})" return f"depolarize(p={self._p},n_qubits={self._n_qubits})" - def _as_paulis_with_nondeterminism_(self, prng: np.random.RandomState): - if prng.random() > self._p: - return [], 1 - gate = prng.choice(['X', 'Y', 'Z']) - return [(gate, 1, [0])], 1 - def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: result: Tuple[str, ...] if args.precision is not None: diff --git a/cirq-core/cirq/ops/common_gates.py b/cirq-core/cirq/ops/common_gates.py index c53b1906503..dcb604b891b 100644 --- a/cirq-core/cirq/ops/common_gates.py +++ b/cirq-core/cirq/ops/common_gates.py @@ -102,16 +102,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _as_paulis_(self): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 0.5 == 0: - return [('X', self.exponent % 2, [0])], phase - return NotImplemented - def in_su2(self) -> 'Rx': """Returns an equal-up-global-phase gate from the group SU2.""" return Rx(rads=self._exponent * _pi(self._exponent)) @@ -336,16 +326,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.available_buffer *= p return args.available_buffer - def _as_paulis_(self): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 0.5 == 0: - return [('Y', self.exponent % 2, [0])], phase - return NotImplemented - def in_su2(self) -> 'Ry': """Returns an equal-up-global-phase gate from the group SU2.""" return Ry(rads=self._exponent * _pi(self._exponent)) @@ -520,16 +500,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _as_paulis_(self): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 0.5 == 0: - return [('Z', self.exponent % 2, [0])], phase - return NotImplemented - def _decompose_into_clifford_with_qubits_(self, qubits): from cirq.ops.clifford_gate import SingleQubitCliffordGate @@ -792,16 +762,6 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: } ) - def _as_paulis_(self): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 2 == 1: - return [('H', 1, [0])], phase - return NotImplemented - def _decompose_into_clifford_with_qubits_(self, qubits): from cirq.ops.clifford_gate import SingleQubitCliffordGate @@ -945,16 +905,6 @@ def _apply_unitary_( args.target_tensor *= p return args.target_tensor - def _as_paulis_(self): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 2 == 1: - return [('CZ', 1, [0, 1])], phase - return NotImplemented - def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): return NotImplemented @@ -1137,16 +1087,6 @@ def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.nda args.target_tensor *= p return args.target_tensor - def _as_paulis_(self): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 2 == 1: - return [('CX', 1, [0, 1])], phase - return NotImplemented - def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): return NotImplemented diff --git a/cirq-core/cirq/ops/identity.py b/cirq-core/cirq/ops/identity.py index ddc50ea0cd9..122185db378 100644 --- a/cirq-core/cirq/ops/identity.py +++ b/cirq-core/cirq/ops/identity.py @@ -65,9 +65,6 @@ def __init__( def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): return True - def _as_paulis_(self): - return [], 1 - def _qid_shape_(self) -> Tuple[int, ...]: return self._qid_shape diff --git a/cirq-core/cirq/ops/swap_gates.py b/cirq-core/cirq/ops/swap_gates.py index 132c733e16a..d0b556a23ca 100644 --- a/cirq-core/cirq/ops/swap_gates.py +++ b/cirq-core/cirq/ops/swap_gates.py @@ -24,7 +24,7 @@ EigenGate. """ -from typing import Optional, Tuple, TYPE_CHECKING, List, Sequence +from typing import Optional, Tuple, TYPE_CHECKING, List import numpy as np import sympy @@ -94,20 +94,6 @@ def _has_stabilizer_effect_(self) -> Optional[bool]: return None return self.exponent % 1 == 0 - def _as_paulis_(self): - if not protocols.has_stabilizer_effect(self): - return NotImplemented - phase = np.exp(1j * np.pi * self.global_shift * self.exponent) - if self.exponent % 2 == 0: - return [], phase - if self.exponent % 2 == 1: - return [ - ('CX', 1, [0, 1]), - ('CX', 1, [1, 0]), - ('CX', 1, [0, 1]), - ], phase ** (1 / 3) - return NotImplemented - def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> Optional[np.ndarray]: if self._exponent != 1: return NotImplemented diff --git a/cirq-core/cirq/protocols/clifford_protocols.py b/cirq-core/cirq/protocols/clifford_protocols.py deleted file mode 100644 index 2d577a13562..00000000000 --- a/cirq-core/cirq/protocols/clifford_protocols.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2021 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import ( - Sequence, - Union, - TYPE_CHECKING, - Tuple, - Optional, -) - -import numpy as np -from typing_extensions import Protocol - -from cirq._doc import doc_private -from cirq.type_workarounds import NotImplementedType - -if TYPE_CHECKING: - import cirq - - -class SupportsAsPaulis(Protocol): - @doc_private - def _as_paulis_(self) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: - """Transforms the gate to paulis. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - - -class SupportsAsPaulisWithNondeterminism(Protocol): - @doc_private - def _as_paulis_with_nondeterminism_( - self, prng: np.random.RandomState - ) -> Union[Sequence[Tuple[str, float, Sequence[int]]], NotImplementedType]: - """Transforms the gate to paulis. - - Args: - prng: The random number generator to use if necessary. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - - -def as_paulis( - gate: 'cirq.Gate', prng: Optional[np.random.RandomState] -) -> Union[Tuple[Sequence[Tuple[str, float, Sequence[int]]], complex], NotImplementedType]: - """Applies a transform to the given Clifford CH-form. - - Args: - gate: The object (typically a gate) that contains a transform to apply. - state: A Clifford CH-form that is the target of the transform. - axes: The axes to which the transform should be applied. - prng: A random number generator to use if necessary. - - Returns: - True: The receiving object (`self`) could apply a transform. - NotImplemented: The receiving object cannot apply a transform. - - All other return values are considered to be errors. - """ - getter = getattr(gate, '_as_paulis_', None) - return_val = NotImplemented if getter is None else getter() - if return_val is not NotImplemented: - return return_val - getter = getattr(gate, '_as_paulis_with_nondeterminism_', None) - return NotImplemented if getter is None else getter(prng) diff --git a/cirq-core/cirq/protocols/clifford_protocols_test.py b/cirq-core/cirq/protocols/clifford_protocols_test.py deleted file mode 100644 index 051db264ec0..00000000000 --- a/cirq-core/cirq/protocols/clifford_protocols_test.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2021 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np - -import cirq - - -class CountingGate(cirq.SingleQubitGate): - def __init__(self, implemented: bool = True): - self._implemented = implemented From 077c019627f2e44b890d47ac98989ab62902b622 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 17:34:13 -0800 Subject: [PATCH 26/41] dead code --- cirq-core/cirq/__init__.py | 3 --- cirq-core/cirq/protocols/__init__.py | 5 ----- cirq-core/cirq/protocols/json_test_data/spec.py | 3 --- 3 files changed, 11 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index cd084eeceb3..dd66f312590 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -499,7 +499,6 @@ ApplyMixtureArgs, ApplyUnitaryArgs, approx_eq, - as_paulis, circuit_diagram_info, CircuitDiagramInfo, CircuitDiagramInfoArgs, @@ -549,8 +548,6 @@ SupportsApplyChannel, SupportsApplyMixture, SupportsApproximateEquality, - SupportsAsPaulis, - SupportsAsPaulisWithNondeterminism, SupportsConsistentApplyUnitary, SupportsCircuitDiagramInfo, SupportsCommutes, diff --git a/cirq-core/cirq/protocols/__init__.py b/cirq-core/cirq/protocols/__init__.py index 7fa948a86c8..8cd88ad897e 100644 --- a/cirq-core/cirq/protocols/__init__.py +++ b/cirq-core/cirq/protocols/__init__.py @@ -44,11 +44,6 @@ has_kraus, SupportsKraus, ) -from cirq.protocols.clifford_protocols import ( - as_paulis, - SupportsAsPaulis, - SupportsAsPaulisWithNondeterminism, -) from cirq.protocols.commutes_protocol import ( commutes, definitely_commutes, diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index 66a7f395d66..09c3780f3bb 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -135,9 +135,6 @@ 'SupportsApplyChannel', 'SupportsApplyMixture', 'SupportsApproximateEquality', - 'SupportsAsCH', - 'SupportsAsPaulis', - 'SupportsAsPaulisWithNondeterminism', 'SupportsCircuitDiagramInfo', 'SupportsCommutes', 'SupportsConsistentApplyUnitary', From c82184ec75c4201f5e722d48ac1355c2b86824b5 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 20:13:32 -0800 Subject: [PATCH 27/41] mostly working --- .../clifford/act_on_clifford_tableau_args.py | 18 ++++++++++++++++-- .../clifford/act_on_stabilizer_ch_form_args.py | 16 +++++++++++++++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index a99e2ff4c58..cdaaea64e68 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -18,7 +18,7 @@ import numpy as np -from cirq import protocols, ops +from cirq import protocols, ops, linalg from cirq.ops import common_gates, matrix_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary @@ -69,6 +69,7 @@ def _act_on_fallback_( strats = [self._strat_apply_to_tableau, self._strat_apply_mixture_to_tableau] if allow_decompose: strats.append(self._strat_act_on_clifford_tableau_from_single_qubit_decompose) + strats.append(self._strat_decompose) for strat in strats: result = strat(action, qubits) if result is False: @@ -219,7 +220,9 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo def _strat_apply_mixture_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: mixture = protocols.mixture(val, None) if mixture is None: - return False + return NotImplemented + if not all(linalg.is_unitary(m) for _, m in mixture): + return NotImplemented rand = self.prng.random() psum = 0.0 for p, mix in mixture: @@ -244,3 +247,14 @@ def _strat_act_on_clifford_tableau_from_single_qubit_decompose( return True return NotImplemented + + def _strat_decompose( + self, val: Any, qubits: Sequence['cirq.Qid'] + ) -> bool: + gate = val.gate if isinstance(val, ops.Operation) else val + operations = protocols.decompose_once_with_qubits(gate, qubits, None) + if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): + return NotImplemented + for op in operations: + assert self._act_on_fallback_(op, op.qubits) + return True diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 078dc66548e..31e18b2c0b1 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -16,7 +16,7 @@ import numpy as np -from cirq import value, ops, protocols +from cirq import value, ops, protocols, linalg from cirq.ops import common_gates, pauli_gates, matrix_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary @@ -67,6 +67,7 @@ def _act_on_fallback_( strats = [self._strat_apply_to_ch_form, self._strat_apply_mixture_to_ch_form] if allow_decompose: strats.append(self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) + strats.append(self._strat_decompose) for strat in strats: result = strat(action, qubits) if result is True: @@ -207,6 +208,8 @@ def _strat_apply_mixture_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid'] mixture = protocols.mixture(val, None) if mixture is None: return NotImplemented + if not all(linalg.is_unitary(m) for _, m in mixture): + return NotImplemented rand = self.prng.random() psum = 0.0 for p, mix in mixture: @@ -244,6 +247,17 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( return NotImplemented + def _strat_decompose( + self, val: Any, qubits: Sequence['cirq.Qid'] + ) -> bool: + gate = val.gate if isinstance(val, ops.Operation) else val + operations = protocols.decompose_once_with_qubits(gate, qubits, None) + if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): + return NotImplemented + for op in operations: + assert self._act_on_fallback_(op, op.qubits) + return True + def _phase(gate): return np.exp(1j * np.pi * gate.global_shift * gate.exponent) \ No newline at end of file From 2e2bbc82710fcaa33ce6feaad1144bb50013d5b4 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 20:45:50 -0800 Subject: [PATCH 28/41] almost --- cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py | 3 +-- cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index cdaaea64e68..3846b8315ce 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -66,10 +66,9 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [self._strat_apply_to_tableau, self._strat_apply_mixture_to_tableau] + strats = [self._strat_apply_to_tableau, self._strat_apply_mixture_to_tableau, self._strat_decompose] if allow_decompose: strats.append(self._strat_act_on_clifford_tableau_from_single_qubit_decompose) - strats.append(self._strat_decompose) for strat in strats: result = strat(action, qubits) if result is False: diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 31e18b2c0b1..14c86705b9a 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -26,7 +26,6 @@ if TYPE_CHECKING: import cirq - from typing import Optional class ActOnStabilizerCHFormArgs(ActOnArgs): @@ -64,10 +63,9 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [self._strat_apply_to_ch_form, self._strat_apply_mixture_to_ch_form] + strats = [self._strat_apply_to_ch_form, self._strat_apply_mixture_to_ch_form, self._strat_decompose] if allow_decompose: strats.append(self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) - strats.append(self._strat_decompose) for strat in strats: result = strat(action, qubits) if result is True: From 955c8fb7fae599019fa02016594920c8f333d4da Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 21:31:32 -0800 Subject: [PATCH 29/41] working --- cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py | 2 +- cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 3846b8315ce..1b692d4bdbd 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -255,5 +255,5 @@ def _strat_decompose( if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): return NotImplemented for op in operations: - assert self._act_on_fallback_(op, op.qubits) + protocols.act_on(op, self) return True diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 14c86705b9a..c8363889d98 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -253,7 +253,7 @@ def _strat_decompose( if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): return NotImplemented for op in operations: - assert self._act_on_fallback_(op, op.qubits) + protocols.act_on(op, self) return True From c0f4f40a4e314dfecc51f45744368c8564247817 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 21:32:12 -0800 Subject: [PATCH 30/41] format --- .../sim/clifford/act_on_clifford_tableau_args.py | 10 ++++++---- .../sim/clifford/act_on_stabilizer_ch_form_args.py | 12 +++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 1b692d4bdbd..8d42f557d49 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -66,7 +66,11 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [self._strat_apply_to_tableau, self._strat_apply_mixture_to_tableau, self._strat_decompose] + strats = [ + self._strat_apply_to_tableau, + self._strat_apply_mixture_to_tableau, + self._strat_decompose, + ] if allow_decompose: strats.append(self._strat_act_on_clifford_tableau_from_single_qubit_decompose) for strat in strats: @@ -247,9 +251,7 @@ def _strat_act_on_clifford_tableau_from_single_qubit_decompose( return NotImplemented - def _strat_decompose( - self, val: Any, qubits: Sequence['cirq.Qid'] - ) -> bool: + def _strat_decompose(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: gate = val.gate if isinstance(val, ops.Operation) else val operations = protocols.decompose_once_with_qubits(gate, qubits, None) if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index c8363889d98..00419208dec 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -63,7 +63,11 @@ def _act_on_fallback_( qubits: Sequence['cirq.Qid'], allow_decompose: bool = True, ) -> Union[bool, NotImplementedType]: - strats = [self._strat_apply_to_ch_form, self._strat_apply_mixture_to_ch_form, self._strat_decompose] + strats = [ + self._strat_apply_to_ch_form, + self._strat_apply_mixture_to_ch_form, + self._strat_decompose, + ] if allow_decompose: strats.append(self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) for strat in strats: @@ -245,9 +249,7 @@ def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( return NotImplemented - def _strat_decompose( - self, val: Any, qubits: Sequence['cirq.Qid'] - ) -> bool: + def _strat_decompose(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: gate = val.gate if isinstance(val, ops.Operation) else val operations = protocols.decompose_once_with_qubits(gate, qubits, None) if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): @@ -258,4 +260,4 @@ def _strat_decompose( def _phase(gate): - return np.exp(1j * np.pi * gate.global_shift * gate.exponent) \ No newline at end of file + return np.exp(1j * np.pi * gate.global_shift * gate.exponent) From 54245f1cf66a2d3458752cebe9d67bd926eb7a65 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 21:46:55 -0800 Subject: [PATCH 31/41] random gate --- cirq-core/cirq/ops/random_gate_channel.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cirq-core/cirq/ops/random_gate_channel.py b/cirq-core/cirq/ops/random_gate_channel.py index fbb0a6d66ae..1ba890ce133 100644 --- a/cirq-core/cirq/ops/random_gate_channel.py +++ b/cirq-core/cirq/ops/random_gate_channel.py @@ -123,20 +123,6 @@ def _trace_distance_bound_(self) -> float: result *= float(self.probability) return result - def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): - from cirq.sim import clifford - - if self._is_parameterized_(): - return NotImplemented - if isinstance(args, clifford.ActOnCliffordTableauArgs): - if args.prng.random() < self.probability: - # Note: because we're doing this probabilistically, it's not - # safe to fallback to other strategies if act_on fails. Those - # strategies could double-count the probability. - protocols.act_on(self.sub_gate, args, qubits) - return True - return NotImplemented - def _json_dict_(self) -> Dict[str, Any]: return protocols.obj_to_dict_helper(self, ['sub_gate', 'probability']) From d78dc4daa6f94b9dfa61c0316423094b860d26e0 Mon Sep 17 00:00:00 2001 From: Dax Fohl Date: Sun, 21 Nov 2021 21:50:18 -0800 Subject: [PATCH 32/41] random gate --- cirq-core/cirq/ops/random_gate_channel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cirq-core/cirq/ops/random_gate_channel.py b/cirq-core/cirq/ops/random_gate_channel.py index 1ba890ce133..21689b297dc 100644 --- a/cirq-core/cirq/ops/random_gate_channel.py +++ b/cirq-core/cirq/ops/random_gate_channel.py @@ -22,7 +22,6 @@ cast, SupportsFloat, Optional, - Sequence, ) import numpy as np From fa9d0831fe4398db3449c70425fbc61cb8469692 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Mon, 13 Dec 2021 11:32:14 -0800 Subject: [PATCH 33/41] global phase --- cirq-core/cirq/ops/global_phase_op.py | 14 -------------- .../sim/clifford/act_on_clifford_tableau_args.py | 7 ++++++- .../sim/clifford/act_on_stabilizer_ch_form_args.py | 7 ++++++- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/cirq-core/cirq/ops/global_phase_op.py b/cirq-core/cirq/ops/global_phase_op.py index 5588172e12e..139f51ab177 100644 --- a/cirq-core/cirq/ops/global_phase_op.py +++ b/cirq-core/cirq/ops/global_phase_op.py @@ -87,20 +87,6 @@ def _apply_unitary_(self, args) -> np.ndarray: def _has_stabilizer_effect_(self) -> bool: return True - def _act_on_(self, args: 'cirq.ActOnArgs', qubits): - from cirq.sim import clifford - - if isinstance(args, clifford.ActOnCliffordTableauArgs): - # Since CliffordTableau does not keep track of the global phase, - # it's safe to just ignore it here. - return True - - if isinstance(args, clifford.ActOnStabilizerCHFormArgs): - args.state.omega *= self.coefficient - return True - - return NotImplemented - def __str__(self) -> str: return str(self.coefficient) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 8d42f557d49..99d543615fc 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -19,7 +19,7 @@ import numpy as np from cirq import protocols, ops, linalg -from cirq.ops import common_gates, matrix_gates +from cirq.ops import common_gates, matrix_gates, global_phase_op from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary from cirq.qis.clifford_tableau import CliffordTableau @@ -199,6 +199,9 @@ def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): tableau.xs[:, axis2] ^= tableau.xs[:, axis1] tableau.zs[:, axis1] ^= tableau.zs[:, axis2] + def _global_phase(self, g: global_phase_op.GlobalPhaseGate): + pass + def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: if not protocols.has_stabilizer_effect(val): return NotImplemented @@ -216,6 +219,8 @@ def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo self._cx(gate, axes[0], axes[1]) elif isinstance(gate, common_gates.CZPowGate): self._cz(gate, axes[0], axes[1]) + elif isinstance(gate, global_phase_op.GlobalPhaseGate): + self._global_phase(gate) else: return NotImplemented return True diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 00419208dec..6022a6c12f7 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -17,7 +17,7 @@ import numpy as np from cirq import value, ops, protocols, linalg -from cirq.ops import common_gates, pauli_gates, matrix_gates +from cirq.ops import common_gates, pauli_gates, matrix_gates, global_phase_op from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary from cirq.sim.act_on_args import ActOnArgs @@ -185,6 +185,9 @@ def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): state.M[axis1, :] ^= state.M[axis2, :] state.omega *= _phase(g) + def _global_phase(self, g: global_phase_op.GlobalPhaseGate): + self.state.omega *= g.coefficient + def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: if not protocols.has_stabilizer_effect(val): return NotImplemented @@ -202,6 +205,8 @@ def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> boo self._cx(gate, axes[0], axes[1]) elif isinstance(gate, common_gates.CZPowGate): self._cz(gate, axes[0], axes[1]) + elif isinstance(gate, global_phase_op.GlobalPhaseGate): + self._global_phase(gate) else: return NotImplemented return True From 4de415d4c857d3fe1981966834ce8effcd439be8 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Mon, 13 Dec 2021 14:51:01 -0800 Subject: [PATCH 34/41] improve mixture handling --- .../sim/clifford/act_on_clifford_tableau_args.py | 16 +++++----------- .../clifford/act_on_stabilizer_ch_form_args.py | 16 +++++----------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 99d543615fc..957df1ae910 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -229,17 +229,11 @@ def _strat_apply_mixture_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid'] mixture = protocols.mixture(val, None) if mixture is None: return NotImplemented - if not all(linalg.is_unitary(m) for _, m in mixture): - return NotImplemented - rand = self.prng.random() - psum = 0.0 - for p, mix in mixture: - psum += p - if psum >= rand: - return self._strat_act_on_clifford_tableau_from_single_qubit_decompose( - matrix_gates.MatrixGate(mix), qubits - ) - raise AssertionError("Probablities don't add to 1") + probabilities, unitaries = zip(*mixture) + index = self.prng.choice(len(unitaries), p=probabilities) + return self._strat_act_on_clifford_tableau_from_single_qubit_decompose( + matrix_gates.MatrixGate(unitaries[index]), qubits + ) def _strat_act_on_clifford_tableau_from_single_qubit_decompose( self, val: Any, qubits: Sequence['cirq.Qid'] diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 6022a6c12f7..7b69878047e 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -215,17 +215,11 @@ def _strat_apply_mixture_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid'] mixture = protocols.mixture(val, None) if mixture is None: return NotImplemented - if not all(linalg.is_unitary(m) for _, m in mixture): - return NotImplemented - rand = self.prng.random() - psum = 0.0 - for p, mix in mixture: - psum += p - if psum >= rand: - return self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( - matrix_gates.MatrixGate(mix), qubits - ) - raise AssertionError("Probablities don't add to 1") + probabilities, unitaries = zip(*mixture) + index = self.prng.choice(len(unitaries), p=probabilities) + return self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( + matrix_gates.MatrixGate(unitaries[index]), qubits + ) def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( self, val: Any, qubits: Sequence['cirq.Qid'] From 2c2de8e1e8d44a52b368566a9485101badce5ebd Mon Sep 17 00:00:00 2001 From: daxfohl Date: Mon, 13 Dec 2021 16:08:50 -0800 Subject: [PATCH 35/41] Fix nonunitary mixtures --- cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py | 2 ++ cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 957df1ae910..89e08c9aba1 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -229,6 +229,8 @@ def _strat_apply_mixture_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid'] mixture = protocols.mixture(val, None) if mixture is None: return NotImplemented + if not all(linalg.is_unitary(m) for _, m in mixture): + return NotImplemented probabilities, unitaries = zip(*mixture) index = self.prng.choice(len(unitaries), p=probabilities) return self._strat_act_on_clifford_tableau_from_single_qubit_decompose( diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 7b69878047e..d8fec5788c7 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -215,6 +215,8 @@ def _strat_apply_mixture_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid'] mixture = protocols.mixture(val, None) if mixture is None: return NotImplemented + if not all(linalg.is_unitary(m) for _, m in mixture): + return NotImplemented probabilities, unitaries = zip(*mixture) index = self.prng.choice(len(unitaries), p=probabilities) return self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( From ccdb0029da45c1db33fdf988301b050f6b205ac7 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Mon, 13 Dec 2021 17:51:31 -0800 Subject: [PATCH 36/41] fix swap_gates_test imports --- cirq-core/cirq/ops/swap_gates_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/ops/swap_gates_test.py b/cirq-core/cirq/ops/swap_gates_test.py index 3f96bd9c237..504f045572b 100644 --- a/cirq-core/cirq/ops/swap_gates_test.py +++ b/cirq-core/cirq/ops/swap_gates_test.py @@ -14,7 +14,7 @@ import numpy as np import pytest -import scipy +from scipy import linalg import sympy import cirq @@ -207,7 +207,7 @@ def test_riswap_hamiltonian(angle_rads): y = np.array([[0, -1j], [1j, 0]]) xx = np.kron(x, x) yy = np.kron(y, y) - expected = scipy.linalg.expm(+0.5j * angle_rads * (xx + yy)) + expected = linalg.expm(+0.5j * angle_rads * (xx + yy)) assert np.allclose(actual, expected) From 2408489f985771c633ba3757ecdcf867380b260d Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 19 Dec 2021 10:29:36 -0800 Subject: [PATCH 37/41] abstract --- .../clifford/act_on_clifford_tableau_args.py | 96 +-------- .../sim/clifford/act_on_stabilizer_args.py | 182 ++++++++++++++++++ .../act_on_stabilizer_ch_form_args.py | 110 +---------- 3 files changed, 196 insertions(+), 192 deletions(-) create mode 100644 cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 89e08c9aba1..911d655fbb2 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -14,23 +14,19 @@ """A protocol for implementing high performance clifford tableau evolutions for Clifford Simulator.""" -from typing import Any, Dict, TYPE_CHECKING, List, Sequence, Union +from typing import Any, Dict, TYPE_CHECKING, List, Sequence import numpy as np -from cirq import protocols, ops, linalg -from cirq.ops import common_gates, matrix_gates, global_phase_op -from cirq.ops.clifford_gate import SingleQubitCliffordGate -from cirq.protocols import has_unitary, num_qubits, unitary +from cirq.ops import common_gates, global_phase_op from cirq.qis.clifford_tableau import CliffordTableau -from cirq.sim.act_on_args import ActOnArgs -from cirq.type_workarounds import NotImplementedType +from cirq.sim.clifford.act_on_stabilizer_args import ActOnStabilizerArgs if TYPE_CHECKING: import cirq -class ActOnCliffordTableauArgs(ActOnArgs): +class ActOnCliffordTableauArgs(ActOnStabilizerArgs): """State and context for an operation acting on a clifford tableau. To act on this object, directly edit the `tableau` property, which is @@ -57,32 +53,9 @@ def __init__( log_of_measurement_results: A mutable object that measurements are being recorded into. """ - super().__init__(prng, qubits, log_of_measurement_results) + super().__init__(prng, log_of_measurement_results, qubits) self.tableau = tableau - def _act_on_fallback_( - self, - action: Union['cirq.Operation', 'cirq.Gate'], - qubits: Sequence['cirq.Qid'], - allow_decompose: bool = True, - ) -> Union[bool, NotImplementedType]: - strats = [ - self._strat_apply_to_tableau, - self._strat_apply_mixture_to_tableau, - self._strat_decompose, - ] - if allow_decompose: - strats.append(self._strat_act_on_clifford_tableau_from_single_qubit_decompose) - for strat in strats: - result = strat(action, qubits) - if result is False: - break # coverage: ignore - if result is True: - return True - assert result is NotImplemented, str(result) - - return NotImplemented - def _perform_measurement(self, qubits: Sequence['cirq.Qid']) -> List[int]: """Returns the measurement from the tableau.""" return [self.tableau._measure(self.qubit_map[q], self.prng) for q in qubits] @@ -201,62 +174,3 @@ def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): def _global_phase(self, g: global_phase_op.GlobalPhaseGate): pass - - def _strat_apply_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - if not protocols.has_stabilizer_effect(val): - return NotImplemented - gate = val.gate if isinstance(val, ops.Operation) else val - axes = self.get_axes(qubits) - if isinstance(gate, common_gates.XPowGate): - self._x(gate, axes[0]) - elif isinstance(gate, common_gates.YPowGate): - self._y(gate, axes[0]) - elif isinstance(gate, common_gates.ZPowGate): - self._z(gate, axes[0]) - elif isinstance(gate, common_gates.HPowGate): - self._h(gate, axes[0]) - elif isinstance(gate, common_gates.CXPowGate): - self._cx(gate, axes[0], axes[1]) - elif isinstance(gate, common_gates.CZPowGate): - self._cz(gate, axes[0], axes[1]) - elif isinstance(gate, global_phase_op.GlobalPhaseGate): - self._global_phase(gate) - else: - return NotImplemented - return True - - def _strat_apply_mixture_to_tableau(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - mixture = protocols.mixture(val, None) - if mixture is None: - return NotImplemented - if not all(linalg.is_unitary(m) for _, m in mixture): - return NotImplemented - probabilities, unitaries = zip(*mixture) - index = self.prng.choice(len(unitaries), p=probabilities) - return self._strat_act_on_clifford_tableau_from_single_qubit_decompose( - matrix_gates.MatrixGate(unitaries[index]), qubits - ) - - def _strat_act_on_clifford_tableau_from_single_qubit_decompose( - self, val: Any, qubits: Sequence['cirq.Qid'] - ) -> bool: - if num_qubits(val) == 1: - if not has_unitary(val): - return NotImplemented - u = unitary(val) - clifford_gate = SingleQubitCliffordGate.from_unitary(u) - if clifford_gate is not None: - for gate, quarter_turns in clifford_gate.decompose_rotation(): - self._strat_apply_to_tableau(gate ** (quarter_turns / 2), qubits) - return True - - return NotImplemented - - def _strat_decompose(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - gate = val.gate if isinstance(val, ops.Operation) else val - operations = protocols.decompose_once_with_qubits(gate, qubits, None) - if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): - return NotImplemented - for op in operations: - protocols.act_on(op, self) - return True diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py new file mode 100644 index 00000000000..6c5c46d3370 --- /dev/null +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py @@ -0,0 +1,182 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""A protocol for implementing high performance clifford CH-Form evolutions + for Clifford Simulator.""" + +import abc +from typing import Any, Dict, TYPE_CHECKING, Sequence, Union + +import numpy as np + +from cirq import ops, protocols, linalg +from cirq.ops import common_gates, matrix_gates, global_phase_op +from cirq.ops.clifford_gate import SingleQubitCliffordGate +from cirq.protocols import has_unitary, num_qubits, unitary +from cirq.sim.act_on_args import ActOnArgs +from cirq.type_workarounds import NotImplementedType + +if TYPE_CHECKING: + import cirq + + +class ActOnStabilizerArgs(ActOnArgs): + """Wrapper around a stabilizer state in CH form for the act_on protocol. + + To act on this object, directly edit the `state` property, which is + storing the stabilizer state of the quantum system with one axis per qubit. + """ + + def __init__( + self, + prng: np.random.RandomState, + log_of_measurement_results: Dict[str, Any], + qubits: Sequence['cirq.Qid'] = None, + ): + """Initializes with the given state and the axes for the operation. + + Args: + qubits: Determines the canonical ordering of the qubits. This + is often used in specifying the initial state, i.e. the + ordering of the computational basis states. + prng: The pseudo random number generator to use for probabilistic + effects. + log_of_measurement_results: A mutable object that measurements are + being recorded into. + """ + super().__init__(prng, qubits, log_of_measurement_results) + + def _act_on_fallback_( + self, + action: Union['cirq.Operation', 'cirq.Gate'], + qubits: Sequence['cirq.Qid'], + allow_decompose: bool = True, + ) -> Union[bool, NotImplementedType]: + strats = [ + self._strat_apply_gate, + self._strat_apply_mixture, + self._strat_decompose, + ] + if allow_decompose: + strats.append(self._strat_act_from_single_qubit_decompose) + for strat in strats: + result = strat(action, qubits) + if result is False: + break # coverage: ignore + if result is True: + return True + assert result is NotImplemented, str(result) + + return NotImplemented + + @abc.abstractmethod + def _x(self, g: common_gates.XPowGate, axis: int): + """Apply an X""" + + @abc.abstractmethod + def _y(self, g: common_gates.YPowGate, axis: int): + """Apply a Y""" + + @abc.abstractmethod + def _z(self, g: common_gates.ZPowGate, axis: int): + """Apply a Z""" + + @abc.abstractmethod + def _h(self, g: common_gates.HPowGate, axis: int): + """Apply an H""" + + @abc.abstractmethod + def _cz(self, g: common_gates.CZPowGate, axis1: int, axis2: int): + """Apply a CZ""" + + @abc.abstractmethod + def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): + """Apply a CX""" + + @abc.abstractmethod + def _global_phase(self, g: global_phase_op.GlobalPhaseGate): + """Apply global phase""" + + def _strat_apply_gate(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: + if not protocols.has_stabilizer_effect(val): + return NotImplemented + gate = val.gate if isinstance(val, ops.Operation) else val + axes = self.get_axes(qubits) + if isinstance(gate, common_gates.XPowGate): + self._x(gate, axes[0]) + elif isinstance(gate, common_gates.YPowGate): + self._y(gate, axes[0]) + elif isinstance(gate, common_gates.ZPowGate): + self._z(gate, axes[0]) + elif isinstance(gate, common_gates.HPowGate): + self._h(gate, axes[0]) + elif isinstance(gate, common_gates.CXPowGate): + self._cx(gate, axes[0], axes[1]) + elif isinstance(gate, common_gates.CZPowGate): + self._cz(gate, axes[0], axes[1]) + elif isinstance(gate, global_phase_op.GlobalPhaseGate): + self._global_phase(gate) + else: + return NotImplemented + return True + + def _strat_apply_mixture(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: + mixture = protocols.mixture(val, None) + if mixture is None: + return NotImplemented + if not all(linalg.is_unitary(m) for _, m in mixture): + return NotImplemented + probabilities, unitaries = zip(*mixture) + index = self.prng.choice(len(unitaries), p=probabilities) + return self._strat_act_from_single_qubit_decompose( + matrix_gates.MatrixGate(unitaries[index]), qubits + ) + + def _strat_act_from_single_qubit_decompose( + self, val: Any, qubits: Sequence['cirq.Qid'] + ) -> bool: + if num_qubits(val) == 1: + if not has_unitary(val): + return NotImplemented + u = unitary(val) + clifford_gate = SingleQubitCliffordGate.from_unitary(u) + if clifford_gate is not None: + # Gather the effective unitary applied so as to correct for the + # global phase later. + final_unitary = np.eye(2) + for axis, quarter_turns in clifford_gate.decompose_rotation(): + gate = axis ** (quarter_turns / 2) + self._strat_apply_gate(gate, qubits) + final_unitary = np.matmul(unitary(gate), final_unitary) + + # Find the entry with the largest magnitude in the input unitary. + k = max(np.ndindex(*u.shape), key=lambda t: abs(u[t])) + # Correct the global phase that wasn't conserved in the above + # decomposition. + self._global_phase(global_phase_op.GlobalPhaseGate(u[k] / final_unitary[k])) + return True + + return NotImplemented + + def _strat_decompose(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: + gate = val.gate if isinstance(val, ops.Operation) else val + operations = protocols.decompose_once_with_qubits(gate, qubits, None) + if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): + return NotImplemented + for op in operations: + protocols.act_on(op, self) + return True + + +def _phase(gate): + return np.exp(1j * np.pi * gate.global_shift * gate.exponent) diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index d8fec5788c7..73e9ecd4941 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -11,24 +11,23 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +"""A protocol for implementing high performance clifford CH-Form evolutions + for Clifford Simulator.""" -from typing import Any, Dict, TYPE_CHECKING, List, Sequence, Union +from typing import Any, Dict, TYPE_CHECKING, List, Sequence import numpy as np -from cirq import value, ops, protocols, linalg -from cirq.ops import common_gates, pauli_gates, matrix_gates, global_phase_op -from cirq.ops.clifford_gate import SingleQubitCliffordGate -from cirq.protocols import has_unitary, num_qubits, unitary -from cirq.sim.act_on_args import ActOnArgs +from cirq import value, ops, protocols +from cirq.ops import common_gates, pauli_gates, global_phase_op +from cirq.sim.clifford.act_on_stabilizer_args import ActOnStabilizerArgs from cirq.sim.clifford.stabilizer_state_ch_form import StabilizerStateChForm -from cirq.type_workarounds import NotImplementedType if TYPE_CHECKING: import cirq -class ActOnStabilizerCHFormArgs(ActOnArgs): +class ActOnStabilizerCHFormArgs(ActOnStabilizerArgs): """Wrapper around a stabilizer state in CH form for the act_on protocol. To act on this object, directly edit the `state` property, which is @@ -43,6 +42,7 @@ def __init__( qubits: Sequence['cirq.Qid'] = None, ): """Initializes with the given state and the axes for the operation. + Args: state: The StabilizerStateChForm to act on. Operations are expected to perform inplace edits of this object. @@ -54,30 +54,9 @@ def __init__( log_of_measurement_results: A mutable object that measurements are being recorded into. """ - super().__init__(prng, qubits, log_of_measurement_results) + super().__init__(prng, log_of_measurement_results, qubits) self.state = state - def _act_on_fallback_( - self, - action: Union['cirq.Operation', 'cirq.Gate'], - qubits: Sequence['cirq.Qid'], - allow_decompose: bool = True, - ) -> Union[bool, NotImplementedType]: - strats = [ - self._strat_apply_to_ch_form, - self._strat_apply_mixture_to_ch_form, - self._strat_decompose, - ] - if allow_decompose: - strats.append(self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose) - for strat in strats: - result = strat(action, qubits) - if result is True: - return True - assert result is NotImplemented, str(result) - - return NotImplemented - def _perform_measurement(self, qubits: Sequence['cirq.Qid']) -> List[int]: """Returns the measurement from the stabilizer state form.""" return [self.state._measure(self.qubit_map[q], self.prng) for q in qubits] @@ -188,77 +167,6 @@ def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): def _global_phase(self, g: global_phase_op.GlobalPhaseGate): self.state.omega *= g.coefficient - def _strat_apply_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - if not protocols.has_stabilizer_effect(val): - return NotImplemented - gate = val.gate if isinstance(val, ops.Operation) else val - axes = self.get_axes(qubits) - if isinstance(gate, common_gates.XPowGate): - self._x(gate, axes[0]) - elif isinstance(gate, common_gates.YPowGate): - self._y(gate, axes[0]) - elif isinstance(gate, common_gates.ZPowGate): - self._z(gate, axes[0]) - elif isinstance(gate, common_gates.HPowGate): - self._h(gate, axes[0]) - elif isinstance(gate, common_gates.CXPowGate): - self._cx(gate, axes[0], axes[1]) - elif isinstance(gate, common_gates.CZPowGate): - self._cz(gate, axes[0], axes[1]) - elif isinstance(gate, global_phase_op.GlobalPhaseGate): - self._global_phase(gate) - else: - return NotImplemented - return True - - def _strat_apply_mixture_to_ch_form(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - mixture = protocols.mixture(val, None) - if mixture is None: - return NotImplemented - if not all(linalg.is_unitary(m) for _, m in mixture): - return NotImplemented - probabilities, unitaries = zip(*mixture) - index = self.prng.choice(len(unitaries), p=probabilities) - return self._strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( - matrix_gates.MatrixGate(unitaries[index]), qubits - ) - - def _strat_act_on_stabilizer_ch_form_from_single_qubit_decompose( - self, val: Any, qubits: Sequence['cirq.Qid'] - ) -> bool: - if num_qubits(val) == 1: - if not has_unitary(val): - return NotImplemented - u = unitary(val) - clifford_gate = SingleQubitCliffordGate.from_unitary(u) - if clifford_gate is not None: - # Gather the effective unitary applied so as to correct for the - # global phase later. - final_unitary = np.eye(2) - for axis, quarter_turns in clifford_gate.decompose_rotation(): - gate = axis ** (quarter_turns / 2) - self._strat_apply_to_ch_form(gate, qubits) - - final_unitary = np.matmul(unitary(gate), final_unitary) - - # Find the entry with the largest magnitude in the input unitary. - k = max(np.ndindex(*u.shape), key=lambda t: abs(u[t])) - # Correct the global phase that wasn't conserved in the above - # decomposition. - self.state.omega *= u[k] / final_unitary[k] - return True - - return NotImplemented - - def _strat_decompose(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: - gate = val.gate if isinstance(val, ops.Operation) else val - operations = protocols.decompose_once_with_qubits(gate, qubits, None) - if operations is None or not all(protocols.has_stabilizer_effect(op) for op in operations): - return NotImplemented - for op in operations: - protocols.act_on(op, self) - return True - def _phase(gate): return np.exp(1j * np.pi * gate.global_shift * gate.exponent) From 6a68460488d29453229daf3f6a09c22701b93e3e Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 19 Dec 2021 17:47:50 -0800 Subject: [PATCH 38/41] small fixes --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/sim/__init__.py | 1 + cirq-core/cirq/sim/clifford/__init__.py | 4 ++ .../clifford/act_on_clifford_tableau_args.py | 8 +--- .../sim/clifford/act_on_stabilizer_args.py | 39 ++----------------- .../act_on_stabilizer_ch_form_args.py | 8 +--- 6 files changed, 14 insertions(+), 47 deletions(-) diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 5842452cc15..679dc5bed71 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -405,6 +405,7 @@ ActOnCliffordTableauArgs, ActOnDensityMatrixArgs, ActOnStabilizerCHFormArgs, + ActOnStabilizerArgs, ActOnStateVectorArgs, StabilizerStateChForm, CIRCUIT_LIKE, diff --git a/cirq-core/cirq/sim/__init__.py b/cirq-core/cirq/sim/__init__.py index 02443dbeff1..7a5cf43194e 100644 --- a/cirq-core/cirq/sim/__init__.py +++ b/cirq-core/cirq/sim/__init__.py @@ -89,6 +89,7 @@ from cirq.sim.clifford import ( ActOnCliffordTableauArgs, ActOnStabilizerCHFormArgs, + ActOnStabilizerArgs, StabilizerSampler, StabilizerStateChForm, CliffordSimulator, diff --git a/cirq-core/cirq/sim/clifford/__init__.py b/cirq-core/cirq/sim/clifford/__init__.py index 2f8a2e5a310..42ee7bf7ad8 100644 --- a/cirq-core/cirq/sim/clifford/__init__.py +++ b/cirq-core/cirq/sim/clifford/__init__.py @@ -7,6 +7,10 @@ ActOnStabilizerCHFormArgs, ) +from cirq.sim.clifford.act_on_stabilizer_args import ( + ActOnStabilizerArgs, +) + from cirq.sim.clifford.stabilizer_state_ch_form import ( StabilizerStateChForm, ) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index 911d655fbb2..669355743cc 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -27,11 +27,7 @@ class ActOnCliffordTableauArgs(ActOnStabilizerArgs): - """State and context for an operation acting on a clifford tableau. - - To act on this object, directly edit the `tableau` property, which is - storing the density matrix of the quantum system with one axis per qubit. - """ + """State and context for an operation acting on a clifford tableau.""" def __init__( self, @@ -53,7 +49,7 @@ def __init__( log_of_measurement_results: A mutable object that measurements are being recorded into. """ - super().__init__(prng, log_of_measurement_results, qubits) + super().__init__(prng, qubits, log_of_measurement_results) self.tableau = tableau def _perform_measurement(self, qubits: Sequence['cirq.Qid']) -> List[int]: diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py index 6c5c46d3370..04a2a29fc3a 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py @@ -11,11 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""A protocol for implementing high performance clifford CH-Form evolutions - for Clifford Simulator.""" import abc -from typing import Any, Dict, TYPE_CHECKING, Sequence, Union +from typing import Any, Callable, List, Sequence, TYPE_CHECKING, Union import numpy as np @@ -30,31 +28,8 @@ import cirq -class ActOnStabilizerArgs(ActOnArgs): - """Wrapper around a stabilizer state in CH form for the act_on protocol. - - To act on this object, directly edit the `state` property, which is - storing the stabilizer state of the quantum system with one axis per qubit. - """ - - def __init__( - self, - prng: np.random.RandomState, - log_of_measurement_results: Dict[str, Any], - qubits: Sequence['cirq.Qid'] = None, - ): - """Initializes with the given state and the axes for the operation. - - Args: - qubits: Determines the canonical ordering of the qubits. This - is often used in specifying the initial state, i.e. the - ordering of the computational basis states. - prng: The pseudo random number generator to use for probabilistic - effects. - log_of_measurement_results: A mutable object that measurements are - being recorded into. - """ - super().__init__(prng, qubits, log_of_measurement_results) +class ActOnStabilizerArgs(ActOnArgs, metaclass=abc.ABCMeta): + """Abstract wrapper around a stabilizer state for the act_on protocol.""" def _act_on_fallback_( self, @@ -70,9 +45,7 @@ def _act_on_fallback_( if allow_decompose: strats.append(self._strat_act_from_single_qubit_decompose) for strat in strats: - result = strat(action, qubits) - if result is False: - break # coverage: ignore + result = strat(action, qubits) # type: ignore if result is True: return True assert result is NotImplemented, str(result) @@ -176,7 +149,3 @@ def _strat_decompose(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: for op in operations: protocols.act_on(op, self) return True - - -def _phase(gate): - return np.exp(1j * np.pi * gate.global_shift * gate.exponent) diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 73e9ecd4941..ada43b172db 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -28,11 +28,7 @@ class ActOnStabilizerCHFormArgs(ActOnStabilizerArgs): - """Wrapper around a stabilizer state in CH form for the act_on protocol. - - To act on this object, directly edit the `state` property, which is - storing the stabilizer state of the quantum system with one axis per qubit. - """ + """Wrapper around a stabilizer state in CH form for the act_on protocol.""" def __init__( self, @@ -54,7 +50,7 @@ def __init__( log_of_measurement_results: A mutable object that measurements are being recorded into. """ - super().__init__(prng, log_of_measurement_results, qubits) + super().__init__(prng, qubits, log_of_measurement_results) self.state = state def _perform_measurement(self, qubits: Sequence['cirq.Qid']) -> List[int]: From e53ae434b75c2b864a9100caebd6b45b057d15be Mon Sep 17 00:00:00 2001 From: daxfohl Date: Sun, 19 Dec 2021 17:54:42 -0800 Subject: [PATCH 39/41] lint --- cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py | 2 +- cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py index 04a2a29fc3a..1f7da1ed052 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py @@ -13,7 +13,7 @@ # limitations under the License. import abc -from typing import Any, Callable, List, Sequence, TYPE_CHECKING, Union +from typing import Any, Sequence, TYPE_CHECKING, Union import numpy as np diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index ada43b172db..f05d58ec213 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -11,8 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""A protocol for implementing high performance clifford CH-Form evolutions - for Clifford Simulator.""" from typing import Any, Dict, TYPE_CHECKING, List, Sequence From 9ebe2a1a7968d8479b4a45fabd9ec347a1e3529f Mon Sep 17 00:00:00 2001 From: daxfohl Date: Tue, 11 Jan 2022 16:04:23 -0800 Subject: [PATCH 40/41] Put decompose strategy in allow_decompose branch --- .../cirq/sim/clifford/act_on_stabilizer_args.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py index 1f7da1ed052..c74d031c5b8 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py @@ -18,7 +18,7 @@ import numpy as np from cirq import ops, protocols, linalg -from cirq.ops import common_gates, matrix_gates, global_phase_op +from cirq.ops import common_gates, global_phase_op, matrix_gates, swap_gates from cirq.ops.clifford_gate import SingleQubitCliffordGate from cirq.protocols import has_unitary, num_qubits, unitary from cirq.sim.act_on_args import ActOnArgs @@ -40,9 +40,9 @@ def _act_on_fallback_( strats = [ self._strat_apply_gate, self._strat_apply_mixture, - self._strat_decompose, ] if allow_decompose: + strats.append(self._strat_decompose) strats.append(self._strat_act_from_single_qubit_decompose) for strat in strats: result = strat(action, qubits) # type: ignore @@ -80,6 +80,15 @@ def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): def _global_phase(self, g: global_phase_op.GlobalPhaseGate): """Apply global phase""" + def _swap(self, g: swap_gates.SwapPowGate, axis1: int, axis2: int): + """Apply a SWAP""" + assert g.exponent % 1 == 0 + self._cx(common_gates.CX, axis1, axis2) + self._cx( + common_gates.CXPowGate(exponent=g.exponent, global_shift=g.global_shift), axis2, axis1 + ) + self._cx(common_gates.CX, axis1, axis2) + def _strat_apply_gate(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: if not protocols.has_stabilizer_effect(val): return NotImplemented @@ -99,6 +108,8 @@ def _strat_apply_gate(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: self._cz(gate, axes[0], axes[1]) elif isinstance(gate, global_phase_op.GlobalPhaseGate): self._global_phase(gate) + elif isinstance(gate, swap_gates.SwapPowGate): + self._swap(gate, axes[0], axes[1]) else: return NotImplemented return True From f16231761a77822564d935492d56b2b0b6028981 Mon Sep 17 00:00:00 2001 From: daxfohl Date: Fri, 14 Jan 2022 22:34:38 -0800 Subject: [PATCH 41/41] Change asserts to exceptions, rename control/target axes --- .../clifford/act_on_clifford_tableau_args.py | 58 ++++++++++--------- .../sim/clifford/act_on_stabilizer_args.py | 31 +++++----- .../act_on_stabilizer_ch_form_args.py | 36 +++++++----- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py index d361fc22e55..15ba74eee98 100644 --- a/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_clifford_tableau_args.py @@ -72,7 +72,8 @@ def _x(self, g: common_gates.XPowGate, axis: int): exponent = g.exponent if exponent % 2 == 0: return - assert exponent % 0.5 == 0.0 + if exponent % 0.5 != 0.0: + raise ValueError('X exponent must be half integer') # coverage: ignore tableau = self.tableau effective_exponent = exponent % 2 if effective_exponent == 0.5: @@ -88,7 +89,8 @@ def _y(self, g: common_gates.YPowGate, axis: int): exponent = g.exponent if exponent % 2 == 0: return - assert exponent % 0.5 == 0.0 + if exponent % 0.5 != 0.0: + raise ValueError('Y exponent must be half integer') # coverage: ignore tableau = self.tableau effective_exponent = exponent % 2 if effective_exponent == 0.5: @@ -110,7 +112,8 @@ def _z(self, g: common_gates.ZPowGate, axis: int): exponent = g.exponent if exponent % 2 == 0: return - assert exponent % 0.5 == 0.0 + if exponent % 0.5 != 0.0: + raise ValueError('Z exponent must be half integer') # coverage: ignore tableau = self.tableau effective_exponent = exponent % 2 if effective_exponent == 0.5: @@ -126,47 +129,50 @@ def _h(self, g: common_gates.HPowGate, axis: int): exponent = g.exponent if exponent % 2 == 0: return - assert exponent % 2 == 1 + if exponent % 1 != 0: + raise ValueError('H exponent must be integer') # coverage: ignore self._y(common_gates.YPowGate(exponent=0.5), axis) self._x(common_gates.XPowGate(), axis) - def _cz(self, g: common_gates.CZPowGate, axis1: int, axis2: int): + def _cz(self, g: common_gates.CZPowGate, control_axis: int, target_axis: int): exponent = g.exponent if exponent % 2 == 0: return - assert exponent % 2 == 1 + if exponent % 1 != 0: + raise ValueError('CZ exponent must be integer') # coverage: ignore tableau = self.tableau - (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( - tableau.zs[:, axis2].copy(), - tableau.xs[:, axis2].copy(), + (tableau.xs[:, target_axis], tableau.zs[:, target_axis]) = ( + tableau.zs[:, target_axis].copy(), + tableau.xs[:, target_axis].copy(), ) - tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + tableau.rs[:] ^= tableau.xs[:, target_axis] & tableau.zs[:, target_axis] tableau.rs[:] ^= ( - tableau.xs[:, axis1] - & tableau.zs[:, axis2] - & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) + tableau.xs[:, control_axis] + & tableau.zs[:, target_axis] + & (~(tableau.xs[:, target_axis] ^ tableau.zs[:, control_axis])) ) - tableau.xs[:, axis2] ^= tableau.xs[:, axis1] - tableau.zs[:, axis1] ^= tableau.zs[:, axis2] - (tableau.xs[:, axis2], tableau.zs[:, axis2]) = ( - tableau.zs[:, axis2].copy(), - tableau.xs[:, axis2].copy(), + tableau.xs[:, target_axis] ^= tableau.xs[:, control_axis] + tableau.zs[:, control_axis] ^= tableau.zs[:, target_axis] + (tableau.xs[:, target_axis], tableau.zs[:, target_axis]) = ( + tableau.zs[:, target_axis].copy(), + tableau.xs[:, target_axis].copy(), ) - tableau.rs[:] ^= tableau.xs[:, axis2] & tableau.zs[:, axis2] + tableau.rs[:] ^= tableau.xs[:, target_axis] & tableau.zs[:, target_axis] - def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): + def _cx(self, g: common_gates.CXPowGate, control_axis: int, target_axis: int): exponent = g.exponent if exponent % 2 == 0: return - assert exponent % 2 == 1 + if exponent % 1 != 0: + raise ValueError('CX exponent must be integer') # coverage: ignore tableau = self.tableau tableau.rs[:] ^= ( - tableau.xs[:, axis1] - & tableau.zs[:, axis2] - & (~(tableau.xs[:, axis2] ^ tableau.zs[:, axis1])) + tableau.xs[:, control_axis] + & tableau.zs[:, target_axis] + & (~(tableau.xs[:, target_axis] ^ tableau.zs[:, control_axis])) ) - tableau.xs[:, axis2] ^= tableau.xs[:, axis1] - tableau.zs[:, axis1] ^= tableau.zs[:, axis2] + tableau.xs[:, target_axis] ^= tableau.xs[:, control_axis] + tableau.zs[:, control_axis] ^= tableau.zs[:, target_axis] def _global_phase(self, g: global_phase_op.GlobalPhaseGate): pass diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py index c74d031c5b8..24be68544e4 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_args.py @@ -54,40 +54,43 @@ def _act_on_fallback_( @abc.abstractmethod def _x(self, g: common_gates.XPowGate, axis: int): - """Apply an X""" + """Apply an X gate""" @abc.abstractmethod def _y(self, g: common_gates.YPowGate, axis: int): - """Apply a Y""" + """Apply a Y gate""" @abc.abstractmethod def _z(self, g: common_gates.ZPowGate, axis: int): - """Apply a Z""" + """Apply a Z gate""" @abc.abstractmethod def _h(self, g: common_gates.HPowGate, axis: int): - """Apply an H""" + """Apply an H gate""" @abc.abstractmethod - def _cz(self, g: common_gates.CZPowGate, axis1: int, axis2: int): - """Apply a CZ""" + def _cz(self, g: common_gates.CZPowGate, control_axis: int, target_axis: int): + """Apply a CZ gate""" @abc.abstractmethod - def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): - """Apply a CX""" + def _cx(self, g: common_gates.CXPowGate, control_axis: int, target_axis: int): + """Apply a CX gate""" @abc.abstractmethod def _global_phase(self, g: global_phase_op.GlobalPhaseGate): """Apply global phase""" - def _swap(self, g: swap_gates.SwapPowGate, axis1: int, axis2: int): - """Apply a SWAP""" - assert g.exponent % 1 == 0 - self._cx(common_gates.CX, axis1, axis2) + def _swap(self, g: swap_gates.SwapPowGate, control_axis: int, target_axis: int): + """Apply a SWAP gate""" + if g.exponent % 1 != 0: + raise ValueError('Swap exponent must be integer') # coverage: ignore + self._cx(common_gates.CX, control_axis, target_axis) self._cx( - common_gates.CXPowGate(exponent=g.exponent, global_shift=g.global_shift), axis2, axis1 + common_gates.CXPowGate(exponent=g.exponent, global_shift=g.global_shift), + target_axis, + control_axis, ) - self._cx(common_gates.CX, axis1, axis2) + self._cx(common_gates.CX, control_axis, target_axis) def _strat_apply_gate(self, val: Any, qubits: Sequence['cirq.Qid']) -> bool: if not protocols.has_stabilizer_effect(val): diff --git a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py index 8b1e41979f3..b3f4153d7bd 100644 --- a/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py +++ b/cirq-core/cirq/sim/clifford/act_on_stabilizer_ch_form_args.py @@ -76,6 +76,8 @@ def sample( def _x(self, g: common_gates.XPowGate, axis: int): exponent = g.exponent if exponent % 2 != 0: + if exponent % 0.5 != 0.0: + raise ValueError('Y exponent must be half integer') # coverage: ignore self._h(common_gates.H, axis) self._z(common_gates.ZPowGate(exponent=exponent), axis) self._h(common_gates.H, axis) @@ -83,6 +85,8 @@ def _x(self, g: common_gates.XPowGate, axis: int): def _y(self, g: common_gates.YPowGate, axis: int): exponent = g.exponent + if exponent % 0.5 != 0.0: + raise ValueError('Y exponent must be half integer') # coverage: ignore if exponent % 2 == 0: self.state.omega *= _phase(g) elif exponent % 2 == 0.5: @@ -104,6 +108,8 @@ def _z(self, g: common_gates.ZPowGate, axis: int): exponent = g.exponent state = self.state if exponent % 2 != 0: + if exponent % 0.5 != 0.0: + raise ValueError('Z exponent must be half integer') # coverage: ignore effective_exponent = exponent % 2 for _ in range(int(effective_exponent * 2)): # Prescription for S left multiplication. @@ -116,6 +122,8 @@ def _h(self, g: common_gates.HPowGate, axis: int): exponent = g.exponent state = self.state if exponent % 2 != 0: + if exponent % 1 != 0: + raise ValueError('H exponent must be integer') # coverage: ignore # Prescription for H left multiplication # Reference: https://arxiv.org/abs/1808.00128 # Equations 48, 49 and Proposition 4 @@ -130,32 +138,34 @@ def _h(self, g: common_gates.HPowGate, axis: int): state.update_sum(t, u, delta=delta, alpha=alpha) state.omega *= _phase(g) - def _cz(self, g: common_gates.CZPowGate, axis1: int, axis2: int): + def _cz(self, g: common_gates.CZPowGate, control_axis: int, target_axis: int): exponent = g.exponent state = self.state if exponent % 2 != 0: - assert exponent % 2 == 1 + if exponent % 1 != 0: + raise ValueError('CZ exponent must be integer') # coverage: ignore # Prescription for CZ left multiplication. # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.M[axis1, :] ^= state.G[axis2, :] - state.M[axis2, :] ^= state.G[axis1, :] + state.M[control_axis, :] ^= state.G[target_axis, :] + state.M[target_axis, :] ^= state.G[control_axis, :] state.omega *= _phase(g) - def _cx(self, g: common_gates.CXPowGate, axis1: int, axis2: int): + def _cx(self, g: common_gates.CXPowGate, control_axis: int, target_axis: int): exponent = g.exponent state = self.state if exponent % 2 != 0: - assert exponent % 2 == 1 + if exponent % 1 != 0: + raise ValueError('CX exponent must be integer') # coverage: ignore # Prescription for CX left multiplication. # Reference: https://arxiv.org/abs/1808.00128 Proposition 4 end - state.gamma[axis1] = ( - state.gamma[axis1] - + state.gamma[axis2] - + 2 * (sum(state.M[axis1, :] & state.F[axis2, :]) % 2) + state.gamma[control_axis] = ( + state.gamma[control_axis] + + state.gamma[target_axis] + + 2 * (sum(state.M[control_axis, :] & state.F[target_axis, :]) % 2) ) % 4 - state.G[axis2, :] ^= state.G[axis1, :] - state.F[axis1, :] ^= state.F[axis2, :] - state.M[axis1, :] ^= state.M[axis2, :] + state.G[target_axis, :] ^= state.G[control_axis, :] + state.F[control_axis, :] ^= state.F[target_axis, :] + state.M[control_axis, :] ^= state.M[target_axis, :] state.omega *= _phase(g) def _global_phase(self, g: global_phase_op.GlobalPhaseGate):