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"""Quantum channels that are commonly used in the literature.""" 16 17import itertools 18from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union, TYPE_CHECKING 19 20import numpy as np 21 22from cirq import protocols, value 23from cirq.ops import raw_types, common_gates, pauli_gates, gate_features, identity 24 25if TYPE_CHECKING: 26 import cirq 27 28 29@value.value_equality 30class AsymmetricDepolarizingChannel(gate_features.SingleQubitGate): 31 """A channel that depolarizes asymmetrically along different directions.""" 32 33 def __init__( 34 self, 35 p_x: Optional[float] = None, 36 p_y: Optional[float] = None, 37 p_z: Optional[float] = None, 38 error_probabilities: Optional[Dict[str, float]] = None, 39 tol: float = 1e-8, 40 ) -> None: 41 r"""The asymmetric depolarizing channel. 42 43 This channel applies one of 4**n disjoint possibilities: nothing (the 44 identity channel) or one of the 4**n - 1 pauli gates. 45 46 This channel evolves a density matrix via 47 48 $$ 49 \sum_i p_i Pi \rho Pi 50 $$ 51 52 where i varies from 0 to 4**n-1 and Pi represents n-qubit Pauli operator 53 (including identity). The input $\rho$ is the density matrix before the 54 depolarization. 55 56 Args: 57 p_x: The probability that a Pauli X and no other gate occurs. 58 p_y: The probability that a Pauli Y and no other gate occurs. 59 p_z: The probability that a Pauli Z and no other gate occurs. 60 error_probabilities: Dictionary of string (Pauli operator) to its 61 probability. If the identity is missing from the list, it will 62 be added so that the total probability mass is 1. 63 tol: The tolerance used making sure the total probability mass is 64 equal to 1. 65 66 Examples of calls: 67 * Single qubit: AsymmetricDepolarizingChannel(0.2, 0.1, 0.3) 68 * Single qubit: AsymmetricDepolarizingChannel(p_z=0.3) 69 * Two qubits: AsymmetricDepolarizingChannel( 70 error_probabilities={'XX': 0.2}) 71 72 Raises: 73 ValueError: if the args or the sum of args are not probabilities. 74 """ 75 if error_probabilities: 76 num_qubits = len(list(error_probabilities)[0]) 77 for k in error_probabilities.keys(): 78 if not set(k).issubset({'I', 'X', 'Y', 'Z'}): 79 raise ValueError(f"{k} is not made solely of I, X, Y, Z.") 80 if len(k) != num_qubits: 81 raise ValueError(f"{k} must have {num_qubits} Pauli gates.") 82 for k, v in error_probabilities.items(): 83 value.validate_probability(v, f"p({k})") 84 sum_probs = sum(error_probabilities.values()) 85 identity = 'I' * num_qubits 86 if sum_probs < 1.0 - tol and identity not in error_probabilities: 87 error_probabilities[identity] = 1.0 - sum_probs 88 elif abs(sum_probs - 1.0) > tol: 89 raise ValueError(f"Probabilities do not add up to 1 but to {sum_probs}") 90 self._num_qubits = num_qubits 91 self._error_probabilities = error_probabilities 92 else: 93 p_x = 0.0 if p_x is None else p_x 94 p_y = 0.0 if p_y is None else p_y 95 p_z = 0.0 if p_z is None else p_z 96 97 p_x = value.validate_probability(p_x, 'p_x') 98 p_y = value.validate_probability(p_y, 'p_y') 99 p_z = value.validate_probability(p_z, 'p_z') 100 p_i = 1 - value.validate_probability(p_x + p_y + p_z, 'p_x + p_y + p_z') 101 102 self._num_qubits = 1 103 self._error_probabilities = {'I': p_i, 'X': p_x, 'Y': p_y, 'Z': p_z} 104 105 def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: 106 ps = [] 107 for pauli in self._error_probabilities: 108 Pi = np.identity(1) 109 for gate in pauli: 110 if gate == 'I': 111 Pi = np.kron(Pi, protocols.unitary(identity.I)) 112 elif gate == 'X': 113 Pi = np.kron(Pi, protocols.unitary(pauli_gates.X)) 114 elif gate == 'Y': 115 Pi = np.kron(Pi, protocols.unitary(pauli_gates.Y)) 116 elif gate == 'Z': 117 Pi = np.kron(Pi, protocols.unitary(pauli_gates.Z)) 118 ps.append(Pi) 119 return tuple(zip(self._error_probabilities.values(), ps)) 120 121 def _num_qubits_(self) -> int: 122 return self._num_qubits 123 124 def _has_mixture_(self) -> bool: 125 return True 126 127 def _value_equality_values_(self): 128 return self._num_qubits, hash(tuple(sorted(self._error_probabilities.items()))) 129 130 def __repr__(self) -> str: 131 return 'cirq.asymmetric_depolarize(' + f"error_probabilities={self._error_probabilities})" 132 133 def __str__(self) -> str: 134 return 'asymmetric_depolarize(' + f"error_probabilities={self._error_probabilities})" 135 136 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: 137 if self._num_qubits == 1: 138 if args.precision is not None: 139 return ( 140 f"A({self.p_x:.{args.precision}g}," 141 + f"{self.p_y:.{args.precision}g}," 142 + f"{self.p_z:.{args.precision}g})" 143 ) 144 return f"A({self.p_x},{self.p_y},{self.p_z})" 145 if args.precision is not None: 146 error_probabilities = [ 147 f"{pauli}:{p:.{args.precision}g}" for pauli, p in self._error_probabilities.items() 148 ] 149 else: 150 error_probabilities = [f"{pauli}:{p}" for pauli, p in self._error_probabilities.items()] 151 return f"A({', '.join(error_probabilities)})" 152 153 @property 154 def p_i(self) -> float: 155 """The probability that an Identity I and no other gate occurs.""" 156 if self._num_qubits != 1: 157 raise ValueError('num_qubits should be 1') 158 return self._error_probabilities.get('I', 0.0) 159 160 @property 161 def p_x(self) -> float: 162 """The probability that a Pauli X and no other gate occurs.""" 163 if self._num_qubits != 1: 164 raise ValueError('num_qubits should be 1') 165 return self._error_probabilities.get('X', 0.0) 166 167 @property 168 def p_y(self) -> float: 169 """The probability that a Pauli Y and no other gate occurs.""" 170 if self._num_qubits != 1: 171 raise ValueError('num_qubits should be 1') 172 return self._error_probabilities.get('Y', 0.0) 173 174 @property 175 def p_z(self) -> float: 176 """The probability that a Pauli Z and no other gate occurs.""" 177 if self._num_qubits != 1: 178 raise ValueError('num_qubits should be 1') 179 return self._error_probabilities.get('Z', 0.0) 180 181 @property 182 def num_qubits(self) -> int: 183 """The number of qubits""" 184 return self._num_qubits 185 186 @property 187 def error_probabilities(self) -> Dict[str, float]: 188 """A dictionary from Pauli gates to probability""" 189 return self._error_probabilities 190 191 def _json_dict_(self) -> Dict[str, Any]: 192 return protocols.obj_to_dict_helper(self, ['error_probabilities']) 193 194 def _approx_eq_(self, other: Any, atol: float) -> bool: 195 return ( 196 self.num_qubits == other.num_qubits 197 and np.isclose(self.p_i, other.p_i, atol=atol).item() 198 and np.isclose(self.p_x, other.p_x, atol=atol).item() 199 and np.isclose(self.p_y, other.p_y, atol=atol).item() 200 and np.isclose(self.p_z, other.p_z, atol=atol).item() 201 ) 202 203 204def asymmetric_depolarize( 205 p_x: Optional[float] = None, 206 p_y: Optional[float] = None, 207 p_z: Optional[float] = None, 208 error_probabilities: Optional[Dict[str, float]] = None, 209 tol: float = 1e-8, 210) -> AsymmetricDepolarizingChannel: 211 r"""Returns a AsymmetricDepolarizingChannel with given parameter. 212 213 This channel applies one of 4**n disjoint possibilities: nothing (the 214 identity channel) or one of the 4**n - 1 pauli gates. 215 216 This channel evolves a density matrix via 217 218 $$ 219 \sum_i p_i Pi \rho Pi 220 $$ 221 222 where i varies from 0 to 4**n-1 and Pi represents n-qubit Pauli operator 223 (including identity). The input $\rho$ is the density matrix before the 224 depolarization. 225 226 Args: 227 p_x: The probability that a Pauli X and no other gate occurs. 228 p_y: The probability that a Pauli Y and no other gate occurs. 229 p_z: The probability that a Pauli Z and no other gate occurs. 230 error_probabilities: Dictionary of string (Pauli operator) to its 231 probability. If the identity is missing from the list, it will 232 be added so that the total probability mass is 1. 233 tol: The tolerance used making sure the total probability mass is 234 equal to 1. 235 236 Examples of calls: 237 * Single qubit: AsymmetricDepolarizingChannel(0.2, 0.1, 0.3) 238 * Single qubit: AsymmetricDepolarizingChannel(p_z=0.3) 239 * Two qubits: AsymmetricDepolarizingChannel( 240 error_probabilities={'XX': 0.2}) 241 242 Raises: 243 ValueError: if the args or the sum of the args are not probabilities. 244 """ 245 return AsymmetricDepolarizingChannel(p_x, p_y, p_z, error_probabilities, tol) 246 247 248@value.value_equality 249class DepolarizingChannel(raw_types.Gate): 250 """A channel that depolarizes one or several qubits.""" 251 252 def __init__(self, p: float, n_qubits: int = 1) -> None: 253 r"""The symmetric depolarizing channel. 254 255 This channel applies one of 4**n disjoint possibilities: nothing (the 256 identity channel) or one of the 4**n - 1 pauli gates. The disjoint 257 probabilities of the non-identity Pauli gates are all the same, 258 p / (4**n - 1), and the identity is done with probability 1 - p. The 259 supplied probability must be a valid probability or else this 260 constructor will raise a ValueError. 261 262 263 This channel evolves a density matrix via 264 265 $$ 266 \rho \rightarrow (1 - p) \rho + p / (4**n - 1) \sum _i P_i \rho P_i 267 $$ 268 269 where $P_i$ are the $4^n - 1$ Pauli gates (excluding the identity). 270 271 Args: 272 p: The probability that one of the Pauli gates is applied. Each of 273 the Pauli gates is applied independently with probability 274 p / (4**n - 1). 275 n_qubits: the number of qubits. 276 277 Raises: 278 ValueError: if p is not a valid probability. 279 """ 280 281 error_probabilities = {} 282 283 p_depol = p / (4 ** n_qubits - 1) 284 p_identity = 1.0 - p 285 for pauli_tuple in itertools.product(['I', 'X', 'Y', 'Z'], repeat=n_qubits): 286 pauli_string = ''.join(pauli_tuple) 287 if pauli_string == 'I' * n_qubits: 288 error_probabilities[pauli_string] = p_identity 289 else: 290 error_probabilities[pauli_string] = p_depol 291 292 self._p = p 293 self._n_qubits = n_qubits 294 295 self._delegate = AsymmetricDepolarizingChannel(error_probabilities=error_probabilities) 296 297 def _qid_shape_(self): 298 return (2,) * self._n_qubits 299 300 def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: 301 return self._delegate._mixture_() 302 303 def _has_mixture_(self) -> bool: 304 return True 305 306 def _value_equality_values_(self): 307 return self._p 308 309 def __repr__(self) -> str: 310 if self._n_qubits == 1: 311 return f"cirq.depolarize(p={self._p})" 312 return f"cirq.depolarize(p={self._p},n_qubits={self._n_qubits})" 313 314 def __str__(self) -> str: 315 if self._n_qubits == 1: 316 return f"depolarize(p={self._p})" 317 return f"depolarize(p={self._p},n_qubits={self._n_qubits})" 318 319 def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']) -> bool: 320 from cirq.sim import clifford 321 322 if isinstance(args, clifford.ActOnCliffordTableauArgs): 323 if args.prng.random() < self._p: 324 gate = args.prng.choice([pauli_gates.X, pauli_gates.Y, pauli_gates.Z]) 325 protocols.act_on(gate, args, qubits) 326 return True 327 return NotImplemented 328 329 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> Tuple[str, ...]: 330 result: Tuple[str, ...] 331 if args.precision is not None: 332 result = (f"D({self._p:.{args.precision}g})",) 333 else: 334 result = (f"D({self._p})",) 335 while len(result) < self.num_qubits(): 336 result += (f"#{len(result) + 1}",) 337 return result 338 339 @property 340 def p(self) -> float: 341 """The probability that one of the Pauli gates is applied. 342 343 Each of the Pauli gates is applied independently with probability 344 p / (4**n_qubits - 1). 345 """ 346 return self._p 347 348 @property 349 def n_qubits(self) -> int: 350 """The number of qubits""" 351 return self._n_qubits 352 353 def _json_dict_(self) -> Dict[str, Any]: 354 if self._n_qubits == 1: 355 return protocols.obj_to_dict_helper(self, ['p']) 356 return protocols.obj_to_dict_helper(self, ['p', 'n_qubits']) 357 358 def _approx_eq_(self, other: Any, atol: float) -> bool: 359 return np.isclose(self.p, other.p, atol=atol).item() and self.n_qubits == other.n_qubits 360 361 362def depolarize(p: float, n_qubits: int = 1) -> DepolarizingChannel: 363 r"""Returns a DepolarizingChannel with given probability of error. 364 365 This channel applies one of 4**n disjoint possibilities: nothing (the 366 identity channel) or one of the 4**n - 1 pauli gates. The disjoint 367 probabilities of the non-identity Pauli gates are all the same, 368 p / (4**n - 1), and the identity is done with probability 1 - p. The 369 supplied probability must be a valid probability or else this constructor 370 will raise a ValueError. 371 372 This channel evolves a density matrix via 373 374 $$ 375 \rho \rightarrow (1 - p) \rho + p / (4**n - 1) \sum _i P_i \rho P_i 376 $$ 377 378 where $P_i$ are the $4^n - 1$ Pauli gates (excluding the identity). 379 380 Args: 381 p: The probability that one of the Pauli gates is applied. Each of 382 the Pauli gates is applied independently with probability 383 p / (4**n - 1). 384 n_qubits: The number of qubits. 385 386 Raises: 387 ValueError: if p is not a valid probability. 388 """ 389 return DepolarizingChannel(p, n_qubits) 390 391 392@value.value_equality 393class GeneralizedAmplitudeDampingChannel(gate_features.SingleQubitGate): 394 """Dampen qubit amplitudes through non ideal dissipation. 395 396 This channel models the effect of energy dissipation into the environment 397 as well as the environment depositing energy into the system. 398 """ 399 400 def __init__(self, p: float, gamma: float) -> None: 401 r"""The generalized amplitude damping channel. 402 403 Construct a channel to model energy dissipation into the environment 404 as well as the environment depositing energy into the system. The 405 probabilities with which the energy exchange occur are given by `gamma`, 406 and the probability of the environment being not excited is given by 407 `p`. 408 409 The stationary state of this channel is the diagonal density matrix 410 with probability `p` of being |0⟩ and probability `1-p` of being |1⟩. 411 412 This channel evolves a density matrix via 413 414 $$ 415 \rho \rightarrow M_0 \rho M_0^\dagger 416 + M_1 \rho M_1^\dagger 417 + M_2 \rho M_2^\dagger 418 + M_3 \rho M_3^\dagger 419 $$ 420 421 With 422 423 $$ 424 \begin{aligned} 425 M_0 =& \sqrt{p} \begin{bmatrix} 426 1 & 0 \\ 427 0 & \sqrt{1 - \gamma} 428 \end{bmatrix} 429 \\ 430 M_1 =& \sqrt{p} \begin{bmatrix} 431 0 & \sqrt{\gamma} \\ 432 0 & 0 433 \end{bmatrix} 434 \\ 435 M_2 =& \sqrt{1-p} \begin{bmatrix} 436 \sqrt{1-\gamma} & 0 \\ 437 0 & 1 438 \end{bmatrix} 439 \\ 440 M_3 =& \sqrt{1-p} \begin{bmatrix} 441 0 & 0 \\ 442 \sqrt{\gamma} & 0 443 \end{bmatrix} 444 \end{aligned} 445 $$ 446 447 Args: 448 p: the probability of the environment being not excited 449 gamma: the probability of energy transfer 450 451 Raises: 452 ValueError: if gamma or p is not a valid probability. 453 """ 454 self._p = value.validate_probability(p, 'p') 455 self._gamma = value.validate_probability(gamma, 'gamma') 456 457 def _kraus_(self) -> Iterable[np.ndarray]: 458 p0 = np.sqrt(self._p) 459 p1 = np.sqrt(1.0 - self._p) 460 sqrt_g = np.sqrt(self._gamma) 461 sqrt_g1 = np.sqrt(1.0 - self._gamma) 462 return ( 463 p0 * np.array([[1.0, 0.0], [0.0, sqrt_g1]]), 464 p0 * np.array([[0.0, sqrt_g], [0.0, 0.0]]), 465 p1 * np.array([[sqrt_g1, 0.0], [0.0, 1.0]]), 466 p1 * np.array([[0.0, 0.0], [sqrt_g, 0.0]]), 467 ) 468 469 def _has_kraus_(self) -> bool: 470 return True 471 472 def _value_equality_values_(self): 473 return self._p, self._gamma 474 475 def __repr__(self) -> str: 476 return f'cirq.generalized_amplitude_damp(p={self._p!r},gamma={self._gamma!r})' 477 478 def __str__(self) -> str: 479 return f'generalized_amplitude_damp(p={self._p!r},gamma={self._gamma!r})' 480 481 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: 482 if args.precision is not None: 483 f = '{:.' + str(args.precision) + 'g}' 484 return f'GAD({f},{f})'.format(self._p, self._gamma) 485 return f'GAD({self._p!r},{self._gamma!r})' 486 487 @property 488 def p(self) -> float: 489 """The probability of the environment being not excited.""" 490 return self._p 491 492 @property 493 def gamma(self) -> float: 494 """The probability of energy transfer.""" 495 return self._gamma 496 497 def _json_dict_(self) -> Dict[str, Any]: 498 return protocols.obj_to_dict_helper(self, ['p', 'gamma']) 499 500 def _approx_eq_(self, other: Any, atol: float) -> bool: 501 return ( 502 np.isclose(self.gamma, other.gamma, atol=atol).item() 503 and np.isclose(self.p, other.p, atol=atol).item() 504 ) 505 506 507# TODO(#3388) Add summary line to docstring. 508# pylint: disable=docstring-first-line-empty 509def generalized_amplitude_damp(p: float, gamma: float) -> GeneralizedAmplitudeDampingChannel: 510 r""" 511 Returns a GeneralizedAmplitudeDampingChannel with the given 512 probabilities gamma and p. 513 514 This channel evolves a density matrix via: 515 516 $$ 517 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 518 + M_2 \rho M_2^\dagger + M_3 \rho M_3^\dagger 519 $$ 520 521 With: 522 523 $$ 524 \begin{aligned} 525 M_0 =& \sqrt{p} \begin{bmatrix} 526 1 & 0 \\ 527 0 & \sqrt{1 - \gamma} 528 \end{bmatrix} 529 \\ 530 M_1 =& \sqrt{p} \begin{bmatrix} 531 0 & \sqrt{\gamma} \\ 532 0 & 0 533 \end{bmatrix} 534 \\ 535 M_2 =& \sqrt{1-p} \begin{bmatrix} 536 \sqrt{1-\gamma} & 0 \\ 537 0 & 1 538 \end{bmatrix} 539 \\ 540 M_3 =& \sqrt{1-p} \begin{bmatrix} 541 0 & 0 \\ 542 \sqrt{\gamma} & 0 543 \end{bmatrix} 544 \end{aligned} 545 $$ 546 547 Args: 548 gamma: the probability of the interaction being dissipative. 549 p: the probability of the qubit and environment exchanging energy. 550 551 Raises: 552 ValueError: gamma or p is not a valid probability. 553 """ 554 return GeneralizedAmplitudeDampingChannel(p, gamma) 555 556 557# pylint: enable=docstring-first-line-empty 558@value.value_equality 559class AmplitudeDampingChannel(gate_features.SingleQubitGate): 560 """Dampen qubit amplitudes through dissipation. 561 562 This channel models the effect of energy dissipation to the 563 surrounding environment. 564 """ 565 566 def __init__(self, gamma: float) -> None: 567 r"""The amplitude damping channel. 568 569 Construct a channel that dissipates energy. The probability of 570 energy exchange occurring is given by gamma. 571 572 This channel evolves a density matrix as follows: 573 574 $$ 575 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 576 $$ 577 578 With: 579 580 $$ 581 \begin{aligned} 582 M_0 =& \begin{bmatrix} 583 1 & 0 \\ 584 0 & \sqrt{1 - \gamma} 585 \end{bmatrix} 586 \\ 587 M_1 =& \begin{bmatrix} 588 0 & \sqrt{\gamma} \\ 589 0 & 0 590 \end{bmatrix} 591 \end{aligned} 592 $$ 593 594 Args: 595 gamma: the probability of the interaction being dissipative. 596 597 Raises: 598 ValueError: is gamma is not a valid probability. 599 """ 600 self._gamma = value.validate_probability(gamma, 'gamma') 601 self._delegate = GeneralizedAmplitudeDampingChannel(1.0, self._gamma) 602 603 def _kraus_(self) -> Iterable[np.ndarray]: 604 # just return first two kraus ops, we don't care about 605 # the last two. 606 return list(self._delegate._kraus_())[:2] 607 608 def _has_kraus_(self) -> bool: 609 return True 610 611 def _value_equality_values_(self): 612 return self._gamma 613 614 def __repr__(self) -> str: 615 return f'cirq.amplitude_damp(gamma={self._gamma!r})' 616 617 def __str__(self) -> str: 618 return f'amplitude_damp(gamma={self._gamma!r})' 619 620 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: 621 if args.precision is not None: 622 f = '{:.' + str(args.precision) + 'g}' 623 return f'AD({f})'.format(self._gamma) 624 return f'AD({self._gamma!r})' 625 626 @property 627 def gamma(self) -> float: 628 """The probability of the interaction being dissipative.""" 629 return self._gamma 630 631 def _json_dict_(self) -> Dict[str, Any]: 632 return protocols.obj_to_dict_helper(self, ['gamma']) 633 634 def _approx_eq_(self, other: Any, atol: float) -> bool: 635 return np.isclose(self.gamma, other.gamma, atol=atol).item() 636 637 638def amplitude_damp(gamma: float) -> AmplitudeDampingChannel: 639 r"""Returns an AmplitudeDampingChannel with the given probability gamma. 640 641 This channel evolves a density matrix via: 642 643 $$ 644 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 645 $$ 646 647 With: 648 649 $$ 650 \begin{aligned} 651 M_0 =& \begin{bmatrix} 652 1 & 0 \\ 653 0 & \sqrt{1 - \gamma} 654 \end{bmatrix} 655 \\ 656 M_1 =& \begin{bmatrix} 657 0 & \sqrt{\gamma} \\ 658 0 & 0 659 \end{bmatrix} 660 \end{aligned} 661 $$ 662 663 Args: 664 gamma: the probability of the interaction being dissipative. 665 666 Raises: 667 ValueError: if gamma is not a valid probability. 668 """ 669 return AmplitudeDampingChannel(gamma) 670 671 672@value.value_equality 673class ResetChannel(gate_features.SingleQubitGate): 674 """Reset a qubit back to its |0⟩ state. 675 676 The reset channel is equivalent to performing an unobserved measurement 677 which then controls a bit flip onto the targeted qubit. 678 """ 679 680 def __init__(self, dimension: int = 2) -> None: 681 r"""The reset channel. 682 683 Construct a channel that resets the qubit. 684 685 This channel evolves a density matrix as follows: 686 687 $$ 688 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 689 $$ 690 691 With: 692 693 $$ 694 \begin{aligned} 695 M_0 =& \begin{bmatrix} 696 1 & 0 \\ 697 0 & 0 698 \end{bmatrix} 699 \\ 700 M_1 =& \begin{bmatrix} 701 0 & 1 \\ 702 0 & 0 703 \end{bmatrix} 704 \end{aligned} 705 $$ 706 707 Args: 708 dimension: Specify this argument when resetting a qudit. There will 709 be `dimension` number of dimension by dimension matrices 710 describing the channel, each with a 1 at a different position in 711 the top row. 712 """ 713 self._dimension = dimension 714 715 def _has_stabilizer_effect_(self) -> Optional[bool]: 716 return True 717 718 def _qasm_(self, args: 'cirq.QasmArgs', qubits: Tuple['cirq.Qid', ...]) -> Optional[str]: 719 args.validate_version('2.0') 720 return args.format('reset {0};\n', qubits[0]) 721 722 def _qid_shape_(self): 723 return (self._dimension,) 724 725 def _act_on_(self, args: 'cirq.ActOnArgs', qubits: Sequence['cirq.Qid']): 726 from cirq import sim, ops 727 728 if isinstance(args, sim.ActOnStabilizerCHFormArgs): 729 axe = args.qubit_map[qubits[0]] 730 if args.state._measure(axe, args.prng): 731 ops.X._act_on_(args, qubits) 732 return True 733 734 if isinstance(args, sim.ActOnStateVectorArgs): 735 # Do a silent measurement. 736 axes = args.get_axes(qubits) 737 measurements, _ = sim.measure_state_vector( 738 args.target_tensor, 739 axes, 740 out=args.target_tensor, 741 qid_shape=args.target_tensor.shape, 742 ) 743 result = measurements[0] 744 745 # Use measurement result to zero the qid. 746 if result: 747 zero = args.subspace_index(axes, 0) 748 other = args.subspace_index(axes, result) 749 args.target_tensor[zero] = args.target_tensor[other] 750 args.target_tensor[other] = 0 751 752 return True 753 754 return NotImplemented 755 756 def _kraus_(self) -> Iterable[np.ndarray]: 757 # The first axis is over the list of channel matrices 758 channel = np.zeros((self._dimension,) * 3, dtype=np.complex64) 759 channel[:, 0, :] = np.eye(self._dimension) 760 return channel 761 762 def _has_kraus_(self) -> bool: 763 return True 764 765 def _value_equality_values_(self): 766 return self._dimension 767 768 def __repr__(self) -> str: 769 if self._dimension == 2: 770 return 'cirq.ResetChannel()' 771 else: 772 return f'cirq.ResetChannel(dimension={self._dimension!r})' 773 774 def __str__(self) -> str: 775 return 'reset' 776 777 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: 778 return 'R' 779 780 @property 781 def dimension(self) -> int: 782 """The dimension of the qudit being reset.""" 783 return self._dimension 784 785 def _json_dict_(self) -> Dict[str, Any]: 786 return protocols.obj_to_dict_helper(self, ['dimension']) 787 788 789def reset(qubit: 'cirq.Qid') -> raw_types.Operation: 790 """Returns a `ResetChannel` on the given qubit.""" 791 return ResetChannel(qubit.dimension).on(qubit) 792 793 794def reset_each(*qubits: 'cirq.Qid') -> List[raw_types.Operation]: 795 """Returns a list of `ResetChannel` instances on the given qubits.""" 796 return [ResetChannel(q.dimension).on(q) for q in qubits] 797 798 799@value.value_equality 800class PhaseDampingChannel(gate_features.SingleQubitGate): 801 """Dampen qubit phase. 802 803 This channel models phase damping which is the loss of quantum 804 information without the loss of energy. 805 """ 806 807 def __init__(self, gamma: float) -> None: 808 r"""The phase damping channel. 809 810 Construct a channel that enacts a phase damping constant gamma. 811 812 This channel evolves a density matrix via: 813 814 $$ 815 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 816 $$ 817 818 With: 819 820 $$ 821 \begin{aligned} 822 M_0 =& \begin{bmatrix} 823 1 & 0 \\ 824 0 & \sqrt{1 - \gamma} 825 \end{bmatrix} 826 \\ 827 M_1 =& \begin{bmatrix} 828 0 & 0 \\ 829 0 & \sqrt{\gamma} 830 \end{bmatrix} 831 \end{aligned} 832 $$ 833 834 Args: 835 gamma: The damping constant. 836 837 Raises: 838 ValueError: if gamma is not a valid probability. 839 """ 840 self._gamma = value.validate_probability(gamma, 'gamma') 841 842 def _kraus_(self) -> Iterable[np.ndarray]: 843 return ( 844 np.array([[1.0, 0.0], [0.0, np.sqrt(1.0 - self._gamma)]]), 845 np.array([[0.0, 0.0], [0.0, np.sqrt(self._gamma)]]), 846 ) 847 848 def _has_kraus_(self) -> bool: 849 return True 850 851 def _value_equality_values_(self): 852 return self._gamma 853 854 def __repr__(self) -> str: 855 return f'cirq.phase_damp(gamma={self._gamma!r})' 856 857 def __str__(self) -> str: 858 return f'phase_damp(gamma={self._gamma!r})' 859 860 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: 861 if args.precision is not None: 862 f = '{:.' + str(args.precision) + 'g}' 863 return f'PD({f})'.format(self._gamma) 864 return f'PD({self._gamma!r})' 865 866 @property 867 def gamma(self) -> float: 868 """The damping constant.""" 869 return self._gamma 870 871 def _json_dict_(self) -> Dict[str, Any]: 872 return protocols.obj_to_dict_helper(self, ['gamma']) 873 874 def _approx_eq_(self, other: Any, atol: float) -> bool: 875 return np.isclose(self._gamma, other._gamma, atol=atol).item() 876 877 878def phase_damp(gamma: float) -> PhaseDampingChannel: 879 r"""Creates a PhaseDampingChannel with damping constant gamma. 880 881 This channel evolves a density matrix via: 882 883 $$ 884 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 885 $$ 886 887 With: 888 889 $$ 890 \begin{aligned} 891 M_0 =& \begin{bmatrix} 892 1 & 0 \\ 893 0 & \sqrt{1 - \gamma} 894 \end{bmatrix} 895 \\ 896 M_1 =& \begin{bmatrix} 897 0 & 0 \\ 898 0 & \sqrt{\gamma} 899 \end{bmatrix} 900 \end{aligned} 901 $$ 902 903 Args: 904 gamma: The damping constant. 905 906 Raises: 907 ValueError: is gamma is not a valid probability. 908 """ 909 return PhaseDampingChannel(gamma) 910 911 912@value.value_equality 913class PhaseFlipChannel(gate_features.SingleQubitGate): 914 """Probabilistically flip the sign of the phase of a qubit.""" 915 916 def __init__(self, p: float) -> None: 917 r"""The phase flip channel. 918 919 Construct a channel to flip the phase with probability p. 920 921 This channel evolves a density matrix via: 922 923 $$ 924 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 925 $$ 926 927 With: 928 929 $$ 930 \begin{aligned} 931 M_0 =& \sqrt{1 - p} \begin{bmatrix} 932 1 & 0 \\ 933 0 & 1 934 \end{bmatrix} 935 \\ 936 M_1 =& \sqrt{p} \begin{bmatrix} 937 1 & 0 \\ 938 0 & -1 939 \end{bmatrix} 940 \end{aligned} 941 $$ 942 943 Args: 944 p: the probability of a phase flip. 945 946 Raises: 947 ValueError: if p is not a valid probability. 948 """ 949 self._p = value.validate_probability(p, 'p') 950 self._delegate = AsymmetricDepolarizingChannel(0.0, 0.0, p) 951 952 def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: 953 mixture = self._delegate._mixture_() 954 # just return identity and z term 955 return (mixture[0], mixture[3]) 956 957 def _has_mixture_(self) -> bool: 958 return True 959 960 def _value_equality_values_(self): 961 return self._p 962 963 def __repr__(self) -> str: 964 return f'cirq.phase_flip(p={self._p!r})' 965 966 def __str__(self) -> str: 967 return f'phase_flip(p={self._p!r})' 968 969 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: 970 if args.precision is not None: 971 f = '{:.' + str(args.precision) + 'g}' 972 return f'PF({f})'.format(self._p) 973 return f'PF({self._p!r})' 974 975 @property 976 def p(self) -> float: 977 """The probability of a phase flip.""" 978 return self._p 979 980 def _json_dict_(self) -> Dict[str, Any]: 981 return protocols.obj_to_dict_helper(self, ['p']) 982 983 def _approx_eq_(self, other: Any, atol: float) -> bool: 984 return np.isclose(self.p, other.p, atol=atol).item() 985 986 987def _phase_flip_Z() -> common_gates.ZPowGate: 988 """Returns a cirq.Z which corresponds to a guaranteed phase flip.""" 989 return common_gates.ZPowGate() 990 991 992def _phase_flip(p: float) -> PhaseFlipChannel: 993 r"""Returns a PhaseFlipChannel that flips a qubit's phase with probability p. 994 995 This channel evolves a density matrix via: 996 997 $$ 998 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 999 $$ 1000 1001 With: 1002 1003 $$ 1004 \begin{aligned} 1005 M_0 =& \sqrt{p} \begin{bmatrix} 1006 1 & 0 \\ 1007 0 & 1 1008 \end{bmatrix} 1009 \\ 1010 M_1 =& \sqrt{1-p} \begin{bmatrix} 1011 1 & 0 \\ 1012 0 & -1 1013 \end{bmatrix} 1014 \end{aligned} 1015 $$ 1016 1017 Args: 1018 p: the probability of a phase flip. 1019 1020 Raises: 1021 ValueError: if p is not a valid probability. 1022 """ 1023 return PhaseFlipChannel(p) 1024 1025 1026# TODO(#3388) Add summary line to docstring. 1027# pylint: disable=docstring-first-line-empty 1028def phase_flip(p: Optional[float] = None) -> Union[common_gates.ZPowGate, PhaseFlipChannel]: 1029 r""" 1030 Returns a PhaseFlipChannel that flips a qubit's phase with probability p 1031 if p is None, return a guaranteed phase flip in the form of a Z operation. 1032 1033 This channel evolves a density matrix via: 1034 1035 $$ 1036 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 1037 $$ 1038 1039 With: 1040 1041 $$ 1042 \begin{aligned} 1043 M_0 =& \sqrt{p} \begin{bmatrix} 1044 1 & 0 \\ 1045 0 & 1 1046 \end{bmatrix} 1047 \\ 1048 M_1 =& \sqrt{1-p} \begin{bmatrix} 1049 1 & 0 \\ 1050 0 & -1 1051 \end{bmatrix} 1052 \end{aligned} 1053 $$ 1054 1055 Args: 1056 p: the probability of a phase flip. 1057 1058 Raises: 1059 ValueError: if p is not a valid probability. 1060 """ 1061 if p is None: 1062 return _phase_flip_Z() 1063 1064 return _phase_flip(p) 1065 1066 1067# pylint: enable=docstring-first-line-empty 1068@value.value_equality 1069class BitFlipChannel(gate_features.SingleQubitGate): 1070 r"""Probabilistically flip a qubit from 1 to 0 state or vice versa.""" 1071 1072 def __init__(self, p: float) -> None: 1073 r"""The bit flip channel. 1074 1075 Construct a channel that flips a qubit with probability p. 1076 1077 This channel evolves a density matrix via: 1078 1079 $$ 1080 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 1081 $$ 1082 1083 With: 1084 1085 $$ 1086 \begin{aligned} 1087 M_0 =& \sqrt{1 - p} \begin{bmatrix} 1088 1 & 0 \\ 1089 0 & 1 1090 \end{bmatrix} 1091 \\ 1092 M_1 =& \sqrt{p} \begin{bmatrix} 1093 0 & 1 \\ 1094 1 & 0 1095 \end{bmatrix} 1096 \end{aligned} 1097 $$ 1098 1099 Args: 1100 p: the probability of a bit flip. 1101 1102 Raises: 1103 ValueError: if p is not a valid probability. 1104 """ 1105 self._p = value.validate_probability(p, 'p') 1106 self._delegate = AsymmetricDepolarizingChannel(p, 0.0, 0.0) 1107 1108 def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]: 1109 mixture = self._delegate._mixture_() 1110 # just return identity and x term 1111 return (mixture[0], mixture[1]) 1112 1113 def _has_mixture_(self) -> bool: 1114 return True 1115 1116 def _value_equality_values_(self): 1117 return self._p 1118 1119 def __repr__(self) -> str: 1120 return f'cirq.bit_flip(p={self._p!r})' 1121 1122 def __str__(self) -> str: 1123 return f'bit_flip(p={self._p!r})' 1124 1125 def _circuit_diagram_info_(self, args: 'protocols.CircuitDiagramInfoArgs') -> str: 1126 if args.precision is not None: 1127 f = '{:.' + str(args.precision) + 'g}' 1128 return f'BF({f})'.format(self._p) 1129 return f'BF({self._p!r})' 1130 1131 @property 1132 def p(self) -> float: 1133 """The probability of a bit flip.""" 1134 return self._p 1135 1136 def _json_dict_(self) -> Dict[str, Any]: 1137 return protocols.obj_to_dict_helper(self, ['p']) 1138 1139 def _approx_eq_(self, other: Any, atol: float) -> bool: 1140 return np.isclose(self._p, other._p, atol=atol).item() 1141 1142 1143# TODO(#3388) Add summary line to docstring. 1144# pylint: disable=docstring-first-line-empty 1145def _bit_flip(p: float) -> BitFlipChannel: 1146 r""" 1147 Construct a BitFlipChannel that flips a qubit state 1148 with probability of a flip given by p. 1149 1150 This channel evolves a density matrix via: 1151 1152 $$ 1153 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 1154 $$ 1155 1156 With: 1157 1158 $$ 1159 \begin{aligned} 1160 M_0 =& \sqrt{p} \begin{bmatrix} 1161 1 & 0 \\ 1162 0 & 1 1163 \end{bmatrix} 1164 \\ 1165 M_1 =& \sqrt{1-p} \begin{bmatrix} 1166 0 & 1 \\ 1167 1 & -0 1168 \end{bmatrix} 1169 \end{aligned} 1170 $$ 1171 1172 Args: 1173 p: the probability of a bit flip. 1174 1175 Raises: 1176 ValueError: if p is not a valid probability. 1177 """ 1178 return BitFlipChannel(p) 1179 1180 1181# TODO(#3388) Add summary line to docstring. 1182def bit_flip(p: Optional[float] = None) -> Union[common_gates.XPowGate, BitFlipChannel]: 1183 r""" 1184 Construct a BitFlipChannel that flips a qubit state 1185 with probability of a flip given by p. If p is None, return 1186 a guaranteed flip in the form of an X operation. 1187 1188 This channel evolves a density matrix via 1189 1190 $$ 1191 \rho \rightarrow M_0 \rho M_0^\dagger + M_1 \rho M_1^\dagger 1192 $$ 1193 1194 With 1195 1196 $$ 1197 \begin{aligned} 1198 M_0 =& \sqrt{p} \begin{bmatrix} 1199 1 & 0 \\ 1200 0 & 1 1201 \end{bmatrix} 1202 \\ 1203 M_1 =& \sqrt{1-p} \begin{bmatrix} 1204 0 & 1 \\ 1205 1 & -0 1206 \end{bmatrix} 1207 \end{aligned} 1208 $$ 1209 1210 Args: 1211 p: the probability of a bit flip. 1212 1213 Raises: 1214 ValueError: if p is not a valid probability. 1215 """ 1216 if p is None: 1217 return pauli_gates.X 1218 1219 return _bit_flip(p) 1220 1221 1222# pylint: enable=docstring-first-line-empty 1223