Source code for qiskit_aer.noise.errors.standard_errors

# This code is part of Qiskit.
#
# (C) Copyright IBM 2018-2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
Standard quantum computing error channels for Aer.
"""

import itertools as it

import numpy as np

from qiskit.circuit import Reset
from qiskit.circuit.library.standard_gates import IGate, XGate, ZGate
from qiskit.circuit.library.generalized_gates import UnitaryGate
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators import Pauli
from qiskit.quantum_info.operators.channel import Choi, Kraus
from qiskit.quantum_info.operators.predicates import is_identity_matrix
from qiskit.quantum_info.operators.predicates import is_unitary_matrix
from .quantum_error import QuantumError
from ..noiseerror import NoiseError


[docs]def kraus_error(noise_ops, canonical_kraus=False): """ Return a Kraus quantum error channel. Args: noise_ops (list[matrix]): Kraus matrices. canonical_kraus (bool): Convert input Kraus matrices into the canonical Kraus representation (default: False) Returns: QuantumError: The quantum error object. Raises: NoiseError: if error parameters are invalid. """ if not isinstance(noise_ops, (list, tuple)): raise NoiseError("Invalid Kraus error input.") if not noise_ops: raise NoiseError("Kraus error noise_ops must not be empty.") kraus = Kraus(noise_ops) if canonical_kraus: # Convert to Choi and back to get canonical Kraus kraus = Kraus(Choi(kraus)) return QuantumError(kraus)
[docs]def mixed_unitary_error(noise_ops): """ Return a mixed unitary quantum error channel. The input should be a list of pairs ``(U[j], p[j])``, where ``U[j]`` is a unitary matrix and ``p[j]`` is a probability. All probabilities must sum to 1 for the input ops to be valid. Args: noise_ops (list[pair[matrix, double]]): unitary error matrices. Returns: QuantumError: The quantum error object. Raises: NoiseError: if error parameters are invalid. """ # Error checking if not isinstance(noise_ops, (list, tuple, zip)): raise NoiseError("Input noise ops is not a list.") # Convert to numpy arrays noise_ops = [(np.array(op, dtype=complex), p) for op, p in noise_ops] if not noise_ops: raise NoiseError("Input noise list is empty.") # Check for identity unitaries prob_identity = 0.0 instructions = [] instructions_probs = [] num_qubits = int(np.log2(noise_ops[0][0].shape[0])) if noise_ops[0][0].shape != (2**num_qubits, 2**num_qubits): raise NoiseError("A unitary matrix in input noise_ops is not a multi-qubit matrix.") for unitary, prob in noise_ops: # Check unitary if unitary.shape != noise_ops[0][0].shape: raise NoiseError("Input matrices different size.") if not is_unitary_matrix(unitary): raise NoiseError("Input matrix is not unitary.") if is_identity_matrix(unitary): prob_identity += prob else: instr = UnitaryGate(unitary) instructions.append(instr) instructions_probs.append(prob) if prob_identity > 0: instructions.append(IGate()) instructions_probs.append(prob_identity) return QuantumError(zip(instructions, instructions_probs))
[docs]def coherent_unitary_error(unitary): """ Return a coherent unitary quantum error channel. Args: unitary (matrix like): unitary error matrix. Returns: QuantumError: The quantum error object. """ return mixed_unitary_error([(unitary, 1)])
[docs]def pauli_error(noise_ops): """ Return a mixed Pauli quantum error channel. The input should be a list of pairs ``(P[j], p[j])``, where ``P[j]`` is a ``Pauli`` object or string label, and ``p[j]`` is a probability. All probabilities must sum to 1 for the input ops to be valid. Args: noise_ops (list[pair[Pauli, double]]): Pauli error terms. Returns: QuantumError: The quantum error object. Raises: NoiseError: If depolarizing probability is less than 0 or greater than 1. """ # Error checking if not isinstance(noise_ops, (list, tuple, zip)): raise NoiseError("Input noise ops is not a list.") noise_ops = list(noise_ops) if not noise_ops: raise NoiseError("Input noise list is empty.") ops, probs = zip(*noise_ops) # unzip def to_pauli(op): if isinstance(op, Pauli): return op elif isinstance(op, str): try: return Pauli(op) except QiskitError: pass raise NoiseError("Invalid Pauli input operator: {}".format(op)) paulis = [to_pauli(op) for op in ops] num_qubits = paulis[0].num_qubits for pauli in paulis: if num_qubits != pauli.num_qubits: raise NoiseError("Pauli's are not all of the same length.") return QuantumError(zip(paulis, probs))
[docs]def depolarizing_error(param, num_qubits): r""" Return a depolarizing quantum error channel. The depolarizing channel is defined as: .. math:: E(ρ) = (1 - λ) ρ + λ \text{Tr}[ρ] \frac{I}{2^n} with :math:`0 \le λ \le 4^n / (4^n - 1)` where :math:`λ` is the depolarizing error param and :math:`n` is the number of qubits. * If :math:`λ = 0` this is the identity channel :math:`E(ρ) = ρ` * If :math:`λ = 1` this is a completely depolarizing channel :math:`E(ρ) = I / 2^n` * If :math:`λ = 4^n / (4^n - 1)` this is a uniform Pauli error channel: :math:`E(ρ) = \sum_j P_j ρ P_j / (4^n - 1)` for all :math:`P_j != I`. Args: param (double): depolarizing error parameter. num_qubits (int): the number of qubits for the error channel. Returns: QuantumError: The quantum error object. Raises: NoiseError: If noise parameters are invalid. """ if not isinstance(num_qubits, int) or num_qubits < 1: raise NoiseError("num_qubits must be a positive integer.") # Check that the depolarizing parameter gives a valid CPTP num_terms = 4**num_qubits max_param = num_terms / (num_terms - 1) if param < 0 or param > max_param: raise NoiseError("Depolarizing parameter must be in between 0 " "and {}.".format(max_param)) # Rescale completely depolarizing channel error probs # with the identity component removed prob_iden = 1 - param / max_param prob_pauli = param / num_terms probs = [prob_iden] + (num_terms - 1) * [prob_pauli] # Generate pauli strings. The order doesn't matter as long # as the all identity string is first. paulis = [Pauli("".join(tup)) for tup in it.product(["I", "X", "Y", "Z"], repeat=num_qubits)] return QuantumError(zip(paulis, probs))
[docs]def reset_error(prob0, prob1=0): r""" Return a single qubit reset quantum error channel. The error channel returned is given by the map .. math:: E(ρ) = (1 - p_0 - p_1) ρ + \text{Tr}[ρ] \left( p_0 |0 \rangle\langle 0| + p_1 |1 \rangle\langle 1| \right) where the probability of no reset is given by :math:`1 - p_0 - p_1`. Args: prob0 (double): reset probability to :math:`|0\rangle`. prob1 (double): reset probability to :math:`|1\rangle`. Returns: QuantumError: the quantum error object. Raises: NoiseError: If noise parameters are invalid. """ if prob0 < 0 or prob1 < 0 or prob0 > 1 or prob1 > 1 or (prob0 + prob1) > 1: raise NoiseError("Invalid reset probabilities.") noise_ops = [ ([(IGate(), [0])], 1 - prob0 - prob1), ([(Reset(), [0])], prob0), ([(Reset(), [0]), (XGate(), [0])], prob1), ] return QuantumError(noise_ops)
# pylint: disable=invalid-name
[docs]def thermal_relaxation_error(t1, t2, time, excited_state_population=0): r""" Return a single-qubit thermal relaxation quantum error channel. Args: t1 (double): the :math:`T_1` relaxation time constant. t2 (double): the :math:`T_2` relaxation time constant. time (double): the gate time for relaxation error. excited_state_population (double): the population of :math:`|1\rangle` state at equilibrium (default: 0). Returns: QuantumError: a quantum error object for a noise model. Raises: NoiseError: If noise parameters are invalid. Additional information: * For parameters to be valid :math:`T_1` and :math:`T_2` must satisfy :math:`T_2 \le 2 T_1`. * If :math:`T_2 \le T_1` the error can be expressed as a mixed reset and unitary error channel. * If :math:`T_1 < T_2 \le 2 T_1` the error must be expressed as a general non-unitary Kraus error channel. """ if excited_state_population < 0: raise NoiseError( "Invalid excited state population " "({} < 0).".format(excited_state_population) ) if excited_state_population > 1: raise NoiseError( "Invalid excited state population " "({} > 1).".format(excited_state_population) ) if time < 0: raise NoiseError("Invalid gate_time ({} < 0)".format(time)) if t1 <= 0: raise NoiseError("Invalid T_1 relaxation time parameter: T_1 <= 0.") if t2 <= 0: raise NoiseError("Invalid T_2 relaxation time parameter: T_2 <= 0.") if t2 - 2 * t1 > 0: raise NoiseError("Invalid T_2 relaxation time parameter: T_2 greater than 2 * T_1.") # T1 relaxation rate if t1 == np.inf: rate1 = 0 p_reset = 0 else: rate1 = 1 / t1 p_reset = 1 - np.exp(-time * rate1) # T2 dephasing rate if t2 == np.inf: rate2 = 0 exp_t2 = 1 else: rate2 = 1 / t2 exp_t2 = np.exp(-time * rate2) # Qubit state equilibrium probabilities p0 = 1 - excited_state_population p1 = excited_state_population if t2 > t1: # If T_2 > T_1 we must express this as a Kraus channel # We start with the Choi-matrix representation: chan = Choi( np.array( [ [1 - p1 * p_reset, 0, 0, exp_t2], [0, p1 * p_reset, 0, 0], [0, 0, p0 * p_reset, 0], [exp_t2, 0, 0, 1 - p0 * p_reset], ] ) ) return QuantumError(Kraus(chan)) else: # If T_2 < T_1 we can express this channel as a probabilistic # mixture of reset operations and unitary errors: circuits = [ [(IGate(), [0])], [(ZGate(), [0])], [(Reset(), [0])], [(Reset(), [0]), (XGate(), [0])], ] # Probability p_reset0 = p_reset * p0 p_reset1 = p_reset * p1 p_z = (1 - p_reset) * (1 - np.exp(-time * (rate2 - rate1))) / 2 p_identity = 1 - p_z - p_reset0 - p_reset1 probabilities = [p_identity, p_z, p_reset0, p_reset1] return QuantumError(zip(circuits, probabilities))
[docs]def phase_amplitude_damping_error( param_amp, param_phase, excited_state_population=0, canonical_kraus=True ): r""" Return a single-qubit combined phase and amplitude damping quantum error channel. The single-qubit combined phase and amplitude damping channel is described by the following Kraus matrices: .. code-block:: python A0 = sqrt(1 - p1) * [[1, 0], [0, sqrt(1 - a - b)]] A1 = sqrt(1 - p1) * [[0, sqrt(a)], [0, 0]] A2 = sqrt(1 - p1) * [[0, 0], [0, sqrt(b)]] B0 = sqrt(p1) * [[sqrt(1 - a - b), 0], [0, 1]] B1 = sqrt(p1) * [[0, 0], [sqrt(a), 0]] B2 = sqrt(p1) * [[sqrt(b), 0], [0, 0]] where ``a = param_amp``, ``b = param_phase``, and ``p1 = excited_state_population``. The equilibrium state after infinitely many applications of the channel is: .. code-block:: python rho_eq = [[1 - p1, 0]], [0, p1]] Args: param_amp (double): the amplitude damping error parameter. param_phase (double): the phase damping error parameter. excited_state_population (double): the population of :math:`|1\rangle` state at equilibrium (default: 0). canonical_kraus (bool): Convert input Kraus matrices into the canonical Kraus representation (default: True) Returns: QuantumError: a quantum error object for a noise model. Raises: NoiseError: If noise parameters are invalid. """ if param_amp < 0: raise NoiseError("Invalid amplitude damping to |0> parameter " "({} < 0)".format(param_amp)) if param_phase < 0: raise NoiseError("Invalid phase damping parameter " "({} < 0)".format(param_phase)) if param_phase + param_amp > 1: raise NoiseError( "Invalid amplitude and phase damping parameters " "({} + {} > 1)".format(param_phase, param_amp) ) if excited_state_population < 0: raise NoiseError( "Invalid excited state population " "({} < 0).".format(excited_state_population) ) if excited_state_population > 1: raise NoiseError( "Invalid excited state population " "({} > 1).".format(excited_state_population) ) c0 = np.sqrt(1 - excited_state_population) c1 = np.sqrt(excited_state_population) param = 1 - param_amp - param_phase # Damping ops to 0 state A0 = c0 * np.array([[1, 0], [0, np.sqrt(param)]], dtype=complex) A1 = c0 * np.array([[0, np.sqrt(param_amp)], [0, 0]], dtype=complex) A2 = c0 * np.array([[0, 0], [0, np.sqrt(param_phase)]], dtype=complex) # Damping ops to 1 state B0 = c1 * np.array([[np.sqrt(param), 0], [0, 1]], dtype=complex) B1 = c1 * np.array([[0, 0], [np.sqrt(param_amp), 0]], dtype=complex) B2 = c1 * np.array([[np.sqrt(param_phase), 0], [0, 0]], dtype=complex) # Select non-zero ops noise_ops = [a for a in [A0, A1, A2, B0, B1, B2] if np.linalg.norm(a) > 1e-10] return kraus_error(noise_ops, canonical_kraus=canonical_kraus)
[docs]def amplitude_damping_error(param_amp, excited_state_population=0, canonical_kraus=True): r""" Return a single-qubit generalized amplitude damping quantum error channel. The single-qubit amplitude damping channel is described by the following Kraus matrices: .. code-block:: python A0 = sqrt(1 - p1) * [[1, 0], [0, sqrt(1 - a)]] A1 = sqrt(1 - p1) * [[0, sqrt(a)], [0, 0]] B0 = sqrt(p1) * [[sqrt(1 - a), 0], [0, 1]] B1 = sqrt(p1) * [[0, 0], [sqrt(a), 0]] where ``a = param_amp``, ``p1 = excited_state_population``. The equilibrium state after infinitely many applications of the channel is: .. code-block:: python rho_eq = [[1 - p1, 0]], [0, p1]] Args: param_amp (double): the amplitude damping parameter. excited_state_population (double): the population of :math:`|0\rangle` state at equilibrium (default: 0). canonical_kraus (bool): Convert input Kraus matrices into the canonical Kraus representation (default: True) Returns: QuantumError: a quantum error object for a noise model. """ return phase_amplitude_damping_error( param_amp, 0, excited_state_population=excited_state_population, canonical_kraus=canonical_kraus, )
[docs]def phase_damping_error(param_phase, canonical_kraus=True): r""" Return a single-qubit generalized phase damping quantum error channel. The single-qubit phase damping channel is described by the following Kraus matrices: .. code-block:: python A0 = [[1, 0], [0, sqrt(1 - b)]] A2 = [[0, 0], [0, sqrt(b)]] where ``b = param_phase``. The equilibrium state after infinitely many applications of the channel is: .. code-block:: python rho_eq = [[rho_init[0, 0], 0]], [0, rho_init[1, 1]]] where ``rho_init`` is the input state ρ. Args: param_phase (double): the phase damping parameter. canonical_kraus (bool): Convert input Kraus matrices into the canonical Kraus representation (default: True) Returns: QuantumError: a quantum error object for a noise model. """ return phase_amplitude_damping_error( 0, param_phase, excited_state_population=0, canonical_kraus=canonical_kraus )