def compute_seq_thiele_methods(profile, committeesize, scorefct_str):
    enough_approved_candidates(profile, committeesize)
    scorefct = sf.get_scorefct(scorefct_str, committeesize)

    comm_scores = {(): 0}

    # build committees starting with the empty set
    for _ in range(0, committeesize):
        comm_scores_next = {}
        for committee, score in comm_scores.items():
            # marginal utility gained by adding candidate to the committee
            additional_score_cand = sf.additional_thiele_scores(
                profile, committee, scorefct)

            for c in range(profile.num_cand):
                if additional_score_cand[c] >= max(additional_score_cand):
                    next_comm = tuple(sorted(committee + (c, )))
                    comm_scores_next[next_comm] = (comm_scores[committee] +
                                                   additional_score_cand[c])
        # remove suboptimal committees
        comm_scores = {}
        cutoff = max(comm_scores_next.values())
        for com, score in comm_scores_next.items():
            if score >= cutoff:
                comm_scores[com] = score
    return sort_committees(list(comm_scores.keys()))
def compute_monroe_bruteforce(profile,
                              committeesize,
                              resolute=False,
                              flowbased=True):
    """Returns the list of winning committees via brute-force Monroe's rule"""
    enough_approved_candidates(profile, committeesize)

    if not profile.has_unit_weights():
        raise Exception("Monroe is only defined for unit weights (weight=1)")

    if profile.totalweight() % committeesize != 0 or flowbased:
        monroescore = sf.monroescore_flowbased
    else:
        monroescore = sf.monroescore_matching

    opt_committees = []
    opt_monroescore = -1
    for comm in combinations(list(range(profile.num_cand)), committeesize):
        score = monroescore(profile, comm)
        if score > opt_monroescore:
            opt_committees = [comm]
            opt_monroescore = score
        elif monroescore(profile, comm) == opt_monroescore:
            opt_committees.append(comm)

    opt_committees = sort_committees(opt_committees)
    if resolute:
        return [opt_committees[0]]
    else:
        return opt_committees
def compute_av(profile, committeesize, resolute=False, sav=False):
    """Returns the list of winning committees according to Approval Voting"""
    enough_approved_candidates(profile, committeesize)

    appr_scores = [0] * profile.num_cand
    for pref in profile.preferences:
        for cand in pref.approved:
            if sav:
                # Satisfaction Approval Voting
                appr_scores[cand] += Fraction(pref.weight, len(pref.approved))
            else:
                # (Classic) Approval Voting
                appr_scores[cand] += pref.weight

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

    certain_cand = [
        c for c in range(profile.num_cand) if appr_scores[c] > cutoff
    ]
    possible_cand = [
        c for c in range(profile.num_cand) if appr_scores[c] == cutoff
    ]
    missing = committeesize - len(certain_cand)
    if resolute:
        return sort_committees([(certain_cand + possible_cand[:missing])])
    else:
        return sort_committees([
            (certain_cand + list(selection))
            for selection in combinations(possible_cand, missing)
        ])
def compute_seqphragmen(profile, committeesize, resolute=False):
    """Returns the list of winning committees
    according to sequential Phragmen"""
    enough_approved_candidates(profile, committeesize)

    load = {v: 0 for v in profile.preferences}
    comm_loads = {(): load}

    approvers_weight = {}
    for c in range(profile.num_cand):
        approvers_weight[c] = sum(v.weight for v in profile.preferences
                                  if c in v.approved)

    # build committees starting with the empty set
    for _ in range(0, committeesize):
        comm_loads_next = {}
        for committee, load in comm_loads.items():
            approvers_load = {}
            for c in range(profile.num_cand):
                approvers_load[c] = sum(v.weight * load[v]
                                        for v in profile.preferences
                                        if c in v.approved)
            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)
            ]
            for c in range(profile.num_cand):
                if c in committee:
                    new_maxload[c] = sys.maxsize
            for c in range(profile.num_cand):
                if new_maxload[c] <= min(new_maxload):
                    new_load = {}
                    for v in profile.preferences:
                        if c in v.approved:
                            new_load[v] = new_maxload[c]
                        else:
                            new_load[v] = load[v]
                    comm_loads_next[tuple(sorted(committee +
                                                 (c, )))] = new_load
        # remove suboptimal committees
        comm_loads = {}
        cutoff = min([max(load.values()) for load in comm_loads_next.values()])
        for com, load in comm_loads_next.items():
            if max(load.values()) <= cutoff:
                comm_loads[com] = load
        if resolute:
            committees = sort_committees(list(comm_loads.keys()))
            comm = tuple(committees[0])
            comm_loads = {comm: comm_loads[comm]}

    committees = sort_committees(list(comm_loads.keys()))
    if resolute:
        return [committees[0]]
    else:
        return committees
