Skip to main content

igraph/
attributes.rs

1//! Graph, vertex and edge attributes (`igraph_attributes.h`).
2//!
3//! igraph can attach *attributes* to a graph as a whole, to its vertices and
4//! to its edges: a vertex `name`, an edge `weight`, a graph `title`, ... The
5//! C core does not store attributes by itself: it notifies an *attribute
6//! handler* (a table of callbacks, `igraph_attribute_table_t`) of every
7//! structural change so that the handler can keep its data aligned with the
8//! vertex and edge ids. This module plugs igraph's own C attribute handler
9//! (`igraph_cattribute_table`, the one behind the `VAN`/`SETVAN`/... macros of
10//! the C API) into the crate and exposes it with a typed, Rusty API on
11//! [`Graph`].
12//!
13//! # Enabling attributes
14//!
15//! The attribute handler is a **process-wide** setting of the C library (it is
16//! *not* thread-local, even in a thread-safe igraph build, see
17//! `src/graph/attributes.c`). It is turned on with [`enable`]:
18//!
19//! - it is idempotent and irreversible: once enabled, attributes stay enabled
20//!   for the rest of the process (detaching a handler would make igraph leak
21//!   the attribute storage and let it get out of sync with the graphs);
22//! - it is also called implicitly by every attribute *setter*, so writing an
23//!   attribute simply works;
24//! - it must be called *before reading graphs from files* if you want the
25//!   foreign readers (GraphML, GML, Pajek, NCOL, LGL, ...) to keep vertex
26//!   names, edge weights and the other attributes found in the file, and
27//!   before *writing* them if you want the writers to emit your attributes;
28//! - graphs created before [`enable`] carry no attribute storage: they keep
29//!   working (this crate installs a *guarded* version of the C handler that
30//!   ignores attribute-less graphs instead of crashing, as the raw C handler
31//!   would), and their storage is created lazily by the first setter call;
32//! - call it early (e.g. at the start of `main`): the C library stores the
33//!   handler in a plain global variable, so enabling it while other threads
34//!   are in the middle of igraph calls is best avoided.
35//!
36//! # Semantics of the C attribute handler
37//!
38//! - Attribute values are numbers (`f64`), booleans or strings
39//!   ([`AttributeType`]); an attribute has a single type for all the
40//!   vertices (or edges) of a graph, fixed when it is first created. Writing a
41//!   value of another type is an error.
42//! - Setting a vertex (edge) attribute on a single vertex (edge) creates it
43//!   for all the vertices (edges), with the default value (`NaN`, `false` or
44//!   `""`) everywhere else. Vertices and edges added later also get the
45//!   default value.
46//! - Attributes follow the structure: deleting vertices or edges
47//!   ([`Graph::delete_vertices`], [`Graph::delete_edges`]), taking subgraphs
48//!   ([`Graph::induced_subgraph`]), permuting vertices
49//!   ([`Graph::permute_vertices`]), copying (and [`Clone`]-ing) a graph keep
50//!   every value attached to the right vertex or edge.
51//! - Operations that merge vertices or edges decide what to do with the
52//!   attributes of the merged elements via an [`AttributeCombination`]. The
53//!   plain wrappers [`Graph::simplify`], [`Graph::contract_vertices`] and
54//!   [`Graph::to_undirected`] pass *no* combination, so they drop the
55//!   attributes of the kind they merge: vertex attributes for
56//!   `contract_vertices`; edge attributes for `to_undirected` (except in
57//!   [`ToUndirected::Each`] mode) and for `simplify` when it actually merges
58//!   multi-edges (when it only deletes loops, or igraph already knows there
59//!   are no multi-edges, the remaining edges keep their attributes). Use
60//!   their attribute-aware twins
61//!   [`Graph::simplify_with_attributes`],
62//!   [`Graph::contract_vertices_with_attributes`] and
63//!   [`Graph::to_undirected_with_attributes`] to keep them.
64//! - The [`Random`](AttributeCombinationType::Random) combination and the
65//!   tie-breaking of boolean majority votes use the default random number
66//!   generator of the calling thread: seed it with [`rng::seed`](crate::rng::seed)
67//!   for reproducible results.
68//!
69//! # Example
70//!
71//! ```
72//! use igraph::prelude::*;
73//! use igraph::attributes::{self, AttributeKind, AttributeValue};
74//!
75//! attributes::enable().unwrap();
76//! let mut g = Graph::from_edges(&[(0, 1), (1, 2), (2, 0)], 3, false).unwrap();
77//!
78//! g.set_graph_attr_str("title", "triangle").unwrap();
79//! g.set_vertex_attr_str_values("name", &["alice", "bob", "carol"]).unwrap();
80//! g.set_edge_attr_numeric_values("weight", &[1.0, 2.5, 4.0]).unwrap();
81//! g.set_vertex_attr_bool("admin", 0, true).unwrap();
82//!
83//! assert_eq!(g.graph_attr_str("title").unwrap(), "triangle");
84//! assert_eq!(g.vertex_attr_str("name", 1).unwrap(), "bob");
85//! assert_eq!(g.vertex_attr("admin", 2).unwrap(), AttributeValue::Boolean(false));
86//! assert!(g.has_attribute(AttributeKind::Vertex, "name"));
87//!
88//! // Numeric edge attributes are the weight vectors of weighted algorithms.
89//! let w = g.edge_attr_numeric_values("weight", ..).unwrap();
90//! assert_eq!(w, vec![1.0, 2.5, 4.0]);
91//! let strength = g.strength(.., NeighborMode::All, Loops::Twice, Some(&w)).unwrap();
92//! assert_eq!(strength, vec![5.0, 3.5, 6.5]);
93//!
94//! // Attributes follow the structure of the graph.
95//! g.delete_vertices(0).unwrap();
96//! assert_eq!(g.vertex_attr_str_values("name", ..).unwrap(), vec!["bob", "carol"]);
97//! assert_eq!(g.edge_attr_numeric_values("weight", ..).unwrap(), vec![2.5]);
98//! ```
99//!
100//! # Provided functionality
101//!
102//! | Rust | C |
103//! |------|---|
104//! | [`enable`], [`is_enabled`], [`has_attribute_table`] | `igraph_set_attribute_table(&igraph_cattribute_table)`, `igraph_has_attribute_table` |
105//! | [`Graph::graph_attr_numeric`], [`Graph::graph_attr_bool`], [`Graph::graph_attr_str`], [`Graph::graph_attr`] | `igraph_cattribute_GAN`, `GAB`, `GAS` |
106//! | [`Graph::vertex_attr_numeric`], [`Graph::vertex_attr_bool`], [`Graph::vertex_attr_str`], [`Graph::vertex_attr`] | `igraph_cattribute_VAN`, `VAB`, `VAS` |
107//! | [`Graph::edge_attr_numeric`], [`Graph::edge_attr_bool`], [`Graph::edge_attr_str`], [`Graph::edge_attr`] | `igraph_cattribute_EAN`, `EAB`, `EAS` |
108//! | [`Graph::vertex_attr_numeric_values`], [`Graph::vertex_attr_bool_values`], [`Graph::vertex_attr_str_values`], [`Graph::vertex_attr_values`] | `igraph_cattribute_VANV`, `VABV`, `VASV` |
109//! | [`Graph::edge_attr_numeric_values`], [`Graph::edge_attr_bool_values`], [`Graph::edge_attr_str_values`], [`Graph::edge_attr_values`] | `igraph_cattribute_EANV`, `EABV`, `EASV` |
110//! | [`Graph::set_graph_attr_numeric`], [`Graph::set_graph_attr_bool`], [`Graph::set_graph_attr_str`], [`Graph::set_graph_attr`] | `igraph_cattribute_GAN_set`, `GAB_set`, `GAS_set` |
111//! | [`Graph::set_vertex_attr_numeric`], [`Graph::set_vertex_attr_bool`], [`Graph::set_vertex_attr_str`], [`Graph::set_vertex_attr`] | `igraph_cattribute_VAN_set`, `VAB_set`, `VAS_set` |
112//! | [`Graph::set_edge_attr_numeric`], [`Graph::set_edge_attr_bool`], [`Graph::set_edge_attr_str`], [`Graph::set_edge_attr`] | `igraph_cattribute_EAN_set`, `EAB_set`, `EAS_set` |
113//! | [`Graph::set_vertex_attr_numeric_values`], [`Graph::set_vertex_attr_bool_values`], [`Graph::set_vertex_attr_str_values`], [`Graph::set_vertex_attr_values`] | `igraph_cattribute_VAN_setv`, `VAB_setv`, `VAS_setv` |
114//! | [`Graph::set_edge_attr_numeric_values`], [`Graph::set_edge_attr_bool_values`], [`Graph::set_edge_attr_str_values`], [`Graph::set_edge_attr_values`] | `igraph_cattribute_EAN_setv`, `EAB_setv`, `EAS_setv` |
115//! | [`Graph::attribute_list`], [`Graph::attribute_names`], [`Graph::attribute_type`] | `igraph_cattribute_list` |
116//! | [`Graph::has_attribute`] | `igraph_cattribute_has_attr` |
117//! | [`Graph::remove_graph_attr`], [`Graph::remove_vertex_attr`], [`Graph::remove_edge_attr`], [`Graph::remove_all_attributes`] | `igraph_cattribute_remove_g`, `remove_v`, `remove_e`, `remove_all` |
118//! | [`Graph::add_vertices_with_attributes`], [`Graph::add_edges_with_attributes`] | `igraph_add_vertices`, `igraph_add_edges` with an attribute record list |
119//! | [`Graph::simplify_with_attributes`], [`Graph::contract_vertices_with_attributes`], [`Graph::to_undirected_with_attributes`] | `igraph_simplify`, `igraph_contract_vertices`, `igraph_to_undirected` with an attribute combination |
120//! | [`AttributeRecord`] | `igraph_attribute_record_*` |
121//! | [`AttributeCombination`] | `igraph_attribute_combination_*` |
122//!
123//! # See also
124//!
125//! - [`crate::foreign`]: the GraphML, GML, Pajek, NCOL, LGL, ... readers and
126//!   writers load and save attributes once [`enable`] has been called, e.g.
127//!   [`Graph::read_graph_graphml_from_str`] and
128//!   [`Graph::write_graph_graphml_to_string`].
129//! - [`crate::operators`] and [`crate::conversion`]: the structural
130//!   operations whose attribute handling is described above.
131//! - Weighted algorithms take the weights as a slice: feed them
132//!   `g.edge_attr_numeric_values("weight", ..)`, e.g. [`Graph::strength`],
133//!   [`Graph::distances_dijkstra`] or [`Graph::community_multilevel`].
134
135#[cfg(doc)]
136use crate::graph::Graph;
137use crate::{
138    constants::ToUndirected,
139    error::{Error, ErrorKind, Result, ensure_init},
140    ffi::*,
141    graph::{EdgeId, VertexId},
142    igraph_call,
143    selector::{EdgeSelector, VertexSelector},
144    strvector::StrVector,
145    vector::{Vector, VectorBool, VectorInt},
146};
147use std::{
148    ffi::{CStr, CString, c_char},
149    fmt,
150    mem::MaybeUninit,
151    ptr,
152    sync::{
153        Once,
154        atomic::{AtomicBool, Ordering},
155    },
156};
157
158// ---------------------------------------------------------------------------
159// Enumerations
160// ---------------------------------------------------------------------------
161
162crate::ffi_enum! {
163    /// The type of an attribute (`igraph_attribute_type_t`).
164    ///
165    /// The C attribute handler supports [`Numeric`](Self::Numeric),
166    /// [`Boolean`](Self::Boolean) and [`String`](Self::String) attributes;
167    /// the other two variants exist for completeness (they are used by the
168    /// attribute handlers of the high-level igraph interfaces).
169    pub enum AttributeType: igraph_attribute_type_t {
170        /// No type yet (a freshly initialized, empty [`AttributeRecord`]).
171        Unspecified = igraph_attribute_type_t_IGRAPH_ATTRIBUTE_UNSPECIFIED,
172        /// Real numbers (`f64`).
173        Numeric = igraph_attribute_type_t_IGRAPH_ATTRIBUTE_NUMERIC,
174        /// Booleans.
175        Boolean = igraph_attribute_type_t_IGRAPH_ATTRIBUTE_BOOLEAN,
176        /// Strings.
177        String = igraph_attribute_type_t_IGRAPH_ATTRIBUTE_STRING,
178        /// Opaque objects of a high-level language (unsupported by the C handler).
179        Object = igraph_attribute_type_t_IGRAPH_ATTRIBUTE_OBJECT,
180    }
181}
182
183impl fmt::Display for AttributeType {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        f.write_str(match self {
186            Self::Unspecified => "unspecified",
187            Self::Numeric => "numeric",
188            Self::Boolean => "boolean",
189            Self::String => "string",
190            Self::Object => "object",
191        })
192    }
193}
194
195crate::ffi_enum! {
196    /// What an attribute is attached to (`igraph_attribute_elemtype_t`).
197    pub enum AttributeKind: igraph_attribute_elemtype_t {
198        /// The graph as a whole.
199        Graph = igraph_attribute_elemtype_t_IGRAPH_ATTRIBUTE_GRAPH,
200        /// Each vertex.
201        Vertex = igraph_attribute_elemtype_t_IGRAPH_ATTRIBUTE_VERTEX,
202        /// Each edge.
203        Edge = igraph_attribute_elemtype_t_IGRAPH_ATTRIBUTE_EDGE,
204    }
205}
206
207impl fmt::Display for AttributeKind {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        f.write_str(match self {
210            Self::Graph => "graph",
211            Self::Vertex => "vertex",
212            Self::Edge => "edge",
213        })
214    }
215}
216
217crate::ffi_enum! {
218    /// How to combine the attribute values of vertices or edges that are
219    /// merged into one (`igraph_attribute_combination_type_t`).
220    ///
221    /// The C attribute handler supports the following combinations, per
222    /// attribute type (anything else fails with
223    /// [`ErrorKind::AttributeCombination`] or [`ErrorKind::Unimplemented`]):
224    ///
225    /// | combination | numeric | boolean | string |
226    /// |-------------|---------|---------|--------|
227    /// | `Sum`       | sum     | any is true | – |
228    /// | `Prod`      | product | all are true | – |
229    /// | `Min`/`Max` | min/max | all/any are true | – |
230    /// | `Mean`      | mean    | majority (ties broken at random) | – |
231    /// | `Median`    | – (unimplemented) | majority (ties broken at random) | – |
232    /// | `Random`, `First`, `Last` | yes | yes | yes |
233    /// | `Concat`    | – | – | concatenation |
234    /// | `Function`  | yes | yes | – (not supported by this crate) |
235    ///
236    /// `Ignore` and `Default` drop the attribute for every type.
237    pub enum AttributeCombinationType: igraph_attribute_combination_type_t {
238        /// Drop the attribute from the result.
239        Ignore = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_IGNORE,
240        /// The default behaviour of the handler: for the C handler, the
241        /// attribute is dropped, like [`Ignore`](Self::Ignore).
242        Default = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_DEFAULT,
243        /// A user supplied C function, see [`AttributeCombination::add_function`].
244        Function = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_FUNCTION,
245        /// Sum of the values (logical *or* for booleans).
246        Sum = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_SUM,
247        /// Product of the values (logical *and* for booleans).
248        Prod = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_PROD,
249        /// Minimum of the values (logical *and* for booleans).
250        Min = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_MIN,
251        /// Maximum of the values (logical *or* for booleans).
252        Max = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_MAX,
253        /// A value chosen uniformly at random (uses igraph's RNG).
254        Random = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_RANDOM,
255        /// The value of the first element of the merged group (in the order
256        /// the merging function lists them, usually increasing id).
257        First = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_FIRST,
258        /// The value of the last element of the merged group (usually the
259        /// highest id).
260        Last = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_LAST,
261        /// Arithmetic mean (majority vote for booleans, with ties broken at
262        /// random using igraph's RNG).
263        Mean = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_MEAN,
264        /// Median (majority vote for booleans; not implemented for numbers).
265        Median = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_MEDIAN,
266        /// Concatenation of the strings.
267        ///
268        /// **Note:** in igraph 1.0.0 and 1.0.1 the C handler
269        /// (`igraph_i_cattributes_cs_concat`) concatenates the values of the
270        /// *first* `k` elements of the graph (where `k` is the size of the
271        /// merged group) instead of the values of the merged elements.
272        Concat = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_CONCAT,
273    }
274}
275
276// ---------------------------------------------------------------------------
277// Values
278// ---------------------------------------------------------------------------
279
280/// A single attribute value, as stored by the C attribute handler.
281///
282/// It converts from `f64`, `i32`, `i64`, `bool`, `&str` and `String`, so the
283/// generic setters such as [`Graph::set_vertex_attr`] accept those directly.
284#[derive(Debug, Clone, PartialEq)]
285pub enum AttributeValue {
286    /// A numeric value.
287    Numeric(f64),
288    /// A boolean value.
289    Boolean(bool),
290    /// A string value.
291    String(String),
292}
293
294impl AttributeValue {
295    /// The [`AttributeType`] of this value.
296    pub fn attribute_type(&self) -> AttributeType {
297        match self {
298            Self::Numeric(_) => AttributeType::Numeric,
299            Self::Boolean(_) => AttributeType::Boolean,
300            Self::String(_) => AttributeType::String,
301        }
302    }
303
304    /// The number, if this is a numeric value.
305    pub fn as_f64(&self) -> Option<f64> {
306        match self {
307            Self::Numeric(x) => Some(*x),
308            _ => None,
309        }
310    }
311
312    /// The boolean, if this is a boolean value.
313    pub fn as_bool(&self) -> Option<bool> {
314        match self {
315            Self::Boolean(b) => Some(*b),
316            _ => None,
317        }
318    }
319
320    /// The string, if this is a string value.
321    pub fn as_str(&self) -> Option<&str> {
322        match self {
323            Self::String(s) => Some(s),
324            _ => None,
325        }
326    }
327}
328
329impl fmt::Display for AttributeValue {
330    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331        match self {
332            Self::Numeric(x) => write!(f, "{x}"),
333            Self::Boolean(b) => write!(f, "{b}"),
334            Self::String(s) => write!(f, "{s:?}"),
335        }
336    }
337}
338
339impl From<f64> for AttributeValue {
340    fn from(x: f64) -> Self {
341        Self::Numeric(x)
342    }
343}
344
345impl From<i32> for AttributeValue {
346    fn from(x: i32) -> Self {
347        Self::Numeric(x.into())
348    }
349}
350
351impl From<i64> for AttributeValue {
352    /// Converts to a numeric value (precision is lost beyond 2^53).
353    fn from(x: i64) -> Self {
354        Self::Numeric(x as f64)
355    }
356}
357
358impl From<bool> for AttributeValue {
359    fn from(b: bool) -> Self {
360        Self::Boolean(b)
361    }
362}
363
364impl From<&str> for AttributeValue {
365    fn from(s: &str) -> Self {
366        Self::String(s.to_owned())
367    }
368}
369
370impl From<String> for AttributeValue {
371    fn from(s: String) -> Self {
372        Self::String(s)
373    }
374}
375
376/// The values of an attribute for several vertices or edges.
377///
378/// Converts from `Vec<f64>`, `Vec<bool>`, `Vec<String>`, `Vec<&str>` and the
379/// corresponding slices, so that [`Graph::set_vertex_attr_values`] accepts
380/// them directly.
381#[derive(Debug, Clone, PartialEq)]
382pub enum AttributeValues {
383    /// Numeric values.
384    Numeric(Vec<f64>),
385    /// Boolean values.
386    Boolean(Vec<bool>),
387    /// String values.
388    String(Vec<String>),
389}
390
391impl AttributeValues {
392    /// The [`AttributeType`] of the values.
393    pub fn attribute_type(&self) -> AttributeType {
394        match self {
395            Self::Numeric(_) => AttributeType::Numeric,
396            Self::Boolean(_) => AttributeType::Boolean,
397            Self::String(_) => AttributeType::String,
398        }
399    }
400
401    /// Number of values.
402    pub fn len(&self) -> usize {
403        match self {
404            Self::Numeric(v) => v.len(),
405            Self::Boolean(v) => v.len(),
406            Self::String(v) => v.len(),
407        }
408    }
409
410    /// Whether there are no values.
411    pub fn is_empty(&self) -> bool {
412        self.len() == 0
413    }
414
415    /// The value at `index`, if any.
416    pub fn get(&self, index: usize) -> Option<AttributeValue> {
417        match self {
418            Self::Numeric(v) => v.get(index).copied().map(AttributeValue::Numeric),
419            Self::Boolean(v) => v.get(index).copied().map(AttributeValue::Boolean),
420            Self::String(v) => v.get(index).cloned().map(AttributeValue::String),
421        }
422    }
423
424    /// The numbers, if these are numeric values.
425    pub fn as_numeric(&self) -> Option<&[f64]> {
426        match self {
427            Self::Numeric(v) => Some(v),
428            _ => None,
429        }
430    }
431
432    /// The booleans, if these are boolean values.
433    pub fn as_bool(&self) -> Option<&[bool]> {
434        match self {
435            Self::Boolean(v) => Some(v),
436            _ => None,
437        }
438    }
439
440    /// The strings, if these are string values.
441    pub fn as_strings(&self) -> Option<&[String]> {
442        match self {
443            Self::String(v) => Some(v),
444            _ => None,
445        }
446    }
447}
448
449impl From<Vec<f64>> for AttributeValues {
450    fn from(v: Vec<f64>) -> Self {
451        Self::Numeric(v)
452    }
453}
454
455impl From<&[f64]> for AttributeValues {
456    fn from(v: &[f64]) -> Self {
457        Self::Numeric(v.to_vec())
458    }
459}
460
461impl From<Vec<bool>> for AttributeValues {
462    fn from(v: Vec<bool>) -> Self {
463        Self::Boolean(v)
464    }
465}
466
467impl From<&[bool]> for AttributeValues {
468    fn from(v: &[bool]) -> Self {
469        Self::Boolean(v.to_vec())
470    }
471}
472
473impl From<Vec<String>> for AttributeValues {
474    fn from(v: Vec<String>) -> Self {
475        Self::String(v)
476    }
477}
478
479impl From<Vec<&str>> for AttributeValues {
480    fn from(v: Vec<&str>) -> Self {
481        Self::String(v.into_iter().map(str::to_owned).collect())
482    }
483}
484
485impl From<&[&str]> for AttributeValues {
486    fn from(v: &[&str]) -> Self {
487        Self::String(v.iter().map(|s| (*s).to_owned()).collect())
488    }
489}
490
491/// Names and types of all the attributes of a graph, see [`Graph::attribute_list`].
492#[derive(Debug, Clone, PartialEq, Eq, Default)]
493pub struct AttributeList {
494    /// Graph attributes, in creation order.
495    pub graph: Vec<(String, AttributeType)>,
496    /// Vertex attributes, in creation order.
497    pub vertex: Vec<(String, AttributeType)>,
498    /// Edge attributes, in creation order.
499    pub edge: Vec<(String, AttributeType)>,
500}
501
502impl AttributeList {
503    /// The attributes of the given kind.
504    pub fn of(&self, kind: AttributeKind) -> &[(String, AttributeType)] {
505        match kind {
506            AttributeKind::Graph => &self.graph,
507            AttributeKind::Vertex => &self.vertex,
508            AttributeKind::Edge => &self.edge,
509        }
510    }
511
512    /// Whether the graph has no attributes at all.
513    pub fn is_empty(&self) -> bool {
514        self.graph.is_empty() && self.vertex.is_empty() && self.edge.is_empty()
515    }
516}
517
518// ---------------------------------------------------------------------------
519// The attribute handler
520// ---------------------------------------------------------------------------
521
522/// Fetches a callback of the C attribute handler `igraph_cattribute_table`.
523macro_rules! c_handler {
524    ($field:ident) => {
525        // SAFETY: `igraph_cattribute_table` is an immutable, statically
526        // initialized C constant.
527        unsafe { igraph_cattribute_table.$field }
528    };
529}
530
531/// Reports an error from inside a callback, like `IGRAPH_ERROR` in C.
532fn raise(reason: &'static CStr, code: igraph_error_t) -> igraph_error_t {
533    unsafe {
534        igraph_error(
535            reason.as_ptr(),
536            c"attributes.rs".as_ptr(),
537            line!() as i32,
538            code,
539        )
540    }
541}
542
543fn missing_callback() -> igraph_error_t {
544    raise(
545        c"The C attribute handler lacks a callback.",
546        igraph_error_type_t_IGRAPH_FAILURE,
547    )
548}
549
550fn no_such_attribute() -> igraph_error_t {
551    raise(
552        c"Attribute does not exist (the graph carries no attributes).",
553        igraph_error_type_t_IGRAPH_EINVAL,
554    )
555}
556
557// The guarded handler: igraph's C attribute handler dereferences `graph->attr`
558// unconditionally in most callbacks, which would crash on graphs created
559// before the handler was attached (their `attr` is NULL). The callbacks below
560// delegate to the C handler for graphs that carry attribute storage and treat
561// attribute-less graphs as graphs without attributes.
562
563unsafe extern "C" fn g_init(
564    graph: *mut igraph_t,
565    attr: *const igraph_attribute_record_list_t,
566) -> igraph_error_t {
567    match c_handler!(init) {
568        Some(f) => unsafe { f(graph, attr) },
569        None => missing_callback(),
570    }
571}
572
573unsafe extern "C" fn g_destroy(graph: *mut igraph_t) {
574    if unsafe { !(*graph).attr.is_null() }
575        && let Some(f) = c_handler!(destroy)
576    {
577        unsafe { f(graph) }
578    }
579}
580
581unsafe extern "C" fn g_copy(
582    to: *mut igraph_t,
583    from: *const igraph_t,
584    ga: igraph_bool_t,
585    va: igraph_bool_t,
586    ea: igraph_bool_t,
587) -> igraph_error_t {
588    if unsafe { (*from).attr.is_null() } {
589        return igraph_error_type_t_IGRAPH_SUCCESS;
590    }
591    match c_handler!(copy) {
592        Some(f) => unsafe { f(to, from, ga, va, ea) },
593        None => missing_callback(),
594    }
595}
596
597unsafe extern "C" fn g_add_vertices(
598    graph: *mut igraph_t,
599    nv: igraph_int_t,
600    attr: *const igraph_attribute_record_list_t,
601) -> igraph_error_t {
602    if unsafe { (*graph).attr.is_null() } {
603        return igraph_error_type_t_IGRAPH_SUCCESS;
604    }
605    match c_handler!(add_vertices) {
606        Some(f) => unsafe { f(graph, nv, attr) },
607        None => missing_callback(),
608    }
609}
610
611unsafe extern "C" fn g_add_edges(
612    graph: *mut igraph_t,
613    edges: *const igraph_vector_int_t,
614    attr: *const igraph_attribute_record_list_t,
615) -> igraph_error_t {
616    if unsafe { (*graph).attr.is_null() } {
617        return igraph_error_type_t_IGRAPH_SUCCESS;
618    }
619    match c_handler!(add_edges) {
620        Some(f) => unsafe { f(graph, edges, attr) },
621        None => missing_callback(),
622    }
623}
624
625unsafe fn both_have_storage(graph: *const igraph_t, newgraph: *const igraph_t) -> bool {
626    unsafe { !(*graph).attr.is_null() && !(*newgraph).attr.is_null() }
627}
628
629unsafe extern "C" fn g_permute_vertices(
630    graph: *const igraph_t,
631    newgraph: *mut igraph_t,
632    idx: *const igraph_vector_int_t,
633) -> igraph_error_t {
634    if unsafe { !both_have_storage(graph, newgraph) } {
635        return igraph_error_type_t_IGRAPH_SUCCESS;
636    }
637    match c_handler!(permute_vertices) {
638        Some(f) => unsafe { f(graph, newgraph, idx) },
639        None => missing_callback(),
640    }
641}
642
643unsafe extern "C" fn g_permute_edges(
644    graph: *const igraph_t,
645    newgraph: *mut igraph_t,
646    idx: *const igraph_vector_int_t,
647) -> igraph_error_t {
648    if unsafe { !both_have_storage(graph, newgraph) } {
649        return igraph_error_type_t_IGRAPH_SUCCESS;
650    }
651    match c_handler!(permute_edges) {
652        Some(f) => unsafe { f(graph, newgraph, idx) },
653        None => missing_callback(),
654    }
655}
656
657unsafe extern "C" fn g_combine_vertices(
658    graph: *const igraph_t,
659    newgraph: *mut igraph_t,
660    merges: *const igraph_vector_int_list_t,
661    comb: *const igraph_attribute_combination_t,
662) -> igraph_error_t {
663    if unsafe { !both_have_storage(graph, newgraph) } {
664        return igraph_error_type_t_IGRAPH_SUCCESS;
665    }
666    match c_handler!(combine_vertices) {
667        Some(f) => unsafe { f(graph, newgraph, merges, comb) },
668        None => missing_callback(),
669    }
670}
671
672unsafe extern "C" fn g_combine_edges(
673    graph: *const igraph_t,
674    newgraph: *mut igraph_t,
675    merges: *const igraph_vector_int_list_t,
676    comb: *const igraph_attribute_combination_t,
677) -> igraph_error_t {
678    if unsafe { !both_have_storage(graph, newgraph) } {
679        return igraph_error_type_t_IGRAPH_SUCCESS;
680    }
681    match c_handler!(combine_edges) {
682        Some(f) => unsafe { f(graph, newgraph, merges, comb) },
683        None => missing_callback(),
684    }
685}
686
687unsafe extern "C" fn g_get_info(
688    graph: *const igraph_t,
689    gnames: *mut igraph_strvector_t,
690    gtypes: *mut igraph_vector_int_t,
691    vnames: *mut igraph_strvector_t,
692    vtypes: *mut igraph_vector_int_t,
693    enames: *mut igraph_strvector_t,
694    etypes: *mut igraph_vector_int_t,
695) -> igraph_error_t {
696    if unsafe { (*graph).attr.is_null() } {
697        for names in [gnames, vnames, enames] {
698            if !names.is_null() {
699                unsafe { igraph_strvector_clear(names) };
700            }
701        }
702        for types in [gtypes, vtypes, etypes] {
703            if !types.is_null() {
704                unsafe { igraph_vector_int_clear(types) };
705            }
706        }
707        return igraph_error_type_t_IGRAPH_SUCCESS;
708    }
709    match c_handler!(get_info) {
710        Some(f) => unsafe { f(graph, gnames, gtypes, vnames, vtypes, enames, etypes) },
711        None => missing_callback(),
712    }
713}
714
715unsafe extern "C" fn g_has_attr(
716    graph: *const igraph_t,
717    kind: igraph_attribute_elemtype_t,
718    name: *const c_char,
719) -> igraph_bool_t {
720    if unsafe { (*graph).attr.is_null() } {
721        return false;
722    }
723    match c_handler!(has_attr) {
724        Some(f) => unsafe { f(graph, kind, name) },
725        None => false,
726    }
727}
728
729unsafe extern "C" fn g_get_type(
730    graph: *const igraph_t,
731    type_: *mut igraph_attribute_type_t,
732    elemtype: igraph_attribute_elemtype_t,
733    name: *const c_char,
734) -> igraph_error_t {
735    if unsafe { (*graph).attr.is_null() } {
736        return no_such_attribute();
737    }
738    match c_handler!(get_type) {
739        Some(f) => unsafe { f(graph, type_, elemtype, name) },
740        None => missing_callback(),
741    }
742}
743
744macro_rules! guarded_graph_getter {
745    ($name:ident, $field:ident, $out:ty) => {
746        unsafe extern "C" fn $name(
747            graph: *const igraph_t,
748            name: *const c_char,
749            value: *mut $out,
750        ) -> igraph_error_t {
751            if unsafe { (*graph).attr.is_null() } {
752                return no_such_attribute();
753            }
754            match c_handler!($field) {
755                Some(f) => unsafe { f(graph, name, value) },
756                None => missing_callback(),
757            }
758        }
759    };
760}
761
762macro_rules! guarded_elem_getter {
763    ($name:ident, $field:ident, $sel:ty, $out:ty) => {
764        unsafe extern "C" fn $name(
765            graph: *const igraph_t,
766            name: *const c_char,
767            sel: $sel,
768            value: *mut $out,
769        ) -> igraph_error_t {
770            if unsafe { (*graph).attr.is_null() } {
771                return no_such_attribute();
772            }
773            match c_handler!($field) {
774                Some(f) => unsafe { f(graph, name, sel, value) },
775                None => missing_callback(),
776            }
777        }
778    };
779}
780
781guarded_graph_getter!(g_get_num_g, get_numeric_graph_attr, igraph_vector_t);
782guarded_graph_getter!(g_get_str_g, get_string_graph_attr, igraph_strvector_t);
783guarded_graph_getter!(g_get_bool_g, get_bool_graph_attr, igraph_vector_bool_t);
784guarded_elem_getter!(
785    g_get_num_v,
786    get_numeric_vertex_attr,
787    igraph_vs_t,
788    igraph_vector_t
789);
790guarded_elem_getter!(
791    g_get_str_v,
792    get_string_vertex_attr,
793    igraph_vs_t,
794    igraph_strvector_t
795);
796guarded_elem_getter!(
797    g_get_bool_v,
798    get_bool_vertex_attr,
799    igraph_vs_t,
800    igraph_vector_bool_t
801);
802guarded_elem_getter!(
803    g_get_num_e,
804    get_numeric_edge_attr,
805    igraph_es_t,
806    igraph_vector_t
807);
808guarded_elem_getter!(
809    g_get_str_e,
810    get_string_edge_attr,
811    igraph_es_t,
812    igraph_strvector_t
813);
814guarded_elem_getter!(
815    g_get_bool_e,
816    get_bool_edge_attr,
817    igraph_es_t,
818    igraph_vector_bool_t
819);
820
821/// igraph's C attribute handler, guarded against attribute-less graphs.
822static GUARDED_TABLE: igraph_attribute_table_t = igraph_attribute_table_t {
823    init: Some(g_init),
824    destroy: Some(g_destroy),
825    copy: Some(g_copy),
826    add_vertices: Some(g_add_vertices),
827    permute_vertices: Some(g_permute_vertices),
828    combine_vertices: Some(g_combine_vertices),
829    add_edges: Some(g_add_edges),
830    permute_edges: Some(g_permute_edges),
831    combine_edges: Some(g_combine_edges),
832    get_info: Some(g_get_info),
833    has_attr: Some(g_has_attr),
834    get_type: Some(g_get_type),
835    get_numeric_graph_attr: Some(g_get_num_g),
836    get_string_graph_attr: Some(g_get_str_g),
837    get_bool_graph_attr: Some(g_get_bool_g),
838    get_numeric_vertex_attr: Some(g_get_num_v),
839    get_string_vertex_attr: Some(g_get_str_v),
840    get_bool_vertex_attr: Some(g_get_bool_v),
841    get_numeric_edge_attr: Some(g_get_num_e),
842    get_string_edge_attr: Some(g_get_str_e),
843    get_bool_edge_attr: Some(g_get_bool_e),
844};
845
846static ENABLE: Once = Once::new();
847static ENABLED: AtomicBool = AtomicBool::new(false);
848
849/// Turns on igraph's C attribute handler for the whole process.
850///
851/// This attaches igraph's C attribute handler (`igraph_cattribute_table`,
852/// in a variant guarded against graphs without attribute storage) with
853/// [`igraph_set_attribute_table`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_set_attribute_table).
854/// From then on every newly created graph carries attribute storage, and
855/// igraph keeps graph, vertex and edge attributes in sync with every
856/// structural change.
857///
858/// - It is idempotent (cheap after the first call) and **irreversible**.
859/// - The setting is process-wide, not per thread: call it early, ideally at
860///   the start of `main`, before other threads use igraph.
861/// - Attribute setters call it implicitly; call it explicitly before reading
862///   graphs from files whose attributes (vertex names, edge weights, ...) you
863///   want to keep.
864/// - Graphs created before enabling keep working; they simply have no
865///   attributes until the first setter creates their storage.
866///
867/// # Errors
868///
869/// [`ErrorKind::Exists`] if a different attribute handler was attached
870/// beforehand through the raw FFI; that handler is left in place.
871///
872/// # Examples
873///
874/// ```
875/// use igraph::attributes;
876/// attributes::enable().unwrap();
877/// attributes::enable().unwrap(); // no-op
878/// assert!(attributes::is_enabled());
879/// assert!(attributes::has_attribute_table());
880/// ```
881pub fn enable() -> Result<()> {
882    ensure_init();
883    ENABLE.call_once(|| {
884        let ours: *const igraph_attribute_table_t = &GUARDED_TABLE;
885        let old = unsafe { igraph_set_attribute_table(ours) };
886        if old.is_null() || ptr::eq(old, ours) {
887            ENABLED.store(true, Ordering::Release);
888        } else {
889            // Someone attached another handler: put it back.
890            unsafe { igraph_set_attribute_table(old) };
891        }
892    });
893    if is_enabled() {
894        Ok(())
895    } else {
896        Err(Error::new(
897            ErrorKind::Exists,
898            "another attribute handler was attached through the raw FFI",
899        ))
900    }
901}
902
903/// Whether this crate's attribute handler is attached (see [`enable`]).
904pub fn is_enabled() -> bool {
905    ENABLED.load(Ordering::Acquire)
906}
907
908/// Whether *some* attribute handler is attached to igraph
909/// ([`igraph_has_attribute_table`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_has_attribute_table)).
910///
911/// This is `true` after [`enable`], but also when a handler was attached
912/// through the raw FFI.
913pub fn has_attribute_table() -> bool {
914    unsafe { igraph_has_attribute_table() }
915}
916
917// ---------------------------------------------------------------------------
918// Helpers
919// ---------------------------------------------------------------------------
920
921fn c_string(what: &str, s: &str) -> Result<CString> {
922    CString::new(s).map_err(|_| Error::invalid(format!("the {what} {s:?} contains a NUL byte")))
923}
924
925fn missing(kind: AttributeKind, name: &str) -> Error {
926    Error::invalid(format!("{kind} attribute '{name}' does not exist"))
927}
928
929fn lossy(ptr: *const c_char) -> String {
930    if ptr.is_null() {
931        String::new()
932    } else {
933        unsafe { CStr::from_ptr(ptr) }
934            .to_string_lossy()
935            .into_owned()
936    }
937}
938
939fn to_strvector<S: AsRef<str>>(values: &[S]) -> Result<StrVector> {
940    let mut sv = StrVector::new();
941    for s in values {
942        let s = s.as_ref();
943        if s.contains('\0') {
944            return Err(Error::invalid(format!(
945                "the string value {s:?} contains a NUL byte"
946            )));
947        }
948        sv.push(s);
949    }
950    Ok(sv)
951}
952
953fn raw_type(raw: igraph_int_t) -> AttributeType {
954    AttributeType::try_from(raw as igraph_attribute_type_t).unwrap_or(AttributeType::Object)
955}
956
957/// An owned `igraph_attribute_record_list_t`, used to hand records to igraph.
958struct RecordList(igraph_attribute_record_list_t);
959
960impl RecordList {
961    fn from_records(records: &[AttributeRecord]) -> Result<Self> {
962        let mut raw = MaybeUninit::<igraph_attribute_record_list_t>::uninit();
963        igraph_call!(igraph_attribute_record_list_init(raw.as_mut_ptr(), 0))?;
964        let mut list = RecordList(unsafe { raw.assume_init() });
965        for rec in records {
966            if rec.name().is_none() {
967                return Err(Error::invalid("attribute records must have a name"));
968            }
969            // igraph's C handler aborts (fatal error) on untyped records.
970            if !matches!(
971                rec.attribute_type(),
972                AttributeType::Numeric | AttributeType::Boolean | AttributeType::String
973            ) {
974                return Err(Error::invalid(format!(
975                    "attribute record '{}' has no supported type",
976                    rec.name().unwrap_or_default()
977                )));
978            }
979            igraph_call!(igraph_attribute_record_list_push_back_copy(
980                &mut list.0,
981                rec
982            ))?;
983        }
984        Ok(list)
985    }
986}
987
988impl Drop for RecordList {
989    fn drop(&mut self) {
990        unsafe { igraph_attribute_record_list_destroy(&mut self.0) };
991    }
992}
993
994// ---------------------------------------------------------------------------
995// Graph methods
996// ---------------------------------------------------------------------------
997
998impl igraph_t {
999    /// Whether this graph can be read through the C attribute handler.
1000    fn has_attr_storage(&self) -> bool {
1001        is_enabled() && !self.attr.is_null()
1002    }
1003
1004    /// Enables attributes and makes sure this graph has attribute storage.
1005    fn ensure_attr_storage(&mut self) -> Result<()> {
1006        enable()?;
1007        if self.attr.is_null() {
1008            igraph_call!(g_init(self, ptr::null()))?;
1009        }
1010        Ok(())
1011    }
1012
1013    fn check_vid(&self, vid: VertexId) -> Result<()> {
1014        if vid < 0 || vid as usize >= self.vcount() {
1015            return Err(Error::new(
1016                ErrorKind::InvalidVertexId,
1017                format!("vertex id {vid} is out of range 0..{}", self.vcount()),
1018            ));
1019        }
1020        Ok(())
1021    }
1022
1023    fn check_eid(&self, eid: EdgeId) -> Result<()> {
1024        if eid < 0 || eid as usize >= self.ecount() {
1025            return Err(Error::new(
1026                ErrorKind::InvalidEdgeId,
1027                format!("edge id {eid} is out of range 0..{}", self.ecount()),
1028            ));
1029        }
1030        Ok(())
1031    }
1032
1033    /// Checks that attribute `name` of the given kind exists with type `expected`.
1034    fn expect_type(&self, kind: AttributeKind, name: &str, expected: AttributeType) -> Result<()> {
1035        match self.attribute_type(kind, name)? {
1036            None => Err(missing(kind, name)),
1037            Some(t) if t == expected => Ok(()),
1038            Some(t) => Err(Error::invalid(format!(
1039                "{kind} attribute '{name}' is {t}, not {expected}"
1040            ))),
1041        }
1042    }
1043
1044    // --- listing --------------------------------------------------------------
1045
1046    /// Names and types of all the graph, vertex and edge attributes
1047    /// ([`igraph_cattribute_list`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_list)).
1048    ///
1049    /// Attributes are listed in creation order. A graph without attribute
1050    /// storage (created before [`enable`]) has an empty list.
1051    ///
1052    /// Time complexity: O(Ag+Av+Ae), the total number of attributes.
1053    ///
1054    /// # Examples
1055    ///
1056    /// ```
1057    /// use igraph::prelude::*;
1058    /// use igraph::attributes::AttributeType;
1059    ///
1060    /// let mut g = Graph::new(2, false);
1061    /// g.set_graph_attr_numeric("year", 2024.0).unwrap();
1062    /// g.set_vertex_attr_str("label", 0, "a").unwrap();
1063    /// let list = g.attribute_list().unwrap();
1064    /// assert_eq!(list.graph, vec![("year".to_string(), AttributeType::Numeric)]);
1065    /// assert_eq!(list.vertex, vec![("label".to_string(), AttributeType::String)]);
1066    /// assert!(list.edge.is_empty());
1067    /// ```
1068    pub fn attribute_list(&self) -> Result<AttributeList> {
1069        if !self.has_attr_storage() {
1070            return Ok(AttributeList::default());
1071        }
1072        let (mut gn, mut vn, mut en) = (StrVector::new(), StrVector::new(), StrVector::new());
1073        let (mut gt, mut vt, mut et) = (VectorInt::new(), VectorInt::new(), VectorInt::new());
1074        igraph_call!(igraph_cattribute_list(
1075            self, &mut gn, &mut gt, &mut vn, &mut vt, &mut en, &mut et
1076        ))?;
1077        let zip = |n: &StrVector, t: &VectorInt| -> Vec<(String, AttributeType)> {
1078            n.iter()
1079                .zip(t.iter())
1080                .map(|(n, &t)| (n, raw_type(t)))
1081                .collect()
1082        };
1083        Ok(AttributeList {
1084            graph: zip(&gn, &gt),
1085            vertex: zip(&vn, &vt),
1086            edge: zip(&en, &et),
1087        })
1088    }
1089
1090    /// Names of the attributes of one kind, in creation order (from
1091    /// [`igraph_cattribute_list`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_list)).
1092    pub fn attribute_names(&self, kind: AttributeKind) -> Result<Vec<String>> {
1093        Ok(self
1094            .attribute_list()?
1095            .of(kind)
1096            .iter()
1097            .map(|(n, _)| n.clone())
1098            .collect())
1099    }
1100
1101    /// The type of an attribute, or `None` if there is no such attribute
1102    /// (from [`igraph_cattribute_list`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_list)).
1103    ///
1104    /// ```
1105    /// use igraph::prelude::*;
1106    /// use igraph::attributes::{AttributeKind, AttributeType};
1107    ///
1108    /// let mut g = Graph::from_edges(&[(0, 1)], 2, false).unwrap();
1109    /// g.set_edge_attr_bool("bridge", 0, true).unwrap();
1110    /// assert_eq!(g.attribute_type(AttributeKind::Edge, "bridge").unwrap(), Some(AttributeType::Boolean));
1111    /// assert_eq!(g.attribute_type(AttributeKind::Vertex, "bridge").unwrap(), None);
1112    /// ```
1113    pub fn attribute_type(&self, kind: AttributeKind, name: &str) -> Result<Option<AttributeType>> {
1114        if !self.has_attr_storage() {
1115            return Ok(None);
1116        }
1117        let mut names = StrVector::new();
1118        let mut types = VectorInt::new();
1119        let (n, t) = (&mut names as *mut StrVector, &mut types as *mut VectorInt);
1120        let null_s = ptr::null_mut::<igraph_strvector_t>();
1121        let null_t = ptr::null_mut::<igraph_vector_int_t>();
1122        let args = match kind {
1123            AttributeKind::Graph => (n, t, null_s, null_t, null_s, null_t),
1124            AttributeKind::Vertex => (null_s, null_t, n, t, null_s, null_t),
1125            AttributeKind::Edge => (null_s, null_t, null_s, null_t, n, t),
1126        };
1127        igraph_call!(igraph_cattribute_list(
1128            self, args.0, args.1, args.2, args.3, args.4, args.5
1129        ))?;
1130        Ok((0..names.len())
1131            .find(|&i| {
1132                names
1133                    .get_cstr(i)
1134                    .is_some_and(|c| c.to_bytes() == name.as_bytes())
1135            })
1136            .map(|i| raw_type(types[i])))
1137    }
1138
1139    /// Whether the graph has a graph, vertex or edge attribute called `name`
1140    /// ([`igraph_cattribute_has_attr`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_has_attr)).
1141    ///
1142    /// Always `false` for graphs without attribute storage (and for names
1143    /// containing a NUL byte).
1144    ///
1145    /// Time complexity: O(A), the number of attributes of that kind.
1146    ///
1147    /// ```
1148    /// use igraph::prelude::*;
1149    /// use igraph::attributes::AttributeKind;
1150    /// let mut g = Graph::new(1, false);
1151    /// g.set_vertex_attr_str("name", 0, "solo").unwrap();
1152    /// assert!(g.has_attribute(AttributeKind::Vertex, "name"));
1153    /// assert!(!g.has_attribute(AttributeKind::Graph, "name"));
1154    /// ```
1155    pub fn has_attribute(&self, kind: AttributeKind, name: &str) -> bool {
1156        if !self.has_attr_storage() {
1157            return false;
1158        }
1159        match CString::new(name) {
1160            Ok(c) => unsafe { igraph_cattribute_has_attr(self, kind.into(), c.as_ptr()) },
1161            Err(_) => false,
1162        }
1163    }
1164
1165    // --- graph attributes -----------------------------------------------------
1166
1167    /// The value of a numeric graph attribute
1168    /// ([`igraph_cattribute_GAN`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_GAN)).
1169    ///
1170    /// # Errors
1171    ///
1172    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not
1173    /// numeric (the C function would only emit a warning and return `NaN`).
1174    ///
1175    /// ```
1176    /// use igraph::prelude::*;
1177    /// let mut g = Graph::new(0, false);
1178    /// g.set_graph_attr_numeric("density", 0.25).unwrap();
1179    /// assert_eq!(g.graph_attr_numeric("density").unwrap(), 0.25);
1180    /// assert_eq!(g.graph_attr_numeric("nope").unwrap_err().kind(), ErrorKind::InvalidValue);
1181    /// ```
1182    pub fn graph_attr_numeric(&self, name: &str) -> Result<f64> {
1183        let c = c_string("attribute name", name)?;
1184        self.expect_type(AttributeKind::Graph, name, AttributeType::Numeric)?;
1185        Ok(unsafe { igraph_cattribute_GAN(self, c.as_ptr()) })
1186    }
1187
1188    /// The value of a boolean graph attribute
1189    /// ([`igraph_cattribute_GAB`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_GAB)).
1190    ///
1191    /// # Errors
1192    ///
1193    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not boolean.
1194    pub fn graph_attr_bool(&self, name: &str) -> Result<bool> {
1195        let c = c_string("attribute name", name)?;
1196        self.expect_type(AttributeKind::Graph, name, AttributeType::Boolean)?;
1197        Ok(unsafe { igraph_cattribute_GAB(self, c.as_ptr()) })
1198    }
1199
1200    /// The value of a string graph attribute, copied into a [`String`]
1201    /// ([`igraph_cattribute_GAS`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_GAS)).
1202    ///
1203    /// Invalid UTF-8 is replaced lossily.
1204    ///
1205    /// # Errors
1206    ///
1207    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not a string.
1208    pub fn graph_attr_str(&self, name: &str) -> Result<String> {
1209        let c = c_string("attribute name", name)?;
1210        self.expect_type(AttributeKind::Graph, name, AttributeType::String)?;
1211        Ok(lossy(unsafe { igraph_cattribute_GAS(self, c.as_ptr()) }))
1212    }
1213
1214    /// The value of a graph attribute, whatever its type (`GAN`/`GAB`/`GAS`).
1215    ///
1216    /// # Errors
1217    ///
1218    /// [`ErrorKind::InvalidValue`] if the attribute does not exist.
1219    pub fn graph_attr(&self, name: &str) -> Result<AttributeValue> {
1220        match self.attribute_type(AttributeKind::Graph, name)? {
1221            Some(AttributeType::Numeric) => {
1222                self.graph_attr_numeric(name).map(AttributeValue::Numeric)
1223            }
1224            Some(AttributeType::Boolean) => self.graph_attr_bool(name).map(AttributeValue::Boolean),
1225            Some(AttributeType::String) => self.graph_attr_str(name).map(AttributeValue::String),
1226            _ => Err(missing(AttributeKind::Graph, name)),
1227        }
1228    }
1229
1230    /// Sets a numeric graph attribute, creating it if needed
1231    /// ([`igraph_cattribute_GAN_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_GAN_set)).
1232    ///
1233    /// Enables attributes (see [`enable`]) if they are not yet.
1234    ///
1235    /// # Errors
1236    ///
1237    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type.
1238    pub fn set_graph_attr_numeric(&mut self, name: &str, value: f64) -> Result<()> {
1239        let c = c_string("attribute name", name)?;
1240        self.ensure_attr_storage()?;
1241        igraph_call!(igraph_cattribute_GAN_set(self, c.as_ptr(), value))
1242    }
1243
1244    /// Sets a boolean graph attribute, creating it if needed
1245    /// ([`igraph_cattribute_GAB_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_GAB_set)).
1246    ///
1247    /// # Errors
1248    ///
1249    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type.
1250    pub fn set_graph_attr_bool(&mut self, name: &str, value: bool) -> Result<()> {
1251        let c = c_string("attribute name", name)?;
1252        self.ensure_attr_storage()?;
1253        igraph_call!(igraph_cattribute_GAB_set(self, c.as_ptr(), value))
1254    }
1255
1256    /// Sets a string graph attribute (the value is copied), creating it if
1257    /// needed ([`igraph_cattribute_GAS_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_GAS_set)).
1258    ///
1259    /// # Errors
1260    ///
1261    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type,
1262    /// or if `name` or `value` contain a NUL byte.
1263    pub fn set_graph_attr_str(&mut self, name: &str, value: &str) -> Result<()> {
1264        let c = c_string("attribute name", name)?;
1265        let v = c_string("string value", value)?;
1266        self.ensure_attr_storage()?;
1267        igraph_call!(igraph_cattribute_GAS_set(self, c.as_ptr(), v.as_ptr()))
1268    }
1269
1270    /// Sets a graph attribute of any type (`GAN_set`/`GAB_set`/`GAS_set`).
1271    ///
1272    /// # Errors
1273    ///
1274    /// As the typed setters, e.g. [`set_graph_attr_numeric`](Self::set_graph_attr_numeric).
1275    ///
1276    /// ```
1277    /// use igraph::prelude::*;
1278    /// use igraph::attributes::AttributeValue;
1279    /// let mut g = Graph::new(0, true);
1280    /// g.set_graph_attr("name", "empty").unwrap();
1281    /// g.set_graph_attr("order", 0).unwrap();
1282    /// assert_eq!(g.graph_attr("name").unwrap(), AttributeValue::String("empty".into()));
1283    /// assert_eq!(g.graph_attr("order").unwrap().as_f64(), Some(0.0));
1284    /// ```
1285    pub fn set_graph_attr(&mut self, name: &str, value: impl Into<AttributeValue>) -> Result<()> {
1286        match value.into() {
1287            AttributeValue::Numeric(x) => self.set_graph_attr_numeric(name, x),
1288            AttributeValue::Boolean(b) => self.set_graph_attr_bool(name, b),
1289            AttributeValue::String(s) => self.set_graph_attr_str(name, &s),
1290        }
1291    }
1292
1293    /// Removes a graph attribute, returning whether it existed
1294    /// ([`igraph_cattribute_remove_g`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_remove_g)).
1295    pub fn remove_graph_attr(&mut self, name: &str) -> bool {
1296        self.remove_attr(AttributeKind::Graph, name)
1297    }
1298
1299    fn remove_attr(&mut self, kind: AttributeKind, name: &str) -> bool {
1300        if !self.has_attribute(kind, name) {
1301            return false;
1302        }
1303        let Ok(c) = CString::new(name) else {
1304            return false;
1305        };
1306        unsafe {
1307            match kind {
1308                AttributeKind::Graph => igraph_cattribute_remove_g(self, c.as_ptr()),
1309                AttributeKind::Vertex => igraph_cattribute_remove_v(self, c.as_ptr()),
1310                AttributeKind::Edge => igraph_cattribute_remove_e(self, c.as_ptr()),
1311            }
1312        }
1313        true
1314    }
1315
1316    /// Removes all the graph, vertex and/or edge attributes
1317    /// ([`igraph_cattribute_remove_all`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_remove_all)).
1318    ///
1319    /// ```
1320    /// use igraph::prelude::*;
1321    /// let mut g = Graph::new(3, false);
1322    /// g.set_graph_attr_str("title", "t").unwrap();
1323    /// g.set_vertex_attr_numeric("x", 0, 1.0).unwrap();
1324    /// g.remove_all_attributes(false, true, true);
1325    /// let list = g.attribute_list().unwrap();
1326    /// assert_eq!(list.graph.len(), 1);
1327    /// assert!(list.vertex.is_empty());
1328    /// ```
1329    pub fn remove_all_attributes(&mut self, graph: bool, vertex: bool, edge: bool) {
1330        if self.has_attr_storage() {
1331            unsafe { igraph_cattribute_remove_all(self, graph, vertex, edge) }
1332        }
1333    }
1334
1335    // --- vertex attributes ----------------------------------------------------
1336
1337    /// The value of a numeric vertex attribute for one vertex
1338    /// ([`igraph_cattribute_VAN`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAN)).
1339    ///
1340    /// Vertices that never had the attribute set hold `NaN`.
1341    ///
1342    /// # Errors
1343    ///
1344    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1345    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not numeric.
1346    ///
1347    /// ```
1348    /// use igraph::prelude::*;
1349    /// let mut g = Graph::new(3, false);
1350    /// g.set_vertex_attr_numeric("age", 1, 42.0).unwrap();
1351    /// assert_eq!(g.vertex_attr_numeric("age", 1).unwrap(), 42.0);
1352    /// assert!(g.vertex_attr_numeric("age", 0).unwrap().is_nan());
1353    /// assert_eq!(g.vertex_attr_numeric("age", 3).unwrap_err().kind(), ErrorKind::InvalidVertexId);
1354    /// ```
1355    pub fn vertex_attr_numeric(&self, name: &str, vid: VertexId) -> Result<f64> {
1356        let c = c_string("attribute name", name)?;
1357        self.check_vid(vid)?;
1358        self.expect_type(AttributeKind::Vertex, name, AttributeType::Numeric)?;
1359        Ok(unsafe { igraph_cattribute_VAN(self, c.as_ptr(), vid) })
1360    }
1361
1362    /// The value of a boolean vertex attribute for one vertex
1363    /// ([`igraph_cattribute_VAB`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAB)).
1364    ///
1365    /// # Errors
1366    ///
1367    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1368    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not boolean.
1369    pub fn vertex_attr_bool(&self, name: &str, vid: VertexId) -> Result<bool> {
1370        let c = c_string("attribute name", name)?;
1371        self.check_vid(vid)?;
1372        self.expect_type(AttributeKind::Vertex, name, AttributeType::Boolean)?;
1373        Ok(unsafe { igraph_cattribute_VAB(self, c.as_ptr(), vid) })
1374    }
1375
1376    /// The value of a string vertex attribute for one vertex, copied into a
1377    /// [`String`] ([`igraph_cattribute_VAS`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAS)).
1378    ///
1379    /// # Errors
1380    ///
1381    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1382    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not a string.
1383    pub fn vertex_attr_str(&self, name: &str, vid: VertexId) -> Result<String> {
1384        let c = c_string("attribute name", name)?;
1385        self.check_vid(vid)?;
1386        self.expect_type(AttributeKind::Vertex, name, AttributeType::String)?;
1387        Ok(lossy(unsafe {
1388            igraph_cattribute_VAS(self, c.as_ptr(), vid)
1389        }))
1390    }
1391
1392    /// The value of a vertex attribute of any type for one vertex (`VAN`/`VAB`/`VAS`).
1393    ///
1394    /// # Errors
1395    ///
1396    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1397    /// [`ErrorKind::InvalidValue`] if the attribute does not exist.
1398    pub fn vertex_attr(&self, name: &str, vid: VertexId) -> Result<AttributeValue> {
1399        match self.attribute_type(AttributeKind::Vertex, name)? {
1400            Some(AttributeType::Numeric) => self
1401                .vertex_attr_numeric(name, vid)
1402                .map(AttributeValue::Numeric),
1403            Some(AttributeType::Boolean) => self
1404                .vertex_attr_bool(name, vid)
1405                .map(AttributeValue::Boolean),
1406            Some(AttributeType::String) => {
1407                self.vertex_attr_str(name, vid).map(AttributeValue::String)
1408            }
1409            _ => Err(missing(AttributeKind::Vertex, name)),
1410        }
1411    }
1412
1413    /// The values of a numeric vertex attribute for the selected vertices, in
1414    /// selector order ([`igraph_cattribute_VANV`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VANV)).
1415    ///
1416    /// Use `..` to select all the vertices.
1417    ///
1418    /// Time complexity: O(v), the number of selected vertices.
1419    ///
1420    /// # Errors
1421    ///
1422    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1423    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not numeric.
1424    ///
1425    /// ```
1426    /// use igraph::prelude::*;
1427    /// let mut g = Graph::new(4, false);
1428    /// g.set_vertex_attr_numeric_values("x", &[0.0, 0.5, 1.0, 1.5]).unwrap();
1429    /// assert_eq!(g.vertex_attr_numeric_values("x", vec![3, 0]).unwrap(), vec![1.5, 0.0]);
1430    /// assert_eq!(g.vertex_attr_numeric_values("x", 1..3).unwrap(), vec![0.5, 1.0]);
1431    /// ```
1432    pub fn vertex_attr_numeric_values<'a>(
1433        &self,
1434        name: &str,
1435        vids: impl Into<VertexSelector<'a>>,
1436    ) -> Result<Vec<f64>> {
1437        let c = c_string("attribute name", name)?;
1438        let vs = vids.into().to_raw()?;
1439        if !self.has_attr_storage() {
1440            return Err(missing(AttributeKind::Vertex, name));
1441        }
1442        let mut res = Vector::new();
1443        igraph_call!(igraph_cattribute_VANV(self, c.as_ptr(), vs.get(), &mut res))?;
1444        Ok(res.into())
1445    }
1446
1447    /// The values of a boolean vertex attribute for the selected vertices
1448    /// ([`igraph_cattribute_VABV`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VABV)).
1449    ///
1450    /// # Errors
1451    ///
1452    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1453    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not boolean.
1454    pub fn vertex_attr_bool_values<'a>(
1455        &self,
1456        name: &str,
1457        vids: impl Into<VertexSelector<'a>>,
1458    ) -> Result<Vec<bool>> {
1459        let c = c_string("attribute name", name)?;
1460        let vs = vids.into().to_raw()?;
1461        if !self.has_attr_storage() {
1462            return Err(missing(AttributeKind::Vertex, name));
1463        }
1464        let mut res = VectorBool::new();
1465        igraph_call!(igraph_cattribute_VABV(self, c.as_ptr(), vs.get(), &mut res))?;
1466        Ok(res.into())
1467    }
1468
1469    /// The values of a string vertex attribute for the selected vertices
1470    /// ([`igraph_cattribute_VASV`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VASV)).
1471    ///
1472    /// # Errors
1473    ///
1474    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1475    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not a string.
1476    ///
1477    /// ```
1478    /// use igraph::prelude::*;
1479    /// let mut g = Graph::famous("Krackhardt_Kite").unwrap();
1480    /// let names = ["Andre", "Beverly", "Carol", "Diane", "Ed", "Fernando", "Garth", "Heather", "Ike", "Jane"];
1481    /// g.set_vertex_attr_str_values("name", &names).unwrap();
1482    /// // The names of Jane's neighbors (Jane is the tail of the kite).
1483    /// let jane = VertexSelector::Adjacent { vertex: 9, mode: NeighborMode::All };
1484    /// assert_eq!(g.vertex_attr_str_values("name", jane).unwrap(), vec!["Ike"]);
1485    /// ```
1486    pub fn vertex_attr_str_values<'a>(
1487        &self,
1488        name: &str,
1489        vids: impl Into<VertexSelector<'a>>,
1490    ) -> Result<Vec<String>> {
1491        let c = c_string("attribute name", name)?;
1492        let vs = vids.into().to_raw()?;
1493        if !self.has_attr_storage() {
1494            return Err(missing(AttributeKind::Vertex, name));
1495        }
1496        let mut res = StrVector::new();
1497        igraph_call!(igraph_cattribute_VASV(self, c.as_ptr(), vs.get(), &mut res))?;
1498        Ok(res.to_vec())
1499    }
1500
1501    /// The values of a vertex attribute of any type for the selected vertices
1502    /// (`VANV`/`VABV`/`VASV`).
1503    ///
1504    /// # Errors
1505    ///
1506    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1507    /// [`ErrorKind::InvalidValue`] if the attribute does not exist.
1508    pub fn vertex_attr_values<'a>(
1509        &self,
1510        name: &str,
1511        vids: impl Into<VertexSelector<'a>>,
1512    ) -> Result<AttributeValues> {
1513        match self.attribute_type(AttributeKind::Vertex, name)? {
1514            Some(AttributeType::Numeric) => self
1515                .vertex_attr_numeric_values(name, vids)
1516                .map(AttributeValues::Numeric),
1517            Some(AttributeType::Boolean) => self
1518                .vertex_attr_bool_values(name, vids)
1519                .map(AttributeValues::Boolean),
1520            Some(AttributeType::String) => self
1521                .vertex_attr_str_values(name, vids)
1522                .map(AttributeValues::String),
1523            _ => Err(missing(AttributeKind::Vertex, name)),
1524        }
1525    }
1526
1527    /// Sets a numeric vertex attribute for one vertex
1528    /// ([`igraph_cattribute_VAN_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAN_set)).
1529    ///
1530    /// A new attribute is created for all the vertices, with `NaN` for the
1531    /// others. Enables attributes (see [`enable`]) if they are not yet.
1532    ///
1533    /// Time complexity: O(n) if the attribute is new, O(1) otherwise.
1534    ///
1535    /// # Errors
1536    ///
1537    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1538    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type.
1539    pub fn set_vertex_attr_numeric(&mut self, name: &str, vid: VertexId, value: f64) -> Result<()> {
1540        let c = c_string("attribute name", name)?;
1541        self.check_vid(vid)?;
1542        self.ensure_attr_storage()?;
1543        igraph_call!(igraph_cattribute_VAN_set(self, c.as_ptr(), vid, value))
1544    }
1545
1546    /// Sets a boolean vertex attribute for one vertex (`false` for the others
1547    /// if new) ([`igraph_cattribute_VAB_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAB_set)).
1548    ///
1549    /// # Errors
1550    ///
1551    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1552    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type.
1553    pub fn set_vertex_attr_bool(&mut self, name: &str, vid: VertexId, value: bool) -> Result<()> {
1554        let c = c_string("attribute name", name)?;
1555        self.check_vid(vid)?;
1556        self.ensure_attr_storage()?;
1557        igraph_call!(igraph_cattribute_VAB_set(self, c.as_ptr(), vid, value))
1558    }
1559
1560    /// Sets a string vertex attribute for one vertex (`""` for the others if
1561    /// new) ([`igraph_cattribute_VAS_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAS_set)).
1562    ///
1563    /// # Errors
1564    ///
1565    /// [`ErrorKind::InvalidVertexId`] for an invalid vertex,
1566    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type
1567    /// or a string contains a NUL byte.
1568    pub fn set_vertex_attr_str(&mut self, name: &str, vid: VertexId, value: &str) -> Result<()> {
1569        let c = c_string("attribute name", name)?;
1570        let v = c_string("string value", value)?;
1571        self.check_vid(vid)?;
1572        self.ensure_attr_storage()?;
1573        igraph_call!(igraph_cattribute_VAS_set(self, c.as_ptr(), vid, v.as_ptr()))
1574    }
1575
1576    /// Sets a vertex attribute of any type for one vertex (`VAN_set`/`VAB_set`/`VAS_set`).
1577    ///
1578    /// # Errors
1579    ///
1580    /// As the typed setters, e.g. [`set_vertex_attr_numeric`](Self::set_vertex_attr_numeric).
1581    ///
1582    /// ```
1583    /// use igraph::prelude::*;
1584    /// let mut g = Graph::new(2, false);
1585    /// g.set_vertex_attr("color", 0, "red").unwrap();
1586    /// g.set_vertex_attr("size", 1, 3.5).unwrap();
1587    /// assert_eq!(g.vertex_attr_str_values("color", ..).unwrap(), vec!["red", ""]);
1588    /// ```
1589    pub fn set_vertex_attr(
1590        &mut self,
1591        name: &str,
1592        vid: VertexId,
1593        value: impl Into<AttributeValue>,
1594    ) -> Result<()> {
1595        match value.into() {
1596            AttributeValue::Numeric(x) => self.set_vertex_attr_numeric(name, vid, x),
1597            AttributeValue::Boolean(b) => self.set_vertex_attr_bool(name, vid, b),
1598            AttributeValue::String(s) => self.set_vertex_attr_str(name, vid, &s),
1599        }
1600    }
1601
1602    /// Sets a numeric vertex attribute for all the vertices at once
1603    /// ([`igraph_cattribute_VAN_setv`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAN_setv)).
1604    ///
1605    /// `values[i]` is the value of vertex `i`.
1606    ///
1607    /// # Errors
1608    ///
1609    /// [`ErrorKind::InvalidValue`] if `values.len()` is not the number of
1610    /// vertices or the attribute exists with another type.
1611    pub fn set_vertex_attr_numeric_values(&mut self, name: &str, values: &[f64]) -> Result<()> {
1612        let c = c_string("attribute name", name)?;
1613        self.ensure_attr_storage()?;
1614        let v = Vector::view(values);
1615        igraph_call!(igraph_cattribute_VAN_setv(self, c.as_ptr(), v.as_ptr()))
1616    }
1617
1618    /// Sets a boolean vertex attribute for all the vertices at once
1619    /// ([`igraph_cattribute_VAB_setv`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAB_setv)).
1620    ///
1621    /// # Errors
1622    ///
1623    /// [`ErrorKind::InvalidValue`] if `values.len()` is not the number of
1624    /// vertices or the attribute exists with another type.
1625    pub fn set_vertex_attr_bool_values(&mut self, name: &str, values: &[bool]) -> Result<()> {
1626        let c = c_string("attribute name", name)?;
1627        self.ensure_attr_storage()?;
1628        let v = VectorBool::view(values);
1629        igraph_call!(igraph_cattribute_VAB_setv(self, c.as_ptr(), v.as_ptr()))
1630    }
1631
1632    /// Sets a string vertex attribute for all the vertices at once
1633    /// ([`igraph_cattribute_VAS_setv`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_VAS_setv)).
1634    ///
1635    /// # Errors
1636    ///
1637    /// [`ErrorKind::InvalidValue`] if `values.len()` is not the number of
1638    /// vertices, the attribute exists with another type or a string contains
1639    /// a NUL byte.
1640    pub fn set_vertex_attr_str_values<S: AsRef<str>>(
1641        &mut self,
1642        name: &str,
1643        values: &[S],
1644    ) -> Result<()> {
1645        let c = c_string("attribute name", name)?;
1646        let sv = to_strvector(values)?;
1647        self.ensure_attr_storage()?;
1648        igraph_call!(igraph_cattribute_VAS_setv(self, c.as_ptr(), &sv))
1649    }
1650
1651    /// Sets a vertex attribute of any type for all the vertices at once
1652    /// (`VAN_setv`/`VAB_setv`/`VAS_setv`).
1653    ///
1654    /// # Errors
1655    ///
1656    /// As the typed setters, e.g. [`set_vertex_attr_numeric_values`](Self::set_vertex_attr_numeric_values).
1657    ///
1658    /// ```
1659    /// use igraph::prelude::*;
1660    /// let mut g = Graph::new(3, false);
1661    /// g.set_vertex_attr_values("seen", vec![true, false, true]).unwrap();
1662    /// assert_eq!(g.vertex_attr_bool_values("seen", ..).unwrap(), vec![true, false, true]);
1663    /// assert!(g.set_vertex_attr_values("seen", vec![true]).is_err()); // wrong length
1664    /// ```
1665    pub fn set_vertex_attr_values(
1666        &mut self,
1667        name: &str,
1668        values: impl Into<AttributeValues>,
1669    ) -> Result<()> {
1670        match values.into() {
1671            AttributeValues::Numeric(v) => self.set_vertex_attr_numeric_values(name, &v),
1672            AttributeValues::Boolean(v) => self.set_vertex_attr_bool_values(name, &v),
1673            AttributeValues::String(v) => self.set_vertex_attr_str_values(name, &v),
1674        }
1675    }
1676
1677    /// Removes a vertex attribute, returning whether it existed
1678    /// ([`igraph_cattribute_remove_v`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_remove_v)).
1679    ///
1680    /// Once removed, the name can be reused for an attribute of another type.
1681    ///
1682    /// ```
1683    /// use igraph::prelude::*;
1684    /// let mut g = Graph::new(2, false);
1685    /// g.set_vertex_attr_numeric("tmp", 0, 1.0).unwrap();
1686    /// assert!(g.remove_vertex_attr("tmp"));
1687    /// assert!(!g.remove_vertex_attr("tmp"));
1688    /// g.set_vertex_attr_str("tmp", 0, "now a string").unwrap();
1689    /// ```
1690    pub fn remove_vertex_attr(&mut self, name: &str) -> bool {
1691        self.remove_attr(AttributeKind::Vertex, name)
1692    }
1693
1694    // --- edge attributes ------------------------------------------------------
1695
1696    /// The value of a numeric edge attribute for one edge
1697    /// ([`igraph_cattribute_EAN`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAN)).
1698    ///
1699    /// # Errors
1700    ///
1701    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1702    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not numeric.
1703    pub fn edge_attr_numeric(&self, name: &str, eid: EdgeId) -> Result<f64> {
1704        let c = c_string("attribute name", name)?;
1705        self.check_eid(eid)?;
1706        self.expect_type(AttributeKind::Edge, name, AttributeType::Numeric)?;
1707        Ok(unsafe { igraph_cattribute_EAN(self, c.as_ptr(), eid) })
1708    }
1709
1710    /// The value of a boolean edge attribute for one edge
1711    /// ([`igraph_cattribute_EAB`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAB)).
1712    ///
1713    /// # Errors
1714    ///
1715    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1716    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not boolean.
1717    pub fn edge_attr_bool(&self, name: &str, eid: EdgeId) -> Result<bool> {
1718        let c = c_string("attribute name", name)?;
1719        self.check_eid(eid)?;
1720        self.expect_type(AttributeKind::Edge, name, AttributeType::Boolean)?;
1721        Ok(unsafe { igraph_cattribute_EAB(self, c.as_ptr(), eid) })
1722    }
1723
1724    /// The value of a string edge attribute for one edge
1725    /// ([`igraph_cattribute_EAS`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAS)).
1726    ///
1727    /// # Errors
1728    ///
1729    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1730    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not a string.
1731    pub fn edge_attr_str(&self, name: &str, eid: EdgeId) -> Result<String> {
1732        let c = c_string("attribute name", name)?;
1733        self.check_eid(eid)?;
1734        self.expect_type(AttributeKind::Edge, name, AttributeType::String)?;
1735        Ok(lossy(unsafe {
1736            igraph_cattribute_EAS(self, c.as_ptr(), eid)
1737        }))
1738    }
1739
1740    /// The value of an edge attribute of any type for one edge (`EAN`/`EAB`/`EAS`).
1741    ///
1742    /// # Errors
1743    ///
1744    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1745    /// [`ErrorKind::InvalidValue`] if the attribute does not exist.
1746    pub fn edge_attr(&self, name: &str, eid: EdgeId) -> Result<AttributeValue> {
1747        match self.attribute_type(AttributeKind::Edge, name)? {
1748            Some(AttributeType::Numeric) => self
1749                .edge_attr_numeric(name, eid)
1750                .map(AttributeValue::Numeric),
1751            Some(AttributeType::Boolean) => {
1752                self.edge_attr_bool(name, eid).map(AttributeValue::Boolean)
1753            }
1754            Some(AttributeType::String) => {
1755                self.edge_attr_str(name, eid).map(AttributeValue::String)
1756            }
1757            _ => Err(missing(AttributeKind::Edge, name)),
1758        }
1759    }
1760
1761    /// The values of a numeric edge attribute for the selected edges, in
1762    /// selector order ([`igraph_cattribute_EANV`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EANV)).
1763    ///
1764    /// This is the natural way to obtain a weight vector for the weighted
1765    /// algorithms of the crate: `g.edge_attr_numeric_values("weight", ..)`
1766    /// (see e.g. [`Graph::distances_dijkstra`] and [`Graph::strength`]).
1767    ///
1768    /// Time complexity: O(e), the number of selected edges.
1769    ///
1770    /// # Errors
1771    ///
1772    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1773    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not numeric.
1774    ///
1775    /// ```
1776    /// use igraph::prelude::*;
1777    /// let mut g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1778    /// g.set_edge_attr_numeric_values("weight", &[0.5, 2.0]).unwrap();
1779    /// let weights = g.edge_attr_numeric_values("weight", ..).unwrap();
1780    /// assert_eq!(weights.iter().sum::<f64>(), 2.5);
1781    /// let d = g.distances_dijkstra(0, 2, Some(&weights), NeighborMode::All).unwrap();
1782    /// assert_eq!(d[(0, 0)], 2.5);
1783    /// ```
1784    pub fn edge_attr_numeric_values<'a>(
1785        &self,
1786        name: &str,
1787        eids: impl Into<EdgeSelector<'a>>,
1788    ) -> Result<Vec<f64>> {
1789        let c = c_string("attribute name", name)?;
1790        let es = eids.into().to_raw()?;
1791        if !self.has_attr_storage() {
1792            return Err(missing(AttributeKind::Edge, name));
1793        }
1794        let mut res = Vector::new();
1795        igraph_call!(igraph_cattribute_EANV(self, c.as_ptr(), es.get(), &mut res))?;
1796        Ok(res.into())
1797    }
1798
1799    /// The values of a boolean edge attribute for the selected edges
1800    /// ([`igraph_cattribute_EABV`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EABV)).
1801    ///
1802    /// # Errors
1803    ///
1804    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1805    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not boolean.
1806    pub fn edge_attr_bool_values<'a>(
1807        &self,
1808        name: &str,
1809        eids: impl Into<EdgeSelector<'a>>,
1810    ) -> Result<Vec<bool>> {
1811        let c = c_string("attribute name", name)?;
1812        let es = eids.into().to_raw()?;
1813        if !self.has_attr_storage() {
1814            return Err(missing(AttributeKind::Edge, name));
1815        }
1816        let mut res = VectorBool::new();
1817        igraph_call!(igraph_cattribute_EABV(self, c.as_ptr(), es.get(), &mut res))?;
1818        Ok(res.into())
1819    }
1820
1821    /// The values of a string edge attribute for the selected edges
1822    /// ([`igraph_cattribute_EASV`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EASV)).
1823    ///
1824    /// # Errors
1825    ///
1826    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1827    /// [`ErrorKind::InvalidValue`] if the attribute does not exist or is not a string.
1828    pub fn edge_attr_str_values<'a>(
1829        &self,
1830        name: &str,
1831        eids: impl Into<EdgeSelector<'a>>,
1832    ) -> Result<Vec<String>> {
1833        let c = c_string("attribute name", name)?;
1834        let es = eids.into().to_raw()?;
1835        if !self.has_attr_storage() {
1836            return Err(missing(AttributeKind::Edge, name));
1837        }
1838        let mut res = StrVector::new();
1839        igraph_call!(igraph_cattribute_EASV(self, c.as_ptr(), es.get(), &mut res))?;
1840        Ok(res.to_vec())
1841    }
1842
1843    /// The values of an edge attribute of any type for the selected edges
1844    /// (`EANV`/`EABV`/`EASV`).
1845    ///
1846    /// # Errors
1847    ///
1848    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1849    /// [`ErrorKind::InvalidValue`] if the attribute does not exist.
1850    pub fn edge_attr_values<'a>(
1851        &self,
1852        name: &str,
1853        eids: impl Into<EdgeSelector<'a>>,
1854    ) -> Result<AttributeValues> {
1855        match self.attribute_type(AttributeKind::Edge, name)? {
1856            Some(AttributeType::Numeric) => self
1857                .edge_attr_numeric_values(name, eids)
1858                .map(AttributeValues::Numeric),
1859            Some(AttributeType::Boolean) => self
1860                .edge_attr_bool_values(name, eids)
1861                .map(AttributeValues::Boolean),
1862            Some(AttributeType::String) => self
1863                .edge_attr_str_values(name, eids)
1864                .map(AttributeValues::String),
1865            _ => Err(missing(AttributeKind::Edge, name)),
1866        }
1867    }
1868
1869    /// Sets a numeric edge attribute for one edge (`NaN` for the others if
1870    /// new) ([`igraph_cattribute_EAN_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAN_set)).
1871    ///
1872    /// # Errors
1873    ///
1874    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1875    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type.
1876    pub fn set_edge_attr_numeric(&mut self, name: &str, eid: EdgeId, value: f64) -> Result<()> {
1877        let c = c_string("attribute name", name)?;
1878        self.check_eid(eid)?;
1879        self.ensure_attr_storage()?;
1880        igraph_call!(igraph_cattribute_EAN_set(self, c.as_ptr(), eid, value))
1881    }
1882
1883    /// Sets a boolean edge attribute for one edge (`false` for the others if
1884    /// new) ([`igraph_cattribute_EAB_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAB_set)).
1885    ///
1886    /// # Errors
1887    ///
1888    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1889    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type.
1890    pub fn set_edge_attr_bool(&mut self, name: &str, eid: EdgeId, value: bool) -> Result<()> {
1891        let c = c_string("attribute name", name)?;
1892        self.check_eid(eid)?;
1893        self.ensure_attr_storage()?;
1894        igraph_call!(igraph_cattribute_EAB_set(self, c.as_ptr(), eid, value))
1895    }
1896
1897    /// Sets a string edge attribute for one edge (`""` for the others if new)
1898    /// ([`igraph_cattribute_EAS_set`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAS_set)).
1899    ///
1900    /// # Errors
1901    ///
1902    /// [`ErrorKind::InvalidEdgeId`] for an invalid edge,
1903    /// [`ErrorKind::InvalidValue`] if the attribute exists with another type
1904    /// or a string contains a NUL byte.
1905    pub fn set_edge_attr_str(&mut self, name: &str, eid: EdgeId, value: &str) -> Result<()> {
1906        let c = c_string("attribute name", name)?;
1907        let v = c_string("string value", value)?;
1908        self.check_eid(eid)?;
1909        self.ensure_attr_storage()?;
1910        igraph_call!(igraph_cattribute_EAS_set(self, c.as_ptr(), eid, v.as_ptr()))
1911    }
1912
1913    /// Sets an edge attribute of any type for one edge (`EAN_set`/`EAB_set`/`EAS_set`).
1914    ///
1915    /// A new attribute gets the default value (`NaN`, `false` or `""`) on
1916    /// the other edges.
1917    ///
1918    /// # Errors
1919    ///
1920    /// As the typed setters, e.g. [`set_edge_attr_numeric`](Self::set_edge_attr_numeric).
1921    ///
1922    /// ```
1923    /// use igraph::prelude::*;
1924    /// use igraph::attributes::AttributeValue;
1925    /// let mut g = Graph::from_edges(&[(0, 1), (1, 2)], 3, false).unwrap();
1926    /// g.set_edge_attr("capacity", 1, 10).unwrap();
1927    /// g.set_edge_attr("road", 0, true).unwrap();
1928    /// assert_eq!(g.edge_attr("capacity", 1).unwrap(), AttributeValue::Numeric(10.0));
1929    /// assert!(g.edge_attr_numeric("capacity", 0).unwrap().is_nan());
1930    /// assert_eq!(g.edge_attr_bool_values("road", ..).unwrap(), vec![true, false]);
1931    /// ```
1932    pub fn set_edge_attr(
1933        &mut self,
1934        name: &str,
1935        eid: EdgeId,
1936        value: impl Into<AttributeValue>,
1937    ) -> Result<()> {
1938        match value.into() {
1939            AttributeValue::Numeric(x) => self.set_edge_attr_numeric(name, eid, x),
1940            AttributeValue::Boolean(b) => self.set_edge_attr_bool(name, eid, b),
1941            AttributeValue::String(s) => self.set_edge_attr_str(name, eid, &s),
1942        }
1943    }
1944
1945    /// Sets a numeric edge attribute for all the edges at once
1946    /// ([`igraph_cattribute_EAN_setv`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAN_setv)).
1947    ///
1948    /// `values[i]` is the value of edge `i`.
1949    ///
1950    /// # Errors
1951    ///
1952    /// [`ErrorKind::InvalidValue`] if `values.len()` is not the number of
1953    /// edges or the attribute exists with another type.
1954    pub fn set_edge_attr_numeric_values(&mut self, name: &str, values: &[f64]) -> Result<()> {
1955        let c = c_string("attribute name", name)?;
1956        self.ensure_attr_storage()?;
1957        let v = Vector::view(values);
1958        igraph_call!(igraph_cattribute_EAN_setv(self, c.as_ptr(), v.as_ptr()))
1959    }
1960
1961    /// Sets a boolean edge attribute for all the edges at once
1962    /// ([`igraph_cattribute_EAB_setv`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAB_setv)).
1963    ///
1964    /// # Errors
1965    ///
1966    /// [`ErrorKind::InvalidValue`] if `values.len()` is not the number of
1967    /// edges or the attribute exists with another type.
1968    pub fn set_edge_attr_bool_values(&mut self, name: &str, values: &[bool]) -> Result<()> {
1969        let c = c_string("attribute name", name)?;
1970        self.ensure_attr_storage()?;
1971        let v = VectorBool::view(values);
1972        igraph_call!(igraph_cattribute_EAB_setv(self, c.as_ptr(), v.as_ptr()))
1973    }
1974
1975    /// Sets a string edge attribute for all the edges at once
1976    /// ([`igraph_cattribute_EAS_setv`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_EAS_setv)).
1977    ///
1978    /// # Errors
1979    ///
1980    /// [`ErrorKind::InvalidValue`] if `values.len()` is not the number of
1981    /// edges, the attribute exists with another type or a string contains a
1982    /// NUL byte.
1983    pub fn set_edge_attr_str_values<S: AsRef<str>>(
1984        &mut self,
1985        name: &str,
1986        values: &[S],
1987    ) -> Result<()> {
1988        let c = c_string("attribute name", name)?;
1989        let sv = to_strvector(values)?;
1990        self.ensure_attr_storage()?;
1991        igraph_call!(igraph_cattribute_EAS_setv(self, c.as_ptr(), &sv))
1992    }
1993
1994    /// Sets an edge attribute of any type for all the edges at once
1995    /// (`EAN_setv`/`EAB_setv`/`EAS_setv`).
1996    ///
1997    /// # Errors
1998    ///
1999    /// As the typed setters, e.g. [`set_edge_attr_numeric_values`](Self::set_edge_attr_numeric_values).
2000    pub fn set_edge_attr_values(
2001        &mut self,
2002        name: &str,
2003        values: impl Into<AttributeValues>,
2004    ) -> Result<()> {
2005        match values.into() {
2006            AttributeValues::Numeric(v) => self.set_edge_attr_numeric_values(name, &v),
2007            AttributeValues::Boolean(v) => self.set_edge_attr_bool_values(name, &v),
2008            AttributeValues::String(v) => self.set_edge_attr_str_values(name, &v),
2009        }
2010    }
2011
2012    /// Removes an edge attribute, returning whether it existed
2013    /// ([`igraph_cattribute_remove_e`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_cattribute_remove_e)).
2014    pub fn remove_edge_attr(&mut self, name: &str) -> bool {
2015        self.remove_attr(AttributeKind::Edge, name)
2016    }
2017
2018    // --- structure + attributes -----------------------------------------------
2019
2020    /// Adds `n` vertices together with their attribute values
2021    /// ([`igraph_add_vertices`](https://igraph.org/c/html/latest/igraph-Basic.html#igraph_add_vertices)
2022    /// with an attribute record list).
2023    ///
2024    /// Each record must be named and hold exactly `n` values. New attributes
2025    /// are created (with default values for the existing vertices);
2026    /// existing attributes not mentioned get their default value for the new
2027    /// vertices. Enables attributes if they are not yet.
2028    ///
2029    /// # Errors
2030    ///
2031    /// [`ErrorKind::InvalidValue`] if a record has the wrong length, no name,
2032    /// or a type different from the existing attribute with the same name.
2033    ///
2034    /// ```
2035    /// use igraph::prelude::*;
2036    /// use igraph::attributes::AttributeRecord;
2037    ///
2038    /// let mut g = Graph::new(1, false);
2039    /// g.set_vertex_attr_str("name", 0, "root").unwrap();
2040    /// let names = AttributeRecord::string("name", &["left", "right"]).unwrap();
2041    /// g.add_vertices_with_attributes(2, &[names]).unwrap();
2042    /// assert_eq!(g.vertex_attr_str_values("name", ..).unwrap(), vec!["root", "left", "right"]);
2043    /// ```
2044    pub fn add_vertices_with_attributes(
2045        &mut self,
2046        n: usize,
2047        records: &[AttributeRecord],
2048    ) -> Result<()> {
2049        let n = igraph_int_t::try_from(n)
2050            .map_err(|_| Error::invalid(format!("cannot add {n} vertices")))?;
2051        let list = RecordList::from_records(records)?;
2052        self.ensure_attr_storage()?;
2053        igraph_call!(igraph_add_vertices(self, n, &list.0))
2054    }
2055
2056    /// Adds edges together with their attribute values
2057    /// ([`igraph_add_edges`](https://igraph.org/c/html/latest/igraph-Basic.html#igraph_add_edges)
2058    /// with an attribute record list).
2059    ///
2060    /// Each record must be named and hold exactly `edges.len()` values.
2061    ///
2062    /// # Errors
2063    ///
2064    /// [`ErrorKind::InvalidVertexId`] for invalid endpoints,
2065    /// [`ErrorKind::InvalidValue`] for invalid records (see
2066    /// [`add_vertices_with_attributes`](Self::add_vertices_with_attributes)).
2067    ///
2068    /// ```
2069    /// use igraph::prelude::*;
2070    /// use igraph::attributes::AttributeRecord;
2071    ///
2072    /// let mut g = Graph::new(3, true);
2073    /// let w = AttributeRecord::numeric("weight", &[1.5, 2.5]).unwrap();
2074    /// g.add_edges_with_attributes(&[(0, 1), (1, 2)], &[w]).unwrap();
2075    /// assert_eq!(g.edge_attr_numeric("weight", 1).unwrap(), 2.5);
2076    /// ```
2077    pub fn add_edges_with_attributes(
2078        &mut self,
2079        edges: &[(VertexId, VertexId)],
2080        records: &[AttributeRecord],
2081    ) -> Result<()> {
2082        let flat: VectorInt = edges.iter().flat_map(|&(a, b)| [a, b]).collect();
2083        let list = RecordList::from_records(records)?;
2084        self.ensure_attr_storage()?;
2085        igraph_call!(igraph_add_edges(self, &flat, &list.0))
2086    }
2087
2088    // --- merging with attribute combinations ------------------------------------
2089
2090    /// Removes multi-edges and/or self-loops like [`Graph::simplify`],
2091    /// combining the attributes of the merged edges according to `edge_comb`
2092    /// ([`igraph_simplify`](https://igraph.org/c/html/latest/igraph-Operators.html#igraph_simplify)
2093    /// with an edge attribute combination).
2094    ///
2095    /// When multi-edges are removed, igraph rebuilds the graph: each group of
2096    /// parallel edges becomes one edge whose attributes are computed from the
2097    /// values of the group (a group of a single edge is combined too, e.g.
2098    /// [`Sum`](AttributeCombinationType::Sum) keeps its value). Attributes
2099    /// the combination maps to [`Ignore`](AttributeCombinationType::Ignore)
2100    /// or [`Default`](AttributeCombinationType::Default), and all of them for
2101    /// an empty combination, are dropped. The edge order may change.
2102    ///
2103    /// `edge_comb` is **not** used, and the remaining edges keep all their
2104    /// attributes, when nothing has to be merged: if `remove_multiple` is
2105    /// `false` (loops are then deleted in place), or if igraph already knows
2106    /// that the graph has no multi-edges (e.g. it was simplified before), or
2107    /// nothing at all is to be removed. Graph and vertex attributes are
2108    /// always kept. Without attributes enabled this is exactly
2109    /// [`Graph::simplify`].
2110    ///
2111    /// Time complexity: O(|V|+|E|), plus the cost of the combinations.
2112    ///
2113    /// # Errors
2114    ///
2115    /// [`ErrorKind::AttributeCombination`] if a combination does not apply to
2116    /// the type of an attribute (e.g. summing strings),
2117    /// [`ErrorKind::Unimplemented`] for the numeric median.
2118    ///
2119    /// # Examples
2120    ///
2121    /// ```
2122    /// use igraph::prelude::*;
2123    /// use igraph::attributes::{AttributeCombination, AttributeCombinationType as Comb};
2124    ///
2125    /// // Three calls between 0 and 1, one between 1 and 2.
2126    /// let mut calls = Graph::from_edges(&[(0, 1), (1, 2), (0, 1), (0, 1)], 3, false).unwrap();
2127    /// calls.set_edge_attr_numeric_values("minutes", &[5.0, 2.0, 1.0, 4.0]).unwrap();
2128    /// let comb = AttributeCombination::all(Comb::Sum).unwrap();
2129    /// calls.simplify_with_attributes(true, true, &comb).unwrap();
2130    /// assert_eq!(calls.edge_list(), vec![(0, 1), (1, 2)]);
2131    /// assert_eq!(calls.edge_attr_numeric_values("minutes", ..).unwrap(), vec![10.0, 2.0]);
2132    /// ```
2133    pub fn simplify_with_attributes(
2134        &mut self,
2135        remove_multiple: bool,
2136        remove_loops: bool,
2137        edge_comb: &AttributeCombination,
2138    ) -> Result<()> {
2139        igraph_call!(igraph_simplify(
2140            self,
2141            remove_multiple,
2142            remove_loops,
2143            edge_comb.as_ptr()
2144        ))
2145    }
2146
2147    /// Merges groups of vertices into single vertices like
2148    /// [`Graph::contract_vertices`], combining their attributes according to
2149    /// `vertex_comb` ([`igraph_contract_vertices`](https://igraph.org/c/html/latest/igraph-Operators.html#igraph_contract_vertices)
2150    /// with a vertex attribute combination).
2151    ///
2152    /// `mapping[v]` is the id of the vertex that `v` becomes; use consecutive
2153    /// ids starting at 0 to avoid creating isolated "orphan" vertices. No
2154    /// edge is removed (merged groups get self-loops and multi-edges: use
2155    /// [`simplify_with_attributes`](Self::simplify_with_attributes)
2156    /// afterwards), and edge and graph attributes are kept unchanged.
2157    ///
2158    /// Time complexity: O(|V|+|E|), plus the cost of the combinations.
2159    ///
2160    /// # Errors
2161    ///
2162    /// [`ErrorKind::InvalidValue`] if `mapping` does not have one entry per
2163    /// vertex or contains negative ids (or `i64::MAX`); the
2164    /// combination errors of
2165    /// [`simplify_with_attributes`](Self::simplify_with_attributes).
2166    ///
2167    /// # Examples
2168    ///
2169    /// ```
2170    /// use igraph::prelude::*;
2171    /// use igraph::attributes::{AttributeCombination, AttributeCombinationType as Comb};
2172    ///
2173    /// // Four towns in two provinces.
2174    /// let mut g = Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], 4, false).unwrap();
2175    /// g.set_vertex_attr_numeric_values("population", &[10.0, 5.0, 7.0, 3.0]).unwrap();
2176    /// g.set_vertex_attr_str_values("name", &["Firenze", "Prato", "Pisa", "Lucca"]).unwrap();
2177    /// let comb = AttributeCombination::from_pairs(&[
2178    ///     (Some("population"), Comb::Sum),
2179    ///     (Some("name"), Comb::First),
2180    /// ]).unwrap();
2181    /// g.contract_vertices_with_attributes(&[0, 0, 1, 1], &comb).unwrap();
2182    /// assert_eq!(g.vertex_attr_numeric_values("population", ..).unwrap(), vec![15.0, 10.0]);
2183    /// assert_eq!(g.vertex_attr_str_values("name", ..).unwrap(), vec!["Firenze", "Pisa"]);
2184    /// assert_eq!(g.ecount(), 3); // two self-loops and the edge between the provinces
2185    /// ```
2186    pub fn contract_vertices_with_attributes(
2187        &mut self,
2188        mapping: &[VertexId],
2189        vertex_comb: &AttributeCombination,
2190    ) -> Result<()> {
2191        if mapping.len() != self.vcount() {
2192            return Err(Error::invalid(format!(
2193                "the mapping has {} entries but the graph has {} vertices",
2194                mapping.len(),
2195                self.vcount()
2196            )));
2197        }
2198        // Negative ids index out of bounds in C, and igraph computes the new
2199        // vertex count as `max + 1`, which overflows for `VertexId::MAX`.
2200        if let Some(&bad) = mapping.iter().find(|&&m| !(0..VertexId::MAX).contains(&m)) {
2201            return Err(Error::invalid(format!(
2202                "the mapping contains the invalid vertex id {bad}"
2203            )));
2204        }
2205        let m = VectorInt::view(mapping);
2206        igraph_call!(igraph_contract_vertices(
2207            self,
2208            m.as_ptr(),
2209            vertex_comb.as_ptr()
2210        ))
2211    }
2212
2213    /// Converts a directed graph to an undirected one like
2214    /// [`Graph::to_undirected`], combining the attributes of the directed
2215    /// edges that become a single undirected edge according to `edge_comb`
2216    /// ([`igraph_to_undirected`](https://igraph.org/c/html/latest/igraph-Structural.html#igraph_to_undirected)
2217    /// with an edge attribute combination).
2218    ///
2219    /// With [`ToUndirected::Collapse`] all the edges between a pair of
2220    /// vertices are merged; with [`ToUndirected::Mutual`] each mutual pair
2221    /// `u -> v`, `v -> u` is merged into one edge (non-mutual edges are lost,
2222    /// loops are kept); with [`ToUndirected::Each`] every edge is kept together
2223    /// with its attributes and `edge_comb` is not used. Graph and vertex
2224    /// attributes are kept. Undirected graphs are left unchanged.
2225    ///
2226    /// Time complexity: O(|V|+|E|), plus the cost of the combinations.
2227    ///
2228    /// # Errors
2229    ///
2230    /// The combination errors of
2231    /// [`simplify_with_attributes`](Self::simplify_with_attributes).
2232    ///
2233    /// # Examples
2234    ///
2235    /// ```
2236    /// use igraph::prelude::*;
2237    /// use igraph::attributes::{AttributeCombination, AttributeCombinationType as Comb};
2238    ///
2239    /// // Messages sent in both directions between 0 and 1, and from 1 to 2.
2240    /// let mut g = Graph::from_edges(&[(0, 1), (1, 0), (1, 2)], 3, true).unwrap();
2241    /// g.set_edge_attr_numeric_values("messages", &[3.0, 4.0, 1.0]).unwrap();
2242    /// let comb = AttributeCombination::all(Comb::Sum).unwrap();
2243    /// g.to_undirected_with_attributes(ToUndirected::Collapse, &comb).unwrap();
2244    /// assert_eq!(g.edge_list(), vec![(0, 1), (1, 2)]);
2245    /// assert_eq!(g.edge_attr_numeric_values("messages", ..).unwrap(), vec![7.0, 1.0]);
2246    /// ```
2247    pub fn to_undirected_with_attributes(
2248        &mut self,
2249        mode: ToUndirected,
2250        edge_comb: &AttributeCombination,
2251    ) -> Result<()> {
2252        igraph_call!(igraph_to_undirected(self, mode.into(), edge_comb.as_ptr()))
2253    }
2254}
2255
2256// ---------------------------------------------------------------------------
2257// Attribute records
2258// ---------------------------------------------------------------------------
2259
2260/// A named, typed vector of attribute values (`igraph_attribute_record_t`).
2261///
2262/// Attribute records are the unit of data exchanged between igraph and an
2263/// attribute handler; in Rust they are mostly useful to add vertices or edges
2264/// together with their attributes, see [`Graph::add_vertices_with_attributes`].
2265/// A record owns its values and frees them on [`Drop`]; [`Clone`] makes a
2266/// deep copy, default value included.
2267///
2268/// A record also has a *default value*, used to fill new slots when it is
2269/// [resized](Self::resize): `NaN`, `false` or `""` unless changed with
2270/// [`set_default`](Self::set_default).
2271///
2272/// ```
2273/// use igraph::attributes::{AttributeRecord, AttributeType, AttributeValue, AttributeValues};
2274///
2275/// let mut rec = AttributeRecord::numeric("score", &[1.0, 2.0]).unwrap();
2276/// rec.set_default(AttributeValue::Numeric(-1.0)).unwrap();
2277/// rec.resize(4).unwrap();
2278/// assert_eq!(rec.name(), Some("score"));
2279/// assert_eq!(rec.attribute_type(), AttributeType::Numeric);
2280/// assert_eq!(rec.values(), AttributeValues::Numeric(vec![1.0, 2.0, -1.0, -1.0]));
2281/// ```
2282pub type AttributeRecord = igraph_attribute_record_t;
2283
2284impl igraph_attribute_record_t {
2285    /// Creates an empty record with the given name and type
2286    /// ([`igraph_attribute_record_init`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_init)).
2287    ///
2288    /// An [`AttributeType::Unspecified`] record is untyped: it holds no
2289    /// values until [`set_type`](Self::set_type) gives it a type.
2290    ///
2291    /// Time complexity: O(1).
2292    ///
2293    /// # Errors
2294    ///
2295    /// [`ErrorKind::InvalidValue`] if `name` contains a NUL byte or the type
2296    /// is [`AttributeType::Object`] (checked in Rust: since igraph 1.0.1 the
2297    /// C library treats an unsupported type as a *fatal* error and aborts the
2298    /// process, while 1.0.0 returned an error).
2299    ///
2300    /// # Examples
2301    ///
2302    /// ```
2303    /// use igraph::attributes::{AttributeRecord, AttributeType};
2304    /// let mut rec = AttributeRecord::new("color", AttributeType::String).unwrap();
2305    /// assert!(rec.is_empty());
2306    /// rec.resize(2).unwrap();
2307    /// assert_eq!(rec.values().as_strings().unwrap(), ["", ""]);
2308    /// assert!(AttributeRecord::new("o", AttributeType::Object).is_err());
2309    /// ```
2310    pub fn new(name: &str, attribute_type: AttributeType) -> Result<Self> {
2311        let c = c_string("attribute name", name)?;
2312        if attribute_type == AttributeType::Object {
2313            return Err(Error::invalid("object attributes are not supported"));
2314        }
2315        // SAFETY: `c` is a valid C string and the type is supported.
2316        unsafe { Self::init_raw(c.as_ptr(), attribute_type.into()) }
2317    }
2318
2319    /// `igraph_attribute_record_init` into a new record.
2320    ///
2321    /// # Safety
2322    ///
2323    /// `name` must be NULL or a valid C string, and `type_` must be
2324    /// `UNSPECIFIED`, `NUMERIC`, `BOOLEAN` or `STRING` (igraph 1.0.1 aborts
2325    /// the process on any other type).
2326    unsafe fn init_raw(name: *const c_char, type_: igraph_attribute_type_t) -> Result<Self> {
2327        let mut raw = MaybeUninit::<Self>::uninit();
2328        // On failure the record must *not* be destroyed: igraph 1.0.1 has
2329        // already released everything it allocated (the name through the
2330        // "finally" stack, which leaves `name` dangling), so destroying it
2331        // would free the name twice.
2332        igraph_call!(igraph_attribute_record_init(raw.as_mut_ptr(), name, type_))?;
2333        // SAFETY: `init` succeeded, so every field is initialized.
2334        Ok(unsafe { raw.assume_init() })
2335    }
2336
2337    /// Creates a numeric record holding `values`.
2338    pub fn numeric(name: &str, values: &[f64]) -> Result<Self> {
2339        let mut rec = Self::new(name, AttributeType::Numeric)?;
2340        let v = Vector::view(values);
2341        igraph_call!(igraph_vector_update(
2342            *unsafe { rec.value.as_vector.as_mut() },
2343            v.as_ptr()
2344        ))?;
2345        Ok(rec)
2346    }
2347
2348    /// Creates a boolean record holding `values`.
2349    pub fn boolean(name: &str, values: &[bool]) -> Result<Self> {
2350        let mut rec = Self::new(name, AttributeType::Boolean)?;
2351        let v = VectorBool::view(values);
2352        igraph_call!(igraph_vector_bool_update(
2353            *unsafe { rec.value.as_vector_bool.as_mut() },
2354            v.as_ptr()
2355        ))?;
2356        Ok(rec)
2357    }
2358
2359    /// Creates a string record holding `values`.
2360    ///
2361    /// # Errors
2362    ///
2363    /// [`ErrorKind::InvalidValue`] if a string contains a NUL byte.
2364    pub fn string<S: AsRef<str>>(name: &str, values: &[S]) -> Result<Self> {
2365        let sv = to_strvector(values)?;
2366        let mut rec = Self::new(name, AttributeType::String)?;
2367        igraph_call!(igraph_strvector_update(
2368            *unsafe { rec.value.as_strvector.as_mut() },
2369            &sv
2370        ))?;
2371        Ok(rec)
2372    }
2373
2374    /// Creates a record of the type of `values`.
2375    pub fn from_values(name: &str, values: impl Into<AttributeValues>) -> Result<Self> {
2376        match values.into() {
2377            AttributeValues::Numeric(v) => Self::numeric(name, &v),
2378            AttributeValues::Boolean(v) => Self::boolean(name, &v),
2379            AttributeValues::String(v) => Self::string(name, &v),
2380        }
2381    }
2382
2383    /// The name of the attribute (`None` if unset or not UTF-8).
2384    pub fn name(&self) -> Option<&str> {
2385        if self.name.is_null() {
2386            None
2387        } else {
2388            unsafe { CStr::from_ptr(self.name) }.to_str().ok()
2389        }
2390    }
2391
2392    /// Renames the record
2393    /// ([`igraph_attribute_record_set_name`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_set_name)).
2394    pub fn set_name(&mut self, name: &str) -> Result<()> {
2395        let c = c_string("attribute name", name)?;
2396        igraph_call!(igraph_attribute_record_set_name(self, c.as_ptr()))
2397    }
2398
2399    /// The type of the values.
2400    pub fn attribute_type(&self) -> AttributeType {
2401        AttributeType::try_from(self.type_).unwrap_or(AttributeType::Object)
2402    }
2403
2404    /// Changes the type of the record
2405    /// ([`igraph_attribute_record_set_type`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_set_type)).
2406    ///
2407    /// When the type changes, the values are discarded and the record becomes
2408    /// empty; setting the same type is a no-op.
2409    ///
2410    /// # Errors
2411    ///
2412    /// [`ErrorKind::InvalidValue`] for [`AttributeType::Unspecified`] and
2413    /// [`AttributeType::Object`], checked in Rust: igraph 1.0.1 aborts the
2414    /// process on these (a fatal error in `igraph_attribute_record_set_type`;
2415    /// 1.0.0 returned `IGRAPH_EINVAL`).
2416    ///
2417    /// # Examples
2418    ///
2419    /// ```
2420    /// use igraph::attributes::{AttributeRecord, AttributeType};
2421    /// let mut rec = AttributeRecord::numeric("x", &[1.0, 2.0]).unwrap();
2422    /// rec.set_type(AttributeType::Numeric).unwrap(); // same type: no-op
2423    /// assert_eq!(rec.len(), 2);
2424    /// rec.set_type(AttributeType::Boolean).unwrap(); // new type: values dropped
2425    /// assert!(rec.is_empty());
2426    /// assert!(rec.set_type(AttributeType::Unspecified).is_err());
2427    /// ```
2428    pub fn set_type(&mut self, attribute_type: AttributeType) -> Result<()> {
2429        if matches!(
2430            attribute_type,
2431            AttributeType::Unspecified | AttributeType::Object
2432        ) {
2433            return Err(Error::invalid(format!(
2434                "cannot set the record type to {attribute_type}"
2435            )));
2436        }
2437        igraph_call!(igraph_attribute_record_set_type(
2438            self,
2439            attribute_type.into()
2440        ))
2441    }
2442
2443    /// Checks that the record has the expected type
2444    /// ([`igraph_attribute_record_check_type`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_check_type)).
2445    ///
2446    /// # Errors
2447    ///
2448    /// [`ErrorKind::InvalidValue`] if the types differ.
2449    pub fn check_type(&self, expected: AttributeType) -> Result<()> {
2450        igraph_call!(igraph_attribute_record_check_type(self, expected.into()))
2451    }
2452
2453    /// Number of values
2454    /// ([`igraph_attribute_record_size`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_size)).
2455    pub fn len(&self) -> usize {
2456        match self.attribute_type() {
2457            AttributeType::Numeric | AttributeType::Boolean | AttributeType::String => unsafe {
2458                igraph_attribute_record_size(self) as usize
2459            },
2460            _ => 0,
2461        }
2462    }
2463
2464    /// Whether the record holds no values.
2465    pub fn is_empty(&self) -> bool {
2466        self.len() == 0
2467    }
2468
2469    /// Resizes the value vector, filling new slots with the default value
2470    /// ([`igraph_attribute_record_resize`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_resize)).
2471    ///
2472    /// # Errors
2473    ///
2474    /// [`ErrorKind::InvalidValue`] if the record has no type yet.
2475    pub fn resize(&mut self, len: usize) -> Result<()> {
2476        if !matches!(
2477            self.attribute_type(),
2478            AttributeType::Numeric | AttributeType::Boolean | AttributeType::String
2479        ) {
2480            return Err(Error::invalid("the attribute record has no type yet"));
2481        }
2482        let len = igraph_int_t::try_from(len)
2483            .map_err(|_| Error::invalid(format!("record length {len} is too large")))?;
2484        igraph_call!(igraph_attribute_record_resize(self, len))
2485    }
2486
2487    /// Sets the value used to fill new slots on [`resize`](Self::resize)
2488    /// ([`igraph_attribute_record_set_default_numeric`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_set_default_numeric),
2489    /// [`_boolean`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_set_default_boolean),
2490    /// [`_string`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_record_set_default_string)).
2491    ///
2492    /// # Errors
2493    ///
2494    /// [`ErrorKind::InvalidValue`] if the value's type is not the record's type.
2495    pub fn set_default(&mut self, value: impl Into<AttributeValue>) -> Result<()> {
2496        let value = value.into();
2497        if value.attribute_type() != self.attribute_type() {
2498            return Err(Error::invalid(format!(
2499                "a {} default value does not fit a {} attribute record",
2500                value.attribute_type(),
2501                self.attribute_type()
2502            )));
2503        }
2504        match value {
2505            AttributeValue::Numeric(x) => {
2506                igraph_call!(igraph_attribute_record_set_default_numeric(self, x))
2507            }
2508            AttributeValue::Boolean(b) => {
2509                igraph_call!(igraph_attribute_record_set_default_boolean(self, b))
2510            }
2511            AttributeValue::String(s) => {
2512                let c = c_string("string value", &s)?;
2513                igraph_call!(igraph_attribute_record_set_default_string(self, c.as_ptr()))
2514            }
2515        }
2516    }
2517
2518    /// The value used to fill new slots on [`resize`](Self::resize), see
2519    /// [`set_default`](Self::set_default); `None` for an untyped record.
2520    ///
2521    /// Unless changed, it is `NaN`, `false` or `""` depending on the type.
2522    ///
2523    /// ```
2524    /// use igraph::attributes::{AttributeRecord, AttributeValue};
2525    /// let mut rec = AttributeRecord::string("city", &["Firenze"]).unwrap();
2526    /// assert_eq!(rec.default_value(), Some(AttributeValue::String(String::new())));
2527    /// rec.set_default("unknown").unwrap();
2528    /// assert_eq!(rec.default_value(), Some(AttributeValue::String("unknown".into())));
2529    /// ```
2530    pub fn default_value(&self) -> Option<AttributeValue> {
2531        // SAFETY: the active union member is the one matching `type_`.
2532        unsafe {
2533            match self.attribute_type() {
2534                AttributeType::Numeric => Some(AttributeValue::Numeric(
2535                    *self.default_value.numeric.as_ref(),
2536                )),
2537                AttributeType::Boolean => Some(AttributeValue::Boolean(
2538                    *self.default_value.boolean.as_ref(),
2539                )),
2540                AttributeType::String => Some(AttributeValue::String(lossy(
2541                    *self.default_value.string.as_ref(),
2542                ))),
2543                _ => None,
2544            }
2545        }
2546    }
2547
2548    /// A copy of the values (an empty [`AttributeValues::Numeric`] for an
2549    /// untyped record).
2550    pub fn values(&self) -> AttributeValues {
2551        unsafe {
2552            match self.attribute_type() {
2553                AttributeType::Numeric => {
2554                    AttributeValues::Numeric((**self.value.as_vector.as_ref()).to_vec())
2555                }
2556                AttributeType::Boolean => {
2557                    AttributeValues::Boolean((**self.value.as_vector_bool.as_ref()).to_vec())
2558                }
2559                AttributeType::String => {
2560                    AttributeValues::String((**self.value.as_strvector.as_ref()).to_vec())
2561                }
2562                _ => AttributeValues::Numeric(Vec::new()),
2563            }
2564        }
2565    }
2566}
2567
2568impl Drop for igraph_attribute_record_t {
2569    /// Frees the name and the values (`igraph_attribute_record_destroy`).
2570    fn drop(&mut self) {
2571        unsafe { igraph_attribute_record_destroy(self) }
2572    }
2573}
2574
2575impl Clone for igraph_attribute_record_t {
2576    /// Deep copy of the name, type, values and [default
2577    /// value](Self::default_value).
2578    ///
2579    /// It does not use `igraph_attribute_record_init_copy`: that function
2580    /// does not copy the default value, and when it fails its partially
2581    /// initialized result can be neither destroyed nor leaked safely.
2582    ///
2583    /// # Panics
2584    ///
2585    /// If igraph fails to allocate the copy.
2586    fn clone(&self) -> Self {
2587        let ty = self.attribute_type();
2588        assert!(
2589            ty != AttributeType::Object,
2590            "attribute records of unsupported types cannot be cloned"
2591        );
2592        // SAFETY: `self.name` is NULL or a valid C string owned by `self`,
2593        // and the type is supported (checked above).
2594        let mut copy = unsafe { Self::init_raw(self.name, ty.into()) }
2595            .expect("igraph failed to copy an attribute record");
2596        // From here on `copy` is a valid record, freed by `Drop` on panic.
2597        // SAFETY: both records have type `ty`, so the matching union
2598        // members are the active ones and point to initialized vectors.
2599        igraph_call!(match ty {
2600            AttributeType::Numeric => igraph_vector_update(
2601                *copy.value.as_vector.as_mut(),
2602                *self.value.as_vector.as_ref(),
2603            ),
2604            AttributeType::Boolean => igraph_vector_bool_update(
2605                *copy.value.as_vector_bool.as_mut(),
2606                *self.value.as_vector_bool.as_ref(),
2607            ),
2608            AttributeType::String => igraph_strvector_update(
2609                *copy.value.as_strvector.as_mut(),
2610                *self.value.as_strvector.as_ref(),
2611            ),
2612            _ => igraph_error_type_t_IGRAPH_SUCCESS,
2613        })
2614        .expect("igraph failed to copy an attribute record");
2615        if let Some(default) = self.default_value() {
2616            copy.set_default(default)
2617                .expect("igraph failed to copy the default value of an attribute record");
2618        }
2619        copy
2620    }
2621}
2622
2623impl PartialEq for igraph_attribute_record_t {
2624    /// Records are equal when their names, types, values and default values
2625    /// are. Records are compared as data: a `NaN` number equals another
2626    /// `NaN` (the default value of a numeric record is `NaN` unless changed,
2627    /// so with `f64` semantics a numeric record would never equal its clone).
2628    fn eq(&self, other: &Self) -> bool {
2629        fn same(a: f64, b: f64) -> bool {
2630            a == b || (a.is_nan() && b.is_nan())
2631        }
2632        let same_values = match (self.values(), other.values()) {
2633            (AttributeValues::Numeric(a), AttributeValues::Numeric(b)) => {
2634                a.len() == b.len() && a.iter().zip(&b).all(|(&x, &y)| same(x, y))
2635            }
2636            (a, b) => a == b,
2637        };
2638        let same_default = match (self.default_value(), other.default_value()) {
2639            (Some(AttributeValue::Numeric(a)), Some(AttributeValue::Numeric(b))) => same(a, b),
2640            (a, b) => a == b,
2641        };
2642        self.name() == other.name()
2643            && self.attribute_type() == other.attribute_type()
2644            && same_values
2645            && same_default
2646    }
2647}
2648
2649impl fmt::Debug for igraph_attribute_record_t {
2650    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2651        f.debug_struct("AttributeRecord")
2652            .field("name", &self.name())
2653            .field("type", &self.attribute_type())
2654            .field("values", &self.values())
2655            .field("default", &self.default_value())
2656            .finish()
2657    }
2658}
2659
2660// A record exclusively owns its heap data.
2661unsafe impl Send for igraph_attribute_record_t {}
2662unsafe impl Sync for igraph_attribute_record_t {}
2663
2664// ---------------------------------------------------------------------------
2665// Attribute combinations
2666// ---------------------------------------------------------------------------
2667
2668/// Signature of a numeric combination function of the C attribute handler:
2669/// `input` holds the values of the merged elements, the result goes to `output`.
2670pub type NumericCombineFn = unsafe extern "C" fn(
2671    input: *const igraph_vector_t,
2672    output: *mut igraph_real_t,
2673) -> igraph_error_t;
2674
2675/// Signature of a boolean combination function of the C attribute handler.
2676pub type BooleanCombineFn = unsafe extern "C" fn(
2677    input: *const igraph_vector_bool_t,
2678    output: *mut igraph_bool_t,
2679) -> igraph_error_t;
2680
2681/// A user supplied C function combining attribute values, see
2682/// [`AttributeCombination::add_function`].
2683#[derive(Debug, Clone, Copy)]
2684pub enum CombineFunction {
2685    /// For numeric attributes.
2686    Numeric(NumericCombineFn),
2687    /// For boolean attributes.
2688    Boolean(BooleanCombineFn),
2689}
2690
2691/// How to combine attributes when vertices or edges are merged
2692/// (`igraph_attribute_combination_t`).
2693///
2694/// It maps attribute names to an [`AttributeCombinationType`]; the entry
2695/// with no name (`None`) is the default for every attribute not listed. An
2696/// empty combination drops all the attributes of merged elements.
2697///
2698/// Pass it to [`Graph::simplify_with_attributes`],
2699/// [`Graph::contract_vertices_with_attributes`] or
2700/// [`Graph::to_undirected_with_attributes`], or to other igraph functions
2701/// merging elements through the raw FFI with [`as_ptr`](Self::as_ptr).
2702///
2703/// ```
2704/// use igraph::attributes::{AttributeCombination, AttributeCombinationType as Comb};
2705///
2706/// let comb = AttributeCombination::from_pairs(&[
2707///     (Some("weight"), Comb::Sum),
2708///     (Some("name"), Comb::First),
2709///     (None, Comb::Ignore),
2710/// ]).unwrap();
2711/// assert_eq!(comb.query(Some("weight")).unwrap(), Comb::Sum);
2712/// assert_eq!(comb.query(Some("color")).unwrap(), Comb::Ignore); // falls back to the default
2713///
2714/// // Merge the two parallel edges: weights are summed, other attributes dropped.
2715/// use igraph::prelude::*;
2716/// let mut g = Graph::from_edges(&[(0, 1), (0, 1)], 2, false).unwrap();
2717/// g.set_edge_attr_numeric_values("weight", &[1.5, 2.0]).unwrap();
2718/// g.set_edge_attr_str_values("color", &["red", "blue"]).unwrap();
2719/// g.simplify_with_attributes(true, true, &comb).unwrap();
2720/// assert_eq!(g.edge_attr_numeric("weight", 0).unwrap(), 3.5);
2721/// assert!(!g.has_attribute(igraph::attributes::AttributeKind::Edge, "color"));
2722/// ```
2723pub type AttributeCombination = igraph_attribute_combination_t;
2724
2725impl igraph_attribute_combination_t {
2726    /// Creates an empty combination list
2727    /// ([`igraph_attribute_combination_init`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_combination_init)).
2728    ///
2729    /// # Panics
2730    ///
2731    /// If igraph fails to allocate it.
2732    pub fn new() -> Self {
2733        ensure_init();
2734        let mut raw = MaybeUninit::<Self>::uninit();
2735        crate::error::check(unsafe { igraph_attribute_combination_init(raw.as_mut_ptr()) })
2736            .expect("igraph failed to allocate an attribute combination");
2737        unsafe { raw.assume_init() }
2738    }
2739
2740    /// Creates a combination list from `(name, type)` pairs, `None` naming the
2741    /// default entry (the Rust counterpart of the variadic
2742    /// [`igraph_attribute_combination`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_combination)).
2743    ///
2744    /// # Errors
2745    ///
2746    /// As [`add`](Self::add).
2747    pub fn from_pairs(pairs: &[(Option<&str>, AttributeCombinationType)]) -> Result<Self> {
2748        let mut comb = Self::new();
2749        for &(name, kind) in pairs {
2750            comb.add(name, kind)?;
2751        }
2752        Ok(comb)
2753    }
2754
2755    /// A combination applying `kind` to every attribute (a single default
2756    /// entry).
2757    ///
2758    /// # Errors
2759    ///
2760    /// As [`add`](Self::add).
2761    ///
2762    /// ```
2763    /// use igraph::attributes::{AttributeCombination, AttributeCombinationType as Comb};
2764    /// let comb = AttributeCombination::all(Comb::Max).unwrap();
2765    /// assert_eq!(comb.query(None).unwrap(), Comb::Max);
2766    /// assert_eq!(comb.query(Some("anything")).unwrap(), Comb::Max);
2767    /// ```
2768    pub fn all(kind: AttributeCombinationType) -> Result<Self> {
2769        Self::from_pairs(&[(None, kind)])
2770    }
2771
2772    /// Adds or replaces the entry of an attribute (`None`: the default entry)
2773    /// ([`igraph_attribute_combination_add`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_combination_add)).
2774    ///
2775    /// # Errors
2776    ///
2777    /// [`ErrorKind::InvalidValue`] for [`AttributeCombinationType::Function`]
2778    /// (use [`add_function`](Self::add_function)) or a name containing a NUL byte.
2779    pub fn add(&mut self, name: Option<&str>, kind: AttributeCombinationType) -> Result<()> {
2780        if kind == AttributeCombinationType::Function {
2781            return Err(Error::invalid(
2782                "use `add_function` to combine attributes with a function",
2783            ));
2784        }
2785        let c = name.map(|n| c_string("attribute name", n)).transpose()?;
2786        let p = c.as_ref().map_or(ptr::null(), |c| c.as_ptr());
2787        igraph_call!(igraph_attribute_combination_add(self, p, kind.into(), None))
2788    }
2789
2790    /// Adds or replaces the entry of an attribute with a user supplied C
2791    /// function ([`igraph_attribute_combination_add`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_combination_add)
2792    /// with `IGRAPH_ATTRIBUTE_COMBINE_FUNCTION`).
2793    ///
2794    /// For each merged vertex or edge, the function receives the values of the
2795    /// merged elements and writes the combined value.
2796    ///
2797    /// # Safety
2798    ///
2799    /// The C attribute handler chooses the calling convention from the *type
2800    /// of the attribute* at combination time: the function must match it, i.e.
2801    /// a [`CombineFunction::Numeric`] may only be registered for numeric
2802    /// attributes and a [`CombineFunction::Boolean`] for boolean ones
2803    /// (registering a default entry with `name = None` requires *every*
2804    /// combined attribute to have that type). The function must not unwind.
2805    /// String functions are not supported by this wrapper (igraph would need to
2806    /// free a string allocated by Rust).
2807    ///
2808    /// ```
2809    /// use igraph::{ffi, attributes::{AttributeCombination, CombineFunction}};
2810    ///
2811    /// unsafe extern "C" fn count(
2812    ///     input: *const ffi::igraph_vector_t,
2813    ///     output: *mut f64,
2814    /// ) -> ffi::igraph_error_t {
2815    ///     unsafe { *output = (&*input).len() as f64 };
2816    ///     ffi::igraph_error_type_t_IGRAPH_SUCCESS
2817    /// }
2818    ///
2819    /// let mut comb = AttributeCombination::new();
2820    /// // SAFETY: "multiplicity" will be a numeric attribute.
2821    /// unsafe { comb.add_function(Some("multiplicity"), CombineFunction::Numeric(count)) }.unwrap();
2822    /// ```
2823    pub unsafe fn add_function(&mut self, name: Option<&str>, func: CombineFunction) -> Result<()> {
2824        let c = name.map(|n| c_string("attribute name", n)).transpose()?;
2825        let p = c.as_ref().map_or(ptr::null(), |c| c.as_ptr());
2826        // SAFETY: igraph stores the pointer type-erased and casts it back to the
2827        // signature matching the attribute type (the caller's responsibility).
2828        let erased: unsafe extern "C" fn() = unsafe {
2829            match func {
2830                CombineFunction::Numeric(f) => {
2831                    std::mem::transmute::<NumericCombineFn, unsafe extern "C" fn()>(f)
2832                }
2833                CombineFunction::Boolean(f) => {
2834                    std::mem::transmute::<BooleanCombineFn, unsafe extern "C" fn()>(f)
2835                }
2836            }
2837        };
2838        igraph_call!(igraph_attribute_combination_add(
2839            self,
2840            p,
2841            AttributeCombinationType::Function.into(),
2842            Some(erased)
2843        ))
2844    }
2845
2846    /// Removes the entry of an attribute (`None`: the default entry); missing
2847    /// entries are ignored
2848    /// ([`igraph_attribute_combination_remove`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_combination_remove)).
2849    pub fn remove(&mut self, name: Option<&str>) -> Result<()> {
2850        let c = name.map(|n| c_string("attribute name", n)).transpose()?;
2851        let p = c.as_ref().map_or(ptr::null(), |c| c.as_ptr());
2852        igraph_call!(igraph_attribute_combination_remove(self, p))
2853    }
2854
2855    /// How the attribute `name` will be combined
2856    /// ([`igraph_attribute_combination_query`](https://igraph.org/c/html/latest/igraph-Attributes.html#igraph_attribute_combination_query)).
2857    ///
2858    /// Falls back to the default entry, and to
2859    /// [`AttributeCombinationType::Default`] when there is none.
2860    pub fn query(&self, name: Option<&str>) -> Result<AttributeCombinationType> {
2861        let c = name.map(|n| c_string("attribute name", n)).transpose()?;
2862        let p = c.as_ref().map_or(ptr::null(), |c| c.as_ptr());
2863        let mut kind = igraph_attribute_combination_type_t_IGRAPH_ATTRIBUTE_COMBINE_DEFAULT;
2864        let mut func: igraph_function_pointer_t = None;
2865        igraph_call!(igraph_attribute_combination_query(
2866            self, p, &mut kind, &mut func
2867        ))?;
2868        AttributeCombinationType::try_from(kind)
2869    }
2870
2871    /// Raw pointer for the igraph functions taking a
2872    /// `const igraph_attribute_combination_t *`.
2873    pub fn as_ptr(&self) -> *const igraph_attribute_combination_t {
2874        self
2875    }
2876}
2877
2878impl Default for igraph_attribute_combination_t {
2879    fn default() -> Self {
2880        Self::new()
2881    }
2882}
2883
2884impl Drop for igraph_attribute_combination_t {
2885    /// Frees the list (`igraph_attribute_combination_destroy`).
2886    fn drop(&mut self) {
2887        if !self.list.stor_begin.is_null() {
2888            unsafe { igraph_attribute_combination_destroy(self) };
2889            self.list.stor_begin = ptr::null_mut();
2890        }
2891    }
2892}
2893
2894// The list exclusively owns its records.
2895unsafe impl Send for igraph_attribute_combination_t {}