Skip to main content

delhi_syntax/
formula.rs

1//! Hash-consed formulas. Identical subterms share a `FormulaId`, so structural
2//! equality is integer equality and entailment can memoise on `(FormulaId, WorldId)`.
3
4use std::collections::HashMap;
5
6/// Index of a ground atomic proposition.
7pub type AtomId = u32;
8/// Index of an agent.
9pub type AgentId = u32;
10/// Bitset over agents, used as the group argument of `C_g`. Caps agents at 32.
11pub type AgentMask = u32;
12/// Handle into a [`Store`].
13pub type FormulaId = u32;
14
15/// One node of the formula DAG. `§4.2` fixes the operator set at six.
16#[derive(Clone, PartialEq, Eq, Hash, Debug)]
17pub enum Node {
18    /// Verum.
19    True,
20    /// An atomic proposition.
21    Atom(AtomId),
22    /// Negation.
23    Not(FormulaId),
24    /// Conjunction.
25    And(FormulaId, FormulaId),
26    /// `K[i] φ` — box over `~ᵢ`.
27    Knows(AgentId, FormulaId),
28    /// `B[i] φ` — box over `Belᵢ`.
29    Believes(AgentId, FormulaId),
30    /// `□[i] φ` — box over `Rᵢ`.
31    Safe(AgentId, FormulaId),
32    /// `B^ψ[i] φ` — arguments are `(agent, ψ, φ)`.
33    CondBel(AgentId, FormulaId, FormulaId),
34    /// `C[g] φ` — box over the reflexive-transitive closure of `∪_{i∈g} ~ᵢ`.
35    Common(AgentMask, FormulaId),
36}
37
38/// Arena of hash-consed formula nodes.
39#[derive(Default, Debug, Clone)]
40pub struct Store {
41    nodes: Vec<Node>,
42    map: HashMap<Node, FormulaId>,
43}
44
45impl Store {
46    /// Interns a node, returning an existing id when the node is already present.
47    pub fn mk(&mut self, n: Node) -> FormulaId {
48        if let Some(&i) = self.map.get(&n) {
49            return i;
50        }
51        let i = self.nodes.len() as FormulaId;
52        self.nodes.push(n.clone());
53        self.map.insert(n, i);
54        i
55    }
56
57    /// The node behind an id.
58    ///
59    /// # Panics
60    /// If `f` was not produced by this store.
61    pub fn node(&self, f: FormulaId) -> &Node {
62        debug_assert!((f as usize) < self.nodes.len(), "id not produced by this store");
63        &self.nodes[f as usize]
64    }
65
66    /// How many distinct nodes are interned.
67    pub fn len(&self) -> usize {
68        self.nodes.len()
69    }
70
71    /// Whether the arena is empty.
72    pub fn is_empty(&self) -> bool {
73        self.nodes.is_empty()
74    }
75
76    /// `⊤`
77    pub fn tru(&mut self) -> FormulaId {
78        self.mk(Node::True)
79    }
80    /// `⊥`
81    pub fn fls(&mut self) -> FormulaId {
82        let t = self.tru();
83        self.mk(Node::Not(t))
84    }
85    /// An atom.
86    pub fn atom(&mut self, a: AtomId) -> FormulaId {
87        self.mk(Node::Atom(a))
88    }
89    /// `!f`
90    pub fn not(&mut self, f: FormulaId) -> FormulaId {
91        self.mk(Node::Not(f))
92    }
93    /// `a & b`
94    pub fn and(&mut self, a: FormulaId, b: FormulaId) -> FormulaId {
95        self.mk(Node::And(a, b))
96    }
97    /// `a | b`, as `!(!a & !b)`.
98    pub fn or(&mut self, a: FormulaId, b: FormulaId) -> FormulaId {
99        let na = self.not(a);
100        let nb = self.not(b);
101        let c = self.and(na, nb);
102        self.not(c)
103    }
104    /// `a -> b`
105    pub fn implies(&mut self, a: FormulaId, b: FormulaId) -> FormulaId {
106        let na = self.not(a);
107        self.or(na, b)
108    }
109    /// Conjunction of a slice; `⊤` when empty.
110    pub fn all(&mut self, fs: &[FormulaId]) -> FormulaId {
111        match fs.split_first() {
112            None => self.tru(),
113            Some((&h, rest)) => rest.iter().fold(h, |acc, &f| self.and(acc, f)),
114        }
115    }
116    /// Disjunction of a slice; `⊥` when empty.
117    pub fn any(&mut self, fs: &[FormulaId]) -> FormulaId {
118        match fs.split_first() {
119            None => self.fls(),
120            Some((&h, rest)) => rest.iter().fold(h, |acc, &f| self.or(acc, f)),
121        }
122    }
123    /// `K[i] f`
124    pub fn knows(&mut self, i: AgentId, f: FormulaId) -> FormulaId {
125        self.mk(Node::Knows(i, f))
126    }
127    /// `B[i] f`
128    pub fn believes(&mut self, i: AgentId, f: FormulaId) -> FormulaId {
129        self.mk(Node::Believes(i, f))
130    }
131    /// `□[i] f`
132    pub fn safe(&mut self, i: AgentId, f: FormulaId) -> FormulaId {
133        self.mk(Node::Safe(i, f))
134    }
135    /// `B^psi[i] phi`
136    pub fn cond_bel(&mut self, i: AgentId, psi: FormulaId, phi: FormulaId) -> FormulaId {
137        self.mk(Node::CondBel(i, psi, phi))
138    }
139    /// `C[g] f`
140    pub fn common(&mut self, g: AgentMask, f: FormulaId) -> FormulaId {
141        self.mk(Node::Common(g, f))
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn identical_subterms_share_one_id() {
151        let mut s = Store::default();
152        let p = s.atom(0);
153        let a = s.believes(0, p);
154        let b = s.believes(0, p);
155        assert_eq!(a, b, "hash-consing must return the same id");
156
157        let before = s.len();
158        let _ = s.believes(0, p);
159        assert_eq!(s.len(), before, "re-making a node must not grow the arena");
160    }
161
162    #[test]
163    fn or_is_built_from_not_and_and() {
164        let mut s = Store::default();
165        let p = s.atom(0);
166        let q = s.atom(1);
167        let disj = s.or(p, q);
168        // !(!p & !q)
169        match s.node(disj) {
170            Node::Not(inner) => match s.node(*inner) {
171                Node::And(x, y) => {
172                    assert!(matches!(s.node(*x), Node::Not(f) if *f == p));
173                    assert!(matches!(s.node(*y), Node::Not(f) if *f == q));
174                }
175                other => panic!("expected And, got {other:?}"),
176            },
177            other => panic!("expected Not, got {other:?}"),
178        }
179    }
180
181    #[test]
182    fn empty_conjunction_is_true_and_empty_disjunction_is_false() {
183        let mut s = Store::default();
184        let t = s.tru();
185        let f = s.fls();
186        assert_eq!(s.all(&[]), t, "empty conjunction must be verum");
187        assert_eq!(s.any(&[]), f, "empty disjunction must be falsum");
188    }
189}