Beispiel #1
0
 def centroid(self):
     """The average centroid of the community's precincts.
     """
     precinct_X = []
     precinct_Y = []
     for precinct in self.precincts.values():
         precinct_X.append(precinct.centroid[0])
         precinct_Y.append(precinct.centroid[1])
     return [average(precinct_X), average(precinct_Y)]
def get_quantification_values(table):
    """Gets the statewide 0 to 1 and -1 to 1 scores from a data table.
    
    :param table: 2d list of a quantification output from the c++ algorithm implementation.
    :type table: list

    :return: 0 to 1 quantification value, and -1 to 1 quantification value.
    :rtype: float, float
    """

    gerry_score = average(table[0])
    avg_rep = average([d["REP"] for d in table[-1]])

    partisanship_score = 2 * (0.5 - avg_rep) * gerry_score

    return gerry_score, partisanship_score
Beispiel #3
0
 def update_partisanship(self):
     """Updates the partisanship attribute for this community.
     """
     self.partisanship = [
         average([eval(f"p.{attr}") for p in self.precincts.values()])
         for attr in self.parties
     ]
Beispiel #4
0
 def update_partisanship_stdev(self):
     """Updates the partisanship_stdev attribute for this community.
     """
     party_stdevs = []
     for party in self.parties:
         percentage_func = eval(f"lambda p: p.{party}")
         precinct_percentages = []
         for precinct in self.precincts.values():
             precinct_percentages.append(percentage_func(precinct))
         party_stdevs.append(standard_deviation(precinct_percentages))
     self.partisanship_stdev = average(party_stdevs)
Beispiel #5
0
def _check_communities_complete(communities, percentage):
    """Checks if communities have satifsfactory populations.

    :param communities: A list of communities.
    :type communities: list of `hacking_the_election.utils.community.Community`

    :param percentage: The maximum tolerated percentage difference between two communities.
    :type percentage: float

    :return: Whether or not the communities fulfil population requirements.
    :rtype: bool
    """

    ideal_pop = average([c.population for c in communities])

    for community in communities:
        if abs(community.population - ideal_pop) / ideal_pop > (percentage /
                                                                200):
            return False

    return True
def optimize_population_stdev(communities, graph, animation_dir=None):
    """Takes a set of communities and exchanges precincts such that the standard deviation of the populations of their precincts are as low as possible.

    :param communities: The current state of the communities within a state.
    :type communities: list of `hacking_the_election.utils.community.Community`

    :param graph: A graph containing precinct data within a state.
    :type graph: `pygraph.classes.graph.graph`

    :param animation_dir: Path to the directory where animation files should be saved, defaults to None
    :type animation_dir: str or NoneType
    """

    for community in communities:
        community.update_population_stdev()

    if animation_dir is not None:
        draw_state(graph, animation_dir)

    best_communities = [copy.copy(c) for c in communities]
    last_communities = []

    while True:

        # Find most inconsistent population community.
        community = max(communities, key=lambda c: c.population_stdev)
        iteration_stdevs = [c.population_stdev for c in communities]
        avg_stdev = average(iteration_stdevs)
        rounded_stdevs = [round(stdev, 3) for stdev in iteration_stdevs]
        print(rounded_stdevs, round(avg_stdev, 3))

        if avg_stdev > average([c.population_stdev for c in best_communities]):
            best_communities = [copy.copy(c) for c in communities]

        if last_communities != []:
            # Check if anything changed. If not, exit the function.

            changed = False
            try:
                for c in communities:
                    for c2 in last_communities:
                        if c.id == c2.id:
                            if c.precincts != c2.precincts:
                                changed = True
                                raise LoopBreakException
            except LoopBreakException:
                pass

            if not changed:

                # Revert to best community state.
                for c in best_communities:
                    for c2 in communities:
                        if c.id == c2.id:
                            c2 = c
                for c in communities:
                    for precinct in c.precincts.values():
                        precinct.community = c.id

                rounded_stdevs = [
                    round(c.population_stdev, 3) for c in communities
                ]
                avg_stdev = average([c.population_stdev for c in communities])
                print(rounded_stdevs, round(avg_stdev, 3))
                if animation_dir is not None:
                    draw_state(graph, animation_dir)
                return

        # Not deepcopy so that precinct objects are not copied (saves memory).
        last_communities = [copy.copy(c) for c in communities]

        giveable_precincts = get_giveable_precincts(graph, communities,
                                                    community.id)
        for precinct, other_community in giveable_precincts:

            starting_stdev = average([c.population_stdev for c in communities])

            community.give_precinct(other_community,
                                    precinct.id,
                                    update={"population_stdev"})
            # Check if exchange made `community` non-contiguous.
            # Or if it hurt the previous population stdev.
            if (len(get_components(community.induced_subgraph)) > 1
                    or community.population_stdev > starting_stdev):
                other_community.give_precinct(community,
                                              precinct.id,
                                              update={"population_stdev"})
            else:
                average_stdev = average(
                    [c.population_stdev for c in communities])
                if average_stdev > starting_stdev:
                    other_community.give_precinct(community,
                                                  precinct.id,
                                                  update={"population_stdev"})
                else:
                    if animation_dir is not None:
                        draw_state(graph, animation_dir)

        takeable_precincts = get_takeable_precincts(graph, communities,
                                                    community.id)
        for precinct, other_community in takeable_precincts:

            starting_stdev = average([c.population_stdev for c in communities])

            other_community.give_precinct(community,
                                          precinct.id,
                                          update={"population_stdev"})
            # Check if exchange made `community` non-contiguous.
            # Or if it hurt the previous population stdev.
            if len(get_components(other_community.induced_subgraph)) > 1:
                community.give_precinct(other_community,
                                        precinct.id,
                                        update={"population_stdev"})
            else:
                average_stdev = average(
                    [c.population_stdev for c in communities])
                if average_stdev > starting_stdev:
                    community.give_precinct(other_community,
                                            precinct.id,
                                            update={"population_stdev"})
                else:
                    if animation_dir is not None:
                        draw_state(graph, animation_dir)
