Compute full graph statistics from the current CSR state. O(V + E) — iterates all nodes and edges once. Intended for query planning, not hot-path execution. # Errors Returns [`GraphError::MemoryBudget`] if a memory governor is installed and the degree-array working set would exceed the `Graph` engine budget.
(&self)
| 66 | /// Returns [`GraphError::MemoryBudget`] if a memory governor is installed |
| 67 | /// and the degree-array working set would exceed the `Graph` engine budget. |
| 68 | pub fn compute_statistics(&self) -> Result<GraphStatistics, GraphError> { |
| 69 | let n = self.node_count(); |
| 70 | if n == 0 { |
| 71 | return Ok(GraphStatistics { |
| 72 | node_count: 0, |
| 73 | edge_count: 0, |
| 74 | label_count: 0, |
| 75 | label_stats: HashMap::new(), |
| 76 | out_degree_histogram: DegreeHistogram { |
| 77 | min: 0, |
| 78 | max: 0, |
| 79 | avg: 0.0, |
| 80 | p50: 0, |
| 81 | p95: 0, |
| 82 | p99: 0, |
| 83 | }, |
| 84 | in_degree_histogram: DegreeHistogram { |
| 85 | min: 0, |
| 86 | max: 0, |
| 87 | avg: 0.0, |
| 88 | p50: 0, |
| 89 | p95: 0, |
| 90 | p99: 0, |
| 91 | }, |
| 92 | }); |
| 93 | } |
| 94 | |
| 95 | // Reserve memory for the two degree-distribution scratch arrays. |
| 96 | let degree_bytes = 2 * n * size_of::<usize>(); |
| 97 | let _degree_guard = self |
| 98 | .governor |
| 99 | .as_ref() |
| 100 | .map(|g| g.reserve(EngineId::Graph, degree_bytes)) |
| 101 | .transpose()?; |
| 102 | |
| 103 | // Per-label counters. |
| 104 | let mut label_edge_count: HashMap<u32, usize> = HashMap::new(); |
| 105 | let mut label_sources: HashMap<u32, std::collections::HashSet<u32>> = HashMap::new(); |
| 106 | let mut label_targets: HashMap<u32, std::collections::HashSet<u32>> = HashMap::new(); |
| 107 | |
| 108 | // Degree arrays. |
| 109 | let mut out_degrees: Vec<usize> = Vec::with_capacity(n); |
| 110 | let mut in_degrees: Vec<usize> = Vec::with_capacity(n); |
| 111 | |
| 112 | let mut total_edges = 0usize; |
| 113 | |
| 114 | for node in 0..n { |
| 115 | let node_id = node as u32; |
| 116 | let mut out_deg = 0usize; |
| 117 | let mut in_deg = 0usize; |
| 118 | |
| 119 | for (lid, dst) in self.dense_iter_out(node_id) { |
| 120 | out_deg += 1; |
| 121 | total_edges += 1; |
| 122 | *label_edge_count.entry(lid).or_insert(0) += 1; |
| 123 | label_sources.entry(lid).or_default().insert(node_id); |
| 124 | label_targets.entry(lid).or_default().insert(dst); |
| 125 | } |