Skip to main content

concept_graph/
closure.rs

1// SPDX-FileCopyrightText: Cadasto B.V.
2// SPDX-License-Identifier: BUSL-1.1
3
4//! The transitive closure of the is-a hierarchy.
5//!
6//! Computed once, offline, by a bitset sweep in topological order: a node's
7//! ancestor set is the union of its parents' ancestor sets plus the parents
8//! themselves; descendants are the same sweep over the transposed adjacency.
9//! A cycle in the is-a edges is a defect in the input and is refused.
10//! Roaring bitmaps keep the sets small and iterate in sorted order, which
11//! keeps every consumer deterministic.
12
13use roaring::RoaringBitmap;
14
15use crate::csr::{Csr, CsrError};
16use crate::ordinal::{Ordinal, to_usize};
17
18/// A failure while computing the closure.
19#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
20pub enum ClosureError {
21    /// The is-a edges contain a cycle through these ordinals.
22    #[error("the is-a hierarchy has a cycle through {} node(s), for example {first}", .members.len())]
23    Cycle {
24        /// The first node found on a cycle.
25        first: Ordinal,
26        /// Every node not reachable from the roots by a topological sweep.
27        members: Vec<Ordinal>,
28    },
29    /// The adjacency is inconsistent.
30    #[error(transparent)]
31    Csr(#[from] CsrError),
32}
33
34/// The ancestor and descendant sets of every node.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct Closure {
37    ancestors: Vec<RoaringBitmap>,
38    descendants: Vec<RoaringBitmap>,
39}
40
41static EMPTY: std::sync::LazyLock<RoaringBitmap> = std::sync::LazyLock::new(RoaringBitmap::new);
42
43impl Closure {
44    /// Computes the closure of `is_a`, whose edges point from child to parent.
45    ///
46    /// # Errors
47    ///
48    /// Returns [`ClosureError::Cycle`] when the edges are not acyclic.
49    pub fn compute(is_a: &Csr) -> Result<Self, ClosureError> {
50        let parents = is_a;
51        let children = is_a.transpose()?;
52        let order = topological_order(parents, &children)?;
53        let nodes = to_usize(parents.nodes());
54        Ok(Self {
55            ancestors: sweep(parents, order.iter(), nodes),
56            descendants: sweep(&children, order.iter().rev(), nodes),
57        })
58    }
59
60    /// Reassembles a closure from its two bitmap lists.
61    #[must_use]
62    pub fn from_parts(ancestors: Vec<RoaringBitmap>, descendants: Vec<RoaringBitmap>) -> Self {
63        Self {
64            ancestors,
65            descendants,
66        }
67    }
68
69    /// The heap bytes the two bitmap lists hold ([`crate::footprint`]).
70    #[must_use]
71    pub fn size_in_bytes(&self) -> usize {
72        crate::footprint::bitmaps(&self.ancestors)
73            .saturating_add(crate::footprint::bitmaps(&self.descendants))
74    }
75
76    /// The heap bytes the ancestor sets alone hold.
77    ///
78    /// Subsumption reads the ancestors and an expansion reads the
79    /// descendants, so the two halves are reported apart.
80    #[must_use]
81    pub fn ancestors_size_in_bytes(&self) -> usize {
82        crate::footprint::bitmaps(&self.ancestors)
83    }
84
85    /// The number of nodes.
86    #[must_use]
87    pub fn nodes(&self) -> u32 {
88        u32::try_from(self.ancestors.len()).unwrap_or(u32::MAX)
89    }
90
91    /// The proper ancestors of `node` (ECL `>`); empty for an unknown node.
92    #[must_use]
93    pub fn ancestors(&self, node: Ordinal) -> &RoaringBitmap {
94        self.ancestors
95            .get(node.as_usize())
96            .unwrap_or_else(|| &*EMPTY)
97    }
98
99    /// The proper descendants of `node` (ECL `<`); empty for an unknown node.
100    #[must_use]
101    pub fn descendants(&self, node: Ordinal) -> &RoaringBitmap {
102        self.descendants
103            .get(node.as_usize())
104            .unwrap_or_else(|| &*EMPTY)
105    }
106
107    /// `node` and its descendants (ECL `<<`).
108    #[must_use]
109    pub fn descendants_or_self(&self, node: Ordinal) -> RoaringBitmap {
110        let mut set = self.descendants(node).clone();
111        set.insert(node.index());
112        set
113    }
114
115    /// `node` and its ancestors (ECL `>>`).
116    #[must_use]
117    pub fn ancestors_or_self(&self, node: Ordinal) -> RoaringBitmap {
118        let mut set = self.ancestors(node).clone();
119        set.insert(node.index());
120        set
121    }
122
123    /// Whether `ancestor` is a proper ancestor of `node`.
124    #[must_use]
125    pub fn is_ancestor(&self, ancestor: Ordinal, node: Ordinal) -> bool {
126        self.ancestors(node).contains(ancestor.index())
127    }
128
129    /// The ancestor bitmaps, in ordinal order.
130    #[must_use]
131    pub fn ancestor_sets(&self) -> &[RoaringBitmap] {
132        &self.ancestors
133    }
134
135    /// The descendant bitmaps, in ordinal order.
136    #[must_use]
137    pub fn descendant_sets(&self) -> &[RoaringBitmap] {
138        &self.descendants
139    }
140}
141
142/// The set each node of `edges` reaches, swept in `order`: a node's set is its
143/// neighbours plus the sets they already reached.
144///
145/// `order` visits a node only after every neighbour it reads, so one pass fills
146/// every set.
147fn sweep<'a>(
148    edges: &Csr,
149    order: impl Iterator<Item = &'a Ordinal>,
150    nodes: usize,
151) -> Vec<RoaringBitmap> {
152    let mut sets: Vec<RoaringBitmap> = vec![RoaringBitmap::new(); nodes];
153    for node in order {
154        let mut set = RoaringBitmap::new();
155        for neighbour in edges.neighbours(*node) {
156            set.insert(*neighbour);
157            if let Some(reached) = sets.get(to_usize(*neighbour)) {
158                set |= reached;
159            }
160        }
161        if let Some(slot) = sets.get_mut(node.as_usize()) {
162            *slot = set;
163        }
164    }
165    sets
166}
167
168/// Kahn's algorithm over child-to-parent edges: parents before children.
169fn topological_order(parents: &Csr, children: &Csr) -> Result<Vec<Ordinal>, ClosureError> {
170    let nodes = parents.nodes();
171    let mut remaining: Vec<u32> = (0..nodes)
172        .map(|n| u32::try_from(parents.neighbours(Ordinal::new(n)).len()).unwrap_or(u32::MAX))
173        .collect();
174    let mut ready: Vec<Ordinal> = (0..nodes)
175        .filter(|n| remaining.get(to_usize(*n)) == Some(&0))
176        .map(Ordinal::new)
177        .collect();
178    let mut order = Vec::with_capacity(to_usize(nodes));
179    while let Some(node) = ready.pop() {
180        order.push(node);
181        for child in children.neighbours(node) {
182            if let Some(count) = remaining.get_mut(to_usize(*child)) {
183                *count = count.saturating_sub(1);
184                if *count == 0 {
185                    ready.push(Ordinal::new(*child));
186                }
187            }
188        }
189    }
190    if order.len() != to_usize(nodes) {
191        let members: Vec<Ordinal> = (0..nodes)
192            .filter(|n| remaining.get(to_usize(*n)).is_some_and(|c| *c > 0))
193            .map(Ordinal::new)
194            .collect();
195        let first = members.first().copied().unwrap_or(Ordinal::new(0));
196        return Err(ClosureError::Cycle { first, members });
197    }
198    Ok(order)
199}
200
201#[cfg(test)]
202mod tests {
203    use super::{Closure, ClosureError};
204    use crate::csr::Csr;
205    use crate::ordinal::Ordinal;
206
207    fn o(i: u32) -> Ordinal {
208        Ordinal::new(i)
209    }
210
211    /// 0 is the root; 1 and 2 are its children; 3 is a child of both 1 and 2.
212    fn diamond() -> Closure {
213        let is_a = Csr::build(4, [(o(1), o(0)), (o(2), o(0)), (o(3), o(1)), (o(3), o(2))])
214            .expect("builds");
215        Closure::compute(&is_a).expect("acyclic")
216    }
217
218    #[test]
219    fn ancestors_and_descendants_are_transitive_and_inverse() {
220        let closure = diamond();
221        assert_eq!(
222            closure.ancestors(o(3)).iter().collect::<Vec<_>>(),
223            vec![0, 1, 2]
224        );
225        assert_eq!(
226            closure.descendants(o(0)).iter().collect::<Vec<_>>(),
227            vec![1, 2, 3]
228        );
229        assert_eq!(
230            closure.descendants(o(1)).iter().collect::<Vec<_>>(),
231            vec![3]
232        );
233        assert!(closure.ancestors(o(0)).is_empty());
234        assert!(closure.descendants(o(3)).is_empty());
235        assert!(closure.is_ancestor(o(0), o(3)));
236        assert!(!closure.is_ancestor(o(3), o(0)));
237        assert!(closure.descendants_or_self(o(3)).contains(3));
238        assert_eq!(closure.ancestors_or_self(o(3)).len(), 4);
239    }
240
241    #[test]
242    fn a_cycle_is_refused() {
243        let is_a = Csr::build(3, [(o(0), o(1)), (o(1), o(2)), (o(2), o(0))]).expect("builds");
244        match Closure::compute(&is_a) {
245            Err(ClosureError::Cycle { members, .. }) => assert_eq!(members.len(), 3),
246            other => panic!("expected a cycle, got {other:?}"),
247        }
248    }
249
250    #[test]
251    fn unknown_nodes_have_empty_sets() {
252        let closure = diamond();
253        assert!(closure.ancestors(o(99)).is_empty());
254        assert!(closure.descendants(o(99)).is_empty());
255    }
256}