Skip to main content

logicaffeine_language/semantics/
kripke.rs

1//! Kripke Semantics Lowering Pass
2//!
3//! Transforms modal operators into explicit possible world semantics:
4//! - Diamond P (force <= 0.5) → Exists w'(Accessible(w, w') And P(w'))
5//! - Box P (force > 0.5) → ForAll w'(Accessible(w, w') Implies P(w'))
6
7use logicaffeine_base::Arena;
8use crate::ast::{LogicExpr, ModalDomain, ModalVector, NeoEventData, QuantifierKind, Term};
9use logicaffeine_base::{Interner, Symbol};
10use crate::token::TokenType;
11
12/// Context for tracking world variables during Kripke lowering.
13///
14/// For hardware verification, `clock_counter` tracks discrete timesteps
15/// and `domain_hint` disambiguates temporal vs modal lowering when
16/// the context is ambiguous.
17pub struct KripkeContext {
18    world_counter: u32,
19    current_world: Symbol,
20    /// Discrete timestep counter for hardware clock cycles.
21    /// Incremented when generating Next_Temporal worlds.
22    clock_counter: u32,
23    /// Hint for the current modal domain being lowered.
24    /// Set to `Some(Temporal)` when processing temporal operators,
25    /// enabling domain-aware disambiguation.
26    domain_hint: Option<ModalDomain>,
27}
28
29impl KripkeContext {
30    pub fn new(interner: &mut Interner) -> Self {
31        Self {
32            world_counter: 0,
33            current_world: interner.intern("w0"),
34            clock_counter: 0,
35            domain_hint: None,
36        }
37    }
38
39    pub fn fresh_world(&mut self, interner: &mut Interner) -> Symbol {
40        self.world_counter += 1;
41        interner.intern(&format!("w{}", self.world_counter))
42    }
43
44    /// Get the current clock counter value (discrete timestep).
45    pub fn clock_counter(&self) -> u32 {
46        self.clock_counter
47    }
48
49    /// Get the current domain hint.
50    pub fn domain_hint(&self) -> Option<ModalDomain> {
51        self.domain_hint
52    }
53
54    /// Advance the clock counter by one tick (for Next_Temporal).
55    fn tick_clock(&mut self) {
56        self.clock_counter += 1;
57    }
58
59    /// Set the domain hint for the current lowering context.
60    fn set_domain_hint(&mut self, domain: ModalDomain) {
61        self.domain_hint = Some(domain);
62    }
63
64    /// Clear the domain hint.
65    fn clear_domain_hint(&mut self) {
66        self.domain_hint = None;
67    }
68}
69
70/// Apply Kripke lowering to transform modal operators into explicit world quantification.
71///
72/// This transforms surface modal operators (`◇`, `□`) into explicit First-Order Logic
73/// with possible world semantics, enabling Z3 verification.
74///
75/// ## Example
76/// Surface: `◇Fly(x)` (John can fly)
77/// Deep: `∃w'(Accessible(w₀, w') ∧ Fly(x, w'))` (There exists an accessible world where John flies)
78pub fn apply_kripke_lowering<'a>(
79    expr: &'a LogicExpr<'a>,
80    expr_arena: &'a Arena<LogicExpr<'a>>,
81    term_arena: &'a Arena<Term<'a>>,
82    interner: &mut Interner,
83) -> &'a LogicExpr<'a> {
84    let mut ctx = KripkeContext::new(interner);
85    lower_expr(expr, &mut ctx, expr_arena, term_arena, interner)
86}
87
88fn lower_expr<'a>(
89    expr: &'a LogicExpr<'a>,
90    ctx: &mut KripkeContext,
91    expr_arena: &'a Arena<LogicExpr<'a>>,
92    term_arena: &'a Arena<Term<'a>>,
93    interner: &mut Interner,
94) -> &'a LogicExpr<'a> {
95    match expr {
96        LogicExpr::Modal { vector, operand } => {
97            lower_modal(vector, operand, ctx, expr_arena, term_arena, interner)
98        }
99
100        LogicExpr::Predicate { name, args, world } => {
101            if world.is_none() {
102                // Add current world to predicate
103                expr_arena.alloc(LogicExpr::Predicate {
104                    name: *name,
105                    args: *args,
106                    world: Some(ctx.current_world),
107                })
108            } else {
109                expr
110            }
111        }
112
113        LogicExpr::Quantifier { kind, variable, body, island_id } => {
114            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
115            expr_arena.alloc(LogicExpr::Quantifier {
116                kind: *kind,
117                variable: *variable,
118                body: new_body,
119                island_id: *island_id,
120            })
121        }
122
123        LogicExpr::BinaryOp { left, op, right } => {
124            let new_left = lower_expr(left, ctx, expr_arena, term_arena, interner);
125            let new_right = lower_expr(right, ctx, expr_arena, term_arena, interner);
126            expr_arena.alloc(LogicExpr::BinaryOp {
127                left: new_left,
128                op: op.clone(),
129                right: new_right,
130            })
131        }
132
133        LogicExpr::UnaryOp { op, operand } => {
134            let new_operand = lower_expr(operand, ctx, expr_arena, term_arena, interner);
135            expr_arena.alloc(LogicExpr::UnaryOp {
136                op: op.clone(),
137                operand: new_operand,
138            })
139        }
140
141        LogicExpr::NeoEvent(data) => {
142            // Set the world on the event to the current world
143            if data.world.is_none() {
144                expr_arena.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
145                    event_var: data.event_var,
146                    verb: data.verb,
147                    roles: data.roles,
148                    modifiers: data.modifiers,
149                    suppress_existential: data.suppress_existential,
150                    world: Some(ctx.current_world),
151                })))
152            } else {
153                expr
154            }
155        }
156
157        LogicExpr::Temporal { operator, body } => {
158            use crate::ast::logic::TemporalOperator;
159            // Set domain hint for temporal lowering
160            ctx.set_domain_hint(ModalDomain::Temporal);
161            let result = match operator {
162                // LTL operators → Kripke world quantification
163                TemporalOperator::Always => {
164                    // G(φ) → ∀w'(Accessible_Temporal(w, w') → φ(w'))
165                    lower_temporal_unary(
166                        body, ctx, expr_arena, term_arena, interner,
167                        "Accessible_Temporal", true,
168                    )
169                }
170                TemporalOperator::Eventually => {
171                    // F(φ) → ∃w'(Reachable_Temporal(w, w') ∧ φ(w'))
172                    lower_temporal_unary(
173                        body, ctx, expr_arena, term_arena, interner,
174                        "Reachable_Temporal", false,
175                    )
176                }
177                TemporalOperator::BoundedEventually(n) => {
178                    // F≤n(φ) — preserve bound for SVA synthesis (##[0:n])
179                    let lowered_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
180                    expr_arena.alloc(LogicExpr::Temporal {
181                        operator: TemporalOperator::BoundedEventually(*n),
182                        body: lowered_body,
183                    })
184                }
185                TemporalOperator::Next => {
186                    // X(φ) → ∀w'(Next_Temporal(w, w') → φ(w'))
187                    ctx.tick_clock();
188                    lower_temporal_unary(
189                        body, ctx, expr_arena, term_arena, interner,
190                        "Next_Temporal", true,
191                    )
192                }
193                // Priorian tense operators — pass through (linguistic, not hardware)
194                TemporalOperator::Past | TemporalOperator::Future => {
195                    let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
196                    expr_arena.alloc(LogicExpr::Temporal {
197                        operator: *operator,
198                        body: new_body,
199                    })
200                }
201            };
202            ctx.clear_domain_hint();
203            result
204        }
205
206        LogicExpr::TemporalBinary { operator, left, right } => {
207            // φ U ψ → ψ(w) ∨ (φ(w) ∧ ∃w'(Next_Temporal(w,w') ∧ (φ U ψ)(w')))
208            // For now: lower both operands with world threading
209            let new_left = lower_expr(left, ctx, expr_arena, term_arena, interner);
210            let new_right = lower_expr(right, ctx, expr_arena, term_arena, interner);
211
212            // Generate Next_Temporal accessibility for the recursive step
213            let source_world = ctx.current_world;
214            let target_world = ctx.fresh_world(interner);
215            let next_name = interner.intern("Next_Temporal");
216            let accessibility = expr_arena.alloc(LogicExpr::Predicate {
217                name: next_name,
218                args: term_arena.alloc_slice([
219                    Term::Variable(source_world),
220                    Term::Variable(target_world),
221                ]),
222                world: None,
223            });
224
225            // Build: right(w) ∨ (left(w) ∧ ∃w'(Next_Temporal(w,w') ∧ ...))
226            let recursive_body = expr_arena.alloc(LogicExpr::BinaryOp {
227                left: accessibility,
228                op: crate::token::TokenType::And,
229                right: expr_arena.alloc(LogicExpr::TemporalBinary {
230                    operator: *operator,
231                    left: new_left,
232                    right: new_right,
233                }),
234            });
235            let existential = expr_arena.alloc(LogicExpr::Quantifier {
236                kind: QuantifierKind::Existential,
237                variable: target_world,
238                body: recursive_body,
239                island_id: 0,
240            });
241            let left_and_next = expr_arena.alloc(LogicExpr::BinaryOp {
242                left: new_left,
243                op: crate::token::TokenType::And,
244                right: existential,
245            });
246            expr_arena.alloc(LogicExpr::BinaryOp {
247                left: new_right,
248                op: crate::token::TokenType::Or,
249                right: left_and_next,
250            })
251        }
252
253        LogicExpr::Aspectual { operator, body } => {
254            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
255            expr_arena.alloc(LogicExpr::Aspectual {
256                operator: *operator,
257                body: new_body,
258            })
259        }
260
261        LogicExpr::Voice { operator, body } => {
262            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
263            expr_arena.alloc(LogicExpr::Voice {
264                operator: *operator,
265                body: new_body,
266            })
267        }
268
269        LogicExpr::Lambda { variable, body } => {
270            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
271            expr_arena.alloc(LogicExpr::Lambda {
272                variable: *variable,
273                body: new_body,
274            })
275        }
276
277        LogicExpr::App { function, argument } => {
278            let new_function = lower_expr(function, ctx, expr_arena, term_arena, interner);
279            let new_argument = lower_expr(argument, ctx, expr_arena, term_arena, interner);
280            expr_arena.alloc(LogicExpr::App {
281                function: new_function,
282                argument: new_argument,
283            })
284        }
285
286        LogicExpr::Intensional { operator, content } => {
287            let new_content = lower_expr(content, ctx, expr_arena, term_arena, interner);
288            expr_arena.alloc(LogicExpr::Intensional {
289                operator: *operator,
290                content: new_content,
291            })
292        }
293
294        LogicExpr::Control { verb, subject, object, infinitive } => {
295            let new_infinitive = lower_expr(infinitive, ctx, expr_arena, term_arena, interner);
296            expr_arena.alloc(LogicExpr::Control {
297                verb: *verb,
298                subject: *subject,
299                object: *object,
300                infinitive: new_infinitive,
301            })
302        }
303
304        LogicExpr::Scopal { operator, body } => {
305            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
306            expr_arena.alloc(LogicExpr::Scopal {
307                operator: *operator,
308                body: new_body,
309            })
310        }
311
312        LogicExpr::Question { wh_variable, body } => {
313            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
314            expr_arena.alloc(LogicExpr::Question {
315                wh_variable: *wh_variable,
316                body: new_body,
317            })
318        }
319
320        LogicExpr::YesNoQuestion { body } => {
321            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
322            expr_arena.alloc(LogicExpr::YesNoQuestion { body: new_body })
323        }
324
325        LogicExpr::Focus { kind, focused, scope } => {
326            let new_scope = lower_expr(scope, ctx, expr_arena, term_arena, interner);
327            expr_arena.alloc(LogicExpr::Focus {
328                kind: *kind,
329                focused: *focused,
330                scope: new_scope,
331            })
332        }
333
334        LogicExpr::Distributive { predicate } => {
335            let new_predicate = lower_expr(predicate, ctx, expr_arena, term_arena, interner);
336            expr_arena.alloc(LogicExpr::Distributive {
337                predicate: new_predicate,
338            })
339        }
340
341        LogicExpr::Counterfactual { antecedent, consequent } => {
342            let new_antecedent = lower_expr(antecedent, ctx, expr_arena, term_arena, interner);
343            let new_consequent = lower_expr(consequent, ctx, expr_arena, term_arena, interner);
344            expr_arena.alloc(LogicExpr::Counterfactual {
345                antecedent: new_antecedent,
346                consequent: new_consequent,
347            })
348        }
349
350        LogicExpr::Event { predicate, adverbs } => {
351            let new_predicate = lower_expr(predicate, ctx, expr_arena, term_arena, interner);
352            expr_arena.alloc(LogicExpr::Event {
353                predicate: new_predicate,
354                adverbs: *adverbs,
355            })
356        }
357
358        LogicExpr::Imperative { action } => {
359            let new_action = lower_expr(action, ctx, expr_arena, term_arena, interner);
360            expr_arena.alloc(LogicExpr::Imperative { action: new_action })
361        }
362        LogicExpr::Exclamative { degree_var, body } => {
363            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
364            expr_arena.alloc(LogicExpr::Exclamative { degree_var: *degree_var, body: new_body })
365        }
366        LogicExpr::Optative { wish } => {
367            let new_wish = lower_expr(wish, ctx, expr_arena, term_arena, interner);
368            expr_arena.alloc(LogicExpr::Optative { wish: new_wish })
369        }
370        LogicExpr::Implicature { assertion, implicature } => {
371            let new_assertion = lower_expr(assertion, ctx, expr_arena, term_arena, interner);
372            let new_implicature = lower_expr(implicature, ctx, expr_arena, term_arena, interner);
373            expr_arena.alloc(LogicExpr::Implicature {
374                assertion: new_assertion,
375                implicature: new_implicature,
376            })
377        }
378
379        LogicExpr::Causal { effect, cause } => {
380            let new_effect = lower_expr(effect, ctx, expr_arena, term_arena, interner);
381            let new_cause = lower_expr(cause, ctx, expr_arena, term_arena, interner);
382            expr_arena.alloc(LogicExpr::Causal {
383                effect: new_effect,
384                cause: new_cause,
385            })
386        }
387
388        LogicExpr::Concessive { main, concession } => {
389            let new_main = lower_expr(main, ctx, expr_arena, term_arena, interner);
390            let new_concession = lower_expr(concession, ctx, expr_arena, term_arena, interner);
391            expr_arena.alloc(LogicExpr::Concessive {
392                main: new_main,
393                concession: new_concession,
394            })
395        }
396
397        LogicExpr::Presupposition { assertion, presupposition } => {
398            let new_assertion = lower_expr(assertion, ctx, expr_arena, term_arena, interner);
399            let new_presupposition = lower_expr(presupposition, ctx, expr_arena, term_arena, interner);
400            expr_arena.alloc(LogicExpr::Presupposition {
401                assertion: new_assertion,
402                presupposition: new_presupposition,
403            })
404        }
405
406        LogicExpr::TemporalAnchor { anchor, body } => {
407            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
408            expr_arena.alloc(LogicExpr::TemporalAnchor {
409                anchor: *anchor,
410                body: new_body,
411            })
412        }
413
414        LogicExpr::GroupQuantifier { group_var, count, member_var, restriction, body } => {
415            let new_restriction = lower_expr(restriction, ctx, expr_arena, term_arena, interner);
416            let new_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
417            expr_arena.alloc(LogicExpr::GroupQuantifier {
418                group_var: *group_var,
419                count: *count,
420                member_var: *member_var,
421                restriction: new_restriction,
422                body: new_body,
423            })
424        }
425
426        // Leaf nodes that don't need transformation
427        LogicExpr::Identity { .. }
428        | LogicExpr::Metaphor { .. }
429        | LogicExpr::Categorical(_)
430        | LogicExpr::Relation(_)
431        | LogicExpr::Atom(_)
432        | LogicExpr::Superlative { .. }
433        | LogicExpr::Comparative { .. }
434        | LogicExpr::SpeechAct { .. } => expr,
435    }
436}
437
438fn lower_modal<'a>(
439    vector: &ModalVector,
440    operand: &'a LogicExpr<'a>,
441    ctx: &mut KripkeContext,
442    expr_arena: &'a Arena<LogicExpr<'a>>,
443    term_arena: &'a Arena<Term<'a>>,
444    interner: &mut Interner,
445) -> &'a LogicExpr<'a> {
446    let source_world = ctx.current_world;
447    let target_world = ctx.fresh_world(interner);
448
449    // Lower operand with new current world
450    let old_world = ctx.current_world;
451    ctx.current_world = target_world;
452    let lowered_operand = lower_expr(operand, ctx, expr_arena, term_arena, interner);
453    ctx.current_world = old_world;
454
455    // Create accessibility predicate based on modal domain
456    let access_name = match vector.domain {
457        ModalDomain::Alethic => interner.intern("Accessible_Alethic"),
458        ModalDomain::Deontic => interner.intern("Accessible_Deontic"),
459        ModalDomain::Temporal => interner.intern("Accessible_Temporal"),
460    };
461
462    let accessibility = expr_arena.alloc(LogicExpr::Predicate {
463        name: access_name,
464        args: term_arena.alloc_slice([
465            Term::Variable(source_world),
466            Term::Variable(target_world),
467        ]),
468        world: None, // Accessibility predicate is a meta-level relation
469    });
470
471    if vector.force > 0.5 {
472        // Necessity (Box): ForAll w'(Accessible(w, w') -> P(w'))
473        let implication = expr_arena.alloc(LogicExpr::BinaryOp {
474            left: accessibility,
475            op: TokenType::Implies,
476            right: lowered_operand,
477        });
478        expr_arena.alloc(LogicExpr::Quantifier {
479            kind: QuantifierKind::Universal,
480            variable: target_world,
481            body: implication,
482            island_id: 0,
483        })
484    } else if vector.force <= 0.0 {
485        // Impossibility (force 0, e.g. "cannot" = ¬◇ = □¬):
486        // ForAll w'(Accessible(w, w') -> ¬P(w')). Lowering this as a Diamond
487        // possibility would assert the LOGICAL OPPOSITE (that P is possible).
488        let negated = expr_arena.alloc(LogicExpr::UnaryOp {
489            op: TokenType::Not,
490            operand: lowered_operand,
491        });
492        let implication = expr_arena.alloc(LogicExpr::BinaryOp {
493            left: accessibility,
494            op: TokenType::Implies,
495            right: negated,
496        });
497        expr_arena.alloc(LogicExpr::Quantifier {
498            kind: QuantifierKind::Universal,
499            variable: target_world,
500            body: implication,
501            island_id: 0,
502        })
503    } else {
504        // Possibility (Diamond): Exists w'(Accessible(w, w') ∧ P(w'))
505        let conjunction = expr_arena.alloc(LogicExpr::BinaryOp {
506            left: accessibility,
507            op: TokenType::And,
508            right: lowered_operand,
509        });
510        expr_arena.alloc(LogicExpr::Quantifier {
511            kind: QuantifierKind::Existential,
512            variable: target_world,
513            body: conjunction,
514            island_id: 0,
515        })
516    }
517}
518
519/// Lower a unary LTL temporal operator into Kripke world quantification.
520///
521/// `is_universal`: true for G/X (∀w'), false for F (∃w')
522fn lower_temporal_unary<'a>(
523    body: &'a LogicExpr<'a>,
524    ctx: &mut KripkeContext,
525    expr_arena: &'a Arena<LogicExpr<'a>>,
526    term_arena: &'a Arena<Term<'a>>,
527    interner: &mut Interner,
528    predicate_name: &str,
529    is_universal: bool,
530) -> &'a LogicExpr<'a> {
531    let source_world = ctx.current_world;
532    let target_world = ctx.fresh_world(interner);
533
534    // Lower body with new current world
535    let old_world = ctx.current_world;
536    ctx.current_world = target_world;
537    let lowered_body = lower_expr(body, ctx, expr_arena, term_arena, interner);
538    ctx.current_world = old_world;
539
540    // Create temporal accessibility predicate
541    let access_name = interner.intern(predicate_name);
542    let accessibility = expr_arena.alloc(LogicExpr::Predicate {
543        name: access_name,
544        args: term_arena.alloc_slice([
545            Term::Variable(source_world),
546            Term::Variable(target_world),
547        ]),
548        world: None,
549    });
550
551    if is_universal {
552        // G/X: ∀w'(Accessible_Temporal(w, w') → φ(w'))
553        let implication = expr_arena.alloc(LogicExpr::BinaryOp {
554            left: accessibility,
555            op: TokenType::Implies,
556            right: lowered_body,
557        });
558        expr_arena.alloc(LogicExpr::Quantifier {
559            kind: QuantifierKind::Universal,
560            variable: target_world,
561            body: implication,
562            island_id: 0,
563        })
564    } else {
565        // F: ∃w'(Reachable_Temporal(w, w') ∧ φ(w'))
566        let conjunction = expr_arena.alloc(LogicExpr::BinaryOp {
567            left: accessibility,
568            op: TokenType::And,
569            right: lowered_body,
570        });
571        expr_arena.alloc(LogicExpr::Quantifier {
572            kind: QuantifierKind::Existential,
573            variable: target_world,
574            body: conjunction,
575            island_id: 0,
576        })
577    }
578}