Channels#

Channels are implemented in Qibo as additional gates and can be accessed from the qibo.gates module. Channels can be used on density matrices to perform noisy simulations. Channels that inherit qibo.gates.UnitaryChannel can also be applied to state vectors using sampling and repeated execution. For more information on the use of channels to simulate noise we refer to How to perform noisy simulation? The following channels are currently implemented:

Kraus channel#

class qibo.gates.KrausChannel(qubits, operators)[source]#

General channel defined by arbitrary Kraus operators.

Implements the following transformation:

\[\mathcal{E}(\rho ) = \sum _k A_k \rho A_k^\dagger\]

where A are arbitrary Kraus operators given by the user. Note that Kraus operators set should be trace preserving, however this is not checked. Simulation of this gate requires the use of density matrices. For more information on channels and Kraus operators please check J. Preskill’s notes.

Parameters:
  • qubits (int or list or tuple) – Qubits that the Kraus operators act on. Type int and tuple will be considered as the same qubit ids for all operators. A list should contain tuples of qubits corresponding to each operator. Can be [] if operators are of type qibo.gates.Gate, otherwise adds given gates on specified qubits.

  • operators (list) – List of Kraus operators Ak as matrices of type ndarray | tf.Tensor or gates qibo.gates.Gate.

Example

import numpy as np

from qibo import Circuit, gates

# initialize circuit with 3 qubits
circuit = Circuit(3, density_matrix=True)
# define a sqrt(0.4) * X gate
a_1 = np.sqrt(0.4) * np.array([[0, 1], [1, 0]])
# define a sqrt(0.6) * CNOT gate
a_2 = np.sqrt(0.6) * np.array([[1, 0, 0, 0], [0, 1, 0, 0],
                              [0, 0, 0, 1], [0, 0, 1, 0]])
# define the channel rho -> 0.4 X{1} rho X{1} + 0.6 CNOT{0, 2} rho CNOT{0, 2}
channel_1 = gates.KrausChannel([(1,), (0, 2)], [a_1, a_2])
# add channel to the circuit
circuit.add(channel_1)

# define the same channel using qibo.gates.Unitary
a_1 = gates.Unitary(a_1, 1)
a_2 = gates.Unitary(a_2, 0, 2)
channel_2 = gates.KrausChannel([], [a_1, a_2])
# add channel to the circuit
circuit.add(channel_2)

# define the channel rho -> 0.4 X{0} rho X{0} + 0.6 CNOT{1, 2} rho CNOT{1, 2}
channel_3 = gates.KrausChannel([(0,), (1, 2)], [a_1, a_2])
# add channel to the circuit
circuit.add(channel_3)
on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Unitary channel#

class qibo.gates.UnitaryChannel(qubits, operators)[source]#

Channel that is a probabilistic sum of unitary operations.

Implements the following transformation:

\[\mathcal{E}(\rho ) = \left (1 - \sum _k p_k \right )\rho + \sum _k p_k U_k \rho U_k^\dagger\]

where U are arbitrary unitary operators and p are floats between 0 and 1. Note that unlike qibo.gates.KrausChannel which requires density matrices, it is possible to simulate the unitary channel using state vectors and probabilistic sampling. For more information on this approach we refer to Using repeated execution.

Parameters:
  • qubits (int or list or tuple) – Qubits that the unitary operators act on. Types int and tuple will be considered as the same qubit(s) for all unitaries. A list should contain tuples of qubits corresponding to each operator. Can be [] if operators are of type qibo.gates.Gate, otherwise adds given gates on specified qubits.

  • operators (list) – List of operators as pairs (pk, Uk) where pk is float probability corresponding to a unitary Uk of type ndarray/tf.Tensor or gates qibo.gates.Gate.

on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Pauli noise channel#

class qibo.gates.PauliNoiseChannel(qubits, operators)[source]#

Multi-qubit noise channel that applies Pauli operators with given probabilities.

Implements the following transformation:

\[\mathcal{E}(\rho ) = \left (1 - \sum _{k} p_{k} \right ) \, \rho + \sum_{k} \, p_{k} \, P_{k} \, \rho \, P_{k}\]

where \(P_{k}\) is the \(k\)-th Pauli string and \(p_{k}\) is the probability associated to \(P_{k}\).

Example

from itertools import product

import numpy as np

from qibo.gates.channels import PauliNoiseChannel

qubits = (0, 2)
nqubits = len(qubits)

# excluding the Identity operator
paulis = list(product(["I", "X"], repeat=nqubits))[1:]
# this next line is optional
paulis = [''.join(pauli) for pauli in paulis]

probabilities = np.random.rand(len(paulis) + 1)
probabilities /= np.sum(probabilities)
#Excluding probability of Identity operator
probabilities = probabilities[1:]

channel = PauliNoiseChannel(
    qubits, list(zip(paulis, probabilities))
)

This channel can be simulated using either density matrices or state vectors and sampling with repeated execution. See How to perform noisy simulation? for more information.

Parameters:
  • qubits (int or list or tuple) – Qubits that the noise acts on.

  • operators (list) – list of operators as pairs \((P_{k}, p_{k})\).

on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Depolarizing channel#

class qibo.gates.DepolarizingChannel(qubits, lam)[source]#

\(n\)-qubit Depolarizing quantum error channel,

\[\mathcal{E}(\rho ) = (1 - \lambda) \rho + \lambda \text{Tr}_q[\rho]\otimes \frac{I}{2^n}\]

