Skip to content

Commit

Permalink
Add conversion support for Qiskit noise models (#577)
Browse files Browse the repository at this point in the history
* add changes

* import order

* remove unused mapping

* doc tweaks

* docs tweak

* apply suggestions

* apply suggestions

* fix example

* apply suggestions

* add suggestions

* typo

* update kwarg

* add kwargs tests

* remove skip test

* minor tweak
  • Loading branch information
obliviateandsurrender authored Jul 25, 2024
1 parent 4cb1847 commit 30b8c31
Show file tree
Hide file tree
Showing 7 changed files with 574 additions and 2 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

### New features since last release

* Added support for converting Qiskit noise models to
PennyLane ``NoiseModels`` using ``load_noise_model``.
[(#577)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/577)

* Qiskit Sessions can now be used for the ``qiskit.remote`` device with the ``qiskit_session`` context
manager.
[(#551)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/551)
Expand Down Expand Up @@ -42,6 +46,10 @@ capabilities and Qiskit.

This release contains contributions from (in alphabetical order):

Utkarsh Azad
Lillian M. A. Frederiksen
Austin Huang

---
# Release 0.37.0

Expand Down
2 changes: 1 addition & 1 deletion pennylane_qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
from .aer import AerDevice
from .basic_sim import BasicSimulatorDevice
from .remote import RemoteDevice
from .converter import load, load_pauli_op, load_qasm, load_qasm_from_file
from .converter import load, load_pauli_op, load_qasm, load_qasm_from_file, load_noise_model
76 changes: 76 additions & 0 deletions pennylane_qiskit/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@
from sympy import lambdify

import pennylane as qml
from pennylane.noise.conditionals import WiresIn, _rename
from pennylane.operation import AnyWires
import pennylane.ops as pennylane_ops
from pennylane.tape.tape import rotations_and_diagonal_measurements

from .noise_models import _build_noise_model_map

# pylint: disable=too-many-instance-attributes
QISKIT_OPERATION_MAP = {
# native PennyLane operations also native to qiskit
"PauliX": lib.XGate,
Expand Down Expand Up @@ -1218,3 +1223,74 @@ def _expr_eval_clvals(clbits, clvals, expr_func, bitwise=False):
condition_res = expr_func(meas1, clreg2)

return condition_res


def load_noise_model(noise_model, **kwargs) -> qml.NoiseModel:
"""Loads a PennyLane `NoiseModel <https://docs.pennylane.ai/en/stable/code/api/pennylane.NoiseModel.html>`_
from a Qiskit `noise model <https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.NoiseModel.html>`_.
Args:
noise_model (qiskit_aer.noise.NoiseModel): a Qiskit noise model object
kwargs: optional keyword arguments for the conversion of the noise model
Keyword Arguments:
verbose (bool): show a complete list of Kraus matrices for ``qml.QubitChannel`` instead of
number of Kraus matrices and the number of qubits they act on. The default is ``False``
decimal_places (int): number of decimal places to round the Kraus matrices when they are
being displayed for each ``qml.QubitChannel`` with ``verbose=False``.
Returns:
pennylane.NoiseModel: An equivalent noise model constructed in PennyLane
Raises:
ValueError: When an encountered quantum error cannot be converted.
.. note::
Currently, PennyLane noise models do not support readout errors, so those will be skipped during 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:
>>> load_noise_model(noise_model)
NoiseModel({
OpIn(['RZ', 'RY']): QubitChannel(num_kraus=4, num_wires=1)
OpIn(['CNOT']): QubitChannel(num_kraus=16, num_wires=2)
})
"""
# Build model maps for quantum error and readout errors in the noise model
qerror_dmap, _ = _build_noise_model_map(noise_model)
model_map = {}
for error, wires_map in qerror_dmap.items():
conditions = []
for wires, operations in wires_map.items():
cond = qml.noise.op_in(operations)
if wires != AnyWires:
cond &= WiresIn(wires)
conditions.append(cond)
fcond = reduce(lambda cond1, cond2: cond1 | cond2, conditions)

noise = qml.noise.partial_wires(error)
if isinstance(error, qml.QubitChannel) and not kwargs.get("verbose", False):
kraus_shape = qml.math.shape(error.data)
num_kraus, num_wires = kraus_shape[0], int(np.log2(kraus_shape[1]))
noise = _rename(f"QubitChannel(num_kraus={num_kraus}, num_wires={num_wires})")(noise)

if isinstance(error, qml.QubitChannel) and kwargs.get("verbose", False):
if (decimals := kwargs.get("decimal_places", None)) is not None:
kraus_matrices = list(np.round(error.data, decimals=decimals))
noise = _rename(f"QubitChannel(Klist={kraus_matrices})")(noise)

model_map[fcond] = noise

return qml.NoiseModel(model_map)
133 changes: 133 additions & 0 deletions pennylane_qiskit/noise_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright 2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
This module contains functions for converting Qiskit NoiseModel objects
into PennyLane NoiseModels.
"""
from collections import defaultdict
from typing import Tuple
from warnings import warn

import pennylane as qml
from pennylane.operation import AnyWires
from qiskit.quantum_info.operators.channel import Kraus

# pylint:disable = protected-access
qiskit_op_map = {
"x": "X",
"y": "Y",
"z": "Z",
"h": "Hadamard",
"cx": "CNOT",
"cz": "CZ",
"swap": "SWAP",
"iswap": "ISWAP",
"rx": "RX",
"ry": "RY",
"rz": "RZ",
"id": "Identity",
"cswap": "CSWAP",
"crx": "CRX",
"cry": "CRY",
"crz": "CRZ",
"p": "PhaseShift",
"ccx": "Toffoli",
"qubitunitary": "QubitUnitary",
"u1": "U1",
"u2": "U2",
"u3": "U3",
"rzz": "IsingZZ",
"ryy": "IsingYY",
"rxx": "IsingXX",
"s": "S",
"t": "T",
"sx": "SX",
"cy": "CY",
"ch": "CH",
"cp": "CPhase",
"ccz": "CCZ",
"ecr": "ECR",
"sdg": qml.adjoint(qml.S),
"tdg": qml.adjoint(qml.T),
"sxdg": qml.adjoint(qml.SX),
"reset": qml.measure(AnyWires, reset=True), # TODO: Improve reset support
}


def _build_qerror_op(error) -> qml.QubitChannel:
"""Builds a PennyLane error channel from a Qiksit ``QuantumError`` object.
Args:
error (QuantumError): Quantum error object
Returns:
qml.QubitChannel: an equivalent PennyLane error channel
"""
try:
kraus_matrices = Kraus(error).data
except Exception as exc: # pragma: no cover
raise ValueError(f"Error {error} could not be converted.") from exc

return qml.QubitChannel(K_list=kraus_matrices, wires=AnyWires)


def _build_noise_model_map(noise_model) -> Tuple[dict, dict]:
"""Builds a noise model map from a Qiskit noise model. This noise model map can be used
to efficiently construct a PennyLane noise model.
Args:
noise_model (qiskit_aer.noise.NoiseModel): Qiskit's noise model
Returns:
(dict, dict): returns mappings for the given quantum errors and readout errors in the ``noise_model``.
For plugin developers: noise model map tuple consists of following two (nested) mappings for
quantum errors (qerror_dmap) and readout errors (rerror_dmap):
* qerror_dmap: noise_operation -> wires -> target_gate
.. code-block:: python
qerror_dmap = {
noise_op1: {
AnyWires: [qiskit_op1, qiskit_op2],
(0, 1): [qiskit_op3],
(2,): [qiskit_op4]
},
noise_op2: {
AnyWires: [qiskit_op5],
(1, 2): [qiskit_op6, qiskit_op7]
}
}
* rerror_dmap: noise_operation -> wires -> target_measurement
"""
qerror_dmap = defaultdict(lambda: defaultdict(list))

# Add default quantum errors
for gate_name, error in noise_model._default_quantum_errors.items():
noise_op = _build_qerror_op(error)
qerror_dmap[noise_op][AnyWires].append(qiskit_op_map[gate_name])

# Add specific qubit errors
for gate_name, qubit_dict in noise_model._local_quantum_errors.items():
for qubits, error in qubit_dict.items():
noise_op = _build_qerror_op(error)
qerror_dmap[noise_op][qubits].append(qiskit_op_map[gate_name])

# TODO: Add support for the readout error
rerror_dmap = defaultdict(lambda: defaultdict(list))
if noise_model._default_readout_error or noise_model._local_readout_errors:
warn("Readout errors are not supported currently and will be skipped.")

return qerror_dmap, rerror_dmap
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
'pennylane.io': [
'qiskit = pennylane_qiskit:load',
'qiskit_op = pennylane_qiskit:load_pauli_op',
'qiskit_noise = pennylane_qiskit:load_noise_model',
'qasm = pennylane_qiskit:load_qasm',
'qasm_file = pennylane_qiskit:load_qasm_from_file',
],
Expand Down
Loading

0 comments on commit 30b8c31

Please sign in to comment.