You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@steve.apache.org by gs...@apache.org on 2022/06/02 02:52:00 UTC

[steve] branch trunk updated (61cfe1b -> 295b28f)

This is an automated email from the ASF dual-hosted git repository.

gstein pushed a change to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git


    from 61cfe1b  gather stv data for testing
     new b5e7298  shift to a main() function; particularly to clarify the VERBOSE global
     new 6354b41  Rejigger entrypoints in stv_tool.py
     new a991c7f  small tweak to collecting a list of unique rando values
     new 8de31df  use sum() to replace a couple loops
     new 295b28f  shift calc_aheads() into CandidateList. note potential error.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 monitoring/stv_tool.py | 116 +++++++++++++++++++++++++++++--------------------
 1 file changed, 69 insertions(+), 47 deletions(-)


[steve] 05/05: shift calc_aheads() into CandidateList. note potential error.

Posted by gs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git

commit 295b28f5052bbc645b16fce87008f787862ca49c
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Wed Jun 1 21:51:48 2022 -0500

    shift calc_aheads() into CandidateList. note potential error.
---
 monitoring/stv_tool.py | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/monitoring/stv_tool.py b/monitoring/stv_tool.py
index a946a5b..12609e7 100755
--- a/monitoring/stv_tool.py
+++ b/monitoring/stv_tool.py
@@ -202,7 +202,7 @@ class CandidateList(object):
     dbg('%-20s %15s %15.9f', 'Non-transferable', ' ', excess)
     dbg('%-20s %15s %15.9f', 'Total', ' ', total)
 
-  def sorted(self):
+  def calc_aheads(self):
     def compare(c1, c2):
       if c1.ahead < c2.ahead:
         return -1
@@ -210,7 +210,15 @@ class CandidateList(object):
         ### expression for removed cmp() function
         return (c1.vote > c2.vote) - (c1.vote < c2.vote)
       return 1
-    return sorted(self.l, key=functools.cmp_to_key(compare))
+
+    c_sorted = sorted(self.l, key=functools.cmp_to_key(compare))
+    last = 0
+    for i in range(1, len(c_sorted)+1):
+      ### STV.java:L233 ... This may need .vote on the latter compare
+      if i == len(c_sorted) or c_sorted[last] != c_sorted[i]:
+        for c in c_sorted[last:i]:
+          c.ahead = (i - 1) + last
+        last = i
 
 
 class Candidate(object):
@@ -254,7 +262,11 @@ def iterate_one(quota, votes, candidates, num_seats):
 
 def recalc(votes, candidates, num_seats):
   excess = calc_totals(votes, candidates)
-  calc_aheads(candidates)
+  a1 = [c.ahead for c in candidates.l]
+  candidates.calc_aheads()
+  a2 = [c.ahead for c in candidates.l]
+  if a1 != a2:
+    pass #print(f'CHANGE:\n  {a1}\n  {a2}')
   candidates.dbg_display_tables(excess)
   quota = calc_quota(len(votes), excess, num_seats)
   any_changed = elect(quota, candidates, num_seats)
@@ -284,14 +296,9 @@ def calc_totals(votes, candidates):
   return excess
 
 
+#@deprecated
 def calc_aheads(candidates):
-  c_sorted = candidates.sorted()
-  last = 0
-  for i in range(1, len(c_sorted)+1):
-    if i == len(c_sorted) or c_sorted[last] != c_sorted[i]:
-      for c in c_sorted[last:i]:
-        c.ahead = (i - 1) + last
-      last = i
+  candidates.calc_aheads()
 
 
 def calc_quota(num_voters, excess, num_seats):


[steve] 02/05: Rejigger entrypoints in stv_tool.py

Posted by gs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git

commit 6354b4179e7cf64f41d3ca58ccfb7b9526ff7bcd
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Wed Jun 1 19:11:48 2022 -0500

    Rejigger entrypoints in stv_tool.py
    
    The old (unpublished?) API used load_votes() and read_nominees().
    Create read_votefile() as a basic function to read the votes out of a
    raw_board_votes.txt file. Similarly, read_labelmap() as a simple
    function to read a lable:name mapping from board_nominations.ini files.
    
    Concept: these two core functions return the needed data for running
    an STV vote. Clarify these, and deprecate the older functions which
    layer upon and alter the core data.
    
    NOTE: it is unclear who may consume this module, so backwards-compat
    is being retained for now.
