use super::atlas_edges::{ATLAS_EDGES, ATLAS_EDGES_POS};
use crate::core::{Graph, IgraphError, IgraphResult};
pub const ATLAS_SIZE: u32 = 1253;
pub fn atlas(number: u32) -> IgraphResult<Graph> {
if number >= ATLAS_SIZE {
return Err(IgraphError::InvalidArgument(format!(
"atlas: graph index {number} is out of range (must be < {ATLAS_SIZE})"
)));
}
let pos = ATLAS_EDGES_POS[number as usize] as usize;
if pos + 2 > ATLAS_EDGES.len() {
return Err(IgraphError::InvalidArgument(
"atlas: corrupt position table (header out of range)".into(),
));
}
let vcount = ATLAS_EDGES[pos];
let ecount = ATLAS_EDGES[pos + 1] as usize;
let edge_words = ecount
.checked_mul(2)
.ok_or_else(|| IgraphError::InvalidArgument("atlas: edge buffer overflow".into()))?;
let end = pos
.checked_add(2)
.and_then(|x| x.checked_add(edge_words))
.ok_or_else(|| IgraphError::InvalidArgument("atlas: edge buffer overflow".into()))?;
if end > ATLAS_EDGES.len() {
return Err(IgraphError::InvalidArgument(
"atlas: corrupt position table (payload out of range)".into(),
));
}
let mut g = Graph::new(vcount, false)?;
let edges: Vec<(u32, u32)> = ATLAS_EDGES[pos + 2..end]
.chunks_exact(2)
.map(|pair| (pair[0], pair[1]))
.collect();
g.add_edges(edges)?;
Ok(g)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
fn canonical_edge_set(g: &Graph) -> BTreeSet<(u32, u32)> {
let mut s = BTreeSet::new();
for v in 0..g.vcount() {
for &u in &g.neighbors(v).expect("neighbors") {
if v <= u {
s.insert((v, u));
} else {
s.insert((u, v));
}
}
}
s
}
#[test]
fn out_of_range_errors() {
assert!(atlas(ATLAS_SIZE).is_err());
assert!(atlas(ATLAS_SIZE + 1).is_err());
assert!(atlas(u32::MAX).is_err());
}
#[test]
fn atlas_size_constant_matches_upstream() {
assert_eq!(ATLAS_SIZE, 1253);
}
#[test]
fn null_graph_indices() {
let starts: [(u32, u32); 8] = [
(0, 0),
(1, 1),
(2, 2),
(3, 4),
(4, 8),
(5, 19),
(6, 53),
(7, 209),
];
for (n, idx) in starts {
let g = atlas(idx).unwrap_or_else(|_| panic!("atlas({idx})"));
assert_eq!(g.vcount(), n, "atlas({idx}) vcount");
assert_eq!(g.ecount(), 0, "atlas({idx}) ecount (null graph)");
}
}
#[test]
fn small_known_graphs() {
let g3 = atlas(3).expect("3");
assert_eq!((g3.vcount(), g3.ecount()), (2, 1));
assert_eq!(
canonical_edge_set(&g3),
[(0u32, 1u32)].into_iter().collect()
);
let g7 = atlas(7).expect("7");
assert_eq!((g7.vcount(), g7.ecount()), (3, 3));
assert_eq!(
canonical_edge_set(&g7),
[(0u32, 1), (0, 2), (1, 2)].into_iter().collect()
);
let k4 = atlas(18).expect("18");
assert_eq!((k4.vcount(), k4.ecount()), (4, 6));
for v in 0..k4.vcount() {
assert_eq!(k4.neighbors(v).expect("nbrs").len(), 3);
}
}
#[test]
fn k7_is_last_atlas_graph() {
let k7 = atlas(1252).expect("1252");
assert_eq!((k7.vcount(), k7.ecount()), (7, 21));
for v in 0..k7.vcount() {
assert_eq!(k7.neighbors(v).expect("nbrs").len(), 6);
}
}
#[test]
fn no_self_loops_or_repeated_edges() {
let samples = [
0, 1, 2, 3, 4, 7, 8, 18, 19, 52, 53, 70, 100, 180, 208, 209, 500, 1000, 1252,
];
for &i in &samples {
let g = atlas(i).unwrap_or_else(|_| panic!("atlas({i})"));
let mut seen: BTreeSet<(u32, u32)> = BTreeSet::new();
for v in 0..g.vcount() {
for &u in &g.neighbors(v).expect("nbrs") {
assert_ne!(v, u, "atlas({i}) self-loop at {v}");
let key = if v <= u { (v, u) } else { (u, v) };
if !seen.insert(key) {
}
}
}
}
}
#[test]
fn all_atlas_graphs_construct_and_are_undirected() {
for i in 0..ATLAS_SIZE {
let g = atlas(i).unwrap_or_else(|_| panic!("atlas({i})"));
assert!(g.vcount() <= 7, "atlas({i}) vcount {}", g.vcount());
assert!(!g.is_directed(), "atlas({i}) should be undirected");
let total_deg: usize = (0..g.vcount())
.map(|v| g.neighbors(v).expect("nbrs").len())
.sum();
assert_eq!(
total_deg,
g.ecount() as usize * 2,
"atlas({i}) degree sum != 2|E|"
);
}
}
#[test]
fn vertex_count_partitions_match_upstream_starts() {
let starts = [0u32, 1, 2, 4, 8, 19, 53, 209, ATLAS_SIZE];
for n in 0..8 {
let lo = starts[n];
let hi = starts[n + 1];
for i in lo..hi {
let g = atlas(i).expect("atlas");
assert_eq!(
g.vcount() as usize,
n,
"atlas({i}) should have vcount {n}, got {}",
g.vcount()
);
}
}
}
}