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    /// Second-order biased random walk (`Node2Vec`) from `start`.
3501    ///
3502    /// `p` controls the likelihood of returning to the previous vertex;
3503    /// `q` controls exploration vs. exploitation (BFS-like vs DFS-like).
3504    /// When `p = q = 1.0` this is equivalent to a standard random walk.
3505    ///
3506    /// ```
3507    /// use rust_igraph::Graph;
3508    ///
3509    /// let g = Graph::from_edges(
3510    ///     &[(0,1), (1,2), (2,3), (3,0), (0,2)], false, None
3511    /// ).unwrap();
3512    /// let (vertices, _edges) = g.random_walk_node2vec(0, 10, 2.0, 0.5, 42).unwrap();
3513    /// assert_eq!(vertices[0], 0);
3514    /// ```
3515    #[allow(clippy::too_many_arguments)]
3516    pub fn random_walk_node2vec(
3517        &self,
3518        start: VertexId,
3519        steps: u32,
3520        p: f64,
3521        q: f64,
3522        seed: u64,
3523    ) -> IgraphResult<(Vec<VertexId>, Vec<EdgeId>)> {
3524        use crate::algorithms::paths::dijkstra::DijkstraMode;
3525        let mode = if self.directed {
3526            DijkstraMode::Out
3527        } else {
3528            DijkstraMode::All
3529        };
3530        crate::algorithms::paths::random_walk_node2vec::random_walk_node2vec(
3531            self, None, start, mode, steps, p, q, seed,
3532        )
3533    }
3534
3535    // ── Graph properties ─────────────────────────────────────────────
3536
3537    /// Compute the radius (minimum eccentricity) of the graph.
3538    ///
3539    /// Returns `None` for the empty graph.
3540    ///
3541    /// # Examples
3542    ///
3543    /// ```
3544    /// use rust_igraph::Graph;
3545    ///
3546    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3547    /// assert_eq!(g.radius().unwrap(), Some(2));
3548    /// ```
3549    pub fn radius(&self) -> IgraphResult<Option<u32>> {
3550        crate::algorithms::paths::radii::radius(self)
3551    }
3552
3553    /// Compute the eccentricity of every vertex.
3554    ///
3555    /// `result[v]` is the maximum shortest-path distance from vertex `v`.
3556    ///
3557    /// # Examples
3558    ///
3559    /// ```
3560    /// use rust_igraph::Graph;
3561    ///
3562    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3563    /// assert_eq!(g.eccentricity().unwrap(), vec![2, 1, 2]);
3564    /// ```
3565    pub fn eccentricity(&self) -> IgraphResult<Vec<u32>> {
3566        crate::algorithms::paths::radii::eccentricity(self)
3567    }
3568
3569    /// Compute the girth (length of the shortest cycle) of the graph.
3570    ///
3571    /// Returns `None` if the graph is acyclic.
3572    ///
3573    /// # Examples
3574    ///
3575    /// ```
3576    /// use rust_igraph::Graph;
3577    ///
3578    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3579    /// assert_eq!(g.girth().unwrap(), Some(3));
3580    /// ```
3581    pub fn girth(&self) -> IgraphResult<Option<u32>> {
3582        crate::algorithms::properties::girth::girth(self)
3583    }
3584
3585    /// Check whether the graph is a tree.
3586    ///
3587    /// Returns `Some(root)` where `root` is the first root vertex found,
3588    /// or `None` if the graph is not a tree. The `mode` parameter controls
3589    /// how edges are followed for directed graphs.
3590    ///
3591    /// # Examples
3592    ///
3593    /// ```
3594    /// use rust_igraph::{Graph, DijkstraMode};
3595    ///
3596    /// let g = Graph::from_edges(&[(0,1), (1,2), (1,3)], false, None).unwrap();
3597    /// assert!(g.is_tree(DijkstraMode::All).unwrap().is_some());
3598    /// ```
3599    pub fn is_tree(
3600        &self,
3601        mode: crate::algorithms::paths::dijkstra::DijkstraMode,
3602    ) -> IgraphResult<Option<VertexId>> {
3603        crate::algorithms::properties::is_tree::is_tree(self, mode)
3604    }
3605
3606    /// Check whether the directed graph is a DAG.
3607    ///
3608    /// Returns `false` for undirected graphs.
3609    ///
3610    /// # Examples
3611    ///
3612    /// ```
3613    /// use rust_igraph::Graph;
3614    ///
3615    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
3616    /// assert!(g.is_dag());
3617    /// ```
3618    pub fn is_dag(&self) -> bool {
3619        crate::algorithms::properties::is_dag::is_dag(self)
3620    }
3621
3622    /// Count the total number of triangles in the graph.
3623    ///
3624    /// # Examples
3625    ///
3626    /// ```
3627    /// use rust_igraph::Graph;
3628    ///
3629    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3630    /// assert_eq!(g.count_triangles().unwrap(), 1);
3631    /// ```
3632    pub fn count_triangles(&self) -> IgraphResult<u64> {
3633        crate::algorithms::properties::triangles::count_triangles(self)
3634    }
3635
3636    /// Compute the harmonic centrality of all vertices.
3637    ///
3638    /// Harmonic centrality of `v` is the sum of inverse distances
3639    /// from `v` to all other reachable vertices.
3640    ///
3641    /// # Examples
3642    ///
3643    /// ```
3644    /// use rust_igraph::Graph;
3645    ///
3646    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3647    /// let h = g.harmonic_centrality().unwrap();
3648    /// assert_eq!(h.len(), 3);
3649    /// ```
3650    pub fn harmonic_centrality(&self) -> IgraphResult<Vec<f64>> {
3651        crate::algorithms::properties::harmonic::harmonic_centrality(self)
3652    }
3653
3654    /// Compute the k-hop neighborhood size for every vertex.
3655    ///
3656    /// `result[v]` is the number of vertices within distance `order` from
3657    /// `v` (including `v` itself).
3658    ///
3659    /// # Examples
3660    ///
3661    /// ```
3662    /// use rust_igraph::Graph;
3663    ///
3664    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3665    /// let sizes = g.neighborhood_size(1).unwrap();
3666    /// assert_eq!(sizes, vec![2, 3, 3, 2]);
3667    /// ```
3668    pub fn neighborhood_size(&self, order: i32) -> IgraphResult<Vec<u32>> {
3669        crate::algorithms::properties::neighborhood::neighborhood_size(self, order)
3670    }
3671
3672    // ── Connectivity ─────────────────────────────────────────────────
3673
3674    /// Compute the vertex connectivity (minimum vertex cut) of the graph.
3675    ///
3676    /// # Examples
3677    ///
3678    /// ```
3679    /// use rust_igraph::Graph;
3680    ///
3681    /// let g = Graph::from_edges(
3682    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None,
3683    /// ).unwrap();
3684    /// assert_eq!(g.vertex_connectivity().unwrap(), 2);
3685    /// ```
3686    pub fn vertex_connectivity(&self) -> IgraphResult<i64> {
3687        crate::algorithms::flow::vertex_connectivity::vertex_connectivity(self, true)
3688    }
3689
3690    /// Compute the edge connectivity (minimum edge cut) of the graph.
3691    ///
3692    /// # Examples
3693    ///
3694    /// ```
3695    /// use rust_igraph::Graph;
3696    ///
3697    /// let g = Graph::from_edges(
3698    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None,
3699    /// ).unwrap();
3700    /// assert_eq!(g.edge_connectivity().unwrap(), 2);
3701    /// ```
3702    pub fn edge_connectivity(&self) -> IgraphResult<i64> {
3703        crate::algorithms::flow::edge_connectivity::edge_connectivity(self, true)
3704    }
3705
3706    /// Find all vertices reachable from `source`.
3707    ///
3708    /// # Examples
3709    ///
3710    /// ```
3711    /// use rust_igraph::{Graph, SubcomponentMode};
3712    ///
3713    /// let g = Graph::from_edges(&[(0,1), (1,2), (3,4)], false, None).unwrap();
3714    /// let comp = g.subcomponent(0, SubcomponentMode::All).unwrap();
3715    /// assert_eq!(comp.len(), 3);
3716    /// ```
3717    pub fn subcomponent(
3718        &self,
3719        source: VertexId,
3720        mode: crate::algorithms::connectivity::subcomponent::SubcomponentMode,
3721    ) -> IgraphResult<Vec<VertexId>> {
3722        crate::algorithms::connectivity::subcomponent::subcomponent(self, source, mode)
3723    }
3724
3725    // ── Cliques ──────────────────────────────────────────────────────
3726
3727    /// Find all cliques in the graph within a size range.
3728    ///
3729    /// # Examples
3730    ///
3731    /// ```
3732    /// use rust_igraph::Graph;
3733    ///
3734    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3735    /// let c = g.cliques(3, 3, None).unwrap();
3736    /// assert_eq!(c.len(), 1);
3737    /// ```
3738    pub fn cliques(
3739        &self,
3740        min_size: u32,
3741        max_size: u32,
3742        max_results: Option<usize>,
3743    ) -> IgraphResult<Vec<Vec<VertexId>>> {
3744        crate::algorithms::cliques::cliques(self, min_size, max_size, max_results)
3745    }
3746
3747    /// Find all maximal cliques in the graph.
3748    ///
3749    /// # Examples
3750    ///
3751    /// ```
3752    /// use rust_igraph::Graph;
3753    ///
3754    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3755    /// let mc = g.maximal_cliques().unwrap();
3756    /// assert_eq!(mc.len(), 1);
3757    /// assert_eq!(mc[0].len(), 3);
3758    /// ```
3759    pub fn maximal_cliques(&self) -> IgraphResult<Vec<Vec<VertexId>>> {
3760        crate::algorithms::cliques::maximal_cliques(self)
3761    }
3762
3763    /// Compute the independence number (max independent set size).
3764    ///
3765    /// # Examples
3766    ///
3767    /// ```
3768    /// use rust_igraph::Graph;
3769    ///
3770    /// // Triangle: independence number is 1 (can pick at most 1 non-adjacent vertex pair... no, 1)
3771    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3772    /// assert_eq!(g.independence_number().unwrap(), 1);
3773    /// ```
3774    pub fn independence_number(&self) -> IgraphResult<u32> {
3775        crate::algorithms::cliques::independence_number(self)
3776    }
3777
3778    // ── Operators ────────────────────────────────────────────────────
3779
3780    /// Permute the vertices of the graph.
3781    ///
3782    /// `permutation[v]` gives the new id for vertex `v`. Returns a new
3783    /// graph with edges reconnected accordingly.
3784    ///
3785    /// # Examples
3786    ///
3787    /// ```
3788    /// use rust_igraph::Graph;
3789    ///
3790    /// // permutation[new] = old: new 0 ← old 2, new 1 ← old 0, new 2 ← old 1
3791    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
3792    /// let p = g.permute_vertices(&[2, 0, 1]).unwrap();
3793    /// assert!(p.has_edge(1, 2));
3794    /// assert!(p.has_edge(2, 0));
3795    /// ```
3796    pub fn permute_vertices(&self, permutation: &[VertexId]) -> IgraphResult<Graph> {
3797        crate::algorithms::operators::permute_vertices::permute_vertices(self, permutation)
3798    }
3799
3800    // ── Layout ───────────────────────────────────────────────────────
3801
3802    /// Fruchterman-Reingold force-directed layout with default parameters.
3803    ///
3804    /// # Examples
3805    ///
3806    /// ```
3807    /// use rust_igraph::Graph;
3808    ///
3809    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3810    /// let coords = g.layout_fruchterman_reingold().unwrap();
3811    /// assert_eq!(coords.len(), 3);
3812    /// ```
3813    pub fn layout_fruchterman_reingold(&self) -> IgraphResult<Vec<(f64, f64)>> {
3814        use crate::algorithms::layout::fruchterman_reingold::FrParams;
3815        crate::algorithms::layout::fruchterman_reingold::layout_fruchterman_reingold(
3816            self,
3817            &FrParams::default(),
3818        )
3819    }
3820
3821    /// Kamada-Kawai spring-embedder layout with default parameters.
3822    ///
3823    /// # Examples
3824    ///
3825    /// ```
3826    /// use rust_igraph::Graph;
3827    ///
3828    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
3829    /// let coords = g.layout_kamada_kawai().unwrap();
3830    /// assert_eq!(coords.len(), 4);
3831    /// ```
3832    pub fn layout_kamada_kawai(&self) -> IgraphResult<Vec<[f64; 2]>> {
3833        use crate::algorithms::layout::kamada_kawai::KkParams;
3834        let params = KkParams::default_for(self.vcount() as usize);
3835        crate::algorithms::layout::kamada_kawai::layout_kamada_kawai(self, None, &params, None)
3836    }
3837
3838    /// `DrL` (Distributed Recursive Layout) with default options.
3839    ///
3840    /// # Examples
3841    ///
3842    /// ```
3843    /// use rust_igraph::Graph;
3844    ///
3845    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3846    /// let coords = g.layout_drl().unwrap();
3847    /// assert_eq!(coords.len(), 3);
3848    /// ```
3849    pub fn layout_drl(&self) -> IgraphResult<Vec<[f64; 2]>> {
3850        use crate::algorithms::layout::drl::DrlOptions;
3851        crate::algorithms::layout::drl::layout_drl(self, None, &DrlOptions::default(), None)
3852    }
3853
3854    /// Circular layout.
3855    ///
3856    /// # Examples
3857    ///
3858    /// ```
3859    /// use rust_igraph::Graph;
3860    ///
3861    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
3862    /// let coords = g.layout_circle();
3863    /// assert_eq!(coords.len(), 3);
3864    /// ```
3865    pub fn layout_circle(&self) -> Vec<(f64, f64)> {
3866        crate::algorithms::layout::simple::layout_circle(self, None)
3867    }
3868
3869    /// Random layout with the given RNG seed.
3870    ///
3871    /// # Examples
3872    ///
3873    /// ```
3874    /// use rust_igraph::Graph;
3875    ///
3876    /// let g = Graph::with_vertices(5);
3877    /// let coords = g.layout_random(42);
3878    /// assert_eq!(coords.len(), 5);
3879    /// ```
3880    pub fn layout_random(&self, seed: u64) -> Vec<(f64, f64)> {
3881        crate::algorithms::layout::simple::layout_random(self, seed)
3882    }
3883
3884    /// Grid layout.
3885    ///
3886    /// `width` specifies the number of columns. Pass 0 to auto-compute
3887    /// (ceil of square root of vertex count).
3888    ///
3889    /// # Examples
3890    ///
3891    /// ```
3892    /// use rust_igraph::Graph;
3893    ///
3894    /// let g = Graph::with_vertices(9);
3895    /// let coords = g.layout_grid(3);
3896    /// assert_eq!(coords.len(), 9);
3897    /// ```
3898    pub fn layout_grid(&self, width: i32) -> Vec<(f64, f64)> {
3899        crate::algorithms::layout::simple::layout_grid(self, width)
3900    }
3901
3902    // ── Triangle / local clustering ──────────────────────────────────
3903
3904    /// Per-vertex triangle count.
3905    ///
3906    /// `result[v]` is the number of triangles incident to vertex `v`.
3907    ///
3908    /// # Examples
3909    ///
3910    /// ```
3911    /// use rust_igraph::Graph;
3912    ///
3913    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3)], false, None).unwrap();
3914    /// let t = g.count_adjacent_triangles().unwrap();
3915    /// assert_eq!(t[0], 1);
3916    /// assert_eq!(t[3], 0);
3917    /// ```
3918    pub fn count_adjacent_triangles(&self) -> IgraphResult<Vec<u64>> {
3919        crate::algorithms::properties::triangles::count_adjacent_triangles(self)
3920    }
3921
3922    /// Per-vertex local clustering coefficient (local transitivity).
3923    ///
3924    /// `result[v]` is `None` for vertices with degree < 2.
3925    ///
3926    /// # Examples
3927    ///
3928    /// ```
3929    /// use rust_igraph::Graph;
3930    ///
3931    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3)], false, None).unwrap();
3932    /// let lcc = g.transitivity_local_undirected().unwrap();
3933    /// assert!(lcc[0].unwrap() > 0.9); // vertex 0 in triangle
3934    /// assert!(lcc[3].is_none());       // degree 1
3935    /// ```
3936    pub fn transitivity_local_undirected(&self) -> IgraphResult<Vec<Option<f64>>> {
3937        crate::algorithms::properties::triangles::transitivity_local_undirected(self)
3938    }
3939
3940    // ── Network metrics ──────────────────────────────────────────────
3941
3942    /// Reciprocity of a directed graph.
3943    ///
3944    /// Returns the fraction of edges that are reciprocated, or `None` for
3945    /// empty graphs.
3946    ///
3947    /// # Examples
3948    ///
3949    /// ```
3950    /// use rust_igraph::Graph;
3951    ///
3952    /// let g = Graph::from_edges(&[(0,1),(1,0),(1,2)], true, None).unwrap();
3953    /// let r = g.reciprocity().unwrap().unwrap();
3954    /// assert!((r - 2.0/3.0).abs() < 1e-12);
3955    /// ```
3956    pub fn reciprocity(&self) -> IgraphResult<Option<f64>> {
3957        crate::algorithms::properties::reciprocity::reciprocity(self)
3958    }
3959
3960    /// Burt's constraint for each vertex.
3961    ///
3962    /// # Examples
3963    ///
3964    /// ```
3965    /// use rust_igraph::Graph;
3966    ///
3967    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
3968    /// let c = g.constraint(None).unwrap();
3969    /// assert_eq!(c.len(), 3);
3970    /// ```
3971    pub fn constraint(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
3972        crate::algorithms::properties::constraint::constraint(self, weights)
3973    }
3974
3975    /// Whether the graph has multi-edges.
3976    ///
3977    /// # Examples
3978    ///
3979    /// ```
3980    /// use rust_igraph::Graph;
3981    ///
3982    /// let g = Graph::from_edges(&[(0,1),(0,1)], false, None).unwrap();
3983    /// assert!(g.has_multiple().unwrap());
3984    /// ```
3985    pub fn has_multiple(&self) -> IgraphResult<bool> {
3986        crate::algorithms::properties::multiplicity::has_multiple(self)
3987    }
3988
3989    /// Per-edge multiplicity count.
3990    ///
3991    /// # Examples
3992    ///
3993    /// ```
3994    /// use rust_igraph::Graph;
3995    ///
3996    /// let g = Graph::from_edges(&[(0,1),(0,1),(1,2)], false, None).unwrap();
3997    /// let mc = g.count_multiple().unwrap();
3998    /// assert_eq!(mc[0], 2);
3999    /// assert_eq!(mc[2], 1);
4000    /// ```
4001    pub fn count_multiple(&self) -> IgraphResult<Vec<usize>> {
4002        crate::algorithms::properties::multiplicity::count_multiple(self)
4003    }
4004
4005    /// Test whether two vertices are adjacent.
4006    ///
4007    /// # Examples
4008    ///
4009    /// ```
4010    /// use rust_igraph::Graph;
4011    ///
4012    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
4013    /// assert!(g.are_adjacent(0, 1).unwrap());
4014    /// assert!(!g.are_adjacent(0, 2).unwrap());
4015    /// ```
4016    pub fn are_adjacent(&self, v1: VertexId, v2: VertexId) -> IgraphResult<bool> {
4017        crate::algorithms::properties::are_adjacent::are_adjacent(self, v1, v2)
4018    }
4019
4020    // ── Motifs ───────────────────────────────────────────────────────
4021
4022    /// Triad census of a directed graph.
4023    ///
4024    /// # Examples
4025    ///
4026    /// ```
4027    /// use rust_igraph::{Graph, TriadType};
4028    ///
4029    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], true, None).unwrap();
4030    /// let tc = g.triad_census().unwrap();
4031    /// assert!(tc.get(TriadType::T030C) > 0.0);
4032    /// ```
4033    pub fn triad_census(
4034        &self,
4035    ) -> IgraphResult<crate::algorithms::motifs::triad_census::TriadCensus> {
4036        crate::algorithms::motifs::triad_census::triad_census(self)
4037    }
4038
4039    /// Dyad census of a directed graph.
4040    ///
4041    /// # Examples
4042    ///
4043    /// ```
4044    /// use rust_igraph::Graph;
4045    ///
4046    /// let g = Graph::from_edges(&[(0,1),(1,0),(1,2)], true, None).unwrap();
4047    /// let dc = g.dyad_census().unwrap();
4048    /// assert!((dc.mutual - 1.0).abs() < 1e-12);
4049    /// ```
4050    pub fn dyad_census(&self) -> IgraphResult<crate::algorithms::motifs::DyadCensus> {
4051        crate::algorithms::motifs::dyad_census(self)
4052    }
4053
4054    // ── Similarity ───────────────────────────────────────────────────
4055
4056    /// Jaccard similarity between all pairs of vertices.
4057    ///
4058    /// Returns a flattened `n × n` matrix (row-major).
4059    ///
4060    /// # Examples
4061    ///
4062    /// ```
4063    /// use rust_igraph::Graph;
4064    ///
4065    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
4066    /// let sim = g.similarity_jaccard().unwrap();
4067    /// assert_eq!(sim.len(), 9); // 3×3 matrix
4068    /// ```
4069    pub fn similarity_jaccard(&self) -> IgraphResult<Vec<f64>> {
4070        crate::algorithms::properties::similarity::similarity_jaccard(self)
4071    }
4072
4073    /// Co-citation scores for all vertex pairs.
4074    ///
4075    /// # Examples
4076    ///
4077    /// ```
4078    /// use rust_igraph::Graph;
4079    ///
4080    /// let g = Graph::from_edges(&[(0,2),(1,2)], true, None).unwrap();
4081    /// let cc = g.cocitation().unwrap();
4082    /// assert!(!cc.is_empty());
4083    /// ```
4084    pub fn cocitation(&self) -> IgraphResult<Vec<u32>> {
4085        crate::algorithms::properties::similarity::cocitation(self)
4086    }
4087
4088    // ── Graph structure recognizers ─────────────────────────────────
4089
4090    /// Check whether the graph is a cograph.
4091    ///
4092    /// # Examples
4093    ///
4094    /// ```
4095    /// use rust_igraph::Graph;
4096    ///
4097    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
4098    /// assert!(g.is_cograph().unwrap());
4099    /// ```
4100    pub fn is_cograph(&self) -> IgraphResult<bool> {
4101        crate::algorithms::properties::is_cograph::is_cograph(self)
4102    }
4103
4104    /// Check whether the graph is series-parallel.
4105    ///
4106    /// # Examples
4107    ///
4108    /// ```
4109    /// use rust_igraph::Graph;
4110    ///
4111    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4112    /// assert!(g.is_series_parallel().unwrap());
4113    /// ```
4114    pub fn is_series_parallel(&self) -> IgraphResult<bool> {
4115        crate::algorithms::properties::is_series_parallel::is_series_parallel(self)
4116    }
4117
4118    /// Check whether the graph is outerplanar.
4119    ///
4120    /// # Examples
4121    ///
4122    /// ```
4123    /// use rust_igraph::Graph;
4124    ///
4125    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4126    /// assert!(g.is_outerplanar().unwrap());
4127    /// ```
4128    pub fn is_outerplanar(&self) -> IgraphResult<bool> {
4129        crate::algorithms::properties::is_outerplanar::is_outerplanar(self)
4130    }
4131
4132    /// Check whether the graph is chordal.
4133    ///
4134    /// # Examples
4135    ///
4136    /// ```
4137    /// use rust_igraph::Graph;
4138    ///
4139    /// let g = Graph::from_edges(
4140    ///     &[(0,1), (1,2), (2,0), (0,3), (1,3), (2,3)], false, None
4141    /// ).unwrap();
4142    /// assert!(g.is_chordal().unwrap());
4143    /// ```
4144    pub fn is_chordal(&self) -> IgraphResult<bool> {
4145        let result = crate::algorithms::chordality::is_chordal(self, None)?;
4146        Ok(result.chordal)
4147    }
4148
4149    /// Check whether the graph is a forest (acyclic).
4150    ///
4151    /// # Examples
4152    ///
4153    /// ```
4154    /// use rust_igraph::Graph;
4155    ///
4156    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4157    /// assert!(g.is_forest().unwrap());
4158    /// ```
4159    pub fn is_forest(&self) -> IgraphResult<bool> {
4160        Ok(crate::algorithms::properties::is_forest::is_forest(
4161            self,
4162            crate::algorithms::paths::dijkstra::DijkstraMode::Out,
4163        )?
4164        .is_some())
4165    }
4166
4167    // ── Cycles and motifs ───────────────────────────────────────────
4168
4169    /// Compute a fundamental cycle basis of the graph.
4170    ///
4171    /// # Examples
4172    ///
4173    /// ```
4174    /// use rust_igraph::Graph;
4175    ///
4176    /// let g = Graph::from_edges(
4177    ///     &[(0,1), (1,2), (2,0)], false, None
4178    /// ).unwrap();
4179    /// let cycles = g.fundamental_cycles().unwrap();
4180    /// assert_eq!(cycles.len(), 1);
4181    /// ```
4182    pub fn fundamental_cycles(&self) -> IgraphResult<Vec<Vec<u32>>> {
4183        crate::algorithms::fundamental_cycles::fundamental_cycles(self, None, None)
4184    }
4185
4186    /// Compute a minimum weight cycle basis.
4187    ///
4188    /// # Examples
4189    ///
4190    /// ```
4191    /// use rust_igraph::Graph;
4192    ///
4193    /// let g = Graph::from_edges(
4194    ///     &[(0,1), (1,2), (2,0), (1,3), (3,0)], false, None
4195    /// ).unwrap();
4196    /// let basis = g.minimum_cycle_basis().unwrap();
4197    /// assert_eq!(basis.len(), 2);
4198    /// ```
4199    pub fn minimum_cycle_basis(&self) -> IgraphResult<Vec<Vec<u32>>> {
4200        crate::algorithms::minimum_cycle_basis::minimum_cycle_basis(self, None, false)
4201    }
4202
4203    // ── Cuts, covers, and sets ──────────────────────────────────────
4204
4205    /// Find a minimum feedback arc set.
4206    ///
4207    /// Returns edges whose removal makes the graph acyclic.
4208    ///
4209    /// # Examples
4210    ///
4211    /// ```
4212    /// use rust_igraph::Graph;
4213    ///
4214    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], true, None).unwrap();
4215    /// let fas = g.feedback_arc_set().unwrap();
4216    /// assert!(!fas.is_empty());
4217    /// ```
4218    pub fn feedback_arc_set(&self) -> IgraphResult<Vec<u32>> {
4219        crate::algorithms::feedback_arc_set::feedback_arc_set(
4220            self,
4221            None,
4222            crate::algorithms::feedback_arc_set::FasAlgorithm::EadesLinSmyth,
4223        )
4224    }
4225
4226    /// Find the maximum cut of the graph.
4227    ///
4228    /// # Examples
4229    ///
4230    /// ```
4231    /// use rust_igraph::Graph;
4232    ///
4233    /// let g = Graph::from_edges(
4234    ///     &[(0,1), (1,2), (2,3)], false, None
4235    /// ).unwrap();
4236    /// let result = g.maximum_cut().unwrap();
4237    /// assert!(result.cut_value > 0);
4238    /// ```
4239    pub fn maximum_cut(&self) -> IgraphResult<crate::algorithms::max_cut::MaxCutResult> {
4240        crate::algorithms::max_cut::maximum_cut(self)
4241    }
4242
4243    /// Find a minimum vertex cover.
4244    ///
4245    /// # Examples
4246    ///
4247    /// ```
4248    /// use rust_igraph::Graph;
4249    ///
4250    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4251    /// let cover = g.minimum_vertex_cover().unwrap();
4252    /// assert!(cover.contains(&1));
4253    /// ```
4254    pub fn minimum_vertex_cover(&self) -> IgraphResult<Vec<u32>> {
4255        crate::algorithms::vertex_cover::minimum_vertex_cover(self)
4256    }
4257
4258    /// Find a minimum edge cover.
4259    ///
4260    /// # Examples
4261    ///
4262    /// ```
4263    /// use rust_igraph::Graph;
4264    ///
4265    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4266    /// let cover = g.minimum_edge_cover().unwrap();
4267    /// assert!(!cover.is_empty());
4268    /// ```
4269    pub fn minimum_edge_cover(&self) -> IgraphResult<Vec<u32>> {
4270        crate::algorithms::edge_cover::minimum_edge_cover(self)
4271    }
4272
4273    /// Find a maximum independent set.
4274    ///
4275    /// # Examples
4276    ///
4277    /// ```
4278    /// use rust_igraph::Graph;
4279    ///
4280    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4281    /// let mis = g.maximum_independent_set().unwrap();
4282    /// assert_eq!(mis.len(), 2);
4283    /// ```
4284    pub fn maximum_independent_set(&self) -> IgraphResult<Vec<u32>> {
4285        crate::algorithms::independent_set::maximum_independent_set(self)
4286    }
4287
4288    // ── Coloring ────────────────────────────────────────────────────
4289
4290    /// Greedy vertex coloring.
4291    ///
4292    /// # Examples
4293    ///
4294    /// ```
4295    /// use rust_igraph::Graph;
4296    ///
4297    /// let g = Graph::from_edges(
4298    ///     &[(0,1), (1,2), (2,0)], false, None
4299    /// ).unwrap();
4300    /// let colors = g.vertex_coloring().unwrap();
4301    /// assert_eq!(colors.len(), 3);
4302    /// // Adjacent vertices must have different colors
4303    /// assert_ne!(colors[0], colors[1]);
4304    /// ```
4305    pub fn vertex_coloring(&self) -> IgraphResult<Vec<u32>> {
4306        crate::algorithms::coloring::vertex_coloring_greedy(
4307            self,
4308            crate::algorithms::coloring::GreedyColoringHeuristic::ColoredNeighbors,
4309        )
4310    }
4311
4312    // ── Spanning trees ──────────────────────────────────────────────
4313
4314    /// Sample a random spanning tree.
4315    ///
4316    /// # Examples
4317    ///
4318    /// ```
4319    /// use rust_igraph::Graph;
4320    ///
4321    /// let g = Graph::from_edges(
4322    ///     &[(0,1), (1,2), (2,0), (1,3)], false, None
4323    /// ).unwrap();
4324    /// let edges = g.random_spanning_tree(42).unwrap();
4325    /// assert_eq!(edges.len(), 3);
4326    /// ```
4327    pub fn random_spanning_tree(&self, seed: u64) -> IgraphResult<Vec<u32>> {
4328        crate::algorithms::spanning::random_spanning_tree::random_spanning_tree(self, None, seed)
4329    }
4330
4331    // ── Community detection (extended) ──────────────────────────────
4332
4333    /// Edge betweenness community detection.
4334    ///
4335    /// # Examples
4336    ///
4337    /// ```
4338    /// use rust_igraph::Graph;
4339    ///
4340    /// let g = Graph::from_edges(
4341    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)],
4342    ///     false, None
4343    /// ).unwrap();
4344    /// let result = g.edge_betweenness_community().unwrap();
4345    /// assert!(!result.membership.is_empty());
4346    /// ```
4347    pub fn edge_betweenness_community(
4348        &self,
4349    ) -> IgraphResult<crate::algorithms::community::edge_betweenness_community::EdgeBetweennessResult>
4350    {
4351        crate::algorithms::community::edge_betweenness_community::edge_betweenness_community(self)
4352    }
4353
4354    // ── Isomorphism (canonical / BLISS) ─────────────────────────────
4355
4356    /// Compute a canonical vertex permutation.
4357    ///
4358    /// # Examples
4359    ///
4360    /// ```
4361    /// use rust_igraph::Graph;
4362    ///
4363    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4364    /// let perm = g.canonical_permutation().unwrap();
4365    /// assert_eq!(perm.len(), 3);
4366    /// ```
4367    pub fn canonical_permutation(&self) -> IgraphResult<Vec<u32>> {
4368        crate::algorithms::isomorphism::canonical::canonical_permutation::canonical_permutation(
4369            self, None,
4370        )
4371    }
4372
4373    /// Count automorphisms of the graph.
4374    ///
4375    /// # Examples
4376    ///
4377    /// ```
4378    /// use rust_igraph::Graph;
4379    ///
4380    /// // K3 has 3! = 6 automorphisms
4381    /// let g = Graph::from_edges(
4382    ///     &[(0,1), (1,2), (2,0)], false, None
4383    /// ).unwrap();
4384    /// let count = g.count_automorphisms().unwrap();
4385    /// assert!((count - 6.0).abs() < 1e-10);
4386    /// ```
4387    pub fn count_automorphisms(&self) -> IgraphResult<f64> {
4388        crate::algorithms::isomorphism::canonical::count_automorphisms::count_automorphisms(
4389            self, None,
4390        )
4391    }
4392
4393    /// Compute a generating set for the automorphism group.
4394    ///
4395    /// # Examples
4396    ///
4397    /// ```
4398    /// use rust_igraph::Graph;
4399    ///
4400    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4401    /// let gens = g.automorphism_group().unwrap();
4402    /// assert!(!gens.is_empty());
4403    /// ```
4404    pub fn automorphism_group(&self) -> IgraphResult<Vec<Vec<u32>>> {
4405        crate::algorithms::isomorphism::canonical::automorphism_group::automorphism_group(
4406            self, None,
4407        )
4408    }
4409
4410    // ── Epidemics ───────────────────────────────────────────────────
4411
4412    /// Run a single SIR (Susceptible-Infected-Recovered) simulation.
4413    ///
4414    /// `beta` is the infection rate, `gamma` is the recovery rate.
4415    ///
4416    /// # Examples
4417    ///
4418    /// ```
4419    /// use rust_igraph::Graph;
4420    ///
4421    /// let g = Graph::from_edges(
4422    ///     &[(0,1), (1,2), (2,3), (3,4)], false, None
4423    /// ).unwrap();
4424    /// let result = g.sir(0.5, 0.1, 1, 42).unwrap();
4425    /// assert!(!result.is_empty());
4426    /// ```
4427    pub fn sir(
4428        &self,
4429        beta: f64,
4430        gamma: f64,
4431        no_sim: usize,
4432        seed: u64,
4433    ) -> IgraphResult<Vec<crate::algorithms::epidemics::Sir>> {
4434        crate::algorithms::epidemics::sir(self, beta, gamma, no_sim, seed)
4435    }
4436
4437    // ── Spanner ─────────────────────────────────────────────────────
4438
4439    /// Compute a graph spanner with the given stretch factor.
4440    ///
4441    /// Returns edge indices forming the spanner subgraph.
4442    ///
4443    /// # Examples
4444    ///
4445    /// ```
4446    /// use rust_igraph::Graph;
4447    ///
4448    /// let g = Graph::from_edges(
4449    ///     &[(0,1), (1,2), (2,0), (1,3)], false, None
4450    /// ).unwrap();
4451    /// let edges = g.spanner(3.0).unwrap();
4452    /// assert!(!edges.is_empty());
4453    /// ```
4454    pub fn spanner(&self, stretch: f64) -> IgraphResult<Vec<u32>> {
4455        crate::algorithms::paths::spanner::spanner(self, stretch, None)
4456    }
4457
4458    // ── Graph recognizers ─────────────────────────────────────────
4459
4460    /// Check whether this graph is acyclic (a DAG for directed, forest for undirected).
4461    ///
4462    /// # Examples
4463    ///
4464    /// ```
4465    /// use rust_igraph::Graph;
4466    ///
4467    /// let tree = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4468    /// assert!(tree.is_acyclic());
4469    /// ```
4470    pub fn is_acyclic(&self) -> bool {
4471        crate::algorithms::properties::is_acyclic::is_acyclic(self)
4472    }
4473
4474    /// Check whether this graph is an apex forest.
4475    ///
4476    /// # Examples
4477    ///
4478    /// ```
4479    /// use rust_igraph::Graph;
4480    ///
4481    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4482    /// assert!(g.is_apex_forest().unwrap());
4483    /// ```
4484    pub fn is_apex_forest(&self) -> IgraphResult<bool> {
4485        crate::algorithms::properties::is_apex_forest::is_apex_forest(self)
4486    }
4487
4488    /// Check whether this graph is an apex tree.
4489    ///
4490    /// # Examples
4491    ///
4492    /// ```
4493    /// use rust_igraph::Graph;
4494    ///
4495    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4496    /// assert!(g.is_apex_tree().unwrap());
4497    /// ```
4498    pub fn is_apex_tree(&self) -> IgraphResult<bool> {
4499        crate::algorithms::properties::is_apex_tree::is_apex_tree(self)
4500    }
4501
4502    /// Check whether this graph is banner-free.
4503    ///
4504    /// # Examples
4505    ///
4506    /// ```
4507    /// use rust_igraph::Graph;
4508    ///
4509    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4510    /// assert!(g.is_banner_free().unwrap());
4511    /// ```
4512    pub fn is_banner_free(&self) -> IgraphResult<bool> {
4513        crate::algorithms::properties::is_banner_free::is_banner_free(self)
4514    }
4515
4516    /// Check whether this graph is a biclique.
4517    ///
4518    /// # Examples
4519    ///
4520    /// ```
4521    /// use rust_igraph::Graph;
4522    ///
4523    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,2), (1,3)], false, None).unwrap();
4524    /// assert!(g.is_biclique().unwrap());
4525    /// ```
4526    pub fn is_biclique(&self) -> IgraphResult<bool> {
4527        crate::algorithms::properties::is_biclique::is_biclique(self)
4528    }
4529
4530    /// Check whether this graph is biregular.
4531    ///
4532    /// # Examples
4533    ///
4534    /// ```
4535    /// use rust_igraph::Graph;
4536    ///
4537    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,2), (1,3)], false, None).unwrap();
4538    /// assert!(g.is_biregular().unwrap());
4539    /// ```
4540    pub fn is_biregular(&self) -> IgraphResult<bool> {
4541        crate::algorithms::properties::is_biregular::is_biregular(self)
4542    }
4543
4544    /// Check whether this graph is a block graph.
4545    ///
4546    /// # Examples
4547    ///
4548    /// ```
4549    /// use rust_igraph::Graph;
4550    ///
4551    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4552    /// assert!(g.is_block_graph().unwrap());
4553    /// ```
4554    pub fn is_block_graph(&self) -> IgraphResult<bool> {
4555        crate::algorithms::properties::is_block::is_block_graph(self)
4556    }
4557
4558    /// Check whether this graph is bowtie-free.
4559    ///
4560    /// # Examples
4561    ///
4562    /// ```
4563    /// use rust_igraph::Graph;
4564    ///
4565    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4566    /// assert!(g.is_bowtie_free().unwrap());
4567    /// ```
4568    pub fn is_bowtie_free(&self) -> IgraphResult<bool> {
4569        crate::algorithms::properties::is_bowtie_free::is_bowtie_free(self)
4570    }
4571
4572    /// Check whether this graph is bull-free.
4573    ///
4574    /// # Examples
4575    ///
4576    /// ```
4577    /// use rust_igraph::Graph;
4578    ///
4579    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4580    /// assert!(g.is_bull_free().unwrap());
4581    /// ```
4582    pub fn is_bull_free(&self) -> IgraphResult<bool> {
4583        crate::algorithms::properties::is_bull_free::is_bull_free(self)
4584    }
4585
4586    /// Check whether this graph is C4-free (contains no 4-cycle).
4587    ///
4588    /// # Examples
4589    ///
4590    /// ```
4591    /// use rust_igraph::Graph;
4592    ///
4593    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4594    /// assert!(g.is_c4_free().unwrap());
4595    /// ```
4596    pub fn is_c4_free(&self) -> IgraphResult<bool> {
4597        crate::algorithms::properties::is_c4_free::is_c4_free(self)
4598    }
4599
4600    /// Check whether this graph is C5-free.
4601    ///
4602    /// # Examples
4603    ///
4604    /// ```
4605    /// use rust_igraph::Graph;
4606    ///
4607    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4608    /// assert!(g.is_c5_free().unwrap());
4609    /// ```
4610    pub fn is_c5_free(&self) -> IgraphResult<bool> {
4611        crate::algorithms::properties::is_c5_free::is_c5_free(self)
4612    }
4613
4614    /// Check whether this graph is a cactus graph.
4615    ///
4616    /// # Examples
4617    ///
4618    /// ```
4619    /// use rust_igraph::Graph;
4620    ///
4621    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4622    /// assert!(g.is_cactus_graph().unwrap());
4623    /// ```
4624    pub fn is_cactus_graph(&self) -> IgraphResult<bool> {
4625        crate::algorithms::properties::is_cactus::is_cactus_graph(self)
4626    }
4627
4628    /// Check whether this graph is a caterpillar.
4629    ///
4630    /// # Examples
4631    ///
4632    /// ```
4633    /// use rust_igraph::Graph;
4634    ///
4635    /// let g = Graph::from_edges(&[(0,1), (1,2), (1,3)], false, None).unwrap();
4636    /// assert!(g.is_caterpillar().unwrap());
4637    /// ```
4638    pub fn is_caterpillar(&self) -> IgraphResult<bool> {
4639        crate::algorithms::properties::is_caterpillar::is_caterpillar(self)
4640    }
4641
4642    /// Check whether this graph is a chain graph.
4643    ///
4644    /// # Examples
4645    ///
4646    /// ```
4647    /// use rust_igraph::Graph;
4648    ///
4649    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,3)], false, None).unwrap();
4650    /// assert!(g.is_chain_graph().unwrap());
4651    /// ```
4652    pub fn is_chain_graph(&self) -> IgraphResult<bool> {
4653        crate::algorithms::properties::is_chain_graph::is_chain_graph(self)
4654    }
4655
4656    /// Check whether this graph is chordal bipartite.
4657    ///
4658    /// # Examples
4659    ///
4660    /// ```
4661    /// use rust_igraph::Graph;
4662    ///
4663    /// let g = Graph::from_edges(&[(0,2), (1,2)], false, None).unwrap();
4664    /// assert!(g.is_chordal_bipartite().unwrap());
4665    /// ```
4666    pub fn is_chordal_bipartite(&self) -> IgraphResult<bool> {
4667        crate::algorithms::properties::is_chordal_bipartite::is_chordal_bipartite(self)
4668    }
4669
4670    /// Check whether this graph is claw-free.
4671    ///
4672    /// # Examples
4673    ///
4674    /// ```
4675    /// use rust_igraph::Graph;
4676    ///
4677    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4678    /// assert!(g.is_claw_free().unwrap());
4679    /// ```
4680    pub fn is_claw_free(&self) -> IgraphResult<bool> {
4681        crate::algorithms::properties::is_claw_free::is_claw_free(self)
4682    }
4683
4684    /// Check whether this graph is a cluster graph (disjoint union of cliques).
4685    ///
4686    /// # Examples
4687    ///
4688    /// ```
4689    /// use rust_igraph::Graph;
4690    ///
4691    /// let g = Graph::from_edges(&[(0,1)], false, None).unwrap();
4692    /// assert!(g.is_cluster_graph().unwrap());
4693    /// ```
4694    pub fn is_cluster_graph(&self) -> IgraphResult<bool> {
4695        crate::algorithms::properties::is_cluster::is_cluster_graph(self)
4696    }
4697
4698    /// Check whether this graph is co-bipartite.
4699    ///
4700    /// # Examples
4701    ///
4702    /// ```
4703    /// use rust_igraph::Graph;
4704    ///
4705    /// let g = Graph::from_edges(&[(0,1)], false, None).unwrap();
4706    /// assert!(g.is_co_bipartite().unwrap());
4707    /// ```
4708    pub fn is_co_bipartite(&self) -> IgraphResult<bool> {
4709        crate::algorithms::properties::is_co_bipartite::is_co_bipartite(self)
4710    }
4711
4712    /// Check whether this graph is co-chordal.
4713    ///
4714    /// # Examples
4715    ///
4716    /// ```
4717    /// use rust_igraph::Graph;
4718    ///
4719    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4720    /// assert!(g.is_co_chordal().unwrap());
4721    /// ```
4722    pub fn is_co_chordal(&self) -> IgraphResult<bool> {
4723        crate::algorithms::properties::is_co_chordal::is_co_chordal(self)
4724    }
4725
4726    /// Check whether this graph is a complete graph.
4727    ///
4728    /// # Examples
4729    ///
4730    /// ```
4731    /// use rust_igraph::Graph;
4732    ///
4733    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4734    /// assert!(g.is_complete().unwrap());
4735    /// ```
4736    pub fn is_complete(&self) -> IgraphResult<bool> {
4737        crate::algorithms::properties::is_complete::is_complete(self)
4738    }
4739
4740    /// Check whether this graph is a complete bipartite graph.
4741    ///
4742    /// # Examples
4743    ///
4744    /// ```
4745    /// use rust_igraph::Graph;
4746    ///
4747    /// let g = Graph::from_edges(&[(0,2), (0,3), (1,2), (1,3)], false, None).unwrap();
4748    /// assert!(g.is_complete_bipartite().unwrap());
4749    /// ```
4750    pub fn is_complete_bipartite(&self) -> IgraphResult<bool> {
4751        crate::algorithms::properties::is_complete_bipartite::is_complete_bipartite(self)
4752    }
4753
4754    /// Check whether this graph is cricket-free.
4755    ///
4756    /// # Examples
4757    ///
4758    /// ```
4759    /// use rust_igraph::Graph;
4760    ///
4761    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4762    /// assert!(g.is_cricket_free().unwrap());
4763    /// ```
4764    pub fn is_cricket_free(&self) -> IgraphResult<bool> {
4765        crate::algorithms::properties::is_cricket_free::is_cricket_free(self)
4766    }
4767
4768    /// Check whether this graph is cubic (3-regular).
4769    ///
4770    /// # Examples
4771    ///
4772    /// ```
4773    /// use rust_igraph::{Graph, full_graph};
4774    ///
4775    /// let g = full_graph(4, false, false).unwrap();
4776    /// assert!(g.is_cubic().unwrap());
4777    /// ```
4778    pub fn is_cubic(&self) -> IgraphResult<bool> {
4779        crate::algorithms::properties::is_cubic::is_cubic(self)
4780    }
4781
4782    /// Check whether this graph is a cycle.
4783    ///
4784    /// # Examples
4785    ///
4786    /// ```
4787    /// use rust_igraph::{Graph, cycle_graph};
4788    ///
4789    /// let g = cycle_graph(5, false, false).unwrap();
4790    /// assert!(g.is_cycle().unwrap());
4791    /// ```
4792    pub fn is_cycle(&self) -> IgraphResult<bool> {
4793        crate::algorithms::properties::is_cycle::is_cycle(self)
4794    }
4795
4796    /// Check whether this graph is dart-free.
4797    ///
4798    /// # Examples
4799    ///
4800    /// ```
4801    /// use rust_igraph::Graph;
4802    ///
4803    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4804    /// assert!(g.is_dart_free().unwrap());
4805    /// ```
4806    pub fn is_dart_free(&self) -> IgraphResult<bool> {
4807        crate::algorithms::properties::is_dart_free::is_dart_free(self)
4808    }
4809
4810    /// Check whether this graph is diamond-free.
4811    ///
4812    /// # Examples
4813    ///
4814    /// ```
4815    /// use rust_igraph::Graph;
4816    ///
4817    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4818    /// assert!(g.is_diamond_free().unwrap());
4819    /// ```
4820    pub fn is_diamond_free(&self) -> IgraphResult<bool> {
4821        crate::algorithms::properties::is_diamond_free::is_diamond_free(self)
4822    }
4823
4824    /// Check whether this graph is distance-hereditary.
4825    ///
4826    /// # Examples
4827    ///
4828    /// ```
4829    /// use rust_igraph::Graph;
4830    ///
4831    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4832    /// assert!(g.is_distance_hereditary().unwrap());
4833    /// ```
4834    pub fn is_distance_hereditary(&self) -> IgraphResult<bool> {
4835        crate::algorithms::properties::is_distance_hereditary::is_distance_hereditary(self)
4836    }
4837
4838    /// Check whether this graph is Eulerian.
4839    ///
4840    /// # Examples
4841    ///
4842    /// ```
4843    /// use rust_igraph::Graph;
4844    ///
4845    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4846    /// let class = g.is_eulerian().unwrap();
4847    /// assert!(class.has_cycle);
4848    /// ```
4849    pub fn is_eulerian(
4850        &self,
4851    ) -> IgraphResult<crate::algorithms::paths::eulerian::EulerianClassification> {
4852        crate::algorithms::paths::eulerian::is_eulerian(self)
4853    }
4854
4855    /// Check whether this graph is fork-free.
4856    ///
4857    /// # Examples
4858    ///
4859    /// ```
4860    /// use rust_igraph::Graph;
4861    ///
4862    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4863    /// assert!(g.is_fork_free().unwrap());
4864    /// ```
4865    pub fn is_fork_free(&self) -> IgraphResult<bool> {
4866        crate::algorithms::properties::is_fork_free::is_fork_free(self)
4867    }
4868
4869    /// Check whether this graph is gem-free.
4870    ///
4871    /// # Examples
4872    ///
4873    /// ```
4874    /// use rust_igraph::Graph;
4875    ///
4876    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4877    /// assert!(g.is_gem_free().unwrap());
4878    /// ```
4879    pub fn is_gem_free(&self) -> IgraphResult<bool> {
4880        crate::algorithms::properties::is_gem_free::is_gem_free(self)
4881    }
4882
4883    /// Check whether this graph is geodetic.
4884    ///
4885    /// # Examples
4886    ///
4887    /// ```
4888    /// use rust_igraph::Graph;
4889    ///
4890    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4891    /// assert!(g.is_geodetic().unwrap());
4892    /// ```
4893    pub fn is_geodetic(&self) -> IgraphResult<bool> {
4894        crate::algorithms::properties::is_geodetic::is_geodetic(self)
4895    }
4896
4897    /// Check whether this graph is house-free.
4898    ///
4899    /// # Examples
4900    ///
4901    /// ```
4902    /// use rust_igraph::Graph;
4903    ///
4904    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4905    /// assert!(g.is_house_free().unwrap());
4906    /// ```
4907    pub fn is_house_free(&self) -> IgraphResult<bool> {
4908        crate::algorithms::properties::is_house_free::is_house_free(self)
4909    }
4910
4911    /// Check whether this graph is k-degenerate.
4912    ///
4913    /// # Examples
4914    ///
4915    /// ```
4916    /// use rust_igraph::Graph;
4917    ///
4918    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
4919    /// assert!(g.is_k_degenerate(1).unwrap());
4920    /// ```
4921    pub fn is_k_degenerate(&self, k: u32) -> IgraphResult<bool> {
4922        crate::algorithms::properties::is_k_degenerate::is_k_degenerate(self, k)
4923    }
4924
4925    /// Check whether this graph is a lobster.
4926    ///
4927    /// # Examples
4928    ///
4929    /// ```
4930    /// use rust_igraph::Graph;
4931    ///
4932    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4933    /// assert!(g.is_lobster().unwrap());
4934    /// ```
4935    pub fn is_lobster(&self) -> IgraphResult<bool> {
4936        crate::algorithms::properties::is_lobster::is_lobster(self)
4937    }
4938
4939    /// Check whether this graph is net-free.
4940    ///
4941    /// # Examples
4942    ///
4943    /// ```
4944    /// use rust_igraph::Graph;
4945    ///
4946    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4947    /// assert!(g.is_net_free().unwrap());
4948    /// ```
4949    pub fn is_net_free(&self) -> IgraphResult<bool> {
4950        crate::algorithms::properties::is_net_free::is_net_free(self)
4951    }
4952
4953    /// Check whether this graph is P5-free.
4954    ///
4955    /// # Examples
4956    ///
4957    /// ```
4958    /// use rust_igraph::Graph;
4959    ///
4960    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4961    /// assert!(g.is_p5_free().unwrap());
4962    /// ```
4963    pub fn is_p5_free(&self) -> IgraphResult<bool> {
4964        crate::algorithms::properties::is_p5_free::is_p5_free(self)
4965    }
4966
4967    /// Check whether this graph is a path.
4968    ///
4969    /// # Examples
4970    ///
4971    /// ```
4972    /// use rust_igraph::Graph;
4973    ///
4974    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
4975    /// assert!(g.is_path().unwrap());
4976    /// ```
4977    pub fn is_path(&self) -> IgraphResult<bool> {
4978        crate::algorithms::properties::is_path::is_path(self)
4979    }
4980
4981    /// Check whether this graph is paw-free.
4982    ///
4983    /// # Examples
4984    ///
4985    /// ```
4986    /// use rust_igraph::Graph;
4987    ///
4988    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
4989    /// assert!(g.is_paw_free().unwrap());
4990    /// ```
4991    pub fn is_paw_free(&self) -> IgraphResult<bool> {
4992        crate::algorithms::properties::is_paw_free::is_paw_free(self)
4993    }
4994
4995    /// Check whether this graph is a proper interval graph.
4996    ///
4997    /// # Examples
4998    ///
4999    /// ```
5000    /// use rust_igraph::Graph;
5001    ///
5002    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5003    /// assert!(g.is_proper_interval().unwrap());
5004    /// ```
5005    pub fn is_proper_interval(&self) -> IgraphResult<bool> {
5006        crate::algorithms::properties::is_proper_interval::is_proper_interval(self)
5007    }
5008
5009    /// Check whether this graph is a pseudo-forest.
5010    ///
5011    /// # Examples
5012    ///
5013    /// ```
5014    /// use rust_igraph::Graph;
5015    ///
5016    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5017    /// assert!(g.is_pseudo_forest().unwrap());
5018    /// ```
5019    pub fn is_pseudo_forest(&self) -> IgraphResult<bool> {
5020        crate::algorithms::properties::is_pseudo_forest::is_pseudo_forest(self)
5021    }
5022
5023    /// Check whether this graph is Ptolemaic.
5024    ///
5025    /// # Examples
5026    ///
5027    /// ```
5028    /// use rust_igraph::Graph;
5029    ///
5030    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5031    /// assert!(g.is_ptolemaic().unwrap());
5032    /// ```
5033    pub fn is_ptolemaic(&self) -> IgraphResult<bool> {
5034        crate::algorithms::properties::is_ptolemaic::is_ptolemaic(self)
5035    }
5036
5037    /// Check whether this graph is self-complementary.
5038    ///
5039    /// # Examples
5040    ///
5041    /// ```
5042    /// use rust_igraph::Graph;
5043    ///
5044    /// // P_4 (path on 4 vertices) is self-complementary
5045    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5046    /// assert!(g.is_self_complementary().unwrap());
5047    /// ```
5048    pub fn is_self_complementary(&self) -> IgraphResult<bool> {
5049        crate::algorithms::properties::is_self_complementary::is_self_complementary(self)
5050    }
5051
5052    /// Check whether this graph is semicomplete.
5053    ///
5054    /// # Examples
5055    ///
5056    /// ```
5057    /// use rust_igraph::Graph;
5058    ///
5059    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], true, None).unwrap();
5060    /// assert!(g.is_semicomplete().unwrap());
5061    /// ```
5062    pub fn is_semicomplete(&self) -> IgraphResult<bool> {
5063        crate::algorithms::properties::is_semicomplete::is_semicomplete(self)
5064    }
5065
5066    /// Check whether this graph is a spider.
5067    ///
5068    /// # Examples
5069    ///
5070    /// ```
5071    /// use rust_igraph::Graph;
5072    ///
5073    /// let g = Graph::from_edges(&[(0,1), (0,2), (0,3)], false, None).unwrap();
5074    /// assert!(g.is_spider().unwrap());
5075    /// ```
5076    pub fn is_spider(&self) -> IgraphResult<bool> {
5077        crate::algorithms::properties::is_spider::is_spider(self)
5078    }
5079
5080    /// Check whether this graph is a split graph.
5081    ///
5082    /// # Examples
5083    ///
5084    /// ```
5085    /// use rust_igraph::Graph;
5086    ///
5087    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2), (2,3)], false, None).unwrap();
5088    /// assert!(g.is_split_graph().unwrap());
5089    /// ```
5090    pub fn is_split_graph(&self) -> IgraphResult<bool> {
5091        crate::algorithms::properties::is_split::is_split_graph(self)
5092    }
5093
5094    /// Check whether this graph is a star.
5095    ///
5096    /// # Examples
5097    ///
5098    /// ```
5099    /// use rust_igraph::Graph;
5100    ///
5101    /// let g = Graph::from_edges(&[(0,1), (0,2), (0,3)], false, None).unwrap();
5102    /// assert!(g.is_star().unwrap());
5103    /// ```
5104    pub fn is_star(&self) -> IgraphResult<bool> {
5105        crate::algorithms::properties::is_star::is_star(self)
5106    }
5107
5108    /// Check whether this graph is strongly chordal.
5109    ///
5110    /// # Examples
5111    ///
5112    /// ```
5113    /// use rust_igraph::Graph;
5114    ///
5115    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5116    /// assert!(g.is_strongly_chordal().unwrap());
5117    /// ```
5118    pub fn is_strongly_chordal(&self) -> IgraphResult<bool> {
5119        crate::algorithms::properties::is_strongly_chordal::is_strongly_chordal(self)
5120    }
5121
5122    /// Check whether this graph is a threshold graph.
5123    ///
5124    /// # Examples
5125    ///
5126    /// ```
5127    /// use rust_igraph::Graph;
5128    ///
5129    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
5130    /// assert!(g.is_threshold_graph().unwrap());
5131    /// ```
5132    pub fn is_threshold_graph(&self) -> IgraphResult<bool> {
5133        crate::algorithms::properties::is_threshold::is_threshold_graph(self)
5134    }
5135
5136    /// Check whether this graph is a tournament.
5137    ///
5138    /// # Examples
5139    ///
5140    /// ```
5141    /// use rust_igraph::Graph;
5142    ///
5143    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], true, None).unwrap();
5144    /// assert!(g.is_tournament().unwrap());
5145    /// ```
5146    pub fn is_tournament(&self) -> IgraphResult<bool> {
5147        crate::algorithms::properties::is_tournament::is_tournament(self)
5148    }
5149
5150    /// Check whether this graph is triangle-free.
5151    ///
5152    /// # Examples
5153    ///
5154    /// ```
5155    /// use rust_igraph::Graph;
5156    ///
5157    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5158    /// assert!(g.is_triangle_free().unwrap());
5159    /// ```
5160    pub fn is_triangle_free(&self) -> IgraphResult<bool> {
5161        crate::algorithms::properties::is_triangle_free::is_triangle_free(self)
5162    }
5163
5164    /// Check whether this graph is trivially perfect.
5165    ///
5166    /// # Examples
5167    ///
5168    /// ```
5169    /// use rust_igraph::Graph;
5170    ///
5171    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
5172    /// assert!(g.is_trivially_perfect().unwrap());
5173    /// ```
5174    pub fn is_trivially_perfect(&self) -> IgraphResult<bool> {
5175        crate::algorithms::properties::is_trivially_perfect::is_trivially_perfect(self)
5176    }
5177
5178    /// Check whether this graph is unicyclic.
5179    ///
5180    /// # Examples
5181    ///
5182    /// ```
5183    /// use rust_igraph::Graph;
5184    ///
5185    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5186    /// assert!(g.is_unicyclic().unwrap());
5187    /// ```
5188    pub fn is_unicyclic(&self) -> IgraphResult<bool> {
5189        crate::algorithms::properties::is_unicyclic::is_unicyclic(self)
5190    }
5191
5192    /// Check whether this graph is a wheel.
5193    ///
5194    /// # Examples
5195    ///
5196    /// ```
5197    /// use rust_igraph::Graph;
5198    ///
5199    /// // W_4: center 0 connected to rim {1,2,3}, rim forms a cycle
5200    /// let g = Graph::from_edges(
5201    ///     &[(0,1), (0,2), (0,3), (1,2), (2,3), (3,1)],
5202    ///     false, None
5203    /// ).unwrap();
5204    /// assert!(g.is_wheel().unwrap());
5205    /// ```
5206    pub fn is_wheel(&self) -> IgraphResult<bool> {
5207        crate::algorithms::properties::is_wheel::is_wheel(self)
5208    }
5209
5210    /// Check whether the graph is regular (all vertices have the same degree).
5211    ///
5212    /// # Examples
5213    ///
5214    /// ```
5215    /// use rust_igraph::{Graph, full_graph};
5216    ///
5217    /// let g = full_graph(4, false, false).unwrap();
5218    /// assert!(g.is_regular().unwrap());
5219    /// ```
5220    pub fn is_regular(&self) -> IgraphResult<bool> {
5221        crate::algorithms::properties::is_regular::is_regular(self)
5222    }
5223
5224    /// Check whether the graph is strongly regular, returning parameters if so.
5225    ///
5226    /// # Examples
5227    ///
5228    /// ```
5229    /// use rust_igraph::{Graph, cycle_graph};
5230    ///
5231    /// let g = cycle_graph(5, false, false).unwrap();
5232    /// let result = g.is_strongly_regular().unwrap();
5233    /// assert!(result.is_some());
5234    /// ```
5235    pub fn is_strongly_regular(
5236        &self,
5237    ) -> IgraphResult<
5238        Option<crate::algorithms::properties::is_strongly_regular::StronglyRegularParams>,
5239    > {
5240        crate::algorithms::properties::is_strongly_regular::is_strongly_regular(self)
5241    }
5242
5243    /// Check whether the graph is weakly chordal.
5244    ///
5245    /// # Examples
5246    ///
5247    /// ```
5248    /// use rust_igraph::Graph;
5249    ///
5250    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
5251    /// assert!(g.is_weakly_chordal().unwrap());
5252    /// ```
5253    pub fn is_weakly_chordal(&self) -> IgraphResult<bool> {
5254        crate::algorithms::properties::is_weakly_chordal::is_weakly_chordal(self)
5255    }
5256
5257    /// Check whether the graph is well-covered.
5258    ///
5259    /// # Examples
5260    ///
5261    /// ```
5262    /// use rust_igraph::{Graph, full_graph};
5263    ///
5264    /// let g = full_graph(4, false, false).unwrap();
5265    /// assert!(g.is_well_covered().unwrap());
5266    /// ```
5267    pub fn is_well_covered(&self) -> IgraphResult<bool> {
5268        crate::algorithms::properties::is_well_covered::is_well_covered(self)
5269    }
5270
5271    /// Check whether the graph is a windmill graph, returning (k, n) if so.
5272    ///
5273    /// # Examples
5274    ///
5275    /// ```
5276    /// use rust_igraph::{Graph, full_graph};
5277    ///
5278    /// let g = full_graph(3, false, false).unwrap();
5279    /// let result = g.is_windmill().unwrap();
5280    /// assert!(result.is_some());
5281    /// ```
5282    pub fn is_windmill(&self) -> IgraphResult<Option<(u32, u32)>> {
5283        crate::algorithms::properties::is_windmill::is_windmill(self)
5284    }
5285
5286    /// Check whether the graph is complete multipartite, returning
5287    /// partition sizes if so.
5288    ///
5289    /// # Examples
5290    ///
5291    /// ```
5292    /// use rust_igraph::{Graph, full_graph};
5293    ///
5294    /// let g = full_graph(3, false, false).unwrap();
5295    /// let result = g.is_complete_multipartite().unwrap();
5296    /// assert!(result.is_some());
5297    /// ```
5298    pub fn is_complete_multipartite(&self) -> IgraphResult<Option<Vec<u32>>> {
5299        crate::algorithms::properties::is_complete_multipartite::is_complete_multipartite(self)
5300    }
5301
5302    /// Check whether this graph satisfies Dirac's condition for Hamiltonicity.
5303    ///
5304    /// # Examples
5305    ///
5306    /// ```
5307    /// use rust_igraph::{Graph, full_graph};
5308    ///
5309    /// let g = full_graph(5, false, false).unwrap();
5310    /// assert!(g.satisfies_dirac().unwrap());
5311    /// ```
5312    pub fn satisfies_dirac(&self) -> IgraphResult<bool> {
5313        crate::algorithms::properties::satisfies_dirac::satisfies_dirac(self)
5314    }
5315
5316    /// Check whether this graph satisfies Ore's condition for Hamiltonicity.
5317    ///
5318    /// # Examples
5319    ///
5320    /// ```
5321    /// use rust_igraph::{Graph, full_graph};
5322    ///
5323    /// let g = full_graph(5, false, false).unwrap();
5324    /// assert!(g.satisfies_ore().unwrap());
5325    /// ```
5326    pub fn satisfies_ore(&self) -> IgraphResult<bool> {
5327        crate::algorithms::properties::satisfies_ore::satisfies_ore(self)
5328    }
5329
5330    // ── Centrality variants ───────────────────────────────────────
5331
5332    /// Compute edge betweenness centrality.
5333    ///
5334    /// # Examples
5335    ///
5336    /// ```
5337    /// use rust_igraph::Graph;
5338    ///
5339    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5340    /// let eb = g.edge_betweenness().unwrap();
5341    /// assert_eq!(eb.len(), 3);
5342    /// ```
5343    pub fn edge_betweenness(&self) -> IgraphResult<Vec<f64>> {
5344        crate::algorithms::properties::edge_betweenness::edge_betweenness(self)
5345    }
5346
5347    /// Compute betweenness centrality with a distance cutoff.
5348    ///
5349    /// # Examples
5350    ///
5351    /// ```
5352    /// use rust_igraph::Graph;
5353    ///
5354    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5355    /// let bc = g.betweenness_cutoff(2).unwrap();
5356    /// assert_eq!(bc.len(), 4);
5357    /// ```
5358    pub fn betweenness_cutoff(&self, cutoff: u32) -> IgraphResult<Vec<f64>> {
5359        crate::algorithms::properties::betweenness_cutoff::betweenness_cutoff(self, cutoff)
5360    }
5361
5362    /// Compute weighted betweenness centrality.
5363    ///
5364    /// # Examples
5365    ///
5366    /// ```
5367    /// use rust_igraph::Graph;
5368    ///
5369    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5370    /// let bc = g.betweenness_weighted(&[1.0, 2.0]).unwrap();
5371    /// assert_eq!(bc.len(), 3);
5372    /// ```
5373    pub fn betweenness_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5374        crate::algorithms::properties::betweenness_weighted::betweenness_weighted(self, weights)
5375    }
5376
5377    /// Compute weighted closeness centrality.
5378    ///
5379    /// # Examples
5380    ///
5381    /// ```
5382    /// use rust_igraph::Graph;
5383    ///
5384    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5385    /// let cc = g.closeness_weighted(&[1.0, 2.0]).unwrap();
5386    /// assert_eq!(cc.len(), 3);
5387    /// ```
5388    pub fn closeness_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<Option<f64>>> {
5389        crate::algorithms::properties::closeness_weighted::closeness_weighted(self, weights)
5390    }
5391
5392    /// Compute edge betweenness centrality with a distance cutoff.
5393    ///
5394    /// # Examples
5395    ///
5396    /// ```
5397    /// use rust_igraph::Graph;
5398    ///
5399    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5400    /// let eb = g.edge_betweenness_cutoff(2).unwrap();
5401    /// assert_eq!(eb.len(), 3);
5402    /// ```
5403    pub fn edge_betweenness_cutoff(&self, cutoff: u32) -> IgraphResult<Vec<f64>> {
5404        crate::algorithms::properties::edge_betweenness_cutoff::edge_betweenness_cutoff(
5405            self, cutoff,
5406        )
5407    }
5408
5409    /// Compute weighted edge betweenness centrality.
5410    ///
5411    /// # Examples
5412    ///
5413    /// ```
5414    /// use rust_igraph::Graph;
5415    ///
5416    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5417    /// let eb = g.edge_betweenness_weighted(&[1.0, 2.0]).unwrap();
5418    /// assert_eq!(eb.len(), 2);
5419    /// ```
5420    pub fn edge_betweenness_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5421        crate::algorithms::properties::edge_betweenness_weighted::edge_betweenness_weighted(
5422            self, weights,
5423        )
5424    }
5425
5426    /// Compute weighted harmonic centrality.
5427    ///
5428    /// # Examples
5429    ///
5430    /// ```
5431    /// use rust_igraph::Graph;
5432    ///
5433    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5434    /// let hc = g.harmonic_centrality_weighted(&[1.0, 2.0]).unwrap();
5435    /// assert_eq!(hc.len(), 3);
5436    /// ```
5437    pub fn harmonic_centrality_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5438        crate::algorithms::properties::harmonic_weighted::harmonic_centrality_weighted(
5439            self, weights,
5440        )
5441    }
5442
5443    /// Compute weighted `PageRank`.
5444    ///
5445    /// # Examples
5446    ///
5447    /// ```
5448    /// use rust_igraph::Graph;
5449    ///
5450    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5451    /// let pr = g.pagerank_weighted(&[1.0, 2.0]).unwrap();
5452    /// assert_eq!(pr.len(), 3);
5453    /// ```
5454    pub fn pagerank_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
5455        crate::algorithms::properties::pagerank_weighted::pagerank_weighted(self, weights)
5456    }
5457
5458    // ── Connectivity & structural ─────────────────────────────────
5459
5460    /// Compute the cohesive block structure.
5461    ///
5462    /// # Examples
5463    ///
5464    /// ```
5465    /// use rust_igraph::Graph;
5466    ///
5467    /// let g = Graph::from_edges(
5468    ///     &[(0,1), (1,2), (2,0), (2,3), (3,4), (4,5), (5,3)],
5469    ///     false, None
5470    /// ).unwrap();
5471    /// let blocks = g.cohesive_blocks().unwrap();
5472    /// assert!(!blocks.blocks.is_empty());
5473    /// ```
5474    pub fn cohesive_blocks(
5475        &self,
5476    ) -> IgraphResult<crate::algorithms::connectivity::cohesive_blocks::CohesiveBlocks> {
5477        crate::algorithms::connectivity::cohesive_blocks::cohesive_blocks(self)
5478    }
5479
5480    /// Count reachable vertices from each vertex.
5481    ///
5482    /// # Examples
5483    ///
5484    /// ```
5485    /// use rust_igraph::Graph;
5486    ///
5487    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5488    /// let counts = g.count_reachable().unwrap();
5489    /// assert_eq!(counts, vec![3, 3, 3]);
5490    /// ```
5491    pub fn count_reachable(&self) -> IgraphResult<Vec<u32>> {
5492        crate::algorithms::connectivity::reachability::count_reachable(self)
5493    }
5494
5495    /// Compute the reachability matrix.
5496    ///
5497    /// # Examples
5498    ///
5499    /// ```
5500    /// use rust_igraph::Graph;
5501    ///
5502    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5503    /// let mat = g.reachability_matrix().unwrap();
5504    /// assert!(mat[0][2]);
5505    /// assert!(!mat[2][0]);
5506    /// ```
5507    pub fn reachability_matrix(&self) -> IgraphResult<Vec<Vec<bool>>> {
5508        crate::algorithms::connectivity::reachability_matrix::reachability_matrix(self)
5509    }
5510
5511    /// Compute the transitive closure.
5512    ///
5513    /// # Examples
5514    ///
5515    /// ```
5516    /// use rust_igraph::Graph;
5517    ///
5518    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5519    /// let tc = g.transitive_closure().unwrap();
5520    /// assert!(tc.has_edge(0, 2));
5521    /// ```
5522    pub fn transitive_closure(&self) -> IgraphResult<Graph> {
5523        crate::algorithms::connectivity::transitive_closure::transitive_closure(self)
5524    }
5525
5526    // ── Flow & cuts ───────────────────────────────────────────────
5527
5528    /// Compute the global minimum cut.
5529    ///
5530    /// # Examples
5531    ///
5532    /// ```
5533    /// use rust_igraph::Graph;
5534    ///
5535    /// let g = Graph::from_edges(
5536    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None
5537    /// ).unwrap();
5538    /// let mc = g.mincut(None).unwrap();
5539    /// assert!(mc.value >= 2.0 - 1e-10);
5540    /// ```
5541    pub fn mincut(
5542        &self,
5543        capacity: Option<&[f64]>,
5544    ) -> IgraphResult<crate::algorithms::flow::mincut::Mincut> {
5545        crate::algorithms::flow::mincut::mincut(self, capacity)
5546    }
5547
5548    /// Compute the global minimum cut value.
5549    ///
5550    /// # Examples
5551    ///
5552    /// ```
5553    /// use rust_igraph::Graph;
5554    ///
5555    /// let g = Graph::from_edges(
5556    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None
5557    /// ).unwrap();
5558    /// let val = g.mincut_value(None).unwrap();
5559    /// assert!(val >= 2.0 - 1e-10);
5560    /// ```
5561    pub fn mincut_value(&self, capacity: Option<&[f64]>) -> IgraphResult<f64> {
5562        crate::algorithms::flow::mincut_value::mincut_value(self, capacity)
5563    }
5564
5565    /// Compute the Gomory-Hu tree.
5566    ///
5567    /// # Examples
5568    ///
5569    /// ```
5570    /// use rust_igraph::Graph;
5571    ///
5572    /// let g = Graph::from_edges(
5573    ///     &[(0,1), (0,2), (1,2), (1,3)], false, None
5574    /// ).unwrap();
5575    /// let tree = g.gomory_hu_tree(None).unwrap();
5576    /// assert_eq!(tree.tree.vcount(), 4);
5577    /// ```
5578    pub fn gomory_hu_tree(
5579        &self,
5580        capacity: Option<&[f64]>,
5581    ) -> IgraphResult<crate::algorithms::flow::gomory_hu_tree::GomoryHuTree> {
5582        crate::algorithms::flow::gomory_hu_tree::gomory_hu_tree(self, capacity)
5583    }
5584
5585    /// Enumerate all minimum s-t cuts.
5586    ///
5587    /// # Examples
5588    ///
5589    /// ```
5590    /// use rust_igraph::Graph;
5591    ///
5592    /// let g = Graph::from_edges(
5593    ///     &[(0,1), (0,2), (1,3), (2,3)], true, None
5594    /// ).unwrap();
5595    /// let cuts = g.all_st_cuts(0, 3).unwrap();
5596    /// assert!(!cuts.cuts.is_empty());
5597    /// ```
5598    pub fn all_st_cuts(
5599        &self,
5600        source: VertexId,
5601        target: VertexId,
5602    ) -> IgraphResult<crate::algorithms::flow::all_st_cuts::StCuts> {
5603        crate::algorithms::flow::all_st_cuts::all_st_cuts(self, source, target)
5604    }
5605
5606    /// Count edge-disjoint paths between two vertices.
5607    ///
5608    /// # Examples
5609    ///
5610    /// ```
5611    /// use rust_igraph::Graph;
5612    ///
5613    /// let g = Graph::from_edges(
5614    ///     &[(0,1), (0,2), (1,3), (2,3)], false, None
5615    /// ).unwrap();
5616    /// let count = g.edge_disjoint_paths(0, 3).unwrap();
5617    /// assert_eq!(count, 2);
5618    /// ```
5619    pub fn edge_disjoint_paths(&self, source: VertexId, target: VertexId) -> IgraphResult<i64> {
5620        crate::algorithms::flow::edge_disjoint_paths::edge_disjoint_paths(self, source, target)
5621    }
5622
5623    // ── Paths & distances ─────────────────────────────────────────
5624
5625    /// Compute BFS distances from a source vertex.
5626    ///
5627    /// # Examples
5628    ///
5629    /// ```
5630    /// use rust_igraph::Graph;
5631    ///
5632    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5633    /// let dist = g.distances(0).unwrap();
5634    /// assert_eq!(dist[3], Some(3));
5635    /// ```
5636    pub fn distances(&self, source: VertexId) -> IgraphResult<Vec<Option<u32>>> {
5637        crate::algorithms::paths::distances::distances(self, source)
5638    }
5639
5640    /// Compute an Eulerian path if one exists.
5641    ///
5642    /// # Examples
5643    ///
5644    /// ```
5645    /// use rust_igraph::Graph;
5646    ///
5647    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5648    /// let path = g.eulerian_path().unwrap();
5649    /// assert!(path.is_some());
5650    /// ```
5651    pub fn eulerian_path(&self) -> IgraphResult<Option<Vec<u32>>> {
5652        crate::algorithms::paths::eulerian_construct::eulerian_path(self)
5653    }
5654
5655    /// Compute mean geodesic distance.
5656    ///
5657    /// # Examples
5658    ///
5659    /// ```
5660    /// use rust_igraph::Graph;
5661    ///
5662    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5663    /// let d = g.mean_distance().unwrap();
5664    /// assert!(d.is_some());
5665    /// ```
5666    pub fn mean_distance(&self) -> IgraphResult<Option<f64>> {
5667        crate::algorithms::properties::basic::mean_distance(self)
5668    }
5669
5670    /// Topological sort of a directed graph.
5671    ///
5672    /// # Examples
5673    ///
5674    /// ```
5675    /// use rust_igraph::Graph;
5676    ///
5677    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5678    /// let order = g.topological_sorting().unwrap();
5679    /// assert_eq!(order, vec![0, 1, 2]);
5680    /// ```
5681    pub fn topological_sorting(&self) -> IgraphResult<Vec<VertexId>> {
5682        crate::algorithms::properties::topological_sorting::topological_sorting(
5683            self,
5684            crate::algorithms::paths::dijkstra::DijkstraMode::Out,
5685        )
5686    }
5687
5688    /// List all triangles in the graph.
5689    ///
5690    /// # Examples
5691    ///
5692    /// ```
5693    /// use rust_igraph::Graph;
5694    ///
5695    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5696    /// let tris = g.list_triangles().unwrap();
5697    /// assert_eq!(tris.len(), 1);
5698    /// ```
5699    pub fn list_triangles(&self) -> IgraphResult<Vec<(u32, u32, u32)>> {
5700        crate::algorithms::properties::list_triangles::list_triangles(self)
5701    }
5702
5703    /// Compute the degree distribution.
5704    ///
5705    /// # Examples
5706    ///
5707    /// ```
5708    /// use rust_igraph::Graph;
5709    ///
5710    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5711    /// let dist = g.degree_distribution().unwrap();
5712    /// assert!(!dist.is_empty());
5713    /// ```
5714    pub fn degree_distribution(&self) -> IgraphResult<Vec<u32>> {
5715        crate::algorithms::properties::degree_distribution::degree_distribution(
5716            self,
5717            crate::algorithms::properties::degree::DegreeMode::All,
5718        )
5719    }
5720
5721    /// Get the edge list as (source, target) pairs.
5722    ///
5723    /// # Examples
5724    ///
5725    /// ```
5726    /// use rust_igraph::Graph;
5727    ///
5728    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5729    /// let edges = g.get_edgelist().unwrap();
5730    /// assert_eq!(edges.len(), 2);
5731    /// ```
5732    pub fn get_edgelist(&self) -> IgraphResult<Vec<(VertexId, VertexId)>> {
5733        crate::algorithms::properties::edgelist::get_edgelist(self)
5734    }
5735
5736    /// Compute the graph's regularity (degree if regular, None otherwise).
5737    ///
5738    /// # Examples
5739    ///
5740    /// ```
5741    /// use rust_igraph::Graph;
5742    ///
5743    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5744    /// assert_eq!(g.regularity().unwrap(), Some(2));
5745    /// ```
5746    pub fn regularity(&self) -> IgraphResult<Option<u32>> {
5747        crate::algorithms::properties::is_regular::regularity(self)
5748    }
5749
5750    /// Find a cycle in the graph.
5751    ///
5752    /// # Examples
5753    ///
5754    /// ```
5755    /// use rust_igraph::Graph;
5756    ///
5757    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
5758    /// let result = g.find_cycle().unwrap();
5759    /// assert!(!result.vertices.is_empty());
5760    /// ```
5761    pub fn find_cycle(&self) -> IgraphResult<crate::algorithms::cycles::CycleResult> {
5762        crate::algorithms::cycles::find_cycle(self, crate::algorithms::cycles::CycleMode::All)
5763    }
5764
5765    /// Compute the joint degree matrix.
5766    ///
5767    /// # Examples
5768    ///
5769    /// ```
5770    /// use rust_igraph::Graph;
5771    ///
5772    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5773    /// let jdm = g.joint_degree_matrix(None).unwrap();
5774    /// assert!(!jdm.is_empty());
5775    /// ```
5776    pub fn joint_degree_matrix(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<Vec<f64>>> {
5777        crate::algorithms::properties::joint_degree_matrix::joint_degree_matrix(self, weights)
5778    }
5779
5780    /// Compute the minimum dominating set.
5781    ///
5782    /// # Examples
5783    ///
5784    /// ```
5785    /// use rust_igraph::Graph;
5786    ///
5787    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5788    /// let dom = g.minimum_dominating_set().unwrap();
5789    /// assert!(!dom.is_empty());
5790    /// ```
5791    pub fn minimum_dominating_set(&self) -> IgraphResult<Vec<VertexId>> {
5792        crate::algorithms::dominating_set::minimum_dominating_set(self)
5793    }
5794
5795    /// Compute the k-th power of this graph.
5796    ///
5797    /// # Examples
5798    ///
5799    /// ```
5800    /// use rust_igraph::Graph;
5801    ///
5802    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5803    /// let g2 = g.graph_power(2).unwrap();
5804    /// assert!(g2.has_edge(0, 2));
5805    /// ```
5806    pub fn graph_power(&self, order: u32) -> IgraphResult<Graph> {
5807        crate::algorithms::operators::connect_neighborhood::graph_power(self, order)
5808    }
5809
5810    /// Compute the trussness of each edge.
5811    ///
5812    /// # Examples
5813    ///
5814    /// ```
5815    /// use rust_igraph::Graph;
5816    ///
5817    /// let g = Graph::from_edges(
5818    ///     &[(0,1), (1,2), (2,0), (2,3)], false, None
5819    /// ).unwrap();
5820    /// let tr = g.trussness().unwrap();
5821    /// assert_eq!(tr.len(), g.ecount());
5822    /// ```
5823    pub fn trussness(&self) -> IgraphResult<Vec<u32>> {
5824        crate::algorithms::properties::trussness::trussness(self)
5825    }
5826
5827    // ── Graph operators ──────────────────────────────────────────────
5828
5829    /// Union of two graphs on the same vertex set.
5830    ///
5831    /// ```
5832    /// use rust_igraph::Graph;
5833    ///
5834    /// let a = Graph::from_edges(&[(0,1)], false, None).unwrap();
5835    /// let b = Graph::from_edges(&[(1,2)], false, None).unwrap();
5836    /// let u = a.union(&b).unwrap();
5837    /// assert_eq!(u.ecount(), 2);
5838    /// ```
5839    pub fn union(&self, other: &Graph) -> IgraphResult<Graph> {
5840        crate::algorithms::operators::union::union(self, other)
5841    }
5842
5843    /// Intersection of two graphs on the same vertex set.
5844    ///
5845    /// ```
5846    /// use rust_igraph::Graph;
5847    ///
5848    /// let a = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5849    /// let b = Graph::from_edges(&[(1,2), (2,3)], false, None).unwrap();
5850    /// let i = a.intersection(&b).unwrap();
5851    /// assert_eq!(i.ecount(), 1);
5852    /// ```
5853    pub fn intersection(&self, other: &Graph) -> IgraphResult<Graph> {
5854        crate::algorithms::operators::intersection::intersection(self, other)
5855    }
5856
5857    /// Edge difference: edges in `self` but not in `other`.
5858    ///
5859    /// ```
5860    /// use rust_igraph::Graph;
5861    ///
5862    /// let a = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
5863    /// let b = Graph::from_edges(&[(1,2)], false, None).unwrap();
5864    /// let d = a.difference(&b).unwrap();
5865    /// assert_eq!(d.ecount(), 1);
5866    /// ```
5867    pub fn difference(&self, other: &Graph) -> IgraphResult<Graph> {
5868        crate::algorithms::operators::difference::difference(self, other)
5869    }
5870
5871    /// Disjoint union: concatenate vertex sets, then concatenate edge sets.
5872    ///
5873    /// ```
5874    /// use rust_igraph::Graph;
5875    ///
5876    /// let a = Graph::from_edges(&[(0,1)], false, None).unwrap();
5877    /// let b = Graph::from_edges(&[(0,1)], false, None).unwrap();
5878    /// let d = a.disjoint_union(&b).unwrap();
5879    /// assert_eq!(d.vcount(), 4);
5880    /// assert_eq!(d.ecount(), 2);
5881    /// ```
5882    pub fn disjoint_union(&self, other: &Graph) -> IgraphResult<Graph> {
5883        crate::algorithms::operators::disjoint_union::disjoint_union(self, other)
5884    }
5885
5886    /// Join: disjoint union plus all edges between the two vertex sets.
5887    ///
5888    /// ```
5889    /// use rust_igraph::Graph;
5890    ///
5891    /// let a = Graph::with_vertices(2);
5892    /// let b = Graph::with_vertices(2);
5893    /// let j = a.join(&b).unwrap();
5894    /// assert_eq!(j.vcount(), 4);
5895    /// assert_eq!(j.ecount(), 4);
5896    /// ```
5897    pub fn join(&self, other: &Graph) -> IgraphResult<Graph> {
5898        crate::algorithms::operators::join::join(self, other)
5899    }
5900
5901    /// Compose two graphs (relational composition).
5902    ///
5903    /// ```
5904    /// use rust_igraph::Graph;
5905    ///
5906    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
5907    /// let c = g.compose(&g).unwrap();
5908    /// assert!(c.ecount() > 0);
5909    /// ```
5910    pub fn compose(&self, other: &Graph) -> IgraphResult<Graph> {
5911        crate::algorithms::operators::compose::compose(self, other)
5912    }
5913
5914    /// Cartesian product of two graphs.
5915    ///
5916    /// ```
5917    /// use rust_igraph::Graph;
5918    ///
5919    /// let p2 = Graph::from_edges(&[(0,1)], false, None).unwrap();
5920    /// let grid = p2.cartesian_product(&p2).unwrap();
5921    /// assert_eq!(grid.vcount(), 4);
5922    /// ```
5923    pub fn cartesian_product(&self, other: &Graph) -> IgraphResult<Graph> {
5924        crate::algorithms::operators::products::cartesian_product(self, other)
5925    }
5926
5927    /// Tensor (categorical/direct) product of two graphs.
5928    ///
5929    /// ```
5930    /// use rust_igraph::Graph;
5931    ///
5932    /// let p2 = Graph::from_edges(&[(0,1)], false, None).unwrap();
5933    /// let t = p2.tensor_product(&p2).unwrap();
5934    /// assert_eq!(t.vcount(), 4);
5935    /// ```
5936    pub fn tensor_product(&self, other: &Graph) -> IgraphResult<Graph> {
5937        crate::algorithms::operators::products::tensor_product(self, other)
5938    }
5939
5940    /// Strong product of two graphs.
5941    ///
5942    /// ```
5943    /// use rust_igraph::Graph;
5944    ///
5945    /// let p2 = Graph::from_edges(&[(0,1)], false, None).unwrap();
5946    /// let s = p2.strong_product(&p2).unwrap();
5947    /// assert_eq!(s.vcount(), 4);
5948    /// ```
5949    pub fn strong_product(&self, other: &Graph) -> IgraphResult<Graph> {
5950        crate::algorithms::operators::products::strong_product(self, other)
5951    }
5952
5953    /// Lexicographic product of two graphs.
5954    ///
5955    /// ```
5956    /// use rust_igraph::Graph;
5957    ///
5958    /// let a = Graph::from_edges(&[(0,1)], false, None).unwrap();
5959    /// let b = Graph::with_vertices(2);
5960    /// let l = a.lexicographic_product(&b).unwrap();
5961    /// assert_eq!(l.vcount(), 4);
5962    /// ```
5963    pub fn lexicographic_product(&self, other: &Graph) -> IgraphResult<Graph> {
5964        crate::algorithms::operators::products::lexicographic_product(self, other)
5965    }
5966
5967    /// Connect each vertex to all vertices within distance `order`.
5968    ///
5969    /// ```
5970    /// use rust_igraph::Graph;
5971    ///
5972    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5973    /// let c = g.connect_neighborhood(2).unwrap();
5974    /// assert!(c.ecount() > g.ecount());
5975    /// ```
5976    pub fn connect_neighborhood(&self, order: u32) -> IgraphResult<Graph> {
5977        crate::algorithms::operators::connect_neighborhood::connect_neighborhood(self, order)
5978    }
5979
5980    /// Rewire edges while preserving the degree sequence.
5981    ///
5982    /// ```
5983    /// use rust_igraph::Graph;
5984    ///
5985    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3), (3,0)], false, None).unwrap();
5986    /// let r = g.rewire(100, false, 42).unwrap();
5987    /// assert_eq!(r.ecount(), g.ecount());
5988    /// ```
5989    pub fn rewire(&self, num_trials: usize, loops: bool, seed: u64) -> IgraphResult<Graph> {
5990        crate::algorithms::operators::rewire::rewire(self, num_trials, loops, seed)
5991    }
5992
5993    /// Randomly rewire each edge with probability `prob`.
5994    ///
5995    /// ```
5996    /// use rust_igraph::Graph;
5997    ///
5998    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
5999    /// let r = g.rewire_edges(0.5, false, 42).unwrap();
6000    /// assert_eq!(r.vcount(), g.vcount());
6001    /// ```
6002    pub fn rewire_edges(&self, prob: f64, loops: bool, seed: u64) -> IgraphResult<Graph> {
6003        crate::algorithms::operators::rewire_edges::rewire_edges(self, prob, loops, seed)
6004    }
6005
6006    /// Extract a subgraph induced by the given edge ids.
6007    ///
6008    /// ```
6009    /// use rust_igraph::Graph;
6010    ///
6011    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
6012    /// let s = g.subgraph_from_edges(&[0, 1]).unwrap();
6013    /// assert_eq!(s.graph.ecount(), 2);
6014    /// ```
6015    pub fn subgraph_from_edges(
6016        &self,
6017        eids: &[u32],
6018    ) -> IgraphResult<crate::algorithms::operators::subgraph_from_edges::SubgraphFromEdgesResult>
6019    {
6020        crate::algorithms::operators::subgraph_from_edges::subgraph_from_edges(self, eids, true)
6021    }
6022
6023    /// The Even-Tarjan reduction of a directed graph.
6024    ///
6025    /// ```
6026    /// use rust_igraph::Graph;
6027    ///
6028    /// let g = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
6029    /// let et = g.even_tarjan_reduction().unwrap();
6030    /// assert!(et.graph.vcount() > g.vcount());
6031    /// ```
6032    pub fn even_tarjan_reduction(
6033        &self,
6034    ) -> IgraphResult<crate::algorithms::operators::even_tarjan::EvenTarjanResult> {
6035        crate::algorithms::operators::even_tarjan::even_tarjan_reduction(self)
6036    }
6037
6038    /// Bipartite projection onto one vertex type.
6039    ///
6040    /// `project_type` selects which side: `false` projects the `false`-typed
6041    /// vertices, `true` projects the `true`-typed vertices.
6042    ///
6043    /// ```
6044    /// use rust_igraph::Graph;
6045    ///
6046    /// let mut g = Graph::new(4, false).unwrap();
6047    /// g.add_edge(0, 2).unwrap();
6048    /// g.add_edge(0, 3).unwrap();
6049    /// g.add_edge(1, 2).unwrap();
6050    /// g.add_edge(1, 3).unwrap();
6051    /// let types = vec![false, false, true, true];
6052    /// let p = g.bipartite_projection(&types, false).unwrap();
6053    /// assert_eq!(p.graph.vcount(), 2);
6054    /// ```
6055    pub fn bipartite_projection(
6056        &self,
6057        types: &[bool],
6058        project_type: bool,
6059    ) -> IgraphResult<crate::algorithms::operators::bipartite_projection::BipartiteProjection> {
6060        crate::algorithms::operators::bipartite_projection::bipartite_projection(
6061            self,
6062            types,
6063            project_type,
6064        )
6065    }
6066
6067    // ── Paths (advanced) ─────────────────────────────────────────────
6068
6069    /// Bellman-Ford shortest-path distances from a single source.
6070    ///
6071    /// ```
6072    /// use rust_igraph::Graph;
6073    ///
6074    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,3)], false, None).unwrap();
6075    /// let w = vec![1.0; g.ecount()];
6076    /// let d = g.bellman_ford_distances(0, &w).unwrap();
6077    /// assert!((d[3].unwrap() - 3.0).abs() < 1e-9);
6078    /// ```
6079    pub fn bellman_ford_distances(
6080        &self,
6081        source: VertexId,
6082        weights: &[f64],
6083    ) -> IgraphResult<Vec<Option<f64>>> {
6084        crate::algorithms::paths::bellman_ford::bellman_ford_distances(self, source, weights)
6085    }
6086
6087    /// Floyd-Warshall all-pairs shortest-path distances.
6088    ///
6089    /// ```
6090    /// use rust_igraph::Graph;
6091    ///
6092    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
6093    /// let d = g.floyd_warshall_distances(None).unwrap();
6094    /// assert!((d[0][2].unwrap() - 2.0).abs() < 1e-9);
6095    /// ```
6096    pub fn floyd_warshall_distances(
6097        &self,
6098        weights: Option<&[f64]>,
6099    ) -> IgraphResult<Vec<Vec<Option<f64>>>> {
6100        crate::algorithms::paths::floyd_warshall::floyd_warshall_distances(self, weights)
6101    }
6102
6103    /// Find the k shortest paths between two vertices.
6104    ///
6105    /// ```
6106    /// use rust_igraph::Graph;
6107    ///
6108    /// let g = Graph::from_edges(
6109    ///     &[(0,1), (1,3), (0,2), (2,3)], false, None,
6110    /// ).unwrap();
6111    /// let w = vec![1.0; g.ecount()];
6112    /// let paths = g.k_shortest_paths(0, 3, &w, 2).unwrap();
6113    /// assert_eq!(paths.len(), 2);
6114    /// ```
6115    pub fn k_shortest_paths(
6116        &self,
6117        source: VertexId,
6118        target: VertexId,
6119        weights: &[f64],
6120        k: usize,
6121    ) -> IgraphResult<Vec<crate::algorithms::paths::k_shortest_paths::KShortestPath>> {
6122        use crate::algorithms::paths::dijkstra::DijkstraMode;
6123        crate::algorithms::paths::k_shortest_paths::k_shortest_paths(
6124            self,
6125            source,
6126            target,
6127            weights,
6128            k,
6129            DijkstraMode::Out,
6130        )
6131    }
6132
6133    /// Enumerate all simple paths from a source vertex.
6134    ///
6135    /// ```
6136    /// use rust_igraph::Graph;
6137    ///
6138    /// let g = Graph::from_edges(&[(0,1), (1,2), (0,2)], false, None).unwrap();
6139    /// let paths = g.all_simple_paths(0, Some(&[2]), 1, 10).unwrap();
6140    /// assert!(paths.len() >= 2);
6141    /// ```
6142    pub fn all_simple_paths(
6143        &self,
6144        from: u32,
6145        to: Option<&[u32]>,
6146        min_length: i32,
6147        max_length: i32,
6148    ) -> IgraphResult<Vec<Vec<u32>>> {
6149        crate::algorithms::paths::simple_paths::all_simple_paths(
6150            self,
6151            from,
6152            to,
6153            crate::algorithms::paths::simple_paths::SimplePathMode::Out,
6154            min_length,
6155            max_length,
6156            -1,
6157        )
6158    }
6159
6160    // ── Matching ──────────────────────────────────────────────────────
6161
6162    /// Maximum bipartite matching.
6163    ///
6164    /// ```
6165    /// use rust_igraph::Graph;
6166    ///
6167    /// let mut g = Graph::new(4, false).unwrap();
6168    /// g.add_edge(0, 2).unwrap();
6169    /// g.add_edge(0, 3).unwrap();
6170    /// g.add_edge(1, 2).unwrap();
6171    /// let types = vec![false, false, true, true];
6172    /// let m = g.maximum_bipartite_matching(&types).unwrap();
6173    /// assert_eq!(m.matching_size, 2);
6174    /// ```
6175    pub fn maximum_bipartite_matching(
6176        &self,
6177        types: &[bool],
6178    ) -> IgraphResult<crate::algorithms::matching::MatchingResult> {
6179        crate::algorithms::matching::maximum_bipartite_matching(self, types)
6180    }
6181
6182    // ── Coloring ─────────────────────────────────────────────────────
6183
6184    /// Greedy vertex coloring.
6185    ///
6186    /// ```
6187    /// use rust_igraph::{Graph, GreedyColoringHeuristic};
6188    ///
6189    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6190    /// let colors = g.vertex_coloring_greedy(GreedyColoringHeuristic::ColoredNeighbors).unwrap();
6191    /// assert_eq!(colors.len(), 3);
6192    /// ```
6193    pub fn vertex_coloring_greedy(
6194        &self,
6195        heuristic: crate::algorithms::coloring::GreedyColoringHeuristic,
6196    ) -> IgraphResult<Vec<u32>> {
6197        crate::algorithms::coloring::vertex_coloring_greedy(self, heuristic)
6198    }
6199
6200    // ── Cycles ───────────────────────────────────────────────────────
6201
6202    /// Enumerate all simple cycles.
6203    ///
6204    /// ```
6205    /// use rust_igraph::{Graph, SimpleCycleMode};
6206    ///
6207    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6208    /// let cycles = g.simple_cycles(SimpleCycleMode::All, 3, None).unwrap();
6209    /// assert!(!cycles.is_empty());
6210    /// ```
6211    pub fn simple_cycles(
6212        &self,
6213        mode: crate::algorithms::simple_cycles::SimpleCycleMode,
6214        min_length: u32,
6215        max_length: Option<u32>,
6216    ) -> IgraphResult<Vec<crate::algorithms::simple_cycles::SimpleCycle>> {
6217        crate::algorithms::simple_cycles::simple_cycles(self, mode, min_length, max_length, None)
6218    }
6219
6220    // ── Community (additional) ───────────────────────────────────────
6221
6222    /// Leading eigenvector community detection.
6223    ///
6224    /// ```
6225    /// use rust_igraph::Graph;
6226    ///
6227    /// let g = Graph::from_edges(
6228    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)],
6229    ///     false, None,
6230    /// ).unwrap();
6231    /// let result = g.leading_eigenvector(None, None).unwrap();
6232    /// assert!(result.membership.len() == g.vcount() as usize);
6233    /// ```
6234    pub fn leading_eigenvector(
6235        &self,
6236        weights: Option<&[f64]>,
6237        steps: Option<u32>,
6238    ) -> IgraphResult<crate::algorithms::community::leading_eigenvector::LeadingEigenvectorResult>
6239    {
6240        crate::algorithms::community::leading_eigenvector::leading_eigenvector(self, weights, steps)
6241    }
6242
6243    /// Fluid community detection.
6244    ///
6245    /// ```
6246    /// use rust_igraph::Graph;
6247    ///
6248    /// let g = Graph::from_edges(
6249    ///     &[(0,1), (1,2), (2,0), (3,4), (4,5), (5,3), (2,3)],
6250    ///     false, None,
6251    /// ).unwrap();
6252    /// let r = g.fluid_communities(2).unwrap();
6253    /// assert_eq!(r.membership.len(), g.vcount() as usize);
6254    /// ```
6255    pub fn fluid_communities(
6256        &self,
6257        k: u32,
6258    ) -> IgraphResult<crate::algorithms::community::fluid_communities::FluidResult> {
6259        crate::algorithms::community::fluid_communities::fluid_communities(self, k)
6260    }
6261
6262    /// Motif census (subgraph isomorphism classes of a given size).
6263    ///
6264    /// ```
6265    /// use rust_igraph::Graph;
6266    ///
6267    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], true, None).unwrap();
6268    /// let hist = g.motifs_randesu(3).unwrap();
6269    /// assert!(!hist.is_empty());
6270    /// ```
6271    pub fn motifs_randesu(&self, size: u32) -> IgraphResult<Vec<f64>> {
6272        crate::algorithms::motifs::motifs_randesu::motifs_randesu(self, size)
6273    }
6274
6275    /// Personalized `PageRank` with a custom reset distribution.
6276    ///
6277    /// ```
6278    /// use rust_igraph::Graph;
6279    ///
6280    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6281    /// let reset = vec![1.0, 0.0, 0.0];
6282    /// let pr = g.personalized_pagerank(&reset).unwrap();
6283    /// assert_eq!(pr.len(), 3);
6284    /// ```
6285    pub fn personalized_pagerank(&self, reset: &[f64]) -> IgraphResult<Vec<f64>> {
6286        crate::algorithms::properties::personalized_pagerank::personalized_pagerank_default(
6287            self, reset,
6288        )
6289    }
6290
6291    // ── Isomorphism ─────────────────────────────────────────────────
6292
6293    /// Test whether two graphs are isomorphic (VF2).
6294    ///
6295    /// ```
6296    /// use rust_igraph::Graph;
6297    ///
6298    /// let a = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6299    /// let b = Graph::from_edges(&[(0,2), (2,1), (1,0)], false, None).unwrap();
6300    /// let result = a.isomorphic_vf2(&b).unwrap();
6301    /// assert!(result.iso);
6302    /// ```
6303    pub fn isomorphic_vf2(
6304        &self,
6305        other: &Graph,
6306    ) -> IgraphResult<crate::algorithms::isomorphism::vf2::Vf2Isomorphism> {
6307        crate::algorithms::isomorphism::vf2::isomorphic_vf2(self, other, None, None, None, None)
6308    }
6309
6310    /// Quick isomorphism test (delegates to the best available method).
6311    ///
6312    /// ```
6313    /// use rust_igraph::Graph;
6314    ///
6315    /// let a = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6316    /// let b = Graph::from_edges(&[(0,2), (2,1), (1,0)], false, None).unwrap();
6317    /// assert!(a.isomorphic(&b).unwrap());
6318    /// ```
6319    pub fn isomorphic(&self, other: &Graph) -> IgraphResult<bool> {
6320        crate::algorithms::isomorphism::queries::isomorphic(self, other)
6321    }
6322
6323    /// Count the number of isomorphisms between two graphs (VF2).
6324    ///
6325    /// ```
6326    /// use rust_igraph::Graph;
6327    ///
6328    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6329    /// let count = g.count_isomorphisms_vf2(&g).unwrap();
6330    /// assert_eq!(count, 6); // C3 has 6 automorphisms
6331    /// ```
6332    pub fn count_isomorphisms_vf2(&self, other: &Graph) -> IgraphResult<u64> {
6333        crate::algorithms::isomorphism::vf2::count_isomorphisms_vf2(
6334            self, other, None, None, None, None,
6335        )
6336    }
6337
6338    // ── Cliques ─────────────────────────────────────────────────────
6339
6340    /// Find all maximal independent vertex sets.
6341    ///
6342    /// ```
6343    /// use rust_igraph::Graph;
6344    ///
6345    /// let g = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
6346    /// let sets = g.independent_vertex_sets(1, 3).unwrap();
6347    /// assert!(!sets.is_empty());
6348    /// ```
6349    pub fn independent_vertex_sets(
6350        &self,
6351        min_size: u32,
6352        max_size: u32,
6353    ) -> IgraphResult<Vec<Vec<u32>>> {
6354        crate::algorithms::cliques::independent_vertex_sets(self, min_size, max_size, None)
6355    }
6356
6357    // ── Network properties ──────────────────────────────────────────
6358
6359    /// Global efficiency of the graph.
6360    ///
6361    /// ```
6362    /// use rust_igraph::Graph;
6363    ///
6364    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6365    /// let e = g.global_efficiency().unwrap();
6366    /// assert!(e.unwrap_or(0.0) > 0.0);
6367    /// ```
6368    pub fn global_efficiency(&self) -> IgraphResult<Option<f64>> {
6369        crate::algorithms::properties::efficiency::global_efficiency(self)
6370    }
6371
6372    /// Local efficiency for each vertex.
6373    ///
6374    /// ```
6375    /// use rust_igraph::Graph;
6376    ///
6377    /// let g = Graph::from_edges(&[(0,1), (1,2), (2,0)], false, None).unwrap();
6378    /// let e = g.local_efficiency().unwrap();
6379    /// assert_eq!(e.len(), 3);
6380    /// ```
6381    pub fn local_efficiency(&self) -> IgraphResult<Vec<f64>> {
6382        crate::algorithms::properties::efficiency::local_efficiency(self)
6383    }
6384
6385    /// Degree assortativity coefficient.
6386    ///
6387    /// ```
6388    /// use rust_igraph::Graph;
6389    ///
6390    /// let g = Graph::from_edges(
6391    ///     &[(0,1), (1,2), (2,3), (3,4)], false, None,
6392    /// ).unwrap();
6393    /// let r = g.assortativity_degree().unwrap();
6394    /// assert!(r.is_some());
6395    /// ```
6396    pub fn assortativity_degree(&self) -> IgraphResult<Option<f64>> {
6397        crate::algorithms::properties::assortativity::assortativity_degree(self)
6398    }
6399
6400    /// Diversity (entropy) of vertex edge weights.
6401    ///
6402    /// ```
6403    /// use rust_igraph::Graph;
6404    ///
6405    /// let g = Graph::from_edges(&[(0,1), (0,2), (1,2)], false, None).unwrap();
6406    /// let w = vec![1.0, 2.0, 3.0];
6407    /// let d = g.diversity(&w).unwrap();
6408    /// assert_eq!(d.len(), 3);
6409    /// ```
6410    pub fn diversity(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
6411        crate::algorithms::properties::strength::diversity(self, weights)
6412    }
6413
6414    // ---- Paths (batch 5) ----
6415
6416    /// All-pairs shortest-path distances (unweighted BFS).
6417    ///
6418    /// Returns a flat `n*n` vector in row-major order where
6419    /// `result[i*n + j]` is the distance from vertex `i` to `j`,
6420    /// or `None` if unreachable.
6421    ///
6422    /// ```
6423    /// use rust_igraph::Graph;
6424    ///
6425    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6426    /// let d = g.distances_all().unwrap();
6427    /// assert_eq!(d[0 * 3 + 2], Some(2)); // path 0→1→2
6428    /// ```
6429    pub fn distances_all(&self) -> IgraphResult<Vec<Option<u32>>> {
6430        crate::algorithms::paths::distances_all::distances_all(self)
6431    }
6432
6433    /// Shortest-path distances from a set of source vertices.
6434    ///
6435    /// Returns a flat `sources.len() * n` vector in row-major order.
6436    ///
6437    /// ```
6438    /// use rust_igraph::Graph;
6439    ///
6440    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6441    /// let d = g.distances_from(&[0]).unwrap();
6442    /// assert_eq!(d[3], Some(3)); // vertex 3 is 3 hops from vertex 0
6443    /// ```
6444    pub fn distances_from(&self, sources: &[VertexId]) -> IgraphResult<Vec<Option<u32>>> {
6445        crate::algorithms::paths::distances_from::distances_from(self, sources)
6446    }
6447
6448    /// Shortest-path trees from a source vertex (one path per target).
6449    ///
6450    /// Returns a vector of vertex sequences, one per target vertex.
6451    ///
6452    /// ```
6453    /// use rust_igraph::Graph;
6454    ///
6455    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6456    /// let paths = g.get_shortest_paths(0).unwrap();
6457    /// assert_eq!(paths[2], vec![0, 1, 2]);
6458    /// ```
6459    pub fn get_shortest_paths(&self, source: VertexId) -> IgraphResult<Vec<Vec<VertexId>>> {
6460        crate::algorithms::paths::shortest_paths::get_shortest_paths(self, source)
6461    }
6462
6463    /// All shortest paths from a source vertex.
6464    ///
6465    /// ```
6466    /// use rust_igraph::Graph;
6467    ///
6468    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,3),(2,3)], false, None).unwrap();
6469    /// let asp = g.get_all_shortest_paths(0).unwrap();
6470    /// // Two shortest paths from 0 to 3: 0-1-3 and 0-2-3
6471    /// assert_eq!(asp.paths[3].len(), 2);
6472    /// ```
6473    pub fn get_all_shortest_paths(
6474        &self,
6475        source: VertexId,
6476    ) -> IgraphResult<crate::AllShortestPaths> {
6477        crate::algorithms::paths::all_shortest_paths::get_all_shortest_paths(self, source)
6478    }
6479
6480    /// Johnson's algorithm for all-pairs shortest paths with edge weights.
6481    ///
6482    /// Handles negative weights (but not negative cycles).
6483    ///
6484    /// ```
6485    /// use rust_igraph::Graph;
6486    ///
6487    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6488    /// let d = g.johnson_distances(&[1.0, 2.0]).unwrap();
6489    /// assert!((d[0][2].unwrap() - 3.0).abs() < 1e-10);
6490    /// ```
6491    pub fn johnson_distances(&self, weights: &[f64]) -> IgraphResult<Vec<Vec<Option<f64>>>> {
6492        crate::algorithms::paths::johnson::johnson_distances(self, weights)
6493    }
6494
6495    /// Widest (bottleneck) paths from a source vertex.
6496    ///
6497    /// ```
6498    /// use rust_igraph::Graph;
6499    ///
6500    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6501    /// let wp = g.widest_paths(0, &[10.0, 5.0]).unwrap();
6502    /// assert!((wp.widths[2].unwrap() - 5.0).abs() < 1e-10);
6503    /// ```
6504    pub fn widest_paths(
6505        &self,
6506        from: VertexId,
6507        weights: &[f64],
6508    ) -> IgraphResult<crate::WidestPaths> {
6509        crate::algorithms::paths::widest_path::widest_paths(self, from, weights)
6510    }
6511
6512    /// Graph center — vertices with minimum eccentricity.
6513    ///
6514    /// ```
6515    /// use rust_igraph::{Graph, cycle_graph};
6516    ///
6517    /// let g = cycle_graph(5, false, false).unwrap();
6518    /// let center = g.graph_center().unwrap();
6519    /// assert_eq!(center.len(), 5); // all vertices equidistant in a cycle
6520    /// ```
6521    pub fn graph_center(&self) -> IgraphResult<Vec<VertexId>> {
6522        crate::algorithms::paths::graph_center::graph_center(
6523            self,
6524            crate::algorithms::paths::radii::EccMode::Out,
6525        )
6526    }
6527
6528    /// Path-length histogram of the graph.
6529    ///
6530    /// ```
6531    /// use rust_igraph::Graph;
6532    ///
6533    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6534    /// let h = g.path_length_hist(false).unwrap();
6535    /// assert!(!h.hist.is_empty());
6536    /// ```
6537    pub fn path_length_hist(&self, directed: bool) -> IgraphResult<crate::PathLengthHistResult> {
6538        crate::algorithms::paths::histogram::path_length_hist(self, directed)
6539    }
6540
6541    /// Find an Eulerian cycle (every edge visited exactly once, returning
6542    /// to start).
6543    ///
6544    /// ```
6545    /// use rust_igraph::cycle_graph;
6546    ///
6547    /// let g = cycle_graph(5, false, false).unwrap();
6548    /// let cycle = g.eulerian_cycle().unwrap();
6549    /// assert_eq!(cycle.len(), 5); // 5 edges in C5
6550    /// ```
6551    pub fn eulerian_cycle(&self) -> IgraphResult<Vec<EdgeId>> {
6552        crate::algorithms::paths::eulerian_construct::eulerian_cycle(self)
6553    }
6554
6555    // ---- Centrality / properties (batch 5) ----
6556
6557    /// HITS hub and authority scores.
6558    ///
6559    /// ```
6560    /// use rust_igraph::Graph;
6561    ///
6562    /// let g = Graph::from_edges(&[(0,1),(1,2)], true, None).unwrap();
6563    /// let hits = g.hub_and_authority_scores().unwrap();
6564    /// assert_eq!(hits.hub.len(), 3);
6565    /// ```
6566    pub fn hub_and_authority_scores(&self) -> IgraphResult<crate::HitsScores> {
6567        crate::algorithms::properties::hits::hub_and_authority_scores(self)
6568    }
6569
6570    /// Weighted vertex degree (strength).
6571    ///
6572    /// ```
6573    /// use rust_igraph::Graph;
6574    ///
6575    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
6576    /// let s = g.strength(&[1.0, 2.0, 3.0]).unwrap();
6577    /// assert!((s[0] - 3.0).abs() < 1e-10); // edges 0-1 (w=1) + 0-2 (w=2)
6578    /// ```
6579    pub fn strength(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
6580        crate::algorithms::properties::strength::strength(self, weights)
6581    }
6582
6583    /// Average nearest-neighbor degree by degree class (knn(k)).
6584    ///
6585    /// ```
6586    /// use rust_igraph::Graph;
6587    ///
6588    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6589    /// let k = g.knnk().unwrap();
6590    /// assert!(k.len() > 0);
6591    /// ```
6592    pub fn knnk(&self) -> IgraphResult<Vec<Option<f64>>> {
6593        crate::algorithms::properties::knn::knnk(self)
6594    }
6595
6596    /// Barrat's weighted clustering coefficient per vertex.
6597    ///
6598    /// ```
6599    /// use rust_igraph::Graph;
6600    ///
6601    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6602    /// let t = g.transitivity_barrat(&[1.0, 1.0, 1.0]).unwrap();
6603    /// assert!(t[0].unwrap() > 0.0);
6604    /// ```
6605    pub fn transitivity_barrat(&self, weights: &[f64]) -> IgraphResult<Vec<Option<f64>>> {
6606        crate::algorithms::properties::triangles::transitivity_barrat(self, weights)
6607    }
6608
6609    /// Local scan statistic (order 1) — triangle counts per vertex.
6610    ///
6611    /// ```
6612    /// use rust_igraph::Graph;
6613    ///
6614    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6615    /// let s = g.local_scan_1(None).unwrap();
6616    /// assert_eq!(s.len(), 3);
6617    /// ```
6618    pub fn local_scan_1(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
6619        crate::algorithms::properties::local_scan::local_scan_1(self, weights)
6620    }
6621
6622    /// Maximum cardinality search ordering.
6623    ///
6624    /// ```
6625    /// use rust_igraph::Graph;
6626    ///
6627    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6628    /// let mcs = g.maximum_cardinality_search().unwrap();
6629    /// assert_eq!(mcs.alpha.len(), 3);
6630    /// ```
6631    pub fn maximum_cardinality_search(&self) -> IgraphResult<crate::McsResult> {
6632        crate::algorithms::chordality::maximum_cardinality_search(self)
6633    }
6634
6635    /// Vertex with the highest degree.
6636    ///
6637    /// ```
6638    /// use rust_igraph::Graph;
6639    ///
6640    /// let g = Graph::from_edges(&[(0,1),(0,2),(0,3),(1,2)], false, None).unwrap();
6641    /// let v = g.max_degree_vertex().unwrap();
6642    /// assert_eq!(v, Some(0)); // vertex 0 has degree 3
6643    /// ```
6644    pub fn max_degree_vertex(&self) -> IgraphResult<Option<VertexId>> {
6645        crate::algorithms::properties::degree::max_degree_vertex(
6646            self,
6647            crate::algorithms::properties::degree::DegreeMode::All,
6648        )
6649    }
6650
6651    // ---- Graph predicates / queries (batch 5) ----
6652
6653    /// Whether the graph has at least one self-loop.
6654    ///
6655    /// ```
6656    /// use rust_igraph::Graph;
6657    ///
6658    /// let g = Graph::from_edges(&[(0,1),(1,1)], false, None).unwrap();
6659    /// assert!(g.has_loop().unwrap());
6660    /// ```
6661    pub fn has_loop(&self) -> IgraphResult<bool> {
6662        crate::algorithms::properties::multiplicity::has_loop(self)
6663    }
6664
6665    /// Whether the graph has at least one pair of mutual (reciprocal) edges.
6666    ///
6667    /// ```
6668    /// use rust_igraph::Graph;
6669    ///
6670    /// let g = Graph::from_edges(&[(0,1),(1,0)], true, None).unwrap();
6671    /// assert!(g.has_mutual(true).unwrap());
6672    /// ```
6673    pub fn has_mutual(&self, loops: bool) -> IgraphResult<bool> {
6674        crate::algorithms::properties::mutual::has_mutual(self, loops)
6675    }
6676
6677    /// Per-edge test: is each edge a multi-edge?
6678    ///
6679    /// ```
6680    /// use rust_igraph::Graph;
6681    ///
6682    /// let g = Graph::from_edges(&[(0,1),(0,1),(1,2)], false, None).unwrap();
6683    /// let m = g.is_multiple().unwrap();
6684    /// assert!(m.iter().any(|&x| x)); // at least one multi-edge
6685    /// ```
6686    pub fn is_multiple(&self) -> IgraphResult<Vec<bool>> {
6687        crate::algorithms::properties::multiplicity::is_multiple(self)
6688    }
6689
6690    /// Per-edge test: is each edge mutual (has a reciprocal)?
6691    ///
6692    /// ```
6693    /// use rust_igraph::Graph;
6694    ///
6695    /// let g = Graph::from_edges(&[(0,1),(1,0),(0,2)], true, None).unwrap();
6696    /// let m = g.is_mutual(true).unwrap();
6697    /// assert!(m[0]); // edge 0→1 has reciprocal 1→0
6698    /// ```
6699    pub fn is_mutual(&self, loops: bool) -> IgraphResult<Vec<bool>> {
6700        crate::algorithms::properties::mutual::is_mutual(self, loops)
6701    }
6702
6703    // ---- Community detection (batch 5) ----
6704
6705    /// Modularity of a given community assignment.
6706    ///
6707    /// ```
6708    /// use rust_igraph::Graph;
6709    ///
6710    /// let g = Graph::from_edges(
6711    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6712    ///     false, None,
6713    /// ).unwrap();
6714    /// let q = g.modularity(&[0,0,0,1,1,1], 1.0).unwrap();
6715    /// assert!(q.unwrap() > 0.0);
6716    /// ```
6717    pub fn modularity(&self, membership: &[u32], resolution: f64) -> IgraphResult<Option<f64>> {
6718        crate::algorithms::community::modularity::modularity(self, membership, resolution)
6719    }
6720
6721    // ---- Constructors / operators (batch 5) ----
6722
6723    /// Mycielskian — triangle-free graph with increasing chromatic number.
6724    ///
6725    /// ```
6726    /// use rust_igraph::Graph;
6727    ///
6728    /// let g = Graph::from_edges(&[(0,1)], false, None).unwrap();
6729    /// let m = g.mycielskian(1).unwrap();
6730    /// assert!(m.vcount() > g.vcount());
6731    /// ```
6732    pub fn mycielskian(&self, k: u32) -> IgraphResult<Graph> {
6733        crate::algorithms::constructors::mycielskian::mycielskian(self, k)
6734    }
6735
6736    /// Prüfer sequence of a labeled tree.
6737    ///
6738    /// ```
6739    /// use rust_igraph::Graph;
6740    ///
6741    /// // Path graph 0-1-2-3 is a tree
6742    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6743    /// let seq = g.to_prufer().unwrap();
6744    /// assert_eq!(seq.len(), 2); // n-2 elements for n=4
6745    /// ```
6746    pub fn to_prufer(&self) -> IgraphResult<Vec<u32>> {
6747        crate::algorithms::constructors::prufer::to_prufer(self)
6748    }
6749
6750    // ---- Connectivity / percolation (batch 5) ----
6751
6752    /// Bond (edge) percolation — add edges one by one and track components.
6753    ///
6754    /// ```
6755    /// use rust_igraph::Graph;
6756    ///
6757    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6758    /// let p = g.bond_percolation(&[0, 1, 2]).unwrap();
6759    /// assert_eq!(p.giant_size.len(), 3); // one snapshot per step
6760    /// ```
6761    pub fn bond_percolation(
6762        &self,
6763        edge_order: &[EdgeId],
6764    ) -> IgraphResult<crate::EdgelistPercolation> {
6765        crate::algorithms::connectivity::percolation::bond_percolation(self, edge_order)
6766    }
6767
6768    /// Site (vertex) percolation — add vertices one by one and track components.
6769    ///
6770    /// ```
6771    /// use rust_igraph::Graph;
6772    ///
6773    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6774    /// let p = g.site_percolation(&[0, 1, 2, 3]).unwrap();
6775    /// assert_eq!(p.giant_size.len(), 4);
6776    /// ```
6777    pub fn site_percolation(
6778        &self,
6779        vertex_order: &[VertexId],
6780    ) -> IgraphResult<crate::SitePercolation> {
6781        crate::algorithms::connectivity::percolation::site_percolation(self, vertex_order)
6782    }
6783
6784    /// Reachability matrix (transitive closure as a boolean matrix).
6785    ///
6786    /// ```
6787    /// use rust_igraph::{Graph, ReachabilityMode};
6788    ///
6789    /// let g = Graph::from_edges(&[(0,1),(1,2)], true, None).unwrap();
6790    /// let r = g.reachability(ReachabilityMode::Out).unwrap();
6791    /// assert!(r.is_reachable(0, 2)); // 0 can reach 2 transitively
6792    /// ```
6793    pub fn reachability(
6794        &self,
6795        mode: crate::ReachabilityMode,
6796    ) -> IgraphResult<crate::ReachabilityResult> {
6797        crate::algorithms::connectivity::reachability_scc::reachability(self, mode)
6798    }
6799
6800    // ---- Neighborhoods (batch 5) ----
6801
6802    /// Induced subgraphs of k-hop neighborhoods around each vertex.
6803    ///
6804    /// ```
6805    /// use rust_igraph::Graph;
6806    ///
6807    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6808    /// let nbrs = g.neighborhood_graphs(1).unwrap();
6809    /// assert_eq!(nbrs.len(), 4); // one subgraph per vertex
6810    /// ```
6811    pub fn neighborhood_graphs(&self, order: i32) -> IgraphResult<Vec<Graph>> {
6812        crate::algorithms::properties::neighborhood::neighborhood_graphs(self, order)
6813    }
6814
6815    // ---- Cliques (batch 5) ----
6816
6817    /// Weighted clique number — maximum total weight of any clique.
6818    ///
6819    /// ```
6820    /// use rust_igraph::Graph;
6821    ///
6822    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
6823    /// let wc = g.weighted_clique_number(&[1.0, 2.0, 3.0]).unwrap();
6824    /// assert!((wc - 6.0).abs() < 1e-10); // triangle, all 3 vertices
6825    /// ```
6826    pub fn weighted_clique_number(&self, vertex_weights: &[f64]) -> IgraphResult<f64> {
6827        crate::algorithms::cliques::weighted_clique_number(self, vertex_weights)
6828    }
6829
6830    // ---- Isomorphism (batch 5) ----
6831
6832    /// Isomorphism class of a small graph (up to 6 vertices).
6833    ///
6834    /// ```
6835    /// use rust_igraph::Graph;
6836    ///
6837    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6838    /// let cls = g.isoclass().unwrap();
6839    /// assert!(cls > 0);
6840    /// ```
6841    pub fn isoclass(&self) -> IgraphResult<u32> {
6842        crate::algorithms::motifs::isoclass::isoclass(self)
6843    }
6844
6845    // ---- Layout (batch 5) ----
6846
6847    /// Multidimensional scaling layout.
6848    ///
6849    /// ```
6850    /// use rust_igraph::Graph;
6851    ///
6852    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6853    /// let pos = g.layout_mds(None).unwrap();
6854    /// assert_eq!(pos.len(), 4);
6855    /// ```
6856    pub fn layout_mds(&self, dist: Option<&[Vec<f64>]>) -> IgraphResult<Vec<[f64; 2]>> {
6857        crate::algorithms::layout::mds::layout_mds(self, dist)
6858    }
6859
6860    /// Spherical layout (3D positions on a unit sphere).
6861    ///
6862    /// ```
6863    /// use rust_igraph::Graph;
6864    ///
6865    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6866    /// let pos = g.layout_sphere();
6867    /// assert_eq!(pos.len(), 3);
6868    /// ```
6869    pub fn layout_sphere(&self) -> Vec<(f64, f64, f64)> {
6870        crate::algorithms::layout::simple::layout_sphere(self)
6871    }
6872
6873    // ---- Weighted community detection (batch 6) ----
6874
6875    /// Louvain community detection with edge weights.
6876    ///
6877    /// ```
6878    /// use rust_igraph::Graph;
6879    ///
6880    /// let g = Graph::from_edges(
6881    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6882    ///     false, None,
6883    /// ).unwrap();
6884    /// let r = g.louvain_weighted(&[1.0; 7]).unwrap();
6885    /// assert!(r.modularity > 0.0);
6886    /// ```
6887    pub fn louvain_weighted(&self, weights: &[f64]) -> IgraphResult<crate::LouvainResult> {
6888        crate::algorithms::community::louvain::louvain_weighted(self, weights)
6889    }
6890
6891    /// Leiden community detection with edge weights.
6892    ///
6893    /// ```
6894    /// use rust_igraph::Graph;
6895    ///
6896    /// let g = Graph::from_edges(
6897    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6898    ///     false, None,
6899    /// ).unwrap();
6900    /// let r = g.leiden_weighted(&[1.0; 7]).unwrap();
6901    /// assert!(r.quality > 0.0);
6902    /// ```
6903    pub fn leiden_weighted(&self, weights: &[f64]) -> IgraphResult<crate::LeidenResult> {
6904        crate::algorithms::community::leiden::leiden_weighted(self, weights)
6905    }
6906
6907    /// Label propagation with edge weights.
6908    ///
6909    /// ```
6910    /// use rust_igraph::Graph;
6911    ///
6912    /// let g = Graph::from_edges(
6913    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6914    ///     false, None,
6915    /// ).unwrap();
6916    /// let r = g.label_propagation_weighted(&[1.0; 7]).unwrap();
6917    /// assert_eq!(r.membership.len(), 6);
6918    /// ```
6919    pub fn label_propagation_weighted(&self, weights: &[f64]) -> IgraphResult<crate::LpaResult> {
6920        crate::algorithms::community::label_propagation::label_propagation_weighted(self, weights)
6921    }
6922
6923    /// Walktrap community detection with edge weights.
6924    ///
6925    /// ```
6926    /// use rust_igraph::Graph;
6927    ///
6928    /// let g = Graph::from_edges(
6929    ///     &[(0,1),(1,2),(2,0),(3,4),(4,5),(5,3),(0,3)],
6930    ///     false, None,
6931    /// ).unwrap();
6932    /// let r = g.walktrap_weighted(&[1.0; 7]).unwrap();
6933    /// assert!(!r.modularity.is_empty());
6934    /// ```
6935    pub fn walktrap_weighted(&self, weights: &[f64]) -> IgraphResult<crate::WalktrapResult> {
6936        crate::algorithms::community::walktrap::walktrap_weighted(self, weights)
6937    }
6938
6939    // ---- Weighted distance/centrality (batch 6) ----
6940
6941    /// Weighted diameter (longest shortest-path distance).
6942    ///
6943    /// ```
6944    /// use rust_igraph::Graph;
6945    ///
6946    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6947    /// let d = g.diameter_weighted(&[1.0, 2.0]).unwrap();
6948    /// assert!((d.unwrap() - 3.0).abs() < 1e-10);
6949    /// ```
6950    pub fn diameter_weighted(&self, weights: &[f64]) -> IgraphResult<Option<f64>> {
6951        crate::algorithms::paths::radii::diameter_weighted(self, weights)
6952    }
6953
6954    /// Weighted eccentricity per vertex.
6955    ///
6956    /// ```
6957    /// use rust_igraph::Graph;
6958    ///
6959    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6960    /// let e = g.eccentricity_weighted(&[1.0, 2.0]).unwrap();
6961    /// assert_eq!(e.len(), 3);
6962    /// ```
6963    pub fn eccentricity_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<f64>> {
6964        crate::algorithms::paths::radii::eccentricity_weighted(self, weights)
6965    }
6966
6967    /// Weighted graph radius.
6968    ///
6969    /// ```
6970    /// use rust_igraph::Graph;
6971    ///
6972    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
6973    /// let r = g.radius_weighted(&[1.0, 2.0]).unwrap();
6974    /// assert!(r.is_some());
6975    /// ```
6976    pub fn radius_weighted(&self, weights: &[f64]) -> IgraphResult<Option<f64>> {
6977        crate::algorithms::paths::radii::radius_weighted(self, weights)
6978    }
6979
6980    /// Weighted knn(k) — average nearest-neighbor degree by degree class.
6981    ///
6982    /// ```
6983    /// use rust_igraph::Graph;
6984    ///
6985    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
6986    /// let k = g.knnk_weighted(&[1.0, 1.0, 1.0]).unwrap();
6987    /// assert!(!k.is_empty());
6988    /// ```
6989    pub fn knnk_weighted(&self, weights: &[f64]) -> IgraphResult<Vec<Option<f64>>> {
6990        crate::algorithms::properties::knn::knnk_weighted(self, weights)
6991    }
6992
6993    /// `PageRank` via linear-system solver (alternative to power iteration).
6994    ///
6995    /// ```
6996    /// use rust_igraph::Graph;
6997    ///
6998    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], true, None).unwrap();
6999    /// let pr = g.pagerank_linsys().unwrap();
7000    /// assert_eq!(pr.len(), 3);
7001    /// ```
7002    pub fn pagerank_linsys(&self) -> IgraphResult<Vec<f64>> {
7003        crate::algorithms::properties::pagerank_linsys::pagerank_linsys(self)
7004    }
7005
7006    /// Local scan statistic of order k.
7007    ///
7008    /// ```
7009    /// use rust_igraph::Graph;
7010    ///
7011    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7012    /// let s = g.local_scan_k(1, None).unwrap();
7013    /// assert_eq!(s.len(), 3);
7014    /// ```
7015    pub fn local_scan_k(&self, k: u32, weights: Option<&[f64]>) -> IgraphResult<Vec<f64>> {
7016        crate::algorithms::properties::local_scan_k::local_scan_k(self, k, weights)
7017    }
7018
7019    // ---- Validators / predicates (batch 6) ----
7020
7021    /// Per-edge test: is each edge a self-loop?
7022    ///
7023    /// ```
7024    /// use rust_igraph::Graph;
7025    ///
7026    /// let g = Graph::from_edges(&[(0,1),(1,1),(2,3)], false, None).unwrap();
7027    /// let loops = g.is_loop().unwrap();
7028    /// assert!(!loops[0]); // 0-1 is not a loop
7029    /// assert!(loops[1]);  // 1-1 is a loop
7030    /// ```
7031    pub fn is_loop(&self) -> IgraphResult<Vec<bool>> {
7032        crate::algorithms::properties::multiplicity::is_loop(self)
7033    }
7034
7035    /// Whether a set of vertices forms a clique.
7036    ///
7037    /// ```
7038    /// use rust_igraph::Graph;
7039    ///
7040    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7041    /// assert!(g.is_clique(&[0, 1, 2], false).unwrap());
7042    /// ```
7043    pub fn is_clique(&self, vertices: &[VertexId], directed: bool) -> IgraphResult<bool> {
7044        crate::algorithms::properties::is_clique::is_clique(self, vertices, directed)
7045    }
7046
7047    /// Whether a set of vertices forms an independent set.
7048    ///
7049    /// ```
7050    /// use rust_igraph::Graph;
7051    ///
7052    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7053    /// assert!(g.is_independent_vertex_set(&[0, 2]).unwrap());
7054    /// ```
7055    pub fn is_independent_vertex_set(&self, vertices: &[VertexId]) -> IgraphResult<bool> {
7056        crate::algorithms::properties::is_clique::is_independent_vertex_set(self, vertices)
7057    }
7058
7059    /// Whether a set of vertices is a separator (its removal disconnects the graph).
7060    ///
7061    /// ```
7062    /// use rust_igraph::Graph;
7063    ///
7064    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7065    /// assert!(g.is_separator(&[1]).unwrap());
7066    /// ```
7067    pub fn is_separator(&self, candidates: &[VertexId]) -> IgraphResult<bool> {
7068        crate::algorithms::connectivity::separators::is_separator(self, candidates)
7069    }
7070
7071    /// Whether a separator is minimal.
7072    ///
7073    /// ```
7074    /// use rust_igraph::Graph;
7075    ///
7076    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7077    /// assert!(g.is_minimal_separator(&[1]).unwrap());
7078    /// ```
7079    pub fn is_minimal_separator(&self, candidates: &[VertexId]) -> IgraphResult<bool> {
7080        crate::algorithms::connectivity::separators::is_minimal_separator(self, candidates)
7081    }
7082
7083    /// Whether a coloring is a valid vertex coloring.
7084    ///
7085    /// ```
7086    /// use rust_igraph::Graph;
7087    ///
7088    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7089    /// assert!(g.is_vertex_coloring(&[0, 1, 0]).unwrap());
7090    /// ```
7091    pub fn is_vertex_coloring(&self, colors: &[u32]) -> IgraphResult<bool> {
7092        crate::algorithms::coloring::is_vertex_coloring(self, colors)
7093    }
7094
7095    /// Whether a coloring is a valid edge coloring.
7096    ///
7097    /// ```
7098    /// use rust_igraph::Graph;
7099    ///
7100    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7101    /// assert!(g.is_edge_coloring(&[0, 1]).unwrap());
7102    /// ```
7103    pub fn is_edge_coloring(&self, colors: &[u32]) -> IgraphResult<bool> {
7104        crate::algorithms::coloring::is_edge_coloring(self, colors)
7105    }
7106
7107    /// Whether a set of vertices is a vertex cover.
7108    ///
7109    /// ```
7110    /// use rust_igraph::Graph;
7111    ///
7112    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7113    /// assert!(g.is_vertex_cover(&[1]));
7114    /// ```
7115    pub fn is_vertex_cover(&self, cover: &[VertexId]) -> bool {
7116        crate::algorithms::vertex_cover::is_vertex_cover(self, cover)
7117    }
7118
7119    /// Whether a set of edges is an edge cover.
7120    ///
7121    /// ```
7122    /// use rust_igraph::Graph;
7123    ///
7124    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7125    /// assert!(g.is_edge_cover(&[0, 1]));
7126    /// ```
7127    pub fn is_edge_cover(&self, cover: &[EdgeId]) -> bool {
7128        crate::algorithms::edge_cover::is_edge_cover(self, cover)
7129    }
7130
7131    /// Whether a set of vertices is a dominating set.
7132    ///
7133    /// ```
7134    /// use rust_igraph::Graph;
7135    ///
7136    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7137    /// assert!(g.is_dominating_set(&[1]));
7138    /// ```
7139    pub fn is_dominating_set(&self, dom_set: &[VertexId]) -> bool {
7140        crate::algorithms::dominating_set::is_dominating_set(self, dom_set)
7141    }
7142
7143    // ---- Operators (batch 6) ----
7144
7145    /// Reverse specific edges in a directed graph.
7146    ///
7147    /// ```
7148    /// use rust_igraph::Graph;
7149    ///
7150    /// let g = Graph::from_edges(&[(0,1),(1,2)], true, None).unwrap();
7151    /// let r = g.reverse_edges(&[0]).unwrap();
7152    /// assert_eq!(r.edge(0).unwrap(), (1, 0));
7153    /// ```
7154    pub fn reverse_edges(&self, eids: &[u32]) -> IgraphResult<Graph> {
7155        crate::algorithms::operators::reverse::reverse_edges(self, eids)
7156    }
7157
7158    /// Edges induced by a subset of vertices.
7159    ///
7160    /// ```
7161    /// use rust_igraph::Graph;
7162    ///
7163    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7164    /// let eids = g.induced_subgraph_edges(&[0, 1]).unwrap();
7165    /// assert_eq!(eids.len(), 1); // only edge 0-1
7166    /// ```
7167    pub fn induced_subgraph_edges(&self, vids: &[u32]) -> IgraphResult<Vec<u32>> {
7168        crate::algorithms::operators::induced_subgraph_edges::induced_subgraph_edges(self, vids)
7169    }
7170
7171    // ---- Similarity (batch 6) ----
7172
7173    /// Dice similarity between all vertex pairs (n*n flat matrix).
7174    ///
7175    /// ```
7176    /// use rust_igraph::Graph;
7177    ///
7178    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7179    /// let s = g.similarity_dice().unwrap();
7180    /// let n = g.vcount() as usize;
7181    /// assert_eq!(s.len(), n * n);
7182    /// ```
7183    pub fn similarity_dice(&self) -> IgraphResult<Vec<f64>> {
7184        crate::algorithms::properties::similarity::similarity_dice(self)
7185    }
7186
7187    /// Inverse-log-weighted similarity between all vertex pairs.
7188    ///
7189    /// ```
7190    /// use rust_igraph::Graph;
7191    ///
7192    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7193    /// let s = g.similarity_inverse_log_weighted().unwrap();
7194    /// let n = g.vcount() as usize;
7195    /// assert_eq!(s.len(), n * n);
7196    /// ```
7197    pub fn similarity_inverse_log_weighted(&self) -> IgraphResult<Vec<f64>> {
7198        crate::algorithms::properties::similarity::similarity_inverse_log_weighted(self)
7199    }
7200
7201    /// Jaccard similarity for given edges.
7202    ///
7203    /// ```
7204    /// use rust_igraph::Graph;
7205    ///
7206    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7207    /// let s = g.similarity_jaccard_es(&[0, 1]).unwrap();
7208    /// assert_eq!(s.len(), 2);
7209    /// ```
7210    pub fn similarity_jaccard_es(&self, eids: &[u32]) -> IgraphResult<Vec<f64>> {
7211        crate::algorithms::properties::similarity::similarity_jaccard_es(self, eids)
7212    }
7213
7214    // ---- Layout (batch 6) ----
7215
7216    /// Large Graph Layout (LGL).
7217    ///
7218    /// ```
7219    /// use rust_igraph::{Graph, LglParams};
7220    ///
7221    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7222    /// let pos = g.layout_lgl(&LglParams::default()).unwrap();
7223    /// assert_eq!(pos.len(), 4);
7224    /// ```
7225    pub fn layout_lgl(&self, params: &crate::LglParams) -> IgraphResult<Vec<[f64; 2]>> {
7226        crate::algorithms::layout::lgl::layout_lgl(self, params)
7227    }
7228
7229    /// Random 3D layout.
7230    ///
7231    /// ```
7232    /// use rust_igraph::Graph;
7233    ///
7234    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7235    /// let pos = g.layout_random_3d(42);
7236    /// assert_eq!(pos.len(), 3);
7237    /// ```
7238    pub fn layout_random_3d(&self, seed: u64) -> Vec<(f64, f64, f64)> {
7239        crate::algorithms::layout::simple::layout_random_3d(self, seed)
7240    }
7241
7242    /// Grid 3D layout.
7243    ///
7244    /// ```
7245    /// use rust_igraph::Graph;
7246    ///
7247    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7248    /// let pos = g.layout_grid_3d(2, 2);
7249    /// assert_eq!(pos.len(), 4);
7250    /// ```
7251    pub fn layout_grid_3d(&self, width: i32, height: i32) -> Vec<(f64, f64, f64)> {
7252        crate::algorithms::layout::simple::layout_grid_3d(self, width, height)
7253    }
7254
7255    // ---- Motifs (batch 6) ----
7256
7257    /// Count the total number of motifs of a given size.
7258    ///
7259    /// ```
7260    /// use rust_igraph::Graph;
7261    ///
7262    /// let g = Graph::from_edges(&[(0,1),(1,2),(0,2)], false, None).unwrap();
7263    /// let n = g.motifs_randesu_no(3).unwrap();
7264    /// assert!((n - 1.0).abs() < 1e-10); // one triangle
7265    /// ```
7266    pub fn motifs_randesu_no(&self, size: u32) -> IgraphResult<f64> {
7267        crate::algorithms::motifs::motifs_randesu::motifs_randesu_no(self, size)
7268    }
7269
7270    // ---- Graph inspection (batch 6) ----
7271
7272    /// Structural summary of the graph.
7273    ///
7274    /// ```
7275    /// use rust_igraph::Graph;
7276    ///
7277    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7278    /// let s = g.graph_summary().unwrap();
7279    /// assert_eq!(s.vcount, 3);
7280    /// assert_eq!(s.ecount, 2);
7281    /// ```
7282    pub fn graph_summary(&self) -> IgraphResult<crate::GraphSummary> {
7283        crate::algorithms::properties::summary::graph_summary(self)
7284    }
7285
7286    /// Human-readable structural summary string.
7287    ///
7288    /// ```
7289    /// use rust_igraph::Graph;
7290    ///
7291    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7292    /// let s = g.graph_summary_string().unwrap();
7293    /// assert!(s.contains("Vertices: 3"));
7294    /// assert!(s.contains("Edges: 2"));
7295    /// ```
7296    pub fn graph_summary_string(&self) -> IgraphResult<String> {
7297        crate::graph_summary_string(self)
7298    }
7299
7300    // ---- Matrix representations (batch 7) ----
7301
7302    /// Adjacency matrix of the graph.
7303    ///
7304    /// Returns a dense `V×V` matrix. For undirected graphs with
7305    /// [`AdjacencyType::Both`](crate::AdjacencyType::Both), the result is symmetric.
7306    ///
7307    /// ```
7308    /// use rust_igraph::{Graph, AdjacencyType, LoopHandling};
7309    ///
7310    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7311    /// let m = g.get_adjacency(AdjacencyType::Both, LoopHandling::Once).unwrap();
7312    /// assert_eq!(m.len(), 3);
7313    /// assert!((m[0][1] - 1.0).abs() < 1e-10);
7314    /// assert!((m[0][2]).abs() < 1e-10);
7315    /// ```
7316    pub fn get_adjacency(
7317        &self,
7318        adj_type: crate::AdjacencyType,
7319        loops: crate::LoopHandling,
7320    ) -> IgraphResult<Vec<Vec<f64>>> {
7321        crate::algorithms::properties::adjacency::get_adjacency(self, adj_type, None, loops)
7322    }
7323
7324    /// Weighted adjacency matrix of the graph.
7325    ///
7326    /// ```
7327    /// use rust_igraph::{Graph, AdjacencyType, LoopHandling};
7328    ///
7329    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7330    /// let w = vec![2.0, 3.0];
7331    /// let m = g.get_adjacency_weighted(AdjacencyType::Both, &w, LoopHandling::Once).unwrap();
7332    /// assert!((m[0][1] - 2.0).abs() < 1e-10);
7333    /// ```
7334    pub fn get_adjacency_weighted(
7335        &self,
7336        adj_type: crate::AdjacencyType,
7337        weights: &[f64],
7338        loops: crate::LoopHandling,
7339    ) -> IgraphResult<Vec<Vec<f64>>> {
7340        crate::algorithms::properties::adjacency::get_adjacency(
7341            self,
7342            adj_type,
7343            Some(weights),
7344            loops,
7345        )
7346    }
7347
7348    /// Laplacian matrix L = D - A (unnormalized, unweighted).
7349    ///
7350    /// ```
7351    /// use rust_igraph::Graph;
7352    ///
7353    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7354    /// let lap = g.get_laplacian().unwrap();
7355    /// assert!((lap[0][0] - 1.0).abs() < 1e-10); // degree of vertex 0
7356    /// assert!((lap[1][1] - 2.0).abs() < 1e-10); // degree of vertex 1
7357    /// ```
7358    pub fn get_laplacian(&self) -> IgraphResult<Vec<Vec<f64>>> {
7359        crate::algorithms::properties::laplacian::get_laplacian(
7360            self,
7361            crate::DegreeMode::All,
7362            crate::LaplacianNormalization::Unnormalized,
7363            None,
7364        )
7365    }
7366
7367    /// Laplacian matrix with normalization and optional weights.
7368    ///
7369    /// ```
7370    /// use rust_igraph::{Graph, DegreeMode, LaplacianNormalization};
7371    ///
7372    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7373    /// let lap = g.get_laplacian_full(
7374    ///     DegreeMode::All,
7375    ///     LaplacianNormalization::Symmetric,
7376    ///     None,
7377    /// ).unwrap();
7378    /// assert!(lap[0][0] > 0.0);
7379    /// ```
7380    pub fn get_laplacian_full(
7381        &self,
7382        mode: crate::DegreeMode,
7383        normalization: crate::LaplacianNormalization,
7384        weights: Option<&[f64]>,
7385    ) -> IgraphResult<Vec<Vec<f64>>> {
7386        crate::algorithms::properties::laplacian::get_laplacian(self, mode, normalization, weights)
7387    }
7388
7389    /// Stochastic (transition) matrix of the graph.
7390    ///
7391    /// Each row (or column, if `column_wise` is true) sums to 1.
7392    ///
7393    /// ```
7394    /// use rust_igraph::Graph;
7395    ///
7396    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,2)], false, None).unwrap();
7397    /// let s = g.get_stochastic(false).unwrap();
7398    /// let row_sum: f64 = s[0].iter().sum();
7399    /// assert!((row_sum - 1.0).abs() < 1e-10);
7400    /// ```
7401    pub fn get_stochastic(&self, column_wise: bool) -> IgraphResult<Vec<Vec<f64>>> {
7402        crate::algorithms::properties::stochastic::get_stochastic(self, column_wise, None)
7403    }
7404
7405    // ---- Spectral embedding (batch 7) ----
7406
7407    /// Adjacency spectral embedding into `no` dimensions.
7408    ///
7409    /// Embeds the graph via the leading eigenvalues/eigenvectors of the
7410    /// adjacency matrix.
7411    ///
7412    /// ```
7413    /// use rust_igraph::{Graph, SpectralWhich};
7414    ///
7415    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3),(3,0)], false, None).unwrap();
7416    /// let emb = g.adjacency_spectral_embedding(2, SpectralWhich::LargestMagnitude).unwrap();
7417    /// assert_eq!(emb.embedding.len(), 4); // one row per vertex
7418    /// ```
7419    pub fn adjacency_spectral_embedding(
7420        &self,
7421        no: usize,
7422        which: crate::SpectralWhich,
7423    ) -> IgraphResult<crate::AdjacencySpectralEmbeddingResult> {
7424        crate::algorithms::embedding::adjacency_spectral_embedding::adjacency_spectral_embedding(
7425            self, no, None, which, true, None,
7426        )
7427    }
7428
7429    /// Laplacian spectral embedding into `no` dimensions.
7430    ///
7431    /// ```
7432    /// use rust_igraph::{Graph, SpectralWhich, LaplacianType};
7433    ///
7434    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3),(3,0)], false, None).unwrap();
7435    /// let emb = g.laplacian_spectral_embedding(
7436    ///     2, SpectralWhich::SmallestAlgebraic, LaplacianType::DA,
7437    /// ).unwrap();
7438    /// assert_eq!(emb.embedding.len(), 4);
7439    /// ```
7440    pub fn laplacian_spectral_embedding(
7441        &self,
7442        no: usize,
7443        which: crate::SpectralWhich,
7444        lap_type: crate::LaplacianType,
7445    ) -> IgraphResult<crate::LaplacianSpectralEmbeddingResult> {
7446        crate::algorithms::embedding::laplacian_spectral_embedding::laplacian_spectral_embedding(
7447            self, no, None, which, lap_type, true,
7448        )
7449    }
7450
7451    /// Eigenvalues and eigenvectors of the adjacency matrix.
7452    ///
7453    /// ```
7454    /// use rust_igraph::{Graph, EigenWhich};
7455    ///
7456    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7457    /// let eig = g.eigen_adjacency(2, EigenWhich::LargestAlgebraic).unwrap();
7458    /// assert_eq!(eig.eigenvalues.len(), 2);
7459    /// ```
7460    pub fn eigen_adjacency(
7461        &self,
7462        nev: usize,
7463        which: crate::EigenWhich,
7464    ) -> IgraphResult<crate::EigenDecomposition> {
7465        crate::algorithms::eigen::adjacency::eigen_adjacency(self, nev, which)
7466    }
7467
7468    // ---- Additional algorithms (batch 7) ----
7469
7470    /// Feedback vertex set — a minimal set of vertices whose removal
7471    /// makes the graph acyclic.
7472    ///
7473    /// ```
7474    /// use rust_igraph::{Graph, FvsAlgorithm};
7475    ///
7476    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], true, None).unwrap();
7477    /// let fvs = g.feedback_vertex_set(FvsAlgorithm::Greedy).unwrap();
7478    /// assert!(!fvs.is_empty()); // need to break the cycle
7479    /// ```
7480    pub fn feedback_vertex_set(&self, algo: crate::FvsAlgorithm) -> IgraphResult<Vec<VertexId>> {
7481        crate::algorithms::feedback_vertex_set::feedback_vertex_set(self, None, algo)
7482    }
7483
7484    /// Complement graph (all edges that are *not* in the original).
7485    ///
7486    /// Self-loops are excluded.
7487    ///
7488    /// ```
7489    /// use rust_igraph::Graph;
7490    ///
7491    /// let g = Graph::from_edges(&[(0,1)], false, Some(3)).unwrap();
7492    /// let c = g.complementer().unwrap();
7493    /// assert_eq!(c.ecount(), 2); // edges 0-2 and 1-2
7494    /// ```
7495    pub fn complementer(&self) -> IgraphResult<Graph> {
7496        crate::algorithms::operators::complementer::complementer(self, false)
7497    }
7498
7499    /// Bipartite projection sizes without building the projected graphs.
7500    ///
7501    /// `types` assigns each vertex to one of two partitions (`false`/`true`).
7502    ///
7503    /// ```
7504    /// use rust_igraph::Graph;
7505    ///
7506    /// // K_{2,3} bipartite graph
7507    /// let g = Graph::from_edges(
7508    ///     &[(0,2),(0,3),(0,4),(1,2),(1,3),(1,4)], false, None
7509    /// ).unwrap();
7510    /// let types = vec![false, false, true, true, true];
7511    /// let sz = g.bipartite_projection_size(&types).unwrap();
7512    /// assert_eq!(sz.vcount1, 2);
7513    /// assert_eq!(sz.vcount2, 3);
7514    /// ```
7515    pub fn bipartite_projection_size(
7516        &self,
7517        types: &[bool],
7518    ) -> IgraphResult<crate::BipartiteProjectionSize> {
7519        crate::algorithms::operators::bipartite_projection_size::bipartite_projection_size(
7520            self, types,
7521        )
7522    }
7523
7524    /// Unfold the graph into a tree by BFS from root vertices.
7525    ///
7526    /// Returns the unfolded tree and a mapping from new to old vertex ids.
7527    ///
7528    /// ```
7529    /// use rust_igraph::{Graph, DegreeMode, DijkstraMode, UnfoldTreeResult};
7530    ///
7531    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7532    /// let r = g.unfold_tree(&[0], DegreeMode::All).unwrap();
7533    /// assert!(r.tree.is_tree(DijkstraMode::All).unwrap().is_some());
7534    /// assert!(!r.vertex_index.is_empty());
7535    /// ```
7536    pub fn unfold_tree(
7537        &self,
7538        roots: &[VertexId],
7539        mode: crate::DegreeMode,
7540    ) -> IgraphResult<crate::UnfoldTreeResult> {
7541        crate::algorithms::properties::unfold_tree::unfold_tree(self, roots, mode)
7542    }
7543
7544    // ---- Analysis / flow / isomorphism (batch 8) ----
7545
7546    /// S-t edge connectivity between two vertices.
7547    ///
7548    /// ```
7549    /// use rust_igraph::Graph;
7550    ///
7551    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,3),(2,3)], false, None).unwrap();
7552    /// let k = g.st_edge_connectivity(0, 3).unwrap();
7553    /// assert_eq!(k, 2);
7554    /// ```
7555    pub fn st_edge_connectivity(&self, source: u32, target: u32) -> IgraphResult<i64> {
7556        crate::st_edge_connectivity(self, source, target)
7557    }
7558
7559    /// Vertex-disjoint paths between two vertices.
7560    ///
7561    /// ```
7562    /// use rust_igraph::Graph;
7563    ///
7564    /// let g = Graph::from_edges(&[(0,1),(0,2),(1,3),(2,3)], false, None).unwrap();
7565    /// let n = g.vertex_disjoint_paths(0, 3).unwrap();
7566    /// assert_eq!(n, 2);
7567    /// ```
7568    pub fn vertex_disjoint_paths(&self, source: u32, target: u32) -> IgraphResult<i64> {
7569        crate::vertex_disjoint_paths(self, source, target)
7570    }
7571
7572    /// Test isomorphism using BLISS canonical labeling.
7573    ///
7574    /// ```
7575    /// use rust_igraph::{Graph, full_graph};
7576    ///
7577    /// let g1 = full_graph(4, false, false).unwrap();
7578    /// let g2 = full_graph(4, false, false).unwrap();
7579    /// let result = g1.isomorphic_bliss(&g2, None, None).unwrap();
7580    /// assert!(result.iso);
7581    /// ```
7582    pub fn isomorphic_bliss(
7583        &self,
7584        other: &Graph,
7585        colors1: Option<&[u32]>,
7586        colors2: Option<&[u32]>,
7587    ) -> IgraphResult<crate::Vf2Isomorphism> {
7588        crate::isomorphic_bliss(self, other, colors1, colors2)
7589    }
7590
7591    /// LAD subgraph isomorphism test.
7592    ///
7593    /// ```
7594    /// use rust_igraph::Graph;
7595    ///
7596    /// let target = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3)], false, None).unwrap();
7597    /// let pattern = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7598    /// let iso = target.subisomorphic_lad(&pattern, false, None).unwrap();
7599    /// assert!(iso.iso);
7600    /// ```
7601    pub fn subisomorphic_lad(
7602        &self,
7603        pattern: &Graph,
7604        induced: bool,
7605        domains: Option<&[Vec<u32>]>,
7606    ) -> IgraphResult<crate::LadSubisomorphism> {
7607        crate::subisomorphic_lad(pattern, self, domains, induced)
7608    }
7609
7610    /// Betweenness centrality for a subset of vertices.
7611    ///
7612    /// ```
7613    /// use rust_igraph::Graph;
7614    ///
7615    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7616    /// let bc = g.betweenness_subset(&[0, 1], &[2, 3]).unwrap();
7617    /// assert_eq!(bc.len(), 4);
7618    /// ```
7619    pub fn betweenness_subset(&self, sources: &[u32], targets: &[u32]) -> IgraphResult<Vec<f64>> {
7620        crate::betweenness_subset(self, sources, targets, self.is_directed())
7621    }
7622
7623    /// Edge betweenness centrality for a subset of vertices.
7624    ///
7625    /// ```
7626    /// use rust_igraph::Graph;
7627    ///
7628    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,3)], false, None).unwrap();
7629    /// let ebc = g.edge_betweenness_subset(&[0, 1], &[2, 3]).unwrap();
7630    /// assert_eq!(ebc.len(), 3);
7631    /// ```
7632    pub fn edge_betweenness_subset(
7633        &self,
7634        sources: &[u32],
7635        targets: &[u32],
7636    ) -> IgraphResult<Vec<f64>> {
7637        crate::edge_betweenness_subset(self, sources, targets, self.is_directed())
7638    }
7639
7640    /// Weighted edge betweenness community detection.
7641    ///
7642    /// ```
7643    /// use rust_igraph::Graph;
7644    ///
7645    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0),(2,3),(3,4),(4,5),(5,3)], false, None).unwrap();
7646    /// let w = vec![1.0; g.ecount() as usize];
7647    /// let result = g.edge_betweenness_community_weighted(&w).unwrap();
7648    /// assert!(!result.merges.is_empty());
7649    /// ```
7650    pub fn edge_betweenness_community_weighted(
7651        &self,
7652        weights: &[f64],
7653    ) -> IgraphResult<crate::EdgeBetweennessResult> {
7654        crate::edge_betweenness_community_weighted(self, weights)
7655    }
7656
7657    /// Modularity matrix B = A - k*k'/2m.
7658    ///
7659    /// ```
7660    /// use rust_igraph::Graph;
7661    ///
7662    /// let g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7663    /// let b = g.modularity_matrix(None).unwrap();
7664    /// assert_eq!(b.len(), 3);
7665    /// ```
7666    pub fn modularity_matrix(&self, weights: Option<&[f64]>) -> IgraphResult<Vec<Vec<f64>>> {
7667        crate::modularity_matrix(self, weights, 1.0, self.is_directed())
7668    }
7669
7670    /// Whether the graph is the same as another (structural equality).
7671    ///
7672    /// ```
7673    /// use rust_igraph::Graph;
7674    ///
7675    /// let g1 = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7676    /// let g2 = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7677    /// assert!(g1.is_same_graph(&g2));
7678    /// ```
7679    pub fn is_same_graph(&self, other: &Graph) -> bool {
7680        crate::is_same_graph(self, other)
7681    }
7682
7683    /// Mean distance (weighted).
7684    ///
7685    /// ```
7686    /// use rust_igraph::Graph;
7687    ///
7688    /// let g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7689    /// let w = vec![1.0, 2.0];
7690    /// let md = g.mean_distance_weighted(&w).unwrap();
7691    /// assert!(md.is_some());
7692    /// ```
7693    pub fn mean_distance_weighted(&self, weights: &[f64]) -> IgraphResult<Option<f64>> {
7694        crate::mean_distance_weighted(self, weights, self.is_directed(), true)
7695    }
7696
7697    // ---- Attribute system ----
7698
7699    /// Set a graph-level attribute.
7700    ///
7701    /// Overwrites any existing value with the same name.
7702    ///
7703    /// ```
7704    /// use rust_igraph::{Graph, AttributeValue};
7705    ///
7706    /// let mut g = Graph::with_vertices(0);
7707    /// g.set_graph_attribute("name", "test".into());
7708    /// assert_eq!(
7709    ///     g.graph_attribute("name").and_then(|v| v.as_str()),
7710    ///     Some("test"),
7711    /// );
7712    /// ```
7713    pub fn set_graph_attribute(&mut self, name: impl Into<String>, value: AttributeValue) {
7714        self.gattrs.insert(name.into(), value);
7715    }
7716
7717    /// Get a graph-level attribute by name.
7718    ///
7719    /// ```
7720    /// use rust_igraph::{Graph, AttributeValue};
7721    ///
7722    /// let g = Graph::with_vertices(0);
7723    /// assert!(g.graph_attribute("missing").is_none());
7724    /// ```
7725    #[must_use]
7726    pub fn graph_attribute(&self, name: &str) -> Option<&AttributeValue> {
7727        self.gattrs.get(name)
7728    }
7729
7730    /// Delete a graph-level attribute. Returns `true` if it existed.
7731    pub fn delete_graph_attribute(&mut self, name: &str) -> bool {
7732        self.gattrs.remove(name).is_some()
7733    }
7734
7735    /// Check whether a graph-level attribute exists.
7736    #[must_use]
7737    pub fn has_graph_attribute(&self, name: &str) -> bool {
7738        self.gattrs.contains_key(name)
7739    }
7740
7741    /// List all graph-level attribute names.
7742    ///
7743    /// ```
7744    /// use rust_igraph::{Graph, AttributeValue};
7745    ///
7746    /// let mut g = Graph::with_vertices(0);
7747    /// g.set_graph_attribute("name", "test".into());
7748    /// g.set_graph_attribute("year", 2024.0.into());
7749    /// let names = g.graph_attribute_names();
7750    /// assert!(names.contains(&"name"));
7751    /// assert!(names.contains(&"year"));
7752    /// ```
7753    #[must_use]
7754    pub fn graph_attribute_names(&self) -> Vec<&str> {
7755        self.gattrs.keys().map(String::as_str).collect()
7756    }
7757
7758    /// Set a vertex attribute for a single vertex.
7759    ///
7760    /// Creates the attribute vector if it doesn't exist. New entries
7761    /// for other vertices are filled with a type-appropriate default.
7762    ///
7763    /// ```
7764    /// use rust_igraph::{Graph, AttributeValue};
7765    ///
7766    /// let mut g = Graph::with_vertices(3);
7767    /// g.set_vertex_attribute("label", 0, "Alice".into()).unwrap();
7768    /// g.set_vertex_attribute("label", 1, "Bob".into()).unwrap();
7769    /// assert_eq!(
7770    ///     g.vertex_attribute("label", 0).and_then(|v| v.as_str()),
7771    ///     Some("Alice"),
7772    /// );
7773    /// ```
7774    pub fn set_vertex_attribute(
7775        &mut self,
7776        name: impl Into<String>,
7777        vertex: VertexId,
7778        value: AttributeValue,
7779    ) -> IgraphResult<()> {
7780        self.check_vertex(vertex)?;
7781        let n = self.n as usize;
7782        let key = name.into();
7783        let vals = self.vertex_attrs.entry(key).or_insert_with(|| {
7784            let default = value.default_for_same_type();
7785            vec![default; n]
7786        });
7787        vals[vertex as usize] = value;
7788        Ok(())
7789    }
7790
7791    /// Set a vertex attribute for all vertices at once.
7792    ///
7793    /// The `values` slice must have length equal to `vcount()`.
7794    ///
7795    /// ```
7796    /// use rust_igraph::{Graph, AttributeValue};
7797    ///
7798    /// let mut g = Graph::with_vertices(3);
7799    /// g.set_vertex_attribute_all(
7800    ///     "color",
7801    ///     vec![1.0.into(), 2.0.into(), 3.0.into()],
7802    /// ).unwrap();
7803    /// let colors = g.vertex_attributes("color").unwrap();
7804    /// assert_eq!(colors.len(), 3);
7805    /// ```
7806    pub fn set_vertex_attribute_all(
7807        &mut self,
7808        name: impl Into<String>,
7809        values: Vec<AttributeValue>,
7810    ) -> IgraphResult<()> {
7811        if values.len() != self.n as usize {
7812            return Err(IgraphError::InvalidArgument(format!(
7813                "attribute vector length {} does not match vcount {}",
7814                values.len(),
7815                self.n,
7816            )));
7817        }
7818        self.vertex_attrs.insert(name.into(), values);
7819        Ok(())
7820    }
7821
7822    /// Get a vertex attribute for a single vertex.
7823    #[must_use]
7824    pub fn vertex_attribute(&self, name: &str, vertex: VertexId) -> Option<&AttributeValue> {
7825        self.vertex_attrs
7826            .get(name)
7827            .and_then(|vals| vals.get(vertex as usize))
7828    }
7829
7830    /// Get the full vertex attribute vector by name.
7831    #[must_use]
7832    pub fn vertex_attributes(&self, name: &str) -> Option<&[AttributeValue]> {
7833        self.vertex_attrs.get(name).map(Vec::as_slice)
7834    }
7835
7836    /// Delete a vertex attribute. Returns `true` if it existed.
7837    pub fn delete_vertex_attribute(&mut self, name: &str) -> bool {
7838        self.vertex_attrs.remove(name).is_some()
7839    }
7840
7841    /// Check whether a vertex attribute exists.
7842    #[must_use]
7843    pub fn has_vertex_attribute(&self, name: &str) -> bool {
7844        self.vertex_attrs.contains_key(name)
7845    }
7846
7847    /// List all vertex attribute names.
7848    ///
7849    /// ```
7850    /// use rust_igraph::{Graph, AttributeValue};
7851    ///
7852    /// let mut g = Graph::with_vertices(2);
7853    /// g.set_vertex_attribute("name", 0, "A".into()).unwrap();
7854    /// assert!(g.vertex_attribute_names().contains(&"name"));
7855    /// ```
7856    #[must_use]
7857    pub fn vertex_attribute_names(&self) -> Vec<&str> {
7858        self.vertex_attrs.keys().map(String::as_str).collect()
7859    }
7860
7861    /// Set an edge attribute for a single edge.
7862    ///
7863    /// Creates the attribute vector if it doesn't exist. New entries
7864    /// for other edges are filled with a type-appropriate default.
7865    ///
7866    /// ```
7867    /// use rust_igraph::{Graph, AttributeValue};
7868    ///
7869    /// let mut g = Graph::from_edges(&[(0,1),(1,2)], false, None).unwrap();
7870    /// g.set_edge_attribute("weight", 0, 1.5.into()).unwrap();
7871    /// assert_eq!(
7872    ///     g.edge_attribute("weight", 0).and_then(|v| v.as_f64()),
7873    ///     Some(1.5),
7874    /// );
7875    /// ```
7876    pub fn set_edge_attribute(
7877        &mut self,
7878        name: impl Into<String>,
7879        edge: EdgeId,
7880        value: AttributeValue,
7881    ) -> IgraphResult<()> {
7882        let m = self.ecount();
7883        if (edge as usize) >= m {
7884            return Err(IgraphError::EdgeOutOfRange {
7885                id: edge,
7886                m: u32::try_from(m).unwrap_or(u32::MAX),
7887            });
7888        }
7889        let key = name.into();
7890        let vals = self.edge_attrs.entry(key).or_insert_with(|| {
7891            let default = value.default_for_same_type();
7892            vec![default; m]
7893        });
7894        vals[edge as usize] = value;
7895        Ok(())
7896    }
7897
7898    /// Set an edge attribute for all edges at once.
7899    ///
7900    /// The `values` slice must have length equal to `ecount()`.
7901    ///
7902    /// ```
7903    /// use rust_igraph::{Graph, AttributeValue};
7904    ///
7905    /// let mut g = Graph::from_edges(&[(0,1),(1,2),(2,0)], false, None).unwrap();
7906    /// g.set_edge_attribute_all(
7907    ///     "weight",
7908    ///     vec![1.0.into(), 2.0.into(), 3.0.into()],
7909    /// ).unwrap();
7910    /// let w = g.edge_attributes("weight").unwrap();
7911    /// assert_eq!(w.len(), 3);
7912    /// ```
7913    pub fn set_edge_attribute_all(
7914        &mut self,
7915        name: impl Into<String>,
7916        values: Vec<AttributeValue>,
7917    ) -> IgraphResult<()> {
7918        let m = self.ecount();
7919        if values.len() != m {
7920            return Err(IgraphError::InvalidArgument(format!(
7921                "attribute vector length {} does not match ecount {}",
7922                values.len(),
7923                m,
7924            )));
7925        }
7926        self.edge_attrs.insert(name.into(), values);
7927        Ok(())
7928    }
7929
7930    /// Get an edge attribute for a single edge.
7931    #[must_use]
7932    pub fn edge_attribute(&self, name: &str, edge: EdgeId) -> Option<&AttributeValue> {
7933        self.edge_attrs
7934            .get(name)
7935            .and_then(|vals| vals.get(edge as usize))
7936    }
7937
7938    /// Get the full edge attribute vector by name.
7939    #[must_use]
7940    pub fn edge_attributes(&self, name: &str) -> Option<&[AttributeValue]> {
7941        self.edge_attrs.get(name).map(Vec::as_slice)
7942    }
7943
7944    /// Delete an edge attribute. Returns `true` if it existed.
7945    pub fn delete_edge_attribute(&mut self, name: &str) -> bool {
7946        self.edge_attrs.remove(name).is_some()
7947    }
7948
7949    /// Check whether an edge attribute exists.
7950    #[must_use]
7951    pub fn has_edge_attribute(&self, name: &str) -> bool {
7952        self.edge_attrs.contains_key(name)
7953    }
7954
7955    /// List all edge attribute names.
7956    ///
7957    /// ```
7958    /// use rust_igraph::{Graph, AttributeValue};
7959    ///
7960    /// let mut g = Graph::from_edges(&[(0,1)], false, None).unwrap();
7961    /// g.set_edge_attribute("weight", 0, 1.0.into()).unwrap();
7962    /// assert!(g.edge_attribute_names().contains(&"weight"));
7963    /// ```
7964    #[must_use]
7965    pub fn edge_attribute_names(&self) -> Vec<&str> {
7966        self.edge_attrs.keys().map(String::as_str).collect()
7967    }
7968}
7969
7970impl std::fmt::Display for Graph {
7971    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7972        let kind = if self.directed {
7973            "Directed"
7974        } else {
7975            "Undirected"
7976        };
7977        write!(
7978            f,
7979            "{kind} graph with {} vertices and {} edges",
7980            self.n,
7981            self.ecount()
7982        )
7983    }
7984}
7985
7986/// Iterate over a graph's edges by reference.
7987///
7988/// Yields `(from, to)` pairs in edge-id order, enabling the idiomatic
7989/// `for (u, v) in &graph { ... }` pattern.
7990///
7991/// # Examples
7992///
7993/// ```
7994/// use rust_igraph::Graph;
7995///
7996/// let mut g = Graph::with_vertices(3);
7997/// g.add_edge(0, 1).unwrap();
7998/// g.add_edge(1, 2).unwrap();
7999///
8000/// let edges: Vec<_> = (&g).into_iter().collect();
8001/// assert_eq!(edges, vec![(0, 1), (1, 2)]);
8002/// ```
8003impl<'a> IntoIterator for &'a Graph {
8004    type Item = (VertexId, VertexId);
8005    type IntoIter = EdgeIter<'a>;
8006
8007    fn into_iter(self) -> Self::IntoIter {
8008        self.iter()
8009    }
8010}
8011
8012/// Construct an undirected graph from a slice of `(from, to)` edge pairs.
8013///
8014/// Vertex count is inferred from the maximum endpoint id (plus one).
8015/// This is a convenience for quick construction; for more control use
8016/// [`Graph::from_edges`] or [`GraphBuilder`](super::builder::GraphBuilder).
8017///
8018/// # Examples
8019///
8020/// ```
8021/// use rust_igraph::Graph;
8022///
8023/// let edges = vec![(0u32, 1), (1, 2), (2, 0)];
8024/// let g = Graph::try_from(edges.as_slice()).unwrap();
8025/// assert_eq!(g.vcount(), 3);
8026/// assert_eq!(g.ecount(), 3);
8027/// assert!(!g.is_directed());
8028/// ```
8029impl TryFrom<&[(VertexId, VertexId)]> for Graph {
8030    type Error = IgraphError;
8031
8032    fn try_from(edges: &[(VertexId, VertexId)]) -> IgraphResult<Self> {
8033        let n = match edges.iter().flat_map(|&(u, v)| [u, v]).max() {
8034            Some(m) => m
8035                .checked_add(1)
8036                .ok_or_else(|| IgraphError::InvalidArgument("vertex id overflow".to_owned()))?,
8037            None => 0,
8038        };
8039        let mut g = Self::new(n, false)?;
8040        g.add_edges(edges.to_vec())?;
8041        Ok(g)
8042    }
8043}
8044
8045/// Construct an undirected graph from a `Vec` of `(from, to)` edge pairs.
8046///
8047/// # Examples
8048///
8049/// ```
8050/// use rust_igraph::Graph;
8051///
8052/// let g = Graph::try_from(vec![(0u32, 1), (1, 2), (2, 3)]).unwrap();
8053/// assert_eq!(g.vcount(), 4);
8054/// assert_eq!(g.ecount(), 3);
8055/// ```
8056impl TryFrom<Vec<(VertexId, VertexId)>> for Graph {
8057    type Error = IgraphError;
8058
8059    fn try_from(edges: Vec<(VertexId, VertexId)>) -> IgraphResult<Self> {
8060        let n = match edges.iter().flat_map(|&(u, v)| [u, v]).max() {
8061            Some(m) => m
8062                .checked_add(1)
8063                .ok_or_else(|| IgraphError::InvalidArgument("vertex id overflow".to_owned()))?,
8064            None => 0,
8065        };
8066        let mut g = Self::new(n, false)?;
8067        g.add_edges(edges)?;
8068        Ok(g)
8069    }
8070}
8071
8072/// Collect edges from an iterator into an undirected graph.
8073///
8074/// Vertex count is inferred from the maximum endpoint id.
8075/// For directed graphs or explicit vertex counts, use [`Graph::from_edges`].
8076///
8077/// # Panics
8078///
8079/// Panics if a vertex id would overflow `u32::MAX`.
8080///
8081/// # Examples
8082///
8083/// ```
8084/// use rust_igraph::Graph;
8085///
8086/// let g: Graph = [(0u32, 1), (1, 2), (2, 0)].into_iter().collect();
8087/// assert_eq!(g.vcount(), 3);
8088/// assert_eq!(g.ecount(), 3);
8089/// ```
8090impl std::iter::FromIterator<(VertexId, VertexId)> for Graph {
8091    fn from_iter<I: IntoIterator<Item = (VertexId, VertexId)>>(iter: I) -> Self {
8092        let edges: Vec<(VertexId, VertexId)> = iter.into_iter().collect();
8093        Self::try_from(edges).expect("FromIterator: vertex id overflow or invalid edge")
8094    }
8095}
8096
8097/// Extend a graph by adding edges from an iterator.
8098///
8099/// New vertices are automatically created as needed. This enables
8100/// patterns like `graph.extend(new_edges)`.
8101///
8102/// # Panics
8103///
8104/// Panics if an edge endpoint exceeds the current vertex count and
8105/// cannot be added.
8106///
8107/// # Examples
8108///
8109/// ```
8110/// use rust_igraph::Graph;
8111///
8112/// let mut g = Graph::with_vertices(3);
8113/// g.extend([(0u32, 1), (1, 2)]);
8114/// assert_eq!(g.ecount(), 2);
8115///
8116/// // Extending with a vertex beyond current count grows the graph
8117/// g.extend([(2u32, 5)]);
8118/// assert_eq!(g.vcount(), 6);
8119/// assert_eq!(g.ecount(), 3);
8120/// ```
8121impl Extend<(VertexId, VertexId)> for Graph {
8122    fn extend<I: IntoIterator<Item = (VertexId, VertexId)>>(&mut self, iter: I) {
8123        let edges: Vec<(VertexId, VertexId)> = iter.into_iter().collect();
8124        if edges.is_empty() {
8125            return;
8126        }
8127        let max_id = edges
8128            .iter()
8129            .flat_map(|&(u, v)| [u, v])
8130            .max()
8131            .expect("non-empty edges");
8132        if max_id >= self.n {
8133            self.add_vertices(max_id - self.n + 1)
8134                .expect("Extend: failed to add vertices");
8135        }
8136        self.add_edges(edges).expect("Extend: failed to add edges");
8137    }
8138}
8139
8140/// Structural equality: two graphs are equal if they have the same
8141/// directedness, same vertex count, and the same sorted edge set.
8142///
8143/// This is *not* isomorphism — vertex ids must match exactly.
8144///
8145/// # Examples
8146///
8147/// ```
8148/// use rust_igraph::Graph;
8149///
8150/// let a = Graph::from_edges(&[(0,1), (1,2)], false, None).unwrap();
8151/// let b = Graph::from_edges(&[(1,2), (0,1)], false, None).unwrap();
8152/// assert_eq!(a, b); // same edges, different insertion order
8153///
8154/// let c = Graph::from_edges(&[(0,1), (1,2)], true, None).unwrap();
8155/// assert_ne!(a, c); // different directedness
8156/// ```
8157impl PartialEq for Graph {
8158    fn eq(&self, other: &Self) -> bool {
8159        if self.directed != other.directed || self.n != other.n || self.ecount() != other.ecount() {
8160            return false;
8161        }
8162        let mut self_edges: Vec<(VertexId, VertexId)> = self.iter().collect();
8163        let mut other_edges: Vec<(VertexId, VertexId)> = other.iter().collect();
8164        self_edges.sort_unstable();
8165        other_edges.sort_unstable();
8166        self_edges == other_edges
8167    }
8168}
8169
8170impl Eq for Graph {}
8171
8172/// Hash a graph by its structural content (directedness + vertex count +
8173/// sorted edge set).
8174///
8175/// This is consistent with the [`PartialEq`] impl: structurally equal
8176/// graphs produce the same hash.
8177impl std::hash::Hash for Graph {
8178    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
8179        self.directed.hash(state);
8180        self.n.hash(state);
8181        let mut edges: Vec<(VertexId, VertexId)> = self.iter().collect();
8182        edges.sort_unstable();
8183        edges.hash(state);
8184    }
8185}
8186
8187#[cfg(test)]
8188mod tests {
8189    use super::*;
8190
8191    #[test]
8192    fn empty_graph_counts() {
8193        let g = Graph::with_vertices(0);
8194        assert_eq!(g.vcount(), 0);
8195        assert_eq!(g.ecount(), 0);
8196        assert!(!g.is_directed());
8197    }
8198
8199    #[test]
8200    fn new_directed_flag() {
8201        let g = Graph::new(3, true).unwrap();
8202        assert!(g.is_directed());
8203        let g = Graph::new(3, false).unwrap();
8204        assert!(!g.is_directed());
8205    }
8206
8207    #[test]
8208    fn add_vertices_then_edges() {
8209        let mut g = Graph::with_vertices(3);
8210        g.add_edge(0, 1).unwrap();
8211        g.add_edge(1, 2).unwrap();
8212        assert_eq!(g.vcount(), 3);
8213        assert_eq!(g.ecount(), 2);
8214        assert_eq!(g.degree(1).unwrap(), 2);
8215        let mut nbrs = g.neighbors(1).unwrap();
8216        nbrs.sort_unstable();
8217        assert_eq!(nbrs, vec![0, 2]);
8218    }
8219
8220    #[test]
8221    fn out_of_range_vertex_errors() {
8222        let mut g = Graph::with_vertices(2);
8223        let err = g.add_edge(0, 5).unwrap_err();
8224        assert!(matches!(err, IgraphError::VertexOutOfRange { id: 5, n: 2 }));
8225    }
8226
8227    #[test]
8228    fn self_loop_counted_correctly() {
8229        let mut g = Graph::with_vertices(1);
8230        g.add_edge(0, 0).unwrap();
8231        assert_eq!(g.ecount(), 1);
8232        // Undirected self-loop: appears as both out and in, degree == 2.
8233        assert_eq!(g.degree(0).unwrap(), 2);
8234        let mut nbrs = g.neighbors(0).unwrap();
8235        nbrs.sort_unstable();
8236        assert_eq!(nbrs, vec![0, 0]);
8237    }
8238
8239    #[test]
8240    fn parallel_edges() {
8241        let mut g = Graph::with_vertices(2);
8242        g.add_edge(0, 1).unwrap();
8243        g.add_edge(0, 1).unwrap();
8244        assert_eq!(g.ecount(), 2);
8245        assert_eq!(g.degree(0).unwrap(), 2);
8246        assert_eq!(g.degree(1).unwrap(), 2);
8247    }
8248
8249    #[test]
8250    fn undirected_canonicalisation() {
8251        // Adding edges (1,0) and (0,1) — both stored canonically as (0,1).
8252        let mut g = Graph::with_vertices(2);
8253        g.add_edge(1, 0).unwrap();
8254        g.add_edge(0, 1).unwrap();
8255        assert_eq!(g.ecount(), 2);
8256        // Both vertices see each other as a neighbour twice.
8257        let mut n0 = g.neighbors(0).unwrap();
8258        let mut n1 = g.neighbors(1).unwrap();
8259        n0.sort_unstable();
8260        n1.sort_unstable();
8261        assert_eq!(n0, vec![1, 1]);
8262        assert_eq!(n1, vec![0, 0]);
8263    }
8264
8265    #[test]
8266    fn directed_neighbors_are_outgoing_only() {
8267        let mut g = Graph::new(3, true).unwrap();
8268        g.add_edge(0, 1).unwrap();
8269        g.add_edge(2, 0).unwrap();
8270        // Directed: neighbors(0) returns out-neighbours only.
8271        assert_eq!(g.neighbors(0).unwrap(), vec![1]);
8272        // Vertex 2 has out-edge to 0.
8273        assert_eq!(g.neighbors(2).unwrap(), vec![0]);
8274        // Vertex 1 has no out-edges.
8275        assert!(g.neighbors(1).unwrap().is_empty());
8276        // Degree counts both in and out for directed.
8277        assert_eq!(g.degree(0).unwrap(), 2); // out: 0->1, in: 2->0
8278        assert_eq!(g.degree(1).unwrap(), 1); // in: 0->1
8279        assert_eq!(g.degree(2).unwrap(), 1); // out: 2->0
8280    }
8281
8282    #[test]
8283    fn add_edges_batch_then_rebuild() {
8284        let mut g = Graph::with_vertices(4);
8285        g.add_edges(vec![(0, 1), (0, 2), (1, 2), (2, 3)]).unwrap();
8286        assert_eq!(g.ecount(), 4);
8287        // Degrees: 0->{1,2} d=2; 1->{0,2} d=2; 2->{0,1,3} d=3; 3->{2} d=1.
8288        assert_eq!(g.degree(0).unwrap(), 2);
8289        assert_eq!(g.degree(1).unwrap(), 2);
8290        assert_eq!(g.degree(2).unwrap(), 3);
8291        assert_eq!(g.degree(3).unwrap(), 1);
8292    }
8293
8294    #[test]
8295    fn clone_is_deep() {
8296        let mut g = Graph::with_vertices(3);
8297        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8298        let g2 = g.clone();
8299        // Mutate g; g2 must be unaffected.
8300        g.add_edge(0, 2).unwrap();
8301        assert_eq!(g.ecount(), 3);
8302        assert_eq!(g2.ecount(), 2);
8303    }
8304
8305    #[test]
8306    fn os_invariant_is_monotone() {
8307        let mut g = Graph::with_vertices(5);
8308        g.add_edges(vec![(0, 1), (0, 2), (3, 4), (1, 2)]).unwrap();
8309        // os should be non-decreasing and end at ecount.
8310        for w in g.os.windows(2) {
8311            assert!(w[0] <= w[1]);
8312        }
8313        assert_eq!(g.os[0], 0);
8314        assert_eq!(*g.os.last().unwrap() as usize, g.ecount());
8315    }
8316
8317    #[test]
8318    fn vertex_out_of_range_when_adding_edge() {
8319        let mut g = Graph::with_vertices(2);
8320        let e = g.add_edge(2, 0).unwrap_err();
8321        assert!(matches!(e, IgraphError::VertexOutOfRange { id: 2, n: 2 }));
8322        // Graph state must be unchanged after the failed add.
8323        assert_eq!(g.ecount(), 0);
8324    }
8325
8326    // -------- ALGO-CORE-001b: edge-id helpers + incident --------
8327
8328    #[test]
8329    fn edge_endpoints_round_trip() {
8330        let mut g = Graph::new(3, true).unwrap();
8331        g.add_edges(vec![(0, 1), (2, 0), (1, 2)]).unwrap();
8332        // Directed: order preserved. edge_id == position in from/to.
8333        assert_eq!(g.edge(0).unwrap(), (0, 1));
8334        assert_eq!(g.edge(1).unwrap(), (2, 0));
8335        assert_eq!(g.edge(2).unwrap(), (1, 2));
8336        assert_eq!(g.edge_source(1).unwrap(), 2);
8337        assert_eq!(g.edge_target(1).unwrap(), 0);
8338    }
8339
8340    #[test]
8341    fn edge_other_endpoint() {
8342        let mut g = Graph::with_vertices(3);
8343        g.add_edge(0, 2).unwrap();
8344        assert_eq!(g.edge_other(0, 0).unwrap(), 2);
8345        assert_eq!(g.edge_other(0, 2).unwrap(), 0);
8346        // Vertex not on the edge: error.
8347        let err = g.edge_other(0, 1).unwrap_err();
8348        assert!(matches!(err, IgraphError::InvalidArgument(_)));
8349    }
8350
8351    #[test]
8352    fn edge_out_of_range() {
8353        let mut g = Graph::with_vertices(2);
8354        g.add_edge(0, 1).unwrap();
8355        let err = g.edge(5).unwrap_err();
8356        assert!(matches!(err, IgraphError::EdgeOutOfRange { id: 5, m: 1 }));
8357    }
8358
8359    #[test]
8360    fn incident_returns_edge_ids_matching_neighbors_order() {
8361        let mut g = Graph::with_vertices(4);
8362        g.add_edges(vec![(0, 1), (0, 2), (3, 0)]).unwrap();
8363        let eids = g.incident(0).unwrap();
8364        // Expect three incident edges; resolving back to neighbours
8365        // must equal `neighbors(0)` exactly (same iteration order).
8366        let resolved: Vec<u32> = eids.iter().map(|&e| g.edge_other(e, 0).unwrap()).collect();
8367        assert_eq!(resolved, g.neighbors(0).unwrap());
8368    }
8369
8370    #[test]
8371    fn incident_self_loop_appears_twice_undirected() {
8372        let mut g = Graph::with_vertices(1);
8373        g.add_edge(0, 0).unwrap();
8374        let eids = g.incident(0).unwrap();
8375        // Undirected self-loop appears once on the out side and once on
8376        // the in side — same edge id, twice. Mirrors `neighbors`.
8377        assert_eq!(eids, vec![0, 0]);
8378        assert_eq!(g.degree(0).unwrap(), 2);
8379    }
8380
8381    #[test]
8382    fn incident_directed_returns_outgoing_only() {
8383        let mut g = Graph::new(3, true).unwrap();
8384        g.add_edges(vec![(0, 1), (2, 0)]).unwrap();
8385        // Directed `incident` mirrors directed `neighbors` (out only).
8386        assert_eq!(g.incident(0).unwrap(), vec![0]);
8387        assert_eq!(g.incident(2).unwrap(), vec![1]);
8388        assert!(g.incident(1).unwrap().is_empty());
8389    }
8390
8391    #[test]
8392    fn get_eid_undirected_finds_edge_either_way() {
8393        let mut g = Graph::with_vertices(3);
8394        g.add_edge(0, 1).unwrap(); // edge 0
8395        g.add_edge(1, 2).unwrap(); // edge 1
8396        assert_eq!(g.get_eid(0, 1).unwrap(), 0);
8397        assert_eq!(g.get_eid(1, 0).unwrap(), 0);
8398        assert_eq!(g.get_eid(1, 2).unwrap(), 1);
8399        assert_eq!(g.get_eid(2, 1).unwrap(), 1);
8400    }
8401
8402    #[test]
8403    fn get_eid_directed_respects_direction() {
8404        let mut g = Graph::new(3, true).unwrap();
8405        g.add_edge(0, 1).unwrap(); // edge 0
8406        assert_eq!(g.get_eid(0, 1).unwrap(), 0);
8407        assert!(g.get_eid(1, 0).is_err()); // reverse direction has no edge
8408    }
8409
8410    #[test]
8411    fn find_eid_returns_none_for_missing() {
8412        let mut g = Graph::with_vertices(3);
8413        g.add_edge(0, 1).unwrap();
8414        assert_eq!(g.find_eid(0, 2).unwrap(), None);
8415        assert!(g.find_eid(0, 99).is_err()); // out-of-range vertex
8416    }
8417
8418    #[test]
8419    fn get_eid_self_loop() {
8420        let mut g = Graph::with_vertices(2);
8421        g.add_edge(0, 0).unwrap(); // self-loop, edge 0
8422        g.add_edge(0, 1).unwrap(); // edge 1
8423        assert_eq!(g.get_eid(0, 0).unwrap(), 0);
8424        assert_eq!(g.get_eid(0, 1).unwrap(), 1);
8425    }
8426
8427    #[test]
8428    fn get_all_eids_between_returns_all_parallel() {
8429        let mut g = Graph::with_vertices(2);
8430        g.add_edge(0, 1).unwrap(); // edge 0
8431        g.add_edge(0, 1).unwrap(); // edge 1
8432        g.add_edge(0, 1).unwrap(); // edge 2
8433        let eids = g.get_all_eids_between(0, 1).unwrap();
8434        assert_eq!(eids, vec![0, 1, 2]);
8435        // Reverse direction yields the same edges on undirected.
8436        let eids = g.get_all_eids_between(1, 0).unwrap();
8437        assert_eq!(eids, vec![0, 1, 2]);
8438    }
8439
8440    #[test]
8441    fn get_all_eids_between_directed_one_way_only() {
8442        let mut g = Graph::new(2, true).unwrap();
8443        g.add_edge(0, 1).unwrap(); // edge 0
8444        g.add_edge(0, 1).unwrap(); // edge 1 (parallel)
8445        assert_eq!(g.get_all_eids_between(0, 1).unwrap(), vec![0, 1]);
8446        // Reverse direction has no edges in directed graph.
8447        assert_eq!(g.get_all_eids_between(1, 0).unwrap(), Vec::<EdgeId>::new());
8448    }
8449
8450    #[test]
8451    fn get_eid_returns_lowest_id_for_parallel() {
8452        // Spec: with multiple edges, get_eid always returns the same
8453        // edge id (matches upstream's "ignored multi-edges" guarantee).
8454        // Our impl returns the lowest from the bucket.
8455        let mut g = Graph::with_vertices(2);
8456        g.add_edge(0, 1).unwrap(); // edge 0
8457        g.add_edge(0, 1).unwrap(); // edge 1
8458        assert_eq!(g.get_eid(0, 1).unwrap(), 0);
8459    }
8460
8461    // -------- ALGO-CORE-001c: delete_edges + delete_vertices --------
8462
8463    #[test]
8464    fn delete_edges_empty_input_is_noop() {
8465        let mut g = Graph::with_vertices(3);
8466        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8467        g.delete_edges(&[]).unwrap();
8468        assert_eq!(g.ecount(), 2);
8469        assert_eq!(g.degree(1).unwrap(), 2);
8470    }
8471
8472    #[test]
8473    fn delete_edges_single_edge_undirected() {
8474        let mut g = Graph::with_vertices(3);
8475        g.add_edges(vec![(0, 1), (1, 2), (0, 2)]).unwrap();
8476        // Remove edge id 1 (the (1,2) edge).
8477        g.delete_edges(&[1]).unwrap();
8478        assert_eq!(g.ecount(), 2);
8479        // Surviving edges renumbered to 0,1: (0,1) and (0,2).
8480        assert_eq!(g.find_eid(0, 1).unwrap(), Some(0));
8481        assert_eq!(g.find_eid(0, 2).unwrap(), Some(1));
8482        assert_eq!(g.find_eid(1, 2).unwrap(), None);
8483        // Degrees consistent post-rebuild.
8484        assert_eq!(g.degree(1).unwrap(), 1);
8485        assert_eq!(g.degree(2).unwrap(), 1);
8486    }
8487
8488    #[test]
8489    fn delete_edges_duplicate_ids_tolerated() {
8490        let mut g = Graph::with_vertices(3);
8491        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8492        g.delete_edges(&[0, 0, 0]).unwrap();
8493        assert_eq!(g.ecount(), 1);
8494        assert_eq!(g.find_eid(1, 2).unwrap(), Some(0));
8495    }
8496
8497    #[test]
8498    fn delete_edges_all_edges_leaves_isolated_vertices() {
8499        let mut g = Graph::with_vertices(3);
8500        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8501        g.delete_edges(&[0, 1]).unwrap();
8502        assert_eq!(g.ecount(), 0);
8503        assert_eq!(g.vcount(), 3);
8504        for v in 0..3 {
8505            assert_eq!(g.degree(v).unwrap(), 0);
8506        }
8507    }
8508
8509    #[test]
8510    fn delete_edges_out_of_range_errors_and_preserves_state() {
8511        let mut g = Graph::with_vertices(3);
8512        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8513        let err = g.delete_edges(&[5]).unwrap_err();
8514        assert!(matches!(err, IgraphError::EdgeOutOfRange { id: 5, m: 2 }));
8515        // Graph unchanged.
8516        assert_eq!(g.ecount(), 2);
8517    }
8518
8519    #[test]
8520    fn delete_edges_self_loop_directed() {
8521        let mut g = Graph::new(2, true).unwrap();
8522        g.add_edges(vec![(0, 0), (0, 1)]).unwrap();
8523        g.delete_edges(&[0]).unwrap(); // remove the self-loop
8524        assert_eq!(g.ecount(), 1);
8525        assert_eq!(g.degree(0).unwrap(), 1);
8526        assert_eq!(g.find_eid(0, 1).unwrap(), Some(0));
8527    }
8528
8529    #[test]
8530    fn delete_vertices_empty_input_is_noop() {
8531        let mut g = Graph::with_vertices(3);
8532        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8533        g.delete_vertices(&[]).unwrap();
8534        assert_eq!(g.vcount(), 3);
8535        assert_eq!(g.ecount(), 2);
8536    }
8537
8538    #[test]
8539    fn delete_vertices_single_renumbers() {
8540        let mut g = Graph::with_vertices(4);
8541        g.add_edges(vec![(0, 1), (1, 2), (2, 3), (0, 3)]).unwrap();
8542        // Remove vertex 1: edges (0,1) and (1,2) go with it. (2,3),(0,3)
8543        // survive but get renumbered: 2 → 1, 3 → 2.
8544        g.delete_vertices(&[1]).unwrap();
8545        assert_eq!(g.vcount(), 3);
8546        assert_eq!(g.ecount(), 2);
8547        // (2,3) → (1,2); (0,3) → (0,2).
8548        assert!(g.find_eid(1, 2).unwrap().is_some());
8549        assert!(g.find_eid(0, 2).unwrap().is_some());
8550        assert_eq!(g.find_eid(0, 1).unwrap(), None);
8551    }
8552
8553    #[test]
8554    fn delete_vertices_duplicate_ids_tolerated() {
8555        let mut g = Graph::with_vertices(3);
8556        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8557        g.delete_vertices(&[1, 1, 1]).unwrap();
8558        assert_eq!(g.vcount(), 2);
8559        assert_eq!(g.ecount(), 0);
8560    }
8561
8562    #[test]
8563    fn delete_vertices_all_yields_empty_graph() {
8564        let mut g = Graph::with_vertices(3);
8565        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8566        g.delete_vertices(&[0, 1, 2]).unwrap();
8567        assert_eq!(g.vcount(), 0);
8568        assert_eq!(g.ecount(), 0);
8569    }
8570
8571    #[test]
8572    fn delete_vertices_out_of_range_errors_and_preserves_state() {
8573        let mut g = Graph::with_vertices(3);
8574        g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
8575        let err = g.delete_vertices(&[5]).unwrap_err();
8576        assert!(matches!(err, IgraphError::VertexOutOfRange { id: 5, n: 3 }));
8577        assert_eq!(g.vcount(), 3);
8578        assert_eq!(g.ecount(), 2);
8579    }
8580
8581    #[test]
8582    fn delete_vertices_map_returns_correct_mappings() {
8583        let mut g = Graph::with_vertices(5);
8584        g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
8585        let (map, invmap) = g.delete_vertices_map(&[1, 3]).unwrap();
8586        // Removed: 1 and 3. Retained: 0 → 0, 2 → 1, 4 → 2.
8587        assert_eq!(map, vec![Some(0), None, Some(1), None, Some(2)]);
8588        assert_eq!(invmap, vec![0, 2, 4]);
8589        assert_eq!(g.vcount(), 3);
8590        // Only edges between retained vertices survive — none do here.
8591        assert_eq!(g.ecount(), 0);
8592    }
8593
8594    #[test]
8595    fn delete_vertices_directed_preserves_direction() {
8596        let mut g = Graph::new(4, true).unwrap();
8597        g.add_edges(vec![(0, 1), (1, 2), (2, 0), (3, 0)]).unwrap();
8598        g.delete_vertices(&[3]).unwrap();
8599        assert_eq!(g.vcount(), 3);
8600        assert!(g.is_directed());
8601        // Surviving directed edges (3 → 0) gone; (0,1),(1,2),(2,0) keep direction.
8602        assert!(g.get_eid(0, 1).is_ok());
8603        assert!(g.get_eid(1, 0).is_err()); // wrong direction
8604    }
8605
8606    #[test]
8607    fn delete_vertices_self_loop_on_removed_vertex() {
8608        let mut g = Graph::with_vertices(3);
8609        g.add_edges(vec![(0, 0), (0, 1), (1, 2)]).unwrap();
8610        g.delete_vertices(&[0]).unwrap();
8611        // Self-loop and edges to vertex 0 gone; only (1,2) → (0,1) survives.
8612        assert_eq!(g.vcount(), 2);
8613        assert_eq!(g.ecount(), 1);
8614        assert!(g.find_eid(0, 1).unwrap().is_some());
8615    }
8616
8617    #[test]
8618    fn delete_vertices_preserves_parallel_edges() {
8619        let mut g = Graph::with_vertices(3);
8620        g.add_edges(vec![(0, 1), (0, 1), (1, 2)]).unwrap();
8621        g.delete_vertices(&[2]).unwrap();
8622        assert_eq!(g.vcount(), 2);
8623        assert_eq!(g.ecount(), 2); // both parallel (0,1) edges retained
8624        assert_eq!(g.degree(0).unwrap(), 2);
8625        assert_eq!(g.degree(1).unwrap(), 2);
8626    }
8627
8628    #[test]
8629    fn add_edges_after_delete_works() {
8630        let mut g = Graph::with_vertices(4);
8631        g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
8632        g.delete_vertices(&[0]).unwrap(); // now n=3, vertices 0,1,2
8633        // Add a new edge and check indexes still work.
8634        g.add_edge(0, 2).unwrap();
8635        assert_eq!(g.ecount(), 3);
8636        assert_eq!(g.degree(0).unwrap(), 2); // (0,1)+(0,2)
8637        assert!(g.find_eid(0, 2).unwrap().is_some());
8638    }
8639
8640    #[test]
8641    fn from_adjacency_matrix_undirected_triangle() {
8642        let adj = vec![
8643            vec![0.0, 1.0, 1.0],
8644            vec![1.0, 0.0, 1.0],
8645            vec![1.0, 1.0, 0.0],
8646        ];
8647        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8648        assert_eq!(g.vcount(), 3);
8649        assert_eq!(g.ecount(), 3);
8650        assert!(!g.is_directed());
8651    }
8652
8653    #[test]
8654    fn from_adjacency_matrix_directed() {
8655        let adj = vec![
8656            vec![0.0, 1.0, 0.0],
8657            vec![0.0, 0.0, 1.0],
8658            vec![1.0, 0.0, 0.0],
8659        ];
8660        let g = Graph::from_adjacency_matrix(&adj, true).unwrap();
8661        assert_eq!(g.vcount(), 3);
8662        assert_eq!(g.ecount(), 3);
8663        assert!(g.is_directed());
8664    }
8665
8666    #[test]
8667    fn from_adjacency_matrix_with_self_loop() {
8668        let adj = vec![vec![1.0, 1.0], vec![1.0, 0.0]];
8669        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8670        assert_eq!(g.vcount(), 2);
8671        assert_eq!(g.ecount(), 2); // self-loop on 0 + edge 0-1
8672    }
8673
8674    #[test]
8675    fn from_adjacency_matrix_empty() {
8676        let adj: Vec<Vec<f64>> = Vec::new();
8677        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8678        assert_eq!(g.vcount(), 0);
8679        assert_eq!(g.ecount(), 0);
8680    }
8681
8682    #[test]
8683    fn from_adjacency_matrix_non_square_error() {
8684        let adj = vec![vec![0.0, 1.0], vec![1.0, 0.0, 1.0]];
8685        assert!(Graph::from_adjacency_matrix(&adj, false).is_err());
8686    }
8687
8688    #[test]
8689    fn from_adjacency_matrix_weighted_basic() {
8690        let adj = vec![
8691            vec![0.0, 2.5, 0.0],
8692            vec![2.5, 0.0, 1.0],
8693            vec![0.0, 1.0, 0.0],
8694        ];
8695        let (g, weights) = Graph::from_adjacency_matrix_weighted(&adj, false).unwrap();
8696        assert_eq!(g.vcount(), 3);
8697        assert_eq!(g.ecount(), 2);
8698        assert_eq!(weights.len(), 2);
8699        assert!((weights[0] - 2.5).abs() < 1e-10);
8700        assert!((weights[1] - 1.0).abs() < 1e-10);
8701    }
8702
8703    #[test]
8704    fn from_adjacency_matrix_weighted_directed() {
8705        let adj = vec![
8706            vec![0.0, 3.0, 0.0],
8707            vec![0.0, 0.0, 2.0],
8708            vec![1.5, 0.0, 0.0],
8709        ];
8710        let (g, weights) = Graph::from_adjacency_matrix_weighted(&adj, true).unwrap();
8711        assert_eq!(g.vcount(), 3);
8712        assert_eq!(g.ecount(), 3);
8713        assert_eq!(weights.len(), 3);
8714        assert!((weights[0] - 3.0).abs() < 1e-10);
8715        assert!((weights[1] - 2.0).abs() < 1e-10);
8716        assert!((weights[2] - 1.5).abs() < 1e-10);
8717    }
8718
8719    #[test]
8720    fn from_adjacency_matrix_multi_edges() {
8721        let adj = vec![vec![0.0, 3.0], vec![3.0, 0.0]];
8722        let g = Graph::from_adjacency_matrix(&adj, false).unwrap();
8723        assert_eq!(g.vcount(), 2);
8724        assert_eq!(g.ecount(), 3); // 3 parallel edges
8725    }
8726
8727    #[test]
8728    fn from_adjacency_list_undirected_triangle() {
8729        let adj = vec![vec![1, 2], vec![0, 2], vec![0, 1]];
8730        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8731        assert_eq!(g.vcount(), 3);
8732        assert_eq!(g.ecount(), 3);
8733        assert!(!g.is_directed());
8734    }
8735
8736    #[test]
8737    fn from_adjacency_list_directed() {
8738        let adj = vec![vec![1, 2], vec![2], vec![]];
8739        let g = Graph::from_adjacency_list(&adj, true).unwrap();
8740        assert_eq!(g.vcount(), 3);
8741        assert_eq!(g.ecount(), 3);
8742        assert!(g.is_directed());
8743    }
8744
8745    #[test]
8746    fn from_adjacency_list_empty() {
8747        let adj: Vec<Vec<u32>> = Vec::new();
8748        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8749        assert_eq!(g.vcount(), 0);
8750        assert_eq!(g.ecount(), 0);
8751    }
8752
8753    #[test]
8754    fn from_adjacency_list_isolated_vertices() {
8755        let adj = vec![vec![], vec![], vec![]];
8756        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8757        assert_eq!(g.vcount(), 3);
8758        assert_eq!(g.ecount(), 0);
8759    }
8760
8761    #[test]
8762    fn from_adjacency_list_self_loop() {
8763        let adj = vec![vec![0, 1], vec![0]];
8764        let g = Graph::from_adjacency_list(&adj, false).unwrap();
8765        assert_eq!(g.vcount(), 2);
8766        assert_eq!(g.ecount(), 2); // self-loop on 0 + edge 0-1
8767    }
8768
8769    #[test]
8770    fn from_adjacency_list_out_of_range_error() {
8771        let adj = vec![vec![5]]; // only 1 vertex but references vertex 5
8772        assert!(Graph::from_adjacency_list(&adj, false).is_err());
8773    }
8774
8775    #[test]
8776    fn neighbors_iter_matches_neighbors_undirected() {
8777        let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (2, 3), (3, 4)], false, None).unwrap();
8778        for v in 0..g.vcount() {
8779            let from_vec = g.neighbors(v).unwrap();
8780            let from_iter: Vec<VertexId> = g.neighbors_iter(v).unwrap().collect();
8781            assert_eq!(from_vec, from_iter, "mismatch at vertex {v}");
8782        }
8783    }
8784
8785    #[test]
8786    fn neighbors_iter_matches_neighbors_directed() {
8787        let g = Graph::from_edges(&[(0, 1), (0, 2), (1, 3), (2, 3), (3, 4)], true, None).unwrap();
8788        for v in 0..g.vcount() {
8789            let from_vec = g.neighbors(v).unwrap();
8790            let from_iter: Vec<VertexId> = g.neighbors_iter(v).unwrap().collect();
8791            assert_eq!(from_vec, from_iter, "mismatch at vertex {v}");
8792        }
8793    }
8794
8795    #[test]
8796    fn neighbors_iter_exact_size() {
8797        let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3)], false, None).unwrap();
8798        let iter = g.neighbors_iter(0).unwrap();
8799        assert_eq!(iter.len(), 3);
8800    }
8801
8802    #[test]
8803    fn neighbors_iter_invalid_vertex() {
8804        let g = Graph::with_vertices(3);
8805        assert!(g.neighbors_iter(5).is_err());
8806    }
8807}