def compute_revseq_thiele_methods_resolute(profile, committeesize,
                                           scorefct_str):
    enough_approved_candidates(profile, committeesize)
    scorefct = sf.get_scorefct(scorefct_str, committeesize)

    committee = set(range(profile.num_cand))

    for _ in range(0, profile.num_cand - committeesize):
        cands_to_remove, _ = __least_relevant_cands(profile, committee,
                                                    scorefct)
        committee.remove(cands_to_remove[0])
    return [sorted(list(committee))]
def compute_seq_thiele_resolute(profile, committeesize, scorefct_str):
    enough_approved_candidates(profile, committeesize)
    scorefct = sf.get_scorefct(scorefct_str, committeesize)

    committee = []

    # build committees starting with the empty set
    for _ in range(0, committeesize):
        additional_score_cand = sf.additional_thiele_scores(
            profile, committee, scorefct)
        next_cand = additional_score_cand.index(max(additional_score_cand))
        committee.append(next_cand)
    return [sorted(committee)]
def compute_greedy_monroe(profile, committeesize):
    """"Returns the winning committee of the greedy monroe.
    Always selects the candidate with the highest approval.
    Always removes the first n/k (rounding depends) voters that approve
    with the selected candidate. (voter sorted by their rankings)
    """
    enough_approved_candidates(profile, committeesize)
    if not profile.has_unit_weights():
        raise Exception("Greedy Monroe is only defined for unit weights" +
                        " (weight=1)")
    v = list(enumerate(list(profile.preferences)))
    # list of tuples (nr, Preferences)
    # sorted by sorted approved list of preferences
    voters = sorted(v, key=lambda p: sorted(p[1].approved))

    n = len(voters)  # number of voters
    cands = set(range(profile.num_cand))

    not_s, committee = (voters, set())  # not_s .. not satisfied voters
    for t in range(1, committeesize + 1):
        remaining_cands = cands - committee
        approval = {c: 0 for c in remaining_cands}
        for nr, voter in not_s:
            for c in voter.approved:
                if c in remaining_cands:
                    approval[c] += 1
        max_approval = max(approval.values())
        winner = [c for c in remaining_cands if approval[c] == max_approval][0]

        # round how many are removed, either up or down
        if t <= n - committeesize * math.floor(n / committeesize):
            to_remove = math.ceil(float(n) / committeesize)
        else:
            to_remove = math.floor(n / committeesize)

        # not more than the voters that approve
        # the candidate can be removed
        to_remove = min(max_approval, to_remove)
        next_voters = []
        for nr, voter in not_s:
            if to_remove > 0 and winner in voter.approved:
                to_remove -= 1
            else:
                next_voters.append((nr, voter))
        not_s = next_voters
        committee.add(winner)

    return sort_committees([committee])
def compute_thiele_methods_branchandbound(profile,
                                          committeesize,
                                          scorefct_str,
                                          resolute=False):
    enough_approved_candidates(profile, committeesize)
    scorefct = sf.get_scorefct(scorefct_str, committeesize)

    best_committees = []
    init_com = compute_seq_thiele_resolute(profile, committeesize,
                                           scorefct_str)
    best_score = sf.thiele_score(profile, init_com[0], scorefct_str)
    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 = sf.thiele_score(profile, part_com, scorefct_str)
            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 = sf.additional_thiele_scores(
                profile, part_com, scorefct)
            upper_bound = (
                sum(sorted(marg_util_cand[largest_cand + 1:])[-missing:]) +
                sf.thiele_score(profile, part_com, scorefct_str))
            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:
        return [committees[0]]
    else:
        return committees
