Skip to content

Commit

Permalink
Implementation and test of QuantumError.from_dict. (Qiskit#1942)
Browse files Browse the repository at this point in the history
* Implementation and test of QuantumError.from_dict.

* Made code lint warning free.

---------

Co-authored-by: Jun Doi <[email protected]>
  • Loading branch information
MarcMaussner and doichanj authored Oct 6, 2023
1 parent 67b8dcf commit 92e16f7
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 3 deletions.
62 changes: 60 additions & 2 deletions qiskit_aer/noise/errors/quantum_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,18 @@

import numpy as np

from qiskit.circuit import QuantumCircuit, Instruction, QuantumRegister
from qiskit.circuit import QuantumCircuit, Instruction, QuantumRegister, Reset
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.library.generalized_gates import PauliGate
from qiskit.circuit.library.standard_gates import IGate
from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.quantum_info.operators.channel import Kraus, SuperOp
from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel
from qiskit.quantum_info.operators.mixins import TolerancesMixin
from qiskit.quantum_info.operators.predicates import is_identity_matrix
from qiskit.quantum_info.operators.symplectic import Clifford
from qiskit.extensions import UnitaryGate
from ..noiseerror import NoiseError


Expand Down Expand Up @@ -345,6 +346,63 @@ def to_dict(self):
}
return error

@staticmethod
def from_dict(error):
"""Implement current error from a dictionary."""
# check if dictionary
if not isinstance(error, dict):
raise NoiseError("error is not a dictionary")
# check expected keys "type, id, operations, instructions, probabilities"
if (
("type" not in error)
or ("id" not in error)
or ("operations" not in error)
or ("instructions" not in error)
or ("probabilities" not in error)
):
raise NoiseError("erorr dictionary not containing expected keys")
error_instructions = error["instructions"]
error_probabilities = error["probabilities"]

if len(error_instructions) != len(error_probabilities):
raise NoiseError("probabilities not matching with instructions")
# parse instructions and turn to noise_ops
noise_ops = []
for idx, inst in enumerate(error_instructions):
noise_elem = []
for elem in inst:
inst_name = elem["name"]
inst_qubits = elem["qubits"]

if inst_name == "x":
noise_elem.append((XGate(), inst_qubits))
elif inst_name == "id":
noise_elem.append((IGate(), inst_qubits))
elif inst_name == "y":
noise_elem.append((YGate(), inst_qubits))
elif inst_name == "z":
noise_elem.append((ZGate(), inst_qubits))
elif inst_name == "kraus":
if "params" not in inst[0]:
raise NoiseError("kraus does not have a parameter value")
noise_elem.append((Kraus(inst[0]["params"]), inst_qubits))
elif inst_name == "reset":
noise_elem.append((Reset(), inst_qubits))
elif inst_name == "measure":
raise NoiseError("instruction 'measure' not supported")
elif inst_name == "unitary":
if "params" not in inst[0]:
raise NoiseError("unitary does not have a parameter value")
noise_elem.append((UnitaryGate(inst[0]["params"][0]), inst_qubits))
else:
raise NoiseError("error gate for instruction not recognized")

noise_ops.append((noise_elem, error_probabilities[idx]))

error_obj = QuantumError(noise_ops)

return error_obj

def compose(self, other, qargs=None, front=False):
if not isinstance(other, QuantumError):
other = QuantumError(other)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
features:
- |
Implements from_dict() method for QuantumError. This takes a dictionary
and checks if style is the one from to_dict() method. If any mismatches
are determined then NoiseError is raised. Else dict is parsed and a new
QuantumError with noise_ops created.
147 changes: 146 additions & 1 deletion test/terra/noise/test_quantum_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from qiskit.circuit.library.standard_gates import IGate, XGate, YGate, ZGate
from qiskit.extensions import UnitaryGate
from qiskit.quantum_info.operators import SuperOp, Kraus, Pauli
from qiskit_aer.noise import QuantumError
from qiskit_aer.noise import QuantumError, pauli_error, reset_error
from qiskit_aer.noise.noiseerror import NoiseError


Expand Down Expand Up @@ -255,6 +255,151 @@ def test_tensor_with_different_type_of_operator(self):
)
self.assertEqual(actual, expected)

def test_from_dict_pauli(self):
"""Test from_dict method for pauli errors."""
p_error_rate = 0.05
error_quantum = pauli_error([("X", p_error_rate), ("I", 1 - p_error_rate)])

error_dict = error_quantum.to_dict()
error_quantum2 = QuantumError.from_dict(error=error_dict)
self.assertEqual(error_quantum, error_quantum2)

