logicaffeine_language/semantics/
axioms.rs

1//! Axiom expansion for predicates using lexical semantics.
2//!
3//! This module enriches logical expressions by:
4//!
5//! - **Hypernym inference**: "Dog(x)" → "Dog(x) ∧ Animal(x)"
6//! - **Verb entailments**: "Kill(e,x,y)" → "Kill(e,x,y) ∧ Cause(e,x,Dead(y))"
7//! - **Privative adjectives**: "Fake gun" → "FakeGun(x) ∧ ¬Gun(x)"
8//!
9//! The transformations use lexicon data generated at compile time.
10
11use logicaffeine_base::Arena;
12use crate::ast::{LogicExpr, NeoEventData, Term, ThematicRole};
13use logicaffeine_base::{Interner, Symbol};
14use crate::lexicon::{lookup_canonical, Polarity};
15use crate::token::TokenType;
16
17use super::{is_privative_adjective, lookup_noun_entailments, lookup_noun_hypernyms, lookup_verb_entailment};
18
19/// Apply axiom expansion to a logical expression.
20pub fn apply_axioms<'a>(
21    expr: &'a LogicExpr<'a>,
22    expr_arena: &'a Arena<LogicExpr<'a>>,
23    term_arena: &'a Arena<Term<'a>>,
24    interner: &mut Interner,
25) -> &'a LogicExpr<'a> {
26    transform_expr(expr, expr_arena, term_arena, interner)
27}
28
29fn transform_expr<'a>(
30    expr: &'a LogicExpr<'a>,
31    expr_arena: &'a Arena<LogicExpr<'a>>,
32    term_arena: &'a Arena<Term<'a>>,
33    interner: &mut Interner,
34) -> &'a LogicExpr<'a> {
35    match expr {
36        LogicExpr::Predicate { name, args, .. } => {
37            expand_predicate(*name, args, expr_arena, term_arena, interner)
38        }
39
40        LogicExpr::Quantifier { kind, variable, body, island_id } => {
41            let new_body = transform_expr(body, expr_arena, term_arena, interner);
42            expr_arena.alloc(LogicExpr::Quantifier {
43                kind: *kind,
44                variable: *variable,
45                body: new_body,
46                island_id: *island_id,
47            })
48        }
49
50        LogicExpr::BinaryOp { left, op, right } => {
51            let new_left = transform_expr(left, expr_arena, term_arena, interner);
52            let new_right = transform_expr(right, expr_arena, term_arena, interner);
53            expr_arena.alloc(LogicExpr::BinaryOp {
54                left: new_left,
55                op: op.clone(),
56                right: new_right,
57            })
58        }
59
60        LogicExpr::UnaryOp { op, operand } => {
61            let new_operand = transform_expr(operand, expr_arena, term_arena, interner);
62            expr_arena.alloc(LogicExpr::UnaryOp {
63                op: op.clone(),
64                operand: new_operand,
65            })
66        }
67
68        LogicExpr::NeoEvent(data) => {
69            expand_neo_event(data, expr_arena, term_arena, interner)
70        }
71
72        LogicExpr::Modal { vector, operand } => {
73            let new_operand = transform_expr(operand, expr_arena, term_arena, interner);
74            expr_arena.alloc(LogicExpr::Modal {
75                vector: *vector,
76                operand: new_operand,
77            })
78        }
79
80        LogicExpr::Temporal { operator, body } => {
81            let new_body = transform_expr(body, expr_arena, term_arena, interner);
82            expr_arena.alloc(LogicExpr::Temporal {
83                operator: *operator,
84                body: new_body,
85            })
86        }
87
88        LogicExpr::Lambda { variable, body } => {
89            let new_body = transform_expr(body, expr_arena, term_arena, interner);
90            expr_arena.alloc(LogicExpr::Lambda {
91                variable: *variable,
92                body: new_body,
93            })
94        }
95
96        LogicExpr::Question { wh_variable, body } => {
97            let new_body = transform_expr(body, expr_arena, term_arena, interner);
98            expr_arena.alloc(LogicExpr::Question {
99                wh_variable: *wh_variable,
100                body: new_body,
101            })
102        }
103
104        LogicExpr::YesNoQuestion { body } => {
105            let new_body = transform_expr(body, expr_arena, term_arena, interner);
106            expr_arena.alloc(LogicExpr::YesNoQuestion { body: new_body })
107        }
108
109        _ => expr,
110    }
111}
112
113fn expand_predicate<'a>(
114    name: Symbol,
115    args: &'a [Term<'a>],
116    expr_arena: &'a Arena<LogicExpr<'a>>,
117    term_arena: &'a Arena<Term<'a>>,
118    interner: &mut Interner,
119) -> &'a LogicExpr<'a> {
120    let name_str = interner.resolve(name).to_string();
121    let lower_name = name_str.to_lowercase();
122
123    // Check for canonical mapping (synonyms/antonyms)
124    // E.g., Lack(x,y) -> ¬Have(x,y), Possess(x,y) -> Have(x,y)
125    if let Some(mapping) = lookup_canonical(&lower_name) {
126        let canonical_sym = interner.intern(mapping.lemma);
127        let canonical_pred = expr_arena.alloc(LogicExpr::Predicate {
128            name: canonical_sym,
129            args,
130            world: None,
131        });
132
133        // Wrap antonyms in negation
134        return match mapping.polarity {
135            Polarity::Positive => canonical_pred,
136            Polarity::Negative => expr_arena.alloc(LogicExpr::UnaryOp {
137                op: TokenType::Not,
138                operand: canonical_pred,
139            }),
140        };
141    }
142
143    // Check for compound predicates (e.g., Fake-Gun from non-intersective adjectives)
144    if let Some(hyphen_pos) = name_str.find('-') {
145        let adj_part = name_str[..hyphen_pos].to_string();
146        let noun_part = name_str[hyphen_pos + 1..].to_string();
147
148        if is_privative_adjective(&adj_part) {
149            return expand_privative(&noun_part, args, expr_arena, term_arena, interner);
150        }
151    }
152
153    // Check for noun entailments (Bachelor -> Unmarried + Male)
154    let entailments = lookup_noun_entailments(&lower_name);
155    if !entailments.is_empty() {
156        return expand_noun_entailments(name, args, entailments, expr_arena, term_arena, interner);
157    }
158
159    // Check for hypernyms (Dog -> Animal)
160    let hypernyms = lookup_noun_hypernyms(&lower_name);
161    if !hypernyms.is_empty() {
162        return expand_hypernyms(name, args, hypernyms, expr_arena, term_arena, interner);
163    }
164
165    // No expansion needed - return original
166    expr_arena.alloc(LogicExpr::Predicate { name, args, world: None })
167}
168
169fn expand_privative<'a>(
170    noun: &str,
171    args: &'a [Term<'a>],
172    expr_arena: &'a Arena<LogicExpr<'a>>,
173    term_arena: &'a Arena<Term<'a>>,
174    interner: &mut Interner,
175) -> &'a LogicExpr<'a> {
176    // Fake-Gun(x) => ¬Gun(x) ∧ Resembles(x, ^Gun)
177    let noun_sym = interner.intern(noun);
178    let resembles_sym = interner.intern("Resembles");
179
180    // Gun(x)
181    let noun_pred = expr_arena.alloc(LogicExpr::Predicate {
182        name: noun_sym,
183        args,
184        world: None,
185    });
186
187    // ¬Gun(x)
188    let negated_noun = expr_arena.alloc(LogicExpr::UnaryOp {
189        op: TokenType::Not,
190        operand: noun_pred,
191    });
192
193    // Resembles(x, ^Gun)
194    let intension_term = Term::Intension(noun_sym);
195    let mut resembles_args_vec = Vec::with_capacity(args.len() + 1);
196    if !args.is_empty() {
197        resembles_args_vec.push(clone_term(&args[0], term_arena));
198    }
199    resembles_args_vec.push(intension_term);
200    let resembles_args = term_arena.alloc_slice(resembles_args_vec);
201
202    let resembles_pred = expr_arena.alloc(LogicExpr::Predicate {
203        name: resembles_sym,
204        args: resembles_args,
205        world: None,
206    });
207
208    // ¬Gun(x) ∧ Resembles(x, ^Gun)
209    expr_arena.alloc(LogicExpr::BinaryOp {
210        left: negated_noun,
211        op: TokenType::And,
212        right: resembles_pred,
213    })
214}
215
216fn expand_noun_entailments<'a>(
217    base: Symbol,
218    args: &'a [Term<'a>],
219    entailments: &[&str],
220    expr_arena: &'a Arena<LogicExpr<'a>>,
221    term_arena: &'a Arena<Term<'a>>,
222    interner: &mut Interner,
223) -> &'a LogicExpr<'a> {
224    // Bachelor(x) => Bachelor(x) ∧ Unmarried(x) ∧ Male(x)
225    let base_pred = expr_arena.alloc(LogicExpr::Predicate { name: base, args, world: None });
226
227    let mut result: &LogicExpr = base_pred;
228    for entailment in entailments {
229        let ent_sym = interner.intern(entailment);
230        let ent_pred = expr_arena.alloc(LogicExpr::Predicate {
231            name: ent_sym,
232            args,
233            world: None,
234        });
235        result = expr_arena.alloc(LogicExpr::BinaryOp {
236            left: result,
237            op: TokenType::And,
238            right: ent_pred,
239        });
240    }
241
242    result
243}
244
245fn expand_hypernyms<'a>(
246    base: Symbol,
247    args: &'a [Term<'a>],
248    hypernyms: &[&str],
249    expr_arena: &'a Arena<LogicExpr<'a>>,
250    term_arena: &'a Arena<Term<'a>>,
251    interner: &mut Interner,
252) -> &'a LogicExpr<'a> {
253    // Dog(x) => Dog(x) ∧ Animal(x)
254    let base_pred = expr_arena.alloc(LogicExpr::Predicate { name: base, args, world: None });
255
256    let mut result: &LogicExpr = base_pred;
257    for hypernym in hypernyms {
258        let hyp_sym = interner.intern(hypernym);
259        let hyp_pred = expr_arena.alloc(LogicExpr::Predicate {
260            name: hyp_sym,
261            args,
262            world: None,
263        });
264        result = expr_arena.alloc(LogicExpr::BinaryOp {
265            left: result,
266            op: TokenType::And,
267            right: hyp_pred,
268        });
269    }
270
271    result
272}
273
274fn expand_neo_event<'a>(
275    data: &NeoEventData<'a>,
276    expr_arena: &'a Arena<LogicExpr<'a>>,
277    term_arena: &'a Arena<Term<'a>>,
278    interner: &mut Interner,
279) -> &'a LogicExpr<'a> {
280    let verb_str = interner.resolve(data.verb);
281    let lower_verb = verb_str.to_lowercase();
282
283    // Check for canonical mapping (synonyms/antonyms)
284    // E.g., Lack(x,y) -> ¬Have(x,y)
285    if let Some(mapping) = lookup_canonical(&lower_verb) {
286        let canonical_sym = interner.intern(mapping.lemma);
287
288        // Create NeoEvent with canonical verb
289        let canonical_event = expr_arena.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
290            event_var: data.event_var,
291            verb: canonical_sym,
292            roles: data.roles,
293            modifiers: data.modifiers,
294            suppress_existential: data.suppress_existential,
295            world: None,
296        })));
297
298        // Wrap antonyms in negation
299        return match mapping.polarity {
300            Polarity::Positive => canonical_event,
301            Polarity::Negative => expr_arena.alloc(LogicExpr::UnaryOp {
302                op: TokenType::Not,
303                operand: canonical_event,
304            }),
305        };
306    }
307
308    if let Some((base_verb, manner_preds)) = lookup_verb_entailment(&lower_verb) {
309        // Murder(e) => Murder(e) ∧ Kill(e) ∧ Intentional(Agent)
310        let base_verb_sym = interner.intern(base_verb);
311
312        // Keep original NeoEvent
313        let original = expr_arena.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
314            event_var: data.event_var,
315            verb: data.verb,
316            roles: data.roles,
317            modifiers: data.modifiers,
318            suppress_existential: data.suppress_existential,
319            world: None,
320        })));
321
322        // Create entailed verb NeoEvent (e.g., Kill)
323        let entailed_event = expr_arena.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
324            event_var: data.event_var,
325            verb: base_verb_sym,
326            roles: data.roles,
327            modifiers: data.modifiers,
328            suppress_existential: data.suppress_existential,
329            world: None,
330        })));
331
332        // Conjoin original with entailed
333        let mut result = expr_arena.alloc(LogicExpr::BinaryOp {
334            left: original,
335            op: TokenType::And,
336            right: entailed_event,
337        });
338
339        // Add manner predicates (e.g., Intentional(Agent))
340        for manner in manner_preds {
341            let manner_sym = interner.intern(manner);
342
343            // Find the agent in roles
344            let agent_term = data.roles.iter()
345                .find(|(role, _)| *role == ThematicRole::Agent)
346                .map(|(_, term)| term);
347
348            if let Some(agent) = agent_term {
349                let manner_args = term_arena.alloc_slice([clone_term(agent, term_arena)]);
350                let manner_pred = expr_arena.alloc(LogicExpr::Predicate {
351                    name: manner_sym,
352                    args: manner_args,
353                    world: None,
354                });
355                result = expr_arena.alloc(LogicExpr::BinaryOp {
356                    left: result,
357                    op: TokenType::And,
358                    right: manner_pred,
359                });
360            }
361        }
362
363        result
364    } else {
365        // No entailment - return original unchanged
366        expr_arena.alloc(LogicExpr::NeoEvent(Box::new(NeoEventData {
367            event_var: data.event_var,
368            verb: data.verb,
369            roles: data.roles,
370            modifiers: data.modifiers,
371            suppress_existential: data.suppress_existential,
372            world: None,
373        })))
374    }
375}
376
377fn clone_term<'a>(term: &Term<'a>, arena: &'a Arena<Term<'a>>) -> Term<'a> {
378    match term {
379        Term::Constant(s) => Term::Constant(*s),
380        Term::Variable(s) => Term::Variable(*s),
381        Term::Function(s, args) => {
382            let cloned_args: Vec<Term<'a>> = args.iter().map(|t| clone_term(t, arena)).collect();
383            Term::Function(*s, arena.alloc_slice(cloned_args))
384        }
385        Term::Group(terms) => {
386            let cloned: Vec<Term<'a>> = terms.iter().map(|t| clone_term(t, arena)).collect();
387            Term::Group(arena.alloc_slice(cloned))
388        }
389        Term::Possessed { possessor, possessed } => {
390            let cloned_possessor = arena.alloc(clone_term(possessor, arena));
391            Term::Possessed { possessor: cloned_possessor, possessed: *possessed }
392        }
393        Term::Sigma(s) => Term::Sigma(*s),
394        Term::Intension(s) => Term::Intension(*s),
395        Term::Proposition(e) => Term::Proposition(*e),
396        Term::Value { kind, unit, dimension } => Term::Value {
397            kind: *kind,
398            unit: *unit,
399            dimension: *dimension,
400        },
401    }
402}