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

Support for converting Qiskit Noise models #5996

Merged
merged 29 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9bf2c3a
init changes
obliviateandsurrender Jul 9, 2024
b117812
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
obliviateandsurrender Jul 9, 2024
567ebbf
add mocktests
obliviateandsurrender Jul 13, 2024
d3287ce
minor tweak
obliviateandsurrender Jul 13, 2024
4892fe1
changelog
obliviateandsurrender Jul 13, 2024
3dccd06
Merge branch 'master' of https://github.com/PennyLaneAI/pennylane int…
obliviateandsurrender Jul 13, 2024
6ea4f96
update example
obliviateandsurrender Jul 15, 2024
40d21c4
apply suggestions
obliviateandsurrender Jul 16, 2024
7dd3d4f
fix example
obliviateandsurrender Jul 17, 2024
439d3ea
minor tweak
obliviateandsurrender Jul 17, 2024
0ea258b
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 17, 2024
768aa23
add adjoint support
obliviateandsurrender Jul 18, 2024
ca0efd9
apply suggestions
obliviateandsurrender Jul 18, 2024
4d19c29
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 18, 2024
c7e2e8f
`io` tests
obliviateandsurrender Jul 19, 2024
6c89408
Merge branch 'noise-model-convert' of https://github.com/PennyLaneAI/…
obliviateandsurrender Jul 19, 2024
2a8079f
apply suggestions
obliviateandsurrender Jul 25, 2024
3b43e9b
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 25, 2024
5c25da5
apply suggestions
obliviateandsurrender Jul 26, 2024
ba578cf
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 26, 2024
1c47395
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 26, 2024
4c3b0e3
apply suggestion
obliviateandsurrender Jul 26, 2024
dacaf36
explicit args
obliviateandsurrender Jul 29, 2024
4a2cb8f
type hint
obliviateandsurrender Jul 29, 2024
8b5d970
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 29, 2024
0178255
minor tweak
obliviateandsurrender Jul 29, 2024
84d5711
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 29, 2024
cdaf4d3
empty commit
obliviateandsurrender Jul 29, 2024
ca196ef
Merge branch 'master' into noise-model-convert
obliviateandsurrender Jul 29, 2024
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
10 changes: 8 additions & 2 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
[(#5756)](https://github.com/PennyLaneAI/pennylane/pull/5756)
[(#5987)](https://github.com/PennyLaneAI/pennylane/pull/5987)

* The `split_to_single_terms` transform is added. This transform splits expectation values of sums
* A new `qml.from_qiskit_noise` method now allows one to convert a Qiskit ``NoiseModel`` to a
PennyLane ``NoiseModel`` via the Pennylane-Qiskit plugin.
[(#5996)](https://github.com/PennyLaneAI/pennylane/pull/5996)

* The `split_to_single_terms` transform is added. This transform splits expectation values of sums
into multiple single-term measurements on a single tape, providing better support for simulators
that can handle non-commuting observables but don't natively support multi-term observables.
[(#5884)](https://github.com/PennyLaneAI/pennylane/pull/5884)
Expand Down Expand Up @@ -214,8 +218,10 @@
<h3>Contributors ✍️</h3>

This release contains contributions from (in alphabetical order):

Guillermo Alonso,
Utkarsh Azad
Utkarsh Azad,
Ahmed Darwish,
Astral Cai,
Yushao Chen,
Gabriel Bottrill,
Expand Down
55 changes: 55 additions & 0 deletions pennylane/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,61 @@ def from_qiskit_op(qiskit_op, params=None, wires=None):
raise RuntimeError(_MISSING_QISKIT_PLUGIN_MESSAGE) from e


def from_qiskit_noise(noise_model, **kwargs):
"""Converts a Qiskit `NoiseModel <https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.NoiseModel.html>`__
into a PennyLane :class:`~.NoiseModel`.
isaacdevlugt marked this conversation as resolved.
Show resolved Hide resolved

Args:
noise_model (qiskit_aer.noise.NoiseModel): a Qiskit ``NoiseModel`` instance.
kwargs: Optional keyword arguments for conversion of the noise model.

Keyword Arguments:
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
verbose (bool): show a complete list of Kraus matrices for ``qml.QubitChannel`` instead of
the number of Kraus matrices and the number of qubits they act on. The default is ``False``.
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
decimal_places (int): number of decimal places to round the Kraus matrices when they are
being displayed for each ``qml.QubitChannel`` with ``verbose=False``.
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved

Returns:
qml.NoiseModel: The PennyLane noise model, created based on the input Qiskit
``NoiseModel`` object.
isaacdevlugt marked this conversation as resolved.
Show resolved Hide resolved

Raises:
ValueError: When a quantum error present in the noise model cannot be converted.

.. note::

- This function depends upon the PennyLane-Qiskit plugin, which can be installed following these
`installation instructions <https://docs.pennylane.ai/projects/qiskit/en/latest/installation.html>`__.
You may need to restart your kernel if you are running it in a notebook environment.
isaacdevlugt marked this conversation as resolved.
Show resolved Hide resolved
- Currently, PennyLane noise models do not support readout errors, so those will be skipped during
isaacdevlugt marked this conversation as resolved.
Show resolved Hide resolved
conversion.

**Example**

Consider the following noise model constructed in Qiskit:

>>> import qiskit_aer.noise as noise
>>> error_1 = noise.depolarizing_error(0.001, 1) # 1-qubit noise
>>> error_2 = noise.depolarizing_error(0.01, 2) # 2-qubit noise
>>> noise_model = noise.NoiseModel()
>>> noise_model.add_all_qubit_quantum_error(error_1, ['rz', 'ry'])
>>> noise_model.add_all_qubit_quantum_error(error_2, ['cx'])

This noise model can be converted into PennyLane using:

>>> from_qiskit_noise(noise_model)
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
NoiseModel({
OpIn(['RZ', 'RY']): QubitChannel(num_kraus=4, num_wires=1)
OpIn(['CNOT']): QubitChannel(num_kraus=16, num_wires=2)
obliviateandsurrender marked this conversation as resolved.
Show resolved Hide resolved
})
"""
try:
plugin_converter = plugin_converters["qiskit_noise"].load()
isaacdevlugt marked this conversation as resolved.
Show resolved Hide resolved
return plugin_converter(noise_model, **kwargs)
except KeyError as e:
raise RuntimeError(_MISSING_QISKIT_PLUGIN_MESSAGE) from e


def from_qasm(quantum_circuit: str, measurements=None):
r"""
Loads quantum circuits from a QASM string using the converter in the
Expand Down
31 changes: 18 additions & 13 deletions pennylane/noise/conditionals.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

import pennylane as qml
from pennylane.boolean_fn import BooleanFn
from pennylane.ops import Controlled
from pennylane.measurements import MeasurementValue, MidMeasureMP
from pennylane.ops import Adjoint, Controlled
from pennylane.templates import ControlledSequence
from pennylane.wires import WireError, Wires

Expand Down Expand Up @@ -182,7 +183,7 @@ def __init__(self, ops):
self._cops = _get_ops(ops)
self.condition = self._cops
super().__init__(
self._check_in_ops, f"OpIn({[getattr(op, '__name__') for op in self._cops]})"
self._check_in_ops, f"OpIn({[getattr(op, '__name__', op) for op in self._cops]})"
)

def _check_in_ops(self, operation):
Expand Down Expand Up @@ -226,7 +227,7 @@ def __init__(self, ops):
self._cond = [ops] if not isinstance(ops, (list, tuple, set)) else ops
self._cops = _get_ops(ops)
self.condition = self._cops
cops_names = list(getattr(op, "__name__") for op in self._cops)
cops_names = list(getattr(op, "__name__", op) for op in self._cops)
super().__init__(
self._check_eq_ops,
f"OpEq({cops_names if len(cops_names) > 1 else cops_names[0]})",
Expand Down Expand Up @@ -267,22 +268,26 @@ def _get_ops(val):
classes corresponding to val.
"""
vals = val if isinstance(val, (list, tuple, set)) else [val]
return tuple(
(
getattr(qml.ops, val, None) or getattr(qml, val)
if isinstance(val, str)
else (val if isclass(val) else getattr(val, "__class__"))
)
for val in vals
)
op_names = []
for _val in vals:
if isinstance(_val, str):
op_names.append(getattr(qml.ops, _val, None) or getattr(qml, _val))
elif isclass(_val):
op_names.append(_val)
elif isinstance(_val, (MeasurementValue, MidMeasureMP)):
mid_measure = _val if isinstance(_val, MidMeasureMP) else _val.measurements[0]
op_names.append(["MidMeasure", "Reset"][getattr(mid_measure, "reset", 0)])
else:
op_names.append(getattr(_val, "__class__"))
return tuple(op_names)


def _check_arithmetic_ops(op1, op2):
"""Helper method for comparing two arithmetic operators based on type check of the bases"""
# pylint: disable = unnecessary-lambda-assignment

if isinstance(op1, (Controlled, ControlledSequence)) or isinstance(
op2, (Controlled, ControlledSequence)
if isinstance(op1, (Adjoint, Controlled, ControlledSequence)) or isinstance(
op2, (Adjoint, Controlled, ControlledSequence)
soranjh marked this conversation as resolved.
Show resolved Hide resolved
):
return (
isinstance(op1, type(op2))
Expand Down
5 changes: 4 additions & 1 deletion tests/noise/test_conditionals.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ def test_get_wires_error(self):
(["CZ", "RY", "CNOT"], qml.CNOT([0, 1]), True),
(qml.Y(1), qml.RY(1.0, 1), False),
(qml.CNOT(["a", "c"]), qml.CNOT([0, 1]), True),
([qml.S("a"), qml.adjoint(qml.T)("b")], qml.adjoint(qml.T)([0]), True),
([qml.CZ(["a", "c"]), qml.Y(1)], qml.CZ([0, 1]), True),
([qml.RZ(1.9, 0), qml.Z(0) @ qml.Z(1)], qml.Z("b") @ qml.Z("a"), True),
([qml.Z(0) + qml.Z(1), qml.Z(2)], qml.Z("b") + qml.Z("a"), True),
Expand All @@ -211,6 +212,8 @@ def test_op_in(self, obj, op, result):
(qml.RX(0, 1), qml.RY(1.0, 1), False),
("RX", qml.RX(0, 1), True),
([qml.RX, qml.RY], qml.RX, False),
(qml.measure(1, reset=True), qml.measure(2, reset=True), True),
(qml.measure(1, reset=True), qml.measure(1, reset=False), False),
([qml.RX, qml.RY], [qml.RX(1.0, 1), qml.RY(2.0, 2)], True),
(["CZ", "RY"], [qml.CZ([0, 1]), qml.RY(1.0, [1])], True),
(qml.Z(0) @ qml.Z(1), qml.Z("b") @ qml.Z("a"), True),
Expand All @@ -229,7 +232,7 @@ def test_op_eq(self, obj, op, result):

assert isinstance(func, qml.BooleanFn)

op_repr = [getattr(op, "__name__") for op in _get_ops(obj)]
op_repr = [getattr(op, "__name__", op) for op in _get_ops(obj)]
assert str(func) == f"OpEq({op_repr if len(op_repr) > 1 else op_repr[0]})"
assert func(op) == result

Expand Down
14 changes: 12 additions & 2 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def call_args(self):
"qasm_file",
"qasm",
"qiskit_op",
"qiskit_noise",
"qiskit",
"quil_file",
"quil",
Expand All @@ -75,7 +76,11 @@ class TestLoad:

@pytest.mark.parametrize(
"method, entry_point_name",
[(qml.from_qiskit, "qiskit"), (qml.from_qiskit_op, "qiskit_op")],
[
(qml.from_qiskit, "qiskit"),
(qml.from_qiskit_op, "qiskit_op"),
(qml.from_qiskit_noise, "qiskit_noise"),
],
)
def test_qiskit_converter_does_not_exist(self, monkeypatch, method, entry_point_name):
"""Test that a RuntimeError with an appropriate message is raised if a Qiskit convenience
Expand All @@ -94,7 +99,11 @@ def test_qiskit_converter_does_not_exist(self, monkeypatch, method, entry_point_

@pytest.mark.parametrize(
"method, entry_point_name",
[(qml.from_qiskit, "qiskit"), (qml.from_qiskit_op, "qiskit_op")],
[
(qml.from_qiskit, "qiskit"),
(qml.from_qiskit_op, "qiskit_op"),
(qml.from_qiskit_noise, "qiskit_noise"),
],
)
def test_qiskit_converter_load_fails(self, monkeypatch, method, entry_point_name):
"""Test that an exception which is raised while calling a Qiskit convenience method (but
Expand All @@ -114,6 +123,7 @@ def test_qiskit_converter_load_fails(self, monkeypatch, method, entry_point_name
[
(qml.from_qiskit, "qiskit"),
(qml.from_qiskit_op, "qiskit_op"),
(qml.from_qiskit_noise, "qiskit_noise"),
(qml.from_pyquil, "pyquil_program"),
(qml.from_quil, "quil"),
(qml.from_quil_file, "quil_file"),
Expand Down
Loading