def greedy_clust_best_eps_with_lpt_typed(LM, LT, MCoeff, eps_precision=0.1):
    """input : list of machines * list of tasks * matrix types'coefficient * precision of eps i.e. 1.1 ? or 1.01 ? etc (by default : 0.1)
        output : the best eps that respect the following condition - all machine's cost must be < eps*LB

        This function uses dichotomy to find the best eps
    """
    # variables needed to dichotomize
    end_eps = float(2.0)
    start_eps = float(0.0)
    min_makespan = data_structure.cost_final_LM_gen(greedy_cluster_with_lpt_typed(LM, LT, MCoeff, end_eps), MCoeff)

    # until eps' precision is not the one wanted
    while ((math.fabs(end_eps - start_eps) > eps_precision)):
        # storing the (list of machines)'s cost when eps is the midpoint of start_eps and end_eps
        A = data_structure.cost_final_LM_gen(
            greedy_cluster_with_lpt_typed(LM, LT, MCoeff, (float)(math.fabs(end_eps + start_eps)) / 2.0), MCoeff)

        # the cost is not better than the actual one, i.e. eps cannot be below the midpoint
        if (A > min_makespan):
            # therefore we set the midpoint as the new start_eps
            start_eps = (float)(math.fabs(end_eps + start_eps)) / 2.0

        # the cost is better, i.e. eps can be below the midpoint
        else:
            min_makespan = A
            end_eps = (float)(math.fabs(end_eps + start_eps)) / 2.0
    # finally we calculate which one between start_eps and end_eps holds the best eps
    # certainly the precision is the wanted one, but nothing says if the (list of machines)'s cost is lower with start_eps or end_eps
    if data_structure.cost_final_LM_gen(greedy_cluster_with_lpt_typed(LM, LT, MCoeff, start_eps),
                                        MCoeff) > data_structure.cost_final_LM_gen(
            greedy_cluster_with_lpt_typed(LM, LT, MCoeff, end_eps), MCoeff):
        return end_eps
    return start_eps
def order_type_best_eps(LM, LT, MCoeff, eps_precision):
    """input : list of machines * list of tasks * matrix types'coefficient * precision of eps i.e. 1.1 ? or 1.01 ? etc (by default : 0.1)
        output : the best eps that respect the following condition - all machine's cost must be < eps*LB
       
        This function uses dichotomy to find the best eps
    """

    #variables needed to dichotomize
    start_eps, end_eps = data_structure.inf_sup_types(LT, LM, MCoeff)

    # until eps' precision is not the one wanted
    while ((math.fabs(end_eps - start_eps) > eps_precision)):
        # storing the (list of machines)'s cost when eps is the midpoint of start_eps and end_eps
        LM_new = order_type(copy.deepcopy(LM), LT, MCoeff,
                            (float(math.fabs(end_eps + start_eps))) / 2.0)
        # the cost is not better than the actual one, i.e. eps cannot be below the midpoint
        if (LM_new == 0):
            # therefore we set the midpoint as the new start_eps
            #print ("on recupere l'ancien LB")
            start_eps = (float(math.fabs(end_eps + start_eps))) / 2.0

        # the cost is better, i.e. eps can be below the midpoint
        else:
            A = data_structure.cost_final_LM_gen(LM_new, MCoeff)
            end_eps = (float(math.fabs(end_eps + start_eps))) / 2.0

    return end_eps