def test_from_dict_kraus(self):
"""Test from_dict method for kraus channels."""
noise_ops = Kraus(
[np.sqrt(0.9) * np.array([[1, 0], [0, 1]]), np.sqrt(0.1) * np.array([[0, 1], [1, 0]])]
)

error_quantum = QuantumError(noise_ops)
error_dict = error_quantum.to_dict()

error_kraus = QuantumError.from_dict(error_dict)
self.assertEqual(error_quantum, error_kraus)

def test_from_dict_reset(self):
"""Test from_dict method for reset errors."""
error_quantum = reset_error(0.98, 0.02)

error_dict = error_quantum.to_dict()

error_reset = QuantumError.from_dict(error_dict)
self.assertEqual(error_quantum, error_reset)

def test_from_dict_unitarygate(self):
"""Test from_dict method for unitarygate errors."""
error_quantum = QuantumError(UnitaryGate(np.eye(2)))

error_dict = error_quantum.to_dict()

error_unitary = QuantumError.from_dict(error_dict)
self.assertEqual(error_quantum, error_unitary)

def test_from_dict_raise_if_error_is_measure(self):
"""Test exception is raised by from_dict method for measure errors."""
error_quantum = QuantumError(UnitaryGate(np.eye(2)))

error_dict = error_quantum.to_dict()

# exchange instruction "unitary" with "measure" to provoke exception
error_dict["instructions"][0][0]["name"] = "measure"
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict)

def test_from_dict_raise_if_parameter_is_non_dict(self):
"""Test exception is raised by from_dict if parameter is not a dict"""
dict_param = []
with self.assertRaises(NoiseError):
QuantumError.from_dict(dict_param)

def test_from_dict_raise_if_parameter_is_not_well_formed(self):
"""Test exception is raised by from_dict if parameter is not well formed"""
error_quantum = QuantumError(UnitaryGate(np.eye(2)))

# remove 'type'
error_dict_type = error_quantum.to_dict()
error_dict_type.pop("type")
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict_type)

# remove 'id'
error_dict_id = error_quantum.to_dict()
error_dict_id.pop("id")
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict_id)

# remove 'operations'
error_dict_operations = error_quantum.to_dict()
error_dict_operations.pop("operations")
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict_operations)

# remove 'instructions'
error_dict_instructions = error_quantum.to_dict()
error_dict_instructions.pop("instructions")
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict_instructions)

# remove 'probabilities'
error_dict_probabilities = error_quantum.to_dict()
error_dict_probabilities.pop("probabilities")
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict_probabilities)

def test_from_dict_raise_if_len_probabilites_is_not_len_instructions(self):
"""Test exception is raised by from_dict if length of probabilities does not meet length of instructions"""
# test more probabilities than instructions
error_quantum = QuantumError(UnitaryGate(np.eye(2)))

error_dict = error_quantum.to_dict()

# add another probabilities not matching no instructions
error_dict["probabilities"].append(0.8)
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict)

# test less probabilities than instructions
error_dict2 = error_quantum.to_dict()

# remove another probabilities not matching no instructions
error_dict2["probabilities"].remove(1.0)
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict2)

def test_from_dict_raise_if_kraus_has_no_params(self):
"""Test exception is raised by from_dict if kraus has not attribute params"""
noise_ops = Kraus(
[np.sqrt(0.9) * np.array([[1, 0], [0, 1]]), np.sqrt(0.1) * np.array([[0, 1], [1, 0]])]
)

error_quantum = QuantumError(noise_ops)
error_dict = error_quantum.to_dict()

# remove params to provoke exception
error_dict["instructions"][0][0].pop("params")
with self.assertRaises(NoiseError):
error_kraus = QuantumError.from_dict(error_dict)

def test_from_dict_raise_if_unitary_has_no_params(self):
error_quantum = QuantumError(UnitaryGate(np.eye(2)))

error_dict = error_quantum.to_dict()

# remove params to provoke exception
error_dict["instructions"][0][0].pop("params")
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict)

def test_from_dict_raise_if_instruction_not_supported(self):
"""Test exception is raised by from_dict if instruction is not supported"""
error_quantum = QuantumError(UnitaryGate(np.eye(2)))

error_dict = error_quantum.to_dict()

# exchange instruction "unitary" with "blubb" to provoke exception
error_dict["instructions"][0][0]["name"] = "blubb"
with self.assertRaises(NoiseError):
error_unitary = QuantumError.from_dict(error_dict)


if __name__ == "__main__":
unittest.main()

0 comments on commit 92e16f7

Please sign in to comment.