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();
}