Skip to content
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
70 changes: 63 additions & 7 deletions src/lax/hypergraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,62 @@ impl<O, A> Hypergraph<O, A> {
.unwrap()
}

/// Delete the specified edges and their adjacency information.
///
/// Panics if any edge id is out of bounds.
pub fn delete_edge(&mut self, edge_ids: &[EdgeId]) {
let edge_count = self.edges.len();
assert_eq!(
edge_count,
self.adjacency.len(),
"malformed hypergraph: edges and adjacency lengths differ"
);

if edge_ids.is_empty() {
return;
}

let mut remove = vec![false; edge_count];
let mut any_removed = false;
let mut remove_count = 0usize;
for edge_id in edge_ids {
assert!(
edge_id.0 < edge_count,
"edge id {:?} is out of bounds",
edge_id
);
if !remove[edge_id.0] {
remove[edge_id.0] = true;
any_removed = true;
remove_count += 1;
}
}

if !any_removed {
return;
}

let mut edges = Vec::with_capacity(edge_count - remove_count);
let mut adjacency = Vec::with_capacity(edge_count - remove_count);
for (i, (edge, adj)) in self
.edges
.drain(..)
.zip(self.adjacency.drain(..))
.enumerate()
{
if !remove[i] {
edges.push(edge);
adjacency.push(adj);
}
}

self.edges = edges;
self.adjacency = adjacency;
}

/// Delete the specified nodes, remapping remaining node indices in adjacency and quotient.
///
/// Out-of-bounds node ids are ignored.
/// Panics if any node id is out of bounds.
pub fn delete_nodes(&mut self, node_ids: &[NodeId]) {
if node_ids.is_empty() {
return;
Expand All @@ -231,12 +284,15 @@ impl<O, A> Hypergraph<O, A> {
let mut any_removed = false;
let mut remove_count = 0usize;
for node_id in node_ids {
if node_id.0 < node_count {
if !remove[node_id.0] {
remove[node_id.0] = true;
any_removed = true;
remove_count += 1;
}
assert!(
node_id.0 < node_count,
"node id {:?} is out of bounds",
node_id
);
if !remove[node_id.0] {
remove[node_id.0] = true;
any_removed = true;
remove_count += 1;
}
}

Expand Down
125 changes: 121 additions & 4 deletions tests/lax/hypergraph.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use open_hypergraphs::lax::{Hyperedge, Hypergraph, NodeId};
use open_hypergraphs::lax::{EdgeId, Hyperedge, Hypergraph, NodeId};

#[test]
fn test_delete_nodes_remap_and_quotient() {
Expand Down Expand Up @@ -64,7 +64,8 @@ fn test_delete_nodes_all_nodes_removed() {
}

#[test]
fn test_delete_nodes_ignores_out_of_range() {
#[should_panic]
fn test_delete_nodes_panics_on_out_of_range() {
let mut h = Hypergraph::empty();
h.nodes = vec![5, 6];
h.edges = vec![1];
Expand All @@ -75,10 +76,126 @@ fn test_delete_nodes_ignores_out_of_range() {
h.quotient = (vec![NodeId(0)], vec![NodeId(1)]);

h.delete_nodes(&[NodeId(99)]);
}

#[test]
fn test_delete_edge_single() {
let mut h = Hypergraph::empty();
h.nodes = vec![10, 20, 30];
h.edges = vec![1, 2, 3];
h.adjacency = vec![
Hyperedge {
sources: vec![NodeId(0)],
targets: vec![NodeId(1)],
},
Hyperedge {
sources: vec![NodeId(2)],
targets: vec![NodeId(0), NodeId(1)],
},
Hyperedge {
sources: vec![],
targets: vec![NodeId(2)],
},
];
h.quotient = (vec![NodeId(0)], vec![NodeId(2)]);

h.delete_edge(&[EdgeId(1)]);

assert_eq!(h.nodes, vec![5, 6]);
assert_eq!(h.nodes, vec![10, 20, 30]);
assert_eq!(h.edges, vec![1, 3]);
assert_eq!(h.adjacency.len(), 2);
assert_eq!(h.adjacency[0].sources, vec![NodeId(0)]);
assert_eq!(h.adjacency[0].targets, vec![NodeId(1)]);
assert_eq!(h.adjacency[1].sources, vec![]);
assert_eq!(h.adjacency[1].targets, vec![NodeId(2)]);
assert_eq!(h.quotient.0, vec![NodeId(0)]);
assert_eq!(h.quotient.1, vec![NodeId(1)]);
assert_eq!(h.quotient.1, vec![NodeId(2)]);
}

#[test]
fn test_delete_edge_multiple_with_duplicates() {
let mut h = Hypergraph::empty();
h.nodes = vec![1, 2];
h.edges = vec![11, 22, 33, 44];
h.adjacency = vec![
Hyperedge {
sources: vec![NodeId(0)],
targets: vec![NodeId(1)],
},
Hyperedge {
sources: vec![NodeId(1)],
targets: vec![NodeId(0)],
},
Hyperedge {
sources: vec![],
targets: vec![NodeId(0)],
},
Hyperedge {
sources: vec![NodeId(0), NodeId(1)],
targets: vec![],
},
];

h.delete_edge(&[EdgeId(3), EdgeId(1), EdgeId(1)]);

assert_eq!(h.edges, vec![11, 33]);
assert_eq!(h.adjacency.len(), 2);
assert_eq!(h.adjacency[0].sources, vec![NodeId(0)]);
assert_eq!(h.adjacency[0].targets, vec![NodeId(1)]);
assert_eq!(h.adjacency[1].sources, vec![]);
assert_eq!(h.adjacency[1].targets, vec![NodeId(0)]);
}

#[test]
fn test_delete_edge_all_edges_removed() {
let mut h = Hypergraph::empty();
h.nodes = vec![7, 8, 9];
h.edges = vec![0, 1];
h.adjacency = vec![
Hyperedge {
sources: vec![NodeId(0)],
targets: vec![NodeId(2)],
},
Hyperedge {
sources: vec![NodeId(1)],
targets: vec![NodeId(0)],
},
];

h.delete_edge(&[EdgeId(0), EdgeId(1)]);

assert!(h.edges.is_empty());
assert!(h.adjacency.is_empty());
assert_eq!(h.nodes, vec![7, 8, 9]);
}

#[test]
fn test_delete_edge_empty_input_no_change() {
let mut h = Hypergraph::empty();
h.nodes = vec![1];
h.edges = vec![99];
h.adjacency = vec![Hyperedge {
sources: vec![],
targets: vec![NodeId(0)],
}];

h.delete_edge(&[]);

assert_eq!(h.edges, vec![99]);
assert_eq!(h.adjacency.len(), 1);
assert_eq!(h.adjacency[0].targets, vec![NodeId(0)]);
}

#[test]
#[should_panic]
fn test_delete_edge_panics_on_out_of_bounds() {
let mut h = Hypergraph::empty();
h.nodes = vec![1];
h.edges = vec![5];
h.adjacency = vec![Hyperedge {
sources: vec![],
targets: vec![NodeId(0)],
}];

h.delete_edge(&[EdgeId(1)]);
}