Exemple #1
0
# See the License for the specific language governing permissions and
# limitations under the License.
"""Code to handle density matrices."""

from typing import List, Optional, TYPE_CHECKING, Tuple

import numpy as np

from cirq import linalg, qis, value
from cirq._compat import deprecated

if TYPE_CHECKING:
    import cirq

to_valid_density_matrix = deprecated(
    deadline='v0.9', fix='Use cirq.to_valid_density_matrix instead.')(
        qis.to_valid_density_matrix)
von_neumann_entropy = deprecated(deadline='v0.9',
                                 fix='Use cirq.von_neumann_entropy instead.')(
                                     qis.von_neumann_entropy)


def sample_density_matrix(
        density_matrix: np.ndarray,
        indices: List[int],
        *,  # Force keyword arguments
        qid_shape: Optional[Tuple[int, ...]] = None,
        repetitions: int = 1,
        seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None) -> np.ndarray:
    """Samples repeatedly from measurements in the computational basis.
Exemple #2
0
    if dtype and state_vector.dtype != dtype:
        raise ValueError(
            'state_vector has invalid dtype. Expected {} but was {}'.format(
                dtype, state_vector.dtype))
    if state_vector.size != np.prod(qid_shape, dtype=int):
        raise ValueError(
            'state_vector has incorrect size. Expected {} but was {}.'.format(
                np.prod(qid_shape, dtype=int), state_vector.size))
    norm = np.sum(np.abs(state_vector)**2)
    if not np.isclose(norm, 1, atol=atol):
        raise ValueError(
            'State_vector is not normalized instead had norm {}'.format(norm))


validate_normalized_state = deprecated(
    deadline='v0.10.0',
    fix='Use `cirq.validate_normalized_state_vector` instead.')(
        validate_normalized_state_vector)


@deprecated_parameter(
    deadline='v0.10.0',
    fix='Use state_vector instead.',
    parameter_desc='state',
    match=lambda args, kwargs: 'state' in kwargs,
    rewrite=lambda args, kwargs: (
        args,
        {('state_vector' if k == 'state' else k): v
         for k, v in kwargs.items()},
    ),
)
def validate_qid_shape(
Exemple #3
0
    Tuple,
    TYPE_CHECKING,
)

import abc
import numpy as np

from cirq import linalg, ops, qis, value
from cirq.sim import simulator
from cirq._compat import deprecated

if TYPE_CHECKING:
    import cirq

bloch_vector_from_state_vector = deprecated(
    deadline='v0.9', fix='Use cirq.bloch_vector_from_state_vector instead.')(
        qis.bloch_vector_from_state_vector)
density_matrix_from_state_vector = deprecated(
    deadline='v0.9', fix='Use cirq.density_matrix_from_state_vector instead.')(
        qis.density_matrix_from_state_vector)
dirac_notation = deprecated(deadline='v0.9',
                            fix='Use cirq.dirac_notation instead.')(
                                qis.dirac_notation)
to_valid_state_vector = deprecated(
    deadline='v0.9',
    fix='Use cirq.to_valid_state_vector instead.')(qis.to_valid_state_vector)
validate_normalized_state = deprecated(
    deadline='v0.9', fix='Use cirq.validate_normalized_state instead.')(
        qis.validate_normalized_state)
STATE_VECTOR_LIKE = qis.STATE_VECTOR_LIKE
Exemple #4
0
            "because it is not unitary. "
            "Maybe you wanted `cirq.final_density_matrix`?\n"
            "\n"
            "Program: {!r}".format(circuit_like))

    result = sparse_simulator.Simulator(dtype=dtype, seed=seed).simulate(
        program=circuit_like,
        initial_state=initial_state,
        qubit_order=qubit_order,
        param_resolver=param_resolver)

    return cast(sparse_simulator.SparseSimulatorStep, result).state_vector()


final_wavefunction = deprecated(
    deadline='v0.10.0',
    fix='Use `cirq.final_state_vector` instead.')(final_state_vector)


def sample_sweep(
        program: 'cirq.Circuit',
        params: study.Sweepable,
        *,
        noise: 'cirq.NOISE_MODEL_LIKE' = None,
        repetitions: int = 1,
        dtype: Type[np.number] = np.complex64,
        seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None
) -> List[study.TrialResult]:
    """Runs the supplied Circuit, mimicking quantum hardware.

    In contrast to run, this allows for sweeping over different parameter
