Source code for cirq.ops.matrix_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.

"""Quantum gates defined by a matrix."""

from typing import cast, Any

import numpy as np

from cirq import linalg, protocols
from cirq.ops import raw_types


def _phase_matrix(turns: float) -> np.ndarray:
    return np.diag([1, np.exp(2j * np.pi * turns)])


[docs]class SingleQubitMatrixGate(raw_types.Gate): """A 1-qubit gate defined by its matrix. More general than specialized classes like `ZPowGate`, but more expensive and more float-error sensitive to work with (due to using eigendecompositions). """
[docs] def __init__(self, matrix: np.ndarray) -> None: """ Initializes the 2-qubit matrix gate. Args: matrix: The matrix that defines the gate. """ if matrix.shape != (2, 2) or not linalg.is_unitary(matrix): raise ValueError('Not a 2x2 unitary matrix: {}'.format(matrix)) self._matrix = matrix
[docs] def validate_args(self, qubits): if len(qubits) != 1: raise ValueError( 'Single-qubit gate applied to multiple qubits: {}({})'.format( self, qubits))
def __pow__(self, exponent: Any) -> 'SingleQubitMatrixGate': if not isinstance(exponent, (int, float)): return NotImplemented e = cast(float, exponent) new_mat = linalg.map_eigenvalues(self._matrix, lambda b: b**e) return SingleQubitMatrixGate(new_mat) def _trace_distance_bound_(self): vals = np.linalg.eigvals(self._matrix) rotation_angle = abs(np.angle(vals[0] / vals[1])) return rotation_angle * 1.2 def _phase_by_(self, phase_turns: float, qubit_index: int): z = _phase_matrix(phase_turns) phased_matrix = z.dot(self._matrix).dot(np.conj(z.T)) return SingleQubitMatrixGate(phased_matrix) def _has_unitary_(self) -> bool: return True def _unitary_(self) -> np.ndarray: return np.array(self._matrix) def _circuit_diagram_info_(self, args: protocols.CircuitDiagramInfoArgs ) -> protocols.CircuitDiagramInfo: return protocols.CircuitDiagramInfo( wire_symbols=(_matrix_to_diagram_symbol(self._matrix, args),)) def __hash__(self): vals = tuple(v for _, v in np.ndenumerate(self._matrix)) return hash((SingleQubitMatrixGate, vals))
[docs] def approx_eq(self, other, ignore_global_phase=True): if not isinstance(other, type(self)): return NotImplemented cmp = (linalg.allclose_up_to_global_phase if ignore_global_phase else np.allclose) return cmp(self._matrix, other._matrix)
def __eq__(self, other): if not isinstance(other, type(self)): return NotImplemented return np.alltrue(self._matrix == other._matrix) def __ne__(self, other): return not self == other def __repr__(self): return 'cirq.SingleQubitMatrixGate({})'.format( _numpy_array_repr(self._matrix)) def __str__(self): return str(self._matrix.round(3))
[docs]class TwoQubitMatrixGate(raw_types.Gate): """A 2-qubit gate defined only by its matrix. More general than specialized classes like `CZPowGate`, but more expensive and more float-error sensitive to work with (due to using eigendecompositions). """
[docs] def __init__(self, matrix: np.ndarray) -> None: """ Initializes the 2-qubit matrix gate. Args: matrix: The matrix that defines the gate. """ if matrix.shape != (4, 4) or not linalg.is_unitary(matrix): raise ValueError('Not a 4x4 unitary matrix: {}'.format(matrix)) self._matrix = matrix
[docs] def validate_args(self, qubits): if len(qubits) != 2: raise ValueError( 'Two-qubit gate not applied to two qubits: {}({})'.format( self, qubits))
def __pow__(self, exponent: Any) -> 'TwoQubitMatrixGate': if not isinstance(exponent, (int, float)): return NotImplemented e = cast(float, exponent) new_mat = linalg.map_eigenvalues(self._matrix, lambda b: b**e) return TwoQubitMatrixGate(new_mat) def _phase_by_(self, phase_turns: float, qubit_index: int): i = np.eye(2) z = _phase_matrix(phase_turns) z2 = np.kron(i, z) if qubit_index else np.kron(z, i) phased_matrix = z2.dot(self._matrix).dot(np.conj(z2.T)) return TwoQubitMatrixGate(phased_matrix)
[docs] def approx_eq(self, other, ignore_global_phase=True): if not isinstance(other, type(self)): return NotImplemented cmp = (linalg.allclose_up_to_global_phase if ignore_global_phase else np.allclose) return cmp(self._matrix, other._matrix)
def _unitary_(self) -> np.ndarray: return np.array(self._matrix) def _circuit_diagram_info_(self, args: protocols.CircuitDiagramInfoArgs ) -> protocols.CircuitDiagramInfo: return protocols.CircuitDiagramInfo( wire_symbols=(_matrix_to_diagram_symbol(self._matrix, args), '#2')) def __hash__(self): vals = tuple(v for _, v in np.ndenumerate(self._matrix)) return hash((SingleQubitMatrixGate, vals)) def __eq__(self, other): if not isinstance(other, type(self)): return NotImplemented return np.alltrue(self._matrix == other._matrix) def __ne__(self, other): return not self == other def __repr__(self): return 'cirq.TwoQubitMatrixGate({})'.format( _numpy_array_repr(self._matrix)) def __str__(self): return str(self._matrix.round(3))
def _matrix_to_diagram_symbol(matrix: np.ndarray, args: protocols.CircuitDiagramInfoArgs) -> str: if args.precision is not None: matrix = matrix.round(args.precision) result = str(matrix) if args.use_unicode_characters: lines = result.split('\n') for i in range(len(lines)): lines[i] = lines[i].replace('[[', '') lines[i] = lines[i].replace(' [', '') lines[i] = lines[i].replace(']', '') w = max(len(line) for line in lines) for i in range(len(lines)): lines[i] = '│' + lines[i].ljust(w) + '│' lines.insert(0, '┌' + ' ' * w + '┐') lines.append('└' + ' ' * w + '┘') result = '\n'.join(lines) return result def _numpy_array_repr(arr: np.ndarray) -> str: return 'np.array({!r})'.format(arr.tolist())