use std::fmt::Write as FmtWrite;
use crate::algorithms::connectivity::components::connected_components;
use crate::algorithms::connectivity::is_connected::{ConnectednessMode, is_connected};
use crate::algorithms::properties::basic::density;
use crate::algorithms::properties::degree::{DegreeMode, max_degree, min_degree};
use crate::algorithms::properties::multiplicity::{has_loop, has_multiple};
use crate::core::{Graph, IgraphResult};
#[derive(Debug, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct GraphSummary {
pub directed: bool,
pub vcount: u32,
pub ecount: usize,
pub connected: bool,
pub component_count: u32,
pub density: Option<f64>,
pub min_degree: u32,
pub max_degree: u32,
pub has_loops: bool,
pub has_multiple: bool,
}
impl std::fmt::Display for GraphSummary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let kind = if self.directed {
"Directed"
} else {
"Undirected"
};
write!(
f,
"{kind} graph: {v} vertices, {e} edges",
v = self.vcount,
e = self.ecount
)?;
if self.vcount > 0 {
let conn = if self.connected {
"connected"
} else {
"disconnected"
};
write!(f, ", {conn}")?;
if !self.connected {
write!(f, " ({} components)", self.component_count)?;
}
}
if let Some(d) = self.density {
write!(f, ", density={d:.4}")?;
}
if self.vcount > 0 {
write!(f, ", degree=[{}, {}]", self.min_degree, self.max_degree)?;
}
let mut flags = Vec::new();
if self.has_loops {
flags.push("loops");
}
if self.has_multiple {
flags.push("multi");
}
if !flags.is_empty() {
write!(f, " [{}]", flags.join(", "))?;
}
Ok(())
}
}
pub fn graph_summary(graph: &Graph) -> IgraphResult<GraphSummary> {
let vcount = graph.vcount();
let ecount = graph.ecount();
let directed = graph.is_directed();
let (connected, component_count) = if vcount == 0 {
(true, 0)
} else {
let conn = is_connected(graph, ConnectednessMode::Weak)?;
let cc = connected_components(graph)?;
(conn, cc.count)
};
let dens = density(graph)?;
let (min_deg, max_deg) = if vcount == 0 {
(0, 0)
} else {
let mn = min_degree(graph, DegreeMode::All)?;
let mx = max_degree(graph, DegreeMode::All)?;
(mn, mx)
};
let loops = has_loop(graph)?;
let multi = has_multiple(graph)?;
Ok(GraphSummary {
directed,
vcount,
ecount,
connected,
component_count,
density: dens,
min_degree: min_deg,
max_degree: max_deg,
has_loops: loops,
has_multiple: multi,
})
}
pub fn graph_summary_string(graph: &Graph) -> IgraphResult<String> {
let s = graph_summary(graph)?;
let mut out = String::new();
let kind = if s.directed { "Directed" } else { "Undirected" };
let _ = writeln!(out, "--- Graph Summary ---");
let _ = writeln!(out, "Type: {kind}");
let _ = writeln!(out, "Vertices: {}", s.vcount);
let _ = writeln!(out, "Edges: {}", s.ecount);
let conn_str = if s.connected { "yes" } else { "no" };
let _ = writeln!(out, "Connected: {conn_str}");
if !s.connected {
let _ = writeln!(out, "Components: {}", s.component_count);
}
if let Some(d) = s.density {
let _ = writeln!(out, "Density: {d:.6}");
}
if s.vcount > 0 {
let _ = writeln!(out, "Degree range: [{}, {}]", s.min_degree, s.max_degree);
}
let simple = !s.has_loops && !s.has_multiple;
let _ = writeln!(out, "Simple: {}", if simple { "yes" } else { "no" });
if s.has_loops {
let _ = writeln!(out, " Self-loops: yes");
}
if s.has_multiple {
let _ = writeln!(out, " Multi-edges: yes");
}
let _ = write!(out, "--------------------");
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn summary_empty_graph() {
let g = Graph::with_vertices(0);
let s = graph_summary(&g).unwrap();
assert_eq!(s.vcount, 0);
assert_eq!(s.ecount, 0);
assert!(s.connected);
assert_eq!(s.component_count, 0);
assert_eq!(s.density, None);
assert_eq!(s.min_degree, 0);
assert_eq!(s.max_degree, 0);
assert!(!s.has_loops);
assert!(!s.has_multiple);
}
#[test]
fn summary_path_graph() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 4).unwrap();
let s = graph_summary(&g).unwrap();
assert!(!s.directed);
assert_eq!(s.vcount, 5);
assert_eq!(s.ecount, 4);
assert!(s.connected);
assert_eq!(s.component_count, 1);
assert_eq!(s.min_degree, 1);
assert_eq!(s.max_degree, 2);
assert!(!s.has_loops);
assert!(!s.has_multiple);
}
#[test]
fn summary_disconnected_graph() {
let mut g = Graph::with_vertices(6);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
let s = graph_summary(&g).unwrap();
assert!(!s.connected);
assert_eq!(s.component_count, 3);
}
#[test]
fn summary_directed_graph() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let s = graph_summary(&g).unwrap();
assert!(s.directed);
assert!(s.connected);
assert_eq!(s.ecount, 3);
}
#[test]
fn summary_with_loops() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 1).unwrap();
let s = graph_summary(&g).unwrap();
assert!(s.has_loops);
}
#[test]
fn summary_with_multiedges() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let s = graph_summary(&g).unwrap();
assert!(s.has_multiple);
}
#[test]
fn summary_display_format() {
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 s = graph_summary(&g).unwrap();
let display = format!("{s}");
assert!(display.contains("Undirected graph"));
assert!(display.contains("4 vertices"));
assert!(display.contains("4 edges"));
assert!(display.contains("connected"));
assert!(display.contains("density="));
}
#[test]
fn summary_string_format() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let out = graph_summary_string(&g).unwrap();
assert!(out.contains("Vertices: 3"));
assert!(out.contains("Edges: 2"));
assert!(out.contains("Connected: yes"));
assert!(out.contains("Simple: yes"));
}
#[test]
fn summary_singleton() {
let g = Graph::with_vertices(1);
let s = graph_summary(&g).unwrap();
assert_eq!(s.vcount, 1);
assert_eq!(s.ecount, 0);
assert!(s.connected);
assert_eq!(s.component_count, 1);
assert_eq!(s.density, None);
assert_eq!(s.min_degree, 0);
assert_eq!(s.max_degree, 0);
}
}