from generator import *
from base_optimizer.optimizer_interface import *


def exact_assembly_time(pcb_data, component_data):
    feeder_data = pd.DataFrame(columns=['slot', 'part'])
    component_result, cycle_result, feeder_slot_result = feeder_priority_assignment(component_data, pcb_data,
                                                                                    feeder_data, hinter=False)
    placement_result, head_sequence_result = place_allocate_sequence_route_generation(component_data, pcb_data,
                                                                                      component_result, cycle_result,
                                                                                      feeder_slot_result, hinter=False)
    opt_res = OptResult(component_result, cycle_result, feeder_slot_result, placement_result, head_sequence_result)
    info = placement_info_evaluation(component_data, pcb_data, opt_res)
    # return info.metric()
    return info.total_time


def error_info(pred_val, real_val, type='train'):
    absolute_error = np.array([])
    for idx, (t1, t2) in enumerate(np.nditer([pred_val, real_val])):
        absolute_error = np.append(absolute_error, abs(t1 - t2) / (t2 + 1e-10) * 100)
        if absolute_error[-1] > 10:
            print(f'\033[0;31;31midx: {idx + 1: d}, net: {t1: .3f},  real: {t2: .3f}, '
                  f'gap: {absolute_error[-1]: .3f}\033[0m')

    print('')
    print(f'mean absolute prediction error for {type} data : {np.average(absolute_error): .2f}% ')
    print(f'maximum absolute prediction error for {type} data : {np.max(absolute_error): .2f}% ')


def converter(pcb_data, component_data, assignment):
    cp_items = defaultdict(list)

    board_width, board_height = pcb_data['x'].max() - pcb_data['x'].min(), pcb_data['y'].max() - pcb_data['y'].min()

    _, partial_component_data = convert_line_assigment(None, component_data, assignment)
    for machine_index in range(len(assignment)):
        cp_item_index = 0
        cp_points, cp_nozzle = defaultdict(int), defaultdict(str)
        for _, data in partial_component_data[machine_index].iterrows():
            feeder_limit, total_points = data.fdn, data.points
            surplus_points = total_points % feeder_limit
            for _ in range(feeder_limit):
                div_points = math.floor(total_points / feeder_limit)
                if surplus_points:
                    div_points += 1
                    surplus_points -= 1

                cp_points[cp_item_index], cp_nozzle[cp_item_index] = div_points, data.nz
                cp_item_index += 1

        cp_items[machine_index] = [cp_points, cp_nozzle, board_width, board_height]

    return cp_items


class Net(torch.nn.Module):
    def __init__(self, input_size, hidden_size=1000, output_size=1):
        super(Net, self).__init__()
        self.fc1 = torch.nn.Linear(input_size, hidden_size)
        self.relu = torch.nn.ReLU()  # 激活函数
        self.fc2 = torch.nn.Linear(hidden_size, hidden_size)
        # self.relu1 = torch.nn.ReLU()  # 激活函数
        self.fc3 = torch.nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x = self.fc1(x)
        # x = self.relu(x)
        x = self.fc2(x)
        x = self.relu(x)
        x = self.fc3(x)
        return x


class LSTMNet(torch.nn.Module):
    def __init__(self, input_size, hidden_size=256, output_size=1, num_layers=1):
        super(LSTMNet, self).__init__()

        self.lstm = torch.nn.LSTM(input_size, hidden_size, num_layers)
        self.fc = torch.nn.Linear(hidden_size, output_size)

    def forward(self, x):
        x, _ = self.lstm(x)  # x is input with size (seq_len, batch_size, input_size)
        x = self.fc(x)
        return x[-1, :, ]


class Estimator:
    def __init__(self):
        self.data_mgr = DataMgr()

    @staticmethod
    def training(self, params):
        pass

    @staticmethod
    def testing(self, params):
        pass

    @staticmethod
    def predict(self, cp_points, cp_nozzle, board_width=None, board_height=None):
        pass


