Skip to content

Commit

Permalink
Merge branch 'master' into qfusion
Browse files Browse the repository at this point in the history
  • Loading branch information
stavros11 committed Apr 21, 2022
2 parents b6ebbad + 69e91b9 commit 34e9363
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 0 deletions.
31 changes: 31 additions & 0 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,37 @@ Thermal relaxation channel

_______________________

Noise
-----

In Qibo it is possible to create a custom noise model using the
class :class:`qibo.noise.NoiseModel`. This enables the user to create
circuits where the noise is gate and qubit dependent.

For more information on the use of :class:`qibo.noise.NoiseModel` see
:ref:`How to perform noisy simulation? <noisemodel-example>`

.. autoclass:: qibo.noise.NoiseModel
:members:
:member-order: bysource

Quantum errors
^^^^^^^^^^^^^^

The quantum errors available to build a noise model are the following:

.. autoclass:: qibo.noise.PauliError
:members:
:member-order: bysource

.. autoclass:: qibo.noise.ThermalRelaxationError
:members:
:member-order: bysource

.. autoclass:: qibo.noise.ResetError
:members:
:member-order: bysource


.. _Hamiltonians:

Expand Down
56 changes: 56 additions & 0 deletions doc/source/code-examples/advancedexamples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,62 @@ initializing a :class:`qibo.core.circuit.DensityMatrixCircuit`
using the ``density_matrix=True`` flag during initialization and call
``.with_noise`` on this circuit.

.. _noisemodel-example:

Using a noise model
^^^^^^^^^^^^^^^^^^^

In a real quantum circuit some gates can be highly faulty and introduce errors.
In order to simulate this behavior Qibo provides the :class:`qibo.noise.NoiseModel`
class which can store errors that are gate-dependent using the
:meth:`qibo.noise.NoiseModel.add` method and generate the corresponding noisy circuit
with :meth:`qibo.noise.NoiseModel.apply`. The corresponding noise is applied after
every instance of the gate in the circuit. It is also possible to specify on which qubits
the noise will be added.

The current quantum errors available to build a custom noise model are:
:class:`qibo.noise.PauliError`, :class:`qibo.noise.ThermalRelaxationError` and
:class:`qibo.noise.ResetError`.

Here is an example on how to use a noise model:

.. testcode::

from qibo import models, gates
from qibo.noise import NoiseModel, PauliError

# Build specific noise model with 2 quantum errors:
# - Pauli error on H only for qubit 1.
# - Pauli error on CNOT for all the qubits.
noise = NoiseModel()
noise.add(PauliError(px = 0.5), gates.H, 1)
noise.add(PauliError(py = 0.5), gates.CNOT)

# Generate noiseless circuit.
c = models.Circuit(2)
c.add([gates.H(0), gates.H(1), gates.CNOT(0, 1)])

# Apply noise to the circuit according to the noise model.
noisy_c = noise.apply(c)

The noisy circuit defined above will be equivalent to the following circuit:

.. testcode::

noisy_c2 = models.Circuit(2)
noisy_c2.add(gates.H(0))
noisy_c2.add(gates.H(1))
noisy_c2.add(gates.PauliNoiseChannel(1, px=0.5))
noisy_c2.add(gates.CNOT(0, 1))
noisy_c2.add(gates.PauliNoiseChannel(0, py=0.5))
noisy_c2.add(gates.PauliNoiseChannel(1, py=0.5))


The :class:`qibo.noise.NoiseModel` class supports also density matrices,
it is sufficient to pass a circuit which was initialized with ``density_matrix=True``
to generate the correspoding :class:`qibo.core.circuit.DensityMatrixCircuit`.



.. _measurementbitflips-example:

Expand Down
108 changes: 108 additions & 0 deletions src/qibo/noise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from qibo import gates

class PauliError():
"""Quantum error associated with the :class:`qibo.abstractions.gates.PauliNoiseChannel`.
Args:
options (tuple): see :class:`qibo.abstractions.gates.PauliNoiseChannel`
"""

