OpenSTV-1.6.1/0001777000175400010010000000000011407513225011406 5ustar jeffNoneOpenSTV-1.6.1/CHANGELOG.txt0000777000175400010010000002252011400774167013450 0ustar jeffNone$Id: CHANGELOG.txt 722 2010-03-08 14:14:34Z jeff.oneill $ OpenSTV 1.6 - Provide YAML report for integration with DemoChoice - Added support for equal rankings and skipped rankings on ballots - Added support for ballot IDs - Restructuring of data structures for ballots - Reports are now done as plugins - Improvements to report generation including writing to file instead of memory - Counting methods are much faster with a large number of candidates - Check folder Plugins for external plugins - Reorganized package structure so that all imports use absolute paths - OpenSTV uses threads for counting and loading ballots so user doesn't see a frozen screen - Allow manual tie-breaking in OpenSTV OpenSTV 1.5 - Improved format of election results - No limit on the number of candidates (previously was limited to 255 candidates) - Election methods are now done as plugins so users can add their own methods (although only for advanced users at this point). - Cambridge STV can save the winner's ballots in files for determining replacements after a vacancy - Added statutes of some rules to the documentation - Added draft of New Zealand Meek (work in progress) - Added new method called QPQ - Comments now allowed in ballot files - Changes under the hood that make OpenSTV easier to maintain. OpenSTV 1.4 - Fixed bug that broke Election Options dialog on mac - Fixed bug when navigating from empty ballot - Fixed bug in remembering working directory on mac OpenSTV 1.3 - Rewrote OpenSTV user interface: (1) User can only run an election from a saved ballot file (previously, you could use a ballot file in memory). (2) User can edit/create ballots in a separate Ballot File Editor window and save them to a file. - Can save election results in HTML. - More explanatory output for Condorcet elections. - Added subclasses for different ballot types to make it easier to support new types. - Changes under the hood that make OpenSTV easier to maintain. OpenSTV 1.2 - Changed output to be more descriptive and easier to read. - Changed implementation of SuppVote to use all rankings. - Changed implementation of IRV to stop sooner (won't change outcome). - Small change in reporting of the threshold for ERS97 (won't change outcome). - Print substages for ERS97. - Now possible to have unlimited precision (but max is set to 20 for gui). - Meek/Warren implemented in fixed point. OpenSTV 1.1 - Added Scottish STV rules. - Added GPCA2000 rules. - Added approval voting. - Bug fixes in N. Ireland STV. - Simplified IRV. Now always eliminates candidates one by one and stops when candidate has a majority (instead of stopping when 2 candidates remain). - Show help files within OpenSTV instead of external browser because the external browser was not working with some platforms. - BCSTV, FTSTV, and Borda now implemented in fixed point instead of floating point. Coombs, Meek, and Warren still in floating point. - Major code rewrite in STV.py. OpenSTV 1.0 - Changed name to OpenSTV! - Test code now in a separate file and added more tests. - Although the tree-packed ballots greatly increased speed, they also greatly increased memory consumption. The tree-data structure is now created dynamically as necessary rather than built completely. This retains (and sometimes improves) the speed, and memory consumption is negligable. Condorcet no longer uses trees. - Ballot data now based on bytes instead of ints which uses 1/4th less memory. The number of candidates is now limited to 255. - Modifications to allow the use of psyco to speed up the code. Psyco on average speeds up the code by a factor of 2-4 times. - Now properly raises and catches warnings and exceptions. - Fixed bug in Condorcet completion methods. - Changed code structure so that each method is a class rather than a function. This allows for cleaner code and better code reuse. - Fixed bugs with withdrawn candidates. Withdrawn candidates are now excised from a copy of the ballots before the start of the election. Since it is done with a copy, it can later be undone. - "raw" ballot format renamed as "text" format because raw also means unpacked ballots. pSTV 0.9 Implemented the supplemental vote and N. Ireland STV. Started Dail STV. Can shuffle the ballots (interesting for Cambridge STV and random transfer STV). Simplified the rounding of floats to make the code cleaner. Implemented tree-packed ballots. Used tree-packed ballots with Meek, Warren, and Condorcet, greatly speeding up the implementations of these methods. Faster implementation of Bucklin and Coombs. For the curious, here are the timing statistics, in seconds, as they have changed over time on a test of data from eight real elections: SNTV: 0.6 IRV: 7.1 -> 1.2 BCSTV: 4.6 Coombs: 68.6 -> 40.4 -> 24.8 Cambridge: 3.7 -> 2.0 ERS97: 6.4 -> 5.5 Meek: 48.3 -> 38.7 -> 5.9 Warren: 38.0 -> 30.7 -> 3.0 Bucklin: 9.4 -> 3.6 Borda: 2.3 Condorcet: 106.0 -> 13.3 -> 7.7 pSTV 0.8 Added tabbed windows for comparing multiple election results. The font size can be changed for each tab separately. The display precision is now chosen in the Election menu and can only be changed by rerunning the election. The ERS97 implementation is finally in fixed point and the desired precision can be specified (for future implementation of ERS97-like rules such as Malta STV). Can now eliminate candidates before running an election. Previously, when loading a BLT file, eliminated candidates were removed from the ballots. Now, the eliminated candidates are noted but they remain on the ballots. This works better with allowing the user to selectively eliminate candidates, and the user can override the eliminated candidates specified in the BLT file. Added more comments and did some random code cleanups. pSTV 0.7 Renamed Simple STV as British Columbia STV. Fixed several UI bugs for Mac. Made comparisons of floating point numbers safer. Added timing to tests. Made IRV much faster. IRV eliminates all losers simultaneously. Fixed bug in Bucklin. Fixed bug in Coombs. Changed internal ballot format: got rid of Blist and append ballots immediately. Revamped ballot entry to make it easier to use. Show brief description of method after it is selected. Added symmetric completion of ballots option for Borda. pSTV 0.6 Condorcet completion methods include Borda, IRV, and SSD. Changed Methods menu to have an option to show only STV methods. Added Bucklin. pSTV 0.5.2 Added Condorcet SSD. pSTV 0.5.1 The primary reason for this release is to change DAT files to BLT files. I've also added Borda count and Coombs methods. pSTV 0.5 Now uses innosetup to create a windows installer. Also has options to create desktop/quicklaunch icons and associate dat files with pSTV (with an icon). Reverted back to previous ERS CSV format (Brian abandoned V6). sql output conforms to spec. DAT files now loaded faster than before. When reading DAT files will handle multiple rankings of a candidate. Should now work properly even with very few candidates or voters. Will return with fewer winners than asked for if not enough candidates receive more than 0 votes. pSTV *should* never crash. pSTV 0.4.2 Can now append DAT files. Now continues gracefully when loading a ballot file with a bad format. Raw ballot files can now accept a weight. Added an edit menu with a copy command. Updated ERSCSV files to V6 and can now use this format for any method (previously it could only be used for ERS97 rules). Changed -m ers to produce a table to be used with SQL and to work with methods other than ERS97 STV. These last two changes do not conform precisely to specs and will need to be tweaked. pSTV 0.4.1 Added substage tiebreaking for ERS97 rules. pSTV 0.4 Previous implementation of Meek STV was actually Warren STV. Now Meek is correctly implemented and Warren has been added. Added an icon. Clarified file formats in saving and loading ballots. Changed how output is handled (though invisible to user): STV.py now prints to stdout and pSTV.py captures this stream. Can now save output in ERS style result sheet. pSTV 0.3 Formal release with Cambridge rules. Fixed little bugs in reading dat files. pSTV 0.2.5 Removed spurious tie break messages when choosing winners for ERS97. pSTV 0.2.4 Added exclusion of candidates in reading dat files. pSTV 0.2.3 Fixed little bugs in reading dat files. pSTV 0.2.2 Implemented Cambridge, MA rules. Results are identical to CC2003, SC2003, CC2001, SC2001, and CC1999 elections. Fixed nasty floating point bug that caused an error with ERS97 tests performed by Brian Wichmann. Comparison (>=) wasn't accurate because of difference at around 1e-14. pSTV 0.2.1 Several bug fixes for bugs found by Brian Wichmann when verifying ERS97 implementation. pSTV 0.2 Many, many changes from the previous version. Highlights: fixed bug in tiebreaking, ERS97 fully implemented and tested, dynamic and fractional thresholds for RT and FT STV, all menu items now work, thorough testing, added Simple STV rules. pSTV 0.1 Initial Release. Methods implemented are SNTV, IRV, Random Transfer STV, Fractional Transfer STV, and Meek STV. Allows for different thresholds and delayed transfer of surplus. Ballots can be loaded from a file or entered via the program. The interface needs work and some features, such as printing, are not implemented. Probably many, many bugs that need to be found and fixed. OpenSTV-1.6.1/openstv/0001777000175400010010000000000011407513225013104 5ustar jeffNoneOpenSTV-1.6.1/openstv/ballots.py0000777000175400010010000004577111400774167015145 0ustar jeffNone"""Module for working with ballot data""" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: ballots.py 722 2010-03-08 14:14:34Z jeff.oneill $" import os from openstv.plugins import getLoaderPlugins, getLoaderPluginClass ################################################################## class Ballots(object): """Class for working with ballot data. A Ballots object is concetptually a list of ballots. A ballot is a list of rankings, with an optional ID. A ranking is typically a candidate index number, but can also be -1 (skipped ranking) or a list of candidate index numbers (equal rankings). Only unique ballots are stored, and the list of ballots is a list of pointers to the appropriate ballot. For methods where the outcome can depend on the order of the ballots (e.g., Cambridge STV) the individual ballots are used, but for methods where the outcome is independent of the order, only the unique ballots are used along with a weight (the number of times that ballot appears). A ballots object may only contain valid ballot data. If the ballot data contains an error (e.g., a candidate index number that is out of range), an error should be raised immediately. """ def __init__(self, customBallotIDs=False): self.title = "Title" # An election title. self.date = "" # The date of the election. self.numSeats = 1 # The number of seats to be filled. self.customBallotIDs = customBallotIDs # Whether custom ballot IDs are # used. If fale, the ballot IDs are just 1 to N. self.exceptionQueue = None # Used to erport exceptions back to GUI self.dirtyBallots = None # For clean ballots this is a pointer to the # dirty ballots from which they were created self._names = [] self._n2i = {} # A list of candidate names. The index for a particular name is the # candidate number. The first name is thus assigned to candidate # number 0. _n2i is a shortcut for getting the inedex from the name. self.withdrawn = [] # A list of the candidates who are withdrawn from the election, specified # by the candidate number. # Conceptually each ballot is a list of candidates and each ballot has # a ballot ID used to identify that ballot. Where no ballot IDs are # provided, ballots are assigned IDs in numerical order. # Note that ballotIDs are assigned to individual ballots and not to # weighted ballots. Where a ballot file specifies a weighted ballot, # then a number of individual ballots will be included and each individual # ballot will be given a ballotID. # In any ballot list, many of the ballots will be identical so, instead # of storing each ballot, only unique ballots will be stored. self.uniqueBallots = [] # This is a list of unique ballots. Each item of the list is a ballot, # and each ballot is a list of candidate numbers that represent the # candidates being ranked. For example, if a ballot is [2 4 1], then # candidate number 2 is ranked first, candidate number 4 is ranked second, # and candidate number 1 is ranked third. self.uniqueBallotCount = [] # This is the weight for each unique ballot. self.uniqueBallotIndexToBallotIndices = [] # This list has the same length as self.uniqueBallots. Each item of the # list is a Python set that contains the indices of self.ballotOrder # corresponding to the unique ballot. self.uniqueBallotsLookup = {} # The keys to this dictionary are string representations of unique # ballots and the values are the indices into self.uniqueBallots. This # dictionary indicates whether a given ballot has already been seen, and # if so, where the ballot exists in self.uniqueBallots. self.ballotOrder = [] # The length of this list is the total number of ballots. Each entry is # the index into self.uniqueBallots of the corresponding ballot. self.ballotIDsList = [] # A list of the ballot IDs in the order specified in the ballot file. # If the file does not have ballot IDs, then this list remains empty and # the ballotID is computed from the ballot index (1 .. N). self.loader = None def copy(self, copyBallots=True): # Documentation for copy module says it doesn't work with arrays ballotList = Ballots() ballotList.customBallotIDs = self.customBallotIDs ballotList.title = self.title ballotList.date = self.date ballotList.numSeats = self.numSeats ballotList.names = self.names[:] ballotList.withdrawn = self.withdrawn[:] if copyBallots: for i in xrange(self.numBallots): ballot = self.getBallot(i) ballotID = self.getBallotID(i) if self.customBallotIDs else None ballotList.appendBallot(ballot, ballotID) # Don't want the copy to save to the same file as the original ballotList.loader = None return ballotList @property def numBallots(self): return len(self.ballotOrder) @property def numWeightedBallots(self): return len(self.uniqueBallots) def getNumCandidates(self): return len(self.names) def setNumCandidates(self, numCandidates): assert(len(self.names) == 0) names = [] for i in range(numCandidates): names.append("Candidate No. %d" % (i + 1)) self.names = names numCandidates = property(getNumCandidates, setNumCandidates) def getNames(self): return self._names def setNames(self, names): self._names = list(names) for index, name in enumerate(names): self._n2i[name] = index names = property(getNames, setNames) def checkBallot(self, ballot): # Check to make sure ballot data is valid. At this point only possible # errors should be that the candidate number is too large. nc = self.numCandidates for ranking in ballot: if isinstance(ranking, list): self.checkBallot(ranking) elif ranking > nc - 1: raise RuntimeError, "Ballot has invalid data: %s" % str(ballot) def appendBallot(self, ballot, ballotID=None): "Append a ballot to this Ballots object." # Check to make sure whether ballot IDs are allowed assert((ballotID == None) ^ (self.customBallotIDs)) # XOR # Not sure if we want to do this. May make more sense to have the # ballot loader do the checking. #self.checkBallot(ballot) # String representation of ballot for determining whether it is unique ballotString = str(ballot) # Record the ballot ID if there is one if ballotID is not None: self.ballotIDsList.append(ballotID) ballotIndex = len(self.ballotOrder) # Index of the ballot being added if ballotString in self.uniqueBallotsLookup: # We have seen this ballot before uniqueBallotIndex = self.uniqueBallotsLookup[ballotString] self.uniqueBallotIndexToBallotIndices[uniqueBallotIndex].add(ballotIndex) self.uniqueBallotCount[uniqueBallotIndex] += 1 else: # We have not seen this ballot before self.uniqueBallots.append(ballot) self.uniqueBallotCount.append(1) uniqueBallotIndex = len(self.uniqueBallots) - 1 self.uniqueBallotsLookup[ballotString] = uniqueBallotIndex self.uniqueBallotIndexToBallotIndices.append(set([ballotIndex])) self.ballotOrder.append(uniqueBallotIndex) def appendBallotUsingNames(self, ballot, ballotID=None): "Append a ballot to this Ballots object." ballot2 = [] for name in ballot: ballot2.append(self._n2i[name]) self.appendBallot(ballot2, ballotID) def getWeight(self, i): "Return the weight of the ith weighted ballot." return self.uniqueBallotCount[i] def getWeightedBallot(self, i): "Return the ith weighted ballot." return (self.uniqueBallotCount[i], self.uniqueBallots[i][:]) def getSortedWeightedBallots(self): "This is used to compare two ballot lists for testing purposes." # We should replace this with a diff-like function that returns true # or false to indicate whether two ballots objects are the same. sortedBallots = [(str(self.uniqueBallots[i]), self.uniqueBallotCount[i]) for i in xrange(self.numWeightedBallots)] sortedBallots.sort() return sortedBallots def getBallot(self, i): j = self.ballotOrder[i] return self.uniqueBallots[j][:] def getBallotID(self, i): if self.customBallotIDs: return self.ballotIDsList[i] else: return i + 1 def getBallotAndID(self, i): return (self.getBallot(i), self.getBallotID(i)) def getBallotsAndIDs(self): if self.customBallotIDs: ballotIDs = self.ballotIDsList[:] else: ballotIDs = range(1, self.numBallots + 1) return zip([self.uniqueBallots[i][:] for i in self.ballotOrder], ballotIDs) def setBallot(self, i, ballot): # This is an expensive operation but it is only done when the user # is editing ballots so it does not need to be done that quickly oldBallots = self.getBallotsAndIDs() oldBallots[i] = (ballot[:], oldBallots[i][1]) self.deleteBallots() for ballot, ballotID in oldBallots: if not self.customBallotIDs: ballotID = None self.appendBallot(ballot, ballotID) def deleteBallot(self, i): # This is an expensive operation but it is only done when the user # is editing ballots so it does not need to be done that quickly oldBallots = self.getBallotsAndIDs() oldBallots.pop(i) self.deleteBallots() for ballot, ballotID in oldBallots: if not self.customBallotIDs: ballotID = None self.appendBallot(ballot, ballotID) def deleteBallots(self): self.uniqueBallots = [] self.uniqueBallotIndexToBallotIndices = [] self.uniqueBallotsLookup = {} self.ballotIDsList = [] self.ballotOrder = [] def getTopChoiceFromBallot(self, i, choices): "Return the top choice on a ballot among candidates still in the running." j = self.ballotOrder[i] ballot = self.uniqueBallots[j] for c in ballot: if c in choices: return c return None def getTopChoiceFromWeightedBallot(self, i, choices): "Return the top choice on a ballot among candidates still in the running." ballot = self.uniqueBallots[i] for c in ballot: if c in choices: return c return None def getCleanBallots(self, removeEmpty=True, removeOvervotes=True, removeDupes=True, removeWithdrawn=True): """Ballots can be cleaned in several ways: (1) Removing withdrawn candidates. This is done if withdrawn is not None. (2) Removing empty ballots. This is done if removeEmpty is True. (3) Removing overvotes (more than one candidate is given the same ranking). This is done if removeOvervotes is True. (4) Remove duplicate rankings (same candidate is ranked more than once on a ballot). This is done if removeDupes is True. (5) Remove skipped rankings. A skipped ranking is indicated by a "-1" instead of a candidate number. These are always removed. (6) Does not currently check for duplicate ballot IDs, but we might want to add this later. """ # We want to keep track of the link between dirty and clean ballots cleanBallots = self.copy(False) cleanBallots.withdrawn = [] cleanBallots.customBallotIDs = True cleanBallots.dirtyBallots = self # Set up a translation list for candidate numbers for removing # withdrawn candidates. c2 = c2c[c] translates an original candidate # number "c" to a translated candidate number "c2" taking into account # candidates that have been removed from the ballots. If a candidate is # withdrawn, c2c returns None. c2c = range(self.numCandidates) if removeWithdrawn: n = 0 for i in range(self.numCandidates): if i in self.withdrawn: c2c[i] = None n += 1 else: c2c[i] -= n # Loop over ballots and perform requested cleaning for i in xrange(self.numBallots): ballot, ballotID = self.getBallotAndID(i) seenCandidates = set() cleanBallot = [] # This will be a cleaned version of ballot for item in ballot: # Candidate may have to pass two tests to get in the cleaned ballots. # First, candidate must not be withdrawn. # Second, candidate must not already be on the ballot when removeDupes # is true. if isinstance(item, list): assert(len(item) > 1) if removeOvervotes: continue cleanItem = [] for c in item: if c == -1: continue # Skipped ranking c2 = c2c[c] # Candidate number after removing withdrawn candidates if not ((c in self.withdrawn) or (removeDupes and c2 in seenCandidates)): assert(c2 is not None) cleanItem.append(c2) seenCandidates.add(c2) if len(cleanItem) > 1: cleanBallot.append(cleanItem) elif len(cleanItem) == 1: cleanBallot.append(cleanItem[0]) else: c = item if c == -1: continue # Skipped ranking c2 = c2c[c] # Candidate number after removing withdrawn candidates if not ((c in self.withdrawn) or (removeDupes and c2 in seenCandidates)): assert(c2 is not None) cleanBallot.append(c2) seenCandidates.add(c2) if not removeEmpty or len(cleanBallot) > 0: cleanBallots.appendBallot(cleanBallot, ballotID) # Remove the withdrawn candidates names cleanBallots.names = [self.names[c] for c in range(self.numCandidates) if c not in self.withdrawn] return cleanBallots def appendFile(self, fName): "Append ballot data from a file." ballotList = Ballots() ballotList.loadUnknown(fName) if (ballotList.numSeats != self.numSeats or ballotList.names != self.names or ballotList.withdrawn != self.withdrawn): raise RuntimeError, \ "Can't append ballots. The numbers of seats and candidates, \n"\ "the names of the candidates, and the withdrawn candidates \n"\ "must be identical." for i in xrange(ballotList.numBallots): ballot = ballotList.getBallot(i) ballotID = ballotList.getBallotID(i) if self.customBallotIDs else None self.appendBallot(ballot, ballotID) def save(self): "Save back to the last file I was saved or loaded from" self.loader.save(self) def saveAs(self, fName, packed=False): "Create a new ballot loader and save ballots" extension = os.path.splitext(fName)[1][1:] loaderClass = getLoaderPluginClass(extension) if loaderClass is None: # If we don't know then the default is blt format loaderClass = getLoaderPluginClass("blt") self.loader = loaderClass() self.loader.save(self, fName, packed) def loadKnown(self, fName, extension=None, exclude0 = True): "Load a file based on its file extension." if extension is None: extension = os.path.splitext(fName)[1][1:] loaderClass = getLoaderPluginClass(extension, exclude0) if loaderClass is None: raise RuntimeError, "Do not know how to load files with extension %s." % (extension) self.loader = loaderClass() self.loader.load(self, fName) def loadUnknown(self, fName, exclude0 = True): "Load a file of unknown format." # Try the loader that claims this extension first. If that one doesn't # work then we try the others. Get the loader classes in the right order. extension = os.path.splitext(fName)[1][1:] loaderClasses = getLoaderPlugins("classes", exclude0) bestGuess = getLoaderPluginClass(extension, exclude0) if bestGuess is not None: loaderClasses.remove(bestGuess) loaderClasses.insert(0, bestGuess) errorMsg = "Could not load ballots from file %s." % fName # Try them in order for loaderClass in loaderClasses: try: self.loader = loaderClass() self.loader.load(self, fName) except RuntimeError, (msg,): errorMsg += "\n" + msg.strip() else: break else: # None of the ballot loaders succeeded so raise an exception if self.exceptionQueue is None: raise RuntimeError(errorMsg) else: self.exceptionQueue.put(errorMsg) def getFileName(self): "The name of the last file I was saved or loaded from" if (self.loader is not None): return self.loader.fName else: return None def reorderCandidates(self, order=None): "Reorder candidates in alphabetical order or the order specified." if order == None: # Default is alphabetical order order = range(self.numCandidates) order.sort(key=lambda c: self.names[c]) # Check to make sure that all candidates are included check = order[:] check.sort() if check != range(self.numCandidates): raise RuntimeError, "Must specify all the candidates when reordering." # Set up a translation list. # order gives the desired candidate order, e.g., [4 0 3 1 2] # Thus, we want 4->0, 0->1, 3->2, 1->3, and 2-> 4 # c2c does this translation c2c = [0] * self.numCandidates for i, c in enumerate(order): c2c[c] = i # Translate all the candidate numbers. This must be done in two places: # (1) The ballots in uniqueBallots # (2) The keys in uniqueBallotsLookup # Easier to create a new uniqueBallotsLookup self.uniqueBallotsLookup = {} # Loop over all the weighted ballots for i in xrange(self.numWeightedBallots): for j, c in enumerate(self.uniqueBallots[i]): self.uniqueBallots[i][j] = c2c[c] ballotString = str(list(self.uniqueBallots[i])) self.uniqueBallotsLookup[ballotString] = i # Put the names in the right order oldNames = self.names[:] names = self.names for c in range(self.numCandidates): cc = c2c[c] names[cc] = oldNames[c] self.names = names def joinList(self, itemList, convert="names"): assert(len(itemList) > 0) if convert == "names": tmp = itemList[:] itemList = [self.names[c] for c in tmp] text = " ".join(itemList) sep = "; " if text.find(",") != -1 else ", " if len(itemList) == 1: txt = itemList[0] elif len(itemList) == 2: txt = itemList[0] + " and " + itemList[1] else: txt = sep.join(itemList[:-1]) txt += sep + "and " + itemList[-1] return txt def isalnum(self): """Are all candidate names alphanumeric?""" for name in self.names: if not name.isalnum(): return False return True OpenSTV-1.6.1/openstv/BFE.py0000777000175400010010000006042511400774167014072 0ustar jeffNone"Module that provides a GUI for editing ballots." ## OpenSTV Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: BFE.py 719 2010-03-01 03:43:54Z jeff.oneill $" import wx import os import warnings import wx.lib.mixins.listctrl as listmix from openstv.STV import * from openstv.ballots import Ballots from openstv.utils import getHome ################################################################## class BFEFrame(wx.Frame): def __init__(self, parent, mode): wx.Frame.__init__(self, parent, -1, "Ballot File Editor") warnings.showwarning = self.catchWarnings self.MakeMenu() self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) self.logfName = "" fn = os.path.join(getHome(), "Icons", "blt.ico") icon = wx.Icon(fn, wx.BITMAP_TYPE_ICO) self.SetIcon(icon) # Edit a new ballot file if mode == "new": # Create an empty ballots class instance self.b = Ballots() # Get the candidate names from the user dlg = CandidatesDialog(parent, self.b) dlg.Center() if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() self.Destroy() return dlg.Destroy() # Edit an existing ballot file elif mode == "old": dlg = wx.FileDialog(self, "Edit Ballot File", style=wx.OPEN|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() self.Destroy() return fName = dlg.GetPath() dlg.Destroy() # Open the file try: self.b = Ballots() self.b.loadUnknown(fName) except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) self.Destroy() return else: assert(0) # Set the window title to include the filename fn = self.b.getFileName() if fn is not None: title = "%s - Ballot File Editor" % os.path.basename(fn) else: title = "%s - Ballot File Editor" % "New File" self.SetTitle(title) # Create a notebook with an editing page and a log page nb = wx.Notebook(self, -1) self.panel = BallotsPanel(nb, self.b) nb.AddPage(self.panel, "Ballots") self.logN = 1 # counter for display purposes self.log = wx.TextCtrl(nb, -1, style=wx.TE_MULTILINE|wx.TE_READONLY|\ wx.TE_WORDWRAP|wx.FIXED) self.log.SetMaxLength(0) nb.AddPage(self.log, "Log") # Initialize if mode == "new": self.panel.NeedToSaveBallots = True self.Log("Created a new ballot file.") elif mode == "old": self.panel.NeedToSaveBallots = False self.Log("Loaded %d ballots from file %s." %\ (self.b.numBallots, os.path.basename(self.b.getFileName()))) else: assert(0) # Set up the sizer sizer = wx.BoxSizer() sizer.Add(nb, 1, wx.EXPAND, 0) self.SetSizer(sizer) sizer.Fit(self) sizer.SetSizeHints(self) def catchWarnings(self, message, category, filename, lineno): "Catch any warnings and display them in a dialog box." wx.MessageBox(str(message), "Warning", wx.OK|wx.ICON_INFORMATION) def Log(self, txt): # create a prompt for each new line prompt = "%3d: " % self.logN self.log.AppendText(prompt + txt + "\n") self.logN += 1 def MakeMenu(self): fileMenu = wx.Menu() append = fileMenu.Append(-1, "A&ppend ballots from file...") saveBallots = fileMenu.Append(-1, "&Save ballots") saveBallotsAs = fileMenu.Append(-1, "Save ballots &as...") saveLog = fileMenu.Append(-1, "Save &log") saveLogAs = fileMenu.Append(-1, "Save log as...") exitBFE = fileMenu.Append(wx.ID_EXIT, "E&xit") self.Bind(wx.EVT_MENU, self.OnAppendBF, append) self.Bind(wx.EVT_MENU, self.OnSaveBallots, saveBallots) self.Bind(wx.EVT_MENU, self.OnSaveBallotsAs, saveBallotsAs) self.Bind(wx.EVT_MENU, self.OnSaveLog, saveLog) self.Bind(wx.EVT_MENU, self.OnSaveLogAs, saveLogAs) self.Bind(wx.EVT_MENU, self.OnExit, exitBFE) if wx.Platform == "__WXMAC__": wx.App.SetMacExitMenuItemId(wx.ID_EXIT) menuBar = wx.MenuBar() menuBar.Append(fileMenu, "&File") self.SetMenuBar(menuBar) def OnAppendBF(self, event): # Get the filename of the ballots to be appended dlg = wx.FileDialog(self, "Select Ballot File", style=wx.OPEN|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return fName = dlg.GetPath() dlg.Destroy() # Attempt to append the ballots try: oldNumBallots = self.b.numBallots self.b.appendFile(fName) self.b = self.b.getCleanBallots(removeEmpty=False, removeWithdrawn=False) except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) else: self.Log("Appended %d ballots from file %s." %\ (self.b.numBallots - oldNumBallots, os.path.basename(fName))) self.panel.NeedToSaveBallots = True self.panel.NeedToSaveLog = True self.panel.UpdatePanel() def OnSaveBallots(self, event): if self.b.getFileName() is None: self.OnSaveBallotsAs(event) return try: self.b.save() except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) return self.panel.NeedToSaveBallots = False def OnSaveBallotsAs(self, event): # Ask the user to choose the filename. dlg = wx.FileDialog(self, "Save Ballot File", style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return fName = dlg.GetPath() dlg.Destroy() # Save try: self.b.saveAs(fName) except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) return self.panel.NeedToSaveBallots = False # Set the window title to include the filename title = "%s - Ballot File Editor" % os.path.basename(fName) self.SetTitle(title) self.panel.fNameC.SetLabel(os.path.basename(fName)) def OnSaveLog(self, event): if self.logfName == "": self.OnSaveLogAs(event) return try: self.log.SaveFile(self.logfName) except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) return self.panel.NeedToSaveLog = False def OnSaveLogAs(self, event): dlg = wx.FileDialog(self, "Save Log to a File", style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return self.logfName = dlg.GetPath() dlg.Destroy() try: self.log.SaveFile(self.logfName) except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) return self.panel.NeedToSaveLog = False def OnExit(self, event): self.Close() def OnCloseWindow(self, event): # Check to see if the current ballot is empty and warn user if self.panel.currentBallot == []: txt = "The current ballot is empty. Ok to close editor?" code = wx.MessageBox(txt, "Warning", wx.OK|wx.CANCEL|wx.ICON_QUESTION) if code == wx.CANCEL: # Don't exit event.Veto() return # Ask user if we should save ballots. if self.panel.NeedToSaveBallots == True: if self.b.getFileName() is None: msg = "Do you want to save the ballots?" else: msg = "Do you want to save the ballots to %s?" % self.b.getFileName() saveBallots = wx.MessageBox(msg, "Warning", wx.YES_NO|wx.CANCEL|wx.ICON_INFORMATION) if saveBallots == wx.CANCEL: event.Veto() # Don't exit return elif saveBallots == wx.YES: self.OnSaveBallots(None) # Save ballots elif saveBallots == wx.NO: # If user is discarding ballot changes then don't ask to save the log self.panel.NeedToSaveLog = False # Ask user if we should also save the log. if self.panel.NeedToSaveLog == True: msg = "Do you want to save the log for ballot file %s?"\ % os.path.basename(self.b.getFileName()) saveLog = wx.MessageBox(msg, "Warning", wx.YES_NO|wx.CANCEL|wx.ICON_INFORMATION) if saveLog == wx.CANCEL: event.Veto() # Don't exit return elif saveLog == wx.YES: self.OnSaveLog(None) # Save log self.Destroy() ################################################################## class BallotCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): def __init__(self, parent, ID): style = wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.LC_VRULES wx.ListCtrl.__init__(self, parent, ID, style=style) listmix.ListCtrlAutoWidthMixin.__init__(self) def computeColumnWidth(self): # This is another kluge to overcome a difficulty with ListCtrl. # You can easily set the column width to the largest entry or to # the width of the header, but not the largest of the two. # This hack does that. self.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER) w1 = self.GetColumnWidth(1) self.SetColumnWidth(1, wx.LIST_AUTOSIZE) w2 = self.GetColumnWidth(1) w = max(w1, w2) return w ## def GetMinSize(self): ## # This is adapted from _doResize() in ## # /c/Python25/Lib/site-packages/wx-2.8-msw-ansi/wx/lib/mixins/listctrl.py ## # but I couldn't get it to work. ## w = self.GetColumnWidth(0) + self.GetColumnWidth(1) ## if self.GetItemCount() > self.GetCountPerPage(): ## w = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X) ## return (w, -1) ################################################################## class BallotsPanel(wx.Panel): def __init__(self, parent, b): wx.Panel.__init__(self, parent, -1) self.NeedToSaveBallots = False self.NeedToSaveLog = False self.b = b self.i = 0 # The number of the ballot being displayed if self.b.numBallots == 0: self.b.appendBallot([]) self.currentBallot = self.b.getBallot(self.i) # Information box informationBox = wx.StaticBox(self, -1, "Information") fNameL = wx.StaticText(self, -1, "Filename:") fn = self.b.getFileName() if fn is None: fn = "New File" else: fn = os.path.basename(fn) self.fNameC = wx.StaticText(self, -1, fn) numBallotsL = wx.StaticText(self, -1, "No. of ballots:") self.numBallotsC = wx.StaticText(self, -1, "%d" % self.b.numBallots) numSeatsL = wx.StaticText(self, -1, "No. of seats:") numSeatsC = wx.StaticText(self, -1, "%d" % self.b.numSeats) numCandidatesL = wx.StaticText(self, -1, "No. of candidates:") numCandidatesC = wx.StaticText(self, -1, "%d" % self.b.numCandidates) titleL = wx.StaticText(self, -1, "Title:") title = "" if vars(self.b).has_key("title"): title = self.b.title titleC = wx.TextCtrl(self, -1, title) self.Bind(wx.EVT_TEXT, self.OnTitle, titleC) # Rankings box rankingsBox = wx.StaticBox(self, -1, "Rankings") txt = """\ Click on a candidate's name to assign that candidate the next available ranking, and double-click on a candidate's name to remove the ranking and reorder the remaining candidates.""" rankingsHelp = wx.StaticText(self, -1, txt) # Navigation box navigationBox = wx.StaticBox(self, -1, "Navigation") first = wx.Button(self, -1, "|<", style=wx.BU_EXACTFIT) prev = wx.Button(self, -1, "<", style=wx.BU_EXACTFIT) next = wx.Button(self, -1, ">", style=wx.BU_EXACTFIT) last = wx.Button(self, -1, ">|", style=wx.BU_EXACTFIT) self.spin = wx.SpinCtrl(self, -1, size=(60, -1)) self.spin.SetRange(1, self.b.numBallots) self.spin.SetValue(1) go = wx.Button(self, -1, "Go", style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self.OnNav, first) self.Bind(wx.EVT_BUTTON, self.OnNav, prev) self.Bind(wx.EVT_BUTTON, self.OnNav, next) self.Bind(wx.EVT_BUTTON, self.OnNav, last) self.Bind(wx.EVT_BUTTON, self.OnNav, go) # Operations box operationsBox = wx.StaticBox(self, -1, "Operations") clear = wx.Button(self, -1, "Clear This Ballot") delete = wx.Button(self, -1, "Delete This Ballot") append = wx.Button(self, -1, "Append New Ballot") exitBFE = wx.Button(self, -1, "Exit") self.Bind(wx.EVT_BUTTON, self.OnClear, clear) self.Bind(wx.EVT_BUTTON, self.OnDelete, delete) self.Bind(wx.EVT_BUTTON, self.OnAppend, append) self.Bind(wx.EVT_BUTTON, self.OnExit, exitBFE) # Ballot box self.ballotBox = wx.StaticBox(self, -1, "Ballot No. %d" % (self.i + 1)) self.ballotC = BallotCtrl(self, -1) self.ballotC.InsertColumn(0, " R ", wx.LIST_FORMAT_RIGHT) self.ballotC.InsertColumn(1, "Candidate") self.ballotBox.SetLabel("Ballot No. %d" % (self.i+1)) for c, name in enumerate(self.b.names): if c in self.currentBallot: r = self.currentBallot.index(c) self.ballotC.InsertStringItem(c, str(r+1)) else: self.ballotC.InsertStringItem(c, "") self.ballotC.SetStringItem(c, 1, name) self.ballotC.SetColumnWidth(0, wx.LIST_AUTOSIZE_USEHEADER) w = self.ballotC.computeColumnWidth() self.ballotC.SetColumnWidth(1, w) self.Bind(wx.EVT_LIST_COL_BEGIN_DRAG, self.OnColBeginDrag, self.ballotC) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnListClick, self.ballotC) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnListDClick, self.ballotC) # Sizers sizer = wx.BoxSizer(wx.HORIZONTAL) leftSizer = wx.BoxSizer(wx.VERTICAL) # Information informationSizer = wx.StaticBoxSizer(informationBox, wx.VERTICAL) fgs = wx.FlexGridSizer(5, 2, 5, 5) fgs.Add(fNameL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.fNameC, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(numBallotsL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.numBallotsC, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(numSeatsL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(numSeatsC, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(numCandidatesL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(numCandidatesC, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(titleL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(titleC, 1, wx.EXPAND|wx.ALL) fgs.AddGrowableCol(1) informationSizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) leftSizer.Add(informationSizer, 0, wx.EXPAND|wx.ALL, 5) # Rankings rankingsSizer = wx.StaticBoxSizer(rankingsBox, wx.VERTICAL) rankingsSizer.Add(rankingsHelp, 0, wx.ALIGN_CENTER|wx.ALL, 5) leftSizer.Add(rankingsSizer, 0, wx.EXPAND|wx.ALL, 5) # Navigation navigationSizer = wx.StaticBoxSizer(navigationBox, wx.VERTICAL) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(first, 0, wx.ALIGN_CENTER|wx.ALL, 5) hSizer.Add(prev, 0, wx.ALIGN_CENTER|wx.ALL, 5) hSizer.Add(next, 0, wx.ALIGN_CENTER|wx.ALL, 5) hSizer.Add(last, 0, wx.ALIGN_CENTER|wx.ALL, 5) navigationSizer.Add(hSizer, 0, wx.ALIGN_CENTER, 0) hSizer = wx.BoxSizer(wx.HORIZONTAL) hSizer.Add(self.spin, 0, wx.ALIGN_CENTER|wx.ALL, 5) hSizer.Add(go, 0, wx.ALIGN_CENTER|wx.ALL, 5) navigationSizer.Add(hSizer, 0, wx.ALIGN_CENTER, 0) leftSizer.Add(navigationSizer, 0, wx.EXPAND|wx.ALL, 5) # Operations operationsSizer = wx.StaticBoxSizer(operationsBox, wx.VERTICAL) gs = wx.GridSizer(2, 2, 5, 5) gs.Add(clear, 0, wx.EXPAND) gs.Add(delete, 0, wx.EXPAND) gs.Add(append, 0, wx.EXPAND) gs.Add(exitBFE, 0, wx.EXPAND) operationsSizer.Add(gs, 0, wx.ALIGN_CENTER|wx.ALL, 5) leftSizer.Add(operationsSizer, 0, wx.EXPAND|wx.ALL, 5) # Ballot ballotSizer = wx.StaticBoxSizer(self.ballotBox, wx.VERTICAL) ballotSizer.Add(self.ballotC, 1, wx.EXPAND|wx.ALL, 5) # Need this ugly hack since wx.ListCtrl doesn't properly set its size w = self.ballotC.GetColumnWidth(0) + self.ballotC.GetColumnWidth(1)\ + wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X) + 5 ballotSizer.SetItemMinSize(self.ballotC, (w, -1)) sizer.Add(leftSizer, 0, wx.EXPAND, 0) sizer.Add(ballotSizer, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) def OnExit(self, event): # Must be a cleaner way to do this? self.GetGrandParent().Close() def Log(self, txt): self.GetGrandParent().Log(txt) def OnColBeginDrag(self, event): # Don't allow column resizing event.Veto() def emptyBallotOK(self): # Check if user is ok with navigating away from an empty ballot txt = "The current ballot is empty. Ok to navigate to another ballot?" code = wx.MessageBox(txt, "Warning", wx.OK|wx.CANCEL|wx.ICON_QUESTION) return code == wx.OK def storeCurrentBallot(self): # Store a copy of the current ballot to the ballots object self.b.setBallot(self.i, self.currentBallot) self.NeedToSaveBallots = True self.NeedToSaveLog = True def UpdatePanel(self): # Display the ballot number, and no. of ballots self.ballotBox.SetLabel("Ballot No. %d" % (self.i + 1)) self.spin.SetValue(self.i + 1) self.spin.SetRange(1, self.b.numBallots) self.numBallotsC.SetLabel("%d" % self.b.numBallots) # Get rid of unsupported features if -1 in self.currentBallot: warnings.warn("Skipped rankings discarded from ballot %d." % (self.i+1)) for ranking in self.currentBallot: if isinstance(ranking, list): warnings.warn("Duplicate rankings discarded from ballot %d" % (self.i+1)) break self.currentBallot = [r for r in self.currentBallot if r != -1 and not isinstance(r, list)] # Update the list box to show the current ballot for c in xrange(self.b.numCandidates): if self.currentBallot.count(c) > 1: warnings.warn("Candidate %s appears on ballot %d more than once. Later rankings ignored." % (self.b.names[c], self.i+1)) if c in self.currentBallot: r = self.currentBallot.index(c) self.ballotC.SetStringItem(c, 0, str(r+1)) else: self.ballotC.SetStringItem(c, 0, "") def OnClear(self, event): self.currentBallot = [] self.storeCurrentBallot() self.UpdatePanel() self.Log("Cleared the rankings of ballot %d." % (self.i+1)) def OnDelete(self, event): # Can't delete the last ballot if self.b.numBallots == 1: txt = "Can't delete. Must have at least one ballot." wx.MessageBox(txt, "Error", wx.OK|wx.ICON_ERROR) return # Delete the current ballot self.b.deleteBallot(self.i) self.Log("Deleted ballot %d." % (self.i+1)) self.NeedToSaveBallots = True self.NeedToSaveLog = True # Update the control self.i = min(self.i, self.b.numBallots - 1) self.currentBallot = self.b.getBallot(self.i) self.UpdatePanel() def OnAppend(self, event): if len(self.currentBallot) == 0 and not self.emptyBallotOK(): return # Add blank ballot to end self.b.appendBallot([]) self.Log("Appended ballot %d." % self.b.numBallots) self.NeedToSaveBallots = True self.NeedToSaveLog = True # Update the control self.i = self.b.numBallots - 1 self.currentBallot = self.b.getBallot(self.i) self.UpdatePanel() def OnNav(self, event): if len(self.currentBallot) == 0 and not self.emptyBallotOK(): return # Move to another ballot label = event.GetEventObject().GetLabel() if label == "|<": self.i = 0 elif label == "<": self.i -= 1 elif label == ">": self.i += 1 elif label == ">|": self.i = self.b.numBallots - 1 elif label == "Go": self.i = self.spin.GetValue() - 1 else: assert(0) self.i = max(0, self.i) self.i = min(self.b.numBallots - 1, self.i) # Update the control self.currentBallot = self.b.getBallot(self.i) self.UpdatePanel() def OnListClick(self, event): c = event.m_itemIndex name = self.b.names[c] if c not in self.currentBallot: self.currentBallot.append(c) self.storeCurrentBallot() self.UpdatePanel() rank = len(self.currentBallot) self.Log("Added candidate %s to ballot %d with rank %d." % (name, self.i+1, rank)) # Unselect the clicked item. In some situations, the default behavior # is that an item of the list ctrl is selected, which prevents the user # from clicking directly on that item (the user must click elsewhere and # then click on the item). self.ballotC.SetItemState(c, 0, wx.LIST_STATE_SELECTED) def OnListDClick(self, event): c = event.m_itemIndex name = self.b.names[c] if c in self.currentBallot: self.currentBallot.remove(c) self.storeCurrentBallot() self.UpdatePanel() self.Log("Removed candidate %s from ballot %d." % (name, self.i+1)) # See comment in OnListClick self.ballotC.SetItemState(c, 0, wx.LIST_STATE_SELECTED) def OnTitle(self, event): titleC = event.GetEventObject() self.b.title = titleC.GetValue().strip() self.NeedToSaveBallots = True ################################################################## class CandidatesDialog(wx.Dialog): def __init__(self, parent, b): wx.Dialog.__init__(self, parent, -1, "New Ballot File") self.b = b self.names = [] # Explanation txt = wx.StaticText(self, -1, """\ Enter the candidates' names one by one. To remove a candidate whose name has already been entered, double click on the candidate's name below.""") # Candidate entry candidateL = wx.StaticText(self, -1, "Candidate to add:") self.candidateC = wx.TextCtrl(self, -1, "", style=wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_TEXT_ENTER, self.OnEnter, self.candidateC) candidateB = wx.Button(self, -1, "Add") self.Bind(wx.EVT_BUTTON, self.OnAdd, candidateB) # Candidate list listL = wx.StaticText(self, -1, "Candidates:") self.listC = wx.ListBox(self, -1, choices=self.b.names, size=(-1, 100)) self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnListDClick, self.listC) blank = wx.StaticText(self, -1, "") # Buttons ok = wx.Button(self, wx.ID_OK) self.Bind(wx.EVT_BUTTON, self.OnOK, ok) cancel = wx.Button(self, wx.ID_CANCEL) # Sizers sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(txt, 0, wx.ALL, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) fgs = wx.FlexGridSizer(2, 3, 5, 5) fgs.Add(candidateL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.candidateC, 0, wx.EXPAND) fgs.Add(candidateB, 0) fgs.Add(listL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.listC, 0, wx.EXPAND) fgs.Add(blank, 0) fgs.AddGrowableCol(1) sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) bs = wx.StdDialogButtonSizer() bs.AddButton(ok) bs.AddButton(cancel) bs.Realize() sizer.Add(bs, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) def OnOK(self, event): # Check to see if name is entered and not added name = self.candidateC.GetValue().strip() if name == "": event.Skip() else: wx.MessageBox("Name entered but not added. Please hit the 'Add' " "button or clear the name in the text box to continue.", "Message", wx.OK|wx.ICON_INFORMATION) self.b.names = self.names def OnEnter(self, event): self.OnAdd(event) def OnAdd(self, event): # Get the name in the text box name = self.candidateC.GetValue().strip() # Make sure the name is unique if name in self.names: wx.MessageBox("Can't have two candidates with the same name.", "Error", wx.OK|wx.ICON_ERROR) self.candidateC.SetFocus() return # Make sure the name is not empty if name == "": wx.MessageBox("Candidate name cannot be blank.", "Error", wx.OK|wx.ICON_ERROR) self.candidateC.SetFocus() return self.names.append(name) self.listC.Set(self.names) # Clear the text box to allow user to enter another name self.candidateC.Clear() self.candidateC.SetFocus() def OnListDClick(self, event): # Remove the candidate from the ballots instance and update the control c = self.listC.GetSelection() self.names.pop(c) self.listC.Set(self.names) OpenSTV-1.6.1/openstv/Help.html0000777000175400010010000002164311400774167014701 0ustar jeffNone OpenSTV Help

OpenSTV Help

I. Overview

The single transferable vote (STV) is an election method that is used to obtain proportional representation. STV is used for national elections in Ireland, Northern Ireland, Malta, Australia, Scotland, and New Zealand. In addition, many organizations use STV, including the Church of England and the Green Party of the United States. However, no two of the above use precisely the same definition of STV. With the same set of ballots, these different methods could produce different winners. STV can best be described as a category of election methods and users of STV must specify precisely which version of STV is to be used. The purpose of this software is to help organizations use STV and also to specify precisely which STV rules they will use.

The basic structure of an STV method can be described as follows:

  1. Establish the winning threshold.
  2. Count the first place votes.
  3. If one or more candidates have surplus votes (votes in excess of the winning threshold), then transfer the surplus votes. Otherwise, eliminate the last place candidate and transfer those votes. The only candidates who can receive votes are those who are under the winning threshold and not eliminated. If there are no further candidates on a ballot than that vote is "exhausted" and does not count towards any candidate.
  4. Repeat step (3) until all of the winners are determined.

Different STV methods define more precisely how the above is implemented. STV methods must define the winning threshold and also how votes are to be transferred from candidates with a surplus and from candidates who are eliminated. Specific implementations of STV define both of these and may also modify the basic structure.

People have different points of view as to which STV method should be used in a particular situation. For a group that values simplicity, we recommend Scottish STV. It is the easiest method to understand and it provides accurate proportional representation. Another advantage is that OpenSTV's implementation of Scottish STV has been validated against the counting software used in Scotland.

For a group that desires the most accurate proportional representation, we recommend Meek STV. The transfer of votes is more complicated with Meek STV, and thus more difficult to explain to the voters. Meek is, however, generally accepted as the most accurate STV method.

The rest of this document assumes that you already chosen the particular STV method that you would like to use and guides the process of using OpenSTV. OpenSTV provides two basic operations:

  1. Running an election with a ballot file.
  2. Creating or editing a ballot file that can later be used to run an election.

II. Running an Election

To run an election, you must already have a complete ballot file. If you do not have a complete ballot file, then you can create or edit a ballot file as described below. To start the process, select "Run Election..." from the File menu.

The first step is to select the file that contains the ballots and the election method to be used. You do not need to specify the format of the ballot file as OpenSTV will determine that automatically.

The next window allows you to specify more information, optionally withdraw candidates, and to set options particular to the election method chosen (if any). This window should be self explanatory.

The election results will appear in a new tab. You then have several options for saving the results. You can copy and paste the text into a different program, or using the File menu, you can save the results in one of three formats. The text format is exactly what you see on the screen. The html format can viewed in a browser. The CSV (comma separated value) output is used by the Electoral Reform Society and can be loaded into any spreadsheet program.

The File menu also allows you to close the tabs containing election results; you can't close the console tab unless all results tabs have been closed. The font size in any tab can be changed to the desired size via the Options menu. If the election results are wider than the window size, then choosing a smaller font size may allow for easier viewing of election results.

III. Editing Ballots

OpenSTV has a built-in ballot file editor. If you would like to create a new ballot file from scratch, select "New Ballot File..." from the File menu. If you would like to edit an existing ballot file, select "Edit Ballot File..." from the File menu.

When you create a new ballot file through OpenSTV, it will always be in the BLT format, and you cannot save the ballot file in any other format. The first step in creating a new ballot file is to enter the names of the candidates, and this process is self explanatory. Once you enter the candidates' names, you are presented with the editing window.

If you are editing an existing file, then you can select ballot files in BLT format or in text format (described below), and OpenSTV will always save the file in the same format (you can't save in a different format). After selecting the ballot file to edit, you are presented with the editing window. You can have multiple ballot editing windows open at a time.

The ballot editing window contains two tabs. The Ballots tab allows you to view and edit ballots, and this process should be self-explanatory. OpenSTV only accepts valid ballots, and an empty ballot is valid. You don't need to rank all of the candidates on each ballot, but you cannot skip rankings and you cannot give two or more candidates the same ranking. The Log tab creates an audit trail of how the ballots have been edited, and you can save the log to a file.

The File menu allows you to save the ballots and the log or to save them under different filenames, but you can't change the file format. The File menu also allows you to append an existing ballot file to the ballot file you are currently editing. To append, the two ballot files must have the same number of candidates, the same number of seats, and identical candidate names.

IV. Ballot File Formats

Two ballot file formats are supported: BLT format and text format.

The BLT format is preferred and is the format used by the Electoral Reform Society. Here is an example:

    4 2          # four candidates are competing for two seats
    -2           # Bob has withdrawn (optional)
    1 4 1 3 2 0  # first ballot
    1 2 4 1 3 0
    1 1 4 2 3 0  # The first number is the ballot weight (>= 1).
    1 1 2 4 3 0  # The last 0 is an end of ballot marker.
    1 1 4 3 0    # Numbers in between correspond to the candidates
    1 3 2 4 1 0  # on the ballot.
    1 3 4 1 2 0
    1 3 4 1 2 0  # Chuck, Diane, Amy, Bob
    1 4 3 2 0
    1 2 3 4 1 0  # last ballot
    0            # end of ballots marker
    "Amy"        # candidate 1
    "Bob"        # candidate 2
    "Chuck"      # candidate 3
    "Diane"      # candidate 4
    "Gardening Club Election"  # title

The text format is a simple format that is easily created with any test editor. Here is an example:

    Amy Bob Chuck
    Bob Amy
    Chuck
    Chuck Bob

The first ballot lists Amy first, Bob second, and Chuck third. Note that candidate names must be alphanumeric. You can also add a weight to a ballot by prefixing it with a number and a colon:

    10: Amy Bob Chuck
    5: Bob Amy
    Chuck
    2: Chuck Bob

V. Advanced Use

Download and install the Linux version of OpenSTV to use OpenSTV from the command line or via scripting.

The runelection.py command runs an election for the given method and ballot file. Results are printed to stdout.

 runElection.py [-d] [-r report] [-t tiebreak] method ballotfile

    -d: enable debug
    -r: report format: text*, html, csv 
    -t: tiebreak method: random*, alpha, index
        *default

The runelection.py command runs an election for the given method and ballot file. Results are printed to stdout. The following methods are available:

Approval
Borda
Bucklin
CambridgeSTV
Condorcet
Coombs
ERS97STV
FTSTV
GPCA2000STV
IRV
MeekQXSTV
MeekSTV
NIrelandSTV
QPQ
RTSTV
SNTV
ScottishSTV
SuppVote
WarrenQXSTV
WarrenSTV

You can also write your own Python scripts to run elections. To do this, you would import OpenSTV's Python modules into your own Python program. Use runElection.py as an example of how to write your own scripts using OpenSTV.

OpenSTV-1.6.1/openstv/Icons/0001777000175400010010000000000011407513225014157 5ustar jeffNoneOpenSTV-1.6.1/openstv/Icons/blt.icns0000777000175400010010000011345411400774167015637 0ustar jeffNoneicns—,h8mk &ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿä&þþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþä&þþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿä&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿä&þþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþä&þþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿä&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿä&þþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÆþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÏ ÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏϨich#HÿÿÿÿÿÿÿÿÿÿÿÿþÕfB¿ÿÿ¶ÝþÿÿÿÿÿoÿÿOÿÿGÿÿCÿÿAÿÿ¿ÿÿÿÿðÿ1Žÿ`ƒÿÿ€€ÿÀÀÿà‡àÿð‡àÿÿùŸàÿüŸðÿþÿðÿÿÿðÿÿÿÿðÿÿÿðÿÿ¿ðÿÿŸðÿÿÿðÿÿ‡àÿÿƒÀÿÿÀÿÿÿ€ÿƒ¿ÿ?Žÿøÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿàÿÿÿðÿÿÿøÿÿÿüÿÿÿþÿÿÿÿÿÿÿÿ€ÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀÿÿÿÿÀich4ˆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýßÝýßÝýßÝêÞÚÞÝÿÿÿÿÿÿÿÿÿÿÿÝÝÝÝÝÝÝÝÝÝÝÝߟÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ Íÿÿÿÿÿÿÿÿÿÿ ÀßÿÿÿÿÿÿÿÿÿÀ ÿÿÿÿÿÿÿÿÿÀßÿÿÿÿÿÿÿÿ À ÿÿÿÿÿÿÿÿÿÿÿùÿÿÿÿÿÿÿÀÀÕÿÿÿÿÿÿÿÍÍÀÏÿÿÿÿÿÿÿ ÿûúÿìÉÿÿÿÿÿÿÿ ÿ±ó3¯àÅÿÿÿÿÿÿÿÀϱó33¯ÀÊÿÿÿÿÿÿÿ ûó33:üÙÿÿÿÿÿÿÿÏïó33¯ŸÅÿÿÿÿÿÿÿþ~ûó3:øÐÊÿÿÿÿÿÿÿ ÷wïó3¯ˆˆüÏÿÿÿÿÿÿÿçw~ûó:øˆˆÏÿÿÿÿÿÿÿwwwï󯈈ˆšÏÿÿÿÿÿÿÿßwww~ûúøˆˆˆÎÿÿÿÿÿÿÿÎwwwwïÿˆˆˆˆÚÿÿÿÿÿÿÿßwwww~ùˆˆˆˆÀÎÿÿÿÿÿÿÿÞwwwwwÿ˜ˆˆˆÀÊÿÿÿÿÿÿÿÏwwww~û¨ˆˆˆßÿÿÿÿÿÿÿÏwwwwwñ¯˜ˆˆÎÿÿÿÿÿÿÿ çwwwwóùˆˆýÊÿÿÿÿÿÿÿ ÷www~ñ¿ˆˆðÏÿÿÿÿÿÿÿÀÞwwwwñ1ùÐßÿÿÿÿÿÿÿÏçwwwó1¿ŸÍÿÿÿÿÿÿÿ þww~ñðÏÿÿÿÿÿÿÿÏîwwñ1¯ßÿÿÿÿÿÿÿ ßîwñ»¯ÐÍÿÿÿÿÿÿÿ ßÿÿÿÐÏÿÿÿÿÿÿÿÌÌßÿÿÿÿÿÿÿÍÿÿÿÿÿÿÿÏÿÿÿÿÿÿÿÏÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÍÿÿÿÿÿÿÿÏÿÿÿÿÿÿÿÜÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌßÿÿÿÿÿÿÿßýÿßýÿßýÿßýÿßýÿßýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿich8 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúúúúúúúúúúúúúúúúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúúúúúúúúþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ¬¬ýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ¬øúþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ¬øúýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ¬øúþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõýøúýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ¬øõõõõýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ¬ÿÿÿÿÿÿÿ¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõõõõõõõõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõö÷VVV÷õø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõV¬ÿ­­ÿÞàþü÷ø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ÷ý­Y/à##ØÛàüöø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõVàY/à####×þ+ø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõVÿ}/à######Þþ÷ø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ÷ÿÉþY/à#####Üêæýöø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõý´ÂþY/à####Üêâ¹èø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõùд´´ÂþY/à###Üêâ¹¹âÿöø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ¬Â´´´´ÂþY/à##Üêâ¹¹¹¹çúø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõÿ´´´´´´ÂþY/à#Üêâ¹¹¹¹¹ä¬ø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ÷д´´´´´´ÂþƒàÜêâ¹¹¹¹¹¹âÿõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõVд´´´´´´´Âêÿêâ¹¹¹¹¹¹¹¹ÿöø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõVд´´´´´´´´Âÿå¹¹¹¹¹¹¹¹¹ê+ø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõøÐ´´´´´´´´´»ÿêä¹¹¹¹¹¹¹¹ÿöø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ÷þ´´´´´´´´´»ê;êä¹¹¹¹¹¹âàø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõê»´´´´´´´´»àYàä¹¹¹¹¹Å¬ø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõûÉ´´´´´´´´»êYàã¹¹¹¹Ñùø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ÷ê´´´´´´´´»à_þã¹¹ãþõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõüÉ´´´´´´´»à _þãáÑùø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõþ´´´´´´»ê _êè¬ø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ÷þ´´´´´»à ­ýõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõ+þд´´´»à5³¬õø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõÿÉ´»à5_­àúø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõöú¬ÿÿÿÿÿ¬ùõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõö+öø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿõø¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøøú¬ÿÿÿÿÿÿÿÿÿÿÿÿÿ¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬ûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿicl4ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÜÜÝÍÍÜÝÍýÿÿÿÿÿÿýÝßÿÿÿÿÿüðÝÿÿÿÿÿýÐ ßÿÿÿÿüüÜíÿÿÿÿýÜÝßÿÿÿÿü ÝÝÀ ÿÿÿÿý[»ª¯ÀÿÿÿÿüÊ£3­ ÿÿÿÿý ï£3¯Ðÿÿÿÿü ~‘£:˜ü ÿÿÿÿýwó©ˆÿÿÿÿüÞwwñª˜ˆ ÿÿÿÿýÞwwùˆˆ‰ÿÿÿÿü×ww~øˆˆ‰À ÿÿÿÿýÞwwwûˆˆÿÿÿÿüÎwww«¨ˆ ÿÿÿÿýwww¡¿ˆÿÿÿÿü çww¡1øð ÿÿÿÿýÞww«¿ÿÿÿÿü îw¡¬ ÿÿÿÿýÍþúýÿÿÿÿü ÿÿÿÿýÿÿÿÿü ÿÿÿÿýÿÿÿÿü ÿÿÿÿÿßßßßßßßßßßßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿicl8ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿVVVVVVVVVVVVVVVVþúÿÿÿÿÿÿÿÿÿÿÿÿÿVüúúÿÿÿÿÿÿÿÿÿÿÿÿV¬úúÿÿÿÿÿÿÿÿÿÿÿVüúúÿÿÿÿÿÿÿÿÿÿV¬VøV¬ùÿÿÿÿÿÿÿÿÿVøVVVV¬ÿÿÿÿÿÿÿÿÿVöùúø¬ÿÿÿÿÿÿÿÿÿVöûƒY/ÞÙÛýV¬ÿÿÿÿÿÿÿÿÿV+­/ Ý###ܬÿÿÿÿÿÿÿÿÿVöЬ))###Üèú¬ÿÿÿÿÿÿÿÿÿVû»»Ð//##Üæ¹ç+¬ÿÿÿÿÿÿÿÿÿV+É´´»¬) Ý#Üæ¹¹¿¬ÿÿÿÿÿÿÿÿÿVV´´´»ýSÜæ¹¹¹¹Ñ¬ÿÿÿÿÿÿÿÿÿV»´´´´»ýÿæ¹¹¹¹¹çõ¬ÿÿÿÿÿÿÿÿÿV»´´´´´»ÿ¿¹¹¹¹¹Ëö¬ÿÿÿÿÿÿÿÿÿVú»´´´´´º­­¿¹¹¹¹èõ¬ÿÿÿÿÿÿÿÿÿV÷´´´´´µ‰5­¿¹¹á¬¬ÿÿÿÿÿÿÿÿÿV¬´´´´´»‰5­¿¹ÅV¬ÿÿÿÿÿÿÿÿÿVVÉ´´´´»ƒ 5þ¿¬õ¬ÿÿÿÿÿÿÿÿÿV´´´º‰5à+¬ÿÿÿÿÿÿÿÿÿVúÉ»´µ‰_¬+¬ÿÿÿÿÿÿÿÿÿV+ÐÐý­üù¬ÿÿÿÿÿÿÿÿÿVõöõ¬ÿÿÿÿÿÿÿÿÿV¬ÿÿÿÿÿÿÿÿÿV¬ÿÿÿÿÿÿÿÿÿVüÿÿÿÿÿÿÿÿÿV¬ÿÿÿÿÿÿÿÿÿ¬¬¬ü¬¬ü¬¬ü¬¬ü¬¬ü¬¬ü¬¬ü¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿICN#ÿÿÿÿÿÿÿÿýRJÿøÿø¿üø/ø_ü@ø °ø„ü0 øxŽø|¿üþ¿øÿøÿÿüÿÿø¿øü?Žø?ø˜üàøøüøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÀÿÿàÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðÿÿðics#HÿÿÀàÀáƒÄ'æ3Ïóï÷Ïsç'ÃàÀéWÿÿ?ð?ø?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?üics4ˆÿÿÿÿÿÿÿÿÿÜÌÌÌÍÿÿÿÐ ßÿÿÐ ÍÿÿÐÛ] ÿÿÐ 3¬ ÿÿÐ×᪠ÿÿÐ×{ø‰ ÿÿÐçwø‰ ÿÿÐ×w»‹ ÿÿÐw³œ ÿÿÐ Þ»À ÿÿÐ ÿÿÐ ÿÿÝÜÝÍÜÝÿÿÿÿÿÿÿÿÿics8ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú++++++++ûÿÿÿÿÿÿùVùÿÿÿÿÿùùVûÿÿÿÿùõVXˆ]õVÿÿÿÿùõû)Ù#ˆ+VÿÿÿÿùV´  Ú‰¿{õVÿÿÿÿùž´´¦­â¹äVÿÿÿÿùž´´»§á¹äõVÿÿÿÿùz´´´_§¹ Vÿÿÿÿùöž´´;§÷VÿÿÿÿùöžÂ‰^÷VÿÿÿÿùõõVÿÿÿÿùVÿÿÿÿúVVVVVVVVVVÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿih32 ö•ýh•o“ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/4’ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/®s‘ï•ÿ/¯þsïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/¯ÿþsïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/¯ÿþþsŽï•ÿ/¤€ïîeïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/“ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿòƒï¤Œï‰ÿä´˜‘½ð‰ÿ¯Œïÿþÿ€þÿþþ÷–0'7@F²þþÿþþÿþþÿþ¯Œïÿþÿ€þÿþ¿"+›ìþÏþþ݃@Þÿþþÿþþÿþ¯Œï„ÿ” ü€ÿÏÿðd„ÿ¯Œïÿþÿ€þ ”~ÿþþÿþÏþþÿ€þ B Çþÿþþÿþ¯Œï‚ÿ¿«ÿÏÿþuê‚ÿ¯Œïÿþÿþ÷#€ «þÿþÏþþÿþu`ÿþþÿþ¯Œïÿþÿþ–‚«ÿþÏ€þu‚Ùþþÿþ¯Œïÿ1ƒ«ÿÏÿþu„wÿ¯Œïÿþÿå…ªÏþu…+þþÿþ¯Œïÿþÿ´†{u†ôþÿþ¯Œï€ÿ™”Ø€ÿ¯Œïÿþÿ’”Ðþÿþ¯ŒïÿþÿŸˆ‡Ýþÿþ¯Œï€ÿˆ¬…ú€ÿ¯Œïÿþÿô‡þ¬„;þþÿþ¯ŒïÿþÿþP‡þþ¬ƒþþÿþ¯Œïÿ½‡€ÿ¬ íÿ¯ŒïÿþÿþþI†þþÿþ¬€…ÿþþÿþ¯Œïÿþÿþþâ…þþÿþþ¬9úÿþþÿþ¯ŒïƒÿÄ „‚ÿö<%çƒÿ¯Œïÿþÿ€þÿɃþþÿþÆ-7çþþÿþþÿþ¯Œïÿþÿ€þÿþëbûÜžD ùÿþþÿþþÿþ¯Œïÿþÿ€þÿþþÿÛw+5…èÿþÿþþÿþþÿþ¯ŒïŠÿôØÐÜøŠÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïœÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïœÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œ¤œ¯xÄý•ýh•o“ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/4’ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/®s‘ï•ÿ/¯þsïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/¯ÿþsïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/¯ÿþþsŽï•ÿ/¤€ïîeïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/“ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿòƒï¤Œï‰ÿä´˜‘½ð‰ÿ¯Œïÿþÿ€þÿþþ÷–0'7€ F²þþÿþþÿþþÿþ¯Œïÿþÿ€þÿþ¿"+›ìþσ @Þÿþþÿþþÿþ¯Œï„ÿ” ü€ÿÏ„„ÿ¯Œïÿþÿ€þ”~ÿþþÿþÏ… Çþÿþþÿþ¯Œï‚ÿ¿ € «ÿÏ„qê‚ÿ¯Œïÿþÿþ÷#þ® «þÿþσ ÖþL`ÿþþÿþ¯Œïÿþÿþ–+üþþ® «ÿþÏ‚ ÖþÿÜÙþþÿþ¯Œïÿ1›ÿ® «ÿÏÖÿUwÿ¯Œïÿþÿåìÿþþÿþ¯ ªÏ€ Öþÿþÿþª+þþÿþ¯Œ ïÿþÿ´&þÿþþÿþþ® {Öþþÿþÿþäôþÿþ¯Œï€ÿ™D…ÿ®Ö„ÿýØ€ÿ¯Œ ïÿþÿ’Lþÿþþÿþþÿþ þÿþþÿþÿþþÐþÿþ¯Œ ïÿþÿŸ=þÿþþÿþþÿþÏ­ÿþþÿþÿþûÝþÿþ¯Œï€ÿÂþ…ÿÏ o­ƒÿØú€ÿ¯Œ ïÿþÿôÕÿþþÿþþÿþÏ £o­þÿþÿþ—;þþÿþ¯Œ ïÿþÿþPyÿþþÿþþÿþÏ ££n­ÿþÿþ;þþÿþ¯Œïÿ½ì„ÿÏ €¤n­ÿÿÀ íÿ¯Œ ïÿþÿþþI]þþÿþþÿþÏ ££¤£o­õ*…ÿþþÿþ¯Œ ïÿþÿþþâ‘þÿþþÿþÏ ££¤££n;9úÿþþÿþ¯ŒïƒÿÄ ŠþÿÏ ‚¤ž&%çƒÿ¯Œïÿþÿ€þÿÉJÛþÿþÏ ££¤£7çþþÿþþÿþ¯Œïÿþÿ€þÿþëbT©ãÍ ¢f+ ùÿþþÿþþÿþ¯Œïÿþÿ€þÿþþÿÛw+5…èÿþÿþþÿþþÿþ¯ŒïŠÿôØÐÜøŠÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïœÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïœÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œ¤œ¯xÄý•ýh•o“ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/4’ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/®s‘ï•ÿ/¯þsïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/¯ÿþsïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/¯ÿþþsŽï•ÿ/¤€ïîeïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿ/“ïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿòƒï¤Œï‰ÿä´˜‘½ð‰ÿ¯Œïÿþÿ€þÿþþ÷–0ƒ F²þþÿþþÿþþÿþ¯Œïÿþÿ€þÿþ¿"ˆ @Þÿþþÿþþÿþ¯Œï„ÿ”Š„ÿ¯Œïÿþÿ€þ” Çþÿþþÿþ¯Œï‚ÿ¿ €Œê‚ÿ¯Œ ïÿþÿþ÷#þ®Œ`ÿþþÿþ¯Œ ïÿþÿþ–+üþþ®‹Ùþþÿþ¯Œïÿ1›ÿ®‹wÿ¯Œ ïÿþÿåìÿþþÿþ¯Š+þþÿþ¯Œïÿþÿ´&þÿþþÿþþ®‰ôþÿþ¯Œï€ÿ™D…ÿ®‰Ø€ÿ¯Œïÿþÿ’Lþÿþþÿþþÿþ ‰Ðþÿþ¯ŒïÿþÿŸ=þÿþþÿþþÿþωÝþÿþ¯Œï€ÿÂþ…ÿψú€ÿ¯ŒïÿþÿôÕÿþþÿþþÿþψ;þþÿþ¯ŒïÿþÿþPyÿþþÿþþÿþψþþÿþ¯Œïÿ½ì„ÿχ íÿ¯ŒïÿþÿþþI]þþÿþþÿþχ…ÿþþÿþ¯Œïÿþÿþþâ‘þÿþþÿþφ9úÿþþÿþ¯ŒïƒÿÄ ŠþÿÏ…%çƒÿ¯Œïÿþÿ€þÿÉJÛþÿþÏ„ 7çþþÿþþÿþ¯Œïÿþÿ€þÿþëbT©ãÍ‚ ùÿþþÿþþÿþ¯Œïÿþÿ€þÿþþÿÛw+5…èÿþÿþþÿþþÿþ¯ŒïŠÿôØÐÜøŠÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïœÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œïœÿ¯Œïÿþÿ€þÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþ¯Œ¤œ¯xÄýil32~ÂcŸ‹Ÿÿ?wŠŸÿ?þw‰Ÿÿ?ÿþwˆŸÿ3€Ÿ,‡Ÿÿ«Ÿ†Ÿ„ÿÙibx¯÷„ÿ†Ÿ‚ÿ âSV©¸PÃ…7œþ‚ÿ†Ÿÿ Ì8Ìÿÿß^ÿÿøwcüÿ†Ÿ€ÿ âÙÿÿß^ÿÿþxyÿ†Ÿ€ÿPÙÿß^ÿþxЀÿ†ŸÿÿÙÙß^þx_€ÿ†Ÿÿÿ‚¹^x‚þÿÿ†Ÿÿÿjƒ„éÿÿ†ŸÿÿcŒáÿÿ†Ÿÿÿ{„=‚öÿÿ†Ÿÿÿµ„^Ú4€ÿ†Ÿÿÿùƒ^ÿÚ€”€ÿ†Ÿ€ÿ¡ƒ^ÿÿÚ'÷€ÿ†Ÿ€ÿþe‚^€ÿ¾Òÿ†Ÿÿý{€^ÿ÷š9Ñ‚ÿ†ŸƒÿÑ`!F>Žöƒÿ†Ÿ…ÿþéáõ†ÿ†Ÿ’ÿ†Ÿ’ÿ†Ÿ’ÿ†Ÿ’ÿ†’ÁÂcŸ‹Ÿÿ?wŠŸÿ?þw‰Ÿÿ?ÿþwˆŸÿ3€Ÿ,‡Ÿÿ«Ÿ†Ÿ„ÿÙibx¯÷„ÿ†Ÿ‚ÿâSV©¸€œþ‚ÿ†ŸÿÌ8Ìÿÿß‚cüÿ†Ÿ€ÿâ87Ùÿÿß‚=yÿ†Ÿ€ÿSÌÛ7ÙÿßuþOЀÿ† ŸÿÿÙVÿÿÛ7Ù߀uþÿÐ_€ÿ†Ÿÿÿ¨€ÿÛ7¹uþ€ÿ:þÿÿ†ŸÿÿjÓÿÛ*uþÿTéÿÿ†ŸÿÿcÛ‚ÿÊ Ú‚ÿ]áÿÿ†Ÿÿÿ{À‚ÿß'-ÚÿEöÿÿ†Ÿÿÿµ‚ÿß=Œ-Úÿÿõ@€ÿ†Ÿÿÿù7öÿß=¤Œ-Úÿ•”€ÿ†Ÿ€ÿ¡sÿß=¤¤Œ-½:÷€ÿ†Ÿ€ÿþfŠþÿÿß=€¤zÒÿ†Ÿÿ ý{NÏÿß=¤Ÿc2Ñ‚ÿ†ŸƒÿÑ`:H.9Žöƒÿ†Ÿ…ÿþéáõ†ÿ†Ÿ’ÿ†Ÿ’ÿ†Ÿ’ÿ†Ÿ’ÿ†’ÁÂcŸ‹Ÿÿ?wŠŸÿ?þw‰Ÿÿ?ÿþwˆŸÿ3€Ÿ,‡Ÿÿ«Ÿ†Ÿ„ÿÙibx¯÷„ÿ†Ÿ‚ÿâPƒœþ‚ÿ†Ÿÿ̆cüÿ†Ÿ€ÿâ8‡yÿ†Ÿ€ÿSÌÛ†Ѐÿ†ŸÿÿÙVÿÿÛ†_€ÿ†Ÿÿÿ¨€ÿÛ…þÿÿ†ŸÿÿjÓÿÛ…éÿÿ†ŸÿÿcÛ‚ÿÊ…áÿÿ†Ÿÿÿ{À‚ÿß„öÿÿ†Ÿÿÿµ‚ÿß„4€ÿ†Ÿÿÿù7öÿß„”€ÿ†Ÿ€ÿ¡sÿ߃'÷€ÿ†Ÿ€ÿþfŠþÿÿß‚Òÿ†Ÿÿý{NÏÿß%Ñ‚ÿ†ŸƒÿÑ`:H0Žöƒÿ†Ÿ…ÿþéáõ†ÿ†Ÿ’ÿ†Ÿ’ÿ†Ÿ’ÿ†Ÿ’ÿ†’Áis32@…Ï3ƒO…ÿŸ{‚O…ÿ‡Ÿ8 Oÿÿ÷ –{™æÿÿ Oÿ÷Méï®ýTÝÿ OÿŠCå®|Lÿ Oÿ>89ùOÿ7€€õOÿr€¥D2ÿ Oÿç®õ:¼ÿ OÿÿÝMq—ÀÿÿOÿùõÿO‡ÿ,‡P@…Ï3ƒO…ÿŸ{‚O…ÿ‡Ÿ8 Oÿÿ÷ –6oæÿÿ Oÿ÷]éï(Ýÿ Oÿ éˆåÜ”ÿ Oÿÿö}Üÿ£ù OÿžÿÿéNõÿ¨õ Oÿ™üÿïjpõ—ÿ Oÿç˜þïpž]Àÿ OÿÿÝ”˜Ir¾ÿÿOÿùõÿO‡ÿ,‡P@…Ï3ƒO…ÿŸ{‚O…ÿ‡Ÿ8 Oÿÿ÷Š>6oæÿÿOÿ÷MÝÿOÿ éELÿOÿÿöE€ùOÿžÿÿéõOÿ™üÿï€2ÿ Oÿç˜þï¼ÿ OÿÿÝ”˜/»ÿÿOÿùõÿO‡ÿ,‡Pit32­’ýýýýýýýýý?¿?¹¿ÿ¹¿ÿ¶¿ÿ}aµ¿ÿüa´¿ÿÿüa³¿ÿÿÿüa²¿ÿ€ÿüa±¿ÿÿüa°¿ÿ‚ÿüa¯¿ÿƒÿüa®¿ÿ„ÿüa­¿ÿ…ÿüa¬¿ÿ†ÿüa«¿ÿ‡ÿüaª¿ÿˆÿüa©¿ÿ‰ÿüa¨¿ÿ?Š}§¿ÿ¹¿ÿ¹¿ÿ¿?¥Óÿ¥Óÿ¥Óÿ¥¢ÿ ò̯™Š‚‚‰˜®Ëñ¢ÿ¥žÿâšY"ˆ W˜àžÿ¥›ÿñ—86”ï›ÿ¥™ÿétƒ *Kc87dL+ƒ qæ™ÿ¥—ÿö‚ FŽËù€ÿ|€ÿúÌH ~ô—ÿ¥–ÿÁ%)“ì„ÿ|„ÿî–,"½–ÿ¥”ÿûz€.°ý†ÿ|†ÿþ³1€uú”ÿ¥“ÿîD€˜üˆÿ|ˆÿýœ€?ë“ÿ¥’ÿà)€EåŠÿ|ŠÿçI€%Ý’ÿ¥‘ÿÙ€ý‹ÿ|‹ÿý…Ö‘ÿ¥ÿà€¥ÿ|ÿ¬Ýÿ¥ÿî)YûŒÿ|Œÿýe&íÿ¥ŽÿüEƒYû‹ÿ|‹ÿýeƒDüŽÿ¥Žÿ{…YûŠÿ|Šÿýe…~Žÿ¥ÿÁ†Yû‰ÿ|‰ÿýe†Èÿ¥Œÿö%ˆYûˆÿ|ˆÿýeˆ-úŒÿ¥ŒÿƒŠYû‡ÿ|‡ÿýeŠ‘Œÿ¥‹ÿé ‹Yû†ÿ|†ÿýe‹ñ‹ÿ¥‹ÿuYû…ÿ|…ÿýe†‹ÿ¥ŠÿñŽYû„ÿ|„ÿýeŽùŠÿ¥Šÿ™Yûƒÿ|ƒÿýe©Šÿ¥Šÿ:‘Yû‚ÿ|‚ÿýe‘JŠÿ¥‰ÿä’Yûÿ|ÿýe’î‰ÿ¥‰ÿœ”Yû€ÿ|€ÿýe”¨‰ÿ¥‰ÿ[• Yûÿÿ|ÿÿýe•e‰ÿ¥‰ÿ$– Yûÿ|ÿýe–,‰ÿ¥ˆÿó—Yû|ýe—öˆÿ¥ˆÿΙX|{e™Ðˆÿ¥ˆÿ°šš±ˆÿ¥ˆÿ›»˜ˆÿ¥ˆÿ‹»ˆˆÿ¥ˆÿ…»€ˆÿ¥ˆÿ†»€ˆÿ¥ˆÿ»‡ˆÿ¥ˆÿ»˜ˆÿ¥ˆÿµš°ˆÿ¥ˆÿÕz^™Ðˆÿ¥ˆÿùœ|ü^—öˆÿ¥‰ÿ0œ|ÿü]–+‰ÿ¥‰ÿjœ|ÿÿü]•e‰ÿ¥‰ÿ®œ|€ÿü]”¨‰ÿ¥‰ÿñ›|ÿü]’î‰ÿ¥ŠÿO›|‚ÿü]‘IŠÿ¥Šÿ¯›|ƒÿü]©Šÿ¥Šÿúš|„ÿü]ŽùŠÿ¥‹ÿŒš|…ÿü]†‹ÿ¥‹ÿô™|†ÿü]‹ñ‹ÿ¥Œÿ—™|‡ÿü]ŠŒÿ¥Œÿû2˜|ˆÿü]ˆ,úŒÿ¥ÿΗ|‰ÿü]†Çÿ¥Žÿ†—|Šÿü]…}Žÿ¥ŽÿýK–|‹ÿü]ƒCûŽÿ¥ÿï+•|Œÿü]&ìÿ¥ÿá”|ÿ¬€Ýÿ¥‘ÿÛ“|‹ÿþ‡Õ‘ÿ¥’ÿá+’|ŠÿèJ€$Ü’ÿ¥“ÿïJ‘|ˆÿý€>ê“ÿ¥”ÿý…|†ÿþ´2€tú”ÿ¥–ÿÌ1Ž|„ÿï—-!¼–ÿ¥—ÿû–Œ|€ÿúΑI }ô—ÿ¥™ÿóŠŠ8fM-ƒ oæ™ÿ¥›ÿú­M4’î›ÿ¥žÿï«g-ˆV–ßžÿ¥¢ÿ ÷Ò±šˆ‡–¬Éï¢ÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥?Ó?ýýýýýýýýýýý’ýýýýýýýýý?¿?¹¿ÿ¹¿ÿ¶¿ÿ}aµ¿ÿüa´¿ÿÿüa³¿ÿÿÿüa²¿ÿ€ÿüa±¿ÿÿüa°¿ÿ‚ÿüa¯¿ÿƒÿüa®¿ÿ„ÿüa­¿ÿ…ÿüa¬¿ÿ†ÿüa«¿ÿ‡ÿüaª¿ÿˆÿüa©¿ÿ‰ÿüa¨¿ÿ?Š}§¿ÿ¹¿ÿ¹¿ÿ¿?¥Óÿ¥Óÿ¥Óÿ¥¢ÿ ò̯™Š‚‚‰˜®Ëñ¢ÿ¥žÿâšY"ˆ W˜àžÿ¥›ÿñ—86”ï›ÿ¥™ÿétƒ*Kc8Š qæ™ÿ¥—ÿö‚ FŽËù€ÿŒ ~ô—ÿ¥–ÿÁ%)“ì„ÿŽ"½–ÿ¥”ÿûz€.°ý†ÿuú”ÿ¥“ÿîD€˜üˆÿ‘?ë“ÿ¥’ÿà)€EåŠÿ’%Ý’ÿ¥‘ÿÙ€ý‹ÿ“Ö‘ÿ¥ÿà€¥ÿ”Ýÿ¥ÿî)YûŒÿ•&íÿ¥ŽÿüEƒYû‹ÿ–DüŽÿ¥Žÿ{§d€YûŠÿ’X¨~Žÿ¥ÿÁÿýd€Yû‰ÿ‘Xûÿ~Èÿ¥Œÿ ö%Dýÿÿýd€Yûˆÿ Xûÿÿü?-úŒÿ¥Œÿƒäÿýd€Yû‡ÿXûÿß ‘Œÿ¥‹ÿé —ƒÿýd€Yû†ÿŽXûƒÿ‹ñ‹ÿ¥‹ÿu-ü„ÿýd€Yû…ÿXû„ÿø"†‹ÿ¥Šÿñ®†ÿýd€Yû„ÿŒXû†ÿžùŠÿ¥Šÿ™(ý‡ÿýd€Yûƒÿ‹Xû‡ÿú©Šÿ¥Šÿ:‘‰ÿýd€Yû‚ÿŠXû‰ÿJŠÿ¥‰ÿäëŠÿýd€Yûÿ‰XûŠÿßî‰ÿ¥‰ÿœDŒÿýd€Yû€ÿˆXûŒÿ5¨‰ÿ¥‰ÿ[‹ÿýd€Yûÿÿ‡Xûÿe‰ÿ¥‰ÿ$ÉŽÿýd€Yûÿ†XûŽÿÀ,‰ÿ¥ˆÿóøÿýd€Yû…Xûÿôöˆÿ¥ˆÿÎ(‘ÿýd€X|„Xû‘ÿ%Јÿ¥ˆÿ°H’ÿýd€ƒXû’ÿH±ˆÿ¥ˆÿ›a“ÿýd…Xû“ÿc˜ˆÿ¥ˆÿ‹q”ÿýdƒXû”ÿuˆˆÿ¥ˆÿ…y•ÿýdXû•ÿ~€ˆÿ¥ˆÿ†y–ÿýQ€_ü•ÿ~€ˆÿ¥ˆÿp—ÿ_ü”ÿu‡ˆÿ¥ˆÿ^—ÿ‚_ü“ÿc˜ˆÿ¥ˆÿµC—ÿ €_ü’ÿI°ˆÿ¥ˆÿÕ —ÿN<€_ü‘ÿ%Јÿ¥ˆÿùð–ÿP¢<€_üÿôöˆÿ¥‰ÿ0º–ÿP¤¢<€_üŽÿÁ+‰ÿ¥‰ÿjz–ÿP¤¤¢<€_üÿ€e‰ÿ¥‰ÿ®0–ÿP€¤¢<€_üŒÿ6¨‰ÿ¥‰ÿñÚ•ÿP¤¢<€_üŠÿßî‰ÿ¥ŠÿOz•ÿP‚¤¢<€_ü‰ÿIŠÿ¥Šÿ¯÷”ÿPƒ¤¢<€_ü‡ÿú©Šÿ¥Šÿú˜”ÿP„¤¢<€_ü†ÿ ùŠÿ¥‹ÿŒö“ÿP…¤¢<€_ü„ÿù#†‹ÿ¥‹ÿôƒ“ÿP†¤¢<€_üƒÿŒñ‹ÿ¥Œÿ— Ú’ÿP‡¤¢<€_üÿá Œÿ¥Œÿû29û‘ÿPˆ¤¢<€ _üÿÿüA,úŒÿ¥ÿÎw‘ÿP‰¤¢<€_üÿÇÿ¥Žÿ†€¥ÿPФ¢;€_ª}Žÿ¥ŽÿýK¹ÿP‹¤¢;€CûŽÿ¥ÿï+ ºŽÿPŒ¤¢;&ìÿ¥ÿá¦ÿP¤o€Ýÿ¥‘ÿÛxû‹ÿP‹¤£VÕ‘ÿ¥’ÿá+€;ÛŠÿPФ•/€$Ü’ÿ¥“ÿïJ€ …÷ˆÿPˆ¤¢e €>ê“ÿ¥”ÿý…€šø†ÿP†¤£t €tú”ÿ¥–ÿÌ1|Ü„ÿP„¤™a!¼–ÿ¥—ÿû–‚3~¾ó€ÿP€¤¡„]/ }ô—ÿ¥™ÿóŠƒ #Gb8$A1ƒ oæ™ÿ¥›ÿú­M4’î›ÿ¥žÿï«g-ˆV–ßžÿ¥¢ÿ ÷Ò±šˆ‡–¬Éï¢ÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥?Ó?ýýýýýýýýýýý’ýýýýýýýýý?¿?¹¿ÿ¹¿ÿ¶¿ÿ}aµ¿ÿüa´¿ÿÿüa³¿ÿÿÿüa²¿ÿ€ÿüa±¿ÿÿüa°¿ÿ‚ÿüa¯¿ÿƒÿüa®¿ÿ„ÿüa­¿ÿ…ÿüa¬¿ÿ†ÿüa«¿ÿ‡ÿüaª¿ÿˆÿüa©¿ÿ‰ÿüa¨¿ÿ?Š}§¿ÿ¹¿ÿ¹¿ÿ¿?¥Óÿ¥Óÿ¥Óÿ¥¢ÿ ò̯™Š‚‚‰˜®Ëñ¢ÿ¥žÿâšY"ˆ W˜àžÿ¥›ÿñ—86”ï›ÿ¥™ÿét• qæ™ÿ¥—ÿö‚ ™ ~ô—ÿ¥–ÿÁ%"½–ÿ¥”ÿûzŸuú”ÿ¥“ÿîD£?ë“ÿ¥’ÿà)¥%Ý’ÿ¥‘ÿÙ§Ö‘ÿ¥ÿà©Ýÿ¥ÿî)«&íÿ¥ŽÿüE­DüŽÿ¥Žÿ{§dª~Žÿ¥ÿÁÿýd©Èÿ¥Œÿ ö%Dýÿÿýd©-úŒÿ¥Œÿƒäÿýd©‘Œÿ¥‹ÿé —ƒÿýd¨ñ‹ÿ¥‹ÿu-ü„ÿýd¨†‹ÿ¥Šÿñ®†ÿýd§ùŠÿ¥Šÿ™(ý‡ÿýd§©Šÿ¥Šÿ:‘‰ÿýd¦JŠÿ¥‰ÿäëŠÿýd¥î‰ÿ¥‰ÿœDŒÿýd¥¨‰ÿ¥‰ÿ[‹ÿýd¤e‰ÿ¥‰ÿ$ÉŽÿýd£,‰ÿ¥ˆÿóøÿýd¢öˆÿ¥ˆÿÎ(‘ÿýd¢Ðˆÿ¥ˆÿ°H’ÿýd¡±ˆÿ¥ˆÿ›a“ÿýd ˜ˆÿ¥ˆÿ‹q”ÿýdŸˆˆÿ¥ˆÿ…y•ÿýdž€ˆÿ¥ˆÿ†y–ÿýQ€ˆÿ¥ˆÿp—ÿ‡ˆÿ¥ˆÿ^—ÿ˜ˆÿ¥ˆÿµC—ÿ°ˆÿ¥ˆÿÕ —ÿЈÿ¥ˆÿùð–ÿœöˆÿ¥‰ÿ0º–ÿœ+‰ÿ¥‰ÿjz–ÿœe‰ÿ¥‰ÿ®0–ÿœ¨‰ÿ¥‰ÿñÚ•ÿ›î‰ÿ¥ŠÿOz•ÿ›IŠÿ¥Šÿ¯÷”ÿ›©Šÿ¥Šÿú˜”ÿšùŠÿ¥‹ÿŒö“ÿš†‹ÿ¥‹ÿôƒ“ÿ™ñ‹ÿ¥Œÿ— Ú’ÿ™Œÿ¥Œÿû29û‘ÿ˜,úŒÿ¥ÿÎw‘ÿ—Çÿ¥Žÿ†€¥ÿ—}Žÿ¥ŽÿýK¹ÿ–CûŽÿ¥ÿï+ ºŽÿ•&ìÿ¥ÿá¦ÿ”Ýÿ¥‘ÿÛxû‹ÿ“Õ‘ÿ¥’ÿá+€;ÛŠÿ’$Ü’ÿ¥“ÿïJ€ …÷ˆÿ‘>ê“ÿ¥”ÿý…€šø†ÿtú”ÿ¥–ÿÌ1|Ü„ÿŽ!¼–ÿ¥—ÿû–‚3~¾ó€ÿŒ }ô—ÿ¥™ÿóŠƒ#Gb8Š oæ™ÿ¥›ÿú­M4’î›ÿ¥žÿï«g-ˆV–ßžÿ¥¢ÿ ÷Ò±šˆ‡–¬Éï¢ÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥Óÿ¥?Ó?ýýýýýýýýýýýl8mk#_________________P_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ„_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßSßßßßßßßßßßßßßßßßßßßßßßÃs8mk ////////¯ÿÿÿÿÿÿÿÿá!¯ÿÿÿÿÿÿÿÿÿá!¯ÿÿÿÿÿÿÿÿÿÿدÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¯ÿÿÿÿÿÿÿÿÿÿï¤ïïïïïïïïïïàt8mk@?mÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿmÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??OpenSTV-1.6.1/openstv/Icons/blt.ico0000777000175400010010000006117611400774167015460 0ustar jeffNone 00h– èþ(æ 00¨ ¨¶h^"00 ¨%Æ'  ¨nM h^(0`€€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿppppppxˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡ÿÿÿÿÿÿÿˆˆÿÿÿÿÿÿÿpÿÿÿÿÿøpÿÿÿÿÿ€ÿÿÿÿøpfî 3ÿÿÿÿ‡ÿÿÿÿ€fîî ›³ÿÿÿÿpÿÿÿøîîæ ¹¹°ÿÿÿ€ÿÿÿ€nîîî »›0 ÿÿÿ‡ÿÿvîîîî ›± ÿÿpÿÿ÷îîîæ ¹0ªªÿÿ€ÿÿ÷nîîîî ³ªªÿÿÿÿ€îîîîî *ªª'ÿÿ†ÿÿ€îîîîæªªª ÿÿpÿÿvîîîîî*ªªª ÿÿÿpîîîîæªªªª ÿ†ÿÿ†îîîî`ªªªª ÿÿqÿÿpîîîæ ªªª ÿÿ†ÿÿðîîî`» ªªª!ÿÿ€ÿÿðnîæ³ ‘ ªª'ÿÿ€ÿÿøî`»» ™ªªÿÿ€ÿÿÿæ»» ™‘  ÿÿÿÿÿ€`»»³ ™™ ÿÿÿ„ÿÿÿ÷»»» ™™‘ÿÿÿrÿÿÿ€;»» ™™ÿÿÿÿÿÿÿø;³ ™oÿÿÿÿ„ÿÿÿÿÿøhÿÿÿÿÿ‚ÿÿÿÿÿÿÿ‡‡ÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿuÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿñÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿôÿpÿÿÿÿÿÿÿÿÿÿÿÿð÷ÿÿÿÿÿÿÿÿÿÿÿÿòpÿÿÿÿÿÿÿÿÿÿÿÿñ‡ÿÿÿÿÿÿÿÿÿÿÿÿôwwwwwwwwwwwwp pwpwpwavqvwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þþÿþÿþÿþÿþÿþÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿ( @€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿpppppppppppÿÿÿÿÿÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿðÿÿÿÿøÿÿÿÿÿ÷ÿÿÿ‡ÿÿÿðÿÿ÷fî³ÿÿ÷ÿÿvîî¹0ÿÿðÿ÷nîî› ÿ÷ÿðîîî0ª'ÿðÿ†îîÿ÷ÿvîî ÿðÿ~îîæ ªª¢÷ÿvîîપ¢ÿðÿvîî *ª ÿ÷ÿöîà» ª§ÿðÿñæ+»‘*ÿ÷ÿø`»³™ÿðÿÿ»»™ÿÿ÷ÿÿÿC3ÿÿðÿÿÿøwwÿÿÿ÷ÿÿÿÿÿÿÿÿxwpÿÿÿÿÿÿÿÿxgÿÿÿÿÿÿÿÿ÷pÿÿÿÿÿÿÿÿwÿÿÿÿÿÿÿÿwpxxw‡‡xw‡ÿÿÿÿÿÿÿÿøøøøøøøøøøøøøøøøøøøøøøøøøø?øøÿÿÿÿÿÿÿÿÿ( €€€€€€€€€€€€€ÀÀÀÿÿÿÿÿÿÿÿÿÿÿÿwxw‡xwÿÿÿÿ÷ÿÿÿÿ÷øv3øöî9(÷~î3£÷nî ¢÷~ã ¢ø~k§÷÷»™÷ÿsWÿ÷ÿÿÿ÷‡ÿÿÿ÷pxˆˆˆ‡ÿÿÀÀÀÀÀÀÀÀÀÀÀÀÀÀÿÿ(0`     *;&&++==-''++&<77"""%%%+++///000555999LUqDDJJLLTT]]yyA+Ddu{{~~@@@FFFIIIPPPWWWaaaeeehhhooopppwwwxxx—ª­ÀÖØÜäõûþ€€ŠŠ‘‘››  ©©®®ÎÎÕÕÛÛããììþþƒfžn¬ÝðþÆ››ªªÜžö¢û£þ¤ÿÏÏììþþ………‘‘‘•••˜˜˜žžž¤¤¤®®®²²²´´´¾¾¾ÂÂÂÅÅÅÉÉÉÐÐÐÙÙÙÝÝÝâââåååéééîîîñññõõõùùùþþþÿÿÿ$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$:uvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvA$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿ…~}~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿ~A"$oƒÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿƒ;-UZW jg^1 B†ÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿ|+Y\\\W jjjjc%‚ÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿ{ P\\\\\W jjjjjh!ÿÿÿÿÿÿv$ƒÿÿÿÿÿR\\\\\\W jjjjj_&ÿÿÿÿÿÿv$ƒÿÿÿÿÿ7.\\\\\\\W jjjj_FLoÿÿÿÿÿv$ƒÿÿÿÿy[\\\\\\\W jjj_FNNG ƒÿÿÿÿv$ƒÿÿÿÿ9/\\\\\\\\W jj_FNNNNpÿÿÿÿv$ƒÿÿÿ…Y\\\\\\\\W j_FNNNNND&ÿÿÿÿv$ƒÿÿÿ{\\\\\\\\\W _FNNNNNNIÿÿÿÿv$ƒÿÿÿt\\\\\\\\\WFNNNNNNNN~ÿÿÿv$ƒÿÿÿq+\\\\\\\\\TCNNNNNNNNN}ÿÿÿv$ƒÿÿÿt*\\\\\\\\UHNNNNNNNN~ÿÿÿv$ƒÿÿÿw\\\\\\\U5 3HNNNNNNK…ÿÿÿv$ƒÿÿÿ[\\\\\Vfl b3HNNNNNF"ÿÿÿÿv$ƒÿÿÿÿ$S\\\\Ufnl bb3HNNNN(@ÿÿÿÿv$ƒÿÿÿÿq\\\Ufnnl bbb3HNNI~ÿÿÿÿv$ƒÿÿÿÿÿ!P\Ufnnnl bbbb3HN';ÿÿÿÿÿv$ƒÿÿÿÿÿyOfnnnnl bbbbb3)ƒÿÿÿÿÿv$ƒÿÿÿÿÿÿq5nnnnnl bbbbbb0 {ÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿq dnnnnl bbbba2{ÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿy!emnl bb`]7~ÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿ…q$08wÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿwtqty…ÿÿÿÿÿÿÿÿÿÿÿÿv$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿƒƒƒƒƒƒƒu$ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"&ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$uƒƒƒƒ;ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"wÿÿÿ@ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"wÿÿ@ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$vÿ@ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"v@ƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"$==@==@=========>==>====>>@=@=@@=@@@@@@@@=@@>@>@@=@=@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þ?þþÿþÿþÿþÿþÿþÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿ( @ *=-:79977!7'=88,,,':'%2911109>;;;>>>EOT]u4@4HHNNVVssP^.F=^wxVVQSQ___bbbffeiiiwwwyyy•½ÐÚõþŠŠ¨¨ÀÀÊÊÍÍÓÓÛÛßßööþþ…cšz¾Ãøþ©©¸¸ŒÚŸ÷¤ÿÌÌÙÙßßÿÿŽŽŽ”””žžž¡¡¡«««¯¯¯µµµÌÌÌÑÑÑÙÙÙáááéééöööùùùþþþÿÿÿRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRÿÿÿÿÿÿÿÿÿ[Z\ÿÿÿÿÿÿÿÿÿRÿÿÿÿÿÿX*  $O\ÿÿÿÿÿÿRÿÿÿÿÿ/;?=%JIAXÿÿÿÿÿRÿÿÿÿ*6???=%JJJBXÿÿÿÿRÿÿÿS ????=%JJH1\ÿÿÿRÿÿÿ >????=%JH350QÿÿÿRÿÿV!?????=%H3554ÿÿÿRÿÿ/8?????=35555\ÿÿRÿÿ*=?????9355555ZÿÿRÿÿ-;????=55555[ÿÿRÿÿQ7???= G#'5555ÿÿÿRÿÿY??= LM#E'552*ÿÿÿRÿÿÿ)9= LNM#EE'5XÿÿÿRÿÿÿZ LNNM#EEE'/ÿÿÿÿRÿÿÿÿWKNNM#EEE&*ÿÿÿÿÿRÿÿÿÿÿZ)(FG"C@ RÿÿÿÿÿÿRÿÿÿÿÿÿÿYP-*/U\ÿÿÿÿÿÿÿRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿURRRRRÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿRRR/Rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ./Rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ./Rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ//*SRRRRRRRRRRRRRRR/ÿÿÿÿÿÿÿÿøøøøøøøøøøøøøøøøøøøøøøøøøø?øøÿÿÿÿÿÿÿÿÿ( 9N]:E}8(T|66{pDIqKKKM]MVVVeeewww{{{£¨2—2ÜõÿEˆCL”L˜˜˜˜žž7>ééïïööþþ””M™™r®/r—j¥p®oo™ý>––žõååééï†ŠŠŠ–––ŸŸŸ  ŠŠ  ¼À¼»¾ÀÏÏÏÝÝÝæææöööùùùÿÿÿ////////// -ÿÿÿÿÿÿÿÿÿÿ/-ÿÿÿÿÿ8ÿÿÿÿ/-ÿÿ6  #4ÿÿ/-ÿ7%)3ÿ/-ÿ!$ÿ/-ÿ8/-ÿÿ/-ÿ1*"ÿ/-ÿ8 +,"'6ÿ/-ÿÿ82(&7ÿÿ/-ÿÿÿÿÿÿÿÿ-0 -ÿÿÿÿÿÿÿÿ0.55555555 ÿÿÀÀÀÀÀÀÀÀÀÀÀÀÀÀÿÿ(0` €% ÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏÏϨþ¤¤¤þ¯¯¯ÿ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þ¯¯¯þ¯¯¯ÿ¯¯¯þxxxþÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôôôÿØØØÿÐÐÐÿÜÜÜÿøøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿÛÛÛþwwwþ+++ÿþþÿþþ555ÿ………þèèèþÿÿÿÿþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþëëëþbbbÿþTTþ©©ÿããþÍÍþ ÿ¢ûþÜþfžÿ+Dþ þÿùùùþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿÉÉÉþþJJÿÛÛþþþþÿÿÿþþþÏÏþ ÿ£þþ£þþ¤ÿÿ£þþÆþ-ÿ777þçççÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÄÄÄÿ ÿŠŠÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÏÏÿ ÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿžöÿ&<ÿ%%%ÿçççÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþâââþÿ‘‘þþþþÿÿÿþþþþþþÿÿÿþþþÏÏþ ÿ£þþ£þþ¤ÿÿ£þþ£þþn¬ÿþ;ÿ999þúúúþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþIIIþ]]ÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÏÏþ ÿ£þþ£þþ¤ÿÿ£þþo¬þÿ­þõÿ*þ………þÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ½½½ÿÿììÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÏÿ ÿ¤ÿÿ¤ÿÿ¤ÿÿn¬ÿÿ­ÿÿÿÿÿÀÿ ÿíííÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþPPPþyyþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÏÏþ ÿ£þþ£þþn¬ÿþ­þÿÿþþÿÿþþ;þÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿôôôþþÕÕþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÏÏþ ÿ£þþo¬þÿ­þþþÿÿþþÿÿþþ—þ;;;ÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÂÂÂÿÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÏÏÿ ÿo¬ÿÿ­ÿÿÿÿÿÿÿÿÿÿÿÿÿØÿÿúúúÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿŸŸŸþ==þþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÏÏþÿþ­þÿÿþþþþÿÿþþÿÿþþûþÿÝÝÝþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿ’’’þLLþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþ  þÿþþþÿÿþþþþÿÿþþÿÿþþþþÿÐÐÐþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿ™™™ÿDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ®®ÿÿÿÿÖÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿØØØÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿ´´´þ&&þþþþÿÿÿþþþþþþÿÿÿþþþþþþ®®ÿ þ{{þÿuþþÖÿþþþþÿÿþþÿÿþþäþÿôôôþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿåååþþììþÿÿÿþþþþþþÿÿÿþþþ¯¯þ ÿªªþÏÏþÿþþuþÿÖþþþÿÿþþÿÿþþªþ+++ÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ111ÿ››ÿÿÿÿÿÿÿÿÿÿÿÿÿ®®ÿ ÿ««ÿÿÿÿÏÏÿÿÿÿþÿuÿÿÖÿÿÿÿÿÿÿÿÿUÿwwwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþ–––þ++þüüÿþþþþþþ®®ÿ þ««þÿÿÿþþþÏÏþÿþþþþþÿuþþÖÿþþÿÿÜþþÙÙÙÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþ÷÷÷þ###þÿþþþ®®þ ÿ««þþþþÿÿÿþþþÏÏþÿþþþþÿÿþþuþÿÖþþÿLþ```þÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¿¿¿ÿ ÿ€€ÿ ÿ««ÿÿÿÿÿÿÿÿÿÿÿÿÿÏÏÿÿÿÿÿÿÿÿÿÿþÿuÿÿqÿÿêêêÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþ”””ÿþ~~þÿÿÿþþþþþþÿÿÿþþþÏÏþÿþþþþÿÿþþþþþÿBþ ÿÇÇÇþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ”””ÿ ÿÿüüÿÿÿÿÿÿÿÿÿÿÏÏÿÿÿÿÿÿÿÿÿÿðÿdÿÿÂÂÂÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþ¿¿¿þ"""ÿ++þ››þììÿþþþÏÏþÿþþþþÝÿƒþþ@@@ÿÞÞÞþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþ÷÷÷ÿ–––þ000þÿ''þ77þÿ@þþÿFFFþ²²²þþþþÿþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþ¯¯¯þÏÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿäääÿ´´´ÿ˜˜˜ÿ‘‘‘ÿÿ½½½ÿðððÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¯¯¯ÿÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿòòòþïïïþïïïÿïïïþïïïþïïïÿïïïþ¤¤¤þÏþïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿ///þþÿþþÿþþÆÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ///ÿ¤¤¤ÿïïïÿïïïÿïïïÿîîîÿeeeÿä&þïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿ///þ¯¯¯þÿÿÿÿþþþþþþþþsssÿä&þïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿ///þ¯¯¯þÿÿÿÿþþþþsssþä&ÿïïïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ///ÿ¯¯¯ÿþþþÿsssÿä&þïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿ///þ®®®þsssÿä&þïïïþÿÿÿÿþþþþÿÿÿÿþþþþþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþþþþþÿÿÿÿþþþþÿÿÿÿ///þ444þä&ÿhhhÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿÿä&&ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?ü?üüÿüÿüÿüÿüÿüÿÿÿÿÿÿÿÿÿÿÿÿÿ( @ €SßßßßßßßßßßßßßßßßßßßßßßÃ_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿéééÿáááÿõõõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÑÑÑÿ```ÿ::ÿHHÿ!ÿ.Fÿ09>ÿŽŽŽÿöööÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýýÿ{{{ÿNNÿÏÏÿÿÿÿßßÿ=^ÿ¤ÿÿŸ÷ÿcšÿ%29ÿÑÑÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿþþþÿffeÿŠŠÿþþÿÿÿÿÿÿÿßßÿ=^ÿ¤ÿÿ¤ÿÿ¤ÿÿz¾ÿÿÒÒÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿ¡¡¡ÿssÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿ=^ÿ¤ÿÿ¤ÿÿŒÚÿ-ÿ½ÿ':'ÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿùùùÿ77ÿööÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿ=^ÿ¤ÿÿŒÚÿ-ÿÚÿÿÿ•ÿ”””ÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿµµµÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿ=^ÿŒÚÿ-ÿÚÿÿÿÿÿõÿ4@4ÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿ{{{ÿÀÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿ'=ÿ-ÿÚÿÿÿÿÿÿÿÿÿEÿöööÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿcccÿÛÛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÊÊÿ ÿÚÿÿÿÿÿÿÿÿÿÿÿ]ÿáááÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿjjjÿÓÓÿÿÿÿÿÿÿÿÿÿÿÿÿÛÛÿ*ÿÿuÿþÿÿÿÿÿÿÿÿÿTÿéééÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿ¨¨ÿÿÿÿÿÿÿÿÿÿÛÛÿ7ÿ¹¹ÿ^ÿxÿuÿþÿÿÿÿÿÿÿ:ÿþþþÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÙÙÙÿVVÿÿÿÿÿÿÿÛÛÿ7ÿÙÙÿßßÿ^ÿþÿxÿuÿþÿÿÿÐÿ___ÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿSSPÿÌÌÿÛÛÿ7ÿÙÙÿÿÿÿßßÿ^ÿÿÿþÿxÿuÿþÿOÿÐÐÐÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿâââÿ88ÿ7ÿÙÙÿÿÿÿÿÿÿßßÿ^ÿÿÿÿÿþÿxÿ=ÿyyyÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÌÌÿ88ÿÌÌÿÿÿÿÿÿÿßßÿ^ÿÿÿÿÿøÿwÿcccÿüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâââÿPSSÿVVÿ©©ÿ¸¸ÿPÿÃÿ…ÿ7ÿœœœÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÙÙÙÿÿiiiÿbbbÿxxxÿ¯¯¯ÿ÷÷÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ«««ÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿß_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ333ÿŸŸŸÿŸŸŸÿŸŸŸÿ,,,ÿ„_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ???ÿÿÿÿÿþþþÿwwwÿ‡_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ???ÿþþþÿwwwÿ‡_ŸŸŸÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ???ÿwwwÿ‡_cccÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿŸŸŸÿÿ‡#_________________Pÿÿÿÿÿÿÿÿðððððððððððððððððððððððððð?ððÿðÿÿÿÿÿ(  @,,,¤ïïïïïïïïïïPPPàOOO¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïOOO¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùùùÿõõõÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïOOO¯ÿÿÿÿÿÿÿÿÝÝÝÿ””Mÿ˜˜ÿIqÿ/r—ÿ»¾ÀÿÿÿÿÿÿÿÿÿïOOO¯ÿÿÿÿçççÿ˜˜ÿþþÿïïÿp®ÿžõÿ]:ÿ¼À¼ÿÿÿÿÿïOOO¯ÿÿÿÿ™™rÿüüÿÿÿÿïïÿj¥ÿpDÿõÿ2—2ÿÿÿÿÿïOOO¯ÿÿÿÿžž7ÿÿÿÿÿÿÿééÿNÿõÿÿÿ¨ÿõõõÿïOOO¯ÿÿÿÿ>ÿÿÿÿööÿE}8ÿ9ÿÜÿÿÿ£ÿùùùÿïOOO¯ÿÿÿÿ  ŠÿééÿEˆCÿååÿ®ÿ|ÿÜÿL”LÿÿÿÿÿïOOO¯ÿÿÿÿ÷÷÷ÿM]Mÿééÿïïÿ®ÿýÿ(TÿÝÝÝÿÿÿÿÿïOOO¯ÿÿÿÿÿÿÿÿ÷÷÷ÿŠ  ÿ>––ÿ66{ÿoo™ÿæææÿÿÿÿÿÿÿÿÿïOOO¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡‡‡ÿŸŸŸÿ888ØOOO¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸŸŸÿ{{{á!@@@¯ÏÏÏÿÏÏÏÿÏÏÏÿÏÏÏÿÏÏÏÿÏÏÏÿÏÏÏÿÏÏÏÿ333á! ////////ÿÿÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀOpenSTV-1.6.1/openstv/Icons/pie.icns0000777000175400010010000010311211400774167015621 0ustar jeffNoneicns†Jh8mk  8a“œœ“a8 vÃùþþÿþþÿþþÿþùÃv:²üþþÿþþÿþþÿþþÿþþÿþü²:"±þÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ±"kôþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿôk ©þÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþ© ÉÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉÑþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþÑ ÉþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÇ ©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¤kþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþa"ôþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþð±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡:þþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþý*²ÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿ üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷vþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþeÃÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ´ ùþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþó8þþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþþ/aÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZþþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþþ{“þþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþþœÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ›œþþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþþ›‘þþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþþ|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ{\þþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþþZ0þþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþþ/óÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóµþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþ´fþÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿþeöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷Ÿÿþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþÿ *üþþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþý+¡þþÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþþ¡ïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿð`þÿþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÿþa£ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¤ ÆþþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþþÇ ÐþÿþÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþÿþÑÆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉ £þÿþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿþþ© `ïþþþÿþþÿþþÿþþÿþþÿþþÿþÿþþÿôk¡üÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ±"*Ÿöþþÿþþÿþþÿþþÿþþÿþü²:fµóþþÿþþÿþþÿþùÃv0\|‘œœ“a8 ich#Hÿÿýÿÿÿÿÿÿÿÿÿÿÿÿ¿ÿÿÿÁ‡ïÿÿþ€ÿÿÿø€=ÿÿp€ÿÿÀ€ÿÿÀ€ÿÿà€ÿÿð€ÿÿø€ÿïü€?÷ÿþ€ÿÿÿ€ÿÿÿÿÿÿÿÿÁƒÿÿÿÿá‡ÿÿÿÿñÿÿÿÿùŸÿÿÿÿý¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿ¿ÿÿÿÿÿŸÿÿÿÿÿÿÿÿÿÿ‡ÿÿÿÿÿƒÿÿÿÿÿÿÿÿÿÿ€ÿûÿÿÿ€ÿÿÿÿ€?ÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿ€ßÿÿÿ€ÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿƒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ¿ÿÿÀþÿÿÀÿÿðÿÿøÿÿþÿÿÿÿÿÿÿÿ€ÿÿÿÿÀÿÿÿÿàÿÿÿÿàÿÿÿÿðÿÿÿÿøÿÿÿÿø?ÿÿÿÿü?ÿÿÿÿü?ÿÿÿÿüÿÿÿÿþÿÿÿÿþÿÿÿÿþÿÿÿÿþÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿþÿÿÿÿþÿÿÿÿþÿÿÿÿþ?ÿÿÿÿü?ÿÿÿÿü?ÿÿÿÿüÿÿÿÿøÿÿÿÿøÿÿÿÿðÿÿÿÿàÿÿÿÿàÿÿÿÿÀÿÿÿÿ€ÿÿÿÿÿÿþÿÿøÿÿðÿÿÀþÀich4ˆÿÿÿÿÿÿÿÿÿÿÿÝÝÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿù¯ÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿßÿû±ó3ª¯ÿýÿÿÿÿÿÿÿÿÿÿÿÿÿ»¿ó333¯ÿÿÿÿÿÿÿÿÿÿÿýÿûó3333¯ÿßÿÿÿÿÿÿÿÿÿûó33333ÿýÿÿÿÿÿÿÿýÿ±ó33333:ÿÿÿÿÿÿÿÿÿÿó333333ÿõÿÿÿÿÿýÿÿñ¿ó33333?ÿÿßÿÿÿÿÿþÿó33333ÿøŸÿÿÿÿÿÿçwÿñú3333?ÿˆÿÿÿÿýÿwwïÿó3333ÿùˆˆÿßÿÿÿþwwwÿñó333?ÿˆˆˆŸÿÿÿßçwwwïÿ¿ó33:ÿøˆˆˆýÿÿÿçwwwwÿñó33?ÿ˜ˆˆˆ‰ÿÿÿÿwwwwwïÿó33ÿøˆˆˆˆˆÿÿþþwwwwwwÿñó3?ÿˆˆˆˆˆˆŸßÿþwwwwwwïÿó3ÿùˆˆˆˆˆˆŸÿÿ÷wwwwwwwÿñó?ÿˆˆˆˆˆˆˆÿÿ÷wwwwwwwïÿóÿøˆˆˆˆˆˆˆÿÿ÷wwwwwwwwÿÿÿÿ˜ˆˆˆˆˆˆˆ‰ÿßçwwwwwwwwïÿÿøˆˆˆˆˆˆˆˆýßçwwwwwwwwwÿÿˆˆˆˆˆˆˆˆˆ‰ýßçwwwwwwwwwïÿˆˆˆˆˆˆˆˆˆ‰ýß÷wwwwwwwwwÿøˆˆˆˆˆˆˆˆ‰ýÿçwwwwwwwwwÿÿˆˆˆˆˆˆˆˆÿÿ÷wwwwwwwwwûÿøˆˆˆˆˆˆˆÿÿ÷wwwwwwwwwïñ?ÿˆˆˆˆˆˆˆÿÿþwwwwwwwwwû¯øˆˆˆˆˆˆŸÿýþwwwwwwwwwñ1ºÿˆˆˆˆˆˆŸßÿÿwwwwwwwwwûÿøˆˆˆˆˆÿÿÿÿçwwwwwwwwñ1ÿˆˆˆˆ‰ÿÿÿß÷wwwwwwwwïó11ÿøˆˆˆýÿÿÿþwwwwwwwwû¿ÿˆˆˆŸÿÿÿýÿwwwwwwwwñ111ÿøˆˆÿßÿÿÿÿçwwwwwwwû1ºÿˆ‰ÿÿÿÿÿÿþwwwwwwwñ11;¯øŸÿÿÿÿÿýÿçwwwwwwû1¿ÿÿßÿÿÿÿÿß÷wwwwwwïñ11ÿÿÿÿÿÿÿÿÿÿçwwwwwû1»ÿßÿÿÿÿÿÿÿÿþwwwwwó1¯ÿÿÿÿÿÿÿÿÿýÿþwwwwñ1¿ÿßÿÿÿÿÿÿÿÿÿÿÿþwwwû11ºÿÿÿÿÿÿÿÿÿÿÿÿÿßÿþçwñ;¿ÿýÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿþÿúÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÝÝÿÿÿÿÿÿÿÿÿÿÿich8 ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿêÿÿÿÿÿÿÿÿêüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿàêàÿþ­ƒÿÿÞÞßÿÿÿÿêûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿÿêþƒY/àÿØ##×Úßÿÿÿûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿ­Y)ÿÿØ######Ú³ÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿÿþY êÿØ########Úßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ÿÿƒ/àÿØ#########×Ýÿÿ¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ÿÿY)ÿÿØ###########kÿÿ¬ÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ÿÿþ/ êÿØ###########Gàÿÿüÿÿÿÿÿÿÿÿÿÿÿûÿÿþÿþ//àÿØ##########Gßÿêÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»þÿþ/)ÿÿØ#########Gßÿê¿åÿÿÿÿÿÿÿÿÿÿÿþÿÉ´´»þÿþ/ êÿØ########Gàÿ꿹¹çÿþÿÿÿÿÿÿÿüÿд´´´»þÿþ//àÿØ#######Gßÿêâ¹¹¹áêÿÿÿÿÿÿÿÿÿ´´´´´»þÿþ/)ÿÿØ######Gßÿêâá¹¹¹¹Åÿÿÿÿÿÿÿûÿд´´´´´´»þÿþ/ êÿØ#####Gàÿ꿹¹¹¹¹¹¹éÿÿÿÿÿÿÿ»´´´´´´´´»þÿþ//àÿØ####Gßÿ꿹¹¹¹¹¹¹¹Åÿþÿÿÿÿÿþ´´´´´´´´´´»þÿþ/)ÿÿØ###Gßÿêâ¹¹¹¹¹¹¹¹¹áêÿÿÿÿüÿÉ´´´´´´´´´´´»þÿþ/ êÿØ##Gàÿêâá¹¹¹¹¹¹¹¹¹¹çÿüÿÿàÿ´´´´´´´´´´´´»þÿþ//àÿØ#Gßÿ꿹¹¹¹¹¹¹¹¹¹¹¹äÿþÿÿÿÿ´´´´´´´´´´´´´´»þÿþ/)ÿÿØGàÿ꿹¹¹¹¹¹¹¹¹¹¹¹¹âÿÿÿÿÿд´´´´´´´´´´´´´´»þÿþ5êÿÚßÿêâ¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹êÿÿÿÿд´´´´´´´´´´´´´´´»þÿêÿÿàÿêâá¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹Ñÿÿÿд´´´´´´´´´´´´´´´´»þÿÿÿÿ꿹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹çÿúÿÉ´´´´´´´´´´´´´´´´´´»ÿÿÿ꿹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹çÿÿÉ´´´´´´´´´´´´´´´´´´´»ÿÿêâ¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹çÿÿÉ´´´´´´´´´´´´´´´´´´´´ÿÿÿêâ¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹çÿúÿÿд´´´´´´´´´´´´´´´´´´»þÿêÿêâ¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹Ñÿÿÿÿþ´´´´´´´´´´´´´´´´´´´»ÿÿ;êàêâ¹¹¹¹¹¹¹¹¹¹¹¹¹¹¹êÿÿÿÿÿ´´´´´´´´´´´´´´´´´´´»ÿÿ5;þÿêâ¹¹¹¹¹¹¹¹¹¹¹¹¹âÿÿÿÿþÿ´´´´´´´´´´´´´´´´´´´ÿÿ5àÿêâ¹¹¹¹¹¹¹¹¹¹¹¹äÿþÿÿüÿÉ´´´´´´´´´´´´´´´´´´»þÿ5 ;þÿêâ¹¹¹¹¹¹¹¹¹¹¹çÿüÿÿÿÿþ´´´´´´´´´´´´´´´´´´»ÿÿ55àÿêâ¹¹¹¹¹¹¹¹¹áêÿÿÿÿÿÿÿ´´´´´´´´´´´´´´´´´»ÿÿ5 5àÿêâ¹¹¹¹¹¹¹¹Åÿÿÿÿÿÿÿд´´´´´´´´´´´´´´´´´ÿÿ5àÿêâ¹¹¹¹¹¹¹éÿÿÿÿÿÿÿÿ´´´´´´´´´´´´´´´´»þÿ5  5àÿêâ¹¹¹¹¹Åÿÿÿÿÿÿÿÿÿþ´´´´´´´´´´´´´´´´»ÿÿ55àÿêâ¹¹¹áêÿÿÿÿÿÿÿÿþÿÉ´´´´´´´´´´´´´´´»ÿÿ5  5àÿêâ¹¹çÿþÿÿÿÿÿÿÿÿÿÿÿ´´´´´´´´´´´´´´´ÿÿ  ;þÿêâåÿÿÿÿÿÿÿÿÿÿÿÿÿÿ»´´´´´´´´´´´´´»ÿÿ5  5àÿêÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿ´´´´´´´´´´´´»ÿÿ5  5àÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ÿÿ´´´´´´´´´´´´ÿÿ _ÿÿ¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ÿÿÉ´´´´´´´´´´»ÿÿ5   ‰ÿÿ¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ´´´´´´´´»þÿ5 _ßêÿûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿд´´´´´»ÿÿ5 _­àÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÉ´´´´ÿÿ_ƒßÿÿÿûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÐÐþÿ­­ßÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüþÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿicl4ÿÿÿÿÿÿýÙÝßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿÿÿÿÿÿÿÿÿÿ»ó3¯úÿÿÿÿÿÿÿßû£33¯ýÿÿÿÿÿõû£333¯ßÿÿÿÿß±ó333?ýÿÿÿýÿû£333¯ÿßÿÿÿçï±ó33:ù‰ÿÿÿþw~û£33¯˜ˆùÿÿþwwï±ó3:ùˆˆŸÿÿçww~û£3¯˜ˆˆßÿçwwwï±ó:ùˆˆˆ‰ÿÿwwww~û£¯˜ˆˆˆˆÿßwwwwwïºúùˆˆˆˆˆý®wwwww~ÿÿ˜ˆˆˆˆˆýßwwwwwwïùˆˆˆˆˆˆ•þwwwwwwyùˆˆˆˆˆˆùßwwwwwwuÿ˜ˆˆˆˆˆýßwwwwwwyªùˆˆˆˆˆýÿwwwwwwuñ¯˜ˆˆˆˆÿÿçwwwwwy¡ºùˆˆˆ‰ÿý÷wwwwwuñ1¯˜ˆˆßÿ÷wwwwwy¡±;ùˆˆŸÿÿÿwwwwwuñ1¿˜ˆõÿÿÿçwwwwy¡;ù‰ÿÿÿýþwwwwu¡1¯Ÿßÿÿÿÿçwwwyûúÿÿÿÿýþwwwu±±11¿ßÿÿÿÿÿß÷ww“ºýÿÿÿÿÿÿÿÿîw~¡ºÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýßÚßÿÿÿÿÿÿicl8ÿÿÿÿÿÿÿÿÿÿÿÿÿúüüüûÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ÿÿþ­þà³þêÿ¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýô­Y/­Þ##GÛ³êýÿÿÿÿÿÿÿÿÿÿÿÿÿÿúÿ­/­Þ#####Ùßÿúÿÿÿÿÿÿÿÿÿÿÿûÿƒ­Þ######×Üÿüÿÿÿÿÿÿÿÿÿüÿ§­Þ########ÞÿüÿÿÿÿÿÿÿúÿÐÿƒ­Þ#######ÜÿèÿúÿÿÿÿÿÿÿÉ´Éÿƒ­Þ######Üÿæ¹Ëÿÿÿÿÿÿýд´´Éÿƒ­Þ#####Üÿæ¹¹¹é¬ÿÿÿÿÿ»´´´´Éÿƒ­Þ####Üÿæ¹¹¹¹äÿÿÿÿ¬Ð´´´´´´Éÿƒ­Þ###Üÿæ¹¹¹¹¹¹éüÿÿÿ´´´´´´´Éÿƒ­Þ##Üÿæ¹¹¹¹¹¹¹åÿÿÿÿ»´´´´´´´´Éÿƒ­Þ#Üÿæ¹¹¹¹¹¹¹¹¿ÿÿúÿ´´´´´´´´´´Éôƒ­ÞÜÿæ¹¹¹¹¹¹¹¹¹¹ÿúûд´´´´´´´´´´Éêÿÿÿæ¹¹¹¹¹¹¹¹¹¹¹éûüд´´´´´´´´´´´Éôÿæ¹¹¹¹¹¹¹¹¹¹¹¹èüüд´´´´´´´´´´´´Ðÿæ¹¹¹¹¹¹¹¹¹¹¹¹Ñüûд´´´´´´´´´´´´Ðþô˹¹¹¹¹¹¹¹¹¹¹Ñûúÿ´´´´´´´´´´´´´Ð­ƒôæá¹¹¹¹¹¹¹¹¹ÿúÿÿ»´´´´´´´´´´´´Ð­‰þæá¹¹¹¹¹¹¹¿ÿÿÿÿÉ´´´´´´´´´´´´Ð­ ‰ôæ¹¹¹¹¹¹¹åÿÿÿüд´´´´´´´´´´´Ð­ƒôæ¹¹¹¹¹¹éüÿÿÿÿ´´´´´´´´´´´Ð­ ‰ÿæ¹¹¹¹äÿÿÿÿÿ¬Ð´´´´´´´´´´´Ð­ ‰ÿæá¹áé¬ÿÿÿÿÿÿÉ´´´´´´´´´´Ð­  ƒÿæ¹æÿÿÿÿÿÿÿúÿ´´´´´´´´´Ð­ƒÿèÿúÿÿÿÿÿÿÿüÿ´´´´´´´´Ð­  ­ÿüÿÿÿÿÿÿÿÿÿüÿÉ´´´´´´´Ð­  ‰ÿüÿÿÿÿÿÿÿÿÿÿÿúÿд´´´´Ð­;­ÿúÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¬ÿÐÉ»´´Ð­5_³êýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÐÿà­ÿÿÿ¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúûüüûúÿÿÿÿÿÿÿÿÿÿÿÿÿICN#ÿý_ÿÿÿÿÿÿáÿý¿þßþ€î÷ÿ€ÿÿƒûÿáÿÿáÿÿùÿÿù¿ÿý¿þÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿ¿þÿÿ¿ÿÿÿŸÿÿÿÿÿÿ‡ÿÿÿƒÿÿÿÿïÿ€ÿÿÿoÿÿ€ÿÿÿÿÿ‡ÿÿÿÿÿÿýßÿà?üÿÿÿÿÀÿÿàÿÿðÿÿøÿÿø?ÿÿü?ÿÿüÿÿþÿÿþÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿþÿÿþ?ÿÿü?ÿÿüÿÿøÿÿøÿÿðÿÿàÿÿÀÿÿ?üàics#Hÿÿù/Ð ðùü?þÿÿÿÿÿÿÿÿ?ÿŸÿÿ ÿÿÿÀð?ü?üþþÿÿÿÿÿÿÿÿþþ?ü?üðÀics4ˆÿÿÿß¾ÿÿÿÿÿû£ªÿÿÿÛ33­ÿÿï£3ùÿþwñ3?ˆÿþw£øˆŸ×wwû¯ˆˆ…÷wwøˆˆ‰çww~øˆˆ§ww~¿ˆˆ÷ww~³˜ˆŸÿww~±ºˆÿÿçw~±1ùÿÿÞw~³½ÿÿÿþ~±ºÿÿÿÿÿßýÿÿÿics8ÿÿÿÿÿÿü¬ýüÿÿÿÿÿÿÿÿÿÿ­5Yk#ÚÞÿÿÿÿÿÿƒYÚ###Üÿÿÿÿɬ)Yk##Øþæÿÿÿ们)YÚ#Gßâ¹Ñÿÿ´´»Ð)YÚØ­â¹¹äÿü´´´´»¬ƒÛþâ¹¹¹áü¬´´´´´»Ðêâ¹¹¹¹¹¬¬´´´´´´Âéâ¹¹¹¹¹¬ü´´´´´´Â_þâ¹¹¹¹üÿ´´´´´Â_5þâ¹¹äÿÿд´´´´Â_ 5­â¹ÑÿÿÿÉ´´´´Â_5­Ëÿÿÿÿ´´´Â_ ‰ÿÿÿÿÿÿдÂ__­ÿÿÿÿÿÿÿÿÿÿü¬ýüÿÿÿÿÿÿih32 cñ@ T_aaaaaZrwwwwwwf' D TaaaaaaaaZrwwwwwwwwfB-3\aaaaaaaaaZrwwwwwwwwwt:+*QaaaaaaaaaaaZrwwwwwwwwwwwe*-WaaaaaaaaaaaaZrwwwwwwwwwwwq-DVaaaaaaaaaaaaaZrwwwwwwwwwwq DQaaaaaaaaaaaaaaZrwwwwwwwwwq LH3aaaaaaaaaaaaaaaZrwwwwwwwwq LPP/ D \aaaaaaaaaaaaaaaZrwwwwwwwq LPPPN DTaaaaaaaaaaaaaaaaZrwwwwwwq LPPPPPJF aaaaaaaaaaaaaaaaaZrwwwwwq LPPPPPPPF TaaaaaaaaaaaaaaaaaZrwwwwq LPPPPPPPPJ_aaaaaaaaaaaaaaaaaZrwwwq LPPPPPPPPPO@4aaaaaaaaaaaaaaaaaaZrwwq LPPPPPPPPPPP0@ UaaaaaaaaaaaaaaaaaaZrwq LPPPPPPPPPPPPK[aaaaaaaaaaaaaaaaaaZrq LPPPPPPPPPPPPPM aaaaaaaaaaaaaaaaaaaZg LPPPPPPPPPPPPPPP "aaaaaaaaaaaaaaaaaaaZ LPPPPPPPPPPPPPPPPG3aaaaaaaaaaaaaaaaaaaZ LPPPPPPPPPPPPPPPPP/GF4aaaaaaaaaaaaaaaaaaaX LPPPPPPPPPPPPPPPPPP0FF4aaaaaaaaaaaaaaaaaaY LPPPPPPPPPPPPPPPPPP0FG3aaaaaaaaaaaaaaaaaY LPPPPPPPPPPPPPPPPP.G"aaaaaaaaaaaaaaaaY  LPPPPPPPPPPPPPPPPaaaaaaaaaaaaaaaY pd LPPPPPPPPPPPPPPP \aaaaaaaaaaaaaY xyih LPPPPPPPPPPPPPMWaaaaaaaaaaaaY x}yimh LPPPPPPPPPPPPK-6aaaaaaaaaaaY x}}yimmh LPPPPPPPPPPP0@aaaaaaaaaaY x}}}yimmmh LPPPPPPPPPOVaaaaaaaaY x}}}}yimmmmh LPPPPPPPPJ @!aaaaaaaY x}}}}}yimmmmmh LPPPPPPPEVaaaaaY x}}}}}}yimmmmmmh LPPPPPJB_aaaY x}}}}}}}yimmmmmmmh LPPPN E 4aaY x}}}}}}}}yimmmmmmmmh LPP/QY x}}}}}}}}}yimmmmmmmmmh LHB x}}}}}}}}}}yimmmmmmmmmmh E,x}}}}}}}}}}}yimmmmmmmmmmmh-*n}}}}}}}}}}}yimmmmmmmmmmmb+,={}}}}}}}}}yimmmmmmmmmk8-Bo}}}}}}}}yimmmmmmmmcC (o}}}}}}yimmmmmmc$@?pz}}yimmjd;B@)<7%A- -GFFGÿÿü?ÿÿÿÿ€ÿÿÿü?ÿÿðÿÿàÿÿ€ÿÿÿþü?øøðààÀÀÀ€€€€€€€€€€ÀÀÀààðøøü?þÿÿÿ€ÿÿàÿÿðÿÿü?ÿÿÿ€ÿÿÿÿü?ÿÿ( @  $);$$**,,11;;>>+,+,0?0++,,(?00??###+++333???AmpCCllppC*BpBBGnIrnnrrCCCDDDIIIRRRTTTnnnqqqtttƒ¬Íöûþ„„ŽŽšš¬¬··ÎÎÖÖööøøþþŽ·[Žv·ÖøþŽŽ··ŠÖ ø£ý¤ÿÖÖøøþþÿÿÿ741145!   >CGG%TTQJ7 AGGGGGTTTTTK52&EGGGGGGTTTTTTT,22@GGGGGGGGTTTTTTTT)27@GGGGGGGGGTTTTTTT,"7&GGGGGGGGGGTTTTTT,$=# EGGGGGGGGGGTTTTT,$==; AGGGGGGGGGGGTTTT,$====9! GGGGGGGGGGGGTTT,$====== !>GGGGGGGGGGGGTT,$=======8CGGGGGGGGGGGGT,$========:7GGGGGGGGGGGGG,$==========74 GGGGGGGGGGGGG$=========== 41GGGGGGGGGGGGG$============ 11GGGGGGGGGGGG'#============ 14 GGGGGGGGGGG'#=========== 45GGGGGGGGGG'.+(*#==========7DGGGGGGGG'.WN*#========:?GGGGGGG'.WWNN*#=======8 GGGGGG'.WWWNNN*#====== 1BGGGG'.WWWWNNNN*#====9 GGG'.WWWWWNNNNN*#==; 'G'.WWWWWWNNNNNN*#=#5%.WWWWWWWNNNNNNN*"72+WWWWWWWWNNNNNNNN(22/WWWWWWWNNNNNNN*25PWWWWWNNNNNI5OUWWNNLH  741145ÿøÿÿÀÿÿÿü?øðààÀÀ€€€€€€ÀÀààðøü?ÿÿÿÀÿÿøÿ(  < -7== ? +? ??&;&11&;;&<<&&&1&-1&11kmmm{FlOzllzzDEEGHGHHGbbb©Úñþžž©©¯¯ÛÛòòôôþþŸ¯fŸp¯ÛôþŸŸ¯¯ŒÚô£ý¤ÿÚÚôôþþÿÿÿ %($-5. #***$-777#****$-774*****$-74 "%*****$-4 ""(*****$ """!******$ """"" *****' """"" (***' """!&**'82+/ ""*'8:2+1/ "8::2+11/:::2+111 392+0, ü?ðÀÀ€€€€ÀÀðü?(0` €%0\|‘œœ“a8 fµóþþÿþþÿþþÿþùÃv*Ÿöþþÿþþ88ÿEEþþÿ-Fþ'<þÿþþÿþü²:¡üÿÿÿ[[ÿ¦¦ÿããÿþþÿÿÿÿßßÿÿÿßÿ¤ÿÿ¤ÿÿ˜íÿs³ÿEkÿÿÿÿþ±"`ïþþ''þ››ÿôôþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ¡úþo­ÿ#6þþÿôk £þÿþœœþûûþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ¤ÿÿ£þþn¬þÿþþ© ÆÿÿTTÿëëÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ›ñÿ;]ÿÿÿÉÐþÿŽŽþþþÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ^“þÿþÑ Æþþ¬¬ÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ†Ðþ ÿþþÇ £ÿÿ¬¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ†Ðÿ ÿÿÿÿÿ¤`þÿŽŽþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ¤ÿÿ£þþ£þþ†Ðÿ þþÿÒþþÿþaïÿTTÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ†Ðÿ ÿÿÿÒÿÿÿþÿUÿÿð¡þþëëÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ¤ÿÿ†Ðþ þÿþÒþÿÿþþþþìÿþþ¡*üþœœþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ£þþ†Ðÿ þþÿÒþþþÿÿþþþþÿÿþþý+Ÿÿ''þûûþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ£þþ¤ÿÿ†Ðþ ÿþþÒÿþþþþÿÿþþþþÿÿüþ'þÿ öÿ››ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ†Ðÿ ÿÿÿÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ›ÿÿ÷fþÿôôþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ£þþ†Ðþ ÿþÿÒþþþÿÿþþþþÿÿþþþþÿÿþþôþ ÿþeµþ[[ÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ£þþ¤ÿÿ†Ðþ þÿþÒÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþ[ÿþ´óÿ¦¦ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿ¤ÿÿ†Ðÿ ÿÿÿÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¥ÿÿó0þþããÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþ†Ðþ ÿþþÒÿþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþâÿþþ/\þþþþÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿq°þ þÿþÒþÿÿþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþþÿþþZ|ÿ88ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿ ÿÿÿÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ8ÿÿ{‘þPPþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿþþÒÿþþþþÿÿþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿOþþœþ\\þÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿÃÃþþÿþÒþÿÿþþþþÿÿþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿ[þþ›œÿ\\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒÒÿÿÿÿÿÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[ÿÿ›“þRRþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþÒÒþÿþþÿþþÐÿþþþþÿÿþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿOþþþ<<þÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿÒÒþþÿ þþÿ þþÿÐþþþÿÿþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿ8þþ{aÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒÒÿÿÿÿ°°ÿÿÿ²ÿÿÿÿÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿZ8þþííÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿþþþÒÒþÿþþÐÐÿßßþþÿßþÒþÿþþÐÿþþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþâÿþþ/ ùþ³³ÿþþþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþþþþÿÿÿÒÒþþÿþÐÐþÿÿÿßßþþÿßþþþÒÿþþÿÐþÿÿþþþþÿÿþþþþÿÿþþþþÿÿþþþþ¥ÿþóÃÿkkÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒÒÿÿÿÿÐÐÿÿÿÿÿÿÿßßÿÿÿßÿÿÿÿÿÒÿÿÿÿÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[ÿÿ´vþÿúúþþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿþþþþþþÒÒþÿþþÐÐÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþÒþÿþÿÐþþþÿÿþþþþÿÿþþþþÿÿþþôþ ÿþeüÿ­­ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒÒÿÿÿÿÐÐÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿÿÿÿÿÿÿÿÿÒÿÿÿÿÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ›ÿÿ÷²ÿ66þþþþÿÿÿþþþþþþÿÿÿþþþÿÿÿÒÒþþþÿÐÐþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿÒþÿþþÐÿþþþþÿÿþþþþÿÿüþ'þÿ :þþ¬¬þÿÿÿþþþþþþÿÿÿþþþÒÒÿþþþÐÐÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿþþÒÿþþÿÐþþþÿÿþþþþÿÿþþý*±ÿÿññÿÿÿÿÿÿÿÿÿÿÒÒÿÿÿÿÐÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒÿÿÿÿÐÿÿÿÿÿÿÿìÿÿÿ¡"ôþ]]ÿþþþþþþÒÒÿþÿþÐÐþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿþþÿÿþþÒþÿþþÐÿþþþþUÿþðkþÿ““þÒÒþÿþÿÐÐþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿþþÿÿþþþþÒÿþþÿÐþþÿþa©ÿÿÿÿÿÐÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒÿÿÿÿÿÿ¤ ÉþþÿÐÐþÿÿÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿþþÿÿþþþþÿÿþþÒþÿþþÇ Ñþÿ““þþþÿþþþþþþþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿþþÿÿþþþþÿÿþþ“þÿþÑÉÿÿ\\ÿññÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿßßÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñÿ]ÿÿÿÉ ©þÿþ¬¬þþþþÿÿÿþþþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿþþÿÿþþ¬þÿþþ© kôþþ66þ­­ÿúúþþþþÿÿÿþþþþþþÿÿÿßßþþÿßþþþÿÿþþþþÿÿúþ­ÿ6þþÿôk"±þÿÿÿkkÿ³³ÿííÿÿÿÿÿÿÿßßÿÿÿßÿÿÿÿÿíÿ³ÿkÿÿÿÿþ±":²üþþÿþþ<<ÿFFþþÿFþ<þÿþþÿþü²:vÃùþþÿþþÿþþÿþùÃv 8a“œœ“a8 ÿÿÀÿÿÿþÿÿøÿÿàÿÿÀÿÿÿþü?øøðààÀÀ€€€€€€ÀÀààðøøü?þÿÿÿÀÿÿàÿÿøÿÿþÿÿÿÀÿÿ( @ €Z‹«»¼­a WÀýÿ ÿ**ÿÿ ÿ,ÿ ÿÿþÌcAÔÿ%%ÿ„„ÿÎÎÿüüÿÿÿÿ??ÿ)?ÿ¤ÿÿ£ýÿŠÖÿ[Žÿ0ÿÿÜIŒý$$ÿ¬¬ÿýýÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ£þÿv·ÿ+ÿþ‘ µÿllÿööÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ øÿIrÿÿ¶ µÿššÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ£þÿ*BÿÿµŒÿššÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ£þÿGnÿÿBÿÿAýllÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ£þÿGnÿÿpÿþÿmÿýBÔ$$ÿööÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ¤ÿÿ¤ÿÿ£þÿGnÿÿpÿþÿÿÿöÿ$ÿÔWÿ¬¬ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ¤ÿÿ£þÿGnÿÿpÿþÿÿÿÿÿÿÿ¬ÿÿWÀ%%ÿýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ¤ÿÿ£þÿGnÿÿpÿþÿÿÿÿÿÿÿÿÿýÿ%ÿÀý„„ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ¤ÿÿ£þÿGnÿÿpÿþÿÿÿÿÿÿÿÿÿÿÿÿÿƒÿýZÿÎÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ)?ÿ£þÿGnÿÿpÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÍÿÿY‹ ÿüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ(?ÿGnÿÿpÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿÿŠ«**ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿÿÿpÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)ÿ«»;;ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿ33ÿÿpÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ;ÿ»¼<<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿppÿÿÿnÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ;ÿ»­,,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿppÿÿÿÿÿnÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)ÿ« ÿýýÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿppÿÿnnÿ??ÿ?ÿpÿÿnÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿûÿÿŠaÿÖÖÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿppÿÿnnÿþþÿ??ÿ?ÿþÿpÿÿnÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÍÿÿY þŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿppÿÿnnÿþþÿÿÿÿ??ÿ?ÿÿÿþÿpÿÿnÿþÿÿÿÿÿÿÿÿÿÿÿÿÿƒÿýÌ00ÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿþþÿppÿÿnnÿþþÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿþÿpÿÿnÿþÿÿÿÿÿÿÿÿÿýÿ%ÿÀcÿ··ÿÿÿÿÿÿÿÿÿÿþþÿppÿÿnnÿþþÿÿÿÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿÿÿþÿpÿÿnÿþÿÿÿÿÿÿÿ¬ÿÿWÜ++ÿøøÿÿÿÿþþÿppÿÿnnÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿÿÿÿÿþÿpÿÿnÿþÿÿÿöÿ$ÿÔIþrrÿþþÿppÿÿnnÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿÿÿÿÿÿÿþÿpÿÿnÿþÿmÿýB‘ÿCCÿÿnnÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿþÿpÿÿAÿÿ¶ÿBBÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿCÿÿµ ¶ÿrrÿøøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿøÿrÿÿ¶ ‘þ++ÿ··ÿþþÿÿÿÿÿÿÿÿÿÿÿÿÿ??ÿ?ÿÿÿÿÿÿÿÿÿþÿ·ÿ+ÿþ‘IÜÿ00ÿŽŽÿÖÖÿýýÿÿÿÿ??ÿ?ÿÿÿýÿÖÿŽÿ0ÿÿÜIcÌþÿ ÿ,,ÿÿÿ,ÿ ÿÿþÌc a­¼¼­a ÿàÿÿÿþøðààÀ€€€€€€Àààðøþÿÿÿàÿ(  @u¸Ù Ù»zr44ô©©ÿòòÿŸŸÿfŸÿôÿp¯ÿ$8övœÿüüÿÿÿÿÿÿÿŸŸÿfŸÿ¤ÿÿ¤ÿÿ£ýÿFlÿrÿÿÿÿÿÿÿÿÿÿÿÿÿŸŸÿfŸÿ¤ÿÿ¤ÿÿŒÚÿ-ÿkÿs44ôüüÿÿÿÿÿÿÿÿÿÿÿÿÿŸŸÿfŸÿ¤ÿÿŒÚÿ-ÿÛÿüÿ4ôu©©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸŸÿfŸÿŒÚÿ-ÿÛÿÿÿÿÿ©ÿu¸òòÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸŸÿOzÿ-ÿÛÿÿÿÿÿÿÿñÿ¸ÙÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿœœÿÿÛÿÿÿÿÿÿÿÿÿÿÿÙÙÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÛÛÿÿÿÚÿÿÿÿÿÿÿÿÿÿÿÙ»ôôÿÿÿÿÿÿÿÿÿÿÛÛÿ7ÿzzÿ{ÿÿÚÿÿÿÿÿÿÿñÿ¸z¯¯ÿÿÿÿÿÿÿÛÛÿ7ÿÚÚÿŸŸÿŸÿÛÿÿÚÿÿÿÿÿ©ÿu88öýýÿÛÛÿ7ÿÚÚÿÿÿÿŸŸÿŸÿÿÿÛÿÿÚÿüÿ4ôvmmÿ7ÿÚÚÿÿÿÿÿÿÿŸŸÿŸÿÿÿÿÿÛÿÿkÿsllÿýýÿÿÿÿÿÿÿŸŸÿŸÿÿÿÿÿýÿmÿv88ö¯¯ÿôôÿŸŸÿŸÿôÿ¯ÿ8övz»ÙÙ»zðÀ€€€€ÀðOpenSTV-1.6.1/openstv/Icons/splash.png0000777000175400010010000007516611402267736016210 0ustar jeffNone‰PNG  IHDRôÑ:À@»sBIT|dˆ pHYs__¢ ^ltEXtSoftwarewww.inkscape.org›î< IDATxœìwxTÅހ߳%Ù”MI ) ½(EºJGŠ ˆØP¯õÚ¯¢ˆ^QôÓ«RETšô"R•*¡÷@ „žl’-ßk›-É&»›Í2ïóìsØ™9s~{rØwgΜé£>2¼õÖ[  aáããÔ)S||| %%%õ@ ‚Zâïï®&Öª"IrL@uAÄpwˆÃb÷ˆCÄ b¨Š;Äá1€{ÄÑcÈËË«ü·âjr 2Y®IÅ®ÜÖÇ1E¬îµ­š&ÎAÃØºC ®ŽUœ«»ÄT)u@ 1ÊÑé2ÐéÒM^¥¥ûÐj“‘Ë#Q©nF¡h‚R…\…Ri|I’ª¾Ã åå%dT¾òó¯n‹Š²P*} Œ$ Àø Œ¬|ïïZßểÁ@AA699_¥¥%…IHH$ÁÁ‘„†FEHH^^â;ézç:º²²}””¬B£YV›ŒN— lìsŒ’’-säò ŠXüüú¢VÀ×·7’tNÀ/îçèÑÕ;¶–ŒŒ£h4yµ®K¡ð"((š-n¥}ûá´n=oo?F[?”””ô+»w¯âðáßÉξˆV[^ëúüýƒˆoG·nCéÑcqqm­ ! Qi´@d²\c¢„˷ΨÓ`ÐPV¶™’’•””¬F§»hýLÔ¹<ÿ!¨ÕÃQ«‡ —ÕËy¬ºu‡j«8 c+I Õj8}z3G®âèÑ5俦â, oZ´¸Ž‡Ó¾ý0BCc튵¦emmk[ï¥Kgسg5{ö¬âðámhµev~úšO÷îÃèÞ}7Þx+J¥Wƒ»ÆÜ!w5$$ˆ¼<ãf ºÁ‹Fó ÍJ4š E¸IRàëÛ›€€Þ—W›17„óZ±ŠsàÞ[­VÃþý?røð/œ:µ‰òòbêƒèèötè0œ^½¦Q£f.9¿öÔ›šz”-[æ³gÏjRSÖíÃÖºtÈ-·Œ¤oß1(J·¸†ªÛºC î«G Š)*ú”ÂÂéèõµïÞ“$0\Ó _1jP_ëú¼ O£F¯£T†UÃU[w¸Ðk«8î¹ÕëµìÝû ¿ý6•¼<çõzÙ‹L&§gχ¹ãŽ)„„Ä8õüÖ¤ÞŒŒd~úi Û¶ýˆÁPÛoÇÏ£NeàÀû‘ÉdnqMYÛºC  ]Kqñ< §¢Ó¥S’7ÝBÓ¦‘‘ÆmD´n ÉÉWËß ü dé@Æ?Û4` °ÐV{TË‘ððç‘ËýjùYíߺÅ^ÛXÅ9p¯­Á`àСEüúëdeÂ]Q(¼éÛ÷i† ÿ0§œßªi×þ;''E‹Þæ·ßæ¡ÓÕþž¸³IHhËãÿ—>}îÜ㫺u‡Ü=Vºf ……¯£ÕÚþrññ~ý`øp6 7¶^¶Y3s¡ï´Qw.°X¬rlF E‘‘oú2™²ÊgrüÖ.ôÚÆ*ÎûlOž\ˆ ¯“–¶{‘$!!q•#Ø"Q«¯ŽbW«QVVD~~ùùéÿl¯¾®\9‡FS`÷qU*5ƒ=Ï!/ R©z~«¦I^aùòé¬Y3ƒ²²»ãõó $<<–àà«£Øwî\É… 'ÌÊ>€œœ ®\ÉàÊ•t.]:‡Á`k¯eÚ´éÆSOM£S§ÛÌ>W}oÝ!wµÁ ½¬l……¯R^¾kDDÀˆF‰÷ïo”zM°Wè×¢ÃØš_,NÛ(ëíHTÔ;EúçÃÕ÷y­ïmu_š×ã9¨ïíùó±~ý«¤¤lÇ||‚hÙr­[¥uë!øù…Õ:ƒAKrò_>¼†ƒW“žnß}hµ:Œ¡CÿÀQ*½jƒ­k³¼¼„Õ«?fùò).¶ïv_||ºtJ×®CiÙ²2™Ì¤îÉ“ó÷ßÌöûâ‹$;šÄuåJ»w¯eçÎ5üý÷FŠ‹íûÔµëÆŸ–-o2‰AüpïX°ÐË)(˜HIÉ—X#$^}&MU-ˬ‹Ð¯Å,ÞÎØ(§V÷'>þgŠºÈ«8õ·=7¾Áï¿¿WãV_ddZµJëÖC‰ï\®pJlW®¤pðàZÉ[(/×Ô(¾ØØŽ<÷Ürš:àüÿž~’÷ßAZšy+Ú*•íÛ÷£K—¡tî|;ááÑ6cЯÝêtå<¸;×°sçRSOÖ(>™LÆ“O¾Ë¸q¯Öû5èNÿÜ9Ö)tƒá2yy£(/·ÜRðõ…瞃—_†À@‹Ej„£„^A9ð%ð_Œ÷ß-áí@bâJ||ŒÏzÂEæˆXÅ9¨Ÿmii‹ÝÏñ㫨I’èÐa ƒM¥Q£.µ¬¬˜mÛ¾díÚi\®6^µ:œgžYBË–}êtl€Öó¿ÿ­Q«<88бcߤÿGP*½k|œÚ ½êöÌ™ƒ|óͶo_^m¬Œeòä¯ñöö¹îÿ?¸{¬× ½ALýªÕî''§³E™+•0~¼QÂï¾[7™;%0c÷û»€¥ðJKÏpâDwrsWº46 *ÙÙ§ùâ‹n5’yóæxæ™=<ðÀO„‡·pAtæxyù2`Às¼÷^2wÜñ*•Úfù‚‚˼ÿ~6ožS§ã®ZõL›6´Z™ûùñÀÓ˜3ç4ƒ?…Ré]§ãÖ–ÄÄöü÷¿¿ðÅ;¹é¦Ûª-¿qãO<ñD/.]rÞ|ÇãöB/-]BnnOtºófy£FÁ±c0{¶q¤º;㼆±ûýY@ª’¯Óœ|'ééïº<6@¯×râÄZ>ÿ¼+™™¶ïQGGwæ‰'~ãñÇ%:º“‹"´J¥fĈ)LŸ~†AƒžG©´~¿M§+ç›ožâÛoŸ¶{zy¹†3äûï_²ù(š——wÝõ2sæœaôèÿàíík×qœEëÖ7óé§›ùøã_iÑÂößîĉ}<òHø³ÖÇÓé´”••Özÿ†JQQa½×…n ¸øMòóÇ`0˜NX!—Ã'ŸÀ’%˜XOáÕ’à`FÉ›b -m2gÎŒE¯¯ŸI:×z½–Ù³;³`Á0JJ¬?§Öœûï_Äĉ»iÖ¬Ÿ #¬9þþaÜsÏG¼÷ÞIúôyI²þ·iÓç¼ÿþ ²jT÷•+yóÍ>lß¾Àj™L΀3{ö)Æ›Ž¿°ÝŸÁté2€¹s÷ðÎ;‹‰‰¹Áj¹+W.1aB_V®œg÷1t:-¯¾z/Ï=wçu%õÅ‹rÓMÍ9räËí¦B/!7·ÅÅïPu®õà`X·ž}¶~"s£1Žˆoj!ïÊ•Ÿ9t¨9eeg]•àzC¯×2cF[22Øü֡ý<ûìÚ·¿›Š§2Ü™yd.Ï>»Ÿ«åŽû7ßìBqq®ÍúrrÒyíµ.$'ï±ZÆß?„)S~eüø/ mRëØ]…$IÜvÛh¾ý6‰¾}ÇX-W^^Æ»ï>Æüùï×¸î ™oܸ„?ÿ\ÝH}É’…<õÔ8.]Ê`Ĉ~.—º Ý@nn´Ú$³œ–-a×.0 Âr€=@ yååi9ÒÎþço‚š ×kY´è^²²¬Ð–$ƒ¿Ï½÷.D©¬á³ŸnD‡CyóÍ]DD4·Z¦S§;ñõ ²YOpp: ²šÛ–>ØC»v}kk}¡RùòÎ;?óÄïXý±æïÈ­·ÞU£ú®•yþ¹žgŸõl©WÈ\§Ó•uÙåRw;¡¿‰Ng>y…ŸìØÍ­ÿ¿l„¿Ý-äiµW8sæAl¯'Ø^¯cñâ{9rd‰Õ2ÞÞŒ·’[nyÅ…‘9ž¨¨–L™²›¶mšåµmÛŸûîû¿ÕóØc_мy7³ô®]ïä½÷v‘PçX듇žÌûïÿ‚¿IºL&ãw~¤iÓê>êõ:^}õ^~ûÍüºòd©W•yYY—>¼GvIn%ô²²E””ü×b^QÜr‹é<ëžÂ<¬?—›»‚ Þte8‚ë™LN@@´Í2QQhÖ¬¿‹"r.¾¾A¼ðÂZþweZ£F‰Lœø32™¼Fu(•Þ¼øâ2‚ƒSMJ’ÄèÑoðÒKËP©ü«Ù»aл÷Ì»ƒ&M®þ8yúé÷èÑcHö—ÉäDDX¿®>‘µk·Ò¤‰íÏŽB &–Lòó» ×Wy4-$®\1 ºCHJ2äÁQ8zb[X•¹³_d2_Ú´ù _ß b²GLÌà¬zÝy[Ç\»ö9vìøk´h1„ü…Âò„(îpÞjº­øwii!*•­ëÒh +»¦y5±Lmþ^z½N‹··ªV±ÿßÿ=Ç?X¿®z÷§Ÿþ‚··wc­í²e?2~üƒ5–¹³bq³‰eÊ(,e.óæÍáÄ ¸ï>³=€otQxNªÌý€S@•z}1'OÞAyyõ³a öpûíŸÐ½»õÇFNœXÇ÷ß{NK¨s¹§t±ÛB.WàåU‹ù³ÿᥗ>áþû­_WÛ·¯ãÙgïj-õšÊ¼qc×´Ì+¨w¡OD«ýÃ41 V¬€°0øî;«RïØÑEA:˜9Øù1 øhfš]ZzŽÓ§ïÆ`pß% “¡C¯?© œOM¤þÌ3 KêÕÉ<..Õ«·¸\æPÏB×j7QVö•i¢L B«VÆ÷r¹GI}02ùç}0°¨2“e~þ蠟äÔ×'C‡~B¶¥þÝwBêûxùeÏ‘ú²e?òôÓ¶e¾fÍVš4‰±˜ïlêQèJJ^5Ož6 †5Mó©×Xæ´@ÕybÓÒ¦£ÕV·òº@`?Æ–ú3Vó…Ôµ¡&RŸ4ɽ¥^™¯^]2‡zzyùtº*뙯XyæµBê÷Þk–Õ¤þ%vʼ‚Àë¦IZm.ii86@à† û”=ª“úH!u]¼òÊ'Ü¿õëÊØRé–Rÿå—Ÿ˜0Á½eõ&t-ÍdÓ$¹>üÐönr9|ÿ}ƒ“ú—ÀSÔBæüˆ2MÊÈøŒ²²tÇ(T¡:©?¾VH]`7¯¼ò©M©oÛ¶–I“ÜKêË—ÿÄÓO?`Sæ«VÕ¿Ì¡ž„^V6½þ¤iâ#çv­Ž&õ:ËÀ¨òûG¯/æÂ…wê @`…šH}þ|!u}¼újÑzu2oÚ4ž+¶¸…Ì¡„n0£ÑL5Môñ·Þªy% Dê‘yUf–ÌÌœ‹F“l±¸@à†R8ž† õšÈ|åÊ­DGǺ82ë¸\èeeŸb0Té*ž4 šØ¹:‘›Ký+lÈü(öÉ@ ¼mšd0”“š*¦…8£Ô'YÍ?vLH]`?ÿùm©ÿþûZ&N¬©/_þ&T×2w/™ƒ‹…n0äRVVe0Wp0¼ja´{M¨úرfYõ)õ¯€'±"ó#@m¯{v¦IYY?Q\|°– 5cĈϪ•ú·ßŽB§+saT‚†ŽQêÖ¯«úúŠ?Û”yl¬{Ê\,ôòòÅ UÖ~õU£Ôk‹\ ¸Ô«•¹¥ÐkŠ x·j¢ž´4ëÓ+ ŽbĈÏèÙÓ–Ô×ðÍ7#…ÔvñÚkŸÕ@ê£(/wþue”ùýVeÇòå[ÜRæàb¡kµ+M""àë].5¦©»jšØ¹8Qæ ª¬à˜“³ÆÒQ‡#¤.pÕI}ëÖ5L˜0Ò©R_¹²z™¯X±•˜G|‘;— Ý`(F«Ýdš8b¨j?W° 6¤¾¿ó¥>x'˼‚q¦oËË/QX¸×rYÀÁ»ß'ZÍ?zTH]`?¯¿^½ÔŸ~Ú9R÷™ƒ …®Óý”˜&îØƒTHýž{̲œ)u—Ê`˜y’±•.¸†;ïœQ­Ô¿þZH]`F©[¿®œ!õšÊ<:Ú½e.ºYw»ôïïøÉåðÃ.“ºËeÆòíM“„ЮæÎ;gгgu-u1PN`“'Ïp™ÔW­ZÄĉÖeÝ”åˆÌÁeB7 Õ®6Mêßß(ugà"©Ï£d^A•Vzaáß”•e8ñ€9ÕIýÈ‘Õ|ýµºÀ>ª“ú–-u—úªU‹˜0á>‘9¸Hè:Ý. †K¦‰Žîn¯Š“¥>ã\/f2÷Åù2ãà8 ää¬uòAsîºKH]àxÞxc—9¸@èf+«Aý ®Jýî»Í²’’àìYÓ´£¸©Ì, C(/B¸£GÏR8œê¤¾iÓªJ©[æ¶e¾d‰gÈ®G¡ƒQê Z”º^oú¾7•9€7Pe]Ñå.p'FžE¯^BêÇòÖ[3¹ÿ~ë×Õo¿­bäÈ6[æǰxñ‘9Ô‡Ðe2ˆ´ÐWìjlHÝ&î"ó ª´ÒE—»ÀݨNꇥ®Õ © jÎÔ©³xà[×ÕßètÖe¾dÉVš6MpVxõ‚ë…n”©;`¯ÔÝMæ`v]]àŽŒ5³Z©Ï›w·ºÀ.ª“º%*dëY2‡úWßÝíU©úèѶ˹£ÌÁ¬…^^.ºÜî‡$IŒ=“^½Æ[-sðàJ!uݼõÖÌKÝØÍî™2—´Ð/›&DD8ûö£PÀ?Z—º8„ûÉ ‘éÛòòË–Ë õŒQ곪•úܹBꂚ#IS§ÎdÈÛ2__-Úâ±2]’Ô¦ ùù– Ö7R÷ó3ÏÓ3\Qͨr:åò€ú‰C ¨’$q÷Ý5“ºNWîÂÈ I’˜1ãgš5ke1_.—3oÞ š6Mtqd®ÅB¯Ò'œîÆ÷x ëö>þíÒhjF•ÓéååTH½woÛRÿòËÑBê‚#“ÉØ°á0Íšµ6I—Ëå,Xð+={ö­§È\‡ „^åž¹; ½:ÜQêfBwÑùA¸ÚRÊj™ƒWòÕW¢¥.¨9F©¢ys£Ô¯'™ƒ „.“UŒF99Î>¬óp7©W§T ¡ ’$1fÌl›R?p`_~)¤.¨9RïÒ¥ üJׇ̡>ºÜÒÒœ}Xçâ.R×™¦I¢Ë]Ð$‰{îR8™LÆâÅÛ¯+™C} ½u»ßMB,d¸ƒÔ3i’èr44*¤Þ»·m©Ï™#¤.ØÂé3¼4t¡+pú#hö\¼R%ó“¶ÿsuTÿ`á4:«ËÝ`ÐQ\|ŠÂÂÔ•e¢Õæ!“y¡REáí…Oc|}›#I.Y‘·Æ”–^¡ àÉ”•åP^žL¦D© À×7‚ÀÀ–6C&SÖw¨ÑhrÈÏ?G~þyòóÏS^^€·w ÑDEuA­öŒpRؾý ‹e*¤þÔS‹‘ËÝóï%Ô'.z0 4WX—»ÊËM¥ná4Úêr¿ti>ÅÅÇLÒââ^C¡°ü¨›Á åÒ¥%\º´”ìì_Ñjm?rèåN£F·1ŒF¡P¨m–wùùÇINþ–´´uä䬶¼\îCTT¢£‡Ð¼ù¨T¡çðá¹äå®|/IЭÛ”J‹åËË‹9yrÌåâÅ?mÖݨQ{:tx„o| //‡Æíj®JÝÀöís,–1J} O>¹…BH] ¸—ÌÁ*“E¡×_³ŒÙáî8¬C©”úóp±ê˜¾ú’úöª 2¼½›Z-~ùò²³W›¤ÅÄL4ºÁ ãÂ…/HI™ŽF“ZãpÊÊ.sáÂ|.\˜RL󿝓0 ™Ì«ÆuÔ…ììÝìßÿ226ÛµŸNWÂ… ¸pa{ö¼BbâX:w~ÿ‡Äuò䤦šÆÔ¹óËfB/+Ë篿ÞâàÁy”•Õl¾†Ì̃lÜøoþúë}n»m7Þø/‡Ä\_H’ÄØ±ŸX•úþýË™3g O=µH´Ô‚kpIÿ¨\ÞÍ4aͰ²Ž;£ò‚ÓC“` ™õqO}éÛ€€îÈåuk¥`÷îÎ?>Ñ.™W¥¼<‡£G_dóæ–¤¥-©SLÕ¡Õ²sçc¬_ßÍn™WE§+åäÉù,ZÔ‚¤¤i úêwr))¿òõ×­Ù»÷5–ùµ]bõêGYºt ee…NˆÐuTH½wï'­–©º¸§.\Å%BW(î0MÈÉmÛ\qh‡ã6R?qnùkZ§*33³wo öשžk).>ËÞ½wsøð¿1tÕï`'yyGY·î&’“çaa¡ÛZ£Õ–°gÏë¬^}%%—V¯%vízeˆPXxÑjI’#—WßÓqôèb.Byy‘#Ct9’$qï½¶¥ž”$¤.\‹KºÜåòÛ/àšù™W¬€¾ ó‘‚ ©'>iõÕý¾Ö<)$dX­«»xñ+ŽKR”$9AAÝ „Ÿ_^^‘x{‡QZz‰’’³””œ¥°ð™™ë0,÷¼$'Baá ºtùÉê={{ÉÎÞÅÖ­·SVVu`CEÜ2BBn¢qãþ„†vÁǧ*U8:]1ÅÅ).¾È•+I¤¤,E£É²XGzú6–/ïɰa püdþ¿ÿþ"ÿý‘YºŸ_­ZÝKBÂ`""nÂ××x_¿ àã¨÷ê¤>~¼¸§.¸¾qÙ3F Ŧ çÎÁ~ÇuíÖ>ÿH½±«»ß5À&Ó¤Úv·çåíäĉ'¸Væ’$#.îUzöL&6ö”JKЕª17Ü0•þýÏÓªÕtd2o Ç;ÀÞ½ÔéÞtii¿ÿ>Ԣ̃ƒÛ3|ø!ºwŸ‹¿ÍZÔ2™‚èèÁ ¸ŠW¡V›¯ÆTXxžµk‡¢Õ:¦+ûرìÚõ®IZll_yä7Ý4…BU£¸;v|Œqã¶áëf–¿eËäßõW¥Þ»÷VË¥~è~\׸Pè#É4qyÃî£ÔO¹Zê[€Ó¤Ú ýøñGÑë¯Þ ‘Ëýhß~Í›¿WëÇÎ$II³f/Ó»÷||bÍò32Vrôè뵪àï¿'RTtÎ,=1ñÞE@@ËZ×;Œ»ï>B\ÜH³¼¬¬$vîüO­ë¾–ßÞä}Ë–c=zþþMì®+((»ï^Ž\núª¨(“}ûæÕ)NwA’$î¿ß¶Ô÷íûEH]p]ã2¡KRrùͦ‰+V¸êðNÅ×ÕR_lúV&ó!0°vã®}¶\¡¦sç턇ßacšÐŽÞ½wàïƒYÞɓù«Ýu¦¦.åüùŸÍÒ¡¸& IDAT[·yÈåÕ·l«C.WѯßÏ$&Ž5Ë;|x&ééÔù×lÞ|$·ßþ2Yíï€ÅÄô¤gÏWÍÒZXë:Ý ©÷éc[êŸ.¤.¸>qé´^fÝîû÷Ãæº=fä.¸Lê§ïM“û"“Yž¤¤¦H’Œ¶m¢VßX§zª¢R5ææ›×âåe>YË‘#¯bÏÈt­¶€¿ÿ~Ú,=,¬;7ßüf=@u@&Sзn©’c`ëÖÇö8[``ƒãö:tø—Y=/Ð|‡Ôï.H’ÄÌRªàR¡ËdÍQ*ï6MÜ»-reN¥BêQA2ë*õý@•Þfÿ΄„ÜU‡JÁϯM›¾R§:ª#2rMšŒ1K?ztJ k0pêÔ ³ÔfÍž  E£³NppÚ¶}Æ,ýðáYu®»iÓÄÆö«s=×Ò¦ùm‚K—8ôî€$I<ø m©ÿý÷/|þùX!uÁuƒËWÒðö~¨òhÉë¯C¹çü§óõ‚Óÿs‚Ô_ì‡:&æ]‹Eí!>þm—,ªÒ²å;fÇ)(8F^^õO;¤§o  à”IšL¦¤U«—£%:txÑ,îóç7PPR§zo¼qRö·Dh¨ùx…ììSJ6|*¤Þ§-©/cöì±èt ofJÀ^\.t™¬^^Uµ$'ÃËó67T.õmÀ:Ó¤€€[ X»ÿÁ×·%ªS5Åßÿ¢¢ÌG§¥­¬vߌŒufiMš Ç×7Ú!±ÙÂ×7ŠÈÈÞUR \¼¸¥Öuz{Ò´iÝþv– Š3K«úŒº'Qs©ß#¤.ðxêe­K•êM$©Êœão¿ õŽÓp¨Ô-<-;­–‘]Å8¢ÝqƒÉª#&æ>³´ôôê…~é’ùàɦMïuHL5¡Y3ó®ìôtÛ+¡Ù"2²«ÙcfŽ@­nl¶lAAÃZÝÐ^®Jý1«e„Ô×.™)®*’Ôoï—Ðh®¹zù2|ø¡QìD…Ô›ýÒs«dÖtF¹•À_¦IÁÁÃP«»×9¾ÐÐ!u®Ã5‚Læeòì{nî>tºb _‹û”–f’—g¾B_FÆF._6>B&YùMrmzÕ2Õ½¿6½´Ô|zÙŒŒÚ =00±ÖûÚFB©ô¥´ôê¤;%%–§Æõ$$IbÜ8ãLr۶͵XfïÞeÌšu&üŒBQ/_}S©·«ÚËëJKgc0\³ðŇˆйs}…å*¤žøoȰWêç³ÞDÉ!÷Î%IF``:×cr¹Š€€¶äæ^;òÚ@ii& EœÅ} Íç+8}ÚòT ®Â¸Æ¹ÚôpšÏFç(ª.â¢Õ–:íXîDM¥>{öX&Lø ¹\H]àYÔK—;€$ù¡RUá¬ÑÀwBzzýåD|½ ùiO÷{00}R‹°°±øú¶¯sL E’äú¹¯ÕêÖfiõÍJK³N­Ñëµ&-a{ rV Ý\è:]™•’ž‡$I<ôЗ6»ß÷ìYʬYb œÀó¨7¡x{?Ž\^eA‘‹á®» ÔóZvIÝ<TyâH©lDl쇉G©4ŸÿÛxy…˜¥•–ZºµÕÔܦv?6||œwîÍ…îyÿ—lqUêÖçgRx"õÜç¤ÀÏo7c0\ó¥½k<ñÌŸ_¡9‰ ©WÛý>Xjš-I^4o¾ //ûçû¶„Baé—…ó±4«^oý±E®Ø,-0°5¡¡]ÍÒ-Ý·ç^yMÒ¯}/“ÕnBg ˆ»Z·©Ð 2q“=H’ÄÃÀ¶m–ç³ß³g)3gŽeâÄŸÄ=uGPïW±LÖ ÿE Æd ´ï¾ƒvíàÅë-6gQ)õç £jí'Àq`ƒù~ññ³Q«{:,½¾¤úBN@«5ï¦ööndµ¼¥icCBºpóÍßX\µê¶&eê²­ Ž˜sÞµý‘áiØ#õI“Ä=uãÈʺŒ¯¯~~þÕv õÚå^BÑ__ £Â^yÖ™?ì øzAò'h!s=fÈDDL"<¼îS¼^‹%±º‚’óǨl ]¥2Ï+)iØã,j²ý´ºÇ ®âBP©&âí]e½Ǝ…_í_•«!P1úÝ¢Ô¯!  ±±;üøÍ9t:×?ûoif8[B·”WRÒ°'Kqf—{m9yrëֽĺu/²xñC3:¾Bê·Üb]ê»w/R¿ŽX½z1‰‰- áòåKh4‡Õ½`Á×lܸŽ=zl>^¨:–,YDBB"ÑÑÑdf^¢°°°ÆûºÐüüf¢Tö1MÌχÛo‡O?­Ÿ œŒŸ·QêÁ~–ó½½iÖl’äŒî@ùù;¡^럣¤ä¼IšJ‰——¥mŒøû' —›>£žŸÌ­ËU‡3»ÜkCZÚ>-º·r¹¤¤ïøê«[((hØ=!H’DDDsl=b¸gÏRŽûÝuA êiÓf³dÉVž}v²CëMK»Àk¯=O£FLŸ^;g½ôÒ«lܸ•Ù³í,×­„JÔê¥Èdq¦É:<÷œq œÍù^ÁÌ_!Ï|Ü’¤ yó•(öÿÊ«)ÙÙŽíjªŽ´4óÕÕ"#o·¹LæM£F¦Ë˜ z22~shl®¤êÀµú$-mß|ÓÆôLjê.fÎìÌ… {ê)2ÇP^®aΜX´Èúr½’$ñøãsiÛÖ±‹å®/žyæ1òóóøè£Ùµj×7:HRkÉbÌ3¿ú  €¬,׿JÊà¾YðêO 7ûž‘7óg¶Ifæb ×t3 zRR¾2Kˆ°-t€ÈÈAfi/®vH\5¡¸8¼<Ï\ädõꉔ”äXÌËÏOcΜ>$%ýàâ¨CNÎE¦MëÍŽ¶ã7î3n¹å_.ŠJà‰|÷Ý\6mÚÀwÞÍðáækV¸·:€\Þš  =(¦6ýýwèÚŽq}`$5zM…ÿ2ÏóöŽ£M›$ÂÂÆ9=&•K—:ý8ii‹(,‚Ì|óü°°'hÞ|# …kgpÓhR8}úU§ãÈ‘ÍÃÉå~´lYóA)mÚ¼a––œüuå-Π  …;^0IÓéJQ(Ì'ÇiÈH’ŒAƒ¦3fÌ›Ôi4ylØ0™÷ßO䯿f¡Ó¹Ï˜–3gvñÿן?ìGròN›e;w¾‹7ßü‹›oã¢èžÊO?ÍgëÖ±~ýjîͤIñÛoë]‹[ ½_ß— \$©Í3Oœ0NÛ³'üe¡ÿÚ 8™£>îS`Ûqó|IR3“¦MçÔËÜê.Ì$-Íòäuåܹ/II1_ï¾Y³çðöލq=AAhÒä“4ƒAËöí£).vücl¥¥WX·nee¦¿¾èØÑvk¶¡Ò¡Ãý<ñÄvlÏFXXx‰åË'òá‡-Ù·ï‡ÊÑñõÁ… ‡˜9óÞ}·ÇŽm²YV’$îºk Ï<³•ʵ“~<¦„wß56JÊÊʸx1™LÆ’%?2jÔ^xa‚KãiBðöFpðär+«Týõ—QêwÝÇ-X³ÈÈ…§¾†6¯À2+…åòš5Û@x¸kÿð–8~ü)ÒÓ¿wh.,äàAóϦV·ä†̻ૣS§f¸i4—غu8M¦•½ìG«-fýúaäæš_K½z}ê–Ï‘;Š&M:3aÂ^bc«_ž7;û >Àôé-Y½úe’“·¢×;ÿ6XFÆ 6nü˜>êËÔ©Ù¿eµûx{û1qâbîºë-¤ºLó'üÃ_|JZÚzõº•¤¤Ó¬]»µkgÿþdúôéËܹ³ùî;ç4”,Ñ æ:T(ÚšDQÑ{ŠÁ`aêÒåËaõj£ØG2>î¶Ð²wzì8eøœMPdun‰àà14n<oï¦.‹¯*^^(+3ŠÐ`ÐräÈ8ŠŠŽ‘˜ø2Yí­2tœ>ýÇ¿IÕG…”Ê@ºu[BaÿßÅ×7†®]çòÇ£LÒsr’ذ¡}û®%  e­ãÈÎNbË–ÈÉ9j–ײå#4m:¬Nõ7ÔêH{l ›7¿Å|‚Vk{⬬SlÙò![¶|ˆJH‹iÓf(­Z A­®û=E®œÓ§·qðàjZMfæi»ö¿á†Þ<üðçDG·©s,A Ì#""Šï¾[FpðÕ†FDD$ ,¥[·¶¼ýöëŒçØY>­Ñ „ Iøû¿‡¯ï ߤ¤d>P¥»O«…Å‹/ooè×Ï(ø;î€ðp‡ÇT¨ ƒÝÖ쇬j&_S«Ѹñ{øúÞèðXì¥mÛÅ>|eeW¥¤¼GVÖJš7ÿ€°°ÁH’}9YY›8~üUrsÍGK’ŒN~Àßÿ†ZÇ=’fÍžâôé/LÒ Ï²~}wÚ·‹-žF&³ïö…Fs™C‡þÇ¡C¡×›/93ˆ>}êw vW¢Px3hÐ{tï>‘M›¦²wï75j}k4y8°˜#I11‰íB@@¤…W …ñ‡cQÑòóÓ)(È //ƒ‚‚ òó3¸|9™ãÇ7¡ÑXxR ±±=zíÛ©ÓÜûAUŠŠ IIIæá‡Ÿ$(È|b¬ÀÀ †½“¯¾šEFF:QQQN©Á ½¹<šÀÀ¯ñ÷ÿ7ùù¯PZjeÎ÷ÒRX»ÖøzòIèÕË(÷= * ""À«æ-Q.çUùë$¬Ü[Ai Æùúv#*ê=Ôê[k|>qDG?Itô£xyYÿ1¤Õrñâ÷œ;7“‚ó–-€L¦¤cÇ9DF­sÜ:}FyyçÎýh’^V–ËÞ½ÏqâÄ Z·~èèaøû[˜Óàôúr._ÞÉÙ³‹9yrZ­…~€°°›8p 2Yƒý/Skš0rä—ôéó7¾ÉÁƒ?×x7ƒÁÀùó{8Þúä4>>A”•;tÝöˆˆæŒù]ºŒA&&8žüü< ¡¡Ö¿ÃÂŒy%%–¿WMƒÿvR(Ú²–²²Íäç¿Ly¹©LõzØ¶Íøº–ˆŒ4¾.™®Ë}*½—òŒ¿œoiÛ¨T­‰Œ|—ÀÀ;íÛÑEøû·£sç?HJDII²I^II §Ný‡Ó§ßÄ×7Ÿx|}ãQ©b(/Ï¢¸ø4EE§).NF¯·>÷·RD×®K ïë˜e2%Ý»/ÀË+S§¾0Ë/(Hf×®§Ùµëi‚ƒÛÔ_ß(||"(//@£É¤¨è<—.ýIy¹í.•¦M‡qÛmß¡T^߃¨Âšsß}?rë­¯°aÃë?¾Ö!õ–”T]G¸öG3bÄ›ôîýˆX=MàT¢¢šÐ´i<;wZÊfÇŽ?hÒ$šøøD—Ää1W¼·w_ÂÃ÷ ÑüBIÉh4ë1j8©ý•+Æ×Qó–å•BøõýñH’_ß^ÿ‹p÷ñ‡>>‰tíú7Ç?Á¥K‹Ìò †rŠŠNPTtÂÂÞ¶ñ÷oÉÍ7ÿ‚¿ÝîmWE’dtéò9>>Q>ü®Ånr€œœƒää¬Eýrºtù/7Þø ¶æ¿Þhܸ#ÿú×RRþà÷ß?äĉ_«½Çîü˜ÚЧÏcÜzëSxy¹×<ù‚†Ïþý{9p`/wßý€É’¨½zÝÆ?|Í¢E ¸çžLöY²äG¶nýxÄ$½´´”Ÿþ„„z÷¾Õ¡qzŒÐHøøŒÄÇg$PJiéf4šå””¬B§sþ"2Yjõ`Ôê¨ÕCœ:»3P(i×îg"#ïåÔ©—).®ÛT§^^a´h1…¸¸§œÚUݶí›ÄÇßϾ}/“šºÌ!uÆÆ¥sç©„…urH}žH\\/âã{¡Õ–pêÔ&Ž_ÃÑ£kÈÍuþLz …7-[ÞFûöÃhß~(aaqâ¹À)h4%ŒÕ‚‚|233xùå·*ó¦L™NRÒÆÇ®]п¿q¬Æ¦Møúë/èØ±Ó¦™.G¼`Á·<ûìS( NžL%22Òa±z˜Ð¯"IÞ¨TCðñBpð”—隸x%%+(/·|·6(•qÿ|8~~· IÊÿÅ~'ááC¹ti1©©3ÉÍÝa×þjuk¢¢F“ð<^^Õ¬ ë üýéÓg)—/ÿÁ©S_–¶žÒÒl»êP(üˆŽHÇŽÿ!<¼Kƒÿ;º ¥Ò‡Ö­‡Ñ¦Í0F‚ôôƒ=ºšcÇÖ’²ÓaϨ5¦]»¡´o?Œ–-û¡R—('3Q*½ ¢  Ÿ°0Ó'6BCÃX¾|3>:†ùó¿â›oŒómxyy1pàí|õÕ‚‚‚Lö©˜IÎÇÇ??+ËlÖ‰ÊgŠ‘ÉŒ÷²*þƒ¸rëÊcét—ÑëSÑjÓÐëÓÐj/¢Ó¥U¾ÊÊNb0h$Je< E4 EryJe …ñåå‹R_/ç«6çõðáádg›.hÒ³g*ÞÞÑ6ëÐhRÉÎ^G^Þn RZš†V›Hx{Gáí…JEPP""†áë›àk .Ÿt\¾¼“´´uœB£É¬|éõZTªpTª0TªpBBÚѤÉ""º#“y¹Íß«¡lmåçpùòqòóÓ-¾òòÒ),¼Œ——/QDE``c‚‚*þEHHì?ËŸºÇùuV½U·“'æï¿7P•/¾H"1±£Ç_cî@QQ))É´k×Ñj¹üü\¶oß‚J¥¢gÏ>•²¶Töøñ£DDDZçØBB‚ÈË3®”(q ýzµ¶B¯X+¸žÿ^ eë1¸:VqD¬îëµBwï‘Z@ j„º@ €º@ €º@ €º@ €º@ €º@ €º@ €º@ €º@ €º@ €Ç®¶&0Çßÿ& †r“4™L¬-ž€úuD\ÜT›‹M á"ºÜ@ ð„Ð@ ð„Ð@ ð„Ð@ ð„Ð@ ð„Ð@ ð„Я3ôúR m}‡a‘¢¢³hµvíS^ž‡N§qhåå…6óõú2›ù:]uù¥vǤח۬W¯×b0èí®÷Zª‹Û‘esæÌŸçT¦¥§!55 ½^ç²8,Q^®©ó¹¼¦Ðf}ÙhµÎ9÷ùùWÈÌLý'Ž"JJŒ×vII!M‘SŽ)¨?„ЯôúΜy•½{Û²}»š?þ")é6Λf6ÑL}²m[_~Í®}öîÄùó‹«-·n]O²³÷Ù,säÈ ~ø!’ï¾ aáÂhNžüÖ$ÿܹU,[v3óæ³bÅ-\¸°É$?3sË—÷çË/ƒùé§›8~|I~nîiÖ­»Ï?ãË/³{÷ûÕÆ—w–E‹óñÇ|üq‹§°0­2_«-aÍšÇøä“Æ|öY ëÖM0ó‘#?óÅY°`°ÅcìÚ5‹O>iÉ´iá|ÿýp._>Vm\,^üÉÉ[k\ ¸8‡wßmźuo³wïBvïþž3²té¿ÉÏO·«>GP\œÇüù“xá…–<òˆ?>Ì´iY»ö ƒÝõåòÝw¯ðÈ#¹ï¾ ÆŽ àå—»³eË÷fe§NÎéÓ°nÝ—äæfZ¬óàÁ­|üñcvÅ1cƳ¼öÚ~ùeóç¿ÀÂ…òãÙU—ÀýËx8%%§9|x*UeìØŸ oMRÒ·,\8š ö TúV{^³²N£ÑäU[îZ._>Ihh&l¨L;sæ/n¿ýMzõzÒå“¥¤$ñÑGÃILìÊØ±ÓhÕêVtºRΜÙ˲eïpôèV&MZ€Jå_£ú23Sxë­þDF&ò䓳hß¾ÅŹ$%mà§ŸÞæÏ?—ðÚkK‘Ë_¿>>þøøšzŒ_ÇlÅËËǤޢ¢<ÒÒNÛõÙ{ì¿åÛµ á"ZèŒÁ ãĉqøûw¦]»µ„‡ßƒ—W#”Ê0BC‡Ð¡Ãzº¡Ñœ7Û·¤ä,yy»ÑëÍ»³µÚ‚Ê.ÄÒÒtrsw¢Ó™vßétÅ»¦õúr´ÚB må>ùù‡(/Ï%  5^^afûŸ'7÷`å1Ç7ï–--ÍâÊ•}&Ý›ƒž²²\ ååV»ÓúˆþCLÌäroÆÐ²åãœYYOXX+"#;Z<ƺuÏsçs‰Šº…›®]Ÿ¤qã›Ø¹s–Õ¸jB~~:));(-5½…¢×k),ÌB.WPR’‹NWFYY1Mƒž’’\4ã5V\œCZÚa“:4š|’“ÿ"?ÿ’YÝ¥¥–ÿ¾%%y[ÚååfϾÎïä¹ç–Ò¥ËHüýC Šâ¦›†óÖ[ÛhÔ(œœšõ zþïÿÆ×É“×ЭÛ]øúËÀóþûÛHK;ɲeVîãí퇟_`åûääýLŸ~»ýËËK9qbéégÌòBB¢hÒ¤Yê4|D ݃ÉÊZLiéE:vüI2ÿí&I ß3I+(ØÇáÃ÷c0hQ*Ã(*:F\Ü‹$$¼VYÇŽ]h×î+NœøZmr¹ùùhÖì5š7 I’“šú-W®üI§N?˜ÔäÈKÈ径†öàÔ©Q*ƒ(.>OëÖS¸pá'7¾‹ØØ{¸re{ö<ŒV[ˆOŠ‹S¸ùæoIJznݾ&$¤ :†?þKvö^¼½C).N¥_¿õ·§°0…íÛï#?ÿ»vM"4´3½{cv.ï#!á“´€€DŠ‹_ä¹¹'éÞýÿLòFsêÔ•ù­[ÿË$?>~87>ŒÁ G­nJïÞW÷×éJ9~3‘‘]­üõÀ×7‚g›üíTª`Tª$IÆ•+'‰‰é…RéW™ïí@BB23zƒÕº¡B®—ˆ‹ëc’Þ¶íh®þ6†%òóÓ™?iiˆŽîDzú!:tÍÝwÏF¡Prþü^V¬x™œœTfÏLÿþ¯páÂ>ŽßHJÊnvîœÏóÏocúô.të6ŽmÛ¾ 1±'>º®œ~x’;×…+WÎÏÓO/% ‚‚‚˼õVG¦OOÆÛûjkúôé|õÕ8¦O?˜6ÿ7oþ//_~x¦ÅÏ£Tª7îã÷ìÝ»†¬¬TÞÿ¯ÊøµGñÊ+‹xþù. > zhÁÁ‘•e†}Š'v3wîK<ñ„ínñÅ‹?b޼׈mIY™­¶Œ©S—rà 7°zõ—œ?‚çž›Q³ hС{0» îGM;bJK/²o_Z¶üœÈH£ÜÊÊÒØ¿$CÍš½]YöÀq´mû%ááþ)w‰Ý»znÎ+µ IDAT¸a 11sòäÛ””\ÀÇ'0¶¬SSзïòò’ÈÊÚFÇŽ3IH˜€$Á… ?UÖŸ“³?ÿN—._u;’eeÙlÛ6ŒÂÂd“¸÷ï™Î?£W¯Ÿ$8{ö¶o¿—#Ž V'0tèNV¯îLs ídñ³wì8ÙäKÛ`ÐsúôBZµzüŸs™‚¿´É>ju,yyÉ zòóÏàïc’/I2||QPpž€€¸ÊômÛ^âÔ©%ÆÑµë¬þ=¢£{™‰äüù­ÈåJBBšsêÔ bÌö J +ë¸Õz+ÈÍ=‡Ze–ÏåËÕï_­¶”™3{ÓµëÃ<ùäT*5åå%,]:… fܸˆ‹ëƘ1³Y»v Ï>»€Žï"''•n¸nݪüÌ»v}ÏÛoŸB©ôàÛoÂ`ÐóÞ{g ŽF’`Ë–YLŸÞ‡©SEÛ¶Cؾýkú÷¦2®?cÀ€g,þ¨=sf7íÚ °û³Z#)i=mÚô±(ó š6m‡¯ogÏ uëžÄÄ´2ù;{yù0uê*ž{®QQ >Áb=Ë–}¦M?0kÖnš5ëÀ¾}yýõ|òÉVÑ2¿]îŒFs˳ĹsиñãDD\m©z{7¦}ûŸIIùZíÕ{qññÿ&,lÀ5å"èÒe-§O¿‡V[€\îKlì¿8{öjË 5u> B¥2Þ'W*ƒ‰Òb,ÉɳhÑ⢢n¯Lóò ¥W¯¥èt%&eÃÃ{ÿà5±Ý‡Fs©ÚÑè¶Ø³ç5äro塞,™L‰LæeRF&S¢Pø¢ÑdST”ŽO#³züü")(H­o{bbn%;û(§N-­qLùù©¬Xq?·ß>€Â üü"ÌÊùûG’ŸŸj–^ãþæ1«ÕQäå]¨q\ìÝ;ÿp ˜Œ··¥Ò‡Ñ£gqüø.]²ïGB߾ϡPežž~”#GÖsï½³¾úÃê¶Û&ÇŽÆ[#þ›?­ì®ÎÍMãèÑßèÕë‹ÇÈÌ®\9Ëûï·4ËS*}¸|ù$æyÖ º:@òüù}èõ:>øÀ¼×B«-%+ë,11gß¾_èÜy›7NÏžYÐGffÍÿTGãÆ7™™b³ŒÁ`àìÙý4nlû–H“&ÍyãeLz'ï½·Á$/=ý M!}d>ê]§ÓÝÜîØ !tF­îƹsoÿr¹wµåår?ÊÊ.YÌ+/ÏB.WW¾/-½„ŸŸù—sYÙ”Ê`TªÆ4j4˜ÔÔoðók†—W8ÁÁ]*Ë*•!VcQ©[makµ¦Ÿ¼½ÍGèËå>f-ùšpîÜJvï~…¡C7¡R] ØŒÂÂT+ÓòòNÔ lNAÁyBCÛUæët¥h4Ù¨Õ1”–æ¢P¨P(®®?Ñ ?¿Æ\¼øqq‰ˆ¸:p-((Ѥž%KFÙ‰=®>ÖÒœ´´fŸ!;û$mÛÞ[íg ˆ¡¨è²Åý5jUíþÆØÊ*¯-•*€Þ½Ÿ¡cŒu]ª×ÏïêõáãH³f½˜4imµõø<«W¿KÇŽÃØ¾}o¼±Ãê1š7ïΟþÀðá/[ì’·—–-{0wî$JK‹ñö¶ü”@ròßhµåÄÇ·¯¶¾6mz2iÒl¦LÁ=÷¼Z™î爚o¾1özˆe‘ ºÜ=šðð1(œ9ó–Ù2ô¤¤L£¨È8Š8$¤ ÍÊæälÇ`ÐãçwµeœžþUÉÎÞŒ$Iøû_•AB¿9sæSΜù„ÄÄçLÊ+Ö‹ŠŠ©SŸš¥_¼¸Š’’‹&i2Yõ?VjBFÆ6¶oŒAƒVÜÖ$/8¸-çΙv}ž=»’ðpcwmhh;RRLóSRÖü{çU™5ðßLÚd2“Iï=¤B•@Š"‚(‹Š¢À**,*®ºÖOÖʲ¸ÊbÅÞèDÒBI)„ôÞ{&É$Sòý12a˜‚¢Föþž‡ç2çž÷}Ï}ïdνç-'§Á€ˆää79xða£ómmÕ44dáèB€»ûH<<ôÿ$ýCQW—–;æ#•:3mÚ&£òNNá””£££{(¤££‰’’ã½Îj¿‘H„½½?ùùä™™;ñôì[Z©¬F¡ð (h2©©ß¡Ói.Ó©á³Ïî½æ c,-»'ûùû¤¸ø, ÆC]]:>ùd uuEÙÀÓim­ç‹/V2`Àh{6LšôW”ÊZ¶m{¥ÇóM'ß~û"ÕÕ}²9:z&^^á|øáÊg©×Ö–òæ›wqÿýo˜,Kë›nšÇìÙ+ؼy•Afgç‚““'IIq&ú_|ñgÎÄ›Èn|‡~C#"4ôKš›HMHcãA4šÔêzêë÷‘–6††±´Ô‡‰½½W¢R•qþüý´´¤ÐÙYMEÅg¤¤Ì!"b"‘™¡æææ²²ž¤µ5µº‘òò/INžÇ A›ôŠ(¬­}Q*/àî~{Ÿ-~’ÎÎzî ¼|ÕÕ?qîÜ‹dg¯ÃÚÚsó¾­ î+uu)ÄÅÍ"(è>TªzJJöQR²òòC þ ))k(*ÚFÓNAÁv23?`äÈW4è1ÊÊ~âüùͨխ”—娱ULœ¸€ÈÈ%äçï$9y Je)EEûÙ»÷BC •šŽc_dß¾¥TV&qqäçï#?ÍÍ%(¾DD,`ÏžÅ45ÓÔTÌ®]ý×>…ÜfÎ|›;þJqñ :;•$$¼MCC11õ¨¿gÏ“9²†ÎN%ééßÓÚZƒ½½¾­©8:²eË]ÔÕå£Ñt—w„M›fàìŒXlÖc}A.wfÚ´§Y¿~&§ÐjÕ”—g°yóêëKppð1èŠD"¦N]É¡Cï2uêÊ+ÖknnÉŠ[IHø‚5knåÂ…c¨T-47WsæÌ.^~yÒÒ@¥*A. &fŸQHý"R©?®®Ó½¥¥# Å`#=¹<‰Äåg[,9ò+Êʾ§¼|''§ÑŒ·‡mÛœ°±ñÀÖ6 ‰Ätb˜“ÓpÌÍ»ß𣱰›èh4­¤¥½Ž£cµµ‰ÔÖ&ÎI$ŽxzND&ófÒ¤-¤¤¼ÎÁƒ ñôœÈÔ©_ÂòææR¦Mû–Ó§_"!á︸ c„õ8:Fþl§?³gÿÀáÓð EÁÁw0jÔK=Ú“³††<ììIL\kt.&f%¶¶ÞL˜ð >Ö-ã07·&2r>cÇþä. )^^#Mä>>c˜4é%~øa% ù„†ÞÂüùß`ffÑ£MÆ-b×®•ÄŽˆ“Óî¾û+$[Ãù… ¿æ§ŸÖðùç÷ÒØXŒ£c'>ÎСÝCÖÖvxyG\]CQ(º—nùùÅ-=ˆ]££/;vB"‘¨ßÚüöÛ·2bÄ|Fºûšû·««ËàÀ{¿þ¾ß·¾Ô÷gÿMlýãÚvp°£©I¿[£ð†.ð›RXøžžwaaawMå\\b‰Ass&nnS±°SSs” Þbܸ=¿‘µ¿¢««ôs.#îOTVfS\œÂcmýEå¯÷µõ羸±ÆÐ®W×Û17W\]Ðj[ øÛÕ/C&À´ièêÒ’™ù©©«P«›?þŠˆk®Oà‡òò î¼óÍ^C×7*"„»`k?9^.úàÏqì6üÞ¶ } ØÚ_l½4ä.¼¡ Ü]@@@@@à@pè7‚Cÿ•h4´´l§¥eJemm'Ðj¯¹žÎÎ*+_ý ,üíhlç§þåí¶‘‘ñ9G>Çѣϑ½•–Ó$)--¥½Ú¢V·‘›ûƒás}}UU©&z55ÔÔdôÉ®’’Ó>ü»w?ΩS›(-M¼æ]ßúl ´ô\çêëKIJÚNRÒ΋'??ùŠkÎ{ã»ïþI]Ý•Ütuu±qã2*+óyõÕ;hl4Ý[¿¯´¶6‘œücçÖ®}˜ãÇwþâºþýWÒÞ~’òòihx—ºº5”—/!''œ¦¦¯¯©¦†††¾gÞê´¶^ ¾þÈØ~••®{½M¤¤ç‰ÏÈØDOÛç^/22¶ðÞ{9ò --Åh4*’“ßfóæPŽùZm÷¾öùùq8ÐóhJe%Û·wgžËÌÜJbâ»&z))Ÿ’’²åŠ656±iÓ8þûßñdgÇannEnn<[¶ÌaÆQTW÷¾[Z&-m/µµE=žËÌ<Ìûï/åÇßeçÎ×Ù¸q!O<ÎéÓÛ®©ãÇ¿¥¥¥î*Z]ìÝ«ßη³SECC%Z­úšÚ¹”ŠŠ|Ö®í9{a}}­­Í=ž¸qÖ¡_$’Áøøì3Ì:lk;Jqñm(s‰„¥3W&-m3ññ˘6íC""ôÎøâw©±1—~¸Ÿ={1kÖç†2‰‰ÿÁÎ.˜˜å=Uù«im­aÓ¦±¸¸„ñüó5†]ÛD"Ðé4:ô6ŒáñÇS°·÷¹Jm.cxê)ý¾ü"¤¥Å±aÃB†¿ë‘À¥'||Âyë­cF³è¯'«Wÿ²5ù.‡þ`csb±•* kk}’‹®. uuQ*ZllFàìübqï JÚÚR©­ý•êVVA8:Þ­íD½ÚÚ/¨¯ßƒNׂ­íX<<¥ªêcìí§ •¡Ñ4RQ±‘ææ8;ÏÁÍm‘¡¬VÛBIÉhn>…™™ {û±xzþ•K·iP*ÏQR²‰ÖÖ ÈåƒðòºïŠ}ÐÙYCQÑf‚ƒŸ5’75¥ÐÔ”Š¯ï}h4-ää¼EXØ‹äço¢ºúG¬­=XŒÝ£r--9äæn ¹9…"?¿»{l·±ñyyÓØ˜†\ˆ¯ï<<ºó¶§¥ý“€€ùde½GGG 7Ýô\²ÑKYÙäçMgg ÃÇgÞÞúT±]]]ääl¡¨h7rÂÂîÇÝý&£ö2ÉÌüÚÚ4lmý œŸß´^û©½½†C‡–sË-ßlº×½½ý,8‚FÓj$wvŽàøñÕØÙù<«×ú){÷®ÂÕ5’{ïÝnÈI~±ØœÉ“ŸgìØåX[ïGPZšLrò§TVfàêBTÔv÷Qzú¬­ØÚº’°™êêlBB&1~ü£˜›w§¦Õé49ò.¹¹GÑéÔøù`Ҥǰ²Òo盟‚ÚÚ¬­=ú_n¾yÁÁcéêêâÈ‘÷ÉÈøNCHÈxbc—±ÿ¿3æ^ìíݯ¹/ šúóÞñÙxy…òå—Ï2wîóXYu'WQ*Ø»÷î¼ó£²iiIHøŽªª|¢¸í¶•ØÙ™nU¬VwðÙg/òÀ¯d­­M?þ=©©‡P*ë ŠfÖ¬G°·7-5vï~ŸˆˆQtïápêÔ>——ЇGÓ§/dÐ ã­rsrRùá‡OÈË;‡O“&ÍeøðI†ó {133ÃÓ3€­[7‘ŸŸÁ°a˜?ÿQ$}ÿÔÕUñÕWÈÉIcæÌ»¹ùæy†ò 5|ñÅzΟOÂÎΑ#&1gÎý×|}z„ûo@këQ4š:¬¬ÂÐéZ)(˜JcãרÚÞŒ£ãƒ¨TääÜ„FÓó˜Ycã.²³§ÓÕÕŽ›Û*¤ÒHòó¤ªj½‘^NÎÝ”•­A¡˜€‡ÇJ4š&ΟŸAUÕttPZº†ÎÎ Üܤ¼ü¿(•gèè('9y,MMÇps»—9ÔÔì&%åVt:•¡ÊÊoHLœ„¥¥3Ï “…‘šº€º:ã,]—¢V7PRò‰‰\©Ì¦ªJ?ƫն‘Ÿ¿ÄÄ(• úry(GΠ¥%ÛP¦¢bññ£17—ö4vvQœ:µ˜²2ãìfû‰¿µº™ðð'qpÂÉ“Ë8~A'7÷câãgccãEDÄßà²]Ûlmñ𘈙™ÞÞӌҥ=ú0åå‡ _Š›Û(ââæSYÙš³´ô Û·OA¥j`ÈÇqq‰æðá$&‡ï/%3ó lmý ê=q>'»ñ>ô¶¶¾ÜqÇ6vï^DEER¯e ­¤¥}ÅM7=iâÌ/E"1væiißñÁ·¢Ói™4é)\\Bùì³{9qb³A'/ïG®çË/—âêÊĉ#;û_|±ô’öÛX¿~:II_6™±c—PUu·ÞKss%¥¥éìØñ?ý´‘qã–àë;N˺u3HHØÂ A3™4éjk yûíÙ>¼¥²öõGzú˜™Yò—ÇŽ‹ZÝa¤ÓÞÞL|¼ñ|Ž;þÅÖ­¯9žyóžE,6cùòÁäå1iC£Q³gO÷¼††Jžzjùù©ÄÆÞˬY¢Rµñàƒaäå¥\ó5?¾“ŠŠ|@ÿ`úÎ;+ùøãÕ„‡Ç°hÑ Dòâ‹ øøãÊ;¶›•+§ÓÑÑν÷®bÀ€¬^½˜¯¿~Ç “ššÀ·ß¾ËK/-ÂÃß{ï}‚ôô“<ÿ|÷ÿ† Ï0}ú_øøã·(+Óg®+*Êáž{Æ““Îܹ‹¹é¦élÝú!«VÝVûç£ñG"¼¡_TªtJJfZÔêrÔê<¼½?C,Ö?¡ÖÔ¼ˆ0à8 ãÙÚΤ¢âÊËŸÆ××ø‡@«m¢°ð¿ÆÖvÒÏa¸)8:ÞEZZ( Åd¬­C©®~•*‡ÈÈØ™É‰@¡˜H}ý23gêknN 0p-2YÎÎÝò¼¼U(£ y×êsu½ƒ´´y­ÅßÿY::*ÉÊZÎ!Û±³Hqs»cÇáâr˯ê»ÎÎz¤R|‘œÇÓÖVDIÉׄ‡?ZÝHbâFúW×É;}|æ²oßôÉ`4šVNœXÄÈ‘›ñôœiÐó÷ŸÏöí!¸»OÁÑQŸ ÆÍm<áá+z oÊ嘙Yaf&ÁÛÛøÍZ«U1iÒ§†rjµ’¬¬qw…V«âÀû?þõÎY$‚¿ðé§¡øøLÆÍÍ4qM}}þþ½¿Á_ /¯1LŸþ.ß|3‹E‹N Pôž&ôZhhÈG,6ÇÏ嶺+ÿL[[ß|³„Å‹÷àï?€ÉDEÍãµ×ÂŽÅÉ)€ŒŒ}üㆠi¾¾Ãxá?Ôêv,,¬‰_‹V«fÕª@ß‘‘3عóy¾ÿþ)x@?ößÜ\Å3Ï$`k«Oè³wïkètZV­:€¹¹"„‡ÇräÈû|ôÑ’>_K~~ÿú×lt:-õõe(•õ,YòÞ5‡Û[Zêxþù=˜™Y ADÄ8ÜÝY·î~ÞyçÊNù•Wæ0{ö ¦N½ßÐÇOgøðiXXüºtÁ[·n °0“rq?þÁƒo"6öNΞ= @kk3«W/â•W¾"&&ög¦0eÊ]ÜqG11“ ЧINHØÇ7ߤãã3€ÈÈfÎô§©©…””Þ{o?®®žL›v—áïgõêe̘ñ}ôeÃ5NŸ~<˶m1oÞâ_uÿ‹ý:`n™Jån||v ‘ 4 ¥76~…ÍMÔÔ¬Œw›jhøÎÄ¡77Ç#YÐÞžF{{šA.¥¥ Û±¶~šººïqw_™™ñÛ›£ãmH¥Ý¡57·ÅddÜIhè'(£}žíššïðö~‚’’uF6™›+¨®þÿg©«û[ÛhììFµaaုïc´·÷-Oô• yÚè³½}4UUú<ÏÕÕ‡‘Équl¤cn.#$äqjjŽþ¬wNMKKYYë z"Èd~o38tw÷Ø_dgT”±ÎÎC)*Òï+_QqµZIKK))Æ÷ÙÖÖŸÜÜ­=:t33+Ôê¶ÛûôÓ£ÉpQQKˆŽ~ÄH',ìNššŠøê«™Üÿñ_t]=Ù¤ÕªÑjÕFap€£G×’œÜ=™ÎÊJÆ£ãÂ…ýH$¶Ÿ¦¤ä´Q;;/ÎÛÅ„ +6lѸ»µµŽŽþÔÖàîNRÒWøûààÁuFõtué8sæ{-ÒG~¼¼"—w§žMJúž¹s_5‰*Œ¿˜ï¿7Í@×ööŒs766vÈdxz†"‘Ø\½àeLºÔdûÙØØE|òÉ3”•eãéÜc¹ææ:*™2å~“sC‡N6-p$$ìfÑ¢¸|yWbcï 9ù ffæä奓ŸŸn¤çææËáÃÛ =6v.ÞÞ ç¥R¾¾Á”•å£P8pÇKyøái¼ñÆ  ±±Ž“'ã>|[¶ÿ½8:º÷½àЂC¿˜›»àîþEE±47T:Âè¼FS•ÕÄb‰‘ÜÒÒOÏ7éêR›è[Zú"éõ/ÝêÏÅe1R©þB­®D" êÑ&KËî±B—XYy’™¹w÷%øú>ƒF£_Z'‘øµ`k; ‡qtt” “…õ؆••{¯ÝÌLŽN×n"×éT˜™u?ìˆÅV&‰[,-íP©ô¡Õöölm{nßÚº;ݦJU…¯¡/í³ÀÀû°³ë®ÃÊÊ¡Çú®†TjRSPpŒ´´ohi©ÂÞÞss‰Qߌó0žžÝéRårÓ1`kk;š›+qw§¥¥g瘛ßKoæÍû—á!G*µ7ª£©©Wמ¤BÑ÷±s{{FŽœgÔö¥H$r:;Ûîï­Z­ÂÊÊx>Œ«k€IY‘H„‡Guue½:ôÂÂt ç>Û{­äå¥]u¾¾¾ ww?,-Mïçm·=H``¤A×ÑÑ´.¹ÜŽºº*,Xާ§?Ë–Í`ñâg˜?õõÕ(ØÙ9Ê\¬?&f‚‘\ ïý:!™ãíý5yyC‘JGckÛÚ–H±¶Š\>åg]½\§kF§k5™ /‘„\\2Ò‰@¥º€µµþ‡ÀÚ:ŒææÃÈå1—YÓ…RiØÙù Á±co3`€~‚”›[çÎmcôè‡L@MÍ\\ºh.ŸH§×íîKw÷p¼¼¢ˆŒœnTOGG *U‹á ür‡îáNvöaœýŒäí”—÷mÍ}_ð𦩩 ‡î‡„¦¦ÃûErsñµYèíÏIDATóh$S©ZÉÉIÄË+´×ú}|ôãämH$½Ošý¥øú†‘ž~/¯ž¿“þþh4jæÌ1½ŸÅÅðñé¾V™Ìô~ŠÅbººº—yŽ+ƒ`áÂ1 >_ß ::T̘±¹ÜÖ¨þÊÊbìí{~ظ2¤¸ëˆ¹¹ÞÞ_QV¶˜ÎÎ\ƒÜÉi%G«m2ȺºÔ/¢¶ö=“zäò±ÔÔ¼o$W*O“‘1µZÿäëîþ8ååoÑÖfËϦá’r)@ææ ¬­ƒ oç^^#;{9:]wXW«m!5u55»~¶}&Z­Š¢¢µQWwˆ²²O¯Ø2YÝoŒZm+EEak;è­+¯Óqq™€¥¥=çÏ¿l$¯­=I^Þ Ÿ‡ca¡àÂcç__ŸÂ¾}hk+ënñ²ü}åJc¨NNƒ‘É<9{v­‘¼®îß~;Ž––ž×=[Y)˜2å=ZΙ3ÿáòµîÍÍÅlÝ:›aÃþ†BáwÛ̘;÷kTª#¹§g %%Ç©¯Ï3ÈššŠÉÉÙ‡—׈˫1û"ju+|0…¶6ãõÔ:†ƒ_¥°0ØX}({À€XÚÛ9sæs#ÝüüclØKkk÷„´«EÇÆ®dûö§ioïÞ I«U³e˃üôS÷÷I,6¾7ßü$[·þƒÚÚN§åãÿŠN§¹b›×‚¯ï`víú—‘lïÞõøú2’}ýõÿQYÙÝïZ­†µkïaìØy8:zöZ¿+ãÇÏçÍ7ïA£éŽÞétZ6o~Šää¸_eÿ‚«Ø´éŠŠ²Œä'OîcýúU 8±XÌŽ›t22y衉ÔÕUdW»ŸYYúI¸.¸»ûÒÜÜ€¹¹óæ-åµ×–M€kh¨å¦”tôW]ãÿ*ÂúuF*‡³óß).¾ƒÀÀˆÅÖ(sQ©2ÈÎŽÂÖö6D"hnÞT:7·çz¨EL@À—äåÍ£±q;Ri*UJåI?ÂÂBj–Ɇá뻆ŒŒéØÚŽÁÒÒ‹¦¦ƒØÛߌB1Ð;ÑÌÌù˜›ËHühkËÆÑQ?êãóÙÙ$&ÁÉi&M µµ;qs»w÷{ô–ˆ-‰ŠúžÔÔyTWï@¡Ry­VI`೨TŽöETÔ‡?~[‘Jý¨©ùW׸¹ÍìsŠDfŒõ wRYù#ÎÎchn΢££†ˆˆç©¯ïžá=fÌg=:²²=8:FÓÜœMuõ1FÚ„wŸÛü¥Lžü ûöÝIqñ~ÜÜbhjÊ¥´ô0'nÀÖÖ¿×rÌfþüãìÞ}' /ãå5‡`ò(.Ž'&f£F=Ýkù‹XXØ0þ¾ür†AfoÀ´iëØ¼9†  XYÉÈÈØÊĉ/âàÐûš¥¥Œ‡>ÉîÝ+øç?]ñð‚¯ïh:;[ÉÏ?ŒBáÁÒ¥ ëÓÅb3.ü†O>™Gjê·¸¹ERUuž’’dî¹g 66}ã4è6ÊËϳzuƒ߆H$"=}¾¾ÑÜrË‹½–‹Œ¼™iÓžâÕWÇ:¹Ü‰ŒŒŒ³OÏÈ^Ë]+wÞù2Ï??–§žJ`à0²³O ‘ÈxôÑŒô.|W_½ ŸH$ÒÒâñ÷⡇6^µeËÞáí·—²ti8QQ±XXX’šzŸ0×k¹ÚÚryd¤á³-kÖ?Œ1ûî{ž+b ¯o99)_à­·ôòb±˜W^ù’gž¹ƒ#GvEQQéé'yá…qrêÛFCC Ë—ÏÂÓә̖ÎΆ Ñ/{ì±Wx??†Ñ£§ÒÐPÃO?ífÑ¢Ç;öæ>Õ/`Œ!}꯲U«­E£)C"l$om=ŠDй¹ó%³¢ ioOB§kC*еu¤A_§SÒÑq©4ú’q»NZ[O£Reaaá­íXÌÌlMlÒhªhiI@§kG.‰µu­­©H$¾XXØ¡Õ*ij:ˆHd…ƒÃD"ñe¶f¢TžA$2C¡ˆÁÚ:À¤®®NÑÞ^ˆ\‰B1”ÎÎ:´Ú&ll‚{í/­VIcã)TªrìíG —_rÍjqtmÔ¯juíí%(ƒ.é µµÇQ*s±µ ÇÑ1µº™ÎÎlmÃ.±SM]]"ÍÍ™H$®¸ºŽÅÊÊÎÐfmíiìí#07·éÕæ®®Njj’qseWTÆÃcüeýÞJCC&®®Ã.¹& 55‰Ô×g •ºàá1‰Ä¾Oß-½í稪:K[[%¡xxÄ —{]v¿*ik«ÁÅe`uµ¶V#“¹ÉUªzòò~D¥ª'4t6¶¶î}²I$‚¦¦R**ÎRY™ŽLæ‚›[$>>#¸8CúR]¦ƒââSÔÔdcgçI@ÀMH$Ý›ÒÔÖæaaa…ñ5••¥âè苵µAÞÐPDaa"juÞÞCðòx‰M¨TM¸¹…šØÜØXJnî  ‹FáèèMAAžžaH$6””¤ãàà‰Læ`R¶¥¥š¦¦*||ÉM¿×j ÏRRr?¿Aøù ÆÌÌÜp>// /¯0ÌÌÄdgŸ¢ººŸ‚ƒcŒê9þ0‘‘ãéêÒ’™y‚ÈȱF狊ÎQP†¹¹ÁÁÃpw÷ïÕ®öv%ÙÙIFr "#ÇPXxGGw ÇKú©šÌÌÓ44T4˜ÀÀXXXÕ©Vw’™yšÂÂ,œ=ˆŠ‹LÖ"///D,áîîkT.7÷..úö”ÊFNŠÇÞÞ‰aÃÆ›ØžJVV*R©”ÁƒGâææe8ß~ïûzü£Ú¾4}ªÁ¡ ¶ö“ãå2¡þÇþ`Ãïm«Ð‚­ýÅV!º€€€€€À †àÐn‡. p 8tÁ¡ Ü] _ÐÚšGCÃÕ³†µ¶¶…í–¢RU]w›ª«OÑØ˜i"ïèh¤ `Çuoï÷@§ÓPV–È… »¨­ÍB§ë?Y­t: '()9k´Ë˜JÕL[[£Ñ?•ª¦“ŽŽÖ+Ô§¥½½ù÷0]@ _ l,#Ð/¨ª:@SÓYìí{ÞŽô".¬E& $$d¹A–™¹[Û BB»®6IW—W×Ñ&ç²³?':ºïÙ»úññÏ’˜¸‘9s¶x3VètZJKøî»…å®»¾þÃì;uêd2'þþ÷$D"øàƒù$%}ɘ1‹yí5ýn„ß|³++n¿ýU@¿×Ý=œ“'?cÜ8ã}ðu:-ññëyàMÚ¸QBîýŠ®. UU¸páMjke +/߃R™GSS:¥¥Û®Ÿeù46¦QR¢—µ´äÑØxŽ®. ••9wî òó?C¥ª¹¼5²²6ÑÖVnbGNÎgÝm"ojÊ¡¹9Ÿé45åP_ÎD§µÕ´¾ÚÚ4*+OSZzØäœRiª¯T–QV–@nîN“li*U#q&;FENÎn“z "1ñ]î½7ŽY†ä&b±¾¾7±xñaÊÊ’HKûÊP&;{MÍÍeœ=û)‡¿I~þO=Ö¯ÓiÈÉ9ÀÁƒo˜ø õõÆøš›+(.>MWW¹¹‡‰‹ììx£ýÕ;;Ûps C,6C,6cñâo3æêé3cc—sðà;&òää­H$¶„‡_9UnSS%ÇÎί‘šº­¶{ïtµZEJÊ^::Ú8tèCΜéîߎŽV’’v±cÇ›œ;wˆ®®. Sönø½º@¿A«mãĉ¹ää¬C«mçìÙ夤¬4œ¯¯OD¥ªD©,¤®Nÿ]W×-«­=IWW¥¥Û¹pa=ÞJfæÛhµ* ¿eçÎ!”•í5Ô×ÖVABÂC|kd‡N§!?ÿ 0uè99Ÿ8±ØµZÉþýw×hÚÙ¾}2ZmÇ%Ò.~øa]]Zöîý ­­Æsöï_DMMš‘ìèÑ穬LäÌ™äå;êääwHM5Þ7üܹ/HMý¸Ç~=yòß òžž—gåÓ£Pø0nÜÓ?Þpä»ïîãܹoÙ¼yeeÉ(•ÕlÛö0<‹ŽŽƒ^{{#›7OçË/¢TVSPp”·ßErògÂÂìß¿šÏ>[H\ÜjÔj»w?ǧŸÞgÐ:t'N|DVÖ€>‡ÿ‡½ÖÖ.\øÉH~àÀÛLºâŠe“’¶òÌ3ƒIOßJ¥dçÎ×yñÅÑÔÔÐÒRËþóÞ~û.rsOrÃggŸàÑG‰•ª•íÛßàÍ7ç°}û›$%íê“Ý¿BÈ] ßPTôÆ}ˆ¿ÿý­`ïÞ ‚‚–#—ùjuƒÑúÀ/ÑÑQoC¿èrr63zô'ÞcUTÄqäÈ=Ìš•Œ7R©óæå"“ùÙQV‡LæiŠËœœ/˜8QÆur‚DbOiéA¼¼ôiD³²¶P_ŸÉ… ŸñÀÏ×µkkgÜÝG~éé›9RŸd¤¶ö……?"“­cÚ4}½ííµäåí&6vööHJZGP>/¹VÛIròF¬¬l‰Ž~~ÎV—˜ø7ßüŸûµ´ôC‡^ùm×Ïo<»w/G§Ó ëŽ]ÃÃ' •ê÷ÿž6í5>ÿ|{ö<Éܹ›Ø¶mvvÞ,Y²33}ö³ÆÆbÖ¯¿ W×P¼½õs"22vsë­oû)"Lžü«W‡RXxÿ‘¸º†²xñ·¼ÿþLŸþ“'?yE{/"›3qâ2âãÿChè “¨¨ÈbäHÓ²‹ÔÔðþû‹ùûß÷0Üðùî»ذá^zIŸí«­­‰°°qÌš¥ÏBVY™Ë[oÝΊ_0p þž‹DpèÐGlÜø AAÃûd·€Àoð†.ÐoP(áçw¿á³……NN7¡T^{ÓÝ} ÷É<<¦âãs99Ýo·ry Á]$7÷óßΫ«O£Óuâî>Æ 8p9ééC¾]¤¦®côè×HIYgÐIM]OT”þdРeœ;÷:>´{æÌ:FŒx†¼¼´µégê§¥½Ohè|,-m œASSuuúÙöçχÇp¬­ÉÍÝ@IÉ1D">>=çKW«Û°³ó»bÉånhµtvvÏŸ1c-R©£á³™™óæ}@bâ‡tv¶ÒÚZCZÚw 2ŸÊÊtÊÊR(+K¡­­ž°°;ÖUL¡ðbâÄîh‹¹¹aaS©©éN3>åË÷»v=E{/eܸ%œ?G]]qqë?~iå.rèÐf&LXL@€±¾ãŽ—hl¬ ?¿{ÅŸq ÿOJÚÉÈ‘s‰ŒœdTnÒ¤ED÷ÙfßÁ¡ ôd2Ótž––ö´µõž¢µ7ìíõ(wrAccF¯å4šVŠŠvø“s99Ÿ3`À¸$‡{@ÀjjÎÐÒRDQÑ>¬¬ìˆŽ~±Øœ’’45åQ[›N`àäroÜÜbÈÉùžööòòv0|ø*BCpöìFºº´¤¤¼GtôÅû"†]Fr²>Ï{bâ:bbž &f%§OÿûgÙ; ¾œÞpuDMMï× PTt ;;_$…Aæî>ØDO*uÄÁÁŸššlêêò°´”qúô‡<øºá_|üë´µ5àààg(çààgòà$•ÚÓÐPb$óñÆãåĉ8vÌ8woÈdN ~n ±±œ3g¶1iÒ#W,SQ‘…O”‰\$ãïMUU÷ƒ†LÖýP“——DhhÏNoç,BÈ] ß`ié`"‹-éêÒô }ezsÚMMÈå½ç/,܆‹Ë¤Rã|Ï]]Zrs¿âÖ[^fŸ9ááK9wn#55gˆŠz€¨¨•œ=ûoìíC8ð!#g6dÈrŽ{†ÆÆlÂÂîÆÊÊŽ¡CWðÕWcqt ÁÉ){û ƒþÀ‹Ø¼9?¿ÉˆÅføøŒ´<¸Š¼¼½”–cölÓåu šAbâ""æ!õü Ÿžþ ÁÁ3ŒdUUç 0ν­V·ÑÐP„ƒƒ?ju;b±9÷Ü£ŸLwi(NkÁØØ˜Þ[33K£‰qqs eæÌ9}ú3ÆŽ]br¾'bccÍšXt: C†ÌÂÞÞóŠú^”•ïñ\YYŽŽúñwkk9ffÝ÷ÎßµµE=–«ªÊÇÓ3¸Oö üoèý‘ÈìêJ}¤ªêuu‰F²––< ¿%  {"Û奷p{i騨xààar."b)ÒÔ”G`àíÝEMM YYŸ2pàR#}OÏqtv*INþ7C†è‡] îî#ˆ‹{ˆèhãÉ\VV BBæ°{÷}ÄÄèD"3† {Œ­[ïbðà03³2*séu³ŠÎN%qq«zt ïq€µµ3~~3qvbp\ff– ´ŒææB$G“2C†,§°p/ …¿A½’úú øùM1ÑŽ~ŒÜÜ]„…ÝiEE-&!áU†}ØH7%åCââVòØcyØØ8![0þ¶o¿M›†3pà_pv¥²2…ÜÜ8TªF.Ücn°³ó㣦µ‰DAFÆŠ‹O²ti¼AgîÜM|øáLª«³ Š¥¹¹‚'ÞÅÞÞ—  ãqæÞÉœˆŒ¼…õëo&6ö ÚÛؽûî¿ÿÓ>•¿Hlìr4šNGšœkn®â©§pß}3æ^‚ƒÇ0aÂbþùÏ L˜°w÷`23súôw¬\ù=V=´!,[öÏ>;‚áÃgãììCaa**U ÇÏB.wº&›®'fÀKúÿJ‰žþ·’à ¶ö—£kkäò`ƒì¢\& F*õ0|–Ë •z•·µ B*õD$‚ÚÚ˜™Y1jÔfZZr(/ÿ‘HÄàÁ/àç7×PÆÌÌ•ªoïX[»‘ý!b±%AA÷Õ­Õ¶qäÈRÆ{KKÛ¯ÁÁ!q˜™Y䎎¸¸DcmíЃ~..CJ r…Âw÷áÈå^&mH¥Nx{ÃÖ¶ûºÍÍ%øúNÀÉ)ì2}ÐEHÈmˆÅú œD¢`ðà…H¥öTWŸ#'ç@Dpðtn½õ] O£öŽ[Ã]w}‰»û Š‹OPV–Œ‡Gsçþ'ƒžLæÄ°a ©©É&77ž¶¶z†_È”)ÏÙ$“9ãîaÒŽŽþ8:ú4©ÔŽôô]44”0gÎ"#§›|G\\àìÐãwÈÝ=oïÁ88x™œ73³¤¹¹š¨¨™(®ˆD:_ß(Š‹S((HÂÉɇ|îþ´´”2ƨ.¦Ly++ *•’˜˜ÙÜ~ûßÙ»w=cÆÜ‰““O?ø{êýØll½~Ç7ÞxŽý2Yý_> ÄâÆÿ¹Îlí?ÇËe¿´žÌÌÑÞ^Atôškêƒ}ûn&"â1|}o1’n#+ë}fÌØó»÷Áu|í5W–/OG.wùÓØü{ôoJÊìÞýožxâ;ll†sùùI¼ýö½¼ñÆ)¤Òžúú˱?Ø ØzýŽv455BÈ]@ÀÀôéû{”ûûߎ¿ÿí¿³5ý‘!Cf”´‹çžͰa·âââO^^"gÏîåÿø©Ôö6QàÁ¡ Üp¸»OC§k¿º¢@¯ÜrËHçÔK–¼KQQ*YYǨ¯/gäȹ<ðÀ:$ÙmšÀÿ8‚C¸á°³‹0„£~ƒÝuu¥ÿa|}ãç§_§i¸U@àDX¶& p 8tÁ¡ Ü]@@@@@à@pè7‚C¸º€€€€€À €àÐn‡. p 8tÁ¡ Ü]@@@@@à@pè7‚C¸º€€€€€À €àÐnD@W÷GÅ/«Dt}Œù56tÓìè6@ÿ°C°A°árúƒýÁèvü™mhjjê®C&“u)•Êëd’€€€€€€Àïµµ5âçž{kkë?Ú_€\.gõêÕü?ý`­pë>´IEND®B`‚OpenSTV-1.6.1/openstv/License.html0000777000175400010010000003601611400774167015373 0ustar jeffNone OpenSTV License
		    GNU GENERAL PUBLIC LICENSE
		       Version 2, June 1991

  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.

			    Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

		    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

			    NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

OpenSTV-1.6.1/openstv/LoaderPlugins/0001777000175400010010000000000011407513225015654 5ustar jeffNoneOpenSTV-1.6.1/openstv/LoaderPlugins/BltBallotLoader.py0000777000175400010010000001523311400774166021250 0ustar jeffNone"Plugin module for ERS format ballots." ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: BltBallotLoader.py 719 2010-03-01 03:43:54Z jeff.oneill $" import re from openstv.plugins import LoaderPlugin class BltBallotLoader(LoaderPlugin): "Ballot loader class for ballots defined by ERS." status = 1 extensions = ["blt"] formatName = "ERS" blankLineRE = re.compile(r'^\s*(?:#.*)?$') nCandnSeatsRE = re.compile(r'^\s*(\d+)\s+(\d+)\s*(?:#.*)?$') withdrawnRE = re.compile(r'^\s*(-\d+(?:\s+-\d+)*)\s*(?:#.*)?$') ballotRE = re.compile(r'^\s*(\d+(?:\s+[\d\-=]+)*)\s+0\s*(?:#.*)?$') ballotAndIDRE = re.compile(r'^\s*\(([^\)]+)\)\s+(\d+(?:\s+[\d\-=]+)*)\s+0\s*(?:#.*)?$') endOfBallotsRE = re.compile(r'\s*0\s*(?:#.*)?') stringRE = re.compile(r'^\s*"([^"]+)"\s*(?:#.*)?$') def __init__(self): LoaderPlugin.__init__(self) def loadFromObject(self, ballotList, f): "Load ERS ballot data from a file-like object." if self.hasCustomBallotIDs(f): ballotList.customBallotIDs = True line = self.getNextNonBlankLine(f) (numCandidates, numSeats) = self.getNumCandidatesAndSeats(line) ballotList.numCandidates = numCandidates ballotList.numSeats = numSeats line = self.getNextNonBlankLine(f) withdrawn = self.getWithdrawnCandidates(line) if withdrawn != []: ballotList.withdrawn = withdrawn line = self.getNextNonBlankLine(f) while not self.atEndOfBallots(line): if ballotList.customBallotIDs: (customID, ballot) = self.getBallotWithCustomID(line) ballotList.appendBallot(ballot, customID) else: (weight, ballot) = self.getBallot(line) for i in xrange(weight): ballotList.appendBallot(ballot) line = self.getNextNonBlankLine(f) names = [] for c in range(numCandidates): line = self.getNextNonBlankLine(f) name = self.getCandidateName(line) names.append(name) ballotList.names = names line = self.getNextNonBlankLine(f) ballotList.title = self.getTitle(line) def hasCustomBallotIDs(self, f): self.getNextNonBlankLine(f) # candidates and seats self.getNextNonBlankLine(f) # maybe withdrawn candidates line = self.getNextNonBlankLine(f) # ballot f.seek(0) if self.ballotAndIDRE.match(line) is None: return False else: return True def getNumCandidatesAndSeats(self, line): out = self.nCandnSeatsRE.match(line) if out is None: self.reportLoadError("Cannot process this line:\n\t%s" % line) numCandidates = int(out.group(1)) numSeats = int(out.group(2)) return numCandidates, numSeats def getWithdrawnCandidates(self, line): withdrawnCandidates = [] out = self.withdrawnRE.match(line) if out is not None: x = out.group(1).split() for s in x: withdrawnCandidates.append(-int(s) - 1) return withdrawnCandidates def atEndOfBallots(self, line): return self.endOfBallotsRE.match(line) is not None def getBallot(self, line): out = self.ballotRE.match(line) if out is None: self.reportLoadError("Cannot process this line:\n\t%s" % line) rankings = out.group(1).split() weight = int(rankings.pop(0)) ballot = self.processRankings(rankings) return (weight, ballot) def getBallotWithCustomID(self, line): out = self.ballotAndIDRE.match(line) if out is None: self.reportLoadError("Cannot process this line:\n\t%s" % line) customID = out.group(1) rankings = out.group(2).split() weight = int(rankings.pop(0)) if weight != 1: self.reportLoadError("Cannot process this line:\n\t%s" % line) ballot = self.processRankings(rankings) return (customID, ballot) def processRankings(self, rankings): ballot = [] for item in rankings: if item == "-": ballot.append(-1) elif item.find("=") == -1: c = item ballot.append(int(c) - 1) else: ballot.append([int(c) - 1 for c in item.split("=")]) return ballot def getCandidateName(self, line): out = self.stringRE.match(line) if out is None: self.reportLoadError("Cannot process this line:\n\t%s" % line) name = out.group(1) return name def getTitle(self, line): out = self.stringRE.match(line) if out is None: self.reportLoadError("Cannot process this line:\n\t%s" % line) title = out.group(1) return title def getNextNonBlankLine(self, f): while True: line = f.next() if self.blankLineRE.match(line) is None: break return line def stringifyBallot(self, weight, ballot): "Convert a ballot to a string in the BLT format." line = str(weight) for item in ballot: if isinstance(item, list): # Handle equal rankings ranking = "=".join([str(c+1) for c in item]) line += " " + ranking else: # Single ranking c = item if c == -1: # Skipped ranking line += " -" else: # Just one candidate line += " " + str(c+1) line += " 0" return line def save(self, ballotList, fName=None, packed=False): "Save ballots in ERS format." if fName is not None: self.fName = self.normalizeFileName(fName) f = open(self.fName, "w") f.write("%d %d\n" % (ballotList.numCandidates, ballotList.numSeats)) if ballotList.withdrawn != []: withdrawnList = [str(-(c+1)) for c in ballotList.withdrawn] f.write(" ".join(withdrawnList) + "\n") if packed: for i in xrange(ballotList.numWeightedBallots): weight, ballot = ballotList.getWeightedBallot(i) line = self.stringifyBallot(weight, ballot) f.write(line + "\n") else: for i in xrange(ballotList.numBallots): ballot = ballotList.getBallot(i) line = self.stringifyBallot(1, ballot) if ballotList.customBallotIDs: ballotID = ballotList.getBallotID(i) f.write("(%s) %s\n" % (ballotID, line)) else: f.write(line + "\n") f.write("0\n") # Marker for end of ballot section for name in ballotList.names: f.write('"%s"\n' % name) f.write('"%s"\n' % ballotList.title) f.close() OpenSTV-1.6.1/openstv/LoaderPlugins/DCBallotLoader.py0000777000175400010010000000403511400774166021013 0ustar jeffNone"Plugin module for ERS format ballots." ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## set this first: ballotList.numCandidates = [numCandidates] __revision__ = "$Id: DCBallotLoader.py 693 2010-01-03 19:12:05Z jeff.oneill $" import re from openstv.plugins import LoaderPlugin class DCBallotLoader(LoaderPlugin): "Ballot loader class for DemoChoice ballot files." status = 0 extensions = ["dc"] formatName = "DC" def __init__(self): LoaderPlugin.__init__(self) self.maxCandidateNumber = 0 def loadFromObject(self, ballotList, f): """Load DemoChoice ballot data from a file-like object. For this loader, user must set the number of candidates in the Ballots object before loading the ballots since the ballot file does not have this information. """ assert(ballotList.numCandidates > 0) for line in f.readlines(): ballot = self.getBallot(line) ballotList.appendBallot(ballot) def getBallot(self, line): line = line.strip() z = re.match("[\d,]*$", line) if z is None: self.reportLoadError("Cannot process this line:\n\t%s" % line) rankings = line.split(',') ballot = [int(c) for c in rankings] return ballot def save(self, ballotList, fName=None): "Save ballots in DC format." if fName is not None: self.fName = self.normalizeFileName(fName) f = open(self.fName, "w") for i in xrange(ballotList.numBallots): ballot = ballotList.getBallot(i) line = ",".join(ballot) f.write(line + "\n") f.close() OpenSTV-1.6.1/openstv/LoaderPlugins/TextBallotLoader.py0000777000175400010010000000627411400774166021460 0ustar jeffNone"Plugin module for simple text format ballots." ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: TextBallotLoader.py 719 2010-03-01 03:43:54Z jeff.oneill $" import re from openstv.plugins import LoaderPlugin class TextBallotLoader(LoaderPlugin): "Class for ballots with a simple text format." status = 1 extensions = ["txt", "text"] formatName = "Text" commentRE = re.compile("#.*") def __init__(self): LoaderPlugin.__init__(self) def loadFromObject(self, ballotList, f): "Load text ballot data from a file-like object" # Load ballots in two passes. On the first pass, get all the candidate # names, and on the second pass, add the ballots. The ballots class # needs to know how many candidates there are before we add any ballots. # First pass allNames = set() for line in f.readlines(): line = self.commentRE.sub("", line) # strip comment # get optional weight y = re.match("\s*(\d+)\s*:", line) if y is None: weight = 1 else: weight = int(y.group(1)) line = line[y.end():] ballotNames = self.getBallot(line) for name in ballotNames: allNames.add(name) ballotList.names = allNames # Second pass f.seek(0) for line in f.readlines(): line = self.commentRE.sub("", line) # strip comment # get optional weight y = re.match("\s*(\d+)\s*:", line) if y is None: weight = 1 else: weight = int(y.group(1)) line = line[y.end():] names = self.getBallot(line) for i in xrange(weight): ballotList.appendBallotUsingNames(names) def getBallot(self, line): line = line.strip() if line == "": return [] z = re.match("[\w\s]*$", line) if z is None: self.reportLoadError("Cannot process this line:\n\t%s" % line) else: names = line.split() return names def save(self, ballotList, fName=None, packed=False): "Save text ballots to a file." if fName is not None: self.fName = self.normalizeFileName(fName) f = open(self.fName, "w") if not ballotList.isalnum(): raise RuntimeError, """\ Can't save ballots in text format. The candidates' names must be alphanumeric with no white space.""" if packed: for i in xrange(ballotList.numWeightedBallots): weight, ballot = ballotList.getWeightedBallot(i) b = [ballotList.names[c] for c in ballot] f.write("%d: %s\n" % (weight, ' '.join(b))) else: for i in xrange(ballotList.numBallots): ballot = ballotList.getBallot(i) b = [ballotList.names[c] for c in ballot] f.write("%s\n" % (' '.join(b))) f.close() OpenSTV-1.6.1/openstv/LoaderPlugins/__init__.py0000777000175400010010000000016711400774166020001 0ustar jeffNone# dummy file to make this directory a package __revision__ = "$Id: __init__.py 537 2009-05-16 18:45:21Z jeff.oneill $" OpenSTV-1.6.1/openstv/MethodPlugins/0001777000175400010010000000000011407513225015666 5ustar jeffNoneOpenSTV-1.6.1/openstv/MethodPlugins/Approval.py0000777000175400010010000000351611400774166020041 0ustar jeffNone"Plugin module for Approval voting" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: Approval.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import NonIterative from openstv.plugins import MethodPlugin ################################################################## class Approval(NonIterative, MethodPlugin): "Implement approval voting" methodName = "Approval" longMethodName = "Approval Voting" status = 1 htmlBody = """

With approval voting, a voter can approve of as many candidates as he or she likes, and the candidate approved of by the greatest number of voters is elected. A voter approves of a candidate by listing the candidate on the ballot, and the order of the candidates on the ballot makes no difference.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): NonIterative.__init__(self, b) MethodPlugin.__init__(self) def countBallots(self): "Count the votes using approval voting." # Count the approvals for i in xrange(self.b.numWeightedBallots): weight, blt = self.b.getWeightedBallot(i) for j in blt: self.count[j] += weight self.msg += "Count of all approvals. " # Choose the winners desc = self.chooseWinners() self.msg += desc OpenSTV-1.6.1/openstv/MethodPlugins/Borda.py0000777000175400010010000000673711400774166017314 0ustar jeffNone"Plugin module for Borda" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: Borda.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import NonIterative from openstv.plugins import MethodPlugin ################################################################## class Borda(NonIterative, MethodPlugin): "Borda count" methodName = "Borda" longMethodName = "Borda Count" status = 1 htmlBody = """

With the Borda count, candidates recieve points based on their position on the ballots. For example, if there are 4 candidates, then a candidate receives 3 points for every first choice, 2 points for every second choice, and 1 point for every third choice. A candidate receives no points if ranked last or not ranked at all. Borda provides some proportionality but not as well as SNTV, IRV, or STV.

As an option, the ballots can by "completed" whereby unranked candidates share the remaining points on a ballot. This option prevents one type of strategic voting whereby a voter ranks only his or her first choice candidate.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): NonIterative.__init__(self, b) MethodPlugin.__init__(self) self.ballotCompletion = "Off" self.createGuiOptions(["ballotCompletion"]) def preCount(self): NonIterative.preCount(self) assert(self.ballotCompletion in ["On", "Off"]) if self.ballotCompletion == "On": # The only fractions that will ever appear because of ballot completion # is 1/2 so no more precision is necessary. self.optionsMsg = "Using ballot completion." self.prec = 1 else: self.optionsMsg = "Not using ballot completion." self.prec = 0 self.p = 10**self.prec def countBallots(self): "Count the votes using the Borda Count." # Add up the Borda counts for i in xrange(self.b.numWeightedBallots): weight, blt = self.b.getWeightedBallot(i) # Ranked candidates get their usual Borda score for j, c in enumerate(blt): self.count[c] += self.p * weight * (self.b.numCandidates-j-1) # If doing ballot completion, then unranked candidates share the # remaining Borda score. Otherwise, goes to exhausted pile. if len(blt) < self.b.numCandidates-1: nMissingCand = self.b.numCandidates - len(blt) # missingCandCount = nMissingCand*(nMissingCand-1)/2/nMissingCand # simplifies to missingCandCount = (nMissingCand-1)/2 if self.ballotCompletion == "On": for c in range(self.b.numCandidates): if c not in blt: self.count[c] += self.p * weight * \ (nMissingCand-1) / 2 else: self.exhausted += self.p * weight * \ (nMissingCand-1) * nMissingCand / 2 self.msg += "Borda count totals. " # Choose the winners desc = self.chooseWinners() self.msg += desc OpenSTV-1.6.1/openstv/MethodPlugins/Bucklin.py0000777000175400010010000000667211400774166017652 0ustar jeffNone"Plugin module for Bucklin" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: Bucklin.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import Iterative from openstv.plugins import MethodPlugin ################################################################## class Bucklin(Iterative, MethodPlugin): "Bucklin system" methodName = "Bucklin" longMethodName = "Bucklin System" onlySingleWinner = True threshMethod = False status = 2 htmlBody = """

The Bucklin system is also known as the Grand Junction system (where it was once used) and American preferential voting. The Bucklin system has been used for electing a single candidate and also for electing multiple candidates, but this implementation can only be used to elect one candidate.

In electing a single candidate, a candidate receiving a majority of first choices is declared the winner. If no candidate has a majority of first choices, then a candidate receiving a majority of first and second choices is the winner. If more than one candidate has a majority of first and second choices, then the candidate having the most first and second choices is the winner. This process is repeated for further choices as necessary.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): Iterative.__init__(self, b) MethodPlugin.__init__(self) self.prec = 0 def countBallots(self): "Count the votes with the Bucklin system." # Sequentially use more candidates until we have a winner. for self.R in range(self.b.numCandidates): self.allocateRound() self.roundInfo[self.R]["action"] = ("", []) if self.R == 0: self.msg[self.R] += "Count of first choices. " else: self.msg[self.R] += "No candidate has a majority. "\ "Using %d rankings. " % (self.R+1) self.count[self.R] = self.count[self.R-1][:] self.exhausted[self.R] = self.exhausted[self.R-1] # Count votes using multiple rankings for i in xrange(self.b.numWeightedBallots): weight, blt = self.b.getWeightedBallot(i) if len(blt) > self.R: c = blt[self.R] self.count[self.R][c] += weight else: self.exhausted[self.R] += weight # Check for winners. Could be more than we need. potWinners = [] for c in self.continuing: if 2*self.count[self.R][c] > self.b.numBallots: potWinners.append(c) if len(potWinners) > 0: (c, desc) = self.breakWeakTie(self.R, potWinners, "most", "winner") desc += self.newWinners([c]) self.msg[self.R] += desc break # If no candidate has a majority then a plurality is good enough if len(self.winners) == 0: (c, desc) = self.breakWeakTie(self.R, self.continuing, "most", "winner") desc += self.newWinners([c], "under") self.msg[self.R] += desc OpenSTV-1.6.1/openstv/MethodPlugins/CambridgeSTV.py0000777000175400010010000007225511400774166020535 0ustar jeffNone"Plugin Module for Cambridge STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: CambridgeSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" import os.path import string from openstv.STV import OrderDependentSTV from openstv.plugins import MethodPlugin from openstv.ballots import Ballots ################################################################## class CambridgeSTV(OrderDependentSTV, MethodPlugin): "Cambridge STV" methodName = "Cambridge STV" longMethodName = "Cambridge STV" status = 1 htmlBody = """

The City of Cambridge, Massachusetts has used the single transferable vote to elect its city council and school committee since 1941. The statute providing the counting rules is Chapter 54A of Massachusetts General Laws, the relevant portions of which are included below. Note that § 16(b) allows Cambridge to use any method for transfering surplus votes that was in use in 1938, and Cambridge has chosen to use the Cincinnati method.

The City of Cambridge describes the Cincinnati method as follows: The ballots of the candidate who has a surplus are numbered sequentially in the order in which they have been counted (that is, in the sequence dictated by the random draw of precincts) and then every nth ballot is drawn and transferred to a continuing candidate until the original candidate is credited with ballots equaling no more than quota. n is nearest whole number computed by the formula

n = Candidate's Total Ballots
Surplus Ballots.

A ballot selected by this method that does not show a preference for a continuing candidate is skipped and remains with the original candidate. If not enough ballots are removed when ballots n, 2n, 3n, .... have been transferred, the sequence starts again with n+1, 2n+1, 3n+1, ....

For more information, see http://www.cambridgema.gov/election/Proportional_Representation.cfm.

Since candidates with fewer than 50 votes are eliminated, this method should not be used with a small number of ballots. OpenSTV's implementation of Cambridge STV has been validated against official Cambridge results from 1999 to present.

OpenSTV provides the option of saving the winning candidates' ballots to separate files. The Cambridge rules use these ballots to elect a replacement candidate in the event of a vacancy.


Massachusetts General Laws, Chapter 54A

ELECTION OF CERTAIN CITY AND TOWN OFFICERS BY PROPORTIONAL REPRESENTATION OR PREFERENTIAL VOTING

§ 9. Rules for counting ballots, and determining results. Ballots cast under proportional representation shall be counted and the results determined under the supervision of the director of the count appointed pursuant to section six, according to the following rules:-

(a) The ballots in each ballot receptacle shall be examined for validity and those which are found to be blank or otherwise invalid shall be separated from the valid ballots. The number of valid ballots from each precinct and the total number of valid ballots shall be recorded. If a ballot does not clearly show which candidate the voter prefers to all others, or if it contains any word, mark or other sign apparently intended to identify the voter, it shall be set aside as invalid. Every ballot not thus invalid shall be counted according to the intent of the voter, so far as that can be clearly ascertained, whether marked according to the directions printed on it or not. No ballot shall be held invalid because the names of candidates thereon for whom the voter did not mark a choice have been stricken out, unless such striking out constitutes an identifying mark. A single cross on a ballot on which no figure 1 appears shall be considered equivalent to the figure 1. If a ballot contains both figures and crosses, the order of the choice shown by the figures shall be taken as the voter's intention in so far as the order is clearly indicated. If the consecutive numerical order of the figures on a ballot is broken by the omission of one or more figures, the smallest number marked shall be taken to indicate the voter's first choice, the next smallest his second, and so on, without regard to the figure or figures omitted.

(b) Each candidate shall be credited with one vote for every valid ballot that is sorted to him as first choice, or otherwise credited to him as hereinafter provided, and no ballot shall ever be credited to more than one candidate at the same time.

(c) A "quota" is the smallest number of votes which any candidate must receive in order to be assured of election without more candidates being elected than there are offices to be filled. It shall be determined by dividing the total number of valid ballots by one more than the total number of candidates to be elected and adding one to the result, disregarding fractions. Whenever at any stage of the counting the number of ballots credited to a candidate becomes equal to the quota, he shall be declared elected, and no ballots in excess of the quota shall be credited to him except as provided in rule (f) or (1) of this section.

(d) The ballots shall be sorted according to the first choices marked on them, the ballots from each polling place being handled together, and those from different polling places being handled in the order of polling places determined under the provisions of section eight.

(e) If a candidate is elected while the ballots are being sorted according to first choices, any subsequent ballots which show him as first choice shall each be credited to the second choice marked on it, or, if the second choice also has been elected, to the next choice marked on it for a candidate not yet elected.

(f) If during the first sorting of ballots, ballots are found which are marked for a candidate already elected as first choice, but show no clear choice for any unelected candidate, such ballots shall at the end of the sorting be given to the candidate of their first choice, and in their place an equal number, as nearly as possible, of the last ballots sorted to that candidate which show a clear choice for unelected candidates, all as determined by the director of the count, shall be taken and re-sorted to unelected candidates as if they were then being sorted for the first time.

(g) When all the ballots have been thus sorted and credited to the first available choices marked on them, every candidate who is credited with fewer ballots than the number of signatures required for his nomination shall be declared defeated.

(h) All the ballots of the candidates thus defeated shall be transferred, each to the candidate indicated on it as next choice among the continuing candidates. A "continuing candidate" is a candidate not as yet either elected or defeated. Any ballot taken for transfer which does not clearly indicate any candidate as next choice among the continuing candidates shall be set aside as "exhausted".

(i) When all the ballots of the candidates thus defeated have been transferred, the one candidate who is then lowest on the poll shall be declared defeated and all his ballots transferred in the same way.

(j) Thereupon the candidate who is then lowest shall be declared defeated and all his ballots similarly transferred; and in like manner candidates shall be declared defeated one at a time and all their ballots transferred.

(k) If, when a candidate is to be declared defeated, two or more candidates are tied at the bottom of the poll, that one of the tied candidates shall be declared defeated who was credited with fewest ballots immediately prior to the last transfer of ballots. If two or more of the tied candidates were tied at that stage of the count, also, the second tie shall be decided by referring similarly to the standing of candidates immediately prior to the last transfer of ballots before that. This principle shall be applied successively as many times as may be necessary, a tie shown at any stage of the count being decided by referring to the standing of the tied candidates immediately prior to the last preceding transfer of ballots. Any tie not otherwise provided for shall be decided by lot.

In interpreting this and other rules contained in this section the transfer of all ballots from candidates defeated together under rule (g) of this section, and the transfer of all ballots from each candidate defeated thereafter shall each constitute a single separate transfer.

(l) Whenever candidates to the number to be elected have received the quota, any transfer of ballots in progress when the last quota was reached shall be completed, but immediately thereafter all continuing candidates shall be declared defeated and the election shall be at an end. Whenever all ballots of all defeated candidates have been transferred, and it is impossible to defeat another candidate without reducing the continuing candidates below the number still to be elected, all the continuing candidates shall be declared elected and the election shall be at an end.

(m) A record of the count shall be kept in such form as to show, after each sorting or transfer of ballots, the number thereby credited to each candidate, the number thereby set aside as exhausted, the total for each candidate, the total set aside as exhausted, and the total number of valid ballots found by adding the totals of all candidates and the total set aside as exhausted.

(n) Every ballot that is transferred from one candidate to another shall be stamped or marked so that its entire course from candidate to candidate can be conveniently traced.

(o) If at any time after the first sorting of the ballots a ballot is found to have been credited to the wrong candidate, it may be transferred, as part of the transfer that is in progress, to the continuing candidate, if any, to whom it should have been credited at the time the error was made, or, if it should previously have become exhausted, may be set aside as exhausted as part of the transfer that is in progress; provided, that if the number of misplaced ballots found is sufficient to make it possible that any candidate has been wrongly defeated, so much of the sorting and transferring as may be required to correct the error shall be done over again before the count proceeds.

If in correcting an error any ballots are re-sorted or re-transferred, every ballot shall be made to take the same course that it took in the original count unless the correction of an error requires its taking a different course. The principles of the rules of this section shall apply also to any recount which may be made after the original count has been completed.

(p) The director of the count and his assistants shall proceed with reasonable expedition in the counting of the ballots, but may take recesses at the discretion of the director. The city or town clerk shall make proper provision for the safekeeping of the ballots while the counting is not in progress.

(q) The candidates, their witnesses, alternate witnesses and representatives accredited under section seven, representatives of the press, and, as far as may be consistent with good order and with convenience in the counting and transferring of the ballots, the public shall be afforded every facility for being present and witnessing the counting and transferring of the ballots.

(r) Each of the candidates entitled to appoint witnesses of the central count as provided in section seven shall be entitled to appoint a member of a board of review of the central count. Such appointment shall be made within the time and in the manner prescribed for the appointment of such witnesses of the central count. In the central counting place a board of review so constituted shall be given facilities for examining all the ballots in the quota of each elected candidate in order to make sure that all the ballots of such quota are rightfully credited to the candidate toward whose election they have been counted, that the number of ballots therein is actually equal to the quota prescribed in this section, and that"exhausted" ballots have been properly so designated. Any errors discovered by such a board of review shall be reported to the director of the count.

(s) When the election with respect to any particular body or office is at an end the director of the count shall publicly announce the result of the vote for such body or office. The provisions of section one hundred and seven of chapter fifty-four relative to presiding officers and other election officers at polling places shall, so far as apt, apply to the director of the count and his assistants with respect to all ballots, records, copies of records, envelopes and ballot boxes, transmitted to the central counting place under section eight and to all other papers, records and apparatus used in counting the votes at the central counting place, except that ballots cast for a particular body or office, as well as those spoiled and returned and those not given out, shall be enclosed, and the envelopes sealed and delivered or transmitted to the city or town clerk as soon as may be after the public announcement of the result of the vote for such body or office.

(t) No canvass or count of the vote shall be made on the Lord's day.

§ 10. Ballots; preservation; examination. The ballots cast at each election by proportional representation or preferential voting shall be preserved by the city or town clerk until the term of office of the members of the body or of the officer elected thereby has expired, and shall be available for examination continuously throughout the business day, under supervision of the city or town clerk, on written application signed by not less than one hundred voters of the city or town and the payment of a fee of twenty-five dollars for each day on which such inspection is held. Such application shall name not more than three representatives of the applicants to make such examination.

§ 11. Publication of statements regarding ballots cast. Within thirty days after an election to elect members of a body by proportional representation or an officer by preferential voting, the city or town clerk shall cause the ballots cast for such body or office to be examined and shall publish a statement showing-

(a) The number of first-choice ballots cast for each candidate at each polling place.

(b) The number of ballots from each polling place finally counted for each of the elected candidates.

(c) The number of the exhausted ballots from each polling place which showed one or more choices for elected candidates and the number which did not show any such choice.

(d) The number of blank ballots cast for each body or office at each polling place.

(e) The number of ballots otherwise invalid cast for each body or office at each polling place.

(f) The number of first choices, second choices, third choices, and so on, used in the election of each of the elected candidates.

(g) Such other information in regard to the ballots as the city or town clerk may deem of interest.

A copy of such statement shall be kept on file in the office of the city or town clerk open to public inspection.

§ 12. Recount of ballots. Partial or complete recounts of the ballots cast for any body or office in an election by proportional representation or by preferential voting shall take place in the manner provided in sections one hundred and thirty-four to one hundred and thirty-seven, inclusive, of said chapter fifty-four, except that any petition shall be submitted on or before five o'clock in the afternoon of the third day following the public announcement by the director of the count of the result of the vote for such body or office and shall be on a form approved and furnished by the city or town clerk and be signed in a town by ten or more voters of such town, in a city, except Boston, by fifty or more voters of such city and in Boston by two hundred and fifty or more voters of said Boston and except that any such recount in any city or in any town divided into precincts shall be conducted for the entire city or town instead of for specified precincts. If a partial or complete recount of the ballots cast in such an election shall in fact take place, it shall be conducted according to the rules prescribed for the original count as nearly as is practicable.

§ 13. Vacancies in bodies elected by proportional representation; filling. When a vacancy occurs in an elective body whose members were elected by proportional representation, such vacancy shall be filled for the remainder of the unexpired term by a public recount of the ballots credited at the end of the original count to the candidate elected thereby whose place has become vacant. Except for the following special rules, the provisions governing the original count shall be in effect:

(a) All choices marked for candidates theretofore elected or who have become ineligible or have withdrawn shall be disregarded:

(b) The ballots shall be sorted each to the earliest choice marked on it for any of the eligible candidates.

(c) If any candidate has to his credit more than half of the ballots which show any preference among the eligible candidates he shall be declared elected to the vacant place.

(d) If no candidate receives more than half of such ballots, the candidates lowest on the poll shall be declared defeated one after another and after each candidate is defeated his ballots shall be transferred among the continuing candidates.

(e) The process hereinbefore provided shall be continued until one candidate is credited with more ballots than all the other undefeated candidates together, when he shall be declared elected to the vacant place.

If a vacancy in an elective body occurs for which no regularly nominated candidate remains it shall be filled for the unexpired term by a majority vote of the remaining members; and if but a single member remains or if a majority vote of the remaining members is not obtained within thirty days after the vacancy occurs, it shall be filled by a special election, in the case of a single vacancy, by preferential voting or, in case two or more vacancies exist at the same time, by proportional representation.

§ 14. Ballots; rules for counting where election by preferential voting. Ballots cast under preferential voting shall be counted in the central counting place under the supervision of the director of the count, in accordance with the following rules:-

(a) The ballots shall first be sorted according to the first choices marked on them, and the total number of valid ballots thus sorted to each candidate shall be ascertained. The validity of ballots shall be determined according to the principles laid down for the count of ballots in an election by proportional representation in rule (a) of section nine.

(b) If any candidate is found to have been marked as first choice on more than half of the valid ballots he shall be declared elected.

(c) If no candidate is so elected after the count of first choices, every candidate who is credited with fewer ballots than the number of signatures required for his nomination shall be declared defeated.

(d) All the ballots of the candidates so defeated shall be transferred, each to the candidate indicated on it as next choice among the undefeated candidates. Any ballot taken for transfer which does not clearly indicate any candidate as next choice among the undefeated candidates shall be set aside as "exhausted".

(e) If, after this or any subsequent transfer of ballots, one candidate is credited with more than half of the valid ballots which have not become exhausted, he shall be declared elected.

(f) If no candidate is so elected after the transfer of the ballots of candidates defeated under rule (c), the one candidate who is then lowest on the poll shall be declared defeated and all his ballots transferred in the same way.

(g) Thereupon, if no candidate is yet elected, the candidate who is then lowest shall be declared defeated and all his ballots similarly transferred. Thus candidates shall be deemed defeated one at a time, and all their ballots transferred until some candidate has received the necessary majority of the ballots which have not become exhausted and is accordingly declared elected.

(h) Ties shall be decided, a record of the count kept, errors corrected, recesses taken, and candidates and others permitted to be present according to the principles prescribed for elections by proportional representation in rules (k), (m), (o), (p) and (q) of section nine.

§ 15. Vacancies in single elective offices; filling. All provisions of law from time to time applicable in the case of a vacancy in an elective office shall continue to apply after the filling of such office by preferential voting, except that any election to fill such vacancy shall also be by preferential voting.

§ 16. Mechanical or other voting devices; methods of counting first choices.

(a) In conducting any election by proportional representation or preferential voting, mechanical or other devices may be used, subject, however, to the provisions of sections thirty-two to thirty-nine, inclusive, of chapter fifty-four, if the city council or the town passes a vote providing expressly that such devices shall be used in such election; and said sections, so far as apt, shall be applicable in all respects in case of such devices so used. In case such devices are to be used in any city or town, the city or town clerk may modify the form of ballot, the rotation of names thereon, the directions to voters and other details in respect to the election process; provided, that no change shall be made which will alter or impair the principles of voting or counting the ballots governing elections by proportional representation or preferential voting, as the case may be, but the voter may be limited to not less than fifteen choices for any particular body or office.

(b) In any city or town where elections by proportional representation are to be held, any method of counting the voters' first choices and treating any such choices in excess of the quota, provided for under any system of proportional representation which on January first, nineteen hundred and thirty-eight was in effect for the purpose of municipal elections in any city of the United States, may be substituted for the method of counting such choices set forth in this chapter, if the registrars of voters determine that such substitution is advisable; provided, that they issue regulations embodying the method so substituted and provided, further, that such regulations shall not be effective with respect to any election unless at least thirty days prior thereto copies of such regulations are available for delivery to such of the voters as may request them.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): OrderDependentSTV.__init__(self, b) MethodPlugin.__init__(self) self.batchElimination = "Cutoff" self.batchCutoff = 50 self.threshName = ["Droop", "Static", "Whole"] self.delayedTransfer = "Off" self.saveWinnersBallots = False self.outputDir = None self.createGuiOptions(["saveWinnersBallots"]) def checkMinRequirements(self): "Only attempt to count votes if there are enough candidates and voters." OrderDependentSTV.checkMinRequirements(self) if self.b.numBallots < self.batchCutoff * self.numSeats: raise RuntimeError, """\ Not enough ballots to run an election. Need at least %d ballots but have only %d.""" % ( self.batchCutoff*self.numSeats, self.b.numBallots) def updateCount(self): "Update the vote totals after a transfer of votes." for c in range(self.b.numCandidates): if c in self.winnersEven: # With CambridgeSTV, some winners may have additional votes if a large # number of votes were not transferable (e.g., no next candidate). self.count[self.R][c] = self.thresh[self.R-1] else: self.count[self.R][c] = len(self.votes[c]) def transferSurplusVotesFromCandidate(self, cSurplus): "Transfer surplus votes according to the Cambridge rules." total = self.count[self.R-1][cSurplus] surplus = total - self.thresh[self.R-1] skip = int(round(1.0 * total / surplus)) # decimation factor start = skip - 1 # starting point # compute the order in which ballots will be considered for transfer if surplus == 1: order = range(total) order = order[-1:] + order[:-1] else: order = [] for i in range(start, start+skip): for j in range(i, total, skip): order.append(j) for i in range(start): order.append(i) # transfer the ballots nTransferred = 0 ctng = self.continuing.copy() # candidates who can receive votes # attempt to transfer votes in the precalculated order cSurplusVotes = self.votes[cSurplus][:] # use this to loop over c0's votes for i in order: # i is the ith vote of a candidate bi = cSurplusVotes[i] # bi is the bith ballot # Get the next candidate. # If no next candidate, then the vote is not transferable and # remains with the current candidate. c = self.b.getTopChoiceFromBallot(bi, ctng) if c != None: self.votes[c].append(bi) # If the receiving candidate is now a winner, then that # candidate can no longer receive any more votes. if len(self.votes[c]) >= self.thresh[self.R-1]: ctng.remove(c) self.votes[cSurplus].remove(bi) nTransferred += 1 # Check if the entire surplus has been transferred if nTransferred == surplus: break desc = "Count after transferring surplus votes from %s by using the "\ "Cincinnati method with a skip value of %d. " \ % (self.b.names[cSurplus], skip) return desc def transferVotesFromCandidates(self, elimList): "Eliminate candidate according to the Cambridge rules." # Get rid of candidates without any votes remainingLosers = [c for c in elimList if self.count[self.R-1][c] > 0] eliminationOrder = [c for c in elimList if self.count[self.R-1][c] == 0] eliminationOrder.sort() # Transfer from candidates with fewest votes first ctng = self.continuing.copy() descTie = "" for i in range(len(remainingLosers)): (loser, desc) = self.breakWeakTie(self.R-1, remainingLosers, "fewest", "order of candidate elimination") descTie += desc eliminationOrder.append(loser) remainingLosers.remove(loser) for bi in self.votes[loser]: c = self.b.getTopChoiceFromBallot(bi, ctng) if c != None: self.votes[c].append(bi) # If receiving candidate becomes a winner, then that # candidate can't receive any more votes. if len(self.votes[c]) >= self.thresh[self.R-1]: ctng.remove(c) self.votes[loser] = [] desc = "Count after eliminating %s and transferring votes. " % \ self.b.joinList(eliminationOrder) return desc + descTie def postCount(self): "Save ballots of winners to a file so that vacancies may be filled." OrderDependentSTV.postCount(self) if not self.saveWinnersBallots: return self.msg.append("") self.msg[self.R+1] = "The winning candidates' votes are stored in the "\ "following files:\n" # Stuff used for creating a unique valid filename for each candidate assert(os.path.exists(self.outputDir)) validChars = string.ascii_letters + string.digits # For each candidate save the candidate's ballots to a unique # filename that starts with the ballot file name. for c in self.winners: # Create a unique filename cName = self.b.names[c] cNameNorm = string.join((x for x in cName if x in validChars), "") fName = os.path.join(self.outputDir, cNameNorm + ".blt") i = 1 while os.path.exists(fName): i += 1 fName = os.path.join(self.outputDir, cNameNorm + str(i) + ".blt") self.msg[self.R+1] += " %s -> %s\n" % (cName, fName) # Create a new ballots object for each winner candidateBallots = self.b.copy(False) candidateBallots.withdrawn = self.winners.copy() candidateBallots.numSeats = 1 candidateBallots.title = "%s's ballots from %s" % (cName, self.b.title) # Copy the relevant ballots and save for i in self.votes[c]: ballot, ID = self.b.getBallotAndID(i) candidateBallots.appendBallot(ballot, ID) candidateBallots.saveAs(fName) del candidateBallots OpenSTV-1.6.1/openstv/MethodPlugins/Condorcet.py0000777000175400010010000001656111400774166020201 0ustar jeffNone"Plugin module for Condorcet" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: Condorcet.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import NonIterative from openstv.plugins import MethodPlugin from openstv.MethodPlugins.Borda import Borda from openstv.MethodPlugins.IRV import IRV ################################################################## class Condorcet(NonIterative, MethodPlugin): "Implement Condorcet voting with several options for completion methods" methodName = "Condorcet" longMethodName = "Condorcet Voting" onlySingleWinner = True status = 1 htmlBody = """

Condorcet voting elects the candidate who wins all pairwise elections against the other candidates. In relatively rare situations, a cycle will occur where no Condorcet winner exists (i.e., every candidte is beaten by at least one other candidate). The set of candidates in the cycle is called the Smith set. In this instance, a "completion method" is used to choose the winner from the Smith set. There are three choices for the completion method:

  • Schwartz sequential dropping
  • Instant runoff voting on the Smith set
  • Borda count on the Smith set
""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): NonIterative.__init__(self, b) MethodPlugin.__init__(self) self.completion = "Schwartz Sequential Dropping" self.createGuiOptions(["completionMethod"]) self.e = None self.smithSet = [] self.SSDinfo = "" self.pMat = [] self.dMat = [] def preCount(self): NonIterative.preCount(self) assert(self.completion in ["Schwartz Sequential Dropping", "IRV on Smith Set", "Borda on Smith Set"]) self.optionsMsg = "Using %s for the completion method." % self.completion def computePMat(self): "Compute the pairwise comparison matrix." # Intialize space self.pMat = [] for c in range(self.b.numCandidates): self.pMat.append([0] * self.b.numCandidates) # Compute pMat for i in xrange(self.b.numWeightedBallots): weight, ballot = self.b.getWeightedBallot(i) remainingC = range(self.b.numCandidates) for c in ballot: remainingC.remove(c) for d in remainingC: self.pMat[c][d] += weight def computeSmithSet(self): "Compute the Smith set." dMat = [] for c in range(self.b.numCandidates): dMat.append([0] * self.b.numCandidates) # compute the Smith set # Adapted from code posted by Markus Schulze at # http://groups.yahoo.com/group/election-methods-list/message/6493 self.smithSet = range(self.b.numCandidates) for c in range(self.b.numCandidates): for d in range(self.b.numCandidates): if c != d: if self.pMat[c][d] >= self.pMat[d][c]: dMat[c][d] = True else: dMat[c][d] = False for c in range(self.b.numCandidates): for d in range(self.b.numCandidates): if c != d: for k in range(self.b.numCandidates): if c != k and d != k: if dMat[d][c] and dMat[c][k]: dMat[d][k] = True for c in range(self.b.numCandidates): for d in range(self.b.numCandidates): if c != d: if ( (not dMat[c][d]) and dMat[d][c] and (c in self.smithSet) ): self.smithSet.remove(c) self.smithSet.sort() def SchwartzSequentialDropping(self): "Complete with SSD." # Initialize the defeats matrix: dMat[i][j] gives the magnitude of i's # defeat of j. If i doesn't defeat j, then dMat[i][j] == 0. self.dMat = [] for c in range(self.b.numCandidates): self.dMat.append([0] * self.b.numCandidates) for c in range(self.b.numCandidates): for d in range(self.b.numCandidates): self.dMat[c][d] = self.pMat[c][d] for c in range(self.b.numCandidates): for d in range(c): if self.pMat[c][d] > self.pMat[d][c]: self.dMat[d][c] = 0 if self.pMat[c][d] < self.pMat[d][c]: self.dMat[c][d] = 0 if self.pMat[c][d] == self.pMat[d][c]: self.dMat[c][d] = self.dMat[d][c] = 0 # Determine "beatpath" magnitudes array: dMat[i][j] will be the # maximum beatpath magnitudes array. The i,j entry is the greatest # magnitude of any beatpath from i to j. A beatpath's magnitude is # the magnitude of its weakest defeat. changing = 1 while changing: changing = 0 for c in range(self.b.numCandidates): for d in range(self.b.numCandidates): for k in range(self.b.numCandidates): dmin = min (self.dMat[c][d], self.dMat[d][k]) if self.dMat[c][k] < dmin: self.dMat[c][k] = dmin changing = 1 ctng = range(self.b.numCandidates)[:] for c in ctng[:]: for d in ctng[:]: if self.dMat[d][c] > self.dMat[c][d] and c in ctng: ctng.remove(c) if len(ctng) > 1: ctng.sort() self.SSDinfo = """ Candidates remaining after SSD: %s Tie broken randomly.""" % self.b.joinList(ctng) (c0, desc) = self.breakStrongTie(ctng) else: self.SSDinfo = "" c0 = ctng[0] return c0 def countBallots(self): "Count the votes using Condorcet voting." # self.pMat[i][j]: number of votes ranking candidate i over candidate j self.computePMat() # Even though the Smith Set isn't needed for all completion methods # it provides interesting info, so compute it always. self.computeSmithSet() if len(self.smithSet) == 1: c0 = self.smithSet[0] else: # Do the completion if self.completion == "Schwartz Sequential Dropping": c0 = self.SchwartzSequentialDropping() elif self.completion in ["IRV on Smith Set", "Borda on Smith Set"]: # Copy ballots and get rid of candidates not in Smith set withdrawList = [] for c in range(self.b.numCandidates): if (c not in self.smithSet): withdrawList.append(c) dirtyBallots = self.b.copy() dirtyBallots.withdrawn = withdrawList dirtyBallots.numSeats = 1 cleanBallots = dirtyBallots.getCleanBallots() if self.completion == "IRV on Smith Set": self.e = IRV(cleanBallots) self.e.strongTieBreakMethod = self.strongTieBreakMethod self.e.runElection() elif self.completion == "Borda on Smith Set": self.e = Borda(cleanBallots) self.e.strongTieBreakMethod = self.strongTieBreakMethod self.e.runElection() assert(len(self.e.winners) == 1) # The Smith set is sorted. The winner just determined is the index # of the winner in the Smith set. cIndex = list(self.e.winners)[0] c0 = self.smithSet[cIndex] else: assert(0) self.winners = set([c0]) OpenSTV-1.6.1/openstv/MethodPlugins/Coombs.py0000777000175400010010000000702011400774166017471 0ustar jeffNone"Plugin module for Coombs" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: Coombs.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import NoSurplusSTV from openstv.plugins import MethodPlugin ################################################################## class Coombs(NoSurplusSTV, MethodPlugin): "Coombs" methodName = "Coombs" longMethodName = "Coombs Method" status = 2 htmlBody = """

Coombs is the same as instant runoff except that the candidate receiving the most last-place votes (instead of the fewest first-place votes) is eliminated at each round. If a ballot does not rank all of the candidates, then the unranked candidates share the last-place vote.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): NoSurplusSTV.__init__(self, b) MethodPlugin.__init__(self) self.stopCond = ["N"] self.batchElimination = "None" self.unranked = [] def preCount(self): NoSurplusSTV.preCount(self) # Create data structures for speeding up mostLast() self.unranked = [None] * self.b.numWeightedBallots for i in xrange(self.b.numWeightedBallots): u = [] b = self.b.getWeightedBallot(i)[1] for c in self.continuing: if c in b: continue u.append(c) self.unranked[i] = u def mostLast(self): "Count the number of last-place votes per candidate." desc = "" # Count last place votes per candidate total = [0] * self.b.numCandidates for i in xrange(self.b.numWeightedBallots): nUnranked = len(self.unranked[i]) weight, blt = self.b.getWeightedBallot(i) # If no unranked cands, last place candidate gets the vote if nUnranked == 0: ballot = blt[:] ballot.reverse() for c in ballot: if c in self.continuing: break total[c] += weight # Otherwise unranked cands share the last place vote else: share = 1.0 * weight / nUnranked for c in self.unranked[i]: total[c] += share # Resolve ties ctng = list(self.continuing) ctng.sort(key=lambda a, f=total: -f[a]) c0 = ctng[0] numTied = total.count(total[c0]) if numTied > 1: desc += "Candidates %s were tied when choosing a candidate to "\ "eliminate. " % self.b.joinList(ctng[:numTied]) (c0, desc2) = self.breakStrongTie(ctng[:numTied]) desc += desc2 desc += "Last place votes: " ctng.sort() for c in ctng[:-1]: desc += "%s, %f; " % (self.b.names[c], total[c]) c = ctng[-1] desc += "and %s, %f. " % (self.b.names[c], total[c]) # Update data structures for i in xrange(self.b.numWeightedBallots): if c0 in self.unranked[i]: self.unranked[i].remove(c0) return (c0, desc) def selectCandidatesToEliminate(self): "Choose candidates to eliminate." (c, desc) = self.mostLast() self.newLosers([c]) elimList = [c] return (elimList, desc) OpenSTV-1.6.1/openstv/MethodPlugins/ERS97STV.py0000777000175400010010000007342211400774166017466 0ustar jeffNone"Plugin module for ERS97STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: ERS97STV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import GregorySTV from openstv.plugins import MethodPlugin ################################################################## class ERS97STV(GregorySTV, MethodPlugin): "ERS97 STV" methodName = "ERS97 STV" longMethodName = "ERS97 STV" status = 1 htmlBody = """

The Electoral Reform Society of Great Britian and Ireland has issued its recommended rules for implementing the single transferable vote. The most recent version of its rules issued in 1997 and is commonly referred to as the ERS97 rules. These rules are similar to those used in Northern Ireland and Malta.

The complete set of rules can be found at http://www.electoral-reform.org.uk/votingsystems/stvrules.htm. The relevant portions of these rules are included below.

OpenSTV's implementation of the ERS97 rules has been validated against the eSTV program, which can be found at http://www.estv.co.uk/.


4. GENERAL DESCRIPTION OF THE COUNT

4.1 The count is divided into a number of stages. At the first stage the voting papers are counted to determine the total vote. They are then sorted according to their first preferences, and any papers which are invalid are removed. The total number of valid votes is then found and the quota calculated. Any candidates who have at least a quota of first preference votes are deemed elected at this stage.

4.2 Each subsequent stage of the count is concerned either with the transfer of surplus votes of a candidate whose vote exceeds the quota, or with the exclusion of one or more candidates with the fewest votes.

4.3 This procedure continues until either sufficient candidates have reached the quota to fill all the seats, or there is the same number of candidates left as unfilled seats.

4.4 These rules refer to the various forms published by the Electoral Reform Society. The use of these forms is optional, but where they are used, the various options should be made easier, particularly for those not experienced in conducting STV counts.

4.5 In the rules below, words in bold type indicate that there is a definition in the glossary (section 6).

5. DETAILED INSTRUCTIONS FOR THE COUNT In a public election, it is necessary to include certain formalities, such as unsealing and opening the ballot boxes at the start, checking the number of papers in each and ascertaining that the candidates and their agents are content at the conclusion of each stage. For simplicity these have been omitted from these instructions.

5.1 First stage

5.1.1 Count all the voting papers to determine the total number of votes cast.

5.1.2 Sort the voting papers into first preferences, setting aside any invalid papers. Count the number of invalid papers, and subtract this from the total vote to get the total valid vote.

5.1.3 Check the sorting, and count the papers for each candidate into bundles, inserting a counting slip (green) in each bundle marked with the name of the candidate, the number of papers, and 'first stage'. For very small elections, the use of counting slips may be dispensed with.

5.1.4 Check the counting. Enter on each candidate's vote record form (yellow) the total number of first preference votes.

5.1.5 Copy the candidates' votes from the vote record forms onto a result sheet (white), and check that their total is the same as the total valid vote.

5.1.6 Calculate the quota by dividing the total valid vote by one more than the number of places to be filled. Take the division to two decimal places. If the result is exact that is the quota. Otherwise ignore the remainder, and add 0.01.

5.1.7 Considering each candidate in turn in descending order of their votes, deem elected any candidate whose vote equals or exceeds (a) the quota, or (b) (on very rare occasions, where this is less than the quota), the total active vote, divided by one more than the number of places not yet filled, up to the number of places to be filled, subject to paragraph 5.6.2.

5.1.8 That completes the first stage of the count. Now proceed to section 5.2 below.

5.2 Subsequent stages

5.2.1 Each subsequent stage will involve either the distribution of a surplus, or, if there is no surplus to distribute, the exclusion of one or more candidates.

5.2.2 If one or more candidates have surpluses, the largest of these should now be transferred. However the transfer of a surplus or surpluses is deferred and reconsidered at the next stage, if the total of such surpluses does not exceed either:

(a) The difference between the votes of the two candidates who have the fewest votes, or

(b) The difference between the total of the votes of two or more candidates with the fewest votes who could be excluded under rule 5.2.5, and the vote of the candidate next above.

5.2.3 If one or more candidates have surpluses which have not been deferred, transfer the largest surplus. If the surpluses of two or more candidates are equal, and they have the largest surplus, transfer the surplus of the candidate who had the greatest vote at the first stage or at the earliest point in the count, after the transfer of a batch of papers, where they had unequal votes. If the votes of such candidates have been equal at all such points, the Returning Officer shall decide which surplus to transfer by lot.

5.2.4 The transfer of a surplus constitutes a stage in the count. Details of how to do this are in section 5.3. If, after completing the transfer, there are still any untransferred surpluses, and not all the places have been filled, proceed as in paragraph 5.2.2

5.2.5 If, after all surpluses have been transferred or deferred, one or more places remain to be filled, the candidate or candidates with the fewest votes must be excluded. Exclude as many candidates together as possible, provided that:

(a) Sufficient candidates remain to fill all the remaining places

(b) The total votes of these candidates, together with the total of any deferred surpluses, does not exceed the vote of the candidate next above.

If the votes of two or more candidates are equal, and those candidates have the fewest votes, exclude the candidate who had the fewest votes at the first stage or at the earliest point in the count, after the transfer of a batch of papers, where they had unequal votes. If the votes of such candidates have been equal at all such points the Returning Officer shall decide which candidate to exclude by lot.

5.2.6 Details of how to exclude a candidate are given in section 5.4.

5.2.7 Exclusion of one or more candidates constitutes a stage in the count. If, after completing this, there are any surpluses to transfer, and not all the places have been filled, proceed as in paragraph 5.2.2. Otherwise proceed to exclude further candidates as in paragraph 5.2.5.

5.3 Transfer of a surplus

5.3.1 If a surplus arises at the first stage, select for examination all the papers which the candidate has received.

5.3.2 If a surplus arises at a later stage, because of the transfer of another surplus or the exclusion of a candidate or candidates, select only the last received batch of papers, which gave rise to the surplus.

5.3.3 Examine the selected voting papers and sort them into their next available preferences for continuing candidates. Set aside as non-transferable papers any on which no next available preference is expressed.

5.3.4 Check the sorting, count and bundle the papers now being transferred to each candidate, also any non-transferable papers. Insert a counting slip in each bundle marked with the stage number, the name of the candidate to whom the papers are being transferred, and the number of papers in the bundle.

5.3.5 Count the number of transferable papers and enter the number for each candidate on the vote record forms.

5.3.6 Prepare a surplus form (pink). Copy the number of papers for each candidate from the vote record forms to the surplus form, and check the total.

5.3.7 Calculate the total value of the transferable papers. If this exceeds the surplus, determine the transfer value of each paper by dividing the surplus by the number of transferable papers, to two decimal places, ignoring any remainder. If the total value does not exceed the surplus, the transfer value of each paper is its present value.

5.3.8 Calculate the value to be credited to each candidate by multiplying the transfer value by the number of papers, check the totals, and enter these on the surplus form.

5.3.9 Copy the values to be credited, and the non-transferable difference arising from the neglected remainder, from the surplus form to the vote record forms and to the result sheet.

5.3.10 Add these values to the previous votes for each candidate, and add the non-transferable difference to the previous total of non-transferable votes, entering the figures onto the vote record forms and the result sheet.

5.3.11 Add up the new total number of votes on the result sheet, and check that this still equals the original total valid vote.

5.3.12 Complete the counting slips with the transfer value of each paper, and place the bundles of voting papers for each candidate with those previously received. In a small election, where counting slips are not being used, each ballot paper should be marked with its transfer value.

5.3.13 Considering each continuing candidate in turn in descending order of their votes, deem elected any candidate whose vote now equals or exceeds

(a) the quota, or

(b) the total active vote, divided by one more than the number of places not yet filled, up to the number of places remaining to be filled, subject to paragraph 5.6.2.

5.4 Transfer of the votes of excluded candidates

5.4.1 Take together all the bundles of papers which are currently credited to the candidate or candidates to be excluded, and arrange them in batches in descending order of transfer value. Check that the number and total value of the papers in each batch agrees with the numbers on the vote record forms and the result sheet. Prepare an exclusion form (blue).

5.4.2 First, take the batch of papers with the highest transfer value. Sort them according to the next available preferences for continuing candidates, and set aside as non-transferable papers any on which no next available preference is expressed.

5.4.3 Check the sorting, count and bundle the papers for each candidate and any non-transferable papers. Insert a counting slip in each bundle stating the stage, the name of the candidate to whom the papers are being transferred, the number of papers, and the transfer value of each paper. If counting slips are not being used, the transfer value should be marked on each paper.

5.4.4 Check the counting and enter the number of papers for each candidate and the number of non-transferable papers on the vote record forms.

5.4.5 Copy the number of papers to be transferred to each candidate and the number of non-transferable papers, from the vote record forms onto a column of the exclusion form, and check the total.

5.4.6 Determine the total value of the papers for each candidate and that of the non-transferable papers and check the total.

5.4.7 Copy the total values for each candidate from the exclusion form to the vote record forms, and place the bundles of voting papers for each candidate with those previously received.

5.4.8 If any papers have become non-transferable before any candidate has been deemed elected, recalculate the quota as in paragraph 5.1.6, ignoring the non-transferable vote.

5.4.9 Considering each continuing candidate in turn in descending order of their votes, deem elected any candidate whose vote now equals or exceeds

(a)the quota, or

(b)the total active vote, divided by one more than the number of places not yet filled, up to the number of places remaining to be filled, subject to paragraph 5.6.2.

5.4.10 Ensure that no further papers are given to candidates who are no longer continuing candidates because they have been deemed to be elected after transferring a batch of papers.

5.4.11 As in paragraph 5.4.2 and subsequently, sort and transfer each batch of papers in turn in descending order of transfer value, complete a column of the exclusion form for each batch, and deem candidates to be elected as appropriate.

5.4.12 After all the batches of papers have been transferred, the right hand (totals) column on the exclusion form should be completed and these totals checked against the vote record form(s) of the excluded candidate(s).

5.4.13 Copy the total values to be credited from the exclusion form to the vote record forms and to the result sheet, and add these to the previous totals for each candidate.

5.4.14 Copy the new vote for each candidate from the vote record forms onto the result sheet, and the new non-transferable vote from the exclusion forms onto the result sheet.

5.4.15 Add up the new total vote on the result sheet and check that this agrees with the original total valid vote.

5.5 Completion of the count

5.5.1 If a proposed exclusion of one or more candidates would leave only the same number of continuing candidates as there are places remaining unfilled, all such continuing candidates shall be deemed to be elected.

5.5.2 If, at any point in the count, the number of candidates deemed to be elected is equal to the number of places to be filled, no further transfers of papers are made, and the remaining continuing candidate(s) are formally excluded.

5.5.3 The count is now completed.

5.5.4 Declare elected all those candidates previously deemed to be elected.

5.6 Notes

5.6.1 Calculation of the total active vote may be simplified if the Count Control Form (beige) is used. This form enables the Returning Officer to keep a continuous check on the number of votes which are required for election of a candidate at any point in the count, by deducting the quotas (or actual votes if less) of the candidates deemed elected, and the total of non-transferable votes, from the total valid vote, to give the total active vote.

5.6.2 If, when candidates should be deemed elected under sections 5.1.7, 5.3.13 or 5.4.9, two or more have the same number of votes, and there are not sufficient places left for them all, then the one or more to be deemed elected shall be selected in descending order of votes at the first stage or at the earliest point in the count, after the transfer of a batch of papers, where they had unequal votes. If, however, their votes have been equal at all such points, then none of them shall be deemed elected at that stage.

5.6.3 If a re-count is conducted where a decision has been determined by lot, and the relevant votes are still equal in the recount, the earlier determination shall still hold.

5.6.4 These rules refer to the various forms published by the Electoral Reform Society. The use of these forms is optional, but where they are used, the various options should be made easier.

6. GLOSSARY OF TERMS in alphabetical order

6.1 Batch: a bundle containing all the papers of one value in a transfer.

6.2 Candidate's vote: the value of voting papers credited to a candidate at any point in the count.

6.3 Continuing candidate: a candidate not yet deemed elected or excluded.

6.4 Count Control form (beige): a form designed to be used to keep a continuous note of the total active vote, and hence the vote required for election of a candidate at any point in the count.

6.5 Counting slip (green): a slip inserted with a bundle of voting papers, showing the stage at which the papers are transferred, the name of the candidate to whom they are transferred, the number of papers in the bundle, and the transfer value of each paper.

6.6 Deemed elected: status of a candidate who is elected subject to formal confirmation.

6.7 Exclusion form (blue): a form showing the distribution of batches of papers in descending order of transfer value from one or more excluded candidates to continuing candidates.

6.8 First preference: this is shown by the figure "1" standing alone against only one candidate on a voting paper; or the name or code of a candidate entered on a voting paper as first preference.

6.9 Invalid paper: a voting paper on which no first or only preference is expressed, or on which any first preference is void for uncertainty.

6.10 Next available preference: the next subsequent preference in order, passing over earlier preferences for candidates already deemed elected or excluded. There is no next available preference where the next sequential preference for a continuing candidate is uncertain.

6.11 Non-transferable difference: the difference between the value of a surplus and the total new value of the papers transferred, which arises from ignoring the remainder when calculating the transfer values to two decimal places.

6.12 Non-transferable paper: a voting paper on which no next available preference for a continuing candidate is expressed, or on which any next available preference is void for uncertainty.

6.13 Non-transferable vote: the value credited as non-transferable at any point in the count.

6.14 Quota: the vote which, if attained by as many candidates as there are places to be filled, leaves at most a quota for all other candidates; the total valid vote divided by one more than the number of places to be filled, or a lesser value calculated as in paragraph

5.4.8.

6.15 Result sheet (white): a sheet showing the vote credited to each and every candidate, and the non-transferable vote at successive stages of the count.

6.16 Stage of the count: the determination of the first preference vote for each candidate (first stage) or the transfer of a surplus or the exclusion of a candidate, or two or more candidates at the same time, and the transfer of their votes.

6.17 Subsequent preferences: shown by the figures "2", "3", etc., standing alone against different candidates on a voting paper; or the names or codes of candidates entered in order on a voting paper as second, third, etc., preferences.

6.18 Surplus: the amount by which a candidate's vote exceeds the quota.

6.19 Surplus form (pink): A form showing the calculation of the transfer value and the distribution of transferable papers from a candidate deemed elected to continuing candidates.

6.20 Total active vote: the sum of the votes credited to all continuing candidates, plus any votes awaiting transfer.

6.21 Total valid vote: the total number of valid voting papers.

6.22 Transfer value: the value, being unity or less, at which a voting paper is transferred from an elected or an excluded candidate to a continuing candidate. Where counting slips are not used, it is recommended that this value be marked on each paper at the time of transfer.

6.23 Transferable paper: a voting paper which, having been allocated to a candidate, bears a next available preference for a continuing candidate.

6.24 Valid voting paper: a voting paper on which a first or an only preference is unambiguously expressed.

6.25 Value: the value of a voting paper is unity, or a lower value at which it was last transferred.

6.26 Vote record form (yellow): a form showing the vote credited to any one candidate, or showing the non-transferable vote, at successive stages of the count.

7. CASUAL VACANCIES

No purpose is served by holding a by-election, since a representative so elected would represent the dominant opinion group in the particular multi-member constituency, and not necessarily the opinion group of the vacating member. There are three possibilities:

7.1 The vacancy may be filled by recounting all the original voting papers for the constituency, passing over all preferences for the vacating representative, and for any other candidate who now withdraws. With the provision that no other previously elected representative should be excluded, the count proceeds until a stage when a new representative has been elected. This representative, together with the surviving representatives, will reflect the original wishes of the electorate. This method requires that the original voting papers should be retained under secure conditions.

7.2 The vacancy may be filled by co-option. A person could be co-opted who reflects, as far as possible, the opinion group of the vacating representative. The party, if any, of the vacating representative might be invited to nominate a candidate for the elected body to co-opt. Alternatively, the last formally excluded candidate could be co-opted.

7.3 The vacancy may be left unfilled. When a large number of representatives has been elected together by the Single Transferable Vote, it may be thought that the surviving representatives can adequately represent the electorate until the next election. """ htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): GregorySTV.__init__(self, b) MethodPlugin.__init__(self) self.prec = 2 self.weakTieBreakMethod = "forward" self.threshName = ["ERS97", "Dynamic", "Fractional"] self.delayedTransfer = "On" self.batchElimination = "LosersERS97" self.numStages = 0 def preCount(self): GregorySTV.preCount(self) # Data structures needed only for ERS97 rules. # ERS97 rules contain stages and substages, but each of these is a round. # Example: # R S # --- # 1 1 # 2 2 # 3 # 4 3 # We need to compute all of the rounds but only print the stages. # Round 3 is a substage between stages 2 and 3. Substages occur when # transferring ballots from eliminated candidate and different ballots # have different values. The stage involves transferring all of the # ballots, but each substage involves transferring ballots of a given # value. self.quota = [] self.S = 0 self.stages = [] # Stores rounds for each stage # [ [0] [1] [2] [3 4] [5] ... ] def postCount(self): GregorySTV.postCount(self) self.numStages = self.S+1 def roundToStage(self, r): "Return the stage corresponding to a given round." for s in range(len(self.stages)): if r in self.stages[s]: return s assert(0) def allocateRound(self): "Add quota allocation." GregorySTV.allocateRound(self) self.quota.append(0) # These are copied from the previous round. Depending on the situation, # they will be reused or updated in place. if self.R > 0: self.quota[self.R] = self.quota[self.R-1] self.thresh[self.R] = self.thresh[self.R-1] def updateThresh(self): "Compute the value of the ERS97 winning threshold." assert(self.threshName[0] == "ERS97") assert(self.threshName[1] == "Dynamic") assert(self.threshName[2] == "Fractional") # The quota is recalculated at every round until there is at # least one winner. Afterwards it is just repeated. if len(self.winners) == 0: quota, r = divmod(self.p*self.b.numBallots - self.exhausted[self.R], self.numSeats+1) if r > 0: quota += 1 if self.R == 0: desc = "The initial quota is %s. " % self.displayValue(quota) else: desc = "Since no candidate has been elected, the quota is reduced "\ "to %s. " % self.displayValue(quota) self.roundInfo[self.R]["quota"] = desc else: quota = self.quota[self.R] # The winning threshold changes every round. See ERS97 rules # for an explanation. totalActiveVote = 0 for c in self.continuing | self.losers: totalActiveVote += self.count[self.R][c] for c in self.winnersOver: if self.count[self.R][c] > quota: totalActiveVote += self.count[self.R][c] - quota numSeatsRemaining = self.numSeats - len(self.winners) if numSeatsRemaining > 0: thresh, r = divmod(totalActiveVote, numSeatsRemaining+1) if r > 0: thresh += 1 else: thresh = self.thresh[self.R] self.quota[self.R] = quota self.thresh[self.R] = thresh def updateSurplus(self): "Compute the threshold and surplus for current round." # Update surplus self.surplus[self.R] = 0 for c in self.winnersOver | self.continuing: if self.count[self.R][c] > self.quota[self.R]: self.surplus[self.R] += self.count[self.R][c] - self.quota[self.R] def updateWinners(self): "Find new winning candidates." # ERS97 is a pain because it can happen that there is one more # candidate over thresh than there are spots remaining! # If this happens there will be a tie. # When there are not enough votes, thresh can go to 0.00. # When this happens, every remaining candidate would be a winner # (even those with 0 votes). Require at least 0.01 votes to be a # winner. # count the number of winners if not self.roundInfo[self.R].has_key("winners"): self.roundInfo[self.R]["winners"] = "" potentialWinnersList = [] for c in self.continuing: if self.count[self.R][c] >= max(self.thresh[self.R], 1): potentialWinnersList.append(c) # if there is an extra do tie breaking assert(len(potentialWinnersList) + len(self.winners) <= self.numSeats + 1) if len(potentialWinnersList) + len(self.winners) == self.numSeats + 1: (c, desc) = self.breakWeakTie(self.R, potentialWinnersList, "fewest", "a candidate over threshold to eliminate") potentialWinnersList.remove(c) self.roundInfo[self.R]["winners"] += desc # change status of all winners if len(potentialWinnersList) > 0: potentialWinnersList.sort(key=lambda a, f=self.count[self.R]: -f[a]) self.roundInfo[self.R]["winners"] += self.newWinners(potentialWinnersList) self.updateThresh() self.updateSurplus() # lowered threshold could create new winners if len(self.winners) < self.numSeats: self.updateWinners() def sortVotesByTransferValue(self, cList): "Sort votes according to ERS97 rules." self.votesByTransferValue = {} for loser in cList: for i in self.votes[loser]: v = self.transferValue[i] if v not in self.votesByTransferValue.keys(): self.votesByTransferValue[v] = [] self.votesByTransferValue[v].append(i) self.transferValues = self.votesByTransferValue.keys() self.transferValues.sort(reverse=True) def describeRound(self, nonFinalSubstage=False): if self.roundInfo[self.R]["action"][0] == "first": text = "Count of first choices. " elif self.roundInfo[self.R]["action"][0] == "surplus": text = self.roundInfo[self.R]["surplus"] elif self.roundInfo[self.R]["action"][0] == "eliminate": text = self.roundInfo[self.R]["eliminate"] if self.roundInfo[self.R].has_key("quota"): text += self.roundInfo[self.R]["quota"] if self.roundInfo[self.R].has_key("winners"): text += self.roundInfo[self.R]["winners"] # Explain what will happen in the next round if self.electionOver() or nonFinalSubstage: text += "" elif self.surplus[self.R] == 0: text += "No candidates have surplus votes so candidates will be "\ "eliminated and their votes transferred for the next round. " elif self.delayedTransfer == "On" and len(self.getSureLosers(self.R)) != 0: text += "Candidates have surplus votes, but since "\ "candidates can be safely eliminated, the transfer of surplus "\ "votes will be delayed and candidates will be eliminated and their "\ "votes transferred for the next round." else: text += "Candidates have surplus votes so "\ "surplus votes will be transferred for the next round. " self.msg[self.R] = text def transferVotesFromCandidates(self, elimList): elimList.sort() nTransferValues = len(self.transferValues) if nTransferValues == 0: # This will happen when all eliminated candidates have 0 votes self.updateRound() self.updateWinners() self.roundInfo[self.R]["eliminate"] += \ "Count after eliminating %s. No votes are "\ "transferred since all eliminated candidates "\ "have zero votes. " % self.b.joinList(elimList) self.describeRound() else: for i, v in enumerate(self.transferValues): if i != 0: self.R += 1 self.allocateRound() self.roundInfo[self.R]["eliminate"] = "" self.roundInfo[self.R]["action"] = self.roundInfo[self.R-1]["action"] self.stages[self.S].append(self.R) self.roundInfo[self.R]["eliminate"] += \ "Count after substage %d of %d of eliminating "\ "%s. Transferred votes with value %s. "\ % (i+1, nTransferValues, self.b.joinList(elimList), self.displayValue(v)) self.transferVotesWithValue(v) self.updateRound() self.describeRound(i+1 < nTransferValues) if self.electionOver(): break OpenSTV-1.6.1/openstv/MethodPlugins/FTSTV.py0000777000175400010010000000663011400774166017163 0ustar jeffNone"Plugin module for FTSTV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: FTSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" import string from openstv.STV import WeightedInclusiveSTV from openstv.plugins import MethodPlugin ################################################################## class FTSTV(WeightedInclusiveSTV, MethodPlugin): "Customizable Fractional Transfer STV" methodName = "FTSTV" longMethodName = "Fractional Transfer STV" status = 2 htmlBody = """

Fractional transfer STV (FTSTV) is similar to the rules used in Scotland and is sometimes referred to as the wieighted-inclusive Gregory method (WIGM). This implementation contains several options that allow the user to customize the rules as desired:

  • Precision: The number of digits after the decimal point used in computing vote values. Note that FTSTV is implemented using fixed-point arithmetic.
  • Threshold: The threshold has three parameters. First, whether to use a Droop or a Hare threshold. Nearly all STV rules use a Droop threshold. Second, whether the threshold should decrease as votes are exhausted. Third, whether the threshold should be an integer or a fraction. For a large number of votes, an integer threshold can simplify the count, but for a small number of votes, a fractional threshold is needed to obtain proportionality.
  • Delay Surplus Transfer: Transferring surplus votes is a more complicated operation than transferring votes from eliminated candidates. Some rules delay the transfer of surplus votes where candidates can be safely eliminated (i.e., the candidates are sure to lose regardless of which candidates receive the surplus votes).
  • Candidate Elimination: This option applies to the elimination of candidates. "None" means that candidates are always eliminated one by one. "Zero" means that all candidates with zero votes are eliminated simultaneously. "Losers" means that all losing candidates are eliminated simultaneously. "Batch" means that at the first elimination round all candidates under a specified threshold are eliminated.
  • Batch Cutoff: This is the threshold to apply when Candidate Elimination is set to "Batch".
""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + \ MethodPlugin.htmlEnd def __init__(self, b): WeightedInclusiveSTV.__init__(self, b) MethodPlugin.__init__(self) self.prec = 6 self.threshName = ["Droop", "Static", "Whole"] self.delayedTransfer = "Off" self.batchElimination = "Zero" self.batchCutoff = 0 self.createGuiOptions(["prec", "thresh0", "thresh1", "thresh2", "delayedTransfer", "batchElimination", "batchCutoff"]) def preCount(self): WeightedInclusiveSTV.preCount(self) self.optionsMsg = "Using a %s threshold." % \ string.join(self.threshName, "-") OpenSTV-1.6.1/openstv/MethodPlugins/GPCA2000STV.py0000777000175400010010000000464311400774166017670 0ustar jeffNone"Plugin module for GPCA2000STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: GPCA2000STV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import WeightedInclusiveSTV from openstv.plugins import MethodPlugin ################################################################## class GPCA2000STV(WeightedInclusiveSTV, MethodPlugin): "Green Party of California STV" methodName = "GPCA2000 STV" longMethodName = "Green Party of California STV" status = 2 htmlBody = """

The Green Party of California (GPCA) adopted these rules in 2000. The rules are described in the GPCA bylaws, available at http://cagreens.org/bylaws/. The rules are based on the description of STV found in Electoral System Design: The New International IDEA Handbook (http://www.idea.int/publications/esd/index.cfm), except that GPCA uses a fractional threshold, and does not elect candidates that do not reach the a full (static) threshold.

Neither IDEA nor the GPCA bylaws specify a method of breaking ties. This implementation breaks all ties randomly. See Jonathan Lundell's paper Random tie-breaking in STV, available at http://www.votingmatters.org.uk/ISSUE22/I22P1.pdf, for the rationale.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): WeightedInclusiveSTV.__init__(self, b) MethodPlugin.__init__(self) self.prec = 5 self.weakTieBreakMethod = "strong" # treat all ties as strong self.threshName = ["Droop", "Static", "Fractional"] self.delayedTransfer = "Off" self.batchElimination = "Zero" # Election is over when we have enough winners or all candidates # have been eliminated. self.stopCond = ["Know Winners", "Continuing Empty"] def updateCandidateStatus(self): "Update candidate status at end of election." self.newLosers(list(self.continuing)) OpenSTV-1.6.1/openstv/MethodPlugins/IRV.py0000777000175400010010000000364211400774166016715 0ustar jeffNone"Plugin module for IRV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: IRV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import NoSurplusSTV from openstv.plugins import MethodPlugin ################################################################## class IRV(NoSurplusSTV, MethodPlugin): "Instant Runoff Voting" methodName = "IRV" longMethodName = "Instant Runoff Voting" status = 1 htmlBody = """

Instant runoff voting (IRV) is more commonly used to elect one candidate but can also be used to provide semi-proportional representation. Ballots are first distributed according to their first choices. The candidate with the fewest number of ballots is eliminated and the ballots are transferred to their next choices. This process is repeated until the winners are determined.

Instant runoff voting is used by the following cities:

  • San Francisco, CA: http://www.sfgov.org/site/election_index.asp?id=27564
  • Burlington, VT: http://www.leg.state.vt.us/statutes/fullsection.cfm?Title=24APPENDIX&Chapter=003&Section=00005
  • Takoma Park, MD: http://www.fairvote.org/media/irv/states/takoma-park-charter-amend.pdf
""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): NoSurplusSTV.__init__(self, b) MethodPlugin.__init__(self) self.batchElimination = "Zero" OpenSTV-1.6.1/openstv/MethodPlugins/MeekNZSTV.py0000777000175400010010000002673511400774166020013 0ustar jeffNone"Plugin module for New Zealand Meek STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: MeekSTV.py 537 2009-05-16 18:45:21Z jeff.oneill $" from openstv.STV import RecursiveSTV from openstv.MethodPlugins.MeekSTV import MeekSTV from openstv.plugins import MethodPlugin ################################################################## # Meek STV per New Zealand rules # # Clauses handled in STV.py: # 7 (initial keep factor): OK # RecursiveSTV.initializeTreeAndKeepFactors() sets all to 1, # which will be correct when winners are determined # 17 (updated keep factors): OK: RecursiveSTV.updateKeepFactors() # 10 (allocate votes): OK? # MeekSTV.treeCount(): result is always exact(?) # 11 (quota): OK: STV.updateThresh() # 13 (threshold of exclusion): via surplusLimit # # A cautionary note: # The New Zealand "STV Calculator", which does the actual counting, # does not conform to the published specification linked below # (Schedule 1A). This implementation attempts to follow the STV # Calculator where it differs from Schedule 1A; however, without # access to the Calculator, it's impossible to guarantee that it # matches in all respects. # class MeekNZSTV(MeekSTV, MethodPlugin): "MeekNZ STV" methodName = "MeekNZ STV" longMethodName = "New Zealand Meek STV" status = 2 htmlBody = """

This variation on Meek STV conforms to the New Zealand method as described here: http://www.legislation.govt.nz/regulation/public/2001/0145/latest/DLM57125.html. In particular, it uses nine digits of precision and employs its own random number generator for breaking ties.

This method has not been verified, and may not be fully compliant with the New Zealand rules.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): # Note: we're deliberate bypassing MeekSTV.__init__ here # because we don't want any UI options RecursiveSTV.__init__(self, b) MethodPlugin.__init__(self) self.weakTieBreakMethod = "forward" # per clauses 19, 34 & 40 self.prec = 9 # per clause 5 self.prng_cands = {} class NZprng(object): "NZ PRNG per clauses 40-48" def __init__(self, c=None, n=None, v=None): "initialize PRNG per clause 42" self.x = c + 5 self.y = n # 10000 per David Hill; clause 42 has 1000 self.z = (v+10000*(v % 10)) % 30323 # prime PRNG per clause 44 self.next() self.next() self.next() self.next() def next(self): "generate next random number per clause 43" self.x = (171*self.x) % 30269 self.y = (172*self.y) % 30307 self.z = (170*self.z) % 30323 # the final mod 10000 per David Hill (clause 43 omits this) rc = ((10000*self.x)//30269 + (10000*self.y)//30307 + (10000*self.z)//30323) % 10000 # start with an inversion per David Hill (clause 48 seens to suggest otherwise) rc = 10000 - rc return rc def preCount(self): "MeekNZ STV: prepare for count" MeekSTV.preCount(self) self.strongTieBreakMethod = "random" # documentation only; we override breakStrongTie() self.surplusLimit = self.p / 10000 # 0.0001 per clause 13 # initialize PRNG per clause 42 prng = MeekNZSTV.NZprng(c=self.b.numCandidates, n=self.numSeats, v=self.b.numBallots) for c in self.continuing: rc = prng.next() while rc in self.prng_cands.values(): rc = prng.next() self.prng_cands[c] = rc # NOTE # This is a variation on MeekSTV.treeCount that rounds up # keep factors per NZ clause 10 # def updateCount(self, tree=None, remainder=None): "Traverse the tree to count the ballots." if tree is None: tree = self.tree if remainder is None: remainder = self.p # Iterate over the next candidates on the ballots for c in tree.keys(): if c == "n" or c == "bi": continue rrr = remainder # # allocate votes for this ballot # # provisional: three methods for comparison # method = "hill" if method == "hill": # this appears to produce results consistent with David Hill's implementation # and (presumably) the NZ STV Calculator keep, rem = divmod(rrr * self.keepFactor[self.R][c], self.p) if rem > 0: keep += 1 # round up per clause 10 self.count[self.R][c] += keep * tree[c]["n"] # times ballot count rrr -= keep elif method == "nz": # this is the method according to NZ Schedule 1A clause 10 keep, rem = divmod(rrr * self.keepFactor[self.R][c], self.p) if rem > 0: keep += 1 # round up per clause 10 self.count[self.R][c] += keep * tree[c]["n"] # times ballot count rrr, rem = divmod(rrr * (1 - self.keepFactor[self.R][c]), self.p) if rem > 0: rrr += 1 # round up per clause 10 else: # this is the method used by MeekSTV.py self.count[self.R][c] += rrr * self.keepFactor[self.R][c] * tree[c]["n"] / self.p rrr = rrr * (self.p - self.keepFactor[self.R][c]) / self.p # If ballot not used up, keep going if rrr > 0: self.updateCount(tree[c], rrr) def inInfiniteLoop(self): "Detect hangs by looking for at keep factor changes" if MeekSTV.inInfiniteLoop(self): return True if (self.R > 1): for kf1, kf2 in zip(self.keepFactor[self.R-2], self.keepFactor[self.R-1]): if kf2 > kf1: # if any keep factor increased, we're in a loop return True return False def getSureLoser(self, ctng): "Return one sure loser (if one exists)." R = self.R - 1 ctng.sort(key=lambda a, f=self.count[R]: f[a]) if len(ctng) + len(self.winners) > self.numSeats: if (self.count[R][ctng[0]] + self.surplus[R]) < self.count[R][ctng[1]]: return ctng[0:1] return [] def selectCandidatesToEliminate(self): "Eliminate losing candidates." # The NZ STV Calculator does multiple eliminations, though Schedule 1A does not. # # If total surplus < 0.00001, eliminate all candidates with zero votes. # Then perform normal elimination of one more loser. desc = "" # If surplus is below limit, eliminate all candidates with zero votes losers = [] ctng = list(self.continuing) if self.surplus[self.R-1] < self.surplusLimit: for c in self.continuing: if (self.count[self.R-1][c] == 0): losers.append(c) ctng.remove(c) # Eliminate one sure loser regardless of surplus limit if losers == []: losers = self.getSureLoser(ctng) # If no other losers and surplus less than limit, find lowest candidate if losers == [] and self.surplus[self.R-1] < self.surplusLimit: (c, desc) = self.breakWeakTie(self.R-1, ctng, "fewest", "candidates to eliminate") losers = [c] # Special case to prevent infinite loops caused by fixed precision # This is different from the Hill stable-state logic, but consistent with the # rather vague Schedule 1A clause 23 language. if losers == [] and self.inInfiniteLoop(): desc = "Candidates tied within precision of computations. " (c, desc2) = self.breakWeakTie(self.R-1, self.continuing, "fewest", "candidates to eliminate") losers = [c] desc += desc2 self.newLosers(losers) return losers, desc def eliminateCandidates(self): "Find candidate(s) to eliminate and transfer their votes" (losers, descChoose) = self.selectCandidatesToEliminate() if losers != []: self.roundInfo[self.R]["action"] = ("eliminate", losers) descTrans = self.transferVotesFromCandidates(losers) self.roundInfo[self.R]["eliminate"] = descTrans + descChoose def breakStrongTie(self, tiedC, what=""): "Break strong tie between candidates using Meek NZ PRNG per clauses 19 & 34" assert(len(tiedC) >= 1) # If we have the right number, then return all. if len(tiedC) == 1: return (tiedC[0], None) # Sort all the candidates by PRN and then return the first tied candidate from the resulting list # Note that round number R starts at 1, and we reverse on even rounds for c in sorted(self.prng_cands.keys(), key=lambda k: self.prng_cands[k], reverse=(self.R & 1)==0): if c in tiedC: desc = "Candidate %s was chosen by breaking the tie randomly. " % \ self.b.names[c] return (c, desc) raise RuntimeError, "Internal error breaking strong tie." # Differs from MeekSTV in that it rounds up the intermediate # keep factor calculation. # def updateKeepFactors(self): "Udpate the candidate keep factors." if len(self.winners) != 0: desc = "Keep factors of candidates who have exceeded the threshold: " winners = [] else: desc = "" candidateList = list(self.continuing | self.winners) candidateList.sort() for c in candidateList: if self.count[self.R-1][c] > self.thresh[self.R-1]: # for testing, support both NZ and OpenSTV rounding method = "nz" if method == "nz": kf, rem = divmod(self.keepFactor[self.R-1][c] * self.thresh[self.R-1], self.p) if rem > 0: kf += 1 kf, rem = divmod(kf * self.p, self.count[self.R-1][c]) if rem > 0: kf += 1 else: kf, rem = divmod(self.keepFactor[self.R-1][c] * self.thresh[self.R-1], self.count[self.R-1][c]) if rem > 0: kf += 1 self.keepFactor[self.R][c] = kf winners.append("%s, %s" % (self.b.names[c], self.displayValue(self.keepFactor[self.R][c]))) else: self.keepFactor[self.R][c] = self.keepFactor[self.R-1][c] if len(self.winners) != 0: desc += self.b.joinList(winners, convert="none") + ". " return desc def describeRound(self): "Update message for this round" if self.roundInfo[self.R]["action"][0] == "first": text = "Count of first choices. " elif self.roundInfo[self.R].has_key("surplus") and \ self.roundInfo[self.R].has_key("eliminate"): text = self.roundInfo[self.R]["eliminate"] + \ self.roundInfo[self.R]["surplus"] elif self.roundInfo[self.R].has_key("surplus"): text = self.roundInfo[self.R]["surplus"] elif self.roundInfo[self.R].has_key("eliminate"): text = self.roundInfo[self.R]["eliminate"] if self.roundInfo[self.R].has_key("winners"): text += self.roundInfo[self.R]["winners"] self.msg[self.R] = text def countBallots(self): "Count the ballots using NZ Meek STV." # Count first place votes self.allocateRound() self.initialVoteTally() self.updateRound() self.describeRound() while (not self.electionOver()): self.R += 1 self.allocateRound() self.eliminateCandidates() self.transferSurplusVotes() self.updateRound() self.describeRound() self.updateCandidateStatus() OpenSTV-1.6.1/openstv/MethodPlugins/MeekQXSTV.py0000777000175400010010000000307111400774166020000 0ustar jeffNone"Plugin module for Meek Quasi-Exact STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: MeekQXSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.qx import RecursiveQXSTV from openstv.MethodPlugins.MeekSTV import MeekSTV from openstv.plugins import MethodPlugin ################################################################## class MeekQXSTV(RecursiveQXSTV, MeekSTV, MethodPlugin): "Meek STV with guard bits and quasi-exact rounding" methodName = "MeekQX STV" longMethodName = "MeekQX STV" status = 2 htmlBody = """

Jonathan Lundell's "quasi-exact" implementation of Meek STV. See David Hill and Jonathan Lundell's paper Notes on the Droop quota, available at http://www.votingmatters.org.uk/ISSUE24/I24P2.pdf, for a brief description of quasi-exact arithmetic.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): RecursiveQXSTV.__init__(self, b) MethodPlugin.__init__(self) self.createGuiOptions(["prec"]) OpenSTV-1.6.1/openstv/MethodPlugins/MeekSTV.py0000777000175400010010000000440211400774166017526 0ustar jeffNone"Plugin module for Meek STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: MeekSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import RecursiveSTV from openstv.plugins import MethodPlugin ################################################################## class MeekSTV(RecursiveSTV, MethodPlugin): "Meek STV" methodName = "Meek STV" longMethodName = "Meek STV" status = 1 htmlBody = """

Meek STV provide more accurate proportional representation than other STV methods, but the count must be done with a computer and cannot be done by hand. There are two main differences between Meek STV and other STV methods: (1) winning candidates still receive vote transfers and (2) when a candidate is eliminated, the votes are transferred as if the candidate never entered the election.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): RecursiveSTV.__init__(self, b) MethodPlugin.__init__(self) self.createGuiOptions(["prec", "thresh0", "thresh1", "thresh2"]) def updateCount(self, tree=None, remainder=None): "Traverse the tree to count the ballots." if tree is None: tree = self.tree if remainder is None: remainder = self.p # Iterate over the next candidates on the ballots count = self.count[self.R] keepFactor = self.keepFactor[self.R] p = self.p updateCount = self.updateCount for c in tree: if c == "n" or c == "bi": continue rrr = remainder count[c] += rrr * keepFactor[c] * tree[c]["n"] / p rrr = rrr * (self.p - keepFactor[c]) / p # If ballot not used up, keep going if rrr > 0: updateCount(tree[c], rrr) OpenSTV-1.6.1/openstv/MethodPlugins/MinneapolisSTV.py0000777000175400010010000000644111400774166021130 0ustar jeffNone"Plugin module for Minneapolis STV" ## Copyright 2009-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: MinneapolisSTV.py 718 2010-02-28 21:02:44Z jlundell $" from openstv.STV import WeightedInclusiveSTV from openstv.plugins import MethodPlugin ################################################################## class MinneapolisSTV(WeightedInclusiveSTV, MethodPlugin): "Minneapolis STV" methodName = "Minneapolis STV" longMethodName = "Minneapolis STV" status = 0 htmlBody = """

Minneapolis enacted these rules for certain local elections in 2009. The current ordinance, as amended 2009-10, is available here: http://www.ci.minneapolis.mn.us/council/2009-meetings/20091002/docs/Ordinance-Ch167.pdf. This implementation has not been verified.

The referenced ordinance has an internal conflict:

Mathematically impossible to be elected means either: (1) the candidate could never win because his or her current vote total plus all votes that could possibly be transferred to him or her in future rounds (from candidates with fewer votes, tied candidates, and surplus votes) would not be enough to surpass the candidate with the next higher current vote total; or (2) the candidate has a lower current vote total than a candidate who is described by (1).

Contrary to the text, mathematical impossibility requires the phrase "enough to surpass" to read instead "enough to tie or surpass". This implementation implements the latter condition, consistent with mathematical impossibility.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): WeightedInclusiveSTV.__init__(self, b) MethodPlugin.__init__(self) self.prec = 4 self.threshName = ["Droop", "Static", "Whole"] self.delayedTransfer = "On" self.batchElimination = "Losers" self.weakTieBreakMethod = "strong" def transferSurplusVotesFromCandidate(self, cSurplus): "Transfer the surplus votes of one candidate." # Transfer all of the votes at a fraction of their value surplus = self.count[self.R-1][cSurplus] - self.thresh[self.R-1] # Calculate surplus fraction to specified precision surplusFraction = (surplus * self.p)/self.count[self.R-1][cSurplus] for i in self.votes[cSurplus][:]: self.transferValue[i] = self.transferValue[i] * surplusFraction / self.p c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.votes[cSurplus] = [] desc = "Count after transferring surplus votes from %s with a transfer "\ "value of %s/%s. " % \ (self.b.names[cSurplus], self.displayValue(surplus), self.displayValue(self.count[self.R-1][cSurplus])) return desc OpenSTV-1.6.1/openstv/MethodPlugins/NIrelandSTV.py0000777000175400010010000004666511400774166020362 0ustar jeffNone"Plugin module for N. Ireland STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: NIrelandSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import GregorySTV from openstv.plugins import MethodPlugin ################################################################## class NIrelandSTV(GregorySTV, MethodPlugin): "N. Ireland STV" methodName = "N. Ireland STV" longMethodName = "N. Ireland STV" status = 1 htmlBody = """

The STV rules used for local elections in Northern Ireland are similar to the ERS97 rules, but significantly simpler.

The Local Elections (Northern Ireland) Order 1985. Statutory Instrument 1985 No. 454. Not available online.


STATUTORY INSTRUMENTS

1985 No. 454

NORTHERN IRELAND

The Local Elections (Northern Ireland) Order 1985

PART IV — Counting Of Votes

Interpretation

41. In this Part of these rules—

"continuing candidate" means any candidate not deemed to be elected and not excluded;

"count" means all the operations involved in the counting of the first preferences recorded for candidates, the transfer of the surpluses of elected candidates, and the transfer of the vote, of excluded candidates;

"deemed to be elected" means deemed to be elected for the purpose of the counting of the votes but without prejudice to the declaration of the result of the poll;

"mark" means a figure a word written in the English language or a mark such as "X";

"non-transferable vote" means a ballot paper—

(a) on which no second or subsequent preference is recorded for a continuing candidate, or

(b) which is excluded by the returning officer under rule 50(4);

"preference" as used in the following contexts has the meaning assigned below:—

(a) "first preference" means the figure "1" or any mark or word which clearly indicates a first (or only) preference;

(b) "next available preference" means a preference which is the second or, as the case may be, subsequent preference recorded in consecutive order for a continuing candidate (any candidate who is deemed to be elected or is excluded thereby being ignored), and

(c) in this context, a "second preference" is shown by the figure "2" or any mark or word which clearly indicates a second preference, a third preference by the figure "3" or any mark or word which clearly indicates a third preference, arid so on;

"quota" means the number calculated in accordance with rule 48;

"surplus" means the number of votes by which the total number of votes for any candidate (whether first preference or transferred votes, or a combination of both) exceeds the quota; but references in these rules to the transfer of the surplus means the transfer (at a transfer value) of all transferable papers from the candidate who has the surplus;

"stage of the count" means—

(a) the determination of the first preference vote for each candidate;

(b) the transfer of a surplus of a candidate deemed to be elected; or

(c) the exclusion of one or more candidates at any given time;

"transferable paper" means a ballot paper on which, following a first preference, a second or subsequent preference is recorded in consecutive numerical order for a continuing candidate;

"transferred vote" means a vote derived from a ballot paper on which a second or subsequent preference is recorded for the candidate to whom that paper has been transferred;

"transfer value" means the value of a transferred vote calculated in accordance with paragraph (4) or (7) of rule 49, as the case may be.

* * *

Rejected ballot papers

46.— (1) Any ballot paper—

(a) which does not bear the official mark; or

(b) on which the figure 1 standing alone is not placed so as to indicate a first preference for some candidate; or

(c) on which the figure 1 standing alone indicating a first preference is set opposite the name of more than one candidate; or

(d) on which anything (other than the printed number on the back) is written or marked by which the voter can be identified; or

(e) which is unmarked or void for uncertainty,

shall be void and not counted, but the ballot paper shall not be void by reason only of carrying the words "one", "two", "three", (and so on) or any other mark instead of a figure if, in the opinion of the returning officer, the word or mark clearly indicates a preference or preferences.

(2) The returning officer shall endorse "rejected" on any ballot paper which under this rule is not to be counted and if an election agent objects to his decision shall add to the endorsement the words "rejection objected to".

(3) The returning officer shall prepare a statement showing the number of ballot papers rejected by him under each of sub-paragraphs (a), (b), (c), (d), and (e) of paragraph (I) and shall, on request, allow any candidate or agent of a candidate to copy such statement.

(4) The decision of the returning officer on any question arising in respect of a ballot paper shall be final but shall be subject to review on an election petition.

First stage

47.— (1) The returning officer shall sort the ballot papers into parcels according to the candidates for whom first preference votes are given.

(2) The returning officer shall then count the number of first preference votes given on ballot papers for each candidate and shall record those numbers.

(3) The returning officer shall also ascertain and record the number of valid ballot papers.

The quota

48.— (1) The returning officer shall divide the number of valid ballot papers by a number exceeding by one the number of members to be elected.

(2) The result, increased by one, of the division under paragraph (1) (any fraction being disregarded) shall be the number of votes sufficient to secure the election of a candidate (in these rules referred to as "the quota").

(3) At any stage of the count a candidate whose total vote equals or exceeds the quota shall be deemed to be elected, except that at an election where there is only one vacancy a candidate shall not be deemed to be elected until the procedure set out in paragraphs (1) to (3) of rule 51 has been complied with.

Transfer of votes

49.— (1) Where the number of first preference votes for any candidate exceeds the quota, the returning officer shall sort all the ballot papers on which first preference votes are given for that candidate into sub-parcels so that they are grouped—

(a) according to the next available preference given on those papers for any continuing candidate, or

(b) where no such preference is given, as the sub-parcel of non-transferable votes.

(2) The returning officer shall count the number of ballot papers in each parcel referred to in paragraph (1)

(3) The returning officer shall, in accordance with this rule and rule 50, transfer each sub-parcel of ballot papers referred to in sub-paragraph (a) of paragraph (1) to the candidate for whom the next available preference is given on those papers.

(4) The vote on each ballot paper transferred under paragraph (3) shall be at a value ("the transfer value") which—

(a) reduces the value of each vote transferred so that the total value of all such votes does not exceed the surplus, and

(b) is calculated by dividing the surplus of the candidate from whom the votes arc being transferred by the total number of the ballot papers on which those votes an given, the calculation being made to two decimal places (ignoring the remainder if any).

(5) Where, at the cad of any stage of the count involving the transfer of ballot papers, the number of votes for any candidate exceeds the quota, the returning officer shall sort the ballot papers in the sub-parcel of transferred votes which was last received by that candidate into separate sub-parcels so that they are grouped—

(a) according to the next available preference given on those papers for any continuing candidate, or

(b) where no such preference is given, as the sub-parcel of non-transferable votes.

(6) The returning officer shall, in accordance with this rule and and rule 50, transfer each sub-parcel of ballot papers referred to in sub-paragraph (a) of paragraph (5) to the candidate for whom the next available preference is given on those papers.

(7) The vote on each ballot paper transferred under paragraph (6) shall be at—

(a) a transfer value calculated as set out in sub-paragraph (b) of paragraph (4), or

(b) at the value at which that vote was received by the candidate from whom it is now being transferred,

whichever is the less.

(8) Each transfer of a surplus constitutes a stage in the count.

(9) Subject to paragraph (10), the returning officer shall proceed to transfer transferable papers until no candidate who is deemed to be elected has a surplus or all of the vacancies have been filled.

(10) Transferable papers shall not be liable to be transferred where any surplus or surpluses which, at a particular stage of the count, have not already been transferred, are

(a) less than the difference between the total vote then credited to the continuing candidate with the lowest recorded vote and the vote of the candidate with the next lowest recorded vote; or

(b) less than the difference between the total votes of the two or more continuing candidates, credited at that stage of the count with the lowest recorded total numbers of votes and the candidate next above such candidates.

(11) This rule shall not apply at an election where there is only one vacancy.

Supplementary provisions on transfer

50.— If at any stage of the count, two or more candidates have surpluses, the transferable papers of the candidate with the largest surplus shall be transferred first, and if—

(a) the surplus determined in respect of two or more candidates are equal, the transferable papers of the candidate who had the highest recorded votes at the earliest preceding stage at which they had unequal votes, shall be transferred first, and

(b) the votes credited to two or more candidates were equal at all stages of the count, the returning officer shall decide between those candidates by lot and the transferable papers of the candidate on whom the lot falls shall be transferred first.

(2) The returning officer shall, on each transfer of transferable papers under rule 49—

(a) record the total transfer value of the votes transferred to each candidate;

(b) add that value to the previous total of votes recorded for each candidate, and record the new total;

(c) record as non-transferable votes the difference between the surplus and the total transfer value of transferred votes and add that difference to the previously recorded total of non-transferable votes, and

(d) compare—

(i) the total number of votes then recorded for all of the candidates, together with total number of non-transferable votes, with

(ii) the recorded total of valid first preference votes.

(3) All ballot papers transferred under rule 49 or 51 shall be clearly marked, either individually or as a sub-parcel, so as to indicate the transfer value recorded at that time to each vote on that paper or, as the case may be, all the papers in that sub-parcel.

(4) Where a ballot paper is so marked that it is unclear to the returning officer at any stage of the count under rule 49 or 51 for which candidate the next preference is recorded, the returning officer shall treat any vote on that ballot paper as a non-transferable vote; and votes on a ballot paper shall be so treated where, for example, the names of two or more candidates (whether continuing candidates or not) are so marked that, in the opinion of the returning officer, the same order of preference is indicated or the numerical sequence is broken.

Exhaustion of candidates

51.—(1) if—

(a) all transferable papers which under the provisions of rule 49 (including that rule as applied by paragraph (11)) and this rule are required to be transferred, have been transferred, and

(b) subject to rule 52 one or more vacancies remain to be filled,

the returning officer shall exclude from the election at that stage the candidate with the then lowest vote (or, where paragraph (12) applies, the candidates with the then lowest votes).

(2) The returning officer shall sort all the ballot papers on which first preference votes are given for the candidate or candidates excluded under paragraph (1) into two sub-parcels so that they are grouped as—

(a) ballot papers on which a next available preference is given, and

(b) ballot papers on which no such preference is given (thereby including ballot papers on which preferences are given only for candidates who are deemed to be elected or are excluded).

(3) The returning officer shall, in accordance with this rule and rule 50, transfer each sub-parcel of ballot papers referred to in sub-paragraph (a) of paragraph (2) to the candidate for whom the next available preference is given on those papers.

(4) The exclusion of a candidate, or of two or more candidates together, constitutes a further stage of the count.

(5) If, subject to rule 52, one or more vacancies still remain to be filled, the returning officer shall then sort the transferable papers, if any, which had been transferred to any candidate excluded under paragraph (1) into sub-parcels according to their transfer values.

(6) The returning officer shall transfer those papers in the sub-parcel of transferable papers with the highest transfer value to the continuing candidates in accordance with the next available preferences given on those papers (thereby passing over candidates who are deemed to be elected or are excluded).

(7) The vote on each transferable paper transferred under paragraph (6) shall be at the value at which that vote was received by the candidate excluded under paragraph (1).

(8) Any papers on which no next available preferences have been expressed shall be set aside as non-transferable votes.

(9) After the returning officer has completed the transfer of the ballot papers in the sub-parcel of ballot papers with the highest transfer value he shall proceed to transfer in the same way the sub-parcel of ballot papers with the next highest value and so on until he has dealt with each sub-parcel of a candidate excluded under paragraph (1).

(10) The returning officer shall after each stage of the count completed under this rule—

(a) record—

(i) the total value of votes, or

(ii) the total transfer value of votes transferred to each candidate;

(b) add that total to the previous total of votes recorded for each candidate and record the new total;

(c) record the value of non-transferable votes and add that value to the previous non-transferable votes total, and

(d) compare—

(i) the total number of votes then recorded for each candidate together with the total number of non-transferable votes, with

(ii) the recorded total of valid first preference votes.

(11) If after a transfer of votes under any prevision of this rule, a candidate has a surplus, that surplus shall be dealt with in accordance with paragraphs (5) to (10) of rule 49 and rule 50.

(12) Where the total of the votes of the two or more lowest candidates, together with any surpluses not transferred is less than the number of votes credited to the next lowest candidate, the returning officer shall in one operation exclude such two or more candidates.

(13) If where a candidate has to be excluded under this rule, two or more candidates each have the same number of votes and are lowest—

(a) regard shall be had to the total number of votes credited to those candidates at the earliest stage of the count at which they had an unequal number of votes and the candidate with the lowest number of votes at that stage shall be excluded; and

(b) where the number of votes credited to those candidates was equal at all stages, the returning officer shall decide between the candidates by lot and the candidate on whom the lot falls shall be excluded.

Filling last vacancies

52.— (1) Where the number of continuing candidates is equal to the number of vacancies remaining unfilled the continuing candidates shall thereupon be deemed to be elected.

(2) Where only one vacancy remains unfilled and the votes of any one continuing candidate are equal to or greater than the total of votes credited to another or other continuing candidates together with any surplus not transferred, the candidate shall thereupon be deemed to be elected.

(3) Where the last vacancies can be filled under this rule, no further transfer of votes shall be made. """ htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): GregorySTV.__init__(self, b) MethodPlugin.__init__(self) self.prec = 2 self.weakTieBreakMethod = "forward" self.threshName = ["Droop", "Static", "Whole"] self.delayedTransfer = "On" self.batchElimination = "Losers" def sortVotesByTransferValue(self, cList): "Sort votes according to N. Ireland rules." self.votesByTransferValue = {} for loser in cList: for i in self.votes[loser]: v = self.transferValue[i] if i in self.batches[loser][0]: key = "first" else: key = v if key not in self.votesByTransferValue.keys(): self.votesByTransferValue[key] = [] self.votesByTransferValue[key].append(i) self.transferValues = self.votesByTransferValue.keys() if "first" in self.transferValues: self.transferValues.remove("first") self.transferValues.sort(reverse=True) self.transferValues.insert(0, "first") else: self.transferValues.sort(reverse=True) def transferVotesFromCandidates(self, elimList): elimList.sort() nTransferValues = len(self.transferValues) if nTransferValues == 0: # This will happen when all eliminated candidates have 0 votes self.updateRound() self.updateWinners() self.roundInfo[self.R]["eliminate"] += \ "Count after eliminating %s. No votes are "\ "transferred since all eliminated candidates "\ "have zero votes. " % self.b.joinList(elimList) self.describeRound() else: self.roundInfo[self.R]["eliminate"] += \ "Count after eliminating %s and transferring "\ "votes. " % self.b.joinList(elimList) for v in self.transferValues: self.transferVotesWithValue(v) self.updateRound() self.describeRound() if self.electionOver(): break OpenSTV-1.6.1/openstv/MethodPlugins/QPQ.py0000777000175400010010000002177311400774166016723 0ustar jeffNone"Plugin module for QPQ" ## Copyright 2009-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: QPQ.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import Iterative from openstv.plugins import MethodPlugin from openstv.qx import QX ## Procedure (from Woodall paper http://www.votingmatters.org.uk/ISSUE17/I17P1.PDF) ## ## 1. Set each candidate to hopeful ## 2. Each ballot has elected 0 candidates ## Round: ## 3. Calculate quotient qc (count) of each hopeful candidate c ## qc = vc/(1+tc) ## where vc is number of ballots ranking C first ## tc is sum of all fractional numbers of candidates those ballots ## have so far elected ## 4. Quota Q = va/(1+s-tx) ## where va is number of active ballots (ballots with hopeful candidate(s)) ## s is total number of seats to be filled ## tx is sum of fractional number of candidates elected by all inactive ballots ## 5a. Elect candidate with highest quotient if quotient > quota ## update contribution of ballots contributing to c ## to 1/qc ## 5b. If no candidate elected in 5a, exclude hopeful with smallest quotient ## 6. Count ends when no hopeful candidates remain ################################################################## class QPQ(Iterative, MethodPlugin): "Quota Preferential by Quotient (QPQ)" methodName = "QPQ" longMethodName = "Quota Preferential by Quotient" onlySingleWinner = False threshMethod = True status = 2 htmlBody = """

Quota Preferential by Quotient (QPQ) is a proportional system that, like STV, meets the Droop proportionality criterion.

For details, see Douglas Woodall's paper, "QPQ, a quota-preferential STV-like election rule", Voting matters Issue 17, p1-7 (October 2003) <http://www.votingmatters.org.uk/ISSUE17/I17P1.PDF>.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): Iterative.__init__(self, b) MethodPlugin.__init__(self) self.threshName = ["Droop", "Dynamic", "Fractional"] self.prec = 10 self.weakTieBreakMethod = "strong" # treat all ties as strong self.optRestart = True # option: restart after exclusion self.stopCond = ["Continuing Empty"] self.va = [] # va[r] is number of active ballots at round r self.vc = [] # vc[r][c] is candidate c's votes at round r # satisfy reporter self.exhausted = [] self.surplus = [] self.tc = [] # tc[r][c] is candidate c's ballots' contributions at round r self.tx = [] # tx[r] is contribution of inactive ballots at round r self.thresh = [] # thresh[r] is the winning quota at round r self.votes = [] # votes[c] stores the indices of all votes for candidate c. self.restart = False def preCount(self): "QPQ pre-count" Iterative.preCount(self) QX.set_precision(self, self.prec) QX.set_guard(self, self.prec) self.R = 0 # current round self.numRounds = 0 # total number of rounds self.msg = [] # msg[r] contains text describing round r self.count = [] # count[r][c] is candidate c's quotient qc at round r for c in range(self.b.numCandidates): self.votes.append([]) def displayValue(self, value): "Format a value with specified precision." return QX.str(value) def allocateRound(self): "Allocate space for all data structures for one round." self.msg.append("") self.roundInfo.append({}) self.vc.append([0] * self.b.numCandidates) self.tc.append([0] * self.b.numCandidates) self.count.append([0] * self.b.numCandidates) self.tx.append(0) self.va.append(0) self.thresh.append(0) # satisfy reporter self.exhausted.append(0) self.surplus.append(0) def findTiedCand(self, cList, mostfewest, function): "Return a list of candidates tied for first or last." assert(mostfewest in ["most", "fewest"]) assert(len(cList) > 0) cList = list(cList) tiedCand = [] # Find a candidate who is winning/losing. He may be tied with others. if mostfewest == "most": cList.sort(key=lambda a, f=function: -f[a]) elif mostfewest == "fewest": cList.sort(key=lambda a, f=function: f[a]) top = cList[0] # first/last place candidate # Find the number of candidates who are tied with him. for c in cList: if QX.eq(function[c], function[top]): tiedCand.append(c) return tiedCand def updateWinners(self): "Find best winning candidate." desc = "" best = 0 winners = set() self.restart = False for c in self.continuing: if QX.gt(self.count[self.R][c], best): winners = set([c]) best = self.count[self.R][c] elif self.count[self.R][c] == best: winners.add(c) if (len(winners) != 0 and QX.gt(best, self.thresh[self.R])): # determine single winner (cWin, desc) = self.breakWeakTie(self.R, winners, "most", "winner") desc = self.newWinners([cWin]) self.roundInfo[self.R]["action"] = ("surplus", [cWin]) # distribute ballots to next choice for i in self.votes[cWin][:]: self.b.contrib[i] = self.b.getWeight(i) * QX.div(QX.One, self.count[self.R][cWin]) c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.votes[cWin] = [] else: # if no winner, exclude one candidate (elimList, desc) = self.selectCandidatesToEliminate() self.roundInfo[self.R]["action"] = ("eliminate", elimList) cLose = elimList[0] # distribute ballots to next choice for i in self.votes[cLose][:]: c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.votes[cLose] = [] self.restart = self.optRestart return desc def selectCandidatesToEliminate(self): "Choose one candidate to eliminate." elimList = [] (c, desc2) = self.breakWeakTie(self.R, self.continuing, "fewest", "candidate to eliminate") elimList = [c] # Put losing candidates in the proper list self.newLosers(elimList) desc = "Candidate %s is eliminated. " % self.b.joinList([c]) return (elimList, desc+desc2) def initialVoteTally(self): "Find initial first place votes." # Allocate ballots to candidates based on the first choices. self.b.contrib = [] for i in xrange(self.b.numWeightedBallots): c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.b.contrib.append(0) self.roundInfo[self.R]["action"] = ("first", []) def restartVoteTally(self): "Restart election after elimination." self.continuing |= self.winners self.winners = set() self.winnersOver = set() self.winnersEven = set() self.votes = [] for c in range(self.b.numCandidates): self.votes.append([]) self.initialVoteTally() self.roundInfo[self.R]["action"] = ("restart", []) return "Restart count. " def updateCount(self): "Update quotients." # Count contribution of all ballots (will eventually subtract active contributions) for i in xrange(self.b.numWeightedBallots): self.tx[self.R] += self.b.contrib[i] # Count number (vc) and contribution (tc) of active ballots (ranking hopeful candidates); # Calculate quotient for each hopeful candidate (qc=count) # Count total number of active ballots (va) # Adjust tx. for c in range(self.b.numCandidates): for i in self.votes[c]: self.vc[self.R][c] += QX.fix(self.b.getWeight(i)) self.tc[self.R][c] += self.b.contrib[i] self.count[self.R][c] = QX.div(self.vc[self.R][c], QX.One + self.tc[self.R][c]) self.va[self.R] += self.vc[self.R][c] self.tx[self.R] -= self.tc[self.R][c] # Calculate quota for current round self.thresh[self.R] = QX.div(self.va[self.R], QX.fix(1 + self.numSeats) - self.tx[self.R]) def countBallots(self): "Count the votes with QPQ." # Do the rounds... while (not self.electionOver()): self.allocateRound() if (self.R == 0): self.initialVoteTally() self.msg[self.R] = "Count of first choices. " elif (self.restart): self.msg[self.R] += self.restartVoteTally() self.updateCount() self.msg[self.R] += self.updateWinners() self.R += 1 def postCount(self): "Report QX stats if enabled" self.numRounds = self.R if False: QX.postCount(self, self.R) OpenSTV-1.6.1/openstv/MethodPlugins/RTSTV.py0000777000175400010010000001006611400774166017175 0ustar jeffNone"Plugin module for RTSTV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: RTSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" import string from openstv.STV import OrderDependentSTV from openstv.plugins import MethodPlugin ################################################################## class RTSTV(OrderDependentSTV, MethodPlugin): "Customizable Random Transfer STV" methodName = "RTSTV" longMethodName = "Random Transfer STV" status = 2 htmlBody = """

Random transfer STV (RTSTV) is a method that treats each vote as a single unit that votes cannot be split up among multiple candidates. Because the order of the votes can change the outcome of the election, it is called a "random" transfer method. This implementation contains several options that allow the user to customize the rules as desired:

  • Threshold: The threshold has two parameters. First, whether to use a Droop or a Hare threshold. Nearly all STV rules use a Droop threshold. Second, whether the threshold should decrease as votes are exhausted.
  • Delay Surplus Transfer: Transferring surplus votes is a more complicated operation than transferring votes from eliminated candidates. Some rules delay the transfer of surplus votes where candidates can be safely eliminated (i.e., the candidates are sure to lose regardless of which candidates receive the surplus votes).
  • Candidate Elimination: This option applies to the elimination of candidates. "None" means that candidates are always eliminated one by one. "Zero" means that all candidates with zero votes are eliminated simultaneously. "Losers" means that all losing candidates are eliminated simultaneously. "Batch" means that at the first elimination round all candidates under a specified threshold are eliminated.
  • Batch Cutoff: This is the threshold to apply when Candidate Elimination is set to "Batch".
""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): OrderDependentSTV.__init__(self, b) MethodPlugin.__init__(self) self.threshName = ["Droop", "Static", "Whole"] self.delayedTransfer = "Off" self.batchElimination = "Zero" self.batchCutoff = 50 self.createGuiOptions(["thresh0", "thresh1", "delayedTransfer", "batchElimination", "batchCutoff"]) def preCount(self): OrderDependentSTV.preCount(self) self.optionsMsg = "Using a %s threshold." % \ string.join(self.threshName, "-") def transferSurplusVotesFromCandidate(self, cSurplus): "Transfer surplus votes with random transfer rules." # transfer whole votes in excess of the threshold surplus = int(self.count[self.R-1][cSurplus] - self.thresh[self.R-1]) for i in self.votes[cSurplus][:surplus]: c = self.b.getTopChoiceFromBallot(i, self.continuing) self.votes[cSurplus].remove(i) if c != None: self.votes[c].append(i) desc = "Count after transferring surplus votes from %s. " \ % self.b.names[cSurplus] return desc def transferVotesFromCandidates(self, elimList): "Eliminate candidates according to the random transfer rules." # Transfer whole votes from losers. for loser in elimList: for i in self.votes[loser]: c = self.b.getTopChoiceFromBallot(i, self.continuing) if c != None: self.votes[c].append(i) self.votes[loser] = [] elimList.sort() desc = "Count after eliminating %s and transferring votes. " \ % self.b.joinList(elimList) return desc OpenSTV-1.6.1/openstv/MethodPlugins/ScottishSTV.py0000777000175400010010000003613311400774166020453 0ustar jeffNone"Plugin module for Scottish STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: ScottishSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import WeightedInclusiveSTV from openstv.plugins import MethodPlugin ################################################################## class ScottishSTV(WeightedInclusiveSTV, MethodPlugin): "Scottish STV" methodName = "Scottish STV" longMethodName = "Scottish STV" status = 1 htmlBody = """

Scotland enacted these rules for local elections in 2007. This is a straightforward implementation of STV and recommended to organizations using STV for the first time.

The complete set of rules can be found at http://www.opsi.gov.uk/legislation/scotland/ssi2007/ssi_20070042_en.pdf. The relevant portions of the these rules are included below.

OpenSTV's implementation of the Scottish STV rules has been validated against the eSTV program.


SCOTTISH STATUTORY INSTRUMENTS

2007 No. 42

REPRESENTATION OF THE PEOPLE

The Scottish Local Government Elections Order 2007

Citation, commencement and extent

1.—(1) This Order may be cited as the Scottish Local Government Elections Order 2007.

(2) This Order shall come into force on 17th February 2007 except for the purposes of any election to be held on or before 2nd May 2007.

(3) This Order shall extend to Scotland only.

Interpretation

2. In this Order, unless the context otherwise requires—

"anonymous entry" in relation to a register of electors, shall be construed in accordance with section 9B of the 1983 Act and "the record of anonymous entries" means the record prepared in pursuance of regulations made by virtue of paragraph 8A of Schedule 2 to the 1983 Act;

"ballot paper account" has the meaning given in rule 39(3);

"companion" has the meaning given in rule 34(1);

"corresponding number list" means the list prepared in accordance with rule 15;

"completed corresponding number list" has the meaning given in rule 39(1);

"continuing candidate" means any candidate not deemed to be elected as a councillor and not excluded from the list of candidates under rule 50;

"council" means a council constituted by section 2 of the Local Government etc. (Scotland) Act 1994(b);

"count" means all the operations involved in counting and crediting votes, including the ascertainment of the quota, the transfer of ballot papers and the exclusion of candidates;

"election court" means the court constituted under the 1983 Act for the trial of a petition questioning an election;

"election petition" means a petition presented in pursuance of Part III of the 1983 Act as that Act is applied by this Order;

"election" means an election under the Local Governance (Scotland) Act 2004 and, for the purposes of articles 1(2) and 6(2), an election under the Local Government etc. (Scotland) Act 1994;

"elector" means a person who is registered in the register (or, in the case of a person who has an anonymous entry in the register, in the record of anonymous entries) to be used at the election as a local government elector for the local government area in which the election is held and includes a person shown in the register as below voting age if (but only if) it appears from the register that such person will be of voting age on the day fixed for the poll;

"electoral registration officer" has the same meaning as in the 1983 Act;

"electronic counting system" means such computer hardware and software, other equipment, data and services as may be necessary in order to—

(a) maintain a list of the areas in relation to which an election is being held by reference to ward barcodes on ballot papers issued to voters in relation to that area;

(b) read electronically the votes marked and the unique identifying number on each ballot paper returned;

(c) calculate the number of votes cast for each candidate at the election otherwise than on any spoilt, tendered or rejected ballot paper; and

(d) ensure the retention of a record of the votes given for each candidate, without identifying the elector by whom, or on whose behalf, the votes were cast;

"list of proxies" has the meaning given by paragraph 5(3) of Schedule 4 to the Representation of the People Act 2000;

"local authority" means a council constituted by section 2 of the Local Government etc. (Scotland) Act 1994;

"local government area" is to be construed in accordance with section 1 (local government areas) of the Local Government etc. (Scotland) Act 1994;

"next available preference" means a preference which is the second or, as the case may be, subsequent preference in consecutive order for a continuing candidate (any preferences for any candidate who is deemed to be elected or is excluded from the list of candidates under rule 50 being ignored);

"non-transferable paper" means a ballot paper on which there is no next available preference;

"proper officer" has the same meaning as in section 235(3) of the Local Government (Scotland) Act 1973(a);

"postal voters list" means the list of persons kept in pursuance of paragraph 5(2) (persons whose applications to vote by post have been granted) of Schedule 4 to the Representation of the People Act 2000(b);

"proxy postal voters list" means the list of persons kept in pursuance of paragraph 7(8) (persons whose applications to vote by post as proxy have been granted) of Schedule 4 to the Representation of the People Act 2000;

"qualifying address" in relation to a person registered in the register of electors, is the address in respect of which that person is entitled to be so registered;

"quota" has the meaning given in rule 46;

"registered political party" means a party registered under Part II of the Political Parties, Elections and Referendums Act 2000(c);

"returning officer" means, in relation to an election, the returning officer appointed for the election under section 41(1) (duty of local authority to appoint returning officer for each local authority election) of the 1983 Act;

"special lists" means the lists kept under paragraph 5 of schedule 4 to the Representation of the People Act 2000(d);

"spoilt ballot paper" has the meaning given in rule 36;

"stage of the count" means—

(a) the determination of the number of votes for each candidate as first preference;

(b) the transfer of transferable papers from a candidate deemed to be elected who has a surplus; or

(c) the exclusion of a candidate at any given time;

"surplus" means the number of votes, if any, by which the total number of votes credited to a candidate deemed to be elected as a councillor exceeds the quota;

"tendered ballot paper" has the meaning given in rule 35(1);

"tendered votes list" has the meaning given in rule 35(8);

"transferable paper" means a ballot paper on which a next available preference is given;

"transfer value" means the value of a vote on a ballot paper calculated in accordance with rule 48;

"unique identifying mark" means the mark (for example, a bar code, letter, number or numerical sequence) on a ballot paper which is unique to that ballot paper and which identifies that ballot paper as a ballot paper to be issued by the returning officer; and

"voter" means a person voting at an election and includes a person voting as proxy and "vote" (whether noun or verb) shall be construed accordingly except that any reference to an elector voting or an elector's vote shall include a reference to an elector voting by proxy or an elector's vote given by proxy.

* * *

SCHEDULE 1

PART III — CONTESTED ELECTIONS

* * *

Counting of votes

* * *

First stage

45.—(1) The returning officer shall sort the valid ballot papers into parcels according to the candidates for whom first preference votes are given.

(2) The returning officer shall then—

(a) count the number of ballot papers in each parcel;

(b) credit the candidate receiving the first preference vote with one vote for each ballot paper; and

(c) record those numbers.

(3) The returning officer shall also ascertain and record the total number of valid ballot papers.

The quota

46.—(1) The returning officer shall divide the total number of valid ballot papers for the electoral ward by a number exceeding by one the number of councillors to be elected at the election for that electoral ward.

(2) The result of the division under paragraph (1) (ignoring any decimal places), increased by one, is the number of votes needed to secure the return of a candidate as a councillor (in these rules referred to as the "quota").

Return of councillors

47.—(1) Where, at any stage of the count, the number of votes for a candidate equals or exceeds the quota, the candidate is deemed to be elected.

(2) A candidate is returned as a councillor when declared to be elected in accordance with rule 55(a).

Transfer of ballot papers

48.—(1) Where, at the end of any stage of the count, the number of votes credited to any candidate exceeds the quota and, subject to rules 49 and 52, one or more vacancies remain to be filled, the returning officer shall sort the ballot papers received by that candidate into further parcels so that they are grouped—

(a) according to the next available preference given on those papers; and

(b) where no such preference is given, as a parcel of non-transferable papers.

(2) The returning officer shall, in accordance with this rule and rule 49, transfer each parcel of ballot papers referred to in paragraph (1)(a) to the continuing candidate for whom the next available preference is given on those papers and shall credit such continuing candidates with an additional number of votes calculated in accordance with paragraph (3).

(3) The vote on each ballot paper transferred under paragraph (2) shall have a value ("the transfer value") calculated as follows—

A divided by B

Where

A = the value which is calculated by multiplying the surplus of the transferring candidate by the value of the ballot paper when received by that candidate; and

B = the total number of votes credited to that candidate,

the calculation being made to five decimal places (any remainder being ignored).

(4) For the purposes of paragraph (3)—

(a) "transferring candidate" means the candidate from whom the ballot paper is being transferred; and

(b) "the value of the ballot paper" means—

(i) for a ballot paper on which a first preference vote is given for the transferring candidate, one; and

(ii) in all other cases, the transfer value of the ballot paper when received by the transferring candidate.

Transfer of ballot papers — supplementary provisions

49.—(1) If, at the end of any stage of the count, the number of votes credited to two or more candidates exceeds the quota the returning officer shall—

(a) first sort the ballot papers of the candidate with the highest surplus; and

(b) then transfer the transferable papers of that candidate.

(2) If the surpluses determined in respect of two or more candidates are equal, the transferable papers of the candidate who had the highest number of votes at the end of the most recent preceding stage at which they had unequal numbers of votes shall be transferred first.

(3) If the numbers of votes credited to two or more candidates were equal at all stages of the count, the returning officer shall decide, by lot, which candidate's transferable papers are to be transferred first.

Exclusion of candidates

50.—(1) If, one or more vacancies remain to be filled and—

(a) the returning officer has transferred all ballot papers which are required by rule 48 or this rule to be transferred; or

(b) there are no ballot papers to be transferred under rule 48 or this rule, the returning officer shall exclude from the election at that stage the candidate with the then lowest number of votes.

(2) The returning officer shall sort the ballot papers for the candidate excluded under paragraph (1) into parcels so that they are grouped—

(a) according to the next available preference given on those papers; and

(b) where no such preference is given, as a parcel of non-transferable papers.

(3) The returning officer shall, in accordance with this article, transfer each parcel of ballot papers referred to in paragraph (2)(a) to the continuing candidate for whom the next available preference is given on those papers and shall credit such continuing candidates with an additional number of votes calculated in accordance with paragraph (4).

(4) The vote on each ballot paper transferred under paragraph (3) shall have a transfer value of one unless the vote was transferred to the excluded candidate in which case it shall have the same transfer value as when transferred to the candidate excluded under paragraph (1).

(5) This rule is subject to rule 52.

Exclusion of candidates — supplementary provisions

51.—(1) If, when a candidate has to be excluded under rule 50—

(a) two or more candidates each have the same number of votes; and

(b) no other candidate has fewer votes,

paragraph (2) applies.

(2) Where this paragraph applies—

(a) regard shall be had to the total number of votes credited to those candidates at the end of the most recently preceding stage of the count at which they had an unequal number of votes and the candidate with the lowest number of votes at that stage shall be excluded; and

(b) where the number of votes credited to those candidates was equal at all stages, the returning officer shall decide, by lot, which of those candidates is to be excluded.

Filling of last vacancies

52.—(1) Where the number of continuing candidates is equal to the number of vacancies remaining unfilled, the continuing candidates are deemed to be elected.

(2) Where the last vacancies can be filled under this rule, no further transfer shall be made. """ htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): WeightedInclusiveSTV.__init__(self, b) MethodPlugin.__init__(self) self.prec = 5 self.threshName = ["Droop", "Static", "Whole"] self.delayedTransfer = "Off" self.batchElimination = "None" OpenSTV-1.6.1/openstv/MethodPlugins/SNTV.py0000777000175400010010000000404411400774166017044 0ustar jeffNone"Plugin module for SNTV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: SNTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import NonIterative from openstv.plugins import MethodPlugin ################################################################## class SNTV(NonIterative, MethodPlugin): "Single Non-Transferable Vote" methodName = "SNTV" longMethodName = "Single Non-Transferable Vote" status = 1 htmlBody = """

With the single non-transferable vote, only the first choices are used in counting the ballots, and the candidate with the greatest number of first choices is the winner. When there is only one seat to be filled, this corresponds to a traditional plurality election. When there is more than one seat to be filled, this provides a simple form of proportional representation.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): NonIterative.__init__(self, b) MethodPlugin.__init__(self) def countBallots(self): "Count the votes using SNTV." # Count the first place votes candidates = set(range(self.b.numCandidates)) for i in xrange(self.b.numWeightedBallots): c = self.b.getTopChoiceFromWeightedBallot(i, candidates) if c == None: self.exhausted += self.b.getWeight(i) else: self.count[c] += self.b.getWeight(i) self.msg += ("Count of first choices. ") # Choose the winners desc = self.chooseWinners() self.msg += desc OpenSTV-1.6.1/openstv/MethodPlugins/SuppVote.py0000777000175400010010000000372111400774166020040 0ustar jeffNone"Plugin module for the Supplemental Vote" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: SuppVote.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import NoSurplusSTV from openstv.plugins import MethodPlugin ################################################################## class SuppVote(NoSurplusSTV, MethodPlugin): "Supplemental Vote" methodName = "Supplemental Vote" longMethodName = "Supplemental Vote" onlySingleWinner = True status = 2 htmlBody = """

The supplemental vote is a simplified version of IRV. Only the first two rankings on the ballots are used. In the first round, the first rankings are counted and all candidates except for the top two are eliminated. Ballots of eliminated candidates are transferred to their second rankings. The supplemental vote is used to elect the Mayor of London.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): NoSurplusSTV.__init__(self, b) MethodPlugin.__init__(self) def selectCandidatesToEliminate(self): "Eliminate all candidates except for the top two." (topTwo, desc) = self.chooseNfromM(2, self.count[0], self.continuing, "top two candidates") loserList = [] for c in self.continuing: if c not in topTwo: loserList.append(c) self.newLosers(loserList) return (loserList, desc) OpenSTV-1.6.1/openstv/MethodPlugins/WarrenQXSTV.py0000777000175400010010000000311211400774166020351 0ustar jeffNone"Plugin module for Warren Quasi-Exact STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: WarrenQXSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.qx import RecursiveQXSTV from openstv.MethodPlugins.WarrenSTV import WarrenSTV from openstv.plugins import MethodPlugin ################################################################## class WarrenQXSTV(RecursiveQXSTV, WarrenSTV, MethodPlugin): "Warren STV with guard bits and quasi-exact rounding" methodName = "WarrenQX STV" longMethodName = "WarrenQX STV" status = 2 htmlBody = """

Jonathan Lundell's "quasi-exact" implementation of Warren STV. See David Hill and Jonathan Lundell's paper Notes on the Droop quota, available at http://www.votingmatters.org.uk/ISSUE24/I24P2.pdf, for a brief description of quasi-exact arithmetic.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): RecursiveQXSTV.__init__(self, b) MethodPlugin.__init__(self) self.createGuiOptions(["prec"]) OpenSTV-1.6.1/openstv/MethodPlugins/WarrenSTV.py0000777000175400010010000000402111400774166020100 0ustar jeffNone"Plugin module for Warren STV" ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: WarrenSTV.py 715 2010-02-27 17:00:55Z jeff.oneill $" from openstv.STV import RecursiveSTV from openstv.plugins import MethodPlugin ################################################################## class WarrenSTV(RecursiveSTV, MethodPlugin): "Warren STV" methodName = "Warren STV" longMethodName = "Warren STV" status = 2 htmlBody = """

Warren STV is similar to Meek STV except that surplus votes are transferred in a different way.

""" htmlHelp = (MethodPlugin.htmlBegin % (longMethodName, longMethodName)) +\ htmlBody + MethodPlugin.htmlEnd def __init__(self, b): RecursiveSTV.__init__(self, b) MethodPlugin.__init__(self) self.createGuiOptions(["prec", "thresh0", "thresh1", "thresh2"]) def updateCount(self, tree=None, remainder=None): "Traverse the tree to count the ballots." if tree is None: tree = self.tree if remainder is None: remainder = self.p # Iterate over the next candidates on the ballots for c in tree.keys(): if c == "n" or c == "bi": continue rrr = remainder if self.keepFactor[self.R][c] < rrr: self.count[self.R][c] += self.keepFactor[self.R][c] * tree[c]["n"] rrr -= self.keepFactor[self.R][c] else: self.count[self.R][c] += rrr * tree[c]["n"] rrr = 0 # If ballot not used up and more candidates, keep going if rrr > 0: self.updateCount(tree[c], rrr) OpenSTV-1.6.1/openstv/MethodPlugins/__init__.py0000777000175400010010000000005611400774166020010 0ustar jeffNone# dummy file to make this directory a package OpenSTV-1.6.1/openstv/OpenSTV.py0000777000175400010010000007157511400774167015004 0ustar jeffNone#!/usr/bin/env python ## OpenSTV Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: OpenSTV.py 725 2010-03-10 04:33:23Z jeff.oneill $" import os import sys import warnings from time import sleep from threading import Thread from Queue import Queue import wx import wx.html import wx.lib.mixins.listctrl as listmix from openstv.BFE import BFEFrame from openstv.ballots import Ballots from openstv.ReportPlugins.TextReport import TextReport from openstv.ReportPlugins.HtmlReport import HtmlReport from openstv.ReportPlugins.CsvReport import CsvReport from openstv.plugins import getMethodPlugins from openstv.utils import getHome ################################################################## class Output: """This is used to capture stdout/stderr from STV.py and send it to a wxPython window.""" def __init__(self, nb): self.nb = nb def write(self, txt): self.nb.GetCurrentPage().AppendText(txt) ################################################################## class Election(): def __init__(self, frame, filename, methodClass): self.frame = frame self.filename = filename self.methodClass = methodClass self.dispWidth = 100 self.dirtyBallots = None self.cleanBallots = None self.e = None def loadBallots(self): self.dirtyBallots = Ballots() self.dirtyBallots.exceptionQueue = Queue(1) loadThread = Thread(target=self.dirtyBallots.loadUnknown, args=(self.filename,)) loadThread.start() # Display a progress dialog dlg = wx.ProgressDialog(\ "Loading ballots", "Loading ballots from %s\nNumber of ballots: %d" % (os.path.basename(self.filename), self.dirtyBallots.numBallots), parent=self.frame, style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME ) while loadThread.isAlive(): sleep(0.1) dlg.Pulse("Loading ballots from %s\nNumber of ballots: %d" % (os.path.basename(self.filename), self.dirtyBallots.numBallots)) dlg.Destroy() if not self.dirtyBallots.exceptionQueue.empty(): raise RuntimeError(self.dirtyBallots.exceptionQueue.get()) def initializeElection(self): self.cleanBallots = self.dirtyBallots.getCleanBallots() self.e = self.methodClass(self.cleanBallots) def runElection(self): if not self.frame.breakTiesRandomly: self.e.strongTieBreakMethod = "manual" self.e.breakTieRequestQueue = Queue(1) self.e.breakTieResponseQueue = Queue(1) countThread = Thread(target=self.e.runElection) countThread.start() # Display a progress dialog dlg = wx.ProgressDialog(\ "Counting votes", "Counting votes using %s\nInitializing..." % self.e.longMethodName, parent=self.frame, style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME ) while countThread.isAlive(): sleep(0.1) if not self.e.breakTieRequestQueue.empty(): [tiedCandidates, names, what] = self.e.breakTieRequestQueue.get() c = self.askUserToBreakTie(tiedCandidates, names, what) self.e.breakTieResponseQueue.put(c) if "R" in vars(self.e): status = "Counting votes using %s\nRound: %d" % \ (self.e.longMethodName, self.e.R+1) else: status = "Counting votes using %s\nInitializing..." % \ self.e.longMethodName dlg.Pulse(status) dlg.Destroy() def askUserToBreakTie(self, candidates, names, what): "Provide a window to ask the user to break a tie." instructions = """\ Tie when selecting %s. Break the tie by selecting a candidate or choose Cancel to break the tie randomly.""" % what dlg = wx.SingleChoiceDialog(self.frame, instructions, "Break Tie Manually", names, wx.CHOICEDLG_STYLE) if dlg.ShowModal() == wx.ID_OK: selection = dlg.GetStringSelection() i = names.index(selection) return candidates[i] else: return None def generateReport(self, reportObj): reportObj.generateReport() def generateTextOutput(self, output): self.generateReport(TextReport(self.e, self.dispWidth, outputFile=output)) def generateCsvOutput(self, output): self.generateReport(CsvReport(self.e, outputFile=output)) def generateHtmlOutput(self, output): self.generateReport(HtmlReport(self.e, outputFile=output)) ################################################################## class Frame(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "OpenSTV", size=(900, 600)) warnings.showwarning = self.catchWarnings # Get method plugins and create dict for easy access plugins = getMethodPlugins("classes") self.methodClasses1 = {} # Methods enabled by default self.methodClasses2 = {} # All methods self.lastMethod = "Scottish STV" for p in plugins: if p.status == 1: self.methodClasses1[p.longMethodName] = p self.methodClasses2[p.longMethodName] = p self.methodClasses = self.methodClasses1 # Methods currently viewable to user self.breakTiesRandomly = False fn = os.path.join(getHome(), "Icons", "pie.ico") self.icon = wx.Icon(fn, wx.BITMAP_TYPE_ICO) self.SetIcon(self.icon) self.lastBallotFile = "" self.electionList = [] self.menuBar = wx.MenuBar() self.MakeMenu() self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) # create a notebook self.notebook = wx.Notebook(self, -1) # create a console window self.console = wx.TextCtrl(self.notebook, -1, style=wx.TE_MULTILINE|wx.TE_READONLY|\ wx.TE_WORDWRAP|wx.FIXED|wx.TE_RICH2) self.console.SetMaxLength(0) ps = self.console.GetFont().GetPointSize() font = wx.Font(ps, wx.MODERN, wx.NORMAL, wx.NORMAL) self.console.SetFont(font) # add the console as the first page self.notebook.AddPage(self.console, "Console") self.output = Output(self.notebook) sys.stdout = self.output sys.stderr = self.output self.introText = """\ OpenSTV Copyright 2003-2010 Jeffrey O'Neill GNU General Public License See Help->License for more details. To run an election with an existing ballot file, select "New Election" from the File menu. To create a new ballot file, select "Create New Ballot File" from the File menu. To edit an existing ballot file, select "Edit Ballot File" from the File menu. For more information about the operation of OpenSTV, see the Help menu, go to www.OpenSTV.org, or send an email to OpenSTV@googlegroups.com. """ self.console.AppendText(self.introText) def catchWarnings(self, message, category, filename, lineno): "Catch any warnings and display them in a dialog box." wx.MessageBox(str(message), "Warning", wx.OK|wx.ICON_INFORMATION) def MakeMenu(self): # File menu FileMenu = wx.Menu() self.AddMenuItem(FileMenu, 'Run Election...\tCtrl+E', 'Run election...', self.OnRunElection) FileMenu.AppendSeparator() self.AddMenuItem(FileMenu, 'New Ballot File...\tCtrl+N', 'New ballot file...', self.OnNewBF) self.AddMenuItem(FileMenu, 'Edit Ballot File...\tCtrl+O', 'Edit ballot file...', self.OnEditBF) FileMenu.AppendSeparator() self.AddMenuItem(FileMenu, 'Close Tab\tCtrl+W', 'Close Tab', self.OnCloseTab) FileMenu.AppendSeparator() self.AddMenuItem(FileMenu, 'Save Results As CSV...', 'Save results as CSV', self.OnSaveResultsCSV) self.AddMenuItem(FileMenu, 'Save Results As Text...', 'Save results as text', self.OnSaveResultsText) self.AddMenuItem(FileMenu, 'Save Results As HTML...', 'Save results as HTML', self.OnSaveResultsHTML) menuId = self.AddMenuItem(FileMenu, 'Exit', 'Exit the application', self.OnExit, "Exit") if wx.Platform == "__WXMAC__": wx.App.SetMacExitMenuItemId(menuId) self.menuBar.Append(FileMenu, '&File') # Edit menu EditMenu = wx.Menu() self.AddMenuItem(EditMenu, '&Copy\tCtrl+C', 'Copy', self.OnCopy) self.AddMenuItem(EditMenu, 'Select All\tCtrl+A', 'Select all', self.OnSelectAll) self.menuBar.Append(EditMenu, '&Edit') # Options menu OptionsMenu = wx.Menu() self.AddMenuItem(OptionsMenu, 'Show All Methods', 'Show all methods', self.OnShowAll, "Check") self.AddMenuItem(OptionsMenu, 'Break Ties Randomly', 'Break Ties Randomly', self.OnBreakTiesRandomly, "Check") subMenu = wx.Menu() self.AddMenuItem(subMenu, '6', '6', self.OnFontSize) self.AddMenuItem(subMenu, '7', '7', self.OnFontSize) self.AddMenuItem(subMenu, '8', '8', self.OnFontSize) self.AddMenuItem(subMenu, '9', '9', self.OnFontSize) self.AddMenuItem(subMenu, '10', '10', self.OnFontSize) self.AddMenuItem(subMenu, '11', '11', self.OnFontSize) self.AddMenuItem(subMenu, '12', '12', self.OnFontSize) self.AddMenuItem(subMenu, '13', '13', self.OnFontSize) self.AddMenuItem(subMenu, '14', '14', self.OnFontSize) OptionsMenu.AppendMenu(wx.NewId(), "Font Size", subMenu) self.menuBar.Append(OptionsMenu, '&Options') # Help menu HelpMenu = wx.Menu() self.AddMenuItem(HelpMenu, 'OpenSTV Help', 'OpenSTV Help', self.OnHelp, "Help") self.AddMenuItem(HelpMenu, 'License', 'GNU General Public License', self.OnLicense) # Help about methods # mac wxPython doesn't allow submenus in the help menu so need to do it # a different way methods = self.methodClasses2.keys() methods.sort() if wx.Platform == "__WXMAC__": HelpMenu.AppendSeparator() for m in methods: self.AddMenuItem(HelpMenu, m, m, self.OnMethodHelp) else: methodHelpMenu = wx.Menu() for m in methods: self.AddMenuItem(methodHelpMenu, m, m, self.OnMethodHelp) HelpMenu.AppendMenu(wx.NewId(), "Methods", methodHelpMenu) # About OpenSTV if wx.Platform != "__WXMAC__": HelpMenu.AppendSeparator() itemId = self.AddMenuItem(HelpMenu, '&About', 'About OpenSTV', self.OnAbout, "About") if wx.Platform == "__WXMAC__": wx.App.SetMacAboutMenuItemId(itemId) self.menuBar.Append(HelpMenu, '&Help') if wx.Platform == "__WXMAC__": wx.GetApp().SetMacHelpMenuTitleName('&Help') self.SetMenuBar(self.menuBar) def AddMenuItem(self, menu, itemText, itemDescription, itemHandler, opt=''): if (opt == "Exit"): menuId = wx.ID_EXIT elif (opt == "Help"): menuId = wx.ID_HELP elif (opt == "About"): menuId = wx.ID_ABOUT else: menuId = wx.ID_ANY if opt == "Radio": item = menu.Append(menuId, itemText, itemDescription, wx.ITEM_RADIO) elif opt == "Check": item = menu.Append(menuId, itemText, itemDescription, wx.ITEM_CHECK) else: item = menu.Append(menuId, itemText, itemDescription) self.Bind(wx.EVT_MENU, itemHandler, item) return item.GetId() ### File Menu def OnRunElection(self, event): # Get the ballot filename and election method dlg = ElectionMethodFileDialog(self) dlg.Center() if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return filename = dlg.filenameC.GetValue().strip() self.lastBallotFile = filename methodName = dlg.methodC.GetStringSelection() self.lastMethod = methodName withdawCandidates = dlg.withdrawC.GetValue() dlg.Destroy() # Create an election instance election = Election(self, filename, self.methodClasses[methodName]) # Load ballots from the file. These are dirty ballots. try: election.loadBallots() except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) return # Allow the user to specify withdrawn candidates. # Will set withdrawn candidates in election.dirtyBallots if withdawCandidates: dlg = WithdrawDialog(self, election.dirtyBallots) dlg.Center() if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return # Initialize the election instance and set default options election.initializeElection() # Allow user to change election information and options dlg = ElectionOptionsDialog(self, election) dlg.Center() if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return dlg.Destroy() try: election.runElection() except RuntimeError, msg: wx.MessageBox(str(msg), "Error", wx.OK|wx.ICON_ERROR) return self.electionList.append(election) # create a new notebook page tc = wx.TextCtrl(self.notebook, -1, style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL|wx.FIXED) tc.SetMaxLength(0) ps = tc.GetFont().GetPointSize() font = wx.Font(ps, wx.MODERN, wx.NORMAL, wx.NORMAL) tc.SetFont(font) tabTitle = election.e.title if len(tabTitle) > 30: tabTitle = tabTitle[:27] + "..." self.notebook.AddPage(tc, tabTitle) page = self.notebook.GetPageCount() - 1 self.notebook.SetSelection(page) election.generateTextOutput(self.notebook.GetPage(page)) def OnNewBF(self, event): BFE = BFEFrame(self, "new") BFE.Show(True) def OnEditBF(self, event): BFE = BFEFrame(self, "old") BFE.Show(True) def OnCloseTab(self, event): n = self.notebook.GetSelection() if n == 0: pages = self.notebook.GetPageCount() if pages > 1: return # don't close console if there are open results pages self.OnCloseWindow(event) # otherwise close window return # The index into electionList is off by one because of the console tab self.notebook.DeletePage(n) self.electionList.pop(n-1) def OnSaveResultsCSV(self, event): n = self.notebook.GetSelection() if n == 0: wx.MessageBox("Please select a tab containing election results.", "Message", wx.OK|wx.ICON_INFORMATION) return T = self.electionList[n-1] if T.e.methodName == "Condorcet": wx.MessageBox("CSV report not available for Condorcet elections.", "Message", wx.OK|wx.ICON_INFORMATION) return if T.e.methodName == "Condorcet": wx.MessageBox("Not available for this method.", "Message", wx.OK|wx.ICON_INFORMATION) return dlg = wx.FileDialog(self, "Save Results in CSV Format", style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return fName = dlg.GetPath() dlg.Destroy() f = open(fName, 'w') T.generateCsvOutput(f) f.close() def OnSaveResultsText(self, event): n = self.notebook.GetSelection() if n == 0: wx.MessageBox("Please select a tab containing election results.", "Message", wx.OK|wx.ICON_INFORMATION) return dlg = wx.FileDialog(self, "Save Results in Text Format", style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return fName = dlg.GetPath() dlg.Destroy() n = self.notebook.GetSelection() self.notebook.GetPage(n).SaveFile(fName) def OnSaveResultsHTML(self, event): n = self.notebook.GetSelection() if n == 0: wx.MessageBox("Please select a tab containing election results.", "Message", wx.OK|wx.ICON_INFORMATION) return T = self.electionList[n-1] if T.e.methodName == "Condorcet": wx.MessageBox("HTML report not available for Condorcet elections.", "Message", wx.OK|wx.ICON_INFORMATION) return dlg = wx.FileDialog(self, "Save Results in HTML Format", style=wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return fName = dlg.GetPath() dlg.Destroy() f = open(fName, 'w') T.generateHtmlOutput(f) f.close() def OnExit(self, event): self.Close() def OnCloseWindow(self, event): childrenList = self.GetChildren() for child in childrenList: # If the child is a frame, then it is a BFE if child.GetClassName() == "wxFrame": # Try to close the child, this will return true if the user selects # "yes" or "no" and false if the user selects "cancel" if not child.Close(): break else: # This only happens if the user did not select cancel for any BFE self.Destroy() ### Edit Menu def OnCopy(self, event): n = self.notebook.GetSelection() text = self.notebook.GetPage(n).GetStringSelection() do = wx.TextDataObject() do.SetText(text) wx.TheClipboard.Open() wx.TheClipboard.SetData(do) wx.TheClipboard.Close() def OnSelectAll(self, event): n = self.notebook.GetSelection() self.notebook.GetPage(n).SetSelection(-1, -1) ### Options Menu Methods def OnShowAll(self, event): itemId = event.GetId() showAll = self.GetMenuBar().FindItemById(itemId).IsChecked() if showAll: self.methodClasses = self.methodClasses2 else: self.methodClasses = self.methodClasses1 def OnBreakTiesRandomly(self, event): itemId = event.GetId() self.breakTiesRandomly = self.GetMenuBar().FindItemById(itemId).IsChecked() def OnFontSize(self, event): itemId = event.GetId() fontSize = int(self.menuBar.FindItemById(itemId).GetLabel()) n = self.notebook.GetSelection() font = self.notebook.GetPage(n).GetFont() font.SetPointSize(fontSize) self.notebook.GetPage(n).SetFont(font) ### Help Menu def OnAbout(self, event): dlg = AboutDialog(self) dlg.Center() dlg.ShowModal() dlg.Destroy() def OnMethodHelp(self, event): itemId = event.GetId() methodName = self.menuBar.FindItemById(itemId).GetLabel() html = self.methodClasses2[methodName].htmlHelp frame = HTMLFrame(self, methodName, html=html) frame.SetIcon(self.icon) frame.Show(True) def OnHelp(self, event): frame = HTMLFrame(self, "OpenSTV Help", fName="Help.html") frame.Show(True) def OnLicense(self, event): frame = HTMLFrame(self, "GNU General Public License", fName="License.html") frame.Show(True) ################################################################## class ElectionMethodFileDialog(wx.Dialog): def __init__(self, parent): wx.Dialog.__init__(self, parent, -1, "Run Election") # Explanation txt = wx.StaticText(self, -1, """\ To run an election, choose the input filename and the election method. See the Help menu for more information about the available methods.""") # Controls filenameL = wx.StaticText(self, -1, "Filename:") self.filenameC = wx.TextCtrl(self, -1, "") self.filenameC.SetValue(parent.lastBallotFile) filenameB = wx.Button(self, -1, "Select...", (50, 50)) self.Bind(wx.EVT_BUTTON, self.OnFilenameSelect, filenameB) methodL = wx.StaticText(self, -1, "Method:") choices = parent.methodClasses.keys() choices.sort() self.methodC = wx.Choice(self, -1, choices = choices) if parent.lastMethod in choices: self.methodC.SetStringSelection(parent.lastMethod) blank1 = wx.StaticText(self, -1, "") self.withdrawC = wx.CheckBox(self, -1, "Withdraw canddiates:", style=wx.ALIGN_RIGHT) self.withdrawC.SetValue(False) blank2 = wx.StaticText(self, -1, "") blank3 = wx.StaticText(self, -1, "") # Buttons ok = wx.Button(self, wx.ID_OK) self.Bind(wx.EVT_BUTTON, self.OnOK, ok) cancel = wx.Button(self, wx.ID_CANCEL) # Sizers sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(txt, 0, wx.ALL, 5) sizer.Add(wx.StaticLine(self), 0, wx.EXPAND|wx.ALL, 5) fgs = wx.FlexGridSizer(3, 3, 5, 5) fgs.Add(filenameL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.filenameC, 0, wx.EXPAND) fgs.Add(filenameB, 0) fgs.Add(methodL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.methodC, 0, wx.EXPAND) fgs.Add(blank1, 0) fgs.Add(blank2, 0) fgs.Add(self.withdrawC, 0) fgs.Add(blank3, 0) fgs.AddGrowableCol(1) sizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) bs = wx.StdDialogButtonSizer() bs.AddButton(ok) bs.AddButton(cancel) bs.Realize() sizer.Add(bs, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) def OnFilenameSelect(self, event): dlg = wx.FileDialog(self, "Select Input File", "", style=wx.OPEN|wx.CHANGE_DIR) if dlg.ShowModal() != wx.ID_OK: dlg.Destroy() return filename = dlg.GetPath() dlg.Destroy() self.filenameC.ChangeValue(filename) def OnOK(self, event): filename = self.filenameC.GetValue().strip() if filename == "": wx.MessageBox("Please select a filename.", "Message", wx.OK|wx.ICON_INFORMATION) return event.Skip() # do normal OK button processing ################################################################## class WithdrawCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin): def __init__(self, parent, ID): style = wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_HRULES|wx.LC_VRULES wx.ListCtrl.__init__(self, parent, ID, style=style, size=(-1, 200)) listmix.ListCtrlAutoWidthMixin.__init__(self) ################################################################## class WithdrawDialog(wx.Dialog): def __init__(self, parent, b): wx.Dialog.__init__(self, parent, -1, "Withdraw Candidates") self.b = b withdrawTxt = wx.StaticText(self, -1, """\ Candidates with "W" in the first column are withdrawn. Double click on a candidate's name to change the status of the candidate.\ """) self.withdrawC = WithdrawCtrl(self, -1) self.withdrawC.InsertColumn(0, "W") self.withdrawC.InsertColumn(1, "Candidate") for c, name in enumerate(self.b.names): if c in self.b.withdrawn: self.withdrawC.InsertStringItem(c, "W") else: self.withdrawC.InsertStringItem(c, "") self.withdrawC.SetStringItem(c, 1, name) self.withdrawC.SetColumnWidth(0, wx.LIST_AUTOSIZE_USEHEADER) self.withdrawC.SetColumnWidth(1, wx.LIST_AUTOSIZE_USEHEADER) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnListDClick, self.withdrawC) # Buttons ok = wx.Button(self, wx.ID_OK) cancel = wx.Button(self, wx.ID_CANCEL) # Sizers sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(withdrawTxt, 0, wx.ALL, 5) sizer.Add(self.withdrawC, 0, wx.EXPAND|wx.ALL, 5) # Buttons bs = wx.StdDialogButtonSizer() bs.AddButton(ok) bs.AddButton(cancel) bs.Realize() sizer.Add(bs, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) def OnListDClick(self, event): withdrawC = event.GetEventObject() c = event.m_itemIndex if c in self.b.withdrawn: self.b.withdrawn.remove(c) withdrawC.SetStringItem(c, 0, "") else: self.b.withdrawn.append(c) withdrawC.SetStringItem(c, 0, "W") ################################################################## class ElectionOptionsDialog(wx.Dialog): def __init__(self, parent, election): wx.Dialog.__init__(self, parent, -1, "Election Options") self.e = election # Controls for all elections informationBox = wx.StaticBox(self, -1, "Election Information") method1L = wx.StaticText(self, -1, "Method:") method2L = wx.StaticText(self, -1, self.e.e.longMethodName) file1L = wx.StaticText(self, -1, "File:") file2L = wx.StaticText(self, -1, os.path.basename( self.e.dirtyBallots.getFileName())) nBallots1L = wx.StaticText(self, -1, "Number of ballots:") nBallots2L = wx.StaticText(self, -1, str(self.e.dirtyBallots.numBallots)) titleL = wx.StaticText(self, -1, "Title:") self.titleC = wx.TextCtrl(self, -1, "", size=(200, -1)) self.titleC.SetValue(self.e.e.title) dateL = wx.StaticText(self, -1, "Date:") self.dateC = wx.TextCtrl(self, -1, "") self.dateC.SetValue(self.e.e.date) seatsL = wx.StaticText(self, -1, "Seats:") self.seatsC = wx.SpinCtrl(self, -1) self.seatsC.SetRange(1, self.e.cleanBallots.numCandidates-1) self.seatsC.SetValue(self.e.e.numSeats) if self.e.e.onlySingleWinner: self.seatsC.SetValue(1) self.seatsC.Enable(False) widthL = wx.StaticText(self, -1, "Display Width:") self.widthC = wx.SpinCtrl(self, -1) self.widthC.SetRange(0, 200) self.widthC.SetValue(self.e.dispWidth) # Method options labelList = [] self.ctrlList = [] if len(self.e.e.guiOptions) > 0: optionsBox = wx.StaticBox(self, -1, "Method Options") for option in self.e.e.guiOptions: exec(option[0]) # this defines label and control labelList.append(label) self.ctrlList.append(control) # Buttons ok = wx.Button(self, wx.ID_OK) self.Bind(wx.EVT_BUTTON, self.OnOK, ok) cancel = wx.Button(self, wx.ID_CANCEL) # Sizers sizer = wx.BoxSizer(wx.VERTICAL) # Election information informationSizer = wx.StaticBoxSizer(informationBox, wx.VERTICAL) sizer.Add(informationSizer, 0, wx.EXPAND|wx.ALL, 5) fgs = wx.FlexGridSizer(7, 2, 5, 5) fgs.Add(method1L, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(method2L, 0, wx.EXPAND) fgs.Add(file1L, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(file2L, 0, wx.EXPAND) fgs.Add(nBallots1L, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(nBallots2L, 0, wx.EXPAND) fgs.Add(titleL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.titleC, 0, wx.EXPAND) fgs.Add(dateL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.dateC, 0, wx.EXPAND) fgs.Add(seatsL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.seatsC, 0, wx.EXPAND) fgs.Add(widthL, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.widthC, 0, wx.EXPAND) fgs.AddGrowableCol(1) informationSizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) # Method specific options n = len(labelList) if n > 0: optionsSizer = wx.StaticBoxSizer(optionsBox, wx.VERTICAL) sizer.Add(optionsSizer, 0, wx.EXPAND|wx.ALL, 5) fgs = wx.FlexGridSizer(n, 2, 5, 5) for i in range(n): fgs.Add(labelList[i], 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL) fgs.Add(self.ctrlList[i], 0, wx.EXPAND) fgs.AddGrowableCol(1) optionsSizer.Add(fgs, 0, wx.EXPAND|wx.ALL, 5) # Buttons bs = wx.StdDialogButtonSizer() bs.AddButton(ok) bs.AddButton(cancel) bs.Realize() sizer.Add(bs, 0, wx.EXPAND|wx.ALL, 5) self.SetSizer(sizer) sizer.Fit(self) def OnOK(self, event): self.e.e.title = self.titleC.GetValue() self.e.e.date = self.dateC.GetValue() self.e.e.numSeats = self.seatsC.GetValue() self.e.dispWidth = self.widthC.GetValue() for i, option in enumerate(self.e.e.guiOptions): cmd = "self.e.e.%s = self.ctrlList[i].%s" % (option[2], option[1]) exec(cmd) event.Skip() # do normal OK button processing ################################################################## class AboutDialog(wx.Dialog): "Dialog for about OpenSTV box." def __init__(self, parent): wx.Dialog.__init__(self, parent, -1, "About OpenSTV") sizer = wx.BoxSizer(wx.VERTICAL) fn = os.path.join(getHome(), "Icons", "splash.png") bmp = wx.Image(fn, wx.BITMAP_TYPE_PNG).ConvertToBitmap() bm = wx.StaticBitmap(self, -1, bmp) sizer.Add(bm) button = wx.Button(self, wx.ID_OK, "Close") button.SetDefault() sizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5) sizer.Fit(self) self.SetAutoLayout(True) self.SetSizer(sizer) ################################################################## class HTMLFrame(wx.Frame): def __init__(self, parent, title, fName=None, html=None): wx.Frame.__init__(self, parent, -1, title, size=(600, 400)) assert(fName is None or html is None) self.win = wx.html.HtmlWindow(self, -1) if fName is not None: fn = os.path.join(getHome(), fName) self.win.LoadFile(fn) if html is not None: self.win.SetPage(html) ################################################################## class App(wx.App): def OnInit(self): wx.InitAllImageHandlers() # Show a splash screen png = os.path.join(getHome(), "Icons", "splash.png") bmp = wx.Image(png).ConvertToBitmap() wx.SplashScreen(bmp, wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT, 5000, None, -1) self.frame = Frame(None) self.frame.Show(True) self.frame.Center() self.frame.Raise() self.SetTopWindow(self.frame) return True ################################################################## if __name__ == '__main__': app = App(0) app.MainLoop() OpenSTV-1.6.1/openstv/plugins.py0000777000175400010010000002407111402505361015142 0ustar jeffNone"Module to load plugins." ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: plugins.py 776 2010-06-05 17:35:35Z jeff.oneill $" import sys import os.path import textwrap import pkgutil from openstv.utils import getHome ################################################################## class MethodPlugin(object): "Base class used to identify method plugins." status = 0 # 0 is disabled; 1 is level 1; 2 is level 2 htmlBegin = """ %s

%s

""" htmlEnd = """ """ def __init__(self): self.guiOptions = [] def createGuiOptions(self, options): for option in options: if option == "prec": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Precision:") control = wx.SpinCtrl(self, -1) control.SetRange(0, 20) control.SetValue(%d)""" % self.prec, "GetValue()", "prec") ) elif option == "thresh0": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Threshold:") control = wx.Choice(self, -1, choices = ["Droop", "Hare"]) control.SetStringSelection("%s")""" % self.threshName[0], "GetStringSelection()", "threshName[0]") ) elif option == "thresh1": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "") control = wx.Choice(self, -1, choices = ["Dynamic", "Static"]) control.SetStringSelection("%s")""" % self.threshName[1], "GetStringSelection()", "threshName[1]") ) elif option == "thresh2": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "") control = wx.Choice(self, -1, choices = ["Whole", "Fractional"]) control.SetStringSelection("%s")""" % self.threshName[2], "GetStringSelection()", "threshName[2]") ) elif option == "ballotCompletion": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Ballot completion:") control = wx.Choice(self, -1, choices = ["Off", "On"]) control.SetStringSelection("%s")""" % (self.ballotCompletion), "GetStringSelection()", "ballotCompletion") ) elif option == "completionMethod": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Completion method:") choices = ["Borda on Smith Set", "IRV on Smith Set", "Schwartz Sequential Dropping"] control = wx.Choice(self, -1, choices = choices) control.SetStringSelection("%s")""" % self.completion, "GetStringSelection()", "completion") ) elif option == "delayedTransfer": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Delay Surplus Transfer:") control = wx.Choice(self, -1, choices = ["Off", "On"]) control.SetStringSelection("%s")""" % self.delayedTransfer, "GetStringSelection()", "delayedTransfer") ) elif option == "batchElimination": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Candidate elimination:") control = wx.Choice(self, -1, choices = ["None", "Zero", "Losers", "Cutoff"]) control.SetStringSelection("%s")""" % self.batchElimination, "GetStringSelection()", "batchElimination") ) elif option == "batchCutoff": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Batch cutoff:") control = wx.SpinCtrl(self, -1) control.SetRange(0, 10000) control.SetValue(%d)""" % self.batchCutoff, "GetValue()", "batchCutoff") ) elif option == "saveWinnersBallots": self.guiOptions.append( (""" label = wx.StaticText(self, -1, "Save Winners Ballots:") control = wx.CheckBox(self, -1, "") control.SetValue(%s)""" % self.saveWinnersBallots, "GetValue()", "saveWinnersBallots") ) else: assert(0) ################################################################## class LoaderPlugin(object): "Base class used to identify ballot loader plugins." status = 0 extensions = ["blt"] formatName = None def __init__(self): self.fName = "" def reportLoadError(self, msg): msg = "Error when loading %s format ballots. %s"\ % (self.formatName, msg) raise RuntimeError(msg) def normalizeFileName(self, fName): if fName == "": raise RuntimeError, "No file name given for saving ballots." ext = os.path.splitext(fName)[1] if ('' == ext): fName += "." + self.extensions[0] return fName def load(self, ballotList, fName): """Load a file from a filename""" self.fName = fName f = open(self.fName, "r") self.loadFromObject(ballotList, f) f.close() ################################################################## class ReportPlugin(object): "Base class used to identify report loader plugins." status = 0 def __init__(self, e, outputFile=None, test=False): self.e = e self.cleanB = self.e.b self.dirtyB = self.e.b.dirtyBallots if self.dirtyB == None: self.dirtyB = self.cleanB self.outputFile = outputFile self.test = test def output(self, output): """Stream output to destination file-like object.""" print >> self.outputFile, output, def generateReport(self): "Selector for major categories of methods." if self.e.methodName == "Condorcet": self.generateReportCondorcet() elif self.e.iterative: self.generateReportIterative() else: self.generateReportNonIterative() def getWinnerText(self, winners, width=0): """Get the text for the declaration of winners.""" winners = list(winners) winners.sort() if len(winners) == 0: winTxt = "No winners." elif len(winners) == 1: winTxt = "Winner is %s." % self.cleanB.joinList(winners) else: winTxt = "Winners are %s." % self.cleanB.joinList(winners) if width > 0: winTxt = textwrap.fill(winTxt, width) return winTxt def getValuesForRound(self, round): """Get a list of values to print out for one round.""" values = [] display = self.e.displayValue #Round / Stage Number roundStage = round if self.e.methodName == "ERS97 STV": roundStage = self.e.roundToStage(round) values.append("%2d" % (roundStage+1)) # Candidate vote totals for the round for candidate in range(self.cleanB.numCandidates): numVotes = self.e.count[round][candidate] # If candidate has lost and has no votes, leave blank if candidate in self.e.losers and \ self.e.lostAtRound[candidate] <= round and numVotes == 0: values.append("") # otherwise print the total. else: values.append( display(numVotes) ) # Exhausted ballots values.append( display( self.e.exhausted[round] )) # Surplus and Threshold if dynamic if self.e.threshMethod: values.append(display( self.e.surplus[round] )) values.append(display( self.e.thresh[round])) return values ################################################################## def getPlugins(package, baseClass, format, exclude0): """Find plugins of a specified type. Each plugin has a status value that may be 0, 1, or 2. For status=0, the plugin is excluded by default, but will be included if exclude0 is set to False. For Method plugins, status=1 or 2 indicates whether the method appears in the fist tier list or second tier list. For Loader and Report plugins there is only one tier. """ assert(format in ["byName", "classes"]) # Import all modules in package ppath = package.__path__ pname = package.__name__ + "." for importer, modname, ispkg in pkgutil.iter_modules(ppath, pname): module = __import__(modname, fromlist = "dummy") # Look for user-installed plugins externalPluginDir = os.path.join(getHome(), "Plugins") if os.path.exists(externalPluginDir): if externalPluginDir not in sys.path: sys.path.append(externalPluginDir) externalPlugins = [x[:-3] for x in os.listdir(externalPluginDir) if x.endswith(".py")] for plugin in externalPlugins: __import__(plugin) # Get plugin list from subclasses of baseClass pluginClasses = [] for m in baseClass.__subclasses__(): if exclude0 and m.status == 0: del m else: pluginClasses.append(m) if format == "classes": return pluginClasses elif format == "byName": pluginClass = {} for p in pluginClasses: pluginClass[p.__name__] = p return pluginClass else: assert(0) def getMethodPlugins(format, exclude0 = True): import openstv.MethodPlugins return getPlugins(openstv.MethodPlugins, MethodPlugin, format, exclude0) def getReportPlugins(format, exclude0 = True): import openstv.ReportPlugins return getPlugins(openstv.ReportPlugins, ReportPlugin, format, exclude0) def getLoaderPlugins(format, exclude0 = True): import openstv.LoaderPlugins return getPlugins(openstv.LoaderPlugins, LoaderPlugin, format, exclude0) def getLoaderPluginClass(extension, exclude0 = True): "Return the most appropriate loader for a given file extension." plugins = getLoaderPlugins("classes", exclude0) for p in plugins: if extension.lower() in p.extensions: return p return None # No loader for this extension OpenSTV-1.6.1/openstv/qx.py0000777000175400010010000002116111400774167014120 0ustar jeffNone"Quasi-exact fixed-point arthmetic support" ## Copyright 2009-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: qx.py 710 2010-02-22 00:19:20Z jlundell $" from openstv.STV import RecursiveSTV # class QX: quasi-exact fixed-point arithmetic support # # All methods of QX are static. # Set precision and guard through setter methods. # If guard > 0, then QX will use quasi-exact guarded-precision arithmetic # See the appendix of http://www.votingmatters.org.uk/ISSUE24/I24P2.pdf # for a brief description of quasi-exact arithmetic # # maxDiff & minDiff are maintained to help determine whether the guard is sufficiently large # class QX(object): "Fixed-point arithmetic with optional guard digits" precision = 6 guard = 0 p = 10**(precision+guard) g = 10**guard grnd = g/2 geps = g/10 One = p Epsilon = 1 maxDiff = 0 minDiff = p * 100 @staticmethod def set_precision(e, v): "set precision in decimal digits" QX.precision = v QX.p = 10 ** (QX.precision + QX.guard) e.p = QX.p # for report.py QX.One = QX.p QX.maxDiff = 0 QX.minDiff = QX.p * 100 @staticmethod def set_guard(e, v): "set number of decimal guard digits" QX.guard = v QX.g = 10 ** QX.guard QX.grnd = QX.g/2 QX.geps = QX.g/10 QX.set_precision(e, QX.precision) @staticmethod def fix(a): "convert int to fixed point" return a * QX.p @staticmethod def eq(a, b): "return True if a == b; else False" if (QX.guard == 0): return a == b gdiff = abs(a - b) if gdiff < QX.geps and gdiff > QX.maxDiff: QX.maxDiff = gdiff if gdiff >= QX.geps and gdiff < QX.minDiff: QX.minDiff = gdiff return abs(a - b) < QX.geps @staticmethod def lt(a, b): "return True if a < b; else False" return (a < b) and (QX.guard == 0 or not QX.eq(a, b)) @staticmethod def gt(a, b): "return True if a > b; else False" return (a > b) and (QX.guard == 0 or not QX.eq(a, b)) @staticmethod def le(a, b): "return True if a <= b; else False" return (a <= b) or (QX.guard != 0 and QX.eq(a, b)) @staticmethod def ge(a, b): "return True if a >= b; else False" return (a >= b) or (QX.guard != 0 and QX.eq(a, b)) @staticmethod def mult(a, b): "multiply two fixed-point numbers" return a * b / QX.p @staticmethod def div(a, b): "divide two fixed-point numbers" return (a * QX.p) / b @staticmethod def add(a, b): "add two fixed-point numbers (for completeness)" return a + b @staticmethod def sub(a, b): "subtract two fixed-point numbers (for completeness)" return a - b @staticmethod def str(v): "stringify a fixed-point value" if QX.p == 0: return str(v) nfmt = "%d.%0" + str(QX.precision) + "d" # %d.%0_d gv = (v + QX.grnd)/QX.g # round off guard digits return nfmt % (gv/(QX.p/QX.g), gv%(QX.p/QX.g)) @staticmethod def postCount(e, R): "Report QX statistics" e.msg.append("") e.msg[R] = """\ maxDiff: %d (s/b << geps) geps: %d minDiff: %d (s/b >> geps) guard: %d prec: %d """ % ( QX.maxDiff, QX.geps, QX.minDiff, QX.g, QX.p ) ################################################################## class RecursiveQXSTV(RecursiveSTV): """Class that reimplements recursive methods using QX (quasi-exact) arithmetic. No additional attributes. """ def __init__(self, b): RecursiveSTV.__init__(self, b) self.prec = 9 self.strongTieBreakMethod = "random" # break all ties randomly self.weakTieBreakMethod = "strong" # treat all ties as strong self.surplusLimit = QX.Epsilon # A note for debugging via print: # comment out the sys.stderr assignment in Frame.__init__ # and then print to stderr thus to send output to terminal: # print >> sys.stderr, "MeekQX: prec:", prec, "ties:", strongTieBreakMethod # def preCount(self): RecursiveSTV.preCount(self) QX.set_precision(self, self.prec) QX.set_guard(self, self.prec) def displayValue(self, value): "RecursiveQXSTV: Format a value with specified precision." return QX.str(value) def updateThresh(self): "RecursiveQXSTV: Compute the value of the winning threshold." threshNum = QX.fix(self.b.numBallots) - self.exhausted[self.R] self.thresh[self.R] = threshNum/(self.numSeats + 1) def updateWinners(self): "RecursiveQXSTV: Find new winning candidates." winners = [] for c in self.continuing: if QX.gt(self.count[self.R][c], self.thresh[self.R]): winners.append(c) if len(winners) > 0: self.roundInfo[self.R]["winners"] = self.newWinners(winners) def isSurplusToTransfer(self): """RecursiveQXSTV: Decide whether to transfer surplus votes or eliminate candidates.""" if ( QX.eq(self.surplus[self.R-1], 0) or (self.delayedTransfer == "On" and len(self.getSureLosers()) != 0) ): return False else: return True def getSureLosers(self, R=None): "RecursiveQXSTV: Return all candidates who are sure losers." # Return all candidates who are sure losers but do not look at previous # rounds to break ties. if R is None: R = self.R - 1 maxNumLosers = len(self.continuing) + len(self.winners) - self.numSeats assert(maxNumLosers < len(self.continuing)) losers = [] # We need to make sure that all candidates with the same number of votes # are treated the same, so group candidates with the same number of votes # together and sort clusters in order of votes (fewest first). continuing = list(self.continuing) continuing.sort(key=lambda a, f=self.count[R]: f[a]) clusteredContinuing = [[continuing[0]]] for c in continuing[1:]: if QX.eq(self.count[R][c], self.count[R][ clusteredContinuing[-1][0] ]): clusteredContinuing[-1].append(c) else: clusteredContinuing.append([c]) s = self.surplus[R] potentialLosers = [] for i, cluster in enumerate(clusteredContinuing[:-1]): currentClusterCount = self.count[R][ cluster[0] ] nextClusterCount = self.count[R][ clusteredContinuing[i+1][0] ] s += len(cluster) * currentClusterCount potentialLosers += cluster if QX.lt(s, nextClusterCount) and len(potentialLosers) <= maxNumLosers: losers = potentialLosers[:] return losers def findTiedCand(self, cList, mostfewest, function): "RecursiveQXSTV: Return a list of candidates tied for first or last." assert(mostfewest in ["most", "fewest"]) assert(len(cList) > 0) tiedCand = [] # Find a candidate who is winning/losing. He may be tied with others. if mostfewest == "most": cList.sort(key=lambda a, f=function: -f[a]) elif mostfewest == "fewest": cList.sort(key=lambda a, f=function: f[a]) top = cList[0] # first/last place candidate # Find the number of candidates who are tied with him. for c in cList: if QX.eq(function[c], function[top]): tiedCand.append(c) return tiedCand def updateKeepFactors(self): "RecursiveQXSTV: Udpate the candidate keep factors." if len(self.winners) != 0: desc = "Keep factors of candidates who have exceeded the threshold: " winners = [] else: desc = "" candidateList = list(self.continuing | self.winners) candidateList.sort() for c in candidateList: if QX.gt(self.count[self.R-1][c], self.thresh[self.R-1]): self.roundInfo[self.R]["action"][1].append(c) kf, rem = divmod(self.keepFactor[self.R-1][c] * self.thresh[self.R-1], self.count[self.R-1][c]) if rem > 0: kf += QX.Epsilon self.keepFactor[self.R][c] = kf winners.append("%s, %s"\ % (self.b.names[c], self.displayValue(self.keepFactor[self.R][c])) ) else: self.keepFactor[self.R][c] = self.keepFactor[self.R-1][c] if len(self.winners) != 0: desc += self.b.joinList(winners, convert="none") + ". " return desc def postCount(self): "RecursiveQXSTV: Report QX stats if enabled" RecursiveSTV.postCount(self) if False: QX.postCount(self, self.R+1) OpenSTV-1.6.1/openstv/ReportPlugins/0001777000175400010010000000000011407513225015721 5ustar jeffNoneOpenSTV-1.6.1/openstv/ReportPlugins/CsvReport.py0000777000175400010010000001466011400774167020242 0ustar jeffNone"Module for generating reports of election results." ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: report.py 570 2009-08-20 17:46:56Z jeff.oneill $" import string from openstv.version import v as OpenSTV_version from openstv.plugins import ReportPlugin ################################################################## class CsvReport(ReportPlugin): "Return a concise table of election results in CSV format." status = 1 reportName = "csv" def __init__(self, e, outputFile=None, test=False): ReportPlugin.__init__(self, e, outputFile, test) if self.e.methodName == "Condorcet": raise RuntimeError, "CSV report not available for Condorcet elections." def generateHeader(self): if self.test: today = "" v = "" else: from datetime import date today = date.today().strftime("%d %b %Y") v = OpenSTV_version if self.e.methodName == "ERS97 STV": quota = self.e.displayValue(self.e.quota[-1]) elif self.e.threshMethod: quota = self.e.displayValue(self.e.thresh[-1]) else: quota = self.e.displayValue(0) header = """\ "Election for","%s" "Date","%s" "Number to be elected",%d "Valid votes",%d "Invalid votes",%d "Quota",%s "OpenSTV","%s" "Election rules","%s" """ % (self.e.title, today, self.e.numSeats, self.cleanB.numBallots, self.dirtyB.numBallots - self.cleanB.numBallots, quota, v, self.e.longMethodName) self.output(header) def generateReportNonIterative(self): "Return a results sheet in the CVS format used by the ERS." self.generateHeader() out = self.output out("""\ , ,"First" "Candidates","Preferences" """) # candidate lines for c in range(len(self.dirtyB.names)): line = "" if c in self.dirtyB.withdrawn: # Withdrawn candidates appear in the results even though there is # nothing to report. Handle them separately. line += '"%s","Withdrawn",\n' % (self.dirtyB.names[c]) else: # Since we are looping over all candidates, need to convert the index # into the list of non-withdrawn candidates. name = self.dirtyB.names[c] cc = self.cleanB.names.index(name) line += '"%s",%d,' % (self.cleanB.names[cc], 1.0*self.e.count[cc]/self.e.p) if cc in self.e.winners: line += '"Elected"' line += '\n' out(line) # non-transferable and totals out('"Non-transferable", ,\n') out('"Totals",' + str(self.cleanB.numBallots) + '\n') def generateReportIterative(self): "Return a results sheet in the CVS format used by the ERS." self.generateHeader() fmt = "%+." + str(self.e.prec) + "f" if self.e.methodName == "ERS97 STV": nRS = self.e.numStages else: nRS = self.e.numRounds # title lines tl1 = ',' tl2 = ',"First"' tl3 = '"Candidates","Preferences"' for RS in range(1, nRS): tl1 += ',"Stage",%d' % (RS+1) if self.e.methodName == "ERS97 STV": R = self.e.stages[RS][-1] else: R = RS if self.e.methodName == "Bucklin": tl2 += ',"",' tl3 += ',"",' elif self.e.roundInfo[R]["action"][0] == "surplus": tl2 += ',"Surplus of",' surplus = [self.cleanB.names[c] for c in self.e.roundInfo[R]["action"][1]] surplus.sort() tl3 += ',"' + string.join(surplus, '+') + '",' elif self.e.roundInfo[R]["action"][0] == "eliminate": tl2 += ',"Exclusion of",' eliminated = [self.cleanB.names[c] for c in self.e.roundInfo[R]["action"][1]] eliminated.sort() tl3 += ',"' + string.join(eliminated, '+') + '",' else: assert(0) out = self.output out(tl1 + '\n') out(tl2 + '\n') out(tl3 + '\n') # candidate lines for c in range(len(self.dirtyB.names)): if c in self.dirtyB.withdrawn: # Withdrawn candidates appear in the results even though there is # nothing to report. Handle them separately. line = '"%s","Withdrawn",' % (self.dirtyB.names[c]) line += ',,' * (nRS-1) + '\n' out(line) else: # Since we are looping over all candidates, need to convert the index # into the list of non-withdrawn candidates. name = self.dirtyB.names[c] cc = self.cleanB.names.index(name) line = '"%s",%d,' % (self.cleanB.names[cc], self.e.count[0][cc]/self.e.p) for RS in range(1, nRS): if self.e.methodName == "ERS97 STV": R = self.e.stages[RS][-1] prevround = self.e.stages[RS-1][-1] else: R = RS prevround = RS - 1 diff = self.e.count[R][cc] - self.e.count[prevround][cc] if diff == 0: diffstr = '' else: diffstr = fmt % (1.0*diff/self.e.p) count = self.e.count[R][cc] if cc in self.e.losers and self.e.lostAtRound[cc] <= R \ and count == 0: countstr = '"-"' else: countstr = self.e.displayValue(count) line += '%s,%s,' % (diffstr, countstr) if cc in self.e.winners: line += '"Elected"' line += '\n' out(line) # non-transferable line = '"Non-transferable", ,' for RS in range(1, nRS): if self.e.methodName == "ERS97 STV": R = self.e.stages[RS][-1] prevround = self.e.stages[RS-1][-1] else: R = RS prevround = RS - 1 diff = self.e.exhausted[R] - self.e.exhausted[prevround] if diff == 0: diffstr = '' else: diffstr = fmt % (1.0*diff/self.e.p) exh = self.e.exhausted[R] exhstr = self.e.displayValue(exh) line += '%s,%s,' % (diffstr, exhstr) line += '\n' out(line) # totals line = '"Totals",' + str(self.cleanB.numBallots) for RS in range(1, nRS): line += ',,%s' % self.e.displayValue(self.cleanB.numBallots*self.e.p) line += '\n' out(line) OpenSTV-1.6.1/openstv/ReportPlugins/HtmlReport.py0000777000175400010010000001166111400774167020411 0ustar jeffNone"Plugin module for generating a concise report in HTML format." ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: report.py 570 2009-08-20 17:46:56Z jeff.oneill $" import os from openstv.plugins import ReportPlugin from openstv.version import v as OpenSTV_version ################################################################## class HtmlReport(ReportPlugin): "Return a concise table of election results in HTML format." status = 1 reportName = "html" def __init__(self, e, outputFile=None, test=False): ReportPlugin.__init__(self, e, outputFile, test) if self.e.methodName == "Condorcet": raise RuntimeError, "HTML report not available for Condorcet elections." def printTableRow(self, values): """Print a single table line""" out = self.output out("\n") out("%s\n" % (values.pop(0))) for value in values: out("%s\n" % value) out("\n\n") def generateHeader(self): header = """\ %s

%s

OpenSTV version %s (http://www.OpenSTV.org/)

Suggested donation for using OpenSTV for an election is $50. Please go to
http://www.OpenSTV.org/donate to donate via PayPal, Google Checkout, or
Amazon Payments.

Certified election reports are also available. Please go to
http://www.openstv.org/certified-reports for more information.

Loading ballots from file %s.
Ballot file contains %d candidates and %d ballots.
Ballot file contains %d non-empty ballots.

Counting votes for %s using %s.
%d candidates running for %d seat%s.

""" % (self.e.title, self.e.title, "" if self.test else OpenSTV_version, os.path.basename(self.dirtyB.getFileName()), self.dirtyB.numCandidates, self.dirtyB.numBallots, self.cleanB.numBallots, self.e.title, self.e.longMethodName, self.cleanB.numCandidates, self.e.numSeats, "s" if self.e.numSeats > 1 else "" ) self.output(header) def generateReportNonIterative(self): "Pretty print results in html format." self.generateHeader() nCol = self.cleanB.numCandidates nCol += 1 # Exhausted out = self.output out("""\ """) for c in range(self.cleanB.numCandidates): out("\n" % self.cleanB.names[c]) out("\n" % "Exhausted") out("\n\n") out("\n") out("\n") for c in range(self.cleanB.numCandidates): out("\n" % self.e.displayValue(self.e.count[c])) out("\n" % self.e.displayValue(self.e.exhausted)) out("\n\n") out("\n\n" % (nCol, self.e.msg)) out("
Round%s%s
1%s%s
%s
\n\n") # Winners winTxt = self.getWinnerText(self.e.winners) winTxt = winTxt.replace("\n", "
\n") out("

%s

\n\n" % winTxt) out("\n\n") def generateReportIterative(self): "Pretty print results in html format." self.generateHeader() nCol = self.cleanB.numCandidates nCol += 1 # Exhausted if self.e.threshMethod: nCol += 2 # Surplus and Thresh out = self.output out("""\ """) for c in range(self.cleanB.numCandidates): out("\n" % self.cleanB.names[c]) out("\n" % "Exhausted") if self.e.threshMethod: out("\n" % "Surplus") out("\n" % "Threshold") out("\n\n") for R in range(self.e.numRounds): self.printTableRow(self.getValuesForRound(R)) out("\n\n" % (nCol, self.e.msg[R])) out("
Round%s%s%s%s
%s
\n\n") # Winners winTxt = self.getWinnerText(self.e.winners) out("

%s

\n\n" % winTxt) out("\n\n") OpenSTV-1.6.1/openstv/ReportPlugins/MinimalReport.py0000777000175400010010000000516511400774167021075 0ustar jeffNone"Plugin module for generating a minimal report." ## Copyright (C) 2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. from openstv.plugins import ReportPlugin class MinimalReport(ReportPlugin): "Return a minimal text report that is not meant for human consumption." status = 0 reportName = "minimal" def __init__(self, e, outputFile=None, test=False): ReportPlugin.__init__(self, e, outputFile, test) def generateHeader(self): "Header line is a sorted list of winning candidate index numbers." winners = list(self.e.winners) winners.sort() self.output(",".join(str(x) for x in winners) + "\n") def generateReportNonIterative(self): """Minimal report for noniterative methods contains two lines. The first line is a list of winning candidates presented as sorted index numbers. The second line has a fake round number (i.e., "1") followed by a list of candidate counts and exhausted votes.""" self.generateHeader() line = "1," # Fake round number line += ",".join(self.e.displayValue(x) for x in self.e.count) line += ",%s" % self.e.displayValue(self.e.exhausted) self.output(line + "\n") def generateReportIterative(self): """Minimal report for iterative methods contains a winners line and one line for each round. The winners line is a list of winning candidates presented as sorted index numbers. The round lines have a round number followed by a list of candidate counts and exhausted votes.""" self.generateHeader() for r in range(self.e.numRounds): roundStage = r if self.e.methodName == "ERS97 STV": roundStage = self.e.roundToStage(r) line = "%s," % (roundStage + 1) line += ",".join(self.e.displayValue(x) for x in self.e.count[r]) line += ",%s" % self.e.displayValue(self.e.exhausted[r]) self.output(line + "\n") def generateReportCondorcet(self): """Minimal report for Condorcet methods contains a winners line and the pairwise comparison matrix.""" self.generateHeader() for c in range(self.cleanB.numCandidates): self.output(",".join(str(x) for x in self.e.pMat[c]) + "\n") OpenSTV-1.6.1/openstv/ReportPlugins/TextReport.py0000777000175400010010000003007711400774167020433 0ustar jeffNone"Plugin module for generating a concise report in text format." ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: report.py 570 2009-08-20 17:46:56Z jeff.oneill $" import textwrap import math import os from openstv.version import v as OpenSTV_version from openstv.plugins import ReportPlugin ################################################################## class TextReport(ReportPlugin): "Return a concise table of election results in text format." status = 1 reportName = "text" def __init__(self, e, maxWidth=79, style="full", outputFile=None, test=False): ReportPlugin.__init__(self, e, outputFile, test) assert(style in ["full", "table", "round"]) self.maxWidth = maxWidth self.style = style self.maxColWidth = 0 self.prtf = None def format(self, value): return self.pipify( self.e.displayValue( value ) ) def pipify(self, value): """Surround text with pipe""" return ("|" + self.prtf) % value def printTableRow(self, values,width, nSubCol): """Print one 'line' of results--might occupy more than one line of text.""" # Separator line self.output( "=" * width + "\n" ) line = values.pop(0) for (index,value) in enumerate(values): if ((index % nSubCol == 0) and index > 0): line += "\n " line += ("|" + self.prtf) % value line += "\n" self.output(line) def generateWithdrawnText(self): # Create description of withdrawn candidates if len(self.dirtyB.withdrawn) == 0: withdrawnText = "No candidates have withdrawn." elif len(self.dirtyB.withdrawn) == 1: withdrawnText = "Removed withdrawn candidate %s from the ballots."\ % self.dirtyB.withdrawn[0] else: self.dirtyB.withdrawn.sort() withdrawnText = "Removed withdrawn candidates %s from the ballots."\ % self.dirtyB.joinList(self.dirtyB.withdrawn) withdrawnText = textwrap.fill(withdrawnText, width=self.maxWidth) return withdrawnText def generateHeader(self): header = "OpenSTV version %s (http://www.OpenSTV.org/)\n\n" % \ ("" if self.test else OpenSTV_version) # Don't want to modify all of the ref files for this as I'll probably # be reworking testing after this release. if not self.test: header += """\ Suggested donation for using OpenSTV for an election is $50. Please go to http://www.OpenSTV.org/donate to donate via PayPal, Google Checkout, or Amazon Payments. Certified election reports are also available. Please go to http://www.openstv.org/certified-reports for more information. """ if self.dirtyB.getFileName() is not None: header += "Loading ballots from file %s.\n" % \ os.path.basename(self.dirtyB.getFileName()) header += """\ Ballot file contains %d candidates and %d ballots. %s Ballot file contains %d non-empty ballots. Counting votes for %s using %s. %d candidates running for %d seat%s. """ % (self.dirtyB.numCandidates, self.dirtyB.numBallots, self.generateWithdrawnText(), self.cleanB.numBallots, self.e.title, self.e.longMethodName, self.cleanB.numCandidates, self.e.numSeats, "s" if self.e.numSeats > 1 else "" ) self.output( header ) if self.e.optionsMsg != "": self.output( textwrap.fill(self.e.optionsMsg, width=self.maxWidth) + "\n" ) self.output( "\n" ) def setMinColWidth(self, minColWidth=4): # Find the largest value to appear in the report mv = 0 if self.e.methodName == "Condorcet": for i in range(self.cleanB.numCandidates): m = max(self.e.pMat[i]) mv = max(m, mv) elif self.e.methodName == "Borda": m = max(self.e.count) mv = max(m, mv) elif self.e.iterative: if "exhausted" in dir(self.e): m = max(self.e.exhausted) mv = max(m, mv) if "thresh" in dir(self.e): m = max(self.e.thresh) mv = max(m, mv) if "surplus" in dir(self.e): m = max(self.e.surplus) mv = max(m, mv) for i in range(len(self.e.count)): m = max(self.e.count[i]) mv = max(m, mv) else: if "exhausted" in dir(self.e): mv = max(self.e.exhausted, mv) if "thresh" in dir(self.e): mv = max(self.e.thresh, mv) if "surplus" in dir(self.e): mv = max(self.e.surplus, mv) m = max(self.e.count) mv = max(m, mv) # From the max value, compute the minimum column width needed mv /= self.e.p self.maxColWidth = int(math.floor(math.log10(mv))) + 1 if self.e.prec > 0: self.maxColWidth += self.e.prec + 1 self.maxColWidth = max(self.maxColWidth, minColWidth) def setPrintField(self, cw): self.prtf = "%" + str(cw) + "." + str(cw) + "s" # %_._s def generateMatrix(self, matrix): "Return a matrix in text format." nCol = self.cleanB.numCandidates txt = self.prtf % "" # Candidate names for c in range(self.cleanB.numCandidates): txt += self.pipify( self.cleanB.names[c] ) txt += "\n" # Separator line txt += "-"* self.maxColWidth + ("+" + "-" * self.maxColWidth )*nCol + "\n" # For each row, candidate name and matrix values for c in range(self.cleanB.numCandidates): txt += self.prtf % self.cleanB.names[c] for d in range(self.cleanB.numCandidates): txt += self.format( matrix[c][d] ) txt += "\n" return txt def generateReportCondorcet(self): "Generate a text report for an election using Condorcet." self.setMinColWidth() self.setPrintField(self.maxColWidth) self.generateHeader() report = """\ Pairwise Comparison Matrix: %s Smith Set: %s """ % (self.generateMatrix(self.e.pMat), self.cleanB.joinList(self.e.smithSet), ) self.output( report ) if len(self.e.smithSet) == 1: self.output("No completion necessary since the Smith set "\ "has just one candidate.\n") elif self.e.completion == "Schwartz Sequential Dropping": completionMsg = """\ Using Schwartz sequential dropping to choose the winner. Matrix of beatpath magnitudes: %s%s """ % (self.generateMatrix(self.e.dMat), self.e.SSDinfo) self.output(completionMsg) elif self.e.completion == "IRV on Smith Set": self.output("Using IRV to choose the winner from the Smith set.\n\n") R = TextReport(self.e.e, self.maxWidth, "table", outputFile = self.outputFile) R.generateReport() self.output("\n") elif self.e.completion == "Borda on Smith Set": self.output("Using the Borda count to choose the winner "\ "from the Smith set.\n\n") R = TextReport(self.e.e, self.maxWidth, "table", outputFile = self.outputFile) R.generateReport() self.output("\n") self.output(self.getWinnerText(self.e.winners) + "\n") def generateTextRoundResults(self, R, width, nSubCol): self.printTableRow( self.getValuesForRound( R ), width, nSubCol) ## Print message. self.output( " |" + "-" * (width-3) + "\n") line = textwrap.fill(self.e.msg[R], initial_indent=" | ", subsequent_indent=" | ", width=width) self.output( line + "\n" ) def generateReportNonIterative(self): "Pretty print results in text format." self.setMinColWidth(5) self.setPrintField(self.maxColWidth) out = self.output # Include summary information for full results. if self.style == "full": self.generateHeader() # Find length of longest candidate name maxNameLen = 9 # 9 letters in "Exhausted" for c in range(self.cleanB.numCandidates): maxNameLen = max(maxNameLen, len(self.cleanB.names[c])) # Print format strings fmt1 = "%" + str(maxNameLen) + "." + str(maxNameLen) + "s" # %_._s fmt2 = "%" + str(self.maxColWidth) + "." + str(self.maxColWidth) + "s" # %_._s # Header out( (fmt1 + " | " + fmt2 + "\n") % ("Candidate", "Count") ) out("=" * (maxNameLen + 3 + self.maxColWidth) + "\n") # Candidate vote totals for the round for c in range(self.cleanB.numCandidates): out( (fmt1 + " | " + fmt2 + "\n") %\ (self.cleanB.names[c], self.e.displayValue(self.e.count[c]))) # Exhausted ballots out((fmt1 + " | " + fmt2 + "\n") % ("Exhausted", self.e.exhausted) ) # Messages out("\n") line = textwrap.fill(self.e.msg, width=self.maxWidth) line += "\n\n" out(line) # Include winners for full results if self.style == "full": out( self.getWinnerText(self.e.winners, self.maxWidth)) def generateReportIterative(self, R=None): "Pretty print results in text format." self.setMinColWidth() out = self.output # The following determines the actual column width to use that # makes the table ouput compact and evenly distributed # nCol is the total number of columns nCol = self.cleanB.numCandidates nCol += 1 # Exhausted if self.e.threshMethod: nCol += 1 # Surplus nCol += 1 # Thresh # maxnSubCol is the maximum number of columns that can fit in a # single row. This is used to determine how many rows we need. maxnSubCol = (self.maxWidth-2)/(self.maxColWidth+1) # nRow is the number of rows needed to display all of the columns (nRow, r) = divmod(nCol, maxnSubCol) if r > 0: nRow += 1 # nSubCol is the number of columns per row (distrubted evenly across rows) (nSubCol, r) = divmod(nCol, nRow) if r > 0: nSubCol += 1 # colWidth is the width of a column in characters colWidth = (self.maxWidth-2)/nSubCol - 1 # width is the actual width of the table width = 2 + nSubCol*(colWidth+1) # Find length of longest string in the table header maxNameLen = 9 # 9 letters in "Exhausted" for c in range(self.cleanB.numCandidates): maxNameLen = max(maxNameLen, len(self.cleanB.names[c])) # Pad strings for table header to a multiple of colWidth maxNameLen += colWidth - (maxNameLen % colWidth) header = [] for c in range(self.cleanB.numCandidates): header.append(self.cleanB.names[c].ljust(maxNameLen)) header.append("Exhausted".ljust(maxNameLen)) if self.e.threshMethod: header.append("Surplus".ljust(maxNameLen)) header.append("Threshold".ljust(maxNameLen)) # nSubRow is the number of rows needed to display the full candidate names (nSubRow, r) = divmod(maxNameLen, colWidth) if r > 0: nSubRow += 1 self.setPrintField(colWidth) # Include summary information for full results. if self.style == "full": self.generateHeader() if self.style in ["full", "table"]: # Table header for r in range(nRow): for sr in range(nSubRow): line = "" if r == 0 and sr == 0: line += " R" else: line += " " b = sr*colWidth e = b + colWidth for sc in range(nSubCol): h = r*nSubCol + sc if h == len(header): break line += ( "|" + header[h][b:e] ) out(line + "\n") if r < nRow-1: out(" |" + ("-" * colWidth + "+" ) *(nSubCol-1) + "-" *colWidth + "\n") # Rounds for R in range(self.e.numRounds): self.generateTextRoundResults(R, width, nSubCol) out("\n") # Include winners for full results if self.style == "full": out( self.getWinnerText(self.e.winners, width) ) # Generate results for only the specified round if self.style == "round": self.generateTextRoundResults(R, width, nSubCol) # Add optional post-count information if len(self.e.msg) > self.e.numRounds: out("\n\n") out(self.e.msg[self.e.numRounds]) OpenSTV-1.6.1/openstv/ReportPlugins/YamlReport.py0000777000175400010010000000526511400774167020412 0ustar jeffNone"Plugin module for generating a YAML report." ## Copyright (C) 2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. from openstv.plugins import ReportPlugin class YamlReport(ReportPlugin): "Return a YAML report." status = 0 reportName = "YAML" def __init__(self, e, outputFile=None, test=False): ReportPlugin.__init__(self, e, outputFile, test) if self.e.methodName == "Condorcet": raise RuntimeError, "YAML report not available for Condorcet elections." def generateHeader(self): winners = list(self.e.winners) winners.sort() header = "---\nWinners: %s\nRound:\n" % str(winners) self.output(header) def generateReportNonIterative(self): self.generateHeader() tally = str([self.e.displayValue(x) for x in self.e.count] + [self.e.displayValue(self.e.exhausted)]) won = list(self.e.winners) won.sort() won = str(won) lost = list(self.e.losers) lost.sort() lost = str(lost) xfer = "[]" roundLine = """\ - Stage: %d Tally: %s Won: %s Lost: %s Xfer: %s """ % (1, tally, won, lost, xfer) self.output(roundLine) def generateReportIterative(self): self.generateHeader() for r in range(self.e.numRounds): roundStage = r if self.e.methodName == "ERS97 STV": roundStage = self.e.roundToStage(r) index = str(roundStage + 1) tally = ", ".join([self.e.displayValue(x) for x in self.e.count[r]] + [self.e.displayValue(self.e.exhausted[r])]) tally = "[" + tally + "]" won = [c for c in range(self.e.b.numCandidates) if self.e.wonAtRound[c] == r] won.sort() won = str(won) lost = [] if (r < self.e.numRounds - 1) and self.e.roundInfo[r+1]["action"][0] == "eliminate": lost = self.e.roundInfo[r+1]["action"][1] lost.sort() lost = str(lost) xfer = [] if (r < self.e.numRounds - 1) and self.e.roundInfo[r+1]["action"][0] == "surplus": xfer = self.e.roundInfo[r+1]["action"][1] xfer.sort() xfer = str(xfer) roundLine = """\ - Stage: %s Tally: %s Won: %s Lost: %s Xfer: %s """ % (index, tally, won, lost, xfer) self.output(roundLine) OpenSTV-1.6.1/openstv/ReportPlugins/__init__.py0000777000175400010010000000005611400774167020044 0ustar jeffNone# dummy file to make this directory a package OpenSTV-1.6.1/openstv/runElection.py0000777000175400010010000000677211400774167015772 0ustar jeffNone#!/usr/bin/env python "run an election from the command line with optional profiling" __revision__ = "$Id: runElection.py 715 2010-02-27 17:00:55Z jeff.oneill $" import sys import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import getopt from openstv.ballots import Ballots from openstv.plugins import getMethodPlugins, getReportPlugins methods = getMethodPlugins("byName", exclude0=False) methodNames = methods.keys() methodNames.sort() reports = getReportPlugins("byName", exclude0=False) reportNames = reports.keys() reportNames.sort() usage = """ Usage: runElection.py [-p prec] [-r report] [-t tiebreak] [-w weaktie] [-s seats] [-P] [-x reps] method ballotfile -p: override default precision (in digits) -r: report format: %s -t: strong tie-break method: random*, alpha, index -w: weak tie-break method: (method-default)*, strong, forward, backward -s: number of seats (for text-format ballot files) -P: profile and send output to profile.out -x: specify repeat count (for profiling) *default Runs an election for the given method and ballot file. Results are printed to stdout. The following methods are available: %s """ % (", ".join(reportNames), "\n".join([" " + name for name in methodNames])) # Parse the command line. try: (opts, args) = getopt.getopt(sys.argv[1:], "Pp:r:s:t:w:x:") except getopt.GetoptError, err: print str(err) # will print something like "option -a not recognized" print usage sys.exit(1) profile = False reps = 1 reportformat = "TextReport" strongTieBreakMethod = None weakTieBreakMethod = None numSeats = None prec = None for o, a in opts: if o == "-r": if a in reportNames: reportformat = a else: print "Unrecognized report format '%s'" % a print usage sys.exit(1) if o == "-p": prec = int(a) if o == "-s": numSeats = int(a) if o == "-t": if a in ["random", "alpha", "index"]: strongTieBreakMethod = a else: print "Unrecognized tie-break method '%s'" % a print usage sys.exit(1) if o == "-w": if a in ["strong", "forward", "backward"]: weakTieBreakMethod = a else: print "Unrecognized weak tie-break method '%s'" % a print usage sys.exit(1) if o == "-P": import cProfile import pstats profile = True profilefile = "profile.out" if o == "-x": reps = int(a) if len(args) != 2: if len(args) < 2: print "Specify method and ballot file" else: print "Too many arguments" print usage sys.exit(1) name = args[0] bltFn = args[1] if name not in methodNames: print "Unrecognized method '%s'" % name print usage sys.exit(1) try: dirtyBallots = Ballots() dirtyBallots.loadKnown(bltFn, exclude0=False) if numSeats: dirtyBallots.numSeats = numSeats cleanBallots = dirtyBallots.getCleanBallots() except RuntimeError, msg: print msg sys.exit(1) def doElection(reps=1): "run election with repeat count for profiling" for i in xrange(reps): e = methods[name](cleanBallots) if strongTieBreakMethod is not None: e.strongTieBreakMethod = strongTieBreakMethod if weakTieBreakMethod is not None: e.weakTieBreakMethod = weakTieBreakMethod if prec is not None: e.prec = prec e.runElection() return e if profile: cProfile.run('e = doElection(reps)', profilefile) else: e = doElection() r = reports[reportformat](e) r.generateReport() if profile: p = pstats.Stats(profilefile) p.strip_dirs().sort_stats('time').print_stats(50) OpenSTV-1.6.1/openstv/spars.py0000777000175400010010000006560511400774167014633 0ustar jeffNone"""Model voters with ranked ballot data and count with various voting systems. VOTING MODELS There are three different voting models. (1) Probability Distribution If there N candidates, then there are O(N!) possible ballots. Ballots need not rank every candidate. The model is a probability distribution where each ballot has a certain probability of occurring. Thus, there are O(N!) parameters in this model. These parameters can be estimated by simply counting the occurrences of each ballot. In one sense, this model has all the information necessary for modeling voter preferences, but the number of parameters is prohibitive. (2) Bigrams Voters are modeled using first-place probabilities and bigram probabilities. First-place probabilities are the probability that a candidate will be first on the ballot. Bigram probabilities are the conditional probabilities that a candidate will be ranked next on the ballot given the current ranking. Note that the probability that a candidate will appear next given that she is also the current candidate must be zero in this model. There are O(N^2) parameters. One can also choose to include in the model the probability that there is no next candidate (none of the above or NOTA) given the current candidate or, alternatively, choose not to model NOTA. The bigram probabilities can be applied directly to model the second choices on the ballot. However, to model the third choices on the ballot, one must take into account the fact that the first choice can not appear again as the third choice. I have chosen to take this into account by removing previous candidates from the probability distribution and renormalizing so that the remaining probabilities sum to one. There are other logical alternatives that may be implemented in the future. The first-place probabilities can be estimated by simply counting the number of times a candidate is ranked first. They are represented as p1[c]. There are two options for estimating bigrams: (a) Simply counting the number of occurrences of each pair of first and second choices. (b) Counting all pairs of consecutive rankings and using the Expectation-Maximization algorithm to compute the bigrams. They are represented as p2[c][d] where c is the current ranking and d is the next ranking. On one hand, this model loses informations since trigrams and other n-grams will contain information that is lacking with just bigrams. On the other hand, there is redundancy in the separate representations of p2[c][d] and p2[d][c] that could possibly be eliminated. (3) Spatial Parameters This parameterization models voters by representing them as a point in D-dimensional space. The median voter is at the origin and the voters are distributed as a D-dimensional unit gaussian around the origin. The candidates are also points in the space. A voter will rank the closest candidate first on the ballot and rank successive candidates accordingly. Note that the axes represent unspecified ideologies. There are O(ND) parameters where D ranges from 1 to N-1. Larger D add redundancy without adding precision to the model. TRANSITION BETWEEN MODELS Some transitions are invertible in the limit as the number of ballots increases to infinity. These are marked as invertible. Bigrams -> Ballots: Ballots.generate() Ballots are generated randomly according to the first-place and bigram probabilities. Invertible. Ballots -> Bigrams: Bigrams.estimate() Bigrams are estimated with the EM algorithm. Spars -> Bigrams: Bigrams.generate() The gaussian space is tessellated into areas of equal probability. Each area is assigned to the appropriate bigram to estimate the bigram probabilities. Invertible? Bigrams -> Spars: Spars.estimate() Optimization methods. Spars -> Ballots: Ballots.generate() Voters are generated randomly in the D-dimensional space and ballots are created according to the proximity of the candidates to the voter. Invertible? Ballots -> Spars: Spars.estimate() ??? """ ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: spars.py 693 2010-01-03 19:12:05Z jeff.oneill $" import re import string import math import os.path import time import random from types import * import STV NOTA = 1 noNOTA = 0 eps = STV.eps ################################################################## def d2(x, y): "Compute the euclidean distance between two vectors." assert(len(x) == len(y)) d = sum( [ math.pow(x[i]-y[i], 2) for i in range(len(x)) ] ) d = math.sqrt(d) return d def norm2(x): "Compute the 2 norm of a vector." n = sum( [ math.pow(x[i], 2) for i in range(len(x)) ] ) n = math.sqrt(n) return n def entropy(x): "Compute the entropy of a probability distribution." e = 0.0 assert(abs(sum(x)-1) < eps) for p in x: assert(p >= 0 and p <= 1) if p > 0: e -= p * math.log(p) return e def probDist(n, low = 0.8, high=1.0): """Generate a probability distribution randomly in an entropy range. Returns a vector of length n where each element is in [0,1] and the vector sums to 1. The vector will be in an entropy range bounded by low and high. Low and high correspond to fractions of the maximum possible entropy, which occurs for the vector where all elements are 1/n. The most obvious way to generate the probabilities would be: x = [] for i in range(n-1): x.append(random.uniform(0, 1-sum(x)) x.append(1-sum(x)) However, this is quite unsatisfactory since the random variables are not independent. A better way is to generate angles and convert them to the probabilities. sum{p[i]} = 1 tan(a[i]) = p[i]/p[n] for i != n thus 1 p[n] = -------------------- 1 + sum{ tan(a[i]) } p[i] = p[n] * tan(a[i]) for i != n This is only an approximation to a uniform distribution but it is accurate for higher entropies. The rejection method is used to get probabilities in the desired entropy range.""" if high > 1.0 or high < low or low < 0.0: raise RuntimeError, "high and low must be in [0,1]." eMax = entropy([1.0/n]*n) eLow = eMax * low eHigh = eMax * high e = -1 while not (e < eHigh and e > eLow): angles = [] for i in range(n-1): angles.append(random.uniform(0, math.pi/2)) sumtan = sum([math.tan(i) for i in angles]) x = [1.0/(1+sumtan)] for i in range(n-1): x.append(x[0]*math.tan(angles[i])) e = entropy(x) return x def pickOne(p, candidates): """Randomly pick one thing from a list according to a prob dist. p is a probability distribution that can be an array or a dictionary. candidates will be a subset of the array indices or the dictionary keys. Renormalizes the probability distribution according the candidate subset and returns one of the candidates. If NOTA is used it should be one of the candidates. If all the candidates have probability 0 then return "".""" s = 0 for c in candidates: s += p[c] x = random.uniform(0, s) a = 0 for c in candidates: if x >= a and x < a + p[c]: return c a += p[c] # This will happen if all candidates have probability 0. return "" ################################################################## class Spars: """Class for working with a spatial parameterization. Initialize with Spars(d) where d is the number of dimensions. c is an array containing the names of the candidates. p is a dictionary containing the parameters which are coordinates in the d dimensional space.""" def __init__(self, d=2): self.pars = "spars" self.d = d # dimensions self.c = [] # candidates self.p = {} # coordinates (parameters) self.bins = [] self.ngrid = 0 ### def genCoord(self, a=1): "Generate a coordinate from Gaussian(0,a) distribution." p = [] for i in range(self.d): p.append(random.gauss(0, a)) return p def random(self, n, a=0.5, norm='none'): "Generate coordinates for n candidates from a Gaussian distribution." self.c = [] self.p = {} for i in range(n): self.c.append(str(i)) self.p[str(i)] = self.genCoord(a) if norm == 'none': return elif norm == 'last': last = self.c[-1] self.p[last] = [0] * self.d for c in self.c[0:-1]: for d in range(self.d): self.p[last][d] -= self.p[c][d] elif norm == 'shift': mean = [0] * self.d for d in range(self.d): for c in self.c: mean[d] += self.p[c][d] mean[d] /= len(self.c) for c in self.c: self.p[c][d] -= mean[d] else: raise RuntimeError, "No such normalization." def orderByDistance(self, x): "Return a list of canidates in order of proximity to a point." cList = [] available = self.c[:] while available != []: minDist = 1e10 closest = "" # choose the closest candidate to the voter for c in available: dist = d2(x, self.p[c]) if dist < minDist: closest = c minDist = dist assert(closest != "") cList.append(closest) available.remove(closest) return cList def grid(self, n): "Create a grid of equiprobable regions for a 2D gaussian." self.ngrid = n*n # compute the centers of bins in the radial direction r = [] for i in range(1, 2*n, 2): p = 1.0*i/2/n # P within a disk of radius R r.append( math.sqrt(-2*math.log(1-p)) ) # compute the centers of bins in angle a = [] for i in range(n): a.append( 2*math.pi*i/n) # convert these to x,y coords self.bins = [] for i in range(n): for j in range(n): x = r[i]*math.cos(a[j] + (i%2)*math.pi/n) y = r[i]*math.sin(a[j] + (i%2)*math.pi/n) self.bins.append([x, y]) def estimate(self, g): "Estimate spatial parameters from bigrams." # also do this from ballots def display(self, ybins=10, cutoff=1.0, v=""): """Display candidates in two-dimensions using ascii characters. 2*ybins+1 is the number of vertical ascii characters to use. xbins is set to 2*ybins and similarly 2*xbins+1 is the number of horizontal characters to use. This gives a decent aspect ratio with most default fonts. cutoff is the value at the largest xbin and ybin.""" xbins = 2*ybins # gives a decent aspect ratio if self.d != 2: raise RuntimeError, "Only implemented for two dimensions." # create the background axis graph = [] for i in range(2*ybins+1): if i == ybins: row = (["-"] * xbins) + ["+"] + (["-"] * (xbins-1)) + ["-"] else: row = ([" "] * xbins) + ["|"] + ([" "] * (xbins-1)) + [" "] graph.append(row) # put the candidates on the graph for c in self.c: x = int(round(self.p[c][0]*xbins/cutoff)) x = max(x, -xbins) x = min(x, xbins) x += xbins y = -int(round(self.p[c][1]*ybins/cutoff)) y = max(y, -ybins) y = min(y, ybins) y += ybins graph[y][x] = c[-1] # if specified, put some voters on the graph too if v != "": x = int(round(v[0]*xbins/cutoff)) x = max(x, -xbins) x = min(x, xbins) x += xbins y = -int(round(v[1]*ybins/cutoff)) y = max(y, -ybins) y = min(y, ybins) y += ybins graph[y][x] = "*" # display the graph for i in range(2*ybins+1): print string.join(graph[i], "") ################################################################## class Bigrams: """Class for working with bigram probabilities. Initialize with Bigrams(NOTA) or Bigrams(noNOTA). NOTA -- Can be 0 or 1. If 1 then p2[c][NOTA] is used, otherwise it is not. fName -- File name where the bigram data is stored. c -- List of all the possible candidates. p1 -- Dictionary that gives the probability that a candidate will be first on a ballot. P(c is first) = p1[c] p2 -- Dictionary that gives the conditional probability for the subsequent candidate on the ballot. P(d is next candidate|c is current candidate) = p2[c][d] plength -- Probabilities for the length of each ballot. comment -- Some information as to where the data came from. """ def __init__(self, mode): self.pars = "bigrams" self.NOTA = mode self.fName = "" self.c = [] self.plength = [] self.p1 = {} self.p2 = {} self.comment = "" def load(self, fName): "Load bigrams from a file." self.fName = fName self.comment = "Bigrams from %s." % self.fName f = open(self.fName, "r") x = f.readline() x = x.strip() if x == "NOTA = 1": self.NOTA = 1 elif x == "NOTA = 0": self.NOTA = 0 else: raise RuntimeError, "Bad format in bigram file: %s" % (x) # load candidate names x = f.readline() self.c = x.split() n = len(self.c) # load probability of ballot lengths self.plength = [0]*n for i in range(n): x = f.readline() y = re.match(r"\s*P\s*\(\s*len\s*=\s*(\d+)\s*\)\s*=\s*(0?\.\d+)", x) if y is None: raise RuntimeError, "Bad format in bigram file %s: %s" % (fName, x) if int(y.group(1)) != i+1: raise RuntimeError, "Bad format in bigram file %s: %s" % (fName, x) p = float(y.group(2)) self.plength[i] = p # check that they add to 1 s = sum(self.plength) if abs(s-1) > eps: raise RuntimeError, "Length probabilities must sum to exactly 1." # load p1 and p2 for i in range(n): x = f.readline() y = re.match(r"\s*P\s*\(\s*(\S+)\s*\)\s*=\s*(0?\.\d+)", x) if y is None: raise RuntimeError, "Bad format in bigram file %s: %s" % (fName, x) c = y.group(1) p = float(y.group(2)) if c not in self.c: raise RuntimeError, "Bad format in bigram file %s: %s" % (fName, x) self.p1[c] = p self.p2[c] = {} for j in range(n-1): x = f.readline() y = re.match(r"\s*P\s*\(\s*(\S+)\s*\|\s*(\S*)\s*\)\s*=\s*(0?\.\d+)", x) if y is None: raise RuntimeError, "Bad format in bigram file %s: %s" % (fName, x) if c != y.group(2): raise RuntimeError, "Bad format in bigram file %s: %s" % (fName, x) c2 = y.group(1) p = float(y.group(3)) if (c2 not in self.c) or (c == c2): raise RuntimeError, "Bad format in bigram file %s: %s" % (fName, x) self.p2[c][c2] = p f.close() # add NOTA if necessary if self.NOTA: for c in self.c: s = sum(self.p2[c].values()) if s > 1: raise RuntimeError, "Conditional probabilities for %s sum to greater than 1." % (c) self.p2[c]["NOTA"] = 1 - s s = sum(self.p1.values()) if abs(s-1) > eps: raise RuntimeError, "First place probabilities must sum to exactly 1." for c in self.c: s = sum(self.p2[c].values()) if abs(s-1) > eps: raise RuntimeError, "Conditional probabilities for %s must sum to exactly 1." % (c) def save(self, fName): "Save bigrams to a file." self.fName = fName if os.path.exists(fName): raise RuntimeError, "I won't overwrite the file: " + fName f = open(fName, "w") if self.NOTA: f.write("NOTA = 1\n") else: f.write("NOTA = 0\n") f.write(string.join(self.c) + "\n") for i in range(len(self.c)): f.write("P(len=%d) = %s\n" % (i+1, str(self.plength[i]))) for c in self.c: f.write("P(%s) = %s\n" % (c, str(self.p1[c]))) for d in self.c: if c == d: continue f.write(" P(%s|%s) = %s\n" % (d, c, str(self.p2[c][d]))) f.close() def display(self): "Print the bigrams in a nice format." print " " * 8 + "|", for c in self.c: print "%-5.5s" % (c), if self.NOTA: print "NOTA" else: print print ("-" * 8) + "+" + "-" * (6*(len(self.c)-1+self.NOTA) + 6) print "%-8.8s|" % "plen", tmp = self.plength[:] for c in self.c: print "%.3f" % tmp.pop(0), print print ("-" * 8) + "+" + "-" * (6*(len(self.c)-1+self.NOTA) + 6) print "%-8.8s|" % "first", for c in self.c: print "%.3f" % self.p1[c], print print ("-" * 8) + "+" + "-" * (6*(len(self.c)-1+self.NOTA) + 6) for c in self.c: print "%-8.8s|" % c, for d in self.c: if c == d: print " ", else: print "%.3f" % (self.p2[c][d]), if self.NOTA: print "%.3f" % self.p2[c]["NOTA"] else: print print def initP2Dict(self): "Initializes a dict for the bigram probabilities." p2 = {} for c in self.c: p2[c] = {} choices = self.c[:] if self.NOTA: choices.append("NOTA") choices.remove(c) for d in choices: p2[c][d] = 0.0 return p2 def maxP2Diff(self, p2, q2): "Computes the maximum difference between two sets of bigrams." maxDiff = abs(p2[self.c[0]][self.c[1]] - q2[self.c[0]][self.c[1]]) for c in self.c: for d in p2[c].keys(): diff = abs(p2[c][d] - q2[c][d]) if diff > maxDiff: maxDiff = diff return maxDiff def estimate(self, ballots, levels=1, verbose=0): "Estimate bigrams from ballot data." self.c = ballots.c self.comment = "Bigrams estimated from ballot data.\n" self.comment += "Ballot data file was: " + ballots.fName + ".\n" self.comment += "Comments from that file are:\n" self.comment += ballots.comment # # Estimate first place probabilities and count ballot lengths # count = {} self.p1 = {} for c in self.c: count[c] = 0 countlength = [0]*len(self.c) self.plength = [0]*len(self.c) for weight, ballot in ballots.getWeightedBallots(): count[ballot[0]] += weight countlength[len(ballots) - 1] += weight for c in self.c: self.p1[c] = 1.0*count[c]/ballots.n for i in range(len(self.c)): self.plength[i] = 1.0*countlength[i]/ballots.n # # Estimate conditional probabilities # if ( (type(levels) is not IntType) or (levels not in range(1, len(self.c)-1)) ): raise RuntimeError, "Parameter 'levels' must be an integer between 1 and len(candidates)-2, inclusive." # Fill in big data structure with all partial counts par_count = [] for level in range(levels): par_count.append({}) for weight, ballot in ballots.getWeightedBallots(): if ( (self.NOTA and len(ballots) < level+1) or (not self.NOTA and len(ballots) < level+2) ): continue # ballot is too short current = ballot[level] if not par_count[level].has_key(current): par_count[level][current] = {} if level == 0: previous = "" else: tmp = ballot[:level] tmp.sort() previous = string.join(tmp) if not par_count[level][current].has_key(previous): par_count[level][current][previous] = {} if len(ballot[i]) < level+2: next = "NOTA" else: next = ballot[level+1] if not par_count[level][current][previous].has_key(next): par_count[level][current][previous][next] = 0 par_count[level][current][previous][next] += weight # Initialize with a uniform distribution. # Can't initialize with level 0 data because zeros in the p2 # matrix can cause nasty singularities (see below). self.p2 = self.initP2Dict() for c in self.c: for d in self.p2[c].keys(): self.p2[c][d] = 1.0/len(self.p2[c].keys()) while(1): # E-Step: Use the current estimate to fill in missing counts. # # let i,j iterate over candidates with missing counts # let c[i] be the missing counts that we need to compute # let p[i] be the current estimate for the cond. prob. (p2[c][d]) # let sc be the sum of the known counts # compute the missing counts as: # c[i] = p[i] * sc # ------------- # 1 - sum(p[j]) count = self.initP2Dict() for level in range(levels): for c in par_count[level].keys(): for prev in par_count[level][c].keys(): # add partial counts for d in par_count[level][c][prev].keys(): count[c][d] += par_count[level][c][prev][d] missing = prev.split() # compute sc from above sc = sum(par_count[level][c][prev].values()) # compute sum(p[j]) from above sp = 0 for d in missing: sp += self.p2[c][d] # compute and add missing counts assert(sp < 1.0) for d in missing: count[c][d] += 1.0 * self.p2[c][d] * sc / (1 - sp) # This will fail if p2[Jim][Mary] = 1.0 after level=0 # and we get a ballot like [Mary Jim Alice] for level=1. # This can happen if we initialize with level 0 data # but shouldn't happen if we initialize with uniform data. # M-Step: Use the counts to re-estimate. p2 = self.initP2Dict() for c in self.c: s = sum(count[c].values()) for d in p2[c].keys(): p2[c][d] = 1.0 * count[c][d] / s maxDiff = self.maxP2Diff(self.p2, p2) self.p2 = p2 if verbose: self.display() if maxDiff < 0.001 or levels == 1: break def generate(self): "Compute bigrams from spars." g = Bigrams(noNOTA) g.c = self.c for c in g.c: g.p1[c] = 0.0 g.p2 = g.initP2Dict() g.plength = [0.0] * len(g.c) for i in range(len(self.bins)): cList = self.orderByDistance(self.bins[i]) first = cList[0] second = cList[1] g.p1[first] += 1 g.p2[first][second] += 1 for c in self.c: g.p1[c] /= self.ngrid p2sum = sum(g.p2[c].values()) for d in g.p2[c].keys(): g.p2[c][d] /= p2sum return g def random(self, candidates, low=0.8, high=1.0, maxNOTA=0.05): "Generate bigrams randomly in an entropy range." self.c = candidates self.comment = "Generated randomly with entropy range [%3.1f, %3.1f]" % (low, high) self.p1 = {} x = probDist(len(candidates), low, high) for c in candidates: self.p1[c] = x.pop() e = entropy(self.p1.values()) # check for validity! self.p2 = self.initP2Dict() for c in candidates: if self.NOTA: self.p2[c]["NOTA"] = random.uniform(0, maxNOTA) x = probDist(len(candidates)-1, low, high) for d in candidates: if c == d: continue self.p2[c][d] = x.pop() if self.NOTA: self.p2[c][d] *= (1 - self.p2[c]["NOTA"]) e = entropy(self.p2[c].values()) # check for validity! ################################################################## class Ballots(STV.Ballots): """Class for working with ballot data. fName -- File name where the ballot data is stored. c -- List of all the possible candidates. n -- Number of ballots. raw -- List of all ballots. packed -- List of unique ballots. weight -- Number of times that each packed ballot occurred. comment -- Some information as to where the data came from. """ def generate(self, n, model): "Wrapper routine for generating ballots from different models." self.n = n # number of ballots to generate self.c = model.c self.nCand = len(self.c) self.raw = [] self.comment = "Ballots generated randomly on " self.comment += time.strftime("%x %X %Z") + ".\n" self.comment += "Parameters are %s.\n" % (model.pars) for i in range(n): if model.pars == "bigrams": ballot = self.genFromBigrams(model) elif model.pars == "spars": ballot = self.genFromSpars(model) else: raise RuntimeError, "Can't generate ballots from this object." self.raw.append(ballot) self.pack() def genFromBigrams(self, bigrams): """Generate one ballot from bigram probabilities. For bigrams without NOTA, there is no modelling of the ballot length so the ballots will generally rank all of the candidates. A candidate will not be listed only if p[c][d]=0. I should think of a good way to model ballot length when NOTA isn't used. For bigrams with NOTA, the ballot length is easy.""" ballot = [] available = self.c[:] c = pickOne(bigrams.p1, available) assert(c != "NOTA" and c != "") if bigrams.NOTA: available.append("NOTA") while c != "NOTA" and c != "": ballot.append(c) available.remove(c) c = pickOne(bigrams.p2[c], available) return ballot def genFromSpars(self, spar): """Generate one ballot from spatial parameters. There is no modeling of the ballot length so the ballots will rank all of the candidates. I should think of a good way of modeling ballot length.""" x = spar.genCoord() # get the coordinate for the voter ballot = spar.orderByDistance(x) return ballot ### def KLDist(self, b, r=1): """Compute the "distance" between two sets of ballots. Compute the "distance" between ballots self.b and b by modeling them as probability distributions and using the Kullback-Leibler divergence measure. r is the number or rankings to use in creating the probability distributions. The KL divergence is not symmetric so b1.KLDist(b2) is different from b2.KLDist(b1). The KL divergence goes to infinity if for any x, q[x] = 0 and p[x] != 0. To avoid this, q is initialized with a count of 1 instead of 0. This is somewhat like using a Bayesian estimate and imposing a prior uniform distribution. This is a hack and should be reexamined. """ if self.c != b.c: raise RuntimeError, "Ballots have different candidates." nc = len(self.c) # estimate the probability distribution for self count1 = [0] * pow(nc, r) # infinity not a problem here for weight, ballot in self.getWeightedBallots(): if len(ballot) < r: continue index = 0 for j in range(r): index += self.c.index(ballot[j]) * pow(nc, j) count1[index] += weight s1 = 1.0*sum(count1) # estimate the probability distribution for b count2 = [1] * pow(nc, r) # avoid infinity! for weight, ballot in b.getWeightedBallots(): if len(ballot) < r: continue index = 0 for j in range(r): index += b.c.index(ballot[j]) * pow(nc, j) count2[index] += weight s2 = 1.0*sum(count2) # compute the KL divergence I = 0 for i in range(len(count1)): if count1[i] > 0: I += count1[i]/s1 * math.log( (count1[i]/s1) / (count2[i]/s2) ) return I OpenSTV-1.6.1/openstv/STV.py0000777000175400010010000015065711400774167014161 0ustar jeffNone"""Module that provides code that can be used for different counting methods. Class ElectionMethod Class NonIterative Class Iterative Class STV Class OrderDependentSTV Class OrderIndependentSTV Class NoSurplusSTV Class GregorySTV Class WeightedInclusiveSTV Class RecursiveSTV """ ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: STV.py 717 2010-02-28 19:29:21Z jeff.oneill $" import random ################################################################## class ElectionMethod(object): """Base class for all election methods. This class provides code that can be used for many different methods and is not a complete election method. Subclasses of this class create categories of election methods, and futher subclasses will create actual election methods. Arguments: The only argument to the constructor is the Ballots object continaing the votes to be counted. Class Attributes: methodName -- Shorter method name used internally. longMethodName -- Longer method name used in generating reports. onlySingleWinner -- Some methods can only be used to elect one person. iterative -- Methods are either iterative or are not. threshMethod -- Some methods have a threshold for election. Instance Attributes: b -- The Ballots object passed in the constructor. numSeats -- The number of seats to be filled. title -- A title for the election. date -- A date for the election. withdrawn -- A list of candidate numbers of withdrawn candidates. losers -- Index numbers of candidates who have been eliminated. winners -- Index numbers of winning candidates. continuing -- Index numbers of candidates who are not losers or winners. strongTieBreakMethod -- A strong tie is one that is broken externally to the count (contrast with weak ties in Iterative methods). Allowable values are "random", "alpha", "index", and "manual". breakTieRequestQueue, breakTieResponseQueue -- These are used to manually break ties from a GUI. The counting is done in a thread, and when a tie needs to be broken, the counting thread puts a request on the request queue. The main (GUI) thread asks the user to break the tie and puts the result on the response queue. prec -- The number of digits of precision for certain methods (e.g., Meek, Gregory, and Weighted Inclusive). p -- A scale factor for doing fixed-point computations. Set to 10**prec. guiOptions -- As election methods are plugins, this tells a GUI what options to present to the user and how to do it. optionsMsg -- Stores test describing options used in the method for reporting purposes. """ methodName = None longMethodName = None onlySingleWinner = False iterative = None threshMethod = None def __init__(self, b): # Get information from ballot file. Copy some attributes into the # elections instance since the user may change them. self.b = b self.numSeats = b.numSeats self.title = b.title self.date = b.date # Defaults for options self.strongTieBreakMethod = "random" self.breakTieRequestQueue = None # overridden if manual tiebreaking self.breakTieResponseQueue = None # overridden if manual tiebreaking self.prec = 0 self.p = 1 self.guiOptions = [] self.optionsMsg = "" self.winners = set() self.losers = set() self.continuing = set(range(self.b.numCandidates)) def runElection(self): self.preCount() self.countBallots() self.postCount() def preCount(self): assert(self.strongTieBreakMethod in ["random", "alpha", "index", "manual"]) self.p = 10**self.prec # Scale factor for computations # Check for sufficient candidates and ballots self.checkMinRequirements() def countBallots(self): raise NotImplementedError def postCount(self): pass def displayValue(self, value): "Format a value with specified precision." if self.prec == 0: return str(value) nfmt = "%d.%0" + str(self.prec) + "d" # %d.%0_d return nfmt % (value/self.p, value%self.p) def checkMinRequirements(self): "Only attempt to count votes if there are enough candidates and voters." # some basic minimum requirements if (self.b.numCandidates < 2 or self.b.numCandidates <= self.numSeats): raise RuntimeError, """\ Not enough candidates to run an election. Need at least %d candidates but have only %d.""" % ( max(2, self.numSeats+1), self.b.numCandidates) if self.numSeats < 1: raise RuntimeError, "The number of seats must be at least 1." if self.b.numBallots <= self.numSeats: raise RuntimeError, """\ Not enough ballots to run an election. Need at least %d ballots but have only %d.""" \ % (self.numSeats+1, self.b.numBallots) def findTiedCand(self, candidateList, mostfewest, values): """Return a list of candidates tied for first or last. candidateList is the list of candidates to be considered. mostfewest is "most" of "fewest" and indicates whether we are looking for candidates tied for first or last. values is the metric to be used for comparing candidates. Often it will be self.count[r], but it could be anything. """ assert(mostfewest in ["most", "fewest"]) assert(len(candidateList) > 0) getMin = (mostfewest == "fewest") candidateListValues = [values[c] for c in candidateList] if getMin: maxMinValue = min(candidateListValues) else: maxMinValue = max(candidateListValues) return [c for c in candidateList if values[c] == maxMinValue] def chooseNfromM(self, N, values, candidateList, what): "Choose the N candidates with the most votes." desc = "" if len(candidateList) <= N: return (candidateList, desc) chosen = [] # Candidates who will be chosen maybe = list(candidateList) # Candidates who may be chosen maybe.sort(key=lambda a, f=values: -f[a]) LC = maybe[N] # First losing candidate cutoff = values[LC] # All candidates with more than the Nth are chosen for c in maybe[:]: if values[c] > cutoff: maybe.remove(c) chosen.append(c) elif values[c] < cutoff: maybe.remove(c) # Break a possible tie for Nth place if len(chosen) < N: maybe.sort() desc = "Candidates %s were tied when when choosing %s. "\ % (self.b.joinList(maybe), what) while len(chosen) < N: (c, desc2) = self.breakStrongTie(maybe) desc += desc2 chosen.append(c) maybe.remove(c) return (chosen, desc) def breakStrongTie(self, tiedCandidates, what=""): "Break a strong tie between candidates." assert(len(tiedCandidates) >= 1) # If we have the right number, then return all. if len(tiedCandidates) == 1: return (tiedCandidates[0], None) # Break the tie randomly. elif self.strongTieBreakMethod == "random": c = random.choice(tiedCandidates) desc = "Candidate %s was chosen by breaking the tie randomly. "\ % self.b.names[c] # Break the tie alphabetically by candidate's names. elif self.strongTieBreakMethod == "alpha": tiedCandidates.sort(key=lambda a: self.b.names[a]) c = tiedCandidates[0] desc = "Candidate %s was chosen by breaking the tie alphabetically. "\ % self.b.names[c] # Break the tie by candidate index number. elif self.strongTieBreakMethod == "index": tiedCandidates.sort() c = tiedCandidates[0] desc = "Candidate %s was chosen by breaking the tie by candidate index "\ "number. " % self.b.names[c] elif self.strongTieBreakMethod == "manual": self.breakTieRequestQueue.put( [tiedCandidates, [self.b.names[c] for c in tiedCandidates], what]) c = self.breakTieResponseQueue.get(True) if c == None: c = random.choice(tiedCandidates) desc = "Candidate %s was chosen by breaking the tie randomly. "\ % self.b.names[c] else: desc = "Candidate %s was chosen by breaking the tie manually. "\ % self.b.names[c] else: assert(0) return (c, desc) ################################################################## class NonIterative(ElectionMethod): """Class that provides additional functionality for noniterative methods. Attributes: count -- List containing the number of votes received by each candidate. exhausted -- Number of exhausted votes. msg -- String describing the count. """ iterative = False def __init__(self, b): ElectionMethod.__init__(self, b) self.exhausted = 0 self.msg = "" self.count = [] def preCount(self): ElectionMethod.preCount(self) self.count = [0] * self.b.numCandidates def chooseWinners(self): "Choose the candidates with the most votes as the winners" for c in list(self.continuing): if self.count[c] == 0: self.continuing.remove(c) self.losers.add(c) (winners, desc) = self.chooseNfromM(self.numSeats, self.count, self.continuing, "winner") self.winners = set(winners) self.losers |= self.continuing - self.winners self.continuing = set() return desc ################################################################## class Iterative(ElectionMethod): """Class that provides additional funcationilty for iterative methods. Attributes: count -- Contains the vote counts for candidates for each round. count[r][c] stores candidate c's vote count at round r. exhausted -- A list of exhausted votes at each round. msg -- A list containing strings describing each round of the count. weakTieBreakMethod -- Method to break a tie at a given round. Allowable values are "backward" (use the previous round), "forward" (use the first round), "strong" (don't use other rounds to break tie). stopCond -- List containing one or more criteria for ending the election. Allowable values are "Know Winners" (all the winners have been determined), "N+1" (only N+1 candidates remain), "N" (only N candidates remain), and "Continuing Empty" (all candidates are either winners or losers). R -- The number of the current round. numRounds -- The total number of rounds. winnersOver, winnersEven -- The union of these two is always the same as "winners". A winning candidate is first placed in winnersOver. After surplus votes have been transferred from a winning candidate he or she is moved from winners over to winners even. roundInfo -- Stores information about what happened during each round. roundInfo[r] is a dictionary that stores information about round r. Possible values include roundInfo[r]["action"] = ("first", []) roundInfo[r]["action"] = ("surplus", [list of candidates]) roundInfo[r]["action"] = ("eliminate", [list of candidates]) roundInfo[r]["winners"] = "Text describing winners" roundInfo[r]["surplus"] = "Text describing surplus transfer" roundInfo[r]["eliminate"] = "Text describing candidate elimination" """ iterative = True threshMethod = True # methods may override this def __init__(self, b): ElectionMethod.__init__(self, b) self.weakTieBreakMethod = "backward" self.stopCond = None self.R = 0 # current round self.numRounds = 0 self.msg = [] # msg[r] contains text describing round r self.count = [] # count[r][c] is candidate c's votes at round r self.exhausted = [] # exhausted[r] is number of exhausted votes self.roundInfo = [] self.winnersEven = set() # winners who have had their surplus transferred self.winnersOver = set() # winners who still have a surplus self.wonAtRound = [None] * self.b.numCandidates self.lostAtRound = [None] * self.b.numCandidates def postCount(self): ElectionMethod.postCount(self) self.numRounds = self.R+1 def allocateRound(self): self.msg.append("") self.roundInfo.append({}) self.count.append([0] * self.b.numCandidates) self.exhausted.append(0) def breakWeakTie(self, R, candidateList, mostfewest, what=""): """Break ties using previous rounds. A weak tie is a tie at a given round, and may be able to be broken by looking at other rounds. A strong tie occurs when candidates are tied at all rounds. Weak ties can be broken in three ways: forward -- start at round 1 and go forward backward -- start at the previous round and go backward strong -- don't use other rounds to break the tie """ assert(mostfewest in ["most", "fewest"]) tiedCandidates = self.findTiedCand(candidateList, mostfewest, self.count[R]) if len(tiedCandidates) == 1: return (tiedCandidates[0], "") # no tie # Let the user know what is going on. tiedCandidates.sort() desc = "Candidates %s were tied when choosing %s. "\ % (self.b.joinList(tiedCandidates), what) # When method is "strong", we go straight to strong tie breaking. if self.weakTieBreakMethod == "strong": (c, desc2) = self.breakStrongTie(tiedCandidates, what) return c, desc + desc2 # When method is "forward" or "backward" we use other rounds order = range(R) if self.weakTieBreakMethod == "backward": order.reverse() if self.weakTieBreakMethod in ["forward", "backward"]: for i in order: tiedCandidates = self.findTiedCand(tiedCandidates, mostfewest, self.count[i]) if len(tiedCandidates) == 1: desc += "Candidate %s was chosen by breaking the tie at round %d. "\ % (self.b.names[tiedCandidates[0]], i+1) return (tiedCandidates[0], desc) # The tie can't be broken with other rounds so do strong tie break. (c, desc2) = self.breakStrongTie(tiedCandidates, what) return c, desc + desc2 def newWinners(self, newWinnersList, status="over"): "Perform basic accounting when a new winner is found." assert(len(newWinnersList) > 0) newWinnersList.sort() for c in newWinnersList: assert(self.count[self.R][c] > 0) self.continuing.remove(c) self.winnersOver.add(c) self.wonAtRound[c] = self.R self.winners = self.winnersOver | self.winnersEven if len(newWinnersList) == 1 and status == "over": desc = "Candidate %s has reached the threshold and is elected. "\ % self.b.joinList(newWinnersList) elif len(newWinnersList) == 1 and status == "under": desc = "Candidate %s is elected. " % self.b.joinList(newWinnersList) elif status == "over": desc = "Candidates %s have reached the threshold and are elected. "\ % self.b.joinList(newWinnersList) elif status == "under": desc = "Candidates %s are elected. " % self.b.joinList(newWinnersList) else: assert(0) return desc def newLosers(self, newLosersList): "Perform basic accounting when a new loser is found." assert(newLosersList > 0) for c in newLosersList: self.continuing.remove(c) self.losers.add(c) self.lostAtRound[c] = self.R def electionOver(self): "Determine whether the election is over." # Election is over when all winners have been identified if ("Know Winners" in self.stopCond and len(self.winners) == self.numSeats): return True # Election is over when N+1 or fewer candidates remain if ("N+1" in self.stopCond and len(self.continuing) + len(self.winners) <= self.numSeats + 1): return True # Election is over when fewer than N candidates remain if ("N" in self.stopCond and len(self.continuing) + len(self.winners) <= self.numSeats): return True # Election is over when no candidates left in continuing if "Continuing Empty" in self.stopCond and len(self.continuing) == 0: return True return False ################################################################## class STV(Iterative): """Class that provides additional functionality for STV methods. Attributes: threshName -- The name of the winning threshold to use. This is a tuple of three criteria. The first is "Droop" or "Hare". The second is "Static" or "Dynamic". The third is "Whole" or "Fractional". delayedTransfer -- Some methods allow the transfer of surplus votes to be delayed where candidates can be safely eliminate. Allowable values are "On" and "Off". surplus -- A list containing the surplus votes at each round. thresh -- A list contiaining the winning threshold at each round. votes -- Contains the votes assigned to each candidate. votes[c] is a list containing the index numbers of all votes assigned to candidate c. batchElimination -- Some methods allow multiple candidates to be eliminated in a single round. Allowable values are "None" (no batch elimination), "Zero" (all candidates with zero votes eliminated simultaneously), "Cutoff" (all candidates with fewer than a specified number of votes are eliminated simultaneously), "Losers" (all losing candidates eliminated simultaneously), and "LosersERS97" (similar to "Losers" but as defined in ERS97 rules). batchCutoff -- When batchElimination is "Cutoff", this is the cutoff. firstEliminationRound -- Initially set to true. Set to false after the first elimination round. """ def __init__(self, b): Iterative.__init__(self, b) self.stopCond = ["Know Winners", "N"] self.threshName = None # must be overridden self.delayedTransfer = "Off" self.batchElimination = None # must be overridden self.batchCutoff = None self.firstEliminationRound = True self.surplus = [] # surplus[r] is number of surplus votes self.thresh = [] # thresh[r] is the winning threshold # votes[c] stores the indices of all votes for candidate c. self.votes = [] def preCount(self): Iterative.preCount(self) for _c in range(self.b.numCandidates): self.votes.append([]) def allocateRound(self): "Allocate space for all data structures for one round." Iterative.allocateRound(self) self.surplus.append(0) self.thresh.append(0) def initialVoteTally(self): "Count the first place votes (must be overridden)." raise NotImplementedError def updateCount(self): raise NotImplementedError def updateExhaustedVotes(self): """Compute the number of exhausted votes""" exhausted = self.p * self.b.numBallots exhausted -= sum(self.count[self.R]) self.exhausted[self.R] = exhausted def updateThresh(self): "Compute the value of the winning threshold." assert(self.threshName[0] in ["Droop", "Hare"]) assert(self.threshName[1] in ["Static", "Dynamic"]) assert(self.threshName[2] in ["Whole", "Fractional"]) if self.threshName[0] == "Droop": threshDen = self.numSeats + 1 elif self.threshName[0] == "Hare": threshDen = self.numSeats if self.threshName[1] == "Static": threshNum = self.p * self.b.numBallots elif self.threshName[1] == "Dynamic": threshNum = self.p * self.b.numBallots - self.exhausted[self.R] if self.threshName[2] == "Whole": thresh = threshNum/threshDen/self.p*self.p + self.p elif self.threshName[2] == "Fractional": thresh = threshNum/threshDen + 1 self.thresh[self.R] = thresh def updateSurplus(self): "Compute the surplus for current round." self.surplus[self.R] = 0 for c in self.winnersOver | self.continuing: if self.count[self.R][c] > self.thresh[self.R]: self.surplus[self.R] += self.count[self.R][c] - self.thresh[self.R] def updateWinners(self): "Find new winning candidates." winners = [] for c in self.continuing: if self.count[self.R][c] >= self.thresh[self.R]: winners.append(c) if len(winners) > 0: text = self.newWinners(winners) # In some instances, updateWinners could be called more than once in a # single round (e.g., N. Ireland STV when eliminating losers). if self.roundInfo[self.R].has_key("winners"): self.roundInfo[self.R]["winners"] += text else: self.roundInfo[self.R]["winners"] = text def updateRound(self): self.updateCount() self.updateExhaustedVotes() self.updateThresh() self.updateSurplus() self.updateWinners() def describeRound(self): if self.roundInfo[self.R]["action"][0] == "first": text = "Count of first choices. " elif self.roundInfo[self.R]["action"][0] == "surplus": text = self.roundInfo[self.R]["surplus"] elif self.roundInfo[self.R]["action"][0] == "eliminate": text = self.roundInfo[self.R]["eliminate"] if self.roundInfo[self.R].has_key("winners"): text += self.roundInfo[self.R]["winners"] # Explain what will happen in the next round if self.electionOver() or not self.threshMethod: pass elif self.surplus[self.R] == 0: text += "No candidates have surplus votes so candidates will be "\ "eliminated and their votes transferred for the next round. " elif self.delayedTransfer == "On" and len(self.getSureLosers(self.R)) != 0: text += "Candidates have surplus votes, but since "\ "candidates can be safely eliminated, the transfer of surplus "\ "votes will be delayed and candidates will be eliminated and their "\ "votes transferred for the next round." else: text += "Candidates have surplus votes so "\ "surplus votes will be transferred for the next round. " self.msg[self.R] = text def isSurplusToTransfer(self): "Decide whether to transfer surplus votes or eliminate candidates." if ( self.surplus[self.R-1] == 0 or (self.delayedTransfer == "On" and len(self.getSureLosers()) != 0) ): return False else: return True def transferSurplusVotes(self): (c, selectSurplusDesc) = self.selectSurplusToTransfer() self.roundInfo[self.R]["action"] = ("surplus", [c]) self.winnersOver.remove(c) self.winnersEven.add(c) transferSurplusDesc = self.transferSurplusVotesFromCandidate(c) self.roundInfo[self.R]["surplus"] = transferSurplusDesc self.roundInfo[self.R]["surplus"] += selectSurplusDesc def selectSurplusToTransfer(self): "Choose the candidate whose surplus will be transferred." # Choose the candidate with the greatest surplus. (c, desc) = self.breakWeakTie(self.R-1, list(self.winnersOver), "most", "surplus to transfer") return (c, desc) def transferSurplusVotesFromCandidate(self, c): "This must be overridden" raise NotImplementedError def getSureLosers(self, R=None): "Return all candidates who are sure losers." # Return all candidates who are sure losers but do not look at previous # rounds to break ties. if R is None: R = self.R - 1 maxNumLosers = len(self.continuing) + len(self.winners) - self.numSeats assert(maxNumLosers < len(self.continuing)) losers = [] # If all continuing candidates have zero votes and there is no surplus # then they are all sure losers. totalContinuingVote = sum([self.count[R][c] for c in self.continuing]) if totalContinuingVote == 0 and self.surplus[R] == 0: losers = list(self.continuing) return losers # We need to make sure that all candidates with the same number of votes # are treated the same, so group candidates with the same number of votes # together and sort clusters in order of votes (fewest first). continuing = list(self.continuing) continuing.sort(key=lambda a, f=self.count[R]: f[a]) clusteredContinuing = [[continuing[0]]] for c in continuing[1:]: if self.count[R][c] == self.count[R][ clusteredContinuing[-1][0] ]: clusteredContinuing[-1].append(c) else: clusteredContinuing.append([c]) s = self.surplus[R] potentialLosers = [] for i, cluster in enumerate(clusteredContinuing[:-1]): currentClusterCount = self.count[R][ cluster[0] ] nextClusterCount = self.count[R][ clusteredContinuing[i+1][0] ] s += len(cluster) * currentClusterCount potentialLosers += cluster if ((self.batchElimination != "LosersERS97" and s < nextClusterCount) or (self.batchElimination == "LosersERS97" and s <= nextClusterCount))\ and len(potentialLosers) <= maxNumLosers: losers = potentialLosers[:] return losers def eliminateCandidates(self): (elimList, selectLosersDesc) = self.selectCandidatesToEliminate() self.roundInfo[self.R]["action"] = ("eliminate", elimList) transferVotesDesc = self.transferVotesFromCandidates(elimList) self.roundInfo[self.R]["eliminate"] = transferVotesDesc + selectLosersDesc def selectCandidatesToEliminate(self): "Choose one or more candidates to eliminate." elimList = [] # First elimination round is different for some methods # Skipped if no candidates would be eliminated if self.firstEliminationRound: if self.batchElimination == "Zero": desc = "Since this is the first elimination round, all candidates "\ "without any votes are eliminated. " elimList = [c for c in self.continuing if self.count[self.R-1][c] == 0] elif self.batchElimination == "Cutoff": desc = "Since this is the first elimination round, all candidates "\ "with fewer than %d votes are eliminated. " % \ self.batchCutoff elimList = [c for c in self.continuing if self.count[self.R-1][c] < self.p*self.batchCutoff] elif self.batchElimination in ["Losers", "LosersERS97"]: desc = "All losing candidates are eliminated. " elimList = self.getSureLosers() # We can eliminate one more candidate if all of the following are # satisfied: # (1) The surplus is zero # (2) The candidates selected for elimination all have zero votes # (3) There are enough candidates remaining if ( self.surplus[self.R-1] == 0): ctng = [c for c in self.continuing if c not in elimList] if ( len(ctng) + len(self.winners) > self.numSeats): elimListTotalCount = sum([self.count[self.R-1][c] for c in elimList]) if ( elimListTotalCount == 0): (c, desc2) = self.breakWeakTie(self.R-1, ctng, "fewest", "candidates to eliminate") elimList.append(c) desc += desc2 elif self.batchElimination == "None": pass else: assert(0) # Normal elimination round # This happens if not firstEliminationRound or if the first # elimination round didn't eliminate any candidates. if (not self.firstEliminationRound) or (len(elimList) == 0): if self.batchElimination in ["Losers", "LosersERS97"]: desc = "All losing candidates are eliminated. " elimList = self.getSureLosers() if len(elimList) == 0: (c, desc2) = self.breakWeakTie(self.R-1, list(self.continuing), "fewest", "candidates to eliminate") elimList = [c] desc += desc2 else: (c, desc) = self.breakWeakTie(self.R-1, self.continuing, "fewest", "candidates to eliminate") elimList = [c] # Don't do first elimination again. self.firstEliminationRound = False # Put losing candidates in the proper list self.newLosers(elimList) return (elimList, desc) def transferVotesFromCandidates(self, elimList): raise NotImplementedError def updateCandidateStatus(self): "Update candidate status at end of election." desc = "" if len(self.winners) == self.numSeats: # All others are losers self.newLosers(list(self.continuing)) else: # Candidates with no votes are losers for c in list(self.continuing): if self.count[self.R][c] == 0: self.newLosers([c]) # Eliminate (N+1)th candidate if len(self.continuing) + len(self.winners) > self.numSeats: (c, desc) = self.breakWeakTie(self.R, self.continuing, "fewest", "winners") self.newLosers([c]) # Everyone else is a winner if len(self.continuing) > 0: desc += self.newWinners(list(self.continuing), "under") self.msg[self.R] += desc def countBallots(self): "Count the votes with STV." # Count first place votes self.allocateRound() self.initialVoteTally() self.updateRound() self.describeRound() # Transfer surplus votes or eliminate candidates until done while (not self.electionOver()): self.R += 1 self.allocateRound() if self.isSurplusToTransfer(): self.transferSurplusVotes() else: self.eliminateCandidates() self.updateRound() self.describeRound() self.updateCandidateStatus() ################################################################## class OrderDependentSTV(STV): """Class that provides additionaly functionality for STV methods that are dependent of the order of the ballots. Order dependent methods must use individual ballots and cannot use weighted ballots. No additional attributes. """ def __init__(self, b): STV.__init__(self, b) def preCount(self): STV.preCount(self) assert(self.threshName[2] == "Whole") def initialVoteTally(self): "Count the first place votes with order dependent rules." # Allocate votes to candidates bases on the first choices. for i in xrange(self.b.numBallots): c = self.b.getTopChoiceFromBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.roundInfo[self.R]["action"] = ("first", []) def updateCount(self): "Update the vote totals after a transfer of votes." for c in range(self.b.numCandidates): self.count[self.R][c] = len(self.votes[c]) ################################################################## class OrderIndependentSTV(STV): """Class that provides additionaly functionality for STV methods that are independent of the order of the ballots. Order independent methods can use weighted ballots to speed up the count. No additional attributes. """ def initialVoteTally(self): "Count the first place votes." # Allocate votes to candidates based on the first choices. for i in range(self.b.numWeightedBallots): c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.roundInfo[self.R]["action"] = ("first", []) ################################################################## class NoSurplusSTV(OrderIndependentSTV): """Class that provides additionaly functionality for STV methods that do not have a winning threshold and thus do not have surplus votes. No additional attributes. """ threshMethod = False def __init__(self, b): OrderIndependentSTV.__init__(self, b) self.threshName = None self.stopCond = ["N+1"] def updateThresh(self): "These methods don't have a threshold." pass def updateSurplus(self): "These methods don't have surplus votes." pass def isSurplusToTransfer(self): "Never transfer surplus votes." return False def transferVotesFromCandidates(self, elimList): "Eliminate candidates for NoSurplus methods." for loser in elimList: for i in self.votes[loser]: c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.votes[loser] = [] desc = "Count after eliminating %s and transferring votes. " \ % self.b.joinList(elimList) return desc def updateWinners(self): "Since there is no threshold, winners are only determined at the end." pass def updateCount(self): "Update the vote totals after a transfer of votes for NoSurplus methods." # Recount votes for all candidates for c in range(self.b.numCandidates): for i in self.votes[c]: self.count[self.R][c] += self.b.getWeight(i) ################################################################## class GregorySTV(OrderIndependentSTV): """Class that provides additional functionality for Gregory STV methods. Note that some of the quirks of the ERS97 STV rules are addressed in this classs to allow for more code reuse. Attributes: quota -- The quota for the ERS97 rules. S -- The current "stage" for ERS97 rules. In transferring votes from eliminated candidates, the ERS97 rules use "substages". Each substage is a round so the number of rounds is greater than the number of stages. stages -- This is used to translate between rounds and stages. stages[s] is a list of the rounds corresponding to that stage. votesByTransferValue -- In eliminating candidates, Gregory methods transfer votes in packets having the same transfer value. votesByTransferValue[v] is a list of vote indices having that transfer value. batches -- In doing secondary transfers, Gregory methods transfer the last batch of votes received by a candidate. batches[c] is a list of batches of votes received by candidate c. Each batch is a list of vote indices. transferValue -- Each ballot has a transfer value. Initially, it is set to 1, but may be reduced when a vote is part of a surplus transfer. """ def __init__(self, b): OrderIndependentSTV.__init__(self, b) self.quota = 0 self.S = 0 self.stages = [] self.votesByTransferValue = [] # Gregory rules do last batch transfers # Need to store batches for each cand self.batches = [] self.transferValue = [] self.transferValues = [] def preCount(self): OrderIndependentSTV.preCount(self) self.transferValue = [self.p] * self.b.numWeightedBallots for _c in range(self.b.numCandidates): self.batches.append([]) def initialVoteTally(self): "Count the first place votes with Gregory rules." OrderIndependentSTV.initialVoteTally(self) # The first batch is all the votes a candidate has. for c in range(self.b.numCandidates): self.batches[c].append(self.votes[c][:]) def transferSurplusVotesFromCandidate(self, cSurplus): "Transfer surplus votes according to the Gregory rules." # Each candidate will receive a new batch of votes so # create a data structure to store the vote indices. newBatch = [] for c in range(self.b.numCandidates): newBatch.append([]) # We need to compute several quantities: # surplus -- the number of votes of the transferor over quota # transferableValue -- total value of batch to be transferred if self.methodName == "ERS97 STV": surplus = self.count[self.R-1][cSurplus] - self.quota[self.R-1] elif self.methodName == "N. Ireland STV": surplus = self.count[self.R-1][cSurplus] - self.thresh[self.R-1] lastBatch = self.batches[cSurplus][-1] transferableValue = 0 nTransferable = 0 for i in lastBatch: if self.b.getTopChoiceFromWeightedBallot(i, self.continuing) \ is not None: transferableValue += \ self.b.getWeight(i) * self.transferValue[i] nTransferable += self.p * self.b.getWeight(i) # Do the transfer for i in lastBatch: if transferableValue > surplus: self.transferValue[i] = self.p * surplus / nTransferable c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) newBatch[c].append(i) # for candidates who received votes, add new batch for c in self.continuing: if len(newBatch[c]) > 0: self.batches[c].append(newBatch[c]) self.votes[cSurplus] = [] desc = "Count after transferring surplus votes from %s. " % \ self.b.names[cSurplus] return desc def updateCount(self): "Update the vote totals after a transfer of votes." # Update counts for losers, continuing, and winnersOver. # Because of substage transfers with ERS97, losing candidates # will sometimes have a count greater than 0. for c in self.losers | self.continuing | self.winnersOver: self.count[self.R][c] = 0 for i in self.votes[c]: self.count[self.R][c] += \ self.b.getWeight(i) * self.transferValue[i] # Set counts for winnersEven. This will always be the same as the # previous round. for c in self.winnersEven: self.count[self.R][c] = self.count[self.R-1][c] # Set counts for the candidate in transition from winnersOver to # winnersEven. The transferor must be treated differently from # winnersOver and winnersEven. if self.roundInfo[self.R]["action"][0] == "surplus": cSurplus = self.roundInfo[self.R]["action"][1][0] if self.methodName == "N. Ireland STV": self.count[self.R][cSurplus] = self.thresh[self.R-1] elif self.methodName == "ERS97 STV": self.count[self.R][cSurplus] = self.quota[self.R-1] else: assert(0) def transferVotesWithValue(self, v): "Eliminate candidates according to the Gregory rules." # Set up holders for transferees newBatch = [] for c in range(self.b.numCandidates): newBatch.append([]) # Transfer votes of this value for i in self.votesByTransferValue[v]: c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) newBatch[c].append(i) # Don't know where this vote came from so try all losers for d in self.losers: if i in self.votes[d]: self.votes[d].remove(i) # For candidates who received votes, add new batch for c in self.continuing: if len(newBatch[c]) > 0: self.batches[c].append(newBatch[c]) def eliminateCandidates(self): (elimList, selectLosersDesc) = self.selectCandidatesToEliminate() self.roundInfo[self.R]["action"] = ("eliminate", elimList) self.roundInfo[self.R]["eliminate"] = selectLosersDesc self.sortVotesByTransferValue(elimList) self.transferVotesFromCandidates(elimList) def sortVotesByTransferValue(self, elimList): raise NotImplementedError def countBallots(self): "Count the votes with Gregory rules." # Count first place votes self.allocateRound() if self.methodName == "ERS97 STV": self.stages.append([]) self.stages[self.S].append(self.R) self.initialVoteTally() self.updateRound() self.describeRound() # Transfer surplus votes or eliminate candidates until done while (not self.electionOver()): self.R += 1 self.allocateRound() if self.methodName == "ERS97 STV": self.S += 1 self.stages.append([]) self.stages[self.S].append(self.R) if self.isSurplusToTransfer(): self.transferSurplusVotes() self.updateRound() self.describeRound() else: self.eliminateCandidates() self.updateCandidateStatus() ################################################################## class WeightedInclusiveSTV(OrderIndependentSTV): """Class that provides additional functionality for weighted inclusive methods. Attributes: transferValue -- Each ballot has a transfer value. Initially, it is set to 1, but may be reduced when a vote is part of a surplus transfer. """ def __init__(self, b): OrderIndependentSTV.__init__(self, b) self.transferValue = [] def preCount(self): OrderIndependentSTV.preCount(self) self.transferValue = [self.p] * self.b.numWeightedBallots def transferSurplusVotesFromCandidate(self, cSurplus): "Transfer the surplus votes of one candidate." # Transfer all of the votes at a fraction of their value surplus = self.count[self.R-1][cSurplus] - self.thresh[self.R-1] for i in self.votes[cSurplus][:]: self.transferValue[i] = self.transferValue[i] * surplus / \ self.count[self.R-1][cSurplus] c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.votes[cSurplus] = [] desc = "Count after transferring surplus votes from %s with a transfer "\ "value of %s/%s. " \ % (self.b.names[cSurplus], self.displayValue(surplus), self.displayValue(self.count[self.R-1][cSurplus])) return desc def updateCount(self): "Update the vote totals after a transfer of votes." # Update counts for losers, continuing, and winnersOver. for c in self.losers | self.continuing | self.winnersOver: self.count[self.R][c] = 0 for i in self.votes[c]: self.count[self.R][c] += \ self.b.getWeight(i) * self.transferValue[i] # Set counts for winnersEven. This will always be the same as the # previous round. for c in self.winnersEven: self.count[self.R][c] = self.count[self.R-1][c] # Set counts for the candidate in transition from winnersOver to # winnersEven. The transferor must be treated differently from # winnersOver and winnersEven. if self.roundInfo[self.R]["action"][0] == "surplus": cSurplus = self.roundInfo[self.R]["action"][1][0] self.count[self.R][cSurplus] = self.thresh[self.R-1] def transferVotesFromCandidates(self, elimList): "Eliminate a list of candidates." # Transfer votes from losers simultaneously. for loser in elimList: for i in self.votes[loser]: c = self.b.getTopChoiceFromWeightedBallot(i, self.continuing) if c is not None: self.votes[c].append(i) self.votes[loser] = [] elimList.sort() desc = "Count after eliminating %s and transferring votes. " \ % self.b.joinList(elimList) return desc ################################################################## class RecursiveSTV(OrderIndependentSTV): """Class that provides additional functionality for recursive STV methods. Attributes: keepFactor -- Each candidate is assigned a keep factor that is initially set to 1. When a candidate's vote exceeds the winning threshold, the keep factor is reduced to transfer surplus votes to other candidates. tree -- A data structure that stores the votes in a tree and allows for faster algorithms. The first level (below the root) contains the current active first choices. When a candidate has exceeded the winning threshold, then that node of the tree is expanded to another level. When a candidate is eliminated, the tree is rebuilt to remove that canddiate entirely as if the candidate had never been in the election. """ def __init__(self, b): OrderIndependentSTV.__init__(self, b) self.threshName = ["Droop", "Dynamic", "Fractional"] self.prec = 6 self.surplusLimit = 1 # lsb, not 1.0 self.delayedTransfer = "On" self.batchElimination = "Losers" self.keepFactor = [] self.tree = {} def allocateRound(self): "Add keep factor allocation." OrderIndependentSTV.allocateRound(self) self.keepFactor.append([0] * self.b.numCandidates) def describeRound(self): if self.roundInfo[self.R]["action"][0] == "first": text = "Count of first choices. " elif self.roundInfo[self.R]["action"][0] == "surplus": text = self.roundInfo[self.R]["surplus"] elif self.roundInfo[self.R]["action"][0] == "eliminate": text = self.roundInfo[self.R]["eliminate"] if self.roundInfo[self.R].has_key("winners"): text += self.roundInfo[self.R]["winners"] self.msg[self.R] = text def initialVoteTally(self): "Initialize the tree data structure and candidate keep factors." # The tree stores exactly the ballot information needed to count the # votes. The first level of the tree is the top active candidate # (winner or in continuing). Since for winning candidates, a portion # of the ballot goes to the next candidate, the tree is extended until # it reaches a candidate in continuing or the ballot is exhausted. # In the beginning, all candidates are in continuing so there is only # one level in the tree. self.roundInfo[self.R]["action"] = ("first", []) for c in range(self.b.numCandidates): self.keepFactor[0][c] = self.p for i in xrange(self.b.numWeightedBallots): self.addBallotToTree(self.tree, i) def addBallotToTree(self, tree, ballotIndex, ballot=""): """Add one ballot to the tree. The root of the tree is a dictionary that has as keys the indicies of all continuing and winning candidates. For each candidate, the value is also a dictionary, and the keys of that dictionary include "n" and "bi". tree[c]["n"] is the number of ballots that rank candidate c first. tree[c]["bi"] is a list of ballot indices where the ballots rank c first. If candidate c is a winning candidate, then that portion of the tree is expanded to indicate the breakdown of the subsequently ranked candidates. In this situation, additional keys are added to the tree[c] dictionary corresponding to subsequently ranked candidates. tree[c]["n"] is the number of ballots that rank candidate c first. tree[c]["bi"] is a list of ballot indices where the ballots rank c first. tree[c][d]["n"] is the number of ballots that rank c first and d second. tree[c][d]["bi"] is a list of the corresponding ballot indices. Where the second ranked candidates is also a winner, then the tree is expanded to the next level. Losing candidates are ignored and treated as if they do not appear on the ballots. For example, tree[c][d]["n"] is the total number of ballots where candidate c is the first non-losing candidate, c is a winner, and d is the next non-losing candidate. This will include the following ballots, where x represents a losing candidate: [c d] [x c d] [c x d] [x c x x d] During the count, the tree is dynamically updated as candidates change their status. The parameter "tree" to this method may be the root of the tree or may be a sub-tree. """ if ballot == "": # Add the complete ballot to the tree weight, ballot = self.b.getWeightedBallot(ballotIndex) else: # When ballot is not "", we are adding a truncated ballot to the tree, # because a higher-ranked candidate is a winner. weight = self.b.getWeight(ballotIndex) # Get the top choice among candidates still in the running # Note that we can't use Ballots.getTopChoiceFromWeightedBallot since # we are looking for the top choice over a truncated ballot. for c in ballot: if c in self.continuing | self.winners: break # c is the top choice so stop else: c = None # no candidates left on this ballot if c is None: # This will happen if the ballot contains only winning and losing # candidates. The ballot index will not need to be transferred # again so it can be thrown away. return # Create space if necessary. if not tree.has_key(c): tree[c] = {} tree[c]["n"] = 0 tree[c]["bi"] = [] tree[c]["n"] += weight if c in self.winners: # Because candidate is a winner, a portion of the ballot goes to # the next candidate. Pass on a truncated ballot so that the same # candidate doesn't get counted twice. i = ballot.index(c) ballot2 = ballot[i+1:] self.addBallotToTree(tree[c], ballotIndex, ballot2) else: # Candidate is in continuing so we stop here. tree[c]["bi"].append(ballotIndex) def updateTree(self, tree, loserSet): "Update the tree data structure to account for new winners and losers." self.updateLoserTree(tree, loserSet) self.updateWinnerTree(tree, loserSet) def updateLoserTree(self, tree, loserSet): "Update the tree data structure to account for new losers." for c in loserSet.intersection(tree): for i in tree[c]["bi"]: ballot = self.b.getWeightedBallot(i)[1] # drop weight j = ballot.index(c) ballot2 = ballot[j+1:] self.addBallotToTree(tree, i, ballot2) del tree[c] def updateWinnerTree(self, tree, loserSet): "Update the tree data structure to account for new winners." for c in self.winners.intersection(tree): if len(tree[c]["bi"]) > 0: # The current candidate is a new winner (has ballot indices), so # expand this node to the next level. There is no need to call # updateTree() recursively since addBallotToTree() will appropriately # expand lower nodes. for i in tree[c]["bi"]: ballot = self.b.getWeightedBallot(i)[1] j = ballot.index(c) ballot2 = ballot[j+1:] self.addBallotToTree(tree[c], i, ballot2) tree[c]["bi"] = [] else: # The current candidate is an old winner, so recurse to see if # anything needs to be done at lower levels. self.updateTree(tree[c], loserSet) def inInfiniteLoop(self): "detect stable state as infinite loop" return self.R > 1 and self.keepFactor[self.R-1] == self.keepFactor[self.R-2] def isSurplusToTransfer(self): # We eliminate candidates if (1) there are losers, (2) the surplus is # below the surplus limit, or (3) we are stuck in an infinite loop. if self.surplus[self.R-1] < self.surplusLimit or \ (self.delayedTransfer == "On" and len(self.getSureLosers()) != 0) or \ self.inInfiniteLoop(): return False else: return True def transferSurplusVotes(self): self.roundInfo[self.R]["action"] = ("surplus", []) self.updateTree(self.tree, self.losers) desc = self.updateKeepFactors() self.roundInfo[self.R]["surplus"] = \ "Count after transferring surplus votes. " + desc def eliminateCandidates(self): (elimList, descChoose) = self.selectCandidatesToEliminate() self.roundInfo[self.R]["action"] = ("eliminate", elimList) descTrans = self.transferVotesFromCandidates(elimList) self.roundInfo[self.R]["eliminate"] = descTrans + descChoose self.updateTree(self.tree, self.losers) self.copyKeepFactors() def selectCandidatesToEliminate(self): "Eliminate any losing candidates." if self.inInfiniteLoop(): (c, desc) = self.breakWeakTie(self.R-1, self.continuing, "fewest", "candidates to eliminate") desc = "Candidates tied within precision of computations. " + desc self.newLosers([c]) self.firstEliminationRound = False return [c], desc else: return STV.selectCandidatesToEliminate(self) def transferVotesFromCandidates(self, elimList): "Eliminate any losing candidates." elimList.sort() desc = "Count after eliminating %s and transferring votes. "\ % self.b.joinList(elimList) return desc def copyKeepFactors(self): "Udpate the candidate keep factors." for c in self.continuing | self.winners: self.keepFactor[self.R][c] = self.keepFactor[self.R-1][c] def updateKeepFactors(self): "Udpate the candidate keep factors." if len(self.winners) != 0: desc = "Keep factors of candidates who have exceeded the threshold: " winners = [] else: desc = "" candidateList = list(self.continuing | self.winners) candidateList.sort() for c in candidateList: if self.count[self.R-1][c] > self.thresh[self.R-1]: self.roundInfo[self.R]["action"][1].append(c) kf, rem = divmod(self.keepFactor[self.R-1][c] * self.thresh[self.R-1], self.count[self.R-1][c]) if rem > 0: kf += 1 self.keepFactor[self.R][c] = kf winners.append("%s, %s"\ % (self.b.names[c], self.displayValue(self.keepFactor[self.R][c])) ) else: self.keepFactor[self.R][c] = self.keepFactor[self.R-1][c] if len(self.winners) != 0: desc += self.b.joinList(winners, convert="none") + ". " return desc OpenSTV-1.6.1/openstv/utils.py0000777000175400010010000000214311400774167014627 0ustar jeffNone"Module of generally useful utilities." ## OpenSTV Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. __revision__ = "$Id: OpenSTV.py 508 2009-04-29 00:51:56Z jco8 $" import os.path import sys def getHome(): if hasattr(sys, "frozen"): if sys.platform == "darwin": # OS X return os.path.join(os.path.dirname(os.path.dirname(sys.executable)), "Resources") return os.path.dirname(sys.executable) else: return os.path.dirname(__file__) def pluralize(count, singular="", plural="s"): "return singular version if count is 1; else return plural version" if count == 1: return singular return plural OpenSTV-1.6.1/openstv/version.py0000777000175400010010000000012511402505361015140 0ustar jeffNone__revision__ = "$Id: version.py 776 2010-06-05 17:35:35Z jeff.oneill $" v = "1.6.1" OpenSTV-1.6.1/openstv/__init__.py0000777000175400010010000000016011400774167015223 0ustar jeffNone# dummy file to make OpenSTV a package __revision__ = "$Id: __init__.py 650 2009-10-26 01:05:07Z jeff.oneill $" OpenSTV-1.6.1/PKG-INFO0000777000175400010010000000053211407513225012505 0ustar jeffNoneMetadata-Version: 1.0 Name: OpenSTV Version: 1.6.1 Summary: Implements the single transferable vote, instant runoff voting, and several other voting systems. Home-page: http://www.openstv.org Author: Jeff O'Neill Author-email: jeff.oneill@openstv.org License: Gnu General Public License version 2 Description: UNKNOWN Platform: UNKNOWN OpenSTV-1.6.1/README.txt0000777000175400010010000000216011400774167013114 0ustar jeffNoneRevision $Id: README.txt 578 2009-08-26 01:13:50Z jeff.oneill $ OVERVIEW OpenSTV is a program for implementing the single transferable vote (STV). STV is used for electing a group of people (e.g. council, committee, legislature) and it provides for proportional representation of the electorate. The idea behind proportional representation is that the demographics of the elected group should, at least roughly, match the demographics of the electorate. The beauty of STV is that there are no reserved seats and the proportional representation arises naturally. For more information see http://www.fairvote.org/ http://www.electoral-reform.org.uk/ In an STV election, each voter simply ranks the candidates in order of preference. The rules for counting the votes with STV are more complicated than winner-take-all elections. The votes can be counted by hand, but it is useful to have a computer program to speed up the process. There are several variations of STV, but most users should use the default options. INSTALLATION See http://www.OpenSTV.org/ for more information. Jeff O'Neill jeff.oneill at openstv.org OpenSTV-1.6.1/setup.py0000777000175400010010000002104511402722620013120 0ustar jeffNone#!/usr/bin/env python ## Copyright (C) 2003-2010 Jeffrey O'Neill ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. import sys import os import glob import shutil import pkgutil if sys.platform == "darwin": from setuptools import setup else: from distutils.core import setup if sys.platform == "win32": import py2exe from openstv.version import v as OpenSTV_version __revision__ = "$Id: setup.py 777 2010-06-06 13:04:54Z jeff.oneill $" # Remove the build and dist folders shutil.rmtree("build", ignore_errors=True) shutil.rmtree("dist", ignore_errors=True) ################################################################ class InnoScript: def __init__(self, lib_dir, dist_dir, windows_exe_files, lib_files): self.lib_dir = lib_dir self.dist_dir = dist_dir if not self.dist_dir[-1] in "\\/": self.dist_dir += "\\" self.windows_exe_files = [self.chop(p) for p in windows_exe_files] self.lib_files = [self.chop(p) for p in lib_files] self.issFile = r"dist\OpenSTV.iss" def chop(self, pathname): assert pathname.startswith(self.dist_dir) return pathname[len(self.dist_dir):] def create(self): ofi = open(self.issFile, "w") print >> ofi, r''' ; WARNING: This script has been created by py2exe. Changes to this script ; will be overwritten the next time py2exe is run! [Setup] AppName=OpenSTV AppVerName=OpenSTV %s DefaultDirName={pf}\OpenSTV DefaultGroupName=OpenSTV ChangesAssociations=yes OutputBaseFilename=OpenSTV-%s-win32 OutputDir=. [Files] ''' % (OpenSTV_version, OpenSTV_version) for path in self.windows_exe_files + self.lib_files: print >> ofi, r'Source: "%s"; DestDir: "{app}\%s"; Flags: ignoreversion' % (path, os.path.dirname(path)) print >> ofi, r''' [Tasks] Name: "associatebltfiles"; Description: "&Associate BLT files with OpenSTV"; Flags: checkedonce Name: "desktopicon"; Description: "Create a &Desktop icon"; Flags: checkedonce Name: "quicklaunchicon"; Description: "Create a &Quick Launch icon"; Flags: checkedonce [Icons] ''' for path in self.windows_exe_files: print >> ofi, r'Name: "{group}\OpenSTV"; Filename: "{app}\%s"' % (path) print >> ofi, r''' Name: "{group}\Help"; Filename: "{app}\Help.html" Name: "{group}\Uninstall OpenSTV"; Filename: "{uninstallexe}" Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\OpenSTV"; Filename: "{app}\OpenSTV.exe"; Tasks: quicklaunchicon Name: "{userdesktop}\OpenSTV"; Filename: "{app}\OpenSTV.exe"; Tasks: desktopicon [Registry] Root: HKCR; Subkey: ".blt"; ValueType: string; ValueName: ""; ValueData: "ERSBLTFile"; Tasks: associatebltfiles Root: HKCR; Subkey: "ERSBLTFile"; ValueType: string; ValueName: ""; ValueData: "ERS Ballot File"; Flags: uninsdeletekey; Tasks: associatebltfiles Root: HKCR; Subkey: "ERSBLTFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\Icons\blt.ico,0"; Tasks: associatebltfiles Root: HKCR; Subkey: "ERSBLTFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\OpenSTV.exe"" ""%1"""; Tasks: associatebltfiles ''' def compile(self): os.startfile(self.issFile) ################################################################ if sys.platform == "win32": class build_installer(py2exe.build_exe.py2exe): # This class first builds the exe file, then creates a Windows installer. # You need InnoSetup for it. def run(self): # First, let py2exe do its work. py2exe.build_exe.py2exe.run(self) # create the Installer, using the files py2exe has created. script = InnoScript(self.lib_dir, self.dist_dir, self.windows_exe_files, self.lib_files) print "*** creating the inno setup script***" script.create() print "*** compiling the inno setup script***" script.compile() ################################################################ # generic arguments for setup() name = "OpenSTV" description = "Implements the single transferable vote, instant runoff "\ "voting, and several other voting systems." author = "Jeff O'Neill" author_email = "jeff.oneill@openstv.org" url = "http://www.openstv.org" license = "Gnu General Public License version 2" packages = ["openstv", "openstv.MethodPlugins", "openstv.LoaderPlugins", "openstv.ReportPlugins"] data_files = [ ("", ["openstv/Help.html", "openstv/License.html"]), ("Icons", ["openstv/Icons/pie.ico", "openstv/Icons/blt.ico", "openstv/Icons/splash.png"]) ] # Get list of plugins because they won't be automatically included includes = [] pluginPackageNames = ["openstv.MethodPlugins", "openstv.LoaderPlugins", "openstv.ReportPlugins"] for x in pluginPackageNames: package = __import__(x, fromlist="dummy") ppath = package.__path__ pname = package.__name__ + "." for importer, modname, ispkg in pkgutil.iter_modules(ppath, pname): includes.append(modname) if sys.platform == "win32": # py2exe ignores package_data py2exeArgs = dict( script = "openstv/OpenSTV.py", icon_resources = [(1, "openstv/Icons/pie.ico")], dest_base = "OpenSTV") py2exeOptions = dict(includes = includes, compressed = 2, optimize = 2, bundle_files = 3, dll_excludes = ["MSVCP90.dll"] ) # Add needed dll's py26MSdll = glob.glob(r"..\py26MSdll\*.*") data_files += [(r"Microsoft.VC90.CRT", py26MSdll), (r"lib\Microsoft.VC90.CRT", py26MSdll), ] setup(name = name, version = OpenSTV_version, description = description, author = author, author_email = author_email, url = url, license = license, packages = packages, data_files = data_files, windows = [py2exeArgs], options = dict(py2exe = py2exeOptions), cmdclass = dict(py2exe = build_installer), zipfile = r"lib\library.zip" ) elif sys.platform == "darwin": # # Usage: ./setup.py # # py2app options plist = { "CFBundleDocumentTypes": [{"CFBundleTypeName": "OpenSTV ballot file", "CFBundleTypeIconFile": "blt.icns", "CFBundleTypeExtensions": ["blt", "txt"], "CFBundleTypeRole": "Viewer"}] } py2app_options = dict( includes = includes, plist = plist, iconfile = 'openstv/Icons/pie.icns', no_strip = 0, ) setup(name = name, script_name = 'setup.py', script_args = ['py2app'], version = OpenSTV_version, description = description, author = author, author_email = author_email, url = url, license = license, packages = packages, data_files = data_files, options = dict(py2app = py2app_options), scripts = ['openstv/OpenSTV.py'], app = ['openstv/OpenSTV.py'], setup_requires = ["py2app"], ) os.system("hdiutil create -ov -imagekey zlib-level=9 -srcfolder dist/OpenSTV.app dist/OpenSTV.dmg") else: # For *nix, data_files goes into the /usr directory, which # we don't want. # pacakge_data doesn't appear to work for sdist so use MANIFEST.in # Create MANIFEST.in f = open("MANIFEST.in", "w") print >> f, r""" include CHANGELOG.txt include README.txt include openstv/Help.html include openstv/License.html include openstv/Icons/splash.png include openstv/Icons/blt.ico include openstv/Icons/pie.ico include openstv/Icons/blt.icns include openstv/Icons/pie.icns """ setup(name = name, version = OpenSTV_version, description = description, author = author, author_email = author_email, url = url, license = license, packages = packages, )