-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelperfunctions.py
388 lines (315 loc) · 10.4 KB
/
helperfunctions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
from __future__ import division
from math import factorial, ceil
from fractions import Fraction
from itertools import product, combinations, permutations, chain
from sys import stderr
from copy import deepcopy
#PARAMS
n=3
m=5
# this sets the starting and end value, for if you want more than 1 value of k. m-1 and m give only k=m-1
k0 = m-1
k1 = m
# this has to remain this way, lots of dependency on this, change k0 and k1
ks = range(k0, k1)
# necessary for converting python objects to ints (which pylgl wants)
# also for transofmring back
def allVoters():
return range(n)
def allAlternatives():
return range(m)
def allBallots():
r = product(*([[0,1]]*m))
return r
def allProfiles():
return product(*([list(allBallots())]*n))
def allCommittees():
for b in allBallots():
if sum(b) in ks:
yield b
def allCommitteesOfSize(k):
for c in combinations(range(m), k):
com = [0]*m
for i in c:
com[i] = 1
yield tuple(com)
def allWinningSets():
s = list(allCommittees())
# from itertools recipe for powerset
for W in chain.from_iterable(combinations(s, r) for r in range(len(s)+1)):
#only non-empty winning sets are allowed
if W:
yield W
def union(p):
union = []
for thing in zip(*p):
if 1 in thing:
union.append(1)
else:
union.append(0)
return tuple(union)
def singletonBallot(a):
ballot = [0]*m
ballot[a] = 1
return tuple(ballot)
def permuteBallot(ballot, perm):
permBallot = []
for alt in allAlternatives():
permBallot.append(ballot[perm[alt]])
return tuple(permBallot)
def candidatePerms(profile, committee):
perms = permutations(allAlternatives())
permProfiles = []
for perm in perms:
permProfile = []
for ballot in profile:
permBallot = permuteBallot(ballot, perm)
permProfile.append(permBallot)
permCommittee = permuteBallot(committee, perm)
permProfiles.append((tuple(permProfile), permCommittee))
return permProfiles
def approves(voter, alternative, profile):
return profile[voter][alternative]
def ivariants(voter, profile):
ivar = list(profile)
for ballot in allBallots():
result = deepcopy(ivar)
result[voter] = ballot
yield tuple(result)
def approvalCount(voter, profile, committee):
c=0
for vote,com in zip(profile[voter], committee):
if vote and com:
c+=1
return c
def ballotUnion(ballot1, ballot2):
result = []
for b1,b2 in zip(ballot1, ballot2):
if b1 or b2:
result.append(1)
else:
result.append(0)
return tuple(result)
# Note: only works for (3, 4, 3)
def largestCoalition(profile):
coal_1 = list(combinations(allVoters(), 1))
coal_2 = list(combinations(allVoters(), 2))
poss_coalitions = coal_1 + coal_2 + [tuple(allVoters())]
largest_coal = []
largest_union = (0,)*m
largest_count = 0
prev_union = (0,)*m
for coal in poss_coalitions:
b_union = (0,)*m
possible = True
for i in coal:
if sum(profile[i]) == 0:
possible = False
b_union = ballotUnion(b_union, profile[i])
if possible and sum(b_union) <= len(coal):
if len(coal) == len(largest_coal):
largest_count += 1
if len(coal) > len(largest_coal):
if largest_count == 1:
prev_union = largest_union
largest_count = 1
largest_coal = coal
largest_union = b_union
if largest_count > 1:
largest_union = prev_union
return largest_union
"""p_union = union(profile)
if(sum(p_union) < 4 and sum(profile[0]) != 0 and sum(profile[1]) != 0 and sum(profile[2]) != 0):
return p_union
for v1, v2 in [[0,1], [1,2], [0,2]]:
b_union = ballotUnion(profile[v1], profile[v2])
if sum(b_union) <= 2 and sum(profile[v1]) != 0 and sum(profile[v2]) != 0:
return b_union
for v in range(3):
b_single = profile[v]
if sum(b_single) == 1:
return b_single
return (0,0,0,0)"""
def isSubsetOf(ballot1, ballot2, strict=True):
for b1,b2 in zip(ballot1, ballot2):
if b1 and not b2:
return False
if b2 and not b1:
strict = False
if strict:
return False
else:
return True
def intersection(committee, ballot):
result = []
for c,v in zip(committee, ballot):
if c and v:
result.append(1)
else:
result.append(0)
return tuple(result)
def strictlyBetter(voter, committee1, committee2, profile):
"""
Returns True if committee 2 is strictly better than committee 1, for a
certain voter according to a certain (truthful for i) ballotprofile
"""
successes1 = intersection(committee1, profile[voter])
successes2 = intersection(committee2, profile[voter])
return intersection(successes1, successes2) == successes1 and successes2 != successes1
def weaklyBetter(voter, committee1, committee2, profile):
"""
Returns True if committee 2 is weakly better than committee 1, for a
certain voter according to a certain (truthful for i) ballotprofile
"""
successes1 = intersection(committee1, profile[voter])
successes2 = intersection(committee2, profile[voter])
return intersection(successes1, successes2) == successes1
def isPartylistProfile(profile):
"""
A profile is a party-list profile iff, after removing duplicate ballots,
there is no more overlap in the ballots
"""
unique_ballots = set(profile)
for e in zip(*unique_ballots):
if sum(e)>1:
return False
return True
def isSemiPartylistProfile(profile, a):
for ballot in profile:
if not (ballot[a] == 0 or (sum(ballot) == 1 and ballot[a] == 1)):
return False
return True
def PAVscore(profile, committee):
"""
For very, very large k (>300) this function does not work well enough
because of floating point errors
"""
score = 0
for voter in profile:
approvecount = 0
for elected,vote in zip(committee, voter):
if vote and elected:
approvecount+=1
for i in range(approvecount):
score+=(1/(i+1))
return score
def sortedProfile(profile):
"""
Returns a sorted version of the profile, not in-place, and still tuple
"""
profile = list(profile)
sortedProfile = sorted(profile)
return tuple(sortedProfile)
def cardinalityOfOverlap(ballot, committee):
"""
Gives the amount of approved candidates in committee according to ballot
"""
c=0
for candidate, vote in zip(committee, ballot):
c += (candidate and vote)
return c
def strictlySupersetDominates(profile, committee1, committee2):
"""
Returns true if committee1 strictly dominates committee2 in the sense
of weak Pareto
"""
strict = False
for voter, ballot in enumerate(profile):
int1 = intersection(ballot, committee1)
int2 = intersection(ballot, committee2)
for i1, i2 in zip(int1, int2):
if i2 > i1:
return False
if i1 > i2:
strict = True
return strict
def satisfiesJR(committee, profile, debug=False):
"""
Return true if the committee, given the profile, satisfies JR
"""
k = sum(committee)
threshold = n/k - 1e-5
uncovered_ballots = []
for ballot in profile:
for vote, elected in zip(ballot, committee):
if vote and elected:
break
else:
uncovered_ballots.append(ballot)
votespercandidate = [0]*m
for ballot in uncovered_ballots:
for candidate, vote in enumerate(ballot):
votespercandidate[candidate]+=vote
if max(votespercandidate) >= threshold:
return False
else:
return True
def satisfiesEJR(committee, profile):
"""
Returns true if committee satisfies Extended Justified Representation
for profile
This is coNP-complete
"""
k = sum(committee)
# calculate for each voter how many representatives he has in committee
voterRepr = []
for ballot in profile:
representatives = 0
for c,b in zip(committee, ballot):
if c and b:
representatives += 1
voterRepr.append(representatives)
for l in range(1, k+1):
# calculate all voters with < l representatives
underRepres = []
for voter, repres in enumerate(voterRepr):
if repres < l:
underRepres.append(voter)
minSize = ceil(l * (n/k) - 1e-5)
# find all subsets of size l * (n/k)
for comb in combinations(underRepres, minSize):
# count how many alternatives they all agree on
approveCount = 0
for i in range(m):
allApprove = True
for voter in comb:
if profile[voter][i] != 1:
allApprove = False
if allApprove:
approveCount += 1
# this should not be >= l if it satisfies EJR
if approveCount >= l:
return False
return True
def strictlyDominates(profile, committee1, committee2):
"""
Checks if every voter has at least as many of their candidates
in committee1 as in committee2, and at least one has strictly
more
"""
strict = False
for ballot in profile:
card1 = cardinalityOfOverlap(ballot, committee1)
card2 = cardinalityOfOverlap(ballot, committee2)
if card1 < card2:
return False
elif card1 > card2:
strict = True
return strict
def votesForCommittee(profile, committee):
"""
Gives AV score for the committee
"""
c=0
for ballot in profile:
c+= cardinalityOfOverlap(ballot, committee)
return c
def meanCardinality(winning_set, ballot):
result = Fraction(0)
for committee in winning_set:
result += cardinalityOfOverlap(ballot, committee)
return result / len(winning_set)
def minCardinality(winning_set, ballot):
return min([cardinalityOfOverlap(ballot, committee) for committee in winning_set])
def maxCardinality(winning_set, ballot):
return max([cardinalityOfOverlap(ballot, committee) for committee in winning_set])