Skip to content

Commit e8585c2

Browse files
all B-JointSP source code
0 parents  commit e8585c2

30 files changed

+3196
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Requries Python 3.5 and Gurobi 7.0.

fixed/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+


fixed/fixed_instance.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# instance with fixed location, e.g., of a legacy network function
2+
class FixedInstance:
3+
def __init__(self, location, component):
4+
self.location = location
5+
self.component = component
6+
7+
def __str__(self):
8+
return "({}, {})".format(self.location, self.component)

fixed/source.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Source:
2+
def __init__(self, location, component, flows):
3+
self.location = location
4+
self.component = component
5+
self.flows = flows # list of flows, each with ID and data rate
6+
7+
def __str__(self):
8+
flow_str = ""
9+
for f in self.flows:
10+
flow_str += str(f)
11+
return "({}, {}, {})".format(self.location, self.component, flow_str)
12+
13+
# return sum of dr of all flows leaving the source
14+
def total_flow_dr(self):
15+
return sum(f.src_dr for f in self.flows)

heuristic/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+


heuristic/control.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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

Comments
 (0)