Ejemplo n.º 1
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.º 2
0
def mavscore(profile, committee):
    score = 0
    for pref in profile:
        hamdistance = hamming(pref, committee)
        if hamdistance > score:
            score = hamdistance
    return score
Ejemplo n.º 3
0
def num_voters_with_upper_bounded_hamming_distance(upperbound, profile,
                                                   committee):
    """
    Return the number of voters having a Hamming distance <= `upperbound` to the given committee.

    Parameters
    ----------
        upperbound : int
            The Hamming distance upper bound.

        profile : abcvoting.preferences.Profile
            A profile.

        committee : set
            A committee.

    Returns
    -------
        int
            The number of voters having a Hamming distance <= `upperbound`.
    """
    return len([
        voter for voter in profile
        if hamming(voter.approved, committee) <= upperbound
    ])
Ejemplo n.º 4
0
def mavscore(profile, committee):
    """Return the Minimax AV (MAV) score of a committee."""
    score = 0
    for voter in profile:
        hamdistance = hamming(voter.approved, committee)
        if hamdistance > score:
            score = hamdistance
    return score
Ejemplo n.º 5
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.º 6
0
def minimaxav_score(profile, committee):
    """
    Return the Minimax AV (MAV) score of a committee.

    Parameters
    ----------
        profile : abcvoting.preferences.Profile
            A profile.

        committee : iterable of int
            A committee.

    Returns
    -------
        int
            The Minimax AV score of `committee`.
    """
    score = 0
    for voter in profile:
        hamdistance = hamming(voter.approved, committee)
        if hamdistance > score:
            score = hamdistance
    return score
Ejemplo n.º 7
0
def test_hamming(a, b, dist):
    assert misc.hamming(a, b) == dist
Ejemplo n.º 8
0
def _gurobi_lexminimaxav(profile, committeesize, resolute,
                         max_num_of_committees):
    def set_opt_model_func(model, in_committee):
        voteratmostdistances = {}

        for i, voter in enumerate(profile):
            for dist in range(profile.num_cand + 1):
                voteratmostdistances[(i, dist)] = model.addVar(
                    vtype=gb.GRB.BINARY, name=f"atmostdistance({i, dist})")
                if dist >= len(voter.approved) + committeesize:
                    # distances are always <= len(voter.approved) + committeesize
                    voteratmostdistances[(i, dist)] = 1
                if dist < abs(len(voter.approved) - committeesize):
                    # distances are never < abs(len(voter.approved) - committeesize)
                    voteratmostdistances[(i, dist)] = 0

        # constraint: the committee has the required size
        model.addConstr(gb.quicksum(in_committee) == committeesize)

        # constraint: distances are consistent with actual committee
        for i, voter in enumerate(profile):
            not_approved = [
                cand for cand in profile.candidates
                if cand not in voter.approved
            ]
            for dist in range(profile.num_cand + 1):
                if isinstance(voteratmostdistances[(i, dist)], int):
                    # trivially satisfied
                    continue
                model.addConstr((voteratmostdistances[(i, dist)] == 1) >> (
                    gb.quicksum(1 - in_committee[cand]
                                for cand in voter.approved) +
                    gb.quicksum(in_committee[cand]
                                for cand in not_approved) <= dist))

        # additional constraints from previous iterations
        for dist, num_voters_achieving_distance in hammingdistance_constraints.items(
        ):
            model.addConstr(
                gb.quicksum(voteratmostdistances[(i, dist)]
                            for i, _ in enumerate(profile)) >=
                num_voters_achieving_distance - ACCURACY)

        new_distance = min(hammingdistance_constraints.keys()) - 1
        # objective: maximize number of voters achieving at most distance `new_distance`
        model.setObjective(
            gb.quicksum(voteratmostdistances[(i, new_distance)]
                        for i, _ in enumerate(profile)),
            gb.GRB.MAXIMIZE,
        )

    # compute minimaxav as baseline and then improve on it
    committees = _gurobi_minimaxav(profile,
                                   committeesize,
                                   resolute=True,
                                   max_num_of_committees=None)
    maxdistance = scores.minimaxav_score(profile, committees[0])
    # all voters have at most this distance
    hammingdistance_constraints = {maxdistance: len(profile)}
    for distance in range(maxdistance - 1, -1, -1):
        # in iteration `distance` we maximize the number of voters that have at
        # most a Hamming distance of `distance` to the committee
        if distance == 0:
            # last iteration
            _resolute = resolute
            _max_num_of_committees = max_num_of_committees
        else:
            _resolute = True
            _max_num_of_committees = None
        committees, _ = _optimize_rule_gurobi(
            set_opt_model_func=set_opt_model_func,
            profile=profile,
            committeesize=committeesize,
            resolute=_resolute,
            max_num_of_committees=_max_num_of_committees,
            name=f"lexminimaxav-atmostdistance{distance}",
            committeescorefct=functools.partial(
                scores.num_voters_with_upper_bounded_hamming_distance,
                distance),
        )
        num_voters_achieving_distance = scores.num_voters_with_upper_bounded_hamming_distance(
            distance, profile, committees[0])
        hammingdistance_constraints[distance] = num_voters_achieving_distance
    committees = sorted_committees(committees)
    detailed_info = {
        "hammingdistance_constraints":
        hammingdistance_constraints,
        "opt_distances":
        [misc.hamming(voter.approved, committees[0]) for voter in profile],
    }
    return committees, detailed_info