Skip to main content

rust_igraph/core/
graph.rs

1//! `Graph` — pure-Rust port of `igraph_t`.
2//!
3//! Storage is the **indexed edge list** that upstream igraph uses (see
4//! `references/igraph/include/igraph_datatype.h:105-116`):
5//!
6//! - `from[e]`, `to[e]` — canonical edge list. Edge `e` runs from
7//!   `from[e]` to `to[e]`; `|from| == |to| == ecount`.
8//! - `oi[i]` — edge ids ordered by `from` (and then `to`).
9//! - `ii[i]` — edge ids ordered by `to` (and then `from`).
10//! - `os[v]..os[v+1]` — slice of `oi` covering vertex `v`'s out-edges.
11//! - `is[v]..is[v+1]` — slice of `ii` covering vertex `v`'s in-edges.
12//!
13//! For undirected graphs the edge list is canonicalised so `from[e] <= to[e]`
14//! (matching upstream igraph's invariant in `type_indexededgelist.c:282-288`).
15//! The doubled in/out indexing makes `neighbors()` symmetric for undirected
16//! graphs without storing each edge twice.
17//!
18//! ALGO-CORE-001a (Phase 1, this file): struct + `new`/`with_vertices` +
19//! `add_vertices`/`add_edge`/`add_edges` + `vcount`/`ecount`/`is_directed` +
20//! `neighbors`/`degree` + `Clone`.
21//!
22//! Follow-up AWUs:
23//! - 001b: `incident`, edge-id helpers.
24//! - 001c: `delete_vertices`/`delete_edges`.
25//! - 001d: `edge`/`edges`/`get_eid`/`get_eids`/`get_all_eids_between`.
26//! - 001e: property cache, `is_same_graph`.
27//!
28//! Attribute system → ALGO-AT-* (out of scope here).
29
30use std::collections::HashMap;
31
32use super::attributes::AttributeValue;
33use super::cache::{
34    CachedProperty, PropertyCache, invalidate_after_add_edges, invalidate_after_add_vertices,
35};
36use super::error::{IgraphError, IgraphResult};
37
38/// Vertex id. The Phase-0 ADR-0007 fixes this to `u32`; `Option<VertexId>`
39/// is the idiomatic "no vertex" sentinel (igraph C uses `-1`).
40pub type VertexId = u32;
41
42/// Edge id. Same width as [`VertexId`]; an edge id is its position in
43/// `from`/`to`.
44pub type EdgeId = u32;
45
46/// Iterator over graph edges as `(from, to)` pairs.
47pub type EdgeIter<'a> = std::iter::Map<
48    std::iter::Zip<std::slice::Iter<'a, VertexId>, std::slice::Iter<'a, VertexId>>,
49    fn((&'a VertexId, &'a VertexId)) -> (VertexId, VertexId),
50>;
51
52/// Zero-allocation iterator over the neighbors of a vertex.
53///
54/// For directed graphs, yields out-neighbors in ascending order.
55/// For undirected graphs, yields all neighbors in ascending order by
56/// merging the out-edge and in-edge sublists on the fly.
57///
58/// Created by [`Graph::neighbors_iter`].
59pub struct NeighborsIter<'a> {
60    graph: &'a Graph,
61    out_pos: usize,
62    out_end: usize,
63    in_pos: usize,
64    in_end: usize,
65    directed: bool,
66}
67
68impl Iterator for NeighborsIter<'_> {
69    type Item = VertexId;
70
71    fn next(&mut self) -> Option<Self::Item> {
72        if self.directed {
73            if self.out_pos < self.out_end {
74                let eid = self.graph.oi[self.out_pos] as usize;
75                self.out_pos += 1;
76                Some(self.graph.to[eid])
77            } else {
78                None
79            }
80        } else {
81            let have_out = self.out_pos < self.out_end;
82            let have_in = self.in_pos < self.in_end;
83            match (have_out, have_in) {
84                (false, false) => None,
85                (true, false) => {
86                    let eid = self.graph.oi[self.out_pos] as usize;
87                    self.out_pos += 1;
88                    Some(self.graph.to[eid])
89                }
90                (false, true) => {
91                    let eid = self.graph.ii[self.in_pos] as usize;
92                    self.in_pos += 1;
93                    Some(self.graph.from[eid])
94                }
95                (true, true) => {
96                    let a = self.graph.to[self.graph.oi[self.out_pos] as usize];
97                    let b = self.graph.from[self.graph.ii[self.in_pos] as usize];
98                    if a <= b {
99                        self.out_pos += 1;
100                        Some(a)
101                    } else {
102                        self.in_pos += 1;
103                        Some(b)
104                    }
105                }
106            }
107        }
108    }
109
110    fn size_hint(&self) -> (usize, Option<usize>) {
111        let remaining = (self.out_end - self.out_pos) + (self.in_end - self.in_pos);
112        (remaining, Some(remaining))
113    }
114}
115
116impl ExactSizeIterator for NeighborsIter<'_> {}
117
118/// Counterpart of `igraph_t` (see `references/igraph/include/igraph_datatype.h`).
119///
120/// Phase-0 callers (`bfs`, `read_edgelist`, oracle tests) only depended on
121/// `with_vertices`, `add_edge`, `add_edges`, `vcount`, `ecount`, `neighbors`,
122/// `degree` — those signatures are preserved here, so existing call sites
123/// compile unchanged. New for Phase 1: `new` (with `directed` flag),
124/// `is_directed`.
125#[derive(Debug, Clone, Default)]
126pub struct Graph {
127    /// Vertex count. Redundant with the highest used id; mirrors `igraph_t::n`.
128    n: u32,
129    /// Whether the graph is directed.
130    directed: bool,
131    /// Source endpoints, one per edge.
132    from: Vec<VertexId>,
133    /// Target endpoints, one per edge.
134    to: Vec<VertexId>,
135    /// Edge ids in `from`-major order.
136    oi: Vec<EdgeId>,
137    /// Edge ids in `to`-major order.
138    ii: Vec<EdgeId>,
139    /// `os[v]..os[v+1]` is the slice of `oi` for vertex `v`'s out-edges.
140    /// Length is `n + 1`; `os[0] == 0`, `os[n] == ecount`.
141    os: Vec<u32>,
142    /// `is[v]..is[v+1]` for incoming. Same shape as `os`.
143    is: Vec<u32>,
144    /// Boolean property cache. Mirrors `igraph_t::cache`.
145    cache: PropertyCache,
146    /// Graph-level attributes (name → value).
147    gattrs: HashMap<String, AttributeValue>,
148    /// Vertex attributes (name → vec of values, one per vertex).
149    vertex_attrs: HashMap<String, Vec<AttributeValue>>,
150    /// Edge attributes (name → vec of values, one per edge).
151    edge_attrs: HashMap<String, Vec<AttributeValue>>,
152}
153
154impl Graph {
155    /// Construct an empty graph on `n` vertices.
156    ///
157    /// Counterpart of `igraph_empty()`; `directed` defaults to `false` if
158    /// you use [`Graph::with_vertices`] instead.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use rust_igraph::Graph;
164    ///
165    /// let g = Graph::new(5, true).unwrap();
166    /// assert_eq!(g.vcount(), 5);
167    /// assert_eq!(g.ecount(), 0);
168    /// assert!(g.is_directed());
169    /// ```
170    pub fn new(n: u32, directed: bool) -> IgraphResult<Self> {
171        let mut g = Self {
172            n: 0,
173            directed,
174            from: Vec::new(),
175            to: Vec::new(),
176            oi: Vec::new(),
177            ii: Vec::new(),
178            os: vec![0],
179            is: vec![0],
180            cache: PropertyCache::new(),
181            gattrs: HashMap::new(),
182            vertex_attrs: HashMap::new(),
183            edge_attrs: HashMap::new(),
184        };
185        g.add_vertices(n)?;
186        Ok(g)
187    }
188
189    /// Build a graph from an edge list, inferring the vertex count from
190    /// the highest endpoint.
191    ///
192    /// This is the most ergonomic way to create a small graph. The vertex
193    /// count is `max(u, v) + 1` over all `(u, v)` pairs (or 0 if `edges`
194    /// is empty and `n_override` is `None`).
195    ///
196    /// `n_override` can force a minimum vertex count (useful when you want
197    /// isolated vertices beyond the edges). Pass `None` to auto-derive.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use rust_igraph::Graph;
203    ///
204    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], false, None).unwrap();
205    /// assert_eq!(g.vcount(), 3);
206    /// assert_eq!(g.ecount(), 3);
207    /// assert!(!g.is_directed());
208    /// ```
209    pub fn from_edges(
210        edges: &[(u32, u32)],
211        directed: bool,
212        n_override: Option<u32>,
213    ) -> IgraphResult<Self> {
214        let max_id = edges
215            .iter()
216            .flat_map(|&(u, v)| [u, v])
217            .max()
218            .map_or(Some(0), |m| m.checked_add(1));
219        let auto_n = max_id.ok_or(IgraphError::InvalidArgument(
220            "vertex id overflow in from_edges".to_owned(),
221        ))?;
222        let n = n_override.map_or(auto_n, |ov| ov.max(auto_n));
223        let mut g = Self::new(n, directed)?;
224        g.add_edges(edges.to_vec())?;
225        Ok(g)
226    }
227
228    /// Build a graph from weighted edges, returning both the graph and the
229    /// weight vector (indexed by edge id).
230    ///
231    /// Each element of `edges` is `(from, to, weight)`. The resulting weight
232    /// vector has length equal to the edge count, with `weights[eid]`
233    /// corresponding to the edge added from the `eid`-th tuple.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use rust_igraph::Graph;
239    ///
240    /// let (g, weights) = Graph::from_weighted_edges(
241    ///     &[(0, 1, 1.5), (1, 2, 2.0), (2, 0, 0.5)],
242    ///     false,
243    ///     None,
244    /// ).unwrap();
245    /// assert_eq!(g.vcount(), 3);
246    /// assert_eq!(g.ecount(), 3);
247    /// assert_eq!(weights, vec![1.5, 2.0, 0.5]);
248    /// ```
249    pub fn from_weighted_edges(
250        edges: &[(u32, u32, f64)],
251        directed: bool,
252        n_override: Option<u32>,
253    ) -> IgraphResult<(Self, Vec<f64>)> {
254        let plain: Vec<(u32, u32)> = edges.iter().map(|&(u, v, _)| (u, v)).collect();
255        let weights: Vec<f64> = edges.iter().map(|&(_, _, w)| w).collect();
256        let g = Self::from_edges(&plain, directed, n_override)?;
257        Ok((g, weights))
258    }
259
260    /// Parse an undirected graph from an edge-list string.
261    ///
262    /// Each non-empty, non-comment line should contain two whitespace-separated
263    /// vertex ids. Lines starting with `#` are ignored. This is the most
264    /// convenient way to construct a graph inline (e.g. in tests or examples).
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// use rust_igraph::Graph;
270    ///
271    /// let g = Graph::from_edge_list_str("0 1\n1 2\n2 0").unwrap();
272    /// assert_eq!(g.vcount(), 3);
273    /// assert_eq!(g.ecount(), 3);
274    /// ```
275    pub fn from_edge_list_str(s: &str) -> IgraphResult<Self> {
276        use std::io::Cursor;
277        crate::algorithms::io::edgelist::read_edgelist(Cursor::new(s))
278    }
279
280    /// Construct a graph from an adjacency matrix.
281    ///
282    /// Counterpart of `igraph_adjacency()`. The matrix should be a
283    /// square `n×n` slice-of-slices where `matrix[i][j]` gives the
284    /// number of edges from vertex `i` to vertex `j` (or the edge
285    /// weight; see below).
286    ///
287    /// For undirected graphs (`directed = false`), only the upper
288    /// triangle is used (including diagonal for self-loops); the lower
289    /// triangle is ignored. Each non-zero entry `matrix[i][j]` (with
290    /// `i <= j`) creates one edge.
291    ///
292    /// For directed graphs, every non-zero entry creates one edge.
293    ///
294    /// Entries are rounded to the nearest integer to determine edge
295    /// count. If you need fractional weights, use
296    /// [`from_adjacency_matrix_weighted`](Graph::from_adjacency_matrix_weighted).
297    ///
298    /// # Errors
299    ///
300    /// Returns an error if the matrix is not square.
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// use rust_igraph::Graph;
306    ///
307    /// let adj = vec![
308    ///     vec![0.0, 1.0, 1.0],
309    ///     vec![1.0, 0.0, 1.0],
310    ///     vec![1.0, 1.0, 0.0],
311    /// ];
312    /// let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
313    /// assert_eq!(g.vcount(), 3);
314    /// assert_eq!(g.ecount(), 3); // triangle
315    /// ```
316    pub fn from_adjacency_matrix(matrix: &[Vec<f64>], directed: bool) -> IgraphResult<Self> {
317        let n = matrix.len();
318        for row in matrix {
319            if row.len() != n {
320                return Err(IgraphError::InvalidArgument(format!(
321                    "adjacency matrix is not square: got row of length {} for {}×{} matrix",
322                    row.len(),
323                    n,
324                    n
325                )));
326            }
327        }
328
329        let n_u32 = u32::try_from(n)
330            .map_err(|_| IgraphError::InvalidArgument("matrix too large for u32".to_owned()))?;
331        let mut graph = Self::new(n_u32, directed)?;
332
333        #[allow(clippy::cast_possible_truncation)]
334        if directed {
335            for (i, row) in matrix.iter().enumerate() {
336                for (j, &val) in row.iter().enumerate() {
337                    let count = val.round() as i64;
338                    for _ in 0..count.max(0) {
339                        graph.add_edge(i as u32, j as u32)?;
340                    }
341                }
342            }
343        } else {
344            for (i, row) in matrix.iter().enumerate() {
345                for (j, &val) in row.iter().enumerate().skip(i) {
346                    let count = val.round() as i64;
347                    for _ in 0..count.max(0) {
348                        graph.add_edge(i as u32, j as u32)?;
349                    }
350                }
351            }
352        }
353
354        Ok(graph)
355    }
356
357    /// Construct a graph from an adjacency matrix, also returning edge weights.
358    ///
359    /// Like [`from_adjacency_matrix`](Graph::from_adjacency_matrix), but
360    /// instead of rounding entries to edge counts, each non-zero entry
361    /// creates exactly one edge with the matrix value as its weight.
362    ///
363    /// Returns the graph and a weight vector aligned with edge indices.
364    ///
365    /// # Errors
366    ///
367    /// Returns an error if the matrix is not square.
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use rust_igraph::Graph;
373    ///
374    /// let adj = vec![
375    ///     vec![0.0, 2.5, 0.0],
376    ///     vec![2.5, 0.0, 1.0],
377    ///     vec![0.0, 1.0, 0.0],
378    /// ];
379    /// let (g, weights) = Graph::from_adjacency_matrix_weighted(&adj, false).unwrap();
380    /// assert_eq!(g.vcount(), 3);
381    /// assert_eq!(g.ecount(), 2);
382    /// assert!((weights[0] - 2.5).abs() < 1e-10);
383    /// assert!((weights[1] - 1.0).abs() < 1e-10);
384    /// ```
385    pub fn from_adjacency_matrix_weighted(
386        matrix: &[Vec<f64>],
387        directed: bool,
388    ) -> IgraphResult<(Self, Vec<f64>)> {
389        let n = matrix.len();
390        for row in matrix {
391            if row.len() != n {
392                return Err(IgraphError::InvalidArgument(format!(
393                    "adjacency matrix is not square: got row of length {} for {}×{} matrix",
394                    row.len(),
395                    n,
396                    n
397                )));
398            }
399        }
400
401        let n_u32 = u32::try_from(n)
402            .map_err(|_| IgraphError::InvalidArgument("matrix too large for u32".to_owned()))?;
403        let mut graph = Self::new(n_u32, directed)?;
404        let mut weights = Vec::new();
405
406        #[allow(clippy::cast_possible_truncation)]
407        if directed {
408            for (i, row) in matrix.iter().enumerate() {
409                for (j, &w) in row.iter().enumerate() {
410                    if w != 0.0 {
411                        graph.add_edge(i as u32, j as u32)?;
412                        weights.push(w);
413                    }
414                }
415            }
416        } else {
417            for (i, row) in matrix.iter().enumerate() {
418                for (j, &w) in row.iter().enumerate().skip(i) {
419                    if w != 0.0 {
420                        graph.add_edge(i as u32, j as u32)?;
421                        weights.push(w);
422                    }
423                }
424            }
425        }
426
427        Ok((graph, weights))
428    }
429
430    /// Construct a graph from an adjacency list.
431    ///
432    /// `adj_list[v]` contains the neighbors of vertex `v`. The number of
433    /// vertices is `adj_list.len()`.
434    ///
435    /// For undirected graphs (`directed = false`), an edge `(u, v)` should
436    /// appear in both `adj_list[u]` and `adj_list[v]`; each pair is
437    /// counted once (duplicates are deduplicated by only adding edge `(u, v)`
438    /// when `u <= v` or when it appears only in `adj_list[u]`).
439    ///
440    /// For directed graphs, `adj_list[v]` lists the **out-neighbors** of `v`.
441    ///
442    /// # Errors
443    ///
444    /// Returns an error if any neighbor index is out of range.
445    ///
446    /// # Examples
447    ///
448    /// ```
449    /// use rust_igraph::Graph;
450    ///
451    /// // Triangle: 0-1, 1-2, 0-2
452    /// let adj = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
453    /// let g = Graph::from_adjacency_list(&adj, false).unwrap();
454    /// assert_eq!(g.vcount(), 3);
455    /// assert_eq!(g.ecount(), 3);
456    /// ```
457    ///
458    /// ```
459    /// use rust_igraph::Graph;
460    ///
461    /// // Directed: 0->1, 0->2, 1->2
462    /// let adj = vec![vec![1, 2], vec![2], vec![]];
463    /// let g = Graph::from_adjacency_list(&adj, true).unwrap();
464    /// assert_eq!(g.vcount(), 3);
465    /// assert_eq!(g.ecount(), 3);
466    /// assert!(g.is_directed());
467    /// ```
468    pub fn from_adjacency_list(adj_list: &[Vec<u32>], directed: bool) -> IgraphResult<Self> {
469        let n = u32::try_from(adj_list.len()).map_err(|_| {
470            IgraphError::InvalidArgument("adjacency list too large for u32".to_owned())
471        })?;
472
473        let mut graph = Self::new(n, directed)?;
474
475        if directed {
476            for (src, neighbors) in adj_list.iter().enumerate() {
477                #[allow(clippy::cast_possible_truncation)]
478                let src_u32 = src as u32;
479                for &tgt in neighbors {
480                    if tgt >= n {
481                        return Err(IgraphError::VertexOutOfRange { id: tgt, n });
482                    }
483                    graph.add_edge(src_u32, tgt)?;
484                }
485            }
486        } else {
487            for (src, neighbors) in adj_list.iter().enumerate() {
488                #[allow(clippy::cast_possible_truncation)]
489                let src_u32 = src as u32;
490                for &tgt in neighbors {
491                    if tgt >= n {
492                        return Err(IgraphError::VertexOutOfRange { id: tgt, n });
493                    }
494                    if src_u32 <= tgt {
495                        graph.add_edge(src_u32, tgt)?;
496                    }
497                }
498            }
499        }
500
501        Ok(graph)
502    }
503
504    /// Construct an empty *undirected* graph on `n` vertices.
505    ///
506    /// Builds the graph directly (no intermediate `Result`) since an
507    /// empty undirected graph with `n` vertices cannot fail to construct.
508    ///
509    /// # Examples
510    ///
511    /// ```
512    /// use rust_igraph::Graph;
513    ///
514    /// let g = Graph::with_vertices(4);
515    /// assert_eq!(g.vcount(), 4);
516    /// assert!(!g.is_directed());
517    /// ```
518    pub fn with_vertices(n: u32) -> Self {
519        let len = n as usize + 1;
520        Self {
521            n,
522            directed: false,
523            from: Vec::new(),
524            to: Vec::new(),
525            oi: Vec::new(),
526            ii: Vec::new(),
527            os: vec![0; len],
528            is: vec![0; len],
529            cache: PropertyCache::new(),
530            gattrs: HashMap::new(),
531            vertex_attrs: HashMap::new(),
532            edge_attrs: HashMap::new(),
533        }
534    }
535
536    /// Number of vertices. Counterpart of `igraph_vcount()`.
537    ///
538    /// # Examples
539    ///
540    /// ```
541    /// use rust_igraph::Graph;
542    ///
543    /// let g = Graph::with_vertices(10);
544    /// assert_eq!(g.vcount(), 10);
545    /// ```
546    #[must_use]
547    pub fn vcount(&self) -> u32 {
548        self.n
549    }
550
551    /// Number of edges. Counterpart of `igraph_ecount()`.
552    ///
553    /// # Examples
554    ///
555    /// ```
556    /// use rust_igraph::Graph;
557    ///
558    /// let mut g = Graph::with_vertices(3);
559    /// g.add_edge(0, 1).unwrap();
560    /// g.add_edge(1, 2).unwrap();
561    /// assert_eq!(g.ecount(), 2);
562    /// ```
563    #[must_use]
564    pub fn ecount(&self) -> usize {
565        self.from.len()
566    }
567
568    /// `true` if the graph is directed. Counterpart of `igraph_is_directed()`.
569    ///
570    /// # Examples
571    ///
572    /// ```
573    /// use rust_igraph::Graph;
574    ///
575    /// let g = Graph::new(3, true).unwrap();
576    /// assert!(g.is_directed());
577    ///
578    /// let g2 = Graph::with_vertices(3);
579    /// assert!(!g2.is_directed());
580    /// ```
581    #[must_use]
582    pub fn is_directed(&self) -> bool {
583        self.directed
584    }
585
586    /// Iterator over vertex ids `0..vcount()`.
587    ///
588    /// # Examples
589    ///
590    /// ```
591    /// use rust_igraph::Graph;
592    ///
593    /// let g = Graph::with_vertices(4);
594    /// let ids: Vec<u32> = g.vertex_ids().collect();
595    /// assert_eq!(ids, vec![0, 1, 2, 3]);
596    /// ```
597    pub fn vertex_ids(&self) -> impl Iterator<Item = VertexId> {
598        0..self.n
599    }
600
601    /// Iterator over edge ids `0..ecount()`.
602    ///
603    /// # Examples
604    ///
605    /// ```
606    /// use rust_igraph::Graph;
607    ///
608    /// let mut g = Graph::with_vertices(3);
609    /// g.add_edge(0, 1).unwrap();
610    /// g.add_edge(1, 2).unwrap();
611    /// let ids: Vec<u32> = g.edge_ids().collect();
612    /// assert_eq!(ids, vec![0, 1]);
613    /// ```
614    pub fn edge_ids(&self) -> impl Iterator<Item = u32> {
615        let m = u32::try_from(self.from.len()).unwrap_or(u32::MAX);
616        0..m
617    }
618
619    /// Iterator over all edges as `(from, to)` pairs.
620    ///
621    /// Yields edges in edge-id order. For undirected graphs, `from <= to`
622    /// (canonicalised storage order).
623    ///
624    /// # Examples
625    ///
626    /// ```
627    /// use rust_igraph::Graph;
628    ///
629    /// let mut g = Graph::with_vertices(3);
630    /// g.add_edge(0, 1).unwrap();
631    /// g.add_edge(1, 2).unwrap();
632    /// let edges: Vec<(u32, u32)> = g.edges().collect();
633    /// assert_eq!(edges, vec![(0, 1), (1, 2)]);
634    /// ```
635    pub fn edges(&self) -> impl Iterator<Item = (VertexId, VertexId)> + '_ {
636        self.from.iter().zip(self.to.iter()).map(|(&u, &v)| (u, v))
637    }
638
639    /// Returns an iterator over edges as `(from, to)` pairs in edge-id order.
640    ///
641    /// This is the named-return counterpart to the `IntoIterator` impl
642    /// for `&Graph`, enabling `graph.iter().filter(...)` usage.
643    ///
644    /// # Examples
645    ///
646    /// ```
647    /// use rust_igraph::Graph;
648    ///
649    /// let mut g = Graph::with_vertices(3);
650    /// g.add_edge(0, 1).unwrap();
651    /// g.add_edge(1, 2).unwrap();
652    ///
653    /// let edges: Vec<_> = g.iter().collect();
654    /// assert_eq!(edges, vec![(0, 1), (1, 2)]);
655    /// ```
656    pub fn iter(&self) -> EdgeIter<'_> {
657        self.from.iter().zip(self.to.iter()).map(|(&a, &b)| (a, b))
658    }
659
660    /// Check whether an edge exists between `from` and `to`.
661    ///
662    /// On undirected graphs `(u, v)` and `(v, u)` are equivalent.
663    /// Returns `false` for out-of-range vertex ids rather than erroring.
664    ///
665    /// # Examples
666    ///
667    /// ```
668    /// use rust_igraph::Graph;
669    ///
670    /// let mut g = Graph::with_vertices(3);
671    /// g.add_edge(0, 1).unwrap();
672    /// assert!(g.has_edge(0, 1));
673    /// assert!(g.has_edge(1, 0)); // undirected
674    /// assert!(!g.has_edge(0, 2));
675    /// ```
676    pub fn has_edge(&self, from: VertexId, to: VertexId) -> bool {
677        self.find_eid(from, to).ok().flatten().is_some()
678    }
679
680    /// Append `nv` isolated vertices, returning the inclusive id range
681    /// `(first, last)` of the new vertices. If `nv == 0` returns
682    /// `(self.n, self.n)` and does nothing.
683    ///
684    /// Counterpart of `igraph_add_vertices()`.
685    ///
686    /// # Examples
687    ///
688    /// ```
689    /// use rust_igraph::Graph;
690    ///
691    /// let mut g = Graph::with_vertices(3);
692    /// let (first, last) = g.add_vertices(2).unwrap();
693    /// assert_eq!(first, 3);
694    /// assert_eq!(last, 4);
695    /// assert_eq!(g.vcount(), 5);
696    /// ```
697    pub fn add_vertices(&mut self, nv: u32) -> IgraphResult<(VertexId, VertexId)> {
698        let new_n = self
699            .n
700            .checked_add(nv)
701            .ok_or(IgraphError::Internal("vertex count overflow"))?;
702        let first = self.n;
703        // os/is grow by `nv` entries, all initialised to ecount.
704        let ec = u32::try_from(self.ecount())
705            .map_err(|_| IgraphError::Internal("edge count exceeds u32::MAX"))?;
706        for _ in 0..nv {
707            self.os.push(ec);
708            self.is.push(ec);
709        }
710        // Extend vertex attribute vectors with defaults.
711        for vals in self.vertex_attrs.values_mut() {
712            if let Some(first_val) = vals.first() {
713                let default = first_val.default_for_same_type();
714                vals.resize(new_n as usize, default);
715            }
716        }
717        self.n = new_n;
718        if nv > 0 {
719            invalidate_after_add_vertices(&self.cache);
720        }
721        Ok((first, new_n.saturating_sub(1)))
722    }
723
724    /// Add a single edge from `u` to `v`.
725    ///
726    /// Self-loops and parallel edges are allowed. For undirected graphs the
727    /// edge is canonicalised so the stored `from <= to`.
728    ///
729    /// # Examples
730    ///
731    /// ```
732    /// use rust_igraph::Graph;
733    ///
734    /// let mut g = Graph::with_vertices(3);
735    /// g.add_edge(0, 1).unwrap();
736    /// g.add_edge(1, 2).unwrap();
737    /// assert_eq!(g.ecount(), 2);
738    /// ```
739    pub fn add_edge(&mut self, u: VertexId, v: VertexId) -> IgraphResult<()> {
740        self.add_edges(std::iter::once((u, v)))
741    }
742
743    /// Add a sequence of edges. After all edges are appended, the indexes
744    /// (`oi` / `ii` / `os` / `is`) are rebuilt in one pass — counterpart of
745    /// `igraph_add_edges` (`type_indexededgelist.c:254-367`).
746    ///
747    /// # Examples
748    ///
749    /// ```
750    /// use rust_igraph::Graph;
751    ///
752    /// let mut g = Graph::with_vertices(4);
753    /// g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
754    /// assert_eq!(g.ecount(), 3);
755    /// ```
756    pub fn add_edges<I>(&mut self, edges: I) -> IgraphResult<()>
757    where
758        I: IntoIterator<Item = (VertexId, VertexId)>,
759    {
760        let m_before = self.ecount();
761        for (u, v) in edges {
762            self.check_vertex(u)?;
763            self.check_vertex(v)?;
764            if !self.directed && u > v {
765                self.from.push(v);
766                self.to.push(u);
767            } else {
768                self.from.push(u);
769                self.to.push(v);
770            }
771        }
772        self.rebuild_indexes()?;
773        let m_after = self.ecount();
774        // Extend edge attribute vectors with defaults.
775        if m_after > m_before {
776            for vals in self.edge_attrs.values_mut() {
777                if let Some(first_val) = vals.first() {
778                    let default = first_val.default_for_same_type();
779                    vals.resize(m_after, default);
780                }
781            }
782            invalidate_after_add_edges(&self.cache);
783        }
784        Ok(())
785    }
786
787    /// Out-edge neighbour iterator for vertex `v`.
788    ///
789    /// For undirected graphs this returns *all* neighbours (since the
790    /// indexing tracks both endpoints symmetrically). Order is the upstream
791    /// igraph order — edges are visited in `oi` order, then `ii` order, with
792    /// duplicates suppressed when the same edge is incident on both.
793    ///
794    /// Counterpart of `igraph_neighbors(graph, _, vid, IGRAPH_ALL, ...)`.
795    ///
796    /// # Examples
797    ///
798    /// ```
799    /// use rust_igraph::Graph;
800    ///
801    /// let mut g = Graph::with_vertices(4);
802    /// g.add_edge(0, 1).unwrap();
803    /// g.add_edge(0, 2).unwrap();
804    /// g.add_edge(0, 3).unwrap();
805    /// let neis = g.neighbors(0).unwrap();
806    /// assert_eq!(neis, vec![1, 2, 3]);
807    /// ```
808    pub fn neighbors(&self, v: VertexId) -> IgraphResult<Vec<VertexId>> {
809        self.check_vertex(v)?;
810        let v_idx = v as usize;
811        if self.directed {
812            // Directed: only outgoing neighbours; oi sorted by (from, to)
813            // so the out-neighbour list is already sorted ascending.
814            let out_range = self.os[v_idx] as usize..self.os[v_idx + 1] as usize;
815            let out: Vec<VertexId> = self.oi[out_range]
816                .iter()
817                .map(|&e| self.to[e as usize])
818                .collect();
819            Ok(out)
820        } else {
821            // Undirected: merge the two already-sorted sublists from oi
822            // (out-side, ascending in `to`) and ii (in-side, ascending
823            // in `from`) into one ascending neighbour list. Matches
824            // upstream `igraph_neighbors(_, _, _, IGRAPH_ALL)` and
825            // python-igraph's `Graph.neighbors(v)` exactly.
826            let out_start = self.os[v_idx] as usize;
827            let out_end = self.os[v_idx + 1] as usize;
828            let in_start = self.is[v_idx] as usize;
829            let in_end = self.is[v_idx + 1] as usize;
830            let mut out = Vec::with_capacity((out_end - out_start) + (in_end - in_start));
831            let mut out_idx = out_start;
832            let mut in_idx = in_start;
833            while out_idx < out_end && in_idx < in_end {
834                let a = self.to[self.oi[out_idx] as usize];
835                let b = self.from[self.ii[in_idx] as usize];
836                if a <= b {
837                    out.push(a);
838                    out_idx += 1;
839                } else {
840                    out.push(b);
841                    in_idx += 1;
842                }
843            }
844            while out_idx < out_end {
845                out.push(self.to[self.oi[out_idx] as usize]);
846                out_idx += 1;
847            }
848            while in_idx < in_end {
849                out.push(self.from[self.ii[in_idx] as usize]);
850                in_idx += 1;
851            }
852            Ok(out)
853        }
854    }
855
856    /// Zero-allocation iterator over the neighbors of vertex `v`.
857    ///
858    /// For directed graphs, yields out-neighbors in ascending order.
859    /// For undirected graphs, yields all neighbors in ascending order
860    /// (merged from out-edge and in-edge sublists without allocation).
861    ///
862    /// Prefer this over [`Graph::neighbors`] in hot loops where avoiding
863    /// a `Vec` allocation matters.
864    ///
865    /// # Examples
866    ///
867    /// ```
868    /// use rust_igraph::Graph;
869    ///
870    /// let mut g = Graph::with_vertices(4);
871    /// g.add_edge(0, 1).unwrap();
872    /// g.add_edge(0, 2).unwrap();
873    /// g.add_edge(0, 3).unwrap();
874    /// let neis: Vec<u32> = g.neighbors_iter(0).unwrap().collect();
875    /// assert_eq!(neis, vec![1, 2, 3]);
876    /// ```
877    pub fn neighbors_iter(&self, v: VertexId) -> IgraphResult<NeighborsIter<'_>> {
878        self.check_vertex(v)?;
879        let v_idx = v as usize;
880        let out_pos = self.os[v_idx] as usize;
881        let out_end = self.os[v_idx + 1] as usize;
882        let (in_pos, in_end) = if self.directed {
883            (0, 0)
884        } else {
885            (self.is[v_idx] as usize, self.is[v_idx + 1] as usize)
886        };
887        Ok(NeighborsIter {
888            graph: self,
889            out_pos,
890            out_end,
891            in_pos,
892            in_end,
893            directed: self.directed,
894        })
895    }
896
897    /// Convert the graph to an adjacency list representation.
898    ///
899    /// Returns a `Vec<Vec<u32>>` where `result[v]` contains the neighbors
900    /// of vertex `v`. For directed graphs, returns out-neighbors.
901    ///
902    /// For undirected graphs, each edge `(u, v)` causes `v` to appear in
903    /// `result[u]` and `u` to appear in `result[v]`.
904    ///
905    /// # Examples
906    ///
907    /// ```
908    /// use rust_igraph::Graph;
909    ///
910    /// let mut g = Graph::with_vertices(3);
911    /// g.add_edge(0, 1).unwrap();
912    /// g.add_edge(1, 2).unwrap();
913    /// let adj = g.to_adjacency_list().unwrap();
914    /// assert_eq!(adj[0], vec![1]);
915    /// assert_eq!(adj[1], vec![0, 2]);
916    /// assert_eq!(adj[2], vec![1]);
917    /// ```
918    pub fn to_adjacency_list(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
919        let n = self.vcount();
920        let mut adj = vec![Vec::new(); n as usize];
921        for v in 0..n {
922            adj[v as usize] = self.neighbors(v)?;
923        }
924        Ok(adj)
925    }
926
927    /// Return the adjacency matrix as a dense `n × n` matrix of `f64`.
928    ///
929    /// Entry `[i][j]` is the number of edges from vertex `i` to vertex `j`.
930    /// For undirected graphs the matrix is symmetric. Self-loops contribute
931    /// 1 to `[i][i]` (not 2).
932    ///
933    /// # Examples
934    ///
935    /// ```
936    /// use rust_igraph::Graph;
937    ///
938    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
939    /// let m = g.to_adjacency_matrix();
940    /// assert_eq!(m[0][1], 1.0);
941    /// assert_eq!(m[1][0], 1.0);
942    /// assert_eq!(m[0][2], 0.0);
943    /// ```
944    pub fn to_adjacency_matrix(&self) -> Vec<Vec<f64>> {
945        let n = self.n as usize;
946        let mut mat = vec![vec![0.0f64; n]; n];
947        for eid in 0..self.ecount() {
948            let u = self.from[eid] as usize;
949            let v = self.to[eid] as usize;
950            mat[u][v] += 1.0;
951            if !self.directed && u != v {
952                mat[v][u] += 1.0;
953            }
954        }
955        mat
956    }
957
958    /// Degree of vertex `v` — number of edges incident to it.
959    ///
960    /// On undirected graphs every edge counts once except a self-loop which
961    /// counts twice (matches upstream igraph's `IGRAPH_LOOPS = TWICE` default
962    /// at `type_indexededgelist.c:1162`).
963    ///
964    /// Counterpart of `igraph_degree_1(_, _, _, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)`.
965    ///
966    /// # Examples
967    ///
968    /// ```
969    /// use rust_igraph::Graph;
970    ///
971    /// let mut g = Graph::with_vertices(3);
972    /// g.add_edge(0, 1).unwrap();
973    /// g.add_edge(0, 2).unwrap();
974    /// assert_eq!(g.degree(0).unwrap(), 2);
975    /// assert_eq!(g.degree(1).unwrap(), 1);
976    /// ```
977    pub fn degree(&self, v: VertexId) -> IgraphResult<usize> {
978        self.check_vertex(v)?;
979        let v_idx = v as usize;
980        let out = (self.os[v_idx + 1] - self.os[v_idx]) as usize;
981        let in_count = (self.is[v_idx + 1] - self.is[v_idx]) as usize;
982        Ok(out + in_count)
983    }
984
985    /// Out-degree of vertex `v` (number of outgoing edges).
986    ///
987    /// For undirected graphs, this equals the total degree.
988    ///
989    /// # Examples
990    ///
991    /// ```
992    /// use rust_igraph::Graph;
993    ///
994    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,0)], true, None).unwrap();
995    /// assert_eq!(g.out_degree(0).unwrap(), 2);
996    /// assert_eq!(g.out_degree(1).unwrap(), 1);
997    /// ```
998    pub fn out_degree(&self, v: VertexId) -> IgraphResult<usize> {
999        self.check_vertex(v)?;
1000        let v_idx = v as usize;
1001        if self.directed {
1002            Ok((self.os[v_idx + 1] - self.os[v_idx]) as usize)
1003        } else {
1004            let out = (self.os[v_idx + 1] - self.os[v_idx]) as usize;
1005            let in_count = (self.is[v_idx + 1] - self.is[v_idx]) as usize;
1006            Ok(out + in_count)
1007        }
1008    }
1009
1010    /// In-degree of vertex `v` (number of incoming edges).
1011    ///
1012    /// For undirected graphs, this equals the total degree.
1013    ///
1014    /// # Examples
1015    ///
1016    /// ```
1017    /// use rust_igraph::Graph;
1018    ///
1019    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,0)], true, None).unwrap();
1020    /// assert_eq!(g.in_degree(0).unwrap(), 1);
1021    /// assert_eq!(g.in_degree(1).unwrap(), 1);
1022    /// assert_eq!(g.in_degree(2).unwrap(), 1);
1023    /// ```
1024    pub fn in_degree(&self, v: VertexId) -> IgraphResult<usize> {
1025        self.check_vertex(v)?;
1026        let v_idx = v as usize;
1027        if self.directed {
1028            Ok((self.is[v_idx + 1] - self.is[v_idx]) as usize)
1029        } else {
1030            let out = (self.os[v_idx + 1] - self.os[v_idx]) as usize;
1031            let in_count = (self.is[v_idx + 1] - self.is[v_idx]) as usize;
1032            Ok(out + in_count)
1033        }
1034    }
1035
1036    /// Maximum degree across all vertices (total degree for undirected,
1037    /// out-degree for directed). Returns 0 for empty graphs.
1038    ///
1039    /// Counterpart of `igraph_maxdegree()`.
1040    ///
1041    /// # Examples
1042    ///
1043    /// ```
1044    /// use rust_igraph::Graph;
1045    ///
1046    /// let mut g = Graph::with_vertices(4);
1047    /// g.add_edge(0, 1).unwrap();
1048    /// g.add_edge(0, 2).unwrap();
1049    /// g.add_edge(0, 3).unwrap();
1050    /// assert_eq!(g.max_degree(), 3);
1051    /// ```
1052    pub fn max_degree(&self) -> usize {
1053        let n = self.vcount();
1054        if n == 0 {
1055            return 0;
1056        }
1057        (0..n)
1058            .map(|v| {
1059                let v_idx = v as usize;
1060                let out = (self.os[v_idx + 1] - self.os[v_idx]) as usize;
1061                let inc = (self.is[v_idx + 1] - self.is[v_idx]) as usize;
1062                if self.directed { out } else { out + inc }
1063            })
1064            .max()
1065            .unwrap_or(0)
1066    }
1067
1068    /// Minimum degree across all vertices (total degree for undirected,
1069    /// out-degree for directed). Returns 0 for empty graphs.
1070    ///
1071    /// Counterpart of `igraph_mindegree()` (custom extension).
1072    ///
1073    /// # Examples
1074    ///
1075    /// ```
1076    /// use rust_igraph::Graph;
1077    ///
1078    /// let mut g = Graph::with_vertices(4);
1079    /// g.add_edge(0, 1).unwrap();
1080    /// g.add_edge(0, 2).unwrap();
1081    /// // vertex 3 has degree 0
1082    /// assert_eq!(g.min_degree(), 0);
1083    /// ```
1084    pub fn min_degree(&self) -> usize {
1085        let n = self.vcount();
1086        if n == 0 {
1087            return 0;
1088        }
1089        (0..n)
1090            .map(|v| {
1091                let v_idx = v as usize;
1092                let out = (self.os[v_idx + 1] - self.os[v_idx]) as usize;
1093                let inc = (self.is[v_idx + 1] - self.is[v_idx]) as usize;
1094                if self.directed { out } else { out + inc }
1095            })
1096            .min()
1097            .unwrap_or(0)
1098    }
1099
1100    // ---------------------------------------------------------------
1101    // ALGO-CORE-001b: edge-id helpers + incident edges.
1102    // ---------------------------------------------------------------
1103
1104    /// Source endpoint of edge `eid`. Counterpart of `IGRAPH_FROM`
1105    /// (`igraph_interface.h:115`).
1106    ///
1107    /// # Examples
1108    ///
1109    /// ```
1110    /// use rust_igraph::Graph;
1111    ///
1112    /// let mut g = Graph::with_vertices(3);
1113    /// g.add_edge(0, 2).unwrap();
1114    /// assert_eq!(g.edge_source(0).unwrap(), 0);
1115    /// ```
1116    pub fn edge_source(&self, eid: EdgeId) -> IgraphResult<VertexId> {
1117        self.check_edge(eid)?;
1118        Ok(self.from[eid as usize])
1119    }
1120
1121    /// Target endpoint of edge `eid`. Counterpart of `IGRAPH_TO`
1122    /// (`igraph_interface.h:128`).
1123    ///
1124    /// # Examples
1125    ///
1126    /// ```
1127    /// use rust_igraph::Graph;
1128    ///
1129    /// let mut g = Graph::with_vertices(3);
1130    /// g.add_edge(0, 2).unwrap();
1131    /// assert_eq!(g.edge_target(0).unwrap(), 2);
1132    /// ```
1133    pub fn edge_target(&self, eid: EdgeId) -> IgraphResult<VertexId> {
1134        self.check_edge(eid)?;
1135        Ok(self.to[eid as usize])
1136    }
1137
1138    /// Both endpoints of edge `eid`, ordered as `(from, to)`. Counterpart
1139    /// of `igraph_edge` (`igraph_interface.h:71`).
1140    ///
1141    /// # Examples
1142    ///
1143    /// ```
1144    /// use rust_igraph::Graph;
1145    ///
1146    /// let mut g = Graph::with_vertices(3);
1147    /// g.add_edge(0, 1).unwrap();
1148    /// let (from, to) = g.edge(0).unwrap();
1149    /// assert_eq!(from, 0);
1150    /// assert_eq!(to, 1);
1151    /// ```
1152    pub fn edge(&self, eid: EdgeId) -> IgraphResult<(VertexId, VertexId)> {
1153        self.check_edge(eid)?;
1154        let i = eid as usize;
1155        Ok((self.from[i], self.to[i]))
1156    }
1157
1158    /// The other endpoint of `eid` given one endpoint `vid`. Counterpart
1159    /// of `IGRAPH_OTHER` (`igraph_interface.h:145`). Errors if `vid` is
1160    /// not actually an endpoint of `eid`.
1161    ///
1162    /// # Examples
1163    ///
1164    /// ```
1165    /// use rust_igraph::Graph;
1166    ///
1167    /// let mut g = Graph::with_vertices(3);
1168    /// g.add_edge(0, 2).unwrap();
1169    /// assert_eq!(g.edge_other(0, 0).unwrap(), 2);
1170    /// assert_eq!(g.edge_other(0, 2).unwrap(), 0);
1171    /// ```
1172    pub fn edge_other(&self, eid: EdgeId, vid: VertexId) -> IgraphResult<VertexId> {
1173        let (u, v) = self.edge(eid)?;
1174        if vid == u {
1175            Ok(v)
1176        } else if vid == v {
1177            Ok(u)
1178        } else {
1179            Err(IgraphError::InvalidArgument(format!(
1180                "vertex {vid} is not an endpoint of edge {eid} ({u}, {v})"
1181            )))
1182        }
1183    }
1184
1185    /// Edge ids incident to vertex `v`, in the same iteration order as
1186    /// [`Graph::neighbors`].
1187    ///
1188    /// For undirected graphs returns the union of out-side (`oi`) and
1189    /// in-side (`ii`) edges — every edge incident to `v` once, except
1190    /// self-loops which appear twice (matching `igraph_neighbors` /
1191    /// `igraph_degree`'s `IGRAPH_LOOPS_TWICE` default at
1192    /// `type_indexededgelist.c:1162`).
1193    ///
1194    /// For directed graphs returns out-edges only, mirroring this AWU's
1195    /// `neighbors()` choice. (The full mode-aware variant lands later
1196    /// alongside `igraph_neighbors(mode = IN/OUT/ALL)`.)
1197    ///
1198    /// Counterpart of `igraph_incident(_, _, v, IGRAPH_ALL, IGRAPH_LOOPS_TWICE)`
1199    /// for undirected; `IGRAPH_OUT` mode for directed.
1200    ///
1201    /// # Examples
1202    ///
1203    /// ```
1204    /// use rust_igraph::Graph;
1205    ///
1206    /// let mut g = Graph::with_vertices(3);
1207    /// g.add_edge(0, 1).unwrap(); // edge 0
1208    /// g.add_edge(0, 2).unwrap(); // edge 1
1209    /// let inc = g.incident(0).unwrap();
1210    /// assert_eq!(inc.len(), 2);
1211    /// ```
1212    pub fn incident(&self, v: VertexId) -> IgraphResult<Vec<EdgeId>> {
1213        self.check_vertex(v)?;
1214        let v_idx = v as usize;
1215        let out_range = self.os[v_idx] as usize..self.os[v_idx + 1] as usize;
1216        if self.directed {
1217            Ok(self.oi[out_range].to_vec())
1218        } else {
1219            let in_range = self.is[v_idx] as usize..self.is[v_idx + 1] as usize;
1220            let mut out = Vec::with_capacity(out_range.len() + in_range.len());
1221            out.extend_from_slice(&self.oi[out_range]);
1222            out.extend_from_slice(&self.ii[in_range]);
1223            Ok(out)
1224        }
1225    }
1226
1227    /// Companion to [`incident`](Self::incident): returns *only* the
1228    /// edges incoming to `v` for directed graphs. For undirected
1229    /// graphs the result is identical to `incident` (every edge is
1230    /// bidirectional).
1231    ///
1232    /// Counterpart of `igraph_incident(_, _, v, IGRAPH_IN, IGRAPH_LOOPS_TWICE)`.
1233    pub(crate) fn incident_in(&self, v: VertexId) -> IgraphResult<Vec<EdgeId>> {
1234        self.check_vertex(v)?;
1235        let v_idx = v as usize;
1236        if self.directed {
1237            let in_range = self.is[v_idx] as usize..self.is[v_idx + 1] as usize;
1238            Ok(self.ii[in_range].to_vec())
1239        } else {
1240            self.incident(v)
1241        }
1242    }
1243
1244    /// Edge id between `from` and `to`, if any.
1245    ///
1246    /// On undirected graphs `(u, v)` and `(v, u)` are equivalent.
1247    /// On directed graphs the search follows the edge direction
1248    /// `from -> to`. Returns [`crate::IgraphError::InvalidArgument`]
1249    /// when no such edge exists; for the "no error, return None" variant
1250    /// use [`Self::find_eid`].
1251    ///
1252    /// Counterpart of
1253    /// `igraph_get_eid(_, _, from, to, /*directed=*/true, /*error=*/true)`
1254    /// from `references/igraph/src/graph/type_indexededgelist.c:1522-1555`.
1255    /// Phase-1 minimal slice: linear scan across the from-bucket; the
1256    /// upstream binary-search optimisation lands in a perf pass.
1257    ///
1258    /// # Examples
1259    ///
1260    /// ```
1261    /// use rust_igraph::Graph;
1262    ///
1263    /// let mut g = Graph::with_vertices(3);
1264    /// g.add_edge(0, 1).unwrap();
1265    /// g.add_edge(1, 2).unwrap();
1266    /// assert_eq!(g.get_eid(0, 1).unwrap(), 0);
1267    /// assert_eq!(g.get_eid(1, 2).unwrap(), 1);
1268    /// assert!(g.get_eid(0, 2).is_err());
1269    /// ```
1270    pub fn get_eid(&self, from: VertexId, to: VertexId) -> IgraphResult<EdgeId> {
1271        self.find_eid(from, to)?
1272            .ok_or_else(|| IgraphError::InvalidArgument(format!("no edge between {from} and {to}")))
1273    }
1274
1275    /// Edge id between `from` and `to`, or `None` if not connected.
1276    ///
1277    /// Same semantics as [`Self::get_eid`] but no-error variant
1278    /// matching upstream's `error=false` mode. When parallel edges
1279    /// exist, returns the lowest edge id (matching upstream's
1280    /// "always returns the same edge ID" guarantee).
1281    ///
1282    /// # Examples
1283    ///
1284    /// ```
1285    /// use rust_igraph::Graph;
1286    ///
1287    /// let mut g = Graph::with_vertices(3);
1288    /// g.add_edge(0, 1).unwrap();
1289    /// assert_eq!(g.find_eid(0, 1).unwrap(), Some(0));
1290    /// assert_eq!(g.find_eid(0, 2).unwrap(), None);
1291    /// ```
1292    pub fn find_eid(&self, from: VertexId, to: VertexId) -> IgraphResult<Option<EdgeId>> {
1293        self.check_vertex(from)?;
1294        self.check_vertex(to)?;
1295        if self.directed {
1296            // Search out-bucket of `from` for `to[e] == to`.
1297            let range = self.os[from as usize] as usize..self.os[from as usize + 1] as usize;
1298            for &e in &self.oi[range] {
1299                if self.to[e as usize] == to {
1300                    return Ok(Some(e));
1301                }
1302            }
1303            Ok(None)
1304        } else {
1305            // Undirected: edges canonicalised so `from[e] <= to[e]`.
1306            // Search the bucket of the smaller endpoint for the larger.
1307            let (lo, hi) = if from <= to { (from, to) } else { (to, from) };
1308            let range = self.os[lo as usize] as usize..self.os[lo as usize + 1] as usize;
1309            for &e in &self.oi[range] {
1310                if self.to[e as usize] == hi {
1311                    return Ok(Some(e));
1312                }
1313            }
1314            Ok(None)
1315        }
1316    }
1317
1318    /// All edge ids between `from` and `to`, including parallel edges
1319    /// and (for self-loops) the loop edge once.
1320    ///
1321    /// Counterpart of
1322    /// `igraph_get_all_eids_between()` from
1323    /// `references/igraph/src/graph/type_indexededgelist.c:~1700`.
1324    /// On undirected graphs `(u, v)` and `(v, u)` are equivalent. The
1325    /// returned vector is sorted ascending by edge id.
1326    ///
1327    /// # Examples
1328    ///
1329    /// ```
1330    /// use rust_igraph::Graph;
1331    ///
1332    /// let mut g = Graph::with_vertices(2);
1333    /// g.add_edge(0, 1).unwrap();
1334    /// g.add_edge(0, 1).unwrap(); // parallel edge
1335    /// let eids = g.get_all_eids_between(0, 1).unwrap();
1336    /// assert_eq!(eids, vec![0, 1]);
1337    /// ```
1338    pub fn get_all_eids_between(&self, from: VertexId, to: VertexId) -> IgraphResult<Vec<EdgeId>> {
1339        self.check_vertex(from)?;
1340        self.check_vertex(to)?;
1341        let mut out = Vec::new();
1342        if self.directed {
1343            let range = self.os[from as usize] as usize..self.os[from as usize + 1] as usize;
1344            for &e in &self.oi[range] {
1345                if self.to[e as usize] == to {
1346                    out.push(e);
1347                }
1348            }
1349        } else {
1350            let (lo, hi) = if from <= to { (from, to) } else { (to, from) };
1351            let range = self.os[lo as usize] as usize..self.os[lo as usize + 1] as usize;
1352            for &e in &self.oi[range] {
1353                if self.to[e as usize] == hi {
1354                    out.push(e);
1355                }
1356            }
1357        }
1358        out.sort_unstable();
1359        Ok(out)
1360    }
1361
1362    /// Out-neighbours of `v` (always — directed or undirected). Each
1363    /// edge contributes one entry, in `oi[os[v]..os[v+1]]` order
1364    /// (lex by `(from, to)`). Self-loops appear once.
1365    ///
1366    /// Internal helper used by direction-aware algorithms (e.g.
1367    /// strongly connected components). The full mode-aware public
1368    /// surface ships with the next `igraph_neighbors` AWU.
1369    pub(crate) fn out_neighbors_vec(&self, v: VertexId) -> IgraphResult<Vec<VertexId>> {
1370        self.check_vertex(v)?;
1371        let v_idx = v as usize;
1372        let range = self.os[v_idx] as usize..self.os[v_idx + 1] as usize;
1373        Ok(self.oi[range]
1374            .iter()
1375            .map(|&e| self.to[e as usize])
1376            .collect())
1377    }
1378
1379    /// In-neighbours of `v` (always — directed or undirected). Each
1380    /// edge contributes one entry, in `ii[is[v]..is[v+1]]` order
1381    /// (lex by `(to, from)`). Self-loops appear once.
1382    ///
1383    /// Companion to [`out_neighbors_vec`](Self::out_neighbors_vec); see
1384    /// its doc for context on visibility.
1385    pub(crate) fn in_neighbors_vec(&self, v: VertexId) -> IgraphResult<Vec<VertexId>> {
1386        self.check_vertex(v)?;
1387        let v_idx = v as usize;
1388        let range = self.is[v_idx] as usize..self.is[v_idx + 1] as usize;
1389        Ok(self.ii[range]
1390            .iter()
1391            .map(|&e| self.from[e as usize])
1392            .collect())
1393    }
1394
1395    // ---------------------------------------------------------------
1396    // ALGO-CORE-001c: delete_edges + delete_vertices + delete_vertices_map.
1397    // ---------------------------------------------------------------
1398
1399    /// Remove the given edges from the graph.
1400    ///
1401    /// `edges` may contain the same id more than once — the second and
1402    /// later occurrences are no-ops. Remaining edges keep their
1403    /// pairwise relative order but are renumbered so edge ids stay
1404    /// contiguous starting at 0. Returns
1405    /// [`IgraphError::EdgeOutOfRange`] if any id is `>= ecount()`; on
1406    /// error the graph is left unchanged.
1407    ///
1408    /// Counterpart of `igraph_delete_edges`
1409    /// (`references/igraph/src/graph/type_indexededgelist.c:500`).
1410    ///
1411    /// # Examples
1412    ///
1413    /// ```
1414    /// use rust_igraph::Graph;
1415    ///
1416    /// let mut g = Graph::with_vertices(3);
1417    /// g.add_edge(0, 1).unwrap();
1418    /// g.add_edge(1, 2).unwrap();
1419    /// g.add_edge(0, 2).unwrap();
1420    /// g.delete_edges(&[1]).unwrap(); // remove edge 1-2
1421    /// assert_eq!(g.ecount(), 2);
1422    /// ```
1423    pub fn delete_edges(&mut self, edges: &[EdgeId]) -> IgraphResult<()> {
1424        let m = self.ecount();
1425        let m_u32 = u32::try_from(m).unwrap_or(u32::MAX);
1426
1427        // Validate up front so a bad id leaves graph state untouched.
1428        for &eid in edges {
1429            if (eid as usize) >= m {
1430                return Err(IgraphError::EdgeOutOfRange { id: eid, m: m_u32 });
1431            }
1432        }
1433        if edges.is_empty() {
1434            return Ok(());
1435        }
1436
1437        let mut remove = vec![false; m];
1438        for &eid in edges {
1439            remove[eid as usize] = true;
1440        }
1441
1442        let mut new_from: Vec<VertexId> = Vec::with_capacity(m);
1443        let mut new_to: Vec<VertexId> = Vec::with_capacity(m);
1444        for (e, &is_removed) in remove.iter().enumerate() {
1445            if !is_removed {
1446                new_from.push(self.from[e]);
1447                new_to.push(self.to[e]);
1448            }
1449        }
1450        // Filter edge attributes to match retained edges.
1451        for vals in self.edge_attrs.values_mut() {
1452            let mut new_vals = Vec::with_capacity(new_from.len());
1453            for (e, &is_removed) in remove.iter().enumerate() {
1454                if !is_removed {
1455                    new_vals.push(vals[e].clone());
1456                }
1457            }
1458            *vals = new_vals;
1459        }
1460        self.from = new_from;
1461        self.to = new_to;
1462        self.rebuild_indexes()?;
1463        self.cache.invalidate_all();
1464        Ok(())
1465    }
1466
1467    /// Remove the given vertices and all their incident edges.
1468    ///
1469    /// `vertices` may repeat ids freely. Surviving vertices get
1470    /// renumbered so the new id space is `0..new_vcount` in their
1471    /// previous relative order. Returns
1472    /// [`IgraphError::VertexOutOfRange`] if any id is `>= vcount()`;
1473    /// on error the graph is left unchanged.
1474    ///
1475    /// Counterpart of `igraph_delete_vertices`
1476    /// (`references/igraph/src/graph/type_indexededgelist.c:540`).
1477    ///
1478    /// # Examples
1479    ///
1480    /// ```
1481    /// use rust_igraph::Graph;
1482    ///
1483    /// let mut g = Graph::with_vertices(4);
1484    /// g.add_edge(0, 1).unwrap();
1485    /// g.add_edge(1, 2).unwrap();
1486    /// g.add_edge(2, 3).unwrap();
1487    /// g.delete_vertices(&[1]).unwrap();
1488    /// assert_eq!(g.vcount(), 3);
1489    /// assert_eq!(g.ecount(), 1); // only edge 2-3 survives (renumbered)
1490    /// ```
1491    pub fn delete_vertices(&mut self, vertices: &[VertexId]) -> IgraphResult<()> {
1492        self.delete_vertices_map(vertices).map(|_| ())
1493    }
1494
1495    /// Like [`delete_vertices`](Self::delete_vertices), but also returns
1496    /// the old↔new vertex id mappings.
1497    ///
1498    /// Returns `(map, invmap)` where:
1499    /// - `map[old_id] == Some(new_id)` if the vertex was retained, else
1500    ///   `None`. Length is the *original* vertex count.
1501    /// - `invmap[new_id] == old_id`. Length is the *new* vertex count.
1502    ///
1503    /// Counterpart of `igraph_delete_vertices_map`
1504    /// (`references/igraph/src/graph/type_indexededgelist.c:645`).
1505    ///
1506    /// # Examples
1507    ///
1508    /// ```
1509    /// use rust_igraph::Graph;
1510    ///
1511    /// let mut g = Graph::with_vertices(4);
1512    /// g.add_edge(0, 1).unwrap();
1513    /// g.add_edge(2, 3).unwrap();
1514    /// let (map, invmap) = g.delete_vertices_map(&[1, 2]).unwrap();
1515    /// assert_eq!(g.vcount(), 2);
1516    /// assert_eq!(map, vec![Some(0), None, None, Some(1)]);
1517    /// assert_eq!(invmap, vec![0, 3]);
1518    /// ```
1519    pub fn delete_vertices_map(
1520        &mut self,
1521        vertices: &[VertexId],
1522    ) -> IgraphResult<(Vec<Option<VertexId>>, Vec<VertexId>)> {
1523        let n_u32 = self.n;
1524        let n = n_u32 as usize;
1525
1526        // Validate first.
1527        for &vid in vertices {
1528            if vid >= n_u32 {
1529                return Err(IgraphError::VertexOutOfRange { id: vid, n: n_u32 });
1530            }
1531        }
1532
1533        let mut remove = vec![false; n];
1534        for &vid in vertices {
1535            remove[vid as usize] = true;
1536        }
1537
1538        // Build map (old → new) and invmap (new → old).
1539        let mut map: Vec<Option<VertexId>> = vec![None; n];
1540        let mut invmap: Vec<VertexId> = Vec::new();
1541        let mut next_new: u32 = 0;
1542        for (i, &is_removed) in remove.iter().enumerate() {
1543            if !is_removed {
1544                let i_u32 = u32::try_from(i)
1545                    .map_err(|_| IgraphError::Internal("vertex index exceeds u32::MAX"))?;
1546                map[i] = Some(next_new);
1547                invmap.push(i_u32);
1548                next_new = next_new
1549                    .checked_add(1)
1550                    .ok_or(IgraphError::Internal("new vertex count overflow"))?;
1551            }
1552        }
1553
1554        // Filter edges: keep only those with both endpoints retained,
1555        // renumber endpoints via `map`.
1556        let m = self.ecount();
1557        let mut new_from: Vec<VertexId> = Vec::with_capacity(m);
1558        let mut new_to: Vec<VertexId> = Vec::with_capacity(m);
1559        let mut edge_keep = Vec::with_capacity(m);
1560        for (u, v) in self.from.iter().zip(self.to.iter()) {
1561            if let (Some(nu), Some(nv)) = (map[*u as usize], map[*v as usize]) {
1562                new_from.push(nu);
1563                new_to.push(nv);
1564                edge_keep.push(true);
1565            } else {
1566                edge_keep.push(false);
1567            }
1568        }
1569
1570        // Filter vertex attributes to match retained vertices.
1571        for vals in self.vertex_attrs.values_mut() {
1572            let new_vals: Vec<AttributeValue> = remove
1573                .iter()
1574                .enumerate()
1575                .filter(|&(_, is_removed)| !is_removed)
1576                .map(|(i, _)| vals[i].clone())
1577                .collect();
1578            *vals = new_vals;
1579        }
1580        // Filter edge attributes to match retained edges.
1581        for vals in self.edge_attrs.values_mut() {
1582            let new_vals: Vec<AttributeValue> = edge_keep
1583                .iter()
1584                .enumerate()
1585                .filter(|&(_, keep)| *keep)
1586                .map(|(i, _)| vals[i].clone())
1587                .collect();
1588            *vals = new_vals;
1589        }
1590
1591        self.n = next_new;
1592        self.from = new_from;
1593        self.to = new_to;
1594        self.rebuild_indexes()?;
1595        self.cache.invalidate_all();
1596
1597        Ok((map, invmap))
1598    }
1599
1600    /// Look up a cached boolean property without computing it.
1601    ///
1602    /// Returns `None` if the property has not been cached yet. Pair with
1603    /// [`Self::cache_set`] in compute functions:
1604    ///
1605    /// ```ignore
1606    /// if let Some(v) = g.cache_get(CachedProperty::IsDag) { return v; }
1607    /// let v = compute_is_dag(g);
1608    /// g.cache_set(CachedProperty::IsDag, v);
1609    /// v
1610    /// ```
1611    ///
1612    /// Counterpart of `igraph_i_property_cache_has` + `_get_bool` from
1613    /// `references/igraph/src/graph/caching.c`.
1614    ///
1615    /// ```
1616    /// use rust_igraph::{Graph, CachedProperty};
1617    ///
1618    /// let g = Graph::with_vertices(3);
1619    /// assert!(g.cache_get(CachedProperty::HasLoop).is_none());
1620    /// g.cache_set(CachedProperty::HasLoop, true);
1621    /// assert_eq!(g.cache_get(CachedProperty::HasLoop), Some(true));
1622    /// ```
1623    #[must_use]
1624    pub fn cache_get(&self, prop: CachedProperty) -> Option<bool> {
1625        self.cache.get(prop)
1626    }
1627
1628    /// Store the value of a cached boolean property.
1629    ///
1630    /// Takes `&self` (interior mutability via `Cell`) — populating the
1631    /// cache from a compute function is **not** considered a mutation of
1632    /// the graph, matching igraph C semantics where compute helpers take
1633    /// `const igraph_t *` and still write to the cache.
1634    ///
1635    /// Counterpart of `igraph_i_property_cache_set_bool`.
1636    pub fn cache_set(&self, prop: CachedProperty, value: bool) {
1637        self.cache.set(prop, value);
1638    }
1639
1640    /// Drop the cached value of a single property (no-op if not cached).
1641    ///
1642    /// Use this if you change the graph via a private path that doesn't
1643    /// go through `add_edges` / `delete_*`.
1644    ///
1645    /// Counterpart of `igraph_i_property_cache_invalidate`.
1646    ///
1647    /// ```
1648    /// use rust_igraph::{Graph, CachedProperty};
1649    ///
1650    /// let g = Graph::with_vertices(3);
1651    /// g.cache_set(CachedProperty::HasLoop, true);
1652    /// g.cache_invalidate(CachedProperty::HasLoop);
1653    /// assert!(g.cache_get(CachedProperty::HasLoop).is_none());
1654    /// ```
1655    pub fn cache_invalidate(&self, prop: CachedProperty) {
1656        self.cache.invalidate(prop);
1657    }
1658
1659    /// Drop every cached boolean property.
1660    ///
1661    /// Counterpart of `igraph_i_property_cache_invalidate_all`.
1662    ///
1663    /// ```
1664    /// use rust_igraph::{Graph, CachedProperty};
1665    ///
1666    /// let g = Graph::with_vertices(3);
1667    /// g.cache_set(CachedProperty::HasLoop, true);
1668    /// g.cache_invalidate_all();
1669    /// assert!(g.cache_get(CachedProperty::HasLoop).is_none());
1670    /// ```
1671    pub fn cache_invalidate_all(&self) {
1672        self.cache.invalidate_all();
1673    }
1674
1675    fn check_vertex(&self, v: VertexId) -> IgraphResult<()> {
1676        if v >= self.n {
1677            return Err(IgraphError::VertexOutOfRange { id: v, n: self.n });
1678        }
1679        Ok(())
1680    }
1681
1682    fn check_edge(&self, eid: EdgeId) -> IgraphResult<()> {
1683        let m = self.ecount();
1684        let m_u32 = u32::try_from(m).unwrap_or(u32::MAX);
1685        if (eid as usize) >= m {
1686            return Err(IgraphError::EdgeOutOfRange { id: eid, m: m_u32 });
1687        }
1688        Ok(())
1689    }
1690
1691    /// Recompute `oi`, `ii`, `os`, `is` from `from`/`to`. Called after
1692    /// any structural change.
1693    ///
1694    /// Each side does a stable lexicographic sort: `oi` orders edges by
1695    /// `(from[e], to[e])`, `ii` by `(to[e], from[e])`. Time complexity
1696    /// is `O(|V| + |E| log |E|)` (Rust stable sort) — same asymptotic
1697    /// as upstream's `igraph_vector_int_pair_order`.
1698    ///
1699    /// The within-bucket secondary sort matches upstream igraph; without
1700    /// it, `neighbors(v)` for an unsorted-edge-input graph diverges from
1701    /// `python-igraph`'s output and breaks DFS order parity. (Counted
1702    /// for an oracle-test failure during ALGO-TR-002 — see
1703    /// `tests/oracle.rs::dfs_small_synthetic_matches_python_igraph`.)
1704    ///
1705    /// Counterpart of `igraph_i_create_start_vectors` + the
1706    /// `igraph_vector_int_pair_order` calls in
1707    /// `type_indexededgelist.c:309-336`.
1708    fn rebuild_indexes(&mut self) -> IgraphResult<()> {
1709        let m = self.ecount();
1710        let n = self.n as usize;
1711
1712        // Build (primary_key, secondary_key, edge_id) tuples for each
1713        // side, sort them lexicographically, then extract edge ids and
1714        // the offset array.
1715
1716        // ---- Out-side: sort by (from, to). ----
1717        let mut tuples: Vec<(VertexId, VertexId, u32)> = (0..m)
1718            .map(|e| {
1719                Ok::<_, IgraphError>((
1720                    self.from[e],
1721                    self.to[e],
1722                    u32::try_from(e)
1723                        .map_err(|_| IgraphError::Internal("edge id exceeds u32::MAX"))?,
1724                ))
1725            })
1726            .collect::<Result<_, _>>()?;
1727        tuples.sort_unstable_by_key(|a| (a.0, a.1));
1728        self.oi = tuples.iter().map(|t| t.2).collect();
1729        // os[v] = number of entries with primary_key < v.
1730        self.os = vec![0u32; n + 1];
1731        for &(u, _, _) in &tuples {
1732            self.os[u as usize + 1] = self.os[u as usize + 1]
1733                .checked_add(1)
1734                .ok_or(IgraphError::Internal("degree overflow in rebuild_indexes"))?;
1735        }
1736        for i in 1..=n {
1737            self.os[i] = self.os[i]
1738                .checked_add(self.os[i - 1])
1739                .ok_or(IgraphError::Internal("offset overflow in rebuild_indexes"))?;
1740        }
1741
1742        // ---- In-side: sort by (to, from). ----
1743        let mut tuples: Vec<(VertexId, VertexId, u32)> = (0..m)
1744            .map(|e| {
1745                Ok::<_, IgraphError>((
1746                    self.to[e],
1747                    self.from[e],
1748                    u32::try_from(e)
1749                        .map_err(|_| IgraphError::Internal("edge id exceeds u32::MAX"))?,
1750                ))
1751            })
1752            .collect::<Result<_, _>>()?;
1753        tuples.sort_unstable_by_key(|a| (a.0, a.1));
1754        self.ii = tuples.iter().map(|t| t.2).collect();
1755        self.is = vec![0u32; n + 1];
1756        for &(v, _, _) in &tuples {
1757            self.is[v as usize + 1] = self.is[v as usize + 1]
1758                .checked_add(1)
1759                .ok_or(IgraphError::Internal("degree overflow in rebuild_indexes"))?;
1760        }
1761        for i in 1..=n {
1762            self.is[i] = self.is[i]
1763                .checked_add(self.is[i - 1])
1764                .ok_or(IgraphError::Internal("offset overflow in rebuild_indexes"))?;
1765        }
1766
1767        Ok(())
1768    }
1769}
1770
1771// — Convenience methods delegating to free-function algorithms —
1772
1773impl Graph {
1774    /// Compute the density of this graph.
1775    ///
1776    /// Density is the ratio of actual edges to possible edges.
1777    /// Returns `None` for graphs with fewer than 2 vertices.
1778    ///
1779    /// # Examples
1780    ///
1781    /// ```
1782    /// use rust_igraph::Graph;
1783    ///
1784    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
1785    /// let d = g.density().unwrap().unwrap();
1786    /// assert!((d - 1.0).abs() < 1e-10); // K_3 is fully connected
1787    /// ```
1788    pub fn density(&self) -> IgraphResult<Option<f64>> {
1789        crate::algorithms::properties::basic::density(self)
1790    }
1791
1792    /// Check whether the graph is connected.
1793    ///
1794    /// For directed graphs this checks weak connectivity by default.
1795    ///
1796    /// # Examples
1797    ///
1798    /// ```
1799    /// use rust_igraph::Graph;
1800    ///
1801    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
1802    /// assert!(g.is_connected().unwrap());
1803    /// ```
1804    pub fn is_connected(&self) -> IgraphResult<bool> {
1805        crate::algorithms::connectivity::is_connected::is_connected(
1806            self,
1807            crate::algorithms::connectivity::is_connected::ConnectednessMode::Weak,
1808        )
1809    }
1810
1811    /// Check whether the graph is simple (no self-loops, no multi-edges).
1812    ///
1813    /// # Examples
1814    ///
1815    /// ```
1816    /// use rust_igraph::Graph;
1817    ///
1818    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
1819    /// assert!(g.is_simple().unwrap());
1820    /// ```
1821    pub fn is_simple(&self) -> IgraphResult<bool> {
1822        crate::algorithms::properties::is_simple::is_simple(self)
1823    }
1824
1825    /// Compute connected components.
1826    ///
1827    /// # Examples
1828    ///
1829    /// ```
1830    /// use rust_igraph::Graph;
1831    ///
1832    /// let mut g = Graph::new(4, false).unwrap();
1833    /// g.add_edge(0, 1).unwrap();
1834    /// g.add_edge(2, 3).unwrap();
1835    /// let cc = g.connected_components().unwrap();
1836    /// assert_eq!(cc.count, 2);
1837    /// ```
1838    pub fn connected_components(
1839        &self,
1840    ) -> IgraphResult<crate::algorithms::connectivity::components::ConnectedComponents> {
1841        crate::algorithms::connectivity::components::connected_components(self)
1842    }
1843
1844    /// Compute `PageRank` centrality for all vertices.
1845    ///
1846    /// Uses the default damping factor (0.85).
1847    ///
1848    /// # Examples
1849    ///
1850    /// ```
1851    /// use rust_igraph::Graph;
1852    ///
1853    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
1854    /// let pr = g.pagerank().unwrap();
1855    /// assert_eq!(pr.len(), 3);
1856    /// ```
1857    pub fn pagerank(&self) -> IgraphResult<Vec<f64>> {
1858        crate::algorithms::properties::pagerank::pagerank(self)
1859    }
1860
1861    /// Compute betweenness centrality for all vertices.
1862    ///
1863    /// # Examples
1864    ///
1865    /// ```
1866    /// use rust_igraph::Graph;
1867    ///
1868    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
1869    /// let bc = g.betweenness().unwrap();
1870    /// // Middle vertices have higher betweenness
1871    /// assert!(bc[1] > bc[0]);
1872    /// ```
1873    pub fn betweenness(&self) -> IgraphResult<Vec<f64>> {
1874        crate::algorithms::properties::betweenness::betweenness(self)
1875    }
1876
1877    /// Compute closeness centrality for all vertices.
1878    ///
1879    /// For each vertex, closeness is the reciprocal of the average shortest
1880    /// path distance to all reachable vertices. Returns `None` for isolated
1881    /// vertices.
1882    ///
1883    /// # Examples
1884    ///
1885    /// ```
1886    /// use rust_igraph::Graph;
1887    ///
1888    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
1889    /// let cl = g.closeness().unwrap();
1890    /// assert_eq!(cl.len(), 4);
1891    /// // Middle vertices have higher closeness
1892    /// assert!(cl[1].unwrap() > cl[0].unwrap());
1893    /// ```
1894    pub fn closeness(&self) -> IgraphResult<Vec<Option<f64>>> {
1895        crate::algorithms::properties::closeness::closeness(self)
1896    }
1897
1898    /// Compute eigenvector centrality for all vertices.
1899    ///
1900    /// # Examples
1901    ///
1902    /// ```
1903    /// use rust_igraph::Graph;
1904    ///
1905    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
1906    /// let ec = g.eigenvector_centrality().unwrap();
1907    /// assert_eq!(ec.len(), 3);
1908    /// ```
1909    pub fn eigenvector_centrality(&self) -> IgraphResult<Vec<f64>> {
1910        crate::algorithms::properties::eigenvector::eigenvector_centrality(self)
1911    }
1912
1913    /// Compute per-vertex local clustering coefficients.
1914    ///
1915    /// Returns the fraction of actual edges between each vertex's neighbours
1916    /// out of all possible edges. Vertices with fewer than 2 neighbours
1917    /// return `None`.
1918    ///
1919    /// # Examples
1920    ///
1921    /// ```
1922    /// use rust_igraph::Graph;
1923    ///
1924    /// // A triangle: all vertices have clustering coefficient 1.0
1925    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
1926    /// let cc = g.clustering_coefficients().unwrap();
1927    /// assert!((cc[0].unwrap() - 1.0).abs() < 1e-10);
1928    /// ```
1929    pub fn clustering_coefficients(&self) -> IgraphResult<Vec<Option<f64>>> {
1930        crate::algorithms::properties::triangles::transitivity_local_undirected(self)
1931    }
1932
1933    /// Compute the complement graph.
1934    ///
1935    /// The complement has the same vertices but edges wherever the original
1936    /// does not (excluding self-loops by default).
1937    ///
1938    /// # Examples
1939    ///
1940    /// ```
1941    /// use rust_igraph::Graph;
1942    ///
1943    /// let g = Graph::from_edges(&[(0,1)], false, Some(3)).unwrap();
1944    /// let c = g.complement().unwrap();
1945    /// // K_3 has 3 edges; original has 1; complement has 2
1946    /// assert_eq!(c.ecount(), 2);
1947    /// ```
1948    pub fn complement(&self) -> IgraphResult<Graph> {
1949        crate::algorithms::operators::complementer::complementer(self, false)
1950    }
1951
1952    /// Construct the line graph L(G).
1953    ///
1954    /// The line graph has one vertex per edge of this graph. Two vertices
1955    /// in L(G) are adjacent iff the corresponding edges share an endpoint.
1956    ///
1957    /// # Examples
1958    ///
1959    /// ```
1960    /// use rust_igraph::Graph;
1961    ///
1962    /// let mut g = Graph::with_vertices(3);
1963    /// g.add_edge(0, 1).unwrap();
1964    /// g.add_edge(1, 2).unwrap();
1965    /// g.add_edge(2, 0).unwrap();
1966    /// let lg = g.line_graph().unwrap();
1967    /// assert_eq!(lg.vcount(), 3);
1968    /// assert_eq!(lg.ecount(), 3);
1969    /// ```
1970    pub fn line_graph(&self) -> IgraphResult<Graph> {
1971        crate::algorithms::operators::line_graph::line_graph(self)
1972    }
1973
1974    /// Detect communities using the Louvain algorithm.
1975    ///
1976    /// # Examples
1977    ///
1978    /// ```
1979    /// use rust_igraph::Graph;
1980    ///
1981    /// let g = Graph::from_edges(
1982    ///     &[(0,1), (0,2), (1,2), (3,4), (3,5), (4,5), (2,3)],
1983    ///     false, None,
1984    /// ).unwrap();
1985    /// let result = g.louvain().unwrap();
1986    /// assert!(result.modularity > 0.0);
1987    /// ```
1988    pub fn louvain(&self) -> IgraphResult<crate::algorithms::community::louvain::LouvainResult> {
1989        crate::algorithms::community::louvain::louvain(self)
1990    }
1991
1992    /// Detect communities using the Leiden algorithm.
1993    ///
1994    /// Leiden improves upon Louvain by guaranteeing well-connected communities
1995    /// and avoiding the "poorly connected community" pathology.
1996    ///
1997    /// # Examples
1998    ///
1999    /// ```
2000    /// use rust_igraph::Graph;
2001    ///
2002    /// let g = Graph::from_edges(
2003    ///     &[(0,1), (0,2), (1,2), (3,4), (3,5), (4,5), (2,3)],
2004    ///     false, None,
2005    /// ).unwrap();
2006    /// let result = g.leiden().unwrap();
2007    /// assert!(result.quality > 0.0);
2008    /// ```
2009    pub fn leiden(&self) -> IgraphResult<crate::algorithms::community::leiden::LeidenResult> {
2010        crate::algorithms::community::leiden::leiden(self)
2011    }
2012
2013    /// Find all bridge edges (edges whose removal disconnects the graph).
2014    ///
2015    /// # Examples
2016    ///
2017    /// ```
2018    /// use rust_igraph::Graph;
2019    ///
2020    /// // 0-1-2 with 1-2 as bridge vs. 0-1, 0-2, 1-2 (triangle, no bridges)
2021    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2022    /// let br = g.bridges().unwrap();
2023    /// assert_eq!(br.len(), 2); // both edges are bridges in a path
2024    /// ```
2025    pub fn bridges(&self) -> IgraphResult<Vec<EdgeId>> {
2026        crate::algorithms::connectivity::bridges::bridges(self)
2027    }
2028
2029    /// Compute the k-core decomposition (coreness of each vertex).
2030    ///
2031    /// The coreness of a vertex is the largest `k` such that the vertex
2032    /// belongs to a k-core — a maximal subgraph where every vertex has
2033    /// degree at least `k`.
2034    ///
2035    /// # Examples
2036    ///
2037    /// ```
2038    /// use rust_igraph::Graph;
2039    ///
2040    /// // Triangle (0,1,2) plus pendant vertex 3 attached to 0
2041    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0), (0,3)], false, None).unwrap();
2042    /// let cores = g.coreness().unwrap();
2043    /// assert_eq!(cores[0], 2); // part of the triangle
2044    /// assert_eq!(cores[3], 1); // pendant
2045    /// ```
2046    pub fn coreness(&self) -> IgraphResult<Vec<u32>> {
2047        crate::algorithms::properties::coreness::coreness(self)
2048    }
2049
2050    /// Create the induced subgraph on the given vertex set.
2051    ///
2052    /// # Examples
2053    ///
2054    /// ```
2055    /// use rust_igraph::Graph;
2056    ///
2057    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3), (3,0)], false, None).unwrap();
2058    /// let sub = g.induced_subgraph(&[0, 1, 2]).unwrap();
2059    /// assert_eq!(sub.graph.vcount(), 3);
2060    /// assert_eq!(sub.graph.ecount(), 2); // edges 0-1 and 1-2
2061    /// ```
2062    pub fn induced_subgraph(
2063        &self,
2064        vertices: &[VertexId],
2065    ) -> IgraphResult<crate::algorithms::operators::induced_subgraph::InducedSubgraphResult> {
2066        crate::algorithms::operators::induced_subgraph::induced_subgraph(self, vertices)
2067    }
2068
2069    /// Export the graph in DOT (Graphviz) format as a string.
2070    ///
2071    /// # Examples
2072    ///
2073    /// ```
2074    /// use rust_igraph::Graph;
2075    ///
2076    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2077    /// let dot = g.to_dot(None).unwrap();
2078    /// assert!(dot.contains("--"));
2079    /// ```
2080    pub fn to_dot(&self, labels: Option<&[String]>) -> IgraphResult<String> {
2081        let mut buf = Vec::new();
2082        crate::algorithms::io::dot::write_dot(self, labels, &mut buf)?;
2083        String::from_utf8(buf).map_err(|e| {
2084            IgraphError::InvalidArgument(format!("DOT output is not valid UTF-8: {e}"))
2085        })
2086    }
2087
2088    /// BFS traversal from a root vertex, returning visit order.
2089    ///
2090    /// # Examples
2091    ///
2092    /// ```
2093    /// use rust_igraph::Graph;
2094    ///
2095    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,3)], false, None).unwrap();
2096    /// let order = g.bfs(0).unwrap();
2097    /// assert_eq!(order[0], 0);
2098    /// assert_eq!(order.len(), 4);
2099    /// ```
2100    pub fn bfs(&self, root: VertexId) -> IgraphResult<Vec<VertexId>> {
2101        crate::algorithms::traversal::bfs::bfs(self, root)
2102    }
2103
2104    /// DFS traversal from a root vertex, returning visit order.
2105    ///
2106    /// # Examples
2107    ///
2108    /// ```
2109    /// use rust_igraph::Graph;
2110    ///
2111    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,3)], false, None).unwrap();
2112    /// let order = g.dfs(0).unwrap();
2113    /// assert_eq!(order[0], 0);
2114    /// assert_eq!(order.len(), 4);
2115    /// ```
2116    pub fn dfs(&self, root: VertexId) -> IgraphResult<Vec<VertexId>> {
2117        crate::algorithms::traversal::dfs::dfs(self, root)
2118    }
2119
2120    /// Unweighted shortest paths from a source to all reachable vertices.
2121    ///
2122    /// Returns a vector of paths (each path is a `Vec<VertexId>`).
2123    ///
2124    /// # Examples
2125    ///
2126    /// ```
2127    /// use rust_igraph::Graph;
2128    ///
2129    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
2130    /// let paths = g.shortest_paths(0).unwrap();
2131    /// assert_eq!(paths[3], vec![0, 1, 2, 3]);
2132    /// ```
2133    pub fn shortest_paths(&self, source: VertexId) -> IgraphResult<Vec<Vec<VertexId>>> {
2134        crate::algorithms::paths::shortest_paths::get_shortest_paths(self, source)
2135    }
2136
2137    /// Weighted shortest-path distances from a source (Dijkstra).
2138    ///
2139    /// Returns distances to all vertices; `None` for unreachable vertices.
2140    ///
2141    /// # Examples
2142    ///
2143    /// ```
2144    /// use rust_igraph::Graph;
2145    ///
2146    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
2147    /// let weights = vec![1.0, 2.0, 3.0];
2148    /// let dist = g.dijkstra(0, &weights).unwrap();
2149    /// assert!((dist[3].unwrap() - 6.0).abs() < 1e-10);
2150    /// ```
2151    pub fn dijkstra(&self, source: VertexId, weights: &[f64]) -> IgraphResult<Vec<Option<f64>>> {
2152        crate::algorithms::paths::dijkstra::dijkstra_distances(self, source, weights)
2153    }
2154
2155    /// Compute the degree sequence (degree of each vertex).
2156    ///
2157    /// # Examples
2158    ///
2159    /// ```
2160    /// use rust_igraph::Graph;
2161    ///
2162    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
2163    /// let seq = g.degree_sequence().unwrap();
2164    /// assert_eq!(seq, vec![2, 2, 2]);
2165    /// ```
2166    pub fn degree_sequence(&self) -> IgraphResult<Vec<u32>> {
2167        crate::algorithms::properties::degree::degree_sequence(
2168            self,
2169            crate::algorithms::properties::degree::DegreeMode::All,
2170        )
2171    }
2172
2173    /// Compute the graph diameter (longest shortest path).
2174    ///
2175    /// Returns `None` for graphs with zero vertices.
2176    ///
2177    /// # Examples
2178    ///
2179    /// ```
2180    /// use rust_igraph::Graph;
2181    ///
2182    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
2183    /// assert_eq!(g.diameter().unwrap(), Some(3));
2184    /// ```
2185    pub fn diameter(&self) -> IgraphResult<Option<u32>> {
2186        crate::algorithms::paths::radii::diameter(self)
2187    }
2188
2189    /// Compute the global transitivity (clustering coefficient).
2190    ///
2191    /// Returns `None` if there are no connected triples.
2192    ///
2193    /// # Examples
2194    ///
2195    /// ```
2196    /// use rust_igraph::Graph;
2197    ///
2198    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
2199    /// let t = g.transitivity().unwrap().unwrap();
2200    /// assert!((t - 1.0).abs() < 1e-10); // triangle is fully transitive
2201    /// ```
2202    pub fn transitivity(&self) -> IgraphResult<Option<f64>> {
2203        crate::algorithms::properties::triangles::transitivity_undirected(self)
2204    }
2205
2206    /// Compute the clique number (size of the largest clique).
2207    ///
2208    /// # Examples
2209    ///
2210    /// ```
2211    /// use rust_igraph::Graph;
2212    ///
2213    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0), (2,3)], false, None).unwrap();
2214    /// assert_eq!(g.clique_number().unwrap(), 3);
2215    /// ```
2216    pub fn clique_number(&self) -> IgraphResult<u32> {
2217        crate::algorithms::cliques::clique_number(self)
2218    }
2219
2220    /// Find all largest cliques (cliques of maximum size).
2221    ///
2222    /// # Examples
2223    ///
2224    /// ```
2225    /// use rust_igraph::Graph;
2226    ///
2227    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2), (2,3)], false, None).unwrap();
2228    /// let lc = g.largest_cliques().unwrap();
2229    /// assert_eq!(lc.len(), 1);
2230    /// assert_eq!(lc[0].len(), 3);
2231    /// ```
2232    pub fn largest_cliques(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
2233        crate::algorithms::cliques::largest_cliques(self)
2234    }
2235
2236    /// Count maximal cliques without enumerating them.
2237    ///
2238    /// # Examples
2239    ///
2240    /// ```
2241    /// use rust_igraph::Graph;
2242    ///
2243    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2), (2,3)], false, None).unwrap();
2244    /// assert!(g.maximal_cliques_count().unwrap() >= 2);
2245    /// ```
2246    pub fn maximal_cliques_count(&self) -> IgraphResult<u64> {
2247        crate::algorithms::cliques::maximal_cliques_count(self)
2248    }
2249
2250    /// Histogram of clique sizes in the graph.
2251    ///
2252    /// Returns a vector where entry `i` is the number of cliques of size `i`.
2253    ///
2254    /// # Examples
2255    ///
2256    /// ```
2257    /// use rust_igraph::Graph;
2258    ///
2259    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2260    /// let hist = g.clique_size_hist().unwrap();
2261    /// assert!(hist.len() >= 3);
2262    /// ```
2263    pub fn clique_size_hist(&self) -> IgraphResult<Vec<u64>> {
2264        crate::algorithms::cliques::clique_size_hist(self)
2265    }
2266
2267    /// Average local efficiency of the graph.
2268    ///
2269    /// # Examples
2270    ///
2271    /// ```
2272    /// use rust_igraph::Graph;
2273    ///
2274    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2275    /// let eff = g.average_local_efficiency().unwrap();
2276    /// assert!(eff > 0.0);
2277    /// ```
2278    pub fn average_local_efficiency(&self) -> IgraphResult<f64> {
2279        crate::algorithms::properties::efficiency::average_local_efficiency(self)
2280    }
2281
2282    /// Count mutual (reciprocal) edges in a directed graph.
2283    ///
2284    /// # Examples
2285    ///
2286    /// ```
2287    /// use rust_igraph::Graph;
2288    ///
2289    /// let g = Graph::from_edges(&[(0,1), (1,0), (1,2)], true, None).unwrap();
2290    /// let m = g.count_mutual().unwrap();
2291    /// assert_eq!(m, 1);
2292    /// ```
2293    pub fn count_mutual(&self) -> IgraphResult<usize> {
2294        crate::algorithms::properties::mutual::count_mutual(self, true)
2295    }
2296
2297    /// Find all maximal independent vertex sets.
2298    ///
2299    /// # Examples
2300    ///
2301    /// ```
2302    /// use rust_igraph::Graph;
2303    ///
2304    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2305    /// let sets = g.maximal_independent_vertex_sets().unwrap();
2306    /// assert!(!sets.is_empty());
2307    /// ```
2308    pub fn maximal_independent_vertex_sets(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
2309        crate::algorithms::cliques::maximal_independent_vertex_sets(self)
2310    }
2311
2312    /// Find all largest independent vertex sets.
2313    ///
2314    /// # Examples
2315    ///
2316    /// ```
2317    /// use rust_igraph::Graph;
2318    ///
2319    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2320    /// let sets = g.largest_independent_vertex_sets().unwrap();
2321    /// assert!(sets.iter().all(|s| s.len() == 2));
2322    /// ```
2323    pub fn largest_independent_vertex_sets(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
2324        crate::algorithms::cliques::largest_independent_vertex_sets(self)
2325    }
2326
2327    /// Greedy edge coloring.
2328    ///
2329    /// # Examples
2330    ///
2331    /// ```
2332    /// use rust_igraph::Graph;
2333    ///
2334    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2335    /// let colors = g.edge_coloring_greedy().unwrap();
2336    /// assert_eq!(colors.len(), 3);
2337    /// ```
2338    pub fn edge_coloring_greedy(&self) -> IgraphResult<Vec<u32>> {
2339        crate::algorithms::coloring::edge_coloring_greedy(self)
2340    }
2341
2342    /// Upper bound on the chromatic number.
2343    ///
2344    /// # Examples
2345    ///
2346    /// ```
2347    /// use rust_igraph::Graph;
2348    ///
2349    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2350    /// assert!(g.chromatic_number_upper_bound().unwrap() >= 3);
2351    /// ```
2352    pub fn chromatic_number_upper_bound(&self) -> IgraphResult<u32> {
2353        crate::algorithms::coloring::chromatic_number_upper_bound(self)
2354    }
2355
2356    /// Test whether the graph is perfect (Strong Perfect Graph Theorem).
2357    ///
2358    /// # Examples
2359    ///
2360    /// ```
2361    /// use rust_igraph::Graph;
2362    ///
2363    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2364    /// assert!(g.is_perfect().unwrap());
2365    /// ```
2366    pub fn is_perfect(&self) -> IgraphResult<bool> {
2367        crate::algorithms::properties::perfect::is_perfect(self)
2368    }
2369
2370    /// Average local transitivity (clustering coefficient).
2371    ///
2372    /// Vertices with degree < 2 are treated as having transitivity 0.
2373    ///
2374    /// # Examples
2375    ///
2376    /// ```
2377    /// use rust_igraph::Graph;
2378    ///
2379    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2380    /// let avg = g.transitivity_avglocal().unwrap();
2381    /// assert!(avg > 0.9);
2382    /// ```
2383    pub fn transitivity_avglocal(&self) -> IgraphResult<f64> {
2384        crate::algorithms::properties::triangles::transitivity_avglocal_undirected(
2385            self,
2386            crate::algorithms::properties::triangles::TransitivityMode::Zero,
2387        )
2388    }
2389
2390    /// Mean degree of all vertices.
2391    ///
2392    /// # Examples
2393    ///
2394    /// ```
2395    /// use rust_igraph::Graph;
2396    ///
2397    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2398    /// let md = g.mean_degree().unwrap();
2399    /// assert!((md.unwrap() - 2.0).abs() < 1e-10);
2400    /// ```
2401    pub fn mean_degree(&self) -> IgraphResult<Option<f64>> {
2402        crate::algorithms::properties::basic::mean_degree(self, true)
2403    }
2404
2405    /// Graph degeneracy (maximum k for which a k-core exists).
2406    ///
2407    /// # Examples
2408    ///
2409    /// ```
2410    /// use rust_igraph::Graph;
2411    ///
2412    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2413    /// assert_eq!(g.degeneracy().unwrap(), 2);
2414    /// ```
2415    pub fn degeneracy(&self) -> IgraphResult<u32> {
2416        crate::algorithms::properties::is_k_degenerate::degeneracy(self)
2417    }
2418
2419    /// Convergence degree of each edge.
2420    ///
2421    /// # Examples
2422    ///
2423    /// ```
2424    /// use rust_igraph::Graph;
2425    ///
2426    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2427    /// let cd = g.convergence_degree().unwrap();
2428    /// assert_eq!(cd.len(), g.ecount());
2429    /// ```
2430    pub fn convergence_degree(&self) -> IgraphResult<Vec<f64>> {
2431        crate::algorithms::properties::convergence_degree::convergence_degree(self)
2432    }
2433
2434    /// Count self-loops in the graph.
2435    ///
2436    /// # Examples
2437    ///
2438    /// ```
2439    /// use rust_igraph::Graph;
2440    ///
2441    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2442    /// assert_eq!(g.count_loops().unwrap(), 0);
2443    /// ```
2444    pub fn count_loops(&self) -> IgraphResult<usize> {
2445        crate::algorithms::properties::multiplicity::count_loops(self)
2446    }
2447
2448    /// Average nearest neighbor degree for each vertex.
2449    ///
2450    /// # Examples
2451    ///
2452    /// ```
2453    /// use rust_igraph::Graph;
2454    ///
2455    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2456    /// let knn = g.avg_nearest_neighbor_degree().unwrap();
2457    /// assert_eq!(knn.len(), 3);
2458    /// ```
2459    pub fn avg_nearest_neighbor_degree(&self) -> IgraphResult<Vec<Option<f64>>> {
2460        crate::algorithms::properties::knn::avg_nearest_neighbor_degree(self)
2461    }
2462
2463    /// Bibliographic coupling scores between all vertex pairs.
2464    ///
2465    /// Returns a flat n*n matrix where entry `[i*n + j]` is the coupling
2466    /// score between vertices `i` and `j`.
2467    ///
2468    /// # Examples
2469    ///
2470    /// ```
2471    /// use rust_igraph::Graph;
2472    ///
2473    /// let g = Graph::from_edges(&[(0,2), (1,2)], true, None).unwrap();
2474    /// let n = g.vcount() as usize;
2475    /// let bc = g.bibcoupling().unwrap();
2476    /// assert_eq!(bc.len(), n * n);
2477    /// ```
2478    pub fn bibcoupling(&self) -> IgraphResult<Vec<u32>> {
2479        crate::algorithms::properties::similarity::bibcoupling(self)
2480    }
2481
2482    /// Biconnected components of the graph.
2483    ///
2484    /// # Examples
2485    ///
2486    /// ```
2487    /// use rust_igraph::Graph;
2488    ///
2489    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0), (2,3)], false, None).unwrap();
2490    /// let bc = g.biconnected_components().unwrap();
2491    /// assert_eq!(bc.components.len(), 2);
2492    /// ```
2493    pub fn biconnected_components(
2494        &self,
2495    ) -> IgraphResult<crate::algorithms::connectivity::biconnected::BiconnectedComponents> {
2496        crate::algorithms::connectivity::biconnected::biconnected_components(self)
2497    }
2498
2499    /// Find all minimum-size vertex separators.
2500    ///
2501    /// # Examples
2502    ///
2503    /// ```
2504    /// use rust_igraph::Graph;
2505    ///
2506    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,3), (2,3)], false, None).unwrap();
2507    /// let seps = g.minimum_size_separators().unwrap();
2508    /// assert!(!seps.is_empty());
2509    /// ```
2510    pub fn minimum_size_separators(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
2511        crate::algorithms::connectivity::separators::minimum_size_separators(self)
2512    }
2513
2514    /// Find all minimal s-t separators.
2515    ///
2516    /// # Examples
2517    ///
2518    /// ```
2519    /// use rust_igraph::Graph;
2520    ///
2521    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,3), (2,3)], false, None).unwrap();
2522    /// let seps = g.all_minimal_st_separators().unwrap();
2523    /// assert!(!seps.is_empty());
2524    /// ```
2525    pub fn all_minimal_st_separators(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
2526        crate::algorithms::connectivity::separators::all_minimal_st_separators(self)
2527    }
2528
2529    /// Graph adhesion (minimum edge connectivity over all pairs).
2530    ///
2531    /// # Examples
2532    ///
2533    /// ```
2534    /// use rust_igraph::Graph;
2535    ///
2536    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
2537    /// assert_eq!(g.adhesion().unwrap(), 2);
2538    /// ```
2539    pub fn adhesion(&self) -> IgraphResult<i64> {
2540        crate::algorithms::flow::edge_connectivity::adhesion(self, true)
2541    }
2542
2543    /// Graph cohesion (minimum vertex connectivity over all pairs).
2544    ///
2545    /// # Examples
2546    ///
2547    /// ```
2548    /// use rust_igraph::Graph;
2549    ///
2550    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
2551    /// assert_eq!(g.cohesion().unwrap(), 2);
2552    /// ```
2553    pub fn cohesion(&self) -> IgraphResult<i64> {
2554        crate::algorithms::flow::vertex_connectivity::cohesion(self, true)
2555    }
2556
2557    /// BFS tree rooted at `root`.
2558    ///
2559    /// # Examples
2560    ///
2561    /// ```
2562    /// use rust_igraph::Graph;
2563    ///
2564    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2565    /// let tree = g.bfs_tree(0).unwrap();
2566    /// assert_eq!(tree.order.len(), 3);
2567    /// ```
2568    pub fn bfs_tree(
2569        &self,
2570        root: VertexId,
2571    ) -> IgraphResult<crate::algorithms::traversal::bfs::BfsTree> {
2572        crate::algorithms::traversal::bfs::bfs_tree(self, root)
2573    }
2574
2575    /// DFS tree rooted at `root`.
2576    ///
2577    /// # Examples
2578    ///
2579    /// ```
2580    /// use rust_igraph::Graph;
2581    ///
2582    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
2583    /// let tree = g.dfs_tree(0).unwrap();
2584    /// assert_eq!(tree.order.len(), 3);
2585    /// ```
2586    pub fn dfs_tree(
2587        &self,
2588        root: VertexId,
2589    ) -> IgraphResult<crate::algorithms::traversal::dfs::DfsTree> {
2590        crate::algorithms::traversal::dfs::dfs_tree(self, root)
2591    }
2592
2593    /// Find all articulation points (cut vertices).
2594    ///
2595    /// # Examples
2596    ///
2597    /// ```
2598    /// use rust_igraph::Graph;
2599    ///
2600    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3), (1,3)], false, None).unwrap();
2601    /// let ap = g.articulation_points().unwrap();
2602    /// assert_eq!(ap, vec![1]); // vertex 1 is the only cut vertex
2603    /// ```
2604    pub fn articulation_points(&self) -> IgraphResult<Vec<VertexId>> {
2605        crate::algorithms::connectivity::articulation::articulation_points(self)
2606    }
2607
2608    /// Topological sort (DAG only, returns error for cyclic graphs).
2609    ///
2610    /// # Examples
2611    ///
2612    /// ```
2613    /// use rust_igraph::Graph;
2614    ///
2615    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], true, None).unwrap();
2616    /// let order = g.topological_sort().unwrap();
2617    /// assert_eq!(order[0], 0); // source comes first
2618    /// ```
2619    pub fn topological_sort(&self) -> IgraphResult<Vec<VertexId>> {
2620        crate::algorithms::properties::topological_sorting::topological_sorting(
2621            self,
2622            crate::algorithms::paths::dijkstra::DijkstraMode::Out,
2623        )
2624    }
2625
2626    /// Compute a minimum spanning tree (unweighted).
2627    ///
2628    /// Returns the edge ids forming the MST.
2629    ///
2630    /// # Examples
2631    ///
2632    /// ```
2633    /// use rust_igraph::Graph;
2634    ///
2635    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0), (2,3)], false, None).unwrap();
2636    /// let mst_edges = g.minimum_spanning_tree().unwrap();
2637    /// assert_eq!(mst_edges.len(), 3); // n-1 edges for connected graph
2638    /// ```
2639    pub fn minimum_spanning_tree(&self) -> IgraphResult<Vec<EdgeId>> {
2640        crate::algorithms::spanning::mst::minimum_spanning_tree(
2641            self,
2642            None,
2643            crate::algorithms::spanning::mst::MstAlgorithm::Automatic,
2644        )
2645    }
2646
2647    /// Compute a quick structural summary of the graph.
2648    ///
2649    /// # Examples
2650    ///
2651    /// ```
2652    /// use rust_igraph::Graph;
2653    ///
2654    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
2655    /// let s = g.summary().unwrap();
2656    /// assert_eq!(s.vcount, 3);
2657    /// assert!(s.connected);
2658    /// ```
2659    pub fn summary(&self) -> IgraphResult<crate::algorithms::properties::summary::GraphSummary> {
2660        crate::algorithms::properties::summary::graph_summary(self)
2661    }
2662
2663    /// Compute the maximum flow value between two vertices.
2664    ///
2665    /// # Examples
2666    ///
2667    /// ```
2668    /// use rust_igraph::Graph;
2669    ///
2670    /// let g = Graph::from_edges(
2671    ///     &[(0,1), (0,2), (1,3), (2,3)], true, None
2672    /// ).unwrap();
2673    /// let flow = g.max_flow(0, 3).unwrap();
2674    /// assert!((flow - 2.0).abs() < 1e-10);
2675    /// ```
2676    pub fn max_flow(&self, source: VertexId, target: VertexId) -> IgraphResult<f64> {
2677        crate::algorithms::flow::max_flow::max_flow_value(self, source, target, None)
2678    }
2679
2680    /// Decompose the graph into its connected components as separate graphs.
2681    ///
2682    /// # Examples
2683    ///
2684    /// ```
2685    /// use rust_igraph::Graph;
2686    ///
2687    /// let g = Graph::from_edges(&[(0,1), (2,3)], false, None).unwrap();
2688    /// let components = g.decompose().unwrap();
2689    /// assert_eq!(components.len(), 2);
2690    /// ```
2691    pub fn decompose(&self) -> IgraphResult<Vec<Graph>> {
2692        crate::algorithms::connectivity::decompose::decompose(self)
2693    }
2694
2695    /// Check whether the graph is biconnected.
2696    ///
2697    /// # Examples
2698    ///
2699    /// ```
2700    /// use rust_igraph::Graph;
2701    ///
2702    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
2703    /// assert!(g.is_biconnected().unwrap());
2704    /// ```
2705    pub fn is_biconnected(&self) -> IgraphResult<bool> {
2706        crate::algorithms::connectivity::is_biconnected::is_biconnected(self)
2707    }
2708
2709    /// Run label propagation community detection.
2710    ///
2711    /// # Examples
2712    ///
2713    /// ```
2714    /// use rust_igraph::Graph;
2715    ///
2716    /// let g = Graph::from_edges(
2717    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3)], false, None
2718    /// ).unwrap();
2719    /// let result = g.label_propagation().unwrap();
2720    /// assert!(result.membership.len() == 6);
2721    /// ```
2722    pub fn label_propagation(
2723        &self,
2724    ) -> IgraphResult<crate::algorithms::community::label_propagation::LpaResult> {
2725        crate::algorithms::community::label_propagation::label_propagation(self)
2726    }
2727
2728    /// Run Walktrap community detection.
2729    ///
2730    /// # Examples
2731    ///
2732    /// ```
2733    /// use rust_igraph::Graph;
2734    ///
2735    /// let g = Graph::from_edges(
2736    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)], false, None
2737    /// ).unwrap();
2738    /// let result = g.walktrap().unwrap();
2739    /// assert!(result.membership.len() == 6);
2740    /// ```
2741    pub fn walktrap(&self) -> IgraphResult<crate::algorithms::community::walktrap::WalktrapResult> {
2742        crate::algorithms::community::walktrap::walktrap(self)
2743    }
2744
2745    /// Run fast greedy modularity community detection.
2746    ///
2747    /// # Examples
2748    ///
2749    /// ```
2750    /// use rust_igraph::Graph;
2751    ///
2752    /// let g = Graph::from_edges(
2753    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)], false, None
2754    /// ).unwrap();
2755    /// let result = g.fast_greedy().unwrap();
2756    /// assert!(result.membership.len() == 6);
2757    /// ```
2758    pub fn fast_greedy(
2759        &self,
2760    ) -> IgraphResult<crate::algorithms::community::fast_greedy_modularity::FastGreedyResult> {
2761        crate::algorithms::community::fast_greedy_modularity::fast_greedy_modularity(self)
2762    }
2763
2764    /// Compute hub and authority scores (HITS algorithm).
2765    ///
2766    /// # Examples
2767    ///
2768    /// ```
2769    /// use rust_igraph::Graph;
2770    ///
2771    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], true, None).unwrap();
2772    /// let hits = g.hits().unwrap();
2773    /// assert_eq!(hits.hub.len(), 3);
2774    /// assert_eq!(hits.authority.len(), 3);
2775    /// ```
2776    pub fn hits(&self) -> IgraphResult<crate::algorithms::properties::hits::HitsScores> {
2777        crate::algorithms::properties::hits::hub_and_authority_scores(self)
2778    }
2779
2780    /// Compute Katz centrality for all vertices.
2781    ///
2782    /// # Examples
2783    ///
2784    /// ```
2785    /// use rust_igraph::Graph;
2786    ///
2787    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
2788    /// let katz = g.katz_centrality(0.1, 1.0).unwrap();
2789    /// assert_eq!(katz.len(), 3);
2790    /// ```
2791    pub fn katz_centrality(&self, alpha: f64, beta: f64) -> IgraphResult<Vec<f64>> {
2792        crate::algorithms::properties::katz_centrality::katz_centrality(
2793            self, alpha, beta, None, None,
2794        )
2795    }
2796
2797    /// Compute degree assortativity of the graph.
2798    ///
2799    /// # Examples
2800    ///
2801    /// ```
2802    /// use rust_igraph::Graph;
2803    ///
2804    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
2805    /// let a = g.assortativity().unwrap();
2806    /// assert!(a.is_some());
2807    /// ```
2808    pub fn assortativity(&self) -> IgraphResult<Option<f64>> {
2809        crate::algorithms::properties::assortativity::assortativity_degree(self)
2810    }
2811
2812    /// Read a graph from a file, auto-detecting the format from the extension.
2813    ///
2814    /// Supported extensions: `.gml`, `.graphml`, `.dot`, `.net` (Pajek),
2815    /// `.ncol`, `.lgl`, `.leda`, `.dl`, `.edges`/`.edgelist`/`.txt`.
2816    ///
2817    /// # Examples
2818    ///
2819    /// ```no_run
2820    /// use rust_igraph::Graph;
2821    ///
2822    /// let g = Graph::from_file("network.gml").unwrap();
2823    /// println!("{}", g.vcount());
2824    /// ```
2825    pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
2826        let p = path.as_ref();
2827        let ext = p
2828            .extension()
2829            .and_then(|e| e.to_str())
2830            .unwrap_or("")
2831            .to_ascii_lowercase();
2832        match ext.as_str() {
2833            "gml" => Self::from_gml_file(p),
2834            "graphml" | "xml" => Self::from_graphml_file(p),
2835            "dot" | "gv" => Self::from_dot_file(p),
2836            "net" | "pajek" => Self::from_pajek_file(p),
2837            "ncol" => Self::from_ncol_file(p),
2838            "lgl" => Self::from_lgl_file(p),
2839            "leda" | "lgr" => Self::from_leda_file(p),
2840            "dl" => Self::from_dl_file(p),
2841            "edges" | "edgelist" | "txt" | "csv" => Self::from_edgelist_file(p),
2842            _ => Err(IgraphError::InvalidArgument(format!(
2843                "cannot detect graph format from extension \".{ext}\"; \
2844                 use a format-specific method like from_gml_file()"
2845            ))),
2846        }
2847    }
2848
2849    /// Write a graph to a file, auto-detecting the format from the extension.
2850    ///
2851    /// Supported extensions: `.gml`, `.graphml`, `.dot`, `.net` (Pajek),
2852    /// `.ncol`, `.lgl`, `.leda`, `.dl`, `.edges`/`.edgelist`/`.txt`.
2853    ///
2854    /// # Examples
2855    ///
2856    /// ```no_run
2857    /// use rust_igraph::Graph;
2858    ///
2859    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
2860    /// g.to_file("output.gml").unwrap();
2861    /// ```
2862    pub fn to_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
2863        let p = path.as_ref();
2864        let ext = p
2865            .extension()
2866            .and_then(|e| e.to_str())
2867            .unwrap_or("")
2868            .to_ascii_lowercase();
2869        match ext.as_str() {
2870            "gml" => self.to_gml_file(p),
2871            "graphml" | "xml" => self.to_graphml_file(p),
2872            "dot" | "gv" => self.to_dot_file(p),
2873            "net" | "pajek" => self.to_pajek_file(p),
2874            "ncol" => self.to_ncol_file(p),
2875            "lgl" => self.to_lgl_file(p),
2876            "leda" | "lgr" => self.to_leda_file(p),
2877            "dl" => self.to_dl_file(p),
2878            "edges" | "edgelist" | "txt" | "csv" => self.to_edgelist_file(p),
2879            _ => Err(IgraphError::InvalidArgument(format!(
2880                "cannot detect graph format from extension \".{ext}\"; \
2881                 use a format-specific method like to_gml_file()"
2882            ))),
2883        }
2884    }
2885
2886    /// Read a graph from an edge list file.
2887    ///
2888    /// Each line should contain two space-separated vertex ids.
2889    ///
2890    /// # Examples
2891    ///
2892    /// ```no_run
2893    /// use rust_igraph::Graph;
2894    ///
2895    /// let g = Graph::from_edgelist_file("my_graph.edges").unwrap();
2896    /// println!("{}", g.vcount());
2897    /// ```
2898    pub fn from_edgelist_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
2899        let file = std::fs::File::open(path.as_ref())
2900            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
2901        crate::algorithms::io::edgelist::read_edgelist(std::io::BufReader::new(file))
2902    }
2903
2904    /// Write the graph to a file in edge list format.
2905    ///
2906    /// # Examples
2907    ///
2908    /// ```no_run
2909    /// use rust_igraph::Graph;
2910    ///
2911    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2912    /// g.to_edgelist_file("output.edges").unwrap();
2913    /// ```
2914    pub fn to_edgelist_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
2915        let mut file = std::fs::File::create(path.as_ref())
2916            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
2917        crate::algorithms::io::edgelist::write_edgelist(self, &mut file)
2918    }
2919
2920    /// Read a graph from a GML file.
2921    ///
2922    /// # Examples
2923    ///
2924    /// ```no_run
2925    /// use rust_igraph::Graph;
2926    ///
2927    /// let g = Graph::from_gml_file("network.gml").unwrap();
2928    /// ```
2929    pub fn from_gml_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
2930        let file = std::fs::File::open(path.as_ref())
2931            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
2932        crate::algorithms::io::gml::read_gml(std::io::BufReader::new(file))
2933    }
2934
2935    /// Write the graph to a file in GML format.
2936    ///
2937    /// # Examples
2938    ///
2939    /// ```no_run
2940    /// use rust_igraph::Graph;
2941    ///
2942    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2943    /// g.to_gml_file("output.gml").unwrap();
2944    /// ```
2945    pub fn to_gml_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
2946        let mut file = std::fs::File::create(path.as_ref())
2947            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
2948        crate::algorithms::io::gml::write_gml(self, &mut file)
2949    }
2950
2951    /// Read a graph from a `GraphML` file.
2952    ///
2953    /// # Examples
2954    ///
2955    /// ```no_run
2956    /// use rust_igraph::Graph;
2957    ///
2958    /// let g = Graph::from_graphml_file("network.graphml").unwrap();
2959    /// ```
2960    pub fn from_graphml_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
2961        let file = std::fs::File::open(path.as_ref())
2962            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
2963        let result = crate::algorithms::io::graphml::read_graphml(std::io::BufReader::new(file))?;
2964        Ok(result.graph)
2965    }
2966
2967    /// Write the graph to a file in `GraphML` format.
2968    ///
2969    /// # Examples
2970    ///
2971    /// ```no_run
2972    /// use rust_igraph::Graph;
2973    ///
2974    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
2975    /// g.to_graphml_file("output.graphml").unwrap();
2976    /// ```
2977    pub fn to_graphml_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
2978        let mut file = std::fs::File::create(path.as_ref())
2979            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
2980        crate::algorithms::io::graphml::write_graphml(self, None, &mut file)
2981    }
2982
2983    /// Read a graph from a DOT (Graphviz) file.
2984    ///
2985    /// # Examples
2986    ///
2987    /// ```no_run
2988    /// use rust_igraph::Graph;
2989    ///
2990    /// let g = Graph::from_dot_file("network.dot").unwrap();
2991    /// ```
2992    pub fn from_dot_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
2993        let file = std::fs::File::open(path.as_ref())
2994            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
2995        let result = crate::algorithms::io::dot::read_dot(std::io::BufReader::new(file))?;
2996        Ok(result.graph)
2997    }
2998
2999    /// Write the graph to a file in DOT (Graphviz) format.
3000    ///
3001    /// # Examples
3002    ///
3003    /// ```no_run
3004    /// use rust_igraph::Graph;
3005    ///
3006    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3007    /// g.to_dot_file("output.dot").unwrap();
3008    /// ```
3009    pub fn to_dot_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
3010        let mut file = std::fs::File::create(path.as_ref())
3011            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
3012        crate::algorithms::io::dot::write_dot(self, None, &mut file)
3013    }
3014
3015    /// Read a graph from a Pajek (.net) file.
3016    ///
3017    /// # Examples
3018    ///
3019    /// ```no_run
3020    /// use rust_igraph::Graph;
3021    ///
3022    /// let g = Graph::from_pajek_file("network.net").unwrap();
3023    /// ```
3024    pub fn from_pajek_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
3025        let file = std::fs::File::open(path.as_ref())
3026            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
3027        let result = crate::algorithms::io::pajek::read_pajek(std::io::BufReader::new(file))?;
3028        Ok(result.graph)
3029    }
3030
3031    /// Write the graph to a file in Pajek (.net) format.
3032    ///
3033    /// # Examples
3034    ///
3035    /// ```no_run
3036    /// use rust_igraph::Graph;
3037    ///
3038    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3039    /// g.to_pajek_file("output.net").unwrap();
3040    /// ```
3041    pub fn to_pajek_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
3042        let mut file = std::fs::File::create(path.as_ref())
3043            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
3044        crate::algorithms::io::pajek::write_pajek(self, None, None, &mut file)
3045    }
3046
3047    /// Read a graph from an NCOL file (Large Graph Layout edge list format).
3048    ///
3049    /// Returns just the graph; use [`read_ncol`](crate::read_ncol) directly
3050    /// to also obtain vertex names and edge weights.
3051    ///
3052    /// # Examples
3053    ///
3054    /// ```no_run
3055    /// use rust_igraph::Graph;
3056    ///
3057    /// let g = Graph::from_ncol_file("network.ncol").unwrap();
3058    /// ```
3059    pub fn from_ncol_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
3060        let file = std::fs::File::open(path.as_ref())
3061            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
3062        let result = crate::algorithms::io::ncol::read_ncol(std::io::BufReader::new(file))?;
3063        Ok(result.graph)
3064    }
3065
3066    /// Write the graph to a file in NCOL format.
3067    ///
3068    /// Writes vertex indices as names (no custom names or weights).
3069    /// Use [`write_ncol`](crate::write_ncol) for full control.
3070    ///
3071    /// # Examples
3072    ///
3073    /// ```no_run
3074    /// use rust_igraph::Graph;
3075    ///
3076    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3077    /// g.to_ncol_file("output.ncol").unwrap();
3078    /// ```
3079    pub fn to_ncol_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
3080        let mut file = std::fs::File::create(path.as_ref())
3081            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
3082        crate::algorithms::io::ncol::write_ncol(self, None, None, &mut file)
3083    }
3084
3085    /// Read a graph from an LGL file (Large Graph Layout adjacency list).
3086    ///
3087    /// Returns just the graph; use [`read_lgl`](crate::read_lgl) directly
3088    /// to also obtain vertex names and edge weights.
3089    ///
3090    /// # Examples
3091    ///
3092    /// ```no_run
3093    /// use rust_igraph::Graph;
3094    ///
3095    /// let g = Graph::from_lgl_file("network.lgl").unwrap();
3096    /// ```
3097    pub fn from_lgl_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
3098        let file = std::fs::File::open(path.as_ref())
3099            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
3100        let result = crate::algorithms::io::lgl::read_lgl(std::io::BufReader::new(file))?;
3101        Ok(result.graph)
3102    }
3103
3104    /// Write the graph to a file in LGL format.
3105    ///
3106    /// Writes vertex indices as names (no custom names or weights).
3107    /// Use [`write_lgl`](crate::write_lgl) for full control.
3108    ///
3109    /// # Examples
3110    ///
3111    /// ```no_run
3112    /// use rust_igraph::Graph;
3113    ///
3114    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3115    /// g.to_lgl_file("output.lgl").unwrap();
3116    /// ```
3117    pub fn to_lgl_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
3118        let mut file = std::fs::File::create(path.as_ref())
3119            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
3120        crate::algorithms::io::lgl::write_lgl(self, None, None, &mut file)
3121    }
3122
3123    /// Read a graph from a LEDA native graph file.
3124    ///
3125    /// Returns just the graph; use [`read_leda`](crate::read_leda) directly
3126    /// to also obtain vertex labels and edge weights.
3127    ///
3128    /// # Examples
3129    ///
3130    /// ```no_run
3131    /// use rust_igraph::Graph;
3132    ///
3133    /// let g = Graph::from_leda_file("network.lgr").unwrap();
3134    /// ```
3135    pub fn from_leda_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
3136        let file = std::fs::File::open(path.as_ref())
3137            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
3138        let result = crate::algorithms::io::leda::read_leda(std::io::BufReader::new(file))?;
3139        Ok(result.graph)
3140    }
3141
3142    /// Write the graph to a file in LEDA native graph format.
3143    ///
3144    /// Writes without vertex labels or edge weights.
3145    /// Use [`write_leda`](crate::write_leda) for full control.
3146    ///
3147    /// # Examples
3148    ///
3149    /// ```no_run
3150    /// use rust_igraph::Graph;
3151    ///
3152    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3153    /// g.to_leda_file("output.lgr").unwrap();
3154    /// ```
3155    pub fn to_leda_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
3156        let mut file = std::fs::File::create(path.as_ref())
3157            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
3158        crate::algorithms::io::leda::write_leda(self, None, None, &mut file)
3159    }
3160
3161    /// Read a graph from a UCINET DL file.
3162    ///
3163    /// Reads as undirected by default. Use [`read_dl`](crate::read_dl)
3164    /// directly for directed graphs or to obtain vertex labels and edge
3165    /// weights.
3166    ///
3167    /// # Examples
3168    ///
3169    /// ```no_run
3170    /// use rust_igraph::Graph;
3171    ///
3172    /// let g = Graph::from_dl_file("network.dl").unwrap();
3173    /// ```
3174    pub fn from_dl_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
3175        let file = std::fs::File::open(path.as_ref())
3176            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
3177        let result = crate::algorithms::io::dl::read_dl(std::io::BufReader::new(file), false)?;
3178        Ok(result.graph)
3179    }
3180
3181    /// Write the graph to a file in UCINET DL format.
3182    ///
3183    /// Writes without vertex labels or edge weights.
3184    /// Use [`write_dl`](crate::write_dl) for full control.
3185    ///
3186    /// # Examples
3187    ///
3188    /// ```no_run
3189    /// use rust_igraph::Graph;
3190    ///
3191    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3192    /// g.to_dl_file("output.dl").unwrap();
3193    /// ```
3194    pub fn to_dl_file<P: AsRef<std::path::Path>>(&self, path: P) -> IgraphResult<()> {
3195        let mut file = std::fs::File::create(path.as_ref())
3196            .map_err(|e| IgraphError::InvalidArgument(format!("cannot create file: {e}")))?;
3197        crate::algorithms::io::dl::write_dl(self, None, None, &mut file)
3198    }
3199
3200    /// Read a graph from a DIMACS file.
3201    ///
3202    /// Reads as directed by default (flow problems). Returns just the graph;
3203    /// use [`read_dimacs`](crate::read_dimacs) directly to also obtain
3204    /// source/target, capacities, or labels.
3205    ///
3206    /// # Examples
3207    ///
3208    /// ```no_run
3209    /// use rust_igraph::Graph;
3210    ///
3211    /// let g = Graph::from_dimacs_file("network.dimacs").unwrap();
3212    /// ```
3213    pub fn from_dimacs_file<P: AsRef<std::path::Path>>(path: P) -> IgraphResult<Self> {
3214        let file = std::fs::File::open(path.as_ref())
3215            .map_err(|e| IgraphError::InvalidArgument(format!("cannot open file: {e}")))?;
3216        let result =
3217            crate::algorithms::io::dimacs::read_dimacs(std::io::BufReader::new(file), true)?;
3218        Ok(result.graph)
3219    }
3220
3221    /// Generate an Erdos-Renyi G(n, p) random graph.
3222    ///
3223    /// Each possible edge exists independently with probability `p`.
3224    ///
3225    /// # Examples
3226    ///
3227    /// ```
3228    /// use rust_igraph::Graph;
3229    ///
3230    /// let g = Graph::erdos_renyi(100, 0.05, 42).unwrap();
3231    /// assert_eq!(g.vcount(), 100);
3232    /// assert!(!g.is_directed());
3233    /// ```
3234    pub fn erdos_renyi(n: u32, p: f64, seed: u64) -> IgraphResult<Self> {
3235        crate::algorithms::games::erdos_renyi::erdos_renyi_gnp(n, p, false, false, seed)
3236    }
3237
3238    /// Generate a Barabasi-Albert preferential attachment graph.
3239    ///
3240    /// Starts with one vertex and adds `n - 1` vertices, each connecting
3241    /// to `m` existing vertices chosen with probability proportional to degree.
3242    ///
3243    /// # Examples
3244    ///
3245    /// ```
3246    /// use rust_igraph::Graph;
3247    ///
3248    /// let g = Graph::barabasi_albert(100, 2, 42).unwrap();
3249    /// assert_eq!(g.vcount(), 100);
3250    /// assert!(!g.is_directed());
3251    /// ```
3252    pub fn barabasi_albert(n: u32, m: u32, seed: u64) -> IgraphResult<Self> {
3253        crate::algorithms::games::barabasi::barabasi_game_bag(n, m, true, false, seed)
3254    }
3255
3256    /// Generate a Watts-Strogatz small-world graph.
3257    ///
3258    /// Creates a ring lattice with `n` vertices where each vertex is connected
3259    /// to its `k` nearest neighbours (must be even), then rewires each edge
3260    /// with probability `p`. This produces graphs with both high clustering
3261    /// and short path lengths — the "small-world" property.
3262    ///
3263    /// # Examples
3264    ///
3265    /// ```
3266    /// use rust_igraph::Graph;
3267    ///
3268    /// let g = Graph::watts_strogatz(20, 4, 0.3, 42).unwrap();
3269    /// assert_eq!(g.vcount(), 20);
3270    /// assert_eq!(g.ecount(), 40); // n * k / 2 = 20 * 4 / 2
3271    /// ```
3272    pub fn watts_strogatz(n: u32, k: u32, p: f64, seed: u64) -> IgraphResult<Self> {
3273        crate::algorithms::games::watts::watts_strogatz_game(n, k / 2, p, false, false, seed)
3274    }
3275
3276    /// Compute strongly connected components (directed graphs).
3277    ///
3278    /// For undirected graphs, this is equivalent to connected components.
3279    ///
3280    /// # Examples
3281    ///
3282    /// ```
3283    /// use rust_igraph::Graph;
3284    ///
3285    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0), (2,3)], true, None).unwrap();
3286    /// let scc = g.strongly_connected_components().unwrap();
3287    /// assert_eq!(scc.count, 2);
3288    /// ```
3289    pub fn strongly_connected_components(
3290        &self,
3291    ) -> IgraphResult<crate::algorithms::connectivity::components::ConnectedComponents> {
3292        crate::algorithms::connectivity::strong::strongly_connected_components(self)
3293    }
3294
3295    /// Find the shortest path between two vertices.
3296    ///
3297    /// Uses BFS for unweighted graphs, Dijkstra/Bellman-Ford for weighted.
3298    /// Returns the vertex and edge sequences along the path.
3299    ///
3300    /// # Examples
3301    ///
3302    /// ```
3303    /// use rust_igraph::Graph;
3304    ///
3305    /// let g = Graph::from_edges(
3306    ///     &[(0,1), (1,2), (2,3), (0,3)], false, None
3307    /// ).unwrap();
3308    /// let path = g.shortest_path_to(0, 3, None).unwrap();
3309    /// assert_eq!(path.vertices, vec![0, 3]);
3310    /// ```
3311    pub fn shortest_path_to(
3312        &self,
3313        source: VertexId,
3314        target: VertexId,
3315        weights: Option<&[f64]>,
3316    ) -> IgraphResult<crate::algorithms::paths::get_shortest_path::ShortestPath> {
3317        use crate::algorithms::paths::dijkstra::DijkstraMode;
3318        let mode = if self.directed {
3319            DijkstraMode::Out
3320        } else {
3321            DijkstraMode::All
3322        };
3323        crate::algorithms::paths::get_shortest_path::get_shortest_path(
3324            self, source, target, weights, mode,
3325        )
3326    }
3327
3328    /// Compute the average path length of the graph.
3329    ///
3330    /// Returns the mean shortest-path distance over all reachable vertex pairs.
3331    /// Unreachable pairs are excluded. Returns `None` if the graph has fewer
3332    /// than 2 vertices or no reachable pairs exist.
3333    ///
3334    /// # Examples
3335    ///
3336    /// ```
3337    /// use rust_igraph::Graph;
3338    ///
3339    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3340    /// let apl = g.average_path_length().unwrap().unwrap();
3341    /// assert!((apl - 5.0 / 3.0).abs() < 1e-10); // (1+2+3+1+2+1)/6
3342    /// ```
3343    pub fn average_path_length(&self) -> IgraphResult<Option<f64>> {
3344        crate::algorithms::properties::basic::mean_distance(self)
3345    }
3346
3347    /// Check if the graph is bipartite and return the partition if so.
3348    ///
3349    /// # Examples
3350    ///
3351    /// ```
3352    /// use rust_igraph::Graph;
3353    ///
3354    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3355    /// let result = g.is_bipartite().unwrap();
3356    /// assert!(result.is_bipartite);
3357    /// ```
3358    pub fn is_bipartite(
3359        &self,
3360    ) -> IgraphResult<crate::algorithms::properties::is_bipartite::BipartiteResult> {
3361        crate::algorithms::properties::is_bipartite::is_bipartite(self)
3362    }
3363
3364    /// Remove self-loops and/or multi-edges from the graph.
3365    ///
3366    /// Returns a new simplified graph.
3367    ///
3368    /// # Examples
3369    ///
3370    /// ```
3371    /// use rust_igraph::Graph;
3372    ///
3373    /// let mut g = Graph::with_vertices(3);
3374    /// g.add_edge(0, 1).unwrap();
3375    /// g.add_edge(0, 1).unwrap(); // multi-edge
3376    /// g.add_edge(1, 1).unwrap(); // self-loop
3377    /// let simple = g.simplify(true, true).unwrap();
3378    /// assert_eq!(simple.ecount(), 1);
3379    /// ```
3380    pub fn simplify(&self, remove_multiple: bool, remove_loops: bool) -> IgraphResult<Graph> {
3381        crate::algorithms::operators::simplify::simplify(self, remove_multiple, remove_loops)
3382    }
3383
3384    /// Reverse all edge directions (directed graphs only).
3385    ///
3386    /// For undirected graphs, returns a copy of the graph unchanged.
3387    ///
3388    /// # Examples
3389    ///
3390    /// ```
3391    /// use rust_igraph::Graph;
3392    ///
3393    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
3394    /// let r = g.reverse().unwrap();
3395    /// assert_eq!(r.neighbors(2).unwrap(), vec![1]);
3396    /// ```
3397    pub fn reverse(&self) -> IgraphResult<Graph> {
3398        crate::algorithms::operators::reverse::reverse(self)
3399    }
3400
3401    /// Convert an undirected graph to directed.
3402    ///
3403    /// In `Mutual` mode each undirected edge becomes two directed edges
3404    /// (u→v and v→u). In `Arbitrary` mode each edge gets one direction
3405    /// (smaller → larger vertex id). Already-directed graphs are copied
3406    /// unchanged.
3407    ///
3408    /// # Examples
3409    ///
3410    /// ```
3411    /// use rust_igraph::{Graph, ToDirectedMode};
3412    ///
3413    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3414    /// let d = g.to_directed(ToDirectedMode::Mutual).unwrap();
3415    /// assert!(d.is_directed());
3416    /// assert_eq!(d.ecount(), 4);
3417    /// ```
3418    pub fn to_directed(
3419        &self,
3420        mode: crate::algorithms::operators::to_directed::ToDirectedMode,
3421    ) -> IgraphResult<Graph> {
3422        crate::algorithms::operators::to_directed::to_directed(self, mode)
3423    }
3424
3425    /// Convert a directed graph to undirected.
3426    ///
3427    /// `Each` keeps every directed edge as undirected. `Collapse` merges
3428    /// mutual pairs into one edge. `Mutual` keeps only edges that exist
3429    /// in both directions. Already-undirected graphs are copied unchanged.
3430    ///
3431    /// # Examples
3432    ///
3433    /// ```
3434    /// use rust_igraph::{Graph, ToUndirectedMode};
3435    ///
3436    /// let g = Graph::from_edges(&[(0,1), (1,0), (1,2)], true, None).unwrap();
3437    /// let u = g.to_undirected(ToUndirectedMode::Collapse).unwrap();
3438    /// assert!(!u.is_directed());
3439    /// assert_eq!(u.ecount(), 2);
3440    /// ```
3441    pub fn to_undirected(
3442        &self,
3443        mode: crate::algorithms::operators::to_undirected::ToUndirectedMode,
3444    ) -> IgraphResult<Graph> {
3445        crate::algorithms::operators::to_undirected::to_undirected(self, mode)
3446    }
3447
3448    /// Contract vertices according to a mapping.
3449    ///
3450    /// `mapping[v]` specifies the new vertex id for vertex `v`. Vertices
3451    /// with the same mapping value are merged into one vertex.
3452    ///
3453    /// # Examples
3454    ///
3455    /// ```
3456    /// use rust_igraph::Graph;
3457    ///
3458    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3459    /// // Merge vertices 0,1 → 0 and 2,3 → 1
3460    /// let contracted = g.contract_vertices(&[0, 0, 1, 1]).unwrap();
3461    /// assert_eq!(contracted.vcount(), 2);
3462    /// ```
3463    pub fn contract_vertices(&self, mapping: &[VertexId]) -> IgraphResult<Graph> {
3464        crate::algorithms::operators::contract_vertices::contract_vertices(self, mapping)
3465    }
3466
3467    /// Perform a random walk starting from a given vertex.
3468    ///
3469    /// Returns the sequence of visited vertex ids (length = `steps + 1`
3470    /// including the starting vertex, or shorter if the walk gets stuck).
3471    ///
3472    /// # Examples
3473    ///
3474    /// ```
3475    /// use rust_igraph::Graph;
3476    ///
3477    /// let g = Graph::from_edges(
3478    ///     &[(0,1), (1,2), (2,3), (3,0)], false, None
3479    /// ).unwrap();
3480    /// let (vertices, edges) = g.random_walk(0, 10, 42).unwrap();
3481    /// assert_eq!(vertices[0], 0);
3482    /// assert!(vertices.len() <= 11);
3483    /// assert_eq!(edges.len(), vertices.len() - 1);
3484    /// ```
3485    pub fn random_walk(
3486        &self,
3487        start: VertexId,
3488        steps: u32,
3489        seed: u64,
3490    ) -> IgraphResult<(Vec<VertexId>, Vec<EdgeId>)> {
3491        use crate::algorithms::paths::dijkstra::DijkstraMode;
3492        let mode = if self.directed {
3493            DijkstraMode::Out
3494        } else {
3495            DijkstraMode::All
3496        };
3497        crate::algorithms::paths::random_walk::random_walk(self, None, start, mode, steps, seed)
3498    }
3499
3500    // ── Graph properties ─────────────────────────────────────────────
3501
3502    /// Compute the radius (minimum eccentricity) of the graph.
3503    ///
3504    /// Returns `None` for the empty graph.
3505    ///
3506    /// # Examples
3507    ///
3508    /// ```
3509    /// use rust_igraph::Graph;
3510    ///
3511    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3512    /// assert_eq!(g.radius().unwrap(), Some(2));
3513    /// ```
3514    pub fn radius(&self) -> IgraphResult<Option<u32>> {
3515        crate::algorithms::paths::radii::radius(self)
3516    }
3517
3518    /// Compute the eccentricity of every vertex.
3519    ///
3520    /// `result[v]` is the maximum shortest-path distance from vertex `v`.
3521    ///
3522    /// # Examples
3523    ///
3524    /// ```
3525    /// use rust_igraph::Graph;
3526    ///
3527    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3528    /// assert_eq!(g.eccentricity().unwrap(), vec![2, 1, 2]);
3529    /// ```
3530    pub fn eccentricity(&self) -> IgraphResult<Vec<u32>> {
3531        crate::algorithms::paths::radii::eccentricity(self)
3532    }
3533
3534    /// Compute the girth (length of the shortest cycle) of the graph.
3535    ///
3536    /// Returns `None` if the graph is acyclic.
3537    ///
3538    /// # Examples
3539    ///
3540    /// ```
3541    /// use rust_igraph::Graph;
3542    ///
3543    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3544    /// assert_eq!(g.girth().unwrap(), Some(3));
3545    /// ```
3546    pub fn girth(&self) -> IgraphResult<Option<u32>> {
3547        crate::algorithms::properties::girth::girth(self)
3548    }
3549
3550    /// Check whether the graph is a tree.
3551    ///
3552    /// Returns `Some(root)` where `root` is the first root vertex found,
3553    /// or `None` if the graph is not a tree. The `mode` parameter controls
3554    /// how edges are followed for directed graphs.
3555    ///
3556    /// # Examples
3557    ///
3558    /// ```
3559    /// use rust_igraph::{Graph, DijkstraMode};
3560    ///
3561    /// let g = Graph::from_edges(&[(0,1), (1,2), (1,3)], false, None).unwrap();
3562    /// assert!(g.is_tree(DijkstraMode::All).unwrap().is_some());
3563    /// ```
3564    pub fn is_tree(
3565        &self,
3566        mode: crate::algorithms::paths::dijkstra::DijkstraMode,
3567    ) -> IgraphResult<Option<VertexId>> {
3568        crate::algorithms::properties::is_tree::is_tree(self, mode)
3569    }
3570
3571    /// Check whether the directed graph is a DAG.
3572    ///
3573    /// Returns `false` for undirected graphs.
3574    ///
3575    /// # Examples
3576    ///
3577    /// ```
3578    /// use rust_igraph::Graph;
3579    ///
3580    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
3581    /// assert!(g.is_dag());
3582    /// ```
3583    pub fn is_dag(&self) -> bool {
3584        crate::algorithms::properties::is_dag::is_dag(self)
3585    }
3586
3587    /// Count the total number of triangles in the graph.
3588    ///
3589    /// # Examples
3590    ///
3591    /// ```
3592    /// use rust_igraph::Graph;
3593    ///
3594    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3595    /// assert_eq!(g.count_triangles().unwrap(), 1);
3596    /// ```
3597    pub fn count_triangles(&self) -> IgraphResult<u64> {
3598        crate::algorithms::properties::triangles::count_triangles(self)
3599    }
3600
3601    /// Compute the harmonic centrality of all vertices.
3602    ///
3603    /// Harmonic centrality of `v` is the sum of inverse distances
3604    /// from `v` to all other reachable vertices.
3605    ///
3606    /// # Examples
3607    ///
3608    /// ```
3609    /// use rust_igraph::Graph;
3610    ///
3611    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3612    /// let h = g.harmonic_centrality().unwrap();
3613    /// assert_eq!(h.len(), 3);
3614    /// ```
3615    pub fn harmonic_centrality(&self) -> IgraphResult<Vec<f64>> {
3616        crate::algorithms::properties::harmonic::harmonic_centrality(self)
3617    }
3618
3619    /// Compute the k-hop neighborhood size for every vertex.
3620    ///
3621    /// `result[v]` is the number of vertices within distance `order` from
3622    /// `v` (including `v` itself).
3623    ///
3624    /// # Examples
3625    ///
3626    /// ```
3627    /// use rust_igraph::Graph;
3628    ///
3629    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3630    /// let sizes = g.neighborhood_size(1).unwrap();
3631    /// assert_eq!(sizes, vec![2, 3, 3, 2]);
3632    /// ```
3633    pub fn neighborhood_size(&self, order: i32) -> IgraphResult<Vec<u32>> {
3634        crate::algorithms::properties::neighborhood::neighborhood_size(self, order)
3635    }
3636
3637    // ── Connectivity ─────────────────────────────────────────────────
3638
3639    /// Compute the vertex connectivity (minimum vertex cut) of the graph.
3640    ///
3641    /// # Examples
3642    ///
3643    /// ```
3644    /// use rust_igraph::Graph;
3645    ///
3646    /// let g = Graph::from_edges(
3647    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None,
3648    /// ).unwrap();
3649    /// assert_eq!(g.vertex_connectivity().unwrap(), 2);
3650    /// ```
3651    pub fn vertex_connectivity(&self) -> IgraphResult<i64> {
3652        crate::algorithms::flow::vertex_connectivity::vertex_connectivity(self, true)
3653    }
3654
3655    /// Compute the edge connectivity (minimum edge cut) of the graph.
3656    ///
3657    /// # Examples
3658    ///
3659    /// ```
3660    /// use rust_igraph::Graph;
3661    ///
3662    /// let g = Graph::from_edges(
3663    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None,
3664    /// ).unwrap();
3665    /// assert_eq!(g.edge_connectivity().unwrap(), 2);
3666    /// ```
3667    pub fn edge_connectivity(&self) -> IgraphResult<i64> {
3668        crate::algorithms::flow::edge_connectivity::edge_connectivity(self, true)
3669    }
3670
3671    /// Find all vertices reachable from `source`.
3672    ///
3673    /// # Examples
3674    ///
3675    /// ```
3676    /// use rust_igraph::{Graph, SubcomponentMode};
3677    ///
3678    /// let g = Graph::from_edges(&[(0,1), (1,2), (3,4)], false, None).unwrap();
3679    /// let comp = g.subcomponent(0, SubcomponentMode::All).unwrap();
3680    /// assert_eq!(comp.len(), 3);
3681    /// ```
3682    pub fn subcomponent(
3683        &self,
3684        source: VertexId,
3685        mode: crate::algorithms::connectivity::subcomponent::SubcomponentMode,
3686    ) -> IgraphResult<Vec<VertexId>> {
3687        crate::algorithms::connectivity::subcomponent::subcomponent(self, source, mode)
3688    }
3689
3690    // ── Cliques ──────────────────────────────────────────────────────
3691
3692    /// Find all cliques in the graph within a size range.
3693    ///
3694    /// # Examples
3695    ///
3696    /// ```
3697    /// use rust_igraph::Graph;
3698    ///
3699    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3700    /// let c = g.cliques(3, 3, None).unwrap();
3701    /// assert_eq!(c.len(), 1);
3702    /// ```
3703    pub fn cliques(
3704        &self,
3705        min_size: u32,
3706        max_size: u32,
3707        max_results: Option<usize>,
3708    ) -> IgraphResult<Vec<Vec<VertexId>>> {
3709        crate::algorithms::cliques::cliques(self, min_size, max_size, max_results)
3710    }
3711
3712    /// Find all maximal cliques in the graph.
3713    ///
3714    /// # Examples
3715    ///
3716    /// ```
3717    /// use rust_igraph::Graph;
3718    ///
3719    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3720    /// let mc = g.maximal_cliques().unwrap();
3721    /// assert_eq!(mc.len(), 1);
3722    /// assert_eq!(mc[0].len(), 3);
3723    /// ```
3724    pub fn maximal_cliques(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
3725        crate::algorithms::cliques::maximal_cliques(self)
3726    }
3727
3728    /// Compute the independence number (max independent set size).
3729    ///
3730    /// # Examples
3731    ///
3732    /// ```
3733    /// use rust_igraph::Graph;
3734    ///
3735    /// // Triangle: independence number is 1 (can pick at most 1 non-adjacent vertex pair... no, 1)
3736    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3737    /// assert_eq!(g.independence_number().unwrap(), 1);
3738    /// ```
3739    pub fn independence_number(&self) -> IgraphResult<u32> {
3740        crate::algorithms::cliques::independence_number(self)
3741    }
3742
3743    // ── Operators ────────────────────────────────────────────────────
3744
3745    /// Permute the vertices of the graph.
3746    ///
3747    /// `permutation[v]` gives the new id for vertex `v`. Returns a new
3748    /// graph with edges reconnected accordingly.
3749    ///
3750    /// # Examples
3751    ///
3752    /// ```
3753    /// use rust_igraph::Graph;
3754    ///
3755    /// // permutation[new] = old: new 0 ← old 2, new 1 ← old 0, new 2 ← old 1
3756    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3757    /// let p = g.permute_vertices(&[2, 0, 1]).unwrap();
3758    /// assert!(p.has_edge(1, 2));
3759    /// assert!(p.has_edge(2, 0));
3760    /// ```
3761    pub fn permute_vertices(&self, permutation: &[VertexId]) -> IgraphResult<Graph> {
3762        crate::algorithms::operators::permute_vertices::permute_vertices(self, permutation)
3763    }
3764
3765    // ── Layout ───────────────────────────────────────────────────────
3766
3767    /// Fruchterman-Reingold force-directed layout with default parameters.
3768    ///
3769    /// # Examples
3770    ///
3771    /// ```
3772    /// use rust_igraph::Graph;
3773    ///
3774    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3775    /// let coords = g.layout_fruchterman_reingold().unwrap();
3776    /// assert_eq!(coords.len(), 3);
3777    /// ```
3778    pub fn layout_fruchterman_reingold(&self) -> IgraphResult<Vec<(f64, f64)>> {
3779        use crate::algorithms::layout::fruchterman_reingold::FrParams;
3780        crate::algorithms::layout::fruchterman_reingold::layout_fruchterman_reingold(
3781            self,
3782            &FrParams::default(),
3783        )
3784    }
3785
3786    /// Kamada-Kawai spring-embedder layout with default parameters.
3787    ///
3788    /// # Examples
3789    ///
3790    /// ```
3791    /// use rust_igraph::Graph;
3792    ///
3793    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3794    /// let coords = g.layout_kamada_kawai().unwrap();
3795    /// assert_eq!(coords.len(), 4);
3796    /// ```
3797    pub fn layout_kamada_kawai(&self) -> IgraphResult<Vec<[f64; 2]>> {
3798        use crate::algorithms::layout::kamada_kawai::KkParams;
3799        let params = KkParams::default_for(self.vcount() as usize);
3800        crate::algorithms::layout::kamada_kawai::layout_kamada_kawai(self, None, &params, None)
3801    }
3802
3803    /// `DrL` (Distributed Recursive Layout) with default options.
3804    ///
3805    /// # Examples
3806    ///
3807    /// ```
3808    /// use rust_igraph::Graph;
3809    ///
3810    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3811    /// let coords = g.layout_drl().unwrap();
3812    /// assert_eq!(coords.len(), 3);
3813    /// ```
3814    pub fn layout_drl(&self) -> IgraphResult<Vec<[f64; 2]>> {
3815        use crate::algorithms::layout::drl::DrlOptions;
3816        crate::algorithms::layout::drl::layout_drl(self, None, &DrlOptions::default(), None)
3817    }
3818
3819    /// Circular layout.
3820    ///
3821    /// # Examples
3822    ///
3823    /// ```
3824    /// use rust_igraph::Graph;
3825    ///
3826    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3827    /// let coords = g.layout_circle();
3828    /// assert_eq!(coords.len(), 3);
3829    /// ```
3830    pub fn layout_circle(&self) -> Vec<(f64, f64)> {
3831        crate::algorithms::layout::simple::layout_circle(self, None)
3832    }
3833
3834    /// Random layout with the given RNG seed.
3835    ///
3836    /// # Examples
3837    ///
3838    /// ```
3839    /// use rust_igraph::Graph;
3840    ///
3841    /// let g = Graph::with_vertices(5);
3842    /// let coords = g.layout_random(42);
3843    /// assert_eq!(coords.len(), 5);
3844    /// ```
3845    pub fn layout_random(&self, seed: u64) -> Vec<(f64, f64)> {
3846        crate::algorithms::layout::simple::layout_random(self, seed)
3847    }
3848
3849    /// Grid layout.
3850    ///
3851    /// `width` specifies the number of columns. Pass 0 to auto-compute
3852    /// (ceil of square root of vertex count).
3853    ///
3854    /// # Examples
3855    ///
3856    /// ```
3857    /// use rust_igraph::Graph;
3858    ///
3859    /// let g = Graph::with_vertices(9);
3860    /// let coords = g.layout_grid(3);
3861    /// assert_eq!(coords.len(), 9);
3862    /// ```
3863    pub fn layout_grid(&self, width: i32) -> Vec<(f64, f64)> {
3864        crate::algorithms::layout::simple::layout_grid(self, width)
3865    }
3866
3867    // ── Triangle / local clustering ──────────────────────────────────
3868
3869    /// Per-vertex triangle count.
3870    ///
3871    /// `result[v]` is the number of triangles incident to vertex `v`.
3872    ///
3873    /// # Examples
3874    ///
3875    /// ```
3876    /// use rust_igraph::Graph;
3877    ///
3878    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3)], false, None).unwrap();
3879    /// let t = g.count_adjacent_triangles().unwrap();
3880    /// assert_eq!(t[0], 1);
3881    /// assert_eq!(t[3], 0);
3882    /// ```
3883    pub fn count_adjacent_triangles(&self) -> IgraphResult<Vec<u64>> {
3884        crate::algorithms::properties::triangles::count_adjacent_triangles(self)
3885    }
3886
3887    /// Per-vertex local clustering coefficient (local transitivity).
3888    ///
3889    /// `result[v]` is `None` for vertices with degree < 2.
3890    ///
3891    /// # Examples
3892    ///
3893    /// ```
3894    /// use rust_igraph::Graph;
3895    ///
3896    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3)], false, None).unwrap();
3897    /// let lcc = g.transitivity_local_undirected().unwrap();
3898    /// assert!(lcc[0].unwrap() > 0.9); // vertex 0 in triangle
3899    /// assert!(lcc[3].is_none());       // degree 1
3900    /// ```
3901    pub fn transitivity_local_undirected(&self) -> IgraphResult<Vec<Option<f64>>> {
3902        crate::algorithms::properties::triangles::transitivity_local_undirected(self)
3903    }
3904
3905    // ── Network metrics ──────────────────────────────────────────────
3906
3907    /// Reciprocity of a directed graph.
3908    ///
3909    /// Returns the fraction of edges that are reciprocated, or `None` for
3910    /// empty graphs.
3911    ///
3912    /// # Examples
3913    ///
3914    /// ```
3915    /// use rust_igraph::Graph;
3916    ///
3917    /// let g = Graph::from_edges(&[(0,1),(1,0),(1,2)], true, None).unwrap();
3918    /// let r = g.reciprocity().unwrap().unwrap();
3919    /// assert!((r - 2.0/3.0).abs() < 1e-12);
3920    /// ```
3921    pub fn reciprocity(&self) -> IgraphResult<Option<f64>> {
3922        crate::algorithms::properties::reciprocity::reciprocity(self)
3923    }
3924
3925    /// Burt's constraint for each vertex.
3926    ///
3927    /// # Examples
3928    ///
3929    /// ```
3930    /// use rust_igraph::Graph;
3931    ///
3932    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
3933    /// let c = g.constraint(None).unwrap();
3934    /// assert_eq!(c.len(), 3);
3935    /// ```
3936    pub fn constraint(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
3937        crate::algorithms::properties::constraint::constraint(self, weights)
3938    }
3939
3940    /// Whether the graph has multi-edges.
3941    ///
3942    /// # Examples
3943    ///
3944    /// ```
3945    /// use rust_igraph::Graph;
3946    ///
3947    /// let g = Graph::from_edges(&[(0,1),(0,1)], false, None).unwrap();
3948    /// assert!(g.has_multiple().unwrap());
3949    /// ```
3950    pub fn has_multiple(&self) -> IgraphResult<bool> {
3951        crate::algorithms::properties::multiplicity::has_multiple(self)
3952    }
3953
3954    /// Per-edge multiplicity count.
3955    ///
3956    /// # Examples
3957    ///
3958    /// ```
3959    /// use rust_igraph::Graph;
3960    ///
3961    /// let g = Graph::from_edges(&[(0,1),(0,1),(1,2)], false, None).unwrap();
3962    /// let mc = g.count_multiple().unwrap();
3963    /// assert_eq!(mc[0], 2);
3964    /// assert_eq!(mc[2], 1);
3965    /// ```
3966    pub fn count_multiple(&self) -> IgraphResult<Vec<usize>> {
3967        crate::algorithms::properties::multiplicity::count_multiple(self)
3968    }
3969
3970    /// Test whether two vertices are adjacent.
3971    ///
3972    /// # Examples
3973    ///
3974    /// ```
3975    /// use rust_igraph::Graph;
3976    ///
3977    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
3978    /// assert!(g.are_adjacent(0, 1).unwrap());
3979    /// assert!(!g.are_adjacent(0, 2).unwrap());
3980    /// ```
3981    pub fn are_adjacent(&self, v1: VertexId, v2: VertexId) -> IgraphResult<bool> {
3982        crate::algorithms::properties::are_adjacent::are_adjacent(self, v1, v2)
3983    }
3984
3985    // ── Motifs ───────────────────────────────────────────────────────
3986
3987    /// Triad census of a directed graph.
3988    ///
3989    /// # Examples
3990    ///
3991    /// ```
3992    /// use rust_igraph::{Graph, TriadType};
3993    ///
3994    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], true, None).unwrap();
3995    /// let tc = g.triad_census().unwrap();
3996    /// assert!(tc.get(TriadType::T030C) > 0.0);
3997    /// ```
3998    pub fn triad_census(
3999        &self,
4000    ) -> IgraphResult<crate::algorithms::motifs::triad_census::TriadCensus> {
4001        crate::algorithms::motifs::triad_census::triad_census(self)
4002    }
4003
4004    /// Dyad census of a directed graph.
4005    ///
4006    /// # Examples
4007    ///
4008    /// ```
4009    /// use rust_igraph::Graph;
4010    ///
4011    /// let g = Graph::from_edges(&[(0,1),(1,0),(1,2)], true, None).unwrap();
4012    /// let dc = g.dyad_census().unwrap();
4013    /// assert!((dc.mutual - 1.0).abs() < 1e-12);
4014    /// ```
4015    pub fn dyad_census(&self) -> IgraphResult<crate::algorithms::motifs::DyadCensus> {
4016        crate::algorithms::motifs::dyad_census(self)
4017    }
4018
4019    // ── Similarity ───────────────────────────────────────────────────
4020
4021    /// Jaccard similarity between all pairs of vertices.
4022    ///
4023    /// Returns a flattened `n × n` matrix (row-major).
4024    ///
4025    /// # Examples
4026    ///
4027    /// ```
4028    /// use rust_igraph::Graph;
4029    ///
4030    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
4031    /// let sim = g.similarity_jaccard().unwrap();
4032    /// assert_eq!(sim.len(), 9); // 3×3 matrix
4033    /// ```
4034    pub fn similarity_jaccard(&self) -> IgraphResult<Vec<f64>> {
4035        crate::algorithms::properties::similarity::similarity_jaccard(self)
4036    }
4037
4038    /// Co-citation scores for all vertex pairs.
4039    ///
4040    /// # Examples
4041    ///
4042    /// ```
4043    /// use rust_igraph::Graph;
4044    ///
4045    /// let g = Graph::from_edges(&[(0,2),(1,2)], true, None).unwrap();
4046    /// let cc = g.cocitation().unwrap();
4047    /// assert!(!cc.is_empty());
4048    /// ```
4049    pub fn cocitation(&self) -> IgraphResult<Vec<u32>> {
4050        crate::algorithms::properties::similarity::cocitation(self)
4051    }
4052
4053    // ── Graph structure recognizers ─────────────────────────────────
4054
4055    /// Check whether the graph is a cograph.
4056    ///
4057    /// # Examples
4058    ///
4059    /// ```
4060    /// use rust_igraph::Graph;
4061    ///
4062    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
4063    /// assert!(g.is_cograph().unwrap());
4064    /// ```
4065    pub fn is_cograph(&self) -> IgraphResult<bool> {
4066        crate::algorithms::properties::is_cograph::is_cograph(self)
4067    }
4068
4069    /// Check whether the graph is series-parallel.
4070    ///
4071    /// # Examples
4072    ///
4073    /// ```
4074    /// use rust_igraph::Graph;
4075    ///
4076    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4077    /// assert!(g.is_series_parallel().unwrap());
4078    /// ```
4079    pub fn is_series_parallel(&self) -> IgraphResult<bool> {
4080        crate::algorithms::properties::is_series_parallel::is_series_parallel(self)
4081    }
4082
4083    /// Check whether the graph is outerplanar.
4084    ///
4085    /// # Examples
4086    ///
4087    /// ```
4088    /// use rust_igraph::Graph;
4089    ///
4090    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4091    /// assert!(g.is_outerplanar().unwrap());
4092    /// ```
4093    pub fn is_outerplanar(&self) -> IgraphResult<bool> {
4094        crate::algorithms::properties::is_outerplanar::is_outerplanar(self)
4095    }
4096
4097    /// Check whether the graph is chordal.
4098    ///
4099    /// # Examples
4100    ///
4101    /// ```
4102    /// use rust_igraph::Graph;
4103    ///
4104    /// let g = Graph::from_edges(
4105    ///     &[(0,1), (1,2), (2,0), (0,3), (1,3), (2,3)], false, None
4106    /// ).unwrap();
4107    /// assert!(g.is_chordal().unwrap());
4108    /// ```
4109    pub fn is_chordal(&self) -> IgraphResult<bool> {
4110        let result = crate::algorithms::chordality::is_chordal(self, None)?;
4111        Ok(result.chordal)
4112    }
4113
4114    /// Check whether the graph is a forest (acyclic).
4115    ///
4116    /// # Examples
4117    ///
4118    /// ```
4119    /// use rust_igraph::Graph;
4120    ///
4121    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4122    /// assert!(g.is_forest().unwrap());
4123    /// ```
4124    pub fn is_forest(&self) -> IgraphResult<bool> {
4125        Ok(crate::algorithms::properties::is_forest::is_forest(
4126            self,
4127            crate::algorithms::paths::dijkstra::DijkstraMode::Out,
4128        )?
4129        .is_some())
4130    }
4131
4132    // ── Cycles and motifs ───────────────────────────────────────────
4133
4134    /// Compute a fundamental cycle basis of the graph.
4135    ///
4136    /// # Examples
4137    ///
4138    /// ```
4139    /// use rust_igraph::Graph;
4140    ///
4141    /// let g = Graph::from_edges(
4142    ///     &[(0,1), (1,2), (2,0)], false, None
4143    /// ).unwrap();
4144    /// let cycles = g.fundamental_cycles().unwrap();
4145    /// assert_eq!(cycles.len(), 1);
4146    /// ```
4147    pub fn fundamental_cycles(&self) -> IgraphResult<Vec<Vec<u32>>> {
4148        crate::algorithms::fundamental_cycles::fundamental_cycles(self, None, None)
4149    }
4150
4151    /// Compute a minimum weight cycle basis.
4152    ///
4153    /// # Examples
4154    ///
4155    /// ```
4156    /// use rust_igraph::Graph;
4157    ///
4158    /// let g = Graph::from_edges(
4159    ///     &[(0,1), (1,2), (2,0), (1,3), (3,0)], false, None
4160    /// ).unwrap();
4161    /// let basis = g.minimum_cycle_basis().unwrap();
4162    /// assert_eq!(basis.len(), 2);
4163    /// ```
4164    pub fn minimum_cycle_basis(&self) -> IgraphResult<Vec<Vec<u32>>> {
4165        crate::algorithms::minimum_cycle_basis::minimum_cycle_basis(self, None, false)
4166    }
4167
4168    // ── Cuts, covers, and sets ──────────────────────────────────────
4169
4170    /// Find a minimum feedback arc set.
4171    ///
4172    /// Returns edges whose removal makes the graph acyclic.
4173    ///
4174    /// # Examples
4175    ///
4176    /// ```
4177    /// use rust_igraph::Graph;
4178    ///
4179    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], true, None).unwrap();
4180    /// let fas = g.feedback_arc_set().unwrap();
4181    /// assert!(!fas.is_empty());
4182    /// ```
4183    pub fn feedback_arc_set(&self) -> IgraphResult<Vec<u32>> {
4184        crate::algorithms::feedback_arc_set::feedback_arc_set(
4185            self,
4186            None,
4187            crate::algorithms::feedback_arc_set::FasAlgorithm::EadesLinSmyth,
4188        )
4189    }
4190
4191    /// Find the maximum cut of the graph.
4192    ///
4193    /// # Examples
4194    ///
4195    /// ```
4196    /// use rust_igraph::Graph;
4197    ///
4198    /// let g = Graph::from_edges(
4199    ///     &[(0,1), (1,2), (2,3)], false, None
4200    /// ).unwrap();
4201    /// let result = g.maximum_cut().unwrap();
4202    /// assert!(result.cut_value > 0);
4203    /// ```
4204    pub fn maximum_cut(&self) -> IgraphResult<crate::algorithms::max_cut::MaxCutResult> {
4205        crate::algorithms::max_cut::maximum_cut(self)
4206    }
4207
4208    /// Find a minimum vertex cover.
4209    ///
4210    /// # Examples
4211    ///
4212    /// ```
4213    /// use rust_igraph::Graph;
4214    ///
4215    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4216    /// let cover = g.minimum_vertex_cover().unwrap();
4217    /// assert!(cover.contains(&1));
4218    /// ```
4219    pub fn minimum_vertex_cover(&self) -> IgraphResult<Vec<u32>> {
4220        crate::algorithms::vertex_cover::minimum_vertex_cover(self)
4221    }
4222
4223    /// Find a minimum edge cover.
4224    ///
4225    /// # Examples
4226    ///
4227    /// ```
4228    /// use rust_igraph::Graph;
4229    ///
4230    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4231    /// let cover = g.minimum_edge_cover().unwrap();
4232    /// assert!(!cover.is_empty());
4233    /// ```
4234    pub fn minimum_edge_cover(&self) -> IgraphResult<Vec<u32>> {
4235        crate::algorithms::edge_cover::minimum_edge_cover(self)
4236    }
4237
4238    /// Find a maximum independent set.
4239    ///
4240    /// # Examples
4241    ///
4242    /// ```
4243    /// use rust_igraph::Graph;
4244    ///
4245    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4246    /// let mis = g.maximum_independent_set().unwrap();
4247    /// assert_eq!(mis.len(), 2);
4248    /// ```
4249    pub fn maximum_independent_set(&self) -> IgraphResult<Vec<u32>> {
4250        crate::algorithms::independent_set::maximum_independent_set(self)
4251    }
4252
4253    // ── Coloring ────────────────────────────────────────────────────
4254
4255    /// Greedy vertex coloring.
4256    ///
4257    /// # Examples
4258    ///
4259    /// ```
4260    /// use rust_igraph::Graph;
4261    ///
4262    /// let g = Graph::from_edges(
4263    ///     &[(0,1), (1,2), (2,0)], false, None
4264    /// ).unwrap();
4265    /// let colors = g.vertex_coloring().unwrap();
4266    /// assert_eq!(colors.len(), 3);
4267    /// // Adjacent vertices must have different colors
4268    /// assert_ne!(colors[0], colors[1]);
4269    /// ```
4270    pub fn vertex_coloring(&self) -> IgraphResult<Vec<u32>> {
4271        crate::algorithms::coloring::vertex_coloring_greedy(
4272            self,
4273            crate::algorithms::coloring::GreedyColoringHeuristic::ColoredNeighbors,
4274        )
4275    }
4276
4277    // ── Spanning trees ──────────────────────────────────────────────
4278
4279    /// Sample a random spanning tree.
4280    ///
4281    /// # Examples
4282    ///
4283    /// ```
4284    /// use rust_igraph::Graph;
4285    ///
4286    /// let g = Graph::from_edges(
4287    ///     &[(0,1), (1,2), (2,0), (1,3)], false, None
4288    /// ).unwrap();
4289    /// let edges = g.random_spanning_tree(42).unwrap();
4290    /// assert_eq!(edges.len(), 3);
4291    /// ```
4292    pub fn random_spanning_tree(&self, seed: u64) -> IgraphResult<Vec<u32>> {
4293        crate::algorithms::spanning::random_spanning_tree::random_spanning_tree(self, None, seed)
4294    }
4295
4296    // ── Community detection (extended) ──────────────────────────────
4297
4298    /// Edge betweenness community detection.
4299    ///
4300    /// # Examples
4301    ///
4302    /// ```
4303    /// use rust_igraph::Graph;
4304    ///
4305    /// let g = Graph::from_edges(
4306    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)],
4307    ///     false, None
4308    /// ).unwrap();
4309    /// let result = g.edge_betweenness_community().unwrap();
4310    /// assert!(!result.membership.is_empty());
4311    /// ```
4312    pub fn edge_betweenness_community(
4313        &self,
4314    ) -> IgraphResult<crate::algorithms::community::edge_betweenness_community::EdgeBetweennessResult>
4315    {
4316        crate::algorithms::community::edge_betweenness_community::edge_betweenness_community(self)
4317    }
4318
4319    // ── Isomorphism (canonical / BLISS) ─────────────────────────────
4320
4321    /// Compute a canonical vertex permutation.
4322    ///
4323    /// # Examples
4324    ///
4325    /// ```
4326    /// use rust_igraph::Graph;
4327    ///
4328    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4329    /// let perm = g.canonical_permutation().unwrap();
4330    /// assert_eq!(perm.len(), 3);
4331    /// ```
4332    pub fn canonical_permutation(&self) -> IgraphResult<Vec<u32>> {
4333        crate::algorithms::isomorphism::canonical::canonical_permutation::canonical_permutation(
4334            self, None,
4335        )
4336    }
4337
4338    /// Count automorphisms of the graph.
4339    ///
4340    /// # Examples
4341    ///
4342    /// ```
4343    /// use rust_igraph::Graph;
4344    ///
4345    /// // K3 has 3! = 6 automorphisms
4346    /// let g = Graph::from_edges(
4347    ///     &[(0,1), (1,2), (2,0)], false, None
4348    /// ).unwrap();
4349    /// let count = g.count_automorphisms().unwrap();
4350    /// assert!((count - 6.0).abs() < 1e-10);
4351    /// ```
4352    pub fn count_automorphisms(&self) -> IgraphResult<f64> {
4353        crate::algorithms::isomorphism::canonical::count_automorphisms::count_automorphisms(
4354            self, None,
4355        )
4356    }
4357
4358    /// Compute a generating set for the automorphism group.
4359    ///
4360    /// # Examples
4361    ///
4362    /// ```
4363    /// use rust_igraph::Graph;
4364    ///
4365    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4366    /// let gens = g.automorphism_group().unwrap();
4367    /// assert!(!gens.is_empty());
4368    /// ```
4369    pub fn automorphism_group(&self) -> IgraphResult<Vec<Vec<u32>>> {
4370        crate::algorithms::isomorphism::canonical::automorphism_group::automorphism_group(
4371            self, None,
4372        )
4373    }
4374
4375    // ── Epidemics ───────────────────────────────────────────────────
4376
4377    /// Run a single SIR (Susceptible-Infected-Recovered) simulation.
4378    ///
4379    /// `beta` is the infection rate, `gamma` is the recovery rate.
4380    ///
4381    /// # Examples
4382    ///
4383    /// ```
4384    /// use rust_igraph::Graph;
4385    ///
4386    /// let g = Graph::from_edges(
4387    ///     &[(0,1), (1,2), (2,3), (3,4)], false, None
4388    /// ).unwrap();
4389    /// let result = g.sir(0.5, 0.1, 1, 42).unwrap();
4390    /// assert!(!result.is_empty());
4391    /// ```
4392    pub fn sir(
4393        &self,
4394        beta: f64,
4395        gamma: f64,
4396        no_sim: usize,
4397        seed: u64,
4398    ) -> IgraphResult<Vec<crate::algorithms::epidemics::Sir>> {
4399        crate::algorithms::epidemics::sir(self, beta, gamma, no_sim, seed)
4400    }
4401
4402    // ── Spanner ─────────────────────────────────────────────────────
4403
4404    /// Compute a graph spanner with the given stretch factor.
4405    ///
4406    /// Returns edge indices forming the spanner subgraph.
4407    ///
4408    /// # Examples
4409    ///
4410    /// ```
4411    /// use rust_igraph::Graph;
4412    ///
4413    /// let g = Graph::from_edges(
4414    ///     &[(0,1), (1,2), (2,0), (1,3)], false, None
4415    /// ).unwrap();
4416    /// let edges = g.spanner(3.0).unwrap();
4417    /// assert!(!edges.is_empty());
4418    /// ```
4419    pub fn spanner(&self, stretch: f64) -> IgraphResult<Vec<u32>> {
4420        crate::algorithms::paths::spanner::spanner(self, stretch, None)
4421    }
4422
4423    // ── Graph recognizers ─────────────────────────────────────────
4424
4425    /// Check whether this graph is acyclic (a DAG for directed, forest for undirected).
4426    ///
4427    /// # Examples
4428    ///
4429    /// ```
4430    /// use rust_igraph::Graph;
4431    ///
4432    /// let tree = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4433    /// assert!(tree.is_acyclic());
4434    /// ```
4435    pub fn is_acyclic(&self) -> bool {
4436        crate::algorithms::properties::is_acyclic::is_acyclic(self)
4437    }
4438
4439    /// Check whether this graph is an apex forest.
4440    ///
4441    /// # Examples
4442    ///
4443    /// ```
4444    /// use rust_igraph::Graph;
4445    ///
4446    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4447    /// assert!(g.is_apex_forest().unwrap());
4448    /// ```
4449    pub fn is_apex_forest(&self) -> IgraphResult<bool> {
4450        crate::algorithms::properties::is_apex_forest::is_apex_forest(self)
4451    }
4452
4453    /// Check whether this graph is an apex tree.
4454    ///
4455    /// # Examples
4456    ///
4457    /// ```
4458    /// use rust_igraph::Graph;
4459    ///
4460    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4461    /// assert!(g.is_apex_tree().unwrap());
4462    /// ```
4463    pub fn is_apex_tree(&self) -> IgraphResult<bool> {
4464        crate::algorithms::properties::is_apex_tree::is_apex_tree(self)
4465    }
4466
4467    /// Check whether this graph is banner-free.
4468    ///
4469    /// # Examples
4470    ///
4471    /// ```
4472    /// use rust_igraph::Graph;
4473    ///
4474    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4475    /// assert!(g.is_banner_free().unwrap());
4476    /// ```
4477    pub fn is_banner_free(&self) -> IgraphResult<bool> {
4478        crate::algorithms::properties::is_banner_free::is_banner_free(self)
4479    }
4480
4481    /// Check whether this graph is a biclique.
4482    ///
4483    /// # Examples
4484    ///
4485    /// ```
4486    /// use rust_igraph::Graph;
4487    ///
4488    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,2), (1,3)], false, None).unwrap();
4489    /// assert!(g.is_biclique().unwrap());
4490    /// ```
4491    pub fn is_biclique(&self) -> IgraphResult<bool> {
4492        crate::algorithms::properties::is_biclique::is_biclique(self)
4493    }
4494
4495    /// Check whether this graph is biregular.
4496    ///
4497    /// # Examples
4498    ///
4499    /// ```
4500    /// use rust_igraph::Graph;
4501    ///
4502    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,2), (1,3)], false, None).unwrap();
4503    /// assert!(g.is_biregular().unwrap());
4504    /// ```
4505    pub fn is_biregular(&self) -> IgraphResult<bool> {
4506        crate::algorithms::properties::is_biregular::is_biregular(self)
4507    }
4508
4509    /// Check whether this graph is a block graph.
4510    ///
4511    /// # Examples
4512    ///
4513    /// ```
4514    /// use rust_igraph::Graph;
4515    ///
4516    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4517    /// assert!(g.is_block_graph().unwrap());
4518    /// ```
4519    pub fn is_block_graph(&self) -> IgraphResult<bool> {
4520        crate::algorithms::properties::is_block::is_block_graph(self)
4521    }
4522
4523    /// Check whether this graph is bowtie-free.
4524    ///
4525    /// # Examples
4526    ///
4527    /// ```
4528    /// use rust_igraph::Graph;
4529    ///
4530    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4531    /// assert!(g.is_bowtie_free().unwrap());
4532    /// ```
4533    pub fn is_bowtie_free(&self) -> IgraphResult<bool> {
4534        crate::algorithms::properties::is_bowtie_free::is_bowtie_free(self)
4535    }
4536
4537    /// Check whether this graph is bull-free.
4538    ///
4539    /// # Examples
4540    ///
4541    /// ```
4542    /// use rust_igraph::Graph;
4543    ///
4544    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4545    /// assert!(g.is_bull_free().unwrap());
4546    /// ```
4547    pub fn is_bull_free(&self) -> IgraphResult<bool> {
4548        crate::algorithms::properties::is_bull_free::is_bull_free(self)
4549    }
4550
4551    /// Check whether this graph is C4-free (contains no 4-cycle).
4552    ///
4553    /// # Examples
4554    ///
4555    /// ```
4556    /// use rust_igraph::Graph;
4557    ///
4558    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4559    /// assert!(g.is_c4_free().unwrap());
4560    /// ```
4561    pub fn is_c4_free(&self) -> IgraphResult<bool> {
4562        crate::algorithms::properties::is_c4_free::is_c4_free(self)
4563    }
4564
4565    /// Check whether this graph is C5-free.
4566    ///
4567    /// # Examples
4568    ///
4569    /// ```
4570    /// use rust_igraph::Graph;
4571    ///
4572    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4573    /// assert!(g.is_c5_free().unwrap());
4574    /// ```
4575    pub fn is_c5_free(&self) -> IgraphResult<bool> {
4576        crate::algorithms::properties::is_c5_free::is_c5_free(self)
4577    }
4578
4579    /// Check whether this graph is a cactus graph.
4580    ///
4581    /// # Examples
4582    ///
4583    /// ```
4584    /// use rust_igraph::Graph;
4585    ///
4586    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4587    /// assert!(g.is_cactus_graph().unwrap());
4588    /// ```
4589    pub fn is_cactus_graph(&self) -> IgraphResult<bool> {
4590        crate::algorithms::properties::is_cactus::is_cactus_graph(self)
4591    }
4592
4593    /// Check whether this graph is a caterpillar.
4594    ///
4595    /// # Examples
4596    ///
4597    /// ```
4598    /// use rust_igraph::Graph;
4599    ///
4600    /// let g = Graph::from_edges(&[(0,1), (1,2), (1,3)], false, None).unwrap();
4601    /// assert!(g.is_caterpillar().unwrap());
4602    /// ```
4603    pub fn is_caterpillar(&self) -> IgraphResult<bool> {
4604        crate::algorithms::properties::is_caterpillar::is_caterpillar(self)
4605    }
4606
4607    /// Check whether this graph is a chain graph.
4608    ///
4609    /// # Examples
4610    ///
4611    /// ```
4612    /// use rust_igraph::Graph;
4613    ///
4614    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,3)], false, None).unwrap();
4615    /// assert!(g.is_chain_graph().unwrap());
4616    /// ```
4617    pub fn is_chain_graph(&self) -> IgraphResult<bool> {
4618        crate::algorithms::properties::is_chain_graph::is_chain_graph(self)
4619    }
4620
4621    /// Check whether this graph is chordal bipartite.
4622    ///
4623    /// # Examples
4624    ///
4625    /// ```
4626    /// use rust_igraph::Graph;
4627    ///
4628    /// let g = Graph::from_edges(&[(0,2), (1,2)], false, None).unwrap();
4629    /// assert!(g.is_chordal_bipartite().unwrap());
4630    /// ```
4631    pub fn is_chordal_bipartite(&self) -> IgraphResult<bool> {
4632        crate::algorithms::properties::is_chordal_bipartite::is_chordal_bipartite(self)
4633    }
4634
4635    /// Check whether this graph is claw-free.
4636    ///
4637    /// # Examples
4638    ///
4639    /// ```
4640    /// use rust_igraph::Graph;
4641    ///
4642    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4643    /// assert!(g.is_claw_free().unwrap());
4644    /// ```
4645    pub fn is_claw_free(&self) -> IgraphResult<bool> {
4646        crate::algorithms::properties::is_claw_free::is_claw_free(self)
4647    }
4648
4649    /// Check whether this graph is a cluster graph (disjoint union of cliques).
4650    ///
4651    /// # Examples
4652    ///
4653    /// ```
4654    /// use rust_igraph::Graph;
4655    ///
4656    /// let g = Graph::from_edges(&[(0,1)], false, None).unwrap();
4657    /// assert!(g.is_cluster_graph().unwrap());
4658    /// ```
4659    pub fn is_cluster_graph(&self) -> IgraphResult<bool> {
4660        crate::algorithms::properties::is_cluster::is_cluster_graph(self)
4661    }
4662
4663    /// Check whether this graph is co-bipartite.
4664    ///
4665    /// # Examples
4666    ///
4667    /// ```
4668    /// use rust_igraph::Graph;
4669    ///
4670    /// let g = Graph::from_edges(&[(0,1)], false, None).unwrap();
4671    /// assert!(g.is_co_bipartite().unwrap());
4672    /// ```
4673    pub fn is_co_bipartite(&self) -> IgraphResult<bool> {
4674        crate::algorithms::properties::is_co_bipartite::is_co_bipartite(self)
4675    }
4676
4677    /// Check whether this graph is co-chordal.
4678    ///
4679    /// # Examples
4680    ///
4681    /// ```
4682    /// use rust_igraph::Graph;
4683    ///
4684    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4685    /// assert!(g.is_co_chordal().unwrap());
4686    /// ```
4687    pub fn is_co_chordal(&self) -> IgraphResult<bool> {
4688        crate::algorithms::properties::is_co_chordal::is_co_chordal(self)
4689    }
4690
4691    /// Check whether this graph is a complete graph.
4692    ///
4693    /// # Examples
4694    ///
4695    /// ```
4696    /// use rust_igraph::Graph;
4697    ///
4698    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4699    /// assert!(g.is_complete().unwrap());
4700    /// ```
4701    pub fn is_complete(&self) -> IgraphResult<bool> {
4702        crate::algorithms::properties::is_complete::is_complete(self)
4703    }
4704
4705    /// Check whether this graph is a complete bipartite graph.
4706    ///
4707    /// # Examples
4708    ///
4709    /// ```
4710    /// use rust_igraph::Graph;
4711    ///
4712    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,2), (1,3)], false, None).unwrap();
4713    /// assert!(g.is_complete_bipartite().unwrap());
4714    /// ```
4715    pub fn is_complete_bipartite(&self) -> IgraphResult<bool> {
4716        crate::algorithms::properties::is_complete_bipartite::is_complete_bipartite(self)
4717    }
4718
4719    /// Check whether this graph is cricket-free.
4720    ///
4721    /// # Examples
4722    ///
4723    /// ```
4724    /// use rust_igraph::Graph;
4725    ///
4726    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4727    /// assert!(g.is_cricket_free().unwrap());
4728    /// ```
4729    pub fn is_cricket_free(&self) -> IgraphResult<bool> {
4730        crate::algorithms::properties::is_cricket_free::is_cricket_free(self)
4731    }
4732
4733    /// Check whether this graph is cubic (3-regular).
4734    ///
4735    /// # Examples
4736    ///
4737    /// ```
4738    /// use rust_igraph::{Graph, full_graph};
4739    ///
4740    /// let g = full_graph(4, false, false).unwrap();
4741    /// assert!(g.is_cubic().unwrap());
4742    /// ```
4743    pub fn is_cubic(&self) -> IgraphResult<bool> {
4744        crate::algorithms::properties::is_cubic::is_cubic(self)
4745    }
4746
4747    /// Check whether this graph is a cycle.
4748    ///
4749    /// # Examples
4750    ///
4751    /// ```
4752    /// use rust_igraph::{Graph, cycle_graph};
4753    ///
4754    /// let g = cycle_graph(5, false, false).unwrap();
4755    /// assert!(g.is_cycle().unwrap());
4756    /// ```
4757    pub fn is_cycle(&self) -> IgraphResult<bool> {
4758        crate::algorithms::properties::is_cycle::is_cycle(self)
4759    }
4760
4761    /// Check whether this graph is dart-free.
4762    ///
4763    /// # Examples
4764    ///
4765    /// ```
4766    /// use rust_igraph::Graph;
4767    ///
4768    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4769    /// assert!(g.is_dart_free().unwrap());
4770    /// ```
4771    pub fn is_dart_free(&self) -> IgraphResult<bool> {
4772        crate::algorithms::properties::is_dart_free::is_dart_free(self)
4773    }
4774
4775    /// Check whether this graph is diamond-free.
4776    ///
4777    /// # Examples
4778    ///
4779    /// ```
4780    /// use rust_igraph::Graph;
4781    ///
4782    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4783    /// assert!(g.is_diamond_free().unwrap());
4784    /// ```
4785    pub fn is_diamond_free(&self) -> IgraphResult<bool> {
4786        crate::algorithms::properties::is_diamond_free::is_diamond_free(self)
4787    }
4788
4789    /// Check whether this graph is distance-hereditary.
4790    ///
4791    /// # Examples
4792    ///
4793    /// ```
4794    /// use rust_igraph::Graph;
4795    ///
4796    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4797    /// assert!(g.is_distance_hereditary().unwrap());
4798    /// ```
4799    pub fn is_distance_hereditary(&self) -> IgraphResult<bool> {
4800        crate::algorithms::properties::is_distance_hereditary::is_distance_hereditary(self)
4801    }
4802
4803    /// Check whether this graph is Eulerian.
4804    ///
4805    /// # Examples
4806    ///
4807    /// ```
4808    /// use rust_igraph::Graph;
4809    ///
4810    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4811    /// let class = g.is_eulerian().unwrap();
4812    /// assert!(class.has_cycle);
4813    /// ```
4814    pub fn is_eulerian(
4815        &self,
4816    ) -> IgraphResult<crate::algorithms::paths::eulerian::EulerianClassification> {
4817        crate::algorithms::paths::eulerian::is_eulerian(self)
4818    }
4819
4820    /// Check whether this graph is fork-free.
4821    ///
4822    /// # Examples
4823    ///
4824    /// ```
4825    /// use rust_igraph::Graph;
4826    ///
4827    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4828    /// assert!(g.is_fork_free().unwrap());
4829    /// ```
4830    pub fn is_fork_free(&self) -> IgraphResult<bool> {
4831        crate::algorithms::properties::is_fork_free::is_fork_free(self)
4832    }
4833
4834    /// Check whether this graph is gem-free.
4835    ///
4836    /// # Examples
4837    ///
4838    /// ```
4839    /// use rust_igraph::Graph;
4840    ///
4841    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4842    /// assert!(g.is_gem_free().unwrap());
4843    /// ```
4844    pub fn is_gem_free(&self) -> IgraphResult<bool> {
4845        crate::algorithms::properties::is_gem_free::is_gem_free(self)
4846    }
4847
4848    /// Check whether this graph is geodetic.
4849    ///
4850    /// # Examples
4851    ///
4852    /// ```
4853    /// use rust_igraph::Graph;
4854    ///
4855    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4856    /// assert!(g.is_geodetic().unwrap());
4857    /// ```
4858    pub fn is_geodetic(&self) -> IgraphResult<bool> {
4859        crate::algorithms::properties::is_geodetic::is_geodetic(self)
4860    }
4861
4862    /// Check whether this graph is house-free.
4863    ///
4864    /// # Examples
4865    ///
4866    /// ```
4867    /// use rust_igraph::Graph;
4868    ///
4869    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4870    /// assert!(g.is_house_free().unwrap());
4871    /// ```
4872    pub fn is_house_free(&self) -> IgraphResult<bool> {
4873        crate::algorithms::properties::is_house_free::is_house_free(self)
4874    }
4875
4876    /// Check whether this graph is k-degenerate.
4877    ///
4878    /// # Examples
4879    ///
4880    /// ```
4881    /// use rust_igraph::Graph;
4882    ///
4883    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4884    /// assert!(g.is_k_degenerate(1).unwrap());
4885    /// ```
4886    pub fn is_k_degenerate(&self, k: u32) -> IgraphResult<bool> {
4887        crate::algorithms::properties::is_k_degenerate::is_k_degenerate(self, k)
4888    }
4889
4890    /// Check whether this graph is a lobster.
4891    ///
4892    /// # Examples
4893    ///
4894    /// ```
4895    /// use rust_igraph::Graph;
4896    ///
4897    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4898    /// assert!(g.is_lobster().unwrap());
4899    /// ```
4900    pub fn is_lobster(&self) -> IgraphResult<bool> {
4901        crate::algorithms::properties::is_lobster::is_lobster(self)
4902    }
4903
4904    /// Check whether this graph is net-free.
4905    ///
4906    /// # Examples
4907    ///
4908    /// ```
4909    /// use rust_igraph::Graph;
4910    ///
4911    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4912    /// assert!(g.is_net_free().unwrap());
4913    /// ```
4914    pub fn is_net_free(&self) -> IgraphResult<bool> {
4915        crate::algorithms::properties::is_net_free::is_net_free(self)
4916    }
4917
4918    /// Check whether this graph is P5-free.
4919    ///
4920    /// # Examples
4921    ///
4922    /// ```
4923    /// use rust_igraph::Graph;
4924    ///
4925    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4926    /// assert!(g.is_p5_free().unwrap());
4927    /// ```
4928    pub fn is_p5_free(&self) -> IgraphResult<bool> {
4929        crate::algorithms::properties::is_p5_free::is_p5_free(self)
4930    }
4931
4932    /// Check whether this graph is a path.
4933    ///
4934    /// # Examples
4935    ///
4936    /// ```
4937    /// use rust_igraph::Graph;
4938    ///
4939    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4940    /// assert!(g.is_path().unwrap());
4941    /// ```
4942    pub fn is_path(&self) -> IgraphResult<bool> {
4943        crate::algorithms::properties::is_path::is_path(self)
4944    }
4945
4946    /// Check whether this graph is paw-free.
4947    ///
4948    /// # Examples
4949    ///
4950    /// ```
4951    /// use rust_igraph::Graph;
4952    ///
4953    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4954    /// assert!(g.is_paw_free().unwrap());
4955    /// ```
4956    pub fn is_paw_free(&self) -> IgraphResult<bool> {
4957        crate::algorithms::properties::is_paw_free::is_paw_free(self)
4958    }
4959
4960    /// Check whether this graph is a proper interval graph.
4961    ///
4962    /// # Examples
4963    ///
4964    /// ```
4965    /// use rust_igraph::Graph;
4966    ///
4967    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4968    /// assert!(g.is_proper_interval().unwrap());
4969    /// ```
4970    pub fn is_proper_interval(&self) -> IgraphResult<bool> {
4971        crate::algorithms::properties::is_proper_interval::is_proper_interval(self)
4972    }
4973
4974    /// Check whether this graph is a pseudo-forest.
4975    ///
4976    /// # Examples
4977    ///
4978    /// ```
4979    /// use rust_igraph::Graph;
4980    ///
4981    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4982    /// assert!(g.is_pseudo_forest().unwrap());
4983    /// ```
4984    pub fn is_pseudo_forest(&self) -> IgraphResult<bool> {
4985        crate::algorithms::properties::is_pseudo_forest::is_pseudo_forest(self)
4986    }
4987
4988    /// Check whether this graph is Ptolemaic.
4989    ///
4990    /// # Examples
4991    ///
4992    /// ```
4993    /// use rust_igraph::Graph;
4994    ///
4995    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4996    /// assert!(g.is_ptolemaic().unwrap());
4997    /// ```
4998    pub fn is_ptolemaic(&self) -> IgraphResult<bool> {
4999        crate::algorithms::properties::is_ptolemaic::is_ptolemaic(self)
5000    }
5001
5002    /// Check whether this graph is self-complementary.
5003    ///
5004    /// # Examples
5005    ///
5006    /// ```
5007    /// use rust_igraph::Graph;
5008    ///
5009    /// // P_4 (path on 4 vertices) is self-complementary
5010    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5011    /// assert!(g.is_self_complementary().unwrap());
5012    /// ```
5013    pub fn is_self_complementary(&self) -> IgraphResult<bool> {
5014        crate::algorithms::properties::is_self_complementary::is_self_complementary(self)
5015    }
5016
5017    /// Check whether this graph is semicomplete.
5018    ///
5019    /// # Examples
5020    ///
5021    /// ```
5022    /// use rust_igraph::Graph;
5023    ///
5024    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], true, None).unwrap();
5025    /// assert!(g.is_semicomplete().unwrap());
5026    /// ```
5027    pub fn is_semicomplete(&self) -> IgraphResult<bool> {
5028        crate::algorithms::properties::is_semicomplete::is_semicomplete(self)
5029    }
5030
5031    /// Check whether this graph is a spider.
5032    ///
5033    /// # Examples
5034    ///
5035    /// ```
5036    /// use rust_igraph::Graph;
5037    ///
5038    /// let g = Graph::from_edges(&[(0,1), (0,2), (0,3)], false, None).unwrap();
5039    /// assert!(g.is_spider().unwrap());
5040    /// ```
5041    pub fn is_spider(&self) -> IgraphResult<bool> {
5042        crate::algorithms::properties::is_spider::is_spider(self)
5043    }
5044
5045    /// Check whether this graph is a split graph.
5046    ///
5047    /// # Examples
5048    ///
5049    /// ```
5050    /// use rust_igraph::Graph;
5051    ///
5052    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2), (2,3)], false, None).unwrap();
5053    /// assert!(g.is_split_graph().unwrap());
5054    /// ```
5055    pub fn is_split_graph(&self) -> IgraphResult<bool> {
5056        crate::algorithms::properties::is_split::is_split_graph(self)
5057    }
5058
5059    /// Check whether this graph is a star.
5060    ///
5061    /// # Examples
5062    ///
5063    /// ```
5064    /// use rust_igraph::Graph;
5065    ///
5066    /// let g = Graph::from_edges(&[(0,1), (0,2), (0,3)], false, None).unwrap();
5067    /// assert!(g.is_star().unwrap());
5068    /// ```
5069    pub fn is_star(&self) -> IgraphResult<bool> {
5070        crate::algorithms::properties::is_star::is_star(self)
5071    }
5072
5073    /// Check whether this graph is strongly chordal.
5074    ///
5075    /// # Examples
5076    ///
5077    /// ```
5078    /// use rust_igraph::Graph;
5079    ///
5080    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5081    /// assert!(g.is_strongly_chordal().unwrap());
5082    /// ```
5083    pub fn is_strongly_chordal(&self) -> IgraphResult<bool> {
5084        crate::algorithms::properties::is_strongly_chordal::is_strongly_chordal(self)
5085    }
5086
5087    /// Check whether this graph is a threshold graph.
5088    ///
5089    /// # Examples
5090    ///
5091    /// ```
5092    /// use rust_igraph::Graph;
5093    ///
5094    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
5095    /// assert!(g.is_threshold_graph().unwrap());
5096    /// ```
5097    pub fn is_threshold_graph(&self) -> IgraphResult<bool> {
5098        crate::algorithms::properties::is_threshold::is_threshold_graph(self)
5099    }
5100
5101    /// Check whether this graph is a tournament.
5102    ///
5103    /// # Examples
5104    ///
5105    /// ```
5106    /// use rust_igraph::Graph;
5107    ///
5108    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], true, None).unwrap();
5109    /// assert!(g.is_tournament().unwrap());
5110    /// ```
5111    pub fn is_tournament(&self) -> IgraphResult<bool> {
5112        crate::algorithms::properties::is_tournament::is_tournament(self)
5113    }
5114
5115    /// Check whether this graph is triangle-free.
5116    ///
5117    /// # Examples
5118    ///
5119    /// ```
5120    /// use rust_igraph::Graph;
5121    ///
5122    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5123    /// assert!(g.is_triangle_free().unwrap());
5124    /// ```
5125    pub fn is_triangle_free(&self) -> IgraphResult<bool> {
5126        crate::algorithms::properties::is_triangle_free::is_triangle_free(self)
5127    }
5128
5129    /// Check whether this graph is trivially perfect.
5130    ///
5131    /// # Examples
5132    ///
5133    /// ```
5134    /// use rust_igraph::Graph;
5135    ///
5136    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
5137    /// assert!(g.is_trivially_perfect().unwrap());
5138    /// ```
5139    pub fn is_trivially_perfect(&self) -> IgraphResult<bool> {
5140        crate::algorithms::properties::is_trivially_perfect::is_trivially_perfect(self)
5141    }
5142
5143    /// Check whether this graph is unicyclic.
5144    ///
5145    /// # Examples
5146    ///
5147    /// ```
5148    /// use rust_igraph::Graph;
5149    ///
5150    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5151    /// assert!(g.is_unicyclic().unwrap());
5152    /// ```
5153    pub fn is_unicyclic(&self) -> IgraphResult<bool> {
5154        crate::algorithms::properties::is_unicyclic::is_unicyclic(self)
5155    }
5156
5157    /// Check whether this graph is a wheel.
5158    ///
5159    /// # Examples
5160    ///
5161    /// ```
5162    /// use rust_igraph::Graph;
5163    ///
5164    /// // W_4: center 0 connected to rim {1,2,3}, rim forms a cycle
5165    /// let g = Graph::from_edges(
5166    ///     &[(0,1), (0,2), (0,3), (1,2), (2,3), (3,1)],
5167    ///     false, None
5168    /// ).unwrap();
5169    /// assert!(g.is_wheel().unwrap());
5170    /// ```
5171    pub fn is_wheel(&self) -> IgraphResult<bool> {
5172        crate::algorithms::properties::is_wheel::is_wheel(self)
5173    }
5174
5175    /// Check whether the graph is regular (all vertices have the same degree).
5176    ///
5177    /// # Examples
5178    ///
5179    /// ```
5180    /// use rust_igraph::{Graph, full_graph};
5181    ///
5182    /// let g = full_graph(4, false, false).unwrap();
5183    /// assert!(g.is_regular().unwrap());
5184    /// ```
5185    pub fn is_regular(&self) -> IgraphResult<bool> {
5186        crate::algorithms::properties::is_regular::is_regular(self)
5187    }
5188
5189    /// Check whether the graph is strongly regular, returning parameters if so.
5190    ///
5191    /// # Examples
5192    ///
5193    /// ```
5194    /// use rust_igraph::{Graph, cycle_graph};
5195    ///
5196    /// let g = cycle_graph(5, false, false).unwrap();
5197    /// let result = g.is_strongly_regular().unwrap();
5198    /// assert!(result.is_some());
5199    /// ```
5200    pub fn is_strongly_regular(
5201        &self,
5202    ) -> IgraphResult<
5203        Option<crate::algorithms::properties::is_strongly_regular::StronglyRegularParams>,
5204    > {
5205        crate::algorithms::properties::is_strongly_regular::is_strongly_regular(self)
5206    }
5207
5208    /// Check whether the graph is weakly chordal.
5209    ///
5210    /// # Examples
5211    ///
5212    /// ```
5213    /// use rust_igraph::Graph;
5214    ///
5215    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
5216    /// assert!(g.is_weakly_chordal().unwrap());
5217    /// ```
5218    pub fn is_weakly_chordal(&self) -> IgraphResult<bool> {
5219        crate::algorithms::properties::is_weakly_chordal::is_weakly_chordal(self)
5220    }
5221
5222    /// Check whether the graph is well-covered.
5223    ///
5224    /// # Examples
5225    ///
5226    /// ```
5227    /// use rust_igraph::{Graph, full_graph};
5228    ///
5229    /// let g = full_graph(4, false, false).unwrap();
5230    /// assert!(g.is_well_covered().unwrap());
5231    /// ```
5232    pub fn is_well_covered(&self) -> IgraphResult<bool> {
5233        crate::algorithms::properties::is_well_covered::is_well_covered(self)
5234    }
5235
5236    /// Check whether the graph is a windmill graph, returning (k, n) if so.
5237    ///
5238    /// # Examples
5239    ///
5240    /// ```
5241    /// use rust_igraph::{Graph, full_graph};
5242    ///
5243    /// let g = full_graph(3, false, false).unwrap();
5244    /// let result = g.is_windmill().unwrap();
5245    /// assert!(result.is_some());
5246    /// ```
5247    pub fn is_windmill(&self) -> IgraphResult<Option<(u32, u32)>> {
5248        crate::algorithms::properties::is_windmill::is_windmill(self)
5249    }
5250
5251    /// Check whether the graph is complete multipartite, returning
5252    /// partition sizes if so.
5253    ///
5254    /// # Examples
5255    ///
5256    /// ```
5257    /// use rust_igraph::{Graph, full_graph};
5258    ///
5259    /// let g = full_graph(3, false, false).unwrap();
5260    /// let result = g.is_complete_multipartite().unwrap();
5261    /// assert!(result.is_some());
5262    /// ```
5263    pub fn is_complete_multipartite(&self) -> IgraphResult<Option<Vec<u32>>> {
5264        crate::algorithms::properties::is_complete_multipartite::is_complete_multipartite(self)
5265    }
5266
5267    /// Check whether this graph satisfies Dirac's condition for Hamiltonicity.
5268    ///
5269    /// # Examples
5270    ///
5271    /// ```
5272    /// use rust_igraph::{Graph, full_graph};
5273    ///
5274    /// let g = full_graph(5, false, false).unwrap();
5275    /// assert!(g.satisfies_dirac().unwrap());
5276    /// ```
5277    pub fn satisfies_dirac(&self) -> IgraphResult<bool> {
5278        crate::algorithms::properties::satisfies_dirac::satisfies_dirac(self)
5279    }
5280
5281    /// Check whether this graph satisfies Ore's condition for Hamiltonicity.
5282    ///
5283    /// # Examples
5284    ///
5285    /// ```
5286    /// use rust_igraph::{Graph, full_graph};
5287    ///
5288    /// let g = full_graph(5, false, false).unwrap();
5289    /// assert!(g.satisfies_ore().unwrap());
5290    /// ```
5291    pub fn satisfies_ore(&self) -> IgraphResult<bool> {
5292        crate::algorithms::properties::satisfies_ore::satisfies_ore(self)
5293    }
5294
5295    // ── Centrality variants ───────────────────────────────────────
5296
5297    /// Compute edge betweenness centrality.
5298    ///
5299    /// # Examples
5300    ///
5301    /// ```
5302    /// use rust_igraph::Graph;
5303    ///
5304    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5305    /// let eb = g.edge_betweenness().unwrap();
5306    /// assert_eq!(eb.len(), 3);
5307    /// ```
5308    pub fn edge_betweenness(&self) -> IgraphResult<Vec<f64>> {
5309        crate::algorithms::properties::edge_betweenness::edge_betweenness(self)
5310    }
5311
5312    /// Compute betweenness centrality with a distance cutoff.
5313    ///
5314    /// # Examples
5315    ///
5316    /// ```
5317    /// use rust_igraph::Graph;
5318    ///
5319    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5320    /// let bc = g.betweenness_cutoff(2).unwrap();
5321    /// assert_eq!(bc.len(), 4);
5322    /// ```
5323    pub fn betweenness_cutoff(&self, cutoff: u32) -> IgraphResult<Vec<f64>> {
5324        crate::algorithms::properties::betweenness_cutoff::betweenness_cutoff(self, cutoff)
5325    }
5326
5327    /// Compute weighted betweenness centrality.
5328    ///
5329    /// # Examples
5330    ///
5331    /// ```
5332    /// use rust_igraph::Graph;
5333    ///
5334    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5335    /// let bc = g.betweenness_weighted(&[1.0, 2.0]).unwrap();
5336    /// assert_eq!(bc.len(), 3);
5337    /// ```
5338    pub fn betweenness_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5339        crate::algorithms::properties::betweenness_weighted::betweenness_weighted(self, weights)
5340    }
5341
5342    /// Compute weighted closeness centrality.
5343    ///
5344    /// # Examples
5345    ///
5346    /// ```
5347    /// use rust_igraph::Graph;
5348    ///
5349    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5350    /// let cc = g.closeness_weighted(&[1.0, 2.0]).unwrap();
5351    /// assert_eq!(cc.len(), 3);
5352    /// ```
5353    pub fn closeness_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<Option<f64>>> {
5354        crate::algorithms::properties::closeness_weighted::closeness_weighted(self, weights)
5355    }
5356
5357    /// Compute edge betweenness centrality with a distance cutoff.
5358    ///
5359    /// # Examples
5360    ///
5361    /// ```
5362    /// use rust_igraph::Graph;
5363    ///
5364    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5365    /// let eb = g.edge_betweenness_cutoff(2).unwrap();
5366    /// assert_eq!(eb.len(), 3);
5367    /// ```
5368    pub fn edge_betweenness_cutoff(&self, cutoff: u32) -> IgraphResult<Vec<f64>> {
5369        crate::algorithms::properties::edge_betweenness_cutoff::edge_betweenness_cutoff(
5370            self, cutoff,
5371        )
5372    }
5373
5374    /// Compute weighted edge betweenness centrality.
5375    ///
5376    /// # Examples
5377    ///
5378    /// ```
5379    /// use rust_igraph::Graph;
5380    ///
5381    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5382    /// let eb = g.edge_betweenness_weighted(&[1.0, 2.0]).unwrap();
5383    /// assert_eq!(eb.len(), 2);
5384    /// ```
5385    pub fn edge_betweenness_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5386        crate::algorithms::properties::edge_betweenness_weighted::edge_betweenness_weighted(
5387            self, weights,
5388        )
5389    }
5390
5391    /// Compute weighted harmonic centrality.
5392    ///
5393    /// # Examples
5394    ///
5395    /// ```
5396    /// use rust_igraph::Graph;
5397    ///
5398    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5399    /// let hc = g.harmonic_centrality_weighted(&[1.0, 2.0]).unwrap();
5400    /// assert_eq!(hc.len(), 3);
5401    /// ```
5402    pub fn harmonic_centrality_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5403        crate::algorithms::properties::harmonic_weighted::harmonic_centrality_weighted(
5404            self, weights,
5405        )
5406    }
5407
5408    /// Compute weighted `PageRank`.
5409    ///
5410    /// # Examples
5411    ///
5412    /// ```
5413    /// use rust_igraph::Graph;
5414    ///
5415    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5416    /// let pr = g.pagerank_weighted(&[1.0, 2.0]).unwrap();
5417    /// assert_eq!(pr.len(), 3);
5418    /// ```
5419    pub fn pagerank_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5420        crate::algorithms::properties::pagerank_weighted::pagerank_weighted(self, weights)
5421    }
5422
5423    // ── Connectivity & structural ─────────────────────────────────
5424
5425    /// Compute the cohesive block structure.
5426    ///
5427    /// # Examples
5428    ///
5429    /// ```
5430    /// use rust_igraph::Graph;
5431    ///
5432    /// let g = Graph::from_edges(
5433    ///     &[(0,1), (1,2), (2,0), (2,3), (3,4), (4,5), (5,3)],
5434    ///     false, None
5435    /// ).unwrap();
5436    /// let blocks = g.cohesive_blocks().unwrap();
5437    /// assert!(!blocks.blocks.is_empty());
5438    /// ```
5439    pub fn cohesive_blocks(
5440        &self,
5441    ) -> IgraphResult<crate::algorithms::connectivity::cohesive_blocks::CohesiveBlocks> {
5442        crate::algorithms::connectivity::cohesive_blocks::cohesive_blocks(self)
5443    }
5444
5445    /// Count reachable vertices from each vertex.
5446    ///
5447    /// # Examples
5448    ///
5449    /// ```
5450    /// use rust_igraph::Graph;
5451    ///
5452    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5453    /// let counts = g.count_reachable().unwrap();
5454    /// assert_eq!(counts, vec![3, 3, 3]);
5455    /// ```
5456    pub fn count_reachable(&self) -> IgraphResult<Vec<u32>> {
5457        crate::algorithms::connectivity::reachability::count_reachable(self)
5458    }
5459
5460    /// Compute the reachability matrix.
5461    ///
5462    /// # Examples
5463    ///
5464    /// ```
5465    /// use rust_igraph::Graph;
5466    ///
5467    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5468    /// let mat = g.reachability_matrix().unwrap();
5469    /// assert!(mat[0][2]);
5470    /// assert!(!mat[2][0]);
5471    /// ```
5472    pub fn reachability_matrix(&self) -> IgraphResult<Vec<Vec<bool>>> {
5473        crate::algorithms::connectivity::reachability_matrix::reachability_matrix(self)
5474    }
5475
5476    /// Compute the transitive closure.
5477    ///
5478    /// # Examples
5479    ///
5480    /// ```
5481    /// use rust_igraph::Graph;
5482    ///
5483    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5484    /// let tc = g.transitive_closure().unwrap();
5485    /// assert!(tc.has_edge(0, 2));
5486    /// ```
5487    pub fn transitive_closure(&self) -> IgraphResult<Graph> {
5488        crate::algorithms::connectivity::transitive_closure::transitive_closure(self)
5489    }
5490
5491    // ── Flow & cuts ───────────────────────────────────────────────
5492
5493    /// Compute the global minimum cut.
5494    ///
5495    /// # Examples
5496    ///
5497    /// ```
5498    /// use rust_igraph::Graph;
5499    ///
5500    /// let g = Graph::from_edges(
5501    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None
5502    /// ).unwrap();
5503    /// let mc = g.mincut(None).unwrap();
5504    /// assert!(mc.value >= 2.0 - 1e-10);
5505    /// ```
5506    pub fn mincut(
5507        &self,
5508        capacity: Option<&[f64]>,
5509    ) -> IgraphResult<crate::algorithms::flow::mincut::Mincut> {
5510        crate::algorithms::flow::mincut::mincut(self, capacity)
5511    }
5512
5513    /// Compute the global minimum cut value.
5514    ///
5515    /// # Examples
5516    ///
5517    /// ```
5518    /// use rust_igraph::Graph;
5519    ///
5520    /// let g = Graph::from_edges(
5521    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None
5522    /// ).unwrap();
5523    /// let val = g.mincut_value(None).unwrap();
5524    /// assert!(val >= 2.0 - 1e-10);
5525    /// ```
5526    pub fn mincut_value(&self, capacity: Option<&[f64]>) -> IgraphResult<f64> {
5527        crate::algorithms::flow::mincut_value::mincut_value(self, capacity)
5528    }
5529
5530    /// Compute the Gomory-Hu tree.
5531    ///
5532    /// # Examples
5533    ///
5534    /// ```
5535    /// use rust_igraph::Graph;
5536    ///
5537    /// let g = Graph::from_edges(
5538    ///     &[(0,1), (0,2), (1,2), (1,3)], false, None
5539    /// ).unwrap();
5540    /// let tree = g.gomory_hu_tree(None).unwrap();
5541    /// assert_eq!(tree.tree.vcount(), 4);
5542    /// ```
5543    pub fn gomory_hu_tree(
5544        &self,
5545        capacity: Option<&[f64]>,
5546    ) -> IgraphResult<crate::algorithms::flow::gomory_hu_tree::GomoryHuTree> {
5547        crate::algorithms::flow::gomory_hu_tree::gomory_hu_tree(self, capacity)
5548    }
5549
5550    /// Enumerate all minimum s-t cuts.
5551    ///
5552    /// # Examples
5553    ///
5554    /// ```
5555    /// use rust_igraph::Graph;
5556    ///
5557    /// let g = Graph::from_edges(
5558    ///     &[(0,1), (0,2), (1,3), (2,3)], true, None
5559    /// ).unwrap();
5560    /// let cuts = g.all_st_cuts(0, 3).unwrap();
5561    /// assert!(!cuts.cuts.is_empty());
5562    /// ```
5563    pub fn all_st_cuts(
5564        &self,
5565        source: VertexId,
5566        target: VertexId,
5567    ) -> IgraphResult<crate::algorithms::flow::all_st_cuts::StCuts> {
5568        crate::algorithms::flow::all_st_cuts::all_st_cuts(self, source, target)
5569    }
5570
5571    /// Count edge-disjoint paths between two vertices.
5572    ///
5573    /// # Examples
5574    ///
5575    /// ```
5576    /// use rust_igraph::Graph;
5577    ///
5578    /// let g = Graph::from_edges(
5579    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None
5580    /// ).unwrap();
5581    /// let count = g.edge_disjoint_paths(0, 3).unwrap();
5582    /// assert_eq!(count, 2);
5583    /// ```
5584    pub fn edge_disjoint_paths(&self, source: VertexId, target: VertexId) -> IgraphResult<i64> {
5585        crate::algorithms::flow::edge_disjoint_paths::edge_disjoint_paths(self, source, target)
5586    }
5587
5588    // ── Paths & distances ─────────────────────────────────────────
5589
5590    /// Compute BFS distances from a source vertex.
5591    ///
5592    /// # Examples
5593    ///
5594    /// ```
5595    /// use rust_igraph::Graph;
5596    ///
5597    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5598    /// let dist = g.distances(0).unwrap();
5599    /// assert_eq!(dist[3], Some(3));
5600    /// ```
5601    pub fn distances(&self, source: VertexId) -> IgraphResult<Vec<Option<u32>>> {
5602        crate::algorithms::paths::distances::distances(self, source)
5603    }
5604
5605    /// Compute an Eulerian path if one exists.
5606    ///
5607    /// # Examples
5608    ///
5609    /// ```
5610    /// use rust_igraph::Graph;
5611    ///
5612    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5613    /// let path = g.eulerian_path().unwrap();
5614    /// assert!(path.is_some());
5615    /// ```
5616    pub fn eulerian_path(&self) -> IgraphResult<Option<Vec<u32>>> {
5617        crate::algorithms::paths::eulerian_construct::eulerian_path(self)
5618    }
5619
5620    /// Compute mean geodesic distance.
5621    ///
5622    /// # Examples
5623    ///
5624    /// ```
5625    /// use rust_igraph::Graph;
5626    ///
5627    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5628    /// let d = g.mean_distance().unwrap();
5629    /// assert!(d.is_some());
5630    /// ```
5631    pub fn mean_distance(&self) -> IgraphResult<Option<f64>> {
5632        crate::algorithms::properties::basic::mean_distance(self)
5633    }
5634
5635    /// Topological sort of a directed graph.
5636    ///
5637    /// # Examples
5638    ///
5639    /// ```
5640    /// use rust_igraph::Graph;
5641    ///
5642    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5643    /// let order = g.topological_sorting().unwrap();
5644    /// assert_eq!(order, vec![0, 1, 2]);
5645    /// ```
5646    pub fn topological_sorting(&self) -> IgraphResult<Vec<VertexId>> {
5647        crate::algorithms::properties::topological_sorting::topological_sorting(
5648            self,
5649            crate::algorithms::paths::dijkstra::DijkstraMode::Out,
5650        )
5651    }
5652
5653    /// List all triangles in the graph.
5654    ///
5655    /// # Examples
5656    ///
5657    /// ```
5658    /// use rust_igraph::Graph;
5659    ///
5660    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5661    /// let tris = g.list_triangles().unwrap();
5662    /// assert_eq!(tris.len(), 1);
5663    /// ```
5664    pub fn list_triangles(&self) -> IgraphResult<Vec<(u32, u32, u32)>> {
5665        crate::algorithms::properties::list_triangles::list_triangles(self)
5666    }
5667
5668    /// Compute the degree distribution.
5669    ///
5670    /// # Examples
5671    ///
5672    /// ```
5673    /// use rust_igraph::Graph;
5674    ///
5675    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5676    /// let dist = g.degree_distribution().unwrap();
5677    /// assert!(!dist.is_empty());
5678    /// ```
5679    pub fn degree_distribution(&self) -> IgraphResult<Vec<u32>> {
5680        crate::algorithms::properties::degree_distribution::degree_distribution(
5681            self,
5682            crate::algorithms::properties::degree::DegreeMode::All,
5683        )
5684    }
5685
5686    /// Get the edge list as (source, target) pairs.
5687    ///
5688    /// # Examples
5689    ///
5690    /// ```
5691    /// use rust_igraph::Graph;
5692    ///
5693    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5694    /// let edges = g.get_edgelist().unwrap();
5695    /// assert_eq!(edges.len(), 2);
5696    /// ```
5697    pub fn get_edgelist(&self) -> IgraphResult<Vec<(VertexId, VertexId)>> {
5698        crate::algorithms::properties::edgelist::get_edgelist(self)
5699    }
5700
5701    /// Compute the graph's regularity (degree if regular, None otherwise).
5702    ///
5703    /// # Examples
5704    ///
5705    /// ```
5706    /// use rust_igraph::Graph;
5707    ///
5708    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5709    /// assert_eq!(g.regularity().unwrap(), Some(2));
5710    /// ```
5711    pub fn regularity(&self) -> IgraphResult<Option<u32>> {
5712        crate::algorithms::properties::is_regular::regularity(self)
5713    }
5714
5715    /// Find a cycle in the graph.
5716    ///
5717    /// # Examples
5718    ///
5719    /// ```
5720    /// use rust_igraph::Graph;
5721    ///
5722    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5723    /// let result = g.find_cycle().unwrap();
5724    /// assert!(!result.vertices.is_empty());
5725    /// ```
5726    pub fn find_cycle(&self) -> IgraphResult<crate::algorithms::cycles::CycleResult> {
5727        crate::algorithms::cycles::find_cycle(self, crate::algorithms::cycles::CycleMode::All)
5728    }
5729
5730    /// Compute the joint degree matrix.
5731    ///
5732    /// # Examples
5733    ///
5734    /// ```
5735    /// use rust_igraph::Graph;
5736    ///
5737    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5738    /// let jdm = g.joint_degree_matrix(None).unwrap();
5739    /// assert!(!jdm.is_empty());
5740    /// ```
5741    pub fn joint_degree_matrix(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<Vec<f64>>> {
5742        crate::algorithms::properties::joint_degree_matrix::joint_degree_matrix(self, weights)
5743    }
5744
5745    /// Compute the minimum dominating set.
5746    ///
5747    /// # Examples
5748    ///
5749    /// ```
5750    /// use rust_igraph::Graph;
5751    ///
5752    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5753    /// let dom = g.minimum_dominating_set().unwrap();
5754    /// assert!(!dom.is_empty());
5755    /// ```
5756    pub fn minimum_dominating_set(&self) -> IgraphResult<Vec<VertexId>> {
5757        crate::algorithms::dominating_set::minimum_dominating_set(self)
5758    }
5759
5760    /// Compute the k-th power of this graph.
5761    ///
5762    /// # Examples
5763    ///
5764    /// ```
5765    /// use rust_igraph::Graph;
5766    ///
5767    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5768    /// let g2 = g.graph_power(2).unwrap();
5769    /// assert!(g2.has_edge(0, 2));
5770    /// ```
5771    pub fn graph_power(&self, order: u32) -> IgraphResult<Graph> {
5772        crate::algorithms::operators::connect_neighborhood::graph_power(self, order)
5773    }
5774
5775    /// Compute the trussness of each edge.
5776    ///
5777    /// # Examples
5778    ///
5779    /// ```
5780    /// use rust_igraph::Graph;
5781    ///
5782    /// let g = Graph::from_edges(
5783    ///     &[(0,1), (1,2), (2,0), (2,3)], false, None
5784    /// ).unwrap();
5785    /// let tr = g.trussness().unwrap();
5786    /// assert_eq!(tr.len(), g.ecount());
5787    /// ```
5788    pub fn trussness(&self) -> IgraphResult<Vec<u32>> {
5789        crate::algorithms::properties::trussness::trussness(self)
5790    }
5791
5792    // ── Graph operators ──────────────────────────────────────────────
5793
5794    /// Union of two graphs on the same vertex set.
5795    ///
5796    /// ```
5797    /// use rust_igraph::Graph;
5798    ///
5799    /// let a = Graph::from_edges(&[(0,1)], false, None).unwrap();
5800    /// let b = Graph::from_edges(&[(1,2)], false, None).unwrap();
5801    /// let u = a.union(&b).unwrap();
5802    /// assert_eq!(u.ecount(), 2);
5803    /// ```
5804    pub fn union(&self, other: &Graph) -> IgraphResult<Graph> {
5805        crate::algorithms::operators::union::union(self, other)
5806    }
5807
5808    /// Intersection of two graphs on the same vertex set.
5809    ///
5810    /// ```
5811    /// use rust_igraph::Graph;
5812    ///
5813    /// let a = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5814    /// let b = Graph::from_edges(&[(1,2), (2,3)], false, None).unwrap();
5815    /// let i = a.intersection(&b).unwrap();
5816    /// assert_eq!(i.ecount(), 1);
5817    /// ```
5818    pub fn intersection(&self, other: &Graph) -> IgraphResult<Graph> {
5819        crate::algorithms::operators::intersection::intersection(self, other)
5820    }
5821
5822    /// Edge difference: edges in `self` but not in `other`.
5823    ///
5824    /// ```
5825    /// use rust_igraph::Graph;
5826    ///
5827    /// let a = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5828    /// let b = Graph::from_edges(&[(1,2)], false, None).unwrap();
5829    /// let d = a.difference(&b).unwrap();
5830    /// assert_eq!(d.ecount(), 1);
5831    /// ```
5832    pub fn difference(&self, other: &Graph) -> IgraphResult<Graph> {
5833        crate::algorithms::operators::difference::difference(self, other)
5834    }
5835
5836    /// Disjoint union: concatenate vertex sets, then concatenate edge sets.
5837    ///
5838    /// ```
5839    /// use rust_igraph::Graph;
5840    ///
5841    /// let a = Graph::from_edges(&[(0,1)], false, None).unwrap();
5842    /// let b = Graph::from_edges(&[(0,1)], false, None).unwrap();
5843    /// let d = a.disjoint_union(&b).unwrap();
5844    /// assert_eq!(d.vcount(), 4);
5845    /// assert_eq!(d.ecount(), 2);
5846    /// ```
5847    pub fn disjoint_union(&self, other: &Graph) -> IgraphResult<Graph> {
5848        crate::algorithms::operators::disjoint_union::disjoint_union(self, other)
5849    }
5850
5851    /// Join: disjoint union plus all edges between the two vertex sets.
5852    ///
5853    /// ```
5854    /// use rust_igraph::Graph;
5855    ///
5856    /// let a = Graph::with_vertices(2);
5857    /// let b = Graph::with_vertices(2);
5858    /// let j = a.join(&b).unwrap();
5859    /// assert_eq!(j.vcount(), 4);
5860    /// assert_eq!(j.ecount(), 4);
5861    /// ```
5862    pub fn join(&self, other: &Graph) -> IgraphResult<Graph> {
5863        crate::algorithms::operators::join::join(self, other)
5864    }
5865
5866    /// Compose two graphs (relational composition).
5867    ///
5868    /// ```
5869    /// use rust_igraph::Graph;
5870    ///
5871    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5872    /// let c = g.compose(&g).unwrap();
5873    /// assert!(c.ecount() > 0);
5874    /// ```
5875    pub fn compose(&self, other: &Graph) -> IgraphResult<Graph> {
5876        crate::algorithms::operators::compose::compose(self, other)
5877    }
5878
5879    /// Cartesian product of two graphs.
5880    ///
5881    /// ```
5882    /// use rust_igraph::Graph;
5883    ///
5884    /// let p2 = Graph::from_edges(&[(0,1)], false, None).unwrap();
5885    /// let grid = p2.cartesian_product(&p2).unwrap();
5886    /// assert_eq!(grid.vcount(), 4);
5887    /// ```
5888    pub fn cartesian_product(&self, other: &Graph) -> IgraphResult<Graph> {
5889        crate::algorithms::operators::products::cartesian_product(self, other)
5890    }
5891
5892    /// Tensor (categorical/direct) product of two graphs.
5893    ///
5894    /// ```
5895    /// use rust_igraph::Graph;
5896    ///
5897    /// let p2 = Graph::from_edges(&[(0,1)], false, None).unwrap();
5898    /// let t = p2.tensor_product(&p2).unwrap();
5899    /// assert_eq!(t.vcount(), 4);
5900    /// ```
5901    pub fn tensor_product(&self, other: &Graph) -> IgraphResult<Graph> {
5902        crate::algorithms::operators::products::tensor_product(self, other)
5903    }
5904
5905    /// Strong product of two graphs.
5906    ///
5907    /// ```
5908    /// use rust_igraph::Graph;
5909    ///
5910    /// let p2 = Graph::from_edges(&[(0,1)], false, None).unwrap();
5911    /// let s = p2.strong_product(&p2).unwrap();
5912    /// assert_eq!(s.vcount(), 4);
5913    /// ```
5914    pub fn strong_product(&self, other: &Graph) -> IgraphResult<Graph> {
5915        crate::algorithms::operators::products::strong_product(self, other)
5916    }
5917
5918    /// Lexicographic product of two graphs.
5919    ///
5920    /// ```
5921    /// use rust_igraph::Graph;
5922    ///
5923    /// let a = Graph::from_edges(&[(0,1)], false, None).unwrap();
5924    /// let b = Graph::with_vertices(2);
5925    /// let l = a.lexicographic_product(&b).unwrap();
5926    /// assert_eq!(l.vcount(), 4);
5927    /// ```
5928    pub fn lexicographic_product(&self, other: &Graph) -> IgraphResult<Graph> {
5929        crate::algorithms::operators::products::lexicographic_product(self, other)
5930    }
5931
5932    /// Connect each vertex to all vertices within distance `order`.
5933    ///
5934    /// ```
5935    /// use rust_igraph::Graph;
5936    ///
5937    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5938    /// let c = g.connect_neighborhood(2).unwrap();
5939    /// assert!(c.ecount() > g.ecount());
5940    /// ```
5941    pub fn connect_neighborhood(&self, order: u32) -> IgraphResult<Graph> {
5942        crate::algorithms::operators::connect_neighborhood::connect_neighborhood(self, order)
5943    }
5944
5945    /// Rewire edges while preserving the degree sequence.
5946    ///
5947    /// ```
5948    /// use rust_igraph::Graph;
5949    ///
5950    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3), (3,0)], false, None).unwrap();
5951    /// let r = g.rewire(100, false, 42).unwrap();
5952    /// assert_eq!(r.ecount(), g.ecount());
5953    /// ```
5954    pub fn rewire(&self, num_trials: usize, loops: bool, seed: u64) -> IgraphResult<Graph> {
5955        crate::algorithms::operators::rewire::rewire(self, num_trials, loops, seed)
5956    }
5957
5958    /// Randomly rewire each edge with probability `prob`.
5959    ///
5960    /// ```
5961    /// use rust_igraph::Graph;
5962    ///
5963    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5964    /// let r = g.rewire_edges(0.5, false, 42).unwrap();
5965    /// assert_eq!(r.vcount(), g.vcount());
5966    /// ```
5967    pub fn rewire_edges(&self, prob: f64, loops: bool, seed: u64) -> IgraphResult<Graph> {
5968        crate::algorithms::operators::rewire_edges::rewire_edges(self, prob, loops, seed)
5969    }
5970
5971    /// Extract a subgraph induced by the given edge ids.
5972    ///
5973    /// ```
5974    /// use rust_igraph::Graph;
5975    ///
5976    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5977    /// let s = g.subgraph_from_edges(&[0, 1]).unwrap();
5978    /// assert_eq!(s.graph.ecount(), 2);
5979    /// ```
5980    pub fn subgraph_from_edges(
5981        &self,
5982        eids: &[u32],
5983    ) -> IgraphResult<crate::algorithms::operators::subgraph_from_edges::SubgraphFromEdgesResult>
5984    {
5985        crate::algorithms::operators::subgraph_from_edges::subgraph_from_edges(self, eids, true)
5986    }
5987
5988    /// The Even-Tarjan reduction of a directed graph.
5989    ///
5990    /// ```
5991    /// use rust_igraph::Graph;
5992    ///
5993    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5994    /// let et = g.even_tarjan_reduction().unwrap();
5995    /// assert!(et.graph.vcount() > g.vcount());
5996    /// ```
5997    pub fn even_tarjan_reduction(
5998        &self,
5999    ) -> IgraphResult<crate::algorithms::operators::even_tarjan::EvenTarjanResult> {
6000        crate::algorithms::operators::even_tarjan::even_tarjan_reduction(self)
6001    }
6002
6003    /// Bipartite projection onto one vertex type.
6004    ///
6005    /// `project_type` selects which side: `false` projects the `false`-typed
6006    /// vertices, `true` projects the `true`-typed vertices.
6007    ///
6008    /// ```
6009    /// use rust_igraph::Graph;
6010    ///
6011    /// let mut g = Graph::new(4, false).unwrap();
6012    /// g.add_edge(0, 2).unwrap();
6013    /// g.add_edge(0, 3).unwrap();
6014    /// g.add_edge(1, 2).unwrap();
6015    /// g.add_edge(1, 3).unwrap();
6016    /// let types = vec![false, false, true, true];
6017    /// let p = g.bipartite_projection(&types, false).unwrap();
6018    /// assert_eq!(p.graph.vcount(), 2);
6019    /// ```
6020    pub fn bipartite_projection(
6021        &self,
6022        types: &[bool],
6023        project_type: bool,
6024    ) -> IgraphResult<crate::algorithms::operators::bipartite_projection::BipartiteProjection> {
6025        crate::algorithms::operators::bipartite_projection::bipartite_projection(
6026            self,
6027            types,
6028            project_type,
6029        )
6030    }
6031
6032    // ── Paths (advanced) ─────────────────────────────────────────────
6033
6034    /// Bellman-Ford shortest-path distances from a single source.
6035    ///
6036    /// ```
6037    /// use rust_igraph::Graph;
6038    ///
6039    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
6040    /// let w = vec![1.0; g.ecount()];
6041    /// let d = g.bellman_ford_distances(0, &w).unwrap();
6042    /// assert!((d[3].unwrap() - 3.0).abs() < 1e-9);
6043    /// ```
6044    pub fn bellman_ford_distances(
6045        &self,
6046        source: VertexId,
6047        weights: &[f64],
6048    ) -> IgraphResult<Vec<Option<f64>>> {
6049        crate::algorithms::paths::bellman_ford::bellman_ford_distances(self, source, weights)
6050    }
6051
6052    /// Floyd-Warshall all-pairs shortest-path distances.
6053    ///
6054    /// ```
6055    /// use rust_igraph::Graph;
6056    ///
6057    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
6058    /// let d = g.floyd_warshall_distances(None).unwrap();
6059    /// assert!((d[0][2].unwrap() - 2.0).abs() < 1e-9);
6060    /// ```
6061    pub fn floyd_warshall_distances(
6062        &self,
6063        weights: Option<&[f64]>,
6064    ) -> IgraphResult<Vec<Vec<Option<f64>>>> {
6065        crate::algorithms::paths::floyd_warshall::floyd_warshall_distances(self, weights)
6066    }
6067
6068    /// Find the k shortest paths between two vertices.
6069    ///
6070    /// ```
6071    /// use rust_igraph::Graph;
6072    ///
6073    /// let g = Graph::from_edges(
6074    ///     &[(0,1), (1,3), (0,2), (2,3)], false, None,
6075    /// ).unwrap();
6076    /// let w = vec![1.0; g.ecount()];
6077    /// let paths = g.k_shortest_paths(0, 3, &w, 2).unwrap();
6078    /// assert_eq!(paths.len(), 2);
6079    /// ```
6080    pub fn k_shortest_paths(
6081        &self,
6082        source: VertexId,
6083        target: VertexId,
6084        weights: &[f64],
6085        k: usize,
6086    ) -> IgraphResult<Vec<crate::algorithms::paths::k_shortest_paths::KShortestPath>> {
6087        use crate::algorithms::paths::dijkstra::DijkstraMode;
6088        crate::algorithms::paths::k_shortest_paths::k_shortest_paths(
6089            self,
6090            source,
6091            target,
6092            weights,
6093            k,
6094            DijkstraMode::Out,
6095        )
6096    }
6097
6098    /// Enumerate all simple paths from a source vertex.
6099    ///
6100    /// ```
6101    /// use rust_igraph::Graph;
6102    ///
6103    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
6104    /// let paths = g.all_simple_paths(0, Some(&[2]), 1, 10).unwrap();
6105    /// assert!(paths.len() >= 2);
6106    /// ```
6107    pub fn all_simple_paths(
6108        &self,
6109        from: u32,
6110        to: Option<&[u32]>,
6111        min_length: i32,
6112        max_length: i32,
6113    ) -> IgraphResult<Vec<Vec<u32>>> {
6114        crate::algorithms::paths::simple_paths::all_simple_paths(
6115            self,
6116            from,
6117            to,
6118            crate::algorithms::paths::simple_paths::SimplePathMode::Out,
6119            min_length,
6120            max_length,
6121            -1,
6122        )
6123    }
6124
6125    // ── Matching ──────────────────────────────────────────────────────
6126
6127    /// Maximum bipartite matching.
6128    ///
6129    /// ```
6130    /// use rust_igraph::Graph;
6131    ///
6132    /// let mut g = Graph::new(4, false).unwrap();
6133    /// g.add_edge(0, 2).unwrap();
6134    /// g.add_edge(0, 3).unwrap();
6135    /// g.add_edge(1, 2).unwrap();
6136    /// let types = vec![false, false, true, true];
6137    /// let m = g.maximum_bipartite_matching(&types).unwrap();
6138    /// assert_eq!(m.matching_size, 2);
6139    /// ```
6140    pub fn maximum_bipartite_matching(
6141        &self,
6142        types: &[bool],
6143    ) -> IgraphResult<crate::algorithms::matching::MatchingResult> {
6144        crate::algorithms::matching::maximum_bipartite_matching(self, types)
6145    }
6146
6147    // ── Coloring ─────────────────────────────────────────────────────
6148
6149    /// Greedy vertex coloring.
6150    ///
6151    /// ```
6152    /// use rust_igraph::{Graph, GreedyColoringHeuristic};
6153    ///
6154    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6155    /// let colors = g.vertex_coloring_greedy(GreedyColoringHeuristic::ColoredNeighbors).unwrap();
6156    /// assert_eq!(colors.len(), 3);
6157    /// ```
6158    pub fn vertex_coloring_greedy(
6159        &self,
6160        heuristic: crate::algorithms::coloring::GreedyColoringHeuristic,
6161    ) -> IgraphResult<Vec<u32>> {
6162        crate::algorithms::coloring::vertex_coloring_greedy(self, heuristic)
6163    }
6164
6165    // ── Cycles ───────────────────────────────────────────────────────
6166
6167    /// Enumerate all simple cycles.
6168    ///
6169    /// ```
6170    /// use rust_igraph::{Graph, SimpleCycleMode};
6171    ///
6172    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6173    /// let cycles = g.simple_cycles(SimpleCycleMode::All, 3, None).unwrap();
6174    /// assert!(!cycles.is_empty());
6175    /// ```
6176    pub fn simple_cycles(
6177        &self,
6178        mode: crate::algorithms::simple_cycles::SimpleCycleMode,
6179        min_length: u32,
6180        max_length: Option<u32>,
6181    ) -> IgraphResult<Vec<crate::algorithms::simple_cycles::SimpleCycle>> {
6182        crate::algorithms::simple_cycles::simple_cycles(self, mode, min_length, max_length, None)
6183    }
6184
6185    // ── Community (additional) ───────────────────────────────────────
6186
6187    /// Leading eigenvector community detection.
6188    ///
6189    /// ```
6190    /// use rust_igraph::Graph;
6191    ///
6192    /// let g = Graph::from_edges(
6193    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)],
6194    ///     false, None,
6195    /// ).unwrap();
6196    /// let result = g.leading_eigenvector(None, None).unwrap();
6197    /// assert!(result.membership.len() == g.vcount() as usize);
6198    /// ```
6199    pub fn leading_eigenvector(
6200        &self,
6201        weights: Option<&[f64]>,
6202        steps: Option<u32>,
6203    ) -> IgraphResult<crate::algorithms::community::leading_eigenvector::LeadingEigenvectorResult>
6204    {
6205        crate::algorithms::community::leading_eigenvector::leading_eigenvector(self, weights, steps)
6206    }
6207
6208    /// Fluid community detection.
6209    ///
6210    /// ```
6211    /// use rust_igraph::Graph;
6212    ///
6213    /// let g = Graph::from_edges(
6214    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)],
6215    ///     false, None,
6216    /// ).unwrap();
6217    /// let r = g.fluid_communities(2).unwrap();
6218    /// assert_eq!(r.membership.len(), g.vcount() as usize);
6219    /// ```
6220    pub fn fluid_communities(
6221        &self,
6222        k: u32,
6223    ) -> IgraphResult<crate::algorithms::community::fluid_communities::FluidResult> {
6224        crate::algorithms::community::fluid_communities::fluid_communities(self, k)
6225    }
6226
6227    /// Motif census (subgraph isomorphism classes of a given size).
6228    ///
6229    /// ```
6230    /// use rust_igraph::Graph;
6231    ///
6232    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], true, None).unwrap();
6233    /// let hist = g.motifs_randesu(3).unwrap();
6234    /// assert!(!hist.is_empty());
6235    /// ```
6236    pub fn motifs_randesu(&self, size: u32) -> IgraphResult<Vec<f64>> {
6237        crate::algorithms::motifs::motifs_randesu::motifs_randesu(self, size)
6238    }
6239
6240    /// Personalized `PageRank` with a custom reset distribution.
6241    ///
6242    /// ```
6243    /// use rust_igraph::Graph;
6244    ///
6245    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6246    /// let reset = vec![1.0, 0.0, 0.0];
6247    /// let pr = g.personalized_pagerank(&reset).unwrap();
6248    /// assert_eq!(pr.len(), 3);
6249    /// ```
6250    pub fn personalized_pagerank(&self, reset: &[f64]) -> IgraphResult<Vec<f64>> {
6251        crate::algorithms::properties::personalized_pagerank::personalized_pagerank_default(
6252            self, reset,
6253        )
6254    }
6255
6256    // ── Isomorphism ─────────────────────────────────────────────────
6257
6258    /// Test whether two graphs are isomorphic (VF2).
6259    ///
6260    /// ```
6261    /// use rust_igraph::Graph;
6262    ///
6263    /// let a = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6264    /// let b = Graph::from_edges(&[(0,2), (2,1), (1,0)], false, None).unwrap();
6265    /// let result = a.isomorphic_vf2(&b).unwrap();
6266    /// assert!(result.iso);
6267    /// ```
6268    pub fn isomorphic_vf2(
6269        &self,
6270        other: &Graph,
6271    ) -> IgraphResult<crate::algorithms::isomorphism::vf2::Vf2Isomorphism> {
6272        crate::algorithms::isomorphism::vf2::isomorphic_vf2(self, other, None, None, None, None)
6273    }
6274
6275    /// Quick isomorphism test (delegates to the best available method).
6276    ///
6277    /// ```
6278    /// use rust_igraph::Graph;
6279    ///
6280    /// let a = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6281    /// let b = Graph::from_edges(&[(0,2), (2,1), (1,0)], false, None).unwrap();
6282    /// assert!(a.isomorphic(&b).unwrap());
6283    /// ```
6284    pub fn isomorphic(&self, other: &Graph) -> IgraphResult<bool> {
6285        crate::algorithms::isomorphism::queries::isomorphic(self, other)
6286    }
6287
6288    /// Count the number of isomorphisms between two graphs (VF2).
6289    ///
6290    /// ```
6291    /// use rust_igraph::Graph;
6292    ///
6293    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6294    /// let count = g.count_isomorphisms_vf2(&g).unwrap();
6295    /// assert_eq!(count, 6); // C3 has 6 automorphisms
6296    /// ```
6297    pub fn count_isomorphisms_vf2(&self, other: &Graph) -> IgraphResult<u64> {
6298        crate::algorithms::isomorphism::vf2::count_isomorphisms_vf2(
6299            self, other, None, None, None, None,
6300        )
6301    }
6302
6303    // ── Cliques ─────────────────────────────────────────────────────
6304
6305    /// Find all maximal independent vertex sets.
6306    ///
6307    /// ```
6308    /// use rust_igraph::Graph;
6309    ///
6310    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
6311    /// let sets = g.independent_vertex_sets(1, 3).unwrap();
6312    /// assert!(!sets.is_empty());
6313    /// ```
6314    pub fn independent_vertex_sets(
6315        &self,
6316        min_size: u32,
6317        max_size: u32,
6318    ) -> IgraphResult<Vec<Vec<u32>>> {
6319        crate::algorithms::cliques::independent_vertex_sets(self, min_size, max_size, None)
6320    }
6321
6322    // ── Network properties ──────────────────────────────────────────
6323
6324    /// Global efficiency of the graph.
6325    ///
6326    /// ```
6327    /// use rust_igraph::Graph;
6328    ///
6329    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6330    /// let e = g.global_efficiency().unwrap();
6331    /// assert!(e.unwrap_or(0.0) > 0.0);
6332    /// ```
6333    pub fn global_efficiency(&self) -> IgraphResult<Option<f64>> {
6334        crate::algorithms::properties::efficiency::global_efficiency(self)
6335    }
6336
6337    /// Local efficiency for each vertex.
6338    ///
6339    /// ```
6340    /// use rust_igraph::Graph;
6341    ///
6342    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6343    /// let e = g.local_efficiency().unwrap();
6344    /// assert_eq!(e.len(), 3);
6345    /// ```
6346    pub fn local_efficiency(&self) -> IgraphResult<Vec<f64>> {
6347        crate::algorithms::properties::efficiency::local_efficiency(self)
6348    }
6349
6350    /// Degree assortativity coefficient.
6351    ///
6352    /// ```
6353    /// use rust_igraph::Graph;
6354    ///
6355    /// let g = Graph::from_edges(
6356    ///     &[(0,1), (1,2), (2,3), (3,4)], false, None,
6357    /// ).unwrap();
6358    /// let r = g.assortativity_degree().unwrap();
6359    /// assert!(r.is_some());
6360    /// ```
6361    pub fn assortativity_degree(&self) -> IgraphResult<Option<f64>> {
6362        crate::algorithms::properties::assortativity::assortativity_degree(self)
6363    }
6364
6365    /// Diversity (entropy) of vertex edge weights.
6366    ///
6367    /// ```
6368    /// use rust_igraph::Graph;
6369    ///
6370    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
6371    /// let w = vec![1.0, 2.0, 3.0];
6372    /// let d = g.diversity(&w).unwrap();
6373    /// assert_eq!(d.len(), 3);
6374    /// ```
6375    pub fn diversity(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
6376        crate::algorithms::properties::strength::diversity(self, weights)
6377    }
6378
6379    // ---- Paths (batch 5) ----
6380
6381    /// All-pairs shortest-path distances (unweighted BFS).
6382    ///
6383    /// Returns a flat `n*n` vector in row-major order where
6384    /// `result[i*n + j]` is the distance from vertex `i` to `j`,
6385    /// or `None` if unreachable.
6386    ///
6387    /// ```
6388    /// use rust_igraph::Graph;
6389    ///
6390    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6391    /// let d = g.distances_all().unwrap();
6392    /// assert_eq!(d[0 * 3 + 2], Some(2)); // path 0→1→2
6393    /// ```
6394    pub fn distances_all(&self) -> IgraphResult<Vec<Option<u32>>> {
6395        crate::algorithms::paths::distances_all::distances_all(self)
6396    }
6397
6398    /// Shortest-path distances from a set of source vertices.
6399    ///
6400    /// Returns a flat `sources.len() * n` vector in row-major order.
6401    ///
6402    /// ```
6403    /// use rust_igraph::Graph;
6404    ///
6405    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6406    /// let d = g.distances_from(&[0]).unwrap();
6407    /// assert_eq!(d[3], Some(3)); // vertex 3 is 3 hops from vertex 0
6408    /// ```
6409    pub fn distances_from(&self, sources: &[VertexId]) -> IgraphResult<Vec<Option<u32>>> {
6410        crate::algorithms::paths::distances_from::distances_from(self, sources)
6411    }
6412
6413    /// Shortest-path trees from a source vertex (one path per target).
6414    ///
6415    /// Returns a vector of vertex sequences, one per target vertex.
6416    ///
6417    /// ```
6418    /// use rust_igraph::Graph;
6419    ///
6420    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6421    /// let paths = g.get_shortest_paths(0).unwrap();
6422    /// assert_eq!(paths[2], vec![0, 1, 2]);
6423    /// ```
6424    pub fn get_shortest_paths(&self, source: VertexId) -> IgraphResult<Vec<Vec<VertexId>>> {
6425        crate::algorithms::paths::shortest_paths::get_shortest_paths(self, source)
6426    }
6427
6428    /// All shortest paths from a source vertex.
6429    ///
6430    /// ```
6431    /// use rust_igraph::Graph;
6432    ///
6433    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,3),(2,3)], false, None).unwrap();
6434    /// let asp = g.get_all_shortest_paths(0).unwrap();
6435    /// // Two shortest paths from 0 to 3: 0-1-3 and 0-2-3
6436    /// assert_eq!(asp.paths[3].len(), 2);
6437    /// ```
6438    pub fn get_all_shortest_paths(
6439        &self,
6440        source: VertexId,
6441    ) -> IgraphResult<crate::AllShortestPaths> {
6442        crate::algorithms::paths::all_shortest_paths::get_all_shortest_paths(self, source)
6443    }
6444
6445    /// Johnson's algorithm for all-pairs shortest paths with edge weights.
6446    ///
6447    /// Handles negative weights (but not negative cycles).
6448    ///
6449    /// ```
6450    /// use rust_igraph::Graph;
6451    ///
6452    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6453    /// let d = g.johnson_distances(&[1.0, 2.0]).unwrap();
6454    /// assert!((d[0][2].unwrap() - 3.0).abs() < 1e-10);
6455    /// ```
6456    pub fn johnson_distances(&self, weights: &[f64]) -> IgraphResult<Vec<Vec<Option<f64>>>> {
6457        crate::algorithms::paths::johnson::johnson_distances(self, weights)
6458    }
6459
6460    /// Widest (bottleneck) paths from a source vertex.
6461    ///
6462    /// ```
6463    /// use rust_igraph::Graph;
6464    ///
6465    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6466    /// let wp = g.widest_paths(0, &[10.0, 5.0]).unwrap();
6467    /// assert!((wp.widths[2].unwrap() - 5.0).abs() < 1e-10);
6468    /// ```
6469    pub fn widest_paths(
6470        &self,
6471        from: VertexId,
6472        weights: &[f64],
6473    ) -> IgraphResult<crate::WidestPaths> {
6474        crate::algorithms::paths::widest_path::widest_paths(self, from, weights)
6475    }
6476
6477    /// Graph center — vertices with minimum eccentricity.
6478    ///
6479    /// ```
6480    /// use rust_igraph::{Graph, cycle_graph};
6481    ///
6482    /// let g = cycle_graph(5, false, false).unwrap();
6483    /// let center = g.graph_center().unwrap();
6484    /// assert_eq!(center.len(), 5); // all vertices equidistant in a cycle
6485    /// ```
6486    pub fn graph_center(&self) -> IgraphResult<Vec<VertexId>> {
6487        crate::algorithms::paths::graph_center::graph_center(
6488            self,
6489            crate::algorithms::paths::radii::EccMode::Out,
6490        )
6491    }
6492
6493    /// Path-length histogram of the graph.
6494    ///
6495    /// ```
6496    /// use rust_igraph::Graph;
6497    ///
6498    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6499    /// let h = g.path_length_hist(false).unwrap();
6500    /// assert!(!h.hist.is_empty());
6501    /// ```
6502    pub fn path_length_hist(&self, directed: bool) -> IgraphResult<crate::PathLengthHistResult> {
6503        crate::algorithms::paths::histogram::path_length_hist(self, directed)
6504    }
6505
6506    /// Find an Eulerian cycle (every edge visited exactly once, returning
6507    /// to start).
6508    ///
6509    /// ```
6510    /// use rust_igraph::cycle_graph;
6511    ///
6512    /// let g = cycle_graph(5, false, false).unwrap();
6513    /// let cycle = g.eulerian_cycle().unwrap();
6514    /// assert_eq!(cycle.len(), 5); // 5 edges in C5
6515    /// ```
6516    pub fn eulerian_cycle(&self) -> IgraphResult<Vec<EdgeId>> {
6517        crate::algorithms::paths::eulerian_construct::eulerian_cycle(self)
6518    }
6519
6520    // ---- Centrality / properties (batch 5) ----
6521
6522    /// HITS hub and authority scores.
6523    ///
6524    /// ```
6525    /// use rust_igraph::Graph;
6526    ///
6527    /// let g = Graph::from_edges(&[(0,1),(1,2)], true, None).unwrap();
6528    /// let hits = g.hub_and_authority_scores().unwrap();
6529    /// assert_eq!(hits.hub.len(), 3);
6530    /// ```
6531    pub fn hub_and_authority_scores(&self) -> IgraphResult<crate::HitsScores> {
6532        crate::algorithms::properties::hits::hub_and_authority_scores(self)
6533    }
6534
6535    /// Weighted vertex degree (strength).
6536    ///
6537    /// ```
6538    /// use rust_igraph::Graph;
6539    ///
6540    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
6541    /// let s = g.strength(&[1.0, 2.0, 3.0]).unwrap();
6542    /// assert!((s[0] - 3.0).abs() < 1e-10); // edges 0-1 (w=1) + 0-2 (w=2)
6543    /// ```
6544    pub fn strength(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
6545        crate::algorithms::properties::strength::strength(self, weights)
6546    }
6547
6548    /// Average nearest-neighbor degree by degree class (knn(k)).
6549    ///
6550    /// ```
6551    /// use rust_igraph::Graph;
6552    ///
6553    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6554    /// let k = g.knnk().unwrap();
6555    /// assert!(k.len() > 0);
6556    /// ```
6557    pub fn knnk(&self) -> IgraphResult<Vec<Option<f64>>> {
6558        crate::algorithms::properties::knn::knnk(self)
6559    }
6560
6561    /// Barrat's weighted clustering coefficient per vertex.
6562    ///
6563    /// ```
6564    /// use rust_igraph::Graph;
6565    ///
6566    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6567    /// let t = g.transitivity_barrat(&[1.0, 1.0, 1.0]).unwrap();
6568    /// assert!(t[0].unwrap() > 0.0);
6569    /// ```
6570    pub fn transitivity_barrat(&self, weights: &[f64]) -> IgraphResult<Vec<Option<f64>>> {
6571        crate::algorithms::properties::triangles::transitivity_barrat(self, weights)
6572    }
6573
6574    /// Local scan statistic (order 1) — triangle counts per vertex.
6575    ///
6576    /// ```
6577    /// use rust_igraph::Graph;
6578    ///
6579    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6580    /// let s = g.local_scan_1(None).unwrap();
6581    /// assert_eq!(s.len(), 3);
6582    /// ```
6583    pub fn local_scan_1(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
6584        crate::algorithms::properties::local_scan::local_scan_1(self, weights)
6585    }
6586
6587    /// Maximum cardinality search ordering.
6588    ///
6589    /// ```
6590    /// use rust_igraph::Graph;
6591    ///
6592    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6593    /// let mcs = g.maximum_cardinality_search().unwrap();
6594    /// assert_eq!(mcs.alpha.len(), 3);
6595    /// ```
6596    pub fn maximum_cardinality_search(&self) -> IgraphResult<crate::McsResult> {
6597        crate::algorithms::chordality::maximum_cardinality_search(self)
6598    }
6599
6600    /// Vertex with the highest degree.
6601    ///
6602    /// ```
6603    /// use rust_igraph::Graph;
6604    ///
6605    /// let g = Graph::from_edges(&[(0,1),(0,2),(0,3),(1,2)], false, None).unwrap();
6606    /// let v = g.max_degree_vertex().unwrap();
6607    /// assert_eq!(v, Some(0)); // vertex 0 has degree 3
6608    /// ```
6609    pub fn max_degree_vertex(&self) -> IgraphResult<Option<VertexId>> {
6610        crate::algorithms::properties::degree::max_degree_vertex(
6611            self,
6612            crate::algorithms::properties::degree::DegreeMode::All,
6613        )
6614    }
6615
6616    // ---- Graph predicates / queries (batch 5) ----
6617
6618    /// Whether the graph has at least one self-loop.
6619    ///
6620    /// ```
6621    /// use rust_igraph::Graph;
6622    ///
6623    /// let g = Graph::from_edges(&[(0,1),(1,1)], false, None).unwrap();
6624    /// assert!(g.has_loop().unwrap());
6625    /// ```
6626    pub fn has_loop(&self) -> IgraphResult<bool> {
6627        crate::algorithms::properties::multiplicity::has_loop(self)
6628    }
6629
6630    /// Whether the graph has at least one pair of mutual (reciprocal) edges.
6631    ///
6632    /// ```
6633    /// use rust_igraph::Graph;
6634    ///
6635    /// let g = Graph::from_edges(&[(0,1),(1,0)], true, None).unwrap();
6636    /// assert!(g.has_mutual(true).unwrap());
6637    /// ```
6638    pub fn has_mutual(&self, loops: bool) -> IgraphResult<bool> {
6639        crate::algorithms::properties::mutual::has_mutual(self, loops)
6640    }
6641
6642    /// Per-edge test: is each edge a multi-edge?
6643    ///
6644    /// ```
6645    /// use rust_igraph::Graph;
6646    ///
6647    /// let g = Graph::from_edges(&[(0,1),(0,1),(1,2)], false, None).unwrap();
6648    /// let m = g.is_multiple().unwrap();
6649    /// assert!(m.iter().any(|&x| x)); // at least one multi-edge
6650    /// ```
6651    pub fn is_multiple(&self) -> IgraphResult<Vec<bool>> {
6652        crate::algorithms::properties::multiplicity::is_multiple(self)
6653    }
6654
6655    /// Per-edge test: is each edge mutual (has a reciprocal)?
6656    ///
6657    /// ```
6658    /// use rust_igraph::Graph;
6659    ///
6660    /// let g = Graph::from_edges(&[(0,1),(1,0),(0,2)], true, None).unwrap();
6661    /// let m = g.is_mutual(true).unwrap();
6662    /// assert!(m[0]); // edge 0→1 has reciprocal 1→0
6663    /// ```
6664    pub fn is_mutual(&self, loops: bool) -> IgraphResult<Vec<bool>> {
6665        crate::algorithms::properties::mutual::is_mutual(self, loops)
6666    }
6667
6668    // ---- Community detection (batch 5) ----
6669
6670    /// Modularity of a given community assignment.
6671    ///
6672    /// ```
6673    /// use rust_igraph::Graph;
6674    ///
6675    /// let g = Graph::from_edges(
6676    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6677    ///     false, None,
6678    /// ).unwrap();
6679    /// let q = g.modularity(&[0,0,0,1,1,1], 1.0).unwrap();
6680    /// assert!(q.unwrap() > 0.0);
6681    /// ```
6682    pub fn modularity(&self, membership: &[u32], resolution: f64) -> IgraphResult<Option<f64>> {
6683        crate::algorithms::community::modularity::modularity(self, membership, resolution)
6684    }
6685
6686    // ---- Constructors / operators (batch 5) ----
6687
6688    /// Mycielskian — triangle-free graph with increasing chromatic number.
6689    ///
6690    /// ```
6691    /// use rust_igraph::Graph;
6692    ///
6693    /// let g = Graph::from_edges(&[(0,1)], false, None).unwrap();
6694    /// let m = g.mycielskian(1).unwrap();
6695    /// assert!(m.vcount() > g.vcount());
6696    /// ```
6697    pub fn mycielskian(&self, k: u32) -> IgraphResult<Graph> {
6698        crate::algorithms::constructors::mycielskian::mycielskian(self, k)
6699    }
6700
6701    /// Prüfer sequence of a labeled tree.
6702    ///
6703    /// ```
6704    /// use rust_igraph::Graph;
6705    ///
6706    /// // Path graph 0-1-2-3 is a tree
6707    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6708    /// let seq = g.to_prufer().unwrap();
6709    /// assert_eq!(seq.len(), 2); // n-2 elements for n=4
6710    /// ```
6711    pub fn to_prufer(&self) -> IgraphResult<Vec<u32>> {
6712        crate::algorithms::constructors::prufer::to_prufer(self)
6713    }
6714
6715    // ---- Connectivity / percolation (batch 5) ----
6716
6717    /// Bond (edge) percolation — add edges one by one and track components.
6718    ///
6719    /// ```
6720    /// use rust_igraph::Graph;
6721    ///
6722    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6723    /// let p = g.bond_percolation(&[0, 1, 2]).unwrap();
6724    /// assert_eq!(p.giant_size.len(), 3); // one snapshot per step
6725    /// ```
6726    pub fn bond_percolation(
6727        &self,
6728        edge_order: &[EdgeId],
6729    ) -> IgraphResult<crate::EdgelistPercolation> {
6730        crate::algorithms::connectivity::percolation::bond_percolation(self, edge_order)
6731    }
6732
6733    /// Site (vertex) percolation — add vertices one by one and track components.
6734    ///
6735    /// ```
6736    /// use rust_igraph::Graph;
6737    ///
6738    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6739    /// let p = g.site_percolation(&[0, 1, 2, 3]).unwrap();
6740    /// assert_eq!(p.giant_size.len(), 4);
6741    /// ```
6742    pub fn site_percolation(
6743        &self,
6744        vertex_order: &[VertexId],
6745    ) -> IgraphResult<crate::SitePercolation> {
6746        crate::algorithms::connectivity::percolation::site_percolation(self, vertex_order)
6747    }
6748
6749    /// Reachability matrix (transitive closure as a boolean matrix).
6750    ///
6751    /// ```
6752    /// use rust_igraph::{Graph, ReachabilityMode};
6753    ///
6754    /// let g = Graph::from_edges(&[(0,1),(1,2)], true, None).unwrap();
6755    /// let r = g.reachability(ReachabilityMode::Out).unwrap();
6756    /// assert!(r.is_reachable(0, 2)); // 0 can reach 2 transitively
6757    /// ```
6758    pub fn reachability(
6759        &self,
6760        mode: crate::ReachabilityMode,
6761    ) -> IgraphResult<crate::ReachabilityResult> {
6762        crate::algorithms::connectivity::reachability_scc::reachability(self, mode)
6763    }
6764
6765    // ---- Neighborhoods (batch 5) ----
6766
6767    /// Induced subgraphs of k-hop neighborhoods around each vertex.
6768    ///
6769    /// ```
6770    /// use rust_igraph::Graph;
6771    ///
6772    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6773    /// let nbrs = g.neighborhood_graphs(1).unwrap();
6774    /// assert_eq!(nbrs.len(), 4); // one subgraph per vertex
6775    /// ```
6776    pub fn neighborhood_graphs(&self, order: i32) -> IgraphResult<Vec<Graph>> {
6777        crate::algorithms::properties::neighborhood::neighborhood_graphs(self, order)
6778    }
6779
6780    // ---- Cliques (batch 5) ----
6781
6782    /// Weighted clique number — maximum total weight of any clique.
6783    ///
6784    /// ```
6785    /// use rust_igraph::Graph;
6786    ///
6787    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6788    /// let wc = g.weighted_clique_number(&[1.0, 2.0, 3.0]).unwrap();
6789    /// assert!((wc - 6.0).abs() < 1e-10); // triangle, all 3 vertices
6790    /// ```
6791    pub fn weighted_clique_number(&self, vertex_weights: &[f64]) -> IgraphResult<f64> {
6792        crate::algorithms::cliques::weighted_clique_number(self, vertex_weights)
6793    }
6794
6795    // ---- Isomorphism (batch 5) ----
6796
6797    /// Isomorphism class of a small graph (up to 6 vertices).
6798    ///
6799    /// ```
6800    /// use rust_igraph::Graph;
6801    ///
6802    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6803    /// let cls = g.isoclass().unwrap();
6804    /// assert!(cls > 0);
6805    /// ```
6806    pub fn isoclass(&self) -> IgraphResult<u32> {
6807        crate::algorithms::motifs::isoclass::isoclass(self)
6808    }
6809
6810    // ---- Layout (batch 5) ----
6811
6812    /// Multidimensional scaling layout.
6813    ///
6814    /// ```
6815    /// use rust_igraph::Graph;
6816    ///
6817    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6818    /// let pos = g.layout_mds(None).unwrap();
6819    /// assert_eq!(pos.len(), 4);
6820    /// ```
6821    pub fn layout_mds(&self, dist: Option<&[Vec<f64>]>) -> IgraphResult<Vec<[f64; 2]>> {
6822        crate::algorithms::layout::mds::layout_mds(self, dist)
6823    }
6824
6825    /// Spherical layout (3D positions on a unit sphere).
6826    ///
6827    /// ```
6828    /// use rust_igraph::Graph;
6829    ///
6830    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6831    /// let pos = g.layout_sphere();
6832    /// assert_eq!(pos.len(), 3);
6833    /// ```
6834    pub fn layout_sphere(&self) -> Vec<(f64, f64, f64)> {
6835        crate::algorithms::layout::simple::layout_sphere(self)
6836    }
6837
6838    // ---- Weighted community detection (batch 6) ----
6839
6840    /// Louvain community detection with edge weights.
6841    ///
6842    /// ```
6843    /// use rust_igraph::Graph;
6844    ///
6845    /// let g = Graph::from_edges(
6846    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6847    ///     false, None,
6848    /// ).unwrap();
6849    /// let r = g.louvain_weighted(&[1.0; 7]).unwrap();
6850    /// assert!(r.modularity > 0.0);
6851    /// ```
6852    pub fn louvain_weighted(&self, weights: &[f64]) -> IgraphResult<crate::LouvainResult> {
6853        crate::algorithms::community::louvain::louvain_weighted(self, weights)
6854    }
6855
6856    /// Leiden community detection with edge weights.
6857    ///
6858    /// ```
6859    /// use rust_igraph::Graph;
6860    ///
6861    /// let g = Graph::from_edges(
6862    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6863    ///     false, None,
6864    /// ).unwrap();
6865    /// let r = g.leiden_weighted(&[1.0; 7]).unwrap();
6866    /// assert!(r.quality > 0.0);
6867    /// ```
6868    pub fn leiden_weighted(&self, weights: &[f64]) -> IgraphResult<crate::LeidenResult> {
6869        crate::algorithms::community::leiden::leiden_weighted(self, weights)
6870    }
6871
6872    /// Label propagation with edge weights.
6873    ///
6874    /// ```
6875    /// use rust_igraph::Graph;
6876    ///
6877    /// let g = Graph::from_edges(
6878    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6879    ///     false, None,
6880    /// ).unwrap();
6881    /// let r = g.label_propagation_weighted(&[1.0; 7]).unwrap();
6882    /// assert_eq!(r.membership.len(), 6);
6883    /// ```
6884    pub fn label_propagation_weighted(&self, weights: &[f64]) -> IgraphResult<crate::LpaResult> {
6885        crate::algorithms::community::label_propagation::label_propagation_weighted(self, weights)
6886    }
6887
6888    /// Walktrap community detection with edge weights.
6889    ///
6890    /// ```
6891    /// use rust_igraph::Graph;
6892    ///
6893    /// let g = Graph::from_edges(
6894    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6895    ///     false, None,
6896    /// ).unwrap();
6897    /// let r = g.walktrap_weighted(&[1.0; 7]).unwrap();
6898    /// assert!(!r.modularity.is_empty());
6899    /// ```
6900    pub fn walktrap_weighted(&self, weights: &[f64]) -> IgraphResult<crate::WalktrapResult> {
6901        crate::algorithms::community::walktrap::walktrap_weighted(self, weights)
6902    }
6903
6904    // ---- Weighted distance/centrality (batch 6) ----
6905
6906    /// Weighted diameter (longest shortest-path distance).
6907    ///
6908    /// ```
6909    /// use rust_igraph::Graph;
6910    ///
6911    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6912    /// let d = g.diameter_weighted(&[1.0, 2.0]).unwrap();
6913    /// assert!((d.unwrap() - 3.0).abs() < 1e-10);
6914    /// ```
6915    pub fn diameter_weighted(&self, weights: &[f64]) -> IgraphResult<Option<f64>> {
6916        crate::algorithms::paths::radii::diameter_weighted(self, weights)
6917    }
6918
6919    /// Weighted eccentricity per vertex.
6920    ///
6921    /// ```
6922    /// use rust_igraph::Graph;
6923    ///
6924    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6925    /// let e = g.eccentricity_weighted(&[1.0, 2.0]).unwrap();
6926    /// assert_eq!(e.len(), 3);
6927    /// ```
6928    pub fn eccentricity_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
6929        crate::algorithms::paths::radii::eccentricity_weighted(self, weights)
6930    }
6931
6932    /// Weighted graph radius.
6933    ///
6934    /// ```
6935    /// use rust_igraph::Graph;
6936    ///
6937    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6938    /// let r = g.radius_weighted(&[1.0, 2.0]).unwrap();
6939    /// assert!(r.is_some());
6940    /// ```
6941    pub fn radius_weighted(&self, weights: &[f64]) -> IgraphResult<Option<f64>> {
6942        crate::algorithms::paths::radii::radius_weighted(self, weights)
6943    }
6944
6945    /// Weighted knn(k) — average nearest-neighbor degree by degree class.
6946    ///
6947    /// ```
6948    /// use rust_igraph::Graph;
6949    ///
6950    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6951    /// let k = g.knnk_weighted(&[1.0, 1.0, 1.0]).unwrap();
6952    /// assert!(!k.is_empty());
6953    /// ```
6954    pub fn knnk_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<Option<f64>>> {
6955        crate::algorithms::properties::knn::knnk_weighted(self, weights)
6956    }
6957
6958    /// `PageRank` via linear-system solver (alternative to power iteration).
6959    ///
6960    /// ```
6961    /// use rust_igraph::Graph;
6962    ///
6963    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], true, None).unwrap();
6964    /// let pr = g.pagerank_linsys().unwrap();
6965    /// assert_eq!(pr.len(), 3);
6966    /// ```
6967    pub fn pagerank_linsys(&self) -> IgraphResult<Vec<f64>> {
6968        crate::algorithms::properties::pagerank_linsys::pagerank_linsys(self)
6969    }
6970
6971    /// Local scan statistic of order k.
6972    ///
6973    /// ```
6974    /// use rust_igraph::Graph;
6975    ///
6976    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6977    /// let s = g.local_scan_k(1, None).unwrap();
6978    /// assert_eq!(s.len(), 3);
6979    /// ```
6980    pub fn local_scan_k(&self, k: u32, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
6981        crate::algorithms::properties::local_scan_k::local_scan_k(self, k, weights)
6982    }
6983
6984    // ---- Validators / predicates (batch 6) ----
6985
6986    /// Per-edge test: is each edge a self-loop?
6987    ///
6988    /// ```
6989    /// use rust_igraph::Graph;
6990    ///
6991    /// let g = Graph::from_edges(&[(0,1),(1,1),(2,3)], false, None).unwrap();
6992    /// let loops = g.is_loop().unwrap();
6993    /// assert!(!loops[0]); // 0-1 is not a loop
6994    /// assert!(loops[1]);  // 1-1 is a loop
6995    /// ```
6996    pub fn is_loop(&self) -> IgraphResult<Vec<bool>> {
6997        crate::algorithms::properties::multiplicity::is_loop(self)
6998    }
6999
7000    /// Whether a set of vertices forms a clique.
7001    ///
7002    /// ```
7003    /// use rust_igraph::Graph;
7004    ///
7005    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7006    /// assert!(g.is_clique(&[0, 1, 2], false).unwrap());
7007    /// ```
7008    pub fn is_clique(&self, vertices: &[VertexId], directed: bool) -> IgraphResult<bool> {
7009        crate::algorithms::properties::is_clique::is_clique(self, vertices, directed)
7010    }
7011
7012    /// Whether a set of vertices forms an independent set.
7013    ///
7014    /// ```
7015    /// use rust_igraph::Graph;
7016    ///
7017    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7018    /// assert!(g.is_independent_vertex_set(&[0, 2]).unwrap());
7019    /// ```
7020    pub fn is_independent_vertex_set(&self, vertices: &[VertexId]) -> IgraphResult<bool> {
7021        crate::algorithms::properties::is_clique::is_independent_vertex_set(self, vertices)
7022    }
7023
7024    /// Whether a set of vertices is a separator (its removal disconnects the graph).
7025    ///
7026    /// ```
7027    /// use rust_igraph::Graph;
7028    ///
7029    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7030    /// assert!(g.is_separator(&[1]).unwrap());
7031    /// ```
7032    pub fn is_separator(&self, candidates: &[VertexId]) -> IgraphResult<bool> {
7033        crate::algorithms::connectivity::separators::is_separator(self, candidates)
7034    }
7035
7036    /// Whether a separator is minimal.
7037    ///
7038    /// ```
7039    /// use rust_igraph::Graph;
7040    ///
7041    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7042    /// assert!(g.is_minimal_separator(&[1]).unwrap());
7043    /// ```
7044    pub fn is_minimal_separator(&self, candidates: &[VertexId]) -> IgraphResult<bool> {
7045        crate::algorithms::connectivity::separators::is_minimal_separator(self, candidates)
7046    }
7047
7048    /// Whether a coloring is a valid vertex coloring.
7049    ///
7050    /// ```
7051    /// use rust_igraph::Graph;
7052    ///
7053    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7054    /// assert!(g.is_vertex_coloring(&[0, 1, 0]).unwrap());
7055    /// ```
7056    pub fn is_vertex_coloring(&self, colors: &[u32]) -> IgraphResult<bool> {
7057        crate::algorithms::coloring::is_vertex_coloring(self, colors)
7058    }
7059
7060    /// Whether a coloring is a valid edge coloring.
7061    ///
7062    /// ```
7063    /// use rust_igraph::Graph;
7064    ///
7065    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7066    /// assert!(g.is_edge_coloring(&[0, 1]).unwrap());
7067    /// ```
7068    pub fn is_edge_coloring(&self, colors: &[u32]) -> IgraphResult<bool> {
7069        crate::algorithms::coloring::is_edge_coloring(self, colors)
7070    }
7071
7072    /// Whether a set of vertices is a vertex cover.
7073    ///
7074    /// ```
7075    /// use rust_igraph::Graph;
7076    ///
7077    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7078    /// assert!(g.is_vertex_cover(&[1]));
7079    /// ```
7080    pub fn is_vertex_cover(&self, cover: &[VertexId]) -> bool {
7081        crate::algorithms::vertex_cover::is_vertex_cover(self, cover)
7082    }
7083
7084    /// Whether a set of edges is an edge cover.
7085    ///
7086    /// ```
7087    /// use rust_igraph::Graph;
7088    ///
7089    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7090    /// assert!(g.is_edge_cover(&[0, 1]));
7091    /// ```
7092    pub fn is_edge_cover(&self, cover: &[EdgeId]) -> bool {
7093        crate::algorithms::edge_cover::is_edge_cover(self, cover)
7094    }
7095
7096    /// Whether a set of vertices is a dominating set.
7097    ///
7098    /// ```
7099    /// use rust_igraph::Graph;
7100    ///
7101    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7102    /// assert!(g.is_dominating_set(&[1]));
7103    /// ```
7104    pub fn is_dominating_set(&self, dom_set: &[VertexId]) -> bool {
7105        crate::algorithms::dominating_set::is_dominating_set(self, dom_set)
7106    }
7107
7108    // ---- Operators (batch 6) ----
7109
7110    /// Reverse specific edges in a directed graph.
7111    ///
7112    /// ```
7113    /// use rust_igraph::Graph;
7114    ///
7115    /// let g = Graph::from_edges(&[(0,1),(1,2)], true, None).unwrap();
7116    /// let r = g.reverse_edges(&[0]).unwrap();
7117    /// assert_eq!(r.edge(0).unwrap(), (1, 0));
7118    /// ```
7119    pub fn reverse_edges(&self, eids: &[u32]) -> IgraphResult<Graph> {
7120        crate::algorithms::operators::reverse::reverse_edges(self, eids)
7121    }
7122
7123    /// Edges induced by a subset of vertices.
7124    ///
7125    /// ```
7126    /// use rust_igraph::Graph;
7127    ///
7128    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7129    /// let eids = g.induced_subgraph_edges(&[0, 1]).unwrap();
7130    /// assert_eq!(eids.len(), 1); // only edge 0-1
7131    /// ```
7132    pub fn induced_subgraph_edges(&self, vids: &[u32]) -> IgraphResult<Vec<u32>> {
7133        crate::algorithms::operators::induced_subgraph_edges::induced_subgraph_edges(self, vids)
7134    }
7135
7136    // ---- Similarity (batch 6) ----
7137
7138    /// Dice similarity between all vertex pairs (n*n flat matrix).
7139    ///
7140    /// ```
7141    /// use rust_igraph::Graph;
7142    ///
7143    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7144    /// let s = g.similarity_dice().unwrap();
7145    /// let n = g.vcount() as usize;
7146    /// assert_eq!(s.len(), n * n);
7147    /// ```
7148    pub fn similarity_dice(&self) -> IgraphResult<Vec<f64>> {
7149        crate::algorithms::properties::similarity::similarity_dice(self)
7150    }
7151
7152    /// Inverse-log-weighted similarity between all vertex pairs.
7153    ///
7154    /// ```
7155    /// use rust_igraph::Graph;
7156    ///
7157    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7158    /// let s = g.similarity_inverse_log_weighted().unwrap();
7159    /// let n = g.vcount() as usize;
7160    /// assert_eq!(s.len(), n * n);
7161    /// ```
7162    pub fn similarity_inverse_log_weighted(&self) -> IgraphResult<Vec<f64>> {
7163        crate::algorithms::properties::similarity::similarity_inverse_log_weighted(self)
7164    }
7165
7166    /// Jaccard similarity for given edges.
7167    ///
7168    /// ```
7169    /// use rust_igraph::Graph;
7170    ///
7171    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7172    /// let s = g.similarity_jaccard_es(&[0, 1]).unwrap();
7173    /// assert_eq!(s.len(), 2);
7174    /// ```
7175    pub fn similarity_jaccard_es(&self, eids: &[u32]) -> IgraphResult<Vec<f64>> {
7176        crate::algorithms::properties::similarity::similarity_jaccard_es(self, eids)
7177    }
7178
7179    // ---- Layout (batch 6) ----
7180
7181    /// Large Graph Layout (LGL).
7182    ///
7183    /// ```
7184    /// use rust_igraph::{Graph, LglParams};
7185    ///
7186    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7187    /// let pos = g.layout_lgl(&LglParams::default()).unwrap();
7188    /// assert_eq!(pos.len(), 4);
7189    /// ```
7190    pub fn layout_lgl(&self, params: &crate::LglParams) -> IgraphResult<Vec<[f64; 2]>> {
7191        crate::algorithms::layout::lgl::layout_lgl(self, params)
7192    }
7193
7194    /// Random 3D layout.
7195    ///
7196    /// ```
7197    /// use rust_igraph::Graph;
7198    ///
7199    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7200    /// let pos = g.layout_random_3d(42);
7201    /// assert_eq!(pos.len(), 3);
7202    /// ```
7203    pub fn layout_random_3d(&self, seed: u64) -> Vec<(f64, f64, f64)> {
7204        crate::algorithms::layout::simple::layout_random_3d(self, seed)
7205    }
7206
7207    /// Grid 3D layout.
7208    ///
7209    /// ```
7210    /// use rust_igraph::Graph;
7211    ///
7212    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7213    /// let pos = g.layout_grid_3d(2, 2);
7214    /// assert_eq!(pos.len(), 4);
7215    /// ```
7216    pub fn layout_grid_3d(&self, width: i32, height: i32) -> Vec<(f64, f64, f64)> {
7217        crate::algorithms::layout::simple::layout_grid_3d(self, width, height)
7218    }
7219
7220    // ---- Motifs (batch 6) ----
7221
7222    /// Count the total number of motifs of a given size.
7223    ///
7224    /// ```
7225    /// use rust_igraph::Graph;
7226    ///
7227    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7228    /// let n = g.motifs_randesu_no(3).unwrap();
7229    /// assert!((n - 1.0).abs() < 1e-10); // one triangle
7230    /// ```
7231    pub fn motifs_randesu_no(&self, size: u32) -> IgraphResult<f64> {
7232        crate::algorithms::motifs::motifs_randesu::motifs_randesu_no(self, size)
7233    }
7234
7235    // ---- Graph inspection (batch 6) ----
7236
7237    /// Structural summary of the graph.
7238    ///
7239    /// ```
7240    /// use rust_igraph::Graph;
7241    ///
7242    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7243    /// let s = g.graph_summary().unwrap();
7244    /// assert_eq!(s.vcount, 3);
7245    /// assert_eq!(s.ecount, 2);
7246    /// ```
7247    pub fn graph_summary(&self) -> IgraphResult<crate::GraphSummary> {
7248        crate::algorithms::properties::summary::graph_summary(self)
7249    }
7250
7251    /// Human-readable structural summary string.
7252    ///
7253    /// ```
7254    /// use rust_igraph::Graph;
7255    ///
7256    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7257    /// let s = g.graph_summary_string().unwrap();
7258    /// assert!(s.contains("Vertices: 3"));
7259    /// assert!(s.contains("Edges: 2"));
7260    /// ```
7261    pub fn graph_summary_string(&self) -> IgraphResult<String> {
7262        crate::graph_summary_string(self)
7263    }
7264
7265    // ---- Matrix representations (batch 7) ----
7266
7267    /// Adjacency matrix of the graph.
7268    ///
7269    /// Returns a dense `V×V` matrix. For undirected graphs with
7270    /// [`AdjacencyType::Both`](crate::AdjacencyType::Both), the result is symmetric.
7271    ///
7272    /// ```
7273    /// use rust_igraph::{Graph, AdjacencyType, LoopHandling};
7274    ///
7275    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7276    /// let m = g.get_adjacency(AdjacencyType::Both, LoopHandling::Once).unwrap();
7277    /// assert_eq!(m.len(), 3);
7278    /// assert!((m[0][1] - 1.0).abs() < 1e-10);
7279    /// assert!((m[0][2]).abs() < 1e-10);
7280    /// ```
7281    pub fn get_adjacency(
7282        &self,
7283        adj_type: crate::AdjacencyType,
7284        loops: crate::LoopHandling,
7285    ) -> IgraphResult<Vec<Vec<f64>>> {
7286        crate::algorithms::properties::adjacency::get_adjacency(self, adj_type, None, loops)
7287    }
7288
7289    /// Weighted adjacency matrix of the graph.
7290    ///
7291    /// ```
7292    /// use rust_igraph::{Graph, AdjacencyType, LoopHandling};
7293    ///
7294    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7295    /// let w = vec![2.0, 3.0];
7296    /// let m = g.get_adjacency_weighted(AdjacencyType::Both, &w, LoopHandling::Once).unwrap();
7297    /// assert!((m[0][1] - 2.0).abs() < 1e-10);
7298    /// ```
7299    pub fn get_adjacency_weighted(
7300        &self,
7301        adj_type: crate::AdjacencyType,
7302        weights: &[f64],
7303        loops: crate::LoopHandling,
7304    ) -> IgraphResult<Vec<Vec<f64>>> {
7305        crate::algorithms::properties::adjacency::get_adjacency(
7306            self,
7307            adj_type,
7308            Some(weights),
7309            loops,
7310        )
7311    }
7312
7313    /// Laplacian matrix L = D - A (unnormalized, unweighted).
7314    ///
7315    /// ```
7316    /// use rust_igraph::Graph;
7317    ///
7318    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7319    /// let lap = g.get_laplacian().unwrap();
7320    /// assert!((lap[0][0] - 1.0).abs() < 1e-10); // degree of vertex 0
7321    /// assert!((lap[1][1] - 2.0).abs() < 1e-10); // degree of vertex 1
7322    /// ```
7323    pub fn get_laplacian(&self) -> IgraphResult<Vec<Vec<f64>>> {
7324        crate::algorithms::properties::laplacian::get_laplacian(
7325            self,
7326            crate::DegreeMode::All,
7327            crate::LaplacianNormalization::Unnormalized,
7328            None,
7329        )
7330    }
7331
7332    /// Laplacian matrix with normalization and optional weights.
7333    ///
7334    /// ```
7335    /// use rust_igraph::{Graph, DegreeMode, LaplacianNormalization};
7336    ///
7337    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7338    /// let lap = g.get_laplacian_full(
7339    ///     DegreeMode::All,
7340    ///     LaplacianNormalization::Symmetric,
7341    ///     None,
7342    /// ).unwrap();
7343    /// assert!(lap[0][0] > 0.0);
7344    /// ```
7345    pub fn get_laplacian_full(
7346        &self,
7347        mode: crate::DegreeMode,
7348        normalization: crate::LaplacianNormalization,
7349        weights: Option<&[f64]>,
7350    ) -> IgraphResult<Vec<Vec<f64>>> {
7351        crate::algorithms::properties::laplacian::get_laplacian(self, mode, normalization, weights)
7352    }
7353
7354    /// Stochastic (transition) matrix of the graph.
7355    ///
7356    /// Each row (or column, if `column_wise` is true) sums to 1.
7357    ///
7358    /// ```
7359    /// use rust_igraph::Graph;
7360    ///
7361    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
7362    /// let s = g.get_stochastic(false).unwrap();
7363    /// let row_sum: f64 = s[0].iter().sum();
7364    /// assert!((row_sum - 1.0).abs() < 1e-10);
7365    /// ```
7366    pub fn get_stochastic(&self, column_wise: bool) -> IgraphResult<Vec<Vec<f64>>> {
7367        crate::algorithms::properties::stochastic::get_stochastic(self, column_wise, None)
7368    }
7369
7370    // ---- Spectral embedding (batch 7) ----
7371
7372    /// Adjacency spectral embedding into `no` dimensions.
7373    ///
7374    /// Embeds the graph via the leading eigenvalues/eigenvectors of the
7375    /// adjacency matrix.
7376    ///
7377    /// ```
7378    /// use rust_igraph::{Graph, SpectralWhich};
7379    ///
7380    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3),(3,0)], false, None).unwrap();
7381    /// let emb = g.adjacency_spectral_embedding(2, SpectralWhich::LargestMagnitude).unwrap();
7382    /// assert_eq!(emb.embedding.len(), 4); // one row per vertex
7383    /// ```
7384    pub fn adjacency_spectral_embedding(
7385        &self,
7386        no: usize,
7387        which: crate::SpectralWhich,
7388    ) -> IgraphResult<crate::AdjacencySpectralEmbeddingResult> {
7389        crate::algorithms::embedding::adjacency_spectral_embedding::adjacency_spectral_embedding(
7390            self, no, None, which, true, None,
7391        )
7392    }
7393
7394    /// Laplacian spectral embedding into `no` dimensions.
7395    ///
7396    /// ```
7397    /// use rust_igraph::{Graph, SpectralWhich, LaplacianType};
7398    ///
7399    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3),(3,0)], false, None).unwrap();
7400    /// let emb = g.laplacian_spectral_embedding(
7401    ///     2, SpectralWhich::SmallestAlgebraic, LaplacianType::DA,
7402    /// ).unwrap();
7403    /// assert_eq!(emb.embedding.len(), 4);
7404    /// ```
7405    pub fn laplacian_spectral_embedding(
7406        &self,
7407        no: usize,
7408        which: crate::SpectralWhich,
7409        lap_type: crate::LaplacianType,
7410    ) -> IgraphResult<crate::LaplacianSpectralEmbeddingResult> {
7411        crate::algorithms::embedding::laplacian_spectral_embedding::laplacian_spectral_embedding(
7412            self, no, None, which, lap_type, true,
7413        )
7414    }
7415
7416    /// Eigenvalues and eigenvectors of the adjacency matrix.
7417    ///
7418    /// ```
7419    /// use rust_igraph::{Graph, EigenWhich};
7420    ///
7421    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7422    /// let eig = g.eigen_adjacency(2, EigenWhich::LargestAlgebraic).unwrap();
7423    /// assert_eq!(eig.eigenvalues.len(), 2);
7424    /// ```
7425    pub fn eigen_adjacency(
7426        &self,
7427        nev: usize,
7428        which: crate::EigenWhich,
7429    ) -> IgraphResult<crate::EigenDecomposition> {
7430        crate::algorithms::eigen::adjacency::eigen_adjacency(self, nev, which)
7431    }
7432
7433    // ---- Additional algorithms (batch 7) ----
7434
7435    /// Feedback vertex set — a minimal set of vertices whose removal
7436    /// makes the graph acyclic.
7437    ///
7438    /// ```
7439    /// use rust_igraph::{Graph, FvsAlgorithm};
7440    ///
7441    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], true, None).unwrap();
7442    /// let fvs = g.feedback_vertex_set(FvsAlgorithm::Greedy).unwrap();
7443    /// assert!(!fvs.is_empty()); // need to break the cycle
7444    /// ```
7445    pub fn feedback_vertex_set(&self, algo: crate::FvsAlgorithm) -> IgraphResult<Vec<VertexId>> {
7446        crate::algorithms::feedback_vertex_set::feedback_vertex_set(self, None, algo)
7447    }
7448
7449    /// Complement graph (all edges that are *not* in the original).
7450    ///
7451    /// Self-loops are excluded.
7452    ///
7453    /// ```
7454    /// use rust_igraph::Graph;
7455    ///
7456    /// let g = Graph::from_edges(&[(0,1)], false, Some(3)).unwrap();
7457    /// let c = g.complementer().unwrap();
7458    /// assert_eq!(c.ecount(), 2); // edges 0-2 and 1-2
7459    /// ```
7460    pub fn complementer(&self) -> IgraphResult<Graph> {
7461        crate::algorithms::operators::complementer::complementer(self, false)
7462    }
7463
7464    /// Bipartite projection sizes without building the projected graphs.
7465    ///
7466    /// `types` assigns each vertex to one of two partitions (`false`/`true`).
7467    ///
7468    /// ```
7469    /// use rust_igraph::Graph;
7470    ///
7471    /// // K_{2,3} bipartite graph
7472    /// let g = Graph::from_edges(
7473    ///     &[(0,2),(0,3),(0,4),(1,2),(1,3),(1,4)], false, None
7474    /// ).unwrap();
7475    /// let types = vec![false, false, true, true, true];
7476    /// let sz = g.bipartite_projection_size(&types).unwrap();
7477    /// assert_eq!(sz.vcount1, 2);
7478    /// assert_eq!(sz.vcount2, 3);
7479    /// ```
7480    pub fn bipartite_projection_size(
7481        &self,
7482        types: &[bool],
7483    ) -> IgraphResult<crate::BipartiteProjectionSize> {
7484        crate::algorithms::operators::bipartite_projection_size::bipartite_projection_size(
7485            self, types,
7486        )
7487    }
7488
7489    /// Unfold the graph into a tree by BFS from root vertices.
7490    ///
7491    /// Returns the unfolded tree and a mapping from new to old vertex ids.
7492    ///
7493    /// ```
7494    /// use rust_igraph::{Graph, DegreeMode, DijkstraMode, UnfoldTreeResult};
7495    ///
7496    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7497    /// let r = g.unfold_tree(&[0], DegreeMode::All).unwrap();
7498    /// assert!(r.tree.is_tree(DijkstraMode::All).unwrap().is_some());
7499    /// assert!(!r.vertex_index.is_empty());
7500    /// ```
7501    pub fn unfold_tree(
7502        &self,
7503        roots: &[VertexId],
7504        mode: crate::DegreeMode,
7505    ) -> IgraphResult<crate::UnfoldTreeResult> {
7506        crate::algorithms::properties::unfold_tree::unfold_tree(self, roots, mode)
7507    }
7508
7509    // ---- Analysis / flow / isomorphism (batch 8) ----
7510
7511    /// S-t edge connectivity between two vertices.
7512    ///
7513    /// ```
7514    /// use rust_igraph::Graph;
7515    ///
7516    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,3),(2,3)], false, None).unwrap();
7517    /// let k = g.st_edge_connectivity(0, 3).unwrap();
7518    /// assert_eq!(k, 2);
7519    /// ```
7520    pub fn st_edge_connectivity(&self, source: u32, target: u32) -> IgraphResult<i64> {
7521        crate::st_edge_connectivity(self, source, target)
7522    }
7523
7524    /// Vertex-disjoint paths between two vertices.
7525    ///
7526    /// ```
7527    /// use rust_igraph::Graph;
7528    ///
7529    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,3),(2,3)], false, None).unwrap();
7530    /// let n = g.vertex_disjoint_paths(0, 3).unwrap();
7531    /// assert_eq!(n, 2);
7532    /// ```
7533    pub fn vertex_disjoint_paths(&self, source: u32, target: u32) -> IgraphResult<i64> {
7534        crate::vertex_disjoint_paths(self, source, target)
7535    }
7536
7537    /// Test isomorphism using BLISS canonical labeling.
7538    ///
7539    /// ```
7540    /// use rust_igraph::{Graph, full_graph};
7541    ///
7542    /// let g1 = full_graph(4, false, false).unwrap();
7543    /// let g2 = full_graph(4, false, false).unwrap();
7544    /// let result = g1.isomorphic_bliss(&g2, None, None).unwrap();
7545    /// assert!(result.iso);
7546    /// ```
7547    pub fn isomorphic_bliss(
7548        &self,
7549        other: &Graph,
7550        colors1: Option<&[u32]>,
7551        colors2: Option<&[u32]>,
7552    ) -> IgraphResult<crate::Vf2Isomorphism> {
7553        crate::isomorphic_bliss(self, other, colors1, colors2)
7554    }
7555
7556    /// LAD subgraph isomorphism test.
7557    ///
7558    /// ```
7559    /// use rust_igraph::Graph;
7560    ///
7561    /// let target = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3)], false, None).unwrap();
7562    /// let pattern = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7563    /// let iso = target.subisomorphic_lad(&pattern, false, None).unwrap();
7564    /// assert!(iso.iso);
7565    /// ```
7566    pub fn subisomorphic_lad(
7567        &self,
7568        pattern: &Graph,
7569        induced: bool,
7570        domains: Option<&[Vec<u32>]>,
7571    ) -> IgraphResult<crate::LadSubisomorphism> {
7572        crate::subisomorphic_lad(pattern, self, domains, induced)
7573    }
7574
7575    /// Betweenness centrality for a subset of vertices.
7576    ///
7577    /// ```
7578    /// use rust_igraph::Graph;
7579    ///
7580    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7581    /// let bc = g.betweenness_subset(&[0, 1], &[2, 3]).unwrap();
7582    /// assert_eq!(bc.len(), 4);
7583    /// ```
7584    pub fn betweenness_subset(&self, sources: &[u32], targets: &[u32]) -> IgraphResult<Vec<f64>> {
7585        crate::betweenness_subset(self, sources, targets, self.is_directed())
7586    }
7587
7588    /// Edge betweenness centrality for a subset of vertices.
7589    ///
7590    /// ```
7591    /// use rust_igraph::Graph;
7592    ///
7593    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7594    /// let ebc = g.edge_betweenness_subset(&[0, 1], &[2, 3]).unwrap();
7595    /// assert_eq!(ebc.len(), 3);
7596    /// ```
7597    pub fn edge_betweenness_subset(
7598        &self,
7599        sources: &[u32],
7600        targets: &[u32],
7601    ) -> IgraphResult<Vec<f64>> {
7602        crate::edge_betweenness_subset(self, sources, targets, self.is_directed())
7603    }
7604
7605    /// Weighted edge betweenness community detection.
7606    ///
7607    /// ```
7608    /// use rust_igraph::Graph;
7609    ///
7610    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3),(3,4),(4,5),(5,3)], false, None).unwrap();
7611    /// let w = vec![1.0; g.ecount() as usize];
7612    /// let result = g.edge_betweenness_community_weighted(&w).unwrap();
7613    /// assert!(!result.merges.is_empty());
7614    /// ```
7615    pub fn edge_betweenness_community_weighted(
7616        &self,
7617        weights: &[f64],
7618    ) -> IgraphResult<crate::EdgeBetweennessResult> {
7619        crate::edge_betweenness_community_weighted(self, weights)
7620    }
7621
7622    /// Modularity matrix B = A - k*k'/2m.
7623    ///
7624    /// ```
7625    /// use rust_igraph::Graph;
7626    ///
7627    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7628    /// let b = g.modularity_matrix(None).unwrap();
7629    /// assert_eq!(b.len(), 3);
7630    /// ```
7631    pub fn modularity_matrix(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<Vec<f64>>> {
7632        crate::modularity_matrix(self, weights, 1.0, self.is_directed())
7633    }
7634
7635    /// Whether the graph is the same as another (structural equality).
7636    ///
7637    /// ```
7638    /// use rust_igraph::Graph;
7639    ///
7640    /// let g1 = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7641    /// let g2 = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7642    /// assert!(g1.is_same_graph(&g2));
7643    /// ```
7644    pub fn is_same_graph(&self, other: &Graph) -> bool {
7645        crate::is_same_graph(self, other)
7646    }
7647
7648    /// Mean distance (weighted).
7649    ///
7650    /// ```
7651    /// use rust_igraph::Graph;
7652    ///
7653    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7654    /// let w = vec![1.0, 2.0];
7655    /// let md = g.mean_distance_weighted(&w).unwrap();
7656    /// assert!(md.is_some());
7657    /// ```
7658    pub fn mean_distance_weighted(&self, weights: &[f64]) -> IgraphResult<Option<f64>> {
7659        crate::mean_distance_weighted(self, weights, self.is_directed(), true)
7660    }
7661
7662    // ---- Attribute system ----
7663
7664    /// Set a graph-level attribute.
7665    ///
7666    /// Overwrites any existing value with the same name.
7667    ///
7668    /// ```
7669    /// use rust_igraph::{Graph, AttributeValue};
7670    ///
7671    /// let mut g = Graph::with_vertices(0);
7672    /// g.set_graph_attribute("name", "test".into());
7673    /// assert_eq!(
7674    ///     g.graph_attribute("name").and_then(|v| v.as_str()),
7675    ///     Some("test"),
7676    /// );
7677    /// ```
7678    pub fn set_graph_attribute(&mut self, name: impl Into<String>, value: AttributeValue) {
7679        self.gattrs.insert(name.into(), value);
7680    }
7681
7682    /// Get a graph-level attribute by name.
7683    ///
7684    /// ```
7685    /// use rust_igraph::{Graph, AttributeValue};
7686    ///
7687    /// let g = Graph::with_vertices(0);
7688    /// assert!(g.graph_attribute("missing").is_none());
7689    /// ```
7690    #[must_use]
7691    pub fn graph_attribute(&self, name: &str) -> Option<&AttributeValue> {
7692        self.gattrs.get(name)
7693    }
7694
7695    /// Delete a graph-level attribute. Returns `true` if it existed.
7696    pub fn delete_graph_attribute(&mut self, name: &str) -> bool {
7697        self.gattrs.remove(name).is_some()
7698    }
7699
7700    /// Check whether a graph-level attribute exists.
7701    #[must_use]
7702    pub fn has_graph_attribute(&self, name: &str) -> bool {
7703        self.gattrs.contains_key(name)
7704    }
7705
7706    /// List all graph-level attribute names.
7707    ///
7708    /// ```
7709    /// use rust_igraph::{Graph, AttributeValue};
7710    ///
7711    /// let mut g = Graph::with_vertices(0);
7712    /// g.set_graph_attribute("name", "test".into());
7713    /// g.set_graph_attribute("year", 2024.0.into());
7714    /// let names = g.graph_attribute_names();
7715    /// assert!(names.contains(&"name"));
7716    /// assert!(names.contains(&"year"));
7717    /// ```
7718    #[must_use]
7719    pub fn graph_attribute_names(&self) -> Vec<&str> {
7720        self.gattrs.keys().map(String::as_str).collect()
7721    }
7722
7723    /// Set a vertex attribute for a single vertex.
7724    ///
7725    /// Creates the attribute vector if it doesn't exist. New entries
7726    /// for other vertices are filled with a type-appropriate default.
7727    ///
7728    /// ```
7729    /// use rust_igraph::{Graph, AttributeValue};
7730    ///
7731    /// let mut g = Graph::with_vertices(3);
7732    /// g.set_vertex_attribute("label", 0, "Alice".into()).unwrap();
7733    /// g.set_vertex_attribute("label", 1, "Bob".into()).unwrap();
7734    /// assert_eq!(
7735    ///     g.vertex_attribute("label", 0).and_then(|v| v.as_str()),
7736    ///     Some("Alice"),
7737    /// );
7738    /// ```
7739    pub fn set_vertex_attribute(
7740        &mut self,
7741        name: impl Into<String>,
7742        vertex: VertexId,
7743        value: AttributeValue,
7744    ) -> IgraphResult<()> {
7745        self.check_vertex(vertex)?;
7746        let n = self.n as usize;
7747        let key = name.into();
7748        let vals = self.vertex_attrs.entry(key).or_insert_with(|| {
7749            let default = value.default_for_same_type();
7750            vec![default; n]
7751        });
7752        vals[vertex as usize] = value;
7753        Ok(())
7754    }
7755
7756    /// Set a vertex attribute for all vertices at once.
7757    ///
7758    /// The `values` slice must have length equal to `vcount()`.
7759    ///
7760    /// ```
7761    /// use rust_igraph::{Graph, AttributeValue};
7762    ///
7763    /// let mut g = Graph::with_vertices(3);
7764    /// g.set_vertex_attribute_all(
7765    ///     "color",
7766    ///     vec![1.0.into(), 2.0.into(), 3.0.into()],
7767    /// ).unwrap();
7768    /// let colors = g.vertex_attributes("color").unwrap();
7769    /// assert_eq!(colors.len(), 3);
7770    /// ```
7771    pub fn set_vertex_attribute_all(
7772        &mut self,
7773        name: impl Into<String>,
7774        values: Vec<AttributeValue>,
7775    ) -> IgraphResult<()> {
7776        if values.len() != self.n as usize {
7777            return Err(IgraphError::InvalidArgument(format!(
7778                "attribute vector length {} does not match vcount {}",
7779                values.len(),
7780                self.n,
7781            )));
7782        }
7783        self.vertex_attrs.insert(name.into(), values);
7784        Ok(())
7785    }
7786
7787    /// Get a vertex attribute for a single vertex.
7788    #[must_use]
7789    pub fn vertex_attribute(&self, name: &str, vertex: VertexId) -> Option<&AttributeValue> {
7790        self.vertex_attrs
7791            .get(name)
7792            .and_then(|vals| vals.get(vertex as usize))
7793    }
7794
7795    /// Get the full vertex attribute vector by name.
7796    #[must_use]
7797    pub fn vertex_attributes(&self, name: &str) -> Option<&[AttributeValue]> {
7798        self.vertex_attrs.get(name).map(Vec::as_slice)
7799    }
7800
7801    /// Delete a vertex attribute. Returns `true` if it existed.
7802    pub fn delete_vertex_attribute(&mut self, name: &str) -> bool {
7803        self.vertex_attrs.remove(name).is_some()
7804    }
7805
7806    /// Check whether a vertex attribute exists.
7807    #[must_use]
7808    pub fn has_vertex_attribute(&self, name: &str) -> bool {
7809        self.vertex_attrs.contains_key(name)
7810    }
7811
7812    /// List all vertex attribute names.
7813    ///
7814    /// ```
7815    /// use rust_igraph::{Graph, AttributeValue};
7816    ///
7817    /// let mut g = Graph::with_vertices(2);
7818    /// g.set_vertex_attribute("name", 0, "A".into()).unwrap();
7819    /// assert!(g.vertex_attribute_names().contains(&"name"));
7820    /// ```
7821    #[must_use]
7822    pub fn vertex_attribute_names(&self) -> Vec<&str> {
7823        self.vertex_attrs.keys().map(String::as_str).collect()
7824    }
7825
7826    /// Set an edge attribute for a single edge.
7827    ///
7828    /// Creates the attribute vector if it doesn't exist. New entries
7829    /// for other edges are filled with a type-appropriate default.
7830    ///
7831    /// ```
7832    /// use rust_igraph::{Graph, AttributeValue};
7833    ///
7834    /// let mut g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7835    /// g.set_edge_attribute("weight", 0, 1.5.into()).unwrap();
7836    /// assert_eq!(
7837    ///     g.edge_attribute("weight", 0).and_then(|v| v.as_f64()),
7838    ///     Some(1.5),
7839    /// );
7840    /// ```
7841    pub fn set_edge_attribute(
7842        &mut self,
7843        name: impl Into<String>,
7844        edge: EdgeId,
7845        value: AttributeValue,
7846    ) -> IgraphResult<()> {
7847        let m = self.ecount();
7848        if (edge as usize) >= m {
7849            return Err(IgraphError::EdgeOutOfRange {
7850                id: edge,
7851                m: u32::try_from(m).unwrap_or(u32::MAX),
7852            });
7853        }
7854        let key = name.into();
7855        let vals = self.edge_attrs.entry(key).or_insert_with(|| {
7856            let default = value.default_for_same_type();
7857            vec![default; m]
7858        });
7859        vals[edge as usize] = value;
7860        Ok(())
7861    }
7862
7863    /// Set an edge attribute for all edges at once.
7864    ///
7865    /// The `values` slice must have length equal to `ecount()`.
7866    ///
7867    /// ```
7868    /// use rust_igraph::{Graph, AttributeValue};
7869    ///
7870    /// let mut g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7871    /// g.set_edge_attribute_all(
7872    ///     "weight",
7873    ///     vec![1.0.into(), 2.0.into(), 3.0.into()],
7874    /// ).unwrap();
7875    /// let w = g.edge_attributes("weight").unwrap();
7876    /// assert_eq!(w.len(), 3);
7877    /// ```
7878    pub fn set_edge_attribute_all(
7879        &mut self,
7880        name: impl Into<String>,
7881        values: Vec<AttributeValue>,
7882    ) -> IgraphResult<()> {
7883        let m = self.ecount();
7884        if values.len() != m {
7885            return Err(IgraphError::InvalidArgument(format!(
7886                "attribute vector length {} does not match ecount {}",
7887                values.len(),
7888                m,
7889            )));
7890        }
7891        self.edge_attrs.insert(name.into(), values);
7892        Ok(())
7893    }
7894
7895    /// Get an edge attribute for a single edge.
7896    #[must_use]
7897    pub fn edge_attribute(&self, name: &str, edge: EdgeId) -> Option<&AttributeValue> {
7898        self.edge_attrs
7899            .get(name)
7900            .and_then(|vals| vals.get(edge as usize))
7901    }
7902
7903    /// Get the full edge attribute vector by name.
7904    #[must_use]
7905    pub fn edge_attributes(&self, name: &str) -> Option<&[AttributeValue]> {
7906        self.edge_attrs.get(name).map(Vec::as_slice)
7907    }
7908
7909    /// Delete an edge attribute. Returns `true` if it existed.
7910    pub fn delete_edge_attribute(&mut self, name: &str) -> bool {
7911        self.edge_attrs.remove(name).is_some()
7912    }
7913
7914    /// Check whether an edge attribute exists.
7915    #[must_use]
7916    pub fn has_edge_attribute(&self, name: &str) -> bool {
7917        self.edge_attrs.contains_key(name)
7918    }
7919
7920    /// List all edge attribute names.
7921    ///
7922    /// ```
7923    /// use rust_igraph::{Graph, AttributeValue};
7924    ///
7925    /// let mut g = Graph::from_edges(&[(0,1)], false, None).unwrap();
7926    /// g.set_edge_attribute("weight", 0, 1.0.into()).unwrap();
7927    /// assert!(g.edge_attribute_names().contains(&"weight"));
7928    /// ```
7929    #[must_use]
7930    pub fn edge_attribute_names(&self) -> Vec<&str> {
7931        self.edge_attrs.keys().map(String::as_str).collect()
7932    }
7933}
7934
7935impl std::fmt::Display for Graph {
7936    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7937        let kind = if self.directed {
7938            "Directed"
7939        } else {
7940            "Undirected"
7941        };
7942        write!(
7943            f,
7944            "{kind} graph with {} vertices and {} edges",
7945            self.n,
7946            self.ecount()
7947        )
7948    }
7949}
7950
7951/// Iterate over a graph's edges by reference.
7952///
7953/// Yields `(from, to)` pairs in edge-id order, enabling the idiomatic
7954/// `for (u, v) in &graph { ... }` pattern.
7955///
7956/// # Examples
7957///
7958/// ```
7959/// use rust_igraph::Graph;
7960///
7961/// let mut g = Graph::with_vertices(3);
7962/// g.add_edge(0, 1).unwrap();
7963/// g.add_edge(1, 2).unwrap();
7964///
7965/// let edges: Vec<_> = (&g).into_iter().collect();
7966/// assert_eq!(edges, vec![(0, 1), (1, 2)]);
7967/// ```
7968impl<'a> IntoIterator for &'a Graph {
7969    type Item = (VertexId, VertexId);
7970    type IntoIter = EdgeIter<'a>;
7971
7972    fn into_iter(self) -> Self::IntoIter {
7973        self.iter()
7974    }
7975}
7976
7977/// Construct an undirected graph from a slice of `(from, to)` edge pairs.
7978///
7979/// Vertex count is inferred from the maximum endpoint id (plus one).
7980/// This is a convenience for quick construction; for more control use
7981/// [`Graph::from_edges`] or [`GraphBuilder`](super::builder::GraphBuilder).
7982///
7983/// # Examples
7984///
7985/// ```
7986/// use rust_igraph::Graph;
7987///
7988/// let edges = vec![(0u32, 1), (1, 2), (2, 0)];
7989/// let g = Graph::try_from(edges.as_slice()).unwrap();
7990/// assert_eq!(g.vcount(), 3);
7991/// assert_eq!(g.ecount(), 3);
7992/// assert!(!g.is_directed());
7993/// ```
7994impl TryFrom<&[(VertexId, VertexId)]> for Graph {
7995    type Error = IgraphError;
7996
7997    fn try_from(edges: &[(VertexId, VertexId)]) -> IgraphResult<Self> {
7998        let n = match edges.iter().flat_map(|&(u, v)| [u, v]).max() {
7999            Some(m) => m
8000                .checked_add(1)
8001                .ok_or_else(|| IgraphError::InvalidArgument("vertex id overflow".to_owned()))?,
8002            None => 0,
8003        };
8004        let mut g = Self::new(n, false)?;
8005        g.add_edges(edges.to_vec())?;
8006        Ok(g)
8007    }
8008}
8009
8010/// Construct an undirected graph from a `Vec` of `(from, to)` edge pairs.
8011///
8012/// # Examples
8013///
8014/// ```
8015/// use rust_igraph::Graph;
8016///
8017/// let g = Graph::try_from(vec![(0u32, 1), (1, 2), (2, 3)]).unwrap();
8018/// assert_eq!(g.vcount(), 4);
8019/// assert_eq!(g.ecount(), 3);
8020/// ```
8021impl TryFrom<Vec<(VertexId, VertexId)>> for Graph {
8022    type Error = IgraphError;
8023
8024    fn try_from(edges: Vec<(VertexId, VertexId)>) -> IgraphResult<Self> {
8025        let n = match edges.iter().flat_map(|&(u, v)| [u, v]).max() {
8026            Some(m) => m
8027                .checked_add(1)
8028                .ok_or_else(|| IgraphError::InvalidArgument("vertex id overflow".to_owned()))?,
8029            None => 0,
8030        };
8031        let mut g = Self::new(n, false)?;
8032        g.add_edges(edges)?;
8033        Ok(g)
8034    }
8035}
8036
8037/// Collect edges from an iterator into an undirected graph.
8038///
8039/// Vertex count is inferred from the maximum endpoint id.
8040/// For directed graphs or explicit vertex counts, use [`Graph::from_edges`].
8041///
8042/// # Panics
8043///
8044/// Panics if a vertex id would overflow `u32::MAX`.
8045///
8046/// # Examples
8047///
8048/// ```
8049/// use rust_igraph::Graph;
8050///
8051/// let g: Graph = [(0u32, 1), (1, 2), (2, 0)].into_iter().collect();
8052/// assert_eq!(g.vcount(), 3);
8053/// assert_eq!(g.ecount(), 3);
8054/// ```
8055impl std::iter::FromIterator<(VertexId, VertexId)> for Graph {
8056    fn from_iter<I: IntoIterator<Item = (VertexId, VertexId)>>(iter: I) -> Self {
8057        let edges: Vec<(VertexId, VertexId)> = iter.into_iter().collect();
8058        Self::try_from(edges).expect("FromIterator: vertex id overflow or invalid edge")
8059    }
8060}
8061
8062/// Extend a graph by adding edges from an iterator.
8063///
8064/// New vertices are automatically created as needed. This enables
8065/// patterns like `graph.extend(new_edges)`.
8066///
8067/// # Panics
8068///
8069/// Panics if an edge endpoint exceeds the current vertex count and
8070/// cannot be added.
8071///
8072/// # Examples
8073///
8074/// ```
8075/// use rust_igraph::Graph;
8076///
8077/// let mut g = Graph::with_vertices(3);
8078/// g.extend([(0u32, 1), (1, 2)]);
8079/// assert_eq!(g.ecount(), 2);
8080///
8081/// // Extending with a vertex beyond current count grows the graph
8082/// g.extend([(2u32, 5)]);
8083/// assert_eq!(g.vcount(), 6);
8084/// assert_eq!(g.ecount(), 3);
8085/// ```
8086impl Extend<(VertexId, VertexId)> for Graph {
8087    fn extend<I: IntoIterator<Item = (VertexId, VertexId)>>(&mut self, iter: I) {
8088        let edges: Vec<(VertexId, VertexId)> = iter.into_iter().collect();
8089        if edges.is_empty() {
8090            return;
8091        }
8092        let max_id = edges
8093            .iter()
8094            .flat_map(|&(u, v)| [u, v])
8095            .max()
8096            .expect("non-empty edges");
8097        if max_id >= self.n {
8098            self.add_vertices(max_id - self.n + 1)
8099                .expect("Extend: failed to add vertices");
8100        }
8101        self.add_edges(edges).expect("Extend: failed to add edges");
8102    }
8103}
8104
8105/// Structural equality: two graphs are equal if they have the same
8106/// directedness, same vertex count, and the same sorted edge set.
8107///
8108/// This is *not* isomorphism — vertex ids must match exactly.
8109///
8110/// # Examples
8111///
8112/// ```
8113/// use rust_igraph::Graph;
8114///
8115/// let a = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
8116/// let b = Graph::from_edges(&[(1,2), (0,1)], false, None).unwrap();
8117/// assert_eq!(a, b); // same edges, different insertion order
8118///
8119/// let c = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
8120/// assert_ne!(a, c); // different directedness
8121/// ```
8122impl PartialEq for Graph {
8123    fn eq(&self, other: &Self) -> bool {
8124        if self.directed != other.directed || self.n != other.n || self.ecount() != other.ecount() {
8125            return false;
8126        }
8127        let mut self_edges: Vec<(VertexId, VertexId)> = self.iter().collect();
8128        let mut other_edges: Vec<(VertexId, VertexId)> = other.iter().collect();
8129        self_edges.sort_unstable();
8130        other_edges.sort_unstable();
8131        self_edges == other_edges
8132    }
8133}
8134
8135impl Eq for Graph {}
8136
8137/// Hash a graph by its structural content (directedness + vertex count +
8138/// sorted edge set).
8139///
8140/// This is consistent with the [`PartialEq`] impl: structurally equal
8141/// graphs produce the same hash.
8142impl std::hash::Hash for Graph {
8143    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
8144        self.directed.hash(state);
8145        self.n.hash(state);
8146        let mut edges: Vec<(VertexId, VertexId)> = self.iter().collect();
8147        edges.sort_unstable();
8148        edges.hash(state);
8149    }
8150}
8151
8152#[cfg(test)]
8153mod tests {
8154    use super::*;
8155
8156    #[test]
8157    fn empty_graph_counts() {
8158        let g = Graph::with_vertices(0);
8159        assert_eq!(g.vcount(), 0);
8160        assert_eq!(g.ecount(), 0);
8161        assert!(!g.is_directed());
8162    }
8163
8164    #[test]
8165    fn new_directed_flag() {
8166        let g = Graph::new(3, true).unwrap();
8167        assert!(g.is_directed());
8168        let g = Graph::new(3, false).unwrap();
8169        assert!(!g.is_directed());
8170    }
8171
8172    #[test]
8173    fn add_vertices_then_edges() {
8174        let mut g = Graph::with_vertices(3);
8175        g.add_edge(0, 1).unwrap();
8176        g.add_edge(1, 2).unwrap();
8177        assert_eq!(g.vcount(), 3);
8178        assert_eq!(g.ecount(), 2);
8179        assert_eq!(g.degree(1).unwrap(), 2);
8180        let mut nbrs = g.neighbors(1).unwrap();
8181        nbrs.sort_unstable();
8182        assert_eq!(nbrs, vec![0, 2]);
8183    }
8184
8185    #[test]
8186    fn out_of_range_vertex_errors() {
8187        let mut g = Graph::with_vertices(2);
8188        let err = g.add_edge(0, 5).unwrap_err();
8189        assert!(matches!(err, IgraphError::VertexOutOfRange { id: 5, n: 2 }));
8190    }
8191
8192    #[test]
8193    fn self_loop_counted_correctly() {
8194        let mut g = Graph::with_vertices(1);
8195        g.add_edge(0, 0).unwrap();
8196        assert_eq!(g.ecount(), 1);
8197        // Undirected self-loop: appears as both out and in, degree == 2.
8198        assert_eq!(g.degree(0).unwrap(), 2);
8199        let mut nbrs = g.neighbors(0).unwrap();
8200        nbrs.sort_unstable();
8201        assert_eq!(nbrs, vec![0, 0]);
8202    }
8203
8204    #[test]
8205    fn parallel_edges() {
8206        let mut g = Graph::with_vertices(2);
8207        g.add_edge(0, 1).unwrap();
8208        g.add_edge(0, 1).unwrap();
8209        assert_eq!(g.ecount(), 2);
8210        assert_eq!(g.degree(0).unwrap(), 2);
8211        assert_eq!(g.degree(1).unwrap(), 2);
8212    }
8213
8214    #[test]
8215    fn undirected_canonicalisation() {
8216        // Adding edges (1,0) and (0,1) — both stored canonically as (0,1).
8217        let mut g = Graph::with_vertices(2);
8218        g.add_edge(1, 0).unwrap();
8219        g.add_edge(0, 1).unwrap();
8220        assert_eq!(g.ecount(), 2);
8221        // Both vertices see each other as a neighbour twice.
8222        let mut n0 = g.neighbors(0).unwrap();
8223        let mut n1 = g.neighbors(1).unwrap();
8224        n0.sort_unstable();
8225        n1.sort_unstable();
8226        assert_eq!(n0, vec![1, 1]);
8227        assert_eq!(n1, vec![0, 0]);
8228    }
8229
8230    #[test]
8231    fn directed_neighbors_are_outgoing_only() {
8232        let mut g = Graph::new(3, true).unwrap();
8233        g.add_edge(0, 1).unwrap();
8234        g.add_edge(2, 0).unwrap();
8235        // Directed: neighbors(0) returns out-neighbours only.
8236        assert_eq!(g.neighbors(0).unwrap(), vec![1]);
8237        // Vertex 2 has out-edge to 0.
8238        assert_eq!(g.neighbors(2).unwrap(), vec![0]);
8239        // Vertex 1 has no out-edges.
8240        assert!(g.neighbors(1).unwrap().is_empty());
8241        // Degree counts both in and out for directed.
8242        assert_eq!(g.degree(0).unwrap(), 2); // out: 0->1, in: 2->0
8243        assert_eq!(g.degree(1).unwrap(), 1); // in: 0->1
8244        assert_eq!(g.degree(2).unwrap(), 1); // out: 2->0
8245    }
8246
8247    #[test]
8248    fn add_edges_batch_then_rebuild() {
8249        let mut g = Graph::with_vertices(4);
8250        g.add_edges(vec![(0, 1), (0, 2), (1, 2), (2, 3)]).unwrap();
8251        assert_eq!(g.ecount(), 4);
8252        // Degrees: 0->{1,2} d=2; 1->{0,2} d=2; 2->{0,1,3} d=3; 3->{2} d=1.
8253        assert_eq!(g.degree(0).unwrap(), 2);
8254        assert_eq!(g.degree(1).unwrap(), 2);
8255        assert_eq!(g.degree(2).unwrap(), 3);
8256        assert_eq!(g.degree(3).unwrap(), 1);
8257    }
8258
8259    #[test]
8260    fn clone_is_deep() {
8261        let mut g = Graph::with_vertices(3);
8262        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8263        let g2 = g.clone();
8264        // Mutate g; g2 must be unaffected.
8265        g.add_edge(0, 2).unwrap();
8266        assert_eq!(g.ecount(), 3);
8267        assert_eq!(g2.ecount(), 2);
8268    }
8269
8270    #[test]
8271    fn os_invariant_is_monotone() {
8272        let mut g = Graph::with_vertices(5);
8273        g.add_edges(vec![(0, 1), (0, 2), (3, 4), (1, 2)]).unwrap();
8274        // os should be non-decreasing and end at ecount.
8275        for w in g.os.windows(2) {
8276            assert!(w[0] <= w[1]);
8277        }
8278        assert_eq!(g.os[0], 0);
8279        assert_eq!(*g.os.last().unwrap() as usize, g.ecount());
8280    }
8281
8282    #[test]
8283    fn vertex_out_of_range_when_adding_edge() {
8284        let mut g = Graph::with_vertices(2);
8285        let e = g.add_edge(2, 0).unwrap_err();
8286        assert!(matches!(e, IgraphError::VertexOutOfRange { id: 2, n: 2 }));
8287        // Graph state must be unchanged after the failed add.
8288        assert_eq!(g.ecount(), 0);
8289    }
8290
8291    // -------- ALGO-CORE-001b: edge-id helpers + incident --------
8292
8293    #[test]
8294    fn edge_endpoints_round_trip() {
8295        let mut g = Graph::new(3, true).unwrap();
8296        g.add_edges(vec![(0, 1), (2, 0), (1, 2)]).unwrap();
8297        // Directed: order preserved. edge_id == position in from/to.
8298        assert_eq!(g.edge(0).unwrap(), (0, 1));
8299        assert_eq!(g.edge(1).unwrap(), (2, 0));
8300        assert_eq!(g.edge(2).unwrap(), (1, 2));
8301        assert_eq!(g.edge_source(1).unwrap(), 2);
8302        assert_eq!(g.edge_target(1).unwrap(), 0);
8303    }
8304
8305    #[test]
8306    fn edge_other_endpoint() {
8307        let mut g = Graph::with_vertices(3);
8308        g.add_edge(0, 2).unwrap();
8309        assert_eq!(g.edge_other(0, 0).unwrap(), 2);
8310        assert_eq!(g.edge_other(0, 2).unwrap(), 0);
8311        // Vertex not on the edge: error.
8312        let err = g.edge_other(0, 1).unwrap_err();
8313        assert!(matches!(err, IgraphError::InvalidArgument(_)));
8314    }
8315
8316    #[test]
8317    fn edge_out_of_range() {
8318        let mut g = Graph::with_vertices(2);
8319        g.add_edge(0, 1).unwrap();
8320        let err = g.edge(5).unwrap_err();
8321        assert!(matches!(err, IgraphError::EdgeOutOfRange { id: 5, m: 1 }));
8322    }
8323
8324    #[test]
8325    fn incident_returns_edge_ids_matching_neighbors_order() {
8326        let mut g = Graph::with_vertices(4);
8327        g.add_edges(vec![(0, 1), (0, 2), (3, 0)]).unwrap();
8328        let eids = g.incident(0).unwrap();
8329        // Expect three incident edges; resolving back to neighbours
8330        // must equal `neighbors(0)` exactly (same iteration order).
8331        let resolved: Vec<u32> = eids.iter().map(|&e| g.edge_other(e, 0).unwrap()).collect();
8332        assert_eq!(resolved, g.neighbors(0).unwrap());
8333    }
8334
8335    #[test]
8336    fn incident_self_loop_appears_twice_undirected() {
8337        let mut g = Graph::with_vertices(1);
8338        g.add_edge(0, 0).unwrap();
8339        let eids = g.incident(0).unwrap();
8340        // Undirected self-loop appears once on the out side and once on
8341        // the in side — same edge id, twice. Mirrors `neighbors`.
8342        assert_eq!(eids, vec![0, 0]);
8343        assert_eq!(g.degree(0).unwrap(), 2);
8344    }
8345
8346    #[test]
8347    fn incident_directed_returns_outgoing_only() {
8348        let mut g = Graph::new(3, true).unwrap();
8349        g.add_edges(vec![(0, 1), (2, 0)]).unwrap();
8350        // Directed `incident` mirrors directed `neighbors` (out only).
8351        assert_eq!(g.incident(0).unwrap(), vec![0]);
8352        assert_eq!(g.incident(2).unwrap(), vec![1]);
8353        assert!(g.incident(1).unwrap().is_empty());
8354    }
8355
8356    #[test]
8357    fn get_eid_undirected_finds_edge_either_way() {
8358        let mut g = Graph::with_vertices(3);
8359        g.add_edge(0, 1).unwrap(); // edge 0
8360        g.add_edge(1, 2).unwrap(); // edge 1
8361        assert_eq!(g.get_eid(0, 1).unwrap(), 0);
8362        assert_eq!(g.get_eid(1, 0).unwrap(), 0);
8363        assert_eq!(g.get_eid(1, 2).unwrap(), 1);
8364        assert_eq!(g.get_eid(2, 1).unwrap(), 1);
8365    }
8366
8367    #[test]
8368    fn get_eid_directed_respects_direction() {
8369        let mut g = Graph::new(3, true).unwrap();
8370        g.add_edge(0, 1).unwrap(); // edge 0
8371        assert_eq!(g.get_eid(0, 1).unwrap(), 0);
8372        assert!(g.get_eid(1, 0).is_err()); // reverse direction has no edge
8373    }
8374
8375    #[test]
8376    fn find_eid_returns_none_for_missing() {
8377        let mut g = Graph::with_vertices(3);
8378        g.add_edge(0, 1).unwrap();
8379        assert_eq!(g.find_eid(0, 2).unwrap(), None);
8380        assert!(g.find_eid(0, 99).is_err()); // out-of-range vertex
8381    }
8382
8383    #[test]
8384    fn get_eid_self_loop() {
8385        let mut g = Graph::with_vertices(2);
8386        g.add_edge(0, 0).unwrap(); // self-loop, edge 0
8387        g.add_edge(0, 1).unwrap(); // edge 1
8388        assert_eq!(g.get_eid(0, 0).unwrap(), 0);
8389        assert_eq!(g.get_eid(0, 1).unwrap(), 1);
8390    }
8391
8392    #[test]
8393    fn get_all_eids_between_returns_all_parallel() {
8394        let mut g = Graph::with_vertices(2);
8395        g.add_edge(0, 1).unwrap(); // edge 0
8396        g.add_edge(0, 1).unwrap(); // edge 1
8397        g.add_edge(0, 1).unwrap(); // edge 2
8398        let eids = g.get_all_eids_between(0, 1).unwrap();
8399        assert_eq!(eids, vec![0, 1, 2]);
8400        // Reverse direction yields the same edges on undirected.
8401        let eids = g.get_all_eids_between(1, 0).unwrap();
8402        assert_eq!(eids, vec![0, 1, 2]);
8403    }
8404
8405    #[test]
8406    fn get_all_eids_between_directed_one_way_only() {
8407        let mut g = Graph::new(2, true).unwrap();
8408        g.add_edge(0, 1).unwrap(); // edge 0
8409        g.add_edge(0, 1).unwrap(); // edge 1 (parallel)
8410        assert_eq!(g.get_all_eids_between(0, 1).unwrap(), vec![0, 1]);
8411        // Reverse direction has no edges in directed graph.
8412        assert_eq!(g.get_all_eids_between(1, 0).unwrap(), Vec::<EdgeId>::new());
8413    }
8414
8415    #[test]
8416    fn get_eid_returns_lowest_id_for_parallel() {
8417        // Spec: with multiple edges, get_eid always returns the same
8418        // edge id (matches upstream's "ignored multi-edges" guarantee).
8419        // Our impl returns the lowest from the bucket.
8420        let mut g = Graph::with_vertices(2);
8421        g.add_edge(0, 1).unwrap(); // edge 0
8422        g.add_edge(0, 1).unwrap(); // edge 1
8423        assert_eq!(g.get_eid(0, 1).unwrap(), 0);
8424    }
8425
8426    // -------- ALGO-CORE-001c: delete_edges + delete_vertices --------
8427
8428    #[test]
8429    fn delete_edges_empty_input_is_noop() {
8430        let mut g = Graph::with_vertices(3);
8431        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8432        g.delete_edges(&[]).unwrap();
8433        assert_eq!(g.ecount(), 2);
8434        assert_eq!(g.degree(1).unwrap(), 2);
8435    }
8436
8437    #[test]
8438    fn delete_edges_single_edge_undirected() {
8439        let mut g = Graph::with_vertices(3);
8440        g.add_edges(vec![(0, 1), (1, 2), (0, 2)]).unwrap();
8441        // Remove edge id 1 (the (1,2) edge).
8442        g.delete_edges(&[1]).unwrap();
8443        assert_eq!(g.ecount(), 2);
8444        // Surviving edges renumbered to 0,1: (0,1) and (0,2).
8445        assert_eq!(g.find_eid(0, 1).unwrap(), Some(0));
8446        assert_eq!(g.find_eid(0, 2).unwrap(), Some(1));
8447        assert_eq!(g.find_eid(1, 2).unwrap(), None);
8448        // Degrees consistent post-rebuild.
8449        assert_eq!(g.degree(1).unwrap(), 1);
8450        assert_eq!(g.degree(2).unwrap(), 1);
8451    }
8452
8453    #[test]
8454    fn delete_edges_duplicate_ids_tolerated() {
8455        let mut g = Graph::with_vertices(3);
8456        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8457        g.delete_edges(&[0, 0, 0]).unwrap();
8458        assert_eq!(g.ecount(), 1);
8459        assert_eq!(g.find_eid(1, 2).unwrap(), Some(0));
8460    }
8461
8462    #[test]
8463    fn delete_edges_all_edges_leaves_isolated_vertices() {
8464        let mut g = Graph::with_vertices(3);
8465        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8466        g.delete_edges(&[0, 1]).unwrap();
8467        assert_eq!(g.ecount(), 0);
8468        assert_eq!(g.vcount(), 3);
8469        for v in 0..3 {
8470            assert_eq!(g.degree(v).unwrap(), 0);
8471        }
8472    }
8473
8474    #[test]
8475    fn delete_edges_out_of_range_errors_and_preserves_state() {
8476        let mut g = Graph::with_vertices(3);
8477        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8478        let err = g.delete_edges(&[5]).unwrap_err();
8479        assert!(matches!(err, IgraphError::EdgeOutOfRange { id: 5, m: 2 }));
8480        // Graph unchanged.
8481        assert_eq!(g.ecount(), 2);
8482    }
8483
8484    #[test]
8485    fn delete_edges_self_loop_directed() {
8486        let mut g = Graph::new(2, true).unwrap();
8487        g.add_edges(vec![(0, 0), (0, 1)]).unwrap();
8488        g.delete_edges(&[0]).unwrap(); // remove the self-loop
8489        assert_eq!(g.ecount(), 1);
8490        assert_eq!(g.degree(0).unwrap(), 1);
8491        assert_eq!(g.find_eid(0, 1).unwrap(), Some(0));
8492    }
8493
8494    #[test]
8495    fn delete_vertices_empty_input_is_noop() {
8496        let mut g = Graph::with_vertices(3);
8497        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8498        g.delete_vertices(&[]).unwrap();
8499        assert_eq!(g.vcount(), 3);
8500        assert_eq!(g.ecount(), 2);
8501    }
8502
8503    #[test]
8504    fn delete_vertices_single_renumbers() {
8505        let mut g = Graph::with_vertices(4);
8506        g.add_edges(vec![(0, 1), (1, 2), (2, 3), (0, 3)]).unwrap();
8507        // Remove vertex 1: edges (0,1) and (1,2) go with it. (2,3),(0,3)
8508        // survive but get renumbered: 2 → 1, 3 → 2.
8509        g.delete_vertices(&[1]).unwrap();
8510        assert_eq!(g.vcount(), 3);
8511        assert_eq!(g.ecount(), 2);
8512        // (2,3) → (1,2); (0,3) → (0,2).
8513        assert!(g.find_eid(1, 2).unwrap().is_some());
8514        assert!(g.find_eid(0, 2).unwrap().is_some());
8515        assert_eq!(g.find_eid(0, 1).unwrap(), None);
8516    }
8517
8518    #[test]
8519    fn delete_vertices_duplicate_ids_tolerated() {
8520        let mut g = Graph::with_vertices(3);
8521        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8522        g.delete_vertices(&[1, 1, 1]).unwrap();
8523        assert_eq!(g.vcount(), 2);
8524        assert_eq!(g.ecount(), 0);
8525    }
8526
8527    #[test]
8528    fn delete_vertices_all_yields_empty_graph() {
8529        let mut g = Graph::with_vertices(3);
8530        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8531        g.delete_vertices(&[0, 1, 2]).unwrap();
8532        assert_eq!(g.vcount(), 0);
8533        assert_eq!(g.ecount(), 0);
8534    }
8535
8536    #[test]
8537    fn delete_vertices_out_of_range_errors_and_preserves_state() {
8538        let mut g = Graph::with_vertices(3);
8539        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8540        let err = g.delete_vertices(&[5]).unwrap_err();
8541        assert!(matches!(err, IgraphError::VertexOutOfRange { id: 5, n: 3 }));
8542        assert_eq!(g.vcount(), 3);
8543        assert_eq!(g.ecount(), 2);
8544    }
8545
8546    #[test]
8547    fn delete_vertices_map_returns_correct_mappings() {
8548        let mut g = Graph::with_vertices(5);
8549        g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
8550        let (map, invmap) = g.delete_vertices_map(&[1, 3]).unwrap();
8551        // Removed: 1 and 3. Retained: 0 → 0, 2 → 1, 4 → 2.
8552        assert_eq!(map, vec![Some(0), None, Some(1), None, Some(2)]);
8553        assert_eq!(invmap, vec![0, 2, 4]);
8554        assert_eq!(g.vcount(), 3);
8555        // Only edges between retained vertices survive — none do here.
8556        assert_eq!(g.ecount(), 0);
8557    }
8558
8559    #[test]
8560    fn delete_vertices_directed_preserves_direction() {
8561        let mut g = Graph::new(4, true).unwrap();
8562        g.add_edges(vec![(0, 1), (1, 2), (2, 0), (3, 0)]).unwrap();
8563        g.delete_vertices(&[3]).unwrap();
8564        assert_eq!(g.vcount(), 3);
8565        assert!(g.is_directed());
8566        // Surviving directed edges (3 → 0) gone; (0,1),(1,2),(2,0) keep direction.
8567        assert!(g.get_eid(0, 1).is_ok());
8568        assert!(g.get_eid(1, 0).is_err()); // wrong direction
8569    }
8570
8571    #[test]
8572    fn delete_vertices_self_loop_on_removed_vertex() {
8573        let mut g = Graph::with_vertices(3);
8574        g.add_edges(vec![(0, 0), (0, 1), (1, 2)]).unwrap();
8575        g.delete_vertices(&[0]).unwrap();
8576        // Self-loop and edges to vertex 0 gone; only (1,2) → (0,1) survives.
8577        assert_eq!(g.vcount(), 2);
8578        assert_eq!(g.ecount(), 1);
8579        assert!(g.find_eid(0, 1).unwrap().is_some());
8580    }
8581
8582    #[test]
8583    fn delete_vertices_preserves_parallel_edges() {
8584        let mut g = Graph::with_vertices(3);
8585        g.add_edges(vec![(0, 1), (0, 1), (1, 2)]).unwrap();
8586        g.delete_vertices(&[2]).unwrap();
8587        assert_eq!(g.vcount(), 2);
8588        assert_eq!(g.ecount(), 2); // both parallel (0,1) edges retained
8589        assert_eq!(g.degree(0).unwrap(), 2);
8590        assert_eq!(g.degree(1).unwrap(), 2);
8591    }
8592
8593    #[test]
8594    fn add_edges_after_delete_works() {
8595        let mut g = Graph::with_vertices(4);
8596        g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
8597        g.delete_vertices(&[0]).unwrap(); // now n=3, vertices 0,1,2
8598        // Add a new edge and check indexes still work.
8599        g.add_edge(0, 2).unwrap();
8600        assert_eq!(g.ecount(), 3);
8601        assert_eq!(g.degree(0).unwrap(), 2); // (0,1)+(0,2)
8602        assert!(g.find_eid(0, 2).unwrap().is_some());
8603    }
8604
8605    #[test]
8606    fn from_adjacency_matrix_undirected_triangle() {
8607        let adj = vec![
8608            vec![0.0, 1.0, 1.0],
8609            vec![1.0, 0.0, 1.0],
8610            vec![1.0, 1.0, 0.0],
8611        ];
8612        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8613        assert_eq!(g.vcount(), 3);
8614        assert_eq!(g.ecount(), 3);
8615        assert!(!g.is_directed());
8616    }
8617
8618    #[test]
8619    fn from_adjacency_matrix_directed() {
8620        let adj = vec![
8621            vec![0.0, 1.0, 0.0],
8622            vec![0.0, 0.0, 1.0],
8623            vec![1.0, 0.0, 0.0],
8624        ];
8625        let g = Graph::from_adjacency_matrix(&adj, true).unwrap();
8626        assert_eq!(g.vcount(), 3);
8627        assert_eq!(g.ecount(), 3);
8628        assert!(g.is_directed());
8629    }
8630
8631    #[test]
8632    fn from_adjacency_matrix_with_self_loop() {
8633        let adj = vec![vec![1.0, 1.0], vec![1.0, 0.0]];
8634        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8635        assert_eq!(g.vcount(), 2);
8636        assert_eq!(g.ecount(), 2); // self-loop on 0 + edge 0-1
8637    }
8638
8639    #[test]
8640    fn from_adjacency_matrix_empty() {
8641        let adj: Vec<Vec<f64>> = Vec::new();
8642        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8643        assert_eq!(g.vcount(), 0);
8644        assert_eq!(g.ecount(), 0);
8645    }
8646
8647    #[test]
8648    fn from_adjacency_matrix_non_square_error() {
8649        let adj = vec![vec![0.0, 1.0], vec![1.0, 0.0, 1.0]];
8650        assert!(Graph::from_adjacency_matrix(&adj, false).is_err());
8651    }
8652
8653    #[test]
8654    fn from_adjacency_matrix_weighted_basic() {
8655        let adj = vec![
8656            vec![0.0, 2.5, 0.0],
8657            vec![2.5, 0.0, 1.0],
8658            vec![0.0, 1.0, 0.0],
8659        ];
8660        let (g, weights) = Graph::from_adjacency_matrix_weighted(&adj, false).unwrap();
8661        assert_eq!(g.vcount(), 3);
8662        assert_eq!(g.ecount(), 2);
8663        assert_eq!(weights.len(), 2);
8664        assert!((weights[0] - 2.5).abs() < 1e-10);
8665        assert!((weights[1] - 1.0).abs() < 1e-10);
8666    }
8667
8668    #[test]
8669    fn from_adjacency_matrix_weighted_directed() {
8670        let adj = vec![
8671            vec![0.0, 3.0, 0.0],
8672            vec![0.0, 0.0, 2.0],
8673            vec![1.5, 0.0, 0.0],
8674        ];
8675        let (g, weights) = Graph::from_adjacency_matrix_weighted(&adj, true).unwrap();
8676        assert_eq!(g.vcount(), 3);
8677        assert_eq!(g.ecount(), 3);
8678        assert_eq!(weights.len(), 3);
8679        assert!((weights[0] - 3.0).abs() < 1e-10);
8680        assert!((weights[1] - 2.0).abs() < 1e-10);
8681        assert!((weights[2] - 1.5).abs() < 1e-10);
8682    }
8683
8684    #[test]
8685    fn from_adjacency_matrix_multi_edges() {
8686        let adj = vec![vec![0.0, 3.0], vec![3.0, 0.0]];
8687        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8688        assert_eq!(g.vcount(), 2);
8689        assert_eq!(g.ecount(), 3); // 3 parallel edges
8690    }
8691
8692    #[test]
8693    fn from_adjacency_list_undirected_triangle() {
8694        let adj = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
8695        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8696        assert_eq!(g.vcount(), 3);
8697        assert_eq!(g.ecount(), 3);
8698        assert!(!g.is_directed());
8699    }
8700
8701    #[test]
8702    fn from_adjacency_list_directed() {
8703        let adj = vec![vec![1, 2], vec![2], vec![]];
8704        let g = Graph::from_adjacency_list(&adj, true).unwrap();
8705        assert_eq!(g.vcount(), 3);
8706        assert_eq!(g.ecount(), 3);
8707        assert!(g.is_directed());
8708    }
8709
8710    #[test]
8711    fn from_adjacency_list_empty() {
8712        let adj: Vec<Vec<u32>> = Vec::new();
8713        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8714        assert_eq!(g.vcount(), 0);
8715        assert_eq!(g.ecount(), 0);
8716    }
8717
8718    #[test]
8719    fn from_adjacency_list_isolated_vertices() {
8720        let adj = vec![vec![], vec![], vec![]];
8721        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8722        assert_eq!(g.vcount(), 3);
8723        assert_eq!(g.ecount(), 0);
8724    }
8725
8726    #[test]
8727    fn from_adjacency_list_self_loop() {
8728        let adj = vec![vec![0, 1], vec![0]];
8729        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8730        assert_eq!(g.vcount(), 2);
8731        assert_eq!(g.ecount(), 2); // self-loop on 0 + edge 0-1
8732    }
8733
8734    #[test]
8735    fn from_adjacency_list_out_of_range_error() {
8736        let adj = vec![vec![5]]; // only 1 vertex but references vertex 5
8737        assert!(Graph::from_adjacency_list(&adj, false).is_err());
8738    }
8739
8740    #[test]
8741    fn neighbors_iter_matches_neighbors_undirected() {
8742        let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (2, 3), (3, 4)], false, None).unwrap();
8743        for v in 0..g.vcount() {
8744            let from_vec = g.neighbors(v).unwrap();
8745            let from_iter: Vec<VertexId> = g.neighbors_iter(v).unwrap().collect();
8746            assert_eq!(from_vec, from_iter, "mismatch at vertex {v}");
8747        }
8748    }
8749
8750    #[test]
8751    fn neighbors_iter_matches_neighbors_directed() {
8752        let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (2, 3), (3, 4)], true, None).unwrap();
8753        for v in 0..g.vcount() {
8754            let from_vec = g.neighbors(v).unwrap();
8755            let from_iter: Vec<VertexId> = g.neighbors_iter(v).unwrap().collect();
8756            assert_eq!(from_vec, from_iter, "mismatch at vertex {v}");
8757        }
8758    }
8759
8760    #[test]
8761    fn neighbors_iter_exact_size() {
8762        let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false, None).unwrap();
8763        let iter = g.neighbors_iter(0).unwrap();
8764        assert_eq!(iter.len(), 3);
8765    }
8766
8767    #[test]
8768    fn neighbors_iter_invalid_vertex() {
8769        let g = Graph::with_vertices(3);
8770        assert!(g.neighbors_iter(5).is_err());
8771    }
8772}