# 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.
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(
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
"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
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))
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:
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)
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
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