import numpy as np
import qiskit
from qiskit import QuantumCircuit
from qiskit import transpile
from qiskit.providers.fake_provider import FakeManhattanV2

from qiskit.circuit.library import *
from qiskit.synthesis import *
from qiskit.quantum_info import *
from qiskit.synthesis.linear import random_invertible_binary_matrix

cliff = random_clifford(50)

device = FakeManhattanV2()  # 65 qubits
num_qubits = device.num_qubits
coupling_map = device.coupling_map

qc = synth_clifford_greedy(cliff)

print(f"original circuit has: "
      f"2q-depth {qc.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

qc_trans = transpile(qc, basis_gates = ['rz', 'sx', 'cx'], coupling_map = edges, optimization_level = 3)

print(f"transpiled circuit has: "
      f"2q-depth {qc_trans.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc_trans.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc_trans.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

qc_lnn = synth_clifford_depth_lnn(cliff).decompose()

print(f"LNN circuit has: "
      f"2q-depth {qc_lnn.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc_lnn.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc_lnn.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

# layout is a line of connected qubits
layout = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 10,
          13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 26,
          37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 38,
          41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 54,
          64, 63, 62, 61, 60, 59, 58, 57, 56, 55]
num_qubits = qc_lnn.num_qubits
initial_layout = layout[0:num_qubits]

qc_lnn_trans = transpile(qc_lnn, coupling_map=coupling_map, basis_gates=['rz', 'sx', 'cx'], 
                         optimization_level=1, initial_layout=initial_layout)

print(f"transpiled LNN circuit has: "
      f"2q-depth {qc_lnn_trans.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc_lnn_trans.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc_lnn_trans.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

num_qubits = 50
# input: random invertible binary matrix
mat = random_invertible_binary_matrix(num_qubits)
qc = synth_cnot_depth_line_kms(mat)

print(f"Linear circuit has: "
      f"2q-depth {qc.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

num_qubits = 50
# input: random permutation
pattern = np.random.permutation(num_qubits)
qc = synth_permutation_depth_lnn_kms(pattern)

print(f"Permutation circuit has: "
      f"2q-depth {qc.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

num_qubits = 50
# input: an upper-diagonal matrix representing the CZ circuit. mat[i][j]=1 for i<j represents a CZ(i,j) gate
qc = synth_cz_depth_line_mr(mat)

print(f"CZ circuit has: "
      f"2q-depth {qc.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

cliff = random_clifford(5)
qc = synth_clifford_layers(cliff)

qc.draw('mpl')

cliff = random_clifford(50)
qc = synth_clifford_depth_lnn(cliff).decompose()

print(f"Clifford circuit has: "
      f"2q-depth {qc.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

cliff = random_clifford(5)
stab = StabilizerState(cliff)
qc = synth_stabilizer_layers(stab)

qc.draw('mpl')

cliff = random_clifford(50)
stab = StabilizerState(cliff)
qc = synth_stabilizer_depth_lnn(stab).decompose()

print(f"Stabilizer circuit has: "
      f"2q-depth {qc.depth(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"2q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 2)}, "
      f"1q-count {qc.size(filter_function=lambda x: x.operation.num_qubits == 1)}. ")

