diff --git a/docs/repeated_subsections/reproducible_envs.rst b/docs/repeated_subsections/reproducible_envs.rst index 762b6f5d..ed40cc40 100644 --- a/docs/repeated_subsections/reproducible_envs.rst +++ b/docs/repeated_subsections/reproducible_envs.rst @@ -73,7 +73,7 @@ Reproducible Environments in VSCode and Jupyter Lab In general, it is easier to use jupyter notebooks with bespoke virtual environments through a text editor like `VSCode `_, but -we will show how to do this using the standard Jupyter Lab interface as well. +we will also show how to do this using the standard Jupyter Lab interface as well. Regardless of which method you prefer, you will need to make sure that you have installed the ``ipykernel`` package into the virtual environment that you will be diff --git a/docs/topics/reporting.rst b/docs/topics/reporting.rst index d65fb7b1..1d19478c 100644 --- a/docs/topics/reporting.rst +++ b/docs/topics/reporting.rst @@ -2,11 +2,11 @@ Reporting Issues ================ -Sometimes, you may encounter a problem with the GerryChain, and that is okay! We would +Sometimes, you may encounter a problem with GerryChain, and that is okay! We would love to hear about it so that we can fix it. If you can provide a minimal example that reproduces the issue, please feel free to report it on the `GitHub issue tracker `_. In the event that you would prefer not to post the issue publicly, you can also email us at "code[at]mggg[dot]org". We will do our best to respond to your issue in a timely -manner. Thank you for your help in making the GerryChain better! \ No newline at end of file +manner. Thank you for your help in making GerryChain better! \ No newline at end of file diff --git a/docs/user/optimizers.rst b/docs/user/optimizers.rst index 32bc17ba..3e8add69 100644 --- a/docs/user/optimizers.rst +++ b/docs/user/optimizers.rst @@ -3,26 +3,29 @@ Optimization Methods of GerryChain ================================== In GerryChain, we provide a class known as the ``SingleMetricOptimizer`` as well as a -``Gingelator``` subclass that allow us to perform optimization runs. +``Gingelator`` subclass that allow us to perform optimization runs. Currently, there are 3 different optimization methods available in GerryChain: - **Short Bursts**: This method chains together a series of neutral explorers. The main - idea is to run the chain for a short period of time (short burst) and then continue - the chain from the partition that maximizes the objective function within the most - recent short burst. For more information, please refer to - `this paper `_. + idea is to run the chain for a short period of time (short burst) and then continue + the chain from the partition that maximizes the objective function within the most + recent short burst. For more information, please refer to + `this paper `_. + - **Simulated Annealing**: This method varies the probablity of accepting a worse plan - according to a temperature schedule which ranges from 0 to 1. -- **Tilted Runs**: This method accepts a worse plan with a fixed probability :math:`p`. + according to a temperature schedule which ranges from 0 to 1. + +- **Tilted Runs**: This method accepts a worse plan with a fixed probability :math:`p`, + and always accepts better plans. While sampling naively with GerryChain can give us an understanding of the neutral baseline for a state, there are often cases where we want to find plans with properties that are rare to encounter in a neutral run. Many states have laws/guidelines that state that plans should be as compact as feasibly possible, maximize -preservation of political boundaries and/or communities of interest, some even look to +preservation of political boundaries and/or communities of interest; some even look to minimize double bunking of incumbents or seek proportionality/competitiveness in contests. Heuristic optimization methods can be used to find example plans with these properties and to explore the trade-offs between them. @@ -96,8 +99,8 @@ And now we load in our file and set up our initial chain. Using ``SingleMetricOptimizer`` ------------------------------- -Now the `SingleMetricOptimizer` is set up as a wrapper around our basic `MarkovChain` -class, so interacting with it should be familiar. To set up our optimizer, we, we simply +Now the ``SingleMetricOptimizer`` is set up as a wrapper around our basic ``MarkovChain`` +class, so interacting with it should be familiar. To set up our optimizer, we simply pass it a proposal function, some constraints, an initial state, and the objective function: .. code:: python @@ -164,13 +167,12 @@ This should give you something like: Using ``Gingleator`` -------------------- -Named for the Supreme Court case *Thornburg v. Gingles*, which created their precedent -as one of the litmus tests in bringing forth a VRA court case, **Gingles' Districts** are +Named for the Supreme Court case *Thornburg v. Gingles*, **Gingles' Districts** are districts that are 50% + 1 of a minority population subgroup (more colloquially called majority-minority districts). It is common to seek plans with greater/maximal numbers -of gingles districts to understand the landscape of the state space. +of Gingles' districts to understand the landscape of the state space. -The `Gingleator` class is a subclass of the `SingleMetricOptimizer` class, so much of +The ``Gingleator`` class is a subclass of the ``SingleMetricOptimizer`` class, so much of the setup is the same: .. code:: python @@ -237,8 +239,7 @@ This should give you something like: :align: center :alt: Gingleator Optimization Method Comparison Image -And we can see a little better how each method performs over the course of the run -by looking at all of the scores relative to the previous graph: +And we can see a little better how each method performs over the course of the run: .. image:: ./images/gingleator_all.png :align: center diff --git a/gerrychain/optimization/gingleator.py b/gerrychain/optimization/gingleator.py index f551e492..37a0b05a 100755 --- a/gerrychain/optimization/gingleator.py +++ b/gerrychain/optimization/gingleator.py @@ -39,26 +39,29 @@ def __init__( :type constraints: Union[Iterable[Callable], Validator, Iterable[Bounds], Callable] :param initial_state: Initial :class:`gerrychain.partition.Partition` class. :type initial_state: Partition - :param minority_perc_col: Which updater is a mapping of district ids to the fraction of - minority population within that district. Defaults to none. - :type minority_perc_col: str - :param threshold: Beyond which fraction to consider something a "Gingles" + :param minority_perc_col: The name of the updater mapping of district ids to the + fraction of minority population within that district. If no updater is + specified, one is made according to the ``min_perc_column_name`` parameter. + Defaults to None. + :type minority_perc_col: Optional[str] + :param threshold: Fraction beyond which to consider something a "Gingles" (or opportunity) district. Defaults to 0.5. - :type threshold: float - :param score_function: The function to using doing optimization. Should have the + :type threshold: float, optional + :param score_function: The function to use during optimization. Should have the signature ``Partition * str (minority_perc_col) * float (threshold) -> 'a where 'a is Comparable``. This class implements a few potential choices as class methods. Defaults to None. - :type score_function: Callable - :param minority_pop_col: If minority_perc_col is defined, the minority population column - with which to compute percentage. Defaults to None. - :type minority_pop_col: str + :type score_function: Optional[Callable] + :param minority_pop_col: If minority_perc_col is not defined, the minority population + column with which to compute percentage. Defaults to None. + :type minority_pop_col: Optional[str] :param total_pop_col: If minority_perc_col is defined, the total population column with which to compute percentage. Defaults to "TOTPOP". - :type total_pop_col: str - :param min_perc_column_name: If minority_perc_col is defined, the name to give the created - percentage updater. Defaults to "_gingleator_auxiliary_helper_updater_min_perc_col". - :type min_perc_column_name: str + :type total_pop_col: str, optional + :param min_perc_column_name: If minority_perc_col is not defined, the name to give the + created percentage updater. Defaults to + "_gingleator_auxiliary_helper_updater_min_perc_col". + :type min_perc_column_name: str, optional """ if minority_perc_col is None and minority_pop_col is None: raise ValueError( @@ -103,10 +106,10 @@ def num_opportunity_dists( :param part: Partition to score. :type part: Partition - :param minority_perc_col: Which updater is a mapping of district ids to the fraction of - minority population within that district. + :param minority_perc_col: The name of the updater mapping of district ids to the + fraction of minority population within that district. :type minority_perc_col: str - :param threshold: Beyond which fraction to consider something a "Gingles" + :param threshold: Fraction beyond which to consider something a "Gingles" (or opportunity) district. :type threshold: float @@ -126,10 +129,10 @@ def reward_partial_dist( :param part: Partition to score. :type part: Partition - :param minority_perc_col: Which updater is a mapping of district ids to the fraction of - minority population within that district. + :param minority_perc_col: The name of the updater mapping of district ids to the + fraction of minority population within that district. :type minority_perc_col: str - :param threshold: Beyond which fraction to consider something a "Gingles" + :param threshold: Fraction beyond which to consider something a "Gingles" (or opportunity) district. :type threshold: float @@ -152,10 +155,10 @@ def reward_next_highest_close( :param part: Partition to score. :type part: Partition - :param minority_perc_col: Which updater is a mapping of district ids to the fraction of - minority population within that district. + :param minority_perc_col: The name of the updater mapping of district ids to the + fraction of minority population within that district. :type minority_perc_col: str - :param threshold: Beyond which fraction to consider something a "Gingles" + :param threshold: Fraction beyond which to consider something a "Gingles" (or opportunity) district. :type threshold: float @@ -182,10 +185,10 @@ def penalize_maximum_over( :param part: Partition to score. :type part: Partition - :param minority_perc_col: Which updater is a mapping of district ids to the fraction of - minority population within that district. + :param minority_perc_col: The name of the updater mapping of district ids to the + fraction of minority population within that district. :type minority_perc_col: str - :param threshold: Beyond which fraction to consider something a "Gingles" + :param threshold: Fraction beyond which to consider something a "Gingles" (or opportunity) district. :type threshold: float @@ -210,10 +213,10 @@ def penalize_avg_over( :param part: Partition to score. :type part: Partition - :param minority_perc_col: Which updater is a mapping of district ids to the fraction of - minority population within that district. + :param minority_perc_col: The name of the updater mapping of district ids to the + fraction of minority population within that district. :type minority_perc_col: str - :param threshold: Beyond which fraction to consider something a "Gingles" + :param threshold: Fraction beyond which to consider something a "Gingles" (or opportunity) district. :type threshold: float diff --git a/gerrychain/optimization/optimization.py b/gerrychain/optimization/optimization.py index 40d98f54..0a3075a5 100644 --- a/gerrychain/optimization/optimization.py +++ b/gerrychain/optimization/optimization.py @@ -12,7 +12,7 @@ class SingleMetricOptimizer: SingleMetricOptimizer represents the class of algorithms / chains that optimize plans with respect to a single metric. An instance of this class encapsulates the following state information: - * the dualgraph and updaters via the initial partition, + * the dual graph and updaters via the initial partition, * the constraints new proposals are subject to, * the metric over which to optimize, * and whether or not to seek maximal or minimal values of the metric. @@ -50,7 +50,7 @@ def __init__( :param initial_state: Initial state of the optimizer. :type initial_state: Partition :param optimization_metric: The score function with which to optimize over. This should have - the signature: ``Partition -> 'a`` where 'a is Comparable + the signature: ``Partition -> 'a`` where 'a is comparable. :type optimization_metric: Callable[[Partition], Any] :param maximize: Boolean indicating whether to maximize or minimize the function. Defaults to True for maximize. :type maximize: bool, optional @@ -135,7 +135,7 @@ def _tilted_acceptance_function(self, p: float) -> Callable[[Partition], bool]: :param p: The probability of accepting a worse score. :type p: float - :return: A acceptance function for tilted chains. + :return: An acceptance function for tilted chains. :rtype: Callable[[Partition], bool] """ @@ -284,7 +284,7 @@ def logitcycle_beta_function( ) -> Callable[[int], float]: """ Class method that binds and returns a logit hot-cool cycle beta temperature function, where - the chain runs hot for some given duration, down according to the logit function + the chain runs hot for some given duration, cools down according to the logit function :math:`f(x) = (log(x/(1-x)) + 5)/10` @@ -334,7 +334,23 @@ def beta_function(step: int): def logit_jumpcycle_beta_function( cls, duration_hot: int, duration_cooldown: int, duration_cold: int ) -> Callable[[int], float]: + """ + Class method that binds and returns a logit hot-cool cycle beta temperature function, where + the chain runs hot for some given duration, cools down according to the logit function + + :math:`f(x) = (log(x/(1-x)) + 5)/10` + for some duration, and then runs cold for some duration before jumping back to hot and + repeating. + + :param duration_hot: Number of steps to run chain hot. + :type duration_hot: int + :param duration_cooldown: Number of steps needed to transition from hot to cold or + vice-versa. + :type duration_cooldown: int + :param duration_cold: Number of steps to run chain cold. + :type duration_cold: int + """ cycle_length = duration_hot + duration_cooldown + duration_cold # this will scale from 0 to 1 approximately @@ -375,6 +391,8 @@ def short_bursts( :param accept: Function accepting or rejecting the proposed state. Defaults to :func:`~gerrychain.accept.always_accept`. :type accept: Callable[[Partition], bool], optional + :param with_progress_bar: Whether or not to draw tqdm progress bar. Defaults to False. + :type with_progress_bar: bool, optional :return: Partition generator. :rtype: Generator[Partition] @@ -423,7 +441,8 @@ def simulated_annealing( the magnitude of change in score. :type beta_function: Callable[[int], float] :param beta_magnitude: Scaling parameter for how much to weight changes in score. - :type beta_magnitude: float + Defaults to 1. + :type beta_magnitude: float, optional :param with_progress_bar: Whether or not to draw tqdm progress bar. Defaults to False. :type with_progress_bar: bool, optional diff --git a/gerrychain/partition/partition.py b/gerrychain/partition/partition.py index 5025a2a8..9f484f61 100644 --- a/gerrychain/partition/partition.py +++ b/gerrychain/partition/partition.py @@ -79,20 +79,20 @@ def from_random_assignment( """ Create a Partition with a random assignment of nodes to districts. - :param graph: The graph to create the Partition from + :param graph: The graph to create the Partition from. :type graph: :class:`~gerrychain.Graph` - :param n_parts: The number of districts to divide the nodes into + :param n_parts: The number of districts to divide the nodes into. :type n_parts: int :param epsilon: The maximum relative population deviation from the ideal - population. Should be in [0,1] :type epsilon: float - :param pop_col: The column of the graph's node data that holds the population data + population. Should be in [0,1]. + :param pop_col: The column of the graph's node data that holds the population data. :type pop_col: str - :param updaters: dictionary of updaters + :param updaters: Dictionary of updaters :type updaters: Optional[Dict[str, Callable]], optional :param use_default_updaters: If `False`, do not include default updaters. :type use_default_updaters: bool, optional - :param flips: dictionary assigning nodes of the graph to their new districts + :param flips: Dictionary assigning nodes of the graph to their new districts. :type flips: Optional[Dict], optional :param method: The function to use to partition the graph into ``n_parts``. Defaults to :func:`~gerrychain.tree.recursive_tree_part`. diff --git a/gerrychain/tree.py b/gerrychain/tree.py index 1d05f2b3..06ce8433 100644 --- a/gerrychain/tree.py +++ b/gerrychain/tree.py @@ -821,14 +821,14 @@ def bipartition_tree_random( Builds up a connected subgraph with a connected complement whose population is ``epsilon * pop_target`` away from ``pop_target``. - :param graph: The graph to partition + :param graph: The graph to partition. :type graph: nx.Graph - :param pop_col: The node attribute holding the population of each node + :param pop_col: The node attribute holding the population of each node. :type pop_col: str - :param pop_target: The target population for the returned subset of nodes + :param pop_target: The target population for the returned subset of nodes. :type pop_target: Union[int, float] :param epsilon: The allowable deviation from ``pop_target`` (as a percentage of - ``pop_target``) for the subgraph's population + ``pop_target``) for the subgraph's population. :type epsilon: float :param node_repeats: A parameter for the algorithm: how many different choices of root to use before drawing a new spanning tree. Defaults to 1. @@ -898,19 +898,19 @@ def epsilon_tree_bipartition( Uses :func:`~gerrychain.tree.bipartition_tree` to partition a tree into two parts of population ``pop_target`` (within ``epsilon``). - :param graph: The graph + :param graph: The graph to partition into two :math:`\varepsilon`-balanced parts. :type graph: nx.Graph - :param parts: Iterable of part labels (like ``[0,1,2]`` or ``range(4)``) + :param parts: Iterable of part (district) labels (like ``[0,1,2]`` or ``range(4)``). :type parts: Sequence - :param pop_target: Target population for each part of the partition + :param pop_target: Target population for each part of the partition. :type pop_target: Union[float, int] - :param pop_col: Node attribute key holding population data + :param pop_col: Node attribute key holding population data. :type pop_col: str :param epsilon: How far (as a percentage of ``pop_target``) from ``pop_target`` the parts - of the partition can be + of the partition can be. :type epsilon: float :param node_repeats: Parameter for :func:`~gerrychain.tree_methods.bipartition_tree` to use. - Defaluts to 1. + Defaults to 1. :type node_repeats: int, optional :param method: The partition method to use. Defaults to `partial(bipartition_tree, max_attempts=10000)`. @@ -982,16 +982,16 @@ def recursive_tree_part( ``len(parts)`` parts of population ``pop_target`` (within ``epsilon``). Can be used to generate initial seed plans or to implement ReCom-like "merge walk" proposals. - :param graph: The graph + :param graph: The graph to partition into ``len(parts)`` :math:`\varepsilon`-balanced parts. :type graph: nx.Graph - :param parts: Iterable of part labels (like ``[0,1,2]`` or ``range(4)``) + :param parts: Iterable of part (district) labels (like ``[0,1,2]`` or ``range(4)``). :type parts: Sequence - :param pop_target: Target population for each part of the partition + :param pop_target: Target population for each part of the partition. :type pop_target: Union[float, int] - :param pop_col: Node attribute key holding population data + :param pop_col: Node attribute key holding population data. :type pop_col: str :param epsilon: How far (as a percentage of ``pop_target``) from ``pop_target`` the parts - of the partition can be + of the partition can be. :type epsilon: float :param node_repeats: Parameter for :func:`~gerrychain.tree_methods.bipartition_tree` to use. Defaluts to 1.