petgraph/acyclic/
order_map.rs

1//! A bijective map between node indices and a `TopologicalPosition`, to store
2//! the total topological order of the graph.
3//!
4//! This data structure is an implementation detail and is not exposed in the
5//! public API.
6use alloc::{collections::BTreeMap, vec, vec::Vec};
7use core::{fmt, ops::RangeBounds};
8
9use crate::{
10    algo::{toposort, Cycle},
11    visit::{GraphBase, IntoNeighborsDirected, IntoNodeIdentifiers, NodeIndexable, Visitable},
12};
13
14/// A position in the topological order of the graph.
15///
16/// This defines a total order over the set of nodes in the graph.
17///
18/// Note that the positions of all nodes in a graph may not form a contiguous
19/// interval.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
21#[repr(transparent)]
22pub struct TopologicalPosition(pub(super) usize);
23
24/// A bijective map between node indices and their position in a topological order.
25///
26/// Note that this map does not check for injectivity or surjectivity, this
27/// must be enforced by the user. Map mutations that invalidate these properties
28/// are allowed to make it easy to perform batch modifications that temporarily
29/// break the invariants.
30#[derive(Clone)]
31pub(super) struct OrderMap<N> {
32    /// Map topological position to node index.
33    pos_to_node: BTreeMap<TopologicalPosition, N>,
34    /// The inverse of `pos_to_node`, i.e. map node indices to their position.
35    ///
36    /// This is a Vec, relying on `N: NodeIndexable` for indexing.
37    node_to_pos: Vec<TopologicalPosition>,
38}
39
40impl<N> Default for OrderMap<N> {
41    fn default() -> Self {
42        Self {
43            pos_to_node: Default::default(),
44            node_to_pos: Default::default(),
45        }
46    }
47}
48
49impl<N: fmt::Debug> fmt::Debug for OrderMap<N> {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_struct("OrderMap")
52            .field("order", &self.pos_to_node)
53            .finish()
54    }
55}
56
57impl<N: Copy> OrderMap<N> {
58    pub(super) fn try_from_graph<G>(graph: G) -> Result<Self, Cycle<G::NodeId>>
59    where
60        G: NodeIndexable<NodeId = N> + IntoNeighborsDirected + IntoNodeIdentifiers + Visitable,
61    {
62        // Compute the topological order.
63        let topo_vec = toposort(graph, None)?;
64
65        // Create the two map directions.
66        let mut pos_to_node = BTreeMap::new();
67        let mut node_to_pos = vec![TopologicalPosition::default(); graph.node_bound()];
68
69        // Populate the maps.
70        for (i, &id) in topo_vec.iter().enumerate() {
71            let pos = TopologicalPosition(i);
72            pos_to_node.insert(pos, id);
73            node_to_pos[graph.to_index(id)] = pos;
74        }
75
76        Ok(Self {
77            pos_to_node,
78            node_to_pos,
79        })
80    }
81
82    pub(super) fn with_capacity(nodes: usize) -> Self {
83        Self {
84            pos_to_node: BTreeMap::new(),
85            node_to_pos: Vec::with_capacity(nodes),
86        }
87    }
88
89    /// Map a node to its position in the topological order.
90    ///
91    /// Panics if the node index is out of bounds.
92    #[track_caller]
93    pub(super) fn get_position(
94        &self,
95        id: N,
96        graph: impl NodeIndexable<NodeId = N>,
97    ) -> TopologicalPosition {
98        let idx = graph.to_index(id);
99        assert!(idx < self.node_to_pos.len());
100        self.node_to_pos[idx]
101    }
102
103    /// Map a position in the topological order to a node, if it exists.
104    pub(super) fn at_position(&self, pos: TopologicalPosition) -> Option<N> {
105        self.pos_to_node.get(&pos).copied()
106    }
107
108    /// Get an iterator over the nodes, ordered by their position.
109    pub(super) fn nodes_iter(&self) -> impl Iterator<Item = N> + '_ {
110        self.pos_to_node.values().copied()
111    }
112
113    /// Get an iterator over the nodes within the range of positions.
114    pub(super) fn range(
115        &self,
116        range: impl RangeBounds<TopologicalPosition>,
117    ) -> impl Iterator<Item = N> + '_ {
118        self.pos_to_node.range(range).map(|(_, &n)| n)
119    }
120
121    /// Add a node to the order map and assign it an arbitrary position.
122    ///
123    /// Return the position of the new node.
124    pub(super) fn add_node(
125        &mut self,
126        id: N,
127        graph: impl NodeIndexable<NodeId = N>,
128    ) -> TopologicalPosition {
129        // The position and node index
130        let new_pos = self
131            .pos_to_node
132            .iter()
133            .next_back()
134            .map(|(TopologicalPosition(idx), _)| TopologicalPosition(idx + 1))
135            .unwrap_or_default();
136        let idx = graph.to_index(id);
137
138        // Make sure the order_inv is large enough.
139        if idx >= self.node_to_pos.len() {
140            self.node_to_pos
141                .resize(graph.node_bound(), TopologicalPosition::default());
142        }
143
144        // Insert both map directions.
145        self.pos_to_node.insert(new_pos, id);
146        self.node_to_pos[idx] = new_pos;
147
148        new_pos
149    }
150
151    /// Remove a node from the order map.
152    ///
153    /// Panics if the node index is out of bounds.
154    #[track_caller]
155    pub(super) fn remove_node(&mut self, id: N, graph: impl NodeIndexable<NodeId = N>) {
156        let idx = graph.to_index(id);
157        assert!(idx < self.node_to_pos.len());
158
159        let pos = self.node_to_pos[idx];
160        self.node_to_pos[idx] = TopologicalPosition::default();
161        self.pos_to_node.remove(&pos);
162    }
163
164    /// Set the position of a node.
165    ///
166    /// Panics if the node index is out of bounds.
167    #[track_caller]
168    pub(super) fn set_position(
169        &mut self,
170        id: N,
171        pos: TopologicalPosition,
172        graph: impl NodeIndexable<NodeId = N>,
173    ) {
174        let idx = graph.to_index(id);
175        assert!(idx < self.node_to_pos.len());
176
177        self.pos_to_node.insert(pos, id);
178        self.node_to_pos[idx] = pos;
179    }
180}
181
182impl<G: Visitable> super::Acyclic<G> {
183    /// Get the position of a node in the topological sort.
184    ///
185    /// Panics if the node index is out of bounds.
186    #[track_caller]
187    pub fn get_position<'a>(&'a self, id: G::NodeId) -> TopologicalPosition
188    where
189        &'a G: NodeIndexable + GraphBase<NodeId = G::NodeId>,
190    {
191        self.order_map.get_position(id, &self.graph)
192    }
193
194    /// Get the node at a given position in the topological sort, if it exists.
195    pub fn at_position(&self, pos: TopologicalPosition) -> Option<G::NodeId> {
196        self.order_map.at_position(pos)
197    }
198}