1# This code is part of Qiskit. 2# 3# (C) Copyright IBM 2018, 2019. 4# 5# This code is licensed under the Apache License, Version 2.0. You may 6# obtain a copy of this license in the LICENSE.txt file in the root directory 7# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. 8# 9# Any modifications or derivative works of this code must retain this 10# copyright notice, and modified files need to carry a notice indicating 11# that they have been altered from the originals. 12""" 13Readout error class for Qiskit Aer noise model. 14""" 15 16import copy 17 18import numpy as np 19from numpy.linalg import norm 20 21from qiskit.circuit import Instruction 22from qiskit.quantum_info.operators.predicates import ATOL_DEFAULT, RTOL_DEFAULT 23 24from ..noiseerror import NoiseError 25from .errorutils import qubits_from_mat 26 27 28class ReadoutError: 29 """ 30 Readout error class for Qiskit Aer noise model. 31 """ 32 # pylint: disable=invalid-name 33 _ATOL_DEFAULT = ATOL_DEFAULT 34 _RTOL_DEFAULT = RTOL_DEFAULT 35 _MAX_TOL = 1e-4 36 37 def __init__(self, probabilities, atol=ATOL_DEFAULT): 38 """ 39 Create a readout error for a noise model. 40 41 For an N-qubit readout error probabilities are entered as vectors: 42 43 .. code-block:: python 44 45 probabilities[j] = [P(0|m), P(1|m), ..., P(2 ** N - 1|m)] 46 47 where ``P(j|m)`` is the probability of recording a measurement outcome 48 of ``m`` as the value ``j``. Where ``j`` and ``m`` are integer 49 representations of bit-strings. 50 51 **Example: 1-qubit** 52 53 .. code-block:: python 54 55 probabilities[0] = [P("0"|"0"), P("1"|"0")] 56 probabilities[1] = [P("0"|"1"), P("1"|"1")] 57 58 **Example: 2-qubit** 59 60 .. code-block:: python 61 62 probabilities[0] = [P("00"|"00"), P("01"|"00"), P("10"|"00"), P("11"|"00")] 63 probabilities[1] = [P("00"|"01"), P("01"|"01"), P("10"|"01"), P("11"|"01")] 64 probabilities[2] = [P("00"|"10"), P("01"|"10"), P("10"|"10"), P("11"|"10")] 65 probabilities[3] = [P("00"|"11"), P("01"|"11"), P("10"|"11"), P("11"|"11")] 66 67 Args: 68 probabilities (matrix): List of outcome assignment probabilities. 69 atol (double): Threshold for checking probabilities are normalized 70 (Default: 1e-8). 71 """ 72 self._check_probabilities(probabilities, atol) 73 self._probabilities = np.array(probabilities, dtype=float) 74 self._number_of_qubits = qubits_from_mat(probabilities) 75 76 def __repr__(self): 77 """Display ReadoutError.""" 78 return "ReadoutError({})".format(self._probabilities) 79 80 def __str__(self): 81 """Print error information.""" 82 output = "ReadoutError on {} qubits.".format(self._number_of_qubits) + \ 83 " Assignment probabilities:" 84 for j, vec in enumerate(self._probabilities): 85 output += "\n P(j|{0}) = {1}".format(j, vec) 86 return output 87 88 def __eq__(self, other): 89 """Test if two ReadoutErrors are equal.""" 90 if not isinstance(other, ReadoutError): 91 return False 92 if self.number_of_qubits != other.number_of_qubits: 93 return False 94 return np.allclose(self._probabilities, other._probabilities, 95 atol=self.atol, rtol=self.rtol) 96 97 def copy(self): 98 """Make a copy of current ReadoutError.""" 99 # pylint: disable=no-value-for-parameter 100 # The constructor of subclasses from raw data should be a copy 101 return copy.deepcopy(self) 102 103 @property 104 def number_of_qubits(self): 105 """Return the number of qubits for the error.""" 106 return self._number_of_qubits 107 108 @property 109 def probabilities(self): 110 """Return the readout error probabilities matrix.""" 111 return self._probabilities 112 113 @property 114 def atol(self): 115 """The default absolute tolerance parameter for float comparisons.""" 116 return ReadoutError._ATOL_DEFAULT 117 118 @property 119 def rtol(self): 120 """The relative tolerance parameter for float comparisons.""" 121 return ReadoutError._RTOL_DEFAULT 122 123 @classmethod 124 def set_atol(cls, value): 125 """Set the class default absolute tolerance parameter for float comparisons.""" 126 if value < 0: 127 raise NoiseError( 128 "Invalid atol ({}) must be non-negative.".format(value)) 129 if value > cls._MAX_TOL: 130 raise NoiseError( 131 "Invalid atol ({}) must be less than {}.".format( 132 value, cls._MAX_TOL)) 133 cls._ATOL_DEFAULT = value 134 135 @classmethod 136 def set_rtol(cls, value): 137 """Set the class default relative tolerance parameter for float comparisons.""" 138 if value < 0: 139 raise NoiseError( 140 "Invalid rtol ({}) must be non-negative.".format(value)) 141 if value > cls._MAX_TOL: 142 raise NoiseError( 143 "Invalid rtol ({}) must be less than {}.".format( 144 value, cls._MAX_TOL)) 145 cls._RTOL_DEFAULT = value 146 147 def ideal(self): 148 """Return True if current error object is an identity""" 149 iden = np.eye(2**self.number_of_qubits) 150 delta = round(norm(np.array(self.probabilities) - iden), 12) 151 if delta == 0: 152 return True 153 return False 154 155 def to_instruction(self): 156 """Convert the ReadoutError to a circuit Instruction.""" 157 return Instruction("roerror", 0, self.number_of_qubits, self._probabilities) 158 159 def to_dict(self): 160 """Return the current error as a dictionary.""" 161 error = { 162 "type": "roerror", 163 "operations": ["measure"], 164 "probabilities": self._probabilities.tolist() 165 } 166 return error 167 168 def compose(self, other, front=False): 169 """Return the composition readout error other * self. 170 171 Note that for `front=True` this is equivalent to the 172 :meth:`ReadoutError.dot` method. 173 174 Args: 175 other (ReadoutError): a readout error. 176 front (bool): If True return the reverse order composation 177 self * other instead [default: False]. 178 179 Returns: 180 ReadoutError: The composition readout error. 181 182 Raises: 183 NoiseError: if other is not a ReadoutError or has incompatible 184 dimensions. 185 """ 186 if front: 187 return self._matmul(other) 188 return self._matmul(other, left_multiply=True) 189 190 def dot(self, other): 191 """Return the composition readout error self * other. 192 193 Args: 194 other (ReadoutError): a readout error. 195 196 Returns: 197 ReadoutError: The composition readout error. 198 199 Raises: 200 NoiseError: if other is not a ReadoutError or has incompatible 201 dimensions. 202 """ 203 return self._matmul(other) 204 205 def power(self, n): 206 """Return the compose of the readout error with itself n times. 207 208 Args: 209 n (int): the number of times to compose with self (n>0). 210 211 Returns: 212 ReadoutError: the n-times composition channel. 213 214 Raises: 215 NoiseError: if the power is not a positive integer. 216 """ 217 if not isinstance(n, int) or n < 1: 218 raise NoiseError("Can only power with positive integer powers.") 219 ret = self.copy() 220 for _ in range(1, n): 221 ret = ret.compose(self) 222 return ret 223 224 def tensor(self, other): 225 """Return the tensor product readout error self ⊗ other. 226 227 Args: 228 other (ReadoutError): a readout error. 229 230 Returns: 231 ReadoutError: the tensor product readout error self ⊗ other. 232 233 Raises: 234 NoiseError: if other is not a ReadoutError. 235 """ 236 return self._tensor_product(other, reverse=False) 237 238 def expand(self, other): 239 """Return the tensor product readout error self ⊗ other. 240 241 Args: 242 other (ReadoutError): a readout error. 243 244 Returns: 245 ReadoutError: the tensor product readout error other ⊗ self. 246 247 Raises: 248 NoiseError: if other is not a ReadoutError. 249 """ 250 return self._tensor_product(other, reverse=True) 251 252 @staticmethod 253 def _check_probabilities(probabilities, threshold): 254 """Check probabilities are valid.""" 255 # probabilities parameter can be a list or a numpy.ndarray 256 if (isinstance(probabilities, list) and not probabilities) or \ 257 (isinstance(probabilities, np.ndarray) and probabilities.size == 0): 258 raise NoiseError("Input probabilities: empty.") 259 num_outcomes = len(probabilities[0]) 260 num_qubits = int(np.log2(num_outcomes)) 261 if 2**num_qubits != num_outcomes: 262 raise NoiseError("Invalid probabilities: length " 263 "{} != 2**{}".format(num_outcomes, num_qubits)) 264 if len(probabilities) != num_outcomes: 265 raise NoiseError("Invalid probabilities.") 266 for vec in probabilities: 267 arr = np.array(vec) 268 if len(arr) != num_outcomes: 269 raise NoiseError( 270 "Invalid probabilities: vectors are different lengths.") 271 if abs(sum(arr) - 1) > threshold: 272 raise NoiseError("Invalid probabilities: sum({})= {} " 273 "is not 1.".format(vec, sum(arr))) 274 if arr[arr < 0].size > 0: 275 raise NoiseError( 276 "Invalid probabilities: {} " 277 "contains a negative probability.".format(vec)) 278 279 def _matmul(self, other, left_multiply=False): 280 """Return the composition readout error. 281 282 Args: 283 other (ReadoutError): a readout error. 284 left_multiply (bool): If True return other * self 285 If False return self * other [Default:False] 286 Returns: 287 ReadoutError: The composition readout error. 288 289 Raises: 290 NoiseError: if other is not a ReadoutError or has incompatible 291 dimensions. 292 """ 293 if not isinstance(other, ReadoutError): 294 other = ReadoutError(other) 295 if self.number_of_qubits != other.number_of_qubits: 296 raise NoiseError("other must have same number of qubits.") 297 if left_multiply: 298 probs = np.dot(other._probabilities, self._probabilities) 299 else: 300 probs = np.dot(self._probabilities, other._probabilities) 301 return ReadoutError(probs) 302 303 def _tensor_product(self, other, reverse=False): 304 """Return the tensor product readout error. 305 306 Args: 307 other (ReadoutError): a readout error. 308 reverse (bool): If False return self ⊗ other, if True return 309 if True return (other ⊗ self) [Default: False 310 Returns: 311 ReadoutError: the tensor product readout error. 312 """ 313 if not isinstance(other, ReadoutError): 314 other = ReadoutError(other) 315 if reverse: 316 probs = np.kron(other._probabilities, self._probabilities) 317 else: 318 probs = np.kron(self._probabilities, other._probabilities) 319 return ReadoutError(probs) 320 321 # Overloads 322 def __matmul__(self, other): 323 return self.compose(other) 324 325 def __mul__(self, other): 326 return self.dot(other) 327 328 def __pow__(self, n): 329 return self.power(n) 330 331 def __xor__(self, other): 332 return self.tensor(other) 333 334 def __rmul__(self, other): 335 raise NotImplementedError( 336 "'ReadoutError' does not support scalar multiplication.") 337 338 def __truediv__(self, other): 339 raise NotImplementedError("'ReadoutError' does not support division.") 340 341 def __add__(self, other): 342 raise NotImplementedError("'ReadoutError' does not support addition.") 343 344 def __sub__(self, other): 345 raise NotImplementedError( 346 "'ReadoutError' does not support subtraction.") 347 348 def __neg__(self): 349 raise NotImplementedError("'ReadoutError' does not support negation.") 350