logicaffeine_proof/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! # Logicaffeine Proof Engine
4//!
5//! Core structures for proof representation and verification.
6//!
7//! This module defines the "Shape of Truth." A proof is not a boolean;
8//! it is a recursive tree of inference rules that can be inspected,
9//! transformed, and verified.
10//!
11//! ## Curry-Howard Correspondence
12//!
13//! The proof engine implements the propositions-as-types paradigm:
14//!
15//! - **A Proposition is a Type** — logical formulas correspond to types
16//! - **A Proof is a Program** — derivation trees are proof terms
17//! - **Verification is Type Checking** — the kernel validates proof terms
18//!
19//! ## Architecture Invariant
20//!
21//! This crate has **no dependency** on the language crate (Liskov boundary).
22//! The conversion from `LogicExpr` → [`ProofExpr`] lives in the language crate,
23//! ensuring the proof engine remains pure and reusable.
24
25pub mod certifier;
26pub mod engine;
27pub mod error;
28pub mod hints;
29pub mod unify;
30
31#[cfg(feature = "verification")]
32pub mod oracle;
33
34pub use engine::BackwardChainer;
35pub use error::ProofError;
36pub use hints::{suggest_hint, SocraticHint, SuggestedTactic};
37pub use unify::Substitution;
38
39use std::fmt;
40
41// =============================================================================
42// PROOF TERM - Owned representation of logical terms
43// =============================================================================
44
45/// Owned term representation for proof manipulation.
46///
47/// Decoupled from arena-allocated `Term<'a>` to allow proof trees to persist
48/// beyond the lifetime of the parsing arena.
49///
50/// # See Also
51///
52/// * [`ProofExpr`] - Expression-level representation containing `ProofTerm`s
53/// * [`unify::unify_terms`] - Unification algorithm for terms
54/// * [`unify::Substitution`] - Maps variables to terms
55/// * [`certifier::certify`] - Converts proof trees to kernel terms
56#[derive(Debug, Clone, PartialEq, Eq, Hash)]
57pub enum ProofTerm {
58 /// A constant symbol (e.g., "Socrates", "42")
59 Constant(String),
60
61 /// A variable symbol (e.g., "x", "y")
62 Variable(String),
63
64 /// A function application (e.g., "father(x)", "add(1, 2)")
65 Function(String, Vec<ProofTerm>),
66
67 /// A group/tuple of terms (e.g., "(x, y)")
68 Group(Vec<ProofTerm>),
69
70 /// Reference to a bound variable in a pattern context.
71 /// Distinct from Variable (unification var) and Constant (global name).
72 /// Prevents variable capture bugs during alpha-conversion.
73 BoundVarRef(String),
74}
75
76impl fmt::Display for ProofTerm {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 ProofTerm::Constant(s) => write!(f, "{}", s),
80 ProofTerm::Variable(s) => write!(f, "{}", s),
81 ProofTerm::Function(name, args) => {
82 write!(f, "{}(", name)?;
83 for (i, arg) in args.iter().enumerate() {
84 if i > 0 {
85 write!(f, ", ")?;
86 }
87 write!(f, "{}", arg)?;
88 }
89 write!(f, ")")
90 }
91 ProofTerm::Group(terms) => {
92 write!(f, "(")?;
93 for (i, t) in terms.iter().enumerate() {
94 if i > 0 {
95 write!(f, ", ")?;
96 }
97 write!(f, "{}", t)?;
98 }
99 write!(f, ")")
100 }
101 ProofTerm::BoundVarRef(s) => write!(f, "#{}", s),
102 }
103 }
104}
105
106// =============================================================================
107// PROOF EXPRESSION - Owned representation of logical expressions
108// =============================================================================
109
110/// Owned expression representation for proof manipulation.
111///
112/// Supports all `LogicExpr` variants to enable full language coverage.
113/// This is the "proposition" side of the Curry-Howard correspondence.
114///
115/// # See Also
116///
117/// * [`ProofTerm`] - Terms embedded within expressions (predicate arguments)
118/// * [`unify::unify_exprs`] - Expression-level unification with alpha-equivalence
119/// * [`unify::beta_reduce`] - Normalizes lambda applications
120/// * [`certifier::certify`] - Converts expressions to kernel types
121/// * [`DerivationTree`] - Proof trees conclude with a `ProofExpr`
122#[derive(Debug, Clone, PartialEq)]
123pub enum ProofExpr {
124 // --- Core FOL ---
125
126 /// Atomic predicate: P(t1, t2, ...)
127 Predicate {
128 name: String,
129 args: Vec<ProofTerm>,
130 world: Option<String>,
131 },
132
133 /// Identity: t1 = t2
134 Identity(ProofTerm, ProofTerm),
135
136 /// Propositional atom
137 Atom(String),
138
139 // --- Logical Connectives ---
140
141 /// Conjunction: P ∧ Q
142 And(Box<ProofExpr>, Box<ProofExpr>),
143
144 /// Disjunction: P ∨ Q
145 Or(Box<ProofExpr>, Box<ProofExpr>),
146
147 /// Implication: P → Q
148 Implies(Box<ProofExpr>, Box<ProofExpr>),
149
150 /// Biconditional: P ↔ Q
151 Iff(Box<ProofExpr>, Box<ProofExpr>),
152
153 /// Negation: ¬P
154 Not(Box<ProofExpr>),
155
156 // --- Quantifiers ---
157
158 /// Universal quantification: ∀x P(x)
159 ForAll {
160 variable: String,
161 body: Box<ProofExpr>,
162 },
163
164 /// Existential quantification: ∃x P(x)
165 Exists {
166 variable: String,
167 body: Box<ProofExpr>,
168 },
169
170 // --- Modal Logic ---
171
172 /// Modal operator: □P or ◇P (with world semantics)
173 Modal {
174 domain: String,
175 force: f32,
176 flavor: String,
177 body: Box<ProofExpr>,
178 },
179
180 // --- Temporal Logic ---
181
182 /// Temporal operator: Past(P), Future(P), Always(P), Eventually(P), Next(P)
183 Temporal {
184 operator: String,
185 body: Box<ProofExpr>,
186 },
187
188 /// Binary temporal operator: Until(P,Q), Release(P,Q), WeakUntil(P,Q)
189 TemporalBinary {
190 operator: String,
191 left: Box<ProofExpr>,
192 right: Box<ProofExpr>,
193 },
194
195 // --- Lambda Calculus (CIC extension) ---
196
197 /// Lambda abstraction: λx.P
198 Lambda {
199 variable: String,
200 body: Box<ProofExpr>,
201 },
202
203 /// Function application: (f x)
204 App(Box<ProofExpr>, Box<ProofExpr>),
205
206 // --- Event Semantics ---
207
208 /// Neo-Davidsonian event: ∃e(Verb(e) ∧ Agent(e,x) ∧ ...)
209 NeoEvent {
210 event_var: String,
211 verb: String,
212 roles: Vec<(String, ProofTerm)>,
213 },
214
215 // --- Peano / Inductive Types (CIC Extension) ---
216
217 /// Data Constructor: Zero, Succ(n), Cons(h, t), etc.
218 /// The building blocks of inductive types.
219 Ctor {
220 name: String,
221 args: Vec<ProofExpr>,
222 },
223
224 /// Pattern Matching: match n { Zero => A, Succ(k) => B }
225 /// Eliminates inductive types by case analysis.
226 Match {
227 scrutinee: Box<ProofExpr>,
228 arms: Vec<MatchArm>,
229 },
230
231 /// Recursive Function (Fixpoint): fix f. λn. ...
232 /// Defines recursive functions over inductive types.
233 Fixpoint {
234 name: String,
235 body: Box<ProofExpr>,
236 },
237
238 /// Typed Variable: n : Nat
239 /// Signals to the prover that induction may be applicable.
240 TypedVar {
241 name: String,
242 typename: String,
243 },
244
245 // --- Higher-Order Pattern Unification ---
246
247 /// Meta-variable (unification hole) to be solved during proof search.
248 /// Represents an unknown expression, typically a function or predicate.
249 /// Example: Hole("P") in ?P(x) = Body, to be solved as P = λx.Body
250 Hole(String),
251
252 /// Embedded term (lifts ProofTerm into ProofExpr context).
253 /// Used when a term appears where an expression is expected.
254 /// Example: App(Hole("P"), Term(BoundVarRef("x")))
255 Term(ProofTerm),
256
257 // --- Fallback ---
258
259 /// Unsupported construct (with description for debugging)
260 Unsupported(String),
261}
262
263// =============================================================================
264// MATCH ARM - A single case in pattern matching
265// =============================================================================
266
267/// A single arm in a match expression.
268/// Example: Succ(k) => Add(k, m)
269#[derive(Debug, Clone, PartialEq)]
270pub struct MatchArm {
271 /// The constructor being matched: "Zero", "Succ", "Cons", etc.
272 pub ctor: String,
273
274 /// Variable bindings for constructor arguments: ["k"] for Succ(k)
275 pub bindings: Vec<String>,
276
277 /// The expression to evaluate when this arm matches
278 pub body: ProofExpr,
279}
280
281impl fmt::Display for ProofExpr {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self {
284 ProofExpr::Predicate { name, args, world } => {
285 write!(f, "{}", name)?;
286 if !args.is_empty() {
287 write!(f, "(")?;
288 for (i, arg) in args.iter().enumerate() {
289 if i > 0 {
290 write!(f, ", ")?;
291 }
292 write!(f, "{}", arg)?;
293 }
294 write!(f, ")")?;
295 }
296 if let Some(w) = world {
297 write!(f, " @{}", w)?;
298 }
299 Ok(())
300 }
301 ProofExpr::Identity(left, right) => write!(f, "{} = {}", left, right),
302 ProofExpr::Atom(s) => write!(f, "{}", s),
303 ProofExpr::And(left, right) => write!(f, "({} ∧ {})", left, right),
304 ProofExpr::Or(left, right) => write!(f, "({} ∨ {})", left, right),
305 ProofExpr::Implies(left, right) => write!(f, "({} → {})", left, right),
306 ProofExpr::Iff(left, right) => write!(f, "({} ↔ {})", left, right),
307 ProofExpr::Not(inner) => write!(f, "¬{}", inner),
308 ProofExpr::ForAll { variable, body } => write!(f, "∀{} {}", variable, body),
309 ProofExpr::Exists { variable, body } => write!(f, "∃{} {}", variable, body),
310 ProofExpr::Modal { domain, force, flavor, body } => {
311 write!(f, "□[{}/{}/{}]{}", domain, force, flavor, body)
312 }
313 ProofExpr::Temporal { operator, body } => write!(f, "{}({})", operator, body),
314 ProofExpr::TemporalBinary { operator, left, right } => {
315 write!(f, "TemporalBinary({}, {}, {})", operator, left, right)
316 }
317 ProofExpr::Lambda { variable, body } => write!(f, "λ{}.{}", variable, body),
318 ProofExpr::App(func, arg) => write!(f, "({} {})", func, arg),
319 ProofExpr::NeoEvent { event_var, verb, roles } => {
320 write!(f, "∃{}({}({})", event_var, verb, event_var)?;
321 for (role, term) in roles {
322 write!(f, " ∧ {}({}, {})", role, event_var, term)?;
323 }
324 write!(f, ")")
325 }
326 ProofExpr::Ctor { name, args } => {
327 write!(f, "{}", name)?;
328 if !args.is_empty() {
329 write!(f, "(")?;
330 for (i, arg) in args.iter().enumerate() {
331 if i > 0 {
332 write!(f, ", ")?;
333 }
334 write!(f, "{}", arg)?;
335 }
336 write!(f, ")")?;
337 }
338 Ok(())
339 }
340 ProofExpr::Match { scrutinee, arms } => {
341 write!(f, "match {} {{ ", scrutinee)?;
342 for (i, arm) in arms.iter().enumerate() {
343 if i > 0 {
344 write!(f, ", ")?;
345 }
346 write!(f, "{}", arm.ctor)?;
347 if !arm.bindings.is_empty() {
348 write!(f, "({})", arm.bindings.join(", "))?;
349 }
350 write!(f, " => {}", arm.body)?;
351 }
352 write!(f, " }}")
353 }
354 ProofExpr::Fixpoint { name, body } => write!(f, "fix {}.{}", name, body),
355 ProofExpr::TypedVar { name, typename } => write!(f, "{}:{}", name, typename),
356 ProofExpr::Unsupported(desc) => write!(f, "⟨unsupported: {}⟩", desc),
357 ProofExpr::Hole(name) => write!(f, "?{}", name),
358 ProofExpr::Term(term) => write!(f, "{}", term),
359 }
360 }
361}
362
363// =============================================================================
364// INFERENCE RULE - The logical moves available to the prover
365// =============================================================================
366
367/// The "Lever" - The specific logical move made at each proof step.
368///
369/// This enum captures HOW we moved from premises to conclusion. Each variant
370/// corresponds to a different proof strategy or logical rule.
371///
372/// # See Also
373///
374/// * [`DerivationTree`] - Each node contains an `InferenceRule`
375/// * [`certifier::certify`] - Maps inference rules to kernel terms
376/// * [`hints::suggest_hint`] - Suggests applicable rules when stuck
377/// * [`BackwardChainer`] - The engine that selects and applies rules
378#[derive(Debug, Clone, PartialEq)]
379pub enum InferenceRule {
380 // --- Basic FOL ---
381
382 /// Direct match with a known fact in the Context/KnowledgeBase.
383 /// Logic: Γ, P ⊢ P
384 PremiseMatch,
385
386 /// Logic: P → Q, P ⊢ Q
387 ModusPonens,
388
389 /// Logic: ¬Q, P → Q ⊢ ¬P
390 ModusTollens,
391
392 /// Logic: P, Q ⊢ P ∧ Q
393 ConjunctionIntro,
394
395 /// Logic: P ∧ Q ⊢ P (or Q)
396 ConjunctionElim,
397
398 /// Logic: P ⊢ P ∨ Q
399 DisjunctionIntro,
400
401 /// Logic: P ∨ Q, P → R, Q → R ⊢ R
402 DisjunctionElim,
403
404 /// Logic: ¬¬P ⊢ P (and P ⊢ ¬¬P)
405 DoubleNegation,
406
407 // --- Quantifiers ---
408
409 /// Logic: ∀x P(x) ⊢ P(c)
410 /// Stores the specific term 'c' used to instantiate the universal.
411 UniversalInst(String),
412
413 /// Logic: Γ, x:T ⊢ P(x) implies Γ ⊢ ∀x:T. P(x)
414 /// Stores variable name and type name for Lambda construction.
415 UniversalIntro { variable: String, var_type: String },
416
417 /// Logic: P(w) ⊢ ∃x.P(x)
418 /// Carries the witness and its type for kernel certification.
419 ExistentialIntro {
420 witness: String,
421 witness_type: String,
422 },
423
424 // --- Modal Logic (World Moves) ---
425
426 /// Logic: □P (in w0), Accessible(w0, w1) ⊢ P (in w1)
427 /// "Necessity Elimination" / "Distribution"
428 ModalAccess,
429
430 /// Logic: If P is true in ALL accessible worlds ⊢ □P
431 /// "Necessity Introduction"
432 ModalGeneralization,
433
434 // --- Temporal Logic ---
435
436 /// Logic: t1 < t2, t2 < t3 ⊢ t1 < t3
437 TemporalTransitivity,
438
439 /// Logic: P(s₀), ∀s(P(s) → P(next(s))) ⊢ G(P)
440 /// Standard k-induction for hardware safety properties.
441 TemporalInduction,
442
443 /// Logic: G(P) ⊢ P ∧ X(G(P))
444 /// Fixpoint unfolding of Always.
445 TemporalUnfolding,
446
447 /// Logic: P(w) ⊢ F(P) for witness world w
448 /// Prove Eventually by exhibiting a reachable witness.
449 EventualityProgress,
450
451 /// Logic: Induction on trace length for P U Q
452 UntilInduction,
453
454 // --- Peano / Inductive Reasoning (CIC seed) ---
455
456 /// Logic: P(0), ∀k(P(k) → P(S(k))) ⊢ ∀n P(n)
457 /// Stores the variable name, its inductive type, and the step variable used.
458 StructuralInduction {
459 variable: String, // "n" - the induction variable
460 ind_type: String, // "Nat" - the inductive type
461 step_var: String, // "k" - the predecessor variable (for IH matching)
462 },
463
464 // --- Equality ---
465
466 /// Leibniz's Law / Substitution of Equals
467 /// Logic: a = b, P(a) ⊢ P(b)
468 /// The equality proof is in `premise\[0\]`, the P(a) proof is in `premise\[1\]`.
469 /// Carries the original term and replacement term for certification.
470 Rewrite {
471 from: ProofTerm,
472 to: ProofTerm,
473 },
474
475 /// Symmetry of Equality: a = b ⊢ b = a
476 EqualitySymmetry,
477
478 /// Transitivity of Equality: a = b, b = c ⊢ a = c
479 EqualityTransitivity,
480
481 /// Reflexivity of Equality: a = a (after normalization)
482 /// Used when both sides of an identity reduce to the same normal form.
483 Reflexivity,
484
485 // --- Fallbacks ---
486
487 /// "The User Said So." Used for top-level axioms.
488 Axiom,
489
490 /// "The Machine Said So." (Z3 Oracle)
491 /// The string contains the solver's justification.
492 OracleVerification(String),
493
494 /// Proof by Contradiction (Reductio ad Absurdum)
495 /// Logic: Assume ¬C, derive P ∧ ¬P (contradiction), conclude C
496 /// Or: Assume P, derive Q ∧ ¬Q, conclude ¬P
497 ReductioAdAbsurdum,
498
499 /// Contradiction detected in premises: P and ¬P both hold
500 /// Logic: P, ¬P ⊢ ⊥ (ex falso quodlibet)
501 Contradiction,
502
503 // --- Quantifier Elimination ---
504
505 /// Existential Elimination (Skolemization in a proof context)
506 /// Logic: ∃x.P(x), [c fresh] P(c) ⊢ Goal implies ∃x.P(x) ⊢ Goal
507 /// The witness c must be fresh (not appearing in Goal).
508 ExistentialElim { witness: String },
509
510 /// Case Analysis (Tertium Non Datur / Law of Excluded Middle)
511 /// Logic: (P → ⊥), (¬P → ⊥) ⊢ ⊥
512 /// Used for self-referential paradoxes like the Barber Paradox.
513 CaseAnalysis { case_formula: String },
514}
515
516// =============================================================================
517// DERIVATION TREE - The recursive proof structure
518// =============================================================================
519
520/// The "Euclidean Structure" - A recursive tree representing the proof.
521///
522/// This is the object returned by the Prover. It allows the UI to render
523/// a step-by-step explanation (Natural Language Generation).
524///
525/// # Structure
526///
527/// Each node contains:
528/// - `conclusion`: The proposition proved at this step
529/// - `rule`: The logical rule applied (how we know the conclusion)
530/// - `premises`: Sub-proofs justifying the rule's requirements
531/// - `substitution`: Variable bindings from unification
532///
533/// # See Also
534///
535/// * [`BackwardChainer::prove`] - Creates derivation trees
536/// * [`InferenceRule`] - The rules that justify each step
537/// * [`ProofExpr`] - The conclusion type
538/// * [`certifier::certify`] - Converts trees to kernel terms
539/// * [`hints::suggest_hint`] - Suggests next steps when stuck
540#[derive(Debug, Clone)]
541pub struct DerivationTree {
542 /// The logical statement proved at this node.
543 pub conclusion: ProofExpr,
544
545 /// The rule applied to justify this conclusion.
546 pub rule: InferenceRule,
547
548 /// The sub-proofs that validate the requirements of the rule.
549 /// Example: ModusPonens will have 2 children (The Implication, The Antecedent).
550 pub premises: Vec<DerivationTree>,
551
552 /// The depth of the tree (used for complexity limits).
553 pub depth: usize,
554
555 /// Substitution applied at this step (for unification-based rules).
556 pub substitution: unify::Substitution,
557}
558
559impl DerivationTree {
560 /// Constructs a new Proof Node.
561 /// Automatically calculates depth based on children.
562 pub fn new(
563 conclusion: ProofExpr,
564 rule: InferenceRule,
565 premises: Vec<DerivationTree>,
566 ) -> Self {
567 let max_depth = premises.iter().map(|p| p.depth).max().unwrap_or(0);
568 Self {
569 conclusion,
570 rule,
571 premises,
572 depth: max_depth + 1,
573 substitution: unify::Substitution::new(),
574 }
575 }
576
577 /// A leaf node (usually a Premise, Axiom, or Oracle result).
578 pub fn leaf(conclusion: ProofExpr, rule: InferenceRule) -> Self {
579 Self::new(conclusion, rule, vec![])
580 }
581
582 /// Set the substitution for this derivation step.
583 pub fn with_substitution(mut self, subst: unify::Substitution) -> Self {
584 self.substitution = subst;
585 self
586 }
587
588 /// Renders the proof as a text-based tree (for debugging/CLI).
589 pub fn display_tree(&self) -> String {
590 self.display_recursive(0)
591 }
592
593 fn display_recursive(&self, indent: usize) -> String {
594 let prefix = " ".repeat(indent);
595
596 let rule_name = match &self.rule {
597 InferenceRule::UniversalInst(var) => format!("UniversalInst({})", var),
598 InferenceRule::UniversalIntro { variable, var_type } => {
599 format!("UniversalIntro({}:{})", variable, var_type)
600 }
601 InferenceRule::ExistentialIntro { witness, witness_type } => {
602 format!("∃Intro({}:{})", witness, witness_type)
603 }
604 InferenceRule::StructuralInduction { variable, ind_type, step_var } => {
605 format!("Induction({}:{}, step={})", variable, ind_type, step_var)
606 }
607 InferenceRule::OracleVerification(s) => format!("Oracle({})", s),
608 InferenceRule::Rewrite { from, to } => format!("Rewrite({} → {})", from, to),
609 InferenceRule::EqualitySymmetry => "EqSymmetry".to_string(),
610 InferenceRule::EqualityTransitivity => "EqTransitivity".to_string(),
611 r => format!("{:?}", r),
612 };
613
614 let mut output = format!("{}└─ [{}] {}\n", prefix, rule_name, self.conclusion);
615
616 for premise in &self.premises {
617 output.push_str(&premise.display_recursive(indent + 1));
618 }
619 output
620 }
621}
622
623impl fmt::Display for DerivationTree {
624 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625 write!(f, "{}", self.display_tree())
626 }
627}
628
629// =============================================================================
630// PROOF GOAL - The target state for backward chaining
631// =============================================================================
632
633/// The Goal State for the Backward Chainer.
634///
635/// Represents a "hole" in the proof that needs to be filled. During backward
636/// chaining, goals are decomposed into subgoals until they match known facts.
637///
638/// # Fields
639///
640/// * `target` - What we are trying to prove
641/// * `context` - Local assumptions (e.g., antecedents of implications we've entered)
642///
643/// # See Also
644///
645/// * [`BackwardChainer::prove`] - Takes a goal and produces a derivation tree
646/// * [`DerivationTree`] - The result of successfully proving a goal
647/// * [`ProofExpr`] - The target type
648#[derive(Debug, Clone)]
649pub struct ProofGoal {
650 /// What we are trying to prove right now.
651 pub target: ProofExpr,
652
653 /// The local assumptions available (e.g., inside an implication).
654 pub context: Vec<ProofExpr>,
655}
656
657impl ProofGoal {
658 pub fn new(target: ProofExpr) -> Self {
659 Self {
660 target,
661 context: Vec::new(),
662 }
663 }
664
665 pub fn with_context(target: ProofExpr, context: Vec<ProofExpr>) -> Self {
666 Self { target, context }
667 }
668}