Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new noise models #2653

Merged
merged 29 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2a7a7ba
Implement new noise models
bmhowe23 Feb 25, 2025
78ca576
phase_damping updates
bmhowe23 Feb 25, 2025
28d753a
Disable execution context checks in applyNoise
bmhowe23 Feb 25, 2025
33939cf
Update test::hello to allow tensornet execution
bmhowe23 Feb 25, 2025
034ca52
Enable new kraus channels and builtin channels in Python (#1)
amccaskey Feb 25, 2025
1d57106
register_channel no longer needed in new test
bmhowe23 Feb 25, 2025
39c4f7c
Formatting
bmhowe23 Feb 25, 2025
b28cc49
Merge branch 'main' into amc-apply.noise-bmh
bmhowe23 Feb 26, 2025
797a7ff
Add more C++ tests; fix bug with pauli2; spelling; formatting
bmhowe23 Feb 26, 2025
c0a5093
Merge branch 'main' into amc-apply.noise-bmh
bmhowe23 Feb 26, 2025
6e9fa21
Forgot this
bmhowe23 Feb 26, 2025
10cb961
Don't need to register builtin channels (#2)
amccaskey Feb 26, 2025
f6613d5
Add/fix docs
bmhowe23 Feb 26, 2025
c8660d6
Add Python tests
bmhowe23 Feb 26, 2025
32109c3
Add pydocs and fix num_parameters bugs
bmhowe23 Feb 26, 2025
1daa3dc
Unfix some of the bugs
bmhowe23 Feb 26, 2025
3cf3935
Bump the MGPU commit
bmhowe23 Feb 26, 2025
67b02a9
Revert "Unfix some of the bugs"
bmhowe23 Feb 26, 2025
2230fc0
Update tests for fixes
bmhowe23 Feb 26, 2025
ba2ad85
Add register_builtins to work around evolve problem
bmhowe23 Feb 26, 2025
576c28d
Add Python docs
bmhowe23 Feb 26, 2025
15b5fa7
DCO Remediation Commit for Ben Howe <[email protected]>
bmhowe23 Feb 26, 2025
48f9ea4
Merge branch 'main' into amc-apply.noise-bmh
bmhowe23 Feb 26, 2025
958ebfd
Address PR comments
bmhowe23 Feb 27, 2025
ee7b37c
Make sphinx happy
bmhowe23 Feb 27, 2025
1d996b7
Merge branch 'main' into amc-apply.noise-bmh
bmhowe23 Feb 27, 2025
7b00533
Fix GIL bug in builtin channel registration (#3)
amccaskey Feb 27, 2025
6bcbe00
Make it static again
bmhowe23 Feb 27, 2025
04462ef
Merge branch 'main' into amc-apply.noise-bmh
bmhowe23 Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/config/gitlab_commits.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
nvidia-mgpu-repo: cuda-quantum/cuquantum-mgpu.git
nvidia-mgpu-commit: 5a4c69fa1d5bc70fecccd758e5d936ff4b8115e3
nvidia-mgpu-commit: 17279375c13290a202ae5c5267f1f571227f1ef4
27 changes: 27 additions & 0 deletions docs/sphinx/api/languages/cpp_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,33 @@ Noise Modeling
.. doxygenclass:: cudaq::depolarization_channel
:members:

.. doxygenclass:: cudaq::x_error
:members:

.. doxygenclass:: cudaq::y_error
:members:

.. doxygenclass:: cudaq::z_error
:members:

.. doxygenclass:: cudaq::amplitude_damping
:members:

.. doxygenclass:: cudaq::phase_damping
:members:

.. doxygenclass:: cudaq::pauli1
:members:

.. doxygenclass:: cudaq::pauli2
:members:

.. doxygenclass:: cudaq::depolarization1
:members:

.. doxygenclass:: cudaq::depolarization2
:members:

.. doxygenclass:: cudaq::noise_model
:members:

Expand Down
16 changes: 16 additions & 0 deletions docs/sphinx/api/languages/python_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,22 @@ Noisy Simulation
:members:
:special-members: __init__

.. autoclass:: cudaq::PhaseDamping

.. autoclass:: cudaq::XError

.. autoclass:: cudaq::YError

.. autoclass:: cudaq::ZError

.. autoclass:: cudaq::Pauli1

.. autoclass:: cudaq::Pauli2

.. autoclass:: cudaq::Depolarization1

.. autoclass:: cudaq::Depolarization2

.. autoclass:: cudaq::KrausChannel
:members:
:special-members: __getitem__
Expand Down
8 changes: 8 additions & 0 deletions python/cudaq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@
AmplitudeDampingChannel = cudaq_runtime.AmplitudeDampingChannel
PhaseFlipChannel = cudaq_runtime.PhaseFlipChannel
BitFlipChannel = cudaq_runtime.BitFlipChannel
PhaseDamping = cudaq_runtime.PhaseDamping
ZError = cudaq_runtime.ZError
XError = cudaq_runtime.XError
YError = cudaq_runtime.YError
Pauli1 = cudaq_runtime.Pauli1
Pauli2 = cudaq_runtime.Pauli2
Depolarization1 = cudaq_runtime.Depolarization1
Depolarization2 = cudaq_runtime.Depolarization2

# Functions
sample_async = cudaq_runtime.sample_async
Expand Down
65 changes: 43 additions & 22 deletions python/cudaq/kernel/ast_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# the terms of the Apache License 2.0 which accompanies this distribution. #
# ============================================================================ #
import ast
import importlib
import hashlib
import graphlib
import sys, os
Expand Down Expand Up @@ -1313,26 +1314,40 @@ def visit_Attribute(self, node):
self.pushValue(complex.ImOp(value).result)
return

if isinstance(node.value,
ast.Name) and node.value.id in ['np', 'numpy', 'math']:
if node.attr == 'complex64':
self.pushValue(self.getComplexType(width=32))
return
if node.attr == 'complex128':
self.pushValue(self.getComplexType(width=64))
return
if node.attr == 'pi':
self.pushValue(self.getConstantFloat(np.pi))
return
if node.attr == 'e':
self.pushValue(self.getConstantFloat(np.e))
return
if node.attr == 'euler_gamma':
self.pushValue(self.getConstantFloat(np.euler_gamma))
return
raise RuntimeError(
"math expression {}.{} was not understood".format(
node.value.id, node.attr))
if isinstance(node.value, ast.Name):
if node.value.id in ['np', 'numpy', 'math']:
if node.attr == 'complex64':
self.pushValue(self.getComplexType(width=32))
return
if node.attr == 'complex128':
self.pushValue(self.getComplexType(width=64))
return
if node.attr == 'pi':
self.pushValue(self.getConstantFloat(np.pi))
return
if node.attr == 'e':
self.pushValue(self.getConstantFloat(np.e))
return
if node.attr == 'euler_gamma':
self.pushValue(self.getConstantFloat(np.euler_gamma))
return
raise RuntimeError(
"math expression {}.{} was not understood".format(
node.value.id, node.attr))

if node.value.id == 'cudaq':
if node.attr in [
'DepolarizationChannel', 'AmplitudeDampingChannel',
'PhaseFlipChannel', 'BitFlipChannel', 'PhaseDamping',
'ZError', 'XError', 'YError', 'Pauli1', 'Pauli2',
'Depolarization1', 'Depolarization2'
]:
cudaq_module = importlib.import_module('cudaq')
channel_class = getattr(cudaq_module, node.attr)
self.pushValue(
self.getConstantInt(channel_class.num_parameters))
self.pushValue(self.getConstantInt(hash(channel_class)))
return

def visit_Call(self, node):
"""
Expand Down Expand Up @@ -2455,8 +2470,9 @@ def bodyBuilder(iterVal):
params[i] = alloca

# The remaining arguments are the qubits
qubits = values[numParams:]
quake.ApplyNoiseOp(params, qubits, key=key)
asVeq = quake.ConcatOp(self.getVeqType(),
values[numParams:]).result
quake.ApplyNoiseOp(params, [asVeq], key=key)
return

if node.func.attr == 'compute_action':
Expand Down Expand Up @@ -4194,6 +4210,11 @@ def visit_Name(self, node):
# We want to create a hash value from it, and
# we then want to push the number of parameters and
# that hash value. This can only be used with apply_noise
if not hasattr(value, 'num_parameters'):
self.emitFatalError(
'apply_noise kraus channels must have `num_parameters` constant class attribute specified.'
)

self.pushValue(self.getConstantInt(value.num_parameters))
self.pushValue(self.getConstantInt(hash(value)))
return
Expand Down
177 changes: 170 additions & 7 deletions python/runtime/common/py_NoiseModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,75 @@ void bindNoiseModel(py::module &mod) {
mod, "NoiseModel",
"The `NoiseModel` defines a set of :class:`KrausChannel`'s applied to "
"specific qubits after the invocation of specified quantum operations.")
.def(py::init<>(), "Construct an empty noise model.")
.def(py::init<>([mod]() {
// Create the noise model
auto model = std::make_unique<noise_model>();

// Define a map of channel names to generator functions
static std::map<std::string, std::function<kraus_channel(
const std::vector<double> &)>>
channelGenerators = {
{"DepolarizationChannel",
[](const std::vector<double> &p) -> kraus_channel {
return depolarization_channel(p);
}},
{"AmplitudeDampingChannel",
[](const std::vector<double> &p) -> kraus_channel {
return amplitude_damping_channel(p);
}},
{"BitFlipChannel",
[](const std::vector<double> &p) -> kraus_channel {
return bit_flip_channel(p);
}},
{"PhaseFlipChannel",
[](const std::vector<double> &p) -> kraus_channel {
return phase_flip_channel(p);
}},
{"XError",
[](const std::vector<double> &p) -> kraus_channel {
return x_error(p);
}},
{"YError",
[](const std::vector<double> &p) -> kraus_channel {
return y_error(p);
}},
{"ZError",
[](const std::vector<double> &p) -> kraus_channel {
return z_error(p);
}},
{"PhaseDamping",
[](const std::vector<double> &p) -> kraus_channel {
return phase_damping(p);
}},
{"Pauli1",
[](const std::vector<double> &p) -> kraus_channel {
return pauli1(p);
}},
{"Pauli2",
[](const std::vector<double> &p) -> kraus_channel {
return pauli2(p);
}},
{"Depolarization1",
[](const std::vector<double> &p) -> kraus_channel {
return depolarization1(p);
}},
{"Depolarization2",
[](const std::vector<double> &p) -> kraus_channel {
return depolarization2(p);
}}};

// Register each channel generator
for (const auto &[name, generator] : channelGenerators) {
if (py::hasattr(mod, name.c_str())) {
py::type channelType = py::getattr(mod, name.c_str());
auto key = py::hash(channelType);
model->register_channel(key, generator);
}
}

return model;
}),
"Construct a noise model with all built-in channels pre-registered.")
.def(
"register_channel",
[](noise_model &self, const py::type krausT) {
Expand Down Expand Up @@ -234,9 +302,13 @@ void bindNoiseChannels(py::module &mod) {
For `probability = 0.0`, the channel will behave noise-free.
For `probability = 0.75`, the channel will fully depolarize the state.
For `probability = 1.0`, the channel will be uniform.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>(), py::arg("probability"),
"Initialize the `DepolarizationChannel` with the provided "
"`probability`.");
"`probability`.")
.def_readonly_static(
"num_parameters", &depolarization_channel::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<amplitude_damping_channel, kraus_channel>(
mod, "AmplitudeDampingChannel",
Expand All @@ -253,9 +325,13 @@ void bindNoiseChannels(py::module &mod) {
representing the probablity that the qubit will decay to its ground
state. The probability of the qubit remaining in the same state is
therefore `1 - probability`.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>(), py::arg("probability"),
"Initialize the `AmplitudeDampingChannel` with the provided "
"`probability`.");
"`probability`.")
.def_readonly_static(
"num_parameters", &amplitude_damping_channel::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<bit_flip_channel, kraus_channel>(
mod, "BitFlipChannel",
Expand All @@ -272,8 +348,12 @@ void bindNoiseChannels(py::module &mod) {

The probability of the qubit remaining in the same state is therefore `1 -
probability`.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>(), py::arg("probability"),
"Initialize the `BitFlipChannel` with the provided `probability`.");
"Initialize the `BitFlipChannel` with the provided `probability`.")
.def_readonly_static(
"num_parameters", &bit_flip_channel::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<phase_flip_channel, kraus_channel>(
mod, "PhaseFlipChannel",
Expand All @@ -289,9 +369,92 @@ void bindNoiseChannels(py::module &mod) {

The probability of the qubit phase remaining untouched is therefore
`1 - probability`.)#")
.def(
py::init<double>(), py::arg("probability"),
"Initialize the `PhaseFlipChannel` with the provided `probability`.");
.def(py::init<std::vector<double>>())
.def(py::init<double>(), py::arg("probability"),
"Initialize the `PhaseFlipChannel` with the provided `probability`.")
.def_readonly_static(
"num_parameters", &phase_flip_channel::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<phase_damping, kraus_channel>(
mod, "PhaseDamping",
R"#(A Kraus channel that models the single-qubit phase damping error. This
is similar to AmplitudeDamping, but for phase.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>())
.def_readonly_static(
"num_parameters", &phase_damping::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<z_error, kraus_channel>(
mod, "ZError",
R"#(A Pauli error that applies the Z operator when an error
occurs. It is the same as PhaseFlipChannel.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>())
.def_readonly_static(
"num_parameters", &z_error::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<x_error, kraus_channel>(
mod, "XError",
R"#(A Pauli error that applies the X operator when an error
occurs. It is the same as BitFlipChannel.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>())
.def_readonly_static(
"num_parameters", &x_error::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<y_error, kraus_channel>(
mod, "YError",
R"#(A Pauli error that applies the Y operator when an error
occurs.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>())
.def_readonly_static(
"num_parameters", &y_error::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<pauli1, kraus_channel>(
mod, "Pauli1",
R"#(A single-qubit Pauli error that applies either an X error, Y error,
or Z error. The probability of each X, Y, or Z error is supplied as a
parameter.)#")
.def(py::init<std::vector<double>>())
.def_readonly_static(
"num_parameters", &pauli1::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<pauli2, kraus_channel>(
mod, "Pauli2",
R"#(A 2-qubit Pauli error that applies one of the following errors, with
the probabilities specified as a vector. Possible errors: IX, IY, IZ, XI, XX,
XY, XZ, YI, YX, YY, YZ, ZI, ZX, ZY, and ZZ.)#")
.def(py::init<std::vector<double>>())
.def_readonly_static(
"num_parameters", &pauli2::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<depolarization1, kraus_channel>(
mod, "Depolarization1",
R"#(The same as DepolarizationChannel (single qubit depolarization))#")
.def(py::init<std::vector<double>>())
.def(py::init<double>())
.def_readonly_static(
"num_parameters", &depolarization1::num_parameters,
"The number of parameters this channel requires at construction.");

py::class_<depolarization2, kraus_channel>(
mod, "Depolarization2",
R"#(A 2-qubit depolarization error that applies one of the following
errors. Possible errors: IX, IY, IZ, XI, XX, XY, XZ, YI, YX, YY, YZ, ZI, ZX,
ZY, and ZZ.)#")
.def(py::init<std::vector<double>>())
.def(py::init<double>())
.def_readonly_static(
"num_parameters", &depolarization2::num_parameters,
"The number of parameters this channel requires at construction.");
}

void bindNoise(py::module &mod) {
Expand Down
Loading
Loading