def optimize_compactness(communities, graph, animation_dir=None):
    """Takes a set of communities and exchanges precincts in a way that maximizes compactness.

    :param communities: The current state of the communities within a state.
    :type communities: list of `hacking_the_election.utils.community.Community`

    :param graph: A graph containing precinct data within a state.
    :type graph: `pygraph.classes.graph.graph`

    :param animation_dir: Path to the directory where animation files should be saved, defaults to None
    :type animation_dir: str or NoneType
    """

    for community in communities:
        community.update_imprecise_compactness()

    if animation_dir is not None:
        draw_state(graph, animation_dir)

    best_communities = [copy.copy(c) for c in communities]
    last_communities = []
    iterations_since_best = 0

    n = 0

    while True:

        # Stop if number of iterations since the best 
        # communities so far is more than N.
        if min([c.imprecise_compactness for c in communities]) \
                > min([c.imprecise_compactness for c in best_communities]):
            # Current communities are new best.
            best_communities = [copy.copy(c) for c in communities]
            iterations_since_best = 0
        else:
            iterations_since_best += 1
            if iterations_since_best > N:
                # Revert to best and exit function.
                for c in communities:
                    for bc in best_communities:
                        if c.id == bc.id:
                            c = bc
                
                rounded_compactnesses = [round(c.imprecise_compactness, 3) for c in communities]
                print(rounded_compactnesses, min(rounded_compactnesses))
                return

        # Stop if there has been no change in communities this iteration.
        if last_communities != []:
            try:
                for c in communities:
                    for lc in last_communities:
                        if c.id == lc.id:
                            if c.precincts != lc.precincts:
                                changed = True
                                raise LoopBreakException
                    # Revert to best and exit function.
                    # (LoopBreakException was not raised,
                    # so communities did not change).
                    for c in communities:
                        for bc in best_communities:
                            if c.id == bc.id:
                                c = bc
                
                    rounded_compactnesses = [round(c.imprecise_compactness, 3) for c in communities]
                    print(rounded_compactnesses, min(rounded_compactnesses))
                    return
            except LoopBreakException:
                pass


        community = communities[n]

        # Precinct centroid coords
        X = []
        Y = []
        precinct_areas = []
        for p in community.precincts.values():
            X.append(p.centroid[0])
            Y.append(p.centroid[1])
            precinct_areas.append(p.coords.area)
        center = [average(X), average(Y)]
        radius = math.sqrt(sum(precinct_areas) / math.pi)

        for _ in range(M):

            # Communities that have exchanged precincts with `community`
            other_communities = set()

            giveable_precincts = get_giveable_precincts(
                graph, communities, community.id)
            
            for precinct, other_community in giveable_precincts:
                if get_distance(precinct.centroid, center) > radius:
                    community.give_precinct(
                        other_community, precinct.id)
                    if len(get_components(community.induced_subgraph)) > 1:
                        # Giving precinct made `community` non contiguous.
                        other_community.give_precinct(
                            community, precinct.id)
                    else:
                        other_communities.add(other_community)
            
            takeable_precincts = get_takeable_precincts(
                graph, communities, community.id)
            
            for precinct, other_community in takeable_precincts:
                if get_distance(precinct.centroid, center) <= radius:
                    other_community.give_precinct(
                        community, precinct.id)
                    if len(get_components(other_community.induced_subgraph)) > 1:
                        # Giving precicnt made `other_community` non contiguous.
                        community.give_precinct(
                            other_community, precinct.id)
                    else:
                        other_communities.add(other_community)
                
            for other_community in other_communities:
                other_community.update_imprecise_compactness()
            other_community.update_imprecise_compactness()

        rounded_compactnesses = [round(c.imprecise_compactness, 3) for c in communities]
        print(rounded_compactnesses, min(rounded_compactnesses))

        n += 1
        if n == len(communities):
            n = 0
