import numpy as np
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import ElasticNetCV


def enhanced_elastic_net_feature_selection(X, y, n_alphas=100, l1_ratios=[.1, .5, .7, .9, .95, .99, 1], max_iter=1e8):
    """
    Perform enhanced feature selection using ElasticNet with cross-validation.

    Parameters:
    - X: Features matrix
    - y: Target vector
    - n_alphas: Number of alpha values to test
    - l1_ratios: List of l1 ratios to test

    Returns:
    - selected_features: List of selected feature indices
    - feature_importances: Importance of each feature
    """

    # Fit ElasticNetCV model
    enet_cv = ElasticNetCV(l1_ratio=l1_ratios, n_alphas=n_alphas, cv=5)
    enet_cv.fit(X, y)

    # Get non-zero coefficients and their absolute values as feature importances
    coef_abs = np.abs(enet_cv.coef_)
    selected_features = np.where(enet_cv.coef_ != 0)[0]

    # Sort features based on importance
    sorted_indices = np.argsort(-coef_abs)
    feature_importances = coef_abs[sorted_indices]

    best_alpha = enet_cv.alpha_
    best_l1_ratio = enet_cv.l1_ratio_

    return selected_features.tolist(), sorted_indices.tolist(), feature_importances.tolist(), best_alpha, best_l1_ratio


import seaborn as sns

def visualize_feature_importances(features, importances, num_features=None):
    """
    Visualize feature importances with a more sophisticated and publication-quality style.

    Parameters:
    - features: List of feature names
    - importances: Corresponding feature importances
    - num_features: Number of top features to display (default is all)
    """

    if num_features is None:
        num_features = len(features)

    # Sort features and their importances
    sorted_idx = np.argsort(importances)[-num_features:][::-1]  # Add [::-1] to invert the order
    sorted_features = [features[i] for i in sorted_idx]
    sorted_importances = [importances[i] for i in sorted_idx]

    # Use a more sophisticated color palette
    sns.set_style("whitegrid")
    plt.figure(figsize=(10, 7), dpi=300)
    bars = plt.barh(sorted_features, sorted_importances, color=sns.color_palette("Blues_r", n_colors=num_features))
    plt.xlabel('Coefficient Magnitude', fontsize=14, fontname='Times New Roman')
    plt.ylabel('Feature', fontsize=14, fontname='Times New Roman')
    plt.title('Top {} Importance Features'.format(num_features), fontsize=16, fontname='Times New Roman')
    plt.gca().invert_yaxis()  # This may not be needed anymore because of [::-1]
    plt.tight_layout()  # Adjust layout to make room for labels

    # Optional: Add value labels to each bar for clarity
    for bar in bars:
        plt.text(bar.get_width(), bar.get_y() + bar.get_height() / 2,
                 '{:.2f}'.format(bar.get_width()),
                 va='center', ha='left', color='black', fontsize=12)

    plt.show()


# Assuming 'selected_feature_names' and 'feature_importances' are defined as per your previous execution context
visualize_feature_importances(selected_feature_names, feature_importances, num_features=40)


def BinaryInitialization(fobj, SearchAgents_no, dim, ub, lb, Data):
    """
    This function initializes the binary array X and calculates the fitness and
    Dynamic Awareness Probability (DAP) for each Search Agent.

    :param fobj: Objective function
    :param SearchAgents_no: Number of Search Agents
    :param dim: Dimension of the array X
    :param ub: Upper bound (not used in this function, but kept for consistency)
    :param lb: Lower bound (not used in this function, but kept for consistency)

    :return: Tuple containing the initialized array X and the calculated fitness array fit.
    """
    import numpy as np
    # Initialization of the array X and fitness array fit
    X = np.zeros((SearchAgents_no, dim), dtype=int)
    fit = np.zeros(SearchAgents_no)
    print(X.shape)

    # Initialization loop
    for i in range(SearchAgents_no):
        X[i, :] = np.random.rand(dim) > 0.2  # Creating a binary array
        fit[i] = fobj(X[i, :], Data)  # Calculating the fitness
    print(X.shape)
    print(fit.shape)

    # Sorting fitness and positions in descending order
    # fit = [1.2, 0.9, 1.5]  # replace with the actual values
    # X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])  # replace with the actual values
    for j in range(SearchAgents_no):
        for k in range(j, SearchAgents_no):
            if fit[j] < fit[k]:
                # swap fitness
                fit[j], fit[k] = fit[k], fit[j]

                # swap positions in X
                X[j, :], X[k, :] = X[k, :].copy(), X[j, :].copy()

    # Calculation of DAP vector
    AP_min = 0.1
    AP_max = 0.8
    DAP = [AP_min + (AP_max - AP_min) * (i / SearchAgents_no) for i in range(1, SearchAgents_no + 1)]

    return X, fit, DAP  # DAP is also returned as it is calculated in this function

