Adding noise to a QuantumProgram or Circuit

Noise is an inherent part of real quantum systems, and simulating its effects is crucial for understanding the behavior of quantum algorithms in real-world conditions. The open source package qoqo provides users with several ways to define noise models precisely. The noise operations can be directly added to a quantum circuit and then be simulated by compatible backends.

Adding Decoherence

As decoherence noise does not translate into unitary quantum gates, the noise operations are defined as Pragma operations in qoqo. The strength of the noise is determined by defining a gate_time and a rate. The noise Pragma operations affect the system as a Lindblad type noise acting on the system with the rate rate for the time gate_time.

The Noise Models

Struqture lets users represent the decoherence part of the Lindblad equation using the struqture.spins.PlusMinusLindbladNoiseOperator class. The available noise models include:

  • ContinousDecoherenceModel: The noise model representing a continuous decoherence process on qubits. This noise model assumes that all qubits are constantly experiencing decoherence over time (e.g. due to coupling to the environment). The noise for each qubit can be different but only single-qubit noise is included in the model

  • DecoherenceOnGateModel: This noise model for noise that is only present on gate executions. It adds noise when specific gates (identified by the name of the gate and qubits it acts on) are executed.

  • DecoherenceOnIdleModel: The noise model which adds noise to qubits not involved in an operation, when that gate operation is executed (i.e. to the qubits which remain idle during the operation).

The NoiseInserter Converter

The NoiseInserter allows users to add noise Pragma operations to a given circuit or QuantumProgram, where the noise operations are derived from the input noise models. The noise model specifies the type of noise operations to be inserted, e.g. PragmaDamping, PragmaDephasing or PragmaDepolarizing as well as the rate of the noise. The gate time is inferred from the device being used.

The noise is added either before, after or symmetrical around gate operations present in the circuit. This is defined by the user when initializing the NoiseInserter.

Initializing the NoiseInserter Class

The NoiseInserter class is imported from the pyqonvert.noise module. The following values are needed to initialize it.

  • noise_mode: specifies the type of noise insertion. Allowed values include
    • active_qubits_only - noise Pragmas are added to the qubits involved in gate operations in the circuit.
    • all_qubits - noise Pragmas are added on all the qubits involved in the circuit.
    • parallelization_blocks - noise Pragmas are added to the qubits involved in the a parallel block marked by PragmaStopParallelBlock operations in the circuit.

Additionally, there is the noise placement mode property of the NoiseInserter, which can be modified by the user. This setting determines whether noise is applied after, before, or symmetrically around (both before and after) a gate operation. By default, the noise placement mode is set to "After", meaning that all noise Pragma operations are sequentially added after gate operations in a circuit. You can modify this behavior using the functions provided below.

from pyqonvert.noise import NoiseInserter

noise_mode = "all_qubits"
# initialize noise inserter
noise_inserter = NoiseInserter(noise_mode)

# Ready to use for noise addition.
Noise placement modes

The following functions help in updating the noise placement mode:

  • noise_before_gate - noise Pragmas are placed before each gate operation.
noise_inserter = NoiseInserter(noise_mode)
noise_inserter.noise_before_gate()
  • noise_symmetric_around_gate - noise Pragmas are placed symmetrically around each gate operation.
noise_inserter = NoiseInserter(noise_mode)
noise_inserter.noise_symmetric_around_gate()
  • noise_after_gate - noise Pragmas are placed after each gate operation.
noise_inserter = NoiseInserter(noise_mode)
noise_inserter.noise_after_gate()

Examples for each type of noise insertion

The NoiseInserter Class provides the convert method to insert noise into quantum circuits and QuantumPrograms.

The following example shows how one can insert noise according to a DecoherenceOnGateModel into a circuit with the "all_qubits" mode.

from pyqonvert.noise import NoiseInserter
from qoqo import devices, noise_models as nm, Circuit
from qoqo import operations as ops
from struqture_py.spins import PlusMinusLindbladNoiseOperator, PlusMinusProduct

noise_inserter = NoiseInserter("all_qubits").noise_before_gate()

decoherence_rate = 0.011
number_qubits = 2
# Initialize device and noise_model
device = devices.AllToAllDevice(
    number_qubits,
    ["Hadamard"],
    ["CNOT"],
    0.5
)
noise = PlusMinusLindbladNoiseOperator()
noise_product = PlusMinusProduct().z(0).plus(0)
noise.add_operator_product((noise_product, noise_product), 2.5)
noise_model = nm.DecoherenceOnGateModel().set_single_qubit_gate_error( 
    "Hadamard",
    0,
    noise

)

# initialize circuit
circuit = Circuit()
circuit += ops.Hadamard(0)
circuit += ops.CNOT(0,1)

# Add noise
circuit_noise = noise_inserter.convert(circuit, device, [noise_model])

decoherence on gate model Figure 1: Adding decoherence noise "before" Hadamard gates on qubit 0

The convert function is applied to a QuantumProgram in a similar fashion.

from pyqonvert.noise import NoiseInserter
from qoqo import devices, noise_models as nm, Circuit, QuantumProgram
from qoqo import operations as ops
from qoqo.measurements import PauliZProduct, PauliZProductInput

noise_inserter = NoiseInserter("active_qubits_only")