Exemple #5
0
class SingleQubitCliffordGate(gate_features.SingleQubitGate):
    """Any single qubit Clifford rotation."""
    I = _pretend_initialized()
    H = _pretend_initialized()
    X = _pretend_initialized()
    Y = _pretend_initialized()
    Z = _pretend_initialized()
    X_sqrt = _pretend_initialized()
    Y_sqrt = _pretend_initialized()
    Z_sqrt = _pretend_initialized()
    X_nsqrt = _pretend_initialized()
    Y_nsqrt = _pretend_initialized()
    Z_nsqrt = _pretend_initialized()

    def __init__(self, *, _rotation_map: Dict[Pauli, PauliTransform],
                 _inverse_map: Dict[Pauli, PauliTransform]) -> None:
        self._rotation_map = _rotation_map
        self._inverse_map = _inverse_map

    @staticmethod
    def from_xz_map(x_to: Tuple[Pauli, bool],
                    z_to: Tuple[Pauli, bool]) -> 'SingleQubitCliffordGate':
        """Returns a SingleQubitCliffordGate for the specified transforms.
        The Y transform is derived from the X and Z.

        Args:
            x_to: Which Pauli to transform X to and if it should negate.
            z_to: Which Pauli to transform Z to and if it should negate.
        """
        return SingleQubitCliffordGate.from_double_map(x_to=x_to, z_to=z_to)

    @staticmethod
    def from_single_map(
        pauli_map_to: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None,
        *,
        x_to: Optional[Tuple[Pauli, bool]] = None,
        y_to: Optional[Tuple[Pauli, bool]] = None,
        z_to: Optional[Tuple[Pauli,
                             bool]] = None) -> 'SingleQubitCliffordGate':
        """Returns a SingleQubitCliffordGate for the
        specified transform with a 90 or 180 degree rotation.

        The arguments are exclusive, only one may be specified.

        Args:
            pauli_map_to: A dictionary with a single key value pair describing
                the transform.
            x_to: The transform from cirq.X
            y_to: The transform from cirq.Y
            z_to: The transform from cirq.Z
        """
        rotation_map = SingleQubitCliffordGate._validate_map_input(
            1, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to)
        (trans_from, (trans_to, flip)), = tuple(rotation_map.items())
        if trans_from == trans_to:
            trans_from2 = Pauli.by_relative_index(trans_to, 1)  # 1 or 2 work
            trans_to2 = Pauli.by_relative_index(trans_from, 1)
            flip2 = False
        else:
            trans_from2 = trans_to
            trans_to2 = trans_from
            flip2 = not flip
        rotation_map[trans_from2] = PauliTransform(trans_to2, flip2)
        return SingleQubitCliffordGate.from_double_map(
            cast(Dict[Pauli, Tuple[Pauli, bool]], rotation_map))

    @staticmethod
    def from_double_map(
        pauli_map_to: Optional[Dict[Pauli, Tuple[Pauli, bool]]] = None,
        *,
        x_to: Optional[Tuple[Pauli, bool]] = None,
        y_to: Optional[Tuple[Pauli, bool]] = None,
        z_to: Optional[Tuple[Pauli,
                             bool]] = None) -> 'SingleQubitCliffordGate':
        """Returns a SingleQubitCliffordGate for the
        specified transform with a 90 or 180 degree rotation.

        Either pauli_map_to or two of (x_to, y_to, z_to) may be specified.

        Args:
            pauli_map_to: A dictionary with two key value pairs describing
                two transforms.
            x_to: The transform from cirq.X
            y_to: The transform from cirq.Y
            z_to: The transform from cirq.Z
        """
        rotation_map = SingleQubitCliffordGate._validate_map_input(
            2, pauli_map_to, x_to=x_to, y_to=y_to, z_to=z_to)
        (from1, trans1), (from2, trans2) = tuple(rotation_map.items())
        from3 = from1.third(from2)
        to3 = trans1.to.third(trans2.to)
        flip3 = (trans1.flip ^ trans2.flip ^ ((from1 < from2) !=
                                              (trans1.to < trans2.to)))
        rotation_map[from3] = PauliTransform(to3, flip3)
        inverse_map = {
            to: PauliTransform(frm, flip)
            for frm, (to, flip) in rotation_map.items()
        }
        return SingleQubitCliffordGate(_rotation_map=rotation_map,
                                       _inverse_map=inverse_map)

    @staticmethod
    def from_pauli(pauli: Pauli,
                   sqrt: bool = False) -> 'SingleQubitCliffordGate':
        prev_pauli = Pauli.by_relative_index(pauli, -1)
        next_pauli = Pauli.by_relative_index(pauli, 1)
        if sqrt:
            rotation_map = {
                prev_pauli: PauliTransform(next_pauli, True),
                pauli: PauliTransform(pauli, False),
                next_pauli: PauliTransform(prev_pauli, False)
            }
        else:
            rotation_map = {
                prev_pauli: PauliTransform(prev_pauli, True),
                pauli: PauliTransform(pauli, False),
                next_pauli: PauliTransform(next_pauli, True)
            }
        inverse_map = {
            to: PauliTransform(frm, flip)
            for frm, (to, flip) in rotation_map.items()
        }
        return SingleQubitCliffordGate(_rotation_map=rotation_map,
                                       _inverse_map=inverse_map)

    @staticmethod
    def from_quarter_turns(pauli: Pauli,
                           quarter_turns: int) -> 'SingleQubitCliffordGate':
        quarter_turns = quarter_turns % 4
        if quarter_turns == 0:
            return SingleQubitCliffordGate.I
        if quarter_turns == 1:
            return SingleQubitCliffordGate.from_pauli(pauli, True)
        if quarter_turns == 2:
            return SingleQubitCliffordGate.from_pauli(pauli)

        return SingleQubitCliffordGate.from_pauli(pauli, True)**-1

    @staticmethod
    def _validate_map_input(
            required_transform_count: int,
            pauli_map_to: Optional[Dict[Pauli, Tuple[Pauli, bool]]],
            x_to: Optional[Tuple[Pauli, bool]], y_to: Optional[Tuple[Pauli,
                                                                     bool]],
            z_to: Optional[Tuple[Pauli, bool]]) -> Dict[Pauli, PauliTransform]:
        if pauli_map_to is None:
            xyz_to = {
                pauli_gates.X: x_to,
                pauli_gates.Y: y_to,
                pauli_gates.Z: z_to
            }
            pauli_map_to = {
                cast(Pauli, p): trans
                for p, trans in xyz_to.items() if trans is not None
            }
        elif x_to is not None or y_to is not None or z_to is not None:
            raise ValueError('{} can take either pauli_map_to or a combination'
                             ' of x_to, y_to, and z_to but both were given')
        if len(pauli_map_to) != required_transform_count:
            raise ValueError(
                'Method takes {} transform{} but {} {} given'.format(
                    required_transform_count,
                    '' if required_transform_count == 1 else 's',
                    len(pauli_map_to),
                    'was' if len(pauli_map_to) == 1 else 'were'))
        if (len(set(
            (to for to, _ in pauli_map_to.values()))) != len(pauli_map_to)):
            raise ValueError('A rotation cannot map two Paulis to the same')
        return {
            frm: PauliTransform(to, flip)
            for frm, (to, flip) in pauli_map_to.items()
        }

    def transform(self, pauli: Pauli) -> PauliTransform:
        return self._rotation_map[pauli]

    def _value_equality_values_(self):
        return (self.transform(pauli_gates.X), self.transform(pauli_gates.Y),
                self.transform(pauli_gates.Z))

    def __pow__(self, exponent) -> 'SingleQubitCliffordGate':
        if exponent == 0.5 or exponent == -0.5:
            return SQRT_EXP_MAP[exponent][self]
        if exponent != -1:
            return NotImplemented

        return SingleQubitCliffordGate(_rotation_map=self._inverse_map,
                                       _inverse_map=self._rotation_map)

    def _commutes_(
            self,
            other: Any,
            *,
            atol: Union[int, float] = 1e-8) -> Union[bool, NotImplementedType]:
        if isinstance(other, SingleQubitCliffordGate):
            return self.commutes_with_single_qubit_gate(other)
        if isinstance(other, Pauli):
            return self.commutes_with_pauli(other)
        return NotImplemented

    commutes_with = deprecated(
        deadline='v0.7.0', fix='Use `cirq.commutes()` instead.')(_commutes_)

    def commutes_with_single_qubit_gate(self,
                                        gate: 'SingleQubitCliffordGate') \
                                        -> bool:
        """Tests if the two circuits would be equivalent up to global phase:
            --self--gate-- and --gate--self--"""
        for pauli0 in (pauli_gates.X, pauli_gates.Z):
            pauli1, flip1 = self.transform(cast(Pauli, pauli0))
            pauli2, flip2 = gate.transform(cast(Pauli, pauli1))
            pauli3, flip3 = self._inverse_map[pauli2]
            pauli4, flip4 = gate._inverse_map[pauli3]
            if pauli4 != pauli0 or (flip1 ^ flip2 ^ flip3 ^ flip4):
                return False
        return True

    def commutes_with_pauli(self, pauli: Pauli) -> bool:
        to, flip = self.transform(pauli)
        return (to == pauli and not flip)

    def merged_with(self,
                    second: 'SingleQubitCliffordGate') \
                    -> 'SingleQubitCliffordGate':
        """Returns a SingleQubitCliffordGate such that the circuits
            --output-- and --self--second--
        are equivalent up to global phase."""
        x_intermediate_pauli, x_flip1 = self.transform(pauli_gates.X)
        x_final_pauli, x_flip2 = second.transform(x_intermediate_pauli)
        z_intermediate_pauli, z_flip1 = self.transform(pauli_gates.Z)
        z_final_pauli, z_flip2 = second.transform(z_intermediate_pauli)
        return SingleQubitCliffordGate.from_xz_map(
            (x_final_pauli, x_flip1 ^ x_flip2),
            (z_final_pauli, z_flip1 ^ z_flip2))

    def _has_unitary_(self) -> bool:
        return True

    def _unitary_(self) -> np.ndarray:
        mat = np.eye(2)
        qubit = named_qubit.NamedQubit('arbitrary')
        for op in protocols.decompose_once_with_qubits(self, (qubit, )):
            mat = protocols.unitary(op).dot(mat)
        return mat

    def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE':
        qubit, = qubits
        if self == SingleQubitCliffordGate.H:
            return common_gates.H(qubit),
        rotations = self.decompose_rotation()
        return tuple(r.on(qubit)**(qt / 2) for r, qt in rotations)

    def decompose_rotation(self) -> Sequence[Tuple[Pauli, int]]:
        """Returns ((first_rotation_axis, first_rotation_quarter_turns), ...)

        This is a sequence of zero, one, or two rotations."""
        x_rot = self.transform(pauli_gates.X)
        y_rot = self.transform(pauli_gates.Y)
        z_rot = self.transform(pauli_gates.Z)
        whole_arr = (x_rot.to == pauli_gates.X, y_rot.to == pauli_gates.Y,
                     z_rot.to == pauli_gates.Z)
        num_whole = sum(whole_arr)
        flip_arr = (x_rot.flip, y_rot.flip, z_rot.flip)
        num_flip = sum(flip_arr)
        if num_whole == 3:
            if num_flip == 0:
                # Gate is identity
                return []

            # 180 rotation about some axis
            pauli = Pauli.by_index(flip_arr.index(False))
            return [(pauli, 2)]
        if num_whole == 1:
            index = whole_arr.index(True)
            pauli = Pauli.by_index(index)
            next_pauli = Pauli.by_index(index + 1)
            flip = flip_arr[index]
            output = []
            if flip:
                # 180 degree rotation
                output.append((next_pauli, 2))
            # 90 degree rotation about some axis
            if self.transform(next_pauli).flip:
                # Negative 90 degree rotation
                output.append((pauli, -1))
            else:
                # Positive 90 degree rotation
                output.append((pauli, 1))
            return output
        elif num_whole == 0:
            # Gate is a 120 degree rotation
            if x_rot.to == pauli_gates.Y:
                return [(pauli_gates.X, -1 if y_rot.flip else 1),
                        (pauli_gates.Z, -1 if x_rot.flip else 1)]

            return [(pauli_gates.Z, 1 if y_rot.flip else -1),
                    (pauli_gates.X, 1 if z_rot.flip else -1)]
        # coverage: ignore
        assert False, ('Impossible condition where this gate only rotates one'
                       ' Pauli to a different Pauli.')

    def equivalent_gate_before(self, after: 'SingleQubitCliffordGate') \
        -> 'SingleQubitCliffordGate':
        """Returns a SingleQubitCliffordGate such that the circuits
            --output--self-- and --self--gate--
        are equivalent up to global phase."""
        return self.merged_with(after).merged_with(self**-1)

    def __repr__(self):
        return 'cirq.SingleQubitCliffordGate(X:{}{!s}, Y:{}{!s}, Z:{}{!s})' \
            .format(
                '+-'[self.transform(pauli_gates.X).flip],
                     self.transform(pauli_gates.X).to,
                '+-'[self.transform(pauli_gates.Y).flip],
                     self.transform(pauli_gates.Y).to,
                '+-'[self.transform(pauli_gates.Z).flip],
                     self.transform(pauli_gates.Z).to)

    def _circuit_diagram_info_(
            self,
            args: 'cirq.CircuitDiagramInfoArgs') -> 'cirq.CircuitDiagramInfo':
        well_known_map = {
            SingleQubitCliffordGate.I: 'I',
            SingleQubitCliffordGate.H: 'H',
            SingleQubitCliffordGate.X: 'X',
            SingleQubitCliffordGate.Y: 'Y',
            SingleQubitCliffordGate.Z: 'Z',
            SingleQubitCliffordGate.X_sqrt: 'X',
            SingleQubitCliffordGate.Y_sqrt: 'Y',
            SingleQubitCliffordGate.Z_sqrt: 'Z',
            SingleQubitCliffordGate.X_nsqrt: 'X',
            SingleQubitCliffordGate.Y_nsqrt: 'Y',
            SingleQubitCliffordGate.Z_nsqrt: 'Z',
        }
        if self in well_known_map:
            symbol = well_known_map[self]
        else:
            rotations = self.decompose_rotation()
            symbol = '-'.join(
                str(r) + ('^' + str(qt / 2)) * (qt % 4 != 2)
                for r, qt in rotations)
            symbol = '({})'.format(symbol)
        return protocols.CircuitDiagramInfo(
            wire_symbols=(symbol, ),
            exponent={
                SingleQubitCliffordGate.X_sqrt: 0.5,
                SingleQubitCliffordGate.Y_sqrt: 0.5,
                SingleQubitCliffordGate.Z_sqrt: 0.5,
                SingleQubitCliffordGate.X_nsqrt: -0.5,
                SingleQubitCliffordGate.Y_nsqrt: -0.5,
                SingleQubitCliffordGate.Z_nsqrt: -0.5,
            }.get(self, 1))
