def __str__(self): if self.has_unit_weights(): output = ("profile with %d votes and %d candidates:\n" % (len(self.preferences), self.num_cand)) for p in self.preferences: output += " " + str_candset(p.approved, self.names) + ",\n" else: output = ("weighted profile with %d votes and %d candidates:\n" % (len(self.preferences), self.num_cand)) for p in self.preferences: output += (" " + str(p.weight) + " * " + str_candset(p.approved, self.names) + ",\n") return output[:-2]
def __revseq_thiele_resolute(profile, committeesize, scorefct_str, verbose): """Compute a *resolute* reverse sequential Thiele method Tiebreaking between candidates in favor of candidate with smaller number/index (candidates with smaller numbers are added first). """ scorefct = scores.get_scorefct(scorefct_str, committeesize) committee = set(range(profile.num_cand)) # optional output if verbose >= 2: output = "full committee (" + str(len(committee)) output += " candidates) has a total score of " output += str(scores.thiele_score( scorefct_str, profile, committee)) print(output + "\n") # end of optional output for _ in range(profile.num_cand - committeesize): marg_util_cand = scores.marginal_thiele_scores_remove( scorefct, profile, committee) score_reduction = min(marg_util_cand) # find smallest elements in marg_util_cand and return indices cands_to_remove = [cand for cand in range(profile.num_cand) if marg_util_cand[cand] == min(marg_util_cand)] committee.remove(cands_to_remove[-1]) # optional output if verbose >= 2: rem_cand = cands_to_remove[-1] output = "removing candidate number " output += str(profile.num_cand - len(committee)) + ": " output += profile.names[rem_cand] + "\n" output += " score decreases by " output += str(score_reduction) output += " to a total of " output += str(scores.thiele_score( scorefct_str, profile, committee)) if len(cands_to_remove) > 1: output += " (tie between candidates " output += str_candset(cands_to_remove) + ")\n" print(output + "\n") # end of optional output return [committee]
def __seq_thiele_resolute(profile, committeesize, scorefct_str, verbose): """Compute a *resolute* reverse sequential Thiele method Tiebreaking between candidates in favor of candidate with smaller number/index (candidates with larger numbers get deleted first). """ committee = [] scorefct = scores.get_scorefct(scorefct_str, committeesize) # optional output if verbose >= 2: output = "starting with the empty committee (score = " output += str(scores.thiele_score( scorefct_str, profile, committee)) + ")" print(output + "\n") # end of optional output # build a committee starting with the empty set for _ in range(committeesize): additional_score_cand = scores.marginal_thiele_scores_add( scorefct, profile, committee) next_cand = additional_score_cand.index(max(additional_score_cand)) committee.append(next_cand) # optional output if verbose >= 2: output = "adding candidate number " output += str(len(committee)) + ": " output += profile.names[next_cand] + "\n" output += " score increases by " output += str(max(additional_score_cand)) output += " to a total of " output += str(scores.thiele_score( scorefct_str, profile, committee)) tied_cands = [c for c in range(len(additional_score_cand)) if (c > next_cand and (additional_score_cand[c] == max(additional_score_cand)))] if len(tied_cands) > 0: output += " tie broken in favor of " + str(next_cand) output += " candidates " + str_candset(tied_cands) output += " would increase the score by the same amount (" output += str(max(additional_score_cand)) + ")" print(output + "\n") # end of optional output return [committee]
def str_compact(self): compact = OrderedDict() for p in self.preferences: if tuple(p.approved) in compact: compact[tuple(p.approved)] += p.weight else: compact[tuple(p.approved)] = p.weight if self.has_unit_weights(): output = "" else: output = "weighted " output += ("profile with %d votes and %d candidates:\n" % (len(self.preferences), self.num_cand)) for apprset in compact: output += (" " + str(compact[apprset]) + " x " + str_candset(apprset, self.names) + ",\n") output = output[:-2] if not self.has_unit_weights(): output += "\ntotal weight: " + str(self.totalweight()) output += "\n" return output
a, b, c = (0, 1, 2) apprsets = [[a]] * 2 + [[a, c]] * 3 + [[b, c]] * 3 + [[b]] * 2 names = "abcde" profile = Profile(num_cand, names=names) profile.add_preferences(apprsets) print(misc.header("1st profile:")) print(profile.str_compact()) print("winning committees for k=1 and k=2:") for rule_id in ["pav", "cc", "monroe", "optphrag", "mav"]: comm1 = abcrules.compute(rule_id, profile, 1, resolute=True)[0] comm2 = abcrules.compute(rule_id, profile, 2, resolute=True)[0] print(" " + abcrules.rules[rule_id].shortname + ": " + misc.str_candset(comm1, names) + " vs " + misc.str_candset(comm2, names)) num_cand = 4 a, b, c, d = 0, 1, 2, 3 apprsets = ([[a]] * 6 + [[a, c]] * 4 + [[a, b, c]] * 2 + [[a]] * 2 + [[a, d]] * 1 + [[b, d]] * 3) names = "abcde" profile = Profile(num_cand, names=names) profile.add_preferences(apprsets) print() print(misc.header("2nd profile:")) print(profile.str_compact())
def __seqphragmen_resolute(profile, committeesize, verbose, start_load=None, partial_committee=None): """Algorithm for computing resolute seq-Phragmen (1 winning committee)""" 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)} committee = partial_committee if partial_committee is None: committee = [] # build committees starting with the empty set for _ in range(len(committee), committeesize): 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 large = max(new_maxload) + 1 for c in range(profile.num_cand): if c in committee: new_maxload[c] = large # find smallest maxload opt = min(new_maxload) next_cand = new_maxload.index(opt) # compute new loads and add new candidate for v, pref in enumerate(profile): if next_cand in pref: load[v] = new_maxload[next_cand] else: load[v] = load[v] committee = committee + [next_cand] # optional output if verbose >= 2: output = "adding candidate number " output += str(len(committee)) + ": " output += profile.names[next_cand] + "\n" output += " maximum load increased to " output += str(opt) print(output) print(" load distribution:") output = " (" for v, _ in enumerate(profile): output += str(load[v]) + ", " print(output[:-2] + ")") tied_cands = [c for c in range(profile.num_cand) if (c > next_cand and (new_maxload[c] == new_maxload))] if len(tied_cands) > 0: output = " tie broken in favor of " + profile.names[next_cand] output += ",\n candidates " + str_candset(tied_cands) output += " would increase the load to the same amount (" output += str(new_maxload) + ")" print(output) print() # end of optional output comm_loads = {tuple(committee): load} return [committee], comm_loads
def compute_rule_x(profile, committeesize, algorithm="standard", resolute=True, verbose=0): """Rule X See https://arxiv.org/pdf/1911.11747.pdf, page 7 """ enough_approved_candidates(profile, committeesize) if not profile.has_unit_weights(): raise ValueError(rules["rule-x"].shortname + " is only defined for unit weights (weight=1)") if algorithm != "standard": raise NotImplementedError( "Algorithm " + str(algorithm) + " not specified for compute_rule_x") # optional output if verbose: print(header(rules["rule-x"].longname)) if resolute: print("Computing only one winning committee (resolute=True)\n") # end of optional output start_budget = {v: Fraction(committeesize, len(profile)) for v, _ in enumerate(profile)} cands = range(profile.num_cand) commbugdets = [(set(), start_budget)] final_committees = set() # optional output if resolute and verbose >= 2: print("Phase 1:\n") print("starting budget:") output = " (" for v, _ in enumerate(profile): output += str(start_budget[v]) + ", " print(output[:-2] + ")\n") # end of optional output for _ in range(committeesize): next_commbudgets = [] for committee, budget in commbugdets: curr_cands = set(cands) - committee min_q = {} for c in curr_cands: q = __rule_x_get_min_q(profile, budget, c) if q is not None: min_q[c] = q if len(min_q) > 0: # one or more candidates are affordable next_cands = [c for c in min_q.keys() if min_q[c] == min(min_q.values())] for next_cand in next_cands: new_budget = dict(budget) for v, pref in enumerate(profile): if next_cand in pref: new_budget[v] -= min(budget[v], min_q[next_cand]) new_comm = set(committee) new_comm.add(next_cand) next_commbudgets.append((new_comm, new_budget)) # optional output if resolute and verbose >= 2: output = "adding candidate number " output += str(len(committee)) + ": " output += profile.names[next_cand] + "\n" output += " with maxmimum cost per voter q = " output += str(min(min_q.values())) print(output) print(" remaining budget:") output = " (" for v, _ in enumerate(profile): output += str(new_budget[v]) + ", " print(output[:-2] + ")") if len(next_cands) > 1: output = " tie broken in favor of " output += profile.names[next_cand] + "," output += "\n candidates " output += str_candset(next_cands[1:]) output += " are tied" print(output) print() # end of optional output if resolute: break else: # no affordable candidates remain # fill committee via seq-Phragmen # optional output if resolute and verbose >= 2: print("Phase 2 (seq-Phragmén):\n") # end of optional output start_load = {} # translate budget to loads for v in range(len(profile)): start_load[v] = (Fraction(committeesize, len(profile)) - budget[v]) # optional output if resolute and verbose >= 2: print("starting loads (= budget spent):") output = " (" for v, _ in enumerate(profile): output += str(start_load[v]) + ", " print(output[:-2] + ")\n") # end of optional output if resolute: committees, _ = __seqphragmen_resolute( profile, committeesize, verbose=verbose, partial_committee=list(committee), start_load=start_load) else: committees, _ = __seqphragmen_irresolute( profile, committeesize, partial_committee=list(committee), start_load=start_load) final_committees.update([tuple(comm) for comm in committees]) # after filling the remaining spots these committees # have size committeesize commbugdets = next_commbudgets final_committees.update([tuple(comm) for comm, _ in commbugdets]) committees = sort_committees(final_committees) if resolute: committees = committees[:1] # optional output if verbose: print(str_committees_header(committees, winning=True)) print(str_candsets(committees, names=profile.names)) # end of optional output return committees