decoherence_rate = 0.011
number_qubits = 2
# Initialize device and noise_model
device = devices.AllToAllDevice(
    number_qubits,
    ["Hadamard"],
    ["CNOT"],
    0.5
)
noise_model = nm.ContinuousDecoherenceModel().add_damping_rate( 
    [0, 1],
    decoherence_rate
)

# initialize program
circuit = Circuit()
circuit += ops.Hadamard(0)
circuit += ops.CNOT(0,1)

z_circuit = Circuit()
z_circuit += ops.DefinitionBit("ro_z", 1, is_output=True)
z_circuit += ops.PragmaRepeatedMeasurement("ro_z", 1000, None)

measurement_input = PauliZProductInput(1, False)
measurement = PauliZProduct(
   constant_circuit = circuit,
   circuits=[z_circuit],
   input=measurement_input,
)
qp = QuantumProgram(measurement, [])

# Add noise
program_noise = noise_inserter.convert(qp, device, [noise_model])

continous decoherence noise Figure 2: Adding continous decoherence noise on active qubits

A circuit that has been optimized using the CircuitParallelizer converter is interspersed with PragmaStopParallelBlock instances. Noise can be added to a parallelized circuit or QuantumProgram using the "parallelization_blocks" mode. More information about the CircuitParallelizer converter can be found here and in the Python API documentation.

from pyqonvert.noise import NoiseInserter
from qoqo import devices, Circuit, noise_models as nm
from qoqo import operations as ops

noise_inserter = NoiseInserter("parallelization_blocks")

decoherence_rate = 0.011
number_qubits = 2
# Initialize device
device = devices.AllToAllDevice(
    number_qubits,
    ["Hadamard"],
    [],
    0.5
)

noise_model = nm.DecoherenceOnIdleModel().add_damping_rate( 
    [0, 1],
    decoherence_rate
)

# initialize circuit
circuit = Circuit()
circuit += ops.Hadamard(0)
circuit += ops.PragmaStopParallelBlock([0], 0.5)
circuit += ops.Hadamard(1)
circuit += ops.PragmaStopParallelBlock([1], 0.5)

# Add noise
circuit_noise = noise_inserter.convert(circuit, device, [noise_model])

Adding Overrotation Noise

Overrotation noise refers to the arbitrary rotation of qubits due to background factors. Pyqonvert facilitates the addition of such noise using the following noise model:

  • SingleQubitOverrotationOnGate: The noise model which places a rotation gate with a randomly distributed rotation angle after a specified gate operation. Through the SingleQubitOverrotationDescription class, one can define the parameters of the Gaussian distribution (mean and standard deviation) from which the overrotation angle is sampled.

The SingleQubitOverrotationAdder converter

The purpose of SingleQubitOverrotationAdder is to add single-qubit rotation gates that simulate overrotation noise informed by a SingleQubitOverrotationOnGate model.

Initializing the SingleQubitOverrotationAdder Class

The SingleQubitOverrotationAdder class is imported from the pyqonvert.noise module.

  • seed: the(optional) seed for the random number generator. If a seed is not provided, a machine-generated seed is used.
from pyqonvert.noise import SingleQubitOverrotationAdder

# initialize SingleQubitOverrotationAdder without a seed
overrotation_adder = SingleQubitOverrotationAdder()

# initialize SingleQubitOverrotationAdder with a seed
overrotation_adder_seeded = SingleQubitOverrotationAdder(seed=42)

# Ready to use for noise addition.

Example

Like NoiseInserter, the SingleQubitOverrotationAdder Class has the convert function which adds overrotation noise to quantum circuits and QuantumPrograms.

from pyqonvert.noise import SingleQubitOverrotationAdder
from qoqo import devices, Circuit, noise_models as nm
from qoqo import operations as ops

# Initializing the converter
noise_inserter = SingleQubitOverrotationAdder(seed=1)

# noise model
rotation_gate = "RotateZ"
# Parameters for Gaussian distribution single-qubit gates
mean_single = 0.0
std_single = 1.0
overrotation_noise_description_single = nm.SingleQubitOverrotationDescription("RotateZ", mean_single, std_single)

# Parameters for Gaussian distribution two qubit gates
mean_two = 1.0
std_two = 2.0
overrotation_noise_description_two = nm.SingleQubitOverrotationDescription("RotateX", mean_two, std_two)

overrotation_noise = nm.SingleQubitOverrotationOnGate()
overrotation_noise = overrotation_noise.set_single_qubit_overrotation(
    "Hadamard",
    0,
    overrotation_noise_description_single
)
overrotation_noise = overrotation_noise.set_two_qubit_overrotation(
    "CNOT",
    0,
    1,
    (overrotation_noise_description_two, overrotation_noise_description_two)
)

# Initialize device and noise_model
number_qubits = 2
device = devices.AllToAllDevice(
    number_qubits,
    ["Hadamard"],
    ["CNOT"],
    0.5
)

# initialize circuit
circuit = Circuit()
circuit += ops.Hadamard(0)
circuit += ops.CNOT(0,1)

# Add noise
circuit_noise = noise_inserter.convert(circuit, device, [overrotation_noise])

overrotation noise The usage in the case of QuantumPrograms is similar:

program_noise = noise_inserter.convert(quantum_program, device, [overrotation_noise])

Figure 3: Adding overrotation noise