fregejs
Version:
A propositional logic library written in Typescript
754 lines (553 loc) • 22.5 kB
text/typescript
import { InferenceException } from 'exceptions';
import {
ProofItemInferred,
Proof,
Formula,
Disjunction,
Negation,
Implication,
} from 'types';
import { isDeepStrictEqual } from 'util';
import {
isImplication,
isBiconditional,
isPropositionalVariable,
isConjunction,
parseToFormulaString,
isDisjunction,
isNegation,
isHypothesis,
isEndOfHypothesis,
} from 'utils';
import { RuleSetter } from './RuleSetter';
/**
* Class responsible for, through a test and its items, applying the inference rules defined by the RuleSetter class.
*/
export class RuleApplier extends RuleSetter {
static biconditionalIntroduction(item: ProofItemInferred, proof: Proof) {
const requiredItens = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 2, requiredItens.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItens, proof);
const formulas = [
proof[requiredItens[0]].expression,
proof[requiredItens[1]].expression,
];
if (!isImplication(formulas[0]) || !isImplication(formulas[1]))
throw new InferenceException(
`Biconditional Introduction (Line ${line}): conditionals not found.`
);
const inferenceResult = RuleApplier.BiconditionalIntroduction(
formulas[0],
formulas[1]
);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static biconditionalElimination(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const biconditional = proof[requiredItem[0]].expression;
if (!isBiconditional(biconditional))
throw new InferenceException(
`Biconditional Elimination (Line ${line}): biconditional not found.`
);
const inferenceResult = RuleApplier.BiconditionalElimination(biconditional);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static conditionalization(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
if (!isImplication(item.expression))
throw new InferenceException(
`Conditionalization (Line ${line}): the formula is not an implication.`
);
const formula = proof[requiredItem[0]].expression;
if (typeof formula === 'string' && !isPropositionalVariable(formula))
throw new InferenceException(
`Conditionalization (Line ${line}): formula not found.`
);
const inferenceResult = RuleApplier.Conditionalization(
formula,
item.expression
);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static conjunctionIntroduction(item: ProofItemInferred, proof: Proof) {
const requiredItens = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 2, requiredItens.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItens, proof);
const firstFormula = proof[requiredItens[0]].expression;
if (
typeof firstFormula === 'string' &&
!isPropositionalVariable(firstFormula)
)
throw new InferenceException(
`Conjunction Introduction (Line ${line}): line ${requiredItens[0]} formula not found.`
);
const secondFormula = proof[requiredItens[1]].expression;
if (
typeof secondFormula === 'string' &&
!isPropositionalVariable(secondFormula)
)
throw new InferenceException(
`Conjunction Introduction (Line ${line}): line ${requiredItens[1]} formula not found.`
);
const inferenceResult = RuleApplier.ConjunctionIntroduction(
firstFormula,
secondFormula
);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static conjunctionElimination(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const conjunction = proof[requiredItem[0]].expression;
if (!isConjunction(conjunction))
throw new InferenceException(
`Conjunction Elimination (Line ${line}): conjunction not found.`
);
const inferenceResults = RuleApplier.ConjunctionElimination(conjunction);
if (
!isDeepStrictEqual(item.expression, inferenceResults[0]) &&
!isDeepStrictEqual(item.expression, inferenceResults[1])
) {
throw new InferenceException(
`Conjunction Elimination (Line ${line}): expected ${parseToFormulaString(
inferenceResults[0]
)} or ${parseToFormulaString(
inferenceResults[1]
)} but received ${parseToFormulaString(item.expression as Formula)}`
);
}
return inferenceResults;
}
static commutativity(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const formula = proof[requiredItem[0]].expression;
if (
!isDisjunction(formula) &&
!isConjunction(formula) &&
!isBiconditional(formula)
)
throw new InferenceException(
`Commutativity (Line ${line}): cannot find any conjunction, biconditional or disjunction.`
);
const inferenceResult = RuleApplier.Commutativity(formula);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static contraposition(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const formula = proof[requiredItem[0]].expression;
if (!isImplication(formula))
throw new InferenceException(
`Contraposition (Line ${line}): implication not found.`
);
const inferenceResult = RuleApplier.Contraposition(formula);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static deMorgan(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const formula = proof[requiredItem[0]].expression;
if (
!isNegation(formula) &&
!isConjunction(formula) &&
!isDisjunction(formula)
)
throw new InferenceException(
`De Morgan (Line ${line}): formula is not a disjunction, conjunction or negation.`
);
const inferenceResult = RuleApplier.DeMorgan(formula);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static disjunctionIntroduction(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
if (!isDisjunction(item.expression))
throw new InferenceException(
`Disjunction Introduction (Line ${line}): expression is not a disjunction`
);
const formula = proof[requiredItem[0]].expression;
if (typeof formula === 'string' && !isPropositionalVariable(formula))
throw new InferenceException(
`Disjunction Introduction (Line ${line}): formula not found.`
);
const inferenceResult = RuleApplier.DisjunctionIntroduction(
formula,
item.expression
);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static disjunctiveSyllogism(item: ProofItemInferred, proof: Proof) {
const requiredItens = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 2, requiredItens.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItens, proof);
const firstFormula = proof[requiredItens[0]].expression;
const secondFormula = proof[requiredItens[1]].expression;
let remainingFormula: ProofItemInferred['expression'];
let disjunction: Disjunction;
if (isDisjunction(firstFormula)) {
disjunction = firstFormula;
remainingFormula = secondFormula;
}
if (isDisjunction(secondFormula)) {
disjunction = secondFormula;
remainingFormula = firstFormula;
}
if (!disjunction)
throw new InferenceException(
`Disjunctive Syllogism (Line ${line}): disjunction not found`
);
let negation: Negation;
if (isNegation(remainingFormula)) negation = remainingFormula;
if (!negation)
throw new InferenceException(
`Disjunctive Syllogism (Line ${line}): negation not found`
);
const inferenceResult = RuleApplier.DisjunctiveSyllogism(
disjunction,
negation
);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static doubleNegation(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const formula = proof[requiredItem[0]].expression;
if (typeof formula === 'string' && !isPropositionalVariable(formula))
throw new InferenceException(
`Double Negation (Line ${line}): formula not found.`
);
const inferenceResult = RuleApplier.DoubleNegation(formula);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static doubleNegationIntroduction(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const formula = proof[requiredItem[0]].expression;
if (typeof formula === 'string' && !isPropositionalVariable(formula))
throw new InferenceException(
`Double Negation Introduction (Line ${line}): negation not found.`
);
const inferenceResult = RuleApplier.DoubleNegationIntroduction(formula);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static hypotheticalSyllogism(item: ProofItemInferred, proof: Proof) {
const requiredItens = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 2, requiredItens.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItens, proof);
const firstFormula = proof[requiredItens[0]].expression;
const secondFormula = proof[requiredItens[1]].expression;
if (!isImplication(firstFormula) || !isImplication(secondFormula))
throw new InferenceException(
`Hypothetical Syllogism (Line ${line}): both formulas should be conditionals.`
);
const inferenceResult = RuleApplier.HypotheticalSyllogism(
firstFormula,
secondFormula
);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static implicationElimination(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const formula = proof[requiredItem[0]].expression;
if (!isImplication(formula))
throw new InferenceException(
`Implication Elimination (Line ${line}): implication not found.`
);
const inferenceResult = RuleApplier.ImplicationElimination(formula);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static implicationNegation(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
let formula = proof[requiredItem[0]].expression;
if (!isNegation(formula))
throw new InferenceException(
`Implication Negation (Line ${line}): negation not found`
);
const inferenceResult = RuleApplier.ImplicationNegation(formula);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static modusPonens(item: ProofItemInferred, proof: Proof) {
const requiredItens = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 2, requiredItens.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItens, proof);
let firstFormula = proof[requiredItens[0]].expression;
let secondFormula = proof[requiredItens[1]].expression;
let remainingFormula: ProofItemInferred['expression'];
let implication: Implication;
if (isImplication(firstFormula)) {
implication = firstFormula;
remainingFormula = secondFormula;
}
if (isImplication(secondFormula)) {
implication = secondFormula;
remainingFormula = firstFormula;
}
if (!implication)
throw new InferenceException(
`Modus Ponens (Line ${line}): implication not found`
);
const antecedent = remainingFormula;
if (!isPropositionalVariable(antecedent) && typeof antecedent === 'string')
throw new InferenceException(
`Modus Ponens (Line ${line}): antecedent not found`
);
const inferenceResult = RuleApplier.ModusPonens(implication, antecedent);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static modusTollens(item: ProofItemInferred, proof: Proof) {
const requiredItens = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 2, requiredItens.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItens, proof);
const firstFormula = proof[requiredItens[0]].expression;
const secondFormula = proof[requiredItens[1]].expression;
let remainingFormula: ProofItemInferred['expression'];
let implication: Implication;
if (isImplication(firstFormula)) {
implication = firstFormula;
remainingFormula = secondFormula;
}
if (isImplication(secondFormula)) {
implication = secondFormula;
remainingFormula = firstFormula;
}
if (!implication)
throw new InferenceException(
`Modus Tollens (Line ${line}): implication not found`
);
const consequent = remainingFormula;
if (!isNegation(consequent))
throw new InferenceException(
`Modus Tollens (Line ${line}): negated consequent not found`
);
const inferenceResult = RuleApplier.ModusTollens(implication, consequent);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static conjunctionOverDisjunctionDistribution(
item: ProofItemInferred,
proof: Proof
) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const conjunction = proof[requiredItem[0]].expression;
if (!isConjunction(conjunction))
throw new InferenceException(
`Distribution (Line ${line}): conjunction not found.`
);
const inferenceResult =
RuleApplier.ConjunctionOverDisjunctionDistribution(conjunction);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static disjunctionOverConjunctionDistribution(
item: ProofItemInferred,
proof: Proof
) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const disjunction = proof[requiredItem[0]].expression;
if (!isDisjunction(disjunction))
throw new InferenceException(
`Distribution (Line ${line}): disjunction not found.`
);
const inferenceResult =
RuleApplier.DisjunctionOverConjunctionDistribution(disjunction);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static conjunctionAssociativity(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const conjunction = proof[requiredItem[0]].expression;
if (!isConjunction(conjunction))
throw new InferenceException(
`Associativity (Line ${line}): conjunction not found.`
);
const inferenceResult = RuleApplier.ConjunctionAssociativity(conjunction);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static disjunctionAssociativity(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const disjunction = proof[requiredItem[0]].expression;
if (!isDisjunction(disjunction))
throw new InferenceException(
`Associativity (Line ${line}): disjunction not found.`
);
const inferenceResult = RuleApplier.DisjunctionAssociativity(disjunction);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static biconditionalAssociativity(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const biconditional = proof[requiredItem[0]].expression;
if (!isBiconditional(biconditional))
throw new InferenceException(
`Associativity (Line ${line}): disjunction not found.`
);
const inferenceResult =
RuleApplier.BiconditionalAssociativity(biconditional);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static conditionalProof(item: ProofItemInferred, proof: Proof) {
const requiredItens = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 2, requiredItens.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItens, proof);
const item1 = proof[requiredItens[0]];
const item2 = proof[requiredItens[1]];
if (
!isPropositionalVariable(item2.expression) &&
typeof item2.expression === 'string'
)
throw new InferenceException(
`Conditional Proof (Line ${line}): cannot find a formula at line ${item2.id}`
);
if (
!isPropositionalVariable(item1.expression) &&
typeof item1.expression === 'string'
)
throw new InferenceException(
`Conditional Proof (Line ${line}): cannot find a formula at line ${item1.id}`
);
let hypothesis: Formula;
let endOfHypothesis: Formula;
if (isHypothesis(item1) && isEndOfHypothesis(item2)) {
hypothesis = item1.expression;
if (item2.hypothesisId != item1.id)
throw new InferenceException(
`Conditional Proof (Line ${line}): end of hypothesis references line ${item2.hypothesisId} hypothesis, but received line ${item1.id} hypothesis`
);
endOfHypothesis = item2.expression;
} else if (isHypothesis(item2) && isEndOfHypothesis(item1)) {
hypothesis = item2.expression;
if (item1.hypothesisId != item2.id)
throw new InferenceException(
`Conditional Proof (Line ${line}): end of hypothesis references line ${item1.hypothesisId} hypothesis, but received line ${item2.id} hypothesis`
);
endOfHypothesis = item1.expression;
} else {
throw new InferenceException(
`Conditional Proof: end of hypothesis or hypothesis not found.`
);
}
const inferenceResult = RuleApplier.ConditionalProof(
hypothesis,
endOfHypothesis
);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
static reductioAdAbsurdum(item: ProofItemInferred, proof: Proof) {
const requiredItem = item.from[0];
const line = item.id;
RuleApplier.throwsIfLengthDoesntMatch(item, 1, requiredItem.length);
RuleApplier.throwsIfIndexDoesntExist(requiredItem, proof);
const conditional = proof[requiredItem[0]].expression;
if (!isImplication(conditional))
throw new InferenceException(
`Reductio Ad Absurdum (Line ${line}): conditional not found.`
);
const inferenceResult = RuleApplier.ReductioAdAbsurdum(conditional);
RuleApplier.throwsIfIsNotEqual(inferenceResult, item);
return inferenceResult;
}
private static throwsIfIsNotEqual(
expectedFormula: Formula,
actualItem: ProofItemInferred
) {
const actualFormula = actualItem.expression as Formula;
const inferenceMethod = actualItem.from[1];
if (!isDeepStrictEqual(expectedFormula, actualFormula)) {
throw new InferenceException(`
${inferenceMethod} (Line ${
actualItem.id
}): expected ${parseToFormulaString(
expectedFormula
)} but received ${parseToFormulaString(actualFormula)}
`);
}
}
private static throwsIfLengthDoesntMatch(
item: ProofItemInferred,
expected: number,
received: number
) {
const rule = item.from[1];
const line = item.id;
if (expected !== received)
throw new InferenceException(
`${rule} (Line ${line}): expect ${expected} formulas to apply the rule but received ${received}.`
);
}
private static throwsIfIndexDoesntExist(
requiredItens: number[],
proof: Proof
) {
requiredItens.forEach((idx) => {
if (!proof[idx])
throw new InferenceException(`Cannot find a formula at index ${idx}`);
});
}
}