Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hexagonal lattice followup #1232

Merged
merged 8 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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