Subversion Repositories Remote Hare Voting

Rev

Rev 55 | Rev 59 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 55 Rev 57
Line 30... Line 30...
30
fdOut = None
30
fdOut = None
31
fTrace = 1
31
fTrace = 1
32
randcalls = 0
32
randcalls = 0
33
 
33
 
34
class Universe:
34
class Universe:
35
    id = 0                              # id of last create Universe
35
    id = 0                              # id of last created Universe
-
 
36
    pendingUniverses = []
-
 
37
    completedUniverses = []
36
 
38
 
37
    def __init__(self, p, nWinners, nBallots, quota):
39
    def __init__(self, p, nWinners, nBallots, quota):
38
        Universe.id += 1
40
        Universe.id += 1
39
        self.id = Universe.id
41
        self.id = Universe.id
40
        self.p = p
42
        self.p = p
Line 43... Line 45...
43
        self.quota = quota
45
        self.quota = quota
44
        self.winners = []
46
        self.winners = []
45
        self.losers = []
47
        self.losers = []
46
        self.tally = {}
48
        self.tally = {}
47
 
49
 
48
    def getP(self):
50
    def forkPendingWinners(self, lWin):
-
 
51
	return self.__forkPendingList(lWin, "winner")
-
 
52
 
49
        print "instance p =", self.p
53
    def forkPendingLosers(self, lMin):
-
 
54
	return self.__forkPendingList(lMin, "loser")
-
 
55
 
-
 
56
    def forkPendingRedist(self, lRedist):
-
 
57
	return self.__forkPendingList(lRedist, "redist")
-
 
58
 
-
 
59
    def __forkPendingList(self, lItems, pendingType):
-
 
60
	self.p /= len(lItems)
-
 
61
	for item in lItems[1:]:
-
 
62
	    u = Universe(self.p, self.nWinners, self.nBallots, self.quota)
50
        print "class p =", Universe.p
63
	    u.winners = self.winners[:]
-
 
64
	    u.losers = self.losers[:]
-
 
65
	    u.tally = self.tally.copy()
-
 
66
	    u.pending = item
-
 
67
	    u.pendingType = pendingType
-
 
68
	    Universe.pendingUniverses.append(u)
-
 
69
	return lItems[0]
-
 
70
 
-
 
71
def combinations(l, n):
-
 
72
    assert(n >= 0)
-
 
73
    if n == 0:
-
 
74
	yield []
-
 
75
    else:
-
 
76
	for i in range(len(l)):
-
 
77
	    for lsub in combinations(l[i+1:], n-1):
-
 
78
		yield [l[i]] + lsub
51
 
79
 
52
def trace(s):
80
def trace(s):
53
    global fTrace, fdOut
81
    global fTrace, fdOut
54
    if fTrace: print >>fdOut, s
82
    if fTrace: print >>fdOut, s
55
    return
83
    return
56
 
84
 
57
def findWinner(winners, losers, tally, quota, nWinners):
85
def findNextWinner(u):
58
    global randcalls
86
    global randcalls
59
    cWin = quota        # number of votes for highest winner
87
    cWin = u.quota	# number of votes for highest winner
60
    lWin = []           # list of candidates with highest votes
88
    lWin = []		# list of candidates with highest votes
61
    for c in tally.keys():
89
    for c in u.tally.keys():
62
        if c not in losers and c not in winners and len(tally[c]) >= cWin:
90
        if c not in u.losers and c not in u.winners and len(u.tally[c]) >= cWin:
63
            if len(tally[c]) == cWin:
91
            if len(u.tally[c]) == cWin:
64
                lWin.append(c)
92
                lWin.append(c)
65
            else:
93
            else:
66
                lWin = [c]
94
                lWin = [c]
67
                cWin = len(tally[c])
95
                cWin = len(u.tally[c])
68
    if len(lWin) == 1:
-
 
69
        return lWin[0]
-
 
70
    elif len(lWin) > 1:
96
    if len(lWin) > 0:
71
        trace("\tselecting winning candidate randomly from %s" % lWin)
-
 
72
        randcalls += 1
-
 
73
        return random.choice(lWin)
97
        return u.forkPendingWinners(lWin)
74
 
98
 