class NeuralEstimator(Estimator):
    def __init__(self):
        super().__init__()
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

        self.net = Net(input_size=self.data_mgr.get_feature(), output_size=1).to(self.device)
        self.net_file = 'model/net_model.pth'
        try:
            self.net.load_state_dict(torch.load(self.net_file, map_location=self.device))
        except:
            warnings.warn('the parameters of neural net model load failed', UserWarning)

    def init_weights(self):
        for m in self.net.modules():
            if isinstance(m, torch.nn.Linear):
                torch.nn.init.xavier_uniform_(m.weight)
                torch.nn.init.zeros_(m.bias)

    def training(self, params):
        self.init_weights()         # 初始化参数

        data = data_mgr.loader('opt/' + params.train_file)
        x_train = np.array(data_mgr.neural_encode(data[0][::data_mgr.get_update_round()]))
        y_train = np.array(data[1][::data_mgr.get_update_round()])

        x_train = torch.from_numpy(x_train.reshape((-1, np.shape(x_train)[1]))).float().to(self.device)
        y_train = torch.from_numpy(y_train.reshape((-1, 1))).float().to(self.device)

        optimizer = torch.optim.Adam(self.net.parameters(), lr=params.lr)
        # scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5000, gamma=0.1)

        loss_func = torch.nn.MSELoss()

        for epoch in range(params.num_epochs):
            pred = self.net(x_train)
            loss = loss_func(pred, y_train)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            # scheduler.step()
            if epoch % 100 == 0:
                print('Epoch:  ', epoch, ', Loss: ', loss.item())
                if loss.item() < 1e-4:
                    break

        net_predict = self.net(x_train).view(-1)
        pred_time, real_time = net_predict.cpu().detach().numpy(), y_train.view(-1).cpu().detach().numpy()
        error_info(pred_time, real_time)

        if params.save:
            if not os.path.exists('model'):
                os.mkdir('model')
            torch.save(self.net.state_dict(), self.net_file)

    def testing(self, params):
        data = data_mgr.loader('opt/' + params.test_file)

        x_test, y_test = np.array(data_mgr.neural_encode(data[0])), np.array(data[1])
        x_test = torch.from_numpy(x_test.reshape((-1, np.shape(x_test)[1]))).float().to(self.device)

        self.net.eval()
        with torch.no_grad():
            pred_time = self.net(x_test).view(-1).cpu().detach().numpy()
            error_info(pred_time, y_test.reshape(-1), 'test')

    def predict(self, cp_points, cp_nozzle, board_width=None, board_height=None):
        assert board_width is not None and board_height is not None
        encoding = np.array(self.data_mgr.encode(cp_points, cp_nozzle, board_width, board_height))
        encoding = torch.from_numpy(encoding.reshape((-1, np.shape(encoding)[0]))).float().to(self.device)
        return self.net(encoding)[0, 0].item()


