mirror of
https://github.com/pezkuwichain/consensus.git
synced 2026-06-14 20:21:09 +00:00
Implemented SFFB18 maximin support algorithm and timing to compare them.
This commit is contained in:
+93
-33
@@ -1,4 +1,5 @@
|
|||||||
#from itertools import count
|
#from itertools import count
|
||||||
|
import math
|
||||||
class edge:
|
class edge:
|
||||||
def __init__(self,voterid,canid):
|
def __init__(self,voterid,canid):
|
||||||
self.voterid=voterid
|
self.voterid=voterid
|
||||||
@@ -50,12 +51,12 @@ class assignment:
|
|||||||
self.edgelist = copyassignment.edgelist
|
self.edgelist = copyassignment.edgelist
|
||||||
self.voterload=copyassignment.voterload.copy()
|
self.voterload=copyassignment.voterload.copy()
|
||||||
self.edgeload = copyassignment.edgeload.copy()
|
self.edgeload = copyassignment.edgeload.copy()
|
||||||
self.edgeweight-copyassignment.edgeweight.copy()
|
self.edgeweight=copyassignment.edgeweight.copy()
|
||||||
self.cansupport=copyassignment.cansupport.copy()
|
self.cansupport=copyassignment.cansupport.copy()
|
||||||
self.canelected=copyassignment.caneelected.copy()
|
self.canelected=copyassignment.canelected.copy()
|
||||||
self.electedcandidates=copyassignment.electedcandidates.copy()
|
self.electedcandidates=copyassignment.electedcandidates.copy()
|
||||||
self.canapproval=copyassignment.canapproval.copy()
|
self.canapproval=copyassignment.canapproval.copy()
|
||||||
self.canscore=copyassignment.canscores.copy()
|
self.canscore=copyassignment.canscore.copy()
|
||||||
self.canscorenumerator = copyassignment.canscorenumerator.copy()
|
self.canscorenumerator = copyassignment.canscorenumerator.copy()
|
||||||
self.canscoredenominator = copyassignment.canscoredenominator.copy()
|
self.canscoredenominator = copyassignment.canscoredenominator.copy()
|
||||||
def setload(self, edge,load):
|
def setload(self, edge,load):
|
||||||
@@ -228,10 +229,11 @@ def approvalvoting(votelist,numtoelect):
|
|||||||
a.setweight(edge,nom.budget/numbelected)
|
a.setweight(edge,nom.budget/numbelected)
|
||||||
return a
|
return a
|
||||||
|
|
||||||
def printresult(a,listvoters=True):
|
def printresult(a,listvoters=True,listelectedcandidates=True):
|
||||||
for candidate in a.electedcandidates:
|
if listelectedcandidates:
|
||||||
print(candidate.canid," is elected with stake ",a.cansupport[candidate.index], "and score ",a.canscore[candidate.index])
|
for candidate in a.electedcandidates:
|
||||||
print()
|
print(candidate.canid," is elected with stake ",a.cansupport[candidate.index], "and score ",a.canscore[candidate.index])
|
||||||
|
print()
|
||||||
if listvoters:
|
if listvoters:
|
||||||
for nom in a.voterlist:
|
for nom in a.voterlist:
|
||||||
print(nom.voterid," has load ",a.voterload[nom.index], "and supported ")
|
print(nom.voterid," has load ",a.voterload[nom.index], "and supported ")
|
||||||
@@ -280,7 +282,7 @@ def equalise(a, nom, tolerance):
|
|||||||
return difference
|
return difference
|
||||||
|
|
||||||
import random
|
import random
|
||||||
def equaliseall(a,maxiterations,tolerance):
|
def equaliseall(a,maxiterations,tolerance,debug=False):
|
||||||
for i in range(maxiterations):
|
for i in range(maxiterations):
|
||||||
for j in range(len(a.voterlist)):
|
for j in range(len(a.voterlist)):
|
||||||
nom=random.choice(a.voterlist)
|
nom=random.choice(a.voterlist)
|
||||||
@@ -290,17 +292,20 @@ def equaliseall(a,maxiterations,tolerance):
|
|||||||
difference=equalise(a,nom,tolerance/10)
|
difference=equalise(a,nom,tolerance/10)
|
||||||
maxdifference=max(difference,maxdifference)
|
maxdifference=max(difference,maxdifference)
|
||||||
if maxdifference < tolerance:
|
if maxdifference < tolerance:
|
||||||
|
if debug:
|
||||||
|
print("max iterations ",maxiterations," actual iterations ",i+1)
|
||||||
return
|
return
|
||||||
|
print(" reached max iterations ",maxiterations)
|
||||||
|
|
||||||
def seqPhragménwithpostprocessing(votelist,numtoelect, passes=2):
|
def seqPhragménwithpostprocessing(votelist,numtoelect, ratio=1):
|
||||||
a = seqPhragmén(votelist,numtoelect)
|
a = seqPhragmén(votelist,numtoelect)
|
||||||
equaliseall(a,passes,0.1)
|
passes=math.floor(ratio*numtoelect)
|
||||||
|
equaliseall(a,ratio*numtoelect,0.1, True)
|
||||||
return a
|
return a
|
||||||
|
|
||||||
|
|
||||||
def factor3point15(votelist, numtoelect,tolerance=0.1):
|
def factor3point15(votelist, numtoelect,tolerance=0.1):
|
||||||
nomlist,candidates=setuplists(votelist)
|
nomlist,candidates=setuplists(votelist)
|
||||||
#creating an assignment now also computes the total possible stake for each candidate
|
|
||||||
a=assignment(nomlist,candidates)
|
a=assignment(nomlist,candidates)
|
||||||
|
|
||||||
for round in range(numtoelect):
|
for round in range(numtoelect):
|
||||||
@@ -309,22 +314,38 @@ def factor3point15(votelist, numtoelect,tolerance=0.1):
|
|||||||
equaliseall(a,1000000,tolerance)
|
equaliseall(a,1000000,tolerance)
|
||||||
return a
|
return a
|
||||||
|
|
||||||
def maybecandidate(a,newcandidate,shouldremoveworst, testonly, tolerance):
|
def maybecandidate(a,newcandidate,shouldremoveworst, tolerance):
|
||||||
assert(a.canelected[candidate.index]==False)
|
assert(a.canelected[newcandidate.index]==False)
|
||||||
currentvalue=min([a.cansupport[candidate.index] for candidate in a.electedcandidates])
|
currentvalue=min([a.cansupport[candidate.index] for candidate in a.electedcandidates])
|
||||||
#To find a new assignment without losing our current one, we will need to copy the edges
|
#To find a new assignment without losing our current one, we will need to copy the edges
|
||||||
b=assignment(a)
|
b=assignment(a.voterlist,a.candidates,a)
|
||||||
if shouldremoveworst:
|
if shouldremoveworst:
|
||||||
worstcanidate =min(electedcandidates, key = lambda x: b.cansupport[x.index])
|
worstcanidate =min(electedcandidates, key = lambda x: b.cansupport[x.index])
|
||||||
b.unelect[worstcandidate]
|
b.unelect(worstcandidate)
|
||||||
b.elect[newcandidate]
|
b.elect(newcandidate)
|
||||||
equaliseall(b,100000000,0.1)
|
equaliseall(b,100000000,tolerance)
|
||||||
newvalue=currentvalue=min([b.cansupport[candidate.index] for candidate in electedcandidates])
|
newvalue=min([b.cansupport[candidate.index] for candidate in b.electedcandidates])
|
||||||
if not (testonly or (shouldremoveworst and newvalue < currentvalue)):
|
return b, newvalue
|
||||||
return b,newvalue
|
|
||||||
a = b
|
|
||||||
return a, newvalue
|
|
||||||
|
|
||||||
|
def SFFB18(votelist, numtoelect,tolerance=0.1):
|
||||||
|
nomlist,candidates=setuplists(votelist)
|
||||||
|
a=assignment(nomlist,candidates)
|
||||||
|
for round in range(numtoelect):
|
||||||
|
if round == 0:
|
||||||
|
newcandidate=max([(can,a.canapproval[can.index]) for can in a.candidates],key = lambda x : x[1])[0]
|
||||||
|
a.elect(newcandidate)
|
||||||
|
equaliseall(a,1,tolerance)
|
||||||
|
else:
|
||||||
|
bestvalue=0
|
||||||
|
for can in a.candidates:
|
||||||
|
if not a.canelected[can.index]:
|
||||||
|
b,newvalue = maybecandidate(a,can, False, tolerance)
|
||||||
|
if newvalue > bestvalue:
|
||||||
|
bestassignment=b
|
||||||
|
bestvalue=newvalue
|
||||||
|
if bestvalue > 0:
|
||||||
|
a=bestassignment
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
@@ -344,22 +365,40 @@ class electiontests(unittest.TestCase):
|
|||||||
def dotests():
|
def dotests():
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
||||||
def doall(votelist, numtoelect, listvoters=True):
|
import time
|
||||||
|
def doall(votelist, numtoelect, listvoters=True, listcans=True):
|
||||||
if listvoters:
|
if listvoters:
|
||||||
print("Votes ",votelist)
|
print("Votes ",votelist)
|
||||||
a = seqPhragmén(votelist,numtoelect)
|
st=time.perf_counter()
|
||||||
print("Sequential Phragmén gives")
|
|
||||||
printresult(a,listvoters)
|
|
||||||
a = approvalvoting(votelist,numtoelect)
|
a = approvalvoting(votelist,numtoelect)
|
||||||
print()
|
et=time.perf_counter()
|
||||||
print("Approval voting gives")
|
print("Approval voting gives")
|
||||||
printresult(a,listvoters)
|
printresult(a,listvoters,listcans)
|
||||||
|
print(" in ",et-st," seconds.")
|
||||||
|
st=time.perf_counter()
|
||||||
|
a = seqPhragmén(votelist,numtoelect)
|
||||||
|
et=time.perf_counter()
|
||||||
|
print("Sequential Phragmén gives")
|
||||||
|
printresult(a,listvoters,listcans)
|
||||||
|
print(" in ",et-st," seconds.")
|
||||||
|
st=time.perf_counter()
|
||||||
a = seqPhragménwithpostprocessing(votelist,numtoelect)
|
a = seqPhragménwithpostprocessing(votelist,numtoelect)
|
||||||
|
et=time.perf_counter()
|
||||||
print("Sequential Phragmén with post processing gives")
|
print("Sequential Phragmén with post processing gives")
|
||||||
printresult(a,listvoters)
|
printresult(a,listvoters,listcans)
|
||||||
|
print(" in ",et-st," seconds.")
|
||||||
|
st=time.perf_counter()
|
||||||
a = factor3point15(votelist,numtoelect)
|
a = factor3point15(votelist,numtoelect)
|
||||||
|
et=time.perf_counter()
|
||||||
print("The factor 3.15 thing gives")
|
print("The factor 3.15 thing gives")
|
||||||
printresult(a,listvoters)
|
printresult(a,listvoters,listcans)
|
||||||
|
print(" in ",et-st," seconds.")
|
||||||
|
st=time.perf_counter()
|
||||||
|
a = SFFB18(votelist,numtoelect)
|
||||||
|
et=time.perf_counter()
|
||||||
|
print("SFFB18 gives")
|
||||||
|
printresult(a,listvoters,listcans)
|
||||||
|
print(" in ",et-st," seconds.")
|
||||||
|
|
||||||
|
|
||||||
def example1():
|
def example1():
|
||||||
@@ -401,6 +440,14 @@ def example5():
|
|||||||
print("Votes ",votelist)
|
print("Votes ",votelist)
|
||||||
doall(votelist,4)
|
doall(votelist,4)
|
||||||
|
|
||||||
|
def example6():
|
||||||
|
#Now we want an example where seq Phragmén is not so good.
|
||||||
|
votelist=[("A",100.0,["V","W","X","Y","Z"]),("B",100.0,["W","X","Y","Z"]),
|
||||||
|
("C",100.0,["X","Y","Z"]),("D",100.0,["Y","Z"]), ("E",100.0,["Z"]),
|
||||||
|
("M",50.0, ["M"])]
|
||||||
|
print("Votes ",votelist)
|
||||||
|
doall(votelist,5)
|
||||||
|
|
||||||
def exampleLine():
|
def exampleLine():
|
||||||
votelist = [
|
votelist = [
|
||||||
("a", 2000, ["A"]),
|
("a", 2000, ["A"]),
|
||||||
@@ -413,9 +460,22 @@ def exampleLine():
|
|||||||
]
|
]
|
||||||
doall(votelist,7)
|
doall(votelist,7)
|
||||||
|
|
||||||
|
def ri(vals=20,noms=2000, votesize=10):
|
||||||
|
#Let's try a random instance
|
||||||
|
candidates=["Val"+str(i) for i in range(vals)]
|
||||||
|
votelist=[("Nom"+str(i), 100, random.sample(candidates,votesize)) for i in range(noms)]
|
||||||
|
doall(votelist, vals // 2, False, False)
|
||||||
|
|
||||||
|
def riparty(vals=200,noms=2000, votesize=10):
|
||||||
|
#Half the validators are in a party which 1/4 of the nominators vote for.
|
||||||
|
# Approval voting does worse now
|
||||||
|
# and this is probably more realistic than the pure random instance.
|
||||||
|
candidates=["Val"+str(i) for i in range(vals//2)]
|
||||||
|
partycandidates=["PartyVal"+str(i) for i in range(vals- vals // 2)]
|
||||||
|
partynoms = noms // 4
|
||||||
|
votelist=[("Nom"+str(i), 100, random.sample(candidates,votesize)) for i in range(noms - partynoms)]
|
||||||
|
votelist += [("Nom"+str(i), 100, partycandidates) for i in range(partynoms)]
|
||||||
|
doall(votelist, vals // 4, False, False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user