def CreatePlacementVariables(self, items, binDx, binDy): for i, item in enumerate(items): self.itemArea += item.Dx * item.Dy filteredStartX = [p for p in self.preprocess.GlobalPlacementPatternsX[i] if (p % binDx) + item.Dx <= binDx] globalStartX = self.model.NewIntVarFromDomain(Domain.FromValues(filteredStartX), f'xb1.{i}') self.startVariablesGlobalX.append(globalStartX) filteredStartY = [p for p in self.preprocess.ItemPlacementPatternsY[i] if p + item.Dy <= binDy] yStart = self.model.NewIntVarFromDomain(Domain.FromValues(filteredStartY),f'y1.{i}') self.startVariablesY.append(yStart)
def CreateVariables(self): binDx = self.Bin.Dx binDy = self.Bin.Dy placementPointGenerator = PlacementPointGenerator(self.Items, self.Bin) placementPatternsX, placementPatternsY = placementPointGenerator.CreatePlacementPatterns( self.placementPointStrategy, self.Items, self.Bin) # Consider modifying domains according to Cote, Iori (2018): The meet-in-the-middle principle for cutting and packing problems. for i, item in enumerate(self.Items): #filteredItems = [itemJ for j, itemJ in enumerate(self.Items) if i != j] placementPatternX = placementPatternsX[i] placementPatternY = placementPatternsY[i] x1 = self.Model.NewIntVarFromDomain( Domain.FromValues(placementPatternX), f'x1.{i}') #placementPointsEndX = [p + item.Dx for p in placementPointsStartX] #x2 = self.Model.NewIntVarFromDomain(Domain.FromValues(placementPointsEndX), f'x2.{i}') self.StartX.append(x1) #self.EndX.append(x2) y1 = self.Model.NewIntVarFromDomain( Domain.FromValues(placementPatternY), f'y1.{i}') #placementPointsEndY = [p + item.Dy for p in placementPointsStartY] #y2 = self.Model.NewIntVarFromDomain(Domain.FromValues(placementPointsEndY), f'y2.{i}') self.StartY.append(y1) #self.EndY.append(y2) intervalX = self.Model.NewFixedSizeIntervalVar( x1, item.Dx, f'xI{i}') intervalY = self.Model.NewFixedSizeIntervalVar( y1, item.Dy, f'yI{i}') self.IntervalX.append(intervalX) self.IntervalY.append(intervalY)
def CreateVariables(self): binDx = self.Bin.Dx placementPointGenerator = PlacementPointGenerator(self.Items, self.Bin) placementPatternsX, placementPatternsY = placementPointGenerator.CreatePlacementPatterns( self.placementPointStrategy, self.Items, self.Bin) for i, item in enumerate(self.Items): placementPatternX = placementPatternsX[i] x1 = self.Model.NewIntVarFromDomain( Domain.FromValues(placementPatternX), f'x1.{i}') self.StartX.append(x1) intervalX = self.Model.NewFixedSizeIntervalVar( x1, item.Dx, f'xI{i}') self.IntervalX.append(intervalX)
def Solve(self, items, itemsToFix, objectiveCoefficients, bin, timeLimit=1.0): # TODO: reformulate with FixedSizeOptionalIntervalVariables and use placement patterns. n = len(items) H = bin.Dy W = bin.Dx model = cp_model.CpModel() #preprocessing binDomains = [] fixItemToBin = [False] * len(items) for i, item in enumerate(items): binDomains.append([0]) if item.Id not in itemsToFix: binDomains[i].extend([i + 1]) else: fixItemToBin[i] = True # variables b = [] xb1 = [] xb2 = [] y1 = [] y2 = [] for i, item in enumerate(items): f = model.NewBoolVar(f'b{i}') b.append(f) yStart = model.NewIntVar(0, H - item.Dy, f'y1.{i}') yEnd = model.NewIntVar(item.Dy, H, f'y2.{i}') y1.append(yStart) y2.append(yEnd) if fixItemToBin[i]: d = model.NewIntVar(0, bin.Dx - item.Dx, f'xb1.{i}') e = model.NewIntVar(item.Dx, bin.Dx, f'xb2.{i}') xb1.append(d) xb2.append(e) else: # TODO: apply bin domains to these variables binStart = (i + 1) * bin.Dx d = model.NewIntVarFromDomain( Domain.FromIntervals([[0, bin.Dx - item.Dx], [binStart, binStart]]), f'xb1.{i}') e = model.NewIntVarFromDomain( Domain.FromIntervals( [[item.Dx, bin.Dx], [binStart + item.Dx, binStart + item.Dx]]), f'xb2.{i}') xb1.append(d) xb2.append(e) # interval variables xival = [ model.NewIntervalVar(xb1[i], items[i].Dx, xb2[i], f'xival{i}') for i in range(n) ] yival = [ model.NewIntervalVar(y1[i], items[i].Dy, y2[i], f'yival{i}') for i in range(n) ] # constraints model.AddNoOverlap2D(xival, yival) for i, item in enumerate(items): #model.AddImplication(xb2[i] <= bin.Dx, b[i]) #model.AddImplication(xb2[i] >= bin.Dx + item.Dx, b[i].Not()) #model.AddLessOrEqual(xb2[i], bin.Dx).OnlyEnforceIf(b[i]) #model.AddGreaterOrEqual(xb2[i], bin.Dx + item.Dx).OnlyEnforceIf(b[i].Not()) model.Add(xb2[i] <= bin.Dx).OnlyEnforceIf(b[i]) model.Add(xb2[i] >= bin.Dx + item.Dx).OnlyEnforceIf(b[i].Not()) #model.Add(b[i] == 1).OnlyEnforceIf(xb2[i] <= bin.Dx) #model.Add(b[i] == 0).OnlyEnforceIf(xb2[i] >= bin.Dx + item.Dx) if item.Id in itemsToFix: model.Add(b[i] == 1) #packedItems = model.NewIntVar(0, len(items), 'itemCount') #model.Add(packedItems == sum(b[i] * objectiveCoefficients[i] for i in range(len(items)))) # objective #model.Maximize(cp_model.LinearExpr.Sum(b)) #model.Maximize(cp_model.LinearExpr.Sum(sum(var for var in b))) #model.Maximize(cp_model.LinearExpr.BooleanSum(b)) #model.Maximize(packedItems) model.Maximize( sum(b[i] * int(objectiveCoefficients[item.Id]) for i, item in enumerate(items))) # solve model solver = cp_model.CpSolver() solver.parameters.log_search_progress = False solver.parameters.max_time_in_seconds = timeLimit solver.parameters.num_search_workers = 1 #with open(f"Model_{0}.txt","a") as f: # f.write(str(model.Proto())) rc = solver.Solve(model) return solver.StatusName(), solver.BestObjectiveBound( ), solver.ObjectiveValue()
def add_constraints(model): # HELPER CONSTRAINTS for f_int in range(model.max_stream_int): for v_int in range(model.max_node_int): f = model.tc.F_routed[model._IntToStreamIDMap[f_int]] v = model.tc.N[model._IntToNodeIDMap[v_int]] x_v_has_s = model.x_v_has_successor[f_int][v_int] if isinstance(v, end_system): if v.id == f.sender_es_id or v.id in f.receiver_es_ids: # Sender and Receivers have guaranteed successor model.model.Add(x_v_has_s == 1) model.model.Add( model.x[f_int][v_int] != -1).OnlyEnforceIf(x_v_has_s) else: # All other ES have guaranteed no successor model.model.Add(x_v_has_s == 0) model.model.Add(model.x[f_int][v_int] == -1).OnlyEnforceIf( x_v_has_s.Not()) else: # All switches can, but don't have to have a successor model.model.Add( model.x[f_int][v_int] != -1).OnlyEnforceIf(x_v_has_s) model.model.Add(model.x[f_int][v_int] == -1).OnlyEnforceIf( x_v_has_s.Not()) model.x_v_has_successor[f_int][v_int] = x_v_has_s # NORMAL CONSTRAINTS for f_int in range(model.max_stream_int): model.x_v_is_not_u.append([]) # model.x_v_is_u.append([]) model.x_v_is_u_and_uses_bandwidth.append([]) for v_int in range(model.max_node_int): model.x_v_is_not_u[f_int].append([]) # model.x_v_is_u[f_int].append([]) # model.x_v_is_u_and_uses_bandwidth[f_int].append([]) f = model.tc.F_routed[model._IntToStreamIDMap[f_int]] v = model.tc.N[model._IntToNodeIDMap[v_int]] # Constraint 1: x(v) != -1 => y(v) = y(x(v)) + 1 -> Avoid cycles if v.id != f.sender_es_id: # necessary, because otherwise AddElement breaks if the possible domain of a node is only -1 if (len(model.x_v_possible_domain[f.id][v.id]) > 1 or model.x_v_possible_domain[f.id][v.id][0] != -1): x_v = model.model.NewIntVarFromDomain( Domain.FromValues( model.x_v_possible_domain[f.id][v.id]), "x_{}({})_temp".format(f.id, v.id), ) model.model.Add( x_v == model.x[f_int][v_int]).OnlyEnforceIf( model.x_v_has_successor[f_int][v_int]) y_of_x_v = model.model.NewIntVar( -1, model.max_node_int, "y(x({}))_{}".format(v.id, f.id)) model.model.AddElement(x_v, model.y[f_int], y_of_x_v) y_v_is_y_x_v_plus_1 = model.model.NewBoolVar( "y({})_f_plus_one".format(v.id, f.id)) model.model.Add(model.y[f_int][v_int] == y_of_x_v + 1).OnlyEnforceIf(y_v_is_y_x_v_plus_1) model.model.Add( model.y[f_int][v_int] != y_of_x_v + 1).OnlyEnforceIf( y_v_is_y_x_v_plus_1.Not()) model.model.AddImplication( model.x_v_has_successor[f_int][v_int], y_v_is_y_x_v_plus_1) # Constraint 2: x(u) == -1 => x(v) != u # If a node has no successor, it has no predecessor # Also means: x(v) == u => x(u) != -1 (Contrapositive) (a => b, !b => !a) # If a node has a predecessor, it has a successor for u_int in range(model.max_node_int): u = model.tc.N[model._IntToNodeIDMap[u_int]] x_u_has_successor = model.x_v_has_successor[f_int][u_int] x_v_is_u = model.x_v_is_u[f_int][v_int][u_int] x_v_is_not_u = model.x_v_is_not_u[f_int][v_int][u_int] model.model.Add( model.x[f_int][v_int] != u_int).OnlyEnforceIf(x_v_is_not_u) model.model.Add( model.x[f_int][v_int] == u_int).OnlyEnforceIf(x_v_is_u) x_v_is_u_and_uses_bandwidth = model.x_v_is_u_and_uses_bandwidth[ f_int][v_int][u_int] model.model.Add(x_v_is_u_and_uses_bandwidth == 0).OnlyEnforceIf(x_v_is_not_u) # For each redundant copy of a stream, create a list of the x_v_is_u_and_uses_bandwidth boolean variables, and set their sum to 1 # This will only allow one of the redundant copies to use bandwidth on a link # sum(x_v_is_u_list) > 0 => sum(x_v_is_u_and_uses_bw) == 1 x_v_is_u_and_uses_bandwidth_list = [ x_v_is_u_and_uses_bandwidth ] x_v_is_u_list = [x_v_is_u] if f.rl > 1: for f_2 in model.tc.F_red[f.get_id_prefix()]: if f_2.id != f.id: f2_int = model._StreamIDToIntMap[f_2.id] x_v_is_u_and_uses_bandwidth_list.append( model.x_v_is_u_and_uses_bandwidth[f2_int] [v_int][u_int]) x_v_is_u_list.append( model.x_v_is_u[f2_int][v_int][u_int]) stream_uses_link = model.model.NewBoolVar( "sum_of_x_{}({})_is_{}_is_greater_0".format( f_int, v_int, u_int)) exactly_one_copy_uses_bw = model.model.NewBoolVar( "sum_of_x_{}({})_is_{}_and_uses_bw_is_1".format( f_int, v_int, u_int)) model.model.Add( sum(x_v_is_u_list) > 0).OnlyEnforceIf(stream_uses_link) model.model.Add(sum(x_v_is_u_list) == 0).OnlyEnforceIf( stream_uses_link.Not()) model.model.Add(sum(x_v_is_u_and_uses_bandwidth_list) == 1).OnlyEnforceIf(exactly_one_copy_uses_bw) model.model.Add( sum(x_v_is_u_and_uses_bandwidth_list) == 0).OnlyEnforceIf( exactly_one_copy_uses_bw.Not()) model.model.AddImplication(stream_uses_link, exactly_one_copy_uses_bw) # Constraint 7.1: Streams may not exceed link capacity # if f is not using the link, capacity use is 0 model.model.Add(model.v_to_u_capc_use_of_f[v_int][u_int][f_int] == 0).OnlyEnforceIf(x_v_is_not_u) model.model.Add(model.v_to_u_capc_use_of_f[v_int][u_int][f_int] == 0).OnlyEnforceIf( x_v_is_u_and_uses_bandwidth.Not()) # if there is link from u to v if (u.id in model.tc.N_conn_inv[v.id] and v.id in model.tc.L_from_nodes[u.id]): model.model.Add( model.v_to_u_capc_use_of_f[v_int][u_int][f_int] == int( (f.size / f.period) * 1000)).OnlyEnforceIf(x_v_is_u).OnlyEnforceIf( x_v_is_u_and_uses_bandwidth) model.model.AddImplication(x_u_has_successor.Not(), x_v_is_not_u) # Constraint 3: x(v) != -1 for all stream destinations if v.id in f.receiver_es_ids: model.model.Add(model.x[f_int][v_int] != -1) # Constraint 4: x(v) = int(v) for stream source # Constraint 5: y(v) = 0 for stream source if v.id == f.sender_es_id: model.model.Add(model.x[f_int][v_int] == v_int) model.model.Add(model.y[f_int][v_int] == 0) # Constraint 6: Redundant copies of each streams should not have common links if f.rl > 1: if v.id != f.sender_es_id: for f_2 in model.tc.F_red[f.get_id_prefix()]: if f_2.id != f.id: f_2_int = model._StreamIDToIntMap[f_2.id] model.model.Add( model.x[f_int][v_int] != model.x[f_2_int] [v_int]).OnlyEnforceIf( model.x_v_has_successor[f_int][v_int] ).OnlyEnforceIf( model.x_v_has_successor[f_2_int][v_int]) # Constraint 7.2: Streams may not exceed link capacity (upper bound is implicit through link_capacity domain) for v_int in range(model.max_node_int): for u_int in range(model.max_node_int): model.model.Add(model.link_capacity[v_int][u_int] == sum( model.v_to_u_capc_use_of_f[v_int][u_int])) for f_int in range(model.max_stream_int): f = model.tc.F_routed[model._IntToStreamIDMap[f_int]] for u_int in range(model.max_node_int): u = model.tc.N[model._IntToNodeIDMap[u_int]] x_v_is_u_list = [ model.x_v_is_u[f_int][v_int][u_int] for v_int in range(model.max_node_int) ] if not u.id in f.receiver_es_ids: model.model.Add(sum(x_v_is_u_list) == 0).OnlyEnforceIf( model.x_v_has_predecessor[f_int][u_int].Not()) model.model.Add(sum(x_v_is_u_list) > 0).OnlyEnforceIf( model.x_v_has_predecessor[f_int][u_int]) x_u_has_successor = model.x_v_has_successor[f_int][u_int] # If u has no predecessors, it can't have successor model.model.AddImplication( model.x_v_has_predecessor[f_int][u_int].Not(), x_u_has_successor.Not(), ) else: model.model.Add(model.x_v_has_predecessor[f_int][u_int] == 0)
def init_optimization_variables(model): # link capacity for v_int in range(model.max_node_int): model.v_to_u_capc_use_of_f.append([]) model.link_capacity.append([]) for u_int in range(model.max_node_int): model.v_to_u_capc_use_of_f[v_int].append([]) v_id = model._IntToNodeIDMap[v_int] u_id = model._IntToNodeIDMap[u_int] if (u_id in model.tc.N_conn_inv[v_id] and v_id in model.tc.L_from_nodes[u_id]): # If there is link from u to v l = model.tc.L_from_nodes[u_id][v_id] model.link_capacity[v_int].append( model.model.NewIntVar( 0, (int)(l.speed * 1000), "capac_v({})_u({})".format(v_id, u_id))) for f_int in range(model.max_stream_int): f_id = model._IntToStreamIDMap[f_int] model.v_to_u_capc_use_of_f[v_int][u_int].append( model.model.NewIntVar( 0, (int)(l.speed * 1000), "capac_v({})_u({})_f({})".format(v_id, u_id, f_id), )) else: model.link_capacity[v_int].append(model.model.NewConstant(0)) for f_int in range(model.max_stream_int): model.v_to_u_capc_use_of_f[v_int][u_int].append( model.model.NewConstant(0)) # x,y, ... for f_int in range(model.max_stream_int): f = model.tc.F_routed[model._IntToStreamIDMap[f_int]] model.x.append([]) model.y.append([]) model.x_v_has_successor.append([]) model.x_v_is_u.append([]) model.x_v_is_not_u.append([]) model.x_v_is_u_and_uses_bandwidth.append([]) model.x_v_has_predecessor.append([]) model.x_v_possible_domain_ids[f.id] = {} model.x_v_possible_domain[f.id] = {} for v_int in range(model.max_node_int): model.x_v_is_u[f_int].append([]) model.x_v_is_not_u[f_int].append([]) model.x_v_is_u_and_uses_bandwidth[f_int].append([]) x_u_has_no_pred = model.model.NewBoolVar( "x_{}_{}_has_no_pred".format(f_int, v_int)) model.x_v_has_predecessor[f_int].append(x_u_has_no_pred) v = model.tc.N[model._IntToNodeIDMap[v_int]] # x possible_domain = [-1] possible_domain_strings = ["-1"] for u_int in range(model.max_node_int): x_v_is_not_u = model.model.NewBoolVar( "x_{}({})_is_not_{}".format(f_int, v_int, u_int)) x_v_is_u = x_v_is_not_u.Not() model.x_v_is_not_u[f_int][v_int].append(x_v_is_not_u) model.x_v_is_u[f_int][v_int].append(x_v_is_u) x_v_is_u_and_uses_bandwidth = model.model.NewBoolVar( "x_{}({})_is_{}_and_uses_bandwidth".format( f_int, v_int, u_int)) model.x_v_is_u_and_uses_bandwidth[f_int][v_int].append( x_v_is_u_and_uses_bandwidth) for u_id in model.tc.N_conn_inv[v.id]: if u_id == v.id: if v.id == f.sender_es_id: # Only append node itself for sender possible_domain.append(model._NodeIDToIntMap[u_id]) possible_domain_strings.append(u_id) else: possible_domain.append(model._NodeIDToIntMap[u_id]) possible_domain_strings.append(u_id) model.x[f_int].append( model.model.NewIntVarFromDomain( Domain.FromValues(possible_domain), "x_{}({})".format(f.id, v.id))) model.x_v_possible_domain[f.id][v.id] = possible_domain model.x_v_possible_domain_ids[f.id][v.id] = possible_domain_strings # y model.y[f_int].append( model.model.NewIntVar(-1, model.max_node_int, "y_{}({})".format(f.id, v.id))) # has_successor x_v_has_s = model.model.NewBoolVar("x({})_{}_has_successor".format( v.id, f.id)) model.x_v_has_successor[f_int].append(x_v_has_s)
def CreateItemBinAssignmentVariables(self, items): binDomains = self.preprocess.BinDomains for i, item in enumerate(items): itemFeasibleBins = self.model.NewIntVarFromDomain(Domain.FromValues(binDomains[i]), f'b{i}') self.placedBinVariables.append(itemFeasibleBins)
def CreateLocalStartVariablesX(self, items, binDx): itemNormalPatternsX = self.preprocess.ItemPlacementPatternsX for i, item in enumerate(items): filteredStartLocalX = [p for p in itemNormalPatternsX[i] if (p % binDx) + item.Dx <= binDx] xStart = self.model.NewIntVarFromDomain(Domain.FromValues(filteredStartLocalX),f'x{i}') self.startVariablesLocalX.append(xStart)