From 278cea6dcc0703ae675c1ad721360ad389dd2f0e Mon Sep 17 00:00:00 2001 From: Sourcery AI <> Date: Sat, 31 Dec 2022 23:19:53 +0000 Subject: [PATCH] 'Refactored by Sourcery' --- .ipynb_checkpoints/vse2-checkpoint.py | 14 +- ballotEval.py | 2 +- cid.py | 52 +++++--- dataClasses.py | 72 ++++------- methods.py | 179 ++++++++++++-------------- mwmethods.py | 57 ++++---- mwvse.py | 35 +++-- pollPerturb.py | 13 +- stratEval.py | 19 ++- stratFunctions.py | 22 +--- voterModels.py | 9 +- vse.py | 27 ++-- 12 files changed, 239 insertions(+), 262 deletions(-) diff --git a/.ipynb_checkpoints/vse2-checkpoint.py b/.ipynb_checkpoints/vse2-checkpoint.py index 3f9bef2..6cde0b4 100644 --- a/.ipynb_checkpoints/vse2-checkpoint.py +++ b/.ipynb_checkpoints/vse2-checkpoint.py @@ -34,8 +34,7 @@ def __init__(self, model, methodsAndStrats, fgs.extend([(paramStrat(m.diehardBallot, intensity=i), targetFunc) for i in m.diehardLevels] + [(paramStrat(m.compBallot, intensity=i), targetFunc) for i in m.compLevels]) fgs.append((swapPolls(m.lowInfoBallot), targetFunc)) - for bg in [m.honBallot, m.lowInfoBallot]: - ms.append((m, bg, fgs)) + ms.extend((m, bg, fgs) for bg in [m.honBallot, m.lowInfoBallot]) else: ms.append(m) for i in range(niter): @@ -55,12 +54,11 @@ def saveFile(self, baseName="SimResults"): i = 1 while os.path.isfile(baseName + str(i) + ".csv"): i += 1 - myFile = open(baseName + str(i) + ".csv", "w") - dw = csv.DictWriter(myFile, self.rows[0].keys(), restval="NA") - dw.writeheader() - for r in self.rows: - dw.writerow(r) - myFile.close() + with open(baseName + str(i) + ".csv", "w") as myFile: + dw = csv.DictWriter(myFile, self.rows[0].keys(), restval="NA") + dw.writeheader() + for r in self.rows: + dw.writerow(r) def compareStrats(method, model, backgroundStrat, nvot, ncand, niter): pass diff --git a/ballotEval.py b/ballotEval.py index dacfa6c..c721a4a 100644 --- a/ballotEval.py +++ b/ballotEval.py @@ -5,7 +5,7 @@ def stratStats(model, strat, nvot, ncand, niter, stratArgs={}, numWinners=1, pollingError=0.2, usePolls=True, pickiness=0.7, pollFilter=None): bulletCount, totalScore = 0, 0 - for i in range(niter): + for _ in range(niter): electorate = model(nvot, ncand) if usePolls: pollBallots = [Approval.zeroInfoBallot(v, pickiness=pickiness) for v in electorate] diff --git a/cid.py b/cid.py index d7a64eb..8ef00dc 100644 --- a/cid.py +++ b/cid.py @@ -139,7 +139,7 @@ def influentialBlocs(voters, method, numWinners=1, utilChange=0.1, numBuckets=5, pollingMethod = method pollingStrat = method.honBallot basePollBallots = [pollingStrat(v, numWinners=numWinners, **pollingStratArgs) for v in voters] - pollErrors = [random.gauss(0, pollingError/2) for i in range(numCands)] + pollErrors = [random.gauss(0, pollingError/2) for _ in range(numCands)] polls = [min(1, max(0, r + e)) for r, e in zip(pollingMethod.results(basePollBallots), pollErrors)] #polls = media(pollingMethod.results(pollBallots), pollingError) #method.results can't depend on numWinners; this may need to be changed baseBallots = [strat(v, polls=polls, electabilities=polls, numWinners=numWinners, **stratArgs) for v in voters] @@ -147,7 +147,7 @@ def influentialBlocs(voters, method, numWinners=1, utilChange=0.1, numBuckets=5, sorter = sorter(voters) baseWinners = method.winnerSet(baseBallots, numWinners) baseResults = method.results(baseBallots) - isIncentive = [[] for i in range(numCands)] + isIncentive = [[] for _ in range(numCands)] #isIncentive[c][b] is 1 if candidate c is incentivized to appeal to the bth bucket of voters, 0 otherwise for cand in range(numCands): utilShifts = [0]*numCands @@ -205,7 +205,7 @@ def __init__(self, model, methodsAndStrats, nvot, ncand, niter, nwinners=1, elif len(m) == 2: ms.append((m[0], m[1], {})) else: ms.append(m) - self.mNames = [m[0].__name__ + ':' + m[1].__name__ + str(m[2]) for m in ms] + self.mNames = [f'{m[0].__name__}:{m[1].__name__}{str(m[2])}' for m in ms] self.rows = [] args = (model, nvot, ncand, ms, nwinners, utilChange, numBuckets, sorter, pollingMethod, pollingError, pollAfterPert) with multiprocessing.Pool(processes=7) as pool: @@ -233,7 +233,10 @@ def chart(self, methodOnly=True): fig, ax = plt.subplots() incentFracts = self.summarize()[0] if methodOnly: - incentFracts = {re.match(".*(?=:)", name).group(0): data for name, data in incentFracts.items()} + incentFracts = { + re.match(".*(?=:)", name)[0]: data + for name, data in incentFracts.items() + } for name, data in incentFracts.items(): ax.plot([i/self.numBuckets for i in range(1, self.numBuckets+1)], data, label=name) #ax.set_xlim(1, self.numBuckets) @@ -258,16 +261,17 @@ def saveFile(self, baseName="cidResults", newFile=True): fields = ['name', 'baseResult'] + list(range(self.numBuckets)) + list(universalInfo.keys()) resultTypeIndices = {'all': 1, 'loss': 2, 'win': 3} - myFile = open(baseName + (str(i) if newFile else "") + ".csv", "w") - dw = csv.DictWriter(myFile, fields, restval="data missing") - dw.writeheader() - for outcome, index in resultTypeIndices.items(): - for name, results in self.summarize()[index].items(): - row = {'name': name, 'baseResult': outcome} - row.update({i: result for i, result in enumerate(results)}) - row.update(universalInfo) - dw.writerow(row) - myFile.close() + with open(baseName + (str(i) if newFile else "") + ".csv", "w") as myFile: + dw = csv.DictWriter(myFile, fields, restval="data missing") + dw.writeheader() + for outcome, index in resultTypeIndices.items(): + for name, results in self.summarize()[index].items(): + row = ( + {'name': name, 'baseResult': outcome} + | enumerate(results) + | universalInfo + ) + dw.writerow(row) def simOneElectorate(model, nvot, ncand, ms, nwinners, utilChange, numBuckets, sorter, pollingMethod, pollingError, pollAfterPert, baseSeed=None, i = 0): @@ -280,9 +284,17 @@ def simOneElectorate(model, nvot, ncand, ms, nwinners, utilChange, numBuckets, s for method, strat, stratArgs in ms: allIncentives, baseWinners, baseResults = influentialBlocs(electorate, method, nwinners, utilChange, numBuckets, sorter, strat, stratArgs, pollingMethod, pollAfterPert=pollAfterPert, pollingError=pollingError) - for i, candIncentives in enumerate(allIncentives): - results.append(dict(incentives=candIncentives, isWinner=i in baseWinners, - method=method.__name__, strat=strat.__name__, stratArgs=stratArgs, voterModel=str(model))) + results.extend( + dict( + incentives=candIncentives, + isWinner=i in baseWinners, + method=method.__name__, + strat=strat.__name__, + stratArgs=stratArgs, + voterModel=str(model), + ) + for i, candIncentives in enumerate(allIncentives) + ) return results def showChart(fileName, norm=1, methodOnly=True, forResult='all', percentages=True, wholeOnly=True): @@ -298,7 +310,7 @@ def showChart(fileName, norm=1, methodOnly=True, forResult='all', percentages=Tr elif norm == 'max': normFactor = max(rawData) data = [d/normFactor for d in rawData] - name = re.match(".*(?=:)", row['name']).group(0) if methodOnly else row['name'] + name = re.match(".*(?=:)", row['name'])[0] if methodOnly else row['name'] ax.plot([(i+.5)*100/buckets for i in range(buckets)], data, label=name) ax.set_xlabel("Voter's support for candidate") ax.set_ylabel("Candidate's incentive to appeal to voter") @@ -308,9 +320,9 @@ def yFormatFunc(value, position): if value == 1: return "Average" if value == 0: return "0" if wholeOnly: - if value % 1 != 0: return "" - return f'{int(value)}x Avg' + return "" if value % 1 != 0 else f'{int(value)}x Avg' return f'{value:1.1f}x Avg' + ax.yaxis.set_major_formatter(mtick.FuncFormatter(yFormatFunc)) ax.grid(True) ax.legend() diff --git a/dataClasses.py b/dataClasses.py index 0c5aa72..4a0b338 100644 --- a/dataClasses.py +++ b/dataClasses.py @@ -103,10 +103,9 @@ def realisticBullets( r = random.Random(utils.id).random() if r < baseBullets + slope * margin: return cls.bulletBallot(utils) - else: - if otherStrat is None: - otherStrat = cls.honBallot - return otherStrat(utils, electabilities=electabilities) + if otherStrat is None: + otherStrat = cls.honBallot + return otherStrat(utils, electabilities=electabilities) @classmethod def diehardBallot( @@ -212,7 +211,7 @@ def stratThresholdSearch( bgBallots + fgBallots[:midpoint] + fgBaselineBallots[midpoint:] ) midpointWinner = cls.winner(cls.results(midpointBallots)) - if not any(midpointWinner == w for w, _ in winnersFound): + if all(midpointWinner != w for w, _ in winnersFound): winnersFound.append((midpointWinner, midpoint)) if midpointWinner == targetWinner: maxThreshold = midpoint @@ -225,16 +224,16 @@ def resultsFor(cls, voters): """Create (honest/naive) ballots and get results. Again, test on subclasses. """ - return cls.results(list(cls.honBallot(v) for v in voters)) + return cls.results([cls.honBallot(v) for v in voters]) @staticmethod def stratTarget2(places): - ((frontId, frontResult), (targId, targResult)) = places[0:2] + ((frontId, frontResult), (targId, targResult)) = places[:2] return (frontId, frontResult, targId, targResult) @staticmethod def stratTarget3(places): - ((frontId, frontResult), (targId, targResult)) = places[0:3:2] + ((frontId, frontResult), (targId, targResult)) = places[:3:2] return (frontId, frontResult, targId, targResult) @@ -379,7 +378,7 @@ def strat(voter, polls=None, electabilities=None, candToHelp=None, candToHurt=No strat.__name__ = strategy.__name__ for key, value in kw.items(): - strat.__name__ += "_" + str(key)[:4] + str(value)[:4] + strat.__name__ += f"_{str(key)[:4]}{str(value)[:4]}" return strat @@ -391,7 +390,7 @@ def selectAB(candA, candB): # candA and candB are candidate IDs def fgSelect(voter, **kw): return max(voter[candA] - voter[candB], 0) - fgSelect.__name__ = "select" + str(candA) + str(candB) + fgSelect.__name__ = f"select{str(candA)}{str(candB)}" return fgSelect @@ -467,26 +466,26 @@ def selectV(v, **kw): "factionFraction", ] for prefix in ["", "min", "t1", "o"]: - for columnName in [ - "fgUtil", - "fgUtilDiff", - "fgSize", - "fgNumHelped", - "fgHelpedUtil", - "fgHelpedUtilDiff", - "fgNumHarmed", - "fgHarmedUtil", - "fgHarmedUtilDiff", - "helpCandElected", - "hurtCandElectedR1", - ]: - resultColumns.append(prefix + columnName) + resultColumns.extend( + prefix + columnName + for columnName in [ + "fgUtil", + "fgUtilDiff", + "fgSize", + "fgNumHelped", + "fgHelpedUtil", + "fgHelpedUtilDiff", + "fgNumHarmed", + "fgHarmedUtil", + "fgHarmedUtilDiff", + "helpCandElected", + "hurtCandElectedR1", + ] + ) def makeResults(**kw): - results = {c: kw.get(c, None) for c in resultColumns} - results.update(kw) - return results + return {c: kw.get(c, None) for c in resultColumns} | kw def makePartialResults(fgVoters, winner, r1Winner, prefix, candToHelp, candToHurt): @@ -703,10 +702,7 @@ def tieFor2Estimate(probs): def adaptiveTieFor2(polls, uncertainty=0.15): - if False and len(polls) < 6: - return tieFor2Probs(polls, uncertainty) - else: - return tieFor2Estimate(tuple(pollsToProbs(polls, uncertainty))) + return tieFor2Estimate(tuple(pollsToProbs(polls, uncertainty))) def appendResults(filename, resultsList, globalComment=dict()): @@ -724,19 +720,7 @@ def appendResults(filename, resultsList, globalComment=dict()): with open(baseName + str(i) + ".csv", "a") as myFile: if needsHeader: - print( - "# " + str(globalComment), - # dict( - # media=self.media.__name__, - # version=self.repo_version, - # seed=self.seed, - ## model=self.model, - # methods=self.methods, - # nvot=self.nvot, - # ncand=self.ncand, - # niter=self.niter)), - file=myFile, - ) + print(f"# {str(globalComment)}", file=myFile) dw = csv.DictWriter(myFile, keys, restval="NA") dw.writeheader() for r in resultsList: diff --git a/methods.py b/methods.py index ba07ce1..845fd2d 100644 --- a/methods.py +++ b/methods.py @@ -309,7 +309,7 @@ def stratBallot(cls, voter, *args, **kws): def abstain(cls, utils, **kw): return [0] * len(utils), [0] * len(utils) - Top2Version.__name__ = noRunoffMethod.__name__ + "Top2" + Top2Version.__name__ = f"{noRunoffMethod.__name__}Top2" return Top2Version @@ -346,6 +346,9 @@ def vaBallot(cls, utils, electabilities=None, **kw): def makeScoreMethod(topRank=10, asClass=False): + + + class Score0to(Method): """Score voting, 0-10. @@ -467,7 +470,7 @@ def exaggeratedBallot(cls, utils, pickiness=0.4, **kw): pickiness=1 corresponds to bullet voting pickiness=0.4 seems about optimal under KSModel """ - expectedUtility = sum(u for u in utils) / len(utils) + expectedUtility = sum(utils) / len(utils) best = max(utils) normalizedUtils = [ (u - expectedUtility) / (best - expectedUtility) for u in utils @@ -601,6 +604,7 @@ def fillStratBallot( for i in range(n): ballot[i] = strat[i] + Score0to.topRank = topRank return Score0to if asClass else Score0to() @@ -640,7 +644,7 @@ def zeroInfoBallot( >>> Approval.zeroInfoBallot([1,2,3,-10], pickiness=0.6) [0, 1, 1, 0] """ - expectedUtility = sum(u for u in utils) / len(utils) + expectedUtility = sum(utils) / len(utils) best = max(utils) normalizedUtils = [ (u - expectedUtility) / (best - expectedUtility) for u in utils @@ -805,7 +809,7 @@ def getCoefficients( # thirdProbs = pollsToProbs( # electabilities[:minF] + electabilities[minF+1:maxF] + electabilities[maxF+1:], pollingUncertainty) for third in range(nCands): - if third == winner or third == runnerUp: + if third in [winner, runnerUp]: continue upsetProb = pollsToProbs( [ @@ -853,6 +857,8 @@ def makeSTARMethod(topRank=5): score0to = makeScoreMethod(topRank, True) + + class STAR0to(score0to): stratTargetFor = Method.stratTarget3 @@ -952,8 +958,8 @@ def vaBallot( @classmethod def compBallot( - cls, utils, intensity, candToHelp, candToHurt, baseBallotFunc=None, **kw - ): + cls, utils, intensity, candToHelp, candToHurt, baseBallotFunc=None, **kw + ): """Intensity determines the strategy used. 0 naive, 1 is honest, 2 semi-honest, 3 is favorite betrayal >>> STAR.compBallot([0,1,2,3,4,5,6,7],0,5,3) [0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0] @@ -984,10 +990,7 @@ def compBallot( baseBallot[i] = 0 if intensity == 2: for i, u in enumerate(utils): - if u > hurtUtil: - baseBallot[i] = cls.topRank - elif u <= hurtUtil: - baseBallot[i] = 0 + baseBallot[i] = cls.topRank if u > hurtUtil else 0 if intensity >= 3: baseBallot = [1 if u > hurtUtil else 0 for u in utils] baseBallot[candToHelp] = cls.topRank @@ -995,17 +998,17 @@ def compBallot( @classmethod def diehardBallot( - cls, - utils, - intensity, - candToHelp, - candToHurt, - electabilities=None, - polls=None, - baseBallotFunc=None, - info="p", - **kw, - ): + cls, + utils, + intensity, + candToHelp, + candToHurt, + electabilities=None, + polls=None, + baseBallotFunc=None, + info="p", + **kw, + ): """Intensity determines the strategy used. 0 naive, 1 is honest, 2 semi-honest, 3 is pushover, and 4 is bullet voting >>> STAR.diehardBallot([0,1,2,3,4,5,6,7],1,5,3) @@ -1039,10 +1042,7 @@ def diehardBallot( baseBallot[i] = cls.topRank if intensity == 2: for i, u in enumerate(utils): - if u < helpUtil: - baseBallot[i] = 0 - elif u >= helpUtil: - baseBallot[i] = cls.topRank + baseBallot[i] = 0 if u < helpUtil else cls.topRank if intensity == 3: for i, u in enumerate(utils): if u >= helpUtil: @@ -1052,9 +1052,7 @@ def diehardBallot( else: baseBallot[i] = 0 baseBallot[candToHurt] = 0 - if intensity == 4: - return cls.bulletBallot(utils) - return baseBallot + return cls.bulletBallot(utils) if intensity == 4 else baseBallot @classmethod def always510Ballot(cls, utils, **kw): @@ -1116,10 +1114,7 @@ def utilGapBallot(cls, utils, **kw): maxGap = sortedUtils[i + 1][1] - sortedUtils[i][1] gapLoc = (sortedUtils[i + 1][1] + sortedUtils[i][1]) / 2 for rank, (cand, util) in enumerate(sortedUtils): - if util < gapLoc: - ballot[cand] = rank - else: - ballot[cand] = rank + cls.topRank + 1 - n + ballot[cand] = rank if util < gapLoc else rank + cls.topRank + 1 - n else: utilRanges = [[u, u] for u in sorted(utils)] # use a greedy algorithm to put most similar utilities together @@ -1169,15 +1164,13 @@ def top6Ballot(cls, utils, electabilities=None, polls=None, info="e", **kw): ballot.append(i + 1) return ballot - # @classmethod - # def defaultfgs(cls): - # return super().defaultfgs()\ - # + [(cls.top6Ballot, targs) for targs in [selectRand, select21]] + # @classmethod + # def defaultfgs(cls): + # return super().defaultfgs()\ + # + [(cls.top6Ballot, targs) for targs in [selectRand, select21]] + - if topRank == 5: - STAR0to.__name__ = "STAR" - else: - STAR0to.__name__ = "STAR" + str(topRank) + STAR0to.__name__ = "STAR" if topRank == 5 else f"STAR{str(topRank)}" return STAR0to @@ -1213,7 +1206,7 @@ class Mav(Method): specificPercentiles = [25, 50, 75, 90] @classmethod - def candScore(self, scores): + def candScore(cls, scores): """For now, only works correctly for odd nvot Basic tests @@ -1232,7 +1225,7 @@ def candScore(self, scores): """ scores = sorted(scores) nvot = len(scores) - nGrades = len(self.baseCuts) + 1 + nGrades = len(cls.baseCuts) + 1 i = int((nvot - 1) / 2) base = scores[i] while i < nvot and base == base: @@ -1301,7 +1294,7 @@ def stratBallot(cls, voter, polls, electabilities=None, info="p", **kw): if info == "e": polls = electabilities places = sorted(enumerate(polls), key=lambda x: -x[1]) - ((frontId, frontResult), (targId, targResult)) = places[0:2] + ((frontId, frontResult), (targId, targResult)) = places[:2] frontUtils = [voter[frontId], voter[targId]] # utils of frontrunners stratGap = frontUtils[1] - frontUtils[0] if stratGap == 0: @@ -1309,26 +1302,25 @@ def stratBallot(cls, voter, polls, electabilities=None, info="p", **kw): if stratGap < 0: return cls.honBallot(voter) - else: - # runner-up is preferred; be strategic in iss run - # sort cuts high to low - frontUtils = (frontUtils[1], frontUtils[0]) - top = max(voter) - # print("lll312") - # print(self.baseCuts, front) - cutoffs = [ - ( - (min(frontUtils[0], cls.baseCuts[i])) - if (i < floor(targResult)) - else ( - (frontUtils[1]) - if (i < floor(frontResult) + 1) - else min(top, cls.baseCuts[i]) - ) + # runner-up is preferred; be strategic in iss run + # sort cuts high to low + frontUtils = (frontUtils[1], frontUtils[0]) + top = max(voter) + # print("lll312") + # print(self.baseCuts, front) + cutoffs = [ + ( + (min(frontUtils[0], cls.baseCuts[i])) + if (i < floor(targResult)) + else ( + (frontUtils[1]) + if (i < floor(frontResult) + 1) + else min(top, cls.baseCuts[i]) ) - for i in range(len(cls.baseCuts)) - ] - return [toVote(cutoffs, util) for util in voter] + ) + for i in range(len(cls.baseCuts)) + ] + return [toVote(cutoffs, util) for util in voter] def stratBallotFor(self, polls): """Returns a function which takes utilities and returns a dict( @@ -1496,11 +1488,11 @@ def buildPreferenceSchedule(self, ballots): return prefs @classmethod - def oldResort(self, ballots, loser, ncand, piles): + def oldResort(cls, ballots, loser, ncand, piles): """No error checking; only works for exhaustive ratings.""" # print("resort",ballots, loser, ncand) # print(piles) - for ballot in ballots: + for _ in ballots: if loser < 0: nextrank = ncand - 1 @@ -1563,7 +1555,7 @@ def results(cls, ballots): ballots = list(ballots) ncand = len(ballots[0]) nbal = len(ballots) - piles = [[] for i in range(ncand)] + piles = [[] for _ in range(ncand)] candsLeft = set(range(ncand)) ballotsToSort = ballots eliminations = [] # (candidateIndex, defeatMargin) tuples @@ -1591,7 +1583,7 @@ def results(cls, ballots): return results @classmethod - def oldResults(self, ballots, **kwargs): + def oldResults(cls, ballots, **kwargs): """IRV results. >>> #Irv.resultsFor(DeterministicModel(3)(5,3)) @@ -1605,7 +1597,7 @@ def oldResults(self, ballots, **kwargs): """ if type(ballots) is not list: ballots = list(ballots) - return self.runIrv(self.buildPreferenceSchedule(ballots), len(ballots[0])) + return cls.runIrv(cls.buildPreferenceSchedule(ballots), len(ballots[0])) @classmethod def honBallot(cls, voter, **kw): @@ -1876,7 +1868,7 @@ class V321(Mav): stratTargetFor = Method.stratTarget3 @classmethod - def results(self, ballots, isHonest=False, **kwargs): + def results(cls, ballots, isHonest=False, **kwargs): """3-2-1 Voting results. >>> V321.resultsFor(DeterministicModel(3)(5,3)) @@ -1922,12 +1914,12 @@ def results(self, ballots, isHonest=False, **kwargs): sign(ballot[semifinalists[o1s[0]]] - ballot[semifinalists[o1s[2]]]) for ballot in ballots ) - self.__class__.extraEvents["3beats1"] = upset2 > 0 + cls.__class__.extraEvents["3beats1"] = upset2 > 0 upset3 = sum( sign(ballot[semifinalists[o1s[0]]] - ballot[semifinalists[o1s[1]]]) for ballot in ballots ) - self.__class__.extraEvents["3beats2"] = upset3 > 0 + cls.__class__.extraEvents["3beats2"] = upset3 > 0 if len(o2s) > 3: fourth = o2s[-4] fourthNotLasts = sum(1 if s > 1 else 0 for s in candScores[fourth]) @@ -1939,7 +1931,7 @@ def results(self, ballots, isHonest=False, **kwargs): ) > 0 ) - self.__class__.extraEvents["4beats1"] = fourthWin + cls.__class__.extraEvents["4beats1"] = fourthWin return r2s @@ -2078,7 +2070,7 @@ def compMatrix(ballots): [[0, 2, -4], [-2, 0, 6], [4, -6, 0]] """ n = len(ballots[0]) - cmat = [[0 for i in range(n)] for j in range(n)] + cmat = [[0 for _ in range(n)] for _ in range(n)] for i in range(n): for j in range(n): @@ -2098,7 +2090,7 @@ def smithSet(cmat): {0, 1, 2, 3} """ winCount = [sum(1 if matchup > 0 else 0 for matchup in row) for row in cmat] - s = set(candID for candID, wins in enumerate(winCount) if wins == max(winCount)) + s = {candID for candID, wins in enumerate(winCount) if wins == max(winCount)} extensionFound = True while extensionFound: extensionFound = False @@ -2132,9 +2124,8 @@ def scenarioType(cls, electorate): cmat = cls.compMatrix(ballots) numWins = [ sum( - 1 + matchup > 0 or (matchup == 0 and i < j) for j, matchup in enumerate(row) - if matchup > 0 or (matchup == 0 and i < j) ) for i, row in enumerate(cmat) ] @@ -2254,11 +2245,7 @@ def resolveCycle(cls, cmat, n, ballots): for i in range(n): for j in range(n): if i != j: - if cmat[i][j] > cmat[j][i]: - beatStrength[i][j] = cmat[i][j] - else: - beatStrength[i][j] = 0 - + beatStrength[i][j] = cmat[i][j] if cmat[i][j] > cmat[j][i] else 0 for i in range(n): for j in range(n): if i != j: @@ -2319,8 +2306,9 @@ def resolveCycle(cls, cmat, n, ballots): """Unnecessary for Minimax; this method is never called.""" smith = cls.smithSet(cmat) places = sorted( - [cand for cand in range(n) if cand not in smith], key=lambda c: min(cmat[c]) - ) + sorted([cand for cand in smith], key=lambda c: min(cmat[c])) + [cand for cand in range(n) if cand not in smith], + key=lambda c: min(cmat[c]), + ) + sorted(list(smith), key=lambda c: min(cmat[c])) return [places.index(cand) for cand in range(n)] @classmethod @@ -2336,23 +2324,22 @@ def results(cls, ballots, **kw): numCands = len(ballots[0]) cmat = cls.compMatrix(ballots) smith = cls.smithSet(cmat) - if len(smith) == 1: - results = [0] * numCands - winner = smith.pop() - results[winner] = 0.5 + min(marg for marg in cmat[winner] if marg != 0) / ( - 2 * len(ballots) - ) - for i in range(numCands): - if i == winner: - continue - results[i] = 0.5 + cmat[i][winner] / (2 * len(ballots)) - return results - else: + if len(smith) != 1: return [ 0.5 + min(cmat[cand][i] for i in smith if cand != i) / (2 * len(ballots)) for cand in range(numCands) ] + results = [0] * numCands + winner = smith.pop() + results[winner] = 0.5 + min(marg for marg in cmat[winner] if marg != 0) / ( + 2 * len(ballots) + ) + for i in range(numCands): + if i == winner: + continue + results[i] = 0.5 + cmat[i][winner] / (2 * len(ballots)) + return results class Raynaud(Condorcet): @@ -2401,7 +2388,7 @@ def results(cls, ballots, **kw): """ numCands = len(ballots[0]) cmat = cls.compMatrix(ballots) - matchupWins = [sum(1 for marg in row if marg > 0) for row in cmat] + matchupWins = [sum(marg > 0 for marg in row) for row in cmat] mostWins = max(matchupWins) finalists = [i for i in range(numCands) if matchupWins[i] == mostWins] rest = [i for i in range(numCands) if i not in finalists] @@ -2603,7 +2590,7 @@ def results(cls, ballots, **kw): rounds = [] while candsLeft > 1: approvals = [0 if i in remainingCands else -1 for i in range(n)] - for i, b in enumerate(ballots): + for b in ballots: threshold = sum(b[c] / candsLeft for c in remainingCands) for cand in remainingCands: if b[cand] > threshold: diff --git a/mwmethods.py b/mwmethods.py index 8bcbd15..acf5a87 100644 --- a/mwmethods.py +++ b/mwmethods.py @@ -2,6 +2,7 @@ from methods import * def makeBlock(method): + class BlockVoting(method): @classmethod def winnerSet(cls, ballots, numWinners): @@ -15,7 +16,7 @@ def winnerSet(cls, ballots, numWinners): winners.append(winner) unelectedCands.remove(winner) return winners - BlockVoting.__name__ = "Block" + method.__name__ + BlockVoting.__name__ = f"Block{method.__name__}" return BlockVoting class BlockApproval(makeBlock(Approval)): pass #What's used in Fargo @@ -99,10 +100,9 @@ def reweight(cls, ballots, winner, quota): score = cls.topRank + 1 while totalSupport < quota: score -= 1 - scoreSupport = 0 - for ballot in ballots: - if ballot[winner] == score: - scoreSupport += ballot.weight + scoreSupport = sum( + ballot.weight for ballot in ballots if ballot[winner] == score + ) totalSupport += scoreSupport surplusFraction = max(0, (totalSupport-quota)/scoreSupport) for ballot in ballots: @@ -265,9 +265,7 @@ def pickWinner(cls, ballots, unelectedCands, quota, numWinners): candTotals[c] += ballot[c]*ballot.weight (runnerUp,top) = sorted(range(len(ballots[0])), key=lambda i: candTotals[i])[-2:] upset = sum(sign(ballot[runnerUp] - ballot[top])*ballot.weight for ballot in ballots) - if upset > 0: - return runnerUp - else: return top + return runnerUp if upset > 0 else top class ASRunoffs(AllocatedScore): @classmethod @@ -275,9 +273,7 @@ def pickWinner(cls, ballots, unelectedCands, quota, numWinners): candTotals = [sum(b[c]*b.weight for b in ballots) if c in unelectedCands else -1 for c in range(len(ballots[0]))] (runnerUp,top) = sorted(range(len(ballots[0])), key=lambda i: candTotals[i])[-2:] upset = sum(sign(ballot[runnerUp] - ballot[top])*ballot.weight for ballot in ballots) - if upset > 0: - return runnerUp - else: return top + return runnerUp if upset > 0 else top class ASRDroop(ASRunoffs): methodQuota = staticmethod(droop) @@ -319,9 +315,8 @@ def pickWinner(cls, ballots, unelectedCands, quota, numWinners): bestCands = [c for c in unelectedCands if quotaStrengths[c]==bestQuota] if len(bestCands) == 1: return bestCands[0] - else: - scores = [sum(ballot[c]*ballot.weight for ballot in ballots) if c in bestCands else -1 for c in range(ncand)] - return scores.index(max(scores)) + scores = [sum(ballot[c]*ballot.weight for ballot in ballots) if c in bestCands else -1 for c in range(ncand)] + return scores.index(max(scores)) class SSS(AllocatedScore): """ @@ -352,10 +347,11 @@ def reweight(cls, ballots, winner, quota): score = cls.topRank + 1 while totalSupport < quota*cls.topRank and score > 1: score -= 1 - scoreSupport = 0 - for ballot in ballots: - if ballot[winner] == score: - scoreSupport += score*ballot.weight + scoreSupport = sum( + score * ballot.weight + for ballot in ballots + if ballot[winner] == score + ) totalSupport += scoreSupport surplusFraction = max((totalSupport-quota*cls.topRank)/scoreSupport, 0) if scoreSupport else 0 for ballot in ballots: @@ -440,11 +436,12 @@ def winnerSet(cls, ballots, numWinners): candsLeft = set(range(nCands)) wBallots = [weightedBallot(b) for b in ballots] ballotsToSort = wBallots - piles = [[] for i in range(nCands)] + piles = [[] for _ in range(nCands)] while len(winners) + len(candsLeft) > numWinners: cls.resort(ballotsToSort, candsLeft, piles) - newWinners = [c for c in candsLeft if sum(b.weight for b in piles[c]) >= quota] - if newWinners: + if newWinners := [ + c for c in candsLeft if sum(b.weight for b in piles[c]) >= quota + ]: ballotsToSort = [] winners.extend(newWinners) for w in newWinners: @@ -462,8 +459,7 @@ def winnerSet(cls, ballots, numWinners): loserVotes = sum(b.weight for b in piles[cand]) candsLeft.remove(loser) ballotsToSort = piles[loser] - for c in candsLeft: - winners.append(c) + winners.extend(iter(candsLeft)) return winners class MinimaxSTV(STV): @@ -481,11 +477,12 @@ def winnerSet(cls, ballots, numWinners): candsLeft = set(range(nCands)) wBallots = [weightedBallot(b) for b in ballots] ballotsToSort = wBallots - piles = [[] for i in range(nCands)] + piles = [[] for _ in range(nCands)] while len(winners) < numWinners - 1 and len(winners) + len(candsLeft) > numWinners - 1: cls.resort(ballotsToSort, candsLeft, piles) - newWinners = [c for c in candsLeft if sum(b.weight for b in piles[c]) >= quota][:max(0, numWinners-len(winners)-1)] - if newWinners: + if newWinners := [ + c for c in candsLeft if sum(b.weight for b in piles[c]) >= quota + ][: max(0, numWinners - len(winners) - 1)]: ballotsToSort = [] winners.extend(newWinners) for w in newWinners: @@ -504,9 +501,7 @@ def winnerSet(cls, ballots, numWinners): candsLeft.remove(loser) ballotsToSort = piles[loser] if len(winners) < numWinners - 1: - for c in candsLeft: - winners.append(c) - + winners.extend(iter(candsLeft)) #Minimax step compMatrix = [[float('inf') if i in winners or j in winners else sum(b.weight*sign(b[i] - b[j]) for b in wBallots) @@ -524,7 +519,7 @@ def assignBallot(ballot, candsLeft): if activeScore == 0: return set() else: - return set(i for i in candsLeft if ballot[i] == activeScore) + return {i for i in candsLeft if ballot[i] == activeScore} class S5HtoSTV(S5H): """Uses S5H for as long as a Droop quota can be filled, then switches to STV @@ -562,7 +557,7 @@ def oneS5HRound(cls, ballots, unelectedCands, quota, numWinners): def useSTV(cls, wBallots, numWinners, winners, quota, useForLastRound): numToElect = numWinners if useForLastRound else numWinners - 1 nCands = len(wBallots[0]) - candsLeft = set(i for i in range(nCands) if i not in winners) + candsLeft = {i for i in range(nCands) if i not in winners} assignedBallots = [[b, assignBallot(b, candsLeft)] for b in wBallots] while len(winners) < numToElect and len(winners) + len(candsLeft) > numToElect: candTotals = [sum(b.weight/len(topCands) for b, topCands in assignedBallots if cand in topCands) diff --git a/mwvse.py b/mwvse.py index 6447c9c..1a216c5 100644 --- a/mwvse.py +++ b/mwvse.py @@ -160,7 +160,7 @@ def addConstResults(self): def addRound0Results(self): r = self.makeResults(results=self.r0Results, totalUtil=self.totalUtils[r0Winner], probOfWin=self.winProbs[r0Winner], **self.constResults) - r['method'] = self.pollingMethod.__name__+'Poll' + r['method'] = f'{self.pollingMethod.__name__}Poll' self.allResults.append(r) def addRound1Results(self): @@ -195,7 +195,7 @@ def stratThresholdSearch(self, targetWinner, foundAt, bgBallots, fgBallots, fgBa midpoint = int(floor((maxThreshold + minThreshold)/2)) midpointBallots = bgBallots + fgBallots[:midpoint] + fgBaselineBallots[midpoint:] midpointWinner = self.winnerFunc(midpointBallots) - if not any(midpointWinner == w for w, _ in winnersFound): + if all(midpointWinner != w for w, _ in winnersFound): winnersFound.append((midpointWinner, midpoint)) if midpointWinner == targetWinner: maxThreshold = midpoint @@ -243,9 +243,7 @@ def finalStats(self): deciderUtilDiffSum=sum(uDiff for uDiff, _ in self.deciderMargUtilDiffs), **self.partialResults)) def makeResults(self, **kw): - results = {c: kw.get(c, None) for c in resultColumns} - results.update(kw) - return results + return {c: kw.get(c, None) for c in resultColumns} | kw def makePartialResults(self, fgVoters, winner, r1Winner, prefix=""): fgHelped = [] @@ -273,20 +271,19 @@ def winnerSetSample(numCands, numWinners): winnerSetSample(5,2) [{0, 1}, {0, 2}, {0, 3}, {0, 4}, {1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 4}, {3, 4}] """ - if math.comb(numCands, numWinners) < 100: - winners = list(range(numWinners)) - winnerSets = [set(winners)] - while winners[0] < numCands - numWinners: - for i in range(numWinners-1, -1, -1): - if winners[i] < numCands - (numWinners - i) and (i==numWinners-1 or winners[i+1] - winners[i] > 1): - winners[i] += 1 - for j in range(i+1, numWinners): - winners[j] = winners[i] + j - i - break - winnerSets.append(set(winners)) - return winnerSets - else: - return [set(random.sample(range(numCands), numWinners)) for i in range(50)] + if math.comb(numCands, numWinners) >= 100: + return [set(random.sample(range(numCands), numWinners)) for _ in range(50)] + winners = list(range(numWinners)) + winnerSets = [set(winners)] + while winners[0] < numCands - numWinners: + for i in range(numWinners-1, -1, -1): + if winners[i] < numCands - (numWinners - i) and (i==numWinners-1 or winners[i+1] - winners[i] > 1): + winners[i] += 1 + for j in range(i+1, numWinners): + winners[j] = winners[i] + j - i + break + winnerSets.append(set(winners)) + return winnerSets mwResultColumns = ["method", "backgroundStrat", "fgStrat", "numVoters", "numCandidates", "numWinners", "magicBestUtil", "magicWorstUtil", "meanCandidateUtil", diff --git a/pollPerturb.py b/pollPerturb.py index b75fc03..9b38f2b 100644 --- a/pollPerturb.py +++ b/pollPerturb.py @@ -14,13 +14,16 @@ def pollPert(model, methodsAndStrats, nvot, ncand, niter, nwinners=1, perturbati totalResults = {p:{m[0].__name__: [0,0,0] for m in ms} for p in perturbations} condResults = {p:{m[0].__name__: [0,0,0] for m in ms} for p in perturbations} nonCondResults = {p:{m[0].__name__: [0,0,0] for m in ms} for p in perturbations} - for i in range(niter): + for _ in range(niter): electorate = model(nvot, ncand) basePolls = pollingMethod.results([pollingStrat(v) for v in electorate]) - if Condorcet.scenarioType(electorate) == 'cycle': - condWinner = None - else: - condWinner = Condorcet.winnerSet([Condorcet.honBallot(v) for v in electorate])[0] + condWinner = ( + None + if Condorcet.scenarioType(electorate) == 'cycle' + else Condorcet.winnerSet( + [Condorcet.honBallot(v) for v in electorate] + )[0] + ) for method, strat, stratArgs in ms: baseWinners = set(method.winnerSet([strat(v, polls=basePolls, electabilities=basePolls, **stratArgs) for v in electorate], numWinners=nwinners)) diff --git a/stratEval.py b/stratEval.py index 6ce6f40..c906715 100644 --- a/stratEval.py +++ b/stratEval.py @@ -43,11 +43,18 @@ def methodStratLoop(voterModel, methods, strats, nvot, ncand, numWinners, bgStra electabilities = noisyMedia(pollingMethod.results(pollingStrat(voter, **pollingStratArgs) for voter in electorate), pollingError) bgBallots = [bgStrat(v, electabilities=electabilities, numWinners=numWinners, **bgStratArgs) for v in electorate] - methodResults = {} - for m in methods: - methodResults[m.__name__] = simpleStratTest(electorate, m, strats, numWinners, - electabilities, bgBallots, pollingError) - return methodResults + return { + m.__name__: simpleStratTest( + electorate, + m, + strats, + numWinners, + electabilities, + bgBallots, + pollingError, + ) + for m in methods + } def keyTuple(method, strat, statName): return (method.__name__, strat[0].__name__, str(strat[1]), statName) @@ -102,7 +109,7 @@ def __init__(self, niter, voterModel, methods, strats, nvot, ncand, numWinners=1 def showESIF(self): for stat in self.statNames: - print(stat + ':') + print(f'{stat}:') print(("Method\t" if self.multiMethods else "")+("Strategy\t" if self.multiStrats else "")+("Arguments\t" if self.multiArgs else "")+"ESIF") for key, value in self.esif.items(): if key[3] != stat or key[1] == 'abstain': continue diff --git a/stratFunctions.py b/stratFunctions.py index 07c39ae..608dbdd 100644 --- a/stratFunctions.py +++ b/stratFunctions.py @@ -40,10 +40,7 @@ def orderOf(standings): def fuzzyMediaFor(biaser = biaserAround(1)): def fuzzyMedia(standings): - if callable(biaser): - bias = biaser(standings) - else: - bias = biaser + bias = biaser(standings) if callable(biaser) else biaser result= [s + random.gauss(0,bias) for s in standings] return result @@ -61,13 +58,11 @@ def biasedMediaFor(biaser=biaserAround(1),numerator=1): """ def biasedMedia(standings): - if callable(biaser): - bias = biaser(standings) - else: - bias = biaser - result= (standings[0:2] + - [(standing - bias + numerator * (bias / max(i+2, 1))) - for i, standing in enumerate(standings[2:])]) + bias = biaser(standings) if callable(biaser) else biaser + result = standings[:2] + [ + (standing - bias + numerator * (bias / max(i + 2, 1))) + for i, standing in enumerate(standings[2:]) + ] return result @@ -79,10 +74,7 @@ def skewedMediaFor(biaser): [0, -1/3, -2/3, -1] """ def skewedMedia(standings): - if callable(biaser): - bias = biaser(standings) - else: - bias = biaser + bias = biaser(standings) if callable(biaser) else biaser result= [(standing - bias * i / (len(standings) - 1)) for i, standing in enumerate(standings)] return result diff --git a/voterModels.py b/voterModels.py index 499f560..23a358c 100644 --- a/voterModels.py +++ b/voterModels.py @@ -263,7 +263,7 @@ def fromDims(cls, v, e, caring = None): and e.dimWeights (a list of numbers indicating how important each dimension is) Returns a voter whose utility for each candidate is minus the voter's distance from them in policyspace """ - if caring==None: + if caring is None: caring = [1] * len(v) totCaring = e.totWeight else: @@ -364,7 +364,12 @@ def chooseClusters(self, n, alpha, caring): for _ in range(self.numSubclusters[c]): cares = caring() - subclusterMeans.append([random.gauss(0,sqrt(cares)) for i in range(self.dimsPerView[c])]) + subclusterMeans.append( + [ + random.gauss(0, sqrt(cares)) + for _ in range(self.dimsPerView[c]) + ] + ) subclusterCaring.append(caring()) self.clusterMeans.append(subclusterMeans) self.clusterCaring.append(subclusterCaring) diff --git a/vse.py b/vse.py index f965c7c..ba593c4 100644 --- a/vse.py +++ b/vse.py @@ -74,6 +74,8 @@ def threeRoundResults(method, voters, backgroundStrat, foregrounds=[], bgArgs = probOfWin=winProbs[r1Winner], winnerPlaceInR0=r0Places[r1Winner], **constResults)] allResults[0]['method'] = 'ApprovalPoll' + fgHelped = [] + fgHarmed = [] for foregroundStrat, targetSelect, foregroundSelect, fgArgs in foregrounds: polls = tuple(r2Media(r1Results, pollingError)) candToHelp, candToHurt = targetSelect(electabilities=electabilities, polls=polls, r0polls=electabilities) @@ -103,18 +105,13 @@ def threeRoundResults(method, voters, backgroundStrat, foregrounds=[], bgArgs = #foregroundBaseUtil = sum(voter[r1Winner] for voter, _, _ in foreground)/fgSize if fgSize else 0 #foregroundStratUtil = sum(voter[winner] for voter, _, _ in foreground)/fgSize if fgSize else 0 totalUtil = voters.socUtils[winner] - fgHelped = [] - fgHarmed = [] winnersFound = [(r1Winner, 0)] partialResults = constResults.copy() if winner != r1Winner: winnersFound.append((winner, fgSize - 1)) i = 1 deciderMargUtilDiffs = [] - if fgSize: #If not I should be quitting earlier than this but easier to just fake it. - lastVoter = foreground[fgSize - 1][0] - else: #zero-sized foreground - lastVoter = [0.] * len(r1Results) + lastVoter = foreground[fgSize - 1][0] if fgSize else [0.] * len(r1Results) deciderUtilDiffs = [(lastVoter[winner] - lastVoter[r1Winner] , nan, fgSize)] allUtilDiffs = [([voter[0][winner] - voter[0][r1Winner] for voter in foreground], fgSize)] while i < len(winnersFound): @@ -129,7 +126,9 @@ def threeRoundResults(method, voters, backgroundStrat, foregrounds=[], bgArgs = elif r1Winner == prevWinner: prefix = "t1" else: prefix = "o"#+str(i) - partialResults.update(makePartialResults(minfg, thisWinner, r1Winner, prefix, candToHelp, candToHurt)) + partialResults |= makePartialResults( + minfg, thisWinner, r1Winner, prefix, candToHelp, candToHurt + ) deciderUtils = foreground[threshold-1][0] #The deciding voter if threshold == 0: #this shouldn't actually matter as we'll end up ignoring it anyway #, so having the wrong utilities would be OK. But let's get it right. @@ -225,12 +224,11 @@ def saveFile(self, baseName="SimResults", newFile=True): if newFile: while os.path.isfile(baseName + str(i) + ".csv"): i += 1 - myFile = open(baseName + (str(i) if newFile else "") + ".csv", "w") - dw = csv.DictWriter(myFile, self.rows[0].keys(), restval="NA") - dw.writeheader() - for r in self.rows: - dw.writerow(r) - myFile.close() + with open(baseName + (str(i) if newFile else "") + ".csv", "w") as myFile: + dw = csv.DictWriter(myFile, self.rows[0].keys(), restval="NA") + dw.writeheader() + for r in self.rows: + dw.writerow(r) def oneStepWorker(model, nvot, ncand, ms, pickiness, pollingError, r1Media, r2Media, baseSeed=None, i = 0): @@ -280,8 +278,7 @@ def listProduct(lists, index=0): return [[i] for i in lists[0]] returnList = [] for other in listProduct(lists[1:]): - for item in lists[0]: - returnList.append([item] + other) + returnList.extend([item] + other for item in lists[0]) return returnList