"""
tsp.py:  solve the traveling salesman problem 

minimize the travel cost for visiting n customers exactly once
approach:
    - start with assignment model
    - add cuts until there are no sub-cycles

Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2012
"""
import math
import random
import networkx
from gurobipy import *

def tsp(V,c):
    """tsp -- model for solving the traveling salesman problem with callbacks
       - start with assignment model
       - add cuts until there are no sub-cycles
    Parameters:
        - V: set/list of nodes in the graph
        - c[i,j]: cost for traversing edge (i,j)
    Returns the optimum objective value and the list of edges used.
    """
    
    EPS = 1.e-6
    def tsp_callback(model,where):
        if where != GRB.Callback.MIPSOL:
            return

        edges = []
        for (i,j) in x:
            if model.cbGetSolution(x[i,j]) > EPS:
                edges.append( (i,j) )

        G = networkx.Graph()
        G.add_edges_from(edges)
        Components = networkx.connected_components(G)

        if len(Components) == 1:
            return
        for S in Components:
            model.cbLazy(quicksum(x[i,j] for i in S for j in S if j>i) <= len(S)-1)
            # print "cut: len(%s) <= %s" % (S,len(S)-1)
        return


    model = Model("tsp")
    # model.Params.OutputFlag = 0 # silent/verbose mode
    x = {}
    for i in V:
        for j in V:
            if j > i:
                x[i,j] = model.addVar(vtype="B", name="x(%s,%s)"%(i,j))
    model.update()
    
    for i in V:
        model.addConstr(quicksum(x[j,i] for j in V if j < i) + \
                        quicksum(x[i,j] for j in V if j > i) == 2, "Degree(%s)"%i)

    model.setObjective(quicksum(c[i,j]*x[i,j] for i in V for j in V if j > i), GRB.MINIMIZE)

    model.update()
    model.__data = x
    return model,tsp_callback


def distance(x1,y1,x2,y2):
    """distance: euclidean distance between (x1,y1) and (x2,y2)"""
    return math.sqrt((x2-x1)**2 + (y2-y1)**2)

def make_data(n):
    """make_data: compute matrix distance based on euclidean distance"""
    V = range(1,n+1)
    x = dict([(i,random.random()) for i in V])
    y = dict([(i,random.random()) for i in V])
    c = {}
    for i in V:
        for j in V:
            if j > i:
                c[i,j] = distance(x[i],y[i],x[j],y[j])
    return V,c
    
def solve_tsp(V,c):
    model,tsp_callback = tsp(V,c)
    model.params.DualReductions = 0
    model.optimize(tsp_callback)
    x = model.__data

    if model.status == GRB.Status.TIME_LIMIT:
        return None,None

    EPS = 1.e-6
    edges = []
    for (i,j) in x:
        if x[i,j].X > EPS:
            edges.append( (i,j) )
    return model.ObjVal,edges
            
if __name__ == "__main__":
    import sys

    # Parse argument
    if len(sys.argv) < 2:
        print "Usage: %s instance" % sys.argv[0]
        exit(1)
        # n = 200
        # seed = 1
        # random.seed(seed)
        # V,c = make_data(n)

    from read_tsplib import read_tsplib
    try:
        V,c,x,y = read_tsplib(sys.argv[1])
    except:
        print "Cannot read TSPLIB file",sys.argv[1]
        exit(1)

    obj,edges = solve_tsp(V,c)

    print
    print "Optimal tour:",edges
    print "Optimal cost:",obj
    print