75
    # Check to see if only enough candidates remain
99
    # Check to see if only enough candidates remain
76
    ## TODO: sort by len(tally[c]) to choose larger winners first
100
    ## TODO: sort by len(tally[c]) to choose larger winners first
77
    ## randomize and count if some with equal votes
101
    ## randomize and count if some with equal votes
78
    n = 0
102
    n = 0
79
    last = ""
103
    last = ""
80
    for c in tally.keys():
104
    for c in u.tally.keys():
81
        if c not in winners and c not in losers:
105
        if c not in u.winners and c not in u.losers:
82
            last = c
106
            last = c
83
            n = n + 1
107
            n = n + 1
84
    if nWinners - len(winners) >= n:
108
    if u.nWinners - len(u.winners) >= n:
85
        trace("\tremaining winner(s) have fewer than quota (%d) ballots" %
109
        trace("\tremaining winner(s) have fewer than quota (%d) ballots" %
86
            quota)
110
            u.quota)
87
        return last
111
        return last
88
    return
112
    return
89
 
113
 
90
def redistributeWinner(winner, winners, losers, tally, quota):
114
def redistributeWinner(winner, u):
91
    global randcalls
115
    global randcalls
92
    excess = len(tally[winner]) - quota
116
    excess = len(u.tally[winner]) - u.quota
93
    if excess <= 0:
117
    if excess <= 0:
94
        trace("\tno excess ballots to redistribute")
118
        trace("\tno excess ballots to redistribute")
95
    else:
119
    else:
96
        lEffective = gatherEffectiveBallots(winner, winners, losers, tally)
120
        lEffective = gatherEffectiveBallots(winner, u)
97
        nRedistribute = min(excess, len(lEffective))
121
        nRedistribute = min(excess, len(lEffective))
98
        trace("\tredistributing %d effective of %d excess ballot(s) at random from %s" %
122
        trace("\tredistributing %d effective of %d excess ballot(s) at random from %s" %
99
              (nRedistribute, excess, winner))
123
              (nRedistribute, excess, winner))
-
 
124
 
-
 
125
## LEFT OFF HERE...
-
 
126
## 	Need to create (len(lEffective) choose nRedistribute)-1 universes.
-
 
127
##  	One solution is to set self.nRedistribute, then forkPendingRedist the lEffective ballots.
-
 
128
## 	Must wind up with nRedistribute ballots redistributed in current universe.
-
 
129
## 	u.forkPendingRedist(lEffective)
-
 
130
 
100
        for ballot in random.sample(lEffective, nRedistribute):
131
        for ballot in random.sample(lEffective, nRedistribute):
101
            randcalls += 1
132
            randcalls += 1
102
            trace("\trandom choice = %s" % ballot)
133
            trace("\trandom choice = %s" % ballot)
103
            tally[winner].remove(ballot)
134
            u.tally[winner].remove(ballot)
104
            redistributeBallot(ballot, winners, losers, tally)
135
            redistributeBallot(ballot, u)
105
            nRedistribute -= 1
136
            nRedistribute -= 1
106
    traceTally(quota, tally)
137
    traceTally(u)
107
 
138
 
108
def gatherEffectiveBallots(winner, winners, losers, tally):
139
def gatherEffectiveBallots(winner, u):
109
    lEffective = []
140
    lEffective = []
110
    for ballot in tally[winner]:
141
    for ballot in u.tally[winner]:
111
        for candidateTo in ballot:
142
        for candidateTo in ballot:
112
           if candidateTo not in winners and candidateTo not in losers:
143
           if candidateTo not in u.winners and candidateTo not in u.losers:
113
                lEffective.append(ballot)
144
                lEffective.append(ballot)
114
                break
145
                break
115
    return lEffective
146
    return lEffective
116
 
147
 
117
def redistributeBallot(ballot, winners, losers, tally):
148
def redistributeBallot(ballot, u):
118
    for candidateTo in ballot:
149
    for candidateTo in ballot:
119
        if candidateTo not in winners and candidateTo not in losers:
150
        if candidateTo not in u.winners and candidateTo not in u.losers:
120
            trace("\tto %s: %s" % (candidateTo, ballot))
151
            trace("\tto %s: %s" % (candidateTo, ballot))
