Skip to content

Commit 22ffd96

Browse files
bmhowe23amccaskey
andauthored
Implement new noise models (#2653)
This implements the following changes: * Implement new Pauli noise models + phase damping * Finish simulator hookup for apply_noise (Stim, Tensornet, and NVIDIA trajectory based simulations) * Finish up some of the Python hookup for apply_noise (thanks, @amccaskey) * Add tests --------- Signed-off-by: Ben Howe <[email protected]> Signed-off-by: Alex McCaskey <[email protected]> Co-authored-by: Alex McCaskey <[email protected]>
1 parent c68d1f7 commit 22ffd96

File tree

13 files changed

+1018
-42
lines changed

13 files changed

+1018
-42
lines changed
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
nvidia-mgpu-repo: cuda-quantum/cuquantum-mgpu.git
2-
nvidia-mgpu-commit: 5a4c69fa1d5bc70fecccd758e5d936ff4b8115e3
2+
nvidia-mgpu-commit: 17279375c13290a202ae5c5267f1f571227f1ef4

docs/sphinx/api/languages/cpp_api.rst

+27
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,33 @@ Noise Modeling
131131
.. doxygenclass:: cudaq::depolarization_channel
132132
:members:
133133

134+
.. doxygenclass:: cudaq::x_error
135+
:members:
136+
137+
.. doxygenclass:: cudaq::y_error
138+
:members:
139+
140+
.. doxygenclass:: cudaq::z_error
141+
:members:
142+
143+
.. doxygenclass:: cudaq::amplitude_damping
144+
:members:
145+
146+
.. doxygenclass:: cudaq::phase_damping
147+
:members:
148+
149+
.. doxygenclass:: cudaq::pauli1
150+
:members:
151+
152+
.. doxygenclass:: cudaq::pauli2
153+
:members:
154+
155+
.. doxygenclass:: cudaq::depolarization1
156+
:members:
157+
158+
.. doxygenclass:: cudaq::depolarization2
159+
:members:
160+
134161
.. doxygenclass:: cudaq::noise_model
135162
:members:
136163

docs/sphinx/api/languages/python_api.rst

+16
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,22 @@ Noisy Simulation
237237
:members:
238238
:special-members: __init__
239239

240+
.. autoclass:: cudaq::PhaseDamping
241+
242+
.. autoclass:: cudaq::XError
243+
244+
.. autoclass:: cudaq::YError
245+
246+
.. autoclass:: cudaq::ZError
247+
248+
.. autoclass:: cudaq::Pauli1
249+
250+
.. autoclass:: cudaq::Pauli2
251+
252+
.. autoclass:: cudaq::Depolarization1
253+
254+
.. autoclass:: cudaq::Depolarization2
255+
240256
.. autoclass:: cudaq::KrausChannel
241257
:members:
242258
:special-members: __getitem__

python/cudaq/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@
128128
AmplitudeDampingChannel = cudaq_runtime.AmplitudeDampingChannel
129129
PhaseFlipChannel = cudaq_runtime.PhaseFlipChannel
130130
BitFlipChannel = cudaq_runtime.BitFlipChannel
131+
PhaseDamping = cudaq_runtime.PhaseDamping
132+
ZError = cudaq_runtime.ZError
133+
XError = cudaq_runtime.XError
134+
YError = cudaq_runtime.YError
135+
Pauli1 = cudaq_runtime.Pauli1
136+
Pauli2 = cudaq_runtime.Pauli2
137+
Depolarization1 = cudaq_runtime.Depolarization1
138+
Depolarization2 = cudaq_runtime.Depolarization2
131139

132140
# Functions
133141
sample_async = cudaq_runtime.sample_async

python/cudaq/kernel/ast_bridge.py

+43-22
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# the terms of the Apache License 2.0 which accompanies this distribution. #
77
# ============================================================================ #
88
import ast
9+
import importlib
910
import hashlib
1011
import graphlib
1112
import sys, os
@@ -1313,26 +1314,40 @@ def visit_Attribute(self, node):
13131314
self.pushValue(complex.ImOp(value).result)
13141315
return
13151316

1316-
if isinstance(node.value,
1317-
ast.Name) and node.value.id in ['np', 'numpy', 'math']:
1318-
if node.attr == 'complex64':
1319-
self.pushValue(self.getComplexType(width=32))
1320-
return
1321-
if node.attr == 'complex128':
1322-
self.pushValue(self.getComplexType(width=64))
1323-
return
1324-
if node.attr == 'pi':
1325-
self.pushValue(self.getConstantFloat(np.pi))
1326-
return
1327-
if node.attr == 'e':
1328-
self.pushValue(self.getConstantFloat(np.e))
1329-
return
1330-
if node.attr == 'euler_gamma':
1331-
self.pushValue(self.getConstantFloat(np.euler_gamma))
1332-
return
1333-
raise RuntimeError(
1334-
"math expression {}.{} was not understood".format(
1335-
node.value.id, node.attr))
1317+
if isinstance(node.value, ast.Name):
1318+
if node.value.id in ['np', 'numpy', 'math']:
1319+
if node.attr == 'complex64':
1320+
self.pushValue(self.getComplexType(width=32))
1321+
return
1322+
if node.attr == 'complex128':
1323+
self.pushValue(self.getComplexType(width=64))
1324+
return
1325+
if node.attr == 'pi':
1326+
self.pushValue(self.getConstantFloat(np.pi))
1327+
return
1328+
if node.attr == 'e':
1329+
self.pushValue(self.getConstantFloat(np.e))
1330+
return
1331+
if node.attr == 'euler_gamma':
1332+
self.pushValue(self.getConstantFloat(np.euler_gamma))
1333+
return
1334+
raise RuntimeError(
1335+
"math expression {}.{} was not understood".format(
1336+
node.value.id, node.attr))
1337+
1338+
if node.value.id == 'cudaq':
1339+
if node.attr in [
1340+
'DepolarizationChannel', 'AmplitudeDampingChannel',
1341+
'PhaseFlipChannel', 'BitFlipChannel', 'PhaseDamping',
1342+
'ZError', 'XError', 'YError', 'Pauli1', 'Pauli2',
1343+
'Depolarization1', 'Depolarization2'
1344+
]:
1345+
cudaq_module = importlib.import_module('cudaq')
1346+
channel_class = getattr(cudaq_module, node.attr)
1347+
self.pushValue(
1348+
self.getConstantInt(channel_class.num_parameters))
1349+
self.pushValue(self.getConstantInt(hash(channel_class)))
1350+
return
13361351

13371352
def visit_Call(self, node):
13381353
"""
@@ -2455,8 +2470,9 @@ def bodyBuilder(iterVal):
24552470
params[i] = alloca
24562471

24572472
# The remaining arguments are the qubits
2458-
qubits = values[numParams:]
2459-
quake.ApplyNoiseOp(params, qubits, key=key)
2473+
asVeq = quake.ConcatOp(self.getVeqType(),
2474+
values[numParams:]).result
2475+
quake.ApplyNoiseOp(params, [asVeq], key=key)
24602476
return
24612477

24622478
if node.func.attr == 'compute_action':
@@ -4194,6 +4210,11 @@ def visit_Name(self, node):
41944210
# We want to create a hash value from it, and
41954211
# we then want to push the number of parameters and
41964212
# that hash value. This can only be used with apply_noise
4213+
if not hasattr(value, 'num_parameters'):
4214+
self.emitFatalError(
4215+
'apply_noise kraus channels must have `num_parameters` constant class attribute specified.'
4216+
)
4217+
41974218
self.pushValue(self.getConstantInt(value.num_parameters))
41984219
self.pushValue(self.getConstantInt(hash(value)))
41994220
return

python/runtime/common/py_NoiseModel.cpp

+170-7
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,75 @@ void bindNoiseModel(py::module &mod) {
5656
mod, "NoiseModel",
5757
"The `NoiseModel` defines a set of :class:`KrausChannel`'s applied to "
5858
"specific qubits after the invocation of specified quantum operations.")
59-
.def(py::init<>(), "Construct an empty noise model.")
59+
.def(py::init<>([mod]() {
60+
// Create the noise model
61+
auto model = std::make_unique<noise_model>();
62+
63+
// Define a map of channel names to generator functions
64+
static std::map<std::string, std::function<kraus_channel(
65+
const std::vector<double> &)>>
66+
channelGenerators = {
67+
{"DepolarizationChannel",
68+
[](const std::vector<double> &p) -> kraus_channel {
69+
return depolarization_channel(p);
70+
}},
71+
{"AmplitudeDampingChannel",
72+
[](const std::vector<double> &p) -> kraus_channel {
73+
return amplitude_damping_channel(p);
74+
}},
75+
{"BitFlipChannel",
76+
[](const std::vector<double> &p) -> kraus_channel {
77+
return bit_flip_channel(p);
78+
}},
79+
{"PhaseFlipChannel",
80+
[](const std::vector<double> &p) -> kraus_channel {
81+
return phase_flip_channel(p);
82+
}},
83+
{"XError",
84+
[](const std::vector<double> &p) -> kraus_channel {
85+
return x_error(p);
86+
}},
87+
{"YError",
88+
[](const std::vector<double> &p) -> kraus_channel {
89+
return y_error(p);
90+
}},
91+
{"ZError",
92+
[](const std::vector<double> &p) -> kraus_channel {
93+
return z_error(p);
94+
}},
95+
{"PhaseDamping",
96+
[](const std::vector<double> &p) -> kraus_channel {
97+
return phase_damping(p);
98+
}},
99+
{"Pauli1",
100+
[](const std::vector<double> &p) -> kraus_channel {
101+
return pauli1(p);
102+
}},
103+
{"Pauli2",
104+
[](const std::vector<double> &p) -> kraus_channel {
105+
return pauli2(p);
106+
}},
107+
{"Depolarization1",
108+
[](const std::vector<double> &p) -> kraus_channel {
109+
return depolarization1(p);
110+
}},
111+
{"Depolarization2",
112+
[](const std::vector<double> &p) -> kraus_channel {
113+
return depolarization2(p);
114+
}}};
115+
116+
// Register each channel generator
117+
for (const auto &[name, generator] : channelGenerators) {
118+
if (py::hasattr(mod, name.c_str())) {
119+
py::type channelType = py::getattr(mod, name.c_str());
120+
auto key = py::hash(channelType);
121+
model->register_channel(key, generator);
122+
}
123+
}
124+
125+
return model;
126+
}),
127+
"Construct a noise model with all built-in channels pre-registered.")
60128
.def(
61129
"register_channel",
62130
[](noise_model &self, const py::type krausT) {
@@ -234,9 +302,13 @@ void bindNoiseChannels(py::module &mod) {
234302
For `probability = 0.0`, the channel will behave noise-free.
235303
For `probability = 0.75`, the channel will fully depolarize the state.
236304
For `probability = 1.0`, the channel will be uniform.)#")
305+
.def(py::init<std::vector<double>>())
237306
.def(py::init<double>(), py::arg("probability"),
238307
"Initialize the `DepolarizationChannel` with the provided "
239-
"`probability`.");
308+
"`probability`.")
309+
.def_readonly_static(
310+
"num_parameters", &depolarization_channel::num_parameters,
311+
"The number of parameters this channel requires at construction.");
240312

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

260336
py::class_<bit_flip_channel, kraus_channel>(
261337
mod, "BitFlipChannel",
@@ -272,8 +348,12 @@ void bindNoiseChannels(py::module &mod) {
272348
273349
The probability of the qubit remaining in the same state is therefore `1 -
274350
probability`.)#")
351+
.def(py::init<std::vector<double>>())
275352
.def(py::init<double>(), py::arg("probability"),
276-
"Initialize the `BitFlipChannel` with the provided `probability`.");
353+
"Initialize the `BitFlipChannel` with the provided `probability`.")
354+
.def_readonly_static(
355+
"num_parameters", &bit_flip_channel::num_parameters,
356+
"The number of parameters this channel requires at construction.");
277357

278358
py::class_<phase_flip_channel, kraus_channel>(
279359
mod, "PhaseFlipChannel",
@@ -289,9 +369,92 @@ void bindNoiseChannels(py::module &mod) {
289369
290370
The probability of the qubit phase remaining untouched is therefore
291371
`1 - probability`.)#")
292-
.def(
293-
py::init<double>(), py::arg("probability"),
294-
"Initialize the `PhaseFlipChannel` with the provided `probability`.");
372+
.def(py::init<std::vector<double>>())
373+
.def(py::init<double>(), py::arg("probability"),
374+
"Initialize the `PhaseFlipChannel` with the provided `probability`.")
375+
.def_readonly_static(
376+
"num_parameters", &phase_flip_channel::num_parameters,
377+
"The number of parameters this channel requires at construction.");
378+
379+
py::class_<phase_damping, kraus_channel>(
380+
mod, "PhaseDamping",
381+
R"#(A Kraus channel that models the single-qubit phase damping error. This
382+
is similar to AmplitudeDamping, but for phase.)#")
383+
.def(py::init<std::vector<double>>())
384+
.def(py::init<double>())
385+
.def_readonly_static(
386+
"num_parameters", &phase_damping::num_parameters,
387+
"The number of parameters this channel requires at construction.");
388+
389+
py::class_<z_error, kraus_channel>(
390+
mod, "ZError",
391+
R"#(A Pauli error that applies the Z operator when an error
392+
occurs. It is the same as PhaseFlipChannel.)#")
393+
.def(py::init<std::vector<double>>())
394+
.def(py::init<double>())
395+
.def_readonly_static(
396+
"num_parameters", &z_error::num_parameters,
397+
"The number of parameters this channel requires at construction.");
398+
399+
py::class_<x_error, kraus_channel>(
400+
mod, "XError",
401+
R"#(A Pauli error that applies the X operator when an error
402+
occurs. It is the same as BitFlipChannel.)#")
403+
.def(py::init<std::vector<double>>())
404+
.def(py::init<double>())
405+
.def_readonly_static(
406+
"num_parameters", &x_error::num_parameters,
407+
"The number of parameters this channel requires at construction.");
408+
409+
py::class_<y_error, kraus_channel>(
410+
mod, "YError",
411+
R"#(A Pauli error that applies the Y operator when an error
412+
occurs.)#")
413+
.def(py::init<std::vector<double>>())
414+
.def(py::init<double>())
415+
.def_readonly_static(
416+
"num_parameters", &y_error::num_parameters,
417+
"The number of parameters this channel requires at construction.");
418+
419+
py::class_<pauli1, kraus_channel>(
420+
mod, "Pauli1",
421+
R"#(A single-qubit Pauli error that applies either an X error, Y error,
422+
or Z error. The probability of each X, Y, or Z error is supplied as a
423+
parameter.)#")
424+
.def(py::init<std::vector<double>>())
425+
.def_readonly_static(
426+
"num_parameters", &pauli1::num_parameters,
427+
"The number of parameters this channel requires at construction.");
428+
429+
py::class_<pauli2, kraus_channel>(
430+
mod, "Pauli2",
431+
R"#(A 2-qubit Pauli error that applies one of the following errors, with
432+
the probabilities specified as a vector. Possible errors: IX, IY, IZ, XI, XX,
433+
XY, XZ, YI, YX, YY, YZ, ZI, ZX, ZY, and ZZ.)#")
434+
.def(py::init<std::vector<double>>())
435+
.def_readonly_static(
436+
"num_parameters", &pauli2::num_parameters,
437+
"The number of parameters this channel requires at construction.");
438+
439+
py::class_<depolarization1, kraus_channel>(
440+
mod, "Depolarization1",
441+
R"#(The same as DepolarizationChannel (single qubit depolarization))#")
442+
.def(py::init<std::vector<double>>())
443+
.def(py::init<double>())
444+
.def_readonly_static(
445+
"num_parameters", &depolarization1::num_parameters,
446+
"The number of parameters this channel requires at construction.");
447+
448+
py::class_<depolarization2, kraus_channel>(
449+
mod, "Depolarization2",
450+
R"#(A 2-qubit depolarization error that applies one of the following
451+
errors. Possible errors: IX, IY, IZ, XI, XX, XY, XZ, YI, YX, YY, YZ, ZI, ZX,
452+
ZY, and ZZ.)#")
453+
.def(py::init<std::vector<double>>())
454+
.def(py::init<double>())
455+
.def_readonly_static(
456+
"num_parameters", &depolarization2::num_parameters,
457+
"The number of parameters this channel requires at construction.");
295458
}
296459

297460
void bindNoise(py::module &mod) {

0 commit comments

Comments
 (0)