from scipy.signal import chirp


def chaos(index, max_iter, Value):
    """
    This function generates a chaos sequence based on the provided index.

    :param index: Index representing the chaos map to be used.
    :param max_iter: Number of iterations (length of the chaos sequence).
    :param Value: A value used in the calculations of the chaos sequence.
    :return: A list representing the chaos sequence.
    """
    O = np.zeros(max_iter)
    x = np.zeros(max_iter + 1)
    x[0] = 0.7
    a = b = 0

    if index == 1:  # Chebyshev map
        for i in range(max_iter):
            x[i + 1] = np.cos(i * np.arccos(x[i]))
            O[i] = ((x[i] + 1) * Value) / 2

    elif index == 2:  # Circle map
        a = 0.5
        b = 0.2
        for i in range(max_iter):
            x[i + 1] = (x[i] + b - (a / (2 * np.pi)) * np.sin(2 * np.pi * x[i])) % 1
            O[i] = x[i] * Value

    elif index == 3:  # Gauss/mouse map
        for i in range(max_iter):
            x[i + 1] = 0 if x[i] == 0 else (1 / x[i]) % 1
            O[i] = x[i] * Value

    elif index == 4:  # Iterative map
        a = 0.7
        for i in range(max_iter):
            x[i + 1] = np.sin((a * np.pi) / x[i])
            O[i] = ((x[i] + 1) * Value) / 2

    elif index == 5:  # Logistic map
        a = 4
        for i in range(max_iter):
            x[i + 1] = a * x[i] * (1 - x[i])
            O[i] = x[i] * Value

    elif index == 6:  # Piecewise map
        P = 0.4
        for i in range(max_iter):
            if x[i] >= 0 and x[i] < P:
                x[i + 1] = x[i] / P
            elif x[i] >= P and x[i] < 0.5:
                x[i + 1] = (x[i] - P) / (0.5 - P)
            elif x[i] >= 0.5 and x[i] < 1 - P:
                x[i + 1] = (1 - P - x[i]) / (0.5 - P)
            elif x[i] >= 1 - P and x[i] < 1:
                x[i + 1] = (1 - x[i]) / P
            O[i] = x[i] * Value

    elif index == 7:  # Sine map
        for i in range(max_iter):
            x[i + 1] = np.sin(np.pi * x[i])
            O[i] = x[i] * Value

    elif index == 8:  # Singer map
        u = 1.07
        for i in range(max_iter):
            x[i + 1] = u * (7.86 * x[i] - 23.31 * (x[i] ** 2) + 28.75 * (x[i] ** 3) - 13.302875 * (x[i] ** 4))
            O[i] = x[i] * Value

    elif index == 9:  # Sinusoidal map
        for i in range(max_iter):
            x[i + 1] = 2.3 * x[i] ** 2 * np.sin(np.pi * x[i])
            O[i] = x[i] * Value

    elif index == 10:  # Tent map
        x[0] = 0.1
        for i in range(max_iter):
            if x[i] < 0.7:
                x[i + 1] = x[i] / 0.7
            else:
                x[i + 1] = (10 / 3) * (1 - x[i])
            O[i] = x[i] * Value

    elif index == 11:  # New map
        x[0] = 0.1
        for i in range(max_iter):
            if x[i] < 0.7:
                x[i + 1] = (x[i] / 0.7) * np.sin(np.pi * np.random.rand())
            else:
                x[i + 1] = ((10 / 3) * (1 - x[i])) * np.sin(np.pi * np.random.rand())
            O[i] = x[i] * Value

    if index == 12:  # chirp log map
        ch_t = np.arange(0, 2, 0.2)
        ch_y = chirp(ch_t, f0=10, f1=10, t1=400, method='logarithmic')
        for i in range(max_iter):
            x[i] = ch_y[i]
            O[i] = x[i] * Value

    elif index == 13:  # chirp quad map
        ch_t = np.arange(0, 2, 0.2)
        ch_y = chirp(ch_t, f0=0, f1=1, t1=1.8, method='quadratic')
        for i in range(max_iter):
            x[i] = ch_y[i]
            O[i] = x[i] * Value

    elif index == 14:  # New map
        x[0] = 0.1
        for i in range(max_iter):
            if x[i] < 0.8:
                x[i + 1] = (x[i] / 0.8) * np.sin(np.pi * np.random.rand())
            else:
                x[i + 1] = ((10 / 2) * (1 - x[i])) * np.sin(np.pi * np.random.rand())
            O[i] = x[i] * Value

    return O

