use super::cache::{
CachedProperty, PropertyCache, invalidate_after_add_edges, invalidate_after_add_vertices,
};
use super::error::{IgraphError, IgraphResult};
pub type VertexId = u32;
pub type EdgeId = u32;
#[derive(Debug, Clone, Default)]
pub struct Graph {
n: u32,
directed: bool,
from: Vec<VertexId>,
to: Vec<VertexId>,
oi: Vec<EdgeId>,
ii: Vec<EdgeId>,
os: Vec<u32>,
is: Vec<u32>,
cache: PropertyCache,
}
impl Graph {
pub fn new(n: u32, directed: bool) -> IgraphResult<Self> {
let mut g = Self {
n: 0,
directed,
from: Vec::new(),
to: Vec::new(),
oi: Vec::new(),
ii: Vec::new(),
os: vec![0],
is: vec![0],
cache: PropertyCache::new(),
};
g.add_vertices(n)?;
Ok(g)
}
pub fn from_edges(
edges: &[(u32, u32)],
directed: bool,
n_override: Option<u32>,
) -> IgraphResult<Self> {
let max_id = edges
.iter()
.flat_map(|&(u, v)| [u, v])
.max()
.map_or(Some(0), |m| m.checked_add(1));
let auto_n = max_id.ok_or(IgraphError::InvalidArgument(
"vertex id overflow in from_edges".to_owned(),
))?;
let n = n_override.map_or(auto_n, |ov| ov.max(auto_n));
let mut g = Self::new(n, directed)?;
g.add_edges(edges.to_vec())?;
Ok(g)
}
pub fn with_vertices(n: u32) -> Self {
let len = n as usize + 1;
Self {
n,
directed: false,
from: Vec::new(),
to: Vec::new(),
oi: Vec::new(),
ii: Vec::new(),
os: vec![0; len],
is: vec![0; len],
cache: PropertyCache::new(),
}
}
#[must_use]
pub fn vcount(&self) -> u32 {
self.n
}
#[must_use]
pub fn ecount(&self) -> usize {
self.from.len()
}
#[must_use]
pub fn is_directed(&self) -> bool {
self.directed
}
pub fn add_vertices(&mut self, nv: u32) -> IgraphResult<(VertexId, VertexId)> {
let new_n = self
.n
.checked_add(nv)
.ok_or(IgraphError::Internal("vertex count overflow"))?;
let first = self.n;
let ec = u32::try_from(self.ecount())
.map_err(|_| IgraphError::Internal("edge count exceeds u32::MAX"))?;
for _ in 0..nv {
self.os.push(ec);
self.is.push(ec);
}
self.n = new_n;
if nv > 0 {
invalidate_after_add_vertices(&self.cache);
}
Ok((first, new_n.saturating_sub(1)))
}
pub fn add_edge(&mut self, u: VertexId, v: VertexId) -> IgraphResult<()> {
self.add_edges(std::iter::once((u, v)))
}
pub fn add_edges<I>(&mut self, edges: I) -> IgraphResult<()>
where
I: IntoIterator<Item = (VertexId, VertexId)>,
{
let m_before = self.ecount();
for (u, v) in edges {
self.check_vertex(u)?;
self.check_vertex(v)?;
if !self.directed && u > v {
self.from.push(v);
self.to.push(u);
} else {
self.from.push(u);
self.to.push(v);
}
}
self.rebuild_indexes()?;
if self.ecount() > m_before {
invalidate_after_add_edges(&self.cache);
}
Ok(())
}
pub fn neighbors(&self, v: VertexId) -> IgraphResult<Vec<VertexId>> {
self.check_vertex(v)?;
let v_idx = v as usize;
if self.directed {
let out_range = self.os[v_idx] as usize..self.os[v_idx + 1] as usize;
let out: Vec<VertexId> = self.oi[out_range]
.iter()
.map(|&e| self.to[e as usize])
.collect();
Ok(out)
} else {
let out_start = self.os[v_idx] as usize;
let out_end = self.os[v_idx + 1] as usize;
let in_start = self.is[v_idx] as usize;
let in_end = self.is[v_idx + 1] as usize;
let mut out = Vec::with_capacity((out_end - out_start) + (in_end - in_start));
let mut out_idx = out_start;
let mut in_idx = in_start;
while out_idx < out_end && in_idx < in_end {
let a = self.to[self.oi[out_idx] as usize];
let b = self.from[self.ii[in_idx] as usize];
if a <= b {
out.push(a);
out_idx += 1;
} else {
out.push(b);
in_idx += 1;
}
}
while out_idx < out_end {
out.push(self.to[self.oi[out_idx] as usize]);
out_idx += 1;
}
while in_idx < in_end {
out.push(self.from[self.ii[in_idx] as usize]);
in_idx += 1;
}
Ok(out)
}
}
pub fn degree(&self, v: VertexId) -> IgraphResult<usize> {
self.check_vertex(v)?;
let v_idx = v as usize;
let out = (self.os[v_idx + 1] - self.os[v_idx]) as usize;
if self.directed {
let in_count = (self.is[v_idx + 1] - self.is[v_idx]) as usize;
Ok(out + in_count)
} else {
let in_count = (self.is[v_idx + 1] - self.is[v_idx]) as usize;
Ok(out + in_count)
}
}
pub fn edge_source(&self, eid: EdgeId) -> IgraphResult<VertexId> {
self.check_edge(eid)?;
Ok(self.from[eid as usize])
}
pub fn edge_target(&self, eid: EdgeId) -> IgraphResult<VertexId> {
self.check_edge(eid)?;
Ok(self.to[eid as usize])
}
pub fn edge(&self, eid: EdgeId) -> IgraphResult<(VertexId, VertexId)> {
self.check_edge(eid)?;
let i = eid as usize;
Ok((self.from[i], self.to[i]))
}
pub fn edge_other(&self, eid: EdgeId, vid: VertexId) -> IgraphResult<VertexId> {
let (u, v) = self.edge(eid)?;
if vid == u {
Ok(v)
} else if vid == v {
Ok(u)
} else {
Err(IgraphError::InvalidArgument(format!(
"vertex {vid} is not an endpoint of edge {eid} ({u}, {v})"
)))
}
}
pub fn incident(&self, v: VertexId) -> IgraphResult<Vec<EdgeId>> {
self.check_vertex(v)?;
let v_idx = v as usize;
let out_range = self.os[v_idx] as usize..self.os[v_idx + 1] as usize;
if self.directed {
Ok(self.oi[out_range].to_vec())
} else {
let in_range = self.is[v_idx] as usize..self.is[v_idx + 1] as usize;
let mut out = Vec::with_capacity(out_range.len() + in_range.len());
out.extend_from_slice(&self.oi[out_range]);
out.extend_from_slice(&self.ii[in_range]);
Ok(out)
}
}
pub(crate) fn incident_in(&self, v: VertexId) -> IgraphResult<Vec<EdgeId>> {
self.check_vertex(v)?;
let v_idx = v as usize;
if self.directed {
let in_range = self.is[v_idx] as usize..self.is[v_idx + 1] as usize;
Ok(self.ii[in_range].to_vec())
} else {
self.incident(v)
}
}
pub fn get_eid(&self, from: VertexId, to: VertexId) -> IgraphResult<EdgeId> {
self.find_eid(from, to)?
.ok_or_else(|| IgraphError::InvalidArgument(format!("no edge between {from} and {to}")))
}
pub fn find_eid(&self, from: VertexId, to: VertexId) -> IgraphResult<Option<EdgeId>> {
self.check_vertex(from)?;
self.check_vertex(to)?;
if self.directed {
let range = self.os[from as usize] as usize..self.os[from as usize + 1] as usize;
for &e in &self.oi[range] {
if self.to[e as usize] == to {
return Ok(Some(e));
}
}
Ok(None)
} else {
let (lo, hi) = if from <= to { (from, to) } else { (to, from) };
let range = self.os[lo as usize] as usize..self.os[lo as usize + 1] as usize;
for &e in &self.oi[range] {
if self.to[e as usize] == hi {
return Ok(Some(e));
}
}
Ok(None)
}
}
pub fn get_all_eids_between(&self, from: VertexId, to: VertexId) -> IgraphResult<Vec<EdgeId>> {
self.check_vertex(from)?;
self.check_vertex(to)?;
let mut out = Vec::new();
if self.directed {
let range = self.os[from as usize] as usize..self.os[from as usize + 1] as usize;
for &e in &self.oi[range] {
if self.to[e as usize] == to {
out.push(e);
}
}
} else {
let (lo, hi) = if from <= to { (from, to) } else { (to, from) };
let range = self.os[lo as usize] as usize..self.os[lo as usize + 1] as usize;
for &e in &self.oi[range] {
if self.to[e as usize] == hi {
out.push(e);
}
}
}
out.sort_unstable();
Ok(out)
}
pub(crate) fn out_neighbors_vec(&self, v: VertexId) -> IgraphResult<Vec<VertexId>> {
self.check_vertex(v)?;
let v_idx = v as usize;
let range = self.os[v_idx] as usize..self.os[v_idx + 1] as usize;
Ok(self.oi[range]
.iter()
.map(|&e| self.to[e as usize])
.collect())
}
pub(crate) fn in_neighbors_vec(&self, v: VertexId) -> IgraphResult<Vec<VertexId>> {
self.check_vertex(v)?;
let v_idx = v as usize;
let range = self.is[v_idx] as usize..self.is[v_idx + 1] as usize;
Ok(self.ii[range]
.iter()
.map(|&e| self.from[e as usize])
.collect())
}
pub fn delete_edges(&mut self, edges: &[EdgeId]) -> IgraphResult<()> {
let m = self.ecount();
let m_u32 = u32::try_from(m).unwrap_or(u32::MAX);
for &eid in edges {
if (eid as usize) >= m {
return Err(IgraphError::EdgeOutOfRange { id: eid, m: m_u32 });
}
}
if edges.is_empty() {
return Ok(());
}
let mut remove = vec![false; m];
for &eid in edges {
remove[eid as usize] = true;
}
let mut new_from: Vec<VertexId> = Vec::with_capacity(m);
let mut new_to: Vec<VertexId> = Vec::with_capacity(m);
for (e, &is_removed) in remove.iter().enumerate() {
if !is_removed {
new_from.push(self.from[e]);
new_to.push(self.to[e]);
}
}
self.from = new_from;
self.to = new_to;
self.rebuild_indexes()?;
self.cache.invalidate_all();
Ok(())
}
pub fn delete_vertices(&mut self, vertices: &[VertexId]) -> IgraphResult<()> {
self.delete_vertices_map(vertices).map(|_| ())
}
pub fn delete_vertices_map(
&mut self,
vertices: &[VertexId],
) -> IgraphResult<(Vec<Option<VertexId>>, Vec<VertexId>)> {
let n_u32 = self.n;
let n = n_u32 as usize;
for &vid in vertices {
if vid >= n_u32 {
return Err(IgraphError::VertexOutOfRange { id: vid, n: n_u32 });
}
}
let mut remove = vec![false; n];
for &vid in vertices {
remove[vid as usize] = true;
}
let mut map: Vec<Option<VertexId>> = vec![None; n];
let mut invmap: Vec<VertexId> = Vec::new();
let mut next_new: u32 = 0;
for (i, &is_removed) in remove.iter().enumerate() {
if !is_removed {
let i_u32 = u32::try_from(i)
.map_err(|_| IgraphError::Internal("vertex index exceeds u32::MAX"))?;
map[i] = Some(next_new);
invmap.push(i_u32);
next_new = next_new
.checked_add(1)
.ok_or(IgraphError::Internal("new vertex count overflow"))?;
}
}
let m = self.ecount();
let mut new_from: Vec<VertexId> = Vec::with_capacity(m);
let mut new_to: Vec<VertexId> = Vec::with_capacity(m);
for (u, v) in self.from.iter().zip(self.to.iter()) {
if let (Some(nu), Some(nv)) = (map[*u as usize], map[*v as usize]) {
new_from.push(nu);
new_to.push(nv);
}
}
self.n = next_new;
self.from = new_from;
self.to = new_to;
self.rebuild_indexes()?;
self.cache.invalidate_all();
Ok((map, invmap))
}
#[must_use]
pub fn cache_get(&self, prop: CachedProperty) -> Option<bool> {
self.cache.get(prop)
}
pub fn cache_set(&self, prop: CachedProperty, value: bool) {
self.cache.set(prop, value);
}
pub fn cache_invalidate(&self, prop: CachedProperty) {
self.cache.invalidate(prop);
}
pub fn cache_invalidate_all(&self) {
self.cache.invalidate_all();
}
fn check_vertex(&self, v: VertexId) -> IgraphResult<()> {
if v >= self.n {
return Err(IgraphError::VertexOutOfRange { id: v, n: self.n });
}
Ok(())
}
fn check_edge(&self, eid: EdgeId) -> IgraphResult<()> {
let m = self.ecount();
let m_u32 = u32::try_from(m).unwrap_or(u32::MAX);
if (eid as usize) >= m {
return Err(IgraphError::EdgeOutOfRange { id: eid, m: m_u32 });
}
Ok(())
}
fn rebuild_indexes(&mut self) -> IgraphResult<()> {
let m = self.ecount();
let n = self.n as usize;
let mut tuples: Vec<(VertexId, VertexId, u32)> = (0..m)
.map(|e| {
Ok::<_, IgraphError>((
self.from[e],
self.to[e],
u32::try_from(e)
.map_err(|_| IgraphError::Internal("edge id exceeds u32::MAX"))?,
))
})
.collect::<Result<_, _>>()?;
tuples.sort_unstable_by_key(|a| (a.0, a.1));
self.oi = tuples.iter().map(|t| t.2).collect();
self.os = vec![0u32; n + 1];
for &(u, _, _) in &tuples {
self.os[u as usize + 1] = self.os[u as usize + 1]
.checked_add(1)
.ok_or(IgraphError::Internal("degree overflow in rebuild_indexes"))?;
}
for i in 1..=n {
self.os[i] = self.os[i]
.checked_add(self.os[i - 1])
.ok_or(IgraphError::Internal("offset overflow in rebuild_indexes"))?;
}
let mut tuples: Vec<(VertexId, VertexId, u32)> = (0..m)
.map(|e| {
Ok::<_, IgraphError>((
self.to[e],
self.from[e],
u32::try_from(e)
.map_err(|_| IgraphError::Internal("edge id exceeds u32::MAX"))?,
))
})
.collect::<Result<_, _>>()?;
tuples.sort_unstable_by_key(|a| (a.0, a.1));
self.ii = tuples.iter().map(|t| t.2).collect();
self.is = vec![0u32; n + 1];
for &(v, _, _) in &tuples {
self.is[v as usize + 1] = self.is[v as usize + 1]
.checked_add(1)
.ok_or(IgraphError::Internal("degree overflow in rebuild_indexes"))?;
}
for i in 1..=n {
self.is[i] = self.is[i]
.checked_add(self.is[i - 1])
.ok_or(IgraphError::Internal("offset overflow in rebuild_indexes"))?;
}
Ok(())
}
}
impl std::fmt::Display for Graph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let kind = if self.directed {
"Directed"
} else {
"Undirected"
};
write!(
f,
"{kind} graph with {} vertices and {} edges",
self.n,
self.ecount()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_counts() {
let g = Graph::with_vertices(0);
assert_eq!(g.vcount(), 0);
assert_eq!(g.ecount(), 0);
assert!(!g.is_directed());
}
#[test]
fn new_directed_flag() {
let g = Graph::new(3, true).unwrap();
assert!(g.is_directed());
let g = Graph::new(3, false).unwrap();
assert!(!g.is_directed());
}
#[test]
fn add_vertices_then_edges() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(g.vcount(), 3);
assert_eq!(g.ecount(), 2);
assert_eq!(g.degree(1).unwrap(), 2);
let mut nbrs = g.neighbors(1).unwrap();
nbrs.sort_unstable();
assert_eq!(nbrs, vec![0, 2]);
}
#[test]
fn out_of_range_vertex_errors() {
let mut g = Graph::with_vertices(2);
let err = g.add_edge(0, 5).unwrap_err();
assert!(matches!(err, IgraphError::VertexOutOfRange { id: 5, n: 2 }));
}
#[test]
fn self_loop_counted_correctly() {
let mut g = Graph::with_vertices(1);
g.add_edge(0, 0).unwrap();
assert_eq!(g.ecount(), 1);
assert_eq!(g.degree(0).unwrap(), 2);
let mut nbrs = g.neighbors(0).unwrap();
nbrs.sort_unstable();
assert_eq!(nbrs, vec![0, 0]);
}
#[test]
fn parallel_edges() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(g.ecount(), 2);
assert_eq!(g.degree(0).unwrap(), 2);
assert_eq!(g.degree(1).unwrap(), 2);
}
#[test]
fn undirected_canonicalisation() {
let mut g = Graph::with_vertices(2);
g.add_edge(1, 0).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(g.ecount(), 2);
let mut n0 = g.neighbors(0).unwrap();
let mut n1 = g.neighbors(1).unwrap();
n0.sort_unstable();
n1.sort_unstable();
assert_eq!(n0, vec![1, 1]);
assert_eq!(n1, vec![0, 0]);
}
#[test]
fn directed_neighbors_are_outgoing_only() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(2, 0).unwrap();
assert_eq!(g.neighbors(0).unwrap(), vec![1]);
assert_eq!(g.neighbors(2).unwrap(), vec![0]);
assert!(g.neighbors(1).unwrap().is_empty());
assert_eq!(g.degree(0).unwrap(), 2); assert_eq!(g.degree(1).unwrap(), 1); assert_eq!(g.degree(2).unwrap(), 1); }
#[test]
fn add_edges_batch_then_rebuild() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0, 1), (0, 2), (1, 2), (2, 3)]).unwrap();
assert_eq!(g.ecount(), 4);
assert_eq!(g.degree(0).unwrap(), 2);
assert_eq!(g.degree(1).unwrap(), 2);
assert_eq!(g.degree(2).unwrap(), 3);
assert_eq!(g.degree(3).unwrap(), 1);
}
#[test]
fn clone_is_deep() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
let g2 = g.clone();
g.add_edge(0, 2).unwrap();
assert_eq!(g.ecount(), 3);
assert_eq!(g2.ecount(), 2);
}
#[test]
fn os_invariant_is_monotone() {
let mut g = Graph::with_vertices(5);
g.add_edges(vec![(0, 1), (0, 2), (3, 4), (1, 2)]).unwrap();
for w in g.os.windows(2) {
assert!(w[0] <= w[1]);
}
assert_eq!(g.os[0], 0);
assert_eq!(*g.os.last().unwrap() as usize, g.ecount());
}
#[test]
fn vertex_out_of_range_when_adding_edge() {
let mut g = Graph::with_vertices(2);
let e = g.add_edge(2, 0).unwrap_err();
assert!(matches!(e, IgraphError::VertexOutOfRange { id: 2, n: 2 }));
assert_eq!(g.ecount(), 0);
}
#[test]
fn edge_endpoints_round_trip() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0, 1), (2, 0), (1, 2)]).unwrap();
assert_eq!(g.edge(0).unwrap(), (0, 1));
assert_eq!(g.edge(1).unwrap(), (2, 0));
assert_eq!(g.edge(2).unwrap(), (1, 2));
assert_eq!(g.edge_source(1).unwrap(), 2);
assert_eq!(g.edge_target(1).unwrap(), 0);
}
#[test]
fn edge_other_endpoint() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 2).unwrap();
assert_eq!(g.edge_other(0, 0).unwrap(), 2);
assert_eq!(g.edge_other(0, 2).unwrap(), 0);
let err = g.edge_other(0, 1).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn edge_out_of_range() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = g.edge(5).unwrap_err();
assert!(matches!(err, IgraphError::EdgeOutOfRange { id: 5, m: 1 }));
}
#[test]
fn incident_returns_edge_ids_matching_neighbors_order() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0, 1), (0, 2), (3, 0)]).unwrap();
let eids = g.incident(0).unwrap();
let resolved: Vec<u32> = eids.iter().map(|&e| g.edge_other(e, 0).unwrap()).collect();
assert_eq!(resolved, g.neighbors(0).unwrap());
}
#[test]
fn incident_self_loop_appears_twice_undirected() {
let mut g = Graph::with_vertices(1);
g.add_edge(0, 0).unwrap();
let eids = g.incident(0).unwrap();
assert_eq!(eids, vec![0, 0]);
assert_eq!(g.degree(0).unwrap(), 2);
}
#[test]
fn incident_directed_returns_outgoing_only() {
let mut g = Graph::new(3, true).unwrap();
g.add_edges(vec![(0, 1), (2, 0)]).unwrap();
assert_eq!(g.incident(0).unwrap(), vec![0]);
assert_eq!(g.incident(2).unwrap(), vec![1]);
assert!(g.incident(1).unwrap().is_empty());
}
#[test]
fn get_eid_undirected_finds_edge_either_way() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); assert_eq!(g.get_eid(0, 1).unwrap(), 0);
assert_eq!(g.get_eid(1, 0).unwrap(), 0);
assert_eq!(g.get_eid(1, 2).unwrap(), 1);
assert_eq!(g.get_eid(2, 1).unwrap(), 1);
}
#[test]
fn get_eid_directed_respects_direction() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap(); assert_eq!(g.get_eid(0, 1).unwrap(), 0);
assert!(g.get_eid(1, 0).is_err()); }
#[test]
fn find_eid_returns_none_for_missing() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
assert_eq!(g.find_eid(0, 2).unwrap(), None);
assert!(g.find_eid(0, 99).is_err()); }
#[test]
fn get_eid_self_loop() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap(); assert_eq!(g.get_eid(0, 0).unwrap(), 0);
assert_eq!(g.get_eid(0, 1).unwrap(), 1);
}
#[test]
fn get_all_eids_between_returns_all_parallel() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); let eids = g.get_all_eids_between(0, 1).unwrap();
assert_eq!(eids, vec![0, 1, 2]);
let eids = g.get_all_eids_between(1, 0).unwrap();
assert_eq!(eids, vec![0, 1, 2]);
}
#[test]
fn get_all_eids_between_directed_one_way_only() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); assert_eq!(g.get_all_eids_between(0, 1).unwrap(), vec![0, 1]);
assert_eq!(g.get_all_eids_between(1, 0).unwrap(), Vec::<EdgeId>::new());
}
#[test]
fn get_eid_returns_lowest_id_for_parallel() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); assert_eq!(g.get_eid(0, 1).unwrap(), 0);
}
#[test]
fn delete_edges_empty_input_is_noop() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
g.delete_edges(&[]).unwrap();
assert_eq!(g.ecount(), 2);
assert_eq!(g.degree(1).unwrap(), 2);
}
#[test]
fn delete_edges_single_edge_undirected() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2), (0, 2)]).unwrap();
g.delete_edges(&[1]).unwrap();
assert_eq!(g.ecount(), 2);
assert_eq!(g.find_eid(0, 1).unwrap(), Some(0));
assert_eq!(g.find_eid(0, 2).unwrap(), Some(1));
assert_eq!(g.find_eid(1, 2).unwrap(), None);
assert_eq!(g.degree(1).unwrap(), 1);
assert_eq!(g.degree(2).unwrap(), 1);
}
#[test]
fn delete_edges_duplicate_ids_tolerated() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
g.delete_edges(&[0, 0, 0]).unwrap();
assert_eq!(g.ecount(), 1);
assert_eq!(g.find_eid(1, 2).unwrap(), Some(0));
}
#[test]
fn delete_edges_all_edges_leaves_isolated_vertices() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
g.delete_edges(&[0, 1]).unwrap();
assert_eq!(g.ecount(), 0);
assert_eq!(g.vcount(), 3);
for v in 0..3 {
assert_eq!(g.degree(v).unwrap(), 0);
}
}
#[test]
fn delete_edges_out_of_range_errors_and_preserves_state() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
let err = g.delete_edges(&[5]).unwrap_err();
assert!(matches!(err, IgraphError::EdgeOutOfRange { id: 5, m: 2 }));
assert_eq!(g.ecount(), 2);
}
#[test]
fn delete_edges_self_loop_directed() {
let mut g = Graph::new(2, true).unwrap();
g.add_edges(vec![(0, 0), (0, 1)]).unwrap();
g.delete_edges(&[0]).unwrap(); assert_eq!(g.ecount(), 1);
assert_eq!(g.degree(0).unwrap(), 1);
assert_eq!(g.find_eid(0, 1).unwrap(), Some(0));
}
#[test]
fn delete_vertices_empty_input_is_noop() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
g.delete_vertices(&[]).unwrap();
assert_eq!(g.vcount(), 3);
assert_eq!(g.ecount(), 2);
}
#[test]
fn delete_vertices_single_renumbers() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0, 1), (1, 2), (2, 3), (0, 3)]).unwrap();
g.delete_vertices(&[1]).unwrap();
assert_eq!(g.vcount(), 3);
assert_eq!(g.ecount(), 2);
assert!(g.find_eid(1, 2).unwrap().is_some());
assert!(g.find_eid(0, 2).unwrap().is_some());
assert_eq!(g.find_eid(0, 1).unwrap(), None);
}
#[test]
fn delete_vertices_duplicate_ids_tolerated() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
g.delete_vertices(&[1, 1, 1]).unwrap();
assert_eq!(g.vcount(), 2);
assert_eq!(g.ecount(), 0);
}
#[test]
fn delete_vertices_all_yields_empty_graph() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
g.delete_vertices(&[0, 1, 2]).unwrap();
assert_eq!(g.vcount(), 0);
assert_eq!(g.ecount(), 0);
}
#[test]
fn delete_vertices_out_of_range_errors_and_preserves_state() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
let err = g.delete_vertices(&[5]).unwrap_err();
assert!(matches!(err, IgraphError::VertexOutOfRange { id: 5, n: 3 }));
assert_eq!(g.vcount(), 3);
assert_eq!(g.ecount(), 2);
}
#[test]
fn delete_vertices_map_returns_correct_mappings() {
let mut g = Graph::with_vertices(5);
g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
let (map, invmap) = g.delete_vertices_map(&[1, 3]).unwrap();
assert_eq!(map, vec![Some(0), None, Some(1), None, Some(2)]);
assert_eq!(invmap, vec![0, 2, 4]);
assert_eq!(g.vcount(), 3);
assert_eq!(g.ecount(), 0);
}
#[test]
fn delete_vertices_directed_preserves_direction() {
let mut g = Graph::new(4, true).unwrap();
g.add_edges(vec![(0, 1), (1, 2), (2, 0), (3, 0)]).unwrap();
g.delete_vertices(&[3]).unwrap();
assert_eq!(g.vcount(), 3);
assert!(g.is_directed());
assert!(g.get_eid(0, 1).is_ok());
assert!(g.get_eid(1, 0).is_err()); }
#[test]
fn delete_vertices_self_loop_on_removed_vertex() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 0), (0, 1), (1, 2)]).unwrap();
g.delete_vertices(&[0]).unwrap();
assert_eq!(g.vcount(), 2);
assert_eq!(g.ecount(), 1);
assert!(g.find_eid(0, 1).unwrap().is_some());
}
#[test]
fn delete_vertices_preserves_parallel_edges() {
let mut g = Graph::with_vertices(3);
g.add_edges(vec![(0, 1), (0, 1), (1, 2)]).unwrap();
g.delete_vertices(&[2]).unwrap();
assert_eq!(g.vcount(), 2);
assert_eq!(g.ecount(), 2); assert_eq!(g.degree(0).unwrap(), 2);
assert_eq!(g.degree(1).unwrap(), 2);
}
#[test]
fn add_edges_after_delete_works() {
let mut g = Graph::with_vertices(4);
g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
g.delete_vertices(&[0]).unwrap(); g.add_edge(0, 2).unwrap();
assert_eq!(g.ecount(), 3);
assert_eq!(g.degree(0).unwrap(), 2); assert!(g.find_eid(0, 2).unwrap().is_some());
}
}