def nodes_with_smallest_ct(ag, tg, shm, task):
    """
    finds nodes with smallest completion time of the task!
    THIS FUNCTION CAN BE STRICTLY USED FOR INDEPENDENT TGs
    :param ag: Arch Graph
    :param tg: Task Graph
    :param shm: System Health Map
    :param task: Task number
    :return: list of nodes with smallest completion time for Task
    """
    node_with_smallest_ct = []
    random_node = random.choice(ag.nodes())
    while (not shm.node[random_node]['NodeHealth']
           ) or ag.node[random_node]['PE'].Dark:
        random_node = random.choice(ag.nodes())
    node_speed_down = 1 + ((100.0 - shm.node[random_node]['NodeSpeed']) / 100)
    task_execution_on_node = tg.node[task]['WCET'] * node_speed_down
    last_allocated_time_on_node = Scheduling_Functions_Nodes.FindLastAllocatedTimeOnNode(
        tg, ag, random_node, logging=None)
    if last_allocated_time_on_node < tg.node[task]['Release']:
        smallest_completion_time = tg.node[task][
            'Release'] + task_execution_on_node
    else:
        smallest_completion_time = last_allocated_time_on_node + task_execution_on_node
    for node in ag.nodes():
        if shm.node[node]['NodeHealth'] and (
                not ag.node[random_node]['PE'].Dark):
            node_speed_down = 1 + ((100.0 - shm.node[node]['NodeSpeed']) / 100)
            task_execution_on_node = tg.node[task]['WCET'] * node_speed_down
            last_allocated_time_on_node = Scheduling_Functions_Nodes.FindLastAllocatedTimeOnNode(
                tg, ag, node, logging=None)
            if last_allocated_time_on_node < tg.node[task]['Release']:
                completion_on_node = tg.node[task][
                    'Release'] + task_execution_on_node
            else:
                completion_on_node = last_allocated_time_on_node + task_execution_on_node

            if ceil(completion_on_node) < smallest_completion_time:
                smallest_completion_time = completion_on_node
    for node in ag.nodes():
        if shm.node[node]['NodeHealth'] and (
                not ag.node[random_node]['PE'].Dark):
            node_speed_down = 1 + ((100.0 - shm.node[node]['NodeSpeed']) / 100)
            last_allocated_time_on_node = Scheduling_Functions_Nodes.FindLastAllocatedTimeOnNode(
                tg, ag, node, logging=None)
            task_execution_on_node = tg.node[task]['WCET'] * node_speed_down
            if last_allocated_time_on_node < tg.node[task]['Release']:
                completion_on_node = tg.node[task][
                    'Release'] + task_execution_on_node
            else:
                completion_on_node = last_allocated_time_on_node + task_execution_on_node
            if completion_on_node == smallest_completion_time:
                node_with_smallest_ct.append(node)
    return node_with_smallest_ct
def mapping_cost_function(tg, ag, shm, report, initial_mapping_string=None):
    """
    Calculates the Costs of a mapping based on the configurations of Config file
    :param tg: Task Graph
    :param ag: Architecture Graph
    :param shm: System Health Map
    :param report: If true prints cost function report to Command-line
    :param initial_mapping_string: Initial mapping string used for calculating distance from the current mapping
    :return: cost of the mapping
    """
    node_makespan_list = []
    link_makespan_list = []
    for node in ag.nodes():
        if shm.node[node]['NodeHealth'] and (not ag.node[node]['PE'].Dark):
            node_makespan_list.append(
                Scheduling_Functions_Nodes.FindLastAllocatedTimeOnNode(
                    tg, ag, node, logging=None))
    for link in ag.edges():
        if shm.edge[link[0]][link[1]]['LinkHealth']:
            link_makespan_list.append(
                Scheduling_Functions_Links.FindLastAllocatedTimeOnLink(
                    tg, ag, link, logging=None))
    node_makspan_sd = statistics.stdev(node_makespan_list)
    node_makspan_max = max(node_makespan_list)
    link_makspan_sd = statistics.stdev(link_makespan_list)
    link_makspan_max = max(link_makespan_list)

    node_util_list = []
    for node in ag.nodes():
        if shm.node[node]['NodeHealth'] and (not ag.node[node]['PE'].Dark):
            node_util_list.append(AG_Functions.return_node_util(tg, ag, node))
    node_util_sd = statistics.stdev(node_util_list)

    if Config.Mapping_CostFunctionType == 'SD':
        cost = node_makspan_sd + link_makspan_sd
    elif Config.Mapping_CostFunctionType == 'Node_SD':
        cost = node_makspan_sd
    elif Config.Mapping_CostFunctionType == 'Node_Util_SD':
        cost = node_util_sd
    elif Config.Mapping_CostFunctionType == 'Link_SD':
        cost = link_makspan_sd
    elif Config.Mapping_CostFunctionType == 'SD+MAX':
        cost = node_makspan_max + node_makspan_sd + link_makspan_sd + link_makspan_max
    elif Config.Mapping_CostFunctionType == 'MAX':
        cost = node_makspan_max + link_makspan_max
    elif Config.Mapping_CostFunctionType == 'CONSTANT':
        cost = 1
    else:
        raise ValueError("Mapping_CostFunctionType is not valid")

    distance = None
    if initial_mapping_string is not None:
        distance = hamming_distance_of_mapping(initial_mapping_string,
                                               mapping_into_string(tg))
        cost += distance
    if report:
        print("===========================================")
        print("      REPORTING MAPPING COST")
        print("===========================================")
        print("NODES MAKE SPAN MAX:" + str(node_makspan_max))
        print("NODES MAKE SPAN STANDARD DEVIATION:" + str(node_makspan_sd))
        print("LINKS MAKE SPAN MAX:" + str(link_makspan_max))
        print("LINKS MAKE SPAN STANDARD DEVIATION:" + str(link_makspan_sd))
        if distance is not None:
            print("DISTANCE FROM STARTING SOLUTION:" + str(distance))
        print("MAPPING SCHEDULING COST:" + str(cost))

    if cost == 0:
        raise ValueError("Mapping with 0 cost... Something is wrong here...")
    return cost