Noisy Simulation

CUDA-Q makes it simple to model noise within the simulation of your quantum program. Let’s take a look at the various built-in noise models we support, before concluding with a brief example of a custom noise model constructed from user-defined Kraus Operators.

The following code illustrates how to run a simulation with depolarization noise.

import cudaq

# Set the target to our density matrix simulator.
cudaq.set_target('density-matrix-cpu')

# CUDA-Q supports several different models of noise. In this
# case, we will examine the modeling of depolarization noise. This
# depolarization will result in the qubit state decaying into a mix
# of the basis states, |0> and |1>, with a user provided probability.

# We will begin by defining an empty noise model that we will add
# our depolarization channel to.
noise = cudaq.NoiseModel()

# We define a depolarization channel setting the probability
# of the qubit state being scrambled to `1.0`.
depolarization = cudaq.DepolarizationChannel(1.0)

# We will apply the channel to any Y-gate on qubit 0. In other words,
# for each Y-gate on our qubit, the qubit will have a `1.0`
# probability of decaying into a mixed state.
noise.add_channel('y', [0], depolarization)


# Now we define our simple kernel function and allocate
# a qubit to it.
@cudaq.kernel
def kernel():
    qubit = cudaq.qubit()
    # First we apply a Y-gate to the qubit.
    # This will bring it to the |1> state, where it will remain
    # with a probability of `1 - p = 0.0`.
    y(qubit)
    mz(qubit)


# Without noise, the qubit should still be in the |1> state.
counts = cudaq.sample(kernel)
counts.dump()

# With noise, the measurements should be a roughly 50/50
# mix between the |0> and |1> states.
noisy_counts = cudaq.sample(kernel, noise_model=noise)
noisy_counts.dump()
// Compile and run with:
// ```
// nvq++ noise_depolarization.cpp --target density-matrix-cpu -o dyn.x
// && ./dyn.x
// ```
//
// Note: You must set the target to a density matrix backend for the noise
// to successfully impact the system.

#include <cudaq.h>

// CUDA-Q supports several different models of noise. In this
// case, we will examine the modeling of depolarization noise. This
// depolarization will result in the qubit state decaying into a mix
// of the basis states, |0> and |1>, with a user-provided probability.

int main() {

  // We will begin by defining an empty noise model that we will add
  // our depolarization channel to.
  cudaq::noise_model noise;

  // We define a depolarization channel setting the probability of the
  // qubit state being scrambled to `1.0`.
  cudaq::depolarization_channel depolarization(1.);
  // We will apply the channel to any Y-gate on qubit 0. In other words,
  // for each Y-gate on our qubit, the qubit will have a `1.0`
  // probability of decaying into a mixed state.
  noise.add_channel<cudaq::types::y>({0}, depolarization);

  // Our kernel will apply a Y-gate to qubit 0.
  // This will bring the qubit to the |1> state, where it will remain
  // with a probability of `1 - p = 0.0`.
  auto kernel = []() __qpu__ {
    cudaq::qubit q;
    y(q);
    mz(q);
  };

  // Now let's set the noise and we're ready to run the simulation!
  cudaq::set_noise(noise);

  // With noise, the measurements should be a roughly 50/50
  // mix between the |0> and |1> states.
  auto noisy_counts = cudaq::sample(kernel);
  noisy_counts.dump();

  // To confirm this, we can run the simulation again without noise.
  // Without noise, the qubit should still be in the |1> state.
  cudaq::unset_noise();
  auto noiseless_counts = cudaq::sample(kernel);
  noiseless_counts.dump();
}

The following code illustrates how to run a simulation with amplitude damping noise.

import cudaq

# Set the target to our density matrix simulator.
cudaq.set_target('density-matrix-cpu')

# CUDA-Q supports several different models of noise. In this case,
# we will examine the modeling of energy dissipation within our system
# via environmental interactions. The result of this "amplitude damping"
# is to return the qubit to the |0> state with a user-specified probability.

# We will begin by defining an empty noise model that we will add
# our damping channel to.
noise = cudaq.NoiseModel()

# We define an amplitude damping channel setting to `1.0` the
# probability of the qubit
# decaying to the ground state.
amplitude_damping = cudaq.AmplitudeDampingChannel(1.0)

# We will apply this channel to any Hadamard gate on the qubit.
# In other words, after each Hadamard on the qubit, there will be a
# probability of `1.0` that the qubit decays back to the ground state.
noise.add_channel('h', [0], amplitude_damping)


