"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])
Esempio n. 3
0
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
Esempio n. 4
0
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(
Esempio n. 6
0
                                         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
Esempio n. 10
0
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)
Esempio n. 11
0
    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",\
Esempio n. 12
0
    '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)
Esempio n. 13
0
    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
Esempio n. 14
0
            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
Esempio n. 15
0
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")
Esempio n. 17
0
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]
Esempio n. 19
0
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")
Esempio n. 23
0
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()
Esempio n. 24
0
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)
Esempio n. 25
0

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

Esempio n. 26
0
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'])