import {
LogicalSystem,
SyllogisticArgument,
PropositionalFormula,
PropositionalArgument,
PredicateFormula,
PredicateArgument,
LogicalFallacy,
SyllogisticStatement,
SyllogisticType
} from './types.js';
import { PropositionalValidator } from './propositionalValidator.js';
import { PredicateValidator } from './predicateValidator.js';
/**
* Validation Engine for logical systems
* Provides common validation logic and fallacy detection
*/
export class ValidationEngine {
/**
* Validate a syllogistic argument
* @param argument The syllogism to validate
* @returns Validation result with fallacies if invalid
*/
validateSyllogism(argument: SyllogisticArgument): {
isValid: boolean;
fallacies: LogicalFallacy[];
explanation: string;
} {
const fallacies: LogicalFallacy[] = [];
// Check the figure of the syllogism
const figure = this.determineSyllogisticFigure(argument);
// Check the mood of the syllogism
const mood =
argument.majorPremise.type +
argument.minorPremise.type +
argument.conclusion.type;
// First, check basic mood-figure validity
const hasValidForm = this.isValidSyllogism(mood, figure);
// Then check distribution rules
const distributionCheck = this.checkTermDistribution(argument);
// Combine all fallacies
if (!hasValidForm) {
fallacies.push({
name: 'Invalid Syllogistic Form',
description: `The mood ${mood} in figure ${figure} is not a valid syllogistic form.`,
location: 'form'
});
}
fallacies.push(...distributionCheck.fallacies);
// Check for other specific fallacies
const otherFallacies = this.checkOtherSyllogisticFallacies(argument);
fallacies.push(...otherFallacies);
const isValid = fallacies.length === 0;
// Create an explanation
const explanation = isValid
? `This is a valid syllogism in figure ${figure} with mood ${mood}.`
: `This syllogism is invalid. ${fallacies.map(f => f.description).join(' ')}`;
return { isValid, fallacies, explanation };
}
/**
* Determine the figure of a syllogistic argument
* @param argument The syllogism
* @returns Figure number (1-4)
*/
private determineSyllogisticFigure(argument: SyllogisticArgument): number {
const majorPremise = argument.majorPremise;
const minorPremise = argument.minorPremise;
// Find the middle term using the same method as findMiddleTerm
const middleTerm = this.findMiddleTerm(argument);
if (!middleTerm) {
// Default to figure 1 if middle term cannot be determined
return 1;
}
// Normalize the middle term for comparison
const normalizedMiddle = this.normalizeTerm(middleTerm);
// Check positions in each premise
const middleInMajorSubject = this.normalizeTerm(majorPremise.subject) === normalizedMiddle;
const middleInMajorPredicate = this.normalizeTerm(majorPremise.predicate) === normalizedMiddle;
const middleInMinorSubject = this.normalizeTerm(minorPremise.subject) === normalizedMiddle;
const middleInMinorPredicate = this.normalizeTerm(minorPremise.predicate) === normalizedMiddle;
// Figure is determined by the position of the middle term in the premises
// Standard figure definitions:
// Figure 1: Major(M-P), Minor(S-M) - middle is subject of major, predicate of minor
// Figure 2: Major(P-M), Minor(S-M) - middle is predicate of both
// Figure 3: Major(M-P), Minor(M-S) - middle is subject of both
// Figure 4: Major(P-M), Minor(M-S) - middle is predicate of major, subject of minor
if (middleInMajorSubject && middleInMinorPredicate) {
return 1; // M-P, S-M
} else if (middleInMajorPredicate && middleInMinorPredicate) {
return 2; // P-M, S-M
} else if (middleInMajorSubject && middleInMinorSubject) {
return 3; // M-P, M-S
} else if (middleInMajorPredicate && middleInMinorSubject) {
return 4; // P-M, M-S
}
// Default to figure 1 if pattern doesn't match any standard figure
return 1;
}
/**
* Check if a syllogism is valid based on its mood and figure
* @param mood The mood (AAA, EAE, etc.)
* @param figure The figure (1-4)
* @returns True if the syllogism is valid
*/
private isValidSyllogism(mood: string, figure: number): boolean {
// Map of valid syllogisms by mood and figure
const validSyllogisms: { [mood: string]: number[] } = {
'AAA': [1],
'EAE': [1, 2],
'AII': [1, 3],
'EIO': [1, 2, 3, 4],
'AEE': [2, 4],
'AOO': [2],
'AAI': [1, 3, 4],
'IAI': [3, 4],
'EAO': [3, 4],
'OAO': [3]
};
return validSyllogisms[mood]?.includes(figure) || false;
}
/**
* Validate a propositional argument
* @param argument The propositional argument to validate
* @returns Validation result with fallacies if invalid
*/
validatePropositionalArgument(argument: PropositionalArgument): {
isValid: boolean;
fallacies: LogicalFallacy[];
explanation: string;
} {
const validator = new PropositionalValidator();
return validator.validateArgument(argument);
}
/**
* Validate a predicate logic argument
* @param argument The predicate logic argument to validate
* @returns Validation result with fallacies if invalid
*/
validatePredicateArgument(argument: PredicateArgument): {
isValid: boolean;
fallacies: LogicalFallacy[];
explanation: string;
} {
const validator = new PredicateValidator();
return validator.validateArgument(argument);
}
/**
* Check term distribution in a syllogism
* @param argument The syllogism to check
* @returns Distribution check result
*/
private checkTermDistribution(argument: SyllogisticArgument): {
fallacies: LogicalFallacy[];
} {
const fallacies: LogicalFallacy[] = [];
// Identify the terms
const middleTerm = this.findMiddleTerm(argument);
const majorTerm = argument.conclusion.predicate;
const minorTerm = argument.conclusion.subject;
// Check if middle term is distributed at least once
const middleInMajor = this.getTermPosition(middleTerm, argument.majorPremise);
const middleInMinor = this.getTermPosition(middleTerm, argument.minorPremise);
const middleDistributedInMajor = this.isTermDistributed(
argument.majorPremise.type,
middleInMajor
);
const middleDistributedInMinor = this.isTermDistributed(
argument.minorPremise.type,
middleInMinor
);
if (!middleDistributedInMajor && !middleDistributedInMinor) {
fallacies.push({
name: 'Fallacy of Undistributed Middle',
description: `The middle term "${middleTerm}" is not distributed in either premise.`,
location: 'middle term'
});
}
// Check for illicit major (major term distributed in conclusion but not in major premise)
const majorInConclusion = this.getTermPosition(majorTerm, argument.conclusion);
const majorInPremise = this.getTermPosition(majorTerm, argument.majorPremise);
if (majorInConclusion && majorInPremise) {
const majorDistributedInConclusion = this.isTermDistributed(
argument.conclusion.type,
majorInConclusion
);
const majorDistributedInPremise = this.isTermDistributed(
argument.majorPremise.type,
majorInPremise
);
if (majorDistributedInConclusion && !majorDistributedInPremise) {
fallacies.push({
name: 'Illicit Major',
description: `The major term "${majorTerm}" is distributed in the conclusion but not in the major premise.`,
location: 'major term'
});
}
}
// Check for illicit minor (minor term distributed in conclusion but not in minor premise)
const minorInConclusion = this.getTermPosition(minorTerm, argument.conclusion);
const minorInPremise = this.getTermPosition(minorTerm, argument.minorPremise);
if (minorInConclusion && minorInPremise) {
const minorDistributedInConclusion = this.isTermDistributed(
argument.conclusion.type,
minorInConclusion
);
const minorDistributedInPremise = this.isTermDistributed(
argument.minorPremise.type,
minorInPremise
);
if (minorDistributedInConclusion && !minorDistributedInPremise) {
fallacies.push({
name: 'Illicit Minor',
description: `The minor term "${minorTerm}" is distributed in the conclusion but not in the minor premise.`,
location: 'minor term'
});
}
}
return { fallacies };
}
/**
* Check for other syllogistic fallacies
* @param argument The syllogism to check
* @returns Array of detected fallacies
*/
private checkOtherSyllogisticFallacies(argument: SyllogisticArgument): LogicalFallacy[] {
const fallacies: LogicalFallacy[] = [];
// Check for exclusive premises (both premises negative)
if ((argument.majorPremise.type === 'E' || argument.majorPremise.type === 'O') &&
(argument.minorPremise.type === 'E' || argument.minorPremise.type === 'O')) {
fallacies.push({
name: 'Fallacy of Exclusive Premises',
description: 'Both premises are negative, which makes a valid conclusion impossible.',
location: 'premises'
});
}
// Check for existential fallacy (universal premises with particular conclusion)
const bothPremisesUniversal =
(argument.majorPremise.type === 'A' || argument.majorPremise.type === 'E') &&
(argument.minorPremise.type === 'A' || argument.minorPremise.type === 'E');
const conclusionParticular =
argument.conclusion.type === 'I' || argument.conclusion.type === 'O';
if (bothPremisesUniversal && conclusionParticular) {
fallacies.push({
name: 'Existential Fallacy',
description: 'Drawing a particular conclusion from universal premises assumes existence.',
location: 'conclusion'
});
}
// Check if negative conclusion follows from affirmative premises
const bothPremisesAffirmative =
(argument.majorPremise.type === 'A' || argument.majorPremise.type === 'I') &&
(argument.minorPremise.type === 'A' || argument.minorPremise.type === 'I');
const conclusionNegative =
argument.conclusion.type === 'E' || argument.conclusion.type === 'O';
if (bothPremisesAffirmative && conclusionNegative) {
fallacies.push({
name: 'Illicit Negative',
description: 'A negative conclusion cannot follow from two affirmative premises.',
location: 'conclusion'
});
}
return fallacies;
}
/**
* Find the middle term in a syllogism
* @param argument The syllogism
* @returns The middle term
*/
private findMiddleTerm(argument: SyllogisticArgument): string {
// The middle term appears in both premises but not in the conclusion
const majorTerms = [argument.majorPremise.subject, argument.majorPremise.predicate];
const minorTerms = [argument.minorPremise.subject, argument.minorPremise.predicate];
const conclusionTerms = [argument.conclusion.subject, argument.conclusion.predicate];
for (const majorTerm of majorTerms) {
for (const minorTerm of minorTerms) {
if (this.normalizeTerm(majorTerm) === this.normalizeTerm(minorTerm)) {
const normalized = this.normalizeTerm(majorTerm);
// Check it's not in the conclusion
if (!conclusionTerms.some(t => this.normalizeTerm(t) === normalized)) {
return majorTerm;
}
}
}
}
// This shouldn't happen with a well-formed syllogism
return '';
}
/**
* Get the position of a term in a statement
* @param term The term to find
* @param statement The statement to search
* @returns 'subject' | 'predicate' | null
*/
private getTermPosition(term: string, statement: SyllogisticStatement): 'subject' | 'predicate' | null {
const normalizedTerm = this.normalizeTerm(term);
if (this.normalizeTerm(statement.subject) === normalizedTerm) {
return 'subject';
} else if (this.normalizeTerm(statement.predicate) === normalizedTerm) {
return 'predicate';
}
return null;
}
/**
* Check if a term is distributed in a statement
* @param statementType The type of statement (A, E, I, O)
* @param position Position of the term (subject or predicate)
* @returns True if the term is distributed
*/
private isTermDistributed(statementType: SyllogisticType, position: 'subject' | 'predicate' | null): boolean {
if (!position) return false;
// Distribution rules:
// A (All S are P): Subject distributed, Predicate not distributed
// E (No S are P): Both Subject and Predicate distributed
// I (Some S are P): Neither Subject nor Predicate distributed
// O (Some S are not P): Subject not distributed, Predicate distributed
switch (statementType) {
case 'A':
return position === 'subject';
case 'E':
return true; // Both distributed
case 'I':
return false; // Neither distributed
case 'O':
return position === 'predicate';
default:
return false;
}
}
/**
* Simple lemmatization to handle singular/plural forms
* @param term The term to lemmatize
* @returns Lemmatized term
*/
private lemmatize(term: string): string {
// Simple rules for common cases
if (term.endsWith('s') && !term.endsWith('ss')) {
// Check if removing 's' creates a valid word
const singular = term.slice(0, -1);
// Handle special cases
if (term.endsWith('ies')) {
return term.slice(0, -3) + 'y'; // berries -> berry
} else if (term.endsWith('es') && (term.endsWith('ches') || term.endsWith('shes') || term.endsWith('xes') || term.endsWith('zes'))) {
return term.slice(0, -2); // watches -> watch
}
return singular;
}
return term;
}
/**
* Check if a term is a proper name (starts with capital letter)
* @param term The term to check
* @returns True if the term is a proper name
*/
private isProperName(term: string): boolean {
return /^[A-Z]/.test(term.trim());
}
/**
* Normalize a term for comparison
* @param term The term to normalize
* @returns Normalized term
*/
private normalizeTerm(term: string): string {
const trimmed = term.trim().replace(/\s+/g, ' ');
// Preserve case for proper names
if (this.isProperName(trimmed)) {
return trimmed;
}
// For non-proper names, convert to lowercase and lemmatize
const lowercased = trimmed.toLowerCase();
return this.lemmatize(lowercased);
}
/**
* Generate explanation for a valid syllogism
* @param argument The valid syllogism
* @returns Explanation string
*/
private generateValidSyllogismExplanation(argument: SyllogisticArgument): string {
const figure = this.determineSyllogisticFigure(argument);
const mood =
argument.majorPremise.type +
argument.minorPremise.type +
argument.conclusion.type;
const middleTerm = this.findMiddleTerm(argument);
const majorTerm = argument.conclusion.predicate;
const minorTerm = argument.conclusion.subject;
return `Valid syllogism (${mood}-${figure}):\n` +
`Major premise: ${this.formatStatement(argument.majorPremise)}\n` +
`Minor premise: ${this.formatStatement(argument.minorPremise)}\n` +
`Conclusion: ${this.formatStatement(argument.conclusion)}\n\n` +
`The middle term "${middleTerm}" properly connects the major term "${majorTerm}" ` +
`with the minor term "${minorTerm}", following all rules of distribution.`;
}
/**
* Generate explanation for an invalid syllogism
* @param argument The invalid syllogism
* @param fallacies The detected fallacies
* @returns Explanation string
*/
private generateInvalidSyllogismExplanation(argument: SyllogisticArgument, fallacies: LogicalFallacy[]): string {
const figure = this.determineSyllogisticFigure(argument);
const mood =
argument.majorPremise.type +
argument.minorPremise.type +
argument.conclusion.type;
let explanation = `Invalid syllogism (attempted ${mood}-${figure}):\n` +
`Major premise: ${this.formatStatement(argument.majorPremise)}\n` +
`Minor premise: ${this.formatStatement(argument.minorPremise)}\n` +
`Conclusion: ${this.formatStatement(argument.conclusion)}\n\n` +
`Fallacies detected:\n`;
for (const fallacy of fallacies) {
explanation += `- ${fallacy.name}: ${fallacy.description}\n`;
}
return explanation;
}
/**
* Format a syllogistic statement for display
* @param statement The statement to format
* @returns Formatted string
*/
private formatStatement(statement: SyllogisticStatement): string {
switch (statement.type) {
case 'A':
return `All ${statement.subject} are ${statement.predicate}`;
case 'E':
return `No ${statement.subject} are ${statement.predicate}`;
case 'I':
return `Some ${statement.subject} are ${statement.predicate}`;
case 'O':
return `Some ${statement.subject} are not ${statement.predicate}`;
default:
return `${statement.subject} ${statement.predicate}`;
}
}
}