# 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 (Any, Dict, ItemsView, Iterable, Iterator, KeysView, Mapping,
Tuple, TypeVar, Union, ValuesView, overload)
from cirq import value
from cirq.ops import (
raw_types, gate_operation, common_gates, op_tree
)
from cirq.ops.pauli import Pauli
from cirq.ops.clifford_gate import SingleQubitCliffordGate
from cirq.ops.pauli_interaction_gate import PauliInteractionGate
TDefault = TypeVar('TDefault')
[docs]@value.value_equality
class PauliString:
[docs] def __init__(self,
qubit_pauli_map: Mapping[raw_types.QubitId, Pauli],
negated: bool = False) -> None:
self._qubit_pauli_map = dict(qubit_pauli_map)
self.negated = negated
[docs] @staticmethod
def from_single(qubit: raw_types.QubitId, pauli: Pauli) -> 'PauliString':
"""Creates a PauliString with a single qubit."""
return PauliString({qubit: pauli})
def _value_equality_values_(self):
return (frozenset(self._qubit_pauli_map.items()),
self.negated)
[docs] def equal_up_to_sign(self, other: 'PauliString') -> bool:
return self._qubit_pauli_map == other._qubit_pauli_map
def __getitem__(self, key: raw_types.QubitId) -> Pauli:
return self._qubit_pauli_map[key]
# pylint: disable=function-redefined
@overload
def get(self, key: raw_types.QubitId) -> Pauli:
pass
@overload
def get(self, key: raw_types.QubitId, default: TDefault
) -> Union[Pauli, TDefault]:
pass
[docs] def get(self, key: raw_types.QubitId, default=None):
return self._qubit_pauli_map.get(key, default)
# pylint: enable=function-redefined
def __contains__(self, key: raw_types.QubitId) -> bool:
return key in self._qubit_pauli_map
[docs] def keys(self) -> KeysView[raw_types.QubitId]:
return self._qubit_pauli_map.keys()
[docs] def qubits(self) -> KeysView[raw_types.QubitId]:
return self.keys()
[docs] def values(self) -> ValuesView[Pauli]:
return self._qubit_pauli_map.values()
[docs] def items(self) -> ItemsView:
return self._qubit_pauli_map.items()
def __iter__(self) -> Iterator[raw_types.QubitId]:
return iter(self._qubit_pauli_map.keys())
def __len__(self) -> int:
return len(self._qubit_pauli_map)
def __repr__(self):
map_str = ', '.join(('{!r}: {!r}'.format(qubit, self[qubit])
for qubit in sorted(self.qubits())))
return 'cirq.PauliString({{{}}}, {})'.format(map_str, self.negated)
def __str__(self):
ordered_qubits = sorted(self.qubits())
return '{{{}, {}}}'.format('+-'[self.negated],
', '.join(('{!s}:{!s}'.format(q, self[q])
for q in ordered_qubits)))
[docs] def zip_items(self, other: 'PauliString'
) -> Iterator[Tuple[raw_types.QubitId, Tuple[Pauli, Pauli]]]:
for qubit, pauli0 in self.items():
if qubit in other:
yield qubit, (pauli0, other[qubit])
[docs] def zip_paulis(self, other: 'PauliString') -> Iterator[Tuple[Pauli, Pauli]]:
return (paulis for qubit, paulis in self.zip_items(other))
[docs] def commutes_with(self, other: 'PauliString') -> bool:
return sum(not p0.commutes_with(p1)
for p0, p1 in self.zip_paulis(other)
) % 2 == 0
[docs] def negate(self) -> 'PauliString':
return PauliString(self._qubit_pauli_map, not self.negated)
def __neg__(self) -> 'PauliString':
return self.negate()
def __pos__(self) -> 'PauliString':
return self
[docs] def map_qubits(self, qubit_map: Dict[raw_types.QubitId, raw_types.QubitId]
) -> 'PauliString':
new_qubit_pauli_map = {qubit_map[qubit]: pauli
for qubit, pauli in self.items()}
return PauliString(new_qubit_pauli_map, self.negated)
[docs] def to_z_basis_ops(self) -> op_tree.OP_TREE:
"""Returns operations to convert the qubits to the computational basis.
"""
for qubit, pauli in self.items():
yield SingleQubitCliffordGate.from_single_map(
{pauli: (Pauli.Z, False)})(qubit)
[docs] def pass_operations_over(self,
ops: Iterable[raw_types.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)
inv = self.negated
for op in ops:
if not set(op.qubits) & set(pauli_map.keys()):
# op operates on an independent set of qubits from the Pauli
# string. The order can be switched with no change no matter
# what op is.
continue
inv ^= PauliString._pass_operation_over(pauli_map,
op,
after_to_before)
return PauliString(pauli_map, inv)
@staticmethod
def _pass_operation_over(pauli_map: Dict[raw_types.QubitId, Pauli],
op: raw_types.Operation,
after_to_before: bool = False) -> bool:
if isinstance(op, gate_operation.GateOperation):
gate = op.gate
if isinstance(gate, SingleQubitCliffordGate):
return PauliString._pass_single_clifford_gate_over(
pauli_map, gate, op.qubits[0],
after_to_before=after_to_before)
if isinstance(gate, common_gates.CZPowGate):
gate = PauliInteractionGate.CZ
if isinstance(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.QubitId,
Pauli],
gate: SingleQubitCliffordGate,
qubit: raw_types.QubitId,
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.QubitId,
Pauli],
gate: PauliInteractionGate,
qubit0: raw_types.QubitId,
qubit1: raw_types.QubitId,
after_to_before: bool = False
) -> bool:
def merge_and_kickback(qubit, pauli_left, pauli_right, inv):
if pauli_left is None or pauli_right is None:
pauli_map[qubit] = pauli_left or pauli_right
return 0
elif pauli_left == pauli_right:
del pauli_map[qubit]
return 0
else:
pauli_map[qubit] = pauli_left.third(pauli_right)
if (pauli_left < pauli_right) ^ after_to_before:
return inv * 2 + 1
else:
return inv * 2 - 1
quarter_kickback = 0
if (qubit0 in pauli_map
and not pauli_map[qubit0].commutes_with(gate.pauli0)):
quarter_kickback += merge_and_kickback(qubit1,
gate.pauli1,
pauli_map.get(qubit1),
gate.invert1)
if (qubit1 in pauli_map
and not pauli_map[qubit1].commutes_with(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)