where \(\lambda\) is the depolarizing error parameter and \(0 \le \lambda \le 4^n / (4^n - 1)\).

  • If \(\lambda = 1\) this is a completely depolarizing channel \(E(\rho) = I / 2^n\)

  • If \(\lambda = 4^n / (4^n - 1)\) this is a uniform Pauli error channel: \(E(\rho) = \sum_j P_j \rho P_j / (4^n - 1)\) for all \(P_j \neq I\).

Parameters:
  • qubits (int or list or tuple) – Qubit ids that the noise acts on.

  • lam (float) – Depolarizing error parameter.

on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Thermal relaxation channel#

class qibo.gates.ThermalRelaxationChannel(qubit, parameters)[source]#

Single-qubit thermal relaxation error channel.

Implements the following transformation:

If \(T_1 \geq T_2\):

\[\mathcal{E} (\rho ) = (1 - p_z - p_0 - p_1) \rho + p_z \, Z\rho Z + \mathrm{Tr}_{q}[\rho] \otimes (p_0 | 0\rangle \langle 0| + p_1|1\rangle \langle 1|)\]

while if \(T_1 < T_2\):

\[\mathcal{E}(\rho ) = \mathrm{Tr}_\mathcal{X} \left[\Lambda_{\mathcal{X}\mathcal{Y}} (\rho_\mathcal{X}^T \otimes I_{\mathcal{Y}}) \right]\]

with

\[\begin{split}\Lambda = \begin{pmatrix} 1 - p_1 & 0 & 0 & e^{-t / T_2} \\ 0 & p_1 & 0 & 0 \\ 0 & 0 & p_0 & 0 \\ e^{-t / T_2} & 0 & 0 & 1 - p_0 \end{pmatrix}\end{split}\]

where \(p_0 = (1 - e^{-t / T_1})(1 - \eta )\), \(p_1 = (1 - e^{-t / T_1})\eta\), and \(p_z = (e^{-t / T_1} - e^{-t / T_2})/2\). Here \(\eta\) is the excited_population and \(t\) is the time, both controlled by the user. This gate is based on Qiskit’s thermal relaxation error channel.

Parameters:
  • qubit (int) – Qubit id that the noise channel acts on.

  • parameters (list) – list of 3 or 4 parameters (t_1, t_2, time, excited_population=0), where t_1 (float): T1 relaxation time. Should satisfy t_1 > 0. t_2 (float): T2 dephasing time. Should satisfy t_1 > 0 and t_2 < 2 * t_1. time (float): the gate time for relaxation error. excited_population (float): the population of the excited state at equilibrium. Default is 0.

on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Amplitude damping channel#

class qibo.gates.AmplitudeDampingChannel(qubit, gamma)[source]#

Single-qubit amplitude damping channel in its Kraus representation, i.e.

\[\begin{split}K_{0} = \begin{pmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \\ \end{pmatrix} \,\, , \,\, K_{1} = \begin{pmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \\ \end{pmatrix}\end{split}\]
Parameters:
  • qubit (int) – Qubit id that the noise channel acts on.

  • gamma (float) – amplitude damping strength.

on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Phase damping channel#

class qibo.gates.PhaseDampingChannel(qubit, gamma)[source]#

Single-qubit phase damping channel in its Kraus representation, i.e.

\[\begin{split}K_{0} = \begin{pmatrix} 1 & 0 \\ 0 & \sqrt{1 - \gamma} \\ \end{pmatrix} \,\, , \,\, K_{1} = \begin{pmatrix} 0 & 0 \\ 0 & \sqrt{\gamma} \\ \end{pmatrix}\end{split}\]
Parameters:
  • qubit (int) – Qubit id that the noise channel acts on.

  • gamma (float) – phase damping strength.

on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Readout error channel#

class qibo.gates.ReadoutErrorChannel(qubits, probabilities)[source]#

Readout error channel implemented as a quantum-to-classical channel.

Parameters:
  • qubits (int or list or tuple) – Qubit ids that the channel acts on.

  • probabilities (ndarray) – row-stochastic matrix \(P\) with all readout transition probabilities.

Example

For 1 qubit, the transition matrix \(P\) would be

\[\begin{split}P = \begin{pmatrix} p(0 \, | \, 0) & p(1 \, | \, 0) \\ p(0 \, | \, 1) & p(1 \, | \, 1) \end{pmatrix} \, .\end{split}\]
on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───

Reset channel#

class qibo.gates.ResetChannel(qubit, probabilities)[source]#

Single-qubit reset channel.

Implements the following transformation:

\[\mathcal{E}(\rho ) = (1 - p_{0} - p_{1}) \rho + \mathrm{Tr}_{q}[\rho] \otimes (p_{0} \, |0\rangle \langle 0| + p_{1} \, |1\rangle \langle 1|),\]
Parameters:
  • qubit (int) – qubit id that the channel acts on.

  • probabilities (list or ndarray) – list \([p_{0}, p_{1}]\), where \(p_{0}\) and \(p_{1}\) are the probabilities to reset to 0 and 1, respectively.

on_qubits(qubit_map)[source]#

Creates the same gate targeting different qubits.

Parameters:

qubit_map (int) – Dictionary mapping original qubit indices to new ones.

Returns:

A qibo.gates.Gate object of the original gate type targeting the given qubits.

Example

from qibo import Circuit, gates
circuit = Circuit(4)

# Add some CNOT gates

# equivalent to gates.CNOT(2, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 3}))
# equivalent to gates.CNOT(3, 0)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 3, 3: 0}))
# equivalent to gates.CNOT(1, 3)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 1, 3: 3}))
# equivalent to gates.CNOT(2, 1)
circuit.add(gates.CNOT(2, 3).on_qubits({2: 2, 3: 1}))

circuit.draw()
0: ───X─────
1: ───|─o─X─
2: ─o─|─|─o─
3: ─X─o─X───