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

"""Tests for stabilizer state synthesis methods."""


import unittest
from test import combine
from ddt import ddt

import numpy as np

from qiskit.test import QiskitTestCase
from qiskit.quantum_info.states import StabilizerState
from qiskit.quantum_info import random_clifford
from qiskit.synthesis.stabilizer import synth_stabilizer_layers, synth_stabilizer_depth_lnn
from qiskit.synthesis.linear.linear_circuits_utils import check_lnn_connectivity


@ddt
class TestStabDecomposeLayers(QiskitTestCase):
    """Tests for stabilizer state decomposition functions."""

    @combine(num_qubits=[4, 5, 6, 7])
    def test_decompose_stab(self, num_qubits):
        """Create layer decomposition for a stabilizer state, and check that it
        results in an equivalent stabilizer state."""
        rng = np.random.default_rng(1234)
        samples = 10
        for _ in range(samples):
            cliff = random_clifford(num_qubits, seed=rng)
            stab = StabilizerState(cliff)
            circ = synth_stabilizer_layers(stab, validate=True)
            stab_target = StabilizerState(circ)
            # Verify that the two stabilizers generate the same state
            self.assertTrue(stab.equiv(stab_target))
            # Verify that the two stabilizers produce the same probabilities
            self.assertEqual(stab.probabilities_dict(), stab_target.probabilities_dict())
            # Verify the layered structure
            self.assertEqual(circ.data[0].operation.name, "H2")
            self.assertEqual(circ.data[1].operation.name, "S1")
            self.assertEqual(circ.data[2].operation.name, "CZ")
            self.assertEqual(circ.data[3].operation.name, "H1")
            self.assertEqual(circ.data[4].operation.name, "Pauli")

    @combine(num_qubits=[4, 5, 6, 7])
    def test_decompose_lnn_depth(self, num_qubits):
        """Test stabilizer state decomposition for linear-nearest-neighbour (LNN) connectivity."""
        rng = np.random.default_rng(1234)
        samples = 10
        for _ in range(samples):
            cliff = random_clifford(num_qubits, seed=rng)
            stab = StabilizerState(cliff)
            circ = synth_stabilizer_depth_lnn(stab)
            # Check that the stabilizer state circuit 2-qubit depth equals 2*n+2
            depth2q = (circ.decompose()).depth(
                filter_function=lambda x: x.operation.num_qubits == 2
            )
            self.assertTrue(depth2q == 2 * num_qubits + 2)
            # Check that the stabilizer state circuit has linear nearest neighbour connectivity
            self.assertTrue(check_lnn_connectivity(circ.decompose()))
            stab_target = StabilizerState(circ)
            # Verify that the two stabilizers generate the same state
            self.assertTrue(stab.equiv(stab_target))
            # Verify that the two stabilizers produce the same probabilities
            self.assertEqual(stab.probabilities_dict(), stab_target.probabilities_dict())

    @combine(num_qubits=[4, 5], method_lnn=[True, False])
    def test_reduced_inverse_clifford(self, num_qubits, method_lnn):
        """Test that one can use this stabilizer state synthesis method to calculate an inverse Clifford
        that preserves the ground state |0...0>, with a reduced circuit depth.
        This is useful for multi-qubit Randomized Benchmarking."""
        rng = np.random.default_rng(5678)
        samples = 5
        for _ in range(samples):
            cliff = random_clifford(num_qubits, seed=rng)
            circ_orig = cliff.to_circuit()
            stab = StabilizerState(cliff)
            # Calculate the reduced-depth inverse Clifford
            if method_lnn:
                circ_inv = synth_stabilizer_depth_lnn(stab).inverse()
            else:
                circ_inv = synth_stabilizer_layers(stab, validate=True).inverse()
            circ = circ_orig.compose(circ_inv)
            stab = StabilizerState(circ)
            # Verify that we get back the ground state |0...0> with probability 1
            target_probs = {"0" * num_qubits: 1}
            self.assertEqual(stab.probabilities_dict(), target_probs)


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