Skip to main content

weavatrix_graph/payload/stable_undirected/
freeze.rs

1use super::core::StableUndirectedPayloadGraph;
2use crate::payload::stable::mapped_node;
3use crate::{
4    EdgeEndpoints, EdgeIndex, NodeIndex, PayloadFreezeMap, Result, UndirectedPayloadGraph, Vec,
5};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct FrozenUndirectedPayloadGraph<NodePayload, EdgePayload> {
9    graph: UndirectedPayloadGraph<NodePayload, EdgePayload>,
10    indices: PayloadFreezeMap,
11}
12
13impl<NodePayload, EdgePayload> FrozenUndirectedPayloadGraph<NodePayload, EdgePayload> {
14    #[must_use]
15    pub const fn graph(&self) -> &UndirectedPayloadGraph<NodePayload, EdgePayload> {
16        &self.graph
17    }
18
19    #[must_use]
20    pub const fn indices(&self) -> &PayloadFreezeMap {
21        &self.indices
22    }
23
24    #[must_use]
25    pub fn into_parts(
26        self,
27    ) -> (
28        UndirectedPayloadGraph<NodePayload, EdgePayload>,
29        PayloadFreezeMap,
30    ) {
31        (self.graph, self.indices)
32    }
33}
34
35impl<NodePayload, EdgePayload> StableUndirectedPayloadGraph<NodePayload, EdgePayload> {
36    /// Compacts live stable slots into immutable undirected incidence CSR.
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if compact index capacity is exhausted.
41    pub fn freeze(self) -> Result<FrozenUndirectedPayloadGraph<NodePayload, EdgePayload>> {
42        let mut node_map = vec![None; self.nodes.len()];
43        let mut nodes = Vec::with_capacity(self.node_count);
44        for (slot, entry) in self.nodes.into_iter().enumerate() {
45            let Some(payload) = entry.value else {
46                continue;
47            };
48            let compact = compact_node(nodes.len())?;
49            node_map[slot] = Some((entry.generation, compact));
50            nodes.push(payload);
51        }
52        let mut edge_map = vec![None; self.edges.len()];
53        let mut edges = Vec::with_capacity(self.edge_count);
54        for (slot, entry) in self.edges.into_iter().enumerate() {
55            let Some(payload) = entry.value else {
56                continue;
57            };
58            let source = mapped_node(&node_map, entry.source)?;
59            let target = mapped_node(&node_map, entry.target)?;
60            let compact = compact_edge(edges.len())?;
61            edge_map[slot] = Some((entry.generation, compact));
62            edges.push((EdgeEndpoints::new(source, target), payload));
63        }
64        let graph = UndirectedPayloadGraph::try_from_edges(nodes, edges)?;
65        Ok(FrozenUndirectedPayloadGraph {
66            graph,
67            indices: PayloadFreezeMap::from_slots(node_map, edge_map),
68        })
69    }
70}
71
72fn compact_node(count: usize) -> Result<NodeIndex> {
73    Ok(NodeIndex::new(u32::try_from(count).map_err(|_| {
74        crate::GraphError::IndexCapacityExceeded {
75            category: "frozen undirected nodes",
76            count,
77        }
78    })?))
79}
80
81fn compact_edge(count: usize) -> Result<EdgeIndex> {
82    Ok(EdgeIndex::new(u32::try_from(count).map_err(|_| {
83        crate::GraphError::IndexCapacityExceeded {
84            category: "frozen undirected edges",
85            count,
86        }
87    })?))
88}