# Now we define our simple kernel function and allocate a qubit.
@cudaq.kernel
def kernel():
    qubit = cudaq.qubit()
    # Then we apply a Hadamard gate to the qubit.
    # This will bring it to `1/sqrt(2) (|0> + |1>)`, where it will remain
    # with a probability of `1 - p = 0.0`.
    h(qubit)
    # Measure.
    mz(qubit)


# Now we're ready to run the noisy simulation of our kernel.
# Note: We must pass the noise model to sample via keyword.
noisy_result = cudaq.sample(kernel, noise_model=noise)
print(noisy_result)

# Our results should show all measurements in the |0> state, indicating
# that the noise has successfully impacted the system.

# To confirm this, we can run the simulation again without noise.
# The qubit will now have a 50/50 mix of measurements between
# |0> and |1>.
noiseless_result = cudaq.sample(kernel)
print(noiseless_result)
// Compile and run with:
// ```
// nvq++ noise_amplitude_damping.cpp --target density-matrix-cpu -o dyn.x
// && ./dyn.x
// ```
//
// Note: You must set the target to a density matrix backend for the noise
// to successfully impact the system.

#include <cudaq.h>

// CUDA-Q supports several different models of noise. In this case,
// we will examine the modeling of energy dissipation within our system
// via environmental interactions. The result of this "amplitude damping"
// is to return the qubit to the |0> state with a user-specified probability.

int main() {

  // We will begin by defining an empty noise model that we will add
  // our damping channel to.
  cudaq::noise_model noise;

  // We define an amplitude damping channel setting the probability of
  // the qubit decaying to the ground state to `1.0`.
  cudaq::amplitude_damping_channel ad(1.);

  // We will apply this channel to any Hadamard gate on the qubit.
  // In other words, after each Hadamard on the qubit, there will be a
  // probability of `1.0` that the qubit decays back to ground.
  noise.add_channel<cudaq::types::h>({0}, ad);

  // The Hadamard gate here will bring the qubit to `1/sqrt(2) (|0> + |1>)`,
  // where it will remain with a probability of `1 - p = 0.0`.
  auto kernel = []() __qpu__ {
    cudaq::qubit q;
    h(q);
    mz(q);
  };

  // Now let's set the noise and we're ready to run the simulation!
  cudaq::set_noise(noise);

  // Our results should show all measurements in the |0> state, indicating
  // that the noise has successfully impacted the system.
  auto noisy_counts = cudaq::sample(kernel);
  noisy_counts.dump();

  // To confirm this, we can run the simulation again without noise.
  // The qubit will now have a 50/50 mix of measurements between
  // |0> and |1>.
  cudaq::unset_noise();
  auto noiseless_counts = cudaq::sample(kernel);
  noiseless_counts.dump();
}

The following code illustrates how to run a simulation with bit-flip noise.

import cudaq

# Set the target to our density matrix simulator.
cudaq.set_target('density-matrix-cpu')

# CUDA-Q supports several different models of noise. In this case,
# we will examine the modeling of decoherence of the qubit state. This
# will occur from "bit flip" errors, wherein the qubit has a user-specified
# probability of undergoing an X-180 rotation.

# We will begin by defining an empty noise model that we will add
# these decoherence channels to.
noise = cudaq.NoiseModel()

# We define a bit-flip channel setting to `1.0` probability of the
# qubit flipping 180 degrees about the X axis.
bit_flip = cudaq.BitFlipChannel(1.0)
# We will apply this channel to any X gate on the qubit, giving each X-gate
# a probability of `1.0` of undergoing an extra X-gate.
noise.add_channel('x', [0], bit_flip)


# Now we define our simple kernel function and allocate a register
# of qubits to it.
@cudaq.kernel
def kernel():
    qubit = cudaq.qubit()
    # Apply an X-gate to the qubit.
    # It will remain in the |1> state with a probability of `1 - p = 0.0`.
    x(qubit)
    # Measure.
    mz(qubit)


# Now we're ready to run the noisy simulation of our kernel.
# Note: We must pass the noise model to sample via keyword.
noisy_result = cudaq.sample(kernel, noise_model=noise)
print(noisy_result)

# Our results should show all measurements in the |0> state, indicating
# that the noise has successfully impacted the system.

# To confirm this, we can run the simulation again without noise.
# We should now see the qubit in the |1> state.
noiseless_result = cudaq.sample(kernel)
print(noiseless_result)
// Compile and run with:
// ```
// nvq++ noise_bit_flip.cpp --target density-matrix-cpu -o dyn.x
// && ./dyn.x
// ```
//
// Note: You must set the target to a density matrix backend for the noise
// to successfully impact the system.

#include <cudaq.h>