def __init__(self, px=0, py=0, pz=0, seed=None):
self.options = px, py, pz, seed
self.channel = gates.PauliNoiseChannel


class ThermalRelaxationError():
"""Quantum error associated with the :class:`qibo.abstractions.gates.ThermalRelaxationChannel`.
Args:
options (tuple): see :class:`qibo.abstractions.gates.ThermalRelaxationChannel`
"""

def __init__(self, t1, t2, time, excited_population=0, seed=None):
self.options = t1, t2, time, excited_population, seed
self.channel = gates.ThermalRelaxationChannel


class ResetError():
"""Quantum error associated with the `qibo.abstractions.gates.ResetChannel`.
Args:
options (tuple): see :class:`qibo.abstractions.gates.ResetChannel`
"""

def __init__(self, p0, p1, seed=None):
self.options = p0, p1, seed
self.channel = gates.ResetChannel


class NoiseModel():
"""Class for the implementation of a custom noise model.
Example:
.. testcode::
from qibo import models, gates
from qibo.noise import NoiseModel, PauliError
# Build specific noise model with 2 quantum errors:
# - Pauli error on H only for qubit 1.
# - Pauli error on CNOT for all the qubits.
noise = NoiseModel()
noise.add(PauliError(px = 0.5), gates.H, 1)
noise.add(PauliError(py = 0.5), gates.CNOT)
# Generate noiseless circuit.
c = models.Circuit(2)
c.add([gates.H(0), gates.H(1), gates.CNOT(0, 1)])
# Apply noise to the circuit according to the noise model.
noisy_c = noise.apply(c)
"""

def __init__(self):
self.errors = {}

def add(self, error, gate, qubits=None):
"""Add a quantum error for a specific gate and qubit to the noise model.
Args:
error: quantum error to associate with the gate. Possible choices
are :class:`qibo.noise.PauliError`,
:class:`qibo.noise.ThermalRelaxationError` and
:class:`qibo.noise.ResetError`.
gate (:class:`qibo.abstractions.gates.Gate`): gate after which the noise will be added.
qubits (tuple): qubits where the noise will be applied, if None the noise
will be added after every instance of the gate.
"""

if isinstance(qubits, int):
qubits = (qubits, )

self.errors[gate] = (error, qubits)

def apply(self, circuit):
"""Generate a noisy quantum circuit according to the noise model built.
Args:
circuit (:class:`qibo.core.circuit.Circuit`): quantum circuit
Returns:
A (:class:`qibo.core.circuit.Circuit`) which corresponds
to the initial circuit with noise gates added according
to the noise model.
"""

circ = circuit.__class__(**circuit.init_kwargs)
for gate in circuit.queue:
circ.add(gate)
if gate.__class__ in self.errors:
error, qubits = self.errors.get(gate.__class__)
if qubits is None:
qubits = gate.qubits
else:
qubits = tuple(set(gate.qubits) & set(qubits))
for q in qubits:
circ.add(error.channel(q, *error.options))
return circ
114 changes: 114 additions & 0 deletions src/qibo/tests/test_noise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import numpy as np
import pytest
from qibo import K, gates
from qibo.models import Circuit
from qibo.noise import *
from qibo.tests.utils import random_density_matrix, random_state

@pytest.mark.parametrize("density_matrix", [False, True])
def test_pauli_error(backend, density_matrix):

pauli = PauliError(0, 0.2, 0.3)
noise = NoiseModel()
noise.add(pauli, gates.X, 1)
noise.add(pauli, gates.CNOT)
noise.add(pauli, gates.Z, (0,1))

circuit = Circuit(3, density_matrix=density_matrix)
circuit.add(gates.CNOT(0,1))
circuit.add(gates.Z(1))
circuit.add(gates.X(1))
circuit.add(gates.X(2))
circuit.add(gates.Z(2))