Exemple #6
0
        ret_shape = tuple(2 for _ in range(len(keep_indices)))

    rho = np.kron(
        np.conj(state_vector.reshape(-1, 1)).T,
        state_vector.reshape(-1, 1)).reshape(
            (2, 2) * int(np.log2(state_vector.size)))
    keep_rho = partial_trace(rho, keep_indices).reshape((keep_dims, ) * 2)
    eigvals, eigvecs = np.linalg.eigh(keep_rho)
    mixture = tuple(zip(eigvals,
                        [vec.reshape(ret_shape) for vec in eigvecs.T]))
    return tuple([(float(p[0]), p[1]) for p in mixture
                  if not protocols.approx_eq(p[0], 0.0)])


wavefunction_partial_trace_as_mixture = deprecated(
    deadline='v0.10.0',
    fix='Use `cirq.partial_trace_of_state_vector_as_mixture` instead.')(
        partial_trace_of_state_vector_as_mixture)


@deprecated_parameter(deadline='v0.10.0',
                      fix='Use state_vector instead.',
                      parameter_desc='wavefunction',
                      match=lambda args, kwargs: 'wavefunction' in kwargs,
                      rewrite=lambda args, kwargs:
                      (args, {('state_vector' if k == 'wavefunction' else k): v
                              for k, v in kwargs.items()}))
