from qiskit import QuantumCircuit
from qiskit.quantum_info import Clifford, random_clifford
from qiskit.synthesis.clifford import synth_clifford_greedy, synth_clifford_layers

clifford = random_clifford(5, seed=0)
print(clifford)

qc = synth_clifford_greedy(clifford)
qc.draw(output='mpl')

qc = synth_clifford_layers(clifford)
qc.draw(output='mpl')

qc.decompose().draw(output='mpl')

import numpy as np
from qiskit.synthesis.linear import synth_cnot_count_full_pmh, synth_cnot_depth_line_kms

mat = np.array([
        [1, 1, 0, 0, 0, 0],
        [1, 0, 0, 1, 1, 0],
        [0, 1, 0, 0, 1, 0],
        [1, 1, 1, 1, 1, 1],
        [1, 1, 0, 1, 1, 1],
        [0, 0, 1, 1, 1, 0],
    ])

qc = synth_cnot_count_full_pmh(mat)
qc.draw(output='mpl')

qc = synth_cnot_depth_line_kms(mat)
qc.draw(output='mpl')

from qiskit.synthesis.permutation import synth_permutation_depth_lnn_kms, synth_permutation_acg
pattern = [1, 2, 3, 4, 5, 6, 7, 0]

qc = synth_permutation_depth_lnn_kms(pattern)
qc.draw(output='mpl')

qc = synth_permutation_acg(pattern)
qc.draw(output='mpl')

from qiskit.circuit.library.generalized_gates import LinearFunction
from qiskit.transpiler.passes.optimization import CollectLinearFunctions
from qiskit.circuit.library import RealAmplitudes

ansatz = RealAmplitudes(4, reps=2, entanglement='full')
qc = ansatz.decompose()
qc.draw(output='mpl')

qc_extracted = CollectLinearFunctions()(qc)
qc_extracted.draw(output='mpl')

from qiskit.compiler import transpile
qc_transpiled = transpile(qc_extracted, optimization_level=1)
qc_transpiled.draw(output='mpl')

qc = QuantumCircuit(2)
qc.z(0)
qc.cx(0, 1)
qc.z(0)
qc.cx(0, 1)
qc.z(0)
qc.cx(0, 1)
qc.draw(output='mpl')

qc_extracted = CollectLinearFunctions(do_commutative_analysis=True)(qc)
qc_extracted.draw(output='mpl')

from functools import partial
from qiskit.transpiler.passes.optimization.collect_and_collapse import CollectAndCollapse, collect_using_filter_function, collapse_to_operation
from qiskit.circuit.library.generalized_gates import PermutationGate

# make use of "collect_using_filter_function", where filter_function collects SWAP gates. 
my_collect_function = partial(
            collect_using_filter_function,
            filter_function=lambda node: node.op.name=="swap" and getattr(node.op, "condition", None) is None,
            split_blocks=False,
            min_block_size=2,
        )

# Given a quantum circuit with SWAP gates, create a PermutationGate out of it.
def _swap_block_to_permutation_gate(qc: QuantumCircuit):
    nq = qc.num_qubits
    pattern = list(range(nq))
    for instruction in qc.data:
        q0 = qc.find_bit(instruction.qubits[0]).index
        q1 = qc.find_bit(instruction.qubits[1]).index
    pattern[q0], pattern[q1] = pattern[q1], pattern[q0]
    return PermutationGate(pattern)


# make use of "collapse_to_operation"
my_collapse_function = partial(collapse_to_operation, collapse_function=_swap_block_to_permutation_gate)

# And that's our pass
SwapCollector = CollectAndCollapse(collect_function=my_collect_function, collapse_function=my_collapse_function)

qc = QuantumCircuit(4)
qc.swap(0, 1)
qc.swap(1, 2)
qc.swap(2, 3)
qc.h(0)
qc.swap(0, 1)
qc.swap(1, 2)
qc.swap(2, 0)
qc.draw(output='mpl')

qc_extracted = SwapCollector(qc)
qc_extracted.draw(output='mpl')

qc = QuantumCircuit(6)

# Let's append a permutation gate and a linear function
qc.append(PermutationGate([1, 2, 3, 4, 0]), [1, 2, 3, 4, 5])
qc.h(range(6))
mat = np.array([
        [1, 1, 0, 0, 0, 0],
        [1, 0, 0, 1, 1, 0],
        [0, 1, 0, 0, 1, 0],
        [1, 1, 1, 1, 1, 1],
        [1, 1, 0, 1, 1, 1],
        [0, 0, 1, 1, 1, 0],
    ])
qc.append(LinearFunction(mat), [5, 4, 3, 2, 1, 0])
qc.draw(output='mpl')

from qiskit.compiler import transpile
qc_transpiled = transpile(qc, optimization_level=1)
qc_transpiled.draw(output='mpl')

from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig, HighLevelSynthesis
hls_config=HLSConfig(
    permutation=[("acg", {})],
)
qc_transpiled = transpile(qc, optimization_level=1, hls_config=hls_config)
qc_transpiled.draw(output='mpl')

from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig, HighLevelSynthesis
hls_config=HLSConfig(
    permutation=[("kms", {})],
)
qc_transpiled = transpile(qc, optimization_level=1, hls_config=hls_config)
qc_transpiled.draw(output='mpl')

from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig, HighLevelSynthesis
hls_config=HLSConfig(
    use_default_on_unspecified=False,
    permutation=[("kms", {})],
)
qc_transpiled = transpile(qc, optimization_level=1, hls_config=hls_config)
qc_transpiled.draw(output='mpl')

qc = QuantumCircuit(4)
qc.swap(0, 1)
qc.swap(1, 2)
qc.swap(2, 3)
qc.swap(1, 0)
qc.swap(2, 3)
qc.swap(3, 1)
qc.h(0)
qc.swap(0, 2)
qc.draw(output='mpl')

# Use our SwapCollector to extract SWAP blocks and to replace them by PermutationGates
qc_extracted = SwapCollector(qc)
qc_extracted.draw(output='mpl')

# Resynthesize using 'acg' method
hls_config = hls_config=HLSConfig(
    permutation=[("acg", {})],
)
qc_resynthesized = HighLevelSynthesis(hls_config=hls_config)(qc_extracted)
qc_resynthesized.draw(output='mpl')