rust_igraph/algorithms/constructors/
full.rs1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
31
32pub fn full_graph(n: u32, directed: bool, loops: bool) -> IgraphResult<Graph> {
69 if n == 0 {
70 return Graph::new(0, directed);
71 }
72
73 let n_us = usize::try_from(n)
74 .map_err(|_| IgraphError::InvalidArgument(format!("full_graph: n {n} cannot fit usize")))?;
75 let ecount = expected_ecount(n_us, directed, loops)?;
76
77 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
78
79 match (directed, loops) {
80 (true, true) => {
81 for i in 0..n {
83 for j in 0..n {
84 edges.push((i, j));
85 }
86 }
87 }
88 (true, false) => {
89 for i in 0..n {
91 for j in 0..i {
92 edges.push((i, j));
93 }
94 for j in (i + 1)..n {
95 edges.push((i, j));
96 }
97 }
98 }
99 (false, true) => {
100 for i in 0..n {
102 for j in i..n {
103 edges.push((i, j));
104 }
105 }
106 }
107 (false, false) => {
108 for i in 0..n {
110 for j in (i + 1)..n {
111 edges.push((i, j));
112 }
113 }
114 }
115 }
116
117 debug_assert_eq!(edges.len(), ecount);
118
119 let mut g = Graph::new(n, directed)?;
120 g.add_edges(edges)?;
121 Ok(g)
122}
123
124fn expected_ecount(n: usize, directed: bool, loops: bool) -> IgraphResult<usize> {
128 match (directed, loops) {
129 (true, true) => n.checked_mul(n).ok_or_else(|| {
130 IgraphError::InvalidArgument(format!("full_graph: n² overflows usize (n = {n})"))
131 }),
132 (true, false) => {
133 n.checked_mul(n - 1).ok_or_else(|| {
135 IgraphError::InvalidArgument(format!(
136 "full_graph: n·(n−1) overflows usize (n = {n})"
137 ))
138 })
139 }
140 (false, true) => {
141 let prod = n
143 .checked_mul(n.checked_add(1).ok_or_else(|| {
144 IgraphError::InvalidArgument(format!(
145 "full_graph: n+1 overflows usize (n = {n})"
146 ))
147 })?)
148 .ok_or_else(|| {
149 IgraphError::InvalidArgument(format!(
150 "full_graph: n·(n+1) overflows usize (n = {n})"
151 ))
152 })?;
153 Ok(prod / 2)
154 }
155 (false, false) => {
156 let prod = n.checked_mul(n - 1).ok_or_else(|| {
158 IgraphError::InvalidArgument(format!(
159 "full_graph: n·(n−1) overflows usize (n = {n})"
160 ))
161 })?;
162 Ok(prod / 2)
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use std::collections::BTreeSet;
171
172 fn dump_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
173 let ec = u32::try_from(g.ecount()).expect("ecount fits u32 in tests");
174 let mut out = Vec::with_capacity(g.ecount());
175 for e in 0..ec {
176 let u = g.edge_source(e).expect("edge in range");
177 let v = g.edge_target(e).expect("edge in range");
178 out.push((u, v));
179 }
180 out
181 }
182
183 #[test]
184 fn null_graph_zero_vertices() {
185 let g = full_graph(0, false, false).expect("K_0");
186 assert_eq!(g.vcount(), 0);
187 assert_eq!(g.ecount(), 0);
188 assert!(!g.is_directed());
189 }
190
191 #[test]
192 fn singleton_no_loops() {
193 let g = full_graph(1, false, false).expect("K_1, no loops");
194 assert_eq!(g.vcount(), 1);
195 assert_eq!(g.ecount(), 0);
196 }
197
198 #[test]
199 fn singleton_with_loops() {
200 let g = full_graph(1, false, true).expect("K_1, with loops");
201 assert_eq!(g.vcount(), 1);
202 assert_eq!(g.ecount(), 1);
203 assert_eq!(dump_edges(&g), vec![(0, 0)]);
204 }
205
206 #[test]
207 fn undirected_no_loops_k10_emission_order() {
208 let g = full_graph(10, false, false).expect("K_10");
211 assert_eq!(g.vcount(), 10);
212 assert_eq!(g.ecount(), 45);
213 let edges = dump_edges(&g);
214 assert_eq!(edges[0], (0, 1));
216 assert_eq!(edges[1], (0, 2));
217 assert_eq!(edges[8], (0, 9));
218 assert_eq!(edges[9], (1, 2));
219 assert_eq!(*edges.last().unwrap(), (8, 9));
221 }
222
223 #[test]
224 fn directed_no_loops_k10_emission_order() {
225 let g = full_graph(10, true, false).expect("directed K_10");
226 assert_eq!(g.vcount(), 10);
227 assert_eq!(g.ecount(), 90);
228 let arcs = dump_edges(&g);
229 assert_eq!(arcs[0], (0, 1));
231 assert_eq!(arcs[8], (0, 9));
232 assert_eq!(arcs[9], (1, 0));
234 assert_eq!(arcs[10], (1, 2));
235 assert_eq!(*arcs.last().unwrap(), (9, 8));
237 }
238
239 #[test]
240 fn undirected_with_loops_k10_counts() {
241 let g = full_graph(10, false, true).expect("undirected K_10 + loops");
242 assert_eq!(g.vcount(), 10);
243 assert_eq!(g.ecount(), 55);
245 let edges: BTreeSet<_> = dump_edges(&g).into_iter().collect();
246 let mut expected: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
247 for i in 0u32..10 {
248 for j in i..10 {
249 expected.insert((i, j));
250 }
251 }
252 assert_eq!(edges, expected);
253 }
254
255 #[test]
256 fn directed_with_loops_k10_counts() {
257 let g = full_graph(10, true, true).expect("directed K_10 + loops");
258 assert_eq!(g.vcount(), 10);
259 assert_eq!(g.ecount(), 100);
260 let arcs: BTreeSet<_> = dump_edges(&g).into_iter().collect();
261 let mut expected: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
262 for i in 0u32..10 {
263 for j in 0u32..10 {
264 expected.insert((i, j));
265 }
266 }
267 assert_eq!(arcs, expected);
268 }
269
270 #[test]
271 fn directed_no_loops_matches_directed_complete_kautz_n_zero() {
272 let g_full = full_graph(4, true, false).expect("directed K_4");
276 let arcs: BTreeSet<_> = dump_edges(&g_full).into_iter().collect();
277 let mut expected = BTreeSet::new();
278 for i in 0u32..4 {
279 for j in 0u32..4 {
280 if i != j {
281 expected.insert((i, j));
282 }
283 }
284 }
285 assert_eq!(arcs, expected);
286 }
287
288 #[test]
289 fn ecount_overflow_rejected() {
290 let r = expected_ecount(usize::MAX / 2, true, true);
298 assert!(matches!(r, Err(IgraphError::InvalidArgument(_))));
299 }
300}
301
302#[cfg(all(test, feature = "proptest-harness"))]
303mod proptest_invariants {
304 use super::*;
305 use proptest::prelude::*;
306 use std::collections::BTreeSet;
307
308 proptest! {
309 #![proptest_config(ProptestConfig::with_cases(64))]
310
311 #[test]
314 fn ecount_matches_closed_form(n in 0u32..=12u32, directed in proptest::bool::ANY, loops in proptest::bool::ANY) {
315 let g = full_graph(n, directed, loops).expect("valid input");
316 let n_us = n as usize;
317 let expected = match (directed, loops) {
318 (true, true) => n_us * n_us,
319 (true, false) => n_us * n_us.saturating_sub(1),
320 (false, true) => n_us * (n_us + 1) / 2,
321 (false, false) => n_us * n_us.saturating_sub(1) / 2,
322 };
323 prop_assert_eq!(g.vcount(), n);
324 prop_assert_eq!(g.ecount(), expected);
325 prop_assert_eq!(g.is_directed(), directed);
326 }
327
328 #[test]
331 fn loop_flag_controls_self_loops(n in 1u32..=10u32, directed in proptest::bool::ANY) {
332 for &loops in &[false, true] {
333 let g = full_graph(n, directed, loops).expect("valid input");
334 let ec = u32::try_from(g.ecount()).unwrap();
335 let mut loop_count = 0u32;
336 for e in 0..ec {
337 let u = g.edge_source(e).expect("edge in range");
338 let v = g.edge_target(e).expect("edge in range");
339 if u == v {
340 loop_count += 1;
341 }
342 }
343 if loops {
344 prop_assert_eq!(loop_count, n, "with loops every vertex has exactly one self-loop");
345 } else {
346 prop_assert_eq!(loop_count, 0u32, "loops=false must yield no self-loops");
347 }
348 }
349 }
350
351 #[test]
355 fn undirected_no_loops_yields_pair_set(n in 0u32..=8u32) {
356 let g = full_graph(n, false, false).expect("valid input");
357 let ec = u32::try_from(g.ecount()).unwrap();
358 let mut got: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
359 for e in 0..ec {
360 let u = g.edge_source(e).expect("edge in range");
361 let v = g.edge_target(e).expect("edge in range");
362 let canon = if u <= v { (u, v) } else { (v, u) };
363 prop_assert!(got.insert(canon), "duplicate undirected edge ({u}, {v})");
364 }
365 let mut expected = BTreeSet::new();
366 for i in 0u32..n {
367 for j in (i + 1)..n {
368 expected.insert((i, j));
369 }
370 }
371 prop_assert_eq!(got, expected);
372 }
373 }
374}