1use crate::edb::{Predicate, RelationSet};
13use crate::error::{program_not_stratifiable, Result};
14use crate::features::{FeatureSet, FEATURE_DISJUNCTION};
15use crate::idb::{Literal, Rule, RuleSet};
16use crate::{Collection, Labeled, MaybePositive, Program, ProgramCore};
17use std::collections::HashSet;
18use std::fmt::{Display, Formatter};
19use std::time::Instant;
20use tracing::trace;
21
22#[derive(Debug)]
53pub struct StratifiedProgram<'a> {
54 strata: Vec<SubProgram<'a>>,
55}
56
57#[derive(Debug, PartialEq)]
58#[allow(single_use_lifetimes)]
59pub struct SubProgram<'a> {
60 program: &'a Program,
61 strata: RuleSet,
62}
63
64#[derive(Debug, Default)]
92#[allow(single_use_lifetimes)]
93pub struct PrecedenceGraph<'a>(HashSet<PrecedenceNode<'a>>);
94
95#[derive(Debug, PartialEq, Eq, Hash)]
96#[allow(single_use_lifetimes)]
97pub struct PrecedenceNode<'a> {
98 negative: bool,
99 extensional: bool,
100 source: &'a Predicate,
101 target: &'a Predicate,
102}
103
104impl Display for StratifiedProgram<'_> {
121 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
122 for (i, strata) in self.strata.iter().enumerate() {
123 writeln!(f, "[{}", i)?;
124 for (j, rule) in strata.rules().iter().enumerate() {
125 writeln!(f, " [{}] {:?}", j, rule)?;
126 }
127 writeln!(f, "]")?;
128 }
129 Ok(())
130 }
131}
132
133impl<'a> Collection<SubProgram<'a>> for StratifiedProgram<'a> {
134 fn is_empty(&self) -> bool {
135 self.strata.is_empty()
136 }
137
138 fn len(&self) -> usize {
139 self.strata.len()
140 }
141
142 fn iter(&self) -> Box<dyn Iterator<Item = &'_ SubProgram<'a>> + '_> {
143 Box::new(self.strata.iter())
144 }
145
146 fn contains(&self, value: &SubProgram<'_>) -> bool {
147 self.strata.contains(value)
148 }
149}
150
151impl<'a> StratifiedProgram<'a> {
152 pub fn from(program: &'a Program) -> Result<Self> {
153 assert!(!program.features().supports(&FEATURE_DISJUNCTION));
155
156 let start = Instant::now();
157
158 let rules = program.rules();
159 let graph = PrecedenceGraph::from(program);
160 let mut strata: Vec<SubProgram<'_>> = Vec::with_capacity(graph.sources().len());
161
162 if graph.is_stratifiable() {
163 for rule in rules.iter() {
164 if strata.is_empty() {
165 strata.push(SubProgram::from_with_rule(program, rule));
166 } else {
167 let mut leveled: bool = false;
168 let head_label = rule.head().map(|atom| atom.label()).next().unwrap();
169
170 for stratum in strata.iter_mut() {
172 if stratum
173 .rules()
174 .iter()
175 .next()
176 .unwrap()
177 .head()
178 .map(|atom| atom.label())
179 .next()
180 .unwrap()
181 == head_label
182 {
183 stratum.add_rule(rule);
184 leveled = true;
185 break;
186 }
187 }
188
189 if !leveled {
200 for (i, stratum) in strata.iter().enumerate() {
201 if stratum.rules().iter().any(|r2| {
202 r2.literals()
203 .filter_map(Literal::as_relational)
204 .any(|atom| atom.label() == head_label)
205 }) {
206 strata.insert(i, SubProgram::from_with_rule(program, rule));
207 leveled = true;
208 break;
209 }
210 }
211 }
212
213 if !leveled {
214 strata.push(SubProgram::from_with_rule(program, rule));
215 }
216 }
217 }
218 let delta = start.elapsed();
219 trace!(
220 "Stratified {} rules into {} strata, in {}s",
221 program.rules().len(),
222 strata.len(),
223 delta.as_secs_f64()
224 );
225 Ok(Self { strata })
226 } else {
227 Err(program_not_stratifiable())
228 }
229 }
230}
231
232impl ProgramCore for SubProgram<'_> {
235 fn features(&self) -> &FeatureSet {
236 self.program.features()
237 }
238
239 fn extensional(&self) -> &RelationSet {
240 self.program.extensional()
241 }
242
243 fn intensional(&self) -> &RelationSet {
244 self.program.intensional()
245 }
246
247 fn rules(&self) -> &RuleSet {
248 &self.strata
249 }
250}
251
252impl<'a> SubProgram<'a> {
253 fn from_with_rule(program: &'a Program, rule: &Rule) -> Self {
254 let mut new = Self {
255 program,
256 strata: Default::default(),
257 };
258 new.add_rule(rule);
259 new
260 }
261
262 fn add_rule(&mut self, rule: &Rule) {
263 self.strata.add(rule.clone());
264 }
265}
266
267impl<'a> PrecedenceGraph<'a> {
270 pub fn from(program: &'a Program) -> Self {
271 let mut graph = PrecedenceGraph::default();
272 for rule in program.rules().iter() {
273 for from_label in head_predicates(rule) {
274 for (negative, to_label) in body_predicates(rule) {
275 graph.0.insert(PrecedenceNode {
276 negative,
277 extensional: program.extensional().contains(to_label),
278 source: from_label,
279 target: to_label,
280 });
281 }
282 }
283 }
284 graph
285 }
286
287 pub fn edges(&self) -> impl Iterator<Item = &'_ PrecedenceNode<'_>> {
288 self.0.iter()
289 }
290
291 pub fn sources(&self) -> HashSet<&'_ Predicate> {
292 self.edges().map(|e| e.source()).collect()
293 }
294
295 pub fn targets(&self) -> HashSet<&'_ Predicate> {
296 self.edges().map(|e| e.target()).collect()
297 }
298
299 pub fn is_level_zero(&self, source: &Predicate) -> bool {
300 self.directly_reachable_nodes_from(source)
301 .into_iter()
302 .all(|node| node.is_extensional_target())
303 }
304
305 pub fn is_recursive(&self) -> bool {
306 self.sources()
307 .into_iter()
308 .any(|source| self.reachable_from(source).contains(source))
309 }
310
311 pub fn is_positive(&self) -> bool {
312 self.edges().all(|edge| !edge.is_negative_target())
313 }
314
315 pub fn is_semi_positive(&self) -> bool {
316 let all_sources = self.sources();
317 all_sources.iter().all(|source| {
318 self.directly_reachable_nodes_from(source)
319 .into_iter()
320 .filter(|node| node.is_negative_target())
321 .all(|node| node.is_extensional_target())
322 })
323 }
324
325 pub fn is_stratifiable(&self) -> bool {
326 let all_sources = self.sources();
327 all_sources.iter().all(|source| {
328 self.directly_reachable_nodes_from(source)
329 .into_iter()
330 .filter(|node| node.is_negative_target())
331 .all(|node| !self.reachable_from(node.target()).contains(source))
332 })
333 }
334
335 pub fn directly_reachable_from(&self, source: &Predicate) -> HashSet<&'_ Predicate> {
336 self.edges()
337 .filter(|e| e.source() == source)
338 .map(PrecedenceNode::target)
339 .collect()
340 }
341
342 pub fn reachable_from(&self, source: &Predicate) -> HashSet<&'_ Predicate> {
343 let mut initial: HashSet<&Predicate> = self.directly_reachable_from(source);
344 if initial.contains(source) {
345 initial
346 } else {
347 let next: HashSet<&Predicate> = initial
348 .iter()
349 .map(|s| self.directly_reachable_from(s))
350 .flatten()
351 .collect();
352 initial.extend(next);
353 initial
354 }
355 }
356
357 fn directly_reachable_nodes_from(&self, source: &Predicate) -> HashSet<&'_ PrecedenceNode<'_>> {
358 self.edges().filter(|e| e.source() == source).collect()
359 }
360
361 pub fn to_graphviz_string(&self) -> Result<String> {
362 let mut edges: Vec<String> = self
363 .edges()
364 .map(|edge| {
365 format!(
366 " {} -> {}{};\n",
367 edge.source(),
368 edge.target(),
369 if edge.is_negative_target() {
370 " [arrowhead=empty]"
371 } else {
372 ""
373 },
374 )
375 })
376 .collect();
377 edges.sort();
378
379 let mut edb: Vec<String> = self
380 .edges()
381 .filter_map(|r| {
382 if r.is_extensional_target() {
383 Some(format!(
384 " {} [style=filled; color=lightgrey];\n",
385 r.target()
386 ))
387 } else {
388 None
389 }
390 })
391 .collect();
392 edb.sort();
393 edb.dedup();
394
395 Ok(format!(
396 "digraph G {{\n{}\n{}}}",
397 edges.join(""),
398 edb.join("")
399 ))
400 }
401}
402
403impl<'a> PrecedenceNode<'a> {
406 #[inline]
407 pub fn is_negative_target(&self) -> bool {
408 self.negative
409 }
410
411 #[inline]
412 pub fn is_extensional_target(&self) -> bool {
413 self.extensional
414 }
415
416 #[inline]
417 pub fn source(&self) -> &'a Predicate {
418 self.source
419 }
420
421 #[inline]
422 pub fn target(&self) -> &'a Predicate {
423 self.target
424 }
425}
426
427#[inline]
432fn head_predicates(rule: &Rule) -> HashSet<&Predicate> {
433 rule.head().map(|a| a.label()).collect()
434}
435
436#[inline]
437fn body_predicates(rule: &Rule) -> HashSet<(bool, &Predicate)> {
438 rule.literals()
439 .filter_map(|l| l.as_relational().map(|a| (l.is_negative(), a.label())))
440 .collect()
441}
442
443#[cfg(test)]
452mod tests {
453 use crate::idb::eval::strata::{PrecedenceGraph, StratifiedProgram};
454 use crate::parse::parse_str;
455 use crate::Predicate;
456 use std::str::FromStr;
457
458 #[test]
459 fn test_stratification() {
460 let program = parse_str(
461 r#".feature(negation).
462.assert r(string, string).
463
464v(X) :- r(X, Y).
465v(Y) :- r(X, Y).
466t(X, Y) :- r(X, Y).
467t(X, Y) :- t(X, Z), r(Z, Y).
468tc(X, Y):- v(X), v(Y), NOT t(X, Y).
469"#,
470 )
471 .unwrap()
472 .into_parsed();
473
474 let stratified = StratifiedProgram::from(&program);
475 assert!(stratified.is_ok());
476 println!("{}", stratified.unwrap());
477 }
478
479 #[test]
480 fn test_precedence_graph() {
481 let program = parse_str(
482 r#".feature(negation).
483.assert r(string, string).
484
485v(X) :- r(X, Y).
486v(Y) :- r(X, Y).
487t(X, Y) :- r(X, Y).
488t(X, Y) :- t(X, Z), r(Z, Y).
489tc(X, Y):- v(X), v(Y), NOT t(X, Y).
490"#,
491 )
492 .unwrap()
493 .into_parsed();
494
495 assert!(program.is_recursive());
496
497 let graph = PrecedenceGraph::from(&program);
498 println!("{}", graph.to_graphviz_string().unwrap());
499
500 assert!(graph.is_recursive());
501 assert!(!graph.is_semi_positive());
502 assert!(graph.is_stratifiable());
503
504 let pred_t = Predicate::from_str("t").unwrap();
505 let pred_v = Predicate::from_str("v").unwrap();
506 let pred_tc = Predicate::from_str("tc").unwrap();
507
508 assert!(graph.is_level_zero(&pred_v));
509 assert!(!graph.is_level_zero(&pred_t));
510 assert!(!graph.is_level_zero(&pred_tc));
511
512 println!(
513 "t -> {:?}",
514 graph.reachable_from(&Predicate::from_str("t").unwrap())
515 );
516 println!(
517 "tc -> {:?}",
518 graph.reachable_from(&Predicate::from_str("tc").unwrap())
519 );
520 }
521
522 #[test]
523 fn test_precedence_graph_rdfs() {
524 let program = parse_str(
525 r#".feature(comparisons).
526.assert triple(string, string, string).
527
528% Section 2.2: Class
529class(C) :- triple(_, rdf:type, C).
530
531% Section 2.4: Datatype
532subClass(R, rdfs:Literal) :- instanceOf(R, rdfs:Datatype).
533
534% Section 2.8: Property
535property(P) :- triple(_, P, _).
536property(P) :- triple(P, rdf:type, rdfs:Property).
537
538% Section 3.1: range
539range(P, C) :- triple(P, rdfs:range, C).
540instanceOf(R, C) :- triple(_, P, R), range(P, C).
541range(P2, R) :- range(P, R), subProperty(P2, P).
542
543% Section 3.2: domain
544domain(P, C) :- triple(P, rdfs:domain, C).
545instanceOf(R, C) :- triple(R, P, _), domain(P, C).
546domain(P2, R) :- domain(P, R), subProperty(P2, P).
547
548% Section 3.3: rdf:type
549instanceOf(R, C) :- triple(R, rdf:type, C).
550instanceOf(R, C) :- triple(R, P, _), triple(P, rdfs:domain, C).
551instanceOf(R, C) :- triple(_, P, R), triple(P, rdfs:range, C).
552
553% Section 3.4: subClassOf
554subClass(C, P) :- triple(C, rdfs:subClassOf, P).
555class(C) :- subClass(C, _).
556class(C) :- subClass(_, C).
557instanceOf(C, C2) :- instanceOf(C, C1), subClass(C1, C2).
558subClass(C, rdfs:Class) :- class(C) AND C /= rdfs:Class.
559
560% Section 3.5: subPropertyOf
561subProperty(C, P) :- triple(C, rdfs:subPropertyOf, P).
562property(P) :- subProperty(P, _).
563property(P) :- subProperty(_, P).
564instanceOf(P, P2) :- instanceOf(P, P1), subProperty(P1, P2).
565subProperty(P, rdfs:Property) :- property(P) AND P /= rdfs:Property.
566
567% Section 5.1.5: ContainerMembershipProperty
568subProperty(P, rdfs:member) :- instanceOf(P, rdfs:ContainerMembershipProperty).
569"#,
570 )
571 .unwrap()
572 .into_parsed();
573
574 let graph = PrecedenceGraph::from(&program);
575 println!("{}", graph.to_graphviz_string().unwrap());
576
577 assert!(graph.is_recursive());
578 assert!(graph.is_semi_positive());
579 assert!(graph.is_stratifiable());
580
581 println!(
582 "property -> {:?}",
583 graph.reachable_from(&Predicate::from_str("property").unwrap())
584 );
585
586 println!(
587 "instanceOf -> {:?}",
588 graph.reachable_from(&Predicate::from_str("instanceOf").unwrap())
589 );
590 }
591
592 #[test]
593 fn test_is_semi_positive() {
594 let program = parse_str(
595 r#".feature(negation).
596.assert link(string, string).
597
598reachable(X, Y) :- link(X, Y).
599reachable(X, Y) :- link(X, Z), reachable(Z, Y).
600indirect(X, Y) :- reachable(X, Y), NOT link(X, Y).
601"#,
602 )
603 .unwrap()
604 .into_parsed();
605
606 let graph = PrecedenceGraph::from(&program);
607 println!("{}", graph.to_graphviz_string().unwrap());
608
609 assert!(graph.is_recursive());
610 assert!(graph.is_semi_positive());
611 assert!(graph.is_stratifiable());
612 }
613
614 #[test]
615 fn test_is_stratifiable() {
616 let program = parse_str(
617 r#".feature(negation).
618.assert link(string, string).
619
620reachable(X, Y) :- link(X, Y).
621reachable(X, Y) :- link(X, Z), reachable(Z, Y).
622anode(X) :- link(X, _).
623anode(Y) :- link(_, Y).
624unreachable(X, Y) :- reachable(X, Y), anode(X), anode(Y), NOT reachable(X, Y).
625"#,
626 )
627 .unwrap()
628 .into_parsed();
629
630 let graph = PrecedenceGraph::from(&program);
631 println!("{}", graph.to_graphviz_string().unwrap());
632
633 assert!(graph.is_recursive());
634 assert!(!graph.is_semi_positive());
635 assert!(graph.is_stratifiable());
636 }
637
638 #[test]
639 fn test_is_not_stratifiable() {
640 let program = parse_str(
641 r#".feature(negation).
642.assert link(string, string).
643
644reachable(X, Y) :- link(X, Y).
645reachable(X, Y) :- link(X, Z), reachable(Z, Y).
646absurdity(X, Y) :- unreachable(X, Y).
647unreachable(X, Y) :- reachable(X, Y), NOT absurdity(X, Y).
648"#,
649 )
650 .unwrap()
651 .into_parsed();
652
653 let graph = PrecedenceGraph::from(&program);
654 println!("{}", graph.to_graphviz_string().unwrap());
655
656 assert!(graph.is_recursive());
657 assert!(!graph.is_semi_positive());
658 assert!(!graph.is_stratifiable());
659
660 let stratified = StratifiedProgram::from(&program);
661 assert!(stratified.is_err());
662 }
663}