""" tsp-mp.py: solve the multi-objective traveling salesman problem Approaches: - segmentation - ideal point - scalarization Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2012 """ import math import random from gurobipy import * def optimize(model,cand): """optimize: function for solving the model, updating candidate solutions' list Will add to cand all the intermediate solutions found, as well as the optimum Parameters: - model: Gurobi model object - cand: list of pairs of objective functions (for appending more solutions) Returns the solver's exit status """ model.Params.OutputFlag = 0 # silent mode model.optimize() x,y,C,T = model.__data status = model.Status if status == GRB.Status.OPTIMAL: for k in range(model.SolCount): model.Params.SolutionNumber = k cand.append((T.Xn,C.Xn)) # print "Adding solution",k,"with objectives:",C.Xn,T.Xn return status def base_model(n,c,t): """base_model: mtz model for the atsp, prepared for two objectives Loads two additional variables/constraints to the mtz model: - C: sum of travel costs - T: sum of travel times Parameters: - n: number of cities - c,t: alternative edge weights, to compute two objective functions Returns list of candidate solutions """ from atsp import mtz_strong model = mtz_strong(n,c) # model for minimizing cost x,u = model.__data # some auxiliary information C = model.addVar(vtype="C", name="C") # for computing solution cost T = model.addVar(vtype="C", name="T") # for computing solution time model.update() model.addConstr(T == quicksum(t[i,j]*x[i,j] for (i,j) in x), "Time") model.addConstr(C == quicksum(c[i,j]*x[i,j] for (i,j) in x), "Cost") model.update() model.__data = x,u,C,T return model def solve_segment_time(n,c,t,segments): """solve_segment: segmentation for finding set of solutions for two-objective TSP Parameters: - n: number of cities - c,t: alternative edge weights, to compute two objective functions - segments: number of segments for finding various non-dominated solutions Returns list of candidate solutions """ model = base_model(n,c,t) # base model for minimizing cost or time x,u,C,T = model.__data # store the set of solutions for plotting cand = [] # print "optimizing time" model.setObjective(T,GRB.MINIMIZE) stat1 = optimize(model,cand) # print "optimizing cost" model.setObjective(C,GRB.MINIMIZE) stat2 = optimize(model,cand) if stat1 != GRB.Status.OPTIMAL or stat1 != GRB.Status.OPTIMAL: return [] times = [ti for (ti,ci) in cand] max_time = max(times) min_time = min(times) delta = (max_time-min_time)/segments # print "making time range from",min_time,"to",max_time # add a time upper bound constraint, moving between min and max values TimeConstr = model.addConstr(T <= max_time, "TimeConstr") model.update() for i in range(segments+1): time_ub = max_time - delta*i TimeConstr.setAttr("RHS",time_ub) # print "optimizing cost subject to time <=",time_ub optimize(model,cand) return cand def solve_ideal(n,c,t,segments): """solve_ideal: use ideal point for finding set of solutions for two-objective TSP Parameters: - n: number of cities - c,t: alternative edge weights, to compute two objective functions - segments: number of segments for finding various non-dominated solutions Returns list of candidate solutions """ model = base_model(n,c,t) # base model for minimizing cost or time x,u,C,T = model.__data # store the set of solutions for plotting cand = [] # print "optimizing time" model.setObjective(T,GRB.MINIMIZE) stat1 = optimize(model,cand) # print "optimizing cost" model.setObjective(C,GRB.MINIMIZE) stat2 = optimize(model,cand) #find the minimum cost routes if stat1 != GRB.Status.OPTIMAL or stat2 != GRB.Status.OPTIMAL: return [] times = [ti for (ti,ci) in cand] costs = [ci for (ti,ci) in cand] min_time = min(times) min_cost = min(costs) # print "ideal point:",min_time,",",min_cost #=============================================================== # Objective function is f1^2 + f2^2 where f=Sum tx-min_time and g=Sum cx-min_cost f1 = model.addVar(vtype="C", name="f1") f2 = model.addVar(vtype="C", name="f2") model.update() model.addConstr(f1 == T - min_time, "obj1") model.addConstr(f2 == C - min_cost, "obj2") # print "optimizing distance to ideal point:" for i in range(segments+1): lambda_ = float(i)/segments # print lambda_ Obj = QuadExpr(lambda_*f1*f1 + (1-lambda_)*f2*f2) model.setObjective(Obj,GRB.MINIMIZE) optimize(model,cand) # find the minimum cost routes return cand def solve_scalarization(n,c,t): """solve_scalarization: scale objective function to find new point Parameters: - n: number of cities - c,t: alternative edge weights, to compute two objective functions Returns list of candidate solutions """ model = base_model(n,c,t) # base model for minimizing cost or time x,u,C,T = model.__data def explore(C1,T1,C2,T2,front): """explore: recursively try to find new non-dominated solutions with a scaled objective Parameters: - C1,T1: cost and time of leftmost point - C1,T1: cost and time of rightmost point - front: current set of non-dominated solutions Returns the updated front """ alpha = float(C1 - C2)/(T2 - T1) # print "%s,%s -- %s,%s (%s)..." % (C1,T1,C2,T2,alpha) init = list(front) model.setObjective(quicksum((c[i,j] + alpha*t[i,j])*x[i,j] for (i,j) in x),GRB.MINIMIZE) optimize(model,front) front = pareto_front(front) # print "... added %s points" % (len(front)-len(init)) if front == init: # print "no points added, returning" return front CM,TM = C.X,T.X # print "will explore %s,%s -- %s,%s and %s,%s -- %s,%s" % (C1,T1,CM,TM,CM,TM,C2,T2) if TM > T1: front = explore(C1,T1,CM,TM,front) if T2 > TM: front = explore(CM,TM,C2,T2,front) return front # store the set of solutions for plotting cand = [] # to store the set of solutions for plotting model.setObjective(T,GRB.MINIMIZE) model.update() stat = optimize(model,cand) if stat != GRB.Status.OPTIMAL: return [] C1,T1 = C.X,T.X # change the objective function to minimize the travel cost model.setObjective(C,GRB.MINIMIZE) model.update() stat = optimize(model,cand) if stat != GRB.Status.OPTIMAL: return [] C2,T2 = C.X,T.X front = pareto_front(cand) return explore(C1,T1,C2,T2,front) def distance(x1,y1,x2,y2): return math.sqrt((x2-x1)**2 + (y2-y1)**2) def make_data(n): x,y = {},{} # positions in the plane c,t = {},{} # cost, time for i in range(1,n+1): x[i] = random.random() y[i] = random.random() for i in range(1,n+1): for j in range(1,n+1): c[i,j] = distance(x[i],y[i],x[j],y[j]) t[i,j] = 1/(c[i,j]+1.0)+0.3*random.random() return c,t,x,y if __name__ == "__main__": from pareto_front import pareto_front random.seed(7) n = 20 c,t,x,y = make_data(n) print "\n\n\nmultiobjective optimization: segmentation with additional (time) constraint" segments = 6 cand_seg_time = solve_segment_time(n,c,t,segments) print "candidate solutions:" for cand in cand_seg_time: print "\t",cand front_seg_time = pareto_front(cand_seg_time) print "pareto front:",len(front_seg_time),"points out of",len(cand_seg_time) for cand in front_seg_time: print "\t",cand print "\n\n\nmultiobjective optimization: min distance to ideal point" cand_ideal = solve_ideal(n,c,t,segments) print "candidate solutions:" for cand in cand_ideal: print "\t",cand front_ideal = pareto_front(cand_ideal) print "pareto front:",len(front_ideal),"points out of",len(cand_ideal) for cand in front_ideal: print "\t",cand print "\n\n\nmultiobjective optimization: scalarization strategy" front_scalarization = solve_scalarization(n,c,t) front_scalarization.sort() print "front solutions:" for cand in front_scalarization: print "\t",cand assert front_scalarization == pareto_front(front_scalarization) try: import matplotlib.pyplot as P except: print "for graphics,install matplotlib" exit(0) P.clf() P.xlabel("cost") P.ylabel("time") P.title("Pareto front") # plot pareto front - scalarization t = [ti for (ti,ci) in front_scalarization] c = [ci for (ti,ci) in front_scalarization] P.plot(t,c,"bo",c="black") t = [ti for (ti,ci) in front_scalarization] c = [ci for (ti,ci) in front_scalarization] P.plot(t,c,c="black",lw=3,label="scalarization") # plot pareto front - segmentation (time) t = [ti for (ti,ci) in cand_seg_time] c = [ci for (ti,ci) in cand_seg_time] P.plot(t,c,"bo",c="cyan") allpoints = [(ti,ci) for (ti,ci) in cand_seg_time] t = [ti for (ti,ci) in front_seg_time] c = [ci for (ti,ci) in front_seg_time] P.plot(t,c,c="cyan",lw=3,label="segmentation (time)") # plot pareto front - -ideal point t = [ti for (ti,ci) in cand_ideal] c = [ci for (ti,ci) in cand_ideal] P.plot(t,c,"bo",c="red") allpoints = [(ti,ci) for (ti,ci) in cand_ideal] t = [ti for (ti,ci) in front_ideal] c = [ci for (ti,ci) in front_ideal] P.plot(t,c,c="red",lw=3,label="ideal point") P.legend() P.savefig("tsp_mo_pareto_ideal.pdf",format="pdf") P.show()