Skip to content

Commit

Permalink
Hexagonal lattice followup (#1232)
Browse files Browse the repository at this point in the history
* use subtests for parameter-dependent tests

* remove `utils` module from `hexagonal_lattice_graph.rs`

* pre-compute the number of edges

* split Rust test, add Python node position test

* fix factor of two in coordinates calculation

* fix offset bug when lattice has an odd number of columns

* release note for position bug fixes

---------

Co-authored-by: Matthew Treinish <[email protected]>
  • Loading branch information
jpacold and mtreinish authored Jun 28, 2024
1 parent ffcd38f commit af5a6ba
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 164 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
fixes:
- |
Fixed two bugs in the node position calculation done by the generator functions
:func:`.hexagonal_lattice_graph` and :func:`.directed_hexagonal_lattice_graph` when
``with_positions = True``:
* Corrected a scale factor that made all the hexagons in the lattice irregular
* Corrected an indexing bug that positioned the nodes in the last column of
the lattice incorrectly when ``periodic = False`` and ``cols`` is odd
311 changes: 159 additions & 152 deletions rustworkx-core/src/generators/hexagonal_lattice_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,178 +17,182 @@ use petgraph::visit::{Data, NodeIndexable};

use super::InvalidInputError;

mod utils {
use super::*;
pub struct HexagonalLatticeBuilder {
rowlen: usize, // Number of nodes in each vertical chain
collen: usize, // Number of vertical chains
num_nodes: usize, // Total number of nodes
num_edges: usize, // Total number of edges
bidirectional: bool,
periodic: bool,
}

pub struct HexagonalLatticeBuilder {
rowlen: usize, // Number of nodes in each vertical chain
collen: usize, // Number of vertical chains
num_nodes: usize, // Total number of nodes
impl HexagonalLatticeBuilder {
pub fn new(
rows: usize,
cols: usize,
bidirectional: bool,
periodic: bool,
}
) -> Result<HexagonalLatticeBuilder, InvalidInputError> {
if periodic && (cols % 2 == 1 || rows < 2 || cols < 2) {
return Err(InvalidInputError {});
}

impl HexagonalLatticeBuilder {
pub fn new(
rows: usize,
cols: usize,
bidirectional: bool,
periodic: bool,
) -> Result<HexagonalLatticeBuilder, InvalidInputError> {
if periodic && (cols % 2 == 1 || rows < 2 || cols < 2) {
return Err(InvalidInputError {});
}
let num_edges_factor = if bidirectional { 2 } else { 1 };

let (rowlen, collen, num_nodes, num_edges) = if periodic {
let r_len = 2 * rows;
(
r_len,
cols,
r_len * cols,
num_edges_factor * 3 * rows * cols,
)
} else {
let r_len = 2 * rows + 2;
(
r_len,
cols + 1,
r_len * (cols + 1) - 2,
num_edges_factor * (3 * rows * cols + 2 * (rows + cols) - 1),
)
};

let (rowlen, collen, num_nodes) = if periodic {
let r_len = 2 * rows;
(r_len, cols, r_len * cols)
} else {
let r_len = 2 * rows + 2;
(r_len, cols + 1, r_len * (cols + 1) - 2)
};

Ok(HexagonalLatticeBuilder {
rowlen,
collen,
num_nodes,
bidirectional,
periodic,
})
}
Ok(HexagonalLatticeBuilder {
rowlen,
collen,
num_nodes,
num_edges,
bidirectional,
periodic,
})
}

pub fn build_with_default_node_weight<G, T, F, H, M>(
self,
mut default_node_weight: F,
default_edge_weight: H,
) -> G
where
G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable,
F: FnMut() -> T,
H: FnMut() -> M,
G::NodeId: Eq + Hash,
{
// ToDo: be more precise about the number of edges
let mut graph = G::with_capacity(self.num_nodes, self.num_nodes);
let nodes: Vec<G::NodeId> = (0..self.num_nodes)
.map(|_| graph.add_node(default_node_weight()))
.collect();
self.add_edges(&mut graph, nodes, default_edge_weight);

graph
}
pub fn build_with_default_node_weight<G, T, F, H, M>(
self,
mut default_node_weight: F,
default_edge_weight: H,
) -> G
where
G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable,
F: FnMut() -> T,
H: FnMut() -> M,
G::NodeId: Eq + Hash,
{
let mut graph = G::with_capacity(self.num_nodes, self.num_edges);
let nodes: Vec<G::NodeId> = (0..self.num_nodes)
.map(|_| graph.add_node(default_node_weight()))
.collect();
self.add_edges(&mut graph, nodes, default_edge_weight);

pub fn build_with_position_dependent_node_weight<G, T, F, H, M>(
self,
mut node_weight: F,
default_edge_weight: H,
) -> G
where
G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable,
F: FnMut(usize, usize) -> T,
H: FnMut() -> M,
G::NodeId: Eq + Hash,
{
// ToDo: be more precise about the number of edges
let mut graph = G::with_capacity(self.num_nodes, self.num_nodes);

let lattice_position = |n| -> (usize, usize) {
if self.periodic {
(n / self.rowlen, n % self.rowlen)
graph
}

pub fn build_with_position_dependent_node_weight<G, T, F, H, M>(
self,
mut node_weight: F,
default_edge_weight: H,
) -> G
where
G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable,
F: FnMut(usize, usize) -> T,
H: FnMut() -> M,
G::NodeId: Eq + Hash,
{
let mut graph = G::with_capacity(self.num_nodes, self.num_edges);

let lattice_position = |n| -> (usize, usize) {
if self.periodic {
(n / self.rowlen, n % self.rowlen)
} else {
// In the non-periodic case the first and last vertical
// chains have rowlen - 1 = 2 * rows + 1 nodes. All others
// have rowlen = 2 * rows + 2 nodes.
if n < self.rowlen - 1 {
(0, n)
} else {
// In the non-periodic case the first and last vertical
// chains have rowlen - 1 = 2 * rows + 1 nodes. All others
// have rowlen = 2 * rows + 2 nodes.
if n < self.rowlen - 1 {
(0, n)
let k = n - (self.rowlen - 1);
let u = k / self.rowlen + 1;
let v = k % self.rowlen;
if u == self.collen - 1 && u % 2 == 0 {
(u, v + 1)
} else {
let k = n - (self.rowlen - 1);
let u = k / self.rowlen + 1;
let v = k % self.rowlen;
if u == self.collen - 1 {
(u, v + 1)
} else {
(u, v)
}
(u, v)
}
}
};
}
};

let nodes: Vec<G::NodeId> = (0..self.num_nodes)
.map(lattice_position)
.map(|(u, v)| graph.add_node(node_weight(u, v)))
.collect();
self.add_edges(&mut graph, nodes, default_edge_weight);
let nodes: Vec<G::NodeId> = (0..self.num_nodes)
.map(lattice_position)
.map(|(u, v)| graph.add_node(node_weight(u, v)))
.collect();
self.add_edges(&mut graph, nodes, default_edge_weight);

graph
}
graph
}

fn add_edges<G, H, M>(
&self,
graph: &mut G,
nodes: Vec<G::NodeId>,
mut default_edge_weight: H,
) where
G: Build + NodeIndexable + Data<EdgeWeight = M>,
H: FnMut() -> M,
{
let mut add_edge = |u, v| {
graph.add_edge(nodes[u], nodes[v], default_edge_weight());
if self.bidirectional {
graph.add_edge(nodes[v], nodes[u], default_edge_weight());
}
};
fn add_edges<G, H, M>(&self, graph: &mut G, nodes: Vec<G::NodeId>, mut default_edge_weight: H)
where
G: Build + NodeIndexable + Data<EdgeWeight = M>,
H: FnMut() -> M,
{
let mut add_edge = |u, v| {
graph.add_edge(nodes[u], nodes[v], default_edge_weight());
if self.bidirectional {
graph.add_edge(nodes[v], nodes[u], default_edge_weight());
}
};

if self.periodic {
// Add column edges
for i in 0..self.collen {
let col_start = i * self.rowlen;
for j in col_start..(col_start + self.rowlen - 1) {
add_edge(j, j + 1);
}
add_edge(col_start + self.rowlen - 1, col_start);
}
// Add row edges
for i in 0..self.collen {
let col_start = i * self.rowlen + i % 2;
for j in (col_start..(col_start + self.rowlen)).step_by(2) {
add_edge(j, (j + self.rowlen) % self.num_nodes);
}
}
} else {
// Add column edges
for j in 0..(self.rowlen - 2) {
if self.periodic {
// Add column edges
for i in 0..self.collen {
let col_start = i * self.rowlen;
for j in col_start..(col_start + self.rowlen - 1) {
add_edge(j, j + 1);
}
for i in 1..(self.collen - 1) {
for j in 0..(self.rowlen - 1) {
add_edge(i * self.rowlen + j - 1, i * self.rowlen + j);
}
add_edge(col_start + self.rowlen - 1, col_start);
}
// Add row edges
for i in 0..self.collen {
let col_start = i * self.rowlen + i % 2;
for j in (col_start..(col_start + self.rowlen)).step_by(2) {
add_edge(j, (j + self.rowlen) % self.num_nodes);
}
for j in 0..(self.rowlen - 2) {
add_edge(
(self.collen - 1) * self.rowlen + j - 1,
(self.collen - 1) * self.rowlen + j,
);
}
} else {
// Add column edges
for j in 0..(self.rowlen - 2) {
add_edge(j, j + 1);
}
for i in 1..(self.collen - 1) {
for j in 0..(self.rowlen - 1) {
add_edge(i * self.rowlen + j - 1, i * self.rowlen + j);
}
}
for j in 0..(self.rowlen - 2) {
add_edge(
(self.collen - 1) * self.rowlen + j - 1,
(self.collen - 1) * self.rowlen + j,
);
}

// Add row edges
for j in (0..(self.rowlen - 1)).step_by(2) {
add_edge(j, j + self.rowlen - 1);
}
for i in 1..(self.collen - 2) {
for j in 0..self.rowlen {
if i % 2 == j % 2 {
add_edge(i * self.rowlen + j - 1, (i + 1) * self.rowlen + j - 1);
}
// Add row edges
for j in (0..(self.rowlen - 1)).step_by(2) {
add_edge(j, j + self.rowlen - 1);
}
for i in 1..(self.collen - 2) {
for j in 0..self.rowlen {
if i % 2 == j % 2 {
add_edge(i * self.rowlen + j - 1, (i + 1) * self.rowlen + j - 1);
}
}
if self.collen > 2 {
for j in ((self.collen % 2)..self.rowlen).step_by(2) {
add_edge(
(self.collen - 2) * self.rowlen + j - 1,
(self.collen - 1) * self.rowlen + j - 1 - (self.collen % 2),
);
}
}
if self.collen > 2 {
for j in ((self.collen % 2)..self.rowlen).step_by(2) {
add_edge(
(self.collen - 2) * self.rowlen + j - 1,
(self.collen - 1) * self.rowlen + j - 1 - (self.collen % 2),
);
}
}
}
Expand Down Expand Up @@ -272,7 +276,7 @@ where
return Ok(G::with_capacity(0, 0));
}

let builder = utils::HexagonalLatticeBuilder::new(rows, cols, bidirectional, periodic)?;
let builder = HexagonalLatticeBuilder::new(rows, cols, bidirectional, periodic)?;

let graph = builder
.build_with_default_node_weight::<G, T, F, H, M>(default_node_weight, default_edge_weight);
Expand Down Expand Up @@ -357,7 +361,7 @@ where
return Ok(G::with_capacity(0, 0));
}

let builder = utils::HexagonalLatticeBuilder::new(rows, cols, bidirectional, periodic)?;
let builder = HexagonalLatticeBuilder::new(rows, cols, bidirectional, periodic)?;

let graph = builder.build_with_position_dependent_node_weight::<G, T, F, H, M>(
node_weight,
Expand Down Expand Up @@ -596,7 +600,10 @@ mod tests {
hexagonal_lattice_graph_weighted(2, 2, |u, v| (u, v), || (), false, true).unwrap();
assert_eq!(g_weighted.node_count(), 8);
check_expected_edges_directed(&g_weighted, &expected_edges);
}

#[test]
fn test_hexagonal_lattice_graph_node_weights() {
let g: petgraph::graph::UnGraph<(usize, usize), ()> =
hexagonal_lattice_graph_weighted(2, 2, |u, v| (u, v), || (), false, false).unwrap();
let expected_node_weights = vec![
Expand Down
Loading

0 comments on commit af5a6ba

Please sign in to comment.