Files
consensus/NPoS/simplePhragmén.py
T

257 lines
9.1 KiB
Python

#from itertools import count
class edge:
def __init__(self,nomid,valiid):
self.nomid=nomid
self.valiid=valiid
#self.validator
self.load=0
class nominator:
def __init__(self,votetuple):
self.nomid=votetuple[0]
self.budget=votetuple[1]
self.edges=[edge(self.nomid,valiid) for valiid in votetuple[2]]
self.load=0
class candidate:
def __init__(self,valiid,valindex):
self.valiid = valiid
self.valindex=valindex
self.approvalstake=0
self.elected=False
self.backedstake=0
self.score=0
def setuplists(votelist):
#Instead of Python's dict here, you can use anything with O(log n) addition and lookup.
#We can also use a hashmap like dict, by generating a random constant r and useing H(canid+r)
#since the naive thing is obviously attackable.
nomlist = [nominator(votetuple) for votetuple in votelist]
candidatedict=dict()
candidatearray=list()
numcandidates=0
#Get an array of candidates. ]#We could reference these by index
#rather than pointer
for nom in nomlist:
for edge in nom.edges:
valiid = edge.valiid
if valiid in candidatedict:
edge.candidate=candidatearray[candidatedict[valiid]]
else:
candidatedict[valiid]=numcandidates
newcandidate=candidate(valiid,numcandidates)
candidatearray.append(newcandidate)
edge.candidate=newcandidate
numcandidates += 1
return(nomlist,candidatearray)
def seqPhragmén(votelist,numtoelect):
nomlist,candidates=setuplists(votelist)
#Compute the total possible stake for each candidate
for nom in nomlist:
for edge in nom.edges:
edge.candidate.approvalstake += nom.budget
electedcandidates=list()
for round in range(numtoelect):
for candidate in candidates:
if not candidate.elected:
candidate.score=1/candidate.approvalstake
for nom in nomlist:
for edge in nom.edges:
if not edge.candidate.elected:
edge.candidate.score +=nom.budget * nom.load / edge.candidate.approvalstake
bestcandidate=0
bestscore = 1000 #should be infinite but I'm lazy
for candidate in candidates:
if not candidate.elected and candidate.score < bestscore:
bestscore=candidate.score
bestcandidate=candidate.valindex
electedcandidate=candidates[bestcandidate]
electedcandidate.elected=True
electedcandidate.electedpos=round
electedcandidates.append(electedcandidate)
for nom in nomlist:
for edge in nom.edges:
if edge.candidate.valindex == bestcandidate:
edge.load=electedcandidate.score - nom.load
nom.load=electedcandidate.score
for candidate in electedcandidates:
candidate.backedstake=0
for nom in nomlist:
for edge in nom.edges:
if nom.load > 0.0:
edge.backingstake = nom.budget * edge.load/nom.load
edge.candidate.backedstake += edge.backingstake
else:
edge.backingstake = 0
return (nomlist,electedcandidates)
def approvalvoting(votelist,numtoelect):
nomlist,candidates=setuplists(votelist)
#Compute the total possible stake for each candidate
for nom in nomlist:
for edge in nom.edges:
edge.candidate.approvalstake += nom.budget
edge.backingstake = nom.budget/min(len(nom.edges),numtoelect)
edge.candidate.backedstake += edge.backingstake
candidates.sort( key = lambda x : x.approvalstake, reverse=True)
electedcandidates=candidates[0:numtoelect]
return nomlist,electedcandidates
def printresult(nomlist,electedcandidates):
for candidate in electedcandidates:
print(candidate.valiid," is elected with stake ",candidate.backedstake, "and score ",candidate.score)
print()
for nom in nomlist:
print(nom.nomid," has load ",nom.load, "and supported ")
for edge in nom.edges:
print(edge.valiid," with stake ",edge.backingstake, end=" ")
print()
def equalise(nom, tolerance):
# Attempts to redistribute the nominators budget between elected validators
# Assumes that all elected validators have backedstake set correctly
# returns the max difference in stakes between sup
electededges=[edge for edge in nom.edges if edge.candidate.elected]
if len(electededges)==0:
return 0.0
stakeused = sum([edge.backingstake for edge in electededges])
backedstakes=[edge.candidate.backedstake for edge in electededges]
backingbackedstakes=[edge.candidate.backedstake for edge in electededges if edge.backingstake > 0.0]
if len(backingbackedstakes) > 0:
difference = max(backingbackedstakes)-min(backedstakes)
difference += nom.budget-stakeused
if difference < tolerance:
return difference
else:
difference = nom.budget
#remove all backing
for edge in nom.edges:
edge.candidate.backedstake -= edge.backingstake
edge.backingstake=0
electededges.sort(key=lambda x: x.candidate.backedstake)
cumulativebackedstake=0
lastcandidateindex=len(electededges)-1
for i in range(len(electededges)):
backedstake=electededges[i].candidate.backedstake
#print(nom.nomid,electededges[i].valiid,backedstake,cumulativebackedstake,i)
if backedstake * i - cumulativebackedstake > nom.budget:
lastcandidateindex=i-1
break
cumulativebackedstake +=backedstake
laststake=electededges[lastcandidateindex].candidate.backedstake
waystosplit=lastcandidateindex+1
excess = nom.budget + cumulativebackedstake - laststake*waystosplit
for edge in electededges[0:waystosplit]:
edge.backingstake = excess / waystosplit + laststake - edge.candidate.backedstake
edge.candidate.backedstake += edge.backingstake
return difference
import random
def equaliseall(nomlist,maxiterations,tolerance):
for i in range(maxiterations):
for j in range(len(nomlist)):
nom=random.choice(nomlist)
equalise(nom,tolerance/10)
maxdifference=0
for nom in nomlist:
difference=equalise(nom,tolerance/10)
maxdifference=max(difference,maxdifference)
if maxdifference < tolerance:
return
def seqPhragménwithpostprocessing(votelist,numtoelect):
nomlist,electedcandidates = seqPhragmén(votelist,numtoelect)
equaliseall(nomlist,2,0.1)
return nomlist,electedcandidates
def example1():
votelist=[("A",10.0,["X","Y"]),("B",20.0,["X","Z"]),("C",30.0,["Y","Z"])]
print("Votes ",votelist)
nomlist, electedcandidates = seqPhragmén(votelist,2)
print("Sequential Phragmén gives")
printresult(nomlist, electedcandidates)
nomlist, electedcandidates = approvalvoting(votelist,2)
print()
print("Approval voting gives")
printresult(nomlist, electedcandidates)
nomlist, electedcandidates = seqPhragménwithpostprocessing(votelist,2)
print("Sequential Phragmén with post processing gives")
printresult(nomlist, electedcandidates)
def example2():
votelist = [
("10", 1000, ["10"]),
("20", 1000, ["20"]),
("30", 1000, ["30"]),
("40", 1000, ["40"]),
('2', 500, ['10', '20', '30']),
('4', 500, ['10', '20', '40'])
]
print("Votes ",votelist)
nomlist, electedcandidates = seqPhragmén(votelist,2)
print("Sequential Phragmén gives")
printresult(nomlist, electedcandidates)
nomlist, electedcandidates = approvalvoting(votelist,2)
print()
print("Approval voting gives")
printresult(nomlist, electedcandidates)
nomlist, electedcandidates = seqPhragménwithpostprocessing(votelist,2)
print("Sequential Phragmén with post processing gives")
printresult(nomlist, electedcandidates)
import unittest
class electiontests(unittest.TestCase):
def testexample1Phragmén(self):
votelist=[("A",10.0,["X","Y"]),("B",20.0,["X","Z"]),("C",30.0,["Y","Z"])]
nomlist, electedcandidates = seqPhragmén(votelist,2)
self.assertEqual(electedcandidates[0].valiid,"Z")
self.assertAlmostEqual(electedcandidates[0].score,0.02)
self.assertEqual(electedcandidates[1].valiid,"Y")
self.assertAlmostEqual(electedcandidates[1].score,0.04)
def testexample1approval(self):
votelist=[("A",10.0,["X","Y"]),("B",20.0,["X","Z"]),("C",30.0,["Y","Z"])]
nomlist, electedcandidates = approvalvoting(votelist,2)
self.assertEqual(electedcandidates[0].valiid,"Z")
self.assertAlmostEqual(electedcandidates[0].approvalstake,50.0)
self.assertEqual(electedcandidates[1].valiid,"Y")
self.assertAlmostEqual(electedcandidates[1].approvalstake,40.0)
def dotests():
unittest.main()