diff --git a/build.sbt b/build.sbt index 4a3136c13..12f553287 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,7 @@ ThisBuild / semanticdbEnabled := true ThisBuild / semanticdbVersion := scalafixSemanticdb.revision val scala2 = "2.13.8" -val scala3 = "3.5.1" +val scala3 = "3.5.2" val commonSettings = Seq( crossScalaVersions := Seq(scala3), @@ -32,7 +32,8 @@ val commonSettings3 = commonSettings ++ Seq( scalacOptions ++= Seq( "-language:implicitConversions", //"-rewrite", "-source", "3.4-migration", - "-Wconf:msg=.*will never be selected.*:silent" + "-Wconf:msg=.*will never be selected.*:silent", + "-language:experimental.modularity" ), javaOptions += "-Xmx10G", @@ -75,6 +76,13 @@ lazy val sets = Project( .settings(commonSettings3) .dependsOn(kernel, withTests(utils)) +lazy val sets2 = Project( + id = "lisa-sets2", + base = file("lisa-sets2") +) + .settings(commonSettings3) + .dependsOn(kernel, withTests(utils)) + lazy val utils = Project( id = "lisa-utils", base = file("lisa-utils") diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala deleted file mode 100644 index 6f65ffdfa..000000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala +++ /dev/null @@ -1,57 +0,0 @@ -package lisa.kernel.fol - -/** - * Definitions that are common to terms and formulas. - */ -private[fol] trait CommonDefinitions { - val MaxArity: Int = 1000000 - - /** - * A labelled node for tree-like structures. - */ - trait Label { - val arity: Int - val id: Identifier - } - - sealed case class Identifier(val name: String, val no: Int) { - require(no >= 0, "Variable index must be positive") - require(Identifier.isValidIdentifier(name), "Variable name " + name + "is not valid.") - override def toString: String = if (no == 0) name else name + Identifier.counterSeparator + no - } - object Identifier { - def unapply(i: Identifier): Option[(String, Int)] = Some((i.name, i.no)) - def apply(name: String): Identifier = new Identifier(name, 0) - def apply(name: String, no: Int): Identifier = new Identifier(name, no) - - val counterSeparator: Char = '_' - val delimiter: Char = '`' - val forbiddenChars: Set[Char] = ("()[]{}?,;" + delimiter + counterSeparator).toSet - def isValidIdentifier(s: String): Boolean = s.forall(c => !forbiddenChars.contains(c) && !c.isWhitespace) - } - - /** - * return am identifier that is different from a set of give identifier. - * @param taken ids which are not available - * @param base prefix of the new id - * @return a fresh id. - */ - private[kernel] def freshId(taken: Iterable[Identifier], base: Identifier): Identifier = { - new Identifier( - base.name, - (taken.collect({ case Identifier(base.name, no) => - no - }) ++ Iterable(base.no)).max + 1 - ) - } - - /** - * A label for constant (non-schematic) symbols in formula and terms - */ - trait ConstantLabel extends Label - - /** - * A schematic label in a formula or a term can be substituted by any formula or term of the adequate kind. - */ - trait SchematicLabel extends Label -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala deleted file mode 100644 index d323f6219..000000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala +++ /dev/null @@ -1,810 +0,0 @@ -package lisa.kernel.fol - -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. - * See https://github.com/epfl-lara/OCBSL for more informations - */ -private[fol] trait EquivalenceChecker extends FormulaDefinitions { - - def reducedForm(formula: Formula): Formula = { - val p = simplify(formula) - val nf = computeNormalForm(p) - val fln = fromLocallyNameless(nf, Map.empty, 0) - val res = toFormulaAIG(fln) - res - } - - def reducedNNFForm(formula: Formula): Formula = { - val p = simplify(formula) - val nf = computeNormalForm(p) - val fln = fromLocallyNameless(nf, Map.empty, 0) - val res = toFormulaNNF(fln) - res - } - def reduceSet(s: Set[Formula]): Set[Formula] = { - var res: List[Formula] = Nil - s.map(reducedForm) - .foreach({ f => - if (!res.exists(isSame(f, _))) res = f :: res - }) - res.toSet - } - - def isSameTerm(term1: Term, term2: Term): Boolean = term1.label == term2.label && term1.args.lazyZip(term2.args).forall(isSameTerm) - def isSame(formula1: Formula, formula2: Formula): Boolean = { - val nf1 = computeNormalForm(simplify(formula1)) - val nf2 = computeNormalForm(simplify(formula2)) - latticesEQ(nf1, nf2) - } - - /** - * returns true if the first argument implies the second by the laws of ortholattices. - */ - def isImplying(formula1: Formula, formula2: Formula): Boolean = { - val nf1 = computeNormalForm(simplify(formula1)) - val nf2 = computeNormalForm(simplify(formula2)) - latticesLEQ(nf1, nf2) - } - - def isSubset(s1: Set[Formula], s2: Set[Formula]): Boolean = { - s1.forall(f1 => s2.exists(g1 => isSame(f1, g1))) - } - def isSameSet(s1: Set[Formula], s2: Set[Formula]): Boolean = - isSubset(s1, s2) && isSubset(s2, s1) - - def isSameSetL(s1: Set[Formula], s2: Set[Formula]): Boolean = - isSame(ConnectorFormula(And, s1.toSeq), ConnectorFormula(And, s2.toSeq)) - - def isSameSetR(s1: Set[Formula], s2: Set[Formula]): Boolean = - isSame(ConnectorFormula(Or, s2.toSeq), ConnectorFormula(Or, s1.toSeq)) - - def contains(s: Set[Formula], f: Formula): Boolean = { - s.exists(g => isSame(f, g)) - } - - private var totPolarFormula = 0 - sealed abstract class SimpleFormula { - val uniqueKey: Int = totPolarFormula - totPolarFormula += 1 - val size: Int - var inverse: Option[SimpleFormula] = None - private[EquivalenceChecker] var normalForm: Option[NormalFormula] = None - def getNormalForm = normalForm - } - case class SimplePredicate(id: AtomicLabel, args: Seq[Term], polarity: Boolean) extends SimpleFormula { - override def toString: String = s"SimplePredicate($id, $args, $polarity)" - val size = 1 - } - case class SimpleSchemConnector(id: SchematicConnectorLabel, args: Seq[SimpleFormula], polarity: Boolean) extends SimpleFormula { - val size = 1 - } - case class SimpleAnd(children: Seq[SimpleFormula], polarity: Boolean) extends SimpleFormula { - val size: Int = (children map (_.size)).foldLeft(1) { case (a, b) => a + b } - } - case class SimpleForall(x: Identifier, inner: SimpleFormula, polarity: Boolean) extends SimpleFormula { - val size: Int = 1 + inner.size - } - case class SimpleLiteral(polarity: Boolean) extends SimpleFormula { - val size = 1 - normalForm = Some(NormalLiteral(polarity)) - } - - private var totNormalFormula = 0 - sealed abstract class NormalFormula { - val uniqueKey: Int = totNormalFormula - totNormalFormula += 1 - var formulaP: Option[Formula] = None - var formulaN: Option[Formula] = None - var formulaAIG: Option[Formula] = None - var inverse: Option[NormalFormula] = None - - private val lessThanBitSet: mutable.Set[Long] = new mutable.HashSet() - setLessThanCache(this, true) - - def lessThanCached(other: NormalFormula): Option[Boolean] = { - val otherIx = 2 * other.uniqueKey - if (lessThanBitSet.contains(otherIx)) Some(lessThanBitSet.contains(otherIx + 1)) - else None - } - - def setLessThanCache(other: NormalFormula, value: Boolean): Unit = { - val otherIx = 2 * other.uniqueKey - lessThanBitSet.contains(otherIx) - if (value) lessThanBitSet.update(otherIx + 1, true) - } - - def recoverFormula: Formula = toFormulaAIG(this) - } - sealed abstract class NonTraversable extends NormalFormula - case class NormalPredicate(id: AtomicLabel, args: Seq[Term], polarity: Boolean) extends NonTraversable { - override def toString: String = s"NormalPredicate($id, $args, $polarity)" - } - case class NormalSchemConnector(id: SchematicConnectorLabel, args: Seq[NormalFormula], polarity: Boolean) extends NonTraversable - case class NormalAnd(args: Seq[NormalFormula], polarity: Boolean) extends NormalFormula - case class NormalForall(x: Identifier, inner: NormalFormula, polarity: Boolean) extends NonTraversable - case class NormalLiteral(polarity: Boolean) extends NormalFormula - - /** - * Puts back in regular formula syntax, in an AIG (without disjunctions) format - */ - def toFormulaAIG(f: NormalFormula): Formula = - if (f.formulaAIG.isDefined) f.formulaAIG.get - else { - val r: Formula = f match { - case NormalPredicate(id, args, polarity) => - if (polarity) AtomicFormula(id, args) else ConnectorFormula(Neg, Seq(AtomicFormula(id, args))) - case NormalSchemConnector(id, args, polarity) => - val f = ConnectorFormula(id, args.map(toFormulaAIG)) - if (polarity) f else ConnectorFormula(Neg, Seq(f)) - case NormalAnd(args, polarity) => - val f = ConnectorFormula(And, args.map(toFormulaAIG)) - if (polarity) f else ConnectorFormula(Neg, Seq(f)) - case NormalForall(x, inner, polarity) => - val f = BinderFormula(Forall, VariableLabel(x), toFormulaAIG(inner)) - if (polarity) f else ConnectorFormula(Neg, Seq(f)) - case NormalLiteral(polarity) => if (polarity) AtomicFormula(top, Seq()) else AtomicFormula(bot, Seq()) - } - f.formulaAIG = Some(r) - r - } - - /** - * Puts back in regular formula syntax, and performs negation normal form to produce shorter version. - */ - def toFormulaNNF(f: NormalFormula, positive: Boolean = true): Formula = { - if (positive){ - if (f.formulaP.isDefined) return f.formulaP.get - if (f.inverse.isDefined && f.inverse.get.formulaN.isDefined) return f.inverse.get.formulaN.get - } - else if (!positive) { - if (f.formulaN.isDefined) return f.formulaN.get - if (f.inverse.isDefined && f.inverse.get.formulaP.isDefined) return f.inverse.get.formulaP.get - } - val r = f match{ - case NormalPredicate(id, args, polarity) => - if (positive==polarity) AtomicFormula(id, args) else ConnectorFormula(Neg, Seq(AtomicFormula(id, args))) - case NormalSchemConnector(id, args, polarity) => - val f = ConnectorFormula(id, args.map(toFormulaNNF(_, true))) - if (positive==polarity) f else ConnectorFormula(Neg, Seq(f)) - case NormalAnd(args, polarity) => - if (positive==polarity) - ConnectorFormula(And, args.map(c => toFormulaNNF(c, true))) - else - ConnectorFormula(Or, args.map(c => toFormulaNNF(c, false))) - case NormalForall(x, inner, polarity) => - if (positive==polarity) - BinderFormula(Forall, VariableLabel(x), toFormulaNNF(inner, true)) - else - BinderFormula(Exists, VariableLabel(x), toFormulaNNF(inner, false)) - case NormalLiteral(polarity) => if (polarity) AtomicFormula(top, Seq()) else AtomicFormula(bot, Seq()) - } - if (positive) f.formulaP = Some(r) - else f.formulaN = Some(r) - r - } - - /** - * Inverse a formula in Polar normal form. Corresponds semantically to taking the negation of the formula. - */ - def getInversePolar(f: SimpleFormula): SimpleFormula = { - f.inverse match { - case Some(value) => value - case None => - val second = f match { - case f: SimplePredicate => f.copy(polarity = !f.polarity) - case f: SimpleSchemConnector => f.copy(polarity = !f.polarity) - case f: SimpleAnd => f.copy(polarity = !f.polarity) - case f: SimpleForall => f.copy(polarity = !f.polarity) - case f: SimpleLiteral => f.copy(polarity = !f.polarity) - } - f.inverse = Some(second) - second.inverse = Some(f) - second - } - } - - /** - * Inverse a formula in Polar normal form. Corresponds semantically to taking the negation of the formula. - */ - def getInverse(f: NormalFormula): NormalFormula = { - f.inverse match { - case Some(value) => value - case None => - val second: NormalFormula = f match { - case f: NormalPredicate => f.copy(polarity = !f.polarity) - case f: NormalSchemConnector => f.copy(polarity = !f.polarity) - case f: NormalAnd => f.copy(polarity = !f.polarity) - case f: NormalForall => f.copy(polarity = !f.polarity) - case f: NormalLiteral => f.copy(polarity = !f.polarity) - } - f.inverse = Some(second) - second.inverse = Some(f) - second - } - } - - /** - * Put a formula in Polar form, which means desugared. In this normal form, the only (non-schematic) symbol is - * conjunction, the only binder is universal, and negations are flattened - * @param f The formula that has to be transformed - * @param polarity If the formula is in a positive or negative context. It is usually true. - * @return The corresponding PolarFormula - */ - def polarize(f: Formula, polarity: Boolean): SimpleFormula = { - if (polarity & f.polarFormula.isDefined) { - f.polarFormula.get - } else if (!polarity & f.polarFormula.isDefined) { - getInversePolar(f.polarFormula.get) - } else { - val r = f match { - case AtomicFormula(label, args) => - if (label == top) SimpleLiteral(polarity) - else if (label == bot) SimpleLiteral(!polarity) - else SimplePredicate(label, args, polarity) - case ConnectorFormula(label, args) => - label match { - case cl: ConstantConnectorLabel => - cl match { - case Neg => polarize(args.head, !polarity) - case Implies => SimpleAnd(Seq(polarize(args(0), true), polarize(args(1), false)), !polarity) - case Iff => - val l1 = polarize(args(0), true) - val r1 = polarize(args(1), true) - SimpleAnd( - Seq( - SimpleAnd(Seq(l1, getInversePolar(r1)), false), - SimpleAnd(Seq(getInversePolar(l1), r1), false) - ), - polarity - ) - case And => - SimpleAnd(args.map(polarize(_, true)), polarity) - case Or => SimpleAnd(args.map(polarize(_, false)), !polarity) - } - case scl: SchematicConnectorLabel => - SimpleSchemConnector(scl, args.map(polarize(_, true)), polarity) - } - case BinderFormula(label, bound, inner) => - label match { - case Forall => SimpleForall(bound.id, polarize(inner, true), polarity) - case Exists => SimpleForall(bound.id, polarize(inner, false), !polarity) - case ExistsOne => - val y = VariableLabel(freshId(inner.freeVariables.map(_.id), bound.id)) - val c = AtomicFormula(equality, Seq(VariableTerm(bound), VariableTerm(y))) - val newInner = polarize(ConnectorFormula(Iff, Seq(c, inner)), true) - SimpleForall(y.id, SimpleForall(bound.id, newInner, false), !polarity) - } - } - if (polarity) f.polarFormula = Some(r) - else f.polarFormula = Some(getInversePolar(r)) - r - } - } - - def toLocallyNameless(t: Term, subst: Map[Identifier, Int], i: Int): Term = { - t match { - case Term(label: VariableLabel, _) => - if (subst.contains(label.id)) VariableTerm(VariableLabel(Identifier("x", i - subst(label.id)))) - else VariableTerm(VariableLabel(Identifier("$" + label.id.name, label.id.no))) - case Term(label, args) => Term(label, args.map(c => toLocallyNameless(c, subst, i))) - } - } - - def toLocallyNameless(phi: SimpleFormula, subst: Map[Identifier, Int], i: Int): SimpleFormula = { - phi match { - case SimplePredicate(id, args, polarity) => SimplePredicate(id, args.map(c => toLocallyNameless(c, subst, i)), polarity) - case SimpleSchemConnector(id, args, polarity) => SimpleSchemConnector(id, args.map(f => toLocallyNameless(f, subst, i)), polarity) - case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless(_, subst, i)), polarity) - case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless(inner, subst + (x -> i), i + 1), polarity) - case SimpleLiteral(polarity) => phi - } - } - - def fromLocallyNameless(t: Term, subst: Map[Int, Identifier], i: Int): Term = { - - t match { - case Term(label: VariableLabel, _) => - if ((label.id.name == "x") && subst.contains(i - label.id.no)) VariableTerm(VariableLabel(subst(i - label.id.no))) - else if (label.id.name.head == '$') VariableTerm(VariableLabel(Identifier(label.id.name.tail, label.id.no))) - else { - throw new Exception("This case should be unreachable, error") - } - case Term(label, args) => Term(label, args.map(c => fromLocallyNameless(c, subst, i))) - } - } - - def fromLocallyNameless(phi: NormalFormula, subst: Map[Int, Identifier], i: Int): NormalFormula = { - phi match { - case NormalPredicate(id, args, polarity) => NormalPredicate(id, args.map(c => fromLocallyNameless(c, subst, i)), polarity) - case NormalSchemConnector(id, args, polarity) => NormalSchemConnector(id, args.map(f => fromLocallyNameless(f, subst, i)), polarity) - case NormalAnd(children, polarity) => NormalAnd(children.map(fromLocallyNameless(_, subst, i)), polarity) - case NormalForall(x, inner, polarity) => NormalForall(x, fromLocallyNameless(inner, subst + (i -> x), i + 1), polarity) - case NormalLiteral(_) => phi - } - - } - - def simplify(f: Formula): SimpleFormula = toLocallyNameless(polarize(f, polarity = true), Map.empty, 0) - - def computeNormalForm(formula: SimpleFormula): NormalFormula = { - formula.normalForm match { - case Some(value) => - value - case None => - val r = formula match { - case SimplePredicate(id, args, true) => - if (id == equality) { - if (args(0) == args(1)) - NormalLiteral(true) - else - NormalPredicate(id, args.sortBy(_.hashCode()), true) - } else NormalPredicate(id, args, true) - case SimplePredicate(id, args, false) => - getInverse(computeNormalForm(getInversePolar(formula))) - case SimpleSchemConnector(id, args, true) => - NormalSchemConnector(id, args.map(computeNormalForm), true) - case SimpleSchemConnector(id, args, false) => - getInverse(computeNormalForm(getInversePolar(formula))) - case SimpleAnd(children, polarity) => - val newChildren = children map computeNormalForm - val simp = reduce(newChildren, polarity) - simp match { - case conj: NormalAnd if checkForContradiction(conj) => - NormalLiteral(!polarity) - case _ => - simp - } - case SimpleForall(x, inner, polarity) => NormalForall(x, computeNormalForm(inner), polarity) - case SimpleLiteral(polarity) => NormalLiteral(polarity) - } - formula.normalForm = Some(r) - r - - } - } - def reduceList(children: Seq[NormalFormula], polarity: Boolean): List[NormalFormula] = { - val nonSimplified = NormalAnd(children, polarity) - var remaining: Seq[NormalFormula] = Nil - def treatChild(i: NormalFormula): Seq[NormalFormula] = { - val r: Seq[NormalFormula] = i match { - case NormalAnd(ch, true) => ch - case NormalAnd(ch, false) => - if (polarity) { - val trCh = ch map getInverse - trCh.find(f => { - latticesLEQ(nonSimplified, f) - }) match { - case Some(value) => - treatChild(value) - case None => List(i) - } - } else { - val trCh = ch - trCh.find(f => { - latticesLEQ(f, nonSimplified) - }) match { - case Some(value) => - treatChild(getInverse(value)) - case None => List(i) - } - } - case _ => List(i) - } - r - } - children.foreach(i => { - val r = treatChild(i) - remaining = r ++ remaining - }) - - var accepted: List[NormalFormula] = Nil - while (remaining.nonEmpty) { - val current = remaining.head - remaining = remaining.tail - if (!latticesLEQ(NormalAnd(remaining ++ accepted, true), current)) { - accepted = current :: accepted - } - } - accepted - } - - def reduce(children: Seq[NormalFormula], polarity: Boolean): NormalFormula = { - val accepted: List[NormalFormula] = reduceList(children, polarity) - val r = { - if (accepted.isEmpty) NormalLiteral(polarity) - else if (accepted.size == 1) - if (polarity) accepted.head else getInverse(accepted.head) - else NormalAnd(accepted, polarity) - } - r - } - - def latticesEQ(formula1: NormalFormula, formula2: NormalFormula): Boolean = latticesLEQ(formula1, formula2) && latticesLEQ(formula2, formula1) - - def latticesLEQ(formula1: NormalFormula, formula2: NormalFormula): Boolean = { - if (formula1.uniqueKey == formula2.uniqueKey) true - else - formula1.lessThanCached(formula2) match { - case Some(value) => value - case None => - val r = (formula1, formula2) match { - case (NormalLiteral(b1), NormalLiteral(b2)) => !b1 || b2 - case (NormalLiteral(b), _) => !b - case (_, NormalLiteral(b)) => b - - case (NormalPredicate(id1, args1, polarity1), NormalPredicate(id2, args2, polarity2)) => - id1 == id2 && polarity1 == polarity2 && - (args1 == args2 || (id1 == equality && args1(0) == args2(1) && args1(1) == args2(0))) - case (NormalSchemConnector(id1, args1, polarity1), NormalSchemConnector(id2, args2, polarity2)) => - id1 == id2 && polarity1 == polarity2 && args1.zip(args2).forall(f => latticesEQ(f._1, f._2)) - case (NormalForall(x1, inner1, polarity1), NormalForall(x2, inner2, polarity2)) => - polarity1 == polarity2 && (if (polarity1) latticesLEQ(inner1, inner2) else latticesLEQ(inner2, inner1)) - case (_: NonTraversable, _: NonTraversable) => false - - case (_, NormalAnd(children, true)) => - children.forall(c => latticesLEQ(formula1, c)) - case (NormalAnd(children, false), _) => - children.forall(c => latticesLEQ(getInverse(c), formula2)) - case (NormalAnd(children1, true), NormalAnd(children2, false)) => - children1.exists(c => latticesLEQ(c, formula2)) || children2.exists(c => latticesLEQ(formula1, getInverse(c))) - - case (nt: NonTraversable, NormalAnd(children, false)) => - children.exists(c => latticesLEQ(nt, getInverse(c))) - case (NormalAnd(children, true), nt: NonTraversable) => - children.exists(c => latticesLEQ(c, nt)) - - } - formula1.setLessThanCache(formula2, r) - r - } - } - - def checkForContradiction(f: NormalAnd): Boolean = { - f match { - case NormalAnd(children, false) => - children.exists(cc => latticesLEQ(cc, f)) - case NormalAnd(children, true) => - val shadowChildren = children map getInverse - shadowChildren.exists(sc => latticesLEQ(f, sc)) - } - } - - /* - - /** - * A class that encapsulate "runtime" information of the equivalence checker. It performs memoization for efficiency. - */ - class LocalEquivalenceChecker2 { - - private val unsugaredVersion = scala.collection.mutable.HashMap[Formula, PolarFormula]() - // transform a LISA formula into a simpler, desugarised version with less symbols. Conjunction, implication, iff, existsOne are treated as alliases and translated. - def removeSugar1(phi: Formula): PolarFormula = { - phi match { - case AtomicFormula(label, args) => - if (label == top) PolarLiteral(true) - else if (label == bot) PolarLiteral(false) - else PolarPredicate(label, args.toList) - case ConnectorFormula(label, args) => - label match { - case Neg => SNeg(removeSugar1(args(0))) - case Implies => - val l = removeSugar1(args(0)) - val r = removeSugar1(args(1)) - PolarAnd(List(SNeg(l), r)) - case Iff => - val l = removeSugar1(args(0)) - val r = removeSugar1(args(1)) - val c1 = SNeg(PolarAnd(List(SNeg(l), r))) - val c2 = SNeg(PolarAnd(List(SNeg(r), l))) - SNeg(PolarAnd(List(c1, c2))) - case And => - SNeg(SOr(args.map(c => SNeg(removeSugar1(c))).toList)) - case Or => PolarAnd((args map removeSugar1).toList) - case _ => PolarSchemConnector(label, args.toList.map(removeSugar1)) - } - case BinderFormula(label, bound, inner) => - label match { - case Forall => PolarForall(bound.id, removeSugar1(inner)) - case Exists => SExists(bound.id, removeSugar1(inner)) - case ExistsOne => - val y = VariableLabel(freshId(inner.freeVariables.map(_.id), bound.id)) - val c1 = PolarPredicate(equality, List(VariableTerm(bound), VariableTerm(y))) - val c2 = removeSugar1(inner) - val c1_c2 = PolarAnd(List(SNeg(c1), c2)) - val c2_c1 = PolarAnd(List(SNeg(c2), c1)) - SExists(y.id, PolarForall(bound.id, SNeg(PolarAnd(List(SNeg(c1_c2), SNeg(c2_c1)))))) - } - } - } - - def toLocallyNameless(t: Term, subst: Map[Identifier, Int], i: Int): Term = { - t match { - case Term(label: VariableLabel, _) => - if (subst.contains(label.id)) VariableTerm(VariableLabel(Identifier("x", i - subst(label.id)))) - else VariableTerm(VariableLabel(Identifier("$" + label.id.name, label.id.no))) - case Term(label, args) => Term(label, args.map(c => toLocallyNameless(c, subst, i))) - } - } - - def toLocallyNameless(phi: PolarFormula, subst: Map[Identifier, Int], i: Int): PolarFormula = { - phi match { - case PolarPredicate(id, args) => PolarPredicate(id, args.map(c => toLocallyNameless(c, subst, i))) - case PolarSchemConnector(id, args) => PolarSchemConnector(id, args.map(f => toLocallyNameless(f, subst, i))) - case SNeg(child) => SNeg(toLocallyNameless(child, subst, i)) - case PolarAnd(children) => PolarAnd(children.map(toLocallyNameless(_, subst, i))) - case PolarForall(x, inner) => PolarForall(Identifier(""), toLocallyNameless(inner, subst + (x -> i), i + 1)) - case SExists(x, inner) => SExists(Identifier(""), toLocallyNameless(inner, subst + (x -> i), i + 1)) - case PolarLiteral(b) => phi - } - } - - def removeSugar(phi: Formula): PolarFormula = { - unsugaredVersion.getOrElseUpdate(phi, toLocallyNameless(removeSugar1(phi), Map.empty, 0)) - } - - 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 Term(label: VariableLabel, _) => - codesSigTerms.getOrElseUpdate((label, Nil), codesSigTerms.size) - case Term(label, args) => - val c = args map codesOfTerm - codesSigTerms.getOrElseUpdate((label, c), codesSigTerms.size) - } - ) - - def checkForContradiction(children: List[(NormalFormula, Int)]): Boolean = { - val (negatives_temp, positives_temp) = 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, positives) = (negatives_temp.sortBy(_.code), positives_temp.reverse) - 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: PolarFormula): 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: PolarFormula, acc: List[NormalFormula]): List[NormalFormula] = { - if (phi.normalForm.nonEmpty) return pDisjNormal(phi.normalForm.get, acc) - val r: List[NormalFormula] = phi match { - case PolarPredicate(label, args) => - val lab = label match { - case _: ConstantAtomicLabel => "cp_" + label.id + "_" + label.arity - case _: SchematicAtomicLabel => "sp_" + label.id + "_" + label.arity - } - if (label == top) { - phi.normalForm = Some(NLiteral(true)) - } else if (label == bot) { - phi.normalForm = Some(NLiteral(false)) - } else if (label == equality) { - if (codesOfTerm(args(0)) == codesOfTerm(args(1))) - phi.normalForm = Some(NLiteral(true)) - else - phi.normalForm = Some(NormalPredicate(label, args, updateCodesSig((lab, (args map codesOfTerm).sorted)))) - } else { - phi.normalForm = Some(NormalPredicate(label, args, updateCodesSig((lab, args map codesOfTerm)))) - } - phi.normalForm.get :: acc - case PolarSchemConnector(label, args) => - val lab = label match { - case _: ConstantConnectorLabel => "cc_" + label.id + "_" + label.arity - case _: SchematicConnectorLabel => "sc_" + label.id + "_" + label.arity - } - phi.normalForm = Some(NormalConnector(label, args.map(_.normalForm.get), updateCodesSig((lab, args map OCBSLCode)))) - phi.normalForm.get :: acc - case SNeg(child) => pNeg(child, phi, acc) - case PolarAnd(children) => children.foldLeft(acc)((p, a) => pDisj(a, p)) - case PolarForall(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 PolarLiteral(true) => - phi.normalForm = Some(NLiteral(true)) - phi.normalForm.get :: acc - case PolarLiteral(false) => - phi.normalForm = Some(NLiteral(false)) - phi.normalForm.get :: acc - } - r - } - - def pNeg(phi: PolarFormula, parent: PolarFormula, acc: List[NormalFormula]): List[NormalFormula] = { - if (phi.normalForm.nonEmpty) return pNegNormal(phi.normalForm.get, parent, acc) - val r: List[NormalFormula] = phi match { - case PolarPredicate(label, args) => - val lab = label match { - case _: ConstantAtomicLabel => "cp_" + label.id + "_" + label.arity - case _: SchematicAtomicLabel => "sp_" + label.id + "_" + label.arity - } - if (label == top) { - phi.normalForm = Some(NLiteral(true)) - parent.normalForm = Some(NLiteral(false)) - } else if (label == bot) { - phi.normalForm = Some(NLiteral(false)) - parent.normalForm = Some(NLiteral(true)) - } else if (label == equality) { - if (codesOfTerm(args(0)) == codesOfTerm(args(1))) { - phi.normalForm = Some(NLiteral(true)) - parent.normalForm = Some(NLiteral(false)) - } else { - phi.normalForm = Some(NormalPredicate(label, 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(label, args, updateCodesSig((lab, args map codesOfTerm)))) - parent.normalForm = Some(NNeg(phi.normalForm.get, updateCodesSig(("neg", List(phi.normalForm.get.code))))) - // phi.normalForm = Some(NormalPredicate(id, args, updateCodesSig((lab, args map codesOfTerm)))) - } - parent.normalForm.get :: acc - case PolarSchemConnector(label, args) => - val lab = label match { - case _: ConstantConnectorLabel => "cc_" + label.id + "_" + label.arity - case _: SchematicConnectorLabel => "sc_" + label.id + "_" + label.arity - } - phi.normalForm = Some(NormalConnector(label, args.map(_.normalForm.get), updateCodesSig((lab, args map OCBSLCode)))) - 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 PolarForall(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 PolarLiteral(true) => - parent.normalForm = Some(NLiteral(false)) - parent.normalForm.get :: acc - case PolarLiteral(false) => - parent.normalForm = Some(NLiteral(true)) - parent.normalForm.get :: acc - case PolarAnd(children) => - if (children.isEmpty) { - parent.normalForm = Some(NLiteral(true)) - parent.normalForm.get :: acc - } else { - 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: PolarFormula, 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(removeSugar(formula)) - - 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 normalForm(phi: Formula): NormalFormula = { - getCode(phi) - removeSugar(phi).normalForm.get - } - - } - def isSame(term1: Term, term2: Term): Boolean = (new LocalEquivalenceChecker2).isSame(term1, term2) - - def isSame(formula1: Formula, formula2: Formula): Boolean = (new LocalEquivalenceChecker2).isSame(formula1, formula2) - - def isSameSet(s1: Set[Formula], s2: Set[Formula]): Boolean = (new LocalEquivalenceChecker2).isSameSet(s1, s2) - - def isSubset(s1: Set[Formula], s2: Set[Formula]): Boolean = (new LocalEquivalenceChecker2).isSubset(s1, s2) - - def contains(s: Set[Formula], f: Formula): Boolean = (new LocalEquivalenceChecker2).contains(s, f) - */ -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala index 24f895f36..30e863d3b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/FOL.scala @@ -1,4 +1,5 @@ package lisa.kernel.fol +import lisa.kernel.fol.OLEquivalenceChecker /** * The concrete implementation of first order logic. @@ -7,4 +8,4 @@ package lisa.kernel.fol * import lisa.fol.FOL._ * */ -object FOL extends FormulaDefinitions with EquivalenceChecker with Substitutions {} +object FOL extends OLEquivalenceChecker {} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala deleted file mode 100644 index 192460cef..000000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala +++ /dev/null @@ -1,138 +0,0 @@ -package lisa.kernel.fol - -/** - * Definitions of formulas; analogous to [[TermDefinitions]]. - * Depends on [[FormulaLabelDefinitions]] and [[TermDefinitions]]. - */ -private[fol] trait FormulaDefinitions extends FormulaLabelDefinitions with TermDefinitions { - - type SimpleFormula - def reducedForm(formula: Formula): Formula - def reduceSet(s: Set[Formula]): Set[Formula] - def isSameTerm(term1: Term, term2: Term): Boolean - def isSame(formula1: Formula, formula2: Formula): Boolean - def isImplying(formula1: Formula, formula2: Formula): Boolean - def isSameSet(s1: Set[Formula], s2: Set[Formula]): Boolean - def isSubset(s1: Set[Formula], s2: Set[Formula]): Boolean - def contains(s: Set[Formula], f: Formula): Boolean - - /** - * The parent class of formulas. - * A formula is a tree whose nodes are either terms or labeled by predicates or logical connectors. - */ - sealed trait Formula extends TreeWithLabel[FormulaLabel] { - val uniqueNumber: Long = Formula.getNewId - private[fol] var polarFormula: Option[SimpleFormula] = None - val arity: Int = label.arity - - override def constantTermLabels: Set[ConstantFunctionLabel] - override def schematicTermLabels: Set[SchematicTermLabel] - override def freeSchematicTermLabels: Set[SchematicTermLabel] - override def freeVariables: Set[VariableLabel] - - /** - * @return The list of constant predicate symbols in the formula. - */ - def constantAtomicLabels: Set[ConstantAtomicLabel] - - /** - * @return The list of schematic predicate symbols in the formula, including variable formulas . - */ - def schematicAtomicLabels: Set[SchematicAtomicLabel] - - /** - * @return The list of schematic connector symbols in the formula. - */ - def schematicConnectorLabels: Set[SchematicConnectorLabel] - - /** - * @return The list of schematic connector, predicate and formula variable symbols in the formula. - */ - def schematicFormulaLabels: Set[SchematicFormulaLabel] = - (schematicAtomicLabels.toSet: Set[SchematicFormulaLabel]) union (schematicConnectorLabels.toSet: Set[SchematicFormulaLabel]) - - /** - * @return The list of free formula variable symbols in the formula - */ - def freeVariableFormulaLabels: Set[VariableFormulaLabel] - - } - private object Formula { - var totalNumberOfFormulas: Long = 0 - def getNewId: Long = { - totalNumberOfFormulas += 1 - totalNumberOfFormulas - } - } - - /** - * The formula counterpart of [[AtomicLabel]]. - */ - sealed case class AtomicFormula(label: AtomicLabel, args: Seq[Term]) extends Formula { - require(label.arity == args.size) - override def constantTermLabels: Set[ConstantFunctionLabel] = - args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.constantTermLabels) - override def schematicTermLabels: Set[SchematicTermLabel] = - args.foldLeft(Set.empty[SchematicTermLabel])((prev, next) => prev union next.schematicTermLabels) - override def freeSchematicTermLabels: Set[SchematicTermLabel] = - args.foldLeft(Set.empty[SchematicTermLabel])((prev, next) => prev union next.freeSchematicTermLabels) - override def freeVariables: Set[VariableLabel] = - args.foldLeft(Set.empty[VariableLabel])((prev, next) => prev union next.freeVariables) - override def constantAtomicLabels: Set[ConstantAtomicLabel] = label match { - case l: ConstantAtomicLabel => Set(l) - case _ => Set() - } - override def schematicAtomicLabels: Set[SchematicAtomicLabel] = label match { - case l: SchematicAtomicLabel => Set(l) - case _ => Set() - } - override def schematicConnectorLabels: Set[SchematicConnectorLabel] = Set() - - override def freeVariableFormulaLabels: Set[VariableFormulaLabel] = label match { - case l: VariableFormulaLabel => Set(l) - case _ => Set() - } - } - - /** - * The formula counterpart of [[ConnectorLabel]]. - */ - sealed case class ConnectorFormula(label: ConnectorLabel, args: Seq[Formula]) extends Formula { - require(label.arity == args.size || label.arity == -1) - require(label.arity != 0) - override def constantTermLabels: Set[ConstantFunctionLabel] = - args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.constantTermLabels) - override def schematicTermLabels: Set[SchematicTermLabel] = - args.foldLeft(Set.empty[SchematicTermLabel])((prev, next) => prev union next.schematicTermLabels) - override def freeSchematicTermLabels: Set[SchematicTermLabel] = - args.foldLeft(Set.empty[SchematicTermLabel])((prev, next) => prev union next.freeSchematicTermLabels) - override def freeVariables: Set[VariableLabel] = - args.foldLeft(Set.empty[VariableLabel])((prev, next) => prev union next.freeVariables) - override def constantAtomicLabels: Set[ConstantAtomicLabel] = - args.foldLeft(Set.empty[ConstantAtomicLabel])((prev, next) => prev union next.constantAtomicLabels) - override def schematicAtomicLabels: Set[SchematicAtomicLabel] = - args.foldLeft(Set.empty[SchematicAtomicLabel])((prev, next) => prev union next.schematicAtomicLabels) - override def schematicConnectorLabels: Set[SchematicConnectorLabel] = label match { - case l: ConstantConnectorLabel => - args.foldLeft(Set.empty[SchematicConnectorLabel])((prev, next) => prev union next.schematicConnectorLabels) - case l: SchematicConnectorLabel => - args.foldLeft(Set(l))((prev, next) => prev union next.schematicConnectorLabels) - } - override def freeVariableFormulaLabels: Set[VariableFormulaLabel] = - args.foldLeft(Set.empty[VariableFormulaLabel])((prev, next) => prev union next.freeVariableFormulaLabels) - } - - /** - * The formula counterpart of [[BinderLabel]]. - */ - sealed case class BinderFormula(label: BinderLabel, bound: VariableLabel, inner: Formula) extends Formula { - override def constantTermLabels: Set[ConstantFunctionLabel] = inner.constantTermLabels - override def schematicTermLabels: Set[SchematicTermLabel] = inner.schematicTermLabels - override def freeSchematicTermLabels: Set[SchematicTermLabel] = inner.freeSchematicTermLabels - bound - override def freeVariables: Set[VariableLabel] = inner.freeVariables - bound - override def constantAtomicLabels: Set[ConstantAtomicLabel] = inner.constantAtomicLabels - override def schematicAtomicLabels: Set[SchematicAtomicLabel] = inner.schematicAtomicLabels - override def schematicConnectorLabels: Set[SchematicConnectorLabel] = inner.schematicConnectorLabels - override def freeVariableFormulaLabels: Set[VariableFormulaLabel] = inner.freeVariableFormulaLabels - } -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala deleted file mode 100644 index 61e750774..000000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala +++ /dev/null @@ -1,112 +0,0 @@ -package lisa.kernel.fol - -/** - * Definitions of formula labels. Analogous to [[TermLabelDefinitions]]. - */ -private[fol] trait FormulaLabelDefinitions extends CommonDefinitions { - - /** - * The parent class of formula labels. - * These are labels that can be applied to nodes that form the tree of a formula. - * In logical terms, those labels are FOL symbols or predicate symbols, including equality. - */ - sealed abstract class FormulaLabel extends Label - - /** - * 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 trait AtomicLabel extends FormulaLabel { - require(arity < MaxArity && arity >= 0) - } - - /** - * The label for a connector, namely a function taking a fixed number of formulas and returning another formula. - */ - sealed trait ConnectorLabel extends FormulaLabel { - require(arity < MaxArity && arity >= -1) - } - - /** - * A standard predicate symbol. Typical example are equality (=) and membership (∈) - */ - sealed case class ConstantAtomicLabel(id: Identifier, arity: Int) extends AtomicLabel with ConstantLabel - - /** - * The equality symbol (=) for first order logic. - * It is represented as any other predicate symbol but has unique semantic and deduction rules. - */ - val equality: ConstantAtomicLabel = ConstantAtomicLabel(Identifier("="), 2) - val top: ConstantAtomicLabel = ConstantAtomicLabel(Identifier("⊤"), 0) - val bot: ConstantAtomicLabel = ConstantAtomicLabel(Identifier("⊥"), 0) - - /** - * The label for a connector, namely a function taking a fixed number of formulas and returning another formula. - */ - sealed abstract class ConstantConnectorLabel(val id: Identifier, val arity: Int) extends ConnectorLabel with ConstantLabel - case object Neg extends ConstantConnectorLabel(Identifier("¬"), 1) - - case object Implies extends ConstantConnectorLabel(Identifier("⇒"), 2) - - case object Iff extends ConstantConnectorLabel(Identifier("⇔"), 2) - - case object And extends ConstantConnectorLabel(Identifier("∧"), -1) - - case object Or extends ConstantConnectorLabel(Identifier("∨"), -1) - - /** - * A schematic symbol that can be instantiated with some formula. - * We distinguish arity-0 schematic formula labels, arity->1 schematic predicates and arity->1 schematic connectors. - */ - sealed trait SchematicFormulaLabel extends FormulaLabel with SchematicLabel - - /** - * A schematic symbol whose arguments are any number of Terms. This means the symbol is either a variable formula or a predicate schema - */ - sealed trait SchematicAtomicLabel extends SchematicFormulaLabel with AtomicLabel - - /** - * A predicate symbol of arity 0 that can be instantiated with any formula. - */ - sealed case class VariableFormulaLabel(id: Identifier) extends SchematicAtomicLabel { - val arity = 0 - } - - /** - * A predicate symbol of non-zero arity that can be instantiated with any functional formula taking term arguments. - */ - sealed case class SchematicPredicateLabel(id: Identifier, arity: Int) extends SchematicAtomicLabel - - /** - * A predicate symbol of non-zero arity that can be instantiated with any functional formula taking formula arguments. - */ - sealed case class SchematicConnectorLabel(id: Identifier, arity: Int) extends SchematicFormulaLabel with ConnectorLabel - - /** - * 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: Identifier) extends FormulaLabel { - val arity = 1 - } - - /** - * The symbol of the universal quantifier ∀ - */ - case object Forall extends BinderLabel(Identifier("∀")) - - /** - * The symbol of the existential quantifier ∃ - */ - case object Exists extends BinderLabel(Identifier("∃")) - - /** - * The symbol of the quantifier for existence and unicity ∃! - */ - case object ExistsOne extends BinderLabel(Identifier("∃!")) - - /** - * A function returning true if and only if the two symbols are considered "the same", i.e. same category, same arity and same id. - */ - def isSame(l: FormulaLabel, r: FormulaLabel): Boolean = l == r - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala new file mode 100644 index 000000000..9c61d71bf --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -0,0 +1,524 @@ +package lisa.kernel.fol + +import scala.collection.mutable +import lisa.kernel.fol.Syntax + +private[fol] trait OLEquivalenceChecker extends Syntax { + + + def reducedForm(expr: Expression): Expression = { + val bnf = expr.betaNormalForm + val p = simplify(bnf) + val nf = computeNormalForm(p) + val fln = fromLocallyNameless(nf, Map.empty, 0) + val res = toExpressionAIG(fln) + res + } + + def reducedNNFForm(expr: Expression): Expression = { + val bnf = expr.betaNormalForm + val p = simplify(expr) + val nf = computeNormalForm(p) + val fln = fromLocallyNameless(nf, Map.empty, 0) + val res = toExpressionNNF(fln, true) + res + } + + def reduceSet(s: Set[Expression]): Set[Expression] = { + var res: List[Expression] = Nil + s.map(reducedForm) + .foreach({ f => + if (!res.exists(isSame(f, _))) res = f :: res + }) + res.toSet + } + + @deprecated("Use isSame instead", "0.8") + def isSameTerm(term1: Expression, term2: Expression): Boolean = isSame(term1, term2) + def isSame(e1: Expression, e2: Expression): Boolean = { + val nf1 = computeNormalForm(simplify(e1.betaNormalForm)) + val nf2 = computeNormalForm(simplify(e2.betaNormalForm)) + latticesEQ(nf1, nf2) + + } + + /** + * returns true if the first argument implies the second by the laws of ortholattices. + */ + def isImplying(e1: Expression, e2: Expression): Boolean = { + require(e1.sort == Formula && e2.sort == Formula) + val nf1 = computeNormalForm(simplify(e1.betaNormalForm)) + val nf2 = computeNormalForm(simplify(e2.betaNormalForm)) + latticesLEQ(nf1, nf2) + } + + def isSubset(s1: Set[Expression], s2: Set[Expression]): Boolean = { + s1.forall(e1 => s2.exists(e2 => isSame(e1, e2))) + } + def isSameSet(s1: Set[Expression], s2: Set[Expression]): Boolean = + isSubset(s1, s2) && isSubset(s2, s1) + + def isSameSetL(s1: Set[Expression], s2: Set[Expression]): Boolean = + isSame(s1.reduceLeft(and(_)(_)), s2.reduceLeft(and(_)(_))) + + def isSameSetR(s1: Set[Expression], s2: Set[Expression]): Boolean = + isSame(s1.reduceLeft(or(_)(_)), s2.reduceLeft(or(_)(_))) + + def contains(s: Set[Expression], f: Expression): Boolean = { + s.exists(g => isSame(f, g)) + } + + + private var totSimpleExpr = 0 + sealed abstract class SimpleExpression { + val sort: Sort + val containsFormulas : Boolean + + val uniqueKey = totSimpleExpr + totSimpleExpr += 1 + val size : Int //number of subterms which are actual concrete formulas + private[OLEquivalenceChecker] var inverse : Option[SimpleExpression] = None + def getInverse = inverse + private[OLEquivalenceChecker] var NNF_pos: Option[Expression] = None + def getNNF_pos = NNF_pos + private[OLEquivalenceChecker] var NNF_neg: Option[Expression] = None + def getNNF_neg = NNF_neg + private[OLEquivalenceChecker] var formulaAIG: Option[Expression] = None + def getFormulaAIG = formulaAIG + private[OLEquivalenceChecker] var normalForm: Option[SimpleExpression] = None + def getNormalForm = normalForm + private[OLEquivalenceChecker] var namelessForm: Option[SimpleExpression] = None + def getNamelessForm = normalForm + + // caching for lessThan + private val lessThanBitSet: mutable.Set[Long] = new mutable.HashSet() + setLessThanCache(this, true) + + def lessThanCached(other: SimpleExpression): Option[Boolean] = { + val otherIx = 2 * other.uniqueKey + if (lessThanBitSet.contains(otherIx)) Some(lessThanBitSet.contains(otherIx + 1)) + else None + } + + def setLessThanCache(other: SimpleExpression, value: Boolean): Unit = { + val otherIx = 2 * other.uniqueKey + lessThanBitSet.contains(otherIx) + if (value) lessThanBitSet.update(otherIx + 1, true) + } + } + + case class SimpleVariable(id: Identifier, sort:Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula + val size = 1 + } + case class SimpleBoundVariable(no: Int, sort: Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula + val size = 1 + } + case class SimpleConstant(id: Identifier, sort: Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula + val size = 1 + } + case class SimpleApplication(f: SimpleExpression, arg: SimpleExpression, polarity: Boolean) extends SimpleExpression { + private val legalapp = legalApplication(f.sort, arg.sort) // Optimize after debugging + val sort = legalapp.get + val containsFormulas: Boolean = sort == Formula || f.containsFormulas || arg.containsFormulas + val size = f.size + arg.size + } + case class SimpleLambda(v: Variable, body: SimpleExpression) extends SimpleExpression { + val containsFormulas: Boolean = body.containsFormulas + val sort = (v.sort -> body.sort) + val size = body.size + } + case class SimpleAnd(children: Seq[SimpleExpression], polarity: Boolean) extends SimpleExpression{ + val containsFormulas: Boolean = true + val sort = Formula + val size = children.map(_.size).sum+1 + } + case class SimpleForall(id: Identifier, body: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val sort = Formula + val size = body.size +1 + } + case class SimpleLiteral(polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val sort = Formula + val size = 1 + } + case class SimpleEquality(left: SimpleExpression, right: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val sort = Formula + val size = left.size + right.size + 1 + } + + + def getInversePolar(e: SimpleExpression): SimpleExpression = e.inverse match { + case Some(inverse) => inverse + case None => + val inverse = e match { + case e: SimpleAnd => e.copy(polarity = !e.polarity) + case e: SimpleForall => e.copy(polarity = !e.polarity) + case e: SimpleLiteral => e.copy(polarity = !e.polarity) + case e: SimpleEquality => e.copy(polarity = !e.polarity) + case e: SimpleVariable if e.sort == Formula => e.copy(polarity = !e.polarity) + case e: SimpleBoundVariable if e.sort == Formula => e.copy(polarity = !e.polarity) + case e: SimpleConstant if e.sort == Formula => e.copy(polarity = !e.polarity) + case e: SimpleApplication if e.sort == Formula => e.copy(polarity = !e.polarity) + case _ => throw new Exception("Cannot invert expression that is not a formula") + } + e.inverse = Some(inverse) + inverse + } + + + def toExpressionAIG(e:SimpleExpression): Expression = + if (e.formulaAIG.isDefined) e.formulaAIG.get + else { + val r: Expression = e match { + case SimpleAnd(children, polarity) => + val f = children.map(toExpressionAIG).reduceLeft(and(_)(_)) + if (polarity) f else neg(f) + case SimpleForall(x, body, polarity) => + val f = forall(Lambda(Variable(x, Term), toExpressionAIG(body))) + if (polarity) f else neg(f) + case SimpleEquality(left, right, polarity) => + val f = equality(toExpressionAIG(left))(toExpressionAIG(right)) + if (polarity) f else neg(f) + case SimpleLiteral(polarity) => if (polarity) top else bot + case SimpleVariable(id, sort, polarity) => if (polarity) Variable(id, sort) else neg(Variable(id, sort)) + case SimpleBoundVariable(no, sort, polarity) => throw new Exception("This case should be unreachable. Can't call toFormulaAIG on a bound variable") + case SimpleConstant(id, sort, polarity) => if (polarity) Constant(id, sort) else neg(Constant(id, sort)) + case SimpleApplication(f, arg, polarity) => + val g = Application(toExpressionAIG(f), toExpressionAIG(arg)) + if (polarity) + g else + neg(g) + case SimpleLambda(v, body) => Lambda(v, toExpressionAIG(body)) + } + e.formulaAIG = Some(r) + r + } + + def toExpressionNNF(e: SimpleExpression, positive: Boolean): Expression = { + if (positive){ + if (e.NNF_pos.isDefined) return e.NNF_pos.get + if (e.inverse.isDefined && e.inverse.get.NNF_neg.isDefined) return e.inverse.get.NNF_neg.get + } + else if (!positive) { + if (e.NNF_neg.isDefined) return e.NNF_neg.get + if (e.inverse.isDefined && e.inverse.get.NNF_pos.isDefined) return e.inverse.get.NNF_pos.get + } + val r = e match { + case SimpleAnd(children, polarity) => + if (positive == polarity) + children.map(toExpressionNNF(_, true)).reduceLeft(and(_)(_)) + else + children.map(toExpressionNNF(_, false)).reduceLeft(or(_)(_)) + case SimpleForall(x, body, polarity) => + if (positive == polarity) + forall(Lambda(Variable(x, Term), toExpressionNNF(body, true))) //rebuilding variable not ideal + else + exists(Lambda(Variable(x, Term), toExpressionNNF(body, false))) + case SimpleEquality(left, right, polarity) => + if (positive == polarity) + equality(toExpressionNNF(left, true))(toExpressionNNF(right, true)) + else + neg(equality(toExpressionNNF(left, true))(toExpressionNNF(right, true))) + case SimpleLiteral(polarity) => + if (positive == polarity) top + else bot + case SimpleVariable(id, sort, polarity) => + if (polarity == positive) Variable(id, sort) + else neg(Variable(id, sort)) + case SimpleBoundVariable(no, sort, polarity) => throw new Exception("This case should be unreachable. Can't call toExpressionNNF on a bound variable") + case SimpleConstant(id, sort, polarity) => + if (polarity == positive) Constant(id, sort) + else neg(Constant(id, sort)) + case SimpleApplication(f, arg, polarity) => + if (polarity == positive) + Application(toExpressionNNF(f, true), toExpressionNNF(arg, true)) + else + neg(Application(toExpressionNNF(f, true), toExpressionNNF(arg, true))) + case SimpleLambda(v, body) => Lambda(v, toExpressionNNF(body, true)) + } + if (positive) e.NNF_pos = Some(r) + else e.NNF_neg = Some(r) + r + } + + + + def polarize(e: Expression, polarity:Boolean): SimpleExpression = { + if (polarity & e.polarExpr.isDefined) { + e.polarExpr.get + } else if (!polarity & e.polarExpr.isDefined) { + getInversePolar(e.polarExpr.get) + } else { + val r = e match { + case neg(arg) => + polarize(arg, !polarity) + case implies(arg1, arg2) => + SimpleAnd(Seq(polarize(arg1, true), polarize(arg2, false)), !polarity) + case iff(arg1, arg2) => + val l1 = polarize(arg1, true) + val r1 = polarize(arg2, true) + SimpleAnd( + Seq( + SimpleAnd(Seq(l1, getInversePolar(r1)), false), + SimpleAnd(Seq(getInversePolar(l1), r1), false) + ), polarity) + case and(arg1, arg2) => + SimpleAnd(Seq(polarize(arg1, true), polarize(arg2, true)), polarity) + case or(arg1, arg2) => + SimpleAnd(Seq(polarize(arg1, false), polarize(arg2, false)), !polarity) + case forall(Lambda(v, body)) => + SimpleForall(v.id, polarize(body, true), polarity) + case forall(p) => + val fresh = freshId(p.freeVariables.map(_.id), Identifier("x", 0)) + val newInner = polarize(Application(p, Variable(fresh, Term)), true) + SimpleForall(fresh, newInner, polarity) + case exists(Lambda(v, body)) => + SimpleForall(v.id, polarize(body, false), !polarity) + case exists(p) => + val fresh = freshId(p.freeVariables.map(_.id), Identifier("x", 0)) + val newInner = polarize(Application(p, Variable(fresh, Term)), false) + SimpleForall(fresh, newInner, !polarity) + case equality(arg1, arg2) => + SimpleEquality(polarize(arg1, true), polarize(arg2, true), polarity) + case Application(f, arg) => + SimpleApplication(polarize(f, true), polarize(arg, true), polarity) + case Lambda(v, body) => SimpleLambda(v, polarize(body, true)) + case `top` => SimpleLiteral(polarity) + case `bot` => SimpleLiteral(!polarity) + case Constant(id, sort) => SimpleConstant(id, sort, polarity) + case Variable(id, sort) => SimpleVariable(id, sort, polarity) + } + if (polarity) e.polarExpr = Some(r) + else e.polarExpr = Some(getInversePolar(r)) + r + } + } + + def toLocallyNameless(e: SimpleExpression): SimpleExpression = + e.namelessForm match { + case None => + val r = e match { + case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless), polarity) + case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless2(inner, Map((x, Term) -> 0), 1), polarity) + case e: SimpleLiteral => e + case SimpleEquality(left, right, polarity) => SimpleEquality(toLocallyNameless(left), toLocallyNameless(right), polarity) + case v: SimpleVariable => v + case s: SimpleBoundVariable => throw new Exception("This case should be unreachable. Can't call toLocallyNameless on a bound variable") + case e: SimpleConstant => e + case SimpleApplication(arg1, arg2, polarity) => SimpleApplication(toLocallyNameless(arg1), toLocallyNameless(arg2), polarity) + case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless2(inner, Map((x.id, Term) -> 0), 1)) + } + + toLocallyNameless2(e, Map.empty, 0) + e.namelessForm = Some(r) + r + case Some(value) => value + } + def toLocallyNameless2(e: SimpleExpression, subst: Map[(Identifier, Sort), Int], i: Int): SimpleExpression = e match { + case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless2(_, subst, i)), polarity) + case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless2(inner, subst + ((x, Term) -> i), i + 1), polarity) + case e: SimpleLiteral => e + case SimpleEquality(left, right, polarity) => SimpleEquality(toLocallyNameless2(left, subst, i), toLocallyNameless2(right, subst, i), polarity) + case v: SimpleVariable => + if (subst.contains((v.id, v.sort))) SimpleBoundVariable(i - subst((v.id, v.sort)), v.sort, v.polarity) + else v + case s: SimpleBoundVariable => throw new Exception("This case should be unreachable. Can't call toLocallyNameless on a bound variable") + case e: SimpleConstant => e + case SimpleApplication(arg1, arg2, polarity) => SimpleApplication(toLocallyNameless2(arg1, subst, i), toLocallyNameless2(arg2, subst, i), polarity) + case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless2(inner, subst + ((x.id, x.sort) -> i), i + 1)) + } + + def fromLocallyNameless(e: SimpleExpression, subst: Map[Int, (Identifier, Sort)], i: Int): SimpleExpression = e match { + case SimpleAnd(children, polarity) => SimpleAnd(children.map(fromLocallyNameless(_, subst, i)), polarity) + case SimpleForall(x, inner, polarity) => SimpleForall(x, fromLocallyNameless(inner, subst + (i -> (x, Term)), i + 1), polarity) + case e: SimpleLiteral => e + case SimpleEquality(left, right, polarity) => SimpleEquality(fromLocallyNameless(left, subst, i), fromLocallyNameless(right, subst, i), polarity) + case SimpleBoundVariable(no, sort, polarity) => + val dist = i - no + if (subst.contains(dist)) {val (id, sort) = subst(dist); SimpleVariable(id, sort, polarity)} + else throw new Exception("This case should be unreachable, error") + case v: SimpleVariable => v + case e: SimpleConstant => e + case SimpleApplication(arg1, arg2, polarity) => SimpleApplication(fromLocallyNameless(arg1, subst, i), fromLocallyNameless(arg2, subst, i), polarity) + case SimpleLambda(x, inner) => SimpleLambda(x, fromLocallyNameless(inner, subst + (i -> (x.id, x.sort)), i + 1)) + } + + def simplify(e: Expression): SimpleExpression = toLocallyNameless(polarize(e, true)) + + + ////////////////////// + //// OL Algorithm //// + ////////////////////// + + def computeNormalForm(e: SimpleExpression): SimpleExpression = { + e.normalForm match { + case Some(value) => + value + case None => + val r: SimpleExpression = e match { + case SimpleAnd(children, polarity) => + val newChildren = children map computeNormalForm + val simp = reduce(newChildren, polarity) + simp match { + case conj: SimpleAnd if checkForContradiction(conj) => SimpleLiteral(!polarity) + case _ => simp + } + + case SimpleApplication(f, arg, true) => SimpleApplication(computeNormalForm(f), computeNormalForm(arg), true) + + case SimpleBoundVariable(no, sort, true) => e + + case SimpleVariable(id, sort, true) => e + + case SimpleConstant(id, sort, true) => e + + case SimpleEquality(left, right, true) => + val l = computeNormalForm(left) + val r = computeNormalForm(right) + if (l == r) SimpleLiteral(true) + else if (l.uniqueKey >= r.uniqueKey) SimpleEquality(l, r, true) + else SimpleEquality(r, l, true) + + case SimpleForall(id, body, true) => SimpleForall(id, computeNormalForm(body), true) + + case SimpleLambda(v, body) => SimpleLambda(v, computeNormalForm(body)) + + case SimpleLiteral(polarity) => e + + case _ => getInversePolar(computeNormalForm(getInversePolar(e))) + + } + e.normalForm = Some(r) + r + } + } + + def checkForContradiction(f: SimpleAnd): Boolean = { + f match { + case SimpleAnd(children, false) => + children.exists(cc => latticesLEQ(cc, f)) + case SimpleAnd(children, true) => + val shadowChildren = children map getInversePolar + shadowChildren.exists(sc => latticesLEQ(f, sc)) + } + } + + def reduceList(children: Seq[SimpleExpression], polarity: Boolean): List[SimpleExpression] = { + val nonSimplified = SimpleAnd(children, polarity) + var remaining : Seq[SimpleExpression] = Nil + def treatChild(i: SimpleExpression): Seq[SimpleExpression] = { + val r: Seq[SimpleExpression] = i match { + case SimpleAnd(ch, true) => ch + case SimpleAnd(ch, false) => + if (polarity) { + val trCh = ch map getInversePolar + trCh.find(f => latticesLEQ(nonSimplified, f)) match { + case Some(value) => treatChild(value) + case None => List(i) + } + } else { + val trCH = ch + trCH.find(f => latticesLEQ(f, nonSimplified)) match { + case Some(value) => treatChild(getInversePolar(value)) + case None => List(i) + } + } + case _ => List(i) + } + r + } + children.foreach(i => { + val r = treatChild(i) + remaining = r ++ remaining + }) + + var accepted: List[SimpleExpression] = Nil + while (remaining.nonEmpty) { + val current = remaining.head + remaining = remaining.tail + if (!latticesLEQ(SimpleAnd(remaining ++ accepted, true), current)) { + accepted = current :: accepted + } + } + accepted + } + + + def reduce(children: Seq[SimpleExpression], polarity: Boolean): SimpleExpression = { + val accepted: List[SimpleExpression] = reduceList(children, polarity) + if (accepted.isEmpty) SimpleLiteral(polarity) + else if (accepted.size == 1) + if (polarity) accepted.head + else getInversePolar(accepted.head) + else SimpleAnd(accepted, polarity) + } + + + def latticesLEQ(e1: SimpleExpression, e2: SimpleExpression): Boolean = { + require(e1.sort == Formula && e2.sort == Formula) + if (e1.uniqueKey == e2.uniqueKey) true + else + e1.lessThanCached(e2) match { + case Some(value) => value + case None => + val r = (e1, e2) match { + case (SimpleLiteral(false), _) => true + + case (_, SimpleLiteral(true)) => true + + case (SimpleEquality(l1, r1, pol1), SimpleEquality(l2, r2, pol2)) => + pol1 == pol2 && ((latticesEQ(l1, l2) && latticesEQ(r1, r2)) || (latticesEQ(l1, r2) && latticesEQ(r1, l2))) + + case (SimpleForall(x1, body1, polarity1), SimpleForall(x2, body2, polarity2)) => + polarity1 == polarity2 && (if (polarity1) latticesLEQ(body1, body2) else latticesLEQ(body2, body1)) + + // Usual lattice conjunction/disjunction cases + case (_, SimpleAnd(children, true)) => + children.forall(c => latticesLEQ(e1, c)) + case (SimpleAnd(children, false), _) => + children.forall(c => latticesLEQ(getInversePolar(c), e2)) + case (SimpleAnd(children1, true), SimpleAnd(children2, false)) => + children1.exists(c => latticesLEQ(c, e2)) || children2.exists(c => latticesLEQ(e1, getInversePolar(c))) + case (_, SimpleAnd(children, false)) => + children.exists(c => latticesLEQ(e1, getInversePolar(c))) + case (SimpleAnd(children, true), _) => + children.exists(c => latticesLEQ(c, e2)) + + + case (s1: SimpleBoundVariable, s2: SimpleBoundVariable) => s1 == s2 + + case (s1: SimpleVariable, s2: SimpleVariable) => s1 == s2 + + case (s1: SimpleConstant, s2: SimpleConstant) => s1 == s2 + + case (SimpleApplication(f1, arg1, polarity1), SimpleApplication(f2, arg2, polarity2)) => + polarity1 == polarity2 && latticesEQ(f1, f2) && latticesEQ(arg1, arg2) + + case (_, _) => false + } + e1.setLessThanCache(e2, r) + r + } + + + } + + def latticesEQ(e1: SimpleExpression, e2: SimpleExpression): Boolean = + if (e1.uniqueKey == e2.uniqueKey) true + else if (e1.sort == Formula) latticesLEQ(e1, e2) && latticesLEQ(e2, e1) + else (e1, e2) match { + case (s1: SimpleBoundVariable, s2: SimpleBoundVariable) => s1 == s2 + case (s1: SimpleVariable, s2: SimpleVariable) => s1 == s2 + case (s1: SimpleConstant, s2: SimpleConstant) => s1 == s2 + case (SimpleApplication(f1, arg1, polarity1), SimpleApplication(f2, arg2, polarity2)) => + polarity1 == polarity2 && latticesEQ(f1, f2) && latticesEQ(arg1, arg2) + case (SimpleLambda(x1, body1), SimpleLambda(x2, body2)) => + latticesEQ(body1, body2) + case (_, _) => false + } +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala deleted file mode 100644 index 51bfb4654..000000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala +++ /dev/null @@ -1,244 +0,0 @@ -package lisa.kernel.fol - -trait Substitutions extends FormulaDefinitions { - - /** - * A lambda term to express a "term with holes". Main use is to be substituted in place of a function schema or variable. - * Also used for some deduction rules. - * Morally equivalent to a 2-tuples containing the same informations. - * @param vars The names of the "holes" in the term, necessarily of arity 0. The bound variables of the functional term. - * @param body The term represented by the object, up to instantiation of the bound schematic variables in args. - */ - case class LambdaTermTerm(vars: Seq[VariableLabel], body: Term) { - def apply(args: Seq[Term]): Term = substituteVariablesInTerm(body, (vars zip args).toMap) - } - - /** - * A lambda formula to express a "formula with term holes". Main use is to be substituted in place of a predicate schema. - * Also used for some deduction rules. - * Morally equivalent to a 2-tuples containing the same informations. - * @param vars The names of the "holes" in a formula, necessarily of arity 0. The bound variables of the functional formula. - * @param body The formula represented by the object, up to instantiation of the bound schematic variables in args. - */ - case class LambdaTermFormula(vars: Seq[VariableLabel], body: Formula) { - def apply(args: Seq[Term]): Formula = { - substituteVariablesInFormula(body, (vars zip args).toMap) - } - } - - /** - * A lambda formula to express a "formula with formula holes". Main use is to be substituted in place of a connector schema. - * Also used for some deduction rules. - * Morally equivalent to a 2-tuples containing the same informations. - * @param vars The names of the "holes" in a formula, necessarily of arity 0. - * @param body The formula represented by the object, up to instantiation of the bound schematic variables in args. - */ - case class LambdaFormulaFormula(vars: Seq[VariableFormulaLabel], body: Formula) { - def apply(args: Seq[Formula]): Formula = { - substituteFormulaVariables(body, (vars zip args).toMap) - // instantiatePredicateSchemas(body, (vars zip (args map (LambdaTermFormula(Nil, _)))).toMap) - } - } - - ////////////////////////// - // **--- ON TERMS ---** // - ////////////////////////// - - /** - * Performs simultaneous substitution of multiple variables by multiple terms in a term. - * @param t The base term - * @param m A map from variables to terms. - * @return t[m] - */ - def substituteVariablesInTerm(t: Term, m: Map[VariableLabel, Term]): Term = t match { - case Term(label: VariableLabel, _) => m.getOrElse(label, t) - case Term(label, args) => Term(label, args.map(substituteVariablesInTerm(_, m))) - } - - /** - * Performs simultaneous substitution of schematic function symbol by "functional" terms, or terms with holes. - * If the arity of one of the function symbol to substitute doesn't match the corresponding number of arguments, it will produce an error. - * @param t The base term - * @param m The map from schematic function symbols to lambda expressions Term(s) -> Term [[LambdaTermTerm]]. - * @return t[m] - */ - def instantiateTermSchemasInTerm(t: Term, m: Map[SchematicTermLabel, LambdaTermTerm]): Term = { - require(m.forall { case (symbol, LambdaTermTerm(arguments, body)) => arguments.length == symbol.arity }) - t match { - case Term(label: VariableLabel, _) => m.get(label).map(_.apply(Nil)).getOrElse(t) - case Term(label, args) => - val newArgs = args.map(instantiateTermSchemasInTerm(_, m)) - label match { - case label: ConstantFunctionLabel => Term(label, newArgs) - case label: SchematicTermLabel => - m.get(label).map(_(newArgs)).getOrElse(Term(label, newArgs)) - } - } - } - - ///////////////////////////// - // **--- ON FORMULAS ---** // - ///////////////////////////// - - /** - * Performs simultaneous substitution of multiple variables by multiple terms in a formula. - * - * @param phi The base formula - * @param m A map from variables to terms - * @return t[m] - */ - def substituteVariablesInFormula(phi: Formula, m: Map[VariableLabel, Term], takenIds: Seq[Identifier] = Seq[Identifier]()): Formula = phi match { - case AtomicFormula(label, args) => AtomicFormula(label, args.map(substituteVariablesInTerm(_, m))) - case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(substituteVariablesInFormula(_, m))) - case BinderFormula(label, bound, inner) => - val newSubst = m - bound - val newTaken = takenIds :+ bound.id - val fv = m.values.flatMap(_.freeVariables).toSet - if (fv.contains(bound)) { - val newBoundVariable = VariableLabel(freshId(fv.map(_.name) ++ m.keys.map(_.id) ++ newTaken, bound.name)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> VariableTerm(newBoundVariable)), newTaken) - BinderFormula(label, newBoundVariable, substituteVariablesInFormula(newInner, newSubst, newTaken)) - } else BinderFormula(label, bound, substituteVariablesInFormula(inner, newSubst, newTaken)) - } - - /** - * Performs simultaneous substitution of multiple formula variables by multiple formula terms in a formula. - * - * @param phi The base formula - * @param m A map from variables to terms - * @return t[m] - */ - def substituteFormulaVariables(phi: Formula, m: Map[VariableFormulaLabel, Formula], takenIds: Seq[Identifier] = Seq[Identifier]()): Formula = phi match { - case AtomicFormula(label: VariableFormulaLabel, _) => m.getOrElse(label, phi) - case _: AtomicFormula => phi - case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(substituteFormulaVariables(_, m, takenIds))) - case BinderFormula(label, bound, inner) => - val fv = m.values.flatMap(_.freeVariables).toSet - val newTaken = takenIds :+ bound.id - if (fv.contains(bound)) { - val newBoundVariable = VariableLabel(freshId(fv.map(_.name) ++ newTaken, bound.name)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> VariableTerm(newBoundVariable)), newTaken) - BinderFormula(label, newBoundVariable, substituteFormulaVariables(newInner, m, newTaken)) - } else BinderFormula(label, bound, substituteFormulaVariables(inner, m, newTaken)) - } - - /** - * Performs simultaneous substitution of schematic function symbol by "functional" terms, or terms with holes. - * If the arity of one of the predicate symbol to substitute doesn't match the corresponding number of arguments, it will produce an error. - * @param phi The base formula - * @param m The map from schematic function symbols to lambda expressions Term(s) -> Term [[LambdaTermTerm]]. - * @return phi[m] - */ - def instantiateTermSchemas(phi: Formula, m: Map[SchematicTermLabel, LambdaTermTerm]): Formula = { - require(m.forall { case (symbol, LambdaTermTerm(arguments, body)) => arguments.length == symbol.arity }) - phi match { - case AtomicFormula(label, args) => AtomicFormula(label, args.map(a => instantiateTermSchemasInTerm(a, m))) - case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(instantiateTermSchemas(_, m))) - case BinderFormula(label, bound, inner) => - val newSubst = m - bound - val fv: Set[VariableLabel] = newSubst.flatMap { case (symbol, LambdaTermTerm(arguments, body)) => body.freeVariables }.toSet ++ inner.freeVariables - if (fv.contains(bound)) { - val newBoundVariable = VariableLabel(freshId(fv.map(_.name) ++ m.keys.map(_.id), bound.name)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> VariableTerm(newBoundVariable))) - BinderFormula(label, newBoundVariable, instantiateTermSchemas(newInner, newSubst)) - } else BinderFormula(label, bound, instantiateTermSchemas(inner, newSubst)) - } - } - - /** - * Instantiate a schematic predicate symbol in a formula, using higher-order instantiation. - * If the arity of one of the connector symbol to substitute doesn't match the corresponding number of arguments, it will produce an error. - * @param phi The base formula - * @param m The map from schematic predicate symbols to lambda expressions Term(s) -> Formula [[LambdaTermFormula]]. - * @return phi[m] - */ - def instantiatePredicateSchemas(phi: Formula, m: Map[SchematicAtomicLabel, LambdaTermFormula]): Formula = { - require(m.forall { case (symbol, LambdaTermFormula(arguments, body)) => arguments.length == symbol.arity }) - phi match { - case AtomicFormula(label, args) => - label match { - case label: SchematicAtomicLabel if m.contains(label) => m(label)(args) - case _ => phi - } - case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(instantiatePredicateSchemas(_, m))) - case BinderFormula(label, bound, inner) => - val fv: Set[VariableLabel] = (m.flatMap { case (symbol, LambdaTermFormula(arguments, body)) => body.freeVariables }).toSet ++ inner.freeVariables - if (fv.contains(bound)) { - val newBoundVariable = VariableLabel(freshId(fv.map(_.name), bound.name)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> VariableTerm(newBoundVariable))) - BinderFormula(label, newBoundVariable, instantiatePredicateSchemas(newInner, m)) - } else BinderFormula(label, bound, instantiatePredicateSchemas(inner, m)) - } - } - - /** - * Instantiate a schematic connector symbol in a formula, using higher-order instantiation. - * - * @param phi The base formula - * @param m The map from schematic function symbols to lambda expressions Formula(s) -> Formula [[LambdaFormulaFormula]]. - * @return phi[m] - */ - def instantiateConnectorSchemas(phi: Formula, m: Map[SchematicConnectorLabel, LambdaFormulaFormula]): Formula = { - require(m.forall { case (symbol, LambdaFormulaFormula(arguments, body)) => arguments.length == symbol.arity }) - phi match { - case _: AtomicFormula => phi - case ConnectorFormula(label, args) => - val newArgs = args.map(instantiateConnectorSchemas(_, m)) - label match { - case label: SchematicConnectorLabel if m.contains(label) => m(label)(newArgs) - case _ => ConnectorFormula(label, newArgs) - } - case BinderFormula(label, bound, inner) => - val fv: Set[VariableLabel] = (m.flatMap { case (symbol, LambdaFormulaFormula(arguments, body)) => body.freeVariables }).toSet ++ inner.freeVariables - if (fv.contains(bound)) { - val newBoundVariable = VariableLabel(freshId(fv.map(_.name), bound.name)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> VariableTerm(newBoundVariable))) - BinderFormula(label, newBoundVariable, instantiateConnectorSchemas(newInner, m)) - } else BinderFormula(label, bound, instantiateConnectorSchemas(inner, m)) - } - } - - /** - * Instantiate a schematic connector symbol in a formula, using higher-order instantiation. - * - * @param phi The base formula - * @param m The map from schematic function symbols to lambda expressions Formula(s) -> Formula [[LambdaFormulaFormula]]. - * @return phi[m] - */ - def instantiateSchemas( - phi: Formula, - mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula], - mPred: Map[SchematicAtomicLabel, LambdaTermFormula], - mTerm: Map[SchematicTermLabel, LambdaTermTerm] - ): Formula = { - require(mCon.forall { case (symbol, LambdaFormulaFormula(arguments, body)) => arguments.length == symbol.arity }) - require(mPred.forall { case (symbol, LambdaTermFormula(arguments, body)) => arguments.length == symbol.arity }) - require(mTerm.forall { case (symbol, LambdaTermTerm(arguments, body)) => arguments.length == symbol.arity }) - phi match { - case AtomicFormula(label, args) => - val newArgs = args.map(a => instantiateTermSchemasInTerm(a, mTerm)) - label match { - case label: SchematicAtomicLabel if mPred.contains(label) => mPred(label)(newArgs) - case _ => AtomicFormula(label, newArgs) - } - case ConnectorFormula(label, args) => - val newArgs = args.map(a => instantiateSchemas(a, mCon, mPred, mTerm)) - label match { - case label: SchematicConnectorLabel if mCon.contains(label) => mCon(label)(newArgs) - case _ => ConnectorFormula(label, newArgs) - } - case BinderFormula(label, bound, inner) => - val newmTerm = mTerm - bound - val fv: Set[VariableLabel] = - (mCon.flatMap { case (symbol, LambdaFormulaFormula(arguments, body)) => body.freeVariables }).toSet ++ - (mPred.flatMap { case (symbol, LambdaTermFormula(arguments, body)) => body.freeVariables }).toSet ++ - (mTerm.flatMap { case (symbol, LambdaTermTerm(arguments, body)) => body.freeVariables }).toSet ++ inner.freeVariables - if (fv.contains(bound)) { - val newBoundVariable = VariableLabel(freshId(fv.map(_.name) ++ mTerm.keys.map(_.id), bound.name)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> VariableTerm(newBoundVariable))) - BinderFormula(label, newBoundVariable, instantiateSchemas(newInner, mCon, mPred, newmTerm)) - } else BinderFormula(label, bound, instantiateSchemas(inner, mCon, mPred, newmTerm)) - } - } - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala new file mode 100644 index 000000000..4eba7dbcd --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -0,0 +1,199 @@ +package lisa.kernel.fol + +private[fol] trait Syntax { + + type SimpleExpression + + sealed case class Identifier(val name: String, val no: Int) { + require(no >= 0, "Variable index must be positive") + require(Identifier.isValidIdentifier(name), "Variable name " + name + "is not valid.") + override def toString: String = if (no == 0) name else name + Identifier.counterSeparator + no + } + + object Identifier { + def unapply(i: Identifier): Option[(String, Int)] = Some((i.name, i.no)) + def apply(name: String): Identifier = new Identifier(name, 0) + def apply(name: String, no: Int): Identifier = new Identifier(name, no) + + val counterSeparator: Char = '_' + val delimiter: Char = '`' + val forbiddenChars: Set[Char] = ("()[]{}?,;" + delimiter + counterSeparator).toSet + def isValidIdentifier(s: String): Boolean = s.forall(c => !forbiddenChars.contains(c) && !c.isWhitespace) + } + + private[kernel] def freshId(taken: Iterable[Identifier], base: Identifier): Identifier = { + new Identifier( + base.name, + (Iterable(base.no) ++ taken.collect({ case Identifier(base.name, no) => + no + })).max + 1 + ) + } + + + + + sealed trait Sort { + def ->(to: Sort): Arrow = Arrow(this, to) + val isFunctional: Boolean + val isPredicate: Boolean + val depth: Int + } + case object Term extends Sort { + val isFunctional = true + val isPredicate = false + val depth = 0 + } + case object Formula extends Sort { + val isFunctional = false + val isPredicate = true + val depth = 0 + } + sealed case class Arrow(from: Sort, to: Sort) extends Sort { + val isFunctional = from == Term && to.isFunctional + val isPredicate = from == Term && to.isPredicate + val depth = 1+to.depth + } + + def depth(t:Sort): Int = t.depth + + + def legalApplication(typ1: Sort, typ2: Sort): Option[Sort] = { + typ1 match { + case Arrow(`typ2`, to) => Some(to) + case _ => None + } + } + + private object ExpressionCounters { + var totalNumberOfExpressions: Long = 0 + def getNewId: Long = { + totalNumberOfExpressions += 1 + totalNumberOfExpressions + } + } + + sealed trait Expression { + private[fol] var polarExpr: Option[SimpleExpression] = None + def getPolarExpr : Option[SimpleExpression] = polarExpr + val sort: Sort + val uniqueNumber: Long = ExpressionCounters.getNewId + val containsFormulas : Boolean + def apply(arg: Expression): Application = Application(this, arg) + def unapplySeq(arg: Expression): Option[Seq[Expression]] = arg match { + case Application(f, arg) if f == this => Some(arg :: Nil) + case Application(f, arg) => unapplySeq(f).map(fargs => fargs :+ arg) + case _ => None + } + + val (betaNormalForm: Expression, isBetaNormal: Boolean) = this match { + case Application(f, arg) => { + val f1 = f.betaNormalForm + val a2 = arg.betaNormalForm + f1 match { + case Lambda(v, body) => (substituteVariables(body, Map(v -> a2)).betaNormalForm, false) + case _ if f.isBetaNormal && arg.isBetaNormal => (this, true) + case _ => (Application(f1, a2), false) + } + } + case Lambda(v, Application(f, arg)) if v == arg => (f.betaNormalForm, false) + case Lambda(v, inner) if inner.isBetaNormal => (this, true) + case Lambda(v, inner) => (Lambda(v, inner.betaNormalForm), false) + case _ => (this, true) + } + + /** + * @return The list of free variables in the tree. + */ + def freeVariables: Set[Variable] + + /** + * @return The list of constant symbols. + */ + def constants: Set[Constant] + + /** + * @return The list of variables in the tree. + */ + def allVariables: Set[Variable] + + } + + case class Variable(id: Identifier, sort:Sort) extends Expression { + val containsFormulas = sort == Formula + def freeVariables: Set[Variable] = Set(this) + def constants: Set[Constant] = Set() + def allVariables: Set[Variable] = Set(this) + } + case class Constant(id: Identifier, sort: Sort) extends Expression { + val containsFormulas = sort == Formula + def freeVariables: Set[Variable] = Set() + def constants: Set[Constant] = Set(this) + def allVariables: Set[Variable] = Set() + } + case class Application(f: Expression, arg: Expression) extends Expression { + private val legalapp = legalApplication(f.sort, arg.sort) + require(legalapp.isDefined, s"Application of $f to $arg is not legal") + val sort = legalapp.get + val containsFormulas = sort == Formula || f.containsFormulas || arg.containsFormulas + + def freeVariables: Set[Variable] = f.freeVariables union arg.freeVariables + def constants: Set[Constant] = f.constants union arg.constants + def allVariables: Set[Variable] = f.allVariables union arg.allVariables + } + + case class Lambda(v: Variable, body: Expression) extends Expression { + val containsFormulas = body.containsFormulas + val sort = (v.sort -> body.sort) + + def freeVariables: Set[Variable] = body.freeVariables - v + def constants: Set[Constant] = body.constants + def allVariables: Set[Variable] = body.allVariables + } + + + val equality = Constant(Identifier("="), Term -> (Term -> Formula)) + val top = Constant(Identifier("⊤"), Formula) + val bot = Constant(Identifier("⊥"), Formula) + val neg = Constant(Identifier("¬"), Formula -> Formula) + val implies = Constant(Identifier("⇒"), Formula -> (Formula -> Formula)) + val iff = Constant(Identifier("⇔"), Formula -> (Formula -> Formula)) + val and = Constant(Identifier("∧"), Formula -> (Formula -> Formula)) + val or = Constant(Identifier("∨"), Formula -> (Formula -> Formula)) + val forall = Constant(Identifier("∀"), (Term -> Formula) -> Formula) + val exists = Constant(Identifier("∃"), (Term -> Formula) -> Formula) + val epsilon = Constant(Identifier("ε"), (Term -> Formula) -> Term) + + + /** + * Performs simultaneous substitution of multiple variables by multiple terms in a term. + * @param t The base term + * @param m A map from variables to terms. + * @return t[m] + */ + def substituteVariables(e: Expression, m: Map[Variable, Expression]): Expression = e match { + case v: Variable => + m.get(v) match { + case Some(r) => + if (r.sort == v.sort) r + else throw new IllegalArgumentException("Sort mismatch in substitution: " + v + " -> " + r) + case None => v + } + case c: Constant => c + case Application(f, arg) => Application(substituteVariables(f, m), substituteVariables(arg, m)) + case Lambda(v, t) => + val newSubst = m - v + val fv = m.values.flatMap(_.freeVariables).toSet + if (fv.contains(v)) { + val newBound = Variable(freshId(fv.view.map(_.id) ++ m.keys.view.map(_.id), v.id), v.sort) + Lambda(newBound, substituteVariables(t, newSubst + (v -> newBound))) + } + else Lambda(v, substituteVariables(t, m - v)) + } + + def flatTypeParameters(t: Sort): List[Sort] = t match { + case Arrow(a, b) => a :: flatTypeParameters(b) + case _ => List() + } + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala deleted file mode 100644 index 6e1b9b5cd..000000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala +++ /dev/null @@ -1,84 +0,0 @@ -package lisa.kernel.fol - -/** - * Definitions of terms; depends on [[TermLabelDefinitions]]. - */ -private[fol] trait TermDefinitions extends TermLabelDefinitions { - - protected trait TreeWithLabel[A] { - val label: A - val arity: Int - - /** - * @return The list of free variables in the tree. - */ - def freeVariables: Set[VariableLabel] - - /** - * @return The list of constant (i.e. non schematic) function symbols, including of arity 0. - */ - def constantTermLabels: Set[ConstantFunctionLabel] - - /** - * @return The list of schematic term symbols (including free and bound variables) in the tree. - */ - def schematicTermLabels: Set[SchematicTermLabel] - - /** - * @return The list of schematic term symbols (excluding bound variables) in the tree. - */ - def freeSchematicTermLabels: Set[SchematicTermLabel] - } - - /** - * A term labelled by a function symbol. It must contain a number of children equal to the arity of the symbol. - * The label can be a constant or schematic term label of any arity, including a variable label. - * @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. - */ - sealed case class Term(label: TermLabel, args: Seq[Term]) extends TreeWithLabel[TermLabel] { - require(label.arity == args.size) - val uniqueNumber: Long = TermCounters.getNewId - val arity: Int = label.arity - - override def freeVariables: Set[VariableLabel] = label match { - case l: VariableLabel => Set(l) - case _ => args.foldLeft(Set.empty[VariableLabel])((prev, next) => prev union next.freeVariables) - } - - override def constantTermLabels: Set[ConstantFunctionLabel] = label match { - case l: ConstantFunctionLabel => args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.constantTermLabels) + l - case l: SchematicTermLabel => args.foldLeft(Set.empty[ConstantFunctionLabel])((prev, next) => prev union next.constantTermLabels) - } - override def schematicTermLabels: Set[SchematicTermLabel] = label match { - case l: ConstantFunctionLabel => args.foldLeft(Set.empty[SchematicTermLabel])((prev, next) => prev union next.schematicTermLabels) - case l: SchematicTermLabel => args.foldLeft(Set.empty[SchematicTermLabel])((prev, next) => prev union next.schematicTermLabels) + l - } - override def freeSchematicTermLabels: Set[SchematicTermLabel] = schematicTermLabels - } - private object TermCounters { - var totalNumberOfTerms: Long = 0 - def getNewId: Long = { - totalNumberOfTerms += 1 - totalNumberOfTerms - } - } - - /** - * A VariableTerm is exactly an arity-0 term whose label is a variable label, but we provide specific constructors and destructors. - */ - object VariableTerm extends (VariableLabel => Term) { - - /** - * A term which consists of a single variable. - * - * @param label The label of the variable. - */ - def apply(label: VariableLabel): Term = Term(label, Seq()) - def unapply(t: Term): Option[VariableLabel] = t.label match { - case l: VariableLabel => Some(l) - case _ => None - } - } - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala deleted file mode 100644 index 610ffe8a6..000000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala +++ /dev/null @@ -1,52 +0,0 @@ -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 some language. - */ - sealed abstract class TermLabel extends Label { - require(arity >= 0 && arity < MaxArity) - } - - /** - * A fixed function symbol. If arity is 0, it is just a regular constant 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 - */ - sealed case class ConstantFunctionLabel(id: Identifier, arity: Int) extends TermLabel with ConstantLabel - - /** - * A schematic symbol which is uninterpreted and can be substituted by functional term of the same arity. - * We distinguish arity 0 schematic term labels which we call variables and can be bound, and arity>1 schematic symbols. - */ - sealed trait SchematicTermLabel extends TermLabel with SchematicLabel {} - - /** - * A schematic function symbol that can be substituted. - * - * @param id The name of the function symbol. - * @param arity The arity of the function symbol. Must be greater than 1. - */ - sealed case class SchematicFunctionLabel(id: Identifier, arity: Int) extends SchematicTermLabel { - require(arity >= 1 && arity < MaxArity, "Trying to define SchemaFunctionLabel with arity " + arity + " for symbol " + id.name + "_" + id.no) - } - - /** - * The label of a term which is a variable. Can be bound in a formulas, or substituted for an arbitrary term. - * - * @param id The name of the variable, for example "x" or "y". - */ - sealed case class VariableLabel(id: Identifier) extends SchematicTermLabel { - val name: Identifier = id - val arity = 0 - } - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala index 8167d9f7a..cbd500ca4 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala @@ -32,40 +32,21 @@ class RunningTheory { /** * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. */ - sealed case class Axiom private[RunningTheory] (name: String, ax: Formula) extends Justification + sealed case class Axiom private[RunningTheory] (name: String, ax: Expression) extends Justification /** - * A definition can be either a PredicateDefinition or a FunctionDefinition. + * A definition of a new symbol. */ - 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 expression The formula, depending on terms, that define the symbol. - */ - sealed case class PredicateDefinition private[RunningTheory] (label: ConstantAtomicLabel, expression: LambdaTermFormula) 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 out The variable representing the result of the function in phi - * @param expression The formula, with term parameters, defining the function. - * @param withSorry Stores if Sorry was used to in the proof used to define the symbol, or one of its ancestor. - */ - sealed case class FunctionDefinition private[RunningTheory] (label: ConstantFunctionLabel, out: VariableLabel, expression: LambdaTermFormula, withSorry: Boolean) extends Definition + sealed case class Definition private[RunningTheory] (cst: Constant, expression: Expression, vars: Seq[Variable]) extends Justification private[proof] val theoryAxioms: mMap[String, Axiom] = mMap.empty private[proof] val theorems: mMap[String, Theorem] = mMap.empty - private[proof] val funDefinitions: mMap[ConstantFunctionLabel, Option[FunctionDefinition]] = mMap.empty - private[proof] val predDefinitions: mMap[ConstantAtomicLabel, Option[PredicateDefinition]] = mMap(equality -> None, top -> None, bot -> None) + private[proof] val definitions: mMap[Constant, Option[Definition]] = + mMap(equality -> None, top -> None, bot -> None, and -> None, or -> None, neg -> None, implies -> None, iff -> None, forall -> None, exists -> None, epsilon -> None) - private[proof] val knownSymbols: mMap[Identifier, ConstantLabel] = mMap(equality.id -> equality) + private[proof] val knownSymbols: mMap[Identifier, Constant] = + mMap(equality.id -> equality, top.id -> top, bot.id -> bot, and.id -> and, or.id -> or, neg.id -> neg, implies.id -> implies, iff.id -> iff, forall.id -> forall, exists.id -> exists, epsilon.id -> epsilon) /** * From a given proof, if it is true in the Running theory, add that theorem to the theory and returns it. @@ -90,11 +71,7 @@ class RunningTheory { val usesSorry = sorry || justifications.exists(_ match { case Theorem(name, proposition, withSorry) => withSorry case Axiom(name, ax) => false - case d: Definition => - d match { - case PredicateDefinition(label, expression) => false - case FunctionDefinition(label, out, expression, withSorry) => withSorry - } + case d: Definition => false }) val thm = Theorem(name, proof.conclusion, usesSorry) theorems.update(name, thm) @@ -105,28 +82,28 @@ class RunningTheory { } else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) else InvalidJustification("Not all imports of the proof are correctly justified.", 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 expression The functional formula defining the predicate. - * @return A definition object if the parameters are correct, - */ - def makePredicateDefinition(label: ConstantAtomicLabel, expression: LambdaTermFormula): RunningTheoryJudgement[this.PredicateDefinition] = { - val LambdaTermFormula(vars, body) = expression - if (belongsToTheory(body)) - if (isAvailable(label)) - if (body.freeSchematicTermLabels.subsetOf(vars.toSet) && body.schematicAtomicLabels.isEmpty) { - val newDef = PredicateDefinition(label, expression) - predDefinitions.update(label, Some(newDef)) - knownSymbols.update(label.id, label) - RunningTheoryJudgement.ValidJustification(newDef) - } else InvalidJustification("The definition is not allowed to contain schematic symbols or free variables.", None) - else InvalidJustification("The specified symbol id is already part of the theory and can't be redefined.", None) - else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) + + def makeDefinition(cst: Constant, expression: Expression, vars: Seq[Variable]): RunningTheoryJudgement[this.Definition] = { + if (cst.sort.depth == vars.length) + if (flatTypeParameters(cst.sort) zip vars.map(_.sort) forall { case (a, b) => a == b }) + if (cst.sort == expression.sort) + if (belongsToTheory(expression)) + if (isAvailable(cst)) + if (expression.freeVariables.isEmpty) { + val newDef = Definition(cst, expression, vars) + definitions.update(cst, Some(newDef)) + knownSymbols.update(cst.id , cst) + RunningTheoryJudgement.ValidJustification(newDef) + } else InvalidJustification("The definition is not allowed to contain schematic symbols or free variables.", None) + else InvalidJustification("The specified symbol id is already part of the theory and can't be redefined.", None) + else InvalidJustification("All symbols in the definition must belong to the theory. You need to add missing symbols to the theory.", None) + else InvalidJustification("The type of the constant and the type of the expression must be the same.", None) + else InvalidJustification("The types of the variables must match the type of the constant.", None) + else InvalidJustification("The arity of the label must be equal to the number of parameters in the definition.", 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 @@ -191,27 +168,22 @@ class RunningTheory { } else InvalidJustification("All symbols in the conclusion of the proof must belong to the theory. You need to add missing symbols to the theory.", None) } else InvalidJustification("The arity of the label must be equal to the number of parameters in the definition.", None) } +*/ + def sequentFromJustification(j: Justification): Sequent = j match { case Theorem(name, proposition, _) => proposition case Axiom(name, ax) => Sequent(Set.empty, Set(ax)) - case PredicateDefinition(label, LambdaTermFormula(vars, body)) => - val inner = ConnectorFormula(Iff, Seq(AtomicFormula(label, vars.map(VariableTerm.apply)), body)) - Sequent(Set(), Set(inner)) - case FunctionDefinition(label, out, LambdaTermFormula(vars, body), _) => - val inner = BinderFormula( - Forall, - out, - ConnectorFormula( - Iff, - Seq( - AtomicFormula(equality, Seq(Term(label, vars.map(VariableTerm.apply)), VariableTerm(out))), - body - ) - ) - ) - Sequent(Set(), Set(inner)) - + case Definition(cst, e, vars) => + val left = vars.foldLeft(cst: Expression)(_(_)) + val right = vars.foldLeft(e)(_(_)) + if (left.sort == Formula) { + val inner = iff(left)(right) + Sequent(Set(), Set(inner)) + } else { + val inner = equality(left)(right) + Sequent(Set(), Set(inner)) + } } /** @@ -222,8 +194,8 @@ class RunningTheory { * @param f the new axiom to be added. * @return true if the axiom was added to the theory, false else. */ - def addAxiom(name: String, f: Formula): Option[Axiom] = { - if (belongsToTheory(f)) { + def addAxiom(name: String, f: Expression): Option[Axiom] = { + if (f.sort == Formula && belongsToTheory(f)) { val ax = Axiom(name, f) theoryAxioms.update(name, ax) Some(ax) @@ -237,22 +209,18 @@ class RunningTheory { * that it is empty can be introduced as well. */ - def addSymbol(s: ConstantLabel): Unit = { - if (isAvailable(s)) { - knownSymbols.update(s.id, s) - s match { - case c: ConstantFunctionLabel => funDefinitions.update(c, None) - case c: ConstantAtomicLabel => predDefinitions.update(c, None) - } + def addSymbol(c: Constant): Unit = { + if (isAvailable(c)) { + knownSymbols.update(c.id, c) + definitions.update(c, None) } else {} } /** * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. */ - def makeFormulaBelongToTheory(phi: Formula): Unit = { - phi.constantAtomicLabels.foreach(addSymbol) - phi.constantTermLabels.foreach(addSymbol) + def makeFormulaBelongToTheory(e: Expression): Unit = { + e.constants.foreach(addSymbol) } /** @@ -264,34 +232,16 @@ class RunningTheory { } /** - * 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 AtomicFormula(label, args) => - label match { - case l: ConstantAtomicLabel => isSymbol(l) && args.forall(belongsToTheory) - case _ => args.forall(belongsToTheory) - } - case ConnectorFormula(label, args) => args.forall(belongsToTheory) - case BinderFormula(label, bound, inner) => belongsToTheory(inner) - } - - /** - * Verify if a given term belongs to the language of the theory. + * Verify if a given expression belongs to the language of the theory. * - * @param t The term to check + * @param e The expression to check * @return Weather t belongs to the specified language. */ - def belongsToTheory(t: Term): Boolean = t match { - case Term(label, args) => - label match { - case l: ConstantFunctionLabel => isSymbol(l) && args.forall(belongsToTheory) - case _: SchematicTermLabel => args.forall(belongsToTheory) - } - + def belongsToTheory(e: Expression): Boolean = e match { + case v: Variable => true + case c: Constant => isSymbol(c) + case Application(f, arg) => belongsToTheory(f) && belongsToTheory(arg) + case Lambda(v, t) => belongsToTheory(t) } /** @@ -308,21 +258,18 @@ class RunningTheory { * * @return the set of symbol currently in the theory's language. */ - def language(): List[(ConstantLabel, Option[Definition])] = funDefinitions.toList ++ predDefinitions.toList + def language(): List[(Constant, Option[Definition])] = definitions.toList /** * Check if a label is a symbol of the theory. */ - def isSymbol(label: ConstantLabel): Boolean = label match { - case c: ConstantFunctionLabel => funDefinitions.contains(c) - case c: ConstantAtomicLabel => predDefinitions.contains(c) - } + def isSymbol(cst: Constant): Boolean = definitions.contains(cst) /** * Check if a label is not already used in the theory. * @return */ - def isAvailable(label: ConstantLabel): Boolean = !knownSymbols.contains(label.id) + def isAvailable(label: Constant): Boolean = !knownSymbols.contains(label.id) /** * Public accessor to the current set of axioms of the theory @@ -334,22 +281,17 @@ class RunningTheory { /** * Verify if a given formula is an axiom of the theory */ - def isAxiom(f: Formula): Boolean = theoryAxioms.exists(a => isSame(a._2.ax, f)) + def isAxiom(f: Expression): Boolean = f.sort == Formula && theoryAxioms.exists(a => isSame(a._2.ax, f)) /** * Get the Axiom that is the same as the given formula, if it exists in the theory. */ - def getAxiom(f: Formula): Option[Axiom] = theoryAxioms.find(a => isSame(a._2.ax, f)).map(_._2) - - /** - * Get the definition of the given label, if it is defined in the theory. - */ - def getDefinition(label: ConstantAtomicLabel): Option[PredicateDefinition] = predDefinitions.get(label).flatten + def getAxiom(f: Expression): Option[Axiom] = if (f.sort == Formula) theoryAxioms.find(a => isSame(a._2.ax, f)).map(_._2) else None /** * Get the definition of the given label, if it is defined in the theory. */ - def getDefinition(label: ConstantFunctionLabel): Option[FunctionDefinition] = funDefinitions.get(label).flatten + def getDefinition(label: Constant): Option[Definition] = definitions.get(label).flatten /** * Get the Axiom with the given name, if it exists in the theory. @@ -364,10 +306,7 @@ class RunningTheory { /** * Get the definition for the given identifier, if it is defined in the theory. */ - def getDefinition(name: Identifier): Option[Definition] = knownSymbols.get(name).flatMap { - case f: ConstantAtomicLabel => getDefinition(f) - case f: ConstantFunctionLabel => getDefinition(f) - } + def getDefinition(name: Identifier): Option[Definition] = knownSymbols.get(name).flatMap(getDefinition) } object RunningTheory { diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala index fb4e5c799..fb283d898 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProof.scala @@ -94,4 +94,4 @@ object SCProof { SCProof(steps.toIndexedSeq) } -} +} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala index bf7c71147..9d7b1c84e 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -4,6 +4,7 @@ import lisa.kernel.fol.FOL._ import lisa.kernel.proof.SCProofCheckerJudgement._ import lisa.kernel.proof.SequentCalculus._ + object SCProofChecker { /** @@ -42,7 +43,7 @@ object SCProofChecker { * Γ |- Γ */ case RestateTrue(s) => - val truth = Sequent(Set(), Set(AtomicFormula(top, Nil))) + val truth = Sequent(Set(), Set(top)) if (isSameSequent(s, truth)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The desired conclusion is not a trivial tautology") /* * @@ -50,17 +51,22 @@ object SCProofChecker { * Γ, φ |- φ, Δ */ case Hypothesis(Sequent(left, right), phi) => - if (contains(left, phi)) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (contains(left, phi)) if (contains(right, phi)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"Right-hand side does not contain formula φ") else SCInvalidProof(SCProof(step), 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) && (!contains(ref(t1).left, phi) || contains(b.left, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (isSameSet(b.left + phi, ref(t1).left union ref(t2).left) && (!contains(ref(t1).left, phi) || contains(b.left, phi))) if (isSameSet(b.right + phi, ref(t2).right union ref(t1).right) && (!contains(ref(t2).right, phi) || contains(b.right, phi))) if (contains(ref(t2).left, phi)) if (contains(ref(t1).right, phi)) @@ -77,8 +83,12 @@ object SCProofChecker { * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ */ case LeftAnd(b, t1, phi, psi) => - if (isSameSet(ref(t1).right, b.right)) { - val phiAndPsi = ConnectorFormula(And, Seq(phi, psi)) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else if (isSameSet(ref(t1).right, b.right)) { + val phiAndPsi = and(phi)(psi) if ( isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || @@ -93,13 +103,15 @@ object SCProofChecker { * Γ, Σ, φ∨ψ |- Δ, Π */ case LeftOr(b, t, disjuncts) => - if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { - val phiOrPsi = ConnectorFormula(Or, disjuncts) + if (disjuncts.exists(phi => phi.sort != Formula)){ + val culprit = disjuncts.find(phi => phi.sort != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.sort) + } else if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { + val phiOrPsi = disjuncts.reduceLeft(or(_)(_)) if ( t.zip(disjuncts).forall { case (s, phi) => isSubset(ref(s).left, b.left + phi) } && isSubset(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _) + phiOrPsi) ) - SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") } else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion is not the union of the right-hand sides of the premises.") @@ -109,30 +121,42 @@ object SCProofChecker { * Γ, Σ, φ⇒ψ |- Δ, Π */ 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)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ⇒ψ.") - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ must be identical to union of right-hand sides of premisces.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(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)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premisces + φ⇒ψ.") + else SCInvalidProof(SCProof(step), 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) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ⇔ψ must be same as left-hand side of premise + either φ⇒ψ, ψ⇒φ or both.") - else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of premise and conclusion must be the same.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(phi)(psi) + val psiImpPhi = implies(psi)(phi) + val phiIffPsi = iff(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) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ⇔ψ must be same as left-hand side of premise + either φ⇒ψ, ψ⇒φ or both.") + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of premise and conclusion must be the same.") + } /* * Γ |- φ, Δ @@ -140,12 +164,16 @@ object SCProofChecker { * Γ, ¬φ |- Δ */ 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)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else { + val nPhi = neg(phi) + if (isSameSet(b.left, ref(t1).left + nPhi)) + if (isSameSet(b.right + phi, ref(t1).right)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ must be the same as right-hand side of premise") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ") + } /* * Γ, φ[t/x] |- Δ @@ -153,8 +181,14 @@ object SCProofChecker { * Γ, ∀x. φ |- Δ */ case LeftForall(b, t1, phi, x, t) => - if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + substituteVariablesInFormula(phi, Map(x -> t)), ref(t1).left + BinderFormula(Forall, x, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (t.sort != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.sort) + else if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + substituteVariables(phi, Map(x -> t)), ref(t1).left + forall(Lambda(x, phi)))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ[t/x] must be the same as left-hand side of premise + ∀x. φ") else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") @@ -165,28 +199,18 @@ object SCProofChecker { * Γ, ∃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 (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left + exists(Lambda(x, phi)))) if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise + ∃x. φ") else SCInvalidProof(SCProof(step), 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)) - val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(AtomicFormula(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))) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ must be the same as left-hand side of premise + ∃!x. φ") - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise") - // Right rules /* * Γ |- φ, Δ Σ |- ψ, Π @@ -194,82 +218,113 @@ object SCProofChecker { * Γ, Σ |- φ∧ψ, Π, Δ */ case RightAnd(b, t, cunjuncts) => - val phiAndPsi = ConnectorFormula(And, cunjuncts) - if (isSameSet(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _))) - if ( - t.zip(cunjuncts).forall { case (s, phi) => isSubset(ref(s).right, b.right + phi) } && - isSubset(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) - //isSameSet(cunjuncts.foldLeft(b.right)(_ + _), t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") - else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + if (cunjuncts.exists(phi => phi.sort != Formula)){ + val culprit = cunjuncts.find(phi => phi.sort != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.sort) + } else { + val phiAndPsi = cunjuncts.reduce(and(_)(_)) + if (isSameSet(b.left, t.map(ref(_).left).fold(Set.empty)(_ union _))) + if ( + t.zip(cunjuncts).forall { case (s, phi) => isSubset(ref(s).right, b.right + phi) } && + isSubset(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) + //isSameSet(cunjuncts.foldLeft(b.right)(_ + _), t.map(ref(_).right).fold(Set.empty)(_ union _) + phiAndPsi) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises φ∧ψ.") + else SCInvalidProof(SCProof(step), 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) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") - else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise and the conclusion must be the same.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiOrPsi = or(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) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ∧ψ must be same as right-hand side of premise + either φ, ψ or both.") + else SCInvalidProof(SCProof(step), 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)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ⇒ψ.") - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(phi)(psi) + if (isSameSet(ref(t1).left, b.left + phi)) + if (isSameSet(b.right + psi, ref(t1).right + phiImpPsi)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ψ must be same as right-hand side of premise + φ⇒ψ.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + psi must be same as left-hand side of premise.") + } /* * Γ |- φ⇒ψ, Δ Σ |- ψ⇒φ, Π * ---------------------------- * Γ, Σ |- φ⇔ψ, Π, Δ */ 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 ( - isSubset(ref(t1).right, b.right + phiImpPsi) && - isSubset(ref(t2).right, b.right + psiImpPhi) && - isSubset(b.right, ref(t1).right union ref(t2).right + phiIffPsi) - ) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), 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 SCInvalidProof(SCProof(step), Nil, s"Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (psi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.sort) + else { + val phiImpPsi = implies(phi)(psi) + val psiImpPhi = implies(psi)(phi) + val phiIffPsi = iff(phi)(psi) + if (isSameSet(b.left, ref(t1).left union ref(t2).left)) + if ( + isSubset(ref(t1).right, b.right + phiImpPsi) && + isSubset(ref(t2).right, b.right + psiImpPhi) && + isSubset(b.right, ref(t1).right union ref(t2).right + phiIffPsi) + ) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), 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 SCInvalidProof(SCProof(step), 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)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion must be the same as right-hand side of premise + ¬φ") + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else { + val nPhi = neg(phi) + if (isSameSet(b.right, ref(t1).right + nPhi)) + if (isSameSet(b.left + phi, ref(t1).left)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of conclusion + φ must be the same as left-hand side of premise") + else SCInvalidProof(SCProof(step), 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 (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + phi, ref(t1).right + forall(Lambda(x, phi)))) if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") @@ -281,27 +336,39 @@ object SCProofChecker { * Γ |- ∃x. φ, Δ */ case RightExists(b, t1, phi, x, t) => - if (isSameSet(b.left, ref(t1).left)) - if (isSameSet(b.right + substituteVariablesInFormula(phi, Map(x -> t)), ref(t1).right + BinderFormula(Exists, x, phi))) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (t.sort != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.sort) + else if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + substituteVariables(phi, Map(x -> t)), ref(t1).right + exists(Lambda(x, phi)))) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") /** *
- * Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ - * ---------------------------- if y is not free in φ - * Γ|- ∃!x. φ, Δ + * Γ |- φ[t/x], Δ + * -------------------------- + * Γ|- φ[(εx. φ)/x], Δ **/ - case RightExistsOne(b, t1, phi, x) => - val y = VariableLabel(freshId(phi.freeVariables.map(_.id), x.id)) - val temp = BinderFormula(Exists, y, BinderFormula(Forall, x, ConnectorFormula(Iff, List(AtomicFormula(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))) + case RightEpsilon(b, t1, phi, x, t) => + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (x.sort != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.sort) + else if (t.sort != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.sort) + else if (isSameSet(b.left, ref(t1).left)) { + val expected_top = substituteVariables(phi, Map(x -> t)) + val expected_bot = substituteVariables(phi, Map(x -> epsilon(Lambda(x, phi)))) + if (isSameSet(b.right + expected_top, ref(t1).right + expected_bot)) SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ must be the same as right-hand side of premise + ∃!x. φ") - else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of conclusion and premise must be the same") + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[t/x] must be the same as right-hand side of the premise + ∃x. φ") + } else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") // Structural rules /* @@ -314,6 +381,18 @@ object SCProofChecker { SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, "Conclusion cannot be trivially derived from premise.") + /** + *
+ * Γ, φ[(λy. e)t/x] |- Δ + * --------------------------- + * Γ, φ[e[t/y]/x] |- Δ + *+ */ + case Beta(b, t1) => + if (isSame(sequentToFormula(b).betaNormalForm, sequentToFormula(ref(t1)).betaNormalForm)) { + SCValidProof(SCProof(step)) + } else SCInvalidProof(SCProof(step), Nil, "The conclusion is not beta-OL-equivalent to the premise.") + // Equality Rules /* * Γ, s=s |- Δ @@ -322,8 +401,8 @@ object SCProofChecker { */ case LeftRefl(b, t1, phi) => phi match { - case AtomicFormula(`equality`, Seq(left, right)) => - if (isSameTerm(left, right)) + case equality(left, right) => + if (isSame(left, right)) if (isSameSet(b.right, ref(t1).right)) if (isSameSet(b.left + phi, ref(t1).left)) SCValidProof(SCProof(step)) @@ -340,8 +419,8 @@ object SCProofChecker { */ case RightRefl(b, phi) => phi match { - case AtomicFormula(`equality`, Seq(left, right)) => - if (isSameTerm(left, right)) + case equality(left, right) => + if (isSame(left, right)) if (contains(b.right, phi)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"Right-Hand side of conclusion does not contain φ") @@ -349,26 +428,32 @@ object SCProofChecker { case _ => SCInvalidProof(SCProof(step), Nil, s"φ is not an equality.") } - /* - * Γ, φ(s_) |- Δ - * --------------------- - * Γ, (s=t)_, φ(t_)|- Δ + /** + *
+ * Γ, φ(s_) |- Δ + * ----------------------------------------------------- + * Γ, (∀x,...,z. (s x ... z)=(t x ... z))_, φ(t_) |- Δ + *+ * equals elements must have type ... -> ... -> Term */ case LeftSubstEq(b, t1, equals, lambdaPhi) => val (s_es, t_es) = equals.unzip val (phi_args, phi_body) = lambdaPhi if (phi_args.size != s_es.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.sort != arg.sort || t.sort != arg.sort || !(arg.sort.isFunctional || arg.sort.isPredicate) }) SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") else { - val phi_s_for_f = instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) - val phi_t_for_f = instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) + val phi_s_for_f = substituteVariables(phi_body, (phi_args zip s_es).toMap) + val phi_t_for_f = substituteVariables(phi_body, (phi_args zip t_es).toMap) val sEqT_es = equals map { case (s, t) => - assert(s.vars.size == t.vars.size) - val base = AtomicFormula(equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } + val no = ((s.freeVariables ++ t.freeVariables).view.map(_.id.no) ++ Seq(-1)).max+1 + val vars = (no until no+s.sort.depth).map(i => Variable(Identifier("x", i), Term)) + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val base = if (inner1.sort == Formula) iff(inner1)(inner2) else equality(inner1)(inner2) + vars.foldRight(base : Expression) { case (s_arg, acc) => forall(Lambda(s_arg, acc)) } } if (isSameSet(b.right, ref(t1).right)) @@ -387,117 +472,128 @@ object SCProofChecker { } /* - * Γ |- φ(s_), Δ - * --------------------- - * Γ, (s=t)_ |- φ(t_), Δ + * Γ |- φ(s), Δ Σ |- s=t, Π + * --------------------------------- + * Γ, Σ |- φ(t), Δ, Π */ case RightSubstEq(b, t1, equals, lambdaPhi) => val (s_es, t_es) = equals.unzip val (phi_args, phi_body) = lambdaPhi - if (phi_args.size != equals.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. + if (phi_args.size != s_es.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.sort != arg.sort || t.sort != arg.sort }) SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") else { - val phi_s_for_f = instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) - val phi_t_for_f = instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) + val phi_s_for_f = substituteVariables(phi_body, (phi_args zip s_es).toMap) + val phi_t_for_f = substituteVariables(phi_body, (phi_args zip t_es).toMap) val sEqT_es = equals map { case (s, t) => - assert(s.vars.size == t.vars.size) - val base = AtomicFormula(equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } + val no = ((s.freeVariables ++ t.freeVariables).view.map(_.id.no) ++ Seq(0)).max+1 + val vars = (no until no+s.sort.depth).map(i => Variable(Identifier("x", i), Term)) + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val base = if (inner1.sort == Formula) iff(inner1)(inner2) else equality(inner1)(inner2) + vars.foldRight(base : Expression) { case (s_arg, acc) => forall(Lambda(s_arg, acc)) } } - - if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) + if (isSameSet(b.left, ref(t1).left ++ sEqT_es)) 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) - ) + isSameSet(b.right + phi_t_for_f, ref(t1).right + phi_s_for_f) || + isSameSet(b.right + phi_s_for_f, ref(t1).right + phi_t_for_f) + ) { SCValidProof(SCProof(step)) - else + } + else { + SCInvalidProof( SCProof(step), Nil, "Right-hand side of the premise and the conclusion should be the same with each containing one of φ(s_) φ(t_), but it isn't the case." - ) + )} else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise + (s=t)_ must be the same as left-hand side of the premise.") } + +/* /* - * Γ, φ(ψ_) |- Δ + * Γ |- φ[ψ/?p], Δ * --------------------- - * Γ, ψ⇔τ, φ(τ) |- Δ + * Γ, ψ⇔τ |- φ[τ/?p], Δ */ - case LeftSubstIff(b, t1, equals, lambdaPhi) => - val (phi_s, tau_s) = equals.unzip - val (phi_args, phi_body) = lambdaPhi - if (phi_args.size != phi_s.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. - SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + case RightSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.sort != phi_arg.sort || tau.sort != phi_arg.sort) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else if (!psi.sort.isPredicate) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") else { - val phi_psi_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip phi_s).toMap) - val phi_tau_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equals map { - case (s, t) => - assert(s.vars.size == t.vars.size) - val base = ConnectorFormula(Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } - } + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> psi)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> tau)) - if (isSameSet(b.right, ref(t1).right)) - if ( - isSameSet(b.left + phi_tau_for_q, ref(t1).left ++ psiIffTau + phi_psi_for_q) || - isSameSet(b.left + phi_psi_for_q, ref(t1).left ++ psiIffTau + phi_tau_for_q) - ) - SCValidProof(SCProof(step)) - else - SCInvalidProof( - SCProof(step), - Nil, - "Left-hand sides of the conclusion + φ(ψ_) must be the same as left-hand side of the premise + (ψ⇔τ)_ + φ(τ_) (or with ψ and τ swapped)." - ) + val inner1 = vars.foldLeft(psi)(_(_)) + val inner2 = vars.foldLeft(tau)(_(_)) + val sEqt = iff(inner1)(inner2) + val varss = vars.toSet + + if ( + isSubset(ref(t1).right, b.right + phi_s_for_f) && + isSubset(ref(t2).right, b.right + sEqt) && + isSubset(b.right, ref(t1).right union ref(t2).right + phi_t_for_f) + ) { + if (isSameSet(b.left, ref(t1).left union ref(t2).left)) { + if ( + ref(t2).left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + ref(t2).right.exists(f => !isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + SCInvalidProof(SCProof(step), Nil, "The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else SCValidProof(SCProof(step)) + } + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } - /* - * Γ |- φ[ψ/?p], Δ - * --------------------- - * Γ, ψ⇔τ |- φ[τ/?p], Δ + /* + * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π + * -------------------------------- + * Γ, Σ φ(b) |- Δ, Π */ - case RightSubstIff(b, t1, equals, lambdaPhi) => - val (psi_s, tau_s) = equals.unzip - val (phi_args, phi_body) = lambdaPhi - if (phi_args.size != psi_s.size) - SCInvalidProof(SCProof(step), Nil, "The number of arguments of φ must be the same as the number of equalities.") - else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") - else { - val phi_psi_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip psi_s).toMap) - val phi_tau_for_q = instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equals map { - case (s, t) => - assert(s.vars.size == t.vars.size) - val base = ConnectorFormula(Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(VariableTerm)))) - (s.vars).foldLeft(base: Formula) { case (acc, s_arg) => BinderFormula(Forall, s_arg, acc) } - } + case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.sort != phi_arg.sort || tau.sort != phi_arg.sort) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else /*if (!psi.sort.isPredicate) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") + else */{ + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> psi)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> tau)) + + val inner1 = vars.foldLeft(psi)(_(_)) + val inner2 = vars.foldLeft(tau)(_(_)) + val sEqt = iff(inner1)(inner2) + val varss = vars.toSet - if (isSameSet(ref(t1).left ++ psiIffTau, b.left)) + if ( + isSubset(ref(t1).right, b.right) && + isSubset(ref(t2).right, b.right + sEqt) && + isSubset(b.right, ref(t1).right union ref(t2).right) + ) { 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) - ) - SCValidProof(SCProof(step)) - else - SCInvalidProof( - SCProof(step), - Nil, - "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 SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the premise + ψ⇔τ must be the same as left-hand side of the premise.") + isSubset(ref(t1).left, b.left + phi_s_for_f) && + isSubset(ref(t2).left, b.left) && + isSubset(b.left, ref(t1).left union ref(t2).left + phi_t_for_f) + ) { + if ( + ref(t2).left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + ref(t2).right.exists(f => !isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + SCInvalidProof(SCProof(step), Nil, "The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else SCValidProof(SCProof(step)) + } + else SCInvalidProof(SCProof(step), Nil, "Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } - - +*/ /** *
@@ -506,9 +602,9 @@ object SCProofChecker { * Γ[ψ/?p] |- Δ[ψ/?p] **/ - case InstSchema(bot, t1, mCon, mPred, mTerm) => + case InstSchema(bot, t1, subst) => val expected = - (ref(t1).left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)), ref(t1).right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm))) + (ref(t1).left.map(phi => substituteVariables(phi, subst)), ref(t1).right.map(phi => substituteVariables(phi, subst))) if (isSameSet(bot.left, expected._1)) if (isSameSet(bot.right, expected._2)) SCValidProof(SCProof(step)) @@ -564,4 +660,4 @@ object SCProofChecker { else possibleError.get } -} +} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala index b84d84b44..0d871d315 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala @@ -21,12 +21,26 @@ object SequentCalculus { * @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]) + case class Sequent(left: Set[Expression], right: Set[Expression]){ + require(left.forall(_.sort == Formula) && right.forall(_.sort == Formula), "Sequent can only contain formulas") + } /** * 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))) + def sequentToFormula(s: Sequent): Expression = { + val left = { + if (s.left.isEmpty) top + else if (s.left.size == 1) s.left.head + else s.left.reduce(and(_)(_)) + } + val right ={ + if (s.right.isEmpty) bot + else if (s.right.size == 1) s.right.head + else s.right.reduce(or(_)(_)) + } + implies(left)(right) + } /** * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. @@ -85,7 +99,7 @@ object SequentCalculus { * Γ, φ |- φ, Δ * */ - case class Hypothesis(bot: Sequent, phi: Formula) extends SCProofStep { val premises = Seq() } + case class Hypothesis(bot: Sequent, phi: Expression) extends SCProofStep { val premises = Seq() } /** *
@@ -94,7 +108,7 @@ object SequentCalculus { * Γ, Σ |-Δ, Π **/ - case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } // Left rules /** @@ -104,7 +118,7 @@ object SequentCalculus { * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ * */ - case class LeftAnd(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftAnd(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -113,7 +127,7 @@ object SequentCalculus { * Γ, Σ, φ∨ψ∨... |- Δ, Π **/ - case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Expression]) extends SCProofStep { val premises = t } /** *
@@ -122,7 +136,7 @@ object SequentCalculus { * Γ, Σ, φ⇒ψ |- Δ, Π **/ - case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } /** *
@@ -131,7 +145,7 @@ object SequentCalculus { * Γ, φ⇔ψ |- Δ Γ, φ⇔ψ |- Δ **/ - case class LeftIff(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftIff(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -140,7 +154,7 @@ object SequentCalculus { * Γ, ¬φ |- Δ **/ - case class LeftNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -150,7 +164,7 @@ object SequentCalculus { * **/ - case class LeftForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } + case class LeftForall(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -160,16 +174,7 @@ object SequentCalculus { * **/ - case class LeftExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } - - /** - *
- * Γ, ∃y.∀x. (x=y) ⇔ φ |- Δ - * ---------------------------- if y is not free in φ - * Γ, ∃!x. φ |- Δ - *- */ - case class LeftExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + case class LeftExists(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } // Right rules /** @@ -179,7 +184,7 @@ object SequentCalculus { * Γ, Σ |- φ∧ψ∧..., Π, Δ * */ - case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Expression]) extends SCProofStep { val premises = t } /** *
@@ -188,7 +193,7 @@ object SequentCalculus { * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ **/ - case class RightOr(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class RightOr(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -197,7 +202,7 @@ object SequentCalculus { * Γ |- φ⇒ψ, Δ **/ - case class RightImplies(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class RightImplies(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -206,7 +211,7 @@ object SequentCalculus { * Γ, Σ |- φ⇔ψ, Π, Δ **/ - case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } /** *
@@ -215,7 +220,7 @@ object SequentCalculus { * Γ |- ¬φ, Δ **/ - case class RightNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + case class RightNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -224,7 +229,7 @@ object SequentCalculus { * Γ |- ∀x. φ, Δ **/ - case class RightForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + case class RightForall(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -235,16 +240,16 @@ object SequentCalculus { * (ln-x stands for locally nameless x) **/ - case class RightExists(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } + case class RightExists(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
- * Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ - * ---------------------------- if y is not free in φ - * Γ|- ∃!x. φ, Δ + * Γ |- φ[t/x], Δ + * -------------------------- if y is not free in φ + * Γ|- φ[(εx. φ)/x], Δ **/ - case class RightExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + case class RightEpsilon(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } // Structural rule /** @@ -256,6 +261,18 @@ object SequentCalculus { */ case class Weakening(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+ * Γ |- φ[(λy. e)t/x], Δ + * --------------------------- + * Γ |- φ[e[t/y]/x], Δ + *+ */ + case class Beta(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + + // Equality Rules /** *
@@ -264,7 +281,7 @@ object SequentCalculus { * Γ |- Δ **/ - case class LeftRefl(bot: Sequent, t1: Int, fa: Formula) extends SCProofStep { val premises = Seq(t1) } + case class LeftRefl(bot: Sequent, t1: Int, fa: Expression) extends SCProofStep { val premises = Seq(t1) } /** *
@@ -273,53 +290,68 @@ object SequentCalculus { * |- s=s **/ - case class RightRefl(bot: Sequent, fa: Formula) extends SCProofStep { val premises = Seq() } + case class RightRefl(bot: Sequent, fa: Expression) extends SCProofStep { val premises = Seq() } /** *
- * Γ, φ(s1,...,sn) |- Δ - * --------------------- - * Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ + * Γ, φ(s) |- Δ + * ----------------------------------------------------- + * Γ, ∀x,...,z. (s x ... z)=(t x ... z), φ(t) |- Δ *+ * equals elements must have type ... -> ... -> Term */ - case class LeftSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class LeftSubstEq(bot: Sequent, t1: Int, equals: Seq[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
- * Γ |- φ(s1,...,sn), Δ - * --------------------- - * Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ + * Γ |- φ(s), Δ + * ------------------------------------------------------ + * Γ, ∀x,...,z. (s x ... z)=(t x ... z) |- φ(t), Δ *+ * equals elements must have type ... -> ... -> Term */ - case class RightSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class RightSubstEq(bot: Sequent, t1: Int, equals: Seq[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } + + + object LeftSubstIff { + def apply(bot: Sequent, t1: Int, equals: Seq[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)): LeftSubstEq = { + new LeftSubstEq(bot, t1, equals, lambdaPhi) + } + } + object RightSubstIff { + def apply(bot: Sequent, t1: Int, equals: Seq[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)): RightSubstEq = { + new RightSubstEq(bot, t1, equals, lambdaPhi) + } + } + + /* /** *
- * Γ, φ(a1,...an) |- Δ - * --------------------- - * Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ + * Γ, φ(ψ) |- Δ Σ |- ψ⇔τ, Π + * -------------------------------- + * Γ, Σ φ(τ) |- Δ, Π *+ * equals elements must have type ... -> ... -> Formula */ - case class LeftSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class LeftSubstIff(bot: Sequent, t1: Int, t2: Int, psi: Expression, tau: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1, t2) } /** *
- * Γ |- φ(a1,...an), Δ - * --------------------- - * Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ + * Γ |- φ(ψ), Δ Σ |- ψ⇔τ, Π + * -------------------------------- + * Γ, Σ |- φ(τ), Δ, Π *+ * equals elements must have type ... -> ... -> Formula */ - - case class RightSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } - + case class RightSubstIff(bot: Sequent, t1: Int, t2: Int, psi: Expression, tau: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1, t2) } +*/ // Rule for schemas case class InstSchema( bot: Sequent, t1: Int, - mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula], - mPred: Map[SchematicAtomicLabel, LambdaTermFormula], - mTerm: Map[SchematicTermLabel, LambdaTermTerm] + subst: Map[Variable, Expression] ) extends SCProofStep { val premises = Seq(t1) } // Proof Organisation rules @@ -345,4 +377,4 @@ object SequentCalculus { */ case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } -} +} \ No newline at end of file diff --git a/lisa-sets/src/main/scala/lisa/Main.scala b/lisa-sets/src/main/scala/lisa/Main.scala index c96ac042b..8a49a8f68 100644 --- a/lisa-sets/src/main/scala/lisa/Main.scala +++ b/lisa-sets/src/main/scala/lisa/Main.scala @@ -25,7 +25,7 @@ trait Main extends BasicMain { knownDefs.update(powerSet, Some(powerAxiom)) knownDefs.update(subset, Some(subsetAxiom)) - extension (symbol: ConstantLabel[?]) { + extension (symbol: Constant[?]) { def definition: JUSTIFICATION = { getDefinition(symbol).get } diff --git a/lisa-sets/src/main/scala/lisa/SetTheoryLibrary.scala b/lisa-sets/src/main/scala/lisa/SetTheoryLibrary.scala index addae4c89..c7862e66d 100644 --- a/lisa-sets/src/main/scala/lisa/SetTheoryLibrary.scala +++ b/lisa-sets/src/main/scala/lisa/SetTheoryLibrary.scala @@ -15,17 +15,17 @@ object SetTheoryLibrary extends lisa.prooflib.Library { /** * The symbol for the set membership predicate. */ - final val in = ConstantPredicateLabel("elem", 2) + final val in = constant[Term >>: Term >>: Formula]("elem") /** * The symbol for the subset predicate. */ - final val subset = ConstantPredicateLabel("subsetOf", 2) + final val subset = constant[Term >>: Term >>: Formula]("subsetOf") /** * The symbol for the equicardinality predicate. Needed for Tarski's axiom. */ - final val sim = ConstantPredicateLabel("sameCardinality", 2) // Equicardinality + final val sim = constant[Term >>: Term >>: Formula]("sameCardinality") // Equicardinality /** * Set Theory basic predicates */ @@ -36,33 +36,34 @@ object SetTheoryLibrary extends lisa.prooflib.Library { /** * The symbol for the empty set constant. */ - final val emptySet = Constant("emptySet") + final val emptySet = constant[Term ]("emptySet") /** * The symbol for the unordered pair function. */ - final val unorderedPair = ConstantFunctionLabel("unorderedPair", 2) + final val unorderedPair = constant[Term >>: Term >>: Term]("unorderedPair") /** * The symbol for the powerset function. */ - final val powerSet = ConstantFunctionLabel("powerSet", 1) + final val powerSet = constant[Term >>: Term]("powerSet") /** * The symbol for the set union function. */ - final val union = ConstantFunctionLabel("union", 1) + final val union = constant[Term >>: Term]("union") /** * The symbol for the universe function. Defined in TG set theory. */ - final val universe = ConstantFunctionLabel("universe", 1) + final val universe = constant[Term >>: Term]("universe") /** * Set Theory basic functions. */ final val functions = Set(unorderedPair, powerSet, union, universe) + /** * The kernel theory loaded with Set Theory symbols and axioms. */ @@ -73,13 +74,13 @@ object SetTheoryLibrary extends lisa.prooflib.Library { functions.foreach(s => addSymbol(s)) addSymbol(emptySet) - private val x = variable - private val y = variable - private val z = variable - final val φ = predicate[1] - private val A = variable - private val B = variable - private val P = predicate[2] + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + final val φ = variable[Term >>: Formula] + private val A = variable[Term] + private val B = variable[Term] + private val P = variable[Term >>: Term >>: Formula] //////////// // Axioms // @@ -94,7 +95,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * * `() |- (x = y) ⇔ ∀ z. z ∈ x ⇔ z ∈ y` */ - final val extensionalityAxiom: this.AXIOM = Axiom(forall(z, in(z, x) <=> in(z, y)) <=> (x === y)) + final val extensionalityAxiom: this.AXIOM = Axiom(forall(z, (z ∈ x) <=> (z ∈ y)) <=> (x === y)) /** * Pairing Axiom --- For any sets `x` and `y`, there is a set that contains @@ -106,7 +107,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * This axiom defines [[unorderedPair]] as the function symbol representing * this set. */ - final val pairAxiom: AXIOM = Axiom(in(z, unorderedPair(x, y)) <=> (x === z) \/ (y === z)) + final val pairAxiom: AXIOM = Axiom(z ∈ unorderedPair(x, y) <=> (x === z) \/ (y === z)) /** * Comprehension/Separation Schema --- For a formula `ϕ(_, _)` and a set `z`, @@ -119,7 +120,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * This schema represents an infinite collection of axioms, one for each * formula `ϕ(x, z)`. */ - final val comprehensionSchema: AXIOM = Axiom(exists(y, forall(x, in(x, y) <=> (in(x, z) /\ φ(x))))) + final val comprehensionSchema: AXIOM = Axiom(exists(y, forall(x, (x ∈ y) <=> ((x ∈ z) /\ φ(x))))) /** * Empty Set Axiom --- From the Comprehension Schema follows the existence of @@ -131,7 +132,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * * `() |- !(x ∈ ∅)` */ - final val emptySetAxiom: AXIOM = Axiom(!in(x, emptySet)) + final val emptySetAxiom: AXIOM = Axiom(!(x ∈ emptySet)) /** * Union Axiom --- For any set `x`, there exists a set `union(x)` which is the @@ -144,7 +145,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * * This axiom defines [[union]] as the function symbol representing this set. */ - final val unionAxiom: AXIOM = Axiom(in(z, union(x)) <=> exists(y, in(y, x) /\ in(z, y))) + final val unionAxiom: AXIOM = Axiom(z ∈ union(x) <=> exists(y, (y ∈ x) /\ (z ∈ y))) /** * Subset Axiom --- For sets `x` and `y`, `x` is a subset of `y` iff every @@ -154,7 +155,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * * This axiom defines the [[subset]] symbol as this predicate. */ - final val subsetAxiom: AXIOM = Axiom(subset(x, y) <=> forall(z, in(z, x) ==> in(z, y))) + final val subsetAxiom: AXIOM = Axiom((x ⊆ y) <=> forall(z, (z ∈ x) ==> (z ∈ y))) /** * Power Set Axiom --- For a set `x`, there exists a power set of `x`, denoted @@ -165,7 +166,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * This axiom defines [[powerSet]] as the function symbol representing this * set. */ - final val powerAxiom: AXIOM = Axiom(in(x, powerSet(y)) <=> subset(x, y)) + final val powerAxiom: AXIOM = Axiom(x ∈ powerSet(y) <=> x ⊆ y) /** * Infinity Axiom --- There exists an infinite set. @@ -180,7 +181,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * * `() |- ∃ x. inductive(x)` */ - final val infinityAxiom: AXIOM = Axiom(exists(x, in(emptySet, x) /\ forall(y, in(y, x) ==> in(union(unorderedPair(y, unorderedPair(y, y))), x)))) + final val infinityAxiom: AXIOM = Axiom(exists(x, emptySet ∈ x /\ forall(y, (y ∈ x) ==> union(unorderedPair(y, unorderedPair(y, y))) ∈ x ))) /** * Foundation/Regularity Axiom --- Every non-empty set `x` has an `∈`-minimal @@ -189,7 +190,7 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * * `() |- (x != ∅) ==> ∃ y ∈ x. ∀ z. z ∈ x ⇒ ! z ∈ y` */ - final val foundationAxiom: AXIOM = Axiom(!(x === emptySet) ==> exists(y, in(y, x) /\ forall(z, in(z, x) ==> !in(z, y)))) + final val foundationAxiom: AXIOM = Axiom(!(x === emptySet) ==> exists(y, (y ∈ x) /\ forall(z, (z ∈ x) ==> !(z ∈ y)))) // ZF ///////// @@ -201,18 +202,18 @@ object SetTheoryLibrary extends lisa.prooflib.Library { * satisfy `P` for each `a ∈ x`. */ final val replacementSchema: AXIOM = Axiom( - forall(x, in(x, A) ==> ∀(y, ∀(z, (P(x, y) /\ P(x, z)) ==> (y === z)))) ==> - exists(B, forall(y, in(y, B) <=> exists(x, in(x, A) /\ P(x, y)))) + forall(x, (x ∈ A) ==> ∀(y, ∀(z, (P(x)(y) /\ P(x)(z)) ==> (y === z)))) ==> + exists(B, forall(y, (y ∈ B) <=> exists(x, (x ∈ A) /\ P(x)(y)))) ) final val tarskiAxiom: AXIOM = Axiom( forall( x, - in(x, universe(x)) /\ + (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)))) + (y ∈ universe(x)) ==> ((powerSet(y) ∈ universe(x)) /\ (powerSet(y) ⊆ universe(x))) /\ + forall(z, (z ⊆ universe(x)) ==> (sim(y)(universe(x)) /\ (y ∈ universe(x)))) ) ) ) @@ -245,12 +246,12 @@ object SetTheoryLibrary extends lisa.prooflib.Library { val ∅ = emptySet val ∈ = in - extension (thi: Term) { - def ∈(that: Term): Formula = in(thi, that) - def ⊆(that: Term): Formula = subset(thi, that) + extension (l: Term) + def ∈(r: Term): Formula = in(l)(r) + def ⊆(r: Term): Formula = subset(l)(r) + def =/=(r: Term): Formula = !(l === r) - def =/=(that: Term): Formula = !(thi === that) - } + def unorderedPair(x: Term, y: Term): Term = App(App(unorderedPair, x), y) } diff --git a/lisa-sets/src/main/scala/lisa/automation/CommonTactics.scala b/lisa-sets/src/main/scala/lisa/automation/CommonTactics.scala index 2d2b8219d..db2433271 100644 --- a/lisa-sets/src/main/scala/lisa/automation/CommonTactics.scala +++ b/lisa-sets/src/main/scala/lisa/automation/CommonTactics.scala @@ -1,7 +1,6 @@ package lisa.automation.kernel import lisa.automation.Tautology -import lisa.fol.FOLHelpers.* import lisa.fol.FOL as F import lisa.prooflib.BasicStepTactic.* import lisa.prooflib.ProofTacticLib.{_, given} @@ -10,6 +9,7 @@ import lisa.prooflib.* import lisa.utils.K object CommonTactics { + /* /** *
@@ -185,7 +185,7 @@ object CommonTactics { TacticSubproof { lib.have(F.∀(y, (y === fxs) <=> P)) by Tautology.from(uniqueness, definition.of(subst*)) lib.thenHave((y === fxs) <=> P) by InstantiateForall(y) - lib.thenHave((fxs === fxs) <=> P.substitute(y := fxs)) by InstFunSchema(Map(y -> fxs)) + lib.thenHave((fxs === fxs) <=> P.substitute(y := fxs)) by InstSchema(Map(y -> fxs)) lib.thenHave(P.substitute(y := fxs)) by Restate } @@ -248,7 +248,7 @@ object CommonTactics { TacticSubproof { lib.have(F.∀(y, (y === fxs) <=> P)) by Tautology.from(uniqueness, definition.of(subst*)) lib.thenHave((y === fxs) <=> P) by InstantiateForall(y) - lib.thenHave((fxs === fxs) <=> P.substitute(y := fxs)) by InstFunSchema(Map(y -> fxs)) + lib.thenHave((fxs === fxs) <=> P.substitute(y := fxs)) by InstSchema(Map(y -> fxs)) lib.thenHave(P.substitute(y := fxs)) by Restate lib.thenHave(phi ==> Q(fxs)) by Tautology lib.thenHave(phi |- Q(fxs)) by Restate @@ -258,5 +258,5 @@ object CommonTactics { } } } - +*/ } diff --git a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala index 8c60de410..98d915f73 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala @@ -4,8 +4,8 @@ import lisa.prooflib.BasicStepTactic.* import lisa.prooflib.ProofTacticLib.* import lisa.prooflib.SimpleDeducedSteps.* import lisa.prooflib.* -import lisa.utils.parsing.UnreachableException -import leo.datastructures.TPTP.CNF.AtomicFormula +import lisa.utils.K +import leo.datastructures.TPTP.AnnotatedFormula.FormulaType /** * This tactic tries to prove a sequent by congruence. @@ -27,79 +27,79 @@ import leo.datastructures.TPTP.CNF.AtomicFormula * */ object Congruence extends ProofTactic with ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: Sequent): proof.ProofTacticJudgement = TacticSubproof { - import lib.* + def apply(using lib: Library, proof: lib.Proof)(bot: Sequent): proof.ProofTacticJudgement = TacticSubproof { + import lib.* - val egraph = new EGraphTerms() - egraph.addAll(bot.left) - egraph.addAll(bot.right) + val egraph = new EGraphExpr() + egraph.addAll(bot.left) + egraph.addAll(bot.right) - bot.left.foreach{ - case (left === right) => egraph.merge(left, right) - case (left <=> right) => egraph.merge(left, right) - case _ => () + bot.left.foreach{ + case (left === right) => egraph.merge(left, right) + case (left <=> right) => egraph.merge(left, right) + case _ => () + } + + if isSameSequent(bot, ⊤) then + have(bot) by Restate + else if bot.left.exists { lf => + bot.right.exists { rf => + if egraph.idEq(lf, rf) then + val base = have(bot.left |- (bot.right + lf) ) by Restate + val eq = have(egraph.proveExpr(lf, rf, bot)) + val a = variable[Formula] + have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParameters(Seq((lf, rf)), (Seq(a), a))(base) + have(bot) by Cut(eq, lastStep) + true + else false + } || + bot.left.exists{ + case rf2 @ neg(rf) if egraph.idEq(lf, rf)=> + val base = have((bot.left + !lf) |- bot.right ) by Restate + val eq = have(egraph.proveExpr(lf, rf, bot)) + val a = variable[Formula] + have((bot.left + (lf <=> rf)) |- (bot.right) ) by LeftSubstIff.withParameters(Seq((lf, rf)), (Seq(a), !a))(base) + have(bot) by Cut(eq, lastStep) + true + case _ => false + } || { + lf match + case !(a === b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case !(a <=> b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case _ => false } - if isSameSequent(bot, ⊤) then - have(bot) by Restate - else if bot.left.exists { lf => - bot.right.exists { rf => - if egraph.idEq(lf, rf) then - val base = have(bot.left |- (bot.right + lf) ) by Restate - val eq = have(egraph.proveFormula(lf, rf, bot)) - val a = formulaVariable - have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParametersSimple(List((lf, rf)), lambda(a, a))(base) - have(bot) by Cut(eq, lastStep) - true - else false - } || - bot.left.exists{ - case rf2 @ Neg(rf) if egraph.idEq(lf, rf)=> - val base = have((bot.left + !lf) |- bot.right ) by Restate - val eq = have(egraph.proveFormula(lf, rf, bot)) - val a = formulaVariable - have((bot.left + (lf <=> rf)) |- (bot.right) ) by LeftSubstIff.withParametersSimple(List((lf, rf)), lambda(a, !a))(base) - have(bot) by Cut(eq, lastStep) - true - case _ => false - } || { - lf match - case !(a === b) if egraph.idEq(a, b) => - have(egraph.proveTerm(a, b, bot)) - true - case !(a <=> b) if egraph.idEq(a, b) => - have(egraph.proveFormula(a, b, bot)) - true - case _ => false - } + } then () + else if bot.right.exists { rf => + bot.right.exists{ + case lf2 @ neg(lf) if egraph.idEq(lf, rf)=> + val base = have((bot.left) |- (bot.right + !rf) ) by Restate + val eq = have(egraph.proveExpr(lf, rf, bot)) + val a = variable[Formula] + have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParameters(Seq((lf, rf)), (Seq(a), !a))(base) + have(bot) by Cut(eq, lastStep) + true + case _ => false + } || { + rf match + case (a === b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case (a <=> b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case _ => false + } + } then () + else + return proof.InvalidProofTactic(s"No congruence found to show sequent\n $bot") + } - } then () - else if bot.right.exists { rf => - bot.right.exists{ - case lf2 @ Neg(lf) if egraph.idEq(lf, rf)=> - val base = have((bot.left) |- (bot.right + !rf) ) by Restate - val eq = have(egraph.proveFormula(lf, rf, bot)) - val a = formulaVariable - have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParametersSimple(List((lf, rf)), lambda(a, !a))(base) - have(bot) by Cut(eq, lastStep) - true - case _ => false - } || { - rf match - case (a === b) if egraph.idEq(a, b) => - have(egraph.proveTerm(a, b, bot)) - true - case (a <=> b) if egraph.idEq(a, b) => - have(egraph.proveFormula(a, b, bot)) - true - case _ => false - } - } then () - else - return proof.InvalidProofTactic(s"No congruence found to show sequent\n $bot") - } - } @@ -114,8 +114,8 @@ class UnionFind[T] { var unionCounter = 0 /** - * add a new element to the union-find. - */ + * add a new element to the union-find. + */ def add(x: T): Unit = { parent(x) = x realParent(x) = (x, ((x, x), true, 0)) @@ -123,10 +123,10 @@ class UnionFind[T] { } /** - * - * @param x the element whose parent we want to find - * @return the root of x - */ + * + * @param x the element whose parent we want to find + * @return the root of x + */ def find(x: T): T = { if parent(x) == x then x @@ -142,8 +142,8 @@ class UnionFind[T] { } /** - * Merges the classes of x and y - */ + * Merges the classes of x and y + */ def union(x: T, y: T): Unit = { unionCounter += 1 val xRoot = find(x) @@ -185,9 +185,9 @@ class UnionFind[T] { } /** - * Returns a path from x to y made of pairs of elements (u, v) - * such that union(u, v) was called. - */ + * Returns a path from x to y made of pairs of elements (u, v) + * such that union(u, v) was called. + */ def explain(x:T, y:T): Option[List[(T, T)]]= { if (x == y) then return Some(List()) @@ -220,291 +220,198 @@ class UnionFind[T] { /** - * Returns the set of all roots of all classes - */ + * Returns the set of all roots of all classes + */ def getClasses: Set[T] = parent.keys.map(find).toSet /** - * Add all elements in the collection to the union-find - */ + * Add all elements in the collection to the union-find + */ def addAll(xs: Iterable[T]): Unit = xs.foreach(add) } -/////////////////////////////// -///////// E-graph ///////////// -/////////////////////////////// + /////////////////////////////// + ///////// E-graph ///////////// + /////////////////////////////// import scala.collection.mutable -class EGraphTerms() { +class EGraphExpr() { - val termParentsT = mutable.Map[Term, mutable.Set[AppliedFunctional]]() - val termParentsF = mutable.Map[Term, mutable.Set[AppliedPredicate]]() - val termUF = new UnionFind[Term]() + val parents = mutable.Map[Expr[?], mutable.Set[Expr[?]]]() + val UF = new UnionFind[Expr[?]]() - val formulaParents = mutable.Map[Formula, mutable.Set[AppliedConnector]]() - val formulaUF = new UnionFind[Formula]() - def find(id: Term): Term = termUF.find(id) - def find(id: Formula): Formula = formulaUF.find(id) + def find[T](id: Expr[T]): Expr[T] = UF.find(id).asInstanceOf[Expr[T]] - trait TermStep - case class TermExternal(between: (Term, Term)) extends TermStep - case class TermCongruence(between: (Term, Term)) extends TermStep + trait Step + case class ExternalStep(between: (Expr[?], Expr[?])) extends Step + case class CongruenceStep(between: (Expr[?], Expr[?])) extends Step - trait FormulaStep - case class FormulaExternal(between: (Formula, Formula)) extends FormulaStep - case class FormulaCongruence(between: (Formula, Formula)) extends FormulaStep - val termProofMap = mutable.Map[(Term, Term), TermStep]() - val formulaProofMap = mutable.Map[(Formula, Formula), FormulaStep]() + val proofMap = mutable.Map[(Expr[?], Expr[?]), Step]() - def explain(id1: Term, id2: Term): Option[List[TermStep]] = { - val steps = termUF.explain(id1, id2) - steps.map(_.foldLeft((id1, List[TermStep]())) { + def explain(id1: Expr[?], id2: Expr[?]): Option[List[Step]] = { + val steps = UF.explain(id1, id2) + steps.map(_.foldLeft((id1, List[Step]())) { case ((prev, acc), step) => - termProofMap(step) match - case s @ TermExternal((l, r)) => + proofMap(step) match + case s @ ExternalStep((l, r)) => if l == prev then (r, s :: acc) else if r == prev then - (l, TermExternal(r, l) :: acc) + (l, ExternalStep(r, l) :: acc) else throw new Exception("Invalid proof recovered: It is not a chain") - case s @ TermCongruence((l, r)) => + case s @ CongruenceStep((l, r)) => if l == prev then (r, s :: acc) else if r == prev then - (l, TermCongruence(r, l) :: acc) + (l, CongruenceStep(r, l) :: acc) else throw new Exception("Invalid proof recovered: It is not a chain") }._2.reverse) } - def explain(id1: Formula, id2: Formula): Option[List[FormulaStep]] = { - val steps = formulaUF.explain(id1, id2) - steps.map(_.foldLeft((id1, List[FormulaStep]())) { - case ((prev, acc), step) => - formulaProofMap(step) match - case s @ FormulaExternal((l, r)) => - if l == prev then - (r, s :: acc) - else if r == prev then - (l, FormulaExternal(r, l) :: acc) - else throw new Exception("Invalid proof recovered: It is not a chain") - case s @ FormulaCongruence((l, r)) => - if l == prev then - (r, s :: acc) - else if r == prev then - (l, FormulaCongruence(r, l) :: acc) - else throw new Exception("Invalid proof recovered: It is not a chain") - }._2.reverse) - } - - - def makeSingletonEClass(node:Term): Term = { - termUF.add(node) - termParentsT(node) = mutable.Set() - termParentsF(node) = mutable.Set() - node - } - def makeSingletonEClass(node:Formula): Formula = { - formulaUF.add(node) - formulaParents(node) = mutable.Set() + def makeSingletonEClass(node:Expr[?]): Expr[?] = { + UF.add(node) + parents(node) = mutable.Set() node } - def idEq(id1: Term, id2: Term): Boolean = find(id1) == find(id2) - def idEq(id1: Formula, id2: Formula): Boolean = find(id1) == find(id2) + def idEq(id1: Expr[?], id2: Expr[?]): Boolean = find(id1) == find(id2) - - def canonicalize(node: Term): Term = node match - case AppliedFunctional(label, args) => - AppliedFunctional(label, args.map(t => find(t))) + def canonicalize(node: Expr[?]) : Expr[?] = node match + case App(f, a) => App.unsafe(canonicalize(f), find(a)) case _ => node - - - def canonicalize(node: Formula): Formula = { - node match - case AppliedPredicate(label, args) => AppliedPredicate(label, args.map(find)) - case AppliedConnector(label, args) => AppliedConnector(label, args.map(find)) - case node => node - } - def add(node: Term): Term = - if termUF.parent.contains(node) then return node - makeSingletonEClass(node) - codes(node) = codes.size - node match - case node @ AppliedFunctional(_, args) => - args.foreach(child => - add(child) - termParentsT(find(child)).add(node) - ) - case _ => () - termSigs(canSig(node)) = node - node - - def add(node: Formula): Formula = - if formulaUF.parent.contains(node) then return node - makeSingletonEClass(node) - node match - case node @ AppliedPredicate(_, args) => - args.foreach(child => - add(child) - termParentsF(find(child)).add(node) - ) - node - case node @ AppliedConnector(_, args) => - args.foreach(child => - add(child) - formulaParents(find(child)).add(node) - ) - node - case _ => node + + + def add(node: Expr[?]): Expr[?] = + if codes.contains(node) then node + else + codes(node) = codes.size + if node.sort == K.Term || node.sort == K.Formula then + makeSingletonEClass(node) + node match + case Multiapp(f, args) => + args.foreach(child => + add(child) + parents(find(child)).add(node) + ) + mapSigs(canSig(node)) = node + node def addAll(nodes: Iterable[Term|Formula]): Unit = nodes.foreach{ - case node: Term => add(node) - case node: Formula => add(node) + case node: Term if node.sort == K.Term => add(node) + case node: Formula if node.sort == K.Formula => add(node) } - - def merge(id1: Term, id2: Term): Unit = { - mergeWithStep(id1, id2, TermExternal((id1, id2))) - } - def merge(id1: Formula, id2: Formula): Unit = { - mergeWithStep(id1, id2, FormulaExternal((id1, id2))) + def merge[S](id1: Expr[S], id2: Expr[S]): Unit = { + mergeWithStep(id1, id2, ExternalStep((id1, id2))) } - type Sig = (TermLabel[?]|Term, List[Int]) - val termSigs = mutable.Map[Sig, Term]() - val codes = mutable.Map[Term, Int]() + def mergeUnsafe(id1: Expr[?], id2: Expr[?]): Unit = { + mergeWithStep(id1, id2, ExternalStep((id1, id2))) + } + + type Sig = (Expr[?], List[Int]) + val mapSigs = mutable.Map[Sig, Expr[?]]() + val codes = mutable.Map[Expr[?], Int]() - def canSig(node: Term): Sig = node match - case AppliedFunctional(label, args) => + def canSig(node: Expr[?]): Sig = node match + case Multiapp(label, args) => (label, args.map(a => codes(find(a))).toList) case _ => (node, List()) - protected def mergeWithStep(id1: Term, id2: Term, step: TermStep): Unit = { + protected def mergeWithStep(id1: Expr[?], id2: Expr[?], step: Step): Unit = { + if id1.sort != id2.sort then throw new IllegalArgumentException("Cannot merge nodes of different sorts") if find(id1) == find(id2) then () else - termProofMap((id1, id2)) = step - val parentsT1 = termParentsT(find(id1)) - val parentsF1 = termParentsF(find(id1)) - - val parentsT2 = termParentsT(find(id2)) - val parentsF2 = termParentsF(find(id2)) - val preSigs : Map[Term, Sig] = parentsT1.map(t => (t, canSig(t))).toMap - codes(find(id2)) = codes(find(id1)) //assume parents(find(id1)) >= parents(find(id2)) - termUF.union(id1, id2) - val newId = find(id1) - - val formulaSeen = mutable.Map[Formula, AppliedPredicate]() - var formWorklist = List[(Formula, Formula, FormulaStep)]() - var termWorklist = List[(Term, Term, TermStep)]() - - parentsT2.foreach { - case pTerm: AppliedFunctional => - val canonicalPTerm = canSig(pTerm) - if termSigs.contains(canonicalPTerm) then - val qTerm = termSigs(canonicalPTerm) - termWorklist = (pTerm, qTerm, TermCongruence((pTerm, qTerm))) :: termWorklist - else - termSigs(canonicalPTerm) = pTerm - } - (parentsF2 ++ parentsF1).foreach { - case pFormula: AppliedPredicate => - val canonicalPFormula = canonicalize(pFormula) - if formulaSeen.contains(canonicalPFormula) then - val qFormula = formulaSeen(canonicalPFormula) - formWorklist = (pFormula, qFormula, FormulaCongruence((pFormula, qFormula))) :: formWorklist - else - formulaSeen(canonicalPFormula) = pFormula - } - termParentsT(newId) = termParentsT(id1) - termParentsT(newId).addAll(termParentsT(id2)) - termParentsF(newId) = formulaSeen.values.to(mutable.Set) - formWorklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } - termWorklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } - } + proofMap((id1, id2)) = step + val parents1 = parents(find(id1)) + val parents2 = parents(find(id2)) - protected def mergeWithStep(id1: Formula, id2: Formula, step: FormulaStep): Unit = - if find(id1) == find(id2) then () - else - formulaProofMap((id1, id2)) = step - val newparents = formulaParents(find(id1)) ++ formulaParents(find(id2)) - formulaUF.union(id1, id2) - val newId = find(id1) - - val formulaSeen = mutable.Map[Formula, AppliedConnector]() - var formWorklist = List[(Formula, Formula, FormulaStep)]() - - newparents.foreach { - case pFormula: AppliedConnector => - val canonicalPFormula = canonicalize(pFormula) - if formulaSeen.contains(canonicalPFormula) then - val qFormula = formulaSeen(canonicalPFormula) - formWorklist = (pFormula, qFormula, FormulaCongruence((pFormula, qFormula))) :: formWorklist - //mergeWithStep(pFormula, qFormula, FormulaCongruence((pFormula, qFormula))) - else - formulaSeen(canonicalPFormula) = pFormula - } - formulaParents(newId) = formulaSeen.values.to(mutable.Set) - formWorklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } + if find(id1) == find(id2) then return () - def proveTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): proof.ProofTacticJudgement = + proofMap((id1, id2)) = step + val (small, big ) = if parents(find(id1)).size < parents(find(id2)).size then + (id1, id2) else (id2, id1) + codes(find(small)) = codes(find(big)) + UF.union(id1, id2) + val newId = find(id1) + var worklist = List[(Expr[?], Expr[?], Step)]() + + parents(small).foreach { pExpr => + val canonicalPExpr = canSig(pExpr) + if mapSigs.contains(canonicalPExpr) then + val qExpr = mapSigs(canonicalPExpr) + + worklist = (pExpr, qExpr, CongruenceStep((pExpr, qExpr))) :: worklist + else + mapSigs(canonicalPExpr) = pExpr + } + parents(newId) = parents(big) + parents(newId).addAll(parents(small)) + worklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } + } + + + def proveExpr[S](using lib: Library, proof: lib.Proof)(id1: Expr[S], id2:Expr[S], base: Sequent): proof.ProofTacticJudgement = TacticSubproof { proveInnerTerm(id1, id2, base) } - def proveInnerTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): Unit = { + + + def proveInnerTerm(using lib: Library, proof: lib.Proof)(id1: Expr[?], id2:Expr[?], base: Sequent): Unit = { import lib.* val steps = explain(id1, id2) steps match { case None => throw new Exception("No proof found in the egraph") case Some(steps) => - if steps.isEmpty then have(base.left |- (base.right + (id1 === id2))) by Restate + if steps.isEmpty then have(base.left |- (base.right + (makeEq(id1, id2)))) by Restate steps.foreach { - case TermExternal((l, r)) => - val goalSequent = base.left |- (base.right + (id1 === r)) + case ExternalStep((l, r)) => + val goalSequent = base.left |- (base.right + (makeEq(id1, r))) if l == id1 then have(goalSequent) by Restate else - val x = freshVariable(id1) - have(goalSequent) by RightSubstEq.withParametersSimple(List((l, r)), lambda(x, id1 === x))(lastStep) - case TermCongruence((l, r)) => + val x = variable[Term](freshId(Seq(id1))) + have(goalSequent) by RightSubstEq.withParameters(List((l, r)), (Seq(x), makeEq(id1, x)))(lastStep) + case CongruenceStep((l, r)) => val prev = if id1 != l then lastStep else null - val leqr = have(base.left |- (base.right + (l === r))) subproof { sp ?=> + val leqr = have(base.left |- (base.right + (makeEq(l, r)))) subproof { sp ?=> (l, r) match - case (AppliedFunctional(labell, argsl), AppliedFunctional(labelr, argsr)) if labell == labelr && argsl.size == argsr.size => - var freshn = freshId((l.freeVariables ++ r.freeVariables).map(_.id), "n").no + case (Multiapp(labell, argsl), Multiapp(labelr, argsr)) if labell == labelr && argsl.size == argsr.size => + var freshn = freshId((l.freeVars ++ r.freeVars).map(_.id), "n").no val ziped = (argsl zip argsr) - var zip = List[(Term, Term)]() - var children = List[Term]() - var vars = List[Variable]() + var zip = List[(Expr[?], Expr[?])]() + var children = List[Expr[?]]() + var vars = List[Variable[?]]() var steps = List[(Formula, sp.ProofStep)]() ziped.reverse.foreach { (al, ar) => if al == ar then children = al :: children else { - val x = Variable(Identifier("n", freshn)) + val x = variable(Identifier("n", freshn), al.sort) freshn = freshn + 1 children = x :: children vars = x :: vars - steps = (al === ar, have(proveTerm(al, ar, base))) :: steps + steps = (makeEq(al, ar), have(proveExpr(al, ar.asInstanceOf, base))) :: steps zip = (al, ar) :: zip } } - have(base.left |- (base.right + (l === l))) by Restate - val eqs = zip.map((l, r) => l === r) - val goal = have((base.left ++ eqs) |- (base.right + (l === r))).by.bot - have((base.left ++ eqs) |- (base.right + (l === r))) by RightSubstEq.withParametersSimple(zip, lambda(vars, l === labelr.applyUnsafe(children)))(lastStep) + have(base.left |- (base.right + makeEq(l, l))) by Restate + val eqs = zip.map((l, r) => makeEq(l, r)) + val goal = have((base.left ++ eqs) |- (base.right + makeEq(l, r))).by.bot + have((base.left ++ eqs) |- (base.right + makeEq(l, r))) by RightSubstEq.withParameters(zip, (vars, makeEq(l, Multiapp.unsafe(labelr, children))))(lastStep) steps.foreach { s => have( if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 @@ -513,19 +420,21 @@ class EGraphTerms() { case _ => println(s"l: $l") println(s"r: $r") - throw UnreachableException + throw Exception("Unreachable") } if id1 != l then - val goalSequent = base.left |- (base.right + (id1 === r)) - val x = freshVariable(id1) - have(goalSequent +<< (l === r)) by RightSubstEq.withParametersSimple(List((l, r)), lambda(x, id1 === x))(prev) + val goalSequent = base.left |- (base.right + (makeEq(id1, r))) + val x = variable(freshId(Seq(id1)), id1.sort) + have(goalSequent +<< makeEq(l, r)) by RightSubstEq.withParameters(List((l, r)), (Seq(x), makeEq(id1, x)))(prev) have(goalSequent) by Cut(leqr, lastStep) } } } - def proveFormula(using lib: Library, proof: lib.Proof)(id1: Formula, id2:Formula, base: Sequent): proof.ProofTacticJudgement = + /* + + def proveExpr(using lib: Library, proof: lib.Proof)(id1: Formula, id2:Formula, base: Sequent): proof.ProofTacticJudgement = TacticSubproof { proveInnerFormula(id1, id2, base) } def proveInnerFormula(using lib: Library, proof: lib.Proof)(id1: Formula, id2:Formula, base: Sequent): Unit = { @@ -536,14 +445,14 @@ class EGraphTerms() { case Some(steps) => if steps.isEmpty then have(base.left |- (base.right + (id1 <=> id2))) by Restate steps.foreach { - case FormulaExternal((l, r)) => + case ExternalStep((l, r)) => val goalSequent = base.left |- (base.right + (id1 <=> r)) if l == id1 then have(goalSequent) by Restate else val x = freshVariableFormula(id1) - have(goalSequent) by RightSubstIff.withParametersSimple(List((l, r)), lambda(x, id1 <=> x))(lastStep) - case FormulaCongruence((l, r)) => + have(goalSequent) by RightSubstIff.withParameters(List((l, r)), lambda(x, id1 <=> x))(lastStep) + case CongruenceStep((l, r)) => val prev = if id1 != l then lastStep else null val leqr = have(base.left |- (base.right + (l <=> r))) subproof { sp ?=> (l, r) match @@ -561,14 +470,14 @@ class EGraphTerms() { freshn = freshn + 1 children = x :: children vars = x :: vars - steps = (al <=> ar, have(proveFormula(al, ar, base))) :: steps + steps = (al <=> ar, have(proveExpr(al, ar, base))) :: steps zip = (al, ar) :: zip } } have(base.left |- (base.right + (l <=> l))) by Restate val eqs = zip.map((l, r) => l <=> r) val goal = have((base.left ++ eqs) |- (base.right + (l <=> r))).by.bot - have((base.left ++ eqs) |- (base.right + (l <=> r))) by RightSubstIff.withParametersSimple(zip, lambda(vars, l <=> labelr.applyUnsafe(children)))(lastStep) + have((base.left ++ eqs) |- (base.right + (l <=> r))) by RightSubstIff.withParameters(zip, lambda(vars, l <=> labelr.applyUnsafe(children)))(lastStep) steps.foreach { s => have( if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 @@ -596,7 +505,7 @@ class EGraphTerms() { have(base.left |- (base.right + (l <=> l))) by Restate val eqs = zip.map((l, r) => l === r) val goal = have((base.left ++ eqs) |- (base.right + (l <=> r))).by.bot - have((base.left ++ eqs) |- (base.right + (l <=> r))) by RightSubstEq.withParametersSimple(zip, lambda(vars, l <=> labelr.applyUnsafe(children)))(lastStep) + have((base.left ++ eqs) |- (base.right + (l <=> r))) by RightSubstEq.withParameters(zip, lambda(vars, l <=> labelr.applyUnsafe(children)))(lastStep) steps.foreach { s => have( if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 @@ -611,12 +520,13 @@ class EGraphTerms() { if id1 != l then val goalSequent = base.left |- (base.right + (id1 <=> r)) val x = freshVariableFormula(id1) - have(goalSequent +<< (l <=> r)) by RightSubstIff.withParametersSimple(List((l, r)), lambda(x, id1 <=> x))(prev) + have(goalSequent +<< (l <=> r)) by RightSubstIff.withParameters(List((l, r)), lambda(x, id1 <=> x))(prev) have(goalSequent) by Cut(leqr, lastStep) } } } + */ } \ No newline at end of file diff --git a/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala new file mode 100644 index 000000000..ec6527481 --- /dev/null +++ b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala @@ -0,0 +1,172 @@ +package lisa.automation +import lisa.fol.FOL.{*, given} +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.* +import lisa.prooflib.SimpleDeducedSteps.* +import lisa.prooflib.* + + + + +/////////////////////////////// +///////// E-graph ///////////// +/////////////////////////////// + +import scala.collection.mutable + + + + + +class EGraphExprSimp() { + /* + + val termParents = mutable.Map[Term, mutable.Set[AppliedFunctional]]() + val termUF = new UnionFind[Term]() + val termProofMap = mutable.Map[(Term, Term), Boolean]() + + + + def find(id: Term): Term = termUF.find(id) + + def add(node: Term): Term = + termUF.add(node) + termParents(node) = mutable.Set() + node match + case node @ AppliedFunctional(_, args) => + + args.foreach(child => + add(child) + termParents(child).add(node) + ) + node + case _ => node + + + def merge(id1: Term, id2: Term): Unit = { + mergeWithStep(id1, id2, true) + } + + type Sig = (TermLabel[?]|Term, List[Int]) + val termSigs = mutable.Map[Sig, Term]() + val codes = mutable.Map[Term, Int]() + + protected def mergeWithStep(id1: Term, id2: Term, isExternal: Boolean): Unit = { + if find(id1) == find(id2) then return () + termProofMap((id1, id2)) = isExternal + val (small, big ) = if termParents(find(id1)).size < termParents(find(id2)).size then + (id1, id2) else (id2, id1) + codes(find(small)) = codes(find(big)) + termUF.union(id1, id2) + val newId = find(id1) + var worklist = List[(Term, Term, Boolean)]() + + termParents(small).foreach { pTerm => + val canonicalPTerm = canonicalize(pTerm) + if termSigs.contains(canonicalPTerm) then + val qTerm = termSigs(canonicalPTerm) + mergeWithStep(pTerm, qTerm, false) + else + termSigs(canonicalPTerm) = pTerm + } + termParents(newId) = termParents(big) + termParents(newId).addAll(termParents(small)) + } + + def canonicalize(node: Term): Sig = node match + case AppliedFunctional(label, args) => + (label, args.map(a => codes(find(a))).toList) + case _ => (node, List()) + + + + + // Explain + + + + + def explain(id1: Term, id2: Term): Option[List[(Term, Term, Boolean)]] = { + val steps = termUF.explain(id1, id2) + steps.map(_.map { a => (a._1, a._2, termProofMap(a)) + + }) + } + + + + + + + + + + + + // Proofs Lisa + + def proveTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): proof.ProofTacticJudgement = + TacticSubproof { proveInnerTerm(id1, id2, base) } + + def proveInnerTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): Unit = { + import lib.* + val steps = explain(id1, id2) + steps match { + case None => throw new Exception("No proof found in the egraph") + case Some(steps) => // External + have(base.left |- (base.right + (id1 === id2))) by Restate + var current = id1 + steps.foreach { + case (l, r, true) => + current = if current == l then r else l + val goalSequent = base.left |- (base.right + (id1 === r)) + val x = freshVariable(id1) + //thenHave(id1 === current) by Transitivity(l === r) + have(goalSequent) by RightSubstEq.withParametersSimple(List((l, r)), lambda(x, id1 === x))(lastStep) + case (l, r, false) => // Congruence + val prev = lastStep + val leqr = have(base.left |- (base.right + (l === r))) subproof { sp ?=> + (l, r) match + case (AppliedFunctional(labell, argsl), AppliedFunctional(labelr, argsr)) if labell == labelr && argsl.size == argsr.size => + var freshn = freshId((l.freeVariables ++ r.freeVariables).map(_.id), "n").no + val ziped = (argsl zip argsr) + var zip = List[(Term, Term)]() + var children = List[Term]() + var vars = List[Variable]() + var steps = List[(Formula, sp.ProofStep)]() + ziped.reverse.foreach { (al, ar) => + if al == ar then children = al :: children + else { + val x = Variable(Identifier("n", freshn)) + freshn = freshn + 1 + children = x :: children + vars = x :: vars + steps = (al === ar, have(proveTerm(al, ar, base))) :: steps + zip = (al, ar) :: zip + } + } + have(base.left |- (base.right + (l === l))) by Restate + val eqs = zip.map((l, r) => l === r) + val goal = have((base.left ++ eqs) |- (base.right + (l === r))).by.bot + have((base.left ++ eqs) |- (base.right + (l === r))) by RightSubstEq.withParametersSimple(zip, lambda(vars, l === labelr.applyUnsafe(children)))(lastStep) + steps.foreach { s => + have( + if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 + ) by Cut(s._2, lastStep) + } + case _ => + println(s"l: $l") + println(s"r: $r") + throw UnreachableException + + } + val goalSequent = base.left |- (base.right + (id1 === r)) + val x = freshVariable(id1) + have(goalSequent +<< (l === r)) by RightSubstEq.withParametersSimple(List((l, r)), lambda(x, id1 === x))(prev) + have(goalSequent) by Cut(leqr, lastStep) + } + } + } +*/ + +} \ No newline at end of file diff --git a/lisa-sets/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets/src/main/scala/lisa/automation/Substitution.scala index 87eab43cd..bf39f308d 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Substitution.scala @@ -1,641 +1,467 @@ package lisa.automation + import lisa.fol.FOL as F import lisa.kernel.proof.RunningTheory import lisa.kernel.proof.SCProof import lisa.kernel.proof.SequentCalculus -import lisa.prooflib.BasicStepTactic.* -import lisa.prooflib.ProofTacticLib.{_, given} +import lisa.prooflib.BasicStepTactic +import lisa.prooflib.SimpleDeducedSteps +import lisa.prooflib.ProofTacticLib.{*, given} import lisa.prooflib.* -import lisa.utils.FOLPrinter import lisa.utils.K import lisa.utils.UserLisaException -import lisa.utils.parsing.FOLPrinter -import lisa.utils.unification.UnificationUtils -import lisa.utils.unification.UnificationUtils.getContextFormulaSet +import lisa.utils.unification.UnificationUtils.* +import lisa.utils.collection.Extensions.* import scala.annotation.nowarn import scala.collection.mutable.{Map as MMap} import F.{*, given} -import F.|- - -object Substitution { - def validRule(using lib: lisa.prooflib.Library, proof: lib.Proof)(r: (proof.Fact | F.Formula | lib.JUSTIFICATION)): Boolean = - r match { - case F.equality(_, _) => true - case F.Iff(_, _) => true - case _: Formula => false - case j: lib.JUSTIFICATION => j.statement.right.size == 1 && validRule(j.statement.right.head) - case f: proof.Fact @unchecked => proof.sequentOfFact(f).right.size == 1 && validRule(proof.sequentOfFact(f).right.head) - // case j: RunningTheory#Justification => - // proof.sequentOfFact(j.asInstanceOf[lib.theory.Justification]).right.size == 1 && validRule(proof.sequentOfFact(j.asInstanceOf[lib.theory.Justification]).right.head) - } - - object ApplyRules extends ProofTactic { - - def apply(using lib: lisa.prooflib.Library, proof: lib.Proof)(substitutions: (proof.Fact | F.Formula | lib.JUSTIFICATION)*)( - premise: proof.Fact - )(bot: F.Sequent): proof.ProofTacticJudgement = { - // figure out instantiations for rules - // takes a premise - val premiseSequent: F.Sequent = proof.getSequent(premise) - - // make sure substitutions are all valid - val violatingSubstitutions = substitutions.collect { - case f : proof.Fact @unchecked if !validRule(f) => proof.sequentOfFact(f) - case j: lib.JUSTIFICATION if !validRule(j) => j.statement - } - - val violatingFormulas = substitutions.collect { - case f: F.Formula if !validRule(f) => f - } - - if (!violatingSubstitutions.isEmpty) - // return error - proof.InvalidProofTactic("Substitution rules must have a single equality or equivalence on the right-hand side. Violating sequents passed:\n" + violatingSubstitutions.zipWithIndex.map { - (s, i) => - s"${i + 1}. ${s.toString}" - }) - else if (!violatingFormulas.isEmpty) - proof.InvalidProofTactic("Substitution rules must be equalities or equivalences. Violating formulas passed:\n" + violatingFormulas.zipWithIndex.map { (s, i) => - s"${i + 1}. ${s.toString}" - }) - else { - // proceed as usual - - // maintain a list of where subtitutions come from - val sourceOf: MMap[(F.Formula, F.Formula) | (F.Term, F.Term), proof.Fact] = MMap() - val takenTermVars: Set[lisa.fol.FOL.Variable] = - premiseSequent.left.flatMap(_.freeVariables).toSet union substitutions.collect { case f: F.Formula => f.freeVariables.toSet }.foldLeft(Set.empty)(_.union(_)) - val takenFormulaVars: Set[lisa.fol.FOL.VariableFormula] = premiseSequent.left.flatMap(_.freeVariableFormulas).toSet union substitutions - .collect { case f: F.Formula => f.freeVariableFormulas.toSet } - .foldLeft(Set.empty)(_.union(_)) // TODO: should this just be the LHS of the premise sequent instead? - - var freeEqualitiesPre = List[(F.Term, F.Term)]() - var confinedEqualitiesPre = List[(F.Term, F.Term)]() - var freeIffsPre = List[(F.Formula, F.Formula)]() - var confinedIffsPre = List[(F.Formula, F.Formula)]() - - def updateSource(t: (F.Formula, F.Formula) | (F.Term, F.Term), f: proof.Fact) = { - sourceOf.update(t, f) - sourceOf.update(t.swap.asInstanceOf[(F.Formula, F.Formula) | (F.Term, F.Term)], f) - } - - // collect substitutions into the right buckets - substitutions.foreach { - case f: F.Formula => - f match { - case F.AppliedPredicate(F.equality, Seq(l, r)) => - confinedEqualitiesPre = (l, r) :: confinedEqualitiesPre - case F.AppliedConnector(F.Iff, Seq(l, r)) => - confinedIffsPre = (l, r) :: confinedIffsPre - case _ => () - } - case j: lib.JUSTIFICATION => - j.statement.right.head match { - case F.AppliedPredicate(F.equality, Seq(l, r)) => - updateSource((l, r), j) - freeEqualitiesPre = (l, r) :: freeEqualitiesPre - case F.AppliedConnector(F.Iff, Seq(l, r)) => - updateSource((l, r), j) - freeIffsPre = (l, r) :: freeIffsPre - case _ => () - } - case f: proof.Fact @unchecked => - proof.sequentOfFact(f).right.head match { - case F.AppliedPredicate(F.equality, Seq(l, r)) => - updateSource((l, r), f) - confinedEqualitiesPre = (l, r) :: confinedEqualitiesPre - case F.AppliedConnector(F.Iff, Seq(l, r)) => - updateSource((l, r), f) - confinedIffsPre = (l, r) :: confinedIffsPre - case _ => () - } - } - - // get the original and swapped versions - val freeEqualities: List[(F.Term, F.Term)] = freeEqualitiesPre ++ freeEqualitiesPre.map(_.swap) - val confinedEqualities: List[(F.Term, F.Term)] = confinedEqualitiesPre ++ confinedEqualitiesPre.map(_.swap) - val freeIffs: List[(F.Formula, F.Formula)] = freeIffsPre ++ freeIffsPre.map(_.swap) - val confinedIffs: List[(F.Formula, F.Formula)] = confinedIffsPre ++ confinedIffsPre.map(_.swap) - - val filteredPrem: Seq[F.Formula] = (premiseSequent.left filter { - case F.AppliedPredicate(F.equality, Seq(l, r)) if freeEqualities.contains((l, r)) || confinedEqualities.contains((l, r)) => false - case F.AppliedConnector(F.Iff, Seq(l, r)) if freeIffs.contains((l, r)) || confinedIffs.contains((l, r)) => false - case _ => true - }).toSeq - - val filteredBot: Seq[F.Formula] = (bot.left filter { - case F.AppliedPredicate(F.equality, Seq(l, r)) if freeEqualities.contains((l, r)) || confinedEqualities.contains((l, r)) => false - case F.AppliedConnector(F.Iff, Seq(l, r)) if freeIffs.contains((l, r)) || confinedIffs.contains((l, r)) => false - case _ => true - }).toSeq - - // construct the right instantiations - lazy val leftContextsOpt: Option[Seq[UnificationUtils.FormulaRewriteLambda]] = getContextFormulaSet( - filteredPrem, - filteredBot, - freeEqualities, - freeIffs, - confinedEqualities, - takenTermVars, - confinedIffs, - takenFormulaVars - ) - - lazy val rightContextsOpt: Option[Seq[UnificationUtils.FormulaRewriteLambda]] = getContextFormulaSet( - premiseSequent.right.toSeq, - bot.right.toSeq, - freeEqualities, - freeIffs, - confinedEqualities, - takenTermVars, - confinedIffs, - takenFormulaVars - ) - - lazy val rightPairs = premiseSequent.right zip premiseSequent.right.map(x => - bot.right.find(y => - UnificationUtils - .getContextFormula( - x, - y, - freeEqualities, - freeIffs, - confinedEqualities, - takenTermVars, - confinedIffs, - takenFormulaVars - ) - .isDefined - ) - ) - - lazy val leftPairs = filteredPrem zip filteredPrem.map(x => - filteredBot.find(y => - UnificationUtils - .getContextFormula( - x, - y, - freeEqualities, - freeIffs, - confinedEqualities, - takenTermVars, - confinedIffs, - takenFormulaVars - ) - .isDefined - ) - ) - - lazy val violatingFormulaLeft = leftPairs.find(_._2.isEmpty) - lazy val violatingFormulaRight = rightPairs.find(_._2.isEmpty) - - if (leftContextsOpt.isEmpty) - proof.InvalidProofTactic(s"Could not rewrite LHS of premise into conclusion with given substitutions.\nViolating Formula: ${violatingFormulaLeft.get}") - else if (rightContextsOpt.isEmpty) - proof.InvalidProofTactic(s"Could not rewrite RHS of premise into conclusion with given substitutions.\nViolating Formula: ${violatingFormulaRight.get}") - else { - // actually construct proof - TacticSubproof { - - def eq(rule: (Term, Term)) = AppliedPredicate(equality, Seq(rule._1, rule._2)) - def iff(rule: (Formula, Formula)) = AppliedConnector(Iff, Seq(rule._1, rule._2)) - - def eqSource(rule: (Term, Term)) = lib.have(eq(rule) |- eq(rule)) by SimpleDeducedSteps.Restate - def iffSource(rule: (Formula, Formula)) = lib.have(iff(rule) |- iff(rule)) by SimpleDeducedSteps.Restate - val leftContexts: Seq[UnificationUtils.FormulaRewriteLambda] = leftContextsOpt.get // remove the options - val rightContexts: Seq[UnificationUtils.FormulaRewriteLambda] = rightContextsOpt.get // remove the options - - val leftBody = AppliedConnector(And, leftContexts.map(f => f.body)) - - val defaultLeft = UnificationUtils.FormulaRewriteLambda(body = leftBody) - - val leftContextReduced = leftContexts.foldLeft(defaultLeft) { (f, s) => - UnificationUtils.FormulaRewriteLambda( - termRules = f.termRules ++ s.termRules, - formulaRules = f.formulaRules ++ s.formulaRules, - leftBody - ) - } - - val rightBody = AppliedConnector(Or, rightContexts.map(f => f.body)) - - val defaultRight = UnificationUtils.FormulaRewriteLambda(body = rightBody) - - val rightContextReduced = rightContexts.foldLeft(defaultRight) { (f, s) => - UnificationUtils.FormulaRewriteLambda( - termRules = f.termRules ++ s.termRules, - formulaRules = f.formulaRules ++ s.formulaRules, - rightBody - ) - } - - // find the justifications for each rule, or generate them, as required - val leftDischarges = - leftContextReduced.termRules.map { case (_, (rule, subst)) => - sourceOf.get(rule) match { - case Some(f: proof.Fact) => - f.of(subst.toSeq.map((l, r) => (l := r))*) - // case Some(j: lib.theory.Justification) => - // j.of(subst.toSeq.map((l, r) => (l, lambda(Seq(), r))): _*) - case _ => - eqSource(rule).of() - } - } ++ - leftContextReduced.formulaRules.map { case (_, (rule, subst)) => - sourceOf.get(rule) match { - case Some(f: proof.Fact) => - f.of(subst._1.toSeq.map((l, r) => (l := r)) ++ subst._2.toSeq.map((l, r) => (l := r))*) - // case Some(j: lib.theory.Justification) => - // j.of(subst._1.toSeq.map((l, r) => (l, lambda(Seq[Variable](), r))) ++ subst._2.toSeq.map((l, r) => (l, lambda(Seq[Variable](), r))): _*) - case _ => - iffSource(rule).of() - } - } - val rightDischarges = - rightContextReduced.termRules.map { case (_, (rule, subst)) => - sourceOf.get(rule) match { - case Some(f: proof.Fact) => - f.of(subst.toSeq.map((l, r) => (l := r))*) - // case Some(j: lib.theory.Justification) => - // j.of(subst.toSeq.map((l, r) => (l, lambda(Seq(), r))): _*) - case None => - eqSource(rule).of() - } - } ++ - rightContextReduced.formulaRules.map { case (_, (rule, subst)) => - sourceOf.get(rule) match { - case Some(f: proof.Fact) => - f.of(subst._1.toSeq.map((l, r) => (l := r)) ++ subst._2.toSeq.map((l, r) => (l := r))*) - // case Some(j: lib.theory.Justification) => - // j.of(subst._1.toSeq.map((l, r) => (l, lambda(Seq[Variable](), r))) ++ subst._2.toSeq.map((l, r) => (l, lambda(Seq[Variable](), r))): _*) - case None => - iffSource(rule).of() - } - } - - val discharges = leftDischarges ++ rightDischarges - // ------------------- - // LEFT SUBSTITUTIONS - // ------------------- - val nextSequent = { - // we have a lambda like λx. Λp. body - // where the p are formula variables, and the x are term variables - val ctx = leftContextReduced - - val termVars = ctx.termRules.map(_._1) - - val termInputs = ctx.termRules.map { case (_, (rule: (Term, Term), subst: UnificationUtils.TermSubstitution)) => - ( - rule._1.substituteUnsafe2(subst), - rule._2.substituteUnsafe2(subst) - ) - } - - lazy val (termInputsL, termInputsR) = (termInputs.map(_._1), termInputs.map(_._2)) - - val formulaVars = ctx.formulaRules.map(_._1) - - val formulaInputs = ctx.formulaRules.map { case (_, (rule, subst)) => - ( - rule._1.substituteUnsafe2(subst._2).substituteUnsafe2(subst._1), - rule._2.substituteUnsafe2(subst._2).substituteUnsafe2(subst._1) - ) - } - val (formulaInputsL, formulaInputsR) = (formulaInputs.map(_._1), formulaInputs.map(_._2)) - - // get premise into the right form - val prem = AppliedConnector(And, filteredPrem.toSeq) |- AppliedConnector(Or, premiseSequent.right.toSeq) - val eqs = termInputs.map(eq(_)) - val iffs = formulaInputs.map(iff(_)) - val premiseWithSubst = prem ++<< (eqs |- ()) ++<< (iffs |- ()) - lib.have(premiseWithSubst) by BasicStepTactic.Weakening(premise) - - // left === - val eqSubst = Map((termVars zip termInputsR)*) - val formSubstL = Map((formulaVars zip formulaInputsL)*) - val lhsAfterEq = ctx.body.substituteUnsafe2(eqSubst).substituteUnsafe2(formSubstL) - val sequentAfterEqPre = lhsAfterEq |- premiseWithSubst.right - val sequentAfterEq = sequentAfterEqPre ++<< (eqs |- ()) ++<< (iffs |- ()) - - // this uses the "lambda" (λx. Λp. body) (p = left formulas) - lib.thenHave(sequentAfterEq) by BasicStepTactic.LeftSubstEq.withParametersSimple(termInputs.toList, lambda(termVars, ctx.body.substituteUnsafe2(formSubstL))) - - // left <=> - val formSubstR = Map((formulaVars zip formulaInputsR)*) - val lhsAfterIff = ctx.body.substituteUnsafe2(eqSubst).substituteUnsafe2(formSubstR) - val sequentAfterIffPre = lhsAfterIff |- sequentAfterEq.right - val sequentAfterIff = sequentAfterIffPre ++<< (eqs |- ()) ++<< (iffs |- ()) - - // this uses the "lambda" (λx. Λp. body) (x = right terms) - lib.thenHave(sequentAfterIff) by BasicStepTactic.LeftSubstIff.withParametersSimple(formulaInputs.toList, lambda(formulaVars, ctx.body.substituteUnsafe2(eqSubst))) - sequentAfterIff - } - - // ------------------- - // RIGHT SUBSTITUTIONS - // ------------------- - val finalSequent = { - // we have a lambda like λx. Λp. body - // where the p are formula variables, and the x are term variables - val ctx = rightContextReduced - - val termVars = ctx.termRules.map(_._1) - - val termInputs = ctx.termRules.map { case (_, (rule, subst)) => - ( - rule._1.substituteUnsafe2(subst), - rule._2.substituteUnsafe2(subst) - ) - } - - lazy val (termInputsL, termInputsR) = (termInputs.map(_._1), termInputs.map(_._2)) - - val formulaVars = ctx.formulaRules.map(_._1) - - val formulaInputs = ctx.formulaRules.map { case (_, (rule, subst)) => - ( - rule._1.substituteUnsafe2(subst._2).substituteUnsafe2(subst._1), - rule._2.substituteUnsafe2(subst._2).substituteUnsafe2(subst._1) - ) - } - val (formulaInputsL, formulaInputsR) = (formulaInputs.map(_._1), formulaInputs.map(_._2)) - - // get premise into the right form - val prem = nextSequent - val eqs = termInputs.map(eq(_)) - val iffs = formulaInputs.map(iff(_)) - val premiseWithSubst = prem ++<< (eqs |- ()) ++<< (iffs |- ()) - lib.thenHave(premiseWithSubst) by BasicStepTactic.Weakening - - // right === - val eqSubst = Map((termVars zip termInputsR)*) - val formSubstL = Map((formulaVars zip formulaInputsL)*) - val rhsAfterEq = ctx.body.substituteUnsafe2(eqSubst).substituteUnsafe2(formSubstL) - val sequentAfterEqPre = premiseWithSubst.left |- rhsAfterEq - val sequentAfterEq = sequentAfterEqPre ++<< (eqs |- ()) ++<< (iffs |- ()) - - // this uses the "lambda" (λx. Λp. body) (p = left formulas) - lib.thenHave(sequentAfterEq) by BasicStepTactic.RightSubstEq.withParametersSimple(termInputs.toList, lambda(termVars, ctx.body.substituteUnsafe2(formSubstL))) - - // right <=> - val formSubstR = Map((formulaVars zip formulaInputsR)*) - val rhsAfterIff = ctx.body.substituteUnsafe2(eqSubst).substituteUnsafe2(formSubstR) - val sequentAfterIffPre = sequentAfterEq.left |- rhsAfterIff - val sequentAfterIff = sequentAfterIffPre ++<< (eqs |- ()) ++<< (iffs |- ()) - - // this uses the "lambda" (λx. Λp. body) (x = right terms) - lib.thenHave(sequentAfterIff) by BasicStepTactic.RightSubstIff.withParametersSimple(formulaInputs.toList, lambda(formulaVars, ctx.body.substituteUnsafe2(eqSubst))) - - } - // discharge any assumptions - - // custom discharge - // invariant: all facts are known to have only one formula in their RHS - discharges.foreach { f => - lib.thenHave(lib.lastStep.bot +<< f.result.right.head) by BasicStepTactic.Weakening // in case of double discharges, add the formula back in - lib.have(lib.lastStep.bot - f.result.right.head ++ (f.result.left |- ())) by BasicStepTactic.Cut.withParameters(f.result.right.head)(f, lib.lastStep) - } - - // finally, make sure our substitutions and discharges led us to the required conclusion - lib.thenHave(bot) by BasicStepTactic.Weakening - } - } - } - } - } - object applySubst extends ProofTactic { - - private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) - - private def findSubterm2(t: Term, subs: Seq[(Variable, Term)]): (Term, Boolean) = { - val eq = subs.find(s => isSameTerm(t, s._2)) - if (eq.nonEmpty) (eq.get._1, true) - else { - val induct = condflat(t.args.map(te => findSubterm2(te, subs))) - if (!induct._2) (t, false) + +object Substitution: + + /** + * Extracts a raw substitution into a `RewriteRule`. + */ + def extractRule + (using lib: Library, proof: lib.Proof) + (rule: proof.Fact | F.Formula): RewriteRule = + rule match + case f: Formula => f match + case l === r => TermRewriteRule(l, r) + case l <=> r => FormulaRewriteRule(l, r) + case f: proof.Fact => extractRule(proof.getSequent(f).right.head) + + /** + * Partitions raw substitution rules into free and confined rules, also + * creating a source map, mapping each rule to the `Fact` it was derived from, + * for proof construction. + */ + def partition + (using lib: Library, proof: lib.Proof) + (substitutions: Seq[proof.Fact | F.Formula]): (Map[RewriteRule, proof.Fact], RewriteContext) = + substitutions.foldLeft((Map.empty, RewriteContext.empty)): + case ((source, ctx), rule) => + val erule = extractRule(rule) + val (l, r) = (erule.l, erule.r) + rule match + case f: Formula => + (source + (erule -> erule.source), ctx.withConfinedRule(l, r).withBound(f.freeVars)) + case j: lib.JUSTIFICATION => + (source + (erule -> j), ctx.withFreeRule(l, r)) + case f: proof.Fact => + (source + (erule -> f), ctx.withConfinedRule(l, r)) + + /** + * Checks if a raw substitution input can be used as a rewrite rule (is === or + * <=>, basically). + */ + def validSubstitutionRule + (using lib: lisa.prooflib.Library, proof: lib.Proof) + (rule: (proof.Fact | F.Formula)): Boolean = + rule match + // as formula + case f: Formula => f match + case _ === _ => true + case _ <=> _ => true + case _ => false + // as a justification + case just: proof.Fact => + val sequent = proof.getSequent(just) + sequent.right.size == 1 && validSubstitutionRule(sequent.right.head) + + object Apply extends ProofTactic: + def apply + (using lib: Library, proof: lib.Proof) + (substitutions: (proof.Fact | F.Formula)*) + (premiseStep: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + + // are all substitution rules actually valid? + // if not, exit early + + val violatingFacts = substitutions.collect: + case f: proof.Fact if !validSubstitutionRule(f) => proof.getSequent(f) + + val violatingFormulas = substitutions.collect: + case f: F.Formula if !validSubstitutionRule(f) => f + + if violatingFacts.nonEmpty then + val msgBase = "Substitution rules must have a single equality or equivalence on the right-hand side. Violating sequents passed:\n" + val msgList = violatingFacts.zipWithIndex.map: + case (f, i) => s"\t${i + 1}. $f" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) + else if violatingFormulas.nonEmpty then + val msgBase = "Substitution rules must be equalities or equivalences. Violating formulas passed:\n" + val msgList = violatingFacts.zipWithIndex.map: + case (f, i) => s"\t${i + 1}. $f" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) else - (t.label.applySeq(induct._1), true) - - } - - } - private def findSubterm2(f: Formula, subs: Seq[(Variable, Term)]): (Formula, Boolean) = { - f match { - case f: VariableFormula => (f, false) - case f: ConstantFormula => (f, false) - case AppliedPredicate(label, args) => - val induct = condflat(args.map(findSubterm2(_, subs))) - if (!induct._2) (f, false) - else (AppliedPredicate(label, induct._1), true) - case AppliedConnector(label, args) => - val induct = condflat(args.map(findSubterm2(_, subs))) - if (!induct._2) (f, false) - else (AppliedConnector(label, induct._1), true) - case BinderFormula(label, bound, inner) => - val fv_in_f = subs.flatMap(e => e._2.freeVariables + e._1) - if (!fv_in_f.contains(bound)) { - val induct = findSubterm2(inner, subs) - if (!induct._2) (f, false) - else (BinderFormula(label, bound, induct._1), true) - } else { - val newv = Variable(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) - val newInner = inner.substitute(bound := newv) - val induct = findSubterm2(newInner, subs) - if (!induct._2) (f, false) - else (BinderFormula(label, newv, induct._1), true) - } - } - } - - private def findSubformula2(f: Formula, subs: Seq[(VariableFormula, Formula)]): (Formula, Boolean) = { - val eq = subs.find(s => isSame(f, s._2)) - if (eq.nonEmpty) (eq.get._1, true) - else - f match { - case f: AtomicFormula => (f, false) - case AppliedConnector(label, args) => - val induct = condflat(args.map(findSubformula2(_, subs))) - if (!induct._2) (f, false) - else (AppliedConnector(label, induct._1), true) - case BinderFormula(label, bound, inner) => - val fv_in_f = subs.flatMap(_._2.freeVariables) - if (!fv_in_f.contains(bound)) { - val induct = findSubformula2(inner, subs) - if (!induct._2) (f, false) - else (BinderFormula(label, bound, induct._1), true) - } else { - val newv = Variable(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) - val newInner = inner.substitute(bound := newv) - val induct = findSubformula2(newInner, subs) - if (!induct._2) (f, false) - else (BinderFormula(label, newv, induct._1), true) - } - } - } - - def findSubterm(t: Term, subs: Seq[(Variable, Term)]): Option[LambdaExpression[Term, Term, ?]] = { - val vars = subs.map(_._1) - val r = findSubterm2(t, subs) - if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) - else None - } - - def findSubterm(f: Formula, subs: Seq[(Variable, Term)]): Option[LambdaExpression[Term, Formula, ?]] = { - val vars = subs.map(_._1) - val r = findSubterm2(f, subs) - if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) - else None - } - - def findSubformula(f: Formula, subs: Seq[(VariableFormula, Formula)]): Option[LambdaExpression[Formula, Formula, ?]] = { - val vars = subs.map(_._1) - val r = findSubformula2(f, subs) - if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) - else None - } - - def applyLeftRight(using lib: lisa.prooflib.Library, proof: lib.Proof)( - phi: Formula - )(premise: proof.Fact)(rightLeft: Boolean = false, toLeft: Boolean = true, toRight: Boolean = true): proof.ProofTacticJudgement = { - import lisa.utils.K - val originSequent = proof.getSequent(premise) - val leftOrigin = AppliedConnector(And, originSequent.left.toSeq) - val rightOrigin = AppliedConnector(Or, originSequent.right.toSeq) - - if (!toLeft && !toRight) return proof.InvalidProofTactic("applyLeftRight called with no substitution selected (toLeft or toRight).") - - phi match { - case AppliedPredicate(label, args) if label == equality => - val left = args(0) - val right = args(1) - val fv_in_phi = (originSequent.left ++ originSequent.right).flatMap(_.allSchematicLabels).map(_.id) - val v = Variable(nFreshId(fv_in_phi, 1).head) - lazy val isolatedLeft = originSequent.left.filterNot(f => isSame(f, phi)).map(f => (f, findSubterm(f, IndexedSeq(v -> left)))) - lazy val isolatedRight = originSequent.right.map(f => (f, findSubterm(f, IndexedSeq(v -> left)))) - if ((!toLeft || isolatedLeft.forall(_._2.isEmpty)) && (!toRight || isolatedRight.forall(_._2.isEmpty))) - if (rightLeft) - return proof.InvalidProofTactic(s"There is no instance of ${right} to replace.") - else - applyLeftRight(equality(right, left))(premise)(true, toLeft, toRight) match { - case proof.InvalidProofTactic(m) => return proof.InvalidProofTactic(s"There is no instance of ${left} to replace.") - case v: proof.ValidProofTactic => return v - } - - val leftForm = AppliedConnector(And, isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) - val rightForm = AppliedConnector(Or, isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) - val newleft = if (toLeft) isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.left - val newright = if (toRight) isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.right - val result1: Sequent = (AppliedConnector(And, newleft.toSeq), phi) |- rightOrigin - val result2: Sequent = result1.left |- AppliedConnector(Or, newright.toSeq) - var scproof: Seq[K.SCProofStep] = Seq(K.Restate((leftOrigin |- rightOrigin).underlying, -1)) - if (toLeft) - scproof = scproof :+ K.LeftSubstEq( - result1.underlying, - scproof.length - 1, - List(K.LambdaTermTerm(Seq(), left.underlying) -> (K.LambdaTermTerm(Seq(), right.underlying))), - (Seq(v.underlyingLabel), leftForm.underlying) - ) - if (toRight) - scproof = scproof :+ K.RightSubstEq( - result2.underlying, - scproof.length - 1, - List(K.LambdaTermTerm(Seq(), left.underlying) -> (K.LambdaTermTerm(Seq(), right.underlying))), - (Seq(v.underlyingLabel), rightForm.underlying) - ) - val bot = newleft + phi |- newright - scproof = scproof :+ K.Restate(bot.underlying, scproof.length - 1) - - proof.ValidProofTactic( - bot, - scproof, - Seq(premise) - ) - - case AppliedConnector(label, args) if label == Iff => - val left = args(0) - val right = args(1) - val fv_in_phi = (originSequent.left ++ originSequent.right).flatMap(_.allSchematicLabels).map(_.id) - val H = VariableFormula(nFreshId(fv_in_phi, 1).head) - lazy val isolatedLeft = originSequent.left.filterNot(f => isSame(f, phi)).map(f => (f, findSubformula(f, IndexedSeq(H -> left)))) - lazy val isolatedRight = originSequent.right.map(f => (f, findSubformula(f, IndexedSeq(H -> left)))) - if ((!toLeft || isolatedLeft.forall(_._2.isEmpty)) && (!toRight || isolatedRight.forall(_._2.isEmpty))) - if (rightLeft) - return proof.InvalidProofTactic(s"There is no instance of ${right} to replace.") - else - applyLeftRight(Iff(right, left))(premise)(true, toLeft, toRight) match { - case proof.InvalidProofTactic(m) => return proof.InvalidProofTactic(s"There is no instance of ${left} to replace.") - case v: proof.ValidProofTactic => return v - } - - val leftForm = AppliedConnector(And, isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) - val rightForm = AppliedConnector(Or, isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) - val newleft = if (toLeft) isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.left - val newright = if (toRight) isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.right - val result1: Sequent = (AppliedConnector(And, newleft.toSeq), phi) |- rightOrigin - val result2: Sequent = result1.left |- AppliedConnector(Or, newright.toSeq) - - var scproof: Seq[K.SCProofStep] = Seq(K.Restate((leftOrigin |- rightOrigin).underlying, -1)) - if (toLeft) - scproof = scproof :+ K.LeftSubstIff( - result1.underlying, - scproof.length - 1, - List(K.LambdaTermFormula(Seq(), left.underlying) -> (K.LambdaTermFormula(Seq(), right.underlying))), - (Seq(H.underlyingLabel), leftForm.underlying) - ) - if (toRight) - scproof = scproof :+ K.RightSubstIff( - result2.underlying, - scproof.length - 1, - List(K.LambdaTermFormula(Seq(), left.underlying) -> (K.LambdaTermFormula(Seq(), right.underlying))), - (Seq(H.underlyingLabel), rightForm.underlying) - ) - - val bot = newleft + phi |- newright - scproof = scproof :+ K.Restate(bot.underlying, scproof.length - 1) - - proof.ValidProofTactic( - bot, - scproof, - Seq(premise) - ) - case _ => proof.InvalidProofTactic(s"Formula in applySingleSimp need to be of the form a=b or q<=>p and not ${phi}") - } - } - - @nowarn("msg=.*the type test for proof.Fact cannot be checked at runtime*") - def apply(using - lib: lisa.prooflib.Library, - proof: lib.Proof, - line: sourcecode.Line, - file: sourcecode.File - )(f: proof.Fact | Formula, rightLeft: Boolean = false, toLeft: Boolean = true, toRight: Boolean = true)( - premise: proof.Fact - ): proof.ProofTacticJudgement = { - f match { - case phi: Formula => applyLeftRight(phi)(premise)(rightLeft, toLeft, toRight) - case f: proof.Fact => - val seq = proof.getSequent(f) - val phi = seq.right.head - val sp = TacticSubproof { - val x = applyLeftRight(phi)(premise)(rightLeft, toLeft, toRight) - proof.library.have(x) - proof.library.andThen(SimpleDeducedSteps.Discharge(f)) - } - - BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") - } - - } - - def toLeft(using lib: lisa.prooflib.Library, proof: lib.Proof, line: sourcecode.Line, file: sourcecode.File)(f: proof.Fact | Formula, rightLeft: Boolean = false)( - premise: proof.Fact - ): proof.ProofTacticJudgement = apply(f, rightLeft, toLeft = true, toRight = false)(premise) - - def toRight(using lib: lisa.prooflib.Library, proof: lib.Proof, line: sourcecode.Line, file: sourcecode.File)(f: proof.Fact | Formula, rightLeft: Boolean = false)( - premise: proof.Fact - ): proof.ProofTacticJudgement = apply(f, rightLeft, toLeft = false, toRight = true)(premise) - - } -} + // continue, we have a list of rules to work with + + // rewrite base + val premise = proof.getSequent(premiseStep) + // the target is bot + + // metadata: + // maintain a list of where substitutions come from + // and categorize them for the rewrite context + val (sourceMap, prectx) = partition(substitutions) + val ctx = prectx.withBound(premise.left.flatMap(_.freeVars)) + + // TODO: CHECK is this really necessary? + // remove from the premise equalities we are rewriting with, as these + // terms themselves are not targets for the rewriting + // val filteredPrem = ??? + + // check whether this rewrite is even possible. + // if it is, get the context (term with holes) corresponding to the + // single-step simultaneous rewrite + + // for each formula in the premise left (resp. right), there must be a + // corresponding formula in the conclusion left (resp. right) that it + // can be rewritten into. + + // discover a (possibly non-injective non-surjective) mapping from one + // formula set to another where a formula maps to another by the + // rewrites above + inline def collectRewritingPairs + (base: Set[Formula], target: Set[Formula]): Option[Seq[RewriteResult]] = + base.iterator.map: formula => + target.collectFirstDefined: target => + rewrite(using ctx)(formula, target) + .toOptionSeq + + // collect the set of formulas in `base` that rewrite to *no* formula + // in `target`. Guaranteed to be non-empty if + // `collectRewritingPairs(base, target)` is None. + inline def collectViolatingPairs + (base: Set[Formula], target: Set[Formula]): Set[Formula] = + premise.left.filter: formula => + bot.left.forall: target => + rewrite(using ctx)(formula, target).isEmpty + + + val leftSubsts = collectRewritingPairs(premise.left, bot.left) + val rightSubsts = collectRewritingPairs(premise.right, bot.right) + + if leftSubsts.isEmpty then + // error, find formulas that failed to rewrite + val msgBase = "Could not rewrite LHS of premise into conclusion with given substitutions.\nViolating Formulas:" + val msgList = + collectViolatingPairs(premise.left, bot.left) + .zipWithIndex + .map: + case (formula, i) => s"\t${i + 1}. $formula" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) + else if rightSubsts.isEmpty then + // error, find formulas that failed to rewrite + val msgBase = "Could not rewrite LHS of premise into conclusion with given substitutions.\nViolating Formulas:" + val msgList = + collectViolatingPairs(premise.right, bot.right) + .zipWithIndex + .map: + case (formula, i) => s"\t${i + 1}. $formula" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) + else + // rewriting is possible, construct the proof + + import lib.{have, thenHave, lastStep} + import BasicStepTactic.{TacticSubproof, Weakening, Cut, LeftSubstEq, RightSubstEq} + import SimpleDeducedSteps.Restate + + TacticSubproof: + val leftRewrites = leftSubsts.get + val rightRewrites = rightSubsts.get + val leftRules = leftRewrites.head.rules + val rightRules = rightRewrites.head.rules + + // instantiated discharges + + val leftDischarges = leftRules.map(r => r -> sourceMap(r)) + val rightDischarges = rightRules.map(r => r -> sourceMap(r)) + + val discharges = leftDischarges ++ rightDischarges + + // start proof + have(andAll(premise.left) |- premise.right) by Restate.from(premiseStep) + + // left rewrites + val leftFormulas = leftRules.map(_.toFormula) + val preLeft = leftRewrites.map(_.toLeft) + val postLeft = leftRewrites.map(_.toRight) + val leftVars = leftRewrites.head.lambda._1 + val leftLambda = andAll(leftRewrites.map(_.lambda._2)) + thenHave(andAll(preLeft) |- premise.right) by Restate + thenHave(andAll(preLeft) +: leftFormulas |- premise.right) by Weakening + thenHave(andAll(postLeft) +: leftFormulas |- premise.right) by LeftSubstEq.withParameters(leftRules.map(r => r.l -> r.r), leftVars -> leftLambda) + + val rpremise = lastStep.bot + + // right rewrites + val rightFormulas = rightRules.map(_.toFormula) + val preRight = rightRewrites.map(_.toLeft).toSet + val postRight = rightRewrites.map(_.toRight).toSet + val rightVars = rightRewrites.head.lambda._1 + val rightLambda = orAll(rightRewrites.map(_.lambda._2)) + thenHave(rpremise.left |- orAll(preRight)) by Restate + thenHave(rpremise.left ++ rightFormulas |- orAll(preRight)) by Weakening + thenHave(rpremise.left ++ rightFormulas |- orAll(postRight)) by RightSubstEq.withParameters(rightRules.map(r => r.l -> r.r), rightVars -> rightLambda) + + // rewrite to destruct sequent + thenHave(postLeft ++ leftFormulas ++ rightFormulas |- postRight) by Restate + + val dpremise = lastStep.bot + + // discharge assumptions + discharges.foldLeft(dpremise): + case (premise, (rule, source)) => + val sseq = proof.getSequent(source) + val form = rule.toFormula + val nextSequent = premise.left - form ++ sseq.left |- premise.right ++ sseq.right - form + have(nextSequent) by Cut.withParameters(form)(source, lastStep) + nextSequent + + // restate to the result + thenHave(bot) by Weakening + + end Apply + + // object applySubst extends ProofTactic { + + // private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) + + // private def findSubterm2(t: Term, subs: Seq[(Variable, Term)]): (Term, Boolean) = { + // val eq = subs.find(s => isSameTerm(t, s._2)) + // if (eq.nonEmpty) (eq.get._1, true) + // else { + // val induct = condflat(t.args.map(te => findSubterm2(te, subs))) + // if (!induct._2) (t, false) + // else + // (t.label.applySeq(induct._1), true) + + // } + + // } + // private def findSubterm2(f: Formula, subs: Seq[(Variable, Term)]): (Formula, Boolean) = { + // f match { + // case f: VariableFormula => (f, false) + // case f: ConstantFormula => (f, false) + // case AppliedPredicate(label, args) => + // val induct = condflat(args.map(findSubterm2(_, subs))) + // if (!induct._2) (f, false) + // else (AppliedPredicate(label, induct._1), true) + // case AppliedConnector(label, args) => + // val induct = condflat(args.map(findSubterm2(_, subs))) + // if (!induct._2) (f, false) + // else (AppliedConnector(label, induct._1), true) + // case BinderFormula(label, bound, inner) => + // val fv_in_f = subs.flatMap(e => e._2.freeVariables + e._1) + // if (!fv_in_f.contains(bound)) { + // val induct = findSubterm2(inner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, bound, induct._1), true) + // } else { + // val newv = Variable(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) + // val newInner = inner.substitute(bound := newv) + // val induct = findSubterm2(newInner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, newv, induct._1), true) + // } + // } + // } + + // private def findSubformula2(f: Formula, subs: Seq[(VariableFormula, Formula)]): (Formula, Boolean) = { + // val eq = subs.find(s => isSame(f, s._2)) + // if (eq.nonEmpty) (eq.get._1, true) + // else + // f match { + // case f: AtomicFormula => (f, false) + // case AppliedConnector(label, args) => + // val induct = condflat(args.map(findSubformula2(_, subs))) + // if (!induct._2) (f, false) + // else (AppliedConnector(label, induct._1), true) + // case BinderFormula(label, bound, inner) => + // val fv_in_f = subs.flatMap(_._2.freeVariables) + // if (!fv_in_f.contains(bound)) { + // val induct = findSubformula2(inner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, bound, induct._1), true) + // } else { + // val newv = Variable(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) + // val newInner = inner.substitute(bound := newv) + // val induct = findSubformula2(newInner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, newv, induct._1), true) + // } + // } + // } + + // def findSubterm(t: Term, subs: Seq[(Variable, Term)]): Option[LambdaExpression[Term, Term, ?]] = { + // val vars = subs.map(_._1) + // val r = findSubterm2(t, subs) + // if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) + // else None + // } + + // def findSubterm(f: Formula, subs: Seq[(Variable, Term)]): Option[LambdaExpression[Term, Formula, ?]] = { + // val vars = subs.map(_._1) + // val r = findSubterm2(f, subs) + // if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) + // else None + // } + + // def findSubformula(f: Formula, subs: Seq[(VariableFormula, Formula)]): Option[LambdaExpression[Formula, Formula, ?]] = { + // val vars = subs.map(_._1) + // val r = findSubformula2(f, subs) + // if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) + // else None + // } + + // def applyLeftRight(using lib: lisa.prooflib.Library, proof: lib.Proof)( + // phi: Formula + // )(premise: proof.Fact)(rightLeft: Boolean = false, toLeft: Boolean = true, toRight: Boolean = true): proof.ProofTacticJudgement = { + // import lisa.utils.K + // val originSequent = proof.getSequent(premise) + // val leftOrigin = AppliedConnector(And, originSequent.left.toSeq) + // val rightOrigin = AppliedConnector(Or, originSequent.right.toSeq) + + // if (!toLeft && !toRight) return proof.InvalidProofTactic("applyLeftRight called with no substitution selected (toLeft or toRight).") + + // phi match { + // case AppliedPredicate(label, args) if label == equality => + // val left = args(0) + // val right = args(1) + // val fv_in_phi = (originSequent.left ++ originSequent.right).flatMap(_.allSchematicLabels).map(_.id) + // val v = Variable(nFreshId(fv_in_phi, 1).head) + // lazy val isolatedLeft = originSequent.left.filterNot(f => isSame(f, phi)).map(f => (f, findSubterm(f, IndexedSeq(v -> left)))) + // lazy val isolatedRight = originSequent.right.map(f => (f, findSubterm(f, IndexedSeq(v -> left)))) + // if ((!toLeft || isolatedLeft.forall(_._2.isEmpty)) && (!toRight || isolatedRight.forall(_._2.isEmpty))) + // if (rightLeft) + // return proof.InvalidProofTactic(s"There is no instance of ${right} to replace.") + // else + // applyLeftRight(equality(right, left))(premise)(true, toLeft, toRight) match { + // case proof.InvalidProofTactic(m) => return proof.InvalidProofTactic(s"There is no instance of ${left} to replace.") + // case v: proof.ValidProofTactic => return v + // } + + // val leftForm = AppliedConnector(And, isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val rightForm = AppliedConnector(Or, isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val newleft = if (toLeft) isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.left + // val newright = if (toRight) isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.right + // val result1: Sequent = (AppliedConnector(And, newleft.toSeq), phi) |- rightOrigin + // val result2: Sequent = result1.left |- AppliedConnector(Or, newright.toSeq) + // var scproof: Seq[K.SCProofStep] = Seq(K.Restate((leftOrigin |- rightOrigin).underlying, -1)) + // if (toLeft) + // scproof = scproof :+ K.LeftSubstEq( + // result1.underlying, + // scproof.length - 1, + // List(K.LambdaTermTerm(Seq(), left.underlying) -> (K.LambdaTermTerm(Seq(), right.underlying))), + // (Seq(v.underlyingLabel), leftForm.underlying) + // ) + // if (toRight) + // scproof = scproof :+ K.RightSubstEq( + // result2.underlying, + // scproof.length - 1, + // List(K.LambdaTermTerm(Seq(), left.underlying) -> (K.LambdaTermTerm(Seq(), right.underlying))), + // (Seq(v.underlyingLabel), rightForm.underlying) + // ) + // val bot = newleft + phi |- newright + // scproof = scproof :+ K.Restate(bot.underlying, scproof.length - 1) + + // proof.ValidProofTactic( + // bot, + // scproof, + // Seq(premise) + // ) + + // case AppliedConnector(label, args) if label == Iff => + // val left = args(0) + // val right = args(1) + // val fv_in_phi = (originSequent.left ++ originSequent.right).flatMap(_.allSchematicLabels).map(_.id) + // val H = VariableFormula(nFreshId(fv_in_phi, 1).head) + // lazy val isolatedLeft = originSequent.left.filterNot(f => isSame(f, phi)).map(f => (f, findSubformula(f, IndexedSeq(H -> left)))) + // lazy val isolatedRight = originSequent.right.map(f => (f, findSubformula(f, IndexedSeq(H -> left)))) + // if ((!toLeft || isolatedLeft.forall(_._2.isEmpty)) && (!toRight || isolatedRight.forall(_._2.isEmpty))) + // if (rightLeft) + // return proof.InvalidProofTactic(s"There is no instance of ${right} to replace.") + // else + // applyLeftRight(Iff(right, left))(premise)(true, toLeft, toRight) match { + // case proof.InvalidProofTactic(m) => return proof.InvalidProofTactic(s"There is no instance of ${left} to replace.") + // case v: proof.ValidProofTactic => return v + // } + + // val leftForm = AppliedConnector(And, isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val rightForm = AppliedConnector(Or, isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val newleft = if (toLeft) isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.left + // val newright = if (toRight) isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.right + // val result1: Sequent = (AppliedConnector(And, newleft.toSeq), phi) |- rightOrigin + // val result2: Sequent = result1.left |- AppliedConnector(Or, newright.toSeq) + + // var scproof: Seq[K.SCProofStep] = Seq(K.Restate((leftOrigin |- rightOrigin).underlying, -1)) + // if (toLeft) + // scproof = scproof :+ K.LeftSubstIff( + // result1.underlying, + // scproof.length - 1, + // List(K.LambdaTermFormula(Seq(), left.underlying) -> (K.LambdaTermFormula(Seq(), right.underlying))), + // (Seq(H.underlyingLabel), leftForm.underlying) + // ) + // if (toRight) + // scproof = scproof :+ K.RightSubstIff( + // result2.underlying, + // scproof.length - 1, + // List(K.LambdaTermFormula(Seq(), left.underlying) -> (K.LambdaTermFormula(Seq(), right.underlying))), + // (Seq(H.underlyingLabel), rightForm.underlying) + // ) + + // val bot = newleft + phi |- newright + // scproof = scproof :+ K.Restate(bot.underlying, scproof.length - 1) + + // proof.ValidProofTactic( + // bot, + // scproof, + // Seq(premise) + // ) + // case _ => proof.InvalidProofTactic(s"Formula in applySingleSimp need to be of the form a=b or q<=>p and not ${phi}") + // } + // } + + // @nowarn("msg=.*the type test for proof.Fact cannot be checked at runtime*") + // def apply(using + // lib: lisa.prooflib.Library, + // proof: lib.Proof, + // line: sourcecode.Line, + // file: sourcecode.File + // )(f: proof.Fact | Formula, rightLeft: Boolean = false, toLeft: Boolean = true, toRight: Boolean = true)( + // premise: proof.Fact + // ): proof.ProofTacticJudgement = { + // f match { + // case phi: Formula => applyLeftRight(phi)(premise)(rightLeft, toLeft, toRight) + // case f: proof.Fact => + // val seq = proof.getSequent(f) + // val phi = seq.right.head + // val sp = TacticSubproof { + // val x = applyLeftRight(phi)(premise)(rightLeft, toLeft, toRight) + // proof.library.have(x) + // proof.library.andThen(SimpleDeducedSteps.Discharge(f)) + // } + + // BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") + // } + + // } + + // def toLeft(using lib: lisa.prooflib.Library, proof: lib.Proof, line: sourcecode.Line, file: sourcecode.File)(f: proof.Fact | Formula, rightLeft: Boolean = false)( + // premise: proof.Fact + // ): proof.ProofTacticJudgement = apply(f, rightLeft, toLeft = true, toRight = false)(premise) + + // def toRight(using lib: lisa.prooflib.Library, proof: lib.Proof, line: sourcecode.Line, file: sourcecode.File)(f: proof.Fact | Formula, rightLeft: Boolean = false)( + // premise: proof.Fact + // ): proof.ProofTacticJudgement = apply(f, rightLeft, toLeft = false, toRight = true)(premise) + + // } + +end Substitution diff --git a/lisa-sets/src/main/scala/lisa/automation/Tableau.scala b/lisa-sets/src/main/scala/lisa/automation/Tableau.scala index de6f6bcbd..6302f59a1 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Tableau.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Tableau.scala @@ -5,10 +5,6 @@ import lisa.prooflib.OutputManager.* import lisa.prooflib.ProofTacticLib.* import lisa.utils.K import lisa.utils.K.{_, given} -import lisa.utils.parsing.FOLPrinter.prettyFormula -import lisa.utils.parsing.FOLPrinter.prettySCProof -import lisa.utils.parsing.FOLPrinter.prettySequent -import lisa.utils.parsing.FOLPrinter.prettyTerm import scala.collection.immutable.HashMap import scala.collection.immutable.HashSet @@ -47,7 +43,7 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent def from(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { val botK = bot.underlying - val premsFormulas: Seq[((proof.Fact, Formula), Int)] = premises.map(p => (p, sequentToFormula(proof.getSequent(p).underlying))).zipWithIndex + val premsFormulas: Seq[((proof.Fact, Expression), Int)] = premises.map(p => (p, sequentToFormula(proof.getSequent(p).underlying))).zipWithIndex val initProof = premsFormulas.map(s => Restate(() |- s._1._2, -(1 + s._2))).toList val sqToProve = botK ++<< (premsFormulas.map(s => s._1._2).toSet |- ()) @@ -69,13 +65,13 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent def solve(sequent: K.Sequent): Option[SCProof] = { - val f = K.ConnectorFormula(K.And, (sequent.left.toSeq ++ sequent.right.map(f => K.ConnectorFormula(K.Neg, List(f))))) - val taken = f.schematicTermLabels + val f = K.multiand(sequent.left.toSeq ++ sequent.right.map(f => K.neg)) + val taken = f.allVariables val nextIdNow = if taken.isEmpty then 0 else taken.maxBy(_.id.no).id.no + 1 - val (fnamed, nextId, _) = makeVariableNamesUnique(f, nextIdNow, f.freeVariables) + val (fnamed, nextId) = makeVariableNamesUnique(f, nextIdNow, f.freeVariables) val nf = reducedNNFForm(fnamed) - val uv = VariableLabel(Identifier("§", nextId)) + val uv = Variable(Identifier("§", nextId), Term) val proof = decide(Branch.empty(nextId + 1, uv).prepended(nf)) proof match case None => None @@ -101,53 +97,50 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent * maxIndex stores an index that is used to generate fresh variable names. */ case class Branch( - alpha: List[ConnectorFormula], // label = And - beta: List[ConnectorFormula], // label = Or - delta: List[BinderFormula], // Exists(...)) - gamma: List[BinderFormula], // Forall(...) - atoms: (List[AtomicFormula], List[AtomicFormula]), // split into positive and negatives! - unifiable: Map[VariableLabel, (BinderFormula, Int)], // map between metavariables and the original formula they came from, with the penalty associated to the complexity of the formula. - numberInstantiated: Map[VariableLabel, Int], // map between variables and the number of times they have been instantiated - - skolemized: Set[VariableLabel], // set of variables that have been skolemized - triedInstantiation: Map[VariableLabel, Set[Term]], // map between metavariables and the term they were already instantiated with + alpha: List[Expression], // label = And + beta: List[Expression], // label = Or + delta: List[Expression], // Exists(...)) + gamma: List[Expression], // Forall(...) + atoms: (List[Expression], List[Expression]), // split into positive and negatives! + unifiable: Map[Variable, (Expression, Int)], // map between metavariables and the original formula they came from, with the penalty associated to the complexity of the formula. + numberInstantiated: Map[Variable, Int], // map between variables and the number of times they have been instantiated + + skolemized: Set[Variable], // set of variables that have been skolemized + triedInstantiation: Map[Variable, Set[Expression]], // map between metavariables and the term they were already instantiated with maxIndex: Int, // the maximum index used for skolemization and metavariables - varsOrder: Map[VariableLabel, Int], // the order in which variables were instantiated. In particular, if the branch contained the formula ∀x. ∀y. ... then x > y. - unusedVar: VariableLabel // a variable the is neither free nor bound in the original formula. + varsOrder: Map[Variable, Int], // the order in which variables were instantiated. In particular, if the branch contained the formula ∀x. ∀y. ... then x > y. + unusedVar: Variable // a variable the is neither free nor bound in the original formula. ) { - def pop(f: Formula): Branch = f match - case f @ ConnectorFormula(Or, args) => + def pop(f: Expression): Branch = f match + case f @ Or(l, r) => if (beta.nonEmpty && beta.head.uniqueNumber == f.uniqueNumber) copy(beta = beta.tail) else throw Exception("First formula of beta is not f") - case f @ BinderFormula(Exists, x, inner) => + case f @ Exists(x, inner) => if (delta.nonEmpty && delta.head.uniqueNumber == f.uniqueNumber) copy(delta = delta.tail) else throw Exception("First formula of delta is not f") - case f @ BinderFormula(Forall, x, inner) => + case f @ Forall(x, inner) => if (gamma.nonEmpty && gamma.head.uniqueNumber == f.uniqueNumber) copy(gamma = gamma.tail) else throw Exception("First formula of gamma is not f") - case ConnectorFormula(And, args) => + case And(left, right) => if (alpha.nonEmpty && alpha.head.uniqueNumber == f.uniqueNumber) copy(alpha = alpha.tail) else throw Exception("First formula of alpha is not f") - case f @ AtomicFormula(id, args) => - throw Exception("Should not pop Atoms") - case f @ ConnectorFormula(Neg, List(AtomicFormula(id, args))) => - throw Exception("Should not pop Atoms") - case _ => ??? - - def prepended(f: Formula): Branch = f match - case f @ ConnectorFormula(And, args) => this.copy(alpha = f :: alpha) - case f @ ConnectorFormula(Or, args) => this.copy(beta = f :: beta) - case f @ BinderFormula(Exists, x, inner) => this.copy(delta = f :: delta) - case f @ BinderFormula(Forall, x, inner) => this.copy(gamma = f :: gamma) - case f @ AtomicFormula(id, args) => - this.copy(atoms = (f :: atoms._1, atoms._2)) - case ConnectorFormula(Neg, List(f @ AtomicFormula(id, args))) => + case _ => + throw Exception("Should not pop Atoms: " + f.repr) + + def prepended(f: Expression): Branch = f match + case And(left, right) => this.copy(alpha = f :: alpha) + case Or(left, right) => this.copy(beta = f :: beta) + case Exists(x, inner) => this.copy(delta = f :: delta) + case Forall(x, inner) => this.copy(gamma = f :: gamma) + case Neg(_) => this.copy(atoms = (atoms._1, f :: atoms._2)) + case _ => + this.copy(atoms = (f :: atoms._1, atoms._2)) case _ => ??? - def prependedAll(l: Seq[Formula]): Branch = l.foldLeft(this)((a, b) => a.prepended(b)) + def prependedAll(l: Seq[Expression]): Branch = l.foldLeft(this)((a, b) => a.prepended(b)) def asSequent: Sequent = (beta ++ delta ++ gamma ++ atoms._1 ++ atoms._2.map(a => !a)).toSet |- Set() // inefficient, not used import Branch.* override def toString(): String = - val pretUnif = unifiable.map((x, f) => x.id + " -> " + prettyFormula(f._1) + " : " + f._2).mkString("Unif(", ", ", ")") + val pretUnif = unifiable.map((x, f) => x.id + " -> " + f._1.repr + " : " + f._2).mkString("Unif(", ", ", ")") // val pretTried = triedInstantiation.map((x, t) => x.id + " -> " + prettyTerm(t, true)).mkString("Tried(", ", ", ")") (s"Branch(" + s"${RED(prettyIte(alpha, "alpha"))}, " + @@ -159,80 +152,63 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent } object Branch { - def empty = Branch(Nil, Nil, Nil, Nil, (Nil, Nil), Map.empty, Map.empty, Set.empty, Map.empty, 1, Map.empty, VariableLabel(Identifier("§uv", 0))) - def empty(n: Int, uv: VariableLabel) = Branch(Nil, Nil, Nil, Nil, (Nil, Nil), Map.empty, Map.empty, Set.empty, Map.empty, n, Map.empty, uv) - def prettyIte(l: Iterable[Formula], head: String): String = l match + def empty = Branch(Nil, Nil, Nil, Nil, (Nil, Nil), Map.empty, Map.empty, Set.empty, Map.empty, 1, Map.empty, Variable(Identifier("§uv", 0), Term)) + def empty(n: Int, uv: Variable) = Branch(Nil, Nil, Nil, Nil, (Nil, Nil), Map.empty, Map.empty, Set.empty, Map.empty, n, Map.empty, uv) + def prettyIte(l: Iterable[Expression], head: String): String = l match case Nil => "Nil" - case _ => l.map(prettyFormula(_, true)).mkString(head + "(", ", ", ")") + case _ => l.map(_.repr).mkString(head + "(", ", ", ")") } - def makeVariableNamesUnique(f: Formula, nextId: Int, seen: Set[VariableLabel]): (Formula, Int, Set[VariableLabel]) = f match - case ConnectorFormula(label, args) => - val (nArgs, nnId, nSeen) = args.foldLeft((List(): Seq[Formula], nextId, seen))((prev, next) => - val (l, n, s) = prev - val (nf, nn, ns) = makeVariableNamesUnique(next, n, s) - (l :+ nf, nn, ns) - ) - (ConnectorFormula(label, nArgs), nnId, nSeen) - case pf: AtomicFormula => (pf, nextId, seen) - case BinderFormula(label, x, inner) => - if (seen.contains(x)) - val (nInner, nnId, nSeen) = makeVariableNamesUnique(inner, nextId + 1, seen) - val newX = VariableLabel(Identifier(x.id, nextId)) - (BinderFormula(label, newX, substituteVariablesInFormula(nInner, Map(x -> newX), Seq())), nnId, nSeen) - else - val (nInner, nnId, nSeen) = makeVariableNamesUnique(inner, nextId, seen + x) - (BinderFormula(label, x, nInner), nnId, nSeen) - - type Substitution = Map[VariableLabel, Term] + def makeVariableNamesUnique(f: Expression, nextId: Int, seen2: Set[Variable]): (Expression, Int) = { + var nextId2: Int = nextId + var seen = seen2 + def recurse(f: Expression): Expression = f match + case Application(f, a) => + Application(recurse(f), recurse(a)) + case Lambda(v, body) => + if seen.contains(v) then + val newV = Variable(Identifier(v.id, nextId2), Term) + nextId2 += 1 + Lambda(newV, substituteVariables(recurse(body), Map(v -> newV))) + else + seen += v + Lambda(v, recurse(body)) + case _ => f + (recurse(f), nextId2) + } + type Substitution = Map[Variable, Expression] val Substitution = HashMap - def prettySubst(s: Substitution): String = s.map((x, t) => x.id + " -> " + prettyTerm(t, true)).mkString("Subst(", ", ", ")") + def prettySubst(s: Substitution): String = s.map((x, t) => x.id + " -> " + t.repr).mkString("Subst(", ", ", ")") /** * Detect if two terms can be unified, and if so, return a substitution that unifies them. */ - def unify(t1: Term, t2: Term, current: Substitution, br: Branch): Option[Substitution] = (t1, t2) match - case (VariableTerm(x), VariableTerm(y)) if (br.unifiable.contains(x) || x.id.no > br.maxIndex) && (br.unifiable.contains(y) || y.id.no > br.maxIndex) => + def unify(t1: Expression, t2: Expression, current: Substitution, br: Branch): Option[Substitution] = (t1, t2) match + case (x: Variable, y: Variable) if (br.unifiable.contains(x) || x.id.no > br.maxIndex) && (br.unifiable.contains(y) || y.id.no > br.maxIndex) => if x == y then Some(current) else if current.contains(x) then unify(current(x), t2, current, br) else if current.contains(y) then unify(t1, current(y), current, br) else Some(current + (x -> y)) - case (VariableTerm(x), t2: Term) if br.unifiable.contains(x) || x.id.no > br.maxIndex => - val newt2 = substituteVariablesInTerm(t2, current) + case (x: Variable, t2: Expression) if br.unifiable.contains(x) || x.id.no > br.maxIndex => + val newt2 = substituteVariables(t2, current) if newt2.freeVariables.contains(x) then None else if (current.contains(x)) unify(current(x), newt2, current, br) else Some(current + (x -> newt2)) - case (t1: Term, VariableTerm(y)) if br.unifiable.contains(y) || y.id.no > br.maxIndex => - val newt1 = substituteVariablesInTerm(t1, current) + case (t1: Expression, y: Variable) if br.unifiable.contains(y) || y.id.no > br.maxIndex => + val newt1 = substituteVariables(t1, current) if newt1.freeVariables.contains(y) then None else if (current.contains(y)) unify(newt1, current(y), current, br) else Some(current + (y -> newt1)) - case (Term(label1, args1), Term(label2, args2)) => - if label1 == label2 && args1.size == args2.size then - args1 - .zip(args2) - .foldLeft(Some(current): Option[Substitution])((prev, next) => - prev match - case None => None - case Some(s) => unify(next._1, next._2, s, br) - ) - else None + case (Application(f1, a1), Application(f2, a2)) => + unify(f1, f2, current, br).flatMap(s => unify(a1, a2, s, br)) /** * Detect if two atoms can be unified, and if so, return a substitution that unifies them. */ - def unifyPred(pos: AtomicFormula, neg: AtomicFormula, br: Branch): Option[Substitution] = { - (pos, neg) match - case (AtomicFormula(id1, args1), AtomicFormula(id2, args2)) if (id1 == id2 && args1.size == args2.size) => - args1 - .zip(args2) - .foldLeft(Some(Substitution.empty): Option[Substitution])((prev, next) => - prev match - case None => None - case Some(s) => unify(next._1, next._2, s, br) - ) - case _ => None + def unifyPred(pos: Expression, neg: Expression, br: Branch): Option[Substitution] = { + assert(pos.sort == Formula && neg.sort == Formula) + unify(pos, neg, Substitution.empty, br) } @@ -242,20 +218,18 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent * The substitution cannot do substitutions that were already done in branch.triedInstantiation. * When multiple substitutions are possible, the one with the smallest size is returned. (Maybe there is a better heuristic, like distance from the root?) */ - def close(branch: Branch): Option[(Substitution, Set[Formula])] = { + def close(branch: Branch): Option[(Substitution, Set[Expression])] = { val newMap = branch.atoms._1 .flatMap(pred => pred.freeVariables.filter(v => branch.unifiable.contains(v))) - .map(v => v -> VariableLabel(Identifier(v.id.name, v.id.no + branch.maxIndex + 1))) + .map(v => v -> Variable(Identifier(v.id.name, v.id.no + branch.maxIndex + 1), Term)) .toMap - val newMapTerm = newMap.map((k, v) => k -> VariableTerm(v)) val inverseNewMap = newMap.map((k, v) => v -> k).toMap - val inverseNewMapTerm = inverseNewMap.map((k, v) => k -> VariableTerm(v)) - val pos = branch.atoms._1.map(pred => substituteVariablesInFormula(pred, newMapTerm, Seq())).asInstanceOf[List[AtomicFormula]].iterator - var substitutions: List[(Substitution, Set[Formula])] = Nil + val pos = branch.atoms._1.map(pred => substituteVariables(pred, newMap)).iterator + var substitutions: List[(Substitution, Set[Expression])] = Nil while (pos.hasNext) { val p = pos.next() - if (p.label == bot) return Some((Substitution.empty, Set(bot))) + if (p == bot) return Some((Substitution.empty, Set(bot))) val neg = branch.atoms._2.iterator while (neg.hasNext) { val n = neg.next() @@ -270,12 +244,12 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent ( sub.flatMap((v, t) => if v.id.no > branch.maxIndex then - if t == inverseNewMapTerm(v) then None - else Some(inverseNewMap(v) -> substituteVariablesInTerm(t, inverseNewMapTerm.map((v, t) => v -> substituteVariablesInTerm(t, sub)))) + if t == inverseNewMap(v) then None + else Some(inverseNewMap(v) -> substituteVariables(t, inverseNewMap.map((v, t) => v -> substituteVariables(t, sub)))) else if newMap.contains(v) && t == newMap(v) then None - else Some(v -> substituteVariablesInTerm(t, inverseNewMapTerm)) + else Some(v -> substituteVariables(t, inverseNewMap)) ), - set.map(f => substituteVariablesInFormula(f, inverseNewMapTerm, Seq())) + set.map(f => substituteVariables(f, inverseNewMap)) ) ) @@ -290,7 +264,7 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent } - def bestSubst(substs: List[(Substitution, Set[Formula])], branch: Branch): Option[(Substitution, Set[Formula])] = { + def bestSubst(substs: List[(Substitution, Set[Expression])], branch: Branch): Option[(Substitution, Set[Expression])] = { if substs.isEmpty then return None val minSize = substs.minBy(_._1.size) val smallSubst = substs.filter(_._1.size == minSize._1.size) @@ -299,22 +273,22 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent val best = smallSubst.minBy(s => substitutionScore(s._1, branch)) Some(best) } - def formulaPenalty(f: Formula, branch: Branch): Int = f match - case ConnectorFormula(And, args) => 10 + args.map(formulaPenalty(_, branch)).sum - case ConnectorFormula(Or, args) => 40 + args.map(formulaPenalty(_, branch)).sum - case BinderFormula(Exists, x, inner) => 30 + formulaPenalty(inner, branch) - case BinderFormula(Forall, x, inner) => 200 + formulaPenalty(inner, branch) - case AtomicFormula(id, args) => 0 - case ConnectorFormula(Neg, List(AtomicFormula(id, args))) => 0 - case _ => ??? + def formulaPenalty(f: Expression, branch: Branch): Int = f match + case And(left, right) => 10 + formulaPenalty(left, branch) + formulaPenalty(right, branch) + case Or(left, right) => 40 + formulaPenalty(left, branch) + formulaPenalty(right, branch) + case Exists(x, inner) => 30 + formulaPenalty(inner, branch) + case Forall(x, inner) => 200 + formulaPenalty(inner, branch) + case _ => 0 def substitutionScore(subst: Substitution, branch: Branch): Int = { - def pairPenalty(v: VariableLabel, t: Term) = { + def pairPenalty(v: Variable, t: Expression) = { val variablePenalty = branch.unifiable(v)._2 + branch.numberInstantiated(v) * 20 - def termPenalty(t: Term): Int = t match - case VariableTerm(x) => if branch.unifiable.contains(x) then branch.unifiable(x)._2 * 1 else 0 - case Term(label, args) => 100 + args.map(termPenalty).sum - variablePenalty + termPenalty(t) + def termPenalty(t: Expression): Int = t match + case x: Variable => if branch.unifiable.contains(x) then branch.unifiable(x)._2 * 1 else 0 + case c: Constant => 40 + case Application(f, a) => 100 + termPenalty(f) + termPenalty(a) + case Lambda(v, inner) => 100 + termPenalty(inner) + 1*variablePenalty + 1*termPenalty(t) } subst.map((v, t) => pairPenalty(v, t)).sum } @@ -325,7 +299,9 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent */ def alpha(branch: Branch): Branch = { val f = branch.alpha.head - branch.copy(alpha = branch.alpha.tail).prependedAll(f.args) + f match + case And(l, r) => branch.copy(alpha = branch.alpha.tail).prepended(l).prepended(r) + case _ => throw Exception("Error: First formula of alpha is not an And") } /** @@ -333,13 +309,13 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent * Add the exploded formula to the used list, if one beta formula is found * The beta list of the branch must not be empty */ - def beta(branch: Branch): List[(Branch, Formula)] = { + def beta(branch: Branch): List[(Branch, Expression)] = { val f = branch.beta.head val b1 = branch.copy(beta = branch.beta.tail) - val resList = f.args.toList.map(disjunct => { - ((b1.prepended(disjunct), disjunct)) - }) - resList + f match + case Or(l, r) => + List((b1.prepended(l), l), (b1.prepended(r), r)) + case _ => throw Exception("Error: First formula of beta is not an Or") } /** @@ -347,13 +323,16 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent * Add the unquantified formula to the branch * Since the bound variable is not marked as suitable for instantiation, it behaves as a constant symbol (skolem) */ - def delta(branch: Branch): (Branch, VariableLabel, Formula) = { + def delta(branch: Branch): (Branch, Variable, Expression) = { val f = branch.delta.head - if branch.skolemized.contains(branch.delta.head.bound) then - val newX = VariableLabel(Identifier(f.bound.id.name, branch.maxIndex)) - val newInner = substituteVariablesInFormula(f.inner, Map(f.bound -> newX), Seq()) - (branch.copy(delta = branch.delta.tail, maxIndex = branch.maxIndex + 1).prepended(newInner), newX, newInner) - else (branch.copy(delta = branch.delta.tail, skolemized = branch.skolemized + f.bound).prepended(f.inner), f.bound, f.inner) + f match + case Exists(v, body) => + if branch.skolemized.contains(v) then + val newV = Variable(Identifier(v.id.name, branch.maxIndex), Term) + val newInner = substituteVariables(body, Map(v -> newV)) + (branch.copy(delta = branch.delta.tail, maxIndex = branch.maxIndex + 1).prepended(newInner), newV, newInner) + else (branch.copy(delta = branch.delta.tail, skolemized = branch.skolemized + v).prepended(body), v, body) + case _ => throw Exception("Error: First formula of delta is not an Exists") } /** @@ -361,36 +340,43 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent * Add the unquantified formula to the branch and mark the bound variable as suitable for unification * This step will most of the time be cancelled when building the proof, unless any arbitrary instantiation is sufficient to get a proof. */ - def gamma(branch: Branch): (Branch, VariableLabel, Formula) = { + def gamma(branch: Branch): (Branch, Variable, Expression) = { val f = branch.gamma.head - val (ni, nb) = branch.unifiable.get(f.bound) match - case None => - (f.inner, f.bound) - case Some(value) => - val newBound = VariableLabel(Identifier(f.bound.id.name, branch.maxIndex)) - val newInner = substituteVariablesInFormula(f.inner, Map(f.bound -> newBound), Seq()) - (newInner, newBound) - val b1 = branch.copy( - gamma = branch.gamma.tail, - unifiable = branch.unifiable + (nb -> (f, formulaPenalty(f.inner, branch))), - numberInstantiated = branch.numberInstantiated + (nb -> (branch.numberInstantiated.getOrElse(f.bound, 0))), - maxIndex = branch.maxIndex + 1, - varsOrder = branch.varsOrder + (nb -> branch.varsOrder.size) - ) - (b1.prepended(ni), nb, ni) + f match + case Forall(v, body) => + val (ni, nb) = branch.unifiable.get(v) match + case None => + (body, v) + case Some(value) => + val newBound = Variable(Identifier(v.id.name, branch.maxIndex), Term) + val newInner = substituteVariables(body, Map(v -> newBound)) + (newInner, newBound) + val b1 = branch.copy( + gamma = branch.gamma.tail, + unifiable = branch.unifiable + (nb -> (f, formulaPenalty(body, branch))), + numberInstantiated = branch.numberInstantiated + (nb -> (branch.numberInstantiated.getOrElse(v, 0))), + maxIndex = branch.maxIndex + 1, + varsOrder = branch.varsOrder + (nb -> branch.varsOrder.size) + ) + (b1.prepended(ni), nb, ni) + case _ => throw Exception("Error: First formula of gamma is not a Forall") + + } /** * When a closing unification has been found, apply it to the branch - * This does not backtracking: The metavariable remains available if it needs further instantiation. + * This does not do backtracking: The metavariable remains available if it needs further instantiation. */ - def applyInst(branch: Branch, x: VariableLabel, t: Term): (Branch, Formula) = { + def applyInst(branch: Branch, x: Variable, t: Expression): (Branch, Expression) = { val f = branch.unifiable(x)._1 val newTried = branch.triedInstantiation.get(x) match case None => branch.triedInstantiation + (x -> Set(t)) case Some(s) => branch.triedInstantiation + (x -> (s + t)) - val inst = instantiate(f.inner, f.bound, t) + val inst = f match + case Forall(v, body) => instantiate(body, v, t) + case _ => throw Exception("Error: Formula in unifiable is not a Forall") val r = branch .prepended(inst) .copy( @@ -414,10 +400,14 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent else if (branch.alpha.nonEmpty) // If branch contains an Alpha formula (LeftAnd) val rec = alpha(branch) decide(rec).map((proof, step) => - if branch.alpha.head.args.exists(proof.head.bot.left.contains) then - val sequent = proof.head.bot.copy(left = (proof.head.bot.left -- branch.alpha.head.args) + branch.alpha.head) - (Weakening(sequent, proof.size - 1) :: proof, step + 1) - else (proof, step) + branch.alpha.head match + case Application(Application(and, left), right) => + + if proof.head.bot.left.contains(left) || proof.head.bot.left.contains(right) then + val sequent = proof.head.bot.copy(left = (proof.head.bot.left - left - right) + branch.alpha.head) + (Weakening(sequent, proof.size - 1) :: proof, step + 1) + else (proof, step) + case _ => throw Exception("Error: First formula of alpha is not an And") ) else if (branch.delta.nonEmpty) // If branch contains a Delta formula (LeftExists) val rec = delta(branch) @@ -452,17 +442,22 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent proof.map(proo => if needed == true then val sequent = ((proo.reverse.zip(list).flatMap((proof, bf) => proof.bot.left - bf._2).toSet + branch.beta.head) |- ()) - (LeftOr(sequent, treversed.reverse, branch.beta.head.args) :: proo, treversed.size) + branch.beta.head match + case Or(left, right) => + (LeftOr(sequent, treversed.reverse, Seq(left, right)) :: proo, treversed.size) + case _ => throw Exception("Error: First formula of beta is not an Or") else (proo, proo.size - 1) ) else if (branch.gamma.nonEmpty) // If branch contains a Gamma formula (LeftForall) val rec = gamma(branch) val upperProof = decide(rec._1) - // LeftForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) + // LeftForall(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) upperProof.map((proof, step) => if proof.head.bot.left.contains(rec._3) then val sequent = (proof.head.bot -<< rec._3) +<< branch.gamma.head - (LeftForall(sequent, step, branch.gamma.head.inner, branch.gamma.head.bound, rec._2()) :: proof, step + 1) + branch.gamma.head match + case Forall(v, body) => + (LeftForall(sequent, step, body, v, rec._2()) :: proof, step + 1) else (proof, step) ) else if (closeSubst.nonEmpty && closeSubst.get._1.nonEmpty) // If branch can be closed with Instantiation (LeftForall) @@ -472,20 +467,23 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent upperProof.map((proof, step) => if proof.head.bot.left.contains(instantiated) then val sequent = (proof.head.bot -<< instantiated) +<< branch.unifiable(x)._1 - (LeftForall(sequent, step, branch.unifiable(x)._1.inner, branch.unifiable(x)._1.bound, t) :: proof, step + 1) + branch.unifiable(x)._1 match + case Forall(v, body) => + (LeftForall(sequent, step, body, v, t) :: proof, step + 1) else (proof, step) ) else None // End of decide } - def containsAlpha(set: Set[Formula], f: Formula) = f match { - case ConnectorFormula(And, args) => args.exists(set.contains) + def containsAlpha(set: Set[Expression], f: Expression): Boolean = f match { + case And(left, right) => containsAlpha(set, left) || containsAlpha(set, right) case _ => set.contains(f) } - def instantiate(f: Formula, x: VariableLabel, t: Term): Formula = f match - case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(instantiate(_, x, t))) - case AtomicFormula(id, args) => AtomicFormula(id, args.map(substituteVariablesInTerm(_, Substitution(x -> t)))) - case BinderFormula(label, y, inner) => if (x == y) f else BinderFormula(label, y, instantiate(inner, x, t)) + def instantiate(f: Expression, x: Variable, t: Expression): Expression = f match + case v: Variable => if v == x then t else v + case c: Constant => c + case Application(f, a) => Application(instantiate(f, x, t), instantiate(a, x, t)) + case Lambda(v, inner) => if (v == x) f else Lambda(v, instantiate(inner, x, t)) } diff --git a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala index d1367feeb..74f96ef1a 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala @@ -39,7 +39,7 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque def from(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { val botK = bot.underlying - val premsFormulas: Seq[((proof.Fact, Formula), Int)] = premises.map(p => (p, sequentToFormula(proof.getSequent(p).underlying))).zipWithIndex + val premsFormulas: Seq[((proof.Fact, Expression), Int)] = premises.map(p => (p, sequentToFormula(proof.getSequent(p).underlying))).zipWithIndex val initProof = premsFormulas.map(s => Restate(() |- s._1._2, -(1 + s._2))).toList val sqToProve = botK ++<< (premsFormulas.map(s => s._1._2).toSet |- ()) @@ -67,7 +67,7 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque */ def solveSequent(s: Sequent): Either[SCProof, (String, Sequent)] = { val augSeq = augmentSequent(s) - val MaRvIn = VariableFormulaLabel(freshId(augSeq.formula.schematicFormulaLabels.map(_.id), "MaRvIn")) // arbitrary name that is unlikely to already exist in the formula + val MaRvIn = Variable(freshId(augSeq.formula.freeVariables.map(_.id), "MaRvIn"), Formula) // arbitrary name that is unlikely to already exist in the formula try { val steps = solveAugSequent(augSeq, 0)(using MaRvIn) @@ -78,7 +78,7 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque ( "The statement may be incorrect or not provable within propositional logic.\n" + "The proof search failed because it needed the truth of the following sequent:\n" + - s"${lisa.utils.FOLPrinter.prettySequent(e.unsolvable)}", + s"${e.unsolvable.repr}", e.unsolvable ) ) @@ -88,36 +88,31 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque // From there, private code. // Augmented Sequent - private case class AugSequent(decisions: (List[Formula], List[Formula]), formula: Formula) + private case class AugSequent(decisions: (List[Expression], List[Expression]), formula: Expression) // Transform a sequent into a format more adequate for solving private def augmentSequent(s: Sequent): AugSequent = { val f = reducedForm(sequentToFormula(s)) - val atoms: scala.collection.mutable.Map[Formula, Int] = scala.collection.mutable.Map.empty + val atoms: scala.collection.mutable.Map[Expression, Int] = scala.collection.mutable.Map.empty AugSequent((Nil, Nil), f) } - def reduceSequent(s: Sequent): Formula = { + def reduceSequent(s: Sequent): Expression = { val p = simplify(sequentToFormula(s)) val nf = computeNormalForm(p) val fln = fromLocallyNameless(nf, Map.empty, 0) - val res = toFormulaAIG(fln) + val res = toExpressionAIG(fln) res } // Find all "atoms" of the formula. // We mean atom in the propositional logic sense, so any formula starting with a predicate symbol, a binder or a schematic connector is an atom here. - def findBestAtom(f: Formula): Option[Formula] = { - val atoms: scala.collection.mutable.Map[Formula, Int] = scala.collection.mutable.Map.empty - def findAtoms2(f: Formula, add: Formula => Unit): Unit = f match { - case AtomicFormula(label, _) if label != top && label != bot => add(f) - case AtomicFormula(_, _) => () - case ConnectorFormula(label, args) => - label match { - case label: ConstantConnectorLabel => args.foreach(c => findAtoms2(c, add)) - case SchematicConnectorLabel(id, arity) => add(f) - } - case BinderFormula(label, bound, inner) => add(f) + def findBestAtom(f: Expression): Option[Expression] = { + val atoms: scala.collection.mutable.Map[Expression, Int] = scala.collection.mutable.Map.empty + def findAtoms2(f: Expression, add: Expression => Unit): Unit = f match { + case And(f1, f2) => findAtoms2(f1, add); findAtoms2(f2, add) + case Neg(f1) => findAtoms2(f1, add) + case _ if f != top && f != bot => add(f) } findAtoms2(f, a => atoms.update(a, { val g = atoms.get(a); if (g.isEmpty) 1 else g.get + 1 })) if (atoms.isEmpty) None else Some(atoms.toList.maxBy(_._2)._1) @@ -128,129 +123,80 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque // Given a sequent, return a proof of that sequent if on exists that only uses propositional logic rules and reflexivity of equality. // Alternates between reducing the formulas using the OL algorithm for propositional logic and branching on an atom using excluded middle. // An atom is a subformula of the input that is either a predicate, a binder or a schematic connector, i.e. a subformula that has not meaning in propositional logic. - private def solveAugSequent(s: AugSequent, offset: Int)(using MaRvIn: VariableFormulaLabel): List[SCProofStep] = { + private def solveAugSequent(s: AugSequent, offset: Int)(using MaRvIn: Variable): List[SCProofStep] = { val bestAtom = findBestAtom(s.formula) val redF = reducedForm(s.formula) if (redF == top()) { - List(RestateTrue(s.decisions._1 ++ s.decisions._2.map((f: Formula) => Neg(f)) |- s.formula)) + List(RestateTrue(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- s.formula)) } else if (bestAtom.isEmpty) { assert(redF == bot()) // sanity check; If the formula has no atom left in it and is reduced, it should be either ⊤ or ⊥. val res = s.decisions._1 |- redF :: s.decisions._2 // the branch that can't be closed throw new NoProofFoundException(res) } else { val atom = bestAtom.get - val optLambda = findSubformula(redF, Seq((MaRvIn, atom))) + val optLambda = findSubformula(redF, MaRvIn, atom) if (optLambda.isEmpty) return solveAugSequent(AugSequent(s.decisions, redF), offset) val lambdaF = optLambda.get - val seq1 = AugSequent((atom :: s.decisions._1, s.decisions._2), lambdaF(Seq(top()))) + val seq1 = AugSequent((atom :: s.decisions._1, s.decisions._2), substituteVariables(lambdaF, Map(MaRvIn -> top))) val proof1 = solveAugSequent(seq1, offset) val subst1 = RightSubstIff( - atom :: s.decisions._1 ++ s.decisions._2.map((f: Formula) => Neg(f)) |- redF, + atom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, offset + proof1.length - 1, - List((LambdaTermFormula(Seq(), atom), LambdaTermFormula(Seq(), top()))), - (lambdaF.vars, lambdaF.body) + Seq((atom, top)), + (Seq(MaRvIn), lambdaF) ) - val seq2 = AugSequent((s.decisions._1, atom :: s.decisions._2), lambdaF(Seq(bot()))) + val negatom = neg(atom) + val seq2 = AugSequent((s.decisions._1, atom :: s.decisions._2), substituteVariables(lambdaF, Map(MaRvIn -> top))) val proof2 = solveAugSequent(seq2, offset + proof1.length + 1) val subst2 = RightSubstIff( - Neg(atom) :: s.decisions._1 ++ s.decisions._2.map((f: Formula) => Neg(f)) |- redF, - offset + proof1.length + proof2.length - 1 + 1, - List((LambdaTermFormula(Seq(), atom), LambdaTermFormula(Seq(), bot()))), - (lambdaF.vars, lambdaF.body) + negatom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, + offset + proof1.length + proof2.length + 1 - 1, + Seq((atom, bot)), + (Seq(MaRvIn), lambdaF) ) - val red2 = Restate(s.decisions._1 ++ s.decisions._2.map((f: Formula) => Neg(f)) |- (redF, atom), offset + proof1.length + proof2.length + 2 - 1) - val cutStep = Cut(s.decisions._1 ++ s.decisions._2.map((f: Formula) => Neg(f)) |- redF, offset + proof1.length + proof2.length + 3 - 1, offset + proof1.length + 1 - 1, atom) - val redStep = Restate(s.decisions._1 ++ s.decisions._2.map((f: Formula) => Neg(f)) |- s.formula, offset + proof1.length + proof2.length + 4 - 1) + val red2 = Restate(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- (redF, atom), offset + proof1.length + proof2.length + 2 - 1) + val cutStep = Cut(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, offset + proof1.length + proof2.length + 3 - 1, offset + proof1.length + 1 - 1, atom) + val redStep = Restate(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- s.formula, offset + proof1.length + proof2.length + 4 - 1) redStep :: cutStep :: red2 :: subst2 :: proof2 ++ (subst1 :: proof1) } } - private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) - private def findSubterm2(t: Term, subs: Seq[(VariableLabel, Term)]): (Term, Boolean) = { - val eq = subs.find(s => isSameTerm(t, s._2)) - if (eq.nonEmpty) (eq.get._1(), true) - else { - val induct = condflat(t.args.map(te => findSubterm2(te, subs))) - if (!induct._2) (t, false) - else (Term(t.label, induct._1), true) - } - } - private def findSubterm2(f: Formula, subs: Seq[(VariableLabel, Term)]): (Formula, Boolean) = { - f match { - case AtomicFormula(label, args) => - val induct = condflat(args.map(findSubterm2(_, subs))) - if (!induct._2) (f, false) - else (AtomicFormula(label, induct._1), true) - case ConnectorFormula(label, args) => - val induct = condflat(args.map(findSubterm2(_, subs))) - if (!induct._2) (f, false) - else (ConnectorFormula(label, induct._1), true) - case BinderFormula(label, bound, inner) => - val fv_in_f = subs.flatMap(e => e._2.freeVariables + e._1) - if (!fv_in_f.contains(bound)) { - val induct = findSubterm2(inner, subs) - if (!induct._2) (f, false) - else (BinderFormula(label, bound, induct._1), true) - } else { - val newv = VariableLabel(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> newv()), Seq.empty) - val induct = findSubterm2(newInner, subs) - if (!induct._2) (f, false) - else (BinderFormula(label, newv, induct._1), true) - } - } - } + private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) - private def findSubformula2(f: Formula, subs: Seq[(VariableFormulaLabel, Formula)]): (Formula, Boolean) = { - val eq = subs.find(s => isSame(f, s._2)) - if (eq.nonEmpty) (eq.get._1(), true) + private def findSubformula2(f: Expression, x: Variable, e: Expression, fv: Set[Variable]): (Expression, Boolean) = { + if (isSame(f, e)) (x, true) else f match { - case AtomicFormula(label, args) => - (f, false) - case ConnectorFormula(label, args) => - val induct = condflat(args.map(findSubformula2(_, subs))) - if (!induct._2) (f, false) - else (ConnectorFormula(label, induct._1), true) - case BinderFormula(label, bound, inner) => - val fv_in_f = subs.flatMap(_._2.freeVariables) - if (!fv_in_f.contains(bound)) { - val induct = findSubformula2(inner, subs) + case Application(f, arg) => + val rf = findSubformula2(f, x, e, fv) + val ra = findSubformula2(arg, x, e, fv) + if (rf._2 || ra._2) (Application(rf._1, ra._1), true) + else (f, false) + case Lambda(v, inner) => + if (!fv.contains(v)) { + val induct = findSubformula2(inner, x, e, fv) if (!induct._2) (f, false) - else (BinderFormula(label, bound, induct._1), true) + else (Lambda(v, induct._1), true) } else { - val newv = VariableLabel(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) - val newInner = substituteVariablesInFormula(inner, Map(bound -> newv()), Seq.empty) - val induct = findSubformula2(newInner, subs) + val newv = Variable(freshId((f.freeVariables ++ fv).map(_.id), v.id), v.sort) + val newInner = substituteVariables(inner, Map(v -> newv)) + val induct = findSubformula2(newInner, x, e, fv + newv) if (!induct._2) (f, false) - else (BinderFormula(label, newv, induct._1), true) + else (Lambda(newv, induct._1), true) } + case _ => (f, false) } } - def findSubterm(t: Term, subs: Seq[(VariableLabel, Term)]): Option[LambdaTermTerm] = { - val vars = subs.map(_._1) - val r = findSubterm2(t, subs) - if (r._2) Some(LambdaTermTerm(vars, r._1)) - else None - } - - def findSubterm(f: Formula, subs: Seq[(VariableLabel, Term)]): Option[LambdaTermFormula] = { - val vars = subs.map(_._1) - val r = findSubterm2(f, subs) - if (r._2) Some(LambdaTermFormula(vars, r._1)) - else None - } - def findSubformula(f: Formula, subs: Seq[(VariableFormulaLabel, Formula)]): Option[LambdaFormulaFormula] = { - val vars = subs.map(_._1) - val r = findSubformula2(f, subs) - if (r._2) Some(LambdaFormulaFormula(vars, r._1)) + def findSubformula(f: Expression, x: Variable, e: Expression): Option[Expression] = { + val r = findSubformula2(f, x, e, e.freeVariables) + if (r._2) Some(r._1) else None } diff --git a/lisa-sets/src/main/scala/lisa/automation/atp/Goeland.scala b/lisa-sets/src/main/scala/lisa/automation/atp/Goeland.scala index e8d50cf0b..2493399e7 100644 --- a/lisa-sets/src/main/scala/lisa/automation/atp/Goeland.scala +++ b/lisa-sets/src/main/scala/lisa/automation/atp/Goeland.scala @@ -81,10 +81,10 @@ object Goeland extends ProofTactic with ProofSequentTactic { val directory = File(foldername) if (directory != null) && !directory.exists() then directory.mkdirs() - val freevars = (sequent.left.flatMap(_.freeVariables) ++ sequent.right.flatMap(_.freeVariables) ).toSet.map(x => x -> K.Term(K.VariableLabel(K.Identifier("X"+x.id.name, x.id.no)), Seq())).toMap + val freevars = (sequent.left.flatMap(_.freeVariables) ++ sequent.right.flatMap(_.freeVariables) ).toSet.map(x => x -> K.Variable(K.Identifier("X"+x.id.name, x.id.no), x.sort) ).toMap val backMap = freevars.map{ - case (x: K.VariableLabel, K.Term(xx: K.VariableLabel, _)) => xx -> K.LambdaTermTerm(Seq(), K.Term(x, Seq())) + case (x: K.Variable, xx: K.Variable) => xx -> x case _ => throw new Exception("This should not happen") } val r = problemToFile(foldername, filename, "question"+i, axioms, sequent, source) diff --git a/lisa-sets/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets/src/main/scala/lisa/maths/Quantifiers.scala index 1aeaf55ac..bad4ab927 100644 --- a/lisa-sets/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets/src/main/scala/lisa/maths/Quantifiers.scala @@ -5,13 +5,13 @@ package lisa.maths */ object Quantifiers extends lisa.Main { - private val x = variable - private val y = variable - private val z = variable - private val a = variable - private val p = formulaVariable - private val P = predicate[1] - private val Q = predicate[1] + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + private val a = variable[Term] + private val p = variable[Formula] + private val P = variable[Term >>: Formula] + private val Q = variable[Term >>: Formula] /** * Theorem --- A formula is equivalent to itself universally quantified if @@ -42,7 +42,7 @@ object Quantifiers extends lisa.Main { ) { have((x === y) <=> P(y) |- (x === y) <=> P(y)) by Hypothesis thenHave(∀(y, (x === y) <=> P(y)) |- (x === y) <=> P(y)) by LeftForall - thenHave(∀(y, (x === y) <=> P(y)) |- P(x)) by InstFunSchema(Map(y -> x)) + thenHave(∀(y, (x === y) <=> P(y)) |- P(x)) by InstSchema(y := x) thenHave(∀(y, (x === y) <=> P(y)) |- ∃(x, P(x))) by RightExists thenHave(∃(x, ∀(y, (x === y) <=> P(y))) |- ∃(x, P(x))) by LeftExists thenHave(thesis) by Restate @@ -55,7 +55,7 @@ object Quantifiers extends lisa.Main { (x === y) /\ (y === z) |- (x === z) ) { have((x === y) |- (x === y)) by Hypothesis - thenHave(((x === y), (y === z)) |- (x === z)) by RightSubstEq.withParametersSimple(List((y, z)), lambda(y, x === y)) + thenHave(((x === y), (y === z)) |- (x === z)) by RightSubstEq.withParametersSimple(List((y, z)), (Seq(y), x === y)) thenHave(thesis) by Restate } @@ -95,7 +95,7 @@ object Quantifiers extends lisa.Main { ) { have(exists(x, P(x) /\ (y === x)) |- P(y)) subproof { have(P(x) |- P(x)) by Hypothesis - thenHave((P(x), y === x) |- P(y)) by RightSubstEq.withParametersSimple(List((y, x)), lambda(y, P(y))) + thenHave((P(x), y === x) |- P(y)) by RightSubstEq.withParametersSimple(List((y, x)), (Seq(y), P(y))) thenHave(P(x) /\ (y === x) |- P(y)) by Restate thenHave(thesis) by LeftExists } @@ -104,7 +104,7 @@ object Quantifiers extends lisa.Main { have(P(y) |- exists(x, P(x) /\ (y === x))) subproof { have(P(x) /\ (y === x) |- P(x) /\ (y === x)) by Hypothesis thenHave(P(x) /\ (y === x) |- exists(x, P(x) /\ (y === x))) by RightExists - thenHave(P(y) /\ (y === y) |- exists(x, P(x) /\ (y === x))) by InstFunSchema(Map(x -> y)) + thenHave(P(y) /\ (y === y) |- exists(x, P(x) /\ (y === x))) by InstSchema(x := y) thenHave(thesis) by Restate } val backward = thenHave(P(y) ==> exists(x, P(x) /\ (y === x))) by Restate @@ -186,8 +186,8 @@ object Quantifiers extends lisa.Main { val fy = thenHave(forall(z, P(z) <=> Q(z)) |- forall(y, ((y === z) <=> P(y)) <=> ((y === z) <=> Q(y)))) by RightForall have(forall(y, P(y) <=> Q(y)) |- (forall(y, P(y)) <=> forall(y, Q(y)))) by Restate.from(universalEquivalenceDistribution) - val univy = thenHave(forall(y, ((y === z) <=> P(y)) <=> ((y === z) <=> Q(y))) |- (forall(y, ((y === z) <=> P(y))) <=> forall(y, ((y === z) <=> Q(y))))) by InstPredSchema( - Map((P -> lambda(y, (y === z) <=> P(y))), (Q -> lambda(y, (y === z) <=> Q(y)))) + val univy = thenHave(forall(y, ((y === z) <=> P(y)) <=> ((y === z) <=> Q(y))) |- (forall(y, ((y === z) <=> P(y))) <=> forall(y, ((y === z) <=> Q(y))))) by InstSchema( + P := lambda(y, (y === z) <=> P(y)), Q := lambda(y, (y === z) <=> Q(y)) ) have(forall(z, P(z) <=> Q(z)) |- (forall(y, ((y === z) <=> P(y))) <=> forall(y, ((y === z) <=> Q(y))))) by Cut(fy, univy) @@ -195,7 +195,7 @@ object Quantifiers extends lisa.Main { thenHave(forall(z, P(z) <=> Q(z)) |- forall(z, forall(y, ((y === z) <=> P(y))) <=> forall(y, ((y === z) <=> Q(y))))) by RightForall have(forall(z, P(z) <=> Q(z)) |- exists(z, forall(y, ((y === z) <=> P(y)))) <=> exists(z, forall(y, ((y === z) <=> Q(y))))) by Cut( lastStep, - existentialEquivalenceDistribution of (P -> lambda(z, forall(y, (y === z) <=> P(y))), Q -> lambda(z, forall(y, (y === z) <=> Q(y)))) + existentialEquivalenceDistribution of (P := lambda(z, forall(y, (y === z) <=> P(y))), Q := lambda(z, forall(y, (y === z) <=> Q(y)))) ) thenHave(thesis) by Restate diff --git a/lisa-sets/src/main/scala/lisa/maths/settheory/InductiveSets.scala b/lisa-sets/src/main/scala/lisa/maths/settheory/InductiveSets.scala index cbc135a14..768e9bd85 100644 --- a/lisa-sets/src/main/scala/lisa/maths/settheory/InductiveSets.scala +++ b/lisa-sets/src/main/scala/lisa/maths/settheory/InductiveSets.scala @@ -38,7 +38,7 @@ object InductiveSets extends lisa.Main { ∃(z, ∀(t, in(t, z) <=> ∀(y, inductive(y) ==> in(t, y)))) ) { val inductExt = - have(∃(x, inductive(x)) |- ∃(z, ∀(t, in(t, z) <=> ∀(y, inductive(y) ==> in(t, y))))) by InstPredSchema(Map(P -> lambda(x, inductive(x))))(intersectionOfPredicateClassExists) + have(∃(x, inductive(x)) |- ∃(z, ∀(t, in(t, z) <=> ∀(y, inductive(y) ==> in(t, y))))) by InstSchema(Map(P -> lambda(x, inductive(x))))(intersectionOfPredicateClassExists) have(∃(z, ∀(t, in(t, z) <=> ∀(y, inductive(y) ==> in(t, y))))) by Cut(inductiveSetExists, inductExt) } @@ -51,7 +51,7 @@ object InductiveSets extends lisa.Main { val prop = ∀(y, inductive(y) ==> in(t, y)) val fprop = ∀(t, in(t, z) <=> prop) - val existsRhs = have(∃(z, fprop) |- ∃!(z, fprop)) by InstPredSchema(Map(schemPred -> (t, prop)))(uniqueByExtension) + val existsRhs = have(∃(z, fprop) |- ∃!(z, fprop)) by InstSchema(Map(schemPred -> (t, prop)))(uniqueByExtension) val existsLhs = have(∃(z, fprop)) by Restate.from(inductiveIntersectionExistence) have(∃!(z, fprop)) by Cut(existsLhs, existsRhs) @@ -117,11 +117,11 @@ object InductiveSets extends lisa.Main { (∀(t, in(t, z) <=> (∀(x, inductive(x) ==> in(t, x)))), inductive(z) <=> (in(∅, z) /\ ∀(y, in(y, z) ==> in(successor(y), z)))) |- inductive(z) ) by RightSubstIff.withParametersSimple(List((inductive(z), in(∅, z) /\ ∀(y, in(y, z) ==> in(successor(y), z)))), lambda(form, form)) - val inductDef = have(inductive(z) <=> (in(∅, z) /\ ∀(y, in(y, z) ==> in(successor(y), z)))) by InstFunSchema(Map(x -> z))(inductive.definition) + val inductDef = have(inductive(z) <=> (in(∅, z) /\ ∀(y, in(y, z) ==> in(successor(y), z)))) by InstSchema(Map(x -> z))(inductive.definition) have((∀(t, in(t, z) <=> (∀(x, inductive(x) ==> in(t, x))))) |- inductive(z)) by Cut(inductDef, inductIff) val inductExpansion = - thenHave((forall(t, in(t, naturalsInductive) <=> (forall(x, inductive(x) ==> in(t, x))))) |- inductive(naturalsInductive)) by InstFunSchema(Map(z -> naturalsInductive)) + thenHave((forall(t, in(t, naturalsInductive) <=> (forall(x, inductive(x) ==> in(t, x))))) |- inductive(naturalsInductive)) by InstSchema(Map(z -> naturalsInductive)) have((naturalsInductive === naturalsInductive) <=> forall(t, in(t, naturalsInductive) <=> (forall(x, inductive(x) ==> in(t, x))))) by InstantiateForall(naturalsInductive)( naturalsInductive.definition diff --git a/lisa-sets/src/main/scala/lisa/maths/settheory/SetTheory.scala b/lisa-sets/src/main/scala/lisa/maths/settheory/SetTheory.scala index 3439bad44..4908bc36a 100644 --- a/lisa-sets/src/main/scala/lisa/maths/settheory/SetTheory.scala +++ b/lisa-sets/src/main/scala/lisa/maths/settheory/SetTheory.scala @@ -83,7 +83,7 @@ object SetTheory extends lisa.Main { * where `P(t)` does not contain `z` as a free variable. * * @example {{{ - * have(∃(z, ∀(t, in(t, z) ⇔ myProperty(t))) ⊢ ∃!(z, ∀(t, in(t, z) ⇔ myProperty(t)))) by InstPredSchema(ScalaMap(schemPred -> (t, myProperty(t))))` + * have(∃(z, ∀(t, in(t, z) ⇔ myProperty(t))) ⊢ ∃!(z, ∀(t, in(t, z) ⇔ myProperty(t)))) by InstSchema(ScalaMap(schemPred -> (t, myProperty(t))))` * }}} * * Instantiation will fail if `myProperty(t)` contains `z` as a free variable. @@ -103,12 +103,12 @@ object SetTheory extends lisa.Main { // backward direction have(fprop(z) |- fprop(z)) by Hypothesis val instLhs = thenHave(fprop(z) |- prop(z)) by InstantiateForall(t) - val instRhs = thenHave(fprop(a) |- prop(a)) by InstFunSchema(ScalaMap(z -> a)) + val instRhs = thenHave(fprop(a) |- prop(a)) by InstSchema(ScalaMap(z -> a)) have((fprop(z), fprop(a)) |- prop(z) /\ prop(a)) by RightAnd(instLhs, instRhs) thenHave(fprop(z) /\ fprop(a) |- in(t, a) <=> in(t, z)) by Tautology val extLhs = thenHave(fprop(z) /\ fprop(a) |- ∀(t, in(t, a) <=> in(t, z))) by RightForall - val extRhs = have(∀(t, in(t, a) <=> in(t, z)) <=> (a === z)) by InstFunSchema(ScalaMap(x -> a, y -> z))(extensionalityAxiom) + val extRhs = have(∀(t, in(t, a) <=> in(t, z)) <=> (a === z)) by InstSchema(ScalaMap(x -> a, y -> z))(extensionalityAxiom) have(fprop(z) /\ fprop(a) |- (∀(t, in(t, a) <=> in(t, z)) <=> (a === z)) /\ ∀(t, in(t, a) <=> in(t, z))) by RightAnd(extLhs, extRhs) thenHave(fprop(z) /\ fprop(a) |- (a === z)) by Tautology @@ -195,7 +195,7 @@ object SetTheory extends lisa.Main { thenHave((in(z, x), (a === x)) |- in(z, a) /\ ((a === x) \/ (a === y))) by Tautology andThen(Substitution.applySubst(upairax, false)) thenHave((in(z, x), (a === x)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by RightExists - thenHave((in(z, x), (x === x)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by InstFunSchema(ScalaMap(a -> x)) + thenHave((in(z, x), (x === x)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by InstSchema(ScalaMap(a -> x)) val tax = thenHave((in(z, x)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by Restate have((in(z, y), (a === y)) |- in(z, y)) by Hypothesis @@ -203,7 +203,7 @@ object SetTheory extends lisa.Main { thenHave((in(z, y), (a === y)) |- in(z, a) /\ ((a === x) \/ (a === y))) by Tautology andThen(Substitution.applySubst(upairax, false)) thenHave((in(z, y), (a === y)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by RightExists - thenHave((in(z, y), (y === y)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by InstFunSchema(ScalaMap(a -> y)) + thenHave((in(z, y), (y === y)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by InstSchema(ScalaMap(a -> y)) val tay = thenHave((in(z, y)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by Restate have((in(z, x) \/ in(z, y)) |- ∃(a, in(z, a) /\ in(a, unorderedPair(x, y)))) by LeftOr(tax, tay) @@ -252,7 +252,7 @@ object SetTheory extends lisa.Main { ) { val form = formulaVariable - have(∀(x, (x === successor(y)) <=> (x === union(unorderedPair(y, unorderedPair(y, y)))))) by InstFunSchema(ScalaMap(x -> y))(successor.definition) + have(∀(x, (x === successor(y)) <=> (x === union(unorderedPair(y, unorderedPair(y, y)))))) by InstSchema(ScalaMap(x -> y))(successor.definition) thenHave(((successor(y) === successor(y)) <=> (successor(y) === union(unorderedPair(y, unorderedPair(y, y)))))) by InstantiateForall(successor(y)) val succDef = thenHave((successor(y) === union(unorderedPair(y, unorderedPair(y, y))))) by Restate val inductDef = have(inductive(x) <=> in(∅, x) /\ ∀(y, in(y, x) ==> in(successor(y), x))) by Restate.from(inductive.definition) @@ -343,7 +343,7 @@ object SetTheory extends lisa.Main { val setWithNoElementsIsEmpty = Theorem( ∀(y, !in(y, x)) |- (x === ∅) ) { - have(!in(y, ∅)) by InstFunSchema(ScalaMap(x -> y))(emptySetAxiom) + have(!in(y, ∅)) by InstSchema(ScalaMap(x -> y))(emptySetAxiom) thenHave(() |- (!in(y, ∅), in(y, x))) by Weakening val lhs = thenHave(in(y, ∅) ==> in(y, x)) by Restate @@ -355,7 +355,7 @@ object SetTheory extends lisa.Main { thenHave(∀(y, !in(y, x)) |- in(y, x) <=> in(y, ∅)) by LeftForall val exLhs = thenHave(∀(y, !in(y, x)) |- ∀(y, in(y, x) <=> in(y, ∅))) by RightForall - have(∀(z, in(z, x) <=> in(z, ∅)) <=> (x === ∅)) by InstFunSchema(ScalaMap(x -> x, y -> ∅))(extensionalityAxiom) + have(∀(z, in(z, x) <=> in(z, ∅)) <=> (x === ∅)) by InstSchema(ScalaMap(x -> x, y -> ∅))(extensionalityAxiom) val exRhs = thenHave(∀(y, in(y, x) <=> in(y, ∅)) <=> (x === ∅)) by Restate have(∀(y, !in(y, x)) |- (∀(y, in(y, x) <=> in(y, ∅)) <=> (x === ∅)) /\ ∀(y, in(y, x) <=> in(y, ∅))) by RightAnd(exLhs, exRhs) @@ -435,7 +435,7 @@ object SetTheory extends lisa.Main { ) { // specialization of the pair axiom to a singleton - have(in(y, unorderedPair(x, x)) <=> (x === y) \/ (x === y)) by InstFunSchema(ScalaMap(x -> x, y -> x, z -> y))(pairAxiom) + have(in(y, unorderedPair(x, x)) <=> (x === y) \/ (x === y)) by InstSchema(ScalaMap(x -> x, y -> x, z -> y))(pairAxiom) thenHave(in(y, singleton(x)) <=> (x === y)) by Restate } @@ -501,13 +501,13 @@ object SetTheory extends lisa.Main { val secondElemInPair = Theorem( in(y, unorderedPair(x, y)) ) { - val lhs = have(in(z, unorderedPair(x, y)) <=> ((z === x) \/ (z === y))) by InstFunSchema(ScalaMap(x -> x, y -> y, z -> z))(pairAxiom) + val lhs = have(in(z, unorderedPair(x, y)) <=> ((z === x) \/ (z === y))) by InstSchema(ScalaMap(x -> x, y -> y, z -> z))(pairAxiom) have((z === y) |- (z === y)) by Hypothesis val rhs = thenHave((z === y) |- (z === x) \/ (z === y)) by Restate val factset = have((z === y) |- (in(z, unorderedPair(x, y)) <=> ((z === x) \/ (z === y))) /\ ((z === x) \/ (z === y))) by RightAnd(lhs, rhs) thenHave((z === y) |- in(z, unorderedPair(x, y))) by Tautology - thenHave((y === y) |- in(y, unorderedPair(x, y))) by InstFunSchema(ScalaMap(z -> y)) + thenHave((y === y) |- in(y, unorderedPair(x, y))) by InstSchema(ScalaMap(z -> y)) thenHave(in(y, unorderedPair(x, y))) by LeftRefl } @@ -583,7 +583,7 @@ object SetTheory extends lisa.Main { ) val lhs = thenHave(Set((a === c) /\ (b === d)) |- unorderedPair(a, b) === unorderedPair(c, d)) by Restate - have(unorderedPair(a, b) === unorderedPair(b, a)) by InstFunSchema(ScalaMap(x -> a, y -> b))(unorderedPairSymmetry) + have(unorderedPair(a, b) === unorderedPair(b, a)) by InstSchema(ScalaMap(x -> a, y -> b))(unorderedPairSymmetry) thenHave((a === d, b === c) |- (unorderedPair(a, b) === unorderedPair(c, d))) by RightSubstEq.withParametersSimple( List((a, d), (b, c)), lambda(Seq(x, y), unorderedPair(a, b) === unorderedPair(y, x)) @@ -604,13 +604,13 @@ object SetTheory extends lisa.Main { val singletonNonEmpty = Theorem( !(singleton(x) === ∅) ) { - val reflLhs = have(in(x, singleton(x)) <=> (x === x)) by InstFunSchema(ScalaMap(y -> x))(singletonHasNoExtraElements) + val reflLhs = have(in(x, singleton(x)) <=> (x === x)) by InstSchema(ScalaMap(y -> x))(singletonHasNoExtraElements) val reflRhs = have((x === x)) by RightRefl have((x === x) /\ (in(x, singleton(x)) <=> (x === x))) by RightAnd(reflLhs, reflRhs) val lhs = thenHave(in(x, singleton(x))) by Tautology - val rhs = have(in(x, singleton(x)) |- !(singleton(x) === ∅)) by InstFunSchema(ScalaMap(y -> x, x -> singleton(x)))(setWithElementNonEmpty) + val rhs = have(in(x, singleton(x)) |- !(singleton(x) === ∅)) by InstSchema(ScalaMap(y -> x, x -> singleton(x)))(setWithElementNonEmpty) have(!(singleton(x) === ∅)) by Cut(lhs, rhs) } @@ -625,18 +625,18 @@ object SetTheory extends lisa.Main { ) { // forward direction // {x} === {y} |- x === y - have(∀(z, in(z, singleton(x)) <=> in(z, singleton(y))) <=> (singleton(x) === singleton(y))) by InstFunSchema(ScalaMap(x -> singleton(x), y -> singleton(y)))(extensionalityAxiom) + have(∀(z, in(z, singleton(x)) <=> in(z, singleton(y))) <=> (singleton(x) === singleton(y))) by InstSchema(ScalaMap(x -> singleton(x), y -> singleton(y)))(extensionalityAxiom) thenHave((singleton(x) === singleton(y)) |- ∀(z, in(z, singleton(x)) <=> in(z, singleton(y)))) by Tautology val singiff = thenHave((singleton(x) === singleton(y)) |- in(z, singleton(x)) <=> in(z, singleton(y))) by InstantiateForall(z) - val singX = have(in(z, singleton(x)) <=> (z === x)) by InstFunSchema(ScalaMap(y -> z))(singletonHasNoExtraElements) + val singX = have(in(z, singleton(x)) <=> (z === x)) by InstSchema(ScalaMap(y -> z))(singletonHasNoExtraElements) have((singleton(x) === singleton(y)) |- (in(z, singleton(x)) <=> in(z, singleton(y))) /\ (in(z, singleton(x)) <=> (z === x))) by RightAnd(singiff, singX) val yToX = thenHave((singleton(x) === singleton(y)) |- (in(z, singleton(y)) <=> (z === x))) by Tautology - val singY = have(in(z, singleton(y)) <=> (z === y)) by InstFunSchema(ScalaMap(x -> y))(singX) + val singY = have(in(z, singleton(y)) <=> (z === y)) by InstSchema(ScalaMap(x -> y))(singX) have((singleton(x) === singleton(y)) |- (in(z, singleton(y)) <=> (z === x)) /\ (in(z, singleton(y)) <=> (z === y))) by RightAnd(yToX, singY) thenHave((singleton(x) === singleton(y)) |- ((z === x) <=> (z === y))) by Tautology - thenHave((singleton(x) === singleton(y)) |- ((x === x) <=> (x === y))) by InstFunSchema(ScalaMap(z -> x)) + thenHave((singleton(x) === singleton(y)) |- ((x === x) <=> (x === y))) by InstSchema(ScalaMap(z -> x)) thenHave((singleton(x) === singleton(y)) |- (x === y)) by Restate val fwd = thenHave((singleton(x) === singleton(y)) ==> (x === y)) by Tautology @@ -786,7 +786,7 @@ object SetTheory extends lisa.Main { ) { val X = singleton(x) - have(!(X === ∅) ==> ∃(y, in(y, X) /\ ∀(z, in(z, X) ==> !in(z, y)))) by InstFunSchema(ScalaMap(x -> X))(foundationAxiom) + have(!(X === ∅) ==> ∃(y, in(y, X) /\ ∀(z, in(z, X) ==> !in(z, y)))) by InstSchema(ScalaMap(x -> X))(foundationAxiom) val lhs = thenHave(!(X === ∅) |- ∃(y, in(y, X) /\ ∀(z, in(z, X) ==> !in(z, y)))) by Restate have(in(y, X) |- in(y, X) <=> (x === y)) by Weakening(singletonHasNoExtraElements) @@ -794,7 +794,7 @@ object SetTheory extends lisa.Main { have((in(x, X), (in(z, X) ==> !in(z, x)), in(y, X)) |- in(z, X) ==> !in(z, x)) by Hypothesis thenHave((in(x, X), ∀(z, in(z, X) ==> !in(z, x)), in(y, X)) |- in(z, X) ==> !in(z, x)) by LeftForall - thenHave((in(x, X), ∀(z, in(z, X) ==> !in(z, x)), in(x, X)) |- in(x, X) ==> !in(x, x)) by InstFunSchema(ScalaMap(z -> x, y -> x)) + thenHave((in(x, X), ∀(z, in(z, X) ==> !in(z, x)), in(x, X)) |- in(x, X) ==> !in(x, x)) by InstSchema(ScalaMap(z -> x, y -> x)) val coreRhs = thenHave(in(x, X) /\ ∀(z, in(z, X) ==> !in(z, x)) |- !in(x, x)) by Restate // now we need to show that the assumption is indeed true @@ -843,12 +843,12 @@ object SetTheory extends lisa.Main { ) { val lhs = have(subset(powerSet(x), x) |- subset(powerSet(x), x)) by Hypothesis - val rhs = have(in(powerSet(x), powerSet(x)) <=> subset(powerSet(x), x)) by InstFunSchema(ScalaMap(x -> powerSet(x), y -> x))(powerAxiom) + val rhs = have(in(powerSet(x), powerSet(x)) <=> subset(powerSet(x), x)) by InstSchema(ScalaMap(x -> powerSet(x), y -> x))(powerAxiom) have(subset(powerSet(x), x) |- subset(powerSet(x), x) /\ (in(powerSet(x), powerSet(x)) <=> subset(powerSet(x), x))) by RightAnd(lhs, rhs) val contraLhs = thenHave(subset(powerSet(x), x) |- in(powerSet(x), powerSet(x))) by Tautology - val contraRhs = have(!in(powerSet(x), powerSet(x))) by InstFunSchema(ScalaMap(x -> powerSet(x)))(selfNonInclusion) + val contraRhs = have(!in(powerSet(x), powerSet(x))) by InstSchema(ScalaMap(x -> powerSet(x)))(selfNonInclusion) have(subset(powerSet(x), x) |- !in(powerSet(x), powerSet(x)) /\ in(powerSet(x), powerSet(x))) by RightAnd(contraLhs, contraRhs) thenHave(subset(powerSet(x), x) |- ()) by Restate @@ -1042,9 +1042,9 @@ object SetTheory extends lisa.Main { val intersectionOfPredicateClassExists = Theorem( ∃(x, P(x)) |- ∃(z, ∀(t, in(t, z) <=> ∀(y, P(y) ==> in(t, y)))) ) { - have(∃(z, ∀(t, in(t, z) <=> (in(t, x) /\ φ(t))))) by InstFunSchema(ScalaMap(z -> x))(comprehensionSchema) + have(∃(z, ∀(t, in(t, z) <=> (in(t, x) /\ φ(t))))) by InstSchema(ScalaMap(z -> x))(comprehensionSchema) - val conjunction = thenHave(∃(z, ∀(t, in(t, z) <=> (in(t, x) /\ ∀(y, P(y) ==> in(t, y)))))) by InstPredSchema(ScalaMap(φ -> lambda(t, ∀(y, P(y) ==> in(t, y))))) + val conjunction = thenHave(∃(z, ∀(t, in(t, z) <=> (in(t, x) /\ ∀(y, P(y) ==> in(t, y)))))) by InstSchema(ScalaMap(φ -> lambda(t, ∀(y, P(y) ==> in(t, y))))) have(∀(y, P(y) ==> in(t, y)) |- ∀(y, P(y) ==> in(t, y))) by Hypothesis thenHave(∀(y, P(y) ==> in(t, y)) /\ P(x) |- ∀(y, P(y) ==> in(t, y))) by Weakening @@ -1224,7 +1224,7 @@ object SetTheory extends lisa.Main { thenHave((union(pair(x, y)) === unaryIntersection(pair(x, y))) |- in(z, union(pair(x, y))) <=> in(z, unaryIntersection(pair(x, y)))) by InstantiateForall(z) have((union(pair(x, y)) === unaryIntersection(pair(x, y))) |- (((z === x) \/ (z === y)) <=> (z === x))) by Tautology.from(lastStep, unionPair, pairUnaryIntersection) - thenHave((union(pair(x, y)) === unaryIntersection(pair(x, y))) |- (((y === x) \/ (y === y)) <=> (y === x))) by InstFunSchema(ScalaMap(z -> y)) + thenHave((union(pair(x, y)) === unaryIntersection(pair(x, y))) |- (((y === x) \/ (y === y)) <=> (y === x))) by InstSchema(ScalaMap(z -> y)) thenHave(thesis) by Restate } diff --git a/lisa-sets/src/test/scala/lisa/automation/CongruenceTest.scala b/lisa-sets/src/test/scala/lisa/automation/CongruenceTest.scala index 34bc77eee..74c6e9ecf 100644 --- a/lisa-sets/src/test/scala/lisa/automation/CongruenceTest.scala +++ b/lisa-sets/src/test/scala/lisa/automation/CongruenceTest.scala @@ -66,7 +66,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("3 terms no congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(a) egraph.add(b) egraph.add(c) @@ -77,7 +77,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("8 terms no congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(a) egraph.add(b) egraph.add(c) @@ -98,7 +98,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("15 terms no congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(a) egraph.add(b) egraph.add(c) @@ -133,7 +133,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("15 terms no congruence egraph test with redundant merges") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(a) egraph.add(b) egraph.add(c) @@ -176,7 +176,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("4 terms withcongruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(F(a)) egraph.add(F(b)) egraph.merge(a, b) @@ -195,7 +195,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("divide-mult-shift in terms by 2 egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(one) egraph.add(two) egraph.add(a) @@ -236,7 +236,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("long chain of terms congruence eGraph") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(x) val fx = egraph.add(F(x)) val ffx = egraph.add(F(fx)) @@ -259,7 +259,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("3 formulas no congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(af) egraph.add(bf) egraph.add(cf) @@ -270,7 +270,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("8 formulas no congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(af) egraph.add(bf) egraph.add(cf) @@ -291,7 +291,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("15 formulas no congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(af) egraph.add(bf) egraph.add(cf) @@ -326,7 +326,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("15 formulas no congruence egraph test with redundant merges") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(af) egraph.add(bf) egraph.add(cf) @@ -369,7 +369,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("4 formulas withcongruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(Ff(af)) egraph.add(Ff(bf)) egraph.merge(af, bf) @@ -386,7 +386,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("divide-mult-shift in formulas by 2 egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(onef) egraph.add(twof) egraph.add(af) @@ -429,7 +429,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("long chain of formulas congruence eGraph") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(xf) val fx = egraph.add(Ff(xf)) val ffx = egraph.add(Ff(fx)) @@ -456,7 +456,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { ////////////////////////////////////// test("2 terms 6 predicates with congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(Ff(Ff(Fp(a)))) egraph.add(Ff(Ff(Fp(b)))) egraph.merge(a, b) @@ -481,7 +481,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("6 terms 6 predicates with congruence egraph test") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(Ff(Ff(Fp(F(F(a)))))) egraph.add(Ff(Ff(Fp(F(F(b)))))) egraph.merge(a, b) @@ -499,7 +499,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("15 terms no congruence with redundant merges test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(a) egraph.add(b) egraph.add(c) @@ -551,7 +551,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("4 elements with congruence test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(F(a)) egraph.add(F(b)) egraph.merge(a, b) @@ -562,7 +562,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("divide-mult-shift by 2 in terms egraph test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(one) egraph.add(two) egraph.add(a) @@ -603,7 +603,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("long chain of termscongruence eGraph with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(x) val fx = egraph.add(F(x)) val ffx = egraph.add(F(fx)) @@ -635,7 +635,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("15 formulas no congruence proofs with redundant merges test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(af) egraph.add(bf) egraph.add(cf) @@ -688,7 +688,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("4 formulas with congruence test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(Ff(af)) egraph.add(Ff(bf)) egraph.merge(af, bf) @@ -698,7 +698,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("divide-mult-shift by 2 in formulas egraph test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(onef) egraph.add(twof) egraph.add(af) @@ -739,7 +739,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("long chain of formulas congruence eGraph with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(xf) val fx = egraph.add(Ff(xf)) val ffx = egraph.add(Ff(fx)) @@ -768,7 +768,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { test("2 terms 6 predicates with congruence egraph test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(Ff(Ff(Fp(a)))) egraph.add(Ff(Ff(Fp(b)))) egraph.merge(a, b) @@ -788,7 +788,7 @@ class CongruenceTest extends AnyFunSuite with lisa.TestMain { } test("6 terms 6 predicates with congruence egraph test with proofs") { - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.add(Ff(Ff(Fp(F(F(a)))))) egraph.add(Ff(Ff(Fp(F(F(b)))))) egraph.merge(a, b) diff --git a/lisa-sets/src/test/scala/lisa/examples/peano_example/Peano.scala b/lisa-sets/src/test/scala/lisa/examples/peano_example/Peano.scala index 2f01882f7..c84112f70 100644 --- a/lisa-sets/src/test/scala/lisa/examples/peano_example/Peano.scala +++ b/lisa-sets/src/test/scala/lisa/examples/peano_example/Peano.scala @@ -123,7 +123,7 @@ object Peano { /* x ) - val inductionInstance: SCProofStep = SC.InstPredSchema( + val inductionInstance: SCProofStep = SC.InstSchema( () |- ((plus(zero, zero) === plus(zero, zero)) /\ forall(x, (plus(x, zero) === plus(zero, x)) ==> (plus(s(x), zero) === plus(zero, s(x))))) ==> forall( x, plus(x, zero) === plus(zero, x) @@ -238,7 +238,7 @@ object Peano { /* val inductionInstance = { val inductionOnY0 = SC.Rewrite(() |- (sPhi(zero) /\ forall(y, sPhi(y) ==> sPhi(s(y)))) ==> forall(y, sPhi(y)), -1) - val inductionInstance1 = SC.InstPredSchema( + val inductionInstance1 = SC.InstSchema( () |- ((plus(s(x), zero) === plus(x, s(zero))) /\ forall(y, (plus(x, s(y)) === plus(s(x), y)) ==> (plus(x, s(s(y))) === plus(s(x), s(y))))) ==> @@ -320,7 +320,7 @@ object Peano { /* val inductionInstance = { val inductionOnY0 = SC.Rewrite(() |- (sPhi(zero) /\ forall(y, sPhi(y) ==> sPhi(s(y)))) ==> forall(y, sPhi(y)), -1) - val inductionInstance1 = SC.InstPredSchema( + val inductionInstance1 = SC.InstSchema( () |- ((plus(x, zero) === plus(zero, x)) /\ forall(y, (plus(x, y) === plus(y, x)) ==> (plus(x, s(y)) === plus(s(y), x)))) ==> diff --git a/lisa-sets2/src/main/scala/lisa/Main.scala b/lisa-sets2/src/main/scala/lisa/Main.scala new file mode 100644 index 000000000..3a56aea6a --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/Main.scala @@ -0,0 +1,38 @@ +package lisa + +import lisa.SetTheoryLibrary +import lisa.prooflib.BasicMain + +/** + * The parent trait of all theory files containing mathematical development + */ +trait Main extends BasicMain { + + export lisa.fol.FOL.{*, given} + export SetTheoryLibrary.{given, _} + export lisa.prooflib.BasicStepTactic.* + export lisa.prooflib.SimpleDeducedSteps.* + + export lisa.automation.Tautology + // export lisa.automation.Substitution + export lisa.automation.Tableau + export lisa.automation.Congruence + // export lisa.automation.Apply + // export lisa.automation.Exact + + knownDefs.update(emptySet, Some(emptySetAxiom)) + knownDefs.update(unorderedPair, Some(pairAxiom)) + knownDefs.update(union, Some(unionAxiom)) + knownDefs.update(powerSet, Some(powerAxiom)) + knownDefs.update(subset, Some(subsetAxiom)) + + extension (symbol: Constant[?]) { + def definition: JUSTIFICATION = { + getDefinition(symbol).get + } + def shortDefinition: JUSTIFICATION = { + getShortDefinition(symbol).get + } + } + +} diff --git a/lisa-sets2/src/main/scala/lisa/SetTheoryLibrary.scala b/lisa-sets2/src/main/scala/lisa/SetTheoryLibrary.scala new file mode 100644 index 000000000..c7862e66d --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/SetTheoryLibrary.scala @@ -0,0 +1,257 @@ +package lisa + +import lisa.fol.FOL.{_, given} +import lisa.kernel.proof.RunningTheory +import lisa.prooflib.Library + +/** + * Specific implementation of [[utilities.Library]] for Set Theory, with a RunningTheory that is supposed to be used by the standard library. + */ +object SetTheoryLibrary extends lisa.prooflib.Library { + + val theory = new RunningTheory() + + // Predicates + /** + * The symbol for the set membership predicate. + */ + final val in = constant[Term >>: Term >>: Formula]("elem") + + /** + * The symbol for the subset predicate. + */ + final val subset = constant[Term >>: Term >>: Formula]("subsetOf") + + /** + * The symbol for the equicardinality predicate. Needed for Tarski's axiom. + */ + final val sim = constant[Term >>: Term >>: Formula]("sameCardinality") // Equicardinality + /** + * Set Theory basic predicates + */ + final val predicates = Set(in, subset, sim) + // val choice + + // Functions + /** + * The symbol for the empty set constant. + */ + final val emptySet = constant[Term ]("emptySet") + + /** + * The symbol for the unordered pair function. + */ + final val unorderedPair = constant[Term >>: Term >>: Term]("unorderedPair") + + /** + * The symbol for the powerset function. + */ + final val powerSet = constant[Term >>: Term]("powerSet") + + /** + * The symbol for the set union function. + */ + final val union = constant[Term >>: Term]("union") + + /** + * The symbol for the universe function. Defined in TG set theory. + */ + final val universe = constant[Term >>: Term]("universe") + + /** + * Set Theory basic functions. + */ + final val functions = Set(unorderedPair, powerSet, union, universe) + + + /** + * The kernel theory loaded with Set Theory symbols and axioms. + */ + // val runningSetTheory: RunningTheory = new RunningTheory() + // given RunningTheory = runningSetTheory + + predicates.foreach(s => addSymbol(s)) + functions.foreach(s => addSymbol(s)) + addSymbol(emptySet) + + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + final val φ = variable[Term >>: Formula] + private val A = variable[Term] + private val B = variable[Term] + private val P = variable[Term >>: Term >>: Formula] + + //////////// + // Axioms // + //////////// + + // Z + //////// + + /** + * Extensionality Axiom --- Two sets are equal iff they have the same + * elements. + * + * `() |- (x = y) ⇔ ∀ z. z ∈ x ⇔ z ∈ y` + */ + final val extensionalityAxiom: this.AXIOM = Axiom(forall(z, (z ∈ x) <=> (z ∈ y)) <=> (x === y)) + + /** + * Pairing Axiom --- For any sets `x` and `y`, there is a set that contains + * exactly `x` and `y`. This set is denoted mathematically as `{x, y}` and + * here as `unorderedPair(x, y)`. + * + * `() |- z ∈ {x, y} ⇔ (z === x ∨ z === y)` + * + * This axiom defines [[unorderedPair]] as the function symbol representing + * this set. + */ + final val pairAxiom: AXIOM = Axiom(z ∈ unorderedPair(x, y) <=> (x === z) \/ (y === z)) + + /** + * Comprehension/Separation Schema --- For a formula `ϕ(_, _)` and a set `z`, + * there exists a set `y` which contains only the elements `x` of `z` that + * satisfy `ϕ(x, z)`. This is represented mathematically as `y = {x ∈ z | ϕ(x, + * z)}`. + * + * `() |- ∃ y. ∀ x. x ∈ y ⇔ (x ∈ z ∧ ϕ(x, z))` + * + * This schema represents an infinite collection of axioms, one for each + * formula `ϕ(x, z)`. + */ + final val comprehensionSchema: AXIOM = Axiom(exists(y, forall(x, (x ∈ y) <=> ((x ∈ z) /\ φ(x))))) + + /** + * Empty Set Axiom --- From the Comprehension Schema follows the existence of + * a set containing no elements, the empty set. + * + * `∅ = {x ∈ X | x != x}`. + * + * This axiom defines [[emptySet]] as the constant symbol representing this set. + * + * `() |- !(x ∈ ∅)` + */ + final val emptySetAxiom: AXIOM = Axiom(!(x ∈ emptySet)) + + /** + * Union Axiom --- For any set `x`, there exists a set `union(x)` which is the + * union of its elements. For every element of `union(x)`, there is an element + * `y` of `x` which contains it. + * + * `() |- z ∈ union(x) ⇔ ∃ y. y ∈ x ∧ z ∈ y` + * + * Mathematically, we write `union(x)` as `∪ x`. + * + * This axiom defines [[union]] as the function symbol representing this set. + */ + final val unionAxiom: AXIOM = Axiom(z ∈ union(x) <=> exists(y, (y ∈ x) /\ (z ∈ y))) + + /** + * Subset Axiom --- For sets `x` and `y`, `x` is a subset of `y` iff every + * element of `x` is in `y`. Denoted `x ⊆ y`. + * + * `() |- x ⊆ y ⇔ (z ∈ x ⇒ z ∈ y)` + * + * This axiom defines the [[subset]] symbol as this predicate. + */ + final val subsetAxiom: AXIOM = Axiom((x ⊆ y) <=> forall(z, (z ∈ x) ==> (z ∈ y))) + + /** + * Power Set Axiom --- For a set `x`, there exists a power set of `x`, denoted + * `PP(x)` or `power(x)` which contains every subset of x. + * + * `() |- z ∈ power(x) ⇔ z ⊆ x` + * + * This axiom defines [[powerSet]] as the function symbol representing this + * set. + */ + final val powerAxiom: AXIOM = Axiom(x ∈ powerSet(y) <=> x ⊆ y) + + /** + * Infinity Axiom --- There exists an infinite set. + * + * The definition requires a notion of finiteness, which generally corresponds + * to natural numbers. Since the naturals have not yet been defined, their + * definition and structure is imitated in the definition of an inductive set. + * + * `inductive(x) ⇔ (∅ ∈ x ∧ ∀ y. y ∈ x ⇒ y ∪ {y} ∈ x)` + * + * This axiom postulates that there exists an inductive set. + * + * `() |- ∃ x. inductive(x)` + */ + final val infinityAxiom: AXIOM = Axiom(exists(x, emptySet ∈ x /\ forall(y, (y ∈ x) ==> union(unorderedPair(y, unorderedPair(y, y))) ∈ x ))) + + /** + * Foundation/Regularity Axiom --- Every non-empty set `x` has an `∈`-minimal + * element. Equivalently, the relation `∈` on any family of sets is + * well-founded. + * + * `() |- (x != ∅) ==> ∃ y ∈ x. ∀ z. z ∈ x ⇒ ! z ∈ y` + */ + final val foundationAxiom: AXIOM = Axiom(!(x === emptySet) ==> exists(y, (y ∈ x) /\ forall(z, (z ∈ x) ==> !(z ∈ y)))) + + // ZF + ///////// + + /** + * Replacement Schema --- If a predicate `P` is 'functional' over `x`, i.e., + * given `a ∈ x`, there is a unique `b` such that `P(x, a, b)`, then the + * 'image' of `x` in P exists and is a set. It contains exactly the `b`'s that + * satisfy `P` for each `a ∈ x`. + */ + final val replacementSchema: AXIOM = Axiom( + forall(x, (x ∈ A) ==> ∀(y, ∀(z, (P(x)(y) /\ P(x)(z)) ==> (y === z)))) ==> + exists(B, forall(y, (y ∈ B) <=> exists(x, (x ∈ A) /\ P(x)(y)))) + ) + + final val tarskiAxiom: AXIOM = Axiom( + forall( + x, + (x ∈ universe(x)) /\ + forall( + y, + (y ∈ universe(x)) ==> ((powerSet(y) ∈ universe(x)) /\ (powerSet(y) ⊆ universe(x))) /\ + forall(z, (z ⊆ universe(x)) ==> (sim(y)(universe(x)) /\ (y ∈ universe(x)))) + ) + ) + ) + + /** + * The set of all axioms of Tarski-Grothedick (TG) set theory. + * + * @return + */ + def axioms: Set[(String, AXIOM)] = Set( + ("EmptySet", emptySetAxiom), + ("extensionalityAxiom", extensionalityAxiom), + ("pairAxiom", pairAxiom), + ("unionAxiom", unionAxiom), + ("subsetAxiom", subsetAxiom), + ("powerAxiom", powerAxiom), + ("foundationAxiom", foundationAxiom), + ("infinityAxiom", infinityAxiom), + ("comprehensionSchema", comprehensionSchema), + ("replacementSchema", replacementSchema), + ("TarskiAxiom", tarskiAxiom) + ) + + ///////////// + // Aliases // + ///////////// + + // Unicode symbols + + val ∅ = emptySet + val ∈ = in + + extension (l: Term) + def ∈(r: Term): Formula = in(l)(r) + def ⊆(r: Term): Formula = subset(l)(r) + def =/=(r: Term): Formula = !(l === r) + + + def unorderedPair(x: Term, y: Term): Term = App(App(unorderedPair, x), y) + +} diff --git a/lisa-sets2/src/main/scala/lisa/automation/CommonTactics.scala b/lisa-sets2/src/main/scala/lisa/automation/CommonTactics.scala new file mode 100644 index 000000000..db2433271 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/CommonTactics.scala @@ -0,0 +1,262 @@ +package lisa.automation.kernel + +import lisa.automation.Tautology +import lisa.fol.FOL as F +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.{_, given} +import lisa.prooflib.SimpleDeducedSteps.* +import lisa.prooflib.* +import lisa.utils.K + +object CommonTactics { + /* + + /** + **/ object LeftForall extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable, t: F.Term | K.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T], t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel - lazy val tK = t match { - case t: F.Term => t.underlying - case t: K.Term => t - } + lazy val xK = x.underlying + lazy val tK = t.underlying lazy val phiK = phi.underlying lazy val botK = bot.underlying - lazy val quantified = K.BinderFormula(K.Forall, xK, phiK) - lazy val instantiated = K.substituteVariablesInFormula(phiK, Map(xK -> tK), Seq()) + lazy val quantified = K.forall(xK, phiK) + lazy val instantiated = K.substituteVariables(phiK, Map(xK -> tK)) if (!K.isSameSet(botK.right, premiseSequent.right)) proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") @@ -361,7 +357,7 @@ object BasicStepTactic { if (!pivot.isEmpty) if (pivot.tail.isEmpty) pivot.head match { - case F.BinderFormula(F.Forall, x, phi) => LeftForall.withParameters(phi, x, t)(premise)(bot) + case F.forall(x, phi) => LeftForall.withParameters(phi, x, t)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") } else @@ -377,13 +373,13 @@ object BasicStepTactic { val in: F.Formula = instantiatedPivot.head val quantifiedPhi: Option[F.Formula] = bot.left.find(f => f match { - case g @ F.BinderFormula(F.Forall, _, _) => F.isSame(F.instantiateBinder(g, t), in) + case g @ F.forall(v, e) => F.isSame(e.substitute(v := t), in) case _ => false } ) quantifiedPhi match { - case Some(F.BinderFormula(F.Forall, x, phi)) => LeftForall.withParameters(phi, x, t)(premise)(bot) + case Some(F.forall(x, phi)) => LeftForall.withParameters(phi, x, t)(premise)(bot) case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") } } else proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") @@ -404,16 +400,18 @@ object BasicStepTactic { // go through conclusion to find a matching quantified formula val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = pivot.find(f => + val quantifiedPhi: Option[(F.Formula, Substitution)] = pivot.collectFirstDefined(f => f match { - case g @ F.BinderFormula(F.Forall, x, phi) => UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined - case _ => false + case g @ F.forall(x, phi) => + val ctx = RewriteContext.withBound(phi.freeVars - x) + matchExpr(using ctx)(phi, in).map(f -> _) + case _ => None } ) quantifiedPhi match { - case Some(F.BinderFormula(F.Forall, x, phi)) => - LeftForall.withParameters(phi, x, UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).get._2.getOrElse(x, x))(premise)(bot) + case Some((F.forall(x, phi), subst)) => + LeftForall.withParameters(phi, x, subst(x).getOrElse(x))(premise)(bot) case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") } } else proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") @@ -429,12 +427,12 @@ object BasicStepTactic { * */ object LeftExists extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel + lazy val xK = x.underlying lazy val phiK = phi.underlying lazy val botK = bot.underlying - lazy val quantified = K.BinderFormula(K.Exists, xK, phiK) + lazy val quantified = K.exists(xK, phiK) if ((botK.left union botK.right).exists(_.freeVariables.contains(xK))) proof.InvalidProofTactic("The variable x must not be free in the resulting sequent.") @@ -462,19 +460,19 @@ object BasicStepTactic { val in: F.Formula = instantiatedPivot.head val quantifiedPhi: Option[F.Formula] = bot.left.find(f => f match { - case F.BinderFormula(F.Exists, _, g) => F.isSame(g, in) + case F.exists(_, g) => F.isSame(g, in) case _ => false } ) quantifiedPhi match { - case Some(F.BinderFormula(F.Exists, x, phi)) => LeftExists.withParameters(phi, x)(premise)(bot) + case Some(F.exists(x, phi)) => LeftExists.withParameters(phi, x)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer an existensially quantified pivot from premise and conclusion.") } } else proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the unquantified formula found.") else if (pivot.tail.isEmpty) pivot.head match { - case F.BinderFormula(F.Exists, x, phi) => LeftExists.withParameters(phi, x)(premise)(bot) + case F.exists(x, phi) => LeftExists.withParameters(phi, x)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") } else @@ -482,6 +480,7 @@ object BasicStepTactic { } } + /* /** *+ * Γ |- ∃x. φ, Δ Γ', φ, φ[y/x] |- x = y, Δ' + * ------------------------------------------- + * Γ, Γ' |- ∃!x. φ, Δ, Δ' + *+ * + * This tactic separates the existence and the uniqueness proofs, which are often easier to prove independently, at + * the expense of brevity. + * + * @see [[RightExistsOne]]. + */ + object ExistenceAndUniqueness extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof, om: OutputManager)(phi: F.Formula, x: F.Variable, y: F.Variable)(existence: proof.Fact, uniqueness: proof.Fact)( + bot: F.Sequent + ): proof.ProofTacticJudgement = { + val existenceSeq = proof.getSequent(existence) + val uniquenessSeq = proof.getSequent(uniqueness) + + lazy val substPhi = phi.substitute(x := y) + lazy val existenceFormula = F.∃(x, phi) + lazy val uniqueExistenceFormula = F.∃!(x, phi) + + // Checking that all formulas are present + if (x == y) { + proof.InvalidProofTactic("x and y can not be equal.") + } else if (!F.contains(existenceSeq.right, existenceFormula)) { + proof.InvalidProofTactic(s"Existence sequent conclusion does not contain ∃x. φ.") + } else if (!F.contains(uniquenessSeq.left, phi)) { + proof.InvalidProofTactic("Uniqueness sequent premises do not contain φ.") + } else if (!F.contains(uniquenessSeq.left, substPhi)) { + proof.InvalidProofTactic(s"Uniqueness sequent premises do not contain φ[y/x].") + } else if (!F.contains(uniquenessSeq.right, x === y) && !F.contains(uniquenessSeq.right, y === x)) { + proof.InvalidProofTactic(s"Uniqueness sequent conclusion does not contain x = y") + } else if (!F.contains(bot.right, uniqueExistenceFormula)) { + proof.InvalidProofTactic(s"Bottom sequent conclusion does not contain ∃!x. φ") + } + + // Checking pivots + else if (!F.isSameSet(existenceSeq.left ++ uniquenessSeq.left, bot.left + phi + substPhi)) { + proof.InvalidProofTactic("Could not infer correct left pivots.") + } else if (!F.isSameSet(existenceSeq.right ++ uniquenessSeq.right + uniqueExistenceFormula, bot.right + existenceFormula + (x === y))) { + proof.InvalidProofTactic("Could not infer correct right pivots.") + } else { + val gammaPrime = uniquenessSeq.left.filter(f => !F.isSame(f, phi) && !F.isSame(f, substPhi)) + + TacticSubproof { + // There's got to be a better way of importing have/thenHave/assume methods + // but I did not find one + + val forward = lib.have(phi |- ((x === y) ==> substPhi)) subproof { + lib.assume(phi) + lib.thenHave((x === y) |- substPhi) by RightSubstEq.withParametersSimple(List((x, y)), F.lambda(x, phi)) + lib.thenHave((x === y) ==> substPhi) by Restate + } + + for (f <- gammaPrime) { + lib.assume(f) + } + + val backward = lib.have(phi |- (substPhi ==> (x === y))) by Restate.from(uniqueness) + + lib.have(phi |- ((x === y) <=> substPhi)) by RightIff(forward, backward) + lib.thenHave(phi |- F.∀(y, (x === y) <=> substPhi)) by RightForall + lib.thenHave(phi |- F.∃(x, F.∀(y, (x === y) <=> substPhi))) by RightExists + lib.thenHave(F.∃(x, phi) |- F.∃(x, F.∀(y, (x === y) <=> substPhi))) by LeftExists + lib.thenHave(F.∃(x, phi) |- F.∃!(x, phi)) by RightExistsOne + + lib.have(bot) by Cut(existence, lib.lastStep) + } + } + } + + def apply(using lib: Library, proof: lib.Proof, om: OutputManager)(phi: F.Formula)(existence: proof.Fact, uniqueness: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val existenceSeq = proof.getSequent(existence) + val uniquenessSeq = proof.getSequent(uniqueness) + + // Try to infer x from the premises + // Specifically, find variables in the correct quantifiers, common to all three sequents + val existsVars: Set[F.Variable] = existenceSeq.right.collect { + case F.BinderFormula(F.Exists, x, f) if F.isSame(f, phi) => x + } + if (existsVars.isEmpty) { + return proof.InvalidProofTactic("Missing existential quantifier in the existence sequent.") + } + + val commonVars = bot.right.collect { + case F.BinderFormula(F.ExistsOne, x, f) if F.isSame(f, phi) && existsVars.contains(x) => x + } + if (commonVars.size != 1) { + return proof.InvalidProofTactic("Could not infer correct variable x in quantifiers.") + } + + val x = commonVars.head + + // Infer y from the equalities in the uniqueness sequent + uniquenessSeq.right.collectFirst { + case F.AppliedPredicate(F.`equality`, Seq(`x`, (y: F.Variable))) if x != y && F.contains(uniquenessSeq.left, phi.substitute(x := y)) => + y + + case F.AppliedPredicate(F.`equality`, List(F.AppliedFunctional(y: F.Variable, _), F.AppliedFunctional(`x`, _))) if x != y && F.contains(uniquenessSeq.left, phi.substitute(x := y)) => + y + } match { + case Some(y) => ExistenceAndUniqueness.withParameters(phi, x, y)(existence, uniqueness)(bot) + case None => proof.InvalidProofTactic("Could not infer correct variable y in uniqueness sequent.") + } + } + } + + /** + *+ * + * ------------- if f(xs) = The(y, P(y)) is a function definition + * |- P(f(xs)) + *+ * Here `xs` is an arbitrary list of parameters. + * + * If `f(xs) = The(y, (φ ==> Q(y)) /\ (!φ ==> (y === t)))` is a conditional function definition, then: + *+ * + * -------------- + * φ |- Q(f(xs)) + *+ */ + object Definition extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(f: F.ConstantFunctionLabel[?], uniqueness: proof.Fact)(xs: F.Term*)(bot: F.Sequent): proof.ProofTacticJudgement = { + val expr = lib.getDefinition(f) match { + case Some(value: lib.FunctionDefinition[?]) => value + case _ => return proof.InvalidProofTactic("Could not get definition of function.") + } + val method: (F.ConstantFunctionLabel[?], proof.Fact) => Seq[F.Term] => F.Sequent => proof.ProofTacticJudgement = + expr.f.substituteUnsafe(expr.vars.zip(xs).toMap) match { + case F.AppliedConnector( + F.And, + Seq( + F.AppliedConnector(F.Implies, Seq(a, _)), + F.AppliedConnector(F.Implies, Seq(b, _)) + ) + ) if F.isSame(F.Neg(a), b) => + conditional(using lib, proof) + + case _ => unconditional(using lib, proof) + } + method(f, uniqueness)(xs)(bot) + } + + /** + *+ * + * ------------- if f(xs) = The(y, P(y)) is a function definition + * |- P(f(xs)) + *+ */ + def unconditional(using lib: Library, proof: lib.Proof)(f: F.ConstantFunctionLabel[?], uniqueness: proof.Fact)(xs: F.Term*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lib.getDefinition(f) match { + case Some(definition: lib.FunctionDefinition[?]) => + if (bot.right.size != 1) { + return proof.InvalidProofTactic("Right-hand side of bottom sequent should contain only 1 formula.") + } + val y = definition.out + val vars = definition.vars + val fxs = f.applyUnsafe(xs) + + // Instantiate terms in the definition + val subst = vars.zip(xs).map(tup => tup._1 := tup._2) + val P = definition.f.substitute(subst*) + val expected = P.substitute(y := fxs) + if (!F.isSame(expected, bot.right.head)) { + return proof.InvalidProofTactic("Right-hand side of bottom sequent should be of the form P(f(xs)).") + } + + TacticSubproof { + lib.have(F.∀(y, (y === fxs) <=> P)) by Tautology.from(uniqueness, definition.of(subst*)) + lib.thenHave((y === fxs) <=> P) by InstantiateForall(y) + lib.thenHave((fxs === fxs) <=> P.substitute(y := fxs)) by InstSchema(Map(y -> fxs)) + lib.thenHave(P.substitute(y := fxs)) by Restate + } + + case _ => proof.InvalidProofTactic("Could not get definition of function.") + } + } + + /** + *+ * + * -------------- if f(xs) = The(y, (φ ==> Q(y)) /\ (!φ ==> R(y))) + * φ |- Q(f(xs)) + *+ */ + def conditional(using lib: Library, proof: lib.Proof)(f: F.ConstantFunctionLabel[?], uniqueness: proof.Fact)(xs: F.Term*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lib.getDefinition(f) match { + case Some(definition: lib.FunctionDefinition[?]) => + if (bot.right.size != 1) { + return proof.InvalidProofTactic("Right-hand side of bottom sequent should contain exactly 1 formula.") + } else if (bot.left.isEmpty) { + return proof.InvalidProofTactic("Left-hand side of bottom sequent should not be empty.") + } + val y = definition.out + val vars = definition.vars + + // Extract variable labels to instantiate them later in the proof + // val F.LambdaTermFormula(vars, _) = expr + // val instantiations: Seq[(F.SchematicTermLabel, F.LambdaTermTerm)] = vars.zip(xs.map(x => F.LambdaTermTerm(Seq(), x))) + + val subst = vars.zip(xs).map(tup => tup._1 := tup._2) + val P = definition.f.substitute(subst*) + // Instantiate terms in the definition + // val P = F.LambdaTermFormula(Seq(y), expr(xs)) + + // Unfold the conditional definition to find Q + val phi = F.And(bot.left.toSeq) + val Q: F.LambdaExpression[F.Term, F.Formula, 1] = P.body match { + case F.AppliedConnector( + F.And, + Seq( + F.AppliedConnector(F.Implies, Seq(a, f)), + F.AppliedConnector(F.Implies, Seq(b, g)) + ) + ) if F.isSame(F.Neg(a), b) => + if (F.isSame(a, phi)) F.lambda(y, f) + else if (F.isSame(b, phi)) F.lambda(y, g) + else return proof.InvalidProofTactic("Condition of definition is not satisfied.") + + case _ => + return proof.InvalidProofTactic("Definition is not conditional.") + } + + val fxs = f.applyUnsafe(xs) + + val expected = P.substitute(y := fxs) + if (!F.isSame(expected, bot.right.head)) { + return proof.InvalidProofTactic("Right-hand side of bottom sequent should be of the form Q(fxs).") + } + + TacticSubproof { + lib.have(F.∀(y, (y === fxs) <=> P)) by Tautology.from(uniqueness, definition.of(subst*)) + lib.thenHave((y === fxs) <=> P) by InstantiateForall(y) + lib.thenHave((fxs === fxs) <=> P.substitute(y := fxs)) by InstSchema(Map(y -> fxs)) + lib.thenHave(P.substitute(y := fxs)) by Restate + lib.thenHave(phi ==> Q(fxs)) by Tautology + lib.thenHave(phi |- Q(fxs)) by Restate + } + + case _ => proof.InvalidProofTactic("Could not get definition of function.") + } + } + } +*/ +} diff --git a/lisa-sets2/src/main/scala/lisa/automation/Congruence.scala b/lisa-sets2/src/main/scala/lisa/automation/Congruence.scala new file mode 100644 index 000000000..75784aa24 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/Congruence.scala @@ -0,0 +1,537 @@ +package lisa.automation +import lisa.fol.FOL.{*, given} +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.* +import lisa.prooflib.SimpleDeducedSteps.* +import lisa.prooflib.* +import lisa.utils.K +import leo.datastructures.TPTP.AnnotatedFormula.FormulaType + +/** + * This tactic tries to prove a sequent by congruence. + * Consider the congruence closure of all terms and formulas in the sequent, with respect to all === and <=> left of the sequent. + * The sequent is provable by congruence if one of the following conditions is met: + * - The right side contains an equality s === t or equivalence a <=> b provable in the congruence closure. + * - The left side contains an negated equality !(s === t) or equivalence !(a <=> b) provable in the congruence closure. + * - There is a formula a on the left and b on the right such that a and b are congruent. + * - There are two formulas a and !b on the left such that a and b are congruent. + * - There are two formulas a and !b on the right such that a and b are congruent. + * - The sequent is Ol-valid without equality reasoning + * Note that complete congruence closure modulo OL is an open problem. + * + * The tactic uses an egraph datastructure to compute the congruence closure. + * The egraph itselfs relies on two underlying union-find datastructure, one for terms and one for formulas. + * The union-finds are equiped with an `explain` method that produces a path between any two elements in the same equivalence class. + * Each edge of the path can come from an external equality, or be the consequence of congruence. + * The tactic uses uses this path to produce needed proofs. + * + */ +object Congruence extends ProofTactic with ProofSequentTactic { + + def apply(using lib: Library, proof: lib.Proof)(bot: Sequent): proof.ProofTacticJudgement = TacticSubproof { + + import lib.* + + val egraph = new EGraphExpr() + egraph.addAll(bot.left) + egraph.addAll(bot.right) + + bot.left.foreach{ + case === #@ l #@ r => egraph.merge(l, r) + case <=> #@ l #@ r => egraph.merge(l, r) + case _ => () + } + + if isSameSequent(bot, ⊤) then + have(bot) by Restate + else if bot.left.exists { lf => + bot.right.exists { rf => + if egraph.idEq(lf, rf) then + val base = have(bot.left |- (bot.right + lf) ) by Restate + val eq = have(egraph.proveExpr(lf, rf, bot)) + val a = variable[Formula] + have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstEq.withParameters(Seq((lf, rf)), (Seq(a), a))(base) + have(bot) by Cut(eq, lastStep) + true + else false + } || + bot.left.exists{ + case rf2 @ neg #@ rf if egraph.idEq(lf, rf)=> + val base = have((bot.left + !lf) |- bot.right ) by Restate + val eq = have(egraph.proveExpr(lf, rf.asInstanceOf, bot)) + val a = variable[Formula] + have((bot.left + makeEq(lf, rf)) |- (bot.right) ) by LeftSubstEq.withParameters(Seq((lf, rf)), (Seq(a), !a))(base) + have(bot) by Cut(eq, lastStep) + true + case _ => false + } || { + lf match + case neg #@ (=== #@ a #@ b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case neg #@ (<=> #@ a #@ b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case _ => false + } + + } then () + else if bot.right.exists { rf => + bot.right.exists{ + case lf2 @ neg #@ (lf) if egraph.idEq(lf, rf)=> + val base = have((bot.left) |- (bot.right + !rf) ) by Restate + val eq = have(egraph.proveExpr[F](lf.asInstanceOf, rf, bot)) + val a = variable[Formula] + have((bot.left + makeEq(lf, rf)) |- (bot.right) ) by RightSubstEq.withParameters(Seq((lf, rf)), (Seq(a), !a))(base) + have(bot) by Cut(eq, lastStep) + true + case _ => false + } || { + rf match + case (=== #@ a #@ b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case (<=> #@ a #@ b) if egraph.idEq(a, b) => + have(egraph.proveExpr(a, b, bot)) + true + case _ => false + } + } then () + else + return proof.InvalidProofTactic(s"No congruence found to show sequent\n $bot") + + } + +} + + +class UnionFind[T] { + // parent of each element, leading to its root. Uses path compression + val parent = scala.collection.mutable.Map[T, T]() + // original parent of each element, leading to its root. Does not use path compression. Used for explain. + val realParent = scala.collection.mutable.Map[T, (T, ((T, T), Boolean, Int))]() + //keep track of the rank (i.e. number of elements bellow it) of each element. Necessary to optimize union. + val rank = scala.collection.mutable.Map[T, Int]() + //tracks order of ancientness of unions. + var unionCounter = 0 + + /** + * add a new element to the union-find. + */ + def add(x: T): Unit = { + parent(x) = x + realParent(x) = (x, ((x, x), true, 0)) + rank(x) = 0 + } + + /** + * + * @param x the element whose parent we want to find + * @return the root of x + */ + def find(x: T): T = { + if parent(x) == x then + x + else + var root = x + while parent(root) != root do + root = parent(root) + var y = x + while parent(y) != root do + parent(y) = root + y = parent(y) + root + } + + /** + * Merges the classes of x and y + */ + def union(x: T, y: T): Unit = { + unionCounter += 1 + val xRoot = find(x) + val yRoot = find(y) + if (xRoot == yRoot) return + if (rank(xRoot) < rank(yRoot)) { + parent(xRoot) = yRoot + realParent(xRoot) = (yRoot, ((x, y), true, unionCounter)) + } else if (rank(xRoot) > rank(yRoot)) { + parent(yRoot) = xRoot + realParent(yRoot) = (xRoot, ((x, y), false, unionCounter)) + } else { + parent(yRoot) = xRoot + realParent(yRoot) = (xRoot, ((x, y), false, unionCounter)) + rank(xRoot) = rank(xRoot) + 1 + } + } + + private def getPathToRoot(x: T): List[T] = { + if x == find(x) then + List(x) + else + val next = realParent(x) + x :: getPathToRoot(next._1) + + } + + private def getExplanationFromTo(x:T, c: T): List[(T, ((T, T), Boolean, Int))] = { + if x == c then + List() + else + val next = realParent(x) + next :: getExplanationFromTo(next._1, c)} + + private def lowestCommonAncestor(x: T, y: T): Option[T] = { + val pathX = getPathToRoot(x) + val pathY = getPathToRoot(y) + pathX.find(pathY.contains) + } + + /** + * Returns a path from x to y made of pairs of elements (u, v) + * such that union(u, v) was called. + */ + def explain(x:T, y:T): Option[List[(T, T)]]= { + + if (x == y) then return Some(List()) + val lca = lowestCommonAncestor(x, y) + lca match + case None => None + case Some(lca) => + var max :((T, T), Boolean, Int) = ((x, x), true, 0) + var itX = x + while itX != lca do + val (next, ((u1, u2), b, c)) = realParent(itX) + if c > max._3 then + max = ((u1, u2), b, c) + itX = next + + var itY = y + while itY != lca do + val (next, ((u1, u2), b, c)) = realParent(itY) + if c > max._3 then + max = ((u1, u2), !b, c) + itY = next + + val u1 = max._1._1 + val u2 = max._1._2 + if max._2 then + Some(explain(x, u1).get ++ List((u1, u2)) ++ explain(u2, y).get) + else + Some(explain(x, u2).get ++ List((u1, u2)) ++ explain(u1, y).get) + } + + + /** + * Returns the set of all roots of all classes + */ + def getClasses: Set[T] = parent.keys.map(find).toSet + + /** + * Add all elements in the collection to the union-find + */ + def addAll(xs: Iterable[T]): Unit = xs.foreach(add) + +} + + + /////////////////////////////// + ///////// E-graph ///////////// + /////////////////////////////// + +import scala.collection.mutable + +class EGraphExpr() { + + val parents = mutable.Map[Expr[?], mutable.Set[Expr[?]]]() + val UF = new UnionFind[Expr[?]]() + + + + + def find[T](id: Expr[T]): Expr[T] = UF.find(id).asInstanceOf[Expr[T]] + + trait Step + case class ExternalStep(between: (Expr[?], Expr[?])) extends Step + case class CongruenceStep(between: (Expr[?], Expr[?])) extends Step + + + val proofMap = mutable.Map[(Expr[?], Expr[?]), Step]() + + def explain(id1: Expr[?], id2: Expr[?]): Option[List[Step]] = { + val steps = UF.explain(id1, id2) + steps.map(_.foldLeft((id1, List[Step]())) { + case ((prev, acc), step) => + proofMap(step) match + case s @ ExternalStep((l, r)) => + if l == prev then + (r, s :: acc) + else if r == prev then + (l, ExternalStep(r, l) :: acc) + else throw new Exception("Invalid proof recovered: It is not a chain") + case s @ CongruenceStep((l, r)) => + if l == prev then + (r, s :: acc) + else if r == prev then + (l, CongruenceStep(r, l) :: acc) + else throw new Exception("Invalid proof recovered: It is not a chain") + + }._2.reverse) + } + + + def makeSingletonEClass(node:Expr[?]): Expr[?] = { + UF.add(node) + parents(node) = mutable.Set() + node + } + + def idEq(id1: Expr[?], id2: Expr[?]): Boolean = find(id1) == find(id2) + + + def canonicalize(node: Expr[?]) : Expr[?] = node match + case App(f, a) => App.unsafe(canonicalize(f), find(a)) + case _ => node + + + + def add(node: Expr[?]): Expr[?] = + if codes.contains(node) then node + else + codes(node) = codes.size + if node.sort == K.Term || node.sort == K.Formula then + makeSingletonEClass(node) + node match + case Multiapp(f, args) => + args.foreach(child => + add(child) + parents(find(child)).add(node) + ) + mapSigs(canSig(node)) = node + node + + def addAll(nodes: Iterable[Term|Formula]): Unit = + nodes.foreach{ e => (e: @unchecked) match + case node: Term if node.sort == K.Term => add(node) + case node: Formula if node.sort == K.Formula => add(node) + case _ => () + } + + + + def merge[S](id1: Expr[S], id2: Expr[S]): Unit = { + mergeWithStep(id1, id2, ExternalStep((id1, id2))) + } + + def mergeUnsafe(id1: Expr[?], id2: Expr[?]): Unit = { + mergeWithStep(id1, id2, ExternalStep((id1, id2))) + } + + type Sig = (Expr[?], List[Int]) + val mapSigs = mutable.Map[Sig, Expr[?]]() + val codes = mutable.Map[Expr[?], Int]() + + def canSig(node: Expr[?]): Sig = node match + case Multiapp(label, args) => + (label, args.map(a => codes(find(a))).toList) + + protected def mergeWithStep(id1: Expr[?], id2: Expr[?], step: Step): Unit = { + if id1.sort != id2.sort then throw new IllegalArgumentException("Cannot merge nodes of different sorts") + if find(id1) == find(id2) then () + else + proofMap((id1, id2)) = step + val parents1 = parents(find(id1)) + val parents2 = parents(find(id2)) + + + if find(id1) == find(id2) then return () + + proofMap((id1, id2)) = step + val (small, big ) = if parents(find(id1)).size < parents(find(id2)).size then + (id1, id2) else (id2, id1) + codes(find(small)) = codes(find(big)) + UF.union(id1, id2) + val newId = find(id1) + var worklist = List[(Expr[?], Expr[?], Step)]() + + parents(small).foreach { pExpr => + val canonicalPExpr = canSig(pExpr) + if mapSigs.contains(canonicalPExpr) then + val qExpr = mapSigs(canonicalPExpr) + + worklist = (pExpr, qExpr, CongruenceStep((pExpr, qExpr))) :: worklist + else + mapSigs(canonicalPExpr) = pExpr + } + parents(newId) = parents(big) + parents(newId).addAll(parents(small)) + worklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } + } + + + def proveExpr[S](using lib: Library, proof: lib.Proof)(id1: Expr[S], id2:Expr[S], base: Sequent): proof.ProofTacticJudgement = + TacticSubproof { proveInnerTerm(id1, id2, base) } + + + + def proveInnerTerm(using lib: Library, proof: lib.Proof)(id1: Expr[?], id2:Expr[?], base: Sequent): Unit = { + import lib.* + val steps = explain(id1, id2) + steps match { + case None => throw new Exception("No proof found in the egraph") + case Some(steps) => + if steps.isEmpty then have(base.left |- (base.right + (makeEq(id1, id2)))) by Restate + steps.foreach { + case ExternalStep((l, r)) => + val goalSequent = base.left |- (base.right + (makeEq(id1, r))) + if l == id1 then + have(goalSequent) by Restate + else + val x = variable[Term](freshId(Seq(id1))) + have(goalSequent) by RightSubstEq.withParameters(List((l, r)), (Seq(x), makeEq(id1, x)))(lastStep) + case CongruenceStep((l, r)) => + val prev = if id1 != l then lastStep else null + val leqr = have(base.left |- (base.right + (makeEq(l, r)))) subproof { sp ?=> + (l, r) match + case (Multiapp(labell, argsl), Multiapp(labelr, argsr)) if labell == labelr && argsl.size == argsr.size => + var freshn = freshId((l.freeVars ++ r.freeVars).map(_.id), "n").no + val ziped = (argsl zip argsr) + var zip = List[(Expr[?], Expr[?])]() + var children = List[Expr[?]]() + var vars = List[Variable[?]]() + var steps = List[(Formula, sp.ProofStep)]() + ziped.reverse.foreach { (al, ar) => + if al == ar then children = al :: children + else { + val x = variable(Identifier("n", freshn), al.sort) + freshn = freshn + 1 + children = x :: children + vars = x :: vars + steps = (makeEq(al, ar), have(proveExpr(al, ar.asInstanceOf, base))) :: steps + zip = (al, ar) :: zip + } + } + have(base.left |- (base.right + makeEq(l, l))) by Restate + val eqs = zip.map((l, r) => makeEq(l, r)) + val goal = have((base.left ++ eqs) |- (base.right + makeEq(l, r))).by.bot + have((base.left ++ eqs) |- (base.right + makeEq(l, r))) by RightSubstEq.withParameters(zip, (vars, makeEq(l, Multiapp.unsafe(labelr, children))))(lastStep) + steps.foreach { s => + have( + if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 + ) by Cut(s._2, lastStep) + } + case _ => + println(s"l: $l") + println(s"r: $r") + throw Exception("Unreachable") + + } + if id1 != l then + val goalSequent = base.left |- (base.right + (makeEq(id1, r))) + val x = variable(freshId(Seq(id1)), id1.sort) + have(goalSequent +<< makeEq(l, r)) by RightSubstEq.withParameters(List((l, r)), (Seq(x), makeEq(id1, x)))(prev) + have(goalSequent) by Cut(leqr, lastStep) + } + } + } + + + + + + /* + + def proveExpr(using lib: Library, proof: lib.Proof)(id1: Formula, id2:Formula, base: Sequent): proof.ProofTacticJudgement = + TacticSubproof { proveInnerFormula(id1, id2, base) } + + def proveInnerFormula(using lib: Library, proof: lib.Proof)(id1: Formula, id2:Formula, base: Sequent): Unit = { + import lib.* + val steps = explain(id1, id2) + steps match { + case None => throw new Exception("No proof found in the egraph") + case Some(steps) => + if steps.isEmpty then have(base.left |- (base.right + (id1 <=> id2))) by Restate + steps.foreach { + case ExternalStep((l, r)) => + val goalSequent = base.left |- (base.right + (id1 <=> r)) + if l == id1 then + have(goalSequent) by Restate + else + val x = freshVariableFormula(id1) + have(goalSequent) by RightSubstEq.withParameters(List((l, r)), lambda(x, id1 <=> x))(lastStep) + case CongruenceStep((l, r)) => + val prev = if id1 != l then lastStep else null + val leqr = have(base.left |- (base.right + (l <=> r))) subproof { sp ?=> + (l, r) match + case (AppliedConnector(labell, argsl), AppliedConnector(labelr, argsr)) if labell == labelr && argsl.size == argsr.size => + var freshn = freshId((l.freeVariableFormulas ++ r.freeVariableFormulas).map(_.id), "n").no + val ziped = (argsl zip argsr) + var zip = List[(Formula, Formula)]() + var children = List[Formula]() + var vars = List[VariableFormula]() + var steps = List[(Formula, sp.ProofStep)]() + ziped.reverse.foreach { (al, ar) => + if al == ar then children = al :: children + else { + val x = VariableFormula(Identifier("n", freshn)) + freshn = freshn + 1 + children = x :: children + vars = x :: vars + steps = (al <=> ar, have(proveExpr(al, ar, base))) :: steps + zip = (al, ar) :: zip + } + } + have(base.left |- (base.right + (l <=> l))) by Restate + val eqs = zip.map((l, r) => l <=> r) + val goal = have((base.left ++ eqs) |- (base.right + (l <=> r))).by.bot + have((base.left ++ eqs) |- (base.right + (l <=> r))) by RightSubstEq.withParameters(zip, lambda(vars, l <=> labelr.applyUnsafe(children)))(lastStep) + steps.foreach { s => + have( + if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 + ) by Cut(s._2, lastStep) + } + + case (AppliedPredicate(labell, argsl), AppliedPredicate(labelr, argsr)) if labell == labelr && argsl.size == argsr.size => + var freshn = freshId((l.freeVariableFormulas ++ r.freeVariableFormulas).map(_.id), "n").no + val ziped = (argsl zip argsr) + var zip = List[(Term, Term)]() + var children = List[Term]() + var vars = List[Variable]() + var steps = List[(Formula, sp.ProofStep)]() + ziped.reverse.foreach { (al, ar) => + if al == ar then children = al :: children + else { + val x = Variable(Identifier("n", freshn)) + freshn = freshn + 1 + children = x :: children + vars = x :: vars + steps = (al === ar, have(proveTerm(al, ar, base))) :: steps + zip = (al, ar) :: zip + } + } + have(base.left |- (base.right + (l <=> l))) by Restate + val eqs = zip.map((l, r) => l === r) + val goal = have((base.left ++ eqs) |- (base.right + (l <=> r))).by.bot + have((base.left ++ eqs) |- (base.right + (l <=> r))) by RightSubstEq.withParameters(zip, lambda(vars, l <=> labelr.applyUnsafe(children)))(lastStep) + steps.foreach { s => + have( + if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1 + ) by Cut(s._2, lastStep) + } + case _ => + println(s"l: $l") + println(s"r: $r") + throw UnreachableException + + } + if id1 != l then + val goalSequent = base.left |- (base.right + (id1 <=> r)) + val x = freshVariableFormula(id1) + have(goalSequent +<< (l <=> r)) by RightSubstEq.withParameters(List((l, r)), lambda(x, id1 <=> x))(prev) + have(goalSequent) by Cut(leqr, lastStep) + + } + } + } +*/ + +} \ No newline at end of file diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala new file mode 100644 index 000000000..29b81e0e5 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -0,0 +1,474 @@ +package lisa.automation + +import lisa.fol.FOL as F +import lisa.kernel.proof.RunningTheory +import lisa.kernel.proof.SCProof +import lisa.kernel.proof.SequentCalculus +import lisa.prooflib.BasicStepTactic +import lisa.prooflib.SimpleDeducedSteps +import lisa.prooflib.ProofTacticLib.{*, given} +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.UserLisaException +import lisa.utils.unification.UnificationUtils.* +import lisa.utils.collection.Extensions.* + +import scala.annotation.nowarn +import scala.collection.mutable.{Map as MMap} + +import F.{*, given} +import lisa.utils.collection.VecSet + +object Substitution: + + /** + * Extracts a raw substitution into a `RewriteRule`. + */ + def extractRule + (using lib: Library, proof: lib.Proof) + (rule: proof.Fact | F.Formula): RewriteRule = + rule match + case f: Formula @unchecked => (f: @unchecked) match + case === #@ (l: Term) #@ (r: Term) => TermRewriteRule(l, r) + case <=> #@ (l: Formula) #@ (r: Formula) => FormulaRewriteRule(l, r) + case f: proof.Fact @unchecked => extractRule(proof.getSequent(f).right.head) + + /** + * Partitions raw substitution rules into free and confined rules, also + * creating a source map, mapping each rule to the `Fact` it was derived from, + * for proof construction. + */ + def partition + (using lib: Library, proof: lib.Proof) + (substitutions: Seq[proof.Fact | F.Formula]): (Map[RewriteRule, proof.Fact], RewriteContext) = + substitutions.foldLeft((Map.empty, RewriteContext.empty)): + case ((source, ctx), rule) => + val erule = extractRule(rule) + rule match + case f: Formula @unchecked => + (source + (erule -> erule.source), ctx.withConfinedRule(erule).withBound(f.freeVars)) + case j: lib.JUSTIFICATION => + (source + (erule -> j), ctx.withFreeRule(erule)) + case f: proof.Fact @unchecked => + (source + (erule -> f), ctx.withConfinedRule(erule)) + + /** + * Checks if a raw substitution input can be used as a rewrite rule (is === or + * <=>, basically). + */ + def validSubstitutionRule + (using lib: lisa.prooflib.Library, proof: lib.Proof) + (rule: (proof.Fact | F.Formula)): Boolean = + rule match + // as formula + case f: Formula @unchecked => f match + case === #@ l #@ r => true + case <=> #@ l #@ r => true + case _ => false + // as a justification + case just: proof.Fact @unchecked => + val sequent = proof.getSequent(just) + sequent.right.size == 1 && validSubstitutionRule(sequent.right.head) + + object Apply extends ProofTactic: + def apply + (using lib: Library, proof: lib.Proof) + (substitutions: (proof.Fact | F.Formula)*) + (premiseStep: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + + // are all substitution rules actually valid? + // if not, exit early + + val violatingFacts = substitutions.collect: + case f: proof.Fact @unchecked if !validSubstitutionRule(f) => proof.getSequent(f) + + val violatingFormulas = substitutions.collect: + case f: F.Formula @unchecked if !validSubstitutionRule(f) => f + + if violatingFacts.nonEmpty then + val msgBase = "Substitution rules must have a single equality or equivalence on the right-hand side. Violating sequents passed:\n" + val msgList = violatingFacts.zipWithIndex.map: + case (f, i) => s"\t${i + 1}. $f" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) + else if violatingFormulas.nonEmpty then + val msgBase = "Substitution rules must be equalities or equivalences. Violating formulas passed:\n" + val msgList = violatingFacts.zipWithIndex.map: + case (f, i) => s"\t${i + 1}. $f" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) + else + // continue, we have a list of rules to work with + + // rewrite base + val premise = proof.getSequent(premiseStep) + // the target is bot + + // metadata: + // maintain a list of where substitutions come from + // and categorize them for the rewrite context + val (sourceMap, prectx) = partition(substitutions) + val ctx = prectx.withBound(premise.left.flatMap(_.freeVars)) + + // TODO: CHECK is this really necessary? + // remove from the premise equalities we are rewriting with, as these + // terms themselves are not targets for the rewriting + // val filteredPrem = ??? + + // check whether this rewrite is even possible. + // if it is, get the context (term with holes) corresponding to the + // single-step simultaneous rewrite + + // for each formula in the premise left (resp. right), there must be a + // corresponding formula in the conclusion left (resp. right) that it + // can be rewritten into. + + // discover a (possibly non-injective non-surjective) mapping from one + // formula set to another where a formula maps to another by the + // rewrites above + inline def collectRewritingPairs + (base: Set[Formula], target: Set[Formula]): Option[Seq[FormulaRewriteResult]] = + base.iterator.map: formula => + target.collectFirstDefined: target => + rewrite(using ctx)(formula, target) + .toOptionSeq + + // collect the set of formulas in `base` that rewrite to *no* formula + // in `target`. Guaranteed to be non-empty if + // `collectRewritingPairs(base, target)` is None. + inline def collectViolatingPairs + (base: Set[Formula], target: Set[Formula]): Set[Formula] = + premise.left.filter: formula => + bot.left.forall: target => + rewrite(using ctx)(formula, target).isEmpty + + + val leftSubsts = collectRewritingPairs(premise.left, bot.left) + val rightSubsts = collectRewritingPairs(premise.right, bot.right) + + if leftSubsts.isEmpty then + // error, find formulas that failed to rewrite + val msgBase = "Could not rewrite LHS of premise into conclusion with given substitutions.\nViolating Formulas:" + val msgList = + collectViolatingPairs(premise.left, bot.left) + .zipWithIndex + .map: + case (formula, i) => s"\t${i + 1}. $formula" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) + else if rightSubsts.isEmpty then + // error, find formulas that failed to rewrite + val msgBase = "Could not rewrite LHS of premise into conclusion with given substitutions.\nViolating Formulas:" + val msgList = + collectViolatingPairs(premise.right, bot.right) + .zipWithIndex + .map: + case (formula, i) => s"\t${i + 1}. $formula" + + proof.InvalidProofTactic(msgBase + msgList.mkString("\n")) + else + // rewriting is possible, construct the proof + + import lib.{have, thenHave, lastStep} + import BasicStepTactic.{TacticSubproof, Weakening, Cut, LeftSubstEq, RightSubstEq} + import SimpleDeducedSteps.Restate + + TacticSubproof: + val leftRewrites = leftSubsts.get + val rightRewrites = rightSubsts.get + val leftRules = leftRewrites.to(VecSet).flatMap(_.rules) + val rightRules = rightRewrites.to(VecSet).flatMap(_.rules) + + // instantiated discharges + + val leftDischarges = leftRules.map(r => r -> proof.InstantiatedFact(sourceMap(r.rule), r.subst.asSubstPair)) + val rightDischarges = rightRules.map(r => r -> proof.InstantiatedFact(sourceMap(r.rule), r.subst.asSubstPair)) + + val discharges = leftDischarges ++ rightDischarges + + // start proof + have(andAll(premise.left) |- premise.right) by Restate.from(premiseStep) + + // left rewrites + val leftFormulas = leftRules.map(_.toFormula) + val preLeft = leftRewrites.map(_.toLeft) + val postLeft = leftRewrites.map(_.toRight) + val leftVars = leftRewrites.head.vars + val leftLambda = andAll(leftRewrites.map(_.lambda)) + thenHave(andAll(preLeft) |- premise.right) by Restate + thenHave(leftFormulas + andAll(preLeft) |- premise.right) by Weakening + thenHave(leftFormulas + andAll(postLeft) |- premise.right) by LeftSubstEq.withParameters(leftRules.map(r => r.l -> r.r).toSeq, leftVars -> leftLambda) + + val rpremise = lastStep.bot + + // right rewrites + val rightFormulas = rightRules.map(_.toFormula) + val preRight = rightRewrites.map(_.toLeft).toSet + val postRight = rightRewrites.map(_.toRight).toSet + val rightVars = rightRewrites.head.vars + val rightLambda = orAll(rightRewrites.map(_.lambda)) + thenHave(rpremise.left |- orAll(preRight)) by Restate + thenHave(rightFormulas ++ rpremise.left |- orAll(preRight)) by Weakening + thenHave(rightFormulas ++ rpremise.left |- orAll(postRight)) by RightSubstEq.withParameters(rightRules.map(r => r.l -> r.r).toSeq, rightVars -> rightLambda) + + // rewrite to destruct sequent + thenHave(postLeft ++ leftFormulas ++ rightFormulas |- postRight) by Restate + + val dpremise = lastStep.bot + + // discharge assumptions + discharges.foldLeft(dpremise): + case (premise, (rule, source)) => + val sseq = proof.getSequent(source) + val form = rule.toFormula + val nextSequent = premise.left - form ++ sseq.left |- premise.right ++ sseq.right - form + have(nextSequent) by Cut.withParameters(form)(source, lastStep) + nextSequent + + // restate to the result + thenHave(bot) by Weakening + + end Apply + + object Unfold extends ProofTactic: + def apply(using lib: Library, proof: lib.Proof)(definition: lib.theory.Definition)(premise: proof.Fact): proof.ProofTacticJudgement = + ??? + + + end Unfold + + // object applySubst extends ProofTactic { + + // private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) + + // private def findSubterm2(t: Term, subs: Seq[(Variable, Term)]): (Term, Boolean) = { + // val eq = subs.find(s => isSameTerm(t, s._2)) + // if (eq.nonEmpty) (eq.get._1, true) + // else { + // val induct = condflat(t.args.map(te => findSubterm2(te, subs))) + // if (!induct._2) (t, false) + // else + // (t.label.applySeq(induct._1), true) + + // } + + // } + // private def findSubterm2(f: Formula, subs: Seq[(Variable, Term)]): (Formula, Boolean) = { + // f match { + // case f: VariableFormula => (f, false) + // case f: ConstantFormula => (f, false) + // case AppliedPredicate(label, args) => + // val induct = condflat(args.map(findSubterm2(_, subs))) + // if (!induct._2) (f, false) + // else (AppliedPredicate(label, induct._1), true) + // case AppliedConnector(label, args) => + // val induct = condflat(args.map(findSubterm2(_, subs))) + // if (!induct._2) (f, false) + // else (AppliedConnector(label, induct._1), true) + // case BinderFormula(label, bound, inner) => + // val fv_in_f = subs.flatMap(e => e._2.freeVariables + e._1) + // if (!fv_in_f.contains(bound)) { + // val induct = findSubterm2(inner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, bound, induct._1), true) + // } else { + // val newv = Variable(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) + // val newInner = inner.substitute(bound := newv) + // val induct = findSubterm2(newInner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, newv, induct._1), true) + // } + // } + // } + + // private def findSubformula2(f: Formula, subs: Seq[(VariableFormula, Formula)]): (Formula, Boolean) = { + // val eq = subs.find(s => isSame(f, s._2)) + // if (eq.nonEmpty) (eq.get._1, true) + // else + // f match { + // case f: AtomicFormula => (f, false) + // case AppliedConnector(label, args) => + // val induct = condflat(args.map(findSubformula2(_, subs))) + // if (!induct._2) (f, false) + // else (AppliedConnector(label, induct._1), true) + // case BinderFormula(label, bound, inner) => + // val fv_in_f = subs.flatMap(_._2.freeVariables) + // if (!fv_in_f.contains(bound)) { + // val induct = findSubformula2(inner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, bound, induct._1), true) + // } else { + // val newv = Variable(freshId((f.freeVariables ++ fv_in_f).map(_.id), bound.id)) + // val newInner = inner.substitute(bound := newv) + // val induct = findSubformula2(newInner, subs) + // if (!induct._2) (f, false) + // else (BinderFormula(label, newv, induct._1), true) + // } + // } + // } + + // def findSubterm(t: Term, subs: Seq[(Variable, Term)]): Option[LambdaExpression[Term, Term, ?]] = { + // val vars = subs.map(_._1) + // val r = findSubterm2(t, subs) + // if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) + // else None + // } + + // def findSubterm(f: Formula, subs: Seq[(Variable, Term)]): Option[LambdaExpression[Term, Formula, ?]] = { + // val vars = subs.map(_._1) + // val r = findSubterm2(f, subs) + // if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) + // else None + // } + + // def findSubformula(f: Formula, subs: Seq[(VariableFormula, Formula)]): Option[LambdaExpression[Formula, Formula, ?]] = { + // val vars = subs.map(_._1) + // val r = findSubformula2(f, subs) + // if (r._2) Some(LambdaExpression(vars, r._1, vars.size)) + // else None + // } + + // def applyLeftRight(using lib: lisa.prooflib.Library, proof: lib.Proof)( + // phi: Formula + // )(premise: proof.Fact)(rightLeft: Boolean = false, toLeft: Boolean = true, toRight: Boolean = true): proof.ProofTacticJudgement = { + // import lisa.utils.K + // val originSequent = proof.getSequent(premise) + // val leftOrigin = AppliedConnector(And, originSequent.left.toSeq) + // val rightOrigin = AppliedConnector(Or, originSequent.right.toSeq) + + // if (!toLeft && !toRight) return proof.InvalidProofTactic("applyLeftRight called with no substitution selected (toLeft or toRight).") + + // phi match { + // case AppliedPredicate(label, args) if label == equality => + // val left = args(0) + // val right = args(1) + // val fv_in_phi = (originSequent.left ++ originSequent.right).flatMap(_.allSchematicLabels).map(_.id) + // val v = Variable(nFreshId(fv_in_phi, 1).head) + // lazy val isolatedLeft = originSequent.left.filterNot(f => isSame(f, phi)).map(f => (f, findSubterm(f, IndexedSeq(v -> left)))) + // lazy val isolatedRight = originSequent.right.map(f => (f, findSubterm(f, IndexedSeq(v -> left)))) + // if ((!toLeft || isolatedLeft.forall(_._2.isEmpty)) && (!toRight || isolatedRight.forall(_._2.isEmpty))) + // if (rightLeft) + // return proof.InvalidProofTactic(s"There is no instance of ${right} to replace.") + // else + // applyLeftRight(equality(right, left))(premise)(true, toLeft, toRight) match { + // case proof.InvalidProofTactic(m) => return proof.InvalidProofTactic(s"There is no instance of ${left} to replace.") + // case v: proof.ValidProofTactic => return v + // } + + // val leftForm = AppliedConnector(And, isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val rightForm = AppliedConnector(Or, isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val newleft = if (toLeft) isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.left + // val newright = if (toRight) isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.right + // val result1: Sequent = (AppliedConnector(And, newleft.toSeq), phi) |- rightOrigin + // val result2: Sequent = result1.left |- AppliedConnector(Or, newright.toSeq) + // var scproof: Seq[K.SCProofStep] = Seq(K.Restate((leftOrigin |- rightOrigin).underlying, -1)) + // if (toLeft) + // scproof = scproof :+ K.LeftSubstEq( + // result1.underlying, + // scproof.length - 1, + // List(K.LambdaTermTerm(Seq(), left.underlying) -> (K.LambdaTermTerm(Seq(), right.underlying))), + // (Seq(v.underlyingLabel), leftForm.underlying) + // ) + // if (toRight) + // scproof = scproof :+ K.RightSubstEq( + // result2.underlying, + // scproof.length - 1, + // List(K.LambdaTermTerm(Seq(), left.underlying) -> (K.LambdaTermTerm(Seq(), right.underlying))), + // (Seq(v.underlyingLabel), rightForm.underlying) + // ) + // val bot = newleft + phi |- newright + // scproof = scproof :+ K.Restate(bot.underlying, scproof.length - 1) + + // proof.ValidProofTactic( + // bot, + // scproof, + // Seq(premise) + // ) + + // case AppliedConnector(label, args) if label == Iff => + // val left = args(0) + // val right = args(1) + // val fv_in_phi = (originSequent.left ++ originSequent.right).flatMap(_.allSchematicLabels).map(_.id) + // val H = VariableFormula(nFreshId(fv_in_phi, 1).head) + // lazy val isolatedLeft = originSequent.left.filterNot(f => isSame(f, phi)).map(f => (f, findSubformula(f, IndexedSeq(H -> left)))) + // lazy val isolatedRight = originSequent.right.map(f => (f, findSubformula(f, IndexedSeq(H -> left)))) + // if ((!toLeft || isolatedLeft.forall(_._2.isEmpty)) && (!toRight || isolatedRight.forall(_._2.isEmpty))) + // if (rightLeft) + // return proof.InvalidProofTactic(s"There is no instance of ${right} to replace.") + // else + // applyLeftRight(Iff(right, left))(premise)(true, toLeft, toRight) match { + // case proof.InvalidProofTactic(m) => return proof.InvalidProofTactic(s"There is no instance of ${left} to replace.") + // case v: proof.ValidProofTactic => return v + // } + + // val leftForm = AppliedConnector(And, isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val rightForm = AppliedConnector(Or, isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.body).toSeq) + // val newleft = if (toLeft) isolatedLeft.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.left + // val newright = if (toRight) isolatedRight.map((f, ltf) => if (ltf.isEmpty) f else ltf.get.applyUnsafe(Seq(right))) else originSequent.right + // val result1: Sequent = (AppliedConnector(And, newleft.toSeq), phi) |- rightOrigin + // val result2: Sequent = result1.left |- AppliedConnector(Or, newright.toSeq) + + // var scproof: Seq[K.SCProofStep] = Seq(K.Restate((leftOrigin |- rightOrigin).underlying, -1)) + // if (toLeft) + // scproof = scproof :+ K.LeftSubstIff( + // result1.underlying, + // scproof.length - 1, + // List(K.LambdaTermFormula(Seq(), left.underlying) -> (K.LambdaTermFormula(Seq(), right.underlying))), + // (Seq(H.underlyingLabel), leftForm.underlying) + // ) + // if (toRight) + // scproof = scproof :+ K.RightSubstIff( + // result2.underlying, + // scproof.length - 1, + // List(K.LambdaTermFormula(Seq(), left.underlying) -> (K.LambdaTermFormula(Seq(), right.underlying))), + // (Seq(H.underlyingLabel), rightForm.underlying) + // ) + + // val bot = newleft + phi |- newright + // scproof = scproof :+ K.Restate(bot.underlying, scproof.length - 1) + + // proof.ValidProofTactic( + // bot, + // scproof, + // Seq(premise) + // ) + // case _ => proof.InvalidProofTactic(s"Formula in applySingleSimp need to be of the form a=b or q<=>p and not ${phi}") + // } + // } + + // @nowarn("msg=.*the type test for proof.Fact cannot be checked at runtime*") + // def apply(using + // lib: lisa.prooflib.Library, + // proof: lib.Proof, + // line: sourcecode.Line, + // file: sourcecode.File + // )(f: proof.Fact | Formula, rightLeft: Boolean = false, toLeft: Boolean = true, toRight: Boolean = true)( + // premise: proof.Fact + // ): proof.ProofTacticJudgement = { + // f match { + // case phi: Formula => applyLeftRight(phi)(premise)(rightLeft, toLeft, toRight) + // case f: proof.Fact => + // val seq = proof.getSequent(f) + // val phi = seq.right.head + // val sp = TacticSubproof { + // val x = applyLeftRight(phi)(premise)(rightLeft, toLeft, toRight) + // proof.library.have(x) + // proof.library.andThen(SimpleDeducedSteps.Discharge(f)) + // } + + // BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") + // } + + // } + + // def toLeft(using lib: lisa.prooflib.Library, proof: lib.Proof, line: sourcecode.Line, file: sourcecode.File)(f: proof.Fact | Formula, rightLeft: Boolean = false)( + // premise: proof.Fact + // ): proof.ProofTacticJudgement = apply(f, rightLeft, toLeft = true, toRight = false)(premise) + + // def toRight(using lib: lisa.prooflib.Library, proof: lib.Proof, line: sourcecode.Line, file: sourcecode.File)(f: proof.Fact | Formula, rightLeft: Boolean = false)( + // premise: proof.Fact + // ): proof.ProofTacticJudgement = apply(f, rightLeft, toLeft = false, toRight = true)(premise) + + // } + +end Substitution diff --git a/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala b/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala new file mode 100644 index 000000000..894ebc592 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala @@ -0,0 +1,489 @@ +package lisa.automation +import lisa.fol.FOL as F +import lisa.prooflib.Library +import lisa.prooflib.OutputManager.* +import lisa.prooflib.ProofTacticLib.* +import lisa.utils.K +import lisa.utils.K.{_, given} + +import scala.collection.immutable.HashMap +import scala.collection.immutable.HashSet + +/** + * Now need to deal with variables unifying with terms containing themselves + * optimiye list siye computation + * Then, optimize unification check by not checking all pairs all the time + * Then, shortcut branches by checking if they are OL-true or OL-false + * + * Next test: No quantifiers but actual terms with variables + */ + +object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { + + var debug = true + def pr(s: Object) = if debug then println(s) + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + solve(bot) match { + case Some(value) => proof.ValidProofTactic(bot, value.steps, Seq()) + case None => proof.InvalidProofTactic("Could not prove the statement.") + } + } + + /** + * Given a targeted conclusion sequent, try to prove it using laws of propositional logic and reflexivity and symmetry of equality. + * Uses the given already proven facts as assumptions to reach the desired goal. + * + * @param proof The ongoing proof object in which the step happens. + * @param premise A previously proven step necessary to reach the conclusion. + * @param bot The desired conclusion. + */ + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + from(using lib, proof)(Seq(premise)*)(bot) + + def from(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val premsFormulas: Seq[((proof.Fact, Expression), Int)] = premises.map(p => (p, sequentToFormula(proof.getSequent(p).underlying))).zipWithIndex + val initProof = premsFormulas.map(s => Restate(() |- s._1._2, -(1 + s._2))).toList + val sqToProve = botK ++<< (premsFormulas.map(s => s._1._2).toSet |- ()) + + solve(sqToProve) match { + case Some(value) => + val subpr = SCSubproof(value) + val stepsList = premsFormulas.foldLeft[List[SCProofStep]](List(subpr))((prev: List[SCProofStep], cur) => { + val ((prem, form), position) = cur + Cut(prev.head.bot -<< form, position, initProof.length + prev.length - 1, form) :: prev + }) + val steps = (initProof ++ stepsList.reverse).toIndexedSeq + proof.ValidProofTactic(bot, steps, premises) + case None => + proof.InvalidProofTactic("Could not prove the statement.") + } + } + + inline def solve(sequent: F.Sequent): Option[SCProof] = solve(sequent.underlying) + + def solve(sequent: K.Sequent): Option[SCProof] = { + val f = K.multiand(sequent.left.toSeq ++ sequent.right.map(f => K.neg(f))) + val taken = f.allVariables + val nextIdNow = if taken.isEmpty then 0 else taken.maxBy(_.id.no).id.no + 1 + val (fnamed, nextId) = makeVariableNamesUnique(f, nextIdNow, f.freeVariables) + val nf = reducedNNFForm(fnamed) + val uv = Variable(Identifier("§", nextId), Term) + val proof = decide(Branch.empty(nextId + 1, uv).prepended(nf)) + proof match + case None => None + case Some((p, _)) => Some(SCProof((Restate(sequent, p.length) :: Weakening(nf |- (), p.length - 1) :: p).reverse.toIndexedSeq, IndexedSeq.empty)) + + } + + /** + * A branch represent a sequent (whose right hand side is empty) that is being proved. + * It is assumed that the sequent is in negation normal form, negations are only applied to atoms. + * Formulas are sorted according to their shape : + * Conjunctions are in alpha + * Disjunctions are in beta + * Existential quantifiers are in delta + * Universal quantifiers are in gamma + * Atoms are in atoms (split into positive and negative) + * At each step of the procedure, a formula is deconstructed in accordance with the rules of the tableau calculus. + * Then that formula is removed from the branch as it is no longer needed. + * Variables coming from universal quantifiers are marked as suitable for unification in unifiable + * Instantiations that have been done already are stored in triedInstantiation, to avoid infinite loops. + * When a quantifier Q1 is below a universal quantifier Q2, Q2 can be instantiated multiple times. + * Then, Q1 may also need to be instantiated multiple versions, requiring fresh variable names. + * maxIndex stores an index that is used to generate fresh variable names. + */ + case class Branch( + alpha: List[Expression], // label = And + beta: List[Expression], // label = Or + delta: List[Expression], // Exists(...)) + gamma: List[Expression], // Forall(...) + atoms: (List[Expression], List[Expression]), // split into positive and negatives! + unifiable: Map[Variable, (Expression, Int)], // map between metavariables and the original formula they came from, with the penalty associated to the complexity of the formula. + numberInstantiated: Map[Variable, Int], // map between variables and the number of times they have been instantiated + + skolemized: Set[Variable], // set of variables that have been skolemized + triedInstantiation: Map[Variable, Set[Expression]], // map between metavariables and the term they were already instantiated with + maxIndex: Int, // the maximum index used for skolemization and metavariables + varsOrder: Map[Variable, Int], // the order in which variables were instantiated. In particular, if the branch contained the formula ∀x. ∀y. ... then x > y. + unusedVar: Variable // a variable the is neither free nor bound in the original formula. + ) { + def pop(f: Expression): Branch = f match + case f @ Or(l, r) => + if (beta.nonEmpty && beta.head.uniqueNumber == f.uniqueNumber) copy(beta = beta.tail) else throw Exception("First formula of beta is not f") + case f @ Exists(x, inner) => + if (delta.nonEmpty && delta.head.uniqueNumber == f.uniqueNumber) copy(delta = delta.tail) else throw Exception("First formula of delta is not f") + case f @ Forall(x, inner) => + if (gamma.nonEmpty && gamma.head.uniqueNumber == f.uniqueNumber) copy(gamma = gamma.tail) else throw Exception("First formula of gamma is not f") + case And(left, right) => + if (alpha.nonEmpty && alpha.head.uniqueNumber == f.uniqueNumber) copy(alpha = alpha.tail) else throw Exception("First formula of alpha is not f") + case _ => + throw Exception("Should not pop Atoms: " + f.repr) + + def prepended(f: Expression): Branch = f match + case And(left, right) => this.copy(alpha = f :: alpha) + case Or(left, right) => this.copy(beta = f :: beta) + case Exists(x, inner) => this.copy(delta = f :: delta) + case Forall(x, inner) => this.copy(gamma = f :: gamma) + case Neg(f) => + this.copy(atoms = (atoms._1, f :: atoms._2)) + case _ => + this.copy(atoms = (f :: atoms._1, atoms._2)) + + def prependedAll(l: Seq[Expression]): Branch = l.foldLeft(this)((a, b) => a.prepended(b)) + + def asSequent: Sequent = (beta ++ delta ++ gamma ++ atoms._1 ++ atoms._2.map(a => !a)).toSet |- Set() // inefficient, not used + + import Branch.* + override def toString(): String = + val pretUnif = unifiable.map((x, f) => x.id + " -> " + f._1.repr + " : " + f._2).mkString("Unif(", ", ", ")") + // val pretTried = triedInstantiation.map((x, t) => x.id + " -> " + prettyTerm(t, true)).mkString("Tried(", ", ", ")") + (s"Branch(" + + s"${RED(prettyIte(alpha, "alpha"))}, " + + s"${GREEN(prettyIte(beta, "beta"))}, " + + s"${BLUE(prettyIte(delta, "delta"))}, " + + s"${YELLOW(prettyIte(gamma, "gamma"))}, " + + s"${MAGENTA(prettyIte(atoms._1, "+"))}, ${CYAN(prettyIte(atoms._2, "-"))}, " + + s"$pretUnif, _, _)").split("'").mkString("").split("_").mkString("") + + } + object Branch { + def empty = Branch(Nil, Nil, Nil, Nil, (Nil, Nil), Map.empty, Map.empty, Set.empty, Map.empty, 1, Map.empty, Variable(Identifier("§uv", 0), Term)) + def empty(n: Int, uv: Variable) = Branch(Nil, Nil, Nil, Nil, (Nil, Nil), Map.empty, Map.empty, Set.empty, Map.empty, n, Map.empty, uv) + def prettyIte(l: Iterable[Expression], head: String): String = l match + case Nil => "Nil" + case _ => l.map(_.repr).mkString(head + "(", ", ", ")") + + } + + def makeVariableNamesUnique(f: Expression, nextId: Int, seen2: Set[Variable]): (Expression, Int) = { + var nextId2: Int = nextId + var seen = seen2 + def recurse(f: Expression): Expression = f match + case Application(f, a) => + Application(recurse(f), recurse(a)) + case Lambda(v, body) => + if seen.contains(v) then + val newV = Variable(Identifier(v.id, nextId2), Term) + nextId2 += 1 + Lambda(newV, substituteVariables(recurse(body), Map(v -> newV))) + else + seen += v + Lambda(v, recurse(body)) + case _ => f + (recurse(f), nextId2) + } + type Substitution = Map[Variable, Expression] + val Substitution = HashMap + def prettySubst(s: Substitution): String = s.map((x, t) => x.id + " -> " + t.repr).mkString("Subst(", ", ", ")") + + /** + * Detect if two terms can be unified, and if so, return a substitution that unifies them. + */ + def unify(t1: Expression, t2: Expression, current: Substitution, br: Branch): Option[Substitution] = (t1, t2) match + case (x: Variable, y: Variable) if (br.unifiable.contains(x) || x.id.no > br.maxIndex) && (br.unifiable.contains(y) || y.id.no > br.maxIndex) => + if x == y then Some(current) + else if current.contains(x) then unify(current(x), t2, current, br) + else if current.contains(y) then unify(t1, current(y), current, br) + else Some(current + (x -> y)) + case (x: Variable, t2: Expression) if br.unifiable.contains(x) || x.id.no > br.maxIndex => + val newt2 = substituteVariables(t2, current) + if newt2.freeVariables.contains(x) then None + else if (current.contains(x)) unify(current(x), newt2, current, br) + else Some(current + (x -> newt2)) + case (t1: Expression, y: Variable) if br.unifiable.contains(y) || y.id.no > br.maxIndex => + val newt1 = substituteVariables(t1, current) + if newt1.freeVariables.contains(y) then None + else if (current.contains(y)) unify(newt1, current(y), current, br) + else Some(current + (y -> newt1)) + case (Application(f1, a1), Application(f2, a2)) => + unify(f1, f2, current, br).flatMap(s => unify(a1, a2, s, br)) + case _ => if t1 == t2 then Some(current) else None + + /** + * Detect if two atoms can be unified, and if so, return a substitution that unifies them. + */ + def unifyPred(pos: Expression, neg: Expression, br: Branch): Option[Substitution] = { + assert(pos.sort == Formula && neg.sort == Formula) + unify(pos, neg, Substitution.empty, br) + + } + + /** + * Detect if a branch can be closed, and if so, return a list of substitutions that closes it along with the formulas used to close it + * If it can't be closed, returns None + * The substitution cannot do substitutions that were already done in branch.triedInstantiation. + * When multiple substitutions are possible, the one with the smallest size is returned. (Maybe there is a better heuristic, like distance from the root?) + */ + def close(branch: Branch): Option[(Substitution, Set[Expression])] = { + val newMap = branch.atoms._1 + .flatMap(pred => pred.freeVariables.filter(v => branch.unifiable.contains(v))) + .map(v => v -> Variable(Identifier(v.id.name, v.id.no + branch.maxIndex + 1), Term)) + .toMap + val inverseNewMap = newMap.map((k, v) => v -> k).toMap + val pos = branch.atoms._1.map(pred => substituteVariables(pred, newMap)).iterator + var substitutions: List[(Substitution, Set[Expression])] = Nil + + while (pos.hasNext) { + val p = pos.next() + if (p == bot) return Some((Substitution.empty, Set(bot))) + val neg = branch.atoms._2.iterator + while (neg.hasNext) { + val n = neg.next() + unifyPred(p, n, branch) match + case None => () + case Some(unif) => + substitutions = (unif, Set(p, !n)) :: substitutions + } + } + + val cr1 = substitutions.map((sub, set) => + ( + sub.flatMap((v, t) => + if v.id.no > branch.maxIndex then + if t == inverseNewMap(v) then None + else Some(inverseNewMap(v) -> substituteVariables(t, inverseNewMap.map((v, t) => v -> substituteVariables(t, sub)))) + else if newMap.contains(v) && t == newMap(v) then None + else Some(v -> substituteVariables(t, inverseNewMap)) + ), + set.map(f => substituteVariables(f, inverseNewMap)) + ) + ) + + val cr = cr1.filterNot(s => + s._1.exists((x, t) => + val v = branch.triedInstantiation.contains(x) && branch.triedInstantiation(x).contains(t) + v + ) + ) + + bestSubst(cr, branch) + + } + + def bestSubst(substs: List[(Substitution, Set[Expression])], branch: Branch): Option[(Substitution, Set[Expression])] = { + if substs.isEmpty then return None + val minSize = substs.minBy(_._1.size) + val smallSubst = substs.filter(_._1.size == minSize._1.size) + // Up to this, it is necessary for completeness. From this, it is heuristic. + + val best = smallSubst.minBy(s => substitutionScore(s._1, branch)) + Some(best) + } + def formulaPenalty(f: Expression, branch: Branch): Int = f match + case And(left, right) => 10 + formulaPenalty(left, branch) + formulaPenalty(right, branch) + case Or(left, right) => 40 + formulaPenalty(left, branch) + formulaPenalty(right, branch) + case Exists(x, inner) => 30 + formulaPenalty(inner, branch) + case Forall(x, inner) => 200 + formulaPenalty(inner, branch) + case _ => 0 + + def substitutionScore(subst: Substitution, branch: Branch): Int = { + def pairPenalty(v: Variable, t: Expression) = { + val variablePenalty = branch.unifiable(v)._2 + branch.numberInstantiated(v) * 20 + def termPenalty(t: Expression): Int = t match + case x: Variable => if branch.unifiable.contains(x) then branch.unifiable(x)._2 * 1 else 0 + case c: Constant => 40 + case Application(f, a) => 100 + termPenalty(f) + termPenalty(a) + case Lambda(v, inner) => 100 + termPenalty(inner) + 1*variablePenalty + 1*termPenalty(t) + } + subst.map((v, t) => pairPenalty(v, t)).sum + } + + /** + * Explodes one And formula + * The alpha list of the branch must not be empty + */ + def alpha(branch: Branch): Branch = { + val f = branch.alpha.head + f match + case And(l, r) => branch.copy(alpha = branch.alpha.tail).prepended(l).prepended(r) + case _ => throw Exception("Error: First formula of alpha is not an And") + } + + /** + * Explodes one Or formula, and alpha-simplifies it + * Add the exploded formula to the used list, if one beta formula is found + * The beta list of the branch must not be empty + */ + def beta(branch: Branch): List[(Branch, Expression)] = { + val f = branch.beta.head + val b1 = branch.copy(beta = branch.beta.tail) + f match + case Or(l, r) => + List((b1.prepended(l), l), (b1.prepended(r), r)) + case _ => throw Exception("Error: First formula of beta is not an Or") + } + + /** + * Explodes one Exists formula + * Add the unquantified formula to the branch + * Since the bound variable is not marked as suitable for instantiation, it behaves as a constant symbol (skolem) + */ + def delta(branch: Branch): (Branch, Variable, Expression) = { + val f = branch.delta.head + f match + case Exists(v, body) => + if branch.skolemized.contains(v) then + val newV = Variable(Identifier(v.id.name, branch.maxIndex), Term) + val newInner = substituteVariables(body, Map(v -> newV)) + (branch.copy(delta = branch.delta.tail, maxIndex = branch.maxIndex + 1).prepended(newInner), newV, newInner) + else (branch.copy(delta = branch.delta.tail, skolemized = branch.skolemized + v).prepended(body), v, body) + case _ => throw Exception("Error: First formula of delta is not an Exists") + } + + /** + * Explodes one Forall formula + * Add the unquantified formula to the branch and mark the bound variable as suitable for unification + * This step will most of the time be cancelled when building the proof, unless any arbitrary instantiation is sufficient to get a proof. + */ + def gamma(branch: Branch): (Branch, Variable, Expression) = { + val f = branch.gamma.head + f match + case Forall(v, body) => + val (ni, nb) = branch.unifiable.get(v) match + case None => + (body, v) + case Some(value) => + val newBound = Variable(Identifier(v.id.name, branch.maxIndex), Term) + val newInner = substituteVariables(body, Map(v -> newBound)) + (newInner, newBound) + val b1 = branch.copy( + gamma = branch.gamma.tail, + unifiable = branch.unifiable + (nb -> (f, formulaPenalty(body, branch))), + numberInstantiated = branch.numberInstantiated + (nb -> (branch.numberInstantiated.getOrElse(v, 0))), + maxIndex = branch.maxIndex + 1, + varsOrder = branch.varsOrder + (nb -> branch.varsOrder.size) + ) + (b1.prepended(ni), nb, ni) + case _ => throw Exception("Error: First formula of gamma is not a Forall") + + + } + + /** + * When a closing unification has been found, apply it to the branch + * This does not do backtracking: The metavariable remains available if it needs further instantiation. + */ + def applyInst(branch: Branch, x: Variable, t: Expression): (Branch, Expression) = { + val f = branch.unifiable(x)._1 + val newTried = branch.triedInstantiation.get(x) match + case None => branch.triedInstantiation + (x -> Set(t)) + case Some(s) => branch.triedInstantiation + (x -> (s + t)) + + val inst = f match + case Forall(v, body) => instantiate(body, v, t) + case _ => throw Exception("Error: Formula in unifiable is not a Forall") + val r = branch + .prepended(inst) + .copy( + triedInstantiation = newTried, + numberInstantiated = branch.numberInstantiated + (x -> (branch.numberInstantiated(x) + 1)) + ) + (r, inst) + } + + /** + * Decide if a branch can be closed, and if not, explode it. + * Main routine of the decision procedure. If it succeeds, return a proof of the branch. + * Note that the proof actually proves a subset of a branch when possible, to cut short on unneeded steps and formulas. + * The return integer is the size of the proof: Used to avoid computing the size every time in linear time. + */ + def decide(branch: Branch): Option[(List[SCProofStep], Int)] = { + + val closeSubst = close(branch) + if (closeSubst.nonEmpty && closeSubst.get._1.isEmpty) // If branch can be closed without Instantiation (Hyp) + Some((List(RestateTrue(Sequent(closeSubst.get._2, Set()))), 0)) + else if (branch.alpha.nonEmpty) // If branch contains an Alpha formula (LeftAnd) + val rec = alpha(branch) + decide(rec).map((proof, step) => + branch.alpha.head match + case Application(Application(and, left), right) => + + if proof.head.bot.left.contains(left) || proof.head.bot.left.contains(right) then + val sequent = proof.head.bot.copy(left = (proof.head.bot.left - left - right) + branch.alpha.head) + (Weakening(sequent, proof.size - 1) :: proof, step + 1) + else (proof, step) + case _ => throw Exception("Error: First formula of alpha is not an And") + ) + else if (branch.delta.nonEmpty) // If branch contains a Delta formula (LeftExists) + val rec = delta(branch) + val upperProof = decide(rec._1) + upperProof.map((proof, step) => + if proof.head.bot.left.contains(rec._3) then + val sequent = (proof.head.bot -<< rec._3) +<< branch.delta.head + (LeftExists(sequent, step, rec._3, rec._2) :: proof, step + 1) + else (proof, step) + ) + else if (branch.beta.nonEmpty) // If branch contains a Beta formula (LeftOr) + val list = beta(branch) + val (proof, treversed, needed) = list.foldLeft((Some(Nil): Option[List[SCProofStep]], Nil: List[Int], true: Boolean))((prev, next) => + prev match + case (None, _, _) => prev // proof failed + case (_, _, false) => + prev // proof succeded early + case (Some(prevProof), t, true) => + val res = decide(next._1) + res match + case None => (None, t, true) + case Some((nextProof, step)) => + if nextProof.head.bot.left.contains(next._2) then // If the disjunct was used, encapsulate the subbranch in a Subproof + val subproofDisj = + if nextProof.size == 1 then nextProof.head + else SCSubproof(SCProof(nextProof.toIndexedSeq.reverse, IndexedSeq.empty), IndexedSeq.empty) + (Some(subproofDisj :: prevProof), prevProof.size :: t, true) + else + // If the disjunct was not used, then the subbranch is a proof of the whole statement and the split is not necessary. + (res.map(_._1), List(nextProof.size - 1), false) + ) + proof.map(proo => + if needed == true then + val sequent = ((proo.reverse.zip(list).flatMap((proof, bf) => proof.bot.left - bf._2).toSet + branch.beta.head) |- ()) + branch.beta.head match + case Or(left, right) => + (LeftOr(sequent, treversed.reverse, Seq(left, right)) :: proo, treversed.size) + case _ => throw Exception("Error: First formula of beta is not an Or") + else (proo, proo.size - 1) + ) + else if (branch.gamma.nonEmpty) // If branch contains a Gamma formula (LeftForall) + val rec = gamma(branch) + val upperProof = decide(rec._1) + // LeftForall(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) + upperProof.map((proof, step) => + if proof.head.bot.left.contains(rec._3) then + val sequent = (proof.head.bot -<< rec._3) +<< branch.gamma.head + branch.gamma.head match + case Forall(v, body) => + (LeftForall(sequent, step, body, v, rec._2()) :: proof, step + 1) + case _ => throw Exception("Error: First formula of gamma is not a Forall") + else (proof, step) + ) + else if (closeSubst.nonEmpty && closeSubst.get._1.nonEmpty) // If branch can be closed with Instantiation (LeftForall) + val (x, t) = closeSubst.get._1.minBy((x, t) => branch.varsOrder(x)) + val (recBranch, instantiated) = applyInst(branch, x, t) + val upperProof = decide(recBranch) + upperProof.map((proof, step) => + if proof.head.bot.left.contains(instantiated) then + val sequent = (proof.head.bot -<< instantiated) +<< branch.unifiable(x)._1 + branch.unifiable(x)._1 match + case Forall(v, body) => + (LeftForall(sequent, step, body, v, t) :: proof, step + 1) + case _ => throw Exception("Error: Formula in unifiable is not a Forall") + else (proof, step) + ) + else None + // End of decide + } + + def containsAlpha(set: Set[Expression], f: Expression): Boolean = f match { + case And(left, right) => containsAlpha(set, left) || containsAlpha(set, right) + case _ => set.contains(f) + } + + def instantiate(f: Expression, x: Variable, t: Expression): Expression = f match + case v: Variable => if v == x then t else v + case c: Constant => c + case Application(f, a) => Application(instantiate(f, x, t), instantiate(a, x, t)) + case Lambda(v, inner) => if (v == x) f else Lambda(v, instantiate(inner, x, t)) +} diff --git a/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala b/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala new file mode 100644 index 000000000..6627ec9f1 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala @@ -0,0 +1,206 @@ +package lisa.automation + +import lisa.fol.FOL as F +import lisa.prooflib.Library +import lisa.prooflib.ProofTacticLib.* +import lisa.utils.K.{_, given} + +/** + * A tactic object dedicated to solve any propositionaly provable sequent (possibly in exponential time). Can be used with arbitrary many premises. + * Leverages the OL algorithm for scalafmpropositional logic. + */ +object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { + + /** + * Given a targeted conclusion sequent, try to prove it using laws of propositional logic and reflexivity and symmetry of equality. + * + * @param proof The ongoing proof object in which the step happens. + * @param bot The desired conclusion. + */ + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + solveSequent(botK) match { + case Left(value) => proof.ValidProofTactic(bot, value.steps, Seq()) + case Right((msg, seq)) => proof.InvalidProofTactic(msg) + } + } + + /** + * Given a targeted conclusion sequent, try to prove it using laws of propositional logic and reflexivity and symmetry of equality. + * Uses the given already proven facts as assumptions to reach the desired goal. + * + * @param proof The ongoing proof object in which the step happens. + * @param premise A previously proven step necessary to reach the conclusion. + * @param bot The desired conclusion. + */ + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + from(using lib, proof)(Seq(premise)*)(bot) + + def from(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val premsFormulas: Seq[((proof.Fact, Expression), Int)] = premises.map(p => (p, sequentToFormula(proof.getSequent(p).underlying))).zipWithIndex + val initProof = premsFormulas.map(s => Restate(() |- s._1._2, -(1 + s._2))).toList + val sqToProve = botK ++<< (premsFormulas.map(s => s._1._2).toSet |- ()) + + solveSequent(sqToProve) match { + case Left(value) => + val subpr = SCSubproof(value) + val stepsList = premsFormulas.foldLeft[List[SCProofStep]](List(subpr))((prev: List[SCProofStep], cur) => { + val ((prem, form), position) = cur + if prev.head.bot.left.contains(form) then Cut(prev.head.bot -<< form, position, initProof.length + prev.length - 1, form) :: prev + else prev + }) + val steps = (initProof ++ stepsList.reverse).toIndexedSeq + proof.ValidProofTactic(bot, steps, premises) + case Right((msg, seq)) => + proof.InvalidProofTactic(msg) + } + } + + /** + * This function returns a proof of the given sequent if such a proof exists using only the rules of propositional logic and reflexivity and symmetry of equality. + * Be aware that the runtime and size of the proof may be exponential in the number of atoms (i.e. number of non-propositional subformulas of the input). + * The strategy consists in leveraging OL formula reduction by alternating between branching on an atom and reducing the formula. + * @param s A sequent that should be a propositional logic tautology. It can contain binders and schematic connector symbols, but they will be treated as atoms. + * @return A proof of the given sequent, if it exists + */ + def solveSequent(s: Sequent): Either[SCProof, (String, Sequent)] = { + val augSeq = augmentSequent(s) + val MaRvIn = Variable(freshId(augSeq.formula.freeVariables.map(_.id), "MaRvIn"), Formula) // arbitrary name that is unlikely to already exist in the formula + + try { + val steps = solveAugSequent(augSeq, 0)(using MaRvIn) + Left(SCProof((Restate(s, steps.length - 1) :: steps).reverse.toIndexedSeq)) + } catch + case e: NoProofFoundException => + Right( + ( + "The statement may be incorrect or not provable within propositional logic.\n" + + "The proof search failed because it needed the truth of the following sequent:\n" + + s"${e.unsolvable.repr}", + e.unsolvable + ) + ) + + } + + // From there, private code. + + // Augmented Sequent + private case class AugSequent(decisions: (List[Expression], List[Expression]), formula: Expression) + + // Transform a sequent into a format more adequate for solving + private def augmentSequent(s: Sequent): AugSequent = { + val f = reducedForm(sequentToFormula(s)) + val atoms: scala.collection.mutable.Map[Expression, Int] = scala.collection.mutable.Map.empty + AugSequent((Nil, Nil), f) + } + + def reduceSequent(s: Sequent): Expression = { + val p = simplify(sequentToFormula(s)) + val nf = computeNormalForm(p) + val fln = fromLocallyNameless(nf, Map.empty, 0) + val res = toExpressionAIG(fln) + res + } + + // Find all "atoms" of the formula. + // We mean atom in the propositional logic sense, so any formula starting with a predicate symbol, a binder or a schematic connector is an atom here. + def findBestAtom(f: Expression): Option[Expression] = { + val atoms: scala.collection.mutable.Map[Expression, Int] = scala.collection.mutable.Map.empty + def findAtoms2(fi: Expression, add: Expression => Unit): Unit = fi match { + case And(f1, f2) => findAtoms2(f1, add); findAtoms2(f2, add) + case Neg(f1) => findAtoms2(f1, add) + case _ if fi != top && fi != bot => add(fi) + case _ => throw new Exception(s"Unreachable case in findBestAtom. \ninner: ${fi.repr},\n outer: ${f.repr}") + } + findAtoms2(f, a => atoms.update(a, { val g = atoms.get(a); if (g.isEmpty) 1 else g.get + 1 })) + if (atoms.isEmpty) None else Some(atoms.toList.maxBy(_._2)._1) + } + + private class NoProofFoundException(val unsolvable: Sequent) extends Exception + + // Given a sequent, return a proof of that sequent if on exists that only uses propositional logic rules and reflexivity of equality. + // Alternates between reducing the formulas using the OL algorithm for propositional logic and branching on an atom using excluded middle. + // An atom is a subformula of the input that is either a predicate, a binder or a schematic connector, i.e. a subformula that has not meaning in propositional logic. + private def solveAugSequent(s: AugSequent, offset: Int)(using MaRvIn: Variable): List[SCProofStep] = { + val redF = reducedForm(s.formula) + if (redF == top()) { + List(RestateTrue(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- s.formula)) + } else + val bestAtom = findBestAtom(redF) + if (bestAtom.isEmpty) { + assert(redF == bot()) // sanity check; If the formula has no atom left in it and is reduced, it should be either ⊤ or ⊥. + val res = s.decisions._1 |- redF :: s.decisions._2 // the branch that can't be closed + throw new NoProofFoundException(res) + } else { + val atom = bestAtom.get + val optLambda = findSubformula(redF, MaRvIn, atom) + if (optLambda.isEmpty) return solveAugSequent(AugSequent(s.decisions, redF), offset) + val lambdaF = optLambda.get + + val seq1 = AugSequent((atom :: s.decisions._1, s.decisions._2), substituteVariables(lambdaF, Map(MaRvIn -> top))) + val proof1 = solveAugSequent(seq1, offset) + val subst1 = RightSubstIff( + atom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, + offset + proof1.length - 1, + Seq((atom, top)), + (Seq(MaRvIn), lambdaF) + ) + val negatom = neg(atom) + val seq2 = AugSequent((negatom :: s.decisions._1, s.decisions._2), substituteVariables(lambdaF, Map(MaRvIn -> bot))) + val proof2 = solveAugSequent(seq2, offset + proof1.length + 1) + val subst2 = RightSubstIff( + negatom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, + offset + proof1.length + proof2.length + 1 - 1, + Seq((atom, bot)), + (Seq(MaRvIn), lambdaF) + ) + val red2 = Restate(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- (redF, atom), offset + proof1.length + proof2.length + 2 - 1) + val cutStep = Cut(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, offset + proof1.length + proof2.length + 3 - 1, offset + proof1.length + 1 - 1, atom) + val redStep = Restate(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- s.formula, offset + proof1.length + proof2.length + 4 - 1) + redStep :: cutStep :: red2 :: subst2 :: proof2 ++ (subst1 :: proof1) + + } + } + + + + + + private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) + + private def findSubformula2(outer: Expression, x: Variable, e: Expression, fv: Set[Variable]): (Expression, Boolean) = { + if (isSame(outer, e)) (x, true) + else + val res = outer match { + case Application(f, arg) => + val rf = findSubformula2(f, x, e, fv) + val ra = findSubformula2(arg, x, e, fv) + if (rf._2 || ra._2) (Application(rf._1, ra._1), true) + else (outer, false) + case Lambda(v, inner) => + if (!fv.contains(v)) { + val induct = findSubformula2(inner, x, e, fv) + if (!induct._2) (outer, false) + else (Lambda(v, induct._1), true) + } else { + val newv = Variable(freshId((outer.freeVariables ++ fv).map(_.id), v.id), v.sort) + val newInner = substituteVariables(inner, Map(v -> newv)) + val induct = findSubformula2(newInner, x, e, fv + newv) + if (!induct._2) (outer, false) + else (Lambda(newv, induct._1), true) + } + case _ => (outer, false) + } + //assert(res._1.sort == f.sort, s"Sort mismatch in findSubformula2. ${res._1.repr} : ${res._1.sort} != ${f.repr} : ${f.sort}") + res + } + + def findSubformula(f: Expression, x: Variable, e: Expression): Option[Expression] = { + val r = findSubformula2(f, x, e, e.freeVariables) + if (r._2) Some(r._1) + else None + } + +} diff --git a/lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala b/lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala new file mode 100644 index 000000000..dc829075d --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala @@ -0,0 +1,121 @@ +package lisa.automation.atp +import lisa.fol.FOL as F +import lisa.prooflib.Library +import lisa.prooflib.OutputManager +import lisa.prooflib.ProofTacticLib.* +import lisa.utils.K +import lisa.utils.tptp.* + +import java.io.* +import scala.io.Source +import scala.util.Failure +import scala.util.Success +import scala.util.Try + +import ProofParser.* +import KernelParser.* +import sys.process._ + +/** + * Goéland is an automated theorem prover. This tactic calls the Goéland prover to solve the current sequent. + * Goéland is only available on Linux yet, but proofs generated by Goéland should be kept in the library for future use. + * To ensure that proofs are published and can be replayed in any system, proofs from an ATPcan only be generated in draft mode. + * When in non-draft mode, the proof file should be given as an argument to the tactic (the exact file is provided by Lisa upon run without draft mode). + */ +object Goeland extends ProofTactic with ProofSequentTactic { + private var i : Int = 0 + + val goelandExec = "../bin/goeland_linux_release" + + class OsNotSupportedException(msg: String) extends Exception(msg) + + val foldername = "goeland/" + + /** + * Fetch a proof of a sequent that was previously proven by Goéland. + * The file must be in SC-TPTP format. + */ + def apply(using lib: Library, proof: lib.Proof)(file:String)(bot: F.Sequent): proof.ProofTacticJudgement = { + val outputname = proof.owningTheorem.fullName+"_sol" + try { + val scproof = reconstructProof(new File(foldername+outputname+".p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable) + proof.ValidProofTactic(bot, scproof.steps, Seq()) + } catch { + case e: FileNotFoundException => + throw FileNotFoundException("The file "+foldername+outputname+".p was not found. To produce a proof, use `by Goeland`. ") + case e => throw e + } + } + + + /** + * Solve a sequent using the Goéland automated theorem prover. + * At the moment, this option is only available on Linux system. + * The proof is generated and saved in a file in the `Goeland` folder. + */ + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + + + solve(Seq(), bot, proof.owningTheorem.fullName, lib.isDraft) match { + case Success(value) => proof.ValidProofTactic(bot, value.steps, Seq()) + case Failure(e) => e match + case e: FileNotFoundException => throw new Exception("For compatibility reasons, external provers can't be called in non-draft mode" + + " unless all proofs have already been generated and be available in static files. You can enable draft mode by adding `draft()` at the top of your working file.") + case e: OsNotSupportedException => throw e + case e => + throw e + } + } + + inline def solve(axioms: Seq[F.Sequent], sequent: F.Sequent, source: String, generateProofs : Boolean): Try[K.SCProof] = + solveK(axioms.map(_.underlying), sequent.underlying, source, generateProofs) + + + /** + * Solve a sequent using the Goéland automated theorem prover, and return the kernel proof. + * At the moment, this option is only available on Linux systems. + */ + def solveK(using line: sourcecode.Line, file: sourcecode.File)(axioms: Seq[K.Sequent], sequent: K.Sequent, source:String, generateProofs : Boolean): Try[K.SCProof] = { + val filename = source + val outputname = source+"_sol" + val directory = File(foldername) + if (directory != null) && !directory.exists() then directory.mkdirs() + + val freevars = (sequent.left.flatMap(_.freeVariables) ++ sequent.right.flatMap(_.freeVariables) ).toSet.map(x => x -> K.Variable(K.Identifier("X"+x.id.name, x.id.no), x.sort) ).toMap + + val backMap = freevars.map{ + case (x: K.Variable, xx: K.Variable) => xx -> x + case null => throw new Exception("This should not happen") + } + val r = problemToFile(foldername, filename, "question"+i, axioms, sequent, source) + i += 1 + + if generateProofs then + val OS = System.getProperty("os.name") + if OS.contains("nix") || OS.contains("nux") || OS.contains("aix") then + val ret = s"chmod u+x \"$goelandExec\"".! + val cmd = (s"$goelandExec -otptp -wlogs -no_id -quoted_pred -proof_file=$foldername$outputname $foldername$filename.p") + val res = try { + cmd.!! + } catch { + case e: Exception => + throw e + } + val proof = reconstructProof(new File(foldername+outputname+".p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable) + Success(proof) + else if OS.contains("win") then + Failure(OsNotSupportedException("The Goeland automated theorem prover is not yet supported on Windows.")) + else + Failure(OsNotSupportedException("The Goeland automated theorem prover is only supported on Linux for now.")) + else + if File(foldername+outputname+".p").exists() then + val proof = reconstructProof(new File(foldername+outputname+".p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable) + println(OutputManager.WARNING(s"WARNING: in ${file.value}:$line, For compatibility reasons, replace `by Goeland` with `by Goeland(\"$foldername$outputname\")`.")) + Success(proof) + + else Failure(Exception("For compatibility reasons, external provers can't be called in non-draft mode. You can enable draft mode by adding `draft()` at the top of your working file.")) + + + } + +} \ No newline at end of file diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala new file mode 100644 index 000000000..2cb2793ed --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -0,0 +1,340 @@ +package lisa.maths + +import lisa.utils.Serialization.sorry +import lisa.prooflib.BasicStepTactic.Sorry +import lisa.utils.K.repr +import lisa.automation.atp.Goeland +import lisa.prooflib.Library +import lisa.utils.Printing + +/** + * Implements theorems about first-order logic. + */ +object Quantifiers extends lisa.Main { + + private val X = variable[Formula] + private val Y = variable[Formula] + private val Z = variable[Formula] + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + private val a = variable[Term] + private val p = variable[Formula] + private val P = variable[Term >>: Formula] + private val Q = variable[Term >>: Formula] + + /** + * Theorem --- A formula is equivalent to itself universally quantified if + * the bound variable is not free in it. + */ + val closedFormulaUniversal1 = Theorem( + () |- ∀(x, p) ==> p + ) { + have(thesis) by Tableau + } + + /** + * Theorem --- A formula is equivalent to itself universally quantified if + * the bound variable is not free in it. + */ + val closedFormulaUniversal = Theorem( + () |- ∀(x, p) <=> p + ) { + have(thesis) by Tableau + } + draft() + + /** + * Theorem --- A formula is equivalent to itself existentially quantified if + * the bound variable is not free in it. + */ + val closedFormulaExistential = Theorem( + () |- ∃(x, p) <=> p + ) { + have(thesis) by Tableau + } + + val ∃! = DEF(lambda(P, exists(x, forall(y, P(y) <=> (x === y))))).asBinder[T, F, F] + val existsOne = ∃! + println(∃!.definition) + + + /** + * Theorem --- If there exists a *unique* element satisfying a predicate, + * then we can say there *exists* an element satisfying it as well. + */ + val existsOneImpliesExists = Theorem( + ∃!(x, P(x)) |- ∃(x, P(x)) + ) { + have((x === y) <=> P(y) |- (x === y) <=> P(y)) by Hypothesis + thenHave(∀(y, (x === y) <=> P(y)) |- (x === y) <=> P(y)) by LeftForall + thenHave(∀(y, (x === y) <=> P(y)) |- P(x)) by InstSchema(y := x) + thenHave(∀(y, (x === y) <=> P(y)) |- ∃(x, P(x))) by RightExists + thenHave(∃(x, ∀(y, (x === y) <=> P(y))) |- ∃(x, P(x))) by LeftExists + thenHave((∃(x, ∀(y, (x === y) <=> P(y))) <=> ∃!(P), ∃!(P) ) |- ∃(x, P(x))) by + LeftSubstEq.withParameters(List((∃!(P), ∃(x, ∀(y, (x === y) <=> P(y))))), (Seq(X), X)) + have(thesis) by Tautology.from(lastStep, existsOne.definition) + } + + + /** + * Theorem --- Equality relation is transitive. + */ + val equalityTransitivity = Theorem( + (x === y) /\ (y === z) |- (x === z) + ) { + have((x === y) |- (x === y)) by Hypothesis + thenHave(((x === y), (y === z)) |- (x === z)) by RightSubstEq.withParameters(List((y, z)), (Seq(y), x === y)) + thenHave(thesis) by Restate + } + + /** + * Theorem --- Conjunction and universal quantification commute. + */ + val universalConjunctionCommutation = Theorem( + () |- forall(x, P(x) /\ Q(x)) <=> forall(x, P(x)) /\ forall(x, Q(x)) + ) { + have(thesis) by Tableau + } + + /** + * Theorem -- Existential quantification distributes conjunction. + */ + val existentialConjunctionDistribution = Theorem( + exists(x, P(x) /\ Q(x)) |- exists(x, P(x)) /\ exists(x, Q(x)) + ) { + have(thesis) by Tableau + } + + /** + * Theorem -- Existential quantification fully distributes when the conjunction involves one closed formula. + */ + val existentialConjunctionWithClosedFormula = Theorem( + exists(x, P(x) /\ p) <=> (exists(x, P(x)) /\ p) + ) { + have(thesis) by Tableau + } + + /** + * Theorem -- If there is an equality on the existential quantifier's bound variable inside its body, then we can reduce + * the existential quantifier to the satisfaction of the remaining body. + */ + val equalityInExistentialQuantifier = Theorem( + exists(x, P(x) /\ (y === x)) <=> P(y) + ) { + have(exists(x, P(x) /\ (y === x)) |- P(y)) subproof { + have(P(x) |- P(x)) by Hypothesis + thenHave((P(x), y === x) |- P(y)) by RightSubstEq.withParameters(List((y, x)), (Seq(y), P(y))) + thenHave(P(x) /\ (y === x) |- P(y)) by Restate + thenHave(thesis) by LeftExists + } + val forward = thenHave(exists(x, P(x) /\ (y === x)) ==> P(y)) by Restate + + have(P(y) |- exists(x, P(x) /\ (y === x))) subproof { + have(P(x) /\ (y === x) |- P(x) /\ (y === x)) by Hypothesis + thenHave(P(x) /\ (y === x) |- exists(x, P(x) /\ (y === x))) by RightExists + thenHave(P(y) /\ (y === y) |- exists(x, P(x) /\ (y === x))) by InstSchema(x := y) + thenHave(thesis) by Restate + } + val backward = thenHave(P(y) ==> exists(x, P(x) /\ (y === x))) by Restate + + have(thesis) by RightIff(forward, backward) + } + + /** + * Theorem --- Disjunction and existential quantification commute. + */ + val existentialDisjunctionCommutation = Theorem( + () |- exists(x, P(x) \/ Q(x)) <=> exists(x, P(x)) \/ exists(x, Q(x)) + ) { + have(thesis) by Tableau + } + + /** + * Theorem --- Universal quantification distributes over equivalence + */ + val universalEquivalenceDistribution = Theorem( + forall(z, P(z) <=> Q(z)) |- (forall(z, P(z)) <=> forall(z, Q(z))) + ) { + have(thesis) by Tableau + } + + /** + * Theorem --- Universal quantification of equivalence implies equivalence + * of existential quantification. + */ + val existentialEquivalenceDistribution = Theorem( + forall(z, P(z) <=> Q(z)) |- (exists(z, P(z)) <=> exists(z, Q(z))) + ) { + have(thesis) by Tableau + + } + + /** + * Theorem --- Universal quantification distributes over implication + */ + val universalImplicationDistribution = Theorem( + forall(z, P(z) ==> Q(z)) |- (forall(z, P(z)) ==> forall(z, Q(z))) + ) { + have(thesis) by Tableau + } + + /** + * Theorem --- Universal quantification of implication implies implication + * of existential quantification. + */ + val existentialImplicationDistribution = Theorem( + forall(z, P(z) ==> Q(z)) |- (exists(z, P(z)) ==> exists(z, Q(z))) + ) { + have(thesis) by Tableau + } + + /* + /** + * Theorem --- Universal quantification of equivalence implies equivalence + * of unique existential quantification. + */ + val uniqueExistentialEquivalenceDistribution = Theorem( + forall(z, P(z) <=> Q(z)) |- (existsOne(z, P(z)) <=> existsOne(z, Q(z))) + ) { + val yz = have(forall(z, P(z) <=> Q(z)) |- ((y === z) <=> P(y)) <=> ((y === z) <=> Q(y))) subproof { + have(forall(z, P(z) <=> Q(z)) |- forall(z, P(z) <=> Q(z))) by Hypothesis + val quant = thenHave(forall(z, P(z) <=> Q(z)) |- P(y) <=> Q(y)) by InstantiateForall(y) + + val lhs = have((forall(z, P(z) <=> Q(z)), ((y === z) <=> P(y))) |- ((y === z) <=> Q(y))) subproof { + have((P(y) <=> Q(y), ((y === z) <=> P(y))) |- ((y === z) <=> Q(y))) by Tautology + have(thesis) by Tautology.from(lastStep, quant) + } + val rhs = have((forall(z, P(z) <=> Q(z)), ((y === z) <=> Q(y))) |- ((y === z) <=> P(y))) subproof { + have((P(y) <=> Q(y), ((y === z) <=> Q(y))) |- ((y === z) <=> P(y))) by Tautology + have(thesis) by Tautology.from(lastStep, quant) + } + + have(thesis) by Tautology.from(lhs, rhs) + } + + val fy = thenHave(forall(z, P(z) <=> Q(z)) |- forall(y, ((y === z) <=> P(y)) <=> ((y === z) <=> Q(y)))) by RightForall + + have(forall(y, P(y) <=> Q(y)) |- (forall(y, P(y)) <=> forall(y, Q(y)))) by Restate.from(universalEquivalenceDistribution) + val univy = thenHave(forall(y, ((y === z) <=> P(y)) <=> ((y === z) <=> Q(y))) |- (forall(y, ((y === z) <=> P(y))) <=> forall(y, ((y === z) <=> Q(y))))) by InstSchema( + P := lambda(y, (y === z) <=> P(y)), Q := lambda(y, (y === z) <=> Q(y)) + ) + + have(forall(z, P(z) <=> Q(z)) |- (forall(y, ((y === z) <=> P(y))) <=> forall(y, ((y === z) <=> Q(y))))) by Cut(fy, univy) + + thenHave(forall(z, P(z) <=> Q(z)) |- forall(z, forall(y, ((y === z) <=> P(y))) <=> forall(y, ((y === z) <=> Q(y))))) by RightForall + have(forall(z, P(z) <=> Q(z)) |- exists(z, forall(y, ((y === z) <=> P(y)))) <=> exists(z, forall(y, ((y === z) <=> Q(y))))) by Cut( + lastStep, + existentialEquivalenceDistribution of (P := lambda(z, forall(y, (y === z) <=> P(y))), Q := lambda(z, forall(y, (y === z) <=> Q(y)))) + ) + thenHave(thesis) by Restate + } + + /** + * Theorem --- if atleast two distinct elements exist, then there is no unique + * existence + */ + val atleastTwoExist = Theorem( + (exists(x, P(x)) /\ !existsOne(x, P(x))) <=> exists(x, exists(y, P(x) /\ P(y) /\ !(x === y))) + ) { + val fwd = have((exists(x, P(x)) /\ !existsOne(x, P(x))) ==> exists(x, exists(y, P(x) /\ P(y) /\ !(x === y)))) subproof { + have((P(x), ((x === y) /\ !P(y))) |- P(x) /\ !P(y)) by Restate + have((P(x), ((x === y) /\ !P(y))) |- P(y) /\ !P(y)) by Sorry //Substitution.ApplyRules(x === y) // contradiction + val xy = thenHave((P(x), ((x === y) /\ !P(y))) |- exists(x, exists(y, P(x) /\ P(y) /\ !(x === y)))) by Weakening + + have((P(x), (!(x === y) /\ P(y))) |- (!(x === y) /\ P(y) /\ P(x))) by Restate + thenHave((P(x), (!(x === y) /\ P(y))) |- exists(y, !(x === y) /\ P(y) /\ P(x))) by RightExists + val nxy = thenHave((P(x), (!(x === y) /\ P(y))) |- exists(x, exists(y, !(x === y) /\ P(y) /\ P(x)))) by RightExists + + have((P(x), (!(x === y) /\ P(y)) \/ ((x === y) /\ !P(y))) |- exists(x, exists(y, P(x) /\ P(y) /\ !(x === y)))) by Tautology.from(xy, nxy) + thenHave((P(x), exists(y, (!(x === y) /\ P(y)) \/ ((x === y) /\ !P(y)))) |- exists(x, exists(y, P(x) /\ P(y) /\ !(x === y)))) by LeftExists + thenHave((P(x), forall(x, exists(y, (!(x === y) /\ P(y)) \/ ((x === y) /\ !P(y))))) |- exists(x, exists(y, P(x) /\ P(y) /\ !(x === y)))) by LeftForall + thenHave((exists(x, P(x)), forall(x, exists(y, (!(x === y) /\ P(y)) \/ ((x === y) /\ !P(y))))) |- exists(x, exists(y, P(x) /\ P(y) /\ !(x === y)))) by LeftExists + + thenHave(thesis) by Restate + } + + val bwd = have(exists(x, exists(y, P(x) /\ P(y) /\ !(x === y))) ==> (exists(x, P(x)) /\ !existsOne(x, P(x)))) subproof { + have((P(x), P(y), !(x === y)) |- P(x)) by Restate + val ex = thenHave((P(x), P(y), !(x === y)) |- exists(x, P(x))) by RightExists + + have((P(x), P(y), !(x === y)) |- P(y) /\ !(y === x)) by Restate + have((P(x), P(y), !(x === y), (x === z)) |- P(y) /\ !(y === z)) by Sorry //Substitution.ApplyRules(x === z) + thenHave((P(x), P(y), !(x === y), (x === z)) |- (P(y) /\ !(y === z)) \/ (!P(y) /\ (y === z))) by Weakening + val xz = thenHave((P(x), P(y), !(x === y), (x === z)) |- exists(y, (P(y) /\ !(y === z)) \/ (!P(y) /\ (y === z)))) by RightExists + + have((P(x), P(y), !(x === y), !(x === z)) |- (P(x) /\ !(x === z)) \/ (!P(x) /\ (x === z))) by Restate + val nxz = thenHave((P(x), P(y), !(x === y), !(x === z)) |- exists(x, (P(x) /\ !(x === z)) \/ (!P(x) /\ (x === z)))) by RightExists + + have((P(x), P(y), !(x === y)) |- exists(x, (P(x) /\ !(x === z)) \/ (!P(x) /\ (x === z)))) by Tautology.from(xz, nxz) + thenHave((P(x), P(y), !(x === y)) |- forall(z, exists(x, (P(x) /\ !(x === z)) \/ (!P(x) /\ (x === z))))) by RightForall + val uex = thenHave(P(x) /\ P(y) /\ !(x === y) |- !existsOne(z, P(z))) by Restate + + have(P(x) /\ P(y) /\ !(x === y) |- exists(x, P(x)) /\ !existsOne(z, P(z))) by Tautology.from(ex, uex) + thenHave(exists(y, P(x) /\ P(y) /\ !(x === y)) |- exists(x, P(x)) /\ !existsOne(z, P(z))) by LeftExists + thenHave(exists(x, exists(y, P(x) /\ P(y) /\ !(x === y))) |- exists(x, P(x)) /\ !existsOne(z, P(z))) by LeftExists + + thenHave(thesis) by Restate + } + + have(thesis) by Tautology.from(fwd, bwd) + } + +*/ + + + /** + * Quantify all variables in a formula on the right side of the premise sequent. + * + *+ * Γ ⊢ φ, Δ + * -------------------------- x, y, ..., z do not appear in Γ + * Γ ⊢ ∀x.∀y. ... ∀z. φ, Δ + *+ */ + def quantifyAll(using lib: Library, proof: lib.Proof)(premiseStep: proof.Fact)(conclusion: Sequent) = + def isQuantifiedOf(target: Formula, pivot: Formula, vars: List[Variable[T]] = Nil): Option[List[Variable[T]]] = + target match + case ∀(x, inner) => + if isSame(inner, pivot) then Some(vars) else isQuantifiedOf(inner, pivot, x :: vars) + case _ => None + val premise = proof.getSequent(premiseStep) + val difference = premise.right -- conclusion.right + + if difference.isEmpty then + Restate(using lib, proof)(premiseStep)(conclusion) + else if difference.size > 1 then + proof.InvalidProofTactic(s"There must be only one formula to quantify over between the premise and the conclusion. Found: \n${Printing.printList(difference)}") + else + val rdifference = conclusion.right -- premise.right + if rdifference.size != 1 then + proof.InvalidProofTactic(s"There must be only one formula to quantify over between the premise and the conclusion. Found: \n${Printing.printList(rdifference)}") + else + val pivot = difference.head + val target = rdifference.head + val varsOption = isQuantifiedOf(target, pivot) + + if varsOption.isEmpty then + proof.InvalidProofTactic("Could not find a formula to quantify over in the conclusion.") + else + val vars = varsOption.get + val conflicts = vars.toSet -- premise.left.flatMap(_.freeVars) + + if conflicts.nonEmpty then + proof.InvalidProofTactic(s"Variables ${conflicts.mkString(", ")} to be quantified appear in the LHS of the conclusion.") + else + // safe, proceed + TacticSubproof: + val vars = varsOption.get + lib.have(premise) by Restate.from(premiseStep) + + val base = premise ->> pivot + + vars.foldLeft(pivot): (pivot, v) => + val quant = ∀(v, pivot) + lib.thenHave(base +>> quant) by RightForall.withParameters(pivot, v) + quant + + lib.thenHave(conclusion) by Restate + +} diff --git a/lisa-sets2/src/main/scala/lisa/maths/Tests.scala b/lisa-sets2/src/main/scala/lisa/maths/Tests.scala new file mode 100644 index 000000000..9f1e75f0e --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/Tests.scala @@ -0,0 +1,28 @@ +package lisa.maths +import lisa.automation.atp.Goeland +import lisa.utils.KernelHelpers.checkProof +import lisa.utils.tptp.* +import java.io.* +import lisa.kernel.proof.SCProofCheckerJudgement.SCInvalidProof +import lisa.kernel.proof.SCProofCheckerJudgement.SCValidProof + +object Tests extends lisa.Main { + draft() + + val x = variable[Term] + val y = variable[Term] + val z = variable[Term] + val P = variable[Term >>: Formula] + + val ppp = ProofParser.reconstructProof(new File("goeland/testEgg.p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable) + + checkProof(ppp) + + + + /* + val buveurs = Theorem(exists(x, P(x) ==> forall(y, P(y)))) { + have(thesis) by Goeland // ("goeland/Example.buveurs2_sol") + } + */ +} \ No newline at end of file diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala new file mode 100644 index 000000000..669afce69 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala @@ -0,0 +1,32 @@ +package lisa.maths.settheory + +import lisa.automation.Substitution + +object Comprehension extends lisa.Main: + + val x = variable[Term] + val y = variable[Term] + val z = variable[Term] + val t = variable[Term] + val s = variable[Term] + + private val comprehension: Constant[Arrow[T, Arrow[Arrow[T, F], T]]] = DEF ( lambda(t, lambda(φ, ε(s, ∀(x, (x ∈ s) <=> (x ∈ t /\ φ(x)))))) ) + + extension (t: Term) + def filter(predicate: Term >>: Formula): Term = + comprehension(t)(predicate) + + // this has to be after the extension for compilation + val filter = comprehension + + val existence = Theorem( ∃(s, ∀(x, (x ∈ s) <=> (x ∈ t /\ φ(x)))) ): + have(thesis) by Restate.from(comprehensionSchema of (z := t)) + + val definition: THM = Theorem( ∀(x, x ∈ s.filter(φ) <=> (x ∈ s /\ φ(x))) ): + have ( ∀(x, x ∈ y <=> (x ∈ s /\ φ(x))) |- ∀(x, x ∈ y <=> (x ∈ s /\ φ(x))) ) by Hypothesis + // thenHave( ∀(x, x ∈ y <=> (x ∈ s /\ φ(x))) |- ∀(x, x ∈ ε(t, ∀(x, x ∈ t <=> (x ∈ s /\ φ(x)))) <=> (x ∈ s /\ φ(x))) ) by RightEpsilon + thenHave( ∀(x, x ∈ y <=> (x ∈ s /\ φ(x))) |- ∀(x, x ∈ s.filter(φ) <=> (x ∈ s /\ φ(x))) ) by Substitution.Apply(filter.definition) + thenHave( ∃(y, ∀(x, x ∈ y <=> (x ∈ s /\ φ(x)))) |- ∀(x, x ∈ s.filter(φ) <=> (x ∈ s /\ φ(x))) ) by LeftExists + have(thesis) by Cut(existence, lastStep) + +end Comprehension \ No newline at end of file diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Equality.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Equality.scala new file mode 100644 index 000000000..4ac74b1ce --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Equality.scala @@ -0,0 +1,13 @@ +package lisa.maths.settheory + +object Equality extends lisa.Main: + + val x = variable[Term] + val y = variable[Term] + val z = variable[Term] + + val transitivity = Theorem((x === y, y === z) |- (x === z)): + have((x === y, y === z) |- (x === y)) by Hypothesis + thenHave(thesis) by InstSchema(y := z) + +end Equality diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala new file mode 100644 index 000000000..78ed0ae64 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala @@ -0,0 +1,49 @@ +package lisa.maths.settheory + +import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.prooflib.Library +import lisa.SetTheoryLibrary +import lisa.prooflib.ProofTacticLib.ProofFactSequentTactic + +object Extensionality extends lisa.Main: + + private val s = variable[Term] + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + private val P = variable[Term >>: Formula] + private val Q = variable[Term >>: Term >>: Formula] + + val implied = Theorem( forall(z, z ∈ x <=> z ∈ y) |- (x === y) ): + have(thesis) by Weakening(extensionalityAxiom) + + /** + * Given that z ∈ x <=> z ∈ y, prove that x = y if z is free. + * + * Γ ⊢ z ∈ x <=> z ∈ y, Δ + * ------------------------ z not in Γ + * Γ ⊢ x === y, Δ + */ + def tactic(using proof: Proof)(premiseStep: proof.Fact)(conclusion: Sequent) = + val premise = proof.getSequent(premiseStep) + val boundVars = premise.left.flatMap(_.freeVars) + inline def valid(z1: Variable[T], z2: Variable[T], x: Expr[T], y: Expr[T]) = + z1 == z2 && !boundVars.contains(z1) && conclusion.right.exists(isSame(_, x === y)) + val pivot: Option[(Variable[T], Expr[T], Expr[T])] = premise.right.collectFirst: + case (<=> #@ (∈ #@ (z1: Variable[T]) #@ (x: Expr[T])) #@ (∈ #@ (z2: Variable[T]) #@ (y: Expr[T]))) if valid(z1, z2, x, y) => (z1, x, y) + + pivot match + case None => + proof.InvalidProofTactic("Could not find a formula of the form z ∈ x <=> z ∈ y in the RHS of the premise.") + case Some((z, xe, ye)) => + TacticSubproof: + val pivot = z ∈ xe <=> z ∈ ye + val qpivot = forall(z, pivot) + val eq = xe === ye + val baseSequent = premise ->> pivot + val implication = proof.InstantiatedFact(implied, Seq(x := xe, y := ye)) + + have(baseSequent +>> qpivot) by RightForall.withParameters(pivot, z)(premiseStep) + have(baseSequent +>> eq) by Cut.withParameters(qpivot)(lastStep, implication) + +end Extensionality \ No newline at end of file diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Pair.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Pair.scala new file mode 100644 index 000000000..0f2d6cdca --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Pair.scala @@ -0,0 +1,34 @@ +package lisa.maths.settheory + +import lisa.maths.settheory.UnorderedPair.* +import lisa.maths.settheory.Singleton.* + +object Pair extends lisa.Main: + + private val s = variable[Term] + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + private val p = variable[Term] + private val P = variable[Term >>: Formula] + private val Q = variable[Term >>: Term >>: Formula] + + /** + * An ordered pair. + */ + val pair = DEF ( lambda(x, lambda(y, ~x <> (x <> y))) ) + + // extension (t: Term) + // infix def :: (s: Term) = pair(t)(s) + + // val firstMemberExists = Theorem(exists(x, exists(y, p === x :: y)) ==> exists(y, p === x :: y)): + // have(p === x :: y |- p === x :: y) by Restate + // thenHave(p === x :: y |- exists(y, p === x :: y)) by Restate + + // // first of a pair + // val first = EpsilonDEF( lambda(p, ε(x, exists(x, exists(y, p === x :: y)) ==> exists(y, p === x :: y))) ) + + // // second of a pair + // val second = EpsilonDEF( lambda(p, ε(y, exists(x, exists(y, p === x :: y)) ==> exists(x, p === x :: y))) ) + +end Pair diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala new file mode 100644 index 000000000..5bfa9d62e --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala @@ -0,0 +1,70 @@ +package lisa.maths.settheory + +import lisa.automation.Substitution +import lisa.maths.Quantifiers + +object Replacement extends lisa.Main: + + val x = variable[Term] + val y = variable[Term] + val z = variable[Term] + val t = variable[Term] + val s = variable[Term] + val A = variable[Term] + val f = variable[Term >>: Term] + val P = variable[Term >>: Term >>: Formula] + + private val replacement: Constant[Arrow[T, Arrow[Arrow[T, T], T]]] = DEF ( lambda(t, lambda(f, ε(s, ∀(x, (x ∈ s) <=> ∃(y, y ∈ t /\ (y === f(x))))))) ) + + extension (t: Term) + def map(function: Term >>: Term): Term = + replacement(t)(function) + + // this has to be after the extension for compilation + val map = replacement + + /** + * The existence of the image of a set under a function. Or, the functional + * form of the replacement schema. + */ + val existence = Theorem( ∃(s, ∀(x, (x ∈ s) <=> ∃(y, y ∈ t /\ (y === f(x))))) ): + val inst = replacementSchema of (A := t, P := lambda(x, lambda(y, y === f(x)))) + val conditional = have(∀(x, x ∈ t ==> ∀(y, ∀(z, ((y === f(x)) /\ (z === f(x))) ==> (y === z) ))) |- ∃(s, ∀(x, (x ∈ s) <=> ∃(y, y ∈ t /\ (y === f(x))))) ) by Weakening(inst) + + val eqTautology = + have(((y === f(x)) /\ (z === f(x))) ==> (y === z)) by Weakening(Equality.transitivity of (x := y, y := f(x), z := z)) + thenHave(∀(y, ∀(z, ((y === f(x)) /\ (z === f(x))) ==> (y === z)))) by Quantifiers.quantifyAll + + thenHave(x ∈ t ==> ∀(y, ∀(z, ((y === f(x)) /\ (z === f(x))) ==> (y === z)))) by Weakening + thenHave(∀(x, x ∈ t ==> ∀(y, ∀(z, ((y === f(x)) /\ (z === f(x))) ==> (y === z))))) by RightForall + + have(thesis) by Cut(lastStep, conditional) + + /** + * The extensional definition of a [[map]]ped set. + * + * `∀(x, x ∈ s.map(f) <=> ∃(y, y ∈ s /\ x === f(y)))` + */ + val definition: THM = Theorem( ∀(x, x ∈ s.map(f) <=> ∃(y, y ∈ s /\ (x === f(y)))) ): + have ( ∀(x, x ∈ y <=> ∃(y, y ∈ s /\ (x === f(y)))) |- ∀(x, x ∈ y <=> ∃(y, y ∈ s /\ (x === f(y)))) ) by Hypothesis + thenHave( ∀(x, x ∈ y <=> ∃(y, y ∈ s /\ (x === f(y)))) |- ∀(x, x ∈ ε(t, ∀(x, x ∈ t <=> ∃(y, y ∈ s /\ (x === f(y))))) <=> ∃(y, y ∈ s /\ (x === f(y)))) ) by RightEpsilon + thenHave( ∀(x, x ∈ y <=> ∃(y, y ∈ s /\ (x === f(y)))) |- ∀(x, x ∈ s.map(f) <=> ∃(y, y ∈ s /\ (x === f(y)))) ) by Substitution.Apply(map.definition) + thenHave( ∃(y, ∀(x, x ∈ y <=> ∃(y, y ∈ s /\ (x === f(y))))) |- ∀(x, x ∈ s.map(f) <=> ∃(y, y ∈ s /\ (x === f(y)))) ) by LeftExists + have(thesis) by Cut(existence, lastStep) + + /** + * The replacement property of a [[map]]ped set. + * + * `x ∈ s ==> f(x) ∈ s.map(f)` + */ + val unfolding: THM = Theorem( x ∈ s ==> f(x) ∈ s.map(f) ): + have(x ∈ s |- x ∈ s /\ (f(x) === f(x))) by Restate + val cond = thenHave(x ∈ s |- ∃(y, y ∈ s /\ (f(x) === f(y)))) by RightExists + + val inst = + have(f(x) ∈ s.map(f) <=> ∃(y, y ∈ s /\ (f(x) === f(y)))) by InstantiateForall(f(x))(definition) + thenHave(∃(y, y ∈ s /\ (f(x) === f(y))) |- f(x) ∈ s.map(f)) by Weakening + + have(x ∈ s |- f(x) ∈ s.map(f)) by Cut(cond, inst) + +end Replacement \ No newline at end of file diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/SetTheory2.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/SetTheory2.scala new file mode 100644 index 000000000..c9811f189 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/SetTheory2.scala @@ -0,0 +1,109 @@ +package lisa.maths.settheory + +/** + * Revamp of the set theory library from scratch, since most of the current one is severely outdated. + * We can make a better presentation and organisation of theorems, better automation, uniform comments/latex tags, etc. + */ +object SetTheory2 extends lisa.Main { + //import lisa.maths.settheory.SetTheory.* + // import Comprehensions.* + + /* + private val s = variable + private val x = variable + private val x_1 = variable + private val y = variable + private val z = variable + private val f = function[1] + private val t = variable + private val A = variable + private val B = variable + private val C = variable + private val P = predicate[2] + private val Q = predicate[1] + private val Filter = predicate[1] + private val Map = function[1] + + val primReplacement = Theorem( + ∀(x, in(x, A) ==> ∀(y, ∀(z, (P(x, y) /\ P(x, z)) ==> (y === z)))) |- + ∃(B, forall(y, in(y, B) <=> ∃(x, in(x, A) /\ P(x, y)))) + ) { + have(thesis) by Restate.from(replacementSchema of (A := A, x := x, P := P)) + } + + val manyForall = Lemma( + ∀(x, in(x, A) ==> ∀(y, ∀(z, (P(x, y) /\ P(x, z)) ==> (y === z)))).substitute(P := lambda((A, B), P(A, B) /\ ∀(C, P(A, C) ==> (B === C)))) <=> top + ) { + have(thesis) by Tableau + } + + val functionalIsFunctional = Theorem( + ∀(x, in(x, A) ==> ∀(y, ∀(z, (P(x, y) /\ P(x, z)) ==> (y === z)))).substitute(P := lambda((A, B), Filter(A) /\ (B === Map(A)))) <=> top + ) { + + have(y === Map(x) |- (y === Map(x))) by Restate + thenHave((y === Map(x), z === Map(x)) |- y === z) by Substitution.ApplyRules(Map(x) === z) + thenHave(in(x, A) |- ((Filter(x) /\ (y === Map(x)) /\ (z === Map(x))) ==> (y === z))) by Weakening + thenHave(in(x, A) |- ∀(z, ((Filter(x) /\ (y === Map(x)) /\ (z === Map(x))) ==> (y === z)))) by RightForall + thenHave(in(x, A) |- ∀(y, ∀(z, ((Filter(x) /\ (y === Map(x)) /\ (z === Map(x))) ==> (y === z))))) by RightForall + thenHave(in(x, A) ==> ∀(y, ∀(z, ((Filter(x) /\ (y === Map(x)) /\ (z === Map(x))) ==> (y === z))))) by Restate + thenHave(∀(x, in(x, A) ==> ∀(y, ∀(z, ((Filter(x) /\ (y === Map(x)) /\ (z === Map(x))) ==> (y === z)))))) by RightForall + thenHave(thesis) by Restate + + } + + /** + * Theorem --- the refined replacement axiom. Easier to use as a rule than primReplacement. + */ + val replacement = Theorem( + ∃(B, ∀(y, in(y, B) <=> ∃(x, in(x, A) /\ P(x, y) /\ ∀(z, P(x, z) ==> (z === y))))) + ) { + have(thesis) by Tautology.from(manyForall, primReplacement of (P := lambda((A, B), P(A, B) /\ ∀(C, P(A, C) ==> (B === C))))) + } + + val onePointRule = Theorem( + ∃(x, (x === y) /\ Q(x)) <=> Q(y) + ) { + val s1 = have(∃(x, (x === y) /\ Q(x)) ==> Q(y)) subproof { + assume(∃(x, (x === y) /\ Q(x))) + val ex = witness(lastStep) + val s1 = have(Q(ex)) by Tautology.from(ex.definition) + val s2 = have(ex === y) by Tautology.from(ex.definition) + have(Q(y)) by Substitution.ApplyRules(s2)(s1) + } + val s2 = have(Q(y) ==> ∃(x, (x === y) /\ Q(x))) subproof { + assume(Q(y)) + thenHave((y === y) /\ Q(y)) by Restate + thenHave(∃(x, (x === y) /\ Q(x))) by RightExists + thenHave(thesis) by Restate.from + } + have(thesis) by Tautology.from(s1, s2) + } + + /** + * Theorem - `∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1))) <=> (x === f(∅))` + */ + val singletonMap = Lemma( + ∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1))) <=> (x === f(∅)) + ) { + val s1 = have(∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1))) ==> (x === f(∅))) subproof { + have(x === f(∅) |- x === f(∅)) by Restate + thenHave((x_1 === ∅, x === f(x_1)) |- x === f(∅)) by Substitution.ApplyRules(x_1 === ∅) + thenHave((x_1 === ∅) /\ (x === f(x_1)) |- x === f(∅)) by Restate + thenHave((in(x_1, singleton(∅))) /\ ((x === f(x_1))) |- x === f(∅)) by Substitution.ApplyRules(singletonHasNoExtraElements of (y := x_1, x := ∅)) + thenHave(∃(x_1, in(x_1, singleton(∅)) /\ ((x === f(x_1)))) |- x === f(∅)) by LeftExists + + } + + val s2 = have((x === f(∅)) ==> ∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1)))) subproof { + have(x === f(∅) |- (∅ === ∅) /\ (x === f(∅))) by Restate + thenHave(x === f(∅) |- in(∅, singleton(∅)) /\ (x === f(∅))) by Substitution.ApplyRules(singletonHasNoExtraElements of (y := x_1, x := ∅)) + thenHave(x === f(∅) |- ∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1)))) by RightExists + thenHave(thesis) by Restate.from + + } + + have(thesis) by Tautology.from(s1, s2) + } +*/ +} diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala new file mode 100644 index 000000000..cc31189ea --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala @@ -0,0 +1,28 @@ +package lisa.maths.settheory + +import lisa.maths.settheory.UnorderedPair.* +import lisa.automation.Substitution + +object Singleton extends lisa.Main: + + private val s = variable[Term] + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + private val P = variable[Term >>: Formula] + private val Q = variable[Term >>: Term >>: Formula] + + val singleton = DEF ( lambda(x, x <> x) ) + + extension (t: Term) + /** + * Prefix notation for singleton set + */ + def unary_~ = singleton(x) + + val membership = Theorem( x ∈ ~x ): + have(x ∈ (x <> x)) by Restate.from(UnorderedPair.firstMember of (y := x)) + thenHave(thesis) by Substitution.Apply(singleton.definition) + + +end Singleton \ No newline at end of file diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/UnorderedPair.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/UnorderedPair.scala new file mode 100644 index 000000000..e5518774b --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/UnorderedPair.scala @@ -0,0 +1,36 @@ +package lisa.maths.settheory + +object UnorderedPair extends lisa.Main: + + private val s = variable[Term] + private val x = variable[Term] + private val y = variable[Term] + private val z = variable[Term] + private val P = variable[Term >>: Formula] + private val Q = variable[Term >>: Term >>: Formula] + + /** Unordered pair of sets */ + val upair = unorderedPair + /** Unordered pair of sets */ + val <> = upair + + extension (t: Term) + /** + * Infix notation for an unordered pair. + */ + infix def <> (s: Term) = upair(t)(s) + + val firstMember = Theorem( x ∈ (x <> y) ): + have(thesis) by Tautology.from(<>.definition of (z := x)) + + val secondMember = Theorem( y ∈ (x <> y) ): + have(thesis) by Tautology.from(<>.definition of (z := y)) + + // val symmetry = Theorem( (x <> y) === (y <> x) ): + // val fwd = have(z ∈ (x <> y) <=> ((z === x) \/ (z === y))) by Restate.from(<>.definition) + // val bwd = have(z ∈ (y <> x) <=> ((z === y) \/ (z === x))) by Restate.from(<>.definition of (x := y, y := x)) + + // have(z ∈ (x <> y) <=> z ∈ (y <> x)) by Tautology.from(fwd, bwd) + // thenHave(thesis) by Extensionality.tactic + +end UnorderedPair \ No newline at end of file diff --git a/lisa-sets2/src/test/scala/lisa/automation/CongruenceTest.scala b/lisa-sets2/src/test/scala/lisa/automation/CongruenceTest.scala new file mode 100644 index 000000000..74c6e9ecf --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/automation/CongruenceTest.scala @@ -0,0 +1,913 @@ +package lisa.automation +import lisa.fol.FOL.{*, given} +import lisa.automation.Congruence.* +import lisa.automation.Congruence +import org.scalatest.funsuite.AnyFunSuite + + +class CongruenceTest extends AnyFunSuite with lisa.TestMain { + + + given lib: lisa.SetTheoryLibrary.type = lisa.SetTheoryLibrary + + val a = variable + val b = variable + val c = variable + val d = variable + val e = variable + val f = variable + val g = variable + val h = variable + val i = variable + val j = variable + val k = variable + val l = variable + val m = variable + val n = variable + val o = variable + + val x = variable + + val F = function[1] + + val one = variable + val two = variable + val * = SchematicFunctionLabel("*", 2) + val << = SchematicFunctionLabel("<<", 2) + val / = SchematicFunctionLabel("/", 2) + + + val af = formulaVariable + val bf = formulaVariable + val cf = formulaVariable + val df = formulaVariable + val ef = formulaVariable + val ff = formulaVariable + val gf = formulaVariable + val hf = formulaVariable + val if_ = formulaVariable + val jf = formulaVariable + val kf = formulaVariable + val lf = formulaVariable + val mf = formulaVariable + val nf = formulaVariable + val of = formulaVariable + + val xf = formulaVariable + + val Ff = SchematicConnectorLabel("Ff", 1) + val Fp = SchematicPredicateLabel("Fp", 1) + + val onef = formulaVariable + val twof = formulaVariable + val `*f` = SchematicConnectorLabel("*f", 2) + val `<cf, ef <=> ff, if_ <=> kf, mf <=> nf, af <=> bf, + of <=> mf, if_ <=> mf, gf <=> hf, lf <=> kf, bf <=> cf, ff <=> ef, of <=> if_, gf <=> ef, if_ <=> jf) + + val test1 = Theorem(base |- bf <=> cf) { + egraph.proveInnerFormula(bf, cf, base |- ()) + } + + val test2 = Theorem(base |- ff <=> hf) { + egraph.proveInnerFormula(ff, hf, base |- ()) + } + + val test3 = Theorem(base |- if_ <=> of) { + egraph.proveInnerFormula(if_, of, base |- ()) + } + + val test4 = Theorem(base |- of <=> if_) { + egraph.proveInnerFormula(of, if_, base |- ()) + } + + } + + test("4 formulas with congruence test with proofs") { + val egraph = new EGraphExpr() + egraph.add(Ff(af)) + egraph.add(Ff(bf)) + egraph.merge(af, bf) + val test5 = Theorem(af <=> bf |- Ff(af) <=> Ff(bf)) { + egraph.proveInnerFormula(Ff(af), Ff(bf), (af <=> bf) |- ()) + } + } + + test("divide-mult-shift by 2 in formulas egraph test with proofs") { + val egraph = new EGraphExpr() + egraph.add(onef) + egraph.add(twof) + egraph.add(af) + val ax2 = egraph.add(`*f`(af, twof)) + val ax2_d2 = egraph.add(`/f`(`*f`(af, twof), twof)) + val `2d2` = egraph.add(`/f`(twof, twof)) + val ax_2d2 = egraph.add(`*f`(af, `/f`(twof, twof))) + val ax1 = egraph.add(`*f`(af, onef)) + val as1 = egraph.add(`< as1, ax2_d2 <=> ax_2d2, `2d2` <=> onef, ax1 <=> af) + + val one_2d2 = Theorem(base |- onef <=> `2d2`) { + egraph.proveInnerFormula(onef, `2d2`, base |- ()) + } + + val ax2_as1 = Theorem(base |- ax2 <=> as1) { + egraph.proveInnerFormula(ax2, as1, base |- ()) + } + + val ax2_d2_ax_2d2 = Theorem(base |- ax2_d2 <=> ax_2d2) { + egraph.proveInnerFormula(ax2_d2, ax_2d2, base |- ()) + } + + val ax_2d2_ax1 = Theorem(base |- ax_2d2 <=> ax1) { + egraph.proveInnerFormula(ax_2d2, ax1, base |- ()) + } + + val ax_2d2_a = Theorem(base |- ax_2d2 <=> af) { + egraph.proveInnerFormula(ax_2d2, af, base |- ()) + } + + } + + test("long chain of formulas congruence eGraph with proofs") { + val egraph = new EGraphExpr() + egraph.add(xf) + val fx = egraph.add(Ff(xf)) + val ffx = egraph.add(Ff(fx)) + val fffx = egraph.add(Ff(ffx)) + val ffffx = egraph.add(Ff(fffx)) + val fffffx = egraph.add(Ff(ffffx)) + val ffffffx = egraph.add(Ff(fffffx)) + val fffffffx = egraph.add(Ff(ffffffx)) + val ffffffffx = egraph.add(Ff(fffffffx)) + + egraph.merge(ffffffffx, xf) + egraph.merge(fffffx, xf) + + val base = List(ffffffffx <=> xf, fffffx <=> xf) + + val test2 = Theorem(base |- fffx <=> xf) { + egraph.proveInnerFormula(fffx, xf, base |- ()) + } + val test3 = Theorem(base |- ffx <=> xf) { + egraph.proveInnerFormula(ffx, xf, base |- ()) + } + val test4 = Theorem(base |- fx <=> xf) { + egraph.proveInnerFormula(fx, xf, base |- ()) + } + } + + + test("2 terms 6 predicates with congruence egraph test with proofs") { + val egraph = new EGraphExpr() + egraph.add(Ff(Ff(Fp(a)))) + egraph.add(Ff(Ff(Fp(b)))) + egraph.merge(a, b) + + val test5 = Theorem((a === b) |- Fp(a) <=> Fp(b)) { + egraph.proveInnerFormula(Fp(a), Fp(b), (a === b) |- ()) + } + + val test6 = Theorem((a === b) |- Ff(Fp(a)) <=> Ff(Fp(b))) { + egraph.proveInnerFormula(Ff(Fp(a)), Ff(Fp(b)), (a === b) |- ()) + } + + val test7 = Theorem((a === b) |- Ff(Ff(Fp(a))) <=> Ff(Ff(Fp(b))) ) { + egraph.proveInnerFormula(Ff(Ff(Fp(a))), Ff(Ff(Fp(b))), (a === b) |- ()) + } + + } + + test("6 terms 6 predicates with congruence egraph test with proofs") { + val egraph = new EGraphExpr() + egraph.add(Ff(Ff(Fp(F(F(a)))))) + egraph.add(Ff(Ff(Fp(F(F(b)))))) + egraph.merge(a, b) + + val test5 = Theorem((a === b) |- (F(a) === F(b))) { + egraph.proveInnerTerm(F(a), F(b), (a === b) |- ()) + } + + val test6 = Theorem((a === b) |- Fp(F(F(a))) <=> Fp(F(F(b))) ) { + egraph.proveInnerFormula(Fp(F(F(a))), Fp(F(F(b))), (a === b) |- ()) + } + + val test7 = Theorem((a === b) |- Ff(Ff(Fp(F(F(a))))) <=> Ff(Ff(Fp(F(F(b))))) ) { + egraph.proveInnerFormula(Ff(Ff(Fp(F(F(a))))), Ff(Ff(Fp(F(F(b))))), (a === b) |- ()) + } + + egraph.merge(Fp(F(F(b))), Ff(Fp(F(F(a))))) + + val test8 = Theorem(((a === b), Fp(F(F(b))) <=> Ff(Fp(F(F(a)))) ) |- Ff(Ff(Fp(F(F(a))))) <=> Ff(Ff(Fp(F(F(b))))) ) { + egraph.proveInnerFormula(Ff(Ff(Fp(F(F(a))))), Ff(Ff(Fp(F(F(b))))), (a === b, Fp(F(F(b))) <=> Ff(Fp(F(F(a)))) ) |- ()) + } + + } + + test("Full congruence tactic tests") { + println("Full congruence tactic tests\n") + + val base1 = List(a === c, e === f, i === k, m === n, a === b, o === m, i === m, g === h, l === k, b === c, f === e, o === i, g === e, i === j) + + val test1 = Theorem(base1 |- (b === c)) { + have(thesis) by Congruence + } + + val test2 = Theorem(base1 |- (f === h)) { + have(thesis) by Congruence + } + + val test3 = Theorem(base1 |- (i === o)) { + have(thesis) by Congruence + } + + + val ax2 = `*`(a, two) + val ax2_d2 = `/`(`*`(a, two), two) + val `2d2` = `/`(two, two) + val ax_2d2 = `*`(a, `/`(two, two)) + val ax1 = `*`(a, one) + val as1 = `<<`(a, one) + + val base2 = List[Formula](ax2 === as1, ax2_d2 === ax_2d2, `2d2` === one, ax1 === a) + + + val one_2d2 = Theorem(base2 |- (one === `2d2`)) { + have(thesis) by Congruence + } + + val ax2_as1 = Theorem(base2 |- (ax2 === as1)) { + have(thesis) by Congruence + } + + val ax2_d2_ax_2d2 = Theorem(base2 |- (ax2_d2 === ax_2d2)) { + have(thesis) by Congruence + } + + val ax_2d2_ax1 = Theorem(base2 |- (ax_2d2 === ax1)) { + have(thesis) by Congruence + } + + val ax_2d2_a = Theorem(base2 |- (ax_2d2 === a)) { + have(thesis) by Congruence + } + + val ax_2d2_a_2 = Theorem(base2 |- (Fp(ax_2d2) <=> Fp(a))) { + have(thesis) by Congruence + } + + val ax_2d2_a_1 = Theorem((Fp(a) :: base2) |- Fp(ax_2d2)) { + have(thesis) by Congruence + } + + val ax_2d2_a_3 = Theorem((base2 :+ Fp(ax_2d2) :+ !Fp(a)) |- () ) { + have(thesis) by Congruence + } + + val test5 = Theorem(a===b |- F(a) === F(b)) { + have(thesis) by Congruence + } + + val test6 = Theorem(a === b |- F(a) === F(b)) { + have(thesis) by Congruence + } + + val test7 = Theorem((Ff(Ff(Ff(Ff(Ff(Ff(Ff(xf))))))) <=> xf, Ff(Ff(Ff(Ff(Ff(xf))))) <=> xf) |- Ff(Ff(Ff(xf))) <=> xf) { + have(thesis) by Congruence + } + + val test8 = Theorem((Ff(Ff(Ff(Ff(Ff(Ff(Ff(xf))))))) <=> xf, Ff(Ff(Ff(Ff(Ff(xf))))) <=> xf) |- Ff(xf) <=> xf) { + have(thesis) by Congruence + } + + val test9 = Theorem((a === b) |- (Fp(F(F(a))), !Fp(F(F(b)))) ) { + have(thesis) by Congruence + } + + val test10 = Theorem((a === b) |- Fp(F(F(a))) <=> Fp(F(F(b))) ) { + have(thesis) by Congruence + } + + + val test11 = Theorem((a === b) |- Ff(Ff(Fp(F(F(a))))) <=> Ff(Ff(Fp(F(F(b))))) ) { + have(thesis) by Congruence + } + + val test12 = Theorem(((a === b), Fp(F(F(b))) <=> Ff(Fp(F(F(a)))), Ff(Ff(Fp(F(F(a))))) ) |- Ff(Ff(Fp(F(F(b))))) ) { + have(thesis) by Congruence + } + + + } + + +} \ No newline at end of file diff --git a/lisa-sets2/src/test/scala/lisa/automation/TableauTest.scala b/lisa-sets2/src/test/scala/lisa/automation/TableauTest.scala new file mode 100644 index 000000000..69a8d2507 --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/automation/TableauTest.scala @@ -0,0 +1,158 @@ +package lisa.test.automation + +import lisa.SetTheoryLibrary.{_, given} +import lisa.automation.Substitution.* +import lisa.automation.Tableau.* +import lisa.fol.FOL.* +import lisa.prooflib.Exports.* +import lisa.utils.K.SCProofChecker.checkSCProof +import lisa.utils.parsing.FOLPrinter.prettyFormula +import lisa.utils.parsing.FOLPrinter.prettySCProof +import lisa.utils.parsing.FOLPrinter.prettyTerm +import org.scalatest.funsuite.AnyFunSuite + +class TableauTest extends AnyFunSuite { + import TableauTest.* + + test(s"Propositional Positive cases (${posi.size})") { + assert(posi.forall(_._3), posi.map((i, f, b, proof, judg) => s"$i $b" + (if !b then s" $f" else "")).mkString("\n")) + if posi.exists(tup => tup._5.nonEmpty & !tup._5.get.isValid) then + fail("A proof is wrong: " + posi.map(tup => if tup._5.nonEmpty & !tup._5.get.isValid then prettySCProof(tup._5.get) + "\n").mkString("\n")) + } + + test(s"First Order Quantifier Free Positive cases (${posqf.size})") { + assert(posqf.forall(_._3), posqf.map((i, f, b, proof, judg) => (s"$i $b" + (if !b then s" $f" else ""))).mkString("\n")) + if posqf.exists(tup => tup._5.nonEmpty & !tup._5.get.isValid) then + fail("A proof is wrong: " + posi.map(tup => if tup._5.nonEmpty & !tup._5.get.isValid then prettySCProof(tup._5.get) + "\n").mkString("\n")) + } + + test(s"First Order Easy Positive cases (${poseasy.size})") { + assert(poseasy.forall(_._3), poseasy.map((i, f, b, proof, judg) => (s"$i $b" + (if !b then s" $f" else ""))).mkString("\n")) + if poseasy.exists(tup => tup._5.nonEmpty & !tup._5.get.isValid) then + fail("A proof is wrong: " + posi.map(tup => if tup._5.nonEmpty & !tup._5.get.isValid then prettySCProof(tup._5.get) + "\n").mkString("\n")) + } + + test(s"First Order Hard Positive cases (${poshard.size})") { + assert(poshard.forall(_._3), poshard.map((i, f, b, proof, judg) => (s"$i $b" + (if !b then s" $f" else ""))).mkString("\n")) + if poshard.exists(tup => tup._5.nonEmpty & !tup._5.get.isValid) then + fail("A proof is wrong: " + posi.map(tup => if tup._5.nonEmpty & !tup._5.get.isValid then prettySCProof(tup._5.get) + "\n").mkString("\n")) + } + +} +object TableauTest { + + val u = variable + val w = variable + val x = variable + val y = variable + val z = variable + + val a = formulaVariable + val b = formulaVariable + val c = formulaVariable + val d = formulaVariable + val e = formulaVariable + + val f = function[1] + val g = function[1] + val h = function[2] + + val D = predicate[1] + val E = predicate[2] + val P = predicate[1] + val Q = predicate[1] + val R = predicate[2] + + var doprint: Boolean = false + def printif(s: Any) = if doprint then println(s) else () + + val posi = List( + a <=> a, + a \/ !a, + ((a ==> b) /\ (b ==> c)) ==> (a ==> c), + (a <=> b) <=> ((a /\ b) \/ (!a /\ !b)), + ((a ==> c) /\ (b ==> c)) <=> ((a \/ b) ==> c), + ((a ==> b) /\ (c ==> d)) ==> ((a \/ c) ==> (b \/ d)) + ).zipWithIndex.map(f => + val res = solve(() |- f._1) + (f._2, f._1, res.nonEmpty, res, res.map(checkSCProof)) + ) + + // Quantifier Free + + val posqf = List( + posi.map(fo => fo._2.substitute(a := P(h(x, y)), b := P(x), c := R(x, h(x, y)))), + posi.map(fo => fo._2.substitute(a := P(h(x, y)), b := P(h(x, y)), c := R(x, h(x, f(x))))), + posi.map(fo => fo._2.substitute(a := R(y, y), b := P(h(y, y)), c := R(h(x, y), h(z, y)))) + ).flatten.zipWithIndex.map(f => + val res = solve(() |- f._1) + (f._2, f._1, res.nonEmpty, res, res.map(checkSCProof)) + ) + + // First Order Easy + + val poseasy = List( + posi.map(fo => fo._2.substitute(a := forall(x, P(x)), b := forall(x, P(y)), c := exists(x, P(x)))), + posi.map(fo => fo._2.substitute(a := forall(x, P(x) /\ Q(f(x))), b := forall(x, P(x) \/ R(y, x)), c := exists(x, Q(x) ==> R(x, y)))), + posi.map(fo => fo._2.substitute(a := exists(y, forall(x, P(x) /\ Q(f(y)))), b := forall(y, exists(x, P(x) \/ R(y, x))), c := forall(y, exists(x, Q(x) ==> R(x, y))))) + ).flatten.zipWithIndex.map(f => + val res = solve(() |- f._1) + (f._2, f._1, res.nonEmpty, res, res.map(checkSCProof)) + ) + + // First Order Hard, from https://isabelle.in.tum.de/library/FOL/FOL-ex/Quantifiers_Cla.html + + val a1 = forall(x, forall(y, forall(z, ((E(x, y) /\ E(y, z)) ==> E(x, z))))) + val a2 = forall(x, forall(y, (E(x, y) ==> E(f(x), f(y))))) + val a3 = forall(x, E(f(g(x)), g(f(x)))) + val biga = forall( + x, + forall( + y, + forall( + z, + ((E(x, y) /\ E(y, z)) ==> E(x, z)) /\ + (E(x, y) ==> E(f(x), f(y))) /\ + E(f(g(x)), g(f(x))) + ) + ) + ) + + val poshard = List( + forall(x, P(x) ==> Q(x)) ==> (forall(x, P(x)) ==> forall(x, Q(x))), + forall(x, forall(y, R(x, y))) ==> forall(y, forall(x, R(x, y))), + forall(x, forall(y, R(x, y))) ==> forall(y, forall(x, R(y, x))), + exists(x, exists(y, R(x, y))) ==> exists(y, exists(x, R(x, y))), + exists(x, exists(y, R(x, y))) ==> exists(y, exists(x, R(y, x))), + (forall(x, P(x)) \/ forall(x, Q(x))) ==> forall(x, P(x) \/ Q(x)), + forall(x, a ==> Q(x)) <=> (a ==> forall(x, Q(x))), + forall(x, P(x) ==> a) <=> (exists(x, P(x)) ==> a), + exists(x, P(x) \/ Q(x)) <=> (exists(x, P(x)) \/ exists(x, Q(x))), + exists(x, P(x) /\ Q(x)) ==> (exists(x, P(x)) /\ exists(x, Q(x))), + exists(y, forall(x, R(x, y))) ==> forall(x, exists(y, R(x, y))), + forall(x, Q(x)) ==> exists(x, Q(x)), + (forall(x, P(x) ==> Q(x)) /\ exists(x, P(x))) ==> exists(x, Q(x)), + ((a ==> exists(x, Q(x))) /\ a) ==> exists(x, Q(x)), + forall(x, P(x) ==> Q(f(x))) /\ forall(x, Q(x) ==> R(g(x), x)) ==> (P(y) ==> R(g(f(y)), f(y))), + forall(x, forall(y, P(x) ==> Q(y))) ==> (exists(x, P(x)) ==> forall(y, Q(y))), + (exists(x, P(x)) ==> forall(y, Q(y))) ==> forall(x, forall(y, P(x) ==> Q(y))), + forall(x, forall(y, P(x) ==> Q(y))) <=> (exists(x, P(x)) ==> forall(y, Q(y))), + exists(x, exists(y, P(x) /\ R(x, y))) ==> (exists(x, P(x) /\ exists(y, R(x, y)))), + (exists(x, P(x) /\ exists(y, R(x, y)))) ==> exists(x, exists(y, P(x) /\ R(x, y))), + exists(x, exists(y, P(x) /\ R(x, y))) <=> (exists(x, P(x) /\ exists(y, R(x, y)))), + exists(y, forall(x, P(x) ==> R(x, y))) ==> (forall(x, P(x)) ==> exists(y, R(x, y))), + forall(x, P(x)) ==> P(y), + !forall(x, D(x) /\ !D(f(x))), + !forall(x, (D(x) /\ !D(f(x))) \/ (D(x) /\ !D(x))), + forall(x, forall(y, (E(x, y) ==> E(f(x), f(y))) /\ E(f(g(x)), g(f(x))))) ==> E(f(f(g(u))), f(g(f(u)))), + !(forall(x, !((E(f(x), x)))) /\ forall(x, forall(y, !(E(x, y)) /\ E(f(x), g(x))))), + a1 /\ a2 /\ a3 ==> E(f(f(g(u))), f(g(f(u)))), + a1 /\ a2 /\ a3 ==> E(f(g(f(u))), g(f(f(u)))), + biga ==> E(f(f(g(u))), f(g(f(u)))), + biga ==> E(f(g(f(u))), g(f(f(u)))) + ).zipWithIndex.map(f => + val res = solve(() |- f._1) + (f._2, f._1, res.nonEmpty, res, res.map(checkSCProof)) + ) + +} diff --git a/lisa-sets2/src/test/scala/lisa/examples/peano_example/Peano.scala b/lisa-sets2/src/test/scala/lisa/examples/peano_example/Peano.scala new file mode 100644 index 000000000..c84112f70 --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/examples/peano_example/Peano.scala @@ -0,0 +1,344 @@ +package lisa.examples.peano_example + +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.RunningTheory +import lisa.kernel.proof.SCProof +import lisa.kernel.proof.SequentCalculus.* +import lisa.prooflib.Library +import lisa.prooflib.OutputManager +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.Printer + +object Peano { /* + export PeanoArithmeticsLibrary.{_, given} + + /////////////////////////// OUTPUT CONTROL ////////////////////////// + given om:OutputManager = new OutputManager { + override val output: String => Unit = println + override val finishOutput: Throwable => Nothing = e => throw e + } + + def main(args: Array[String]): Unit = {} + ///////////////////////////////////////////////////////////////////// + + def instantiateForallImport(proofImport: Sequent, t: Peano.Term): SCProof = { + require(proofImport.right.size == 1) + val formula = proofImport.right.head + require(formula.isInstanceOf[BinderFormula] && formula.asInstanceOf[BinderFormula].label == Forall) + val forall = formula.asInstanceOf[BinderFormula] + instantiateForall(SCProof(IndexedSeq(), IndexedSeq(proofImport))) + val tempVar = VariableLabel(freshId(formula.freeVariables.map(_.id), "x")) + val instantiated = instantiateBinder(forall, t) + val p1 = SC.Hypothesis(instantiated |- instantiated, instantiated) + val p2 = SC.LeftForall(formula |- instantiated, 0, instantiateBinder(forall, tempVar), tempVar, t) + val p3 = SC.Cut(() |- instantiated, -1, 1, formula) + SCProof(IndexedSeq(p1, p2, p3), IndexedSeq(proofImport)) + } + + def applyInduction(baseProof: SC.SCSubproof, inductionStepProof: SC.SCSubproof, inductionInstance: SCProofStep): IndexedSeq[SCProofStep] = { + require(baseProof.bot.right.size == 1, s"baseProof should prove exactly one formula, got ${FOLPrinter.prettySequent(baseProof.bot)}") + require(inductionStepProof.bot.right.size == 1, s"inductionStepProof should prove exactly one formula, got ${FOLPrinter.prettySequent(inductionStepProof.bot)}") + require( + inductionInstance.bot.left.isEmpty && inductionInstance.bot.right.size == 1, + s"induction instance step should have nothing on the left and exactly one formula on the right, got ${FOLPrinter.prettySequent(inductionInstance.bot)}" + ) + val (premise, conclusion) = (inductionInstance.bot.right.head match { + case ConnectorFormula(Implies, Seq(premise, conclusion)) => (premise, conclusion) + case _ => require(false, s"induction instance should be of the form A => B, got ${FOLPrinter.prettyFormula(inductionInstance.bot.right.head)}") + }): @unchecked + val baseFormula = baseProof.bot.right.head + val stepFormula = inductionStepProof.bot.right.head + require( + isSame(baseFormula /\ stepFormula, premise), + "induction instance premise should be of the form base /\\ step, got " + + s"premise: ${FOLPrinter.prettyFormula(premise)}, base: ${FOLPrinter.prettyFormula(baseFormula)}, step: ${FOLPrinter.prettyFormula(stepFormula)}" + ) + + val lhs: Set[Formula] = baseProof.bot.left ++ inductionStepProof.bot.left + val base0 = baseProof + val step1 = inductionStepProof + val instance2 = inductionInstance + val inductionPremise3 = SC.RightAnd(lhs |- baseFormula /\ stepFormula, Seq(0, 1), Seq(baseFormula, stepFormula)) + val hypConclusion4 = hypothesis(conclusion) + val inductionInstanceOnTheLeft5 = SC.LeftImplies(lhs + (premise ==> conclusion) |- conclusion, 3, 4, premise, conclusion) + val cutInductionInstance6 = SC.Cut(lhs |- conclusion, 2, 5, premise ==> conclusion) + IndexedSeq(base0, step1, instance2, inductionPremise3, hypConclusion4, inductionInstanceOnTheLeft5, cutInductionInstance6) + } + + val (y1, z1) = + (VariableLabel("y1"), VariableLabel("z1")) + + THEOREM("x + 0 = 0 + x") of "∀'x. +('x, 0) = +(0, 'x)" PROOF2 { + val refl0: SCProofStep = SC.RightRefl(() |- s(x) === s(x), s(x) === s(x)) + val subst1 = SC.RightSubstEq((x === plus(zero, x)) |- s(x) === s(plus(zero, x)), 0, (x, plus(zero, x)) :: Nil, LambdaTermFormula(Seq(y), s(x) === s(y))) + val implies2 = SC.RightImplies(() |- (x === plus(zero, x)) ==> (s(x) === s(plus(zero, x))), 1, x === plus(zero, x), s(x) === s(plus(zero, x))) + val transform3 = SC.RightSubstEq( + (plus(zero, s(x)) === s(plus(zero, x))) |- (x === plus(zero, x)) ==> (s(x) === plus(zero, s(x))), + 2, + (plus(zero, s(x)), s(plus(zero, x))) :: Nil, + LambdaTermFormula(Seq(y), (x === plus(zero, x)) ==> (s(x) === y)) + ) + + // result: ax4plusSuccessor |- 0+Sx = S(0 + x) + val instanceAx4_4 = SC.SCSubproof( + instantiateForall(instantiateForallImport(ax"ax4plusSuccessor", zero), x), + Seq(-1) + ) + val cut5 = SC.Cut(() |- (x === plus(zero, x)) ==> (s(x) === plus(zero, s(x))), 4, 3, plus(zero, s(x)) === s(plus(zero, x))) + + val transform6 = SC.RightSubstEq( + Set(plus(x, zero) === x, plus(s(x), zero) === s(x)) |- (plus(x, zero) === plus(zero, x)) ==> (plus(s(x), zero) === plus(zero, s(x))), + 5, + (plus(x, zero), x) :: (plus(s(x), zero), s(x)) :: Nil, + LambdaTermFormula(Seq(y, z), (y === plus(zero, x)) ==> (z === plus(zero, s(x)))) + ) + val leftAnd7 = SC.LeftAnd( + (plus(x, zero) === x) /\ (plus(s(x), zero) === s(x)) |- (plus(x, zero) === plus(zero, x)) ==> (plus(s(x), zero) === plus(zero, s(x))), + 6, + plus(x, zero) === x, + plus(s(x), zero) === s(x) + ) + + val instancePlusZero8 = SC.SCSubproof( + instantiateForallImport(ax"ax3neutral", x), + Seq(-2) + ) + val instancePlusZero9 = SC.SCSubproof( + instantiateForallImport(ax"ax3neutral", s(x)), + Seq(-2) + ) + val rightAnd10 = SC.RightAnd(() |- (plus(x, zero) === x) /\ (plus(s(x), zero) === s(x)), Seq(8, 9), Seq(plus(x, zero) === x, plus(s(x), zero) === s(x))) + + val cut11 = SC.Cut( + () |- (plus(x, zero) === plus(zero, x)) ==> (plus(s(x), zero) === plus(zero, s(x))), + 10, + 7, + (plus(x, zero) === x) /\ (plus(s(x), zero) === s(x)) + ) + + val forall12 = SC.RightForall( + cut11.bot.left |- forall(x, (plus(x, zero) === plus(zero, x)) ==> (plus(s(x), zero) === plus(zero, s(x)))), + 11, + (plus(x, zero) === plus(zero, x)) ==> (plus(s(x), zero) === plus(zero, s(x))), + x + ) + + val inductionInstance: SCProofStep = SC.InstSchema( + () |- ((plus(zero, zero) === plus(zero, zero)) /\ forall(x, (plus(x, zero) === plus(zero, x)) ==> (plus(s(x), zero) === plus(zero, s(x))))) ==> forall( + x, + plus(x, zero) === plus(zero, x) + ), + -3, + Map(sPhi -> LambdaTermFormula(Seq(x), plus(x, zero) === plus(zero, x))) + ) + + SCProof( + applyInduction( + SC.SCSubproof(SCProof(SC.RightRefl(() |- zero === zero, zero === zero))), + SC.SCSubproof( + SCProof( + IndexedSeq(refl0, subst1, implies2, transform3, instanceAx4_4, cut5, transform6, leftAnd7, instancePlusZero8, instancePlusZero9, rightAnd10, cut11, forall12), + IndexedSeq(ax"ax4plusSuccessor", ax"ax3neutral") + ), + Seq(-1, -2) + ), + inductionInstance + ), + IndexedSeq(ax"ax4plusSuccessor", ax"ax3neutral", ax"ax7induction") + ) + } using (ax"ax4plusSuccessor", ax"ax3neutral", ax"ax7induction") + show + + THEOREM("switch successor") of "∀'x. ∀'y. +('x, S('y)) = +(S('x), 'y)" PROOF2 { + //////////////////////////////////// Base: x + S0 = Sx + 0 /////////////////////////////////////////////// + val base0 = { + // x + 0 = x + val xEqXPlusZero0 = SC.SCSubproof(instantiateForallImport(ax"ax3neutral", x), IndexedSeq(-1)) + // Sx + 0 = Sx + val succXEqSuccXPlusZero1 = SC.SCSubproof(instantiateForallImport(ax"ax3neutral", s(x)), IndexedSeq(-1)) + // x + S0 = S(x + 0) + val xPlusSuccZero2 = SC.SCSubproof(instantiateForall(instantiateForallImport(ax"ax4plusSuccessor", x), zero), IndexedSeq(-2)) + + // ------------------- x + 0 = x, Sx + 0 = Sx, x + S0 = S(x + 0) |- Sx + 0 = x + S0 --------------------- + val succX3 = SC.RightRefl(() |- s(x) === s(x), s(x) === s(x)) + val substEq4 = SC.RightSubstEq( + Set(s(x) === plus(s(x), zero), x === plus(x, zero)) |- plus(s(x), zero) === s(plus(x, zero)), + 3, + (s(x), plus(s(x), zero)) :: (VariableTerm(x), plus(x, zero)) :: Nil, + LambdaTermFormula(Seq(y, z), y === s(z)) + ) + val substEq5 = SC.RightSubstEq( + Set(s(x) === plus(s(x), zero), x === plus(x, zero), s(plus(x, zero)) === plus(x, s(zero))) |- plus(s(x), zero) === plus(x, s(zero)), + 4, + (s(plus(x, zero)), plus(x, s(zero))) :: Nil, + LambdaTermFormula(Seq(z), plus(s(x), zero) === z) + ) + // ------------------------------------------------------------------------------------------------------- + val cut6 = SC.Cut(Set(s(x) === plus(s(x), zero), x === plus(x, zero)) |- plus(s(x), zero) === plus(x, s(zero)), 2, 5, s(plus(x, zero)) === plus(x, s(zero))) + val cut7 = SC.Cut(x === plus(x, zero) |- plus(s(x), zero) === plus(x, s(zero)), 1, 6, s(x) === plus(s(x), zero)) + val cut8 = SC.Cut(() |- plus(s(x), zero) === plus(x, s(zero)), 0, 7, x === plus(x, zero)) + SC.SCSubproof( + SCProof( + IndexedSeq(xEqXPlusZero0, succXEqSuccXPlusZero1, xPlusSuccZero2, succX3, substEq4, substEq5, cut6, cut7, cut8), + IndexedSeq(ax"ax3neutral", ax"ax4plusSuccessor") + ), + IndexedSeq(-1, -2) + ) + } + + /////////////// Induction step: ?y. (x + Sy === Sx + y) ==> (x + SSy === Sx + Sy) //////////////////// + val inductionStep1 = { + // x + SSy = S(x + Sy) + val moveSuccessor0 = SC.SCSubproof(instantiateForall(instantiateForallImport(ax"ax4plusSuccessor", x), s(y)), IndexedSeq(-2)) + + // Sx + Sy = S(Sx + y) + val moveSuccessor1 = SC.SCSubproof(instantiateForall(instantiateForallImport(ax"ax4plusSuccessor", s(x)), y), IndexedSeq(-2)) + + // ----------- x + SSy = S(x + Sy), x + Sy = Sx + y, S(Sx + y) = Sx + Sy |- x + SSy = Sx + Sy ------------ + val middleEq2 = SC.RightRefl(() |- s(plus(x, s(y))) === s(plus(x, s(y))), s(plus(x, s(y))) === s(plus(x, s(y)))) + val substEq3 = + SC.RightSubstEq( + Set(plus(x, s(y)) === plus(s(x), y)) |- s(plus(x, s(y))) === s(plus(s(x), y)), + 2, + (plus(x, s(y)), plus(s(x), y)) :: Nil, + LambdaTermFormula(Seq(z1), s(plus(x, s(y))) === s(z1)) + ) + val substEq4 = + SC.RightSubstEq( + Set(plus(x, s(y)) === plus(s(x), y), plus(x, s(s(y))) === s(plus(x, s(y)))) |- plus(x, s(s(y))) === s(plus(s(x), y)), + 3, + (plus(x, s(s(y))), s(plus(x, s(y)))) :: Nil, + LambdaTermFormula(Seq(z1), z1 === s(plus(s(x), y))) + ) + val substEq5 = + SC.RightSubstEq( + Set(plus(x, s(s(y))) === s(plus(x, s(y))), plus(x, s(y)) === plus(s(x), y), s(plus(s(x), y)) === plus(s(x), s(y))) |- plus(x, s(s(y))) === plus(s(x), s(y)), + 4, + (s(plus(s(x), y)), plus(s(x), s(y))) :: Nil, + LambdaTermFormula(Seq(z1), plus(x, s(s(y))) === z1) + ) + // ------------------------------------------------------------------------------------------------------- + val cut6 = SC.Cut(Set(plus(x, s(y)) === plus(s(x), y), s(plus(s(x), y)) === plus(s(x), s(y))) |- plus(x, s(s(y))) === plus(s(x), s(y)), 0, 5, plus(x, s(s(y))) === s(plus(x, s(y)))) + val cut7 = SC.Cut(plus(x, s(y)) === plus(s(x), y) |- plus(x, s(s(y))) === plus(s(x), s(y)), 1, 6, s(plus(s(x), y)) === plus(s(x), s(y))) + val implies8 = SC.RightImplies(() |- (plus(x, s(y)) === plus(s(x), y)) ==> (plus(x, s(s(y))) === plus(s(x), s(y))), 7, plus(x, s(y)) === plus(s(x), y), plus(x, s(s(y))) === plus(s(x), s(y))) + val forall9 = SC.RightForall( + () |- forall(y, (plus(x, s(y)) === plus(s(x), y)) ==> (plus(x, s(s(y))) === plus(s(x), s(y)))), + 8, + (plus(x, s(y)) === plus(s(x), y)) ==> (plus(x, s(s(y))) === plus(s(x), s(y))), + y + ) + SC.SCSubproof( + SCProof( + IndexedSeq(moveSuccessor0, moveSuccessor1, middleEq2, substEq3, substEq4, substEq5, cut6, cut7, implies8, forall9), + IndexedSeq(ax"ax3neutral", ax"ax4plusSuccessor") + ), + IndexedSeq(-1, -2) + ) + } + + val inductionInstance = { + val inductionOnY0 = SC.Rewrite(() |- (sPhi(zero) /\ forall(y, sPhi(y) ==> sPhi(s(y)))) ==> forall(y, sPhi(y)), -1) + val inductionInstance1 = SC.InstSchema( + () |- + ((plus(s(x), zero) === plus(x, s(zero))) /\ + forall(y, (plus(x, s(y)) === plus(s(x), y)) ==> (plus(x, s(s(y))) === plus(s(x), s(y))))) ==> + forall(y, plus(x, s(y)) === plus(s(x), y)), + 0, + Map(sPhi -> LambdaTermFormula(Seq(y), plus(x, s(y)) === plus(s(x), y))) + ) + SC.SCSubproof(SCProof(IndexedSeq(inductionOnY0, inductionInstance1), IndexedSeq(ax"ax7induction")), Seq(-3)) + } + val inductionApplication = applyInduction(base0, inductionStep1, inductionInstance) + val addForall = SC.RightForall(() |- forall(x, forall(y, plus(x, s(y)) === plus(s(x), y))), inductionApplication.size - 1, forall(y, plus(x, s(y)) === plus(s(x), y)), x) + val proof: SCProof = SCProof( + inductionApplication :+ addForall, + IndexedSeq(ax"ax3neutral", ax"ax4plusSuccessor", ax"ax7induction") + ) + proof + } using (ax"ax3neutral", ax"ax4plusSuccessor", ax"ax7induction") + show + + THEOREM("additivity of addition") of "" PROOF2 { + val base0 = SC.SCSubproof(instantiateForallImport(thm"x + 0 = 0 + x", x), Seq(-3)) + val inductionStep1 = { + val start0 = SC.RightRefl(() |- plus(x, s(y)) === plus(x, s(y)), plus(x, s(y)) === plus(x, s(y))) + val applyPlusSuccAx1 = + SC.RightSubstEq(plus(x, s(y)) === s(plus(x, y)) |- plus(x, s(y)) === s(plus(x, y)), 0, (plus(x, s(y)), s(plus(x, y))) :: Nil, LambdaTermFormula(Seq(z1), plus(x, s(y)) === z1)) + val applyInductionPremise2 = + SC.RightSubstEq( + Set(plus(x, s(y)) === s(plus(x, y)), plus(x, y) === plus(y, x)) |- plus(x, s(y)) === s(plus(y, x)), + 1, + (plus(x, y), plus(y, x)) :: Nil, + LambdaTermFormula(Seq(z1), plus(x, s(y)) === s(z1)) + ) + val applyPlusSuccAx3 = + SC.RightSubstEq( + Set(plus(x, s(y)) === s(plus(x, y)), plus(x, y) === plus(y, x), s(plus(y, x)) === plus(y, s(x))) |- plus(x, s(y)) === plus(y, s(x)), + 2, + (s(plus(y, x)), plus(y, s(x))) :: Nil, + LambdaTermFormula(Seq(z1), plus(x, s(y)) === z1) + ) + val applySwitchSuccessor4 = + SC.RightSubstEq( + Set(plus(x, s(y)) === s(plus(x, y)), plus(x, y) === plus(y, x), s(plus(y, x)) === plus(y, s(x)), plus(y, s(x)) === plus(s(y), x)) |- plus(x, s(y)) === plus(s(y), x), + 3, + (plus(y, s(x)), plus(s(y), x)) :: Nil, + LambdaTermFormula(Seq(z1), plus(x, s(y)) === z1) + ) + + val xPlusSYInstance5 = SC.SCSubproof(instantiateForall(instantiateForallImport(ax"ax4plusSuccessor", x), y), Seq(-1)) + val cutXPlusSY6 = + SC.Cut(Set(plus(x, y) === plus(y, x), s(plus(y, x)) === plus(y, s(x)), plus(y, s(x)) === plus(s(y), x)) |- plus(x, s(y)) === plus(s(y), x), 5, 4, plus(x, s(y)) === s(plus(x, y))) + val yPlusSXInstance7 = SC.SCSubproof(instantiateForall(instantiateForallImport(ax"ax4plusSuccessor", y), x), Seq(-1)) + val cutYPlusSX8 = SC.Cut(Set(plus(x, y) === plus(y, x), plus(y, s(x)) === plus(s(y), x)) |- plus(x, s(y)) === plus(s(y), x), 7, 6, s(plus(y, x)) === plus(y, s(x))) + val swichSuccessorInstance9 = SC.SCSubproof(instantiateForall(instantiateForallImport(thm"switch successor", y), x), Seq(-2)) + val cutSwitchSuccessor10 = SC.Cut(plus(x, y) === plus(y, x) |- plus(x, s(y)) === plus(s(y), x), 9, 8, plus(y, s(x)) === plus(s(y), x)) + val rightImplies11 = SC.RightImplies(() |- (plus(x, y) === plus(y, x)) ==> (plus(x, s(y)) === plus(s(y), x)), 10, plus(x, y) === plus(y, x), plus(x, s(y)) === plus(s(y), x)) + val forall12 = SC.RightForall(() |- forall(y, (plus(x, y) === plus(y, x)) ==> (plus(x, s(y)) === plus(s(y), x))), 11, (plus(x, y) === plus(y, x)) ==> (plus(x, s(y)) === plus(s(y), x)), y) + SC.SCSubproof( + SCProof( + IndexedSeq( + start0, + applyPlusSuccAx1, + applyInductionPremise2, + applyPlusSuccAx3, + applySwitchSuccessor4, + xPlusSYInstance5, + cutXPlusSY6, + yPlusSXInstance7, + cutYPlusSX8, + swichSuccessorInstance9, + cutSwitchSuccessor10, + rightImplies11, + forall12 + ), + IndexedSeq(ax"ax4plusSuccessor", thm"switch successor") + ), + IndexedSeq(-1, -4) + ) + } + + val inductionInstance = { + val inductionOnY0 = SC.Rewrite(() |- (sPhi(zero) /\ forall(y, sPhi(y) ==> sPhi(s(y)))) ==> forall(y, sPhi(y)), -1) + val inductionInstance1 = SC.InstSchema( + () |- + ((plus(x, zero) === plus(zero, x)) /\ + forall(y, (plus(x, y) === plus(y, x)) ==> (plus(x, s(y)) === plus(s(y), x)))) ==> + forall(y, plus(x, y) === plus(y, x)), + 0, + Map(sPhi -> LambdaTermFormula(Seq(y), plus(x, y) === plus(y, x))) + ) + SC.SCSubproof(SCProof(IndexedSeq(inductionOnY0, inductionInstance1), IndexedSeq(ax"ax7induction")), Seq(-2)) + } + val inductionApplication = applyInduction(base0, inductionStep1, inductionInstance) + val addForall = SC.RightForall(() |- forall(x, forall(y, plus(x, y) === plus(y, x))), inductionApplication.size - 1, forall(y, plus(x, y) === plus(y, x)), x) + val proof: SCProof = SCProof( + inductionApplication :+ addForall, + IndexedSeq(ax"ax4plusSuccessor", ax"ax7induction", thm"x + 0 = 0 + x", thm"switch successor") + ) + proof + } using (ax"ax4plusSuccessor", ax"ax7induction", thm"x + 0 = 0 + x", thm"switch successor") + show + + */ +} diff --git a/lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmetics.scala b/lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmetics.scala new file mode 100644 index 000000000..e422b6f15 --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmetics.scala @@ -0,0 +1,39 @@ +package lisa.examples.peano_example + +import lisa.kernel.fol.FOL.* +import lisa.kernel.proof.RunningTheory +import lisa.utils.KernelHelpers.{_, given} + +object PeanoArithmetics extends lisa.prooflib.Library { + export lisa.fol.FOL.{*, given} + final val (x, y, z) = + (variable, variable, variable) + + final val zero = Constant("0") + final val s = ConstantFunctionLabel("S", 1) + final val plus = ConstantFunctionLabel("+", 2) + final val times = ConstantFunctionLabel("*", 2) + final val sPhi = predicate[1] + val theory: RunningTheory = new RunningTheory() + final val ax1ZeroSuccessor: Formula = forall(x, !(s(x) === zero)) + final val ax2Injectivity: Formula = forall(x, forall(y, (s(x) === s(y)) ==> (x === y))) + final val ax3neutral: Formula = forall(x, plus(x, zero) === x) + final val ax4plusSuccessor: Formula = forall(x, forall(y, plus(x, s(y)) === s(plus(x, y)))) + final val ax5timesZero: Formula = forall(x, times(x, zero) === zero) + final val ax6timesDistrib: Formula = forall(x, forall(y, times(x, s(y)) === plus(times(x, y), x))) + final val ax7induction: Formula = (sPhi(zero) /\ forall(x, sPhi(x) ==> sPhi(s(x)))) ==> forall(x, sPhi(x)) + + final val functions: Set[ConstantTermLabel[?]] = Set(ConstantFunctionLabel("0", 0), s, plus, times) + functions.foreach(l => theory.addSymbol(l.underlyingLabel)) + + private val peanoAxioms: Set[(String, Formula)] = Set( + ("ax1ZeroSuccessor", ax1ZeroSuccessor), + ("ax2Injectivity", ax2Injectivity), + ("ax3neutral", ax3neutral), + ("ax4plusSuccessor", ax4plusSuccessor), + ("ax5timesZero", ax5timesZero), + ("ax6timesDistrib", ax6timesDistrib), + ("ax7induction", ax7induction) + ) + peanoAxioms.foreach(a => theory.addAxiom(a._1, a._2.underlying)) +} diff --git a/lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmeticsLibrary.scala b/lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmeticsLibrary.scala new file mode 100644 index 000000000..3f9d423fe --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmeticsLibrary.scala @@ -0,0 +1,7 @@ +package lisa.examples.peano_example + +import lisa.examples.peano_example + +trait PeanoArithmeticsLibrary extends lisa.prooflib.BasicMain { + export PeanoArithmetics.* +} diff --git a/lisa-sets2/src/test/scala/lisa/proven/InitialProofsTests.scala b/lisa-sets2/src/test/scala/lisa/proven/InitialProofsTests.scala new file mode 100644 index 000000000..9800e4fe9 --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/proven/InitialProofsTests.scala @@ -0,0 +1,20 @@ +package lisa.proven + +import lisa.test.ProofCheckerSuite +import lisa.utils.Printer + +class InitialProofsTests extends ProofCheckerSuite { + import lisa.SetTheoryLibrary.* + + /* + test("File SetTheory initialize well") { + lisa.proven.mathematics.SetTheory + succeed + } + + test("File Mapping initialize well") { + lisa.proven.mathematics.Mapping + succeed + } + */ +} diff --git a/lisa-sets2/src/test/scala/lisa/utilities/ComprehensionsTests.scala b/lisa-sets2/src/test/scala/lisa/utilities/ComprehensionsTests.scala new file mode 100644 index 000000000..7b7a67c38 --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/utilities/ComprehensionsTests.scala @@ -0,0 +1,83 @@ +package lisa.utilities + +import org.scalatest.funsuite.AnyFunSuite + +class ComprehensionsTests extends AnyFunSuite { + + object ComprehensionsTests extends lisa.Main { + import lisa.maths.settheory.SetTheory.* + import lisa.maths.settheory.Comprehensions.* + + private val x = variable + private val x_1 = variable + private val y = variable + private val z = variable + private val s = variable + private val t = variable + private val A = variable + private val B = variable + private val C = variable + private val P = predicate[2] + private val Q = predicate[1] + private val Filter = predicate[1] + private val Map = function[1] + private val f = function[1] + + val singletonMap = Lemma( + ∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1))) <=> (x === f(∅)) + ) { + val s1 = have(∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1))) ==> (x === f(∅))) subproof { + have(x === f(∅) |- x === f(∅)) by Restate + thenHave((x_1 === ∅, x === f(x_1)) |- x === f(∅)) by Substitution.ApplyRules(x_1 === ∅) + thenHave((x_1 === ∅) /\ (x === f(x_1)) |- x === f(∅)) by Restate + thenHave((in(x_1, singleton(∅))) /\ ((x === f(x_1))) |- x === f(∅)) by Substitution.ApplyRules(singletonHasNoExtraElements of (y := x_1, x := ∅)) + thenHave(∃(x_1, in(x_1, singleton(∅)) /\ ((x === f(x_1)))) |- x === f(∅)) by LeftExists + + } + + val s2 = have((x === f(∅)) ==> ∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1)))) subproof { + have(x === f(∅) |- (∅ === ∅) /\ (x === f(∅))) by Restate + thenHave(x === f(∅) |- in(∅, singleton(∅)) /\ (x === f(∅))) by Substitution.ApplyRules(singletonHasNoExtraElements of (y := x_1, x := ∅)) + thenHave(x === f(∅) |- ∃(x_1, in(x_1, singleton(∅)) /\ (x === f(x_1)))) by RightExists + thenHave(thesis) by Restate.from + + } + + have(thesis) by Tautology.from(s1, s2) + } + + val testCollector = Theorem( + ∃(s, ∀(x, in(x, s) <=> (x === f(∅)))) + ) { + val r = singleton(∅).collect(lambda(x, top), f) + + have(in(x, r) <=> (x === f(∅))) by Substitution.ApplyRules(singletonMap)(r.elim(x)) + thenHave(∀(x, in(x, r) <=> (x === f(∅)))) by RightForall + thenHave(thesis) by RightExists + } + + val testMap = Theorem( + ∃(s, ∀(x, in(x, s) <=> (x === f(∅)))) + ) { + val r = singleton(∅).map(f) + have(in(x, r) <=> (x === f(∅))) by Substitution.ApplyRules(singletonMap)(r.elim(x)) + thenHave(∀(x, in(x, r) <=> (x === f(∅)))) by RightForall + thenHave(thesis) by RightExists + } + + val testFilter = Theorem( + ∃(x, Q(x)) |- ∃(z, ∀(t, in(t, z) <=> ∀(y, Q(y) ==> in(t, y)))) + ) { + val s1 = assume(∃(x_1, Q(x_1))) + val x = witness(s1) + val z = x.filter(lambda(t, ∀(y, Q(y) ==> in(t, y)))) + have(∀(y, Q(y) ==> in(t, y)) <=> ((t ∈ x) /\ ∀(y, Q(y) ==> in(t, y)))) by Tableau + have(in(t, z) <=> ∀(y, Q(y) ==> (t ∈ y))) by Substitution.ApplyRules(lastStep)(z.elim(t)) + thenHave(∀(t, in(t, z) <=> ∀(y, Q(y) ==> in(t, y)))) by RightForall + thenHave(thesis) by RightExists + + } + } + assert(ComprehensionsTests.theory.getTheorem("testFilter").nonEmpty) + +} diff --git a/lisa-sets2/src/test/scala/lisa/utilities/SerializationTest.scala b/lisa-sets2/src/test/scala/lisa/utilities/SerializationTest.scala new file mode 100644 index 000000000..29826e761 --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/utilities/SerializationTest.scala @@ -0,0 +1,188 @@ +package lisa.test.utils + +import lisa.automation.Tautology +import lisa.test.automation.TableauTest +import lisa.utils.K +import lisa.utils.K.getJustification +import lisa.utils.K.|- +import lisa.utils.Serialization.* +import org.scalatest.funsuite.AnyFunSuite + +import java.io._ + +//import lisa.automation.TableauTest + +class SerializationTest extends AnyFunSuite { + + val theory = K.RunningTheory() + + val testfile = "SerializationTestuioavrebvtaevslbxfgh" // chances of collision with an existing file is quite low + + def test(proof: K.SCProof, name: String, theory: K.RunningTheory, justs: List[theory.Justification]) = { + thmsToFile(testfile, theory, List((name, proof, justs.map(("test", _))))) + val thm = thmsFromFile(testfile, theory) + File(testfile + ".trees").delete() + File(testfile + ".proof").delete() + thm.head + } + + def testMulti(theory: K.RunningTheory, thms: List[(String, K.SCProof, List[theory.Justification])]) = { + thmsToFile(testfile, theory, thms.map(thm => (thm._1, thm._2, thm._3.map(("test", _))))) + val thm = thmsFromFile(testfile, theory) + File(testfile + ".trees").delete() + File(testfile + ".proof").delete() + thm + } + + test("exporting a proof to a file and back should work, propositional tableau") { + val proofs = TableauTest.posi + proofs.foreach(p => + try { + val proof = p._4.get + val no = p._1 + test(proof, "posi" + no, theory, Nil) + } catch { + case e: Exception => + println("Exception thrown for test case posi" + p._1) + throw e + } + ) + } + + test("exporting a proof to a file and back should work, propositional OL tautology") { + val proofs = TableauTest.posi + proofs.foreach(p => + try { + val formula = p._2 + val no = p._1 + val proof = Tautology.solveSequent(() |- formula.underlying) match { + case Left(proof) => proof + case Right(_) => throw new Exception("OLPropositionalSolver failed to prove a tautology") + } + test(proof, "posiOL" + no, theory, Nil) + } catch { + case e: Exception => + println("Exception thrown for test case posi" + p._1) + throw e + } + ) + } + + test("exporting a proof to a file and back should work, first order quantifier free tableau") { + val proofs = TableauTest.posqf + proofs.foreach(p => + try { + val proof = p._4.get + val no = p._1 + test(proof, "posqf" + no, theory, Nil) + } catch { + case e: Exception => + println("Exception thrown for test case posqf" + p._1) + throw e + } + ) + } + + test("exporting a proof to a file and back should work, first order quantifier free OL tautology") { + val proofs = TableauTest.posqf + proofs.foreach(p => + try { + val formula = p._2 + val no = p._1 + val proof = Tautology.solveSequent(() |- formula.underlying) match { + case Left(proof) => proof + case Right(_) => throw new Exception("OLPropositionalSolver failed to prove a tautology") + } + test(proof, "posqfOL" + no, theory, Nil) + } catch { + case e: Exception => + println("Exception thrown for test case posqf" + p._1) + throw e + } + ) + } + + test("exporting a proof to a file and back should work, first order easy tableau") { + val proofs = TableauTest.poseasy + proofs.foreach(p => + try { + val proof = p._4.get + val no = p._1 + test(proof, "poseasy" + no, theory, Nil) + } catch { + case e: Exception => + println("Exception thrown for test case poseasy" + p._1) + throw e + } + ) + } + + test("exporting a proof to a file and back should work, first order hard tableau") { + val proofs = TableauTest.poshard + proofs.foreach(p => + try { + val proof = p._4.get + val no = p._1 + test(proof, "poshard" + no, theory, Nil) + } catch { + case e: Exception => + println("Exception thrown for test case poshard" + p._1) + throw e + } + ) + } + + test("exporting a proof to a file and back should work, with imports") { + import lisa.maths.settheory.SetTheory as ST + val thms = List( + // ("russelsParadox", ST.russelsParadox), + ("setUnionMembership", ST.setUnionMembership), + ("inductiveSetExists", ST.inductiveSetExists), + ("setWithNoElementsIsEmpty", ST.setWithNoElementsIsEmpty), + ("emptySetIsItsOwnOnlySubset", ST.emptySetIsItsOwnOnlySubset) + ) + thms.foreach(thm => + try { + val proof = thm._2.kernelProof.get + val justifs = thm._2.highProof.get.justifications.map(_.innerJustification) + + test(proof, thm._1 + "_test", ST.theory, justifs) + } catch { + case e: Exception => + println("Exception thrown for string encoding-decoding of theorem " + thm._1) + throw e + } + ) + } + + test("exporting multiple theorems at once to a file and back should work") { + import lisa.maths.settheory.SetTheory as ST + val thms = List( + // ("russelsParadox", ST.russelsParadox), + ("setUnionMembership", ST.setUnionMembership), + ("inductiveSetExists", ST.inductiveSetExists), + ("setWithNoElementsIsEmpty", ST.setWithNoElementsIsEmpty), + ("emptySetIsItsOwnOnlySubset", ST.emptySetIsItsOwnOnlySubset) + ) + + val thmBack = testMulti( + ST.theory, + thms.map(thm => + val proof = thm._2.kernelProof.get + val justifs = thm._2.highProof.get.justifications.map(_.innerJustification) + + (thm._1, proof, justifs) + ) + ) + assert(thmBack.length == thms.length) + thmBack + .zip(thms) + .foreach(pair => { + val thm = pair._1 + val thmOrig = pair._2 + assert(thm._1.name == thmOrig._1) + assert(thm._1.proposition == thmOrig._2.innerJustification.proposition) + }) + } + +} diff --git a/lisa-sets2/src/test/scala/lisa/utilities/SubstitutionTacticTest.scala b/lisa-sets2/src/test/scala/lisa/utilities/SubstitutionTacticTest.scala new file mode 100644 index 000000000..b69b8e4ef --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/utilities/SubstitutionTacticTest.scala @@ -0,0 +1,119 @@ +package lisa.automation + +import lisa.automation.Substitution +import lisa.kernel.proof.RunningTheory +import lisa.kernel.proof.SequentCalculus as SC +import lisa.test.ProofTacticTestLib +import lisa.utils.parsing.FOLParser.* +import lisa.utils.parsing.FOLPrinter.* +import org.scalatest.funsuite.AnyFunSuite + +class SubstitutionTacticTest extends ProofTacticTestLib { + /* + // subst with formula list + test("Tactic Tests: Substitution - From theorems and formulas (LR)") { + val correct = List( + // ( + // "sequent before substitution", "sequent after substitution", + // List("substSequent1", "|- p <=> q", ...), List("substFormula1", "p <=> q", "p = q",...) + // ), + ( + "'P('x) |- 'P('x)", + "'P('x); 'x = 'y |- 'P('y)", + List(), + List("'x = 'y") + ), + ( + "'P('x) |- 'P('x)", + "'P('x); 'x = 'y |- 'P('x)", + List(), + List("'x = 'y") + ), + ( + "'P('x) |- 'P('x)", + "'P('x) |- 'P('y)", + List("|- 'x = 'y"), + List() + ), + ( + "'P('x) |- 'P('x)", + "'P('x) |- 'P('x)", + List("|- 'x = 'y"), + List() + ), + ( + "'P('x) |- 'P('x) \\/ 'R('y)", + "'P('x); 'R('y) <=> 'Q('x) |- 'P('x) \\/ 'Q('x)", + List("|- 'x = 'y"), + List("'R('y) <=> 'Q('x)") + ), + ( + "'P('x) |- 'P('x) \\/ 'R('y)", + "'P('x); 'R('y) <=> 'Q('x) |- 'P('x) \\/ 'Q('x)", + List("|- 'x = 'y", "|- 'R('y) <=> 'Q('x)"), + List() + ), + ( + "'P('x) |- 'P('x) \\/ 'R('y)", + "'P('x); 'R('y) <=> 'Q('x) |- 'P('x) \\/ 'R('y)", + List("|- 'x = 'y"), + List("'R('y) <=> 'Q('x)") + ), + ( + "'P('x) |- 'P('x) \\/ 'R('y)", + "'P('x); 'R('y) <=> 'Q('x) |- 'P('x) \\/ 'R('y)", + List("|- 'x = 'y", "|- 'R('y) <=> 'Q('x)"), + List() + ) + ) + + val incorrect = List( + // ( + // "sequent before substitution", "sequent after substitution", + // List("substSequent1", "|- p <=> q", ...), List("substFormula1", "p <=> q", "p = q",...) + // ), + ( + "'P('x); 'Q('z) |- 'P('x)", + "'P('x); 'Q('z); 'x = 'y |- 'P('y)", + List(), + List("'z = 'y") + ), + ( + "'P('x) |- 'P('x)", + "'P('x); 'x = 'y |- 'P('z)", + List(), + List("'x = 'y") + ), + ( + "'P('x); 'Q('z) |- 'P('x)", + "'P('x); 'Q('z) |- 'P('y)", + List("|- 'x = 'z"), + List() + ), + ( + "'P('x); 'Q('y) |- 'P('x)", + "'P('x); 'Q('y) |- 'P('z)", + List("|- 'x = 'y"), + List() + ), + ( + "'P('x) |- 'P('x) \\/ 'R('y)", + "'P('x) |- 'P('z) \\/ 'Q('x)", + List("|- 'x = 'y"), + List("'R('y) <=> 'Q('x)") + ) + ) + + testTacticCases(using testProof)(correct, incorrect) { (stmt1, stmt2, premiseSequents, formSubsts) => + val prem = introduceSequent(using testProof)(stmt1) + val substPrem: Seq[testProof.Fact | Formula | RunningTheory#Justification] = premiseSequents.map(introduceSequent(using testProof)(_)) + val substForm: Seq[testProof.Fact | Formula | RunningTheory#Justification] = formSubsts.map(parseFormula(_)) + val substJust: Seq[testProof.Fact | Formula | RunningTheory#Justification] = Nil + Substitution + .ApplyRules(using lisa.test.TestTheoryLibrary, testProof)( + (substPrem ++ substForm ++ substJust).asInstanceOf[Seq[testProof.Fact | Formula | RunningTheory#Justification]]: _* + )(prem)(lisa.utils.parsing.FOLParser.parseSequent(stmt2)) + } + } + */ +} diff --git a/lisa-sets2/src/test/scala/lisa/utilities/TestMain.scala b/lisa-sets2/src/test/scala/lisa/utilities/TestMain.scala new file mode 100644 index 000000000..6d93e5d05 --- /dev/null +++ b/lisa-sets2/src/test/scala/lisa/utilities/TestMain.scala @@ -0,0 +1,16 @@ +package lisa + +import lisa.prooflib.* + +trait TestMain extends lisa.Main { + + override val om: OutputManager = new OutputManager { + def finishOutput(exception: Exception): Nothing = { + log(exception) + main(Array[String]()) + throw exception + } + val stringWriter: java.io.StringWriter = new java.io.StringWriter() + } + +} diff --git a/lisa-utils/src/main/scala/lisa/fol/Common.scala b/lisa-utils/src/main/scala/lisa/fol/Common.scala deleted file mode 100644 index ab09773d5..000000000 --- a/lisa-utils/src/main/scala/lisa/fol/Common.scala +++ /dev/null @@ -1,929 +0,0 @@ -package lisa.fol - -import lisa.utils.K -import lisa.utils.UserLisaException - -import scala.annotation.nowarn -import scala.annotation.showAsInfix -import scala.annotation.targetName -import scala.compiletime.ops.int.- - -import K.given_Conversion_Identifier_String - -trait Common { - - //////////////////////////////////////////////// - ////////////// Base Definitions ////////////// - //////////////////////////////////////////////// - - export K.Identifier - type Arity = Int & Singleton - - /** - * Type of sequences of length N - */ - opaque type **[+T, N <: Arity] >: Seq[T] = Seq[T] - object ** { - def apply[T, N <: Arity](args: T*): T ** N = args.toSeq - def unapplySeq[T, N <: Arity](arg: T ** N): Option[Seq[T]] = Some(arg) - } - - extension [T, N <: Arity](self: T ** N) { - def toSeq: Seq[T] = self - - } - - trait WithArity[N <: Arity] { - val arity: N - } - - class BadArityException(msg: String)(using line: sourcecode.Line, file: sourcecode.File) extends UserLisaException(msg) { - def showError: String = msg - } - - def isLegalApplication(withArity: WithArity[?], args: Seq[?]): Boolean = - withArity.arity == -1 || withArity.arity == args.size - - /** - * A container for valid substitutions. For example, if X is a schematic variable and t a term, SubstPair(X, t) is valid. - * If a is a formula, SubstPair(X, a) is not valid - * If P is a schematic predicate of arity N, and L a Lambda of type Term**N |-> Formula, SubstPair(P, L) is valid. - * Etc. SubstPair can be constructed with X := t. - * - * @param _1 The schematic label to substitute - * @param _2 The value to replace it with - */ - class SubstPair private (val _1: SchematicLabel[?], val _2: LisaObject[?]) { - // def toTuple = (_1, _2) - } - object SubstPair { - def apply[S <: LisaObject[S]](_1: SchematicLabel[S], _2: S) = new SubstPair(_1, _2) - } - - given trsubst[S <: LisaObject[S]]: Conversion[(SchematicLabel[S], S), SubstPair] = s => SubstPair(s._1, s._2) - - /** - * A LisaObject is the type for formulas, terms, lambdas. A child of LisaObject is supposed to be parametrized by itself. - * It key property is to define substitution and computation of free scematic symbols. - * The type T denotes the type that the object is guaranteed to keep after a substitution. - * For example, Term <: LisaObject[Term], because a term with some substitution is still a term. - * Similarly, Variable <: LisaObject[Term] because a variable is a term and still is after any substitution. - * However, Variable <: LisaObject[Variable] does not hold because a variable after a substitution is not necessarily a variable anymore. - */ - trait LisaObject[+T <: LisaObject[T]] { - this: T => - - def lift: T & this.type = this - - /** - * Substitution in the LisaObject of schematics symbols by values. It is not guaranteed by the type system that types of schematics and values match, and the substitution can fail if that is the case. - * This is the substitution function that should be implemented. - */ - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): T - def substituteUnsafe2[A <: SchematicLabel[?], B <: LisaObject[B]](map: Map[A, B]): T = substituteUnsafe(map.asInstanceOf) - - /** - * Substitution in the LisaObject of schematics by values, with guaranteed correspondance between the types of schematics and values. - * This is the substitution that should be used when writing proofs. - */ - def substitute(pairs: SubstPair*): T = { - substituteUnsafe(Map(pairs.map(s => (s._1, (s._2: LisaObject[?])))*)) - } - def substituteOne[S <: LisaObject[S]](v: SchematicLabel[S], arg: S): T = substituteUnsafe(Map(v -> arg)) - - /** - * Compute the free schematic symbols in the expression. - */ - def freeSchematicLabels: Set[SchematicLabel[?]] - def freeVariables: Set[Variable] = freeSchematicLabels.collect { case v: Variable => v } - def freeVariableFormulas: Set[VariableFormula] = freeSchematicLabels.collect { case v: VariableFormula => v } - - /** - * Compute the free and non-free schematic symbols in the expression. - */ - def allSchematicLabels: Set[SchematicLabel[?]] - } - - /** - * Base types for LisaObjects: Terms and Formulas. - */ - sealed trait TermOrFormula extends LisaObject[TermOrFormula] {} - - /** - * Constructor types for LISA Objects: Functions into Terms and Formulas. - */ - @showAsInfix - infix trait |->[-I, +O <: LisaObject[O]] extends LisaObject[I |-> O] { - def applyUnsafe(arg: I): O - - } - - /** - * A label is a [[LisaObject]] which is just a name. In general, constant symbols and schematic symbols. - */ - trait Label[-A <: LisaObject[A]] { - this: A & LisaObject[A] => - def liftLabel: LisaObject[?] = this - def id: Identifier - - /** - * Renames the symbol. - */ - def rename(newid: Identifier): Label[A] - - /** - * Renames the symbol with an identifier that is fresh for the given list. - */ - def freshRename(taken: Iterable[Identifier]): Label[A] - - } - - /** - * Schematic labels can be substituted by expressions (LisaObject) of the corresponding type - */ - sealed trait SchematicLabel[-A <: LisaObject[A]] extends Label[A] { - this: A & LisaObject[A] => - - /** - * The schematic label can be substituted by anything of an equivalent type. See [[SubstPair]], [[LisaObject]]. - */ - // type SubstitutionType <: A - def rename(newid: Identifier): SchematicLabel[A] - def freshRename(taken: Iterable[Identifier]): SchematicLabel[A] - - /** - * Helper to build a [[SubstPair]] - */ - def :=(replacement: A) = SubstPair(this, replacement) - - } - - /** - * ConstantLabel represent constants in the theory and can't be freely substituted. - */ - sealed trait ConstantLabel[-A <: LisaObject[A]] extends Label[A] { - this: A & LisaObject[A] => - def rename(newid: Identifier): ConstantLabel[A] - def freshRename(taken: Iterable[Identifier]): ConstantLabel[A] - } - - class TypeError extends Error - - /** - * Can be thrown during an unsafe substitution when the type of a schematic symbol and its substituted value don't match. - */ - class SubstitutionException extends Exception - - /** - * Indicates LisaObjects corresponding directly to a Kernel member - */ - trait Absolute - - //////////////////////////////////// - ////////////// Term ////////////// - //////////////////////////////////// - - /** - * The type of terms, corresponding to [[K.Term]]. It can be either of a [[Variable]], a [[Constant]] - * a [[ConstantFunctionLabel]] or a [[SchematicFunctionLabel]]. - */ - sealed trait Term extends TermOrFormula with LisaObject[Term] { - val underlying: K.Term - val label: TermLabel[?] - val args: Seq[Term] - def toStringSeparated(): String = toString() - } - - /** - * A TermLabel is a [[LisaObject]] of type ((Term ** N) |-> Term), that is represented by a functional label. - * It can be either a [[SchematicFunctionLabel]] or a [[ConstantFunctionLabel]]. It corresponds to [[K.TermLabel]] - */ - sealed trait TermLabel[A <: (Term | (Seq[Term] |-> Term)) & LisaObject[A]] extends Label[A] with Absolute { - this: A & LisaObject[A] => - val arity: Arity - def id: Identifier - val underlyingLabel: K.TermLabel - def applySeq(args: Seq[Term]): Term = this match - case l: Variable => l.applyUnsafe(args) - case l: Constant => l.applyUnsafe(args) - case l: FunctionLabel[?] => l.applyUnsafe(args) - def rename(newid: Identifier): TermLabel[A] - def freshRename(taken: Iterable[Identifier]): TermLabel[A] - def mkString(args: Seq[Term]): String - def mkStringSeparated(args: Seq[Term]): String = mkString(args) - } - - /** - * A constant [[TermLabel]], which can be either a [[Constant]] symbol or a [[ConstantFunctionSymbol]]. Corresponds to a [[K.ConstantFunctionLabel]] - */ - sealed trait ConstantTermLabel[A <: (Term | (Seq[Term] |-> Term)) & LisaObject[A]] extends TermLabel[A] with ConstantLabel[A] { - this: A & LisaObject[A] => - val underlyingLabel: K.ConstantFunctionLabel - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): ConstantTermLabel[A] - override def rename(newid: Identifier): ConstantTermLabel[A] - def freshRename(taken: Iterable[Identifier]): ConstantTermLabel[A] - } - object ConstantTermLabel { - - /** - * Construct a ConstantTermLabel according to arity: - * A [[Constant]] for arity 0, a [[ConstantFunctionLabel]] otherwise. - * @param id The identifier of the new symbol - * @param arity The arity of the new symbol - * @return The new symbol - */ - def apply[N <: Arity](id: Identifier, arity: N): ConstantTermLabelOfArity[N] = arity match { - case a: 0 => Constant(id) - case n: N => ConstantFunctionLabel[N](id, arity) - } - } - - /** - * Types of constant term labels: [[Constant]] for if N = 0, [[ConstantFunctionLabel]] otherwise. - */ - type ConstantTermLabelOfArity[N <: Arity] <: ConstantTermLabel[?] = N match - case 0 => Constant - case N => ConstantFunctionLabel[N] - - /** - * A schematic [[TermLabel]], which can be either a [[Variable]] symbol or a [[SchematicFunctionSymbol]]. Corresponds to a [[K.SchematicFunctionLabel]] - */ - sealed trait SchematicTermLabel[A <: (Term | (Seq[Term] |-> Term)) & LisaObject[A]] extends TermLabel[A] with SchematicLabel[A] { - this: A & LisaObject[A] => - val underlyingLabel: K.SchematicTermLabel - override def rename(newid: Identifier): SchematicTermLabel[A] - def freshRename(taken: Iterable[Identifier]): SchematicTermLabel[A] - } - object SchematicTermLabel { // Companion - /** - * Construct a SchematicTermLabel according to arity: - * A [[Variable]] for arity 0, a [[SchematicFunctionLabel]] otherwise. - * @param id The identifier of the new symbol - * @param arity The arity of the new symbol - * @return The new symbol - */ - def apply[N <: Arity](id: Identifier, arity: N): SchematicFunctionLabelOfArity[N] = arity match { - case a: 0 => new Variable(id) - case n: N => new SchematicFunctionLabel[N](id, arity) - } - } - type SchematicFunctionLabelOfArity[N <: Arity] <: SchematicTermLabel[?] = N match - case 0 => Variable - case N => SchematicFunctionLabel[N] - - /** - * Can be either a [[ConstantFunctionSymbol]] symbol or a [[SchematicFunctionSymbol]]. Corresponds to a [[K.TermLabel]] - */ - sealed trait FunctionLabel[N <: Arity] extends TermLabel[(Term ** N) |-> Term] with ((Term ** N) |-> Term) { - val underlyingLabel: K.TermLabel - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): (Term ** N) |-> Term - def applyUnsafe(args: (Term ** N)): Term = AppliedFunctional(this, args.toSeq) - override def rename(newid: Identifier): FunctionLabel[N] - def freshRename(taken: Iterable[Identifier]): FunctionLabel[N] - } - - /** - * A Variable, corresponding to [[K.VariableLabel]], is a schematic symbol for terms. - * It counts both as the label and as the term itself. - */ - class Variable(val id: Identifier) extends SchematicTermLabel[Term] with Term with Absolute { - val arity: 0 = 0 - val label: Variable = this - val args: Seq[Nothing] = Seq.empty - val underlyingLabel: K.VariableLabel = K.VariableLabel(id) - val underlying = K.VariableTerm(underlyingLabel) - def applyUnsafe(args: Term ** 0) = this - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): Term = { - map.get(this) match { - case Some(subst) => - subst match { - case s: Term => s - case _ => throw SubstitutionException() - } - case None => this - } - } - def freeSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def allSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def rename(newid: Identifier): Variable = Variable(newid) - def freshRename(taken: Iterable[Identifier]): Variable = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = if (args.size == 0) toString() else toString() + "(" + "illegal_arguments: " + args.mkString(", ") + ")" - - def canEqual(that: Any): Boolean = - that.isInstanceOf[Variable] - - // Intentionally avoiding the call to super.equals because no ancestor has overridden equals (see note 7 below) - override def equals(that: Any): Boolean = - that match { - case other: Variable => - ((this eq other) // optional, but highly recommended sans very specific knowledge about this exact class implementation - || (other.canEqual(this) // optional only if this class is marked final - && (hashCode == other.hashCode) // optional, exceptionally execution efficient if hashCode is cached, at an obvious space inefficiency tradeoff - && ((id == other.id)))) - case _ => - false - } - - // Intentionally avoiding the call to super.hashCode because no ancestor has overridden hashCode (see note 7 below) - override def hashCode(): Int = - id.## - } - object Variable { - def unapply(variable: Variable): Option[Identifier] = Some(variable.id) - } - - /** - * A Constant, corresponding to [[K.ConstantLabel]], is a label for terms. - * It counts both as the label and as the term itself. - */ - class Constant(val id: Identifier) extends Term with Absolute with ConstantTermLabel[Constant] with LisaObject[Constant] { - val arity: 0 = 0 - val label: Constant = this - val args: Seq[Nothing] = Seq.empty - val underlyingLabel: K.ConstantFunctionLabel = K.ConstantFunctionLabel(id, 0) - val underlying = K.Term(underlyingLabel, Seq.empty) - def applyUnsafe(args: Term ** 0) = this - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): Constant = this - def freeSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def allSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def rename(newid: Identifier): Constant = Constant(newid) - def freshRename(taken: Iterable[Identifier]): Constant = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = if (args.size == 0) toString() else toString() + "(" + "illegal_arguments: " + args.mkString(", ") + ")" - - def canEqual(that: Any): Boolean = - that.isInstanceOf[Constant] - - // Intentionally avoiding the call to super.equals because no ancestor has overridden equals (see note 7 below) - override def equals(that: Any): Boolean = - that match { - case other: Constant => - ((this eq other) // optional, but highly recommended sans very specific knowledge about this exact class implementation - || (other.canEqual(this) // optional only if this class is marked final - && (hashCode == other.hashCode) // optional, exceptionally execution efficient if hashCode is cached, at an obvious space inefficiency tradeoff - && ((id == other.id)))) - case _ => - false - } - - // Intentionally avoiding the call to super.hashCode because no ancestor has overridden hashCode (see note 7 below) - override def hashCode(): Int = - id.## - } - object Constant { - def unapply(constant: Constant): Option[Identifier] = Some(constant.id) - } - - /** - * A schematic functional label (corresponding to [[K.SchematicFunctionLabel]]) is a functional label and also a schematic label. - * It can be substituted by any expression of type (Term ** N) |-> Term - */ - class SchematicFunctionLabel[N <: Arity](val id: Identifier, val arity: N) extends SchematicTermLabel[(Term ** N) |-> Term] with FunctionLabel[N] { - val underlyingLabel: K.SchematicTermLabel = K.SchematicFunctionLabel(id, arity) - - def unapplySeq(t: AppliedFunctional): Seq[Term] = t match { - case AppliedFunctional(label, args) if (label == this) => args - case _ => Seq.empty - } - @nowarn("msg=the type test for.*cannot be checked at runtime because its type arguments") - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): ((Term ** N) |-> Term) = { - map.get(this) match { - case Some(subst) => - subst match { - case s: ((Term ** N) |-> Term) => s - case _ => throw SubstitutionException() - } - case None => this - } - } - def freeSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def allSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def rename(newid: Identifier): SchematicFunctionLabel[N] = SchematicFunctionLabel(newid, arity) - def freshRename(taken: Iterable[Identifier]): SchematicFunctionLabel[N] = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = toString() + "(" + args.mkString(", ") + ")" - override def mkStringSeparated(args: Seq[Term]): String = mkString(args) - - def canEqual(that: Any): Boolean = - that.isInstanceOf[SchematicFunctionLabel[?]] - - override def equals(that: Any): Boolean = - that match { - case other: SchematicFunctionLabel[_] => - ((this eq other) // optional, but highly recommended sans very specific knowledge about this exact class implementation - || (other.canEqual(this) // optional only if this class is marked final - && (hashCode == other.hashCode) // optional, exceptionally execution efficient if hashCode is cached, at an obvious space inefficiency tradeoff - && ((id == other.id) - && (arity == other.arity)))) - case _ => - false - } - - // Intentionally avoiding the call to super.hashCode because no ancestor has overridden hashCode (see note 7 below) - override def hashCode(): Int = - 31 * ( - id.## - ) + arity.## - } - object SchematicFunctionLabel { - def unapply[N <: Arity](sfl: SchematicFunctionLabel[N]): Option[(Identifier, N)] = Some((sfl.id, sfl.arity)) - } - - /** - * A constant functional label of arity N. - */ - class ConstantFunctionLabel[N <: Arity](val id: Identifier, val arity: N) extends ConstantTermLabel[((Term ** N) |-> Term)] with FunctionLabel[N] { - val underlyingLabel: K.ConstantFunctionLabel = K.ConstantFunctionLabel(id, arity) - private var infix: Boolean = false - def unapplySeq(t: AppliedFunctional): Seq[Term] = t match { - case AppliedFunctional(label, args) if (label == this) => args - case _ => Seq.empty - } - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): ConstantFunctionLabel[N] = this - def freeSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def allSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def rename(newid: Identifier): ConstantFunctionLabel[N] = ConstantFunctionLabel(newid, arity) - def freshRename(taken: Iterable[Identifier]): ConstantFunctionLabel[N] = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = - if (infix & args.size == 2) (args(0).toStringSeparated() + " " + toString() + " " + args(1).toStringSeparated()) else toString() + "(" + args.mkString(", ") + ")" - override def mkStringSeparated(args: Seq[Term]): String = if (infix) "(" + mkString(args) + ")" else mkString(args) - - def canEqual(that: Any): Boolean = - that.isInstanceOf[SchematicFunctionLabel[?]] - - // Intentionally avoiding the call to super.equals because no ancestor has overridden equals (see note 7 below) - override def equals(that: Any): Boolean = - that match { - case other: ConstantFunctionLabel[_] => - ((this eq other) // optional, but highly recommended sans very specific knowledge about this exact class implementation - || (other.canEqual(this) // optional only if this class is marked final - && (hashCode == other.hashCode) // optional, exceptionally execution efficient if hashCode is cached, at an obvious space inefficiency tradeoff - && ((id == other.id) - && (arity == other.arity)))) - case _ => - false - } - - // Intentionally avoiding the call to super.hashCode because no ancestor has overridden hashCode (see note 7 below) - override def hashCode(): Int = - 31 * ( - id.## - ) + arity.## - } - object ConstantFunctionLabel { - def infix[N <: Arity](id: Identifier, arity: N): ConstantFunctionLabel[N] = - val x = ConstantFunctionLabel[N](id, arity) - x.infix = true - x - def unapply[N <: Arity](cfl: ConstantFunctionLabel[N]): Option[(Identifier, N)] = Some((cfl.id, cfl.arity)) - } - - /** - * A term made from a functional label of arity N and N arguments - */ - class AppliedFunctional(val label: FunctionLabel[?], val args: Seq[Term]) extends Term with Absolute { - override val underlying = K.Term(label.underlyingLabel, args.map(_.underlying)) - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): Term = - label.substituteUnsafe(map).applyUnsafe(args.map[Term]((x: Term) => x.substituteUnsafe(map))) - - def freeSchematicLabels: Set[SchematicLabel[?]] = label.freeSchematicLabels ++ args.flatMap(_.freeSchematicLabels) - def allSchematicLabels: Set[SchematicLabel[?]] = label.allSchematicLabels ++ args.flatMap(_.allSchematicLabels) - override def toString: String = label.mkString(args) - override def toStringSeparated(): String = label.mkStringSeparated(args) - - def canEqual(that: Any): Boolean = - that.isInstanceOf[AppliedFunctional] - - // Intentionally avoiding the call to super.equals because no ancestor has overridden equals (see note 7 below) - override def equals(that: Any): Boolean = - that match { - case other: AppliedFunctional => - ((this eq other) // optional, but highly recommended sans very specific knowledge about this exact class implementation - || (other.canEqual(this) // optional only if this class is marked final - && (hashCode == other.hashCode) // optional, exceptionally execution efficient if hashCode is cached, at an obvious space inefficiency tradeoff - && ((label == other.label) - && (args == other.args)))) - case _ => - false - } - - // Intentionally avoiding the call to super.hashCode because no ancestor has overridden hashCode (see note 7 below) - override def hashCode(): Int = - 31 * ( - label.## - ) + args.## - } - object AppliedFunctional { - def unapply(af: AppliedFunctional): Option[(FunctionLabel[?], Seq[Term])] = Some((af.label, af.args)) - } - - ////////////////////////////////////// - ////////////// Formulas ////////////// - ////////////////////////////////////// - - /** - * The type of formulas, corresponding to [[K.Formula]] - */ - sealed trait Formula extends TermOrFormula with LisaObject[Formula] { - val underlying: K.Formula - def toStringSeparated() = toString() - } - - ///////////////////// - // Atomic Formulas // - ///////////////////// - - sealed trait AtomicFormula extends Formula { - val label: AtomicLabel[?] - val args: Seq[Term] - } - - /** - * A AtomicLabel is a [[LisaObject]] of type ((Term ** N) |-> Formula), that is represented by a predicate label. - * It can be either a [[SchematicPredicateLabel]] or a [[ConstantPredicateLabel]]. - */ - sealed trait AtomicLabel[A <: (Formula | (Seq[Term] |-> Formula)) & LisaObject[A]] extends Label[A] with Absolute { - this: A & LisaObject[A] => - val arity: Arity - def id: Identifier - val underlyingLabel: K.AtomicLabel - def applySeq(args: Seq[Term]): Formula = this match - case l: VariableFormula => l.applyUnsafe(args) - case l: ConstantFormula => l.applyUnsafe(args) - case l: PredicateLabel[?] => l.applyUnsafe(args) - - def rename(newid: Identifier): AtomicLabel[A] - def freshRename(taken: Iterable[Identifier]): AtomicLabel[A] - def mkString(args: Seq[Term]): String - def mkStringSeparated(args: Seq[Term]): String = mkString(args) - } - - /** - * A constant [[AtomicLabel]], which can be either a [[ConstantFormula]] symbol or a [[ConstantPredicateSymbol]]. Corresponds to a [[K.ConstantAtomicLabel]] - */ - sealed trait ConstantAtomicLabel[A <: (Formula | (Seq[Term] |-> Formula)) & LisaObject[A]] extends AtomicLabel[A] with ConstantLabel[A] { - this: A & LisaObject[A] => - val underlyingLabel: K.ConstantAtomicLabel - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): ConstantAtomicLabel[A] - override def rename(newid: Identifier): ConstantAtomicLabel[A] - def freshRename(taken: Iterable[Identifier]): ConstantAtomicLabel[A] - } - object ConstantAtomicLabel { - - /** - * Construct a ConstantTermLabel according to arity: - * A [[Constant]] for arity 0, a [[ConstantFunctionLabel]] otherwise. - * @param id The identifier of the new symbol - * @param arity The arity of the new symbol - * @return The new symbol - */ - def apply[N <: Arity](id: Identifier, arity: N): ConstantAtomicLabelOfArity[N] = arity match { - case a: 0 => ConstantFormula(id) - case n: N => ConstantPredicateLabel[N](id, arity) - } - } - - /** - * Types of constant atomic labels: [[ConstantFormula]] for if N = 0, [[ConstantPredicateLabel]] otherwise. - */ - type ConstantAtomicLabelOfArity[N <: Arity] <: ConstantAtomicLabel[?] = N match { - case 0 => ConstantFormula - case N => ConstantPredicateLabel[N] - } - - /** - * A schematic [[AtomicLabel]], which can be either a [[VariableFormula]] symbol or a [[SchematicPredicateLabel]]. Corresponds to a [[K.SchematicAtomicLabel]] - */ - sealed trait SchematicAtomicLabel[A <: (Formula | (Seq[Term] |-> Formula)) & LisaObject[A]] extends AtomicLabel[A] with SchematicLabel[A] { - this: A & LisaObject[A] => - val underlyingLabel: K.SchematicAtomicLabel - override def rename(newid: Identifier): SchematicAtomicLabel[A] - def freshRename(taken: Iterable[Identifier]): SchematicAtomicLabel[A] - - } - object SchematicAtomicLabel { // Companion - /** - * Construct a SchematicTermLabel according to arity: - * A [[Variable]] for arity 0, a [[SchematicFunctionLabel]] otherwise. - * @param id The identifier of the new symbol - * @param arity The arity of the new symbol - * @return The new symbol - */ - def apply[N <: Arity](id: Identifier, arity: N): SchematicAtomicLabelOfArity[N] = arity match { - case a: 0 => new VariableFormula(id) - case n: N => new SchematicPredicateLabel[N](id, arity) - } - } - - type SchematicAtomicLabelOfArity[N <: Arity] <: SchematicAtomicLabel[?] = N match { - case 0 => VariableFormula - case N => SchematicPredicateLabel[N] - } - - /** - * Can be either a [[ConstantFunctionSymbol]] symbol or a [[SchematicFunctionSymbol]]. Corresponds to a [[K.TermLabel]] - */ - sealed trait PredicateLabel[N <: Arity] extends AtomicLabel[(Term ** N) |-> Formula] with ((Term ** N) |-> Formula) with Absolute { - val underlyingLabel: K.AtomicLabel - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): (Term ** N) |-> Formula - def applyUnsafe(args: (Term ** N)): Formula = AppliedPredicate(this, args.toSeq) - override def rename(newid: Identifier): PredicateLabel[N] - def freshRename(taken: Iterable[Identifier]): PredicateLabel[N] - } - - /** - * A Variable for formulas, corresponding to [[K.VariableFormulaLabel]], is a schematic symbol for formulas. - * It counts both as the label and as the term itself. - */ - case class VariableFormula(id: Identifier) extends SchematicAtomicLabel[Formula] with AtomicFormula with Absolute { - override val arity: 0 = 0 - val label: VariableFormula = this - val args: Seq[Nothing] = Seq.empty - val underlyingLabel: K.VariableFormulaLabel = K.VariableFormulaLabel(id) - val underlying = K.AtomicFormula(underlyingLabel, Seq.empty) - def applyUnsafe(args: Term ** 0): Formula = this - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): Formula = { - map.get(this) match { - case Some(subst) => - subst match { - case s: Formula => s - case _ => throw SubstitutionException() - } - case None => this - } - } - def freeSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def allSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def rename(newid: Identifier): VariableFormula = VariableFormula(newid) - def freshRename(taken: Iterable[Identifier]): VariableFormula = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = if (args.size == 0) toString() else toString() + "(" + "illegal_arguments: " + args.mkString(", ") + ")" - } - - /** - * A Constant formula, corresponding to [[K.ConstantFormulaLabel]]. - * It counts both as the label and as the formula itself. Usually either True or False. - */ - case class ConstantFormula(id: Identifier) extends ConstantAtomicLabel[Formula] with AtomicFormula with Absolute with ConstantLabel[Formula] { - override val arity: 0 = 0 - val label: ConstantFormula = this - val args: Seq[Nothing] = Seq.empty - val underlyingLabel: K.ConstantAtomicLabel = K.ConstantAtomicLabel(id, 0) - val underlying = K.AtomicFormula(underlyingLabel, Seq.empty) - def applyUnsafe(args: Term ** 0): Formula = this - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): ConstantFormula = this - def freeSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def allSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def rename(newid: Identifier): ConstantFormula = ConstantFormula(newid) - def freshRename(taken: Iterable[Identifier]): ConstantFormula = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = if (args.size == 0) toString() else toString() + "(" + "illegal_arguments: " + args.mkString(", ") + ")" - } - - /** - * A schematic predicate label (corresponding to [[K.SchematicPredicateLabel]]) is a [[AtomicLabel]] and also a [[SchematicLabel]]. - * It can be substituted by any expression of type (Term ** N) |-> Formula - */ - case class SchematicPredicateLabel[N <: Arity](id: Identifier, arity: N) extends SchematicAtomicLabel[(Term ** N) |-> Formula] with PredicateLabel[N] { - val underlyingLabel: K.SchematicPredicateLabel = K.SchematicPredicateLabel(id, arity) - def unapplySeq(t: AppliedFunctional): Seq[Term] = t match { - case AppliedFunctional(label, args) if (label == this) => args - case _ => Seq.empty - } - @nowarn("msg=the type test for.*cannot be checked at runtime because its type arguments") - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): |->[Term ** N, Formula] = { - map.get(this) match { - case Some(subst) => - subst match { - case s: |->[Term ** N, Formula] => s - case _ => throw SubstitutionException() - } - case None => this - } - } - def freeSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def allSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def rename(newid: Identifier): SchematicPredicateLabel[N] = SchematicPredicateLabel(newid, arity) - def freshRename(taken: Iterable[Identifier]): SchematicPredicateLabel[N] = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = toString() + "(" + args.mkString(", ") + ")" - override def mkStringSeparated(args: Seq[Term]): String = mkString(args) - } - - /** - * A constant predicate label corresponding to [[K.ConstantAtomicLabel]] of arity >= 1. - */ - case class ConstantPredicateLabel[N <: Arity](id: Identifier, arity: N) extends ConstantAtomicLabel[Term ** N |-> Formula] with PredicateLabel[N] { - val underlyingLabel: K.ConstantAtomicLabel = K.ConstantAtomicLabel(id, arity) - private var infix = false - def unapplySeq(f: AppliedPredicate): Seq[Term] = f match { - case AppliedPredicate(label, args) if (label == this) => args - case _ => Seq.empty - } - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): ConstantPredicateLabel[N] = this - def freeSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def allSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def rename(newid: Identifier): ConstantPredicateLabel[N] = ConstantPredicateLabel(newid, arity) - def freshRename(taken: Iterable[Identifier]): ConstantPredicateLabel[N] = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Term]): String = if (infix) (args(0).toStringSeparated() + " " + toString() + " " + args(1).toStringSeparated()) else toString() + "(" + args.mkString(", ") + ")" - override def mkStringSeparated(args: Seq[Term]): String = if (infix) "(" + mkString(args) + ")" else mkString(args) - } - object ConstantPredicateLabel { - def infix[N <: Arity](id: Identifier, arity: N): ConstantPredicateLabel[N] = - val x = new ConstantPredicateLabel[N](id, arity) - x.infix = true - x - } - - /** - * A formula made from a predicate label of arity N and N arguments - */ - case class AppliedPredicate(label: PredicateLabel[?], args: Seq[Term]) extends AtomicFormula with Absolute { - override val underlying = K.AtomicFormula(label.underlyingLabel, args.map(_.underlying)) - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): Formula = - label.substituteUnsafe(map).applyUnsafe(args.map[Term]((x: Term) => x.substituteUnsafe(map))) - - def freeSchematicLabels: Set[SchematicLabel[?]] = label.freeSchematicLabels ++ args.toSeq.flatMap(_.freeSchematicLabels) - def allSchematicLabels: Set[SchematicLabel[?]] = label.allSchematicLabels ++ args.toSeq.flatMap(_.allSchematicLabels) - - override def toString: String = label.mkString(args) - override def toStringSeparated(): String = label.mkStringSeparated(args) - } - - //////////////// - // Connectors // - //////////////// - - /** - * A ConnectorLabel is a [[LisaObject]] of type ((Formula ** N) |-> Formula), that is represented by a connector label in the kernel. - * It can be either a [[SchematicConnectorLabel]] or a [[ConstantConnectorLabel]]. - */ - sealed trait ConnectorLabel extends (Seq[Formula] |-> Formula) with Label[(Seq[Formula] |-> Formula)] with Absolute { - val arity: Arity - def id: Identifier - val underlyingLabel: K.ConnectorLabel - def applySeq(args: Seq[Formula]): Formula = applyUnsafe(args) - def rename(newid: Identifier): ConnectorLabel - def freshRename(taken: Iterable[Identifier]): ConnectorLabel - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): |->[Seq[Formula], Formula] - def mkString(args: Seq[Formula]): String - def mkStringSeparated(args: Seq[Formula]): String - - } - - /** - * A schematic predicate label (corresponding to [[K.SchematicPredicateLabel]]) is a [[ConnectorLabel]] and also a [[SchematicLabel]]. - * It can be substituted by any expression of type (Formula ** N) |-> Formula - */ - case class SchematicConnectorLabel[N <: Arity](id: Identifier, arity: N) extends ConnectorLabel with SchematicLabel[Formula ** N |-> Formula] with ((Formula ** N) |-> Formula) { - val underlyingLabel: K.SchematicConnectorLabel = K.SchematicConnectorLabel(id, arity) - def unapplySeq(f: AppliedPredicate): Seq[Term] = f match { - case AppliedPredicate(label, args) if (label == this) => args - case _ => Seq.empty - } - @nowarn("msg=the type test for.*cannot be checked at runtime because its type arguments") - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): |->[Formula ** N, Formula] = { - map.get(this) match { - case Some(subst) => - subst match { - case s: |->[Formula ** N, Formula] => s - case _ => throw SubstitutionException() - } - case None => this - } - } - // def apply(args: Seq[Formula]): Formula = apply(args) - def applyUnsafe(args: Formula ** N): Formula = AppliedConnector(this, args.toSeq) - - def freeSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def allSchematicLabels: Set[SchematicLabel[?]] = Set(this) - def rename(newid: Identifier): SchematicConnectorLabel[N] = SchematicConnectorLabel(newid, arity) - def freshRename(taken: Iterable[Identifier]): SchematicConnectorLabel[N] = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Formula]): String = toString() + "(" + args.mkString(", ") + ")" - def mkStringSeparated(args: Seq[Formula]): String = mkString(args) - - } - - /** - * A constant connector label is a logical operator such as /\, \/, !, ==>, <=>. - * It corresponds to a [[K.ConstantConnectorLabel]]. - */ - trait ConstantConnectorLabel[N <: Arity] extends ConnectorLabel with ConstantLabel[Formula ** N |-> Formula] with ((Formula ** N) |-> Formula) { - val underlyingLabel: K.ConstantConnectorLabel - def id: Identifier = underlyingLabel.id - def unapplySeq(f: AppliedConnector): Seq[Formula] = f match { - case AppliedConnector(label, args) if (label == this) => args - case _ => Seq.empty - } - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): this.type = this - def applyUnsafe(args: Formula ** N): Formula = AppliedConnector(this, args.toSeq) - def freeSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def allSchematicLabels: Set[SchematicLabel[?]] = Set.empty - def rename(newid: Identifier): ConstantConnectorLabel[N] = throw new Error("Can't rename a constant connector label") - def freshRename(taken: Iterable[Identifier]): ConstantConnectorLabel[N] = rename(K.freshId(taken, id)) - override def toString(): String = id - def mkString(args: Seq[Formula]): String = if (args.length == 2) (args(0).toString() + " " + toString() + " " + args(1).toString()) else toString() + "(" + args.mkString(", ") + ")" - override def mkStringSeparated(args: Seq[Formula]): String = if (args.length == 2) "(" + mkString(args) + ")" else mkString(args) - - } - - /** - * A formula made from a connector label of arity N and N arguments - */ - case class AppliedConnector(label: ConnectorLabel, args: Seq[Formula]) extends Formula with Absolute { - override val underlying = K.ConnectorFormula(label.underlyingLabel, args.map(_.underlying)) - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): Formula = - label.applyUnsafe(args.map[Formula]((x: Formula) => x.substituteUnsafe(map))) - def freeSchematicLabels: Set[SchematicLabel[?]] = label.freeSchematicLabels ++ args.flatMap(_.freeSchematicLabels) - def allSchematicLabels: Set[SchematicLabel[?]] = label.allSchematicLabels ++ args.flatMap(_.allSchematicLabels) - // override def substituteUnsafe(v: Variable, subs: Term) = AppliedPredicateFormula[N](f, args.map(_.substituteUnsafe(v, subs))) - - override def toString: String = label.mkString(args) - override def toStringSeparated(): String = label.mkString(args) - } - - ///////////// - // Binders // - ///////////// - - /** - * A binder for variables, for example \exists, \forall and \exists! but possibly others. - */ - trait BinderLabel extends |->[(Variable, Formula), Formula] with Absolute { - def id: Identifier - } - - /** - * A binder label that exactly correspond to a kernel binder, i.e. \exists, \forall and \exists! - */ - trait BaseBinderLabel extends BinderLabel with ((Variable, Formula) |-> BinderFormula) with Absolute { - val underlyingLabel: K.BinderLabel - - def applyUnsafe(arg: (Variable, Formula)): BinderFormula = BinderFormula(this, arg._1, arg._2) - def apply(v: Variable, f: Formula): BinderFormula = applyUnsafe((v, f)) - def unapply(b: BinderFormula): Option[(Variable, Formula)] = b match { - case BinderFormula(label, v, f) if (label == this) => Some((v, f)) - case _ => None - } - inline def freeSchematicLabels: Set[SchematicLabel[?]] = Set.empty - inline def allSchematicLabels: Set[SchematicLabel[?]] = Set.empty - inline def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): this.type = this - override def toString() = id - - } - - /** - * A quantified formula made of a [[BaseBinderLabel]] and an underlying formula, in a namefull representation. - */ - case class BinderFormula(f: BaseBinderLabel, bound: Variable, body: Formula) extends Absolute with Formula with LisaObject[BinderFormula] { - override val underlying = K.BinderFormula(f.underlyingLabel, bound.underlyingLabel, body.underlying) - - def allSchematicLabels: Set[Common.this.SchematicLabel[?]] = body.allSchematicLabels + bound - def freeSchematicLabels: Set[Common.this.SchematicLabel[?]] = body.freeSchematicLabels - bound - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): BinderFormula = { - val newSubst = map - bound - if (map.values.flatMap(_.freeSchematicLabels).toSet.contains(bound)) { - val taken: Set[SchematicLabel[?]] = body.allSchematicLabels ++ map.keys - val newBound: Variable = bound.rename(lisa.utils.KernelHelpers.freshId(taken.map(_.id), bound.id)) - val newBody = body.substituteOne(bound, newBound.lift) - BinderFormula(f, newBound, newBody.substituteUnsafe(newSubst)) - } else { - BinderFormula(f, bound, body.substituteUnsafe(newSubst)) - } - } - // override def toString():String = f.toString()+bound.toString()+". "+body.toString() - override def toString(): String = f.toString() + "(" + bound.toString() + ", " + body.toString() + ")" - - } - def instantiateBinder(f: BinderFormula, t: Term): Formula = f.body.substituteUnsafe(Map(f.bound -> t)) - - // Application methods for |-> - - extension [S, T <: LisaObject[T]](t: (S ** -1) |-> T) { - def apply(s: Seq[S]): T = t.applyUnsafe(s) - } - extension [S, T <: LisaObject[T], N <: Arity](t: (S ** N) |-> T) { - def applySeq(s: Seq[S]): T = t.applyUnsafe(s) - } - - extension [S, T <: LisaObject[T]](t: (S ** 1) |-> T) { - def apply(s1: S): T = t.applyUnsafe(Seq(s1)) - } - extension [S, T <: LisaObject[T]](t: (S ** 2) |-> T) { - def apply(s1: S, s2: S): T = t.applyUnsafe(Seq(s1, s2)) - } - extension [S <: LisaObject[S], T <: LisaObject[T]](t: (S ** 3) |-> T) { - def apply(s1: S, s2: S, s3: S): T = t.applyUnsafe(Seq(s1, s2, s3)) - } - extension [S <: LisaObject[S], T <: LisaObject[T]](t: (S ** 4) |-> T) { - def apply(s1: S, s2: S, s3: S, s4: S): T = t.applyUnsafe(Seq(s1, s2, s3, s4)) - } - extension [S <: LisaObject[S], T <: LisaObject[T]](t: (S ** 5) |-> T) { - def apply(s1: S, s2: S, s3: S, s4: S, s5: S): T = t.applyUnsafe(Seq(s1, s2, s3, s4, s5)) - } - -} diff --git a/lisa-utils/src/main/scala/lisa/fol/FOL.scala b/lisa-utils/src/main/scala/lisa/fol/FOL.scala deleted file mode 100644 index d22d98c5e..000000000 --- a/lisa-utils/src/main/scala/lisa/fol/FOL.scala +++ /dev/null @@ -1,6 +0,0 @@ -package lisa.fol - -object FOL extends Common with Sequents with Lambdas with Predef { - export FOLHelpers.{*, given} - -} diff --git a/lisa-utils/src/main/scala/lisa/fol/FOLHelpers.scala b/lisa-utils/src/main/scala/lisa/fol/FOLHelpers.scala deleted file mode 100644 index d9232d3ba..000000000 --- a/lisa-utils/src/main/scala/lisa/fol/FOLHelpers.scala +++ /dev/null @@ -1,138 +0,0 @@ -package lisa.fol - -import lisa.fol.FOL.* -import lisa.kernel.fol.FOL.Identifier -import lisa.utils.FOLParser -import lisa.utils.K -import lisa.utils.LisaException - -/** - * A helper file that provides various syntactic sugars for LISA's FOL and proofs. Best imported through utilities.Helpers - * Usage: - * - * import utilities.Helpers.* - *- * or - *- * extends utilities.KernelHelpers.* - *- */ -object FOLHelpers { - export lisa.utils.KernelHelpers.{freshId, nFreshId, given_Conversion_String_Identifier, given_Conversion_Identifier_String, given_Conversion_Boolean_List_String_Option} - - ///////////////// - // FOL helpers // - ///////////////// - - /* Conversions */ - // Conversions to lambdaExpression's - given [T <: LisaObject[T], R <: LisaObject[R]]: Conversion[R, LambdaExpression[T, R, 0]] = LambdaExpression[T, R, 0](Seq(), _, 0) - given [T <: LisaObject[T], R <: LisaObject[R]]: Conversion[(SchematicLabel[T], R), LambdaExpression[T, R, 1]] = a => LambdaExpression(Seq(a._1), a._2, 1) - given [T <: LisaObject[T], R <: LisaObject[R], N <: Arity]: Conversion[(SchematicLabel[T] ** N, R), LambdaExpression[T, R, N]] = a => { - val s = a._1.toSeq - LambdaExpression(s, a._2, s.length.asInstanceOf) - } - - given [T <: LisaObject[T]]: Conversion[T, T ** 1] = **.apply[T, 1](_) - - given Conversion[Int, Arity] = _.asInstanceOf - - /* - extension [I, O <: LisaObject[O]] (e: (I ** 0) |-> O) { - def apply() = e.apply(EmptyTuple) - } - */ - - // helpers to create new schematic symbols, fetching the scala name of the variable. - def variable(using name: sourcecode.Name): Variable = Variable(name.value) - def function[N <: Arity: ValueOf](using name: sourcecode.Name): SchematicFunctionLabel[N] = SchematicFunctionLabel[N](name.value, valueOf[N]) - def formulaVariable(using name: sourcecode.Name): VariableFormula = VariableFormula(name.value) - def predicate[N <: Arity: ValueOf](using name: sourcecode.Name): SchematicPredicateLabel[N] = SchematicPredicateLabel[N](name.value, valueOf[N]) - def connector[N <: Arity: ValueOf](using name: sourcecode.Name): SchematicConnectorLabel[N] = SchematicConnectorLabel[N](name.value, valueOf[N]) - - def freshVariable(using name: sourcecode.Name)(elems: LisaObject[?]*): Variable = Variable(freshId(elems.flatMap(_.freeVariables).map(_.id), name.value)) - def freshVariableFormula(using name: sourcecode.Name)(elems: LisaObject[?]*): VariableFormula = VariableFormula(freshId(elems.flatMap(_.freeVariables).map(_.id), name.value)) - - //////////////////////////////////////// - // Kernel to Front transformers // - //////////////////////////////////////// - - // TermLabel - def asFrontLabel(tl: K.TermLabel): TermLabel[?] = tl match - case tl: K.ConstantFunctionLabel => asFrontLabel(tl) - case tl: K.SchematicTermLabel => asFrontLabel(tl) - def asFrontLabel[N <: Arity](cfl: K.ConstantFunctionLabel): ConstantTermLabelOfArity[N] = cfl.arity.asInstanceOf[N] match - case n: 0 => Constant(cfl.id) - case n: N => ConstantFunctionLabel[N](cfl.id, n) - def asFrontLabel(stl: K.SchematicTermLabel): SchematicTermLabel[?] = stl match - case v: K.VariableLabel => asFrontLabel(v) - case v: K.SchematicFunctionLabel => asFrontLabel(v) - def asFrontLabel[N <: Arity](sfl: K.SchematicFunctionLabel): SchematicFunctionLabel[N] = - SchematicFunctionLabel(sfl.id, sfl.arity.asInstanceOf) - def asFrontLabel(v: K.VariableLabel): Variable = Variable(v.id) - - // Term - def asFront(t: K.Term): Term = asFrontLabel(t.label).applySeq(t.args.map(asFront)) - - // FormulaLabel - def asFrontLabel(fl: K.FormulaLabel): AtomicLabel[?] | ConnectorLabel | BinderLabel = fl match - case fl: K.ConnectorLabel => asFrontLabel(fl) - case fl: K.AtomicLabel => asFrontLabel(fl) - case fl: K.BinderLabel => asFrontLabel(fl) - def asFrontLabel(pl: K.AtomicLabel): AtomicLabel[?] = pl match - case pl: K.ConstantAtomicLabel => asFrontLabel(pl) - case pl: K.SchematicAtomicLabel => asFrontLabel(pl) - def asFrontLabel(cl: K.ConnectorLabel): ConnectorLabel = cl match - case cl: K.ConstantConnectorLabel => asFrontLabel(cl) - case cl: K.SchematicConnectorLabel => asFrontLabel(cl) - def asFrontLabel[N <: Arity](cpl: K.ConstantAtomicLabel): ConstantAtomicLabelOfArity[N] = cpl.arity.asInstanceOf[N] match - case n: 0 => ConstantFormula(cpl.id) - case n: N => ConstantPredicateLabel(cpl.id, cpl.arity.asInstanceOf) - def asFrontLabel(sfl: K.SchematicFormulaLabel): SchematicAtomicLabel[?] | SchematicConnectorLabel[?] = - sfl match - case v: K.VariableFormulaLabel => asFrontLabel(v) - case v: K.SchematicPredicateLabel => asFrontLabel(v) - case v: K.SchematicConnectorLabel => asFrontLabel(v) - def asFrontLabel(svop: K.SchematicAtomicLabel): SchematicAtomicLabel[?] = svop match - case v: K.VariableFormulaLabel => asFrontLabel(v) - case v: K.SchematicPredicateLabel => asFrontLabel(v) - def asFrontLabel(v: K.VariableFormulaLabel): VariableFormula = VariableFormula(v.id) - def asFrontLabel[N <: Arity](spl: K.SchematicPredicateLabel): SchematicPredicateLabel[N] = - SchematicPredicateLabel(spl.id, spl.arity.asInstanceOf) - def asFrontLabel[N <: Arity](scl: K.SchematicConnectorLabel): SchematicConnectorLabel[N] = - SchematicConnectorLabel(scl.id, scl.arity.asInstanceOf) - def asFrontLabel(cpl: K.ConstantConnectorLabel): ConnectorLabel = cpl match - case K.Neg => Neg - case K.Implies => Implies - case K.Iff => Iff - case K.And => And - case K.Or => Or - def asFrontLabel(bl: K.BinderLabel): BaseBinderLabel = bl match { - case K.Forall => Forall - case K.Exists => Exists - case K.ExistsOne => ExistsOne - } - - // Formula - def asFront(f: K.Formula): Formula = f match - case f: K.AtomicFormula => asFront(f) - case f: K.ConnectorFormula => asFront(f) - case f: K.BinderFormula => asFront(f) - def asFront(pf: K.AtomicFormula): Formula = - asFrontLabel(pf.label).applySeq(pf.args.map(asFront)) - def asFront(cf: K.ConnectorFormula): Formula = - asFrontLabel(cf.label).applyUnsafe(cf.args.map(asFront)) - def asFront(bf: K.BinderFormula): BinderFormula = - asFrontLabel(bf.label).apply(asFrontLabel(bf.bound), asFront(bf.inner)) - - // Sequents - def asFront(s: K.Sequent): Sequent = Sequent(s.left.map(asFront), s.right.map(asFront)) - - // Lambdas - def asFrontLambda(l: K.LambdaTermTerm): LambdaExpression[Term, Term, ?] = LambdaExpression(l.vars.map(asFrontLabel), asFront(l.body), l.vars.size) - def asFrontLambda(l: K.LambdaTermFormula): LambdaExpression[Term, Formula, ?] = LambdaExpression(l.vars.map(asFrontLabel), asFront(l.body), l.vars.size) - def asFrontLambda(l: K.LambdaFormulaFormula): LambdaExpression[Formula, Formula, ?] = LambdaExpression(l.vars.map(asFrontLabel), asFront(l.body), l.vars.size) - - def freshVariable[A <: LisaObject[A]](obj: A, name: Identifier): Variable = Variable(freshId(obj.allSchematicLabels.map(_.id), name)) - def freshVariable[A <: LisaObject[A]](objs: Iterable[A], name: Identifier): Variable = Variable(freshId(objs.flatMap(_.allSchematicLabels).map(_.id), name)) -} diff --git a/lisa-utils/src/main/scala/lisa/fol/Lambdas.scala b/lisa-utils/src/main/scala/lisa/fol/Lambdas.scala deleted file mode 100644 index 45d1f8689..000000000 --- a/lisa-utils/src/main/scala/lisa/fol/Lambdas.scala +++ /dev/null @@ -1,100 +0,0 @@ -package lisa.fol -import lisa.kernel.fol.FOL.Identifier -import lisa.utils.K - -import scala.reflect.ClassTag - -import FOLHelpers.freshId -trait Lambdas extends Common { - - /** - * Denotes a lambda expression, i.e. an expression with "holes". - * N is the number of arguments (-1 for arbitrary or unknown). - * T is the type of input of the lambda. - * R is the return type. - * For example, LambdaExpression[Term, Formula, 2] denotes an expression of type (Term**2 |-> Formula), - * i.e. an expression that can be substituted in place of a 2-variable predicate - * - * @param bounds The bound variable encoding the parameter of the lambda - * @param body The body of the lambda - * @param arity The number of parameters. - */ - case class LambdaExpression[T <: LisaObject[T], R <: LisaObject[R], N <: Arity](bounds: Seq[SchematicLabel[T]], body: R, arity: N) extends |->[T ** N, R] { - assert(arity == bounds.length) - private val seqBounds = bounds.toSeq - - def applyUnsafe(args: T ** N): R = body.substituteUnsafe((bounds zip args.toSeq).toMap) - def appUnsafe(args: Seq[T]): R = body.substituteUnsafe((bounds zip args.toSeq).toMap) - - /** - * Substitute schematic symbols by values of corresponding type in the body of expressions. The variables of the expression are bound: This implies that - * 1. They are not substituted in the body even if they are in the substitution map, and - * 2. The bounds of the expression are renamed before substitution if they appear in the substitution. - * - * @param map - * @return - */ - def substituteUnsafe(map: Map[SchematicLabel[?], LisaObject[?]]): LambdaExpression[T, R, N] = { - val newSubst = map -- seqBounds - val conflict = map.values.flatMap(_.freeSchematicLabels).toSet.intersect(bounds.toSet.asInstanceOf) - if (conflict.nonEmpty) { - val taken = (map.values.flatMap(_.allSchematicLabels).map(_.id) ++ map.keys.map(_.id)).toList - val newBounds = seqBounds.scanLeft[List[Identifier]](taken)((list, v: SchematicLabel[T]) => freshId(list, v.id) :: list).map(_.head).zip(seqBounds).map(v => v._2.rename(v._1)) - val newBody = body.substituteUnsafe(seqBounds.zip(newBounds.map(_.liftLabel)).toMap) - LambdaExpression(newBounds, newBody.substituteUnsafe(newSubst), arity) - } else { - LambdaExpression(bounds, body.substituteUnsafe(newSubst), arity) - } - } - - def freeSchematicLabels: Set[SchematicLabel[?]] = body.freeSchematicLabels -- seqBounds - def allSchematicLabels: Set[SchematicLabel[?]] = body.freeSchematicLabels - - } - - /** - * Construct a Lambda expression with a single variable - */ - def lambda[T <: LisaObject[T], R <: LisaObject[R]](bound: SchematicLabel[T], body: R): LambdaExpression[T, R, 1] = LambdaExpression[T, R, 1](Seq(bound), body, 1) - - /** - * Construct a Lambda expression with multiple variables - */ - def lambda[T <: LisaObject[T], R <: LisaObject[R], N <: Arity, Tu <: Tuple](bounds: Tu, body: R)(using Tuple.Union[Tu] <:< SchematicLabel[T], Tuple.Size[Tu] =:= N): LambdaExpression[T, R, N] = { - val boundsSeq = bounds.toList - LambdaExpression[T, R, N](boundsSeq.asInstanceOf, body, boundsSeq.length.asInstanceOf) - } - def lambda[T <: LisaObject[T], R <: LisaObject[R]](bounds: Seq[SchematicLabel[T]], body: R): LambdaExpression[T, R, ?] = { - val boundsSeq = bounds - LambdaExpression(boundsSeq, body, boundsSeq.length.asInstanceOf) - } - - type LambdaTT[N <: Arity] = LambdaExpression[Term, Term, N] - type LambdaTF[N <: Arity] = LambdaExpression[Term, Formula, N] - type LambdaFF[N <: Arity] = LambdaExpression[Formula, Formula, N] - - /** - * Recovers the underlying [[K.LambdaTermTerm]] - */ - extension [N <: Arity](ltt: LambdaExpression[Term, Term, N]) { - def underlyingLTT: K.LambdaTermTerm = - K.LambdaTermTerm(ltt.bounds.map(b => b.asInstanceOf[Variable].underlyingLabel), ltt.body.underlying) - } - - /** - * Recovers the underlying [[K.LambdaTermFormula]] - */ - extension [N <: Arity](ltf: LambdaExpression[Term, Formula, N]) { - def underlyingLTF: K.LambdaTermFormula = - K.LambdaTermFormula(ltf.bounds.map(b => b.asInstanceOf[Variable].underlyingLabel), ltf.body.underlying) - } - - /** - * Recovers the underlying [[K.LambdaFormulaFormula]] - */ - extension [N <: Arity](lff: LambdaExpression[Formula, Formula, N]) { - def underlyingLFF: K.LambdaFormulaFormula = - K.LambdaFormulaFormula(lff.bounds.map((b: SchematicLabel[Formula]) => b.asInstanceOf[VariableFormula].underlyingLabel), lff.body.underlying) - } - -} diff --git a/lisa-utils/src/main/scala/lisa/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/fol/Predef.scala deleted file mode 100644 index 5d53b6a92..000000000 --- a/lisa-utils/src/main/scala/lisa/fol/Predef.scala +++ /dev/null @@ -1,78 +0,0 @@ -package lisa.fol - -import lisa.utils.K - -trait Predef extends Common { - - val equality: ConstantPredicateLabel[2] = ConstantPredicateLabel.infix(K.Identifier("="), 2) - val === = equality - val = = equality - - extension (t: Term) { - infix def ===(u: Term): Formula = equality(t, u) - infix def =(u: Term): Formula = equality(t, u) - } - - val top: ConstantFormula = ConstantFormula(K.Identifier("⊤")) - val ⊤ : top.type = top - val True: top.type = top - - val bot: ConstantFormula = ConstantFormula(K.Identifier("⊥")) - val ⊥ : bot.type = bot - val False: bot.type = bot - - case object Neg extends ConstantConnectorLabel[1] { val underlyingLabel = K.Neg; val arity = 1 } - val neg = Neg - val ¬ = Neg - val ! = Neg - - case object And extends ConstantConnectorLabel[-1] { val underlyingLabel = K.And; val arity = -1 } - val and: And.type = And - val /\ : And.type = And - val ∧ : And.type = And - - case object Or extends ConstantConnectorLabel[-1] { val underlyingLabel = K.Or; val arity = -1 } - val or: Or.type = Or - val \/ : Or.type = Or - val ∨ : Or.type = Or - - case object Implies extends ConstantConnectorLabel[2] { val underlyingLabel = K.Implies; val arity = 2 } - val implies: Implies.type = Implies - val ==> : Implies.type = Implies - - case object Iff extends ConstantConnectorLabel[2] { val underlyingLabel = K.Iff; val arity = 2 } - val iff: Iff.type = Iff - val <=> : Iff.type = Iff - - case object Forall extends BaseBinderLabel { - val id = K.Identifier("∀") - val underlyingLabel: K.Forall.type = K.Forall - } - val forall: Forall.type = Forall - val ∀ : Forall.type = forall - - case object Exists extends BaseBinderLabel { - val id = K.Identifier("∃") - val underlyingLabel: K.Exists.type = K.Exists - } - val exists: Exists.type = Exists - val ∃ : Exists.type = exists - - case object ExistsOne extends BaseBinderLabel { - val id = K.Identifier("∃!") - val underlyingLabel: K.ExistsOne.type = K.ExistsOne - } - val existsOne: ExistsOne.type = ExistsOne - val ∃! : ExistsOne.type = existsOne - - extension (f: Formula) { - def unary_! = Neg(f) - infix inline def ==>(g: Formula): Formula = Implies(f, g) - infix inline def <=>(g: Formula): Formula = Iff(f, g) - infix inline def /\(g: Formula): Formula = And(List(f, g)) - infix inline def ∧(g: Formula): Formula = And(List(f, g)) - infix inline def \/(g: Formula): Formula = Or(List(f, g)) - infix inline def ∨(g: Formula): Formula = Or(List(f, g)) - } - -} diff --git a/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala deleted file mode 100644 index f6f030f8e..000000000 --- a/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala +++ /dev/null @@ -1,441 +0,0 @@ -package lisa.prooflib - -import lisa.kernel.proof.SCProofChecker.checkSCProof -import lisa.prooflib.BasicStepTactic.Rewrite -import lisa.prooflib.BasicStepTactic.* -import lisa.prooflib.ProofTacticLib.* -import lisa.prooflib.SimpleDeducedSteps.* -import lisa.prooflib.* -import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.LisaException -import lisa.utils.UserLisaException -import lisa.utils.parsing.FOLPrinter -import lisa.utils.{_, given} - -import scala.annotation.targetName - -trait ProofsHelpers { - library: Library & WithTheorems => - - import lisa.fol.FOL.{given, *} - - class HaveSequent(val bot: Sequent) { - - inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = By(proof, line, file).asInstanceOf - - class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { - - val bot = HaveSequent.this.bot ++ (F.iterable_to_set(_proof.getAssumptions) |- ()) - inline infix def apply(tactic: Sequent => _proof.ProofTacticJudgement): _proof.ProofStep & _proof.Fact = { - tactic(bot).validate(line, file) - } - inline infix def apply(tactic: ProofSequentTactic): _proof.ProofStep = { - tactic(using library, _proof)(bot).validate(line, file) - } - } - - infix def subproof(using proof: Library#Proof, line: sourcecode.Line, file: sourcecode.File)(computeProof: proof.InnerProof ?=> Unit): proof.ProofStep = { - val botWithAssumptions = HaveSequent.this.bot ++ (proof.getAssumptions |- ()) - val iProof: proof.InnerProof = new proof.InnerProof(Some(botWithAssumptions)) - computeProof(using iProof) - (new BasicStepTactic.SUBPROOF(using proof)(Some(botWithAssumptions))(iProof)).judgement.validate(line, file).asInstanceOf[proof.ProofStep] - } - - } - - class AndThenSequent private[ProofsHelpers] (val bot: Sequent) { - - inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = - By(proof, line, file).asInstanceOf[By { val _proof: proof.type }] - - class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { - private val bot = AndThenSequent.this.bot ++ (_proof.getAssumptions |- ()) - inline infix def apply(tactic: _proof.Fact => Sequent => _proof.ProofTacticJudgement): _proof.ProofStep = { - tactic(_proof.mostRecentStep)(bot).validate(line, file) - } - - inline infix def apply(tactic: ProofFactSequentTactic): _proof.ProofStep = { - tactic(using library, _proof)(_proof.mostRecentStep)(bot).validate(line, file) - } - - } - } - - /** - * Claim the given Sequent as a ProofTactic, which may require a justification by a proof tactic and premises. - */ - def have(using proof: library.Proof)(res: Sequent): HaveSequent = HaveSequent(res) - - def have(using line: sourcecode.Line, file: sourcecode.File)(using proof: library.Proof)(v: proof.Fact | proof.ProofTacticJudgement) = v match { - case judg: proof.ProofTacticJudgement => judg.validate(line, file) - case fact: proof.Fact @unchecked => HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) - } - - /** - * Claim the given Sequent as a ProofTactic directly following the previously proven tactic, - * which may require a justification by a proof tactic. - */ - def thenHave(using proof: library.Proof)(res: Sequent): AndThenSequent = AndThenSequent(res) - - infix def andThen(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): AndThen { val _proof: proof.type } = AndThen(proof, line, file).asInstanceOf - - class AndThen private[ProofsHelpers] (val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { - inline infix def apply(tactic: _proof.Fact => _proof.ProofTacticJudgement): _proof.ProofStep = { - tactic(_proof.mostRecentStep).validate(line, file) - } - inline infix def apply(tactic: ProofFactTactic): _proof.ProofStep = { - tactic(using library, _proof)(_proof.mostRecentStep).validate(line, file) - } - } - - /* - /** - * Assume the given formula in all future left hand-side of claimed sequents. - */ - def assume(using proof: library.Proof)(f: Formula): proof.ProofStep = { - proof.addAssumption(f) - have(() |- f) by BasicStepTactic.Hypothesis - } - */ - /** - * Assume the given formulas in all future left hand-side of claimed sequents. - */ - def assume(using proof: library.Proof)(fs: Formula*): proof.ProofStep = { - fs.foreach(f => proof.addAssumption(f)) - have(() |- fs.toSet) by BasicStepTactic.Hypothesis - } - - def thesis(using proof: library.Proof): Sequent = proof.possibleGoal.get - def goal(using proof: library.Proof): Sequent = proof.possibleGoal.get - - def lastStep(using proof: library.Proof): proof.ProofStep = proof.mostRecentStep - - def sorry(using proof: library.Proof): proof.ProofStep = have(thesis) by Sorry - - def showCurrentProof(using om: OutputManager, _proof: library.Proof)(): Unit = { - om.output("Current proof of " + _proof.owningTheorem.prettyGoal + ": ") - om.output( - lisa.utils.parsing.ProofPrinter.prettyProof(_proof, 2) - ) - } - - extension (using proof: library.Proof)(fact: proof.Fact) { - infix def of(insts: (F.SubstPair | F.Term)*): proof.InstantiatedFact = { - proof.InstantiatedFact(fact, insts) - } - def statement: F.Sequent = proof.sequentOfFact(fact) - } - - def currentProof(using p: library.Proof): Library#Proof = p - - //////////////////////////////////////// - // DSL for definitions and theorems // - //////////////////////////////////////// - - class UserInvalidDefinitionException(val symbol: String, errorMessage: String)(using line: sourcecode.Line, file: sourcecode.File) extends UserLisaException(errorMessage) { // TODO refine - val showError: String = { - val source = scala.io.Source.fromFile(file.value) - val textline = source.getLines().drop(line.value - 1).next().dropWhile(c => c.isWhitespace) - source.close() - s" Definition of $symbol at.(${file.value.split("/").last.split("\\\\").last}:${line.value}) is invalid:\n" + - " " + Console.RED + textline + Console.RESET + "\n\n" + - " " + errorMessage - } - } - - class The(val out: Variable, val f: Formula)( - val just: JUSTIFICATION - ) - class definitionWithVars[N <: Arity](val args: Seq[Variable]) { - - // inline infix def -->(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(t: Term) = simpleDefinition(lambda(args, t, args.length)) - // inline infix def -->(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(f: Formula) = predicateDefinition(lambda(args, f, args.length)) - - inline infix def -->(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(t: The): ConstantTermLabelOfArity[N] = - FunctionDefinition[N](name.value, line.value, file.value)(args, t.out, t.f, t.just).label - - inline infix def -->(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(term: Term): ConstantTermLabelOfArity[N] = - SimpleFunctionDefinition[N](name.value, line.value, file.value)(lambda(args, term).asInstanceOf).label - - inline infix def -->(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(formula: Formula): ConstantAtomicLabelOfArity[N] = - PredicateDefinition[N](name.value, line.value, file.value)(lambda(args, formula).asInstanceOf).label - - } - - def DEF(): definitionWithVars[0] = new definitionWithVars[0](Nil) - def DEF(a: Variable): definitionWithVars[1] = new definitionWithVars[1](Seq(a)) - def DEF(a: Variable, b: Variable): definitionWithVars[2] = new definitionWithVars[2](Seq(a, b)) - def DEF(a: Variable, b: Variable, c: Variable): definitionWithVars[3] = new definitionWithVars[3](Seq(a, b, c)) - def DEF(a: Variable, b: Variable, c: Variable, d: Variable): definitionWithVars[4] = new definitionWithVars[4](Seq(a, b, c, d)) - def DEF(a: Variable, b: Variable, c: Variable, d: Variable, e: Variable): definitionWithVars[5] = new definitionWithVars[5](Seq(a, b, c, d, e)) - def DEF(a: Variable, b: Variable, c: Variable, d: Variable, e: Variable, f: Variable): definitionWithVars[6] = new definitionWithVars[6](Seq(a, b, c, d, e, f)) - def DEF(a: Variable, b: Variable, c: Variable, d: Variable, e: Variable, f: Variable, g: Variable): definitionWithVars[7] = new definitionWithVars[7](Seq(a, b, c, d, e, f, g)) - - // def DEF: definitionWithVars[0] = new definitionWithVars[0](EmptyTuple) //todo conversion to empty tuple gets bad type - - // Definition helpers, not part of the DSL - - /** - * Allows to make definitions "by unique existance" of a function symbol - */ - class FunctionDefinition[N <: F.Arity](using om: OutputManager)(val fullName: String, line: Int, file: String)( - val vars: Seq[F.Variable], - val out: F.Variable, - val f: F.Formula, - j: JUSTIFICATION - ) extends DEFINITION(line, file) { - def funWithArgs = label.applySeq(vars) - override def repr: String = - s" ${if (withSorry) " Sorry" else ""} Definition of function symbol ${funWithArgs} := the ${out} such that ${(out === funWithArgs) <=> f})\n" - - // val expr = LambdaExpression[Term, Formula, N](vars, f, valueOf[N]) - - lazy val label: ConstantTermLabelOfArity[N] = (if (vars.length == 0) F.Constant(name) else F.ConstantFunctionLabel[N](name, vars.length.asInstanceOf)).asInstanceOf - - val innerJustification: theory.FunctionDefinition = { - val conclusion: F.Sequent = j.statement - val pr: SCProof = SCProof(IndexedSeq(SC.Restate(conclusion.underlying, -1)), IndexedSeq(conclusion.underlying)) - if (!(conclusion.left.isEmpty && (conclusion.right.size == 1))) { - om.lisaThrow( - UserInvalidDefinitionException( - name, - s"The given justification is not valid for a definition" + - s"The justification should be of the form ${(() |- F.BinderFormula(F.ExistsOne, out, F.VariableFormula("phi")))}" + - s"instead of the given ${conclusion.underlying}" - ) - ) - } - if (!(f.freeSchematicLabels.subsetOf(vars.toSet + out))) { - om.lisaThrow( - UserInvalidDefinitionException( - name, - s"The definition is not allowed to contain schematic symbols or free variables." + - s"The symbols {${(f.freeSchematicLabels -- vars.toSet - out).mkString(", ")}} are free in the expression ${f.toString}." - ) - ) - } - val proven = conclusion.right.head match { - case F.BinderFormula(F.ExistsOne, bound, inner) => inner - case F.BinderFormula(F.Exists, x, F.BinderFormula(F.Forall, y, F.AppliedConnector(F.Iff, Seq(l, r)))) if F.isSame(l, x === y) => r - case F.BinderFormula(F.Exists, x, F.BinderFormula(F.Forall, y, F.AppliedConnector(F.Iff, Seq(l, r)))) if F.isSame(r, x === y) => l - case _ => - om.lisaThrow( - UserInvalidDefinitionException( - name, - s"The given justification is not valid for a definition" + - s"The justification should be of the form ${(() |- F.BinderFormula(F.ExistsOne, out, F.VariableFormula("phi")))}" + - s"instead of the given ${conclusion.underlying}" - ) - ) - } - val underf = f.underlying - val undervars = vars.map(_.underlyingLabel) - val ulabel = K.ConstantFunctionLabel(name, vars.size) - val judgement = theory.makeFunctionDefinition(pr, Seq(j.innerJustification), ulabel, out.underlyingLabel, K.LambdaTermFormula(undervars, underf), proven.underlying) - judgement match { - case K.ValidJustification(just) => - just - case wrongJudgement: K.InvalidJustification[?] => - if (!theory.belongsToTheory(underf)) { - import K.findUndefinedSymbols - om.lisaThrow( - UserInvalidDefinitionException( - name, - s"All symbols in the definition must belong to the theory. The symbols ${theory.findUndefinedSymbols(underf)} are unknown and you need to define them first." - ) - ) - } - if (!theory.isAvailable(ulabel)) { - om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) - } - if (!(underf.freeSchematicTermLabels.subsetOf(undervars.toSet + out.underlyingLabel) && underf.schematicFormulaLabels.isEmpty)) { - om.lisaThrow( - UserInvalidDefinitionException( - name, - s"The definition is not allowed to contain schematic symbols or free variables." + - s"Kernel returned error: The symbols {${(underf.freeSchematicTermLabels -- undervars.toSet - out.underlyingLabel ++ underf.freeSchematicTermLabels) - .mkString(", ")}} are free in the expression ${underf.toString}." - ) - ) - } - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or an error in LISA.", - wrongJudgement, - None - ) - ) - } - } - - // val label: ConstantTermLabel[N] - val statement: F.Sequent = - () |- F.Forall( - out, - Iff( - equality(label.applySeq(vars), out), - f - ) - ) - - library.last = Some(this) - - } - - /** - * Allows to make definitions "by equality" of a function symbol - */ - class SimpleFunctionDefinition[N <: F.Arity](using om: OutputManager)(fullName: String, line: Int, file: String)( - val lambda: LambdaExpression[Term, Term, N], - out: F.Variable, - j: JUSTIFICATION - ) extends FunctionDefinition[N](fullName, line, file)(lambda.bounds.asInstanceOf, out, out === lambda.body, j) { - - private val term = label.applySeq(lambda.bounds.asInstanceOf) - private val simpleProp = lambda.body === term - val simplePropName = "simpleDef_" + fullName - val simpleDef = THM(simpleProp, simplePropName, line, file, InternalStatement)({ - have(thesis) by Restate.from(this of term) - }) - shortDefs.update(label, Some(simpleDef)) - - } - - object SimpleFunctionDefinition { - def apply[N <: F.Arity](using om: OutputManager)(fullName: String, line: Int, file: String)(lambda: LambdaExpression[Term, Term, N]): SimpleFunctionDefinition[N] = { - val intName = "definition_" + fullName - val out = Variable(freshId(lambda.allSchematicLabels.map(_.id), "y")) - val defThm = THM(F.ExistsOne(out, out === lambda.body), intName, line, file, InternalStatement)({ - have(SimpleDeducedSteps.simpleFunctionDefinition(lambda, out)) - }) - new SimpleFunctionDefinition[N](fullName, line, file)(lambda, out, defThm) - } - } - - class PredicateDefinition[N <: F.Arity](using om: OutputManager)(val fullName: String, line: Int, file: String)(val lambda: LambdaExpression[Term, Formula, N]) extends DEFINITION(line, file) { - - lazy val vars: Seq[F.Variable] = lambda.bounds.asInstanceOf - val arity = lambda.arity - - lazy val label: ConstantAtomicLabelOfArity[N] = { - ( - if (vars.length == 0) F.ConstantFormula(name) - else F.ConstantPredicateLabel[N](name, vars.length.asInstanceOf[N]) - ).asInstanceOf[ConstantAtomicLabelOfArity[N]] - } - - val innerJustification: theory.PredicateDefinition = { - import lisa.utils.K.{predicateDefinition, findUndefinedSymbols} - val underlambda = lambda.underlyingLTF - val ulabel = K.ConstantFunctionLabel(name, vars.size) - val undervars = vars.map(_.asInstanceOf[F.Variable].underlyingLabel) - val judgement = theory.predicateDefinition(name, lambda.underlyingLTF) - judgement match { - case K.ValidJustification(just) => - just - case wrongJudgement: K.InvalidJustification[?] => - if (!theory.belongsToTheory(underlambda.body)) { - om.lisaThrow( - UserInvalidDefinitionException( - name, - s"All symbols in the definition must belong to the theory. The symbols ${theory.findUndefinedSymbols(underlambda.body)} are unknown and you need to define them first." - ) - ) - } - if (!theory.isAvailable(ulabel)) { - om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) - } - if (!(underlambda.body.freeSchematicTermLabels.subsetOf(undervars.toSet) && underlambda.body.schematicFormulaLabels.isEmpty)) { - om.lisaThrow( - UserInvalidDefinitionException( - name, - s"The definition is not allowed to contain schematic symbols or free variables." + - s"Kernel returned error: The symbols {${(underlambda.body.freeSchematicTermLabels -- undervars.toSet ++ underlambda.body.freeSchematicTermLabels) - .mkString(", ")}} are free in the expression ${underlambda.body.toString}." - ) - ) - } - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or an error in LISA.", - wrongJudgement, - None - ) - ) - } - } - - val statement: F.Sequent = () |- Iff(label.applySeq(vars), lambda.body) - library.last = Some(this) - } - - ///////////////////////// - // Local Definitions // - ///////////////////////// - - import lisa.utils.parsing.FOLPrinter.prettySCProof - import lisa.utils.KernelHelpers.apply - - /** - * A term with a definition, local to a proof. - * - * @param proof - * @param id - */ - abstract class LocalyDefinedVariable(val proof: library.Proof, id: Identifier) extends Variable(id) { - - val definition: proof.Fact - lazy val definingFormula = proof.sequentOfFact(definition).right.head - - // proof.addDefinition(this, defin(this), fact) - // val definition: proof.Fact = proof.getDefinition(this) - } - - /** - * A witness for a statement of the form ∃(x, P(x)) is a (fresh) variable y such that P(y) holds. This is a local definition, typically used with `val y = witness(fact)` - * where `fact` is a fact of the form `∃(x, P(x))`. The property P(y) can then be used with y.elim - */ - def witness(using _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File, name: sourcecode.Name)(fact: _proof.Fact): LocalyDefinedVariable { val proof: _proof.type } = { - - val (els, eli) = _proof.sequentAndIntOfFact(fact) - els.right.head match - case Exists(x, inner) => - val id = freshId((els.allSchematicLabels ++ _proof.lockedSymbols ++ _proof.possibleGoal.toSet.flatMap(_.allSchematicLabels)).map(_.id), name.value) - val c: LocalyDefinedVariable = new LocalyDefinedVariable(_proof, id) { val definition = assume(using proof)(inner.substitute(x := this)) } - val defin = c.definingFormula - val definU = defin.underlying - val exDefinU = K.Exists(c.underlyingLabel, definU) - - if els.right.size != 1 || !K.isSame(els.right.head.underlying, exDefinU) then - throw new UserInvalidDefinitionException(c.id, "Eliminator fact for " + c + " in a definition should have a single formula, equivalent to " + exDefinU + ", on the right side.") - - _proof.addElimination( - defin, - (i, sequent) => - val resSequent = (sequent.underlying -<< definU) - List( - SC.LeftExists(resSequent +<< exDefinU, i, definU, c.underlyingLabel), - SC.Cut(resSequent ++<< els.underlying, eli, i + 1, exDefinU) - ) - ) - - c.asInstanceOf[LocalyDefinedVariable { val proof: _proof.type }] - - case _ => throw new Exception("Pick is used to obtain a witness of an existential statement.") - - } - - /** - * Check correctness of the proof, using LISA's logical kernel, to the current point. - */ - def sanityProofCheck(using p: Proof)(message: String): Unit = { - val csc = p.toSCProof - if checkSCProof(csc).isValid then - println("Proof is valid. " + message) - Thread.sleep(100) - else - checkProof(csc) - throw Exception("Proof is not valid: " + message) - } - -} diff --git a/lisa-utils/src/main/scala/lisa/utils/K.scala b/lisa-utils/src/main/scala/lisa/utils/K.scala index 2269cfed3..1980bc0f4 100644 --- a/lisa-utils/src/main/scala/lisa/utils/K.scala +++ b/lisa-utils/src/main/scala/lisa/utils/K.scala @@ -11,6 +11,5 @@ object K { export lisa.kernel.proof.RunningTheoryJudgement as Judgement export lisa.kernel.proof.RunningTheoryJudgement.* export lisa.utils.KernelHelpers.{*, given} - export lisa.utils.parsing.FOLPrinter.* } diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 70d404521..354d3f3a9 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -3,110 +3,221 @@ package lisa.utils import lisa.kernel.fol.FOL.* import lisa.kernel.proof.RunningTheoryJudgement.InvalidJustification import lisa.kernel.proof.SCProofCheckerJudgement.SCInvalidProof +import lisa.kernel.proof.SCProofCheckerJudgement.SCValidProof import lisa.kernel.proof.SequentCalculus.* import lisa.kernel.proof.* -import lisa.utils.FOLParser import scala.annotation.targetName - +import lisa.utils.unification.UnificationUtils.matchExpr /** * A helper file that provides various syntactic sugars for LISA's FOL and proofs at the Kernel level. */ object KernelHelpers { + def predicateType(arity: Int) = Range(0, arity).foldLeft(Formula: Sort)((acc, _) => Term -> acc) + def functionType(arity: Int) = Range(0, arity).foldLeft(Term: Sort)((acc, _) => Term -> acc) + ///////////////// // FOL helpers // ///////////////// /* Prefix syntax */ + extension (s: Sort) { + def >>:(t: Sort) : Sort = Arrow(s, t) + } + + val Equality = equality val === = equality - val ⊤ : Formula = top() - val ⊥ : Formula = bot() - val True: Formula = top() - val False: Formula = bot() + val ⊤ : Expression = top + val ⊥ : Expression = bot + val True: Expression = top + val False: Expression = bot - val neg = Neg val ¬ = neg val ! = neg - val and = And - val /\ = And - val or = Or - val \/ = Or - val implies = Implies - val ==> = Implies - val iff = Iff - val <=> = Iff - val forall = Forall + val /\ = and + val \/ = or + val ==> = implies + val <=> = iff val ∀ = forall - val exists = Exists val ∃ = exists - val existsOne = ExistsOne - val ∃! = existsOne - - extension [L <: TermLabel](label: L) { - def apply(args: Term*): Term = Term(label, args) - @targetName("applySeq") - def apply(args: Seq[Term]): Term = Term(label, args) - def unapply(f: Formula): Option[Seq[Formula]] = f match { - case ConnectorFormula(`label`, args) => Some(args) + val ε = epsilon + + + + // UnapplyMethods + + object And : + def unapply(e: Expression): Option[(Expression, Expression)] = e match + case Application(Application(`and`, l), r) => Some((l, r)) case _ => None - } - } - extension [L <: AtomicLabel](label: L) { - def apply(args: Term*): Formula = AtomicFormula(label, args) - @targetName("applySeq") - def apply(args: Seq[Term]): Formula = AtomicFormula(label, args) - def unapply(f: Formula): Option[Seq[Term]] = f match { - case AtomicFormula(`label`, args) => Some(args) + object Or : + def unapply(e: Expression): Option[(Expression, Expression)] = e match + case Application(Application(`or`, l), r) => Some((l, r)) case _ => None - } - } - extension [L <: ConnectorLabel](label: L) { - def apply(args: Formula*): Formula = ConnectorFormula(label, args) - @targetName("applySeq") - def apply(args: Seq[Formula]): Formula = ConnectorFormula(label, args) - def unapply(f: Formula): Option[Seq[Formula]] = f match { - case ConnectorFormula(`label`, args) => Some(args) + object Neg : + def unapply(e: Expression): Option[Expression] = e match + case Application(`neg`, a) => Some(a) + case _ => None + + object Implies : + def unapply(e: Expression): Option[(Expression, Expression)] = e match + case Application(Application(`implies`, l), r) => Some((l, r)) case _ => None - } - } - extension [L <: BinderLabel](label: L) { - def apply(bound: VariableLabel, inner: Formula): Formula = BinderFormula(label, bound, inner) - def unapply(f: Formula): Option[(VariableLabel, Formula)] = f match { - case BinderFormula(`label`, x, inner) => Some((x, inner)) + object Iff : + def unapply(e: Expression): Option[(Expression, Expression)] = e match + case Application(Application(`iff`, l), r) => Some((l, r)) + case _ => None + + object Forall : + def unapply(e: Expression): Option[(Variable, Expression)] = e match + case Application(`forall`, Lambda(x, inner)) => Some((x, inner)) + case _ => None + + object Exists : + def unapply(e: Expression): Option[(Variable, Expression)] = e match + case Application(`exists`, Lambda(x, inner)) => Some((x, inner)) + case _ => None + + object Epsilon : + def unapply(e: Expression): Option[(Variable, Expression)] = e match + case Application(`epsilon`, Lambda(x, inner)) => Some((x, inner)) case _ => None - } - } + + object Multiand : + def unapply(e: Expression): Option[Seq[Expression]] = e match + case Application(Application(`and`, l), r) => Some(l +: unapply(r).getOrElse(Seq(r))) + case _ => None + + object Multior : + def unapply(e: Expression): Option[Seq[Expression]] = e match + case Application(Application(`or`, l), r) => Some(l +: unapply(r).getOrElse(Seq(r))) + case _ => None + + object Multiapp : + def unapply(e: Expression): Option[(Expression, Seq[Expression])] = + def inner(e: Expression): Option[List[Expression]] = e match + case Application(f, arg) => inner(f) map (l => arg :: l) + case _ => Some(List(e)) + val r = inner(e) + r match + case Some(l) if l.size > 1 => + val rev = l.reverse + Some(rev.head, rev.tail) + case _ => None + + + + + def multiand(args: Seq[Expression]): Expression = args.reduceLeft(and(_)(_)) + def multior(args: Seq[Expression]): Expression = args.reduceLeft(or(_)(_)) + def multiapply(f: Expression)(args: Seq[Expression]): Expression = args.foldLeft(f)(_(_)) /* Infix syntax */ - extension (f: Formula) { - def unary_! = ConnectorFormula(Neg, Seq(f)) - infix inline def ==>(g: Formula): Formula = ConnectorFormula(Implies, Seq(f, g)) - infix inline def <=>(g: Formula): Formula = ConnectorFormula(Iff, Seq(f, g)) - infix inline def /\(g: Formula): Formula = ConnectorFormula(And, Seq(f, g)) - infix inline def \/(g: Formula): Formula = ConnectorFormula(Or, Seq(f, g)) + + extension (e: exists.type) + @targetName("existsApply") + def apply(v: Variable, inner: Expression): Expression = exists(lambda(v, inner)) + + extension (e: forall.type) + @targetName("forallApply") + def apply(v: Variable, inner: Expression): Expression = forall(lambda(v, inner)) + + extension (e: epsilon.type) + @targetName("epsilonApply") + def apply(v: Variable, inner: Expression): Expression = epsilon(lambda(v, inner)) + + + extension (f: Expression) { + def apply(args: Expression*): Expression = multiapply(f)(args) + def unary_! = neg(f) + infix inline def ==>(g: Expression): Expression = implies(f)(g) + infix inline def <=>(g: Expression): Expression = iff(f)(g) + infix inline def /\(g: Expression): Expression = and(f)(g) + infix inline def \/(g: Expression): Expression = or(f)(g) + infix def ===(g: Expression): Expression = equality(f)(g) + infix def =(g: Expression): Expression = equality(f)(g) + + def maxVarId(): Int = f match { + case Variable(id, _) => id.no+1 + case Constant(_, _) => 0 + case Application(f, arg) => f.maxVarId() max arg.maxVarId() + case Lambda(v, inner) => v.id.no max inner.maxVarId() + } + + def leadingVars(): List[Variable] = + def recurse(e:Expression) : List[Variable] = e match { + case Lambda(v, inner) => v :: recurse(inner) + case _ => Nil + } + recurse(f).reverse + + def repr: String = f match + case equality(a, b) => s"${a.repr} === ${b.repr}" + case neg(a) => s"!${a.repr}" + case and(a, b) => s"(${a.repr} /\\ ${b.repr})" + case or(a, b) => s"(${a.repr} \\/ ${b.repr})" + case implies(a, b) => s"(${a.repr} ==> ${b.repr})" + case iff(a, b) => s"(${a.repr} <=> ${b.repr})" + case forall(v, inner) => s"(forall(${v.repr}, ${inner.repr})" + case exists(v, inner) => s"(exists(${v.repr}, ${inner.repr})" + case epsilon(v, inner) => s"(epsilon(${v.repr}, ${inner.repr})" + + case Application(f, arg) => s"${f.repr}(${arg.repr})" + case Constant(id, sort) => id.toString + case Lambda(v, body) => s"lambda(${v.repr}, ${body.repr})" + case Variable(id, sort) => id.toString + + def fullRepr: String = f match + case Application(f, arg) => s"${f.fullRepr}(${arg.fullRepr})" + case Constant(id, sort) => s"cst(${id},${sort})" + case Lambda(v, body) => s"λ${v.fullRepr}.${body.fullRepr}" + case Variable(id, sort) => s"v(${id},${sort})" } - extension (t: Term) { - infix def ===(u: Term): Formula = AtomicFormula(equality, Seq(t, u)) - infix def =(u: Term): Formula = AtomicFormula(equality, Seq(t, u)) + extension (se: SimpleExpression) { + def repr: String = se match + case SimpleAnd(children, polarity) => + val pol = if polarity then "" else "!" + s"${pol}and(${children.map(_.repr).mkString(", ")})" + case SimpleForall(x, inner, polarity) => + val pol = if polarity then "" else "!" + s"${pol}∀$x.${inner.repr}" + case SimpleLiteral(polarity) => + val pol = if polarity then "" else "!" + s"${pol}lit" + case SimpleEquality(left, right, polarity) => + val pol = if polarity then "" else "!" + s"${pol}(${left.repr} === ${right.repr})" + case SimpleVariable(id, sort, polarity) => + val pol = if polarity then "" else "!" + s"${pol}${id}" + case SimpleBoundVariable(no, sort, polarity) => + val pol = if polarity then "" else "!" + s"${pol}bv$no" + case SimpleConstant(id, sort, polarity) => + val pol = if polarity then "" else "!" + s"${pol}${id}" + case SimpleApplication(arg1, arg2, polarity) => + val pol = if polarity then "" else "!" + s"${pol}(${arg1.repr}(${arg2.repr}))" + case SimpleLambda(x, inner) => + s"λ${x.repr}.${inner.repr}" + + } /* Conversions */ - given Conversion[TermLabel, Term] = Term(_, Seq()) - given Conversion[Term, TermLabel] = _.label - given Conversion[AtomicLabel, AtomicFormula] = AtomicFormula(_, Seq()) - given Conversion[AtomicFormula, AtomicLabel] = _.label - - given Conversion[VariableFormulaLabel, AtomicFormula] = AtomicFormula(_, Seq()) + /* given Conversion[(Boolean, List[Int], String), Option[(List[Int], String)]] = tr => if (tr._1) None else Some(tr._2, tr._3) - given Conversion[Formula, Sequent] = () |- _ +*/ + given Conversion[Expression, Sequent] = () |- _ /* Sequents */ @@ -114,10 +225,10 @@ object KernelHelpers { extension (s: Sequent) { // non OL-based / naive Sequent manipulation - 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 +<<(f: Expression): Sequent = s.copy(left = s.left + f) + infix def -<<(f: Expression): Sequent = s.copy(left = s.left - f) + infix def +>>(f: Expression): Sequent = s.copy(right = s.right + f) + infix def ->>(f: Expression): 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) @@ -126,68 +237,67 @@ object KernelHelpers { infix def --(s1: Sequent): Sequent = s.copy(left = s.left -- s1.left, right = s.right -- s1.right) // OL-based Sequent manipulation - infix def removeLeft(f: Formula): Sequent = s.copy(left = s.left.filterNot(isSame(_, f))) - infix def removeRight(f: Formula): Sequent = s.copy(right = s.right.filterNot(isSame(_, f))) + infix def removeLeft(f: Expression): Sequent = s.copy(left = s.left.filterNot(isSame(_, f))) + infix def removeRight(f: Expression): Sequent = s.copy(right = s.right.filterNot(isSame(_, f))) infix def removeAllLeft(s1: Sequent): Sequent = s.copy(left = s.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2)))) - infix def removeAllLeft(s1: Set[Formula]): Sequent = s.copy(left = s.left.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAllLeft(s1: Set[Expression]): Sequent = s.copy(left = s.left.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) infix def removeAllRight(s1: Sequent): Sequent = s.copy(right = s.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) - infix def removeAllRight(s1: Set[Formula]): Sequent = s.copy(right = s.right.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAllRight(s1: Set[Expression]): Sequent = s.copy(right = s.right.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) infix def removeAll(s1: Sequent): Sequent = s.copy(left = s.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2))), right = s.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) - infix def addLeftIfNotExists(f: Formula): Sequent = if (s.left.exists(isSame(_, f))) s else (s +<< f) - infix def addRightIfNotExists(f: Formula): Sequent = if (s.right.exists(isSame(_, f))) s else (s +>> f) + infix def addLeftIfNotExists(f: Expression): Sequent = if (s.left.exists(isSame(_, f))) s else (s +<< f) + infix def addRightIfNotExists(f: Expression): Sequent = if (s.right.exists(isSame(_, f))) s else (s +>> f) infix def addAllLeftIfNotExists(s1: Sequent): Sequent = s ++<< s1.copy(left = s1.left.filterNot(e1 => s.left.exists(isSame(_, e1)))) infix def addAllRightIfNotExists(s1: Sequent): Sequent = s ++>> s1.copy(right = s1.right.filterNot(e1 => s.right.exists(isSame(_, e1)))) infix def addAllIfNotExists(s1: Sequent): Sequent = s ++ s1.copy(left = s1.left.filterNot(e1 => s.left.exists(isSame(_, e1))), right = s1.right.filterNot(e1 => s.right.exists(isSame(_, e1)))) // OL shorthands - infix def +(f: Formula): Sequent = s addLeftIfNotExists f - infix def -(f: Formula): Sequent = s removeLeft f - infix def +>?(f: Formula): Sequent = s addRightIfNotExists f - infix def ->?(f: Formula): Sequent = s removeRight f + infix def +(f: Expression): Sequent = s addLeftIfNotExists f + infix def -(f: Expression): Sequent = s removeLeft f + infix def +>?(f: Expression): Sequent = s addRightIfNotExists f + infix def ->?(f: Expression): Sequent = s removeRight f infix def ++(s1: Sequent): Sequent = s addAllLeftIfNotExists s1 infix def --(s1: Sequent): Sequent = s removeAllLeft s1 infix def ++>?(s1: Sequent): Sequent = s addAllRightIfNotExists s1 infix def -->?(s1: Sequent): Sequent = s removeAllRight s1 infix def --?(s1: Sequent): Sequent = s removeAll s1 infix def ++?(s1: Sequent): Sequent = s addAllIfNotExists s1 + + def repr: String = s"${s.left.map(_.repr).mkString(", ")} |- ${s.right.map(_.repr).mkString(", ")}" + + def fullRepr: String = s"${s.left.map(_.fullRepr).mkString(", ")} |- ${s.right.map(_.fullRepr).mkString(", ")}" } - // TODO: Should make less generic /** * Represents a converter of some object into a set. * @tparam S The type of elements in that set * @tparam T The type to convert from */ protected trait FormulaSetConverter[T] { - def apply(t: T): Set[Formula] + def apply(t: T): Set[Expression] } given FormulaSetConverter[Unit] with { - override def apply(u: Unit): Set[Formula] = Set.empty + override def apply(u: Unit): Set[Expression] = Set.empty } given FormulaSetConverter[EmptyTuple] with { - override def apply(t: EmptyTuple): Set[Formula] = Set.empty - } - - given [H <: Formula, T <: Tuple](using c: FormulaSetConverter[T]): FormulaSetConverter[H *: T] with { - override def apply(t: H *: T): Set[Formula] = c.apply(t.tail) + t.head + override def apply(t: EmptyTuple): Set[Expression] = Set.empty } - given formula_to_set[T <: Formula]: FormulaSetConverter[T] with { - override def apply(f: T): Set[Formula] = Set(f) + given [H <: Expression, T <: Tuple](using c: FormulaSetConverter[T]): FormulaSetConverter[H *: T] with { + override def apply(t: H *: T): Set[Expression] = c.apply(t.tail) + t.head } - given [T <: Formula, I <: Iterable[T]]: FormulaSetConverter[I] with { - override def apply(s: I): Set[Formula] = s.toSet + given formula_to_set[T <: Expression]: FormulaSetConverter[T] with { + override def apply(f: T): Set[Expression] = Set(f) } - given FormulaSetConverter[VariableFormulaLabel] with { - override def apply(s: VariableFormulaLabel): Set[Formula] = Set(s()) + given [T <: Expression, I <: Iterable[T]]: FormulaSetConverter[I] with { + override def apply(s: I): Set[Expression] = s.toSet } - private def any2set[A, T <: A](any: T)(using c: FormulaSetConverter[T]): Set[Formula] = c.apply(any) + private def any2set[A, T <: A](any: T)(using c: FormulaSetConverter[T]): Set[Expression] = c.apply(any) extension [A, T1 <: A](left: T1)(using FormulaSetConverter[T1]) { infix def |-[B, T2 <: B](right: T2)(using FormulaSetConverter[T2]): Sequent = Sequent(any2set(left), any2set(right)) @@ -196,21 +306,8 @@ object KernelHelpers { // Instatiation functions for formulas lifted to sequents. - def instantiatePredicateSchemaInSequent(s: Sequent, m: Map[SchematicAtomicLabel, LambdaTermFormula]): Sequent = { - s.left.map(phi => instantiatePredicateSchemas(phi, m)) |- s.right.map(phi => instantiatePredicateSchemas(phi, m)) - } - - def instantiateFunctionSchemaInSequent(s: Sequent, m: Map[SchematicTermLabel, LambdaTermTerm]): Sequent = { - s.left.map(phi => instantiateTermSchemas(phi, m)) |- s.right.map(phi => instantiateTermSchemas(phi, m)) - } - - def instantiateSchemaInSequent( - s: Sequent, - mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula], - mPred: Map[SchematicAtomicLabel, LambdaTermFormula], - mTerm: Map[SchematicTermLabel, LambdaTermTerm] - ): Sequent = { - s.left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)) |- s.right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)) + def substituteVariablesInSequent(s: Sequent, m: Map[Variable, Expression]): Sequent = { + s.left.map(phi => substituteVariables(phi, m)) |- s.right.map(phi => substituteVariables(phi, m)) } ////////////////////// @@ -240,50 +337,58 @@ object KernelHelpers { def followPath(path: Seq[Int]): SCProofStep = SCSubproof(p, p.imports.indices).followPath(path) } - // Conversions from String to datatypes - // given Conversion[String, Sequent] = FOLParser.parseSequent(_) - // given Conversion[String, Formula] = FOLParser.parseFormula(_) - // given Conversion[String, Term] = FOLParser.parseTerm(_) - // given Conversion[String, VariableLabel] = s => VariableLabel(if (s.head == '?') s.tail else s) - +/* // Conversion from pairs (e.g. x -> f(x)) to lambdas - given Conversion[Term, LambdaTermTerm] = LambdaTermTerm(Seq(), _) - given Conversion[VariableLabel, LambdaTermTerm] = a => LambdaTermTerm(Seq(), a: Term) - given Conversion[(VariableLabel, Term), LambdaTermTerm] = a => LambdaTermTerm(Seq(a._1), a._2) - given Conversion[(Seq[VariableLabel], Term), LambdaTermTerm] = a => LambdaTermTerm(a._1, a._2) + given Conversion[Expression, LambdaTermTerm] = LambdaTermTerm(Seq(), _) + given Conversion[VariableLabel, LambdaTermTerm] = a => LambdaTermTerm(Seq(), a: Expression) + given Conversion[(VariableLabel, Expression), LambdaTermTerm] = a => LambdaTermTerm(Seq(a._1), a._2) + given Conversion[(Seq[VariableLabel], Expression), LambdaTermTerm] = a => LambdaTermTerm(a._1, a._2) - given Conversion[Formula, LambdaTermFormula] = LambdaTermFormula(Seq(), _) - given Conversion[(VariableLabel, Formula), LambdaTermFormula] = a => LambdaTermFormula(Seq(a._1), a._2) - given Conversion[(Seq[VariableLabel], Formula), LambdaTermFormula] = a => LambdaTermFormula(a._1, a._2) + given Conversion[Expression, LambdaTermFormula] = LambdaTermFormula(Seq(), _) + given Conversion[(VariableLabel, Expression), LambdaTermFormula] = a => LambdaTermFormula(Seq(a._1), a._2) + given Conversion[(Seq[VariableLabel], Expression), LambdaTermFormula] = a => LambdaTermFormula(a._1, a._2) - given Conversion[Formula, LambdaFormulaFormula] = LambdaFormulaFormula(Seq(), _) - given Conversion[(VariableFormulaLabel, Formula), LambdaFormulaFormula] = a => LambdaFormulaFormula(Seq(a._1), a._2) - given Conversion[(Seq[VariableFormulaLabel], Formula), LambdaFormulaFormula] = a => LambdaFormulaFormula(a._1, a._2) + given Conversion[Expression, LambdaFormulaFormula] = LambdaFormulaFormula(Seq(), _) + given Conversion[(VariableFormulaLabel, Expression), LambdaFormulaFormula] = a => LambdaFormulaFormula(Seq(a._1), a._2) + given Conversion[(Seq[VariableFormulaLabel], Expression), LambdaFormulaFormula] = a => LambdaFormulaFormula(a._1, a._2) + def // Shortcut for LambdaTermTerm, LambdaTermFormula and LambdaFormulaFormula construction - def lambda(x: VariableLabel, t: Term): LambdaTermTerm = LambdaTermTerm(Seq(x), t) - def lambda(xs: Seq[VariableLabel], t: Term): LambdaTermTerm = LambdaTermTerm(xs, t) + def lambda(x: VariableLabel, t: Expression): LambdaTermTerm = LambdaTermTerm(Seq(x), t) + def lambda(xs: Seq[VariableLabel], t: Expression): LambdaTermTerm = LambdaTermTerm(xs, t) def lambda(x: VariableLabel, l: LambdaTermTerm): LambdaTermTerm = LambdaTermTerm(Seq(x) ++ l.vars, l.body) def lambda(xs: Seq[VariableLabel], l: LambdaTermTerm): LambdaTermTerm = LambdaTermTerm(xs ++ l.vars, l.body) - def lambda(x: VariableLabel, phi: Formula): LambdaTermFormula = LambdaTermFormula(Seq(x), phi) - def lambda(xs: Seq[VariableLabel], phi: Formula): LambdaTermFormula = LambdaTermFormula(xs, phi) + def lambda(x: VariableLabel, phi: Expression): LambdaTermFormula = LambdaTermFormula(Seq(x), phi) + def lambda(xs: Seq[VariableLabel], phi: Expression): LambdaTermFormula = LambdaTermFormula(xs, phi) def lambda(x: VariableLabel, l: LambdaTermFormula): LambdaTermFormula = LambdaTermFormula(Seq(x) ++ l.vars, l.body) def lambda(xs: Seq[VariableLabel], l: LambdaTermFormula): LambdaTermFormula = LambdaTermFormula(xs ++ l.vars, l.body) - def lambda(X: VariableFormulaLabel, phi: Formula): LambdaFormulaFormula = LambdaFormulaFormula(Seq(X), phi) - def lambda(Xs: Seq[VariableFormulaLabel], phi: Formula): LambdaFormulaFormula = LambdaFormulaFormula(Xs, phi) + def lambda(X: VariableFormulaLabel, phi: Expression): LambdaFormulaFormula = LambdaFormulaFormula(Seq(X), phi) + def lambda(Xs: Seq[VariableFormulaLabel], phi: Expression): LambdaFormulaFormula = LambdaFormulaFormula(Xs, phi) def lambda(X: VariableFormulaLabel, l: LambdaFormulaFormula): LambdaFormulaFormula = LambdaFormulaFormula(Seq(X) ++ l.vars, l.body) def lambda(Xs: Seq[VariableFormulaLabel], l: LambdaFormulaFormula): LambdaFormulaFormula = LambdaFormulaFormula(Xs ++ l.vars, l.body) - - def instantiateBinder(f: BinderFormula, t: Term): Formula = substituteVariablesInFormula(f.inner, Map(f.bound -> t)) +*/ + def lambda(x: Variable, t: Expression): Lambda = Lambda(x, t) + def lambda(xs: Seq[Variable], t: Expression): Expression = xs.foldRight(t)((x, t) => Lambda(x, t)) + def reduceLambda(f: Lambda, t: Expression): Expression = substituteVariables(f.body, Map(f.v -> t)) // declare symbols easily: "val x = variable;" - def variable(using name: sourcecode.Name): VariableLabel = VariableLabel(name.value) - def function(arity: Integer)(using name: sourcecode.Name): SchematicFunctionLabel = SchematicFunctionLabel(name.value, arity) - def formulaVariable(using name: sourcecode.Name): VariableFormulaLabel = VariableFormulaLabel(name.value) - def predicate(arity: Integer)(using name: sourcecode.Name): SchematicPredicateLabel = SchematicPredicateLabel(name.value, arity) - def connector(arity: Integer)(using name: sourcecode.Name): SchematicConnectorLabel = SchematicConnectorLabel(name.value, arity) + def HOvariable(using name: sourcecode.Name)(sort: Sort): Variable = Variable(name.value, sort) + def variable(using name: sourcecode.Name): Variable = Variable(name.value, Term) + def function(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Term: Sort)((acc, _)=> Term -> acc)) + def formulaVariable(using name: sourcecode.Name): Variable = Variable(name.value, Formula) + def predicate(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Formula: Sort)((acc, _)=> Term -> acc)) + def connector(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Formula: Sort)((acc, _)=> Formula -> acc)) + def cst(using name: sourcecode.Name)(sort: Sort): Constant = Constant(name.value, sort) + + def HOvariable(sort: Sort)(id: Identifier): Variable = Variable(id, sort) + def variable(id: Identifier): Variable = Variable(id, Term) + def function(arity: Integer)(id: Identifier): Variable = Variable(id, Range(0, arity).foldLeft(Term: Sort)((acc, _)=> Term -> acc)) + def formulaVariable(id: Identifier): Variable = Variable(id, Formula) + def predicate(arity: Integer)(id: Identifier): Variable = Variable(id, Range(0, arity).foldLeft(Formula: Sort)((acc, _)=> Term -> acc)) + def connector(arity: Integer)(id: Identifier): Variable = Variable(id, Range(0, arity).foldLeft(Formula: Sort)((acc, _)=> Formula -> acc)) + def cst(id: Identifier, sort:Sort): Constant = Constant(id, sort) // Conversions from String to Identifier class InvalidIdentifierException(identifier: String, errorMessage: String) extends LisaException(errorMessage) { @@ -336,9 +441,9 @@ object KernelHelpers { ///////////////////////////// extension (theory: RunningTheory) { - def makeAxiom(using name: sourcecode.Name)(formula: Formula): theory.Axiom = theory.addAxiom(name.value, formula) match { + def makeAxiom(using name: sourcecode.Name)(formula: Expression): theory.Axiom = theory.addAxiom(name.value, formula) match { case Some(value) => value - case None => throw new LisaException.InvalidKernelAxiomException("Axiom contains undefined symbols", name.value, formula, theory) + case None => throw new Exception("Axiom contains undefined symbols " + name.value + formula + theory) } /** @@ -348,32 +453,22 @@ object KernelHelpers { def theorem(name: String, statement: Sequent, proof: SCProof, justifications: Seq[theory.Justification]): RunningTheoryJudgement[theory.Theorem] = { if (statement == proof.conclusion) theory.makeTheorem(name, statement, proof, justifications) else if (isSameSequent(statement, proof.conclusion)) theory.makeTheorem(name, statement, proof.appended(Restate(statement, proof.length - 1)), justifications) - else InvalidJustification(s"The proof proves \n ${FOLPrinter.prettySequent(proof.conclusion)}\ninstead of claimed \n ${FOLPrinter.prettySequent(statement)}", None) - } - - /** - * Make a function definition in the theory, but only ask for the identifier of the new symbol; Arity is inferred - * of the theorem to have more explicit writing and for sanity check. See [[lisa.kernel.proof.RunningTheory.makeFunctionDefinition]] - */ - def functionDefinition( - symbol: String, - expression: LambdaTermFormula, - out: VariableLabel, - proof: SCProof, - proven: Formula, - justifications: Seq[theory.Justification] - ): RunningTheoryJudgement[theory.FunctionDefinition] = { - val label = ConstantFunctionLabel(symbol, expression.vars.size) - theory.makeFunctionDefinition(proof, justifications, label, out, expression, proven) + else InvalidJustification(s"The proof proves \n ${proof.conclusion.repr}\ninstead of claimed \n ${statement.repr}", None) } /** * Make a predicate definition in the theory, but only ask for the identifier of the new symbol; Arity is inferred * of the theorem to have more explicit writing and for sanity check. See also [[lisa.kernel.proof.RunningTheory.makePredicateDefinition]] */ - def predicateDefinition(symbol: String, expression: LambdaTermFormula): RunningTheoryJudgement[theory.PredicateDefinition] = { - val label = ConstantAtomicLabel(symbol, expression.vars.size) - theory.makePredicateDefinition(label, expression) + def definition(symbol: String, expression: Expression): RunningTheoryJudgement[theory.Definition] = { + val label = Constant(symbol, expression.sort) + val vars = expression.leadingVars() + if (vars.length == expression.sort.depth) then + theory.makeDefinition(label, expression, vars) + else + var maxid = expression.maxVarId()-1 + val newvars = flatTypeParameters(expression.sort).drop(vars.length).map(t => {maxid+=1;Variable(Identifier("x", maxid), t)}) + theory.makeDefinition(label, expression, vars ++ newvars) } /** @@ -388,29 +483,11 @@ object KernelHelpers { * @param phi The formula to check * @return The List of undefined symols */ - def findUndefinedSymbols(phi: Formula): Set[ConstantLabel] = phi match { - case AtomicFormula(label, args) => - label match { - case l: ConstantAtomicLabel => ((if (theory.isSymbol(l)) Nil else List(l)) ++ args.flatMap(findUndefinedSymbols)).toSet - case _ => args.flatMap(findUndefinedSymbols).toSet - } - case ConnectorFormula(label, args) => args.flatMap(findUndefinedSymbols).toSet - case BinderFormula(label, bound, inner) => findUndefinedSymbols(inner) - } - - /** - * Verify if a given term belongs to the language of the theory. - * - * @param t The term to check - * @return The List of undefined symols - */ - def findUndefinedSymbols(t: Term): Set[ConstantLabel] = t match { - case Term(label, args) => - label match { - case l: ConstantFunctionLabel => ((if (theory.isSymbol(l)) Nil else List(l)) ++ args.flatMap(findUndefinedSymbols)).toSet - case _: SchematicTermLabel => args.flatMap(findUndefinedSymbols).toSet - } - + def findUndefinedSymbols(phi: Expression): Set[Constant] = phi match { + case Variable(id, sort) => Set.empty + case cst: Constant => if (theory.isSymbol(cst)) Set.empty else Set(cst) + case Lambda(v, inner) => findUndefinedSymbols(inner) + case Application(f, arg) => findUndefinedSymbols(f) ++ findUndefinedSymbols(arg) } /** @@ -419,23 +496,18 @@ object KernelHelpers { * @param s The sequent to check * @return The List of undefined symols */ - def findUndefinedSymbols(s: Sequent): Set[ConstantLabel] = + def findUndefinedSymbols(s: Sequent): Set[Constant] = s.left.flatMap(findUndefinedSymbols) ++ s.right.flatMap(findUndefinedSymbols) } extension (just: RunningTheory#Justification) { def repr: String = just match { - case thm: RunningTheory#Theorem => s" Theorem ${thm.name} := ${FOLPrinter.prettySequent(thm.proposition)}${if (thm.withSorry) " (!! Relies on Sorry)" else ""}\n" - case axiom: RunningTheory#Axiom => s" Axiom ${axiom.name} := ${FOLPrinter.prettyFormula(axiom.ax)}\n" + case thm: RunningTheory#Theorem => s" Theorem ${thm.name} := ${thm.proposition.repr}${if (thm.withSorry) " (!! Relies on Sorry)" else ""}\n" + case axiom: RunningTheory#Axiom => s" Axiom ${axiom.name} := ${axiom.ax.repr}\n" case d: RunningTheory#Definition => - d match { - case pd: RunningTheory#PredicateDefinition => - s" Definition of predicate symbol ${pd.label.id} := ${FOLPrinter.prettyFormula(pd.label(pd.expression.vars.map(VariableTerm.apply)*) <=> pd.expression.body)}\n" - case fd: RunningTheory#FunctionDefinition => - s" Definition of function symbol ${FOLPrinter.prettyTerm(fd.label(fd.expression.vars.map(VariableTerm.apply)*))} := the ${fd.out.id} such that ${FOLPrinter - .prettyFormula((fd.out === fd.label(fd.expression.vars.map(VariableTerm.apply)*)) <=> fd.expression.body)})${if (fd.withSorry) " (!! Relies on Sorry)" else ""}\n" - } + s" Definition of symbol ${d.cst.id} : ${d.cst.sort} := ${d.expression}\n" + } } @@ -451,22 +523,164 @@ object KernelHelpers { just.repr case InvalidJustification(message, error) => s"$message\n${error match { - case Some(judgement) => FOLPrinter.prettySCProof(judgement) + case Some(judgement) => prettySCProof(judgement) case None => "" }}" } } } + + extension (judg: SCProofCheckerJudgement) { + def repr: String = prettySCProof(judg) + } + + /** * output a readable representation of a proof. */ def checkProof(proof: SCProof, output: String => Unit = println): Unit = { val judgement = SCProofChecker.checkSCProof(proof) + if judgement.isValid then + output("Proof is valid") + else + output("Proof is invalid") val pl = proof.totalLength if pl > 100 then output("...") output(s"Proof is too long to be displayed [$pl steps]") - else output(FOLPrinter.prettySCProof(judgement)) + else output(prettySCProof(judgement)) } + + private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " + + private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" + + /** + * Returns a string representation of this proof. + * + * @param proof the proof + * @param judgement optionally provide a proof checking judgement that will mark a particular step in the proof + * (`->`) as an error. The proof is considered to be valid by default + * @return a string where each indented line corresponds to a step in the proof + */ + def prettySCProof(judgement: SCProofCheckerJudgement, forceDisplaySubproofs: Boolean = false): String = { + val proof = judgement.proof + def computeMaxNumberingLengths(proof: SCProof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { + val resultWithCurrent = result.updated( + level, + (Seq((proof.steps.size - 1).toString.length, result(level)) ++ (if (proof.imports.nonEmpty) Seq((-proof.imports.size).toString.length) else Seq.empty)).max + ) + proof.steps.collect { case sp: SCSubproof => sp }.foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.sp, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) + } + + val maxNumberingLengths = computeMaxNumberingLengths(proof, 0, IndexedSeq(0)) // The maximum value for each number column + val maxLevel = maxNumberingLengths.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 = judgement match { + case SCValidProof(_, _) => false + case SCInvalidProof(proof, position, _) => 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(" "), + imp.repr + ) + + Seq(pretty("Import", 0)) + } + printedImports ++ proof.steps.zipWithIndex.flatMap { case (step, i) => + val currentTree = tree :+ i + val showErrorForLine = judgement match { + case SCValidProof(_, _) => false + case SCInvalidProof(proof, position, _) => + 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(" "), + step.bot.repr + ) + + step match { + case sp @ SCSubproof(_, _) => + 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 Restate(_, t1) => pretty("Rewrite", t1) + case RestateTrue(_) => pretty("RewriteTrue") + 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 RightEpsilon(_, 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 LeftImplies(_, t1, t2, _, _) => pretty("Left ⇒", t1, t2) + case LeftIff(_, t1, _, _) => pretty("Left ⇔", t1) + case Weakening(_, t1) => pretty("Weakening", t1) + case Beta(_, t1) => pretty("Beta", t1) + case LeftRefl(_, t1, _) => pretty("L. Refl", t1) + case RightRefl(_, _) => pretty("R. Refl") + case LeftSubstEq(_, t1, _, _) => pretty("L. SubstEq", t1) + case RightSubstEq(_, t1, _, _) => pretty("R. SubstEq", t1) + case InstSchema(_, t1, _) => pretty("Schema Instantiation", t1) + case Sorry(_) => pretty("Sorry") + case SCSubproof(_, _) => throw new Exception("Should not happen") + } + 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 (!judgement.isValid) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix + full.mkString(" ") + } + .mkString("\n") + (judgement match { + case SCValidProof(_, _) => "" + case SCInvalidProof(proof, path, message) => s"\nProof checker has reported an error at line ${path.mkString(".")}: $message" + }) + } + + def prettySCProof(proof: SCProof): String = prettySCProof(SCValidProof(proof), false) + + } diff --git a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala index 6d141d390..126397ff6 100644 --- a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala +++ b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala @@ -6,8 +6,9 @@ import lisa.kernel.proof.RunningTheoryJudgement import lisa.kernel.proof.RunningTheoryJudgement.InvalidJustification import lisa.kernel.proof.SCProof import lisa.prooflib.Library -import lisa.prooflib.ProofTacticLib.ProofTactic +// import lisa.prooflib.ProofTacticLib.ProofTactic import lisa.utils.KernelHelpers.repr +import lisa.utils.KernelHelpers.prettySCProof abstract class LisaException(errorMessage: String)(using val line: sourcecode.Line, val file: sourcecode.File) extends Exception(errorMessage) { def showError: String @@ -15,8 +16,10 @@ abstract class LisaException(errorMessage: String)(using val line: sourcecode.Li import lisa.utils.KernelHelpers.{_, given} + import java.io.File object LisaException { + case class InvalidKernelJustificationComputation(errorMessage: String, underlying: RunningTheoryJudgement.InvalidJustification[?], proof: Option[Library#Proof])(using sourcecode.Line, sourcecode.File @@ -24,12 +27,12 @@ object LisaException { def showError: String = "Construction of proof succedded, but the resulting proof or definition has been reported to be faulty. This may be due to an internal bug.\n" + "The resulting faulty event is:\n" + s"$underlying.message\n${underlying.error match { - case Some(judgement) => FOLPrinter.prettySCProof(judgement) + case Some(judgement) => prettySCProof(judgement) case None => "" }}" } - class InvalidKernelAxiomException(errorMessage: String, name: String, formula: lisa.kernel.fol.FOL.Formula, theory: lisa.kernel.proof.RunningTheory)(using sourcecode.Line, sourcecode.File) + class InvalidKernelAxiomException(errorMessage: String, name: String, formula: lisa.kernel.fol.FOL.Expression, theory: lisa.kernel.proof.RunningTheory)(using sourcecode.Line, sourcecode.File) extends LisaException(errorMessage) { def showError: String = s"The desired axiom \"$name\" contains symbol that are not part of the theory.\n" + s"The symbols {${theory.findUndefinedSymbols(formula)}} are undefined." @@ -37,12 +40,15 @@ object LisaException { } + + /** * Error made by the user, should be "explained" */ abstract class UserLisaException(var errorMessage: String)(using line: sourcecode.Line, file: sourcecode.File) extends LisaException(errorMessage) { def fixTrace(): Unit = () } + object UserLisaException { class InvalidProofFromFileException(errorMessage: String, file: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { def showError: String = errorMessage @@ -58,7 +64,7 @@ object UserLisaException { def showError: String = "" } - class UndefinedSymbolException(errorMessage: String, symbol: F.ConstantLabel[?], library: lisa.prooflib.Library)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { + class UndefinedSymbolException(errorMessage: String, symbol: F.Constant[?], library: lisa.prooflib.Library)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { def showError: String = s"The desired symbol \"$symbol\" is unknown and has not been defined.\n" } diff --git a/lisa-utils/src/main/scala/lisa/utils/Printing.scala b/lisa-utils/src/main/scala/lisa/utils/Printing.scala new file mode 100644 index 000000000..b6ba22fcb --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/Printing.scala @@ -0,0 +1,6 @@ +package lisa.utils + +object Printing: + def printList[T](seq: Iterable[T], separator: String = "\n\t"): String = + (seq lazyZip (0 until seq.size)).map((x, i) => s"$i: $x").mkString(separator) + diff --git a/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala b/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala index 5f620aa87..4d0d3b94a 100644 --- a/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala +++ b/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala @@ -10,7 +10,7 @@ import lisa.kernel.proof.SequentCalculus.* * If the provided proofs are valid, then the resulting proofs will also be valid. */ object ProofsShrink { - +/* /** * Computes the size of a proof. Size corresponds to the number of proof steps. * Subproofs are count as one plus the size of their body. @@ -300,4 +300,5 @@ object ProofsShrink { } def minimizeProofOnce(proof: SCProof): SCProof = deadStepsElimination(factorProof(simplifyProof(flattenProof(proof)))) + */ } diff --git a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala index 3b934506a..6cfca5475 100644 --- a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala +++ b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala @@ -21,7 +21,6 @@ object Serialization { inline def leftNot: Byte = 8 inline def leftForall: Byte = 9 inline def leftExists: Byte = 10 - inline def leftExistsOne: Byte = 11 inline def rightAnd: Byte = 12 inline def rightOr: Byte = 13 inline def rightImplies: Byte = 14 @@ -29,55 +28,41 @@ object Serialization { inline def rightNot: Byte = 16 inline def rightForall: Byte = 17 inline def rightExists: Byte = 18 - inline def rightExistsOne: Byte = 19 + inline def rightEpsilon: Byte = 19 inline def weakening: Byte = 20 - inline def leftRefl: Byte = 21 - inline def rightRefl: Byte = 22 - inline def leftSubstEq: Byte = 23 - inline def rightSubstEq: Byte = 24 - inline def leftSubstIff: Byte = 25 - inline def rightSubstIff: Byte = 26 - inline def instSchema: Byte = 27 - inline def scSubproof: Byte = 28 - inline def sorry: Byte = 29 + inline def beta: Byte = 21 + inline def leftRefl: Byte = 22 + inline def rightRefl: Byte = 23 + inline def leftSubstEq: Byte = 24 + inline def rightSubstEq: Byte = 25 + inline def instSchema: Byte = 26 + inline def scSubproof: Byte = 27 + inline def sorry: Byte = 28 type Line = Int - // Injectively represent a TermLabel as a string - def termLabelToString(label: TermLabel): String = - label match - case l: ConstantFunctionLabel => "cfl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: SchematicFunctionLabel => "sfl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: VariableLabel => "vl_" + l.id.name + "_" + l.id.no - - // Injectively represent a FormulaLabel as a string. - def formulaLabelToString(label: FormulaLabel): String = - label match - case l: ConstantAtomicLabel => "cpl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: SchematicPredicateLabel => "spl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: ConstantConnectorLabel => "ccl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: SchematicConnectorLabel => "scl_" + l.id.name + "_" + l.id.no + "_" + l.arity - case l: VariableFormulaLabel => "vfl_" + l.id.name + "_" + l.id.no - case l: BinderLabel => "bl_" + l.id.name + "_" + l.id.no - - // write a term label to an OutputStream - def termLabelToDOS(label: TermLabel, dos: DataOutputStream): Unit = - label match - case l: ConstantFunctionLabel => - dos.writeByte(0) - dos.writeUTF(l.id.name) - dos.writeInt(l.id.no) - dos.writeInt(l.arity) - case l: SchematicFunctionLabel => - dos.writeByte(1) - dos.writeUTF(l.id.name) - dos.writeInt(l.id.no) - dos.writeInt(l.arity) - case l: VariableLabel => - dos.writeByte(2) - dos.writeUTF(l.id.name) - dos.writeInt(l.id.no) - // write a formula label to an OutputStream + def typeToString(t: Sort): String = + t match + case Term => "T" + case Formula => "F" + case Arrow(from, to) => s">${typeToString(from)}${typeToString(to)}" + + def constantToSting(c: Constant): String = "cst_" + c.id.name + "_" + c.id.no + "_" + typeToString(c.sort) + def variableToSting(v: Variable): String = "var_" + v.id.name + "_" + v.id.no + "_" + typeToString(v.sort) + + def constantToDos(c: Constant, dos: DataOutputStream): Unit = + dos.writeByte(0) + dos.writeUTF(c.id.name) + dos.writeInt(c.id.no) + dos.writeUTF(typeToString(c.sort)) + + def variableToDOS(v: Variable, dos: DataOutputStream): Unit = + dos.writeByte(1) + dos.writeUTF(v.id.name) + dos.writeInt(v.id.no) + dos.writeUTF(typeToString(v.sort)) + + /* def formulaLabelToDOS(label: FormulaLabel, dos: DataOutputStream): Unit = label match case l: ConstantAtomicLabel => @@ -105,77 +90,56 @@ object Serialization { case l: BinderLabel => dos.writeByte(8) dos.writeUTF(l.id.name) + */ /** * Main function that, when given a proof, will serialize it to a file. It will also serialize all the formulas appearing in it to another file. */ def proofsToDataStream(treesDOS: DataOutputStream, proofDOS: DataOutputStream, theorems: Seq[(String, SCProof, List[String])]): Unit = { - val termMap = MutMap[Long, Line]() - val formulaMap = MutMap[Long, Line]() + val exprMap = MutMap[Long, Line]() var line = -1 - // Compute the line of a term. If it is not in the map, add it to the map and write it to the tree file - def lineOfTerm(term: Term): Line = - termMap.get(term.uniqueNumber) match - case Some(line) => line - case None => - val na = term.args.map(t => lineOfTerm(t)) - termLabelToDOS(term.label, treesDOS) - na.foreach(t => treesDOS.writeInt(t)) - line = line + 1 - termMap(term.uniqueNumber) = line - line - // Compute the line of a formula. If it is not in the map, add it to the map and write it to the tree file - def lineOfFormula(formula: Formula): Line = - formulaMap.get(formula.uniqueNumber) match + def lineOfExpr(e: Expression): Line = + exprMap.get(e.uniqueNumber) match case Some(line) => line case None => - val nextLine = formula match - case AtomicFormula(label, args) => - val na = args.map(t => lineOfTerm(t)) - formulaLabelToDOS(label, treesDOS) - na.foreach(t => treesDOS.writeInt(t)) - case ConnectorFormula(label, args) => - val na = args.map(t => lineOfFormula(t)) - formulaLabelToDOS(label, treesDOS) - treesDOS.writeShort(na.size) - na.foreach(t => treesDOS.writeInt(t)) - case BinderFormula(label, bound, inner) => - val ni = lineOfFormula(inner) - formulaLabelToDOS(label, treesDOS) - termLabelToDOS(bound, treesDOS) + val nextLine = e match + case v: Variable => + treesDOS.writeByte(0) + treesDOS.writeUTF(v.id.name) + treesDOS.writeInt(v.id.no) + treesDOS.writeUTF(typeToString(v.sort)) + case c: Constant => + treesDOS.writeByte(1) + treesDOS.writeUTF(c.id.name) + treesDOS.writeInt(c.id.no) + treesDOS.writeUTF(typeToString(c.sort)) + case Lambda(v, inner) => + treesDOS.writeByte(2) + val vi = lineOfExpr(v) + val ni = lineOfExpr(inner) + treesDOS.writeInt(vi) treesDOS.writeInt(ni) + case Application(f, arg) => + treesDOS.writeByte(3) + val a1 = lineOfExpr(f) + val a2 = lineOfExpr(arg) + treesDOS.writeInt(a1) + treesDOS.writeInt(a2) line = line + 1 - formulaMap(formula.uniqueNumber) = line + exprMap(e.uniqueNumber) = line line // Write a sequent to the proof file. def sequentToProofDOS(sequent: Sequent): Unit = proofDOS.writeShort(sequent.left.size) - sequent.left.foreach(f => proofDOS.writeInt(lineOfFormula(f))) + sequent.left.foreach(f => proofDOS.writeInt(lineOfExpr(f))) proofDOS.writeShort(sequent.right.size) - sequent.right.foreach(f => proofDOS.writeInt(lineOfFormula(f))) - - def lttToProofDOS(ltt: LambdaTermTerm): Unit = - val body = lineOfTerm(ltt.body) - proofDOS.writeShort(ltt.vars.size) - ltt.vars.foreach(v => termLabelToDOS(v, proofDOS)) - proofDOS.writeInt(body) - - def ltfToProofDOS(ltf: LambdaTermFormula): Unit = - val body = lineOfFormula(ltf.body) - proofDOS.writeShort(ltf.vars.size) - ltf.vars.foreach(v => termLabelToDOS(v, proofDOS)) - proofDOS.writeInt(body) - - def lffToProofDOS(lff: LambdaFormulaFormula): Unit = - val body = lineOfFormula(lff.body) - proofDOS.writeShort(lff.vars.size) - lff.vars.foreach(v => formulaLabelToDOS(v, proofDOS)) - proofDOS.writeInt(body) + sequent.right.foreach(f => proofDOS.writeInt(lineOfExpr(f))) + /** * Write a proof step to the proof file. @@ -196,192 +160,157 @@ object Serialization { case Hypothesis(bot, phi) => proofDOS.writeByte(hypothesis) sequentToProofDOS(bot) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case Cut(bot, t1, t2, phi) => proofDOS.writeByte(cut) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeInt(t2) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case LeftAnd(bot, t1, phi, psi) => proofDOS.writeByte(leftAnd) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case LeftOr(bot, t, disjuncts) => proofDOS.writeByte(leftOr) sequentToProofDOS(bot) proofDOS.writeShort(t.size) t.foreach(proofDOS.writeInt) proofDOS.writeShort(disjuncts.size) - disjuncts.foreach(f => proofDOS.writeInt(lineOfFormula(f))) + disjuncts.foreach(f => proofDOS.writeInt(lineOfExpr(f))) case LeftImplies(bot, t1, t2, phi, psi) => proofDOS.writeByte(leftImplies) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeInt(t2) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case LeftIff(bot, t1, phi, psi) => proofDOS.writeByte(leftIff) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case LeftNot(bot, t1, phi) => proofDOS.writeByte(leftNot) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case LeftForall(bot, t1, phi, x, t) => proofDOS.writeByte(leftForall) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) - proofDOS.writeInt(lineOfTerm(t)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) + proofDOS.writeInt(lineOfExpr(t)) case LeftExists(bot, t1, phi, x) => proofDOS.writeByte(leftExists) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) - case LeftExistsOne(bot, t1, phi, x) => - proofDOS.writeByte(leftExistsOne) - sequentToProofDOS(bot) - proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) case RightAnd(bot, t, conjuncts) => proofDOS.writeByte(rightAnd) sequentToProofDOS(bot) proofDOS.writeShort(t.size) t.foreach(proofDOS.writeInt) proofDOS.writeShort(conjuncts.size) - conjuncts.foreach(f => proofDOS.writeInt(lineOfFormula(f))) + conjuncts.foreach(f => proofDOS.writeInt(lineOfExpr(f))) case RightOr(bot, t1, phi, psi) => proofDOS.writeByte(rightOr) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case RightImplies(bot, t1, phi, psi) => proofDOS.writeByte(rightImplies) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case RightIff(bot, t1, t2, phi, psi) => proofDOS.writeByte(rightIff) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeInt(t2) - proofDOS.writeInt(lineOfFormula(phi)) - proofDOS.writeInt(lineOfFormula(psi)) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(psi)) case RightNot(bot, t1, phi) => proofDOS.writeByte(rightNot) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) + proofDOS.writeInt(lineOfExpr(phi)) case RightForall(bot, t1, phi, x) => proofDOS.writeByte(rightForall) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) case RightExists(bot, t1, phi, x, t) => proofDOS.writeByte(rightExists) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) - proofDOS.writeInt(lineOfTerm(t)) - case RightExistsOne(bot, t1, phi, x) => - proofDOS.writeByte(rightExistsOne) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) + proofDOS.writeInt(lineOfExpr(t)) + case RightEpsilon(bot, t1, phi, x, t) => + proofDOS.writeByte(rightEpsilon) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(phi)) - termLabelToDOS(x, proofDOS) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(x)) + proofDOS.writeInt(lineOfExpr(t)) case Weakening(bot, t1) => proofDOS.writeByte(weakening) sequentToProofDOS(bot) proofDOS.writeInt(t1) + case Beta(bot, t1) => + proofDOS.writeByte(beta) + sequentToProofDOS(bot) + proofDOS.writeInt(t1) case LeftRefl(bot, t1, fa) => proofDOS.writeByte(leftRefl) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfFormula(fa)) + proofDOS.writeInt(lineOfExpr(fa)) case RightRefl(bot, fa) => proofDOS.writeByte(rightRefl) sequentToProofDOS(bot) - proofDOS.writeInt(lineOfFormula(fa)) + proofDOS.writeInt(lineOfExpr(fa)) case LeftSubstEq(bot, t1, equals, lambdaPhi) => proofDOS.writeByte(leftSubstEq) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeShort(equals.size) equals.foreach(ltts => - lttToProofDOS(ltts._1) - lttToProofDOS(ltts._2) + proofDOS.writeInt(lineOfExpr(ltts._1)) + proofDOS.writeInt(lineOfExpr(ltts._2)) ) proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => termLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) + lambdaPhi._1.foreach(stl => proofDOS.writeInt(lineOfExpr(stl))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) case RightSubstEq(bot, t1, equals, lambdaPhi) => - proofDOS.writeByte(rightSubstEq) - sequentToProofDOS(bot) - proofDOS.writeInt(t1) - proofDOS.writeShort(equals.size) - equals.foreach(ltts => - lttToProofDOS(ltts._1) - lttToProofDOS(ltts._2) - ) - proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => termLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) - case LeftSubstIff(bot, t1, equals, lambdaPhi) => - proofDOS.writeByte(leftSubstIff) - sequentToProofDOS(bot) - proofDOS.writeInt(t1) - proofDOS.writeShort(equals.size) - equals.foreach(ltts => - ltfToProofDOS(ltts._1) - ltfToProofDOS(ltts._2) - ) - proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => formulaLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) - case RightSubstIff(bot, t1, equals, lambdaPhi) => - proofDOS.writeByte(rightSubstIff) + proofDOS.writeByte(leftSubstEq) sequentToProofDOS(bot) proofDOS.writeInt(t1) proofDOS.writeShort(equals.size) equals.foreach(ltts => - ltfToProofDOS(ltts._1) - ltfToProofDOS(ltts._2) + proofDOS.writeInt(lineOfExpr(ltts._1)) + proofDOS.writeInt(lineOfExpr(ltts._2)) ) proofDOS.writeShort(lambdaPhi._1.size) - lambdaPhi._1.foreach(stl => formulaLabelToDOS(stl, proofDOS)) - proofDOS.writeInt(lineOfFormula(lambdaPhi._2)) - case InstSchema(bot, t1, mCon, mPred, mTerm) => + lambdaPhi._1.foreach(stl => proofDOS.writeInt(lineOfExpr(stl))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case InstSchema(bot, t1, m) => proofDOS.writeByte(instSchema) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeShort(mCon.size) - mCon.foreach(t => - formulaLabelToDOS(t._1, proofDOS) - lffToProofDOS(t._2) - ) - proofDOS.writeShort(mPred.size) - mPred.foreach(t => - formulaLabelToDOS(t._1, proofDOS) - ltfToProofDOS(t._2) - ) - proofDOS.writeShort(mTerm.size) - mTerm.foreach(t => - termLabelToDOS(t._1, proofDOS) - lttToProofDOS(t._2) + proofDOS.writeShort(m.size) + m.foreach(t => + proofDOS.writeInt(lineOfExpr(t._1)) + proofDOS.writeInt(lineOfExpr(t._2)) ) case SCSubproof(sp, premises) => throw new Exception("Cannot support subproofs, flatten the proof first.") case Sorry(bot) => @@ -403,6 +332,15 @@ object Serialization { } + def typeFromString(s: String): (Sort, String) = + if s(0) == 'T' then (Term, s.drop(1)) + else if s(0) == 'F' then (Formula, s.drop(1)) + else if s(0) == '>' then + val (from, reminder) = typeFromString(s.drop(1)) + val (to, r) = typeFromString(reminder) + (Arrow(from, to), r) + else throw new Exception("Unknown type: " + s) + /** * This functions reverses the effect of proofToDataStream * @@ -410,110 +348,44 @@ object Serialization { */ def proofsFromDataStream(treesDIS: DataInputStream, proofDIS: DataInputStream): Seq[(String, SCProof, List[String])] = { - val termMap = MutMap[Line, Term]() - val formulaMap = MutMap[Line, Formula]() - - // Read a label from the tree file, reversing the effect of termLabelToDOS and formulaLabelToDOS. - def labelFromInputStream(dis: DataInputStream): Label = { - val labelType = dis.readByte() - labelType match - case 0 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - ConstantFunctionLabel(Identifier(name, no), arity) - case 1 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - SchematicFunctionLabel(Identifier(name, no), arity) - case 2 => - val name = dis.readUTF() - val no = dis.readInt() - VariableLabel(Identifier(name, no)) - case 3 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - ConstantAtomicLabel(Identifier(name, no), arity) - case 4 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - SchematicPredicateLabel(Identifier(name, no), arity) - case 5 => - val name = dis.readUTF() - name match - case And.id.name => And - case Or.id.name => Or - case Implies.id.name => Implies - case Iff.id.name => Iff - case Neg.id.name => Neg - case 6 => - val name = dis.readUTF() - val no = dis.readInt() - val arity = dis.readInt() - SchematicConnectorLabel(Identifier(name, no), arity) - case 7 => - val name = dis.readUTF() - val no = dis.readInt() - VariableFormulaLabel(Identifier(name, no)) - case 8 => - dis.readUTF() match - case Forall.id.name => Forall - case Exists.id.name => Exists - case ExistsOne.id.name => ExistsOne - - } + val exprMap = MutMap[Line, Expression]() // Read and reconstruct all the terms and formulas in the tree file. Fill the table with it. var lineNo = -1 try { while true do lineNo = lineNo + 1 - val label = labelFromInputStream(treesDIS) - label match - case l: TermLabel => - val args = (1 to l.arity).map(_ => termMap(treesDIS.readInt())).toSeq - termMap(lineNo) = Term(l, args) - case l: FormulaLabel => - val formula = label match - case l: AtomicLabel => - val args = (1 to l.arity).map(_ => termMap(treesDIS.readInt())).toSeq - AtomicFormula(l, args) - case l: ConnectorLabel => - val ar = treesDIS.readShort() - val args = (1 to ar).map(_ => formulaMap(treesDIS.readInt())).toSeq - ConnectorFormula(l, args) - case l: BinderLabel => - BinderFormula(l, labelFromInputStream(treesDIS).asInstanceOf[VariableLabel], formulaMap(treesDIS.readInt())) - formulaMap(lineNo) = formula + treesDIS.readByte() match + case 0 => + val name = treesDIS.readUTF() + val no = treesDIS.readInt() + val sort = treesDIS.readUTF() + Variable(Identifier(name, no), typeFromString(sort)._1) + case 1 => + val name = treesDIS.readUTF() + val no = treesDIS.readInt() + val sort = treesDIS.readUTF() + Constant(Identifier(name, no), typeFromString(sort)._1) + case 2 => + val v = exprMap(treesDIS.readInt()) + val body = exprMap(treesDIS.readInt()) + Lambda(v.asInstanceOf[Variable], body) + case 3 => + val f = exprMap(treesDIS.readInt()) + val arg = exprMap(treesDIS.readInt()) + Application(f, arg) } catch case _: EOFException => () // Terms and Formulas finished, deal with the proof now. - def lttFromProofDIS(): LambdaTermTerm = - val vars = (1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]).toSeq - val body = termMap(proofDIS.readInt()) - LambdaTermTerm(vars, body) - - def ltfFromProofDIS(): LambdaTermFormula = - val vars = (1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]).toSeq - val body = formulaMap(proofDIS.readInt()) - LambdaTermFormula(vars, body) - - def lffFromProofDIS(): LambdaFormulaFormula = - val vars = (1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[VariableFormulaLabel]).toSeq - val body = formulaMap(proofDIS.readInt()) - LambdaFormulaFormula(vars, body) def sequentFromProofDIS(): Sequent = val leftSize = proofDIS.readShort() - val left = (1 to leftSize).map(_ => formulaMap(proofDIS.readInt())).toSet + val left = (1 to leftSize).map(_ => exprMap(proofDIS.readInt())).toSet val rightSize = proofDIS.readShort() - val right = (1 to rightSize).map(_ => formulaMap(proofDIS.readInt())).toSet + val right = (1 to rightSize).map(_ => exprMap(proofDIS.readInt())).toSet Sequent(left, right) // Read a proof step from the proof file. Inverse of proofStepToProofDOS @@ -521,86 +393,80 @@ object Serialization { val psType = proofDIS.readByte() if (psType == restate) Restate(sequentFromProofDIS(), proofDIS.readInt()) else if (psType == restateTrue) RestateTrue(sequentFromProofDIS()) - else if (psType == hypothesis) Hypothesis(sequentFromProofDIS(), formulaMap(proofDIS.readInt())) - else if (psType == cut) Cut(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) - else if (psType == leftAnd) LeftAnd(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) + else if (psType == hypothesis) Hypothesis(sequentFromProofDIS(), exprMap(proofDIS.readInt())) + else if (psType == cut) Cut(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), exprMap(proofDIS.readInt())) + else if (psType == leftAnd) LeftAnd(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) else if (psType == leftOr) LeftOr( sequentFromProofDIS(), (1 to proofDIS.readShort()).map(_ => proofDIS.readInt()).toSeq, - (1 to proofDIS.readShort()).map(_ => formulaMap(proofDIS.readInt())).toSeq + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt())).toSeq ) - else if (psType == leftImplies) LeftImplies(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == leftIff) LeftIff(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == leftNot) LeftNot(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) + else if (psType == leftImplies) LeftImplies(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == leftIff) LeftIff(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == leftNot) LeftNot(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt())) else if (psType == leftForall) LeftForall( sequentFromProofDIS(), proofDIS.readInt(), - formulaMap(proofDIS.readInt()), - labelFromInputStream(proofDIS).asInstanceOf[VariableLabel], - termMap(proofDIS.readInt()) + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable], + exprMap(proofDIS.readInt()) ) - else if (psType == leftExists) LeftExists(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) - else if (psType == leftExistsOne) LeftExistsOne(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) + else if (psType == leftExists) LeftExists(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt()).asInstanceOf[Variable]) else if (psType == rightAnd) RightAnd( sequentFromProofDIS(), (1 to proofDIS.readShort()).map(_ => proofDIS.readInt()).toSeq, - (1 to proofDIS.readShort()).map(_ => formulaMap(proofDIS.readInt())).toSeq + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt())).toSeq ) - else if (psType == rightOr) RightOr(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == rightImplies) RightImplies(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == rightIff) RightIff(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), formulaMap(proofDIS.readInt())) - else if (psType == rightNot) RightNot(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) - else if (psType == rightForall) RightForall(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) + else if (psType == rightOr) RightOr(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == rightImplies) RightImplies(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == rightIff) RightIff(sequentFromProofDIS(), proofDIS.readInt(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt())) + else if (psType == rightNot) RightNot(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt())) + else if (psType == rightForall) RightForall(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt()).asInstanceOf[Variable]) else if (psType == rightExists) RightExists( sequentFromProofDIS(), proofDIS.readInt(), - formulaMap(proofDIS.readInt()), - labelFromInputStream(proofDIS).asInstanceOf[VariableLabel], - termMap(proofDIS.readInt()) + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable], + exprMap(proofDIS.readInt()) + ) + else if (psType == rightEpsilon) + RightEpsilon( + sequentFromProofDIS(), + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable], + exprMap(proofDIS.readInt()) ) - else if (psType == rightExistsOne) RightExistsOne(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt()), labelFromInputStream(proofDIS).asInstanceOf[VariableLabel]) else if (psType == weakening) Weakening(sequentFromProofDIS(), proofDIS.readInt()) - else if (psType == leftRefl) LeftRefl(sequentFromProofDIS(), proofDIS.readInt(), formulaMap(proofDIS.readInt())) - else if (psType == rightRefl) RightRefl(sequentFromProofDIS(), formulaMap(proofDIS.readInt())) + else if (psType == beta) + Beta(sequentFromProofDIS(), + proofDIS.readInt() + ) + else if (psType == leftRefl) LeftRefl(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt())) + else if (psType == rightRefl) RightRefl(sequentFromProofDIS(), exprMap(proofDIS.readInt())) else if (psType == leftSubstEq) LeftSubstEq( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (lttFromProofDIS(), lttFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicTermLabel]).toList, formulaMap(proofDIS.readInt())) + (1 to proofDIS.readShort()).map(_ => (exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt()))).toList, + ((1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, exprMap(proofDIS.readInt())) ) else if (psType == rightSubstEq) RightSubstEq( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (lttFromProofDIS(), lttFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicTermLabel]).toList, formulaMap(proofDIS.readInt())) - ) - else if (psType == leftSubstIff) - LeftSubstIff( - sequentFromProofDIS(), - proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (ltfFromProofDIS(), ltfFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicAtomicLabel]).toList, formulaMap(proofDIS.readInt())) - ) - else if (psType == rightSubstIff) - RightSubstIff( - sequentFromProofDIS(), - proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (ltfFromProofDIS(), ltfFromProofDIS())).toList, - ((1 to proofDIS.readShort()).map(_ => labelFromInputStream(proofDIS).asInstanceOf[SchematicAtomicLabel]).toList, formulaMap(proofDIS.readInt())) + (1 to proofDIS.readShort()).map(_ => (exprMap(proofDIS.readInt()), exprMap(proofDIS.readInt()))).toList, + ((1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, exprMap(proofDIS.readInt())) ) else if (psType == instSchema) InstSchema( sequentFromProofDIS(), proofDIS.readInt(), - (1 to proofDIS.readShort()).map(_ => (labelFromInputStream(proofDIS).asInstanceOf[SchematicConnectorLabel], lffFromProofDIS())).toMap, - (1 to proofDIS.readShort()).map(_ => (labelFromInputStream(proofDIS).asInstanceOf[SchematicAtomicLabel], ltfFromProofDIS())).toMap, - (1 to proofDIS.readShort()).map(_ => (labelFromInputStream(proofDIS).asInstanceOf[SchematicTermLabel], lttFromProofDIS())).toMap + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable] -> exprMap(proofDIS.readInt())).toMap ) else if (psType == sorry) Sorry(sequentFromProofDIS()) else throw new Exception("Unknown proof step type: " + psType) @@ -636,10 +502,12 @@ object Serialization { val justNames = justs.map { case (obj, theory.Axiom(name, ax)) => "a" + obj + "$" + name case (obj, theory.Theorem(name, proposition, withSorry)) => "t" + obj + "$" + name - case (obj, theory.FunctionDefinition(label, out, expression, withSorry)) => "f" + obj + "$" + label.id.name + "_" + label.id.no + "_" + label.arity - case (obj, theory.PredicateDefinition(label, expression)) => "p" + obj + "$" + label.id.name + "_" + label.id.no + "_" + label.arity + case (obj, theory.Definition(label, expression, vars)) => + "d" + obj + "$" + label.id.name + "_" + label.id.no + "_" + typeToString(label.sort) //+ "__" + + //vars.size + vars.map(v => v.id.name + "_" + v.id.no + "_" + typeToString(v.sort)).mkString("__") } - (name, minimizeProofOnce(proof), justNames) + //(name, minimizeProofOnce(proof), justNames) + (name, proof, justNames) ) ) } @@ -663,12 +531,10 @@ object Serialization { case 'a' => theory.getAxiom(name).get case 't' => theory.getTheorem(name).get - case 'f' => - name.split("_") match - case Array(name, no, arity) => theory.getDefinition(ConstantFunctionLabel(Identifier(name, no.toInt), arity.toInt)).get - case 'p' => - name.split("_") match - case Array(name, no, arity) => theory.getDefinition(ConstantAtomicLabel(Identifier(name, no.toInt), arity.toInt)).get + case 'd' => + val Array(id, no, sort) = name.split("_") + val cst = Constant(Identifier(id, no.toInt), typeFromString(sort)._1) + theory.getDefinition(cst).get } if debug then // To avoid conflicts where a theorem already exists, for example in test suits. diff --git a/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala b/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala new file mode 100644 index 000000000..e0b38ee97 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala @@ -0,0 +1,36 @@ +package lisa.utils.collection + +import scala.collection.immutable.SeqOps +import scala.collection.immutable.VectorBuilder + +object Extensions: + + extension [A](seq: IterableOnce[A]) + /** + * Iterable.collectFirst, but for a function returning an Option. Evaluates + * the function only once per argument. Returns when the first non-`None` + * value is found. + * + * @param f the function to evaluate + */ + def collectFirstDefined[B](f: A => Option[B]): Option[B] = + var res: Option[B] = None + val iter = seq.iterator + while (res.isEmpty && iter.hasNext) res = f(iter.next()) + res + + /** + * Convert an iterable of options to an option of a sequence. + * `Some(Seq(...))` if every value in the iterable is defined, and `None` + * otherwise. + * + * Attempts to do so lazily (if your iterable is lazy). + */ + def toOptionSeq[B](using ev: A <:< Option[B]): Option[Seq[B]] = + val state = true + var res = Option(Vector.newBuilder[B]) + val iter = seq.iterator + while (res.nonEmpty && iter.hasNext) + val next = iter.next() + if next.isDefined then res = res.map(_ += next.get) else res = None + res.map(_.result()) diff --git a/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala b/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala new file mode 100644 index 000000000..4e74b7a06 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala @@ -0,0 +1,76 @@ +package lisa.utils.collection + +import scala.collection.mutable +import scala.collection.IterableFactory +import scala.collection.IterableFactoryDefaults +import scala.collection.immutable.HashSet +import scala.collection.immutable.SetOps + +object VecSet extends IterableFactory[VecSet]: + def empty[A]: VecSet[A] = + new VecSet(Vector.empty, Set.empty) + def from[A](source: IterableOnce[A]): VecSet[A] = + val vec = Vector.from(source) + new VecSet(vec, vec.to(Set)) + def newBuilder[A]: mutable.ReusableBuilder[A, VecSet[A]] = new VecSetBuilder() + + private sealed class VecSetBuilder[A] () extends mutable.ReusableBuilder[A, VecSet[A]]: + protected val vecBuilder: mutable.ReusableBuilder[A, Vector[A]] = Vector.newBuilder[A] + protected val setBuilder: mutable.ReusableBuilder[A, Set[A]] = HashSet.newBuilder[A] + protected var currentSize = 0 + + def addOne(elem: A): this.type = + vecBuilder.addOne(elem) + setBuilder.addOne(elem) + currentSize += 1 + this + + override def clear(): Unit = + vecBuilder.clear() + setBuilder.clear() + currentSize = 0 + + override def result(): VecSet[A] = + new VecSet(vecBuilder.result(), setBuilder.result()) + +sealed class VecSet[A] private (protected val evec: Vector[A], protected val eset: Set[A]) + extends Set[A] + with SetOps[A, VecSet, VecSet[A]] + with IterableFactoryDefaults[A, VecSet]: + // invariants: + // require( evec.toSet == eset ) + // require( evec.distinct == evec ) + + def iterator: Iterator[A] = evec.iterator + + def length: Int = evec.length + + override def iterableFactory: IterableFactory[VecSet] = VecSet + + override def contains(elem: A): Boolean = eset.contains(elem) + + override def excl(elem: A): VecSet[A] = + eset(elem) match + case false => this + case true => + // specialized version of Vector.diff + // without the added dramatic flair + val builder = Vector.newBuilder[A] + val iter = evec.iterator + + while (iter.hasNext) do + val next = iter.next + if next == elem then + // found the element to remove, rush through the remaining + builder.addAll(iter) + else builder.addOne(next) + + new VecSet(builder.result(), eset.excl(elem)) + + override def incl(elem: A): VecSet[A] = + eset(elem) match + case false => new VecSet(evec :+ elem, eset + elem) + case true => this + + override def toSeq: Seq[A] = evec + override def toVector: Vector[A] = evec diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala new file mode 100644 index 000000000..dfc4d36eb --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala @@ -0,0 +1,6 @@ +package lisa.fol + +object FOL extends Sequents { + //export lisa.utils.K + export lisa.utils.K.Identifier +} diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala new file mode 100644 index 000000000..c967ccbc5 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -0,0 +1,158 @@ +package lisa.fol + +import lisa.utils.K +import K.given + +trait Predef extends Syntax { + + export K.{given_Conversion_String_Identifier, given_Conversion_Identifier_String} + + def variable[S](using IsSort[SortOf[S]])(id: K.Identifier): Variable[SortOf[S]] = new Variable(id) + def constant[S](using IsSort[SortOf[S]])(id: K.Identifier): Constant[SortOf[S]] = new Constant(id) + def binder[S1, S2, S3](using IsSort[SortOf[S1]], IsSort[SortOf[S2]], IsSort[SortOf[S3]]) + (id: K.Identifier): Binder[SortOf[S1], SortOf[S2], SortOf[S3]] = new Binder(id) + + def variable[S](using name: sourcecode.Name, is: IsSort[SortOf[S]]): Variable[SortOf[S]] = new Variable(name.value) + def constant[S](using name: sourcecode.Name, is: IsSort[SortOf[S]]): Constant[SortOf[S]] = new Constant(name.value) + def binder[S1, S2, S3](using name: sourcecode.Name) + (using IsSort[SortOf[S1]], IsSort[SortOf[S2]], IsSort[SortOf[S3]]): Binder[SortOf[S1], SortOf[S2], SortOf[S3]] = new Binder(name.value) + + def variable(id: K.Identifier, s: K.Sort): Variable[?] = Variable.unsafe(id, s) + def constant(id: K.Identifier, s: K.Sort): Constant[?] = Constant.unsafe(id, s) + + def variable(using name: sourcecode.Name)(s: K.Sort): Variable[?] = Variable.unsafe(name.value, s) + def constant(using name: sourcecode.Name)(s: K.Sort): Constant[?] = Constant.unsafe(name.value, s) + + + val equality = constant[Term >>: Term >>: Formula]("=") + val === = equality + val = = equality + + extension (t: Term) { + infix def ===(u: Term): Formula = equality(t)(u) + infix def =(u: Term): Formula = equality(t)(u) + } + + val top = constant[Formula]("⊤") + val ⊤ : top.type = top + val True: top.type = top + + val bot = constant[Formula]("⊥") + val ⊥ : bot.type = bot + val False: bot.type = bot + + val neg = constant[Formula >>: Formula]("¬") + val ¬ : neg.type = neg + val ! : neg.type = neg + + val and = constant[Formula >>: Formula >>: Formula]("∧").printInfix() + val /\ : and.type = and + val ∧ : and.type = and + + val or = constant[Formula >>: Formula >>: Formula]("∨").printInfix() + val \/ : or.type = or + val ∨ : or.type = or + + val implies = constant[Formula >>: Formula >>: Formula]("⇒").printInfix() + val ==> : implies.type = implies + + val iff = constant[Formula >>: Formula >>: Formula]("⇔").printInfix() + val <=> : iff.type = iff + val ⇔ : iff.type = iff + + val forall = binder[Term, Formula, Formula]("∀") + val ∀ : forall.type = forall + + val exists = binder[Term, Formula, Formula]("∃") + val ∃ : exists.type = exists + + val epsilon = binder[Term, Formula, Term]("ε") + val ε : epsilon.type = epsilon + + extension (f: Formula) { + def unary_! = neg(f) + infix inline def ==>(g: Formula): Formula = implies(f)(g) + infix inline def <=>(g: Formula): Formula = iff(f)(g) + infix inline def /\(g: Formula): Formula = and(f)(g) + infix inline def ∧(g: Formula): Formula = and(f)(g) + infix inline def \/(g: Formula): Formula = or(f)(g) + infix inline def ∨(g: Formula): Formula = or(f)(g) + } + + inline def andAll(forms: IterableOnce[Formula]): Formula = + forms.iterator.reduce(_ /\ _) + + inline def orAll(forms: IterableOnce[Formula]): Formula = + forms.iterator.reduce(_ \/ _) + + def asFrontExpression(e: K.Expression): Expr[?] = e match + case c: K.Constant => asFrontConstant(c) + case v: K.Variable => asFrontVariable(v) + case a: K.Application => asFrontApplication(a) + case l: K.Lambda => asFrontLambda(l) + + def asFrontConstant(c: K.Constant): Constant[?] = + new Constant[T](c.id)(using unsafeSortEvidence(c.sort)) + + def asFrontVariable(v: K.Variable): Variable[?] = + new Variable[T](v.id)(using unsafeSortEvidence(v.sort)) + + def asFrontApplication(a: K.Application): App[?, ?] = + new App(asFrontExpression(a.f).asInstanceOf, asFrontExpression(a.arg)) + + def asFrontLambda(l: K.Lambda): Abs[?, ?] = + new Abs(asFrontVariable(l.v).asInstanceOf, asFrontExpression(l.body)) + + def greatestId(exprs: Seq[K.Expression | Expr[?] | K.Identifier ]): Int = + exprs.view.flatMap({ + case e: K.Expression => e.freeVariables.map(_.id) + case e: Expr[?] => e.freeVars.map(_.id) + case id: K.Identifier => Seq(id) + }).map(_.no).max + + def freshId(exprs: Iterable[K.Expression | Expr[?] | K.Identifier ], base: String = "x"): K.Identifier = { + val i = exprs.view.flatMap({ + case e: K.Expression => e.freeVariables.map(_.id) + case e: Expr[?] => e.freeVars.map(_.id) + case id: K.Identifier => Seq(id) + }).filter(_.name == base).map(_.no).max + K.Identifier(base, i + 1) + } + + def nFreshIds(n: Int, exprs: Seq[K.Expression | Expr[?] | K.Identifier ], base: String = "x"): Seq[K.Identifier] = { + val i = exprs.view.flatMap({ + case e: K.Expression => e.freeVariables.map(_.id) + case e: Expr[?] => e.freeVars.map(_.id) + case id: K.Identifier => Seq(id) + }).filter(_.name == base).map(_.no).max + (i + 1 to i + n).map(K.Identifier(base, _)) + } + + + object Functional : + def unapply(e: Expr[?]): Option[Seq[K.Sort]] = + if e.sort.isFunctional then Some(K.flatTypeParameters(e.sort)) else None + + def unapply(s: K.Sort): Option[Seq[K.Sort]] = + if s.isFunctional then Some(K.flatTypeParameters(s)) else None + + object Predicate: + def unapply(e: Expr[?]): Option[Seq[K.Sort]] = + if e.sort.isPredicate then Some(K.flatTypeParameters(e.sort)) else None + + def unapply(s: K.Sort): Option[Seq[K.Sort]] = + if s.isPredicate then Some(K.flatTypeParameters(s)) else None + + + def makeEq(s: Expr[?], t: Expr[?]): Formula = + if s.sort != t.sort || !(s.sort.isFunctional || s.sort.isPredicate) then throw new IllegalArgumentException("Can only make equality between predicate and functional expressions") + val no = ((s.freeVars ++ t.freeVars).view.map(_.id.no) ++ Seq(-1)).max+1 + val vars = (no until no+s.sort.depth).map(i => variable[Term](K.Identifier("x", i))) + val inner1 = vars.foldLeft(s)(_ #@ _) + val inner2 = vars.foldLeft(t)(_ #@ _) + val base = if (inner1.sort == K.Formula) iff #@ inner1 #@ inner2 else equality #@ inner1 #@ inner2 + vars.foldRight(base : Formula) { case (s_arg, acc) => forall(s_arg, acc) } + + + +} \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala similarity index 61% rename from lisa-utils/src/main/scala/lisa/fol/Sequents.scala rename to lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala index 7c8b4f355..ff9fc5e19 100644 --- a/lisa-utils/src/main/scala/lisa/fol/Sequents.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala @@ -1,26 +1,36 @@ package lisa.fol -//import lisa.kernel.proof.SequentCalculus.Sequent - -import lisa.fol.FOLHelpers.* import lisa.prooflib.BasicStepTactic import lisa.prooflib.Library import lisa.prooflib.ProofTacticLib.ProofTactic + import lisa.utils.K import scala.annotation.showAsInfix -trait Sequents extends Common with lisa.fol.Lambdas with Predef { +trait Sequents extends Predef { + + object SequentInstantiationRule extends ProofTactic given ProofTactic = SequentInstantiationRule - case class Sequent(left: Set[Formula], right: Set[Formula]) extends LisaObject[Sequent] with Absolute { + case class Sequent(left: Set[Formula], right: Set[Formula]) extends LisaObject{ def underlying: lisa.kernel.proof.SequentCalculus.Sequent = K.Sequent(left.map(_.underlying), right.map(_.underlying)) - def allSchematicLabels: Set[SchematicLabel[?]] = left.flatMap(_.allSchematicLabels) ++ right.flatMap(_.allSchematicLabels) - def freeSchematicLabels: Set[SchematicLabel[?]] = left.flatMap(_.freeSchematicLabels) ++ right.flatMap(_.freeSchematicLabels) - def substituteUnsafe(map: Map[SchematicLabel[?], ? <: LisaObject[?]]): Sequent = Sequent(left.map(_.substituteUnsafe(map)), right.map(_.substituteUnsafe(map))) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Sequent = Sequent(left.map(_.substituteUnsafe(m)), right.map(_.substituteUnsafe(m))) + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Sequent = + super.substituteWithCheck(m).asInstanceOf[Sequent] + override def substitute(pairs: SubstPair*): Sequent = + super.substitute(pairs*).asInstanceOf[Sequent] + + def freeVars: Set[Variable[?]] = left.flatMap(_.freeVars) ++ right.flatMap(_.freeVars) + def freeTermVars: Set[Variable[T]] = left.flatMap(_.freeTermVars) ++ right.flatMap(_.freeTermVars) + def constants: Set[Constant[?]] = left.flatMap(_.constants) ++ right.flatMap(_.constants) + + + + /*Ok for now but what when we have more*/ /** * Substitute schematic symbols inside this, and produces a kernel proof. @@ -30,58 +40,19 @@ trait Sequents extends Common with lisa.fol.Lambdas with Predef { * @param map * @return */ - def instantiateWithProof(map: Map[SchematicLabel[?], ? <: LisaObject[?]], index: Int): (Sequent, Seq[K.SCProofStep]) = { - - val mTerm: Map[SchematicFunctionLabel[?] | Variable, LambdaExpression[Term, Term, ?]] = - map.collect[SchematicFunctionLabel[?] | Variable, LambdaExpression[Term, Term, ?]](p => - p._1 match { - case sl: Variable => (sl, LambdaExpression[Term, Term, 0](Seq(), p._2.asInstanceOf[Term], 0)) - case sl: SchematicFunctionLabel[?] => - p._2 match { - case l: LambdaExpression[Term, Term, ?] @unchecked if (l.bounds.isEmpty || l.bounds.head.isInstanceOf[Variable]) & l.body.isInstanceOf[Term] => - (sl, l) - case s: TermLabel[?] => - val vars = nFreshId(Seq(s.id), s.arity).map(id => Variable(id)) - (sl, LambdaExpression(vars, s.applySeq(vars), s.arity)) - } - } - ) - val mPred: Map[SchematicPredicateLabel[?] | VariableFormula, LambdaExpression[Term, Formula, ?]] = - map.collect[SchematicPredicateLabel[?] | VariableFormula, LambdaExpression[Term, Formula, ?]](p => - p._1 match { - case sl: VariableFormula => (sl, LambdaExpression[Term, Formula, 0](Seq(), p._2.asInstanceOf[Formula], 0)) - case sl: SchematicPredicateLabel[?] => - p._2 match { - case l: LambdaExpression[Term, Formula, ?] @unchecked if (l.bounds.isEmpty || l.bounds.head.isInstanceOf[Variable]) & l.body.isInstanceOf[Formula] => (sl, l) - case s: AtomicLabel[?] => - val vars = nFreshId(Seq(s.id), s.arity).map(id => Variable(id)) - (sl, LambdaExpression(vars, s.applySeq(vars), s.arity)) - } - } - ) - val mConn = map.collect[SchematicConnectorLabel[?], LambdaExpression[Formula, Formula, ?]](p => - p._1 match { - case sl: SchematicConnectorLabel[?] => - p._2 match { - case l: LambdaExpression[Formula, Formula, ?] @unchecked if (l.bounds.isEmpty || l.bounds.head.isInstanceOf[VariableFormula]) & l.body.isInstanceOf[Formula] => (sl, l) - case s: ConnectorLabel => - val vars = nFreshId(Seq(s.id), s.arity).map(VariableFormula.apply) - (sl, LambdaExpression(vars, s.applyUnsafe(vars), s.arity)) - } - } - ) - (substituteUnsafe(map), instantiateWithProofLikeKernel(mConn, mPred, mTerm, index)) + def instantiateWithProof(map: Map[Variable[?], Expr[?]], index: Int): (Sequent, Seq[K.SCProofStep]) = { + (substituteUnsafe(map), instantiateWithProofLikeKernel(map, index)) } def instantiateForallWithProof(args: Seq[Term], index: Int): (Sequent, Seq[K.SCProofStep]) = { if this.right.size != 1 then throw new IllegalArgumentException("Right side of sequent must be a single universally quantified formula") this.right.head match { - case r @ Forall(x, f) => + case r @ App(forall, Abs(x: Variable[T], f: Formula)) => val t = args.head - val newf = f.substitute(x := t) + val newf: Formula = f.substitute(x := t) val s0 = K.Hypothesis((newf |- newf).underlying, newf.underlying) - val s1 = K.LeftForall((r |- newf).underlying, index + 1, f.underlying, x.underlyingLabel, t.underlying) + val s1 = K.LeftForall((r |- newf).underlying, index + 1, f.underlying, x.underlying, t.underlying) val s2 = K.Cut((this.left |- newf).underlying, index, index + 2, r.underlying) if args.tail.isEmpty then (this.left |- newf, Seq(s0, s1, s2)) else @@ -104,30 +75,16 @@ trait Sequents extends Common with lisa.fol.Lambdas with Predef { * @return */ def instantiateWithProofLikeKernel( - mCon: Map[SchematicConnectorLabel[?], LambdaExpression[Formula, Formula, ?]], - mPred: Map[SchematicPredicateLabel[?] | VariableFormula, LambdaExpression[Term, Formula, ?]], - mTerm: Map[SchematicFunctionLabel[?] | Variable, LambdaExpression[Term, Term, ?]], + map: Map[Variable[?], Expr[?]], index: Int ): Seq[K.SCProofStep] = { val premiseSequent = this.underlying - val mConK = mCon.map((sl, le) => (sl.underlyingLabel, underlyingLFF(le))) - val mPredK = mPred.map((sl, le) => - sl match { - case v: VariableFormula => (v.underlyingLabel, underlyingLTF(le)) - case spl: SchematicPredicateLabel[?] => (spl.underlyingLabel, underlyingLTF(le)) - } - ) - val mTermK = mTerm.map((sl, le) => - sl match { - case v: Variable => (v.underlyingLabel, underlyingLTT(le)) - case sfl: SchematicFunctionLabel[?] => (sfl.underlyingLabel, underlyingLTT(le)) - } - ) - val botK = lisa.utils.KernelHelpers.instantiateSchemaInSequent(premiseSequent, mConK, mPredK, mTermK) - val smap = Map[SchematicLabel[?], LisaObject[?]]() ++ mCon ++ mPred ++ mTerm - Seq(K.InstSchema(botK, index, mConK, mPredK, mTermK)) + val mapK = map.map((v, e) => (v.underlying, e.underlying)) + val botK = lisa.utils.KernelHelpers.substituteVariablesInSequent(premiseSequent, mapK) + Seq(K.InstSchema(botK, index, mapK)) } + infix def +<<(f: Formula): Sequent = this.copy(left = this.left + f) infix def -<<(f: Formula): Sequent = this.copy(left = this.left - f) infix def +>>(f: Formula): Sequent = this.copy(right = this.right + f) @@ -178,12 +135,8 @@ trait Sequents extends Common with lisa.fol.Lambdas with Predef { given Conversion[Formula, Sequent] = f => Sequent(Set.empty, Set(f)) - def isSame(formula1: Formula, formula2: Formula): Boolean = { - K.isSame(formula1.underlying, formula2.underlying) - } - - def isSameTerm(term1: Term, term2: Term): Boolean = { - K.isSameTerm(term1.underlying, term2.underlying) + def isSame(e1: Expr[?], e2: Expr[?]): Boolean = { + e1.sort == e2.sort && K.isSame(e1.underlying, e2.underlying) } def isSameSequent(sequent1: Sequent, sequent2: Sequent): Boolean = { @@ -193,20 +146,20 @@ trait Sequents extends Common with lisa.fol.Lambdas with Predef { /** * returns true if the first argument implies the second by the laws of ortholattices. */ - def isImplying(formula1: Formula, formula2: Formula): Boolean = { - K.isImplying(formula1.underlying, formula2.underlying) + def isImplying[S: Sort](e1: Formula, e2: Formula): Boolean = { + K.isImplying(e1.underlying, e2.underlying) } def isImplyingSequent(sequent1: Sequent, sequent2: Sequent): Boolean = { K.isImplyingSequent(sequent1.underlying, sequent2.underlying) } - def isSubset(s1: Set[Formula], s2: Set[Formula]): Boolean = { + def isSubset[A, B](s1: Set[Expr[A]], s2: Set[Expr[B]]): Boolean = { K.isSubset(s1.map(_.underlying), s2.map(_.underlying)) } - def isSameSet(s1: Set[Formula], s2: Set[Formula]): Boolean = + def isSameSet[A, B](s1: Set[Expr[A]], s2: Set[Expr[B]]): Boolean = K.isSameSet(s1.map(_.underlying), s2.map(_.underlying)) - def contains(s: Set[Formula], f: Formula): Boolean = { + def contains[A, B](s: Set[Expr[A]], f: Expr[B]): Boolean = { K.contains(s.map(_.underlying), f.underlying) } diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala new file mode 100644 index 000000000..1d717aac8 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -0,0 +1,313 @@ +package lisa.fol + +import lisa.utils.K +import lisa.utils.K.Identifier +import lisa.utils.K.given_Conversion_String_Identifier + + +import scala.annotation.nowarn +import scala.annotation.showAsInfix +import scala.annotation.targetName +import scala.util.Sorting + +trait Syntax { + + type IsSort[T] = Sort{type Self = T} + + + type Formula = Expr[F] + type Term = Expr[T] + @showAsInfix + infix type >>:[I, O] = (I, O) match { + case (Expr[a], Expr[b]) => Expr[Arrow[a, b]] + } + + type SortOf[T] = T match { + case Expr[t] => t + } + + trait Sort { + type Self + val underlying: K.Sort + } + + + sealed trait T + sealed trait F + sealed trait Arrow[A: Sort, B: Sort] + //final class UnsafeSort(val underlying: K.Sort) extends Sort: + // type Self = UnsafeSort + + given given_TermType: IsSort[T] with + val underlying = K.Term + given given_FormulaType: IsSort[F] with + val underlying = K.Formula + given given_ArrowType[A : Sort as ta, B : Sort as tb]: (IsSort[Arrow[A, B]]) with + val underlying = K.Arrow(ta.underlying, tb.underlying) + + sealed trait SubstPair extends Product { + type S + val _1: Variable[S] + val _2: Expr[S] + } + private case class ConcreteSubstPair[S1] (_1: Variable[S1], _2: Expr[S1]) extends SubstPair {type S = S1} + object SubstPair { + def apply[S1 : Sort](_1: Variable[S1], _2: Expr[S1]): SubstPair {type S = S1} = new ConcreteSubstPair[S1](_1, _2) + def unapply[S1](s: SubstPair{type S = S1}): SubstPair{type S = S1} = s + } + + def unsafeSortEvidence[S](sort: K.Sort) : IsSort[S] = new Sort { type Self = S; val underlying = sort } + + given [T: Sort]: Conversion[(Variable[T], Expr[T]), SubstPair{type S = T}] = s => SubstPair(s._1, s._2) + + + type ArgsTo[S1, Target] <: NonEmptyTuple = S1 match { + case Arrow[a, Target] => Tuple1[Expr[a]] + case Arrow[a, b] => Expr[a] *: ArgsTo[b, Target] + } + + + trait LisaObject { + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): LisaObject + def substituteWithCheck(m: Map[Variable[?], Expr[?]]): LisaObject = { + if m.forall((k, v) => k.sort == v.sort) then + substituteUnsafe(m) + else + val culprit = m.find((k, v) => k.sort != v.sort).get + throw new IllegalArgumentException("Sort mismatch in substitution: " + culprit._1 + " -> " + culprit._2) + } + def substitute(pairs: SubstPair*): LisaObject = + substituteWithCheck(pairs.view.map(s => (s._1, s._2)).toMap) + + def freeVars: Set[Variable[?]] + def freeTermVars: Set[Variable[T]] + def constants: Set[Constant[?]] + } + sealed trait Expr[S] extends LisaObject { + val sort: K.Sort + private val arity = K.flatTypeParameters(sort).size + def underlying: K.Expression + + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Expr[S] + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Expr[S]] + override def substitute(pairs: SubstPair*): Expr[S] = + super.substitute(pairs*).asInstanceOf[Expr[S]] + + def unapplySeq[Target](e: Expr[Target]): Option[ArgsTo[S, Target]] = + def inner[Target](e: Expr[Target]): Option[ArgsTo[S, Target]] = e match + case App(f2, arg) if this == f2 => Some((arg *: EmptyTuple).asInstanceOf[ArgsTo[S, Target]]) + case App(f2, arg) => inner(f2).map(value => (arg *: value).asInstanceOf[ArgsTo[S, Target]]) + case _ => None + inner[Target](e) + + @targetName("unapplySeq2") + def unapplySeq(e: Expr[?]): Option[Seq[Expr[?]]] = Multiapp(this).unapply(e) + + final def defaultMkString(args: Seq[Expr[?]]): String = s"$this(${args.map(a => s"${a}").mkString(", ")})" + final def defaultMkStringSeparated(args: Seq[Expr[?]]): String = s"(${defaultMkString(args)})" + var mkString: Seq[Expr[?]] => String = defaultMkString + var mkStringSeparated: Seq[Expr[?]] => String = defaultMkStringSeparated + + + def #@(arg: Expr[?]): RetExpr[S] = + App.unsafe(this, arg).asInstanceOf + + def #@@(args: Seq[Expr[?]]): Expr[?] = + Multiapp.unsafe(this, args) + + def structuralToString: String = this match + case Variable(id) => s"Variable($id, $sort)" + case Constant(id) => s"Constant($id, $sort)" + case App(f, arg) => s"App(${f.structuralToString}, ${arg.structuralToString})" + case Abs(v, body) => s"Abs(${v.structuralToString}, ${body.structuralToString})" + + } + object #@ { + def unapply[T1, T2](e: Expr[T2]): Option[(Expr[Arrow[T1, T2]], Expr[T1])] = (e: @unchecked) match + case App[T1, T2](f, arg) => Some((f, arg)) + case _ => None + } + + extension [T1, T2](f: Expr[Arrow[T1, T2]]) + def apply(using IsSort[T1], IsSort[T2])(arg: Expr[T1]): Expr[T2] = App(f, arg) + + + type RetExpr[T] <: Expr[?] = T match { + case Arrow[a, b] => Expr[b] + } + + class Multiapp(f: Expr[?]): + def unapply (e: Expr[?]): Option[Seq[Expr[?]]] = + def inner(e: Expr[?]): Option[List[Expr[?]]] = e match + case App(f2, arg) if f == f2 => Some(List(arg)) + case App(f2, arg) => inner(f2).map(arg :: _) + case _ => None + inner(e).map(_.reverse) + + object Multiapp: + def unsafe(f: Expr[?], args: Seq[Expr[?]]): Expr[?] = + args.foldLeft(f)((f, arg) => App.unsafe(f, arg)) + def unapply(e: Expr[?]): Some[(Expr[?], Seq[Expr[?]])] = Some(unfoldAllApp(e)) + + + + + def unfoldAllApp(e:Expr[?]): (Expr[?], List[Expr[?]]) = + def rec(e: Expr[?]): (Expr[?], List[Expr[?]]) = e match + case App(f, arg) => + val (f1, args) = unfoldAllApp(f) + (f1, arg :: args ) + case _ => (e, Nil) + val (f, args) = rec(e) + (f, args.reverse) + + + + + case class Variable[S : Sort as sortEv](id: K.Identifier) extends Expr[S] { + val sort: K.Sort = sortEv.underlying + val underlying: K.Variable = K.Variable(id, sort) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Expr[S] = m.getOrElse(this, this).asInstanceOf[Expr[S]] + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Expr[S]] + override def substitute(pairs: SubstPair*): Expr[S] = + super.substitute(pairs*).asInstanceOf[Expr[S]] + def freeVars: Set[Variable[?]] = Set(this) + def freeTermVars: Set[Variable[T]] = if sort == K.Term then Set(this.asInstanceOf) else Set.empty + def constants: Set[Constant[?]] = Set.empty + def rename(newId: K.Identifier): Variable[S] = Variable(newId) + def freshRename(existing: Iterable[Expr[?]]): Variable[S] = { + val newId = K.freshId(existing.flatMap(_.freeVars.map(_.id)), id) + Variable(newId) + } + override def toString(): String = id.toString + def :=(replacement: Expr[S]) = SubstPair(this, replacement) + } + + object Variable { + def unsafe(id: String, sort: K.Sort): Variable[?] = Variable(id)(using unsafeSortEvidence(sort)) + } + + + case class Constant[S : Sort as sortEv](id: K.Identifier) extends Expr[S] { + val sort: K.Sort = sortEv.underlying + private var infix: Boolean = false + def printInfix(): Constant[S] = + infix = true + this + val underlying: K.Constant = K.Constant(id, sort) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Constant[S] = this + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Constant[S]] + override def substitute(pairs: SubstPair*): Constant[S] = + super.substitute(pairs*).asInstanceOf[Constant[S]] + def freeVars: Set[Variable[?]] = Set.empty + def freeTermVars: Set[Variable[T]] = Set.empty + def constants: Set[Constant[?]] = Set(this) + def rename(newId: K.Identifier): Constant[S] = Constant(newId) + override def toString(): String = id.toString + mkString = (args: Seq[Expr[?]]) => + if infix && args.size == 2 then + s"${args(0)} $this ${args(1)}" + else if infix & args.size > 2 then + s"(${args(0)} $this ${args(1)})${args.drop(2).map(_.mkStringSeparated).mkString})" + else + defaultMkString(args) + mkStringSeparated = (args: Seq[Expr[?]]) => + if infix && args.size == 2 then + s"${args(0)} $this ${args(1)}" + else if infix & args.size > 2 then + s"(${args(0)} $this ${args(1)})${args.drop(2).map(_.mkStringSeparated).mkString})" + else + defaultMkStringSeparated(args) + + def asBinder[T1: Sort, T2: Sort, T3: Sort](using S =:= Arrow[Arrow[T1, T2], T3]): Binder[T1, T2, T3] & Constant[Arrow[Arrow[T1, T2], T3]] = new Binder[T1, T2, T3](id) + } + + object Constant { + def unsafe(id: String, sort: K.Sort): Constant[?] = Constant(id)(using unsafeSortEvidence(sort)) + } + + class Binder[T1: Sort, T2: Sort, T3: Sort](id: K.Identifier) extends Constant[Arrow[Arrow[T1, T2], T3]](id) { + def apply(v1: Variable[T1], e: Expr[T2]): App[Arrow[T1, T2], T3] = App(this, Abs(v1, e)) + @targetName("unapplyBinder") + def unapply(e: Expr[T3]): Option[(Variable[T1], Expr[T2])] = e match { + case App(f:Expr[Arrow[Arrow[T1, T2], T3]], Abs(v, e)) if f == this => Some((v, e)) + case _ => None + } + mkString = (args: Seq[Expr[?]]) => + if args.size == 0 then toString + else args(0) match { + case Abs(v, e) => s"$id($v, $e)${args.drop(1).map(_.mkStringSeparated).mkString}" + case _ => defaultMkString(args) + } + mkStringSeparated = (args: Seq[Expr[?]]) => + args match { + case Seq(Abs(v, e)) => s"($id($v, $e))" + case _ => defaultMkStringSeparated(args) + } + } + + + case class App[T1, T2](f: Expr[Arrow[T1, T2]], arg: Expr[T1]) extends Expr[T2] { + val sort: K.Sort = f.sort match + case K.Arrow(from, to) if from == arg.sort => to + case _ => throw new IllegalArgumentException("Sort mismatch. f: " + f.sort + ", arg: " + arg.sort) + + val underlying: K.Application = K.Application(f.underlying, arg.underlying) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): App[T1, T2] = App[T1, T2](f.substituteUnsafe(m), arg.substituteUnsafe(m)) + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): App[T1, T2] = + super.substituteWithCheck(m).asInstanceOf[App[T1, T2]] + override def substitute(pairs: SubstPair*): App[T1, T2] = + super.substitute(pairs*).asInstanceOf[App[T1, T2]] + def freeVars: Set[Variable[?]] = f.freeVars ++ arg.freeVars + def freeTermVars: Set[Variable[T]] = f.freeTermVars ++ arg.freeTermVars + def constants: Set[Constant[?]] = f.constants ++ arg.constants + override def toString(): String = + val (f, args) = unfoldAllApp(this) + f.mkString(args) + } + + object App { + def unsafe(f: Expr[?], arg: Expr[?]): Expr[?] = + val rsort = K.legalApplication(f.sort, arg.sort) + rsort match + case Some(to) => + App(f.asInstanceOf, arg) + case None => throw new IllegalArgumentException(s"Cannot apply $f of sort ${f.sort} to $arg of sort ${arg.sort}") + } + + + case class Abs[T1, T2](v: Variable[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { + val sort: K.Sort = K.Arrow(v.sort, body.sort) + val underlying: K.Lambda = K.Lambda(v.underlying, body.underlying) + def substituteUnsafe(m: Map[Variable[?], Expr[?]]): Abs[T1, T2] = Abs(v, body.substituteUnsafe(m - v)) + override def substituteWithCheck(m: Map[Variable[?], Expr[?]]): Abs[T1, T2] = + super.substituteWithCheck(m).asInstanceOf[Abs[T1, T2]] + override def substitute(pairs: SubstPair*): Abs[T1, T2] = + super.substitute(pairs*).asInstanceOf[Abs[T1, T2]] + def freeVars: Set[Variable[?]] = body.freeVars - v + def freeTermVars: Set[Variable[T]] = body.freeTermVars.filterNot(_ == v) + def constants: Set[Constant[?]] = body.constants + override def toString(): String = s"Abs($v, $body)" + } + + object Abs: + def unsafe(v: Variable[?], body: Expr[?]): Expr[?] = + new Abs(v.asInstanceOf, body.asInstanceOf) + + def apply[S1, S2](v: Variable[S1], body: Expr[S2]): Abs[S1, S2] = new Abs(v, body) + + def apply(xs: Seq[Variable[?]], t: Expr[?]): Expr[?] = xs.foldRight(t)((x, t) => new Abs(x, t)) + + val lambda = Abs + + + + +} + + + + diff --git a/lisa-utils/src/main/scala/lisa/utils/memoization/Memoized.scala b/lisa-utils/src/main/scala/lisa/utils/memoization/Memoized.scala index 43afb5527..b010cabed 100644 --- a/lisa-utils/src/main/scala/lisa/utils/memoization/Memoized.scala +++ b/lisa-utils/src/main/scala/lisa/utils/memoization/Memoized.scala @@ -8,9 +8,9 @@ case class MemoizationStats(hits: Int, miss: Int, faulted: Int): case object InfiniteRecursionDetectedException extends Exception class Memoized[From, To](fun: From => To) extends Function[From, To]: - private val visited = scala.collection.mutable.HashSet.empty[From] - private val memory = scala.collection.mutable.HashMap.empty[From, To] - private var stats = MemoizationStats(0, 0, 0) + protected val visited = scala.collection.mutable.HashSet.empty[From] + protected val memory = scala.collection.mutable.HashMap.empty[From, To] + protected var stats = MemoizationStats(0, 0, 0) protected def handleFault(): To = throw InfiniteRecursionDetectedException diff --git a/lisa-utils/src/main/scala/lisa/utils/package.scala b/lisa-utils/src/main/scala/lisa/utils/package.scala deleted file mode 100644 index 0dd12e8fc..000000000 --- a/lisa-utils/src/main/scala/lisa/utils/package.scala +++ /dev/null @@ -1,4 +0,0 @@ -package lisa.utils - -export lisa.utils.parsing.{FOLParser, FOLPrinter, Parser, Printer, ProofPrinter} -//export lisa.utils.KernelHelpers.{*, given} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/Parser.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/Parser.scala deleted file mode 100644 index 806caf3da..000000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/Parser.scala +++ /dev/null @@ -1,689 +0,0 @@ -package lisa.utils.parsing - -import lisa.kernel.fol.FOL -import lisa.kernel.fol.FOL.* -import lisa.kernel.fol.FOL.equality -import lisa.kernel.proof.SequentCalculus.* -import lisa.utils.KernelHelpers.False -import lisa.utils.KernelHelpers.given_Conversion_Identifier_String -import lisa.utils.KernelHelpers.given_Conversion_String_Identifier -import lisa.utils.parsing.ParsingUtils -import scallion.* -import scallion.util.Unfolds.unfoldRight -import silex.* - -import scala.collection.mutable - -val FOLParser = Parser(SynonymInfo.empty, "=" :: Nil, Nil) - -enum Associativity { - case Left, Right, None -} - -//TODO: Deal with errors in parsing. -class ParsingException(parsedString: String, errorMessage: String) extends lisa.utils.LisaException(errorMessage) { - def showError: String = "" -} - -abstract class ParserException(msg: String) extends Exception(msg) - -class UnexpectedInputException(input: String, position: (Int, Int), expected: String) - extends ParserException( - s""" - |$input - |${" " * position._1 + "^" * (position._2 - position._1)} - |Unexpected input: expected $expected - |""".stripMargin - ) - -object UnexpectedEndOfInputException extends Exception("Unexpected end of input") - -object UnreachableException extends ParserException("Internal error: expected unreachable") - -class PrintFailedException(inp: Sequent | Formula | Term) extends ParserException(s"Printing of $inp failed unexpectedly") - -/** - * @param synonymToCanonical information about synonyms that correspond to the same FunctionLabel / AtomicLabel. - * Can be constructed with [[lisa.utils.SynonymInfoBuilder]] - * @param infixPredicates list of infix predicates' names - * @param infixFunctions list of infix functions and their associativity in the decreasing order of priority - */ -class Parser( - synonymToCanonical: SynonymInfo, - infixPredicates: List[String], - infixFunctions: List[(String, Associativity)] -) { - private val infixPredicateSet = infixPredicates.toSet - private val infixFunctionSet = infixFunctions.map(_._1).toSet - private val infixSet = infixPredicateSet ++ infixFunctionSet - - /** - * Parses a sequent from a string. A sequent consists of the left and right side, separated by `⊢` or `|-`. - * Left and right sides consist of formulas, separated by `;`. - * - * @see Parser#parseFormula - * @param s string representation of the sequent - * @return parsed sequent on success, throws an exception when unexpected input or end of input. - */ - def parseSequent(s: String): Sequent = - try { - extractParseResult(s, SequentParser.parseTermulaSequent(SequentLexer(s.iterator))).toSequent - } catch { - case e: ExpectedFormulaGotTerm => throw new UnexpectedInputException(s, e.range, "formula") - case e: ExpectedTermGotFormula => throw new UnexpectedInputException(s, e.range, "term") - } - - /** - * Parses a formula from a string. A formula can be: - *- a bound formula: `∀'x. f`, `∃'x. f`, `∃!'x. f`. A binder binds the entire formula until the end of the scope (a closing parenthesis or the end of string). - *
- two formulas, connected by `⇔` or `⇒`. Iff / implies bind less tight than and / or. - *
- a conjunction or disjunction of arbitrary number of formulas. `∧` binds tighter than `∨`. - *
- negated formula. - *
- schematic connector formula: `?c(f1, f2, f3)`. - *
- equality of two formulas: `f1 = f2`. - *
- a constant `p(a)` or schematic `'p(a)` predicate application to arbitrary number of term arguments. - *
- boolean constant: `⊤` or `⊥`. - * - * @param s string representation of the formula - * @return parsed formula on success, throws an exception when unexpected input or end of input. - */ - def parseFormula(s: String): Formula = - try { - extractParseResult(s, SequentParser.parseTermula(SequentLexer(s.iterator))).toFormula - } catch { - case e: ExpectedFormulaGotTerm => throw new UnexpectedInputException(s, e.range, "formula") - case e: ExpectedTermGotFormula => throw new UnexpectedInputException(s, e.range, "term") - } - - /** - * Parses a term from a string. A term is a constant `c`, a schematic variable `'x` or an application of a constant `f(a)` - * or a schematic `'f(a)` function to other terms. - * - * @param s string representation of the term - * @return parsed term on success, throws an exception when unexpected input or end of input. - */ - def parseTerm(s: String): Term = - try { - extractParseResult(s, SequentParser.parseTermula(SequentLexer(s.iterator))).toTerm - } catch { - case e: ExpectedFormulaGotTerm => throw new UnexpectedInputException(s, e.range, "formula") - case e: ExpectedTermGotFormula => throw new UnexpectedInputException(s, e.range, "term") - } - - private def extractParseResult[T](input: String, r: SequentParser.ParseResult[T]): T = r match { - case SequentParser.Parsed(value, _) => value - case SequentParser.UnexpectedToken(token, rest) => throw UnexpectedInputException(input, token.range, "one of " + rest.first.mkString(", ")) - case SequentParser.UnexpectedEnd(_) => throw UnexpectedEndOfInputException - } - - /** - * Returns a string representation of the sequent. Empty set of formulas on the left side is not printed. - * Empty set of formulas on the right side is represented as `⊥` (false). - * - * @param s sequent to print - * @return string representation of the sequent, or the smallest component thereof, on which printing failed - */ - def printSequent(s: Sequent): String = SequentParser - .printTermulaSequent(s.toTermulaSequent) - .getOrElse({ - // attempt to print individual formulas. It might throw a more detailed PrintFailedException - s.left.foreach(printFormula) - s.right.foreach(printFormula) - throw PrintFailedException(s) - }) - - /** - * @param f formula to print - * @return string representation of the formula, or the smallest component thereof, on which printing failed - */ - def printFormula(f: Formula): String = SequentParser - .printTermula(f.toTermula) - .getOrElse({ - f match { - case AtomicFormula(_, args) => args.foreach(printTerm) - case ConnectorFormula(_, args) => args.foreach(printFormula) - case BinderFormula(_, _, inner) => printFormula(inner) - } - throw PrintFailedException(f) - }) - - /** - * @param t term to print - * @return string representation of the term, or the smallest component thereof, on which printing failed - */ - def printTerm(t: Term): String = SequentParser - .printTermula(t.toTermula) - .getOrElse({ - t match { - case Term(_, args) => args.foreach(printTerm) - case VariableTerm(_) => () - } - throw PrintFailedException(t) - }) - - private val UNKNOWN_RANGE = (-1, -1) - - private[Parser] object SequentLexer extends Lexers with CharLexers { - sealed abstract class FormulaToken(stringRepr: String) { - def printString: String = stringRepr - val range: (Int, Int) - } - - case class ForallToken(range: (Int, Int)) extends FormulaToken(Forall.id) - - case class ExistsOneToken(range: (Int, Int)) extends FormulaToken(ExistsOne.id) - - case class ExistsToken(range: (Int, Int)) extends FormulaToken(Exists.id) - - case class DotToken(range: (Int, Int)) extends FormulaToken(".") - - case class AndToken(range: (Int, Int), prefix: Boolean) extends FormulaToken(And.id) - - case class OrToken(range: (Int, Int), prefix: Boolean) extends FormulaToken(Or.id) - - case class ImpliesToken(range: (Int, Int)) extends FormulaToken(Implies.id) - - case class IffToken(range: (Int, Int)) extends FormulaToken(Iff.id) - - case class NegationToken(range: (Int, Int)) extends FormulaToken(Neg.id) - - case class TrueToken(range: (Int, Int)) extends FormulaToken("⊤") - - case class FalseToken(range: (Int, Int)) extends FormulaToken("⊥") - - // Constant functions and predicates - case class ConstantToken(id: String, range: (Int, Int)) extends FormulaToken(id) - - // Variables, schematic functions and predicates - case class SchematicToken(id: String, range: (Int, Int)) extends FormulaToken(schematicSymbol + id) - - // This token is not required for parsing, but is needed to print spaces around infix operations - case class InfixToken(id: String, range: (Int, Int)) extends FormulaToken(id) - - // Schematic connector (prefix notation is expected) - case class SchematicConnectorToken(id: String, range: (Int, Int)) extends FormulaToken(schematicConnectorSymbol + id) - - case class ParenthesisToken(isOpen: Boolean, range: (Int, Int)) extends FormulaToken(if (isOpen) "(" else ")") - - case class CommaToken(range: (Int, Int)) extends FormulaToken(",") - - case class SemicolonToken(range: (Int, Int)) extends FormulaToken(";") - - case class SequentToken(range: (Int, Int)) extends FormulaToken("⊢") - - case class SpaceToken(range: (Int, Int)) extends FormulaToken(" ") - - case class UnknownToken(str: String, range: (Int, Int)) extends FormulaToken(str) - - type Token = FormulaToken - type Position = Int - - val escapeChar = '`' - val pathSeparator = '$' - private val schematicSymbol = "'" - private val schematicConnectorSymbol = "?" - - private val letter = elem(_.isLetter) - private val variableLike = letter ~ many(elem(c => c.isLetterOrDigit || c == '_')) - private val number = many1(elem(_.isDigit)) - private val escaped = elem(escapeChar) ~ many1(elem(_ != escapeChar)) ~ elem(escapeChar) - private val arbitrarySymbol = elem(!_.isWhitespace) - private val symbolSequence = many1(oneOf("*/+-^:<>#%&@")) - private val path = many1(many1(letter) ~ elem(pathSeparator)) - - private val lexer = Lexer( - elem('∀') |> { (_, r) => ForallToken(r) }, - word("∃!") |> { (_, r) => ExistsOneToken(r) }, - elem('∃') |> { (_, r) => ExistsToken(r) }, - elem('.') |> { (_, r) => DotToken(r) }, - elem('∧') | word("/\\") |> { (_, r) => AndToken(r, false) }, - elem('∨') | word("\\/") |> { (_, r) => OrToken(r, false) }, - word(Implies.id.name) | word("=>") | word("==>") | elem('⇒') |> { (_, r) => ImpliesToken(r) }, - word(Iff.id.name) | word("<=>") | word("<==>") | elem('⟷') | elem('⇔') |> { (_, r) => IffToken(r) }, - elem('⊤') | elem('T') | word("True") | word("true") |> { (_, r) => TrueToken(r) }, - elem('⊥') | elem('F') | word("False") | word("false") |> { (_, r) => FalseToken(r) }, - elem('¬') | elem('!') |> { (_, r) => NegationToken(r) }, - elem('(') |> { (_, r) => ParenthesisToken(true, r) }, - elem(')') |> { (_, r) => ParenthesisToken(false, r) }, - elem(',') |> { (_, r) => CommaToken(r) }, - elem(';') |> { (_, r) => SemicolonToken(r) }, - elem('⊢') | word("|-") |> { (_, r) => SequentToken(r) }, - many1(whiteSpace) |> { (_, r) => SpaceToken(r) }, - word(schematicSymbol) ~ variableLike |> { (cs, r) => - // drop the ' - SchematicToken(cs.drop(1).mkString, r) - }, - word(schematicConnectorSymbol) ~ variableLike |> { (cs, r) => - SchematicConnectorToken(cs.drop(1).mkString, r) - }, - // Currently the path is merged into the id on the lexer level. When qualified ids are supported, this should be - // lifted into the parser. - opt(path) ~ (variableLike | number | arbitrarySymbol | symbolSequence | escaped) |> { (cs, r) => ConstantToken(cs.filter(_ != escapeChar).mkString, r) } - ) onError { (cs, r) => - UnknownToken(cs.mkString, r) - } - - def apply(it: Iterator[Char]): Iterator[Token] = { - val source = Source.fromIterator(it, IndexPositioner) - lexer - .spawn(source) - .map({ - case ConstantToken(id, r) if infixSet.contains(id) => InfixToken(id, r) - case t => t - }) - .filter(!_.isInstanceOf[SpaceToken]) - } - - def unapply(tokens: Iterator[Token]): String = { - val space = " " - tokens - .foldLeft(Nil: List[String]) { - // Sequent token is the only separator that can have an empty left side; in this case, omit the space before - case (Nil, s: SequentToken) => s.printString :: space :: Nil - case (l, t) => - t match { - // do not require spaces - - case _: ForallToken | _: ExistsToken | _: ExistsOneToken | _: NegationToken | _: ConstantToken | _: SchematicToken | _: SchematicConnectorToken | _: TrueToken | _: FalseToken | - _: ParenthesisToken | _: SpaceToken | AndToken(_, true) | OrToken(_, true) => - l :+ t.printString - // space after: separators - case _: DotToken | _: CommaToken | _: SemicolonToken => l :+ t.printString :+ space - // space before and after: infix, connectors, sequent symbol - - case _: InfixToken | _: AndToken | _: OrToken | _: ImpliesToken | _: IffToken | _: SequentToken => l :+ space :+ t.printString :+ space - case _: UnknownToken => throw UnreachableException - } - } - .mkString - } - } - - case class RangedLabel(folLabel: FOL.FormulaLabel, range: (Int, Int)) - - abstract class TermulaConversionError(range: (Int, Int)) extends Exception - case class ExpectedTermGotFormula(range: (Int, Int)) extends TermulaConversionError(range) - case class ExpectedFormulaGotTerm(range: (Int, Int)) extends TermulaConversionError(range) - - /** - * Termula represents parser-level union of terms and formulas. - * After parsing, the termula can be converted either to a term or to a formula: - *
- to be converted to a term, termula must consist only of function applications; - *
- to be converted to a formula, `args` of the termula are interpreted as formulas until a predicate application is observed; - * `args` of the predicate application are terms. - * - *
Convention: since the difference between `TermLabel`s and `AtomicLabel`s is purely semantic and Termula needs - * FormulaLabels (because of connector and binder labels), all TermLabels are translated to the corresponding - * PredicateLabels (see [[toTermula]]). - * - * @param label `AtomicLabel` for predicates and functions, `ConnectorLabel` or `BinderLabel` - * @param args Predicate / function arguments for `AtomicLabel`, connected formulas for `ConnectorLabel`, - * `Seq(VariableFormulaLabel(bound), inner)` for `BinderLabel` - */ - case class Termula(label: RangedLabel, args: Seq[Termula], range: (Int, Int)) { - def toTerm: Term = label.folLabel match { - case _: BinderLabel | _: ConnectorLabel => throw ExpectedTermGotFormula(range) - case t: ConstantAtomicLabel => Term(ConstantFunctionLabel(t.id, t.arity), args.map(_.toTerm)) - case t: SchematicPredicateLabel => Term(SchematicFunctionLabel(t.id, t.arity), args.map(_.toTerm)) - case v: VariableFormulaLabel => Term(VariableLabel(v.id), Seq()) - } - - def toFormula: Formula = label.folLabel match { - case p: AtomicLabel => AtomicFormula(p, args.map(_.toTerm)) - case c: ConnectorLabel => ConnectorFormula(c, args.map(_.toFormula)) - case b: BinderLabel => - args match { - case Seq(Termula(RangedLabel(v: VariableFormulaLabel, _), Seq(), _), f) => BinderFormula(b, VariableLabel(v.id), f.toFormula) - // wrong binder format. Termulas can only be constructed within parser and they are expected to always be constructed according - // to the format above - case _ => throw UnreachableException - } - } - } - - extension (f: Formula) { - - def toTermula: Termula = { - given Conversion[FOL.FormulaLabel, RangedLabel] with { - def apply(f: FOL.FormulaLabel): RangedLabel = RangedLabel(f, UNKNOWN_RANGE) - } - - f match { - case AtomicFormula(label, args) => Termula(label, args.map(_.toTermula), UNKNOWN_RANGE) - // case ConnectorFormula(And | Or, Seq(singleArg)) => singleArg.toTermula - case ConnectorFormula(label, args) => Termula(label, args.map(_.toTermula), UNKNOWN_RANGE) - case BinderFormula(label, bound, inner) => Termula(label, Seq(Termula(VariableFormulaLabel(bound.id), Seq(), UNKNOWN_RANGE), inner.toTermula), UNKNOWN_RANGE) - } - } - } - - extension (t: Term) { - def toTermula: Termula = { - given Conversion[FOL.FormulaLabel, RangedLabel] with { - def apply(f: FOL.FormulaLabel): RangedLabel = RangedLabel(f, UNKNOWN_RANGE) - } - val newLabel = t.label match { - case ConstantFunctionLabel(id, arity) => ConstantAtomicLabel(id, arity) - case SchematicFunctionLabel(id, arity) => SchematicPredicateLabel(id, arity) - case VariableLabel(id) => VariableFormulaLabel(id) - } - Termula(newLabel, t.args.map(_.toTermula), UNKNOWN_RANGE) - } - } - - case class TermulaSequent(left: Set[Termula], right: Set[Termula]) { - def toSequent: Sequent = Sequent(left.map(_.toFormula), right.map(_.toFormula)) - } - - extension (s: Sequent) { - def toTermulaSequent: TermulaSequent = TermulaSequent(s.left.map(_.toTermula), s.right.map(_.toTermula)) - } - - private[Parser] object SequentParser extends Parsers with ParsingUtils { - sealed abstract class TokenKind(debugString: String) { - override def toString: Mark = debugString - } - // and, or are both (left) associative and bind tighter than implies, iff - case object AndKind extends TokenKind(And.id) - case object OrKind extends TokenKind(Or.id) - // implies, iff are both non-associative and are of equal priority, lower than and, or - case object TopLevelConnectorKind extends TokenKind(s"${Implies.id} or ${Iff.id}") - case object NegationKind extends TokenKind(Neg.id) - case object BooleanConstantKind extends TokenKind("boolean constant") - case object IdKind extends TokenKind("constant or schematic id") - case class InfixKind(id: String) extends TokenKind(s"infix operation $id") - case object CommaKind extends TokenKind(",") - case class ParenthesisKind(isOpen: Boolean) extends TokenKind(if (isOpen) "(" else ")") - case object BinderKind extends TokenKind("binder") - case object DotKind extends TokenKind(".") - case object SemicolonKind extends TokenKind(";") - case object SequentSymbolKind extends TokenKind("⊢") - case object OtherKind extends TokenKind("
") - - import SequentLexer._ - type Token = FormulaToken - type Kind = TokenKind - - // can safely skip tokens which were mapped to unit with elem(kind).unit(token) - import SafeImplicits._ - - def getKind(t: Token): Kind = t match { - case _: AndToken => AndKind - case _: OrToken => OrKind - case _: IffToken | _: ImpliesToken => TopLevelConnectorKind - case _: NegationToken => NegationKind - case _: TrueToken | _: FalseToken => BooleanConstantKind - case InfixToken(id, _) => InfixKind(id) - case _: ConstantToken | _: SchematicToken | _: SchematicConnectorToken => IdKind - case _: CommaToken => CommaKind - case ParenthesisToken(isOpen, _) => ParenthesisKind(isOpen) - case _: ExistsToken | _: ExistsOneToken | _: ForallToken => BinderKind - case _: DotToken => DotKind - case _: SemicolonToken => SemicolonKind - case _: SequentToken => SequentSymbolKind - case _: SpaceToken | _: UnknownToken => OtherKind - } - - /////////////////////// SEPARATORS //////////////////////////////// - def parens(isOpen: Boolean): Syntax[Unit] = - elem(ParenthesisKind(isOpen)).unit(ParenthesisToken(isOpen, UNKNOWN_RANGE)) - - val open: Syntax[Unit] = parens(true) - - val closed: Syntax[Unit] = parens(false) - - val comma: Syntax[Unit] = elem(CommaKind).unit(CommaToken(UNKNOWN_RANGE)) - - val dot: Syntax[Unit] = elem(DotKind).unit(DotToken(UNKNOWN_RANGE)) - - val semicolon: Syntax[Unit] = elem(SemicolonKind).unit(SemicolonToken(UNKNOWN_RANGE)) - - val sequentSymbol: Syntax[Unit] = elem(SequentSymbolKind).unit(SequentToken(UNKNOWN_RANGE)) - /////////////////////////////////////////////////////////////////// - - //////////////////////// LABELS /////////////////////////////////// - val toplevelConnector: Syntax[RangedLabel] = accept(TopLevelConnectorKind)( - { - case ImpliesToken(r) => RangedLabel(Implies, r) - case IffToken(r) => RangedLabel(Iff, r) - }, - { - case RangedLabel(Implies, r) => Seq(ImpliesToken(r)) - case RangedLabel(Iff, r) => Seq(IffToken(r)) - case _ => throw UnreachableException - } - ) - - val negation: Syntax[RangedLabel] = accept(NegationKind)( - { case NegationToken(r) => RangedLabel(Neg, r) }, - { - case RangedLabel(Neg, r) => Seq(NegationToken(r)) - case _ => throw UnreachableException - } - ) - - val INFIX_ARITY = 2 - /////////////////////////////////////////////////////////////////// - - //////////////////////// TERMULAS ///////////////////////////////// - // can appear without parentheses - lazy val simpleTermula: Syntax[Termula] = prefixApplication | negated | bool - lazy val subtermula: Syntax[Termula] = simpleTermula | open.skip ~ termula ~ closed.skip - - val bool: Syntax[Termula] = accept(BooleanConstantKind)( - { - case TrueToken(r) => Termula(RangedLabel(top, r), Seq(), r) - case FalseToken(r) => Termula(RangedLabel(bot, r), Seq(), r) - }, - { - case Termula(RangedLabel(And, _), Seq(), r) => Seq(TrueToken(r)) - case Termula(RangedLabel(Or, _), Seq(), r) => Seq(FalseToken(r)) - case _ => throw UnreachableException - } - ) - - case class RangedTermulaSeq(ts: Seq[Termula], range: (Int, Int)) - lazy val args: Syntax[RangedTermulaSeq] = recursive( - (elem(ParenthesisKind(true)) ~ repsep(termula, comma) ~ elem(ParenthesisKind(false))).map( - { case start ~ ts ~ end => - RangedTermulaSeq(ts, (start.range._1, end.range._2)) - }, - { case RangedTermulaSeq(ts, (start, end)) => - Seq(ParenthesisToken(true, (start, start)) ~ ts ~ ParenthesisToken(false, (end, end))) - } - ) - ) - - def reconstructPrefixApplication(t: Termula): Token ~ Option[RangedTermulaSeq] = { - val argsRange = (t.label.range._2 + 1, t.range._2) - t.label.folLabel match { - case VariableFormulaLabel(id) => SchematicToken(id, t.label.range) ~ None - case SchematicPredicateLabel(id, _) => SchematicToken(id, t.range) ~ Some(RangedTermulaSeq(t.args, argsRange)) - case ConstantAtomicLabel(id, arity) => - ConstantToken(synonymToCanonical.getPrintName(id), t.label.range) ~ - (if (arity == 0) None else Some(RangedTermulaSeq(t.args, argsRange))) - case _ => throw UnreachableException - } - } - - // schematic connector, function, or predicate; constant function or predicate - val prefixApplication: Syntax[Termula] = ((elem(IdKind) | elem(OrKind) | elem(AndKind)) ~ opt(args)).map( - { case p ~ optArgs => - val args: Seq[Termula] = optArgs.map(_.ts).getOrElse(Seq()) - val l = p match { - - case ConstantToken(id, _) => ConstantAtomicLabel(synonymToCanonical.getInternalName(id), args.size) - case SchematicToken(id, _) => - if (args.isEmpty) VariableFormulaLabel(id) else SchematicPredicateLabel(id, args.size) - case SchematicConnectorToken(id, _) => SchematicConnectorLabel(id, args.size) - case AndToken(_, _) => And - case OrToken(_, _) => Or - case _ => throw UnreachableException - } - Termula(RangedLabel(l, p.range), args, (p.range._1, optArgs.map(_.range._2).getOrElse(p.range._2))) - }, - { - case t @ Termula(RangedLabel(_: AtomicLabel, _), _, _) => Seq(reconstructPrefixApplication(t)) - case t @ Termula(RangedLabel(SchematicConnectorLabel(id, _), r), args, _) => - val argsRange = (t.label.range._2 + 1, t.range._2) - Seq(SchematicConnectorToken(id, r) ~ Some(RangedTermulaSeq(args, argsRange))) - case t @ Termula(RangedLabel(And, r), args, _) => - val argsRange = (t.label.range._2 + 1, t.range._2) - Seq(AndToken(r, true) ~ Some(RangedTermulaSeq(args, argsRange))) - case t @ Termula(RangedLabel(Or, r), args, _) => - val argsRange = (t.label.range._2 + 1, t.range._2) - Seq(OrToken(r, true) ~ Some(RangedTermulaSeq(args, argsRange))) - - case _ => throw UnreachableException - } - ) - - val infixFunctionLabels: List[PrecedenceLevel[RangedLabel]] = infixFunctions.map({ case (l, assoc) => - elem(InfixKind(l)).map[RangedLabel]( - { - case InfixToken(`l`, range) => RangedLabel(ConstantAtomicLabel(synonymToCanonical.getInternalName(l), INFIX_ARITY), range) - case _ => throw UnreachableException - }, - { - case RangedLabel(ConstantAtomicLabel(id, INFIX_ARITY), range) => Seq(InfixToken(synonymToCanonical.getPrintName(id), range)) - case _ => throw UnreachableException - } - ) has assoc - }) - - val infixPredicateLabels: List[PrecedenceLevel[RangedLabel]] = infixPredicates.map(l => - elem(InfixKind(l)).map[RangedLabel]( - { - case InfixToken(`l`, range) => RangedLabel(ConstantAtomicLabel(synonymToCanonical.getInternalName(l), INFIX_ARITY), range) - case _ => throw UnreachableException - }, - { - case RangedLabel(ConstantAtomicLabel(id, INFIX_ARITY), range) => Seq(InfixToken(synonymToCanonical.getPrintName(id), range)) - case _ => throw UnreachableException - } - ) has Associativity.None - ) - - val negated: Syntax[Termula] = recursive { - (negation ~ subtermula).map( - { case n ~ f => - Termula(n, Seq(f), (n.range._1, f.range._2)) - }, - { - case Termula(l @ RangedLabel(Neg, _), Seq(f), _) => Seq(l ~ f) - case _ => throw UnreachableException - } - ) - } - - val and: Syntax[RangedLabel] = elem(AndKind).map[RangedLabel]( - { - - case AndToken(r, _) => RangedLabel(And, r) - case _ => throw UnreachableException - }, - { - case RangedLabel(And, r) => Seq(AndToken(r, false)) - case _ => throw UnreachableException - } - ) - - val or: Syntax[RangedLabel] = elem(OrKind).map[RangedLabel]( - { - case OrToken(r, _) => RangedLabel(Or, r) - case _ => throw UnreachableException - }, - { - case RangedLabel(Or, r) => Seq(OrToken(r, false)) - case _ => throw UnreachableException - } - ) - - // 'and' has higher priority than 'or' - val connectorTermula: Syntax[Termula] = infixOperators[RangedLabel, Termula](subtermula)( - infixFunctionLabels ++ - infixPredicateLabels ++ - ((and has Associativity.Left) :: - (or has Associativity.Left) :: - (toplevelConnector has Associativity.None) :: Nil)* - )( - (l, conn, r) => Termula(conn, Seq(l, r), (l.range._1, r.range._2)), - { - case Termula(pred @ RangedLabel(ConstantAtomicLabel(_, INFIX_ARITY), _), Seq(l, r), _) => (l, pred, r) - case Termula(conn @ RangedLabel(_: ConnectorLabel, _), Seq(l, r), _) => - (l, conn, r) - case Termula(conn @ RangedLabel(_: ConnectorLabel, _), l +: rest, _) if rest.nonEmpty => - val last = rest.last - val leftSide = rest.dropRight(1) - // parser only knows about connector formulas of two arguments, so we unfold the formula of many arguments to - // multiple nested formulas of two arguments - (leftSide.foldLeft(l)((f1, f2) => Termula(conn, Seq(f1, f2), UNKNOWN_RANGE)), conn, last) - } - ) - - val binderLabel: Syntax[RangedLabel] = accept(BinderKind)( - { - case ExistsToken(r) => RangedLabel(Exists, r) - case ExistsOneToken(r) => RangedLabel(ExistsOne, r) - case ForallToken(r) => RangedLabel(Forall, r) - }, - { - case RangedLabel(Exists, r) => Seq(ExistsToken(r)) - case RangedLabel(ExistsOne, r) => Seq(ExistsOneToken(r)) - case RangedLabel(Forall, r) => Seq(ForallToken(r)) - case _ => throw UnreachableException - } - ) - - val boundVariable: Syntax[RangedLabel] = accept(IdKind)( - t => { - val (id, r) = t match { - case ConstantToken(id, r) => (id, r) - case SchematicToken(id, r) => (id, r) - case _ => throw UnreachableException - } - RangedLabel(VariableFormulaLabel(id), r) - }, - { - case RangedLabel(VariableFormulaLabel(id), r) => Seq(SchematicToken(id, r)) - case _ => throw UnreachableException - } - ) - - val binder: Syntax[RangedLabel ~ RangedLabel] = binderLabel ~ boundVariable ~ dot.skip - - lazy val termula: Syntax[Termula] = recursive { - prefixes(binder, connectorTermula)( - { case (label ~ variable, f) => Termula(label, Seq(Termula(variable, Seq(), variable.range), f), (label.range._1, f.range._2)) }, - { case Termula(label @ RangedLabel(_: BinderLabel, _), Seq(Termula(variable @ RangedLabel(_: VariableFormulaLabel, _), Seq(), _), f), _) => - (label ~ variable, f) - } - ) - } - /////////////////////////////////////////////////////////////////// - - val sequent: Syntax[TermulaSequent] = (repsep(termula, semicolon) ~ opt(sequentSymbol.skip ~ repsep(termula, semicolon))).map[TermulaSequent]( - { - case left ~ Some(right) => TermulaSequent(left.toSet, right.toSet) - case right ~ None => TermulaSequent(Set(), right.toSet) - }, - { - case TermulaSequent(Seq(), right) => Seq(right.toSeq ~ None) - case TermulaSequent(left, Seq()) => Seq(left.toSeq ~ Some(Seq(False.toTermula))) - case TermulaSequent(left, right) => Seq(left.toSeq ~ Some(right.toSeq)) - } - ) - - val parser: Parser[TermulaSequent] = Parser(sequent) - val printer: PrettyPrinter[TermulaSequent] = PrettyPrinter(sequent) - - val termulaParser: SequentParser.Parser[Termula] = Parser(termula) - val termulaPrinter: SequentParser.PrettyPrinter[Termula] = PrettyPrinter(termula) - - def parseTermulaSequent(it: Iterator[Token]): ParseResult[TermulaSequent] = parser(it) - def printTermulaSequent(s: TermulaSequent): Option[String] = printer(s).map(SequentLexer.unapply) - - def parseTermula(it: Iterator[Token]): ParseResult[Termula] = termulaParser(it) - def printTermula(t: Termula): Option[String] = termulaPrinter(t).map(SequentLexer.unapply) - } -} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/ParsingUtils.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/ParsingUtils.scala deleted file mode 100644 index 858f6abff..000000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/ParsingUtils.scala +++ /dev/null @@ -1,41 +0,0 @@ -package lisa.utils.parsing - -import lisa.utils.parsing.ParserException -import scallion.* -import scallion.util.Unfolds.unfoldLeft - -trait ParsingUtils extends Operators { self: Parsers => - case class PrecedenceLevel[Op](operator: Syntax[Op], associativity: lisa.utils.parsing.Associativity) - - implicit class PrecedenceLevelDecorator[Op](operator: Syntax[Op]) { - - /** - * Indicates the associativity of the operator. - */ - infix def has(associativity: lisa.utils.parsing.Associativity): PrecedenceLevel[Op] = PrecedenceLevel(operator, associativity) - } - - def singleInfix[Op, A](elem: Syntax[A], op: Syntax[Op])(function: (A, Op, A) => A, inverse: PartialFunction[A, (A, Op, A)] = PartialFunction.empty): Syntax[A] = - (elem ~ opt(op ~ elem)).map( - { - case first ~ None => first - case first ~ Some(op ~ second) => function(first, op, second) - }, - v => - inverse.lift(v) match { - // see the usage of singleInfix in infixOperators: the inverse function is the same for all ops, so it might - // or might not be correct to unwrap the current element with the inverse function. Hence, provide both options. - case Some(l, op, r) => Seq(l ~ Some(op ~ r), v ~ None) - case None => Seq(v ~ None) - } - ) - - def infixOperators[Op, A](elem: Syntax[A])(levels: PrecedenceLevel[Op]*)(function: (A, Op, A) => A, inverse: PartialFunction[A, (A, Op, A)] = PartialFunction.empty): Syntax[A] = - levels.foldLeft(elem) { case (currSyntax, PrecedenceLevel(op, assoc)) => - assoc match { - case Associativity.Left => infixLeft(currSyntax, op)(function, inverse) - case Associativity.Right => infixRight(currSyntax, op)(function, inverse) - case Associativity.None => singleInfix(currSyntax, op)(function, inverse) - } - } -} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala deleted file mode 100644 index 6b33007d7..000000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala +++ /dev/null @@ -1,185 +0,0 @@ -package lisa.utils.parsing - -import lisa.kernel.fol.FOL.* -import lisa.kernel.proof.SCProof -import lisa.kernel.proof.SCProofCheckerJudgement -import lisa.kernel.proof.SCProofCheckerJudgement.* -import lisa.kernel.proof.SequentCalculus.* - -val FOLPrinter = Printer(FOLParser) - -/** - * A set of methods to pretty-print kernel trees. - */ -class Printer(parser: Parser) { - - private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " - - private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" - - /** - * Returns a string representation of this formula. See also [[prettyTerm]]. - * Example output: - * - * ∀x, y. (∀z. (z ∈ x) ⇔ (z ∈ y)) ⇔ (x = y) - *- * - * @param formula the formula - * @param compact whether spaces should be omitted between tokens - * @return the string representation of this formula - */ - def prettyFormula(formula: Formula, compact: Boolean = false): String = parser.printFormula(formula) - - /** - * Returns a string representation of this term. See also [[prettyFormula]]. - * Example output: - *- * f({w, (x, y)}, z) - *- * - * @param term the term - * @param compact whether spaces should be omitted between tokens - * @return the string representation of this term - */ - def prettyTerm(term: Term, compact: Boolean = false): String = parser.printTerm(term) - - /** - * Returns a string representation of this sequent. - * Example output: - *- * ⊢ ∀x, y. (∀z. (z ∈ x) ⇔ (z ∈ y)) ⇔ (x = y) - *- * - * @param sequent the sequent - * @param compact whether spaces should be omitted between tokens - * @return the string representation of this sequent - */ - def prettySequent(sequent: Sequent, compact: Boolean = false): String = parser.printSequent(sequent) - - /** - * Returns a string representation of this proof. - * - * @param proof the proof - * @param judgement optionally provide a proof checking judgement that will mark a particular step in the proof - * (`->`) as an error. The proof is considered to be valid by default - * @return a string where each indented line corresponds to a step in the proof - */ - def prettySCProof(judgement: SCProofCheckerJudgement, forceDisplaySubproofs: Boolean = false): String = { - val proof = judgement.proof - def computeMaxNumberingLengths(proof: SCProof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { - val resultWithCurrent = result.updated( - level, - (Seq((proof.steps.size - 1).toString.length, result(level)) ++ (if (proof.imports.nonEmpty) Seq((-proof.imports.size).toString.length) else Seq.empty)).max - ) - proof.steps.collect { case sp: SCSubproof => sp }.foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.sp, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) - } - - val maxNumberingLengths = computeMaxNumberingLengths(proof, 0, IndexedSeq(0)) // The maximum value for each number column - val maxLevel = maxNumberingLengths.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 = judgement match { - case SCValidProof(_, _) => false - case SCInvalidProof(proof, position, _) => 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 = judgement match { - case SCValidProof(_, _) => false - case SCInvalidProof(proof, position, _) => 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) - ) - - step match { - case sp @ SCSubproof(_, _) => - 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 Restate(_, t1) => pretty("Rewrite", t1) - case RestateTrue(_) => pretty("RewriteTrue") - 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 LeftExistsOne(_, t1, _, _) => pretty("Left ∃!", t1) - case LeftOr(_, l, _) => pretty("Left ∨", l*) - case RightExists(_, t1, _, _, _) => pretty("Right ∃", t1) - case RightForall(_, t1, _, _) => pretty("Right ∀", t1) - case RightExistsOne(_, 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. Refl", t1) - case RightRefl(_, _) => pretty("R. Refl") - 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 InstSchema(_, t1, _, _, _) => pretty("Schema Instantiation", t1) - case Sorry(_) => pretty("Sorry") - case SCSubproof(_, _) => throw UnreachableException - } - 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 (!judgement.isValid) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix - full.mkString(" ") - } - .mkString("\n") + (judgement match { - case SCValidProof(_, _) => "" - case SCInvalidProof(proof, path, message) => s"\nProof checker has reported an error at line ${path.mkString(".")}: $message" - }) - } - - def prettySCProof(proof: SCProof): String = prettySCProof(SCValidProof(proof), false) -} diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala deleted file mode 100644 index 2ab844296..000000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala +++ /dev/null @@ -1,34 +0,0 @@ -package lisa.utils.parsing - -import scala.collection.mutable - -case class CanonicalId(internal: String, print: String) - -case class SynonymInfo(private val synonymToCanonical: Map[String, CanonicalId]) { - - /** - * @return the preferred way to output this id, if available, otherwise the id itself. - */ - def getPrintName(id: String): String = synonymToCanonical.get(id).map(_.print).getOrElse(id) - - /** - * @return the synonym of `id` that is used to construct the corresponding `ConstantAtomicLabel` or - * `ConstantFunctionLabel`. If not available, `id` has no known synonyms, so return `id` itself. - */ - def getInternalName(id: String): String = synonymToCanonical.get(id).map(_.internal).getOrElse(id) -} - -object SynonymInfo { - val empty: SynonymInfo = SynonymInfo(Map.empty[String, CanonicalId]) -} - -class SynonymInfoBuilder { - private val mapping: mutable.Map[String, CanonicalId] = mutable.Map() - - def addSynonyms(internal: String, print: String, equivalentLabels: List[String] = Nil): SynonymInfoBuilder = { - (print :: internal :: equivalentLabels).foreach(mapping(_) = CanonicalId(internal, print)) - this - } - - def build: SynonymInfo = SynonymInfo(mapping.toMap) -} diff --git a/lisa-utils/src/main/scala/lisa/prooflib/BasicMain.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/BasicMain.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala similarity index 71% rename from lisa-utils/src/main/scala/lisa/prooflib/BasicStepTactic.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index 899c51da7..6644d84c1 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -1,13 +1,12 @@ package lisa.prooflib -import lisa.fol.FOLHelpers.* import lisa.fol.FOL as F import lisa.prooflib.ProofTacticLib.{_, given} import lisa.prooflib.* import lisa.utils.K import lisa.utils.KernelHelpers.{|- => `K|-`, _} -import lisa.utils.UserLisaException -import lisa.utils.parsing.FOLPrinter -import lisa.utils.unification.UnificationUtils +//import lisa.utils.UserLisaException +import lisa.utils.unification.UnificationUtils.* +import lisa.utils.collection.Extensions.* object BasicStepTactic { @@ -43,7 +42,7 @@ object BasicStepTactic { object RewriteTrue extends ProofTactic with ProofSequentTactic { def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { val botK = bot.underlying - if (!K.isSameSequent(botK, () `K|-` K.AtomicFormula(K.top, Nil))) + if (!K.isSameSequent(botK, () `K|-` K.top)) proof.InvalidProofTactic("The desired conclusion is not a trivial tautology.") else proof.ValidProofTactic(bot, Seq(K.RestateTrue(botK)), Seq()) @@ -110,7 +109,7 @@ object BasicStepTactic { val botK = bot.underlying val phiK = phi.underlying val psiK = psi.underlying - lazy val phiAndPsi = K.ConnectorFormula(K.And, Seq(phiK, psiK)) + lazy val phiAndPsi = phiK /\ psiK if (!K.isSameSet(botK.right, premiseSequent.right)) proof.InvalidProofTactic("Right-hand side of the conclusion is not the same as the right-hand side of the premise.") @@ -130,7 +129,7 @@ object BasicStepTactic { if (!pivot.isEmpty && pivot.tail.isEmpty) pivot.head match { - case F.AppliedConnector(F.And, Seq(phi, psi)) => + case F.App(F.App(F.and, phi), psi) => if (premiseSequent.left.contains(phi)) LeftAnd.withParameters(phi, psi)(premise)(bot) else @@ -158,7 +157,7 @@ object BasicStepTactic { lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) val botK = bot.underlying val disjunctsK = disjuncts.map(_.underlying) - lazy val disjunction = K.ConnectorFormula(K.Or, disjunctsK) + lazy val disjunction = K.multior(disjunctsK) if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") @@ -208,7 +207,7 @@ object BasicStepTactic { val botK = bot.underlying val phiK = phi.underlying val psiK = psi.underlying - lazy val implication = K.ConnectorFormula(K.Implies, Seq(phiK, psiK)) + lazy val implication = (phiK ==> psiK) if (!K.isSameSet(botK.right + phiK, leftSequent.right union rightSequent.right)) proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of right-hand sides of premises.") @@ -253,9 +252,9 @@ object BasicStepTactic { val botK = bot.underlying val phiK = phi.underlying val psiK = psi.underlying - lazy val implication = K.ConnectorFormula(K.Iff, Seq(phiK, psiK)) - lazy val impLeft = K.ConnectorFormula(K.Implies, Seq(phiK, psiK)) - lazy val impRight = K.ConnectorFormula(K.Implies, Seq(psiK, phiK)) + lazy val implication = phiK <=> psiK + lazy val impLeft = phiK ==> psiK + lazy val impRight = psiK ==> phiK if (!K.isSameSet(botK.right, premiseSequent.right)) proof.InvalidProofTactic("Right-hand side of premise is not the same as right-hand side of conclusion.") @@ -280,7 +279,7 @@ object BasicStepTactic { proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") else pivot.head match { - case F.AppliedConnector(F.Implies, Seq(phi, psi)) => LeftIff.withParameters(phi, psi)(premise)(bot) + case F.App(F.App(F.implies, phi), psi) => LeftIff.withParameters(phi, psi)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer a pivot implication from premise.") } } @@ -298,7 +297,7 @@ object BasicStepTactic { lazy val premiseSequent = proof.getSequent(premise).underlying val botK = bot.underlying val phiK = phi.underlying - lazy val negation = K.ConnectorFormula(K.Neg, Seq(phiK)) + lazy val negation = !phiK if (!K.isSameSet(botK.right + phiK, premiseSequent.right)) proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") @@ -333,17 +332,14 @@ object BasicStepTactic { *
* Γ, ∃y.∀x. (x=y) ⇔ φ |- Δ @@ -490,22 +489,14 @@ object BasicStepTactic { **/ object LeftExistsOne extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel + lazy val xK = x.underlying lazy val phiK = phi.underlying lazy val botK = bot.underlying - lazy val y = K.VariableLabel(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id)) - lazy val instantiated = K.BinderFormula( - K.Exists, - y, - K.BinderFormula( - K.Forall, - xK, - K.ConnectorFormula(K.Iff, List(K.AtomicFormula(K.equality, List(K.VariableTerm(xK), K.VariableTerm(y))), phiK)) - ) - ) - lazy val quantified = K.BinderFormula(K.ExistsOne, xK, phiK) + lazy val y = K.Variable(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id), K.Term) + lazy val instantiated = K.exists(y, K.forall(xK, (xK === y) <=> phiK )) + lazy val quantified = K.ExistsOne(xK, phiK) if (!K.isSameSet(botK.right, premiseSequent.right)) proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise.") @@ -529,7 +520,7 @@ object BasicStepTactic { else if (instantiatedPivot.tail.isEmpty) { instantiatedPivot.head match { // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi - case F.BinderFormula(F.Exists, _, F.BinderFormula(F.Forall, x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => LeftExistsOne.withParameters(phi, x)(premise)(bot) + case F.exists(_, F.forall(x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => LeftExistsOne.withParameters(phi, x)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") } } else @@ -544,6 +535,8 @@ object BasicStepTactic { } } + */ + // Right rules /** *
@@ -557,7 +550,7 @@ object BasicStepTactic { lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) lazy val botK = bot.underlying lazy val conjunctsK = conjuncts.map(_.underlying) - lazy val conjunction = K.ConnectorFormula(K.And, conjunctsK) + lazy val conjunction = K.multiand(conjunctsK) if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") @@ -606,7 +599,7 @@ object BasicStepTactic { lazy val phiK = phi.underlying lazy val psiK = psi.underlying lazy val botK = bot.underlying - lazy val phiAndPsi = K.ConnectorFormula(K.Or, Seq(phiK, psiK)) + lazy val phiAndPsi = phiK \/ psiK if (!K.isSameSet(botK.left, premiseSequent.left)) proof.InvalidProofTactic("Left-hand side of the premise is not the same as the left-hand side of the conclusion.") @@ -625,7 +618,7 @@ object BasicStepTactic { if (!pivot.isEmpty && pivot.tail.isEmpty) pivot.head match { - case F.AppliedConnector(F.Or, Seq(phi, psi)) => + case F.App(F.App(F.or, phi), psi) => if (premiseSequent.left.contains(phi)) RightOr.withParameters(phi, psi)(premise)(bot) else @@ -654,7 +647,7 @@ object BasicStepTactic { lazy val phiK = phi.underlying lazy val psiK = psi.underlying lazy val botK = bot.underlying - lazy val implication = K.ConnectorFormula(K.Implies, Seq(phiK, psiK)) + lazy val implication = phiK ==> psiK if (!K.isSameSet(botK.left + phiK, premiseSequent.left)) proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") @@ -693,25 +686,25 @@ object BasicStepTactic { lazy val phiK = phi.underlying lazy val psiK = psi.underlying lazy val botK = bot.underlying - lazy val implication = K.ConnectorFormula(K.Iff, Seq(phiK, psiK)) - lazy val impLeft = K.ConnectorFormula(K.Implies, Seq(phiK, psiK)) - lazy val impRight = K.ConnectorFormula(K.Implies, Seq(psiK, phiK)) + lazy val implication = phiK <=> psiK + lazy val impLeft = phiK ==> psiK + lazy val impRight = psiK ==> phiK if (!K.isSameSet(botK.left, leftSequent.left union rightSequent.left)) proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") else if (!K.isSubset(leftSequent.right, botK.right + impLeft)) proof.InvalidProofTactic( - "Conclusion is missing the following formulas from the left premise: " + (leftSequent.right -- botK.right).map(f => s"[${FOLPrinter.prettyFormula(f)}]").reduce(_ ++ ", " ++ _) + "Conclusion is missing the following formulas from the left premise: " + (leftSequent.right -- botK.right).map(f => s"[${f.repr}]").reduce(_ ++ ", " ++ _) ) else if (!K.isSubset(rightSequent.right, botK.right + impRight)) proof.InvalidProofTactic( - "Conclusion is missing the following formulas from the right premise: " + (rightSequent.right -- botK.right).map(f => s"[${FOLPrinter.prettyFormula(f)}]").reduce(_ ++ ", " ++ _) + "Conclusion is missing the following formulas from the right premise: " + (rightSequent.right -- botK.right).map(f => s"[${f.repr}]").reduce(_ ++ ", " ++ _) ) else if (!K.isSubset(botK.right, leftSequent.right union rightSequent.right + implication)) proof.InvalidProofTactic( "Conclusion has extraneous formulas apart from premises and implication: " ++ (botK.right .removedAll(leftSequent.right union rightSequent.right + implication)) - .map(f => s"[${FOLPrinter.prettyFormula(f)}]") + .map(f => s"[${f.repr}]") .reduce(_ ++ ", " ++ _) ) else @@ -729,7 +722,7 @@ object BasicStepTactic { proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") else if (pivot.tail.isEmpty) pivot.head match { - case F.AppliedConnector(F.Implies, Seq(phi, psi)) => RightIff.withParameters(phi, psi)(prem1, prem2)(bot) + case F.App(F.App(F.implies, phi), psi) => RightIff.withParameters(phi, psi)(prem1, prem2)(bot) case _ => proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") } else @@ -749,7 +742,7 @@ object BasicStepTactic { lazy val premiseSequent = proof.getSequent(premise).underlying lazy val phiK = phi.underlying lazy val botK = bot.underlying - lazy val negation = K.ConnectorFormula(K.Neg, Seq(phiK)) + lazy val negation = !phiK if (!K.isSameSet(botK.left + phiK, premiseSequent.left)) proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") @@ -784,12 +777,12 @@ object BasicStepTactic { **/ object RightForall extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel + lazy val xK = x.underlying lazy val phiK = phi.underlying lazy val botK = bot.underlying - lazy val quantified = K.BinderFormula(K.Forall, xK, phiK) + lazy val quantified = K.forall(xK, phiK) if ((botK.left union botK.right).exists(_.freeVariables.contains(xK))) proof.InvalidProofTactic("The variable x is free in the resulting sequent.") @@ -816,19 +809,19 @@ object BasicStepTactic { val in: F.Formula = instantiatedPivot.head val quantifiedPhi: Option[F.Formula] = bot.right.find(f => f match { - case F.BinderFormula(F.Forall, _, g) => F.isSame(g, in) + case F.forall(_, g) => F.isSame(g, in) case _ => false } ) quantifiedPhi match { - case Some(F.BinderFormula(F.Forall, x, phi)) => RightForall.withParameters(phi, x)(premise)(bot) + case Some(F.forall(x, phi)) => RightForall.withParameters(phi, x)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") } } else proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∃x. φ.") else if (pivot.tail.isEmpty) pivot.head match { - case F.BinderFormula(F.Forall, x, phi) => RightForall.withParameters(phi, x)(premise)(bot) + case F.forall(x, phi) => RightForall.withParameters(phi, x)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") } else @@ -846,17 +839,14 @@ object BasicStepTactic { * */ object RightExists extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable, t: F.Term | K.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T], t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel - lazy val tK = t match { - case t: F.Term => t.underlying - case t: K.Term => t - } + lazy val xK = x.underlying + lazy val tK = t.underlying lazy val phiK = phi.underlying lazy val botK = bot.underlying - lazy val quantified = K.BinderFormula(K.Exists, xK, phiK) - lazy val instantiated = K.substituteVariablesInFormula(phiK, Map(xK -> tK), Seq()) + lazy val quantified = K.exists(xK, phiK) + lazy val instantiated = K.substituteVariables(phiK, Map(xK -> tK)) if (!K.isSameSet(botK.left, premiseSequent.left)) proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise") @@ -874,7 +864,7 @@ object BasicStepTactic { if (!pivot.isEmpty) if (pivot.tail.isEmpty) pivot.head match { - case F.BinderFormula(F.Exists, x, phi) => RightExists.withParameters(phi, x, t)(premise)(bot) + case F.exists(x, phi) => RightExists.withParameters(phi, x, t)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") } else @@ -890,13 +880,13 @@ object BasicStepTactic { val in: F.Formula = instantiatedPivot.head val quantifiedPhi: Option[F.Formula] = bot.right.find(f => f match { - case g @ F.BinderFormula(F.Exists, _, _) => F.isSame(F.instantiateBinder(g, t), in) + case g @ F.exists(v, e) => F.isSame(e.substitute(v := t), in) case _ => false } ) quantifiedPhi match { - case Some(F.BinderFormula(F.Exists, x, phi)) => RightExists.withParameters(phi, x, t)(premise)(bot) + case Some(F.exists(x, phi)) => RightExists.withParameters(phi, x, t)(premise)(bot) case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") } } else proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") @@ -918,23 +908,25 @@ object BasicStepTactic { val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = pivot.find(f => + val quantifiedPhi: Option[(F.Formula, Substitution)] = pivot.collectFirstDefined(f => f match { - case g @ F.BinderFormula(F.Exists, x, phi) => - UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined - case _ => false + case g @ F.exists(x, phi) => + val ctx = RewriteContext.withBound(phi.freeVars - x) + matchExpr(using ctx)(phi, in).map(f -> _) + case _ => None } ) quantifiedPhi match { - case Some(F.BinderFormula(F.Exists, x, phi)) => - RightExists.withParameters(phi, x, UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).get._2.getOrElse(x, x))(premise)(bot) + case Some((F.exists(x, phi), subst)) => + RightExists.withParameters(phi, x, subst(x).getOrElse(x))(premise)(bot) case _ => proof.InvalidProofTactic("Could not match discovered quantified pivot with premise.") } } else proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") } } + /* /** *
* Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ @@ -945,17 +937,17 @@ object BasicStepTactic { object RightExistsOne extends ProofTactic with ProofFactSequentTactic { def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel + lazy val xK = x.underlying lazy val phiK = phi.underlying lazy val botK = bot.underlying - lazy val y = K.VariableLabel(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id)) + lazy val y = K.Variable(lisa.utils.KernelHelpers.freshId(phiK.freeVariables.map(_.id), x.id)) lazy val instantiated = K.BinderFormula( K.Exists, y, K.BinderFormula( K.Forall, xK, - K.ConnectorFormula(K.Iff, List(K.AtomicFormula(K.equality, List(K.VariableTerm(xK), K.VariableTerm(y))), phiK)) + K.ConnectorFormula(K.Iff, Seq(K.AtomicFormula(K.equality, Seq(K.VariableTerm(xK), K.VariableTerm(y))), phiK)) ) ) lazy val quantified = K.BinderFormula(K.ExistsOne, xK, phiK) @@ -982,7 +974,7 @@ object BasicStepTactic { else if (instantiatedPivot.tail.isEmpty) { instantiatedPivot.head match { // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi - case F.BinderFormula(F.Exists, _, F.BinderFormula(F.Forall, x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => + case F.exists(_, F.forall(x, F.AppliedConnector(F.Iff, Seq(_, phi)))) => RightExistsOne.withParameters(phi, x)(premise)(bot) case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") } @@ -998,6 +990,88 @@ object BasicStepTactic { } } + */ + + /** + *+ * Γ |- φ[t/x], Δ + * -------------------------- + * Γ|- φ[(εx. φ)/x], Δ + *+ * + * Note that if Δ contains φ[(εx. φ)/x] as well, the parameter inference will + * fail. Use [[RightEpsilon.withParameters]] instead. + */ + object RightEpsilon extends ProofTactic with ProofFactSequentTactic { + def collectEpsilons(in: F.Expr[?]): Set[F.Term] = ??? + + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, x: F.Variable[F.T], t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val xK = x.underlying + lazy val tK = t.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + lazy val epsilonTerm = K.epsilon(xK, phiK) + lazy val instantiated = K.substituteVariables(phiK, Map(xK -> tK)) + lazy val bound = K.substituteVariables(phiK, Map(xK -> epsilonTerm)) + + if (!K.isSameSet(botK.left, premiseSequent.left)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise.") + else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + bound)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + φ[(εx. φ)/x].") + else + proof.ValidProofTactic(bot, Seq(K.RightEpsilon(botK, -1, phiK, xK, tK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + val premiseSequent = proof.getSequent(premise) + val pivotSet = bot.right -- premiseSequent.right + val targetSet = premiseSequent.right -- bot.right + + inline def theFailure = + proof.InvalidProofTactic("Could not infer an epsilon pivot from premise and conclusion.") + + if pivotSet.size == 0 || targetSet.size == 0 then + theFailure + else if pivotSet.size == 1 && targetSet.size == 1 then + val pivot = pivotSet.head + val target = targetSet.head + + // the new binding is one of these + val epsilons = collectEpsilons(target) + + val newBindingOption = epsilons.collectFirstDefined: + case eps @ F.ε(x, phi) => + val asTerm = (eps : F.Term) + val substituted = phi.substitute(x := eps) + if F.isSame(substituted, target) then Some(eps) else None + case _ => None + + newBindingOption match + case Some(F.ε(x, phi)) => + // match pivot with phi to discover t + val pivotMatch = matchExpr(using RewriteContext.withBound(phi.freeVars - x))(phi, pivot) + pivotMatch match + case Some(subst) if subst.contains(x) => + val t = subst(x).get + RightEpsilon.withParameters(phi, x, t)(premise)(bot) + case _ => theFailure + case _ => theFailure + else + theFailure + } + + object Beta extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val red1 = K.sequentToFormula(botK).betaNormalForm + val red2 = K.sequentToFormula(proof.getSequent(premise).underlying).betaNormalForm + if (!K.isSame(red1,red2)) + proof.InvalidProofTactic("The conclusion is not beta-OL-equivalent to the premise.") + else + proof.ValidProofTactic(bot, Seq(K.Beta(botK, -1)), Seq(premise)) + } + } + // Structural rules /** *@@ -1037,8 +1111,8 @@ object BasicStepTactic { proof.InvalidProofTactic("Right-hand side of the premise is not the same as the right-hand side of the conclusion.") else faK match { - case K.AtomicFormula(K.equality, Seq(left, right)) => - if (K.isSameTerm(left, right)) + case K.Application(K.Application(K.equality, left), right) => + if (K.isSame(left, right)) proof.ValidProofTactic(bot, Seq(K.LeftRefl(botK, -1, faK)), Seq(premise)) else proof.InvalidProofTactic("φ is not an instance of reflexivity.") @@ -1072,8 +1146,8 @@ object BasicStepTactic { proof.InvalidProofTactic("Right-hand side of conclusion does not contain φ.") else faK match { - case K.AtomicFormula(K.equality, Seq(left, right)) => - if (K.isSameTerm(left, right)) + case K.Application(K.Application(K.equality, left), right) => + if (K.isSame(left, right)) proof.ValidProofTactic(bot, Seq(K.RightRefl(botK, faK)), Seq()) else proof.InvalidProofTactic("φ is not an instance of reflexivity.") @@ -1088,7 +1162,7 @@ object BasicStepTactic { val pivot: Option[F.Formula] = bot.right.find(f => val Eq = F.equality // (F.equality: (F.|->[F.**[F.Term, 2], F.Formula])) f match { - case F.AppliedPredicate(e, Seq(l, r)) => + case F.App(F.App(e, l), r) => (F.equality) == (e) && l == r // termequality case _ => false } @@ -1106,212 +1180,123 @@ object BasicStepTactic { /** *- * Γ, φ(s1,...,sn) |- Δ - * ---------------------------------------- - * Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ + * Γ, φ(s) |- Δ Σ |- s=t, Π + * -------------------------------- + * Γ, Σ φ(t) |- Δ, Π **/ object LeftSubstEq extends ProofTactic { - + @deprecated("Use withParameters instead", "0.9") def withParametersSimple(using lib: Library, proof: lib.Proof)( - equals: List[(F.Term, F.Term)], - lambdaPhi: F.LambdaExpression[F.Term, F.Formula, ?] + equals: Seq[(F.Term, F.Term)], + lambdaPhi: (Seq[F.Variable[?]], F.Formula) )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(equals.map { case (a, b) => (F.lambda(Seq(), a), F.lambda(Seq(), b)) }, (lambdaPhi.bounds.asInstanceOf[Seq[F.SchematicTermLabel[?]]], lambdaPhi.body))(premise)(bot) + withParameters(equals, lambdaPhi)(premise)(bot) } def withParameters(using lib: Library, proof: lib.Proof)( - equals: List[(F.LambdaExpression[F.Term, F.Term, ?], F.LambdaExpression[F.Term, F.Term, ?])], - lambdaPhi: (Seq[F.SchematicTermLabel[?]], F.Formula) + equals: Seq[(F.Expr[?], F.Expr[?])], + lambdaPhi: (Seq[F.Variable[?]], F.Formula) )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying lazy val botK = bot.underlying - lazy val equalsK = equals.map(p => (p._1.underlyingLTT, p._2.underlyingLTT)) - lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlyingLabel), lambdaPhi._2.underlying) + lazy val equalsK = equals.map(p => (p._1.underlying, p._2.underlying)) + lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlying), lambdaPhi._2.underlying) val (s_es, t_es) = equalsK.unzip val (phi_args, phi_body) = lambdaPhiK - if (phi_args.size != s_es.size) - return proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") - else if (equalsK.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - return proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") - - val phi_s = K.instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) - val phi_t = K.instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) - val sEqT_es = equalsK map { case (s, t) => - assert(s.vars.size == t.vars.size) - val base = K.AtomicFormula(K.equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(K.VariableTerm)))) - (s.vars).foldLeft(base: K.Formula) { case (acc, s_arg) => K.BinderFormula(K.Forall, s_arg, acc) } - } + if (phi_args.size != s_es.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. + proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.sort != arg.sort || t.sort != arg.sort }) + proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") + else { + val phi_s_for_f = K.substituteVariables(phi_body, (phi_args zip s_es).toMap) + val phi_t_for_f = K.substituteVariables(phi_body, (phi_args zip t_es).toMap) + val sEqT_es = equalsK map { + case (s, t) => + val no = (s.freeVariables ++ t.freeVariables).view.map(_.id.no).max+1 + val vars = (no until no+s.sort.depth).map(i => K.Variable(K.Identifier("x", i), K.Term)) + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val base = if (inner1.sort == K.Formula) K.iff(inner1)(inner2) else K.equality(inner1)(inner2) + vars.foldLeft(base : K.Expression) { case (acc, s_arg) => K.forall(s_arg, acc) } + } - if (!K.isSameSet(botK.right, premiseSequent.right)) - proof.InvalidProofTactic("Right-hand side of the premise is not the same as the right-hand side of the conclusion.") - else if ( - !K.isSameSet(botK.left + phi_s, premiseSequent.left ++ sEqT_es + phi_t) && - !K.isSameSet(botK.left + phi_t, premiseSequent.left ++ sEqT_es + phi_s) - ) - proof.InvalidProofTactic("Left-hand side of the conclusion + φ(s_) is not the same as left-hand side of the premise + (s=t)_ + φ(t_) (or with s_ and t_ swapped).") - else - proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) + if (K.isSameSet(botK.right, premiseSequent.right)) then + if ( + K.isSameSet(botK.left + phi_t_for_f, premiseSequent.left ++ sEqT_es + phi_s_for_f) || + K.isSameSet(botK.left + phi_s_for_f, premiseSequent.left ++ sEqT_es + phi_t_for_f) + ) + proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) + else + proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_) (or with s_ and t_ swapped).") + else proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + } } } + /** *- * Γ |- φ(s1,...,sn), Δ - * ---------------------------------------- - * Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ + * Γ |- φ(s), Δ Σ |- s=t, Π + * --------------------------------- + * Γ, Σ |- φ(t), Δ, Π **/ object RightSubstEq extends ProofTactic { + @deprecated("Use withParameters instead", "0.9") def withParametersSimple(using lib: Library, proof: lib.Proof)( - equals: List[(F.Term, F.Term)], - lambdaPhi: F.LambdaExpression[F.Term, F.Formula, ?] + equals: Seq[(F.Term, F.Term)], + lambdaPhi: (Seq[F.Variable[?]], F.Formula) )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(equals.map { case (a, b) => (F.lambda(Seq(), a), F.lambda(Seq(), b)) }, (lambdaPhi.bounds.asInstanceOf[Seq[F.SchematicTermLabel[?]]], lambdaPhi.body))(premise)(bot) + withParameters(equals, lambdaPhi)(premise)(bot) } def withParameters(using lib: Library, proof: lib.Proof)( - equals: List[(F.LambdaExpression[F.Term, F.Term, ?], F.LambdaExpression[F.Term, F.Term, ?])], - lambdaPhi: (Seq[F.SchematicTermLabel[?]], F.Formula) + equals: Seq[(F.Expr[?], F.Expr[?])], + lambdaPhi: (Seq[F.Variable[?]], F.Formula) )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent = proof.getSequent(premise).underlying lazy val botK = bot.underlying - lazy val equalsK = equals.map(p => (p._1.underlyingLTT, p._2.underlyingLTT)) - lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlyingLabel), lambdaPhi._2.underlying) + lazy val equalsK = equals.map(p => (p._1.underlying, p._2.underlying)) + lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlying), lambdaPhi._2.underlying) val (s_es, t_es) = equalsK.unzip val (phi_args, phi_body) = lambdaPhiK - if (phi_args.size != s_es.size) - return proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") - else if (equalsK.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - return proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") - - val phi_s = K.instantiateTermSchemas(phi_body, (phi_args zip s_es).toMap) - val phi_t = K.instantiateTermSchemas(phi_body, (phi_args zip t_es).toMap) - val sEqT_es = equalsK map { case (s, t) => - assert(s.vars.size == t.vars.size) - val base = K.AtomicFormula(K.equality, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(K.VariableTerm)))) - (s.vars).foldLeft(base: K.Formula) { case (acc, s_arg) => K.BinderFormula(K.Forall, s_arg, acc) } - } - - if (!K.isSameSet(botK.left, premiseSequent.left ++ sEqT_es)) - proof.InvalidProofTactic("Left-hand side of the conclusion is not the same as the left-hand side of the premise + (s=t)_.") - else if ( - !K.isSameSet(botK.right + phi_s, premiseSequent.right + phi_t) && - !K.isSameSet(botK.right + phi_t, premiseSequent.right + phi_s) - ) - proof.InvalidProofTactic("Right-hand side of the conclusion + φ(s_) is not the same as right-hand side of the premise + φ(t_) (or with s_ and t_ swapped).") - else - proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) - - } - - } - - /** - *- * Γ, φ(a1,...an) |- Δ - * ---------------------------------------- - * Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ - *- */ - object LeftSubstIff extends ProofTactic { - def withParametersSimple(using lib: Library, proof: lib.Proof)( - equals: List[(F.Formula, F.Formula)], - lambdaPhi: F.LambdaExpression[F.Formula, F.Formula, ?] - )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(equals.map { case (a, b) => (F.lambda(Seq(), a), F.lambda(Seq(), b)) }, (lambdaPhi.bounds.asInstanceOf[Seq[F.SchematicAtomicLabel[?]]], lambdaPhi.body))(premise)(bot) - } - - def withParameters(using lib: Library, proof: lib.Proof)( - equals: List[(F.LambdaExpression[F.Term, F.Formula, ?], F.LambdaExpression[F.Term, F.Formula, ?])], - lambdaPhi: (Seq[F.SchematicAtomicLabel[?]], F.Formula) - )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val botK = bot.underlying - lazy val equalsK = equals.map(p => (p._1.underlyingLTF, p._2.underlyingLTF)) - lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlyingLabel), lambdaPhi._2.underlying) - - val (psi_s, tau_s) = equalsK.unzip - val (phi_args, phi_body) = lambdaPhiK - if (phi_args.size != psi_s.size) - return proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") - else if (equalsK.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - return proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") - - val phi_psi = K.instantiatePredicateSchemas(phi_body, (phi_args zip psi_s).toMap) - val phi_tau = K.instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equalsK map { case (s, t) => - assert(s.vars.size == t.vars.size) - val base = K.ConnectorFormula(K.Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(K.VariableTerm)))) - (s.vars).foldLeft(base: K.Formula) { case (acc, s_arg) => K.BinderFormula(K.Forall, s_arg, acc) } - } - - if (!K.isSameSet(botK.right, premiseSequent.right)) - proof.InvalidProofTactic("Right-hand side of the premise is not the same as the right-hand side of the conclusion.") - else if ( - !K.isSameSet(botK.left + phi_psi, premiseSequent.left ++ psiIffTau + phi_tau) && - !K.isSameSet(botK.left + phi_tau, premiseSequent.left ++ psiIffTau + phi_psi) - ) - proof.InvalidProofTactic("Left-hand side of the conclusion + φ(ψ_) is not the same as left-hand side of the premise + (ψ ⇔ τ)_ + φ(τ_) (or with ψ_ and τ_ swapped).") - else - proof.ValidProofTactic(bot, Seq(K.LeftSubstIff(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) - } - } - - /** - *- * Γ |- φ(a1,...an), Δ - * ---------------------------------------- - * Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ - *- */ - object RightSubstIff extends ProofTactic { - def withParametersSimple(using lib: Library, proof: lib.Proof)( - equals: List[(F.Formula, F.Formula)], - lambdaPhi: F.LambdaExpression[F.Formula, F.Formula, ?] - )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(equals.map { case (a, b) => (F.lambda(Seq(), a), F.lambda(Seq(), b)) }, (lambdaPhi.bounds.asInstanceOf[Seq[F.SchematicAtomicLabel[?]]], lambdaPhi.body))(premise)(bot) - } - - def withParameters(using lib: Library, proof: lib.Proof)( - equals: List[(F.LambdaExpression[F.Term, F.Formula, ?], F.LambdaExpression[F.Term, F.Formula, ?])], - lambdaPhi: (Seq[F.SchematicAtomicLabel[?]], F.Formula) - )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val botK = bot.underlying - lazy val equalsK = equals.map(p => (p._1.underlyingLTF, p._2.underlyingLTF)) - lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlyingLabel), lambdaPhi._2.underlying) + if (phi_args.size != s_es.size) // Not strictly necessary, but it's a good sanity check. To reactivate when tactics have been modified. + proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") + else if (equals.zip(phi_args).exists { case ((s, t), arg) => s.sort != arg.sort || t.sort != arg.sort }) + proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") + else { + val phi_s_for_f = K.substituteVariables(phi_body, (phi_args zip s_es).toMap) + val phi_t_for_f = K.substituteVariables(phi_body, (phi_args zip t_es).toMap) + val sEqT_es = equalsK map { + case (s, t) => + val no = (s.freeVariables ++ t.freeVariables).view.map(_.id.no).max+1 + val vars = (no until no+s.sort.depth).map(i => K.Variable(K.Identifier("x", i), K.Term)) + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val base = if (inner1.sort == K.Formula) K.iff(inner1)(inner2) else K.equality(inner1)(inner2) + vars.foldLeft(base : K.Expression) { case (acc, s_arg) => K.forall(s_arg, acc) } + } - val (psi_s, tau_s) = equalsK.unzip - val (phi_args, phi_body) = lambdaPhiK - if (phi_args.size != psi_s.size) - return proof.InvalidProofTactic("The number of arguments of φ must be the same as the number of equalities.") - else if (equalsK.zip(phi_args).exists { case ((s, t), arg) => s.vars.size != arg.arity || t.vars.size != arg.arity }) - return proof.InvalidProofTactic("The arities of symbols in φ must be the same as the arities of equalities.") - - val phi_psi = K.instantiatePredicateSchemas(phi_body, (phi_args zip psi_s).toMap) - val phi_tau = K.instantiatePredicateSchemas(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equalsK map { case (s, t) => - assert(s.vars.size == t.vars.size) - val base = K.ConnectorFormula(K.Iff, Seq(s.body, if (s.vars == t.vars) t.body else t(s.vars.map(K.VariableTerm)))) - (s.vars).foldLeft(base: K.Formula) { case (acc, s_arg) => K.BinderFormula(K.Forall, s_arg, acc) } + if (K.isSameSet(botK.left, premiseSequent.left ++ sEqT_es)) + if ( + K.isSameSet(botK.right + phi_t_for_f, premiseSequent.right + phi_s_for_f) || + K.isSameSet(botK.right + phi_s_for_f, premiseSequent.right + phi_t_for_f) + ) + proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) + else + proof.InvalidProofTactic("Right-hand side of the premise and the conclusion should be the same with each containing one of φ(s_) φ(t_), but it isn't the case.") + else proof.InvalidProofTactic("Left-hand sides of the premise + (s=t)_ must be the same as left-hand side of the premise.") } - - if (!K.isSameSet(botK.left, premiseSequent.left ++ psiIffTau)) { - proof.InvalidProofTactic("Left-hand side of the conclusion is not the same as the left-hand side of the premise + (ψ ⇔ τ)_.") - } else if ( - !K.isSameSet(botK.right + phi_psi, premiseSequent.right + phi_tau) && - !K.isSameSet(botK.right + phi_tau, premiseSequent.right + phi_psi) - ) - proof.InvalidProofTactic("Right-hand side of the conclusion + φ(ψ_) is not the same as right-hand side of the premise + φ(τ_) (or with ψ_ and τ_ swapped).") - else - proof.ValidProofTactic(bot, Seq(K.RightSubstIff(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) } } + + @deprecated("Use LeftSubstEq instead", "0.9") + val LeftSubstIff = LeftSubstEq + @deprecated("Use RightSubstEq instead", "0.9") + val RightSubstIff = RightSubstEq /** *@@ -1320,87 +1305,20 @@ object BasicStepTactic { * Γ[r(a)/?f] |- Δ[r(a)/?f] **/ - object InstFunSchema extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)( - insts: Map[F.SchematicFunctionLabel[?] | F.Variable, F.LambdaExpression[F.Term, F.Term, ?]] - )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val botK = bot.underlying - val instsK = insts.map((sl, le) => - sl match { - case v: F.Variable => (v.underlyingLabel, F.underlyingLTT(le)) - case sfl: F.SchematicFunctionLabel[?] => (sfl.underlyingLabel, F.underlyingLTT(le)) - } - ) - - if (!K.isSameSet(botK.left, premiseSequent.left.map(K.instantiateTermSchemas(_, instsK)))) - proof.InvalidProofTactic("Left-hand side of premise instantiated with the map 'insts' is not the same as left-hand side of conclusion.") - else if (!K.isSameSet(botK.right, premiseSequent.right.map(K.instantiateTermSchemas(_, instsK)))) - proof.InvalidProofTactic("Right-hand side of premise instantiated with the map 'insts' is not the same as right-hand side of conclusion.") - else - proof.ValidProofTactic(bot, Seq(K.InstSchema(botK, -1, Map.empty, Map.empty, instsK)), Seq(premise)) - } - } + object InstSchema extends ProofTactic { + def unsafe(using lib: Library, proof: lib.Proof)(map: Map[F.Variable[?], F.Expr[?]])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + val premiseSequent = proof.getSequent(premise).underlying + val mapK = map.map((v, e) => (v.underlying, e.underlying)) + val botK = K.substituteVariablesInSequent(premiseSequent, mapK) + val res = proof.getSequent(premise).substituteUnsafe(map) + proof.ValidProofTactic(res, Seq(K.InstSchema(botK, -1, mapK)), Seq(premise)) - /** - *- * Γ |- Δ - * -------------------------- - * Γ[ψ(a)/?p] |- Δ[ψ(a)/?p] - *- */ - object InstPredSchema extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)( - insts: Map[F.SchematicPredicateLabel[?] | F.VariableFormula, F.LambdaExpression[F.Term, F.Formula, ?]] - )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val botK = bot.underlying - val instsK = insts.map((sl, le) => - sl match { - case v: F.VariableFormula => (v.underlyingLabel, F.underlyingLTF(le)) - case sfl: F.SchematicPredicateLabel[?] => (sfl.underlyingLabel, F.underlyingLTF(le)) - } - ) + def apply(using lib: Library, proof: lib.Proof)(subst: F.SubstPair*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + val map = subst.map(p => (p._1, p._2)).toMap + unsafe(using lib, proof)(map)(premise)(bot) - if (!K.isSameSet(botK.left, premiseSequent.left.map(K.instantiatePredicateSchemas(_, instsK)))) - proof.InvalidProofTactic("Left-hand side of premise instantiated with the map 'insts' is not the same as left-hand side of conclusion.") - else if (!K.isSameSet(botK.right, premiseSequent.right.map(K.instantiatePredicateSchemas(_, instsK)))) - proof.InvalidProofTactic("Right-hand side of premise instantiated with the map 'insts' is not the same as right-hand side of conclusion.") - else - proof.ValidProofTactic(bot, Seq(K.InstSchema(botK, -1, Map.empty, instsK, Map.empty)), Seq(premise)) - } - } + - object InstSchema extends ProofTactic { - def apply(using - lib: Library, - proof: lib.Proof - )( - mCon: Map[F.SchematicConnectorLabel[?], F.LambdaExpression[F.Formula, F.Formula, ?]], - mPred: Map[F.SchematicPredicateLabel[?] | F.VariableFormula, F.LambdaExpression[F.Term, F.Formula, ?]], - mTerm: Map[F.SchematicFunctionLabel[?] | F.Variable, F.LambdaExpression[F.Term, F.Term, ?]] - )( - premise: proof.Fact - ): proof.ProofTacticJudgement = { - val premiseSequent = proof.getSequent(premise).underlying - val mConK = mCon.map((sl, le) => (sl.underlyingLabel, F.underlyingLFF(le))) - val mPredK = mPred.map((sl, le) => - sl match { - case v: F.VariableFormula => (v.underlyingLabel, F.underlyingLTF(le)) - case spl: F.SchematicPredicateLabel[?] => (spl.underlyingLabel, F.underlyingLTF(le)) - } - ) - val mTermK = mTerm.map((sl, le) => - sl match { - case v: F.Variable => (v.underlyingLabel, F.underlyingLTT(le)) - case sfl: F.SchematicFunctionLabel[?] => (sfl.underlyingLabel, F.underlyingLTT(le)) - } - ) - val botK = instantiateSchemaInSequent(premiseSequent, mConK, mPredK, mTermK) - val smap = Map[F.SchematicLabel[?], F.LisaObject[?]]() ++ mCon ++ mPred ++ mTerm - val res = proof.getSequent(premise).substituteUnsafe(smap) - proof.ValidProofTactic(res, Seq(K.InstSchema(botK, -1, mConK, mPredK, mTermK)), Seq(premise)) - } } object Subproof extends ProofTactic { def apply(using proof: Library#Proof)(statement: Option[F.Sequent])(iProof: proof.InnerProof) = { @@ -1414,7 +1332,7 @@ object BasicStepTactic { proof.ValidProofTactic(iProof.mostRecentStep.bot, scproof.steps, premises) else if (!K.isSameSequent(botK.get, scproof.conclusion)) proof.InvalidProofTactic( - s"The subproof does not prove the desired conclusion.\n\tExpected: ${FOLPrinter.prettySequent(botK.get)}\n\tObtained: ${FOLPrinter.prettySequent(scproof.conclusion)}" + s"The subproof does not prove the desired conclusion.\n\tExpected: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}" ) else proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) @@ -1422,7 +1340,6 @@ object BasicStepTactic { judgement } } - class SUBPROOF(using val proof: Library#Proof)(statement: Option[F.Sequent])(val iProof: proof.InnerProof) extends ProofTactic { val bot: Option[F.Sequent] = statement val botK: Option[K.Sequent] = statement map (_.underlying) @@ -1435,7 +1352,7 @@ object BasicStepTactic { if (botK.isEmpty) proof.ValidProofTactic(iProof.mostRecentStep.bot, scproof.steps, premises) else if (!K.isSameSequent(botK.get, scproof.conclusion)) - proof.InvalidProofTactic(s"The subproof does not prove the desired conclusion.\n\tExpected: ${FOLPrinter.prettySequent(botK.get)}\n\tObtained: ${FOLPrinter.prettySequent(scproof.conclusion)}") + proof.InvalidProofTactic(s"The subproof does not prove the desired conclusion.\n\tExpected: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}") else proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) } @@ -1454,4 +1371,5 @@ object BasicStepTactic { } } + } diff --git a/lisa-utils/src/main/scala/lisa/prooflib/Exports.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/Exports.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/Library.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala similarity index 54% rename from lisa-utils/src/main/scala/lisa/prooflib/Library.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala index 9d64e2784..0a677f30f 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/Library.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala @@ -4,7 +4,7 @@ import lisa.kernel.proof.RunningTheory import lisa.kernel.proof.SCProofChecker import lisa.kernel.proof.SCProofCheckerJudgement import lisa.kernel.proof.SequentCalculus -import lisa.prooflib.ProofTacticLib.ProofTactic +//import lisa.prooflib.ProofTacticLib.ProofTactic import lisa.utils.KernelHelpers.{_, given} import lisa.utils.{_, given} @@ -42,23 +42,18 @@ abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.Pro else _draft = Some(file) def isDraft = _draft.nonEmpty - val knownDefs: scala.collection.mutable.Map[F.ConstantLabel[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty - val shortDefs: scala.collection.mutable.Map[F.ConstantLabel[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty + val knownDefs: scala.collection.mutable.Map[F.Constant[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty + val shortDefs: scala.collection.mutable.Map[F.Constant[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty - def addSymbol(s: F.ConstantFunctionLabel[?] | F.ConstantPredicateLabel[?] | F.Constant): Unit = { - s match { - case s: F.ConstantFunctionLabel[?] => theory.addSymbol(s.underlyingLabel) - case s: F.ConstantPredicateLabel[?] => theory.addSymbol(s.underlyingLabel) - case s: F.Constant => theory.addSymbol(s.underlyingLabel) - } + def addSymbol(s: F.Constant[?]): Unit = + theory.addSymbol(s.underlying) knownDefs.update(s, None) - } - def getDefinition(label: F.ConstantLabel[?]): Option[JUSTIFICATION] = knownDefs.get(label) match { + def getDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = knownDefs.get(label) match { case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) case Some(value) => value } - def getShortDefinition(label: F.ConstantLabel[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { + def getShortDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) case Some(value) => value } @@ -71,38 +66,12 @@ abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.Pro // DEFINITION Syntax - /** - * Allows to create a definition by shortcut of a function symbol: - */ - def makeSimpleFunctionDefinition(symbol: String, expression: K.LambdaTermTerm): K.Judgement[theory.FunctionDefinition] = { - import K.* - val LambdaTermTerm(vars, body) = expression - - val out: VariableLabel = VariableLabel(freshId((vars.map(_.id) ++ body.schematicTermLabels.map(_.id)).toSet, "y")) - val proof: SCProof = simpleFunctionDefinition(expression, out) - theory.functionDefinition(symbol, LambdaTermFormula(vars, out === body), out, proof, out === body, Nil) - } - /** * Allows to create a definition by shortcut of a predicate symbol: */ - def makeSimplePredicateDefinition(symbol: String, expression: K.LambdaTermFormula): K.Judgement[theory.PredicateDefinition] = - theory.predicateDefinition(symbol, expression) - - private def simpleFunctionDefinition(expression: K.LambdaTermTerm, out: K.VariableLabel): K.SCProof = { - import K.{*, given} - val x = out - val LambdaTermTerm(vars, body) = expression - val xeb = x === body - val y = VariableLabel(freshId(body.freeVariables.map(_.id) ++ vars.map(_.id) + out.id, "y")) - val s0 = SC.RightRefl(() |- body === body, body === body) - val s1 = SC.Restate(() |- (xeb) <=> (xeb), 0) - val s2 = SC.RightForall(() |- forall(x, (xeb) <=> (xeb)), 1, (xeb) <=> (xeb), x) - val s3 = SC.RightExists(() |- exists(y, forall(x, (x === y) <=> (xeb))), 2, forall(x, (x === y) <=> (xeb)), y, body) - val s4 = SC.Restate(() |- existsOne(x, xeb), 3) - val v = Vector(s0, s1, s2, s3, s4) - K.SCProof(v) - } + def makeSimpleDefinition(symbol: String, expression: K.Expression): K.Judgement[theory.Definition] = + theory.definition(symbol, expression) + /** * Prints a short representation of the given theorem or definition diff --git a/lisa-utils/src/main/scala/lisa/prooflib/OutputManager.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala similarity index 94% rename from lisa-utils/src/main/scala/lisa/prooflib/OutputManager.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala index ce3f84f8b..bc7442ef2 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/OutputManager.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala @@ -25,7 +25,7 @@ abstract class OutputManager { case e: LisaException.InvalidKernelJustificationComputation => e.proof match { - case Some(value) => output(lisa.utils.ProofPrinter.prettyProof(value)) + case Some(value) => output(lisa.prooflib.ProofPrinter.prettyProof(value)) case None => () } output(e.underlying.repr) diff --git a/lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala similarity index 97% rename from lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala index ea2fe2ffd..09cc6d8d8 100644 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala @@ -1,4 +1,4 @@ -package lisa.utils.parsing +package lisa.prooflib import lisa.kernel.proof.SCProofCheckerJudgement import lisa.prooflib.BasicStepTactic.SUBPROOF @@ -6,7 +6,6 @@ import lisa.prooflib.Library import lisa.prooflib.* import lisa.utils.* -//temporary - get merged wit regular printer in time object ProofPrinter { private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " @@ -126,6 +125,4 @@ object ProofPrinter { def prettySimpleProof(proof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): String = prettyProofLines(proof, error).mkString("\n") def prettySimpleProof(proof: Library#Proof, indent: Int, error: Option[(IndexedSeq[Int], String)]): String = prettyProofLines(proof, None).mkString("\n" + " " * indent) - // def prettyProof(judgement: InvalidProofTactic): String = prettyProof(judgement.tactic.proof) - } diff --git a/lisa-utils/src/main/scala/lisa/prooflib/ProofTacticLib.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala similarity index 94% rename from lisa-utils/src/main/scala/lisa/prooflib/ProofTacticLib.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala index fb108de47..c398c880d 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/ProofTacticLib.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala @@ -3,12 +3,13 @@ package lisa.prooflib import lisa.fol.FOL as F import lisa.prooflib.* import lisa.utils.K -import lisa.utils.Printer import lisa.utils.UserLisaException +import lisa.prooflib.ProofPrinter object ProofTacticLib { type Arity = Int & Singleton + /** * A ProofTactic is an object that relies on a step of premises and which can be translated into pure Sequent Calculus. */ @@ -46,7 +47,7 @@ object ProofTacticLib { val textline = source.getLines().drop(line.value - 1).next().dropWhile(c => c.isWhitespace) source.close() Console.RED + proof.owningTheorem.prettyGoal + Console.RESET + "\n" + - lisa.utils.ProofPrinter.prettyProof(proof, 2) + "\n" + + ProofPrinter.prettyProof(proof, 2) + "\n" + " " * (1 + proof.depth) + Console.RED + textline + Console.RESET + "\n\n" + s" Proof tactic ${tactic.name} used in.(${file.value.split("/").last.split("\\\\").last}:${line.value}) did not succeed:\n" + " " + errorMessage @@ -60,7 +61,7 @@ object ProofTacticLib { extends lisa.utils.LisaException(errorMessage) { def showError: String = "A proof tactic used in another proof tactic returned an unexpected error. This may indicate an implementation error in either of the two tactics.\n" + "Status of the proof at time of the error is:" + - lisa.utils.ProofPrinter.prettyProof(failure.proof) + ProofPrinter.prettyProof(failure.proof) } } diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala new file mode 100644 index 000000000..f50a6e7d4 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -0,0 +1,356 @@ +package lisa.prooflib + +import lisa.kernel.proof.SCProofChecker.checkSCProof +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.* +import lisa.prooflib.SimpleDeducedSteps.* +import lisa.prooflib.* +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.K.Identifier +import lisa.utils.LisaException +import lisa.utils.UserLisaException +import lisa.utils.{_, given} + +import scala.annotation.targetName + +trait ProofsHelpers { + library: Library & WithTheorems => + + import lisa.fol.FOL.{given, *} + + class HaveSequent(val bot: Sequent) { + + inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = By(proof, line, file).asInstanceOf + + class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + + val bot = HaveSequent.this.bot ++ (F.iterable_to_set(_proof.getAssumptions) |- ()) + inline infix def apply(tactic: Sequent => _proof.ProofTacticJudgement): _proof.ProofStep & _proof.Fact = { + tactic(bot).validate(line, file) + } + inline infix def apply(tactic: ProofSequentTactic): _proof.ProofStep = { + tactic(using library, _proof)(bot).validate(line, file) + } + } + + infix def subproof(using proof: Library#Proof, line: sourcecode.Line, file: sourcecode.File)(computeProof: proof.InnerProof ?=> Unit): proof.ProofStep = { + val botWithAssumptions = HaveSequent.this.bot ++ (proof.getAssumptions |- ()) + val iProof: proof.InnerProof = new proof.InnerProof(Some(botWithAssumptions)) + computeProof(using iProof) + (new BasicStepTactic.SUBPROOF(using proof)(Some(botWithAssumptions))(iProof)).judgement.validate(line, file).asInstanceOf[proof.ProofStep] + } + + } + + class AndThenSequent private[ProofsHelpers] (val bot: Sequent) { + + inline infix def by(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): By { val _proof: proof.type } = + By(proof, line, file).asInstanceOf[By { val _proof: proof.type }] + + class By(val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + private val bot = AndThenSequent.this.bot ++ (_proof.getAssumptions |- ()) + inline infix def apply(tactic: _proof.Fact => Sequent => _proof.ProofTacticJudgement): _proof.ProofStep = { + tactic(_proof.mostRecentStep)(bot).validate(line, file) + } + + inline infix def apply(tactic: ProofFactSequentTactic): _proof.ProofStep = { + tactic(using library, _proof)(_proof.mostRecentStep)(bot).validate(line, file) + } + + } + } + + /** + * Claim the given Sequent as a ProofTactic, which may require a justification by a proof tactic and premises. + */ + def have(using proof: library.Proof)(res: Sequent): HaveSequent = HaveSequent(res) + + def have(using line: sourcecode.Line, file: sourcecode.File)(using proof: library.Proof)(v: proof.Fact | proof.ProofTacticJudgement) = v match { + case judg: proof.ProofTacticJudgement => judg.validate(line, file) + case fact: proof.Fact @unchecked => HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) + } + + /** + * Claim the given Sequent as a ProofTactic directly following the previously proven tactic, + * which may require a justification by a proof tactic. + */ + def thenHave(using proof: library.Proof)(res: Sequent): AndThenSequent = AndThenSequent(res) + + infix def andThen(using proof: library.Proof, line: sourcecode.Line, file: sourcecode.File): AndThen { val _proof: proof.type } = AndThen(proof, line, file).asInstanceOf + + class AndThen private[ProofsHelpers] (val _proof: library.Proof, line: sourcecode.Line, file: sourcecode.File) { + inline infix def apply(tactic: _proof.Fact => _proof.ProofTacticJudgement): _proof.ProofStep = { + tactic(_proof.mostRecentStep).validate(line, file) + } + inline infix def apply(tactic: ProofFactTactic): _proof.ProofStep = { + tactic(using library, _proof)(_proof.mostRecentStep).validate(line, file) + } + } + + + /* + /** + * Assume the given formula in all future left hand-side of claimed sequents. + */ + def assume(using proof: library.Proof)(f: Formula): proof.ProofStep = { + proof.addAssumption(f) + have(() |- f) by BasicStepTactic.Hypothesis + } + */ + /** + * Assume the given formulas in all future left hand-side of claimed sequents. + */ + def assume(using proof: library.Proof)(fs: Formula*): proof.ProofStep = { + fs.foreach(f => proof.addAssumption(f)) + have(() |- fs.toSet) by BasicStepTactic.Hypothesis + } + + def thesis(using proof: library.Proof): Sequent = proof.possibleGoal.get + def goal(using proof: library.Proof): Sequent = proof.possibleGoal.get + + def lastStep(using proof: library.Proof): proof.ProofStep = proof.mostRecentStep + + def sorry(using proof: library.Proof): proof.ProofStep = have(thesis) by Sorry + + def showCurrentProof(using om: OutputManager, _proof: library.Proof)(): Unit = { + om.output("Current proof of " + _proof.owningTheorem.prettyGoal + ": ") + om.output( + ProofPrinter.prettyProof(_proof, 2) + ) + } + + extension (using proof: library.Proof)(fact: proof.Fact) { + infix def of(insts: (F.SubstPair | F.Term)*): proof.InstantiatedFact = { + proof.InstantiatedFact(fact, insts) + } + def statement: F.Sequent = proof.sequentOfFact(fact) + } + + def currentProof(using p: library.Proof): Library#Proof = p + + + //////////////////////////////////////// + // DSL for definitions and theorems // + //////////////////////////////////////// + + class UserInvalidDefinitionException(val symbol: String, errorMessage: String)(using line: sourcecode.Line, file: sourcecode.File) extends UserLisaException(errorMessage) { // TODO refine + val showError: String = { + val source = scala.io.Source.fromFile(file.value) + val textline = source.getLines().drop(line.value - 1).next().dropWhile(c => c.isWhitespace) + source.close() + s" Definition of $symbol at.(${file.value.split("/").last.split("\\\\").last}:${line.value}) is invalid:\n" + + " " + Console.RED + textline + Console.RESET + "\n\n" + + " " + errorMessage + } + } + + + def leadingVarsAndBody(e: Expr[?]): (Seq[Variable[?]], Expr[?]) = + def inner(e: Expr[?]): (Seq[Variable[?]], Expr[?]) = e match + case Abs(v, body) => + val (vars, bodyR) = inner(body) + (v +: vars, bodyR) + case _ => (Seq(), e) + val r = inner(e) + (r._1.reverse, r._2) + + def DEF[S: Sort](using name: sourcecode.FullName)(using om: OutputManager, line: sourcecode.Line, file: sourcecode.File) + (e: Expr[S]): Constant[S] = + val (vars, body) = leadingVarsAndBody(e) + if vars.size == e.sort.depth then + DirectDefinition[S](name.value, line.value, file.value)(e, vars).cst + else + val maxV: Int = vars.maxBy(_.id.no).id.no + val maxB: Int = body.freeVars.maxBy(_.id.no).id.no + var no = List(maxV, maxB).max + val newvars = K.flatTypeParameters(body.sort).map(i =>{no+=1; Variable.unsafe(K.Identifier("x", no), i)}) + val totvars = vars ++ newvars + DirectDefinition[S](name.value, line.value, file.value)(e, totvars)(using F.unsafeSortEvidence(e.sort)).cst + + def EpsilonDEF[S: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File) + (e: Expr[S], j: JUSTIFICATION): Constant[S] = + val (vars, body) = leadingVarsAndBody(e) + if vars.size == e.sort.depth then + body match + case epsilon(x, inner) => + EpsilonDefinition[S](name.value, line.value, file.value)(e, vars, j).cst + case _ => om.lisaThrow(UserInvalidDefinitionException(name.value, "The given expression is not an epsilon term.")) + else om.lisaThrow(UserInvalidDefinitionException(name.value, "The given expression is not an epsilon term.")) + + + + class DirectDefinition[S : Sort](using om: OutputManager)(val fullName: String, line: Int, file: String)(val expr: Expr[S], val vars: Seq[Variable[?]]) extends DEFINITION(line, file) { + + val arity = vars.size + + lazy val cst: Constant[S] = F.Constant(name) + + + val appliedCst: Expr[?] = cst#@@(vars) + + + val innerJustification: theory.Definition = { + import lisa.utils.K.{findUndefinedSymbols} + val uexpr = expr.underlying + val ucst = K.Constant(name, cst.sort) + val uvars = vars.map(_.underlying) + val judgement = theory.makeDefinition(ucst, uexpr, uvars) + judgement match { + case K.ValidJustification(just) => + just + case wrongJudgement: K.InvalidJustification[?] => + if (!theory.belongsToTheory(uexpr)) { + om.lisaThrow( + UserInvalidDefinitionException( + name, + s"All symbols in the definition must belong to the theory. The symbols ${theory.findUndefinedSymbols(uexpr)} are unknown and you need to define them first." + ) + ) + } + if !theory.isAvailable(ucst) then + om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) + if !uexpr.freeVariables.nonEmpty then + om.lisaThrow( + UserInvalidDefinitionException( + name, + s"The definition is not allowed to contain schematic symbols or free variables. " + + s"The variables {${(uexpr.freeVariables).mkString(", ")}} are free in the expression ${uexpr}." + ) + ) + if !theory.isAvailable(ucst) then + om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or an error in LISA.", + wrongJudgement, + None + ) + ) + } + } + val right = expr#@@(vars) + val statement = + if appliedCst.sort == K.Term then + () |- (equality #@ appliedCst #@ right).asInstanceOf[Formula] + else + () |- (iff #@ appliedCst #@ right).asInstanceOf[Formula] + library.last = Some(this) + } + + def dropAllLambdas(s: Expr[?]): Expr[?] = s match { + case Abs(v, body) => dropAllLambdas(body) + case _ => s + } + + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * λx.(λy.(λz. base)) + */ + def abstractVars(v: Seq[Variable[?]], body: Expr[?]): Expr[?] = + def inner(v: Seq[Variable[?]], body: Expr[?]) = v match + case Seq() => body + case x +: xs => Abs.unsafe(x, abstractVars(xs, body)) + inner(v.reverse, body) + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * λx.(λy.(λz. base)) + */ + def applyVars(v: Seq[Variable[?]], body: Expr[?]): Expr[?] = v match + case Seq() => body + case x +: xs => applyVars(xs, body#@(x)) + + /** + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * ((((λx.(λy.(λz. base))) x) y) z) + */ + def betaExpand(base: Expr[?], vars: Seq[Variable[?]]): Expr[?] = + applyVars(vars, abstractVars(vars.reverse, base)) + + + + /** + * Allows to make definitions "by unique existance" of a symbol. May need debugging + */ + class EpsilonDefinition[S: Sort](using om: OutputManager)(fullName: String, line: Int, file: String)( + expr: Expr[S], + vars: Seq[Variable[?]], + val j: JUSTIFICATION + ) extends DirectDefinition(fullName, line, file)(expr, vars) { + + val body: Term = dropAllLambdas(expr).asInstanceOf[Term] + override val appliedCst : Term = (cst#@@(vars)).asInstanceOf[Term] + val (epsilonVar, inner) = body match + case epsilon(x, inner) => (x, inner) + case _ => om.lisaThrow(UserInvalidDefinitionException(name, "The given expression is not an epsilon term.")) + + private val propCst = inner.substitute(epsilonVar := appliedCst) + private val propEpsilon = inner.substitute(epsilonVar := body) + val definingProp = Theorem(propCst) { + val fresh = freshId(vars, "x") + have(this) + + def loop(expr: Expr[?], leading: List[Variable[?]]) : Unit = + expr match { + case App(lam @ Abs(vAbs, body1: Term), _) => + val freshX = Variable.unsafe(fresh, body1.sort) + val right: Term = applyVars(leading.reverse, freshX).asInstanceOf[Term] + var instRight: Term = applyVars(leading.reverse, body1).asInstanceOf[Term] + thenHave(appliedCst === instRight) by Beta + case App(f, a: Variable[?]) => loop(expr, a :: leading) + case _ => throw new Exception("Unreachable") + } + while lastStep.bot.right.head match {case App(epsilon, _) => false; case _ => true} do + loop(lastStep.bot.right.head, List()) + val eqStep = lastStep // appliedCst === body + // j is exists(x, prop(x)) + val existsStep = ??? // have(propEpsilon) by // prop(body) + val s3 = have(appliedCst === body |- propCst) by RightSubstEq.withParameters(List((appliedCst, body)), (List(epsilonVar), inner))(lastStep) + val s4 = have(propCst) by Cut(s3, j) + ??? + } + + override def repr: String = + s" ${if (withSorry) " Sorry" else ""} Definition of symbol ${appliedCst} such that ${definingProp.statement})\n" + + } + + + + ///////////////////////// + // Local Definitions // + ///////////////////////// + + import lisa.utils.K.prettySCProof + import lisa.utils.KernelHelpers.apply + + /** + * A term with a definition, local to a proof. + * + * @param proof + * @param id + */ + abstract class LocalyDefinedVariable[S:Sort](val proof: library.Proof, id: Identifier) extends Variable(id) { + + val definition: proof.Fact + lazy val definingFormula = proof.sequentOfFact(definition).right.head + + // proof.addDefinition(this, defin(this), fact) + // val definition: proof.Fact = proof.getDefinition(this) + } + + /** + * Check correctness of the proof, using LISA's logical kernel, to the current point. + */ + def sanityProofCheck(using p: Proof)(message: String): Unit = { + val csc = p.toSCProof + if checkSCProof(csc).isValid then + println("Proof is valid. " + message) + Thread.sleep(100) + else + checkProof(csc) + throw Exception("Proof is not valid: " + message) + } + +} diff --git a/lisa-utils/src/main/scala/lisa/prooflib/SimpleDeducedSteps.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala similarity index 90% rename from lisa-utils/src/main/scala/lisa/prooflib/SimpleDeducedSteps.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala index d9a2cd984..5cb6bebf7 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/SimpleDeducedSteps.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala @@ -1,37 +1,14 @@ package lisa.prooflib -import lisa.fol.FOLHelpers.* import lisa.fol.FOL as F import lisa.prooflib.BasicStepTactic.* import lisa.prooflib.ProofTacticLib.{_, given} import lisa.prooflib.* -import lisa.utils.FOLParser import lisa.utils.K import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.Printer object SimpleDeducedSteps { - object simpleFunctionDefinition extends ProofTactic { - - def apply[N <: Arity](using lib: Library, proof: lib.Proof)(expression: F.LambdaExpression[F.Term, F.Term, N], out: F.Variable) = { - val scp = { - import K.{*, given} - val x = out.underlyingLabel - val LambdaTermTerm(vars, body) = expression.underlyingLTT - val xeb = x === body - val y = VariableLabel(freshId(body.freeVariables.map(_.id) ++ vars.map(_.id) + out.id, "y")) - val s0 = K.RightRefl(() |- body === body, body === body) - val s1 = K.Restate(() |- (xeb) <=> (xeb), 0) - val s2 = K.RightForall(() |- forall(x, (xeb) <=> (xeb)), 1, (xeb) <=> (xeb), x) - val s3 = K.RightExists(() |- exists(y, forall(x, (x === y) <=> (xeb))), 2, forall(x, (x === y) <=> (xeb)), y, body) - val s4 = K.Restate(() |- existsOne(x, xeb), 3) - Vector(s0, s1, s2, s3, s4) - } - proof.ValidProofTactic(F.Sequent(Set(), Set(F.ExistsOne(out, out === expression.body))), scp, Seq()) - } - - } object Restate extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = @@ -106,14 +83,14 @@ object SimpleDeducedSteps { // by construction the premise is well-formed // verify the formula structure and instantiate f match { - case psi @ K.BinderFormula(K.Forall, x, _) => - val tempVar = K.VariableLabel(K.freshId(psi.freeVariables.map(_.id), x.id)) + case psi @ K.Forall(x, inner) => + val tempVar = K.Variable(K.freshId(psi.freeVariables.map(_.id), x.id), K.Term) // instantiate the formula with input - val in = instantiateBinder(psi, t) + val in = K.substituteVariables(inner, Map(x -> t)) val con = p.conclusion ->> f +>> in // construct proof val p0 = K.Hypothesis(in |- in, in) - val p1 = K.LeftForall(f |- in, 0, instantiateBinder(psi, tempVar), tempVar, t) + val p1 = K.LeftForall(f |- in, 0, K.substituteVariables(inner, Map(x -> tempVar)) , tempVar, t) val p2 = K.Cut(con, -1, 1, f) /** @@ -144,7 +121,7 @@ object SimpleDeducedSteps { if (K.isImplyingSequent(res._1.conclusion, botK)) proof.ValidProofTactic(bot, Seq(K.SCSubproof(res._1.withNewSteps(IndexedSeq(K.Weakening(botK, res._1.length - 1))), Seq(-1))), Seq(premise)) else - proof.InvalidProofTactic(s"InstantiateForall proved \n\t${FOLParser.printSequent(res._1.conclusion)}\ninstead of input sequent\n\t${FOLParser.printSequent(botK)}") + proof.InvalidProofTactic(s"InstantiateForall proved \n\t${res._1.conclusion.repr}\ninstead of input sequent\n\t${botK.repr}") } } } @@ -173,6 +150,10 @@ object SimpleDeducedSteps { } } + + + + /* /** * Performs a cut when the formula to be used as pivot for the cut is diff --git a/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala similarity index 95% rename from lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala index 628ef7ffa..bf0fed201 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala @@ -8,8 +8,6 @@ import lisa.utils.KernelHelpers.{_, given} import lisa.utils.LisaException import lisa.utils.UserLisaException import lisa.utils.UserLisaException.* -import lisa.utils.parsing.FOLPrinter.prettySCProof -import lisa.utils.parsing.UnreachableException import scala.annotation.nowarn import scala.collection.mutable.Buffer as mBuf @@ -27,6 +25,7 @@ trait WithTheorems { * @param assump list of starting assumptions, usually propagated from outer proofs. */ sealed abstract class Proof(assump: List[F.Formula]) { + val possibleGoal: Option[F.Sequent] type SelfType = this.type type OutsideFact >: JUSTIFICATION @@ -44,9 +43,9 @@ trait WithTheorems { ) { val baseFormula: F.Sequent = sequentOfFact(fact) val (result, proof) = { - val (terms, substPairs) = insts.partitionMap { - case t: F.Term => Left(t) - case sp: F.SubstPair => Right(sp) + val (terms, substPairs) = insts.partitionMap {e => + if e.isInstanceOf[F.Expr[?]] then Left(e.asInstanceOf[F.Term]) + else Right(e.asInstanceOf[F.SubstPair]) } val (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) @@ -147,17 +146,6 @@ trait WithTheorems { ) ) } - /* - def addDefinition(v: LocalyDefinedVariable, defin: F.Formula): Unit = { - if localdefs.contains(v) then - throw new UserInvalidDefinitionException("v", "Variable already defined with" + v.definition + " in current proof") - else { - localdefs(v) = defin - addAssumption(defin) - } - } - def getDefinition(v: LocalyDefinedVariable): Fact = localdefs(v)._2 - */ // Getters @@ -275,7 +263,7 @@ trait WithTheorems { /** * The set of symbols that can't be instantiated because they are free in an assumption. */ - def lockedSymbols: Set[F.SchematicLabel[?]] = assumptions.toSet.flatMap(f => f.freeSchematicLabels.toSet) + def lockedSymbols: Set[F.Variable[?]] = assumptions.toSet.flatMap(f => f.freeVars.toSet) /** * Used to "lift" the type of a justification when the compiler can't infer it. @@ -345,12 +333,14 @@ trait WithTheorems { private val nstack = Throwable() val stack: Array[StackTraceElement] = nstack.getStackTrace.drop(2) } + } /** * Top-level instance of [[Proof]] directly proving a theorem */ sealed class BaseProof(val owningTheorem: THMFromProof) extends Proof(Nil) { + val goal: F.Sequent = owningTheorem.goal val possibleGoal: Option[F.Sequent] = Some(goal) type OutsideFact = JUSTIFICATION @@ -359,6 +349,7 @@ trait WithTheorems { override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement def justifications: List[JUSTIFICATION] = getImports.map(_._1) + } /** @@ -401,8 +392,7 @@ trait WithTheorems { */ def withSorry: Boolean = innerJustification match { case thm: theory.Theorem => thm.withSorry - case fd: theory.FunctionDefinition => fd.withSorry - case pd: theory.PredicateDefinition => false + case d: theory.Definition => false case ax: theory.Axiom => false } } @@ -441,8 +431,8 @@ trait WithTheorems { val fullName: String def repr: String = innerJustification.repr - def label: F.ConstantLabel[?] - knownDefs.update(label, Some(this)) + def cst: F.Constant[?] + knownDefs.update(cst, Some(this)) } @@ -467,7 +457,7 @@ trait WithTheorems { /** * A pretty representation of the goal of the theorem */ - def prettyGoal: String = lisa.utils.FOLPrinter.prettySequent(statement.underlying) + def prettyGoal: String = statement.underlying.repr } object THM { @@ -596,7 +586,7 @@ trait WithTheorems { } if (proof.length == 0) - om.lisaThrow(new UnimplementedProof(this)) + then om.lisaThrow(new UnimplementedProof(this)) val scp = proof.toSCProof val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) @@ -612,6 +602,7 @@ trait WithTheorems { ) ) } + } } diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala index 4c834ae95..ded5883e3 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala @@ -1,17 +1,15 @@ package lisa.utils.tptp -import lisa.utils.parsing.FOLParser.* import lisa.utils.tptp.KernelParser.annotatedStatementToKernel import lisa.utils.tptp.KernelParser.parseToKernel import lisa.utils.tptp.KernelParser.problemToSequent import lisa.utils.tptp.ProblemGatherer.getPRPproblems +import lisa.utils.K.{repr, given} import KernelParser.{mapAtom, mapTerm, mapVariable} object Example { - val prettyFormula = lisa.utils.parsing.FOLParser.printFormula - val prettySequent = lisa.utils.parsing.FOLParser.printSequent def tptpExample(): Unit = { val axioms = List( "( ~ ( ? [X] : ( big_s(X) & big_q(X) ) ) )", @@ -29,8 +27,8 @@ object Example { ) println("\n---Individual Fetched Formulas---") - axioms.foreach(a => println(prettyFormula(parseToKernel(a)(using mapAtom, mapTerm, mapVariable)))) - println(prettyFormula(parseToKernel(conjecture)(using mapAtom, mapTerm, mapVariable))) + axioms.foreach(a => println(parseToKernel(a)(using mapAtom, mapTerm, mapVariable).repr)) + println(parseToKernel(conjecture)(using mapAtom, mapTerm, mapVariable).repr) println("\n---Annotated Formulas---") anStatements.map(annotatedStatementToKernel(_)(using mapAtom, mapTerm, mapVariable)).foreach(f => printAnnotatedStatement(f)) @@ -48,7 +46,7 @@ object Example { val seq = problemToSequent(probs.head) printProblem(probs.head) println("\n---Sequent---") - println(prettySequent(seq)) + println(seq.repr) } } catch { case error: NullPointerException => println("You can download the tptp library at http://www.tptp.org/ and put it in main/resources") @@ -59,8 +57,8 @@ object Example { // Utility def printAnnotatedStatement(a: AnnotatedStatement): Unit = { val prettyStatement = a match { - case f: AnnotatedFormula => prettyFormula(f.formula) - case s: AnnotatedSequent => prettySequent(s.sequent) + case f: AnnotatedFormula => f.formula.repr + case s: AnnotatedSequent => s.sequent.repr } if (a.role == "axiom") println("Given " + a.name + ": " + prettyStatement) else if (a.role == "conjecture") println("Prove " + a.name + ": " + prettyStatement) diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala index 1911e4791..5414be78d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala @@ -23,29 +23,32 @@ object KernelParser { * @param formula A formula in the tptp language * @return the corresponding LISA formula */ - def parseToKernel(formula: String)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Formula = convertToKernel( + def parseToKernel(formula: String)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = convertToKernel( Parser.fof(formula) - ) + )(using mapAtom, mapTerm, mapVariable) /** * @param formula a tptp formula in leo parser * @return the same formula in LISA */ - def convertToKernel(formula: FOF.Formula)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Formula = { + def convertToKernel(formula: FOF.Formula)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = { + val (mapAtom, mapTerm, mapVariable) = maps formula match { case FOF.AtomicFormula(f, args) => - if f == "$true" then K.top() - else if f == "$false" then K.bot() - else K.AtomicFormula(mapAtom(f, args.size), args map convertTermToKernel) + if f == "$true" then K.top + else if f == "$false" then K.bot + else args.foldLeft(mapAtom(f, args.size): K.Expression)((acc, arg) => acc(convertTermToKernel(arg))) + // else throw new Exception("Unknown atomic formula kind: " + kind +" in " + f) case FOF.QuantifiedFormula(quantifier, variableList, body) => quantifier match { - case FOF.! => variableList.foldRight(convertToKernel(body))((s, f) => K.Forall(mapVariable(s), f)) - case FOF.? => variableList.foldRight(convertToKernel(body))((s, f) => K.Exists(mapVariable(s), f)) + case FOF.! => + variableList.foldRight(convertToKernel(body))((s, f) => K.forall(mapVariable(s), f)) + case FOF.? => variableList.foldRight(convertToKernel(body))((s, f) => K.exists(mapVariable(s), f)) } case FOF.UnaryFormula(connective, body) => connective match { - case FOF.~ => K.Neg(convertToKernel(body)) + case FOF.~ => K.neg(convertToKernel(body)) } case FOF.BinaryFormula(connective, left, right) => connective match { @@ -58,24 +61,22 @@ object KernelParser { 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)) + case FOF.Equality(left, right) => K.equality(convertTermToKernel(left))(convertTermToKernel(right)) + case FOF.Inequality(left, right) => !K.equality(convertTermToKernel(left))(convertTermToKernel(right)) } } - def convertToKernel(sequent: FOF.Sequent)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Sequent = { + def convertToKernel(sequent: FOF.Sequent)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Sequent = { K.Sequent(sequent.lhs.map(convertToKernel).toSet, sequent.rhs.map(convertToKernel).toSet) } - def convertToKernel(formula: CNF.Formula)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Formula = { - - K.ConnectorFormula( - K.Or, + def convertToKernel(formula: CNF.Formula)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = { + K.multior( formula.map { - case CNF.PositiveAtomic(formula) => K.AtomicFormula(mapAtom(formula.f, formula.args.size), formula.args.map(convertTermToKernel).toList) - case CNF.NegativeAtomic(formula) => !K.AtomicFormula(mapAtom(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)) + case CNF.PositiveAtomic(formula) => multiapply(mapAtom(formula.f, formula.args.size))(formula.args.map(convertTermToKernel).toList) + case CNF.NegativeAtomic(formula) => !multiapply(mapAtom(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)) } ) } @@ -84,31 +85,33 @@ object KernelParser { * @param term a tptp term in leo parser * @return the same term in LISA */ - def convertTermToKernel(term: CNF.Term)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Term = term match { - case CNF.AtomicTerm(f, args) => K.Term(mapTerm(f, args.size), args map convertTermToKernel) - case CNF.Variable(name) => K.VariableTerm(mapVariable(name)) - case CNF.DistinctObject(name) => ??? - } + def convertTermToKernel(term: CNF.Term)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = + val (mapAtom, mapTerm, mapVariable) = maps + term match { + case CNF.AtomicTerm(f, args) => K.multiapply(mapTerm(f, args.size))(args map convertTermToKernel) + case CNF.Variable(name) => mapVariable(name) + case CNF.DistinctObject(name) => ??? + } /** * @param term a tptp term in leo parser * @return the same term in LISA */ - def convertTermToKernel(term: FOF.Term)(using mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.Term = term match { - - case FOF.AtomicTerm(f, args) => - K.Term(mapTerm(f, args.size), args map convertTermToKernel) - case FOF.Variable(name) => K.VariableTerm(mapVariable(name)) - case FOF.DistinctObject(name) => ??? - case FOF.NumberTerm(value) => ??? - + def convertTermToKernel(term: FOF.Term)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.Expression = + val (mapAtom, mapTerm, mapVariable) = maps + term match { + case FOF.AtomicTerm(f, args) => + K.multiapply(mapTerm(f, args.size))(args map convertTermToKernel) + case FOF.Variable(name) => mapVariable(name) + case FOF.DistinctObject(name) => ??? + case FOF.NumberTerm(value) => ??? } /** * @param formula an annotated tptp statement * @return the corresponding LISA formula augmented with name and role. */ - def annotatedStatementToKernel(formula: String)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): AnnotatedStatement = { + def annotatedStatementToKernel(formula: String)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): AnnotatedStatement = { val i = Parser.annotatedFOF(formula) i match case TPTP.FOFAnnotated(name, role, formula, annotations) => @@ -120,11 +123,8 @@ object KernelParser { } - private def problemToKernel(problemFile: File, md: ProblemMetadata)(using - mapAtom: (String, Int) => K.AtomicLabel, - mapTerm: (String, Int) => K.TermLabel, - mapVariable: String => K.VariableLabel - ): Problem = { + private def problemToKernel(problemFile: File, md: ProblemMetadata)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Problem = { + val (mapAtom, mapTerm, mapVariable) = maps val file = io.Source.fromFile(problemFile) val pattern = "SPC\\s*:\\s*[A-z]{3}(_[A-z]{3})*".r val g = file.getLines() @@ -152,7 +152,7 @@ object KernelParser { * @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)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Problem = { + def problemToKernel(problemFile: File)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Problem = { problemToKernel(problemFile, getProblemInfos(problemFile)) } @@ -160,7 +160,7 @@ object KernelParser { * @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)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Problem = { + def problemToKernel(problemFile: String)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Problem = { problemToKernel(File(problemFile)) } @@ -188,9 +188,9 @@ object KernelParser { if last.nonEmpty && last.forall(_.isDigit) && last.head != '0' then lead.mkString("$u") + "_" + last else pieces.mkString("$u") - val mapAtom: ((String, Int) => K.AtomicLabel) = (f, n) => K.ConstantAtomicLabel(sanitize(f), n) - val mapTerm: ((String, Int) => K.TermLabel) = (f, n) => K.ConstantFunctionLabel(sanitize(f), n) - val mapVariable: (String => K.VariableLabel) = f => K.VariableLabel(sanitize(f)) + val mapAtom: ((String, Int) => K.Constant) = (f, n) => K.Constant(sanitize(f), predicateType(n)) + val mapTerm: ((String, Int) => K.Constant) = (f, n) => K.Constant(sanitize(f), functionType(n)) + val mapVariable: (String => K.Variable) = f => K.Variable(sanitize(f), K.Term) /** * Given a folder containing folders containing problem (typical organisation of TPTP library) and a list of spc, diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala index 162313ebb..ecaac2d44 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -5,6 +5,7 @@ import leo.datastructures.TPTP.FOF import leo.datastructures.TPTP.FOFAnnotated import leo.modules.input.{TPTPParser => Parser} import lisa.utils.K +import K.repr import java.io.File @@ -16,24 +17,26 @@ object ProofParser { val TPTPversion = "TPTP v8.0.0" val rand = scala.util.Random() - given mapAtom: ((String, Int) => K.AtomicLabel) = (f, n) => + type MapTriplet = ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable) + + val mapAtom: ((String, Int) => K.Expression) = (f, n) => val kind = f.head val id = f.tail if kind == 's' then - if n == 0 then K.VariableFormulaLabel(sanitize(id)) - else K.SchematicPredicateLabel(sanitize(id), n) - else if kind == 'c' then K.ConstantAtomicLabel(sanitize(id), n) + K.Variable(sanitize(id), K.predicateType(n)) + else if kind == 'c' then K.Constant(sanitize(id), K.predicateType(n)) else throw new Exception(s"Unknown kind of atomic label: $f") - given mapTerm: ((String, Int) => K.TermLabel) = (f, n) => + val mapTerm: ((String, Int) => K.Expression) = (f, n) => val kind = f.head val id = f.tail - if kind == 's' then K.SchematicFunctionLabel(sanitize(id), n) - else if kind == 'c' then K.ConstantFunctionLabel(sanitize(id), n) - else if n == 0 then K.VariableLabel(sanitize(f)) - else K.SchematicFunctionLabel(sanitize(f), n) - given mapVariable: (String => K.VariableLabel) = f => - if f.head == 'X' then K.VariableLabel(f.tail) - else K.VariableLabel(f) + if kind == 's' then K.Variable(sanitize(id), K.functionType(n)) + else if kind == 'c' then K.Constant(sanitize(id), K.functionType(n)) + else throw new Exception(s"Unknown kind of term label: $f") + val mapVariable: (String => K.Variable) = f => + if f.head == 'X' then K.Variable(f.tail, K.Term) + else K.Variable(f, K.Term) + + given maps: MapTriplet = (mapAtom, mapTerm, mapVariable) def problemToFile(fileDirectory: String, fileName: String, name: String, axioms: Seq[K.Sequent], conjecture: K.Sequent, source: String): File = { // case class Problem(file: String, domain: String, name: String, status: String, spc: Seq[String], formulas: Seq[AnnotatedStatement]) @@ -80,51 +83,50 @@ object ProofParser { def isLowerWord(s: String): Boolean = s.head.isLower && s.tail.forall(_.isLetterOrDigit) inline def quoted(s: String): String = if isLowerWord(s) then s else s"'$s'" - def termToFOFTerm(term: K.Term): FOF.Term = { - val K.Term(label, args) = term - label match - case K.ConstantFunctionLabel(id, arity) => + def termToFOFTerm(term: K.Expression): FOF.Term = { + term match { + case K.Variable(id, K.Term) => FOF.Variable("X" + id) + case K.Constant(id, K.Term) => FOF.AtomicTerm(quoted("c" + id), Seq()) + case K.Multiapp(K.Constant(id, typ), args) => FOF.AtomicTerm(quoted("c" + id), args.map(termToFOFTerm)) - case K.SchematicFunctionLabel(id, arity) => + case K.Multiapp(K.Variable(id, typ), args) => FOF.AtomicTerm(quoted("s" + id), args.map(termToFOFTerm)) - case K.VariableLabel(id) => FOF.Variable("X" + id) + case K.Epsilon(v, f) => throw new Exception("Epsilon terms are not supported") + case _ => throw new Exception("The expression is not purely first order") + } } - def formulaToFOFFormula(formula: K.Formula): FOF.Formula = { + def formulaToFOFFormula(formula: K.Expression): FOF.Formula = { formula match - case K.AtomicFormula(label, args) => - label match - case K.equality => FOF.Equality(termToFOFTerm(args(0)), termToFOFTerm(args(1))) - case K.top => FOF.AtomicFormula("$true", Seq()) - case K.bot => FOF.AtomicFormula("$false", Seq()) - case K.ConstantAtomicLabel(id, arity) => FOF.AtomicFormula(quoted("c" + id), args.map(termToFOFTerm)) - case K.SchematicPredicateLabel(id, arity) => FOF.AtomicFormula(quoted("s" + id), args.map(termToFOFTerm)) - case K.VariableFormulaLabel(id) => FOF.AtomicFormula(quoted("s" + id), Seq()) - case K.ConnectorFormula(label, args) => - label match - case K.Neg => FOF.UnaryFormula(FOF.~, formulaToFOFFormula(args.head)) - case K.Implies => FOF.BinaryFormula(FOF.Impl, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case K.Iff => FOF.BinaryFormula(FOF.<=>, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case K.And => - if args.size == 0 then FOF.AtomicFormula("$true", Seq()) - else if args.size == 1 then formulaToFOFFormula(args(0)) - else FOF.BinaryFormula(FOF.&, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case K.Or => - if args.size == 0 then FOF.AtomicFormula("$false", Seq()) - else if args.size == 1 then formulaToFOFFormula(args(0)) - else FOF.BinaryFormula(FOF.|, formulaToFOFFormula(args(0)), formulaToFOFFormula(args(1))) - case scl: K.SchematicConnectorLabel => throw new Exception(s"Schematic connectors are unsupported") - case K.BinderFormula(label, bound, inner) => - label match - case K.Forall => FOF.QuantifiedFormula(FOF.!, Seq("X" + bound.id), formulaToFOFFormula(inner)) - case K.Exists => FOF.QuantifiedFormula(FOF.?, Seq("X" + bound.id), formulaToFOFFormula(inner)) - case K.ExistsOne => ??? + case K.equality(left, right) => + FOF.Equality(termToFOFTerm(left), termToFOFTerm(right)) + case K.top => FOF.AtomicFormula("$true", Seq()) + case K.bot => FOF.AtomicFormula("$false", Seq()) + case K.neg(f) => FOF.UnaryFormula(FOF.~, formulaToFOFFormula(f)) + case K.and(f1, f2) => FOF.BinaryFormula(FOF.&, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.or(f1, f2) => FOF.BinaryFormula(FOF.|, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.implies(f1, f2) => FOF.BinaryFormula(FOF.Impl, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.iff(f1, f2) => FOF.BinaryFormula(FOF.<=>, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) + case K.forall(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.!, Seq("X" + v.id), formulaToFOFFormula(f)) + case K.exists(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.?, Seq("X" + v.id), formulaToFOFFormula(f)) + case K.forall(p) => + val x = K.freshId(p.freeVariables.map(_.id), "x") + FOF.QuantifiedFormula(FOF.!, Seq("X" + x), formulaToFOFFormula(K.Application(p, K.Variable(x, K.Term)))) + case K.exists(p) => + val x = K.freshId(p.freeVariables.map(_.id), "x") + FOF.QuantifiedFormula(FOF.?, Seq("X" + x), formulaToFOFFormula(K.Application(p, K.Variable(x, K.Term)))) + case K.Multiapp(K.Constant(id, typ), args) => + FOF.AtomicFormula(quoted("c" + id), args.map(termToFOFTerm)) + case K.Multiapp(K.Variable(id, typ), args) => + FOF.AtomicFormula(quoted("s" + id), args.map(termToFOFTerm)) + case _ => throw new Exception("The expression is not purely first order: " + formula) + } - def formulaToFOFStatement(formula: K.Formula): FOF.Statement = { + def formulaToFOFStatement(formula: K.Expression): FOF.Statement = { FOF.Logical(formulaToFOFFormula(formula)) } - def reconstructProof(file: File)(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): K.SCProof = { + def reconstructProof(file: File)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): K.SCProof = { val problem = Parser.problem(io.Source.fromFile(file)) val nameMap = scala.collection.mutable.Map[String, (Int, FOF.Sequent)]() var prems = List[K.Sequent]() @@ -155,11 +157,8 @@ object ProofParser { K.SCProof(steps.reverse.toIndexedSeq, prems.reverse.toIndexedSeq) } - def annotatedStatementToProofStep(ann: FOFAnnotated, numbermap: String => Int, sequentmap: String => FOF.Sequent)(using - mapAtom: (String, Int) => K.AtomicLabel, - mapTerm: (String, Int) => K.TermLabel, - mapVariable: String => K.VariableLabel - ): Option[(K.SCProofStep, String)] = { + def annotatedStatementToProofStep(ann: FOFAnnotated, numbermap: String => Int, sequentmap: String => FOF.Sequent) + (using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Option[(K.SCProofStep, String)] = { given (String => Int) = numbermap given (String => FOF.Sequent) = sequentmap val r = ann match { @@ -180,13 +179,18 @@ object ProofParser { case Inference.LeftAll(step, name) => Some((step, name)) case Inference.LeftNEx(step, name) => Some((step, name)) case Inference.RightNot(step, name) => Some((step, name)) + case Inference.RightRefl(step, name) => Some((step, name)) + case Inference.RightSubstEq(step, name) => Some((step, name)) + case Inference.LeftSubstEq(step, name) => Some((step, name)) + case Inference.RightSubstIff(step, name) => Some((step, name)) + case Inference.LeftSubstIff(step, name) => Some((step, name)) case _ => None } r } object Inference { - import leo.datastructures.TPTP.{Annotations, GeneralTerm, MetaFunctionData, NumberData, Integer, FOF, GeneralFormulaData, FOTData} + import leo.datastructures.TPTP.{Annotations, GeneralTerm, MetaFunctionData, NumberData, Integer, FOF, GeneralFormulaData, FOTData, FOFData} import K.apply object Number { @@ -197,12 +201,19 @@ object ProofParser { } } object Term { - def unapply(ann_seq: GeneralTerm)(using mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[K.Term] = + def unapply(ann_seq: GeneralTerm)(using maps: MapTriplet): Option[K.Expression] = ann_seq match { case GeneralTerm(List(GeneralFormulaData(FOTData(term))), None) => Some(convertTermToKernel(term)) case _ => None } } + object Formula { + def unapply(ann_seq: GeneralTerm)(using maps: MapTriplet): Option[K.Expression] = + ann_seq match { + case GeneralTerm(List(GeneralFormulaData(FOFData(FOF.Logical(formula)))), None) => Some(convertToKernel(formula)) + case _ => None + } + } object String { def unapply(ann_seq: GeneralTerm): Option[String] = ann_seq match { @@ -256,7 +267,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("hyp", Seq(StrOrNum(n), StrOrNum(m)), Seq())) => if (sequent.lhs(n.toInt) == sequent.rhs(m.toInt)) then @@ -272,7 +283,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("cut", Seq(StrOrNum(n), StrOrNum(m)), Seq(t1, t2))) => val formula1 = sequentmap(t1).rhs(n.toInt) @@ -289,13 +300,13 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftHyp", Seq(StrOrNum(n), StrOrNum(m)), Seq())) => val left = sequent.lhs.map(convertToKernel) val right = sequent.rhs.map(convertToKernel) val formula = left(n.toInt) - if (formula == K.Neg(left(m.toInt)) || K.Neg(formula) == left(m.toInt)) then Some((K.RestateTrue(K.Sequent(left.toSet, right.toSet)), name)) + if (formula == K.neg(left(m.toInt)) || K.neg(formula) == left(m.toInt)) then Some((K.RestateTrue(K.Sequent(left.toSet, right.toSet)), name)) else None case _ => None @@ -305,7 +316,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotNot", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -316,7 +327,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftAnd", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -327,7 +338,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotOr", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -338,7 +349,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotImp", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -350,15 +361,15 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotAnd", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Neg, Seq(K.ConnectorFormula(K.And, Seq(x, y)))) => (x, y) + case K.Neg(K.And(x, y)) => (x, y) case _ => throw new Exception(s"Expected a negated conjunction, but got $f") } - Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(K.Neg(a), K.Neg(b))), name)) + Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(K.neg(a), K.neg(b))), name)) case _ => None } } @@ -367,12 +378,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftOr", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Or, Seq(x, y)) => (x, y) + case K.Or(x, y) => (x, y) case _ => throw new Exception(s"Expected a disjunction, but got $f") } Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(a, b))), name) @@ -384,12 +395,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftImp1", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Implies, Seq(x, y)) => (x, y) + case K.Implies(x, y) => (x, y) case _ => throw new Exception(s"Expected an implication, but got $f") } Some((K.LeftImplies(convertToKernel(sequent), numbermap(t1), numbermap(t2), a, b), name)) @@ -401,15 +412,15 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftImp2", Seq(StrOrNum(n)), Seq(t1, t2))) => val f = sequent.lhs(n.toInt) val (a, b) = convertToKernel(f) match { - case K.ConnectorFormula(K.Implies, Seq(x, y)) => (x, y) + case K.Implies(x, y) => (x, y) case _ => throw new Exception(s"Expected an implication, but got $f") } - Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(K.Neg(a), b)), name)) + Some((K.LeftOr(convertToKernel(sequent), Seq(numbermap(t1), numbermap(t2)), Seq(K.neg(a), b)), name)) case _ => None } } @@ -418,19 +429,19 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotForall", Seq(StrOrNum(n), Term(xl)), Seq(t1))) => // x has to be a GeneralTerm representinf a variable, i.e. $fot(x) val f = sequent.lhs(n.toInt) val x = xl match - case K.Term(x: K.VariableLabel, Seq()) => x + case x: K.Variable => x case _ => throw new Exception(s"Expected a variable, but got $xl") - val (y: K.VariableLabel, phi: K.Formula) = convertToKernel(f) match { - case K.ConnectorFormula(K.Neg, Seq(K.BinderFormula(K.Forall, x, phi))) => (x, phi) + val (y: K.Variable, phi: K.Expression) = convertToKernel(f) match { + case K.Neg(K.forall(K.Lambda(x, phi))) => (x, phi) case _ => throw new Exception(s"Expected a universal quantification, but got $f") } if x == y then Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), phi, x), name)) - else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariablesInFormula(K.ConnectorFormula(K.Neg, Seq(phi)), Map(y -> xl), Seq()), x), name)) + else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariables(K.neg(phi), Map(y -> xl)), x), name)) case _ => None } } @@ -439,19 +450,19 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftEx", Seq(StrOrNum(n), Term(xl)), Seq(t1))) => // x has to be a GeneralTerm representinf a variable, i.e. $fot(x) val f = sequent.lhs(n.toInt) val x = xl match - case K.Term(x: K.VariableLabel, Seq()) => x + case x:K.Variable => x case _ => throw new Exception(s"Expected a variable, but got $xl") - val (y: K.VariableLabel, phi: K.Formula) = convertToKernel(f) match { - case K.BinderFormula(K.Exists, x, phi) => (x, phi) + val (y: K.Variable, phi: K.Expression) = convertToKernel(f) match { + case K.Exists(x, phi) => (x, phi) case _ => throw new Exception(s"Expected an existential quantification, but got $f") } if x == y then Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), phi, x), name)) - else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariablesInFormula(phi, Map(y -> xl), Seq()), x), name)) + else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariables(phi, Map(y -> xl)), x), name)) case _ => None } } @@ -460,12 +471,12 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftForall", Seq(StrOrNum(n), Term(t)), Seq(t1))) => val f = sequent.lhs(n.toInt) val (x, phi) = convertToKernel(f) match { - case K.BinderFormula(K.Forall, x, phi) => (x, phi) + case K.Forall(x, phi) => (x, phi) case _ => throw new Exception(s"Expected a universal quantification, but got $f") } Some((K.LeftForall(convertToKernel(sequent), numbermap(t1), phi, x, t), name)) @@ -477,15 +488,15 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftNotEx", Seq(StrOrNum(n), Term(t)), Seq(t1))) => val f = sequent.lhs(n.toInt) val (x, phi) = convertToKernel(f) match { - case K.ConnectorFormula(K.Neg, Seq(K.BinderFormula(K.Exists, x, phi))) => (x, phi) + case K.Neg(K.Exists(x, phi)) => (x, phi) case _ => throw new Exception(s"Expected a negated existential quantification, but got $f") } - Some((K.LeftForall(convertToKernel(sequent), numbermap(t1), K.ConnectorFormula(K.Neg, Seq(phi)), x, t), name)) + Some((K.LeftForall(convertToKernel(sequent), numbermap(t1), K.neg(phi), x, t), name)) case _ => None } } @@ -494,7 +505,7 @@ object ProofParser { def unapply(ann_seq: FOFAnnotated)(using numbermap: String => Int, sequentmap: String => FOF.Sequent - )(using mapAtom: (String, Int) => K.AtomicLabel, mapTerm: (String, Int) => K.TermLabel, mapVariable: String => K.VariableLabel): Option[(K.SCProofStep, String)] = + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = ann_seq match { case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("rightNot", Seq(StrOrNum(n)), Seq(t1))) => Some((K.Weakening(convertToKernel(sequent), numbermap(t1)), name)) @@ -502,5 +513,103 @@ object ProofParser { } } + object RightRefl { + def unapply(ann_seq: FOFAnnotated)(using + numbermap: String => Int, + sequentmap: String => FOF.Sequent + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = + ann_seq match { + case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("rightRefl", Seq(StrOrNum(n)), Seq())) => + val left = sequent.lhs.map(convertToKernel) + val right = sequent.rhs.map(convertToKernel) + val formula = right(n.toInt) + formula match + case K.equality(s, t) if K.isSame(s, t) => Some((K.RightRefl(K.Sequent(left.toSet, right.toSet), formula), name)) + case _ => throw new Exception(s"Expected an equality, but got $formula") + case _ => None + } + } + + object RightSubstEq { + def unapply(ann_seq: FOFAnnotated)(using + numbermap: String => Int, + sequentmap: String => FOF.Sequent + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = + ann_seq match { + case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("rightSubstEq", Seq(StrOrNum(n), Formula(fl), Term(xl)), Seq(t1))) => + val f = sequent.lhs(n.toInt) + val x = xl match + case x:K.Variable => x + case _ => throw new Exception(s"Expected a variable, but got $xl") + val (s, t) = convertToKernel(f) match { + case K.equality(s, t) => (s, t) + case _ => throw new Exception(s"Expected an existential quantification, but got $f") + } + Some((K.RightSubstEq(convertToKernel(sequent), numbermap(t1), Seq((s, t)), (Seq(x), fl)), name)) + + case _ => None + } + } + + object LeftSubstEq { + def unapply(ann_seq: FOFAnnotated)(using + numbermap: String => Int, + sequentmap: String => FOF.Sequent + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = + ann_seq match { + case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftSubstEq", Seq(StrOrNum(n), Formula(fl), Term(xl)), Seq(t1))) => + val f = sequent.lhs(n.toInt) + val x = xl match + case x:K.Variable => x + case _ => throw new Exception(s"Expected a variable, but got $xl") + val (s, t) = convertToKernel(f) match { + case K.equality(s, t) => (s, t) + case _ => throw new Exception(s"Expected an existential quantification, but got $f") + } + Some((K.LeftSubstEq(convertToKernel(sequent), numbermap(t1), Seq((s, t)), (Seq(x), fl)), name)) + case _ => None + } + } + + object LeftSubstIff { + def unapply(ann_seq: FOFAnnotated)(using + numbermap: String => Int, + sequentmap: String => FOF.Sequent + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = + ann_seq match { + case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("leftSubstIff", Seq(StrOrNum(n), Formula(fl), Formula(xl)), Seq(t1))) => + val f = sequent.lhs(n.toInt) + val x = xl match + case x:K.Variable => x + case _ => throw new Exception(s"Expected a variable, but got $xl") + val (s, t) = convertToKernel(f) match { + case K.iff(s, t) => (s, t) + case _ => throw new Exception(s"Expected an existential quantification, but got $f") + } + Some((K.LeftSubstEq(convertToKernel(sequent), numbermap(t1), Seq((s, t)), (Seq(x), fl)), name)) + case _ => None + } + } + + object RightSubstIff { + def unapply(ann_seq: FOFAnnotated)(using + numbermap: String => Int, + sequentmap: String => FOF.Sequent + )(using maps: MapTriplet): Option[(K.SCProofStep, String)] = + ann_seq match { + case FOFAnnotated(name, role, sequent: FOF.Sequent, Inference("rightSubstIff", Seq(StrOrNum(n), Formula(fl), Formula(xl)), Seq(t1))) => + val f = sequent.lhs(n.toInt) + val x = xl match + case x:K.Variable => x + case _ => throw new Exception(s"Expected a variable, but got $xl") + val (s, t) = convertToKernel(f) match { + case K.iff(s, t) => (s, t) + case _ => throw new Exception(s"Expected an existential quantification, but got $f") + } + Some((K.RightSubstEq(convertToKernel(sequent), numbermap(t1), Seq((s, t)), (Seq(x), fl)), name)) + case _ => None + } + } + } } diff --git a/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala index 56192deef..5c4de54ee 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala @@ -19,7 +19,7 @@ sealed trait AnnotatedStatement { } } -case class AnnotatedFormula(role: String, name: String, formula: K.Formula, annotations: TPTP.Annotations) extends AnnotatedStatement +case class AnnotatedFormula(role: String, name: String, formula: K.Expression, annotations: TPTP.Annotations) extends AnnotatedStatement case class AnnotatedSequent(role: String, name: String, sequent: K.Sequent, annotations: TPTP.Annotations) extends AnnotatedStatement diff --git a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala index dcc762fa5..4f58765ce 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -1,26 +1,399 @@ package lisa.utils.unification import lisa.fol.FOL.{_, given} -//import lisa.fol.FOLHelpers.* - -//import lisa.kernel.fol.FOL.* -//import lisa.utils.KernelHelpers.{_, given} +import lisa.prooflib.Library +import lisa.prooflib.SimpleDeducedSteps +import lisa.utils.K +import lisa.utils.memoization.memoized +import lisa.utils.collection.Extensions.* +import lisa.utils.collection.{VecSet => Set} +import lisa.fol.FOL /** * General utilities for unification, substitution, and rewriting */ -object UnificationUtils { +object UnificationUtils: - extension [A](seq: Seq[A]) { + /** + * Chosen equality for terms in matching and rewriting. + */ + inline def eq[A](l: Expr[A], r: Expr[A]) = isSame(l, r) + + /** + * Context containing information and constraints pertaining to matching, + * unification, and rewriting. + * + * @param boundVariables variables in terms that cannot be substituted + */ + case class RewriteContext( + boundVariables: Set[Variable[?]], + freeRules: Set[RewriteRule], + confinedRules: Set[RewriteRule], + ): + // when a context is constructed, update the global ID counter to make sure + // we aren't conflicting with variable names in the rewrite rules + RewriteContext.updateIDCounts(this) + + /** + * Checks if a variable is free under this context. + */ + def isFree[A](v: Variable[A]) = !isBound(v) + + /** + * Checks if a variable is bound under this context. + */ + def isBound[A](v: Variable[A]) = boundVariables.contains(v) + + /** + * A copy of this context with the given variable additionally bound. + */ + def withBound[A](v: Variable[A]) = + this.copy(boundVariables = boundVariables + v) + + /** + * A copy of this context with the given variables additionally bound. + */ + def withBound(vs: Iterable[Variable[?]]) = + this.copy(boundVariables = boundVariables ++ vs) + + /** + * A copy of this context with the given pair added as a _free_ rewrite + * rule, whose variables may be instantiated during rewriting. + */ + def withFreeRule[A](rule: RewriteRule) = + this.copy(freeRules = freeRules + rule) /** - * Seq.collectFirst, but for a function returning an Option. Evaluates the - * function only once per argument. Returns when the first non-`None` value - * is found. + * A copy of this context with the given pair added as a _confined_ rewrite + * rule, whose variables may *not* be instantiated during rewriting. + */ + def withConfinedRule[A](rule: RewriteRule) = + this.copy(confinedRules = confinedRules + rule) + + /** + * All rules (free + confined) in this context. + */ + def allRules: Set[RewriteRule] = freeRules ++ confinedRules + + val representativeVariable = memoized(__representativeVariable) + + private def __representativeVariable(rule: InstantiatedRewriteRule): Variable[?] = + val id = RewriteContext.freshRepresentative + rule.rule match + case TermRewriteRule(_, _) => Variable[T](id) + case FormulaRewriteRule(_, _) => Variable[F](id) + // should not reach under general use, but why not: + case r: InstantiatedRewriteRule => representativeVariable(r) + + object RewriteContext: + /** + * The empty rewrite context. + */ + def empty = RewriteContext(Set.empty, Set.empty, Set.empty) + + /** + * A rewrite context with the given variables considered bound. + */ + def withBound(vars: Iterable[Variable[?]]) = + RewriteContext(vars.to(Set), Set.empty, Set.empty) + + private object IDCounter: + val reprName = "@@internalRewriteVar@@" + private var current = 0 + def setIDCountTo(limit: Int): Unit = + current = math.max(limit, current) + def nextIDCount: Int = + current += 1 + current + + import IDCounter.{reprName, setIDCountTo, nextIDCount} + + private def freshRepresentative: Identifier = + Identifier(reprName, nextIDCount) + + private def maxVarId[A](expr: Expr[A]): Int = + expr match + case Variable(id: Identifier) => id.no + case Constant(id) => 0 + case App(f, arg) => math.max(maxVarId(f), maxVarId(arg)) + case Abs(v: Variable[?], body: Expr[?]) => math.max(maxVarId(v), maxVarId(body)) + + private def updateIDCounts(ctx: RewriteContext): Unit = + val max = ctx.allRules.map(r => maxVarId(r.toFormula)).maxOption.getOrElse(0) + 1 + setIDCountTo(max) + + /** + * Immutable representation of a typed variable substitution. + * + * Wraps an immutable map while preserving variable types. + * + * Types are discarded for storage but are guaranteed to be sound by + * construction. + * + * @param assignments mappings to initialize the substitution with + */ + class Substitution private ( + protected val assignments: Map[Variable[?], Expr[?]], + protected val freeVariables: Set[Variable[?]] + ): + // invariant: + // require( + // freeVariables == assignments.keySet ++ assignments.values.flatMap(_.freeVars) + // ) + + /** + * (Optionally) retrieves a variable's mapping + */ + def apply[A](v: Variable[A]): Option[Expr[A]] = + assignments.get(v).map(_.asInstanceOf) + + /** + * Creates a new substitution with a new mapping added + */ + def +[A](mapping: (Variable[A], Expr[A])): Substitution = + val newfree = mapping._2.freeVars + mapping._1 + Substitution(assignments + mapping, freeVariables ++ newfree) + + /** + * Checks whether a variable is assigned by this substitution + */ + def contains[A](v: Variable[A]): Boolean = + assignments.contains(v) + + /** + * Checks whether any substitution contains the given variable. Needed for + * verifying ill-formed substitutions containing bound variables. * - * @param T output type under option - * @param f the function to evaluate + * Eg: if `v` is externally bound, then `x` and `f(v)` have no matcher under + * capture avoiding substitution. + */ + def substitutes[A](v: Variable[A]): Boolean = + freeVariables(v) + + def asSubstPair: Seq[SubstPair] = + assignments.map((v, e) => v := e.asInstanceOf).toSeq + + object Substitution: + /** + * The empty substitution */ + def empty: Substitution = Substitution(Map.empty, Set.empty) + + /** + * Performs first-order matching for two terms. Returns a (most-general) + * substitution from variables to terms such that `expr` substituted is equal + * to `pattern`, if one exists. + * + * Does not use rewrite rules provided by `ctx`, if any. + * + * @param expr the reference term (to substitute in) + * @param pattern the pattern to match against + * @param subst partial substitution to match under + * @param ctx (implicit) context to match under + * @return substitution (Option) from variables to terms. `None` iff a + * substitution does not exist. + */ + def matchExpr[A](using ctx: RewriteContext)(expr: Expr[A], pattern: Expr[A], subst: Substitution = Substitution.empty): Option[Substitution] = + // chosen equality: ortholattice equivalence + inline def eq(l: Expr[A], r: Expr[A]) = isSame(l, r) + + if eq(expr, pattern) then + // trivial, done + Some(subst) + else + (expr, pattern) match + case (v @ Variable(_), _) if ctx.isFree(v) => + subst(v) match + case Some(e) => + // this variable has been assigned before. + // is that subst compatible with this instance? + if eq(e, pattern) then Some(subst) else None + case None => + // first encounter + Some(subst + (v -> pattern)) + case (App(fe, arge), App(fp, argp)) if fe.sort == fp.sort => + // the sort of fp is already runtime checked here; the sort of argp + // is implied by combination of static and runtime checks + matchExpr(fe, fp.asInstanceOf, subst) + .flatMap(subst => matchExpr(arge, argp.asInstanceOf, subst)) + + case (Abs(ve, fe), Abs(vp, fp)) => + val freshVar = ve.freshRename(Seq(fe, fp)) + matchExpr(using ctx.withBound(freshVar))( + fe.substitute(ve := freshVar), + fp.substitute(vp := freshVar), + subst + ).filterNot(_.substitutes(freshVar)) + + case _ => None + + + sealed trait RewriteRule: + type Base + + def l: Expr[Base] + + def r: Expr[Base] + + /** + * Flip this rewrite rule + */ + def swap: RewriteRule + + /** + * The trivial hypothesis step that can be used as a source for this rewrite + */ + def source(using lib: Library, proof: lib.Proof): proof.Fact = + val form = toFormula + lib.have(using proof)(form |- form) by SimpleDeducedSteps.Restate + + /** + * Reduce this rewrite rule to a formula representing the equivalence. + */ + def toFormula: Formula + + /** + * The sort of the terms in this rewrite rule. + */ + def sort: K.Sort = l.sort + + + case class TermRewriteRule(l: Term, r: Term) extends RewriteRule: + type Base = T + def swap: TermRewriteRule = TermRewriteRule(r, l) + def toFormula: Formula = l === r + + case class FormulaRewriteRule(l: Formula, r: Formula) extends RewriteRule: + type Base = F + def swap: FormulaRewriteRule = FormulaRewriteRule(r, l) + def toFormula: Formula = l <=> r + + case class InstantiatedRewriteRule(rule: RewriteRule, subst: Substitution) extends RewriteRule: + type Base = rule.Base + def l: Expr[rule.Base] = rule.l.substitute(subst.asSubstPair*) + def r: Expr[rule.Base] = rule.r.substitute(subst.asSubstPair*) + def toFormula: Formula = rule.toFormula.substitute(subst.asSubstPair*) + def swap: RewriteRule = InstantiatedRewriteRule(rule.swap, subst) + + + /** + * Given a single *free* rewrite rule, checks whether it rewrite `from` to + * `to` under this context. If the rewrite succeeds, returns the rule and + * the instantiation of the rule corresponding to the rewrite step. + * + * @param from term to rewrite from + * @param to term to rewrite into + * @param rule *free* rewrite rule to use + */ + private def rewriteOneWithFree[A](from: Expr[A], to: Expr[A], rule: RewriteRule {type Base = A}): Option[InstantiatedRewriteRule] = + val ctx = RewriteContext.empty + // attempt to rewrite with all bound variables discarded + rewriteOneWith(using ctx)(from, to, rule) + + /** + * Given a single rewrite rule, checks whether it rewrite `from` to `to` + * under this context. The rewrite rule is considered *confined* by the + * context. See [[rewriteOneWithFree]] for free rules. If the rewrite + * succeeds, returns the rule and the instantiation of the rule + * corresponding to the rewrite step. + * + * @param ctx (implicit) context to rewrite under + * @param from term to rewrite from + * @param to term to rewrite into + * @param rule *free* rewrite rule to use + */ + private def rewriteOneWith[A](using ctx: RewriteContext)(from: Expr[A], to: Expr[A], rule: RewriteRule {type Base = A}): Option[InstantiatedRewriteRule] = + val (l: Expr[A], r: Expr[A]) = (rule.l, rule.r) + // match the left side + matchExpr(l, from, Substitution.empty) + // based on this partial substitution, try to match the right side + // note: given that first match succeeded, any extension of it is still a successful matcher for l -> from + .flatMap(partialSubst => matchExpr(r, to, partialSubst)) + // if succeeded, pair the rule together and ship out + .map(finalSubst => InstantiatedRewriteRule(rule, finalSubst)) + + /** + * Tries to find a *top-level* rewrite from `from` to `to` using the + * rewrite rules in the implicit context. The rewrite rule unifying the two + * terms is returned if one exists. + * + * @param from term to rewrite from + * @param to term to rewrite into + */ + private def rewriteOne[A] + (using ctx: RewriteContext) + (from: Expr[A], to: Expr[A]): Option[InstantiatedRewriteRule] = + // rule sort is runtime checked + lazy val confinedRewrite = ctx.confinedRules + .filter(_.sort == from.sort) + .collectFirstDefined(rule => rewriteOneWith(from, to, rule.asInstanceOf)) + lazy val freeRewrite = ctx.freeRules + .filter(_.sort == from.sort) + .collectFirstDefined(rule => rewriteOneWithFree(from, to, rule.asInstanceOf)) + + // confined rules take precedence + // local rewrites are more likely to succeed than global ones + // (anecdotally) :) + confinedRewrite.orElse(freeRewrite) + + case class RewriteResult[A](ctx: RewriteContext, usedRules: Set[InstantiatedRewriteRule], context: Expr[A]): + def toLeft: Expr[A] = + context.substitute((vars `lazyZip` rules.map(_.l)).map((v, e) => v := e.asInstanceOf)*) + def toRight: Expr[A] = + context.substitute((vars `lazyZip` rules.map(_.r)).map((v, e) => v := e.asInstanceOf)*) + def vars: Seq[Variable[?]] = usedRules.map(ctx.representativeVariable).toSeq + def lambda: Expr[A] = context + def rules: Set[InstantiatedRewriteRule] = usedRules + def substitutes(v: Variable[?]): Boolean = + usedRules.exists(_.subst.substitutes(v)) + + // invariant: + // require( (vars `zip` rules).forall((v, e) => v.Sort == rule.Base ) ) // equality is over types + + type FormulaRewriteResult = RewriteResult[F] + + def rewrite[A](using ctx: RewriteContext)(from: Expr[A], to: Expr[A]): Option[RewriteResult[A]] = + lazy val rule = rewriteOne(from, to) + + if eq(from, to) then + Some(RewriteResult(ctx, Set.empty, from)) + else if rule.isDefined then + val irule = rule.get + Some(RewriteResult(ctx, Set(irule), ctx.representativeVariable(irule).asInstanceOf)) + else + (from, to) match + case (App(fe, arge), App(fp, argp)) if fe.sort == fp.sort => + lazy val fun = rewrite(fe, fp.asInstanceOf) + lazy val arg = rewrite(arge, argp.asInstanceOf) + + for + f <- fun + a <- arg + yield RewriteResult(ctx, f.rules ++ a.rules, f.context #@ a.context) + + case (Abs(ve, fe), Abs(vp, fp)) => + val freshVar = ve.freshRename(Seq(fe, fp)) + rewrite(fe.substitute(ve := freshVar), fp.substitute(vp := freshVar)) + .filterNot(_.substitutes(freshVar)) + .map: + case RewriteResult(c, r, e) => + RewriteResult(c, r, Abs(freshVar, e)) + case _ => None + +end UnificationUtils + +// object UnificationUtils { +/* + extension [A](seq: Seq[A]) { + + /** + * Seq.collectFirst, but for a function returning an Option. Evaluates the + * function only once per argument. Returns when the first non-`None` value + * is found. + * + * @param T output type under option + * @param f the function to evaluate + */ def getFirst[T](f: A => Option[T]): Option[T] = { var res: Option[T] = None val iter = seq.iterator @@ -33,8 +406,8 @@ object UnificationUtils { } /** - * All the information required for performing rewrites. - */ + * All the information required for performing rewrites. + */ case class RewriteContext( freeFormulaRules: Seq[(Formula, Formula)] = Seq.empty, freeTermRules: Seq[(Term, Term)] = Seq.empty, @@ -46,11 +419,11 @@ object UnificationUtils { private var lastID: Identifier = freshId((takenFormulaVars ++ takenTermVars).map(_.id), "@@rewriteVar@@") /** - * Generates a fresh identifier with an internal label `__rewriteVar__`. - * Mutates state. - * - * @return fresh identifier - */ + * Generates a fresh identifier with an internal label `__rewriteVar__`. + * Mutates state. + * + * @return fresh identifier + */ def freshIdentifier = { lastID = freshId(Seq(lastID), "@@rewriteVar@@") lastID @@ -60,11 +433,11 @@ object UnificationUtils { def isFreeVariable(v: VariableFormula) = !takenFormulaVars.contains(v) /** - * Update the last generated fresh ID to that of another context if it is - * larger, otherwise retain the previous value. Mutates state. - * - * @param other another context - */ + * Update the last generated fresh ID to that of another context if it is + * larger, otherwise retain the previous value. Mutates state. + * + * @param other another context + */ def updateTo(other: RewriteContext) = lastID = if (other.lastID.no > lastID.no) other.lastID else lastID } @@ -82,34 +455,34 @@ object UnificationUtils { val FormulaSubstitution = Map /** - * Performs first-order matching for two terms. Returns a (most-general) - * substitution from variables to terms such that `first` substituted is equal TODO: Fix `first`and `second` - * to `second`, if one exists. Uses [[matchTermRecursive]] as the actual - * implementation. - * - * @param reference the reference term - * @param template the term to match - * @param takenVariables any variables in the template which cannot be - * substituted, i.e., treated as constant - * @return substitution (Option) from variables to terms. `None` iff a - * substitution does not exist. - */ + * Performs first-order matching for two terms. Returns a (most-general) + * substitution from variables to terms such that `first` substituted is equal TODO: Fix `first`and `second` + * to `second`, if one exists. Uses [[matchTermRecursive]] as the actual + * implementation. + * + * @param reference the reference term + * @param template the term to match + * @param takenVariables any variables in the template which cannot be + * substituted, i.e., treated as constant + * @return substitution (Option) from variables to terms. `None` iff a + * substitution does not exist. + */ def matchTerm(reference: Term, template: Term, takenVariables: Iterable[Variable] = Iterable.empty): Option[TermSubstitution] = { val context = RewriteContext(takenTermVars = takenVariables.toSet) matchTermRecursive(using context)(reference, template, TermSubstitution.empty) } /** - * Implementation for matching terms. See [[matchTerm]] for the interface. - * - * @param context all information about restricted variables and fresh name - * generation state - * @param reference the reference terms - * @param template the terms to match - * @param substitution currently accumulated susbtitutions to variables - * @return substitution (Option) from variables to terms. `None` if a - * substitution does not exist. - */ + * Implementation for matching terms. See [[matchTerm]] for the interface. + * + * @param context all information about restricted variables and fresh name + * generation state + * @param reference the reference terms + * @param template the terms to match + * @param substitution currently accumulated substitutions to variables + * @return substitution (Option) from variables to terms. `None` if a + * substitution does not exist. + */ private def matchTermRecursive(using context: RewriteContext)(reference: Term, template: Term, substitution: TermSubstitution): Option[TermSubstitution] = if (reference == template) Some(substitution) @@ -133,25 +506,24 @@ object UnificationUtils { } /** - * Performs first-order matching for two formulas. Returns a (most-general) - * substitution from variables to terms such that `first` substituted is equal - * to `second`, if one exists. Uses [[matchFormulaRecursive]] as the actual - * implementation. - * - * @param reference the reference formula - * @param template the formula to match - * @param takenTermVariables any variables in the template which cannot be - * substituted, i.e., treated as constant - * @param takenFormulaVariables any formula variables in the template which - * cannot be substituted, i.e., treated as constant - * @return substitution pair (Option) from formula variables to formulas, and - * variables to terms. `None` if a substitution does not exist. - */ + * Performs first-order matching for two formulas. Returns a (most-general) + * substitution from variables to terms such that `first` substituted is equal + * to `second`, if one exists. Uses [[matchFormulaRecursive]] as the actual + * implementation. + * + * @param reference the reference formula + * @param template the formula to match + * @param takenTermVariables any variables in the template which cannot be + * substituted, i.e., treated as constant + * @param takenFormulaVariables any formula variables in the template which + * cannot be substituted, i.e., treated as constant + * @return substitution pair (Option) from formula variables to formulas, and + * variables to terms. `None` if a substitution does not exist. + */ def matchFormula( reference: Formula, template: Formula, takenTermVariables: Iterable[Variable] = Iterable.empty, - takenFormulaVariables: Iterable[VariableFormula] = Iterable.empty ): Option[(FormulaSubstitution, TermSubstitution)] = { val context = RewriteContext( takenTermVars = takenTermVariables.toSet, @@ -161,17 +533,17 @@ object UnificationUtils { } /** - * Implementation for matching formulas. See [[matchFormula]] for the - * interface. - * - * @param context all information about restricted variables and fresh name generation state - * @param reference the reference formula - * @param template the formula to match - * @param formulaSubstitution currently accumulated susbtitutions to formula variables - * @param termSubstitution currently accumulated susbtitutions to term variables - * @return substitution pair (Option) from formula variables to formulas, and - * variables to terms. `None` if a substitution does not exist. - */ + * Implementation for matching formulas. See [[matchFormula]] for the + * interface. + * + * @param context all information about restricted variables and fresh name generation state + * @param reference the reference formula + * @param template the formula to match + * @param formulaSubstitution currently accumulated substitutions to formula variables + * @param termSubstitution currently accumulated substitutions to term variables + * @return substitution pair (Option) from formula variables to formulas, and + * variables to terms. `None` if a substitution does not exist. + */ private def matchFormulaRecursive(using context: RewriteContext )(reference: Formula, template: Formula, formulaSubstitution: FormulaSubstitution, termSubstitution: TermSubstitution): Option[(FormulaSubstitution, TermSubstitution)] = { @@ -241,37 +613,37 @@ object UnificationUtils { // rewrites /** - * A term rewrite rule (`l -> r`) with an accompanying instantiation, given - * by a term substitution. - * - * @example A rule without any instantiation would be `((l -> r), - * TermSubstitution.empty)`. - * @example Commutativity of a function with instantiation can be `((f(x, y) - * -> f(y, x)), Map(x -> pair(a, b), y -> c))` - */ + * A term rewrite rule (`l -> r`) with an accompanying instantiation, given + * by a term substitution. + * + * @example A rule without any instantiation would be `((l -> r), + * TermSubstitution.empty)`. + * @example Commutativity of a function with instantiation can be `((f(x, y) + * -> f(y, x)), Map(x -> pair(a, b), y -> c))` + */ type TermRule = ((Term, Term), TermSubstitution) /** - * A formula rewrite rule (`l -> r`) with an accompanying instantiation, - * given by a formula and a term substitution. - * - * @example A rule without any instantiation would be `((l -> r), - * FormulaSubstitution.empty)`. - * @example `((P(x) \/ Q -> Q /\ R(x)), Map(Q -> A \/ B, x -> f(t)))` - */ + * A formula rewrite rule (`l -> r`) with an accompanying instantiation, + * given by a formula and a term substitution. + * + * @example A rule without any instantiation would be `((l -> r), + * FormulaSubstitution.empty)`. + * @example `((P(x) \/ Q -> Q /\ R(x)), Map(Q -> A \/ B, x -> f(t)))` + */ type FormulaRule = ((Formula, Formula), (FormulaSubstitution, TermSubstitution)) /** - * A lambda representing a term, with inputs as terms. Carries extra - * information about rewrite rules used in its construction for proof - * genration later. - * - * @param termVars variables in the body to be treated as parameters closed - * under this function - * @param termRules mapping to the rules (with instantiations) used to - * construct this function; used for proof construction - * @param body the body of the function - */ + * A lambda representing a term, with inputs as terms. Carries extra + * information about rewrite rules used in its construction for proof + * genration later. + * + * @param termVars variables in the body to be treated as parameters closed + * under this function + * @param termRules mapping to the rules (with instantiations) used to + * construct this function; used for proof construction + * @param body the body of the function + */ case class TermRewriteLambda( termVars: Seq[Variable] = Seq.empty, termRules: Seq[(Variable, TermRule)] = Seq.empty, @@ -279,21 +651,21 @@ object UnificationUtils { ) {} /** - * A lambda representing a formula, with inputs as terms or formulas. Carries - * extra information about rewrite rules used in its construction for proof - * geenration later. - * - * @param termVars variables in the body to be treated as parameters closed - * under this function - * @param formulaVars formula variables in the body to be treated as - * parameters closed under this function - * @param termRules mapping to the term rewrite rules (with instantiations) - * used to construct this function; used for proof construction - * @param formulaRules mapping to the formula rewrite rules (with - * instantiations) used to construct this function; used for proof - * construction - * @param body the body of the function - */ + * A lambda representing a formula, with inputs as terms or formulas. Carries + * extra information about rewrite rules used in its construction for proof + * geenration later. + * + * @param termVars variables in the body to be treated as parameters closed + * under this function + * @param formulaVars formula variables in the body to be treated as + * parameters closed under this function + * @param termRules mapping to the term rewrite rules (with instantiations) + * used to construct this function; used for proof construction + * @param formulaRules mapping to the formula rewrite rules (with + * instantiations) used to construct this function; used for proof + * construction + * @param body the body of the function + */ case class FormulaRewriteLambda( termRules: Seq[(Variable, TermRule)] = Seq.empty, formulaRules: Seq[(VariableFormula, FormulaRule)] = Seq.empty, @@ -301,61 +673,61 @@ object UnificationUtils { ) { /** - * **Unsafe** conversion to a term lambda, discarding rule and formula information - * - * Use if **know that only term rewrites were applied**. - */ + * **Unsafe** conversion to a term lambda, discarding rule and formula information + * + * Use if **know that only term rewrites were applied**. + */ def toLambdaTF: LambdaExpression[Term, Formula, ?] = LambdaExpression(termRules.map(_._1), body, termRules.size) /** - * **Unsafe** conversion to a formula lambda, discarding rule and term information - * - * Use if **know that only formula rewrites were applied**. - */ + * **Unsafe** conversion to a formula lambda, discarding rule and term information + * + * Use if **know that only formula rewrites were applied**. + */ def toLambdaFF: LambdaExpression[Formula, Formula, ?] = LambdaExpression(formulaRules.map(_._1), body, formulaRules.size) } /** - * Dummy connector used to combine formulas for convenience during rewriting - */ + * Dummy connector used to combine formulas for convenience during rewriting + */ val formulaRewriteConnector = SchematicConnectorLabel(Identifier("@@rewritesTo@@"), 2) /** - * Dummy function symbol used to combine terms for convenience during rewriting - */ + * Dummy function symbol used to combine terms for convenience during rewriting + */ val termRewriteConnector = ConstantFunctionLabel(Identifier("@@rewritesTo@@"), 2) /** - * Decides whether a term `first` be rewritten into `second` at the top level - * using the provided rewrite rule (with instantiation). - * - * Reduces to matching using [[matchTermRecursive]]. - */ + * Decides whether a term `first` be rewritten into `second` at the top level + * using the provided rewrite rule (with instantiation). + * + * Reduces to matching using [[matchTermRecursive]]. + */ private def canRewrite(using context: RewriteContext)(first: Term, second: Term, rule: (Term, Term)): Option[TermSubstitution] = matchTermRecursive(termRewriteConnector(first, second), termRewriteConnector(rule._1, rule._2), TermSubstitution.empty) /** - * Decides whether a formula `first` be rewritten into `second` at the top - * level using the provided rewrite rule (with instantiation). Produces the - * instantiation as output, if one exists. - * - * Reduces to matching using [[matchFormulaRecursive]]. - */ + * Decides whether a formula `first` be rewritten into `second` at the top + * level using the provided rewrite rule (with instantiation). Produces the + * instantiation as output, if one exists. + * + * Reduces to matching using [[matchFormulaRecursive]]. + */ private def canRewrite(using context: RewriteContext)(first: Formula, second: Formula, rule: (Formula, Formula)): Option[(FormulaSubstitution, TermSubstitution)] = matchFormulaRecursive(formulaRewriteConnector(first, second), formulaRewriteConnector(rule._1, rule._2), FormulaSubstitution.empty, TermSubstitution.empty) /** - * Decides whether a term `first` can be rewritten into another term `second` - * under the given rewrite rules and restrictions. - * - * Calls [[getContextRecursive]] as its actual implementation. - * - * @param first source term - * @param second destination term - * @param freeTermRules rewrite rules with unrestricted instantiations - * @param confinedTermRules rewrite rules with restricted instantiations wrt takenTermVariables - * @param takenTermVariables variables to *not* instantiate, i.e., treat as constant, for confined rules - */ + * Decides whether a term `first` can be rewritten into another term `second` + * under the given rewrite rules and restrictions. + * + * Calls [[getContextRecursive]] as its actual implementation. + * + * @param first source term + * @param second destination term + * @param freeTermRules rewrite rules with unrestricted instantiations + * @param confinedTermRules rewrite rules with restricted instantiations wrt takenTermVariables + * @param takenTermVariables variables to *not* instantiate, i.e., treat as constant, for confined rules + */ def getContextTerm( first: Term, second: Term, @@ -372,12 +744,12 @@ object UnificationUtils { } /** - * Inner implementation for [[getContextTerm]]. - * - * @param context all information about rewrite rules and allowed instantiations - * @param first source term - * @param second destination term - */ + * Inner implementation for [[getContextTerm]]. + * + * @param context all information about rewrite rules and allowed instantiations + * @param first source term + * @param second destination term + */ private def getContextRecursive(using context: RewriteContext)(first: Term, second: Term): Option[TermRewriteLambda] = { // check if there exists a substitution lazy val validSubstitution = @@ -434,25 +806,25 @@ object UnificationUtils { } /** - * Decides whether a formula `first` can be rewritten into another formula - * `second` under the given rewrite rules and restrictions. - * - * Calls [[getContextRecursive]] as its actual implementation. - * - * @param first source formula - * @param second destination formula - * @param freeTermRules term rewrite rules with unrestricted instantiations - * @param freeFormulaRules formula rewrite rules with unrestricted - * instantiations - * @param confinedTermRules term rewrite rules with restricted instantiations - * wrt takenTermVariables - * @param confinedTermRules formula rewrite rules with restricted - * instantiations wrt takenTermVariables - * @param takenTermVariables term variables to *not* instantiate, i.e., treat - * as constant, for confined rules - * @param takenFormulaVariables formula variables to *not* instantiate, i.e., - * treat as constant, for confined rules - */ + * Decides whether a formula `first` can be rewritten into another formula + * `second` under the given rewrite rules and restrictions. + * + * Calls [[getContextRecursive]] as its actual implementation. + * + * @param first source formula + * @param second destination formula + * @param freeTermRules term rewrite rules with unrestricted instantiations + * @param freeFormulaRules formula rewrite rules with unrestricted + * instantiations + * @param confinedTermRules term rewrite rules with restricted instantiations + * wrt takenTermVariables + * @param confinedTermRules formula rewrite rules with restricted + * instantiations wrt takenTermVariables + * @param takenTermVariables term variables to *not* instantiate, i.e., treat + * as constant, for confined rules + * @param takenFormulaVariables formula variables to *not* instantiate, i.e., + * treat as constant, for confined rules + */ def getContextFormula( first: Formula, second: Formula, @@ -507,12 +879,12 @@ object UnificationUtils { } /** - * Inner implementation for [[getContextFormula]]. - * - * @param context all information about rewrite rules and allowed instantiations - * @param first source formula - * @param second destination formula - */ + * Inner implementation for [[getContextFormula]]. + * + * @param context all information about rewrite rules and allowed instantiations + * @param first source formula + * @param second destination formula + */ private def getContextRecursive(using context: RewriteContext)(first: Formula, second: Formula): Option[FormulaRewriteLambda] = { // check if there exists a substitution lazy val validSubstitution = @@ -615,5 +987,5 @@ object UnificationUtils { } } } - -} + */ +// } diff --git a/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala b/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala index 5b14743b4..01d567da6 100644 --- a/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala +++ b/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala @@ -6,56 +6,43 @@ import lisa.kernel.proof.SCProofCheckerJudgement import lisa.kernel.proof.SCProofCheckerJudgement.SCInvalidProof import lisa.kernel.proof.SequentCalculus.Sequent import lisa.kernel.proof.SequentCalculus.isSameSequent -import lisa.utils.FOLPrinter import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.Printer import org.scalatest.funsuite.AnyFunSuite import scala.language.adhocExtensions -abstract class ProofCheckerSuite(printer: Printer = FOLPrinter) extends AnyFunSuite { +abstract class ProofCheckerSuite extends AnyFunSuite { import lisa.kernel.fol.FOL.* - protected val (xl, yl, zl, wl, xpl, ypl, zpl, wpl) = ( - VariableLabel("x"), - VariableLabel("y"), - VariableLabel("z"), - VariableLabel("w"), - VariableLabel("x1"), - VariableLabel("y1"), - VariableLabel("z1"), - VariableLabel("w1") - ) protected val (x, y, z, w, xp, yp, zp, wp) = ( - VariableTerm(xl), - VariableTerm(yl), - VariableTerm(zl), - VariableTerm(wl), - VariableTerm(xpl), - VariableTerm(ypl), - VariableTerm(zpl), - VariableTerm(wpl) + Variable("x", Term), + Variable("y", Term), + Variable("z", Term), + Variable("w", Term), + Variable("x1", Term), + Variable("y1", Term), + Variable("z1", Term), + Variable("w1", Term) ) - 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)) + protected val (s, t, u, v) = (Variable("s", Term), Variable("t", Term), Variable("u", Term), Variable("v", Term)) def checkProof(proof: SCProof): Unit = { val judgement = checkSCProof(proof) - assert(judgement.isValid, printer.prettySCProof(judgement, true)) + assert(judgement.isValid, prettySCProof(judgement, true)) } def checkProof(proof: SCProof, expected: Sequent): Unit = { val judgement = checkSCProof(proof) - assert(judgement.isValid, "\n" + printer.prettySCProof(judgement)) - assert(isSameSequent(proof.conclusion, expected), s"(${printer.prettySequent(proof.conclusion)} did not equal ${printer.prettySequent(expected)})") + assert(judgement.isValid, "\n" + prettySCProof(judgement)) + assert(isSameSequent(proof.conclusion, expected), s"(${proof.conclusion.repr} did not equal ${expected.repr})") } - def checkIncorrectProof(incorrectProof: SCProof): Unit = { + inline def checkIncorrectProof(incorrectProof: SCProof): Unit = { assert( !checkSCProof(incorrectProof).isValid, - s"(incorrect proof with conclusion '${printer.prettySequent(incorrectProof.conclusion)}' was accepted by the proof checker)\nSequent: ${incorrectProof.conclusion}" + s"(incorrect proof with conclusion '${incorrectProof.conclusion.repr}' was accepted by the proof checker)\nSequent: ${incorrectProof.conclusion}" ) } } diff --git a/lisa-utils/src/test/scala/lisa/ProofTacticTestLib.scala b/lisa-utils/src/test/scala/lisa/ProofTacticTestLib.scala index ebecd9b39..3c6db36af 100644 --- a/lisa-utils/src/test/scala/lisa/ProofTacticTestLib.scala +++ b/lisa-utils/src/test/scala/lisa/ProofTacticTestLib.scala @@ -8,13 +8,14 @@ import lisa.prooflib.ProofTacticLib import org.scalatest.funsuite.AnyFunSuite import scala.collection.immutable.LazyList +import leo.datastructures.TPTP.FOF.Term trait ProofTacticTestLib extends AnyFunSuite with BasicMain { export lisa.test.TestTheoryLibrary.{_, given} - private val x: lisa.fol.FOL.Variable = variable - private val P = predicate[1] + private val x = variable[Term] + private val P = variable[Term >>: Formula] // generate a placeholde theorem to take ownership of proofs for test val placeholderTheorem: THMFromProof = Theorem(P(x) |- P(x)) { have(P(x) |- P(x)) by Hypothesis }.asInstanceOf diff --git a/lisa-utils/src/test/scala/lisa/TestTheoryAxioms.scala b/lisa-utils/src/test/scala/lisa/TestTheoryAxioms.scala index aeaa403d3..259ad336e 100644 --- a/lisa-utils/src/test/scala/lisa/TestTheoryAxioms.scala +++ b/lisa-utils/src/test/scala/lisa/TestTheoryAxioms.scala @@ -5,11 +5,11 @@ import lisa.kernel.proof.RunningTheory import lisa.utils.KernelHelpers.{_, given} trait TestTheoryAxioms { - final val p1 = ConstantAtomicLabel("p1", 1) - final val p2 = ConstantAtomicLabel("p2", 1) - final val f1 = ConstantFunctionLabel("f1", 1) - final val fixedElement = ConstantFunctionLabel("fixedElement", 0) - final val anotherFixed = ConstantFunctionLabel("anotherElement", 0) + final val p1 = Constant("p1", Arrow(Term, Formula)) + final val p2 = Constant("p2", Arrow(Term, Formula)) + final val f1 = Constant("f1", Arrow(Term, Term)) + final val fixedElement = Constant("fixedElement", Term) + final val anotherFixed = Constant("anotherElement", Term) val runningTestTheory = new RunningTheory() runningTestTheory.addSymbol(p1) @@ -18,11 +18,11 @@ trait TestTheoryAxioms { runningTestTheory.addSymbol(fixedElement) runningTestTheory.addSymbol(anotherFixed) - private final val x = VariableLabel("x") - final val p1_implies_p2_f: Formula = forall(x, p1(x) ==> p2(x)) - final val ax2_f = p1(fixedElement()) - final val same_fixed_f = fixedElement() === anotherFixed() - final val fixed_point_f = forall(x, (f1(x) === fixedElement()) <=> (x === fixedElement())) + private final val x = Variable("x", Term) + final val p1_implies_p2_f = forall(x, p1(x) ==> p2(x)) + final val ax2_f = p1(fixedElement) + final val same_fixed_f = fixedElement === anotherFixed + final val fixed_point_f = forall(x, (f1(x) === fixedElement) <=> (x === fixedElement)) val p1_implies_p2 = runningTestTheory.addAxiom("p1_implies_p2", p1_implies_p2_f).get val A2 = runningTestTheory.addAxiom("A2", ax2_f).get diff --git a/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala b/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala index 87b0cf73c..f3446d044 100644 --- a/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala +++ b/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala @@ -7,26 +7,29 @@ object TestTheoryLibrary extends Library { export lisa.fol.FOL.{*, given} - final val p1 = ConstantPredicateLabel("p1", 1) - final val p2 = ConstantPredicateLabel("p2", 1) - final val f1 = ConstantFunctionLabel("f1", 1) - final val fixedElement = Constant("fixedElement") - final val anotherFixed = Constant("anotherElement") - + final val p1 = constant[Term >>: Formula] + final val p2 = constant[Term >>: Formula] + final val f1 = constant[Term >>: Term] + final val fixedElement = constant[Term] + final val anotherElement = constant[Term] addSymbol(p1) addSymbol(p2) addSymbol(f1) addSymbol(fixedElement) - addSymbol(anotherFixed) + addSymbol(anotherElement) - private final val x = Variable("x") + private final val x = variable[Term] final val p1_implies_p2_f: Formula = forall(x, p1(x) ==> p2(x)) final val ax2 = p1(fixedElement) - final val same_fixed_f = fixedElement === anotherFixed + final val same_fixed_f = fixedElement === anotherElement final val fixed_point_f = forall(x, (f1(x) === fixedElement) <=> (x === fixedElement)) val p1_implies_p2 = AXIOM(TestTheory.p1_implies_p2, p1_implies_p2_f, "p1_implies_p2") val A2 = AXIOM(TestTheory.A2, ax2, "A2") + println(s"TestTheory.same_fixed: ${TestTheory.same_fixed}") + println(s"same_fixed_f : ${same_fixed_f}") + println(s"same_fixed_f.underlying : ${same_fixed_f.underlying}") + println(s"TestTheory.same_fixed.ax : ${TestTheory.same_fixed.ax}") val same_fixed = AXIOM(TestTheory.same_fixed, same_fixed_f, "same_fixed") val fixed_point = AXIOM(TestTheory.fixed_point, fixed_point_f, "fixed_point") diff --git a/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala b/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala index 81a39258f..002c55ae5 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala @@ -2,7 +2,6 @@ package lisa.kernel import lisa.kernel.fol.FOL import lisa.kernel.fol.FOL.* -import lisa.utils.FOLPrinter import lisa.utils.KernelHelpers._ import lisa.utils.KernelHelpers.given_Conversion_Identifier_String import lisa.utils.KernelHelpers.given_Conversion_String_Identifier @@ -17,17 +16,17 @@ 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 = { + def checkEquivalence(left: Expression, right: Expression): Unit = { assert( isSame(left, right), - s"Couldn't prove the equivalence between ${FOLPrinter.prettyFormula(left)} and ${FOLPrinter.prettyFormula(right)}\nLeft tree: ${left}\nRight tree: ${right}" + s"Couldn't prove the equivalence between ${left.repr} and ${right.repr}\nLeft tree: ${left}\nRight tree: ${right}" ) } - def checkNonEquivalence(left: Formula, right: Formula): Unit = { + def checkNonEquivalence(left: Expression, right: Expression): Unit = { assert( !isSame(left, right), - s"Expected the checker to not be able to show equivalence between ${FOLPrinter.prettyFormula(left)} and ${FOLPrinter.prettyFormula(right)}\nLeft tree: ${left}\nRight tree: ${right}" + s"Expected the checker to not be able to show equivalence between ${left.repr} and ${right.repr}\nLeft tree: ${left}\nRight tree: ${right}" ) } @@ -41,6 +40,7 @@ class EquivalenceCheckerTests extends AnyFunSuite { id } } + def numbersGenerator(): () => Int = { var i = 1 () => { @@ -50,15 +50,15 @@ class EquivalenceCheckerTests extends AnyFunSuite { } } - def constantsGenerator(): () => Formula = { + def constantsGenerator(): () => Expression = { val generator = nameGenerator() () => { val id = generator() - AtomicFormula(ConstantAtomicLabel(id, 0), Seq.empty) + Constant(id, Formula) } } - def formulasGenerator(c: Double)(random: Random): () => Formula = { + def ExpressionsGenerator(c: Double)(random: Random): () => Expression = { val connectors = ArrayBuffer.empty[String] val variables = ArrayBuffer.empty[String] val nextConnectorName = nameGenerator() @@ -66,7 +66,7 @@ class EquivalenceCheckerTests extends AnyFunSuite { val gen = numbersGenerator() () => s"v${gen()}" } - def generate(p: Double): Formula = { + def generate(p: Double): Expression = { val q = random.nextDouble() if (q >= p) { @@ -81,7 +81,7 @@ class EquivalenceCheckerTests extends AnyFunSuite { // Reuse existing name connectors(random.nextInt(connectors.size)) } - AtomicFormula(ConstantAtomicLabel(name, 0), Seq.empty) + Constant(name, Formula) } else { // Branch val nextP = p * c @@ -105,9 +105,9 @@ class EquivalenceCheckerTests extends AnyFunSuite { // Binder val name = nextVariableName() variables += name - val binderTypes: IndexedSeq[BinderLabel] = IndexedSeq(Forall, Exists, ExistsOne) + val binderTypes = IndexedSeq(forall, exists) val binderType = binderTypes(random.nextInt(binderTypes.size)) - BinderFormula(binderType, VariableLabel(name), generate(nextP)) + binderType(lambda(Variable(name, Term), generate(nextP))) } } } @@ -115,10 +115,10 @@ class EquivalenceCheckerTests extends AnyFunSuite { () => generate(c) } - def testcasesAny(generatorToTestcases: (() => Formula) => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = { + def testcasesAny(generatorToTestcases: (() => Expression) => Random => Seq[(Expression, Expression)], equivalent: Boolean): Unit = { val random: Random = new Random(1) - def testWith(generator: () => () => Formula): Unit = { + def testWith(generator: () => () => Expression): Unit = { val cases = generatorToTestcases(generator())(random) cases.foreach { (left, right) => // For completeness we also test symmetry @@ -126,19 +126,19 @@ class EquivalenceCheckerTests extends AnyFunSuite { checkEquivalence(left, right) checkEquivalence(right, left) if (verbose) { - println(s"${FOLPrinter.prettyFormula(left)} <==> ${FOLPrinter.prettyFormula(right)}") + println(s"${left.repr} <==> ${right.repr}") } } else { checkNonEquivalence(left, right) checkNonEquivalence(right, left) if (verbose) { - println(s"${FOLPrinter.prettyFormula(left)} ${FOLPrinter.prettyFormula(right)}") + println(s"${left.repr} ${right.repr}") } } } } - def testWithRepeat(generator: () => () => Formula, n: Int): Unit = { + def testWithRepeat(generator: () => () => Expression, n: Int): Unit = { for (i <- 0 until n) { testWith(generator) } @@ -148,80 +148,64 @@ class EquivalenceCheckerTests extends AnyFunSuite { testWith(constantsGenerator) - // 2. Random formulas (small) + // 2. Random Expressions (small) - testWithRepeat(() => formulasGenerator(0.8)(random), 5) + testWithRepeat(() => ExpressionsGenerator(0.8)(random), 5) - // 3. Random formulas (larger) + // 3. Random Expressions (larger) - testWithRepeat(() => formulasGenerator(0.90)(random), 15) + testWithRepeat(() => ExpressionsGenerator(0.90)(random), 15) } - def testcases(f: Formula => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = + def testcases(f: Expression => Random => Seq[(Expression, Expression)], equivalent: Boolean): Unit = testcasesAny(generator => r => f(generator())(r), equivalent) - def testcases(f: (Formula, Formula) => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = + def testcases(f: (Expression, Expression) => Random => Seq[(Expression, Expression)], equivalent: Boolean): Unit = testcasesAny(generator => r => f(generator(), generator())(r), equivalent) - def testcases(f: (Formula, Formula, Formula) => Random => Seq[(Formula, Formula)], equivalent: Boolean): Unit = + def testcases(f: (Expression, Expression, Expression) => Random => Seq[(Expression, Expression)], 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 = + def testcases(f: (Expression, Expression, Expression, Expression) => Random => Seq[(Expression, Expression)], 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 AtomicFormula(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)) + def commutativeShuffle(iterations: Int)(random: Random)(f: Expression): Expression = { + def transform(f: Expression): Expression = f match { + case And(a, b) => if random.nextBoolean() then and(transform(a), transform(b)) else and(transform(b), transform(a)) + case Or(a, b) => if random.nextBoolean() then or(transform(a), transform(b)) else or(transform(b), transform(a)) + case Iff(a, b) => if random.nextBoolean() then iff(transform(a), transform(b)) else iff(transform(b), transform(a)) + case Application(f, arg) => Application(transform(f), transform(arg)) + case Lambda(v, body) => Lambda(v, transform(body)) + case _ => f } repeatApply(iterations)(transform)(f) } - def associativeShuffle(iterations: Int)(random: Random)(f: Formula): Formula = { - def transform(f: Formula): Formula = f match { - case AtomicFormula(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)) + def associativeShuffle(iterations: Int)(random: Random)(f: Expression): Expression = { + def transform(f: Expression): Expression = f match { + case And(And(a, b), c) => if (random.nextBoolean()) and(transform(a), and(transform(b), transform(c))) else and(and(transform(a), transform(b)), transform(c)) + case Or(Or(a, b), c) => if (random.nextBoolean()) or(transform(a), or(transform(b), transform(c))) else or(or(transform(a), transform(b)), transform(c)) + case Application(f, arg) => Application(transform(f), transform(arg)) + case Lambda(v, body) => Lambda(v, transform(body)) + case _ => f } 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))) + def addDoubleNegations(p: Double)(random: Random)(f: Expression): Expression = { + def transform(f: Expression): Expression = + if (random.nextDouble() < p && f.sort == Formula) neg(neg(transform(f))) else f match { - case _: AtomicFormula => f - case ConnectorFormula(label, args) => ConnectorFormula(label, args.map(transform)) - case BinderFormula(label, bound, inner) => BinderFormula(label, bound, transform(inner)) + case Application(f, arg) => Application(transform(f), transform(arg)) + case Lambda(v, body) => Lambda(v, transform(body)) + case _ => f } transform(f) } - def addDeMorgans(p: Double)(random: Random)(f: Formula): Formula = { - def transform(f: Formula): Formula = f match { - case _: AtomicFormula => 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)) + def addDeMorgans(p: Double)(random: Random)(f: Expression): Expression = { + def transform(f: Expression): Expression = f match { + case And(a, b) => if random.nextBoolean() then !or(!transform(a), !transform(b)) else and(transform(b), transform(a)) + case Or(a, b) => if random.nextBoolean() then !and(!transform(a), !transform(b)) else or(transform(b), transform(a)) + case Application(f, arg) => Application(transform(f), transform(arg)) + case Lambda(v, body) => Lambda(v, transform(body)) + case _ => f } transform(f) } @@ -364,13 +348,13 @@ class EquivalenceCheckerTests extends AnyFunSuite { } test("All allowed transformations") { - val transformations: Seq[Random => Formula => Formula] = IndexedSeq( + val transformations: Seq[Random => Expression => Expression] = 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 = { + def randomTransformations(random: Random)(f: Expression): Expression = { val n = random.nextInt(50) Seq.fill(n)(transformations(random.nextInt(transformations.size))).foldLeft(f)((acc, e) => e(random)(acc)) } diff --git a/lisa-utils/src/test/scala/lisa/kernel/FolTests.scala b/lisa-utils/src/test/scala/lisa/kernel/FolTests.scala deleted file mode 100644 index bc769dd8c..000000000 --- a/lisa-utils/src/test/scala/lisa/kernel/FolTests.scala +++ /dev/null @@ -1,119 +0,0 @@ -package lisa.kernel - -import lisa.kernel.fol.FOL.* -import lisa.kernel.proof.RunningTheory -import lisa.kernel.proof.RunningTheory.* -import lisa.kernel.proof.SCProof -import lisa.kernel.proof.SCProofChecker -import lisa.kernel.proof.SequentCalculus.* -import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.Printer -import org.scalatest.funsuite.AnyFunSuite - -import scala.collection.immutable.SortedSet -import scala.language.adhocExtensions -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)) - Term(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)) - Term(ConstantFunctionLabel(name, 0), List()) - } else if (r == 1) { - val name = "" + ('v' to 'z')(gen.between(0, 5)) - VariableTerm(VariableLabel(name)) - } - if (r <= 3) Term(ConstantFunctionLabel(name, 1), Seq(termGenerator(maxDepth - 1, gen))) - else if (r <= 5) Term(ConstantFunctionLabel(name, 2), Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) - else if (r == 6) Term(ConstantFunctionLabel(name, 3), Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) - else - Term( - 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)) - AtomicFormula(ConstantAtomicLabel(name, 0), Seq()) - } else if (r <= 3) { - val name = "" + ('A' to 'E')(gen.between(0, 5)) - AtomicFormula(ConstantAtomicLabel(name, 1), Seq(termGenerator(maxDepth - 1, gen))) - } else if (r <= 5) { - val s = gen.between(0, 3) - if (s == 0) AtomicFormula(equality, Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) - else { - val name = "" + ('A' to 'E')(gen.between(0, 5)) - AtomicFormula(ConstantAtomicLabel(name, 2), Seq(termGenerator(maxDepth - 1, gen), termGenerator(maxDepth - 1, gen))) - } - } else { - val name = "" + ('A' to 'E')(gen.between(0, 5)) - AtomicFormula(ConstantAtomicLabel(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 = AtomicFormula(ConstantAtomicLabel("A", 0), Seq()) - private val b = AtomicFormula(ConstantAtomicLabel("B", 0), Seq()) - private val fp = ConstantAtomicLabel("F", 1) - private val sT = VariableLabel("t") - - 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(lisa.kernel.fol.FOL.freshId(equality(VariableTerm(x), VariableTerm(x)).freeVariables.map(_.name), x.name)) - - assert(!(x.id == y1.id)) - } -} diff --git a/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala b/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala index 8a9ba63a1..bbdf89599 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala @@ -19,10 +19,7 @@ class IncorrectProofsTests extends ProofCheckerSuite { // Shorthand implicit def proofStepToProof(proofStep: SCProofStep): SCProof = SCProof(proofStep) - val (fl, gl, hl) = (VariableFormulaLabel("f"), VariableFormulaLabel("g"), VariableFormulaLabel("h")) - val f = AtomicFormula(fl, Seq.empty) // Some arbitrary formulas - val g = AtomicFormula(gl, Seq.empty) - val h = AtomicFormula(hl, Seq.empty) + val (f, g, h) = (Variable("f", Formula), Variable("g", Formula), Variable("h", Formula)) val incorrectProofs: Seq[SCProof] = List( SCProof( @@ -40,23 +37,23 @@ class IncorrectProofsTests extends ProofCheckerSuite { SCProof( Hypothesis(emptySeq +<< (x === y) +>> (x === y), x === y), - RightSubstEq(emptySeq +<< (x === y) +<< (x === z) +>> (z === y), 0, List(((LambdaTermTerm(Seq(), x), LambdaTermTerm(Seq(), z)))), (Seq(yl), x === y)) // wrong variable replaced + RightSubstEq(emptySeq +<< (x === y) +<< (x === z) +>> (z === y), 0, Seq((x, z)),(Seq(y), x === y)) // wrong variable replaced ), SCProof( Hypothesis(emptySeq +<< (x === y) +>> (x === y), x === y), - RightSubstEq(emptySeq +<< (x === y) +>> (z === y), 0, List(((LambdaTermTerm(Seq(), x), LambdaTermTerm(Seq(), z)))), (Seq(xl), x === y)) // missing hypothesis + RightSubstEq(emptySeq +<< (x === y) +>> (z === y), 0, Seq((x, z)), (Seq(x), x === y)) // missing hypothesis ), SCProof( Hypothesis(emptySeq +<< (x === y) +>> (x === y), x === y), - RightSubstEq(emptySeq +<< (x === y) +<< (x === z) +>> (z === y), 0, List(((LambdaTermTerm(Seq(), x), LambdaTermTerm(Seq(), z)))), (Seq(xl), x === z)) // replacement mismatch + RightSubstEq(emptySeq +<< (x === y) +<< (x === z) +>> (z === y), 0, Seq((x, z)), (Seq(x), x === z)) // replacement mismatch ), SCProof( Hypothesis(emptySeq +<< (x === y) +>> (x === y), x === y), - LeftSubstEq(emptySeq +<< (z === y) +<< (x === z) +>> (x === y), 0, List(((LambdaTermTerm(Seq(), x), LambdaTermTerm(Seq(), z)))), (Seq(yl), x === y)) + LeftSubstEq(emptySeq +<< (z === y) +<< (x === z) +>> (x === y), 0, Seq((x, z)), (Seq(y), x === y)) ), SCProof( Hypothesis(emptySeq +<< (f <=> g) +>> (f <=> g), f <=> g), - LeftSubstIff(emptySeq +<< (h <=> g) +<< (f <=> h) +>> (f <=> g), 0, List(((LambdaTermFormula(Seq(), f), LambdaTermFormula(Seq(), h)))), (Seq(gl), f <=> g)) + LeftSubstIff(emptySeq +<< (h <=> g) +<< (f <=> h) +>> (f <=> g), 0, Seq((f, h)), (Seq(g), f <=> g)) ), SCProof( Hypothesis(emptySeq +<< f +>> f, f), diff --git a/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala b/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala index f86a18137..0a9b5d7fb 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala @@ -8,7 +8,6 @@ import lisa.kernel.proof.SCProofChecker import lisa.kernel.proof.SCProofChecker.checkSCProof import lisa.kernel.proof.SequentCalculus.* import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.Printer import org.scalatest.funsuite.AnyFunSuite import scala.language.adhocExtensions @@ -16,23 +15,23 @@ import scala.util.Random class ProofTests extends AnyFunSuite { - private val x = VariableLabel("x") - private val y = VariableLabel("y") - private val z = VariableLabel("z") - val f = SchematicFunctionLabel("f", 1) - val f2 = SchematicFunctionLabel("f2", 1) - val g = ConstantFunctionLabel("g", 2) - val g2 = SchematicFunctionLabel("g2", 2) - private val a = AtomicFormula(ConstantAtomicLabel("A", 0), Seq()) - private val b = AtomicFormula(ConstantAtomicLabel("B", 0), Seq()) - private val fp = ConstantAtomicLabel("F", 1) - val sT = VariableLabel("t") + private val x = variable + private val y = variable + private val z = variable + val f = function(1) + val f2 = function(1) + val g = cst("g", Term >>: Term >>: Term) + val g2 = function(2) + private val a = cst("A", Formula) + private val b = cst("A", Formula) + private val fp = cst("F", Term >>: Formula) + val sT = variable("t") - val X = VariableFormulaLabel("X") - val P = SchematicPredicateLabel("f", 1) - val P2 = SchematicPredicateLabel("f2", 1) - val Q = ConstantAtomicLabel("g", 2) - val Q2 = SchematicPredicateLabel("g2", 2) + val X = formulaVariable("X") + val P = predicate(1) + val P2 = predicate(1) + val Q = cst(Term >>: Term >>: Formula) + val Q2 = predicate(2) test("Verification of Pierce law") { val s0 = Hypothesis(a |- a, a) @@ -47,203 +46,259 @@ class ProofTests extends AnyFunSuite { test("Verification of LeftSubstEq") { { val t0 = Hypothesis(fp(x) |- fp(x), fp(x)) - val t1 = LeftSubstEq(Set(fp(y), x === y) |- fp(x), 0, List(((LambdaTermTerm(Seq(), x), LambdaTermTerm(Seq(), y)))), (Seq(sT), fp(sT))) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t1 = Hypothesis(x === y |- x === y, x === y) + val t2 = LeftSubstEq(Set(fp(y), x === y) |- fp(x), 0, Seq((x, y)), (Seq(sT), fp(sT))) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) val t1 = LeftSubstEq( - Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), + Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === lambda(x, g(x, x))(y))) |- exists(x, fp(f(x))), 0, - List((LambdaTermTerm(Seq(x), f(x)), LambdaTermTerm(Seq(x), g(x, x)))), + Seq((f, lambda(x, g(x, x)))), (Seq(f2), exists(x, fp(f2(x)))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t2 = Beta( + Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), + 1 + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) val t1 = LeftSubstEq( - Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), + Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === lambda(x, g(x, x))(y))) |- exists(x, fp(f(x))), 0, - List((LambdaTermTerm(Seq(y), f(y)), LambdaTermTerm(Seq(z), g(z, z)))), + Seq((f, lambda(z, g(z, z)))), (Seq(f2), exists(x, fp(f2(x)))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t2 = Beta( + Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), + 1 + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, forall(y, fp(g(y, g(x, z))))) |- exists(x, forall(y, fp(g(y, g(x, z))))), exists(x, forall(y, fp(g(y, g(x, z)))))) val t1 = LeftSubstEq( - Set(exists(x, forall(y, fp(g(g(x, z), y)))), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(y, g(x, z))))), + Set(exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(y, g(x, z))))), forall(y, forall(z, g(y, z) === lambda(Seq(y, z), g(z, y))(y, z)))) |- exists(x, forall(y, fp(g(y, g(x, z))))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y)))), + Seq((g, lambda(Seq(y, z), g(z, y)))), (Seq(g2), exists(x, forall(y, fp(g2(y, g(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, forall(y, fp(g(y, g(x, z))))) |- exists(x, forall(y, fp(g(y, g(x, z))))), exists(x, forall(y, fp(g(y, g(x, z)))))) val t1 = LeftSubstEq( - Set(exists(x, forall(y, fp(g(g(z, x), y)))), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(y, g(x, z))))), + Set( + exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(y, lambda(Seq(y, z), g(z, y))(x, z))))), + forall(y, forall(z, g(y, z) === lambda(Seq(y, z), g(z, y))(y, z))) + ) |- exists(x, forall(y, fp(g(y, g(x, z))))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y)))), + Seq((g, lambda(Seq(y, z), g(z, y)))), (Seq(g2), exists(x, forall(y, fp(g2(y, g2(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1))) + assert(judg.isValid, "\n" + judg.repr) + } + // + { + val t0 = Hypothesis(P(x) |- P(x), P(x)) + val t1 = Hypothesis(P(x) <=> P(y) |- P(x) <=> P(y), P(x) <=> P(y)) + val t2 = LeftSubstIff(Set(P(y), P(x) <=> P(y)) |- P(x), 0, Seq((P(x), P(y))), (Seq(X), X)) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) + } + { + val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) + val t1 = LeftSubstIff( + Set(exists(x, lambda(x, Q(x, x))(x)), forall(y, P(y) <=> lambda(x, Q(x, x))(y))) |- exists(x, P(x)), + 0, + Seq((P, lambda(x, Q(x, x)))), + (Seq(P2), exists(x, P2(x))) + ) + val t2 = Beta( + Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), + 1 + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) + } + { + val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) + val t1 = LeftSubstIff( + Set(exists(x, lambda(z, Q(z, z))(x)), forall(y, P(y) <=> lambda(x, Q(x, x))(y))) |- exists(x, P(x)), + 0, + Seq((P, lambda(z, Q(z, z)))), + (Seq(P2), exists(x, P2(x))) + ) + val t2 = Beta( + Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), + 1 + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) + } + { + val t0 = Hypothesis(exists(x, forall(y, Q(y, g(x, z)))) |- exists(x, forall(y, Q(y, g(x, z)))), exists(x, forall(y, Q(y, g(x, z))))) + val t1 = LeftSubstIff( + Set(exists(x, forall(y, lambda(Seq(y, z), Q(z, y))(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> lambda(Seq(y, z), Q(z, y))(x, y)))) |- exists(x, forall(y, Q(y, g(x, z)))), + 0, + Seq((Q, lambda(Seq(y, z), Q(z, y)))), + (Seq(Q2), exists(x, forall(y, Q2(y, g(x, z))))) + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, forall(y, fp(g(y, g(x, f(z)))))) |- exists(x, forall(y, fp(g(y, g(x, f(z)))))), exists(x, forall(y, fp(g(y, g(x, f(z))))))) val t1 = LeftSubstEq( - Set(exists(x, forall(y, fp(g(g(g(z, z), x), y)))), forall(y, f(y) === g(y, y)), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(y, g(x, f(z)))))), + Set(exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(y, lambda(Seq(y, z), g(z, y))(x, lambda(Seq(z), g(z, z))(z)))))), forall(y, f(y) === lambda(Seq(z), g(z, z))(y)), forall(y, forall(z, g(y, z) === lambda(Seq(y, z), g(z, y))(y, z)))) |- exists(x, forall(y, fp(g(y, g(x, f(z)))))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y))), (LambdaTermTerm(Seq(y), f(y)), LambdaTermTerm(Seq(z), g(z, z)))), + List((g, lambda(Seq(y, z), g(z, y))), (f, lambda(Seq(z), g(z, z)))), (Seq(g2, f2), exists(x, forall(y, fp(g2(y, g2(x, f2(z))))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t2 = Beta(Set(exists(x, forall(y, fp(g(g(g(z, z), x), y)))), forall(y, f(y) === g(y, y)), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(y, g(x, f(z)))))), 1) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } } test("Verification of RightSubstEq") { { val t0 = Hypothesis(fp(x) |- fp(x), fp(x)) - val t1 = RightSubstEq(Set(fp(x), x === y) |- fp(y), 0, List(((LambdaTermTerm(Seq(), x), LambdaTermTerm(Seq(), y)))), (Seq(sT), fp(sT))) + val t1 = Hypothesis(x === y |- x === y, x === y) + val t2 = RightSubstEq(Set(fp(x), x === y) |- fp(y), 0, Seq((x, y)), (Seq(sT), fp(sT))) assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) val t1 = RightSubstEq( - Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), + Set(exists(x, fp(f(x))), forall(y, f(y) === lambda(x, g(x, x))(y))) |- exists(x, fp(lambda(x, g(x, x))(x))), 0, - List((LambdaTermTerm(Seq(x), f(x)), LambdaTermTerm(Seq(x), g(x, x)))), + Seq((f, lambda(x, g(x, x)))), (Seq(f2), exists(x, fp(f2(x)))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t2 = Beta( + Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), + 1 + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) val t1 = RightSubstEq( - Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), + Set(exists(x, fp(f(x))), forall(y, f(y) === lambda(x, g(x, x))(y))) |- exists(x, fp(lambda(z, g(z, z))(x))), 0, - List((LambdaTermTerm(Seq(y), f(y)), LambdaTermTerm(Seq(z), g(z, z)))), + Seq((f, lambda(z, g(z, z)))), (Seq(f2), exists(x, fp(f2(x)))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t2 = Beta( + Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), + 1 + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, forall(y, fp(g(y, g(x, z))))) |- exists(x, forall(y, fp(g(y, g(x, z))))), exists(x, forall(y, fp(g(y, g(x, z)))))) val t1 = RightSubstEq( - Set(exists(x, forall(y, fp(g(y, g(x, z))))), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(g(x, z), y)))), + Set( + exists(x, forall(y, fp(g(y, g(x, z))))), + forall(y, forall(z, g(y, z) ===lambda(Seq(y, z), g(z, y))(y, z))) + ) |- exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(y, g(x, z))))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y)))), + Seq((g, lambda(Seq(y, z), g(z, y)))), (Seq(g2), exists(x, forall(y, fp(g2(y, g(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, forall(y, fp(g(y, g(x, z))))) |- exists(x, forall(y, fp(g(y, g(x, z))))), exists(x, forall(y, fp(g(y, g(x, z)))))) val t1 = RightSubstEq( - Set(exists(x, forall(y, fp(g(y, g(x, z))))), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(g(z, x), y)))), + Set( + exists(x, forall(y, fp(g(y, g(x, z))))), + forall(y, forall(z, g(y, z) === lambda(Seq(y, z), g(z, y))(y, z))) + ) |- exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(y, lambda(Seq(y, z), g(z, y))(x, z))))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y)))), + Seq((g, lambda(Seq(y, z), g(z, y)))), (Seq(g2), exists(x, forall(y, fp(g2(y, g2(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1))) + assert(judg.isValid, "\n" + judg.repr) } - { - val t0 = Hypothesis(exists(x, forall(y, fp(g(y, g(x, f(z)))))) |- exists(x, forall(y, fp(g(y, g(x, f(z)))))), exists(x, forall(y, fp(g(y, g(x, f(z))))))) - val t1 = RightSubstEq( - Set(exists(x, forall(y, fp(g(y, g(x, f(z)))))), forall(y, f(y) === g(y, y)), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(g(g(z, z), x), y)))), - 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y))), (LambdaTermTerm(Seq(y), f(y)), LambdaTermTerm(Seq(z), g(z, z)))), - (Seq(g2, f2), exists(x, forall(y, fp(g2(y, g2(x, f2(z))))))) - ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) - } - - } - - test("Verification of LeftSubstIff") { + // { val t0 = Hypothesis(P(x) |- P(x), P(x)) - val t1 = LeftSubstIff(Set(P(y), P(x) <=> P(y)) |- P(x), 0, List(((LambdaTermFormula(Seq(), P(x)), LambdaTermFormula(Seq(), P(y))))), (Seq(X), X)) - val pr = new SCProof(IndexedSeq(t0, t1)) - assert(checkSCProof(pr).isValid) - } - { - val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) - val t1 = LeftSubstIff( - Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), - 0, - List((LambdaTermFormula(Seq(x), P(x)), LambdaTermFormula(Seq(x), Q(x, x)))), - (Seq(P2), exists(x, P2(x))) - ) + val t1 = RightSubstIff(Set(P(x), P(x) <=> P(y)) |- P(y), 0, Seq((P(x), P(y))), (Seq(X), X)) assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) } { val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) - val t1 = LeftSubstIff( - Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), + val t1 = RightSubstIff( + Set(exists(x, P(x)), forall(y, P(y) <=> lambda(x, Q(x, x))(y))) |- exists(x, lambda(x, Q(x, x))(x)), 0, - List((LambdaTermFormula(Seq(y), P(y)), LambdaTermFormula(Seq(z), Q(z, z)))), + Seq((P, lambda(x, Q(x, x)))), (Seq(P2), exists(x, P2(x))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) - } - { - val t0 = Hypothesis(exists(x, forall(y, Q(y, g(x, z)))) |- exists(x, forall(y, Q(y, g(x, z)))), exists(x, forall(y, Q(y, g(x, z))))) - val t1 = LeftSubstIff( - Set(exists(x, forall(y, Q(g(x, z), y))), forall(x, forall(y, Q(x, y) <=> Q(y, x)))) |- exists(x, forall(y, Q(y, g(x, z)))), - 0, - List((LambdaTermFormula(Seq(y, z), Q(y, z)), LambdaTermFormula(Seq(y, z), Q(z, y)))), - (Seq(Q2), exists(x, forall(y, Q2(y, g(x, z))))) + val t2 = Beta( + Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), + 1 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) - } - - } - - test("Verification of RightSubstIff") { - { - val t0 = Hypothesis(P(x) |- P(x), P(x)) - val t1 = RightSubstIff(Set(P(x), P(x) <=> P(y)) |- P(y), 0, List(((LambdaTermFormula(Seq(), P(x)), LambdaTermFormula(Seq(), P(y))))), (Seq(X), X)) - val pr = new SCProof(IndexedSeq(t0, t1)) - assert(checkSCProof(pr).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } { val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) val t1 = RightSubstIff( - Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), + Set(exists(x, P(x)), forall(y, P(y) <=> lambda(x, Q(x, x))(y))) |- exists(x, lambda(z, Q(z, z))(x)), 0, - List((LambdaTermFormula(Seq(x), P(x)), LambdaTermFormula(Seq(x), Q(x, x)))), + Seq((P, lambda(z, Q(z, z)))), (Seq(P2), exists(x, P2(x))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t2 = Beta( + Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), + 1 + ) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } { - val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) + val t0 = Hypothesis(exists(x, forall(y, Q(y, g(x, z)))) |- exists(x, forall(y, Q(y, g(x, z)))), exists(x, forall(y, Q(y, g(x, z))))) val t1 = RightSubstIff( - Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), + Set(exists(x, forall(y, Q(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> lambda(Seq(y, z), Q(z, y))(x, y)))) |- exists(x, forall(y, lambda(Seq(y, z), Q(z, y))(y, g(x, z)))), 0, - List((LambdaTermFormula(Seq(y), P(y)), LambdaTermFormula(Seq(z), Q(z, z)))), - (Seq(P2), exists(x, P2(x))) + Seq((Q, lambda(Seq(y, z), Q(z, y)))), + (Seq(Q2), exists(x, forall(y, Q2(y, g(x, z))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1))) + assert(judg.isValid, "\n" + judg.repr) } { - val t0 = Hypothesis(exists(x, forall(y, Q(y, g(x, z)))) |- exists(x, forall(y, Q(y, g(x, z)))), exists(x, forall(y, Q(y, g(x, z))))) - val t1 = RightSubstIff( - Set(exists(x, forall(y, Q(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> Q(y, x)))) |- exists(x, forall(y, Q(g(x, z), y))), + val t0 = Hypothesis(exists(x, forall(y, fp(g(y, g(x, f(z)))))) |- exists(x, forall(y, fp(g(y, g(x, f(z)))))), exists(x, forall(y, fp(g(y, g(x, f(z))))))) + val t1 = RightSubstEq( + Set(exists(x, forall(y, fp(g(y, g(x, f(z)))))), forall(y, f(y) === lambda(Seq(z), g(z, z))(y)), forall(y, forall(z, g(y, z) === lambda(Seq(y, z), g(z, y))(y, z)))) |- exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(y, lambda(Seq(y, z), g(z, y))(x, lambda(Seq(z), g(z, z))(z)))))), 0, - List((LambdaTermFormula(Seq(y, z), Q(y, z)), LambdaTermFormula(Seq(y, z), Q(z, y)))), - (Seq(Q2), exists(x, forall(y, Q2(g(x, z), y)))) + List((g, lambda(Seq(y, z), g(z, y))), (f, lambda(Seq(z), g(z, z)))), + (Seq(g2, f2), exists(x, forall(y, fp(g2(y, g2(x, f2(z))))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t2 = Beta(Set(exists(x, forall(y, fp(g(y, g(x, f(z)))))), forall(y, f(y) === g(y, y)), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(g(g(z, z), x), y)))), 1) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, "\n" + judg.repr) } } test("Commutativity on a random large formula") { val k = 9 val r = new Random() - val vars = (0 until 1 << k).map(i => AtomicFormula(ConstantAtomicLabel(s"P$i", 0), Seq())) + val vars = (0 until 1 << k).map(i => Constant(s"P$i", Formula)) val pairs = vars.grouped(2) val sPairs = vars.grouped(2) diff --git a/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala b/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala index b5e59ffc7..9127f49f8 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala @@ -6,7 +6,6 @@ import lisa.kernel.proof.RunningTheory.* import lisa.kernel.proof.SCProof import lisa.kernel.proof.SCProofChecker import lisa.kernel.proof.SequentCalculus.* -import lisa.utils.FOLPrinter.* import lisa.utils.KernelHelpers.{_, given} import org.scalatest.compatible.Assertion import org.scalatest.funsuite.AnyFunSuite @@ -16,7 +15,7 @@ import org.scalatest.funsuite.AnyFunSuite */ class SubstitutionTest extends AnyFunSuite { private val x = variable - private val x1 = VariableLabel(Identifier("x", 1)) + private val x1 = variable private val x2 = variable private val x3 = variable private val y = variable @@ -42,160 +41,88 @@ class SubstitutionTest extends AnyFunSuite { private val d1 = connector(1) private val e2 = connector(2) - - test("Substitution in Terms") { - case class $(t: Term, m: (SchematicTermLabel, LambdaTermTerm)*) - extension (c: $) { - inline infix def _VS_(t2: Term): Assertion = { - assert(instantiateTermSchemasInTerm(c.t, c.m.toMap) == t2, "\n - " + prettyTerm(instantiateTermSchemasInTerm(c.t, c.m.toMap)) + " didn't match " + prettyTerm(t2)) - } + case class $(t: Expression, m: (Variable, Expression)*){ + inline infix def _VS_(t2: Expression): Assertion = { + assert(isSame(betaReduce(substituteVariables(t, m.toMap)), t2), "\n - " + substituteVariables(t, m.toMap).repr + " didn't match " + t2.repr) } + } + test("First Order Substitutions") { val cases: List[Assertion] = List( - $(x, x -> x()) _VS_ x, - $(x, y -> y()) _VS_ x, - $(x, x -> y()) _VS_ y, - $(x, y -> z(), x -> y()) _VS_ y, + $(x, x -> x) _VS_ x, + $(x, y -> y) _VS_ x, + $(x, x -> y) _VS_ y, + $(x, y -> z, x -> y) _VS_ y, $(x, g -> lambda(y, f(y))) _VS_ x, - $(f(x), x -> y()) _VS_ f(y), - $(f(f(h(x, y))), x -> y()) _VS_ f(f(h(y, y))), - $(f(f(h(x, y))), x -> z()) _VS_ f(f(h(z, y))), - $(f(f(h(x, y))), x -> z(), z -> x()) _VS_ f(f(h(z, y))), - $(f(f(h(x, y))), x -> y(), y -> x()) _VS_ f(f(h(y, x))), - $(f(f(h(x, y))), z -> y(), g -> lambda(Seq(x), f(h(y, x)))) _VS_ f(f(h(x, y))), - $(f(f(h(x, y))), f -> lambda(x, x)) _VS_ h(x, y), - $(f(f(h(x, y))), f -> lambda(x, y)) _VS_ y, - $(f(f(h(x, y))), f -> lambda(x, f(f(x)))) _VS_ f(f(f(f(h(x, y))))), - $(f(f(h(x, y))), f -> lambda(x, f(f(x))), h -> lambda(Seq(x, z), h(f(x), h(g(z), x)))) _VS_ f(f(f(f(h(f(x), h(g(y), x)))))) + $(f(x), x -> y) _VS_ f(y), + $(f(f(h(x, y))), x -> y) _VS_ f(f(h(y, y))), + $(f(f(h(x, y))), x -> z) _VS_ f(f(h(z, y))), + $(f(f(h(x, y))), x -> z, z -> x) _VS_ f(f(h(z, y))), + $(f(f(h(x, y))), x -> y, y -> x) _VS_ f(f(h(y, x))), + $(Q(x), x -> x) _VS_ Q(x), + $(Q(x), y -> y) _VS_ Q(x), + $(c1(c1(Q(x))), x -> y) _VS_ c1(c1(Q(y))), + $(Q(f(f(h(x, y)))), x -> y) _VS_ Q(f(f(h(y, y)))), + $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z) _VS_ Q(f(f(h(z, y)))) /\ R(z, f(y)), + $(forall(x, R(x, y)), x -> z) _VS_ forall(x, R(x, y)), + $(forall(x, R(x, y)), y -> z) _VS_ forall(x, R(x, z)), + $(forall(x, P(x)), x1 -> f(x)) _VS_ forall(x, P(x)), + $(forall(x, R(x, y)) /\ P(h(x, y)), y -> z, x -> y) _VS_ forall(x, R(x, z)) /\ P(h(y, z)), + $(forall(x, R(x, y)) /\ P(h(x, y)), y -> x) _VS_ forall(y, R(y, x)) /\ P(h(x, x)), + $(X, X -> X) _VS_ X, + $(X, Y -> Y) _VS_ X, + $(X, X -> Y) _VS_ Y, + $(X, Y -> Z, X -> Y) _VS_ Y, + $(c1(X), X -> Y) _VS_ c1(Y), + $(c1(c1(e2(X, Y))), X -> Y) _VS_ c1(c1(e2(Y, Y))), + $(c1(c1(e2(X, Y))), X -> Z) _VS_ c1(c1(e2(Z, Y))), + $(c1(c1(e2(X, Y))), X -> Z, Z -> X) _VS_ c1(c1(e2(Z, Y))), + $(c1(c1(e2(X, Y))), X -> Y, Y -> X) _VS_ c1(c1(e2(Y, X))), + $(Q(x), x -> x) _VS_ Q(x), + $(Q(x), y -> y) _VS_ Q(x), + $(c1(c1(Q(x))), x -> y) _VS_ c1(c1(Q(y))), + $(Q(f(f(h(x, y)))), x -> y) _VS_ Q(f(f(h(y, y)))), + $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z) _VS_ Q(f(f(h(z, y)))) /\ R(z, f(y)), + $(forall(x, R(x, y)), x -> z) _VS_ forall(x, R(x, y)), + $(forall(x, R(x, y)), y -> z) _VS_ forall(x, R(x, z)), + $(forall(x, R(x, y)) /\ P(h(x, y)), y -> z, x -> y) _VS_ forall(x, R(x, z)) /\ P(h(y, z)), + $(forall(x, R(x, y)) /\ P(h(x, y)), y -> x) _VS_ forall(y, R(y, x)) /\ P(h(x, x)), + $(X, X -> X) _VS_ X, + $(X, Y -> Y) _VS_ X, + $(X, X -> Y) _VS_ Y, + $(X, Y -> Z, X -> Y) _VS_ Y, + $(c1(X), X -> Y) _VS_ c1(Y), + $(c1(c1(e2(X, Y))), X -> Y) _VS_ c1(c1(e2(Y, Y))), + $(c1(c1(e2(X, Y))), X -> Z) _VS_ c1(c1(e2(Z, Y))), + $(c1(c1(e2(X, Y))), X -> Z, Z -> X) _VS_ c1(c1(e2(Z, Y))), + $(c1(c1(e2(X, Y))), X -> Y, Y -> X) _VS_ c1(c1(e2(Y, X))), + $(c1(c1(e2(X, Y))), Z -> Y) _VS_ c1(c1(e2(X, Y))), + $(c1(c1(e2(X, Y))), Z -> Y) _VS_ c1(c1(e2(X, Y))), ) } - - test("Substitution of Terms in Formulas") { - case class $(f: Formula, m: (SchematicTermLabel, LambdaTermTerm)*) - extension (c: $) { - inline infix def _VS_(t2: Formula): Assertion = { - assert(isSame(instantiateTermSchemas(c.f, c.m.toMap), t2), "\n - " + prettyFormula(instantiateTermSchemas(c.f, c.m.toMap)) + " didn't match " + prettyFormula(t2)) - } - } + + test("Higher Order Substitutions, with beta normalization") { val cases: List[Assertion] = List( - $(Q(x), x -> x()) _VS_ Q(x), - $(Q(x), y -> y()) _VS_ Q(x), - $(c1(c1(Q(x))), x -> y()) _VS_ c1(c1(Q(y))), - $(Q(f(f(h(x, y)))), x -> y()) _VS_ Q(f(f(h(y, y)))), - $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z()) _VS_ Q(f(f(h(z, y)))) /\ R(z, f(y)), - $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z(), h -> lambda(Seq(x, z), g(h(z, y)))) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, f(y)), - $(R(x, h(f(z), y)), x -> z(), h -> lambda(Seq(x, z), g(h(z, y))), z -> y()) _VS_ R(z, g(h(y, y))), - $(Q(f(f(h(x, y)))) /\ R(x, h(y, f(z))), x -> z(), h -> lambda(Seq(x, z), g(h(z, y))), z -> y()) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, g(h(f(y), y))), - $(forall(x, R(x, y)), x -> z()) _VS_ forall(x, R(x, y)), - $(forall(x, R(x, y)), y -> z()) _VS_ forall(x, R(x, z)), - $(forall(x, P(x)), x1 -> f(x())) _VS_ forall(x, P(x)), - $(forall(x, R(x, y)) /\ P(h(x, y)), y -> z(), x -> y()) _VS_ forall(x, R(x, z)) /\ P(h(y, z)), - $(forall(x, R(x, y)) /\ P(h(x, y)), y -> x()) _VS_ forall(y, R(y, x)) /\ P(h(x, x)), - $(existsOne(x, R(x, y)) /\ P(h(x, y)), y -> x()) _VS_ existsOne(y, R(y, x)) /\ P(h(x, x)) - ) - } - - test("Substitution of Predicates in Formulas") { - case class $(f: Formula, m: (SchematicAtomicLabel, LambdaTermFormula)*) - extension (c: $) { - inline infix def _VS_(t2: Formula): Assertion = { - assert( - isSame(instantiatePredicateSchemas(c.f, c.m.toMap), t2), - "\n - " + prettyFormula(instantiatePredicateSchemas(c.f, c.m.toMap)) + " didn't match " + prettyFormula(t2) - ) - } - } - val cases: List[Assertion] = List( - $(X, X -> X()) _VS_ X, - $(X, Y -> Y()) _VS_ X, - $(X, X -> Y()) _VS_ Y, - $(X, Y -> Z(), X -> Y()) _VS_ Y, - $(c1(X), X -> Y()) _VS_ c1(Y), - $(c1(c1(e2(X, Y))), X -> Y()) _VS_ c1(c1(e2(Y, Y))), - $(c1(c1(e2(X, Y))), X -> Z()) _VS_ c1(c1(e2(Z, Y))), - $(c1(c1(e2(X, Y))), X -> Z(), Z -> X()) _VS_ c1(c1(e2(Z, Y))), - $(c1(c1(e2(X, Y))), X -> Y(), Y -> X()) _VS_ c1(c1(e2(Y, X))), - $(c1(c1(e2(X, Y))), Z -> Y()) _VS_ c1(c1(e2(X, Y))), + $(f(f(h(x, y))), z -> y, g -> lambda(x, f(h(y, x)))) _VS_ f(f(h(x, y))), + $(f(f(h(x, y))), f -> lambda(x, x)) _VS_ h(x, y), + $(f(f(h(x, y))), f -> lambda(x, y)) _VS_ y, + $(f(f(h(x, y))), f -> lambda(x, f(f(x)))) _VS_ f(f(f(f(h(x, y))))), + $(f(f(h(x, y))), f -> lambda(x, f(f(x))), h -> lambda(Seq(x, z), h(f(x), h(g(z), x)))) _VS_ f(f(f(f(h(f(x), h(g(y), x)))))), + $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z, h -> lambda(Seq(x, z), g(h(z, y)))) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, f(y)), + $(R(x, h(f(z), y)), x -> z, h -> lambda(Seq(x, z), g(h(z, y))), z -> y) _VS_ R(z, g(h(y, y))), + $(Q(f(f(h(x, y)))) /\ R(x, h(y, f(z))), x -> z, h -> lambda(Seq(x, z), g(h(z, y))), z -> y) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, g(h(f(y), y))), $(R(x, f(y)), R -> lambda(Seq(z, y), P(z) /\ Q(y))) _VS_ P(x) /\ Q(f(y)), $(R(x, f(y)) /\ R(z, z), R -> lambda(Seq(z, y), P(z) /\ Q(y))) _VS_ (P(x) /\ Q(f(y))) /\ (P(z) /\ Q(z)), - $(forall(y, R(x, f(y))), R -> lambda(Seq(x, z), (x === z) /\ P(f(y)))) _VS_ forall(y2, (x === f(y2)) /\ P(f(y))) - ) - } - - test("Substitution of Formulas in Formulas") { - case class $(f: Formula, m: (SchematicConnectorLabel, LambdaFormulaFormula)*) - extension (c: $) { - inline infix def _VS_(t2: Formula): Assertion = { - assert(instantiateConnectorSchemas(c.f, c.m.toMap) == t2, "\n - " + prettyFormula(instantiateConnectorSchemas(c.f, c.m.toMap)) + " didn't match " + prettyFormula(t2)) - } - } - - val cases: List[Assertion] = List( + $(forall(y, R(x, f(y))), R -> lambda(Seq(x, z), (x === z) /\ P(f(y)))) _VS_ forall(y2, (x === f(y2)) /\ P(f(y))), $(c1(P(x)), c1 -> lambda(X, !X)) _VS_ !P(x), $(c1(c1(e2(X, P(y)))), c1 -> lambda(X, X)) _VS_ e2(X, P(y)), $(c1(c1(e2(X, P(y)))), c1 -> lambda(X, Y)) _VS_ Y, - $(c1(c1(e2(X, P(y)))), c1 -> lambda(X, c1(c1(X)))) _VS_ c1(c1(c1(c1(e2(X, P(y)))))) - ) - } - - test("Simultaneous Substitutions in Formulas") { - case class $(f: Formula, m: ((SchematicConnectorLabel, LambdaFormulaFormula) | (SchematicAtomicLabel, LambdaTermFormula) | (SchematicTermLabel, LambdaTermTerm))*) - extension (c: $) { - inline infix def _VS_(t2: Formula): Assertion = { - @annotation.nowarn - val mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula] = c.m - .collect({ - case e if e._1.isInstanceOf[SchematicConnectorLabel] => e - }) - .toMap - .asInstanceOf - @annotation.nowarn - val mPred: Map[SchematicAtomicLabel, LambdaTermFormula] = c.m - .collect({ - case e if e._1.isInstanceOf[SchematicAtomicLabel] => e - }) - .toMap - .asInstanceOf - @annotation.nowarn - val mTerm: Map[SchematicTermLabel, LambdaTermTerm] = c.m - .collect({ - case e if e._1.isInstanceOf[SchematicTermLabel] => e - }) - .toMap - .asInstanceOf - assert( - isSame(instantiateSchemas(c.f, mCon, mPred, mTerm), t2), - "\n - " + prettyFormula(instantiateSchemas(c.f, mCon, mPred, mTerm)) + " didn't match " + prettyFormula(t2) - ) - } - } - - val cases: List[Assertion] = List( - $(Q(x), x -> x()) _VS_ Q(x), - $(Q(x), y -> y()) _VS_ Q(x), - $(c1(c1(Q(x))), x -> y()) _VS_ c1(c1(Q(y))), - $(Q(f(f(h(x, y)))), x -> y()) _VS_ Q(f(f(h(y, y)))), - $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z()) _VS_ Q(f(f(h(z, y)))) /\ R(z, f(y)), - $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z(), h -> lambda(Seq(x, z), g(h(z, y)))) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, f(y)), - $(R(x, h(f(z), y)), x -> z(), h -> lambda(Seq(x, z), g(h(z, y))), z -> y()) _VS_ R(z, g(h(y, y))), - $(Q(f(f(h(x, y)))) /\ R(x, h(y, f(z))), x -> z(), h -> lambda(Seq(x, z), g(h(z, y))), z -> y()) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, g(h(f(y), y))), - $(forall(x, R(x, y)), x -> z()) _VS_ forall(x, R(x, y)), - $(forall(x, R(x, y)), y -> z()) _VS_ forall(x, R(x, z)), - $(forall(x, R(x, y)) /\ P(h(x, y)), y -> z(), x -> y()) _VS_ forall(x, R(x, z)) /\ P(h(y, z)), - $(forall(x, R(x, y)) /\ P(h(x, y)), y -> x()) _VS_ forall(y, R(y, x)) /\ P(h(x, x)), - $(existsOne(x, R(x, y)) /\ P(h(x, y)), y -> x()) _VS_ existsOne(y, R(y, x)) /\ P(h(x, x)), - $(X, X -> X()) _VS_ X, - $(X, Y -> Y()) _VS_ X, - $(X, X -> Y()) _VS_ Y, - $(X, Y -> Z(), X -> Y()) _VS_ Y, - $(c1(X), X -> Y()) _VS_ c1(Y), - $(c1(c1(e2(X, Y))), X -> Y()) _VS_ c1(c1(e2(Y, Y))), - $(c1(c1(e2(X, Y))), X -> Z()) _VS_ c1(c1(e2(Z, Y))), - $(c1(c1(e2(X, Y))), X -> Z(), Z -> X()) _VS_ c1(c1(e2(Z, Y))), - $(c1(c1(e2(X, Y))), X -> Y(), Y -> X()) _VS_ c1(c1(e2(Y, X))), - $(c1(c1(e2(X, Y))), Z -> Y()) _VS_ c1(c1(e2(X, Y))), + $(c1(c1(e2(X, P(y)))), c1 -> lambda(X, c1(c1(X)))) _VS_ c1(c1(c1(c1(e2(X, P(y)))))), + $(Q(f(f(h(x, y)))) /\ R(x, f(y)), x -> z, h -> lambda(Seq(x, z), g(h(z, y)))) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, f(y)), + $(R(x, h(f(z), y)), x -> z, h -> lambda(Seq(x, z), g(h(z, y))), z -> y) _VS_ R(z, g(h(y, y))), + $(Q(f(f(h(x, y)))) /\ R(x, h(y, f(z))), x -> z, h -> lambda(Seq(x, z), g(h(z, y))), z -> y) _VS_ Q(f(f(g(h(y, y))))) /\ R(z, g(h(f(y), y))), $(R(x, f(y)), R -> lambda(Seq(z, y), P(z) /\ Q(y))) _VS_ P(x) /\ Q(f(y)), $(R(x, f(y)) /\ R(z, z), R -> lambda(Seq(z, y), P(z) /\ Q(y))) _VS_ (P(x) /\ Q(f(y))) /\ (P(z) /\ Q(z)), $(forall(y, R(x, f(y))), R -> lambda(Seq(x, z), (x === z) /\ P(f(y)))) _VS_ forall(y2, (x === f(y2)) /\ P(f(y))), @@ -222,7 +149,7 @@ class SubstitutionTest extends AnyFunSuite { X -> (z === y), P -> lambda(x2, exists(y, R(x2, y) /\ P(x))) ) _VS_ (exists(y2, Q(y2) /\ ((z === y) <=> exists(x2, P(x2) /\ exists(y, R(x, y) /\ P(x))))) /\ R(y, f(y))), - $(forall(x, P(x)), x1 -> f(x())) _VS_ forall(x, P(x)), + $(forall(x, P(x)), x1 -> f(x)) _VS_ forall(x, P(x)), $( forall(x, e2(c1(e2(X, P(x))) /\ R(y, f(y)), exists(x, P(x) /\ X))), c1 -> lambda(X, exists(y, Q(y) /\ X)), diff --git a/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala b/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala index 988ead998..e265eab0c 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala @@ -17,23 +17,25 @@ class TheoriesHelpersTest extends AnyFunSuite { export TestTheory.* test("theorem with incorrect statement") { - val (s0, s1) = (ConstantFunctionLabel("0", 0), ConstantFunctionLabel("1", 0)) - runningTestTheory.addSymbol(s0) - runningTestTheory.addSymbol(s1) + val (c0, c1) = (Constant("Z", Term), Constant("S", Term)) + runningTestTheory.addSymbol(c0) + runningTestTheory.addSymbol(c1) - val (c0, c1) = (s0(), s1()) val judgement = runningTestTheory.theorem("False theorem", c1 === c0, SCProof(Hypothesis((c0 === c1) |- (c0 === c1), c0 === c1)), Seq()) assert(!judgement.isValid) try { judgement.get fail("Shouldn't be able to get a theorem from an invalid judgement") } catch { - case InvalidJustificationException(msg, None) => () } + // same theorem but with correct statement - assert(runningTestTheory.theorem("True theorem", c1 === c0 |- c1 === c0, SCProof(Hypothesis((c0 === c1) |- (c0 === c1), c0 === c1)), Seq()).isValid) + + + assert(runningTestTheory.theorem("True theorem", c1 === c0 |- c1 === c0, SCProof(Hypothesis((c0 === c1) |- (c0 === c1), c0 === c1)), Seq()).isValid, + runningTestTheory.theorem("True theorem", c1 === c0 |- c1 === c0, SCProof(Hypothesis((c0 === c1) |- (c0 === c1), c0 === c1)), Seq()).repr) } } diff --git a/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala b/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala index 8d46bbcf4..ed9a30679 100644 --- a/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala +++ b/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala @@ -1,29 +1,23 @@ package lisa.utils //import lisa.kernel.proof.SequentCalculus as SC -//import lisa.prooflib.BasicStepTactic.* -//import lisa.prooflib.Library -//import lisa.prooflib.ProofTacticLib -//import lisa.utils.Printer +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.Library +import lisa.prooflib.ProofTacticLib import lisa.test.ProofTacticTestLib //import org.scalatest.funsuite.AnyFunSuite class BasicTacticTest extends ProofTacticTestLib { - /* - given Conversion[String, Sequent] = FOLParser.parseSequent(_) - given Conversion[String, Formula] = FOLParser.parseFormula(_) - given Conversion[String, Term] = FOLParser.parseTerm(_) - given Conversion[String, VariableLabel] = s => VariableLabel(if (s.head == '?') s.tail else s) - */ - /* - val x: lisa.fol.FOL.Variable = variable - val y = variable - val z = variable - - val P = predicate[1] - val Q = predicate[1] - val R = predicate[1] - val S = predicate[2] + + + val x = variable[Term] + val y = variable[Term] + val z = variable[Term] + + val P = variable[Term >>: Formula] + val Q = variable[Term >>: Formula] + val R = variable[Term >>: Formula] + val S = variable[Term >>: Term >>: Formula] // hypothesis test("Tactic Tests: Hypothesis") { val correct = List[lisa.fol.FOL.Sequent]( @@ -41,10 +35,10 @@ class BasicTacticTest extends ProofTacticTestLib { (Q(x) |- ()) ) - /*testTacticCases(correct, incorrect) { + testTacticCases(correct, incorrect) { Hypothesis(_) - }*/ - }*/ + } + } /* // rewrite // TODO: make this use equivalence checker tests @@ -1432,7 +1426,7 @@ class BasicTacticTest extends ProofTacticTestLib { } // instfunschema - test("Tactic Tests: InstFunSchema") { + test("Tactic Tests: InstSchema") { val x = variable val y = variable val f = SchematicFunctionLabel("f", 1) @@ -1462,12 +1456,12 @@ class BasicTacticTest extends ProofTacticTestLib { testTacticCases(correct, incorrect) { (stmt1, stmt2, termMap) => val prem = introduceSequent(stmt1) - InstFunSchema(termMap)(prem)(stmt2) + InstSchema(termMap)(prem)(stmt2) } } // instpredschema - test("Tactic Tests: InstPredSchema") { + test("Tactic Tests: InstSchema") { val x = variable val y = variable val f = SchematicPredicateLabel("f", 1) @@ -1498,7 +1492,7 @@ class BasicTacticTest extends ProofTacticTestLib { testTacticCases(correct, incorrect) { (stmt1, stmt2, termMap) => val prem = introduceSequent(stmt1) - InstPredSchema(termMap)(prem)(stmt2) + InstSchema(termMap)(prem)(stmt2) } } */ diff --git a/lisa-utils/src/test/scala/lisa/utils/ParserTest.scala b/lisa-utils/src/test/scala/lisa/utils/ParserTest.scala index b6e20602b..6774488b5 100644 --- a/lisa-utils/src/test/scala/lisa/utils/ParserTest.scala +++ b/lisa-utils/src/test/scala/lisa/utils/ParserTest.scala @@ -2,13 +2,15 @@ package lisa.utils import lisa.kernel.fol.FOL._ import lisa.kernel.proof.SequentCalculus.Sequent -import lisa.utils.FOLParser import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.parsing.* import lisa.utils.{_, given} import org.scalatest.funsuite.AnyFunSuite +/** + * TODO: Port to TPTP-based parsing + */ class ParserTest extends AnyFunSuite with TestUtils { + /* test("constant") { assert(FOLParser.parseTerm("x") == Term(cx, Seq())) } @@ -285,4 +287,6 @@ class ParserTest extends AnyFunSuite with TestUtils { |Unexpected input: expected term""".stripMargin)) } } + + */ } diff --git a/lisa-utils/src/test/scala/lisa/utils/PrinterTest.scala b/lisa-utils/src/test/scala/lisa/utils/PrinterTest.scala index 8f2132419..5db68146d 100644 --- a/lisa-utils/src/test/scala/lisa/utils/PrinterTest.scala +++ b/lisa-utils/src/test/scala/lisa/utils/PrinterTest.scala @@ -2,46 +2,47 @@ package lisa.utils import lisa.kernel.fol.FOL.* import lisa.kernel.proof.SequentCalculus.Sequent -import lisa.utils.FOLParser import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.parsing.* import lisa.utils.{_, given} import org.scalatest.funsuite.AnyFunSuite import scala.language.adhocExtensions +/** + * TODO: Port to TPTP-based printing + */ class PrinterTest extends AnyFunSuite with TestUtils { - +/* test("Minimal parenthesization") { - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, b))) == "a ∧ b") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "a ∧ b ∧ c") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, ConnectorFormula(And, Seq(b, c))))) == "a ∧ (b ∧ c)") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(a, ConnectorFormula(And, Seq(b, c))))) == "a ∨ b ∧ c") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "a ∧ b ∨ c") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(Or, Seq(a, b)), c))) == "(a ∨ b) ∧ c") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, ConnectorFormula(Or, Seq(b, c))))) == "a ∧ (b ∨ c)") - - assert(FOLParser.printFormula(ConnectorFormula(Neg, Seq(a))) == "¬a") - assert(FOLParser.printFormula(ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(a))))) == "¬¬a") - assert(FOLParser.printFormula(ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(ConnectorFormula(And, Seq(a, b))))))) == "¬¬(a ∧ b)") + assert((multiand(Seq(a, b))).repr == "a ∧ b") + assert((multiand(Seq(multiand(Seq(a, b)), c))).repr == "a ∧ b ∧ c") + assert((multiand(Seq(a, multiand(Seq(b, c))))).repr == "a ∧ (b ∧ c)") + assert((multior(Seq(a, multiand(Seq(b, c))))).repr == "a ∨ b ∧ c") + assert((multior(Seq(multiand(Seq(a, b)), c))).repr == "a ∧ b ∨ c") + assert((multiand(Seq(multior(Seq(a, b)), c))).repr == "(a ∨ b) ∧ c") + assert((multiand(Seq(a, multior(Seq(b, c))))).repr == "a ∧ (b ∨ c)") + + assert((!a).repr == "¬a") + assert((!!a).repr == "¬¬a") + assert((!!Seq(multiand(Seq(a, b))))).repr == "¬¬(a ∧ b)") assert( - FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(Neg, Seq(a)), ConnectorFormula(And, Seq(ConnectorFormula(Neg, Seq(b)), ConnectorFormula(Neg, Seq(c))))))) == "¬a ∧ (¬b ∧ ¬c)" + (multiand(Seq(!a, multiand(Seq(!b, !c))))).repr == "¬a ∧ (¬b ∧ ¬c)" ) assert( - FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(ConnectorFormula(Neg, Seq(a)), ConnectorFormula(Neg, Seq(b)))), ConnectorFormula(Neg, Seq(c))))) == "¬a ∧ ¬b ∧ ¬c" + (multiand(Seq(multiand(Seq(ConnectorFormula(Neg, Seq(a)), ConnectorFormula(Neg, Seq(b)))), ConnectorFormula(Neg, Seq(c))))).repr == "¬a ∧ ¬b ∧ ¬c" ) - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, AtomicFormula(equality, Seq(x, x))))) == "a ∧ 'x = 'x") + assert((multiand(Seq(a, AtomicFormula(equality, Seq(x, x))))).repr == "a ∧ 'x = 'x") - assert(FOLParser.printFormula(BinderFormula(Forall, x, AtomicFormula(equality, Seq(x, x)))) == "∀'x. 'x = 'x") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, AtomicFormula(equality, Seq(x, x)))))) == "a ∧ (∀'x. 'x = 'x)") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(BinderFormula(Forall, x, b), a))) == "(∀'x. b) ∧ a") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, b))), a))) == "a ∧ (∀'x. b) ∧ a") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, b))), a))) == "a ∧ (∀'x. b) ∨ a") + assert((BinderFormula(Forall, x, AtomicFormula(equality, Seq(x, x)))).repr == "∀'x. 'x = 'x") + assert((multiand(Seq(a, BinderFormula(Forall, x, AtomicFormula(equality, Seq(x, x)))))).repr == "a ∧ (∀'x. 'x = 'x)") + assert((multiand(Seq(BinderFormula(Forall, x, b), a))).repr == "(∀'x. b) ∧ a") + assert((multiand(Seq(multiand(Seq(a, BinderFormula(Forall, x, b))), a))).repr == "a ∧ (∀'x. b) ∧ a") + assert((multior(Seq(multiand(Seq(a, BinderFormula(Forall, x, b))), a))).repr == "a ∧ (∀'x. b) ∨ a") - assert(FOLParser.printFormula(BinderFormula(Forall, x, BinderFormula(Exists, y, BinderFormula(ExistsOne, z, a)))) == "∀'x. ∃'y. ∃!'z. a") + assert((BinderFormula(Forall, x, BinderFormula(Exists, y, BinderFormula(ExistsOne, z, a)))).repr == "∀'x. ∃'y. ∃!'z. a") - assert(FOLParser.printFormula(AtomicFormula(ConstantAtomicLabel("f", 3), Seq(x, y, z))) == "f('x, 'y, 'z)") + assert((AtomicFormula(ConstantAtomicLabel("f", 3), Seq(x, y, z))).repr == "f('x, 'y, 'z)") } test("constant") { @@ -77,117 +78,117 @@ class PrinterTest extends AnyFunSuite with TestUtils { } test("0-ary predicate") { - assert(FOLParser.printFormula(AtomicFormula(ConstantAtomicLabel("a", 0), Seq())) == "a") + assert((AtomicFormula(ConstantAtomicLabel("a", 0), Seq())).repr == "a") } test("predicate application") { - assert(FOLParser.printFormula(AtomicFormula(ConstantAtomicLabel("p", 3), Seq(cx, cy, cz))) == "p(x, y, z)") - assert(FOLParser.printFormula(AtomicFormula(ConstantAtomicLabel("p", 3), Seq(x, y, z))) == "p('x, 'y, 'z)") + assert((AtomicFormula(ConstantAtomicLabel("p", 3), Seq(cx, cy, cz))).repr == "p(x, y, z)") + assert((AtomicFormula(ConstantAtomicLabel("p", 3), Seq(x, y, z))).repr == "p('x, 'y, 'z)") } test("equality") { - assert(FOLParser.printFormula(AtomicFormula(equality, Seq(cx, cx))) == "x = x") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, AtomicFormula(equality, Seq(x, x))))) == "a ∧ 'x = 'x") + assert((AtomicFormula(equality, Seq(cx, cx))).repr == "x = x") + assert((multiand(Seq(a, AtomicFormula(equality, Seq(x, x))))).repr == "a ∧ 'x = 'x") } test("toplevel connectors") { - assert(FOLParser.printFormula(ConnectorFormula(Implies, Seq(a, b))) == "a ⇒ b") - assert(FOLParser.printFormula(ConnectorFormula(Iff, Seq(a, b))) == "a ⇔ b") + assert((ConnectorFormula(Implies, Seq(a, b))).repr == "a ⇒ b") + assert((ConnectorFormula(Iff, Seq(a, b))).repr == "a ⇔ b") } test("unicode connectors") { - assert(FOLParser.printFormula(ConnectorFormula(Neg, Seq(a))) == "¬a") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, b))) == "a ∧ b") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(a, b))) == "a ∨ b") + assert((ConnectorFormula(Neg, Seq(a))).repr == "¬a") + assert((multiand(Seq(a, b))).repr == "a ∧ b") + assert((multior(Seq(a, b))).repr == "a ∨ b") } test("connector associativity") { - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "a ∧ b ∧ c") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(ConnectorFormula(Or, Seq(a, b)), c))) == "a ∨ b ∨ c") + assert((multiand(Seq(multiand(Seq(a, b)), c))).repr == "a ∧ b ∧ c") + assert((multior(Seq(multior(Seq(a, b)), c))).repr == "a ∨ b ∨ c") } test("and/or of 1 argument") { - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a))) == "∧(a)") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(a))) == "∨(a)") + assert((multiand(Seq(a))).repr == "∧(a)") + assert((multior(Seq(a))).repr == "∨(a)") - assert(FOLParser.printFormula(ConnectorFormula(Implies, Seq(ConnectorFormula(Or, Seq(a)), ConnectorFormula(And, Seq(a))))) == "∨(a) ⇒ ∧(a)") - assert(FOLParser.printFormula(ConnectorFormula(Implies, Seq(a, a))) == "a ⇒ a") - assert(FOLParser.printFormula(BinderFormula(Forall, x, ConnectorFormula(Or, Seq(a)))) == "∀'x. ∨(a)") + assert((ConnectorFormula(Implies, Seq(multior(Seq(a)), multiand(Seq(a))))).repr == "∨(a) ⇒ ∧(a)") + assert((ConnectorFormula(Implies, Seq(a, a))).repr == "a ⇒ a") + assert((BinderFormula(Forall, x, multior(Seq(a)))).repr == "∀'x. ∨(a)") } test("connectors of >2 arguments") { - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, b, c))) == "a ∧ b ∧ c") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(a, b, c))) == "a ∨ b ∨ c") + assert((multiand(Seq(a, b, c))).repr == "a ∧ b ∧ c") + assert((multior(Seq(a, b, c))).repr == "a ∨ b ∨ c") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, b, c, a))) == "a ∧ b ∧ c ∧ a") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(a, b, c, b))) == "a ∨ b ∨ c ∨ b") + assert((multiand(Seq(a, b, c, a))).repr == "a ∧ b ∧ c ∧ a") + assert((multior(Seq(a, b, c, b))).repr == "a ∨ b ∨ c ∨ b") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(ConnectorFormula(And, Seq(a, b, c)), ConnectorFormula(And, Seq(c, b, a))))) == "a ∧ b ∧ c ∨ c ∧ b ∧ a") + assert((multior(Seq(multiand(Seq(a, b, c)), multiand(Seq(c, b, a))))).repr == "a ∧ b ∧ c ∨ c ∧ b ∧ a") } test("connectors with no arguments") { - assert(FOLParser.printFormula(ConnectorFormula(And, Seq())) == "⊤") - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq())) == "⊥") + assert((multiand(Seq())).repr == "⊤") + assert((multior(Seq())).repr == "⊥") } test("connector priority") { // a ∨ (b ∧ c) - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(a, ConnectorFormula(And, Seq(b, c))))) == "a ∨ b ∧ c") + assert((multior(Seq(a, multiand(Seq(b, c))))).repr == "a ∨ b ∧ c") // (a ∧ b) ∨ c - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "a ∧ b ∨ c") + assert((multior(Seq(multiand(Seq(a, b)), c))).repr == "a ∧ b ∨ c") // (a ∧ b) => c - assert(FOLParser.printFormula(ConnectorFormula(Implies, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "a ∧ b ⇒ c") + assert((ConnectorFormula(Implies, Seq(multiand(Seq(a, b)), c))).repr == "a ∧ b ⇒ c") // a => (b ∧ c) - assert(FOLParser.printFormula(ConnectorFormula(Implies, Seq(a, ConnectorFormula(And, Seq(b, c))))) == "a ⇒ b ∧ c") + assert((ConnectorFormula(Implies, Seq(a, multiand(Seq(b, c))))).repr == "a ⇒ b ∧ c") // (a ∨ b) => c - assert(FOLParser.printFormula(ConnectorFormula(Implies, Seq(ConnectorFormula(Or, Seq(a, b)), c))) == "a ∨ b ⇒ c") + assert((ConnectorFormula(Implies, Seq(multior(Seq(a, b)), c))).repr == "a ∨ b ⇒ c") // a => (b ∨ c) - assert(FOLParser.printFormula(ConnectorFormula(Implies, Seq(a, ConnectorFormula(Or, Seq(b, c))))) == "a ⇒ b ∨ c") + assert((ConnectorFormula(Implies, Seq(a, multior(Seq(b, c))))).repr == "a ⇒ b ∨ c") // (a ∧ b) <=> c - assert(FOLParser.printFormula(ConnectorFormula(Iff, Seq(ConnectorFormula(And, Seq(a, b)), c))) == "a ∧ b ⇔ c") + assert((ConnectorFormula(Iff, Seq(multiand(Seq(a, b)), c))).repr == "a ∧ b ⇔ c") // a <=> (b ∧ c) - assert(FOLParser.printFormula(ConnectorFormula(Iff, Seq(a, ConnectorFormula(And, Seq(b, c))))) == "a ⇔ b ∧ c") + assert((ConnectorFormula(Iff, Seq(a, multiand(Seq(b, c))))).repr == "a ⇔ b ∧ c") // (a ∨ b) <=> c - assert(FOLParser.printFormula(ConnectorFormula(Iff, Seq(ConnectorFormula(Or, Seq(a, b)), c))) == "a ∨ b ⇔ c") + assert((ConnectorFormula(Iff, Seq(multior(Seq(a, b)), c))).repr == "a ∨ b ⇔ c") // a <=> (b ∨ c) - assert(FOLParser.printFormula(ConnectorFormula(Iff, Seq(a, ConnectorFormula(Or, Seq(b, c))))) == "a ⇔ b ∨ c") + assert((ConnectorFormula(Iff, Seq(a, multior(Seq(b, c))))).repr == "a ⇔ b ∨ c") } test("connector parentheses") { - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(Or, Seq(a, b)), c))) == "(a ∨ b) ∧ c") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(a, ConnectorFormula(Or, Seq(b, c))))) == "a ∧ (b ∨ c)") + assert((multiand(Seq(multior(Seq(a, b)), c))).repr == "(a ∨ b) ∧ c") + assert((multiand(Seq(a, multior(Seq(b, c))))).repr == "a ∧ (b ∨ c)") } test("schematic connectors") { - assert(FOLParser.printFormula(sc1(p(x))) == "?c(p('x))") - assert(FOLParser.printFormula(iff(sc1(p(x)), sc2(p(y), p(y)))) == "?c(p('x)) ⇔ ?c(p('y), p('y))") + assert((sc1(p(x))).repr == "?c(p('x))") + assert((iff(sc1(p(x)), sc2(p(y), p(y)))).repr == "?c(p('x)) ⇔ ?c(p('y), p('y))") } test("quantifiers") { - assert(FOLParser.printFormula(BinderFormula(Forall, VariableLabel("x"), AtomicFormula(ConstantAtomicLabel("p", 0), Seq()))) == "∀'x. p") - assert(FOLParser.printFormula(BinderFormula(Exists, VariableLabel("x"), AtomicFormula(ConstantAtomicLabel("p", 0), Seq()))) == "∃'x. p") - assert(FOLParser.printFormula(BinderFormula(ExistsOne, VariableLabel("x"), AtomicFormula(ConstantAtomicLabel("p", 0), Seq()))) == "∃!'x. p") + assert((BinderFormula(Forall, VariableLabel("x"), AtomicFormula(ConstantAtomicLabel("p", 0), Seq()))).repr == "∀'x. p") + assert((BinderFormula(Exists, VariableLabel("x"), AtomicFormula(ConstantAtomicLabel("p", 0), Seq()))).repr == "∃'x. p") + assert((BinderFormula(ExistsOne, VariableLabel("x"), AtomicFormula(ConstantAtomicLabel("p", 0), Seq()))).repr == "∃!'x. p") } test("nested quantifiers") { - assert(FOLParser.printFormula(BinderFormula(Forall, x, BinderFormula(Exists, y, BinderFormula(ExistsOne, z, a)))) == "∀'x. ∃'y. ∃!'z. a") + assert((BinderFormula(Forall, x, BinderFormula(Exists, y, BinderFormula(ExistsOne, z, a)))).repr == "∀'x. ∃'y. ∃!'z. a") } test("quantifier parentheses") { - assert(FOLParser.printFormula(BinderFormula(Forall, x, ConnectorFormula(And, Seq(b, a)))) == "∀'x. b ∧ a") + assert((BinderFormula(Forall, x, multiand(Seq(b, a)))).repr == "∀'x. b ∧ a") assert( FOLParser.printFormula( BinderFormula( Forall, x, - ConnectorFormula(And, Seq(AtomicFormula(ConstantAtomicLabel("p", 1), Seq(x)), AtomicFormula(ConstantAtomicLabel("q", 1), Seq(x)))) + multiand(Seq(AtomicFormula(ConstantAtomicLabel("p", 1), Seq(x)), AtomicFormula(ConstantAtomicLabel("q", 1), Seq(x)))) ) ) == "∀'x. p('x) ∧ q('x)" ) - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(BinderFormula(Forall, x, b), a))) == "(∀'x. b) ∧ a") + assert((multiand(Seq(BinderFormula(Forall, x, b), a))).repr == "(∀'x. b) ∧ a") assert( FOLParser.printFormula( @@ -201,21 +202,21 @@ class PrinterTest extends AnyFunSuite with TestUtils { ) == "(∀'x. p('x)) ∧ q('x)" ) - assert(FOLParser.printFormula(ConnectorFormula(Or, Seq(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, b))), a))) == "a ∧ (∀'x. b) ∨ a") - assert(FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(a, BinderFormula(Forall, x, b))), a))) == "a ∧ (∀'x. b) ∧ a") + assert((multior(Seq(multiand(Seq(a, BinderFormula(Forall, x, b))), a))).repr == "a ∧ (∀'x. b) ∨ a") + assert((multiand(Seq(multiand(Seq(a, BinderFormula(Forall, x, b))), a))).repr == "a ∧ (∀'x. b) ∧ a") } test("complex formulas with connectors") { - assert(FOLParser.printFormula(ConnectorFormula(Neg, Seq(ConnectorFormula(Or, Seq(a, b))))) == "¬(a ∨ b)") - assert(FOLParser.printFormula(ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(a))))) == "¬¬a") - assert(FOLParser.printFormula(ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(ConnectorFormula(And, Seq(a, b))))))) == "¬¬(a ∧ b)") + assert((ConnectorFormula(Neg, Seq(multior(Seq(a, b))))).repr == "¬(a ∨ b)") + assert((ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(a))))).repr == "¬¬a") + assert((ConnectorFormula(Neg, Seq(ConnectorFormula(Neg, Seq(multiand(Seq(a, b))))))).repr == "¬¬(a ∧ b)") assert( - FOLParser.printFormula(ConnectorFormula(And, Seq(ConnectorFormula(And, Seq(ConnectorFormula(Neg, Seq(a)), ConnectorFormula(Neg, Seq(b)))), ConnectorFormula(Neg, Seq(c))))) == "¬a ∧ ¬b ∧ ¬c" + (multiand(Seq(multiand(Seq(ConnectorFormula(Neg, Seq(a)), ConnectorFormula(Neg, Seq(b)))), ConnectorFormula(Neg, Seq(c))))).repr == "¬a ∧ ¬b ∧ ¬c" ) } test("complex formulas") { - assert(FOLParser.printFormula(BinderFormula(Forall, x, AtomicFormula(equality, Seq(x, x)))) == "∀'x. 'x = 'x") + assert((BinderFormula(Forall, x, AtomicFormula(equality, Seq(x, x)))).repr == "∀'x. 'x = 'x") } test("sequent") { @@ -279,13 +280,13 @@ class PrinterTest extends AnyFunSuite with TestUtils { val parser = Parser(SynonymInfoBuilder().addSynonyms(prefixIn.id, in.id).build, in.id :: Nil, Nil) assert(parser.printFormula(AtomicFormula(in, Seq(cx, cy))) == "x ∊ y") assert(parser.printFormula(AtomicFormula(in, Seq(x, y))) == "'x ∊ 'y") - assert(parser.printFormula(ConnectorFormula(And, Seq(AtomicFormula(in, Seq(x, y)), a))) == "'x ∊ 'y ∧ a") - assert(parser.printFormula(ConnectorFormula(Or, Seq(a, AtomicFormula(in, Seq(x, y))))) == "a ∨ 'x ∊ 'y") + assert(parser.printFormula(multiand(Seq(AtomicFormula(in, Seq(x, y)), a))) == "'x ∊ 'y ∧ a") + assert(parser.printFormula(multior(Seq(a, AtomicFormula(in, Seq(x, y))))) == "a ∨ 'x ∊ 'y") assert(parser.printFormula(AtomicFormula(prefixIn, Seq(cx, cy))) == "x ∊ y") assert(parser.printFormula(AtomicFormula(prefixIn, Seq(x, y))) == "'x ∊ 'y") - assert(parser.printFormula(ConnectorFormula(And, Seq(AtomicFormula(prefixIn, Seq(x, y)), a))) == "'x ∊ 'y ∧ a") - assert(parser.printFormula(ConnectorFormula(Or, Seq(a, AtomicFormula(prefixIn, Seq(x, y))))) == "a ∨ 'x ∊ 'y") + assert(parser.printFormula(multiand(Seq(AtomicFormula(prefixIn, Seq(x, y)), a))) == "'x ∊ 'y ∧ a") + assert(parser.printFormula(multior(Seq(a, AtomicFormula(prefixIn, Seq(x, y))))) == "a ∨ 'x ∊ 'y") } test("infix functions") { @@ -301,10 +302,12 @@ class PrinterTest extends AnyFunSuite with TestUtils { parser.printFormula( ConnectorFormula( And, - Seq(ConnectorFormula(And, Seq(AtomicFormula(in, Seq(cx, cy)), AtomicFormula(in, Seq(cx, cz)))), AtomicFormula(in, Seq(Term(plus, Seq(cx, cy)), cz))) + Seq(multiand(Seq(AtomicFormula(in, Seq(cx, cy)), AtomicFormula(in, Seq(cx, cz)))), AtomicFormula(in, Seq(Term(plus, Seq(cx, cy)), cz))) ) ) == "x ∊ y ∧ x ∊ z ∧ x + y ∊ z" ) }*/ + + */ } diff --git a/lisa-utils/src/test/scala/lisa/utils/TestUtils.scala b/lisa-utils/src/test/scala/lisa/utils/TestUtils.scala index 243bd3c1a..2f1cd58f3 100644 --- a/lisa-utils/src/test/scala/lisa/utils/TestUtils.scala +++ b/lisa-utils/src/test/scala/lisa/utils/TestUtils.scala @@ -8,21 +8,16 @@ import lisa.utils.KernelHelpers.{_, given} import lisa.utils.{_, given} trait TestUtils { - val (a, b, c) = (ConstantAtomicLabel("a", 0), ConstantAtomicLabel("b", 0), ConstantAtomicLabel("c", 0)) - val p = ConstantAtomicLabel("p", 1) - val (x, y, z) = (VariableLabel("x"), VariableLabel("y"), VariableLabel("z")) - val (x1, y1, z1) = (VariableLabel("x1"), VariableLabel("y1"), VariableLabel("z1")) - val (xPrime, yPrime, zPrime) = (VariableLabel("x'"), VariableLabel("y'"), VariableLabel("z'")) - val (cx, cy, cz) = (ConstantFunctionLabel("x", 0), ConstantFunctionLabel("y", 0), ConstantFunctionLabel("z", 0)) - val (f0, f1, f2, f3) = (ConstantFunctionLabel("f", 0), ConstantFunctionLabel("f", 1), ConstantFunctionLabel("f", 2), ConstantFunctionLabel("f", 3)) - val (sf1, sf2, sf3) = (SchematicFunctionLabel("f", 1), SchematicFunctionLabel("f", 2), SchematicFunctionLabel("f", 3)) - val (sPhi1, sPhi2) = (SchematicPredicateLabel("phi", 1), SchematicPredicateLabel("phi", 2)) - val (sc1, sc2) = (SchematicConnectorLabel("c", 1), SchematicConnectorLabel("c", 2)) - val (in, plus) = (ConstantAtomicLabel("elem", 2), ConstantFunctionLabel("+", 2)) + val (a, b, c) = (Constant("a", Formula), Constant("b", Formula), Constant("c", Formula)) + val p = Constant("p", Arrow(Term, Formula)) + val (x, y, z) = (Variable("x", Term), Variable("y", Term), Variable("z", Term)) + val (x1, y1, z1) = (Variable("x1", Term), Variable("y1", Term), Variable("z1", Term)) + val (xPrime, yPrime, zPrime) = (Variable("x'", Term), Variable("y'", Term), Variable("z'", Term)) + val (cx, cy, cz) = (Constant("x", Term), Constant("y", Term), Constant("z", Term)) + val (f0, f1, f2, f3) = (Constant("f", Term), Constant("f", Arrow(Term, Term)), Constant("f", Arrow(Term, Arrow(Term, Term))), Constant("f", Arrow(Term, Arrow(Term, Arrow(Term, Term))))) + val (sf1, sf2, sf3) = (Variable("f", Arrow(Term, Term)), Variable("f", Arrow(Term, Arrow(Term, Term))), Variable("f", Arrow(Term, Arrow(Term, Arrow(Term, Term))))) + val (sPhi1, sPhi2) = (Variable("phi", Arrow(Term, Formula)), Variable("phi", Arrow(Term, Arrow(Term, Formula)))) + val (sc1, sc2) = (Variable("c", Formula >>: Formula), Variable("c", Formula >>: Formula)) + val (in, plus) = (Constant("elem", Formula >>: Formula >>: Term), Constant("+", Arrow(Term, Arrow(Term, Term)))) - given Conversion[AtomicLabel, AtomicFormula] = AtomicFormula(_, Seq.empty) - - given Conversion[ConstantFunctionLabel, Term] = Term(_, Seq()) - - given Conversion[VariableLabel, Term] = VariableTerm.apply }