1# Copyright 2018 The Cirq Developers 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Protocol and methods for obtaining Kraus representation of quantum channels.""" 16 17from typing import Any, Sequence, Tuple, TypeVar, Union 18import warnings 19 20import numpy as np 21from typing_extensions import Protocol 22 23from cirq._doc import doc_private 24from cirq.protocols.decompose_protocol import ( 25 _try_decompose_into_operations_and_qubits, 26) 27from cirq.protocols.mixture_protocol import has_mixture 28 29 30from cirq.type_workarounds import NotImplementedType 31 32 33# This is a special indicator value used by the channel method to determine 34# whether or not the caller provided a 'default' argument. It must be of type 35# Sequence[np.ndarray] to ensure the method has the correct type signature in 36# that case. It is checked for using `is`, so it won't have a false positive 37# if the user provides a different (np.array([]),) value. 38RaiseTypeErrorIfNotProvided = (np.array([]),) 39 40 41TDefault = TypeVar('TDefault') 42 43 44class SupportsKraus(Protocol): 45 """An object that may be describable as a quantum channel.""" 46 47 @doc_private 48 def _kraus_(self) -> Union[Sequence[np.ndarray], NotImplementedType]: 49 r"""A list of Kraus matrices describing the quantum channel. 50 51 These matrices are the terms in the operator sum representation of a 52 quantum channel. If the returned matrices are ${A_0,A_1,..., A_{r-1}}$, 53 then this describes the channel: 54 $$ 55 \rho \rightarrow \sum_{k=0}^{r-1} A_k \rho A_k^\dagger 56 $$ 57 These matrices are required to satisfy the trace preserving condition 58 $$ 59 \sum_{k=0}^{r-1} A_k^\dagger A_k = I 60 $$ 61 where $I$ is the identity matrix. The matrices $A_k$ are sometimes 62 called Kraus or noise operators. 63 64 This method is used by the global `cirq.channel` method. If this method 65 or the _unitary_ method is not present, or returns NotImplement, 66 it is assumed that the receiving object doesn't have a channel 67 (resulting in a TypeError or default result when calling `cirq.channel` 68 on it). (The ability to return NotImplemented is useful when a class 69 cannot know if it is a channel until runtime.) 70 71 The order of cells in the matrices is always implicit with respect to 72 the object being called. For example, for GateOperations these matrices 73 must be ordered with respect to the list of qubits that the channel is 74 applied to. The qubit-to-amplitude order mapping matches the 75 ordering of numpy.kron(A, B), where A is a qubit earlier in the list 76 than the qubit B. 77 78 Returns: 79 A list of matrices describing the channel (Kraus operators), or 80 NotImplemented if there is no such matrix. 81 """ 82 83 @doc_private 84 def _has_kraus_(self) -> bool: 85 """Whether this value has a Kraus representation. 86 87 This method is used by the global `cirq.has_channel` method. If this 88 method is not present, or returns NotImplemented, it will fallback 89 to similarly checking `cirq.has_mixture` or `cirq.has_unitary`. If none 90 of these are present or return NotImplemented, then `cirq.has_channel` 91 will fall back to checking whether `cirq.channel` has a non-default 92 value. Otherwise `cirq.has_channel` returns False. 93 94 Returns: 95 True if the value has a channel representation, False otherwise. 96 """ 97 98 99def kraus( 100 val: Any, default: Any = RaiseTypeErrorIfNotProvided 101) -> Union[Tuple[np.ndarray, ...], TDefault]: 102 r"""Returns a list of matrices describing the channel for the given value. 103 104 These matrices are the terms in the operator sum representation of 105 a quantum channel. If the returned matrices are ${A_0,A_1,..., A_{r-1}}$, 106 then this describes the channel: 107 $$ 108 \rho \rightarrow \sum_{k=0}^{r-1} A_k \rho A_k^\dagger 109 $$ 110 These matrices are required to satisfy the trace preserving condition 111 $$ 112 \sum_{k=0}^{r-1} A_k^\dagger A_k = I 113 $$ 114 where $I$ is the identity matrix. The matrices $A_k$ are sometimes called 115 Kraus or noise operators. 116 117 Args: 118 val: The value to describe by a channel. 119 default: Determines the fallback behavior when `val` doesn't have 120 a channel. If `default` is not set, a TypeError is raised. If 121 default is set to a value, that value is returned. 122 123 Returns: 124 If `val` has a `_kraus_` method and its result is not NotImplemented, 125 that result is returned. Otherwise, if `val` has a `_mixture_` method 126 and its results is not NotImplement a tuple made up of channel 127 corresponding to that mixture being a probabilistic mixture of unitaries 128 is returned. Otherwise, if `val` has a `_unitary_` method and 129 its result is not NotImplemented a tuple made up of that result is 130 returned. Otherwise, if a default value was specified, the default 131 value is returned. 132 133 Raises: 134 TypeError: `val` doesn't have a _kraus_ or _unitary_ method (or that 135 method returned NotImplemented) and also no default value was 136 specified. 137 """ 138 channel_getter = getattr(val, '_channel_', None) 139 if channel_getter is not None: 140 warnings.warn( 141 '_channel_ is deprecated and will be removed in cirq 0.13, rename to _kraus_', 142 DeprecationWarning, 143 ) 144 145 kraus_getter = getattr(val, '_kraus_', None) 146 kraus_result = NotImplemented if kraus_getter is None else kraus_getter() 147 if kraus_result is not NotImplemented: 148 return tuple(kraus_result) 149 150 mixture_getter = getattr(val, '_mixture_', None) 151 mixture_result = NotImplemented if mixture_getter is None else mixture_getter() 152 if mixture_result is not NotImplemented and mixture_result is not None: 153 return tuple(np.sqrt(p) * u for p, u in mixture_result) 154 155 unitary_getter = getattr(val, '_unitary_', None) 156 unitary_result = NotImplemented if unitary_getter is None else unitary_getter() 157 if unitary_result is not NotImplemented and unitary_result is not None: 158 return (unitary_result,) 159 160 channel_result = NotImplemented if channel_getter is None else channel_getter() 161 if channel_result is not NotImplemented: 162 return tuple(channel_result) 163 164 if default is not RaiseTypeErrorIfNotProvided: 165 return default 166 167 if kraus_getter is None and unitary_getter is None and mixture_getter is None: 168 raise TypeError( 169 "object of type '{}' has no _kraus_ or _mixture_ or " 170 "_unitary_ method.".format(type(val)) 171 ) 172 173 raise TypeError( 174 "object of type '{}' does have a _kraus_, _mixture_ or " 175 "_unitary_ method, but it returned NotImplemented.".format(type(val)) 176 ) 177 178 179def has_kraus(val: Any, *, allow_decompose: bool = True) -> bool: 180 """Returns whether the value has a Kraus representation. 181 182 Args: 183 val: The value to check. 184 allow_decompose: Used by internal methods to stop redundant 185 decompositions from being performed (e.g. there's no need to 186 decompose an object to check if it is unitary as part of determining 187 if the object is a quantum channel, when the quantum channel check 188 will already be doing a more general decomposition check). Defaults 189 to True. When False, the decomposition strategy for determining 190 the result is skipped. 191 192 Returns: 193 If `val` has a `_has_kraus_` method and its result is not 194 NotImplemented, that result is returned. Otherwise, if `val` has a 195 `_has_mixture_` method and its result is not NotImplemented, that 196 result is returned. Otherwise if `val` has a `_has_unitary_` method 197 and its results is not NotImplemented, that result is returned. 198 Otherwise, if the value has a _kraus_ method return if that 199 has a non-default value. Returns False if none of these functions 200 exists. 201 """ 202 kraus_getter = getattr(val, '_has_kraus_', None) 203 result = NotImplemented if kraus_getter is None else kraus_getter() 204 if result is not NotImplemented: 205 return result 206 207 result = has_mixture(val, allow_decompose=False) 208 if result is not NotImplemented and result: 209 return result 210 211 if allow_decompose: 212 operations, _, _ = _try_decompose_into_operations_and_qubits(val) 213 if operations is not None: 214 return all(has_kraus(val) for val in operations) 215 216 # No has methods, use `_kraus_` or delegates instead. 217 return kraus(val, None) is not None 218