"population": updaters.Tally(pop_col, alias="population"), "HISP": updaters.Tally("HISP", alias="HISP"), "BPOP": updaters.Tally("NH_BLACK", alias="BPOP") } #initial partition initial_ass = littlehelpers.factor_seed(graph, k, pop_tol, pop_col) initial_partition = Partition(graph, initial_ass, myupdaters) #chain compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 2*len(initial_partition["cut_edges"]) ) myconstraints = [ constraints.within_percent_of_ideal_population(initial_partition, pop_tol), compactness_bound ] chain = MarkovChain( proposal=myproposal, constraints=myconstraints, accept=always_accept, initial_state=initial_partition, total_steps=steps ) #run ReCom for index, step in enumerate(chain): if index%INTERVAL == 0: print(index, end=" ") HISPs.append(list(step["HISP"].values()))
def main(): #gerrychain parameters #num districts k = 12 epsilon = .05 updaters = { 'population': Tally('population'), 'cut_edges': cut_edges, } graph, dual = preprocessing("json/NC.json") ideal_population = sum(graph.nodes[x]["population"] for x in graph.nodes()) / k faces = graph.graph["faces"] faces = list(faces) #random.choice(faces) will return a random face #TODO: run gerrychain on graph totpop = 0 for node in graph.nodes(): totpop += int(graph.nodes[node]['population']) # length of chain steps = 30000 #beta thereshold, how many steps to hold beta at 0 temperature = 1 beta_threshold = 10000 #length of each gerrychain step gerrychain_steps = 250 #faces that are currently sierp special_faces = [] chain_output = {'dem_seat_data': [], 'rep_seat_data': [], 'score': []} #start with small score to move in right direction chain_output['score'].append(1 / 1100000) z = 0 for i in range(steps): if z % 100 == 0: z += 1 print("step ", z) face = random.choice(faces) ##Makes the Markov chain lazy -- this just makes the chain aperiodic. if random.random() > .5: if not face in special_faces: special_faces.append(face) else: special_faces.remove(face) face_sierpinski_mesh(graph, special_faces) initial_partition = Partition(graph, assignment=config['ASSIGN_COL'], updaters=updaters) # Sets up Markov chain popbound = within_percent_of_ideal_population(initial_partition, epsilon) tree_proposal = partial( recom, pop_col=config['POP_COL'], pop_target=ideal_population, epsilon=epsilon, node_repeats=1, method=facefinder.my_mst_bipartition_tree_random) #make new function -- this computes the energy of the current map exp_chain = MarkovChain(tree_proposal, Validator([popbound]), accept=accept.always_accept, initial_state=initial_partition, total_steps=gerrychain_steps) seats_won_for_republicans = [] seats_won_for_democrats = [] for part in exp_chain: rep_seats_won = 0 dem_seats_won = 0 for i in range(k): rep_votes = 0 dem_votes = 0 for n in graph.nodes(): if part.assignment[n] == i: rep_votes += graph.nodes[n]["EL16G_PR_R"] dem_votes += graph.nodes[n]["EL16G_PR_D"] total_seats_dem = int(dem_votes > rep_votes) total_seats_rep = int(rep_votes > dem_votes) rep_seats_won += total_seats_rep dem_seats_won += total_seats_dem seats_won_for_republicans.append(rep_seats_won) seats_won_for_democrats.append(dem_seats_won) score = statistics.mean(seats_won_for_republicans) ##This is the acceptance step of the Metropolis-Hasting's algorithm. if random.random() < min( 1, (math.exp(score) / chain_output['score'][z - 1]) **(1 / temperature)): #if code acts weird, check if sign is wrong, unsure #rand < min(1, P(x')/P(x)) chain_output['dem_seat_data'].append(seats_won_for_democrats) chain_output['rep_seat_data'].append(seats_won_for_republicans) chain_output['score'].append( math.exp(statistics.mean(seats_won_for_republicans))) else: chain_output['dem_seat_data'].append( chain_output['dem_seat_data'][z - 1]) chain_output['rep_seat_data'].append( chain_output['rep_seat_data'][z - 1]) chain_output['score'].append(chain_output['score'][z - 1])
def produce_gerrymanders(graph, k, tag, sample_size, chaintype): #Samples k partitions of the graph #stores vote histograms, and returns most extreme partitions. for n in graph.nodes(): graph.nodes[n]["last_flipped"] = 0 graph.nodes[n]["num_flips"] = 0 ideal_population= sum( graph.nodes[x]["population"] for x in graph.nodes())/k updaters = {'population': Tally('population'), 'cut_edges': cut_edges, 'step_num': step_num, } initial_partition = Partition(graph, assignment='part', updaters=updaters) pop1 = .05 popbound = within_percent_of_ideal_population(initial_partition, pop1) if chaintype == "tree": tree_proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=pop1, node_repeats=1, method=my_mst_bipartition_tree_random) elif chaintype == "uniform_tree": tree_proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=pop1, node_repeats=1, method=my_uu_bipartition_tree_random) else: print("Chaintype used: ", chaintype) raise RuntimeError("Chaintype not recongized. Use 'tree' or 'uniform_tree' instead") exp_chain = MarkovChain(tree_proposal, Validator([popbound]), accept=always_true, initial_state=initial_partition, total_steps=sample_size) seats_won_table = [] best_left = np.inf best_right = -np.inf ctr = 0 for part in exp_chain: ctr += 1 seats_won = 0 if ctr % 100 == 0: print("step ", ctr) for i in range(k): rep_votes = 0 dem_votes = 0 for n in graph.nodes(): if part.assignment[n] == i: rep_votes += graph.nodes[n]["EL16G_PR_R"] dem_votes += graph.nodes[n]["EL16G_PR_D"] total_seats = int(rep_votes > dem_votes) seats_won += total_seats #total seats won by rep seats_won_table.append(seats_won) # save gerrymandered partitionss if seats_won < best_left: best_left = seats_won left_mander = copy.deepcopy(part.parts) if seats_won > best_right: best_right = seats_won right_mander = copy.deepcopy(part.parts) #print("finished round" print("max", best_right, "min:", best_left) #plt.figure() #plt.hist(seats_won_table, bins = 10) name = "./plots/seats_histogram" + tag +".png" #plt.savefig(name) #plt.close() sns_plot = sns.distplot(seats_won_table, label="North Carolina Republican Vote Distribution").get_figure() plt.legend() sns_plot.savefig(name) return left_mander, right_mander
init_partition = Partition(graph, assignment=cddict, updaters=or_updaters) ## Setup chain proposal = partial(recom, pop_col=POP_COL, pop_target=ideal_pop, epsilon=EPS, node_repeats=1) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 2 * len(init_partition["cut_edges"])) chain = MarkovChain(proposal, constraints=[ constraints.within_percent_of_ideal_population( init_partition, EPS), compactness_bound ], accept=accept.always_accept, initial_state=init_partition, total_steps=ITERS) ## Run chain print("Starting Markov Chain") def init_min_results(): data = {"cutedges": np.zeros(ITERS)} data["HVAP"] = np.zeros((ITERS, NUM_DISTRICTS)) data["ASIANVAP"] = np.zeros((ITERS, NUM_DISTRICTS)) data["HVAP_perc"] = np.zeros((ITERS, NUM_DISTRICTS))
print("Creating seed plan") total_pop = sum([graph.nodes[n][POP_COL] for n in graph.nodes]) ideal_pop = total_pop / NUM_DISTRICTS if args.map != "state_house": cddict = recursive_tree_part(graph=graph, parts=range(NUM_DISTRICTS), pop_target=ideal_pop, pop_col=POP_COL, epsilon=EPS) else: with open("GA_house_seed_part_0.05.p", "rb") as f: cddict = pickle.load(f) init_partition = Partition(graph, assignment=cddict, updaters=ga_updaters) while(not constraints.within_percent_of_ideal_population(init_partition, EPS)(init_partition)): cddict = recursive_tree_part(graph=graph, parts=range(NUM_DISTRICTS), pop_target=ideal_pop, pop_col=POP_COL, epsilon=EPS) init_partition = Partition(graph, assignment=cddict, updaters=ga_updaters) ## Setup chain proposal = partial(recom, pop_col=POP_COL, pop_target=ideal_pop, epsilon=EPS, node_repeats=1) compactness_bound = constraints.UpperBound(lambda p: len(p["cut_edges"]), 2*len(init_partition["cut_edges"])) chain = MarkovChain(
range(num_dist), pop_target=pop / num_dist, pop_col=pop_col, epsilon=0.05, node_repeats=3) random_init_partition = GeographicPartition(graph, recursive_part, updaters) # Racial backsliding test racial_breakdown = random_init_partition["racial_demographics"] if not (backsliding_pass(racial_vap_ref, racial_breakdown)): chain_racial_requirements = MarkovChain( proposal=proposal, constraints=[ constraints.within_percent_of_ideal_population( random_init_partition, 0.05) ], accept=accept.always_accept, initial_state=random_init_partition, total_steps=10000) found_acceptable_race_plan = False counter = 1 for part in chain_racial_requirements: print(counter) partition_racial_demo = part["racial_demographics"] if backsliding_pass(racial_vap_ref, partition_racial_demo): random_init_partition = part found_acceptable_race_plan = True break counter = counter + 1
# we can improve speed by bailing early on unbalanced partitions. ideal_population = sum( initial_partition["population"].values()) / len(initial_partition) # We use functools.partial to bind the extra parameters (pop_col, pop_target, epsilon, node_repeats) # of the recom proposal. proposal = partial(recom, pop_col="TOTPOP", pop_target=ideal_population, epsilon=0.03, node_repeats=2) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 2 * len(initial_partition["cut_edges"])) pop_constraint = constraints.within_percent_of_ideal_population( initial_partition, 0.03) chain1 = MarkovChain(proposal=proposal, constraints=[pop_constraint, compactness_bound], accept=accept.always_accept, initial_state=initial_partition, total_steps=50000) data = pd.DataFrame(partition["SEN12"].mean_median() for partition in chain.with_progress_bar()) temp = [partition["PRES16"].mean_median() for partition in chain] temp1 = [partition["PRES16"].efficiency_gap() for partition in chain] plt.hist(temp, bins=200) count = 0 for partition in chain1: count += 1
def run_chain(init_part, chaintype, length, ideal_population, id): """Runs a Recom chain, and saves the seats won histogram to a file and returns the most Gerrymandered plans for both PartyA and PartyB Args: init_part (Gerrychain Partition): initial partition of chain chaintype (String): indicates which proposal to be used to generate spanning tree during Recom. Must be either "tree" or "uniform_tree" length (int): total steps of chain id (String): id of experiment, used when printing progress Raises: RuntimeError: If chaintype is not "tree" nor 'uniform_tree" Returns: list of partitions generated by chain """ graph = init_part.graph popbound = within_percent_of_ideal_population(init_part, config['EPSILON']) # Determine proposal for generating spanning tree based upon parameter if chaintype == "tree": tree_proposal = partial( recom, pop_col=config["POP_COL"], pop_target=ideal_population, epsilon=config['EPSILON'], node_repeats=config['NODE_REPEATS'], method=facefinder.my_mst_bipartition_tree_random) elif chaintype == "uniform_tree": tree_proposal = partial( recom, pop_col=config["POP_COL"], pop_target=ideal_population, epsilon=config['EPSILON'], node_repeats=config['NODE_REPEATS'], method=facefinder.my_uu_bipartition_tree_random) else: print("Chaintype used: ", chaintype) raise RuntimeError( "Chaintype not recognized. Use 'tree' or 'uniform_tree' instead") # Chain to be run chain = MarkovChain(tree_proposal, Validator([popbound]), accept=accept.always_accept, initial_state=init_part, total_steps=length) electionDict = { 'seats': (lambda x: x[config['ELECTION_NAME']].seats('PartyA')), 'won': (lambda x: x[config['ELECTION_NAME']].seats('PartyA')), 'efficiency_gap': (lambda x: x[config['ELECTION_NAME']].efficiency_gap()), 'mean_median': (lambda x: x[config['ELECTION_NAME']].mean_median()), 'mean_thirdian': (lambda x: x[config['ELECTION_NAME']].mean_thirdian()), 'partisan_bias': (lambda x: x[config['ELECTION_NAME']].partisan_bias()), 'partisan_gini': (lambda x: x[config['ELECTION_NAME']].partisan_gini()) } # Run chain, save each desired statistic, and keep track of cuts. Save most # left gerrymandered partition statistics = {statistic: [] for statistic in config['ELECTION_STATISTICS']} for i, partition in enumerate(chain): # Save statistics of partition for statistic in config['ELECTION_STATISTICS']: statistics[statistic].append(electionDict[statistic](partition)) if i % 500 == 0: print('{}: {}'.format(id, i)) saveRunStatistics(statistics, id)
def demo(): graph_path = "./Data/PA_VTDALL.json" graph = Graph.from_json(graph_path) k = 18 ep = 0.05 pop_col = "TOT_POP" plot_path = "./Data/VTD_FINAL" unit_df = gpd.read_file(plot_path) unit_col = "GEOID10" division_col = "COUNTYFP10" divisions = unit_df[[division_col, 'geometry']].dissolve(by=division_col, aggfunc='sum') county_dict = pd.Series(unit_df[division_col].values, index=unit_df[unit_col]).to_dict() for v in graph.nodes: graph.nodes[v]['division'] = county_dict[v] graph.nodes[v][unit_col] = v updaters = { "population": Tally("TOT_POP", alias="population"), "cut_edges": cut_edges, } cddict = recursive_tree_part(graph, range(k), unit_df[pop_col].sum() / k, pop_col, .01, node_repeats=1) initial_partition = Partition(graph, cddict, updaters) ideal_population = sum( initial_partition["population"].values()) / len(initial_partition) division_proposal = partial(recom, pop_col=pop_col, pop_target=ideal_population, epsilon=0.05, method=partial(division_bipartition_tree, division_col='division'), node_repeats=2) chain = MarkovChain(proposal=division_proposal, constraints=[ constraints.within_percent_of_ideal_population( initial_partition, 0.05), ], accept=accept.always_accept, initial_state=initial_partition, total_steps=1000) t = 0 snapshot = 100 for part in chain: if t % snapshot == 0: draw_graph(graph, part.assignment, unit_df, divisions, './figs/chain_' + str(t) + '.png', geo_id=unit_col) print( "t: ", t, ", num_splits: ", num_splits(part, unit_df, geo_id=unit_col, division_col=division_col), ", cut_length", cut_length(part)) t += 1
def run_GerryChain_heuristic(G, population_deviation, k, threshold, iterations): my_updaters = {"population": updaters.Tally("TOTPOP", alias="population")} start = recursive_tree_part( G, range(k), sum(G.nodes[i]["TOTPOP"] for i in G.nodes()) / k, "TOTPOP", population_deviation / 2, 1) initial_partition = GeographicPartition(G, start, updaters=my_updaters) proposal = partial(recom, pop_col="TOTPOP", pop_target=sum(G.nodes[i]["TOTPOP"] for i in G.nodes()) / k, epsilon=population_deviation / 2, node_repeats=2) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 1.5 * len(initial_partition["cut_edges"])) pop_constraint = constraints.within_percent_of_ideal_population( initial_partition, population_deviation / 2) my_chain = MarkovChain(proposal=proposal, constraints=[pop_constraint, compactness_bound], accept=accept.always_accept, initial_state=initial_partition, total_steps=iterations) min_cut_edges = sum(G[i][j]['edge_length'] for i, j in G.edges) print( "In GerryChain heuristic, current # of cut edges and minority districts: ", end='') print(min_cut_edges, ",", sep='', end=' ') max_of_minority_districts = -1 all_maps = [] pareto_frontier = [] obj_vals = [] for partition in my_chain: current_cut_edges = sum(G[i][j]['edge_length'] for i, j in partition["cut_edges"]) number_minority_district = 0 for district in range(k): total_pop_district = 0 total_pop_minority = 0 for node in partition.graph: if partition.assignment[node] == district: total_pop_district += G.node[node]["VAP"] total_pop_minority += G.node[node]["BVAP"] #total_pop_minority += G.node[node]["HVAP"] #total_pop_minority += G.node[node]["AMINVAP"] #total_pop_minority += G.node[node]["ASIANVAP"] #total_pop_minority += G.node[node]["NHPIVAP"] if (total_pop_minority > threshold * total_pop_district): number_minority_district += 1 if number_minority_district > max_of_minority_districts: max_of_minority_districts = number_minority_district if current_cut_edges < min_cut_edges: min_cut_edges = current_cut_edges print((current_cut_edges, number_minority_district), ",", sep='', end=' ') obj_vals.append([current_cut_edges, number_minority_district]) all_maps.append( [partition, current_cut_edges, number_minority_district]) print("Best heuristic solution has # cut edges =", min_cut_edges) print("Best heuristic solution has # minority districts =", max_of_minority_districts) all_maps.sort(key=lambda x: x[1]) all_maps.sort(key=lambda x: x[2], reverse=True) pareto_frontier.append(all_maps[0]) least_number_of_cut_edges = all_maps[0][1] for i in range(1, len(all_maps)): if all_maps[i][1] < least_number_of_cut_edges: pareto_frontier.append(all_maps[i]) least_number_of_cut_edges = all_maps[i][1] print("Pareto Frontier: ", pareto_frontier) optimal_maps = [] optimal_cut_edges = [] optimal_minority_districts = [] for plan in pareto_frontier: optimal_maps.append( [[i for i in G.nodes if plan[0].assignment[i] == j] for j in range(k)]) optimal_cut_edges.append(plan[1]) optimal_minority_districts.append(plan[2]) return (optimal_maps, optimal_cut_edges, optimal_minority_districts, obj_vals)
bcvap_share = list(bcvap_share_dict.values()) hcvap_share = list(hcvap_share_dict.values()) hcvap_over_thresh = len([k for k in hcvap_share if k > .45]) bcvap_over_thresh = len([k for k in bcvap_share if k > .25]) return (hcvap_over_thresh >= enacted_hisp and bcvap_over_thresh >= enacted_black) #acceptance functions ##################################### accept = accept.always_accept #set Markov chain parameters chain = MarkovChain( proposal = proposal, constraints = [constraints.within_percent_of_ideal_population(initial_partition, pop_tol), inclusion] \ if ensemble_inclusion else [constraints.within_percent_of_ideal_population(initial_partition, pop_tol), inclusion_demo]\ if ensemble_inclusion_demo else [constraints.within_percent_of_ideal_population(initial_partition, pop_tol)], accept = accept, initial_state = initial_partition, total_steps = total_steps ) #prep plan storage ################################################################################# store_plans = pd.DataFrame(columns=["Index", "GEOID"]) store_plans["Index"] = list(initial_partition.assignment.keys()) state_gdf_geoid = state_gdf[[geo_id]] store_plans["GEOID"] = [ state_gdf_geoid.iloc[i][0] for i in store_plans["Index"] ] map_metric = pd.DataFrame(columns = ["Latino_state", "Black_state", "Distinct_state",\
'cut_edges': cut_edges, 'step_num': step_num, } runlist = [0] partdict = {r: [] for r in runlist} allparts = [] #run annealing flip for run in runlist: initial_ass = recursive_tree_part(graph, range(6), totpop / 6, "TOTPOP", .01, 1) initial_partition = Partition(graph, assignment=initial_ass, updaters=myupdaters) popbound = within_percent_of_ideal_population(initial_partition, pop_tol) ideal_population = totpop / 6 print("Dumping seed", run) pickle.dump(initial_ass, open("oneseed" + str(run), "wb")) #make flip chain exp_chain = MarkovChain(propose_random_flip, constraints=[single_flip_contiguous, popbound], accept=annealing_cut_accept2, initial_state=initial_partition, total_steps=STEPS) for index, part in enumerate(exp_chain): if index % INTERVAL == 0: print(run, "-", index) allparts.append(part)
graph, pos={x: x for x in graph.nodes()}, node_color=[grid_partition.assignment[x] for x in graph.nodes()], node_size=ns, node_shape="s", cmap="tab20", ) plt.savefig("./grids/plots/medium/initial.png") plt.close() # ADD CONSTRAINTS popbound = within_percent_of_ideal_population(grid_partition, 1) # ########Setup Proposal ideal_population = sum( grid_partition["population"].values()) / len(grid_partition) tree_proposal = partial( recom, pop_col="population", pop_target=ideal_population, epsilon=0.05, node_repeats=1, ) # ######BUILD AND RUN FIRST MARKOV CHAIN
pop_target=df['CVAP'].sum() / num_districts, epsilon=0.01, node_repeats=1 # e = .02 )) #recom, pop_col="TOTPOP", pop_target=df['TOTPOP'].sum()/num_districts, epsilon=0.01, node_repeats=1 # e = .02 #)) compactness_bounds.append( constraints.UpperBound(lambda p: len(p["cut_edges"]), 2 * len(initial_partitions[i]["cut_edges"]))) chains.append( MarkovChain( proposal=proposals[i], constraints=[ constraints.within_percent_of_ideal_population( initial_partitions[i], .01), compactness_bounds[i] # e = .05 #constraints.single_flip_contiguous#no_more_discontiguous #constraints.within_percent_of_ideal_population(initial_partitions[i], .3) ], accept=always_accept, initial_state=initial_partitions[i], total_steps=10000)) # In[7]: cuts = [[], [], [], []] BVAPS = [[], [], [], []] BCVAPS = [[], [], [], []] #37 to 55 is opportunity district BVAP range in VA
def produce_sample(graph, k, tag, sample_size = 500): #Samples k partitions of the graph, stores the cut edges and records them graphically #Also stores vote histograms, and returns most extreme partitions. for edge in graph.edges(): graph[edge[0]][edge[1]]['cut_times'] = 0 for n in graph.nodes(): #graph.nodes[n]["population"] = 1 #graph.nodes[n]["POP10"] #This is something gerrychain will refer to for checking population balance graph.nodes[n]["last_flipped"] = 0 graph.nodes[n]["num_flips"] = 0 #sierp_partition = build_balanced_partition(g_sierpinsky, "population", ideal_population, .01) ideal_population= sum( graph.nodes[x]["population"] for x in graph.nodes())/k updaters = {'population': Tally('population'), 'cut_edges': cut_edges, 'step_num': step_num, } initial_partition = Partition(graph, assignment='part', updaters=updaters) #viz(g_sierpinsky, set([]), sierp_partition.parts) #ideal_population = sum(sierp_partition["population"].values()) / len(sierp_partition) print(ideal_population) steps = sample_size pop1 = .1 popbound = within_percent_of_ideal_population(initial_partition, pop1) chaintype = "tree" if chaintype == "tree": tree_proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=pop1, node_repeats=1, method=my_mst_bipartition_tree_random) if chaintype == "uniform_tree": tree_proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=pop1, node_repeats=1, method=my_uu_bipartition_tree_random) exp_chain = MarkovChain(tree_proposal, Validator([popbound]), accept=always_true, initial_state=initial_partition, total_steps=steps) z = 0 num_cuts_list = [] seats_won_table = [] best_left = np.inf best_right = -np.inf ctr = 0 for part in exp_chain: ctr += 1 print(ctr) #for i in range(steps): # part = build_balanced_partition(g_sierpinsky, "population", ideal_population, .05) seats_won = 0 z += 1 if z % 100 == 0: print("step ", z) for edge in part["cut_edges"]: graph[edge[0]][edge[1]]["cut_times"] += 1 for i in range(k): rural_pop = 0 urban_pop = 0 for n in graph.nodes(): if part.assignment[n] == i: rural_pop += graph.nodes[n]["EL16G_PR_R"] urban_pop += graph.nodes[n]["EL16G_PR_D"] total_seats = int(rural_pop > urban_pop) seats_won += total_seats #total seats won by rual pop seats_won_table.append(seats_won) # save gerrymandered partitionss if seats_won < best_left: best_left = seats_won left_mander = copy.deepcopy(part.parts) if seats_won > best_right: best_right = seats_won right_mander = copy.deepcopy(part.parts) #print("finished round" print("max", best_right, "min:", best_left) edge_colors = [graph[edge[0]][edge[1]]["cut_times"] for edge in graph.edges()] pos=nx.get_node_attributes(graph, 'pos') plt.figure() nx.draw(graph, pos=nx.get_node_attributes(graph, 'pos'), node_size=1, edge_color=edge_colors, node_shape='s', cmap='magma', width=3) plt.savefig("./plots/edges" + tag + ".png") plt.close() plt.figure() plt.hist(seats_won_table, bins = 10) name = "./plots/seats_histogram" + tag +".png" plt.savefig(name) plt.close() return left_mander, right_mander
init_partition = Partition(graph, assignment=cddict, updaters=updaters) if tree_walk: ideal_population = sum( init_partition["population"].values()) / len(init_partition) proposal = partial( recom, pop_col="TOTPOP", pop_target=ideal_population, epsilon=0.02, node_repeats=1, ) popbound = within_percent_of_ideal_population(init_partition, 0.05) gchain = MarkovChain( proposal, Validator([popbound]), accept=always_accept, initial_state=init_partition, total_steps=100, ) t = 0 for part in gchain: t += 1 init_partition = part print("FINISHED TREE WALK")
def MC_sample(jgraph, settings, save_part = True): """ :param jgraph: gerrychain Graph object :param settings: settings dictionary (possibly loaded from a yaml file) with election info, MC parameters, and constraints params (see settings.yaml file for an example of the structure needed) :param save_part: True is you want to save the partition as json :returns: a list of partitions sapmpled every interval step """ my_updaters = { "cut_edges": cut_edges, "population": updaters.Tally("TOTPOP", alias = "population"), "avg_pop_dist": avg_pop_dist, "pop_dist_pct" : pop_dist_pct, "area_land": updaters.Tally("ALAND10", alias = "area_land"), "area_water": updaters.Tally("AWATER10", alias = "area_water"), "Perimeter": updaters.Tally("perimeter", alias = "Perimeter"), "Area": updaters.Tally("area", alias = "Area") } num_elections = settings['num_elections'] election_names = settings['election_names'] election_columns = settings['election_columns'] num_steps = settings['num_steps'] interval = settings['interval'] pop_tol = settings['pop_tol'] MC_type = settings['MC_type'] elections = [ Election( election_names[i], {"Democratic": election_columns[i][0], "Republican": election_columns[i][1]}, ) for i in range(num_elections) ] election_updaters = {election.name: election for election in elections} my_updaters.update(election_updaters) initial_partition = Partition(jgraph, "CD", my_updaters) # by typing in "CD," we are saying to put every county into the congressional district that they belong to print('computed initial partition') ideal_population = sum(initial_partition["population"].values()) / len( initial_partition ) pop_constraint = constraints.within_percent_of_ideal_population(initial_partition, pop_tol) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 2 * len(initial_partition["cut_edges"]) ) proposal = partial( recom, pop_col=pop_col, pop_target=ideal_population, epsilon=pop_tol, node_repeats=1) constraints_=[pop_constraint, compactness_bound] if MC_type == "flip": proposal = propose_random_flip constraints_=[single_flip_contiguous, pop_constraint, compactness_bound] chain = MarkovChain( proposal=proposal, constraints=constraints_, accept=always_accept, initial_state=initial_partition, total_steps=num_steps ) partitions=[] # recording partitions at each step for index, part in enumerate(chain): if index % interval == 0: print('Markov chain step '+str(index)) partitions += [part] if save_part: sd.dump_run(settings['partitions_path'], partitions) print('saved partitions to '+ settings['partitions_path']) return(partitions)
def run_ensemble_on_distro(graph, min_pop_col, maj_pop_col, tot_pop_col, num_districts, initial_plan, num_steps, pop_tol = 0.05, min_win_thresh = 0.5): """Runs a Recom chain on a given graph with a given minority/majority population distribution and returns lists of cut edges, minority seat wins, and tuples of minority percentage by district for each step of the chain. Parameters: graph (networkx.Graph) -- a NetworkX graph object representing the dual graph on which to run the chain. The nodes should have attributes for majority population, minority population, and total population. min_pop_col (string) -- the key/column name for the minority population attribute in graph maj_pop_col (string) -- the key/column name for the majority population attribute in graph tot_pop_col (string) -- the key/column name for the total population attribute in graph num_districts (int) -- number of districts to run for the chain initial_plan (gerrychain.Partition) -- an initial partition for the chain (which does not need updaters since the function will supply its own updaters) num_steps (int) -- the number of steps for which to run the chain pop_tol (float, default 0.05) -- tolerance for deviation from perfectly balanced populations between districts min_win_thresh (float, default 0.5) -- percent of minority population needed in a district for it to be considered a minority win. If the minority percentage in a district is greater than or equal to min_win_thresh then that district is considered a minority win. Returns: [cut_edges_list,min_seats_list,min_percents_list] (list) WHERE cut_edges_list (list) -- list where cut_edges_list[i] is the number of cut edges in the partition at step i of the Markov chain min_seats_list -- list where min_seats_list[i] is the number of districts won by the minority (according to min_win_thresh) at step i of the chain min_percents_list -- list where min_percents_list[i] is a tuple, with min_percents_list[i][j] being the minority percentage in district j at step i of the chain """ my_updaters = { "population": Tally(tot_pop_col, alias = "population"), "cut_edges": cut_edges, "maj-min": Election("maj-min", {"maj": maj_pop_col, "min": min_pop_col}), } initial_partition = Partition(graph = initial_plan.graph, assignment = initial_plan.assignment, updaters = my_updaters) # ADD CONSTRAINTS popbound = within_percent_of_ideal_population(initial_partition, 0.1) # ########Setup Proposal ideal_population = sum(initial_partition["population"].values()) / len(initial_partition) tree_proposal = partial( recom, pop_col=tot_pop_col, pop_target=ideal_population, epsilon=pop_tol, node_repeats=1, ) # ######BUILD MARKOV CHAINS recom_chain = MarkovChain( tree_proposal, Validator([popbound]), accept=always_accept, initial_state=initial_partition, total_steps=num_steps, ) cut_edges_list = [] min_seats_list = [] min_percents_list = [] for part in recom_chain: cut_edges_list.append(len(part["cut_edges"])) min_percents_list.append(part["maj-min"].percents("min")) min_seats = (np.array(part["maj-min"].percents("min")) >= min_win_thresh).sum() min_seats_list.append(min_seats) return [cut_edges_list,min_seats_list,min_percents_list]
for n in graph.nodes(): graph.node[n][pop_col] = int(graph.node[n][pop_col]) totpop += graph.node[n][pop_col] proposal = partial( recom, pop_col=pop_col, pop_target=totpop/num_districts, epsilon=0.02, node_repeats=1 ) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 2 * len(starting_partition["cut_edges"]) ) chain = MarkovChain( proposal, constraints=[ constraints.within_percent_of_ideal_population(starting_partition, 0.05),compactness_bound #constraints.single_flip_contiguous#no_more_discontiguous ], accept=accept.always_accept, initial_state=starting_partition, total_steps=5000 ) cuts = [] splittings = [] moon_metric_scores = [] #CHANGE t = 0 for part in chain: cuts.append(len(part["cut_edges"]))
def run_experiment(bases=[2 * 2.63815853], pops=[.1], time_between_outputs=10000, total_run_length=100000000000000): mu = 2.63815853 subsequence_step_size = 10000 balances_burn_in = 1000000 #ignore the first 10000 balances # creating the boundary figure plot plt.figure() fig = plt.figure() #fig_intervals = plt.figure() #ax2=fig.add_axes([0,0,1,1]) ax = plt.subplot(111, projection='polar') #ax.set_axis_off() #ax.xaxis.set_ticklabels([]) ax.yaxis.set_ticklabels([]) for pop1 in pops: for base in bases: for alignment in [1]: gn = 20 k = 2 ns = 120 p = .5 graph = nx.grid_graph([k * gn, k * gn]) ########## BUILD ASSIGNMENT #cddict = {x: int(x[0]/gn) for x in graph.nodes()} cddict = {x: 1 - 2 * int(x[0] / gn) for x in graph.nodes()} for n in graph.nodes(): if alignment == 0: if n[0] > 19: cddict[n] = 1 else: cddict[n] = -1 elif alignment == 1: if n[1] > 19: cddict[n] = 1 else: cddict[n] = -1 elif alignment == 2: if n[0] > n[1]: cddict[n] = 1 elif n[0] == n[1] and n[0] > 19: cddict[n] = 1 else: cddict[n] = -1 elif alignment == 10: #This is for debugging the case of reaching trivial partitions. if n[0] == 10 and n[1] == 10: cddict[n] = 1 else: cddict[n] = -1 for n in graph.nodes(): graph.nodes[n]["population"] = 1 graph.nodes[n]["part_sum"] = cddict[n] graph.nodes[n]["last_flipped"] = 0 graph.nodes[n]["num_flips"] = 0 if random.random() < p: graph.nodes[n]["pink"] = 1 graph.nodes[n]["purple"] = 0 else: graph.nodes[n]["pink"] = 0 graph.nodes[n]["purple"] = 1 if 0 in n or k * gn - 1 in n: graph.nodes[n]["boundary_node"] = True graph.nodes[n]["boundary_perim"] = 1 else: graph.nodes[n]["boundary_node"] = False #graph.add_edges_from([((0,1),(1,0)), ((0,38),(1,39)), ((38,0),(39,1)), ((38,39),(39,38))]) for edge in graph.edges(): graph[edge[0]][edge[1]]['cut_times'] = 0 #this part adds queen adjacency #for i in range(k*gn-1): # for j in range(k*gn): # if j<(k*gn-1): # graph.add_edge((i,j),(i+1,j+1)) # graph[(i,j)][(i+1,j+1)]["shared_perim"]=0 # if j >0: # graph.add_edge((i,j),(i+1,j-1)) # graph[(i,j)][(i+1,j-1)]["shared_perim"]=0 #graph.remove_nodes_from([(0,0),(0,39),(39,0),(39,39)]) #del cddict[(0,0)] #del cddict[(0,39)] # cddict[(39,0)] #del cddict[(39,39)] ######PLOT GRIDS """ plt.figure() nx.draw(graph, pos = {x:x for x in graph.nodes()} ,node_size = ns, node_shape ='s') plt.show() cdict = {1:'pink',0:'purple'} plt.figure() nx.draw(graph, pos = {x:x for x in graph.nodes()}, node_color = [cdict[graph.nodes[x]["pink"]] for x in graph.nodes()],node_size = ns, node_shape ='s' ) plt.show() plt.figure() nx.draw(graph, pos = {x:x for x in graph.nodes()}, node_color = [cddict[x] for x in graph.nodes()] ,node_size = ns, node_shape ='s',cmap = 'tab20') plt.show() """ ####CONFIGURE UPDATERS def new_base(partition): return base def step_num(partition): parent = partition.parent if not parent: return 0 return parent["step_num"] + 1 bnodes = [ x for x in graph.nodes() if graph.nodes[x]["boundary_node"] == 1 ] def bnodes_p(partition): return [ x for x in graph.nodes() if graph.nodes[x]["boundary_node"] == 1 ] updaters = { 'population': Tally('population'), "boundary": bnodes_p, #"slope": boundary_slope, 'cut_edges': cut_edges, 'step_num': step_num, 'b_nodes': b_nodes_bi, 'base': new_base, 'geom': geom_wait, #"Pink-Purple": Election("Pink-Purple", {"Pink":"pink","Purple":"purple"}) } balances = [] #########BUILD PARTITION grid_partition = Partition(graph, assignment=cddict, updaters=updaters) #ADD CONSTRAINTS popbound = within_percent_of_ideal_population( grid_partition, pop1) #plt.figure() #nx.draw(graph, pos = {x:x for x in graph.nodes()}, node_color = [dict(grid_partition.assignment)[x] for x in graph.nodes()] ,node_size = ns, node_shape ='s',cmap = 'tab20') #plt.savefig("./plots/"+str(alignment)+"B"+str(int(100*base))+"P"+str(int(100*pop1))+"start.png") #plt.close() #########Setup Proposal ideal_population = sum(grid_partition["population"].values() ) / len(grid_partition) tree_proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=pop1, node_repeats=1) #######BUILD MARKOV CHAINS exp_chain = MarkovChain( slow_reversible_propose_bi, Validator([ single_flip_contiguous, popbound #,boundary_condition ]), accept=cut_accept, initial_state=grid_partition, total_steps=total_run_length) #########Run MARKOV CHAINS rsw = [] rmm = [] reg = [] rce = [] rbn = [] waits = [] slopes = [] angles = [] angles_safe = [] ends_vectors_normalized = LinkedList() ends_vectors_normalized_bloated = LinkedList() import time st = time.time() total_waits = 0 last_total_waits = 0 t = 0 subsequence_timer = 0 balances = {} for b in np.linspace(0, 2, 100001): balances[int(b * 100) / 100] = 0 #first_partition = True for part in exp_chain: rce.append(len(part["cut_edges"])) wait_time_rv = part.geom waits.append(wait_time_rv) total_waits += wait_time_rv rbn.append(len(list(part["b_nodes"]))) if total_waits > subsequence_timer + subsequence_step_size: last_total_waits = total_waits ends = boundary_ends(part) if len(ends) == 2: ends_vector = np.asarray(ends[1]) - np.asarray( ends[0]) ends_vector_normalized = ends_vector / np.linalg.norm( ends_vector) #if first_partition == True: # ends_vectors_normalized.last_vector = ends_vector_normalized # first_partition = False if ends_vectors_normalized.last: # We choose the vector that preserves continuity # previous_angle = ends_vectors_normalized.last_value() previous = ends_vectors_normalized.last_vector d_previous = np.linalg.norm( previous - ends_vector_normalized) d_previous_neg = np.linalg.norm( previous + ends_vector_normalized) if d_previous < d_previous_neg: continuous_lift = ends_vector_normalized else: continuous_lift = -1 * ends_vector_normalized #print(previous, ends_vector_normalized) else: continuous_lift = ends_vector_normalized # *random.choice([-1,1]) # just to debias it, in the regime of very unbalanced partitions # that touch the empty partition frequently else: continuous_lift = [0, 0] ##############For Debugging############# ''' if total_waits > subsequence_timer + subsequence_step_size: last_total_waits = total_waits ends = boundary_ends(part) if ends: ends_vector = np.asarray(ends[1]) - np.asarray(ends[0]) ends_vector_normalized = ends_vector / np.linalg.norm(ends_vector) if ends_vectors_normalized_bloated.last: # We choose the vector that preserves continuity previous = ends_vectors_normalized_bloated.last_value() d_previous = np.linalg.norm( ends_vector_normalized - previous) d_previous_neg = np.linalg.norm( ends_vector_normalized + previous ) if d_previous < d_previous_neg: continuous_lift_bloated = ends_vector_normalized else: continuous_lift_bloated = -1* ends_vector_normalized else: continuous_lift_bloated = ends_vector_normalized # *random.choice([-1,1]) # just to debias it, in the regime of very unbalanced partitions # that touch the empty partition frequently else: continuous_lift_bloated = [0,0] ''' ################ # Pop balance stuff: left_pop, right_pop = part["population"].values() ideal_population = (left_pop + right_pop) / 2 left_bal = (left_pop / ideal_population) right_bal = (right_pop / ideal_population) while subsequence_timer < total_waits: subsequence_timer += subsequence_step_size if (continuous_lift == np.asarray([0, 0])).all(): lifted_angle = False print("false") draw_other_plots(balances, graph, alignment, "NonSimplyConnected", base, pop1, part, ns) #Flag to hold the exceptional case of the boundary vanishing else: lifted_angle = np.arctan2( continuous_lift[1], continuous_lift[0]) #+ np.pi ends_vectors_normalized.last_vector = continuous_lift ends_vectors_normalized.append(lifted_angle) if subsequence_timer > balances_burn_in: left_bal_rounded = int(left_bal * 100) / 100 right_bal_rounded = int(right_bal * 100) / 100 balances[ left_bal_rounded] += 1 #left_bal_rounded balances[ right_bal_rounded] += 1 # right_bal_rounded #NB wait times are accounted for by the while loops for edge in part["cut_edges"]: graph[edge[0]][edge[1]]["cut_times"] += wait_time_rv #print(graph[edge[0]][edge[1]]["cut_times"]) if part.flips is not None: f = list(part.flips.keys())[0] graph.nodes[f]["part_sum"] = graph.nodes[f][ "part_sum"] - part.assignment[f] * ( total_waits - graph.nodes[f]["last_flipped"]) graph.nodes[f]["last_flipped"] = total_waits graph.nodes[f]["num_flips"] = graph.nodes[f][ "num_flips"] + wait_time_rv t += 1 if t % time_between_outputs == 0: #ends_vectors_normalized[1:] #Remove the first one because it will overlap with last one of previous dump identifier_string = "state_after_num_steps" + str( t) + "and_time" + str(st - time.time()) #print("finished no", st-time.time()) with open( "./plots/" + str(alignment) + "B" + str(int(100 * base)) + "P" + str(int(100 * pop1)) + "wait.txt", 'w') as wfile: wfile.write(str(sum(waits))) #with open("./plots/"+str(alignment)+"B"+str(int(100*base))+"P"+str(int(100*pop1)) + "ends_vectors.txt",'w') as wfile: # wfile.write(str(ends_vectors_normalized)) #with open("./plots/"+str(alignment)+"B"+str(int(100*base))+"P"+str(int(100*pop1)) + "ends_vectors.pkl",'wb') as wfile: # pickle.dump(ends_vectors_normalized, wfile) with open( "./plots/" + str(alignment) + "B" + str(int(100 * base)) + "P" + str(int(100 * pop1)) + "balances.txt", 'w') as wfile: wfile.write(str(balances)) with open( "./plots/" + str(alignment) + "B" + str(int(100 * base)) + "P" + str(int(100 * pop1)) + "balances.pkl", 'wb') as wfile: pickle.dump(balances, wfile) for n in graph.nodes(): if graph.nodes[n]["last_flipped"] == 0: graph.nodes[n][ "part_sum"] = total_waits * part.assignment[ n] graph.nodes[n]["lognum_flips"] = math.log( graph.nodes[n]["num_flips"] + 1) total_part_sum = 0 for n in graph.nodes(): total_part_sum += graph.nodes[n]["part_sum"] for n in graph.nodes(): if total_part_sum != 0: graph.nodes[n][ "normalized_part_sum"] = graph.nodes[n][ "part_sum"] / total_part_sum else: graph.nodes[n]["normalized_part_sum"] = 0 #print(len(rsw[-1])) #print(graph[(1,0)][(0,1)]["cut_times"]) print("creating boundary plot, ", time.time()) max_time = ends_vectors_normalized.last.end_time non_simply_connected_intervals = [ [x.start_time, x.end_time] for x in ends_vectors_normalized if type(x.data) == bool ] for x in ends_vectors_normalized: if type(x.data) != bool: #times = np.linspace(x.start_time, x.end_time, 100) #times.append(x.end_time) times = [x.start_time, x.end_time] angles = [x.data] * len(times) plt.polar(angles, times, lw=.1, color='b') next_point = x.next #''' if next_point != None: if type(next_point.data) != bool: if np.abs( (x.data - next_point.data)) % ( 2 * np.pi) < .1: # added that last if to avoid # the big jumps that happen with # small size subcritical plt.polar( [x.data, next_point.data], [ x.end_time, next_point.start_time ], lw=.1, color='b') #''' # Create the regular segments corresponding to time ''' for k in range(11): plt.polar ( np.arange(0, (2 * np.pi), 0.01), [int(max_time/10) * k ] * len( np.arange(0, (2 * np.pi), 0.01)), lw = .2, color = 'g' ) ''' # Create the intervals representing when the partition is null. # Removing these might be just as good, and a little cleaner. #''' for interval in non_simply_connected_intervals: start = interval[0] end = interval[1] for s in np.linspace(start, end, 10): plt.polar( np.arange(0, (2 * np.pi), 0.01), s * np.ones( len(np.arange(0, (2 * np.pi), 0.01))), lw=.3, color='r') #''' #plt.savefig("./plots/"+str(alignment)+"B"+str(int(100*base))+"P"+str(int(100*pop1)) + str("proposals_") + str( max_time * subsequence_step_size ) + "boundary_slope.svg") plt.savefig("./plots/" + str(alignment) + "B" + str(int(100 * base)) + "P" + str(int(100 * pop1)) + str("proposals_") + identifier_string + "boundary_slope.png", dpi=500) # now clear the ends vectors list last = ends_vectors_normalized.last last_non_zero = ends_vectors_normalized.last_non_zero last_vector = ends_vectors_normalized.last_vector # Explicit Garbage collection https://stackoverflow.com/questions/1316767/how-can-i-explicitly-free-memory-in-python print( "finished boundary plot, doing garbage collection", time.time()) del ends_vectors_normalized gc.collect() ends_vectors_normalized = LinkedList() ends_vectors_normalized.head = last ends_vectors_normalized.last = last ends_vectors_normalized.last_non_zero = last_non_zero # can be ahead of head... ends_vectors_normalized.last_vector = last_vector #print(last) print("drawing other plots", time.time()) draw_other_plots(balances, graph, alignment, identifier_string, base, pop1, part, ns) print("finished drawing other plots, ", time.time())
init_partition = Partition(graph, assignment=cddict, updaters=or_updaters) ## Setup chain proposal = partial(recom, pop_col=POP_COL, pop_target=ideal_pop, epsilon=EPS, node_repeats=1) compactness_bound = constraints.UpperBound(lambda p: len(p["cut_edges"]), 2*len(init_partition["cut_edges"])) chain = MarkovChain( proposal, constraints=[ constraints.within_percent_of_ideal_population(init_partition, EPS), compactness_bound], accept=accept.always_accept, initial_state=init_partition, total_steps=ITERS) ## Run chain print("Starting Markov Chain") def init_election_results(elections): data = {"cutedges": np.zeros(ITERS)} parts = {"samples": [], "compact": []} for election in elections: name = election.lower()
# election_updaters=dict() # election_updaters["Pink-Purple"]=Election("Pink-Purple",{"Pink":"pink","Purple":"purple"},alias="Pink-Purple") gp3 = Partition(graph, assignment=cddict, updaters=updaters) ideal_population = sum(gp3["population"].values()) / len(gp3) proposal = partial( recom, pop_col="population", pop_target=ideal_population, epsilon=0.02, node_repeats=1, ) popbound = within_percent_of_ideal_population(gp3, 0.05) g3chain = MarkovChain( proposal, # propose_chunk_flip, Validator([popbound]), accept=always_accept, initial_state=gp3, total_steps=100, ) t = 0 for part3 in g3chain: t += 1 print("finished tree walk")
def main(): """ Contains majority of expermiment. Runs a markov chain on the state dual graph, determining how the distribution is affected to changes in the state dual graph. Raises: RuntimeError if PROPOSAL_TYPE of config file is neither 'sierpinski' nor 'convex' """ output_directory = createDirectory(config) epsilon = config["epsilon"] k = config["NUM_DISTRICTS"] updaters = {'population': Tally('population'), 'cut_edges': cut_edges, "Blue-Red": Election("Blue-Red", {"Blue": "blue", "Red": "red"}) } graph, dual = preprocessing(output_directory) cddict = {x: int(x[0] / gn) for x in graph.nodes()} plt.figure() nx.draw( graph, pos={x: x for x in graph.nodes()}, node_color=[cddict[x] for x in graph.nodes()], node_size=ns, node_shape="s", cmap="tab20", ) plt.show() ideal_population = sum(graph.nodes[x]["population"] for x in graph.nodes()) / k faces = graph.graph["faces"] faces = list(faces) square_faces = [face for face in faces if len(face) == 4] totpop = 0 for node in graph.nodes(): totpop += int(graph.nodes[node]['population']) # length of chain steps = config["CHAIN_STEPS"] # length of each gerrychain step gerrychain_steps = config["GERRYCHAIN_STEPS"] # faces that are currently modified. Code maintains list of modified faces, and at each step selects a face. if face is already in list, # the face is un-modified, and if it is not, the face is modified by the specified proposal type. special_faces = set([face for face in square_faces if np.random.uniform(0, 1) < .5]) #chain_output = {'dem_seat_data': [], 'rep_seat_data': [], 'score': []} chain_output = defaultdict(list) # start with small score to move in right direction print("Choosing", math.floor(len(faces) * config['PERCENT_FACES']), "faces of the dual graph at each step") max_score = -math.inf # this is the main markov chain for i in tqdm.tqdm(range(1, steps + 1), ncols=100, desc="Chain Progress"): special_faces_proposal = copy.deepcopy(special_faces) proposal_graph = copy.deepcopy(graph) if (config["PROPOSAL_TYPE"] == "sierpinski"): for i in range(math.floor(len(faces) * config['PERCENT_FACES'])): face = random.choice(faces) ##Makes the Markov chain lazy -- this just makes the chain aperiodic. if random.random() > .5: if not (face in special_faces_proposal): special_faces_proposal.append(face) else: special_faces_proposal.remove(face) chain_on_subsets_of_faces.face_sierpinski_mesh(proposal_graph, special_faces_proposal) elif (config["PROPOSAL_TYPE"] == "add_edge"): for j in range(math.floor(len(square_faces) * config['PERCENT_FACES'])): face = random.choice(square_faces) ##Makes the Markov chain lazy -- this just makes the chain aperiodic. if random.random() > .5: if not (face in special_faces_proposal): special_faces_proposal.add(face) else: special_faces_proposal.remove(face) chain_on_subsets_of_faces.add_edge_proposal(proposal_graph, special_faces_proposal) else: raise RuntimeError('PROPOSAL TYPE must be "sierpinski" or "convex"') initial_partition = Partition(proposal_graph, assignment=cddict, updaters=updaters) # Sets up Markov chain popbound = within_percent_of_ideal_population(initial_partition, epsilon) tree_proposal = partial(recom, pop_col=config['POP_COL'], pop_target=ideal_population, epsilon=epsilon, node_repeats=1) # make new function -- this computes the energy of the current map exp_chain = MarkovChain(tree_proposal, Validator([popbound]), accept=accept.always_accept, initial_state=initial_partition, total_steps=gerrychain_steps) seats_won_for_republicans = [] seats_won_for_democrats = [] for part in exp_chain: for u, v in part["cut_edges"]: proposal_graph[u][v]["cut_times"] += 1 rep_seats_won = 0 dem_seats_won = 0 for j in range(k): rep_votes = 0 dem_votes = 0 for n in graph.nodes(): if part.assignment[n] == j: rep_votes += graph.nodes[n]["blue"] dem_votes += graph.nodes[n]["red"] total_seats_dem = int(dem_votes > rep_votes) total_seats_rep = int(rep_votes > dem_votes) rep_seats_won += total_seats_rep dem_seats_won += total_seats_dem seats_won_for_republicans.append(rep_seats_won) seats_won_for_democrats.append(dem_seats_won) seat_score = statistics.mean(seats_won_for_republicans) # implement mattingly simulated annealing scheme, from evaluating partisan gerrymandering in wisconsin if i <= math.floor(steps * .67): beta = i / math.floor(steps * .67) else: beta = (i / math.floor(steps * 0.67)) * 100 temperature = 1 / (beta) weight_seats = 1 weight_flips = -.2 config['PERCENT_FACES'] = config['PERCENT_FACES'] # what is this? flip_score = len(special_faces) # This is the number of edges being swapped score = weight_seats * seat_score + weight_flips * flip_score ##This is the acceptance step of the Metropolis-Hasting's algorithm. Specifically, rand < min(1, P(x')/P(x)), where P is the energy and x' is proposed state # if the acceptance criteria is met or if it is the first step of the chain def update_outputs(): chain_output['dem_seat_data'].append(seats_won_for_democrats) chain_output['rep_seat_data'].append(seats_won_for_republicans) chain_output['score'].append(score) chain_output['seat_score'].append(seat_score) chain_output['flip_score'].append(flip_score) def propagate_outputs(): for key in chain_output.keys(): chain_output[key].append(chain_output[key][-1]) if i == 1: update_outputs() special_faces = copy.deepcopy(special_faces_proposal) # this is the simplified form of the acceptance criteria, for intuitive purposes # exp((1/temperature) ( proposal_score - previous_score)) elif np.random.uniform(0, 1) < (math.exp(score) / math.exp(chain_output['score'][-1])) ** (1 / temperature): update_outputs() special_faces = copy.deepcopy(special_faces_proposal) else: propagate_outputs() # if score is highest seen, save map. if score > max_score: # todo: all graph coloring for graph changes that produced this score nx.write_gpickle(proposal_graph, "obj/graphs/"+str(score)+'sc_'+str(config['CHAIN_STEPS'])+'mcs_'+ str(config["GERRYCHAIN_STEPS"])+ "gcs_" + config['PROPOSAL_TYPE']+'_'+ str(len(special_faces)), pickle.HIGHEST_PROTOCOL) save_obj(special_faces, output_directory, 'north_carolina_highest_found') nx.write_gpickle(proposal_graph, output_directory + '/' + "max_score", pickle.HIGHEST_PROTOCOL) f=open(output_directory + "/max_score_data.txt","w+") f.write("maximum score: " + str(score) + "\n" + "edges changed: " + str(len(special_faces)) + "\n" + "Seat Score: " + str(seat_score)) save_obj(special_faces, output_directory + '/', "special_faces") max_score = score plt.plot(range(len(chain_output['score'])), chain_output['score']) plt.xlabel("Meta-Chain Step") plt.ylabel("Score") plt.show() plt.close() plt.plot(range(len(chain_output['seat_score'])), chain_output['seat_score']) plt.xlabel("Meta-Chain Step") plt.ylabel("Number of average seats republicans won") plt.show() plt.close()
def produce_sample(graph, k, tag, sample_size = 500, chaintype='tree'): #Samples k partitions of the graph, stores the cut edges and records them graphically #Also stores vote histograms, and returns most extreme partitions. print("producing sample") updaters = {'population': Tally('population'), 'cut_edges': cut_edges, 'step_num': step_num, } for edge in graph.edges(): graph[edge[0]][edge[1]]['cut_times'] = 0 for n in graph.nodes(): #graph.nodes[n]["population"] = 1 #graph.nodes[n]["POP10"] #This is something gerrychain will refer to for checking population balance graph.nodes[n]["last_flipped"] = 0 graph.nodes[n]["num_flips"] = 0 print("set up chain") ideal_population= sum( graph.nodes[x]["population"] for x in graph.nodes())/k initial_partition = Partition(graph, assignment='part', updaters=updaters) pop1 = .05 print("popbound") popbound = within_percent_of_ideal_population(initial_partition, pop1) if chaintype == "tree": tree_proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=pop1, node_repeats=1, method=my_mst_bipartition_tree_random) elif chaintype == "uniform_tree": tree_proposal = partial(recom, pop_col="population", pop_target=ideal_population, epsilon=pop1, node_repeats=1, method=my_uu_bipartition_tree_random) else: print("Chaintype used: ", chaintype) raise RuntimeError("Chaintype not recongized. Use 'tree' or 'uniform_tree' instead") exp_chain = MarkovChain(tree_proposal, Validator([popbound]), accept=always_true, initial_state=initial_partition, total_steps=sample_size) z = 0 num_cuts_list = [] seats_won_table = [] best_left = np.inf best_right = -np.inf print("begin chain") for part in exp_chain: z += 1 if z % 100 == 0: print("step ", z) seats_won = 0 for edge in part["cut_edges"]: graph[edge[0]][edge[1]]["cut_times"] += 1 for i in range(k): rep_votes = 0 dem_votes = 0 for n in graph.nodes(): if part.assignment[n] == i: rep_votes += graph.nodes[n]["EL16G_PR_R"] dem_votes += graph.nodes[n]["EL16G_PR_D"] total_seats = int(rep_votes > dem_votes) seats_won += total_seats) #total seats won by rep seats_won_table.append(seats_won) # save gerrymandered partitionss if seats_won < best_left: best_left = seats_won #left_mander = copy.deepcopy(part.parts) if seats_won > best_right: best_right = seats_won #right_mander = copy.deepcopy(part.parts) #print("finished round" print("max", best_right, "min:", best_left) plt.figure() plt.hist(seats_won_table, bins = 10) name = "./plots/seats_histogram_metamander" + tag +".png" plt.savefig(name) plt.close() edge_colors = [graph[edge[0]][edge[1]]["cut_times"] for edge in graph.edges()] pos=nx.get_node_attributes(graph, 'pos') plt.figure() nx.draw(graph, pos=nx.get_node_attributes(graph, 'pos'), node_size=1, edge_color=edge_colors, node_shape='s', cmap='magma', width=3)
updaters = { "population": Tally("population"), "cut_edges": cut_edges, "step_num": step_num, "Pink-Purple": Election("Pink-Purple", {"Pink": "pink", "Purple": "purple"}), } # ########BUILD PARTITION grid_partition = Partition(graph, assignment=cddict, updaters=updaters) # ADD CONSTRAINTS popbound = within_percent_of_ideal_population(grid_partition, 0.1) # ########Setup Proposal ideal_population = sum(grid_partition["population"].values()) / len(grid_partition) tree_proposal = partial( recom, pop_col="population", pop_target=ideal_population, epsilon=0.05, node_repeats=1, ) # ######BUILD MARKOV CHAINS
def run_chain(init_part, chaintype, length, ideal_population, id, tag): """Runs a Recom chain, and saves the seats won histogram to a file and returns the most Gerrymandered plans for both PartyA and PartyB Args: init_part (Gerrychain Partition): initial partition of chain chaintype (String): indicates which proposal to be used to generate spanning tree during Recom. Must be either "tree" or "uniform_tree" length (int): total steps of chain id (String): id of experiment, used when printing progress tag (String): tag added to filename to identify run Raises: RuntimeError: If chaintype is not "tree" nor 'uniform_tree" Returns: list of partitions generated by chain """ graph = init_part.graph for edge in graph.edges(): graph.edges[edge]['cut_times'] = 0 graph.edges[edge]['sibling_cuts'] = 0 if 'siblings' not in graph.edges[edge]: graph.edges[edge]['siblings'] = tuple([edge]) popbound = within_percent_of_ideal_population(init_part, config['EPSILON']) # Determine proposal for generating spanning tree based upon parameter if chaintype == "tree": tree_proposal = partial(recom, pop_col=config["POP_COL"], pop_target=ideal_population, epsilon=config['EPSILON'], node_repeats=config['NODE_REPEATS'], method=facefinder.my_mst_bipartition_tree_random) elif chaintype == "uniform_tree": tree_proposal = partial(recom, pop_col=config["POP_COL"], pop_target=ideal_population, epsilon=config['EPSILON'], node_repeats=config['NODE_REPEATS'], method=facefinder.my_uu_bipartition_tree_random) else: print("Chaintype used: ", chaintype) raise RuntimeError("Chaintype not recognized. Use 'tree' or 'uniform_tree' instead") # Chain to be run chain = MarkovChain(tree_proposal, Validator([popbound]), accept=accept.always_accept, initial_state=init_part, total_steps=length) electionDict = { 'seats' : (lambda x: x[config['ELECTION_NAME']].seats('PartyA')), 'won' : (lambda x: x[config['ELECTION_NAME']].seats('PartyA')), 'efficiency_gap' : (lambda x: x[config['ELECTION_NAME']].efficiency_gap()), 'mean_median' : (lambda x: x[config['ELECTION_NAME']].mean_median()), 'mean_thirdian' : (lambda x: x[config['ELECTION_NAME']].mean_thirdian()), 'partisan_bias' : (lambda x: x[config['ELECTION_NAME']].partisan_bias()), 'partisan_gini' : (lambda x: x[config['ELECTION_NAME']].partisan_gini()) } # Run chain, save each desired statistic, and keep track of cuts. Save most # left gerrymandered partition statistics = {statistic : [] for statistic in config['ELECTION_STATISTICS']} # Value of a partition is determined by each of the Gerry Statistics. # Lexicographical ordering is used, such that if two partitions have the same # value under the first Gerry Statistic, then the second is used as a tie # breaker, and so on. leftManderVal = [float('inf')] * len(config['GERRY_STATISTICS']) leftMander = None for i, partition in enumerate(chain): for edge in partition["cut_edges"]: graph.edges[edge]['cut_times'] += 1 for sibling in graph.edges[edge]['siblings']: graph.edges[sibling]['sibling_cuts'] += 1 # Save statistics of partition for statistic in config['ELECTION_STATISTICS']: statistics[statistic].append(electionDict[statistic](partition)) # Update left mander if applicable curPartVal = [electionDict[statistic](partition) for statistic in config['GERRY_STATISTICS']] if curPartVal < leftManderVal: leftManderVal = curPartVal leftMander = partition if i % 500 == 0: print('{}: {}'.format(id, i)) saveRunStatistics(statistics, tag) # Return average number of seats return sum(statistics['seats']) / len(statistics['seats'])