Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions gerrychain/graph/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
"""
This module provides the Graph class that is used
This module implements the Graph class that is used
by GerryChain code.

It exposes standard graph functionality for a dual-graph
containing nodes and edges. Both nodes and edges can have
data associated with them.

A Graph object is typically created by first creating
a NetworkX Graph object and then converting it to
a NetworkX.Graph object and then converting it to
a GerryChain Graph object using from_networkx().

For instance:

import networkx
from gerrychain import Graph

# Create a NetworkX graph
nx_graph = networkx.Graph()
nx_graph.add_edges_from(...)

# Create a GerryChain graph from the NetworkX graph
my gerrychain_graph = Graph.from_networkx(nx_graph)
Comment thread
chief-dweeb marked this conversation as resolved.
Outdated

Internally, a Graph object contains and embedded graph
Comment thread
chief-dweeb marked this conversation as resolved.
Outdated
object based either on NetworkX or RustworkX. After
creating a Partition object in GerryChain, the embedded
graph object is converted to be based on RustworkX (for
performance reasons).

The class Graph is the only part of this module that
is intended to be used directly by users of GerryChain.
"""

from .adjacency import *
Expand Down
344 changes: 344 additions & 0 deletions v1.0.0_changelog.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of changes to the public signatures, and they might be easiest to organize this in the following way:

Renamed parameters

Parameters accepting functions now generally use the _fn suffix:

  • proposalproposal_fn
    • MarkovChain
    • SingleMetricOptimizer
    • Gingleator

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started down the path of documenting every place where a parameter name was changed to end in "_fn" and then realized that I was violating the "terse" change-log goal.

This is where I stopped:

* proposal → proposal_fn
    * MarkovChain
    * SingleMetricOptimizer
    * Gingleator
* ??? → acceptance_fn
    * MarkovChain
    * SingleMetricOptimizer.short_burts()
    * ???
* ??? → value_fn
    * Bounds
* ??? → score_fn
    * Gingleator
    * SingleMetricOptimizer
* ??? → optimization_metric_fn
    * SingleMetricOptimizer
* ??? → beta_fn
    * SingleMetricOptimizer.simulated_annealing()
* ??? → container_fn
    * assignmewnt.level_sets()---

I then shifted to just showing one example with the assumption that any code that used the old named parameter would be flagged, and users could easily just say: OK - try adding an "_fn" to the name...

However, if you really want the entire list, I can do that...

Renamed parameters that accept functions

  • Parameters accepting functions now generally have names that use the _fn suffix:

    This is only an issue for function calls using named parameters.

    An example of this is the renaming of "proposal" to "proposal_fn" in the constructor for a MarkovChain object:

    Old:

    my_chain = MarkovChain(
        proposal = my_proposal,
        ...
    )
    

    New:

    my_chain = MarkovChain(
        proposal_fn = my_proposal,
        ...
    )
    

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the deprecation shims in place, and the minimality of the change (post-fixing _fn), this is probably sufficient. I'll handle the emails of anyone who is confused

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change this file name to "CHANGELOG.md". When that is finished, we can add it to the documentation by

  1. Creating a file called "docs/topics/CONTRIBUTING.md" and ad: this to it:

```{include} ../../CHANGELOG.md
```

  1. In docs/index.md, we should update the format to look like

```{toctree}
:hidden:
:caption: Topics
:maxdepth: 1

topics/v1p0p0_migration_guide
topics/reproducibility
topics/tools
```

```{toctree}
:hidden:
:caption: Help and Project
:maxdepth: 1

topics/changelog
topics/contributing
topics/reporting
```

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am inclined to let you do this, because I am not confident that I know how to configure the docs correctly.

On the one hand, how hard could it be, but on the other hand, it looks to be trivial for someone who knows how to do it, so why take the chance of screwing up?

So, I am planning to leave the current file as-is, and let you rename it in git and put it where you want and then update the docs tables of contents, etc.

Also TBD is to add the actual links in the change-log to other documentation. I have flagged each place where a link is needed with my standard: frm: TODO: prefix.

In short, I am assuming that it would take you longer to explain to me what I need to do than it would take you to just do it. If that is not correct, then just tell me to not be a scaredy-cat and to just do it...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's no problem; I'm happy to do all of this.

Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
## [v1.0.0] - August 2026

### Added
Comment thread
chief-dweeb marked this conversation as resolved.

