1use serde::{Deserialize, Serialize};
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct FolConfig {
33 pub generate_logical_forms: bool,
35 pub detect_fallacies: bool,
37 pub check_validity: bool,
39 pub max_quantifier_depth: usize,
41 pub check_soundness: bool,
43 pub translation_confidence_threshold: f32,
45}
46
47impl Default for FolConfig {
48 fn default() -> Self {
49 Self {
50 generate_logical_forms: true,
51 detect_fallacies: true,
52 check_validity: true,
53 max_quantifier_depth: 3,
54 check_soundness: true,
55 translation_confidence_threshold: 0.7,
56 }
57 }
58}
59
60impl FolConfig {
61 pub fn laser_logic() -> Self {
63 Self {
64 generate_logical_forms: true,
65 detect_fallacies: true,
66 check_validity: true,
67 max_quantifier_depth: 4,
68 check_soundness: true,
69 translation_confidence_threshold: 0.75,
70 }
71 }
72
73 pub fn quick_check() -> Self {
75 Self {
76 generate_logical_forms: false,
77 detect_fallacies: true,
78 check_validity: true,
79 max_quantifier_depth: 2,
80 check_soundness: false,
81 translation_confidence_threshold: 0.6,
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88pub enum Connective {
89 And,
91 Or,
93 Not,
95 Implies,
97 Iff,
99}
100
101impl Connective {
102 pub fn symbol(&self) -> &'static str {
103 match self {
104 Self::And => "∧",
105 Self::Or => "∨",
106 Self::Not => "¬",
107 Self::Implies => "→",
108 Self::Iff => "↔",
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
115pub enum Quantifier {
116 ForAll,
118 Exists,
120}
121
122impl Quantifier {
123 pub fn symbol(&self) -> &'static str {
124 match self {
125 Self::ForAll => "∀",
126 Self::Exists => "∃",
127 }
128 }
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133pub enum Term {
134 Variable(String),
136 Constant(String),
138 Function { name: String, args: Vec<Term> },
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub enum Formula {
145 Predicate { name: String, args: Vec<Term> },
147 Not(Box<Formula>),
149 And(Box<Formula>, Box<Formula>),
151 Or(Box<Formula>, Box<Formula>),
153 Implies(Box<Formula>, Box<Formula>),
155 Iff(Box<Formula>, Box<Formula>),
157 ForAll {
159 variable: String,
160 formula: Box<Formula>,
161 },
162 Exists {
164 variable: String,
165 formula: Box<Formula>,
166 },
167}
168
169impl Formula {
170 pub fn is_atomic(&self) -> bool {
172 matches!(self, Formula::Predicate { .. })
173 }
174
175 pub fn free_variables(&self) -> Vec<String> {
177 match self {
178 Formula::Predicate { args, .. } => args
179 .iter()
180 .filter_map(|t| match t {
181 Term::Variable(v) => Some(v.clone()),
182 _ => None,
183 })
184 .collect(),
185 Formula::Not(f) => f.free_variables(),
186 Formula::And(l, r)
187 | Formula::Or(l, r)
188 | Formula::Implies(l, r)
189 | Formula::Iff(l, r) => {
190 let mut vars = l.free_variables();
191 vars.extend(r.free_variables());
192 vars.sort();
193 vars.dedup();
194 vars
195 }
196 Formula::ForAll { variable, formula } | Formula::Exists { variable, formula } => {
197 formula
198 .free_variables()
199 .into_iter()
200 .filter(|v| v != variable)
201 .collect()
202 }
203 }
204 }
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct FolArgument {
210 pub premises_nl: Vec<String>,
212 pub conclusion_nl: String,
214 pub premises_fol: Vec<Formula>,
216 pub conclusion_fol: Option<Formula>,
218 pub translation_confidence: f32,
220 pub translation_notes: Vec<String>,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
226pub enum FolFallacy {
227 AffirmingConsequent,
229 DenyingAntecedent,
231 UndistributedMiddle,
233 IllicitDistribution,
235 FourTerms,
237 ExistentialFallacy,
239 Composition,
241 Division,
243 CircularReasoning,
245 NonSequitur,
247}
248
249impl FolFallacy {
250 pub fn description(&self) -> &'static str {
252 match self {
253 Self::AffirmingConsequent => {
254 "Inferring P from P→Q and Q (invalid: other causes possible)"
255 }
256 Self::DenyingAntecedent => {
257 "Inferring ¬Q from P→Q and ¬P (invalid: Q might still be true)"
258 }
259 Self::UndistributedMiddle => "Middle term not distributed in at least one premise",
260 Self::IllicitDistribution => "Term distributed in conclusion but not in premises",
261 Self::FourTerms => "Four distinct terms used where three expected",
262 Self::ExistentialFallacy => "Assuming existence from purely universal premises",
263 Self::Composition => "Assuming whole has properties of its parts",
264 Self::Division => "Assuming parts have properties of the whole",
265 Self::CircularReasoning => "Conclusion is equivalent to or contained in premises",
266 Self::NonSequitur => "Conclusion does not logically follow from premises",
267 }
268 }
269
270 pub fn pattern(&self) -> &'static str {
272 match self {
273 Self::AffirmingConsequent => "P→Q, Q ⊢ P (INVALID)",
274 Self::DenyingAntecedent => "P→Q, ¬P ⊢ ¬Q (INVALID)",
275 Self::UndistributedMiddle => "∀x(Mx→Px), ∀x(Mx→Sx) ⊢ ∀x(Sx→Px) (INVALID)",
276 Self::IllicitDistribution => "∀x(Px→Mx), ∃x(Mx∧Sx) ⊢ ∀x(Sx→Px) (INVALID)",
277 Self::FourTerms => "Four distinct predicates instead of three",
278 Self::ExistentialFallacy => "∀x(Px→Qx) ⊢ ∃x(Px∧Qx) (INVALID if ¬∃xPx)",
279 Self::Composition => "∀x∈S(Px) ⊢ P(S) (INVALID)",
280 Self::Division => "P(S) ⊢ ∀x∈S(Px) (INVALID)",
281 Self::CircularReasoning => "P ⊢ P (TRIVIALLY VALID but uninformative)",
282 Self::NonSequitur => "No valid derivation path",
283 }
284 }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct DetectedFallacy {
290 pub fallacy: FolFallacy,
291 pub confidence: f32,
293 pub evidence: String,
295 pub involved_premises: Vec<usize>,
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
301pub enum ValidityStatus {
302 Valid,
304 Invalid,
306 Undetermined,
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
312pub enum SoundnessStatus {
313 Sound,
315 Unsound,
317 Undetermined,
319}
320
321#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct FolResult {
324 pub argument: FolArgument,
326 pub validity: ValidityStatus,
328 pub validity_explanation: String,
330 pub soundness: SoundnessStatus,
332 pub premise_assessments: Vec<PremiseAssessment>,
334 pub fallacies: Vec<DetectedFallacy>,
336 pub confidence: f32,
338 pub suggestions: Vec<String>,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct PremiseAssessment {
345 pub premise_index: usize,
346 pub premise_nl: String,
347 pub likely_true: Option<bool>,
349 pub confidence: f32,
351 pub reasoning: String,
353}
354
355impl FolResult {
356 pub fn verdict(&self) -> &'static str {
358 match (self.validity, self.soundness) {
359 (ValidityStatus::Valid, SoundnessStatus::Sound) => {
360 "SOUND - Argument is valid with true premises"
361 }
362 (ValidityStatus::Valid, SoundnessStatus::Unsound) => {
363 "VALID BUT UNSOUND - Valid logic but questionable premises"
364 }
365 (ValidityStatus::Valid, SoundnessStatus::Undetermined) => {
366 "VALID - Logic is correct, premises unverified"
367 }
368 (ValidityStatus::Invalid, _) => "INVALID - Conclusion does not follow from premises",
369 (ValidityStatus::Undetermined, _) => "UNDETERMINED - Could not fully analyze",
370 }
371 }
372
373 pub fn has_fallacies(&self) -> bool {
375 !self.fallacies.is_empty()
376 }
377
378 pub fn most_confident_fallacy(&self) -> Option<&DetectedFallacy> {
380 self.fallacies.iter().max_by(|a, b| {
381 a.confidence
382 .partial_cmp(&b.confidence)
383 .unwrap_or(std::cmp::Ordering::Equal)
384 })
385 }
386}
387
388pub struct FolPrompts;
390
391impl FolPrompts {
392 pub fn translate(premises: &[String], conclusion: &str) -> String {
394 let premises_formatted: String = premises
395 .iter()
396 .enumerate()
397 .map(|(i, p)| format!("P{}: {}", i + 1, p))
398 .collect::<Vec<_>>()
399 .join("\n");
400
401 format!(
402 r#"Translate this argument into First-Order Logic (FOL).
403
404PREMISES:
405{premises_formatted}
406
407CONCLUSION:
408C: {conclusion}
409
410For each statement:
4111. Identify predicates (properties and relations)
4122. Identify quantifiers (all, some, none)
4133. Identify logical connectives (and, or, not, if-then)
4144. Express in FOL notation
415
416Use standard symbols:
417- ∀x (for all x)
418- ∃x (there exists x)
419- ∧ (and)
420- ∨ (or)
421- ¬ (not)
422- → (implies)
423- ↔ (if and only if)
424
425Format:
426P1_FOL: [FOL translation]
427P2_FOL: [FOL translation]
428...
429C_FOL: [FOL translation]
430
431TRANSLATION_CONFIDENCE: [0.0-1.0]
432TRANSLATION_NOTES: [any ambiguities or assumptions]"#,
433 premises_formatted = premises_formatted,
434 conclusion = conclusion
435 )
436 }
437
438 pub fn check_validity(premises_fol: &[String], conclusion_fol: &str) -> String {
440 let premises_formatted: String = premises_fol
441 .iter()
442 .enumerate()
443 .map(|(i, p)| format!("P{}: {}", i + 1, p))
444 .collect::<Vec<_>>()
445 .join("\n");
446
447 format!(
448 r#"Determine the logical validity of this argument.
449
450PREMISES (FOL):
451{premises_formatted}
452
453CONCLUSION (FOL):
454C: {conclusion_fol}
455
456An argument is VALID if: whenever all premises are true, the conclusion MUST be true.
457An argument is INVALID if: it's possible for all premises to be true while the conclusion is false.
458
459Analysis steps:
4601. Identify the logical structure
4612. Look for counterexamples (model where premises true, conclusion false)
4623. If no counterexample possible, the argument is valid
463
464VALIDITY: [VALID | INVALID | UNDETERMINED]
465EXPLANATION: [why valid/invalid]
466COUNTEREXAMPLE: [if invalid, describe a scenario where premises are true but conclusion is false]
467CONFIDENCE: [0.0-1.0]"#,
468 premises_formatted = premises_formatted,
469 conclusion_fol = conclusion_fol
470 )
471 }
472
473 pub fn detect_fallacies(argument_nl: &str, argument_fol: Option<&str>) -> String {
475 let fol_section = argument_fol.map_or(
476 "Not available - analyze from natural language only.".to_string(),
477 |f| format!("FOL:\n{}", f),
478 );
479
480 format!(
481 r#"Analyze this argument for logical fallacies.
482
483ARGUMENT (Natural Language):
484{argument_nl}
485
486{fol_section}
487
488Check for these formal fallacies:
4891. Affirming the Consequent: P→Q, Q ⊢ P
4902. Denying the Antecedent: P→Q, ¬P ⊢ ¬Q
4913. Undistributed Middle: syllogism with undistributed middle term
4924. Illicit Distribution: invalid distribution in syllogism
4935. Four-Term Fallacy: using equivocal term
4946. Existential Fallacy: invalid existential inference
4957. Composition/Division: part-whole errors
4968. Circular Reasoning: conclusion in premises
4979. Non Sequitur: conclusion doesn't follow
498
499For each fallacy found:
500FALLACY: [name]
501PATTERN: [the invalid logical pattern]
502EVIDENCE: [where this appears in the argument]
503CONFIDENCE: [0.0-1.0]
504
505If no fallacies found:
506NO_FALLACIES_DETECTED
507CONFIDENCE: [0.0-1.0]"#,
508 argument_nl = argument_nl,
509 fol_section = fol_section
510 )
511 }
512
513 pub fn assess_premises(premises: &[String]) -> String {
515 let premises_formatted: String = premises
516 .iter()
517 .enumerate()
518 .map(|(i, p)| format!("P{}: {}", i + 1, p))
519 .collect::<Vec<_>>()
520 .join("\n");
521
522 format!(
523 r#"Assess the truth of each premise.
524
525PREMISES:
526{premises_formatted}
527
528For each premise:
5291. Is it empirically verifiable?
5302. Is it a definitional truth?
5313. Is it a widely accepted claim?
5324. What evidence supports or refutes it?
533
534Format:
535P1_ASSESSMENT:
536- LIKELY_TRUE: [true | false | unknown]
537- CONFIDENCE: [0.0-1.0]
538- REASONING: [explanation]
539
540P2_ASSESSMENT:
541..."#,
542 premises_formatted = premises_formatted
543 )
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550
551 #[test]
552 fn test_config_default() {
553 let config = FolConfig::default();
554 assert!(config.generate_logical_forms);
555 assert!(config.detect_fallacies);
556 assert!(config.check_validity);
557 }
558
559 #[test]
560 fn test_connectives() {
561 assert_eq!(Connective::And.symbol(), "∧");
562 assert_eq!(Connective::Or.symbol(), "∨");
563 assert_eq!(Connective::Implies.symbol(), "→");
564 }
565
566 #[test]
567 fn test_quantifiers() {
568 assert_eq!(Quantifier::ForAll.symbol(), "∀");
569 assert_eq!(Quantifier::Exists.symbol(), "∃");
570 }
571
572 #[test]
573 fn test_formula_free_variables() {
574 let formula = Formula::Predicate {
576 name: "P".into(),
577 args: vec![Term::Variable("x".into()), Term::Variable("y".into())],
578 };
579 let vars = formula.free_variables();
580 assert!(vars.contains(&"x".to_string()));
581 assert!(vars.contains(&"y".to_string()));
582
583 let quantified = Formula::ForAll {
585 variable: "x".into(),
586 formula: Box::new(formula.clone()),
587 };
588 let vars = quantified.free_variables();
589 assert!(!vars.contains(&"x".to_string()));
590 assert!(vars.contains(&"y".to_string()));
591 }
592
593 #[test]
594 fn test_fallacy_descriptions() {
595 let fallacy = FolFallacy::AffirmingConsequent;
596 assert!(fallacy.description().contains("Inferring P"));
597 assert!(fallacy.pattern().contains("INVALID"));
598 }
599
600 #[test]
601 fn test_result_verdict() {
602 let result = FolResult {
603 argument: FolArgument {
604 premises_nl: vec!["All men are mortal".into()],
605 conclusion_nl: "Socrates is mortal".into(),
606 premises_fol: vec![],
607 conclusion_fol: None,
608 translation_confidence: 0.9,
609 translation_notes: vec![],
610 },
611 validity: ValidityStatus::Valid,
612 validity_explanation: "Valid syllogism".into(),
613 soundness: SoundnessStatus::Sound,
614 premise_assessments: vec![],
615 fallacies: vec![],
616 confidence: 0.95,
617 suggestions: vec![],
618 };
619
620 assert!(result.verdict().contains("SOUND"));
621 assert!(!result.has_fallacies());
622 }
623
624 #[test]
625 fn test_invalid_result() {
626 let result = FolResult {
627 argument: FolArgument {
628 premises_nl: vec![
629 "If it rains, the ground is wet".into(),
630 "The ground is wet".into(),
631 ],
632 conclusion_nl: "It rained".into(),
633 premises_fol: vec![],
634 conclusion_fol: None,
635 translation_confidence: 0.85,
636 translation_notes: vec![],
637 },
638 validity: ValidityStatus::Invalid,
639 validity_explanation: "Affirming the consequent".into(),
640 soundness: SoundnessStatus::Unsound,
641 premise_assessments: vec![],
642 fallacies: vec![DetectedFallacy {
643 fallacy: FolFallacy::AffirmingConsequent,
644 confidence: 0.95,
645 evidence: "Inferring cause from effect".into(),
646 involved_premises: vec![0, 1],
647 }],
648 confidence: 0.9,
649 suggestions: vec!["Consider other causes".into()],
650 };
651
652 assert!(result.verdict().contains("INVALID"));
653 assert!(result.has_fallacies());
654 assert_eq!(result.fallacies[0].fallacy, FolFallacy::AffirmingConsequent);
655 }
656}