diff --git a/README.md b/README.md index 1010401b023d4d7feabe2944a69e4a6a92dd20ee..be8c88f42a96a9fc992840c17a614e4711ee7e42 100644 --- a/README.md +++ b/README.md @@ -16,3 +16,19 @@ The TPTP package contains a parser from the TPTP file format to LISA. The simple * `sbt test` to compile and execute the test suite * `sbt assembly` to package it as a library * `sbt doc` to generate the Scala documentation + + +#LICENSE + Copyright [2022] [EPFL] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000000000000000000000000000000000000..ea69a270c6d5c87bc4b98982aec362d0d4a820db --- /dev/null +++ b/build.sbt @@ -0,0 +1,19 @@ +name := "lisa" +organization := "ch.epfl.lara" +licenses := Seq("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0.html")) + +version := "0.1" + +scalaVersion := "3.0.2" + +scalacOptions ++= Seq("-deprecation", "-feature") +scalacOptions ++= Seq( + "-language:implicitConversions" +) + + + +libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.10" % "test" +libraryDependencies += "io.github.leoprover" % "scala-tptp-parser_2.13" % "1.4" +Test / parallelExecution := false + diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000000000000000000000000000000000000..8378cad582d4344fe3358570c7950936251be224 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.5.7 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000000000000000000000000000000000000..7bc4622d2c974aee1dc6dbcd1050a6b196ba9585 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0") diff --git a/src/main/scala/Example.scala b/src/main/scala/Example.scala new file mode 100644 index 0000000000000000000000000000000000000000..4e59959d529ef2d50abaff8b3e7e41eb3231876f --- /dev/null +++ b/src/main/scala/Example.scala @@ -0,0 +1,163 @@ + +import lisa.KernelHelpers.{*, given} +import lisa.kernel.Printer.* +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.SCProofChecker.* +import lisa.kernel.proof.SequentCalculus.* +import lisa.kernel.proof.{SCProof, SCProofChecker} +import proven.tactics.SimplePropositionalSolver.solveSequent +import tptp.KernelParser.* +import tptp.ProblemGatherer.getPRPproblems + +/** + * Discover some of the elements of LISA to get started. + */ +object Example { + def main(args: Array[String]):Unit = { + //proofExample() //uncomment when exercise finished + solverExample() + tptpExample() + } + + /** + * A little example of a small LISA proof with holes to fill as an exercise. + * All interrogation marks have to be replaced by proof steps, (sets of) formulas or integers. + * The last two lines don't need to be changed. + */ + def proofExample(): Unit = { + val proof: SCProof = SCProof(Vector( + ???, + ???, + ?????( Set(P(x), P(f(x)), P(f(x)) ==> P(f(f(x)))) |- P(f(f(x))), 1, 0, ????, ???? ), + Hypothesis( Set(P(x), P(f(x)) ==> P(f(f(x)))) |- Set(P(x), P(f(f(x)))), P(x) ), + LeftImplies( ???? |- ????, 3, 2, ????, ???? ), + LeftForall( Set(????, ????, ????) |- ????, 4, ????, x, x ), + LeftForall( Set(????, ????) |- ????, 5, P(x) ==> P(f(x)), x, f(x) ), + RightImplies( forall(x, P(x) ==> P(f(x))) |- P(x) ==> P(f(f(x))), 6, P(x), P(f(f(x))) ), + RightForall( forall(x, P(x) ==> P(f(x))) |- forall(x, P(x) ==> P(f(f(x)))), 7, P(x) ==> P(f(f(x))), x ) + )) + checkProof(proof) + } + + + /** + * An example of how to use the simple propositional solver. + * The solver is complete (for propositional logic) but naive and won't succede on large formulas. + */ + def solverExample(): Unit = { + println("\n --- Pierce's Law ---") + checkProof(solveSequent(emptySeq +> ((A ==> B) ==> A ==> A))) + println("\n --- Various Tautologies ---") + val tests: List[Sequent] = List( + () |- Q \/ !Q, + Q /\ !Q |- (), + A /\ B |- A, + A /\ B |- Set(B, Q), + A |- A \/ B, + Set(A, Q) |- A \/ B, + () |- (A /\ B) ==> (A \/ C), + Set(A, B) |- (A \/ C) /\ B, + () |- ((T ==> Q) /\ (R ==> S)) ==> ((T \/ R) ==> (Q \/ S)), + (T ==> Q) /\ (R ==> S) /\ (!Q \/ !S) |- !T \/ !R, + () |- ((T /\ Q) \/ (!T /\ R)) <=> ((T ==> Q) /\ (!T ==> R)) + ) + println("\n --- Wrong ---") + tests.map(solveSequent).zipWithIndex.foreach(p => { + println(s"\nPropositional statement no ${p._2}") + checkProof(p._1) + }) + } + + /** + * An example about how to use the tptp parser. Please note that only problems and formulas expressed in the + * fof or cnf language of tptp are supported. + */ + def tptpExample(): Unit = { + val axioms = List( + "( ~ ( ? [X] : ( big_s(X) & big_q(X) ) ) )", + "( ! [X] : ( big_p(X) => ( big_q(X) | big_r(X) ) ) )", + "( ~ ( ? [X] : big_p(X) ) => ? [Y] : big_q(Y) )", + "( ! [X] : ( ( big_q(X) | big_r(X) ) => big_s(X) ) )" + ) + val conjecture = "( ? [X] : ( big_p(X) & big_r(X) ) )" + val anStatements = List( + "fof(pel24_1,axiom,\n ( ~ ( ? [X] :\n ( big_s(X)\n & big_q(X) ) ) )).", + "\nfof(pel24_2,axiom,\n ( ! [X] :\n ( big_p(X)\n => ( big_q(X)\n | big_r(X) ) ) )).", + "fof(pel24_3,axiom,\n ( ~ ( ? [X] : big_p(X) )\n => ? [Y] : big_q(Y) )).", + "fof(pel24_4,axiom,\n ( ! [X] :\n ( ( big_q(X)\n | big_r(X) )\n => big_s(X) ) )).", + "fof(pel24,conjecture,\n ( ? [X] :\n ( big_p(X)\n & big_r(X) ) )).", + ) + + println("\n---Individual Fetched Formulas---") + axioms.foreach(a => println(prettyFormula(parseToKernel(a)))) + println(prettyFormula(parseToKernel(conjecture))) + + println("\n---Annotated Formulas---") + anStatements.map(annotatedFormulaToKernel).foreach(printAnnotatedFormula) + + println("\n---Problems---") + + try { + val probs = getPRPproblems + probs.foreach(p => + println("Problem: "+p.name+" ("+p.domain+") --- "+p.file) + ) + + println("Number of problems found with PRP spc: "+probs.size) + + if (probs.nonEmpty) { + println(" - First problem as illustration:") + val seq = problemToSequent(probs.head) + printProblem(probs.head) + println("\n---Sequent---") + println(prettySequent(seq)) + } + } + catch { + case error:NullPointerException => println("You can download the tptp library at http://www.tptp.org/ and put it in main/resources") + } + + + + } + + + + + + + + // Utility + def printAnnotatedFormula(a:AnnotatedFormula):Unit = { + if (a.role == "axiom") println("Given " + a.name + ": " + prettyFormula(a.formula)) + else if (a.role == "conjecture") println("Prove " + a.name + ": " + prettyFormula(a.formula)) + else println(a.role + " " + a.name + ": " + prettyFormula(a.formula)) + } + + def printProblem(p:Problem) : Unit = { + println("Problem: "+p.name+" ("+p.domain+") ---") + println("Status: "+p.status) + println("SPC: "+p.spc.mkString(", ")) + p.formulas.foreach(printAnnotatedFormula) + } + private def checkProof(proof: SCProof): Unit = { + val error = SCProofChecker.checkSCProof(proof) + println(prettySCProof(proof, error)) + } + + val P = SchematicPredicateLabel("P", 1) + + val Q = PredicateFormula(SchematicPredicateLabel("Q", 0), Seq()) + val R = PredicateFormula(SchematicPredicateLabel("R", 0), Seq()) + val S = PredicateFormula(SchematicPredicateLabel("S", 0), Seq()) + val T = PredicateFormula(SchematicPredicateLabel("T", 0), Seq()) + val A = PredicateFormula(SchematicPredicateLabel("A", 0), Seq()) + val B = PredicateFormula(SchematicPredicateLabel("B", 0), Seq()) + val C = PredicateFormula(SchematicPredicateLabel("C", 0), Seq()) + val x = VariableLabel("x") + val f = ConstantFunctionLabel("f", 1) + + + def ???? :Formula = ??? + def ?????(args: Any*) = ??? +} diff --git a/src/main/scala/lisa/KernelHelpers.scala b/src/main/scala/lisa/KernelHelpers.scala new file mode 100644 index 0000000000000000000000000000000000000000..afa8008b772cbc7fbacf9675770866a6caf86995 --- /dev/null +++ b/src/main/scala/lisa/KernelHelpers.scala @@ -0,0 +1,81 @@ +package lisa + + +object KernelHelpers { + + import lisa.kernel.proof.SequentCalculus.Sequent + import lisa.kernel.fol.FOL.* + + // Prefix + def neg(f: Formula): Formula = ConnectorFormula(Neg, Seq(f)) + def and(l: Formula, r: Formula): Formula = ConnectorFormula(And, Seq(l, r)) + def or(l: Formula, r: Formula): Formula = ConnectorFormula(Or, Seq(l, r)) + def implies(l: Formula, r: Formula): Formula = ConnectorFormula(Implies, Seq(l, r)) + def iff(l: Formula, r: Formula): Formula = ConnectorFormula(Iff, Seq(l, r)) + def forall(label: VariableLabel, body: Formula): Formula = BinderFormula(Forall, label, body) + def exists(label: VariableLabel, body: Formula): Formula = BinderFormula(Exists, label, body) + def existsOne(label: VariableLabel, body: Formula): Formula = BinderFormula(ExistsOne, label, body) + def equ(l: Term, r: Term): Formula = PredicateFormula(equality, Seq(l, r)) + + extension (label: PredicateLabel) + def apply(args: Term*): Formula = PredicateFormula(label, args) + + extension (label: ConnectorLabel) + def apply(args: Formula*): Formula = ConnectorFormula(label, args) + + extension (label: FunctionLabel) + def apply(args: Term*): Term = FunctionTerm(label, args) + + extension (label: BinderLabel) + def apply(bound: VariableLabel, inner: Formula): Formula = BinderFormula(label, bound, inner) + + // Infix + extension (f: Formula) { + def unary_! : Formula = neg(f) + infix def ==>(g: Formula): Formula = implies(f, g) + infix def <=>(g: Formula): Formula = iff(f, g) + infix def /\(g: Formula): Formula = and(f, g) + infix def \/(g: Formula): Formula = or(f, g) + } + + extension (t: Term) + infix def ===(u: Term): Formula = PredicateFormula(equality, Seq(t, u)) + + // Other + given Conversion[VariableLabel, VariableTerm] = VariableTerm.apply + given Conversion[VariableTerm, VariableLabel] = _.label + + val emptySeq = Sequent(Set.empty, Set.empty) + + extension (l: Unit) + infix def |-(r: Iterable[Formula]): Sequent = Sequent(Set.empty, r.toSet) + infix def |-(r: Formula): Sequent = Sequent(Set.empty, Set(r)) + infix def |-(r: Unit): Sequent = emptySeq + extension (l: Iterable[Formula]) + infix def |-(r: Iterable[Formula]): Sequent = Sequent(l.toSet, r.toSet) + infix def |-(r: Formula): Sequent = Sequent(l.toSet, Set(r)) + infix def |-(r: Unit): Sequent = Sequent(l.toSet, Set.empty) + extension (l: Formula) + infix def |-(r: Iterable[Formula]): Sequent = Sequent(Set(l), r.toSet) + infix def |-(r: Formula): Sequent = Sequent(Set(l), Set(r)) + infix def |-(r: Unit): Sequent = Sequent(Set(l), Set.empty) + + // given Conversion[Tuple, List[Union[_.type]]] = _.toList + + given Conversion[(Boolean, List[Int], String), Option[(List[Int], String)]] = tr => if (tr._1) None else Some(tr._2, tr._3) + + + + extension (s: Sequent) { + infix def +<(f: Formula): Sequent = s.copy(left = s.left + f) + infix def -<(f: Formula): Sequent = s.copy(left = s.left - f) + infix def +>(f: Formula): Sequent = s.copy(right = s.right + f) + infix def ->(f: Formula): Sequent = s.copy(right = s.right - f) + infix def ++<(s1: Sequent): Sequent = s.copy(left = s.left ++ s1.left) + infix def --<(s1: Sequent): Sequent = s.copy(left = s.left -- s1.left) + infix def ++>(s1: Sequent): Sequent = s.copy(right = s.right ++ s1.right) + infix def -->(s1: Sequent): Sequent = s.copy(right = s.right -- s1.right) + infix def ++(s1: Sequent): Sequent = s.copy(left = s.left ++ s1.left, right = s.right ++ s1.right) + infix def --(s1: Sequent): Sequent = s.copy(left = s.left -- s1.left, right = s.right -- s1.right) + } +} diff --git a/src/main/scala/lisa/kernel/Printer.scala b/src/main/scala/lisa/kernel/Printer.scala new file mode 100644 index 0000000000000000000000000000000000000000..752eb43f989520fbdf1d7ece469ceee2689637cc --- /dev/null +++ b/src/main/scala/lisa/kernel/Printer.scala @@ -0,0 +1,281 @@ +package lisa.kernel + +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.SequentCalculus._ +import lisa.kernel.proof.SCProof + +/** + * A set of methods to pretty-print kernel trees. + */ +object Printer { + + private def spaceSeparator(compact: Boolean): String = if(compact) "" else " " + private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" + + private def prettyParentheses(content: String): String = s"(${content})" + + private def prettyFunction(name: String, args: Seq[String], compact: Boolean, dropParentheses: Boolean = true): String = { + if(dropParentheses && args.isEmpty) name else s"$name(${args.mkString(commaSeparator(compact))})" + } + + private def prettyInfix(operator: String, left: String, right: String, compact: Boolean): String = + Seq(left, operator, right).mkString(spaceSeparator(compact)) + + // Special symbols that aren't defined in this theory + private val (membership, subsetOf, sameCardinality) = ( + ConstantPredicateLabel("set_membership", 2), + ConstantPredicateLabel("subset_of", 2), + ConstantPredicateLabel("same_cardinality", 2) + ) + private val (emptySet, unorderedPair, orderedPair, singleton, powerSet, unionSet) = ( + ConstantFunctionLabel("empty_set", 0), + ConstantFunctionLabel("unordered_pair", 2), + ConstantFunctionLabel("ordered_pair", 2), + ConstantFunctionLabel("singleton", 1), + ConstantFunctionLabel("power_set", 1), + ConstantFunctionLabel("union", 1), + ) + private val nonAtomicPredicates = Set(equality, membership, subsetOf, sameCardinality) // Predicates which require parentheses (for readability) + + private def prettyFormulaInternal(formula: Formula, isRightMost: Boolean, compact: Boolean): String = formula match { + case PredicateFormula(label, args) => label match { + case `equality` => args match { + case Seq(l, r) => prettyInfix(label.id, prettyTerm(l), prettyTerm(r), compact) + case _ => throw new Exception + } + case `membership` => args match { + case Seq(l, r) => prettyInfix("∈", prettyTerm(l), prettyTerm(r), compact) + case _ => throw new Exception + } + case `subsetOf` => args match { + case Seq(l, r) => prettyInfix("⊆", prettyTerm(l), prettyTerm(r), compact) + case _ => throw new Exception + } + case `sameCardinality` => args match { + case Seq(l, r) => prettyInfix("~", prettyTerm(l), prettyTerm(r), compact) + case _ => throw new Exception + } + case _ => prettyFunction(label.id, args.map(prettyTerm(_, compact)), compact) + } + case ConnectorFormula(label, args) => + (label, args) match { + case (Neg, Seq(arg)) => + val isAtomic = arg match { + case PredicateFormula(label, _) => !nonAtomicPredicates.contains(label) + case ConnectorFormula(Neg, _) => true + case _ => false + } + val bodyString = prettyFormulaInternal(arg, isRightMost, compact) + val bodyParenthesized = if(isAtomic) bodyString else prettyParentheses(bodyString) + s"${label.id}${bodyParenthesized}" + case (binary @ (Implies | Iff | And | Or), Seq(l, r)) => + val precedences: Map[ConnectorLabel, Int] = Map( + And -> 1, + Or -> 2, + Implies -> 3, + Iff -> 4, + ) + val precedence = precedences(binary) + val isLeftParentheses = l match { + case _: BinderFormula => true + case PredicateFormula(leftLabel, _) => nonAtomicPredicates.contains(leftLabel) + case ConnectorFormula(leftLabel, _) => precedences.get(leftLabel).exists(_ >= precedence) + } + val isRightParentheses = r match { + case _: BinderFormula => !isRightMost + case PredicateFormula(leftLabel, _) => nonAtomicPredicates.contains(leftLabel) + case ConnectorFormula(rightLabel, _) => precedences.get(rightLabel).exists(_ > precedence) + } + val (leftString, rightString) = (prettyFormulaInternal(l, isLeftParentheses, compact), prettyFormulaInternal(r, isRightMost || isRightParentheses, compact)) + val leftParenthesized = if(isLeftParentheses) prettyParentheses(leftString) else leftString + val rightParenthesized = if(isRightParentheses) prettyParentheses(rightString) else rightString + prettyInfix(label.id, leftParenthesized, rightParenthesized, compact) + case _ => label.id+"("+args.map(a => prettyFormulaInternal(a, isRightMost, compact)).mkString(",")+")" //throw new Exception // Invalid number of arguments, cannot print + } + case BinderFormula(label, bound, inner) => + def accumulateNested(f: Formula, acc: Seq[VariableLabel]): (Seq[VariableLabel], Formula) = f match { + case BinderFormula(`label`, nestBound, nestInner) => accumulateNested(nestInner, nestBound +: acc) + case _ => (acc, f) + } + val (bounds, innerNested) = accumulateNested(inner, Seq(bound)) + Seq(s"${label.id}${bounds.reverse.map(_.id).mkString(commaSeparator(compact))}.", prettyFormulaInternal(innerNested, true, compact)).mkString(spaceSeparator(compact)) + } + + /** + * Returns a string representation of this formula. See also {@link prettyTerm}. + * Example output: + * <pre> + * ∀x, y. (∀z. (z ∈ x) ↔ (z ∈ y)) ↔ (x = y) + * </pre> + * @param formula the formula + * @param compact if spaces should be omitted between separators + * @return the string representation of this formula + */ + def prettyFormula(formula: Formula, compact: Boolean = false): String = prettyFormulaInternal(formula, true, compact) + + /** + * Returns a string representation of this term. See also {@link prettyFormula}. + * Example output: + * <pre> + * f({w, (x, y)}, z) + * </pre> + * @param term the term + * @param compact if spaces should be omitted between separators + * @return the string representation of this term + */ + def prettyTerm(term: Term, compact: Boolean = false): String = term match { + case VariableTerm(label) => label.id + case FunctionTerm(label, args) => + label match { + case `emptySet` => args match { + case Seq() => prettyFunction("∅", Seq.empty, compact, dropParentheses = true) + case _ => throw new Exception + } + case `unorderedPair` => args match { + case Seq(l, r) => s"{${Seq(l, r).map(prettyTerm(_, compact)).mkString(commaSeparator(compact))}}" + case _ => throw new Exception + } + case `orderedPair` => args match { + case Seq(l, r) => s"(${Seq(l, r).map(prettyTerm(_, compact)).mkString(commaSeparator(compact))})" + case _ => throw new Exception + } + case `singleton` => args match { + case Seq(s) => s"{${prettyTerm(s)}}" + case _ => throw new Exception + } + case `powerSet` => args match { + case Seq(s) => prettyFunction("P", Seq(prettyTerm(s)), compact) + case _ => throw new Exception + } + case `unionSet` => args match { + case Seq(s) => prettyFunction("U", Seq(prettyTerm(s)), compact) + case _ => throw new Exception + } + case _ => prettyFunction(label.id, args.map(prettyTerm(_, compact)), compact) + } + } + + /** + * Returns a string representation of this sequent. + * Example output: + * <pre> + * ⊢ ∀x, y. (∀z. (z ∈ x) ↔ (z ∈ y)) ↔ (x = y) + * </pre> + * @param sequent the sequent + * @param compact if spaces should be omitted between separators + * @return the string representation of this sequent + */ + def prettySequent(sequent: Sequent, compact: Boolean = false): String = { + def prettyFormulas(set: Set[Formula]): String = set.toSeq.map(prettyFormula(_, compact)).sorted.mkString(commaSeparator(compact, ";")) + Seq(prettyFormulas(sequent.left), "⊢", prettyFormulas(sequent.right)).filter(_.nonEmpty).mkString(spaceSeparator(compact)) + } + + /** + * Returns a string representation of this proof. + * @param proof the proof + * @param showError if set, marks that particular step in the proof (`->`) as an error + * @return a string where each indented line corresponds to a step in the proof + */ + def prettySCProof(proof: SCProof, showError: Option[(Seq[Int], String)] = None): String = { + def computeMaxNumbering(proof: SCProof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { + val resultWithCurrent = result.updated(level, Math.max(proof.steps.size - 1, result(level))) + proof.steps.collect { case sp: SCSubproof => sp }.foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumbering(sp.sp, level + 1, if(acc.size <= level + 1) acc :+ 0 else acc)) + } + val maxNumbering = computeMaxNumbering(proof, 0, IndexedSeq(0)) // The maximum value for each number column + val maxNumberingLengths = maxNumbering.map(_.toString.length) + val maxLevel = maxNumbering.size - 1 + + def leftPadSpaces(v: Any, n: Int): String = { + val s = String.valueOf(v) + if(s.length < n) (" " * (n - s.length)) + s else s + } + def rightPadSpaces(v: Any, n: Int): String = { + val s = String.valueOf(v) + if(s.length < n) s + (" " * (n - s.length)) else s + } + def prettySCProofRecursive(proof: SCProof, level: Int, tree: IndexedSeq[Int], topMostIndices: IndexedSeq[Int]): Seq[(Boolean, String, String, String)] = { + val printedImports = proof.imports.zipWithIndex.reverse.flatMap { case (imp, i) => + val currentTree = tree :+ (-i-1) + val showErrorForLine = showError.exists((position, reason) => currentTree.startsWith(position) && currentTree.drop(position.size).forall(_ == 0)) + val prefix = (Seq.fill(level - topMostIndices.size)(None) ++ Seq.fill(topMostIndices.size)(None) :+ Some(-i-1)) ++ Seq.fill(maxLevel - level)(None) + val prefixString = prefix.map(_.map(_.toString).getOrElse("")).zipWithIndex.map { case (v, i1) => leftPadSpaces(v, maxNumberingLengths(i1)) }.mkString(" ") + def pretty(stepName: String, topSteps: Int*): (Boolean, String, String, String) = + ( + showErrorForLine, + prefixString, + Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), + prettySequent(imp) + ) + + Seq(pretty("Import", 0)) + } + printedImports ++ proof.steps.zipWithIndex.flatMap { case (step, i) => + val currentTree = tree :+ i + val showErrorForLine = showError.exists((position, reason) => currentTree.startsWith(position) && currentTree.drop(position.size).forall(_ == 0)) + val prefix = (Seq.fill(level - topMostIndices.size)(None) ++ Seq.fill(topMostIndices.size)(None) :+ Some(i)) ++ Seq.fill(maxLevel - level)(None) + val prefixString = prefix.map(_.map(_.toString).getOrElse("")).zipWithIndex.map { case (v, i1) => leftPadSpaces(v, maxNumberingLengths(i1)) }.mkString(" ") + def pretty(stepName: String, topSteps: Int*): (Boolean, String, String, String) = + ( + showErrorForLine, + prefixString, + Seq(stepName, topSteps.mkString(commaSeparator(compact = false))).filter(_.nonEmpty).mkString(" "), + prettySequent(step.bot) + ) + def prettyDefinition(stepName: String, left: String, right: String, condition: Option[String] = None): (Boolean, String, String, String) = + ( // This method is an alternative to print something more meaningful for definitions + showErrorForLine, + prefixString, + stepName, + Seq(Seq(left, ":=", right) ++ condition.toSeq).filter(_.nonEmpty).map(_.mkString(" ")).mkString(", ") + ) + step match { + case sp @ SCSubproof(_, _, true) => pretty("Subproof", sp.premises*) +: prettySCProofRecursive(sp.sp, level + 1, currentTree, (if(i == 0) topMostIndices else IndexedSeq.empty) :+ i) + case other => + + val line = other match { + case Rewrite(_, t1) => pretty("Rewrite", t1) + case Hypothesis(_, _) => pretty("Hypo.") + case Cut(_, t1, t2, _) => pretty("Cut", t1, t2) + case LeftAnd(_, t1, _, _) => pretty("Left ∧", t1) + case LeftNot(_, t1, _) => pretty("Left ¬", t1) + case RightOr(_, t1, _, _) => pretty("Right ∨", t1) + case RightNot(_, t1, _) => pretty("Right ¬", t1) + case LeftExists(_, t1, _, _) => pretty("Left ∃", t1) + case LeftForall(_, t1, _, _, _) => pretty("Left ∀", t1) + case LeftOr(_, l, _) => pretty("Left ∨", l*) + case RightExists(_, t1, _, _, _) => pretty("Right ∃", t1) + case RightForall(_, t1, _, _) => pretty("Right ∀", t1) + case RightAnd(_, l,_) => pretty("Right ∧", l*) + case RightIff(_, t1, t2, _, _) => pretty("Right ↔", t1, t2) + case RightImplies(_, t1, _, _) => pretty("Right →", t1) + case Weakening(_, t1) => pretty("Weakening", t1) + case LeftImplies(_, t1, t2, _, _) => pretty("Left →", t1, t2) + case LeftIff(_, t1, _, _) => pretty("Left ↔", t1) + case LeftRefl(_, t1, _) => pretty("L. Axiom", t1) + case RightRefl(_, _) => pretty("R. Axiom") + case LeftSubstEq(_, t1, _, _, _, _) => pretty("L. SubstEq", t1) + case RightSubstEq(_, t1, _, _, _, _) => pretty("R. SubstEq", t1) + case LeftSubstIff(_, t1, _, _, _, _) => pretty("L. SubstIff", t1) + case RightSubstIff(_, t1, _, _, _, _) => pretty("R. SubstIff", t1) + case SCSubproof(_, _, false) => pretty("SCSubproof (hidden)") + case other => throw new Exception(s"No available method to print this proof step, consider updating Printer.scala\n$other") + } + Seq(line) + } + } + } + + + val marker = "->" + + val lines = prettySCProofRecursive(proof, 0, IndexedSeq.empty, IndexedSeq.empty) + val maxStepNameLength = lines.map { case (_, _, stepName, _) => stepName.length }.maxOption.getOrElse(0) + lines.map { + case (isMarked, indices, stepName, sequent) => + val suffix = Seq(indices, rightPadSpaces(stepName, maxStepNameLength), sequent) + val full = if(showError.nonEmpty) (if(isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix + full.mkString(" ") + }.mkString("\n")+(if (showError.nonEmpty) s"\nProof checker has reported error at line ${showError.get._1}: ${showError.get._2}" else "") + } + +} diff --git a/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala b/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala new file mode 100644 index 0000000000000000000000000000000000000000..054b4aafe16d2576cbfae65aee69bae1c523d92d --- /dev/null +++ b/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala @@ -0,0 +1,28 @@ +package lisa.kernel.fol + +/** + * Definitions that are common to terms and formulas. + */ +private[fol] trait CommonDefinitions { + val MaxArity: Int = 1000000 + /** + * An object with arity information for tree-like structures. + */ + protected trait Arity { + val arity: Int + } + + /** + * An labelled node for tree-like structures. + * + */ + protected trait Label[A <: Label[A]] extends Ordered[A] { + val id: String + } + + def freshId(taken: Set[String], base: String): String = { + var i = 0; + while (taken contains base + "_" + i) i += 1 + base + "_" + i + } +} diff --git a/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala b/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala new file mode 100644 index 0000000000000000000000000000000000000000..a4f79c526ad5ed0fde2ed8eb2bf1dbef6c70bda7 --- /dev/null +++ b/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala @@ -0,0 +1,335 @@ +package lisa.kernel.fol + +import scala.annotation.tailrec +import scala.collection.mutable +import scala.math.Numeric.IntIsIntegral + + +/** + * An EquivalenceChecker is an object that allows to detect some notion of equivalence between formulas + * and between terms. + * This allows the proof checker and writer to avoid having to deal with a class of "easy" equivalence. + * For example, by considering "x ∨ y" as being the same formula as "y ∨ x", we can avoid frustrating errors. + * For soundness, this relation should always be a subrelation of the usual FOL implication. + * The implementation checks for Orthocomplemented Bismeilatices equivalence, plus symetry and reflexivity + * of equality and alpha-equivalence. + */ +private[fol] trait EquivalenceChecker extends FormulaDefinitions { + sealed abstract class SimpleFormula { + val size: Int + private[EquivalenceChecker] var normalForm: Option[NormalFormula] = None + } + case class SimplePredicate(id: PredicateLabel, args: List[Term]) extends SimpleFormula { + val size = 1 + } + case class SNeg(child: SimpleFormula) extends SimpleFormula { + val size: Int = 1 + child.size + } + case class SOr(children: List[SimpleFormula]) extends SimpleFormula { + val size: Int = (children map (_.size)).foldLeft(1) { case (a, b) => a + b } + } + case class SForall(x: String, inner: SimpleFormula) extends SimpleFormula { + val size: Int = 1 + inner.size + } + case class SExists(x: String, inner: SimpleFormula) extends SimpleFormula { + val size: Int = 1 + inner.size + } + case class SLiteral(b: Boolean) extends SimpleFormula { + val size = 1 + normalForm = Some(NLiteral(b)) + } + sealed abstract class NormalFormula { + val code:Int + } + case class NormalPredicate(id: PredicateLabel, args: List[Term], code:Int) extends NormalFormula + case class NNeg(child: NormalFormula, code:Int) extends NormalFormula + case class NOr(children: List[NormalFormula], code:Int) extends NormalFormula + case class NForall(x: String, inner: NormalFormula, code:Int) extends NormalFormula + case class NExists(x: String, inner: NormalFormula, code:Int) extends NormalFormula + case class NLiteral(b: Boolean) extends NormalFormula{ + val code:Int = if (b) 1 else 0 + } + + + /** + * A class that encapsulate "runtime" information of the equivalence checker. It performs memoization for efficiency. + */ + class LocalEquivalenceChecker { + private val unsugaredVersion = scala.collection.mutable.HashMap[Formula, SimpleFormula]() + + // transform a LISA formula into a simpler, desugarised version with less symbols. Conjunction, implication, iff, existsOne are treated as alliases and translated. + def removeSugar(phi: Formula): SimpleFormula = unsugaredVersion.getOrElseUpdate(phi, { + phi match { + case PredicateFormula(label, args) => SimplePredicate(label, args.toList) + case ConnectorFormula(label, args) => label match { + case Neg => SNeg(removeSugar(args(0))) + case Implies => + val l = removeSugar(args(0)) + val r = removeSugar(args(1)) + SOr(List(SNeg(l), r)) + case Iff => + val l = removeSugar(args(0)) + val r = removeSugar(args(1)) + val c1 = SNeg(SOr(List(SNeg(l), r))) + val c2 = SNeg(SOr(List(SNeg(r), l))) + SNeg(SOr(List(c1, c2))) + case And => + SNeg(SOr(args.map(c => SNeg(removeSugar(c))).toList)) + case Or => SOr((args map removeSugar).toList) + } + case BinderFormula(label, bound, inner) => label match { + case Forall => SForall(bound.id, removeSugar(inner)) + case Exists => SExists(bound.id, removeSugar(inner)) + case ExistsOne => + val y = VariableLabel(freshId(inner.freeVariables.map(_.id), bound.id)) + val c1 = SimplePredicate(equality, List(VariableTerm(bound), VariableTerm(y))) + val c2 = removeSugar(inner) + val c1_c2 = SOr(List(SNeg(c1), c2)) + val c2_c1 = SOr(List(SNeg(c2), c1)) + SExists(y.id, SForall(bound.id, + SNeg(SOr(List(SNeg(c1_c2), SNeg(c2_c1)))) + )) + } + } + }) + + def toLocallyNameless(t: Term, subst: Map[VariableLabel, Int], i: Int): Term = t match { + case VariableTerm(label) => + if (subst.contains(label)) VariableTerm(VariableLabel("x" + (i - subst(label)))) + else VariableTerm(VariableLabel("_" + label)) + case FunctionTerm(label, args) => FunctionTerm(label, args.map(c => toLocallyNameless(c, subst, i))) + } + + def toLocallyNameless(phi: SimpleFormula, subst: Map[VariableLabel, Int], i: Int): SimpleFormula = phi match { + case SimplePredicate(id, args) => SimplePredicate(id, args.map(c => toLocallyNameless(c, subst, i))) + case SNeg(child) => SNeg(toLocallyNameless(child, subst, i)) + case SOr(children) => SOr(children.map(toLocallyNameless(_, subst, i))) + case SForall(x, inner) => SForall("", toLocallyNameless(inner, subst + (VariableLabel(x) -> i), i + 1)) + case SExists(x, inner) => SExists("", toLocallyNameless(inner, subst + (VariableLabel(x) -> i), i + 1)) + case SLiteral(b) => phi + } + + private val codesSig: mutable.HashMap[(String, Seq[Int]), Int] = mutable.HashMap() + codesSig.update(("zero", Nil), 0) + codesSig.update(("one", Nil), 1) + + val codesTerms: mutable.HashMap[Term, Int] = mutable.HashMap() + val codesSigTerms: mutable.HashMap[(TermLabel, Seq[Int]), Int] = mutable.HashMap() + + def codesOfTerm(t: Term): Int = codesTerms.getOrElseUpdate(t, t match { + case VariableTerm(label) => + codesSigTerms.getOrElseUpdate((label, Nil), codesSigTerms.size) + case FunctionTerm(label, args) => + val c = args map codesOfTerm + + codesSigTerms.getOrElseUpdate((label, c), codesSigTerms.size) + }) + + /* + def hasNormaleRecComputed(sf:SimpleFormula): Boolean = sf.normalForm.nonEmpty && (sf match { + case SNeg(child) => hasNormaleRecComputed(child) + case SOr(children) => children.forall(c => hasNormaleRecComputed(c)) + case SForall(x, inner) => hasNormaleRecComputed(inner) + case SExists(x, inner) => hasNormaleRecComputed(inner) + case _ => true + }) + */ + def checkForContradiction(children:List[(NormalFormula, Int)]): Boolean = { + val (negatives_temp, positives) = children.foldLeft[(List[NormalFormula], List[NormalFormula])]((Nil, Nil))( + (acc, ch) => acc match { + case (negatives, positives) => ch._1 match { + case NNeg(child, c) =>(child::negatives, positives) + case _ => (negatives, ch._1::positives) + } + } + ) + val negatives = negatives_temp.sortBy(_.code) + var i, j = 0 + while (i<positives.size && j<negatives.size){ //checks if there is a positive and negative nodes with same code. + val (c1, c2) = (positives(i).code, negatives(j).code) + if (c1<c2) i+=1 + else if (c1 == c2) return true + else j+=1 + } + var k = 0 + val children_codes = children.map(c => c._2).toSet //check if there is a negated disjunction whose children all share a code with an uncle + while(k<negatives.size){ + negatives(k) match { + case NOr(gdChildren, c) => + if (gdChildren.forall(sf => children_codes.contains(sf.code))) return true + case _ => () + } + k+=1 + } + false + } + + def updateCodesSig(sig: (String, Seq[Int])): Int = { + if (!codesSig.contains(sig)) codesSig.update(sig, codesSig.size) + codesSig(sig) + } + + + def OCBSLCode(phi: SimpleFormula): Int = { + if (phi.normalForm.nonEmpty) return phi.normalForm.get.code + val L = pDisj(phi, Nil) + val L2 = L zip (L map (_.code)) + val L3 = L2.sortBy(_._2).distinctBy(_._2).filterNot(_._2 == 0) //actually efficient has set based implementation already + if (L3.isEmpty) { + phi.normalForm = Some(NLiteral(false)) + } else if (L3.length == 1) { + phi.normalForm = Some(L3.head._1) + } else if (L3.exists(_._2 == 1) || checkForContradiction(L3) ) { + phi.normalForm = Some(NLiteral(true)) + } else { + phi.normalForm = Some(NOr(L3.map(_._1), updateCodesSig(("or", L3.map(_._2))))) + } + phi.normalForm.get.code + } + + def pDisj(phi: SimpleFormula, acc: List[NormalFormula]): List[NormalFormula] = { + if (phi.normalForm.nonEmpty) return pDisjNormal(phi.normalForm.get, acc) + val r: List[NormalFormula] = phi match { + case SimplePredicate(id, args) => + val lab = "pred_" + id.id + "_" + id.arity + if (id == equality) { + if (codesOfTerm(args(0)) == codesOfTerm(args(1))) + phi.normalForm = Some(NLiteral(true)) + else + phi.normalForm = Some(NormalPredicate(id, args, updateCodesSig((lab, (args map codesOfTerm).sorted)))) + } else { + phi.normalForm = Some(NormalPredicate(id, args, updateCodesSig((lab, args map codesOfTerm)))) + } + phi.normalForm.get :: acc + case SNeg(child) => pNeg(child, phi, acc) + case SOr(children) => children.foldLeft(acc)((p, a) => pDisj(a, p)) + case SForall(x, inner) => + val r = OCBSLCode(inner) + phi.normalForm = Some(NForall(x, inner.normalForm.get, updateCodesSig(("forall", List(r))))) + phi.normalForm.get :: acc + case SExists(x, inner) => + val r = OCBSLCode(inner) + phi.normalForm = Some(NExists(x, inner.normalForm.get, updateCodesSig(("exists", List(r))))) + phi.normalForm.get :: acc + case SLiteral(true) => + phi.normalForm = Some(NLiteral(true)) + phi.normalForm.get :: acc + case SLiteral(false) => + phi.normalForm = Some(NLiteral(false)) + phi.normalForm.get :: acc + } + r + } + + def pNeg(phi: SimpleFormula, parent: SimpleFormula, acc: List[NormalFormula]): List[NormalFormula] = { + if (phi.normalForm.nonEmpty) return pNegNormal(phi.normalForm.get, parent, acc) + val r:List[NormalFormula] = phi match { + case SimplePredicate(id, args) => + val lab = "pred_" + id.id + "_" + id.arity + if (id == equality) { + if (codesOfTerm(args(0)) == codesOfTerm(args(1))) + phi.normalForm = Some(NLiteral(true)) + parent.normalForm = Some(NLiteral(false)) + else + phi.normalForm = Some(NormalPredicate(id, args, updateCodesSig((lab, (args map codesOfTerm).sorted)))) + parent.normalForm = Some(NNeg(phi.normalForm.get, updateCodesSig(("neg", List(phi.normalForm.get.code))))) + } else { + phi.normalForm = Some(NormalPredicate(id, args, updateCodesSig((lab, args map codesOfTerm)))) + parent.normalForm = Some(NNeg(phi.normalForm.get, updateCodesSig(("neg", List(phi.normalForm.get.code))))) + } + parent.normalForm.get :: acc + case SNeg(child) => pDisj(child, acc) + case SForall(x, inner) => + val r = OCBSLCode(inner) + phi.normalForm = Some(NForall(x, inner.normalForm.get, updateCodesSig(("forall", List(r))))) + parent.normalForm = Some(NNeg(phi.normalForm.get, updateCodesSig(("neg", List(phi.normalForm.get.code))))) + parent.normalForm.get :: acc + case SExists(x, inner) => + val r = OCBSLCode(inner) + phi.normalForm = Some(NExists(x, inner.normalForm.get, updateCodesSig(("exists", List(r))))) + parent.normalForm = Some(NNeg(phi.normalForm.get, updateCodesSig(("neg", List(phi.normalForm.get.code))))) + parent.normalForm.get :: acc + case SLiteral(true) => + parent.normalForm = Some(NLiteral(false)) + parent.normalForm.get :: acc + case SLiteral(false) => + parent.normalForm = Some(NLiteral(true)) + parent.normalForm.get :: acc + case SOr(children) => + val T = children.sortBy(_.size) + val r1 = T.tail.foldLeft(List[NormalFormula]())((p, a) => pDisj(a, p)) + val r2 = r1 zip (r1 map (_.code)) + val r3 = r2.sortBy(_._2).distinctBy(_._2).filterNot(_._2 == 0) + if (r3.isEmpty) pNeg(T.head, parent, acc) + else { + val s1 = pDisj(T.head, r1) + val s2 = s1 zip (s1 map (_.code)) + val s3 = s2.sortBy(_._2).distinctBy(_._2).filterNot(_._2 == 0) + if (s3.exists(_._2 == 1) || checkForContradiction(s3) ) { + phi.normalForm=Some(NLiteral(true)) + parent.normalForm = Some(NLiteral(false)) + parent.normalForm.get :: acc + } else if (s3.length == 1) { + pNegNormal(s3.head._1, parent, acc) + } else { + phi.normalForm = Some(NOr(s3.map(_._1), updateCodesSig(("or", s3.map(_._2))))) + parent.normalForm = Some(NNeg(phi.normalForm.get, updateCodesSig(("neg", List(phi.normalForm.get.code))))) + parent.normalForm.get :: acc + } + } + } + r + } + def pDisjNormal(f:NormalFormula, acc:List[NormalFormula]):List[NormalFormula] = f match { + case NOr(children, c) => children ++ acc + case p@_ => p :: acc + } + def pNegNormal(f:NormalFormula, parent: SimpleFormula, acc:List[NormalFormula]): List[NormalFormula] = f match { + case NNeg(child, c) => + pDisjNormal(child, acc) + case _ => + parent.normalForm = Some(NNeg(f, updateCodesSig(("neg", List(f.code))))) + parent.normalForm.get :: acc + } + + def check(formula1: Formula, formula2: Formula): Boolean = { + getCode(formula1) == getCode(formula2) + } + def getCode(formula:Formula): Int = OCBSLCode(toLocallyNameless(removeSugar(formula), Map.empty, 0)) + + + def isSame(term1: Term, term2: Term): Boolean = codesOfTerm(term1) == codesOfTerm(term2) + + def isSame(formula1: Formula, formula2: Formula): Boolean = { + this.check(formula1, formula2) + } + + def isSameSet(s1: Set[Formula], s2:Set[Formula]): Boolean = { + s1.map(this.getCode).toList.sorted == s2.map(this.getCode).toList.sorted + } + + def isSubset(s1:Set[Formula], s2:Set[Formula]): Boolean = { + val codesSet1 = s1.map(this.getCode) + val codesSet2 = s2.map(this.getCode) + codesSet1.subsetOf(codesSet2) + + } + def contains(s:Set[Formula], f:Formula): Boolean = { + val codesSet= s.map(this.getCode) + val codesFormula = this.getCode(f) + codesSet.contains(codesFormula) + } + + + + } + def isSame(term1: Term, term2: Term): Boolean = (new LocalEquivalenceChecker).isSame(term1, term2) + + def isSame(formula1: Formula, formula2: Formula): Boolean = (new LocalEquivalenceChecker).isSame(formula1, formula2) + + def isSameSet(s1: Set[Formula], s2:Set[Formula]): Boolean = (new LocalEquivalenceChecker).isSameSet(s1, s2) + + def isSubset(s1:Set[Formula], s2:Set[Formula]): Boolean = (new LocalEquivalenceChecker).isSubset(s1, s2) + + def contains(s:Set[Formula], f:Formula): Boolean = (new LocalEquivalenceChecker).contains(s, f) +} diff --git a/src/main/scala/lisa/kernel/fol/FOL.scala b/src/main/scala/lisa/kernel/fol/FOL.scala new file mode 100644 index 0000000000000000000000000000000000000000..6f09ba07e254e60994196c68795ab58e8278b000 --- /dev/null +++ b/src/main/scala/lisa/kernel/fol/FOL.scala @@ -0,0 +1,12 @@ +package lisa.kernel.fol + +/** + * The concrete implementation of first order logic. + * All its content can be imported using a single statement: + * <pre> + * import lisa.fol.FOL._ + * </pre> + */ +object FOL extends FormulaDefinitions with EquivalenceChecker { + +} diff --git a/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala b/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala new file mode 100644 index 0000000000000000000000000000000000000000..8e1e772bde023e8c6bb5f7494a29e34e43620ce8 --- /dev/null +++ b/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala @@ -0,0 +1,159 @@ +package lisa.kernel.fol + +/** + * Definitions of formulas; analogous to [[TermDefinitions]]. + * Depends on [[FormulaLabelDefinitions]] and [[TermDefinitions]]. + */ +private[fol] trait FormulaDefinitions extends FormulaLabelDefinitions with TermDefinitions { + + /** + * The parent class of formulas. + * A formula is a tree whose nodes are either terms or labeled by predicates or logical connectors. + */ + sealed abstract class Formula extends TreeWithLabel[FormulaLabel] { + def predicates: Set[ConstantPredicateLabel] + def functions: Set[ConstantFunctionLabel] + //def predicatesSchemas: Set[PredicateLabel] = predicates filter { case _: ConstantPredicateLabel => false; case _: SchematicPredicateLabel => true } + } + + /** + * The formula counterpart of [[PredicateLabel]]. + */ + final case class PredicateFormula(label: PredicateLabel, args: Seq[Term]) extends Formula { + override def freeVariables: Set[VariableLabel] = args.foldLeft(Set.empty[VariableLabel])((prev, next) => prev union next.freeVariables) + + override def predicates: Set[ConstantPredicateLabel] = label match { + case l: ConstantPredicateLabel => Set(l) + case l: SchematicPredicateLabel => Set() + } + + override def functions: Set[ConstantFunctionLabel] = args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.functions) + } + + + /** + * The formula counterpart of [[ConnectorLabel]]. + */ + final case class ConnectorFormula(label: ConnectorLabel, args: Seq[Formula]) extends Formula { + override def freeVariables: Set[VariableLabel] = args.foldLeft(Set.empty[VariableLabel])((prev, next) => prev union next.freeVariables) + + override def predicates: Set[ConstantPredicateLabel] = args.foldLeft(Set.empty[ConstantPredicateLabel])((prev, next) => prev union next.predicates) + + override def functions: Set[ConstantFunctionLabel] = args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.functions) + } + + /** + * The formula counterpart of [[BinderLabel]]. + */ + final case class BinderFormula(label: BinderLabel, bound: VariableLabel, inner: Formula) extends Formula { + override def freeVariables: Set[VariableLabel] = inner.freeVariables - bound + + override def predicates: Set[ConstantPredicateLabel] = inner.predicates + + override def functions: Set[ConstantFunctionLabel] = inner.functions + } + + /** + * Performs the substitution of x by r in f. May rename bound variables of f if necessary + * to avoid capture of free variables in r. + * + * @param f The base formula + * @param x A variable, which should be free in f + * @param r A term that will replace x in f + * @return f[r/x] + */ + def substituteVariable(f: Formula, x: VariableLabel, r: Term): Formula = f match { + case PredicateFormula(label, args) => PredicateFormula(label, args.map(substituteVariable(_, x, r))) + case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(substituteVariable(_, x, r))) + case BinderFormula(label, bound, inner) => + if (isSame(bound, x)) f + else { + val fv = r.freeVariables + if (fv.contains(bound)) { + val newBoundVariable = VariableLabel(freshId(fv.map(_.name), bound.name)) + val newInner = substituteVariable(inner, bound, VariableTerm(newBoundVariable)) + BinderFormula(label, newBoundVariable, substituteVariable(newInner, x, r)) + } + else BinderFormula(label, bound, substituteVariable(inner, x, r)) + } + } + + def bindAll(binder: BinderLabel, vars: Seq[VariableLabel], phi:Formula): Formula = + vars.sortBy(_.name).foldLeft(phi)((f, v) => BinderFormula(binder, v, f)) + + /** + * Performs simultaneous substitution of multiple variables by multiple terms in a formula f. + * + * @param f The base formula + * @param m A map from variables to terms. + * @return t[m] + */ + def simultaneousSubstitution(f: Formula, m: Map[VariableLabel, Term]): Formula = f match { + case PredicateFormula(label, args) => PredicateFormula(label, args.map(simultaneousSubstitution(_, m))) + case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(simultaneousSubstitution(_, m))) + case BinderFormula(label, bound, inner) => + val newSubst = m - bound + val fv = m.values.flatMap(_.freeVariables).toSet + if (fv.contains(bound)) { + val newBoundVariable = VariableLabel(freshId(fv.map(_.name), bound.name)) + val newInner = substituteVariable(inner, bound, VariableTerm(newBoundVariable)) + BinderFormula(label, newBoundVariable, simultaneousSubstitution(newInner, newSubst)) + } + else BinderFormula(label, bound, simultaneousSubstitution(inner, newSubst)) + } + + /** + * Instantiate a schematic predicate symbol in a formula, using higher-order instantiation. + * + * @param phi The base formula + * @param p The symbol to replace + * @param psi A formula, seen as a function, that will replace p in t. + * @param a The "arguments" of psi when seen as a function rather than a ground formula. + * Those variables are replaced by the actual arguments of p. + * @return phi[psi(a1,..., an)/p] + */ + def instantiatePredicateSchema(phi: Formula, p: SchematicPredicateLabel, psi: Formula, a: List[VariableLabel]): Formula = { + require(a.length == p.arity) + phi match { + case PredicateFormula(label, args) => + if (isSame(label, p)) simultaneousSubstitution(psi, (a zip args).toMap) + else phi + case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(instantiatePredicateSchema(_, p, psi, a))) + case BinderFormula(label, bound, inner) => + val fv: Set[VariableLabel] = psi.freeVariables -- a + if (fv.contains(bound)) { + val newBoundVariable = VariableLabel(freshId(fv.map(_.name), bound.name)) + val newInner = substituteVariable(inner, bound, VariableTerm(newBoundVariable)) + BinderFormula(label, newBoundVariable, instantiatePredicateSchema(newInner, p, psi, a)) + } else BinderFormula(label, bound, instantiatePredicateSchema(inner, p, psi, a)) + } + } + + /** + * Instantiate a schematic function symbol in a formula, using higher-order instantiation. + * + * @param phi The base formula + * @param f The symbol to replace + * @param r A term, seen as a function, that will replace f in t. + * @param a The "arguments" of r when seen as a function rather than a ground term. + * Those variables are replaced by the actual arguments of f. + * @return phi[r(a1,..., an)/f] + */ + def instantiateFunctionSchema(phi: Formula, f: SchematicFunctionLabel, r: Term, a: List[VariableLabel]): Formula = { + require(a.length == f.arity) + phi match { + case PredicateFormula(label, args) => PredicateFormula(label, args.map(instantiateFunctionSchema(_, f, r, a))) + case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(instantiateFunctionSchema(_, f, r, a))) + case BinderFormula(label, bound, inner) => + val fv: Set[VariableLabel] = r.freeVariables -- a.toSet + if (fv.contains(bound)) { + val newBoundVariable = VariableLabel(freshId(fv.map(_.name), bound.name)) + val newInner = substituteVariable(inner, bound, VariableTerm(newBoundVariable)) + BinderFormula(label, newBoundVariable, instantiateFunctionSchema(newInner, f, r, a)) + } else BinderFormula(label, bound, instantiateFunctionSchema(inner, f, r, a)) + } + } + + def instantiateBinder(f: BinderFormula, t: Term): Formula = substituteVariable(f.inner, f.bound, t) + +} diff --git a/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala b/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala new file mode 100644 index 0000000000000000000000000000000000000000..81de8601f0bad8f71ddc954341e616a1ee9199d1 --- /dev/null +++ b/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala @@ -0,0 +1,86 @@ +package lisa.kernel.fol + +/** + * Definitions of formula labels. Analogous to [[TermLabelDefinitions]]. + */ +private[fol] trait FormulaLabelDefinitions extends CommonDefinitions { + + /** + * The parent class of formula labels. + * It similar as with terms; they denote the Predicates and logical connector themselves, and not the terms they help forming. + * They label the nodes of a tree that defines a formula. + */ + sealed abstract class FormulaLabel extends Label[FormulaLabel] { + def priority: Int = this match { + case _: ConstantPredicateLabel => 1 + case _: SchematicPredicateLabel => 2 + case _: ConnectorLabel => 3 + case _: BinderLabel => 4 + } + + /** + * Compare two formula labels by type, then by arity, then by id. + */ + def compare(that: FormulaLabel): Int = (this, that) match { + case (thi: ConstantPredicateLabel, tha: ConstantPredicateLabel) => (2 * (thi.arity compare tha.arity) + (thi.id compare tha.id)).sign + case (thi: SchematicPredicateLabel, tha: SchematicPredicateLabel) => (2 * (thi.arity compare tha.arity) + (thi.id compare tha.id)).sign + case (thi: ConnectorLabel, tha: ConnectorLabel) => thi.id compare tha.id + case (thi: BinderLabel, tha: BinderLabel) => thi.id compare tha.id + case _ => this.priority - that.priority + } + } + + /** + * The label for a predicate, namely a function taking a fixed number of terms and returning a formula. + * In logical terms it is a predicate symbol. + */ + sealed abstract class PredicateLabel extends FormulaLabel with Arity { + require(arity < MaxArity && arity >= 0) + } + + /** + * A standard predicate symbol. Typical example are equality (=) and membership (∈) + */ + final case class ConstantPredicateLabel(id: String, arity: Int) extends PredicateLabel + + /** + * The equality symbol (=) for first order logic. + */ + val equality: PredicateLabel = ConstantPredicateLabel("=", 2) + + /** + * A predicate symbol that can be instantiated with any formula. + */ + final case class SchematicPredicateLabel(id: String, arity: Int) extends PredicateLabel + + /** + * The label for a connector, namely a function taking a fixed number of formulas and returning another formula. + */ + sealed abstract class ConnectorLabel(val id: String, val arity: Int) extends FormulaLabel with Arity { + require(arity < MaxArity && arity >= -1) + } + + case object Neg extends ConnectorLabel("¬", 1) + + case object Implies extends ConnectorLabel("⇒", 2) + + case object Iff extends ConnectorLabel("↔", 2) + + case object And extends ConnectorLabel("∧", -1) + + case object Or extends ConnectorLabel("∨", -1) + + /** + * The label for a binder, namely an object with a body that has the ability to bind variables in it. + */ + sealed abstract class BinderLabel(val id: String) extends FormulaLabel + + case object Forall extends BinderLabel(id = "∀") + + case object Exists extends BinderLabel(id = "∃") + + case object ExistsOne extends BinderLabel(id = "∃!") + + def isSame(l: FormulaLabel, r: FormulaLabel): Boolean = (l compare r) == 0 + +} diff --git a/src/main/scala/lisa/kernel/fol/TermDefinitions.scala b/src/main/scala/lisa/kernel/fol/TermDefinitions.scala new file mode 100644 index 0000000000000000000000000000000000000000..03411af89036811ad6b942419ba3f739c7daccfc --- /dev/null +++ b/src/main/scala/lisa/kernel/fol/TermDefinitions.scala @@ -0,0 +1,100 @@ +package lisa.kernel.fol + +/** + * Definitions of terms; depends on [[TermLabelDefinitions]]. + */ +private[fol] trait TermDefinitions extends TermLabelDefinitions { + + protected trait TreeWithLabel[A] { + val label: A + + def freeVariables: Set[VariableLabel] + + def functions: Set[ConstantFunctionLabel] + } + + + /** + * The parent classes of terms. + * A term is a tree with nodes labeled by functions labels or variables. + * The number of children of a node is restricted by the arity imposed by the label. + */ + sealed abstract class Term extends TreeWithLabel[TermLabel] + + + /** + * A term which consists of a single variable. + * + * @param label The label of the variable. + */ + final case class VariableTerm(label: VariableLabel) extends Term { + override def freeVariables: Set[VariableLabel] = Set(label) + + override def functions: Set[ConstantFunctionLabel] = Set.empty + } + + /** + * A term labelled by a function symbol. It must contain a number of children equal to the arity of the symbol + * + * @param label The label of the node + * @param args children of the node. The number of argument must be equal to the arity of the function. + */ + final case class FunctionTerm(label: FunctionLabel, args: Seq[Term]) extends Term { + require(label.arity == args.size) + + override def freeVariables: Set[VariableLabel] = args.foldLeft(Set.empty[VariableLabel])((prev, next) => prev union next.freeVariables) + + override def functions: Set[ConstantFunctionLabel] = label match { + case l:ConstantFunctionLabel => args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.functions) + l + case l:SchematicFunctionLabel => args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.functions) + } + + val arity: Int = args.size + } + + /** + * Performs the substitution of x by r in t. + * + * @param t The base term + * @param x A variable, which should be free in t + * @param r A term that will replace x in t. + * @return t[r/x] + */ + def substituteVariable(t: Term, x: VariableLabel, r: Term): Term = t match { + case VariableTerm(label) => if (isSame(label, x)) r else t + case FunctionTerm(label, args) => FunctionTerm(label, args.map(substituteVariable(_, x, r))) + } + + /** + * Performs simultaneous substitution of multiple variables by multiple terms in a term t. + * + * @param t The base term + * @param m A map from variables to terms. + * @return t[m] + */ + def simultaneousSubstitution(t: Term, m: Map[VariableLabel, Term]): Term = t match { + case VariableTerm(label) => m.getOrElse(label, t) + case FunctionTerm(label, args) => FunctionTerm(label, args.map(simultaneousSubstitution(_, m))) + } + + /** + * instantiate a schematic function symbol in a term, using higher-order instantiation. + * + * @param t The base term + * @param f The symbol to replace + * @param r A term, seen as a function, that will replace f in t. + * @param a The "arguments" of r when seen as a function rather than a ground term. + * Those variables are replaced by the actual arguments of f. + * @return t[r(a1,..., an)/f] + */ + def instantiateFunctionSchema(t: Term, f: SchematicFunctionLabel, r: Term, a: List[VariableLabel]): Term = { + require(a.length == f.arity) + t match { + case VariableTerm(label) => t + case FunctionTerm(label, args) => + val newArgs = args.map(instantiateFunctionSchema(_, f, r, a)) + if (isSame(label, f)) simultaneousSubstitution(r, (a zip newArgs).toMap) + else FunctionTerm(label, newArgs) + } + } +} diff --git a/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala b/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala new file mode 100644 index 0000000000000000000000000000000000000000..288c27c1521c7c21b82c51228958b68ed19558db --- /dev/null +++ b/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala @@ -0,0 +1,70 @@ +package lisa.kernel.fol + +/** + * Definitions of term labels. + */ +private[fol] trait TermLabelDefinitions extends CommonDefinitions { + /** + * The parent class of term labels. + * These are labels that can be applied to nodes that form the tree of a term. + * For example, Powerset is not a term itself, it's a label for a node with a single child in a tree corresponding to a term. + * In logical terms, those labels are essentially symbols of sme language. + */ + sealed abstract class TermLabel extends Label[TermLabel] { + def priority: Int = this match { + case _: VariableLabel => 1 + case _: ConstantFunctionLabel => 2 + case _: SchematicFunctionLabel => 3 + } + + /** + * Sorts labels according to first whether the term is a variable or function, then according to arity, then to the id. + */ + def compare(that: TermLabel): Int = (this, that) match { + case (thi: VariableLabel, tha: VariableLabel) => thi.id compare tha.id + case (thi: ConstantFunctionLabel, tha: ConstantFunctionLabel) => (2 * (thi.arity compare tha.arity) + (thi.id compare tha.id)).sign + case (thi: SchematicFunctionLabel, tha: SchematicFunctionLabel) => (2 * (thi.arity compare tha.arity) + (thi.id compare tha.id)).sign + case _ => this.priority - that.priority + } + } + + /** + * The label of a term which is a variable. + * + * @param id The name of the variable, for example "x" or "y". + */ + final case class VariableLabel(id: String) extends TermLabel { + val name: String = id + } + + /** + * The label of a function-like term. Constants are functions of arity 0. + * There are two kinds of function symbols: Standards and schematic. + * Standard function symbols denote a particular function. Schematic function symbols + * can be instantiated with any term. This is particularly useful to express axiom schemas. + */ + sealed abstract class FunctionLabel extends TermLabel with Arity { + require(arity >= 0 && arity < MaxArity) + } + + /** + * A function symbol. + * + * @param id The name of the function symbol. + * @param arity The arity of the function symbol. A function symbol of arity 0 is a constant + */ + final case class ConstantFunctionLabel(id: String, arity: Int) extends FunctionLabel + + /** + * A schematic function symbol that can be substituted. + * + * @param id The name of the function symbol. + * @param arity The arity of the function symbol. A function symbol of arity 0 is a constant + */ + final case class SchematicFunctionLabel(id: String, arity: Int) extends FunctionLabel + + /** + * A function returning true if and only if the two symbols are considered "the same". + */ + def isSame(l: TermLabel, r: TermLabel): Boolean = (l compare r) == 0 +} diff --git a/src/main/scala/lisa/kernel/proof/RunningTheory.scala b/src/main/scala/lisa/kernel/proof/RunningTheory.scala new file mode 100644 index 0000000000000000000000000000000000000000..49a4688bc4b56e1e5333e04320da7824ddcc0d88 --- /dev/null +++ b/src/main/scala/lisa/kernel/proof/RunningTheory.scala @@ -0,0 +1,276 @@ +package lisa.kernel.proof + +import lisa.kernel.proof.SequentCalculus.* +import lisa.kernel.proof.SCProofChecker.* +import lisa.kernel.fol.FOL.* + +import scala.collection.mutable.Map as mMap +import scala.collection.mutable.Set as mSet +import scala.collection.immutable.Set + +/** + * This class describes the theory, i.e. the context and language, in which theorems are proven. + * A theory is built from scratch by introducing axioms and symbols first, then by definitional extensions. + * The structure is one-way mutable: Once an axiom or definition has been introduced, it can't be removed. + * On the other hand, theorems proven before the theory is extended will still hold. + * A theorem only holds true within a specific theory. + * A theory is responsible to make sure that a symbol already defined or present in the language can't + * be redefined. If a theory needs to be extanded in two different ways, or if a theory and its extension need + * to coexist independently, they should be different instances of this class. + */ +class RunningTheory { + /** + * A Justification is either a Theorem, an Axiom or a Definition + */ + sealed abstract class Justification + /** + * A theorem encapsulate a sequent and assert that this sequent has been correctly proven and may be used safely in further proofs. + */ + final case class Theorem private[RunningTheory](proposition: Sequent) extends Justification + /** + * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. + */ + final case class Axiom private[RunningTheory](ax: Formula) extends Justification + + /** + * A definition can be either a PredicateDefinition or a FunctionDefinition. + */ + sealed abstract class Definition extends Justification + + /** + * Define a predicate symbol as a shortcut for a formula. Example : P(x,y) := ∃!z. (x=y+z) + * @param label The name and arity of the new symbol + * @param args The variables representing the arguments of the predicate in the formula phi. + * @param phi The formula defining the predicate. + */ + final case class PredicateDefinition private[RunningTheory](label: ConstantPredicateLabel, args: Seq[VariableLabel], phi:Formula) extends Definition + + /** + * Define a function symbol as the unique element that has some property. The existence and uniqueness + * of that elements must have been proven before obtaining such a definition. Example + * f(x,y) := the "z" s.t. x=y+z + * @param label The name and arity of the new symbol + * @param args The variables representing the arguments of the predicate in the formula phi. + * @param out The variable representing the result of the function in phi + * @param phi The formula defining the function. + */ + final case class FunctionDefinition private[RunningTheory](label: ConstantFunctionLabel, args: Seq[VariableLabel], out: VariableLabel, phi: Formula) extends Definition + + + private[proof] val theoryAxioms : mSet[Axiom] = mSet.empty + private[proof] val funDefinitions: mMap[FunctionLabel, Option[FunctionDefinition]] = mMap.empty + private[proof] val predDefinitions: mMap[PredicateLabel, Option[PredicateDefinition]] = mMap(equality -> None) + + /** + * Check if a label is a symbol of the theory + */ + def isAcceptedFunctionLabel(label:FunctionLabel): Boolean = funDefinitions.contains(label) + /** + * Check if a label is a symbol of the theory + */ + def isAcceptedPredicateLabel(label:PredicateLabel): Boolean = predDefinitions.contains(label) + + /** + * From a given proof, if it is true in the Running theory, returns a theorem that is valid in the given theory. + * The proof's imports must be justified by the list of justification, and the conclusion of the theorem + * can't contain symbols that do not belong to the theory. + * @param justifications The list of justifications of the proof's imports. + * @param p The proof of the desired Theorem. + * @return A Theorem if the proof is correct, None else + */ + def proofToTheorem(p: SCProof, justifications:Seq[Justification]): Option[this.Theorem] = + if (checkProofWithinTheory(p, justifications) && belongsToTheory(p.conclusion)) + Some(Theorem(p.conclusion)) + else None + + + /** + * Introduce a new definition of a predicate in the theory. The symbol must not already exist in the theory + * and the formula can't contain symbols that are not in the theory. + * @param label The desired label. + * @param args The variables representing the arguments of the predicate in the formula phi. + * @param phi The formula defining the predicate. + * @return A definition object if the parameters are correct, + */ + def makePredicateDefinition(label: ConstantPredicateLabel, args: Seq[VariableLabel], phi: Formula): Option[this.PredicateDefinition] = { + if (belongsToTheory(phi) && !isAcceptedPredicateLabel(label)) { + val newDef = PredicateDefinition(label, args, phi) + predDefinitions.update(label, Some(newDef)) + Some(newDef) + } else { + None + } + } + + /** + * Introduce a new definition of a function in the theory. The symbol must not already exist in the theory + * and the formula can't contain symbols that are not in the theory. The existence and uniqueness of an element + * satisfying the definition's formula must first be proven. This is easy if the formula behaves as a shortcut, + * for example f(x,y) = 3x+2y + * but is much more general. The proof's conclusion must be of the form: |- ∀args. ∃!out. phi + * @param proof The proof of existence and uniqueness + * @param justifications The justifications of the proof. + * @param label The desired label. + * @param args The variables representing the arguments of the predicate in the formula phi. + * @param out The variable representing the function's result in the formula + * @param phi The formula defining the predicate. + * @return A definition object if the parameters are correct, + */ + def makeFunctionDefinition(proof: SCProof, justifications:Seq[Justification], label: ConstantFunctionLabel, args: Seq[VariableLabel], out: VariableLabel, phi: Formula): Option[this.FunctionDefinition] = { + if (belongsToTheory(phi) && !isAcceptedFunctionLabel(label) && checkProofWithinTheory(proof, justifications)) { + + proof.conclusion match{ + case Sequent(l, r) if l.isEmpty && r.size == 1 => + val subst = bindAll(ExistsOne, args, phi) + if (isSame(r.head, subst)){ + val newDef = FunctionDefinition(label, args, out, phi) + funDefinitions.update(label, Some(newDef)) + Some(newDef) + } + else None + + case _ => None + } + } else { + None + } + } + + + + /** + * Verify is a proof is correct withing a given Theory. It separately verifies if the proof is correct from + * a pure sequent calculus point of view, and if definitions and theorem imported into the proof are indeed + * part of the theory. + * + */ + def checkProofWithinTheory(proof: SCProof, justifications: Seq[Justification]): Boolean = { + + if (belongsToTheory(proof.conclusion)){ + if (proof.imports.forall(i => justifications.exists( j => isSameSequent(i, sequentFromJustification(j))) )) + true + else + false + } + else false + } + + private def sequentFromJustification(j:Justification): Sequent = j match { + case Theorem(proposition) => proposition + case Axiom(ax) => Sequent(Set.empty, Set(ax)) + case PredicateDefinition(label, args, phi) => + val inner = ConnectorFormula(Iff, Seq(PredicateFormula(label, args.map(VariableTerm.apply)), phi)) + Sequent(Set(), Set(bindAll(Forall, args, inner))) + case FunctionDefinition(label, args, out, phi) => + val inner = BinderFormula(Forall, out, + ConnectorFormula(Iff, Seq( + PredicateFormula(equality, Seq(FunctionTerm(label, args.map(VariableTerm.apply)), VariableTerm(out))), + phi + )) + ) + Sequent(Set(), Set( + bindAll(Forall, args, inner) + )) + + } + + + + + /** + * Verify if a given formula belongs to some language + * @param phi The formula to check + * @return Weather phi belongs to the specified language + */ + def belongsToTheory(phi:Formula):Boolean = phi match { + case PredicateFormula(label, args) => + label match { + case _: ConstantPredicateLabel => isAcceptedPredicateLabel(label) && args.forall(belongsToTheory) + case _: SchematicPredicateLabel => args.forall(belongsToTheory) + } + case ConnectorFormula(label, args) => args.forall(belongsToTheory) + case BinderFormula(label, bound, inner) => belongsToTheory(inner) + } + + def makeFormulaBelongToTheory(phi:Formula) : Unit = { + phi.predicates.foreach(addSymbol) + phi.functions.foreach(addSymbol) + } + /** + * Verify if a given term belongs to some language + * @param t The term to check + * @return Weather t belongs to the specified language + */ + def belongsToTheory(t:Term):Boolean = t match { + case VariableTerm(label) => true + case FunctionTerm(label, args) => label match { + case _: ConstantFunctionLabel => isAcceptedFunctionLabel(label) && args.forall(belongsToTheory(_)) + case _: SchematicFunctionLabel => args.forall(belongsToTheory(_)) + } + + } + /** + * Verify if a given sequent belongs to some language + * @param s The sequent to check + * @return Weather s belongs to the specified language + */ + def belongsToTheory(s:Sequent):Boolean = + s.left.forall(belongsToTheory(_)) && s.right.forall(belongsToTheory(_)) + + + def makeSequentBelongToTheory(s:Sequent): Unit = { + s.left.foreach(makeFormulaBelongToTheory) + s.right.foreach(makeFormulaBelongToTheory) + } + + + /** + * Add a new axiom to the Theory. For example, if the theory contains the language and theorems + * of Zermelo-Fraenkel Set Theory, this function can add the axiom of choice to it. + * If the axiom belongs to the language of the theory, adds it and return true. Else, returns false. + * @param f the new axiom to be added. + * @return true if the axiom was added to the theory, false else. + */ + def addAxiom(f:Formula):Boolean = { + if (belongsToTheory(f)){ + theoryAxioms.add(Axiom(f)) + true + } + else false + } + + /** + * Add a new symbol to the theory, without providing a definition. An ad-hoc definition can be + * added via an axiom, typically if the desired object is not derivable in the base theory itself. + * For example, This function can add the empty set symbol to a theory, and then an axiom asserting + * the it is empty can be introduced as well. + */ + def addSymbol(l:FunctionLabel):Unit = funDefinitions.update(l, None) + def addSymbol(l:PredicateLabel):Unit = predDefinitions.update(l, None) + + /** + * Public accessor to the set of symbol currently in the theory's language. + * @return the set of symbol currently in the theory's language. + */ + def language: (List[(FunctionLabel, Option[FunctionDefinition])], List[(PredicateLabel, Option[PredicateDefinition])]) = (funDefinitions.toList, predDefinitions.toList) + /** + * Public accessor to the current set of axioms of the theory + * @return the current set of axioms of the theory + */ + def getAxioms: List[Axiom] = theoryAxioms.toList + + /** + * Verify if a given formula is an axiom of the theory + * @param f the candidate axiom + * @return wether f is an axiom of the theory + */ + def isAxiom(f:Formula): Boolean = theoryAxioms.exists(a => isSame(a.ax,f)) + +} +object RunningTheory { + /** + * An empty theory suitable to reason about first order logic. + */ + def PredicateLogic: RunningTheory = RunningTheory() +} + diff --git a/src/main/scala/lisa/kernel/proof/SCProof.scala b/src/main/scala/lisa/kernel/proof/SCProof.scala new file mode 100644 index 0000000000000000000000000000000000000000..62573bc87c32a3a9218aa66f1f5d851b8523c471 --- /dev/null +++ b/src/main/scala/lisa/kernel/proof/SCProof.scala @@ -0,0 +1,98 @@ +package lisa.kernel.proof + +import lisa.kernel.proof.SequentCalculus._ +import lisa.kernel.proof.SCProofChecker._ + + +/** + * A SCPRoof (for Sequent Calculus Proof) is a (dependant) proof. While technically a proof is an Directed Acyclic Graph, + * here proofs are linearized and represented as a list of proof steps. + * Moreover, a proof can depend on some assumed, unproved, sequents specified in the second argument + * @param steps A list of Proof Steps that should form a valid proof. Each individual step should only refer to earlier + * proof steps as premisces. + * @param imports A list of assumed sequents that further steps may refer to. Imports are refered to using negative integers + * To refer to the first sequent of imports, use integer -1. + */ +case class SCProof(steps: IndexedSeq[SCProofStep], imports: IndexedSeq[Sequent] = IndexedSeq.empty) { + def numberedSteps: Seq[(SCProofStep, Int)] = steps.zipWithIndex + + /** + * Fetches the <code>i</code>th step of the proof. + * @param i the index + * @return a step + */ + def apply(i: Int): SCProofStep = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i) + + else throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + } + + /** + * Get the ith sequent of the proof. If the index is positive, give the bottom sequent of proof step number i. + * If the index is positive, return the (-i-1)th imported sequent. + * + * @param i The reference number of a sequent in the proof + * @return A sequent, either imported or reached during the proof. + */ + def getSequent(i:Int): Sequent = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i).bot + else + val i2 = -(i+1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(i2) + + } + + /** + * The length of the proof in terms of top-level steps, without including the imports. + */ + def length: Int = steps.length + + + /** + * The total length of the proof in terms of proof-step, including steps in subproof, but excluding the imports. + */ + def totalLength: Int = steps.foldLeft(0)((i, s) => i + (s match { + case s: SCSubproof => s.sp.totalLength + 1 + case _ => 1 + })) + + /** + * The conclusion of the proof, namely the bottom sequent of the last proof step. + * Can be undefined if the proof is empty. + */ + def conclusion: Sequent = { + if (steps.isEmpty) throw new NoSuchElementException("conclusion of an empty proof") + this(length - 1).bot + } + + /** + * A helper method that creates a new proof with a new step appended at the end. + * @param newStep the new step to be added + * @return a new proof + */ + def appended(newStep: SCProofStep): SCProof = copy(steps = steps appended newStep) + + /** + * A helper method that creates a new proof with a sequence of new steps appended at the end. + * @param newSteps the sequence of steps to be added + * @return a new proof + */ + def withNewSteps(newSteps: IndexedSeq[SCProofStep]): SCProof = copy(steps = steps ++ newSteps) +} + +object SCProof { + /** + * Instantiates a proof from an indexed list of proof steps. + * @param steps the steps of the proof + * @return the corresponding proof + */ + def apply(steps: SCProofStep*): SCProof = { + SCProof(steps.toIndexedSeq) + } + +} diff --git a/src/main/scala/lisa/kernel/proof/SCProofChecker.scala b/src/main/scala/lisa/kernel/proof/SCProofChecker.scala new file mode 100644 index 0000000000000000000000000000000000000000..bb475952dff29893a69c8f56eb2d8b72469f5868 --- /dev/null +++ b/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -0,0 +1,441 @@ +package lisa.kernel.proof + +import lisa.kernel.fol.FOL._ +import lisa.kernel.proof.SequentCalculus._ + +object SCProofChecker { + + private object Set { + def unapplySeq[T](s: Set[T]): Option[Seq[T]] = Some(s.toSeq) + } + + + /** + * This function verifies that a single SCProofStep is correctly applied. It verify that the step only refers to sequents with a lower number, and that + * the type and parameters of the proofstep correspond to the sequent claimed sequent. + * + * @param no The number of the given proof step. Needed to vewrify that the proof step doesn't refer to posterior sequents. + * @param step The proof step whose correctness needs to be checked + * @param references A function that associates sequents to a range of positive and negative integers that the proof step may refer to. Typically, + * a proof's [[SCProof.getSequent]] function. + * @return + */ + def checkSingleSCStep(no:Int, step: SCProofStep, references : Int => Sequent, importsSize: Option[Int]=None):(Boolean, List[Int], String) = { + val ref = references + val false_premise = step.premises.find(i => i >= no) + val false_premise2 = if (importsSize.nonEmpty) step.premises.find(i => i< -importsSize.get) else None + + val r: (Boolean, List[Int], String) = + if (false_premise.nonEmpty) + return (false, Nil, "A step can't refer to a step with a higher or equal number as a premise.") + if (false_premise2.nonEmpty) + return (false, Nil, "A step can't refer to a step with a lower number than minus the number of imports.") + else step match { + /* + * Γ |- Δ + * ------------ + * Γ |- Δ + */ + case Rewrite(s, t1) => + if (isSameSequent(s, ref(t1))) (true, Nil, "") else (false, Nil, s"The premise and the conclusion are not trivially equivalent.") + /* + * + * -------------- + * Γ, φ |- φ, Δ + */ + case Hypothesis(Sequent(left, right), phi) => + if (contains(left, phi)) + if (contains(right, phi)) (true, Nil, "") + + else (false, Nil, s"Right-hand side does not contain formula φ") + else (false, Nil, s"Left-hand side does not contain formula φ") + /* + * Γ |- Δ, φ φ, Σ |- Π + * ------------------------ + * Γ, Σ |-Δ, Π + */ + case Cut(b, t1, t2, phi) => + if (isSameSet(b.left + phi, ref(t1).left union ref(t2).left)) + if (isSameSet(b.right + phi, ref(t2).right union ref(t1).right)) + if (ref(t2).left.contains(phi)) + if (ref(t1).right.contains(phi)) + (true, Nil, "") + else (false, Nil, s"Right-hand side of first premise does not contain φ as claimed.") + else (false, Nil, s"Left-hand side of second premise does not contain φ as claimed.") + else (false, Nil, s"Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") + else (false, Nil, s"Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") + + // Left rules + /* + * Γ, φ |- Δ Γ, φ, ψ |- Δ + * -------------- or ------------- + * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ + */ + case LeftAnd(b, t1, phi, psi) => + if (isSameSet(ref(t1).right, b.right)) + val phiAndPsi = ConnectorFormula(And, Seq(phi, psi)) + if (isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || + isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || + isSameSet(b.left + phi + psi, ref(t1).left + phiAndPsi)) + (true, Nil, "") + else (false, Nil, "Left-hand side of conclusion + φ∧ψ must be same as left-hand side of premise + either φ, ψ or both.") + else (false, Nil, "Right-hand sides of the premise and the conclusion must be the same.") + /* + * Γ, φ |- Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ∨ψ |- Δ, Π + */ + case LeftOr(b, t, disjuncts) => + if (isSameSet(b.right, t.map(ref(_).right).reduce(_ union _)) ) + val phiOrPsi = ConnectorFormula(Or, disjuncts) + if (isSameSet(disjuncts.foldLeft(b.left)(_ + _), t.map(ref(_).left).reduce(_ union _) + phiOrPsi)) + (true, Nil, "") + else (false, Nil, s"Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + else (false, Nil, s"Right-hand side of conclusion is not the union of the right-hand sides of the premises.") + /* + * Γ |- φ, Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ→ψ |- Δ, Π + */ + case LeftImplies(b, t1, t2, phi, psi) => + val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) + if (isSameSet(b.right + phi, ref(t1).right union ref(t2).right)) + if (isSameSet(b.left + psi, ref(t1).left union ref(t2).left + phiImpPsi)) + (true, Nil, "") + else (false, Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ→ψ.") + else (false, Nil, s"Right-hand side of conclusion + φ must be identical to union of right-hand sides of premisces.") + /* + * Γ, φ→ψ |- Δ Γ, φ→ψ, ψ→φ |- Δ + * -------------- or --------------- + * Γ, φ↔ψ |- Δ Γ, φ↔ψ |- Δ + */ + case LeftIff(b, t1, phi, psi) => + val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) + val psiImpPhi = ConnectorFormula(Implies, Seq(psi, phi)) + val phiIffPsi = ConnectorFormula(Iff, Seq(phi, psi)) + if (isSameSet(ref(t1).right, b.right)) + if (isSameSet(b.left + phiImpPsi , ref(t1).left + phiIffPsi) || + isSameSet(b.left + psiImpPhi , ref(t1).left + phiIffPsi) || + isSameSet(b.left + phiImpPsi + psiImpPhi , ref(t1).left + phiIffPsi)) + (true, Nil, "") + else (false, Nil, "Left-hand side of conclusion + φ↔ψ must be same as left-hand side of premise + either φ→ψ, ψ→φ or both.") + else (false, Nil, "Right-hand sides of premise and conclusion must be the same.") + + /* + * Γ |- φ, Δ + * -------------- + * Γ, ¬φ |- Δ + */ + case LeftNot(b, t1, phi) => + val nPhi = ConnectorFormula(Neg, Seq(phi)) + if (isSameSet(b.left, ref(t1).left + nPhi)) + if (isSameSet(b.right + phi, ref(t1).right)) + (true, Nil, "") + else (false, Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") + else (false, Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") + + /* + * Γ, φ[t/x] |- Δ + * ------------------- + * Γ, ∀x. φ |- Δ + */ + case LeftForall(b, t1, phi, x, t) => + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + substituteVariable(phi, x, t), ref(t1).left + BinderFormula(Forall, x, phi))) + (true, Nil, "") + else (false, Nil, "Left-hand side of conclusion + φ[t/x] must be the same as left-hand side of premise + ∀x. φ") + else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + + /* + * Γ, φ |- Δ + * ------------------- if x is not free in the resulting sequent + * Γ, ∃x. φ|- Δ + */ + case LeftExists(b, t1, phi, x) => + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left + BinderFormula(Exists, x, phi))) + if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) + (true, Nil, "") + else (false, Nil, "The variable x must not be free in the resulting sequent.") + else (false, Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise + ∃x. φ") + else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + + /* + * Γ, ∃y.∀x. (x=y) ↔ φ |- Δ + * ---------------------------- if y is not free in φ + * Γ, ∃!x. φ |- Δ + */ + case LeftExistsOne(b, t1, phi, x) => + val y = VariableLabel(freshId(phi.freeVariables.map(_.id) + x.id, "y")) + val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(PredicateFormula(equality, List(VariableTerm(x), VariableTerm(y))), phi)))) + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + temp, ref(t1).left + BinderFormula(ExistsOne, x, phi))) + (true, Nil, "") + else (false, Nil, "Left-hand side of conclusion + ∃y.∀x. (x=y) ↔ φ must be the same as left-hand side of premise + ∃!x. φ") + else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") + + // Right rules + /* + * Γ |- φ, Δ Σ |- ψ, Π + * ------------------------ + * Γ, Σ |- φ∧ψ, Π, Δ + */ + case RightAnd(b, t, cunjuncts) => + val phiAndPsi = ConnectorFormula(And, cunjuncts) + if (isSameSet(b.left, t.map(ref(_).left).reduce(_ union _))) + if (isSameSet(cunjuncts.foldLeft(b.right)(_ + _), t.map(ref(_).right).reduce(_ union _) + phiAndPsi)) + (true, Nil, "") + else (false, Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") + else (false, Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + /* + * Γ |- φ, Δ Γ |- φ, ψ, Δ + * -------------- or --------------- + * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ + */ + case RightOr(b, t1, phi, psi) => + val phiOrPsi = ConnectorFormula(Or, Seq(phi, psi)) + if (isSameSet(ref(t1).left, b.left)) + if (isSameSet(b.right + phi, ref(t1).right + phiOrPsi) || + isSameSet(b.right + psi, ref(t1).right + phiOrPsi) || + isSameSet(b.right + phi + psi, ref(t1).right + phiOrPsi)) + (true, Nil, "") + else (false, Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") + else (false, Nil, "Left-hand sides of the premise and the conclusion must be the same.") + /* + * Γ, φ |- ψ, Δ + * -------------- + * Γ |- φ→ψ, Δ + */ + case RightImplies(b, t1, phi, psi) => + val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) + if (isSameSet(ref(t1).left, b.left + phi)) + if (isSameSet(b.right + psi, ref(t1).right + phiImpPsi)) + (true, Nil, "") + else (false, Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ→ψ.") + else (false, Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") + /* + * Γ |- a→ψ, Δ Σ |- ψ→φ, Π + * ---------------------------- + * Γ, Σ |- φ↔b, Π, Δ + */ + case RightIff(b, t1, t2, phi, psi) => + val phiImpPsi = ConnectorFormula(Implies, Seq(phi, psi)) + val psiImpPhi = ConnectorFormula(Implies, Seq(psi, phi)) + val phiIffPsi = ConnectorFormula(Iff, Seq(phi, psi)) + if (isSameSet(b.left, ref(t1).left union ref(t2).left)) + if (isSameSet(b.right + phiImpPsi + psiImpPhi, ref(t1).right union ref(t2).right + phiIffPsi)) + (true, Nil, "") + else (false, Nil, s"Right-hand side of conclusion + a→ψ + ψ→φ is not the same as the union of the right-hand sides of the premises φ↔b.") + else (false, Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + /* + * Γ, φ |- Δ + * -------------- + * Γ |- ¬φ, Δ + */ + case RightNot(b, t1, phi) => + val nPhi = ConnectorFormula(Neg, Seq(phi)) + if (isSameSet(b.right, ref(t1).right + nPhi)) + if (isSameSet(b.left + phi, ref(t1).left)) + (true, Nil, "") + else (false, Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") + else (false, Nil, "Right-hand side of conclusion must be the same as right-hand side of premise + ¬φ") + /* + * Γ |- φ, Δ + * ------------------- if x is not free in the resulting sequent + * Γ |- ∀x. φ, Δ + */ + case RightForall(b, t1, phi, x) => + if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + phi, ref(t1).right + BinderFormula(Forall, x ,phi))) + if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) + (true, Nil, "") + else (false, Nil, "The variable x must not be free in the resulting sequent.") + else (false, Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise + ∀x. φ") + else (false, Nil, "Left-hand sides of conclusion and premise must be the same.") + /* + * Γ |- φ[t/x], Δ + * ------------------- + * Γ |- ∃x. φ, Δ + */ + case RightExists(b, t1, phi, x, t) => + if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + substituteVariable(phi, x, t), ref(t1).right + BinderFormula(Exists, x ,phi))) + (true, Nil, "") + else (false, Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") + else (false, Nil, "Left-hand sides or conclusion and premise must be the same.") + + /** + * <pre> + * Γ |- ∃y.∀x. (x=y) ↔ φ, Δ + * ---------------------------- if y is not free in φ + * Γ|- ∃!x. φ, Δ + * </pre> + */ + case RightExistsOne(b, t1, phi, x) => + val y = VariableLabel(freshId(phi.freeVariables.map(_.id), "x")) + val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(PredicateFormula(equality, List(VariableTerm(x), VariableTerm(y))), phi)))) + if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + temp, ref(t1).right + BinderFormula(ExistsOne, x, phi))) + (true, Nil, "") + else (false, Nil, "Right-hand side of conclusion + ∃y.∀x. (x=y) ↔ φ must be the same as right-hand side of premise + ∃!x. φ") + else (false, Nil, "Left-hand sides of conclusion and premise must be the same") + + + // Structural rules + /* + * Γ |- Δ + * -------------- + * Γ, Σ |- Δ + */ + case Weakening(b, t1) => + if (isSubset(ref(t1).left, b.left)) + if (isSubset(ref(t1).right, b.right)) + (true, Nil, "") + else (false, Nil, "Right-hand side of premise must be a subset of right-hand side of conclusion") + else (false, Nil, "Left-hand side of premise must be a subset of left-hand side of conclusion") + + // Equality Rules + /* + * Γ, s=s |- Δ + * -------------- + * Γ |- Δ + */ + case LeftRefl(b, t1, phi) => + phi match { + case PredicateFormula(`equality`, Seq(left, right)) => + if (isSame(left, right)) + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left)) + (true, Nil, "") + else (false, Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") + else (false, Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") + else (false, Nil, s"φ is not an instance of reflexivity.") + case _ => (false, Nil, "φ is not an equality") + } + + /* + * + * -------------- + * |- s=s + */ + case RightRefl(b, phi) => + phi match { + case PredicateFormula(`equality`, Seq(left, right)) => + if (isSame(left, right)) + if (b.right.contains(phi)) + (true, Nil, "") + else (false, Nil, s"Right-Hand side of conclusion does not contain φ") + else (false, Nil, s"φ is not an instance of reflexivity.") + case _ => (false, Nil, s"φ is not an equality.") + } + + /* + * Γ, φ[s/?f] |- Δ + * --------------------- + * Γ, s=t, φ[t/?f] |- Δ + */ + case LeftSubstEq(b, t1, s, t, phi, f) => + val sEqT = PredicateFormula(equality, Seq(s, t)) + val phi_s_for_f = instantiateFunctionSchema(phi, f, s, Nil) + val phi_t_for_f = instantiateFunctionSchema(phi, f, t, Nil) + if (f.arity == 0) + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi_t_for_f, ref(t1).left + sEqT + phi_s_for_f) || + isSameSet(b.left + phi_s_for_f, ref(t1).left + sEqT + phi_t_for_f)) + (true, Nil, "") + else (false, Nil, "Left-hand sides of the conclusion + φ[s/?f] must be the same as left-hand side of the premise + s=t + φ[t/?f] (or with s and t swapped).") + else (false, Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + else (false, Nil, "Function schema ?f must have arity 0") + + + /* + * Γ |- φ[s/?f], Δ + * --------------------- + * Γ, s=t |- φ[t/?f], Δ + */ + case RightSubstEq(b, t1, s, t, phi, f) => + val sEqt = PredicateFormula(equality, Seq(s, t)) + if (f.arity == 0) + if (isSameSet(ref(t1).left + sEqt, b.left)) + val phi_s_for_f = instantiateFunctionSchema(phi, f, s, Nil) + val phi_t_for_f = instantiateFunctionSchema(phi, f, t, Nil) + if (isSameSet(b.right + phi_s_for_f, ref(t1).right + phi_t_for_f) || + isSameSet(b.right + phi_t_for_f, ref(t1).right + phi_s_for_f)) + (true, Nil, "") + else (false, Nil, s"Right-hand side of the premise and the conclusion should be the same with each containing one of φ[s/?f] φ[t/?f], but it isn't the case." ) + else (false, Nil, "Left-hand sides of the premise + s=t must be the same as left-hand side of the premise.") + else (false, Nil, "Function schema ?f must have arity 0.") + /* + * Γ, φ[ψ/?q] |- Δ + * --------------------- + * Γ, ψ↔τ, φ[τ/?q] |- Δ + */ + case LeftSubstIff(b, t1, psi, tau, phi, q) => + val psiIffTau = ConnectorFormula(Iff, Seq(psi, tau)) + val phi_tau_for_q = instantiatePredicateSchema(phi, q, tau, Nil) + val phi_psi_for_q = instantiatePredicateSchema(phi, q, psi, Nil) + if (q.arity == 0) + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(ref(t1).left + psiIffTau + phi_tau_for_q, b.left + phi_psi_for_q) || + isSameSet(ref(t1).left + psiIffTau + phi_psi_for_q, b.left + phi_tau_for_q)) + (true, Nil, "") + else (false, Nil, "Left-hand sides of the conclusion + φ[ψ/?q] must be the same as left-hand side of the premise + ψ↔τ + φ[τ/?q] (or with ψ and τ swapped).") + else (false, Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + else (false, Nil, "Predicate schema ?q must have arity 0.") + + /* + * Γ |- φ[ψ/?p], Δ + * --------------------- + * Γ, ψ↔τ |- φ[τ/?p], Δ + */ + case RightSubstIff(b, t1, psi, tau, phi, q) => + val psiIffTau = ConnectorFormula(Iff, Seq(psi, tau)) + val phi_tau_for_q = instantiatePredicateSchema(phi, q, tau, Nil) + val phi_psi_for_q = instantiatePredicateSchema(phi, q, psi, Nil) + if (q.arity == 0) + if (isSameSet(ref(t1).left + psiIffTau, b.left)) + if (isSameSet(b.right + phi_tau_for_q, ref(t1).right + phi_psi_for_q) || + isSameSet(b.right + phi_psi_for_q, ref(t1).right + phi_tau_for_q)) + (true, Nil, "") + else (false, Nil, s"Right-hand side of the premise and the conclusion should be the same with each containing one of φ[τ/?q] and φ[ψ/?q], but it isn't the case." ) + else (false, Nil, "Left-hand sides of the premise + ψ↔τ must be the same as left-hand side of the premise.") + else (false, Nil, "Predicate schema ?q must have arity 0.") + + case SCSubproof(sp, premises, _) => + if (premises.size == sp.imports.size){ + val invalid = premises.zipWithIndex.find((no, p) => !isSameSequent(ref(no), sp.imports(p)) ) + if (invalid.isEmpty){ + val r_subproof = checkSCProof(sp) + if (r_subproof._1) + (true, Nil, "") + else (false, r_subproof._2, "Subproof reports an error:\n"+r_subproof._3) + } else (false, Nil, s"Premise number ${invalid.get._1} (refering to step ${invalid.get}) is not the same as import number ${invalid.get._1} of the subproof.") + } else (false, Nil, "Number of premises and imports don't match: "+premises.size+" "+sp.imports.size) + + } + r + } + + /** + * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. + * If the proof is not correct, the functrion will report the faulty line and a brief explanation. + * @param proof A SC proof to check + * @return (true, Nil, "") if the proof is correct, else (false, l, s) with l the path to the incorrect + proof step and s an explanation. + * + */ + def checkSCProof(proof: SCProof) : (Boolean, List[Int], String) = { + + + val possibleError = proof.steps.view.zipWithIndex.map((step, no) => + + val r = checkSingleSCStep(no, step, (i:Int) => proof.getSequent(i), Some(proof.imports.size)) + (r._1, no::r._2, r._3) + ).find(p => !p._1 ) + if (possibleError.isEmpty) (true, Nil, "") + else possibleError.get + + + } + +} diff --git a/src/main/scala/lisa/kernel/proof/SequentCalculus.scala b/src/main/scala/lisa/kernel/proof/SequentCalculus.scala new file mode 100644 index 0000000000000000000000000000000000000000..cf79543367bde60c6c3ce6827dff22f56f695eaf --- /dev/null +++ b/src/main/scala/lisa/kernel/proof/SequentCalculus.scala @@ -0,0 +1,286 @@ +package lisa.kernel.proof + +import lisa.kernel.fol.FOL._ +import scala.collection.immutable.Set + + +/** + * The concrete implementation of sequent calculus (with equality). + * This file specifies the sequents and the allowed operations on them, the deduction rules of sequent calculus. + * It contains typical sequent calculus rules for FOL with equality as can be found in a text book, as well as a couple more for + * non-elementary symbols (↔, ∃!) and rules for substituting equal terms or equivalent formulas. I also contains two structural rules, + * subproof and a dummy rewrite step. + * Further mathematical steps, such as introducing or using definitions, axioms or theorems are not part of the basic sequent calculus. + */ +object SequentCalculus { + + + + /** + * A sequent is an object that can contain two sets of formulas, [[left]] and [[right]]. + * The intended semantic is for the [[left]] formulas to be interpreted as a conjunction, while the [[right]] ones as a disjunction. + * Traditionally, sequents are represented by two lists of formulas. + * Since sequent calculus includes rules for permuting and weakening, it is in essence equivalent to sets. + * Seqs make verifying proof steps much easier, but proof construction much more verbose and proofs longer. + * @param left the left side of the sequent + * @param right the right side of the sequent + */ + case class Sequent(left: Set[Formula], right: Set[Formula]) + + /** + * Simple method that transforms a sequent to a logically equivalent formula. + * + */ + def sequentToFormula(s:Sequent): Formula = ConnectorFormula(Implies, List(ConnectorFormula(And, s.left.toSeq), ConnectorFormula(Or, s.right.toSeq))) + /** + * Checks whether two sequents are equivalent, with respect to [[isSame]]. + * @param l the first sequent + * @param r the second sequent + * @return see [[isSame]] + */ + def isSameSequent(l: Sequent, r: Sequent): Boolean = isSame(sequentToFormula(l), sequentToFormula(r)) + + /** + * The parent of all proof steps types. + * A proof step is a deduction rule of sequent calculus, with the sequents forming the prerequisite and conclusion. + * For easier linearisation of the proof, the prerequisite are represented with numbers showing the place in the proof of the sequent used. + */ + + /** + * The parent of all sequent calculus rules. + */ + sealed trait SCProofStep { + val bot: Sequent + val premises : Seq[Int] + } + + /** + * <pre> + * Γ |- Δ + * ------------ + * Γ |- Δ + * </pre> + */ + case class Rewrite(bot: Sequent, t1: Int) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * + * -------------- + * Γ, φ |- φ, Δ + * </pre> + */ + case class Hypothesis(bot: Sequent, phi: Formula) extends SCProofStep{val premises = Seq()} + /** + * <pre> + * Γ |- Δ, φ φ, Σ |- Π + * ------------------------ + * Γ, Σ |-Δ, Π + * </pre> + */ + case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Formula) extends SCProofStep{val premises = Seq(t1, t2)} + + // Left rules + /** + * <pre> + * Γ, φ |- Δ Γ, φ, ψ |- Δ + * -------------- or -------------- + * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ + * </pre> + */ + case class LeftAnd(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ, φ |- Δ Σ, ψ |- Π ... + * -------------------------------- + * Γ, Σ, φ∨ψ∨... |- Δ, Π + * </pre> + */ + case class LeftOr(bot: Sequent, t:Seq[Int], disjuncts:Seq[Formula]) extends SCProofStep{val premises = t} + /** + * <pre> + * Γ |- φ, Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ→ψ |- Δ, Π + * </pre> + */ + case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep{val premises = Seq(t1, t2)} + /** + * <pre> + * Γ, φ→ψ |- Δ Γ, φ→ψ, ψ→φ |- Δ + * -------------- or -------------------- + * Γ, φ↔ψ |- Δ Γ, φ↔ψ |- Δ + * </pre> + */ + case class LeftIff(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ |- φ, Δ + * -------------- + * Γ, ¬φ |- Δ + * </pre> + */ + case class LeftNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ, φ[t/x] |- Δ + * ------------------- + * Γ, ∀ φ |- Δ + * + * </pre> + */ + case class LeftForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ, φ |- Δ + * ------------------- if x is not free in the resulting sequent + * Γ, ∃x φ|- Δ + * + * </pre> + */ + case class LeftExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ, ∃y.∀x. (x=y) ↔ φ |- Δ + * ---------------------------- if y is not free in φ + * Γ, ∃!x. φ |- Δ + * </pre> + */ + case class LeftExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep{val premises = Seq(t1)} + + + // Right rules + /** + * <pre> + * Γ |- φ, Δ Σ |- ψ, Π ... + * ------------------------------------ + * Γ, Σ |- φ∧ψ∧..., Π, Δ + * </pre> + */ + case class RightAnd(bot: Sequent, t:Seq[Int], cunjuncts:Seq[Formula]) extends SCProofStep{val premises = t} + /** + * <pre> + * Γ |- φ, Δ Γ |- φ, ψ, Δ + * -------------- or --------------- + * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ + * </pre> + */ + case class RightOr(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ, φ |- ψ, Δ + * -------------- + * Γ |- φ→ψ, Δ + * </pre> + */ + case class RightImplies(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ |- a→ψ, Δ Σ |- ψ→φ, Π + * ---------------------------- + * Γ, Σ |- φ↔ψ, Π, Δ + * </pre> + */ + case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep{val premises = Seq(t1, t2)} + /** + * <pre> + * Γ, φ |- Δ + * -------------- + * Γ |- ¬φ, Δ + * </pre> + */ + case class RightNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ |- φ, Δ + * ------------------- if x is not free in the resulting sequent + * Γ |- ∀x. φ, Δ + * </pre> + */ + case class RightForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ |- φ[t/x], Δ + * ------------------- + * Γ |- ∃x. φ, Δ + * + * (ln-x stands for locally nameless x) + * </pre> + */ + case class RightExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ |- ∃y.∀x. (x=y) ↔ φ, Δ + * ---------------------------- if y is not free in φ + * Γ|- ∃!x. φ, Δ + * </pre> + */ + case class RightExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep{val premises = Seq(t1)} + + // Structural rules + /** + * <pre> + * Γ |- Δ + * -------------- + * Γ, Σ |- Δ, Π + * </pre> + */ + case class Weakening(bot: Sequent, t1: Int) extends SCProofStep{val premises = Seq(t1)} + + + // Equality Rules + /** + * <pre> + * Γ, s=s |- Δ + * -------------- + * Γ |- Δ + * </pre> + */ + case class LeftRefl(bot: Sequent, t1: Int, fa: Formula) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * + * -------------- + * |- s=s + * </pre> + */ + case class RightRefl(bot: Sequent, fa: Formula) extends SCProofStep{val premises = Seq()} + /** + * <pre> + * Γ, φ[s/?f] |- Δ + * --------------------- + * Γ, s=t, φ[t/?f ] |- Δ + * </pre> + */ + case class LeftSubstEq(bot: Sequent, t1: Int, s: Term, t: Term, phi: Formula, f: SchematicFunctionLabel) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ |- φ[s/?f], Δ + * --------------------- + * Γ, s=t |- φ[t/?f], Δ + * </pre> + */ + case class RightSubstEq(bot: Sequent, t1: Int, s: Term, t: Term, phi: Formula, f: SchematicFunctionLabel) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ, φ[a/h] |- Δ + * --------------------- + * Γ, a↔b, φ[b/h] |- Δ + * </pre> + */ + case class LeftSubstIff(bot: Sequent, t1: Int, fa: Formula, fb: Formula, phi: Formula, h: SchematicPredicateLabel) extends SCProofStep{val premises = Seq(t1)} + /** + * <pre> + * Γ |- φ[a/h], Δ + * --------------------- + * Γ, a↔b |- φ[b/h], Δ + * </pre> + */ + case class RightSubstIff(bot: Sequent, t1: Int, fa: Formula, fb: Formula, phi: Formula, h: SchematicPredicateLabel) extends SCProofStep{val premises = Seq(t1)} + + // Proof Organisation rules + case class SCSubproof(sp: SCProof, premises: Seq[Int] = Seq.empty, display:Boolean = true) extends SCProofStep { + // premises is a list of ints similar to t1, t2... that verifies that imports of the subproof sp are justified by previous steps. + val bot: Sequent = sp.conclusion + } + +} diff --git a/src/main/scala/lisa/settheory/AxiomaticSetTheory.scala b/src/main/scala/lisa/settheory/AxiomaticSetTheory.scala new file mode 100644 index 0000000000000000000000000000000000000000..377d64b6d8611607f7c4d1382774feb1f6c12247 --- /dev/null +++ b/src/main/scala/lisa/settheory/AxiomaticSetTheory.scala @@ -0,0 +1,8 @@ +package lisa.settheory + +import lisa.kernel.proof.SCProofChecker + +object AxiomaticSetTheory extends SetTheoryTGAxioms { + + +} \ No newline at end of file diff --git a/src/main/scala/lisa/settheory/SetTheoryDefinitions.scala b/src/main/scala/lisa/settheory/SetTheoryDefinitions.scala new file mode 100644 index 0000000000000000000000000000000000000000..bf8b7bf4fe62efa8c0a0c9fe281d9e348593b34d --- /dev/null +++ b/src/main/scala/lisa/settheory/SetTheoryDefinitions.scala @@ -0,0 +1,39 @@ +package lisa.settheory + +import lisa.kernel.fol.FOL._ +import lisa.kernel.proof.RunningTheory +import lisa.KernelHelpers.{given, _} +/** + * Base trait for set theoretical axiom systems. + * Defines the symbols used in set theory. + */ +private[settheory] trait SetTheoryDefinitions{ + type Axiom = Formula + def axioms: Set[Axiom] = Set.empty + private[settheory] final val (x, y, z, a, b) = + (VariableLabel("x"), VariableLabel("y"), VariableLabel("z"), VariableLabel("A"), VariableLabel("B")) + private[settheory] final val sPhi = SchematicPredicateLabel("P", 2) + private[settheory] final val sPsi = SchematicPredicateLabel("P", 3) + // Predicates + final val in: PredicateLabel = ConstantPredicateLabel("set_membership", 2) + final val subset: PredicateLabel = ConstantPredicateLabel("subset_of", 2) + final val sim: PredicateLabel = ConstantPredicateLabel("same_cardinality", 2) // Equicardinality + final val predicates = Set(in, subset, sim) + // val application + // val pick + + // Functions + final val emptySet: FunctionLabel = ConstantFunctionLabel("empty_set", 0) + final val pair: FunctionLabel = ConstantFunctionLabel("unordered_pair", 2) + final val singleton: FunctionLabel = ConstantFunctionLabel("singleton", 1) + final val powerSet: FunctionLabel = ConstantFunctionLabel("power_set", 1) + final val union: FunctionLabel = ConstantFunctionLabel("union", 1) + final val universe: FunctionLabel = ConstantFunctionLabel("universe", 1) + final val functions = Set(emptySet, pair, singleton, powerSet, union, universe) + + val runningSetTheory:RunningTheory = new RunningTheory() + given RunningTheory = runningSetTheory + + predicates.foreach(s => runningSetTheory.addSymbol(s)) + functions.foreach(s => runningSetTheory.addSymbol(s)) +} \ No newline at end of file diff --git a/src/main/scala/lisa/settheory/SetTheoryTGAxioms.scala b/src/main/scala/lisa/settheory/SetTheoryTGAxioms.scala new file mode 100644 index 0000000000000000000000000000000000000000..c1a92a892a5097b1dac2357d11123da93acff40d --- /dev/null +++ b/src/main/scala/lisa/settheory/SetTheoryTGAxioms.scala @@ -0,0 +1,19 @@ +package lisa.settheory +import lisa.kernel.fol.FOL._ +import lisa.KernelHelpers.{given, _} +/** + * Axioms for the Tarski-Grothendieck theory (TG) + */ +private[settheory] trait SetTheoryTGAxioms extends SetTheoryZFAxioms { + + final val tarskiAxiom: Axiom = forall(x, in(x, universe(x)) /\ + forall(y, in(y, universe(x)) ==> (in(powerSet(y), universe(x)) /\ subset(powerSet(y), universe(x))) /\ + forall(z, subset(z, universe(x)) ==> (sim(y, universe(x)) /\ in(y, universe(x)))) + ) + ) + + runningSetTheory.addAxiom(tarskiAxiom) + + override def axioms: Set[Axiom] = super.axioms + tarskiAxiom + +} \ No newline at end of file diff --git a/src/main/scala/lisa/settheory/SetTheoryZAxioms.scala b/src/main/scala/lisa/settheory/SetTheoryZAxioms.scala new file mode 100644 index 0000000000000000000000000000000000000000..da30c8780d16dbeaf9dbf1acb94464a29aff1bc2 --- /dev/null +++ b/src/main/scala/lisa/settheory/SetTheoryZAxioms.scala @@ -0,0 +1,29 @@ +package lisa.settheory + +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.RunningTheory +import lisa.KernelHelpers.{given, _} +/** + * Axioms for the Zermelo theory (Z) + */ +private[settheory] trait SetTheoryZAxioms extends SetTheoryDefinitions { + + final val emptySetAxiom: Axiom = forall(x, !in(x, emptySet())) + final val extensionalityAxiom: Axiom = forall(x, forall(y, forall(z, in(z, x) <=> in(z, y)) <=> (x === y))) + final val pairAxiom: Axiom = forall(x, forall(y, forall(z, in(z, pair(x, y)) <=> (x === z) \/ (y === z)))) + final val unionAxiom: Axiom = forall(x, forall(z, in(x, union(z)) <=> exists(y, in(x, y) /\ in(y, z)))) + final val powerAxiom: Axiom = forall(x, forall(y, in(x, powerSet(y)) <=> subset(x, y))) + final val foundationAxiom: Axiom = forall(x, !(x === emptySet()) ==> exists(y, in(y, x) /\ forall(z, in(z, x) ==> !in(z, y)))) + + + final val comprehensionSchema: Axiom = forall(z, exists(y, forall(x, in(x,y) <=> (in(x,y) /\ sPhi(x,z))))) + + private val zAxioms: Set[Axiom] = Set(emptySetAxiom, extensionalityAxiom, pairAxiom, unionAxiom, powerAxiom, foundationAxiom) + + + + zAxioms.foreach(a => runningSetTheory.addAxiom(a)) + + override def axioms: Set[Axiom] = super.axioms ++ zAxioms + +} \ No newline at end of file diff --git a/src/main/scala/lisa/settheory/SetTheoryZFAxioms.scala b/src/main/scala/lisa/settheory/SetTheoryZFAxioms.scala new file mode 100644 index 0000000000000000000000000000000000000000..2b77f013d58533e80f783d7dc4dd6bce6250653e --- /dev/null +++ b/src/main/scala/lisa/settheory/SetTheoryZFAxioms.scala @@ -0,0 +1,16 @@ +package lisa.settheory + +import lisa.kernel.fol.FOL._ +import lisa.KernelHelpers.{given, _} +/** + * Axioms for the Zermelo-Fraenkel theory (ZF) + */ +private[settheory] trait SetTheoryZFAxioms extends SetTheoryZAxioms { + + final val replacementSchema: Axiom = forall(a, + forall(x, (in(x, a)) ==> existsOne(y, sPsi(a,x,y))) ==> + exists(b, forall(x, in(x, a) ==> exists(y, in(y, b) /\ sPsi(a,x,y)))) + ) + + +} \ No newline at end of file diff --git a/src/main/scala/proven/ElementsOfSetTheory.scala b/src/main/scala/proven/ElementsOfSetTheory.scala new file mode 100644 index 0000000000000000000000000000000000000000..a1bf44ded463ff24e926edbab8169cf12ee40eae --- /dev/null +++ b/src/main/scala/proven/ElementsOfSetTheory.scala @@ -0,0 +1,346 @@ +package proven +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.SequentCalculus.* +import lisa.KernelHelpers.{*, given} +import proven.tactics.ProofTactics.* +import proven.tactics.Destructors.* +import lisa.settheory.AxiomaticSetTheory.* +import scala.collection.immutable.SortedSet +import lisa.kernel.proof.SCProof + +import scala.collection.immutable + +/** + * Some proofs in set theory. See it as a proof of concept. + */ +object ElementsOfSetTheory { + + private val x = VariableLabel("x") + private val y = VariableLabel("y") + private val x1 = VariableLabel("x'") + private val y1 = VariableLabel("y'") + private val z = VariableLabel("z") + private val g = SchematicFunctionLabel("g", 0) + private val h = SchematicPredicateLabel("h", 0) + + val oPair: FunctionLabel = ConstantFunctionLabel("ordered_pair", 2) + val oPairFirstElement: FunctionLabel = ConstantFunctionLabel("ordered_pair_first_element", 1) + val oPairSecondElement: FunctionLabel = ConstantFunctionLabel("ordered_pair_second_element", 1) + + val proofUnorderedPairSymmetry: SCProof = { + val imps: IndexedSeq[Sequent] = IndexedSeq(() |- extensionalityAxiom, () |- pairAxiom) + val s0 = Rewrite(() |- extensionalityAxiom, -1) + val pairExt1 = instantiateForall(new SCProof(IndexedSeq(s0), imps), extensionalityAxiom, pair(x, y)) + val pairExt2 = instantiateForall(pairExt1, pairExt1.conclusion.right.head, pair(y, x)) + val t0 = Rewrite(() |- pairAxiom, -2) + val pairSame11 = instantiateForall(new SCProof(IndexedSeq(t0), imps), pairAxiom, x) + val pairSame12 = instantiateForall(pairSame11, pairSame11.conclusion.right.head, y) + val pairSame13 = instantiateForall(pairSame12, pairSame12.conclusion.right.head, z) + + val pairSame21 = instantiateForall(new SCProof(IndexedSeq(t0), imps), pairAxiom, y) + val pairSame22 = instantiateForall(pairSame21, pairSame21.conclusion.right.head, x) + val pairSame23 = instantiateForall(pairSame22, pairSame22.conclusion.right.head, z) + val condsymor = ((y === z) \/ (x === z)) <=> ((x === z) \/ (y === z)) + val pairSame24 = pairSame23 // + st1 + + val pr0 = SCSubproof(pairSame13, Seq(-1, -2)) + val pr1 = SCSubproof(pairSame23, Seq(-1, -2)) + val pr2 = RightSubstIff(Sequent(pr1.bot.right, Set(in(z, pair(x, y)) <=> in(z, pair(y, x)))), 0, + (x === z) \/ (y === z), + in(z, pair(y, x)), + in(z, pair(x, y)) <=> h(), + h) + val pr3 = Cut(Sequent(pr1.bot.left, pr2.bot.right), 1, 2, pr2.bot.left.head) + //val pr4 = LeftAxiom(Sequent(Set(), pr2.bot.right), 3, pr1.bot.left.head, "") + val pr4 = RightForall(Sequent(Set(), Set(forall(z, pr2.bot.right.head))), 3, pr2.bot.right.head, z) + val fin = SCProof(IndexedSeq(pr0, pr1, pr2, pr3, pr4), imps) + val fin2 = byEquiv(pairExt2.conclusion.right.head, fin.conclusion.right.head)(SCSubproof(pairExt2, Seq(-1, -2)), SCSubproof(fin, Seq(-1, -2))) + val fin3 = generalizeToForall(fin2, fin2.conclusion.right.head, x) + val fin4 = generalizeToForall(fin3, fin3.conclusion.right.head, y) + fin4.copy(imports = imps) + } // |- ∀∀({x$1,y$2}={y$2,x$1}) + + val proofUnorderedPairDeconstruction: SCProof = { + val pxy = pair(x, y) + val pxy1 = pair(x1, y1) + val zexy = (z === x) \/ (z === y) + val p0 = SCSubproof { + val p0 = SCSubproof({ + val zf = in(z, pxy) + val p1_0 = hypothesis(zf) + val p1_1 = RightImplies(emptySeq +> (zf ==> zf), 0, zf, zf) + val p1_2 = RightIff(emptySeq +> (zf <=> zf), 1, 1, zf, zf) // |- (z in {x,y} <=> z in {x,y}) + val p1_3 = RightSubstEq(emptySeq +< (pxy === pxy1) +> (zf <=> in(z, pxy1)), 2, pxy, pxy1, zf <=> in(z, g()), g) + SCProof(p1_0, p1_1, p1_2, p1_3) + }, display = false) // ({x,y}={x',y'}) |- ((z∈{x,y})↔(z∈{x',y'})) + val p1 = SCSubproof({ + val p1_0 = Rewrite(()|-pairAxiom, -1) // |- ∀∀∀((z$1∈{x$3,y$2})↔((x$3=z$1)∨(y$2=z$1))) + val p1_1 = instantiateForall(SCProof(IndexedSeq(p1_0), IndexedSeq(()|-pairAxiom)), x, y, z) + p1_1 + }, display = false) // |- (z in {x,y}) <=> (z=x \/ z=y) + val p2 = SCSubproof({ + val p2_0 = Rewrite(()|-pairAxiom, -1) // |- ∀∀∀((z$1∈{x$3,y$2})↔((x$3=z$1)∨(y$2=z$1))) + val p2_1 = instantiateForall(SCProof(IndexedSeq(p2_0), IndexedSeq(()|-pairAxiom)), x1, y1, z) + p2_1 + }, display = false) // |- (z in {x',y'}) <=> (z=x' \/ z=y') + val p3 = RightSubstEq( + emptySeq +< (pxy === pxy1) +> (in(z, pxy1) <=> ((z === x) \/ (z === y))), 1, pxy, pxy1, in(z, g()) <=> ((z === x) \/ (z === y)), g + ) // ({x,y}={x',y'}) |- ((z∈{x',y'})↔((z=x)∨(z=y))) + val p4 = RightSubstIff( + emptySeq +< p3.bot.left.head +< p2.bot.right.head +> (((z === x) \/ (z === y)) <=> ((z === x1) \/ (z === y1))), + 3, + (z === x1) \/ (z === y1), + in(z, pxy1), + h() <=> ((z === x) \/ (z === y)), + h + ) // ((z∈{x',y'})↔((x'=z)∨(y'=z))), ({x,y}={x',y'}) |- (((z=x)∨(z=y))↔((z=x')∨(z=y'))) + val p5 = Cut(emptySeq ++< p3.bot ++> p4.bot, 2, 4, p2.bot.right.head) + SCProof(p0, p1, p2, p3, p4, p5) + } // ({x,y}={x',y'}) |- (((z=x)∨(z=y))↔((z=x')∨(z=y'))) + + val p1 = SCSubproof( + SCProof(byCase(x === x1)( + SCSubproof({ + val pcm1 = p0 + val pc0 = SCSubproof(SCProof(byCase(y === x)( + SCSubproof({ + val pam1 = pcm1 + val pa0 = SCSubproof({ + val f1 = z === x + val pa0_m1 = pcm1 // ({x,y}={x',y'}) |- (((z=x)∨(z=y))↔((z=x')∨(z=y'))) + val pa0_0 = SCSubproof({ + val pa0_0_0 = hypothesis(f1) + val pa0_1_1 = RightOr(emptySeq +< f1 +> (f1 \/ f1), 0, f1, f1) + val pa0_1_2 = RightImplies(emptySeq +> (f1 ==> (f1 \/ f1)), 1, f1, f1 \/ f1) + val pa0_1_3 = LeftOr(emptySeq +< (f1 \/ f1) +> f1, Seq(0,0), Seq(f1, f1)) + val pa0_1_4 = RightImplies(emptySeq +> ((f1 \/ f1) ==> f1), 3, f1 \/ f1, f1) + val pa0_1_5 = RightIff(emptySeq +> ((f1 \/ f1) <=> f1), 2, 4, (f1 \/ f1), f1) + val r = SCProof(pa0_0_0, pa0_1_1, pa0_1_2, pa0_1_3, pa0_1_4, pa0_1_5) + r + }, display = false) // |- (((z=x)∨(z=x))↔(z=x)) + val pa0_1 = RightSubstEq(emptySeq +< (pxy === pxy1) +< (x === y) +> ((f1 \/ f1) <=> (z === x1) \/ (z === y1)), -1, x, y, (f1 \/ (z === g())) <=> ((z === x1) \/ (z === y1)), g) // ({x,y}={x',y'}) y=x|- (z=x)\/(z=x) <=> (z=x' \/ z=y') + val pa0_2 = RightSubstIff(emptySeq +< (pxy === pxy1) +< (x === y) +< (f1 <=> (f1 \/ f1)) +> (f1 <=> ((z === x1) \/ (z === y1))), 1, f1, f1 \/ f1, h() <=> ((z === x1) \/ (z === y1)), h) + val pa0_3 = Cut(emptySeq +< (pxy === pxy1) +< (x === y) +> (f1 <=> ((z === x1) \/ (z === y1))), 0, 2, f1 <=> (f1 \/ f1)) // (x=y), ({x,y}={x',y'}) |- ((z=x)↔((z=x')∨(z=y'))) + val pa0_4 = RightForall(emptySeq +< (pxy === pxy1) +< (x === y) +> forall(z, f1 <=> ((z === x1) \/ (z === y1))), 3, f1 <=> ((z === x1) \/ (z === y1)), z) + val ra0_0 = instantiateForall(SCProof(IndexedSeq(pa0_0, pa0_1, pa0_2, pa0_3, pa0_4), IndexedSeq(pa0_m1.bot)), y1) // (x=y), ({x,y}={x',y'}) |- ((y'=x)↔((y'=x')∨(y'=y'))) + ra0_0 + }, IndexedSeq(-1)) // ({x,y}={x',y'}) y=x|- ((y'=x)↔((y'=x')∨(y'=y'))) + val pa1 = SCSubproof({ + val pa1_0 = RightRefl(emptySeq +> (y1 === y1), y1 === y1) + val pa1_1 = RightOr(emptySeq +> ((y1 === y1) \/ (y1 === x1)), 0, y1 === y1, y1 === x1) + SCProof(pa1_0, pa1_1) + }, display = false) // |- (y'=x' \/ y'=y') + val ra3 = byEquiv(pa0.bot.right.head, pa1.bot.right.head)(pa0, pa1) // ({x,y}={x',y'}) y=x|- ((y'=x) + val pal = RightSubstEq(emptySeq ++< pa0.bot +> (y1 === y), ra3.length - 1, x, y, y1 === g(), g) + SCProof(ra3.steps, IndexedSeq(pam1.bot)) appended pal // (x=y), ({x,y}={x',y'}) |- (y'=y) + }, IndexedSeq(-1)) // (x=y), ({x,y}={x',y'}) |- (y'=y) + , + SCSubproof({ + val pbm1 = pcm1 // ({x,y}={x',y'}) |- (((z=x)∨(z=y))↔((z=x')∨(z=y'))) + val pb0_0 = SCSubproof({ + val pb0_0 = RightForall(emptySeq ++< pcm1.bot +> forall(z, pcm1.bot.right.head), -1, pcm1.bot.right.head, z) + instantiateForall(SCProof(IndexedSeq(pb0_0), IndexedSeq(pcm1.bot)), y) + }, IndexedSeq(-1)) // ({x,y}={x',y'}) |- (((y=x)∨(y=y))↔((y=x')∨(y=y'))) + val pb0_1 = SCSubproof({ + val pa1_0 = RightRefl(emptySeq +> (y === y), y === y) + val pa1_1 = RightOr(emptySeq +> ((y === y) \/ (y === x)), 0, y === y, y === x) + SCProof(pa1_0, pa1_1) + }, display = false) // |- (y=x)∨(y=y) + val rb0 = byEquiv(pb0_0.bot.right.head, pb0_1.bot.right.head)(pb0_0, pb0_1) // ({x,y}={x',y'}) |- (y=x')∨(y=y') + val pb1 = RightSubstEq(emptySeq ++< rb0.conclusion +< (x === x1) +> ((y === x) \/ (y === y1)), rb0.length - 1, x, x1, (y === g()) \/ (y === y1), g) + val rb1 = destructRightOr( + rb0 appended pb1, // ({x,y}={x',y'}) , x=x'|- (y=x)∨(y=y') + y === x, y === y1 + ) + val rb2 = rb1 appended LeftNot(rb1.conclusion +< !(y === x) -> (y === x), rb1.length - 1, y === x) // (x=x'), ({x,y}={x',y'}), ¬(y=x) |- (y=y') + SCProof(rb2.steps, IndexedSeq(pbm1.bot)) + + }, IndexedSeq(-1)) // ({x,y}={x',y'}), x=x', !y=x |- y=y' + ).steps, IndexedSeq(pcm1.bot)), IndexedSeq(-1)) // (x=x'), ({x,y}={x',y'}) |- (y'=y) + val pc1 = RightRefl(emptySeq +> (x === x), x === x) + val pc2 = RightAnd(emptySeq ++< pc0.bot +> ((y1 === y) /\ (x === x)), Seq(0, 1), Seq(y1 === y, x === x)) // ({x,y}={x',y'}), x=x' |- (x=x /\ y=y') + val pc3 = RightSubstEq(emptySeq ++< pc2.bot +> ((y1 === y) /\ (x1 === x)), 2, x, x1, (y1 === y) /\ (g() === x), g) // ({x,y}={x',y'}), x=x' |- (x=x' /\ y=y') + val pc4 = RightOr(emptySeq ++< pc3.bot +> (pc3.bot.right.head \/ ((x === y1) /\ (y === x1))), 3, pc3.bot.right.head, (x === y1) /\ (y === x1)) // ({x,y}={x',y'}), x=x' |- (x=x' /\ y=y')\/(x=y' /\ y=x') + val r = SCProof(IndexedSeq(pc0, pc1, pc2, pc3, pc4), IndexedSeq(pcm1.bot)) + r + }, IndexedSeq(-1)) // ({x,y}={x',y'}), x=x' |- (x=x' /\ y=y')\/(x=y' /\ y=x') + , + SCSubproof({ + val pdm1 = p0 + val pd0 = SCSubproof({ + val pd0_m1 = pdm1 + val pd0_0 = SCSubproof { + val ex1x1 = x1 === x1 + val pd0_0_0 = RightRefl(emptySeq +> ex1x1, ex1x1) // |- x'=x' + val pd0_0_1 = RightOr(emptySeq +> (ex1x1 \/ (x1 === y1)), 0, ex1x1, x1 === y1) // |- (x'=x' \/ x'=y') + SCProof(IndexedSeq(pd0_0_0, pd0_0_1)) + } // |- (x'=x' \/ x'=y') + val pd0_1 = SCSubproof({ + val pd0_1_m1 = pd0_m1 // ({x,y}={x',y'}) |- (((z=x)∨(z=y))↔((z=x')∨(z=y'))) + val pd0_1_0 = RightForall(emptySeq ++< pd0_1_m1.bot +> forall(z, pd0_1_m1.bot.right.head), -1, pd0_1_m1.bot.right.head, z) + val rd0_1_1 = instantiateForall(SCProof(IndexedSeq(pd0_1_0), IndexedSeq(pd0_m1.bot)), x1) // ({x,y}={x',y'}) |- (x'=x \/ x'=y) <=> (x'=x' \/ x'=y') + rd0_1_1 + }, IndexedSeq(-1)) // ({x,y}={x',y'}) |- (x'=x \/ x'=y) <=> (x'=x' \/ x'=y') + val rd0_2 = byEquiv(pd0_1.bot.right.head, pd0_0.bot.right.head)(pd0_1, pd0_0) + val pd0_3 = SCSubproof(SCProof(rd0_2.steps, IndexedSeq(pd0_m1.bot)), IndexedSeq(-1)) // ({x,y}={x',y'}) |- (x=x' \/ y=x') + destructRightOr(SCProof(IndexedSeq(pd0_0, pd0_1, pd0_3), IndexedSeq(pd0_m1.bot)), x === x1, y === x1) // ({x,y}={x',y'}) |- x=x', y=x' + }, IndexedSeq(-1)) // ({x,y}={x',y'}) |- x=x', y=x' -- + val pd1 = SCSubproof({ + val pd1_m1 = pdm1 + val pd1_0 = SCSubproof { + val exx = x === x + val pd1_0_0 = RightRefl(emptySeq +> exx, exx) // |- x=x + val pd1_0_1 = RightOr(emptySeq +> (exx \/ (x === y)), 0, exx, x === y) // |- (x=x \/ x=y) + SCProof(IndexedSeq(pd1_0_0, pd1_0_1)) + } // |- (x=x \/ x=y) + val pd1_1 = SCSubproof({ + val pd1_1_m1 = pd1_m1 // ({x,y}={x',y'}) |- (((z=x)∨(z=y))↔((z=x')∨(z=y'))) + val pd1_1_0 = RightForall(emptySeq ++< pd1_1_m1.bot +> forall(z, pd1_1_m1.bot.right.head), -1, pd1_1_m1.bot.right.head, z) + val rd1_1_1 = instantiateForall(SCProof(IndexedSeq(pd1_1_0), IndexedSeq(pd1_m1.bot)), x) // ({x,y}={x',y'}) |- (x=x \/ x=y) <=> (x=x' \/ x=y') + rd1_1_1 + }, IndexedSeq(-1)) // // ({x,y}={x',y'}) |- (x=x \/ x=y) <=> (x=x' \/ x=y') + val rd1_2 = byEquiv(pd1_1.bot.right.head, pd1_0.bot.right.head)(pd1_1, pd1_0) + val pd1_3 = SCSubproof(SCProof(rd1_2.steps, IndexedSeq(pd1_m1.bot)), IndexedSeq(-1)) // // ({x,y}={x',y'}) |- x=x' \/ x=y' + destructRightOr(SCProof(IndexedSeq(pd1_0, pd1_1, pd1_3), IndexedSeq(pd1_m1.bot)), x === x1, x === y1) // ({x,y}={x',y'}) |- x=x', x=y' + }, IndexedSeq(-1))// ({x,y}={x',y'}) |- x=x', x=y' -- + val pd2 = RightAnd(emptySeq ++< pd1.bot +> (x === x1) +> ((x === y1) /\ (y === x1)), Seq(0, 1), Seq(x === y1, y === x1)) // ({x,y}={x',y'}) |- x=x', (x=y' /\ y=x') --- + val pd3 = LeftNot(emptySeq ++< pd2.bot +< !(x === x1) +> ((x === y1) /\ (y === x1)), 2, x === x1) // ({x,y}={x',y'}), !x===x1 |- (x=y' /\ y=x') + val pd4 = RightOr(emptySeq ++< pd3.bot +> (pd3.bot.right.head \/ ((x === x1) /\ (y === y1))), 3, pd3.bot.right.head, (x === x1) /\ (y === y1)) // ({x,y}={x',y'}), !x===x1 |- (x=x' /\ y=y')\/(x=y' /\ y=x') + SCProof(IndexedSeq(pd0, pd1, pd2, pd3, pd4), IndexedSeq(pdm1.bot)) + }, IndexedSeq(-1)) // ({x,y}={x',y'}), !x=x' |- (x=x' /\ y=y')\/(x=y' /\ y=x') + ).steps, IndexedSeq(p0.bot) + ), IndexedSeq(0) + ) // ({x,y}={x',y'}) |- (x=x' /\ y=y')\/(x=y' /\ y=x') + + val p2 = RightImplies(emptySeq +> (p1.bot.left.head ==> p1.bot.right.head), 1, p1.bot.left.head, p1.bot.right.head) // |- ({x,y}={x',y'}) ==> (x=x' /\ y=y')\/(x=y' /\ y=x') + generalizeToForall(SCProof(p0, p1, p2), x, y, x1, y1) + } // |- ∀∀∀∀(({x$4,y$3}={x'$2,y'$1})⇒(((y'$1=y$3)∧(x'$2=x$4))∨((x$4=y'$1)∧(y$3=x'$2)))) + + + // i2, i1, p0, p1, p2, p3 + + val orderedPairDefinition: SCProof = simpleFunctionDefinition(oPair, pair(pair(x, y), pair(x, x)), Seq(x, y)) + +/* + val proofOrderedPairDeconstruction: SCProof = { + + val opairxy = pair(pair(x, y), pair(x, x)) + val opairxx = pair(pair(x, x), pair(x, x)) + val opairxy1 = pair(pair(x1, y1), pair(x1, x1)) + val opairxx1 = pair(pair(x1, x1), pair(x1, x1)) + val pairxy = pair(x, y) + val pairxx = pair(x, x) + val pairxy1 = pair(x1, y1) + val pairxx1 = pair(x1, x1) + + + val p0 = SCSubproof(orderedPairDefinition, display = false) + val p1 = SCSubproof(proofUnorderedPairDeconstruction) // |- ∀∀∀∀(({x$4,y$3}={x'$2,y'$1})⇒(((y'$1=y$3)∧(x'$2=x$4))∨((x$4=y'$1)∧(y$3=x'$2)))) + + val p2: SCSubproof = SCSubproof(SCProof(byCase(x === y)( + { + val p2_0 = SCSubproof(SCProof({ + val pa2_0 = SCSubproof({ + val p2_0_0 = UseFunctionDefinition(emptySeq +> (oPair(x, y) === opairxy), oPair, Seq(x, y)) // |- (x, y) === {{x,y}, {x,x}} + val p2_0_1 = RightSubstEq(emptySeq +< (x === y) +> (oPair(x, y) === opairxx), + 0, x, y, oPair(x, y) === pair(pair(x, z), pair(x, x)), z) // (x=y) |- ((x,y)={{x,x},{x,x}}) + val p2_0_2 = UseFunctionDefinition(emptySeq +> (oPair(x1, y1) === opairxy1), oPair, Seq(x1, y1)) // |- (x1, y1) === {{x1,y1}, {x1,x1}} + val p2_0_3 = RightSubstEq(emptySeq +< (oPair(x, y) === oPair(x1, y1)) +< (x === y) +> (oPair(x1, y1) === pair(pair(x, x), pair(x, x))), + 1, oPair(x, y), oPair(x1, y1), z === pair(pair(x, x), pair(x, x)), z) // (x=y), (x1,y1)=(x,y) |- ((x1,y1)={{x,x},{x,x}}) + val p2_0_4 = RightSubstEq(emptySeq +< (oPair(x, y) === oPair(x1, y1)) +< (x === y) +< (opairxy1 === oPair(x1, y1)) +> (pair(pair(x, x), pair(x, x)) === pair(pair(x1, y1), pair(x1, x1))), + 3, opairxy1, oPair(x1, y1), z === pair(pair(x, x), pair(x, x)), z) // (x=y), (x1,y1)=(x,y), (x1,y1)={{x1,y1}, {x1,x1}} |- {{x1,y1}, {x1,x1}}={{x,x},{x,x}}) + val p2_0_5 = Cut(emptySeq +< (oPair(x, y) === oPair(x1, y1)) +< (x === y) +> (pair(pair(x, x), pair(x, x)) === pair(pair(x1, y1), pair(x1, x1))), + 2, 4, opairxy1 === oPair(x1, y1)) + SCProof(IndexedSeq(p2_0_0, p2_0_1, p2_0_2, p2_0_3, p2_0_4, p2_0_5), IndexedSeq(p0)) + }, IndexedSeq(-1)) // ((x,y)=(x',y')), (x=y) |- ({{x,x},{x,x}}={{x',y'},{x',x'}}) + val pa2_1 = SCSubproof({ + instantiateForall(SCProof(IndexedSeq(), IndexedSeq(p1)), pairxx, pairxx, pairxy1, pairxx1) + }, IndexedSeq(-2)) // ({{x,x},{x,x}}={{x',y'},{x',x'}})⇒((({x',x'}={x,x})∧({x',y'}={x,x}))∨(({x,x}={x',x'})∧({x,x}={x',y'})))) + val pr2_0 = modusPonens(pa2_0.bot.right.head)(pa2_0, pa2_1) // ((x,y)=(x',y')), (x=y) |- ((({x',x'}={x,x})∧({x',y'}={x,x}))∨(({x,x}={x',x'})∧({x,x}={x',y'}))) + val f = (pairxx === pairxx1) /\ (pairxx === pairxy1) + destructRightAnd(destructRightOr(pr2_0, f, f), pairxx === pairxy1, pairxx === pairxx1) // ((x,y)=(x',y')), (x=y) |- {x',y'}={x,x} + }.steps, IndexedSeq(p0, p1)), IndexedSeq(-1, -2)) // ((x,y)=(x',y')), (x=y) |- {x',y'}={x,x} + val p2_1 = SCSubproof({ + val pr2_1_0 = instantiateForall(SCProof(IndexedSeq(), IndexedSeq(p1)), x, x, x1, y1) // (({x,x}={x',y'})⇒(((y'=x)∧(x'=x))∨((x=y')∧(x=x')))) + val f = (y1 === x) /\ (x1 === x) + val pr2_1_1 = destructRightImplies(pr2_1_0, pairxx === pairxy1, f \/ f) // (({x,x}={x',y'}) |- (((y'=x)∧(x'=x))∨((x=y')∧(x=x')))) + val pr2_1_2 = destructRightOr(pr2_1_1, f, f) + pr2_1_2 + }, IndexedSeq(-2)) // {x',y'}={x,x}, x=y |- x=x' /\ x=y' + val p2_2 = Cut(emptySeq ++< p2_0.bot ++> p2_1.bot, 0, 1, p2_0.bot.right.head) // ((x,y)=(x',y')), x=y |- x=x' /\ x=y' + val p2_3 = RightSubstEq(emptySeq +< (oPair(x, y) === oPair(x1, y1)) +< (x === y) +> ((x === x1) /\ (y === y1)), 2, x, y, (x === x1) /\ (z === y1), z) + + SCSubproof(SCProof(IndexedSeq(p2_0, p2_1, p2_2, p2_3), IndexedSeq(p0, p1)), IndexedSeq(-1, -2)) + } // ((x,y)=(x',y')), x=y |- x=x' /\ y=y' + , + { + val pa2_0: SCSubproof = SCSubproof({ + val p2_0_0 = UseFunctionDefinition(emptySeq +> (oPair(x, y) === opairxy), oPair, Seq(x, y)) // |- (x, y) === {{x,y}, {x,x}} + val p2_0_1 = UseFunctionDefinition(emptySeq +> (oPair(x1, y1) === opairxy1), oPair, Seq(x1, y1)) // |- (x1, y1) === {{x1,y1}, {x1,x1}} + val p2_0_2 = RightSubstEq(emptySeq +< (oPair(x, y) === oPair(x1, y1)) +> (oPair(x1, y1) === pair(pair(x, y), pair(x, x))), + 0, oPair(x, y), oPair(x1, y1), z === pair(pair(x, y), pair(x, x)), z) // (x1,y1)=(x,y) |- ((x1,y1)={{x,y},{x,x}}) + + val p2_0_3 = RightSubstEq(emptySeq +< (oPair(x, y) === oPair(x1, y1)) +< (opairxy1 === oPair(x1, y1)) +> (pair(pair(x, y), pair(x, x)) === pair(pair(x1, y1), pair(x1, x1))), + 2, opairxy1, oPair(x1, y1), z === pair(pair(x, y), pair(x, x)), z) // (x1,y1)=(x,y), (x1,y1)={{x1,y1}, {x1,x1}} |- {{x1,y1}, {x1,x1}}={{x,y},{x,x}}) + + val p2_0_4 = Cut(emptySeq +< (oPair(x, y) === oPair(x1, y1)) +> (pair(pair(x, y), pair(x, x)) === pair(pair(x1, y1), pair(x1, x1))), + 1, 3, opairxy1 === oPair(x1, y1)) + SCProof(IndexedSeq(p2_0_0, p2_0_1, p2_0_2, p2_0_3, p2_0_4), IndexedSeq(p0)) + }, IndexedSeq(-1)) // ((x,y)=(x',y')) |- ({{x,y},{x,x}}={{x',y'},{x',x'}}) + val pa2_1 = SCSubproof({ + instantiateForall(SCProof(IndexedSeq(), IndexedSeq(p1)), pairxy, pairxx, pairxy1, pairxx1) + }, IndexedSeq(-2)) // ({{x,y},{x,x}}={{x',y'},{x',x'}}) => ((({x',x'}={x,y})∧({x',y'}={x,x}))∨(({x,x}={x',x'})∧({x,y}={x',y'}))) + val pr2_0 = SCProof(modusPonens(pa2_0.bot.right.head)(pa2_0, pa2_1).steps, IndexedSeq(p0, p1)) // ((x,y)=(x',y')) |- (({x',x'}={x,y})∧({x',y'}={x,x}))∨(({x,x}={x',x'})∧({x,y}={x',y'})) + val (f, g) = pr2_0.conclusion.right.head match { + case BinaryConnectorFormula(`or`, l, r) => (l, r) + } + val pr2_1: SCProof = destructRightOr(pr2_0, f, g) // ((x,y)=(x',y')) |- (({x',x'}={x,y})∧({x',y'}={x,x})), (({x,x}={x',x'})∧({x,y}={x',y'})) + + val pb2_0 = SCSubproof({ + val prb2_0_0 = instantiateForall(SCProof(IndexedSeq(), IndexedSeq(p1)), x, y, x1, x1) // |- (({x,y}={x',x'}) ⇒ (((x'=y)∧(x'=x))∨((x=x')∧(y=x')))) + val f = (x1 === y) /\ (x1 === x) + val prb2_0_1 = destructRightImplies(prb2_0_0, pairxy === pairxx1, f \/ f) // (({x,y}={x',x'}) |- ((x'=y)∧(x'=x))∨((x=x')∧(y=x'))) + val pb3_0_0 = SCSubproof(destructRightOr(prb2_0_1, f, f), IndexedSeq(-1)) // (({x,y}={x',x'}) |- (x'=y)∧(x'=x) + val pb3_0_1 = SCSubproof(destructRightAnd(SCProof(IndexedSeq(), IndexedSeq(pb3_0_0)), x1 === y, x1 === x), IndexedSeq(0)) // (({x,y}={x',x'}) |- x'=y + val pb3_0_2 = SCSubproof(destructRightAnd(SCProof(IndexedSeq(), IndexedSeq(pb3_0_0)), x1 === x, x1 === y), IndexedSeq(0)) // (({x,y}={x',x'}) |- x'=x + val pb3_0_3 = RightSubstEq(emptySeq +< (pairxy === pairxx1) +< (x1 === y) +> (y === x), 2, x1, y, z === x, z) // (({x,y}={x',x'}), x'=y |- y=x + val pb3_0_4 = Cut(emptySeq +< (pairxy === pairxx1) +> (y === x), 1, 3, x1 === y) // {x',x'}={x,y} |- x=y + SCProof(IndexedSeq(pb3_0_0, pb3_0_1, pb3_0_2, pb3_0_3, pb3_0_4), IndexedSeq(p1)) + }, IndexedSeq(-2)) // {x',x'}={x,y} |- x=y + val pb2_1 = SCSubproof(destructRightAnd(pr2_1, pairxx1 === pairxy, pairxx === pairxy1), IndexedSeq(-1, -2)) // ((x,y)=(x',y')) |- (({x',x'}={x,y}), (({x,x}={x',x'})∧({x,y}={x',y'})) + + val pb2_2 = Cut(pb2_1.bot -> pb2_0.bot.left.head +> (x === y), 1, 0, pb2_0.bot.left.head) // ((x,y)=(x',y')) |- x=y, (({x,x}={x',x'})∧({x,y}={x',y'})) + val pc2_0 = SCSubproof(SCProof(IndexedSeq(pb2_0, pb2_1, pb2_2), IndexedSeq(p0, p1)), IndexedSeq(-1, -2)) // ((x,y)=(x',y')) |- x=y, (({x,x}={x',x'})∧({x,y}={x',y'})) + + val pc2_1 = SCSubproof({ + val pc2_1_0 = SCSubproof(destructRightAnd(SCProof(IndexedSeq(), IndexedSeq(pc2_0)), pairxy === pairxy1, pairxx === pairxx1), IndexedSeq(-2)) // ((x,y)=(x',y')) |- x=y, {x,y}={x',y'} + val pc2_1_1 = SCSubproof(instantiateForall(SCProof(IndexedSeq(), IndexedSeq(p1)), x, y, x1, y1), IndexedSeq(-1)) // (({x,y}={x',y'})⇒(((y'=y)∧(x'=x))∨((x=y')∧(y=x')))) + val prc2_1_2 = modusPonens(pairxy === pairxy1)(pc2_1_0, pc2_1_1) // ((x,y)=(x',y')) |- x=y, (((y'=y)∧(x'=x))∨((x=y')∧(y=x')))) + val f = (y1 === y) /\ (x1 === x) + val g = (y1 === x) /\ (x1 === y) + val prc2_1_3 = destructRightOr(prc2_1_2, f, g) // ((x,y)=(x',y')) |- x=y, ((y'=x)∧(x'=y)), ((y=y')∧(x=x'))) + val prc2_1_4 = destructRightAnd(prc2_1_3, x1 === y, y1 === x) // ((x,y)=(x',y')) |- x=y, (x'=y), ((y=y')∧(x=x'))) + SCProof(prc2_1_4.steps, IndexedSeq(p1, pc2_0)) + }, IndexedSeq(-2, 0)) // ((x,y)=(x',y')) |- x=y, (x'=y), ((y=y')∧(x=x'))) + val pc2_2 = SCSubproof({ + val pc2_2_0 = SCSubproof(destructRightAnd(SCProof(IndexedSeq(), IndexedSeq(pc2_0)), pairxx === pairxx1, pairxy === pairxy1), IndexedSeq(-2)) // ((x,y)=(x',y')) |- x=y, {x,x}={x',x'} + val pc2_2_1 = SCSubproof(instantiateForall(SCProof(IndexedSeq(), IndexedSeq(p1)), x, x, x1, x1), IndexedSeq(-1)) // (({x,x}={x',x'})⇒(((x'=x)∧(x'=x))∨((x=x')∧(x=x')))) + val prc2_2_2 = modusPonens(pairxx === pairxx1)(pc2_2_0, pc2_2_1) // ((x,y)=(x',y')) |- x=y, (((x'=x)∧(x'=x))∨((x=x')∧(x=x')))) + val f = (x === x1) /\ (x1 === x) + val prc2_2_3 = destructRightOr(prc2_2_2, f, f) // ((x,y)=(x',y')) |- x=y, ((x'=x)∧(x'=x)) + val prc2_2_4 = destructRightAnd(prc2_2_3, x === x1, x === x1) // ((x,y)=(x',y')) |- x=y, x'=x + SCProof(prc2_2_4.steps, IndexedSeq(p1, pc2_0)) + }, IndexedSeq(-2, 0)) // ((x,y)=(x',y')) |- x=y, x'=x + + val pc2_3 = RightSubstEq(pc2_1.bot -> (x1 === y) +> (x === y) +< (x1 === x), 1, x, x1, z === y, z) // ((x,y)=(x',y')), x=x' |- x=y, ((y=y')∧(x=x'))) + val pc2_4 = Cut(pc2_3.bot -< (x === x1), 2, 3, x === x1) // ((x,y)=(x',y')) |- x=y, ((y=y')∧(x=x'))) + val pc2_5 = LeftNot(pc2_4.bot +< !(x === y) -> (x === y), 4, x === y) // ((x,y)=(x',y')), !x=y |- ((y=y')∧(x=x'))) + SCSubproof(SCProof(IndexedSeq(pc2_0, pc2_1, pc2_2, pc2_3, pc2_4, pc2_5), IndexedSeq(p0, p1)), IndexedSeq(-1, -2)) + } // ((x,y)=(x',y')), !x=y |- x=x' /\ y=y' + ).steps, IndexedSeq(p0, p1)), IndexedSeq(0, 1)) // ((x,y)=(x',y')) |- x=x' /\ y=y' + SCProof(p0, p1, p2) + + ??? + } // |- (x,y)=(x', y') ===> x=x' /\ y=y' +*/ +} diff --git a/src/main/scala/proven/tactics/Destructors.scala b/src/main/scala/proven/tactics/Destructors.scala new file mode 100644 index 0000000000000000000000000000000000000000..12c080e88a76dbc8b29650611122900c1e9ca9ea --- /dev/null +++ b/src/main/scala/proven/tactics/Destructors.scala @@ -0,0 +1,31 @@ +package proven.tactics + +import lisa.kernel.proof.SequentCalculus.* +import lisa.kernel.fol.FOL.* +import lisa.KernelHelpers.* +import proven.tactics.ProofTactics.hypothesis +import lisa.kernel.proof.SCProof + +object Destructors { + def destructRightOr(p: SCProof, a: Formula, b: Formula): SCProof = { + require(contains(p.conclusion.right, a \/ b)) + val p0 = hypothesis(a) + val p1 = hypothesis(b) + val p2 = LeftOr(emptySeq +< (a \/ b) +> a +> b, Seq(p.length, p.length + 1), Seq(a, b)) + val p3 = Cut(p.conclusion -> (a \/ b) +> a +> b, p.length - 1, p.length + 2, a \/ b) + p withNewSteps IndexedSeq(p0, p1, p2, p3) + } + def destructRightAnd(p: SCProof, f: Formula, g: Formula): SCProof = { + val p0 = hypothesis(f) // n + val p1 = LeftAnd(emptySeq +< (f /\ g) +> f, p.length, f, g) // n+1 + val p2 = Cut(p.conclusion -> (f /\ g) +> f, p.length - 1, p.length + 1, f /\ g) + p withNewSteps IndexedSeq(p0, p1, p2) + } + def destructRightImplies(p: SCProof, f: Formula, g: Formula): SCProof = { // |- f=>g + val p0 = hypothesis(f) // f |- f + val p1 = hypothesis(g) // g |- g + val p2 = LeftImplies(emptySeq +< f +< (f ==> g) +> g, p.length, p.length + 1, f, g) // f, f=>g |- g + val p3 = Cut(p.conclusion -> (f ==> g) +< f +> g, p.length - 1, p.length + 2, f ==> g) // f|- g + p withNewSteps IndexedSeq(p0, p1, p2, p3) + } +} diff --git a/src/main/scala/proven/tactics/ProofTactics.scala b/src/main/scala/proven/tactics/ProofTactics.scala new file mode 100644 index 0000000000000000000000000000000000000000..85c576fc716c2b8d25dcabfb14bb22f54eb81590 --- /dev/null +++ b/src/main/scala/proven/tactics/ProofTactics.scala @@ -0,0 +1,156 @@ +package proven.tactics + +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.SequentCalculus.* +import lisa.KernelHelpers.{*, given} +import lisa.kernel.Printer.* + +import scala.collection.immutable.Set +import lisa.kernel.proof.SCProof + +/** + * SCProof tactics are a set of strategies that help the user write proofs in a more expressive way + * by focusing on the final goal rather on the individual steps. + */ +object ProofTactics { + + def hypothesis(f: Formula): SCProofStep = Hypothesis(emptySeq +< f +> f, f) + + def instantiateForall(p: SCProof, phi: Formula, t: Term): SCProof = { // given a proof with a formula quantified with \forall on the right, extend the proof to the same formula with something instantiated instead. + require(p.conclusion.right.contains(phi)) + phi match { + case b @ BinderFormula(Forall, _, _) => + val c = p.conclusion + val tempVar = VariableLabel(freshId(b.freeVariables.map(_.id), "x")) + val in = instantiateBinder(b, t) + val p1 = Hypothesis(Sequent(Set(in), Set(in)), in) + val p2 = LeftForall(Sequent(Set(b), Set(in)), p.length, instantiateBinder(b, tempVar), tempVar, t) + val p3 = Cut(Sequent(c.left, c.right - phi + in), p.length - 1, p.length + 1, phi) + p withNewSteps IndexedSeq(p1, p2, p3) + case _ => throw new Exception("not a forall") + } + } + def instantiateForall(p: SCProof, phi: Formula, t: Term*): SCProof = { // given a proof with a formula quantified with \forall on the right, extend the proof to the same formula with something instantiated instead. + t.foldLeft((p, phi)) { case ((p, f), t1) => (instantiateForall(p, f, t1), f match { + case b @ BinderFormula(Forall, _, _) => instantiateBinder(b, t1) + case _ => throw new Exception + }) }._1 + } + def instantiateForall(p: SCProof, t: Term): SCProof = instantiateForall(p, p.conclusion.right.head, t) // if a single formula on the right + def instantiateForall(p: SCProof, t: Term*): SCProof = { // given a proof with a formula quantified with \forall on the right, extend the proof to the same formula with something instantiated instead. + t.foldLeft(p)((p1, t1) => instantiateForall(p1, t1)) + } + def generalizeToForall(p: SCProof, phi: Formula, x: VariableLabel): SCProof = { + require(p.conclusion.right.contains(phi)) + val p1 = RightForall(p.conclusion -> phi +> forall(x, phi), p.length - 1, phi, x) + p appended p1 + } + def generalizeToForall(p: SCProof, x: VariableLabel): SCProof = generalizeToForall(p, p.conclusion.right.head, x) + def generalizeToForall(p: SCProof, x: VariableLabel*): SCProof = { // given a proof with a formula on the right, extend the proof to the same formula with variables universally quantified. + x.foldRight(p)((x1, p1) => generalizeToForall(p1, x1)) + } + def byEquiv(f: Formula, f1: Formula)(pEq: SCProofStep, pr1: SCProofStep): SCProof = { + require(pEq.bot.right.contains(f)) + require(pr1.bot.right.contains(f1)) + f match { + case ConnectorFormula(Iff, Seq(fl, fr)) => + val f2 = if (isSame(f1, fl)) fr else if (isSame(f1, fr)) fl else throw new Error("not applicable") + val p2 = hypothesis(f1) + val p3 = hypothesis(f2) + val p4 = LeftImplies(Sequent(Set(f1, f1 ==> f2), Set(f2)), 2, 3, f1, f2) + val p5 = LeftIff(Sequent(Set(f1, f1 <=> f2), Set(f2)), 4, f1, f2) + val p6 = Cut(pEq.bot -> (f1 <=> f2) +< f1 +> f2, 0, 5, f1 <=> f2) + val p7 = Cut(p6.bot -< f1 ++ pr1.bot -> f1, 1, 6, f1) + new SCProof(IndexedSeq(pEq, pr1, p2, p3, p4, p5, p6, p7)) + case _ => throw new Error("not applicable") + } + } + def simpleFunctionDefinition(f: FunctionLabel, t: Term, args: Seq[VariableLabel]): SCProof = { + assert(t.freeVariables subsetOf args.toSet) + val x = VariableLabel(freshId(t.freeVariables.map(_.id), "x")) + val y = VariableLabel(freshId(t.freeVariables.map(_.id)+x.id, "x")) + val p0 = RightRefl(emptySeq +> (t === t), t === t) // |- t===t + val p1 = hypothesis(y === t) // (t===y)|-(t===y) + val p2 = RightImplies(emptySeq +> ((t === y) ==> (t === y)), 1, t === y, t === y) // |- (t===y)==>(t===y) + val p3 = RightForall(emptySeq +> forall(y, (t === y) ==> (t === y)), 2, p2.bot.right.head, y) // |- ∀y (t===y)==>(t===y) + val p4 = RightAnd(emptySeq +> p0.bot.right.head /\ p3.bot.right.head, Seq(0, 3), Seq(p0.bot.right.head, p3.bot.right.head)) // |- t===t /\ ∀y(t===y)==>(t===y) + val p5 = RightExists(emptySeq +> exists(x, (x === t) /\ forall(y, (t === y) ==> (x === y))), 4, + (x === t) /\ forall(y, (t === y) ==> (x === y)), x, t) // |- ∃x x === t /\ ∀y(t===y)==>(x===y) + val definition = SCProof(IndexedSeq(p0, p1, p2, p3, p4, p5)) + val fdef = args.foldLeft((definition.steps, p5.bot.right.head, 5))((prev, x) => { + val fo = forall(x, prev._2) + (prev._1 appended RightForall(emptySeq +> fo, prev._3, prev._2, x), fo, prev._3 + 1) + }) + //val p6 = MakeFunctionDefinition(fdef._1.size - 1, f, args, x, x === t) + SCProof(fdef._1 ) + } + // p1 is a proof of psi given phi, p2 is a proof of psi given !phi + def byCase(phi: Formula)(pa: SCProofStep, pb: SCProofStep): SCProof = { + val nphi = !phi + val (leftAphi, leftBnphi) = (pa.bot.left.find(isSame(_, phi)), pb.bot.left.find(isSame(_, nphi))) + + require(leftAphi.nonEmpty && leftBnphi.nonEmpty) + val p2 = RightNot(pa.bot -< leftAphi.get +> nphi, 0, phi) + val p3 = Cut(pa.bot -< leftAphi.get ++ (pb.bot -< leftBnphi.get), 2, 1, nphi) + SCProof(IndexedSeq(pa, pb, p2, p3)) + } + // pa is a proof of phi, pb is a proof of phi ==> ??? + // |- phi ==> psi, phi===>gamma |- phi + // ------------------------------------- + // |- psi, gamma + def modusPonens(phi: Formula)(pa: SCProofStep, pb: SCProofStep): SCProof = { + require(pa.bot.right.contains(phi)) + val opsi = pb.bot.right.find { + case ConnectorFormula(Implies, Seq(l, _)) if isSame(l, phi) => true + case _ => false + } + if (opsi.isEmpty) SCProof(pa, pb) + else { + val psi = opsi.get.asInstanceOf[ConnectorFormula].args(1) + val p2 = hypothesis(psi) + val p3 = LeftImplies(emptySeq ++ (pa.bot -> phi) +< (phi ==> psi) +> psi, 0, 2, phi, psi) + val p4 = Cut(emptySeq ++ (pa.bot -> phi) ++< pb.bot +> psi ++> (pb.bot -> (phi ==> psi)), 1, 3, phi ==> psi) + SCProof(pa, pb, p2, p3, p4) + } + } + + def detectSubstitution(x: VariableLabel, f: Formula, s: Formula, c: Option[Term] = None): (Option[Term], Boolean) = (f, s) match { + case (PredicateFormula(la1, args1), PredicateFormula(la2, args2)) if isSame(la1, la2) => { + args1.zip(args2).foldLeft[(Option[Term], Boolean)](c, true)((r1, a) => { + val r2 = detectSubstitutionT(x, a._1, a._2, r1._1) + (if (r1._1.isEmpty) r2._1 else r1._1, r1._2 && r2._2 && (r1._1.isEmpty || r2._1.isEmpty || isSame(r1._1.get, r2._1.get))) + }) + } + case (ConnectorFormula(la1, args1), ConnectorFormula(la2, args2)) if isSame(la1, la2) => { + args1.zip(args2).foldLeft[(Option[Term], Boolean)](c, true)((r1, a) => { + val r2 = detectSubstitution(x, a._1, a._2, r1._1) + (if (r1._1.isEmpty) r2._1 else r1._1, r1._2 && r2._2 && (r1._1.isEmpty || r2._1.isEmpty || isSame(r1._1.get, r2._1.get))) + }) + } + case (BinderFormula(la1, bound1, inner1), BinderFormula(la2, bound2, inner2)) if la1 == la2 && bound1 == bound2 => { // TODO renaming + detectSubstitution(x, inner1, inner2, c) + } + case _ => (c, false) + } + def detectSubstitutionT(x: VariableLabel, t: Term, s: Term, c: Option[Term] = None): (Option[Term], Boolean) = (t, s) match { + case (y: VariableTerm, z: Term) => { + if (isSame(y.label, x)) { + if (c.isDefined) { + (c, isSame(c.get, z)) + } + else { + (Some(z), true) + } + } + else (c, isSame(y, z)) + } + case (FunctionTerm(la1, args1), FunctionTerm(la2, args2)) if isSame(la1, la2) => { + args1.zip(args2).foldLeft[(Option[Term], Boolean)](c, true)((r1, a) => { + val r2 = detectSubstitutionT(x, a._1, a._2, r1._1) + (if (r1._1.isEmpty) r2._1 else r1._1, r1._2 && r2._2 && (r1._1.isEmpty || r2._1.isEmpty || isSame(r1._1.get, r2._1.get))) + }) + } + case _ => (c, false) + } + +} diff --git a/src/main/scala/proven/tactics/SimplePropositionalSolver.scala b/src/main/scala/proven/tactics/SimplePropositionalSolver.scala new file mode 100644 index 0000000000000000000000000000000000000000..86f9d7fbcddfe15b35d6a1a7d38edaadc54f0475 --- /dev/null +++ b/src/main/scala/proven/tactics/SimplePropositionalSolver.scala @@ -0,0 +1,173 @@ +package proven.tactics + +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.SequentCalculus.* +import scala.collection.mutable.Set as mSet +import lisa.KernelHelpers.* +import lisa.kernel.proof.SCProof + +/** + * A simple but complete solver for propositional logic. Will not terminate for large problems + */ +object SimplePropositionalSolver { + class OrganisedFormulaSet { + val negs: mSet[ConnectorFormula] = mSet() + val impliess: mSet[ConnectorFormula] = mSet() + val iffs: mSet[ConnectorFormula] = mSet() + val ands: mSet[ConnectorFormula] = mSet() + val ors: mSet[ConnectorFormula] = mSet() + val others: mSet[Formula] = mSet() + + def updateFormula(phi: Formula, add: Boolean): Unit = (phi match { + case phi@ConnectorFormula(label, args) => + label match { + case Neg => if (add) negs.add(phi) else negs.remove(phi) + case Implies => if (add) impliess.add(phi) else impliess.remove(phi) + case Iff => if (add) iffs.add(phi) else iffs.remove(phi) + case And => if (add) ands.add(phi) else ands.remove(phi) + case Or => if (add) ors.add(phi) else ors.remove(phi) + } + case _ => if (add) others.add(phi) else others.remove(phi) + }) + + def copy(): OrganisedFormulaSet = { + val r = new OrganisedFormulaSet + r.negs.addAll(negs) + r.impliess.addAll(impliess) + r.iffs.addAll(iffs) + r.ands.addAll(ands) + r.ors.addAll(ors) + r.others.addAll(others) + r + } + } + + def solveOrganisedSequent(left: OrganisedFormulaSet, right: OrganisedFormulaSet, s: Sequent, offset: Int): List[SCProofStep] = { + if (left.negs.nonEmpty) { + val f = left.negs.head + val phi = f.args.head + left.updateFormula(f, false) + right.updateFormula(f.args.head, true) + val proof = solveOrganisedSequent(left, right, s -< f +> phi, offset) + LeftNot(s, proof.length - 1 + offset, phi) :: proof + } + else if (left.impliess.nonEmpty) { + val f = left.impliess.head + val phi = f.args(0) + val psi = f.args(1) + + left.updateFormula(f, false) //gamma + val rl = left.copy() //sigma + val rr = right.copy() //pi + right.updateFormula(phi, true) //delta + rl.updateFormula(psi, true) + val proof1 = solveOrganisedSequent(left, right, s -< f +> phi, offset) + val proof2 = solveOrganisedSequent(rl, rr, s -< f +< psi, offset + proof1.size) + LeftImplies(s, proof1.size + offset - 1, proof1.size + proof2.size + offset - 1, phi, psi) :: (proof2 ++ proof1) + } + else if (left.iffs.nonEmpty) + val f = left.iffs.head + val phi = f.args(0) + val psi = f.args(1) + left.updateFormula(f, false) + left.updateFormula(phi ==> psi, true) + left.updateFormula(psi ==> phi, true) + val proof = solveOrganisedSequent(left, right, s -< f +< (phi ==> psi) +< (psi ==> phi), offset) + LeftIff(s, proof.length - 1 + offset, phi, psi) :: proof + else if (left.ands.nonEmpty) { + val f = left.ands.head + val phi = f.args(0) + val psi = f.args(1) + left.updateFormula(f, false) + left.updateFormula(phi, true) + left.updateFormula(psi, true) + val proof = solveOrganisedSequent(left, right, s -< f +< phi +< psi, offset) + LeftAnd(s, proof.length - 1 + offset, phi, psi) :: proof + + } + else if (left.ors.nonEmpty) { + val f = left.ors.head + val phi = f.args(0) + val psi = f.args(1) + + left.updateFormula(f, false) //gamma + val rl = left.copy() //sigma + val rr = right.copy() //pi + left.updateFormula(phi, true) //delta + rl.updateFormula(psi, true) + val proof1 = solveOrganisedSequent(left, right, s -< f +< phi, offset) + val proof2 = solveOrganisedSequent(rl, rr, s -< f +< psi, offset + proof1.size) + LeftOr(s, Seq(proof1.size + offset - 1, proof1.size + proof2.size + offset - 1), Seq(phi, psi)) :: (proof2 ++ proof1) + } + else if (right.negs.nonEmpty) + val f = right.negs.head + val phi = f.args.head + right.updateFormula(f, false) + left.updateFormula(phi, true) + val proof = solveOrganisedSequent(left, right, s -> f +< phi, offset) + RightNot(s, proof.length - 1 + offset, phi) :: proof + else if (right.impliess.nonEmpty) { + val f = right.impliess.head + val phi = f.args(0) + val psi = f.args(1) + left.updateFormula(phi, true) + right.updateFormula(f, false) + right.updateFormula(psi, true) + val proof = solveOrganisedSequent(left, right, s -> f +< phi +> psi, offset) + RightImplies(s, proof.length - 1 + offset, phi, psi) :: proof + } + else if (right.iffs.nonEmpty) + val f = right.iffs.head + val phi = f.args(0) + val psi = f.args(1) + right.updateFormula(f, false) //gamma + val rl = left.copy() //sigma + val rr = right.copy() //pi + right.updateFormula(phi ==> psi, true) //delta + rr.updateFormula(psi ==> phi, true) + val proof1 = solveOrganisedSequent(left, right, s -> f +> (phi ==> psi), offset) + val proof2 = solveOrganisedSequent(rl, rr, s -> f +> (psi ==> phi), offset + proof1.size) + RightIff(s, proof1.size + offset - 1, proof1.size + proof2.size + offset - 1, phi, psi) :: (proof2 ++ proof1) + else if (right.ands.nonEmpty) { + val f = right.ands.head + val phi = f.args(0) + val psi = f.args(1) + + right.updateFormula(f, false) //gamma + val rl = left.copy() //sigma + val rr = right.copy() //pi + right.updateFormula(phi, true) //delta + rr.updateFormula(psi, true) + val proof1 = solveOrganisedSequent(left, right, s -> f +> phi, offset) + val proof2 = solveOrganisedSequent(rl, rr, s -> f +> psi, offset + proof1.size) + RightAnd(s, Seq(proof1.size + offset - 1, proof1.size + proof2.size + offset - 1), Seq(phi, psi)) :: (proof2 ++ proof1) + } + else if (right.ors.nonEmpty) { + val f = right.ors.head + val phi = f.args(0) + val psi = f.args(1) + right.updateFormula(f, false) + right.updateFormula(phi, true) + right.updateFormula(psi, true) + val proof = solveOrganisedSequent(left, right, s -> f +> phi +> psi, offset) + RightOr(s, proof.length - 1 + offset, phi, psi) :: proof + + } + else { + val f = s.left.find(f => s.right.contains(f)) + List(Hypothesis(s, if (f.nonEmpty) f.get else PredicateFormula(SchematicPredicateLabel("P", 0), Seq()))) + } + } + + def solveSequent(s: Sequent): SCProof = { + val l = new OrganisedFormulaSet + s.left.foreach(f => l.updateFormula(f, true)) + val r = new OrganisedFormulaSet + s.right.foreach(f => r.updateFormula(f, true)) + val r2 = solveOrganisedSequent(l, r, s, 0) + val r3 = r2.reverse.toVector + val r4 = SCProof(r3) + r4 + } + +} diff --git a/src/main/scala/tptp/KernelParser.scala b/src/main/scala/tptp/KernelParser.scala new file mode 100644 index 0000000000000000000000000000000000000000..cca3ff4fbdeccb45ce3c6e86c3f63650fd5a8bd4 --- /dev/null +++ b/src/main/scala/tptp/KernelParser.scala @@ -0,0 +1,238 @@ +package tptp + +import leo.modules.input.TPTPParser as Parser +import Parser.TPTPParseException +import leo.datastructures.TPTP +import leo.datastructures.TPTP.{CNF, FOF} +import lisa.kernel.fol.FOL as K +import lisa.kernel.proof.SequentCalculus as SK +import lisa.KernelHelpers.* + +import java.io.File +import scala.util.matching.Regex + +object KernelParser { + + case class AnnotatedFormula(role: String, name: String, formula: K.Formula) + + case class Problem(file: String, domain: String, name: String, status: String, spc: Seq[String], formulas: Seq[AnnotatedFormula]) + + case class FileNotAcceptedException(msg: String, file: String) extends Exception(msg + " File: " + file) + + private case class ProblemMetadata(file: String, domain: String, problem: String, status: String, spc: Seq[String]) + + /** + * + * @param formula A formula in the tptp language + * @return the corresponding LISA formula + */ + def parseToKernel(formula: String): K.Formula = convertToKernel(Parser.fof(formula)) + + /** + * + * @param formula a tptp formula in leo parser + * @return the same formula in LISA + */ + def convertToKernel(formula: FOF.Formula): K.Formula = { + formula match { + case FOF.AtomicFormula(f, args) => K.PredicateFormula(K.ConstantPredicateLabel(f, args.size), args map convertTermToKernel) + case FOF.QuantifiedFormula(quantifier, variableList, body) => quantifier match { + case FOF.! => variableList.foldRight(convertToKernel(body))((s, f) => K.Forall(K.VariableLabel(s), f)) + case FOF.? => variableList.foldRight(convertToKernel(body))((s, f) => K.Exists(K.VariableLabel(s), f)) + } + case FOF.UnaryFormula(connective, body) => connective match { + case FOF.~ => K.Neg(convertToKernel(body)) + } + case FOF.BinaryFormula(connective, left, right) => connective match { + case FOF.<=> => convertToKernel(left) <=> convertToKernel(right) + case FOF.Impl => convertToKernel(left) ==> convertToKernel(right) + case FOF.<= => convertToKernel(right) ==> convertToKernel(left) + case FOF.<~> => !(convertToKernel(left) <=> convertToKernel(right)) + case FOF.~| => !(convertToKernel(left) \/ convertToKernel(right)) + case FOF.~& => !(convertToKernel(left) /\ convertToKernel(right)) + case FOF.| => convertToKernel(left) \/ convertToKernel(right) + case FOF.& => convertToKernel(left) /\ convertToKernel(right) + } + case FOF.Equality(left, right) => K.equality(convertTermToKernel(left), convertTermToKernel(right)) + case FOF.Inequality(left, right) => !K.equality(convertTermToKernel(left), convertTermToKernel(right)) + } + } + + def convertToKernel(formula: CNF.Formula): K.Formula = { + + K.ConnectorFormula(K.Or, formula.map { + case CNF.PositiveAtomic(formula) => K.PredicateFormula(K.ConstantPredicateLabel(formula.f, formula.args.size), formula.args.map(convertTermToKernel).toList) + case CNF.NegativeAtomic(formula) => !K.PredicateFormula(K.ConstantPredicateLabel(formula.f, formula.args.size), formula.args.map(convertTermToKernel).toList) + case CNF.Equality(left, right) => K.equality(convertTermToKernel(left), convertTermToKernel(right)) + case CNF.Inequality(left, right) => !K.equality(convertTermToKernel(left), convertTermToKernel(right)) + }) + } + + /** + * + * @param term a tptp term in leo parser + * @return the same term in LISA + */ + def convertTermToKernel(term: CNF.Term): K.Term = term match { + case CNF.AtomicTerm(f, args) => K.FunctionTerm(K.ConstantFunctionLabel(f, args.size), args map convertTermToKernel) + case CNF.Variable(name) => K.VariableTerm(K.VariableLabel(name)) + case CNF.DistinctObject(name) => ??? + } + + /** + * + * @param term a tptp term in leo parser + * @return the same term in LISA + */ + def convertTermToKernel(term: FOF.Term): K.Term = term match { + case FOF.AtomicTerm(f, args) => + K.FunctionTerm(K.ConstantFunctionLabel(f, args.size), args map convertTermToKernel) + case FOF.Variable(name) => K.VariableTerm(K.VariableLabel(name)) + case FOF.DistinctObject(name) => ??? + case FOF.NumberTerm(value) => ??? + } + + /** + * + * @param formula an annotated tptp formula + * @return the corresponding LISA formula augmented with name and role. + */ + def annotatedFormulaToKernel(formula: String): AnnotatedFormula = { + val i = Parser.annotatedFOF(formula) + i.formula match { + case FOF.Logical(formula) => AnnotatedFormula(i.role, i.name, convertToKernel(formula)) + } + } + + private def problemToKernel(problemFile: File, md: ProblemMetadata): Problem = { + val file = io.Source.fromFile(problemFile) + val pattern = "SPC\\s*:\\s*[A-z]{3}(_[A-z]{3})*".r + val g = file.getLines() + + def search(): String = pattern.findFirstIn(g.next()).getOrElse(search()) + + val i = Parser.problem(file) + val sq = i.formulas map { + case TPTP.FOFAnnotated(name, role, formula, annotations) => formula match { + case FOF.Logical(formula) => AnnotatedFormula(role, name, convertToKernel(formula)) + } + case TPTP.CNFAnnotated(name, role, formula, annotations) => formula match { + case CNF.Logical(formula) => AnnotatedFormula(role, name, convertToKernel(formula)) + } + case _ => throw FileNotAcceptedException("Only FOF formulas are supported", problemFile.getPath) + } + Problem(md.file, md.domain, md.problem, md.status, md.spc, sq) + } + + /** + * + * @param problemFile a file containning a tptp problem + * @return a Problem object containing the data of the tptp problem in LISA representation + */ + def problemToKernel(problemFile: File): Problem = { + problemToKernel(problemFile, getProblemInfos(problemFile)) + } + + /** + * + * @param problemFile a path to a file containing a tptp problem + * @return a Problem object containing the data of the tptp problem in LISA representation + */ + def problemToKernel(problemFile: String): Problem = { + problemToKernel(File(problemFile)) + } + + /** + * Given a problem consisting of many axioms and a single conjecture, create a sequent with axioms on the left + * and conjecture on the right. + * + * @param problem a problem, containing a list of annotated formulas from a tptp file + * @return a sequent with axioms of the problem on the left, and the conjecture on the right + */ + def problemToSequent(problem: Problem): SK.Sequent = { + if (problem.spc.contains("CNF")) problem.formulas.map(_.formula) |- () + else problem.formulas.foldLeft[SK.Sequent](() |- ())((s, f) => + if (f._1 == "axiom") s +< f._3 + else if (f._1 == "conjecture" && s.right.isEmpty) s +> f._3 + else throw Exception("Can only agglomerate axioms and one conjecture into a sequents") + ) + } + + /** + * Given a folder containing folders containing problem (typical organisation of TPTP library) and a list of spc, + * return the same arrangement of problems in LISA syntax, filtered so that only problems with at least one + * spc from the "spc" argument. + * + * @param spc a list of 3-characters codes representing properties of a problem, such as FOF for First Order Logic. + * @param path the path to the tptp library. + * @return A sequence of domains, each being a sequence of problems + */ + def gatherAllTPTPFormulas(spc: Seq[String], path: String): Seq[Seq[Problem]] = { + val d = new File(path) + val probfiles: Array[File] = if (d.exists) { + if (d.isDirectory) { + if (d.listFiles().isEmpty) println("empty directory") + d.listFiles.filter(_.isDirectory) + + } + else throw new Exception("Specified path is not a directory.") + } + else throw new Exception("Specified path does not exist.") + + probfiles.map(d => gatherFormulas(spc, d.getPath)).toSeq + } + + def gatherFormulas(spc: Seq[String], path: String): Seq[Problem] = { + val d = new File(path) + val probfiles: Array[File] = if (d.exists) { + if (d.isDirectory) { + if (d.listFiles().isEmpty) println("empty directory") + d.listFiles.filter(_.isFile) + + } + else throw new Exception("Specified path is not a directory.") + } + else throw new Exception("Specified path does not exist.") + + val r = probfiles.foldRight(List.empty[Problem])((p, current) => + val md = getProblemInfos(p) + if (md.spc.exists(spc.contains)) problemToKernel(p, md) :: current + else current + ) + r + } + + + /** + * + * @param file a file containing a tptp problem + * @return the metadata info (file name, domain, problem, status and spc) in the file + */ + private def getProblemInfos(file: File): ProblemMetadata = { + val pattern = "((File)|(Domain)|(Problem)|(Status)|(SPC))\\s*:.*".r + val s = io.Source.fromFile(file) + val g = s.getLines() + var fil: String = "?" + var dom: String = "?" + var pro: String = "?" + var sta: String = "?" + var spc: Seq[String] = Seq() + + val count: Int = 0 + while (g.hasNext && count < 5) { + val line = g.next() + val res = pattern.findFirstIn(line) + if (res.nonEmpty) { + val act = res.get + if (act(0) == 'F') fil = act.drop(act.indexOf(":") + 2) + else if (act(0) == 'D') dom = act.drop(act.indexOf(":") + 2) + else if (act(0) == 'P') pro = act.drop(act.indexOf(":") + 2) + else if (act(1) == 't') sta = act.drop(act.indexOf(":") + 2) + else if (act(1) == 'P') spc = act.drop(act.indexOf(":") + 2).split("_").toIndexedSeq + } + } + s.close() + ProblemMetadata(fil, dom, pro, sta, spc) + } + +} diff --git a/src/main/scala/tptp/ProblemGatherer.scala b/src/main/scala/tptp/ProblemGatherer.scala new file mode 100644 index 0000000000000000000000000000000000000000..5fb3199202094ae466912200661f930d9a22891e --- /dev/null +++ b/src/main/scala/tptp/ProblemGatherer.scala @@ -0,0 +1,16 @@ +package tptp + +import tptp.KernelParser._ + +object ProblemGatherer { + + /** + * The tptp library needs to be included in main/resources. It can be found at http://www.tptp.org/ + * @return sequence of tptp problems with the PRP (propositional) tag. + */ + def getPRPproblems: Seq[Problem] = { + val path = getClass.getResource("/TPTP/Problems/SYN/").getPath + gatherFormulas(Seq("PRP"), path) + } + +} diff --git a/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala b/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..68a5fa28e5b32e6af711482c46e7644f30b25f2a --- /dev/null +++ b/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala @@ -0,0 +1,332 @@ +package lisa.kernel + +import lisa.KernelHelpers.* +import lisa.kernel.Printer +import lisa.kernel.fol.FOL +import lisa.kernel.fol.FOL.* +import org.scalatest.funsuite.AnyFunSuite + +import scala.collection.MapView +import scala.collection.mutable.{ArrayBuffer, ListBuffer} +import scala.util.Random + +class EquivalenceCheckerTests extends AnyFunSuite { + private val verbose = false // Turn this on to print all tested couples + + def checkEquivalence(left: Formula, right: Formula): Unit = { + assert(isSame(left, right), s"Couldn't prove the equivalence between ${Printer.prettyFormula(left)} and ${Printer.prettyFormula(right)}\nLeft tree: ${left}\nRight tree: ${right}") + } + + def checkNonEquivalence(left: Formula, right: Formula): Unit = { + assert(!isSame(left, right), s"Expected the checker to not be able to show equivalence between ${Printer.prettyFormula(left)} and ${Printer.prettyFormula(right)}\nLeft tree: ${left}\nRight tree: ${right}") + } + + def nameGenerator(): () => String = { + val chars = 'a' to 'z' + def stream: LazyList[Seq[Char]] = LazyList.from(chars.map(Seq(_))) #::: stream.flatMap(l => chars.map(_ +: l)) + var current = stream + () => { + val id = current.head.reverse.mkString + current = current.tail + id + } + } + def numbersGenerator(): () => Int = { + var i = 1 + () => { + val n = i + i += 1 + n + } + } + + def constantsGenerator(): () => Formula = { + val generator = nameGenerator() + () => { + val id = generator() + PredicateFormula(ConstantPredicateLabel(id, 0), Seq.empty) + } + } + + def formulasGenerator(c: Double)(random: Random): () => Formula = { + val connectors = ArrayBuffer.empty[String] + val variables = ArrayBuffer.empty[String] + val nextConnectorName = nameGenerator() + val nextVariableName = { + val gen = numbersGenerator() + () => s"v${gen()}" + } + def generate(p: Double): Formula = { + val q = random.nextDouble() + + if(q >= p) { + // Leaf + val name = + if(connectors.isEmpty || random.nextDouble() <= 0.75) { // TODO adapt + // New name + val id = nextConnectorName() + connectors += id + id + } else { + // Reuse existing name + connectors(random.nextInt(connectors.size)) + } + PredicateFormula(ConstantPredicateLabel(name, 0), Seq.empty) + } else { + // Branch + val nextP = p * c + if(random.nextDouble() < 0.9) { + // Connector + val binaries = Seq(and, or, iff, implies) + if(random.nextInt(binaries.size + 1) > 0) { + // Binary + val binary = binaries(random.nextInt(binaries.size)) + val (l, r) = { + val p1 = nextP * nextP + val (f1, f2) = (generate(p1), generate(p1)) + if(random.nextBoolean()) (f1, f2) else (f2, f1) + } + (binary(l, r)) + } else { + // Unary + neg(generate(nextP)) + } + } else { + // Binder + val name = nextVariableName() + variables += name + val binderTypes: IndexedSeq[BinderLabel] = IndexedSeq(Forall, Exists, ExistsOne) + val binderType = binderTypes(random.nextInt(binderTypes.size)) + BinderFormula(binderType, VariableLabel(name), generate(nextP)) + } + } + } + + () => generate(c) + } + + def testcasesAny(generatorToTestcases: (() => Formula) => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = { + val random: Random = new Random(1) + + def testWith(generator: () => () => Formula): Unit = { + val cases = generatorToTestcases(generator())(random) + cases.foreach { (left, right) => + // For completeness we also test symmetry + if(equivalent) { + checkEquivalence(left, right) + checkEquivalence(right, left) + if(verbose) { + println(s"${Printer.prettyFormula(left)} <==> ${Printer.prettyFormula(right)}") + } + } else { + checkNonEquivalence(left, right) + checkNonEquivalence(right, left) + if(verbose) { + println(s"${Printer.prettyFormula(left)} <!=> ${Printer.prettyFormula(right)}") + } + } + } + } + + def testWithRepeat(generator: () => () => Formula, n: Int): Unit = { + for(i <- 0 until n) { + testWith(generator) + } + } + + // 1. Constants (a, b, c, ...) + + testWith(constantsGenerator) + + // 2. Random formulas (small) + + testWithRepeat(() => formulasGenerator(0.8)(random), 100) + + // 3. Random formulas (larger) + + testWithRepeat(() => formulasGenerator(0.99)(random), 100) + } + + def testcases(f: Formula => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = + testcasesAny(generator => r => f(generator())(r), equivalent) + def testcases(f: (Formula, Formula) => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = + testcasesAny(generator => r => f(generator(), generator())(r), equivalent) + def testcases(f: (Formula, Formula, Formula) => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = + testcasesAny(generator => r => f(generator(), generator(), generator())(r), equivalent) + def testcases(f: (Formula, Formula, Formula, Formula) => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = + testcasesAny(generator => r => f(generator(), generator(), generator(), generator())(r), equivalent) + + def repeatApply[T](n: Int)(f: T => T)(initial: T): T = if(n > 0) repeatApply(n - 1)(f)(f(initial)) else initial + def commutativeShuffle(iterations: Int)(random: Random)(f: Formula): Formula = { + def transform(f: Formula): Formula = f match { + case PredicateFormula(label, args) => f + case ConnectorFormula(label, args) => + val newArgs = label match { + case And | Or | Iff => random.shuffle(args) + case _ => args + } + ConnectorFormula(label, newArgs.map(transform)) + case BinderFormula(label, bound, inner) => BinderFormula(label, bound, transform(inner)) + } + repeatApply(iterations)(transform)(f) + } + def associativeShuffle(iterations: Int)(random: Random)(f: Formula): Formula = { + def transform(f: Formula): Formula = f match { + case PredicateFormula(label, args) => f + // Simple for now, assume binary operations + case ConnectorFormula(label1 @ (And | Or), Seq(ConnectorFormula(label2, Seq(a1, a2)), a3)) if label1 == label2 => + if(random.nextBoolean()) { + ConnectorFormula(label1, Seq(a1, ConnectorFormula(label2, Seq(a2, a3)))) + } else { + f + } + case ConnectorFormula(label1 @ (And | Or), Seq(a1, ConnectorFormula(label2, Seq(a2, a3)))) if label1 == label2 => + if(random.nextBoolean()) { + ConnectorFormula(label1, Seq(ConnectorFormula(label2, Seq(a1, a2)), a3)) + } else { + f + } + case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(transform)) + case BinderFormula(label, bound, inner) => BinderFormula(label, bound, transform(inner)) + } + repeatApply(iterations)(transform)(f) + } + def addDoubleNegations(p: Double)(random: Random)(f: Formula): Formula = { + def transform(f: Formula): Formula = + if(random.nextDouble() < p) neg(neg(transform(f))) + else f match { + case _: PredicateFormula => f + case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(transform)) + case BinderFormula(label, bound, inner) => BinderFormula(label, bound, transform(inner)) + } + transform(f) + } + def addDeMorgans(p: Double)(random: Random)(f: Formula): Formula = { + def transform(f: Formula): Formula = f match { + case _: PredicateFormula => f + case ConnectorFormula(label, args) => + val map: Map[ConnectorLabel, ConnectorLabel] = Map(And -> Or, Or -> And) + map.get(label) match { + case Some(opposite) if random.nextDouble() < p => transform(neg(ConnectorFormula(opposite, args.map(neg)))) + case _ => ConnectorFormula(label, args.map(transform)) + } + case BinderFormula(label, bound, inner) => BinderFormula(label, bound, transform(inner)) + } + transform(f) + } + + // Positive + + test("Reflexivity") { + testcases(a => _ => Seq(a -> a), equivalent = true) + } + + test("Commutativity (root with equal leaves)") { + testcases((a, b) => _ => Seq( + and(a, b) -> and(b, a), + or(a, b) -> or(b, a), + iff(a, b) -> iff(b, a), + ), equivalent = true) + } + + test("Associativity (root with equal leaves)") { + testcases((a, b, c, d) => _ => Seq( + and(and(a, b), c) -> and(a, and(b, c)), + or(or(a, b), c) -> or(a, or(b, c)), + ), equivalent = true) + } + + test("Commutativity (general)") { + testcases((a, b) => random => Seq( + a -> commutativeShuffle(15)(random)(a) + ), equivalent = true) + } + + test("Associativity (general)") { + testcases((a, b) => random => Seq( + a -> associativeShuffle(15)(random)(a) + ), equivalent = true) + } + + test("Commutativity and associativity (general)") { + testcases((a, b) => random => Seq( + a -> repeatApply(15)(commutativeShuffle(1)(random).andThen(associativeShuffle(1)(random)))(a) + ), equivalent = true) + } + + test("Double negation (root with equal leaf)") { + testcases(a => _ => Seq( + a -> neg(neg(a)), + neg(a) -> neg(neg(neg(a))), + a -> neg(neg(neg(neg(a)))), + ), equivalent = true) + } + + test("Double negation (general)") { + val p = 0.25 + testcases(a => random => Seq( + addDoubleNegations(p)(random)(a) -> addDoubleNegations(p)(random)(a), + ), equivalent = true) + } + + test("De Morgan's law (root)") { + testcases((a, b) => random => Seq( + and(a, b) -> neg(or(neg(a), neg(b))), + or(a, b) -> neg(and(neg(a), neg(b))), + ), equivalent = true) + } + + test("De Morgan's law (general)") { + val p = 0.25 + testcases(a => random => Seq( + addDeMorgans(p)(random)(a) -> addDeMorgans(p)(random)(a) + ), equivalent = true) + } + + test("Allowed tautologies") { + testcases((a, b, c) => random => Seq( + or(a, neg(a)) -> or(neg(a), neg(neg(a))), + and(a, neg(a)) -> and(neg(a), neg(neg(a))), + ), equivalent = true) + } + + test("Absorption") { + testcases((a, b, c) => random => Seq( + and(a, a) -> a, + or(a, a) -> a, + //or(or(a, neg(a)), c) -> c, + //and(and(a, neg(a)), c) -> and(a, neg(a)), + ), equivalent = true) + } + + test("All allowed transformations") { + val transformations: Seq[Random => Formula => Formula] = IndexedSeq( + r => commutativeShuffle(1)(r) _, + r => associativeShuffle(1)(r) _, + r => addDoubleNegations(0.02)(r) _, + r => addDeMorgans(0.05)(r) _, + ) + def randomTransformations(random: Random)(f: Formula): Formula = { + val n = random.nextInt(50) + Seq.fill(n)(transformations(random.nextInt(transformations.size))).foldLeft(f)((acc, e) => e(random)(acc)) + } + + testcases(a => random => Seq( + randomTransformations(random)(a) -> randomTransformations(random)(a) + ), equivalent = true) + } + + // Negative + + test("Negative by construction") { + val x = PredicateFormula(ConstantPredicateLabel("$", 0), Seq.empty) // Globally free + testcases((a, b) => random => Seq( + a -> and(a, x), + a -> or(a, x), + a -> implies(a, x), + a -> iff(a, x), + ), equivalent = false) + } + + +} diff --git a/src/test/scala/lisa/kernel/FolTests.scala b/src/test/scala/lisa/kernel/FolTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..c5f3b6f3de7543c1f47ddf7a4fe628919259232e --- /dev/null +++ b/src/test/scala/lisa/kernel/FolTests.scala @@ -0,0 +1,126 @@ +package lisa.kernel + +import lisa.KernelHelpers.{*, given} +import lisa.kernel.Printer +import lisa.kernel.proof.RunningTheory +import org.scalatest.funsuite.AnyFunSuite +import lisa.kernel.proof.SequentCalculus.* +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.RunningTheory.* +import lisa.kernel.proof.{SCProof, SCProofChecker} + +import scala.collection.immutable.SortedSet +import scala.util.Random + +class FolTests extends AnyFunSuite { + + val predicateVerifier = SCProofChecker.checkSCProof + + def nameGenerator(candidates: Seq[String], gen: Random = new Random(), l: Int = 1): String = { + if (gen.nextBoolean()) gen.nextString(1) + else candidates(gen.between(0, candidates.length)) + } + + def termGenerator(maxDepth: Int, gen: Random = new Random()): Term = { + if (maxDepth <= 1) { + val r = gen.between(0, 3) + if (r == 0) { + val name = "" + ('a' to 'e') (gen.between(0, 5)) + FunctionTerm(ConstantFunctionLabel(name, 0), List()) + } + else { + val name = "" + ('v' to 'z') (gen.between(0, 5)) + VariableTerm(VariableLabel(name)) + } + } else { + val r = gen.between(0, 8) + val name = "" + ('f' to 'j') (gen.between(0, 5)) + if (r == 0) { + val name = "" + ('a' to 'e') (gen.between(0, 5)) + FunctionTerm(ConstantFunctionLabel(name, 0), List()) + } + else if (r == 1) { + val name = "" + ('v' to 'z') (gen.between(0, 5)) + VariableTerm(VariableLabel(name)) + } + if (r <= 3) FunctionTerm(ConstantFunctionLabel(name, 1), Seq(termGenerator(maxDepth - 1, gen))) + else if (r <= 5) FunctionTerm(ConstantFunctionLabel(name, 2), Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) + else if (r == 6) FunctionTerm(ConstantFunctionLabel(name, 3), + Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) + else FunctionTerm(ConstantFunctionLabel(name, 4), + Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) + + } + } + + def formulaGenerator(maxDepth: Int, gen: Random = new Random()): Formula = { + if (maxDepth <= 2 || (gen.nextBoolean() && gen.nextBoolean())) { + val r = gen.between(0, 7) + if (r <= 1) { + val name = "" + ('A' to 'E') (gen.between(0, 5)) + PredicateFormula(ConstantPredicateLabel(name, 0), Seq()) + } + + else if (r <= 3) { + val name = "" + ('A' to 'E') (gen.between(0, 5)) + PredicateFormula(ConstantPredicateLabel(name, 1), Seq(termGenerator(maxDepth - 1, gen))) + } + + else if (r <= 5) { + val s = gen.between(0, 3) + if (s == 0) PredicateFormula(equality, Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) + else { + val name = "" + ('A' to 'E') (gen.between(0, 5)) + PredicateFormula(ConstantPredicateLabel(name, 2), Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) + } + } + else { + val name = "" + ('A' to 'E') (gen.between(0, 5)) + PredicateFormula(ConstantPredicateLabel(name, 3), + Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) + } + + } + else { + val r = gen.between(0, 7) + if (r <= 1) neg(formulaGenerator(maxDepth - 1, gen)) + else if (r == 2) and(formulaGenerator(maxDepth - 1, gen), formulaGenerator(maxDepth - 1, gen)) + else if (r == 3) or(formulaGenerator(maxDepth - 1, gen), formulaGenerator(maxDepth - 1, gen)) + else if (r == 4) implies(formulaGenerator(maxDepth - 1, gen),formulaGenerator(maxDepth - 1, gen)) + else if (r == 5) { + val f = formulaGenerator(maxDepth - 1, gen) + val l = f.freeVariables.toSeq ++ Seq(x) + forall(l(gen.between(0, l.size)), f) + } + else { + val f = formulaGenerator(maxDepth - 1, gen) + val l = f.freeVariables.toSeq ++ Seq(x) + exists(l(gen.between(0, l.size)), f) + } + } + + } + + private val x = VariableLabel("x") + private val y = VariableLabel("y") + private val z = VariableLabel("z") + private val a = PredicateFormula(ConstantPredicateLabel("A", 0), Seq()) + private val b = PredicateFormula(ConstantPredicateLabel("B", 0), Seq()) + private val fp = ConstantPredicateLabel("F", 1) + private val sT = SchematicFunctionLabel("t", 0) + + def test_some_random_formulas(n: Int, maxDepth: Int): Unit = { + (0 to n).foreach(_ => println(formulaGenerator(maxDepth))) + } + + test("Random formulas well-constructed") { + (0 to 50).foreach(_ => formulaGenerator(10)) + } + + + test("Fresh variables should be fresh") { + val y1 = VariableLabel(freshId(equ(VariableTerm(x), VariableTerm(x)).freeVariables.map(_.name), x.name)) + //println(x1, y1) + assert(!isSame(x, y1)) + } +} \ No newline at end of file diff --git a/src/test/scala/lisa/kernel/IncorrectProofsTests.scala b/src/test/scala/lisa/kernel/IncorrectProofsTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..9c7c151505387f5acb63e80589bc2266101ee380 --- /dev/null +++ b/src/test/scala/lisa/kernel/IncorrectProofsTests.scala @@ -0,0 +1,97 @@ +package lisa.kernel + +//import lisa.settheory.AxiomaticSetTheory.{emptySet, emptySetAxiom, pair, pairAxiom, useAxiom} +import lisa.KernelHelpers.* +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.SCProof +import lisa.kernel.proof.SequentCalculus.* +import test.ProofCheckerSuite + +import scala.collection.immutable.SortedSet +import scala.language.implicitConversions + +class IncorrectProofsTests extends ProofCheckerSuite { + // These tests help to ensure that the proof checker correctly rejects incorrect proofs + // The cases are designed to be almost correct (but still incorrect) proofs to check various parts of the system + + test("Minimal incorrect proofs") { + // Shorthand + implicit def proofStepToProof(proofStep: SCProofStep): SCProof = SCProof(proofStep) + + val (fl, gl, hl) = (SchematicPredicateLabel("f", 0), SchematicPredicateLabel("g", 0), SchematicPredicateLabel("h", 0)) + val f = PredicateFormula(fl, Seq.empty) // Some arbitrary formulas + val g = PredicateFormula(gl, Seq.empty) + val h = PredicateFormula(hl, Seq.empty) + + val emptySetAxiom = lisa.settheory.AxiomaticSetTheory.emptySetAxiom + val pairAxiom = lisa.settheory.AxiomaticSetTheory.pairAxiom + + + val incorrectProofs: Seq[SCProof] = List( + SCProof( + Hypothesis(emptySeq +< (x === x) +> (x === x), x === x), + LeftRefl(emptySeq +> (x === y), 0, x === x) + ), + SCProof( + Hypothesis(emptySeq +< (x === y) +> (x === y), x === y), + LeftRefl(emptySeq +> (x === y), 0, x === y) + ), + RightRefl(emptySeq +> (x === y), x === y), + RightRefl(emptySeq +> f, x === x), + RightRefl(emptySeq +> (x === x), f), + // Correct proof would be: x=y |- x=y \ x=y,x=z |- z=y + + SCProof( + Hypothesis(emptySeq +< (x === y) +> (x === y), x === y), + RightSubstEq(emptySeq +< (x === y) +< (x === z) +> (z === y), 0, x, z, x === y, yl) // wrong variable replaced + ), + SCProof( + Hypothesis(emptySeq +< (x === y) +> (x === y), x === y), + RightSubstEq(emptySeq +< (x === y) +> (z === y), 0, x, z, x === y, xl) // missing hypothesis + ), + SCProof( + Hypothesis(emptySeq +< (x === y) +> (x === y), x === y), + RightSubstEq(emptySeq +< (x === y) +< (x === z) +> (z === y), 0, x, z, x === z, xl) // replacement mismatch + ), + SCProof( + Hypothesis(emptySeq +< (x === y) +> (x === y), x === y), + LeftSubstEq(emptySeq +< (z === y) +< (x === z) +> (x === y), 0, x, z, x === y, yl) + ), + SCProof( + Hypothesis(emptySeq +< (f <=> g) +> (f <=> g), f <=> g), + LeftSubstIff(emptySeq +< (h <=> g) +< (f <=> h) +> (f <=> g), 0, f, h, f <=> g, gl) + ), + SCProof( + Hypothesis(emptySeq +< f +> f, f), + LeftAnd(emptySeq +< g +> f, 0, f, g) + ), + SCProof( + Hypothesis(emptySeq +< f +> f, f), + Hypothesis(emptySeq +< g +> g, g), + LeftImplies(emptySeq +< (g ==> f) +< f +> g, 0, 1, f, g) + ), + SCProof( + Hypothesis(emptySeq +< f +> f, f), + Hypothesis(emptySeq +< g +> g, g), + LeftOr(emptySeq +< (f \/ g) +< f +> g, Seq(0, 1), Seq(f, g)) + ), + SCProof( + Hypothesis(emptySeq +< f +> f, f), + LeftNot(emptySeq +< f +> f, 0, f) + ), + SCProof( + Hypothesis(emptySeq +< f +> f, f), + Hypothesis(emptySeq +< g +> g, g), + RightAnd(emptySeq +< f +> (f /\ g), Seq(0, 1), Seq(f, g)) // missing left g + ), + SCProof( + Hypothesis(emptySeq +< f +> f, f), + RightOr(emptySeq +< f +> (f \/ g) +> g, 0, f, g) // supplemental right g + ) + + ) + + incorrectProofs.foreach(checkIncorrectProof) + } + +} diff --git a/src/test/scala/lisa/kernel/PrinterTest.scala b/src/test/scala/lisa/kernel/PrinterTest.scala new file mode 100644 index 0000000000000000000000000000000000000000..23edf5a24b16f47616809d8c9171255976e4800b --- /dev/null +++ b/src/test/scala/lisa/kernel/PrinterTest.scala @@ -0,0 +1,43 @@ +package lisa.kernel + +import lisa.kernel.fol.FOL._ +import lisa.kernel.Printer._ + +import org.scalatest.funsuite.AnyFunSuite + +class PrinterTest extends AnyFunSuite { + + val (a, b, c) = (ConstantPredicateLabel("a", 0), ConstantPredicateLabel("b", 0), ConstantPredicateLabel("c", 0)) + val (x, y, z) = (VariableLabel("x"), VariableLabel("y"), VariableLabel("z")) + + given Conversion[PredicateLabel, PredicateFormula] = PredicateFormula(_, Seq.empty) + given Conversion[VariableLabel, VariableTerm] = VariableTerm.apply + + test("Minimal parenthesization") { + assert(prettyFormula(ConnectorFormula(And, Seq(a, b))) == "a ∧ b") + assert(prettyFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "(a ∧ b) ∧ c") + assert(prettyFormula(ConnectorFormula(And, Seq(a, ConnectorFormula(And, Seq(b, c))))) == "a ∧ b ∧ c") + assert(prettyFormula(ConnectorFormula(Or, Seq(a, ConnectorFormula(And, Seq(b, c))))) == "a ∨ b ∧ c") + assert(prettyFormula(ConnectorFormula(Or, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "a ∧ b ∨ c") + assert(prettyFormula(ConnectorFormula(And, Seq(ConnectorFormula(Or, Seq(a, b)), c))) == "(a ∨ b) ∧ c") + assert(prettyFormula(ConnectorFormula(And, Seq(a, ConnectorFormula(Or, Seq(b, c))))) == "a ∧ (b ∨ c)") + + assert(prettyFormula(ConnectorFormula(Neg, Seq(a))) == "¬a") + assert(prettyFormula(ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(a))))) == "¬¬a") + assert(prettyFormula(ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(ConnectorFormula(And, Seq(a, b))))))) == "¬¬(a ∧ b)") + assert(prettyFormula(ConnectorFormula(And, Seq(ConnectorFormula(Neg, Seq(a)), ConnectorFormula(And, Seq(ConnectorFormula(Neg, Seq(b)), ConnectorFormula(Neg, Seq(c))))))) == "¬a ∧ ¬b ∧ ¬c") + + assert(prettyFormula(ConnectorFormula(And, Seq(a, PredicateFormula(equality, Seq(x, x))))) == "a ∧ (x = x)") + + assert(prettyFormula(BinderFormula(Forall, x, PredicateFormula(equality, Seq(x, x)))) == "∀x. x = x") + assert(prettyFormula(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, PredicateFormula(equality, Seq(x, x)))))) == "a ∧ ∀x. x = x") + assert(prettyFormula(ConnectorFormula(And, Seq(BinderFormula(Forall, x, b), a))) == "(∀x. b) ∧ a") + assert(prettyFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, b))), a))) == "(a ∧ ∀x. b) ∧ a") + assert(prettyFormula(ConnectorFormula(Or, Seq(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, b))), a))) == "a ∧ (∀x. b) ∨ a") + + assert(prettyFormula(BinderFormula(Forall, x, BinderFormula(Exists, y, BinderFormula(ExistsOne, z, a)))) == "∀x. ∃y. ∃!z. a") + + assert(prettyFormula(PredicateFormula(ConstantPredicateLabel("f", 2), Seq(x, y, z))) == "f(x, y, z)") + } + +} diff --git a/src/test/scala/lisa/kernel/ProofTests.scala b/src/test/scala/lisa/kernel/ProofTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..d2a91582516ff99e34a851dcdef73712570788cd --- /dev/null +++ b/src/test/scala/lisa/kernel/ProofTests.scala @@ -0,0 +1,63 @@ +package lisa.kernel + +import lisa.KernelHelpers.{*, given} +import lisa.kernel.Printer +import lisa.kernel.proof.RunningTheory +import org.scalatest.funsuite.AnyFunSuite +import lisa.kernel.proof.SequentCalculus.* +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.RunningTheory.* +import lisa.kernel.proof.{SCProof, SCProofChecker} + +import scala.util.Random + + +class ProofTests extends AnyFunSuite { + val predicateVerifier = SCProofChecker.checkSCProof + + private val x = VariableLabel("x") + private val y = VariableLabel("y") + private val z = VariableLabel("z") + private val a = PredicateFormula(ConstantPredicateLabel("A", 0), Seq()) + private val b = PredicateFormula(ConstantPredicateLabel("B", 0), Seq()) + private val fp = ConstantPredicateLabel("F", 1) + val sT = SchematicFunctionLabel("t", 0) + + + test("Verification of Pierce law") { + val s0 = Hypothesis( a |- a, a) + val s1 = Weakening(a |- Set(a,b), 0) + val s2 = RightImplies(() |- Set(a,a==>b), 1, a, b) + val s3 = LeftImplies((a ==> b) ==> a |- a, 2, 0, a ==> b, a) + val s4 = RightImplies(() |- (a ==> b) ==> a ==> a, 3, (a ==> b) ==> a, a) + val ppl: SCProof = SCProof(IndexedSeq(s0, s1, s2, s3, s4)) + assert(predicateVerifier(ppl)._1) + } + + test("Verification of substitution") { + val t0 = Hypothesis(fp(x)|-fp(x), fp(x)) + val t1 = RightSubstEq(Set(fp(x), x === y) |- fp(y), 0, x, y, fp(sT()), sT) + val pr = new SCProof(IndexedSeq(t0, t1)) + assert(predicateVerifier(pr)._1) + } + + test("Commutativity on a random large formula") { + val k = 9 + val r = new Random() + val vars = (0 until 1 << k).map(i => PredicateFormula(ConstantPredicateLabel(s"P$i", 0), Seq())) + + val pairs = vars.grouped(2) + val sPairs = vars.grouped(2) + var subformulas = pairs.map(p => or(p.head, p.last)).grouped(2) + var subformulasSwapped = sPairs.map(p => if (r.nextBoolean()) or(p.head, p.last) else or(p.last, p.head)).grouped(2) + for (i <- 1 until k) { + val op = if (i % 2 == 0) or _ else and _ + subformulas = subformulas.map(sf => op(sf.head, sf.last)).grouped(2) + subformulasSwapped = subformulasSwapped.map(sf => if (r.nextBoolean()) op(sf.head, sf.last) else op(sf.last, sf.head)).grouped(2) + } + val orig = subformulas.next().head + val swapped = subformulasSwapped.next().head + val prf = SCProof(Vector(Hypothesis(Sequent(Set(orig), Set(orig)), orig), Rewrite(Sequent(Set(orig), Set(swapped)), 0))) + assert(predicateVerifier(prf)._1) + } +} diff --git a/src/test/scala/lisa/proven/ElementsOfSetTheoryTests.scala b/src/test/scala/lisa/proven/ElementsOfSetTheoryTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..efdbd45fa6a0d46617264e4882f3eaaa7b6b95f0 --- /dev/null +++ b/src/test/scala/lisa/proven/ElementsOfSetTheoryTests.scala @@ -0,0 +1,76 @@ +package proven + +import lisa.kernel.Printer +import lisa.settheory.AxiomaticSetTheory.* +import test.ProofCheckerSuite +import proven.tactics.ProofTactics +import lisa.kernel.proof.SCProof + +class ElementsOfSetTheoryTests extends ProofCheckerSuite { + import proven.ElementsOfSetTheory.* + import lisa.kernel.proof.SequentCalculus.Sequent + import lisa.kernel.fol.FOL.* + import lisa.KernelHelpers.{*, given} + import lisa.kernel.proof.SequentCalculus.* + + test("Verification of the proof of symmetry of the pair") { + checkProof(proofUnorderedPairSymmetry, emptySeq +> forall(vl, forall(ul, pair(u, v) === pair(v, u)))) + } + + test("Verification of the proof of deconstruction of the unordered pair") { + checkProof(proofUnorderedPairDeconstruction, emptySeq +> + forall(sl, forall(tl, forall(ul, forall(vl, + (pair(s, t) === pair(xp, v)) ==> + (((VariableTerm(v) === t) /\ (VariableTerm(u) === s)) \/ ((VariableTerm(s) === v) /\ (VariableTerm(t) === u))))))) + ) + } + + // forall x, y. (x in {y, y}) <=> (x = y) + val singletonSetEqualityProof: SCProof = { + val t0 = SCSubproof({ + val t0 = Rewrite(()|-pairAxiom, -1) + val p0 = ProofTactics.instantiateForall(SCProof(IndexedSeq(t0), IndexedSeq(()|-pairAxiom)), t0.bot.right.head, y) + val p1 = ProofTactics.instantiateForall(p0, p0.conclusion.right.head, y) + val p2:SCProof = ProofTactics.instantiateForall(p1, p1.conclusion.right.head, x) + p2 + } ) + + // |- f <=> (f \/ f) + def selfIffOr(f: Formula): SCSubproof = SCSubproof({ + val t0 = Hypothesis(Sequent(Set(f), Set(f)), f) + val t1 = RightOr(Sequent(Set(f), Set(f \/ f)), 0, f, f) + val t2 = LeftOr(Sequent(Set(f \/ f), Set(f)), Seq(0, 0), Seq(f, f)) + val t3 = RightImplies(Sequent(Set(), Set(f ==> (f \/ f))), 1, f, f \/ f) + val t4 = RightImplies(Sequent(Set(), Set((f \/ f) ==> f)), 2, f \/ f, f) + val t5 = RightIff(Sequent(Set(), Set(f <=> (f \/ f))), 3, 4, f, f \/ f) + SCProof(IndexedSeq(t0, t1, t2, t3, t4, t5)) + }) + + val t1 = selfIffOr(y === x) + + val xy = x === x + val h = SchematicPredicateLabel("h", 0) + val t2 = RightSubstIff(Sequent(Set((xy \/ xy) <=> xy), Set(xy <=> in(x, pair(y, y)))), 0, + (y === x) \/ (y === x), + y === x, + in(x, pair(y, y)) <=> PredicateFormula(h, Seq.empty), + h) + val t3 = Cut(Sequent(Set(), Set(xy <=> in(x, pair(y, y)))), 1, 2, (xy \/ xy) <=> xy) + + val p0 = SCProof(IndexedSeq(t0, t1, t2, t3)) + + ProofTactics.generalizeToForall(ProofTactics.generalizeToForall(p0, ul), vl) + } + + test("Singleton set equality") { + checkProof(singletonSetEqualityProof, emptySeq +> forall(vl, forall(ul, (u === v) <=> in(u, pair(v, v))))) + } + + val proofSingletonDefinition: SCProof = ProofTactics.simpleFunctionDefinition(singleton, pair(u, u), Seq(ul)) + + test("Verify singleton set definition") { + checkProof(proofSingletonDefinition) + } + + +} diff --git a/src/test/scala/lisa/proven/SimpleProverTests.scala b/src/test/scala/lisa/proven/SimpleProverTests.scala new file mode 100644 index 0000000000000000000000000000000000000000..8f88cc953bed2e22bcb571b011f690d4bd645244 --- /dev/null +++ b/src/test/scala/lisa/proven/SimpleProverTests.scala @@ -0,0 +1,32 @@ +package proven + +import lisa.kernel.Printer.* +import lisa.kernel.proof.{RunningTheory, SCProofChecker} +import lisa.kernel.fol.FOL.* +import lisa.KernelHelpers.* +import lisa.kernel.Printer +import lisa.kernel.proof.RunningTheory.PredicateLogic +import org.scalatest.funsuite.AnyFunSuite +import tptp.ProblemGatherer.getPRPproblems +import tptp.KernelParser.* +import proven.tactics.SimplePropositionalSolver as SPS + + +class SimpleProverTests extends AnyFunSuite { + + test("Simple propositional logic proofs") { + val problems = getPRPproblems.take(1) + + problems.foreach( pr => { + println("--- Problem "+pr.file) + val sq = problemToSequent(pr) + println(prettySequent(sq)) + val proof = SPS.solveSequent(sq) + if (!Seq("Unsatisfiable", "Theorem", "Satisfiable").contains(pr.status)) println("Unknown status: "+pr.status+", "+pr.file) + + assert(SCProofChecker.checkSCProof(proof)._1 == (pr.status =="Unsatisfiable" || pr.status == "Theorem")) + + }) + + } +} diff --git a/src/test/scala/test/ProofCheckerSuite.scala b/src/test/scala/test/ProofCheckerSuite.scala new file mode 100644 index 0000000000000000000000000000000000000000..df8786aac3c0d1722d32f116de201259402add4b --- /dev/null +++ b/src/test/scala/test/ProofCheckerSuite.scala @@ -0,0 +1,37 @@ +package test + +import lisa.kernel.Printer +import lisa.kernel.proof.SequentCalculus.{Sequent, isSameSequent} +import lisa.kernel.proof.SCProofChecker._ +import lisa.settheory.AxiomaticSetTheory +import org.scalatest.funsuite.AnyFunSuite +import lisa.kernel.proof.SCProof +import lisa.KernelHelpers._ +import lisa.KernelHelpers.given_Conversion_Boolean_List_String_Option + +abstract class ProofCheckerSuite extends AnyFunSuite { + import lisa.kernel.fol.FOL.* + + protected val (xl, yl, zl, wl, xpl, ypl, zpl, wpl) = (SchematicFunctionLabel("x", 0), SchematicFunctionLabel("y", 0), SchematicFunctionLabel("z", 0), SchematicFunctionLabel("w", 0), SchematicFunctionLabel("x'", 0), SchematicFunctionLabel("y'", 0), SchematicFunctionLabel("z'", 0), SchematicFunctionLabel("w'", 0)) + protected val (x, y, z, w, xp, yp, zp, wp) = (FunctionTerm(xl, Seq.empty), FunctionTerm(yl, Seq.empty), FunctionTerm(zl, Seq.empty), FunctionTerm(wl, Seq.empty), FunctionTerm(xpl, Seq.empty), FunctionTerm(ypl, Seq.empty), FunctionTerm(zpl, Seq.empty), FunctionTerm(wpl, Seq.empty)) + + protected val (sl, tl, ul, vl) = (VariableLabel("s"), VariableLabel("t"), VariableLabel("u"), VariableLabel("v")) + protected val (s, t, u, v) = (VariableTerm(sl), VariableTerm(tl), VariableTerm(ul), VariableTerm(vl)) + + val axioms = AxiomaticSetTheory.axioms + def checkProof(proof: SCProof): Unit = { + val error = checkSCProof(proof) + println(Printer.prettySCProof(proof, error)) + println(s"\n(${proof.totalLength} proof steps in total)") + } + + def checkProof(proof: SCProof, expected: Sequent): Unit = { + val error = checkSCProof(proof) + assert(error._1, "\n"+Printer.prettySCProof(proof, error)) + assert(isSameSequent(proof.conclusion, expected), s"(${Printer.prettySequent(proof.conclusion)} did not equal ${Printer.prettySequent(expected)})") + } + + def checkIncorrectProof(incorrectProof: SCProof): Unit = { + assert(!checkSCProof(incorrectProof)._1, s"(incorrect proof with conclusion '${Printer.prettySequent(incorrectProof.conclusion)}' was accepted by the proof checker)\nSequent: ${incorrectProof.conclusion}") + } +}