121
            if not tally.has_key(candidateTo): tally[candidateTo] = []
152
            if not u.tally.has_key(candidateTo): u.tally[candidateTo] = []
122
            tally[candidateTo].append(ballot)
153
            u.tally[candidateTo].append(ballot)
123
            ballot = ""
154
            ballot = ""
124
            break
155
            break
125
    if ballot:
156
    if ballot:
126
        trace("\tineffective ballot dropped: %s" % ballot)
157
        trace("\tineffective ballot dropped: %s" % ballot)
127
 
158
 
128
def findLoser(losers, winners, tally):
159
def findNextLoser(u):
129
    global randcalls
160
    global randcalls
130
    cMin = sys.maxint  # least number of votes for candidate loser
161
    cMin = sys.maxint  # least number of votes for candidate loser
131
    lMin = []          # list of candidates with least votes
162
    lMin = []          # list of candidates with least votes
132
    for c in tally.keys():
163
    for c in u.tally.keys():
133
        if c not in losers and c not in winners and len(tally[c]) <= cMin:
164
        if c not in u.losers and c not in u.winners and len(u.tally[c]) <= cMin:
134
            if len(tally[c]) == cMin:
165
            if len(u.tally[c]) == cMin:
135
                lMin.append(c)
166
                lMin.append(c)
136
            else:
167
            else:
137
                lMin = [c]
168
                lMin = [c]
138
                cMin = len(tally[c])
169
                cMin = len(u.tally[c])
139
    if len(lMin) == 0:
170
    if len(lMin) == 0:
140
        return None
171
        return None
141
    elif len(lMin) == 1:
-
 
142
        return lMin[0]
-
 
143
    else:
172
    else: 
144
        trace("\teliminating low candidate randomly from %s" % lMin)
-
 
145
        randcalls += 1
-
 
146
        return random.choice(lMin)
173
        return u.forkPendingLosers(lMin)
147
 
174
 
148
def redistributeLoser(loser, losers, winners, tally, quota):
175
def redistributeLoser(loser, u):
149
    excess = len(tally[loser])
176
    excess = len(u.tally[loser])
150
    if excess <= 0:
177
    if excess <= 0:
151
        trace("\tno ballots to redistribute")
178
        trace("\tno ballots to redistribute")
152
    else:
179
    else:
153
        trace("\tredistributing %d ballot(s) from %s" % (excess, loser))
180
        trace("\tredistributing %d ballot(s) from %s" % (excess, loser))
154
        while len(tally[loser]) > 0:
181
        while len(u.tally[loser]) > 0:
155
            ballot = tally[loser][0]
182
            ballot = u.tally[loser][0]
156
            tally[loser] = tally[loser][1:]
183
            u.tally[loser] = u.tally[loser][1:]
157
            redistributeBallot(ballot, winners, losers, tally)
184
            redistributeBallot(ballot, u)
158
    traceTally(quota, tally)
185
    traceTally(u)
159
    return
186
    return
160
 
187
 
161
def traceTally(quota, tally):
188
def traceTally(u):
162
    global fTrace
189
    global fTrace
163
    if not fTrace: return
190
    if not fTrace: return
164
    trace("\nCURRENT ASSIGNMENT OF BALLOTS (%d needed to win)" % quota)
191
    trace("\nCURRENT ASSIGNMENT OF BALLOTS (%d needed to win)" % u.quota)
165
    for candidate in tally.keys():
192
    for candidate in u.tally.keys():
166
        trace("\t%s:" % candidate)
193
        trace("\t%s:" % candidate)
167
        for ballot in tally[candidate]:
194
        for ballot in u.tally[candidate]:
168
            trace("\t\t%s" % ballot)
195
            trace("\t\t%s" % ballot)
169
    return
196
    return
170
 
197
 
171
# The basic Single Transferable Vote algorithm with Hare quota
198
# The basic Single Transferable Vote algorithm with Hare quota
172
#
199
#
Line 182... Line 209...
182
    randcalls = 0
209
    randcalls = 0
183
 
210
 
184
    nBallots=len(ballots)
211
    nBallots=len(ballots)
185
    quota=int(math.ceil((nBallots + 1.0)/(nWinners + 1)))