def compute_minimaxav(profile, committeesize, ilp=True, resolute=False):
    """Returns the list of winning committees according to Minimax AV"""

    if ilp:
        return compute_minimaxav_ilp(profile, committeesize, resolute)

    def hamming(a, b, elements):
        diffs = 0
        for x in elements:
            if (x in a and x not in b) or (x in b and x not in a):
                diffs += 1
        return diffs

    def mavscore(committee, profile):
        score = 0
        for vote in profile.preferences:
            hamdistance = hamming(vote.approved, committee,
                                  list(range(profile.num_cand)))
            if hamdistance > score:
                score = hamdistance
        return score

    enough_approved_candidates(profile, committeesize)

    opt_committees = []
    opt_mavscore = profile.num_cand + 1
    for comm in combinations(list(range(profile.num_cand)), committeesize):
        score = mavscore(comm, profile)
        if score < opt_mavscore:
            opt_committees = [comm]
            opt_mavscore = score
        elif mavscore(comm, profile) == opt_mavscore:
            opt_committees.append(comm)

    opt_committees = sort_committees(opt_committees)
    if resolute:
        return [opt_committees[0]]
    else:
        return sort_committees(opt_committees)
Example #10
0
def compute_revseq_thiele_methods(profile, committeesize, scorefct_str):
    enough_approved_candidates(profile, committeesize)
    scorefct = sf.get_scorefct(scorefct_str, committeesize)

    allcandcomm = tuple(range(profile.num_cand))
    comm_scores = {
        allcandcomm: sf.thiele_score(profile, allcandcomm, scorefct_str)
    }

    for _ in range(0, profile.num_cand - committeesize):
        comm_scores_next = {}
        for committee, score in comm_scores.items():
            cands_to_remove, score_reduction = \
                __least_relevant_cands(profile, committee, scorefct)
            for c in cands_to_remove:
                next_comm = tuple(set(committee) - set([c]))
                comm_scores_next[next_comm] = score - score_reduction
        # remove suboptimal committees
        comm_scores = {}
        cutoff = max(comm_scores_next.values())
        for com, score in comm_scores_next.items():
            if score >= cutoff:
                comm_scores[com] = score
    return sort_committees(list(comm_scores.keys()))