#### Markov chains (module: chain)
* Added ability to incrementally configure the chain.

You can explicitly set class attributes, such as: `chain.initial_partition = ...`.

Functions to add constraints and updaters:

* add_constraint()
* add_constraints()
* add_updater()
* add_updaters()

Function to check whether a chain is fully configured:

* check_valid()

#### Graphs (module: graph)
* Added methods to query and access the embedded NetworkX/RustworkX graph.

* is_nx_graph()
* is_rx_graph()
* get_nx_graph()
* get_rx_graph()

* Added methods for converting between GerryChain, NetworkX, and RustworkX graphs, and to create an empty graph.

* from_networkx()
* from_rustworkx()
* from_null_networkx()
* to_networkx_graph()


* Added methods to translate node_ids between NetworkX and RustworkX and between parent graphs and subgraphs - to deal with the differences between the way NetworkX and RustworkX implement node_ids.

* original_nx_node_id_for_internal_node_id()
* internal_node_id_for_original_nx_node_id()
* get_nx_to_rx_node_id_map()
* translate_subgraph_node_ids_for_set_of_nodes()
* original_nx_node_ids_for_list()
* original_nx_node_ids_for_set()
* translate_subgraph_node_ids_for_flips()

* Added methods to handle the fact that in RustworkX, edges are different from edge_ids. In both NetworkX and RustworkX, an edge is a tuple of node_ids. In NetworkX, there is no difference between an edge and an edge_id, but in RustworkX an edge_id is an integer.

* get_edge_id_from_edge()
* get_edge_from_edge_id()

#### Proposals (module: proposals)

* Added new names for legacy functions. The new names adhere to the naming convention for creating proposal functions, "build_xxx_proposal_fn". Note that the legacy functions continue to exist.

* build_any_node_flip_proposal_fn() == propose_any_node_flip()
* build_random_flip_proposal_fn() == propose_random_flip()
* build_flip_every_district_proposal_fn() == propose_flip_every_district()
* build_chunk_flip_proposal_fn() == propose_chunk_flip()
* build_slow_reversible_proposal_fn() == slow_reversible_propose()
* build_slow_reversible_bi_proposal_fn() == slow_reversible_propose_bi()

#### ReCom proposals (module: proposals)
* Changed the ReCom class to be a namespace that defines the five variants of recom that were discussed in the article, "Spanning Tree Methods for Sampling Graph Partitions":

* A = cut_edges_mst()
* B = district_pairs_mst()
* C = cut_edges_ust()
* D = district_pairs_ust()
* R = reversible()

* Added convenience functions to create recom proposal functions:

* build_recom_proposal_fn()
* build_reversible_recom_proposal_fn()

#### Multi-member ReCom (module: proposals)

* Added functions and the MultiMemberReCom class to support multi-member ReCom. These new functions and the MultiMemberReCom class mirror the routines and class for standard ReCom:

* multi_member_recom()
* build_multi_member_recom_proposal_fn()
* epsilon_tree_bipartition_multi_member()
* class MultiMemberReCom
* cut_edges_mst()
* district_pairs_mst()
* cut_edges_ust()
* district_pairs_ust()

#### Constraints for multi-member ReCom (module: constraints)

* Added a new constraint to support multi-member ReCom

* within_percent_of_ideal_population_per_member()

### Changed

#### Graphs

