Skip to main content

causal_hub/models/graphs/
undirected.rs

1use ndarray::prelude::*;
2use serde::{
3    Deserialize, Deserializer, Serialize, Serializer,
4    de::{MapAccess, Visitor},
5    ser::SerializeMap,
6};
7
8use crate::{
9    impl_json_io,
10    models::{Graph, HasLabels},
11    types::{Error, Labels, Result, Set},
12};
13
14/// A struct representing an undirected graph using an adjacency matrix.
15#[derive(Clone, Debug)]
16pub struct UnGraph {
17    labels: Labels,
18    adjacency_matrix: Array2<bool>,
19}
20
21impl UnGraph {
22    /// Check if a vertex is within bounds.
23    #[inline]
24    fn check_vertex(&self, x: usize) -> Result<()> {
25        if x >= self.labels.len() {
26            return Err(Error::IndexOutOfBounds(x));
27        }
28        Ok(())
29    }
30
31    /// Returns the neighbors of a set of vertices.
32    ///
33    /// # Arguments
34    ///
35    /// * `x` - The set of vertices for which to find the neighbors.
36    ///
37    /// # Errors
38    ///
39    /// * If any vertex is out of bounds.
40    ///
41    /// # Returns
42    ///
43    /// The neighbors of the vertex.
44    ///
45    pub fn neighbors(&self, x: &Set<usize>) -> Result<Set<usize>> {
46        // Check if the vertices are within bounds.
47        x.iter().try_for_each(|&v| self.check_vertex(v))?;
48
49        // Iterate over all vertices and filter the ones that are neighbors.
50        let mut neighbors: Set<_> = x
51            .iter()
52            .flat_map(|&v| {
53                self.adjacency_matrix
54                    .row(v)
55                    .into_iter()
56                    .enumerate()
57                    .filter_map(|(y, &has_edge)| has_edge.then_some(y))
58            })
59            .collect();
60
61        // Sort the neighbors.
62        neighbors.sort();
63
64        // Return the neighbors.
65        Ok(neighbors)
66    }
67}
68
69impl HasLabels for UnGraph {
70    fn labels(&self) -> &Labels {
71        &self.labels
72    }
73}
74
75impl Graph for UnGraph {
76    fn empty<I, V>(labels: I) -> Result<Self>
77    where
78        I: IntoIterator<Item = V>,
79        V: AsRef<str>,
80    {
81        // Initialize labels counter.
82        let mut n = 0;
83        // Collect the labels.
84        let mut labels: Labels = labels
85            .into_iter()
86            .inspect(|_| n += 1)
87            .map(|x| x.as_ref().to_owned())
88            .collect();
89
90        // Check for duplicate labels.
91        if labels.len() != n {
92            return Err(Error::NonUniqueLabels());
93        }
94
95        // Sort the labels.
96        labels.sort();
97
98        // Initialize the adjacency matrix with `false` values.
99        let adjacency_matrix: Array2<_> = Array::from_elem((n, n), false);
100
101        Ok(Self {
102            labels,
103            adjacency_matrix,
104        })
105    }
106
107    fn complete<I, V>(labels: I) -> Result<Self>
108    where
109        I: IntoIterator<Item = V>,
110        V: AsRef<str>,
111    {
112        // Construct the empty graph.
113        let mut graph = Self::empty(labels)?;
114        // Fill the adjacency matrix with `true` values.
115        graph.adjacency_matrix.fill(true);
116        // Remove the self-loops.
117        graph.adjacency_matrix.diag_mut().fill(false);
118
119        Ok(graph)
120    }
121
122    fn vertices(&self) -> Set<usize> {
123        (0..self.labels.len()).collect()
124    }
125
126    fn has_vertex(&self, x: usize) -> bool {
127        // Check if the vertex is within bounds.
128        x < self.labels.len()
129    }
130
131    fn add_vertex<V>(&mut self, x: V) -> usize
132    where
133        V: AsRef<str>,
134    {
135        // Cast the vertex label.
136        let x = x.as_ref().to_owned();
137        // Try to insert the vertex label.
138        let (i, f) = self.labels.insert_full(x.clone());
139
140        // If the vertex was already present ...
141        if !f {
142            // ... return early.
143            return i;
144        }
145
146        // Sort the labels.
147        self.labels.sort();
148
149        // Assert the vertex has been added.
150        debug_assert!(self.labels.contains(&x));
151        // Assert the labels are still sorted.
152        debug_assert!(self.labels.iter().is_sorted());
153
154        // Compute the index of the new vertex:
155        // since labels are unique and sorted, it is
156        // the number of labels preceding the new one.
157        let i = self
158            .labels
159            .iter()
160            .filter(|&y| y.as_str() < x.as_str())
161            .count();
162
163        // Compute the size of the adjacency matrix.
164        let n = self.adjacency_matrix.nrows();
165        // Allocate the new adjacency matrix.
166        let mut adjacency_matrix = Array2::from_elem((n + 1, n + 1), false);
167        // Copy the top-left block.
168        adjacency_matrix
169            .slice_mut(s![0..i, 0..i])
170            .assign(&self.adjacency_matrix.slice(s![0..i, 0..i]));
171        // Copy the top-right block.
172        adjacency_matrix
173            .slice_mut(s![0..i, (i + 1)..(n + 1)])
174            .assign(&self.adjacency_matrix.slice(s![0..i, i..n]));
175        // Copy the bottom-left block.
176        adjacency_matrix
177            .slice_mut(s![(i + 1)..(n + 1), 0..i])
178            .assign(&self.adjacency_matrix.slice(s![i..n, 0..i]));
179        // Copy the bottom-right block.
180        adjacency_matrix
181            .slice_mut(s![(i + 1)..(n + 1), (i + 1)..(n + 1)])
182            .assign(&self.adjacency_matrix.slice(s![i..n, i..n]));
183        // Replace the old adjacency matrix.
184        self.adjacency_matrix = adjacency_matrix;
185
186        // Assert the label set is still consistent with the adjacency matrix shape.
187        debug_assert_eq!(self.labels.len(), self.adjacency_matrix.nrows());
188        // Assert the adjacency matrix is still square.
189        debug_assert!(self.adjacency_matrix.is_square());
190
191        // Return the new vertex index.
192        i
193    }
194
195    fn del_vertex(&mut self, x: usize) -> bool {
196        // Remove the vertex label, shifting the subsequent indices.
197        let Some(label) = self.labels.shift_remove_index(x) else {
198            // If the vertex was not present, return early.
199            return false;
200        };
201
202        // Assert the vertex has been removed.
203        debug_assert!(!self.labels.contains(&label));
204        // Assert the labels are still sorted.
205        debug_assert!(self.labels.iter().is_sorted());
206
207        // Compute the size of the adjacency matrix.
208        let n = self.adjacency_matrix.nrows();
209        // Allocate the new adjacency matrix.
210        let mut adjacency_matrix = Array2::from_elem((n - 1, n - 1), false);
211        // Copy the top-left block.
212        adjacency_matrix
213            .slice_mut(s![0..x, 0..x])
214            .assign(&self.adjacency_matrix.slice(s![0..x, 0..x]));
215        // Copy the top-right block.
216        adjacency_matrix
217            .slice_mut(s![0..x, x..(n - 1)])
218            .assign(&self.adjacency_matrix.slice(s![0..x, (x + 1)..n]));
219        // Copy the bottom-left block.
220        adjacency_matrix
221            .slice_mut(s![x..(n - 1), 0..x])
222            .assign(&self.adjacency_matrix.slice(s![(x + 1)..n, 0..x]));
223        // Copy the bottom-right block.
224        adjacency_matrix
225            .slice_mut(s![x..(n - 1), x..(n - 1)])
226            .assign(&self.adjacency_matrix.slice(s![(x + 1)..n, (x + 1)..n]));
227        // Replace the old adjacency matrix.
228        self.adjacency_matrix = adjacency_matrix;
229
230        // Assert the label set is still consistent with the adjacency matrix shape.
231        debug_assert_eq!(self.labels.len(), self.adjacency_matrix.nrows());
232        // Assert the adjacency matrix is still square.
233        debug_assert!(self.adjacency_matrix.is_square());
234
235        true
236    }
237
238    fn edges(&self) -> Set<(usize, usize)> {
239        // Iterate over the adjacency matrix and collect the edges.
240        self.adjacency_matrix
241            .indexed_iter()
242            .filter_map(|((x, y), &has_edge)| {
243                // Since the graph is undirected, we only need to check one direction.
244                (has_edge && x <= y).then_some((x, y))
245            })
246            .collect()
247    }
248
249    fn has_edge(&self, x: usize, y: usize) -> Result<bool> {
250        // Check if the vertices are within bounds.
251        self.check_vertex(x)?;
252        self.check_vertex(y)?;
253
254        Ok(self.adjacency_matrix[[x, y]])
255    }
256
257    fn add_edge(&mut self, x: usize, y: usize) -> Result<bool> {
258        // Check if the vertices are within bounds.
259        self.check_vertex(x)?;
260        self.check_vertex(y)?;
261
262        // Check if the edge already exists.
263        if self.adjacency_matrix[[x, y]] {
264            return Ok(false);
265        }
266
267        // Add the edge.
268        self.adjacency_matrix[[x, y]] = true;
269        self.adjacency_matrix[[y, x]] = true;
270
271        Ok(true)
272    }
273
274    fn del_edge(&mut self, x: usize, y: usize) -> Result<bool> {
275        // Check if the vertices are within bounds.
276        self.check_vertex(x)?;
277        self.check_vertex(y)?;
278
279        // Check if the edge exists.
280        if !self.adjacency_matrix[[x, y]] {
281            return Ok(false);
282        }
283
284        // Delete the edge.
285        self.adjacency_matrix[[x, y]] = false;
286        self.adjacency_matrix[[y, x]] = false;
287
288        Ok(true)
289    }
290
291    fn select(&self, x: &Set<usize>) -> Result<Self>
292    where
293        Self: Sized,
294    {
295        // Check if the vertices are within bounds.
296        x.iter().try_for_each(|&v| self.check_vertex(v))?;
297
298        // Clone and sort the vertices.
299        let mut x = x.clone();
300        x.sort();
301
302        // Allocate the new labels.
303        let labels: Labels = x.iter().map(|&v| self.labels[v].clone()).collect();
304        // Allocate the new adjacency matrix.
305        let mut adjacency_matrix: Array2<bool> = Array::from_elem((x.len(), x.len()), false);
306        // Fill the new adjacency matrix.
307        for (i, &v_i) in x.iter().enumerate() {
308            for (j, &v_j) in x.iter().enumerate() {
309                adjacency_matrix[[i, j]] = self.adjacency_matrix[[v_i, v_j]];
310            }
311        }
312
313        Self::from_adjacency_matrix(labels, adjacency_matrix)
314    }
315
316    fn from_adjacency_matrix(
317        mut labels: Labels,
318        mut adjacency_matrix: Array2<bool>,
319    ) -> Result<Self> {
320        // Check labels and adjacency matrix dimensions match.
321        if labels.len() != adjacency_matrix.nrows() {
322            return Err(Error::IncompatibleShape(
323                &labels.len().to_string(),
324                &adjacency_matrix.nrows().to_string(),
325            ));
326        }
327        // Check adjacency matrix must be square.
328        if adjacency_matrix.nrows() != adjacency_matrix.ncols() {
329            return Err(Error::IncompatibleShape(
330                &adjacency_matrix.nrows().to_string(),
331                &adjacency_matrix.ncols().to_string(),
332            ));
333        }
334        // Check the adjacency matrix is symmetric.
335        if adjacency_matrix != adjacency_matrix.t() {
336            return Err(Error::InvalidParameter(
337                "adjacency_matrix",
338                "Adjacency matrix must be symmetric.",
339            ));
340        }
341
342        // Check if the labels are sorted.
343        if !labels.is_sorted() {
344            // Allocate the sorted indices.
345            let mut indices: Vec<usize> = (0..labels.len()).collect();
346            // Sort the indices based on the labels.
347            indices.sort_by_key(|&i| &labels[i]);
348            // Sort the labels.
349            labels.sort();
350            // Allocate a new adjacency matrix.
351            let mut new_adjacency_matrix = adjacency_matrix.clone();
352            // Fill the rows.
353            indices.iter().enumerate().for_each(|(i, &j)| {
354                new_adjacency_matrix
355                    .row_mut(i)
356                    .assign(&adjacency_matrix.row(j));
357            });
358            // Update the adjacency matrix.
359            adjacency_matrix = new_adjacency_matrix;
360            // Allocate a new adjacency matrix.
361            let mut new_adjacency_matrix = adjacency_matrix.clone();
362            // Fill the columns.
363            indices.iter().enumerate().for_each(|(i, &j)| {
364                new_adjacency_matrix
365                    .column_mut(i)
366                    .assign(&adjacency_matrix.column(j));
367            });
368            // Update the adjacency matrix.
369            adjacency_matrix = new_adjacency_matrix;
370        }
371
372        // Create a new graph instance.
373        Ok(Self {
374            labels,
375            adjacency_matrix,
376        })
377    }
378
379    #[inline]
380    fn to_adjacency_matrix(&self) -> Array2<bool> {
381        self.adjacency_matrix.clone()
382    }
383}
384
385impl Serialize for UnGraph {
386    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
387    where
388        S: Serializer,
389    {
390        // Convert adjacency matrix to a flat format.
391        let edges = self
392            .edges()
393            .into_iter()
394            .map(|(x, y)| {
395                let x = self.index_to_label(x).map_err(serde::ser::Error::custom)?;
396                let y = self.index_to_label(y).map_err(serde::ser::Error::custom)?;
397                Ok((x.to_owned(), y.to_owned()))
398            })
399            .collect::<std::result::Result<Vec<_>, S::Error>>()?;
400
401        // Allocate the map.
402        let mut map = serializer.serialize_map(Some(3))?;
403
404        // Serialize labels.
405        map.serialize_entry("labels", &self.labels)?;
406        // Serialize edges.
407        map.serialize_entry("edges", &edges)?;
408        // Serialize type.
409        map.serialize_entry("type", "ungraph")?;
410
411        // Finalize the map serialization.
412        map.end()
413    }
414}
415
416impl<'de> Deserialize<'de> for UnGraph {
417    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
418    where
419        D: Deserializer<'de>,
420    {
421        #[derive(Deserialize)]
422        #[serde(field_identifier, rename_all = "snake_case")]
423        enum Field {
424            Labels,
425            Edges,
426            Type,
427        }
428
429        struct UnGraphVisitor;
430
431        impl<'de> Visitor<'de> for UnGraphVisitor {
432            type Value = UnGraph;
433
434            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
435                formatter.write_str("struct UnGraph")
436            }
437
438            fn visit_map<V>(self, mut map: V) -> std::result::Result<UnGraph, V::Error>
439            where
440                V: MapAccess<'de>,
441            {
442                use serde::de::Error as E;
443
444                // Allocate fields
445                let mut labels = None;
446                let mut edges = None;
447                let mut type_ = None;
448
449                // Parse the map.
450                while let Some(key) = map.next_key()? {
451                    match key {
452                        Field::Labels => {
453                            if labels.is_some() {
454                                return Err(E::duplicate_field("labels"));
455                            }
456                            labels = Some(map.next_value()?);
457                        }
458                        Field::Edges => {
459                            if edges.is_some() {
460                                return Err(E::duplicate_field("edges"));
461                            }
462                            edges = Some(map.next_value()?);
463                        }
464                        Field::Type => {
465                            if type_.is_some() {
466                                return Err(E::duplicate_field("type"));
467                            }
468                            type_ = Some(map.next_value()?);
469                        }
470                    }
471                }
472
473                // Check required fields.
474                let labels = labels.ok_or_else(|| E::missing_field("labels"))?;
475                let edges = edges.ok_or_else(|| E::missing_field("edges"))?;
476
477                // Check type is correct.
478                let type_: String = type_.ok_or_else(|| E::missing_field("type"))?;
479                if type_ != "ungraph" {
480                    return Err(E::custom(format!(
481                        "Invalid type for UnGraph: expected 'ungraph', found '{type_}'"
482                    )));
483                }
484
485                // Convert edges to an adjacency matrix.
486                let labels: Labels = labels;
487                let edges: Vec<(String, String)> = edges;
488                let shape = (labels.len(), labels.len());
489                let mut adjacency_matrix = Array2::from_elem(shape, false);
490                edges.into_iter().try_for_each(|(x, y)| {
491                    let x = labels
492                        .get_index_of(&x)
493                        .ok_or_else(|| E::custom(format!("Vertex `{x}` label does not exist")))?;
494                    let y = labels
495                        .get_index_of(&y)
496                        .ok_or_else(|| E::custom(format!("Vertex `{y}` label does not exist")))?;
497                    adjacency_matrix[(x, y)] = true;
498                    Ok(())
499                })?;
500
501                UnGraph::from_adjacency_matrix(labels, adjacency_matrix)
502                    .map_err(|evidence| E::custom(evidence.to_string()))
503            }
504        }
505
506        const FIELDS: &[&str] = &["labels", "edges", "type"];
507
508        deserializer.deserialize_struct("UnGraph", FIELDS, UnGraphVisitor)
509    }
510}
511
512// Implement `JsonIO` for `UnGraph`.
513impl_json_io!(UnGraph);