Permalink
Browse files

modularized loading of election params, first pass at IRV tallying

  • Loading branch information...
1 parent e4b1fbd commit 526f6c38611fce8d089f4cd2b57a263b91904793 @benadida committed Oct 31, 2009
Showing with 156 additions and 22 deletions.
  1. +5 −3 data.py
  2. +23 −0 electionparams.py
  3. +3 −13 meeting1.py
  4. +40 −6 tally.py
  5. +85 −0 tallydata.py
View
@@ -187,6 +187,7 @@ def __init__(self, partition_info):
# a linear list of all the questions, when the sections don't matter
self.questions = []
+ self.questions_by_id = {}
# a list of questions by partition
self.questions_by_partition = []
@@ -227,6 +228,7 @@ def parse(self, etree):
new_section[q.attrib['id']] = q_object
self.questions.append(q_object)
self.questions_by_partition[q_object.partition_num].append(q_object)
+ self.questions_by_id[q_object.id] = q_object
class Election(object):
@@ -254,7 +256,7 @@ def partition_map_choices(self):
partitions = self.spec.partition_info.partitions
# look up the number of answers for each question within each section
- return [[q_info.max_num_answers for q_info in partition] for partition in partitions]
+ return [[self.spec.sections[q_info['section_id']][q_info['question_id']].max_num_answers for q_info in partition] for partition in partitions]
@property
def num_partitions(self):
@@ -386,7 +388,7 @@ def check_full_row(self, *args):
return self.check_cl(*args) and self.check_cr(*args)
class RTable(Table):
- pass
+ PERMUTATION_FIELDS = ['r']
class Ballot(object):
"""
@@ -488,7 +490,7 @@ def parse_r_tables(etree, path='database/partition'):
r_table = RTable()
r_table.parse(r_table_el)
- partitions[partition_el.attrib['id']] = r_table
+ partitions[int(partition_el.attrib['id'])] = r_table
return partitions
View
@@ -0,0 +1,23 @@
+"""
+Load up just election params
+Scantegrity audit
+
+Ben Adida
+2009-10-31
+"""
+
+import base, data, filenames
+
+partition_xml = base.file_in_dir(base.DATA_PATH, filenames.PARTITIONS, 'Partition File')
+election_xml = base.file_in_dir(base.DATA_PATH, filenames.ELECTION_SPEC, 'Election Spec')
+meeting_one_in_xml = base.file_in_dir(base.DATA_PATH, filenames.MEETING_ONE_IN, 'Meeting One In')
+
+# parse
+partition_info = data.PartitionInfo()
+partition_info.parse(partition_xml)
+
+election_spec = data.ElectionSpec(partition_info)
+election_spec.parse(election_xml)
+
+election = data.Election(election_spec)
+election.parse(meeting_one_in_xml)
View
@@ -10,22 +10,12 @@
import sys
import base, data, filenames
+# base election params
+from electionparams import *
+
# get the election data
-partition_xml = base.file_in_dir(base.DATA_PATH, filenames.PARTITIONS, 'Partition File')
-election_xml = base.file_in_dir(base.DATA_PATH, filenames.ELECTION_SPEC, 'Election Spec')
-meeting_one_in_xml = base.file_in_dir(base.DATA_PATH, filenames.MEETING_ONE_IN, 'Meeting One In')
meeting_one_out_xml = base.file_in_dir(base.DATA_PATH, filenames.MEETING_ONE_OUT, "Meeting One Out")
-# parse
-partition_info = data.PartitionInfo()
-partition_info.parse(partition_xml)
-
-election_spec = data.ElectionSpec(partition_info)
-election_spec.parse(election_xml)
-
-election = data.Election(election_spec)
-election.parse(meeting_one_in_xml)
-
# get the p table and d tables
p_table, partitions = data.parse_database(meeting_one_out_xml)
View
@@ -11,13 +11,47 @@
import sys
import base, data, filenames
-# use the meeting1 and meeting2 data structures too
-import meeting1, meeting4
+import tallydata
-election = meeting1.election
-r_tables = meeting4.r_tables_by_partition
+# use the meeting1 data structures
+from electionparams import *
-import pdb; pdb.set_trace()
+# import just the R tables
+r_tables_xml = base.file_in_dir(base.DATA_PATH, filenames.MEETING_THREE_OUT, 'Meeting Three Out')
+r_tables = data.parse_r_tables(r_tables_xml)
+
+print "ok now tallying\n\n"
+
+# the list of partitions, each of which is a list of the max number of answers each question allows
+partition_map = election.partition_map_choices
+
+# indexed by "p_id/q_id", where each value is an array of ballots of the appropriate type
+BALLOTS = {}
+
+for question in election.spec.questions:
+ BALLOTS[question.id] = []
+
+# go through each partition
+for p_id, r_table in r_tables.iteritems():
+ for row_id, row in r_table.rows.iteritems():
+ # split the result among questions for this partition, according to partition map
+ split_result = r_table.get_permutations_by_row_id(row_id, partition_map[p_id])
+
+ # go through the questions
+ for q_num, question in enumerate(election.spec.questions_by_partition[p_id]):
+ # index 0 because there is only one permutation field in this table,
+ # but it's returned as a list
+ raw_answer = split_result[0][q_num]
+
+ ballot = tallydata.BALLOTS_BY_TYPE[question.type_answer_choice](raw_answer)
+
+ BALLOTS[question.id].append(ballot)
+
+# now tally
+TALLIES = {}
+
+for q_id in BALLOTS.keys():
+ TALLIES[q_id] = BALLOTS[q_id][0].tally(election.spec.questions_by_id[q_id], BALLOTS[q_id])
def tally(output_stream):
@@ -26,7 +60,7 @@ def tally(output_stream):
%s ballots cast
-""" % (election.spec.id, len(ballots_with_codes)))
+""" % (election.spec.id, len(r_tables[0].rows)))
if __name__ == '__main__':
tally(sys.stdout)
View
@@ -0,0 +1,85 @@
+"""
+The data abstractions for plaintext ballot tallying
+part of Scantegrity Audit code
+
+ben@adida.net
+2009-10-31
+"""
+
+BALLOTS_BY_TYPE = {}
+
+class RankBallot(object):
+ def __init__(self, raw_data):
+ # split the raw data into array of choices
+ self.choices = raw_data
+
+ self.reset()
+
+ def go_next_choice(self, choice_to_cancel):
+ if self.exhausted:
+ return
+
+ if self.current_choice == choice_to_cancel:
+ self.__current_index += 1
+
+ if not self.current_choice:
+ self.exhausted = True
+
+ @property
+ def current_choice(self):
+ if self.exhausted:
+ return None
+
+ if len(self.choices) <= self.__current_index:
+ return None
+
+ return self.choices[self.__current_index]
+
+ def reset(self):
+ self.__current_index = 0
+ self.exhausted = False
+
+ @classmethod
+ def tally(cls, question, ballots):
+ """
+ tally a bunch of ranked ballots.
+ Could be easily improved for efficiency, but went for obviously correct code first.
+ Plus, this is pretty quick anyways
+ """
+ # if we round to the half, we increment by 1
+ absolute_majority = len(ballots) / 2 + len(ballots)%2 + 1
+
+ eliminated = []
+ # eliminate and redistribute until done
+ while True:
+ candidate_tallies = [0] * len(question.answers)
+ for i in eliminated:
+ candidate_tallies[i] = None
+
+ try:
+ # count
+ for b in ballots:
+ if not b.exhausted:
+ candidate_tallies[b.current_choice] += 1
+ except Exception, e:
+ print
+ import pdb; pdb.set_trace()
+
+ print candidate_tallies
+
+ if max(candidate_tallies) >= absolute_majority:
+ break
+
+ # eliminate
+ lowest_count = min([tally for tally in candidate_tallies if tally is not None])
+ lowest_count_index = candidate_tallies.index(lowest_count)
+ eliminated.append(lowest_count_index)
+ print "eliminating candidate %s with count %s" % (lowest_count_index, lowest_count)
+ for b in ballots:
+ b.go_next_choice(lowest_count_index)
+
+ return candidate_tallies
+
+BALLOTS_BY_TYPE['rank'] = RankBallot
+BALLOTS_BY_TYPE['one_answer'] = RankBallot
+BALLOTS_BY_TYPE['multiple_answers'] = RankBallot

0 comments on commit 526f6c3

Please sign in to comment.