# Representing the entire function as a string to display it
# return O.tolist()

def MySort(X, fitness, arr_size):
    """
    This function sorts the given 2D array X and 1D array fitness in descending order based on the values in fitness.

    :param X: 2D Numpy array to be sorted.
    :param fitness: 1D Numpy array based on which the sorting is to be performed.
    :param arr_size: Integer representing the size of the arrays (number of rows in X and number of elements in fitness).

    :return: Tuple containing the sorted X and fitness arrays.
    """
    for j in range(arr_size):
        for k in range(j, arr_size):
            if fitness[j] < fitness[k]:
                #print(type(fitness[j]), fitness[j])
                #print(type(fitness[k]), fitness[k])
                # Swap fitness
                fitness[j], fitness[k] = fitness[k], fitness[j]

                # Swap position because of swapping fitness
                X[j, :], X[k, :] = X[k, :].copy(), X[j, :].copy()

    RetX = X
    RetFit = fitness
    return RetX, RetFit
from sklearn.model_selection import KFold
from sklearn.neighbors import KNeighborsClassifier

def obj(solution, Data):
    # If no feature is selected, fitness is 0
    if np.sum(solution) == 0:
        return 0
    # global Data
    # This is the last column of the dataset
    # This column is exactly groups of the instance
    # We use this column to calculate our accuracy
    last_column_index = Data.columns[-1]  # Get the column name of the last column
    last_column_index = Data.columns.get_loc(last_column_index)
    groups = Data.iloc[:, last_column_index]

    # These are other columns
    Data_DELGROUPS = Data.iloc[:,:last_column_index]
    meas = Data_DELGROUPS.iloc[:, solution]

    # All of the data
    Data_All = np.column_stack((meas, groups))

    # Our "k" value for classification
    k = 3
    kf = KFold(n_splits=k)

    # Initialize accuracy and class values
    accuracy = 0
    class_labels = np.zeros(groups.shape)

    for train_index, test_index in kf.split(Data_All):
        # Split train and test data
        train_data, test_data = Data_All[train_index], Data_All[test_index]
        train_meas, train_groups = train_data[:, :-1], train_data[:, -1]
        test_meas, test_groups = test_data[:, :-1], test_data[:, -1]

        # Run the KNN classifier
        knn = KNeighborsClassifier(n_neighbors=8, metric='manhattan')
        knn.fit(train_meas, train_groups)
        class_labels[test_index] = knn.predict(test_meas)

        # Calculate the accuracy
        accuracy += np.sum(class_labels[test_index] == test_groups)

    # Calculate accuracy in percentages
    accuracy /= (k * len(class_labels))

    # Main fitness formula
    # It encourages solutions to select fewer features
    Fitness = accuracy + 0.2 * (1 - (np.sum(solution) / len(solution)))
    #Fitness = accuracy
    print(np.sum(solution))


    return Fitness

# Example usage:
# solution = [1, 0, 1, 0, 1]  # Replace with your binary feature selection
# Data is a NumPy array with your dataset
# fitness_score = fitness(solution, Data)
from sklearn.model_selection import GroupKFold
import numpy as np