* `GerryChain.Graph` no longer subclasses `networkx.Graph`. Node and edge data access has changed. See the v1.0 migration guide. [link-name](https://ibm.com)

frm: TODO: Add link to the v1.0 migration guide.

* Changed the way to access node and edge data

* To access node data, use: my_graph.node_data(...node_id...)
* To access edge data, use: my_graph.edge_data(...edge_id...)

* GerryChain now uses independent random number generators configured through parameters, rng= . See the reproducibility guide. [link-name](https://ibm.com)

frm: TODO: add link to reproducibility guide

#### Changes to function names:

* In module constraints:

* L1_polsby_popper() renamed to be: L_1_polsby_popper()
* L1_reciprocal_polsby_popper() renamed to be: L_1_reciprocal_polsby_popper()
Comment thread
chief-dweeb marked this conversation as resolved.
Outdated

* In module grid:

* create_grid_graph() renamed to be: create_grid_nx_graph()

#### Changes to names for function parameters

* GerryChain has adopted the convention that the names of parameters that are themselves function will end in "_fn".

### Removed
Comment thread
chief-dweeb marked this conversation as resolved.
Outdated
#### ?
#### ?

Comment thread
chief-dweeb marked this conversation as resolved.
Outdated
# DELETE EVERYTHING BELOW HERE
After checking with Peter

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to go after addressing above comments.


## Internal functions - not included in changelog...

📄 [ADDED FILE] _config.py

📄 [ADDED FILE] _rng.py

📄 [REMOVED FILE] _version.py

📝 File: chain.py

🔹 [Class MarkovChain] ADDED function: __lock(self)

🔹 [Class MarkovChain] ADDED function: __run(self, proposal_fn, total_steps, initial_partition)

🔹 [Class MarkovChain] ADDED function: __setattr__(self, name, value)

🔹 [Class MarkovChain] ADDED function: __unlock(self)

🔸 [Class MarkovChain] REMOVED function: __next__

📝 File: graph/graph.py

🔹 [Global] ADDED function: _add_boundary_perimeters_to_nx_graph(nx_graph, geometries)
🔸 [Global] REMOVED function: add_boundary_perimeters


🔹 [ADDED CLASS] _GeoInterface

🔹 [Class Graph] ADDED function: __iter__(self)

📝 File: proposals/tree_proposals.py

🔸 [Class ReCom] REMOVED function: __call__

📝 File: updaters/election.py

⚠️ [Class Election] SIGNATURE CHANGED: __call__
Old: (self)
New: (self, partition)

📄 [ADDED FILE] examples/__init__.py

## Stuff that I think should not be included:

📝 File: proposals/tree_proposals.py
📝 File: proposals/multi_member_tree_proposals.py

* Both epsilon_tree_bipartition() and epsilon_tree_bipartition_multi_member() have changes - epsilon_tree_bipartition() is in a different module - it used to be in tree but is now in proposals. epsilon_tree_bipartition_multi_member() is new. However, in both cases, the routines are only ever used inside the file that they are defined in, so while they are technically externally visible, I am not sure anyone cares...

📝 File: updaters/compactness.py

🔸 [Global] REMOVED function: flips

Note: this routine seems to have never been used and all it did
was return partition.flips, so I don't think mentioning it
is worth the noise.

📝 File: partition/partition.py

⚠️ [Class Partition] SIGNATURE CHANGED: flip
Old: (self, flips)
New: (self, flips, flips_passed_in_use_original_nx_node_ids)

📄 [ADDED FILE] tree/__init__.py

Note: One can still import the module, "tree", but its implementation has been split into several files.

📝 File: accept.py

🔹 [ADDED CLASS] AcceptanceFn

📝 File: graph.py

🔹 [ADDED CLASS] GraphValidationError

🔹 [Class Graph] ADDED function: laplacian_matrix(self)
🔹 [Class Graph] ADDED function: normalized_laplacian_matrix(self)

Note: Previous code relied on NetworkX Laplacian functions

🔹 [Class Graph] ADDED function: generic_bfs_predecessors(self, root_node_id)
🔹 [Class Graph] ADDED function: predecessors(self, root_node_id)
🔹 [Class Graph] ADDED function: generic_bfs_successors(self, root_node_id)
🔹 [Class Graph] ADDED function: successors(self, root_node_id)
🔹 [Class Graph] ADDED function: generic_bfs_successors_generator(self, root_node_id)

Note: Required because NetworkX and RustworkX have different ways of dealing with predecessors and successors. These routines insulate user code from having to know what the embedded graph object is.

🔹 [Class Graph] ADDED function: verify_graph_is_valid(self, thorough)

🔹 [Class Graph] ADDED function: is_directed(self)

🔹 [Class Graph] ADDED function: convert_from_nx_to_rx(self)

Note: Used to convert from NetworkX.Graph to RustworkX.PyGraph when creating a Partition object.

🔹 [Class Graph] ADDED function: minimum_spanning_tree_from_edge_weight(self, edge_weight_attribute_name)

🔹 [Class Graph] ADDED function: num_connected_components(self)
🔹 [Class Graph] ADDED function: is_node_set_connected(self, nodes)
🔹 [Class Graph] ADDED function: is_connected(self)
🔹 [Class Graph] ADDED function: subgraphs_for_connected_components(self)

📝 File: partition/partition.py

🔹 [Class Partition] ADDED function: assignment_vector(self)

📝 File: optimization/gingleator.py

🔹 [ADDED CLASS] GingleScoreFn

📝 File: partition/assignment.py

🔹 [Class Assignment] ADDED function: to_vector(self)

🔹 [Class Assignment] ADDED function: new_assignment_convert_old_node_ids_to_new_node_ids(self, node_id_mapping)

Note: Supports translating node_ids in an Assignment object - typically translating from the internal RustworkX node_ids used when running a chain back to the "original" NetworkX node_ids so that post processing a chain's data can use the "original" NetworkX node_ids.

📝 File: proposals/spectral_proposals.py

🔹 [Global] ADDED function: build_spectral_recom_proposal_fn(weight_type, lap_type)

📝 File: updaters/flows.py

🔹 [Global] ADDED function: create_edge_flow()

🔹 [ADDED CLASS] EdgeFlowUpdateFn

### Changes in function signature to add RNG param:


📝 File: accept.py

⚠️ [Global] SIGNATURE CHANGED: always_accept
Old: (partition)
New: (partition, rng)

⚠️ [Global] SIGNATURE CHANGED: cut_edge_accept
Old: (partition)
New: (partition, rng)

📝 File: chain.py
⚠️ [Class MarkovChain] SIGNATURE CHANGED: __init__
Old: (self, proposal, constraints, accept, initial_state, total_steps)
New: (self, proposal_fn, constraints, acceptance_fn, initial_partition, total_steps, rng)

📝 File: proposals/tree_proposals.py

⚠️ [Global] SIGNATURE CHANGED: reversible_recom
Old: (partition, pop_col, pop_target, epsilon, balance_edge_fn, M, repeat_until_valid, choice)
New: (partition, pop_col, pop_target, epsilon, max_balanced_edge_cuts, find_balanced_edge_cuts_fn, repeat_until_valid, rng)

⚠️ [Global] SIGNATURE CHANGED: recom
Old: (partition, pop_col, pop_target, epsilon, node_repeats, region_surcharge, method)
New: (partition, pop_col, pop_target, epsilon, node_repeats, region_surcharge, bipartition_tree_fn, pair_selection, rng)

📝 File: optimization/gingleator.py

⚠️ [Class Gingleator] SIGNATURE CHANGED: __init__
Old: (self, proposal, constraints, initial_state, minority_perc_col, threshold, score_function, minority_pop_col, total_pop_col, min_perc_column_name)
New: (self, proposal_fn, constraints, initial_state, minority_perc_col, threshold, score_fn, minority_pop_col, total_pop_col, min_perc_column_name, rng)

📝 File: optimization/optimization.py

⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: __init__
Old: (self, proposal, constraints, initial_state, optimization_metric, maximize, step_indexer)
New: (self, proposal_fn, constraints, initial_state, optimization_metric_fn, maximize, step_indexer, rng)

📝 File: partition/partition.py

⚠️ [Class Partition] SIGNATURE CHANGED: from_random_assignment
Old: (cls, graph, n_parts, epsilon, pop_col, updaters, use_default_updaters, flips, method)
New: (cls, graph, n_parts, epsilon, pop_col, updaters, use_default_updaters, partition_fn, rng)

📝 File: proposals/proposals.py

⚠️ [Global] SIGNATURE CHANGED: propose_flip_every_district
Old: (partition)
New: (partition, rng)

⚠️ [Global] SIGNATURE CHANGED: propose_random_flip
Old: (partition)
New: (partition, rng)

⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose
Old: (partition)
New: (partition, rng)

⚠️ [Global] SIGNATURE CHANGED: propose_chunk_flip
Old: (partition)
New: (partition, rng)

⚠️ [Global] SIGNATURE CHANGED: propose_any_node_flip
Old: (partition)
New: (partition, rng)

⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose_bi
Old: (partition)
New: (partition, rng)

📝 File: proposals/spectral_proposals.py

⚠️ [Global] SIGNATURE CHANGED: spectral_cut
Old: (graph, part_labels, weight_type, lap_type)
New: (subgraph, part_labels, weight_type, lap_type, rng)

⚠️ [Global] SIGNATURE CHANGED: spectral_recom
Old: (partition, weight_type, lap_type)
New: (partition, weight_type, lap_type, rng)
Loading