Source code for mapof.elections.distances.ilp_subelections

import logging

import gurobipy as gp
from gurobipy import GRB

from mapof.elections.distances.register import register_ordinal_election_distance


# THIS FUNCTION HAS NOT BEEN TESTED SINCE CONVERSION TO GUROBI
[docs] @register_ordinal_election_distance("maximum_common_voter_subelection") def maximum_common_voter_subelection(election_1, election_2, metric_name='0') -> int: """ This function solves the maximum common voter subelection problem between two elections. Parameters ---------- election_1 : Election The first election. election_2 : Election The second election. metric_name : str Returns ------- int The maximum number of common voters between the two elections. """ # Initialize model model = gp.Model() # Limit the number of threads model.setParam('Threads', 1) # OBJECTIVE FUNCTION names = [] for v1 in range(election_1.num_voters): for v2 in range(election_2.num_voters): names.append('N' + str(v1) + '_' + str(v2)) # Add variables N_vars = model.addVars(names, vtype=GRB.BINARY, obj=1.0, name="N") # Set objective to maximize model.ModelSense = GRB.MAXIMIZE # FIRST CONSTRAINT FOR VOTERS for v1 in range(election_1.num_voters): model.addConstr(gp.quicksum( N_vars['N' + str(v1) + '_' + str(v2)] for v2 in range(election_2.num_voters)) <= 1.0, name='C1_' + str(v1)) # SECOND CONSTRAINT FOR VOTERS for v2 in range(election_2.num_voters): model.addConstr(gp.quicksum( N_vars['N' + str(v1) + '_' + str(v2)] for v1 in range(election_1.num_voters)) <= 1.0, name='C2_' + str(v2)) # ADD VARIABLES FOR CANDIDATES M_names = [] for c1 in range(election_1.num_candidates): for c2 in range(election_2.num_candidates): M_names.append('M' + str(c1) + '_' + str(c2)) M_vars = model.addVars(M_names, vtype=GRB.BINARY, name="M") # FIRST CONSTRAINT FOR CANDIDATES for c1 in range(election_1.num_candidates): model.addConstr(gp.quicksum(M_vars['M' + str(c1) + '_' + str(c2)] for c2 in range(election_2.num_candidates)) == 1.0, name='C3_' + str(c1)) # SECOND CONSTRAINT FOR CANDIDATES for c2 in range(election_2.num_candidates): model.addConstr(gp.quicksum(M_vars['M' + str(c1) + '_' + str(c2)] for c1 in range(election_1.num_candidates)) == 1.0, name='C4_' + str(c2)) # MAIN CONSTRAINT FOR VOTES potes_1 = election_1.get_potes() potes_2 = election_2.get_potes() for v1 in range(election_1.num_voters): for v2 in range(election_2.num_voters): M_constr = gp.LinExpr() for c1 in range(election_1.num_candidates): for c2 in range(election_2.num_candidates): if abs(potes_1[v1][c1] - potes_2[v2][c2]) <= int(metric_name): M_constr += M_vars['M' + str(c1) + '_' + str(c2)] M_constr -= N_vars['N' + str(v1) + '_' + str(v2)] * election_1.num_candidates model.addConstr(M_constr >= 0.0, name='C5_' + str(v1) + '_' + str(v2)) # Optimize the model model.optimize() # Return the objective value if model.status == GRB.OPTIMAL: return model.objVal else: logging.warning("No optimal solution found")