Skip to main content

igraph/
isomorphism.rs

1//! Graph isomorphism, motifs and graphlets (`igraph_isomorphism.h`,
2//! `igraph_motifs.h`, `igraph_graphlets.h`).
3//!
4//! Two graphs are *isomorphic* when they become indistinguishable once
5//! their vertex labels are removed, i.e. when a bijection between their
6//! vertex sets maps the edges of one onto the edges of the other. This
7//! module wraps the four families of isomorphism tools of igraph:
8//!
9//! * the **generic** entry points [`Graph::isomorphic`] and
10//!   [`Graph::subisomorphic`], which pick a suitable algorithm by
11//!   themselves;
12//! * **VF2** (Foggia, Sansone and Vento, 2001), which supports vertex and edge
13//!   colors, arbitrary compatibility predicates written as Rust closures,
14//!   counting and listing of all (sub)isomorphisms, and streaming them to a
15//!   closure that can stop the search early; configure it with
16//!   [`Vf2Options`];
17//! * **Bliss** (Junttila and Kaski), a successor of NAUTY, which computes
18//!   canonical labelings and automorphism groups, and reports the size of
19//!   the automorphism group *exactly*, as a decimal string (see
20//!   [`BlissInfo`] and the splitting heuristics [`BlissSh`]);
21//! * **LAD** (Solnon, 2010) for (induced) subgraph isomorphism with
22//!   per-vertex *domains*.
23//!
24//! In addition, all directed graphs on 3–4 vertices and all undirected
25//! graphs on 3–6 vertices are numbered by *isomorphism classes*
26//! ([`Graph::isoclass`], [`Graph::isoclass_create`], [`graph_count`]), which
27//! are used by the **motif** finder ([`Graph::motifs_randesu`], FANMOD's
28//! RAND-ESU algorithm), and the module also provides the dyad and triad
29//! censuses, triangle listing and counting, and the **graphlet
30//! decomposition** of weighted graphs (Azari Soufiani and Airoldi).
31//!
32//! VF2 and Bliss only support *simple* graphs (Bliss tolerates self-loops);
33//! use [`Graph::simplify_and_colorize`] to encode multi-edges and self-loops
34//! as edge and vertex colors first, as [`Graph::isomorphic`] does
35//! automatically.
36//!
37//! # Example
38//!
39//! ```
40//! use igraph::prelude::*;
41//! use igraph::isomorphism::{BlissSh, Vf2Options};
42//!
43//! // A 4-cycle, and the same cycle with scrambled labels.
44//! let c4 = Graph::ring(4, false, false, true).unwrap();
45//! let scrambled = c4.permute_vertices(&[2, 0, 3, 1]).unwrap();
46//! assert!(c4.isomorphic(&scrambled).unwrap());
47//!
48//! // VF2 also returns a witness mapping ...
49//! let m = c4.isomorphic_vf2(&scrambled, &mut Vf2Options::new()).unwrap().unwrap();
50//! for (u, v) in c4.edge_list() {
51//!     let (a, b) = (m.map12[u as usize], m.map12[v as usize]);
52//!     assert!(scrambled.get_eid(a, b, false).unwrap().is_some());
53//! }
54//! // ... and counts them: the dihedral group of the square has 8 elements.
55//! assert_eq!(c4.count_isomorphisms_vf2(&scrambled, &mut Vf2Options::new()).unwrap(), 8);
56//! assert_eq!(c4.count_automorphisms(None).unwrap(), 8.0);
57//!
58//! // A path on 3 vertices occurs 4 times in C4 as an induced subgraph (motif).
59//! let hist = c4.motifs_randesu(3, None).unwrap();
60//! assert!(hist[0].is_nan() && hist[1].is_nan()); // disconnected classes
61//! assert_eq!(&hist[2..], &[4.0, 0.0]);
62//!
63//! // The Petersen graph has 120 symmetries, and Bliss reports them exactly.
64//! let petersen = Graph::famous("Petersen").unwrap();
65//! let info = petersen.count_automorphisms_bliss(None, BlissSh::Fl).unwrap();
66//! assert_eq!(info.group_size, "120");
67//! ```
68//!
69//! # Provided functionality
70//!
71//! | Rust | C function | What |
72//! |------|------------|------|
73//! | [`Graph::isomorphic`] | `igraph_isomorphic` | automatic isomorphism test |
74//! | [`Graph::subisomorphic`] | `igraph_subisomorphic` | automatic subgraph isomorphism test |
75//! | [`Graph::isomorphic_vf2`] | `igraph_isomorphic_vf2` | VF2 test with mapping |
76//! | [`Graph::count_isomorphisms_vf2`] | `igraph_count_isomorphisms_vf2` | count isomorphisms |
77//! | [`Graph::get_isomorphisms_vf2`] | `igraph_get_isomorphisms_vf2` | list isomorphisms |
78//! | [`Graph::get_isomorphisms_vf2_callback`] | `igraph_get_isomorphisms_vf2_callback` | stream isomorphisms to a closure |
79//! | [`Graph::subisomorphic_vf2`] | `igraph_subisomorphic_vf2` | VF2 subgraph test with mapping |
80//! | [`Graph::count_subisomorphisms_vf2`] | `igraph_count_subisomorphisms_vf2` | count subgraph isomorphisms |
81//! | [`Graph::get_subisomorphisms_vf2`] | `igraph_get_subisomorphisms_vf2` | list subgraph isomorphisms |
82//! | [`Graph::get_subisomorphisms_vf2_callback`] | `igraph_get_subisomorphisms_vf2_callback` | stream subgraph isomorphisms |
83//! | [`Graph::subisomorphic_lad`], [`Graph::get_subisomorphisms_lad`] | `igraph_subisomorphic_lad` | LAD, with domains and induced mode |
84//! | [`Graph::isomorphic_bliss`] | `igraph_isomorphic_bliss` | Bliss test with mapping and statistics |
85//! | [`Graph::canonical_permutation`], [`Graph::canonical_permutation_bliss`] | `igraph_canonical_permutation(_bliss)` | canonical labeling |
86//! | [`Graph::canonical_form`] | (canonical labeling + `igraph_permute_vertices`) | canonical representative |
87//! | [`Graph::count_automorphisms`], [`Graph::count_automorphisms_bliss`] | `igraph_count_automorphisms(_bliss)` | automorphism group size |
88//! | [`Graph::automorphism_group`], [`Graph::automorphism_group_bliss`] | `igraph_automorphism_group(_bliss)` | automorphism group generators |
89//! | [`Graph::simplify_and_colorize`] | `igraph_simplify_and_colorize` | multigraph → colored simple graph |
90//! | [`invert_permutation`] | `igraph_invert_permutation` | inverse of a permutation |
91//! | [`Graph::isoclass`], [`Graph::isoclass_subgraph`] | `igraph_isoclass(_subgraph)` | isomorphism class of small graphs |
92//! | [`Graph::isoclass_create`] | `igraph_isoclass_create` | representative of an isomorphism class |
93//! | [`graph_count`] | `igraph_graph_count` | number of unlabeled graphs |
94//! | [`Graph::motifs_randesu`] | `igraph_motifs_randesu` | motif histogram |
95//! | [`Graph::motifs_randesu_callback`] | `igraph_motifs_randesu_callback` | stream motifs to a closure |
96//! | [`Graph::motifs_randesu_no`] | `igraph_motifs_randesu_no` | number of connected subgraphs |
97//! | [`Graph::motifs_randesu_estimate`] | `igraph_motifs_randesu_estimate` | estimate of the above |
98//! | [`Graph::dyad_census`] | `igraph_dyad_census` | mutual / asymmetric / null dyads |
99//! | [`Graph::triad_census`] | `igraph_triad_census` | the 16 MAN triad types |
100//! | [`Graph::count_triangles`], [`Graph::count_adjacent_triangles`], [`Graph::list_triangles`] | `igraph_count_triangles`, ... | triangles |
101//! | [`Graph::graphlets`], [`Graph::graphlets_candidate_basis`], [`Graph::graphlets_project`] | `igraph_graphlets*` | graphlet decomposition |
102//!
103//! # See also
104//!
105//! * [`Graph::is_same_graph`] tests *labeled* equality (same vertex ids and
106//!   edges), while this module tests equality up to relabeling;
107//!   [`Graph::permute_vertices`] relabels a graph.
108//! * [`Graph::famous`], [`Graph::full`],
109//!   [`Graph::ring`] and [`Graph::lcf`] build the classic symmetric graphs
110//!   used as test cases here; [`rng::seed`](crate::rng::seed) makes random
111//!   relabelings and motif sampling reproducible (per thread).
112//! * The [`cliques`](crate::cliques) module (complete subgraphs, used by the
113//!   graphlet decomposition), and the transitivity measures of
114//!   [`mixing`](crate::mixing), e.g. [`Graph::transitivity_undirected`],
115//!   which are ratios of triangle counts.
116//! * [`Graph::reciprocity`] summarizes the dyad census in one number.
117//! * [`Graph::simplify`] and [`Graph::count_multiple`] for dealing with
118//!   multigraphs without encoding them as colors.
119
120use crate::{
121    cliques::directed_cache_guard,
122    error::{Error, ErrorKind, Result, catch_panic, catch_panic_or},
123    ffi::*,
124    graph::{Graph, VertexId},
125    igraph_call,
126    list::VectorIntList,
127    selector::VertexSelector,
128    vector::{Vector, VectorInt, View},
129};
130use std::{
131    ffi::{CStr, c_void},
132    fmt,
133    mem::MaybeUninit,
134    ptr,
135};
136
137// ---------------------------------------------------------------------------
138// Private helpers
139// ---------------------------------------------------------------------------
140
141/// Zero-copy view of an optional integer slice.
142fn opt_view(data: Option<&[i64]>) -> Option<View<'_, VectorInt>> {
143    data.map(VectorInt::view)
144}
145
146/// Raw pointer of an optional view (null when absent).
147fn opt_ptr<V>(view: &Option<View<'_, V>>) -> *const V {
148    view.as_ref().map_or(ptr::null(), |v| v.as_ptr())
149}
150
151/// Runs `f` (the Rust side of a callback invoked by igraph) in a new level of
152/// igraph's "finally" stack.
153///
154/// A user closure may itself call igraph functions. When one of those fails,
155/// the error handler frees the objects of the current finally-stack level,
156/// which without a new level would include the temporaries of the *outer*
157/// igraph function that invoked the callback (a use after free; igraph 1.0.1
158/// itself does not open a level around callbacks). The level is closed when
159/// `f` returns.
160///
161/// [`catch_panic`] / [`catch_panic_or`] do not open a level themselves (as of
162/// this writing), so this is the protection for the VF2 callbacks. Should
163/// they start doing so, this level becomes redundant but stays harmless:
164/// finally levels nest, and each one is closed by its own opener.
165fn in_finally_level<T>(f: impl FnOnce() -> T) -> T {
166    struct Exit;
167    impl Drop for Exit {
168        fn drop(&mut self) {
169            unsafe { IGRAPH_FINALLY_EXIT() };
170        }
171    }
172    unsafe { IGRAPH_FINALLY_ENTER() };
173    let _exit = Exit;
174    f()
175}
176
177fn check_len(what: &str, data: Option<&[i64]>, expected: usize) -> Result<()> {
178    match data {
179        Some(d) if d.len() != expected => Err(Error::invalid(format!(
180            "{what} has length {}, expected {expected}",
181            d.len()
182        ))),
183        _ => Ok(()),
184    }
185}
186
187// ---------------------------------------------------------------------------
188// Result types
189// ---------------------------------------------------------------------------
190
191/// An isomorphism (or subgraph isomorphism) between two graphs, as a pair
192/// of mutually inverse vertex maps.
193///
194/// For a full isomorphism both vectors are permutations. For a subgraph
195/// isomorphism of `graph2` into `graph1`, `map21[v]` is the vertex of
196/// `graph1` matched to vertex `v` of `graph2`, while `map12[u]` is the vertex
197/// of `graph2` matched to `u`, or `-1` if `u` is not part of the match.
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub struct IsoMapping {
200    /// Maps each vertex of the first graph to a vertex of the second one.
201    pub map12: Vec<VertexId>,
202    /// Maps each vertex of the second graph to a vertex of the first one.
203    pub map21: Vec<VertexId>,
204}
205
206/// Statistics of a Bliss run (`igraph_bliss_info_t`).
207///
208/// Mostly useful to study the internal working of the algorithm, except for
209/// [`group_size`](Self::group_size), the *exact* size of the automorphism
210/// group, which may be astronomically large (e.g. `n!` for the complete
211/// graph `K_n`) and is therefore given as a decimal string.
212#[derive(Debug, Clone, PartialEq, Eq, Default)]
213pub struct BlissInfo {
214    /// Number of nodes in the search tree.
215    pub nof_nodes: u64,
216    /// Number of leaf nodes in the search tree.
217    pub nof_leaf_nodes: u64,
218    /// Number of bad nodes.
219    pub nof_bad_nodes: u64,
220    /// Number of canonical representative updates.
221    pub nof_canupdates: u64,
222    /// Number of generators of the automorphism group.
223    pub nof_generators: u64,
224    /// Maximum level of the search tree.
225    pub max_level: u64,
226    /// Size of the automorphism group, in base 10. It is empty when Bliss did
227    /// not run to completion, e.g. in [`Graph::isomorphic_bliss`] when the
228    /// two graphs have different vertex or edge counts.
229    pub group_size: String,
230}
231
232impl BlissInfo {
233    /// The group size parsed as a float (possibly rounded, or infinite for
234    /// huge groups); `None` if [`group_size`](Self::group_size) is empty.
235    pub fn group_size_f64(&self) -> Option<f64> {
236        self.group_size.parse().ok()
237    }
238
239    /// The group size parsed as a `u128`; `None` if it is empty or too large.
240    pub fn group_size_u128(&self) -> Option<u128> {
241        self.group_size.parse().ok()
242    }
243}
244
245/// Owner of a raw `igraph_bliss_info_t`, which frees the `group_size` string.
246struct RawBlissInfo(igraph_bliss_info_t);
247
248impl RawBlissInfo {
249    fn new() -> Self {
250        // All-zero is a valid "empty" info: counters at 0 and a null string.
251        Self(unsafe { MaybeUninit::<igraph_bliss_info_t>::zeroed().assume_init() })
252    }
253
254    // `c_ulong` is `u64` on some platforms only: keep the casts portable.
255    #[allow(clippy::unnecessary_cast)]
256    fn to_info(&self) -> BlissInfo {
257        let r = &self.0;
258        let group_size = if r.group_size.is_null() {
259            String::new()
260        } else {
261            unsafe { CStr::from_ptr(r.group_size) }
262                .to_string_lossy()
263                .into_owned()
264        };
265        BlissInfo {
266            nof_nodes: r.nof_nodes as u64,
267            nof_leaf_nodes: r.nof_leaf_nodes as u64,
268            nof_bad_nodes: r.nof_bad_nodes as u64,
269            nof_canupdates: r.nof_canupdates as u64,
270            nof_generators: r.nof_generators as u64,
271            max_level: r.max_level as u64,
272            group_size,
273        }
274    }
275}
276
277impl Drop for RawBlissInfo {
278    fn drop(&mut self) {
279        if !self.0.group_size.is_null() {
280            unsafe { igraph_free(self.0.group_size.cast()) };
281            self.0.group_size = ptr::null_mut();
282        }
283    }
284}
285
286crate::ffi_enum! {
287    /// Splitting heuristics of Bliss (`igraph_bliss_sh_t`).
288    ///
289    /// They affect performance (and the particular generators returned by
290    /// [`Graph::automorphism_group_bliss`]), never the correctness of the
291    /// result. [`BlissSh::Fl`] is a good general-purpose default (the
292    /// [`Default`] here), while [`BlissSh::Fsm`] is recommended for graphs with
293    /// some combinatorial structure and is the default of the Bliss command
294    /// line tool.
295    #[derive(Default)]
296    pub enum BlissSh: igraph_bliss_sh_t {
297        /// First non-singleton cell.
298        F = igraph_bliss_sh_t_IGRAPH_BLISS_F,
299        /// First largest non-singleton cell (the default).
300        #[default]
301        Fl = igraph_bliss_sh_t_IGRAPH_BLISS_FL,
302        /// First smallest non-singleton cell.
303        Fs = igraph_bliss_sh_t_IGRAPH_BLISS_FS,
304        /// First maximally non-trivially connected non-singleton cell.
305        Fm = igraph_bliss_sh_t_IGRAPH_BLISS_FM,
306        /// Largest maximally non-trivially connected non-singleton cell.
307        Flm = igraph_bliss_sh_t_IGRAPH_BLISS_FLM,
308        /// Smallest maximally non-trivially connected non-singleton cell.
309        Fsm = igraph_bliss_sh_t_IGRAPH_BLISS_FSM,
310    }
311}
312
313impl BlissSh {
314    /// All the heuristics, in the order of the C enumeration.
315    pub const ALL: [BlissSh; 6] = [Self::F, Self::Fl, Self::Fs, Self::Fm, Self::Flm, Self::Fsm];
316}
317
318/// Result of [`Graph::isomorphic_bliss`].
319#[derive(Debug, Clone, PartialEq, Eq)]
320pub struct BlissIsomorphism {
321    /// An isomorphism, or `None` if the graphs are not isomorphic.
322    pub mapping: Option<IsoMapping>,
323    /// Statistics of the canonization of the first graph.
324    pub info1: BlissInfo,
325    /// Statistics of the canonization of the second graph.
326    pub info2: BlissInfo,
327}
328
329impl BlissIsomorphism {
330    /// Whether the two graphs are isomorphic.
331    pub fn is_isomorphic(&self) -> bool {
332        self.mapping.is_some()
333    }
334}
335
336/// Result of [`Graph::simplify_and_colorize`]: a colored simple graph that
337/// encodes a multigraph.
338#[derive(Debug, Clone, PartialEq)]
339pub struct ColorizedGraph {
340    /// The simple graph (no multi-edges, no self-loops).
341    pub graph: Graph,
342    /// For each vertex, the number of self-loops it had in the input.
343    pub vertex_color: Vec<i64>,
344    /// For each edge of [`graph`](Self::graph), the number of parallel
345    /// input edges it replaces.
346    pub edge_color: Vec<i64>,
347}
348
349/// The dyad census of a graph, see [`Graph::dyad_census`].
350///
351/// `mutual + asymmetric + null == n (n - 1) / 2` for `n` vertices.
352#[derive(Debug, Clone, Copy, PartialEq)]
353pub struct DyadCensus {
354    /// Pairs connected in both directions.
355    pub mutual: f64,
356    /// Pairs connected in exactly one direction.
357    pub asymmetric: f64,
358    /// Unconnected pairs.
359    pub null: f64,
360}
361
362/// The triad census of a graph, see [`Graph::triad_census`].
363///
364/// Holds the number of vertex triples of each of the 16 types described by
365/// Davis and Leinhardt's *MAN labels* ([`TriadCensus::NAMES`]): the digits
366/// count **M**utual, **A**symmetric and **N**ull dyads of the triple, and the
367/// letter distinguishes **D**own, **U**p, **C**yclic and **T**ransitive
368/// variants. Index it by position (`census[3]`) or by label
369/// (`census["021D"]`).
370#[derive(Debug, Clone, Copy, PartialEq)]
371pub struct TriadCensus {
372    /// The 16 counts, in the order of [`TriadCensus::NAMES`].
373    pub counts: [f64; 16],
374}
375
376impl TriadCensus {
377    /// The MAN labels of the 16 triad types, in igraph's order:
378    ///
379    /// | idx | label | shape |
380    /// |---|---|---|
381    /// | 0 | `003` | empty |
382    /// | 1 | `012` | A→B, C |
383    /// | 2 | `102` | A↔B, C |
384    /// | 3 | `021D` | A←B→C (out-star) |
385    /// | 4 | `021U` | A→B←C (in-star) |
386    /// | 5 | `021C` | A→B→C (directed line) |
387    /// | 6 | `111D` | A↔B←C |
388    /// | 7 | `111U` | A↔B→C |
389    /// | 8 | `030T` | A→B←C, A→C |
390    /// | 9 | `030C` | A←B←C, A→C (cycle) |
391    /// | 10 | `201` | A↔B↔C |
392    /// | 11 | `120D` | A←B→C, A↔C |
393    /// | 12 | `120U` | A→B←C, A↔C |
394    /// | 13 | `120C` | A→B→C, A↔C |
395    /// | 14 | `210` | A→B↔C, A↔C |
396    /// | 15 | `300` | complete |
397    pub const NAMES: [&'static str; 16] = [
398        "003", "012", "102", "021D", "021U", "021C", "111D", "111U", "030T", "030C", "201", "120D",
399        "120U", "120C", "210", "300",
400    ];
401
402    /// The count of the triad type with the given MAN label, if valid.
403    pub fn get(&self, label: &str) -> Option<f64> {
404        Self::NAMES
405            .iter()
406            .position(|&n| n == label)
407            .map(|i| self.counts[i])
408    }
409
410    /// Iterates over `(label, count)` pairs.
411    pub fn iter(&self) -> impl Iterator<Item = (&'static str, f64)> + '_ {
412        Self::NAMES.iter().copied().zip(self.counts.iter().copied())
413    }
414
415    /// Total number of triples, `n (n-1) (n-2) / 6`.
416    pub fn total(&self) -> f64 {
417        self.counts.iter().sum()
418    }
419}
420
421impl std::ops::Index<usize> for TriadCensus {
422    type Output = f64;
423    fn index(&self, i: usize) -> &f64 {
424        &self.counts[i]
425    }
426}
427
428impl std::ops::Index<&str> for TriadCensus {
429    type Output = f64;
430    /// # Panics
431    /// If `label` is not one of [`TriadCensus::NAMES`].
432    fn index(&self, label: &str) -> &f64 {
433        let i = Self::NAMES
434            .iter()
435            .position(|&n| n == label)
436            .unwrap_or_else(|| panic!("unknown triad label {label:?}"));
437        &self.counts[i]
438    }
439}
440
441/// How [`Graph::motifs_randesu_estimate`] chooses the sample of vertices.
442#[derive(Debug, Clone, PartialEq, Eq)]
443pub enum MotifSample<'a> {
444    /// Draw this many vertices uniformly at random (uses igraph's RNG).
445    Random(usize),
446    /// Use exactly these vertices.
447    Vertices(&'a [VertexId]),
448}
449
450/// A graphlet basis with thresholds, see [`Graph::graphlets_candidate_basis`].
451#[derive(Debug, Clone, PartialEq)]
452pub struct GraphletBasis {
453    /// The candidate cliques, as lists of vertex ids.
454    pub cliques: Vec<Vec<VertexId>>,
455    /// For each clique, the highest weight threshold at which it was found.
456    pub thresholds: Vec<f64>,
457}
458
459/// A graphlet decomposition, see [`Graph::graphlets`].
460#[derive(Debug, Clone, PartialEq)]
461pub struct Graphlets {
462    /// The graphlets (cliques), as lists of vertex ids, by decreasing weight.
463    pub cliques: Vec<Vec<VertexId>>,
464    /// The weight (`Mu`) of each graphlet.
465    pub weights: Vec<f64>,
466}
467
468// ---------------------------------------------------------------------------
469// VF2 configuration and callbacks
470// ---------------------------------------------------------------------------
471
472/// A vertex or edge compatibility predicate for VF2: it receives the id of a
473/// vertex (edge) of the first graph and one of the second graph, and tells
474/// whether they may be matched.
475pub type CompatFn<'a> = Box<dyn FnMut(i64, i64) -> bool + 'a>;
476
477/// Options of the VF2 functions: vertex and edge colors, and custom
478/// compatibility predicates.
479///
480/// * With **vertex colors**, a vertex may only be matched to a vertex of the
481///   same color; with **edge colors**, an edge only to an edge of the same
482///   color. Colors are given for both graphs at once (igraph ignores the
483///   colors, with a warning, when only one graph is colored).
484/// * A **node (edge) compatibility closure** is called every time VF2 tries
485///   to match vertex (edge) `i` of the first graph with vertex (edge) `j` of
486///   the second, and can veto the match by returning `false`. If a closure
487///   panics, the search is aborted and the panic is resumed when the
488///   wrapper returns.
489///
490/// Build it with the builder methods and pass it as `&mut`, so that it
491/// (and its closures) can be reused:
492///
493/// ```
494/// use igraph::prelude::*;
495/// use igraph::isomorphism::Vf2Options;
496///
497/// let path = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
498/// // Colors pin the middle vertex: only the reflection 0 <-> 2 survives.
499/// let colors = [0, 1, 0];
500/// let mut opts = Vf2Options::new().with_vertex_colors(&colors, &colors);
501/// assert_eq!(path.count_isomorphisms_vf2(&path, &mut opts).unwrap(), 2);
502/// // A predicate forbidding fixed points leaves only the reflection.
503/// let mut opts = Vf2Options::new().with_node_compat(|i, j| i != j || i == 1);
504/// assert_eq!(path.get_isomorphisms_vf2(&path, &mut opts).unwrap(), vec![vec![2, 1, 0]]);
505/// ```
506#[derive(Default)]
507pub struct Vf2Options<'a> {
508    /// Vertex colors of the first graph.
509    pub vertex_color1: Option<&'a [i64]>,
510    /// Vertex colors of the second graph.
511    pub vertex_color2: Option<&'a [i64]>,
512    /// Edge colors of the first graph.
513    pub edge_color1: Option<&'a [i64]>,
514    /// Edge colors of the second graph.
515    pub edge_color2: Option<&'a [i64]>,
516    node_compat: Option<CompatFn<'a>>,
517    edge_compat: Option<CompatFn<'a>>,
518}
519
520impl fmt::Debug for Vf2Options<'_> {
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        f.debug_struct("Vf2Options")
523            .field("vertex_color1", &self.vertex_color1)
524            .field("vertex_color2", &self.vertex_color2)
525            .field("edge_color1", &self.edge_color1)
526            .field("edge_color2", &self.edge_color2)
527            .field("node_compat", &self.node_compat.is_some())
528            .field("edge_compat", &self.edge_compat.is_some())
529            .finish()
530    }
531}
532
533impl<'a> Vf2Options<'a> {
534    /// No colors and no compatibility predicates: plain (sub)isomorphism.
535    pub fn new() -> Self {
536        Self::default()
537    }
538
539    /// Sets the vertex colors of the first and second graph.
540    pub fn with_vertex_colors(mut self, color1: &'a [i64], color2: &'a [i64]) -> Self {
541        self.vertex_color1 = Some(color1);
542        self.vertex_color2 = Some(color2);
543        self
544    }
545
546    /// Sets the edge colors of the first and second graph.
547    pub fn with_edge_colors(mut self, color1: &'a [i64], color2: &'a [i64]) -> Self {
548        self.edge_color1 = Some(color1);
549        self.edge_color2 = Some(color2);
550        self
551    }
552
553    /// Sets the vertex compatibility predicate `f(v1, v2)`, where `v1` is a
554    /// vertex of the first graph and `v2` one of the second graph.
555    pub fn with_node_compat(mut self, f: impl FnMut(VertexId, VertexId) -> bool + 'a) -> Self {
556        self.node_compat = Some(Box::new(f));
557        self
558    }
559
560    /// Sets the edge compatibility predicate `f(e1, e2)`, where `e1` is an
561    /// edge of the first graph and `e2` one of the second graph.
562    pub fn with_edge_compat(mut self, f: impl FnMut(i64, i64) -> bool + 'a) -> Self {
563        self.edge_compat = Some(Box::new(f));
564        self
565    }
566
567    /// Sets the vertex colors; use
568    /// [`with_vertex_colors`](Self::with_vertex_colors) instead.
569    #[deprecated(note = "use with_vertex_colors")]
570    pub fn vertex_colors(self, color1: &'a [i64], color2: &'a [i64]) -> Self {
571        self.with_vertex_colors(color1, color2)
572    }
573
574    /// Sets the edge colors; use
575    /// [`with_edge_colors`](Self::with_edge_colors) instead.
576    #[deprecated(note = "use with_edge_colors")]
577    pub fn edge_colors(self, color1: &'a [i64], color2: &'a [i64]) -> Self {
578        self.with_edge_colors(color1, color2)
579    }
580
581    /// Sets the vertex compatibility predicate; use
582    /// [`with_node_compat`](Self::with_node_compat) instead.
583    #[deprecated(note = "use with_node_compat")]
584    pub fn node_compat(self, f: impl FnMut(VertexId, VertexId) -> bool + 'a) -> Self {
585        self.with_node_compat(f)
586    }
587
588    /// Sets the edge compatibility predicate; use
589    /// [`with_edge_compat`](Self::with_edge_compat) instead.
590    #[deprecated(note = "use with_edge_compat")]
591    pub fn edge_compat(self, f: impl FnMut(i64, i64) -> bool + 'a) -> Self {
592        self.with_edge_compat(f)
593    }
594}
595
596/// A closure receiving `(map12, map21)` for each mapping found by VF2.
597type IsoHandler<'h> = dyn FnMut(&[VertexId], &[VertexId]) -> bool + 'h;
598
599/// The state shared by all the VF2 trampolines through the `arg` pointer.
600struct Vf2State<'s, 'a, 'h> {
601    node: Option<&'s mut CompatFn<'a>>,
602    edge: Option<&'s mut CompatFn<'a>>,
603    handler: Option<&'s mut IsoHandler<'h>>,
604    panicked: bool,
605}
606
607unsafe extern "C" fn node_compat_trampoline(
608    _graph1: *const igraph_t,
609    _graph2: *const igraph_t,
610    g1_num: igraph_int_t,
611    g2_num: igraph_int_t,
612    arg: *mut c_void,
613) -> igraph_bool_t {
614    let state = unsafe { &mut *(arg as *mut Vf2State<'_, '_, '_>) };
615    if state.panicked {
616        return false;
617    }
618    let Some(f) = state.node.as_mut() else {
619        return true;
620    };
621    match in_finally_level(|| catch_panic_or(None, || Some(f(g1_num, g2_num)))) {
622        Some(ok) => ok,
623        None => {
624            state.panicked = true;
625            false
626        }
627    }
628}
629
630unsafe extern "C" fn edge_compat_trampoline(
631    _graph1: *const igraph_t,
632    _graph2: *const igraph_t,
633    g1_num: igraph_int_t,
634    g2_num: igraph_int_t,
635    arg: *mut c_void,
636) -> igraph_bool_t {
637    let state = unsafe { &mut *(arg as *mut Vf2State<'_, '_, '_>) };
638    if state.panicked {
639        return false;
640    }
641    let Some(f) = state.edge.as_mut() else {
642        return true;
643    };
644    match in_finally_level(|| catch_panic_or(None, || Some(f(g1_num, g2_num)))) {
645        Some(ok) => ok,
646        None => {
647            state.panicked = true;
648            false
649        }
650    }
651}
652
653unsafe extern "C" fn iso_handler_trampoline(
654    map12: *const igraph_vector_int_t,
655    map21: *const igraph_vector_int_t,
656    arg: *mut c_void,
657) -> igraph_error_t {
658    let state = unsafe { &mut *(arg as *mut Vf2State<'_, '_, '_>) };
659    if state.panicked {
660        return igraph_error_type_t_IGRAPH_INTERRUPTED;
661    }
662    let Some(h) = state.handler.as_mut() else {
663        return igraph_error_type_t_IGRAPH_SUCCESS;
664    };
665    let (m12, m21) = unsafe { ((*map12).as_slice(), (*map21).as_slice()) };
666    in_finally_level(|| {
667        catch_panic(|| {
668            if h(m12, m21) {
669                igraph_error_type_t_IGRAPH_SUCCESS
670            } else {
671                igraph_error_type_t_IGRAPH_STOP
672            }
673        })
674    })
675}
676
677/// Raw arguments of a VF2 call, valid during [`with_vf2`]'s closure.
678struct Vf2Raw {
679    vc1: *const igraph_vector_int_t,
680    vc2: *const igraph_vector_int_t,
681    ec1: *const igraph_vector_int_t,
682    ec2: *const igraph_vector_int_t,
683    node: igraph_isocompat_t,
684    edge: igraph_isocompat_t,
685    handler: igraph_isohandler_t,
686    arg: *mut c_void,
687}
688
689/// Prepares the raw VF2 arguments (color views, trampolines and state) and
690/// runs `f` with them, converting the result code.
691fn with_vf2(
692    graph1: &Graph,
693    graph2: &Graph,
694    opts: &mut Vf2Options<'_>,
695    handler: Option<&mut IsoHandler<'_>>,
696    f: impl FnOnce(&Vf2Raw) -> igraph_error_t,
697) -> Result<()> {
698    check_len("vertex_color1", opts.vertex_color1, graph1.vcount())?;
699    check_len("vertex_color2", opts.vertex_color2, graph2.vcount())?;
700    check_len("edge_color1", opts.edge_color1, graph1.ecount())?;
701    check_len("edge_color2", opts.edge_color2, graph2.ecount())?;
702    let vc1 = opt_view(opts.vertex_color1);
703    let vc2 = opt_view(opts.vertex_color2);
704    let ec1 = opt_view(opts.edge_color1);
705    let ec2 = opt_view(opts.edge_color2);
706    let has_node = opts.node_compat.is_some();
707    let has_edge = opts.edge_compat.is_some();
708    let has_handler = handler.is_some();
709    let mut state = Vf2State {
710        node: opts.node_compat.as_mut(),
711        edge: opts.edge_compat.as_mut(),
712        handler,
713        panicked: false,
714    };
715    let raw = Vf2Raw {
716        vc1: opt_ptr(&vc1),
717        vc2: opt_ptr(&vc2),
718        ec1: opt_ptr(&ec1),
719        ec2: opt_ptr(&ec2),
720        node: if has_node {
721            Some(node_compat_trampoline)
722        } else {
723            None
724        },
725        edge: if has_edge {
726            Some(edge_compat_trampoline)
727        } else {
728            None
729        },
730        handler: if has_handler {
731            Some(iso_handler_trampoline)
732        } else {
733            None
734        },
735        arg: (&mut state as *mut Vf2State<'_, '_, '_>).cast(),
736    };
737    igraph_call!(f(&raw))
738}
739
740/// First mapping found by LAD (if any), and all the mappings (if requested).
741type LadResult = (Option<Vec<VertexId>>, Vec<Vec<VertexId>>);
742
743// ---------------------------------------------------------------------------
744// Motif callback
745// ---------------------------------------------------------------------------
746
747type MotifHandler<'h> = dyn FnMut(&[VertexId], usize) -> bool + 'h;
748
749unsafe extern "C" fn motif_trampoline(
750    _graph: *const igraph_t,
751    vids: *const igraph_vector_int_t,
752    isoclass: igraph_int_t,
753    extra: *mut c_void,
754) -> igraph_error_t {
755    let f = unsafe { &mut *(extra as *mut &mut MotifHandler<'_>) };
756    let vids = unsafe { (*vids).as_slice() };
757    in_finally_level(|| {
758        catch_panic(|| {
759            if f(vids, isoclass as usize) {
760                igraph_error_type_t_IGRAPH_SUCCESS
761            } else {
762                igraph_error_type_t_IGRAPH_STOP
763            }
764        })
765    })
766}
767
768// ---------------------------------------------------------------------------
769// Free functions
770// ---------------------------------------------------------------------------
771
772/// Inverts a permutation of `0..n` (`igraph_invert_permutation`).
773///
774/// The result `inv` satisfies `inv[perm[i]] == i` for all `i`. Handy to turn
775/// the labeling returned by [`Graph::canonical_permutation`] ("which vertex
776/// goes to position `i`") into "which position does vertex `v` go to", or to
777/// turn a `map21` of [`IsoMapping`] into a `map12`.
778///
779/// Binds [`igraph_invert_permutation`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_invert_permutation).
780///
781/// # Errors
782/// [`ErrorKind::InvalidValue`] if `perm` is
783/// not a permutation of `0..perm.len()`.
784///
785/// # Examples
786/// ```
787/// use igraph::isomorphism::invert_permutation;
788/// assert_eq!(invert_permutation(&[2, 0, 1]).unwrap(), vec![1, 2, 0]);
789/// assert!(invert_permutation(&[0, 0, 1]).is_err());
790/// ```
791pub fn invert_permutation(perm: &[i64]) -> Result<Vec<i64>> {
792    let p = VectorInt::view(perm);
793    let mut res = VectorInt::new();
794    igraph_call!(igraph_invert_permutation(p.as_ptr(), &mut res))?;
795    Ok(res.into())
796}
797
798/// The number of unlabeled simple graphs on `n` vertices (`igraph_graph_count`).
799///
800/// This is the number of isomorphism classes, i.e. the length of the
801/// histogram returned by [`Graph::motifs_randesu`] and one more than the
802/// largest valid [`Graph::isoclass`]. See OEIS [A000088] (undirected) and
803/// [A000273] (directed). Time complexity: O(1).
804///
805/// Binds [`igraph_graph_count`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_graph_count).
806///
807/// [A000088]: https://oeis.org/A000088
808/// [A000273]: https://oeis.org/A000273
809///
810/// # Errors
811/// [`ErrorKind::Overflow`] when the count does not
812/// fit in an `i64` (`n > 14` undirected, `n > 9` directed, on 64-bit
813/// platforms).
814///
815/// # Examples
816/// ```
817/// use igraph::isomorphism::graph_count;
818/// assert_eq!(graph_count(4, false).unwrap(), 11);
819/// assert_eq!(graph_count(3, true).unwrap(), 16);
820/// ```
821pub fn graph_count(n: usize, directed: bool) -> Result<usize> {
822    let n = igraph_int_t::try_from(n)
823        .map_err(|_| Error::new(ErrorKind::Overflow, format!("graph size {n} is too large")))?;
824    let mut count = 0;
825    igraph_call!(igraph_graph_count(n, directed, &mut count))?;
826    Ok(count as usize)
827}
828
829// ---------------------------------------------------------------------------
830// Graph methods
831// ---------------------------------------------------------------------------
832
833impl igraph_t {
834    // ----- generic --------------------------------------------------------
835
836    /// Whether `self` and `other` are isomorphic (`igraph_isomorphic`).
837    ///
838    /// The algorithm is chosen automatically:
839    /// 1. a directed and an undirected graph are an error;
840    /// 2. if either graph has multi-edges, both are
841    ///    [simplified and colorized](Self::simplify_and_colorize) and
842    ///    compared with VF2;
843    /// 3. graphs with different vertex or edge counts are not isomorphic;
844    /// 4. small loop-free graphs supported by [`isoclass`](Self::isoclass)
845    ///    (directed with 3–4 vertices, undirected with 3–6) use precomputed
846    ///    O(1) data;
847    /// 5. otherwise Bliss is used.
848    ///
849    /// Use [`isomorphic_vf2`](Self::isomorphic_vf2) or
850    /// [`isomorphic_bliss`](Self::isomorphic_bliss) to obtain a mapping.
851    /// Time complexity: exponential in the worst case.
852    ///
853    /// See also [`Graph::is_same_graph`], which compares *labeled* graphs
854    /// (identical vertex ids and edges), and
855    /// [`canonical_form`](Self::canonical_form) to test many graphs against
856    /// each other.
857    ///
858    /// Binds [`igraph_isomorphic`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_isomorphic).
859    ///
860    /// # Errors
861    /// [`ErrorKind::InvalidValue`] when one
862    /// graph is directed and the other is not.
863    ///
864    /// # Examples
865    /// ```
866    /// use igraph::prelude::*;
867    /// let a = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
868    /// let b = Graph::from_edges(&[(0, 2), (2, 1)], 3, false).unwrap();
869    /// let c = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false).unwrap();
870    /// assert!(a.isomorphic(&b).unwrap());
871    /// assert!(!a.is_same_graph(&b).unwrap()); // different labels
872    /// assert!(!a.isomorphic(&c).unwrap());
873    /// // The Petersen graph is the generalized Petersen graph GP(5, 2).
874    /// let p = Graph::famous("Petersen").unwrap();
875    /// assert!(p.isomorphic(&Graph::generalized_petersen(5, 2).unwrap()).unwrap());
876    /// ```
877    pub fn isomorphic(&self, other: &Graph) -> Result<bool> {
878        let mut iso = false;
879        igraph_call!(igraph_isomorphic(self, other, &mut iso))?;
880        Ok(iso)
881    }
882
883    /// Whether `pattern` is isomorphic to a (not necessarily induced)
884    /// subgraph of `self` (`igraph_subisomorphic`).
885    ///
886    /// `self` is the larger graph. Currently this always uses VF2, and so
887    /// does not support non-simple graphs (self-loops are an error,
888    /// multi-edges give wrong results). Time complexity: exponential.
889    ///
890    /// See also [`subisomorphic_lad`](Self::subisomorphic_lad) (note its
891    /// reversed argument order), which is often faster and can look for
892    /// induced subgraphs, and [`Graph::clique_number`] for the special case
893    /// of complete patterns.
894    ///
895    /// Binds [`igraph_subisomorphic`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_subisomorphic).
896    ///
897    /// # Errors
898    /// [`ErrorKind::InvalidValue`] when the graphs differ in directedness or
899    /// have self-loops.
900    ///
901    /// # Examples
902    /// ```
903    /// use igraph::prelude::*;
904    /// let square = Graph::ring(4, false, false, true).unwrap();
905    /// let path3 = Graph::ring(3, false, false, false).unwrap();
906    /// let triangle = Graph::full(3, false, false).unwrap();
907    /// assert!(square.subisomorphic(&path3).unwrap());
908    /// assert!(!square.subisomorphic(&triangle).unwrap());
909    /// ```
910    pub fn subisomorphic(&self, pattern: &Graph) -> Result<bool> {
911        let mut iso = false;
912        igraph_call!(igraph_subisomorphic(self, pattern, &mut iso))?;
913        Ok(iso)
914    }
915
916    /// Turns a multigraph into a colored simple graph
917    /// (`igraph_simplify_and_colorize`).
918    ///
919    /// Multi-edges are merged into single edges whose *color* is their
920    /// multiplicity, and self-loops are removed, the *color* of each vertex
921    /// being its number of self-loops. The result is suitable for the VF2
922    /// functions (which only support simple graphs but honour colors): two
923    /// multigraphs are isomorphic iff their colorized versions are
924    /// isomorphic as colored graphs.
925    ///
926    /// See also [`Graph::simplify`], which removes multi-edges and self-loops
927    /// in place without recording them, and [`Graph::count_multiple`].
928    ///
929    /// Binds [`igraph_simplify_and_colorize`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_simplify_and_colorize).
930    ///
931    /// # Examples
932    /// ```
933    /// use igraph::prelude::*;
934    /// // A double edge 0-1 plus a loop on 1.
935    /// let g = Graph::from_edges(&[(0, 1), (0, 1), (1, 1)], 2, false).unwrap();
936    /// let c = g.simplify_and_colorize().unwrap();
937    /// assert_eq!(c.graph.ecount(), 1);
938    /// assert_eq!(c.vertex_color, vec![0, 1]);
939    /// assert_eq!(c.edge_color, vec![2]);
940    /// ```
941    pub fn simplify_and_colorize(&self) -> Result<ColorizedGraph> {
942        let mut vertex_color = VectorInt::new();
943        let mut edge_color = VectorInt::new();
944        let graph = Graph::init_with(|res| unsafe {
945            igraph_simplify_and_colorize(self, res, &mut vertex_color, &mut edge_color)
946        })?;
947        Ok(ColorizedGraph {
948            graph,
949            vertex_color: vertex_color.into(),
950            edge_color: edge_color.into(),
951        })
952    }
953
954    // ----- VF2 --------------------------------------------------------------
955
956    /// Isomorphism test with VF2, returning a mapping
957    /// (`igraph_isomorphic_vf2`).
958    ///
959    /// Returns `Some(mapping)` if `self` and `other` are isomorphic under the
960    /// colors and compatibility predicates of `opts` (see [`Vf2Options`]),
961    /// `None` otherwise. Both graphs must be simple: self-loops are
962    /// rejected with an error, multi-edges are *not* detected and give
963    /// wrong results (use [`simplify_and_colorize`](Self::simplify_and_colorize)
964    /// first). Use [`subisomorphic_vf2`](Self::subisomorphic_vf2) for subgraphs.
965    /// Time complexity: exponential.
966    ///
967    /// Binds [`igraph_isomorphic_vf2`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_isomorphic_vf2).
968    ///
969    /// # Errors
970    /// [`ErrorKind::InvalidValue`] on directedness mismatch, self-loops, or
971    /// color vectors of the wrong length.
972    ///
973    /// # Examples
974    /// ```
975    /// use igraph::prelude::*;
976    /// use igraph::isomorphism::Vf2Options;
977    /// let g = Graph::from_edges(&[(0, 1), (1, 2)], 3, true).unwrap();
978    /// let h = Graph::from_edges(&[(2, 0), (1, 2)], 3, true).unwrap();
979    /// let m = g.isomorphic_vf2(&h, &mut Vf2Options::new()).unwrap().unwrap();
980    /// assert_eq!(m.map12, vec![1, 2, 0]); // 0->1->2 becomes 1->2->0
981    /// assert_eq!(m.map21, vec![2, 0, 1]);
982    /// ```
983    pub fn isomorphic_vf2(
984        &self,
985        other: &Graph,
986        opts: &mut Vf2Options<'_>,
987    ) -> Result<Option<IsoMapping>> {
988        let mut iso = false;
989        let mut map12 = VectorInt::new();
990        let mut map21 = VectorInt::new();
991        with_vf2(self, other, opts, None, |r| unsafe {
992            igraph_isomorphic_vf2(
993                self, other, r.vc1, r.vc2, r.ec1, r.ec2, &mut iso, &mut map12, &mut map21, r.node,
994                r.edge, r.arg,
995            )
996        })?;
997        Ok(iso.then(|| IsoMapping {
998            map12: map12.into(),
999            map21: map21.into(),
1000        }))
1001    }
1002
1003    /// Number of isomorphisms between `self` and `other` with VF2
1004    /// (`igraph_count_isomorphisms_vf2`).
1005    ///
1006    /// With `other == self` this is the number of automorphisms (but
1007    /// [`count_automorphisms`](Self::count_automorphisms) is much faster).
1008    /// Time complexity: exponential.
1009    ///
1010    /// Binds [`igraph_count_isomorphisms_vf2`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_count_isomorphisms_vf2).
1011    ///
1012    /// # Examples
1013    /// ```
1014    /// use igraph::prelude::*;
1015    /// use igraph::isomorphism::Vf2Options;
1016    /// let triangle = Graph::full(3, false, false).unwrap();
1017    /// assert_eq!(triangle.count_isomorphisms_vf2(&triangle, &mut Vf2Options::new()).unwrap(), 6);
1018    /// ```
1019    pub fn count_isomorphisms_vf2(
1020        &self,
1021        other: &Graph,
1022        opts: &mut Vf2Options<'_>,
1023    ) -> Result<usize> {
1024        let mut count = 0;
1025        with_vf2(self, other, opts, None, |r| unsafe {
1026            igraph_count_isomorphisms_vf2(
1027                self, other, r.vc1, r.vc2, r.ec1, r.ec2, &mut count, r.node, r.edge, r.arg,
1028            )
1029        })?;
1030        Ok(count as usize)
1031    }
1032
1033    /// All the isomorphisms between `self` and `other` with VF2
1034    /// (`igraph_get_isomorphisms_vf2`).
1035    ///
1036    /// Each mapping is given as `map21`, i.e. `m[v]` is the vertex of `self`
1037    /// matched to vertex `v` of `other`. The result is empty if the graphs
1038    /// are not isomorphic. Time complexity: exponential.
1039    ///
1040    /// Binds [`igraph_get_isomorphisms_vf2`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_get_isomorphisms_vf2).
1041    ///
1042    /// # Examples
1043    /// ```
1044    /// use igraph::prelude::*;
1045    /// use igraph::isomorphism::Vf2Options;
1046    /// let p = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1047    /// let mut maps = p.get_isomorphisms_vf2(&p, &mut Vf2Options::new()).unwrap();
1048    /// maps.sort();
1049    /// assert_eq!(maps, vec![vec![0, 1, 2], vec![2, 1, 0]]);
1050    /// ```
1051    pub fn get_isomorphisms_vf2(
1052        &self,
1053        other: &Graph,
1054        opts: &mut Vf2Options<'_>,
1055    ) -> Result<Vec<Vec<VertexId>>> {
1056        let mut maps = VectorIntList::new();
1057        with_vf2(self, other, opts, None, |r| unsafe {
1058            igraph_get_isomorphisms_vf2(
1059                self, other, r.vc1, r.vc2, r.ec1, r.ec2, &mut maps, r.node, r.edge, r.arg,
1060            )
1061        })?;
1062        Ok(maps.to_vecs())
1063    }
1064
1065    /// Streams the isomorphisms between `self` and `other` to a closure
1066    /// (`igraph_get_isomorphisms_vf2_callback`).
1067    ///
1068    /// `handler(map12, map21)` is called for each isomorphism found; return
1069    /// `true` to continue the search or `false` to stop it (which is not an
1070    /// error). A panic in the handler aborts the search and is resumed when
1071    /// this function returns. Time complexity: exponential.
1072    ///
1073    /// Binds [`igraph_get_isomorphisms_vf2_callback`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_get_isomorphisms_vf2_callback).
1074    ///
1075    /// # Examples
1076    /// ```
1077    /// use igraph::prelude::*;
1078    /// use igraph::isomorphism::Vf2Options;
1079    /// let k4 = Graph::full(4, false, false).unwrap();
1080    /// // Take the first three automorphisms of K4 only (out of 24).
1081    /// let mut found = vec![];
1082    /// k4.get_isomorphisms_vf2_callback(&k4, &mut Vf2Options::new(), |m12, _m21| {
1083    ///     found.push(m12.to_vec());
1084    ///     found.len() < 3
1085    /// })
1086    /// .unwrap();
1087    /// assert_eq!(found.len(), 3);
1088    /// ```
1089    pub fn get_isomorphisms_vf2_callback(
1090        &self,
1091        other: &Graph,
1092        opts: &mut Vf2Options<'_>,
1093        mut handler: impl FnMut(&[VertexId], &[VertexId]) -> bool,
1094    ) -> Result<()> {
1095        with_vf2(self, other, opts, Some(&mut handler), |r| unsafe {
1096            igraph_get_isomorphisms_vf2_callback(
1097                self,
1098                other,
1099                r.vc1,
1100                r.vc2,
1101                r.ec1,
1102                r.ec2,
1103                ptr::null_mut(),
1104                ptr::null_mut(),
1105                r.handler,
1106                r.node,
1107                r.edge,
1108                r.arg,
1109            )
1110        })
1111    }
1112
1113    /// Subgraph isomorphism test with VF2, returning a mapping
1114    /// (`igraph_subisomorphic_vf2`).
1115    ///
1116    /// Decides whether `pattern` (the smaller graph) is isomorphic to a
1117    /// subgraph of `self` (the larger graph); the subgraph need not be
1118    /// induced. Returns `Some(mapping)` with `mapping.map21[v]` the vertex of
1119    /// `self` matched to vertex `v` of `pattern`, and `mapping.map12[u]` the
1120    /// vertex of `pattern` matched to `u` (or `-1`). Time complexity:
1121    /// exponential.
1122    ///
1123    /// Binds [`igraph_subisomorphic_vf2`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_subisomorphic_vf2).
1124    ///
1125    /// # Examples
1126    /// ```
1127    /// use igraph::prelude::*;
1128    /// use igraph::isomorphism::Vf2Options;
1129    /// let big = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
1130    /// let triangle = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false).unwrap();
1131    /// let m = big.subisomorphic_vf2(&triangle, &mut Vf2Options::new()).unwrap().unwrap();
1132    /// let mut hit = m.map21.clone();
1133    /// hit.sort();
1134    /// assert_eq!(hit, vec![0, 1, 2]);
1135    /// assert_eq!(m.map12[3], -1); // vertex 3 is not in the match
1136    /// ```
1137    pub fn subisomorphic_vf2(
1138        &self,
1139        pattern: &Graph,
1140        opts: &mut Vf2Options<'_>,
1141    ) -> Result<Option<IsoMapping>> {
1142        let mut iso = false;
1143        let mut map12 = VectorInt::new();
1144        let mut map21 = VectorInt::new();
1145        with_vf2(self, pattern, opts, None, |r| unsafe {
1146            igraph_subisomorphic_vf2(
1147                self, pattern, r.vc1, r.vc2, r.ec1, r.ec2, &mut iso, &mut map12, &mut map21,
1148                r.node, r.edge, r.arg,
1149            )
1150        })?;
1151        Ok(iso.then(|| IsoMapping {
1152            map12: map12.into(),
1153            map21: map21.into(),
1154        }))
1155    }
1156
1157    /// Number of subgraph isomorphisms from `pattern` into `self` with VF2
1158    /// (`igraph_count_subisomorphisms_vf2`).
1159    ///
1160    /// Every embedding is counted, so each copy of `pattern` in `self` is
1161    /// counted as many times as `pattern` has automorphisms. Time
1162    /// complexity: exponential.
1163    ///
1164    /// Binds [`igraph_count_subisomorphisms_vf2`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_count_subisomorphisms_vf2).
1165    ///
1166    /// # Examples
1167    /// ```
1168    /// use igraph::prelude::*;
1169    /// use igraph::isomorphism::Vf2Options;
1170    /// let k4 = Graph::full(4, false, false).unwrap();
1171    /// let triangle = Graph::full(3, false, false).unwrap();
1172    /// // 4 triangles in K4, each with 6 automorphisms.
1173    /// assert_eq!(k4.count_subisomorphisms_vf2(&triangle, &mut Vf2Options::new()).unwrap(), 24);
1174    /// ```
1175    pub fn count_subisomorphisms_vf2(
1176        &self,
1177        pattern: &Graph,
1178        opts: &mut Vf2Options<'_>,
1179    ) -> Result<usize> {
1180        let mut count = 0;
1181        with_vf2(self, pattern, opts, None, |r| unsafe {
1182            igraph_count_subisomorphisms_vf2(
1183                self, pattern, r.vc1, r.vc2, r.ec1, r.ec2, &mut count, r.node, r.edge, r.arg,
1184            )
1185        })?;
1186        Ok(count as usize)
1187    }
1188
1189    /// All the subgraph isomorphisms from `pattern` into `self` with VF2
1190    /// (`igraph_get_subisomorphisms_vf2`).
1191    ///
1192    /// Each mapping `m` is a `map21`: `m[v]` is the vertex of `self` matched
1193    /// to vertex `v` of `pattern`. Time complexity: exponential.
1194    ///
1195    /// Binds [`igraph_get_subisomorphisms_vf2`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_get_subisomorphisms_vf2).
1196    ///
1197    /// # Examples
1198    /// ```
1199    /// use igraph::prelude::*;
1200    /// use igraph::isomorphism::Vf2Options;
1201    /// let star = Graph::star(4, StarMode::Undirected, 0).unwrap();
1202    /// let edge = Graph::full(2, false, false).unwrap();
1203    /// let maps = star.get_subisomorphisms_vf2(&edge, &mut Vf2Options::new()).unwrap();
1204    /// assert_eq!(maps.len(), 6); // 3 edges x 2 orientations
1205    /// ```
1206    pub fn get_subisomorphisms_vf2(
1207        &self,
1208        pattern: &Graph,
1209        opts: &mut Vf2Options<'_>,
1210    ) -> Result<Vec<Vec<VertexId>>> {
1211        let mut maps = VectorIntList::new();
1212        with_vf2(self, pattern, opts, None, |r| unsafe {
1213            igraph_get_subisomorphisms_vf2(
1214                self, pattern, r.vc1, r.vc2, r.ec1, r.ec2, &mut maps, r.node, r.edge, r.arg,
1215            )
1216        })?;
1217        Ok(maps.to_vecs())
1218    }
1219
1220    /// Streams the subgraph isomorphisms from `pattern` into `self` to a
1221    /// closure (`igraph_get_subisomorphisms_vf2_callback`).
1222    ///
1223    /// `handler(map12, map21)` is called for each embedding found (see
1224    /// [`IsoMapping`] for the meaning of the maps); return `true` to go on or
1225    /// `false` to stop the search early. A panic in the handler aborts the
1226    /// search and is resumed when this function returns. Time complexity:
1227    /// exponential.
1228    ///
1229    /// Binds [`igraph_get_subisomorphisms_vf2_callback`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_get_subisomorphisms_vf2_callback).
1230    ///
1231    /// # Examples
1232    /// ```
1233    /// use igraph::prelude::*;
1234    /// use igraph::isomorphism::Vf2Options;
1235    /// let c5 = Graph::ring(5, false, false, true).unwrap();
1236    /// let p3 = Graph::ring(3, false, false, false).unwrap();
1237    /// // Collect the middle vertices of the 2-paths of the pentagon.
1238    /// let mut centers = std::collections::BTreeSet::new();
1239    /// c5.get_subisomorphisms_vf2_callback(&p3, &mut Vf2Options::new(), |_, m21| {
1240    ///     centers.insert(m21[1]);
1241    ///     true
1242    /// })
1243    /// .unwrap();
1244    /// assert_eq!(centers.len(), 5);
1245    /// ```
1246    pub fn get_subisomorphisms_vf2_callback(
1247        &self,
1248        pattern: &Graph,
1249        opts: &mut Vf2Options<'_>,
1250        mut handler: impl FnMut(&[VertexId], &[VertexId]) -> bool,
1251    ) -> Result<()> {
1252        with_vf2(self, pattern, opts, Some(&mut handler), |r| unsafe {
1253            igraph_get_subisomorphisms_vf2_callback(
1254                self,
1255                pattern,
1256                r.vc1,
1257                r.vc2,
1258                r.ec1,
1259                r.ec2,
1260                ptr::null_mut(),
1261                ptr::null_mut(),
1262                r.handler,
1263                r.node,
1264                r.edge,
1265                r.arg,
1266            )
1267        })
1268    }
1269
1270    // ----- LAD --------------------------------------------------------------
1271
1272    fn lad_raw(
1273        &self,
1274        target: &Graph,
1275        domains: Option<&[Vec<VertexId>]>,
1276        induced: bool,
1277        all: bool,
1278    ) -> Result<LadResult> {
1279        if let Some(d) = domains
1280            && d.len() != self.vcount()
1281        {
1282            return Err(Error::invalid(format!(
1283                "there are {} domains but the pattern has {} vertices",
1284                d.len(),
1285                self.vcount()
1286            )));
1287        }
1288        // igraph 1.0.0 and 1.0.1 do not validate the domain entries: an
1289        // out-of-range id would be written out of bounds of an internal bitset.
1290        if let Some(d) = domains {
1291            let n = target.vcount() as i64;
1292            if let Some(&bad) = d.iter().flatten().find(|&&v| !(0..n).contains(&v)) {
1293                return Err(Error::new(
1294                    ErrorKind::InvalidVertexId,
1295                    format!("domain vertex {bad} is not a vertex of the target graph (0..{n})"),
1296                ));
1297            }
1298        }
1299        let domains: Option<VectorIntList> = domains.map(VectorIntList::from);
1300        let dp = domains
1301            .as_ref()
1302            .map_or(ptr::null(), |d| d as *const VectorIntList);
1303        let mut iso = false;
1304        let mut map = VectorInt::new();
1305        let mut maps = VectorIntList::new();
1306        let maps_ptr: *mut VectorIntList = if all { &mut maps } else { ptr::null_mut() };
1307        igraph_call!(igraph_subisomorphic_lad(
1308            self, target, dp, &mut iso, &mut map, maps_ptr, induced
1309        ))?;
1310        Ok((iso.then(|| map.into()), maps.to_vecs()))
1311    }
1312
1313    /// Subgraph isomorphism with the LAD algorithm (`igraph_subisomorphic_lad`).
1314    ///
1315    /// **Note the argument order**, which follows the C library: `self` is
1316    /// the (smaller) *pattern* and `target` the (larger) graph searched.
1317    /// Returns `Some(map)` with `map[v]` the vertex of `target` matched to
1318    /// vertex `v` of the pattern, or `None` if the pattern does not occur.
1319    ///
1320    /// * `domains`: optionally, for each pattern vertex, the list of target
1321    ///   vertices it may be matched to (this is how LAD implements vertex
1322    ///   colors); it must have one entry per pattern vertex.
1323    /// * `induced`: whether to look for *induced* subgraphs only (then
1324    ///   non-edges of the pattern must map to non-edges).
1325    ///
1326    /// Works for directed and undirected graphs without multi-edges.
1327    /// Time complexity: exponential.
1328    ///
1329    /// Binds [`igraph_subisomorphic_lad`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_subisomorphic_lad).
1330    ///
1331    /// # Errors
1332    /// [`ErrorKind::InvalidValue`] if `domains` does not have one entry per
1333    /// pattern vertex or the graphs differ in directedness or have
1334    /// multi-edges, and [`ErrorKind::InvalidVertexId`] if a domain contains
1335    /// an id that is not a vertex of `target`.
1336    ///
1337    /// # Examples
1338    /// ```
1339    /// use igraph::prelude::*;
1340    /// let square = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], 4, false).unwrap();
1341    /// let path3 = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1342    /// let map = path3.subisomorphic_lad(&square, None, true).unwrap().unwrap();
1343    /// assert_eq!(map.len(), 3);
1344    /// // Force the pattern's middle vertex onto vertex 2 of the square.
1345    /// let domains = vec![vec![0, 1, 2, 3], vec![2], vec![0, 1, 2, 3]];
1346    /// let map = path3.subisomorphic_lad(&square, Some(&domains), true).unwrap().unwrap();
1347    /// assert_eq!(map[1], 2);
1348    /// ```
1349    pub fn subisomorphic_lad(
1350        &self,
1351        target: &Graph,
1352        domains: Option<&[Vec<VertexId>]>,
1353        induced: bool,
1354    ) -> Result<Option<Vec<VertexId>>> {
1355        Ok(self.lad_raw(target, domains, induced, false)?.0)
1356    }
1357
1358    /// All the subgraph isomorphisms of the pattern `self` into `target`
1359    /// with the LAD algorithm (`igraph_subisomorphic_lad` with `maps`).
1360    ///
1361    /// Same arguments as [`subisomorphic_lad`](Self::subisomorphic_lad);
1362    /// returns every mapping (pattern vertex → target vertex).
1363    ///
1364    /// Binds [`igraph_subisomorphic_lad`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_subisomorphic_lad).
1365    ///
1366    /// # Examples
1367    /// ```
1368    /// use igraph::prelude::*;
1369    /// let square = Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], 4, false).unwrap();
1370    /// let path3 = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1371    /// // 4 induced 2-paths, each traversed in 2 directions.
1372    /// assert_eq!(path3.get_subisomorphisms_lad(&square, None, true).unwrap().len(), 8);
1373    /// ```
1374    pub fn get_subisomorphisms_lad(
1375        &self,
1376        target: &Graph,
1377        domains: Option<&[Vec<VertexId>]>,
1378        induced: bool,
1379    ) -> Result<Vec<Vec<VertexId>>> {
1380        Ok(self.lad_raw(target, domains, induced, true)?.1)
1381    }
1382
1383    // ----- Bliss -------------------------------------------------------------
1384
1385    /// Isomorphism test with Bliss, with mapping and statistics
1386    /// (`igraph_isomorphic_bliss`).
1387    ///
1388    /// Both graphs are brought into canonical form with the splitting
1389    /// heuristic `sh` and compared. `colors1` and `colors2` are optional
1390    /// vertex colors (a colored vertex may only map to a vertex of the same
1391    /// color; if only one graph is colored, colors are ignored with a
1392    /// warning). Multi-edges are not supported (the result would be wrong),
1393    /// self-loops are. Time complexity: exponential, fast in practice.
1394    ///
1395    /// Binds [`igraph_isomorphic_bliss`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_isomorphic_bliss).
1396    ///
1397    /// # Examples
1398    /// ```
1399    /// use igraph::prelude::*;
1400    /// use igraph::isomorphism::BlissSh;
1401    /// let a = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1402    /// let b = Graph::from_edges(&[(3, 1), (1, 0), (0, 2)], 4, false).unwrap();
1403    /// let res = a.isomorphic_bliss(&b, None, None, BlissSh::Fl).unwrap();
1404    /// assert!(res.is_isomorphic());
1405    /// assert_eq!(res.info1.group_size, "2");
1406    /// ```
1407    pub fn isomorphic_bliss(
1408        &self,
1409        other: &Graph,
1410        colors1: Option<&[i64]>,
1411        colors2: Option<&[i64]>,
1412        sh: BlissSh,
1413    ) -> Result<BlissIsomorphism> {
1414        check_len("colors1", colors1, self.vcount())?;
1415        check_len("colors2", colors2, other.vcount())?;
1416        let c1 = opt_view(colors1);
1417        let c2 = opt_view(colors2);
1418        let mut iso = false;
1419        let mut map12 = VectorInt::new();
1420        let mut map21 = VectorInt::new();
1421        let mut info1 = RawBlissInfo::new();
1422        let mut info2 = RawBlissInfo::new();
1423        igraph_call!(igraph_isomorphic_bliss(
1424            self,
1425            other,
1426            opt_ptr(&c1),
1427            opt_ptr(&c2),
1428            &mut iso,
1429            &mut map12,
1430            &mut map21,
1431            sh.into(),
1432            &mut info1.0,
1433            &mut info2.0
1434        ))?;
1435        Ok(BlissIsomorphism {
1436            mapping: iso.then(|| IsoMapping {
1437                map12: map12.into(),
1438                map21: map21.into(),
1439            }),
1440            info1: info1.to_info(),
1441            info2: info2.to_info(),
1442        })
1443    }
1444
1445    /// The canonical labeling of the graph (`igraph_canonical_permutation`).
1446    ///
1447    /// Returns `labeling`, where `labeling[i]` is the vertex of `self` that
1448    /// becomes vertex `i` of the canonical form (since igraph 1.0; this is
1449    /// the convention of `igraph_permute_vertices`, and the inverse of the
1450    /// convention of igraph 0.10). Use [`invert_permutation`] to get, for
1451    /// each vertex, its position in the canonical form. Relabeling two graphs
1452    /// by their canonical labelings yields *identical* graphs if and only if
1453    /// the graphs are isomorphic (see [`canonical_form`](Self::canonical_form)).
1454    /// `colors` optionally assigns vertex colors, which must be preserved;
1455    /// colored graphs are isomorphic if and only if, in addition, their
1456    /// colors relabeled the same way (`colors[labeling[i]]`) are equal.
1457    /// Multi-edges are not
1458    /// supported. Uses Bliss with sensible defaults; see
1459    /// [`canonical_permutation_bliss`](Self::canonical_permutation_bliss) to
1460    /// tune it.
1461    ///
1462    /// Binds [`igraph_canonical_permutation`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_canonical_permutation).
1463    ///
1464    /// # Examples
1465    /// ```
1466    /// use igraph::prelude::*;
1467    /// let g = Graph::star(4, StarMode::Undirected, 0).unwrap();
1468    /// let mut sorted = g.canonical_permutation(None).unwrap();
1469    /// sorted.sort();
1470    /// assert_eq!(sorted, vec![0, 1, 2, 3]); // it is a permutation
1471    ///
1472    /// // The canonical form is the graph relabeled by the labeling...
1473    /// let labeling = g.canonical_permutation(None).unwrap();
1474    /// let relabeled = g.permute_vertices(&labeling).unwrap();
1475    /// assert!(relabeled.isomorphic(&g.canonical_form(None).unwrap()).unwrap());
1476    ///
1477    /// // ... built by hand: vertex v goes to position pos[v].
1478    /// let pos = igraph::isomorphism::invert_permutation(&labeling).unwrap();
1479    /// let mut edges: Vec<(i64, i64)> = g
1480    ///     .edge_list()
1481    ///     .into_iter()
1482    ///     .map(|(a, b)| {
1483    ///         let (a, b) = (pos[a as usize], pos[b as usize]);
1484    ///         (a.min(b), a.max(b))
1485    ///     })
1486    ///     .collect();
1487    /// edges.sort();
1488    /// let by_hand = Graph::from_edges(&edges, 4, false).unwrap();
1489    /// assert!(by_hand == g.canonical_form(None).unwrap());
1490    /// ```
1491    pub fn canonical_permutation(&self, colors: Option<&[i64]>) -> Result<Vec<VertexId>> {
1492        check_len("colors", colors, self.vcount())?;
1493        let c = opt_view(colors);
1494        let mut labeling = VectorInt::new();
1495        igraph_call!(igraph_canonical_permutation(
1496            self,
1497            opt_ptr(&c),
1498            &mut labeling
1499        ))?;
1500        Ok(labeling.into())
1501    }
1502
1503    /// The canonical labeling of the graph computed by Bliss with the given
1504    /// splitting heuristic, together with the Bliss statistics
1505    /// (`igraph_canonical_permutation_bliss`).
1506    ///
1507    /// See [`canonical_permutation`](Self::canonical_permutation).
1508    ///
1509    /// Binds [`igraph_canonical_permutation_bliss`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_canonical_permutation_bliss).
1510    ///
1511    /// # Examples
1512    /// ```
1513    /// use igraph::prelude::*;
1514    /// use igraph::isomorphism::BlissSh;
1515    /// let k3 = Graph::full(3, false, false).unwrap();
1516    /// let (labeling, info) = k3.canonical_permutation_bliss(None, BlissSh::Fsm).unwrap();
1517    /// assert_eq!(labeling.len(), 3);
1518    /// assert_eq!(info.group_size, "6");
1519    /// ```
1520    pub fn canonical_permutation_bliss(
1521        &self,
1522        colors: Option<&[i64]>,
1523        sh: BlissSh,
1524    ) -> Result<(Vec<VertexId>, BlissInfo)> {
1525        check_len("colors", colors, self.vcount())?;
1526        let c = opt_view(colors);
1527        let mut labeling = VectorInt::new();
1528        let mut info = RawBlissInfo::new();
1529        igraph_call!(igraph_canonical_permutation_bliss(
1530            self,
1531            opt_ptr(&c),
1532            &mut labeling,
1533            sh.into(),
1534            &mut info.0
1535        ))?;
1536        Ok((labeling.into(), info.to_info()))
1537    }
1538
1539    /// The canonical form of the graph: the graph with its vertices permuted
1540    /// by its [canonical labeling](Self::canonical_permutation).
1541    ///
1542    /// Two uncolored graphs are isomorphic if and only if their canonical
1543    /// forms are identical, i.e. compare equal with `==`
1544    /// ([`Graph::is_same_graph`]). This makes canonical forms perfect keys to
1545    /// deduplicate graphs up to isomorphism. With `colors`, the returned
1546    /// graph does not carry the colors: two colored graphs are isomorphic
1547    /// (as colored graphs) if and only if their canonical forms are identical
1548    /// *and* so are their colors relabeled by the canonical labelings, i.e.
1549    /// `colors[labeling[i]]` for each `i` (see
1550    /// [`canonical_permutation`](Self::canonical_permutation)).
1551    /// Multi-edges are not supported (encode them with
1552    /// [`simplify_and_colorize`](Self::simplify_and_colorize) first).
1553    ///
1554    /// Combines [`igraph_canonical_permutation`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_canonical_permutation)
1555    /// and [`igraph_permute_vertices`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_permute_vertices)
1556    /// (see [`Graph::permute_vertices`]), then sorts the edges (and, when
1557    /// undirected, their endpoints) so that the result does not depend on
1558    /// the input edge order.
1559    ///
1560    /// # Examples
1561    /// ```
1562    /// use igraph::prelude::*;
1563    /// let a = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1564    /// let b = Graph::from_edges(&[(2, 0), (0, 3), (3, 1)], 4, false).unwrap();
1565    /// assert!(a.canonical_form(None).unwrap() == b.canonical_form(None).unwrap());
1566    /// ```
1567    pub fn canonical_form(&self, colors: Option<&[i64]>) -> Result<Graph> {
1568        let labeling = self.canonical_permutation(colors)?;
1569        let permuted = self.permute_vertices(&labeling)?;
1570        // igraph_permute_vertices keeps the original edge order; sort the
1571        // edges so that identical canonical forms compare equal.
1572        let mut edges: Vec<(VertexId, VertexId)> = permuted
1573            .edge_list()
1574            .into_iter()
1575            .map(|(a, b)| {
1576                if self.is_directed() || a <= b {
1577                    (a, b)
1578                } else {
1579                    (b, a)
1580                }
1581            })
1582            .collect();
1583        edges.sort_unstable();
1584        Graph::from_edges(&edges, self.vcount(), self.is_directed())
1585    }
1586
1587    /// Number of automorphisms of the graph (`igraph_count_automorphisms`).
1588    ///
1589    /// An automorphism is an isomorphism of the graph onto itself, i.e. a
1590    /// symmetry. The count is returned as an `f64` because it can be huge;
1591    /// use [`count_automorphisms_bliss`](Self::count_automorphisms_bliss) to
1592    /// get the exact value as a string. `colors` optionally restricts to
1593    /// color-preserving automorphisms. Multi-edges are not supported.
1594    ///
1595    /// Binds [`igraph_count_automorphisms`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_count_automorphisms).
1596    ///
1597    /// # Errors
1598    /// [`ErrorKind::Overflow`] if the count does
1599    /// not fit into an `f64`.
1600    ///
1601    /// # Examples
1602    /// ```
1603    /// use igraph::prelude::*;
1604    /// let star = Graph::star(5, StarMode::Undirected, 0).unwrap();
1605    /// assert_eq!(star.count_automorphisms(None).unwrap(), 24.0); // 4!
1606    /// // The Petersen graph's symmetry group is S5, of order 120.
1607    /// assert_eq!(Graph::famous("Petersen").unwrap().count_automorphisms(None).unwrap(), 120.0);
1608    /// // Coloring one leaf differently leaves 3! symmetries.
1609    /// assert_eq!(star.count_automorphisms(Some(&[0, 1, 1, 1, 2])).unwrap(), 6.0);
1610    /// ```
1611    pub fn count_automorphisms(&self, colors: Option<&[i64]>) -> Result<f64> {
1612        check_len("colors", colors, self.vcount())?;
1613        let c = opt_view(colors);
1614        let mut res = 0.0;
1615        igraph_call!(igraph_count_automorphisms(self, opt_ptr(&c), &mut res))?;
1616        Ok(res)
1617    }
1618
1619    /// Number of automorphisms computed by Bliss, returned exactly in
1620    /// [`BlissInfo::group_size`] (`igraph_count_automorphisms_bliss`).
1621    ///
1622    /// Same semantics as [`count_automorphisms`](Self::count_automorphisms)
1623    /// (optional `colors`, no multi-edges), with the splitting heuristic `sh`
1624    /// chosen explicitly; the returned statistics also describe the search
1625    /// tree. Unlike the `f64` count, the decimal
1626    /// [`group_size`](BlissInfo::group_size) is exact for arbitrarily large
1627    /// groups.
1628    ///
1629    /// Binds [`igraph_count_automorphisms_bliss`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_count_automorphisms_bliss).
1630    ///
1631    /// # Examples
1632    /// ```
1633    /// use igraph::prelude::*;
1634    /// use igraph::isomorphism::BlissSh;
1635    /// let k23 = Graph::full(23, false, false).unwrap();
1636    /// let info = k23.count_automorphisms_bliss(None, BlissSh::F).unwrap();
1637    /// assert_eq!(info.group_size, "25852016738884976640000"); // 23!
1638    /// ```
1639    pub fn count_automorphisms_bliss(
1640        &self,
1641        colors: Option<&[i64]>,
1642        sh: BlissSh,
1643    ) -> Result<BlissInfo> {
1644        check_len("colors", colors, self.vcount())?;
1645        let c = opt_view(colors);
1646        let mut info = RawBlissInfo::new();
1647        igraph_call!(igraph_count_automorphisms_bliss(
1648            self,
1649            opt_ptr(&c),
1650            sh.into(),
1651            &mut info.0
1652        ))?;
1653        Ok(info.to_info())
1654    }
1655
1656    /// Generators of the automorphism group of the graph
1657    /// (`igraph_automorphism_group`).
1658    ///
1659    /// Each generator is a permutation of the vertices (0-based). The set may
1660    /// not be minimal and depends on the algorithm's internals; every
1661    /// automorphism is a product of generators. `colors` optionally restricts
1662    /// to color-preserving automorphisms. Multi-edges are not supported.
1663    ///
1664    /// Binds [`igraph_automorphism_group`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_automorphism_group).
1665    ///
1666    /// # Examples
1667    /// ```
1668    /// use igraph::prelude::*;
1669    /// let path = Graph::ring(3, false, false, false).unwrap();
1670    /// assert_eq!(path.automorphism_group(None).unwrap(), vec![vec![2, 1, 0]]);
1671    /// ```
1672    pub fn automorphism_group(&self, colors: Option<&[i64]>) -> Result<Vec<Vec<VertexId>>> {
1673        check_len("colors", colors, self.vcount())?;
1674        let c = opt_view(colors);
1675        let mut gens = VectorIntList::new();
1676        igraph_call!(igraph_automorphism_group(self, opt_ptr(&c), &mut gens))?;
1677        Ok(gens.to_vecs())
1678    }
1679
1680    /// Generators of the automorphism group computed by Bliss with the given
1681    /// splitting heuristic, with statistics (`igraph_automorphism_group_bliss`).
1682    ///
1683    /// See [`automorphism_group`](Self::automorphism_group).
1684    ///
1685    /// Binds [`igraph_automorphism_group_bliss`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_automorphism_group_bliss).
1686    ///
1687    /// # Examples
1688    /// ```
1689    /// use igraph::prelude::*;
1690    /// use igraph::isomorphism::BlissSh;
1691    /// let c4 = Graph::ring(4, false, false, true).unwrap();
1692    /// let (gens, info) = c4.automorphism_group_bliss(None, BlissSh::Fs).unwrap();
1693    /// assert_eq!(gens.len() as u64, info.nof_generators);
1694    /// assert_eq!(info.group_size, "8");
1695    /// ```
1696    pub fn automorphism_group_bliss(
1697        &self,
1698        colors: Option<&[i64]>,
1699        sh: BlissSh,
1700    ) -> Result<(Vec<Vec<VertexId>>, BlissInfo)> {
1701        check_len("colors", colors, self.vcount())?;
1702        let c = opt_view(colors);
1703        let mut gens = VectorIntList::new();
1704        let mut info = RawBlissInfo::new();
1705        igraph_call!(igraph_automorphism_group_bliss(
1706            self,
1707            opt_ptr(&c),
1708            &mut gens,
1709            sh.into(),
1710            &mut info.0
1711        ))?;
1712        Ok((gens.to_vecs(), info.to_info()))
1713    }
1714
1715    // ----- isomorphism classes -------------------------------------------------
1716
1717    /// The isomorphism class of a small graph (`igraph_isoclass`).
1718    ///
1719    /// Graphs with the same number of vertices and directedness are
1720    /// isomorphic iff they have the same class. Classes are numbered from 0
1721    /// (the empty graph) to [`graph_count`]`(n, directed) - 1` (the complete
1722    /// graph). Supported: directed graphs with 3–4 vertices, undirected
1723    /// graphs with 3–6 vertices. Multi-edges and self-loops are ignored.
1724    /// Time complexity: O(|E|).
1725    ///
1726    /// Binds [`igraph_isoclass`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_isoclass).
1727    ///
1728    /// # Errors
1729    /// [`ErrorKind::Unimplemented`] for unsupported sizes.
1730    ///
1731    /// # Examples
1732    /// ```
1733    /// use igraph::prelude::*;
1734    /// let empty = Graph::new(3, false);
1735    /// let triangle = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false).unwrap();
1736    /// assert_eq!(empty.isoclass().unwrap(), 0);
1737    /// assert_eq!(triangle.isoclass().unwrap(), 3);
1738    /// ```
1739    pub fn isoclass(&self) -> Result<usize> {
1740        let mut class = 0;
1741        igraph_call!(igraph_isoclass(self, &mut class))?;
1742        Ok(class as usize)
1743    }
1744
1745    /// The isomorphism class of the subgraph induced by `vids`
1746    /// (`igraph_isoclass_subgraph`).
1747    ///
1748    /// Same numbering and size limits as [`isoclass`](Self::isoclass); each
1749    /// vertex may appear at most once in `vids`. Time complexity:
1750    /// O((d+n)·n), d being the average degree and n the number of vertices.
1751    ///
1752    /// Binds [`igraph_isoclass_subgraph`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_isoclass_subgraph).
1753    ///
1754    /// # Errors
1755    /// [`ErrorKind::InvalidVertexId`] for invalid vertices,
1756    /// [`ErrorKind::InvalidValue`] if a vertex is selected twice, and
1757    /// [`ErrorKind::Unimplemented`] for unsupported subgraph sizes.
1758    ///
1759    /// # Examples
1760    /// ```
1761    /// use igraph::prelude::*;
1762    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
1763    /// assert_eq!(g.isoclass_subgraph(&[0, 1, 2]).unwrap(), 3); // triangle
1764    /// assert_eq!(g.isoclass_subgraph(&[1, 2, 3]).unwrap(), 2); // path
1765    /// ```
1766    pub fn isoclass_subgraph<'a>(&self, vids: impl Into<VertexSelector<'a>>) -> Result<usize> {
1767        let vs = vids.into().to_raw()?;
1768        // igraph 1.0.0 and 1.0.1 silently return a wrong class for repeated
1769        // vertices.
1770        let mut list = VectorInt::new();
1771        igraph_call!(igraph_vs_as_vector(self, vs.get(), &mut list))?;
1772        let mut sorted = list.to_vec();
1773        sorted.sort_unstable();
1774        if sorted.windows(2).any(|w| w[0] == w[1]) {
1775            return Err(Error::invalid(
1776                "isoclass_subgraph: a vertex is selected twice",
1777            ));
1778        }
1779        let mut class = 0;
1780        igraph_call!(igraph_isoclass_subgraph(self, vs.get(), &mut class))?;
1781        Ok(class as usize)
1782    }
1783
1784    /// Creates the canonical representative graph of an isomorphism class
1785    /// (`igraph_isoclass_create`).
1786    ///
1787    /// `size` is the number of vertices (3–4 directed, 3–6 undirected) and
1788    /// `number` the class, in `0..graph_count(size, directed)`. This is the
1789    /// inverse of [`isoclass`](Self::isoclass). Time complexity: O(|V|+|E|).
1790    /// Use it to draw or inspect the shapes counted by
1791    /// [`motifs_randesu`](Self::motifs_randesu).
1792    ///
1793    /// Binds [`igraph_isoclass_create`](https://igraph.org/c/html/latest/igraph-Isomorphism.html#igraph_isoclass_create).
1794    ///
1795    /// # Errors
1796    /// For unsupported sizes or out-of-range class numbers.
1797    ///
1798    /// # Examples
1799    /// ```
1800    /// use igraph::prelude::*;
1801    /// // Class 15 of directed triads is the complete digraph.
1802    /// let g = Graph::isoclass_create(3, 15, true).unwrap();
1803    /// assert_eq!(g.ecount(), 6);
1804    /// assert_eq!(g.isoclass().unwrap(), 15);
1805    /// ```
1806    pub fn isoclass_create(size: usize, number: usize, directed: bool) -> Result<Graph> {
1807        Graph::init_with(|g| unsafe {
1808            igraph_isoclass_create(g, size as igraph_int_t, number as igraph_int_t, directed)
1809        })
1810    }
1811
1812    // ----- motifs -------------------------------------------------------------
1813
1814    /// The motif histogram of the graph with the RAND-ESU algorithm
1815    /// (`igraph_motifs_randesu`).
1816    ///
1817    /// Motifs are small weakly connected *induced* subgraphs. Entry `i` of
1818    /// the result counts the induced subgraphs on `size` vertices of
1819    /// [isomorphism class](Self::isoclass) `i`; classes that are not
1820    /// (weakly) connected are reported as `NaN`, since they are not counted.
1821    /// Supported sizes: 3–4 (directed), 3–6 (undirected); directed motifs are
1822    /// counted in directed graphs.
1823    ///
1824    /// `cut_prob` optionally gives, for each of the `size` levels of the
1825    /// search tree, the probability of cutting a branch (sampling à la
1826    /// FANMOD, Wernicke and Rasche 2006); `None` performs a complete search.
1827    /// Cutting uses igraph's RNG: seed it with [`rng::seed`](crate::rng::seed)
1828    /// for reproducible samples.
1829    ///
1830    /// To assess the significance of motif counts, compare them with those
1831    /// of degree-preserving randomizations, e.g. from [`Graph::rewire`].
1832    /// For size 3 in directed graphs, [`triad_census`](Self::triad_census)
1833    /// additionally counts the unconnected triads.
1834    ///
1835    /// Binds [`igraph_motifs_randesu`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_motifs_randesu).
1836    ///
1837    /// # Errors
1838    /// For unsupported sizes, or if `cut_prob` does not have length `size`.
1839    ///
1840    /// # Examples
1841    /// ```
1842    /// use igraph::prelude::*;
1843    /// // A triangle with a pendant vertex: two 2-paths and one triangle.
1844    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
1845    /// let hist = g.motifs_randesu(3, None).unwrap();
1846    /// assert_eq!(&hist[2..], &[2.0, 1.0]);
1847    /// ```
1848    pub fn motifs_randesu(&self, size: usize, cut_prob: Option<&[f64]>) -> Result<Vec<f64>> {
1849        let cp = cut_prob.map(Vector::view);
1850        let mut hist = Vector::new();
1851        igraph_call!(igraph_motifs_randesu(
1852            self,
1853            &mut hist,
1854            size as igraph_int_t,
1855            opt_ptr(&cp)
1856        ))?;
1857        Ok(hist.into())
1858    }
1859
1860    /// Finds the motifs of the graph and streams them to a closure
1861    /// (`igraph_motifs_randesu_callback`).
1862    ///
1863    /// `f(vids, isoclass)` is called for every motif (weakly connected
1864    /// induced subgraph on `size` vertices) found, with its vertices and its
1865    /// [isomorphism class](Self::isoclass); return `true` to continue or
1866    /// `false` to stop the search. A panic in `f` aborts the search and is
1867    /// resumed when this function returns. See
1868    /// [`motifs_randesu`](Self::motifs_randesu) for `size` and `cut_prob`.
1869    ///
1870    /// Binds [`igraph_motifs_randesu_callback`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_motifs_randesu_callback).
1871    ///
1872    /// # Examples
1873    /// ```
1874    /// use igraph::prelude::*;
1875    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
1876    /// let mut triangles = vec![];
1877    /// g.motifs_randesu_callback(3, None, |vids, class| {
1878    ///     if class == 3 {
1879    ///         let mut t = vids.to_vec();
1880    ///         t.sort();
1881    ///         triangles.push(t);
1882    ///     }
1883    ///     true
1884    /// })
1885    /// .unwrap();
1886    /// assert_eq!(triangles, vec![vec![0, 1, 2]]);
1887    /// ```
1888    pub fn motifs_randesu_callback(
1889        &self,
1890        size: usize,
1891        cut_prob: Option<&[f64]>,
1892        mut f: impl FnMut(&[VertexId], usize) -> bool,
1893    ) -> Result<()> {
1894        let cp = cut_prob.map(Vector::view);
1895        let mut dynf: &mut MotifHandler<'_> = &mut f;
1896        let extra = (&mut dynf as *mut &mut MotifHandler<'_>).cast::<c_void>();
1897        igraph_call!(igraph_motifs_randesu_callback(
1898            self,
1899            size as igraph_int_t,
1900            opt_ptr(&cp),
1901            Some(motif_trampoline),
1902            extra
1903        ))
1904    }
1905
1906    /// Total number of motifs, i.e. of weakly connected induced subgraphs on
1907    /// `size` vertices (`igraph_motifs_randesu_no`).
1908    ///
1909    /// Unlike [`motifs_randesu`](Self::motifs_randesu), it does not classify
1910    /// the subgraphs, so arbitrarily large sizes are supported. The result
1911    /// is an integer returned as `f64` (to avoid overflow). See
1912    /// [`motifs_randesu`](Self::motifs_randesu) for `cut_prob`.
1913    ///
1914    /// Binds [`igraph_motifs_randesu_no`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_motifs_randesu_no).
1915    ///
1916    /// # Errors
1917    /// [`ErrorKind::InvalidValue`] if `size < 3` or `cut_prob` does not have
1918    /// length `size`.
1919    ///
1920    /// # Examples
1921    /// ```
1922    /// use igraph::prelude::*;
1923    /// let k10 = Graph::full(10, false, false).unwrap();
1924    /// assert_eq!(k10.motifs_randesu_no(4, None).unwrap(), 210.0); // C(10, 4)
1925    /// ```
1926    pub fn motifs_randesu_no(&self, size: usize, cut_prob: Option<&[f64]>) -> Result<f64> {
1927        let cp = cut_prob.map(Vector::view);
1928        let mut no = 0.0;
1929        igraph_call!(igraph_motifs_randesu_no(
1930            self,
1931            &mut no,
1932            size as igraph_int_t,
1933            opt_ptr(&cp)
1934        ))?;
1935        Ok(no)
1936    }
1937
1938    /// Estimates the total number of motifs of the given size
1939    /// (`igraph_motifs_randesu_estimate`).
1940    ///
1941    /// Takes a *sample* of vertices ([`MotifSample`]: either a number of
1942    /// distinct vertices drawn uniformly at random, or an explicit list),
1943    /// runs the RAND-ESU enumeration rooted at each sampled vertex `v`
1944    /// (which counts the connected induced subgraphs on `size` vertices
1945    /// whose smallest vertex id is `v`), and multiplies the total by
1946    /// `vcount / sample_size`. With every vertex in the sample the result
1947    /// equals [`motifs_randesu_no`](Self::motifs_randesu_no); otherwise it
1948    /// depends on the vertex numbering, even for very symmetric graphs.
1949    /// Useful for large graphs where counting all subgraphs is too slow. See
1950    /// [`motifs_randesu`](Self::motifs_randesu) for `cut_prob`. The random
1951    /// sample is drawn from the calling thread's RNG ([`rng::seed`](crate::rng::seed)).
1952    /// The null graph has an estimate of 0 whatever the sample.
1953    ///
1954    /// Binds [`igraph_motifs_randesu_estimate`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_motifs_randesu_estimate).
1955    ///
1956    /// # Errors
1957    /// [`ErrorKind::InvalidValue`] if `size < 3`, `cut_prob` does not have
1958    /// length `size`, or the sample is empty or larger than the graph
1959    /// ([`MotifSample::Random`]); [`ErrorKind::InvalidVertexId`] if
1960    /// [`MotifSample::Vertices`] has invalid vertices.
1961    ///
1962    /// # Examples
1963    /// ```
1964    /// use igraph::prelude::*;
1965    /// use igraph::isomorphism::MotifSample;
1966    /// let k10 = Graph::full(10, false, false).unwrap();
1967    /// // Sampling every vertex gives the exact count, C(10, 3).
1968    /// let all: Vec<i64> = (0..10).collect();
1969    /// let est = k10.motifs_randesu_estimate(3, None, MotifSample::Vertices(&all)).unwrap();
1970    /// assert_eq!(est, 120.0);
1971    /// // A random sample of 5 vertices gives an estimate, reproducible
1972    /// // once the (per-thread) RNG is seeded.
1973    /// igraph::rng::seed(42).unwrap();
1974    /// let est = k10.motifs_randesu_estimate(3, None, MotifSample::Random(5)).unwrap();
1975    /// assert!(est > 0.0);
1976    /// igraph::rng::seed(42).unwrap();
1977    /// assert_eq!(k10.motifs_randesu_estimate(3, None, MotifSample::Random(5)).unwrap(), est);
1978    /// ```
1979    pub fn motifs_randesu_estimate(
1980        &self,
1981        size: usize,
1982        cut_prob: Option<&[f64]>,
1983        sample: MotifSample<'_>,
1984    ) -> Result<f64> {
1985        let cp = cut_prob.map(Vector::view);
1986        let (sample_size, sample_view) = match sample {
1987            // A size above `igraph_int_t::MAX` would turn negative, which
1988            // igraph 1.0.1 does not reject (it aborts on an assertion).
1989            MotifSample::Random(n) => (
1990                igraph_int_t::try_from(n).map_err(|_| {
1991                    Error::invalid(format!("sample size {n} exceeds the number of vertices"))
1992                })?,
1993                None,
1994            ),
1995            MotifSample::Vertices(v) => (v.len() as igraph_int_t, Some(VectorInt::view(v))),
1996        };
1997        let mut est = 0.0;
1998        igraph_call!(igraph_motifs_randesu_estimate(
1999            self,
2000            &mut est,
2001            size as igraph_int_t,
2002            opt_ptr(&cp),
2003            sample_size,
2004            opt_ptr(&sample_view)
2005        ))?;
2006        Ok(est)
2007    }
2008
2009    /// The dyad census of the graph (`igraph_dyad_census`), as defined by
2010    /// Holland and Leinhardt (1970).
2011    ///
2012    /// Classifies each unordered pair of vertices as *mutual* (edges in both
2013    /// directions), *asymmetric* (in one direction only) or *null* (not
2014    /// connected). In undirected graphs every connected pair is mutual.
2015    /// Multi-edges and self-loops do not matter. Time complexity: O(|V|+|E|).
2016    ///
2017    /// See also [`Graph::reciprocity`]: with the default mode it equals
2018    /// `2 mutual / (2 mutual + asymmetric)` on simple graphs.
2019    ///
2020    /// Binds [`igraph_dyad_census`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_dyad_census).
2021    ///
2022    /// # Examples
2023    /// ```
2024    /// use igraph::prelude::*;
2025    /// let g = Graph::from_edges(&[(0, 1), (1, 0), (1, 2)], 4, true).unwrap();
2026    /// let d = g.dyad_census().unwrap();
2027    /// assert_eq!((d.mutual, d.asymmetric, d.null), (1.0, 1.0, 4.0));
2028    /// ```
2029    pub fn dyad_census(&self) -> Result<DyadCensus> {
2030        let (mut mutual, mut asymmetric, mut null) = (0.0, 0.0, 0.0);
2031        igraph_call!(igraph_dyad_census(
2032            self,
2033            &mut mutual,
2034            &mut asymmetric,
2035            &mut null
2036        ))?;
2037        Ok(DyadCensus {
2038            mutual,
2039            asymmetric,
2040            null,
2041        })
2042    }
2043
2044    /// The triad census of the graph (`igraph_triad_census`), as defined by
2045    /// Davis and Leinhardt (1972).
2046    ///
2047    /// Classifies every vertex triple into one of the 16 types of directed
2048    /// triads, see [`TriadCensus`]. Intended for directed graphs: undirected
2049    /// edges are treated as mutual (and igraph emits a warning). Note that
2050    /// the order differs from the isoclass order of
2051    /// [`motifs_randesu`](Self::motifs_randesu).
2052    ///
2053    /// Binds [`igraph_triad_census`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_triad_census).
2054    ///
2055    /// # Examples
2056    /// ```
2057    /// use igraph::prelude::*;
2058    /// let cycle = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, true).unwrap();
2059    /// let t = cycle.triad_census().unwrap();
2060    /// assert_eq!(t["030C"], 1.0);
2061    /// assert_eq!(t.total(), 1.0);
2062    /// ```
2063    pub fn triad_census(&self) -> Result<TriadCensus> {
2064        let mut res = Vector::new();
2065        igraph_call!(igraph_triad_census(self, &mut res))?;
2066        let mut counts = [0.0; 16];
2067        if res.len() != 16 {
2068            return Err(Error::new(
2069                ErrorKind::Internal,
2070                "triad census of unexpected length",
2071            ));
2072        }
2073        counts.copy_from_slice(&res);
2074        Ok(TriadCensus { counts })
2075    }
2076
2077    /// Number of triangles each selected vertex is part of
2078    /// (`igraph_count_adjacent_triangles`).
2079    ///
2080    /// Edge directions and multiplicities are ignored. Returned as `f64`
2081    /// (to avoid overflow). Time complexity: O(d²·n), d the average degree
2082    /// of the n queried vertices.
2083    ///
2084    /// See also [`Graph::transitivity_local_undirected`], the local
2085    /// clustering coefficient `t(v) / (d(v) (d(v) - 1) / 2)`.
2086    ///
2087    /// Binds [`igraph_count_adjacent_triangles`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_count_adjacent_triangles).
2088    ///
2089    /// # Examples
2090    /// ```
2091    /// use igraph::prelude::*;
2092    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
2093    /// assert_eq!(g.count_adjacent_triangles(..).unwrap(), vec![1.0, 1.0, 1.0, 0.0]);
2094    /// ```
2095    pub fn count_adjacent_triangles<'a>(
2096        &self,
2097        vids: impl Into<VertexSelector<'a>>,
2098    ) -> Result<Vec<f64>> {
2099        let vs = vids.into().to_raw()?;
2100        let mut res = Vector::new();
2101        directed_cache_guard(self, || {
2102            igraph_call!(igraph_count_adjacent_triangles(self, &mut res, vs.get()))
2103        })?;
2104        Ok(res.into())
2105    }
2106
2107    /// All the triangles of the graph, each listed once as a triple of
2108    /// vertex ids (`igraph_list_triangles`).
2109    ///
2110    /// Edge directions and multi-edges are ignored. Time complexity:
2111    /// O(d²·n). See also [`Graph::cliques`] for complete subgraphs of any
2112    /// size.
2113    ///
2114    /// Binds [`igraph_list_triangles`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_list_triangles).
2115    ///
2116    /// # Examples
2117    /// ```
2118    /// use igraph::prelude::*;
2119    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
2120    /// let mut t = g.list_triangles().unwrap();
2121    /// t[0].sort();
2122    /// assert_eq!(t, vec![[0, 1, 2]]);
2123    /// ```
2124    pub fn list_triangles(&self) -> Result<Vec<[VertexId; 3]>> {
2125        let mut res = VectorInt::new();
2126        directed_cache_guard(self, || igraph_call!(igraph_list_triangles(self, &mut res)))?;
2127        Ok(res.as_chunks::<3>().0.to_vec())
2128    }
2129
2130    /// Total number of triangles of the graph (`igraph_count_triangles`).
2131    ///
2132    /// Edge directions, multiplicities and self-loops are ignored. Returned
2133    /// as `f64` (to avoid overflow). Time complexity: O(|V|·d²).
2134    ///
2135    /// See also [`Graph::transitivity_undirected`], the global clustering
2136    /// coefficient `3 × triangles / connected triples`.
2137    ///
2138    /// Binds [`igraph_count_triangles`](https://igraph.org/c/html/latest/igraph-Motifs.html#igraph_count_triangles).
2139    ///
2140    /// # Examples
2141    /// ```
2142    /// use igraph::prelude::*;
2143    /// let k4 = Graph::full(4, false, false).unwrap();
2144    /// assert_eq!(k4.count_triangles().unwrap(), 4.0);
2145    /// // Zachary's karate club has 45 triangles.
2146    /// assert_eq!(Graph::famous("Zachary").unwrap().count_triangles().unwrap(), 45.0);
2147    /// ```
2148    pub fn count_triangles(&self) -> Result<f64> {
2149        let mut res = 0.0;
2150        directed_cache_guard(self, || {
2151            igraph_call!(igraph_count_triangles(self, &mut res))
2152        })?;
2153        Ok(res)
2154    }
2155
2156    // ----- graphlets ---------------------------------------------------------
2157
2158    fn isomorphism_check_weights(&self, weights: &[f64]) -> Result<()> {
2159        if weights.len() != self.ecount() {
2160            return Err(Error::invalid(format!(
2161                "weights has length {}, expected {} (one per edge)",
2162                weights.len(),
2163                self.ecount()
2164            )));
2165        }
2166        Ok(())
2167    }
2168
2169    /// The candidate basis of the graphlet decomposition
2170    /// (`igraph_graphlets_candidate_basis`).
2171    ///
2172    /// First step of the graphlet decomposition (Azari Soufiani and Airoldi):
2173    /// the cliques of the graph thresholded at decreasing edge weights, with
2174    /// the highest threshold at which each was found. The graph must be
2175    /// simple; edge directions are ignored; `weights` (one per edge) are
2176    /// mandatory.
2177    ///
2178    /// An edgeless graph has an empty basis.
2179    ///
2180    /// Binds [`igraph_graphlets_candidate_basis`](https://igraph.org/c/html/latest/igraph-Graphlets.html#igraph_graphlets_candidate_basis).
2181    ///
2182    /// # Errors
2183    /// [`ErrorKind::InvalidValue`] if `weights` does not have one entry per
2184    /// edge or the graph is not simple (ignoring directions).
2185    ///
2186    /// # Examples
2187    /// ```
2188    /// use igraph::prelude::*;
2189    /// // A heavy triangle attached to a light edge.
2190    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
2191    /// let basis = g.graphlets_candidate_basis(&[5.0, 5.0, 5.0, 1.0]).unwrap();
2192    /// assert_eq!(basis.cliques.len(), basis.thresholds.len());
2193    /// assert!(basis.cliques.iter().any(|c| c.len() == 3));
2194    /// ```
2195    pub fn graphlets_candidate_basis(&self, weights: &[f64]) -> Result<GraphletBasis> {
2196        self.isomorphism_check_weights(weights)?;
2197        if weights.is_empty() {
2198            // igraph 1.0.0 and 1.0.1 assert (aborting the process) on
2199            // edgeless graphs.
2200            return Ok(GraphletBasis {
2201                cliques: vec![],
2202                thresholds: vec![],
2203            });
2204        }
2205        let w = Vector::view(weights);
2206        let mut cliques = VectorIntList::new();
2207        let mut thresholds = Vector::new();
2208        igraph_call!(igraph_graphlets_candidate_basis(
2209            self,
2210            w.as_ptr(),
2211            &mut cliques,
2212            &mut thresholds
2213        ))?;
2214        Ok(GraphletBasis {
2215            cliques: cliques.to_vecs(),
2216            thresholds: thresholds.into(),
2217        })
2218    }
2219
2220    /// Projects the graph on a graphlet basis (`igraph_graphlets_project`).
2221    ///
2222    /// Second step of the graphlet decomposition: fits a weight (`Mu`) for
2223    /// each clique of `cliques` so that the sum of the cliques, weighted,
2224    /// explains the edge weights, with `niter` iterations of the
2225    /// expectation-maximization algorithm. Each clique of size `n` is
2226    /// normalized by `n (n + 1) / 2`, so that a clique whose edges all have
2227    /// weight `w` and belong to no other clique converges to
2228    /// `w (n - 1) / (n + 1)`. `start` optionally provides the
2229    /// initial weights (one per clique), otherwise all ones are used. The
2230    /// graph need not be the one used to compute the basis, but must have
2231    /// matching vertex ids.
2232    ///
2233    /// Binds [`igraph_graphlets_project`](https://igraph.org/c/html/latest/igraph-Graphlets.html#igraph_graphlets_project).
2234    ///
2235    /// # Errors
2236    /// [`ErrorKind::InvalidValue`] if `weights` or `start` have the wrong
2237    /// length, a clique lists a vertex twice, or the graph is not simple;
2238    /// [`ErrorKind::InvalidVertexId`] if a clique contains an invalid vertex.
2239    ///
2240    /// # Examples
2241    /// ```
2242    /// use igraph::prelude::*;
2243    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
2244    /// let w = [5.0, 5.0, 5.0, 1.0];
2245    /// let mu = g.graphlets_project(&w, &[vec![0, 1, 2], vec![2, 3]], None, 100).unwrap();
2246    /// // Fixed points w (n - 1) / (n + 1): 5 * 2/4 for the triangle, 1 * 1/3 for the edge.
2247    /// assert!((mu[0] - 2.5).abs() < 1e-3 && (mu[1] - 1.0 / 3.0).abs() < 1e-3);
2248    /// ```
2249    pub fn graphlets_project(
2250        &self,
2251        weights: &[f64],
2252        cliques: &[Vec<VertexId>],
2253        start: Option<&[f64]>,
2254        niter: usize,
2255    ) -> Result<Vec<f64>> {
2256        self.isomorphism_check_weights(weights)?;
2257        if let Some(s) = start
2258            && s.len() != cliques.len()
2259        {
2260            return Err(Error::invalid("start must have one weight per clique"));
2261        }
2262        // igraph 1.0.0 and 1.0.1 index internal arrays with the clique
2263        // members unchecked.
2264        let n = self.vcount() as i64;
2265        for (i, c) in cliques.iter().enumerate() {
2266            if let Some(&bad) = c.iter().find(|&&v| !(0..n).contains(&v)) {
2267                return Err(Error::new(
2268                    ErrorKind::InvalidVertexId,
2269                    format!("clique {i} contains the invalid vertex {bad}"),
2270                ));
2271            }
2272            let mut sorted = c.clone();
2273            sorted.sort_unstable();
2274            if sorted.windows(2).any(|w| w[0] == w[1]) {
2275                return Err(Error::invalid(format!(
2276                    "clique {i} contains a vertex twice"
2277                )));
2278            }
2279        }
2280        let w = Vector::view(weights);
2281        let cl = VectorIntList::from(cliques);
2282        let mut mu = start.map_or_else(Vector::new, Vector::from_slice);
2283        igraph_call!(igraph_graphlets_project(
2284            self,
2285            w.as_ptr(),
2286            &cl,
2287            &mut mu,
2288            start.is_some(),
2289            niter as igraph_int_t
2290        ))?;
2291        Ok(mu.into())
2292    }
2293
2294    /// The graphlet decomposition of a weighted graph (`igraph_graphlets`).
2295    ///
2296    /// Models the graph as a union of potentially overlapping dense groups
2297    /// (cliques), each with a weight: runs
2298    /// [`graphlets_candidate_basis`](Self::graphlets_candidate_basis), then
2299    /// [`graphlets_project`](Self::graphlets_project) with `niter`
2300    /// iterations, and sorts the graphlets by decreasing weight. The graph
2301    /// must be simple; edge directions are ignored. An edgeless graph has no
2302    /// graphlets. See also the [`cliques`](crate::cliques) module, e.g.
2303    /// [`Graph::maximal_cliques`], for unweighted dense groups.
2304    ///
2305    /// Binds [`igraph_graphlets`](https://igraph.org/c/html/latest/igraph-Graphlets.html#igraph_graphlets).
2306    ///
2307    /// # Examples
2308    /// ```
2309    /// use igraph::prelude::*;
2310    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
2311    /// let gl = g.graphlets(&[5.0, 5.0, 5.0, 1.0], 1000).unwrap();
2312    /// // The heaviest graphlet is the triangle.
2313    /// let mut top = gl.cliques[0].clone();
2314    /// top.sort();
2315    /// assert_eq!(top, vec![0, 1, 2]);
2316    /// assert!(gl.weights.windows(2).all(|w| w[0] >= w[1]));
2317    /// ```
2318    pub fn graphlets(&self, weights: &[f64], niter: usize) -> Result<Graphlets> {
2319        self.isomorphism_check_weights(weights)?;
2320        if weights.is_empty() {
2321            // igraph 1.0.0 and 1.0.1 assert (aborting the process) on
2322            // edgeless graphs.
2323            return Ok(Graphlets {
2324                cliques: vec![],
2325                weights: vec![],
2326            });
2327        }
2328        let w = Vector::view(weights);
2329        let mut cliques = VectorIntList::new();
2330        let mut mu = Vector::new();
2331        igraph_call!(igraph_graphlets(
2332            self,
2333            w.as_ptr(),
2334            &mut cliques,
2335            &mut mu,
2336            niter as igraph_int_t
2337        ))?;
2338        Ok(Graphlets {
2339            cliques: cliques.to_vecs(),
2340            weights: mu.into(),
2341        })
2342    }
2343}