def compute_monroe_ilp(profile, committeesize, resolute):
    enough_approved_candidates(profile, committeesize)

    # Monroe is only defined for unit weights
    if not profile.has_unit_weights():
        raise Exception("Monroe is only defined for unit weights (weight=1)")

    num_voters = len(profile.preferences)
    cands = list(range(profile.num_cand))

    # Alternative: split voters -> generate new profile with all weights = 1
    # prof2 = Profile(profile.num_cand)
    # for v in profile:
    #    for _ in range(v.weight):
    #        prof2.add_preference(DichotomousPreference(v.approved,
    #                                                   profile.num_cand))
    # total_weight = profile.voters_num()

    m = gb.Model()

    # optimization goal: variable "satisfaction"
    satisfaction = m.addVar(vtype=gb.GRB.INTEGER, name="satisfaction")

    # a list of committee members
    in_committee = m.addVars(profile.num_cand,
                             vtype=gb.GRB.BINARY,
                             name="in_comm")
    m.addConstr(gb.quicksum(in_committee[c] for c in cands) == committeesize)

    # a partition of voters into committeesize many sets
    partition = m.addVars(profile.num_cand,
                          len(profile.preferences),
                          vtype=gb.GRB.INTEGER,
                          lb=0,
                          name="partition")
    for i in range(len(profile.preferences)):
        # every voter has to be part of a voter partition set
        m.addConstr(
            gb.quicksum(partition[(j, i)]
                        for j in cands) == profile.preferences[i].weight)
    for i in cands:
        # every voter set in the partition has to contain
        # at least (num_voters // committeesize) candidates
        m.addConstr(
            gb.quicksum(partition[(i, j)]
                        for j in range(len(profile.preferences))) >=
            (num_voters // committeesize - num_voters * (1 - in_committee[i])))
        # every voter set in the partition has to contain
        # at most ceil(num_voters/committeesize) candidates
        m.addConstr(
            gb.quicksum(partition[(i, j)]
                        for j in range(len(profile.preferences))) <=
            (num_voters // committeesize + bool(num_voters % committeesize) +
             num_voters * (1 - in_committee[i])))
        # if in_committee[i] = 0 then partition[(i,j) = 0
        m.addConstr(
            gb.quicksum(
                partition[(i, j)]
                for j in range(len(profile.preferences))) <= num_voters *
            in_committee[i])

    m.update()

    # constraint for objective variable "satisfaction"
    m.addConstr(
        gb.quicksum(partition[(i, j)] * (i in profile.preferences[j].approved)
                    for j in range(len(profile.preferences))
                    for i in cands) >= satisfaction)

    # optimization objective
    m.setObjective(satisfaction, gb.GRB.MAXIMIZE)

    # m.setParam('OutputFlag', False)

    m.setParam('OutputFlag', False)

    if resolute:
        m.setParam('PoolSearchMode', 0)
    else:
        # output all optimal committees
        m.setParam('PoolSearchMode', 2)
        # abort after (roughly) 100 optimal solutions
        m.setParam('PoolSolutions', 100)
        # ignore suboptimal committees
        m.setParam('PoolGap', 0)

    m.optimize()

    if m.Status != 2:
        print "Warning (Monroe): solutions may be incomplete or not optimal."
        print "(Gurobi return code", m.Status, ")"

    # extract committees from model
    committees = []
    if resolute:
        committees.append([c for c in cands if in_committee[c].Xn >= 0.99])
    else:
        for sol in range(m.SolCount):
            m.setParam('SolutionNumber', sol)
            committees.append([c for c in cands if in_committee[c].Xn >= 0.99])

    # if len(committees)>10:
    #    print "Warning (Monroe): more than 10 committees found;",
    #    print "returning first 10"
    #    committees = committees[:10]

    committees = sort_committees(committees)

    return committees
def compute_thiele_methods_ilp(profile,
                               committeesize,
                               scorefct_str,
                               resolute=False):

    enough_approved_candidates(profile, committeesize)
    scorefct = sf.get_scorefct(scorefct_str, committeesize)

    m = gb.Model()
    cands = list(range(profile.num_cand))

    # a binary variable indicating whether c is in the committee
    in_committee = m.addVars(profile.num_cand,
                             vtype=gb.GRB.BINARY,
                             name="in_comm")

    # a (intended binary) variable indicating
    # whether v approves at least l candidates in the committee
    utility = {}
    for v in profile.preferences:
        for l in range(1, committeesize + 1):
            utility[(v, l)] = m.addVar(ub=1.0)

    # constraint: the committee has the required size
    m.addConstr(gb.quicksum(in_committee[c] for c in cands) == committeesize)

    # constraint: utilities are consistent with actual committee
    for v in profile.preferences:
        m.addConstr(
            gb.quicksum(utility[v, l]
                        for l in range(1, committeesize + 1)) == gb.quicksum(
                            in_committee[c] for c in v.approved))

    # objective: the PAV score of the committee
    m.setObjective(
        gb.quicksum(
            float(scorefct(l)) * v.weight * utility[(v, l)]
            for v in profile.preferences
            for l in range(1, committeesize + 1)), gb.GRB.MAXIMIZE)

    m.setParam('OutputFlag', False)

    if resolute:
        m.setParam('PoolSearchMode', 0)
    else:
        # output all optimal committees
        m.setParam('PoolSearchMode', 2)
        # abort after (roughly) 100 optimal solutions
        m.setParam('PoolSolutions', 100)
        # ignore suboptimal committees
        m.setParam('PoolGap', 0)

    m.optimize()

    if m.Status != 2:
        print "Warning (" + scorefct_str + "):",
        print "solutions may be incomplete or not optimal."
        print "(Gurobi return code", m.Status, ")"

    # extract committees from model
    committees = []
    if resolute:
        committees.append([c for c in cands if in_committee[c].Xn >= 0.99])
    else:
        for sol in range(m.SolCount):
            m.setParam('SolutionNumber', sol)
            committees.append([c for c in cands if in_committee[c].Xn >= 0.99])

    committees = sort_committees(committees)

    return committees
Example #13
0
def compute_minimaxav_ilp(profile, committeesize, resolute=False):
    enough_approved_candidates(profile, committeesize)

    voters = profile.preferences
    num_voters = len(voters)
    cands = list(range(profile.num_cand))

    m = gb.Model()

    # optimization goal: variable "sum_difference"
    max_hamdistance = m.addVar(vtype=gb.GRB.INTEGER, name="max_hamdistance")

    # a list of committee members
    in_committee = m.addVars(profile.num_cand,
                             vtype=gb.GRB.BINARY,
                             name="in_comm")
    m.addConstr(gb.quicksum(in_committee[c] for c in cands) == committeesize)

    # the single differences between the committee and the voters
    difference = m.addVars(profile.num_cand,
                           num_voters,
                           vtype=gb.GRB.INTEGER,
                           name="diff")

    for i in cands:
        for j in range(num_voters):
            if i in voters[j].approved:
                # constraint for the case that the candidate is approved
                m.addConstr(difference[i, j] == 1 - in_committee[i])
            else:
                # constraint for the case that the candidate isn't approved
                m.addConstr(difference[i, j] == in_committee[i])

    for j in range(num_voters):
        # maximum hamming distance is greater of equal than any individual one
        m.addConstr(max_hamdistance >= gb.quicksum(difference[i, j]
                                                   for i in cands))

    # optimization objective
    m.setObjective(max_hamdistance, gb.GRB.MINIMIZE)

    # m.setParam('OutputFlag', False)

    m.setParam('OutputFlag', False)

    if resolute:
        m.setParam('PoolSearchMode', 0)
    else:
        # output all optimal committees
        m.setParam('PoolSearchMode', 2)
        # abort after (roughly) 100 optimal solutions
        m.setParam('PoolSolutions', 1000)
        # ignore suboptimal committees
        m.setParam('PoolGap', 0)

    m.optimize()

    if m.Status != 2:
        print(
            "Warning (Minimax AV): solutions may be incomplete or not optimal."
        )
        print("(Gurobi return code", m.Status, ")")

    # extract committees from model
    committees = []
    if resolute:
        committees.append([c for c in cands if in_committee[c].Xn >= 0.99])
    else:
        for sol in range(m.SolCount):
            m.setParam('SolutionNumber', sol)
            committees.append([c for c in cands if in_committee[c].Xn >= 0.99])

    committees = sort_committees(committees)

    return committees
Example #14
0
def compute_optphragmen_ilp(profile, committeesize, resolute=False):

    enough_approved_candidates(profile, committeesize)

    cands = list(range(profile.num_cand))

    m = gb.Model()
    m.setParam('OutputFlag', False)

    # a binary variable indicating whether c is in the committee
    in_committee = m.addVars(profile.num_cand,
                             vtype=gb.GRB.BINARY,
                             name="in_comm")

    load = {}
    for c in cands:
        for v in profile.preferences:
            load[(v, c)] = m.addVar(ub=1.0, lb=0.0)

    # constraint: the committee has the required size
    m.addConstr(gb.quicksum(in_committee[c] for c in cands) == committeesize)

    for c in cands:
        for v in profile.preferences:
            if c not in v.approved:
                m.addConstr(load[(v, c)] == 0)

    # a candidate's load is distributed among his approvers
    for c in cands:
        m.addConstr(
            gb.quicksum(v.weight * load[(v, c)] for v in profile.preferences
                        if c in cands) == in_committee[c])

    loadbound = m.addVar(name="loadbound")
    for v in profile.preferences:
        m.addConstr(gb.quicksum(load[(v, c)] for c in v.approved) <= loadbound)
    m.setObjective(loadbound, gb.GRB.MINIMIZE)

    m.setParam('OutputFlag', False)

    if resolute:
        m.setParam('PoolSearchMode', 0)
    else:
        # output all optimal committees
        m.setParam('PoolSearchMode', 2)
        # abort after (roughly) 100 optimal solutions
        m.setParam('PoolSolutions', 100)
        # ignore suboptimal committees
        m.setParam('PoolGap', 0)

    m.optimize()

    if m.Status != 2:
        print("Warning (opt-Phragmen): solutions may be " +
              "incomplete or not optimal.")
        print("(Gurobi return code", m.Status, ")")

    # extract committees from model
    committees = []
    if resolute:
        committees.append([c for c in cands if in_committee[c].Xn >= 0.99])
    else:
        for sol in range(m.SolCount):
            m.setParam('SolutionNumber', sol)
            committees.append([c for c in cands if in_committee[c].Xn >= 0.99])

    committees = sort_committees(committees)

    return committees
Example #15
0
def compute_phragmen_enestroem(profile, committeesize, resolute=False):
    """"Returns the winning committees with
    Phragmen's first method (Enestroem's method) –
    STV with unordered ballots
    In every step the candidate with the highest combined budget of
    their supporters gets into a committee.
    For equal voting power multiple committees are computed.
    Method from:
    https://arxiv.org/pdf/1611.08826.pdf (18.5, Page 59)
    """
    enough_approved_candidates(profile, committeesize)
    num_voters = len(profile.preferences)

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

    cands = range(profile.num_cand)

    committees = [(start_budget, set())]
    for i 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.preferences):
                voting_power = budget[nr]
                if voting_power <= 0:
                    continue
                for cand in pref.approved:
                    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)  # new 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.preferences):
                    if cand in pref.approved:
                        b[nr] *= multiplier
                c = comm.union([cand])  # new committee with candidate
                next_committees.append((b, c))

        if resolute:  # only one is requested
            if len(next_committees) > 0:
                committees = [next_committees[0]]
            else:  # should not happen
                committees = []
                raise Exception(
                    "phragmen enestroem failed to find " +
                    "next candidate for", committees)
        else:
            committees = next_committees
    committees = [comm for b, comm in committees]
    committees = sort_committees(committees)
    if resolute:
        if len(committees) > 0:
            return [committees[0]]
        else:
            return []
    else:
        return committees
