use crate::core::error::IgraphError;
use crate::core::{Graph, IgraphResult, VertexId};
pub fn invert_permutation(permutation: &[VertexId]) -> IgraphResult<Vec<VertexId>> {
let n = permutation.len();
let n_u32 = u32::try_from(n).map_err(|_| {
IgraphError::InvalidArgument("invert_permutation: length overflows u32".to_string())
})?;
let mut inverse = vec![u32::MAX; n];
for (i, &j) in permutation.iter().enumerate() {
if j >= n_u32 {
return Err(IgraphError::InvalidArgument(format!(
"invert_permutation: invalid index {j} in permutation (must be < {n_u32})"
)));
}
if inverse[j as usize] != u32::MAX {
return Err(IgraphError::InvalidArgument(format!(
"invert_permutation: duplicate entry {j} in permutation"
)));
}
#[allow(clippy::cast_possible_truncation)]
{
inverse[j as usize] = i as u32;
}
}
Ok(inverse)
}
pub fn permute_vertices(graph: &Graph, permutation: &[VertexId]) -> IgraphResult<Graph> {
let n = graph.vcount();
if permutation.len() != n as usize {
return Err(IgraphError::InvalidArgument(format!(
"permutation length {} does not match vertex count {n}",
permutation.len()
)));
}
let mut inverse = vec![u32::MAX; n as usize];
for (new_id, &old_id) in permutation.iter().enumerate() {
if old_id >= n {
return Err(IgraphError::InvalidArgument(format!(
"permutation contains invalid vertex id {old_id} (graph has {n} vertices)"
)));
}
if inverse[old_id as usize] != u32::MAX {
return Err(IgraphError::InvalidArgument(format!(
"duplicate entry {old_id} in permutation"
)));
}
#[allow(clippy::cast_possible_truncation)]
let new_id_u32 = new_id as u32;
inverse[old_id as usize] = new_id_u32;
}
let ecount = graph.ecount();
let directed = graph.is_directed();
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
for eid in 0..ecount {
#[allow(clippy::cast_possible_truncation)]
let eid_u32 = eid as u32;
let (src, tgt) = graph.edge(eid_u32)?;
let new_src = inverse[src as usize];
let new_tgt = inverse[tgt as usize];
edges.push((new_src, new_tgt));
}
let mut result = Graph::new(n, directed)?;
result.add_edges(edges)?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identity_permutation() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let pg = permute_vertices(&g, &[0, 1, 2, 3]).unwrap();
assert_eq!(pg.vcount(), 4);
assert_eq!(pg.ecount(), 3);
for eid in 0..3u32 {
assert_eq!(g.edge(eid).unwrap(), pg.edge(eid).unwrap());
}
}
#[test]
fn test_reverse_permutation() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let pg = permute_vertices(&g, &[2, 1, 0]).unwrap();
assert_eq!(pg.vcount(), 3);
assert_eq!(pg.ecount(), 2);
assert_eq!(pg.edge(0).unwrap(), (1, 2));
assert_eq!(pg.edge(1).unwrap(), (0, 1));
}
#[test]
fn test_directed() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let pg = permute_vertices(&g, &[2, 0, 1]).unwrap();
assert!(pg.is_directed());
assert_eq!(pg.edge(0).unwrap(), (1, 2));
assert_eq!(pg.edge(1).unwrap(), (2, 0));
}
#[test]
fn test_empty_graph() {
let g = Graph::with_vertices(0);
let pg = permute_vertices(&g, &[]).unwrap();
assert_eq!(pg.vcount(), 0);
}
#[test]
fn test_wrong_length() {
let g = Graph::with_vertices(3);
let result = permute_vertices(&g, &[0, 1]);
assert!(result.is_err());
}
#[test]
fn test_out_of_range() {
let g = Graph::with_vertices(3);
let result = permute_vertices(&g, &[0, 1, 5]);
assert!(result.is_err());
}
#[test]
fn test_duplicate() {
let g = Graph::with_vertices(3);
let result = permute_vertices(&g, &[0, 1, 1]);
assert!(result.is_err());
}
#[test]
fn test_self_loop() {
let mut g = Graph::with_vertices(3);
g.add_edge(1, 1).unwrap();
let pg = permute_vertices(&g, &[2, 0, 1]).unwrap();
assert_eq!(pg.edge(0).unwrap(), (2, 2));
}
#[test]
fn test_cycle_permutation() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 0).unwrap();
let pg = permute_vertices(&g, &[1, 2, 3, 0]).unwrap();
assert_eq!(pg.vcount(), 4);
assert_eq!(pg.ecount(), 4);
assert_eq!(pg.edge(0).unwrap(), (0, 3));
assert_eq!(pg.edge(1).unwrap(), (0, 1));
}
#[test]
fn invert_identity() {
let inv = invert_permutation(&[0, 1, 2, 3]).unwrap();
assert_eq!(inv, vec![0, 1, 2, 3]);
}
#[test]
fn invert_reverse() {
let inv = invert_permutation(&[2, 1, 0]).unwrap();
assert_eq!(inv, vec![2, 1, 0]);
}
#[test]
fn invert_cycle() {
let inv = invert_permutation(&[1, 2, 0]).unwrap();
assert_eq!(inv, vec![2, 0, 1]);
}
#[test]
fn invert_empty() {
let inv = invert_permutation(&[]).unwrap();
assert!(inv.is_empty());
}
#[test]
fn invert_single() {
let inv = invert_permutation(&[0]).unwrap();
assert_eq!(inv, vec![0]);
}
#[test]
fn invert_roundtrip() {
let perm = [3, 0, 2, 1];
let inv = invert_permutation(&perm).unwrap();
let inv2 = invert_permutation(&inv).unwrap();
assert_eq!(inv2, perm.to_vec());
}
#[test]
fn invert_out_of_range() {
let err = invert_permutation(&[0, 5, 1]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn invert_duplicate() {
let err = invert_permutation(&[0, 1, 1]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
}