import benchmark_functions
from BA import BatAlgorithm
from ABC import ArtificialBeeColony
from ALO import AntLionOptimization
from WOA import WhaleOptimization
from FA import FireflyAlgorithm
from MFO import MothFlameOptimization
from GWO import GreyWolfOptimization
from DAO import DragonflyAlgorithm
from CS import CuckooSearch
from PSO import ParticleSwarmOptimization
from EHO import ElephantHerdOptimization
import pytictoc
import numpy as np
import csv
import os
import matplotlib.pyplot as plt


def mean_of_all_evals(iter_eval_histories):
    mean_evals = np.zeros(len(iter_eval_histories[0]))
    for index in range(len(iter_eval_histories[0])):
        one_index = np.zeros(len(iter_eval_histories))
        i = 0
        for run in iter_eval_histories:
            one_index[i] = run[index][1]
            i += 1
        mean_evals[index] = np.mean(one_index, dtype=np.int32)
    return mean_evals


def mean_of_all_runs(g_best_histories):
    mean_run = np.zeros(len(g_best_histories[0]))
    for index in range(len(g_best_histories[0])):
        one_index = np.zeros(len(g_best_histories))
        i = 0
        for run in g_best_histories:
            one_index[i] = run[index]
            i += 1
        mean_run[index] = np.mean(one_index)
    return mean_run


def check_len(g_opt_history):
    iteration = iter(g_opt_history)
    length = len(next(iteration))
    if not all(len(l) == length for l in iteration):
        return False
    else:
        return True


def concat_values(g_opt_history, eval_iter_history):
    best = g_opt_history[0]
    for a_g_opt_history in g_opt_history:
        if len(a_g_opt_history) > len(best):
            best = a_g_opt_history
    joined_lists = []
    for iteration in range(len(g_opt_history)):
        joined_lists.append([g_opt_history[iteration], eval_iter_history[iteration]])
    g_opt_history_eqlen = []
    eval_iter_eqlen = []
    for turn in joined_lists:
        if len(turn[0]) <= len(best):
            empty_no = (len(best) - len(turn[0]))
            empty_evaluations = np.ones((empty_no, 1)) * turn[1][-1]
            empty_iterations = np.ones((empty_no, 1)) * turn[0][-1]
            g_opt_concat = np.concatenate((turn[0], empty_iterations), axis=0)
            # eval_iter_concat = np.concatenate((turn[1], empty_evaluations), axis=0)
            eval_iter_concat = turn[1] + empty_evaluations.tolist()
            # np.append(a_g_opt_history, empty_iterations, axis=0) alternatively can use this, same result
            g_opt_history_eqlen.append(g_opt_concat)
            eval_iter_eqlen.append(eval_iter_concat)

    return g_opt_history_eqlen, eval_iter_eqlen


def save_statistic_F_D_R_B(pso_v, ba_v, abc_v, alo_v, cs_v, dao_v, eho_v, gwo_v, mfo_v, woa_v, fa_v, problem_info):
    # algorithm_info = [pso_v, ba_v, abc_v, alo_v, cs_v, dao_v, eho_v, gwo_v, mfo_v, woa_v, fa_v]
    fname = problem_info[0]
    fid = problem_info[1]
    dim_value = problem_info[2]
    no_runs = problem_info[3]
    budget_value = problem_info[4]
    csv_name = fid + "_" + "D" + str(dim_value) + "_" + "statistics" + ".csv"
    folder_name = fid + "_" + "benchmarking_statistics"
    path_to_script = os.path.abspath(os.path.dirname(__file__))
    full_path = path_to_script + "\\" + "n_DIM_statistics" + "\\" + folder_name + "\\" + csv_name
    with open(full_path, 'w', newline='') as f:
        file_writer = csv.writer(f, delimiter=',')
        file_writer.writerow(['Name', 'ID', 'Dim', 'No. Runs', 'Budget'])
        file_writer.writerow([fname, fid, dim_value, no_runs, budget_value])
        file_writer.writerow([''])
        # For writing in rows -> takes more space, better to write info in columns -> save 3 rows
        # file_writer.writerow(['Algorithm', 'Mean', 'Median', 'std', 'Min', 'Max', 'MeanTime', 'MeanEval'])
        # file_writer.writerows(algorithm_info)

        # Write in columns
        criteria = ['Algorithm', 'Mean', 'Median', 'std', 'Min', 'Max', 'MeanTime', 'MeanEval']
        whole_info = [criteria, pso_v, ba_v, abc_v, alo_v, cs_v, dao_v, eho_v, gwo_v, mfo_v, woa_v, fa_v]

        row1 = []
        row2 = []
        row3 = []
        row4 = []
        row5 = []
        row6 = []
        row7 = []
        row8 = []
        for info in whole_info:
            i = 0
            for cell in info:
                if i == 0:
                    row1.append(cell)
                    i += 1
                    continue
                if i == 1:
                    row2.append(cell)
                    i += 1
                    continue
                if i == 2:
                    row3.append(cell)
                    i += 1
                    continue
                if i == 3:
                    row4.append(cell)
                    i += 1
                    continue
                if i == 4:
                    row5.append(cell)
                    i += 1
                    continue
                if i == 5:
                    row6.append(cell)
                    i += 1
                    continue
                if i == 6:
                    row7.append(cell)
                    i += 1
                    continue
                if i == 7:
                    row8.append(cell)
                    i += 1
                    continue

        row_info = [row1, row2, row3, row4, row5, row6, row7, row8]
        file_writer.writerows(row_info)


