Exemple #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
Exemple #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
Exemple #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
Exemple #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
Exemple #5
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
Exemple #6
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
Exemple #7
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
Exemple #8
0
"""
Proposition A.4.

From "Multi-Winner Voting with Approval Preferences"
by Martin Lackner and Piotr Skowron
https://arxiv.org/abs/2007.01795
"""

from abcvoting import abcrules
from abcvoting.preferences import Profile
from abcvoting import misc
from collections import namedtuple

print(misc.header("Proposition A.4", "*"))

num_cand = 6
a, b, c, d, e, f = range(6)  # a = 0, b = 1, c = 2, ...
cand_names = "abcdef"

ManipulationInstance = namedtuple(
    "ManipulationInstance",
    "rule_id committeesize approval_sets manipulated_vote committees_first committees_after",
)

manipulations = [
    ManipulationInstance(
        rule_id="cc",
        committeesize=2,
        approval_sets=[{a, b}] + [{a}] * 3 + [{c}],
        manipulated_vote={b},
        committees_first=[{a, c}],
Exemple #9
0
Proposition A.2.

From "Multi-Winner Voting with Approval Preferences"
by Martin Lackner and Piotr Skowron
https://arxiv.org/abs/2007.01795
"""

from abcvoting import abcrules
from abcvoting.preferences import Profile
from abcvoting import misc
from abcvoting.output import output
from abcvoting.output import DETAILS

output.set_verbosity(DETAILS)

print(misc.header("Proposition A.2", "*"))

###

num_cand = 3
a, b, c = (0, 1, 2)
approval_sets = [{a}] * 2 + [{a, c}] * 3 + [{b, c}] * 3 + [{b}] * 2
cand_names = "abcde"
profile = Profile(num_cand, cand_names=cand_names)
profile.add_voters(approval_sets)

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", "minimaxphragmen", "minimaxav"]:
Exemple #10
0
"""Proposition A.4
from the survey: "Approval-Based Multi-Winner Voting:
Axioms, Algorithms, and Applications"
by Martin Lackner and Piotr Skowron
"""

from abcvoting import abcrules
from abcvoting.preferences import Profile
from abcvoting import misc

print(misc.header("Proposition A.4", "*"))

num_cand = 6
a, b, c, d, e, f = range(6)  # a = 0, b = 1, c = 2, ...
cand_names = "abcdef"

manipulations = [
    ("cc", True, 2, [{a, b}] + [{a}] * 3 + [{c}], {b}, [{a, c}], [{a, b}]),
    ("sav", False, 1, [{a, b, c}, {d, e}], {a}, [{d}, {e}], [{a}]),
    (
        "revseqpav",
        False,
        2,
        [{a, b, c}, {b, d}, {c, b}, {a, d, e}, {b, e}],
        {a},
        [{b, d}, {b, e}],
        [{a, b}],
    ),
    (
        "seqphragmen",
        False,
Exemple #11
0
def print_profile():
    print(misc.header("Example 1", "*"))
    print(profile.str_compact())
    print("desired committee size k = " + str(committeesize))
Exemple #12
0
def test_output(capfd, rule_id, algorithm, resolute, verbosity):
    if algorithm == "fastest":
        return
        # not necessary, output for "fastest" is the same as
        # whatever algorithm is selected as fastest
        # (and "fastest" depends on the available solvers)

    if algorithm == "cvxpy_glpk_mi":
        # TODO unfortunately GLPK_MI prints "Long-step dual simplex will be used" to stderr and it
        #  would be very complicated to capture this on all platforms reliably, changing
        #  sys.stderr doesn't help.
        #  This seems to be fixed in GLPK 5.0 but not in GLPK 4.65. For some weird reason this
        #  test succeeds and does not need to be skipped when using conda-forge, although the
        #  version from conda-forge is given as glpk 4.65 he80fd80_1002.
        #  This could help to introduce a workaround: https://github.com/xolox/python-capturer
        #  Sage math is fighting the same problem: https://trac.sagemath.org/ticket/24824
        pytest.skip("GLPK_MI prints something to stderr, not easy to capture")

    output.set_verbosity(verbosity=verbosity)

    try:
        profile = Profile(2)
        profile.add_voters([[0]])
        committeesize = 1

        committees = abcrules.compute(
            rule_id, profile, committeesize, algorithm=algorithm, resolute=resolute
        )
        out = str(capfd.readouterr().out)

        # remove unwanted solver output
        out = remove_solver_output(out)

        if verbosity >= WARNING:
            assert out == ""
        else:
            assert len(out) > 0
            rule = abcrules.get_rule(rule_id)
            start_output = misc.header(rule.longname) + "\n"
            if resolute and rule.resolute_values[0] == False:
                # only if irresolute is default but resolute is chosen
                start_output += "Computing only one winning committee (resolute=True)\n\n"
            if not resolute and rule.resolute_values[0] == True:
                # only if resolute is default but resolute=False is chosen
                start_output += (
                    "Computing all possible winning committees for any tiebreaking order\n"
                    " (aka parallel universes tiebreaking) (resolute=False)\n\n"
                )
            if verbosity <= DETAILS:
                start_output += "Algorithm: " + abcrules.ALGORITHM_NAMES[algorithm] + "\n"
            if verbosity <= DEBUG:
                assert start_output in out
            else:
                assert out.startswith(start_output)
            end_output = (
                f"{misc.str_committees_header(committees, winning=True)}\n"
                f"{misc.str_sets_of_candidates(committees, cand_names=profile.cand_names)}\n"
            )
            if verbosity == INFO:
                assert out.endswith(end_output)
            else:
                assert end_output in out

    finally:
        output.set_verbosity(verbosity=WARNING)
Exemple #13
0
"""Example 1 (approval profile for running example)
from the survey: "Approval-Based Multi-Winner Voting:
Axioms, Algorithms, and Applications"
by Martin Lackner and Piotr Skowron
"""

from __future__ import print_function
import sys
sys.path.insert(0, '..')
from abcvoting.preferences import Profile
from abcvoting import misc

num_cand = 8
a, b, c, d, e, f, g = list(range(7))  # a = 0, b = 1, c = 2, ...
apprsets = [[a, b], [a, b], [a, b], [a, c], [a, c], [a, c], [a, d], [a, d],
            [b, c, f], [e], [f], [g]]
names = "abcdefgh"
committeesize = 4

profile = Profile(num_cand, names=names)
profile.add_preferences(apprsets)

if __name__ == "__main__":
    print(misc.header("Example 1", "*"))
    print(profile.str_compact())
    print("desired committee size k = " + str(committeesize))
Exemple #14
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
Exemple #15
0
"""
Example 2.6 (PAV, seq-PAV, revseq-PAV).

From "Multi-Winner Voting with Approval Preferences"
by Martin Lackner and Piotr Skowron
https://arxiv.org/abs/2007.01795
"""

from abcvoting import abcrules
from abcvoting.preferences import Profile
from abcvoting import misc
from abcvoting.output import output, DETAILS

output.set_verbosity(DETAILS)

print(misc.header("Example 6", "*"))

# Approval profile
num_cand = 4
a, b, c, d = range(4)  # a = 0, b = 1, c = 2, ...
cand_names = "abcd"

profile = Profile(num_cand, cand_names=cand_names)
profile.add_voters([{a, b}, {a, b, c}, {a, b, d}, {a, c, d}, {a, c, d}, {b},
                    {c}, {d}])

print(misc.header("Input:"))
print(profile.str_compact())

committees_av = abcrules.compute_av(profile, 1)
Exemple #16
0
from __future__ import print_function
import sys
sys.path.insert(0, '..')
from abcvoting import abcrules
from abcvoting.preferences import Profile
from abcvoting import misc


print("Remark 2:\n*********\n")

# Approval profile
num_cand = 3
a, b, c = list(range(3))  # a = 0, b = 1, c = 2
apprsets = [[a]] * 99 + [[a, b, c]]
names = "abc"

profile = Profile(num_cand, names=names)
profile.add_preferences(apprsets)

print(misc.header("Input:"))
print(profile.str_compact())

committees_mav = abcrules.compute_mav(profile, 1, verbose=2)

committees_lexmav = abcrules.compute_lexmav(profile, 1, verbose=2)


# verify correctness
assert committees_mav == [[a], [b], [c]]
assert committees_lexmav == [[a]]
Exemple #17
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
Exemple #18
0
"""Example 7 (Greedy Monroe)
from the survey: "Approval-Based Multi-Winner Voting:
Axioms, Algorithms, and Applications"
by Martin Lackner and Piotr Skowron
"""

from __future__ import print_function
import sys
sys.path.insert(0, '..')
from abcvoting import abcrules
from survey import example01 as ex1
from abcvoting.scores import monroescore
from abcvoting import misc

print(misc.header("Example 7", "*"))

print(misc.header("Input (election instance from Example 1):"))
print(ex1.profile.str_compact())

committees = abcrules.compute_greedy_monroe(ex1.profile, 4, verbose=2)

# verify correctness
a, b, c, d, e, f, g = list(range(7))  # a = 0, b = 1, c = 2, ...
assert committees == [[a, c, d, f]]
assert monroescore(ex1.profile, committees[0]) == 10
Exemple #19
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
Exemple #20
0
"""
Example 2.5 (PAV, seq-PAV, revseq-PAV).

From "Multi-Winner Voting with Approval Preferences"
by Martin Lackner and Piotr Skowron
https://arxiv.org/abs/2007.01795
"""

from abcvoting import abcrules
from abcvoting.preferences import Profile, Voter
from abcvoting import misc
from abcvoting.output import output, DETAILS

output.set_verbosity(DETAILS)

print(misc.header("Example 5", "*"))

# Approval profile
num_cand = 4
a, b, c, d = range(4)  # a = 0, b = 1, c = 2, ...
cand_names = "abcd"

approval_sets = [[a, b]] * 3 + [[a, d]] * 6 + [[b]] * 4 + [[c]] * 5 + [[c, d]
                                                                       ] * 5
profile = Profile(num_cand, cand_names=cand_names)
profile.add_voters(approval_sets)

print(misc.header("Input:"))
print(profile.str_compact())

committees_pav = abcrules.compute_pav(profile, 2)
Exemple #21
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