def modified_obj(solution, Data):
    # If no feature is selected, fitness is 0
    if np.sum(solution) == 0:
        return 0

    # Convert 'name' column to unique integer labels
    Data['name_int'] = pd.factorize(Data['name'])[0]

    # Create new group labels
    max_name_int = np.max(Data['name_int'])

    # Generate unique integer labels for rows where result is 2
    unique_int_labels = np.arange(max_name_int + 1, max_name_int + 1 + sum(Data['result'] == 2))

    # Assign these unique labels to the 'group' column for rows where result is 2
    Data.loc[Data['result'] == 2, 'group'] = unique_int_labels

    # For other rows, simply use the 'name_int' column as the group
    Data.loc[Data['result'] != 2, 'group'] = Data['name_int']

    # Extracting groups
    groups = Data['group']
    labels = Data['result']

    # Using solution to extract features
    features = Data.iloc[:, solution]

    # Our "k" value for classification
    k = 3
    gkf = GroupKFold(n_splits=k)

    # Initialize accuracy and class values
    accuracy = 0
    class_labels = np.zeros(labels.shape)

    for train_index, test_index in gkf.split(features, labels, groups):
        # Split train and test data
        train_data, test_data = features.iloc[train_index], features.iloc[test_index]
        train_labels, test_labels = labels.iloc[train_index], labels.iloc[test_index]

        # Run the KNN classifier
        knn = KNeighborsClassifier(n_neighbors=8, metric='manhattan')
        knn.fit(train_data, train_labels)
        class_labels[test_index] = knn.predict(test_data)

        # Calculate the accuracy
        accuracy += np.sum(class_labels[test_index] == test_labels)

    # Calculate accuracy in percentages
    accuracy /= len(class_labels)

    # Main fitness formula
    Fitness = accuracy + 0.1 * (1 - (np.sum(solution) / len(solution)))
    print(np.sum(solution))
    return Fitness


from sklearn.model_selection import GroupKFold


def Ecsa_obj(solution, Data):
    # Check the length of the solution in each iteration
    Solution_len = np.sum(solution)

    if np.sum(solution) == 0:
        # No features are selected
        ECSA_Fit9 = 0
        ECSA_Fit8 = 0
        ECSA_Fit7 = 0
        ECSA_Acc = 0
        return ECSA_Fit9, ECSA_Fit8, ECSA_Fit7, ECSA_Acc, Solution_len

    # Convert 'name' column to unique integer labels
    Data['name_int'] = pd.factorize(Data['name'])[0]

    # Create new group labels
    max_name_int = np.max(Data['name_int'])
    unique_int_labels = np.arange(max_name_int + 1, max_name_int + 1 + sum(Data['result'] == 2))
    Data.loc[Data['result'] == 2, 'group'] = unique_int_labels
    Data.loc[Data['result'] != 2, 'group'] = Data['name_int']

    # Extracting groups
    groups = Data['group']
    labels = Data['result']

    # Using solution to extract features
    features = Data.iloc[:, solution]

    k = 5
    Alp9 = 0.9
    Alp8 = 0.8
    Alp7 = 0.7

    gkf = GroupKFold(n_splits=k)
    Accuracy = 0
    class_predictions = []

    for train_idx, test_idx in gkf.split(features, labels, groups):
        X_train, y_train = features.iloc[train_idx], labels.iloc[train_idx]
        X_test, y_test = features.iloc[test_idx], labels.iloc[test_idx]

        knn = KNeighborsClassifier(n_neighbors=8, metric='cityblock')
        knn.fit(X_train, y_train)
        class_predictions.extend(knn.predict(X_test))

        Accuracy += np.sum(knn.predict(X_test) == y_test)

    Accuracy /= len(labels)
    ECSA_Fit9 = Alp9 * (1 - Accuracy) + ((1 - Alp9) * (np.sum(solution) / len(solution)))
    ECSA_Fit8 = Alp8 * (1 - Accuracy) + ((1 - Alp8) * (np.sum(solution) / len(solution)))
    ECSA_Fit7 = Alp7 * (1 - Accuracy) + ((1 - Alp7) * (np.sum(solution) / len(solution)))
    ECSA_Acc = Accuracy

    return ECSA_Fit9, ECSA_Fit8, ECSA_Fit7, ECSA_Acc, Solution_len