def save_statistics_optimum(algorithm_optima, problem_info):
    fname = problem_info[0]
    fid = problem_info[1]
    dim_value = problem_info[2]
    no_runs = problem_info[3]
    budget_value = problem_info[4]
    csv_name = fid + "_" + "D" + str(dim_value) + "_" + "optimum_data" + ".csv"
    folder_name = fid + "_" + "benchmarking_statistics"
    path_to_script = os.path.abspath(os.path.dirname(__file__))
    full_path = path_to_script + "\\" + "n_DIM_statistics" + "\\" + folder_name + "\\" + csv_name
    with open(full_path, 'w', newline='') as f:
        file_writer = csv.writer(f, delimiter=',')
        file_writer.writerow(['Name', 'ID', 'Dim', 'No. Runs', 'Budget'])
        file_writer.writerow([fname, fid, dim_value, no_runs, budget_value])
        file_writer.writerow([''])
        header = ['no.run', 'PSO', 'BA', 'ABC', 'ALO', 'CS', 'DAO', 'EHO', 'GWO', 'MFO', 'WOA', 'FA']
        file_writer.writerow(header)
        runs = np.linspace(1, no_runs, no_runs)

        row = []
        for i in range(0, len(runs)):
            row.append(runs[i])
            row.append(algorithm_optima[0][i])
            row.append(algorithm_optima[1][i])
            row.append(algorithm_optima[2][i])
            row.append(algorithm_optima[3][i])
            row.append(algorithm_optima[4][i])
            row.append(algorithm_optima[5][i])
            row.append(algorithm_optima[6][i])
            row.append(algorithm_optima[7][i])
            row.append(algorithm_optima[8][i])
            row.append(algorithm_optima[9][i])
            row.append(algorithm_optima[10][i])
            file_writer.writerow(row)
            row = []


def plot_convergence(history_of_optima, history_of_eval, fID, dimN, budget, no_run, optimum):
    fig, ax = plt.subplots()
    i = 0
    colors = ['xkcd:black', 'xkcd:blue', 'xkcd:sienna', 'xkcd:green', 'xkcd:fuchsia', 'xkcd:goldenrod', 'xkcd:grey',
              'xkcd:indigo', 'xkcd:red', 'xkcd:orange', 'xkcd:cyan']
    o_flag = False
    c_flag = False
    for algorithm_history in history_of_optima:
        values = np.append(algorithm_history[1], algorithm_history[1][-1])
        for val in values:
            if val < optimum:
                o_flag = True
        if o_flag:
            indexes = np.argwhere(values < optimum)
            for n_index in indexes:
                values[n_index] = optimum
        values = values - optimum
        ax.plot(history_of_eval[i], values, color=colors[i], label=algorithm_history[0])
        for val in values:
            if val < (10 **-8):
                c_flag = True
        i += 1
    ax.legend(bbox_to_anchor=(1.02, 1), loc='upper left', borderaxespad=0)
    plt.title(str(fID) + '_Dim' + str(dimN) + '_Budget:' + str(budget) + '_Runs:' + str(no_run))
    plt.xlabel('Evaluations')
    plt.ylabel('Optimum')
    # plt.show()
    plt_name = fID + "_" + "D" + str(dimN) + "_" + "convergence" + ".png"
    folder_name = fID + "_" + "benchmarking_statistics"
    path_to_script = os.path.abspath(os.path.dirname(__file__))
    full_path = path_to_script + "\\" + "n_DIM_statistics" + "\\" + folder_name + "\\" + "plots" + "\\" + plt_name
    plt.savefig(full_path, bbox_inches="tight")
    plt.xscale("log")
    plt.yscale("log")
    if c_flag:
        plt.ylim(10**-8)
    log_plt_name = fID + "_" + "D" + str(dimN) + "_" + "convergence_LOG" + ".png"
    log_full_path = path_to_script + "\\" + "n_DIM_statistics" + "\\" + folder_name + "\\" + "plots" + "\\" + log_plt_name
    plt.savefig(log_full_path, bbox_inches="tight")


