"""
ssp_ts.py: Construction and tabu search for the stable set problem.

The Stable Set Problem (SSP) is a combinatorial optimization problem
where: given a graph, find the maximum subset of nodes S such that
there are no edges between nodes in S.  This problem is equivalent to
finding the maximum clique in the complementary graph.

This file contains a set of functions to illustrate tabu search with
diversification/intensification

Copyright (c) by Joao Pedro PEDROSO and Mikio KUBO, 2007
"""

LOG = True      # whether or not to print intermediate solutions
import random
Infinity = 1.e10000

from graphtools import *
from ssp_ts import evaluate, construct, move_in, move_out


def diversify(nodes, adj, v):
    """Find a maximal stable set, starting from node 'v'"""
    b = [0 for i in nodes]
    sol = set([v])
    for j in adj[v]:
        b[j] += 1

    indices = list(nodes)
    random.shuffle(indices)
    for ii in nodes:
        i = indices[ii]
        if i == v:
            continue
        if b[i] == 0:
            sol.add(i)
            for j in adj[i]:
                b[j] += 1
    return sol


def ts_intens_divers(nodes, adj, sol, max_iter, tabulen, report):
    """Execute a tabu search run using intensification/diversification."""
    n = len(nodes)
    tabu = [0 for i in nodes]

    card, infeas, b = evaluate(nodes, adj, sol)
    assert infeas == 0
    bestsol, bestcard, bestb = set(sol), card, list(b)

    D = 1       # self-tuning diversification parameter
    count = 0   # counter for consecutive non-improving iterations
    lastcard = card
    if LOG:
        print "iter:", 0, "non-impr: %d/%d" % (count,D), \
              "\tcard: %d (%d conflicts)" % (card,infeas), "/ best:", bestcard # , "\t", sol
    for it in range(max_iter):
        tabuIN = 1+int(tabulen/100. * card)     # update tabu parameter for inserting vertices
        tabuOUT = 1+int(tabulen/100. * (n-card))# update tabu parameter for removing vertices
        if infeas == 0:         # solution is feasible, add a new vertex
            infeas += move_in(nodes, adj, sol, b, tabu, tabuIN, tabuOUT, it)
            card += 1
        else:           # solution is infeasible, remove a vertex
            infeas += move_out(nodes, adj, sol, b, tabu, tabuIN, tabuOUT, it)
            card -= 1

        if LOG:
            print "iter:", it+1, "non-impr: %d/%d" % (count,D), \
                  "\tcard: %d (%d conflicts)" % (card,infeas), "/ best:", bestcard # , "\t", sol

        if infeas == 0 and card > bestcard:
            # improved best found solution, intensify search
            bestsol, bestcard, bestb = set(sol), card, list(b)
            if report:
                report(card, "iter:%d" % it)
            if LOG:
                print "*** intensifying: clearing tabu list***"
            tabu = [min(tabu[i],it) for i in nodes]     # clear tabu list
            count = 0
        elif infeas == 0 and card > lastcard:
            count = 0   # reset non-improving iterations counter
        else:
            count += 1

        if count > D:   # exceeded allowed non-improving iterations, restart int/div cycle
            if D % 2 == 0:      # intensification: switch to best found solution
                if LOG:
                    print "*** intensifying: switching to best found solution ***"
                sol, card, b = set(bestsol), bestcard, list(bestb)
                infeas = 0
                # keep tabu list unchanged, for ensuring a different path
            else:       # diversification: construct maximal stable set from less used vertex
                if LOG:
                    print "*** diversifying: constructing maximal set from less used vertex ***"
                # use the tabu history as long-term memory:
                cand = []
                mintabu = Infinity
                for j in set(nodes) - sol:      # find less used vertex (smallest tabu)
                    if tabu[i] < mintabu:
                        cand = [i]
                    if tabu[i] == mintabu:
                        cand.append(i)
                v = random.choice(cand)
                
                sol = diversify(nodes,adj,v)
                card, infeas, b = evaluate(nodes, adj, sol)
                if infeas == 0 and card > bestcard:
                    bestsol, bestcard, bestb = set(sol), card, list(b)
                    if report:
                        report(card, "iter:%d" % it)
                tabu = [min(tabu[i],it) for i in nodes] # clear tabu list

            count = 0   # reset counter
            D += 1      # increase self-tuning parameter

        if infeas == 0:
            lastcard = card
            
    # check solution correctness:
    # xcard, xinfeas, xb = evaluate(nodes, adj, bestsol)
    # assert bestcard == xcard and xinfeas == 0
    return bestsol, bestcard
        

if __name__ == "__main__":
    """Tabu search for the Stable Set Problem:
         sample usage with intensification/diversification
    """
    import sys
    if len(sys.argv) == 1:
        # rndseed = 1	# uncomment for having always the same behavior
        # random.seed(rndseed)
        instance = "randomly created"
        nodes,edges = rnd_graph(100,.5)
        edges = complement(nodes,edges)
        adj = adjacent(nodes, edges)
        max_iterations = 1000
        tabulength = len(nodes)/10
    elif len(sys.argv) == 5:
        instance = sys.argv[1]
        rndseed = int(sys.argv[2])
        random.seed(rndseed)
        tabulength = float(sys.argv[3])
        max_iterations = int(sys.argv[4])
        print "reading file:", instance
        nodes,adj = read_compl_graph(instance)
    else:
        print "usage:", sys.argv[0], "instance seed tabulength iterations"
        exit(-1)

    # function for printing best found solution when it is found
    from time import clock
    init = clock()
    def report_sol(obj,s=""):
        print "cpu:%g\tobj:%g\t%s" % \
              (clock(), obj, s)

    print "*** stable set problem ***"
    print

    print "instance", instance
    sol = construct(nodes,adj)
    print "initial solution:", sol
    xcard, xinfeas, xb = evaluate(nodes, adj, sol)
    print "cardinality: %d (%d conflicts)" % (xcard, xinfeas)
    assert xinfeas == 0
    print

    print "starting tabu search"
    sol, card = ts_intens_divers(nodes, adj, sol, max_iterations, tabulength, report_sol)
    print
    print "final solution: z =", card
    print sol
    print
