import numpy as np
np.random.seed(999999)
target_distr = np.random.rand(2)
# We now convert the random vector into a valid probability vector
target_distr /= sum(target_distr)
print(target_distr)

from qiskit.circuit.library import EfficientSU2
ansatz = EfficientSU2(num_qubits=2, entanglement='linear')
print(ansatz.parameters)

from qiskit import QuantumCircuitc, ClassicalRegister, QuantumRegister

qc = QuantumCircuit(qr, cr)
qr = QuantumRegister(1, name="q")
cr = ClassicalRegister(1, name='c')

from qiskit import QuantumCircuitc, ClassicalRegister, QuantumRegister

def get_var_form(params):
    qr = QuantumRegister(1, name="q")
    cr = ClassicalRegister(1, name='c')
    ansatz = EfficientSU2(num_qubits=2, entanglement='linear')
    ansatz.u3(params[0], params[1], params[2], qr[0])
    ansatz.measure(qr, cr[0])
    return qc

from qiskit import Aer, transpile, assemble
backend = Aer.get_backend("qasm_simulator")
NUM_SHOTS = 10000

def get_probability_distribution(counts):
    output_distr = [v / NUM_SHOTS for v in counts.values()]
    if len(output_distr) == 1:
        output_distr.append(1 - output_distr[0])
    return output_distr

def objective_function(params):
    # Obtain a quantum circuit instance from the paramters
    qc = get_var_form(params)
    qc.draw()
    # Execute the quantum circuit to obtain the probability distribution associated with the current parameters
    t_qc = transpile(qc, backend)
    qobj = assemble(t_qc, shots=NUM_SHOTS)
    result = backend.run(qobj).result()
    # Obtain the counts for each measured state, and convert those counts into a probability vector
    output_distr = get_probability_distribution(result.get_counts(qc))
    # Calculate the cost as the distance between the output distribution and the target distribution
    cost = sum([np.abs(output_distr[i] - target_distr[i]) for i in range(2)])
    return cost

from qiskit.algorithms.optimizers import L_BFGS_B
import numpy as np

bfgs_optimizer = L_BFGS_B(maxiter=60)

# Create the initial parameters (noting that our single qubit variational form has 3 parameters)
params = np.random.rand(3)
ret = bfgs_optimizer.optimize(num_vars=3, objective_function=objective_function, initial_point=params)

# Obtain the output distribution using the final parameters
qc = get_var_form(ret[0])
t_qc = transpile(qc, backend)
qobj = assemble(t_qc, shots=NUM_SHOTS)
counts = backend.run(qobj).result().get_counts(qc)
output_distr = get_probability_distribution(counts)

print("Target Distribution:", target_distr)
print("Obtained Distribution:", output_distr)
print("Output Error (Manhattan Distance):", ret[1])
print("Parameters Found:", ret[0])