---
 monitoring/stv_tool.py | 56 ++++++++++++++++++++++++++++++++------------------
 1 file changed, 36 insertions(+), 20 deletions(-)

diff --git a/monitoring/stv_tool.py b/monitoring/stv_tool.py
index de20885..cb7ffbc 100755
--- a/monitoring/stv_tool.py
+++ b/monitoring/stv_tool.py
@@ -51,9 +51,12 @@ RE_VOTE = re.compile(r'\[.{19}\]\s+'
 VERBOSE = False
 
 
+#@deprecated
 def load_votes(fname):
-  lines = open(fname).readlines()
-  if lines[0].strip() == 'rank order':
+  line = open(fname).readline()
+  if line.strip() == 'rank order':
+    lines = open(fname).readlines()
+
     # The input file was processed by nstv-rank.py, or somehow otherwise
     # converted to the standard input for VoteMain.jar
     names = [s.strip() for s in lines[1].strip().split(',')][1:]
@@ -67,41 +70,54 @@ def load_votes(fname):
       votes[parts[0]] = [remap[l] for l in parts[1:]]
     return names, votes
 
-  # Let's assume we're looking at a raw_board_votes.txt file, and a
-  # companion board_nominations.ini file.
-  nominees = read_nominees(fname)
+  ini_fname = os.path.join(os.path.dirname(fname),
+                           'board_nominations.ini')
+  labelmap = read_labelmap(ini_fname)
+
+  # Construct a label-sorted list of names from the labelmap.
+  names = [name for _, name in sorted(labelmap.items())]
 
+  # Load the raw votes that were recorded.
+  votes_by_label = read_votefile(fname)
+
+  # Remap all labels to names in the votes.
+  # NOTE: v represents the voter hash. (### why return this?)
+  votes = dict((v, [labelmap[l] for l in vote])
+               for v, vote in votes_by_label.items())
+
+  return names, votes
+
+
+def read_votefile(fname):
   votes = { }
-  for line in lines:
+  for line in open(fname).readlines():
     match = RE_VOTE.match(line)
     if match:
-      # For a given voter hashcode, record their latest set of votes,
-      # mapped from (character) labels to the nominee's name.
-      votes[match.group('voterhash')] = [nominees[label]
-                                         for label in match.group('votes')]
+      # For a given voter hashcode, record their latest set of votes.
+      votes[match.group('voterhash')] = match.group('votes')
 
-  # Map the nominee dictionary into a label-sorted list of names
-  names = [nominees[label] for label in sorted(nominees)]
-
-  return names, votes
+  ### we should discard voterhash, and just return .values()
+  return votes
 
 
+#@deprecated
 def read_nominees(votefile):
   ini_fname = os.path.join(os.path.dirname(votefile),
                            'board_nominations.ini')
+  return read_labelmap(ini_fname)
+
 
-  # Use the below try instead to catch this??
-  if not os.path.exists(ini_fname):
-    print("Error: board_nominations.ini could not be found at " + ini_fname, file=sys.stderr)
+def read_labelmap(fname):
+  if not os.path.exists(fname):
+    print(f'ERROR: "{fname}" could not be found.', file=sys.stderr)
     sys.exit(2)
 
   config = configparser.ConfigParser()
-  config.read(ini_fname)
+  config.read(fname)
   try:
     return dict(config.items('nominees'))
   except:
-    print("Error processing input file: " + ini_fname, file=sys.stderr)
-    print(" Goodbye!", file=sys.stderr)
+    print(f'ERROR: could not process input file, "{fname}".', file=sys.stderr)
     sys.exit(2)
 
 


[steve] 01/05: shift to a main() function; particularly to clarify the VERBOSE global

Posted by gs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git

commit b5e7298aa0035574f258c445f13fc3abc761c80b
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Wed Jun 1 17:14:41 2022 -0500

    shift to a main() function; particularly to clarify the VERBOSE global
---
 monitoring/stv_tool.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/monitoring/stv_tool.py b/monitoring/stv_tool.py
index 131bcc6..de20885 100755
--- a/monitoring/stv_tool.py
+++ b/monitoring/stv_tool.py
@@ -386,7 +386,7 @@ def dbg(fmt, *args):
     print(fmt % args)
 
 
-if __name__ == '__main__':
+def main(argv):
   parser = argparse.ArgumentParser(description="Calculate a winner for a vote")
   parser.add_argument('raw_file')
   parser.add_argument("-s", "--seats", dest="seats", type=int,
@@ -395,9 +395,11 @@ if __name__ == '__main__':
   parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
                       help="Enable verbose logging", default=False)
 
-  args = parser.parse_args()
+  args = parser.parse_args(argv)
 
+  global VERBOSE
   VERBOSE = args.verbose
+
   votefile = args.raw_file
   num_seats = args.seats
 
@@ -410,3 +412,7 @@ if __name__ == '__main__':
   candidates = run_vote(names, votes, num_seats)
   candidates.print_results()
   print('Done!')
+
+
+if __name__ == '__main__':
+  main(sys.argv[1:])


[steve] 04/05: use sum() to replace a couple loops

Posted by gs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git

commit 8de31df41d9bb247953491cdd26451dc74db8bad
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Wed Jun 1 20:50:29 2022 -0500

    use sum() to replace a couple loops
---
 monitoring/stv_tool.py | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/monitoring/stv_tool.py b/monitoring/stv_tool.py
index 22389c1..a946a5b 100755
--- a/monitoring/stv_tool.py
+++ b/monitoring/stv_tool.py
@@ -165,11 +165,7 @@ class CandidateList(object):
       self.l.append(c)
 
   def count(self, state):
-    count = 0
-    for c in self.l:
-      if (c.status & state) != 0:
-        count += 1
-    return count
+    return sum(int((c.status & state) != 0) for c in self.l)
 
   def change_state(self, from_state, to_state):
     any_changed = False
@@ -216,6 +212,7 @@ class CandidateList(object):
       return 1
     return sorted(self.l, key=functools.cmp_to_key(compare))
 
+
 class Candidate(object):
   def __init__(self, name, rand, ahead):
     self.name = name
@@ -238,6 +235,7 @@ class Candidate(object):
     assert quota is not None
     self.weight = (self.weight * quota) / self.vote
 
+
 def iterate_one(quota, votes, candidates, num_seats):
   # assume that: count(ELECTED) < num_seats
   if candidates.count(ELECTED + HOPEFUL) <= num_seats:
@@ -326,10 +324,7 @@ def elect(quota, candidates, num_seats):
 
 
 def calc_surplus(quota, candidates):
-  surplus = 0.0
-  for c in candidates.l:
-    if c.status == ELECTED:
-      surplus += c.vote - quota
+  surplus = sum(c.vote - quota for c in candidates.l if c.status == ELECTED)
   dbg('Total Surplus = %.9f', surplus)
   return surplus
 


[steve] 03/05: small tweak to collecting a list of unique rando values

Posted by gs...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git

commit a991c7f85a94376c49e9f5bfd322678c4a1f05e1
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Wed Jun 1 20:36:26 2022 -0500

    small tweak to collecting a list of unique rando values
---
 monitoring/stv_tool.py | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/monitoring/stv_tool.py b/monitoring/stv_tool.py
index cb7ffbc..22389c1 100755
--- a/monitoring/stv_tool.py
+++ b/monitoring/stv_tool.py
@@ -157,6 +157,7 @@ class CandidateList(object):
   def __init__(self, names):
     num_cand = len(names)
     randset = generate_random(num_cand)
+    #dbg('RANDSET: %s', randset)
 
     self.l = [ ]
     for n, r in zip(names, randset):
@@ -386,14 +387,11 @@ def generate_random(count):
   random.seed(0)  ### choose a seed based on input? for now: repeatable.
   while True:
     # Generate COUNT values in [0.0, 1.0)
+    # NOTE: use a list (not a set or dict) for repeatable ordering.
     values = [random.random() for x in range(count)]
 
-    # Ensure there are no duplicates
-    for value in values:
-      if values.count(value) > 1:
-        break
-    else:
-      # The loop finished without breaking out
+    # Use a set() to check for dups. If no dups, then return the values.
+    if len(set(values)) == count:
       return values