def compute_optimum_mean(optimum):
    return np.mean(optimum)


def compute_eval_mean(evaluations, budget):
    for eval in evaluations:
        if eval > budget:
            evaluations[evaluations.index(eval)] = budget
    return np.mean(evaluations)


def compute_time_mean(time):
    nptime = np.zeros(len(time))
    i = 0
    for element in time:
        nptime[i] = element
        i += 1
    return np.mean(nptime)


def compute_optimum_std(optimum):
    return np.std(optimum)


def compute_optimum_median(optimum):
    return np.median(optimum)


def get_optimum_max(optimum):
    return np.amax(optimum)


def get_optimum_min(optimum):
    return np.amin(optimum)


def limit_cut(algorithm_gbest_history, algorithm_iter_eval, budget):
    cut_a_best_history = []
    for r_gbest in algorithm_gbest_history:
        i = 0
        for element in r_gbest:
            if element == -105:
                break
            i += 1
        cut_r_g_best = np.zeros([i, 1])
        for j in range(0, i):
            cut_r_g_best[j] = r_gbest[j]
        cut_a_best_history.append(cut_r_g_best)

    for element in algorithm_iter_eval:
        for iter_eval in element:
            if iter_eval[1] > budget:
                i = element.index(iter_eval)
                element[i][1] = budget

    return cut_a_best_history


