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