def PTAS_difference(LM, LT, MCoeff, k, group=False):
    """
    ARUGMENTS
        List of machines * List of Tasks * Array coefficient * k first biggest tasks

    ALGORITHM
        Polynomial-time approximation scheme algorithm

        use prep_for_PTAS for the k biggest tasks LM
        put the remaining tasks using the lpt-typed_difference method in LM

        Returns a NEW LM

    COMPLEXITY
        lpt_typed complexity or prep_for_PTAS

    N.B. :
            to function properly please insert a low value for k otherwise be patient and be exponential
    """
    LT_copy_sorted_temp = sorted(LT, key=itemgetter('size'), reverse=True)

    # case where k <= m
    if k <= len(LM):
        print("little")
        # one task per machine
        current_best_LM = [copy.deepcopy(LM)]
        for i in range(k):
            try:
                current_best_LM[0][i]['LTM'].append(LT_copy_sorted_temp[i])
            except:
                print(current_best_LM, k, len(LT))
                sys.exit(1)

    else:  # use prep_PTAS

        #   tinitializes the needed stuffs i.e.   current_lowest_cost_max
        #all below is done in a temp, putting all tasks in one machine to initialize supra
        current_best_LM = [copy.deepcopy(LM)]
        LM_copy_temp = copy.deepcopy(LM)
        for i in range(0, k):
            if (len(LT_copy_sorted_temp) != 0):
                tmp_task = LT_copy_sorted_temp[0]
                (LM_copy_temp[0]['LTM']).append(tmp_task)
                LT_copy_sorted_temp.remove(tmp_task)
        #after this, it is initialized =)
        if isinstance(LT, np.ndarray):
            current_lowest_cost_max = [lpt.final_cost_LM_1type(LM)]
        else:
            current_lowest_cost_max = [
                data_structure.cost_final_LM_gen(LM_copy_temp, MCoeff)
            ]

        #   finding the optimal, the best way to put the k biggest task in LM calling prep_for_PTAS
        if (k > len(LT)):
            k = len(LT)
        prep_for_PTAS(LM, LT, MCoeff, k, current_lowest_cost_max,
                      current_best_LM)

    #   using lpt_typed for the remaining tasks

    #removing the k biggest tasks in LT_copy_sorted
    LT_copy_sorted = sorted(LT, key=itemgetter('size'), reverse=True)
    for i in range(k):
        if (len(LT_copy_sorted) != 0):
            LT_copy_sorted.pop(0)

    #calling lpt_typed with the LM returned by prep_for_PTAS and LT_copy_sorted
    #return lpt_typed.lpt_typed_difference(current_best_LM[0],LT_copy_sorted,MCoeff,group)
    return lpt.lpt(current_best_LM[0], LT_copy_sorted, MCoeff)
def prep_for_PTAS(LM, LT, MCoeff, k, current_lowest_cost_max, current_best_LM):
    """
    ARGUMENTS
        List of machines * List of Tasks * Array coefficient * k first biggest tasks * List of size 1 containing cost_max * List containing a LM
        e.g. : current_lowest_cost_max = [50], it is like so in order to have a pointer effect
                same for current_best_LM

    ALGORITHM
        preparation for Polynomial-time approximation scheme algorithm
        This is algorithm works by following branches in a tree :
            looping and calling itself in the loops so we can have something like this :

                for
                    for
                        for

            See it like a tree of height k and for each level there's nb_machines branches where you can put your task
            number of for = k biggest tasks
            loop's lentgh = nb_machines

        Returns nothing, passes the result in argument current_best_LM

    COMPLEXITY
        nb_machines ** k biggest tasks
        nb_machines ^ k
        len(LM) raised to k

    N.B. :
            only PTAS shall call this function

            in order to run properly
                --current_lowest_cost_max have to be initialize by putting all k tasks into one machine then calculate its cost
            No problemo returning nothing since it's used in PTAS
    """

    # calculates the cost_max of LM --- the branch and bound technique : cutting the branch when the cost is already bigger than the current_lowest_cost
    if isinstance(LT, np.ndarray):
        cost = lpt.final_cost_LM_1type(LM)
    else:
        cost = data_structure.cost_final_LM_gen(LM, MCoeff)
    if (
            cost >= current_lowest_cost_max[0]
    ):  # the previous added task makes things worse so we abort/cut this branch
        # branch cutted
        return

        # the following test is after the cost test because we want to avoid leafs that are useless
    # verify if there's still tasks to add
    if (k == 0):  # no more big tasks to add
        if (cost != 0):  # useful when we first enter this function
            # lowest cost change and put LM into the current_best_LM
            current_lowest_cost_max[0] = cost
            current_best_LM[0] = copy.deepcopy(
                LM)  # copy otherwise the last task is not added
        # else : lowest cost NOT changed doesn't change
        return

        # as always create copies to avoid unwanted bugs
    if isinstance(LT, np.ndarray):
        LT_copy_sorted = sorted(LT, reverse=True)
    else:
        LT_copy_sorted = sorted(LT, key=itemgetter('size'), reverse=True)
    LM_copy = copy.deepcopy(LM)

    # loopings
    for num_machine in range(0, len(LM)):
        if (len(LT) != 0):
            task_temp = LT_copy_sorted[0]
            # put the task in the num_machine-th machine in LM_copy
            (LM_copy[num_machine]['LTM']).append(task_temp)

            # take out the task in LT_copy
            LT_copy_sorted.remove(task_temp)

            # call itself  and in argument decrease k by 1
            prep_for_PTAS(copy.deepcopy(LM_copy), LT_copy_sorted, MCoeff,
                          k - 1, current_lowest_cost_max, current_best_LM)

            # put the task back in LT_copy
            LT_copy_sorted.insert(0, task_temp)

            # take out the task in LM_copy
            (LM_copy[num_machine]['LTM']).remove(task_temp)

    return