Source code for cirq.ops.gate_features

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

"""Marker classes for indicating which additional features gates support.

For example: some gates are reversible, some have known matrices, etc.
"""

from typing import Any, Dict, Optional, Sequence, Tuple, Iterable, TypeVar

import string
import numpy as np

from cirq import abc
from cirq.ops import op_tree
from cirq.ops import raw_types
from cirq.study import ParamResolver


[docs]class InterchangeableQubitsGate(metaclass=abc.ABCMeta): """Indicates operations should be equal under some qubit permutations."""
[docs] def qubit_index_to_equivalence_group_key(self, index: int) -> int: """Returns a key that differs between non-interchangeable qubits.""" return 0
[docs]class ReversibleEffect(metaclass=abc.ABCMeta): """A gate whose effect can be undone in a known way."""
[docs] @abc.abstractmethod def inverse(self) -> 'ReversibleEffect': """Returns a gate with an exactly opposite effect."""
TSelf_ExtrapolatableEffect = TypeVar('TSelf_ExtrapolatableEffect', bound='ExtrapolatableEffect')
[docs]class ExtrapolatableEffect(ReversibleEffect, metaclass=abc.ABCMeta): """A gate whose effect can be continuously scaled up/down/negated."""
[docs] @abc.abstractmethod def extrapolate_effect(self: TSelf_ExtrapolatableEffect, factor: float ) -> TSelf_ExtrapolatableEffect: """Augments, diminishes, or reverses the effect of the receiving gate. Args: factor: The amount to scale the gate's effect by. Returns: A gate equivalent to applying the receiving gate 'factor' times. """
def __pow__(self: TSelf_ExtrapolatableEffect, power: float ) -> TSelf_ExtrapolatableEffect: """Extrapolates the effect of the gate. Note that there are cases where (G**a)**b != G**(a*b). For example, start with a 90 degree rotation then cube it then raise it to a non-integer power such as 3/2. Assuming that rotations are always normalized into the range (-180, 180], note that: ((rot 90)**3)**1.5 = (rot 270)**1.5 = (rot -90)**1.5 = rot -135 but (rot 90)**(3*1.5) = (rot 90)**4.5 = rot 405 = rot 35 Because normalization discards the winding number. Args: power: The extrapolation factor. Returns: A gate with the extrapolated effect. """ return self.extrapolate_effect(power)
[docs] def inverse(self: TSelf_ExtrapolatableEffect) -> TSelf_ExtrapolatableEffect: return self.extrapolate_effect(-1)
[docs]class CompositeOperation(metaclass=abc.ABCMeta): """An operation with a known decomposition into simpler operations."""
[docs] @abc.abstractmethod def default_decompose(self) -> op_tree.OP_TREE: """Yields simpler operations for performing the receiving operation."""
[docs]class CompositeGate(metaclass=abc.ABCMeta): """A gate with a known decomposition into simpler gates."""
[docs] @abc.abstractmethod def default_decompose( self, qubits: Sequence[raw_types.QubitId]) -> op_tree.OP_TREE: """Yields operations for performing this gate on the given qubits. Args: qubits: The qubits the gate should be applied to. """
[docs]class KnownMatrix(metaclass=abc.ABCMeta): """An effect that can be described by a matrix."""
[docs] @abc.abstractmethod def matrix(self) -> np.ndarray: """The unitary matrix of the gate/operation. The matrix order is implicit for both gates and operations. For a gate, the matrix must be in order with respect to the list of qubits that the gate is applied to. For an operation, the order must be with respect to its qubits attribute. The qubit-to-amplitude order mapping matches the ordering of numpy.kron(A, B), where A is a qubit earlier in the list than the qubit B. For example, when applying a CNOT gate the control qubit goes first and so the CNOT gate's matrix is: 1 _ _ _ _ 1 _ _ _ _ _ 1 _ _ 1 _ """
class TextDiagramInfoArgs: """ Attributes: known_qubits: The qubits the gate is being applied to. None means this information is not known by the caller. known_qubit_count: The number of qubits the gate is being applied to None means this information is not known by the caller. use_unicode_characters: If true, the wire symbols are permitted to include unicode characters (as long as they work well in fixed width fonts). If false, use only ascii characters. ASCII is preferred in cases where UTF8 support is done poorly, or where the fixed-width font being used to show the diagrams does not properly handle unicode characters. precision: The number of digits after the decimal to show for numbers in the text diagram. None means use full precision. """ UNINFORMED_DEFAULT = None # type: TextDiagramInfoArgs def __init__(self, known_qubits: Optional[Tuple[raw_types.QubitId, ...]], known_qubit_count: Optional[int], use_unicode_characters: bool, precision: Optional[int]) -> None: self.known_qubits = known_qubits self.known_qubit_count = known_qubit_count self.use_unicode_characters = use_unicode_characters self.precision = precision TextDiagramInfoArgs.UNINFORMED_DEFAULT = TextDiagramInfoArgs( known_qubits=None, known_qubit_count=None, use_unicode_characters=True, precision=3) class TextDiagramInfo: def __init__(self, wire_symbols: Tuple[str, ...], exponent: Any = 1) -> None: """ Args: wire_symbols: The symbols that should be shown on the qubits affected by this operation. Must match the number of qubits that the operation is applied to. exponent: An optional convenience value that will be appended onto an operation's final gate symbol with a caret in front (unless it's equal to 1). For example, the square root of X gate has a text diagram exponent of 0.5 and symbol of 'X' so it is drawn as 'X^0.5'. """ self.wire_symbols = wire_symbols self.exponent = exponent def _eq_tuple(self): return TextDiagramInfo, self.wire_symbols, self.exponent def __eq__(self, other): if not isinstance(other, type(self)): return NotImplemented return self._eq_tuple() == other._eq_tuple() def __ne__(self, other): return not self == other def __hash__(self): return hash(self._eq_tuple()) def __repr__(self): return 'TextDiagramInfo(wire_symbols={!r}, exponent={!r})'.format( self.wire_symbols, self.exponent)
[docs]class TextDiagrammable(metaclass=abc.ABCMeta): """A thing which can be printed in a text diagram."""
[docs] @abc.abstractmethod def text_diagram_info(self, args: TextDiagramInfoArgs) -> TextDiagramInfo: """Describes how to draw something in a text diagram. Args: args: A TextDiagramInfoArgs instance encapsulating various pieces of information (e.g. how many qubits are we being applied to) as well as user options (e.g. whether to avoid unicode characters). Returns: A TextDiagramInfo instance describing what to print. """
TSelf_PhaseableEffect = TypeVar('TSelf_PhaseableEffect', bound='PhaseableEffect')
[docs]class PhaseableEffect(metaclass=abc.ABCMeta): """An effect that can be phased around the Z axis of target qubits."""
[docs] @abc.abstractmethod def phase_by(self: TSelf_PhaseableEffect, phase_turns: float, qubit_index: int) -> TSelf_PhaseableEffect: """Returns a phased version of the effect. For example, an X gate phased by 90 degrees would be a Y gate. Args: phase_turns: The amount to phase the gate, in fractions of a whole turn. qubit_index: The index of the target qubit the phasing applies to. Returns: The phased gate or operation. """
[docs]class BoundedEffect(metaclass=abc.ABCMeta): """An effect with known bounds on how easy it is to detect. Used when deciding whether or not an operation is negligible. For example, the trace distance between the states before and after a Z**0.00000001 operation is very close to 0, so it would typically be considered negligible. """
[docs] @abc.abstractmethod def trace_distance_bound(self) -> float: """A maximum on the trace distance between this effect's input/output. Generally this method is used when deciding whether to keep gates, so only the behavior near 0 is important. Approximations that overestimate the maximum trace distance are permitted. Even ones that exceed 1. Underestimates are not permitted. """
[docs]class SingleQubitGate(raw_types.Gate, metaclass=abc.ABCMeta): """A gate that must be applied to exactly one qubit."""
[docs] def validate_args(self, qubits): if len(qubits) != 1: raise ValueError( 'Single-qubit gate applied to multiple qubits: {}({})'. format(self, qubits))
[docs] def on_each(self, targets: Iterable[raw_types.QubitId]) -> op_tree.OP_TREE: """Returns a list of operations apply this gate to each of the targets. Args: targets: The qubits to apply this gate to. Returns: Operations applying this gate to the target qubits. """ return [self.on(target) for target in targets]
[docs]class TwoQubitGate(raw_types.Gate, metaclass=abc.ABCMeta): """A gate that must be applied to exactly two qubits."""
[docs] def validate_args(self, qubits): if len(qubits) != 2: raise ValueError( 'Two-qubit gate not applied to two qubits: {}({})'. format(self, qubits))
class ThreeQubitGate(raw_types.Gate, metaclass=abc.ABCMeta): """A gate that must be applied to exactly three qubits.""" def validate_args(self, qubits): if len(qubits) != 3: raise ValueError( 'Three-qubit gate not applied to three qubits: {}({})'. format(self, qubits)) TSelf_ParameterizableEffect = TypeVar('TSelf_ParameterizableEffect', bound='ParameterizableEffect')
[docs]class ParameterizableEffect(metaclass=abc.ABCMeta): """An effect that can be parameterized by Symbols."""
[docs] @abc.abstractmethod def is_parameterized(self) -> bool: """Whether the effect is parameterized. Returns True if the gate has any unresolved Symbols and False otherwise. """
[docs] @abc.abstractmethod def with_parameters_resolved_by(self: TSelf_ParameterizableEffect, param_resolver: ParamResolver ) -> TSelf_ParameterizableEffect: """Resolve the parameters in the effect. Returns a gate or operation of the same type, but with all Symbols replaced with floats according to the given ParamResolver. """
class QasmOutputArgs(string.Formatter): """ Attributes: precision: The number of digits after the decimal to show for numbers in the text diagram. version: The QASM version to output. QasmConvertableGate/Operation may return different text depending on version. qubit_id_map: A dictionary mapping qubits to qreg QASM identifiers. meas_key_id_map: A dictionary mapping measurement keys to creg QASM identifiers. """ def __init__(self, precision: int = 10, version: str = '2.0', qubit_id_map: Dict[raw_types.QubitId, str] = None, meas_key_id_map: Dict[str, str] = None, ) -> None: self.precision = precision self.version = version self.qubit_id_map = {} if qubit_id_map is None else qubit_id_map self.meas_key_id_map = ({} if meas_key_id_map is None else meas_key_id_map) def format_field(self, value: Any, spec: str) -> str: """Method of string.Formatter that specifies the output of format().""" if isinstance(value, float): value = round(value, self.precision) if spec == 'half_turns': value = 'pi*{}'.format(value) if value != 0 else '0' spec = '' elif isinstance(value, raw_types.QubitId): value = self.qubit_id_map[value] elif isinstance(value, str) and spec == 'meas': value = self.meas_key_id_map[value] spec = '' return super().format_field(value, spec) def validate_version(self, *supported_versions: str) -> None: if self.version not in supported_versions: raise ValueError('QASM version {} output is not supported.'.format( self.version))
[docs]class QasmConvertableGate(metaclass=abc.ABCMeta): """A gate that knows its representation in QASM."""
[docs] @abc.abstractmethod def known_qasm_output(self, qubits: Tuple[raw_types.QubitId, ...], args: QasmOutputArgs) -> Optional[str]: """Returns lines of QASM output representing the gate on the given qubits or None if a simple conversion is not possible. """
[docs]class QasmConvertableOperation(metaclass=abc.ABCMeta): """An operation that knows its representation in QASM."""
[docs] @abc.abstractmethod def known_qasm_output(self, args: QasmOutputArgs) -> Optional[str]: """Returns lines of QASM output representing the operation or None if a simple conversion is not possible."""