gapt.proofs.nd.nd.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gapt_3 Show documentation
Show all versions of gapt_3 Show documentation
General Architecture for Proof Theory
The newest version!
package gapt.proofs.nd
import gapt.expr._
import gapt.expr.formula.All
import gapt.expr.formula.And
import gapt.expr.formula.Bottom
import gapt.expr.formula.Eq
import gapt.expr.formula.Ex
import gapt.expr.formula.Formula
import gapt.expr.formula.Imp
import gapt.expr.formula.Neg
import gapt.expr.formula.Or
import gapt.expr.formula.Top
import gapt.expr.subst.Substitution
import gapt.expr.ty.FunctionType
import gapt.expr.util.freeVariables
import gapt.expr.util.replacementContext
import gapt.proofs.IndexOrFormula.{IsFormula, IsIndex}
import gapt.proofs._
import scala.collection.mutable
abstract class NDProof extends SequentProof[Formula, NDProof] {
protected def NDRuleCreationException(message: String): NDRuleCreationException =
new NDRuleCreationException(longName, message)
/**
* The end-sequent of the rule.
*/
final def endSequent = conclusion
/** The unique formula in the succedent of the end-sequent. */
final def target: Formula = conclusion.succedent.head
/**
* Checks whether indices are in the right place and premise is defined at all of them.
*
* @param premise The sequent to be checked.
* @param antecedentIndices Indices that should be in the antecedent.
*/
protected def validateIndices(premise: HOLSequent, antecedentIndices: Seq[SequentIndex]): Unit = {
val antSet = mutable.HashSet[SequentIndex]()
for (i <- antecedentIndices) i match {
case Ant(_) =>
if (!premise.isDefinedAt(i))
throw NDRuleCreationException(s"Sequent $premise is not defined at index $i.")
if (antSet contains i)
throw NDRuleCreationException(s"Duplicate index $i for sequent $premise.")
antSet += i
case Suc(_) => throw NDRuleCreationException(s"Index $i should be in the antecedent.")
}
}
}
/**
* An NDProof deriving a sequent from another sequent:
*
* (π)
* Γ :- A
* ----------
* Γ' :- A'
*
*/
abstract class UnaryNDProof extends NDProof {
/**
* The immediate subproof of the rule.
*
* @return
*/
def subProof: NDProof
/**
* The object connecting the lower and upper sequents.auxFormulas.
*
* @return
*/
def getSequentConnector: SequentConnector = occConnectors.head
/**
* The upper sequent of the rule.
*
* @return
*/
def premise = subProof.endSequent
override def immediateSubProofs = Seq(subProof)
}
object UnaryNDProof {
def unapply(p: UnaryNDProof) = Some(p.endSequent, p.subProof)
}
/**
* An NDProof deriving a sequent from two other sequents:
*
* (π1) (π2)
* Γ :- A Γ' :- A'
* ------------------
* Π :- B
*
*/
abstract class BinaryNDProof extends NDProof {
/**
* The immediate left subproof of the rule.
*
* @return
*/
def leftSubProof: NDProof
/**
* The immediate right subproof of the rule.
*
* @return
*/
def rightSubProof: NDProof
/**
* The object connecting the lower and left upper sequents.
*
* @return
*/
def getLeftSequentConnector: SequentConnector = occConnectors.head
/**
* The object connecting the lower and right upper sequents.
*
* @return
*/
def getRightSequentConnector: SequentConnector = occConnectors.tail.head
/**
* The left upper sequent of the rule.
*
* @return
*/
def leftPremise = leftSubProof.endSequent
/**
* The right upper sequent of the rule.
*
* @return
*/
def rightPremise = rightSubProof.endSequent
override def immediateSubProofs = Seq(leftSubProof, rightSubProof)
}
object BinaryNDProof {
def unapply(p: BinaryNDProof) = Some(p.endSequent, p.leftSubProof, p.rightSubProof)
}
/**
* An NDProof deriving a sequent from three other sequents:
*
* (π1) (π2) (π3)
* Γ1 :- A1 Γ2 :- A2 Γ3 :- A3
* --------------------------------
* Π :- B
*
*/
abstract class TernaryNDProof extends NDProof {
/**
* The immediate left subproof of the rule.
*
* @return
*/
def leftSubProof: NDProof
/**
* The immediate middle subproof of the rule.
*
* @return
*/
def middleSubProof: NDProof
/**
* The immediate right subproof of the rule.
*
* @return
*/
def rightSubProof: NDProof
/**
* The object connecting the lower and left upper sequents.
*
* @return
*/
def getLeftSequentConnector: SequentConnector = occConnectors(0)
/**
* The object connecting the lower and middle upper sequents.
*
* @return
*/
def getMiddleSequentConnector: SequentConnector = occConnectors(1)
/**
* The object connecting the lower and right upper sequents.
*
* @return
*/
def getRightSequentConnector: SequentConnector = occConnectors(2)
/**
* The left upper sequent of the rule.
*
* @return
*/
def leftPremise = leftSubProof.endSequent
/**
* The middle upper sequent of the rule.
*
* @return
*/
def middlePremise = middleSubProof.endSequent
/**
* The right upper sequent of the rule.
*
* @return
*/
def rightPremise = rightSubProof.endSequent
override def immediateSubProofs = Seq(leftSubProof, middleSubProof, rightSubProof)
}
object TernaryNDProof {
def unapply(p: TernaryNDProof) = Some(p.endSequent, p.leftSubProof, p.middleSubProof, p.rightSubProof)
}
trait CommonRule extends NDProof with ContextRule[Formula, NDProof]
/**
* Use this trait for rules that use eigenvariables.
*
*/
trait Eigenvariable {
def eigenVariable: Var
}
/**
* An NDProof consisting of a single sequent:
*
* --------ax
* Γ :- A
*
*/
abstract class InitialSequent extends NDProof {
override def mainIndices = endSequent.indices
override def auxIndices = Seq()
override def immediateSubProofs = Seq()
override def occConnectors = Seq()
}
object InitialSequent {
def unapply(proof: InitialSequent) = Some(proof.endSequent)
}
/**
* An NDProof ending with weakening:
*
* (π)
* Γ :- B
* ---------wkn
* A, Γ :- B
*
*
* @param subProof The subproof π.
* @param formula The formula A.
*/
case class WeakeningRule(subProof: NDProof, formula: Formula)
extends UnaryNDProof with CommonRule {
override def auxIndices = Seq(Seq())
override def name = "wkn"
def mainFormula = formula
override def mainFormulaSequent = mainFormula +: Sequent()
}
object WeakeningRule extends ConvenienceConstructor("WeakeningRule") {
/**
* Convenience constructor for ax, taking a context.
* Applies the axiom rule followed by 0 or more weakenings.
*
* (π)
* Γ :- B
* ---------------------wkn*
* A1, ..., An, Γ :- B
*
*
* @param subProof The subproof π.
* @param formulas The formulas A1, ..., An
* @return
*/
def apply(subProof: NDProof, formulas: Seq[Formula]): NDProof = {
formulas.foldLeft[NDProof](subProof) { (ant, c) =>
WeakeningRule(ant, c)
}
}
}
/**
* An NDProof ending with a contraction:
*
* (π)
* A, A, Γ :- B
* --------------ctr
* A, Γ :- B
*
*
* @param subProof The subproof π.
* @param aux1 The index of one occurrence of A.
* @param aux2 The index of the other occurrence of A.
*/
case class ContractionRule(subProof: NDProof, aux1: SequentIndex, aux2: SequentIndex)
extends UnaryNDProof with CommonRule {
validateIndices(premise, Seq(aux1, aux2))
if (premise(aux1) != premise(aux2))
throw NDRuleCreationException(s"Auxiliary formulas ${premise(aux1)} and ${premise(aux2)} are not equal.")
val mainFormula = premise(aux1)
override def auxIndices = Seq(Seq(aux1, aux2))
override def name = "ctr"
override def mainFormulaSequent = mainFormula +: Sequent()
}
object ContractionRule extends ConvenienceConstructor("ContractionRule") {
/**
* Convenience constructor for ctr that, given a formula to contract, will automatically pick the
* first two occurrences of that formula.
*
* @param subProof The subproof π.
* @param f The formula to contract.
*/
def apply(subProof: NDProof, f: Formula): ContractionRule = {
val premise = subProof.endSequent
val (indices, _) = findAndValidate(premise)(Seq(f, f), Suc(0))
val p = ContractionRule(subProof, Ant(indices(0)), Ant(indices(1)))
assert(p.mainFormula == f)
p
}
}
/**
* An NDProof consisting of a logical axiom:
*
* --------ax
* A :- A
*
*
* @param A The formula A.
*/
case class LogicalAxiom(A: Formula) extends InitialSequent {
override def name = "ax"
override def conclusion = NDSequent(Seq(A), A)
def mainFormula = A
}
object LogicalAxiom extends ConvenienceConstructor("LogicalAxiom") {
/**
* Convenience constructor for ax, taking a context.
* Applies the axiom rule followed by 0 or more weakenings.
*
* --------ax
* A :- A
* -----------wkn*
* Γ, A :- A
*
*
* @param A The atom a.
* @param context The context Γ.
* @return
*/
def apply(A: Formula, context: Seq[Formula]): NDProof = {
context.foldLeft[NDProof](LogicalAxiom(A)) { (ant, c) =>
WeakeningRule(ant, c)
}
}
}
/**
* An NDProof ending with elimination of the right conjunct:
*
* (π)
* Γ :- A ∧ B
* --------------∧:e1
* Γ :- A
*
*
* @param subProof The subproof π.
*/
case class AndElim1Rule(subProof: NDProof)
extends UnaryNDProof with CommonRule {
val conjunction = premise(Suc(0))
val mainFormula = conjunction match {
case And(leftConjunct, _) => leftConjunct
case _ =>
throw NDRuleCreationException(s"Proposed main formula $conjunction is not a conjunction.")
}
override def auxIndices = Seq(Seq(Suc(0)))
override def name = "∧:e1"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with elimination of the left conjunct:
*
* (π)
* Γ :- A ∧ B
* --------------∧:e2
* Γ :- B
*
*
* @param subProof The subproof π.
*/
case class AndElim2Rule(subProof: NDProof)
extends UnaryNDProof with CommonRule {
val conjunction = premise(Suc(0))
val mainFormula = conjunction match {
case And(_, rightConjunct) => rightConjunct
case _ =>
throw NDRuleCreationException(s"Proposed main formula $conjunction is not a conjunction.")
}
override def auxIndices = Seq(Seq(Suc(0)))
override def name = "∧:e2"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with a conjunction on the right:
*
* (π1) (π2)
* Γ :- A Π :- B
* --------------------∧:i
* Γ, Π :- A∧B
*
*
* @param leftSubProof The proof π,,1,,.
* @param rightSubProof The proof π,,2,,.
*/
case class AndIntroRule(leftSubProof: NDProof, rightSubProof: NDProof)
extends BinaryNDProof with CommonRule {
val leftConjunct = leftPremise(Suc(0))
val rightConjunct = rightPremise(Suc(0))
val mainFormula = And(leftConjunct, rightConjunct)
def auxIndices = Seq(Seq(Suc(0)), Seq(Suc(0)))
override def name = "∧:i"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with elimination of a disjunction:
*
* (π1) (π2) (π3)
* Γ :- A∨B Π, A :- C Δ, B :- C
* ------------------------------------∨:e
* Γ, Π, Δ :- C
*
*
* @param leftSubProof The proof π,,1,,.
* @param middleSubProof The proof π,,2,,.
* @param aux1 The index of A.
* @param rightSubProof The proof π,,3,,.
* @param aux2 The index of B.
*/
case class OrElimRule(
leftSubProof: NDProof,
middleSubProof: NDProof,
aux1: SequentIndex,
rightSubProof: NDProof,
aux2: SequentIndex
) extends TernaryNDProof with CommonRule {
validateIndices(middlePremise, Seq(aux1))
validateIndices(rightPremise, Seq(aux2))
val leftDisjunct = middlePremise(aux1)
val rightDisjunct = rightPremise(aux2)
val disjunction = leftPremise(Suc(0))
require(
disjunction == Or(leftDisjunct, rightDisjunct),
throw NDRuleCreationException(s"Formula $disjunction is not a disjunction of $leftDisjunct and $rightDisjunct.")
)
val middleC = middlePremise(Suc(0))
val rightC = rightPremise(Suc(0))
val mainFormula = if (middleC == rightC) middleC
else
throw NDRuleCreationException(s"Formulas $middleC an $rightC are not the same.")
def auxIndices = Seq(Seq(Suc(0)), Seq(aux1, Suc(0)), Seq(aux2, Suc(0)))
override def name = "∨:e"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object OrElimRule extends ConvenienceConstructor("OrElimRule") {
/**
* Convenience constructor for ∨:e.
* Given only the subproofs, it will attempt to create an inference with this.
*
* @param leftSubProof The left subproof.
* @param middleSubProof The middle subproof.
* @param rightSubProof The right subproof.
* @return
*/
def apply(leftSubProof: NDProof, middleSubProof: NDProof, rightSubProof: NDProof): OrElimRule = {
val disjunction = leftSubProof.endSequent(Suc(0))
val (leftDisjunct, rightDisjunct) = disjunction match {
case Or(f, g) => (f, g)
case _ => throw NDRuleCreationException(s"Formula $disjunction is not a disjunction.")
}
val (middlePremise, rightPremise) = (middleSubProof.endSequent, rightSubProof.endSequent)
val (middleIndices, _) = findAndValidate(middlePremise)(Seq(leftDisjunct), Suc(0))
val (rightIndices, _) = findAndValidate(rightPremise)(Seq(rightDisjunct), Suc(0))
new OrElimRule(leftSubProof, middleSubProof, Ant(middleIndices(0)), rightSubProof, Ant(rightIndices(0)))
}
}
/**
* An NDProof ending with introduction of a disjunction, with a new formula as the right disjunct:
*
* (π)
* Γ :- A
* ------------∨:i1
* Γ :- A ∨ B
*
*
* @param subProof The subproof π.
* @param rightDisjunct The formula B.
*/
case class OrIntro1Rule(subProof: NDProof, rightDisjunct: Formula)
extends UnaryNDProof with CommonRule {
val leftDisjunct = premise(Suc(0))
val mainFormula = Or(leftDisjunct, rightDisjunct)
override def auxIndices = Seq(Seq(Suc(0)))
override def name = "∨:i1"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with introduction of a disjunction, with a new formula as the left disjunct:
*
* (π)
* Γ :- A
* ------------∨:i2
* Γ :- B ∨ A
*
*
* @param subProof The subproof π.
* @param leftDisjunct The formula B.
*/
case class OrIntro2Rule(subProof: NDProof, leftDisjunct: Formula)
extends UnaryNDProof with CommonRule {
val rightDisjunct = premise(Suc(0))
val mainFormula = Or(leftDisjunct, rightDisjunct)
override def auxIndices = Seq(Seq(Suc(0)))
override def name = "∨:i2"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with elimination of an implication:
*
* (π1) (π2)
* Γ :- A→B Π :- A
* --------------------→:e
* Γ, Π :- B
*
*
* @param leftSubProof The proof π,,1,,.
* @param rightSubProof The proof π,,2,,.
*/
case class ImpElimRule(leftSubProof: NDProof, rightSubProof: NDProof)
extends BinaryNDProof with CommonRule {
val implication = leftPremise(Suc(0))
val antecedent = rightPremise(Suc(0))
val mainFormula = implication match {
case Imp(`antecedent`, consequent) => consequent
case Imp(_, _) =>
throw NDRuleCreationException(s"Proposed main formula $antecedent is not the antecedent of $implication.")
case _ =>
throw NDRuleCreationException(s"Proposed main formula $implication is not an implication.")
}
def auxIndices = Seq(Seq(Suc(0)), Seq(Suc(0)))
override def name = "→:e"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with introduction of an implication:
*
* (π)
* A, Γ :- B
* ------------→:i
* Γ :- A → B
*
*
* @param subProof The subproof π.
* @param aux The index of A.
*/
case class ImpIntroRule(subProof: NDProof, aux: SequentIndex)
extends UnaryNDProof with CommonRule {
validateIndices(premise, Seq(aux))
val impPremise = premise(aux)
val impConclusion = premise(Suc(0))
val mainFormula = Imp(impPremise, impConclusion)
override def auxIndices = Seq(Seq(aux, Suc(0)))
override def name = "→:i"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object ImpIntroRule extends ConvenienceConstructor("ImpIntroRule") {
/**
* Convenience constructor for →:i.
* The aux formula can be given as an index or a formula. If it is given as a formula, the constructor
* will attempt to find an appropriate index on its own.
*
* @param subProof The subproof.
* @param impPremise Index of the premise of the implication or the premise itself.
* @return
*/
def apply(subProof: NDProof, impPremise: IndexOrFormula): ImpIntroRule = {
val premise = subProof.endSequent
val (antIndices, sucIndices) = findAndValidate(premise)(Seq(impPremise), Suc(0))
new ImpIntroRule(subProof, Ant(antIndices(0)))
}
/**
* Convenience constructor for →:i
* If the subproof has precisely one element in the antecedent of its premise, this element will be the aux index.
*
* @param subProof The subproof.
* @return
*/
def apply(subProof: NDProof): ImpIntroRule = {
val premise = subProof.endSequent
if (premise.antecedent.size == 1) apply(subProof, Ant(0))
else if (premise.antecedent.size == 0)
throw NDRuleCreationException(s"Antecedent of $premise doesn't contain any elements.")
else throw NDRuleCreationException(s"Antecedent of $premise has more than one element, " +
s"the formula serving as antecedent of the implication should be specified.")
}
}
/**
* An NDProof ending with elimination of a negation:
*
* (π1) (π2)
* Γ :- ¬A Π :- A
* -------------------¬:e
* Γ, Π :- ⊥
*
*
* @param leftSubProof The proof π,,1,,.
* @param rightSubProof The proof π,,2,,.
*/
case class NegElimRule(leftSubProof: NDProof, rightSubProof: NDProof)
extends BinaryNDProof with CommonRule {
val negatedFormula = leftPremise(Suc(0))
val formula = rightPremise(Suc(0))
val mainFormula =
if ((negatedFormula == Neg(formula)))
Bottom()
else
throw NDRuleCreationException(
s"""Formula $negatedFormula is not the negation of $formula.
""".stripMargin
)
def auxIndices = Seq(Seq(Suc(0)), Seq(Suc(0)))
override def name = "¬:e"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with introduction of a negation:
*
* (π)
* A, Γ :- ⊥
* -----------¬:i
* Γ :- ¬A
*
*
* @param subProof The subproof π.
* @param aux The index of A.
*/
case class NegIntroRule(subProof: NDProof, aux: SequentIndex)
extends UnaryNDProof with CommonRule {
validateIndices(premise, Seq(aux))
val bottom = premise(Suc(0))
require(bottom == Bottom(), s"Formula $bottom is not ⊥.")
val formula = premise(aux)
val mainFormula = Neg(formula)
override def auxIndices = Seq(Seq(aux, Suc(0)))
override def name = "¬:i"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object NegIntroRule extends ConvenienceConstructor("NegIntroRule") {
/**
* Convenience constructor for ¬:i.
* The aux formula can be given as an index or a formula. If it is given as a formula, the constructor
* will attempt to find an appropriate index on its own.
*
* @param subProof The subproof.
* @param negation Index of the negation or the negation itself.
* @return
*/
def apply(subProof: NDProof, negation: IndexOrFormula): NegIntroRule = {
val premise = subProof.endSequent
val (antIndices, sucIndices) = findAndValidate(premise)(Seq(negation), Suc(0))
new NegIntroRule(subProof, Ant(antIndices(0)))
}
/**
* Convenience constructor for ¬:i.
* If the subproof has precisely one element in the antecedent of its premise, this element will be the aux index.
*
* @param subProof The subproof.
* @return
*/
def apply(subProof: NDProof): NegIntroRule = {
val premise = subProof.endSequent
if (premise.antecedent.size == 1) apply(subProof, Ant(0))
else if (premise.antecedent.size == 0)
throw NDRuleCreationException(s"Antecedent of $premise doesn't contain any elements.")
else throw NDRuleCreationException(
s"Antecedent of $premise has more than one element, the formula to be negated should be specified."
)
}
}
/**
* An NDProof that is the introduction of ⊤:
*
* ------⊤:i
* :- ⊤
*
*/
case object TopIntroRule extends InitialSequent {
def mainFormula = Top()
def conclusion = NDSequent(Seq(), mainFormula)
override def name = "⊤:i"
}
/**
* An NDProof eliminating ⊥:
*
* (π)
* Γ :- ⊥
* --------⊥:e
* Γ :- A
*
*
* @param subProof The subproof π.
* @param mainFormula The formula A.
*/
case class BottomElimRule(subProof: NDProof, mainFormula: Formula)
extends UnaryNDProof with CommonRule {
val bottom = premise(Suc(0))
require(bottom == Bottom(), s"Formula $bottom is not ⊥.")
override def auxIndices = Seq(Seq(Suc(0)))
override def name = "⊥:e"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with a universal quantifier introduction:
*
* (π)
* Γ :- A[x\α]
* -------------∀:i
* Γ :- ∀x.A
*
* This rule is only applicable if the eigenvariable condition is satisfied: α must not occur freely in Γ.
*
* @param subProof The proof π.
* @param eigenVariable The variable α.
* @param quantifiedVariable The variable x.
*/
case class ForallIntroRule(subProof: NDProof, eigenVariable: Var, quantifiedVariable: Var)
extends UnaryNDProof with CommonRule with Eigenvariable {
val (auxFormula, context) = premise focus Suc(0)
// eigenvariable condition
if (freeVariables(context) contains eigenVariable)
throw NDRuleCreationException(s"Eigenvariable condition is violated: $context contains $eigenVariable")
def subFormula = BetaReduction.betaNormalize(Substitution(eigenVariable, quantifiedVariable)(auxFormula))
if (BetaReduction.betaNormalize(Substitution(quantifiedVariable, eigenVariable)(subFormula)) != auxFormula)
throw NDRuleCreationException(s"Aux formula should be $subFormula[$quantifiedVariable\\$eigenVariable] = " +
BetaReduction.betaNormalize(Substitution(quantifiedVariable, eigenVariable)(subFormula))
+ s", but is $auxFormula.")
def mainFormula = BetaReduction.betaNormalize(All(quantifiedVariable, subFormula))
override def name = "∀:i"
def auxIndices = Seq(Seq(Suc(0)))
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object ForallIntroRule extends ConvenienceConstructor("ForallIntroRule") {
/**
* Convenience constructor for ∀:i that, given a main formula and an eigenvariable, will try to
* construct an inference with that instantiation.
*
* @param subProof The subproof.
* @param mainFormula The formula to be inferred. Must be of the form ∀x.A.
* @param eigenVariable A variable α such that A[α] occurs in the premise.
* @return
*/
def apply(subProof: NDProof, mainFormula: Formula, eigenVariable: Var): ForallIntroRule = {
if (freeVariables(mainFormula) contains eigenVariable) {
throw NDRuleCreationException(s"Illegal main formula: Eigenvariable $eigenVariable is free in $mainFormula.")
} else mainFormula match {
case All(v, subFormula) =>
val auxFormula = Substitution(v, eigenVariable)(subFormula)
val premise = subProof.endSequent
val (_, indices) = findAndValidate(premise)(Seq(), auxFormula)
val p = ForallIntroRule(subProof, eigenVariable, v)
assert(p.mainFormula == mainFormula)
p
case _ => throw NDRuleCreationException(s"Proposed main formula $mainFormula is not universally quantified.")
}
}
def apply(subProof: NDProof, eigenVariable: Var): ForallIntroRule =
ForallIntroRule(subProof, eigenVariable, eigenVariable)
}
/**
* An NDProof ending with a universal quantifier elimination:
*
* (π)
* Γ :- ∀x.A
* -------------∀:e
* Γ :- A[x\t]
*
*
* @param subProof The proof π.
* @param term The term t.
*/
case class ForallElimRule(subProof: NDProof, term: Expr)
extends UnaryNDProof with CommonRule {
val universal = premise(Suc(0))
val mainFormula = universal match {
case All(v, subFormula) => Substitution(v, term)(subFormula)
case _ =>
throw NDRuleCreationException(s"Proposed main formula $universal is not universally quantified.")
}
override def name = "∀:e"
def auxIndices = Seq(Seq(Suc(0)))
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object ForallElimBlock {
/**
* Applies the ForallElim-rule n times.
*
* The rule:
*
* (π)
* Γ :- ∀x1,..,xN.A
* ---------------------------------- (∀_e x n)
* Γ :- A[x1\t1,...,xN\tN]
*
* where t1,...,tN are terms.
*
*
* @param subProof The proof π with (Γ :- ∀x1,..,xN.A) as the bottommost sequent.
* @param terms The list of terms with which to instantiate main. The caller of this
* method has to ensure the correctness of these terms, and, specifically, that
* ∀x1,..,xN.A indeed occurs at the bottom of the proof π.
*/
def apply(subProof: NDProof, terms: Seq[Expr]): NDProof =
terms.foldLeft(subProof)((acc, t) => nd.ForallElimRule(acc, t))
}
/**
* An NDProof ending with an existential quantifier introduction:
*
* (π)
* Γ :- A[x\t]
* ------------∃:i
* Γ :- ∃x.A
*
*
* @param subProof The proof π.
* @param A The formula A.
* @param term The term t.
* @param v The variable x.
*/
case class ExistsIntroRule(subProof: NDProof, A: Formula, term: Expr, v: Var)
extends UnaryNDProof with CommonRule {
if (premise(Suc(0)) != BetaReduction.betaNormalize(Substitution(v, term)(A)))
throw NDRuleCreationException(s"Substituting $term for $v in $A does not result in ${premise(Suc(0))}.")
val mainFormula = BetaReduction.betaNormalize(Ex(v, A))
override def name = "∃:i"
def auxIndices = Seq(Seq(Suc(0)))
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object ExistsIntroRule extends ConvenienceConstructor("ExistsIntroRule") {
/**
* Convenience constructor for ∃:i that, given a main formula and a term, will try to
* construct an inference with that instantiation.
*
* @param subProof The subproof.
* @param mainFormula The formula to be inferred. Must be of the form ∃x.A.
* @param term A term t such that A[t] occurs in the premise.
* @return
*/
def apply(subProof: NDProof, mainFormula: Formula, term: Expr): ExistsIntroRule = {
val premise = subProof.endSequent
mainFormula match {
case Ex(v, subFormula) =>
val auxFormula = BetaReduction.betaNormalize(Substitution(v, term)(subFormula))
if (premise(Suc(0)) == auxFormula) {
val p = ExistsIntroRule(subProof, subFormula, term, v)
assert(p.mainFormula == mainFormula)
p
} else throw NDRuleCreationException(s"Formula $auxFormula is not the succedent of $premise.")
case _ => throw NDRuleCreationException(s"Proposed main formula $mainFormula is not existentially quantified.")
}
}
/**
* Convenience constructor for ∃:i that, given a main formula, will try to construct an inference with that formula.
*
* @param subProof The subproof.
* @param mainFormula The formula to be inferred. Must be of the form ∃x.A[t\x]. The premise must contain A[t].
* @return
*/
def apply(subProof: NDProof, mainFormula: Formula): ExistsIntroRule = mainFormula match {
case Ex(v, subFormula) =>
val pos = subFormula.find(v).head
val t = if (subProof.endSequent(Suc(0)).isDefinedAt(pos))
subProof.endSequent(Suc(0)).get(pos).get
else
throw NDRuleCreationException(s"Premise is not defined at $pos.")
val p = apply(subProof, mainFormula, t)
assert(p.mainFormula == mainFormula)
p
case _ => throw NDRuleCreationException(s"Proposed main formula $mainFormula is not existentially quantified.")
}
}
/**
* An NDProof ending with an existential quantifier elimination:
*
* (π1) (π2)
* Γ :- ∃x.A Π, A[x\α] :- B
* ----------------------------∃:e
* Γ, Π :- B
*
* This rule is only applicable if the eigenvariable condition is satisfied: α must not occur freely in Π, and B
*
* @param leftSubProof The proof π1.
* @param rightSubProof The proof π2.
* @param aux The index of A[x\α].
* @param eigenVariable The variable α.
*/
case class ExistsElimRule(leftSubProof: NDProof, rightSubProof: NDProof, aux: SequentIndex, eigenVariable: Var)
extends BinaryNDProof with CommonRule with Eigenvariable {
validateIndices(rightPremise, Seq(aux))
val (existentialFormula, leftContext) = leftPremise focus Suc(0)
val (auxFormula, rightContext) = rightPremise focus aux
// eigenvariable condition
if (freeVariables(rightContext) contains eigenVariable)
throw NDRuleCreationException(s"Eigenvariable condition is violated: $rightContext contains $eigenVariable")
val (quantifiedVariable, subFormula) = existentialFormula match {
case Ex(variable, sub) => (variable, sub)
case _ =>
throw NDRuleCreationException(s"Formula $existentialFormula is not existentially quantified.")
}
val auxShouldBe = BetaReduction.betaNormalize(Substitution(quantifiedVariable, eigenVariable)(subFormula))
if (auxShouldBe != auxFormula) throw NDRuleCreationException(s"Formula $auxFormula should be $auxShouldBe.")
val mainFormula = rightPremise(Suc(0))
override def name = "∃:e"
def auxIndices = Seq(Seq(Suc(0)), Seq(aux, Suc(0)))
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object ExistsElimRule extends ConvenienceConstructor("ExistsElimRule") {
/**
* Convenience constructor for ∃:e that, given an eigenvariable, will try to
* construct an inference with that instantiation.
*
* @param leftSubProof The proof π1.
* @param rightSubProof The proof π2.
* @param eigenVariable A variable α such that A[α] occurs in the premise.
* @return
*/
def apply(leftSubProof: NDProof, rightSubProof: NDProof, eigenVariable: Var): ExistsElimRule = {
val existentialFormula = leftSubProof.conclusion(Suc(0))
existentialFormula match {
case Ex(v, subFormula) =>
val auxFormula = Substitution(v, eigenVariable)(subFormula)
val premise = rightSubProof.endSequent
val (indices, _) = findAndValidate(premise)(Seq(auxFormula), Suc(0))
ExistsElimRule(leftSubProof, rightSubProof, Ant(indices(0)), eigenVariable)
case _ => throw NDRuleCreationException(s"Formula $existentialFormula is not existentially quantified.")
}
}
/**
* Convenience constructor for ∃:e that, given only its subproofs, will try to
* construct an inference with that formula.
*
* @param leftSubProof The proof π1.
* @param rightSubProof The proof π2.
* @return
*/
def apply(leftSubProof: NDProof, rightSubProof: NDProof): ExistsElimRule = {
val existentialFormula = leftSubProof.conclusion(Suc(0))
existentialFormula match {
case Ex(v, subFormula) => apply(leftSubProof, rightSubProof, v)
case _ =>
throw NDRuleCreationException(s"Formula $existentialFormula is not existentially quantified.")
}
}
}
/**
* An NDProof consisting of an axiom from a theory:
*
* --------th
* :- A
*
*
* @param mainFormula The axiom A.
*/
case class TheoryAxiom(mainFormula: Formula) extends InitialSequent {
def conclusion = NDSequent(Seq(), mainFormula)
override def name = "th"
}
/**
* An NDProof ending with elimination of equality:
*
* (π1) (π2)
* Γ :- s = t Π :- A[x\s]
* ------------------------------eq:e
* Γ,Π :- A[x\t]
*
*
*
* @param leftSubProof The subproof π1.
* @param rightSubProof The subproof π2.
* @param formulaA The formula A.
* @param variablex The variable x.
*/
case class EqualityElimRule(leftSubProof: NDProof, rightSubProof: NDProof, formulaA: Formula, variablex: Var)
extends BinaryNDProof with CommonRule {
val eqFormula = leftPremise(Suc(0))
val (s, t) = eqFormula match {
case Eq(s, t) => (s, t)
case _ => throw NDRuleCreationException(s"Formula $eqFormula is not an equation.")
}
val substitution1 = Substitution(variablex, s)
val substitution2 = Substitution(variablex, t)
val auxFormula = rightPremise(Suc(0))
val mainFormula = if (auxFormula == BetaReduction.betaNormalize(substitution1(formulaA)))
BetaReduction.betaNormalize(substitution2(formulaA))
else if (auxFormula == BetaReduction.betaNormalize(substitution2(formulaA)))
BetaReduction.betaNormalize(substitution1(formulaA))
else
throw NDRuleCreationException(
s"Formula $auxFormula is not equal to $formulaA with either " +
s"substitution $substitution1 or $substitution2 applied to it."
)
def auxIndices = Seq(Seq(Suc(0)), Seq(Suc(0)))
override def name = "eq:e"
override def mainFormulaSequent = Sequent() :+ mainFormula
}
object EqualityElimRule extends ConvenienceConstructor("EqualityElimRule") {
/**
* Convenience constructor for eq:e.
* Given only the subproofs, it will attempt to create an inference with this.
*
* @param leftSubProof The left subproof.
* @param rightSubProof The right subproof.
* @return
*/
def apply(leftSubProof: NDProof, rightSubProof: NDProof): EqualityElimRule = {
val eqFormula = leftSubProof.conclusion(Suc(0))
val auxFormula = rightSubProof.conclusion(Suc(0))
val (s, _) = eqFormula match {
case Eq(s, t) => (s, t)
case _ => throw NDRuleCreationException(s"Formula $eqFormula is not an equation.")
}
val repContext = replacementContext.abstractTerm(auxFormula)(s)
val formulaA = repContext.term.asInstanceOf[Formula]
val variablex = repContext.variable.asInstanceOf[Var]
new EqualityElimRule(leftSubProof, rightSubProof, formulaA, variablex)
}
}
/**
* An NDProof that consist of the introduction of an equality.
*
* ----------eq:i
* :- t = t
*
*
*
* @param t The term t.
*/
case class EqualityIntroRule(t: Expr) extends InitialSequent {
override def name = "eq:i"
override def conclusion = NDSequent(Seq(), Eq(t, t))
def mainFormula = Eq(t, t)
}
/**
* Proof that a given data type constructor c preserves a formula F:
*
*
* (π)
* F(x,,1,,), F(x,,2,,), ..., F(x,,n,,), Γ :- F(c(x,,1,,,...,x,,n,,,y,,1,,,...,y,,n,,))
*
*
* The variables x,,i,, and y,,i,, are eigenvariables; x,,i,, are the eigenvariables of the
* same type as the inductive data type, y,,i,, are the other arguments of the constructor c.
* They can come in any order in the constructor.
*
* @param proof The NDProof ending in the sequent of this case.
* @param constructor The constructor c of the inductive data type that we're considering.
* @param hypotheses Indices of F(x,,1,,), ..., F(x,,n,,)
* @param eigenVars The eigenvariables of this case: x,,1,,, ..., x,,n,,, y,,1,,, ..., y,,n,,
* (these need to correspond to the order in c)
*/
case class InductionCase(proof: NDProof, constructor: Const, hypotheses: List[SequentIndex], eigenVars: List[Var]) {
val FunctionType(indTy, fieldTypes) = constructor.ty: @unchecked
require(fieldTypes == eigenVars.map(_.ty))
val hypVars = eigenVars filter { _.ty == indTy }
require(hypotheses.size == hypVars.size)
hypotheses foreach { hyp =>
require(hyp.isAnt && proof.endSequent.isDefinedAt(hyp))
}
val term = constructor(eigenVars: _*)
require(proof.endSequent.isDefinedAt(Suc(0)))
}
/**
* An NDProof ending with an induction rule:
*
* (π,,1,,) (π,,2,,) (π,,n,,)
* case 1 case 2 ... case n
* -------------------------------------(ind)
* Γ :- F(t: indTy)
*
*
* This induction rule can handle inductive data types.
* The cases are proofs that the various type constructors preserve the formula we want to prove.
* They are provided via the [[InductionCase]] class.
*
* @param cases A sequence of proofs showing that each type constructor preserves the validity of the main formula.
* @param formula The formula we want to prove via induction.
*/
case class InductionRule(cases: Seq[InductionCase], formula: Abs, term: Expr) extends CommonRule {
val Abs(quant @ Var(_, indTy), qfFormula) = formula
require(term.ty == indTy)
cases foreach { c =>
require(c.indTy == indTy)
c.hypotheses.lazyZip(c.hypVars) foreach { (hyp, eigen) =>
require(c.proof.endSequent(hyp) == Substitution(quant -> eigen)(qfFormula))
}
require(c.proof.endSequent(Suc(0)) == Substitution(quant -> c.term)(qfFormula))
}
require(freeVariables(contexts.flatMap(_.elements) :+ formula) intersect
cases.flatMap(_.eigenVars).toSet isEmpty)
val mainFormula = BetaReduction.betaNormalize(formula(term).asInstanceOf[Formula])
override protected def mainFormulaSequent = Sequent() :+ mainFormula
override def auxIndices: Seq[Seq[SequentIndex]] = cases map { c => c.hypotheses :+ Suc(0) }
override def immediateSubProofs: Seq[NDProof] = cases map { _.proof }
private lazy val product = cases.flatMap { _.productIterator } :+ formula :+ term
override def productArity = product.size
override def productElement(n: Int) = product(n)
override def name = "ind"
def eigenVariables = cases.flatMap(_.eigenVars).toSet
}
/**
* An NDProof ending with excluded middle:
*
* (π1) (π2)
* Γ, A :- B Π, ¬A :- B
* -------------------------EM
* Γ, Π :- B
*
*
* @param leftSubProof The proof π1.
* @param aux1 The index of A.
* @param rightSubProof The proof π2.
* @param aux2 The index of ¬A.
*/
case class ExcludedMiddleRule(leftSubProof: NDProof, aux1: SequentIndex, rightSubProof: NDProof, aux2: SequentIndex)
extends BinaryNDProof with CommonRule {
validateIndices(leftPremise, Seq(aux1))
validateIndices(rightPremise, Seq(aux2))
val formulaA = leftPremise(aux1)
val formulaNegA = rightPremise(aux2)
require(Neg(formulaA) == formulaNegA, s"Formula $formulaNegA is not the negation of $formulaA.")
val leftB = leftPremise(Suc(0))
val rightB = rightPremise(Suc(0))
val mainFormula = if (leftB == rightB) leftB
else
throw NDRuleCreationException(s"Formula $leftB is not equal to $rightB.")
override def name = "EM"
def auxIndices = Seq(Seq(aux1, Suc(0)), Seq(aux2, Suc(0)))
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* An NDProof ending with a definition
*
*
* (π)
* Γ :- A[φ]
* -----------d
* Γ :- A[c]
*
*
* @param subProof The proof π.
* @param mainFormula The formula A[c].
*/
case class DefinitionRule(subProof: NDProof, mainFormula: Formula) extends UnaryNDProof with CommonRule {
override def name = "d"
override def auxIndices = Seq(Seq(Suc(0)))
override def mainFormulaSequent = Sequent() :+ mainFormula
}
/**
* Class for reducing boilerplate code in ND companion objects.
*
* @param longName The long name of the rule.
*/
class ConvenienceConstructor(val longName: String) {
/**
* Create an NDRuleCreationException with a message starting
* with "Cannot create longName: ..."
*
* @param text The rest of the message.
* @return
*/
protected def NDRuleCreationException(text: String): NDRuleCreationException =
new NDRuleCreationException(longName, text)
def findIndicesOrFormulasInPremise(premise: HOLSequent)(
antIndicesFormulas: Seq[IndexOrFormula],
sucIndexFormula: IndexOrFormula
): (Seq[Formula], Seq[Int], Formula, Int) = {
val antReservedIndices = (antIndicesFormulas.foldLeft(scala.collection.mutable.HashSet.empty[Int])) { (acc, e) =>
e match {
case IsIndex(Ant(i)) => acc ++ Set(i)
case IsIndex(i: Suc) => throw NDRuleCreationException(s"Index $i should be in the antecedent.")
case IsFormula(_) => acc
}
}
val ant = for (e <- antIndicesFormulas) yield {
e match {
case IsIndex(idx @ Ant(i)) =>
antReservedIndices += i
val f = premise(idx)
(f, i)
case IsFormula(f) =>
var i = premise.antecedent.indexOf(f)
while (antReservedIndices contains i)
i = premise.antecedent.indexOf(f, i + 1)
if (i != -1)
antReservedIndices += i
(f, i)
case IsIndex(i: Suc) => throw NDRuleCreationException(s"Index $i should be in the antecedent.")
}
}
val suc = sucIndexFormula match {
case IsIndex(Suc(i: Int)) =>
(premise(Suc(i)), i)
case IsFormula(f) =>
val i = premise.succedent.indexOf(f)
(f, i)
case IsIndex(i: Ant) => throw NDRuleCreationException(s"Index $i should be in the succedent.")
}
val (antFormulas, antIndices) = ant.unzip
val (sucFormula, sucIndex) = suc
(antFormulas, antIndices, sucFormula, sucIndex)
}
/**
* Throws an exception if the output of findFormulasInPremise contains any -1 entries.
*
* @param premise The sequent in question.
* @param antFormulas The list of formulas in the antecedent.
* @param antIndices The list of indices corresponding to antFormulas.
* @return
*/
protected def validateIndices(premise: HOLSequent)(antFormulas: Seq[Formula], antIndices: Seq[Int]) = {
val antMap = scala.collection.mutable.HashMap.empty[Formula, Int]
for ((f, i) <- antFormulas zip antIndices) {
val count = antMap.getOrElse(f, 0)
if (i == -1)
throw NDRuleCreationException(s"Formula $f only found $count times in antecedent of $premise.")
antMap += f -> (count + 1)
}
}
/**
* Combines findIndicesOrFormulasInPremise and validateIndices. That is, it will return a pair of a lists of indices
* and an index, and throw an exception if either list contains a -1.
*
* @param premise The sequent in question.
* @param antIndicesFormulas The list of indices or formulas in the antecedent.
* @param sucIndexFormula The index or formula in the succedent.
* @return
*/
protected def findAndValidate(premise: HOLSequent)(
antIndicesFormulas: Seq[IndexOrFormula],
sucIndexFormula: IndexOrFormula
): (Seq[Int], Int) = {
val (antFormulas, antIndices, sucFormula, sucIndex) =
findIndicesOrFormulasInPremise(premise)(antIndicesFormulas, sucIndexFormula)
validateIndices(premise)(antFormulas, antIndices)
(antIndices, sucIndex)
}
}
class NDRuleCreationException(name: String, message: String) extends Exception(s"Cannot create $name: " + message)