use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphResult, VertexId};
pub fn linegraph(graph: &Graph) -> IgraphResult<Graph> {
if graph.is_directed() {
linegraph_directed(graph)
} else {
linegraph_undirected(graph)
}
}
fn build_incident_loops_twice(graph: &Graph) -> IgraphResult<Vec<Vec<EdgeId>>> {
let vcount = graph.vcount() as usize;
let ecount = graph.ecount();
let mut incident: Vec<Vec<EdgeId>> = vec![Vec::new(); vcount];
for eid in 0..u32::try_from(ecount).map_err(|_| {
crate::core::IgraphError::InvalidArgument(format!(
"linegraph: ecount {ecount} does not fit u32"
))
})? {
let (u, v) = graph.edge(eid)?;
incident[u as usize].push(eid);
incident[v as usize].push(eid);
}
Ok(incident)
}
fn linegraph_undirected(graph: &Graph) -> IgraphResult<Graph> {
let ecount = graph.ecount();
let new_vcount = u32::try_from(ecount).map_err(|_| {
crate::core::IgraphError::InvalidArgument(format!(
"linegraph: source ecount {ecount} cannot index a u32 vertex space"
))
})?;
let incident = build_incident_loops_twice(graph)?;
let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
for e1 in 0..new_vcount {
let (from, to) = graph.edge(e1)?;
for &e2 in &incident[from as usize] {
if e2 < e1 {
edges.push((e1, e2));
}
}
for &e2 in &incident[to as usize] {
if e2 < e1 {
edges.push((e1, e2));
}
}
if from == to {
edges.push((e1, e1));
}
}
let mut out = Graph::new(new_vcount, false)?;
out.add_edges(edges)?;
Ok(out)
}
fn linegraph_directed(graph: &Graph) -> IgraphResult<Graph> {
let ecount = graph.ecount();
let new_vcount = u32::try_from(ecount).map_err(|_| {
crate::core::IgraphError::InvalidArgument(format!(
"linegraph: source ecount {ecount} cannot index a u32 vertex space"
))
})?;
let vcount = graph.vcount() as usize;
let mut incoming: Vec<Vec<EdgeId>> = vec![Vec::new(); vcount];
for eid in 0..new_vcount {
let (_from, to) = graph.edge(eid)?;
incoming[to as usize].push(eid);
}
let mut edges: Vec<(VertexId, VertexId)> = Vec::new();
for e1 in 0..new_vcount {
let (from, _to) = graph.edge(e1)?;
for &e in &incoming[from as usize] {
edges.push((e, e1));
}
}
let mut out = Graph::new(new_vcount, true)?;
out.add_edges(edges)?;
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn dump_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
let ec = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
let mut out = Vec::with_capacity(g.ecount());
for e in 0..ec {
out.push(g.edge(e).expect("edge in range"));
}
out
}
#[test]
fn empty_graph() {
let g = Graph::new(0, false).expect("empty G");
let l = linegraph(&g).expect("L(G)");
assert_eq!(l.vcount(), 0);
assert_eq!(l.ecount(), 0);
assert!(!l.is_directed());
}
#[test]
fn no_edges_graph() {
let g = Graph::new(5, false).expect("isolated vertices");
let l = linegraph(&g).expect("L(G)");
assert_eq!(l.vcount(), 0);
assert_eq!(l.ecount(), 0);
}
#[test]
fn path_p4_undirected() {
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 l = linegraph(&g).expect("L(P_4)");
assert_eq!(l.vcount(), 3);
assert_eq!(l.ecount(), 2);
assert_eq!(dump_edges(&l), vec![(0, 1), (1, 2)]);
}
#[test]
fn triangle_k3_undirected() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 2).unwrap();
let l = linegraph(&g).expect("L(K_3)");
assert_eq!(l.vcount(), 3);
assert_eq!(l.ecount(), 3);
assert_eq!(dump_edges(&l), vec![(0, 1), (0, 2), (1, 2)]);
}
#[test]
fn star_s4_undirected() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
let l = linegraph(&g).expect("L(star)");
assert_eq!(l.vcount(), 3);
assert_eq!(l.ecount(), 3);
}
#[test]
fn self_loop_undirected() {
let mut g = Graph::with_vertices(1);
g.add_edge(0, 0).unwrap();
let l = linegraph(&g).expect("L(self-loop)");
assert_eq!(l.vcount(), 1);
assert_eq!(l.ecount(), 1);
assert_eq!(dump_edges(&l), vec![(0, 0)]);
}
#[test]
fn self_loop_plus_non_loop_undirected() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let l = linegraph(&g).expect("L");
assert_eq!(l.vcount(), 2);
assert_eq!(l.ecount(), 3);
assert_eq!(dump_edges(&l), vec![(0, 0), (0, 1), (0, 1)]);
}
#[test]
fn non_loop_then_self_loop_undirected() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 0).unwrap();
let l = linegraph(&g).expect("L");
assert_eq!(l.vcount(), 2);
assert_eq!(l.ecount(), 3);
assert_eq!(dump_edges(&l), vec![(0, 1), (0, 1), (1, 1)]);
}
#[test]
fn multi_edge_undirected() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let l = linegraph(&g).expect("L");
assert_eq!(l.vcount(), 2);
assert_eq!(l.ecount(), 2);
assert_eq!(dump_edges(&l), vec![(0, 1), (0, 1)]);
}
#[test]
fn directed_chain() {
let mut g = Graph::new(3, true).expect("directed");
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let l = linegraph(&g).expect("L");
assert_eq!(l.vcount(), 2);
assert_eq!(l.ecount(), 1);
assert!(l.is_directed());
assert_eq!(dump_edges(&l), vec![(0, 1)]);
}
#[test]
fn directed_fan_in() {
let mut g = Graph::new(4, true).expect("directed");
g.add_edge(0, 2).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let l = linegraph(&g).expect("L");
assert_eq!(l.vcount(), 3);
assert_eq!(l.ecount(), 2);
assert_eq!(dump_edges(&l), vec![(0, 2), (1, 2)]);
}
#[test]
fn directed_self_loop() {
let mut g = Graph::new(1, true).expect("directed");
g.add_edge(0, 0).unwrap();
let l = linegraph(&g).expect("L");
assert_eq!(l.vcount(), 1);
assert_eq!(l.ecount(), 1);
assert!(l.is_directed());
assert_eq!(dump_edges(&l), vec![(0, 0)]);
}
#[test]
fn directed_isolated_arcs() {
let mut g = Graph::new(4, true).expect("directed");
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let l = linegraph(&g).expect("L");
assert_eq!(l.vcount(), 2);
assert_eq!(l.ecount(), 0);
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptest_invariants {
use super::*;
use proptest::collection::vec as pvec;
use proptest::prelude::*;
use std::collections::BTreeMap;
fn arb_undirected_graph() -> impl Strategy<Value = Graph> {
(1u32..=8u32).prop_flat_map(|n| {
let edge = (0u32..n, 0u32..n);
pvec(edge, 0..=12).prop_map(move |raw| {
let mut g = Graph::with_vertices(n);
for (u, v) in raw {
g.add_edge(u, v).expect("add_edge");
}
g
})
})
}
fn arb_directed_graph() -> impl Strategy<Value = Graph> {
(1u32..=8u32).prop_flat_map(|n| {
let edge = (0u32..n, 0u32..n);
pvec(edge, 0..=12).prop_map(move |raw| {
let mut g = Graph::new(n, true).expect("directed");
for (u, v) in raw {
g.add_edge(u, v).expect("add_edge");
}
g
})
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(96))]
#[test]
fn vcount_and_directedness_match(g in arb_undirected_graph()) {
let l = linegraph(&g).expect("L(G)");
prop_assert_eq!(l.vcount() as usize, g.ecount());
prop_assert!(!l.is_directed());
}
#[test]
fn directed_vcount_and_directedness_match(g in arb_directed_graph()) {
let l = linegraph(&g).expect("L(G)");
prop_assert_eq!(l.vcount() as usize, g.ecount());
prop_assert!(l.is_directed());
}
#[test]
fn undirected_edge_set_matches_endpoint_oracle(g in arb_undirected_graph()) {
let ec = u32::try_from(g.ecount()).unwrap();
let vcount = g.vcount() as usize;
let mut incident: Vec<Vec<u32>> = vec![Vec::new(); vcount];
for eid in 0..ec {
let (u, v) = g.edge(eid).unwrap();
incident[u as usize].push(eid);
incident[v as usize].push(eid);
}
let mut expected: BTreeMap<(u32, u32), u32> = BTreeMap::new();
for e1 in 0..ec {
let (a, b) = g.edge(e1).unwrap();
for &e2 in &incident[a as usize] {
if e2 < e1 {
*expected.entry((e1, e2)).or_insert(0) += 1;
}
}
for &e2 in &incident[b as usize] {
if e2 < e1 {
*expected.entry((e1, e2)).or_insert(0) += 1;
}
}
if a == b {
*expected.entry((e1, e1)).or_insert(0) += 1;
}
}
let l = linegraph(&g).expect("L(G)");
let lec = u32::try_from(l.ecount()).unwrap();
let mut got: BTreeMap<(u32, u32), u32> = BTreeMap::new();
for e in 0..lec {
let (u, v) = l.edge(e).unwrap();
let key = if u >= v { (u, v) } else { (v, u) };
*got.entry(key).or_insert(0) += 1;
}
prop_assert_eq!(got, expected);
}
#[test]
fn directed_arc_set_matches_chain_oracle(g in arb_directed_graph()) {
let ec = u32::try_from(g.ecount()).unwrap();
let vcount = g.vcount() as usize;
let mut incoming: Vec<Vec<u32>> = vec![Vec::new(); vcount];
for eid in 0..ec {
let (_u, v) = g.edge(eid).unwrap();
incoming[v as usize].push(eid);
}
let mut expected: BTreeMap<(u32, u32), u32> = BTreeMap::new();
for e1 in 0..ec {
let (s, _t) = g.edge(e1).unwrap();
for &e in &incoming[s as usize] {
*expected.entry((e, e1)).or_insert(0) += 1;
}
}
let l = linegraph(&g).expect("L(G)");
let lec = u32::try_from(l.ecount()).unwrap();
let mut got: BTreeMap<(u32, u32), u32> = BTreeMap::new();
for e in 0..lec {
let (u, v) = l.edge(e).unwrap();
*got.entry((u, v)).or_insert(0) += 1;
}
prop_assert_eq!(got, expected);
}
}
}