class CompactFormulation: """ This class implements the proposed formulation using Gurobi. """ def __init__(self, inst, big_m=None, murray_rules=False): if not big_m: big_m = inst.big_m if inst.big_m else 1000 model = Model() L = Ell(inst) x = {(i, j, ell): model.add_var(obj=0, var_type=BINARY, name="x({i},{j}__{ell})".format(**locals())) for (i, j) in inst.A for ell in L.list} y = {(i, j, k, ell, ell_): model.add_var(obj=0, var_type=BINARY, name="y({i},{j},{k}__{ell},{ell_})".format(**locals())) for (i, j, k) in inst.D for ell in L.list for ell_ in L.list_ell_prime(ell)} t = [model.add_var(obj=0, name="t({ell})".format(**locals())) for ell in L.list] t[0].ub = 0 t.append(model.add_var(obj=1, name="t_total".format(**locals()))) # depot constraints model.add_constr(xsum(x[0, j, 0] for j in inst.V if (0, j) in inst.A) == xsum(x[j, 0, ell] for j in inst.V if (j, 0) in inst.A for ell in L.list[1:]), "depot") model.add_constr(xsum(x[0, j, 0] for j in inst.V if (0, j) in inst.A) == 1, "depot_eq_1") # single customer visit for i in inst.V: model.add_constr(xsum(x[i, j, ell] for j in inst.V if (i, j) in inst.A for ell in L.list) <= 1, "single_position_in({i})".format(**locals())) model.add_constr(xsum(x[j, i, ell] for j in inst.V if (j, i) in inst.A for ell in L.list) <= 1, "single_position_out({i})".format(**locals())) # flow preservation constraints for k in inst.V_: for ell in L.list[1:]: model.add_constr(xsum(x[j, k, ell - 1] for j in inst.V if (j, k) in inst.A) == xsum(x[k, j, ell] for j in inst.V if (k, j) in inst.A), "flow({k},{ell})".format(**locals())) # one arc per position constraints for ell in L.list: model.add_constr(xsum(x[i, j, ell] for (i, j) in inst.A) <= 1, "one_arc({ell})".format(**locals())) # one drone per position constraints for ell in L.list: model.add_constr(xsum(y[i, k, j, l, l_] for (i, k, j) in inst.D for l in L.list[:ell + 1] for l_ in L.list[ell + 1:l + 1 + L.max_l]) <= 1, "one_drone({ell})".format(**locals())) # all customers must be visited constraints for k in inst.V_: model.add_constr(xsum(x[k, j, l] for j in inst.V if (k, j) in inst.A for l in L.list) + xsum(y[i, k, j, l, l_] for i in inst.V for j in inst.V if (i, k, j) in inst.D for l in L.list for l_ in L.list_ell_prime(l)) == 1, "all_customers({k})".format(**locals())) # drone launch constraints for i in inst.V: for ell in L.list: model.add_constr(xsum(y[i, k, j, ell, l_] for k in inst.V_ for j in inst.V if (i, k, j) in inst.D for l_ in L.list_ell_prime(ell)) <= xsum(x[i, j, ell] for j in inst.V if (i, j) in inst.A), "drone_launch({i},{ell})".format(**locals())) # drone return constraints for j in inst.V: for ell_ in L.list[1:]: model.add_constr(xsum(y[i, k, j, l, ell_] for i in inst.V for k in inst.V_ if (i, k, j) in inst.D for l in L.list_ell(ell_)) <= xsum(x[i, j, ell_ - 1] for i in inst.V if (i, j) in inst.A), "drone_return({j},{ell_})".format(**locals())) # maximum drone endurance constraints for idx, ell in enumerate(L.list[1:]): if murray_rules: # and idx == 0: continue for ell_ in L.list_ell_prime(ell): model.add_constr(t[ell_] - t[ell] <= inst.E + big_m * (1 - xsum(y[i, k, j, ell, ell_] for (i, k, j) in inst.D)), "endurance({ell},{ell_})".format(**locals())) # time constraints considering only the truck and drone setup time for ell in L.list[1:] + [len(L.list)]: model.add_constr(t[ell] >= t[ell - 1] + xsum(inst.tau_truck[i][j] * x[i, j, ell - 1] for (i, j) in inst.A) + inst.sl * (xsum(y[i, k, j, ell - 1, ell_] for (i, k, j) in inst.D for ell_ in L.list_ell_prime(ell - 1) if i != 0)) + inst.sr * (xsum(y[i, k, j, ell_, ell] for (i, k, j) in inst.D for ell_ in L.list_ell(ell))), "time_truck_only({ell})".format(**locals())) # time constraints accounting drone's time for ell_ in L.list[1:]: for ell in L.list_ell(ell_): model.add_constr(t[ell_] >= t[ell] + xsum((inst.tau_drone[i][k] + inst.tau_drone[k][j] + (0 if murray_rules else inst.sl) + inst.sr) * y[i, k, j, ell, ell_] for (i, k, j) in inst.D), "time_drones({ell},{ell_})".format(**locals())) # creating class variables self.inst = inst self.big_m = big_m self.murray_rules = murray_rules self.model = model self.L = L self.x = x self.y = y self.t = t self.solution = None def optimize(self, timelimit, sol=None): """ Solves the compact formulation considering the time limit and the initial solution passed as arguments """ if sol: mip_start = [] # reading initial solution file initial_solution = Solution(self.inst, self.murray_rules) initial_solution.read(sol) # setting initial truck path i = initial_solution.truck_path[0] for ell, j in enumerate(initial_solution.truck_path[1:]): mip_start.append((self.x[i, j, ell], 1.0)) i = j # setting initial drone paths for (i, j, k) in initial_solution.drone_paths: ell = initial_solution.truck_path.index(i) ell_ = initial_solution.truck_path.index(k) if k != 0 else len(initial_solution.truck_path) - 1 mip_start.append((self.y[i, j, k, ell, ell_], 1.0)) self.model.start = mip_start self.model.write("logs/fstsp.lp") self.model.optimize(max_seconds=timelimit) # creating final solution self.solution = Solution(self.inst, cost=self.model.objective_value, murray_rules=self.murray_rules) for key in [key for key in self.x.keys() if self.x[key].x > EPS]: self.solution.add_truck_arc((key[0], key[1])) for key in [key for key in self.y.keys() if self.y[key].x > EPS]: self.solution.add_drone_visit((key[0], key[1], key[2]))
class MurrayFormulation: def __init__(self, inst, big_m=5000): model = Model() x = {(i, j): model.add_var(obj=0, var_type=BINARY, name='x({i},{j})'.format(**locals())) for (i, j) in inst.A} y = {(i, k, j): model.add_var(obj=0, var_type=BINARY, name='y({i},{k},{j})'.format(**locals())) for (i, k, j) in inst.D} u = {i: model.add_var(obj=0, var_type=INTEGER, lb=1, ub=len(inst.V_) + 2, name="u({i})".format(**locals())) for i in inst.V} p = {(i, j): model.add_var(obj=0, var_type=BINARY, lb=1 if i == 0 else 0, name="p({i},{j})".format(**locals())) for (i, j) in inst.A} t = {i: model.add_var(obj=0 if i != inst.V[-1] else 1, lb=0, name='t({i})'.format(**locals())) for i in inst.V} t[0].ub = 0 t_ = {i: model.add_var(obj=0, lb=0, name='t_prime({i})'.format(**locals())) for i in inst.V} t_[0].ub = 0 model.add_constrs((quicksum(x[i, j] for i in inst.V[:-1] if (i, j) in inst.A) + quicksum(y[i, j, k] for i in inst.V[:-1] for k in inst.V[1:] if (i, j, k) in inst.D) == 1 for j in inst.V_), "c2") model.add_constr(quicksum(x[0, j] for j in inst.V[1:] if (0, j) in inst.A) == 1, "c3") model.add_constr(quicksum(x[i, inst.V[-1]] for i in inst.V[:-1] if (i, inst.V[-1]) in inst.A) == 1, "c4") model.add_constrs((u[i] - u[j] + 1 <= (len(inst.V_) + 2) * (1 - x[i, j]) for (i, j) in inst.A if i != 0), "c5") model.add_constrs((quicksum(x[i, j] for i in inst.V[:-1] if (i, j) in inst.A) == quicksum(x[j, k] for k in inst.V[1:] if (j, k) in inst.A) for j in inst.V_), "c6") model.add_constrs((quicksum(y[i, j, k] for j in inst.V_ for k in inst.V[1:] if i != j != k and (i, j, k) in inst.D) <= 1 for i in inst.V[:-1]), "c7") model.add_constrs((quicksum(y[i, j, k] for i in inst.V[:-1] for j in inst.V_ if i != j != k and (i, j, k) in inst.D) <= 1 for k in inst.V[1:]), "c8") model.add_constrs((2 * y[i, j, k] <= quicksum(x[h, i] for h in inst.V[:-1] if (h, i) in inst.A) + quicksum(x[l, k] for l in inst.V_ if (l, k) in inst.A) for i in inst.V_ for j in inst.V_ for k in inst.V[1:] if (i, j, k) in inst.D), "c9") model.add_constrs((y[0, j, k] <= quicksum(x[h, k] for h in inst.V[:-1] if (h, k) in inst.A) for j in inst.V_ for k in inst.V[1:] if (0, j, k) in inst.D), "c10") model.add_constrs((u[k] - u[i] >= 1 - (len(inst.V_) + 2) * (1 - quicksum(y[i, j, k] for j in inst.V_ if (i, j, k) in inst.D)) for i in inst.V_ for k in inst.V[1:] if (i, k) in inst.A), "c11") model.add_constrs((t_[i] >= t[i] - big_m * (1 - quicksum(y[i, j, k] for j in inst.V_ for k in inst.V[1:] if i != j != k and (i, j, k) in inst.D)) for i in inst.V_), "c12") model.add_constrs((t_[i] <= t[i] + big_m * (1 - quicksum(y[i, j, k] for j in inst.V_ for k in inst.V[1:] if i != j != k and (i, j, k) in inst.D)) for i in inst.V_), "c13") model.add_constrs((t_[k] >= t[k] - big_m * (1 - quicksum(y[i, j, k] for i in inst.V[:-1] for j in inst.V_ if i != j != k and (i, j, k) in inst.D)) for k in inst.V[1:]), "c14") model.add_constrs((t_[k] <= t[k] + big_m * (1 - quicksum(y[i, j, k] for i in inst.V[:-1] for j in inst.V_ if i != j != k and (i, j, k) in inst.D)) for k in inst.V[1:]), "c15") model.add_constrs((t[k] >= t[h] + inst.tau_truck[h][k] + inst.sl * (quicksum(y[k, l, m] for l in inst.V_ for m in inst.V[1:] if k != l != m and (k, l, m) in inst.D)) + inst.sr * (quicksum(y[i, j, k] for i in inst.V[:-1] for j in inst.V_ if i != j != k and (i, j, k) in inst.D)) - (big_m * (1 - x[h, k])) for h in inst.V[:-1] for k in inst.V[1:] if k != h), "c16") model.add_constrs((t_[j] >= t_[i] + inst.tau_drone[i][j] - big_m * (1 - quicksum(y[i, j, k] for k in inst.V[1:] if (i, j, k) in inst.D)) for j in inst.V_drone for i in inst.V[:-1] if i != j), "c17") model.add_constrs((t_[k] >= t_[j] + inst.tau_drone[j][k] + inst.sr - big_m * (1 - quicksum(y[i, j, k] for i in inst.V[:-1] if (i, j, k) in inst.D)) for j in inst.V_drone for k in inst.V[1:] if j != k), "c18") model.add_constrs((t_[k] - (t_[j] - inst.tau_drone[i][j]) <= inst.E + big_m * (1 - y[i, j, k]) for (i, j, k) in inst.D), "c19") model.add_constrs((u[i] - u[j] >= 1 - (len(inst.V_) + 2) * p[i, j] for i in inst.V_ for j in inst.V_ if j != i), "c20") model.add_constrs((u[i] - u[j] <= -1 + (len(inst.V_) + 2) * (1 - p[i, j]) for i in inst.V_ for j in inst.V_ if j != i), "c21") model.add_constrs((p[i, j] + p[j, i] == 1 for i in inst.V_ for j in inst.V_ if j != i), "c22") model.add_constrs((t_[l] >= t_[k] - big_m * (3 - quicksum(y[i, j, k] for j in inst.V_ if j != l and (i, j, k) in inst.D) - quicksum(y[l, m, n] for m in inst.V_ for n in inst.V[1:] if m != i and m != k and m != l and n != i and n != k and (l, m, n) in inst.D) - p[i, l]) for i in inst.V[:-1] for k in inst.V[1:] for l in inst.V_ if k != i and l != i and l != k), "c23") # creating class variables self.inst = inst self.big_m = big_m self.murray_rules = True self.model = model self.x = x self.y = y self.p = p self.u = u self.t = t self.t_ = t_ self.solution = None def optimize(self, timelimit, sol=None): """ Solves the compact formulation considering the time limit given as argument. """ if sol: mip_start = [] # reading initial solution file initial_solution = Solution(self.inst, murray_rules=self.murray_rules) initial_solution.read(sol) # setting initial truck path i = initial_solution.truck_path[0] for ell, j in enumerate(initial_solution.truck_path[1:]): j = j if j > 0 else self.inst.V[-1] mip_start.append((self.x[i, j], 1.0)) i = j # setting initial drone paths for (i, j, k) in initial_solution.drone_paths: k = k if k > 0 else self.inst.V[-1] mip_start.append((self.y[i, j, k], 1.0)) self.model.start = mip_start self.model.write('logs/murray_fstsp.lp') self.model.optimize(max_seconds=timelimit) # creating final solution self.solution = Solution(self.inst, cost=self.model.objective_value, murray_rules=self.murray_rules) for key in [key for key in self.x.keys() if self.x[key].x > EPS]: self.solution.add_truck_arc((key[0], key[1] if key[1] != self.inst.V[-1] else 0)) for key in [key for key in self.y.keys() if self.y[key].x > EPS]: self.solution.add_drone_visit((key[0], key[1], key[2] if key[2] != self.inst.V[-1] else 0))