212
    quota=int(math.ceil((nBallots + 1.0)/(nWinners + 1)))
186
 
213
 
187
    universe = Universe(1.0, nWinners, nBallots, quota)
214
    u = Universe(1.0, nWinners, nBallots, quota)
188
 
215
 
189
    trace("INPUT SUMMARY AND RUN TRACE")
216
    trace("INPUT SUMMARY AND RUN TRACE")
190
    trace("\t%d ballots" % universe.nBallots)
217
    trace("\t%d ballots" % u.nBallots)
191
    trace("\tChoosing %s winners" % universe.nWinners)
218
    trace("\tChoosing %s winners" % u.nWinners)
192
    trace("\tNeed ceil((%d + 1)/(%d + 1)) = %d ballots to win" %
219
    trace("\tNeed ceil((%d + 1)/(%d + 1)) = %d ballots to win" %
193
          (universe.nBallots, universe.nWinners, universe.quota))
220
          (u.nBallots, u.nWinners, u.quota))
194
 
221
 
195
    # Create initial tally and statistics
222
    # Create initial tally and statistics
196
    #
223
    #
197
    for ballot in ballots:
224
    for ballot in ballots:
198
        candidate = ballot[0]
225
        candidate = ballot[0]
199
        if not universe.tally.has_key(candidate):
226
        if not u.tally.has_key(candidate):
200
            universe.tally[candidate] = []
227
            u.tally[candidate] = []
201
        universe.tally[candidate].append(ballot)
228
        u.tally[candidate].append(ballot)
202
 
229
 
203
    total1st = {}
230
    total1st = {}
204
    total2nd = {}
231
    total2nd = {}
205
    totalMrk = {}
232
    totalMrk = {}
206
 
233
 
Line 220... Line 247...
220
    trace("\n\tBALLOT MARKS\t1ST\t2ND\tNONE")
247
    trace("\n\tBALLOT MARKS\t1ST\t2ND\tNONE")
221
    candidates = totalMrk.keys()
248
    candidates = totalMrk.keys()
222
    candidates.sort()
249
    candidates.sort()
223
    for c in candidates:
250
    for c in candidates:
224
        trace("\t%-16s%3d\t%3d\t%4d" % (c, total1st[c], total2nd[c], len(ballots)-totalMrk[c]))
251
        trace("\t%-16s%3d\t%3d\t%4d" % (c, total1st[c], total2nd[c], len(ballots)-totalMrk[c]))
225
    traceTally(universe.quota, universe.tally)
252
    traceTally(u)
226
 
253
 
227
    continueTally(universe)
254
    continueTally(u)
228
    return universe.winners
255
    return u.winners
229
 
256
 
230
def continueTally(universe):
257
def continueTally(u):
231
    while len(universe.winners) < universe.nWinners:
258
    while len(u.winners) < u.nWinners:
232
        winner = findWinner(universe.winners, universe.losers, universe.tally, universe.quota, universe.nWinners)
259
        winner = findNextWinner(u)
233
        if winner:
260
        if winner:
234
            universe.winners.append(winner)
261
            u.winners.append(winner)
235
            trace("\nSELECTION #%d: %s" % (len(universe.winners), winner))
262
            trace("\nSELECTION #%d: %s" % (len(u.winners), winner))
236
            redistributeWinner(winner, universe.winners, universe.losers, universe.tally, universe.quota)
263
            redistributeWinner(winner, u)
237
        else:
264
        else:
238
            loser = findLoser(universe.losers, universe.winners, universe.tally)
265
            loser = findNextLoser(u)
239
            if loser:
266
            if loser:
240
                universe.losers.append(loser)
267
                u.losers.append(loser)
241
                trace("\nELIMINATED: %s" % loser)
268
                trace("\nELIMINATED: %s" % loser)
242
                redistributeLoser(loser, universe.losers, universe.winners, universe.tally, universe.quota)
269
                redistributeLoser(loser, u)
243
            else:
270
            else:
244
                trace("Not enough chosen candidates to fill all positions")
271
                trace("Not enough chosen candidates to fill all positions")
245
                break
272
                break
246
    trace("\nNumber of random choices made: %d" % randcalls)
273
    trace("\nNumber of random choices made: %d" % randcalls)