def test_election_result_has_a_cute_str_method(): election = Election("2008 Presidential", { "Democratic": [3, 1, 2], "Republican": [1, 2, 1] }) results = ElectionResults( election, { "Democratic": { 0: 3, 1: 1, 2: 2 }, "Republican": { 0: 1, 1: 2, 2: 1 } }, [0, 1, 2], ) expected = ("Election Results for 2008 Presidential\n" "0:\n" " Democratic: 0.75\n" " Republican: 0.25\n" "1:\n" " Democratic: 0.3333\n" " Republican: 0.6667\n" "2:\n" " Democratic: 0.6667\n" " Republican: 0.3333") assert str(results) == expected
def main(config_data, id): try: timeBeg = time.time() print('Experiment', id, 'has begun') # Save configuration into global variable global config config = config_data graph = Graph.from_json(config['INPUT_GRAPH_FILENAME']) # List of districts in original graph parts = list( set([ graph.nodes[node][config['ASSIGN_COL']] for node in graph.nodes() ])) # Ideal population of districts ideal_pop = sum( [graph.nodes[node][config['POP_COL']] for node in graph.nodes()]) / len(parts) election = Election(config['ELECTION_NAME'], { 'PartyA': config['PARTY_A_COL'], 'PartyB': config['PARTY_B_COL'] }) updaters = { 'population': Tally(config['POP_COL']), 'cut_edges': cut_edges, config['ELECTION_NAME']: election } partDict = recursive_tree_part(graph, parts, ideal_pop, config['POP_COL'], config['EPSILON'], config['NODE_REPEATS']) for node in graph.nodes(): graph.nodes[node][config['ASSIGN_COL']] = partDict[node] part = Partition(graph=graph, assignment=config['ASSIGN_COL'], updaters=updaters) for len_ in config['RUN_LENGTHS']: for num in range(config['RUNS_PER_LEN']): run_chain(part, config['CHAIN_TYPE'], len_, ideal_pop, '{}_{}_{}_{}'.format(config['TAG'], id, len_, num)) print('Experiment {} completed in {} seconds'.format( id, time.time() - timeBeg)) except Exception as e: # Print notification if any experiment fails to complete track = traceback.format_exc() print(track) print('Experiment {} failed to complete after {:.2f} seconds'.format( id, time.time() - timeBeg))
def partition_with_election(graph_with_d_and_r_cols): graph = graph_with_d_and_r_cols assignment = random_assignment(graph, 3) parties_to_columns = { "D": {node: graph.nodes[node]["D"] for node in graph.nodes}, "R": {node: graph.nodes[node]["R"] for node in graph.nodes}, } election = Election("Mock Election", parties_to_columns) updaters = {"Mock Election": election, "cut_edges": cut_edges} return Partition(graph, assignment, updaters)
def partition_with_election(graph_with_d_and_r_cols): graph = graph_with_d_and_r_cols assignment = random_assignment(graph, 3) parties_to_columns = { 'D': {node: graph.nodes[node]['D'] for node in graph.nodes}, 'R': {node: graph.nodes[node]['R'] for node in graph.nodes} } election = Election("Mock Election", parties_to_columns) updaters = {"Mock Election": election} return Partition(graph, assignment, updaters)
def test_vote_proportion_returns_nan_if_total_votes_is_zero( three_by_three_grid): election = Election("Mock Election", ["D", "R"], alias="election") graph = three_by_three_grid for node in graph.nodes: for col in election.columns: graph.nodes[node][col] = 0 updaters = {"election": election} assignment = random_assignment(graph, 3) partition = Partition(graph, assignment, updaters) assert all( math.isnan(value) for party_percents in partition["election"].percents_for_party.values() for value in party_percents.values())
graph.nodes[node][P1] = 0 graph.nodes[node][P2] = 1 vote_dict = {1: P1, 0: P2} def step_num(partition): parent = partition.parent if not parent: return 0 return parent["step_num"] + 1 updaters = { "population": Tally("population"), "cut_edges": cut_edges, "step_num": step_num, "P1 vs P2": Election("P1 vs P2", {"P1": P1, "P2": P2}),} part_dict = {x: int(x[0]/(grid_width/k_partitions)) for x in graph.nodes()} init_part = Partition(graph, assignment=part_dict, updaters=updaters) def p1_wins(partition): if partition["P1 vs P2"].wins("P1")+partition["P1 vs P2"].wins("P2") > k_partitions: return equal_check(partition, "P1") else: return (partition["P1 vs P2"].wins("P1")/k_partitions)*100 def p2_wins(partition): if partition["P1 vs P2"].wins("P1")+partition["P1 vs P2"].wins("P2") > k_partitions: return equal_check(partition, "P2")
def produce_gerrymanders(graph, k, tag, sample_size, chaintype): # Samples k-partitions of the graph # stores vote histograms, and returns most extreme partitions. for node in graph.nodes(): graph.nodes[node]["last_flipped"] = 0 graph.nodes[node]["num_flips"] = 0 ideal_population = sum(graph.nodes[x][config["POPULATION_COLUMN"]] for x in graph.nodes()) / k election = Election( config['ELECTION_NAME'], {'PartyA': config['PARTY_A_COL'], 'PartyB': config['PARTY_B_COL']}, alias=config['ELECTION_ALIAS'] ) updaters = {'population': Tally(config['POPULATION_COLUMN']), 'cut_edges': cut_edges, 'step_num': step_num, config['ELECTION_ALIAS'] : election } initial_partition = Partition(graph, assignment=config['ASSIGNMENT_COLUMN'], updaters=updaters) popbound = within_percent_of_ideal_population(initial_partition, config['POPULATION_EPSILON']) if chaintype == "tree": tree_proposal = partial(recom, pop_col=config["POPULATION_COLUMN"], pop_target=ideal_population, epsilon=config['POPULATION_EPSILON'], node_repeats=config['NODE_REPEATS'], method=facefinder.my_mst_bipartition_tree_random) elif chaintype == "uniform_tree": tree_proposal = partial(recom, pop_col=config["POPULATION_COLUMN"], pop_target=ideal_population, epsilon=config['POPULATION_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") exp_chain = MarkovChain(tree_proposal, Validator([popbound]), accept=accept.always_accept, initial_state=initial_partition, total_steps=sample_size) seats_won_table = [] best_left = np.inf best_right = -np.inf for ctr, part in enumerate(exp_chain): seats_won = 0 if ctr % 100 == 0: print("step ", ctr) for i in range(k): rep_votes = 0 dem_votes = 0 for node in graph.nodes(): if part.assignment[node] == i: rep_votes += graph.nodes[node]["EL16G_PR_R"] dem_votes += graph.nodes[node]["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 partitions 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/large_sample/seats_hist/seats_histogram_orig" + tag + ".png" plt.savefig(name) plt.close() return left_mander, right_mander
def b_nodes_bi(partition): return {x[0] for x in partition["cut_edges"] }.union({x[1] for x in partition["cut_edges"]}) updaters = { "population": Tally("TOT_POP"), "cut_edges": cut_edges, "step_num": step_num, 'b_nodes': b_nodes_bi, 'SEN16': Election('SEN16', { 'DEM': 'T16SEND', 'GOP': 'T16SENR' }) } cols = [ '2011_PLA_1', 'GOV', 'TS', 'REMEDIAL_P', '538CPCT__1', '538DEM_PL', '538GOP_PL', '8THGRADE_1' ] Parts = [Partition(g, cddicts[i], updaters) for i in range(num_trees)] for col in cols: Parts.append(Partition(g, col, updaters)) for i in range(num_trees): cols.insert(0, f'Tree{i}')
neighbor = (vertex[0] + 1, vertex[1]) if assign[vertex] != assign[neighbor]: cut_edge_set.add((vertex, neighbor)) if vertex[1] < n - 1: neighbor = (vertex[0], vertex[1] + 1) if assign[vertex] != assign[neighbor]: cut_edge_set.add((vertex, neighbor)) return cut_edge_set updaters = { "population": Tally("population"), "cut_edges": cut_edges, "step_num": step_num, "Pink-Purple": Election("Pink-Purple", { "Pink": "pink", "Purple": "purple" }), "rook_cut_edges": rook_cut_edges } # ########BUILD PARTITION grid_partition = Partition(graph, assignment=cddict, updaters=updaters) # ADD CONSTRAINTS # FOr our 10x10 grid, will only allow districts of exactly 10 vertices popbound = within_percent_of_ideal_population(grid_partition, 0.1) # ########Setup Proposal ideal_population = sum( grid_partition["population"].values()) / len(grid_partition)
# ###CONFIGURE UPDATERS def step_num(partition): parent = partition.parent if not parent: return 0 return parent["step_num"] + 1 updaters = { "population": Tally("population"), "cut_edges": cut_edges, "step_num": step_num, "Pink-Purple": Election("Pink-Purple", {"Pink": "pink", "Purple": "purple"}), "Green-Yellow": Election("Green-Yellow", {"green": "green", "yellow": "yellow"}), 'b_nodes':b_nodes, } # ########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)
def main(): """ Contains majority of expermiment. Runs a markov chain on the state dual graph, determining how the distribution is affected to changes in the state dual graph. Raises: RuntimeError if PROPOSAL_TYPE of config file is neither 'sierpinski' nor 'convex' """ output_directory = createDirectory(config) epsilon = config["epsilon"] k = config["NUM_DISTRICTS"] updaters = {'population': Tally('population'), 'cut_edges': cut_edges, "Blue-Red": Election("Blue-Red", {"Blue": "blue", "Red": "red"}) } graph, dual = preprocessing(output_directory) cddict = {x: int(x[0] / gn) for x in graph.nodes()} plt.figure() nx.draw( graph, pos={x: x for x in graph.nodes()}, node_color=[cddict[x] for x in graph.nodes()], node_size=ns, node_shape="s", cmap="tab20", ) plt.show() ideal_population = sum(graph.nodes[x]["population"] for x in graph.nodes()) / k faces = graph.graph["faces"] faces = list(faces) square_faces = [face for face in faces if len(face) == 4] totpop = 0 for node in graph.nodes(): totpop += int(graph.nodes[node]['population']) # length of chain steps = config["CHAIN_STEPS"] # length of each gerrychain step gerrychain_steps = config["GERRYCHAIN_STEPS"] # faces that are currently modified. Code maintains list of modified faces, and at each step selects a face. if face is already in list, # the face is un-modified, and if it is not, the face is modified by the specified proposal type. special_faces = set([face for face in square_faces if np.random.uniform(0, 1) < .5]) #chain_output = {'dem_seat_data': [], 'rep_seat_data': [], 'score': []} chain_output = defaultdict(list) # start with small score to move in right direction print("Choosing", math.floor(len(faces) * config['PERCENT_FACES']), "faces of the dual graph at each step") max_score = -math.inf # this is the main markov chain for i in tqdm.tqdm(range(1, steps + 1), ncols=100, desc="Chain Progress"): special_faces_proposal = copy.deepcopy(special_faces) proposal_graph = copy.deepcopy(graph) if (config["PROPOSAL_TYPE"] == "sierpinski"): for i in range(math.floor(len(faces) * config['PERCENT_FACES'])): face = random.choice(faces) ##Makes the Markov chain lazy -- this just makes the chain aperiodic. if random.random() > .5: if not (face in special_faces_proposal): special_faces_proposal.append(face) else: special_faces_proposal.remove(face) chain_on_subsets_of_faces.face_sierpinski_mesh(proposal_graph, special_faces_proposal) elif (config["PROPOSAL_TYPE"] == "add_edge"): for j in range(math.floor(len(square_faces) * config['PERCENT_FACES'])): face = random.choice(square_faces) ##Makes the Markov chain lazy -- this just makes the chain aperiodic. if random.random() > .5: if not (face in special_faces_proposal): special_faces_proposal.add(face) else: special_faces_proposal.remove(face) chain_on_subsets_of_faces.add_edge_proposal(proposal_graph, special_faces_proposal) else: raise RuntimeError('PROPOSAL TYPE must be "sierpinski" or "convex"') initial_partition = Partition(proposal_graph, assignment=cddict, updaters=updaters) # Sets up Markov chain popbound = within_percent_of_ideal_population(initial_partition, epsilon) tree_proposal = partial(recom, pop_col=config['POP_COL'], pop_target=ideal_population, epsilon=epsilon, node_repeats=1) # make new function -- this computes the energy of the current map exp_chain = MarkovChain(tree_proposal, Validator([popbound]), accept=accept.always_accept, initial_state=initial_partition, total_steps=gerrychain_steps) seats_won_for_republicans = [] seats_won_for_democrats = [] for part in exp_chain: for u, v in part["cut_edges"]: proposal_graph[u][v]["cut_times"] += 1 rep_seats_won = 0 dem_seats_won = 0 for j in range(k): rep_votes = 0 dem_votes = 0 for n in graph.nodes(): if part.assignment[n] == j: rep_votes += graph.nodes[n]["blue"] dem_votes += graph.nodes[n]["red"] total_seats_dem = int(dem_votes > rep_votes) total_seats_rep = int(rep_votes > dem_votes) rep_seats_won += total_seats_rep dem_seats_won += total_seats_dem seats_won_for_republicans.append(rep_seats_won) seats_won_for_democrats.append(dem_seats_won) seat_score = statistics.mean(seats_won_for_republicans) # implement mattingly simulated annealing scheme, from evaluating partisan gerrymandering in wisconsin if i <= math.floor(steps * .67): beta = i / math.floor(steps * .67) else: beta = (i / math.floor(steps * 0.67)) * 100 temperature = 1 / (beta) weight_seats = 1 weight_flips = -.2 config['PERCENT_FACES'] = config['PERCENT_FACES'] # what is this? flip_score = len(special_faces) # This is the number of edges being swapped score = weight_seats * seat_score + weight_flips * flip_score ##This is the acceptance step of the Metropolis-Hasting's algorithm. Specifically, rand < min(1, P(x')/P(x)), where P is the energy and x' is proposed state # if the acceptance criteria is met or if it is the first step of the chain def update_outputs(): chain_output['dem_seat_data'].append(seats_won_for_democrats) chain_output['rep_seat_data'].append(seats_won_for_republicans) chain_output['score'].append(score) chain_output['seat_score'].append(seat_score) chain_output['flip_score'].append(flip_score) def propagate_outputs(): for key in chain_output.keys(): chain_output[key].append(chain_output[key][-1]) if i == 1: update_outputs() special_faces = copy.deepcopy(special_faces_proposal) # this is the simplified form of the acceptance criteria, for intuitive purposes # exp((1/temperature) ( proposal_score - previous_score)) elif np.random.uniform(0, 1) < (math.exp(score) / math.exp(chain_output['score'][-1])) ** (1 / temperature): update_outputs() special_faces = copy.deepcopy(special_faces_proposal) else: propagate_outputs() # if score is highest seen, save map. if score > max_score: # todo: all graph coloring for graph changes that produced this score nx.write_gpickle(proposal_graph, "obj/graphs/"+str(score)+'sc_'+str(config['CHAIN_STEPS'])+'mcs_'+ str(config["GERRYCHAIN_STEPS"])+ "gcs_" + config['PROPOSAL_TYPE']+'_'+ str(len(special_faces)), pickle.HIGHEST_PROTOCOL) save_obj(special_faces, output_directory, 'north_carolina_highest_found') nx.write_gpickle(proposal_graph, output_directory + '/' + "max_score", pickle.HIGHEST_PROTOCOL) f=open(output_directory + "/max_score_data.txt","w+") f.write("maximum score: " + str(score) + "\n" + "edges changed: " + str(len(special_faces)) + "\n" + "Seat Score: " + str(seat_score)) save_obj(special_faces, output_directory + '/', "special_faces") max_score = score plt.plot(range(len(chain_output['score'])), chain_output['score']) plt.xlabel("Meta-Chain Step") plt.ylabel("Score") plt.show() plt.close() plt.plot(range(len(chain_output['seat_score'])), chain_output['seat_score']) plt.xlabel("Meta-Chain Step") plt.ylabel("Number of average seats republicans won") plt.show() plt.close()
def 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]
def main(config_data, id): """Runs a single experiment with the given config file. Loads a graph, runs a Chain to search for a Gerrymander, metamanders around that partition, runs another chain, and then saves the generated data. Args: config_data (Object): configuration of experiment loaded from JSON file id (String): id of experiment, used in tags to differentiate between experiments """ try: timeBeg = time.time() print('Experiment', id, 'has begun') # Save configuration into global variable global config config = config_data # Get graph and dual graph, dual = preprocessing(config["INPUT_GRAPH_FILENAME"]) # List of districts in original graph parts = list(set([graph.nodes[node][config['ASSIGN_COL']] for node in graph.nodes()])) # Ideal population of districts ideal_pop = sum([graph.nodes[node][config['POP_COL']] for node in graph.nodes()]) / len(parts) # Initialize partition election = Election( config['ELECTION_NAME'], {'PartyA': config['PARTY_A_COL'], 'PartyB': config['PARTY_B_COL']} ) updaters = {'population': Tally(config['POP_COL']), 'cut_edges': cut_edges, config['ELECTION_NAME'] : election } origPartition = Partition(graph=graph, assignment=config['ASSIGN_COL'], updaters=updaters) minAvg, minPartition = float('inf'), None for i in range(config['RUNS_PER_K_VAL']): tempGraph = copy.deepcopy(origPartition.graph) face_sierpinski_mesh(origPartition, tempGraph , getKFaces(dual, config['k'])) # Refresh assignment and election of partition updaters[config['ELECTION_NAME']] = Election( config['ELECTION_NAME'], {'PartyA': config['PARTY_A_COL'], 'PartyB': config['PARTY_B_COL']} ) newPartition = Partition(graph=tempGraph, assignment=config['ASSIGN_COL'], updaters=updaters) if (avg := run_chain(newPartition, config['CHAIN_TYPE'], config['TEST_META_LENGTH'], ideal_pop, id + 'a' + str(i), config['TEST_RUN_STATS_TAG'] + id + str(i))) < minAvg: minAvg, minPartition = avg, newPartition updaters[config['ELECTION_NAME']] = Election( config['ELECTION_NAME'], {'PartyA': config['PARTY_A_COL'], 'PartyB': config['PARTY_B_COL']} ) partition = Partition(graph=minPartition.graph, assignment=config['ASSIGN_COL'], updaters=updaters) # Run chain again run_chain(partition, config['CHAIN_TYPE'], config['FULL_CHAIN_LENGTH'], ideal_pop, id + 'b', config['FULL_RUN_STATS_TAG'] + id) # Save data from experiment to JSON files drawGraph(partition.graph, 'cut_times', config['GRAPH_TAG'] + '_single_raw_' + id) drawGraph(partition.graph, 'sibling_cuts', config['GRAPH_TAG'] + '_single_adjusted_' + id) drawDoubleGraph(partition.graph, 'cut_times', config['GRAPH_TAG'] + '_double_raw_' + id) drawDoubleGraph(partition.graph, 'sibling_cuts', config['GRAPH_TAG'] + '_double_adjusted_' + id) saveGraphStatistics(partition.graph, config['GRAPH_STATISTICS_TAG'] + id) print('Experiment {} completed in {:.2f} seconds'.format(id, time.time() - timeBeg))
def main(config_data, id): """Runs a single experiment with the given config file. Loads a graph, runs a Chain to search for a Gerrymander, metamanders around that partition, runs another chain, and then saves the generated data. Args: config_data (Object): configuration of experiment loaded from JSON file id (String): id of experiment, used in tags to differentiate between experiments """ try: timeBeg = time.time() print('Experiment', id, 'has begun') # Save configuration into global variable global config config = config_data # Get graph and dual graph graph, dual = preprocessing(config["INPUT_GRAPH_FILENAME"]) # List of districts in original graph parts = list( set([ graph.nodes[node][config['ASSIGN_COL']] for node in graph.nodes() ])) # Ideal population of districts ideal_pop = sum( [graph.nodes[node][config['POP_COL']] for node in graph.nodes()]) / len(parts) # Initialize partition election = Election(config['ELECTION_NAME'], { 'PartyA': config['PARTY_A_COL'], 'PartyB': config['PARTY_B_COL'] }) updaters = { 'population': Tally(config['POP_COL']), 'cut_edges': cut_edges, config['ELECTION_NAME']: election } partition = Partition(graph=graph, assignment=config['ASSIGN_COL'], updaters=updaters) # Run Chain to search for a gerrymander, and get it mander = run_chain(partition, config['CHAIN_TYPE'], config['FIND_GERRY_LENGTH'], ideal_pop, id + 'a', config['ORIG_RUN_STATS_TAG'] + id) savePartition(mander, config['LEFT_MANDER_TAG'] + id) # Metamanders around the found gerrymander metamander_around_partition(mander, dual, config['TARGET_TAG'] + id, config['SECRET'], config['META_PARAM']) # Refresh assignment and election of partition updaters[config['ELECTION_NAME']] = Election( config['ELECTION_NAME'], { 'PartyA': config['PARTY_A_COL'], 'PartyB': config['PARTY_B_COL'] }) partition = Partition(graph=graph, assignment=config['ASSIGN_COL'], updaters=updaters) # Run chain again run_chain(partition, config['CHAIN_TYPE'], config['SAMPLE_META_LENGTH'], ideal_pop, id + 'b', config['GERRY_RUN_STATS_TAG'] + id) # Save data from experiment to JSON files drawGraph(partition.graph, 'cut_times', config['GRAPH_TAG'] + '_single_raw_' + id) drawGraph(partition.graph, 'sibling_cuts', config['GRAPH_TAG'] + '_single_adjusted_' + id) drawDoubleGraph(partition.graph, 'cut_times', config['GRAPH_TAG'] + '_double_raw_' + id) drawDoubleGraph(partition.graph, 'sibling_cuts', config['GRAPH_TAG'] + '_double_adjusted_' + id) saveGraphStatistics(partition.graph, config['GRAPH_STATISTICS_TAG'] + id) print('Experiment {} completed in {:.2f} seconds'.format( id, time.time() - timeBeg)) except Exception as e: # Print notification if any experiment fails to complete track = traceback.format_exc() print(track) print('Experiment {} failed to complete after {:.2f} seconds'.format( id, time.time() - timeBeg))