import numpy as np
from utils import compare_solvers

class Initializer:

    def __init__(self, n, a, b):
        self.n = n
        self.a = a
        self.b = b

    def generate_nodes_and_weight_matrix(self):

        n = self.n
        a = self.a
        b = self.b
        
        np.random.seed(100*a + b)

        x = (np.random.rand(n) - 0.5) * 50
        y = (np.random.rand(n) - 0.5) * 50

        weight_matrix = np.zeros([n, n])
        for i in range(n):
            for j in range(i+1, n):
                weight_matrix[i, j] = (x[i] - x[j]) ** 2 + (y[i] - y[j]) ** 2
                weight_matrix[j, i] = weight_matrix[i, j]

        return x, y, weight_matrix

### Select the type of model to solve VRP
#    1: Constrained Quadratic Model - A new model released by D-Wave Systems capable of encoding Quadratically Constrained Quadratic Programs (QCQPs)
#    2: Binary Quadratic Model - A model that encodes Ising or QUBO problems
model = 'CQM'


### The time limit (in seconds) for the solvers to run on the `LeapHybridCQMSampler` backend
time_limit = 5


### Select solver
#    1: RAS (Route Activation Solver)
#    2: FQS (Full QUBO Solver)
#    3: GPS (Guillermo, Parfait, Saúl) (only using CQM)
#    4: DBSCANS (Density-Based Spatial Clustering of Applications with Noise - Solver)
#    5: SPS (Solution Partition Solver)
solver = 'ras'

# Number of iterations to get the average approximation ratio for a particular solver
# Warning! More iterations will take more time and resources to run
n_iter = 1

for n in range(10, 13):                                ### Here, (2, 6) could be replaced with the some other range of no. of locations you want.
    for instance in range(4):                         ### Here, (10) could be replaced with some other number of instcnces you want to generate for a particular no. of locations.
        initializer = Initializer(n, n, instance)
        xc, yc, cost = initializer.generate_nodes_and_weight_matrix()
        for m in range(1, n):
            comparison_table = compare_solvers(n-1, m, cost, xc, yc, n_iter=n_iter, time_limit=time_limit)
            print(f'\nn = {n}, m = {m}, instance = {instance}')
            print('Classical cost from best known solution:', comparison_table[0]['exact_min_cost'])
            for solver_id in comparison_table[1]:
                print(f'{solver_id}:', '\t', f'quantum cost = {comparison_table[1][solver_id]["avg_min_cost"]}',
                                       '\t', f'runtime = {comparison_table[1][solver_id]["avg_runtime"]}',
                                       '\t', f'number of variables = {comparison_table[1][solver_id]["num_vars"]}',
                                       '\t', f'approximation ratio = {comparison_table[1][solver_id]["approximation_ratio"]}'
                )

# for n in range(2, 6):                               ### Here, (2, 6) could be replaced with the some other range of no. of locations you want.
#     for instance in range(10):                      ### Here, (10) could be replaced with some other number of instcnces you want to generate for a particular no. of locations.
#         initializer = Initializer(n, n, instance)
#         xc, yc, cost = initializer.generate_nodes_and_weight_matrix()
#         for m in range(1, n):
#             comparison_table = compare_solvers(n-1, m, cost, xc, yc, n_iter=n_iter, time_limit=time_limit)
#             print(f'n = {n}, m = {m}, instance = {instance}')
#             print('Classical cost from best known solution:', comparison_table[0]['exact_min_cost'])
#             for solver_id in comparison_table[1]:
#                 print(f'{solver_id}:', '\t', f'quantum cost = {comparison_table[1][solver_id]["avg_min_cost"]}',
#                                     '\t', f'runtime = {comparison_table[1][solver_id]["avg_runtime"]}',
#                                     '\t', f'number of variables = {comparison_table[1][solver_id]["num_vars"]}',
#                                     '\t', f'approximation ratio = {comparison_table[1][solver_id]["approximation_ratio"]}'
#                 )