Welcome, guest | Sign In | My Account | Store | Cart
# Author: James Collins

# Purpose: To allocate shares of cost amongst

# a group of friends who benifit by mutual cooperation.



from __future__ import division

from random import randint

from math import factorial

from copy import copy



# Travelling salesman solver

# global variables for tsp

qstart = 0 

qend = 0

queue = []

bestCost = 0

bestTrack = ''

def tsp(nodes, start):

    global qstart

    global qend

    global queue

    global bestCost

    global bestTrack

    n = len(nodes)

    if n == 0:

        return [], 0

    qstart = 0

    qend = n - 1

    queue   = copy(nodes) #range(n)

    bestCost = 9999999 # a very large number

    bestTrack = ''

    def recur(head, last, CostSoFar, track):

        global bestCost

        global bestTrack

        for j in  range( n-head):

            node = gett()

            track2 = track + node.initial

            NewCost = CostSoFar + metric(last, node)

            if NewCost < bestCost:

                if head < n-2:

                   recur(head+1, node, NewCost, track2)

                else:

                    node2 = gett()

                    finalCost = NewCost + metric(node, node2)

                    if finalCost < bestCost:

                        bestCost = finalCost

                        bestTrack = track2 + node2.initial

                    putt(node2)

            putt(node)

    def gett():

        global qstart

        x = queue[qstart]

        qstart += 1

        if qstart == n:

           qstart = 0

        return x

    def putt(x):

        global queue

        global qend

        qend += 1

        if qend == n:

           qend = 0

        queue[qend] = x

    recur(0, start, 0, '') # exhaustive search of all permutations

    return bestTrack, bestCost



# MAIN PROGRAM

people = []

class person:

    def __init__(self, name, x, y):

        self.name     = name

        self.initial  = name[0]

        self.x        = x # house location North/South

        self.y        = y # house location East/West

        self.bitplace = pow(2, len(people))

    def isElementOf(self, n): # If the ith bit in the index of

        # powerset (base 2)is 1 then it contains the ith person,

        # or else it dosnt.                

        if self.bitplace & n <> 0:

           return True

        return False                



def metric(a,b): # city grid

    return abs(a.x - b.x) + abs(a.y - b.y)



   

# random locations are being used for test purposes.

# Cost of travel is asssumed to be proportional to distance



pub = person('Shakespear', 5, 5)

people.append(person('Anne',   randint(1, 10), randint(1, 10)))

people.append(person('Brian',  randint(1, 10), randint(1, 10)))

people.append(person('Cindy',  randint(1, 10), randint(1, 10)))

people.append(person('David',  randint(1, 10), randint(1, 10)))

people.append(person('Elaine', randint(1, 10), randint(1, 10)))

#people.append(person('Francis', randint(1, 10), randint(1, 10)))

#people.append(person('Girlee', randint(1, 10), randint(1, 10)))

#people.append(person('Him', randint(1, 10), randint(1, 10)))

#people.append(person('Indy', randint(1, 10), randint(1, 10)))

#people.append(person('Jim', randint(1, 10), randint(1, 10)))

#people.append(person('Kathy', randint(1, 10), randint(1, 10))) # On my 3Gz Dual Core this takes 3min, mostly solving tsp



lenPeople = len(people)

class solution:

    def __init__(self, coalition):

        self.coalition = coalition

        self.size   = len(coalition)

        self.index  = len(powerset)

        self.split  = []

        if len(coalition) == 1: # individual

            self.cost = metric(coalition[0], pub)

            self.trk  = coalition[0].initial

            coalition[0].cost = self.cost

        else:

            self.trk, self.cost = tsp(coalition, pub)

        print 'route: ', self.trk, 'cost>',self.cost



powerset = []

# The size of the problem increases exponentialy

# w.r.t the number of people.

# Fortunately only about four people can fit in a taxi.

for i in range(pow(2, lenPeople)): # itterating over the power set

    newset = []

    for j in people:

        if j.isElementOf(i):

            newset.append(j)

    powerset.append(solution(newset))



def ShapleySub(q, p, setindex, personbit):

    s = q.size

    t = factorial(s) * factorial(lenPeople - s - 1)

    v = powerset[setindex + personbit]

    return t * ( v.cost - q.cost)

# Note: The Shapley value method assumes that there is no

# incentive for any party to defect from the grand

# coalition. This may not be true in this scenario as it

# may be more efficient for parties to travel seperately.

# In this aproach I calculate the minimum possible cost for the grand coalition

# allowing for seperate taxi travel and assign cost portions to each person

# as if there were no strategic politicing.

# For example if A can travel with B or C, but

# A, B and C cannot travel together. Both B and C are advantaged and A pays less

# than choosing to travel with either. So even if B ends up travelling home alone

# his travel is discounted by the others because of his ability to dispute

# the status quoe.



# test if union cost > sum cost of partitions

for setSize in range(2, lenPeople + 1):

    for p in powerset:

        if p.size == setSize:

            for i in range(1, pow(2, p.size-1)): # itterating all possible splits

                subset0s = 0

                subset1s = 0

                for j in range(p.size):

                    k = pow(2, j)

                    if k & i == 0: # note this coresponds to the bit places of the coalition not to the people list.

                       subset0s += p.coalition[j].bitplace # bitplace coresponds to the people list

                    else:

                       subset1s += p.coalition[j].bitplace

                if powerset[subset0s].cost + powerset[subset1s].cost < p.cost:

                   # subsets gain more by travelling seperately

                   print 'New costing for', p.trk, powerset[subset0s].cost + powerset[subset1s].cost, 'down from', p.cost

                   p.cost = powerset[subset0s].cost + powerset[subset1s].cost

                   p.split = [subset0s, subset1s]



                            

print 'Shapley values'

for p in people:

    Shapley = 0.0

    personbit = p.bitplace

    for q in powerset:

        setindex = q.index

        if not(p.isElementOf( setindex)):

            Shapley += ShapleySub(q, p, setindex, personbit)

    Shapley = Shapley / factorial(lenPeople)

    diff = p.cost - Shapley

    if p.cost > 0:

        perc = int(diff / p.cost * 100)

    else:

        perc = 0

    print "%s pays %6.2f saves %6.2f saving %d percent." %(p.name, Shapley, diff, perc) 



# Draws map

print

print '#######################'

for i in range(1, 11):

    line = '# '

    for j in range(1, 11):

         match = False 

         if i == pub.x and j == pub.y:

             line += pub.initial

             match = True

         for p in people:

             if p.x == i and p.y == j:

                 line += p.initial

                 match = True

         if not match:

             line += '. '

         else:

             line += ' '

    line += '#'

    print line

print '#######################'    



def AllocateTaxis(pset):

    if len(pset.split) == 0:

       print pset.trk, pset.cost

    else:

       AllocateTaxis(powerset[pset.split[0]])

       AllocateTaxis(powerset[pset.split[1]])

print

print 'Allocating taxis'

AllocateTaxis(powerset[len(powerset)-1])

History