def sqrt(x): p = x.rational_part s, t = p.numerator, p.denominator if p == 0: return Quadratic() elif p < 0: raise NotImplementedError elif x.quadratic_power == 0: if is_square(s) and is_square(t): return Quadratic(Fraction(isqrt(s), isqrt(t))) r = Fraction(1, t) p = s * t prime_power_list = [] for prime, x_power in zip(primes, x.quadratic_part or (0, ) * len(primes)): power = 0 while p % prime == 0: p //= prime power += 1 r *= prime**(power >> 1) prime_power_list.append(((power & 1) << x.quadratic_power) | x_power) if not is_square(p): return return Quadratic(r * isqrt(p), x.quadratic_power + 1, tuple(prime_power_list))
def __lp_av_score_fct(i, ell): # l-th root of i # l=1 ... Approval Voting # l=\infty ... Chamberlin-Courant if i == 1: return 1 else: return i**Fraction(1, ell) - (i - 1)**Fraction(1, ell)
def quota(votes, seats, parties=string.ascii_letters, tiesallowed=True, verbose=True): if verbose: print("\nQuota method") representatives = [0] * len(votes) while sum(representatives) < seats: quotas = [ Fraction(votes[i], representatives[i] + 1) for i in range(len(votes)) ] # check if upper quota is violated for i in range(len(votes)): upperquota = int( math.ceil( float(votes[i]) * (sum(representatives) + 1) / sum(votes))) if representatives[i] >= upperquota: quotas[i] = 0 maxquotas = [i for i in range(len(votes)) if quotas[i] == max(quotas)] nextrep = maxquotas[0] representatives[nextrep] += 1 if len(maxquotas) > 1 and not tiesallowed: raise TiesException("Tie occurred") # print tiebreaking information if verbose and len(maxquotas) > 1: quotas_now = [ Fraction(votes[i], representatives[i] + 1) for i in range(len(votes)) ] tiebreaking_message = (" tiebreaking in order of: " + str(parties[:len(votes)]) + "\n ties broken in favor of: ") ties_favor = [ i for i in range(len(votes)) if quotas_now[i] == quotas_now[nextrep] ] for i in ties_favor: tiebreaking_message += str(parties[i]) + ", " tiebreaking_message = (tiebreaking_message[:-2] + "\n to the disadvantage of: ") for i in maxquotas[1:]: tiebreaking_message += str(parties[i]) + ", " print(tiebreaking_message[:-2]) if verbose: __print_results(representatives, parties) return representatives
def __new__(cls, rational_part=Fraction(), quadratic_power=0, quadratic_part=None): self = super(Quadratic, cls).__new__(cls) if type(rational_part) is str: rational_part = Fraction(rational_part) if isinstance(rational_part, (int, mpz_type, mpq_type)): self.rational_part = Fraction(rational_part) self.quadratic_power = quadratic_power self.quadratic_part = quadratic_part return self elif isinstance(rational_part, Quadratic): return rational_part else: raise NotImplementedError
def geometric_marginal_score_fct(i, base): """ Geometric marginal score functions. This is the additional (marginal) score from a voter for the `i`-th approved candidate in the committee. For example, the 2-Geometric marginal scoring function (`base=2`) is .. math:: f(i) = 1 / 2^{i-1}. For a mathematical description of Geometric score functions, see e.g. Martin Lackner and Piotr Skowron Utilitarian Welfare and Representation Guarantees of Approval-Based Multiwinner Rules In Artificial Intelligence, 288: 103366, 2020. https://arxiv.org/abs/1801.01527 Parameters ---------- i : int We are calculating the score for the `i`-th approved candidate in the committee. base : float or int or Fraction The base for the geometric function `1 / base ** (i-1)`. Returns ------- Fraction The corresponding marginal score. """ if i == 0: return 0 return Fraction(1, base**(i - 1))
def get_marginal_scorefct(scorefct_id, committeesize=None): """ Return marginal score function (for a Thiele method) given its name. Parameters ---------- scorefct_id : str A string identifying the score function. committeesize : int, optional Committee size. Some marginal score functions require fixing the size of committees. Returns ------- function The corresponding marginal score function. """ if scorefct_id == "pav": return pav_score_fct if scorefct_id == "slav": return slav_score_fct if scorefct_id == "cc": return cc_score_fct if scorefct_id == "av": return av_score_fct if scorefct_id[:4] == "geom": base = Fraction(scorefct_id[4:]) return functools.partial(geometric_marginal_score_fct, base=base) if scorefct_id[:7] == "atleast": param = int(scorefct_id[7:]) return functools.partial(at_least_ell_fct, ell=param) raise UnknownScoreFunctionError(scorefct_id)
def compute_av(profile, committeesize, resolute=False, sav=False): """Returns the list of winning committees according to Approval Voting""" enough_approved_candidates(profile, committeesize) appr_scores = [0] * profile.num_cand for pref in profile.preferences: for cand in pref.approved: if sav: # Satisfaction Approval Voting appr_scores[cand] += Fraction(pref.weight, len(pref.approved)) else: # (Classic) Approval Voting appr_scores[cand] += pref.weight # smallest score to be in the committee cutoff = sorted(appr_scores)[-committeesize] certain_cand = [ c for c in range(profile.num_cand) if appr_scores[c] > cutoff ] possible_cand = [ c for c in range(profile.num_cand) if appr_scores[c] == cutoff ] missing = committeesize - len(certain_cand) if resolute: return sort_committees([(certain_cand + possible_cand[:missing])]) else: return sort_committees([ (certain_cand + list(selection)) for selection in combinations(possible_cand, missing) ])
def elect_winners(self): eligible = self.candidates - self.elected - self.disqualified self.vote_weights = reweight_votes(self.vote_weights, eligible) if not self.vote_weights: raise Ambiguous() current_scores = {c: Fraction(0) for c in eligible} voters = defaultdict(lambda: []) for v, w in self.vote_weights.items(): c = v[0] assert c in eligible voters[c].append(v) current_scores[c] += w n_winners = 0 for candidate, score in current_scores.items(): if score >= self.quota: n_winners += 1 self.elected.add(candidate) excess = score - self.quota reweight = excess / score assert 0 <= reweight < 1 for v in voters[candidate]: self.vote_weights[v] *= reweight assert len(self.elected) * self.quota + sum( self.vote_weights.values()) == len(self.votes) return current_scores
def prm(ctx): """ Parser based on CHP and PRM files. CHP file contains contest meta-info and candidate code maps. PRM file contains ballot info. PRM files may appear separated out by precinct. """ # get candidate code map name_map = chp_names(ctx) # get prm file list prm_filepaths = chp_order(ctx) ballots = [] for prm_filepath in prm_filepaths: with open(prm_filepath, 'r', encoding='utf8') as f: for i in f: if any(map(str.isalnum, i)) and i.strip()[0] != '#': b = [] s = i.split() choices = [] if len(s) == 1 else s[1].split(',') for choice in filter(None, choices): can, rank = choice.split(']')[0].split('[') b.extend([SKIPPEDRANK] * (int(rank) - len(b) - 1)) b.append(OVERVOTE if '=' in choice else name_map[can]) ballots.append(b) # add in tail skipped ranks maxlen = max(map(len, ballots)) for b in ballots: b.extend([SKIPPEDRANK] * (maxlen - len(b))) return {'ranks': ballots, 'weight': [Fraction(1) for b in ballots]}
def slav_score_fct(i): """ SLAV (Sainte-Lague Approval Voting) marginal score function. This is the additional (marginal) score from a voter for the `i`-th approved candidate in the committee. For a mathematical description of this score function, see e.g. Martin Lackner and Piotr Skowron Utilitarian Welfare and Representation Guarantees of Approval-Based Multiwinner Rules In Artificial Intelligence, 288: 103366, 2020. https://arxiv.org/abs/1801.01527 Parameters ---------- i : int We are calculating the score for the `i`-th approved candidate in the committee. Returns ------- Fraction The corresponding marginal score. """ if i == 0: return 0 return Fraction(1, 2 * i - 1)
def compute_seqphragmen(profile, committeesize, resolute=False): """Returns the list of winning committees according to sequential Phragmen""" enough_approved_candidates(profile, committeesize) load = {v: 0 for v in profile.preferences} comm_loads = {(): load} approvers_weight = {} for c in range(profile.num_cand): approvers_weight[c] = sum(v.weight for v in profile.preferences if c in v.approved) # build committees starting with the empty set for _ in range(0, committeesize): comm_loads_next = {} for committee, load in comm_loads.items(): approvers_load = {} for c in range(profile.num_cand): approvers_load[c] = sum(v.weight * load[v] for v in profile.preferences if c in v.approved) new_maxload = [ Fraction(approvers_load[c] + 1, approvers_weight[c]) if approvers_weight[c] > 0 else committeesize + 1 for c in range(profile.num_cand) ] for c in range(profile.num_cand): if c in committee: new_maxload[c] = sys.maxsize for c in range(profile.num_cand): if new_maxload[c] <= min(new_maxload): new_load = {} for v in profile.preferences: if c in v.approved: new_load[v] = new_maxload[c] else: new_load[v] = load[v] comm_loads_next[tuple(sorted(committee + (c, )))] = new_load # remove suboptimal committees comm_loads = {} cutoff = min([max(load.values()) for load in comm_loads_next.values()]) for com, load in comm_loads_next.items(): if max(load.values()) <= cutoff: comm_loads[com] = load if resolute: committees = sort_committees(list(comm_loads.keys())) comm = tuple(committees[0]) comm_loads = {comm: comm_loads[comm]} committees = sort_committees(list(comm_loads.keys())) if resolute: return [committees[0]] else: return committees
def common_csv(ctx, path=None): # if no path passed, get from ctx if path is None: path = ctx['path'] # assume contest-specific filename, otherwise revert to default name cvr_path = path + '/' + ctx['dop'] + '.csv' if os.path.isfile(cvr_path) is False: cvr_path = path + '/cvr.csv' df = pd.read_csv(cvr_path) # find rank columns rank_col = [col for col in df.columns if 'rank' in col.lower()] # ensure rank columns are strings df[rank_col] = df[rank_col].astype(str) # if candidate codes file exist, swap in names candidate_codes_fpath = path + '/candidate_codes.csv' if os.path.isfile(candidate_codes_fpath): cand_codes = pd.read_csv(candidate_codes_fpath) cand_codes_dict = {str(code): cand for code, cand in zip(cand_codes['code'], cand_codes['candidate'])} replace_dict = {col: cand_codes_dict for col in rank_col} df = df.replace(replace_dict) # replace skipped ranks and overvotes with constants df = df.replace({col: {'under': SKIPPEDRANK, 'skipped': SKIPPEDRANK, 'undervote': SKIPPEDRANK, 'over': OVERVOTE, 'overvote': OVERVOTE} for col in rank_col}) # pull out rank lists rank_col_list = [df[col].tolist() for col in rank_col] rank_lists = [list(rank_tuple) for rank_tuple in list(zip(*rank_col_list))] # double check if not all([len(i) == len(rank_lists[0]) for i in rank_lists]): print('not all rank lists are same length. debug') raise RuntimeError # assemble dict dct = {'ranks': rank_lists} # add in non-rank columns for col in df.columns: if col not in rank_col: dct[col] = df[col].tolist() # add weight if not present in csv if 'weight' not in dct: dct['weight'] = [Fraction(1) for b in dct['ranks']] return dct
def largest_remainder(votes, seats, parties=string.ascii_letters, tiesallowed=True, verbose=True): if verbose: print("\nLargest remainder method with Hare quota (Hamilton)") q = Fraction(sum(votes), seats) quotas = [Fraction(p, q) for p in votes] representatives = [ int(qu.numerator) // int(qu.denominator) for qu in quotas ] ties = False if sum(representatives) < seats: remainders = [a - b for a, b in zip(quotas, representatives)] cutoff = sorted(remainders, reverse=True)[seats - sum(representatives) - 1] tiebreaking_message = (" tiebreaking in order of: " + str(parties[:len(votes)]) + "\n ties broken in favor of: ") for i in range(len(votes)): if sum(representatives) == seats and remainders[i] >= cutoff: if not ties: tiebreaking_message = tiebreaking_message[:-2] tiebreaking_message += "\n to the disadvantage of: " ties = True tiebreaking_message += parties[i] + ", " elif sum(representatives) < seats and remainders[i] > cutoff: representatives[i] += 1 elif sum(representatives) < seats and remainders[i] == cutoff: tiebreaking_message += parties[i] + ", " representatives[i] += 1 if ties and verbose: print(tiebreaking_message[:-2]) if ties and not tiesallowed: raise TiesException("Tie occurred") if verbose: __print_results(representatives, parties) return representatives
def get_scorefct(scorefct_str, committeesize): if scorefct_str == 'pav': return __pav_score_fct elif scorefct_str == 'cc': return __cc_score_fct elif scorefct_str == 'av': return __av_score_fct elif scorefct_str[:4] == 'geom': base = Fraction(scorefct_str[4:]) return functools.partial(__geom_score_fct, base=base) elif scorefct_str.startswith('generalizedcc'): param = Fraction(scorefct_str[13:]) return functools.partial(__generalizedcc_score_fct, ell=param, committeesize=committeesize) elif scorefct_str.startswith('lp-av'): param = Fraction(scorefct_str[5:]) return functools.partial(__lp_av_score_fct, ell=param) else: raise Exception("Scoring function", scorefct_str, "does not exist.")
def geometric_score_fct(i, base): """Geometric score functions. For a mathematical description of Geomtric score functions, see e.g. Martin Lackner and Piotr Skowron Utilitarian Welfare and Representation Guarantees of Approval-Based Multiwinner Rules In Artificial Intelligence, 288: 103366, 2020. https://arxiv.org/abs/1801.01527 """ if i == 0: return 0 else: return Fraction(1, base**(i - 1))
def slav_score_fct(i): """SLAV (Sainte-Lague Approval Voting) score function. For a mathematical description of this score function, see e.g. Martin Lackner and Piotr Skowron Utilitarian Welfare and Representation Guarantees of Approval-Based Multiwinner Rules In Artificial Intelligence, 288: 103366, 2020. https://arxiv.org/abs/1801.01527 """ if i == 0: return 0 else: return Fraction(1, 2 * i - 1)
def get_scorefct(scorefct_str, committeesize): if scorefct_str == 'pav': return __pav_score_fct elif scorefct_str == 'slav': return __slav_score_fct elif scorefct_str == 'cc': return __cc_score_fct elif scorefct_str == 'av': return __av_score_fct elif scorefct_str[:4] == 'geom': base = Fraction(scorefct_str[4:]) return functools.partial(__geom_score_fct, base=base) else: raise Exception("Score function", scorefct_str, "does not exist.")
def get_scorefct(scorefct_id, committeesize=None): """Return score function (for a Thiele method) given its name.""" if scorefct_id == "pav": return pav_score_fct elif scorefct_id == "slav": return slav_score_fct elif scorefct_id == "cc": return cc_score_fct elif scorefct_id == "av": return av_score_fct elif scorefct_id[:4] == "geom": base = Fraction(scorefct_id[4:]) return functools.partial(geometric_score_fct, base=base) else: raise UnknownScoreFunctionError(scorefct_id)
def __seqphragmen_irresolute(profile, committeesize, start_load=None, partial_committee=None): """Algorithm for computing irresolute seq-Phragmen (>=1 winning committees) """ approvers_weight = {} for c in range(profile.num_cand): approvers_weight[c] = sum(pref.weight for pref in profile if c in pref) load = start_load if load is None: load = {v: 0 for v, _ in enumerate(profile)} if partial_committee is None: partial_committee = [] # build committees starting with the empty set comm_loads = {tuple(partial_committee): load} for _ in range(len(partial_committee), committeesize): comm_loads_next = {} for committee, load in comm_loads.items(): approvers_load = {} for c in range(profile.num_cand): approvers_load[c] = sum(pref.weight * load[v] for v, pref in enumerate(profile) if c in pref) new_maxload = [ Fraction(approvers_load[c] + 1, approvers_weight[c]) if approvers_weight[c] > 0 else committeesize + 1 for c in range(profile.num_cand)] # exclude committees already in the committee for c in range(profile.num_cand): if c in committee: new_maxload[c] = sys.maxsize # compute new loads # and add new committees for c in range(profile.num_cand): if new_maxload[c] <= min(new_maxload): new_load = {} for v, pref in enumerate(profile): if c in pref: new_load[v] = new_maxload[c] else: new_load[v] = load[v] new_comm = tuple(sorted(committee + (c,))) comm_loads_next[new_comm] = new_load comm_loads = comm_loads_next committees = sort_committees(list(comm_loads.keys())) return committees, comm_loads
def __rule_x_get_min_q(profile, budget, cand): rich = set([v for v, pref in enumerate(profile) if cand in pref]) poor = set() while len(rich) > 0: poor_budget = sum(budget[v] for v in poor) q = Fraction(1 - poor_budget, len(rich)) new_poor = set([v for v in rich if budget[v] < q]) if len(new_poor) == 0: return q rich -= new_poor poor.update(new_poor) return None # not sufficient budget available
def minneapolis(ctx): choice_map = {} default = None if ctx['year'] == '2009': with open('/'.join(ctx['path'].split('/')[:-1] + ['convert.csv']), encoding='utf8') as f: for i in f: split = i.strip().split('\t') if len(split) >= 3 and split[0] == ctx['office']: choice_map[split[2]] = split[1] if choice_map == {}: print( 'No candidates found. Ensure "office" field in contest_set matches CVR.' ) raise RuntimeError choice_map['XXX'] = SKIPPEDRANK default = WRITEIN else: choice_map = { 'UWI': WRITEIN, 'undervote': SKIPPEDRANK, 'overvote': OVERVOTE } path = ctx['path'] precincts = [] ballots = [] with open(path, "r", encoding='utf8') as f: f.readline() for line in csv.reader(f): choices = [ choice_map.get(i.strip(), i if default is None else default) for i in line[1:-1] ] if choices != ['', '', '']: ballots.extend([choices] * int(float(line[-1]))) for p in range(int(float(line[-1]))): precincts.append(line[0]) bs = { 'ranks': ballots, 'weight': [Fraction(1) for b in ballots], 'precinct': precincts } return bs
def pav_score_fct(i): """ PAV marginal score function. This is the additional (marginal) score from a voter for the `i`-th approved candidate in the committee. Parameters ---------- i : int We are calculating the score for the `i`-th approved candidate in the committee. Returns ------- Fraction The corresponding marginal score. """ if i == 0: return 0 return Fraction(1, i)
def lsb_leak_attack(lsb_oracle, n, e, c): """RSA LSB Leak Attack Given a cryptosystem such that: - Using the "textbook" RSA (RSA without pading) - We can give any ciphertexts to decrypt and can get the least significant bit of decrypted plaintext. - We can try to decrypt ciphertexts without limit we can break the ciphertext with LSB Leak Attack(We should make name more cool) Usage: plain = padding_oracle(lsb_oracle, N, e, C) The function lsb_oracle must return LSB (1 or 0). """ logger = getLogger(__name__) L = n.bit_length() t = L // 100 left, right = 0, n c2 = c i = 0 while right - left > 1: m = Fraction(left + right, 2) c2 = (c2 * pow(2, e, n)) % n oracle = lsb_oracle(c2) if oracle == 1: left = m elif oracle == 0: right = m else: raise ValueError("The function `lsb_oracle` must return 1 or 0") i += 1 if i % t == 0: logger.info("LSB Leak Attack {}/{}".format(i, L)) assert (i <= L) return int(ceil(left))
def elect_winners(self): vote_retention = { c: Fraction(0) if c in self.disqualified else Fraction(1) for c in self.candidates } for _ in range(MEEK_ITERATIONS): excess = Fraction(0) candidate_scores = { c: Fraction(0) for c in self.candidates } for v in self.votes: remaining = Fraction(1) for c in v: capture = vote_retention[c] assert 0 <= capture <= 1 candidate_scores[c] += capture * remaining remaining *= (1 - capture) assert remaining == 0 quota = self.quota_type(len(self.votes) - excess, self.winners) precision = 0 for v in candidate_scores.values(): assert v >= 0 assert sum(candidate_scores.values()) + excess == len(self.votes) for e in self.elected: score = candidate_scores[e] if score == 0: reweight = 1 else: reweight = quota / score vote_retention[e] = min( Fraction(1), vote_retention[e] * reweight) precision = max(precision, abs(1 - reweight)) for e in self.disqualified: assert candidate_scores[e] == 0 if precision <= MEEK_PRECISION: for candidate, score in candidate_scores.items(): if score >= quota: self.elected.add(candidate) return candidate_scores assert False
from functools import reduce from gmpy2 import mpz, mpq as Fraction, is_square, isqrt __all__ = ["Quadratic"] primes = (2, 3, 5, 7) def exp2(n): return {1: 0, 3: 1, 7: 2, 15: 3}[n ^ (n - 1)] _PyHASH_MODULUS = sys.hash_info.modulus mpz_type = type(mpz()) mpq_type = type(Fraction()) class Quadratic(numbers.Real): __slots__ = ("rational_part", "quadratic_power", "quadratic_part") def __new__(cls, rational_part=Fraction(), quadratic_power=0, quadratic_part=None): self = super(Quadratic, cls).__new__(cls) if type(rational_part) is str: rational_part = Fraction(rational_part) if isinstance(rational_part, (int, mpz_type, mpq_type)): self.rational_part = Fraction(rational_part) self.quadratic_power = quadratic_power
def sqrt(self, x, digits): if is_square(x.numerator) and is_square(x.denominator): y = isqrt(x.numerator) z = isqrt(x.denominator) self.check(Fraction(y, z), digits, Expression.sqrt(x))
@pytest.mark.parametrize("committee,score", [([1, 3, 2], 5), ([2, 1, 5], 4), ([2, 5, 4], 3), ([0, 1, 2, 3, 4, 5], 6)]) @pytest.mark.parametrize("num_cand", [6, 7, 8]) def test_monroescore_matching(committee, score, num_cand): profile = Profile(num_cand) approval_sets = [[0, 1], [1], [1, 3], [4], [2], [1, 5, 3]] profile.add_voters(approval_sets) assert monroescore_matching(profile, committee) == score @pytest.mark.parametrize( "scorefct_id,score", [ ("pav", Fraction(119, 12)), ("av", 14), ("slav", Fraction(932, 105)), ("cc", 7), ("geom2", Fraction(77, 8)), ], ) @pytest.mark.parametrize("num_cand", [8, 9]) def test_thiele_scores(scorefct_id, score, num_cand): profile = Profile(num_cand) approval_sets = [[0, 1], [1], [1, 3], [4], [1, 2, 3, 4, 5], [1, 5, 3], [0, 1, 2, 4, 5]] profile.add_voters(approval_sets) committee = [6, 7] assert scores.thiele_score(scorefct_id, profile, committee) == 0 committee = [1, 2, 3, 4]
def to_number(string): number = Fraction(string) if number.denominator == 1: number = int(number) return number
def __slav_score_fct(i): if i == 0: return 0 else: return Fraction(1, 2 * i - 1)
def __pav_score_fct(i): if i == 0: return 0 else: return Fraction(1, i)