def main():
    """
        define benchmark
        D-dimension Unimodal f1-f7, Multimodal f8-f15
    """
    f1 = benchmark_functions.F1()
    f2 = benchmark_functions.F2()
    f3 = benchmark_functions.F3()
    f4 = benchmark_functions.F4()
    f5 = benchmark_functions.F5()
    f6 = benchmark_functions.F6()
    f7 = benchmark_functions.F7()
    f8 = benchmark_functions.F8()
    f9 = benchmark_functions.F9()
    f10 = benchmark_functions.F10()
    f11 = benchmark_functions.F11()
    f12 = benchmark_functions.F12()
    f13 = benchmark_functions.F13()
    f14 = benchmark_functions.F14()
    f15 = benchmark_functions.F15()
    uni_multimodal_benchmark = [f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15]
    d_dim = [2, 5, 10, 15, 20]
    """
        define budget
        <5D, 10D, 15D, 20D
    """
    budget5 = 25000   # 50000
    budget10 = 30000  # 200000
    budget15 = 35000  # 500000
    budget20 = 40000  # 1000000

    """
        define number of runs
    """
    n_run = 25  # 30

    """
        ABC Parameters: https://www.sciencedirect.com/science/article/pii/S1568494609002798
        Limit = 1000
        FA Parameters: Xin-She Yang (2022). Firefly Algorithm (https://www.mathworks.com/matlabcentral/fileexchange/29693-firefly-algorithm), MATLAB Central File Exchange. Retrieved April 9, 2022. 
        alpha = 0.5, gamma = 0.1, beta = 0.2
        CS Parameters: http://arxiv.org/PS_cache/arxiv/pdf/1005/1005.2908v2.pdf
        alpha ( scaling factor ) = 0.01 , pa = 0.25, lambda (beta) = 1.5,  
        PSO Parameters: https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=488968
        c1 = 2.05, c2 = 2.05, w = 0.9
        BA Parameters: Yang, XS. (2010). A New Metaheuristic Bat-Inspired Algorithm. In: González, J.R., Pelta, D.A., Cruz, C., Terrazas, G., Krasnogor, N. (eds) Nature Inspired Cooperative Strategies for Optimization (NICSO 2010). Studies in Computational Intelligence, vol 284. Springer, Berlin, Heidelberg. https://doi.org/10.1007/978-3-642-12538-6_6
        XS Yang (2022). The Standard Bat Algorithm (BA) (https://www.mathworks.com/matlabcentral/fileexchange/74768-the-standard-bat-algorithm-ba), MATLAB Central File Exchange. Retrieved April 9, 2022.
        Xin-She Yang, Nature-Inspired Optimization Algorithms, Elsevier Insights, (2014)
        alpha = 0.9, gamma = 0.1,
        param_limits = [A, r, f] [[0.5, 3], [0, 1.0], [0, 10]]
        EHO Parameters:  Gai-Ge Wang (2022). Elephant Herding Optimization (EHO) (https://www.mathworks.com/matlabcentral/fileexchange/53486-elephant-herding-optimization-eho), MATLAB Central File Exchange. Retrieved April 9, 2022. 
        alpha = 0.5, beta = 0.1, number of clans = 5
        WOA Parameters: https://seyedalimirjalili.com/woa
        b = 1
        MFO Parameters: https://seyedalimirjalili.com/mfo
        b = 10
        
        Other algorithms do not require any specific starting parameters, all algorithms will share the same number of 
        individuals and same number of iterations => no_individuals = 50, max_iterations = 1000
    """

    no_individuals = 50
    max_iterations = 1000

    for benchmark_f in uni_multimodal_benchmark:
        budget = 0
        for dimension in d_dim:
            if dimension <= 5:
                budget = budget5
            if dimension == 10:
                budget = budget10
            if dimension == 15:
                budget = budget15
            if dimension == 20:
                budget = budget20

            """
                define variables for algorithms, which will hold vital information from them in each run
                that is -> history of g_best, g_best, number of evaluations, iter/eval ratio list, time of optimization
                they are defined for every function and its respective dimensions, then saved
            """
            pso_g_best_history = []
            pso_gbest = []
            pso_iter_eval = []
            pso_eval_number = []
            pso_run_time = []

            ba_g_best_history = []
            ba_gbest = []
            ba_iter_eval = []
            ba_eval_number = []
            ba_run_time = []

            abc_g_best_history = []
            abc_gbest = []
            abc_iter_eval = []
            abc_eval_number = []
            abc_run_time = []

            alo_g_best_history = []
            alo_gbest = []
            alo_iter_eval = []
            alo_eval_number = []
            alo_run_time = []

            cs_g_best_history = []
            cs_gbest = []
            cs_iter_eval = []
            cs_eval_number = []
            cs_run_time = []

            dao_g_best_history = []
            dao_gbest = []
            dao_iter_eval = []
            dao_eval_number = []
            dao_run_time = []

            eho_g_best_history = []
            eho_gbest = []
            eho_iter_eval = []
            eho_eval_number = []
            eho_run_time = []

            gwo_g_best_history = []
            gwo_gbest = []
            gwo_iter_eval = []
            gwo_eval_number = []
            gwo_run_time = []

            mfo_g_best_history = []
            mfo_gbest = []
            mfo_iter_eval = []
            mfo_eval_number = []
            mfo_run_time = []

            woa_g_best_history = []
            woa_gbest = []
            woa_iter_eval = []
            woa_eval_number = []
            woa_run_time = []

            fa_g_best_history = []
            fa_gbest = []
            fa_iter_eval = []
            fa_eval_number = []
            fa_run_time = []

            for a_run in range(0, n_run):
                # Particle Swarm Optimization
                t_pso = pytictoc.TicToc()
                t_pso.tic()
                pso = ParticleSwarmOptimization(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                                benchmark_f.limits, benchmark_f.limits, 2.05, 2.05, 0.9)
                pso.evaluate()
                # pso.print_results()

                time_pso = t_pso.tocvalue()
                pso_gbest.append(pso.g_best)
                pso_eval_number.append(pso.evaluations)
                pso_iter_eval.append(pso.iter_eval)
                pso_run_time.append(time_pso)
                pso_g_best_history.append(pso.gbest_history)

                # Bat Algorithm
                t_ba = pytictoc.TicToc()
                t_ba.tic()
                ba = BatAlgorithm(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                  benchmark_f.limits, 0.1, 0.9, [[0.5, 3], [0, 1.0], [0, 10]])
                ba.evaluate()
                # ba.print_results()

                time_ba = t_ba.tocvalue()
                ba_gbest.append(ba.gbest)
                ba_eval_number.append(ba.evaluations)
                ba_iter_eval.append(ba.iter_eval)
                ba_run_time.append(time_ba)
                ba_g_best_history.append(ba.gbest_history)

                # Artifical Bee Colony
                t_abc = pytictoc.TicToc()
                t_abc.tic()
                abc = ArtificialBeeColony(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                          benchmark_f.limits, 1000)
                abc.evaluate()
                # abc.print_results()

                time_abc = t_abc.tocvalue()
                abc_gbest.append(abc.gbest)
                abc_eval_number.append(abc.evaluations)
                abc_iter_eval.append(abc.iter_eval)
                abc_run_time.append(time_abc)
                abc_g_best_history.append(abc.gbest_history)

                # Antlion optimization
                t_alo = pytictoc.TicToc()
                t_alo.tic()
                alo = AntLionOptimization(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                          benchmark_f.limits)
                alo.evaluate()
                # alo.print_results()

                time_alo = t_alo.tocvalue()
                alo_gbest.append(alo.gbest)
                alo_eval_number.append(alo.evaluations)
                alo_iter_eval.append(alo.iter_eval)
                alo_run_time.append(time_alo)
                alo_g_best_history.append(alo.gbest_history)

                # Cuckoo Search
                t_cs = pytictoc.TicToc()
                t_cs.tic()
                cs = CuckooSearch(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                  benchmark_f.limits, 0.01, 1.5, 0.25)
                cs.evaluate()
                # cs.print_results()

                time_cs = t_cs.tocvalue()
                cs_gbest.append(cs.gbest)
                cs_eval_number.append(cs.evaluations)
                cs_iter_eval.append(cs.iter_eval)
                cs_run_time.append(time_cs)
                cs_g_best_history.append(cs.gbest_history)

                # Dragonfly Algorithm Optimization
                t_dao = pytictoc.TicToc()
                t_dao.tic()
                dao = DragonflyAlgorithm(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                         benchmark_f.limits)
                dao.evaluate()
                # dao.print_results()

                time_dao = t_dao.tocvalue()
                dao_gbest.append(dao.gbest)
                dao_eval_number.append(dao.evaluations)
                dao_iter_eval.append(dao.iter_eval)
                dao_run_time.append(time_dao)
                dao_g_best_history.append(dao.gbest_history)

                # Elephant Herding Optimization
                t_eho = pytictoc.TicToc()
                t_eho.tic()
                eho = ElephantHerdOptimization(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                               benchmark_f.limits, 0.5, 0.1, 5)
                eho.evaluate()
                # eho.print_results()

                time_eho = t_eho.tocvalue()
                eho_gbest.append(eho.gbest)
                eho_eval_number.append(eho.evaluations)
                eho_iter_eval.append(eho.iter_eval)
                eho_run_time.append(time_eho)
                eho_g_best_history.append(eho.gbest_history)

                # Grey Wolf Optimization
                t_gwo = pytictoc.TicToc()
                t_gwo.tic()
                gwo = GreyWolfOptimization(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                           benchmark_f.limits)
                gwo.evaluate()
                # gwo.print_results()

                time_gwo = t_gwo.tocvalue()
                gwo_gbest.append(gwo.gbest)
                gwo_eval_number.append(gwo.evaluations)
                gwo_iter_eval.append(gwo.iter_eval)
                gwo_run_time.append(time_gwo)
                gwo_g_best_history.append(gwo.gbest_history)

                # Moth-flame Optimization
                t_mfo = pytictoc.TicToc()
                t_mfo.tic()
                mfo = MothFlameOptimization(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                            benchmark_f.limits, 10)
                mfo.evaluate()
                # mfo.print_results()

                time_mfo = t_mfo.tocvalue()
                mfo_gbest.append(mfo.gbest)
                mfo_eval_number.append(mfo.evaluations)
                mfo_iter_eval.append(mfo.iter_eval)
                mfo_run_time.append(time_mfo)
                mfo_g_best_history.append(mfo.gbest_history)

                # Whale Optimization Algorithm
                t_woa = pytictoc.TicToc()
                t_woa.tic()
                woa = WhaleOptimization(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                        benchmark_f.limits, 1)
                woa.evaluate()
                # woa.print_results()

                time_woa = t_woa.tocvalue()
                woa_gbest.append(woa.gbest)
                woa_eval_number.append(woa.evaluations)
                woa_iter_eval.append(woa.iter_eval)
                woa_run_time.append(time_woa)
                woa_g_best_history.append(woa.gbest_history)

                # Firefly Algorithm
                t_fa = pytictoc.TicToc()
                t_fa.tic()
                fa = FireflyAlgorithm(dimension, no_individuals, max_iterations, benchmark_f.equation, budget,
                                      benchmark_f.limits, 0.5, 0.2, 0.1)
                fa.evaluate()
                # fa.print_results()
                time_fa = t_fa.tocvalue()
                fa_gbest.append(fa.gbest)
                fa_eval_number.append(fa.evaluations)
                fa_iter_eval.append(fa.iter_eval)
                fa_run_time.append(time_fa)
                fa_g_best_history.append(fa.gbest_history)

            """
                Cut out of budget values
            """
            pso_g_best_history = limit_cut(pso_g_best_history, pso_iter_eval, budget)
            ba_g_best_history = limit_cut(ba_g_best_history, ba_iter_eval, budget)
            abc_g_best_history = limit_cut(abc_g_best_history, abc_iter_eval, budget)
            alo_g_best_history = limit_cut(alo_g_best_history, alo_iter_eval, budget)
            cs_g_best_history = limit_cut(cs_g_best_history, cs_iter_eval, budget)
            dao_g_best_history = limit_cut(dao_g_best_history, dao_iter_eval, budget)
            eho_g_best_history = limit_cut(eho_g_best_history, eho_iter_eval, budget)
            gwo_g_best_history = limit_cut(gwo_g_best_history, gwo_iter_eval, budget)
            mfo_g_best_history = limit_cut(mfo_g_best_history, mfo_iter_eval, budget)
            woa_g_best_history = limit_cut(woa_g_best_history, woa_iter_eval, budget)
            fa_g_best_history = limit_cut(fa_g_best_history, fa_iter_eval, budget)

            """
                Compute mean of optimum, eval and time
            """
            pso_optimum_mean = compute_optimum_mean(pso_gbest)
            pso_time_mean = compute_time_mean(pso_run_time)
            pso_eval_mean = compute_eval_mean(pso_eval_number, budget)

            ba_optimum_mean = compute_optimum_mean(ba_gbest)
            ba_time_mean = compute_time_mean(ba_run_time)
            ba_eval_mean = compute_eval_mean(ba_eval_number, budget)

            abc_optimum_mean = compute_optimum_mean(abc_gbest)
            abc_time_mean = compute_time_mean(abc_run_time)
            abc_eval_mean = compute_eval_mean(abc_eval_number, budget)

            alo_optimum_mean = compute_optimum_mean(alo_gbest)
            alo_time_mean = compute_time_mean(alo_run_time)
            alo_eval_mean = compute_eval_mean(alo_eval_number, budget)

            cs_optimum_mean = compute_optimum_mean(cs_gbest)
            cs_time_mean = compute_time_mean(cs_run_time)
            cs_eval_mean = compute_eval_mean(cs_eval_number, budget)

            dao_optimum_mean = compute_optimum_mean(dao_gbest)
            dao_time_mean = compute_time_mean(dao_run_time)
            dao_eval_mean = compute_eval_mean(dao_eval_number, budget)

            eho_optimum_mean = compute_optimum_mean(eho_gbest)
            eho_time_mean = compute_time_mean(eho_run_time)
            eho_eval_mean = compute_eval_mean(eho_eval_number, budget)

            gwo_optimum_mean = compute_optimum_mean(gwo_gbest)
            gwo_time_mean = compute_time_mean(gwo_run_time)
            gwo_eval_mean = compute_eval_mean(gwo_eval_number, budget)

            mfo_optimum_mean = compute_optimum_mean(mfo_gbest)
            mfo_time_mean = compute_time_mean(mfo_run_time)
            mfo_eval_mean = compute_eval_mean(mfo_eval_number, budget)

            woa_optimum_mean = compute_optimum_mean(woa_gbest)
            woa_time_mean = compute_time_mean(woa_run_time)
            woa_eval_mean = compute_eval_mean(woa_eval_number, budget)

            fa_optimum_mean = compute_optimum_mean(fa_gbest)
            fa_time_mean = compute_time_mean(fa_run_time)
            fa_eval_mean = compute_eval_mean(fa_eval_number, budget)

            """
                Compute std of optimum            
            """
            pso_optimum_std = compute_optimum_std(pso_gbest)
            ba_optimum_std = compute_optimum_std(ba_gbest)
            abc_optimum_std = compute_optimum_std(abc_gbest)
            alo_optimum_std = compute_optimum_std(alo_gbest)
            cs_optimum_std = compute_optimum_std(cs_gbest)
            dao_optimum_std = compute_optimum_std(dao_gbest)
            eho_optimum_std = compute_optimum_std(eho_gbest)
            gwo_optimum_std = compute_optimum_std(gwo_gbest)
            mfo_optimum_std = compute_optimum_std(mfo_gbest)
            woa_optimum_std = compute_optimum_std(woa_gbest)
            fa_optimum_std = compute_optimum_std(fa_gbest)

            """
                Compute median of optimum
            """
            pso_optimum_median = compute_optimum_median(pso_gbest)
            ba_optimum_median = compute_optimum_median(ba_gbest)
            abc_optimum_median = compute_optimum_median(abc_gbest)
            alo_optimum_median = compute_optimum_median(alo_gbest)
            cs_optimum_median = compute_optimum_median(cs_gbest)
            dao_optimum_median = compute_optimum_median(dao_gbest)
            eho_optimum_median = compute_optimum_median(eho_gbest)
            gwo_optimum_median = compute_optimum_median(gwo_gbest)
            mfo_optimum_median = compute_optimum_median(mfo_gbest)
            woa_optimum_median = compute_optimum_median(woa_gbest)
            fa_optimum_median = compute_optimum_median(fa_gbest)

            """
                Compute min, max of optimum            
            """
            pso_optimum_max = get_optimum_max(pso_gbest)
            pso_optimum_min = get_optimum_min(pso_gbest)
            ba_optimum_max = get_optimum_max(ba_gbest)
            ba_optimum_min = get_optimum_min(ba_gbest)
            abc_optimum_max = get_optimum_max(abc_gbest)
            abc_optimum_min = get_optimum_min(abc_gbest)
            alo_optimum_max = get_optimum_max(alo_gbest)
            alo_optimum_min = get_optimum_min(alo_gbest)
            cs_optimum_max = get_optimum_max(cs_gbest)
            cs_optimum_min = get_optimum_min(cs_gbest)
            dao_optimum_max = get_optimum_max(dao_gbest)
            dao_optimum_min = get_optimum_min(dao_gbest)
            eho_optimum_max = get_optimum_max(eho_gbest)
            eho_optimum_min = get_optimum_min(eho_gbest)
            gwo_optimum_max = get_optimum_max(gwo_gbest)
            gwo_optimum_min = get_optimum_min(gwo_gbest)
            mfo_optimum_max = get_optimum_max(mfo_gbest)
            mfo_optimum_min = get_optimum_min(mfo_gbest)
            woa_optimum_max = get_optimum_max(woa_gbest)
            woa_optimum_min = get_optimum_min(woa_gbest)
            fa_optimum_max = get_optimum_max(fa_gbest)
            fa_optimum_min = get_optimum_min(fa_gbest)

            """
                Create lists with info about algoritms and problem, these will be inputs into function, which will
                export them into csv
            """
            pso_info = ['PSO', pso_optimum_mean, pso_optimum_median, pso_optimum_std, pso_optimum_min, pso_optimum_max,
                        pso_time_mean, pso_eval_mean]
            ba_info = ['BA', ba_optimum_mean, ba_optimum_median, ba_optimum_std, ba_optimum_min, ba_optimum_max,
                       ba_time_mean, ba_eval_mean]
            abc_info = ['ABC', abc_optimum_mean, abc_optimum_median, abc_optimum_std, abc_optimum_min, abc_optimum_max,
                        abc_time_mean, abc_eval_mean]
            alo_info = ['ALO', alo_optimum_mean, alo_optimum_median, alo_optimum_std, alo_optimum_min, alo_optimum_max,
                        alo_time_mean, alo_eval_mean]
            cs_info = ['CS', cs_optimum_mean, cs_optimum_median, cs_optimum_std, cs_optimum_min, cs_optimum_max,
                       cs_time_mean, cs_eval_mean]
            dao_info = ['DAO', dao_optimum_mean, dao_optimum_median, dao_optimum_std, dao_optimum_min, dao_optimum_max,
                        dao_time_mean, dao_eval_mean]
            eho_info = ['EHO', eho_optimum_mean, eho_optimum_median, eho_optimum_std, eho_optimum_min, eho_optimum_max,
                        eho_time_mean, eho_eval_mean]
            gwo_info = ['GWO', gwo_optimum_mean, gwo_optimum_median, gwo_optimum_std, gwo_optimum_min, gwo_optimum_max,
                        gwo_time_mean, gwo_eval_mean]
            mfo_info = ['MFO', mfo_optimum_mean, mfo_optimum_median, mfo_optimum_std, mfo_optimum_min, mfo_optimum_max,
                        mfo_time_mean, mfo_eval_mean]
            woa_info = ['WOA', woa_optimum_mean, woa_optimum_median, woa_optimum_std, woa_optimum_min, woa_optimum_max,
                        woa_time_mean, woa_eval_mean]
            fa_info = ['FA', fa_optimum_mean, fa_optimum_median, fa_optimum_std, fa_optimum_min, fa_optimum_max,
                       fa_time_mean, fa_eval_mean]
            problem_info = [benchmark_f.name, benchmark_f.ID, dimension, n_run, budget]

            save_statistic_F_D_R_B(pso_info, ba_info, abc_info, alo_info, cs_info, dao_info, eho_info, gwo_info,
                                   mfo_info, woa_info, fa_info, problem_info)

            """
                Save optima from all runs into csv file
            """
            algoritms_runs_optima = [pso_gbest, ba_gbest, abc_gbest, alo_gbest, cs_gbest, dao_gbest, eho_gbest,
                                     gwo_gbest,
                                     mfo_gbest, woa_gbest, fa_gbest]
            save_statistics_optimum(algoritms_runs_optima, problem_info)

            """
                Check, if all runs have same number of iterations, if not, then concatinate with empty budget
                that means, if algorithm ran out of budget, it is no longer able to improve its solution, thus
                it is stuck with the last it found, "performing" "empty" iterations - not doing anything
            """
            if not check_len(pso_g_best_history):
                pso_g_best_history, pso_iter_eval = concat_values(pso_g_best_history, pso_iter_eval)
            if not check_len(ba_g_best_history):
                ba_g_best_history, ba_iter_eval = concat_values(ba_g_best_history, ba_iter_eval)
            if not check_len(abc_g_best_history):
                abc_g_best_history, abc_iter_eval = concat_values(abc_g_best_history, abc_iter_eval)
            if not check_len(alo_g_best_history):
                alo_g_best_history, alo_iter_eval = concat_values(alo_g_best_history, alo_iter_eval)
            if not check_len(cs_g_best_history):
                cs_g_best_history, cs_iter_eval = concat_values(cs_g_best_history, cs_iter_eval)
            if not check_len(dao_g_best_history):
                dao_g_best_history, dao_iter_eval = concat_values(dao_g_best_history, dao_iter_eval)
            if not check_len(eho_g_best_history):
                eho_g_best_history, eho_iter_eval = concat_values(eho_g_best_history, eho_iter_eval)
            if not check_len(gwo_g_best_history):
                gwo_g_best_history, gwo_iter_eval = concat_values(gwo_g_best_history, gwo_iter_eval)
            if not check_len(mfo_g_best_history):
                mfo_g_best_history, mfo_iter_eval = concat_values(mfo_g_best_history, mfo_iter_eval)
            if not check_len(woa_g_best_history):
                woa_g_best_history, woa_iter_eval = concat_values(woa_g_best_history, woa_iter_eval)
            if not check_len(fa_g_best_history):
                fa_g_best_history, fa_iter_eval = concat_values(fa_g_best_history, fa_iter_eval)

            """
                Compute mean of optima convergence from all runs
            """
            pso_run_mean = mean_of_all_runs(pso_g_best_history)
            ba_run_mean = mean_of_all_runs(ba_g_best_history)
            abc_run_mean = mean_of_all_runs(abc_g_best_history)
            alo_run_mean = mean_of_all_runs(alo_g_best_history)
            cs_run_mean = mean_of_all_runs(cs_g_best_history)
            dao_run_mean = mean_of_all_runs(dao_g_best_history)
            eho_run_mean = mean_of_all_runs(eho_g_best_history)
            gwo_run_mean = mean_of_all_runs(gwo_g_best_history)
            mfo_run_mean = mean_of_all_runs(mfo_g_best_history)
            woa_run_mean = mean_of_all_runs(woa_g_best_history)
            fa_run_mean = mean_of_all_runs(fa_g_best_history)

            """
                Compute mean of evaluations in each iteration from all runs
            """
            pso_eval_mean = mean_of_all_evals(pso_iter_eval)
            ba_eval_mean = mean_of_all_evals(ba_iter_eval)
            abc_eval_mean = mean_of_all_evals(abc_iter_eval)
            alo_eval_mean = mean_of_all_evals(alo_iter_eval)
            cs_eval_mean = mean_of_all_evals(cs_iter_eval)
            dao_eval_mean = mean_of_all_evals(dao_iter_eval)
            eho_eval_mean = mean_of_all_evals(eho_iter_eval)
            gwo_eval_mean = mean_of_all_evals(gwo_iter_eval)
            mfo_eval_mean = mean_of_all_evals(mfo_iter_eval)
            woa_eval_mean = mean_of_all_evals(woa_iter_eval)
            fa_eval_mean = mean_of_all_evals(fa_iter_eval)

            """
                Prepare data for plot
            """
            algorithms_optima_history = [['PSO', pso_run_mean], ['BA', ba_run_mean],
                                         ['ABC', abc_run_mean], ['ALO', alo_run_mean],
                                         ['CS', cs_run_mean], ['DAO', dao_run_mean],
                                         ['EHO', eho_run_mean], ['GWO', gwo_run_mean],
                                         ['MFO', mfo_run_mean], ['WOA', woa_run_mean],
                                         ['FA', fa_run_mean]]
            algorithms_iter_eval = [pso_eval_mean, ba_eval_mean, abc_eval_mean,
                                    alo_eval_mean, cs_eval_mean, dao_eval_mean,
                                    eho_eval_mean, gwo_eval_mean, mfo_eval_mean,
                                    woa_eval_mean, fa_eval_mean]

            """
                Plot convergence
            """
            plot_convergence(algorithms_optima_history, algorithms_iter_eval, benchmark_f.ID, dimension, budget, n_run,
                             benchmark_f.optimum)
            stop = 0


if __name__ == "__main__":
    main()