// CUDA-Q supports several different models of noise. In this case,
// we will examine the modeling of decoherence of the qubit state. This
// will occur from "bit flip" errors, wherein the qubit has a user-specified
// probability of undergoing an X-180 rotation.

int main() {

  // We will begin by defining an empty noise model that we will add
  // these decoherence channels to.
  cudaq::noise_model noise;

  // We define a bit-flip channel setting the probability of the
  // qubit flipping 180 degrees about the X axis to `1.0`.
  cudaq::bit_flip_channel bf(1.);
  // We will apply this channel to any X gate on the qubit, giving each X-gate
  // a probability of `1.0` of undergoing an extra X-gate.
  noise.add_channel<cudaq::types::x>({0}, bf);

  // After the X-gate, the qubit will remain in the |1> state with a probability
  // of `1 - p = 0.0`.
  auto kernel = []() __qpu__ {
    cudaq::qubit q;
    x(q);
    mz(q);
  };

  // Now let's set the noise and we're ready to run the simulation!
  cudaq::set_noise(noise);

  // Our results should show all measurements in the |0> state, indicating
  // that the noise has successfully impacted the system.
  auto noisy_counts = cudaq::sample(kernel);
  noisy_counts.dump();

  // To confirm this, we can run the simulation again without noise.
  // We should now see the qubit in the |1> state.
  cudaq::unset_noise();
  auto noiseless_counts = cudaq::sample(kernel);
  noiseless_counts.dump();
}

The following code illustrates how to run a simulation with phase-flip noise.

import cudaq

# Set the target to our density matrix simulator.
cudaq.set_target('density-matrix-cpu')

# CUDA-Q supports several different models of noise. In this
# case, we will examine the modeling of decoherence of the qubit phase.
# This will occur from "phase flip" errors, wherein the qubit has a
# user-specified probability of undergoing a Z-180 rotation.

# We will begin by defining an empty noise model that we will add
# our phase flip channel to.
noise = cudaq.NoiseModel()

# We define a phase-flip channel setting to `1.0` the probability of the qubit
# undergoing a phase rotation of 180 degrees (π).
phase_flip = cudaq.PhaseFlipChannel(1.0)
# We will apply this channel to any Z gate on the qubit.
# In other words, after each Z gate on qubit 0, there will be a
# probability of `1.0` that the qubit undergoes an extra
# Z rotation.
noise.add_channel('z', [0], phase_flip)


@cudaq.kernel
def kernel():
    # Single qubit initialized to the |0> state.
    qubit = cudaq.qubit()
    # Place qubit in superposition state.
    h(qubit)
    # Rotate the phase around Z by 180 degrees (π).
    z(qubit)
    # Apply another Hadamard and measure.
    h(qubit)
    mz(qubit)


# Without noise, we'd expect the qubit to end in the |1>
# state due to the phase rotation between the two Hadamard
# gates.
noiseless_result = cudaq.sample(kernel)
print(noiseless_result)

# With noise, our Z-gate will effectively cancel out due
# to the presence of a phase flip error on the gate with a
# probability of `1.0`. This will put us back in the |0> state.
noisy_result = cudaq.sample(kernel, noise_model=noise)
print(noisy_result)
// Compile and run with:
// ```
// nvq++ noise_phase_flip.cpp --target density-matrix-cpu -o dyn.x
// && ./dyn.x
// ```
//
// Note: You must set the target to a density matrix backend for the noise
// to successfully impact the system.

#include <cudaq.h>

// CUDA-Q supports several different models of noise. In this
// case, we will examine the modeling of decoherence of the qubit phase.
// This will occur from "phase flip" errors, wherein the qubit has a
// user-specified probability of undergoing a Z-180 rotation.

int main() {

  // We will begin by defining an empty noise model that we will add
  // our phase flip channel to.
  cudaq::noise_model noise;

  // We define a phase-flip channel setting the probability of the
  // qubit undergoing a phase rotation of 180 degrees (π) to `1.0`.
  cudaq::phase_flip_channel pf(1.);
  // We will apply this channel to any Z gate on the qubit.
  // In other words, after each Z gate on qubit 0, there will be a
  // probability of `1.0` that the qubit undergoes an extra
  // Z rotation.
  noise.add_channel<cudaq::types::z>({0}, pf);

  auto kernel = []() __qpu__ {
    cudaq::qubit q;
    // Place qubit in superposition state.
    h(q);
    // Rotate on Z by 180 degrees.
    z(q);
    // Apply another Hadamard.
    h(q);
    mz(q);
  };

  // Now let's set the noise and we're ready to run the simulation!
  cudaq::set_noise(noise);

  // With noise, our Z-gate will effectively cancel out due
  // to the presence of a phase-flip error on the gate with a
  // probability of `1.0`. This will put us back in the |0> state.
  auto noisy_counts = cudaq::sample(kernel);
  noisy_counts.dump();

  // To confirm this, we can run the simulation again without noise.
  // Without noise, we'd expect the qubit to end in the |1> state due
  // to the phase rotation between the two Hadamard gates.
  cudaq::unset_noise();
  auto noiseless_counts = cudaq::sample(kernel);
  noiseless_counts.dump();
}

