1use crate::deps::{DepEdge, dependency_edges};
12use petgraph::algo::tarjan_scc;
13use petgraph::graph::{DiGraph, NodeIndex};
14use serde::{Deserialize, Serialize};
15use shifty_algebra::{ShapeArena, ShapeId};
16use std::collections::{HashMap, HashSet};
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct Stratum {
21 pub shapes: Vec<ShapeId>,
22 pub recursive: bool,
24 pub stratifiable: bool,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct Stratification {
30 pub strata: Vec<Stratum>,
32 pub stratifiable: bool,
34}
35
36impl Stratification {
37 pub fn shape_count(&self) -> usize {
38 self.strata.iter().map(|s| s.shapes.len()).sum()
39 }
40
41 pub fn recursive(&self) -> impl Iterator<Item = &Stratum> {
42 self.strata.iter().filter(|s| s.recursive)
43 }
44}
45
46pub fn analyze(arena: &ShapeArena) -> Stratification {
48 let edges = dependency_edges(arena);
49
50 let mut graph = DiGraph::<ShapeId, ()>::new();
51 let mut node_of: HashMap<ShapeId, NodeIndex> = HashMap::new();
52 for i in 0..arena.len() {
53 let id = ShapeId(i as u32);
54 node_of.insert(id, graph.add_node(id));
55 }
56 for e in &edges {
57 graph.add_edge(node_of[&e.from], node_of[&e.to], ());
58 }
59
60 let sccs = tarjan_scc(&graph);
61
62 let mut scc_of: HashMap<ShapeId, usize> = HashMap::new();
64 for (i, scc) in sccs.iter().enumerate() {
65 for n in scc {
66 scc_of.insert(graph[*n], i);
67 }
68 }
69
70 let self_loops: HashSet<ShapeId> = edges
71 .iter()
72 .filter(|e| e.from == e.to)
73 .map(|e| e.from)
74 .collect();
75
76 let depths = scc_depths(&sccs, &edges, &scc_of);
78
79 let mut indexed: Vec<(usize, Stratum)> = sccs
80 .iter()
81 .enumerate()
82 .map(|(i, scc)| {
83 let mut shapes: Vec<ShapeId> = scc.iter().map(|n| graph[*n]).collect();
84 shapes.sort();
85 let recursive = shapes.len() > 1 || shapes.iter().any(|s| self_loops.contains(s));
86 let members: HashSet<ShapeId> = shapes.iter().copied().collect();
87 let stratifiable = !recursive || sign_balanced(&members, &edges);
88 (
89 depths[i],
90 Stratum {
91 shapes,
92 recursive,
93 stratifiable,
94 },
95 )
96 })
97 .collect();
98
99 indexed.sort_by_key(|(d, s)| (*d, s.shapes.first().copied()));
100 let strata: Vec<Stratum> = indexed.into_iter().map(|(_, s)| s).collect();
101 let stratifiable = strata.iter().all(|s| s.stratifiable);
102
103 Stratification {
104 strata,
105 stratifiable,
106 }
107}
108
109fn scc_depths(
112 sccs: &[Vec<NodeIndex>],
113 edges: &[DepEdge],
114 scc_of: &HashMap<ShapeId, usize>,
115) -> Vec<usize> {
116 let mut succ: Vec<HashSet<usize>> = vec![HashSet::new(); sccs.len()];
117 for e in edges {
118 let (a, b) = (scc_of[&e.from], scc_of[&e.to]);
119 if a != b {
120 succ[a].insert(b);
121 }
122 }
123 let mut memo = vec![None; sccs.len()];
124 for i in 0..sccs.len() {
125 depth_of(i, &succ, &mut memo);
126 }
127 memo.into_iter().map(|d| d.unwrap()).collect()
128}
129
130fn depth_of(i: usize, succ: &[HashSet<usize>], memo: &mut [Option<usize>]) -> usize {
131 if let Some(d) = memo[i] {
132 return d;
133 }
134 memo[i] = Some(0); let d = succ[i]
136 .iter()
137 .map(|&j| 1 + depth_of(j, succ, memo))
138 .max()
139 .unwrap_or(0);
140 memo[i] = Some(d);
141 d
142}
143
144fn sign_balanced(members: &HashSet<ShapeId>, edges: &[DepEdge]) -> bool {
148 let mut adj: HashMap<ShapeId, Vec<(ShapeId, i8)>> = HashMap::new();
149 for e in edges {
150 if members.contains(&e.from) && members.contains(&e.to) {
151 let s = e.polarity.sign();
152 adj.entry(e.from).or_default().push((e.to, s));
153 adj.entry(e.to).or_default().push((e.from, s));
154 }
155 }
156
157 let mut sigma: HashMap<ShapeId, i8> = HashMap::new();
158 for &start in members {
159 if sigma.contains_key(&start) {
160 continue;
161 }
162 sigma.insert(start, 1);
163 let mut stack = vec![start];
164 while let Some(a) = stack.pop() {
165 let sa = sigma[&a];
166 if let Some(neighbours) = adj.get(&a) {
167 for &(b, s) in neighbours {
168 let want = sa * s;
169 match sigma.get(&b) {
170 Some(&sb) if sb != want => return false,
171 Some(_) => {}
172 None => {
173 sigma.insert(b, want);
174 stack.push(b);
175 }
176 }
177 }
178 }
179 }
180 }
181 true
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use shifty_algebra::{NamedNode, Path, Shape};
188
189 fn pred() -> NamedNode {
190 NamedNode::new("http://ex/p").unwrap()
191 }
192
193 #[test]
194 fn positive_recursion_is_stratifiable() {
195 let mut arena = ShapeArena::new();
197 let s = arena.reserve();
198 let ns = arena.insert(Shape::Not(s));
199 arena.set(
200 s,
201 Shape::Count {
202 path: Path::Pred(pred()),
203 min: None,
204 max: Some(0),
205 qualifier: ns,
206 },
207 );
208
209 let strat = analyze(&arena);
210 assert!(strat.stratifiable, "∀-recursion must be stratifiable");
211 let rec: Vec<_> = strat.recursive().collect();
213 assert_eq!(rec.len(), 1);
214 assert!(rec[0].shapes.contains(&s) && rec[0].shapes.contains(&ns));
215 }
216
217 #[test]
218 fn negation_recursion_is_not_stratifiable() {
219 let mut arena = ShapeArena::new();
221 let s = arena.reserve();
222 let exists = arena.insert(Shape::Count {
223 path: Path::Pred(pred()),
224 min: Some(1),
225 max: None,
226 qualifier: s,
227 });
228 arena.set(s, Shape::Not(exists));
229
230 let strat = analyze(&arena);
231 assert!(!strat.stratifiable, "¬∃-recursion must be flagged");
232 }
233
234 #[test]
235 fn acyclic_schema_has_no_recursion() {
236 let mut arena = ShapeArena::new();
237 let a = arena.insert(Shape::TestKind(shifty_algebra::NodeKindSet::IRI));
238 let _b = arena.insert(Shape::Not(a));
239 let strat = analyze(&arena);
240 assert!(strat.stratifiable);
241 assert_eq!(strat.recursive().count(), 0);
242 }
243}