<filename>test/terra/noise/test_quantum_error.py
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
QuantumError class tests
"""

import unittest
from test.terra import common
import numpy as np

from qiskit.quantum_info.operators.channel.superop import SuperOp
from qiskit.quantum_info.operators.channel.kraus import Kraus
from qiskit.providers.aer.noise.noiseerror import NoiseError
from qiskit.providers.aer.noise.errors.quantum_error import QuantumError
from qiskit.providers.aer.noise.errors.errorutils import standard_gate_unitary


class TestQuantumError(common.QiskitAerTestCase):
    """Testing QuantumError class"""

    def test_standard_gate_unitary(self):
        """Test standard gates are correct"""

        def norm(a, b):
            return round(np.linalg.norm(a - b), 15)

        self.assertEqual(
            norm(standard_gate_unitary('id'), np.eye(2)),
            0,
            msg="identity matrix")
        self.assertEqual(
            norm(standard_gate_unitary('x'), np.array([[0, 1], [1, 0]])),
            0,
            msg="Pauli-X matrix")
        self.assertEqual(
            norm(standard_gate_unitary('y'), np.array([[0, -1j], [1j, 0]])),
            0,
            msg="Pauli-Y matrix")
        self.assertEqual(
            norm(standard_gate_unitary('z'), np.diag([1, -1])),
            0,
            msg="Pauli-Z matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('h'),
                np.array([[1, 1], [1, -1]]) / np.sqrt(2)),
            0,
            msg="Hadamard gate matrix")
        self.assertEqual(
            norm(standard_gate_unitary('s'), np.diag([1, 1j])),
            0,
            msg="Phase gate matrix")
        self.assertEqual(
            norm(standard_gate_unitary('sdg'), np.diag([1, -1j])),
            0,
            msg="Adjoint phase gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('t'), np.diag([1,
                                                     (1 + 1j) / np.sqrt(2)])),
            0,
            msg="T gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('tdg'),
                np.diag([1, (1 - 1j) / np.sqrt(2)])),
            0,
            msg="Adjoint T gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('cx'),
                np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0],
                          [0, 1, 0, 0]])),
            0,
            msg="Controlled-NOT gate matrix")
        self.assertEqual(
            norm(standard_gate_unitary('cz'), np.diag([1, 1, 1, -1])),
            0,
            msg="Controlled-Z gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('swap'),
                np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0],
                          [0, 0, 0, 1]])),
            0,
            msg="SWAP matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('ccx'),
                np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0],
                          [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1],
                          [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0],
                          [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 0, 0, 0,
                                                     0]])),
            0,
            msg="Toffoli gate matrix")

    def test_raise_probabilities_negative(self):
        """Test exception is raised for negative probabilities."""
        noise_ops = [([{
            "name": "id",
            "qubits": [0]
        }], 1.1), ([{
            "name": "x",
            "qubits": [0]
        }], -0.1)]
        self.assertRaises(NoiseError, lambda: QuantumError(noise_ops))

    def test_raise_probabilities_normalized_qobj(self):
        """Test exception is raised for qobj probabilities greater than 1."""
        noise_ops = [([{
            "name": "id",
            "qubits": [0]
        }], 0.9), ([{
            "name": "x",
            "qubits": [0]
        }], 0.2)]
        self.assertRaises(NoiseError, lambda: QuantumError(noise_ops))

    def test_raise_probabilities_normalized_unitary_kraus(self):
        """Test exception is raised for unitary kraus probs greater than 1."""
        A0 = np.sqrt(0.9) * np.eye(2)
        A1 = np.sqrt(0.2) * np.diag([1, -1])
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))

    def test_raise_probabilities_normalized_nonunitary_kraus(self):
        """Test exception is raised for non-unitary kraus probs greater than 1."""
        A0 = np.sqrt(0.9) * np.array([[1, 0], [0, np.sqrt(1 - 0.3)]],
                                     dtype=complex)
        A1 = np.sqrt(0.2) * np.array([[0, np.sqrt(0.3)], [0, 0]],
                                     dtype=complex)
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))

    def test_raise_non_cptp_kraus(self):
        """Test exception is raised for non-CPTP input."""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [np.sqrt(0.3), 0]], dtype=complex)
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))
        self.assertRaises(NoiseError, lambda: QuantumError([A0]))

    def test_raise_non_multiqubit_kraus(self):
        """Test exception is raised for non-multiqubit input."""
        A0 = np.sqrt(0.5) * np.diag([1, 1, 1])
        A1 = np.sqrt(0.5) * np.diag([1, 1, -1])
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))

    def test_pauli_conversion_standard_gates(self):
        """Test conversion of Pauli channel kraus to gates"""
        Ai = np.sqrt(0.25) * standard_gate_unitary('id')
        Ax = np.sqrt(0.25) * standard_gate_unitary('x')
        Ay = np.sqrt(0.25) * standard_gate_unitary('y')
        Az = np.sqrt(0.25) * standard_gate_unitary('z')
        error_dict = QuantumError([Ai, Ax, Ay, Az],
                                  standard_gates=True).as_dict()
        self.assertEqual(error_dict['type'], 'qerror')
        self.assertAlmostEqual(
            np.linalg.norm(
                np.array(4 * [0.25]) - np.array(error_dict['probabilities'])),
            0.0)
        for instr in error_dict['instructions']:
            self.assertEqual(len(instr), 1)
            self.assertIn(instr[0]['name'], ['x', 'y', 'z', 'id'])
            self.assertEqual(instr[0]['qubits'], [0])

    def test_pauli_conversion_unitary(self):
        """Test conversion of Pauli channel kraus to unitary qobj"""
        Ai = np.sqrt(0.25) * standard_gate_unitary('id')
        Ax = np.sqrt(0.25) * standard_gate_unitary('x')
        Ay = np.sqrt(0.25) * standard_gate_unitary('y')
        Az = np.sqrt(0.25) * standard_gate_unitary('z')
        error_dict = QuantumError([Ai, Ax, Ay, Az],
                                  standard_gates=False).as_dict()
        self.assertEqual(error_dict['type'], 'qerror')
        self.assertAlmostEqual(
            np.linalg.norm(
                np.array(4 * [0.25]) - np.array(error_dict['probabilities'])),
            0.0)
        for instr in error_dict['instructions']:
            self.assertEqual(len(instr), 1)
            self.assertIn(instr[0]['name'], ['unitary', 'id'])
            self.assertEqual(instr[0]['qubits'], [0])

    def test_tensor_both_kraus(self):
        """Test tensor of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).tensor(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).tensor(QuantumError([B0, B1]))
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0, 1])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect tensor kraus")

    def test_tensor_both_unitary_instruction(self):
        """Test tensor of two unitary instruction errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=False)
        error = error1.tensor(error0)
        # Kronecker product unitaries
        target_unitaries = [
            np.kron(unitaries1[0], unitaries0[0]),
            np.kron(unitaries1[0], unitaries0[1]),
            np.kron(unitaries1[1], unitaries0[0]),
            np.kron(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0, 1])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect tensor probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect tensor unitaries")

    def test_tensor_both_unitary_standard_gates(self):
        """Test tensor of two unitary standard gate errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=True)
        error = error1.tensor(error0)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'y',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [1]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect tensor probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect tensor circuits")

    def test_expand_both_kraus(self):
        """Test expand of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).expand(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).expand(QuantumError([B0, B1]))
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0, 1])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect expand kraus")

    def test_expand_both_unitary_instruction(self):
        """Test expand of two unitary instruction errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=False)
        error = error0.expand(error1)
        # Kronecker product unitaries
        target_unitaries = [
            np.kron(unitaries1[0], unitaries0[0]),
            np.kron(unitaries1[0], unitaries0[1]),
            np.kron(unitaries1[1], unitaries0[0]),
            np.kron(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0, 1])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect expand probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect expand unitaries")

    def test_expand_both_unitary_standard_gates(self):
        """Test expand of two unitary standard gate errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=True)
        error = error0.expand(error1)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'y',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [1]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect expand probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect expand circuits")

    def test_raise_compose_different_dim(self):
        """Test composing incompatible errors raises exception"""
        error0 = QuantumError([np.diag([1, 1, 1,
                                        -1])])  # 2-qubit coherent error
        error1 = QuantumError([np.diag([1, -1])])  # 1-qubit coherent error
        self.assertRaises(NoiseError, lambda: error0.compose(error1))
        self.assertRaises(NoiseError, lambda: error1.compose(error0))

    def test_compose_both_kraus(self):
        """Test composition of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).compose(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).compose(QuantumError([B0, B1]))
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect compose kraus")

    def test_compose_both_unitary(self):
        """Test composition of two unitary errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ], standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ], standard_gates=False)
        error = error0.compose(error1)
        # Kronecker product unitaries
        target_unitaries = [
            np.dot(unitaries1[0], unitaries0[0]),
            np.dot(unitaries1[0], unitaries0[1]),
            np.dot(unitaries1[1], unitaries0[0]),
            np.dot(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect compose unitaries")

    def test_compose_both_qobj(self):
        """Test composition of two circuit errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ], standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ], standard_gates=True)
        error = error0.compose(error1)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [0]
        }], [{
            'name': 'y',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [0]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect compose circuits")

    def test_compose_front_both_kraus(self):
        """Test front composition of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).compose(Kraus([B0, B1]), front=True))
        error = QuantumError([A0, A1]).compose(QuantumError([B0, B1]), front=True)
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect front compose kraus")

    def test_compose_front_both_unitary(self):
        """Test front composition of two unitary errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=False)
        error = error1.compose(error0, front=True)
        # Kronecker product unitaries
        target_unitaries = [
            np.dot(unitaries1[0], unitaries0[0]),
            np.dot(unitaries1[0], unitaries0[1]),
            np.dot(unitaries1[1], unitaries0[0]),
            np.dot(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect compose unitaries")

    def test_compose_front_both_qobj(self):
        """Test front composition of two circuit errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=True)
        error = error1.compose(error0, front=True)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [0]
        }], [ {
            'name': 'y',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [0]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect compose circuits")

    def test_to_quantumchannel_kraus(self):
        """Test to_quantumchannel for Kraus inputs."""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        target = SuperOp(Kraus([A0, A1])).tensor(SuperOp(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).tensor(QuantumError([B0, B1]))
        self.assertEqual(target, error.to_quantumchannel())

    def test_to_quantumchannel_circuit(self):
        """Test to_quantumchannel for circuit inputs."""
        noise_ops = [([{
            'name': 'reset',
            'qubits': [0]
        }], 0.2), ([{
            'name': 'reset',
            'qubits': [1]
        }], 0.3), ([{
            'name': 'id',
            'qubits': [0]
        }], 0.5)]
        error = QuantumError(noise_ops)
        reset = SuperOp(
            np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]))
        iden = SuperOp(np.eye(4))
        target = 0.2 * iden.tensor(reset) + 0.3 * reset.tensor(
            iden) + 0.5 * iden.tensor(iden)
        self.assertEqual(target, error.to_quantumchannel())

    def test_equal(self):
        """Test two quantum errors are equal"""
        Ai = np.sqrt(0.25) * standard_gate_unitary('id')
        Ax = np.sqrt(0.25) * standard_gate_unitary('x')
        Ay = np.sqrt(0.25) * standard_gate_unitary('y')
        Az = np.sqrt(0.25) * standard_gate_unitary('z')
        error1 = QuantumError([Ai, Ax, Ay, Az], standard_gates=True)
        error2 = QuantumError([Ai, Ax, Ay, Az], standard_gates=False)
        self.assertEqual(error1, error2)


if __name__ == '__main__':
    unittest.main()
<filename>test/terra/noise/test_quantum_error.py
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
QuantumError class tests
"""

import unittest
from test.terra import common
import numpy as np

from qiskit.quantum_info.operators.channel.superop import SuperOp
from qiskit.quantum_info.operators.channel.kraus import Kraus
from qiskit.providers.aer.noise.noiseerror import NoiseError
from qiskit.providers.aer.noise.errors.quantum_error import QuantumError
from qiskit.providers.aer.noise.errors.errorutils import standard_gate_unitary


class TestQuantumError(common.QiskitAerTestCase):
    """Testing QuantumError class"""

    def test_standard_gate_unitary(self):
        """Test standard gates are correct"""

        def norm(a, b):
            return round(np.linalg.norm(a - b), 15)

        self.assertEqual(
            norm(standard_gate_unitary('id'), np.eye(2)),
            0,
            msg="identity matrix")
        self.assertEqual(
            norm(standard_gate_unitary('x'), np.array([[0, 1], [1, 0]])),
            0,
            msg="Pauli-X matrix")
        self.assertEqual(
            norm(standard_gate_unitary('y'), np.array([[0, -1j], [1j, 0]])),
            0,
            msg="Pauli-Y matrix")
        self.assertEqual(
            norm(standard_gate_unitary('z'), np.diag([1, -1])),
            0,
            msg="Pauli-Z matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('h'),
                np.array([[1, 1], [1, -1]]) / np.sqrt(2)),
            0,
            msg="Hadamard gate matrix")
        self.assertEqual(
            norm(standard_gate_unitary('s'), np.diag([1, 1j])),
            0,
            msg="Phase gate matrix")
        self.assertEqual(
            norm(standard_gate_unitary('sdg'), np.diag([1, -1j])),
            0,
            msg="Adjoint phase gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('t'), np.diag([1,
                                                     (1 + 1j) / np.sqrt(2)])),
            0,
            msg="T gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('tdg'),
                np.diag([1, (1 - 1j) / np.sqrt(2)])),
            0,
            msg="Adjoint T gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('cx'),
                np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0],
                          [0, 1, 0, 0]])),
            0,
            msg="Controlled-NOT gate matrix")
        self.assertEqual(
            norm(standard_gate_unitary('cz'), np.diag([1, 1, 1, -1])),
            0,
            msg="Controlled-Z gate matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('swap'),
                np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0],
                          [0, 0, 0, 1]])),
            0,
            msg="SWAP matrix")
        self.assertEqual(
            norm(
                standard_gate_unitary('ccx'),
                np.array([[1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0],
                          [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1],
                          [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0],
                          [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 0, 0, 0,
                                                     0]])),
            0,
            msg="Toffoli gate matrix")

    def test_raise_probabilities_negative(self):
        """Test exception is raised for negative probabilities."""
        noise_ops = [([{
            "name": "id",
            "qubits": [0]
        }], 1.1), ([{
            "name": "x",
            "qubits": [0]
        }], -0.1)]
        self.assertRaises(NoiseError, lambda: QuantumError(noise_ops))

    def test_raise_probabilities_normalized_qobj(self):
        """Test exception is raised for qobj probabilities greater than 1."""
        noise_ops = [([{
            "name": "id",
            "qubits": [0]
        }], 0.9), ([{
            "name": "x",
            "qubits": [0]
        }], 0.2)]
        self.assertRaises(NoiseError, lambda: QuantumError(noise_ops))

    def test_raise_probabilities_normalized_unitary_kraus(self):
        """Test exception is raised for unitary kraus probs greater than 1."""
        A0 = np.sqrt(0.9) * np.eye(2)
        A1 = np.sqrt(0.2) * np.diag([1, -1])
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))

    def test_raise_probabilities_normalized_nonunitary_kraus(self):
        """Test exception is raised for non-unitary kraus probs greater than 1."""
        A0 = np.sqrt(0.9) * np.array([[1, 0], [0, np.sqrt(1 - 0.3)]],
                                     dtype=complex)
        A1 = np.sqrt(0.2) * np.array([[0, np.sqrt(0.3)], [0, 0]],
                                     dtype=complex)
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))

    def test_raise_non_cptp_kraus(self):
        """Test exception is raised for non-CPTP input."""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [np.sqrt(0.3), 0]], dtype=complex)
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))
        self.assertRaises(NoiseError, lambda: QuantumError([A0]))

    def test_raise_non_multiqubit_kraus(self):
        """Test exception is raised for non-multiqubit input."""
        A0 = np.sqrt(0.5) * np.diag([1, 1, 1])
        A1 = np.sqrt(0.5) * np.diag([1, 1, -1])
        self.assertRaises(NoiseError, lambda: QuantumError([A0, A1]))

    def test_pauli_conversion_standard_gates(self):
        """Test conversion of Pauli channel kraus to gates"""
        Ai = np.sqrt(0.25) * standard_gate_unitary('id')
        Ax = np.sqrt(0.25) * standard_gate_unitary('x')
        Ay = np.sqrt(0.25) * standard_gate_unitary('y')
        Az = np.sqrt(0.25) * standard_gate_unitary('z')
        error_dict = QuantumError([Ai, Ax, Ay, Az],
                                  standard_gates=True).as_dict()
        self.assertEqual(error_dict['type'], 'qerror')
        self.assertAlmostEqual(
            np.linalg.norm(
                np.array(4 * [0.25]) - np.array(error_dict['probabilities'])),
            0.0)
        for instr in error_dict['instructions']:
            self.assertEqual(len(instr), 1)
            self.assertIn(instr[0]['name'], ['x', 'y', 'z', 'id'])
            self.assertEqual(instr[0]['qubits'], [0])

    def test_pauli_conversion_unitary(self):
        """Test conversion of Pauli channel kraus to unitary qobj"""
        Ai = np.sqrt(0.25) * standard_gate_unitary('id')
        Ax = np.sqrt(0.25) * standard_gate_unitary('x')
        Ay = np.sqrt(0.25) * standard_gate_unitary('y')
        Az = np.sqrt(0.25) * standard_gate_unitary('z')
        error_dict = QuantumError([Ai, Ax, Ay, Az],
                                  standard_gates=False).as_dict()
        self.assertEqual(error_dict['type'], 'qerror')
        self.assertAlmostEqual(
            np.linalg.norm(
                np.array(4 * [0.25]) - np.array(error_dict['probabilities'])),
            0.0)
        for instr in error_dict['instructions']:
            self.assertEqual(len(instr), 1)
            self.assertIn(instr[0]['name'], ['unitary', 'id'])
            self.assertEqual(instr[0]['qubits'], [0])

    def test_tensor_both_kraus(self):
        """Test tensor of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).tensor(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).tensor(QuantumError([B0, B1]))
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0, 1])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect tensor kraus")

    def test_tensor_both_unitary_instruction(self):
        """Test tensor of two unitary instruction errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=False)
        error = error1.tensor(error0)
        # Kronecker product unitaries
        target_unitaries = [
            np.kron(unitaries1[0], unitaries0[0]),
            np.kron(unitaries1[0], unitaries0[1]),
            np.kron(unitaries1[1], unitaries0[0]),
            np.kron(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0, 1])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect tensor probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect tensor unitaries")

    def test_tensor_both_unitary_standard_gates(self):
        """Test tensor of two unitary standard gate errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=True)
        error = error1.tensor(error0)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'y',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [1]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect tensor probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect tensor circuits")

    def test_expand_both_kraus(self):
        """Test expand of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).expand(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).expand(QuantumError([B0, B1]))
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0, 1])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect expand kraus")

    def test_expand_both_unitary_instruction(self):
        """Test expand of two unitary instruction errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=False)
        error = error0.expand(error1)
        # Kronecker product unitaries
        target_unitaries = [
            np.kron(unitaries1[0], unitaries0[0]),
            np.kron(unitaries1[0], unitaries0[1]),
            np.kron(unitaries1[1], unitaries0[0]),
            np.kron(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0, 1])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect expand probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect expand unitaries")

    def test_expand_both_unitary_standard_gates(self):
        """Test expand of two unitary standard gate errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=True)
        error = error0.expand(error1)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'y',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [1]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [1]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect expand probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect expand circuits")

    def test_raise_compose_different_dim(self):
        """Test composing incompatible errors raises exception"""
        error0 = QuantumError([np.diag([1, 1, 1,
                                        -1])])  # 2-qubit coherent error
        error1 = QuantumError([np.diag([1, -1])])  # 1-qubit coherent error
        self.assertRaises(NoiseError, lambda: error0.compose(error1))
        self.assertRaises(NoiseError, lambda: error1.compose(error0))

    def test_compose_both_kraus(self):
        """Test composition of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).compose(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).compose(QuantumError([B0, B1]))
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect compose kraus")

    def test_compose_both_unitary(self):
        """Test composition of two unitary errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ], standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ], standard_gates=False)
        error = error0.compose(error1)
        # Kronecker product unitaries
        target_unitaries = [
            np.dot(unitaries1[0], unitaries0[0]),
            np.dot(unitaries1[0], unitaries0[1]),
            np.dot(unitaries1[1], unitaries0[0]),
            np.dot(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect compose unitaries")

    def test_compose_both_qobj(self):
        """Test composition of two circuit errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ], standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ], standard_gates=True)
        error = error0.compose(error1)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [0]
        }], [{
            'name': 'y',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [0]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect compose circuits")

    def test_compose_front_both_kraus(self):
        """Test front composition of two kraus errors"""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        # Use quantum channels for reference
        target = SuperOp(Kraus([A0, A1]).compose(Kraus([B0, B1]), front=True))
        error = QuantumError([A0, A1]).compose(QuantumError([B0, B1]), front=True)
        kraus, p = error.error_term(0)
        self.assertEqual(p, 1)
        self.assertEqual(kraus[0]['name'], 'kraus')
        self.assertEqual(kraus[0]['qubits'], [0])
        error_superop = SuperOp(Kraus(kraus[0]['params']))
        self.assertEqual(target, error_superop, msg="Incorrect front compose kraus")

    def test_compose_front_both_unitary(self):
        """Test front composition of two unitary errors."""
        unitaries0 = [standard_gate_unitary('z'), standard_gate_unitary('s')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=False)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=False)
        error = error1.compose(error0, front=True)
        # Kronecker product unitaries
        target_unitaries = [
            np.dot(unitaries1[0], unitaries0[0]),
            np.dot(unitaries1[0], unitaries0[1]),
            np.dot(unitaries1[1], unitaries0[0]),
            np.dot(unitaries1[1], unitaries0[1])
        ]
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]

        for j in range(4):
            circ, p = error.error_term(j)
            unitary = circ[0]['params'][0]
            self.assertEqual(circ[0]['name'], 'unitary')
            self.assertEqual(circ[0]['qubits'], [0])
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(unitary, target_unitaries)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(
            target_unitaries, [], msg="Incorrect compose unitaries")

    def test_compose_front_both_qobj(self):
        """Test front composition of two circuit errors"""
        unitaries0 = [standard_gate_unitary('id'), standard_gate_unitary('z')]
        probs0 = [0.9, 0.1]
        unitaries1 = [standard_gate_unitary('x'), standard_gate_unitary('y')]
        probs1 = [0.6, 0.4]
        error0 = QuantumError([
            np.sqrt(probs0[0]) * unitaries0[0],
            np.sqrt(probs0[1]) * unitaries0[1]
        ],
                              standard_gates=True)
        error1 = QuantumError([
            np.sqrt(probs1[0]) * unitaries1[0],
            np.sqrt(probs1[1]) * unitaries1[1]
        ],
                              standard_gates=True)
        error = error1.compose(error0, front=True)
        # Kronecker product probabilities
        target_probs = [
            probs1[0] * probs0[0], probs1[0] * probs0[1],
            probs1[1] * probs0[0], probs1[1] * probs0[1]
        ]
        # Target circuits
        target_circs = [[{
            'name': 'x',
            'qubits': [0]
        }], [ {
            'name': 'y',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'x',
            'qubits': [0]
        }], [{
            'name': 'z',
            'qubits': [0]
        }, {
            'name': 'y',
            'qubits': [0]
        }]]
        for j in range(4):
            circ, p = error.error_term(j)
            # Remove prob from target if it is found
            # later we will check that target_probs is empty so all
            # the required ones have been removed
            self.remove_if_found(p, target_probs)
            self.remove_if_found(circ, target_circs)
        # Check we had all the correct target probs and unitaries
        # by seeing if these lists are empty
        # Note that this doesn't actually check that the correct
        # prob was assigned to the correct unitary.
        self.assertEqual(
            target_probs, [], msg="Incorrect compose probabilities")
        self.assertEqual(target_circs, [], msg="Incorrect compose circuits")

    def test_to_quantumchannel_kraus(self):
        """Test to_quantumchannel for Kraus inputs."""
        A0 = np.array([[1, 0], [0, np.sqrt(1 - 0.3)]], dtype=complex)
        A1 = np.array([[0, 0], [0, np.sqrt(0.3)]], dtype=complex)
        B0 = np.array([[1, 0], [0, np.sqrt(1 - 0.5)]], dtype=complex)
        B1 = np.array([[0, 0], [0, np.sqrt(0.5)]], dtype=complex)
        target = SuperOp(Kraus([A0, A1])).tensor(SuperOp(Kraus([B0, B1])))
        error = QuantumError([A0, A1]).tensor(QuantumError([B0, B1]))
        self.assertEqual(target, error.to_quantumchannel())

    def test_to_quantumchannel_circuit(self):
        """Test to_quantumchannel for circuit inputs."""
        noise_ops = [([{
            'name': 'reset',
            'qubits': [0]
        }], 0.2), ([{
            'name': 'reset',
            'qubits': [1]
        }], 0.3), ([{
            'name': 'id',
            'qubits': [0]
        }], 0.5)]
        error = QuantumError(noise_ops)
        reset = SuperOp(
            np.array([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]))
        iden = SuperOp(np.eye(4))
        target = 0.2 * iden.tensor(reset) + 0.3 * reset.tensor(
            iden) + 0.5 * iden.tensor(iden)
        self.assertEqual(target, error.to_quantumchannel())

    def test_equal(self):
        """Test two quantum errors are equal"""
        Ai = np.sqrt(0.25) * standard_gate_unitary('id')
        Ax = np.sqrt(0.25) * standard_gate_unitary('x')
        Ay = np.sqrt(0.25) * standard_gate_unitary('y')
        Az = np.sqrt(0.25) * standard_gate_unitary('z')
        error1 = QuantumError([Ai, Ax, Ay, Az], standard_gates=True)
        error2 = QuantumError([Ai, Ax, Ay, Az], standard_gates=False)
        self.assertEqual(error1, error2)


if __name__ == '__main__':
    unittest.main()
# This code is part of Qiskit. # # (C) Copyright IBM 2018, 2019. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. # # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. QuantumError class tests Testing QuantumError class Test standard gates are correct Test exception is raised for negative probabilities. Test exception is raised for qobj probabilities greater than 1. Test exception is raised for unitary kraus probs greater than 1. Test exception is raised for non-unitary kraus probs greater than 1. Test exception is raised for non-CPTP input. Test exception is raised for non-multiqubit input. Test conversion of Pauli channel kraus to gates Test conversion of Pauli channel kraus to unitary qobj Test tensor of two kraus errors # Use quantum channels for reference Test tensor of two unitary instruction errors. # Kronecker product unitaries # Kronecker product probabilities # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test tensor of two unitary standard gate errors # Kronecker product probabilities # Target circuits # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test expand of two kraus errors # Use quantum channels for reference Test expand of two unitary instruction errors. # Kronecker product unitaries # Kronecker product probabilities # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test expand of two unitary standard gate errors # Kronecker product probabilities # Target circuits # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test composing incompatible errors raises exception # 2-qubit coherent error # 1-qubit coherent error Test composition of two kraus errors # Use quantum channels for reference Test composition of two unitary errors. # Kronecker product unitaries # Kronecker product probabilities # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test composition of two circuit errors # Kronecker product probabilities # Target circuits # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test front composition of two kraus errors # Use quantum channels for reference Test front composition of two unitary errors. # Kronecker product unitaries # Kronecker product probabilities # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test front composition of two circuit errors # Kronecker product probabilities # Target circuits # Remove prob from target if it is found # later we will check that target_probs is empty so all # the required ones have been removed # Check we had all the correct target probs and unitaries # by seeing if these lists are empty # Note that this doesn't actually check that the correct # prob was assigned to the correct unitary. Test to_quantumchannel for Kraus inputs. Test to_quantumchannel for circuit inputs. Test two quantum errors are equal