target_circuit = Circuit(3, density_matrix=density_matrix)
target_circuit.add(gates.CNOT(0,1))
target_circuit.add(gates.PauliNoiseChannel(0, 0, 0.2, 0.3))
target_circuit.add(gates.PauliNoiseChannel(1, 0, 0.2, 0.3))
target_circuit.add(gates.Z(1))
target_circuit.add(gates.PauliNoiseChannel(1, 0, 0.2, 0.3))
target_circuit.add(gates.X(1))
target_circuit.add(gates.PauliNoiseChannel(1, 0, 0.2, 0.3))
target_circuit.add(gates.X(2))
target_circuit.add(gates.Z(2))

initial_psi = random_density_matrix(3) if density_matrix else random_state(3)
np.random.seed(123)
K.set_seed(123)
final_state = noise.apply(circuit)(initial_state=np.copy(initial_psi))
np.random.seed(123)
K.set_seed(123)
target_final_state = target_circuit(initial_state=np.copy(initial_psi))

K.assert_allclose(final_state, target_final_state)


@pytest.mark.parametrize("density_matrix", [False, True])
def test_thermal_error(backend, density_matrix):

thermal = ThermalRelaxationError(1, 1, 0.3)
noise = NoiseModel()
noise.add(thermal, gates.X, 1)
noise.add(thermal, gates.CNOT)
noise.add(thermal, gates.Z, (0,1))

circuit = Circuit(3, density_matrix=density_matrix)
circuit.add(gates.CNOT(0,1))
circuit.add(gates.Z(1))
circuit.add(gates.X(1))
circuit.add(gates.X(2))
circuit.add(gates.Z(2))

target_circuit = Circuit(3, density_matrix=density_matrix)
target_circuit.add(gates.CNOT(0,1))
target_circuit.add(gates.ThermalRelaxationChannel(0, 1, 1, 0.3))
target_circuit.add(gates.ThermalRelaxationChannel(1, 1, 1, 0.3))
target_circuit.add(gates.Z(1))
target_circuit.add(gates.ThermalRelaxationChannel(1, 1, 1, 0.3))
target_circuit.add(gates.X(1))
target_circuit.add(gates.ThermalRelaxationChannel(1, 1, 1, 0.3))
target_circuit.add(gates.X(2))
target_circuit.add(gates.Z(2))

initial_psi = random_density_matrix(3) if density_matrix else random_state(3)
np.random.seed(123)
K.set_seed(123)
final_state = noise.apply(circuit)(initial_state=np.copy(initial_psi))
np.random.seed(123)
K.set_seed(123)
target_final_state = target_circuit(initial_state=np.copy(initial_psi))

K.assert_allclose(final_state, target_final_state)


@pytest.mark.parametrize("density_matrix", [False, True])
def test_reset_error(backend, density_matrix):

reset = ResetError(0.8, 0.2)
noise = NoiseModel()
noise.add(reset, gates.X, 1)
noise.add(reset, gates.CNOT)
noise.add(reset, gates.Z, (0,1))

circuit = Circuit(3, density_matrix=density_matrix)
circuit.add(gates.CNOT(0,1))
circuit.add(gates.Z(1))

target_circuit = Circuit(3, density_matrix=density_matrix)
target_circuit.add(gates.CNOT(0,1))
target_circuit.add(gates.ResetChannel(0, 0.8, 0.2))
target_circuit.add(gates.ResetChannel(1, 0.8, 0.2))
target_circuit.add(gates.Z(1))
target_circuit.add(gates.ResetChannel(1, 0.8, 0.2))

initial_psi = random_density_matrix(3) if density_matrix else random_state(3)
np.random.seed(123)
K.set_seed(123)
final_state = noise.apply(circuit)(initial_state=np.copy(initial_psi))
np.random.seed(123)
K.set_seed(123)
target_final_state = target_circuit(initial_state=np.copy(initial_psi))

K.assert_allclose(final_state, target_final_state)


0 comments on commit 34e9363

Please sign in to comment.