def testDefeatBatch(self): "tag is name of rule" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), dict(rule='wigm-prf')) self.assertEqual(E.rule.tag(), 'wigm-prf') E = Election(ElectionProfile(data=b), dict(rule='wigm-prf-batch')) self.assertEqual(E.rule.tag(), 'wigm-prf-batch')
def testElectAll(self): "count a profile with nSeats candidates" b = '''2 2 4 1 0 4 2 0 2 1 0 0 "Castor" "Pollux" "test nseats candidates"''' E = Election(ElectionProfile(data=b), dict(rule='cfer')) E.count() elected = [c.name for c in E.elected] self.assertEqual(elected, ['Castor', 'Pollux']) defeated = [c.name for c in E.defeated] self.assertEqual(defeated, []) report = E.report() self.assertTrue(report.find('Elect all'))
def testDefeatRemaining(self): "count a profile that has hopeful candidates left over to defeat" b = '''3 2 4 1 0 4 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "test defeat-remaining"''' E = Election(ElectionProfile(data=b), dict(rule='wigm-prf')) E.count() elected = [c.name for c in E.elected] self.assertEqual(elected, ['Castor', 'Pollux']) defeated = [c.name for c in E.defeated] self.assertEqual(defeated, ['Helen']) report = E.report() self.assertTrue(report.find('Defeat remaining'))
def testDefeatBatch(self): "count a profile that has with defeat_batch option" b = '''4 2 4 1 0 3 2 0 2 3 2 0 0 "Castor" "Pollux" "Helen" "George" "test defeat-batch"''' E = Election(ElectionProfile(data=b), dict(rule="wigm", defeat_batch="zero")) E.count() elected = [c.name for c in E.elected] self.assertEqual(elected, ["Castor", "Pollux"]) defeated = [c.name for c in E.defeated] self.assertEqual(defeated, ["Helen", "George"]) report = E.report() self.assertTrue(report.find("Defeat batch"))
def testDefeatBatch(self): "count a profile that has with defeat_batch option" b = '''4 2 4 1 0 3 2 0 2 3 2 0 0 "Castor" "Pollux" "Helen" "George" "test defeat-batch"''' E = Election(ElectionProfile(data=b), dict(rule='wigm', defeat_batch='zero')) E.count() elected = [c.name for c in E.elected] self.assertEqual(elected, ['Castor', 'Pollux']) defeated = [c.name for c in E.defeated] self.assertEqual(defeated, ['Helen', 'George']) report = E.report() self.assertTrue(report.find('Defeat batch'))
def testDroopOptions(self): "test [droop ...]" if 'meek' in droop.electionRuleNames(): b = '''3 2 [droop arithmetic=fixed precision=4] 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), dict(rule='meek')) self.assertEqual(E.V.precision, 4) E = Election(ElectionProfile(data=b), dict(rule='meek', precision=6)) self.assertEqual(E.V.precision, 6) b = '''3 2 [droop rational precision=4] 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), dict(rule='meek')) self.assertEqual(E.V.name, 'rational') b = '''3 2 [droop meek] 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), dict()) self.assertEqual(E.rule.method, 'meek') E = Election(ElectionProfile(data=b), None) self.assertEqual(E.rule.method, 'meek') b = '''3 2 [droop dump meek] 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), None) self.assertTrue(E.options.getopt('dump')) b = '''3 2 [droop dump=true meek] 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), None) self.assertTrue(E.options.getopt('dump')) b = '''3 2 [droop dump=false meek] 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), None) self.assertFalse(E.options.getopt('dump')) # fake a path to test double-path logic b = '''3 2 [droop 42.blt 513.blt meek] 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' self.assertRaises(UsageError, Election, ElectionProfile(data=b), dict())
def testNickReport(self): "using nicknames shouldn't alter dump or report" b1 = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' b2 = '''3 2 [nick a b c] 4 a b 0 2 c 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b1), dict(rule='wigm-prf')) E.count() r1 = E.report() d1 = E.dump() E = Election(ElectionProfile(data=b2), dict(rule='wigm-prf')) E.count() r2 = E.report() d2 = E.dump() self.assertEqual(r1, r2) self.assertEqual(d1, d2)
def usage(subject=None): "usage and help" helps = Election.makehelp() helpers = sorted(helps.keys()) u = '\n%s v%s\n' % (droop.common.droopName, droop.common.droopVersion) u += '\nUsage:\n' u += '%s options ballotfile\n' % me u += ' options:\n' u += ' rule name (%s)\n' % ','.join(droop.electionRuleNames()) u += ' arithmetic class name (%s)\n' % ','.join(droop.values.arithmeticNames) u += ' profile=reps, to profile the count, running reps repetitions\n' u += ' dump, to dump a csv of the election actions\n' u += ' rule- or arithmetic-specific options:\n' u += ' precision=n: decimal digits of precision (fixed, guarded)\n' u += ' guard=n: guard digits (guarded; default to guard=precision)\n' u += ' dp=n: display precision (rational)\n' u += ' omega=n: meek iteration terminates when surplus < 1/10^omega\n' u += '\n' u += ' help is available on the following subjects:\n' u += ' %s' % ' '.join(helpers) helps['usage'] = u if not subject: return u if subject in helps: return '\n%s' % helps[subject] return 'no help available on %s' % subject
def usage(subject=None): "usage and help" helps = Election.makehelp() helpers = sorted(helps.keys()) u = '\n%s v%s\n' % (droop.common.droopName, droop.common.droopVersion) u += '\nUsage:\n' u += '%s options ballotfile\n' % me u += ' options:\n' u += ' rule name (%s)\n' % ','.join(droop.electionRuleNames()) u += ' arithmetic class name (%s)\n' % ','.join( droop.values.arithmeticNames) u += ' profile=reps, to profile the count, running reps repetitions\n' u += ' dump, to dump a csv of the election actions\n' u += ' rule- or arithmetic-specific options:\n' u += ' precision=n: decimal digits of precision (fixed, guarded)\n' u += ' guard=n: guard digits (guarded; default to guard=precision)\n' u += ' dp=n: display precision (rational)\n' u += ' omega=n: meek iteration terminates when surplus < 1/10^omega\n' u += '\n' u += ' help is available on the following subjects:\n' u += ' %s' % ' '.join(helpers) helps['usage'] = u if not subject: return u if subject in helps: return '\n%s' % helps[subject] return 'no help available on %s' % subject
def testMeekWarren1(self): "meek responds to warren" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' profile = ElectionProfile(data=b) E = Election(profile, dict(rule='warren')) self.assertEqual(E.rule.tag(), 'warren-o9') self.assertRaises(UsageError, Election, profile, dict(rule='warren', defeat_batch='whatever'))
def testArithmetic(self): "wigm-prf uses fixed" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b), dict(rule='wigm-prf', arithmetic='guarded', abc=4)) self.assertEqual(E.options.getopt('arithmetic'), 'fixed') self.assertEqual(E.options.getopt('precision'), 4) self.assertEqual(E.options.overrides(), ['arithmetic']) self.assertEqual(E.options.unused(), ['abc'])
def testRules(self): "basic test of each rule" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' profile = ElectionProfile(data=b) for rulename in droop.electionRuleNames(): options = dict(rule=rulename) E = Election(profile, options) self.assertTrue(E.rule.__class__.__name__ == 'Rule', 'bad rule class') self.assertTrue(len(options) >= 1, 'rule should set/leave at least one option') self.assertTrue(E.options.getopt('arithmetic') in ('fixed', 'integer', 'guarded', 'rational'), 'legal arithmetic') candidates = E.C self.assertTrue("Castor" in [c.name for c in candidates]) self.assertTrue("Castor" in [str(c) for c in candidates]) self.assertTrue(1 in [c for c in candidates]) for c in candidates: self.assertEqual(c.order, c.tieOrder) E.count() self.assertEqual(len(E.elected), E.nSeats)
class TestQpq(unittest.TestCase): ''' Create an Election instance from a simple profile and the QPQ rule and test its basic initialization, and that it elects the specified number of seats. ''' def setUp(self): "initialize profile and rule" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' self.Profile = ElectionProfile(data=b) self.E = Election(self.Profile, dict(rule='qpq')) def testElectionInit(self): "check that election is initialized" E = self.E self.assertTrue(E.rule.__class__.__name__ == 'Rule', 'bad rule class') self.assertEqual(len(E.options.force), 4, 'qpq should force 4 options') self.assertEqual(E.options.getopt('arithmetic'), 'guarded', 'qpq should set arithmetic=guarded') self.assertEqual(E.options.getopt('precision'), 9, 'qpq should set precision=9') self.assertEqual(E.options.getopt('guard'), 9, 'qpq should set guard=9') self.assertEqual(E.options.getopt('display'), 9, 'qpq should set display=9') self.assertEqual(E.C.byCid(1).name, "Castor") self.assertEqual(str(E.C.byCid(1)), "Castor") self.assertTrue(E.C.byCid(1) == 1) self.assertTrue(E.C.byCid(1) == '1') self.assertFalse(E.C.byCid(1) == None) def testElectionTieOrder(self): "test default tie order" for c in self.E.C: self.assertEqual(c.order, c.tieOrder) def testElectionCount1(self): "try a basic count" self.E.count() self.assertEqual(len(self.E.elected), self.E.nSeats)
def testRules(self): "basic test of each rule" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' profile = ElectionProfile(data=b) for rulename in droop.electionRuleNames(): options = dict(rule=rulename) E = Election(profile, options) self.assertTrue(E.rule.__class__.__name__ == 'Rule', 'bad rule class') self.assertTrue( len(options) >= 1, 'rule should set/leave at least one option') self.assertTrue( E.options.getopt('arithmetic') in ('fixed', 'integer', 'guarded', 'rational'), 'legal arithmetic') candidates = E.C self.assertTrue("Castor" in [c.name for c in candidates]) self.assertTrue("Castor" in [str(c) for c in candidates]) self.assertTrue(1 in [c for c in candidates]) for c in candidates: self.assertEqual(c.order, c.tieOrder) E.count() self.assertEqual(len(E.elected), E.nSeats)
def testElectionQpq1(self): "qpq: everyone elected at first" b = '''3 2 4 1 2 0 4 2 1 0 1 3 0 0 "a" "b" "c" "2 elected at first"''' E = Election(ElectionProfile(data=b), dict(rule='qpq')) E.count() self.assertEqual(len(E.elected), 2)
def doCount(options, blt): "run the count and return the Election" p = ElectionProfile(testdir + '/blt/' + blt) E = Election(p, options) E.count() return E
def testNickReport(self): "using nicknames shouldn't alter dump or report" b1 = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' b2 = '''3 2 [nick a b c] 4 a b 0 2 c 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' E = Election(ElectionProfile(data=b1), dict(rule='meek-prf')) E.count() r1 = E.report() d1 = E.dump() E = Election(ElectionProfile(data=b2), dict(rule='meek-prf')) E.count() r2 = E.report() d2 = E.dump() self.assertEqual(r1, r2) self.assertEqual(d1, d2)
def setUp(self): "initialize profile and rule" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' self.Profile = ElectionProfile(data=b) self.E = Election(self.Profile, dict(rule='qpq'))
def doQpqCount(filename): "each of five elections from the Woodall paper" blt = os.path.join(testdir, 'blt', 'qpq', filename) E = Election(ElectionProfile(blt), dict(rule='qpq')) E.count() return E
def getDump(options, base): "run a count and return the dump" blt = '%s/blt/%s.blt' % (testdir, base) E = Election(ElectionProfile(blt), options) E.count() return E.dump()
def testElectionMpls1(self): "mpls: everyone elected at first" p_mpls1 = '''3 2 4 1 2 0 4 2 1 0 1 3 0 0 "a" "b" "c" "2 elected at first"''' E = Election(ElectionProfile(data=p_mpls1), dict(rule='mpls')) E.count() self.assertEqual(len(E.elected), 2)
def doDumpCompare(options, filename, subdir=''): ''' helper: do a count and compare dump/report to reference ''' if not filename.endswith('.blt'): filename += '.blt' base, ext = os.path.splitext(filename) # pylint: disable=W0612 blt = os.path.join(testdir, 'blt', subdir, filename) E = Election(ElectionProfile(blt), options) E.count() tag = '%s-%s-%s' % (base, E.rule.tag(), E.V.tag()) def readFile(path): "read a json/dump/report file" f = open(path, 'r') data = f.read() f.close() return data def writeFile(path, data): "write a json/dump/report file" if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) f = open(path, 'w') f.write(data) f.close() # first do report # rref = os.path.join(testdir, 'ref', 'report', subdir, '%s.txt' % tag) rout = os.path.join(testdir, 'out', 'report', subdir, '%s.txt' % tag) report = E.report() if not os.path.isfile(rref): writeFile(rref, report) reportref = readFile(rref) if os.path.isfile(rout): os.unlink(rout) # don't include version number in comparison report0 = re.sub(r'droop v\d+\.\d+', 'droop v0.0', report) reportref = re.sub(r'droop v\d+\.\d+', 'droop v0.0', reportref) if report0 != reportref: writeFile(rout, report) if compare_report: return False # same logic with json # sref = os.path.join(testdir, 'ref', 'json', subdir, '%s.txt' % tag) sout = os.path.join(testdir, 'out', 'json', subdir, '%s.txt' % tag) json = E.json() if not os.path.isfile(sref): writeFile(sref, json) jsonref = readFile(sref) if os.path.isfile(sout): os.unlink(sout) # don't include version number in comparison json0 = re.sub(r'"droop_version": "\d+\.\d+"', '"droop_version": "0.0"', json) jsonref = re.sub(r'"droop_version": "\d+\.\d+"', '"droop_version": "0.0"', jsonref) if json0 != jsonref: writeFile(sout, json) if compare_json: return False # same logic with dump # dref = os.path.join(testdir, 'ref', 'dump', subdir, '%s.txt' % tag) dout = os.path.join(testdir, 'out', 'dump', subdir, '%s.txt' % tag) dump = E.dump() if not os.path.isfile(dref): writeFile(dref, dump) dumpref = readFile(dref) if os.path.isfile(dout): os.unlink(dout) if dump != dumpref: writeFile(dout, dump) if compare_dump: return False return True
def testReports(self): "look at election outputs" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' profile = ElectionProfile(data=b) rulename = droop.electionRuleNames()[0] # pick the first rule arbitrarily E = Election(profile, dict(rule=rulename)) E.count() self.assertEqual(E.report().find('interrupted'), -1) self.assertTrue(E.report(intr=True).find('interrupted') > 0) E = Election(profile, dict(rule=rulename)) E.count() self.assertEqual(E.dump().find('interrupted'), -1) self.assertTrue(E.dump(intr=True).find('interrupted') > 0) E = Election(profile, dict(rule=rulename)) E.count() self.assertEqual(E.json().find('interrupted'), -1) self.assertTrue(E.json(intr=True).find('interrupted') > 0) r = E.record() self.assertTrue(r, dict) self.assertEqual(r['actions'][-1]['tag'], 'log')
def calc_winners(self): # Import here to avoid loop in loading from politicians.models import Politician r = self # All ballots for a riding all_ballots = r.ballots() # All ballots for the calculation # TODO: Should this filtering move to the Riding class or Ballot class? calculation_ballots = all_ballots.filter(state='C').filter(spoiled=False) # All spoiled ballots # TODO: Should this filtering move to the Riding class or Ballot class? num_spoiled_ballots = r.num_spoiled_ballots() # All candidates for the riding c = self.candidates() # Get distinct ballot contents and how many times they occured b2 = calculation_ballots.values("vote").annotate(cnt=Count('vote')) # Dictionary of (key=droop candidate ID, value=politician.id) c2 = dict((i+1,v.id) for i, v in enumerate(list(c))) # Dictionary of (key=politician.id, value=droop candidate ID) c2b = dict((v,k) for k,v in c2.iteritems()) fc_votes = {} for politician in c: fc_votes[politician.id] = 0 # More sanity if r.num_seats < 1: raise DroopElectionProfileError("Too few Seats in " + r.name) if r.num_candidates() < r.num_seats: raise DroopElectionProfileError("Too few Candidates in " + r.name + ". " + str(r.num_seats) + " seats for " + str(r.num_candidates()) + " candidates.") #check if the number of ballots is enough for BCSTV if calculation_ballots.count() < (r.num_candidates() + 1): raise DroopElectionProfileError("Too few ballots to calculate BCSTV") # Start of BLT generation # Number of candidates, Number of seats data = str(c.count()) + " " + str(r.num_seats) + "\n" # For each distinct ballot content for ballot in b2: # Count of timesro_home.html data = data + str(ballot['cnt']) + " " # Content of ballot vote_line = json.loads(ballot['vote']) first = True for _i, _c in vote_line.iteritems(): # Of the droop ID numbers for the candidate if _c == "": # If empty, skip (empty line on ballot) pass else: data = data + str(c2b[int(_c)]) + " " if first: fc_votes[int(_c)] += ballot['cnt'] first = False # 0 to say no more candidates on ballot data = data + "0\n" # 0 to say no more ballots data = data + "0\n" # candidates in droop order for key, candidate in c2.iteritems(): data = data + "\"" + unicode(candidate) + "\"\n" # Name of election data = data + "\"" + r.name + " Results\"" # End of BLT generation try: E = DroopElection(DroopElectionProfile(data=data.encode('ascii', 'ignore')), dict(rule='bcstv')) except DroopElectionProfileError as e: raise e E.count() result = E.record() candidate_states = {} for i in range(len(result['actions'][-1]['cstate'])): temp = {} k = result['cdict'][i+1]['name'] pol = Politician.objects.get(id = k) temp['droop_cstate'] = result['actions'][-1]['cstate'][i+1] temp['first_choice_votes'] = fc_votes[int(k)] temp['droop_id'] = i temp['cand_id'] = k candidate_states[pol] = temp return { 'E': E, 'candidate_states': candidate_states, 'result': result, 'candidates': c, 'num_spoiled_ballots': num_spoiled_ballots, 'nballots': result['nballots'], 'riding': self, }
def testReports(self): "look at election outputs" b = '''3 2 4 1 2 0 2 3 0 0 "Castor" "Pollux" "Helen" "Pollux and Helen should tie"''' profile = ElectionProfile(data=b) rulename = droop.electionRuleNames()[ 0] # pick the first rule arbitrarily E = Election(profile, dict(rule=rulename)) E.count() self.assertEqual(E.report().find('interrupted'), -1) self.assertTrue(E.report(intr=True).find('interrupted') > 0) E = Election(profile, dict(rule=rulename)) E.count() self.assertEqual(E.dump().find('interrupted'), -1) self.assertTrue(E.dump(intr=True).find('interrupted') > 0) E = Election(profile, dict(rule=rulename)) E.count() self.assertEqual(E.json().find('interrupted'), -1) self.assertTrue(E.json(intr=True).find('interrupted') > 0) r = E.record() self.assertTrue(r, dict) self.assertEqual(r['actions'][-1]['tag'], 'log')
def main(options=None): "run an election" if not options: raise droop.common.UsageError("no ballot file specified") # process options # # we know about (path, profile) # all the others are passed to the various consumers # path = None # ballot path must be specified doProfile = False # performance profiling reps = 1 # repetitions (for profiling) for opt, arg in options.items(): if opt == 'path': # path=<path to ballot file> path = arg elif opt == 'profile': # profile=<number of repetitions> import cProfile import pstats reps = int(arg) doProfile = True profilefile = "profile.out" if not path: raise droop.common.UsageError("no ballot file specfied") # run the election # # fetch the election profile # create the Election object # count # report # def countElection(E, repeat=1): "encapsulate for optional profiling" for i in xrange(repeat): # pylint: disable=W0612 E.count() electionProfile = ElectionProfile( path=path) # don't repeat the profile loading E = Election(electionProfile, options) try: intr = False if doProfile: cProfile.runctx('countElection(E, reps)', globals(), locals(), profilefile) else: countElection(E, reps) except KeyboardInterrupt: intr = True E.options.setopt('dump', default=False) E.options.setopt('json', default=False) ereport = '' if E.options.setopt('report', default=True): ereport += E.report(intr) if E.options.getopt('dump'): ereport += E.dump(intr) if E.options.getopt('json'): ereport += E.json(intr) if doProfile: p = pstats.Stats(profilefile) p.strip_dirs().sort_stats('time').print_stats(50) return ereport
def main(options=None): "run an election" if not options: raise droop.common.UsageError("no ballot file specified") # process options # # we know about (path, profile) # all the others are passed to the various consumers # path = None # ballot path must be specified doProfile = False # performance profiling reps = 1 # repetitions (for profiling) for opt, arg in options.items(): if opt == 'path': # path=<path to ballot file> path = arg elif opt == 'profile': # profile=<number of repetitions> import cProfile import pstats reps = int(arg) doProfile = True profilefile = "profile.out" if not path: raise droop.common.UsageError("no ballot file specfied") # run the election # # fetch the election profile # create the Election object # count # report # def countElection(E, repeat=1): "encapsulate for optional profiling" for i in xrange(repeat): # pylint: disable=W0612 E.count() electionProfile = ElectionProfile(path=path) # don't repeat the profile loading E = Election(electionProfile, options) try: intr = False if doProfile: cProfile.runctx('countElection(E, reps)', globals(), locals(), profilefile) else: countElection(E, reps) except KeyboardInterrupt: intr = True E.options.setopt('dump', default=False) E.options.setopt('json', default=False) ereport = '' if E.options.setopt('report', default=True): ereport += E.report(intr) if E.options.getopt('dump'): ereport += E.dump(intr) if E.options.getopt('json'): ereport += E.json(intr) if doProfile: p = pstats.Stats(profilefile) p.strip_dirs().sort_stats('time').print_stats(50) return ereport
def testElectionHelps(self): "test helps" helps = Election.makehelp() self.assertTrue(isinstance(helps['rule'], str)) self.assertTrue(isinstance(helps['arithmetic'], str))