예제 #1
0
 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]
예제 #2
0
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]
예제 #3
0
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]
예제 #4
0
    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
예제 #5
0
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())
예제 #6
0
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
예제 #7
0
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