def sub_state_vector(state_vector: np.ndarray,
                     keep_indices: List[int],
                     *,
                     default: TDefault = RaiseValueErrorIfNotProvided,
                     atol: Union[int, float] = 1e-8) -> np.ndarray:
Exemple #7
0
        without_reverse: bool = False,
        inverse: bool = False) -> 'cirq.Operation':
    """The quantum Fourier transform.

    Transforms a qubit register from the computational basis to the frequency
    basis.

    The inverse quantum Fourier transform is `cirq.qft(*qubits)**-1` or
    equivalently `cirq.inverse(cirq.qft(*qubits))`.

    Args:
        qubits: The qubits to apply the qft to.
        without_reverse: When set, swap gates at the end of the qft are omitted.
            This reverses the qubit order relative to the standard qft effect,
            but makes the gate cheaper to apply.
        inverse: If set, the inverse qft is performed instead of the qft.
            Equivalent to calling `cirq.inverse` on the result, or raising it
            to the -1.

    Returns:
        A `cirq.Operation` applying the qft to the given qubits.
    """
    result = QuantumFourierTransformGate(
        len(qubits), without_reverse=without_reverse).on(*qubits)
    if inverse:
        result = cirq.inverse(result)
    return result


QFT = deprecated(deadline='v0.10.0', fix='Use cirq.qft instead.')(qft)
Exemple #8
0
class Pauli(raw_types.Gate, metaclass=abc.ABCMeta):
    """Represents the Pauli gates.

    This is an abstract class with no public subclasses. The only instances
    of private subclasses are the X, Y, or Z Pauli gates defined below.
    """
    _XYZ = None  # type: Tuple[Pauli, Pauli, Pauli]

    @staticmethod
    def by_index(index: int) -> 'Pauli':
        return Pauli._XYZ[index % 3]

    @staticmethod
    def by_relative_index(p: 'Pauli', relative_index: int) -> 'Pauli':
        return Pauli._XYZ[(p._index + relative_index) % 3]

    def __init__(self, index: int, name: str) -> None:
        self._index = index
        self._name = name

    def num_qubits(self):
        return 1

    def _commutes_(
        self,
        other: Any,
        *,
        atol: Union[int,
                    float] = 1e-8) -> Union[bool, NotImplementedType, None]:
        if not isinstance(other, Pauli):
            return NotImplemented
        return self is other

    commutes_with = deprecated(
        deadline='v0.7.0', fix='Use `cirq.commutes()` instead.')(_commutes_)

    def third(self, second: 'Pauli') -> 'Pauli':
        return Pauli._XYZ[(-self._index - second._index) % 3]

    def relative_index(self, second: 'Pauli') -> int:
        """Relative index of self w.r.t. second in the (X, Y, Z) cycle."""
        return (self._index - second._index + 1) % 3 - 1

    def phased_pauli_product(
        self, other: Union['cirq.Pauli', 'identity.IdentityGate']
    ) -> Tuple[complex, Union['cirq.Pauli', 'identity.IdentityGate']]:
        if self == other:
            return 1, identity.I
        if other is identity.I:
            return 1, self
        return 1j**cast(Pauli, other).relative_index(self), self.third(
            cast(Pauli, other))

    def __gt__(self, other):
        if not isinstance(other, Pauli):
            return NotImplemented
        return (self._index - other._index) % 3 == 1

    def __lt__(self, other):
        if not isinstance(other, Pauli):
            return NotImplemented
        return (other._index - self._index) % 3 == 1

    def on(self, *qubits: 'cirq.Qid') -> 'SingleQubitPauliStringGateOperation':
        """Returns an application of this gate to the given qubits.

        Args:
            *qubits: The collection of qubits to potentially apply the gate to.
        """
        if len(qubits) != 1:
            raise ValueError(
                'Expected a single qubit, got <{!r}>.'.format(qubits))
        from cirq.ops.pauli_string import SingleQubitPauliStringGateOperation
        return SingleQubitPauliStringGateOperation(self, qubits[0])

    @property
    def _canonical_exponent(self):
        """Overrides EigenGate._canonical_exponent in subclasses."""
        return 1
