def test_abcrules_weightsconsidered(rule_id, algorithm, resolute): profile = Profile(3) profile.add_voter(Voter([0])) profile.add_voter(Voter([0])) profile.add_voter(Voter([1], 5)) profile.add_voter(Voter([0])) committeesize = 1 if "monroe" in rule_id or rule_id in [ "lexminimaxav", "rule-x", "rule-x-without-phragmen-phase", "phragmen-enestroem", ]: with pytest.raises(ValueError): abcrules.compute(rule_id, profile, committeesize, algorithm=algorithm) return result = abcrules.compute( rule_id, profile, committeesize, algorithm=algorithm, resolute=resolute ) if rule_id == "minimaxav": # Minimax AV ignores weights by definition if resolute: assert result == [{0}] or result == [{1}] or result == [{2}] else: assert result == [{0}, {1}, {2}] else: assert len(result) == 1 assert result[0] == {1}
def test_party_list(num_cand, additional_approval_set): profile = Profile(num_cand) profile.add_voter(Voter([1, 3, 5], 3)) profile.add_voter([0, 4, 6]) profile.add_voter([0, 4, 6]) profile.add_voter([2, 7]) profile.add_voter(Voter([1, 3, 5], 3)) assert profile.is_party_list() profile.add_voter(additional_approval_set) assert not profile.is_party_list()
def test_invalid_approvalsets(): with pytest.raises(ValueError): Voter([-1]) with pytest.raises(ValueError): Voter([1]).check_valid(1) with pytest.raises(TypeError): Voter([0.42]) with pytest.raises(ValueError): Voter([1, 1, 2, 42])
def test_unitweights(num_cand): profile = Profile(num_cand) profile.add_voters([]) profile.add_voter(Voter([0, 4, 5])) profile.add_voter([0, 4, 5]) p1 = Voter([0, 4, 5]) p2 = Voter([1, 2]) profile.add_voters([p1, p2]) assert profile.has_unit_weights() profile.add_voter(Voter([0, 4, 5], 2.4)) assert not profile.has_unit_weights() assert profile.totalweight() == 6.4
def test_iterate(num_cand): profile = Profile(num_cand) profile.add_voter(Voter([1, 3, 5], 3)) profile.add_voter([0, 4, 5]) assert len(profile) == 2 for p in profile: assert isinstance(p, Voter)
def test_read_special_abc_yaml_file1(): currdir = os.path.dirname(os.path.abspath(__file__)) filename = currdir + "/data/test7.abc.yaml" profile1 = Profile(6) profile1.add_voter(Voter([3], weight=1.3)) profile1.add_voters([[4, 1, 5], [0, 2], [], [0, 1, 2, 3, 4, 5], [5]]) profile1.add_voter(Voter([1], weight=2)) fileio.write_abcvoting_instance_to_yaml_file(filename, profile1, description="just a profile") profile2, committeesize, compute_instances2, data2 = fileio.read_abcvoting_yaml_file( filename) assert str(profile1) == str(profile2) assert committeesize is None assert compute_instances2 == []
def convert_election_to_profile(election): profile = Profile(num_cand=election.num_candidates) voters = [] for i, vote in enumerate(election.votes): voter = Voter(vote) voters.append(voter) profile._voters = voters return profile
def test_approved_candidates(): profile = Profile(10) profile.add_voter(Voter([1, 3, 5], 3)) profile.add_voter([0]) assert profile.approved_candidates() == {0, 1, 3, 5} profile.add_voter([4]) assert profile.approved_candidates() == {0, 1, 3, 4, 5} profile.add_voters([[7], [1, 8]]) assert profile.approved_candidates() == {0, 1, 3, 4, 5, 7, 8} profile[0].approved = [1, 5] assert profile.approved_candidates() == {0, 1, 4, 5, 7, 8}
def test_invalid_approvalsets(): with pytest.raises(ValueError): Voter([-1]) with pytest.raises(ValueError): Voter([1], num_cand=1) with pytest.raises(TypeError): Voter([0.42]) with pytest.raises(ValueError): Voter([1, 1, 2, 42]) with pytest.raises(ValueError): CandidateSet([-1]) with pytest.raises(ValueError): CandidateSet([1], num_cand=1) with pytest.raises(TypeError): CandidateSet([0.42]) with pytest.raises(ValueError): CandidateSet([1, 1, 2, 42])
def test_invalidprofiles(num_cand): with pytest.raises(ValueError): Profile(0) with pytest.raises(ValueError): Profile(-8) with pytest.raises(ValueError): Profile(4, ["a", "b", "c"]) Profile(4, ["a", 3, "b", "c"]) profile = Profile(num_cand, "abcdefgh") voter = Voter([num_cand]) with pytest.raises(ValueError): profile.add_voter(voter) with pytest.raises(TypeError): profile.add_voter([0, 4, 5, "1"]) with pytest.raises(TypeError): profile.add_voter(["1", 0, 4, 5]) with pytest.raises(TypeError): # note: this raises a TypeError because a list of lists can't be converted to a set, # but that's fine too profile.add_voter([[0, 4, 5]])
# verify correctness assert committees_pav == [{a, c}] assert committees_seqpav == [{c, d}] assert committees_revseqpav == [{c, d}] print("\n") print(misc.header("Example from Janson's survey (Example 13.3) / Thiele:", "*")) # 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_voter(Voter([a, c, d], 960)) profile.add_voter(Voter([b, c, d], 3000)) profile.add_voter(Voter([b, c], 520)) profile.add_voter(Voter([a, b], 1620)) profile.add_voter(Voter([a, d], 1081)) profile.add_voter(Voter([a, c], 1240)) profile.add_voter(Voter([b, d], 360)) profile.add_voter(Voter([d], 360)) profile.add_voter(Voter([c], 120)) profile.add_voter(Voter([b], 60)) print(misc.header("Input:")) print(profile.str_compact()) committees_pav = abcrules.compute_pav(profile, 2)
def read_preflib_file(filename, setsize=1, relative_setsize=None, use_weights=False): """Reads a single preflib file (soi, toi, soc or toc). Parameters: filename: str Name of the preflib file. setsize: int Number of top-ranked candidates that voters approve. In case of ties, more than `setsize` candidates are approved. Paramer `setsize` is ignored if `relative_setsize` is used. relative_setsize: float in (0, 1] Indicates which proportion of candidates of the ranking are approved (rounded up). In case of ties, more candidates are approved. E.g., if a voter has 10 approved candidates and `relative_setsize` is 0.75, then the approval set contains the top 8 candidates. use_weights: bool If False, treat vote count in preflib file as the number of duplicate ballots, i.e., the number of voters that have this approval set. If True, treat vote count as weight and use this weight in class Voter. Returns: profile: abcvoting.preferences.Profile Preference profile extracted from preflib file, including names of candidates """ if setsize <= 0: raise ValueError("Parameter setsize must be > 0") if relative_setsize and (relative_setsize <= 0.0 or relative_setsize > 1.0): raise ValueError("Parameter relative_setsize not in interval (0, 1]") with open(filename, "r") as f: line = f.readline() num_cand = int(line.strip()) candidate_map = {} for _ in range(num_cand): parts = f.readline().strip().split(",") candidate_map[int(parts[0].strip())] = ",".join(parts[1:]).strip() parts = f.readline().split(",") try: voter_count, _, unique_orders = [int(p.strip()) for p in parts] except ValueError: raise PreflibException( f"Number of voters ill specified ({str(parts)}), should be triple of integers" ) approval_sets = [] lines = [line.strip() for line in f.readlines() if line.strip()] if len(lines) != unique_orders: raise PreflibException( f"Expected {unique_orders} lines that specify voters in the input, " f"encountered {len(lines)}" ) for line in lines: parts = line.split(",") if len(parts) < 1: continue try: count = int(parts[0]) except ValueError: raise PreflibException(f"Each ranking must start with count/weight ({line})") ranking = parts[1:] # ranking starts after count if len(ranking) == 0: raise PreflibException("Empty ranking: " + str(line)) if relative_setsize: num_appr = int(ceil(len(ranking) * relative_setsize)) else: num_appr = setsize approval_set = _approval_set_from_preflib_datastructures(num_appr, ranking, candidate_map) approval_sets.append((count, approval_set)) # normalize candidates to 0, 1, 2, ... cand_names = [] normalize_map = {} for cand in candidate_map.keys(): cand_names.append(candidate_map[cand]) normalize_map[cand] = len(cand_names) - 1 profile = Profile(num_cand, cand_names=cand_names) for count, approval_set in approval_sets: normalized_approval_set = [] for cand in approval_set: normalized_approval_set.append(normalize_map[cand]) if use_weights: profile.add_voter(Voter(normalized_approval_set, weight=count)) else: profile.add_voters([normalized_approval_set] * count) if use_weights: if len(profile) != unique_orders: raise PreflibException("Number of voters wrongly specified in preflib file.") else: if len(profile) != voter_count: raise PreflibException("Number of voters wrongly specified in preflib file.") return profile
def read_abcvoting_yaml_file(filename): """ Read contents of an abcvoting yaml file (ending with .abc.yaml). Parameters ---------- filename : str File name of the .abc.yaml file. Returns ------- profile : abcvoting.preferences.Profile A profile. committeesize : int or None The desired committee size. compute_instances : list of dict A list of compute instances, which are dictionaries. Compute instances can be passed to `Rule.compute`. data : dict The YAML data from `filename`. """ yaml = ruamel.yaml.YAML(typ="safe", pure=True) with open(filename) as inputfile: data = yaml.load(inputfile) if "profile" not in data.keys(): raise MalformattedFileException( f"{filename} does not contain a profile.") if "num_cand" in data.keys(): num_cand = int(data["num_cand"]) else: num_cand = max(cand for approval_set in data["profile"] for cand in approval_set) + 1 profile = Profile(num_cand) approval_sets = data["profile"] if "voter_weights" in data.keys(): weights = data["voter_weights"] if len(weights) != len(approval_sets): raise MalformattedFileException( f"{filename}: the number of voters differs from the number of voter weights." ) for appr_set, weight in zip(approval_sets, weights): profile.add_voter(Voter(appr_set, weight=weight)) else: profile.add_voters(approval_sets) if "committeesize" in data.keys(): committeesize = int(data["committeesize"]) else: committeesize = None if "compute" in data.keys(): compute_instances = data["compute"] else: compute_instances = [] for compute_instance in compute_instances: if "rule_id" not in compute_instance.keys(): raise MalformattedFileException( 'Each rule instance (dict) requires key "rule_id".') compute_instance["profile"] = profile compute_instance["committeesize"] = committeesize if "result" in compute_instance.keys(): if compute_instance["result"] is not None: # compute_instance["result"] should be a list of CandidateSet compute_instance["result"] = [ misc.CandidateSet(committee) for committee in compute_instance["result"] ] for key in data.keys(): if key not in ABC_YAML_VALID_KEYS: raise MalformattedFileException( f'Key "{key}" is not valid (undefined).') return profile, committeesize, compute_instances, data
def read_preflib_file(filename, setsize=1, relative_setsize=None, use_weights=False): """ Read a Preflib file (soi, toi, soc or toc). Parameters ---------- filename : str Name of the Preflib file. setsize : int Minimum number of candidates that voters approve. These candidates are taken from the top of ranking. In case of ties, more than setsize candidates are approved. Paramer `setsize` is ignored if `relative_setsize` is used. relative_setsize : float Proportion (number between 0 and 1) of candidates that voters approve (rounded up). In case of ties, more candidates are approved. E.g., if there are 10 candidates and `relative_setsize=0.75`, then the voter approves the top 8 candidates. use_weights : bool, default=False Use weights of voters instead of individual voters. If False, treat vote count in Preflib file as the number of identical ballots, i.e., the number of voters that approve this set of candidates. If True, treat vote count as weight and use this weight in class Voter. Returns ------- abcvoting.preferences.Profile Preference profile extracted from Preflib file. """ if setsize <= 0: raise ValueError("Parameter setsize must be > 0") if relative_setsize and (relative_setsize <= 0.0 or relative_setsize > 1.0): raise ValueError("Parameter relative_setsize not in interval (0, 1]") with open(filename) as f: line = f.readline() num_cand = int(line.strip()) candidate_map = {} for _ in range(num_cand): parts = f.readline().strip().split(",") candidate_map[int(parts[0].strip())] = ",".join(parts[1:]).strip() parts = f.readline().split(",") try: voter_count, _, unique_orders = (int(p.strip()) for p in parts) except ValueError as error: raise MalformattedFileException( f"Number of voters ill specified ({str(parts)}), should be triple of integers" ) from error approval_sets = [] lines = [line.strip() for line in f.readlines() if line.strip()] if len(lines) != unique_orders: raise MalformattedFileException( f"Expected {unique_orders} lines that specify voters in the input, " f"encountered {len(lines)}") for line in lines: parts = line.split(",") if len(parts) < 1: continue try: count = int(parts[0]) except ValueError as error: raise MalformattedFileException( f"Each ranking must start with count/weight ({line})." ) from error ranking = parts[1:] # ranking starts after count if len(ranking) == 0: raise MalformattedFileException("Empty ranking: " + str(line)) if relative_setsize: num_appr = int(ceil(len(ranking) * relative_setsize)) else: num_appr = setsize approval_set = _approval_set_from_preflib_datastructures( num_appr, ranking, candidate_map) approval_sets.append((count, approval_set)) # normalize candidates to 0, 1, 2, ... cand_names = [] normalize_map = {} for cand, name in candidate_map.items(): cand_names.append(name) normalize_map[cand] = len(cand_names) - 1 profile = Profile(num_cand, cand_names=cand_names) for count, approval_set in approval_sets: normalized_approval_set = [] for cand in approval_set: normalized_approval_set.append(normalize_map[cand]) if use_weights: profile.add_voter(Voter(normalized_approval_set, weight=count)) else: profile.add_voters([normalized_approval_set] * count) if use_weights: if len(profile) != unique_orders: raise MalformattedFileException( "Number of voters wrongly specified in preflib file.") else: if len(profile) != voter_count: raise MalformattedFileException( "Number of voters wrongly specified in preflib file.") return profile