def getpart(i): humanlist = ['judge', 'oldplan', 'newplan'] if i < 3: ass = Partition(graph, humanlist[i], my_updaters).assignment to0index = {x: i for i, x in enumerate(ass.parts)} return Partition(graph, {n: to0index[ass[n]] for n in graph.nodes}, my_updaters) else: ass = get_partition(files[240 * (i)], fid_to_geoid_file, geoidcol) return Partition(graph, ass, my_updaters)
def test_uses_graph_geometries_by_default(self, geodataframe): mock_plot = MagicMock() gp.GeoDataFrame.plot = mock_plot graph = Graph.from_geodataframe(geodataframe) partition = Partition(graph=graph, assignment={node: 0 for node in graph}) partition.plot() assert mock_plot.call_count == 1
def test_data_tally_works_as_an_updater(three_by_three_grid): assignment = random_assignment(three_by_three_grid, 4) data = {node: random.randint(1, 100) for node in three_by_three_grid.nodes} parts = tuple(set(assignment.values())) updaters = {"tally": DataTally(data, alias="tally")} partition = Partition(three_by_three_grid, assignment, updaters) flip = {random.choice(list(partition.graph.nodes)): random.choice(parts)} new_partition = partition.flip(flip) assert new_partition["tally"]
def test_data_tally_gives_expected_value(three_by_three_grid): first_node = next(iter(three_by_three_grid.nodes)) assignment = {node: 1 for node in three_by_three_grid.nodes} assignment[first_node] = 2 data = {node: 1 for node in three_by_three_grid} updaters = {"tally": DataTally(data, alias="tally")} partition = Partition(three_by_three_grid, assignment, updaters) flip = {first_node: 1} new_partition = partition.flip(flip) assert new_partition["tally"][1] == partition["tally"][1] + 1
def test_implementation_of_cut_edges_matches_naive_method(three_by_three_grid): graph = three_by_three_grid assignment = {0: 1, 1: 1, 2: 2, 3: 1, 4: 1, 5: 2, 6: 2, 7: 2, 8: 2} partition = Partition(graph, assignment, {"cut_edges": cut_edges}) flip = {4: 2} new_partition = Partition(parent=partition, flips=flip) result = cut_edges(new_partition) naive_cut_edges = { edge for edge in graph.edges if new_partition.crosses_parts(edge) } assert edge_set_equal(result, naive_cut_edges)
def split_partition(graph_with_counties): partition = Partition( graph_with_counties, assignment={ 0: 1, 1: 2, 2: 3, 3: 1, 4: 2, 5: 3, 6: 1, 7: 2, 8: 3 }, updaters={ "cut_edges": cut_edges, "splits": LocalitySplits("splittings", "county", "pop", [ 'num_parts', 'num_pieces', 'naked_boundary', 'shannon_entropy', 'power_entropy', 'symmetric_entropy', 'num_split_localities' ]) }, ) return partition
def test_no_splits(self, graph_with_counties): partition = Partition(graph_with_counties, assignment="county") result = compute_county_splits(partition, "county", None) for splits_info in result.values(): assert splits_info.split == CountySplit.NOT_SPLIT
def split_partition(graph_with_counties): partition = Partition( graph_with_counties, assignment={0: 1, 1: 1, 2: 1, 3: 1, 4: 2, 5: 2, 6: 3, 7: 3, 8: 3}, updaters={"splits": county_splits("splits", "county")}, ) return partition
def buildPartition(graph, mean): """ The function build 2 partition based on x coordinate value Parameters: graph (Graph): The given graph represent state information mean (int): the value to determine partition Returns: Partition: the partitions of the graph based on mean value """ assignment = {} # assign node into different partition based on x coordinate for x in graph.node(): if graph.node[x]['C_X'] < mean: assignment[x] = -1 else: assignment[x] = 1 updaters = { 'population': Tally('population'), "boundary": bnodes_p, 'cut_edges': cut_edges, 'step_num': step_num, 'b_nodes': b_nodes_bi, 'base': new_base, 'geom': geom_wait, } grid_partition = Partition(graph, assignment=assignment, updaters=updaters) return grid_partition
def test_cut_edges_doesnt_duplicate_edges_with_different_order_of_nodes( three_by_three_grid): graph = three_by_three_grid assignment = {0: 1, 1: 1, 2: 2, 3: 1, 4: 1, 5: 2, 6: 2, 7: 2, 8: 2} partition = Partition(graph, assignment, {"cut_edges": cut_edges}) # 112 111 # 112 -> 121 # 222 222 flip = {4: 2, 2: 1, 5: 1} new_partition = Partition(parent=partition, flips=flip) result = new_partition["cut_edges"] for edge in result: assert (edge[1], edge[0]) not in result
def test_from_file_and_then_to_json_with_Partition(shapefile, target_file): partition = Partition.from_file(shapefile, assignment="data") # Even the geometry column is copied to the graph assert all("geometry" in node_data for node_data in partition.graph.nodes.values()) partition.to_json(target_file)
def run_simple2(graph): election = Election("2014 Senate", { "Democratic": "sen_blue", "Republican": "sen_red" }, alias="2014_Senate") initial_partition = Partition(graph, assignment="con_distri", updaters={ "2014_Senate": election, "population": Tally("population", alias="population"), "exterior_boundaries": exterior_boundaries, "interior_boundaries": interior_boundaries, "perimeter": perimeter, "boundary_nodes": boundary_nodes, "cut_edges": cut_edges, "area": Tally("area", alias="area"), "cut_edges_by_part": cut_edges_by_part, "county_split": county_splits('county_split', "COUNTY_ID"), "black_population": Tally("black_pop", alias="black_population"), }) districts_within_tolerance_2 = lambda part: districts_within_tolerance( part, 'population', 0.3) is_valid = Validator( [single_flip_contiguous, districts_within_tolerance_2]) chain = MarkovChain( proposal=propose_random_flip, is_valid=is_valid, accept=always_accept, # accept=accept, #THe acceptance criteria is what needs to be defined ourselves - to match the paper initial_state=initial_partition, total_steps=30, ) efficiency_gaps = [] wins = [] for partition in chain: if (hasattr(partition, 'accepted') and partition.accepted): efficiency_gaps.append( gerrychain.scores.efficiency_gap(partition["2014_Senate"])) wins.append(partition["2014_Senate"].wins("Democratic")) return (efficiency_gaps, wins, partition)
def partition(graph): return Partition( graph, {0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2, 6: 3, 7: 3, 8: 3}, { "cut_edges": updaters.cut_edges, "cut_edges_by_part": updaters.cut_edges_by_part, }, )
def test_cut_edges_by_part_doesnt_duplicate_edges_with_opposite_order_of_nodes( three_by_three_grid): graph = three_by_three_grid assignment = {0: 1, 1: 1, 2: 2, 3: 1, 4: 1, 5: 2, 6: 2, 7: 2, 8: 2} updaters = {"cut_edges_by_part": cut_edges_by_part} partition = Partition(graph, assignment, updaters) # 112 111 # 112 -> 121 # 222 222 flip = {4: 2, 2: 1, 5: 1} new_partition = Partition(parent=partition, flips=flip) result = new_partition["cut_edges_by_part"] for part in result: for edge in result[part]: assert (edge[1], edge[0]) not in result
def test_cut_edges_can_handle_multiple_flips(three_by_three_grid): graph = three_by_three_grid assignment = {0: 1, 1: 1, 2: 2, 3: 1, 4: 1, 5: 2, 6: 2, 7: 2, 8: 2} partition = Partition(graph, assignment, {"cut_edges": cut_edges}) # 112 111 # 112 -> 121 # 222 222 flip = {4: 2, 2: 1, 5: 1} new_partition = Partition(parent=partition, flips=flip) result = new_partition["cut_edges"] naive_cut_edges = { tuple(sorted(edge)) for edge in graph.edges if new_partition.crosses_parts(edge) } assert result == naive_cut_edges
def test_data_tally_mimics_old_tally_usage(graph_with_random_data_factory): graph = graph_with_random_data_factory(["total"]) # Make a DataTally the same way you make a Tally updaters = {"total": DataTally("total", alias="total")} assignment = {i: 1 if i in range(4) else 2 for i in range(9)} partition = Partition(graph, assignment, updaters) expected_total_in_district_one = sum(graph.nodes[i]["total"] for i in range(4)) assert partition["total"][1] == expected_total_in_district_one
def relabel_by_dem_vote_share(part, election): """ Renumbers districts by DEM vote share, 0-indexed """ dem_percent = election.percents('Democratic') unranked_to_ranked = sorted([(list(part.parts.keys())[x], dem_percent[x]) for x in range(0, len(part))], key=operator.itemgetter(1)) unranked_to_ranked_list = [x[0] for x in unranked_to_ranked] unranked_to_ranked = {unranked_to_ranked[x][0]:x for x in range(0, len(part))} newpart = Partition(part.graph, {x:unranked_to_ranked[part.assignment[x]] for x in part.graph.nodes}, part.updaters) return newpart
def test_works_when_no_flips_occur(): graph = Graph([(0, 1), (1, 2), (2, 3), (3, 0)]) for node in graph: graph.nodes[node]["pop"] = node + 1 partition = Partition(graph, {0: 0, 1: 0, 2: 1, 3: 1}, {"pop": Tally("pop")}) chain = MarkovChain(lambda p: p.flip({}), [], always_accept, partition, 10) expected = {0: 3, 1: 7} for partition in chain: assert partition["pop"] == expected
def test(): graph_path = "./Data/PA_VTDALL.json" graph = Graph.from_json(graph_path) k = 18 ep = 0.05 unit_id = "GEOID10" pop_col = "TOT_POP" plot_path = "./Data/VTD_FINAL" unit_df = gpd.read_file(plot_path) division_col = "COUNTYFP10" divisions = unit_df[[division_col, 'geometry']].dissolve(by=division_col, aggfunc='sum') updaters = {"population": updaters.Tally(pop_col, alias="population")} cddict = recursive_tree_part(graph, range(k), 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_col), 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, './chain_' + str(t) + '.png', geo_id=unit_id) t += 1
def make_partitions(n): ''' Returns a list of every possible partition on the nXn grid. This is nearly instantaneous for n = 3-5, and takes about 60s for n=6. ''' partitions = [] assignment_dict = make_assignment_dicts(n) grid = Grid((n, n)) for i in range(len(assignment_dict)): partition = Partition(grid.graph, assignment_dict[i]) partitions.append(partition) return partitions
def test_cut_edges_by_part_gives_same_total_edges_as_naive_method( three_by_three_grid): graph = three_by_three_grid assignment = {0: 1, 1: 1, 2: 2, 3: 1, 4: 1, 5: 2, 6: 2, 7: 2, 8: 2} updaters = {"cut_edges_by_part": cut_edges_by_part} partition = Partition(graph, assignment, updaters) # 112 111 # 112 -> 121 # 222 222 flip = {4: 2, 2: 1, 5: 1} new_partition = Partition(parent=partition, flips=flip) result = new_partition["cut_edges_by_part"] naive_cut_edges = { tuple(sorted(edge)) for edge in graph.edges if new_partition.crosses_parts(edge) } assert naive_cut_edges == { tuple(sorted(edge)) for part in result for edge in result[part] }
def toChain(self, initial_partition): """Returns chain with instance variables of self NoInitialChain and parameter initial_partition.""" return MarkovChain( proposal=self.proposal, constraints=self.constraints, accept=self.accept, # Declares new Partition with identical instances in order to avoid # attempting to access parent initial_state=Partition(initial_partition.graph, assignment=initial_partition.assignment, updaters=initial_partition.updaters), total_steps=self.total_steps )
def compute_cross_edge(graph, partition): """ The function finds the edges that cross from one partition to another partition Parameters: graph (Graph): The given graph represent state information partition (Partition): the partition of the given graph Returns: list: the list of edges that cross two partitions """ cross_list = [] for n in graph.edges: if Partition.crosses_parts(partition, n): cross_list.append(n) return cross_list # cut edges of partition
def test_repeatable(three_by_three_grid): from gerrychain import ( MarkovChain, Partition, accept, constraints, proposals, updaters, ) partition = Partition( three_by_three_grid, {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 2, 6: 2, 7: 2, 8: 2, 9: 2}, {"cut_edges": updaters.cut_edges}, ) chain = MarkovChain( proposals.propose_random_flip, constraints.single_flip_contiguous, accept.always_accept, partition, 20, ) # Note: these might not even be the actual expected flips expected_flips = [ None, {2: 2}, {4: 2}, {6: 1}, {1: 2}, {7: 1}, {0: 2}, {3: 2}, {7: 2}, {3: 1}, {4: 1}, {7: 1}, {8: 1}, {8: 2}, {8: 1}, {8: 2}, {3: 2}, {4: 2}, {7: 2}, {3: 1}, ] flips = [partition.flips for partition in chain] print(flips) assert flips == expected_flips
def tree_grid(n=20, m=20, k=40, varepsilon=0.01): graph = nx.grid_graph([n, m]) for node in graph.nodes(): graph.node[node]["population"] = 1 cddict = recursive_tree_part(graph, range(k), m * n / k, "population", varepsilon, 1) updater = {"cut_edges": cut_edges} initial_partition = Partition(graph, cddict, updater) dg = nx.Graph() for edge in initial_partition["cut_edges"]: dg.add_edge(cddict[edge[0]], cddict[edge[1]]) return dg
def buildPartition(graph, mean): # horizontal = [] assignment = {} for x in graph.node(): if graph.node[x]['C_X'] < mean: assignment[x] = -1 else: assignment[x] = 1 updaters = { 'population': Tally('population'), "boundary": bnodes_p, 'cut_edges': cut_edges, 'step_num': step_num, 'b_nodes': b_nodes_bi, 'base': new_base, 'geom': geom_wait, } grid_partition = Partition(graph, assignment=assignment, updaters=updaters) return grid_partition
def factor_seed(graph, k, pop_tol, pop_col): ''' Recursively partitions a graph into k districts. Returns an assignment. ''' total_population = sum([graph.nodes[n][pop_col] for n in graph.nodes]) pop_target = total_population / k num_d = 1 ass = {x: 0 for x in graph.nodes} while num_d != k: for r in range(2, int(k / num_d) + 1): if int(k / num_d) % r == 0: print("Splitting from {:d} down to {:d}".format( int(num_d), int(r * num_d))) ass = split_districts(Partition(graph, ass), r, pop_target * k / num_d / r, pop_col, pop_tol / k) num_d *= r break return ass
def test_contiguous_components(graph): partition = Partition(graph, { 0: 1, 1: 1, 2: 1, 3: 2, 4: 2, 5: 2, 6: 1, 7: 1, 8: 1 }) components = contiguous_components(partition) assert len(components[1]) == 2 assert len(components[2]) == 1 assert set(frozenset(g.nodes) for g in components[1]) == { frozenset([0, 1, 2]), frozenset([6, 7, 8]), } assert set(components[2][0].nodes) == {3, 4, 5}
#for n in graph.nodes(): # graph.node[n]["tree_plan"] = tree_dict[graph.node[n]["GEOID10"]] with open("./Outputs/build/init31.json", 'r') as f: tree_dict = json.load(f) tree_dict = {int(k):int(v) for k,v in tree_dict.items()} print('loaded new plan') #initial_partition = Partition(graph, "tree_plan", updater) initial_partition = Partition(graph, tree_dict, updater) print("built initial partition") ideal_population = sum(initial_partition["population"].values()) / len( initial_partition ) # print(ideal_population) tree_proposal = partial( recom, pop_col="TOT_POP", pop_target=ideal_population, epsilon=0.01, node_repeats=1, method = bipartition_tree_random ) compactness_bound = constraints.UpperBound( lambda p: len(p["cut_edges"]), 2 * len(initial_partition["cut_edges"])
def example_partition(): graph = Graph.from_networkx(networkx.complete_graph(3)) assignment = {0: 1, 1: 1, 2: 2} partition = Partition(graph, assignment, {"cut_edges": cut_edges}) return partition