Ejemplo n.º 1
0
def compute_optphragmen(profile, committeesize,
                        algorithm="gurobi", resolute=False, verbose=0):
    enough_approved_candidates(profile, committeesize)

    # optional output
    if verbose:
        print(header(rules["optphrag"].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")
    if verbose >= 3:
        if algorithm == "gurobi":
            print("Using the Gurobi ILP solver")
    # end of optional output

    if algorithm != "gurobi":
        raise NotImplementedError("Algorithm " + str(algorithm)
                                  + " not specified for compute_optphragmen")

    committees = abcrules_gurobi.__gurobi_optphragmen(
        profile, committeesize, resolute=resolute, verbose=verbose)
    committees = sort_committees(committees)

    # optional output
    if verbose:
        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    # end of optional output

    return committees
Ejemplo n.º 2
0
def compute_lexmav(profile, committeesize, algorithm="brute-force",
                   resolute=False, verbose=0):
    """Lexicographic Minimax AV"""
    enough_approved_candidates(profile, committeesize)

    if not profile.has_unit_weights():
        raise ValueError(rules["lexmav"].shortname +
                         " is only defined for unit weights (weight=1)")

    if algorithm != "brute-force":
        raise NotImplementedError(
            "Algorithm " + str(algorithm)
            + " not specified for compute_lexmav")

    opt_committees = []
    opt_distances = [profile.num_cand + 1] * len(profile)
    for comm in combinations(list(range(profile.num_cand)), committeesize):
        distances = sorted([hamming(pref, comm)
                            for pref in profile],
                           reverse=True)
        for i in range(len(distances)):
            if opt_distances[i] < distances[i]:
                break
            if opt_distances[i] > distances[i]:
                opt_distances = distances
                opt_committees = [comm]
                break
        else:
            opt_committees.append(comm)

    committees = sort_committees(opt_committees)
    if resolute:
        committees = [committees[0]]

    # optional output
    if verbose:
        print(header(rules["lexmav"].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")

        print("Minimum maximal distance: " + str(max(opt_distances)))

        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))

        print("Corresponding distances to voters:")
        for comm in committees:
            print([hamming(pref, comm) for pref in profile])
        print()
    # end of optional output

    return committees
Ejemplo n.º 3
0
def compute_thiele_method(scorefct_str, profile, committeesize,
                          algorithm="gurobi", resolute=False, verbose=0):
    """Thiele methods

    Compute winning committees of the Thiele method specified
    by the score function (scorefct_str)
    """
    enough_approved_candidates(profile, committeesize)
    scorefct = scores.get_scorefct(scorefct_str, committeesize)

    # optional output
    if verbose:
        print(header(rules[scorefct_str].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")
    if verbose >= 3:
        if algorithm == "gurobi":
            print("Using the Gurobi ILP solver\n")
        if algorithm == "branch-and-bound":
            print("Using a branch-and-bound algorithm\n")
    # end of optional output

    if algorithm == "gurobi":
        committees = abcrules_gurobi.__gurobi_thiele_methods(
            profile, committeesize, scorefct, resolute)

        committees = sort_committees(committees)
    elif algorithm == "branch-and-bound":
        committees = __thiele_methods_branchandbound(
            profile, committeesize, scorefct_str, resolute)
    else:
        raise NotImplementedError(
            "Algorithm " + str(algorithm)
            + " not specified for compute_thiele_method")

    # optional output
    if verbose >= 2:
        print("Optimal " + scorefct_str.upper() + "-score: "
              + str(scores.thiele_score(scorefct_str, profile, committees[0])))
        print()
    if verbose:
        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    # end of optional output

    return committees
Ejemplo n.º 4
0
def compute_mav(profile, committeesize, algorithm="brute-force",
                resolute=False, verbose=0):
    """Minimax AV (MAV)"""
    enough_approved_candidates(profile, committeesize)

    # optional output
    if verbose:
        print(header(rules["mav"].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")
    if verbose >= 3:
        if algorithm == "gurobi":
            print("Using the Gurobi ILP solver\n")
        if algorithm == "brute-force":
            print("Using a brute-force algorithm\n")
    # end of optional output

    if algorithm == "gurobi":
        committees = abcrules_gurobi.__gurobi_minimaxav(
            profile, committeesize, resolute)
        committees = sort_committees(committees)
    elif algorithm == "brute-force":
        committees = __minimaxav_bruteforce(profile, committeesize)
        if resolute:
            committees = [committees[0]]
    else:
        raise NotImplementedError("Algorithm " + str(algorithm)
                                  + " not specified for compute_mav")

    opt_mavscore = scores.mavscore(profile, committees[0])

    # optional output
    if verbose:
        print("Minimum maximal distance: " + str(opt_mavscore))

        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))

        print("Corresponding distances to voters:")
        for comm in committees:
            print([hamming(pref, comm) for pref in profile])
        print()
    # end of optional output

    return committees
Ejemplo n.º 5
0
def __thiele_methods_branchandbound(profile, committeesize,
                                    scorefct_str, resolute):
    """Branch-and-bound algorithm to compute winning committees
    for Thiele methods"""
    enough_approved_candidates(profile, committeesize)
    scorefct = scores.get_scorefct(scorefct_str, committeesize)

    best_committees = []
    init_com = compute_seq_thiele_method(
        profile, committeesize, scorefct_str, resolute=True)[0]
    best_score = scores.thiele_score(scorefct_str, profile, init_com)
    part_coms = [[]]
    while part_coms:
        part_com = part_coms.pop(0)
        # potential committee, check if at least as good
        # as previous best committee
        if len(part_com) == committeesize:
            score = scores.thiele_score(scorefct_str, profile, part_com)
            if score == best_score:
                best_committees.append(part_com)
            elif score > best_score:
                best_committees = [part_com]
                best_score = score
        else:
            if len(part_com) > 0:
                largest_cand = part_com[-1]
            else:
                largest_cand = -1
            missing = committeesize - len(part_com)
            marg_util_cand = scores.marginal_thiele_scores_add(
                scorefct, profile, part_com)
            upper_bound = (
                sum(sorted(marg_util_cand[largest_cand + 1:])[-missing:])
                + scores.thiele_score(scorefct_str, profile, part_com))
            if upper_bound >= best_score:
                for c in range(largest_cand + 1,
                               profile.num_cand - missing + 1):
                    part_coms.insert(0, part_com + [c])

    committees = sort_committees(best_committees)
    if resolute:
        committees = [committees[0]]

    return committees
Ejemplo n.º 6
0
def compute_monroe(profile, committeesize, algorithm="brute-force",
                   resolute=False, verbose=0):
    """Monroe's rule"""
    enough_approved_candidates(profile, committeesize)

    # optional output
    if verbose:
        print(header(rules["monroe"].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")
    if verbose >= 3:
        if algorithm == "gurobi":
            print("Using the Gurobi ILP solver\n")
        if algorithm == "brute-force":
            print("Using a brute-force algorithm\n")
    # end of optional output

    if not profile.has_unit_weights():
        raise ValueError(rules["monroe"].shortname +
                         " is only defined for unit weights (weight=1)")

    if algorithm == "gurobi":
        committees = abcrules_gurobi.__gurobi_monroe(
            profile, committeesize, resolute)
        committees = sort_committees(committees)
    elif algorithm == "brute-force":
        committees = __monroe_bruteforce(
            profile, committeesize, resolute)
    else:
        raise NotImplementedError(
            "Algorithm " + str(algorithm)
            + " not specified for compute_monroe")

    # optional output
    if verbose:
        print("Optimal Monroe score: "
              + str(scores.monroescore(profile, committees[0])) + "\n")

        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    # end of optional output

    return committees
Ejemplo n.º 7
0
def compute_seqphragmen(profile, committeesize, algorithm="standard",
                        resolute=True, verbose=False):
    """Phragmen's sequential rule (seq-Phragmen)"""
    enough_approved_candidates(profile, committeesize)

    if algorithm != "standard":
        raise NotImplementedError(
            "Algorithm " + str(algorithm)
            + " not specified for compute_seqphragmen")

    # optional output
    if verbose:
        print(header(rules["seqphrag"].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")
    # end of optional output

    if resolute:
        committees, comm_loads = __seqphragmen_resolute(
            profile, committeesize, verbose)
    else:
        committees, comm_loads = __seqphragmen_irresolute(
            profile, committeesize)

    # optional output
    if verbose:
        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    if verbose >= 2:
        if resolute or len(committees) == 1:
            print("corresponding load distribution:")
        else:
            print("corresponding load distributions:")
        for comm in committees:
            output = "("
            for v, _ in enumerate(profile):
                output += str(comm_loads[tuple(comm)][v]) + ", "
            print(output[:-2] + ")")
    # end of optional output

    return committees
Ejemplo n.º 8
0
def compute_revseq_thiele_method(profile, committeesize,
                                 scorefct_str, algorithm="standard",
                                 resolute=True, verbose=0):
    """Reverse sequential Thiele methods"""
    enough_approved_candidates(profile, committeesize)

    if algorithm != "standard":
        raise NotImplementedError(
            "Algorithm " + str(algorithm)
            + " not specified for compute_revseq_thiele_method")

    # optional output
    if verbose:
        print(header(rules["revseq" + scorefct_str].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")
    # end of optional output

    if resolute:
        committees = __revseq_thiele_resolute(
            profile, committeesize, scorefct_str, verbose=verbose)
    else:
        committees = __revseq_thiele_irresolute(
            profile, committeesize, scorefct_str)

    # optional output
    if verbose:
        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    if verbose >= 2:
        if resolute or len(committees) == 1:
            print("PAV-score of winning committee:", end="")
        else:
            print("PAV-score of winning committees:")
        for comm in committees:
            print(" " + str(scores.thiele_score(scorefct_str, profile, comm)))
        print()
    # end of optional output

    return committees
Ejemplo n.º 9
0
def compute_greedy_monroe(profile, committeesize,
                          algorithm="standard", resolute=True, verbose=0):
    """"Greedy Monroe"""
    enough_approved_candidates(profile, committeesize)
    if not profile.has_unit_weights():
        raise ValueError(rules["greedy-monroe"].shortname +
                         " is only defined for unit weights (weight=1)")

    if not resolute:
        raise NotImplementedError(
            "compute_greedy_monroe does not support resolute=False.")

    if algorithm != "standard":
        raise NotImplementedError(
            "Algorithm " + str(algorithm)
            + " not specified for compute_greedy_monroe")

    num_voters = len(profile)
    committee = []

    # remaining voters
    remaining_voters = list(range(num_voters))
    remaining_cands = set(range(profile.num_cand))

    assignment = []
    for t in range(committeesize):
        maxapprovals = -1
        selected = None
        for c in remaining_cands:
            approvals = len([i for i in remaining_voters
                             if c in profile[i]])
            if approvals > maxapprovals:
                maxapprovals = approvals
                selected = c

        # determine how many voters are removed (at most)
        if t < num_voters - committeesize * (num_voters // committeesize):
            num_remove = num_voters // committeesize + 1
        else:
            num_remove = num_voters // committeesize

        # only voters that approve the chosen candidate
        # are removed
        to_remove = [i for i in remaining_voters
                     if selected in profile[i]]
        if len(to_remove) > num_remove:
            to_remove = to_remove[:num_remove]
        assignment.append((selected, to_remove))
        remaining_voters = [i for i in remaining_voters
                            if i not in to_remove]
        committee.append(selected)
        remaining_cands.remove(selected)

    committees = sort_committees([committee])

    # optional output
    if verbose:
        print(header(rules["greedy-monroe"].longname))

    if verbose >= 2:
        score1 = scores.monroescore(profile, committees[0])

        score2 = len(profile) - len(remaining_voters)
        print("The Monroe assignment computed by Greedy Monroe")
        print("has a Monroe score of " + str(score2) + ".")

        if score1 > score2:
            print("Monroe assignment found by Greedy Monroe is not "
                  + "optimal for the winning committee,")
            print("i.e., by redistributing voters to candidates a higher "
                  + "satisfaction is possible "
                  + "(without changing the committee).")
            print("Optimal Monroe score of the winning committee is "
                  + str(score1) + ".")

        # build actual Monroe assignment for winning committee
        for t, district in enumerate(assignment):
            cand, voters = district
            if t < num_voters - committeesize * (num_voters // committeesize):
                missing = num_voters // committeesize + 1 - len(voters)
            else:
                missing = num_voters // committeesize - len(voters)
            for _ in range(missing):
                v = remaining_voters.pop()
                voters.append(v)

        print("Assignment (unsatisfatied voters marked with *):\n")
        for cand, voters in assignment:
            print(" candidate " + profile.names[cand]
                  + " assigned to: ", end="")
            output = ""
            for v in sorted(voters):
                output += str(v)
                if cand not in profile[v].approved:
                    output += "*"
                output += ", "
            print(output[:-2])
        print()

    if verbose:
        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    # end of optional output

    return committees
Ejemplo n.º 10
0
def __separable(rule_id, profile, committeesize, resolute, verbose):
    enough_approved_candidates(profile, committeesize)

    appr_scores = [0] * profile.num_cand
    for pref in profile:
        for cand in pref:
            if rule_id == "sav":
                # Satisfaction Approval Voting
                appr_scores[cand] += Fraction(pref.weight, len(pref))
            elif rule_id == "av":
                # (Classic) Approval Voting
                appr_scores[cand] += pref.weight
            else:
                raise UnknownRuleIDError(rule_id)

    # smallest score to be in the committee
    cutoff = sorted(appr_scores)[-committeesize]

    certain_cands = [c for c in range(profile.num_cand)
                     if appr_scores[c] > cutoff]
    possible_cands = [c for c in range(profile.num_cand)
                      if appr_scores[c] == cutoff]
    missing = committeesize - len(certain_cands)
    if len(possible_cands) == missing:
        # candidates with appr_scores[c] == cutoff
        # are also certain candidates because all these candidates
        # are required to fill the committee
        certain_cands = sorted(certain_cands + possible_cands)
        possible_cands = []
        missing = 0

    if resolute:
        committees = sort_committees(
            [(certain_cands + possible_cands[:missing])])
    else:
        committees = sort_committees(
            [(certain_cands + list(selection))
             for selection
             in combinations(possible_cands, missing)])

    # optional output
    if verbose:
        print(header(rules[rule_id].longname))
        if resolute:
            print("Computing only one winning committee (resolute=True)\n")
    if verbose >= 2:
        print("Scores of candidates:")
        for c in range(profile.num_cand):
            print(profile.names[c] + ": " + str(appr_scores[c]))

        print("\nCandidates are contained in winning committees")
        print("if their score is >= " + str(cutoff) + ".")

        if len(certain_cands) > 0:
            print("\nThe following candidates are contained in")
            print("every winning committee:")
            namedset = [profile.names[c] for c in certain_cands]
            print(" " + ", ".join(map(str, namedset)))
            print()

        if len(possible_cands) > 0:
            print("The following candidates are contained in")
            print("some of the winning committees:")
            namedset = [profile.names[c] for c in possible_cands]
            print(" " + ", ".join(map(str, namedset)))
            print("(" + str(missing) + " of those candidates is contained\n"
                  + " in every winning committee.)\n")
    if verbose:
        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    # end of optional output

    return committees
Ejemplo n.º 11
0
def compute_phragmen_enestroem(profile, committeesize, algorithm="standard",
                               resolute=True, verbose=0):
    """"Phragmen-Enestroem (aka Phragmen's first method, Enestroem's method)

    In every round the candidate with the highest combined budget of
    their supporters is put in the committee.
    Method described in:
    https://arxiv.org/pdf/1611.08826.pdf (Section 18.5, Page 59)
    """
    enough_approved_candidates(profile, committeesize)
    if not profile.has_unit_weights():
        raise ValueError(rules["phrag-enestr"].shortname +
                         " is only defined for unit weights (weight=1)")

    if algorithm != "standard":
        raise NotImplementedError(
            "Algorithm " + str(algorithm)
            + " not specified for compute_phragmen_enestroem")

    num_voters = len(profile)

    start_budget = {i: Fraction(profile[i].weight)
                    for i in range(num_voters)}
    price = Fraction(sum(start_budget.values()), committeesize)

    cands = range(profile.num_cand)

    committees = [(start_budget, set())]
    for _ in range(committeesize):
        # here the committees with i+1 candidates are
        # stored (together with budget)
        next_committees = []
        # loop in case multiple possible committees
        # with i filled candidates
        for committee in committees:
            budget, comm = committee
            curr_cands = set(cands) - comm
            support = {c: 0 for c in curr_cands}
            for nr, pref in enumerate(profile):
                voting_power = budget[nr]
                if voting_power <= 0:
                    continue
                for cand in pref:
                    if cand in curr_cands:
                        support[cand] += voting_power
            max_support = max(support.values())
            winners = [c for c, s in support.items()
                       if s == max_support]
            for cand in winners:
                b = dict(budget)  # copy of budget
                if max_support > price:  # supporters can afford it
                    # (voting_power - price) / voting_power
                    multiplier = Fraction(max_support - price,
                                          max_support)
                else:  # set supporters to 0
                    multiplier = 0
                for nr, pref in enumerate(profile):
                    if cand in pref:
                        b[nr] *= multiplier
                c = comm.union([cand])  # new committee with candidate
                next_committees.append((b, c))

        if resolute:
            committees = [next_committees[0]]
        else:
            committees = next_committees
    committees = [comm for b, comm in committees]
    committees = sort_committees(committees)
    if resolute:
        committees = [committees[0]]

    # optional output
    if verbose:
        print(header(rules["phrag-enestr"].longname))

        print(str_committees_header(committees, winning=True))
        print(str_candsets(committees, names=profile.names))
    # end of optional output

    return committees
Ejemplo n.º 12
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
Ejemplo n.º 13
0
def output(profile, gen_profile_name):
    print("Randomly generated profile via " + gen_profile_name + ":")
    print(str(profile))
    print("****************************************")


"""
For some methods, it might happen that fewer than
committeesize many candidates are approved (in total by all voters).
We thus recommended to verify this before computing the rule.
"""

while True:
    profile = genprofiles.random_urn_profile(num_cand, 5, 2, 0.4)
    try:
        enough_approved_candidates(profile, committeesize)
        break
    except ValueError:
        pass
output(profile, "random_urn")

while True:
    profile = genprofiles.random_urn_party_list_profile(num_cand,
                                                        3,
                                                        2,
                                                        0.4,
                                                        uniform=False)
    try:
        enough_approved_candidates(profile, committeesize)
        break
    except ValueError: