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}