diff --git a/__pycache__/offline.cpython-310.pyc b/__pycache__/offline.cpython-310.pyc index 96c49e067a7b75e37dcb2c9136d52054011f2260..16a3fa36a4aed55b365073a140143af5d59a233f 100644 Binary files a/__pycache__/offline.cpython-310.pyc and b/__pycache__/offline.cpython-310.pyc differ diff --git a/main.py b/main.py index c353db59d5d209f0e7b0dccee778d439405bc963..4a84c0c122907d219edd90ddfd52730c714d34b8 100644 --- a/main.py +++ b/main.py @@ -35,9 +35,6 @@ def ParseInput(src : str) -> Tuple[List[float], List[Tuple[float, float]]]: endTime = float(string[1]) blackoutCollection.append((startTime, startTime + endTime)) - print(f"image sizes: {imageCollection}") - print(f"blackouts: {blackoutCollection} \n") - return (imageCollection, blackoutCollection) def PrintOutput(filename : str, endTime : float, imageTimes : List[float]): @@ -54,19 +51,19 @@ def PrintOutput(filename : str, endTime : float, imageTimes : List[float]): OUTPUT_FILE.write(f"{str(endTime)}") OUTPUT_FILE.writelines(reduce(lambda x, y: x + "\n" + f"{str(y)}", imageTimes, "")) - print(f"\n Results written to {filename}, program finished.") + print(f"\nResults written to {filename}, program finished.") if __name__ == "__main__": - testcase = 6 + testcase = 9 parsedIn = ParseInput(f"t{testcase}_in.txt") res = LP.Solve(parsedIn[0], parsedIn[1]) output_file = "t{}_out.txt".format(testcase) PrintOutput(output_file, res[0], res[1]) """ - solved 6 (= Instance5.txt from Instances_10) correctly as 842.563 in 0.01s + 0.02s + solved 6 (= Instance5.txt from Instances_10) correctly as 842.563 in 0.03s pre-solved 7 (= test_instance_generated5.txt from Instances_4, the biggest one available) - with heuristic at 2079, optimal is 2054.747 (according to TXT) + with heuristic at 2079.164, optimal is 2054.747 (according to TXT) solved 7 in 293 seconds, but failed to write output because of ??? pre-solved 8 (= test_instance_generated3.txt from Instances_4) @@ -78,5 +75,4 @@ if __name__ == "__main__": with heuristic at 197.796, optimal is 190.055 (according to TXT) manually aborted at (189.197 ; 189.255) objective bounds after 500 seconds which is better than the reported optimal? - """ \ No newline at end of file diff --git a/offline.py b/offline.py index fb5847ecdf11b08c3a439c7268b8ae71a569bfed..e50a0c5f6bd88cf8cc3af53ee8b9b9325642d650 100644 --- a/offline.py +++ b/offline.py @@ -2,154 +2,150 @@ import gurobipy as gp from gurobipy import GRB import copy + def Solve(images, blackouts): # if there are no blackouts, just send the images in after eachother, there is no unique optimal solution - image_count = len(images) - blackout_count = len(blackouts) - if blackout_count == 0: - endtime = 0 - times = [] - for image in range(image_count): - times.append(endtime) - endtime += images[image] - print(f"No blackboxes, easy solving: end time = {endtime}, startingtimes = {times}.") - return (endtime, times) - whitebox_count = blackout_count+1 - + n = len(images) + m = len(blackouts) + if m == 0: + finish_objective = 0 + starting_times = [] + for image in range(n): + starting_times.append(finish_objective) + finish_objective += images[image] + print("No blackboxes in the instance, easy solving without LP:") + print(f"\nImage starting times: {starting_times}") + print(f"=====> END TIME: {finish_objective} <=====") + return (finish_objective, starting_times) + + whitebox_count = m+1 # extra whitebox until infinity after the last blackbox + model = gp.Model("image_scheduling") - """Refer to the Google Docs file for full explanation + """Refer to the paper for extensive elaboration + SETS / INDICES""" - br = blackouts # tuples (b^L_br, b^U_br) = [start, end) - js = images # real number p_j = image size - - white_boxes_t = calculate_whiteboxes(copy.deepcopy(blackouts)) # tuples (time_t, delta_t) = [start, duration) + f_i = images # real number = image size - print(f"br |{blackout_count}| = {br}") - print(f"\njs |{image_count}| = {js}") - print(f"\nts |{whitebox_count}| = {white_boxes_t}") + # tuples (time_t, delta_t) = [start, duration) + wb_t = calculate_whiteboxes(copy.deepcopy(blackouts)) """"DECISION VARIABLES""" - # makespan - ms = model.addVar(lb=0,vtype=GRB.CONTINUOUS) # makespan, objective function minimization value, bounded with constraints - PV_t = model.addVars(whitebox_count,lb=0,vtype=GRB.CONTINUOUS) # sum of length of images in whitebox t - X_j_t = model.addVars(image_count, whitebox_count,vtype=GRB.BINARY) # order j in whitebox t - Xfree_t = model.addVars(whitebox_count,vtype=GRB.BINARY) # whitebox t has no images in it + # objective function to minimize + makepsan = model.addVar(lb=0, vtype=GRB.CONTINUOUS) + + # Processing Volume = sum of length of images transmitted in whitebox t + PV_t = model.addVars(whitebox_count, lb=0, vtype=GRB.CONTINUOUS) + + # image i in whitebox t: yes (1)/no (0) + X_i_t = model.addVars(n, whitebox_count, vtype=GRB.BINARY) + + # whitebox t is empty (no images being transmitted): yes (1)/no (0) + Xfree_t = model.addVars(whitebox_count, vtype=GRB.BINARY) """CONSTRAINTS""" - # (1) minimize makespan - model.setObjective(ms, GRB.MINIMIZE) - - # (9) complete model finish time is when the final filled whitebox finished processing its images - model.addConstrs((ms >= (PV_t[t] + white_boxes_t[t][0]) * (1-Xfree_t[t]) - for t in range(whitebox_count)), - name="Eq(9)") - - # (17) sets Xfree_t to be (at least) 1 if there is no image in the whitebox - model.addConstrs((gp.quicksum(X_j_t[j,t] for j in range(image_count)) - + Xfree_t[t] # Xfree_t is binary, no need for another <= 1 constraint on it - >= 1 - for t in range(whitebox_count)), - name="Eq(17)") - - # (17.2) Xfree_t should be 0 if there is at least one image being processed in whitebox t - model.addConstrs((gp.quicksum(X_j_t[j,t] for j in range(image_count)) - * Xfree_t[t] - == 0 - for t in range(whitebox_count)), - name="Eq(17.2)") - - # (19) send each image exactly once - model.addConstrs((gp.quicksum(X_j_t[j,t] - for t in range (whitebox_count)) == 1 - for j in range (image_count)), - name="Eq(19)") - - # (20) total processing time in a whitebox is the sum of image lengths of images assigned to that whitebox - model.addConstrs((PV_t[t] == gp.quicksum(js[j] * X_j_t[j,t] - for j in range(image_count)) - for t in range(whitebox_count)), - name="Eq(20)") - # (20.2) but is upperbounded by the length of the whitebox - model.addConstrs((PV_t[t] <= white_boxes_t[t][1] - for t in range(whitebox_count)), - name="Eq(20.2)") - - # SOLVE AND PRINT THE RESULTS + # minimize makespan + model.setObjective(makepsan, GRB.MINIMIZE) + + # end time is when the last filled whitebox finished processing all its images + model.addConstrs((makepsan >= (PV_t[t] + wb_t[t][0]) * (1-Xfree_t[t]) + for t in range(whitebox_count))) + + # sets Xfree_t to be (at least) 1 if there is no image in the whitebox + model.addConstrs((gp.quicksum(X_i_t[i, t] for i in range(n)) + # Xfree_t is binary, no need for another <= 1 constraint on it + + Xfree_t[t] + >= 1 + for t in range(whitebox_count))) + + # Xfree_t should be 0 if there is at least one image being transmitted in whitebox t + model.addConstrs((gp.quicksum(X_i_t[i, t] for i in range(n)) + * Xfree_t[t] + == 0 + for t in range(whitebox_count))) + + # transmit each image exactly once + model.addConstrs((gp.quicksum(X_i_t[i, t] + for t in range(whitebox_count)) == 1 + for i in range(n))) + + # total transmitting time in a whitebox is the sum of image lengths of images assigned to that whitebox... + model.addConstrs((PV_t[t] == gp.quicksum(f_i[i] * X_i_t[i, t] + for i in range(n)) + for t in range(whitebox_count))) + + # ... but is upperbounded by the length of the whitebox + model.addConstrs((PV_t[t] <= wb_t[t][1] + for t in range(whitebox_count))) + + # SOLVE, RECONSTRUCT AND PRINT(optional) THE RESULTS model.optimize() - time = -1 - starts = [-1] + # dummy start times, to catch errors + end_time = -1 + image_starts = [-1] if model.status == GRB.OPTIMAL: - _ = starts.pop() - time = model.ObjVal - + _ = image_starts.pop() # remove the dummy start time + end_time = model.ObjVal + print("\nWhite box decision variables:") - for (j,t) in X_j_t: - if X_j_t[j,t].X > 0: - print(f"(im{j},wb{t}) = {X_j_t[j,t].X}") - print("All other X_j_t are 0") + for (i, t) in X_i_t: + if X_i_t[i, t].X > 0: # image i was assigned to whitebox t + print(f"Image {i} assigned to whitebox {t}") + print("All other X_i_t are 0") print('Whitebox contains:') for t in PV_t: - print(f"wb{t} is {Xfree_t[t].X} empty and holds length {PV_t[t].X} of images") + print( + f"wb{t} is {Xfree_t[t].X} empty with volume {PV_t[t].X} of images") - print('\nImage times:') timers = [0]*whitebox_count - # initialize by setting to start of whitebox for t in range(whitebox_count): - timers[t] = white_boxes_t[t][0] + timers[t] = wb_t[t][0] - print(f"Timers before={timers}") - # add the image lengths - for j in range(image_count): + for j in range(n): for t in range(whitebox_count): - if X_j_t[j,t].X == 1: - starts.append(timers[t]) - print(f"image {j} is scheduled in whitebox {t}") - timers[t] += js[j] + if X_i_t[j, t].X == 1: + image_starts.append(timers[t]) + timers[t] += f_i[j] - print(f"Timers after={timers}") - print(f"Starts={starts}") - print('Finish time: %g' % time) + print(f"\nImage starting times: {image_starts}") + print(f"=====> END TIME: {end_time} <=====") else: - print('\nNo solution, Check input or constraints! Reason:') - iis = model.computeIIS() # Irreducible Infeasible Subsystem, https://support.gurobi.com/hc/en-us/articles/360029969391-How-do-I-determine-why-my-model-is-infeasible- + print('\nNo solution: check input or constraints! Reason (gurobipy):') + iis = model.computeIIS() # Irreducible Infeasible Subsystem, https://support.gurobi.com/hc/en-us/articles/360029969391-How-do-I-determine-why-my-model-is-infeasible- print(iis) - - return (time, starts) + + return (end_time, image_starts) + def calculate_whiteboxes(blackouts): + """Given the blackouts, 'invert' them to create the whiteboxes. + Returns a list of tuples (start time, duration).""" + wb = [] - length = len(blackouts) - - if (len(blackouts) == 0): # no black boxes, so whitebox length is infinite + m = len(blackouts) + + if m == 0: # no black boxes, so whitebox length is infinite return [(0, GRB.INFINITY)] current_blackout = blackouts[0] - begin = 0 - end = current_blackout[0] - wb.append((begin, end - begin)) - - for i in range(1, len(blackouts) + 2): - begin = current_blackout[1] - if (i < length): - current_blackout = blackouts[i] - end = current_blackout[0] - wb.append((begin, end - begin)) + current_time = 0 + end_time = current_blackout[0] + wb.append((current_time, end_time - current_time)) + + for bo in range(1, len(blackouts) + 2): + current_time = current_blackout[1] + if (bo < m): + current_blackout = blackouts[bo] + end_time = current_blackout[0] + wb.append((current_time, end_time - current_time)) else: - wb.append((begin, GRB.INFINITY)) # maybe needed, or else it won't fill after the last black box + wb.append((current_time, GRB.INFINITY)) + # it won't fill images after the last black box without this extra whitebox that lasts until infinity break return wb - -def PrepareImages(numberOfImages, numberOfBlackouts): - decVariables = [] - for i in range(0, numberOfImages): - for j in range(0, numberOfBlackouts): - decVariables.append((i,j)) - - return gp.tuplelist(decVariables) \ No newline at end of file