Skip to main content

shifty_opt/
deps.rs

1//! Polarity-aware shape dependency graph (Layer 4).
2//!
3//! An edge `A → B` means shape `A`'s satisfaction depends on shape `B`'s. The
4//! polarity is the *semantic* monotonicity of that dependency, not the surface
5//! `¬`: because our IR encodes `∀π.φ` as `∃≤0 π.¬φ`, a positive SHACL constraint
6//! looks syntactically negative, and two anti-monotone operators compose back to
7//! monotone (see `docs/03-recursion-semantics.md`).
8//!
9//! Per-node polarity:
10//! - `And` / `Or`        → children **positive** (monotone)
11//! - `Not`               → child **negative**
12//! - `Count` lower bound → qualifier **positive** (monotone in the qualifier)
13//! - `Count` upper bound → qualifier **negative** (anti-monotone)
14//!
15//! A `Count{min,max}` with both bounds emits both a positive and a negative edge
16//! to its qualifier (un-fusing), so a genuinely two-sided qualified count reads
17//! as non-monotone.
18
19use serde::{Deserialize, Serialize};
20use shifty_algebra::{Shape, ShapeArena, ShapeId};
21
22#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
23pub enum Polarity {
24    Positive,
25    Negative,
26}
27
28impl Polarity {
29    /// `+1` / `-1`, so a cycle's net polarity is the product of its edges'.
30    pub fn sign(self) -> i8 {
31        match self {
32            Polarity::Positive => 1,
33            Polarity::Negative => -1,
34        }
35    }
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
39pub struct DepEdge {
40    pub from: ShapeId,
41    pub to: ShapeId,
42    pub polarity: Polarity,
43}
44
45/// All polarity-annotated dependency edges of an arena.
46pub fn dependency_edges(arena: &ShapeArena) -> Vec<DepEdge> {
47    let mut edges = Vec::new();
48    for i in 0..arena.len() {
49        let from = ShapeId(i as u32);
50        match arena.get(from) {
51            Shape::Annotated { shape, .. } => edges.push(DepEdge {
52                from,
53                to: *shape,
54                polarity: Polarity::Positive,
55            }),
56            Shape::Not(c) => edges.push(DepEdge {
57                from,
58                to: *c,
59                polarity: Polarity::Negative,
60            }),
61            Shape::And(cs) | Shape::Or(cs) => {
62                for c in cs {
63                    edges.push(DepEdge {
64                        from,
65                        to: *c,
66                        polarity: Polarity::Positive,
67                    });
68                }
69            }
70            Shape::Count {
71                min,
72                max,
73                qualifier,
74                ..
75            } => {
76                if min.is_some() {
77                    edges.push(DepEdge {
78                        from,
79                        to: *qualifier,
80                        polarity: Polarity::Positive,
81                    });
82                }
83                if max.is_some() {
84                    edges.push(DepEdge {
85                        from,
86                        to: *qualifier,
87                        polarity: Polarity::Negative,
88                    });
89                }
90            }
91            Shape::Expression(e) => {
92                // A `Filter` inside the expression keeps inputs satisfying its
93                // shape — monotone, so the edge is positive.
94                let mut refs = Vec::new();
95                e.referenced_shapes(&mut refs);
96                for to in refs {
97                    edges.push(DepEdge {
98                        from,
99                        to,
100                        polarity: Polarity::Positive,
101                    });
102                }
103            }
104            _ => {}
105        }
106    }
107    edges
108}