|
| 1 | +import math |
| 2 | +import time |
| 3 | +import logging |
| 4 | +import objective |
| 5 | +from collections import defaultdict |
| 6 | +from heuristic import heuristic |
| 7 | +from heuristic import improvement |
| 8 | +from heuristic import shortest_paths as sp |
| 9 | +from overlay.instance import Instance |
| 10 | + |
| 11 | + |
| 12 | +# global variables for easy access by all functions |
| 13 | +nodes, links, prev_instances, obj = None, None, None, None |
| 14 | + |
| 15 | + |
| 16 | +# return dict of currently consumed node resources based on the instances of the specified overlays |
| 17 | +def consumed_node_resources(overlays): |
| 18 | + consumed_cpu, consumed_mem = {}, {} |
| 19 | + # reused instances exist in multiple overlays with diff ingoing edges -> have to allow duplicates -> use list not set |
| 20 | + instances = [i for t in overlays.keys() for i in overlays[t].instances] |
| 21 | + for v in nodes.ids: |
| 22 | + consumed_cpu[v] = sum(i.consumed_cpu() for i in instances if i.location == v) |
| 23 | + consumed_mem[v] = sum(i.consumed_mem() for i in instances if i.location == v) |
| 24 | + return consumed_cpu, consumed_mem |
| 25 | + |
| 26 | + |
| 27 | +# return the objective value based on the specified overlays |
| 28 | +def objective_value(overlays, print_info=False): |
| 29 | + # check delay of each edge; if too high, return math.inf for infeasible/infinity |
| 30 | + edges = [e for ol in overlays.values() for e in ol.edges] |
| 31 | + for e in edges: |
| 32 | + for path in e.paths: |
| 33 | + if sp.path_delay(links, path) > e.arc.max_delay: |
| 34 | + print("Embedding INFEASIBLE because delay of path of {} is too high".format(e)) |
| 35 | + logging.warning("Embedding INFEASIBLE because delay of path of {} is too high".format(e)) |
| 36 | + return math.inf |
| 37 | + |
| 38 | + # calculate changed instances (compared to previous instances) |
| 39 | + curr_instances = {i for ol in overlays.values() for i in ol.instances} |
| 40 | + changed = prev_instances ^ curr_instances # instances that are were added or removed |
| 41 | + |
| 42 | + # record max over-subscription of node capacities |
| 43 | + consumed_cpu, consumed_mem = consumed_node_resources(overlays) |
| 44 | + max_cpu_over, max_mem_over = 0, 0 |
| 45 | + for v in nodes.ids: |
| 46 | + if consumed_cpu[v] - nodes.cpu[v] > max_cpu_over: |
| 47 | + max_cpu_over = consumed_cpu[v] - nodes.cpu[v] |
| 48 | + if consumed_mem[v] - nodes.mem[v] > max_mem_over: |
| 49 | + max_mem_over = consumed_mem[v] - nodes.mem[v] |
| 50 | + |
| 51 | + # calculate data rate of each link and mark used links for each edge |
| 52 | + consumed_dr = defaultdict(int) # default = 0 |
| 53 | + link_used = {} |
| 54 | + edges = [e for ol in overlays.values() for e in ol.edges] |
| 55 | + for e in edges: |
| 56 | + for path in e.paths: |
| 57 | + # go along nodes of the path and increment data rate of each traversed link |
| 58 | + for i in range(len(path) - 1): |
| 59 | + # skip connections on same node without a link (both inst at same node) |
| 60 | + if path[i] != path[i + 1]: |
| 61 | + # assume the edge dr is split equally among all paths (currently only 1 path per edge) |
| 62 | + consumed_dr[(path[i], path[i+1])] += e.flow_dr() / len(e.paths) |
| 63 | + link_used[(e.arc, e.source.location, e.dest.location, path[i], path[i+1])] = 1 |
| 64 | + |
| 65 | + # record max over-subscription of link capacitiy |
| 66 | + max_dr_over = 0 |
| 67 | + for l in links.ids: |
| 68 | + if consumed_dr[l] - links.dr[l] > max_dr_over: |
| 69 | + max_dr_over = consumed_dr[l] - links.dr[l] |
| 70 | + |
| 71 | + # calculate total delay over all used links (by different edges) |
| 72 | + total_delay = 0 |
| 73 | + for key in link_used: |
| 74 | + total_delay += links.delay[(key[3], key[4])] |
| 75 | + |
| 76 | + # calculate total consumed resources |
| 77 | + total_consumed_cpu = sum(consumed_cpu[v] for v in nodes.ids) |
| 78 | + total_consumed_mem = sum(consumed_mem[v] for v in nodes.ids) |
| 79 | + total_consumed_dr = sum(consumed_dr[l] for l in links.ids) |
| 80 | + |
| 81 | + # print objective value info |
| 82 | + if print_info: |
| 83 | + print("Max over-subscription: {} (cpu), {} (mem), {} (dr)".format(max_cpu_over, max_mem_over, max_dr_over)) |
| 84 | + print("Total delay: {}, Num changed instances: {}".format(total_delay, len(changed))) |
| 85 | + print("Total consumed resources: {} (cpu), {} (mem), {} (dr)".format(total_consumed_cpu, total_consumed_mem, total_consumed_dr)) |
| 86 | + logging.info("Max over-subscription: {} (cpu), {} (mem), {} (dr)".format(max_cpu_over, max_mem_over, max_dr_over)) |
| 87 | + logging.info("Total delay: {}, Num changed instances: {}".format(total_delay, len(changed))) |
| 88 | + logging.info("Total consumed resources: {} (cpu), {} (mem), {} (dr)".format(total_consumed_cpu, total_consumed_mem, total_consumed_dr)) |
| 89 | + |
| 90 | + # calculate objective value; objectives & weights have to be identical to the MIP |
| 91 | + # lexicographical combination of all objectives |
| 92 | + if obj == objective.COMBINED: |
| 93 | + w1 = 100 * 1000 * 1000 # assuming changed instances < 100 |
| 94 | + w2 = 1000 * 1000 # assuming total resource consumption < 1000 |
| 95 | + w3 = 1000 # assuming total delay < 1000 |
| 96 | + value = w1 * (max_cpu_over + max_mem_over + max_dr_over) |
| 97 | + value += w2 * len(changed) |
| 98 | + value += w3 * (total_consumed_cpu + total_consumed_mem + total_consumed_dr) |
| 99 | + value += total_delay |
| 100 | + |
| 101 | + # minimize max over-subscription |
| 102 | + elif obj == objective.OVER_SUB: |
| 103 | + value = max_cpu_over + max_mem_over + max_dr_over |
| 104 | + |
| 105 | + # minimize changed instances (compared to previous embedding) |
| 106 | + elif obj == objective.CHANGED: |
| 107 | + value = len(changed) |
| 108 | + |
| 109 | + # minimize total resource consumption |
| 110 | + elif obj == objective.RESOURCES: |
| 111 | + value = total_consumed_cpu + total_consumed_mem + total_consumed_dr |
| 112 | + |
| 113 | + # minimize total delay |
| 114 | + elif obj == objective.DELAY: |
| 115 | + value = total_delay |
| 116 | + |
| 117 | + else: |
| 118 | + logging.error("Objective {} unknown".format(obj)) |
| 119 | + raise ValueError("Objective {} unknown".format(obj)) |
| 120 | + |
| 121 | + return value |
| 122 | + |
| 123 | + |
| 124 | +# return a dict with the total source data rate for each source component |
| 125 | +def total_source_drs(sources): |
| 126 | + src_drs = defaultdict(int) # default = 0 |
| 127 | + for src in sources: |
| 128 | + src_drs[src.component] += src.dr |
| 129 | + return src_drs |
| 130 | + |
| 131 | + |
| 132 | +def solve(arg_nodes, arg_links, templates, prev_overlays, sources, fixed, arg_obj): |
| 133 | + # write global variables |
| 134 | + global nodes, links, prev_instances, obj |
| 135 | + nodes = arg_nodes |
| 136 | + links = arg_links |
| 137 | + # copy previous instances (attributes like edges_in etc are not needed and not copied) |
| 138 | + prev_instances = {Instance(i.component, i.location, i.src_flows) for ol in prev_overlays.values() for i in ol.instances} |
| 139 | + obj = arg_obj |
| 140 | + |
| 141 | + # print input |
| 142 | + print("Templates:", *templates, sep=" ") |
| 143 | + print("Sources:", *sources, sep=" ") |
| 144 | + print("Fixed instances:", *fixed, sep=" ") |
| 145 | + print("Previous instances:", *prev_instances, sep=" ") |
| 146 | + |
| 147 | + # pre-computation of shortest paths |
| 148 | + start_init = time.time() |
| 149 | + shortest_paths = sp.all_pairs_shortest_paths(nodes, links) |
| 150 | + init_time = time.time() - start_init |
| 151 | + print("Time for pre-computation of shortest paths: {}s\n".format(init_time)) |
| 152 | + logging.info("Time for pre-computation of shortest paths: {}s\n".format(init_time)) |
| 153 | + |
| 154 | + start_heuristic = time.time() |
| 155 | + # get total source data rate for each source component (for sorting the templates later) |
| 156 | + src_drs = defaultdict(int) # default = 0 |
| 157 | + for src in sources: |
| 158 | + src_drs[src.component] += src.total_flow_dr() |
| 159 | + |
| 160 | + # sort templates with decreasing weight: heaviest/most difficult templates get embedded first |
| 161 | + templates.sort(key=lambda t: t.weight(src_drs[t.source()]), reverse=True) |
| 162 | + print("Templates sorted to start with heaviest:", *templates, sep=" ") |
| 163 | + |
| 164 | + # initial solution |
| 165 | + print("\n----- Initial solution -----") |
| 166 | + logging.info("----- Initial solution -----") |
| 167 | + overlays = heuristic.solve(arg_nodes, arg_links, templates, prev_overlays, sources, fixed, shortest_paths) |
| 168 | + obj_value = objective_value(overlays) |
| 169 | + print("Objective value of initial solution: {}".format(obj_value)) |
| 170 | + print("Runtime for initial solution: {}".format(time.time() - start_heuristic)) |
| 171 | + logging.info("Objective value of initial solution: {}".format(obj_value)) |
| 172 | + logging.info("Runtime for initial solution: {}\n".format(time.time() - start_heuristic)) |
| 173 | + |
| 174 | + |
| 175 | + # iterative improvement |
| 176 | + if len(nodes.ids) > 1: # doesn't work for networks with just 1 node |
| 177 | + print("\n----- Iterative improvement -----") |
| 178 | + logging.info("----- Iterative improvement -----") |
| 179 | + overlays = improvement.improve(arg_nodes, arg_links, templates, overlays, sources, fixed, shortest_paths) |
| 180 | + obj_value = objective_value(overlays, True) |
| 181 | + runtime = time.time() - start_heuristic |
| 182 | + print("Objective value after improvement: {}".format(obj_value)) |
| 183 | + print("Heuristic runtime: {}s".format(runtime)) |
| 184 | + logging.info("Objective value after improvement: {}".format(obj_value)) |
| 185 | + logging.info("Heuristic runtime: {}s".format(runtime)) |
| 186 | + else: |
| 187 | + runtime = time.time() - start_heuristic |
| 188 | + print("Skip iterative improvement for network with just 1 node") |
| 189 | + logging.info("Skip iterative improvement for network with just 1 node") |
| 190 | + |
| 191 | + # calculate changed instances for writing result |
| 192 | + curr_instances = {i for ol in overlays.values() for i in ol.instances} |
| 193 | + changed = prev_instances ^ curr_instances # instances that were added or removed |
| 194 | + |
| 195 | + return init_time, runtime, obj_value, changed, overlays |
0 commit comments