def BCCSA(x, ft, N, tmax, l, u, pd, fobj, AP, fl, ChaosVec, DAP, Data):
    # global DAP
    np.set_printoptions(precision=15)
    # xn is a vector for saving positions
    # it will use for position updating
    xn = x.copy()
    mem = x.copy()
    fit_mem = ft.copy()

    convergence = []  # Add array to record optimal fitness for each iteration

    num_rows, num_cols = x.shape
    xnew = np.zeros((num_rows * 2, num_cols))
    length = len(ft)
    ft = np.zeros(length * 2)
    best_solution = None
    ffit = np.zeros(tmax)
    BS = np.zeros(tmax)
    # Main loop, crows will search the search space tmax times
    for t in range(tmax):
        print('    Iter:', t + 1)
        rvalue = ChaosVec[t]
        print(rvalue)
        idx = 0

        # Crow neighbors generation loop
        for i in range(10):
            for k in range(i + 1, i + 11):
                # if DAP[i] <= 0.2:
                if AP <= 0.2:
                    # Local search part
                    # xnew[idx, :] = (x[i, :] + fl * rvalue * (mem[k, :] ^ x[i, :])) > 0.5
                    # xnew[idx, :] = (x[i, :] + fl * rvalue * (mem[k, :] - x[i, :])) > 0.5
                    xnew[idx, :] = (x[i, :].astype(int) + fl * rvalue * (
                                mem[k, :].astype(int) - x[i, :].astype(int))) > 0.5

                    v1 = 1 / (1 + np.exp(-10 * (xnew[i, :] - 0.9)))
                    v1 = np.tanh(v1)
                    v1[v1 < np.random.rand()] = 0
                    v1[v1 >= np.random.rand()] = 1
                    xnew[idx, :] = (xnew[idx, :] + v1) >= 1
                    idx += 1

                else:
                    # Global search part
                    Chaos_Values = chaos(10, pd, 1)
                    # print(Chaos_Values)
                    for j in range(pd):
                        # if our Chaos_value is bigger than some value this will be selected.
                        xnew[idx, j] = Chaos_Values[j] > 0.3  # Generation of a new position for crow i (state 2)

                        v1 = 1 / (1 + np.exp(-10 * (xnew[idx, j] - 0.9)))
                        v1 = np.tanh(v1)
                        if v1 < np.random.rand():
                            v1 = 0
                        else:
                            v1 = 1
                        xnew[idx, j] = (xnew[idx, j] + v1) >= 1
                    idx += 1

        xn = xnew.copy()

        # Calculate fitness for each crow
        for ii in range(idx):
            if ii >= len(xn):
                raise IndexError("Index is outside the valid range of the array")

            else:
                ft[ii] = fobj(xn[ii, :], Data)

        # Sort fitnesses and positions using the 'MySort' function
        xn, ft = MySort(xnew, ft, len(xn))

        # Update position and memory
        for i in range(50):
            x[i, :] = xn[i, :]  # Update position
            if ft[i] >= fit_mem[i]:
                mem[i, :] = xn[i, :]  # Update memory
                fit_mem[i] = ft[i]

        ffit[t] = np.max(fit_mem)
        maxidx = np.argmax(fit_mem)
        BS[t] = np.sum(xn[maxidx, :]) / pd

        # Call ECSA calculator
        ffit_ecsa9, _, _, Acc_ecsa, _ = Ecsa_obj(xn[maxidx, :], Data)

        # ngbest1 = np.where(fit_mem == min(fit_mem))[0]
        # g_best1 = mem[ngbest1[0], :]
        # Destination_position1 = g_best1
        # print(Destination_position1)
        convergence.append(np.max(ft))

    # Calculate the best value of fitness in this run of the algorithm
    Destination_fitness = max(fit_mem)
    ngbest = np.where(fit_mem == min(fit_mem))[0]
    g_best = mem[ngbest[0], :]
    Destination_position = g_best

    Convergence_curve = ffit
    Sum_of_fitness = np.sum(Convergence_curve)
    Mean_val = Sum_of_fitness / tmax

    Sum_of_ffit = np.sum(ffit)
    mean_based_on_bestes = Sum_of_ffit / tmax

    # Calculate SD (Standard Deviation)
    Mean_of_ffit = np.mean(ffit)
    bs_minus_mean = ffit - Mean_of_ffit
    bs_minus_mean_2 = bs_minus_mean ** 2

    sum_of_bs_minus_mean_2 = np.sum(bs_minus_mean_2)
    under_sqrt = sum_of_bs_minus_mean_2 / tmax
    SD = np.sqrt(under_sqrt)

    Sum_of_BS = np.sum(BS)
    ASS = Sum_of_BS / tmax

    Best = max(ffit)
    Worst = min(ffit)
    Mean = mean_based_on_bestes
    ECSA_fit_Mean9 = np.mean(ffit_ecsa9)
    ECSA_ACC_Mean = np.mean(Acc_ecsa)


    plt.plot(convergence)
    plt.xlabel('Iteration')
    plt.ylabel('Fitness')
    plt.title('Convergence Curve')
    plt.show()

    return Worst, Best, Mean, SD, ASS, ECSA_fit_Mean9, ECSA_ACC_Mean, Destination_position, Destination_fitness, convergence


