use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn full_graph(n: u32, directed: bool, loops: bool) -> IgraphResult<Graph> {
if n == 0 {
return Graph::new(0, directed);
}
let n_us = usize::try_from(n)
.map_err(|_| IgraphError::InvalidArgument(format!("full_graph: n {n} cannot fit usize")))?;
let ecount = expected_ecount(n_us, directed, loops)?;
let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
match (directed, loops) {
(true, true) => {
for i in 0..n {
for j in 0..n {
edges.push((i, j));
}
}
}
(true, false) => {
for i in 0..n {
for j in 0..i {
edges.push((i, j));
}
for j in (i + 1)..n {
edges.push((i, j));
}
}
}
(false, true) => {
for i in 0..n {
for j in i..n {
edges.push((i, j));
}
}
}
(false, false) => {
for i in 0..n {
for j in (i + 1)..n {
edges.push((i, j));
}
}
}
}
debug_assert_eq!(edges.len(), ecount);
let mut g = Graph::new(n, directed)?;
g.add_edges(edges)?;
Ok(g)
}
fn expected_ecount(n: usize, directed: bool, loops: bool) -> IgraphResult<usize> {
match (directed, loops) {
(true, true) => n.checked_mul(n).ok_or_else(|| {
IgraphError::InvalidArgument(format!("full_graph: n² overflows usize (n = {n})"))
}),
(true, false) => {
n.checked_mul(n - 1).ok_or_else(|| {
IgraphError::InvalidArgument(format!(
"full_graph: n·(n−1) overflows usize (n = {n})"
))
})
}
(false, true) => {
let prod = n
.checked_mul(n.checked_add(1).ok_or_else(|| {
IgraphError::InvalidArgument(format!(
"full_graph: n+1 overflows usize (n = {n})"
))
})?)
.ok_or_else(|| {
IgraphError::InvalidArgument(format!(
"full_graph: n·(n+1) overflows usize (n = {n})"
))
})?;
Ok(prod / 2)
}
(false, false) => {
let prod = n.checked_mul(n - 1).ok_or_else(|| {
IgraphError::InvalidArgument(format!(
"full_graph: n·(n−1) overflows usize (n = {n})"
))
})?;
Ok(prod / 2)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
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 {
let u = g.edge_source(e).expect("edge in range");
let v = g.edge_target(e).expect("edge in range");
out.push((u, v));
}
out
}
#[test]
fn null_graph_zero_vertices() {
let g = full_graph(0, false, false).expect("K_0");
assert_eq!(g.vcount(), 0);
assert_eq!(g.ecount(), 0);
assert!(!g.is_directed());
}
#[test]
fn singleton_no_loops() {
let g = full_graph(1, false, false).expect("K_1, no loops");
assert_eq!(g.vcount(), 1);
assert_eq!(g.ecount(), 0);
}
#[test]
fn singleton_with_loops() {
let g = full_graph(1, false, true).expect("K_1, with loops");
assert_eq!(g.vcount(), 1);
assert_eq!(g.ecount(), 1);
assert_eq!(dump_edges(&g), vec![(0, 0)]);
}
#[test]
fn undirected_no_loops_k10_emission_order() {
let g = full_graph(10, false, false).expect("K_10");
assert_eq!(g.vcount(), 10);
assert_eq!(g.ecount(), 45);
let edges = dump_edges(&g);
assert_eq!(edges[0], (0, 1));
assert_eq!(edges[1], (0, 2));
assert_eq!(edges[8], (0, 9));
assert_eq!(edges[9], (1, 2));
assert_eq!(*edges.last().unwrap(), (8, 9));
}
#[test]
fn directed_no_loops_k10_emission_order() {
let g = full_graph(10, true, false).expect("directed K_10");
assert_eq!(g.vcount(), 10);
assert_eq!(g.ecount(), 90);
let arcs = dump_edges(&g);
assert_eq!(arcs[0], (0, 1));
assert_eq!(arcs[8], (0, 9));
assert_eq!(arcs[9], (1, 0));
assert_eq!(arcs[10], (1, 2));
assert_eq!(*arcs.last().unwrap(), (9, 8));
}
#[test]
fn undirected_with_loops_k10_counts() {
let g = full_graph(10, false, true).expect("undirected K_10 + loops");
assert_eq!(g.vcount(), 10);
assert_eq!(g.ecount(), 55);
let edges: BTreeSet<_> = dump_edges(&g).into_iter().collect();
let mut expected: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
for i in 0u32..10 {
for j in i..10 {
expected.insert((i, j));
}
}
assert_eq!(edges, expected);
}
#[test]
fn directed_with_loops_k10_counts() {
let g = full_graph(10, true, true).expect("directed K_10 + loops");
assert_eq!(g.vcount(), 10);
assert_eq!(g.ecount(), 100);
let arcs: BTreeSet<_> = dump_edges(&g).into_iter().collect();
let mut expected: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
for i in 0u32..10 {
for j in 0u32..10 {
expected.insert((i, j));
}
}
assert_eq!(arcs, expected);
}
#[test]
fn directed_no_loops_matches_directed_complete_kautz_n_zero() {
let g_full = full_graph(4, true, false).expect("directed K_4");
let arcs: BTreeSet<_> = dump_edges(&g_full).into_iter().collect();
let mut expected = BTreeSet::new();
for i in 0u32..4 {
for j in 0u32..4 {
if i != j {
expected.insert((i, j));
}
}
}
assert_eq!(arcs, expected);
}
#[test]
fn ecount_overflow_rejected() {
let r = expected_ecount(usize::MAX / 2, true, true);
assert!(matches!(r, Err(IgraphError::InvalidArgument(_))));
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptest_invariants {
use super::*;
use proptest::prelude::*;
use std::collections::BTreeSet;
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn ecount_matches_closed_form(n in 0u32..=12u32, directed in proptest::bool::ANY, loops in proptest::bool::ANY) {
let g = full_graph(n, directed, loops).expect("valid input");
let n_us = n as usize;
let expected = match (directed, loops) {
(true, true) => n_us * n_us,
(true, false) => n_us * n_us.saturating_sub(1),
(false, true) => n_us * (n_us + 1) / 2,
(false, false) => n_us * n_us.saturating_sub(1) / 2,
};
prop_assert_eq!(g.vcount(), n);
prop_assert_eq!(g.ecount(), expected);
prop_assert_eq!(g.is_directed(), directed);
}
#[test]
fn loop_flag_controls_self_loops(n in 1u32..=10u32, directed in proptest::bool::ANY) {
for &loops in &[false, true] {
let g = full_graph(n, directed, loops).expect("valid input");
let ec = u32::try_from(g.ecount()).unwrap();
let mut loop_count = 0u32;
for e in 0..ec {
let u = g.edge_source(e).expect("edge in range");
let v = g.edge_target(e).expect("edge in range");
if u == v {
loop_count += 1;
}
}
if loops {
prop_assert_eq!(loop_count, n, "with loops every vertex has exactly one self-loop");
} else {
prop_assert_eq!(loop_count, 0u32, "loops=false must yield no self-loops");
}
}
}
#[test]
fn undirected_no_loops_yields_pair_set(n in 0u32..=8u32) {
let g = full_graph(n, false, false).expect("valid input");
let ec = u32::try_from(g.ecount()).unwrap();
let mut got: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
for e in 0..ec {
let u = g.edge_source(e).expect("edge in range");
let v = g.edge_target(e).expect("edge in range");
let canon = if u <= v { (u, v) } else { (v, u) };
prop_assert!(got.insert(canon), "duplicate undirected edge ({u}, {v})");
}
let mut expected = BTreeSet::new();
for i in 0u32..n {
for j in (i + 1)..n {
expected.insert((i, j));
}
}
prop_assert_eq!(got, expected);
}
}
}