def solve(self): """ Returns the optimal schedule Returns ------- time_of_op : dict Timeslot for each operation in the DAG ops_at_time : defaultdic List of operations in each timeslot length : int Maximum latency of optimal schedule """ init_drmt_schedule = None cpath, cplat = self.G.critical_path() Q_MAX = int(math.ceil(1.5 * cplat / self.period_duration)) print ('{:*^80}'.format(' Running DRMT ILP solver ')) T = self.period_duration nodes = self.G.nodes() match_nodes = self.G.nodes(select='match') action_nodes = self.G.nodes(select='action') edges = self.G.edges() match_action_pairs = self.get_match_action_pairs(edges) m = Model() m.setParam("LogToConsole", 0) # Create variables # t is the start time for each DAG node in the first scheduling period t = m.addVars(nodes, lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name="t") # The quotients and remainders when dividing by T (see below) # qr[v, q, r] is 1 when t[v] # leaves a quotient of q and a remainder of r, when divided by T. qr = m.addVars(list(itertools.product(nodes, range(Q_MAX), range(T))), vtype=GRB.BINARY, name="qr") qra = m.addVars(list(itertools.product(match_action_pairs, range(Q_MAX), range(T))), vtype=GRB.BINARY, name="qra") qrm = m.addVars(list(itertools.product(match_action_pairs, range(Q_MAX), range(T))), vtype=GRB.BINARY, name="qrm") qru = m.addVars(list(itertools.product(match_action_pairs, range(Q_MAX), range(T))), vtype=GRB.BINARY, name="qru") # Is there any match/action from packet q in time slot r? # This is required to enforce limits on the number of packets that # can be performing matches or actions concurrently on any processor. any_match = m.addVars(list(itertools.product(range(Q_MAX), range(T))), vtype=GRB.BINARY, name = "any_match") any_action = m.addVars(list(itertools.product(range(Q_MAX), range(T))), vtype=GRB.BINARY, name = "any_action") # The length of the schedule length = m.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name="length") # Set objective: minimize length of schedule m.setObjective(length, GRB.MINIMIZE) # Set constraints # The length is the maximum of all t's m.addConstrs((t[v] <= length for v in nodes), "constr_length_is_max") # Given v, qr[v, q, r] is 1 for exactly one q, r, i.e., there's a unique quotient and remainder m.addConstrs((sum(qr[v, q, r] for q in range(Q_MAX) for r in range(T)) == 1 for v in nodes),\ "constr_unique_quotient_remainder") # This is just a way to write dividend = quotient * divisor + remainder m.addConstrs((t[v] == \ sum(q * qr[v, q, r] for q in range(Q_MAX) for r in range(T)) * T + \ sum(r * qr[v, q, r] for q in range(Q_MAX) for r in range(T)) \ for v in nodes), "constr_division") m.addConstrs((t[v] - t[u] >= self.G.edge[u][v]['delay'] for (u,v) in edges),\ "constr_dag_dependencies") # add constraints for monitoring scratch space for (u,v) in match_action_pairs: for q in range(Q_MAX): for r in range(T): m.addConstr(q*T + r + 1 - t[v] <= BIG_M - BIG_M*qra[(u,v), q, r], "action_util"+str(u)+str(v)) m.addConstr( -q*T - r + 1 + t[u] <= BIG_M - BIG_M*qrm[(u,v), q, r], "match_util"+str(u)+str(v)) m.addConstr(qru[(u,v), q, r] >= qra[(u,v), q, r] + qrm[(u,v), q, r] - 1, "real_util"+str(u)+str(v)) #m.addConstrs((sum(qru[(u,v) q, r] for (u,v) in match_action_pairs for q in range(Q_MAX)) <= scratch_max for r in range(T)), "constr_scratch") m.addConstrs((sum(qru[(u,v), q, r] for (u,v) in match_action_pairs) <= self.scratch_max for q in range(Q_MAX) for r in range(T) ), "scratch_util") # Number of match units does not exceed match_unit_limit # for every time step (j) < T, check the total match unit requirements # across all nodes (v) that can be "rotated" into this time slot. m.addConstrs((sum(math.ceil((1.0 * self.G.node[v]['key_width']) / self.input_spec.match_unit_size) * qr[v, q, r]\ for v in match_nodes for q in range(Q_MAX))\ <= self.input_spec.match_unit_limit for r in range(T)),\ "constr_match_units") # The action field resource constraint (similar comments to above) m.addConstrs((sum(self.G.node[v]['num_fields'] * qr[v, q, r]\ for v in action_nodes for q in range(Q_MAX))\ <= self.input_spec.action_fields_limit for r in range(T)),\ "constr_action_fields") # Any time slot (r) can have match or action operations # from only match_proc_limit/action_proc_limit packets # We do this in two steps. # First, detect if there is any (at least one) match/action operation from packet q in time slot r # if qr[v, q, r] = 1 for any match node, then any_match[q,r] must = 1 (same for actions) # Notice that any_match[q, r] may be 1 even if all qr[v, q, r] are zero m.addConstrs((sum(qr[v, q, r] for v in match_nodes) <= (len(match_nodes) * any_match[q, r]) \ for q in range(Q_MAX)\ for r in range(T)),\ "constr_any_match1"); m.addConstrs((sum(qr[v, q, r] for v in action_nodes) <= (len(action_nodes) * any_action[q, r]) \ for q in range(Q_MAX)\ for r in range(T)),\ "constr_any_action1"); # Second, check that, for any r, the summation over q of any_match[q, r] is under proc_limits m.addConstrs((sum(any_match[q, r] for q in range(Q_MAX)) <= self.input_spec.match_proc_limit\ for r in range(T)), "constr_match_proc") m.addConstrs((sum(any_action[q, r] for q in range(Q_MAX)) <= self.input_spec.action_proc_limit\ for r in range(T)), "constr_action_proc") # Seed initial values if init_drmt_schedule: for i in nodes: t[i].start = init_drmt_schedule[i] # Solve model m.setParam('TimeLimit', self.minute_limit * 60) m.optimize() ret = m.Status if (ret == GRB.INFEASIBLE): print ('Infeasible') return None elif ((ret == GRB.TIME_LIMIT) or (ret == GRB.INTERRUPTED)): if (m.SolCount == 0): print ('Hit time limit or interrupted, no solution found yet') return None else: print ('Hit time limit or interrupted, suboptimal solution found with gap ', m.MIPGap) elif (ret == GRB.OPTIMAL): print ('Optimal solution found with gap ', m.MIPGap) else: print ('Return code is ', ret) assert(False) # Construct and return schedule self.time_of_op = {} self.ops_at_time = collections.defaultdict(list) self.length = int(length.x + 1) assert(self.length == length.x + 1) for v in nodes: tv = int(t[v].x) self.time_of_op[v] = tv self.ops_at_time[tv].append(v) # Compute periodic schedule to calculate resource usage self.compute_periodic_schedule() # Populate solution solution = Solution() solution.time_of_op = self.time_of_op solution.ops_at_time = self.ops_at_time solution.ops_on_ring = self.ops_on_ring solution.length = self.length solution.match_key_usage = self.match_key_usage solution.action_fields_usage = self.action_fields_usage solution.match_units_usage = self.match_units_usage solution.match_proc_usage = self.match_proc_usage solution.action_proc_usage = self.action_proc_usage return solution