class HeuristicEstimator(Estimator):
    def __init__(self):
        super().__init__()

        self.lr = LinearRegression()
        self.pickle_file = 'model/heuristic_lr_model.pkl'
        if os.path.exists(self.pickle_file):
            with open(self.pickle_file, 'rb') as f:
                self.lr = pickle.load(f)
        else:
            warnings.warn('the parameters of heuristic lr model load failed', UserWarning)

    def training(self, params):
        data = data_mgr.loader('opt/' + params.train_file)
        x_fit = [self.heuristic_genetic(cp_points, cp_nozzle) for cp_points, cp_nozzle, _, _ in data[0]]
        y_fit = np.array([data[1]]).T
        self.lr.fit(x_fit, y_fit)

        if params.save:
            if not os.path.exists('model'):
                os.mkdir('model')
            with open(self.pickle_file, 'wb') as f:
                pickle.dump(self.lr, f)

        y_predict = self.lr.predict(x_fit)
        error_info(y_fit, y_predict)

    def testing(self, params):
        data = data_mgr.loader('opt/' + params.test_file)
        x_fit = [self.heuristic_genetic(cp_points, cp_nozzle) for cp_points, cp_nozzle, _, _ in data[0]]
        y_fit = np.array([data[1]]).T

        y_predict = self.lr.predict(x_fit)
        error_info(y_fit, y_predict, 'test')

    def predict(self, cp_points, cp_nozzle, board_width=None, board_height=None):
        return self.lr.predict(np.array(self.heuristic_genetic(cp_points, cp_nozzle)).reshape(1, -1))

    def heuristic_genetic(self, cp_points, cp_nozzle):
        nozzle_points, nozzle_component_points = defaultdict(int), defaultdict(list)
        for idx, nozzle in cp_nozzle.items():
            if cp_points[idx] == 0:
                continue
            nozzle_points[nozzle] += cp_points[idx]

            nozzle_component_points[cp_nozzle[idx]] = [0] * len(cp_points)

        for idx, (part_index, points) in enumerate(cp_points.items()):
            nozzle_component_points[cp_nozzle[part_index]][idx] = points

        nl = sum(cp_points.values())  # num of placement points
        ul = math.ceil(len(nozzle_points) * 1.0 / max_head_index) - 1  # num of nozzle set

        # assignments of nozzles to heads
        wl = 0  # num of workload
        total_heads = (1 + ul) * max_head_index - len(nozzle_points)
        nozzle_heads = defaultdict(int)
        for nozzle in nozzle_points.keys():
            if nozzle_points[nozzle] == 0:
                continue
            nozzle_heads[nozzle] = math.floor(nozzle_points[nozzle] * 1.0 / nl * total_heads)
            nozzle_heads[nozzle] += 1

        total_heads = (1 + ul) * max_head_index
        for heads in nozzle_heads.values():
            total_heads -= heads

        while True:
            nozzle = max(nozzle_heads, key=lambda x: nozzle_points[x] / nozzle_heads[x])
            if total_heads == 0:
                break
            nozzle_heads[nozzle] += 1
            total_heads -= 1

        # averagely assign placements to heads
        heads_placement = []
        for nozzle in nozzle_heads.keys():
            points = math.floor(nozzle_points[nozzle] / nozzle_heads[nozzle])

            heads_placement += [[nozzle, points] for _ in range(nozzle_heads[nozzle])]
            nozzle_points[nozzle] -= (nozzle_heads[nozzle] * points)
            for idx in range(len(heads_placement) - 1, -1, -1):
                if nozzle_points[nozzle] <= 0:
                    break
                nozzle_points[nozzle] -= 1
                heads_placement[idx][1] += 1
        heads_placement = sorted(heads_placement, key=lambda x: x[1], reverse=True)
        # every max_head_index heads in the non-decreasing order are grouped together as nozzle set
        for idx in range(len(heads_placement) // max_head_index):
            wl += heads_placement[idx][1]

        # the number of pick-up operations
        # (under the assumption of the number of feeder available for each comp. type is equal 1)
        pl = 0
        heads_placement_points = [0 for _ in range(max_head_index)]
        while True:
            head_assign_point = []
            for head in range(max_head_index):
                if heads_placement_points[head] != 0 or heads_placement[head] == 0:
                    continue

                nozzle, points = heads_placement[head]
                max_comp_index = np.argmax(nozzle_component_points[nozzle])

                heads_placement_points[head] = min(points, nozzle_component_points[nozzle][max_comp_index])
                nozzle_component_points[nozzle][max_comp_index] -= heads_placement_points[head]

                head_assign_point.append(heads_placement_points[head])

            min_points_list = list(filter(lambda x: x > 0, heads_placement_points))
            if len(min_points_list) == 0 or len(head_assign_point) == 0:
                break

            pl += max(head_assign_point)

            for head in range(max_head_index):
                heads_placement[head][1] -= min(min_points_list)
                heads_placement_points[head] -= min(min_points_list)

        return [nl, wl, ul]


class ReconfigEstimator(Estimator):
    def __init__(self):
        super().__init__()

        self.lr = LinearRegression()
        self.pickle_file = 'model/reconfig_model.pkl'
        if os.path.exists(self.pickle_file):
            with open(self.pickle_file, 'rb') as f:
                self.lr = pickle.load(f)
        else:
            warnings.warn('the parameters of reconfig model load failed', UserWarning)

    def training(self, params):
        data = data_mgr.loader('opt/' + params.train_file)
        x_fit = [self.heuristic_reconfig(cp_points, cp_nozzle) for cp_points, cp_nozzle, _, _ in data[0]]
        y_fit = np.array([data[1]]).T
        self.lr.fit(x_fit, y_fit)

        if params.save:
            if not os.path.exists('model'):
                os.mkdir('model')
            with open(self.pickle_file, 'wb') as f:
                pickle.dump(self.lr, f)

        y_predict = self.lr.predict(x_fit)
        error_info(y_fit, y_predict)

    def testing(self, params):
        data = data_mgr.loader('opt/' + params.test_file)
        x_fit = [self.heuristic_reconfig(cp_points, cp_nozzle) for cp_points, cp_nozzle, _, _ in data[0]]
        y_fit = np.array([data[1]]).T

        y_predict = self.lr.predict(x_fit)
        error_info(y_fit, y_predict, 'test')

    def predict(self, cp_points, cp_nozzle, board_width=None, board_height=None):
        return self.lr.predict(np.array(self.heuristic_reconfig(cp_points, cp_nozzle)).reshape(1, -1))[0, 0]

    def heuristic_reconfig(self, cp_points, cp_nozzle):
        task_block_number, total_point_number = 0, sum(cp_points.values())
        if total_point_number == 0:
            return [total_point_number, task_block_number]
        nozzle_points, nozzle_heads = defaultdict(int), defaultdict(int)

        for part, points in cp_points.items():
            nozzle_points[cp_nozzle[part]] += points
            nozzle_heads[cp_nozzle[part]] = 1
        remaining_head = max_head_index - len(nozzle_heads)

        nozzle_fraction = []
        for nozzle, points in nozzle_points.items():
            val = remaining_head * points / total_point_number
            nozzle_heads[nozzle] += math.floor(val)
            nozzle_fraction.append([nozzle, val - math.floor(val)])

        remaining_head = max_head_index - sum(nozzle_heads.values())
        sorted(nozzle_fraction, key=lambda x: x[1])
        nozzle_fraction_index = 0
        while remaining_head > 0:
            nozzle_heads[nozzle_fraction[nozzle_fraction_index][0]] += 1
            remaining_head -= 1

        for nozzle, heads_number in nozzle_heads.items():
            task_block_number = max(task_block_number, math.ceil(nozzle_points[nozzle] / heads_number))

        return [total_point_number, task_block_number]


class SVREstimator(Estimator):
    def __init__(self):
        super().__init__()

        # === symbiotic organism search parameter ===
        # population of meta heuristic: 20
        # number of iteration: 100
        self.population_size = 20
        self.num_iteration = 100
        self.w_quart = 1.5

        # === support vector regression parameters ===
        self.kernel_func = "rbf"
        self.C_range = [0.1, 10]
        self.gamma_range = [0.01, 0.5]
        self.epsilon_range = [0.01, 0.1]
        self.benefit_factor = [1, 2]

        # number of folds: 5
        self.num_folds = 5
        self.svr_list = [SVR() for _ in range(self.num_folds + 1)]

        for i in range(self.num_folds + 1):
            pickle_file = 'model/svr' + str(i + 1) + '_model.pkl'
            if not os.path.exists(pickle_file):
                continue
            with open(pickle_file, 'rb') as f:
                self.svr_list[i] = pickle.load(f)

        self.pbar = tqdm(total=self.num_iteration * self.num_folds * self.population_size)
        self.pbar.set_description('svr training process')

    def training(self, params):
        data = data_mgr.loader('opt/' + params.train_file)
        Q1, Q3 = np.percentile(np.array(data[1]), 25), np.percentile(np.array(data[1]), 75)
        indices = [i for i in range(len(data[1])) if Q1 - self.w_quart * (Q3 - Q1) <= data[1][i] <= Q3 + self.w_quart * (Q3 - Q1)]
        data[0], data[1] = [data[0][i] for i in indices], [data[1][i] for i in indices]

        self.svr_list = []
        division = len(data[0]) // self.num_folds

        for cnt in range(self.num_folds):
            x_train, y_train = data[0], data[1]
            x_train = [[sum(x_train[i][0].values()), x_train[i][2], x_train[i][3]] for i in range(len(data[0])) if
                       not cnt * division <= i < (cnt + 1) * division]
            y_train = [y_train[i] for i in range(len(data[0])) if not cnt * division <= i < (cnt + 1) * division]

            self.svr_list.append(self.sos_svr_training(x_train, y_train))

        final_input, final_output = [], []
        for cnt in range(self.num_folds):
            x_valid = [[sum(data[0][i][0].values()), data[0][i][2], data[0][i][3]] for i in range(len(data[0])) if
                       cnt * division <= i < (cnt + 1) * division]

            final_input.extend([[v] for v in self.svr_list[cnt].predict(x_valid)])
            final_output.extend(
                [data[1][i] for i in range(len(data[0])) if cnt * division <= i < (cnt + 1) * division])
        self.svr_list.append(self.sos_svr_training(final_input, final_output))

        if params.save:
            for i in range(self.num_folds + 1):
                pickle_file = 'model/svr' + str(i + 1) + '_model.pkl'
                with open(pickle_file, 'wb') as f:
                    pickle.dump(self.svr_list[i], f)

        predict_x = [[sum(data[0][i][0].values()), data[0][i][2], data[0][i][3]] for i in range(len(data[0]))]
        predict_y = []
        for cnt in range(self.num_folds):
            predict_y.extend(self.svr_list[cnt].predict(predict_x))

        input = [[np.average(predict_y[i:i + self.num_folds])] for i in range(len(predict_y) // self.num_folds)]
        predict_val = self.svr_list[-1].predict(input)

        error_info(data[1], predict_val)

    def sos_svr_training(self, x_train, y_train):
        population = []
        for _ in range(self.population_size):
            svr_param = [random.uniform(self.C_range[0], self.C_range[1]),
                         random.uniform(self.gamma_range[0], self.gamma_range[1]),
                         random.uniform(self.epsilon_range[0], self.epsilon_range[1])]
            population.append(SVR(kernel=self.kernel_func, C=svr_param[0], gamma=svr_param[1], epsilon=svr_param[2]))

        population_val = []
        for individual in population:
            population_val.append(self.svr_error(individual, x_train, y_train))

        for _ in range(self.num_iteration):
            best_svr = population[np.argmin(population_val)]
            for i in range(self.population_size):
                # === mutualism phase ===
                while True:
                    j = random.randint(0, self.population_size - 1)
                    if i != j:
                        break

                Mv_C, Mv_gamma, Mv_epsilon = (population[i].C + population[j].C) / 2, (
                            population[i].gamma + population[j].gamma) / 2, (
                                                         population[i].epsilon + population[j].epsilon) / 2

                for idx, svr in zip([i, j], [population[i], population[j]]):
                    new_C = svr.C + random.random() * (best_svr.C - Mv_C * random.choice(self.benefit_factor))
                    new_gamma = svr.gamma + random.random() * (
                                best_svr.gamma - Mv_gamma * random.choice(self.benefit_factor))
                    new_epsilon = svr.epsilon + random.random() * (
                                best_svr.epsilon - Mv_epsilon * random.choice(self.benefit_factor))

                    if new_C >= 0 and new_gamma >= 0 and new_epsilon >= 0:
                        new_svr = SVR(kernel=self.kernel_func, C=new_C, gamma=new_gamma, epsilon=new_epsilon)
                        new_svr_val = self.svr_error(new_svr, x_train, y_train)

                        if new_svr_val < population_val[idx]:
                            population[idx], population_val[idx] = new_svr, new_svr_val

                # === commensalism phase ===
                while True:
                    j = random.randint(0, self.population_size - 1)
                    if i != j:
                        break

                new_C = population[i].C + random.uniform(-1, 1) * (best_svr.C - population[j].C)
                new_gamma = population[i].gamma + random.uniform(-1, 1) * (best_svr.gamma - population[j].gamma)
                new_epsilon = population[i].epsilon + random.uniform(-1, 1) * (
                            best_svr.epsilon - population[j].epsilon)

                if new_C >= 0 and new_gamma >= 0 and new_epsilon >= 0:
                    new_svr = SVR(kernel=self.kernel_func, C=new_C, gamma=new_gamma, epsilon=new_epsilon)
                    new_svr_val = self.svr_error(new_svr, x_train, y_train)

                    if new_svr_val < population_val[j]:
                        population[j], population_val[j] = new_svr, new_svr_val

                # === parasitism phase ===
                while True:
                    j = random.randint(0, self.population_size - 1)
                    if i != j:
                        break
                new_svr = copy.deepcopy(population[j])
                idx = random.randint(0, 2)
                if idx == 0:
                    new_svr.C = random.uniform(self.C_range[0], self.C_range[1])
                elif idx == 1:
                    new_svr.gamma = random.uniform(self.gamma_range[0], self.gamma_range[1])
                else:
                    new_svr.epsilon = random.uniform(self.epsilon_range[0], self.epsilon_range[1])

                new_svr_val = self.svr_error(new_svr, x_train, y_train)
                if new_svr_val < population_val[j]:
                    population[j], population_val[j] = new_svr, new_svr_val
                self.pbar.update(1)

        return population[np.argmin(population_val)]

    def testing(self, params):
        data = data_mgr.loader('opt/' + params.test_file)

        predict_x = [[sum(data[0][i][0].values()), data[0][i][2], data[0][i][3]] for i in range(len(data[0]))]
        predict_y = []
        for cnt in range(self.num_folds):
            predict_y.extend(self.svr_list[cnt].predict(predict_x))

        input = [[np.average(predict_y[i:i + self.num_folds])] for i in range(len(predict_y) // self.num_folds)]
        predict_val = self.svr_list[-1].predict(input)
        error_info(data[1], predict_val, 'test')

    def predict(self, cp_points, cp_nozzle, board_width=None, board_height=None):
        pass

    def svr_error(self, svr, x_train, y_train):
        num_data = len(x_train)
        num_division = len(x_train) // self.num_folds

        pred_error = np.array([])
        for cnt in range(self.num_folds):
            x_fit = [x_train[i] for i in range(num_data) if not cnt * num_division <= i < (cnt + 1) * num_division]
            y_fit = [y_train[i] for i in range(num_data) if not cnt * num_division <= i < (cnt + 1) * num_division]
            svr.fit(x_fit, y_fit)

            x_valid = [x_train[i] for i in range(num_data) if cnt * num_division <= i < (cnt + 1) * num_division]
            y_valid = [y_train[i] for i in range(num_data) if cnt * num_division <= i < (cnt + 1) * num_division]

            for t1, t2 in np.nditer([y_valid, svr.predict(x_valid)]):
                pred_error = np.append(pred_error, abs(t1 - t2) / (t2 + 1e-10) * 100)

        return np.average(pred_error)


class MetricEstimator(Estimator):
    def __init__(self):
        super().__init__()

        self.lr = LinearRegression()
        self.pickle_file = 'model/metric_model.pkl'
        if os.path.exists(self.pickle_file):
            with open(self.pickle_file, 'rb') as f:
                self.lr = pickle.load(f)

    def training(self, params):
        x_fit, y_fit = data_mgr.metric('opt/' + params.train_file)
        self.lr.fit(x_fit, y_fit)
        y_predict = self.lr.predict(x_fit)
        error_info(y_fit, y_predict, 'train')
        print(self.lr.coef_)

    def testing(self, params):
        x_fit, y_fit = data_mgr.metric('opt/' + params.test_file)

        y_predict = self.lr.predict(x_fit)
        error_info(y_fit, y_predict, 'test')

    def predict(self, cp_points, cp_nozzle, board_width=None, board_height=None):
        pass


if __name__ == '__main__':
    warnings.simplefilter(action='ignore', category=FutureWarning)

    parser = argparse.ArgumentParser(description='network training implementation')
    # parser.add_argument('--train', default=True, type=bool, help='determine whether training the network')
    parser.add_argument('--save', default=False, type=bool,
                        help='determine whether saving the parameters of network, linear regression model, etc.')
    parser.add_argument('--overwrite', default=False, type=bool,
                        help='determine whether overwriting the training and testing data')
    parser.add_argument('--train_file', default='train_data.txt', type=str, help='training file path')
    parser.add_argument('--test_file', default='test_data.txt', type=str, help='testing file path')
    parser.add_argument('--num_epochs', default=8000, type=int, help='number of epochs for training process')
    parser.add_argument('--batch_size', default=2000, type=int, help='size of training batch')
    parser.add_argument('--lr', default=1e-5, type=float, help='learning rate for the network')
    parser.add_argument('--model', default='neural-network', help='method for assembly time estimation')
    parser.add_argument('--machine_optimizer', default='feeder-priority', type=str, help='optimizer for single machine')
    params = parser.parse_args()

    data_mgr = DataMgr()
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    if params.overwrite:
        file = {params.train_file: params.batch_size,
                params.test_file: params.batch_size // data_mgr.get_update_round() // 5}
        for file_name, file_batch_size in file.items():
            with open('opt/' + file_name, 'a') as f:
                for _ in range(int(file_batch_size)):

                    mode = file_name.split('.')[0].split('_')[0]
                    pcb_data, component_data = data_mgr.generator(mode)     # random generate a PCB data
                    # data_mgr.remover()                                # remove the last saved data
                    # data_mgr.saver('data/' + file_name, pcb_data)     # save new data

                    info = base_optimizer(1, pcb_data, component_data, pd.DataFrame(columns=['slot', 'part']), params,
                                          hinter=True)

                    data_mgr.recorder(f, info, pcb_data, component_data)
            f.close()

    estimator = NeuralEstimator()

    estimator.training(params)
    estimator.testing(params)


