From e4efc64953ff177e5c01c4d09ef5cdf0ca38f06d Mon Sep 17 00:00:00 2001 From: Fred Mueller Date: Wed, 29 Jul 2026 15:01:56 -0400 Subject: [PATCH 1/6] Added a new file, v1.0.0_changelog.md with what has been added, deleted, or changed (classes and functions) in the RustWorkX migration effort. --- v1.0.0_changelog.md | 520 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 520 insertions(+) create mode 100644 v1.0.0_changelog.md diff --git a/v1.0.0_changelog.md b/v1.0.0_changelog.md new file mode 100644 index 00000000..e537a600 --- /dev/null +++ b/v1.0.0_changelog.md @@ -0,0 +1,520 @@ +## [v1.0.0] - 026-0?-?? + frm: TODO: Need to add release date above + +### Added + +📝 File: accept.py + + 🔹 [ADDED CLASS] AcceptanceFn + +📝 File: chain.py + + 🔹 [Class MarkovChain] ADDED function: add_updater(self, name, updater) + 🔹 [Class MarkovChain] ADDED function: add_constraint(self, constraint) + 🔹 [Class MarkovChain] ADDED function: add_updaters(self, updaters) + 🔹 [Class MarkovChain] ADDED function: check_valid(self) + 🔹 [Class MarkovChain] ADDED function: add_constraints(self, constraints) + + Note: Added in support of building a MarkovChain incrementally. + +📝 File: constraints/validity.py + + 🔹 [Global] ADDED function: within_percent_of_ideal_population_per_member(initial_partition, members_per_district, percent, pop_key) + + Note: Added in upport of multi-member ReCom + +📝 File: graph/graph.py + + 🔹 [ADDED CLASS] GraphValidationError + + 🔹 [Class Graph] ADDED function: node_data(self, node_id) + 🔹 [Class Graph] ADDED function: edge_data(self, edge_id) + + + 🔹 [Class Graph] ADDED function: original_nx_node_id_for_internal_node_id(self, internal_node_id) + 🔹 [Class Graph] ADDED function: get_nx_to_rx_node_id_map(self) + 🔹 [Class Graph] ADDED function: translate_subgraph_node_ids_for_set_of_nodes(self, set_of_nodes) + 🔹 [Class Graph] ADDED function: internal_node_id_for_original_nx_node_id(self, original_nx_node_id) + 🔹 [Class Graph] ADDED function: original_nx_node_ids_for_list(self, list_of_node_ids) + 🔹 [Class Graph] ADDED function: original_nx_node_ids_for_set(self, set_of_node_ids) + 🔹 [Class Graph] ADDED function: translate_subgraph_node_ids_for_flips(self, flips) + + Note: Added in support of translating node_ids to/from NetworkX and RustworkX and to/from subgraphs and their parent graphs. + + 🔹 [Class Graph] ADDED function: is_nx_graph(self) + 🔹 [Class Graph] ADDED function: is_rx_graph(self) + 🔹 [Class Graph] ADDED function: get_nx_graph(self) + 🔹 [Class Graph] ADDED function: get_rx_graph(self) + + Note: Supports knowing kind of embedded graph object and accessing an embedded graph object. + + 🔹 [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: from_rustworkx(cls, rx_graph) + 🔹 [Class Graph] ADDED function: from_null_networkx(cls) + + Note: Supports creating a new GerryChain.Graph object + + 🔹 [Class Graph] ADDED function: neighbors(self, node_id) + 🔹 [Class Graph] ADDED function: degree(self, node_id) + 🔹 [Class Graph] ADDED function: add_edge(self, node_id1, node_id2) + 🔹 [Class Graph] ADDED function: subgraph(self, nodes) + + Note: Implements functions previously inherited from NetworkX.Graph + + 🔹 [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: to_networkx_graph(self) + + Note: Supports creating a native NetworkX.Graph object from a GerryChain.Graph object that has an embedded RustworkX graph. This is useful after running a chain if you want to use NetworkX code to post-process the data in the graph. + + 🔹 [Class Graph] ADDED function: get_edge_id_from_edge(self, edge) + 🔹 [Class Graph] ADDED function: get_edge_from_edge_id(self, edge_id) + + Note: Required because in RustworkX (unlike NetworkX) an edge (tuple of node_ids) is not the same as an edge_id. These functions isolate user code from having to know what the underlying embedded graph object is. + + 🔹 [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: minimum_spanning_tree_from_edge_weight(self, edge_weight_attribute_name) + + 🔹 [Class Graph] ADDED function: edges(self) + + 🔹 [Class Graph] ADDED function: num_connected_components(self) + + 🔹 [Class Graph] ADDED function: verify_graph_is_valid(self, thorough) + + 🔹 [Class Graph] ADDED function: is_node_set_connected(self, nodes) + + 🔹 [Class Graph] ADDED function: nodes(self) + + 🔹 [Class Graph] ADDED function: is_connected(self) + + 🔹 [Class Graph] ADDED function: subgraphs_for_connected_components(self) + + 🔹 [Class Graph] ADDED function: is_directed(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. + +📄 [ADDED FILE] partition/initial_partition_generators.py + + Note: The functionality in this new file used to be in tree.py, but it didn't really have much to do with trees. The code created initial partitions to be used in a partition. Moved to its own file to reduce the complexity of tree.py. + +📝 File: partition/partition.py + + 🔹 [Class Partition] ADDED function: assignment_vector(self) + +📄 [ADDED FILE] proposals/multi_member_tree_proposals.py + +📝 File: proposals/proposals.py + + 🔹 [ADDED CLASS] ProposalFn + 🔹 [Global] ADDED function: build_slow_reversible_proposal_fn() + 🔹 [Global] ADDED function: build_random_flip_proposal_fn() + 🔹 [Global] ADDED function: build_any_node_flip_proposal_fn() + 🔹 [Global] ADDED function: build_flip_every_district_proposal_fn() + 🔹 [Global] ADDED function: build_chunk_flip_proposal_fn() + 🔹 [Global] ADDED function: build_slow_reversible_bi_proposal_fn() + + Note: New functions to create a proposal function without needing to use functools.partial() + +📝 File: proposals/spectral_proposals.py + + 🔹 [Global] ADDED function: build_spectral_recom_proposal_fn(weight_type, lap_type) + +📝 File: proposals/tree_proposals.py + + 🔹 [Class ReCom] ADDED function: cut_edges_mst(pop_col, pop_target, epsilon, region_surcharge, allow_pair_reselection) + 🔹 [Class ReCom] ADDED function: cut_edges_ust(pop_col, pop_target, epsilon, allow_pair_reselection) + 🔹 [Class ReCom] ADDED function: district_pairs_mst(pop_col, pop_target, epsilon, region_surcharge, allow_pair_reselection) + 🔹 [Class ReCom] ADDED function: district_pairs_ust(pop_col, pop_target, epsilon, allow_pair_reselection) + + Note: New ReCom namespace proposal function generators that bind common proposal arguments. + + 🔹 [Global] ADDED function: build_recom_proposal_fn(pop_col, pop_target, epsilon, node_repeats, region_surcharge, bipartition_tree_fn, pair_selection) + 🔹 [Global] ADDED function: build_reversible_recom_proposal_fn(pop_col, pop_target, epsilon, max_balanced_edge_cuts, find_balanced_edge_cuts_fn, repeat_until_valid) + + Note: ReCom namespace proposal function generators that expose all possible arguments. + + 🔹 [Global] ADDED function: epsilon_tree_bipartition(subgraph_to_split, parts, pop_target, pop_col, epsilon, node_repeats, bipartition_tree_fn, rng) + + 🔹 [Class ReCom] ADDED function: reversible(pop_col, pop_target, epsilon, max_balanced_edge_cuts, repeat_until_valid) + +📄 [ADDED FILE] tree/bipartition_tree.py + + Note: Bipartition_tree functionality moved from tree.py - to reduce complexity of the code in tree.py + +📄 [ADDED FILE] tree/spanning_tree.py + + Note: Spanning tree functionality moved from tree.py - to reduce complexity of the code in tree.py + +📝 File: updaters/county_splits.py + + 🔹 [ADDED CLASS] CountyInfo + +📝 File: updaters/flows.py + + 🔹 [Global] ADDED function: create_edge_flow() + + 🔹 [ADDED CLASS] EdgeFlowUpdateFn + +### Changed + +📝 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: constraints/compactness.py + + 🔸 [Global] REMOVED function: L1_reciprocal_polsby_popper + 🔹 [Global] ADDED function: L_1_reciprocal_polsby_popper(partition) + + 🔸 [Global] REMOVED function: L1_polsby_popper + 🔹 [Global] ADDED function: L_1_polsby_popper(partition) + +📝 File: graph/graph.py + + ⚠️ [Class Graph] SIGNATURE CHANGED: from_networkx + Old: (cls, graph) + New: (cls, nx_graph) + + ⚠️ [Class Graph] SIGNATURE CHANGED: from_json + Old: (cls, json_file) + New: (cls, json_file_name) + + ⚠️ [Class Graph] SIGNATURE CHANGED: to_json + Old: (self, json_file, include_geometries_as_geojson) + New: (self, json_file_name, include_geometries_as_geojson) + +📝 File: optimization/optimization.py + + 🔸 [Class SingleMetricOptimizer] REMOVED function: score + 🔹 [Class SingleMetricOptimizer] ADDED function: score_fn(self) + + ⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: short_bursts + Old: (self, burst_length, num_bursts, accept, with_progress_bar) + New: (self, burst_length, num_bursts, acceptance_fn, with_progress_bar) + + ⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: variable_length_short_bursts + Old: (self, num_steps, stuck_buffer, accept, with_progress_bar) + New: (self, num_steps, stuck_buffer, acceptance_fn, with_progress_bar) + + ⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: simulated_annealing + Old: (self, num_steps, beta_function, beta_magnitude, with_progress_bar) + New: (self, num_steps, beta_fn, beta_magnitude, with_progress_bar) + +📝 File: partition/assignment.py + + ⚠️ [Global] SIGNATURE CHANGED: level_sets + Old: (mapping, container) + New: (mapping, container_fn) + + ⚠️ [Class Assignment] SIGNATURE CHANGED: from_dict + Old: (cls, assignment) + New: (cls, nodes_to_parts) + +📝 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) + + ⚠️ [Class Partition] SIGNATURE CHANGED: flip + Old: (self, flips) + New: (self, flips, flips_passed_in_use_original_nx_node_ids) + +📝 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) + +📝 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: updaters/county_splits.py + + 🔸 [Global] REMOVED function: total_reg_splits + 🔹 [Global] ADDED function: total_region_splits(partition, region_attr) + + ⚠️ [Global] SIGNATURE CHANGED: tally_region_splits + Old: (reg_attr_lst) + New: (region_attr_lst) + +📝 File: updaters/flows.py + + ⚠️ [Global] SIGNATURE CHANGED: on_edge_flow + Old: (initializer, alias) + New: (initializer_fn, alias) + + ⚠️ [Global] SIGNATURE CHANGED: on_flow + Old: (initializer, alias) + New: (initializer_fn, alias) + +### Removed + +📝 File: constraints/contiguity.py + + 🔸 [Global] REMOVED function: contiguous_bfs + +📝 File: graph/graph.py + + 🔸 [Class Graph] REMOVED function: lookup + + 🔸 [Class FrozenGraph] REMOVED function: lookup + +📝 File: grid.py + + 🔸 [Global] REMOVED function: create_grid_graph + +📝 File: partition/assignment.py + + 🔸 [Class Assignment] REMOVED function: items + + 🔸 [Class Assignment] REMOVED function: values + + 🔸 [Class Assignment] REMOVED function: keys + +📄 [REMOVED FILE] tree.py + + Note: To reduce the complexity of the code in tree.py, the code was split into three new files: tree/bipartition_tree.py, tree/spanning_tree.py, and partition/initial_partition_generators.py. + +📝 File: updaters/compactness.py + + 🔸 [Global] REMOVED function: flips + +## 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: _assert_initial_partition_valid(self, validator) + + 🔹 [Class MarkovChain] ADDED function: __setattr__(self, name, value) + + ⚠️ [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) + + 🔹 [Class MarkovChain] ADDED function: __unlock(self) + + 🔸 [Class MarkovChain] REMOVED function: __next__ + +📝 File: constraints/bounds.py + + 🔹 [Global] ADDED function: _fn_name(value_fn) + + ⚠️ [Class SelfConfiguringUpperBound] SIGNATURE CHANGED: __init__ + Old: (self, func) + New: (self, value_fn) + + ⚠️ [Class SelfConfiguringLowerBound] SIGNATURE CHANGED: __init__ + Old: (self, func, epsilon) + New: (self, value_fn, epsilon) + + ⚠️ [Class UpperBound] SIGNATURE CHANGED: __init__ + Old: (self, func, bound) + New: (self, value_fn, bound) + + ⚠️ [Class WithinPercentRangeOfBounds] SIGNATURE CHANGED: __init__ + Old: (self, func, percent) + New: (self, value_fn, percent) + + ⚠️ [Class LowerBound] SIGNATURE CHANGED: __init__ + Old: (self, func, bound) + New: (self, value_fn, bound) + + ⚠️ [Class Bounds] SIGNATURE CHANGED: __init__ + Old: (self, func, bounds) + New: (self, value_fn, bounds) + +📝 File: constraints/contiguity.py + + 🔸 [Global] REMOVED function: affected_parts + 🔹 [Global] ADDED function: _affected_parts(partition) + + 🔸 [Global] REMOVED function: are_reachable + 🔹 [Global] ADDED function: _are_reachable(graph, start_node, mapping, part, targets) + + 🔸 [Global] REMOVED function: _bfs + +📝 File: constraints/validity.py + +📝 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) + + 🔹 [Class Graph] ADDED function: _generic_bfs_edges(self, source) + + 🔹 [Class Graph] ADDED function: __len__(self) + + 🔸 [Class Graph] REMOVED function: __repr__ + + 🔹 [Class Graph] ADDED function: __getitem__(self, node_id) + + 🔹 [Class Graph] ADDED function: _verify_graph_thoroughly(self) + + 🔹 [Class Graph] ADDED function: __getattr__(self, __name) + + 🔸 [Class FrozenGraph] REMOVED function: __getattribute__ + + 🔹 [Class FrozenGraph] ADDED function: __getattr__(self, __name) + + ⚠️ [Class FrozenGraph] SIGNATURE CHANGED: __getitem__ + Old: (self, __name) + New: (self, node_id) + +📝 File: grid.py + + 🔸 [Global] REMOVED function: tag_boundary_nodes + 🔹 [Global] ADDED function: _tag_boundary_nodes(nx_graph, dimensions) + + 🔹 [Global] ADDED function: _create_grid_nx_graph(dimensions, with_diagonals) + + 🔸 [Global] REMOVED function: get_boundary_perim + 🔹 [Global] ADDED function: _get_boundary_perim(node_id, dimensions) + + 🔸 [Global] REMOVED function: color_quadrants + 🔹 [Global] ADDED function: _color_quadrants(node, thresholds) + + 🔸 [Class Grid] REMOVED function: as_list_of_lists + 🔹 [Class Grid] ADDED function: _as_list_of_lists(self) + +📝 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: _simulated_annealing_acceptance_function + Old: (self, beta_function, beta_magnitude) + New: (self, beta_fn, beta_magnitude) + + ⚠️ [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: proposals/tree_proposals.py + + 🔹 [Global] ADDED function: _candidate_district_pairs(partition, pair_selection, rng) + + 🔸 [Class ReCom] REMOVED function: __call__ + + ⚠️ [Class ReCom] SIGNATURE CHANGED: __init__ + Old: (self, pop_col, ideal_pop, epsilon, method) + New: (self) + +📄 [ADDED FILE] tree/__init__.py + +📝 File: updaters/cut_edges.py + + 🔹 [Global] ADDED function: _put_edges_into_parts(cut_edges, assignment) + + 🔸 [Global] REMOVED function: put_edges_into_parts + + 🔸 [Global] REMOVED function: new_cuts + 🔹 [Global] ADDED function: _new_cuts(partition) + + 🔸 [Global] REMOVED function: obsolete_cuts + 🔹 [Global] ADDED function: _obsolete_cuts(partition) + +📝 File: updaters/election.py + + ⚠️ [Class Election] SIGNATURE CHANGED: __call__ + Old: (self) + New: (self, partition) + + ⚠️ [Class Election] SIGNATURE CHANGED: __init__ + Old: (self, name, parties_to_columns, alias) + New: (self, name, party_names_to_node_attribute_names, alias) + +📝 File: updaters/tally.py + + 🔹 [Class DataTally] ADDED function: _value(self, node) + + ⚠️ [Class Tally] SIGNATURE CHANGED: __init__ + Old: (self, fields, alias, dtype) + New: (self, fields, alias, dtype_fn) + +📄 [ADDED FILE] examples/__init__.py + From 5f69be05d1abbe3b9edf4445fd4b3682a154cb0c Mon Sep 17 00:00:00 2001 From: Fred Mueller Date: Thu, 30 Jul 2026 14:46:07 -0400 Subject: [PATCH 2/6] Modified the change-log for v1.0 to mostly reformat and remove stuff. --- v1.0.0_changelog.md | 582 +++++++++++++++----------------------------- 1 file changed, 203 insertions(+), 379 deletions(-) diff --git a/v1.0.0_changelog.md b/v1.0.0_changelog.md index e537a600..971fa4a3 100644 --- a/v1.0.0_changelog.md +++ b/v1.0.0_changelog.md @@ -1,289 +1,283 @@ -## [v1.0.0] - 026-0?-?? - frm: TODO: Need to add release date above +## [v1.0.0] - August 2026 ### Added -📝 File: accept.py +#### 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() - 🔹 [ADDED CLASS] AcceptanceFn + Function to check whether a chain is fully configured: + + * check_valid() -📝 File: chain.py +#### Graphs (module: graph) +* Added methods to query and access the embedded NetworkX/RustworkX graph. - 🔹 [Class MarkovChain] ADDED function: add_updater(self, name, updater) - 🔹 [Class MarkovChain] ADDED function: add_constraint(self, constraint) - 🔹 [Class MarkovChain] ADDED function: add_updaters(self, updaters) - 🔹 [Class MarkovChain] ADDED function: check_valid(self) - 🔹 [Class MarkovChain] ADDED function: add_constraints(self, constraints) - - Note: Added in support of building a MarkovChain incrementally. + * is_nx_graph() + * is_rx_graph() + * get_nx_graph() + * get_rx_graph() -📝 File: constraints/validity.py +* Added methods for converting between GerryChain, NetworkX, and RustworkX graphs, and to create an empty graph. - 🔹 [Global] ADDED function: within_percent_of_ideal_population_per_member(initial_partition, members_per_district, percent, pop_key) - - Note: Added in upport of multi-member ReCom + * from_networkx() + * from_rustworkx() + * from_null_networkx() + * to_networkx_graph() -📝 File: graph/graph.py - 🔹 [ADDED CLASS] GraphValidationError +* 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() - 🔹 [Class Graph] ADDED function: node_data(self, node_id) - 🔹 [Class Graph] ADDED function: edge_data(self, edge_id) +* 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() - 🔹 [Class Graph] ADDED function: original_nx_node_id_for_internal_node_id(self, internal_node_id) - 🔹 [Class Graph] ADDED function: get_nx_to_rx_node_id_map(self) - 🔹 [Class Graph] ADDED function: translate_subgraph_node_ids_for_set_of_nodes(self, set_of_nodes) - 🔹 [Class Graph] ADDED function: internal_node_id_for_original_nx_node_id(self, original_nx_node_id) - 🔹 [Class Graph] ADDED function: original_nx_node_ids_for_list(self, list_of_node_ids) - 🔹 [Class Graph] ADDED function: original_nx_node_ids_for_set(self, set_of_node_ids) - 🔹 [Class Graph] ADDED function: translate_subgraph_node_ids_for_flips(self, flips) +#### Proposals (module: proposals) - Note: Added in support of translating node_ids to/from NetworkX and RustworkX and to/from subgraphs and their parent graphs. +* 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. - 🔹 [Class Graph] ADDED function: is_nx_graph(self) - 🔹 [Class Graph] ADDED function: is_rx_graph(self) - 🔹 [Class Graph] ADDED function: get_nx_graph(self) - 🔹 [Class Graph] ADDED function: get_rx_graph(self) + * 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() - Note: Supports knowing kind of embedded graph object and accessing an embedded graph object. +#### 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": - 🔹 [Class Graph] ADDED function: laplacian_matrix(self) - 🔹 [Class Graph] ADDED function: normalized_laplacian_matrix(self) + * A = cut_edges_mst() + * B = district_pairs_mst() + * C = cut_edges_ust() + * D = district_pairs_ust() + * R = reversible() - Note: Previous code relied on NetworkX Laplacian functions +* Added convenience functions to create recom proposal functions: - 🔹 [Class Graph] ADDED function: from_rustworkx(cls, rx_graph) - 🔹 [Class Graph] ADDED function: from_null_networkx(cls) + * build_recom_proposal_fn() + * build_reversible_recom_proposal_fn() - Note: Supports creating a new GerryChain.Graph object +#### Multi-member ReCom (module: proposals) - 🔹 [Class Graph] ADDED function: neighbors(self, node_id) - 🔹 [Class Graph] ADDED function: degree(self, node_id) - 🔹 [Class Graph] ADDED function: add_edge(self, node_id1, node_id2) - 🔹 [Class Graph] ADDED function: subgraph(self, nodes) +* 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: - Note: Implements functions previously inherited from NetworkX.Graph + * 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() - 🔹 [Class Graph] ADDED function: convert_from_nx_to_rx(self) +#### Constraints for multi-member ReCom (module: constraints) - Note: Used to convert from NetworkX.Graph to RustworkX.PyGraph when creating a Partition object. +* Added a new constraint to support multi-member ReCom - 🔹 [Class Graph] ADDED function: to_networkx_graph(self) + * within_percent_of_ideal_population_per_member() - Note: Supports creating a native NetworkX.Graph object from a GerryChain.Graph object that has an embedded RustworkX graph. This is useful after running a chain if you want to use NetworkX code to post-process the data in the graph. +### Changed - 🔹 [Class Graph] ADDED function: get_edge_id_from_edge(self, edge) - 🔹 [Class Graph] ADDED function: get_edge_from_edge_id(self, edge_id) +#### Graphs - Note: Required because in RustworkX (unlike NetworkX) an edge (tuple of node_ids) is not the same as an edge_id. These functions isolate user code from having to know what the underlying embedded graph object is. +* `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) - 🔹 [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) + frm: TODO: Add link to the v1.0 migration guide. - 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. +* Changed the way to access node and edge data - 🔹 [Class Graph] ADDED function: minimum_spanning_tree_from_edge_weight(self, edge_weight_attribute_name) + * To access node data, use: my_graph.node_data(...node_id...) + * To access edge data, use: my_graph.edge_data(...edge_id...) - 🔹 [Class Graph] ADDED function: edges(self) +* GerryChain now uses independent random number generators configured through parameters, rng= . See the reproducibility guide. [link-name](https://ibm.com) - 🔹 [Class Graph] ADDED function: num_connected_components(self) + frm: TODO: add link to reproducibility guide - 🔹 [Class Graph] ADDED function: verify_graph_is_valid(self, thorough) +#### Changes to function names: - 🔹 [Class Graph] ADDED function: is_node_set_connected(self, nodes) +* In module constraints: - 🔹 [Class Graph] ADDED function: nodes(self) + * L1_polsby_popper() renamed to be: L_1_polsby_popper() + * L1_reciprocal_polsby_popper() renamed to be: L_1_reciprocal_polsby_popper() - 🔹 [Class Graph] ADDED function: is_connected(self) +* In module grid: - 🔹 [Class Graph] ADDED function: subgraphs_for_connected_components(self) + * create_grid_graph() renamed to be: create_grid_nx_graph() - 🔹 [Class Graph] ADDED function: is_directed(self) +#### Changes to names for function parameters -📝 File: optimization/gingleator.py +* GerryChain has adopted the convention that the names of parameters that are themselves function will end in "_fn". - 🔹 [ADDED CLASS] GingleScoreFn +### Removed +#### ? +#### ? -📝 File: partition/assignment.py +# DELETE EVERYTHING BELOW HERE +After checking with Peter - 🔹 [Class Assignment] ADDED function: to_vector(self) +## Internal functions - not included in changelog... - 🔹 [Class Assignment] ADDED function: new_assignment_convert_old_node_ids_to_new_node_ids(self, node_id_mapping) +📄 [ADDED FILE] _config.py - 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. +📄 [ADDED FILE] _rng.py -📄 [ADDED FILE] partition/initial_partition_generators.py +📄 [REMOVED FILE] _version.py - Note: The functionality in this new file used to be in tree.py, but it didn't really have much to do with trees. The code created initial partitions to be used in a partition. Moved to its own file to reduce the complexity of tree.py. +📝 File: chain.py -📝 File: partition/partition.py + 🔹 [Class MarkovChain] ADDED function: __lock(self) - 🔹 [Class Partition] ADDED function: assignment_vector(self) + 🔹 [Class MarkovChain] ADDED function: __run(self, proposal_fn, total_steps, initial_partition) -📄 [ADDED FILE] proposals/multi_member_tree_proposals.py + 🔹 [Class MarkovChain] ADDED function: __setattr__(self, name, value) -📝 File: proposals/proposals.py + 🔹 [Class MarkovChain] ADDED function: __unlock(self) - 🔹 [ADDED CLASS] ProposalFn - 🔹 [Global] ADDED function: build_slow_reversible_proposal_fn() - 🔹 [Global] ADDED function: build_random_flip_proposal_fn() - 🔹 [Global] ADDED function: build_any_node_flip_proposal_fn() - 🔹 [Global] ADDED function: build_flip_every_district_proposal_fn() - 🔹 [Global] ADDED function: build_chunk_flip_proposal_fn() - 🔹 [Global] ADDED function: build_slow_reversible_bi_proposal_fn() + 🔸 [Class MarkovChain] REMOVED function: __next__ - Note: New functions to create a proposal function without needing to use functools.partial() +📝 File: graph/graph.py -📝 File: proposals/spectral_proposals.py + 🔹 [Global] ADDED function: _add_boundary_perimeters_to_nx_graph(nx_graph, geometries) + 🔸 [Global] REMOVED function: add_boundary_perimeters - 🔹 [Global] ADDED function: build_spectral_recom_proposal_fn(weight_type, lap_type) -📝 File: proposals/tree_proposals.py + 🔹 [ADDED CLASS] _GeoInterface - 🔹 [Class ReCom] ADDED function: cut_edges_mst(pop_col, pop_target, epsilon, region_surcharge, allow_pair_reselection) - 🔹 [Class ReCom] ADDED function: cut_edges_ust(pop_col, pop_target, epsilon, allow_pair_reselection) - 🔹 [Class ReCom] ADDED function: district_pairs_mst(pop_col, pop_target, epsilon, region_surcharge, allow_pair_reselection) - 🔹 [Class ReCom] ADDED function: district_pairs_ust(pop_col, pop_target, epsilon, allow_pair_reselection) + 🔹 [Class Graph] ADDED function: __iter__(self) - Note: New ReCom namespace proposal function generators that bind common proposal arguments. +📝 File: proposals/tree_proposals.py - 🔹 [Global] ADDED function: build_recom_proposal_fn(pop_col, pop_target, epsilon, node_repeats, region_surcharge, bipartition_tree_fn, pair_selection) - 🔹 [Global] ADDED function: build_reversible_recom_proposal_fn(pop_col, pop_target, epsilon, max_balanced_edge_cuts, find_balanced_edge_cuts_fn, repeat_until_valid) + 🔸 [Class ReCom] REMOVED function: __call__ - Note: ReCom namespace proposal function generators that expose all possible arguments. +📝 File: updaters/election.py - 🔹 [Global] ADDED function: epsilon_tree_bipartition(subgraph_to_split, parts, pop_target, pop_col, epsilon, node_repeats, bipartition_tree_fn, rng) + ⚠️ [Class Election] SIGNATURE CHANGED: __call__ + Old: (self) + New: (self, partition) - 🔹 [Class ReCom] ADDED function: reversible(pop_col, pop_target, epsilon, max_balanced_edge_cuts, repeat_until_valid) +📄 [ADDED FILE] examples/__init__.py -📄 [ADDED FILE] tree/bipartition_tree.py +## Stuff that I think should not be included: - Note: Bipartition_tree functionality moved from tree.py - to reduce complexity of the code in tree.py +📝 File: proposals/tree_proposals.py +📝 File: proposals/multi_member_tree_proposals.py -📄 [ADDED FILE] tree/spanning_tree.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... - Note: Spanning tree functionality moved from tree.py - to reduce complexity of the code in tree.py +📝 File: updaters/compactness.py -📝 File: updaters/county_splits.py + 🔸 [Global] REMOVED function: flips - 🔹 [ADDED CLASS] CountyInfo + 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: updaters/flows.py +📝 File: partition/partition.py - 🔹 [Global] ADDED function: create_edge_flow() + ⚠️ [Class Partition] SIGNATURE CHANGED: flip + Old: (self, flips) + New: (self, flips, flips_passed_in_use_original_nx_node_ids) - 🔹 [ADDED CLASS] EdgeFlowUpdateFn +📄 [ADDED FILE] tree/__init__.py -### Changed + Note: One can still import the module, "tree", but its implementation has been split into several files. 📝 File: accept.py - ⚠️ [Global] SIGNATURE CHANGED: always_accept - Old: (partition) - New: (partition, rng) + 🔹 [ADDED CLASS] AcceptanceFn - ⚠️ [Global] SIGNATURE CHANGED: cut_edge_accept - Old: (partition) - New: (partition, rng) +📝 File: graph.py + + 🔹 [ADDED CLASS] GraphValidationError + + 🔹 [Class Graph] ADDED function: laplacian_matrix(self) + 🔹 [Class Graph] ADDED function: normalized_laplacian_matrix(self) -📝 File: constraints/compactness.py + Note: Previous code relied on NetworkX Laplacian functions - 🔸 [Global] REMOVED function: L1_reciprocal_polsby_popper - 🔹 [Global] ADDED function: L_1_reciprocal_polsby_popper(partition) + 🔹 [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) - 🔸 [Global] REMOVED function: L1_polsby_popper - 🔹 [Global] ADDED function: L_1_polsby_popper(partition) + 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. -📝 File: graph/graph.py + 🔹 [Class Graph] ADDED function: verify_graph_is_valid(self, thorough) - ⚠️ [Class Graph] SIGNATURE CHANGED: from_networkx - Old: (cls, graph) - New: (cls, nx_graph) + 🔹 [Class Graph] ADDED function: is_directed(self) - ⚠️ [Class Graph] SIGNATURE CHANGED: from_json - Old: (cls, json_file) - New: (cls, json_file_name) + 🔹 [Class Graph] ADDED function: convert_from_nx_to_rx(self) - ⚠️ [Class Graph] SIGNATURE CHANGED: to_json - Old: (self, json_file, include_geometries_as_geojson) - New: (self, json_file_name, include_geometries_as_geojson) + Note: Used to convert from NetworkX.Graph to RustworkX.PyGraph when creating a Partition object. -📝 File: optimization/optimization.py + 🔹 [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) - 🔸 [Class SingleMetricOptimizer] REMOVED function: score - 🔹 [Class SingleMetricOptimizer] ADDED function: score_fn(self) +📝 File: partition/partition.py - ⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: short_bursts - Old: (self, burst_length, num_bursts, accept, with_progress_bar) - New: (self, burst_length, num_bursts, acceptance_fn, with_progress_bar) + 🔹 [Class Partition] ADDED function: assignment_vector(self) - ⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: variable_length_short_bursts - Old: (self, num_steps, stuck_buffer, accept, with_progress_bar) - New: (self, num_steps, stuck_buffer, acceptance_fn, with_progress_bar) +📝 File: optimization/gingleator.py - ⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: simulated_annealing - Old: (self, num_steps, beta_function, beta_magnitude, with_progress_bar) - New: (self, num_steps, beta_fn, beta_magnitude, with_progress_bar) + 🔹 [ADDED CLASS] GingleScoreFn 📝 File: partition/assignment.py - ⚠️ [Global] SIGNATURE CHANGED: level_sets - Old: (mapping, container) - New: (mapping, container_fn) + 🔹 [Class Assignment] ADDED function: to_vector(self) - ⚠️ [Class Assignment] SIGNATURE CHANGED: from_dict - Old: (cls, assignment) - New: (cls, nodes_to_parts) + 🔹 [Class Assignment] ADDED function: new_assignment_convert_old_node_ids_to_new_node_ids(self, node_id_mapping) -📝 File: partition/partition.py + 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. - ⚠️ [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/spectral_proposals.py - ⚠️ [Class Partition] SIGNATURE CHANGED: flip - Old: (self, flips) - New: (self, flips, flips_passed_in_use_original_nx_node_ids) + 🔹 [Global] ADDED function: build_spectral_recom_proposal_fn(weight_type, lap_type) -📝 File: proposals/proposals.py +📝 File: updaters/flows.py - ⚠️ [Global] SIGNATURE CHANGED: propose_flip_every_district - Old: (partition) - New: (partition, rng) + 🔹 [Global] ADDED function: create_edge_flow() - ⚠️ [Global] SIGNATURE CHANGED: propose_random_flip - Old: (partition) - New: (partition, rng) + 🔹 [ADDED CLASS] EdgeFlowUpdateFn - ⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose - Old: (partition) - New: (partition, rng) +### Changes in function signature to add RNG param: - ⚠️ [Global] SIGNATURE CHANGED: propose_chunk_flip - Old: (partition) - New: (partition, rng) - ⚠️ [Global] SIGNATURE CHANGED: propose_any_node_flip +📝 File: accept.py + + ⚠️ [Global] SIGNATURE CHANGED: always_accept Old: (partition) New: (partition, rng) - ⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose_bi + ⚠️ [Global] SIGNATURE CHANGED: cut_edge_accept 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) +📝 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 @@ -295,169 +289,6 @@ 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: updaters/county_splits.py - - 🔸 [Global] REMOVED function: total_reg_splits - 🔹 [Global] ADDED function: total_region_splits(partition, region_attr) - - ⚠️ [Global] SIGNATURE CHANGED: tally_region_splits - Old: (reg_attr_lst) - New: (region_attr_lst) - -📝 File: updaters/flows.py - - ⚠️ [Global] SIGNATURE CHANGED: on_edge_flow - Old: (initializer, alias) - New: (initializer_fn, alias) - - ⚠️ [Global] SIGNATURE CHANGED: on_flow - Old: (initializer, alias) - New: (initializer_fn, alias) - -### Removed - -📝 File: constraints/contiguity.py - - 🔸 [Global] REMOVED function: contiguous_bfs - -📝 File: graph/graph.py - - 🔸 [Class Graph] REMOVED function: lookup - - 🔸 [Class FrozenGraph] REMOVED function: lookup - -📝 File: grid.py - - 🔸 [Global] REMOVED function: create_grid_graph - -📝 File: partition/assignment.py - - 🔸 [Class Assignment] REMOVED function: items - - 🔸 [Class Assignment] REMOVED function: values - - 🔸 [Class Assignment] REMOVED function: keys - -📄 [REMOVED FILE] tree.py - - Note: To reduce the complexity of the code in tree.py, the code was split into three new files: tree/bipartition_tree.py, tree/spanning_tree.py, and partition/initial_partition_generators.py. - -📝 File: updaters/compactness.py - - 🔸 [Global] REMOVED function: flips - -## 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: _assert_initial_partition_valid(self, validator) - - 🔹 [Class MarkovChain] ADDED function: __setattr__(self, name, value) - - ⚠️ [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) - - 🔹 [Class MarkovChain] ADDED function: __unlock(self) - - 🔸 [Class MarkovChain] REMOVED function: __next__ - -📝 File: constraints/bounds.py - - 🔹 [Global] ADDED function: _fn_name(value_fn) - - ⚠️ [Class SelfConfiguringUpperBound] SIGNATURE CHANGED: __init__ - Old: (self, func) - New: (self, value_fn) - - ⚠️ [Class SelfConfiguringLowerBound] SIGNATURE CHANGED: __init__ - Old: (self, func, epsilon) - New: (self, value_fn, epsilon) - - ⚠️ [Class UpperBound] SIGNATURE CHANGED: __init__ - Old: (self, func, bound) - New: (self, value_fn, bound) - - ⚠️ [Class WithinPercentRangeOfBounds] SIGNATURE CHANGED: __init__ - Old: (self, func, percent) - New: (self, value_fn, percent) - - ⚠️ [Class LowerBound] SIGNATURE CHANGED: __init__ - Old: (self, func, bound) - New: (self, value_fn, bound) - - ⚠️ [Class Bounds] SIGNATURE CHANGED: __init__ - Old: (self, func, bounds) - New: (self, value_fn, bounds) - -📝 File: constraints/contiguity.py - - 🔸 [Global] REMOVED function: affected_parts - 🔹 [Global] ADDED function: _affected_parts(partition) - - 🔸 [Global] REMOVED function: are_reachable - 🔹 [Global] ADDED function: _are_reachable(graph, start_node, mapping, part, targets) - - 🔸 [Global] REMOVED function: _bfs - -📝 File: constraints/validity.py - -📝 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) - - 🔹 [Class Graph] ADDED function: _generic_bfs_edges(self, source) - - 🔹 [Class Graph] ADDED function: __len__(self) - - 🔸 [Class Graph] REMOVED function: __repr__ - - 🔹 [Class Graph] ADDED function: __getitem__(self, node_id) - - 🔹 [Class Graph] ADDED function: _verify_graph_thoroughly(self) - - 🔹 [Class Graph] ADDED function: __getattr__(self, __name) - - 🔸 [Class FrozenGraph] REMOVED function: __getattribute__ - - 🔹 [Class FrozenGraph] ADDED function: __getattr__(self, __name) - - ⚠️ [Class FrozenGraph] SIGNATURE CHANGED: __getitem__ - Old: (self, __name) - New: (self, node_id) - -📝 File: grid.py - - 🔸 [Global] REMOVED function: tag_boundary_nodes - 🔹 [Global] ADDED function: _tag_boundary_nodes(nx_graph, dimensions) - - 🔹 [Global] ADDED function: _create_grid_nx_graph(dimensions, with_diagonals) - - 🔸 [Global] REMOVED function: get_boundary_perim - 🔹 [Global] ADDED function: _get_boundary_perim(node_id, dimensions) - - 🔸 [Global] REMOVED function: color_quadrants - 🔹 [Global] ADDED function: _color_quadrants(node, thresholds) - - 🔸 [Class Grid] REMOVED function: as_list_of_lists - 🔹 [Class Grid] ADDED function: _as_list_of_lists(self) - 📝 File: optimization/gingleator.py ⚠️ [Class Gingleator] SIGNATURE CHANGED: __init__ @@ -466,55 +297,48 @@ 📝 File: optimization/optimization.py - ⚠️ [Class SingleMetricOptimizer] SIGNATURE CHANGED: _simulated_annealing_acceptance_function - Old: (self, beta_function, beta_magnitude) - New: (self, beta_fn, beta_magnitude) - ⚠️ [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: proposals/tree_proposals.py - - 🔹 [Global] ADDED function: _candidate_district_pairs(partition, pair_selection, rng) - - 🔸 [Class ReCom] REMOVED function: __call__ - - ⚠️ [Class ReCom] SIGNATURE CHANGED: __init__ - Old: (self, pop_col, ideal_pop, epsilon, method) - New: (self) - -📄 [ADDED FILE] tree/__init__.py - -📝 File: updaters/cut_edges.py - - 🔹 [Global] ADDED function: _put_edges_into_parts(cut_edges, assignment) +📝 File: partition/partition.py - 🔸 [Global] REMOVED function: put_edges_into_parts + ⚠️ [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) - 🔸 [Global] REMOVED function: new_cuts - 🔹 [Global] ADDED function: _new_cuts(partition) +📝 File: proposals/proposals.py - 🔸 [Global] REMOVED function: obsolete_cuts - 🔹 [Global] ADDED function: _obsolete_cuts(partition) + ⚠️ [Global] SIGNATURE CHANGED: propose_flip_every_district + Old: (partition) + New: (partition, rng) -📝 File: updaters/election.py + ⚠️ [Global] SIGNATURE CHANGED: propose_random_flip + Old: (partition) + New: (partition, rng) - ⚠️ [Class Election] SIGNATURE CHANGED: __call__ - Old: (self) - New: (self, partition) + ⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose + Old: (partition) + New: (partition, rng) - ⚠️ [Class Election] SIGNATURE CHANGED: __init__ - Old: (self, name, parties_to_columns, alias) - New: (self, name, party_names_to_node_attribute_names, alias) + ⚠️ [Global] SIGNATURE CHANGED: propose_chunk_flip + Old: (partition) + New: (partition, rng) -📝 File: updaters/tally.py + ⚠️ [Global] SIGNATURE CHANGED: propose_any_node_flip + Old: (partition) + New: (partition, rng) - 🔹 [Class DataTally] ADDED function: _value(self, node) + ⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose_bi + Old: (partition) + New: (partition, rng) - ⚠️ [Class Tally] SIGNATURE CHANGED: __init__ - Old: (self, fields, alias, dtype) - New: (self, fields, alias, dtype_fn) +📝 File: proposals/spectral_proposals.py -📄 [ADDED FILE] examples/__init__.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) From b27717af080b35aee55e3574eaf08df0ef842a4c Mon Sep 17 00:00:00 2001 From: Fred Mueller Date: Thu, 30 Jul 2026 15:35:37 -0400 Subject: [PATCH 3/6] Updated documentation about Graph in graph/__init__.py --- gerrychain/graph/__init__.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/gerrychain/graph/__init__.py b/gerrychain/graph/__init__.py index 7ee558e6..f91a11a5 100644 --- a/gerrychain/graph/__init__.py +++ b/gerrychain/graph/__init__.py @@ -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) + Internally, a Graph object contains and embedded graph 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 * From 6d490e18b6b9ea80513afe5ec5a8cea7d83d2cdb Mon Sep 17 00:00:00 2001 From: chief-dweeb Date: Fri, 31 Jul 2026 10:29:02 -0400 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Peter <27579114+peterrrock2@users.noreply.github.com> --- gerrychain/graph/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gerrychain/graph/__init__.py b/gerrychain/graph/__init__.py index f91a11a5..0d8cf19d 100644 --- a/gerrychain/graph/__init__.py +++ b/gerrychain/graph/__init__.py @@ -20,9 +20,9 @@ nx_graph.add_edges_from(...) # Create a GerryChain graph from the NetworkX graph - my gerrychain_graph = Graph.from_networkx(nx_graph) + my_gerrychain_graph = Graph.from_networkx(nx_graph) -Internally, a Graph object contains and embedded graph +Internally, a Graph object contains an embedded graph 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 From ac7ac74651e4d7dd24ed2ed01c4f29809876940d Mon Sep 17 00:00:00 2001 From: Fred Mueller Date: Fri, 31 Jul 2026 11:41:14 -0400 Subject: [PATCH 5/6] Responded to PR comments and suggestions... --- v1.0.0_changelog.md | 249 +++++++------------------------------------- 1 file changed, 40 insertions(+), 209 deletions(-) diff --git a/v1.0.0_changelog.md b/v1.0.0_changelog.md index 971fa4a3..35857c48 100644 --- a/v1.0.0_changelog.md +++ b/v1.0.0_changelog.md @@ -33,6 +33,10 @@ * from_null_networkx() * to_networkx_graph() +* Added method to create a fictional 8x8 grid graph of 64 unit-population nodes that is used in the User Guide. + + * gerrymandria() + * 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. @@ -106,239 +110,66 @@ * 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) +* 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() - * 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 -#### ? -#### ? - -# DELETE EVERYTHING BELOW HERE -After checking with Peter - -## 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. +#### Renamed parameters that accept functions -📝 File: accept.py +* Parameters accepting functions now generally have names that use the _fn suffix: - 🔹 [ADDED CLASS] AcceptanceFn + This is only an issue for function calls using named parameters. -📝 File: graph.py + An example of this is the renaming of "proposal" to "proposal_fn" in the constructor for a MarkovChain object: - 🔹 [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) + Old: + ``` + my_chain = MarkovChain( + proposal = my_proposal, + ... + ) + ``` + New: + ``` + my_chain = MarkovChain( + proposal_fn = my_proposal, + ... + ) + ``` +### Removed -📝 File: optimization/optimization.py +#### Removed the pre-1.0 NetworkX-derived Graph interface. - ⚠️ [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) +* gerrychain.Graph is no longer a subclass of networkx.Graph. + + See the migration guide for the replacement graph operations. -📝 File: partition/partition.py + frm: TODO: Add link for migration guide above. - ⚠️ [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) +#### Removed the old instantiable and callable ReCom interface. -📝 File: proposals/proposals.py +* The old ReCom class was instantiable and callable. It has been replaced with an interface that implements a namespace that defines functions that generate proposal functions. - ⚠️ [Global] SIGNATURE CHANGED: propose_flip_every_district - Old: (partition) - New: (partition, rng) + See the ??? for details. - ⚠️ [Global] SIGNATURE CHANGED: propose_random_flip - Old: (partition) - New: (partition, rng) + frm: TODO: Add link for ??? details. - ⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose - Old: (partition) - New: (partition, rng) +### Deprecated - ⚠️ [Global] SIGNATURE CHANGED: propose_chunk_flip - Old: (partition) - New: (partition, rng) +#### Pre-1.0 proposal and acceptance callbacks that lack `rng= `. - ⚠️ [Global] SIGNATURE CHANGED: propose_any_node_flip - Old: (partition) - New: (partition, rng) +* The callbacks now support a "reproducibility" parameter, `rng= `, that defines a random number generator. This allows the user to control the initial conditions so that the behavior of GerryChain is deterministic. - ⚠️ [Global] SIGNATURE CHANGED: slow_reversible_propose_bi - Old: (partition) - New: (partition, rng) + For more details see the reproducibility guide. [link-name](https://ibm.com) -📝 File: proposals/spectral_proposals.py + frm: TODO: add link to reproducibility guide - ⚠️ [Global] SIGNATURE CHANGED: spectral_cut - Old: (graph, part_labels, weight_type, lap_type) - New: (subgraph, part_labels, weight_type, lap_type, rng) +#### Legacy constructor and method parameter names for MarkovChain, Gingleator, SingleMetricOptimizer, etc. - ⚠️ [Global] SIGNATURE CHANGED: spectral_recom - Old: (partition, weight_type, lap_type) - New: (partition, weight_type, lap_type, rng) +* The legacy versions of these remain temporarily supported with warnings. \ No newline at end of file From e7e03f6e013f89bc6a5f7eb583f9e0ad5a2d6237 Mon Sep 17 00:00:00 2001 From: Fred Mueller Date: Fri, 31 Jul 2026 12:11:57 -0400 Subject: [PATCH 6/6] Minor updates based on PR feedback --- v1.0.0_changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v1.0.0_changelog.md b/v1.0.0_changelog.md index 35857c48..b165bb21 100644 --- a/v1.0.0_changelog.md +++ b/v1.0.0_changelog.md @@ -156,9 +156,9 @@ * The old ReCom class was instantiable and callable. It has been replaced with an interface that implements a namespace that defines functions that generate proposal functions. - See the ??? for details. + See the ??? #### The `ReCom` Namespace for details. - frm: TODO: Add link for ??? details. + frm: TODO: Add link for ??? the discussion in the Migration Guide for details. ### Deprecated