Example #16
0
def compute_rule_x(profile, committeesize, resolute=False):
    """Returns the list of winning candidates according to rule x.
    But rule x does stop if not enough budget is there to finance a
    candidate. As this is not optimal the committee is filled with the
    candidates that have the most remaining budget as support.
    Rule from:
    https://arxiv.org/pdf/1911.11747.pdf (Page 7)"""
    enough_approved_candidates(profile, committeesize)
    if not profile.has_unit_weights():
        raise Exception("Rule X is only defined \
                            for unit weights (weight=1)")
    num_voters = len(profile.preferences)
    price = Fraction(num_voters, committeesize)

    start_budget = {v: Fraction(1, 1) for v in range(num_voters)}
    cands = range(profile.num_cand)
    committees = [(start_budget, set())]
    final_committees = []

    for _ in range(committeesize):
        next_committees = []
        for committee in committees:
            budget = committee[0]
            q_affordability = {}
            curr_cands = set(cands) - committee[1]
            for c in curr_cands:
                approved_by = set()
                for v, vote in enumerate(profile.preferences):
                    if c in vote.approved and budget[v] > 0.0:
                        approved_by.add(v)
                too_poor = set()
                already_available = Fraction(0)
                rich = set(approved_by)
                q = 0.0
                while already_available < price and q == 0.0 and len(rich) > 0:
                    fair_split = Fraction(price - already_available, len(rich))
                    still_rich = set()
                    for v in rich:
                        if budget[v] <= fair_split:
                            too_poor.add(v)
                            already_available += budget[v]
                        else:
                            still_rich.add(v)
                    if len(still_rich) == len(rich):
                        q = fair_split
                        q_affordability[c] = q
                    elif already_available == price:
                        q = fair_split
                        q_affordability[c] = q
                    else:
                        rich = still_rich

            if len(q_affordability) > 0:
                min_q = min(q_affordability.values())
                cheapest_split = [
                    c for c in q_affordability if q_affordability[c] == min_q
                ]

                for c in cheapest_split:
                    b = dict(committee[0])
                    for v, vote in enumerate(profile.preferences):
                        if c in vote.approved:
                            b[v] -= min(budget[v], min_q)
                    comm = set(committee[1])
                    comm.add(c)
                    next_committees.append((b, comm))

            else:  # no affordable candidate remains
                comms = fill_remaining_committee(committee, curr_cands,
                                                 committeesize, profile)
                # after filling the remaining spots these committees
                # have size committeesize
                for b, comm in comms:
                    final_committees.append(comm)
        if resolute:
            if len(next_committees) > 0:
                committees = [next_committees[0]]
            else:
                committees = []
        else:
            committees = next_committees

    # The committees that could be fully filled with Rule X:
    for b, comm in committees:  # budget and committee
        final_committees.append(comm)

    committees = sort_committees(final_committees)
    if resolute:
        if len(committees) > 0:
            return [committees[0]]
        else:
            return []
    else:
        return committees