Beispiel #8
0
def optimize_population(communities, graph, percentage, animation_dir=None):
    """Takes a set of communities and exchanges precincts so that the population is as evenly distributed as possible.

    :param communities: The current state of the communities within a state.
    :type communities: list of `hacking_the_election.utils.community.Community`

    :param graph: A graph containing precinct data within a state.
    :type graph: `pygraph.classes.graph.graph`

    :param percentage: By how much the populations are able to deviate from one another. A value of 1% means that all the communities must be within 0.5% of the ideal population, so that they are guaranteed to be within 1% of each other.
    :type percentage: float between 0 and 1

    :param animation_dir: Path to the directory where animation files should be saved, defaults to None
    :type animation_dir: str or NoneType
    """

    for community in communities:
        community.update_population()

    if animation_dir is not None:
        draw_state(graph, animation_dir)

    ideal_population = average([c.population for c in communities])
    print(f"{ideal_population=}")

    while not _check_communities_complete(communities, percentage):

        community = max(communities,
                        key=lambda c: abs(c.population - ideal_population))

        if community.population > ideal_population:

            giveable_precincts = get_giveable_precincts(
                graph, communities, community.id)

            # Weight random choice by distance from community centroid.
            community_centroid = community.centroid
            giveable_precinct_weights = [
                get_distance(pair[0].centroid, community_centroid)
                for pair in giveable_precincts
            ]
            max_distance = max(giveable_precinct_weights)
            giveable_precinct_weights = \
                [max_distance - weight for weight in giveable_precinct_weights]

            while community.population > ideal_population:

                if giveable_precincts == []:
                    giveable_precincts = get_giveable_precincts(
                        graph, communities, community.id)
                    # Weight random choice by distance from community centroid.
                    community_centroid = community.centroid
                    giveable_precinct_weights = [
                        get_distance(pair[0].centroid, community_centroid)
                        for pair in giveable_precincts
                    ]
                    max_distance = max(giveable_precinct_weights)
                    giveable_precinct_weights = \
                        [max_distance - weight for weight in giveable_precinct_weights]

                # Choose random precinct and community and remove from lists.
                precinct, other_community = random.choices(
                    giveable_precincts, weights=giveable_precinct_weights)[0]
                giveable_precinct_weights.pop(
                    giveable_precincts.index((precinct, other_community)))
                giveable_precincts.remove((precinct, other_community))

                community.give_precinct(other_community,
                                        precinct.id,
                                        update={"population"})
                if len(get_components(community.induced_subgraph)) > 1:
                    # Give back precinct.
                    other_community.give_precinct(community,
                                                  precinct.id,
                                                  update={"population"})

        elif community.population < ideal_population:

            takeable_precincts = get_takeable_precincts(
                graph, communities, community.id)

            # Weight random choice by distance from community centroid.
            community_centroid = community.centroid
            takeable_precinct_weights = [
                get_distance(pair[0].centroid, community_centroid)
                for pair in takeable_precincts
            ]

            while community.population < ideal_population:

                if takeable_precincts == []:
                    takeable_precincts = get_takeable_precincts(
                        graph, communities, community.id)

                    # Weight random choice by distance from community centroid.
                    community_centroid = community.centroid
                    takeable_precinct_weights = [
                        get_distance(pair[0].centroid, community_centroid)
                        for pair in takeable_precincts
                    ]

                precinct, other_community = random.choices(
                    takeable_precincts, weights=takeable_precinct_weights)[0]
                takeable_precinct_weights.pop(
                    takeable_precincts.index((precinct, other_community)))
                takeable_precincts.remove((precinct, other_community))

                other_community.give_precinct(community,
                                              precinct.id,
                                              update={"population"})
                if len(get_components(other_community.induced_subgraph)) > 1:
                    # Give back precinct.
                    community.give_precinct(other_community,
                                            precinct.id,
                                            update={"population"})

        if animation_dir is not None:
            draw_state(graph, animation_dir)

        print([c.population for c in communities], community.population)