rust_igraph/algorithms/constructors/
create.rs1use crate::core::{Graph, IgraphError, IgraphResult};
24
25pub fn create(edges: &[(u32, u32)], n: u32, directed: bool) -> IgraphResult<Graph> {
58 let inferred = if edges.is_empty() {
59 0u32
60 } else {
61 let mut m = 0u32;
62 for &(u, v) in edges {
63 if u > m {
64 m = u;
65 }
66 if v > m {
67 m = v;
68 }
69 }
70 m.checked_add(1).ok_or_else(|| {
71 IgraphError::InvalidArgument(
72 "create: vertex id u32::MAX cannot be promoted to a vcount".into(),
73 )
74 })?
75 };
76
77 let vcount = n.max(inferred);
78 let mut g = Graph::new(vcount, directed)?;
79 g.add_edges(edges.iter().copied())?;
80 Ok(g)
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use std::collections::BTreeSet;
87
88 fn canonical_undirected_edges(g: &Graph) -> BTreeSet<(u32, u32)> {
89 let mut s = BTreeSet::new();
90 for v in 0..g.vcount() {
91 for &u in &g.neighbors(v).expect("neighbors") {
92 let key = if v <= u { (v, u) } else { (u, v) };
93 s.insert(key);
94 }
95 }
96 s
97 }
98
99 fn directed_arc_list(g: &Graph) -> Vec<(u32, u32)> {
100 (0..u32::try_from(g.ecount()).expect("ecount fits u32"))
101 .map(|eid| g.edge(eid).expect("edge"))
102 .collect()
103 }
104
105 #[test]
106 fn empty_edges_n_zero_yields_null_graph() {
107 let g = create(&[], 0, false).expect("create");
108 assert_eq!(g.vcount(), 0);
109 assert_eq!(g.ecount(), 0);
110 assert!(!g.is_directed());
111 }
112
113 #[test]
114 fn empty_edges_n_positive_yields_isolated_vertices() {
115 let g = create(&[], 5, true).expect("create");
116 assert_eq!(g.vcount(), 5);
117 assert_eq!(g.ecount(), 0);
118 assert!(g.is_directed());
119 }
120
121 #[test]
122 fn upstream_simple_example_n_zero_infers_four_vertices() {
123 let g = create(&[(0, 1), (1, 2), (2, 3), (2, 2)], 0, false).expect("create");
126 assert_eq!(g.vcount(), 4);
127 assert_eq!(g.ecount(), 4);
128 }
129
130 #[test]
131 fn upstream_example_n_ten_keeps_ten_vertices() {
132 let g = create(&[(0, 1), (1, 2), (2, 3), (2, 2)], 10, false).expect("create");
134 assert_eq!(g.vcount(), 10);
135 assert_eq!(g.ecount(), 4);
136 }
137
138 #[test]
139 fn n_smaller_than_max_silently_extends() {
140 let g = create(&[(0, 1), (5, 6)], 3, false).expect("create");
142 assert_eq!(g.vcount(), 7); assert_eq!(g.ecount(), 2);
144 }
145
146 #[test]
147 fn directed_preserves_arc_order() {
148 let arcs = vec![(0, 1), (1, 0), (1, 2), (2, 1)];
150 let g = create(&arcs, 3, true).expect("create");
151 assert_eq!(g.vcount(), 3);
152 assert_eq!(g.ecount(), 4);
153 assert_eq!(directed_arc_list(&g), arcs);
154 }
155
156 #[test]
157 fn undirected_canonicalises_edges() {
158 let g = create(&[(2, 1), (3, 0)], 0, false).expect("create");
159 let want: BTreeSet<(u32, u32)> = [(0, 3), (1, 2)].into_iter().collect();
160 assert_eq!(canonical_undirected_edges(&g), want);
161 }
162
163 #[test]
164 fn self_loops_accepted() {
165 let g = create(&[(0, 0), (1, 1), (0, 1)], 0, false).expect("create");
166 assert_eq!(g.vcount(), 2);
167 assert_eq!(g.ecount(), 3);
168 }
169
170 #[test]
171 fn parallel_edges_preserved() {
172 let g = create(&[(0, 1), (0, 1), (0, 1)], 0, false).expect("create");
173 assert_eq!(g.ecount(), 3);
174 assert_eq!(g.degree(0).unwrap(), 3);
176 assert_eq!(g.degree(1).unwrap(), 3);
177 }
178
179 #[test]
180 fn u32_max_endpoint_errors_rather_than_wraps() {
181 let r = create(&[(0, u32::MAX)], 0, false);
183 assert!(matches!(r, Err(IgraphError::InvalidArgument(_))));
184 }
185
186 #[test]
187 fn n_max_vcount_works_when_max_endpoint_lt_n() {
188 let g = create(&[(0, 1), (1, 2)], 10, false).expect("create");
190 assert_eq!(g.vcount(), 10);
191 }
192
193 #[test]
194 fn star_via_create_matches_handrolled_star() {
195 let n: u32 = 8;
196 let edges: Vec<(u32, u32)> = (1..n).map(|i| (0, i)).collect();
197 let g = create(&edges, n, false).expect("create");
198 assert_eq!(g.vcount(), n);
199 assert_eq!(g.ecount(), (n - 1) as usize);
200 assert_eq!(g.degree(0).unwrap(), (n - 1) as usize);
201 for v in 1..n {
202 assert_eq!(g.degree(v).unwrap(), 1);
203 }
204 }
205}
206
207#[cfg(all(test, feature = "proptest-harness"))]
208mod proptests {
209 use super::*;
210 use proptest::prelude::*;
211
212 fn arb_pairs(max_n: u32) -> impl Strategy<Value = Vec<(u32, u32)>> {
213 prop::collection::vec((0..max_n, 0..max_n), 0..30)
214 }
215
216 proptest! {
217 #[test]
218 fn vcount_at_least_n_and_at_least_max_plus_one(
219 edges in arb_pairs(20),
220 n in 0u32..30,
221 directed: bool,
222 ) {
223 let g = create(&edges, n, directed).expect("create");
224 let inferred = edges
225 .iter()
226 .flat_map(|&(u, v)| [u, v])
227 .max()
228 .map_or(0u32, |m| m + 1);
229 prop_assert!(g.vcount() >= n);
230 prop_assert!(g.vcount() >= inferred);
231 prop_assert_eq!(g.vcount(), n.max(inferred));
232 }
233
234 #[test]
235 fn ecount_equals_input_length(
236 edges in arb_pairs(20),
237 n in 0u32..30,
238 directed: bool,
239 ) {
240 let g = create(&edges, n, directed).expect("create");
241 prop_assert_eq!(g.ecount(), edges.len());
242 }
243
244 #[test]
245 fn directed_preserves_arc_order_property(
246 edges in arb_pairs(15),
247 ) {
248 let g = create(&edges, 0, true).expect("create");
249 let got: Vec<(u32, u32)> = (0..u32::try_from(g.ecount()).unwrap())
250 .map(|eid| g.edge(eid).unwrap())
251 .collect();
252 prop_assert_eq!(got, edges);
253 }
254
255 #[test]
256 fn directedness_flag_round_trips(
257 edges in arb_pairs(15),
258 n in 0u32..20,
259 directed: bool,
260 ) {
261 let g = create(&edges, n, directed).expect("create");
262 prop_assert_eq!(g.is_directed(), directed);
263 }
264 }
265}