Source code for cirq.ops.three_qubit_gates

# 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.

"""Common quantum gates that target three qubits."""

from typing import Optional, Tuple

import numpy as np

from cirq import linalg, protocols, value
from cirq._compat import proper_repr
from cirq.ops import (
    common_gates,
    controlled_gate,
    eigen_gate,
    gate_features,
    pauli_gates,
    op_tree,
    raw_types,
)


[docs]class CCZPowGate(eigen_gate.EigenGate, gate_features.ThreeQubitGate, gate_features.InterchangeableQubitsGate): """A doubly-controlled-Z that can be raised to a power. The matrix of `CCZ**t` is `diag(1, 1, 1, 1, 1, 1, 1, exp(i pi t))`. """ def _eigen_components(self): return [ (0, np.diag([1, 1, 1, 1, 1, 1, 1, 0])), (1, np.diag([0, 0, 0, 0, 0, 0, 0, 1])), ] def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): return NotImplemented global_phase = 1j**(2 * self._exponent * self._global_shift) z_phase = 1j**self._exponent c = -1j * z_phase * np.sin(np.pi * self._exponent / 2) / 4 return value.LinearDict({ 'III': global_phase * (1 - c), 'IIZ': global_phase * c, 'IZI': global_phase * c, 'ZII': global_phase * c, 'ZZI': global_phase * -c, 'ZIZ': global_phase * -c, 'IZZ': global_phase * -c, 'ZZZ': global_phase * c, }) def _decompose_(self, qubits): """An adjacency-respecting decomposition. 0: ───p───@──────────────@───────@──────────@────────── │ │ │ │ 1: ───p───X───@───p^-1───X───@───X──────@───X──────@─── │ │ │ │ 2: ───p───────X───p──────────X───p^-1───X───p^-1───X─── where p = T**self._exponent """ if protocols.is_parameterized(self): return NotImplemented a, b, c = qubits # Hacky magic: avoid the non-adjacent edge. if hasattr(b, 'is_adjacent'): if not b.is_adjacent(a): b, c = c, b elif not b.is_adjacent(c): a, b = b, a p = common_gates.T**self._exponent sweep_abc = [common_gates.CNOT(a, b), common_gates.CNOT(b, c)] return [ p(a), p(b), p(c), sweep_abc, p(b)**-1, p(c), sweep_abc, p(c)**-1, sweep_abc, p(c)**-1, sweep_abc, ] def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray: if protocols.is_parameterized(self): return NotImplemented ooo = args.subspace_index(0b111) args.target_tensor[ooo] *= np.exp(1j * self.exponent * np.pi) p = 1j**(2 * self._exponent * self._global_shift) if p != 1: args.target_tensor *= p return args.target_tensor def _circuit_diagram_info_(self, args: protocols.CircuitDiagramInfoArgs ) -> protocols.CircuitDiagramInfo: return protocols.CircuitDiagramInfo( ('@', '@', '@'), exponent=self._diagram_exponent(args)) def _qasm_(self, args: protocols.QasmArgs, qubits: Tuple[raw_types.Qid, ...]) -> Optional[str]: if self._exponent != 1: return None args.validate_version('2.0') lines = [ args.format('h {0};\n', qubits[2]), args.format('ccx {0},{1},{2};\n', qubits[0], qubits[1], qubits[2]), args.format('h {0};\n', qubits[2])] return ''.join(lines) def __repr__(self) -> str: if self._global_shift == 0: if self._exponent == 1: return 'cirq.CCZ' return '(cirq.CCZ**{})'.format(proper_repr(self._exponent)) return ( 'cirq.CCZPowGate(exponent={}, ' 'global_shift={!r})' ).format(proper_repr(self._exponent), self._global_shift) def __str__(self) -> str: if self._exponent == 1: return 'CCZ' return 'CCZ**{}'.format(self._exponent)
[docs]class CCXPowGate(eigen_gate.EigenGate, gate_features.ThreeQubitGate, gate_features.InterchangeableQubitsGate): """A Toffoli (doubly-controlled-NOT) that can be raised to a power. The matrix of `CCX**t` is an 8x8 identity except the bottom right 2x2 area is the matrix of `X**t`. """ def _eigen_components(self): return [ (0, linalg.block_diag(np.diag([1, 1, 1, 1, 1, 1]), np.array([[0.5, 0.5], [0.5, 0.5]]))), (1, linalg.block_diag(np.diag([0, 0, 0, 0, 0, 0]), np.array([[0.5, -0.5], [-0.5, 0.5]]))), ] def _pauli_expansion_(self) -> value.LinearDict[str]: if protocols.is_parameterized(self): return NotImplemented global_phase = 1j**(2 * self._exponent * self._global_shift) z_phase = 1j**self._exponent c = -1j * z_phase * np.sin(np.pi * self._exponent / 2) / 4 return value.LinearDict({ 'III': global_phase * (1 - c), 'IIX': global_phase * c, 'IZI': global_phase * c, 'ZII': global_phase * c, 'ZZI': global_phase * -c, 'ZIX': global_phase * -c, 'IZX': global_phase * -c, 'ZZX': global_phase * c, })
[docs] def qubit_index_to_equivalence_group_key(self, index): return index < 2
def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray: if protocols.is_parameterized(self): return NotImplemented p = 1j**(2 * self._exponent * self._global_shift) if p != 1: args.target_tensor *= p return protocols.apply_unitary( controlled_gate.ControlledGate( controlled_gate.ControlledGate( pauli_gates.X**self.exponent)), protocols.ApplyUnitaryArgs( args.target_tensor, args.available_buffer, args.axes), default=NotImplemented) def _decompose_(self, qubits): c1, c2, t = qubits yield common_gates.H(t) yield CCZ(c1, c2, t)**self._exponent yield common_gates.H(t) def _circuit_diagram_info_(self, args: protocols.CircuitDiagramInfoArgs ) -> protocols.CircuitDiagramInfo: return protocols.CircuitDiagramInfo( ('@', '@', 'X'), exponent=self._diagram_exponent(args)) def _qasm_(self, args: protocols.QasmArgs, qubits: Tuple[raw_types.Qid, ...]) -> Optional[str]: if self._exponent != 1: return None args.validate_version('2.0') return args.format('ccx {0},{1},{2};\n', qubits[0], qubits[1], qubits[2]) def __repr__(self) -> str: if self._global_shift == 0: if self._exponent == 1: return 'cirq.TOFFOLI' return '(cirq.TOFFOLI**{})'.format(proper_repr(self._exponent)) return ( 'cirq.CCXPowGate(exponent={}, ' 'global_shift={!r})' ).format(proper_repr(self._exponent), self._global_shift) def __str__(self) -> str: if self._exponent == 1: return 'TOFFOLI' return 'TOFFOLI**{}'.format(self._exponent)
[docs]class CSwapGate(gate_features.ThreeQubitGate, gate_features.InterchangeableQubitsGate): """A controlled swap gate. The Fredkin gate."""
[docs] def qubit_index_to_equivalence_group_key(self, index): return 0 if index == 0 else 1
def _pauli_expansion_(self) -> value.LinearDict[str]: return value.LinearDict({ 'III': 3/4, 'IXX': 1/4, 'IYY': 1/4, 'IZZ': 1/4, 'ZII': 1/4, 'ZXX': -1/4, 'ZYY': -1/4, 'ZZZ': -1/4, }) def _decompose_(self, qubits): c, t1, t2 = qubits # Hacky magic: special case based on adjacency. if hasattr(t1, 'is_adjacent'): if not t1.is_adjacent(t2): # Targets separated by control. return self._decompose_inside_control(t1, c, t2) if not t1.is_adjacent(c): # Control separated from t1 by t2. return self._decompose_outside_control(c, t2, t1) return self._decompose_outside_control(c, t1, t2) def _decompose_inside_control(self, target1: raw_types.Qid, control: raw_types.Qid, target2: raw_types.Qid ) -> op_tree.OP_TREE: """A decomposition assuming the control separates the targets. target1: ─@─X───────T──────@────────@─────────X───@─────X^-0.5─ │ │ │ │ │ │ control: ─X─@─X─────@─T^-1─X─@─T────X─@─X^0.5─@─@─X─@────────── │ │ │ │ │ │ target2: ─────@─H─T─X─T──────X─T^-1───X─T^-1────X───X─H─S^-1─── """ a, b, c = target1, control, target2 yield common_gates.CNOT(a, b) yield common_gates.CNOT(b, a) yield common_gates.CNOT(c, b) yield common_gates.H(c) yield common_gates.T(c) yield common_gates.CNOT(b, c) yield common_gates.T(a) yield common_gates.T(b)**-1 yield common_gates.T(c) yield common_gates.CNOT(a, b) yield common_gates.CNOT(b, c) yield common_gates.T(b) yield common_gates.T(c)**-1 yield common_gates.CNOT(a, b) yield common_gates.CNOT(b, c) yield pauli_gates.X(b)**0.5 yield common_gates.T(c)**-1 yield common_gates.CNOT(b, a) yield common_gates.CNOT(b, c) yield common_gates.CNOT(a, b) yield common_gates.CNOT(b, c) yield common_gates.H(c) yield common_gates.S(c)**-1 yield pauli_gates.X(a)**-0.5 def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray: return protocols.apply_unitary( controlled_gate.ControlledGate(common_gates.SWAP), protocols.ApplyUnitaryArgs( args.target_tensor, args.available_buffer, args.axes), default=NotImplemented) def _decompose_outside_control(self, control: raw_types.Qid, near_target: raw_types.Qid, far_target: raw_types.Qid ) -> op_tree.OP_TREE: """A decomposition assuming one of the targets is in the middle. control: ───T──────@────────@───@────────────@──────────────── │ │ │ │ near: ─X─T──────X─@─T^-1─X─@─X────@─X^0.5─X─@─X^0.5──────── │ │ │ │ │ far: ─@─Y^-0.5─T─X─T──────X─T^-1─X─T^-1────X─S─────X^-0.5─ """ a, b, c = control, near_target, far_target t = common_gates.T sweep_abc = [common_gates.CNOT(a, b), common_gates.CNOT(b, c)] yield common_gates.CNOT(c, b) yield pauli_gates.Y(c)**-0.5 yield t(a), t(b), t(c) yield sweep_abc yield t(b) ** -1, t(c) yield sweep_abc yield t(c) ** -1 yield sweep_abc yield t(c) ** -1 yield pauli_gates.X(b)**0.5 yield sweep_abc yield common_gates.S(c) yield pauli_gates.X(b)**0.5 yield pauli_gates.X(c)**-0.5 def _has_unitary_(self) -> bool: return True def _unitary_(self) -> np.ndarray: return linalg.block_diag(np.diag([1, 1, 1, 1, 1]), np.array([[0, 1], [1, 0]]), np.diag([1])) def _circuit_diagram_info_(self, args: protocols.CircuitDiagramInfoArgs ) -> protocols.CircuitDiagramInfo: if not args.use_unicode_characters: return protocols.CircuitDiagramInfo(('@', 'swap', 'swap')) return protocols.CircuitDiagramInfo(('@', '×', '×')) def _qasm_(self, args: protocols.QasmArgs, qubits: Tuple[raw_types.Qid, ...]) -> Optional[str]: args.validate_version('2.0') return args.format('cswap {0},{1},{2};\n', qubits[0], qubits[1], qubits[2]) def __str__(self) -> str: return 'FREDKIN' def __repr__(self) -> str: return 'cirq.FREDKIN'
# Explicit names. CCZ = CCZPowGate() CCX = CCXPowGate() CSWAP = CSwapGate() # Common names. TOFFOLI = CCX CCNOT = TOFFOLI FREDKIN = CSWAP