The following code illustrates how to run a simulation with a custom noise model.

import cudaq
import numpy as np

# Set the target to our density matrix simulator.
cudaq.set_target('density-matrix-cpu')

# CUDA-Q supports custom noise models through the definition of
# `KrausChannel`'s. In this case, we will define a set of `KrausOperator`'s
# that  affect the same noise as the `AmplitudeDampingChannel`. This
# channel will model the energy dissipation within our system via
# environmental interactions. With a variable probability, it will
# return the qubit to the |0> state.

# We will begin by defining an empty noise model that we will add
# our Kraus Channel to.
noise = cudaq.NoiseModel()


# We will define our Kraus Operators within functions, as to
# allow for easy control over the noise probability.
def kraus_operators(probability):
    """See Nielsen, Chuang Chapter 8.3.5 for definition source."""
    kraus_0 = np.array([[1, 0], [0, np.sqrt(1 - probability)]],
                       dtype=np.complex128)
    kraus_1 = np.array([[0, 0], [np.sqrt(probability), 0]], dtype=np.complex128)
    return [kraus_0, kraus_1]


# We manually define an amplitude damping channel setting to `1.0`
# the probability of the qubit decaying to the ground state.
amplitude_damping = cudaq.KrausChannel(kraus_operators(1.0))

# We will apply this channel to any Hadamard gate on the qubit.
# In other words, after each Hadamard on the qubit, there will be a
# probability of `1.0` that the qubit decays back to ground.
noise.add_channel('h', [0], amplitude_damping)


@cudaq.kernel
def kernel():
    qubit = cudaq.qubit()
    # Then we apply a Hadamard gate to the qubit.
    # This will bring it to `1/sqrt(2) (|0> + |1>)`, where it will remain
    # with a probability of `1 - p = 0.0`.
    h(qubit)
    # Measure.
    mz(qubit)


# Now we're ready to run the noisy simulation of our kernel.
# Note: We must pass the noise model to sample via keyword.
noisy_result = cudaq.sample(kernel, noise_model=noise)
print(noisy_result)

# Our results should show all measurements in the |0> state, indicating
# that the noise has successfully impacted the system.

# To confirm this, we can run the simulation again without noise.
# The qubit will now have a 50/50 mix of measurements between
# |0> and |1>.
noiseless_result = cudaq.sample(kernel)
print(noiseless_result)
// Compile and run with:
// ```
// nvq++ noise_modeling.cpp --target density-matrix-cpu -o noise.x && ./noise.x
// ```

#include "cudaq.h"

int main() {
  // Define a  kernel
  auto xgate = []() __qpu__ {
    cudaq::qubit q;
    x(q);
    mz(q);
  };

  // Run noise-less simulation
  auto counts = cudaq::sample(xgate);
  counts.dump();

  // Create a depolarizing Kraus channel made up of two Kraus operators.
  cudaq::kraus_channel depol({cudaq::complex{0.99498743710662, 0.0},
                              {0.0, 0.0},
                              {0.0, 0.0},
                              {0.99498743710662, 0.0}},

                             {cudaq::complex{0.0, 0.0},
                              {0.05773502691896258, 0.0},
                              {0.05773502691896258, 0.0},
                              {0.0, 0.0}},

                             {cudaq::complex{0.0, 0.0},
                              {0.0, -0.05773502691896258},
                              {0.0, 0.05773502691896258},
                              {0.0, 0.0}},

                             {cudaq::complex{0.05773502691896258, 0.0},
                              {0.0, 0.0},
                              {0.0, 0.0},
                              {-0.05773502691896258, 0.0}});

  // Create the noise model
  cudaq::noise_model noise;
  // Add the Kraus channel to the x operation on qubit 0.
  noise.add_channel<cudaq::types::x>({0}, depol);

  // Set the noise model
  cudaq::set_noise(noise);

  // Run the noisy simulation
  counts = cudaq::sample(xgate);
  counts.dump();

  // Unset the noise model when done. This is not necessary in this case, but it
  // is good practice in order to avoid interference with future simulations.
  cudaq::unset_noise();
}