Skip to main content

logicaffeine_verify/
automata.rs

1//! Automata for Reactive Synthesis
2//!
3//! Büchi automaton construction from LTL specifications.
4//! Used as the intermediate representation for game solving.
5
6use crate::ir::VerifyExpr;
7
8/// A state in a Büchi automaton.
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct AutomatonState {
11    pub id: usize,
12    pub label: String,
13}
14
15/// A transition in a Büchi automaton.
16#[derive(Debug, Clone)]
17pub struct AutomatonTransition {
18    pub from: usize,
19    pub guard: VerifyExpr,
20    pub to: usize,
21}
22
23/// A Büchi automaton for LTL specifications.
24#[derive(Debug, Clone)]
25pub struct BuchiAutomaton {
26    pub states: Vec<AutomatonState>,
27    pub initial: usize,
28    pub accepting: Vec<usize>,
29    pub transitions: Vec<AutomatonTransition>,
30}
31
32/// Convert an LTL specification to a Büchi automaton.
33///
34/// Handles common LTL patterns:
35/// - G(p) → single accepting state, self-loop with guard p
36/// - G(F(p)) → two states (waiting, seen), both accepting
37/// - G(p → F(q)) → two states, transitions on p/q
38/// - Simple boolean → single state
39pub fn ltl_to_buchi(spec: &VerifyExpr) -> BuchiAutomaton {
40    match detect_ltl_pattern(spec) {
41        LtlPattern::Safety(prop) => {
42            // G(p) → single accepting state with self-loop guarded by p
43            BuchiAutomaton {
44                states: vec![AutomatonState { id: 0, label: "safe".into() }],
45                initial: 0,
46                accepting: vec![0],
47                transitions: vec![AutomatonTransition {
48                    from: 0,
49                    guard: prop,
50                    to: 0,
51                }],
52            }
53        }
54        LtlPattern::Response(trigger, response) => {
55            // G(p → F(q)) → two states: idle, waiting_for_response
56            BuchiAutomaton {
57                states: vec![
58                    AutomatonState { id: 0, label: "idle".into() },
59                    AutomatonState { id: 1, label: "waiting".into() },
60                ],
61                initial: 0,
62                accepting: vec![0], // Only idle is accepting (response must eventually happen)
63                transitions: vec![
64                    // idle → idle (no trigger)
65                    AutomatonTransition {
66                        from: 0,
67                        guard: VerifyExpr::not(trigger.clone()),
68                        to: 0,
69                    },
70                    // idle → waiting (trigger fires, response not yet)
71                    AutomatonTransition {
72                        from: 0,
73                        guard: VerifyExpr::and(trigger.clone(), VerifyExpr::not(response.clone())),
74                        to: 1,
75                    },
76                    // idle → idle (trigger fires AND response immediate)
77                    AutomatonTransition {
78                        from: 0,
79                        guard: VerifyExpr::and(trigger, response.clone()),
80                        to: 0,
81                    },
82                    // waiting → idle (response arrives)
83                    AutomatonTransition {
84                        from: 1,
85                        guard: response.clone(),
86                        to: 0,
87                    },
88                    // waiting → waiting (still waiting)
89                    AutomatonTransition {
90                        from: 1,
91                        guard: VerifyExpr::not(response),
92                        to: 1,
93                    },
94                ],
95            }
96        }
97        LtlPattern::Liveness(prop) => {
98            // G(F(p)) → two states, loop between them
99            BuchiAutomaton {
100                states: vec![
101                    AutomatonState { id: 0, label: "unseen".into() },
102                    AutomatonState { id: 1, label: "seen".into() },
103                ],
104                initial: 0,
105                accepting: vec![1], // Must visit "seen" infinitely often
106                transitions: vec![
107                    AutomatonTransition { from: 0, guard: VerifyExpr::not(prop.clone()), to: 0 },
108                    AutomatonTransition { from: 0, guard: prop.clone(), to: 1 },
109                    AutomatonTransition { from: 1, guard: VerifyExpr::bool(true), to: 0 },
110                ],
111            }
112        }
113        LtlPattern::Boolean(prop) => {
114            // Simple boolean → single state
115            BuchiAutomaton {
116                states: vec![AutomatonState { id: 0, label: "check".into() }],
117                initial: 0,
118                accepting: vec![0],
119                transitions: vec![AutomatonTransition {
120                    from: 0,
121                    guard: prop,
122                    to: 0,
123                }],
124            }
125        }
126    }
127}
128
129/// Detected LTL pattern.
130enum LtlPattern {
131    Safety(VerifyExpr),                      // G(p)
132    Response(VerifyExpr, VerifyExpr),         // G(p → F(q))
133    Liveness(VerifyExpr),                     // G(F(p))
134    Boolean(VerifyExpr),                      // p
135}
136
137/// Detect the LTL pattern of a specification.
138fn detect_ltl_pattern(spec: &VerifyExpr) -> LtlPattern {
139    // Pattern: NOT(something) at top level → safety (G(NOT(bad)))
140    if let VerifyExpr::Not(_inner) = spec {
141        return LtlPattern::Safety(spec.clone());
142    }
143
144    // Pattern: implies at top → response (G(p → F(q)))
145    if let VerifyExpr::Binary { op: crate::ir::VerifyOp::Implies, left, right } = spec {
146        return LtlPattern::Response(*left.clone(), *right.clone());
147    }
148
149    // Pattern: single variable or boolean → safety
150    if matches!(spec, VerifyExpr::Var(_) | VerifyExpr::Bool(_)) {
151        return LtlPattern::Safety(spec.clone());
152    }
153
154    // Default: treat as safety
155    LtlPattern::Safety(spec.clone())
156}