Source code for mapof.elections.objects.OrdinalElection

import csv
import logging
from collections import Counter

from matplotlib import pyplot as plt

from mapof.elections.other.glossary import PATHS, LIST_OF_FAKE_MODELS
import mapof.elections.persistence.election_exports as exports
import mapof.elections.persistence.election_imports as imports
from mapof.core.inner_distances import swap_distance_between_potes, \
    spearman_distance_between_potes
from mapof.core.utils import *
from mapof.elections.cultures.fake import *
from mapof.elections.cultures.matrices.group_separable_matrices import get_gs_caterpillar_vectors
from mapof.elections.cultures.preflib import get_sushi_vectors
from mapof.elections.cultures.matrices.single_crossing_matrices import get_single_crossing_vectors
from mapof.elections.cultures_ import generate_ordinal_votes, \
    from_approval, generate_ordinal_alliance_votes
from mapof.elections.features.other import is_condorcet
from mapof.elections.objects.Election import Election
from mapof.elections.other.winners import compute_sntv_winners, compute_borda_winners, \
    compute_stv_winners
from mapof.elections.other.winners import generate_winners
from mapof.elections.other.winners import get_borda_points


[docs] class OrdinalElection(Election): """ Ordinal Election class. """ def __init__(self, experiment_id=None, election_id=None, culture_id=None, votes=None, label=None, num_voters: int = None, num_candidates: int = None, variable=None, fast_import=False, **kwargs): super().__init__(experiment_id=experiment_id, election_id=election_id, culture_id=culture_id, votes=votes, label=label, num_voters=num_voters, num_candidates=num_candidates, fast_import=fast_import, **kwargs) self.variable = variable self.vectors = [] self.matrix = [] self.potes = None self.condorcet = None self.points = {} self.alliances = {} self.quantities = None self.import_ordinal_election()
[docs] def import_ordinal_election(self): """ Import ordinal election. """ if self.is_imported and self.experiment_id != 'virtual': try: if self.votes is not None: self.culture_id = self.culture_id if str(self.votes[0]) in LIST_OF_FAKE_MODELS: self.fake = True self.votes = self.votes[0] self.num_candidates = self.votes[1] self.num_voters = self.votes[2] else: self.votes = self.votes self.num_candidates = len(self.votes[0]) self.num_voters = len(self.votes) self.compute_potes() else: try: self.fake = imports.check_if_fake( self.experiment_id, self.election_id, 'soc') except: self.fake = False if self.fake: self.culture_id, self.params, self.num_voters, \ self.num_candidates = imports.import_fake_soc_election(self.experiment_id, self.election_id) else: self.votes, self.num_voters, self.num_candidates, self.params, \ self.culture_id, self.alliances, \ self.num_options, self.quantities, \ self.distinct_votes = imports.import_real_soc_election( experiment_id=self.experiment_id, election_id=self.election_id, is_shifted=self.is_shifted) try: self.points['voters'] = self.import_ideal_points('voters') self.points['candidates'] = self.import_ideal_points('candidates') except: pass try: self.alpha = 1 if self.params and 'alpha' in self.params: self.alpha = self.params['alpha'] except KeyError: print("Error") self.candidatelikeness_original_vectors = {} if not self.fast_import: self.votes_to_positionwise_vectors() except: self.is_correct = False if self.params is None: self.params = {} try: self.params, self.printing_params = update_params_ordinal( self.params, self.printing_params, self.variable, self.culture_id, self.num_candidates) except: pass
[docs] def get_vectors(self): """ Get vectors. """ if self.vectors is not None and len(self.vectors) > 0: return self.vectors return self.votes_to_positionwise_vectors()
[docs] def get_matrix(self): """ Get matrix. """ if self.matrix is not None and len(self.matrix) > 0: return self.matrix return self.votes_to_positionwise_matrix()
[docs] def get_potes(self): """ Get potes. """ if self.potes is not None: return self.potes return self.compute_potes()
[docs] def votes_to_positionwise_vectors(self): """ Converts votes to positionwise vectors. """ vectors = np.zeros([self.num_candidates, self.num_candidates]) if self.culture_id == 'conitzer_matrix': vectors = get_conitzer_vectors(self.num_candidates) elif self.culture_id == 'walsh_matrix': vectors = get_walsh_vectors(self.num_candidates) elif self.culture_id == 'single-crossing_matrix': vectors = get_single_crossing_vectors(self.num_candidates) elif self.culture_id == 'gs_caterpillar_matrix': vectors = get_gs_caterpillar_vectors(self.num_candidates) elif self.culture_id == 'sushi_matrix': vectors = get_sushi_vectors() elif self.culture_id in {'norm-mallows_matrix', 'mallows_matrix_path'}: vectors = get_mallows_vectors(self.num_candidates, self.params) elif self.culture_id in {'identity', 'uniformity', 'antagonism', 'stratification'}: vectors = get_fake_vectors_single(self.culture_id, self.num_candidates) elif self.culture_id in {'walsh_path', 'conitzer_path'}: vectors = get_fake_multiplication(self.num_candidates, self.params, self.culture_id) elif self.culture_id in PATHS: vectors = get_fake_convex(self.culture_id, self.num_candidates, self.num_voters, self.params, get_fake_vectors_single) elif self.culture_id in ['crate']: vectors = get_fake_vectors_crate(num_candidates=self.num_candidates, fake_param=self.params) elif self.culture_id in ['from_approval']: vectors = from_approval(num_candidates=self.num_candidates, num_voters=self.num_voters, params=self.params) else: for i in range(self.num_voters): pos = 0 for j in range(self.num_candidates): vote = self.votes[i][j] if vote == -1: continue vectors[vote][pos] += 1 pos += 1 for i in range(self.num_candidates): for j in range(self.num_candidates): vectors[i][j] /= float(self.num_voters) self.vectors = vectors self.matrix = self.vectors.transpose() return vectors
[docs] def votes_to_positionwise_matrix(self): """ Convert votes to positionwise matrix. """ return self.votes_to_positionwise_vectors().transpose()
[docs] def votes_to_pairwise_matrix(self) -> np.ndarray: """ Convert votes to pairwise matrix. """ matrix = np.zeros([self.num_candidates, self.num_candidates]) if self.fake: if self.culture_id in {'identity', 'uniformity', 'antagonism', 'stratification'}: matrix = get_fake_matrix_single(self.culture_id, self.num_candidates) elif self.culture_id in PATHS: matrix = get_fake_convex(self.culture_id, self.num_candidates, self.num_voters, self.fake_param, get_fake_matrix_single) else: for v in range(self.num_voters): for c1 in range(self.num_candidates): for c2 in range(c1 + 1, self.num_candidates): matrix[int(self.votes[v][c1])][ int(self.votes[v][c2])] += 1 for i in range(self.num_candidates): for j in range(i + 1, self.num_candidates): matrix[i][j] /= float(self.num_voters) matrix[j][i] = 1. - matrix[i][j] return matrix
[docs] def votes_to_bordawise_vector(self) -> np.ndarray: """ convert VOTES to Borda vector """ borda_vector = np.zeros([self.num_candidates]) if self.fake: if self.culture_id in {'identity', 'uniformity', 'antagonism', 'stratification'}: borda_vector = get_fake_borda_vector(self.culture_id, self.num_candidates, self.num_voters) elif self.culture_id in PATHS: borda_vector = get_fake_convex(self.culture_id, self.num_candidates, self.num_voters, self.params, get_fake_borda_vector) else: c = self.num_candidates v = self.num_voters vectors = self.votes_to_positionwise_vectors() borda_vector = [sum([vectors[i][j] * (c - j - 1) for j in range(c)]) * v for i in range(self.num_candidates)] borda_vector = sorted(borda_vector, reverse=True) return np.array(borda_vector)
[docs] def votes_to_candidatelikeness_original_vectors(self) -> None: """ convert VOTES to candidate-likeness VECTORS """ matrix = np.zeros([self.num_candidates, self.num_candidates]) for c_1 in range(self.num_candidates): for c_2 in range(self.num_candidates): for vote in self.approval_votes: if (c_1 in vote and c_2 not in vote) or (c_1 not in vote and c_2 in vote): matrix[c_1][c_2] += 1 self.candidatelikeness_original_vectors = matrix / self.num_voters
def votes_to_positionwise_intervals(self, precision: int = None) -> list: vectors = self.votes_to_positionwise_matrix() return [self.vector_to_interval(vectors[i], precision=precision) for i in range(len(vectors))] def votes_to_voterlikeness_vectors(self) -> np.ndarray: return self.votes_to_voterlikeness_matrix()
[docs] def votes_to_voterlikeness_matrix(self, vote_distance='swap') -> np.ndarray: """ convert VOTES to voter-likeness MATRIX """ matrix = np.zeros([self.num_voters, self.num_voters]) self.compute_potes() for v1 in range(self.num_voters): for v2 in range(self.num_voters): if vote_distance == 'swap': matrix[v1][v2] = swap_distance_between_potes(self.potes[v1], self.potes[v2]) elif vote_distance == 'spearman': matrix[v1][v2] = spearman_distance_between_potes(self.potes[v1], self.potes[v2]) for i in range(self.num_voters): for j in range(i + 1, self.num_voters): matrix[j][i] = matrix[i][j] return matrix
[docs] def votes_to_agg_voterlikeness_vector(self): """ convert VOTES to Borda vector """ vector = np.zeros([self.num_voters]) for v1 in range(self.num_voters): for v2 in range(self.num_voters): swap_distance = 0 for i in range(self.num_candidates): for j in range(i + 1, self.num_candidates): if (self.potes[v1][i] > self.potes[v1][j] and self.potes[v2][i] < self.potes[v2][j]) or \ (self.potes[v1][i] < self.potes[v1][j] and self.potes[v2][i] > self.potes[v2][j]): swap_distance += 1 vector[v1] += swap_distance return vector, len(vector)
def compute_winners(self, method=None, num_winners=None): self.borda_points = get_borda_points(self.votes, self.num_voters, self.num_candidates) if method == 'sntv': self.winners = compute_sntv_winners(election=self, num_winners=num_winners) if method == 'borda': self.winners = compute_borda_winners(election=self, num_winners=num_winners) if method == 'stv': self.winners = compute_stv_winners(election=self, num_winners=num_winners) if method in {'approx_cc', 'approx_hb', 'approx_pav'}: self.winners = generate_winners(election=self, num_winners=num_winners)
[docs] def prepare_instance(self, is_exported=None, is_aggregated=True): """ Prepares instance """ if 'num_alliances' in self.params: self.votes, self.alliances = generate_ordinal_alliance_votes( culture_id=self.culture_id, num_candidates=self.num_candidates, num_voters=self.num_voters, params=self.params) else: self.votes = generate_ordinal_votes(culture_id=self.culture_id, num_candidates=self.num_candidates, num_voters=self.num_voters, params=self.params) if not self.fake: c = Counter(map(tuple, self.votes)) counted_votes = [[count, list(row)] for row, count in c.items()] counted_votes = sorted(counted_votes, reverse=True) self.quantities = [a[0] for a in counted_votes] self.distinct_votes = [a[1] for a in counted_votes] self.num_options = len(counted_votes) else: self.quantities = [self.num_voters] self.num_options = 1 if is_exported: exports.export_ordinal_election(self, is_aggregated=is_aggregated)
[docs] def compute_distances(self, distance_id='swap', object_type=None): """ Return: distances between votes """ if object_type is None: object_type = self.object_type self.distinct_potes = convert_votes_to_potes(self.distinct_votes) self.num_dist_votes = len(self.distinct_votes) self.num_options = self.num_dist_votes if object_type == 'vote': distances = np.zeros([self.num_dist_votes, self.num_dist_votes]) for v1 in range(self.num_dist_votes): for v2 in range(self.num_dist_votes): if distance_id == 'swap': distances[v1][v2] = swap_distance_between_potes( self.distinct_potes[v1], self.distinct_potes[v2]) elif distance_id == 'spearman': distances[v1][v2] = spearman_distance_between_potes( self.distinct_potes[v1], self.distinct_potes[v2]) elif object_type == 'candidate': self.compute_potes() if distance_id == 'domination': distances = self.votes_to_pairwise_matrix() distances = np.abs(distances - 0.5) * self.num_voters np.fill_diagonal(distances, 0) elif distance_id == 'position': distances = np.zeros([self.num_candidates, self.num_candidates]) for c1 in range(self.num_candidates): for c2 in range(self.num_candidates): dist = 0 for pote in self.potes: dist += abs(pote[c1] - pote[c2]) distances[c1][c2] = dist else: logging.warning('incorrect object_type') self.distances[object_type] = distances if object_type == 'vote': length = self.num_dist_votes elif object_type == 'candidate': length = self.num_candidates if self.is_exported: exports.export_distances(self, object_type=object_type, length=length)
[docs] def is_condorcet(self): """ Check if election witness Condorcet winner""" if self.condorcet is None: self.condorcet = is_condorcet(self) return self.condorcet
def import_ideal_points(self, name): path = os.path.join(os.getcwd(), "experiments", self.experiment_id, "elections", f'{self.election_id}_{name}.csv') points = [] with open(path, 'r', newline='') as csv_file: reader = csv.DictReader(csv_file, delimiter=';') for row in reader: points.append([float(row['x']), float(row['y'])]) return points @staticmethod def texify_label(name): return name.replace('phi', '$\phi$'). \ replace('alpha', '$\\ \\alpha$'). \ replace('omega', '$\\ \\omega$'). \ replace('§', '\n', 1). \ replace('0.005', '$\\frac{1}{200}$'). \ replace('0.025', '$\\frac{1}{40}$'). \ replace('0.75', '$\\frac{3}{4}$'). \ replace('0.25', '$\\frac{1}{4}$'). \ replace('0.01', '$\\frac{1}{100}$'). \ replace('0.05', '$\\frac{1}{20}$'). \ replace('0.5', '$\\frac{1}{2}$'). \ replace('0.1', '$\\frac{1}{10}$'). \ replace('0.2', '$\\frac{1}{5}$'). \ replace('0.4', '$\\frac{2}{5}$'). \ replace('0.8', '$\\frac{4}{5}$'). \ replace(' ', '\n', 1) def print_map( self, show=True, radius=None, alpha=0.1, s=30, object_type=None, double_gradient=False, saveas=None, color='blue', marker='o', title_size=20 ): if object_type is None: object_type = self.object_type if object_type == 'vote': length = self.num_voters elif object_type == 'candidate': length = self.num_candidates else: logging.warning(f'Incorrect object type: {object_type}') length = 0 plt.figure(figsize=(6.4, 6.4)) X = [] Y = [] for elem in self.coordinates[object_type]: X.append(elem[0]) Y.append(elem[1]) start = False if start: plt.scatter(X[0], Y[0], color='sienna', s=1000, alpha=1, marker='X') if object_type == 'vote': if double_gradient: for i in range(length): x = float(self.points['voters'][i][0]) y = float(self.points['voters'][i][1]) plt.scatter(X[i], Y[i], color=[0, y, x], s=s, alpha=alpha) else: for i in range(len(X)): plt.scatter(X[i], Y[i], color=color, alpha=alpha, marker=marker, s=self.quantities[i] * s) elif object_type == 'candidate': for i in range(len(X)): plt.scatter(X[i], Y[i], color=color, alpha=alpha, marker=marker, s=s) avg_x = np.mean(X) avg_y = np.mean(Y) if radius: plt.xlim([avg_x - radius, avg_x + radius]) plt.ylim([avg_y - radius, avg_y + radius]) try: plt.title(self.texify_label(self.label), size=title_size) # tmp except: pass plt.axis('off') if saveas is not None: if saveas == 'default': dir = os.path.join(os.getcwd(), "images", self.experiment_id) if not os.path.isdir(dir): os.mkdir(os.path.join(os.getcwd(), dir)) saveas = f'{self.label}_{object_type}' file_name = os.path.join(os.getcwd(), "images", self.experiment_id, f'{saveas}.png') plt.savefig(file_name, bbox_inches='tight', dpi=100) if show: plt.show() else: plt.clf() plt.close()
[docs] def convert_votes_to_potes(votes) -> np.array: """ Convert votes to positional votes (called potes) """ return np.array([[list(vote).index(i) for i, _ in enumerate(vote)] for vote in votes])