Exemple #9
0
class PauliString(raw_types.Operation):
    def __init__(self,
                 *contents: PAULI_STRING_LIKE,
                 qubit_pauli_map: Optional[Dict['cirq.Qid',
                                                'cirq.Pauli']] = None,
                 coefficient: Union[int, float, complex] = 1):
        """Initializes a new PauliString.

        Args:
            *contents: A value or values to convert into a pauli string. This
                can be a number, a pauli operation, a dictionary from qubit to
                pauli/identity gates, or collections thereof. If a list of
                values is given, they are each individually converted and then
                multiplied from left to right in order.
            qubit_pauli_map: Initial dictionary mapping qubits to pauli
                operations. Defaults to the empty dictionary. Note that, unlike
                dictionaries passed to contents, this dictionary must not
                contain any identity gate values. Further note that this
                argument specifies values that are logically *before* factors
                specified in `contents`; `contents` are *right* multiplied onto
                the values in this dictionary.
            coefficient: Initial scalar coefficient. Defaults to 1.

        Examples:
            >>> a, b, c = cirq.LineQubit.range(3)

            >>> print(cirq.PauliString([cirq.X(a), cirq.X(a)]))
            I

            >>> print(cirq.PauliString(-1, cirq.X(a), cirq.Y(b), cirq.Z(c)))
            -X(0)*Y(1)*Z(2)

            >>> print(cirq.PauliString({a: cirq.X}, [-2, 3, cirq.Y(a)]))
            -6j*Z(0)

            >>> print(cirq.PauliString({a: cirq.I, b: cirq.X}))
            X(1)

            >>> print(cirq.PauliString({a: cirq.Y},
            ...                        qubit_pauli_map={a: cirq.X}))
            1j*Z(0)
        """
        if qubit_pauli_map is not None:
            for v in qubit_pauli_map.values():
                if not isinstance(v, pauli_gates.Pauli):
                    raise TypeError(f'{v} is not a Pauli')

        p = _MutablePauliString(coef=complex(coefficient),
                                paulis=dict(qubit_pauli_map or {}))
        p.inline_times_pauli_string_like(contents)
        self._qubit_pauli_map = p.paulis
        self._coefficient = p.coef

    @property
    def coefficient(self) -> complex:
        return self._coefficient

    def _value_equality_values_(self):
        if len(self._qubit_pauli_map) == 1 and self.coefficient == 1:
            q, p = list(self._qubit_pauli_map.items())[0]
            return gate_operation.GateOperation(p,
                                                [q])._value_equality_values_()

        return (frozenset(self._qubit_pauli_map.items()), self._coefficient)

    def _json_dict_(self):
        return {
            'cirq_type': self.__class__.__name__,
            # JSON requires mappings to have string keys.
            'qubit_pauli_map': list(self._qubit_pauli_map.items()),
            'coefficient': self.coefficient,
        }

    @classmethod
    def _from_json_dict_(cls, qubit_pauli_map, coefficient, **kwargs):
        return cls(qubit_pauli_map=dict(qubit_pauli_map),
                   coefficient=coefficient)

    def _value_equality_values_cls_(self):
        if len(self._qubit_pauli_map) == 1 and self.coefficient == 1:
            return gate_operation.GateOperation
        return PauliString

    def equal_up_to_coefficient(self, other: 'PauliString') -> bool:
        return self._qubit_pauli_map == other._qubit_pauli_map

    def __getitem__(self, key: 'cirq.Qid') -> pauli_gates.Pauli:
        return self._qubit_pauli_map[key]

    # pylint: disable=function-redefined
    @overload
    def get(self, key: 'cirq.Qid') -> pauli_gates.Pauli:
        pass

    @overload
    def get(self, key: 'cirq.Qid',
            default: TDefault) -> Union[pauli_gates.Pauli, TDefault]:
        pass

    def get(self, key: 'cirq.Qid', default=None):
        return self._qubit_pauli_map.get(key, default)

    # pylint: enable=function-redefined

    def __mul__(self, other) -> 'PauliString':
        known = False
        if isinstance(other, raw_types.Operation) and isinstance(
                other.gate, identity.IdentityGate):
            known = True
        elif isinstance(other, (PauliString, numbers.Number)):
            known = True
        if known:
            return PauliString(cast(PAULI_STRING_LIKE, other),
                               qubit_pauli_map=self._qubit_pauli_map,
                               coefficient=self.coefficient)
        return NotImplemented

    @property
    def gate(self) -> 'cirq.DensePauliString':
        order: List[Optional[pauli_gates.Pauli]] = [
            None, pauli_gates.X, pauli_gates.Y, pauli_gates.Z
        ]
        from cirq.ops.dense_pauli_string import DensePauliString
        return DensePauliString(
            coefficient=self.coefficient,
            pauli_mask=[order.index(self[q]) for q in self.qubits])

    def __rmul__(self, other) -> 'PauliString':
        if isinstance(other, numbers.Number):
            return PauliString(qubit_pauli_map=self._qubit_pauli_map,
                               coefficient=self._coefficient *
                               complex(cast(SupportsComplex, other)))

        if (isinstance(other, raw_types.Operation)
                and isinstance(other.gate, identity.IdentityGate)):
            return self

        # Note: PauliString case handled by __mul__.
        return NotImplemented

    def __truediv__(self, other):
        if isinstance(other, numbers.Number):
            return PauliString(qubit_pauli_map=self._qubit_pauli_map,
                               coefficient=self._coefficient /
                               complex(cast(SupportsComplex, other)))
        return NotImplemented

    def __add__(self, other):
        from cirq.ops.linear_combinations import PauliSum
        return PauliSum.from_pauli_strings(self).__add__(other)

    def __radd__(self, other):
        return self.__add__(other)

    def __sub__(self, other):
        from cirq.ops.linear_combinations import PauliSum
        return PauliSum.from_pauli_strings(self).__sub__(other)

    def __rsub__(self, other):
        return -self.__sub__(other)

    def __contains__(self, key: 'cirq.Qid') -> bool:
        return key in self._qubit_pauli_map

    def _decompose_(self):
        if not self._has_unitary_():
            return None
        return [
            *([] if self.coefficient == 1 else
              [global_phase_op.GlobalPhaseOperation(self.coefficient)]),
            *[self[q].on(q) for q in self.qubits],
        ]

    def keys(self) -> KeysView[raw_types.Qid]:
        return self._qubit_pauli_map.keys()

    @property
    def qubits(self) -> Tuple[raw_types.Qid, ...]:
        return tuple(sorted(self.keys()))

    def with_qubits(self, *new_qubits: 'cirq.Qid') -> 'PauliString':
        return PauliString(qubit_pauli_map=dict(
            zip(new_qubits, (self[q] for q in self.qubits))),
                           coefficient=self._coefficient)

    def values(self) -> ValuesView[pauli_gates.Pauli]:
        return self._qubit_pauli_map.values()

    def items(self) -> ItemsView:
        return self._qubit_pauli_map.items()

    def __iter__(self) -> Iterator[raw_types.Qid]:
        return iter(self._qubit_pauli_map.keys())

    def __bool__(self):
        return bool(self._qubit_pauli_map)

    def __len__(self) -> int:
        return len(self._qubit_pauli_map)

    def _repr_pretty_(self, p: Any, cycle: bool) -> None:
        """Print ASCII diagram in Jupyter."""
        if cycle:
            # There should never be a cycle.  This is just in case.
            p.text('cirq.PauliString(...)')
        else:
            p.text(str(self))

    def __repr__(self):
        ordered_qubits = sorted(self.qubits)
        prefix = ''

        factors = []
        if self._coefficient == -1:
            prefix = '-'
        elif self._coefficient != 1:
            factors.append(repr(self._coefficient))

        if not ordered_qubits:
            factors.append('cirq.PauliString()')
        for q in ordered_qubits:
            factors.append(repr(cast(raw_types.Gate, self[q]).on(q)))

        fused = prefix + '*'.join(factors)
        if len(factors) > 1:
            return '({})'.format(fused)
        return fused

    def __str__(self):
        ordered_qubits = sorted(self.qubits)
        prefix = ''

        factors = []
        if self._coefficient == -1:
            prefix = '-'
        elif self._coefficient != 1:
            factors.append(repr(self._coefficient))

        if not ordered_qubits:
            factors.append('I')
        for q in ordered_qubits:
            factors.append(str(cast(raw_types.Gate, self[q]).on(q)))

        return prefix + '*'.join(factors)

    def _has_unitary_(self) -> bool:
        return abs(1 - abs(self.coefficient)) < 1e-6

    def _unitary_(self) -> Optional[np.ndarray]:
        if not self._has_unitary_():
            return None
        return linalg.kron(self.coefficient,
                           *[protocols.unitary(self[q]) for q in self.qubits])

    def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs'):
        if not self._has_unitary_():
            return None
        if self.coefficient != 1:
            args.target_tensor *= self.coefficient
        return protocols.apply_unitaries([self[q].on(q) for q in self.qubits],
                                         self.qubits, args)

    def expectation_from_wavefunction(
            self,
            state: np.ndarray,
            qubit_map: Mapping[raw_types.Qid, int],
            *,
            atol: float = 1e-7,
            check_preconditions: bool = True) -> float:
        r"""Evaluate the expectation of this PauliString given a wavefunction.

        Compute the expectation value of this PauliString with respect to a
        wavefunction. By convention expectation values are defined for Hermitian
        operators, and so this method will fail if this PauliString is
        non-Hermitian.

        `state` must be an array representation of a wavefunction and have
        shape `(2 ** n, )` or `(2, 2, ..., 2)` (n entries) where `state` is
        expressed over n qubits.

        `qubit_map` must assign an integer index to each qubit in this
        PauliString that determines which bit position of a computational basis
        state that qubit corresponds to. For example if `state` represents
        $|0\rangle |+\rangle$ and `q0, q1 = cirq.LineQubit.range(2)` then:

            cirq.X(q0).expectation(state, qubit_map={q0: 0, q1: 1}) = 0
            cirq.X(q0).expectation(state, qubit_map={q0: 1, q1: 0}) = 1

        Args:
            state: An array representing a valid wavefunction.
            qubit_map: A map from all qubits used in this PauliString to the
                indices of the qubits that `state` is defined over.
            atol: Absolute numerical tolerance.
            check_preconditions: Whether to check that `state` represents a
                valid wavefunction.

        Returns:
            The expectation value of the input state.

        Raises:
            NotImplementedError if this PauliString is non-Hermitian.
        """
        if abs(self.coefficient.imag) > 0.0001:
            raise NotImplementedError(
                "Cannot compute expectation value of a non-Hermitian "
                "PauliString <{}>. Coefficient must be real.".format(self))

        # FIXME: Avoid enforce specific complex type. This is necessary to
        # prevent an `apply_unitary` bug (Issue #2041).
        if state.dtype.kind != 'c':
            raise TypeError("Input state dtype must be np.complex64 or "
                            "np.complex128")

        size = state.size
        num_qubits = size.bit_length() - 1
        if len(state.shape) != 1 and state.shape != (2, ) * num_qubits:
            raise ValueError("Input array does not represent a wavefunction "
                             "with shape `(2 ** n,)` or `(2, ..., 2)`.")

        _validate_qubit_mapping(qubit_map, self.qubits, num_qubits)
        if check_preconditions:
            # HACK: avoid circular import
            from cirq.sim.wave_function import validate_normalized_state
            validate_normalized_state(state=state,
                                      qid_shape=(2, ) * num_qubits,
                                      dtype=state.dtype,
                                      atol=atol)
        return self._expectation_from_wavefunction_no_validation(
            state, qubit_map)

    def _expectation_from_wavefunction_no_validation(
            self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid,
                                                        int]) -> float:
        """Evaluate the expectation of this PauliString given a wavefunction.

        This method does not provide input validation. See
        `PauliString.expectation_from_wavefunction` for function description.

        Args:
            state: An array representing a valid wavefunction.
            qubit_map: A map from all qubits used in this PauliString to the
            indices of the qubits that `state` is defined over.

        Returns:
            The expectation value of the input state.
        """
        if len(state.shape) == 1:
            num_qubits = state.shape[0].bit_length() - 1
            state = np.reshape(state, (2, ) * num_qubits)

        ket = np.copy(state)
        for qubit, pauli in self.items():
            buffer = np.empty(ket.shape, dtype=state.dtype)
            args = protocols.ApplyUnitaryArgs(target_tensor=ket,
                                              available_buffer=buffer,
                                              axes=(qubit_map[qubit], ))
            ket = protocols.apply_unitary(pauli, args)

        return self.coefficient * (np.tensordot(
            state.conj(), ket, axes=len(ket.shape)).item())

    def expectation_from_density_matrix(
            self,
            state: np.ndarray,
            qubit_map: Mapping[raw_types.Qid, int],
            *,
            atol: float = 1e-7,
            check_preconditions: bool = True) -> float:
        r"""Evaluate the expectation of this PauliString given a density matrix.

        Compute the expectation value of this PauliString with respect to an
        array representing a density matrix. By convention expectation values
        are defined for Hermitian operators, and so this method will fail if
        this PauliString is non-Hermitian.

        `state` must be an array representation of a density matrix and have
        shape `(2 ** n, 2 ** n)` or `(2, 2, ..., 2)` (2*n entries), where
        `state` is expressed over n qubits.

        `qubit_map` must assign an integer index to each qubit in this
        PauliString that determines which bit position of a computational basis
        state that qubit corresponds to. For example if `state` represents
        $|0\rangle |+\rangle$ and `q0, q1 = cirq.LineQubit.range(2)` then:

            cirq.X(q0).expectation(state, qubit_map={q0: 0, q1: 1}) = 0
            cirq.X(q0).expectation(state, qubit_map={q0: 1, q1: 0}) = 1

        Args:
            state: An array representing a valid  density matrix.
            qubit_map: A map from all qubits used in this PauliString to the
                indices of the qubits that `state` is defined over.
            atol: Absolute numerical tolerance.
            check_preconditions: Whether to check that `state` represents a
                valid density matrix.

        Returns:
            The expectation value of the input state.

        Raises:
            NotImplementedError if this PauliString is non-Hermitian.
        """
        if abs(self.coefficient.imag) > 0.0001:
            raise NotImplementedError(
                "Cannot compute expectation value of a non-Hermitian "
                "PauliString <{}>. Coefficient must be real.".format(self))

        # FIXME: Avoid enforcing specific complex type. This is necessary to
        # prevent an `apply_unitary` bug (Issue #2041).
        if state.dtype.kind != 'c':
            raise TypeError("Input state dtype must be np.complex64 or "
                            "np.complex128")

        size = state.size
        num_qubits = int(np.sqrt(size)).bit_length() - 1
        dim = 1 << num_qubits
        if state.shape != (dim, dim) and state.shape != (2, 2) * num_qubits:
            raise ValueError("Input array does not represent a density matrix "
                             "with shape `(2 ** n, 2 ** n)` or `(2, ..., 2)`.")

        _validate_qubit_mapping(qubit_map, self.qubits, num_qubits)
        if check_preconditions:
            # HACK: avoid circular import
            from cirq.sim.density_matrix_utils import to_valid_density_matrix
            # Do not enforce reshaping if the state all axes are dimension 2.
            _ = to_valid_density_matrix(density_matrix_rep=state.reshape(
                dim, dim),
                                        num_qubits=num_qubits,
                                        dtype=state.dtype,
                                        atol=atol)
        return self._expectation_from_density_matrix_no_validation(
            state, qubit_map)

    def _expectation_from_density_matrix_no_validation(
            self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid,
                                                        int]) -> float:
        """Evaluate the expectation of this PauliString given a density matrix.

        This method does not provide input validation. See
        `PauliString.expectation_from_density_matrix` for function description.

        Args:
            state: An array representing a valid  density matrix.
            qubit_map: A map from all qubits used in this PauliString to the
            indices of the qubits that `state` is defined over.

        Returns:
            The expectation value of the input state.
        """
        result = np.copy(state)
        if len(state.shape) == 2:
            num_qubits = state.shape[0].bit_length() - 1
            result = np.reshape(result, (2, ) * num_qubits * 2)

        for qubit, pauli in self.items():
            buffer = np.empty(result.shape, dtype=state.dtype)
            args = protocols.ApplyUnitaryArgs(target_tensor=result,
                                              available_buffer=buffer,
                                              axes=(qubit_map[qubit], ))
            result = protocols.apply_unitary(pauli, args)

        while any(result.shape):
            result = np.trace(result, axis1=0, axis2=len(result.shape) // 2)
        return result * self.coefficient

    def zip_items(
        self, other: 'PauliString'
    ) -> Iterator[Tuple[raw_types.Qid, Tuple[pauli_gates.Pauli,
                                             pauli_gates.Pauli]]]:
        for qubit, pauli0 in self.items():
            if qubit in other:
                yield qubit, (pauli0, other[qubit])

    def zip_paulis(
        self, other: 'PauliString'
    ) -> Iterator[Tuple[pauli_gates.Pauli, pauli_gates.Pauli]]:
        return (paulis for qubit, paulis in self.zip_items(other))

    def _commutes_(
        self,
        other: Any,
        *,
        atol: Union[int,
                    float] = 1e-8) -> Union[bool, NotImplementedType, None]:
        if not isinstance(other, PauliString):
            return NotImplemented
        return sum(not protocols.commutes(p0, p1)
                   for p0, p1 in self.zip_paulis(other)) % 2 == 0

    commutes_with = deprecated(
        deadline='v0.7.0', fix='Use `cirq.commutes()` instead.')(_commutes_)

    def __neg__(self) -> 'PauliString':
        return PauliString(qubit_pauli_map=self._qubit_pauli_map,
                           coefficient=-self._coefficient)

    def __pos__(self) -> 'PauliString':
        return self

    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
        """Override behavior of numpy's exp method."""
        if ufunc == np.exp and len(inputs) == 1 and inputs[0] is self:
            return math.e**self
        return NotImplemented

    def __pow__(self, power):
        if power == 1:
            return self
        if power == -1:
            return PauliString(qubit_pauli_map=self._qubit_pauli_map,
                               coefficient=self.coefficient**-1)
        if isinstance(power, (int, float)):
            r, i = cmath.polar(self.coefficient)
            if abs(r - 1) > 0.0001:
                # Raising non-unitary PauliStrings to a power is not supported.
                return NotImplemented

            if len(self) == 1:
                q, p = next(iter(self.items()))
                gates = {
                    pauli_gates.X: common_gates.XPowGate,
                    pauli_gates.Y: common_gates.YPowGate,
                    pauli_gates.Z: common_gates.ZPowGate,
                }
                return gates[p](exponent=power).on(q)

            global_half_turns = power * (i / math.pi)

            # HACK: Avoid circular dependency.
            from cirq.ops import pauli_string_phasor
            return pauli_string_phasor.PauliStringPhasor(
                PauliString(qubit_pauli_map=self._qubit_pauli_map),
                exponent_neg=global_half_turns + power,
                exponent_pos=global_half_turns)
        return NotImplemented

    def __rpow__(self, base):
        if isinstance(base, (int, float)) and base > 0:
            if abs(self.coefficient.real) > 0.0001:
                raise NotImplementedError(
                    "Exponentiated to a non-Hermitian PauliString <{}**{}>. "
                    "Coefficient must be imaginary.".format(base, self))

            half_turns = math.log(base) * (-self.coefficient.imag / math.pi)

            if len(self) == 1:
                q, p = next(iter(self.items()))
                gates = {
                    pauli_gates.X: common_gates.XPowGate,
                    pauli_gates.Y: common_gates.YPowGate,
                    pauli_gates.Z: common_gates.ZPowGate,
                }
                return gates[p](exponent=half_turns, global_shift=-0.5).on(q)

            # HACK: Avoid circular dependency.
            from cirq.ops import pauli_string_phasor
            return pauli_string_phasor.PauliStringPhasor(
                PauliString(qubit_pauli_map=self._qubit_pauli_map),
                exponent_neg=+half_turns / 2,
                exponent_pos=-half_turns / 2)
        return NotImplemented

    def map_qubits(
            self, qubit_map: Dict[raw_types.Qid,
                                  raw_types.Qid]) -> 'PauliString':
        new_qubit_pauli_map = {
            qubit_map[qubit]: pauli
            for qubit, pauli in self.items()
        }
        return PauliString(qubit_pauli_map=new_qubit_pauli_map,
                           coefficient=self._coefficient)

    def to_z_basis_ops(self) -> Iterator[raw_types.Operation]:
        """Returns operations to convert the qubits to the computational basis.
        """
        for qubit, pauli in self.items():
            yield clifford_gate.SingleQubitCliffordGate.from_single_map(
                {pauli: (pauli_gates.Z, False)})(qubit)

    def dense(self, qubits: Sequence['cirq.Qid']) -> 'cirq.DensePauliString':
        """Returns a `cirq.DensePauliString` version of this Pauli string.

        This method satisfies the invariant `P.dense(qubits).on(*qubits) == P`.

        Args:
            qubits: The implicit sequence of qubits used by the dense pauli
                string. Specifically, if the returned dense Pauli string was
                applied to these qubits (via its `on` method) then the result
                would be a Pauli string equivalent to the receiving Pauli
                string.

        Returns:
            A `cirq.DensePauliString` instance `D` such that `D.on(*qubits)`
            equals the receiving `cirq.PauliString` instance `P`.
        """
        from cirq.ops.dense_pauli_string import DensePauliString
        if not self.keys() <= set(qubits):
            raise ValueError('not self.keys() <= set(qubits)')
        # pylint: disable=too-many-function-args
        pauli_mask = [self.get(q, identity.I) for q in qubits]
        # pylint: enable=too-many-function-args
        return DensePauliString(pauli_mask, coefficient=self.coefficient)

    def conjugated_by(self, clifford: 'cirq.OP_TREE') -> 'PauliString':
        r"""Returns the Pauli string conjugated by a clifford operation.

        The product-of-Paulis $P$ conjugated by the Clifford operation $C$ is

            $$
            C^\dagger P C
            $$

        For example, conjugating a +Y operation by an S operation results in a
        +X operation (as opposed to a -X operation).

        In a circuit diagram where `P` is a pauli string observable immediately
        after a Clifford operation `C`, the pauli string `P.conjugated_by(C)` is
        the equivalent pauli string observable just before `C`.

            --------------------------C---P---

            = ---C---P------------------------

            = ---C---P---------C^-1---C-------

            = ---C---P---C^-1---------C-------

            = --(C^-1 · P · C)--------C-------

            = ---P.conjugated_by(C)---C-------

        Analogously, a Pauli product P can be moved from before a Clifford C in
        a circuit diagram to after the Clifford C by conjugating P by the
        inverse of C:

            ---P---C---------------------------

            = -----C---P.conjugated_by(C^-1)---

        Args:
            clifford: The Clifford operation to conjugate by. This can be an
                individual operation, or a tree of operations.

                Note that the composite Clifford operation defined by a sequence
                of operations is equivalent to a circuit containing those
                operations in the given order. Somewhat counter-intuitively,
                this means that the operations in the sequence are conjugated
                onto the Pauli string in reverse order. For example,
                `P.conjugated_by([C1, C2])` is equivalent to
                `P.conjugated_by(C2).conjugated_by(C1)`.

        Examples:
            >>> a, b = cirq.LineQubit.range(2)
            >>> print(cirq.X(a).conjugated_by(cirq.CZ(a, b)))
            X(0)*Z(1)
            >>> print(cirq.X(a).conjugated_by(cirq.S(a)))
            -Y(0)
            >>> print(cirq.X(a).conjugated_by([cirq.H(a), cirq.CNOT(a, b)]))
            Z(0)*X(1)

        Returns:
            The Pauli string conjugated by the given Clifford operation.
        """
        pauli_map = dict(self._qubit_pauli_map)
        should_negate = False
        for op in list(op_tree.flatten_to_ops(clifford))[::-1]:
            if pauli_map.keys().isdisjoint(set(op.qubits)):
                continue
            for clifford_op in _decompose_into_cliffords(op)[::-1]:
                if pauli_map.keys().isdisjoint(set(clifford_op.qubits)):
                    continue
                should_negate ^= PauliString._pass_operation_over(
                    pauli_map, clifford_op, False)
        coef = -self._coefficient if should_negate else self.coefficient
        return PauliString(qubit_pauli_map=pauli_map, coefficient=coef)

    def pass_operations_over(self,
                             ops: Iterable['cirq.Operation'],
                             after_to_before: bool = False) -> 'PauliString':
        """Determines how the Pauli string changes when conjugated by Cliffords.

        The output and input pauli strings are related by a circuit equivalence.
        In particular, this circuit:

            ───ops───INPUT_PAULI_STRING───

        will be equivalent to this circuit:

            ───OUTPUT_PAULI_STRING───ops───

        up to global phase (assuming `after_to_before` is not set).

        If ops together have matrix C, the Pauli string has matrix P, and the
        output Pauli string has matrix P', then P' == C^-1 P C up to
        global phase.

        Setting `after_to_before` inverts the relationship, so that the output
        is the input and the input is the output. Equivalently, it inverts C.

        Args:
            ops: The operations to move over the string.
            after_to_before: Determines whether the operations start after the
                pauli string, instead of before (and so are moving in the
                opposite direction).
        """
        pauli_map = dict(self._qubit_pauli_map)
        should_negate = False
        for op in ops:
            if pauli_map.keys().isdisjoint(set(op.qubits)):
                continue
            decomposed = _decompose_into_cliffords(op)
            if not after_to_before:
                decomposed = decomposed[::-1]
            for clifford_op in decomposed:
                if pauli_map.keys().isdisjoint(set(clifford_op.qubits)):
                    continue
                should_negate ^= PauliString._pass_operation_over(
                    pauli_map, clifford_op, after_to_before)
        coef = -self._coefficient if should_negate else self.coefficient
        return PauliString(qubit_pauli_map=pauli_map, coefficient=coef)

    @staticmethod
    def _pass_operation_over(pauli_map: Dict[raw_types.Qid, pauli_gates.Pauli],
                             op: 'cirq.Operation',
                             after_to_before: bool = False) -> bool:
        if isinstance(op, gate_operation.GateOperation):
            gate = op.gate
            if isinstance(gate, clifford_gate.SingleQubitCliffordGate):
                return PauliString._pass_single_clifford_gate_over(
                    pauli_map,
                    gate,
                    op.qubits[0],
                    after_to_before=after_to_before)
            if isinstance(gate, pauli_interaction_gate.PauliInteractionGate):
                return PauliString._pass_pauli_interaction_gate_over(
                    pauli_map,
                    gate,
                    op.qubits[0],
                    op.qubits[1],
                    after_to_before=after_to_before)
        raise TypeError('Unsupported operation: {!r}'.format(op))

    @staticmethod
    def _pass_single_clifford_gate_over(
            pauli_map: Dict[raw_types.Qid, pauli_gates.Pauli],
            gate: clifford_gate.SingleQubitCliffordGate,
            qubit: 'cirq.Qid',
            after_to_before: bool = False) -> bool:
        if qubit not in pauli_map:
            return False
        if not after_to_before:
            gate **= -1
        pauli, inv = gate.transform(pauli_map[qubit])
        pauli_map[qubit] = pauli
        return inv

    @staticmethod
    def _pass_pauli_interaction_gate_over(
            pauli_map: Dict[raw_types.Qid, pauli_gates.Pauli],
            gate: pauli_interaction_gate.PauliInteractionGate,
            qubit0: 'cirq.Qid',
            qubit1: 'cirq.Qid',
            after_to_before: bool = False) -> bool:
        def merge_and_kickback(qubit: 'cirq.Qid',
                               pauli_left: Optional[pauli_gates.Pauli],
                               pauli_right: Optional[pauli_gates.Pauli],
                               inv: bool) -> int:
            assert pauli_left is not None or pauli_right is not None
            if pauli_left is None or pauli_right is None:
                pauli_map[qubit] = cast(pauli_gates.Pauli, pauli_left
                                        or pauli_right)
                return 0
            if pauli_left == pauli_right:
                del pauli_map[qubit]
                return 0

            pauli_map[qubit] = pauli_left.third(pauli_right)
            if (pauli_left < pauli_right) ^ after_to_before:
                return int(inv) * 2 + 1

            return int(inv) * 2 - 1

        quarter_kickback = 0
        if (qubit0 in pauli_map
                and not protocols.commutes(pauli_map[qubit0], gate.pauli0)):
            quarter_kickback += merge_and_kickback(qubit1, gate.pauli1,
                                                   pauli_map.get(qubit1),
                                                   gate.invert1)
        if (qubit1 in pauli_map
                and not protocols.commutes(pauli_map[qubit1], gate.pauli1)):
            quarter_kickback += merge_and_kickback(qubit0,
                                                   pauli_map.get(qubit0),
                                                   gate.pauli0, gate.invert0)
        assert quarter_kickback % 2 == 0, (
            'Impossible condition.  '
            'quarter_kickback is either incremented twice or never.')
        return quarter_kickback % 4 == 2