Skip to main content

igraph/
layout.rs

1//! Graph layouts: placing vertices in the plane or in 3D space (`igraph_layout.h`).
2//!
3//! A *layout* assigns coordinates to every vertex of a graph, typically to
4//! draw it. Every layout function of this module returns a [`Matrix`] with
5//! **one row per vertex** (row `i` holds the coordinates of vertex `i`) and
6//! two columns (`x`, `y`) for 2D layouts, or three columns (`x`, `y`, `z`) for
7//! the `*_3d` variants. Use [`Matrix::row`], [`Matrix::column`] or
8//! [`Matrix::to_rows`] to read them.
9//!
10//! This module binds the whole of the C header `igraph_layout.h`: simple
11//! geometric layouts, force-directed layouts (with tunable *options structs*
12//! whose [`Default`] follows the recommendations of the igraph documentation),
13//! tree and layered layouts, dimensionality reduction layouts (MDS, UMAP) and
14//! a few helpers.
15//!
16//! ```
17//! use igraph::prelude::*;
18//! use igraph::layout::FruchtermanReingoldOptions;
19//!
20//! // A 6-cycle.
21//! let g = Graph::ring(6, false, false, true).unwrap();
22//!
23//! // Deterministic geometric layout: all vertices on the unit circle.
24//! let circle = g.layout_circle(..).unwrap();
25//! assert_eq!(circle.shape(), (6, 2));
26//! for p in circle.rows() {
27//!     assert!((p[0].hypot(p[1]) - 1.0).abs() < 1e-12);
28//! }
29//!
30//! // Randomized force-directed layout, made reproducible by seeding the RNG.
31//! let opts = FruchtermanReingoldOptions::default();
32//! rng::seed(42).unwrap();
33//! let fr = g.layout_fruchterman_reingold(&opts).unwrap();
34//! rng::seed(42).unwrap();
35//! assert_eq!(g.layout_fruchterman_reingold(&opts).unwrap(), fr);
36//! assert_eq!(fr.shape(), (6, 2));
37//! ```
38//!
39//! # Provided functionality
40//!
41//! | Family | 2D | 3D |
42//! |--------|----|----|
43//! | Random | [`layout_random`](Graph::layout_random) | [`layout_random_3d`](Graph::layout_random_3d) |
44//! | Regular shapes | [`layout_circle`](Graph::layout_circle), [`layout_star`](Graph::layout_star), [`layout_grid`](Graph::layout_grid) | [`layout_sphere`](Graph::layout_sphere), [`layout_grid_3d`](Graph::layout_grid_3d) |
45//! | Fruchterman–Reingold | [`layout_fruchterman_reingold`](Graph::layout_fruchterman_reingold) | [`layout_fruchterman_reingold_3d`](Graph::layout_fruchterman_reingold_3d) |
46//! | Kamada–Kawai | [`layout_kamada_kawai`](Graph::layout_kamada_kawai) | [`layout_kamada_kawai_3d`](Graph::layout_kamada_kawai_3d) |
47//! | DrL | [`layout_drl`](Graph::layout_drl) | [`layout_drl_3d`](Graph::layout_drl_3d) |
48//! | UMAP | [`layout_umap`](Graph::layout_umap), [`layout_umap_compute_weights`](Graph::layout_umap_compute_weights) | [`layout_umap_3d`](Graph::layout_umap_3d) |
49//! | Other force-directed | [`layout_lgl`](Graph::layout_lgl), [`layout_graphopt`](Graph::layout_graphopt), [`layout_gem`](Graph::layout_gem), [`layout_davidson_harel`](Graph::layout_davidson_harel) | |
50//! | Trees | [`layout_reingold_tilford`](Graph::layout_reingold_tilford), [`layout_reingold_tilford_circular`](Graph::layout_reingold_tilford_circular), [`roots_for_tree_layout`](Graph::roots_for_tree_layout) | |
51//! | Layered / bipartite | [`layout_sugiyama`](Graph::layout_sugiyama), [`layout_bipartite`](Graph::layout_bipartite) | |
52//! | Distance based | [`layout_mds`](Graph::layout_mds) (any dimension) | |
53//! | Helpers | [`layout_align`](Graph::layout_align) (any dimension), [`layout_merge_dla`] | |
54//!
55//! # Starting positions and reproducibility
56//!
57//! Iterative layouts (Fruchterman–Reingold, Kamada–Kawai, DrL, UMAP, graphopt,
58//! GEM, Davidson–Harel) can start from a given layout: set the `initial` field
59//! of their options (or pass it explicitly) to refine an existing layout,
60//! otherwise they start from a random (or, for Kamada–Kawai, circular)
61//! configuration. Random choices (random starts, LGL's random root, GEM's
62//! vertex order, DLA random walks, ...) use the calling thread's default
63//! random number generator. Every thread has its own, so
64//! [`rng::seed`](crate::rng::seed) makes the layouts computed afterwards in
65//! that thread reproducible, whatever other threads do. To leave the
66//! thread's default stream untouched, run the layout inside
67//! [`Rng::scoped`](crate::rng::Rng::scoped) with a generator of your own:
68//!
69//! ```
70//! use igraph::prelude::*;
71//! use igraph::layout::GemOptions;
72//!
73//! let g = Graph::ring(4, false, false, true).unwrap();
74//! let run = || Rng::new(RngType::Pcg64, 42).unwrap().scoped(|| g.layout_gem(&GemOptions::default()));
75//! assert_eq!(run().unwrap(), run().unwrap());
76//! ```
77//!
78//! # See also
79//!
80//! - Graphs to lay out: [`Graph::famous`], [`Graph::ring`],
81//!   [`Graph::kary_tree`], [`Graph::square_lattice`] (constructors module).
82//! - Disconnected graphs: [`Graph::decompose`] splits a graph into its
83//!   components, which can be laid out separately and merged with
84//!   [`layout_merge_dla`].
85//! - Inputs of some layouts: [`Graph::distances`] (the default distance
86//!   matrix of [`layout_mds`](Graph::layout_mds)), [`Graph::bipartite_types`]
87//!   (the `types` of [`layout_bipartite`](Graph::layout_bipartite)),
88//!   [`Graph::feedback_arc_set`] (how [`layout_sugiyama`](Graph::layout_sugiyama)
89//!   breaks cycles), [`Graph::nearest_neighbor_graph`] (a typical input of
90//!   [`layout_umap`](Graph::layout_umap)).
91//! - Using a layout: [`Graph::spatial_edge_lengths`] measures the edges of a
92//!   drawing, [`convex_hull_2d`](crate::misc::convex_hull_2d) its outline, and
93//!   community detection (e.g. [`Graph::community_multilevel`]) gives vertex
94//!   colors.
95
96use crate::{
97    constants::{LayoutGrid, NeighborMode},
98    error::{Error, Result},
99    ffi::*,
100    graph::{Graph, VertexId},
101    igraph_call,
102    list::MatrixList,
103    matrix::Matrix,
104    selector::VertexSelector,
105    vector::{Vector, VectorBool, VectorInt, View},
106};
107use std::{ffi::c_void, mem::MaybeUninit, ptr};
108
109// ---------------------------------------------------------------------------
110// Private helpers
111// ---------------------------------------------------------------------------
112
113/// Zero-copy view of an optional real slice.
114fn opt_view(v: Option<&[f64]>) -> Option<View<'_, Vector>> {
115    v.map(Vector::view)
116}
117
118/// Raw pointer to an optional view, `NULL` when absent.
119fn opt_ptr<V>(v: &Option<View<'_, V>>) -> *const V {
120    v.as_ref().map_or(ptr::null(), |v| v.as_ptr())
121}
122
123/// Checks that an optional per-item vector has the expected length.
124fn check_len<T>(name: &str, v: Option<&[T]>, expected: usize) -> Result<()> {
125    match v {
126        Some(v) if v.len() != expected => Err(Error::invalid(format!(
127            "`{name}` has length {}, expected {expected}",
128            v.len()
129        ))),
130        _ => Ok(()),
131    }
132}
133
134/// Builds the result matrix, copying the starting positions if given.
135///
136/// Returns `(matrix, use_seed)`.
137fn start_matrix(initial: Option<&Matrix>, n: usize, dim: usize) -> Result<(Matrix, bool)> {
138    match initial {
139        Some(m) if m.shape() != (n, dim) => Err(Error::invalid(format!(
140            "the initial layout is {}x{}, expected {n}x{dim}",
141            m.nrow(),
142            m.ncol()
143        ))),
144        Some(m) if !m.as_slice().iter().all(|x| x.is_finite()) => Err(Error::invalid(
145            "the initial layout contains NaN or infinite coordinates",
146        )),
147        Some(m) => Ok((m.clone(), true)),
148        None => Ok((Matrix::new(), false)),
149    }
150}
151
152/// Checks that an optional real parameter is finite and positive.
153fn check_positive(name: &str, x: f64) -> Result<()> {
154    if x.is_finite() && x > 0.0 {
155        Ok(())
156    } else {
157        Err(Error::invalid(format!(
158            "`{name}` must be finite and positive, got {x}"
159        )))
160    }
161}
162
163/// Largest number of grid cells [`Graph::layout_lgl`] accepts regardless of
164/// the graph size (the limit is `max(LGL_MAX_CELLS, 4 |V|)`; the default
165/// parameters give about `1.3 |V|` cells).
166const LGL_MAX_CELLS: i64 = 1 << 28;
167
168/// Largest vertex count (graph plus the extra `rootlevel` vertices) accepted
169/// by the Reingold–Tilford layouts.
170const RT_MAX_VERTICES: igraph_int_t = 1 << 40;
171
172/// Invalidates the property cache of a graph when dropped.
173struct CacheReset<'a>(Option<&'a Graph>);
174
175impl Drop for CacheReset<'_> {
176    fn drop(&mut self) {
177        if let Some(g) = self.0 {
178            g.invalidate_cache();
179        }
180    }
181}
182
183/// Largest edge weight accepted by [`Graph::layout_drl`]. DrL computes its
184/// energies in single precision: in igraph 1.0.1 weights around `1e37`
185/// already overflow them into NaN positions (converted to integers by its
186/// density grid, which is undefined behavior); `1e20` leaves a wide margin.
187const DRL_MAX_WEIGHT: f64 = 1e20;
188
189/// Converts a count to `igraph_int_t`, saturating at `igraph_int_t::MAX`
190/// (a plain `as` cast would turn huge counts into negative values, which
191/// igraph interprets differently, e.g. as "automatic").
192fn int(x: usize) -> igraph_int_t {
193    igraph_int_t::try_from(x).unwrap_or(igraph_int_t::MAX)
194}
195
196/// Vertex count as an `f64`, at least one (for defaults that must be positive).
197fn nf(g: &Graph) -> f64 {
198    g.vcount().max(1) as f64
199}
200
201/// Checks that all the ids are valid vertex ids.
202fn check_vertices(name: &str, ids: &[VertexId], n: usize) -> Result<()> {
203    match ids.iter().find(|&&v| v < 0 || v as usize >= n) {
204        Some(v) => Err(Error::new(
205            crate::error::ErrorKind::InvalidVertexId,
206            format!("`{name}` contains {v}, which is not a vertex id (the graph has {n} vertices)"),
207        )),
208        None => Ok(()),
209    }
210}
211
212/// Coordinate bounds of 2D/3D force-directed layouts, as raw views.
213struct Bounds<'a> {
214    views: [Option<View<'a, Vector>>; 6],
215}
216
217impl<'a> Bounds<'a> {
218    fn new(g: &Graph, bounds: [Option<&'a [f64]>; 6]) -> Result<Self> {
219        const NAMES: [&str; 6] = ["minx", "maxx", "miny", "maxy", "minz", "maxz"];
220        let n = g.vcount();
221        for (i, (name, b)) in NAMES.iter().zip(bounds.iter()).enumerate() {
222            check_len(name, *b, n)?;
223            // A lower bound of `-inf` or an upper bound of `+inf` means "no
224            // bound" and is fine. igraph requires the caller to rule out NaN,
225            // and its random initial placement fails on a lower bound of
226            // `+inf` or an upper bound of `-inf` with an error that the
227            // force-directed layouts ignore, going on with a wrongly sized
228            // matrix (heap corruption).
229            let forbidden = if i % 2 == 0 {
230                f64::INFINITY
231            } else {
232                f64::NEG_INFINITY
233            };
234            if b.is_some_and(|b| b.iter().any(|&x| x.is_nan() || x == forbidden)) {
235                return Err(Error::invalid(format!(
236                    "`{name}` contains NaN or {forbidden}"
237                )));
238            }
239        }
240        Ok(Self {
241            views: bounds.map(opt_view),
242        })
243    }
244
245    fn ptr(&self, i: usize) -> *const Vector {
246        opt_ptr(&self.views[i])
247    }
248}
249
250// ---------------------------------------------------------------------------
251// Enums
252// ---------------------------------------------------------------------------
253
254crate::ffi_enum! {
255    /// Heuristic used by [`Graph::roots_for_tree_layout`] to choose among
256    /// several possible roots (`igraph_root_choice_t`).
257    pub enum RootChoice: igraph_root_choice_t {
258        /// Prefer the vertices with the highest degree (out- or in-degree in
259        /// directed mode). Fast even on large graphs.
260        Degree = igraph_root_choice_t_IGRAPH_ROOT_CHOICE_DEGREE,
261        /// Prefer the vertices with the lowest eccentricity: usually gives
262        /// "wide and shallow" trees, but takes quadratic time.
263        Eccentricity = igraph_root_choice_t_IGRAPH_ROOT_CHOICE_ECCENTRICITY,
264    }
265}
266
267crate::ffi_enum! {
268    /// Predefined parameter templates for the DrL layout
269    /// (`igraph_layout_drl_default_t`), see [`DrlOptions::from_template`].
270    pub enum DrlTemplate: igraph_layout_drl_default_t {
271        /// The default parameters.
272        Default = igraph_layout_drl_default_t_IGRAPH_LAYOUT_DRL_DEFAULT,
273        /// Slightly modified parameters giving a coarser layout.
274        Coarsen = igraph_layout_drl_default_t_IGRAPH_LAYOUT_DRL_COARSEN,
275        /// An even coarser layout.
276        Coarsest = igraph_layout_drl_default_t_IGRAPH_LAYOUT_DRL_COARSEST,
277        /// Refine an already computed layout.
278        Refine = igraph_layout_drl_default_t_IGRAPH_LAYOUT_DRL_REFINE,
279        /// Finalize an already refined layout.
280        Final = igraph_layout_drl_default_t_IGRAPH_LAYOUT_DRL_FINAL,
281    }
282}
283
284// ---------------------------------------------------------------------------
285// Options structs
286// ---------------------------------------------------------------------------
287
288/// Parameters of the Fruchterman–Reingold layouts
289/// ([`Graph::layout_fruchterman_reingold`] and
290/// [`Graph::layout_fruchterman_reingold_3d`]).
291///
292/// The [`Default`] follows the igraph documentation: 500 iterations, a start
293/// temperature of `sqrt(n) / 10`, automatic grid selection, unit weights, a
294/// random start and no coordinate bounds.
295///
296/// ```
297/// use igraph::layout::FruchtermanReingoldOptions;
298/// let weights = [1.0, 2.0, 3.0];
299/// let opts = FruchtermanReingoldOptions { niter: 1000, ..Default::default() }
300///     .with_weights(&weights);
301/// assert_eq!(opts.niter, 1000);
302/// ```
303#[derive(Debug, Clone, PartialEq)]
304pub struct FruchtermanReingoldOptions<'a> {
305    /// Number of iterations (default 500).
306    pub niter: usize,
307    /// Start temperature: the maximum movement of a vertex along one axis in
308    /// one step; it decreases linearly to zero. `None` means `sqrt(n) / 10`.
309    pub start_temp: Option<f64>,
310    /// Whether to use the faster, less accurate grid-based variant (2D only;
311    /// [`LayoutGrid::AutoGrid`], the default, uses it above 1000 vertices).
312    pub grid: LayoutGrid,
313    /// Positive edge weights multiplying the attraction along edges (higher
314    /// weight: closer vertices; an isolated edge of weight `w` has length
315    /// `w^(-1/3)`). `None` means unit weights.
316    pub weights: Option<&'a [f64]>,
317    /// Starting positions (`n` × 2, or `n` × 3 for the 3D version); `None`
318    /// starts from a random layout.
319    pub initial: Option<&'a Matrix>,
320    /// Per-vertex minimum `x` coordinate (`-inf` means no bound).
321    pub minx: Option<&'a [f64]>,
322    /// Per-vertex maximum `x` coordinate (`+inf` means no bound).
323    pub maxx: Option<&'a [f64]>,
324    /// Per-vertex minimum `y` coordinate (`-inf` means no bound).
325    pub miny: Option<&'a [f64]>,
326    /// Per-vertex maximum `y` coordinate (`+inf` means no bound).
327    pub maxy: Option<&'a [f64]>,
328    /// Per-vertex minimum `z` coordinate (3D only).
329    pub minz: Option<&'a [f64]>,
330    /// Per-vertex maximum `z` coordinate (3D only).
331    pub maxz: Option<&'a [f64]>,
332}
333
334impl Default for FruchtermanReingoldOptions<'_> {
335    fn default() -> Self {
336        Self {
337            niter: 500,
338            start_temp: None,
339            grid: LayoutGrid::AutoGrid,
340            weights: None,
341            initial: None,
342            minx: None,
343            maxx: None,
344            miny: None,
345            maxy: None,
346            minz: None,
347            maxz: None,
348        }
349    }
350}
351
352impl<'a> FruchtermanReingoldOptions<'a> {
353    /// Sets the edge weights.
354    pub fn with_weights(mut self, weights: &'a [f64]) -> Self {
355        self.weights = Some(weights);
356        self
357    }
358
359    /// Sets the starting positions.
360    pub fn with_initial(mut self, initial: &'a Matrix) -> Self {
361        self.initial = Some(initial);
362        self
363    }
364
365    /// Sets the number of iterations.
366    pub fn with_niter(mut self, niter: usize) -> Self {
367        self.niter = niter;
368        self
369    }
370}
371
372/// Parameters of the Kamada–Kawai layouts ([`Graph::layout_kamada_kawai`] and
373/// [`Graph::layout_kamada_kawai_3d`]).
374///
375/// The [`Default`] uses `50 n` iterations (the C documentation asks for at
376/// least `10 n`; `50 n` is the default of the R and Python interfaces),
377/// `epsilon = 0` (always run all the iterations), `kkconst = n` (as the C
378/// documentation recommends), unit edge lengths, a circular (spherical in
379/// 3D) start and no bounds.
380#[derive(Debug, Clone, PartialEq, Default)]
381pub struct KamadaKawaiOptions<'a> {
382    /// Maximum number of iterations; `None` means `50 n`.
383    pub maxiter: Option<usize>,
384    /// Stop when the maximum energy change falls below this value (`0.0`,
385    /// the default, performs all `maxiter` iterations).
386    pub epsilon: f64,
387    /// The vertex attraction constant; `None` means the number of vertices.
388    pub kkconst: Option<f64>,
389    /// Positive edge *lengths* used in the shortest path computation
390    /// (higher weight: farther vertices). `None` means unit lengths.
391    pub weights: Option<&'a [f64]>,
392    /// Starting positions (`n` × 2, or `n` × 3 for the 3D version). `None`
393    /// starts from a circle (sphere) of radius `0.36 sqrt(n)`, or from a
394    /// random layout when some bounds are given.
395    pub initial: Option<&'a Matrix>,
396    /// Per-vertex minimum `x` coordinate (`-inf` means no bound).
397    pub minx: Option<&'a [f64]>,
398    /// Per-vertex maximum `x` coordinate (`+inf` means no bound).
399    pub maxx: Option<&'a [f64]>,
400    /// Per-vertex minimum `y` coordinate (`-inf` means no bound).
401    pub miny: Option<&'a [f64]>,
402    /// Per-vertex maximum `y` coordinate (`+inf` means no bound).
403    pub maxy: Option<&'a [f64]>,
404    /// Per-vertex minimum `z` coordinate (3D only).
405    pub minz: Option<&'a [f64]>,
406    /// Per-vertex maximum `z` coordinate (3D only).
407    pub maxz: Option<&'a [f64]>,
408}
409
410impl<'a> KamadaKawaiOptions<'a> {
411    /// Sets the edge lengths.
412    pub fn with_weights(mut self, weights: &'a [f64]) -> Self {
413        self.weights = Some(weights);
414        self
415    }
416
417    /// Sets the starting positions.
418    pub fn with_initial(mut self, initial: &'a Matrix) -> Self {
419        self.initial = Some(initial);
420        self
421    }
422}
423
424/// Parameters of the Large Graph Layout ([`Graph::layout_lgl`]).
425///
426/// The [`Default`] follows the igraph documentation (`n` is the number of
427/// vertices): 150 iterations, `maxdelta = n`, `area = n²`, `coolexp = 1.5`,
428/// `repulserad = area · n`, `cellsize = area^(1/4)` and a random root.
429#[derive(Debug, Clone, PartialEq)]
430pub struct LglOptions {
431    /// Maximum number of cooling iterations per layout step (default 150).
432    pub maxiter: usize,
433    /// Maximum movement of a vertex in one iteration; `None` means `n`.
434    pub maxdelta: Option<f64>,
435    /// Area of the square the vertices are placed on; `None` means `n²`.
436    pub area: Option<f64>,
437    /// The cooling exponent (default 1.5).
438    pub coolexp: f64,
439    /// Radius at which repulsion cancels attraction; `None` means `area · n`.
440    pub repulserad: Option<f64>,
441    /// Side of the grid cells; `None` means the fourth root of `area`.
442    pub cellsize: Option<f64>,
443    /// Root vertex, placed first; `None` picks a random vertex.
444    pub root: Option<VertexId>,
445}
446
447impl Default for LglOptions {
448    fn default() -> Self {
449        Self {
450            maxiter: 150,
451            maxdelta: None,
452            area: None,
453            coolexp: 1.5,
454            repulserad: None,
455            cellsize: None,
456            root: None,
457        }
458    }
459}
460
461/// Parameters of the Sugiyama layered layout ([`Graph::layout_sugiyama`]).
462///
463/// The [`Default`] uses automatic layering, unit gaps, 100 crossing
464/// minimization iterations and unit weights.
465#[derive(Debug, Clone, PartialEq)]
466pub struct SugiyamaOptions<'a> {
467    /// Non-negative layer index of every vertex. Empty layers are skipped
468    /// when minimizing crossings, but keep their room on the `y` axis.
469    /// `None` lets igraph compute a layering, after breaking cycles.
470    pub layers: Option<&'a [i64]>,
471    /// Preferred minimum horizontal gap between vertices of a layer (default 1).
472    pub hgap: f64,
473    /// Distance between consecutive layers (default 1).
474    pub vgap: f64,
475    /// Maximum number of iterations of the crossing minimization (default 100).
476    pub maxiter: usize,
477    /// Edge weights, used only to break cycles: igraph tends to reverse
478    /// edges with smaller weights.
479    pub weights: Option<&'a [f64]>,
480}
481
482impl Default for SugiyamaOptions<'_> {
483    fn default() -> Self {
484        Self {
485            layers: None,
486            hgap: 1.0,
487            vgap: 1.0,
488            maxiter: 100,
489            weights: None,
490        }
491    }
492}
493
494impl<'a> SugiyamaOptions<'a> {
495    /// Sets the layer of each vertex.
496    pub fn with_layers(mut self, layers: &'a [i64]) -> Self {
497        self.layers = Some(layers);
498        self
499    }
500}
501
502/// Result of [`Graph::layout_sugiyama`].
503#[derive(Debug, Clone, PartialEq)]
504pub struct SugiyamaLayout {
505    /// The vertex coordinates, `n` × 2; the `y` coordinate of a vertex is
506    /// its layer index times `vgap`.
507    pub coords: Matrix,
508    /// One matrix per edge with the extra *control points* (one per row)
509    /// the edge must pass through, from its source to its target; edges
510    /// spanning adjacent layers have a 0 × 2 matrix.
511    pub routing: Vec<Matrix>,
512}
513
514/// Parameters of the graphopt layout ([`Graph::layout_graphopt`]).
515///
516/// The [`Default`] uses the original graphopt defaults: 500 iterations,
517/// node charge 0.001, node mass 30, spring length 0, spring constant 1,
518/// maximum movement 5, and a random start.
519#[derive(Debug, Clone, PartialEq)]
520pub struct GraphoptOptions<'a> {
521    /// Number of iterations (default 500).
522    pub niter: usize,
523    /// Charge of the vertices, for the electric repulsion (default 0.001).
524    /// With zero charge each iteration is only `O(|E|)`.
525    pub node_charge: f64,
526    /// Mass of the vertices, for the spring forces (default 30).
527    pub node_mass: f64,
528    /// Rest length of the springs (default 0).
529    pub spring_length: f64,
530    /// Spring constant (default 1).
531    pub spring_constant: f64,
532    /// Maximum movement along an axis in a single step (default 5).
533    pub max_sa_movement: f64,
534    /// Starting positions (`n` × 2); `None` starts from a random layout.
535    pub initial: Option<&'a Matrix>,
536}
537
538impl Default for GraphoptOptions<'_> {
539    fn default() -> Self {
540        Self {
541            niter: 500,
542            node_charge: 0.001,
543            node_mass: 30.0,
544            spring_length: 0.0,
545            spring_constant: 1.0,
546            max_sa_movement: 5.0,
547            initial: None,
548        }
549    }
550}
551
552/// Parameters of the UMAP layouts ([`Graph::layout_umap`] and
553/// [`Graph::layout_umap_3d`]).
554///
555/// The [`Default`] uses `min_dist = 0.01`, 500 epochs, unit distances and a
556/// random start.
557#[derive(Debug, Clone, PartialEq)]
558pub struct UmapOptions<'a> {
559    /// Distance (or, with `distances_are_weights`, weight) of every edge;
560    /// `None` means all edges have the same distance.
561    pub distances: Option<&'a [f64]>,
562    /// How close two unconnected vertices may get before repelling each
563    /// other; non-negative, typically in `[0, 1]` (default 0.01).
564    pub min_dist: f64,
565    /// Number of epochs of stochastic gradient descent, typically in
566    /// `[30, 500]` (default 500).
567    pub epochs: usize,
568    /// Whether `distances` already holds UMAP weights (e.g. from
569    /// [`Graph::layout_umap_compute_weights`]).
570    pub distances_are_weights: bool,
571    /// Starting positions (`n` × 2, or `n` × 3 for the 3D version); `None`
572    /// starts from a random layout.
573    pub initial: Option<&'a Matrix>,
574}
575
576impl Default for UmapOptions<'_> {
577    fn default() -> Self {
578        Self {
579            distances: None,
580            min_dist: 0.01,
581            epochs: 500,
582            distances_are_weights: false,
583            initial: None,
584        }
585    }
586}
587
588/// Parameters of the GEM layout ([`Graph::layout_gem`]).
589///
590/// The [`Default`] follows the igraph documentation (`n` is the number of
591/// vertices): `40 n²` iterations, `temp_max = n`, `temp_min = 0.1`,
592/// `temp_init = sqrt(n)` and a random start.
593#[derive(Debug, Clone, PartialEq)]
594pub struct GemOptions<'a> {
595    /// Maximum number of iterations (a single vertex update counts as one);
596    /// `None` means `40 n²`.
597    pub maxiter: Option<usize>,
598    /// Maximum local temperature; `None` means `n`.
599    pub temp_max: Option<f64>,
600    /// Global temperature at which the algorithm stops (default 0.1).
601    pub temp_min: f64,
602    /// Initial local temperature; `None` means `sqrt(n)`.
603    pub temp_init: Option<f64>,
604    /// Starting positions (`n` × 2); `None` starts from a random layout.
605    pub initial: Option<&'a Matrix>,
606}
607
608impl Default for GemOptions<'_> {
609    fn default() -> Self {
610        Self {
611            maxiter: None,
612            temp_max: None,
613            temp_min: 0.1,
614            temp_init: None,
615            initial: None,
616        }
617    }
618}
619
620/// Parameters of the Davidson–Harel layout ([`Graph::layout_davidson_harel`]).
621///
622/// The [`Default`] follows the igraph documentation, where `d` is the density
623/// of the graph (edge directions ignored): 10 annealing iterations,
624/// `max(10, log2 n)` fine tuning iterations, cooling factor 0.75, node
625/// distance weight 1, border weight 0, edge length weight `d / 10`, edge
626/// crossing weight `1 - sqrt(d)`, node-edge distance weight `(1 - d) / 5`.
627#[derive(Debug, Clone, PartialEq)]
628pub struct DavidsonHarelOptions<'a> {
629    /// Number of annealing iterations (default 10).
630    pub maxiter: usize,
631    /// Number of fine tuning iterations; `None` means `max(10, log2 n)`.
632    pub fineiter: Option<usize>,
633    /// Cooling factor, in `(0, 1)` (default 0.75).
634    pub cool_fact: f64,
635    /// Weight of the node-node distance energy term (default 1).
636    pub weight_node_dist: f64,
637    /// Weight of the distance-from-border term (default 0: vertices may sit
638    /// on the border).
639    pub weight_border: f64,
640    /// Weight of the edge length term; `None` means density / 10.
641    pub weight_edge_lengths: Option<f64>,
642    /// Weight of the edge crossing term; `None` means `1 - sqrt(density)`.
643    pub weight_edge_crossings: Option<f64>,
644    /// Weight of the node-edge distance term; `None` means `(1 - density) / 5`.
645    pub weight_node_edge_dist: Option<f64>,
646    /// Starting positions (`n` × 2); `None` starts from a random layout.
647    pub initial: Option<&'a Matrix>,
648}
649
650impl Default for DavidsonHarelOptions<'_> {
651    fn default() -> Self {
652        Self {
653            maxiter: 10,
654            fineiter: None,
655            cool_fact: 0.75,
656            weight_node_dist: 1.0,
657            weight_border: 0.0,
658            weight_edge_lengths: None,
659            weight_edge_crossings: None,
660            weight_node_edge_dist: None,
661            initial: None,
662        }
663    }
664}
665
666/// Parameters of the DrL layout (`igraph_layout_drl_options_t`), used by
667/// [`Graph::layout_drl`] and [`Graph::layout_drl_3d`].
668///
669/// This is the C struct itself: start from a [`DrlTemplate`] with
670/// [`DrlOptions::from_template`] (or [`Default`], which is
671/// [`DrlTemplate::Default`]) and adjust the public fields. The algorithm runs
672/// through six phases (*init*, *liquid*, *expansion*, *cooldown*, *crunch*,
673/// *simmer*), each with its number of iterations, start temperature,
674/// attraction and (non-negative) damping multiplier; `edge_cut` in `[0, 1]`
675/// controls how aggressively stressed edges are cut in the late phases
676/// (default 32/40).
677///
678/// ```
679/// use igraph::layout::{DrlOptions, DrlTemplate};
680/// let mut opts = DrlOptions::from_template(DrlTemplate::Coarsest);
681/// assert_eq!(opts.crunch_iterations, 200);
682/// opts.edge_cut = 0.0; // no edge cutting
683/// assert_eq!(DrlOptions::default().liquid_iterations, 200);
684/// ```
685pub type DrlOptions = igraph_layout_drl_options_t;
686
687impl igraph_layout_drl_options_t {
688    /// Parameters initialized from a predefined template
689    /// ([`igraph_layout_drl_options_init`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_drl_options_init)).
690    pub fn from_template(template: DrlTemplate) -> Self {
691        let mut raw = MaybeUninit::<Self>::zeroed();
692        // Cannot fail: every template is valid.
693        igraph_call!(igraph_layout_drl_options_init(
694            raw.as_mut_ptr(),
695            template.into()
696        ))
697        .expect("igraph_layout_drl_options_init failed on a valid template");
698        unsafe { raw.assume_init() }
699    }
700}
701
702impl Default for igraph_layout_drl_options_t {
703    fn default() -> Self {
704        Self::from_template(DrlTemplate::Default)
705    }
706}
707
708// A plain-old-data struct of numbers: copying it is trivially safe.
709impl Copy for igraph_layout_drl_options_t {}
710
711impl Clone for igraph_layout_drl_options_t {
712    fn clone(&self) -> Self {
713        *self
714    }
715}
716
717// ---------------------------------------------------------------------------
718// Layouts
719// ---------------------------------------------------------------------------
720
721impl igraph_t {
722    /// Places the vertices uniformly at random in the square `[-1, 1]²`.
723    ///
724    /// Uses the calling thread's default random number generator (see
725    /// [`rng::seed`](crate::rng::seed)). Time complexity: `O(|V|)`.
726    /// Binds [`igraph_layout_random`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_random).
727    ///
728    /// # Examples
729    ///
730    /// ```
731    /// use igraph::prelude::*;
732    /// let g = Graph::new(10, false);
733    /// rng::seed(1).unwrap();
734    /// let l = g.layout_random().unwrap();
735    /// assert_eq!(l.shape(), (10, 2));
736    /// assert!(l.as_slice().iter().all(|c| (-1.0..=1.0).contains(c)));
737    /// rng::seed(1).unwrap();
738    /// assert_eq!(g.layout_random().unwrap(), l);
739    /// ```
740    pub fn layout_random(&self) -> Result<Matrix> {
741        let mut res = Matrix::new();
742        igraph_call!(igraph_layout_random(self, &mut res))?;
743        Ok(res)
744    }
745
746    /// Places the vertices uniformly at random in the cube `[-1, 1]³`.
747    ///
748    /// The 3D version of [`layout_random`](Graph::layout_random). Time
749    /// complexity: `O(|V|)`.
750    /// Binds [`igraph_layout_random_3d`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_random_3d).
751    pub fn layout_random_3d(&self) -> Result<Matrix> {
752        let mut res = Matrix::new();
753        igraph_call!(igraph_layout_random_3d(self, &mut res))?;
754        Ok(res)
755    }
756
757    /// Places the vertices uniformly on the unit circle, in the given order.
758    ///
759    /// The `k`-th vertex of `order` (out of `m` selected vertices) is placed
760    /// at angle `2πk/m`; vertices not in `order` are placed at the origin.
761    /// Pass `..` to place all vertices in increasing id order. This is the
762    /// natural drawing of [`Graph::ring`]. Time complexity: `O(|V|)`.
763    /// Binds [`igraph_layout_circle`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_circle).
764    ///
765    /// # Errors
766    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) if
767    /// `order` contains invalid vertices.
768    ///
769    /// # Examples
770    ///
771    /// ```
772    /// use igraph::prelude::*;
773    /// let g = Graph::new(4, false);
774    /// let l = g.layout_circle(..).unwrap();
775    /// assert!((l[(1, 0)] - 0.0).abs() < 1e-12 && (l[(1, 1)] - 1.0).abs() < 1e-12);
776    /// // Only two vertices on the circle, the others at the origin.
777    /// let l = g.layout_circle(&[3, 1]).unwrap();
778    /// assert_eq!(l.row(3), vec![1.0, 0.0]);
779    /// assert_eq!(l.row(0), vec![0.0, 0.0]);
780    /// ```
781    pub fn layout_circle<'a>(&self, order: impl Into<VertexSelector<'a>>) -> Result<Matrix> {
782        let vs = order.into().to_raw()?;
783        let mut res = Matrix::new();
784        igraph_call!(igraph_layout_circle(self, &mut res, vs.get()))?;
785        Ok(res)
786    }
787
788    /// Star-like layout: `center` at the origin, the other vertices evenly
789    /// spaced on the unit circle.
790    ///
791    /// The edges are ignored. The non-center vertices are placed in the
792    /// order given by `order`, which must be a permutation of *all* the
793    /// vertices (including the center), or in increasing id order if `None`;
794    /// the first one is at angle zero. `center` is ignored for the null
795    /// graph. Time complexity: `O(|V|)`. This is the natural drawing of
796    /// [`Graph::star`].
797    /// Binds [`igraph_layout_star`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_star).
798    ///
799    /// # Errors
800    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
801    /// `center` is not a vertex, or `order` is not a permutation of the
802    /// vertices. (igraph itself only checks the length and the range of
803    /// `order`: with a repeated vertex it would leave the rows of the missing
804    /// vertices uninitialized, so this wrapper rejects such orders.)
805    ///
806    /// # Examples
807    ///
808    /// ```
809    /// use igraph::prelude::*;
810    /// let g = Graph::star(5, StarMode::Undirected, 2).unwrap();
811    /// let l = g.layout_star(2, None).unwrap();
812    /// assert_eq!(l.row(2), vec![0.0, 0.0]);
813    /// assert_eq!(l.row(0), vec![1.0, 0.0]);
814    /// ```
815    pub fn layout_star(&self, center: VertexId, order: Option<&[VertexId]>) -> Result<Matrix> {
816        let n = self.vcount();
817        check_len("order", order, n)?;
818        if let Some(order) = order {
819            // igraph does not initialize the rows of vertices missing from
820            // `order`: insist on a permutation so no garbage is ever read.
821            let mut seen = vec![false; n];
822            for &v in order {
823                match usize::try_from(v).ok().and_then(|i| seen.get_mut(i)) {
824                    Some(s) if !*s => *s = true,
825                    _ => {
826                        return Err(Error::invalid(format!(
827                            "`order` must be a permutation of the {n} vertices, \
828                             but {v} is out of range or repeated"
829                        )));
830                    }
831                }
832            }
833        }
834        let order = order.map(VectorInt::view);
835        let mut res = Matrix::new();
836        igraph_call!(igraph_layout_star(self, &mut res, center, opt_ptr(&order)))?;
837        Ok(res)
838    }
839
840    /// Places the vertices on a regular 2D grid, row by row.
841    ///
842    /// Vertex `i` gets coordinates `(i mod w, i div w)`, where the width `w`
843    /// is `width`, or `ceil(sqrt(n))` if `None` (`Some(0)` is the same as
844    /// `None`). With `width` equal to the first dimension, this draws the
845    /// vertices of a [`Graph::square_lattice`] at their lattice points. Time
846    /// complexity: `O(|V|)`.
847    /// Binds [`igraph_layout_grid`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_grid).
848    ///
849    /// # Examples
850    ///
851    /// ```
852    /// use igraph::prelude::*;
853    /// let g = Graph::new(5, false);
854    /// let l = g.layout_grid(Some(2)).unwrap();
855    /// assert_eq!(l.to_rows(), vec![
856    ///     vec![0.0, 0.0], vec![1.0, 0.0], vec![0.0, 1.0], vec![1.0, 1.0], vec![0.0, 2.0],
857    /// ]);
858    /// ```
859    pub fn layout_grid(&self, width: Option<usize>) -> Result<Matrix> {
860        let mut res = Matrix::new();
861        let width = width.map_or(0, int);
862        igraph_call!(igraph_layout_grid(self, &mut res, width))?;
863        Ok(res)
864    }
865
866    /// Places the vertices on a regular 3D grid, filling rows (along `x`),
867    /// then layers (along `y`), then stacking layers along `z`.
868    ///
869    /// `width` is the number of vertices in a row and `height` the number of
870    /// rows in a layer; if both are `None` they are `ceil(cbrt(n))`, if one is
871    /// `None` it is chosen so that layers are roughly square (`Some(0)` is
872    /// the same as `None`). Time complexity: `O(|V|)`.
873    /// Binds [`igraph_layout_grid_3d`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_grid_3d).
874    ///
875    /// # Examples
876    ///
877    /// ```
878    /// use igraph::prelude::*;
879    /// // 8 vertices: a 2 x 2 x 2 cube.
880    /// let l = Graph::new(8, false).layout_grid_3d(None, None).unwrap();
881    /// assert_eq!(l.row(0), vec![0.0, 0.0, 0.0]);
882    /// assert_eq!(l.row(3), vec![1.0, 1.0, 0.0]);
883    /// assert_eq!(l.row(7), vec![1.0, 1.0, 1.0]);
884    /// ```
885    pub fn layout_grid_3d(&self, width: Option<usize>, height: Option<usize>) -> Result<Matrix> {
886        let mut res = Matrix::new();
887        let width = width.map_or(0, int);
888        let height = height.map_or(0, int);
889        igraph_call!(igraph_layout_grid_3d(self, &mut res, width, height))?;
890        Ok(res)
891    }
892
893    /// Places the vertices (more or less) uniformly on the unit sphere.
894    ///
895    /// Vertices are placed along a spiral wrapped around the sphere, in
896    /// increasing id order, so consecutive ids end up close to each other
897    /// (Saff & Kuijlaars, 1997). Time complexity: `O(|V|)`.
898    /// Binds [`igraph_layout_sphere`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_sphere).
899    ///
900    /// # Examples
901    ///
902    /// ```
903    /// use igraph::prelude::*;
904    /// let l = Graph::new(20, false).layout_sphere().unwrap();
905    /// for p in l.rows() {
906    ///     let r = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
907    ///     assert!((r - 1.0).abs() < 1e-9);
908    /// }
909    /// ```
910    pub fn layout_sphere(&self) -> Result<Matrix> {
911        let mut res = Matrix::new();
912        igraph_call!(igraph_layout_sphere(self, &mut res))?;
913        Ok(res)
914    }
915
916    /// Force-directed layout in the plane with the Fruchterman–Reingold algorithm.
917    ///
918    /// It simulates an attractive force `f_a(d) = -w d²` between connected
919    /// vertices (`w` is the edge weight) and a repulsive force `f_r(d) = 1/d`
920    /// between all pairs, so the equilibrium length of an isolated edge is
921    /// `w^(-1/3)` (the C documentation of igraph 1.0.0 and 1.0.1 says `1/w^3`,
922    /// a typo). In
923    /// disconnected graphs a weak attraction of weight `n^(-3/2)` between all
924    /// pairs keeps the components close. The movement is limited by a
925    /// temperature decreasing linearly to zero, and per-vertex coordinate
926    /// bounds may be given. Uses the calling thread's default random number
927    /// generator for the random start. See [`FruchtermanReingoldOptions`] for
928    /// all the parameters. Time complexity: `O(|V|²)` per iteration (less with the
929    /// grid variant).
930    /// Binds [`igraph_layout_fruchterman_reingold`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_fruchterman_reingold).
931    ///
932    /// # Errors
933    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
934    /// non-positive weights, vectors of the wrong length, inconsistent bounds,
935    /// NaN bounds, a lower bound of `+inf` or an upper bound of `-inf` (an
936    /// infinite bound in the other direction means "no bound"), or a start matrix of the wrong shape or with NaN or infinite
937    /// coordinates.
938    ///
939    /// # Examples
940    ///
941    /// ```
942    /// use igraph::prelude::*;
943    /// use igraph::layout::FruchtermanReingoldOptions;
944    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (2, 3)], 4, false).unwrap();
945    /// rng::seed(7).unwrap();
946    /// // Keep every vertex in the right half-plane.
947    /// let minx = [0.0; 4];
948    /// let opts = FruchtermanReingoldOptions { minx: Some(&minx), ..Default::default() };
949    /// let l = g.layout_fruchterman_reingold(&opts).unwrap();
950    /// assert!(l.column(0).iter().all(|&x| x >= 0.0));
951    /// ```
952    pub fn layout_fruchterman_reingold(
953        &self,
954        options: &FruchtermanReingoldOptions<'_>,
955    ) -> Result<Matrix> {
956        let n = self.vcount();
957        check_len("weights", options.weights, self.ecount())?;
958        let bounds = Bounds::new(
959            self,
960            [
961                options.minx,
962                options.maxx,
963                options.miny,
964                options.maxy,
965                None,
966                None,
967            ],
968        )?;
969        let (mut res, use_seed) = start_matrix(options.initial, n, 2)?;
970        let weights = opt_view(options.weights);
971        let start_temp = options
972            .start_temp
973            .unwrap_or_else(|| (n as f64).sqrt() / 10.0);
974        igraph_call!(igraph_layout_fruchterman_reingold(
975            self,
976            &mut res,
977            use_seed,
978            int(options.niter),
979            start_temp,
980            options.grid.into(),
981            opt_ptr(&weights),
982            bounds.ptr(0),
983            bounds.ptr(1),
984            bounds.ptr(2),
985            bounds.ptr(3)
986        ))?;
987        Ok(res)
988    }
989
990    /// Force-directed layout in 3D space with the Fruchterman–Reingold algorithm.
991    ///
992    /// The 3D version of [`layout_fruchterman_reingold`](Graph::layout_fruchterman_reingold)
993    /// (the `grid` option is ignored, the `minz`/`maxz` bounds are used).
994    ///
995    /// Caveat (igraph 1.0.0 and 1.0.1): on *disconnected* graphs the C code
996    /// adds the `z` component of the repulsion acting on one vertex of each
997    /// pair to its `y` displacement instead of its `z` displacement, so the
998    /// forces are slightly unbalanced; connected graphs are not affected.
999    /// Binds [`igraph_layout_fruchterman_reingold_3d`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_fruchterman_reingold_3d).
1000    ///
1001    /// # Errors
1002    /// As for the 2D version.
1003    pub fn layout_fruchterman_reingold_3d(
1004        &self,
1005        options: &FruchtermanReingoldOptions<'_>,
1006    ) -> Result<Matrix> {
1007        let n = self.vcount();
1008        check_len("weights", options.weights, self.ecount())?;
1009        let bounds = Bounds::new(
1010            self,
1011            [
1012                options.minx,
1013                options.maxx,
1014                options.miny,
1015                options.maxy,
1016                options.minz,
1017                options.maxz,
1018            ],
1019        )?;
1020        let (mut res, use_seed) = start_matrix(options.initial, n, 3)?;
1021        let weights = opt_view(options.weights);
1022        let start_temp = options
1023            .start_temp
1024            .unwrap_or_else(|| (n as f64).sqrt() / 10.0);
1025        igraph_call!(igraph_layout_fruchterman_reingold_3d(
1026            self,
1027            &mut res,
1028            use_seed,
1029            int(options.niter),
1030            start_temp,
1031            opt_ptr(&weights),
1032            bounds.ptr(0),
1033            bounds.ptr(1),
1034            bounds.ptr(2),
1035            bounds.ptr(3),
1036            bounds.ptr(4),
1037            bounds.ptr(5)
1038        ))?;
1039        Ok(res)
1040    }
1041
1042    /// Force-directed layout in the plane with the Kamada–Kawai spring algorithm.
1043    ///
1044    /// A spring is placed between *every* pair of vertices `u`, `v`, whose
1045    /// rest length is proportional to their (undirected, possibly weighted)
1046    /// graph distance `d(u, v)`, namely `sqrt(n) · d(u, v) / D` where `D` is
1047    /// the largest finite distance (so the drawing has a size of about
1048    /// `sqrt(n)`), and whose stiffness `kkconst / d(u, v)²` decreases with
1049    /// that distance; the energy is then minimized one vertex at a time.
1050    /// Vertices in different components are treated as being at distance
1051    /// `D`. It works particularly well for lattice-like, locally connected
1052    /// graphs; memory is `O(|V|²)`, so it is not suitable for large graphs.
1053    /// Without a start layout (and with no bounds) it starts from a circle of
1054    /// radius `0.36 sqrt(n)`, so the result is deterministic.
1055    /// The target distances are the ones [`Graph::distances`] would compute
1056    /// with `NeighborMode::All`. See [`KamadaKawaiOptions`]. Time complexity:
1057    /// `O(|V|)` per iteration after an `O(|V|² log |V|)` initialization.
1058    /// Binds [`igraph_layout_kamada_kawai`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_kamada_kawai).
1059    ///
1060    /// # Errors
1061    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1062    /// non-positive weights or `kkconst`, wrong lengths or shapes, NaN bounds,
1063    /// a lower bound of `+inf` or an upper bound of `-inf`, or NaN or infinite
1064    /// start coordinates.
1065    ///
1066    /// # Examples
1067    ///
1068    /// ```
1069    /// use igraph::prelude::*;
1070    /// use igraph::layout::KamadaKawaiOptions;
1071    /// // A path 0 - 1 - 2: drawn straight, with edges of length sqrt(3) / 2.
1072    /// let g = Graph::ring(3, false, false, false).unwrap();
1073    /// let l = g.layout_kamada_kawai(&KamadaKawaiOptions::default()).unwrap();
1074    /// let d = |i: usize, j: usize| (l[(i, 0)] - l[(j, 0)]).hypot(l[(i, 1)] - l[(j, 1)]);
1075    /// assert!((d(0, 2) - d(0, 1) - d(1, 2)).abs() < 1e-3);
1076    /// assert!((d(0, 1) - 3f64.sqrt() / 2.0).abs() < 1e-3);
1077    /// ```
1078    pub fn layout_kamada_kawai(&self, options: &KamadaKawaiOptions<'_>) -> Result<Matrix> {
1079        self.kamada_kawai_impl(options, false)
1080    }
1081
1082    /// Force-directed layout in 3D space with the Kamada–Kawai spring algorithm.
1083    ///
1084    /// The 3D version of [`layout_kamada_kawai`](Graph::layout_kamada_kawai);
1085    /// without a start layout (and with no bounds) it starts from a
1086    /// [`layout_sphere`](Graph::layout_sphere) of radius `0.36 sqrt(n)`.
1087    /// Binds [`igraph_layout_kamada_kawai_3d`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_kamada_kawai_3d).
1088    ///
1089    /// # Errors
1090    /// As for the 2D version.
1091    pub fn layout_kamada_kawai_3d(&self, options: &KamadaKawaiOptions<'_>) -> Result<Matrix> {
1092        self.kamada_kawai_impl(options, true)
1093    }
1094
1095    fn kamada_kawai_impl(&self, options: &KamadaKawaiOptions<'_>, three_d: bool) -> Result<Matrix> {
1096        let n = self.vcount();
1097        check_len("weights", options.weights, self.ecount())?;
1098        let (minz, maxz) = if three_d {
1099            (options.minz, options.maxz)
1100        } else {
1101            (None, None)
1102        };
1103        let bounds = Bounds::new(
1104            self,
1105            [
1106                options.minx,
1107                options.maxx,
1108                options.miny,
1109                options.maxy,
1110                minz,
1111                maxz,
1112            ],
1113        )?;
1114        let (mut res, use_seed) = start_matrix(options.initial, n, if three_d { 3 } else { 2 })?;
1115        let weights = opt_view(options.weights);
1116        let maxiter = int(options.maxiter.unwrap_or(n.saturating_mul(50)));
1117        let kkconst = options.kkconst.unwrap_or_else(|| nf(self));
1118        if three_d {
1119            igraph_call!(igraph_layout_kamada_kawai_3d(
1120                self,
1121                &mut res,
1122                use_seed,
1123                maxiter,
1124                options.epsilon,
1125                kkconst,
1126                opt_ptr(&weights),
1127                bounds.ptr(0),
1128                bounds.ptr(1),
1129                bounds.ptr(2),
1130                bounds.ptr(3),
1131                bounds.ptr(4),
1132                bounds.ptr(5)
1133            ))?;
1134        } else {
1135            igraph_call!(igraph_layout_kamada_kawai(
1136                self,
1137                &mut res,
1138                use_seed,
1139                maxiter,
1140                options.epsilon,
1141                kkconst,
1142                opt_ptr(&weights),
1143                bounds.ptr(0),
1144                bounds.ptr(1),
1145                bounds.ptr(2),
1146                bounds.ptr(3)
1147            ))?;
1148        }
1149        Ok(res)
1150    }
1151
1152    /// Force-directed layout for large (connected) graphs, inspired by the
1153    /// Large Graph Layout program.
1154    ///
1155    /// The root is placed first, then its neighbors, then the second
1156    /// neighbors and so on (following a BFS); after each layer a
1157    /// Fruchterman–Reingold-style simulated annealing runs, computing
1158    /// repulsion only between vertices in nearby grid cells. The graph must
1159    /// be connected (igraph warns otherwise; lay out the pieces from
1160    /// [`Graph::decompose`] separately and merge them with
1161    /// [`layout_merge_dla`] instead). See [`LglOptions`]. Time
1162    /// complexity: ideally `O(dia · maxiter · (|V| + |E|))`.
1163    /// Binds [`igraph_layout_lgl`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_lgl).
1164    ///
1165    /// # Errors
1166    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if a
1167    /// parameter is not finite and positive (`repulserad` may be `+inf`), or if `cellsize` is so small
1168    /// compared to `area` that the grid would have more than
1169    /// `max(2^28, 4 |V|)` cells;
1170    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for
1171    /// an invalid root.
1172    ///
1173    /// # Examples
1174    ///
1175    /// ```
1176    /// use igraph::prelude::*;
1177    /// use igraph::layout::LglOptions;
1178    /// let g = Graph::kary_tree(40, 3, TreeMode::Undirected).unwrap();
1179    /// rng::seed(5).unwrap();
1180    /// let l = g.layout_lgl(&LglOptions { root: Some(0), ..Default::default() }).unwrap();
1181    /// assert_eq!(l.shape(), (40, 2));
1182    /// assert!(l.as_slice().iter().all(|x| x.is_finite()));
1183    /// ```
1184    pub fn layout_lgl(&self, options: &LglOptions) -> Result<Matrix> {
1185        let n = self.vcount();
1186        let nn = nf(self);
1187        if let Some(root) = options.root {
1188            check_vertices("root", &[root], n)?;
1189        }
1190        let area = options.area.unwrap_or(nn * nn);
1191        let maxdelta = options.maxdelta.unwrap_or(nn);
1192        let repulserad = options.repulserad.unwrap_or(area * nn);
1193        let cellsize = options.cellsize.unwrap_or_else(|| area.sqrt().sqrt());
1194        if n > 0 {
1195            // igraph only rejects values `<= 0`: NaN and infinite values
1196            // reach its grid code, which aborts the process.
1197            check_positive("area", area)?;
1198            check_positive("maxdelta", maxdelta)?;
1199            check_positive("coolexp", options.coolexp)?;
1200            // An infinite cutoff radius only drops the `dist² / repulserad`
1201            // term from the repulsive force, which is harmless.
1202            if repulserad.is_nan() || repulserad <= 0.0 {
1203                return Err(Error::invalid(format!(
1204                    "`repulserad` must be positive, got {repulserad}"
1205                )));
1206            }
1207            check_positive("cellsize", cellsize)?;
1208            // igraph covers the disc of the given area with a square grid of
1209            // `steps × steps` cells (a C integer), which must not overflow
1210            // nor be absurdly large.
1211            let steps = (2.0 * (area / std::f64::consts::PI).sqrt() / cellsize).ceil();
1212            let cells = steps * steps;
1213            let limit = (LGL_MAX_CELLS as f64).max(4.0 * n as f64);
1214            if !cells.is_finite() || cells > limit {
1215                return Err(Error::invalid(format!(
1216                    "`cellsize` {cellsize} is too small for `area` {area}: \
1217                     the grid would have {cells:e} cells (at most {limit:e} allowed)"
1218                )));
1219            }
1220        }
1221        let mut res = Matrix::new();
1222        igraph_call!(igraph_layout_lgl(
1223            self,
1224            &mut res,
1225            int(options.maxiter),
1226            maxdelta,
1227            area,
1228            options.coolexp,
1229            repulserad,
1230            cellsize,
1231            options.root.unwrap_or(-1)
1232        ))?;
1233        Ok(res)
1234    }
1235
1236    /// Reingold–Tilford tree layout: parents centered above their children.
1237    ///
1238    /// Vertices are placed in levels by distance from the root (`y` is the
1239    /// depth), and subtrees are packed as tightly as possible. If the graph
1240    /// is not a tree a BFS spanning tree is used. `mode` selects which edges
1241    /// are followed from a parent ([`NeighborMode::Out`], `In`, or `All`,
1242    /// which is forced for undirected graphs). `roots` should reach every
1243    /// vertex (e.g. one per component): igraph hangs any unreachable vertex
1244    /// directly below the (single) root, or places it next to the roots when
1245    /// there are several. With several roots, igraph joins them under a
1246    /// hidden common root, so the roots are drawn at depth `y = 1` and their
1247    /// children at `y = 2`. `None` (or empty) selects the roots
1248    /// automatically: in igraph 1.0.1 with [`RootChoice::Degree`] below 500
1249    /// vertices and [`RootChoice::Eccentricity`] from 500 on (the C
1250    /// documentation states the opposite, the code does this; see
1251    /// [`roots_for_tree_layout`](Graph::roots_for_tree_layout) for a
1252    /// controllable choice). `rootlevel`, only used with several given roots,
1253    /// gives the extra depth of each root, which is useful for forests.
1254    /// Trees are conveniently built with [`Graph::kary_tree`]; use
1255    /// [`Graph::is_tree`] to check whether the drawing shows every edge, and
1256    /// [`Graph::unfold_tree`] to get the tree itself when the graph has cycles.
1257    /// Binds [`igraph_layout_reingold_tilford`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_reingold_tilford).
1258    ///
1259    /// # Errors
1260    /// [`ErrorKind::InvalidVertexId`](crate::ErrorKind::InvalidVertexId) for
1261    /// invalid roots, [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue)
1262    /// if `rootlevel` has negative entries or a huge sum (the extra levels
1263    /// are added as vertices), or is non-empty with a length
1264    /// different from that of `roots` (when several roots are given).
1265    ///
1266    /// # Examples
1267    ///
1268    /// ```
1269    /// use igraph::prelude::*;
1270    /// // A binary tree of depth 2 rooted at 0.
1271    /// let g = Graph::kary_tree(7, 2, TreeMode::Undirected).unwrap();
1272    /// let l = g.layout_reingold_tilford(NeighborMode::All, Some(&[0]), None).unwrap();
1273    /// assert_eq!(l.column(1), &[0.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0]); // depths
1274    /// assert_eq!(l[(0, 0)], (l[(1, 0)] + l[(2, 0)]) / 2.0); // root centered
1275    /// ```
1276    pub fn layout_reingold_tilford(
1277        &self,
1278        mode: NeighborMode,
1279        roots: Option<&[VertexId]>,
1280        rootlevel: Option<&[i64]>,
1281    ) -> Result<Matrix> {
1282        self.reingold_tilford_impl(mode, roots, rootlevel, false)
1283    }
1284
1285    /// Circular Reingold–Tilford tree layout: the root at the center and the
1286    /// levels on concentric circles.
1287    ///
1288    /// Same parameters as [`layout_reingold_tilford`](Graph::layout_reingold_tilford);
1289    /// the distance from the origin is the depth of a vertex, and the
1290    /// horizontal positions of the plain layout are mapped linearly to
1291    /// angles, the leftmost vertex at angle 0 and the rightmost one at
1292    /// `2π (n - 1) / n`.
1293    /// Binds [`igraph_layout_reingold_tilford_circular`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_reingold_tilford_circular).
1294    ///
1295    /// # Errors
1296    /// As for [`layout_reingold_tilford`](Graph::layout_reingold_tilford).
1297    ///
1298    /// # Examples
1299    ///
1300    /// ```
1301    /// use igraph::prelude::*;
1302    /// let g = Graph::kary_tree(13, 3, TreeMode::Undirected).unwrap();
1303    /// let l = g.layout_reingold_tilford_circular(NeighborMode::All, Some(&[0]), None).unwrap();
1304    /// let radius = |v: usize| l[(v, 0)].hypot(l[(v, 1)]);
1305    /// assert_eq!(radius(0), 0.0);
1306    /// assert!((radius(1) - 1.0).abs() < 1e-12 && (radius(12) - 2.0).abs() < 1e-12);
1307    /// ```
1308    pub fn layout_reingold_tilford_circular(
1309        &self,
1310        mode: NeighborMode,
1311        roots: Option<&[VertexId]>,
1312        rootlevel: Option<&[i64]>,
1313    ) -> Result<Matrix> {
1314        self.reingold_tilford_impl(mode, roots, rootlevel, true)
1315    }
1316
1317    fn reingold_tilford_impl(
1318        &self,
1319        mode: NeighborMode,
1320        roots: Option<&[VertexId]>,
1321        rootlevel: Option<&[i64]>,
1322        circular: bool,
1323    ) -> Result<Matrix> {
1324        let n = self.vcount();
1325        if let Some(r) = roots {
1326            check_vertices("roots", r, n)?;
1327            // Like igraph, `rootlevel` is only used (and checked) when it is
1328            // non-empty and there are several roots.
1329            if let Some(levels) = rootlevel
1330                && r.len() > 1
1331                && !levels.is_empty()
1332                && levels.len() != r.len()
1333            {
1334                return Err(Error::invalid("`roots` and `rootlevel` lengths differ"));
1335            }
1336        }
1337        if let Some(levels) = rootlevel {
1338            if levels.iter().any(|&x| x < 0) {
1339                return Err(Error::invalid(
1340                    "`rootlevel` must not contain negative levels",
1341                ));
1342            }
1343            // igraph adds one vertex per level to a copy of the graph,
1344            // summing the levels unchecked.
1345            let total = levels
1346                .iter()
1347                .try_fold(int(n), |acc, &x| acc.checked_add(x))
1348                .filter(|&t| t <= RT_MAX_VERTICES);
1349            if total.is_none() {
1350                return Err(Error::invalid(format!(
1351                    "the sum of `rootlevel` is too large (the graph plus the extra \
1352                     levels must have at most {RT_MAX_VERTICES} vertices)"
1353                )));
1354            }
1355        }
1356        // igraph writes into the (nominally `const`) `roots` vector when
1357        // `rootlevel` is used: always hand it an owned copy, through a
1358        // pointer derived from a mutable borrow.
1359        let mut roots = roots.map(VectorInt::from_slice);
1360        let roots_ptr = roots
1361            .as_mut()
1362            .map_or(ptr::null(), |r| ptr::from_mut(r).cast_const());
1363        let rootlevel = rootlevel.map(VectorInt::view);
1364        let mut res = Matrix::new();
1365        // An earlier ALL-mode adjacency list of a directed graph with mutual
1366        // edges may have cached a wrong "has multi-edges" flag (igraph 1.0.1),
1367        // which makes igraph abort when its OUT/IN adjacency list finds none:
1368        // drop the cache before the call, and after it (ALL mode may write
1369        // the wrong value again).
1370        let guard = self.is_directed();
1371        if guard {
1372            self.invalidate_cache();
1373        }
1374        let _reset = CacheReset(guard.then_some(self));
1375        if circular {
1376            igraph_call!(igraph_layout_reingold_tilford_circular(
1377                self,
1378                &mut res,
1379                mode.into(),
1380                roots_ptr,
1381                opt_ptr(&rootlevel)
1382            ))?;
1383        } else {
1384            igraph_call!(igraph_layout_reingold_tilford(
1385                self,
1386                &mut res,
1387                mode.into(),
1388                roots_ptr,
1389                opt_ptr(&rootlevel)
1390            ))?;
1391        }
1392        Ok(res)
1393    }
1394
1395    /// Chooses a minimal set of roots for a nice tree layout, such that all
1396    /// vertices are reachable from them.
1397    ///
1398    /// For undirected graphs (or `mode = All`) one root is chosen per
1399    /// connected component; in directed mode one per strongly connected
1400    /// component without incoming (for `Out`) or outgoing (for `In`) edges
1401    /// (the components are those of [`Graph::connected_components`]). Within
1402    /// a component the root is chosen with the given [`RootChoice`] heuristic
1403    /// ([`RootChoice::Eccentricity`] relates to [`Graph::eccentricity`]).
1404    /// Typically used with [`layout_reingold_tilford`](Graph::layout_reingold_tilford).
1405    /// Binds [`igraph_roots_for_tree_layout`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_roots_for_tree_layout).
1406    ///
1407    /// # Examples
1408    ///
1409    /// ```
1410    /// use igraph::prelude::*;
1411    /// use igraph::layout::RootChoice;
1412    /// // Two stars: centers 0 and 4.
1413    /// let g = Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (4, 5), (4, 6)], 7, false).unwrap();
1414    /// let roots = g.roots_for_tree_layout(NeighborMode::All, RootChoice::Degree).unwrap();
1415    /// assert_eq!(roots, vec![0, 4]);
1416    /// ```
1417    pub fn roots_for_tree_layout(
1418        &self,
1419        mode: NeighborMode,
1420        heuristic: RootChoice,
1421    ) -> Result<Vec<VertexId>> {
1422        let mut roots = VectorInt::new();
1423        igraph_call!(igraph_roots_for_tree_layout(
1424            self,
1425            mode.into(),
1426            &mut roots,
1427            heuristic.into()
1428        ))?;
1429        Ok(roots.into())
1430    }
1431
1432    /// Sugiyama layout for layered (directed acyclic) graphs, minimizing edge
1433    /// crossings.
1434    ///
1435    /// Vertices of the same layer are placed on the same horizontal line
1436    /// (`y = layer · vgap`; empty layers are skipped by the crossing
1437    /// minimization but still take room vertically); their `x`
1438    /// coordinates follow the heuristic of Sugiyama, Tagawa and Toda (1981).
1439    /// Without given layers, igraph breaks cycles (via a feedback arc set) and
1440    /// computes a layering itself. Edges spanning several layers are routed
1441    /// through dummy vertices, whose positions are returned in
1442    /// [`SugiyamaLayout::routing`]. Disconnected components are placed side
1443    /// by side. See [`SugiyamaOptions`]. Related: [`Graph::feedback_arc_set`]
1444    /// (the edges reversed to break cycles), [`Graph::is_dag`] and
1445    /// [`Graph::topological_sorting`].
1446    /// Binds [`igraph_layout_sugiyama`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_sugiyama).
1447    ///
1448    /// # Errors
1449    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `layers`
1450    /// or `weights` have the wrong length, or `layers` contains a negative
1451    /// index.
1452    ///
1453    /// # Examples
1454    ///
1455    /// ```
1456    /// use igraph::prelude::*;
1457    /// use igraph::layout::SugiyamaOptions;
1458    /// // 0 -> 1 -> 2 plus a shortcut 0 -> 2 spanning two layers.
1459    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], 3, true).unwrap();
1460    /// let s = g.layout_sugiyama(&SugiyamaOptions::default()).unwrap();
1461    /// assert_eq!(s.coords.column(1), &[0.0, 1.0, 2.0]);
1462    /// assert_eq!(s.routing[2].nrow(), 1); // one bend for the shortcut
1463    /// ```
1464    pub fn layout_sugiyama(&self, options: &SugiyamaOptions<'_>) -> Result<SugiyamaLayout> {
1465        check_len("layers", options.layers, self.vcount())?;
1466        check_len("weights", options.weights, self.ecount())?;
1467        if options.layers.is_some_and(|l| l.iter().any(|&x| x < 0)) {
1468            return Err(Error::invalid("layer indices must not be negative"));
1469        }
1470        let layers = options.layers.map(VectorInt::view);
1471        let weights = opt_view(options.weights);
1472        let mut coords = Matrix::new();
1473        let mut routing = MatrixList::new();
1474        igraph_call!(igraph_layout_sugiyama(
1475            self,
1476            &mut coords,
1477            &mut routing,
1478            opt_ptr(&layers),
1479            options.hgap,
1480            options.vgap,
1481            int(options.maxiter),
1482            opt_ptr(&weights)
1483        ))?;
1484        Ok(SugiyamaLayout {
1485            coords,
1486            routing: routing.into_vec(),
1487        })
1488    }
1489
1490    /// Places the vertices in a space of dimension `dim` with classical
1491    /// (Torgerson) multidimensional scaling.
1492    ///
1493    /// The Euclidean distances of the layout approximate the given symmetric
1494    /// `n` × `n` distance matrix (symmetry is not checked, and its diagonal
1495    /// is ignored), or, if `None`, the undirected shortest path lengths. `dim`
1496    /// must be at least 2 and at most the number of vertices. Disconnected
1497    /// graphs are laid out
1498    /// per component and merged with [`layout_merge_dla`], which only works
1499    /// for `dim = 2`. Vertices symmetric to each other (e.g. leaves of the
1500    /// same parent) may receive identical coordinates. Time complexity:
1501    /// usually around `O(|V|² dim)`. The default distance matrix is the one of
1502    /// [`Graph::distances`] with `NeighborMode::All`.
1503    /// Binds [`igraph_layout_mds`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_mds).
1504    ///
1505    /// # Errors
1506    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for a
1507    /// distance matrix of the wrong shape, `dim` out of range, or `dim > 2`
1508    /// on a disconnected graph.
1509    ///
1510    /// # Examples
1511    ///
1512    /// ```
1513    /// use igraph::prelude::*;
1514    /// // Three points on a line at 0, 1, 3: MDS recovers their distances.
1515    /// let g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1516    /// let d = Matrix::from_rows(&[[0.0, 1.0, 3.0], [1.0, 0.0, 2.0], [3.0, 2.0, 0.0]]).unwrap();
1517    /// let l = g.layout_mds(Some(&d), 2).unwrap();
1518    /// let dist = |i: usize, j: usize| (l[(i, 0)] - l[(j, 0)]).hypot(l[(i, 1)] - l[(j, 1)]);
1519    /// assert!((dist(0, 2) - 3.0).abs() < 1e-9 && (dist(0, 1) - 1.0).abs() < 1e-9);
1520    /// ```
1521    pub fn layout_mds(&self, dist: Option<&Matrix>, dim: usize) -> Result<Matrix> {
1522        let n = self.vcount();
1523        if let Some(d) = dist
1524            && d.shape() != (n, n)
1525        {
1526            return Err(Error::invalid(format!(
1527                "the distance matrix is {}x{}, expected {n}x{n}",
1528                d.nrow(),
1529                d.ncol()
1530            )));
1531        }
1532        if let Some(d) = dist {
1533            // NaN distances reach LAPACK, which converts them to integers.
1534            crate::linalg::check_finite(d.as_slice(), "the distance matrix")?;
1535        }
1536        let dist = dist.map_or(ptr::null(), |d| d as *const Matrix);
1537        let mut res = Matrix::new();
1538        igraph_call!(igraph_layout_mds(self, &mut res, dist, int(dim)))?;
1539        Ok(res)
1540    }
1541
1542    /// Simple two-row layout for bipartite graphs.
1543    ///
1544    /// Vertices with type `true` are placed on the line `y = 0`, those with
1545    /// type `false` on `y = vgap`; positions within the rows are then
1546    /// optimized to reduce edge crossings with the Sugiyama heuristic, using
1547    /// `hgap` as the preferred minimum gap and at most `maxiter` iterations
1548    /// (100 is a reasonable default). Only the `types` matter, edges between
1549    /// vertices of the same type are allowed: a proper 2-coloring can be
1550    /// obtained with [`Graph::bipartite_types`], and the bipartite
1551    /// constructors (e.g. [`Graph::full_bipartite`]) return the types with
1552    /// the graph.
1553    /// Binds [`igraph_layout_bipartite`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_bipartite).
1554    ///
1555    /// # Errors
1556    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if `types`
1557    /// has the wrong length or `hgap` is negative.
1558    ///
1559    /// # Examples
1560    ///
1561    /// ```
1562    /// use igraph::prelude::*;
1563    /// let g = Graph::from_edges(&[(0, 2), (1, 2), (1, 3)], 4, false).unwrap();
1564    /// let l = g.layout_bipartite(&[false, false, true, true], 1.0, 1.0, 100).unwrap();
1565    /// assert_eq!(l.column(1), &[1.0, 1.0, 0.0, 0.0]);
1566    /// ```
1567    pub fn layout_bipartite(
1568        &self,
1569        types: &[bool],
1570        hgap: f64,
1571        vgap: f64,
1572        maxiter: usize,
1573    ) -> Result<Matrix> {
1574        check_len("types", Some(types), self.vcount())?;
1575        let types = VectorBool::view(types);
1576        let mut res = Matrix::new();
1577        igraph_call!(igraph_layout_bipartite(
1578            self,
1579            types.as_ptr(),
1580            &mut res,
1581            hgap,
1582            vgap,
1583            int(maxiter)
1584        ))?;
1585        Ok(res)
1586    }
1587
1588    /// Layout with Uniform Manifold Approximation and Projection (UMAP), in
1589    /// the plane. **Experimental in igraph.**
1590    ///
1591    /// UMAP embeds a (typically sparse, e.g. k-nearest-neighbors) distance
1592    /// graph: distances are turned into exponentially decaying weights in
1593    /// `[0, 1]`, and a stochastic gradient descent on the cross-entropy then
1594    /// places strongly connected vertices close together, while repelling
1595    /// unconnected ones beyond `min_dist`. Without distances all edges have
1596    /// the same weight. If `distances_are_weights` is set, the `distances`
1597    /// are used directly as weights (see
1598    /// [`layout_umap_compute_weights`](Graph::layout_umap_compute_weights)).
1599    /// A typical input is a k-nearest-neighbor graph of data points built
1600    /// with [`Graph::nearest_neighbor_graph`], with the distances from
1601    /// [`Graph::spatial_edge_lengths`]. Uses the calling thread's default
1602    /// random number generator. See [`UmapOptions`].
1603    /// Binds [`igraph_layout_umap`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_umap).
1604    ///
1605    /// # Errors
1606    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1607    /// invalid distances, a negative `min_dist` or a wrongly shaped or non-finite start.
1608    pub fn layout_umap(&self, options: &UmapOptions<'_>) -> Result<Matrix> {
1609        self.umap_impl(options, false)
1610    }
1611
1612    /// Layout with UMAP in 3D space. **Experimental in igraph.**
1613    ///
1614    /// The 3D version of [`layout_umap`](Graph::layout_umap).
1615    /// Binds [`igraph_layout_umap_3d`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_umap_3d).
1616    ///
1617    /// # Errors
1618    /// As for the 2D version.
1619    pub fn layout_umap_3d(&self, options: &UmapOptions<'_>) -> Result<Matrix> {
1620        self.umap_impl(options, true)
1621    }
1622
1623    fn umap_impl(&self, options: &UmapOptions<'_>, three_d: bool) -> Result<Matrix> {
1624        check_len("distances", options.distances, self.ecount())?;
1625        let (mut res, use_seed) =
1626            start_matrix(options.initial, self.vcount(), if three_d { 3 } else { 2 })?;
1627        let distances = opt_view(options.distances);
1628        let f = if three_d {
1629            igraph_layout_umap_3d
1630        } else {
1631            igraph_layout_umap
1632        };
1633        igraph_call!(f(
1634            self,
1635            &mut res,
1636            use_seed,
1637            opt_ptr(&distances),
1638            options.min_dist,
1639            int(options.epochs),
1640            options.distances_are_weights
1641        ))?;
1642        Ok(res)
1643    }
1644
1645    /// Computes the UMAP edge weights from the edge distances.
1646    /// **Experimental in igraph.**
1647    ///
1648    /// For each vertex a scale factor and a connectivity correction are
1649    /// computed, and distances become exponentially decaying weights in
1650    /// `[0, 1]`. The graph may be directed but must have no loops or multi-edges
1651    /// (pairs of opposite directed edges are allowed): such pairs are
1652    /// symmetrized with the *fuzzy union* `W = W1 + W2 - W1 W2`, stored on one
1653    /// of the two edges, while the other gets weight 0 (see
1654    /// [`Graph::is_simple`] and [`Graph::simplify`]). Loops are reported as
1655    /// errors, but multi-edges are not detected by igraph: all but one of
1656    /// the parallel edges silently get weight 0. Pass the result to
1657    /// [`layout_umap`](Graph::layout_umap) with `distances_are_weights`.
1658    /// `None` means that all edges have the same distance.
1659    /// Binds [`igraph_layout_umap_compute_weights`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_umap_compute_weights).
1660    ///
1661    /// # Errors
1662    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1663    /// negative or NaN distances, a wrong length, or loops.
1664    ///
1665    /// # Examples
1666    ///
1667    /// ```
1668    /// use igraph::prelude::*;
1669    /// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
1670    /// let w = g.layout_umap_compute_weights(Some(&[1.0, 2.0, 3.0])).unwrap();
1671    /// assert_eq!(w.len(), 3);
1672    /// assert!(w.iter().all(|&x| (0.0..=1.0).contains(&x)));
1673    /// ```
1674    pub fn layout_umap_compute_weights(&self, distances: Option<&[f64]>) -> Result<Vec<f64>> {
1675        check_len("distances", distances, self.ecount())?;
1676        let distances = opt_view(distances);
1677        let mut weights = Vector::new();
1678        igraph_call!(igraph_layout_umap_compute_weights(
1679            self,
1680            opt_ptr(&distances),
1681            &mut weights
1682        ))?;
1683        Ok(weights.into())
1684    }
1685
1686    /// The DrL (Distributed Recursive Layout) force-directed layout, in the plane.
1687    ///
1688    /// Designed for large graphs, it runs a sequence of simulated annealing
1689    /// phases driven by the given [`DrlOptions`] (start from a
1690    /// [`DrlTemplate`]), cutting highly stressed edges in the late phases to
1691    /// produce less dense, clustered layouts (Martin et al., 2008). `weights`
1692    /// must be positive (`None`: unit weights); `initial` gives optional
1693    /// starting positions (`n` × 2).
1694    /// Binds [`igraph_layout_drl`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_drl).
1695    ///
1696    /// # Errors
1697    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for
1698    /// negative damping multipliers, non-positive weights, weights above
1699    /// `1e20` (DrL computes in single precision and overflows; rescale such
1700    /// weights), NaN or infinite start coordinates, or wrong lengths.
1701    ///
1702    /// # Examples
1703    ///
1704    /// ```
1705    /// use igraph::prelude::*;
1706    /// use igraph::layout::DrlOptions;
1707    /// let edges: Vec<(i64, i64)> = (0..10).map(|i| (i, (i + 1) % 10)).collect();
1708    /// let g = Graph::from_edges(&edges, 10, false).unwrap();
1709    /// rng::seed(3).unwrap();
1710    /// let l = g.layout_drl(&DrlOptions::default(), None, None).unwrap();
1711    /// assert_eq!(l.shape(), (10, 2));
1712    /// ```
1713    pub fn layout_drl(
1714        &self,
1715        options: &DrlOptions,
1716        weights: Option<&[f64]>,
1717        initial: Option<&Matrix>,
1718    ) -> Result<Matrix> {
1719        self.drl_impl(options, weights, initial, false)
1720    }
1721
1722    /// The DrL force-directed layout in 3D space.
1723    ///
1724    /// The 3D version of [`layout_drl`](Graph::layout_drl) (`initial`, if
1725    /// given, must be `n` × 3).
1726    /// Binds [`igraph_layout_drl_3d`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_drl_3d).
1727    ///
1728    /// # Errors
1729    /// As for the 2D version.
1730    pub fn layout_drl_3d(
1731        &self,
1732        options: &DrlOptions,
1733        weights: Option<&[f64]>,
1734        initial: Option<&Matrix>,
1735    ) -> Result<Matrix> {
1736        self.drl_impl(options, weights, initial, true)
1737    }
1738
1739    fn drl_impl(
1740        &self,
1741        options: &DrlOptions,
1742        weights: Option<&[f64]>,
1743        initial: Option<&Matrix>,
1744        three_d: bool,
1745    ) -> Result<Matrix> {
1746        check_len("weights", weights, self.ecount())?;
1747        if let Some(w) = weights
1748            && !w.iter().all(|&x| x > 0.0 && x <= DRL_MAX_WEIGHT)
1749        {
1750            // Huge weights overflow DrL's single-precision energies into NaN
1751            // positions, which its density grid converts to integers (UB).
1752            return Err(Error::invalid(format!(
1753                "DrL weights must be positive and at most {DRL_MAX_WEIGHT:e}"
1754            )));
1755        }
1756        let (mut res, use_seed) =
1757            start_matrix(initial, self.vcount(), if three_d { 3 } else { 2 })?;
1758        let weights = opt_view(weights);
1759        let f = if three_d {
1760            igraph_layout_drl_3d
1761        } else {
1762            igraph_layout_drl
1763        };
1764        igraph_call!(f(self, &mut res, use_seed, options, opt_ptr(&weights)))?;
1765        Ok(res)
1766    }
1767
1768    /// The graphopt force-directed layout (a port of Michael Schmuhl's graphopt).
1769    ///
1770    /// Vertices are charged particles repelling each other (Coulomb's law,
1771    /// ignored beyond distance 500) and edges are springs (Hooke's law); the
1772    /// physical system is simulated for `niter` steps, without simulated
1773    /// annealing, so a stable fixed point is not guaranteed. A layout can be
1774    /// refined by passing it back as `initial`. See [`GraphoptOptions`].
1775    /// Time complexity: `O(niter (|V|² + |E|))`, or `O(niter |E|)` with zero
1776    /// charge.
1777    /// Binds [`igraph_layout_graphopt`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_graphopt).
1778    ///
1779    /// # Errors
1780    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) for a
1781    /// wrongly shaped or non-finite start.
1782    ///
1783    /// # Examples
1784    ///
1785    /// Pure springs of rest length 1 (no charge) stretch a squeezed path
1786    /// along its diagonal (values from igraph's `igraph_layout_graphopt` unit
1787    /// test):
1788    ///
1789    /// ```
1790    /// use igraph::prelude::*;
1791    /// use igraph::layout::GraphoptOptions;
1792    /// let g = Graph::ring(4, false, false, false).unwrap(); // the path 0 - 1 - 2 - 3
1793    /// let start = Matrix::from_rows(&[[0.15, -0.15], [0.05, -0.05], [-0.05, 0.05], [-0.15, 0.15]]).unwrap();
1794    /// let opts = GraphoptOptions {
1795    ///     node_charge: 0.0,
1796    ///     spring_length: 1.0,
1797    ///     spring_constant: 10.0,
1798    ///     initial: Some(&start),
1799    ///     ..Default::default()
1800    /// };
1801    /// let l = g.layout_graphopt(&opts).unwrap();
1802    /// assert!((l[(0, 0)] - 1.06066).abs() < 1e-5 && (l[(1, 1)] + 0.353553).abs() < 1e-5);
1803    /// ```
1804    pub fn layout_graphopt(&self, options: &GraphoptOptions<'_>) -> Result<Matrix> {
1805        let (mut res, use_seed) = start_matrix(options.initial, self.vcount(), 2)?;
1806        igraph_call!(igraph_layout_graphopt(
1807            self,
1808            &mut res,
1809            int(options.niter),
1810            options.node_charge,
1811            options.node_mass,
1812            options.spring_length,
1813            options.spring_constant,
1814            options.max_sa_movement,
1815            use_seed
1816        ))?;
1817        Ok(res)
1818    }
1819
1820    /// The GEM force-directed layout (Frick, Ludwig and Mehldau, 1994).
1821    ///
1822    /// Vertices are updated one at a time in random order, each with its own
1823    /// local temperature adapted to detect oscillations and rotations; the
1824    /// algorithm stops when the global temperature drops below `temp_min`
1825    /// or after `maxiter` vertex updates. Edge directions are ignored. See
1826    /// [`GemOptions`]. Time complexity: `O(t · n · (n + e))` for `t` steps.
1827    /// Binds [`igraph_layout_gem`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_gem).
1828    ///
1829    /// # Errors
1830    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) unless
1831    /// `0 < temp_min <= temp_init <= temp_max`, or for a wrongly shaped or non-finite start.
1832    ///
1833    /// # Examples
1834    ///
1835    /// ```
1836    /// use igraph::prelude::*;
1837    /// use igraph::layout::GemOptions;
1838    /// let g = Graph::ring(8, false, false, true).unwrap();
1839    /// rng::seed(11).unwrap();
1840    /// let l = g.layout_gem(&GemOptions::default()).unwrap();
1841    /// assert_eq!(l.shape(), (8, 2));
1842    /// // Zero iterations keep the start.
1843    /// let opts = GemOptions { maxiter: Some(0), initial: Some(&l), ..Default::default() };
1844    /// assert_eq!(g.layout_gem(&opts).unwrap(), l);
1845    /// ```
1846    pub fn layout_gem(&self, options: &GemOptions<'_>) -> Result<Matrix> {
1847        let n = self.vcount();
1848        let nn = nf(self);
1849        let (mut res, use_seed) = start_matrix(options.initial, n, 2)?;
1850        igraph_call!(igraph_layout_gem(
1851            self,
1852            &mut res,
1853            use_seed,
1854            int(options
1855                .maxiter
1856                .unwrap_or_else(|| n.saturating_mul(n).saturating_mul(40))),
1857            options.temp_max.unwrap_or(nn),
1858            options.temp_min,
1859            options.temp_init.unwrap_or_else(|| nn.sqrt())
1860        ))?;
1861        if n == 0 {
1862            // igraph returns early on the null graph without resizing.
1863            res.resize(0, 2);
1864        }
1865        Ok(res)
1866    }
1867
1868    /// The Davidson–Harel simulated annealing layout (1996).
1869    ///
1870    /// Minimizes an energy combining node-node distances, distances from the
1871    /// border, edge lengths, edge crossings and node-edge distances, first
1872    /// with simulated annealing then with a fine tuning phase; coordinates are
1873    /// kept within the bounds of the layout rectangle. Edge directions are
1874    /// ignored. The energy weights are hard to tune in general; see
1875    /// [`DavidsonHarelOptions`] for defaults determined by experimentation.
1876    /// Time complexity: `O(n² + m²)` per annealing iteration, `O(mn)` per fine
1877    /// tuning iteration.
1878    /// Binds [`igraph_layout_davidson_harel`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_davidson_harel).
1879    ///
1880    /// # Errors
1881    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if
1882    /// `cool_fact` is not in `(0, 1)`, if `maxiter + fineiter` overflows a
1883    /// 64-bit integer, or for a wrongly shaped or non-finite start.
1884    ///
1885    /// # Examples
1886    ///
1887    /// ```
1888    /// use igraph::prelude::*;
1889    /// use igraph::layout::DavidsonHarelOptions;
1890    /// let g = Graph::full(10, false, false).unwrap();
1891    /// rng::seed(42).unwrap();
1892    /// let l = g.layout_davidson_harel(&DavidsonHarelOptions::default()).unwrap();
1893    /// assert_eq!(l.shape(), (10, 2));
1894    /// assert!(l.as_slice().iter().all(|x| x.abs() < 20.0));
1895    /// ```
1896    pub fn layout_davidson_harel(&self, options: &DavidsonHarelOptions<'_>) -> Result<Matrix> {
1897        let n = self.vcount();
1898        let density = if n < 2 {
1899            0.0
1900        } else {
1901            (self.ecount() as f64 / (n as f64 * (n as f64 - 1.0) / 2.0)).min(1.0)
1902        };
1903        let fineiter = options
1904            .fineiter
1905            .unwrap_or_else(|| ((n.max(1) as f64).log2() as usize).max(10));
1906        // igraph loops while `round < maxiter + fineiter`.
1907        let (maxiter, fineiter) = (int(options.maxiter), int(fineiter));
1908        if maxiter.checked_add(fineiter).is_none() {
1909            return Err(Error::invalid(
1910                "`maxiter + fineiter` does not fit in a 64-bit integer",
1911            ));
1912        }
1913        let (mut res, use_seed) = start_matrix(options.initial, n, 2)?;
1914        igraph_call!(igraph_layout_davidson_harel(
1915            self,
1916            &mut res,
1917            use_seed,
1918            maxiter,
1919            fineiter,
1920            options.cool_fact,
1921            options.weight_node_dist,
1922            options.weight_border,
1923            options.weight_edge_lengths.unwrap_or(density / 10.0),
1924            options
1925                .weight_edge_crossings
1926                .unwrap_or(1.0 - density.sqrt()),
1927            options
1928                .weight_node_edge_dist
1929                .unwrap_or((1.0 - density) / 5.0)
1930        ))?;
1931        Ok(res)
1932    }
1933
1934    /// Centers a layout on the origin and rotates it to align it with the
1935    /// coordinate axes, in place.
1936    ///
1937    /// The principal axes are computed from the edge directions (weighted by
1938    /// squared edge lengths), or from the vertex positions if there are no
1939    /// edges of non-zero length. Useful after force-directed layouts; works
1940    /// in any dimension. Time complexity: `O(|V| + |E|)`.
1941    /// Binds [`igraph_layout_align`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_align).
1942    ///
1943    /// # Errors
1944    /// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the
1945    /// layout does not have one row per vertex, has zero columns (and the
1946    /// graph is not the null graph), or contains NaN or infinite coordinates.
1947    ///
1948    /// # Examples
1949    ///
1950    /// ```
1951    /// use igraph::prelude::*;
1952    /// // A diagonal segment becomes horizontal and centered.
1953    /// let g = Graph::from_edges(&[(0, 1)], 2, false).unwrap();
1954    /// let mut l = Matrix::from_rows(&[[1.0, 1.0], [3.0, 3.0]]).unwrap();
1955    /// g.layout_align(&mut l).unwrap();
1956    /// assert!(l[(0, 1)].abs() < 1e-9 && l[(1, 1)].abs() < 1e-9);
1957    /// assert!((l[(0, 0)] + l[(1, 0)]).abs() < 1e-9);
1958    /// ```
1959    pub fn layout_align(&self, layout: &mut Matrix) -> Result<()> {
1960        if layout.nrow() != self.vcount() {
1961            return Err(Error::invalid(format!(
1962                "the layout has {} rows, expected one per vertex ({})",
1963                layout.nrow(),
1964                self.vcount()
1965            )));
1966        }
1967        if !layout.as_slice().iter().all(|x| x.is_finite()) {
1968            return Err(Error::invalid(
1969                "the layout contains NaN or infinite coordinates",
1970            ));
1971        }
1972        igraph_call!(igraph_layout_align(self, layout))
1973    }
1974}
1975
1976/// Merges the 2D layouts of several graphs (typically the components of a
1977/// graph) into one, using diffusion-limited aggregation (DLA).
1978///
1979/// Each layout is covered by a circle and rescaled so that the area of the
1980/// circle grows with the size of the graph; the largest layout is placed at
1981/// the origin and the others, from larger to smaller, perform random walks
1982/// until they stick next to the already placed ones. The result has the rows
1983/// of all the layouts, in the given order. The graphs are currently only
1984/// used for bookkeeping (igraph only looks at the coordinates). Uses the
1985/// calling thread's default random number generator. The typical input is
1986/// the list of components from [`Graph::decompose`], each laid out on its
1987/// own. `graphs` is any collection of graphs or references to graphs (like
1988/// the multi-graph functions of [`operators`](crate::operators)), e.g. the
1989/// `&Vec<Graph>` returned by `decompose` or a `&[&Graph]`.
1990/// Binds [`igraph_layout_merge_dla`](https://igraph.org/c/html/latest/igraph-Layout.html#igraph_layout_merge_dla).
1991///
1992/// # Errors
1993/// [`ErrorKind::InvalidValue`](crate::ErrorKind::InvalidValue) if the number
1994/// of graphs and layouts differ, no layout is given, a layout is empty or
1995/// is not 2D, or a layout does not have one row per vertex of its graph.
1996///
1997/// # Examples
1998///
1999/// ```
2000/// use igraph::prelude::*;
2001/// use igraph::layout::layout_merge_dla;
2002/// // A triangle and a separate edge, laid out component by component.
2003/// let g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0), (3, 4)], 5, false).unwrap();
2004/// let parts = g.decompose(Connectedness::Weak, None, 1).unwrap();
2005/// let layouts: Vec<Matrix> = parts.iter().map(|p| p.layout_circle(..).unwrap()).collect();
2006/// rng::seed(42).unwrap();
2007/// // Any collection of graphs works: `&Vec<Graph>`, `&[&Graph]`, `Vec<Graph>`...
2008/// let merged = layout_merge_dla(&parts, &layouts).unwrap();
2009/// assert_eq!(merged.shape(), (5, 2));
2010/// // Each component is only scaled and translated: the triangle stays equilateral.
2011/// let d = |i: usize, j: usize| (merged[(i, 0)] - merged[(j, 0)]).hypot(merged[(i, 1)] - merged[(j, 1)]);
2012/// assert!((d(0, 1) - d(1, 2)).abs() < 1e-9 && (d(1, 2) - d(2, 0)).abs() < 1e-9);
2013/// ```
2014pub fn layout_merge_dla<G: AsRef<Graph>>(
2015    graphs: impl IntoIterator<Item = G>,
2016    coords: &[Matrix],
2017) -> Result<Matrix> {
2018    let graphs: Vec<G> = graphs.into_iter().collect();
2019    let graphs: Vec<&Graph> = graphs.iter().map(AsRef::as_ref).collect();
2020    if graphs.len() != coords.len() {
2021        return Err(Error::invalid(format!(
2022            "{} graphs but {} layouts given",
2023            graphs.len(),
2024            coords.len()
2025        )));
2026    }
2027    if coords.is_empty() {
2028        return Err(Error::invalid("at least one layout is needed"));
2029    }
2030    for (i, (g, m)) in graphs.iter().zip(coords).enumerate() {
2031        if m.ncol() != 2 || m.nrow() == 0 {
2032            return Err(Error::invalid(format!(
2033                "layout {i} is {}x{}, but only non-empty 2D layouts can be merged",
2034                m.nrow(),
2035                m.ncol()
2036            )));
2037        }
2038        if m.nrow() != g.vcount() {
2039            return Err(Error::invalid(format!(
2040                "layout {i} has {} rows, but its graph has {} vertices",
2041                m.nrow(),
2042                g.vcount()
2043            )));
2044        }
2045    }
2046    let graph_ptrs = GraphPtrs::new(&graphs)?;
2047    let mut list = MatrixList::new();
2048    for m in coords {
2049        list.push(m.clone());
2050    }
2051    let mut res = Matrix::new();
2052    igraph_call!(igraph_layout_merge_dla(&graph_ptrs.raw, &list, &mut res))?;
2053    Ok(res)
2054}
2055
2056/// A graph is trivially a reference to itself: this lets functions taking
2057/// several graphs, like [`layout_merge_dla`], accept collections of graphs
2058/// (`Vec<Graph>`, `&[Graph]`) as well as of references (`&[&Graph]`).
2059impl AsRef<Graph> for Graph {
2060    fn as_ref(&self) -> &Graph {
2061        self
2062    }
2063}
2064
2065/// An `igraph_vector_ptr_t` of borrowed graph pointers (never owning them).
2066struct GraphPtrs<'a> {
2067    raw: igraph_vector_ptr_t,
2068    _borrow: std::marker::PhantomData<&'a Graph>,
2069}
2070
2071impl<'a> GraphPtrs<'a> {
2072    fn new(graphs: &[&'a Graph]) -> Result<Self> {
2073        let mut raw = MaybeUninit::<igraph_vector_ptr_t>::uninit();
2074        igraph_call!(igraph_vector_ptr_init(raw.as_mut_ptr(), 0))?;
2075        // No item destructor is set: destroying the vector leaves the graphs alone.
2076        let mut ptrs = Self {
2077            raw: unsafe { raw.assume_init() },
2078            _borrow: std::marker::PhantomData,
2079        };
2080        for g in graphs {
2081            let p = *g as *const Graph as *mut c_void;
2082            igraph_call!(igraph_vector_ptr_push_back(&mut ptrs.raw, p))?;
2083        }
2084        Ok(ptrs)
2085    }
2086}
2087
2088impl Drop for GraphPtrs<'_> {
2089    fn drop(&mut self) {
2090        unsafe { igraph_vector_ptr_destroy(&mut self.raw) };
2091    }
2092}