diff --git a/qiskit_aer/noise/errors/quantum_error.py b/qiskit_aer/noise/errors/quantum_error.py index 59d65c469f..0846467eeb 100644 --- a/qiskit_aer/noise/errors/quantum_error.py +++ b/qiskit_aer/noise/errors/quantum_error.py @@ -19,10 +19,10 @@ 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 @@ -30,6 +30,7 @@ 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 @@ -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) diff --git a/releasenotes/notes/quantum_error_from_dict-8188a864109edd67.yaml b/releasenotes/notes/quantum_error_from_dict-8188a864109edd67.yaml new file mode 100644 index 0000000000..87ab89e115 --- /dev/null +++ b/releasenotes/notes/quantum_error_from_dict-8188a864109edd67.yaml @@ -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. diff --git a/test/terra/noise/test_quantum_error.py b/test/terra/noise/test_quantum_error.py index f4d821ec02..07b790fe32 100644 --- a/test/terra/noise/test_quantum_error.py +++ b/test/terra/noise/test_quantum_error.py @@ -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 @@ -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()