import pandas as pd
import matplotlib.pyplot as plt
import numpy as np


# ================================================
def main():
    # Parameters
    MAIN_RUN = 1
    # In this part we choose how many crows do we need to search in search space
    SearchAgents_no = 10  # Number of search agents
    # Our crows search the search space in Max_iter times.
    Max_iter = 30  # Maximum number of iterations

    # ================================================
    # Load data
    train_data = pd.read_csv(r'E:\train_data.csv')
    # Define features (X) and label (y)
    X_train = train_data.iloc[:, :-2]  # All columns except the last one are features
    y_train = train_data.iloc[:, -2]  # The last column is the label
    # Extracting features and target
    X = data.drop(columns=['result']).values
    y = data['result'].values

    # Using the enhanced_elastic_net_feature_selection function for feature selection
    selected_features, sorted_indices, feature_importances, best_alpha, best_l1_ratio = enhanced_elastic_net_feature_selection(
        X, y)

    # Getting the feature names based on their indices
    selected_feature_names = data.columns[sorted_indices]
    # Creating the 0-1 selection vector
    selection_vector = np.zeros(X_train.shape[1], dtype=int)
    selection_vector[selected_features] = 1

    # Output the results
    selected_features, selected_feature_names.tolist(), feature_importances, best_alpha, best_l1_ratio, selection_vector.tolist()
    # selected_features, selected_feature_names.tolist(), feature_importances, best_alpha, best_l1_ratio
    global Data
    Data = pd.read_csv(r'E:\train_data.csv')
    # Data = pd.read_csv(r'E:\Datasets\clean1.csv')
    # Each dataset has some columns.
    # One of them is the class of group for classification,
    # others are our features.
    dim = Data.shape[1] - 2


    # This is the name of our fitness function
    # fobj = obj
    fobj = modified_obj
    # Create a vector to save the value of chaos maps
    ChaosVec = np.zeros((11, Max_iter))

    # Calculate chaos vectors
    # It is 11:11 because we need chaos 11 only.
    # for i in range(10,11):
    # ChaosVec[i, :] = chaos(i, Max_iter, 1)
    # Calculate chaos vectors
    # Here, we only compute for index 11 as in the original MATLAB code
    ChaosVec[7, :] = chaos(11, Max_iter, 1)
    # Algorithm parameters
    AP = 0.1
    fl = 2
    # This is the main loop of the program.
    # We can set the number of algorithm runs.
    # This loop has three steps:
    #   1) Initialize the crows
    #   2) Run the main algorithm
    #   3) Export data to an array
    out_xls = np.zeros((MAIN_RUN, 7))

    for mainloop in range(1, MAIN_RUN + 1):
        print(f'Run: {mainloop}')
        Positions, Fitness, Dap = BinaryInitialization(fobj, SearchAgents_no, dim, 1, 0, Data)
        Worst, Best, Mean, SD, ASS, ECSA_fit_Mean9, ECSA_ACC_Mean, Destination_position, Destination_fitness, convergence_bccsa = BCCSA(
            Positions, Fitness, SearchAgents_no, Max_iter, 0, 1, dim, fobj, AP, fl, ChaosVec[7, :], Dap, Data)
        out_xls[mainloop - 1, :] = [Worst, Best, Mean, SD, ASS, ECSA_fit_Mean9, ECSA_ACC_Mean]

        Destination_position_list = list(Destination_position)
        print(Destination_position_list)
        print(Destination_fitness)

    # Save the outputs to a CSV file (you can use pandas to_csv method)
    output_df = pd.DataFrame(out_xls, columns=["Worst", "Best", "Mean", "SD", "ASS", "ECSA_fit_Mean9", "ECSA_ACC_Mean"])
    output_df.to_csv(r'E:\train_data.csv', index=False)
    # output_df.to_csv(r'E:\Datasets_res\clean1_res.csv', index=False)
    print(Destination_position)
    print(Destination_fitness)
    # Alert that the algorithm was finished!
    print('Algorithm finished!')


if __name__ == "__main__":
    main()