def __revseq_thiele_irresolute(profile, committeesize, scorefct_str): """Compute an *irresolute* sequential Thiele method Consider all possible ways to break ties between candidates (aka parallel universe tiebreaking) """ scorefct = scores.get_scorefct(scorefct_str, committeesize) allcandcomm = tuple(range(profile.num_cand)) comm_scores = {allcandcomm: scores.thiele_score( scorefct_str, profile, allcandcomm)} for _ in range(profile.num_cand - committeesize): comm_scores_next = {} for committee, score in comm_scores.items(): marg_util_cand = scores.marginal_thiele_scores_remove( scorefct, profile, committee) score_reduction = min(marg_util_cand) # find smallest elements in marg_util_cand and return indices cands_to_remove = [cand for cand in range(profile.num_cand) if marg_util_cand[cand] == min(marg_util_cand)] for c in cands_to_remove: next_comm = tuple(set(committee) - set([c])) comm_scores_next[next_comm] = score - score_reduction comm_scores = comm_scores_next return sort_committees(list(comm_scores.keys()))
def test_gurobi_cant_compute_av(): profile = Profile(4) profile.add_voters([[0, 1], [1, 2]]) committeesize = 2 scorefct = scores.get_scorefct("av", committeesize) with pytest.raises(ValueError): _gurobi_thiele_methods(profile, committeesize, scorefct, resolute=False)
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
def __revseq_thiele_resolute(profile, committeesize, scorefct_str, verbose): """Compute a *resolute* reverse sequential Thiele method Tiebreaking between candidates in favor of candidate with smaller number/index (candidates with smaller numbers are added first). """ scorefct = scores.get_scorefct(scorefct_str, committeesize) committee = set(range(profile.num_cand)) # optional output if verbose >= 2: output = "full committee (" + str(len(committee)) output += " candidates) has a total score of " output += str(scores.thiele_score( scorefct_str, profile, committee)) print(output + "\n") # end of optional output for _ in range(profile.num_cand - committeesize): marg_util_cand = scores.marginal_thiele_scores_remove( scorefct, profile, committee) score_reduction = min(marg_util_cand) # find smallest elements in marg_util_cand and return indices cands_to_remove = [cand for cand in range(profile.num_cand) if marg_util_cand[cand] == min(marg_util_cand)] committee.remove(cands_to_remove[-1]) # optional output if verbose >= 2: rem_cand = cands_to_remove[-1] output = "removing candidate number " output += str(profile.num_cand - len(committee)) + ": " output += profile.names[rem_cand] + "\n" output += " score decreases by " output += str(score_reduction) output += " to a total of " output += str(scores.thiele_score( scorefct_str, profile, committee)) if len(cands_to_remove) > 1: output += " (tie between candidates " output += str_candset(cands_to_remove) + ")\n" print(output + "\n") # end of optional output return [committee]
def __seq_thiele_resolute(profile, committeesize, scorefct_str, verbose): """Compute a *resolute* reverse sequential Thiele method Tiebreaking between candidates in favor of candidate with smaller number/index (candidates with larger numbers get deleted first). """ committee = [] scorefct = scores.get_scorefct(scorefct_str, committeesize) # optional output if verbose >= 2: output = "starting with the empty committee (score = " output += str(scores.thiele_score( scorefct_str, profile, committee)) + ")" print(output + "\n") # end of optional output # build a committee starting with the empty set for _ in range(committeesize): additional_score_cand = scores.marginal_thiele_scores_add( scorefct, profile, committee) next_cand = additional_score_cand.index(max(additional_score_cand)) committee.append(next_cand) # optional output if verbose >= 2: output = "adding candidate number " output += str(len(committee)) + ": " output += profile.names[next_cand] + "\n" output += " score increases by " output += str(max(additional_score_cand)) output += " to a total of " output += str(scores.thiele_score( scorefct_str, profile, committee)) tied_cands = [c for c in range(len(additional_score_cand)) if (c > next_cand and (additional_score_cand[c] == max(additional_score_cand)))] if len(tied_cands) > 0: output += " tie broken in favor of " + str(next_cand) output += " candidates " + str_candset(tied_cands) output += " would increase the score by the same amount (" output += str(max(additional_score_cand)) + ")" print(output + "\n") # end of optional output return [committee]
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
def __seq_thiele_irresolute(profile, committeesize, scorefct_str): """Compute an *irresolute* reverse sequential Thiele method Consider all possible ways to break ties between candidates (aka parallel universe tiebreaking) """ scorefct = scores.get_scorefct(scorefct_str, committeesize) comm_scores = {(): 0} # build committees starting with the empty set for _ in range(committeesize): comm_scores_next = {} for committee, score in comm_scores.items(): # marginal utility gained by adding candidate to the committee additional_score_cand = scores.marginal_thiele_scores_add( scorefct, profile, committee) 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] = ( score + additional_score_cand[c]) comm_scores = comm_scores_next return sort_committees(list(comm_scores.keys()))