import numpy as np
from qiskit.optimization.applications.ising.common import random_graph

np.random.seed(123)
num_nodes = 22
w = random_graph(num_nodes, edge_prob=0.8, weight_range=10)

from qiskit.optimization.applications.ising import vertex_cover
from qiskit.aqua.algorithms import QAOA

qubit_op, offset = vertex_cover.get_operator(w)

p = 10
qaoa = QAOA(qubit_op, p=p)

import qiskit
from typing import List

def create_qiskit_circuit(params: List[float]) -> qiskit.QuantumCircuit:
    assert len(params) == 2 * p, "invalid number of angles"
    return qaoa.var_form.construct_circuit(params)

import qusetta as qs
import cirq

def create_cirq_circuit(params: List[float]) -> cirq.Circuit:
    qiskit_circuit = create_qiskit_circuit(params)
    return qs.Qiskit.to_cirq(qiskit_circuit)

import quasar

def create_quasar_circuit(params: List[float]) -> quasar.Circuit:
    qiskit_circuit = create_qiskit_circuit(params)
    return qs.Qiskit.to_quasar(qiskit_circuit)

c = create_quasar_circuit([0.] * (2*p))
print("Number of qubits :", c.nqubit)
print("Number of gates  :", c.ngate)

def expectation_value(statevector: np.ndarray) -> float:
    # note that the second element (eg [1]) is the standard deviation
    return offset + qubit_op.evaluate_with_statevector(statevector)[0].real

from qiskit.optimization.applications.ising.common import sample_most_likely

def get_size_cover(statevector: np.ndarray) -> int:
    return int(sum(
        vertex_cover.get_graph_solution(sample_most_likely(statevector))
    ))

from typing import Callable
import time

f_type = Callable[[List[float]], np.ndarray]

def info_decorator(function: f_type) -> f_type:
    # `function` will be one of statevector_from_qiskit,
    # statevector_from_quasar, statevector_from_cirq, or statevector_from_vulcan.

    def f(params: List[float]) -> np.ndarray:
        print('='*40)
        print("Simulating with", function.__name__)
        print('-'*40)
        t0 = time.time()
        statevector = function(params)
        print("Time to completion : ", round(time.time() - t0, 2), "seconds")
        print("Expectation value  : ", round(expectation_value(statevector), 2))
        print("Size of cover      : ", get_size_cover(statevector))
        print('='*40, "\n")
        return statevector
        
    return f

@info_decorator
def statevector_from_qiskit(params: List[float]) -> np.ndarray:
    return qiskit.execute(
        create_qiskit_circuit(params),
        qiskit.BasicAer.get_backend('statevector_simulator')
    ).result().get_statevector()

@info_decorator
def statevector_from_cirq(params: List[float]) -> np.ndarray:
    return cirq.Simulator(dtype=np.complex128).simulate(
        create_cirq_circuit(params)
    ).final_state

@info_decorator
def statevector_from_quasar(params: List[float]) -> np.ndarray:
    return quasar.QuasarSimulatorBackend().run_statevector(
        circuit=create_quasar_circuit(params)
    )

import qcware
from qcware.circuits.quasar_backend import QuasarBackend
qcware.config.set_api_key('Put your API key here!')

@info_decorator
def statevector_from_vulcan(params: List[float]) -> np.ndarray:
    return QuasarBackend("vulcan/simulator").run_statevector(
        circuit=create_quasar_circuit(params)
    )

params = list(np.random.random(2*p) * np.pi)

qiskit_statevector = statevector_from_qiskit(params)
cirq_statevector   = statevector_from_cirq(params)
quasar_statevector = statevector_from_quasar(params)
vulcan_statevector = statev
ector_from_vulcan(params)

# check that probability vectors are the same
np.testing.assert_allclose(np.abs(qiskit_statevector)**2, np.abs(quasar_statevector)**2)
np.testing.assert_allclose(np.abs(cirq_statevector)**2,   np.abs(quasar_statevector)**2)
np.testing.assert_allclose(np.abs(vulcan_statevector)**2, np.abs(quasar_statevector)**2)