# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 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.

"""Randomized tests of quantum synthesis."""
import unittest
from test.python.quantum_info.test_synthesis import CheckDecompositions
from hypothesis import given, strategies, settings
import numpy as np

from qiskit import execute
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.extensions import UnitaryGate
from qiskit.providers.basicaer import UnitarySimulatorPy
from qiskit.quantum_info.random import random_unitary
from qiskit.quantum_info.synthesis.two_qubit_decompose import (
    two_qubit_cnot_decompose,
    TwoQubitBasisDecomposer,
    Ud,
)


class TestSynthesis(CheckDecompositions):
    """Test synthesis"""

    seed = strategies.integers(min_value=0, max_value=2**32 - 1)
    rotation = strategies.floats(min_value=-np.pi * 10, max_value=np.pi * 10)

    @given(seed)
    def test_1q_random(self, seed):
        """Checks one qubit decompositions"""
        unitary = random_unitary(2, seed=seed)
        self.check_one_qubit_euler_angles(unitary)
        self.check_one_qubit_euler_angles(unitary, "U3")
        self.check_one_qubit_euler_angles(unitary, "U1X")
        self.check_one_qubit_euler_angles(unitary, "PSX")
        self.check_one_qubit_euler_angles(unitary, "ZSX")
        self.check_one_qubit_euler_angles(unitary, "ZYZ")
        self.check_one_qubit_euler_angles(unitary, "ZXZ")
        self.check_one_qubit_euler_angles(unitary, "XYX")
        self.check_one_qubit_euler_angles(unitary, "RR")

    @settings(deadline=None)
    @given(seed)
    def test_2q_random(self, seed):
        """Checks two qubit decompositions"""
        unitary = random_unitary(4, seed=seed)
        self.check_exact_decomposition(unitary.data, two_qubit_cnot_decompose)

    @given(strategies.tuples(*[seed] * 5))
    def test_exact_supercontrolled_decompose_random(self, seeds):
        """Exact decomposition for random supercontrolled basis and random target"""
        k1 = np.kron(random_unitary(2, seed=seeds[0]).data, random_unitary(2, seed=seeds[1]).data)
        k2 = np.kron(random_unitary(2, seed=seeds[2]).data, random_unitary(2, seed=seeds[3]).data)
        basis_unitary = k1 @ Ud(np.pi / 4, 0, 0) @ k2
        decomposer = TwoQubitBasisDecomposer(UnitaryGate(basis_unitary))
        self.check_exact_decomposition(random_unitary(4, seed=seeds[4]).data, decomposer)

    @given(strategies.tuples(*[rotation] * 6), seed)
    def test_cx_equivalence_0cx_random(self, rnd, seed):
        """Check random circuits with  0 cx gates locally equivalent to identity."""
        qr = QuantumRegister(2, name="q")
        qc = QuantumCircuit(qr)

        qc.u(rnd[0], rnd[1], rnd[2], qr[0])
        qc.u(rnd[3], rnd[4], rnd[5], qr[1])

        sim = UnitarySimulatorPy()
        unitary = execute(qc, sim, seed_simulator=seed).result().get_unitary()
        self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 0)

    @given(strategies.tuples(*[rotation] * 12), seed)
    def test_cx_equivalence_1cx_random(self, rnd, seed):
        """Check random circuits with 1 cx gates locally equivalent to a cx."""
        qr = QuantumRegister(2, name="q")
        qc = QuantumCircuit(qr)

        qc.u(rnd[0], rnd[1], rnd[2], qr[0])
        qc.u(rnd[3], rnd[4], rnd[5], qr[1])

        qc.cx(qr[1], qr[0])

        qc.u(rnd[6], rnd[7], rnd[8], qr[0])
        qc.u(rnd[9], rnd[10], rnd[11], qr[1])

        sim = UnitarySimulatorPy()
        unitary = execute(qc, sim, seed_simulator=seed).result().get_unitary()
        self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 1)

    @given(strategies.tuples(*[rotation] * 18), seed)
    def test_cx_equivalence_2cx_random(self, rnd, seed):
        """Check random circuits with 2 cx gates locally equivalent to some circuit with 2 cx."""
        qr = QuantumRegister(2, name="q")
        qc = QuantumCircuit(qr)

        qc.u(rnd[0], rnd[1], rnd[2], qr[0])
        qc.u(rnd[3], rnd[4], rnd[5], qr[1])

        qc.cx(qr[1], qr[0])

        qc.u(rnd[6], rnd[7], rnd[8], qr[0])
        qc.u(rnd[9], rnd[10], rnd[11], qr[1])

        qc.cx(qr[0], qr[1])

        qc.u(rnd[12], rnd[13], rnd[14], qr[0])
        qc.u(rnd[15], rnd[16], rnd[17], qr[1])

        sim = UnitarySimulatorPy()
        unitary = execute(qc, sim, seed_simulator=seed).result().get_unitary()
        self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 2)

    @given(strategies.tuples(*[rotation] * 24), seed)
    def test_cx_equivalence_3cx_random(self, rnd, seed):
        """Check random circuits with 3 cx gates are outside the 0, 1, and 2 qubit regions."""
        qr = QuantumRegister(2, name="q")
        qc = QuantumCircuit(qr)

        qc.u(rnd[0], rnd[1], rnd[2], qr[0])
        qc.u(rnd[3], rnd[4], rnd[5], qr[1])

        qc.cx(qr[1], qr[0])

        qc.u(rnd[6], rnd[7], rnd[8], qr[0])
        qc.u(rnd[9], rnd[10], rnd[11], qr[1])

        qc.cx(qr[0], qr[1])

        qc.u(rnd[12], rnd[13], rnd[14], qr[0])
        qc.u(rnd[15], rnd[16], rnd[17], qr[1])

        qc.cx(qr[1], qr[0])

        qc.u(rnd[18], rnd[19], rnd[20], qr[0])
        qc.u(rnd[21], rnd[22], rnd[23], qr[1])

        sim = UnitarySimulatorPy()
        unitary = execute(qc, sim, seed_simulator=seed).result().get_unitary()
        self.assertEqual(two_qubit_cnot_decompose.num_basis_gates(unitary), 3)


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