From 26e3b86eef628f87aa94845491887d2e75f163bf Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Tue, 8 Oct 2024 18:25:56 +0200 Subject: [PATCH 01/92] Reimplemented syntax and OL equivalence checker. --- .../lambdafol/OLEquivalenceChecker.scala | 484 ++++++++++++++++++ .../lisa/kernel/lambdafol/Substitutions.scala | 5 + .../scala/lisa/kernel/lambdafol/Syntax.scala | 189 +++++++ 3 files changed, 678 insertions(+) create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala new file mode 100644 index 00000000..2c7eaf01 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala @@ -0,0 +1,484 @@ +package lisa.kernel.lambdafol + +import scala.collection.mutable + +private[lambdafol] trait OLEquivalenceChecker extends Syntax { + + + def reducedForm(expr: Expression): Expression = { + val p = simplify(expr) + val nf = computeNormalForm(p) + val fln = fromLocallyNameless(nf, Map.empty, 0) + val res = toExpressionAIG(fln) + res + } + + def reducedNNFForm(formula: Expression): Expression = { + val p = simplify(formula) + 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 = { + if (e1.containsFormulas != e2.containsFormulas) false + else if (!e1.containsFormulas) e1 == e2 + else { + val nf1 = computeNormalForm(simplify(e1)) + val nf2 = computeNormalForm(simplify(e2)) + 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.typ == Formula && e2.typ == Formula) + val nf1 = computeNormalForm(simplify(e1)) + val nf2 = computeNormalForm(simplify(e2)) + 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(And(s1), And(s2)) + + def isSameSetR(s1: Set[Expression], s2: Set[Expression]): Boolean = + isSame(Or(s1), Or(s2)) + + def contains(s: Set[Expression], f: Expression): Boolean = { + s.exists(g => isSame(f, g)) + } + + + private var totSimpleExpr = 0 + sealed abstract class SimpleExpression { + val typ: Type + 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] = if (containsFormulas) None else Some(this) + def getNormalForm = 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, typ:Type, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = typ == Formula + val size = 1 + } + case class SimpleBoundVariable(no: Int, typ: Type, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = typ == Formula + val size = 1 + } + case class SimpleConstant(id: Identifier, typ: Type, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = typ == Formula + val size = 1 + } + case class SimpleApplication(f: SimpleExpression, arg: SimpleExpression, polarity: Boolean) extends SimpleExpression { + private val legalapp = legalApplication(f.typ, arg.typ) // Optimize after debugging + val typ = legalapp.get + val containsFormulas: Boolean = typ == 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 typ = (v.typ -> body.typ) + val size = body.size + } + case class SimpleAnd(children: Seq[SimpleExpression], polarity: Boolean) extends SimpleExpression{ + val containsFormulas: Boolean = true + val typ = Formula + val size = children.map(_.size).sum+1 + } + case class SimpleForall(id: Identifier, body: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val typ = Formula + val size = body.size +1 + } + case class SimpleLiteral(polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val typ = Formula + val size = 1 + } + case class SimpleEquality(left: SimpleExpression, right: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val typ = 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.typ == Formula => e.copy(polarity = !e.polarity) + case e: SimpleBoundVariable if e.typ == Formula => e.copy(polarity = !e.polarity) + case e: SimpleConstant if e.typ == Formula => e.copy(polarity = !e.polarity) + case e: SimpleApplication if e.typ == 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 = And(children.map(toExpressionAIG)) + 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, typ, polarity) => if (polarity) Variable(id, typ) else neg(Variable(id, typ)) + case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toFormulaAIG on a bound variable") + case SimpleConstant(id, typ, polarity) => if (polarity) Constant(id, typ) else neg(Constant(id, typ)) + 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, typ, polarity) => + if (polarity == positive) Variable(id, typ) + else neg(Variable(id, typ)) + case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toExpressionNNF on a bound variable") + case SimpleConstant(id, typ, polarity) => + if (polarity == positive) Constant(id, typ) + else neg(Constant(id, typ)) + 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 = 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(v, body) => + SimpleForall(v.id, polarize(body, true), polarity) + case Exists(v, body) => + SimpleForall(v.id, polarize(body, false), !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 Constant(`top`, Formula) => SimpleLiteral(true) + case Constant(`bot`, Formula) => SimpleLiteral(false) + case Constant(id, typ) => SimpleConstant(id, typ, polarity) + case Variable(id, typ) => SimpleVariable(id, typ, polarity) + } + + def toLocallyNameless(e: SimpleExpression, subst: Map[(Identifier, Type), Int], i: Int): SimpleExpression = e match { + case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless(_, subst, i)), polarity) + case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless(inner, subst + ((x, Term) -> i), i + 1), polarity) + case e: SimpleLiteral => e + case SimpleEquality(left, right, polarity) => SimpleEquality(toLocallyNameless(left, subst, i), toLocallyNameless(right, subst, i), polarity) + case v: SimpleVariable => + if (subst.contains((v.id, v.typ))) SimpleBoundVariable(i - subst((v.id, v.typ)), v.typ, 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(toLocallyNameless(arg1, subst, i), toLocallyNameless(arg2, subst, i), polarity) + case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless(inner, subst + ((x.id, x.typ) -> i), i + 1)) + } + + def fromLocallyNameless(e: SimpleExpression, subst: Map[Int, (Identifier, Type)], 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, typ, polarity) => + val dist = i - no + if (subst.contains(dist)) {val (id, typ) = subst(dist); SimpleVariable(id, typ, 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.typ)), i + 1)) + } + + def simplify(e: Expression): SimpleExpression = toLocallyNameless(polarize(e, true), Map.empty, 0) + + + ////////////////////// + //// 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, typ, true) => e + + case SimpleVariable(id, typ, true) => e + + case SimpleConstant(id, typ, 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.typ == Formula && e2.typ == 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) + + 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.containsFormulas && e2.containsFormulas) { + if (e1.typ == 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 + } + } else e1 == e2 +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala new file mode 100644 index 00000000..1663d1a3 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala @@ -0,0 +1,5 @@ +package lisa.kernel.lambdafol + +trait Substitutions extends OLEquivalenceChecker{ + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala new file mode 100644 index 00000000..e68c31b0 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala @@ -0,0 +1,189 @@ +package lisa.kernel.lambdafol + +private[lambdafol] trait Syntax { + + + 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, + (taken.collect({ case Identifier(base.name, no) => + no + }) ++ Iterable(base.no)).max + 1 + ) + } + + + + + sealed trait Type { + def ->(to: Type): Arrow = Arrow(this, to) + } + case object Term extends Type + case object Formula extends Type + sealed case class Arrow(from: Type, to: Type) extends Type + + def legalApplication(typ1: Type, typ2: Type): Option[Type] = { + typ1 match { + case Arrow(`typ2`, to) => Some(to) + case _ => None + } + } + + private object ExpressionCounters { + var totalNumberOfExpressions: Long = 0 + def getNewId: Long = { + totalNumberOfExpressions += 1 + totalNumberOfExpressions + } + } + + protected trait Expression { + val typ: Type + val uniqueNumber: Long = ExpressionCounters.getNewId + val containsFormulas : Boolean + def apply(arg: Expression): Application = Application(this, arg) + + /** + * @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, typ:Type) extends Expression { + val containsFormulas = typ == Formula + def freeVariables: Set[Variable] = Set(this) + def constants: Set[Constant] = Set() + def allVariables: Set[Variable] = Set(this) + } + case class Constant(id: Identifier, typ: Type) extends Expression { + val containsFormulas = typ == 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.typ, arg.typ) + require(legalapp.isDefined, s"Application of $f to $arg is not legal") + val typ = legalapp.get + val containsFormulas = typ == 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 typ = (v.typ -> body.typ) + + def freeVariables: Set[Variable] = body.freeVariables - v + def constants: Set[Constant] = body.constants + def allVariables: Set[Variable] = body.allVariables + } + + object Equality { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`equality`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + } + + object Neg { + def unapply (e: Expression): Option[Expression] = e match { + case Application(`neg`, arg) => Some(arg) + case _ => None + } + def apply(arg: Expression): Expression = neg(arg) + } + object Implies { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`implies`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(arg1: Expression, arg2: Expression): Expression = implies(arg1)(arg2) + } + object Iff { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`iff`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + } + object And { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`and`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) + } + object Or { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`or`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) + } + object Forall { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`forall`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = forall(Lambda(v, body)) + } + object Exists { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`exists`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = exists(Lambda(v, body)) + } + object Epsilon { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`epsilon`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = epsilon(Lambda(v, body)) + } + + + 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) + + +} From 690a9dea81a274a2b59f5359923fe3687f0d8fc3 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Wed, 9 Oct 2024 00:20:15 +0200 Subject: [PATCH 02/92] Added Sequent calculus and proofs, but not yet proof checking. --- .../scala/lisa/kernel/lambdafol/FOL.scala | 10 + .../lisa/kernel/lambdafol/Substitutions.scala | 5 - .../scala/lisa/kernel/lambdafol/Syntax.scala | 22 +- .../lisa/kernel/lambdaproof/SCProof.scala | 97 +++++ .../kernel/lambdaproof/SequentCalculus.scala | 353 ++++++++++++++++++ 5 files changed, 481 insertions(+), 6 deletions(-) create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/FOL.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/FOL.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/FOL.scala new file mode 100644 index 00000000..1187aced --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/FOL.scala @@ -0,0 +1,10 @@ +package lisa.kernel.lambdafol + +/** + * The concrete implementation of first order logic. + * All its content can be imported using a single statement: + *
+ * import lisa.fol.FOL._
+ * 
+ */ +object FOL extends OLEquivalenceChecker {} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala deleted file mode 100644 index 1663d1a3..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Substitutions.scala +++ /dev/null @@ -1,5 +0,0 @@ -package lisa.kernel.lambdafol - -trait Substitutions extends OLEquivalenceChecker{ - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala index e68c31b0..2691bb05 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala @@ -54,7 +54,7 @@ private[lambdafol] trait Syntax { } } - protected trait Expression { + sealed trait Expression { val typ: Type val uniqueNumber: Long = ExpressionCounters.getNewId val containsFormulas : Boolean @@ -186,4 +186,24 @@ private[lambdafol] trait Syntax { 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.typ == v.typ) r + else throw new IllegalArgumentException("Type 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) => + Lambda(v, substituteVariables(t, m - v)) + } + } diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala new file mode 100644 index 00000000..fc944d89 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala @@ -0,0 +1,97 @@ +package lisa.kernel.lambdaproof + +import lisa.kernel.lambdaproof.SequentCalculus._ + +/** + * A SCPRoof (for Sequent Calculus Proof) is a (dependant) proof. While technically a proof is an Directed Acyclic Graph, + * here proofs are linearized and represented as a list of proof steps. + * Moreover, a proof can depend on some assumed, unproved, sequents specified in the second argument + * @param steps A list of Proof Steps that should form a valid proof. Each individual step should only refer to earlier + * proof steps as premisces. + * @param imports A list of assumed sequents that further steps may refer to. Imports are refered to using negative integers + * To refer to the first sequent of imports, use integer -1. + */ +case class SCProof(steps: IndexedSeq[SCProofStep], imports: IndexedSeq[Sequent] = IndexedSeq.empty) { + def numberedSteps: Seq[(SCProofStep, Int)] = steps.zipWithIndex + + /** + * Fetches the ith step of the proof. + * @param i the index + * @return a step + */ + def apply(i: Int): SCProofStep = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i) + else throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + } + + /** + * Get the ith sequent of the proof. If the index is positive, give the bottom sequent of proof step number i. + * If the index is negative, return the (-i-1)th imported sequent. + * + * @param i The reference number of a sequent in the proof + * @return A sequent, either imported or reached during the proof. + */ + def getSequent(i: Int): Sequent = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(i2) + } + } + + /** + * The length of the proof in terms of top-level steps, without including the imports. + */ + def length: Int = steps.length + + /** + * The total length of the proof in terms of proof-step, including steps in subproof, but excluding the imports. + */ + def totalLength: Int = steps.foldLeft(0)((i, s) => + i + (s match { + case s: SCSubproof => s.sp.totalLength + 1 + case _ => 1 + }) + ) + + /** + * The conclusion of the proof, namely the bottom sequent of the last proof step. + * Can be undefined if the proof is empty. + */ + def conclusion: Sequent = { + if (steps.isEmpty && imports.isEmpty) throw new NoSuchElementException("conclusion of an empty proof") + this.getSequent(length - 1) + } + + /** + * A helper method that creates a new proof with a new step appended at the end. + * @param newStep the new step to be added + * @return a new proof + */ + def appended(newStep: SCProofStep): SCProof = copy(steps = steps appended newStep) + + /** + * A helper method that creates a new proof with a sequence of new steps appended at the end. + * @param newSteps the sequence of steps to be added + * @return a new proof + */ + def withNewSteps(newSteps: IndexedSeq[SCProofStep]): SCProof = copy(steps = steps ++ newSteps) +} + +object SCProof { + + /** + * Instantiates a proof from an indexed list of proof steps. + * @param steps the steps of the proof + * @return the corresponding proof + */ + def apply(steps: SCProofStep*): SCProof = { + SCProof(steps.toIndexedSeq) + } + +} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala new file mode 100644 index 00000000..3acc44c3 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala @@ -0,0 +1,353 @@ +package lisa.kernel.lambdaproof + +import lisa.kernel.lambdafol.FOL._ + +/** + * The concrete implementation of sequent calculus (with equality). + * This file specifies the sequents and the allowed operations on them, the deduction rules of sequent calculus. + * It contains typical sequent calculus rules for FOL with equality as can be found in a text book, as well as a couple more for + * non-elementary symbols (⇔, ∃!) and rules for substituting equal terms or equivalent formulas. I also contains two structural rules, + * subproof and a dummy rewrite step. + * Further mathematical steps, such as introducing or using definitions, axioms or theorems are not part of the basic sequent calculus. + */ +object SequentCalculus { + + /** + * A sequent is an object that can contain two sets of formulas, [[left]] and [[right]]. + * The intended semantic is for the [[left]] formulas to be interpreted as a conjunction, while the [[right]] ones as a disjunction. + * Traditionally, sequents are represented by two lists of formulas. + * Since sequent calculus includes rules for permuting and weakening, it is in essence equivalent to sets. + * Seqs make verifying proof steps much easier, but proof construction much more verbose and proofs longer. + * @param left the left side of the sequent + * @param right the right side of the sequent + */ + case class Sequent(left: Set[Expression], right: Set[Expression]){ + require(left.forall(_.typ == Formula) && right.forall(_.typ == Formula), "Sequent can only contain formulas") + } + + /** + * Simple method that transforms a sequent to a logically equivalent formula. + */ + def sequentToFormula(s: Sequent): Expression = Implies(And(s.left), Or(s.right)) + + /** + * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. + * + * @param l the first sequent + * @param r the second sequent + * @return see [[isSameTerm]] + */ + def isSameSequent(l: Sequent, r: Sequent): Boolean = isSame(sequentToFormula(l), sequentToFormula(r)) + + /** + * Checks whether a given sequent implies another, with respect to [[latticeLEQ]]. + * + * @param l the first sequent + * @param r the second sequent + * @return see [[latticeLEQ]] + */ + def isImplyingSequent(l: Sequent, r: Sequent): Boolean = isImplying(sequentToFormula(l), sequentToFormula(r)) + + /** + * The parent of all proof steps types. + * A proof step is a deduction rule of sequent calculus, with the sequents forming the prerequisite and conclusion. + * For easier linearisation of the proof, the prerequisite are represented with numbers showing the place in the proof of the sequent used. + */ + + /** + * The parent of all sequent calculus rules. + */ + sealed trait SCProofStep { + val bot: Sequent + val premises: Seq[Int] + } + + /** + *
+   *    Γ |- Δ
+   * ------------
+   *    Γ |- Δ  (OL rewrite)
+   * 
+ */ + case class Restate(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *
+   * ------------
+   *    Γ |- Γ  (OL tautology)
+   * 
+ */ + case class RestateTrue(bot: Sequent) extends SCProofStep { val premises = Seq() } + + /** + *
+   *
+   * --------------
+   *   Γ, φ |- φ, Δ
+   * 
+ */ + case class Hypothesis(bot: Sequent, phi: Expression) extends SCProofStep { val premises = Seq() } + + /** + *
+   *  Γ |- Δ, φ    φ, Σ |- Π
+   * ------------------------
+   *       Γ, Σ |-Δ, Π
+   * 
+ */ + case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } + + // Left rules + /** + *
+   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
+   * --------------     or     --------------
+   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
+   * 
+ */ + case class LeftAnd(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
+   * --------------------------------
+   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
+   * 
+ */ + case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Expression]) extends SCProofStep { val premises = t } + + /** + *
+   *  Γ |- φ, Δ    Σ, ψ |- Π
+   * ------------------------
+   *    Γ, Σ, φ⇒ψ |- Δ, Π
+   * 
+ */ + case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } + + /** + *
+   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
+   * --------------    or     --------------------
+   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
+   * 
+ */ + case class LeftIff(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ |- φ, Δ
+   * --------------
+   *   Γ, ¬φ |- Δ
+   * 
+ */ + case class LeftNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ, φ[t/x] |- Δ
+   * -------------------
+   *  Γ, ∀ φ |- Δ
+   *
+   * 
+ */ + case class LeftForall(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ, φ |- Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ, ∃x φ|- Δ
+   *
+   * 
+ */ + case class LeftExists(bot: Sequent, t1: Int, phi: Expression, x: Variable) 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: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + + // Right rules + /** + *
+   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
+   * ------------------------------------
+   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
+   * 
+ */ + case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Expression]) extends SCProofStep { val premises = t } + + /** + *
+   *   Γ |- φ, Δ                Γ |- φ, ψ, Δ
+   * --------------    or    ---------------
+   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
+   * 
+ */ + case class RightOr(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ, φ |- ψ, Δ
+   * --------------
+   *  Γ |- φ⇒ψ, Δ
+   * 
+ */ + case class RightImplies(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ |- a⇒ψ, Δ    Σ |- ψ⇒φ, Π
+   * ----------------------------
+   *      Γ, Σ |- φ⇔ψ, Π, Δ
+   * 
+ */ + case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } + + /** + *
+   *  Γ, φ |- Δ
+   * --------------
+   *   Γ |- ¬φ, Δ
+   * 
+ */ + case class RightNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ, Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ |- ∀x. φ, Δ
+   * 
+ */ + case class RightForall(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ |- φ[t/x], Δ
+   * -------------------
+   *  Γ |- ∃x. φ, Δ
+   *
+   * (ln-x stands for locally nameless x)
+   * 
+ */ + 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. φ,  Δ
+   * 
+ */ + case class RightExistsOne(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + + // Structural rule + /** + *
+   *     Γ |- Δ
+   * --------------
+   *   Γ, Σ |- Δ, Π
+   * 
+ */ + case class Weakening(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + // Equality Rules + /** + *
+   *  Γ, s=s |- Δ
+   * --------------
+   *     Γ |- Δ
+   * 
+ */ + case class LeftRefl(bot: Sequent, t1: Int, fa: Expression) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *
+   * --------------
+   *     |- s=s
+   * 
+ */ + case class RightRefl(bot: Sequent, fa: Expression) extends SCProofStep { val premises = Seq() } + + /** + *
+   *    Γ, φ(s1,...,sn) |- Δ
+   * ---------------------
+   *  Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ
+   * 
+ * equals elements must have type Term -> ... -> 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: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ(s1,...,sn), Δ
+   * ---------------------
+   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
+   * 
+ * equals elements must have type Term -> ... -> Term + */ + case class RightSubstEq(bot: Sequent, t1: Int, equals: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ, φ(a1,...an) |- Δ
+   * ---------------------
+   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   * 
+ * equals elements must have type Term -> ... -> Term + */ + case class LeftSubstIff(bot: Sequent, t1: Int, equals: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ(a1,...an), Δ
+   * ---------------------
+   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   * 
+ * equals elements must have type Term -> ... -> Term + */ + + case class RightSubstIff(bot: Sequent, t1: Int, equals: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } + + // Rule for schemas + + case class InstSchema( + bot: Sequent, + t1: Int, + mCon: Map[Variable, Expression] + ) extends SCProofStep { val premises = Seq(t1) } + + // Proof Organisation rules + + /** + * Encapsulate a proof into a single step. The imports of the subproof correspond to the premisces of the step. + * @param sp The encapsulated subproof. + * @param premises The indices of steps on the outside proof that are equivalent to the import of the subproof. + * @param display A boolean value indicating whether the subproof needs to be expanded when printed. Should probably go and + * be replaced by encapsulation. + */ + case class SCSubproof(sp: SCProof, premises: Seq[Int] = Seq.empty) extends SCProofStep { + // premises is a list of ints similar to t1, t2... that verifies that imports of the subproof sp are justified by previous steps. + val bot: Sequent = sp.conclusion + } + + /** + *
+   *
+   * --------------
+   *   Γ  |- Δ
+   * 
+ */ + case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } + +} \ No newline at end of file From 8747557314e5b475ae611cac3f187d902ebd956f Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Wed, 9 Oct 2024 17:44:15 +0200 Subject: [PATCH 03/92] Finished proof checker, have to deal with definitions and beta redutions. --- .../scala/lisa/kernel/lambdafol/Syntax.scala | 29 +- .../lisa/kernel/lambdaproof/Judgement.scala | 76 ++ .../kernel/lambdaproof/RunningTheory.scala | 357 ++++++++++ .../kernel/lambdaproof/SCProofChecker.scala | 654 ++++++++++++++++++ .../kernel/lambdaproof/SequentCalculus.scala | 2 +- 5 files changed, 1114 insertions(+), 4 deletions(-) create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala index 2691bb05..eee66dfe 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala @@ -34,10 +34,31 @@ private[lambdafol] trait Syntax { sealed trait Type { def ->(to: Type): Arrow = Arrow(this, to) + val isFunctional: Boolean + val isPredicate: Boolean + val depth: Int } - case object Term extends Type - case object Formula extends Type - sealed case class Arrow(from: Type, to: Type) extends Type + case object Term extends Type { + val isFunctional = true + val isPredicate = false + val depth = 0 + } + case object Formula extends Type { + val isFunctional = false + val isPredicate = true + val depth = 0 + } + sealed case class Arrow(from: Type, to: Type) extends Type { + val isFunctional = from == Term && to.isFunctional + val isPredicate = from == Term && to.isPredicate + val depth = 1+to.depth + } + + def depth(t:Type): Int = t match { + case Arrow(a, b) => 1 + depth(b) + case _ => 0 + } + def legalApplication(typ1: Type, typ2: Type): Option[Type] = { typ1 match { @@ -114,6 +135,7 @@ private[lambdafol] trait Syntax { case Application(Application(`equality`, arg1), arg2) => Some((arg1, arg2)) case _ => None } + def apply(arg1: Expression, arg2: Expression): Expression = equality(arg1)(arg2) } object Neg { @@ -135,6 +157,7 @@ private[lambdafol] trait Syntax { case Application(Application(`iff`, arg1), arg2) => Some((arg1, arg2)) case _ => None } + def apply(arg1: Expression, arg2: Expression): Expression = iff(arg1)(arg2) } object And { def unapply (e: Expression): Option[(Expression, Expression)] = e match { diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala new file mode 100644 index 00000000..9f55dd14 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala @@ -0,0 +1,76 @@ +package lisa.kernel.lambdaproof + +import lisa.kernel.lambdaproof.RunningTheory + +/** + * The judgement (or verdict) of a proof checking procedure. + * Typically, see [[SCProofChecker.checkSingleSCStep]] and [[SCProofChecker.checkSCProof]]. + */ +sealed abstract class SCProofCheckerJudgement { + import SCProofCheckerJudgement._ + val proof: SCProof + + /** + * Whether this judgement is positive -- the proof is concluded to be valid; + * or negative -- the proof checker couldn't certify the validity of this proof. + * @return An instance of either [[SCValidProof]] or [[SCInvalidProof]] + */ + def isValid: Boolean = this match { + case _: SCValidProof => true + case _: SCInvalidProof => false + } +} + +object SCProofCheckerJudgement { + + /** + * A positive judgement. + */ + case class SCValidProof(proof: SCProof, val usesSorry: Boolean = false) extends SCProofCheckerJudgement + + /** + * A negative judgement. + * @param path The path of the error, expressed as indices + * @param message The error message that hints about the first error encountered + */ + case class SCInvalidProof(proof: SCProof, path: Seq[Int], message: String) extends SCProofCheckerJudgement +} + +/** + * The judgement (or verdict) of a running theory. + */ +sealed abstract class RunningTheoryJudgement[+J <: RunningTheory#Justification] { + import RunningTheoryJudgement._ + + /** + * Whether this judgement is positive -- the justification could be imported into the running theory; + * or negative -- the justification is not suitable to be imported in the theory. + * @return An instance of either [[ValidJustification]] or [[InvalidJustification]] + */ + def isValid: Boolean = this match { + case _: ValidJustification[_] => true + case _: InvalidJustification[_] => false + } + def get: J = this match { + case ValidJustification(just) => just + case InvalidJustification(message, error) => + throw InvalidJustificationException(message, error) + } +} + +object RunningTheoryJudgement { + + /** + * A positive judgement. + */ + case class ValidJustification[J <: RunningTheory#Justification](just: J) extends RunningTheoryJudgement[J] + + /** + * A negative judgement. + * @param error If the justification is rejected because the proof is wrong, will contain the error in the proof. + * @param message The error message that hints about the first error encountered + */ + case class InvalidJustification[J <: RunningTheory#Justification](message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends RunningTheoryJudgement[J] + + case class InvalidJustificationException(message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends Exception(message) +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala new file mode 100644 index 00000000..e85b44cb --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala @@ -0,0 +1,357 @@ +package lisa.kernel.lambdaproof + +import lisa.kernel.lambdafol.FOL._ +import lisa.kernel.lambdaproof.RunningTheoryJudgement._ +import lisa.kernel.lambdaproof.SequentCalculus._ + +import scala.collection.immutable.Set +import scala.collection.mutable.{Map => mMap} + +/** + * This class describes the theory, i.e. the context and language, in which theorems are proven. + * A theory is built from scratch by introducing axioms and symbols first, then by definitional extensions. + * The structure is one-way mutable: Once an axiom or definition has been introduced, it can't be removed. + * On the other hand, theorems proven before the theory is extended will still hold. + * A theorem only holds true within a specific theory. + * A theory is responsible to make sure that a symbol already defined or present in the language can't + * be redefined. If a theory needs to be extanded in two different ways, or if a theory and its extension need + * to coexist independently, they should be different instances of this class. + */ +class RunningTheory { + + /** + * A Justification is either a Theorem, an Axiom or a Definition + */ + sealed abstract class Justification + + /** + * A theorem encapsulate a sequent and assert that this sequent has been correctly proven and may be used safely in further proofs. + */ + sealed case class Theorem private[RunningTheory] (name: String, proposition: Sequent, withSorry: Boolean) extends Justification + + /** + * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. + */ + sealed case class Axiom private[RunningTheory] (name: String, ax: Expression) extends Justification + + /** + * A definition of a new symbol. + */ + sealed case class Definition private[RunningTheory] (label: Identifier, expression: Expression) + + private[lambdaproof] val theoryAxioms: mMap[String, Axiom] = mMap.empty + private[lambdaproof] val theorems: mMap[String, Theorem] = mMap.empty + + + private[lambdaproof] val knownSymbols: mMap[Identifier, Constant] = mMap(equality.id -> equality) + + /** + * From a given proof, if it is true in the Running theory, add that theorem to the theory and returns it. + * The proof's imports must be justified by the list of justification, and the conclusion of the theorem + * can't contain symbols that do not belong to the theory. + * + * @param justifications The list of justifications of the proof's imports. + * @param proof The proof of the desired Theorem. + * @return A Theorem if the proof is correct, None else + */ + def makeTheorem(name: String, statement: Sequent, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = { + if (proof.conclusion == statement) proofToTheorem(name, proof, justifications) + else InvalidJustification("The proof does not prove the claimed statement", None) + } + + private def proofToTheorem(name: String, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = + if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) + if (belongsToTheory(proof.conclusion)) { + val r = SCProofChecker.checkSCProof(proof) + r match { + case SCProofCheckerJudgement.SCValidProof(_, sorry) => + 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 + } + }) + val thm = Theorem(name, proof.conclusion, usesSorry) + theorems.update(name, thm) + ValidJustification(thm) + case r @ SCProofCheckerJudgement.SCInvalidProof(_, _, message) => + InvalidJustification("The given proof is incorrect: " + message, Some(r)) + } + } 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) + } + + /** + * Introduce a new definition of a function in the theory. The symbol must not already exist in the theory + * and the formula can't contain symbols that are not in the theory. The existence and uniqueness of an element + * satisfying the definition's formula must first be proven. This is easy if the formula behaves as a shortcut, + * for example f(x,y) = 3x+2y + * but is much more general. The proof's conclusion must be of the form: |- ∀args. ∃!out. phi + * + * @param proof The proof of existence and uniqueness + * @param justifications The justifications of the proof. + * @param label The desired label. + * @param expression The functional term defining the function symbol. + * @param out The variable representing the function's result in the formula + * @param proven A formula possibly stronger than `expression` that the proof proves. It is always correct if it is the same as "expression", but + * if `expression` is less strong, this allows to make underspecified definitions. + * @return A definition object if the parameters are correct, + */ + def makeFunctionDefinition( + proof: SCProof, + justifications: Seq[Justification], + label: ConstantFunctionLabel, + out: VariableLabel, + expression: LambdaTermFormula, + proven: Formula + ): RunningTheoryJudgement[this.FunctionDefinition] = { + val LambdaTermFormula(vars, body) = expression + if (vars.length == label.arity) { + if (belongsToTheory(body)) { + if (isAvailable(label)) { + if (body.freeSchematicTermLabels.subsetOf((vars appended out).toSet) && body.schematicFormulaLabels.isEmpty) { + if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) { + val r = SCProofChecker.checkSCProof(proof) + r match { + case SCProofCheckerJudgement.SCValidProof(_, sorry) => + proof.conclusion match { + case Sequent(l, r) if l.isEmpty && r.size == 1 => + if (isImplying(proven, body)) { + val subst = BinderFormula(ExistsOne, out, proven) + if (isSame(r.head, subst)) { + 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 + } + }) + val newDef = FunctionDefinition(label, out, expression, usesSorry) + funDefinitions.update(label, Some(newDef)) + knownSymbols.update(label.id, label) + RunningTheoryJudgement.ValidJustification(newDef) + } else InvalidJustification("The proof is correct but its conclusion does not correspond to the claimed proven property.", None) + } else InvalidJustification("The proven property must be at least as strong as the desired definition, and it is not.", None) + + case _ => InvalidJustification("The conclusion of the proof must have an empty left hand side, and a single formula on the right hand side.", None) + } + case r @ SCProofCheckerJudgement.SCInvalidProof(_, path, message) => InvalidJustification("The given proof is incorrect: " + message, Some(r)) + } + } else InvalidJustification("Not all imports of the proof are correctly justified.", None) + } 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) + } 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)) + + } + + /** + * Add a new axiom to the Theory. For example, if the theory contains the language and theorems + * of Zermelo-Fraenkel Set Theory, this function may add the axiom of choice to it. + * If the axiom belongs to the language of the theory, adds it and return true. Else, returns false. + * + * @param f the new axiom to be added. + * @return true if the axiom was added to the theory, false else. + */ + def addAxiom(name: String, f: Formula): Option[Axiom] = { + if (belongsToTheory(f)) { + val ax = Axiom(name, f) + theoryAxioms.update(name, ax) + Some(ax) + } else None + } + + /** + * Add a new symbol to the theory, without providing a definition. An ad-hoc definition can be + * added via an axiom, typically if the desired object is not derivable in the base theory itself. + * For example, This function can add the empty set symbol to a theory, and then an axiom asserting + * 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) + } + } 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) + } + + /** + * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. + */ + def makeSequentBelongToTheory(s: Sequent): Unit = { + s.left.foreach(makeFormulaBelongToTheory) + s.right.foreach(makeFormulaBelongToTheory) + } + + /** + * 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. + * + * @param t The term 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) + } + + } + + /** + * Verify if a given sequent belongs to the language of the theory. + * + * @param s The sequent to check + * @return Weather s belongs to the specified language + */ + def belongsToTheory(s: Sequent): Boolean = + s.left.forall(belongsToTheory) && s.right.forall(belongsToTheory) + + /** + * Public accessor to the set of symbol currently in the theory's language. + * + * @return the set of symbol currently in the theory's language. + */ + def language(): List[(ConstantLabel, Option[Definition])] = funDefinitions.toList ++ predDefinitions.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) + } + + /** + * Check if a label is not already used in the theory. + * @return + */ + def isAvailable(label: ConstantLabel): Boolean = !knownSymbols.contains(label.id) + + /** + * Public accessor to the current set of axioms of the theory + * + * @return the current set of axioms of the theory + */ + def axiomsList(): Iterable[Axiom] = theoryAxioms.values + + /** + * Verify if a given formula is an axiom of the theory + */ + def isAxiom(f: Formula): Boolean = 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 + + /** + * Get the definition of the given label, if it is defined in the theory. + */ + def getDefinition(label: ConstantFunctionLabel): Option[FunctionDefinition] = funDefinitions.get(label).flatten + + /** + * Get the Axiom with the given name, if it exists in the theory. + */ + def getAxiom(name: String): Option[Axiom] = theoryAxioms.get(name) + + /** + * Get the Theorem with the given name, if it exists in the theory. + */ + def getTheorem(name: String): Option[Theorem] = theorems.get(name) + + /** + * 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) + } + +} +object RunningTheory { + + /** + * An empty theory suitable to reason about first order logic. + */ + def PredicateLogic: RunningTheory = new RunningTheory() +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala new file mode 100644 index 00000000..938761ae --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala @@ -0,0 +1,654 @@ +package lisa.kernel.lambdaproof + +import lisa.kernel.lambdafol.FOL._ +import lisa.kernel.lambdaproof.SCProofCheckerJudgement._ +import lisa.kernel.lambdaproof.SequentCalculus._ + + +object SCProofChecker { + + /** + * This function verifies that a single SCProofStep is correctly applied. It verifies that the step only refers to sequents with a lower number, + * and that the type, premises and parameters of the proof step correspond to the claimed conclusion. + * + * @param no The number of the given proof step. Needed to vewrify that the proof step doesn't refer to posterior sequents. + * @param step The proof step whose correctness needs to be checked + * @param references A function that associates sequents to a range of positive and negative integers that the proof step may refer to. Typically, + * a proof's [[SCProof.getSequent]] function. + * @return A Judgement about the correctness of the proof step. + */ + def checkSingleSCStep(no: Int, step: SCProofStep, references: Int => Sequent, importsSize: Int): SCProofCheckerJudgement = { + val ref = references + val false_premise = step.premises.find(i => i >= no) + val false_premise2 = step.premises.find(i => i < -importsSize) + + val r: SCProofCheckerJudgement = + if (false_premise.nonEmpty) + SCInvalidProof(SCProof(step), Nil, s"Step no $no can't refer to higher number ${false_premise.get} as a premise.") + else if (false_premise2.nonEmpty) + SCInvalidProof(SCProof(step), Nil, s"A step can't refer to step ${false_premise2.get}, imports only contains ${importsSize} elements.") + else + step match { + /* + * Γ |- Δ + * ------------ + * Γ |- Δ + */ + case Restate(s, t1) => + if (isSameSequent(ref(t1), s)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The premise does not trivially imply the conclusion.") + + /* + * + * ------------ + * Γ |- Γ + */ + case RestateTrue(s) => + 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") + /* + * + * -------------- + * Γ, φ |- φ, Δ + */ + case Hypothesis(Sequent(left, right), phi) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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 (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of first premise does not contain φ as claimed.") + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of second premise does not contain φ as claimed.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ is not 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.") + + // Left rules + /* + * Γ, φ |- Δ Γ, φ, ψ |- Δ + * -------------- or ------------- + * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ + */ + case LeftAnd(b, t1, phi, psi) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + else if (isSameSet(ref(t1).right, b.right)) { + val phiAndPsi = And(Seq(phi, psi)) + if ( + isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || + isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || + isSameSet(b.left + phi + psi, ref(t1).left + phiAndPsi) + ) + 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 the premise and the conclusion must be the same.") + /* + * Γ, φ |- Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ∨ψ |- Δ, Π + */ + case LeftOr(b, t, disjuncts) => + if (disjuncts.exists(phi => phi.typ != Formula)){ + val culprit = disjuncts.find(phi => phi.typ != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) + } else if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { + val phiOrPsi = Or(disjuncts) + 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.") + /* + * Γ |- φ, Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ⇒ψ |- Δ, Π + */ + case LeftImplies(b, t1, t2, phi, psi) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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.") + } + + /* + * Γ |- φ, Δ + * -------------- + * Γ, ¬φ |- Δ + */ + case LeftNot(b, t1, phi) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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] |- Δ + * ------------------- + * Γ, ∀x. φ |- Δ + */ + case LeftForall(b, t1, phi, x, t) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + else if (t.typ != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + else if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + substituteVariables(phi, Map(x -> t)), ref(t1).left + Forall(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") + + /* + * Γ, φ |- Δ + * ------------------- if x is not free in the resulting sequent + * Γ, ∃x. φ|- Δ + */ + case LeftExists(b, t1, phi, x) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + else if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left + Exists(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) => + ??? + + // Right rules + /* + * Γ |- φ, Δ Σ |- ψ, Π + * ------------------------ + * Γ, Σ |- φ∧ψ, Π, Δ + */ + case RightAnd(b, t, cunjuncts) => + if (cunjuncts.exists(phi => phi.typ != Formula)){ + val culprit = cunjuncts.find(phi => phi.typ != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) + } else { + val phiAndPsi = 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.") + } + /* + * Γ |- φ, Δ Γ |- φ, ψ, Δ + * -------------- or --------------- + * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ + */ + case RightOr(b, t1, phi, psi) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + else { + val phiOrPsi = 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.") + } + /* + * Γ, φ |- ψ, Δ + * -------------- + * Γ |- φ⇒ψ, Δ + */ + case RightImplies(b, t1, phi, psi) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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 (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + else if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + phi, ref(t1).right + Forall(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, "Right-hand side of conclusion + φ 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.") + /* + * Γ |- φ[t/x], Δ + * ------------------- + * Γ |- ∃x. φ, Δ + */ + case RightExists(b, t1, phi, x, t) => + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + else if (t.typ != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + else if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + substituteVariables(phi, Map(x -> t)), ref(t1).right + Exists(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. φ,  Δ
+           * 
+ */ + case RightExistsOne(b, t1, phi, x) => + ??? + + // Structural rules + /* + * Γ |- Δ + * -------------- + * Γ, Σ |- Δ + */ + case Weakening(b, t1) => + if (isImplyingSequent(ref(t1), b)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Conclusion cannot be trivially derived from premise.") + + // Equality Rules + /* + * Γ, s=s |- Δ + * -------------- + * Γ |- Δ + */ + case LeftRefl(b, t1, phi) => + phi match { + case Equality(left, right) => + if (isSameTerm(left, right)) + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(SCProof(step), Nil, "φ is not an equality") + } + + /* + * + * -------------- + * |- s=s + */ + case RightRefl(b, phi) => + phi match { + case Equality(left, right) => + if (isSameTerm(left, right)) + if (contains(b.right, phi)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-Hand side of conclusion does not contain φ") + else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(SCProof(step), Nil, s"φ is not an equality.") + } + + /* + * Γ, φ(s_) |- Δ + * --------------------- + * Γ, (s=t)_, φ(t_)|- Δ + */ + 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) + 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.typ != arg.typ || t.typ != arg.typ }) + SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") + else if (phi_args.exists { arg => !arg.typ.isFunctional }) + SCInvalidProof(SCProof(step), Nil, "Can only substitute functional-like terms (with type Term -> ... -> Term -> Term)") + else { + 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.typ == t.typ) + val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no + val vars = Range(0, depth(s.typ)).map(n => Variable(Identifier("x", no+n), Term)) + val inner1 = vars.foldLeft(s) { case (acc, v) => acc(v)} + val inner2 = vars.foldLeft(t) { case (acc, v) => acc(v) } + vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => Forall(s_arg, acc) } + + } + + if (isSameSet(b.right, ref(t1).right)) + if ( + isSameSet(b.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || + isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) + ) + 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_) (or with s_ and t_ swapped)." + ) + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + } + + /* + * Γ |- φ(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 != s_es.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.typ != arg.typ || t.typ != arg.typ }) + SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") + else if (phi_args.exists { arg => !arg.typ.isFunctional }) + SCInvalidProof(SCProof(step), Nil, "Can only substitute functional-like terms (with type Term -> ... -> Term -> Term)") + else { + 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.typ == t.typ) + val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no + val vars = Range(0, depth(s.typ)).map(n => Variable(Identifier("x", no+n), Term)) + val inner1 = vars.foldLeft(s) { case (acc, v) => acc(v)} + val inner2 = vars.foldLeft(t) { case (acc, v) => acc(v) } + vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => Forall(s_arg, acc) } + + } + + if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) + 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) + ) + 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 φ(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.") + } + + /* + * Γ, φ(ψ_) |- Δ + * --------------------- + * Γ, ψ⇔τ, φ(τ) |- Δ + */ + case LeftSubstIff(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.typ != arg.typ || t.typ != arg.typ }) + SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") + else if (phi_args.exists { arg => !arg.typ.isPredicate }) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> term -> Formula)") + else { + val phi_tau_for_q = substituteVariables(phi_body, (phi_args zip psi_s).toMap) + val phi_psi_for_q = substituteVariables(phi_body, (phi_args zip tau_s).toMap) + val psiIffTau = equals map { + case (phi, psi) => + assert(phi.typ == psi.typ) // remove + val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no + val vars = Range(0, depth(phi.typ)).map(n => Variable(Identifier("x", no+n), Term)) + val inner1 = vars.foldLeft(phi) { case (acc, v) => acc(v)} + val inner2 = vars.foldLeft(psi) { case (acc, v) => acc(v) } + vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => Forall(s_arg, acc) } + + } + + 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)." + ) + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + } + + /* + * Γ |- φ[ψ/?p], Δ + * --------------------- + * Γ, ψ⇔τ |- φ[τ/?p], Δ + */ + 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.typ != arg.typ || t.typ != arg.typ }) + SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") + else if (phi_args.exists { arg => !arg.typ.isPredicate }) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> term -> Formula)") + else { + val phi_tau_for_q = substituteVariables(phi_body, (phi_args zip psi_s).toMap) + val phi_psi_for_q = substituteVariables(phi_body, (phi_args zip tau_s).toMap) + val psiIffTau = equals map { + case (phi, psi) => + assert(phi.typ == psi.typ) // remove + val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no + val vars = Range(0, depth(phi.typ)).map(n => Variable(Identifier("x", no+n), Term)) + val inner1 = vars.foldLeft(phi) { case (acc, v) => acc(v)} + val inner2 = vars.foldLeft(psi) { case (acc, v) => acc(v) } + vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => Forall(s_arg, acc) } + + } + + if (isSameSet(ref(t1).left ++ psiIffTau, b.left)) + if ( + isSameSet(b.right + phi_tau_for_q, ref(t1).right + phi_psi_for_q) || + isSameSet(b.right + phi_psi_for_q, ref(t1).right + phi_tau_for_q) + ) + 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.") + } + + + + /** + *
+           * Γ |- Δ
+           * --------------------------
+           * Γ[ψ/?p] |- Δ[ψ/?p]
+           * 
+ */ + case InstSchema(bot, t1, subst) => + val expected = + (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)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of premise instantiated with the given maps must be the same as right-hand side of conclusion.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of premise instantiated with the given maps must be the same as left-hand side of conclusion.") + + case SCSubproof(sp, premises) => + if (premises.size == sp.imports.size) { + val invalid = premises.zipWithIndex.find { case (no, p) => !isSameSequent(ref(no), sp.imports(p)) } + if (invalid.isEmpty) { + checkSCProof(sp) + } else + SCInvalidProof( + SCProof(step), + Nil, + s"Premise number ${invalid.get._1} (refering to step ${invalid.get}) is not the same as import number ${invalid.get._1} of the subproof." + ) + } else SCInvalidProof(SCProof(step), Nil, "Number of premises and imports don't match: " + premises.size + " " + sp.imports.size) + + /* + * + * -------------- + * |- s=s + */ + case Sorry(b) => + SCValidProof(SCProof(step), usesSorry = true) + + } + r + } + + /** + * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. + * If the proof is not correct, the function will report the faulty line and a brief explanation. + * + * @param proof A SC proof to check + * @return SCValidProof(SCProof(step)) if the proof is correct, else SCInvalidProof with the path to the incorrect proof step + * and an explanation. + */ + def checkSCProof(proof: SCProof): SCProofCheckerJudgement = { + var isSorry = false + val possibleError = proof.steps.view.zipWithIndex + .map { case (step, no) => + checkSingleSCStep(no, step, (i: Int) => proof.getSequent(i), proof.imports.size) match { + case SCInvalidProof(_, path, message) => SCInvalidProof(proof, no +: path, message) + case SCValidProof(_, sorry) => + isSorry = isSorry || sorry + SCValidProof(proof, sorry) + } + } + .find(j => !j.isValid) + if (possibleError.isEmpty) SCValidProof(proof, isSorry) + else possibleError.get + } + +} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala index 3acc44c3..83e112d2 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala @@ -324,7 +324,7 @@ object SequentCalculus { case class InstSchema( bot: Sequent, t1: Int, - mCon: Map[Variable, Expression] + subst: Map[Variable, Expression] ) extends SCProofStep { val premises = Seq(t1) } // Proof Organisation rules From f62a926bae26121b069a5d748de75ac08609e8f1 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Thu, 10 Oct 2024 00:56:04 +0200 Subject: [PATCH 04/92] Mostly finished Kernel, left with Beta and epsilon rules. --- .../scala/lisa/kernel/lambdafol/Syntax.scala | 12 +- .../kernel/lambdaproof/RunningTheory.scala | 157 ++++------ .../kernel/lambdaproof/SCProofChecker.scala | 270 +++++++++--------- .../kernel/lambdaproof/SequentCalculus.scala | 41 ++- 4 files changed, 214 insertions(+), 266 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala index eee66dfe..3f40346b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala @@ -35,22 +35,19 @@ private[lambdafol] trait Syntax { sealed trait Type { def ->(to: Type): Arrow = Arrow(this, to) val isFunctional: Boolean - val isPredicate: Boolean + def isPredicate: Boolean = !isFunctional val depth: Int } case object Term extends Type { val isFunctional = true - val isPredicate = false val depth = 0 } case object Formula extends Type { val isFunctional = false - val isPredicate = true val depth = 0 } sealed case class Arrow(from: Type, to: Type) extends Type { - val isFunctional = from == Term && to.isFunctional - val isPredicate = from == Term && to.isPredicate + val isFunctional = to.isFunctional val depth = 1+to.depth } @@ -229,4 +226,9 @@ private[lambdafol] trait Syntax { Lambda(v, substituteVariables(t, m - v)) } + def flatTypeParameters(t: Type): List[Type] = t match { + case Arrow(a, b) => a :: flatTypeParameters(b) + case _ => List() + } + } diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala index e85b44cb..5305a492 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala @@ -37,13 +37,16 @@ class RunningTheory { /** * A definition of a new symbol. */ - sealed case class Definition private[RunningTheory] (label: Identifier, expression: Expression) + sealed case class Definition private[RunningTheory] (cst: Constant, expression: Expression, vars: Seq[Variable]) extends Justification private[lambdaproof] val theoryAxioms: mMap[String, Axiom] = mMap.empty private[lambdaproof] val theorems: mMap[String, Theorem] = mMap.empty + private[lambdaproof] 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[lambdaproof] val knownSymbols: mMap[Identifier, Constant] = mMap(equality.id -> equality) + private[lambdaproof] 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. @@ -68,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) @@ -83,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.typ.depth == vars.length) + if (flatTypeParameters(cst.typ) zip vars.map(_.typ) forall { case (a, b) => a == b }) + if (cst.typ == expression.typ) + 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 @@ -169,27 +168,20 @@ 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) => + if (cst.typ.isPredicate){ + val inner = Iff(vars.foldLeft(cst: Expression)(_(_)), vars.foldLeft(e)(_(_))) + Sequent(Set(), Set(inner)) + } else { + val inner = Equality(vars.foldLeft(cst: Expression)(_(_)), vars.foldLeft(e)(_(_))) + Sequent(Set(), Set(inner)) + } } /** @@ -200,8 +192,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.typ == Formula && belongsToTheory(f)) { val ax = Axiom(name, f) theoryAxioms.update(name, ax) Some(ax) @@ -215,22 +207,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) } /** @@ -242,34 +230,16 @@ class RunningTheory { } /** - * Verify if a given formula belongs to some language + * Verify if a given expression belongs to the language of the theory. * - * @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. - * - * @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) } /** @@ -286,21 +256,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 @@ -312,22 +279,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.typ == 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.typ == 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. @@ -342,10 +304,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/lambdaproof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala index 938761ae..daffe881 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala @@ -413,131 +413,124 @@ object SCProofChecker { } /* - * Γ, φ(s_) |- Δ - * --------------------- - * Γ, (s=t)_, φ(t_)|- Δ + * Γ, φ(s) |- Δ Σ |- s=t, Π + * -------------------------------- + * Γ, Σ φ(t) |- Δ, Π */ - 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) - 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.typ != arg.typ || t.typ != arg.typ }) - SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") - else if (phi_args.exists { arg => !arg.typ.isFunctional }) - SCInvalidProof(SCProof(step), Nil, "Can only substitute functional-like terms (with type Term -> ... -> Term -> Term)") + case LeftSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") + else if (!s.typ.isFunctional) + SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") else { - 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.typ == t.typ) - val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no - val vars = Range(0, depth(s.typ)).map(n => Variable(Identifier("x", no+n), Term)) - val inner1 = vars.foldLeft(s) { case (acc, v) => acc(v)} - val inner2 = vars.foldLeft(t) { case (acc, v) => acc(v) } - vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => Forall(s_arg, acc) } - - } + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) + + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val sEqt = Equality(inner1, inner2) + val varss = vars.toSet - if (isSameSet(b.right, ref(t1).right)) + 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.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || - isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) - ) - 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_) (or with s_ and t_ swapped)." - ) + 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.") } /* - * Γ |- φ(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 != s_es.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.typ != arg.typ || t.typ != arg.typ }) - SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") - else if (phi_args.exists { arg => !arg.typ.isFunctional }) - SCInvalidProof(SCProof(step), Nil, "Can only substitute functional-like terms (with type Term -> ... -> Term -> Term)") + case RightSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") + else if (!s.typ.isFunctional) + SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") else { - 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.typ == t.typ) - val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no - val vars = Range(0, depth(s.typ)).map(n => Variable(Identifier("x", no+n), Term)) - val inner1 = vars.foldLeft(s) { case (acc, v) => acc(v)} - val inner2 = vars.foldLeft(t) { case (acc, v) => acc(v) } - vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => Forall(s_arg, acc) } - - } + val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) - 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) - ) - 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 φ(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.") + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val sEqt = Equality(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.") } /* - * Γ, φ(ψ_) |- Δ - * --------------------- - * Γ, ψ⇔τ, φ(τ) |- Δ + * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π + * -------------------------------- + * Γ, Σ φ(b) |- Δ, Π */ - case LeftSubstIff(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.typ != arg.typ || t.typ != arg.typ }) - SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") - else if (phi_args.exists { arg => !arg.typ.isPredicate }) - SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> term -> Formula)") + case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else if (!psi.typ.isPredicate) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") else { - val phi_tau_for_q = substituteVariables(phi_body, (phi_args zip psi_s).toMap) - val phi_psi_for_q = substituteVariables(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equals map { - case (phi, psi) => - assert(phi.typ == psi.typ) // remove - val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no - val vars = Range(0, depth(phi.typ)).map(n => Variable(Identifier("x", no+n), Term)) - val inner1 = vars.foldLeft(phi) { case (acc, v) => acc(v)} - val inner2 = vars.foldLeft(psi) { case (acc, v) => acc(v) } - vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => 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)) + + val inner1 = vars.foldLeft(psi)(_(_)) + val inner2 = vars.foldLeft(tau)(_(_)) + val sEqt = Iff(inner1, inner2) + val varss = vars.toSet - if (isSameSet(b.right, ref(t1).right)) + 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.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)." - ) + 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.") } @@ -546,42 +539,37 @@ object SCProofChecker { * --------------------- * Γ, ψ⇔τ |- φ[τ/?p], Δ */ - 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.typ != arg.typ || t.typ != arg.typ }) - SCInvalidProof(SCProof(step), Nil, "The types of symbols in φ must be the same as the types of ψ and τ.") - else if (phi_args.exists { arg => !arg.typ.isPredicate }) - SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> term -> Formula)") + case RightSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else if (!psi.typ.isPredicate) + SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") else { - val phi_tau_for_q = substituteVariables(phi_body, (phi_args zip psi_s).toMap) - val phi_psi_for_q = substituteVariables(phi_body, (phi_args zip tau_s).toMap) - val psiIffTau = equals map { - case (phi, psi) => - assert(phi.typ == psi.typ) // remove - val no = freshId(equals.flatMap { case (s, t) => s.allVariables.map(_.id) ++ t.allVariables.map(_.id) }, Identifier("x")).no - val vars = Range(0, depth(phi.typ)).map(n => Variable(Identifier("x", no+n), Term)) - val inner1 = vars.foldLeft(phi) { case (acc, v) => acc(v)} - val inner2 = vars.foldLeft(psi) { case (acc, v) => acc(v) } - vars.foldLeft(Equality(inner1, inner2)) { case (acc, s_arg) => 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(ref(t1).left ++ psiIffTau, b.left)) - if ( - isSameSet(b.right + phi_tau_for_q, ref(t1).right + phi_psi_for_q) || - isSameSet(b.right + phi_psi_for_q, ref(t1).right + phi_tau_for_q) - ) - 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.") + 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.") } diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala index 83e112d2..d493f870 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala @@ -279,45 +279,44 @@ object SequentCalculus { /** *
-   *    Γ, φ(s1,...,sn) |- Δ
-   * ---------------------
-   *  Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ
+   *  Γ, φ(s) |- Δ     Σ1 |- s=t, Π     
+   * ----------------------------------------
+   *             Γ, Σ φ(t) |- Δ, Π
    * 
- * equals elements must have type Term -> ... -> Term + * 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: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } + case class LeftSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *    Γ |- φ(s1,...,sn), Δ
-   * ---------------------
-   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
+   *  Γ |- φ(s), Δ     Σ1 |- s=t, Π
+   * ---------------------------------
+   *         Γ, Σ |- φ(t), Δ, Π
    * 
- * equals elements must have type Term -> ... -> Term + * equals elements must have type ... -> ... -> Term */ - case class RightSubstEq(bot: Sequent, t1: Int, equals: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } + case class RightSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *    Γ, φ(a1,...an) |- Δ
-   * ---------------------
-   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   *   Γ, φ(ψ) |- Δ     Σ |- ψ⇔τ, Π     
+   * --------------------------------
+   *        Γ, Σ φ(τ) |- Δ, Π
    * 
- * equals elements must have type Term -> ... -> Term + * equals elements must have type ... -> ... -> Formula */ - case class LeftSubstIff(bot: Sequent, t1: Int, equals: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) 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) } /** *
-   *    Γ |- φ(a1,...an), Δ
-   * ---------------------
-   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   *   Γ |- φ(ψ), Δ     Σ |- ψ⇔τ, Π     
+   * --------------------------------
+   *        Γ, Σ |- φ(τ), Δ, Π
    * 
- * equals elements must have type Term -> ... -> Term + * equals elements must have type ... -> ... -> Formula */ - - case class RightSubstIff(bot: Sequent, t1: Int, equals: List[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) 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) } // Rule for schemas From 414f027383747b72b778c0774fa49398c1ec75b3 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Fri, 11 Oct 2024 11:16:32 +0200 Subject: [PATCH 05/92] Update Kernel helper, serialization, and more. Next step: rest of TPTP parsing, utils.fol, utils.prooflib. --- .../lisa/kernel/fol/CommonDefinitions.scala | 57 -- .../lisa/kernel/fol/EquivalenceChecker.scala | 810 ------------------ .../src/main/scala/lisa/kernel/fol/FOL.scala | 3 +- .../lisa/kernel/fol/FormulaDefinitions.scala | 138 --- .../kernel/fol/FormulaLabelDefinitions.scala | 112 --- .../scala/lisa/kernel/fol/Substitutions.scala | 244 ------ .../lisa/kernel/fol/TermDefinitions.scala | 84 -- .../kernel/fol/TermLabelDefinitions.scala | 52 -- .../scala/lisa/kernel/lambdafol/FOL.scala | 10 - .../lambdafol/OLEquivalenceChecker.scala | 484 ----------- .../scala/lisa/kernel/lambdafol/Syntax.scala | 234 ----- .../lisa/kernel/lambdaproof/Judgement.scala | 76 -- .../kernel/lambdaproof/RunningTheory.scala | 316 ------- .../lisa/kernel/lambdaproof/SCProof.scala | 97 --- .../kernel/lambdaproof/SCProofChecker.scala | 642 -------------- .../kernel/lambdaproof/SequentCalculus.scala | 352 -------- .../lisa/kernel/proof/RunningTheory.scala | 183 ++-- .../scala/lisa/kernel/proof/SCProof.scala | 2 +- .../lisa/kernel/proof/SCProofChecker.scala | 596 ++++++++----- .../lisa/kernel/proof/SequentCalculus.scala | 123 +-- .../scala/lisa/prooflib/ProofsHelpers.scala | 2 +- lisa-utils/src/main/scala/lisa/utils/K.scala | 2 +- .../main/scala/lisa/utils/KernelHelpers.scala | 460 ++++++---- .../scala/lisa/utils/parsing/Printer.scala | 127 +-- .../lisa/utils/parsing/ProofPrinter.scala | 131 --- 25 files changed, 789 insertions(+), 4548 deletions(-) delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/CommonDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/EquivalenceChecker.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/FormulaLabelDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/TermLabelDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/FOL.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala delete mode 100644 lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala 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 6f65ffdf..00000000 --- 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 d323f621..00000000 --- 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 24f895f3..30e863d3 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 192460ce..00000000 --- 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 61e75077..00000000 --- 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/Substitutions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Substitutions.scala deleted file mode 100644 index 51bfb465..00000000 --- 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/TermDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/TermDefinitions.scala deleted file mode 100644 index 6e1b9b5c..00000000 --- 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 610ffe8a..00000000 --- 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/lambdafol/FOL.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/FOL.scala deleted file mode 100644 index 1187aced..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/FOL.scala +++ /dev/null @@ -1,10 +0,0 @@ -package lisa.kernel.lambdafol - -/** - * The concrete implementation of first order logic. - * All its content can be imported using a single statement: - *
- * import lisa.fol.FOL._
- * 
- */ -object FOL extends OLEquivalenceChecker {} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala deleted file mode 100644 index 2c7eaf01..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/OLEquivalenceChecker.scala +++ /dev/null @@ -1,484 +0,0 @@ -package lisa.kernel.lambdafol - -import scala.collection.mutable - -private[lambdafol] trait OLEquivalenceChecker extends Syntax { - - - def reducedForm(expr: Expression): Expression = { - val p = simplify(expr) - val nf = computeNormalForm(p) - val fln = fromLocallyNameless(nf, Map.empty, 0) - val res = toExpressionAIG(fln) - res - } - - def reducedNNFForm(formula: Expression): Expression = { - val p = simplify(formula) - 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 = { - if (e1.containsFormulas != e2.containsFormulas) false - else if (!e1.containsFormulas) e1 == e2 - else { - val nf1 = computeNormalForm(simplify(e1)) - val nf2 = computeNormalForm(simplify(e2)) - 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.typ == Formula && e2.typ == Formula) - val nf1 = computeNormalForm(simplify(e1)) - val nf2 = computeNormalForm(simplify(e2)) - 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(And(s1), And(s2)) - - def isSameSetR(s1: Set[Expression], s2: Set[Expression]): Boolean = - isSame(Or(s1), Or(s2)) - - def contains(s: Set[Expression], f: Expression): Boolean = { - s.exists(g => isSame(f, g)) - } - - - private var totSimpleExpr = 0 - sealed abstract class SimpleExpression { - val typ: Type - 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] = if (containsFormulas) None else Some(this) - def getNormalForm = 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, typ:Type, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = typ == Formula - val size = 1 - } - case class SimpleBoundVariable(no: Int, typ: Type, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = typ == Formula - val size = 1 - } - case class SimpleConstant(id: Identifier, typ: Type, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = typ == Formula - val size = 1 - } - case class SimpleApplication(f: SimpleExpression, arg: SimpleExpression, polarity: Boolean) extends SimpleExpression { - private val legalapp = legalApplication(f.typ, arg.typ) // Optimize after debugging - val typ = legalapp.get - val containsFormulas: Boolean = typ == 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 typ = (v.typ -> body.typ) - val size = body.size - } - case class SimpleAnd(children: Seq[SimpleExpression], polarity: Boolean) extends SimpleExpression{ - val containsFormulas: Boolean = true - val typ = Formula - val size = children.map(_.size).sum+1 - } - case class SimpleForall(id: Identifier, body: SimpleExpression, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = true - val typ = Formula - val size = body.size +1 - } - case class SimpleLiteral(polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = true - val typ = Formula - val size = 1 - } - case class SimpleEquality(left: SimpleExpression, right: SimpleExpression, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = true - val typ = 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.typ == Formula => e.copy(polarity = !e.polarity) - case e: SimpleBoundVariable if e.typ == Formula => e.copy(polarity = !e.polarity) - case e: SimpleConstant if e.typ == Formula => e.copy(polarity = !e.polarity) - case e: SimpleApplication if e.typ == 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 = And(children.map(toExpressionAIG)) - 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, typ, polarity) => if (polarity) Variable(id, typ) else neg(Variable(id, typ)) - case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toFormulaAIG on a bound variable") - case SimpleConstant(id, typ, polarity) => if (polarity) Constant(id, typ) else neg(Constant(id, typ)) - 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, typ, polarity) => - if (polarity == positive) Variable(id, typ) - else neg(Variable(id, typ)) - case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toExpressionNNF on a bound variable") - case SimpleConstant(id, typ, polarity) => - if (polarity == positive) Constant(id, typ) - else neg(Constant(id, typ)) - 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 = 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(v, body) => - SimpleForall(v.id, polarize(body, true), polarity) - case Exists(v, body) => - SimpleForall(v.id, polarize(body, false), !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 Constant(`top`, Formula) => SimpleLiteral(true) - case Constant(`bot`, Formula) => SimpleLiteral(false) - case Constant(id, typ) => SimpleConstant(id, typ, polarity) - case Variable(id, typ) => SimpleVariable(id, typ, polarity) - } - - def toLocallyNameless(e: SimpleExpression, subst: Map[(Identifier, Type), Int], i: Int): SimpleExpression = e match { - case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless(_, subst, i)), polarity) - case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless(inner, subst + ((x, Term) -> i), i + 1), polarity) - case e: SimpleLiteral => e - case SimpleEquality(left, right, polarity) => SimpleEquality(toLocallyNameless(left, subst, i), toLocallyNameless(right, subst, i), polarity) - case v: SimpleVariable => - if (subst.contains((v.id, v.typ))) SimpleBoundVariable(i - subst((v.id, v.typ)), v.typ, 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(toLocallyNameless(arg1, subst, i), toLocallyNameless(arg2, subst, i), polarity) - case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless(inner, subst + ((x.id, x.typ) -> i), i + 1)) - } - - def fromLocallyNameless(e: SimpleExpression, subst: Map[Int, (Identifier, Type)], 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, typ, polarity) => - val dist = i - no - if (subst.contains(dist)) {val (id, typ) = subst(dist); SimpleVariable(id, typ, 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.typ)), i + 1)) - } - - def simplify(e: Expression): SimpleExpression = toLocallyNameless(polarize(e, true), Map.empty, 0) - - - ////////////////////// - //// 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, typ, true) => e - - case SimpleVariable(id, typ, true) => e - - case SimpleConstant(id, typ, 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.typ == Formula && e2.typ == 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) - - 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.containsFormulas && e2.containsFormulas) { - if (e1.typ == 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 - } - } else e1 == e2 -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala deleted file mode 100644 index 3f40346b..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdafol/Syntax.scala +++ /dev/null @@ -1,234 +0,0 @@ -package lisa.kernel.lambdafol - -private[lambdafol] trait Syntax { - - - 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, - (taken.collect({ case Identifier(base.name, no) => - no - }) ++ Iterable(base.no)).max + 1 - ) - } - - - - - sealed trait Type { - def ->(to: Type): Arrow = Arrow(this, to) - val isFunctional: Boolean - def isPredicate: Boolean = !isFunctional - val depth: Int - } - case object Term extends Type { - val isFunctional = true - val depth = 0 - } - case object Formula extends Type { - val isFunctional = false - val depth = 0 - } - sealed case class Arrow(from: Type, to: Type) extends Type { - val isFunctional = to.isFunctional - val depth = 1+to.depth - } - - def depth(t:Type): Int = t match { - case Arrow(a, b) => 1 + depth(b) - case _ => 0 - } - - - def legalApplication(typ1: Type, typ2: Type): Option[Type] = { - 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 { - val typ: Type - val uniqueNumber: Long = ExpressionCounters.getNewId - val containsFormulas : Boolean - def apply(arg: Expression): Application = Application(this, arg) - - /** - * @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, typ:Type) extends Expression { - val containsFormulas = typ == Formula - def freeVariables: Set[Variable] = Set(this) - def constants: Set[Constant] = Set() - def allVariables: Set[Variable] = Set(this) - } - case class Constant(id: Identifier, typ: Type) extends Expression { - val containsFormulas = typ == 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.typ, arg.typ) - require(legalapp.isDefined, s"Application of $f to $arg is not legal") - val typ = legalapp.get - val containsFormulas = typ == 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 typ = (v.typ -> body.typ) - - def freeVariables: Set[Variable] = body.freeVariables - v - def constants: Set[Constant] = body.constants - def allVariables: Set[Variable] = body.allVariables - } - - object Equality { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`equality`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(arg1: Expression, arg2: Expression): Expression = equality(arg1)(arg2) - } - - object Neg { - def unapply (e: Expression): Option[Expression] = e match { - case Application(`neg`, arg) => Some(arg) - case _ => None - } - def apply(arg: Expression): Expression = neg(arg) - } - object Implies { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`implies`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(arg1: Expression, arg2: Expression): Expression = implies(arg1)(arg2) - } - object Iff { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`iff`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(arg1: Expression, arg2: Expression): Expression = iff(arg1)(arg2) - } - object And { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`and`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) - } - object Or { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`or`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) - } - object Forall { - def unapply (e: Expression): Option[(Variable, Expression)] = e match { - case Application(`forall`, Lambda(v, body)) => Some((v, body)) - case _ => None - } - def apply(v: Variable, body: Expression): Expression = forall(Lambda(v, body)) - } - object Exists { - def unapply (e: Expression): Option[(Variable, Expression)] = e match { - case Application(`exists`, Lambda(v, body)) => Some((v, body)) - case _ => None - } - def apply(v: Variable, body: Expression): Expression = exists(Lambda(v, body)) - } - object Epsilon { - def unapply (e: Expression): Option[(Variable, Expression)] = e match { - case Application(`epsilon`, Lambda(v, body)) => Some((v, body)) - case _ => None - } - def apply(v: Variable, body: Expression): Expression = epsilon(Lambda(v, body)) - } - - - 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.typ == v.typ) r - else throw new IllegalArgumentException("Type 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) => - Lambda(v, substituteVariables(t, m - v)) - } - - def flatTypeParameters(t: Type): List[Type] = t match { - case Arrow(a, b) => a :: flatTypeParameters(b) - case _ => List() - } - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala deleted file mode 100644 index 9f55dd14..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/Judgement.scala +++ /dev/null @@ -1,76 +0,0 @@ -package lisa.kernel.lambdaproof - -import lisa.kernel.lambdaproof.RunningTheory - -/** - * The judgement (or verdict) of a proof checking procedure. - * Typically, see [[SCProofChecker.checkSingleSCStep]] and [[SCProofChecker.checkSCProof]]. - */ -sealed abstract class SCProofCheckerJudgement { - import SCProofCheckerJudgement._ - val proof: SCProof - - /** - * Whether this judgement is positive -- the proof is concluded to be valid; - * or negative -- the proof checker couldn't certify the validity of this proof. - * @return An instance of either [[SCValidProof]] or [[SCInvalidProof]] - */ - def isValid: Boolean = this match { - case _: SCValidProof => true - case _: SCInvalidProof => false - } -} - -object SCProofCheckerJudgement { - - /** - * A positive judgement. - */ - case class SCValidProof(proof: SCProof, val usesSorry: Boolean = false) extends SCProofCheckerJudgement - - /** - * A negative judgement. - * @param path The path of the error, expressed as indices - * @param message The error message that hints about the first error encountered - */ - case class SCInvalidProof(proof: SCProof, path: Seq[Int], message: String) extends SCProofCheckerJudgement -} - -/** - * The judgement (or verdict) of a running theory. - */ -sealed abstract class RunningTheoryJudgement[+J <: RunningTheory#Justification] { - import RunningTheoryJudgement._ - - /** - * Whether this judgement is positive -- the justification could be imported into the running theory; - * or negative -- the justification is not suitable to be imported in the theory. - * @return An instance of either [[ValidJustification]] or [[InvalidJustification]] - */ - def isValid: Boolean = this match { - case _: ValidJustification[_] => true - case _: InvalidJustification[_] => false - } - def get: J = this match { - case ValidJustification(just) => just - case InvalidJustification(message, error) => - throw InvalidJustificationException(message, error) - } -} - -object RunningTheoryJudgement { - - /** - * A positive judgement. - */ - case class ValidJustification[J <: RunningTheory#Justification](just: J) extends RunningTheoryJudgement[J] - - /** - * A negative judgement. - * @param error If the justification is rejected because the proof is wrong, will contain the error in the proof. - * @param message The error message that hints about the first error encountered - */ - case class InvalidJustification[J <: RunningTheory#Justification](message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends RunningTheoryJudgement[J] - - case class InvalidJustificationException(message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends Exception(message) -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala deleted file mode 100644 index 5305a492..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/RunningTheory.scala +++ /dev/null @@ -1,316 +0,0 @@ -package lisa.kernel.lambdaproof - -import lisa.kernel.lambdafol.FOL._ -import lisa.kernel.lambdaproof.RunningTheoryJudgement._ -import lisa.kernel.lambdaproof.SequentCalculus._ - -import scala.collection.immutable.Set -import scala.collection.mutable.{Map => mMap} - -/** - * This class describes the theory, i.e. the context and language, in which theorems are proven. - * A theory is built from scratch by introducing axioms and symbols first, then by definitional extensions. - * The structure is one-way mutable: Once an axiom or definition has been introduced, it can't be removed. - * On the other hand, theorems proven before the theory is extended will still hold. - * A theorem only holds true within a specific theory. - * A theory is responsible to make sure that a symbol already defined or present in the language can't - * be redefined. If a theory needs to be extanded in two different ways, or if a theory and its extension need - * to coexist independently, they should be different instances of this class. - */ -class RunningTheory { - - /** - * A Justification is either a Theorem, an Axiom or a Definition - */ - sealed abstract class Justification - - /** - * A theorem encapsulate a sequent and assert that this sequent has been correctly proven and may be used safely in further proofs. - */ - sealed case class Theorem private[RunningTheory] (name: String, proposition: Sequent, withSorry: Boolean) extends Justification - - /** - * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. - */ - sealed case class Axiom private[RunningTheory] (name: String, ax: Expression) extends Justification - - /** - * A definition of a new symbol. - */ - sealed case class Definition private[RunningTheory] (cst: Constant, expression: Expression, vars: Seq[Variable]) extends Justification - - private[lambdaproof] val theoryAxioms: mMap[String, Axiom] = mMap.empty - private[lambdaproof] val theorems: mMap[String, Theorem] = mMap.empty - - private[lambdaproof] 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[lambdaproof] 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. - * The proof's imports must be justified by the list of justification, and the conclusion of the theorem - * can't contain symbols that do not belong to the theory. - * - * @param justifications The list of justifications of the proof's imports. - * @param proof The proof of the desired Theorem. - * @return A Theorem if the proof is correct, None else - */ - def makeTheorem(name: String, statement: Sequent, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = { - if (proof.conclusion == statement) proofToTheorem(name, proof, justifications) - else InvalidJustification("The proof does not prove the claimed statement", None) - } - - private def proofToTheorem(name: String, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = - if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) - if (belongsToTheory(proof.conclusion)) { - val r = SCProofChecker.checkSCProof(proof) - r match { - case SCProofCheckerJudgement.SCValidProof(_, sorry) => - val usesSorry = sorry || justifications.exists(_ match { - case Theorem(name, proposition, withSorry) => withSorry - case Axiom(name, ax) => false - case d: Definition => false - }) - val thm = Theorem(name, proof.conclusion, usesSorry) - theorems.update(name, thm) - ValidJustification(thm) - case r @ SCProofCheckerJudgement.SCInvalidProof(_, _, message) => - InvalidJustification("The given proof is incorrect: " + message, Some(r)) - } - } 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) - - - def makeDefinition(cst: Constant, expression: Expression, vars: Seq[Variable]): RunningTheoryJudgement[this.Definition] = { - if (cst.typ.depth == vars.length) - if (flatTypeParameters(cst.typ) zip vars.map(_.typ) forall { case (a, b) => a == b }) - if (cst.typ == expression.typ) - 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 - * satisfying the definition's formula must first be proven. This is easy if the formula behaves as a shortcut, - * for example f(x,y) = 3x+2y - * but is much more general. The proof's conclusion must be of the form: |- ∀args. ∃!out. phi - * - * @param proof The proof of existence and uniqueness - * @param justifications The justifications of the proof. - * @param label The desired label. - * @param expression The functional term defining the function symbol. - * @param out The variable representing the function's result in the formula - * @param proven A formula possibly stronger than `expression` that the proof proves. It is always correct if it is the same as "expression", but - * if `expression` is less strong, this allows to make underspecified definitions. - * @return A definition object if the parameters are correct, - */ - def makeFunctionDefinition( - proof: SCProof, - justifications: Seq[Justification], - label: ConstantFunctionLabel, - out: VariableLabel, - expression: LambdaTermFormula, - proven: Formula - ): RunningTheoryJudgement[this.FunctionDefinition] = { - val LambdaTermFormula(vars, body) = expression - if (vars.length == label.arity) { - if (belongsToTheory(body)) { - if (isAvailable(label)) { - if (body.freeSchematicTermLabels.subsetOf((vars appended out).toSet) && body.schematicFormulaLabels.isEmpty) { - if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) { - val r = SCProofChecker.checkSCProof(proof) - r match { - case SCProofCheckerJudgement.SCValidProof(_, sorry) => - proof.conclusion match { - case Sequent(l, r) if l.isEmpty && r.size == 1 => - if (isImplying(proven, body)) { - val subst = BinderFormula(ExistsOne, out, proven) - if (isSame(r.head, subst)) { - 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 - } - }) - val newDef = FunctionDefinition(label, out, expression, usesSorry) - funDefinitions.update(label, Some(newDef)) - knownSymbols.update(label.id, label) - RunningTheoryJudgement.ValidJustification(newDef) - } else InvalidJustification("The proof is correct but its conclusion does not correspond to the claimed proven property.", None) - } else InvalidJustification("The proven property must be at least as strong as the desired definition, and it is not.", None) - - case _ => InvalidJustification("The conclusion of the proof must have an empty left hand side, and a single formula on the right hand side.", None) - } - case r @ SCProofCheckerJudgement.SCInvalidProof(_, path, message) => InvalidJustification("The given proof is incorrect: " + message, Some(r)) - } - } else InvalidJustification("Not all imports of the proof are correctly justified.", None) - } 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) - } 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 Definition(cst, e, vars) => - if (cst.typ.isPredicate){ - val inner = Iff(vars.foldLeft(cst: Expression)(_(_)), vars.foldLeft(e)(_(_))) - Sequent(Set(), Set(inner)) - } else { - val inner = Equality(vars.foldLeft(cst: Expression)(_(_)), vars.foldLeft(e)(_(_))) - Sequent(Set(), Set(inner)) - } - } - - /** - * Add a new axiom to the Theory. For example, if the theory contains the language and theorems - * of Zermelo-Fraenkel Set Theory, this function may add the axiom of choice to it. - * If the axiom belongs to the language of the theory, adds it and return true. Else, returns false. - * - * @param f the new axiom to be added. - * @return true if the axiom was added to the theory, false else. - */ - def addAxiom(name: String, f: Expression): Option[Axiom] = { - if (f.typ == Formula && belongsToTheory(f)) { - val ax = Axiom(name, f) - theoryAxioms.update(name, ax) - Some(ax) - } else None - } - - /** - * Add a new symbol to the theory, without providing a definition. An ad-hoc definition can be - * added via an axiom, typically if the desired object is not derivable in the base theory itself. - * For example, This function can add the empty set symbol to a theory, and then an axiom asserting - * that it is empty can be introduced as well. - */ - - 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(e: Expression): Unit = { - e.constants.foreach(addSymbol) - } - - /** - * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. - */ - def makeSequentBelongToTheory(s: Sequent): Unit = { - s.left.foreach(makeFormulaBelongToTheory) - s.right.foreach(makeFormulaBelongToTheory) - } - - /** - * Verify if a given expression belongs to the language of the theory. - * - * @param e The expression to check - * @return Weather t belongs to the specified language. - */ - 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) - } - - /** - * Verify if a given sequent belongs to the language of the theory. - * - * @param s The sequent to check - * @return Weather s belongs to the specified language - */ - def belongsToTheory(s: Sequent): Boolean = - s.left.forall(belongsToTheory) && s.right.forall(belongsToTheory) - - /** - * Public accessor to the set of symbol currently in the theory's language. - * - * @return the set of symbol currently in the theory's language. - */ - def language(): List[(Constant, Option[Definition])] = definitions.toList - - /** - * Check if a label is a symbol of the theory. - */ - def isSymbol(cst: Constant): Boolean = definitions.contains(cst) - - /** - * Check if a label is not already used in the theory. - * @return - */ - def isAvailable(label: Constant): Boolean = !knownSymbols.contains(label.id) - - /** - * Public accessor to the current set of axioms of the theory - * - * @return the current set of axioms of the theory - */ - def axiomsList(): Iterable[Axiom] = theoryAxioms.values - - /** - * Verify if a given formula is an axiom of the theory - */ - def isAxiom(f: Expression): Boolean = f.typ == 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: Expression): Option[Axiom] = if (f.typ == 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: Constant): Option[Definition] = definitions.get(label).flatten - - /** - * Get the Axiom with the given name, if it exists in the theory. - */ - def getAxiom(name: String): Option[Axiom] = theoryAxioms.get(name) - - /** - * Get the Theorem with the given name, if it exists in the theory. - */ - def getTheorem(name: String): Option[Theorem] = theorems.get(name) - - /** - * Get the definition for the given identifier, if it is defined in the theory. - */ - def getDefinition(name: Identifier): Option[Definition] = knownSymbols.get(name).flatMap(getDefinition) - -} -object RunningTheory { - - /** - * An empty theory suitable to reason about first order logic. - */ - def PredicateLogic: RunningTheory = new RunningTheory() -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala deleted file mode 100644 index fc944d89..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProof.scala +++ /dev/null @@ -1,97 +0,0 @@ -package lisa.kernel.lambdaproof - -import lisa.kernel.lambdaproof.SequentCalculus._ - -/** - * A SCPRoof (for Sequent Calculus Proof) is a (dependant) proof. While technically a proof is an Directed Acyclic Graph, - * here proofs are linearized and represented as a list of proof steps. - * Moreover, a proof can depend on some assumed, unproved, sequents specified in the second argument - * @param steps A list of Proof Steps that should form a valid proof. Each individual step should only refer to earlier - * proof steps as premisces. - * @param imports A list of assumed sequents that further steps may refer to. Imports are refered to using negative integers - * To refer to the first sequent of imports, use integer -1. - */ -case class SCProof(steps: IndexedSeq[SCProofStep], imports: IndexedSeq[Sequent] = IndexedSeq.empty) { - def numberedSteps: Seq[(SCProofStep, Int)] = steps.zipWithIndex - - /** - * Fetches the ith step of the proof. - * @param i the index - * @return a step - */ - def apply(i: Int): SCProofStep = { - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(i) - else throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - } - - /** - * Get the ith sequent of the proof. If the index is positive, give the bottom sequent of proof step number i. - * If the index is negative, return the (-i-1)th imported sequent. - * - * @param i The reference number of a sequent in the proof - * @return A sequent, either imported or reached during the proof. - */ - def getSequent(i: Int): Sequent = { - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(i).bot - else { - val i2 = -(i + 1) - if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") - else imports(i2) - } - } - - /** - * The length of the proof in terms of top-level steps, without including the imports. - */ - def length: Int = steps.length - - /** - * The total length of the proof in terms of proof-step, including steps in subproof, but excluding the imports. - */ - def totalLength: Int = steps.foldLeft(0)((i, s) => - i + (s match { - case s: SCSubproof => s.sp.totalLength + 1 - case _ => 1 - }) - ) - - /** - * The conclusion of the proof, namely the bottom sequent of the last proof step. - * Can be undefined if the proof is empty. - */ - def conclusion: Sequent = { - if (steps.isEmpty && imports.isEmpty) throw new NoSuchElementException("conclusion of an empty proof") - this.getSequent(length - 1) - } - - /** - * A helper method that creates a new proof with a new step appended at the end. - * @param newStep the new step to be added - * @return a new proof - */ - def appended(newStep: SCProofStep): SCProof = copy(steps = steps appended newStep) - - /** - * A helper method that creates a new proof with a sequence of new steps appended at the end. - * @param newSteps the sequence of steps to be added - * @return a new proof - */ - def withNewSteps(newSteps: IndexedSeq[SCProofStep]): SCProof = copy(steps = steps ++ newSteps) -} - -object SCProof { - - /** - * Instantiates a proof from an indexed list of proof steps. - * @param steps the steps of the proof - * @return the corresponding proof - */ - def apply(steps: SCProofStep*): SCProof = { - SCProof(steps.toIndexedSeq) - } - -} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala deleted file mode 100644 index daffe881..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SCProofChecker.scala +++ /dev/null @@ -1,642 +0,0 @@ -package lisa.kernel.lambdaproof - -import lisa.kernel.lambdafol.FOL._ -import lisa.kernel.lambdaproof.SCProofCheckerJudgement._ -import lisa.kernel.lambdaproof.SequentCalculus._ - - -object SCProofChecker { - - /** - * This function verifies that a single SCProofStep is correctly applied. It verifies that the step only refers to sequents with a lower number, - * and that the type, premises and parameters of the proof step correspond to the claimed conclusion. - * - * @param no The number of the given proof step. Needed to vewrify that the proof step doesn't refer to posterior sequents. - * @param step The proof step whose correctness needs to be checked - * @param references A function that associates sequents to a range of positive and negative integers that the proof step may refer to. Typically, - * a proof's [[SCProof.getSequent]] function. - * @return A Judgement about the correctness of the proof step. - */ - def checkSingleSCStep(no: Int, step: SCProofStep, references: Int => Sequent, importsSize: Int): SCProofCheckerJudgement = { - val ref = references - val false_premise = step.premises.find(i => i >= no) - val false_premise2 = step.premises.find(i => i < -importsSize) - - val r: SCProofCheckerJudgement = - if (false_premise.nonEmpty) - SCInvalidProof(SCProof(step), Nil, s"Step no $no can't refer to higher number ${false_premise.get} as a premise.") - else if (false_premise2.nonEmpty) - SCInvalidProof(SCProof(step), Nil, s"A step can't refer to step ${false_premise2.get}, imports only contains ${importsSize} elements.") - else - step match { - /* - * Γ |- Δ - * ------------ - * Γ |- Δ - */ - case Restate(s, t1) => - if (isSameSequent(ref(t1), s)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The premise does not trivially imply the conclusion.") - - /* - * - * ------------ - * Γ |- Γ - */ - case RestateTrue(s) => - 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") - /* - * - * -------------- - * Γ, φ |- φ, Δ - */ - case Hypothesis(Sequent(left, right), phi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - 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 (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - 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)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of first premise does not contain φ as claimed.") - else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of second premise does not contain φ as claimed.") - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ is not 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.") - - // Left rules - /* - * Γ, φ |- Δ Γ, φ, ψ |- Δ - * -------------- or ------------- - * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ - */ - case LeftAnd(b, t1, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) - else if (isSameSet(ref(t1).right, b.right)) { - val phiAndPsi = And(Seq(phi, psi)) - if ( - isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || - isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || - isSameSet(b.left + phi + psi, ref(t1).left + phiAndPsi) - ) - 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 the premise and the conclusion must be the same.") - /* - * Γ, φ |- Δ Σ, ψ |- Π - * ------------------------ - * Γ, Σ, φ∨ψ |- Δ, Π - */ - case LeftOr(b, t, disjuncts) => - if (disjuncts.exists(phi => phi.typ != Formula)){ - val culprit = disjuncts.find(phi => phi.typ != Formula).get - SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) - } else if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { - val phiOrPsi = Or(disjuncts) - 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.") - /* - * Γ |- φ, Δ Σ, ψ |- Π - * ------------------------ - * Γ, Σ, φ⇒ψ |- Δ, Π - */ - case LeftImplies(b, t1, t2, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) - 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) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) - 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.") - } - - /* - * Γ |- φ, Δ - * -------------- - * Γ, ¬φ |- Δ - */ - case LeftNot(b, t1, phi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - 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] |- Δ - * ------------------- - * Γ, ∀x. φ |- Δ - */ - case LeftForall(b, t1, phi, x, t) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) - else if (t.typ != Term) - SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) - else if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + substituteVariables(phi, Map(x -> t)), ref(t1).left + Forall(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") - - /* - * Γ, φ |- Δ - * ------------------- if x is not free in the resulting sequent - * Γ, ∃x. φ|- Δ - */ - case LeftExists(b, t1, phi, x) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) - else if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + phi, ref(t1).left + Exists(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) => - ??? - - // Right rules - /* - * Γ |- φ, Δ Σ |- ψ, Π - * ------------------------ - * Γ, Σ |- φ∧ψ, Π, Δ - */ - case RightAnd(b, t, cunjuncts) => - if (cunjuncts.exists(phi => phi.typ != Formula)){ - val culprit = cunjuncts.find(phi => phi.typ != Formula).get - SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) - } else { - val phiAndPsi = 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.") - } - /* - * Γ |- φ, Δ Γ |- φ, ψ, Δ - * -------------- or --------------- - * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ - */ - case RightOr(b, t1, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) - else { - val phiOrPsi = 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.") - } - /* - * Γ, φ |- ψ, Δ - * -------------- - * Γ |- φ⇒ψ, Δ - */ - case RightImplies(b, t1, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) - 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) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) - 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) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - 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 (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) - else if (isSameSet(b.left, ref(t1).left)) - if (isSameSet(b.right + phi, ref(t1).right + Forall(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, "Right-hand side of conclusion + φ 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.") - /* - * Γ |- φ[t/x], Δ - * ------------------- - * Γ |- ∃x. φ, Δ - */ - case RightExists(b, t1, phi, x, t) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) - else if (t.typ != Term) - SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) - else if (isSameSet(b.left, ref(t1).left)) - if (isSameSet(b.right + substituteVariables(phi, Map(x -> t)), ref(t1).right + Exists(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. φ,  Δ
-           * 
- */ - case RightExistsOne(b, t1, phi, x) => - ??? - - // Structural rules - /* - * Γ |- Δ - * -------------- - * Γ, Σ |- Δ - */ - case Weakening(b, t1) => - if (isImplyingSequent(ref(t1), b)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Conclusion cannot be trivially derived from premise.") - - // Equality Rules - /* - * Γ, s=s |- Δ - * -------------- - * Γ |- Δ - */ - case LeftRefl(b, t1, phi) => - phi match { - case Equality(left, right) => - if (isSameTerm(left, right)) - if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + phi, ref(t1).left)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") - else SCInvalidProof(SCProof(step), Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") - else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") - case _ => SCInvalidProof(SCProof(step), Nil, "φ is not an equality") - } - - /* - * - * -------------- - * |- s=s - */ - case RightRefl(b, phi) => - phi match { - case Equality(left, right) => - if (isSameTerm(left, right)) - if (contains(b.right, phi)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Right-Hand side of conclusion does not contain φ") - else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") - case _ => SCInvalidProof(SCProof(step), Nil, s"φ is not an equality.") - } - - /* - * Γ, φ(s) |- Δ Σ |- s=t, Π - * -------------------------------- - * Γ, Σ φ(t) |- Δ, Π - */ - case LeftSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => - val (phi_arg, phi_body) = lambdaPhi - if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) - SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else if (!s.typ.isFunctional) - SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - else { - val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) - val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - - val inner1 = vars.foldLeft(s)(_(_)) - val inner2 = vars.foldLeft(t)(_(_)) - val sEqt = Equality(inner1, inner2) - val varss = vars.toSet - - 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 ( - 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.") - } - - /* - * Γ |- φ(s), Δ Σ |- s=t, Π - * --------------------------------- - * Γ, Σ |- φ(t), Δ, Π - */ - case RightSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => - val (phi_arg, phi_body) = lambdaPhi - if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) - SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else if (!s.typ.isFunctional) - SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - else { - val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) - val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - - val inner1 = vars.foldLeft(s)(_(_)) - val inner2 = vars.foldLeft(t)(_(_)) - val sEqt = Equality(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.") - } - - /* - * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π - * -------------------------------- - * Γ, Σ φ(b) |- Δ, Π - */ - case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => - val (phi_arg, phi_body) = lambdaPhi - if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) - SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") - else if (!psi.typ.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 ( - isSubset(ref(t1).right, b.right) && - isSubset(ref(t2).right, b.right + sEqt) && - isSubset(b.right, ref(t1).right union ref(t2).right) - ) { - if ( - 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.") - } - - /* - * Γ |- φ[ψ/?p], Δ - * --------------------- - * Γ, ψ⇔τ |- φ[τ/?p], Δ - */ - case RightSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => - val (phi_arg, phi_body) = lambdaPhi - if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) - SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") - else if (!psi.typ.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 ( - 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]
-           * 
- */ - case InstSchema(bot, t1, subst) => - val expected = - (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)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of premise instantiated with the given maps must be the same as right-hand side of conclusion.") - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of premise instantiated with the given maps must be the same as left-hand side of conclusion.") - - case SCSubproof(sp, premises) => - if (premises.size == sp.imports.size) { - val invalid = premises.zipWithIndex.find { case (no, p) => !isSameSequent(ref(no), sp.imports(p)) } - if (invalid.isEmpty) { - checkSCProof(sp) - } else - SCInvalidProof( - SCProof(step), - Nil, - s"Premise number ${invalid.get._1} (refering to step ${invalid.get}) is not the same as import number ${invalid.get._1} of the subproof." - ) - } else SCInvalidProof(SCProof(step), Nil, "Number of premises and imports don't match: " + premises.size + " " + sp.imports.size) - - /* - * - * -------------- - * |- s=s - */ - case Sorry(b) => - SCValidProof(SCProof(step), usesSorry = true) - - } - r - } - - /** - * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. - * If the proof is not correct, the function will report the faulty line and a brief explanation. - * - * @param proof A SC proof to check - * @return SCValidProof(SCProof(step)) if the proof is correct, else SCInvalidProof with the path to the incorrect proof step - * and an explanation. - */ - def checkSCProof(proof: SCProof): SCProofCheckerJudgement = { - var isSorry = false - val possibleError = proof.steps.view.zipWithIndex - .map { case (step, no) => - checkSingleSCStep(no, step, (i: Int) => proof.getSequent(i), proof.imports.size) match { - case SCInvalidProof(_, path, message) => SCInvalidProof(proof, no +: path, message) - case SCValidProof(_, sorry) => - isSorry = isSorry || sorry - SCValidProof(proof, sorry) - } - } - .find(j => !j.isValid) - if (possibleError.isEmpty) SCValidProof(proof, isSorry) - else possibleError.get - } - -} \ No newline at end of file diff --git a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala deleted file mode 100644 index d493f870..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/lambdaproof/SequentCalculus.scala +++ /dev/null @@ -1,352 +0,0 @@ -package lisa.kernel.lambdaproof - -import lisa.kernel.lambdafol.FOL._ - -/** - * The concrete implementation of sequent calculus (with equality). - * This file specifies the sequents and the allowed operations on them, the deduction rules of sequent calculus. - * It contains typical sequent calculus rules for FOL with equality as can be found in a text book, as well as a couple more for - * non-elementary symbols (⇔, ∃!) and rules for substituting equal terms or equivalent formulas. I also contains two structural rules, - * subproof and a dummy rewrite step. - * Further mathematical steps, such as introducing or using definitions, axioms or theorems are not part of the basic sequent calculus. - */ -object SequentCalculus { - - /** - * A sequent is an object that can contain two sets of formulas, [[left]] and [[right]]. - * The intended semantic is for the [[left]] formulas to be interpreted as a conjunction, while the [[right]] ones as a disjunction. - * Traditionally, sequents are represented by two lists of formulas. - * Since sequent calculus includes rules for permuting and weakening, it is in essence equivalent to sets. - * Seqs make verifying proof steps much easier, but proof construction much more verbose and proofs longer. - * @param left the left side of the sequent - * @param right the right side of the sequent - */ - case class Sequent(left: Set[Expression], right: Set[Expression]){ - require(left.forall(_.typ == Formula) && right.forall(_.typ == Formula), "Sequent can only contain formulas") - } - - /** - * Simple method that transforms a sequent to a logically equivalent formula. - */ - def sequentToFormula(s: Sequent): Expression = Implies(And(s.left), Or(s.right)) - - /** - * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. - * - * @param l the first sequent - * @param r the second sequent - * @return see [[isSameTerm]] - */ - def isSameSequent(l: Sequent, r: Sequent): Boolean = isSame(sequentToFormula(l), sequentToFormula(r)) - - /** - * Checks whether a given sequent implies another, with respect to [[latticeLEQ]]. - * - * @param l the first sequent - * @param r the second sequent - * @return see [[latticeLEQ]] - */ - def isImplyingSequent(l: Sequent, r: Sequent): Boolean = isImplying(sequentToFormula(l), sequentToFormula(r)) - - /** - * The parent of all proof steps types. - * A proof step is a deduction rule of sequent calculus, with the sequents forming the prerequisite and conclusion. - * For easier linearisation of the proof, the prerequisite are represented with numbers showing the place in the proof of the sequent used. - */ - - /** - * The parent of all sequent calculus rules. - */ - sealed trait SCProofStep { - val bot: Sequent - val premises: Seq[Int] - } - - /** - *
-   *    Γ |- Δ
-   * ------------
-   *    Γ |- Δ  (OL rewrite)
-   * 
- */ - case class Restate(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *
-   * ------------
-   *    Γ |- Γ  (OL tautology)
-   * 
- */ - case class RestateTrue(bot: Sequent) extends SCProofStep { val premises = Seq() } - - /** - *
-   *
-   * --------------
-   *   Γ, φ |- φ, Δ
-   * 
- */ - case class Hypothesis(bot: Sequent, phi: Expression) extends SCProofStep { val premises = Seq() } - - /** - *
-   *  Γ |- Δ, φ    φ, Σ |- Π
-   * ------------------------
-   *       Γ, Σ |-Δ, Π
-   * 
- */ - case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } - - // Left rules - /** - *
-   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
-   * --------------     or     --------------
-   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
-   * 
- */ - case class LeftAnd(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
-   * --------------------------------
-   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
-   * 
- */ - case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Expression]) extends SCProofStep { val premises = t } - - /** - *
-   *  Γ |- φ, Δ    Σ, ψ |- Π
-   * ------------------------
-   *    Γ, Σ, φ⇒ψ |- Δ, Π
-   * 
- */ - case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } - - /** - *
-   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
-   * --------------    or     --------------------
-   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
-   * 
- */ - case class LeftIff(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *   Γ |- φ, Δ
-   * --------------
-   *   Γ, ¬φ |- Δ
-   * 
- */ - case class LeftNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *   Γ, φ[t/x] |- Δ
-   * -------------------
-   *  Γ, ∀ φ |- Δ
-   *
-   * 
- */ - case class LeftForall(bot: Sequent, t1: Int, phi: Expression, x: Variable, t: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *    Γ, φ |- Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ, ∃x φ|- Δ
-   *
-   * 
- */ - case class LeftExists(bot: Sequent, t1: Int, phi: Expression, x: Variable) 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: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } - - // Right rules - /** - *
-   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
-   * ------------------------------------
-   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
-   * 
- */ - case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Expression]) extends SCProofStep { val premises = t } - - /** - *
-   *   Γ |- φ, Δ                Γ |- φ, ψ, Δ
-   * --------------    or    ---------------
-   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
-   * 
- */ - case class RightOr(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ, φ |- ψ, Δ
-   * --------------
-   *  Γ |- φ⇒ψ, Δ
-   * 
- */ - case class RightImplies(bot: Sequent, t1: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ |- a⇒ψ, Δ    Σ |- ψ⇒φ, Π
-   * ----------------------------
-   *      Γ, Σ |- φ⇔ψ, Π, Δ
-   * 
- */ - case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Expression, psi: Expression) extends SCProofStep { val premises = Seq(t1, t2) } - - /** - *
-   *  Γ, φ |- Δ
-   * --------------
-   *   Γ |- ¬φ, Δ
-   * 
- */ - case class RightNot(bot: Sequent, t1: Int, phi: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *    Γ |- φ, Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ |- ∀x. φ, Δ
-   * 
- */ - case class RightForall(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *   Γ |- φ[t/x], Δ
-   * -------------------
-   *  Γ |- ∃x. φ, Δ
-   *
-   * (ln-x stands for locally nameless x)
-   * 
- */ - 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. φ,  Δ
-   * 
- */ - case class RightExistsOne(bot: Sequent, t1: Int, phi: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } - - // Structural rule - /** - *
-   *     Γ |- Δ
-   * --------------
-   *   Γ, Σ |- Δ, Π
-   * 
- */ - case class Weakening(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } - - // Equality Rules - /** - *
-   *  Γ, s=s |- Δ
-   * --------------
-   *     Γ |- Δ
-   * 
- */ - case class LeftRefl(bot: Sequent, t1: Int, fa: Expression) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *
-   * --------------
-   *     |- s=s
-   * 
- */ - case class RightRefl(bot: Sequent, fa: Expression) extends SCProofStep { val premises = Seq() } - - /** - *
-   *  Γ, φ(s) |- Δ     Σ1 |- s=t, Π     
-   * ----------------------------------------
-   *             Γ, Σ φ(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, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ |- φ(s), Δ     Σ1 |- s=t, Π
-   * ---------------------------------
-   *         Γ, Σ |- φ(t), Δ, Π
-   * 
- * equals elements must have type ... -> ... -> Term - */ - case class RightSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *   Γ, φ(ψ) |- Δ     Σ |- ψ⇔τ, Π     
-   * --------------------------------
-   *        Γ, Σ φ(τ) |- Δ, Π
-   * 
- * equals elements must have type ... -> ... -> Formula - */ - 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) } - - /** - *
-   *   Γ |- φ(ψ), Δ     Σ |- ψ⇔τ, Π     
-   * --------------------------------
-   *        Γ, Σ |- φ(τ), Δ, Π
-   * 
- * equals elements must have type ... -> ... -> Formula - */ - 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) } - - // Rule for schemas - - case class InstSchema( - bot: Sequent, - t1: Int, - subst: Map[Variable, Expression] - ) extends SCProofStep { val premises = Seq(t1) } - - // Proof Organisation rules - - /** - * Encapsulate a proof into a single step. The imports of the subproof correspond to the premisces of the step. - * @param sp The encapsulated subproof. - * @param premises The indices of steps on the outside proof that are equivalent to the import of the subproof. - * @param display A boolean value indicating whether the subproof needs to be expanded when printed. Should probably go and - * be replaced by encapsulation. - */ - case class SCSubproof(sp: SCProof, premises: Seq[Int] = Seq.empty) extends SCProofStep { - // premises is a list of ints similar to t1, t2... that verifies that imports of the subproof sp are justified by previous steps. - val bot: Sequent = sp.conclusion - } - - /** - *
-   *
-   * --------------
-   *   Γ  |- Δ
-   * 
- */ - case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } - -} \ No newline at end of file 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 8167d9f7..5b247911 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.typ.depth == vars.length) + if (flatTypeParameters(cst.typ) zip vars.map(_.typ) forall { case (a, b) => a == b }) + if (cst.typ == expression.typ) + 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,20 @@ 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) => + if (cst.typ.isPredicate){ + val inner = iff(vars.foldLeft(cst: Expression)(_(_)))(vars.foldLeft(e)(_(_))) + Sequent(Set(), Set(inner)) + } else { + val inner = equality(vars.foldLeft(cst: Expression)(_(_)))(vars.foldLeft(e)(_(_))) + Sequent(Set(), Set(inner)) + } } /** @@ -222,8 +192,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.typ == Formula && belongsToTheory(f)) { val ax = Axiom(name, f) theoryAxioms.update(name, ax) Some(ax) @@ -237,22 +207,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 +230,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 +256,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 +279,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.typ == 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.typ == 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 +304,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 fb4e5c79..fb283d89 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 bf7c7114..65aee20d 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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.typ != Formula)){ + val culprit = disjuncts.find(phi => phi.typ != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) + } 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + else if (t.typ != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + 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.typ != Formula)){ + val culprit = cunjuncts.find(phi => phi.typ != Formula).get + SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) + } 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (psi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + 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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + else if (t.typ != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + 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], Δ
+           * -------------------------- if y is not free in φ
+           *     Γ|- φ[(ε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.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (x.typ != Term) + SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + else if (t.typ != Term) + SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + 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,59 @@ 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 LeftBeta(b, t1, phi, lambda, t, x) => + val Lambda(y, e) = lambda + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (y.typ != t.typ) + SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.typ + " and " + y.typ) + else if (e.typ != x.typ) + SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.typ + " and " + x.typ) + else if (isSameSet(b.left, ref(t1).left)) { + val redex = lambda(t) + val normalized = substituteVariables(e, Map(y -> t)) + val phi_redex = substituteVariables(phi, Map(x -> redex)) + val phi_normalized = substituteVariables(phi, Map(x -> normalized)) + if (isSameSet(b.right + phi_redex, ref(t1).right + phi_normalized) || isSameSet(b.right + phi_normalized, ref(t1).right + phi_redex)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[(λy. e)t/x] must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") + + + /** + *
+           *    Γ, φ[(λy. e)t/x] |- Δ
+           * ---------------------------
+           *     Γ, φ[e[t/y]/x] |- Δ
+           * 
+ */ + case RightBeta(b, t1, phi, lambda, t, x) => + val Lambda(y, e) = lambda + if (phi.typ != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + else if (y.typ != t.typ) + SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.typ + " and " + y.typ) + else if (e.typ != x.typ) + SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.typ + " and " + x.typ) + else if (isSameSet(b.right, ref(t1).right)) { + val redex = lambda(t) + val normalized = substituteVariables(e, Map(y -> t)) + val phi_redex = substituteVariables(phi, Map(x -> redex)) + val phi_normalized = substituteVariables(phi, Map(x -> normalized)) + if (isSameSet(b.left + phi_redex, ref(t1).left + phi_normalized) || isSameSet(b.left + phi_normalized, ref(t1).left + phi_redex)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of the conclusion + φ[(λy. e)t/x] must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides or conclusion and premise must be the same.") + + // Equality Rules /* * Γ, s=s |- Δ @@ -322,8 +442,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 +460,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 φ") @@ -350,113 +470,124 @@ object SCProofChecker { } /* - * Γ, φ(s_) |- Δ - * --------------------- - * Γ, (s=t)_, φ(t_)|- Δ + * Γ, φ(s) |- Δ Σ |- s=t, Π + * -------------------------------- + * Γ, Σ φ(t) |- Δ, Π */ - 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 }) - SCInvalidProof(SCProof(step), Nil, "The arities of symbols in φ must be the same as the arities of equalities.") + case LeftSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") + else if (!s.typ.isFunctional) + SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") 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 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 phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) + + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val sEqt = equality(inner1)(inner2) + val varss = vars.toSet - if (isSameSet(b.right, ref(t1).right)) + 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.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || - isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) - ) - 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_) (or with s_ and t_ swapped)." - ) + 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.") } /* - * Γ |- φ(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. - 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 RightSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") + else if (!s.typ.isFunctional) + SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") 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 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 phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) - 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) - ) - 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 φ(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.") + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val sEqt = equality(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.") } /* - * Γ, φ(ψ_) |- Δ - * --------------------- - * Γ, ψ⇔τ, φ(τ) |- Δ + * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π + * -------------------------------- + * Γ, Σ φ(b) |- Δ, Π */ - 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 LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else if (!psi.typ.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)) + + val inner1 = vars.foldLeft(psi)(_(_)) + val inner2 = vars.foldLeft(tau)(_(_)) + val sEqt = iff(inner1)(inner2) + val varss = vars.toSet - if (isSameSet(b.right, ref(t1).right)) + 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.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)." - ) + 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.") } @@ -465,36 +596,37 @@ object SCProofChecker { * --------------------- * Γ, ψ⇔τ |- φ[τ/?p], Δ */ - 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.") + case RightSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + val (phi_arg, phi_body) = lambdaPhi + if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of ψ and τ.") + else if (!psi.typ.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 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) } - } + 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(ref(t1).left ++ psiIffTau, b.left)) - if ( - isSameSet(b.right + phi_tau_for_q, ref(t1).right + phi_psi_for_q) || - isSameSet(b.right + phi_psi_for_q, ref(t1).right + phi_tau_for_q) - ) - 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.") + 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.") } @@ -506,9 +638,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 +696,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 b84d84b4..58bcf989 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,14 @@ 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(_.typ == Formula) && right.forall(_.typ == 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 = implies(s.left.reduce(and(_)(_)))(s.right.reduce(or(_)(_))) /** * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. @@ -85,7 +87,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 +96,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 +106,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 +115,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 +124,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 +133,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 +142,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 +152,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 +162,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 +172,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 +181,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 +190,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 +199,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 +208,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 +217,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 +228,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 +249,26 @@ 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 LeftBeta(bot: Sequent, t1: Int, phi: Expression, lambda: Lambda, t: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + + + /** + *
+   *    Γ, φ[(λy. e)t/x] |- Δ
+   * ---------------------------
+   *     Γ, φ[e[t/y]/x] |- Δ
+   * 
+ */ + case class RightBeta(bot: Sequent, t1: Int, phi: Expression, lambda: Lambda, t: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + // Equality Rules /** *
@@ -264,7 +277,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 +286,55 @@ 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) |- Δ     Σ1 |- s=t, Π     
+   * ----------------------------------------
+   *             Γ, Σ φ(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: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + case class LeftSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *    Γ |- φ(s1,...,sn), Δ
-   * ---------------------
-   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
+   *  Γ |- φ(s), Δ     Σ1 |- s=t, Π
+   * ---------------------------------
+   *         Γ, Σ |- φ(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, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *    Γ, φ(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) } /** *
-   *    Γ |- φ(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) } // 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 +360,4 @@ object SequentCalculus { */ case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } -} +} \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala index f6f030f8..77ef9744 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala @@ -115,7 +115,7 @@ trait ProofsHelpers { 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) + lisa.utils.ProofPrinter.prettyProof(_proof, 2) ) } diff --git a/lisa-utils/src/main/scala/lisa/utils/K.scala b/lisa-utils/src/main/scala/lisa/utils/K.scala index 2269cfed..c75e7510 100644 --- a/lisa-utils/src/main/scala/lisa/utils/K.scala +++ b/lisa-utils/src/main/scala/lisa/utils/K.scala @@ -11,6 +11,6 @@ object K { export lisa.kernel.proof.RunningTheoryJudgement as Judgement export lisa.kernel.proof.RunningTheoryJudgement.* export lisa.utils.KernelHelpers.{*, given} - export lisa.utils.parsing.FOLPrinter.* + export ProofPrinter.* } diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 70d40452..6d6a15b0 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -3,12 +3,11 @@ 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 - /** * A helper file that provides various syntactic sugars for LISA's FOL and proofs at the Kernel level. */ @@ -21,92 +20,108 @@ object KernelHelpers { /* Prefix syntax */ 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 = 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 = and + val /\ = and + val Or = or + val \/ = or + val Implies = implies + val ==> = implies + val Iff = iff + val <=> = iff + val Forall = forall val ∀ = forall - val exists = Exists + 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) - case _ => None - } - } + val Epsilon = epsilon + val ε = epsilon - 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) + + extension (binder: forall.type) { + @targetName("forallApply") + def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) + @targetName("forallUnapply") + def unapply(e: Expression): Option[(Variable, Expression)] = e match { + case forall(Lambda(x, inner)) => Some((x, inner)) 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) + extension (binder: exists.type) { + @targetName("existsApply") + def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) + @targetName("existsUnapply") + def unapply(e: Expression): Option[(Variable, Expression)] = e match { + case exists(Lambda(x, inner)) => Some((x, inner)) 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)) + extension (binder: epsilon.type) { + @targetName("epsilonApply") + def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) + @targetName("epsilonUnapply") + def unapply(e: Expression): Option[(Variable, Expression)] = e match { + case epsilon(Lambda(x, inner)) => Some((x, inner)) case _ => None } } /* 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 (f: Expression) { + 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() + } - extension (t: Term) { - infix def ===(u: Term): Formula = AtomicFormula(equality, Seq(t, u)) - infix def =(u: Term): Formula = AtomicFormula(equality, Seq(t, u)) + 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 Application(f, arg) => s"${f.repr}(${arg.repr})" + case Constant(id, typ) => id.toString + case Lambda(v, body) => s"lambda(${v.repr}, ${body.repr})" + case Variable(id, typ) => id.toString + + def fullRepr: String = f match + case Application(f, arg) => s"${f.fullRepr}(${arg.fullRepr})" + case Constant(id, typ) => s"cst(${id},${typ})" + case Lambda(v, body) => s"λ${v.fullRepr}.${body.fullRepr}" + case Variable(id, typ) => s"v(${id},${typ})" } /* Conversions */ - given Conversion[TermLabel, Term] = Term(_, Seq()) - given Conversion[Term, TermLabel] = _.label + /* + given Conversion[TermLabel, Expression] = Expression(_, Seq()) + given Conversion[Expression, 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 +129,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,31 +141,35 @@ 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 addRightIfNotExists f - infix def ->?(f: Formula): Sequent = s removeRight f + infix def +?(f: Expression): Sequent = s addRightIfNotExists f + infix def ->?(f: Expression): Sequent = s removeRight f 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 @@ -160,34 +179,30 @@ object KernelHelpers { * @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 + override def apply(t: EmptyTuple): Set[Expression] = 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 + 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 formula_to_set[T <: Formula]: FormulaSetConverter[T] with { - override def apply(f: T): Set[Formula] = Set(f) + given formula_to_set[T <: Expression]: FormulaSetConverter[T] with { + override def apply(f: T): Set[Expression] = Set(f) } - given [T <: Formula, I <: Iterable[T]]: FormulaSetConverter[I] with { - override def apply(s: I): Set[Formula] = s.toSet + given [T <: Expression, I <: Iterable[T]]: FormulaSetConverter[I] with { + override def apply(s: I): Set[Expression] = s.toSet } - given FormulaSetConverter[VariableFormulaLabel] with { - override def apply(s: VariableFormulaLabel): Set[Formula] = Set(s()) - } - - 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,6 +211,11 @@ object KernelHelpers { // Instatiation functions for formulas lifted to sequents. + def substituteVariablesInSequent(s: Sequent, m: Map[Variable, Expression]): Sequent = { + s.left.map(phi => substituteVariables(phi, m)) |- s.right.map(phi => substituteVariables(phi, m)) + } + + /* def instantiatePredicateSchemaInSequent(s: Sequent, m: Map[SchematicAtomicLabel, LambdaTermFormula]): Sequent = { s.left.map(phi => instantiatePredicateSchemas(phi, m)) |- s.right.map(phi => instantiatePredicateSchemas(phi, m)) } @@ -212,6 +232,7 @@ object KernelHelpers { ): Sequent = { s.left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)) |- s.right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)) } + */ ////////////////////// // SCProofs helpers // @@ -240,50 +261,50 @@ 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(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)(typ: Type): Variable = Variable(name.value, typ) + def variable(using name: sourcecode.Name): Variable = Variable(name.value, Term) + def v(id: Identifier, typ:Type): Variable = Variable(id, typ) + def function(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Term: Type)((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: Type)((acc, _)=> Term -> acc)) + def connector(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Formula: Type)((acc, _)=> Formula -> acc)) + def cst(id: Identifier, typ:Type): Constant = Constant(id, typ) // Conversions from String to Identifier class InvalidIdentifierException(identifier: String, errorMessage: String) extends LisaException(errorMessage) { @@ -336,7 +357,7 @@ 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) } @@ -351,29 +372,19 @@ object KernelHelpers { 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) - } - /** * 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.typ) + val vars = expression.leadingVars() + if (vars.length == expression.typ.depth) then + theory.makeDefinition(label, expression, vars) + else + var maxid = expression.maxVarId()-1 + val newvars = flatTypeParameters(expression.typ).drop(vars.length).map(t => {maxid+=1;Variable(Identifier("x", maxid), t)}) + theory.makeDefinition(label, expression, vars ++ newvars) } /** @@ -388,29 +399,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, typ) => 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,7 +412,7 @@ 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) } @@ -429,13 +422,8 @@ object KernelHelpers { 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 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.typ} := ${d.expression}\n" + } } @@ -451,7 +439,7 @@ 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 => "" }}" } @@ -467,6 +455,138 @@ object KernelHelpers { 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 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, t2, _, _, _, _) => pretty("L. SubstEq", t1, t2) + case RightSubstEq(_, t1, t2, _, _, _, _) => pretty("R. SubstEq", t1, t2) + case LeftSubstIff(_, t1, t2, _, _, _, _) => pretty("L. SubstIff", t1, t2) + case RightSubstIff(_, t1, t2, _, _, _, _) => pretty("R. SubstIff", t1, t2) + 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/parsing/Printer.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala index 6b33007d..821473e3 100644 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala +++ b/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala @@ -56,130 +56,5 @@ class Printer(parser: Parser) { */ 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/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala deleted file mode 100644 index ea2fe2ff..00000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/ProofPrinter.scala +++ /dev/null @@ -1,131 +0,0 @@ -package lisa.utils.parsing - -import lisa.kernel.proof.SCProofCheckerJudgement -import lisa.prooflib.BasicStepTactic.SUBPROOF -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 " " - - private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" - - private def prettyProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { - def computeMaxNumberingLengths(proof: Library#Proof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { - val resultWithCurrent = result.updated( - level, - (Seq((proof.getSteps.size - 1).toString.length, result(level)) ++ (if (proof.getImports.nonEmpty) Seq((-proof.getImports.size).toString.length) else Seq.empty)).max - ) - proof.getSteps - .collect { case ps: proof.ProofStep if ps.tactic.isInstanceOf[SUBPROOF] => ps.tactic.asInstanceOf[SUBPROOF] } - .foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.iProof, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) - } - - val maxNumberingLengths = computeMaxNumberingLengths(printedProof, 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 prettyProofRecursive(printedProof: Library#Proof, level: Int, tree: IndexedSeq[Int], topMostIndices: IndexedSeq[Int]): Seq[(Boolean, String, String, String)] = { - val imports = printedProof.getImports - val steps = printedProof.getSteps - val printedImports = imports.zipWithIndex.reverse.flatMap { case (imp, i) => - val currentTree = tree :+ (-i - 1) - - val showErrorForLine: Boolean = error match { - case None => false - case Some((position, message)) => 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._2.toString() - ) - - Seq(pretty("Import", 0)) - } - printedImports ++ steps.zipWithIndex.flatMap { case (step, i) => - val currentTree = tree :+ i - val showErrorForLine: Boolean = error match { - case None => false - case Some((position, message)) => 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.toString() - ) - - step.tactic match { - case sp: SUBPROOF => - val topSteps: Seq[Int] = sp.premises.map((f: sp.proof.Fact) => sp.proof.sequentAndIntOfFact(f)._2) - pretty("Subproof", topSteps*) +: prettyProofRecursive(sp.iProof, level + 1, currentTree, (if (i == 0) topMostIndices else IndexedSeq.empty) :+ i) - case other => - val line = pretty(other.name) - Seq(line) - } - } - } - - val marker = "->" - - val lines = prettyProofRecursive(printedProof, 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 (error.isDefined) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix - full.mkString(" ") - } - ++ (error match { - case None => Nil - case Some((path, message)) => List(s"\nProof checker has reported an error at line ${path.mkString(".")}: $message") - }) - } - - def prettyFullProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { - printedProof match { - case proof: Library#Proof#InnerProof => - prettyFullProofLines(proof.parent, None) ++ prettyProofLines(proof, error).map(" " + _) - case proof: Library#BaseProof => - prettyProofLines(proof, None) - } - } - - def prettyProof(proof: Library#Proof): String = prettyFullProofLines(proof, None).mkString("\n") - def prettyProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyFullProofLines(proof, None).mkString("\n" + (" " * indent)) - - def prettyProof(proof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, error).mkString("\n") - def prettyProof(proof: Library#Proof, indent: Int, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, None).mkString("\n" + " " * indent) - - def prettySimpleProof(proof: Library#Proof): String = prettyProofLines(proof, None).mkString("\n") - def prettySimpleProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyProofLines(proof, None).mkString("\n" + (" " * indent)) - - 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) - -} From 1486fa3f3df77b65e5bb464833d93252f2a50f94 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Sat, 12 Oct 2024 13:28:05 +0200 Subject: [PATCH 06/92] refactor and move temp files --- .../main/scala/lisa/utils/KernelHelpers.scala | 24 +- .../main/scala/lisa/utils/LisaException.scala | 5 +- .../main/scala/lisa/utils/ProofPrinter.scala | 130 ++++ .../main/scala/lisa/utils/ProofsShrink.scala | 3 +- .../main/scala/lisa/utils/Serialization.scala | 563 ++++++-------- .../src/main/scala/lisa/utils/package.scala | 2 +- .../scala/lisa/utils/parsing/Parser.scala | 689 ------------------ .../lisa/utils/parsing/ParsingUtils.scala | 41 -- .../lisa/utils/parsing/SynonymInfo.scala | 34 - .../main/scala/lisa/utils/tptp/Example.scala | 14 +- .../scala/lisa/utils/tptp/KernelParser.scala | 86 +-- .../main/scala/lisa/utils/tptp/package.scala | 2 +- 12 files changed, 450 insertions(+), 1143 deletions(-) create mode 100644 lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala delete mode 100644 lisa-utils/src/main/scala/lisa/utils/parsing/Parser.scala delete mode 100644 lisa-utils/src/main/scala/lisa/utils/parsing/ParsingUtils.scala delete mode 100644 lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 6d6a15b0..47854b9f 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -13,12 +13,16 @@ import scala.annotation.targetName */ object KernelHelpers { + def predicateType(arity: Int) = Range(0, arity).foldLeft(Formula: Type)((acc, _) => Term -> acc) + def functionType(arity: Int) = Range(0, arity).foldLeft(Term: Type)((acc, _) => Term -> acc) + ///////////////// // FOL helpers // ///////////////// /* Prefix syntax */ + val Equality = equality val === = equality val ⊤ : Expression = top val ⊥ : Expression = bot @@ -72,6 +76,10 @@ object KernelHelpers { } } + 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: Expression) { @@ -98,6 +106,16 @@ object KernelHelpers { 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, typ) => id.toString case Lambda(v, body) => s"lambda(${v.repr}, ${body.repr})" @@ -369,7 +387,7 @@ 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) + else InvalidJustification(s"The proof proves \n ${proof.conclusion.repr}\ninstead of claimed \n ${statement.repr}", None) } /** @@ -419,8 +437,8 @@ object KernelHelpers { 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 => s" Definition of symbol ${d.cst.id} : ${d.cst.typ} := ${d.expression}\n" diff --git a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala index 6d141d39..2f233ba1 100644 --- a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala +++ b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala @@ -8,6 +8,7 @@ import lisa.kernel.proof.SCProof import lisa.prooflib.Library 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 @@ -24,12 +25,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." diff --git a/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala new file mode 100644 index 00000000..6db84510 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala @@ -0,0 +1,130 @@ +package lisa.utils + +import lisa.kernel.proof.SCProofCheckerJudgement +import lisa.prooflib.BasicStepTactic.SUBPROOF +import lisa.prooflib.Library +import lisa.prooflib.* +import lisa.utils.* + +object ProofPrinter { + + private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " + + private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" + + private def prettyProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { + def computeMaxNumberingLengths(proof: Library#Proof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { + val resultWithCurrent = result.updated( + level, + (Seq((proof.getSteps.size - 1).toString.length, result(level)) ++ (if (proof.getImports.nonEmpty) Seq((-proof.getImports.size).toString.length) else Seq.empty)).max + ) + proof.getSteps + .collect { case ps: proof.ProofStep if ps.tactic.isInstanceOf[SUBPROOF] => ps.tactic.asInstanceOf[SUBPROOF] } + .foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.iProof, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) + } + + val maxNumberingLengths = computeMaxNumberingLengths(printedProof, 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 prettyProofRecursive(printedProof: Library#Proof, level: Int, tree: IndexedSeq[Int], topMostIndices: IndexedSeq[Int]): Seq[(Boolean, String, String, String)] = { + val imports = printedProof.getImports + val steps = printedProof.getSteps + val printedImports = imports.zipWithIndex.reverse.flatMap { case (imp, i) => + val currentTree = tree :+ (-i - 1) + + val showErrorForLine: Boolean = error match { + case None => false + case Some((position, message)) => 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._2.toString() + ) + + Seq(pretty("Import", 0)) + } + printedImports ++ steps.zipWithIndex.flatMap { case (step, i) => + val currentTree = tree :+ i + val showErrorForLine: Boolean = error match { + case None => false + case Some((position, message)) => 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.toString() + ) + + step.tactic match { + case sp: SUBPROOF => + val topSteps: Seq[Int] = sp.premises.map((f: sp.proof.Fact) => sp.proof.sequentAndIntOfFact(f)._2) + pretty("Subproof", topSteps*) +: prettyProofRecursive(sp.iProof, level + 1, currentTree, (if (i == 0) topMostIndices else IndexedSeq.empty) :+ i) + case other => + val line = pretty(other.name) + Seq(line) + } + } + } + + val marker = "->" + + val lines = prettyProofRecursive(printedProof, 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 (error.isDefined) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix + full.mkString(" ") + } + ++ (error match { + case None => Nil + case Some((path, message)) => List(s"\nProof checker has reported an error at line ${path.mkString(".")}: $message") + }) + } + + def prettyFullProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { + printedProof match { + case proof: Library#Proof#InnerProof => + prettyFullProofLines(proof.parent, None) ++ prettyProofLines(proof, error).map(" " + _) + case proof: Library#BaseProof => + prettyProofLines(proof, None) + } + } + + def prettyProof(proof: Library#Proof): String = prettyFullProofLines(proof, None).mkString("\n") + def prettyProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyFullProofLines(proof, None).mkString("\n" + (" " * indent)) + + def prettyProof(proof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, error).mkString("\n") + def prettyProof(proof: Library#Proof, indent: Int, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, None).mkString("\n" + " " * indent) + + def prettySimpleProof(proof: Library#Proof): String = prettyProofLines(proof, None).mkString("\n") + def prettySimpleProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyProofLines(proof, None).mkString("\n" + (" " * indent)) + + 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/utils/ProofsShrink.scala b/lisa-utils/src/main/scala/lisa/utils/ProofsShrink.scala index 5f620aa8..4d0d3b94 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 3b934506..2c40c790 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,44 @@ 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 leftBeta: Byte = 21 + inline def rightBeta: Byte = 22 + inline def leftRefl: Byte = 23 + inline def rightRefl: Byte = 24 + inline def leftSubstEq: Byte = 25 + inline def rightSubstEq: Byte = 26 + inline def leftSubstIff: Byte = 27 + inline def rightSubstIff: Byte = 28 + inline def instSchema: Byte = 29 + inline def scSubproof: Byte = 30 + inline def sorry: Byte = 31 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: Type): 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.typ) + def variableToSting(v: Variable): String = "var_" + v.id.name + "_" + v.id.no + "_" + typeToString(v.typ) + + def constantToDos(c: Constant, dos: DataOutputStream): Unit = + dos.writeByte(0) + dos.writeUTF(c.id.name) + dos.writeInt(c.id.no) + dos.writeUTF(typeToString(c.typ)) + + def variableToDOS(v: Variable, dos: DataOutputStream): Unit = + dos.writeByte(1) + dos.writeUTF(v.id.name) + dos.writeInt(v.id.no) + dos.writeUTF(typeToString(v.typ)) + + /* def formulaLabelToDOS(label: FormulaLabel, dos: DataOutputStream): Unit = label match case l: ConstantAtomicLabel => @@ -105,77 +93,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.typ)) + case c: Constant => + treesDOS.writeByte(1) + treesDOS.writeUTF(c.id.name) + treesDOS.writeInt(c.id.no) + treesDOS.writeUTF(typeToString(c.typ)) + 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,113 +163,108 @@ 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) @@ -311,77 +273,63 @@ object Serialization { 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)) - case LeftSubstEq(bot, t1, equals, lambdaPhi) => + proofDOS.writeInt(lineOfExpr(fa)) + case LeftSubstEq(bot, t1, t2, s, t, vars, lambdaPhi) => proofDOS.writeByte(leftSubstEq) 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 RightSubstEq(bot, t1, equals, lambdaPhi) => + proofDOS.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case RightSubstEq(bot, t1, t2, s, t, vars, 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.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case LeftSubstIff(bot, t1, t2, s, t, vars, 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.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + proofDOS.writeInt(lineOfExpr(lambdaPhi._2)) + case RightSubstIff(bot, t1, t2, s, t, vars, lambdaPhi) => proofDOS.writeByte(rightSubstIff) 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 InstSchema(bot, t1, mCon, mPred, mTerm) => + proofDOS.writeInt(t2) + proofDOS.writeInt(lineOfExpr(s)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeShort(vars.size) + vars.foreach(v => proofDOS.writeInt(lineOfExpr(v))) + proofDOS.writeInt(lineOfExpr(lambdaPhi._1)) + 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 +351,15 @@ object Serialization { } + def typeFromString(s: String): (Type, 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 +367,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 typ = treesDIS.readUTF() + Variable(Identifier(name, no), typeFromString(typ)._1) + case 1 => + val name = treesDIS.readUTF() + val no = treesDIS.readInt() + val typ = treesDIS.readUTF() + Constant(Identifier(name, no), typeFromString(typ)._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 +412,118 @@ 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 == leftBeta) + LeftBeta(sequentFromProofDIS(), + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Lambda], + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable] + ) + else if (psType == rightBeta) + RightBeta(sequentFromProofDIS(), + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Lambda], + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()).asInstanceOf[Variable] + ) + 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())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], 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())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(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())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(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())) + proofDIS.readInt(), + exprMap(proofDIS.readInt()), + exprMap(proofDIS.readInt()), + (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList, + (exprMap(proofDIS.readInt()).asInstanceOf[Variable], 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 +559,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.typ) //+ "__" + + //vars.size + vars.map(v => v.id.name + "_" + v.id.no + "_" + typeToString(v.typ)).mkString("__") } - (name, minimizeProofOnce(proof), justNames) + //(name, minimizeProofOnce(proof), justNames) + (name, proof, justNames) ) ) } @@ -663,12 +588,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, typ) = name.split("_") + val cst = Constant(Identifier(id, no.toInt), typeFromString(typ)._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/package.scala b/lisa-utils/src/main/scala/lisa/utils/package.scala index 0dd12e8f..8919c80e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/package.scala +++ b/lisa-utils/src/main/scala/lisa/utils/package.scala @@ -1,4 +1,4 @@ package lisa.utils -export lisa.utils.parsing.{FOLParser, FOLPrinter, Parser, Printer, ProofPrinter} +//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 806caf3d..00000000 --- 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 858f6abf..00000000 --- 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/SynonymInfo.scala b/lisa-utils/src/main/scala/lisa/utils/parsing/SynonymInfo.scala deleted file mode 100644 index 2ab84429..00000000 --- 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/utils/tptp/Example.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala index 4c834ae9..ded5883e 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 1911e479..c5030ad1 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala @@ -23,25 +23,28 @@ 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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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 { @@ -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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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.Constant, (String, Int) => K.Constant, 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/package.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/package.scala index 56192dee..5c4de54e 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 From f38067f6e01d183cd0bcfe75260138f4232c9398 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Sat, 12 Oct 2024 13:55:15 +0200 Subject: [PATCH 07/92] add missing files --- .../lisa/kernel/exfol/CommonDefinitions.scala | 57 ++ .../kernel/exfol/EquivalenceChecker.scala | 810 ++++++++++++++++++ .../main/scala/lisa/kernel/exfol/FOL.scala | 10 + .../kernel/exfol/FormulaDefinitions.scala | 138 +++ .../exfol/FormulaLabelDefinitions.scala | 112 +++ .../lisa/kernel/exfol/Substitutions.scala | 244 ++++++ .../lisa/kernel/exfol/TermDefinitions.scala | 84 ++ .../kernel/exfol/TermLabelDefinitions.scala | 52 ++ .../scala/lisa/kernel/exproof/Judgement.scala | 76 ++ .../lisa/kernel/exproof/RunningTheory.scala | 379 ++++++++ .../scala/lisa/kernel/exproof/SCProof.scala | 97 +++ .../lisa/kernel/exproof/SCProofChecker.scala | 567 ++++++++++++ .../lisa/kernel/exproof/SequentCalculus.scala | 348 ++++++++ .../kernel/fol/OLEquivalenceChecker.scala | 485 +++++++++++ .../main/scala/lisa/kernel/fol/Syntax.scala | 240 ++++++ 15 files changed, 3699 insertions(+) create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala create mode 100644 lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala new file mode 100644 index 00000000..679e7ec4 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala @@ -0,0 +1,57 @@ +package lisa.kernel.exfol + +/** + * Definitions that are common to terms and formulas. + */ +private[exfol] 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/exfol/EquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala new file mode 100644 index 00000000..20d80c24 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala @@ -0,0 +1,810 @@ +package lisa.kernel.exfol + +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[exfol] 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/exfol/FOL.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala new file mode 100644 index 00000000..7a0cc33c --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala @@ -0,0 +1,10 @@ +package lisa.kernel.exfol + +/** + * The concrete implementation of first order logic. + * All its content can be imported using a single statement: + *

+ * import lisa.fol.FOL._
+ * 
+ */ +object FOL extends FormulaDefinitions with EquivalenceChecker with Substitutions {} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala new file mode 100644 index 00000000..07c48a0d --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala @@ -0,0 +1,138 @@ +package lisa.kernel.exfol + +/** + * Definitions of formulas; analogous to [[TermDefinitions]]. + * Depends on [[FormulaLabelDefinitions]] and [[TermDefinitions]]. + */ +private[exfol] 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[exfol] 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/exfol/FormulaLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala new file mode 100644 index 00000000..59b38c2f --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala @@ -0,0 +1,112 @@ +package lisa.kernel.exfol + +/** + * Definitions of formula labels. Analogous to [[TermLabelDefinitions]]. + */ +private[exfol] 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/exfol/Substitutions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala new file mode 100644 index 00000000..278682ab --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala @@ -0,0 +1,244 @@ +package lisa.kernel.exfol + +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/exfol/TermDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala new file mode 100644 index 00000000..bf25f09f --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala @@ -0,0 +1,84 @@ +package lisa.kernel.exfol + +/** + * Definitions of terms; depends on [[TermLabelDefinitions]]. + */ +private[exfol] 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/exfol/TermLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala new file mode 100644 index 00000000..209820ce --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala @@ -0,0 +1,52 @@ +package lisa.kernel.exfol + +/** + * Definitions of term labels. + */ +private[exfol] 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/exproof/Judgement.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala new file mode 100644 index 00000000..f7e774c6 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala @@ -0,0 +1,76 @@ +package lisa.kernel.exproof + +import lisa.kernel.exproof.RunningTheory + +/** + * The judgement (or verdict) of a proof checking procedure. + * Typically, see [[SCProofChecker.checkSingleSCStep]] and [[SCProofChecker.checkSCProof]]. + */ +sealed abstract class SCProofCheckerJudgement { + import SCProofCheckerJudgement._ + val proof: SCProof + + /** + * Whether this judgement is positive -- the proof is concluded to be valid; + * or negative -- the proof checker couldn't certify the validity of this proof. + * @return An instance of either [[SCValidProof]] or [[SCInvalidProof]] + */ + def isValid: Boolean = this match { + case _: SCValidProof => true + case _: SCInvalidProof => false + } +} + +object SCProofCheckerJudgement { + + /** + * A positive judgement. + */ + case class SCValidProof(proof: SCProof, val usesSorry: Boolean = false) extends SCProofCheckerJudgement + + /** + * A negative judgement. + * @param path The path of the error, expressed as indices + * @param message The error message that hints about the first error encountered + */ + case class SCInvalidProof(proof: SCProof, path: Seq[Int], message: String) extends SCProofCheckerJudgement +} + +/** + * The judgement (or verdict) of a running theory. + */ +sealed abstract class RunningTheoryJudgement[+J <: RunningTheory#Justification] { + import RunningTheoryJudgement._ + + /** + * Whether this judgement is positive -- the justification could be imported into the running theory; + * or negative -- the justification is not suitable to be imported in the theory. + * @return An instance of either [[ValidJustification]] or [[InvalidJustification]] + */ + def isValid: Boolean = this match { + case _: ValidJustification[_] => true + case _: InvalidJustification[_] => false + } + def get: J = this match { + case ValidJustification(just) => just + case InvalidJustification(message, error) => + throw InvalidJustificationException(message, error) + } +} + +object RunningTheoryJudgement { + + /** + * A positive judgement. + */ + case class ValidJustification[J <: RunningTheory#Justification](just: J) extends RunningTheoryJudgement[J] + + /** + * A negative judgement. + * @param error If the justification is rejected because the proof is wrong, will contain the error in the proof. + * @param message The error message that hints about the first error encountered + */ + case class InvalidJustification[J <: RunningTheory#Justification](message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends RunningTheoryJudgement[J] + + case class InvalidJustificationException(message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends Exception(message) +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala new file mode 100644 index 00000000..dc35bfaf --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala @@ -0,0 +1,379 @@ +package lisa.kernel.exproof + +import lisa.kernel.exfol.FOL._ +import lisa.kernel.exproof.RunningTheoryJudgement._ +import lisa.kernel.exproof.SequentCalculus._ + +import scala.collection.immutable.Set +import scala.collection.mutable.{Map => mMap} + +/** + * This class describes the theory, i.e. the context and language, in which theorems are proven. + * A theory is built from scratch by introducing axioms and symbols first, then by definitional extensions. + * The structure is one-way mutable: Once an axiom or definition has been introduced, it can't be removed. + * On the other hand, theorems proven before the theory is extended will still hold. + * A theorem only holds true within a specific theory. + * A theory is responsible to make sure that a symbol already defined or present in the language can't + * be redefined. If a theory needs to be extanded in two different ways, or if a theory and its extension need + * to coexist independently, they should be different instances of this class. + */ +class RunningTheory { + + /** + * A Justification is either a Theorem, an Axiom or a Definition + */ + sealed abstract class Justification + + /** + * A theorem encapsulate a sequent and assert that this sequent has been correctly proven and may be used safely in further proofs. + */ + sealed case class Theorem private[RunningTheory] (name: String, proposition: Sequent, withSorry: Boolean) extends Justification + + /** + * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. + */ + sealed case class Axiom private[RunningTheory] (name: String, ax: Formula) extends Justification + + /** + * A definition can be either a PredicateDefinition or a FunctionDefinition. + */ + sealed abstract class Definition extends Justification + + /** + * Define a predicate symbol as a shortcut for a formula. Example : P(x,y) := ∃!z. (x=y+z) + * + * @param label The name and arity of the new symbol + * @param 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 + + private[exproof] val theoryAxioms: mMap[String, Axiom] = mMap.empty + private[exproof] val theorems: mMap[String, Theorem] = mMap.empty + + private[exproof] val funDefinitions: mMap[ConstantFunctionLabel, Option[FunctionDefinition]] = mMap.empty + private[exproof] val predDefinitions: mMap[ConstantAtomicLabel, Option[PredicateDefinition]] = mMap(equality -> None, top -> None, bot -> None) + + private[exproof] val knownSymbols: mMap[Identifier, ConstantLabel] = mMap(equality.id -> equality) + + /** + * From a given proof, if it is true in the Running theory, add that theorem to the theory and returns it. + * The proof's imports must be justified by the list of justification, and the conclusion of the theorem + * can't contain symbols that do not belong to the theory. + * + * @param justifications The list of justifications of the proof's imports. + * @param proof The proof of the desired Theorem. + * @return A Theorem if the proof is correct, None else + */ + def makeTheorem(name: String, statement: Sequent, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = { + if (proof.conclusion == statement) proofToTheorem(name, proof, justifications) + else InvalidJustification("The proof does not prove the claimed statement", None) + } + + private def proofToTheorem(name: String, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = + if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) + if (belongsToTheory(proof.conclusion)) { + val r = SCProofChecker.checkSCProof(proof) + r match { + case SCProofCheckerJudgement.SCValidProof(_, sorry) => + 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 + } + }) + val thm = Theorem(name, proof.conclusion, usesSorry) + theorems.update(name, thm) + ValidJustification(thm) + case r @ SCProofCheckerJudgement.SCInvalidProof(_, _, message) => + InvalidJustification("The given proof is incorrect: " + message, Some(r)) + } + } 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) + } + + /** + * Introduce a new definition of a function in the theory. The symbol must not already exist in the theory + * and the formula can't contain symbols that are not in the theory. The existence and uniqueness of an element + * satisfying the definition's formula must first be proven. This is easy if the formula behaves as a shortcut, + * for example f(x,y) = 3x+2y + * but is much more general. The proof's conclusion must be of the form: |- ∀args. ∃!out. phi + * + * @param proof The proof of existence and uniqueness + * @param justifications The justifications of the proof. + * @param label The desired label. + * @param expression The functional term defining the function symbol. + * @param out The variable representing the function's result in the formula + * @param proven A formula possibly stronger than `expression` that the proof proves. It is always correct if it is the same as "expression", but + * if `expression` is less strong, this allows to make underspecified definitions. + * @return A definition object if the parameters are correct, + */ + def makeFunctionDefinition( + proof: SCProof, + justifications: Seq[Justification], + label: ConstantFunctionLabel, + out: VariableLabel, + expression: LambdaTermFormula, + proven: Formula + ): RunningTheoryJudgement[this.FunctionDefinition] = { + val LambdaTermFormula(vars, body) = expression + if (vars.length == label.arity) { + if (belongsToTheory(body)) { + if (isAvailable(label)) { + if (body.freeSchematicTermLabels.subsetOf((vars appended out).toSet) && body.schematicFormulaLabels.isEmpty) { + if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) { + val r = SCProofChecker.checkSCProof(proof) + r match { + case SCProofCheckerJudgement.SCValidProof(_, sorry) => + proof.conclusion match { + case Sequent(l, r) if l.isEmpty && r.size == 1 => + if (isImplying(proven, body)) { + val subst = BinderFormula(ExistsOne, out, proven) + if (isSame(r.head, subst)) { + 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 + } + }) + val newDef = FunctionDefinition(label, out, expression, usesSorry) + funDefinitions.update(label, Some(newDef)) + knownSymbols.update(label.id, label) + RunningTheoryJudgement.ValidJustification(newDef) + } else InvalidJustification("The proof is correct but its conclusion does not correspond to the claimed proven property.", None) + } else InvalidJustification("The proven property must be at least as strong as the desired definition, and it is not.", None) + + case _ => InvalidJustification("The conclusion of the proof must have an empty left hand side, and a single formula on the right hand side.", None) + } + case r @ SCProofCheckerJudgement.SCInvalidProof(_, path, message) => InvalidJustification("The given proof is incorrect: " + message, Some(r)) + } + } else InvalidJustification("Not all imports of the proof are correctly justified.", None) + } 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) + } 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)) + + } + + /** + * Add a new axiom to the Theory. For example, if the theory contains the language and theorems + * of Zermelo-Fraenkel Set Theory, this function may add the axiom of choice to it. + * If the axiom belongs to the language of the theory, adds it and return true. Else, returns false. + * + * @param f the new axiom to be added. + * @return true if the axiom was added to the theory, false else. + */ + def addAxiom(name: String, f: Formula): Option[Axiom] = { + if (belongsToTheory(f)) { + val ax = Axiom(name, f) + theoryAxioms.update(name, ax) + Some(ax) + } else None + } + + /** + * Add a new symbol to the theory, without providing a definition. An ad-hoc definition can be + * added via an axiom, typically if the desired object is not derivable in the base theory itself. + * For example, This function can add the empty set symbol to a theory, and then an axiom asserting + * 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) + } + } 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) + } + + /** + * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. + */ + def makeSequentBelongToTheory(s: Sequent): Unit = { + s.left.foreach(makeFormulaBelongToTheory) + s.right.foreach(makeFormulaBelongToTheory) + } + + /** + * 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. + * + * @param t The term 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) + } + + } + + /** + * Verify if a given sequent belongs to the language of the theory. + * + * @param s The sequent to check + * @return Weather s belongs to the specified language + */ + def belongsToTheory(s: Sequent): Boolean = + s.left.forall(belongsToTheory) && s.right.forall(belongsToTheory) + + /** + * Public accessor to the set of symbol currently in the theory's language. + * + * @return the set of symbol currently in the theory's language. + */ + def language(): List[(ConstantLabel, Option[Definition])] = funDefinitions.toList ++ predDefinitions.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) + } + + /** + * Check if a label is not already used in the theory. + * @return + */ + def isAvailable(label: ConstantLabel): Boolean = !knownSymbols.contains(label.id) + + /** + * Public accessor to the current set of axioms of the theory + * + * @return the current set of axioms of the theory + */ + def axiomsList(): Iterable[Axiom] = theoryAxioms.values + + /** + * Verify if a given formula is an axiom of the theory + */ + def isAxiom(f: Formula): Boolean = 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 + + /** + * Get the definition of the given label, if it is defined in the theory. + */ + def getDefinition(label: ConstantFunctionLabel): Option[FunctionDefinition] = funDefinitions.get(label).flatten + + /** + * Get the Axiom with the given name, if it exists in the theory. + */ + def getAxiom(name: String): Option[Axiom] = theoryAxioms.get(name) + + /** + * Get the Theorem with the given name, if it exists in the theory. + */ + def getTheorem(name: String): Option[Theorem] = theorems.get(name) + + /** + * 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) + } + +} +object RunningTheory { + + /** + * An empty theory suitable to reason about first order logic. + */ + def PredicateLogic: RunningTheory = new RunningTheory() +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala new file mode 100644 index 00000000..21846db3 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala @@ -0,0 +1,97 @@ +package lisa.kernel.exproof + +import lisa.kernel.exproof.SequentCalculus._ + +/** + * A SCPRoof (for Sequent Calculus Proof) is a (dependant) proof. While technically a proof is an Directed Acyclic Graph, + * here proofs are linearized and represented as a list of proof steps. + * Moreover, a proof can depend on some assumed, unproved, sequents specified in the second argument + * @param steps A list of Proof Steps that should form a valid proof. Each individual step should only refer to earlier + * proof steps as premisces. + * @param imports A list of assumed sequents that further steps may refer to. Imports are refered to using negative integers + * To refer to the first sequent of imports, use integer -1. + */ +case class SCProof(steps: IndexedSeq[SCProofStep], imports: IndexedSeq[Sequent] = IndexedSeq.empty) { + def numberedSteps: Seq[(SCProofStep, Int)] = steps.zipWithIndex + + /** + * Fetches the ith step of the proof. + * @param i the index + * @return a step + */ + def apply(i: Int): SCProofStep = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i) + else throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + } + + /** + * Get the ith sequent of the proof. If the index is positive, give the bottom sequent of proof step number i. + * If the index is negative, return the (-i-1)th imported sequent. + * + * @param i The reference number of a sequent in the proof + * @return A sequent, either imported or reached during the proof. + */ + def getSequent(i: Int): Sequent = { + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(i).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(i2) + } + } + + /** + * The length of the proof in terms of top-level steps, without including the imports. + */ + def length: Int = steps.length + + /** + * The total length of the proof in terms of proof-step, including steps in subproof, but excluding the imports. + */ + def totalLength: Int = steps.foldLeft(0)((i, s) => + i + (s match { + case s: SCSubproof => s.sp.totalLength + 1 + case _ => 1 + }) + ) + + /** + * The conclusion of the proof, namely the bottom sequent of the last proof step. + * Can be undefined if the proof is empty. + */ + def conclusion: Sequent = { + if (steps.isEmpty && imports.isEmpty) throw new NoSuchElementException("conclusion of an empty proof") + this.getSequent(length - 1) + } + + /** + * A helper method that creates a new proof with a new step appended at the end. + * @param newStep the new step to be added + * @return a new proof + */ + def appended(newStep: SCProofStep): SCProof = copy(steps = steps appended newStep) + + /** + * A helper method that creates a new proof with a sequence of new steps appended at the end. + * @param newSteps the sequence of steps to be added + * @return a new proof + */ + def withNewSteps(newSteps: IndexedSeq[SCProofStep]): SCProof = copy(steps = steps ++ newSteps) +} + +object SCProof { + + /** + * Instantiates a proof from an indexed list of proof steps. + * @param steps the steps of the proof + * @return the corresponding proof + */ + def apply(steps: SCProofStep*): SCProof = { + SCProof(steps.toIndexedSeq) + } + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala new file mode 100644 index 00000000..1dcb768a --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala @@ -0,0 +1,567 @@ +package lisa.kernel.exproof + +import lisa.kernel.exfol.FOL._ +import lisa.kernel.exproof.SCProofCheckerJudgement._ +import lisa.kernel.exproof.SequentCalculus._ + +object SCProofChecker { + + /** + * This function verifies that a single SCProofStep is correctly applied. It verifies that the step only refers to sequents with a lower number, + * and that the type, premises and parameters of the proof step correspond to the claimed conclusion. + * + * @param no The number of the given proof step. Needed to vewrify that the proof step doesn't refer to posterior sequents. + * @param step The proof step whose correctness needs to be checked + * @param references A function that associates sequents to a range of positive and negative integers that the proof step may refer to. Typically, + * a proof's [[SCProof.getSequent]] function. + * @return A Judgement about the correctness of the proof step. + */ + def checkSingleSCStep(no: Int, step: SCProofStep, references: Int => Sequent, importsSize: Int): SCProofCheckerJudgement = { + val ref = references + val false_premise = step.premises.find(i => i >= no) + val false_premise2 = step.premises.find(i => i < -importsSize) + + val r: SCProofCheckerJudgement = + if (false_premise.nonEmpty) + SCInvalidProof(SCProof(step), Nil, s"Step no $no can't refer to higher number ${false_premise.get} as a premise.") + else if (false_premise2.nonEmpty) + SCInvalidProof(SCProof(step), Nil, s"A step can't refer to step ${false_premise2.get}, imports only contains ${importsSize} elements.") + else + step match { + /* + * Γ |- Δ + * ------------ + * Γ |- Δ + */ + case Restate(s, t1) => + if (isSameSequent(ref(t1), s)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The premise does not trivially imply the conclusion.") + + /* + * + * ------------ + * Γ |- Γ + */ + case RestateTrue(s) => + val truth = Sequent(Set(), Set(AtomicFormula(top, Nil))) + if (isSameSequent(s, truth)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The desired conclusion is not a trivial tautology") + /* + * + * -------------- + * Γ, φ |- φ, Δ + */ + case Hypothesis(Sequent(left, right), phi) => + 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 (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)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of first premise does not contain φ as claimed.") + else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of second premise does not contain φ as claimed.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ is not 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.") + + // Left rules + /* + * Γ, φ |- Δ Γ, φ, ψ |- Δ + * -------------- or ------------- + * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ + */ + case LeftAnd(b, t1, phi, psi) => + if (isSameSet(ref(t1).right, b.right)) { + val phiAndPsi = ConnectorFormula(And, Seq(phi, psi)) + if ( + isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || + isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || + isSameSet(b.left + phi + psi, ref(t1).left + phiAndPsi) + ) + 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 the premise and the conclusion must be the same.") + /* + * Γ, φ |- Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ∨ψ |- Δ, Π + */ + case LeftOr(b, t, disjuncts) => + if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { + val phiOrPsi = ConnectorFormula(Or, disjuncts) + 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.") + /* + * Γ |- φ, Δ Σ, ψ |- Π + * ------------------------ + * Γ, Σ, φ⇒ψ |- Δ, Π + */ + 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.") + /* + * Γ, φ⇒ψ |- Δ Γ, φ⇒ψ, ψ⇒φ |- Δ + * -------------- 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.") + + /* + * Γ |- φ, Δ + * -------------- + * Γ, ¬φ |- Δ + */ + 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 + ¬φ") + + /* + * Γ, φ[t/x] |- Δ + * ------------------- + * Γ, ∀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))) + 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") + + /* + * Γ, φ |- Δ + * ------------------- if x is not free in the resulting sequent + * Γ, ∃x. φ|- Δ + */ + case LeftExists(b, t1, phi, x) => + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left + BinderFormula(Exists, x, phi))) + if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) + 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 + /* + * Γ |- φ, Δ Σ |- ψ, Π + * ------------------------ + * Γ, Σ |- φ∧ψ, Π, Δ + */ + 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.") + /* + * Γ |- φ, Δ Γ |- φ, ψ, Δ + * -------------- 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.") + /* + * Γ, φ |- ψ, Δ + * -------------- + * Γ |- φ⇒ψ, Δ + */ + 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.") + /* + * Γ |- φ⇒ψ, Δ Σ |- ψ⇒φ, Π + * ---------------------------- + * Γ, Σ |- φ⇔ψ, Π, Δ + */ + 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.") + /* + * Γ, φ |- Δ + * -------------- + * Γ |- ¬φ, Δ + */ + 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 x is not free in the resulting sequent + * Γ |- ∀x. φ, Δ + */ + case RightForall(b, t1, phi, x) => + if (isSameSet(b.left, ref(t1).left)) + if (isSameSet(b.right + phi, ref(t1).right + BinderFormula(Forall, x, phi))) + if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ 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.") + /* + * Γ |- φ[t/x], Δ + * ------------------- + * Γ |- ∃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))) + 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. φ,  Δ
+           * 
+ */ + 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))) + 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") + + // Structural rules + /* + * Γ |- Δ + * -------------- + * Γ, Σ |- Δ + */ + case Weakening(b, t1) => + if (isImplyingSequent(ref(t1), b)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Conclusion cannot be trivially derived from premise.") + + // Equality Rules + /* + * Γ, s=s |- Δ + * -------------- + * Γ |- Δ + */ + case LeftRefl(b, t1, phi) => + phi match { + case AtomicFormula(`equality`, Seq(left, right)) => + if (isSameTerm(left, right)) + if (isSameSet(b.right, ref(t1).right)) + if (isSameSet(b.left + phi, ref(t1).left)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") + else SCInvalidProof(SCProof(step), Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(SCProof(step), Nil, "φ is not an equality") + } + + /* + * + * -------------- + * |- s=s + */ + case RightRefl(b, phi) => + phi match { + case AtomicFormula(`equality`, Seq(left, right)) => + if (isSameTerm(left, right)) + if (contains(b.right, phi)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, s"Right-Hand side of conclusion does not contain φ") + else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") + case _ => SCInvalidProof(SCProof(step), Nil, s"φ is not an equality.") + } + + /* + * Γ, φ(s_) |- Δ + * --------------------- + * Γ, (s=t)_, φ(t_)|- Δ + */ + 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 }) + 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 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) } + } + + if (isSameSet(b.right, ref(t1).right)) + if ( + isSameSet(b.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || + isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) + ) + 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_) (or with s_ and t_ swapped)." + ) + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + } + + /* + * Γ |- φ(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. + 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_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 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) } + } + + if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) + 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) + ) + 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 φ(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.") + } + + /* + * Γ, φ(ψ_) |- Δ + * --------------------- + * Γ, ψ⇔τ, φ(τ) |- Δ + */ + 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.") + 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) } + } + + 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)." + ) + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + } + + /* + * Γ |- φ[ψ/?p], Δ + * --------------------- + * Γ, ψ⇔τ |- φ[τ/?p], Δ + */ + 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) } + } + + if (isSameSet(ref(t1).left ++ psiIffTau, b.left)) + if ( + isSameSet(b.right + phi_tau_for_q, ref(t1).right + phi_psi_for_q) || + isSameSet(b.right + phi_psi_for_q, ref(t1).right + phi_tau_for_q) + ) + 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.") + } + + + + /** + *
+           * Γ |- Δ
+           * --------------------------
+           * Γ[ψ/?p] |- Δ[ψ/?p]
+           * 
+ */ + case InstSchema(bot, t1, mCon, mPred, mTerm) => + val expected = + (ref(t1).left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)), ref(t1).right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm))) + if (isSameSet(bot.left, expected._1)) + if (isSameSet(bot.right, expected._2)) + SCValidProof(SCProof(step)) + else SCInvalidProof(SCProof(step), Nil, "Right-hand side of premise instantiated with the given maps must be the same as right-hand side of conclusion.") + else SCInvalidProof(SCProof(step), Nil, "Left-hand side of premise instantiated with the given maps must be the same as left-hand side of conclusion.") + + case SCSubproof(sp, premises) => + if (premises.size == sp.imports.size) { + val invalid = premises.zipWithIndex.find { case (no, p) => !isSameSequent(ref(no), sp.imports(p)) } + if (invalid.isEmpty) { + checkSCProof(sp) + } else + SCInvalidProof( + SCProof(step), + Nil, + s"Premise number ${invalid.get._1} (refering to step ${invalid.get}) is not the same as import number ${invalid.get._1} of the subproof." + ) + } else SCInvalidProof(SCProof(step), Nil, "Number of premises and imports don't match: " + premises.size + " " + sp.imports.size) + + /* + * + * -------------- + * |- s=s + */ + case Sorry(b) => + SCValidProof(SCProof(step), usesSorry = true) + + } + r + } + + /** + * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. + * If the proof is not correct, the function will report the faulty line and a brief explanation. + * + * @param proof A SC proof to check + * @return SCValidProof(SCProof(step)) if the proof is correct, else SCInvalidProof with the path to the incorrect proof step + * and an explanation. + */ + def checkSCProof(proof: SCProof): SCProofCheckerJudgement = { + var isSorry = false + val possibleError = proof.steps.view.zipWithIndex + .map { case (step, no) => + checkSingleSCStep(no, step, (i: Int) => proof.getSequent(i), proof.imports.size) match { + case SCInvalidProof(_, path, message) => SCInvalidProof(proof, no +: path, message) + case SCValidProof(_, sorry) => + isSorry = isSorry || sorry + SCValidProof(proof, sorry) + } + } + .find(j => !j.isValid) + if (possibleError.isEmpty) SCValidProof(proof, isSorry) + else possibleError.get + } + +} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala new file mode 100644 index 00000000..ef9cbd9b --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala @@ -0,0 +1,348 @@ +package lisa.kernel.exproof + +import lisa.kernel.exfol.FOL._ + +/** + * The concrete implementation of sequent calculus (with equality). + * This file specifies the sequents and the allowed operations on them, the deduction rules of sequent calculus. + * It contains typical sequent calculus rules for FOL with equality as can be found in a text book, as well as a couple more for + * non-elementary symbols (⇔, ∃!) and rules for substituting equal terms or equivalent formulas. I also contains two structural rules, + * subproof and a dummy rewrite step. + * Further mathematical steps, such as introducing or using definitions, axioms or theorems are not part of the basic sequent calculus. + */ +object SequentCalculus { + + /** + * A sequent is an object that can contain two sets of formulas, [[left]] and [[right]]. + * The intended semantic is for the [[left]] formulas to be interpreted as a conjunction, while the [[right]] ones as a disjunction. + * Traditionally, sequents are represented by two lists of formulas. + * Since sequent calculus includes rules for permuting and weakening, it is in essence equivalent to sets. + * Seqs make verifying proof steps much easier, but proof construction much more verbose and proofs longer. + * @param left the left side of the sequent + * @param right the right side of the sequent + */ + case class Sequent(left: Set[Formula], right: Set[Formula]) + + /** + * Simple method that transforms a sequent to a logically equivalent formula. + */ + def sequentToFormula(s: Sequent): Formula = ConnectorFormula(Implies, List(ConnectorFormula(And, s.left.toSeq), ConnectorFormula(Or, s.right.toSeq))) + + /** + * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. + * + * @param l the first sequent + * @param r the second sequent + * @return see [[isSameTerm]] + */ + def isSameSequent(l: Sequent, r: Sequent): Boolean = isSame(sequentToFormula(l), sequentToFormula(r)) + + /** + * Checks whether a given sequent implies another, with respect to [[latticeLEQ]]. + * + * @param l the first sequent + * @param r the second sequent + * @return see [[latticeLEQ]] + */ + def isImplyingSequent(l: Sequent, r: Sequent): Boolean = isImplying(sequentToFormula(l), sequentToFormula(r)) + + /** + * The parent of all proof steps types. + * A proof step is a deduction rule of sequent calculus, with the sequents forming the prerequisite and conclusion. + * For easier linearisation of the proof, the prerequisite are represented with numbers showing the place in the proof of the sequent used. + */ + + /** + * The parent of all sequent calculus rules. + */ + sealed trait SCProofStep { + val bot: Sequent + val premises: Seq[Int] + } + + /** + *
+   *    Γ |- Δ
+   * ------------
+   *    Γ |- Δ  (OL rewrite)
+   * 
+ */ + case class Restate(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *
+   * ------------
+   *    Γ |- Γ  (OL tautology)
+   * 
+ */ + case class RestateTrue(bot: Sequent) extends SCProofStep { val premises = Seq() } + + /** + *
+   *
+   * --------------
+   *   Γ, φ |- φ, Δ
+   * 
+ */ + case class Hypothesis(bot: Sequent, phi: Formula) extends SCProofStep { val premises = Seq() } + + /** + *
+   *  Γ |- Δ, φ    φ, Σ |- Π
+   * ------------------------
+   *       Γ, Σ |-Δ, Π
+   * 
+ */ + case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + + // Left rules + /** + *
+   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
+   * --------------     or     --------------
+   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
+   * 
+ */ + case class LeftAnd(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
+   * --------------------------------
+   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
+   * 
+ */ + case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + + /** + *
+   *  Γ |- φ, Δ    Σ, ψ |- Π
+   * ------------------------
+   *    Γ, Σ, φ⇒ψ |- Δ, Π
+   * 
+ */ + case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + + /** + *
+   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
+   * --------------    or     --------------------
+   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
+   * 
+ */ + case class LeftIff(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ |- φ, Δ
+   * --------------
+   *   Γ, ¬φ |- Δ
+   * 
+ */ + case class LeftNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ, φ[t/x] |- Δ
+   * -------------------
+   *  Γ, ∀ φ |- Δ
+   *
+   * 
+ */ + case class LeftForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ, φ |- Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ, ∃x φ|- Δ
+   *
+   * 
+ */ + 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) } + + // Right rules + /** + *
+   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
+   * ------------------------------------
+   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
+   * 
+ */ + case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Formula]) extends SCProofStep { val premises = t } + + /** + *
+   *   Γ |- φ, Δ                Γ |- φ, ψ, Δ
+   * --------------    or    ---------------
+   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
+   * 
+ */ + case class RightOr(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ, φ |- ψ, Δ
+   * --------------
+   *  Γ |- φ⇒ψ, Δ
+   * 
+ */ + case class RightImplies(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *  Γ |- a⇒ψ, Δ    Σ |- ψ⇒φ, Π
+   * ----------------------------
+   *      Γ, Σ |- φ⇔ψ, Π, Δ
+   * 
+ */ + case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } + + /** + *
+   *  Γ, φ |- Δ
+   * --------------
+   *   Γ |- ¬φ, Δ
+   * 
+ */ + case class RightNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ, Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ |- ∀x. φ, Δ
+   * 
+ */ + case class RightForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *   Γ |- φ[t/x], Δ
+   * -------------------
+   *  Γ |- ∃x. φ, Δ
+   *
+   * (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) } + + /** + *
+   *  Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ|- ∃!x. φ,  Δ
+   * 
+ */ + case class RightExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } + + // Structural rule + /** + *
+   *     Γ |- Δ
+   * --------------
+   *   Γ, Σ |- Δ, Π
+   * 
+ */ + case class Weakening(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } + + // Equality Rules + /** + *
+   *  Γ, s=s |- Δ
+   * --------------
+   *     Γ |- Δ
+   * 
+ */ + case class LeftRefl(bot: Sequent, t1: Int, fa: Formula) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *
+   * --------------
+   *     |- s=s
+   * 
+ */ + case class RightRefl(bot: Sequent, fa: Formula) extends SCProofStep { val premises = Seq() } + + /** + *
+   *    Γ, φ(s1,...,sn) |- Δ
+   * ---------------------
+   *  Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ
+   * 
+ */ + case class LeftSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ(s1,...,sn), Δ
+   * ---------------------
+   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
+   * 
+ */ + case class RightSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ, φ(a1,...an) |- Δ
+   * ---------------------
+   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   * 
+ */ + case class LeftSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + /** + *
+   *    Γ |- φ(a1,...an), Δ
+   * ---------------------
+   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   * 
+ */ + + case class RightSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } + + // Rule for schemas + + case class InstSchema( + bot: Sequent, + t1: Int, + mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula], + mPred: Map[SchematicAtomicLabel, LambdaTermFormula], + mTerm: Map[SchematicTermLabel, LambdaTermTerm] + ) extends SCProofStep { val premises = Seq(t1) } + + // Proof Organisation rules + + /** + * Encapsulate a proof into a single step. The imports of the subproof correspond to the premisces of the step. + * @param sp The encapsulated subproof. + * @param premises The indices of steps on the outside proof that are equivalent to the import of the subproof. + * @param display A boolean value indicating whether the subproof needs to be expanded when printed. Should probably go and + * be replaced by encapsulation. + */ + case class SCSubproof(sp: SCProof, premises: Seq[Int] = Seq.empty) extends SCProofStep { + // premises is a list of ints similar to t1, t2... that verifies that imports of the subproof sp are justified by previous steps. + val bot: Sequent = sp.conclusion + } + + /** + *
+   *
+   * --------------
+   *   Γ  |- Δ
+   * 
+ */ + case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } + +} 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 00000000..b637ccb4 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -0,0 +1,485 @@ +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 p = simplify(expr) + val nf = computeNormalForm(p) + val fln = fromLocallyNameless(nf, Map.empty, 0) + val res = toExpressionAIG(fln) + res + } + + def reducedNNFForm(formula: Expression): Expression = { + val p = simplify(formula) + 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 = { + if (e1.containsFormulas != e2.containsFormulas) false + else if (!e1.containsFormulas) e1 == e2 + else { + val nf1 = computeNormalForm(simplify(e1)) + val nf2 = computeNormalForm(simplify(e2)) + 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.typ == Formula && e2.typ == Formula) + val nf1 = computeNormalForm(simplify(e1)) + val nf2 = computeNormalForm(simplify(e2)) + 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 typ: Type + 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] = if (containsFormulas) None else Some(this) + def getNormalForm = 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, typ:Type, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = typ == Formula + val size = 1 + } + case class SimpleBoundVariable(no: Int, typ: Type, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = typ == Formula + val size = 1 + } + case class SimpleConstant(id: Identifier, typ: Type, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = typ == Formula + val size = 1 + } + case class SimpleApplication(f: SimpleExpression, arg: SimpleExpression, polarity: Boolean) extends SimpleExpression { + private val legalapp = legalApplication(f.typ, arg.typ) // Optimize after debugging + val typ = legalapp.get + val containsFormulas: Boolean = typ == 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 typ = (v.typ -> body.typ) + val size = body.size + } + case class SimpleAnd(children: Seq[SimpleExpression], polarity: Boolean) extends SimpleExpression{ + val containsFormulas: Boolean = true + val typ = Formula + val size = children.map(_.size).sum+1 + } + case class SimpleForall(id: Identifier, body: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val typ = Formula + val size = body.size +1 + } + case class SimpleLiteral(polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val typ = Formula + val size = 1 + } + case class SimpleEquality(left: SimpleExpression, right: SimpleExpression, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = true + val typ = 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.typ == Formula => e.copy(polarity = !e.polarity) + case e: SimpleBoundVariable if e.typ == Formula => e.copy(polarity = !e.polarity) + case e: SimpleConstant if e.typ == Formula => e.copy(polarity = !e.polarity) + case e: SimpleApplication if e.typ == 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, typ, polarity) => if (polarity) Variable(id, typ) else neg(Variable(id, typ)) + case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toFormulaAIG on a bound variable") + case SimpleConstant(id, typ, polarity) => if (polarity) Constant(id, typ) else neg(Constant(id, typ)) + 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, typ, polarity) => + if (polarity == positive) Variable(id, typ) + else neg(Variable(id, typ)) + case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toExpressionNNF on a bound variable") + case SimpleConstant(id, typ, polarity) => + if (polarity == positive) Constant(id, typ) + else neg(Constant(id, typ)) + 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 = 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 exists(Lambda(v, body)) => + SimpleForall(v.id, polarize(body, false), !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 Constant(`top`, Formula) => SimpleLiteral(true) + case Constant(`bot`, Formula) => SimpleLiteral(false) + case Constant(id, typ) => SimpleConstant(id, typ, polarity) + case Variable(id, typ) => SimpleVariable(id, typ, polarity) + } + + def toLocallyNameless(e: SimpleExpression, subst: Map[(Identifier, Type), Int], i: Int): SimpleExpression = e match { + case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless(_, subst, i)), polarity) + case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless(inner, subst + ((x, Term) -> i), i + 1), polarity) + case e: SimpleLiteral => e + case SimpleEquality(left, right, polarity) => SimpleEquality(toLocallyNameless(left, subst, i), toLocallyNameless(right, subst, i), polarity) + case v: SimpleVariable => + if (subst.contains((v.id, v.typ))) SimpleBoundVariable(i - subst((v.id, v.typ)), v.typ, 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(toLocallyNameless(arg1, subst, i), toLocallyNameless(arg2, subst, i), polarity) + case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless(inner, subst + ((x.id, x.typ) -> i), i + 1)) + } + + def fromLocallyNameless(e: SimpleExpression, subst: Map[Int, (Identifier, Type)], 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, typ, polarity) => + val dist = i - no + if (subst.contains(dist)) {val (id, typ) = subst(dist); SimpleVariable(id, typ, 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.typ)), i + 1)) + } + + def simplify(e: Expression): SimpleExpression = toLocallyNameless(polarize(e, true), Map.empty, 0) + + + ////////////////////// + //// 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, typ, true) => e + + case SimpleVariable(id, typ, true) => e + + case SimpleConstant(id, typ, 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.typ == Formula && e2.typ == 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) + + 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.containsFormulas && e2.containsFormulas) { + if (e1.typ == 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 + } + } else e1 == e2 +} 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 00000000..aab6d544 --- /dev/null +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -0,0 +1,240 @@ +package lisa.kernel.fol + +private[fol] trait Syntax { + + + 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 Type { + def ->(to: Type): Arrow = Arrow(this, to) + val isFunctional: Boolean + def isPredicate: Boolean = !isFunctional + val depth: Int + } + case object Term extends Type { + val isFunctional = true + val depth = 0 + } + case object Formula extends Type { + val isFunctional = false + val depth = 0 + } + sealed case class Arrow(from: Type, to: Type) extends Type { + val isFunctional = to.isFunctional + val depth = 1+to.depth + } + + def depth(t:Type): Int = t match { + case Arrow(a, b) => 1 + depth(b) + case _ => 0 + } + + + def legalApplication(typ1: Type, typ2: Type): Option[Type] = { + 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 { + val typ: Type + 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) => f.unapplySeq(arg).map(fargs => fargs :+ arg) + case _ => None + } + + /** + * @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, typ:Type) extends Expression { + val containsFormulas = typ == Formula + def freeVariables: Set[Variable] = Set(this) + def constants: Set[Constant] = Set() + def allVariables: Set[Variable] = Set(this) + } + case class Constant(id: Identifier, typ: Type) extends Expression { + val containsFormulas = typ == 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.typ, arg.typ) + require(legalapp.isDefined, s"Application of $f to $arg is not legal") + val typ = legalapp.get + val containsFormulas = typ == 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 typ = (v.typ -> body.typ) + + def freeVariables: Set[Variable] = body.freeVariables - v + def constants: Set[Constant] = body.constants + def allVariables: Set[Variable] = body.allVariables + } + + /* + object Equality { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`equality`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(arg1: Expression, arg2: Expression): Expression = equality(arg1)(arg2) + } + + object Neg { + def unapply (e: Expression): Option[Expression] = e match { + case Application(`neg`, arg) => Some(arg) + case _ => None + } + def apply(arg: Expression): Expression = neg(arg) + } + object Implies { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`implies`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(arg1: Expression, arg2: Expression): Expression = implies(arg1)(arg2) + } + object Iff { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`iff`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(arg1: Expression, arg2: Expression): Expression = iff(arg1)(arg2) + } + object And { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`and`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) + } + object Or { + def unapply (e: Expression): Option[(Expression, Expression)] = e match { + case Application(Application(`or`, arg1), arg2) => Some((arg1, arg2)) + case _ => None + } + def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) + } + object Forall { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`forall`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = forall(Lambda(v, body)) + } + object Exists { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`exists`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = exists(Lambda(v, body)) + } + object Epsilon { + def unapply (e: Expression): Option[(Variable, Expression)] = e match { + case Application(`epsilon`, Lambda(v, body)) => Some((v, body)) + case _ => None + } + def apply(v: Variable, body: Expression): Expression = epsilon(Lambda(v, body)) + } +*/ + + 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.typ == v.typ) r + else throw new IllegalArgumentException("Type 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) => + Lambda(v, substituteVariables(t, m - v)) + } + + def flatTypeParameters(t: Type): List[Type] = t match { + case Arrow(a, b) => a :: flatTypeParameters(b) + case _ => List() + } + +} From bbeffa57ec590c59b3a593e5a08fda6580fc5464 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Sat, 12 Oct 2024 16:21:55 +0200 Subject: [PATCH 08/92] update the sctptp parser --- .../main/scala/lisa/utils/KernelHelpers.scala | 9 + .../scala/lisa/utils/tptp/KernelParser.scala | 20 +-- .../scala/lisa/utils/tptp/ProofParser.scala | 162 +++++++++--------- 3 files changed, 96 insertions(+), 95 deletions(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 47854b9f..418e4294 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -82,6 +82,15 @@ object KernelHelpers { /* Infix syntax */ + 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 _ => None + inner(e).map(l => {val rev = l.reverse; (rev.head, rev.tail)}) + + } + extension (f: Expression) { def unary_! = neg(f) infix inline def ==>(g: Expression): Expression = implies(f)(g) 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 c5030ad1..5d4f1d31 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala @@ -23,7 +23,7 @@ object KernelParser { * @param formula A formula in the tptp language * @return the corresponding LISA formula */ - def parseToKernel(formula: String)(using maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): K.Expression = 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) @@ -31,7 +31,7 @@ object KernelParser { * @param formula a tptp formula in leo parser * @return the same formula in LISA */ - def convertToKernel(formula: FOF.Formula)(using maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): K.Expression = { + 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) => @@ -66,11 +66,11 @@ object KernelParser { } } - def convertToKernel(sequent: FOF.Sequent)(using maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): 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 maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): K.Expression = { + 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) => multiapply(mapAtom(formula.f, formula.args.size))(formula.args.map(convertTermToKernel).toList) @@ -85,7 +85,7 @@ object KernelParser { * @param term a tptp term in leo parser * @return the same term in LISA */ - def convertTermToKernel(term: CNF.Term)(using maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): K.Expression = + 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) @@ -97,7 +97,7 @@ object KernelParser { * @param term a tptp term in leo parser * @return the same term in LISA */ - def convertTermToKernel(term: FOF.Term)(using maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): K.Expression = + 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) => @@ -111,7 +111,7 @@ object KernelParser { * @param formula an annotated tptp statement * @return the corresponding LISA formula augmented with name and role. */ - def annotatedStatementToKernel(formula: String)(using maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): 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) => @@ -123,7 +123,7 @@ object KernelParser { } - private def problemToKernel(problemFile: File, md: ProblemMetadata)(using maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): 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 @@ -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 maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): 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 maps: ((String, Int) => K.Constant, (String, Int) => K.Constant, String => K.Variable)): Problem = { + def problemToKernel(problemFile: String)(using maps: ((String, Int) => K.Expression, (String, Int) => K.Expression, String => K.Variable)): Problem = { problemToKernel(File(problemFile)) } 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 162313eb..ac25cba5 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -16,24 +16,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 +82,44 @@ 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(quoted("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(quoted("X" + v.id)), formulaToFOFFormula(f)) + case K.exists(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.?, Seq(quoted("X" + v.id)), formulaToFOFFormula(f)) + 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") + } - 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 +150,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 { @@ -197,7 +189,7 @@ 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 @@ -256,7 +248,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 +264,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,7 +281,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("leftHyp", Seq(StrOrNum(n), StrOrNum(m)), Seq())) => val left = sequent.lhs.map(convertToKernel) @@ -305,7 +297,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 +308,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 +319,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 +330,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,12 +342,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("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)) @@ -367,12 +359,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 +376,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,12 +393,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("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)) @@ -418,19 +410,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 +431,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(Lambda(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 +452,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(K.Lambda(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 +469,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(K.Lambda(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 +486,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)) From 256fd44d7efb48fec5bf2d23deea1769374daf47 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Mon, 14 Oct 2024 00:11:29 +0200 Subject: [PATCH 09/92] POrted most of top-level syntax --- .../scala/lisa => backup}/fol/Common.scala | 0 .../main/scala/lisa => backup}/fol/FOL.scala | 0 .../lisa => backup}/fol/FOLHelpers.scala | 0 .../scala/lisa => backup}/fol/Lambdas.scala | 0 .../scala/lisa => backup}/fol/Predef.scala | 0 .../scala/lisa => backup}/fol/Sequents.scala | 0 .../lisa => backup}/prooflib/BasicMain.scala | 0 .../prooflib/BasicStepTactic.scala | 0 .../lisa => backup}/prooflib/Exports.scala | 0 .../lisa => backup}/prooflib/Library.scala | 0 .../prooflib/OutputManager.scala | 0 .../prooflib/ProofTacticLib.scala | 0 .../prooflib/ProofsHelpers.scala | 0 .../prooflib/SimpleDeducedSteps.scala | 0 .../prooflib/WithTheorems.scala | 0 .../unification/UnificationUtils.scala | 0 build.sbt | 4 +- .../main/scala/lisa/utils/KernelHelpers.scala | 2 +- .../main/scala/lisa/utils/LisaException.scala | 10 +- .../main/scala/lisa/utils/ProofPrinter.scala | 10 +- .../main/scala/lisa/utils/fol/Syntax.scala | 223 ++++++++++++++++++ .../scala/lisa/utils/parsing/Printer.scala | 60 ----- .../scala/lisa/utils/tptp/ProofParser.scala | 2 +- 23 files changed, 240 insertions(+), 71 deletions(-) rename {lisa-utils/src/main/scala/lisa => backup}/fol/Common.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/fol/FOL.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/fol/FOLHelpers.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/fol/Lambdas.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/fol/Predef.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/fol/Sequents.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/BasicMain.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/BasicStepTactic.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/Exports.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/Library.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/OutputManager.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/ProofTacticLib.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/ProofsHelpers.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/SimpleDeducedSteps.scala (100%) rename {lisa-utils/src/main/scala/lisa => backup}/prooflib/WithTheorems.scala (100%) rename {lisa-utils/src/main/scala/lisa/utils => backup}/unification/UnificationUtils.scala (100%) create mode 100644 lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala delete mode 100644 lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/Common.scala b/backup/fol/Common.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Common.scala rename to backup/fol/Common.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/FOL.scala b/backup/fol/FOL.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/FOL.scala rename to backup/fol/FOL.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/FOLHelpers.scala b/backup/fol/FOLHelpers.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/FOLHelpers.scala rename to backup/fol/FOLHelpers.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/Lambdas.scala b/backup/fol/Lambdas.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Lambdas.scala rename to backup/fol/Lambdas.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/Predef.scala b/backup/fol/Predef.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Predef.scala rename to backup/fol/Predef.scala diff --git a/lisa-utils/src/main/scala/lisa/fol/Sequents.scala b/backup/fol/Sequents.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/fol/Sequents.scala rename to backup/fol/Sequents.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/BasicMain.scala b/backup/prooflib/BasicMain.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/BasicMain.scala rename to backup/prooflib/BasicMain.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/BasicStepTactic.scala b/backup/prooflib/BasicStepTactic.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/BasicStepTactic.scala rename to backup/prooflib/BasicStepTactic.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/Exports.scala b/backup/prooflib/Exports.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/Exports.scala rename to backup/prooflib/Exports.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/Library.scala b/backup/prooflib/Library.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/Library.scala rename to backup/prooflib/Library.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/OutputManager.scala b/backup/prooflib/OutputManager.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/OutputManager.scala rename to backup/prooflib/OutputManager.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/ProofTacticLib.scala b/backup/prooflib/ProofTacticLib.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/ProofTacticLib.scala rename to backup/prooflib/ProofTacticLib.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala b/backup/prooflib/ProofsHelpers.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/ProofsHelpers.scala rename to backup/prooflib/ProofsHelpers.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/SimpleDeducedSteps.scala b/backup/prooflib/SimpleDeducedSteps.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/SimpleDeducedSteps.scala rename to backup/prooflib/SimpleDeducedSteps.scala diff --git a/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala b/backup/prooflib/WithTheorems.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala rename to backup/prooflib/WithTheorems.scala diff --git a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala b/backup/unification/UnificationUtils.scala similarity index 100% rename from lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala rename to backup/unification/UnificationUtils.scala diff --git a/build.sbt b/build.sbt index 4a3136c1..4a9f57e5 100644 --- a/build.sbt +++ b/build.sbt @@ -32,7 +32,9 @@ 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", + "-source future" ), javaOptions += "-Xmx10G", diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 418e4294..cdc20f01 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -386,7 +386,7 @@ object KernelHelpers { extension (theory: RunningTheory) { 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) } /** diff --git a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala index 2f233ba1..b3df33f7 100644 --- a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala +++ b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala @@ -1,12 +1,12 @@ package lisa.utils -import lisa.fol.FOL as F +//import lisa.fol.FOL as F import lisa.kernel.fol.FOL 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.Library +//import lisa.prooflib.ProofTacticLib.ProofTactic import lisa.utils.KernelHelpers.repr import lisa.utils.KernelHelpers.prettySCProof @@ -16,6 +16,7 @@ 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 @@ -38,6 +39,7 @@ object LisaException { } + /** * Error made by the user, should be "explained" */ @@ -64,3 +66,5 @@ object UserLisaException { } } + */ + diff --git a/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala index 6db84510..0db2d8ae 100644 --- a/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala +++ b/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala @@ -1,13 +1,13 @@ package lisa.utils import lisa.kernel.proof.SCProofCheckerJudgement -import lisa.prooflib.BasicStepTactic.SUBPROOF -import lisa.prooflib.Library -import lisa.prooflib.* +//import lisa.prooflib.BasicStepTactic.SUBPROOF +//import lisa.prooflib.Library +//import lisa.prooflib.* import lisa.utils.* object ProofPrinter { - +/* private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" @@ -126,5 +126,5 @@ object ProofPrinter { 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/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala new file mode 100644 index 00000000..8da823ef --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -0,0 +1,223 @@ +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 + +trait Syntax { + + + + + + + + + object First { + trait T + trait F + trait Arrow[A: Type, B: Type] + + trait Type { + type Self + val underlying: K.Type + } + given given_TermType: (Type{type Self = T}) with + val underlying = K.Term + given given_FormulaType: (Type{type Self = F}) with + val underlying = K.Formula + given given_ArrowType[A : Type, B : Type]: (Type{type Self = Arrow[A, B]}) with + val underlying = K.Arrow(summon[Type{type Self = A}].underlying, summon[Type{type Self = B}].underlying) + + class SubstPair[T: Type] private (val _1: Var[T], val _2: Expr[T]) { + // def toTuple = (_1, _2) + } + object SubstPair { + def apply[T : Type](_1: Var[T], _2: Expr[T]) = new SubstPair(_1, _2) + } + + given [T: Type]: Conversion[(Var[T], Expr[T]), SubstPair[T]] = s => SubstPair(s._1, s._2) + + + trait Expr[S : Type] { + val typ: K.Type = summon[Type{type Self = S}].underlying + def underlying: K.Expression + def substUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] + def substituteWithCheck(m: Map[Var[?], Expr[?]]): Expr[S] = { + if m.forall((k, v) => k.typ == v.typ) then + substUnsafe(m) + else + val culprit = m.find((k, v) => k.typ != v.typ).get + throw new IllegalArgumentException("Type mismatch in substitution: " + culprit._1 + " -> " + culprit._2) + } + def substitute(pairs: SubstPair[?]*): Expr[S] = + substituteWithCheck(pairs.view.map(s => (s._1, s._2)).toMap) + + def freeVars: Set[Var[?]] + def freeTermVars: Set[Var[T]] + } + + + type Formula = Expr[F] + type Term = Expr[T] + + case class Var[S : Type](id: K.Identifier) extends Expr[S] { + val underlying: K.Variable = K.Variable(id, typ) + def substUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] = m.getOrElse(this, this).asInstanceOf[Expr[S]] + def freeVars: Set[Var[?]] = Set(this) + def freeTermVars: Set[Var[T]] = if typ == K.Term then Set(this.asInstanceOf) else Set.empty + def rename(newId: K.Identifier): Var[S] = Var(newId) + def freshRename(existing: Iterable[Expr[?]]): Var[S] = { + val newId = K.freshId(existing.flatMap(_.freeVars.map(_.id)), id) + Var(newId) + } + + def :=(replacement: Expr[S]) = SubstPair(this, replacement) + } + case class Cst[S : Type](id: K.Identifier) extends Expr[S] { + val underlying: K.Constant = K.Constant(id, typ) + def substUnsafe(m: Map[Var[?], Expr[?]]): Cst[S] = this + def freeVars: Set[Var[?]] = Set.empty + def freeTermVars: Set[Var[T]] = Set.empty + def rename(newId: K.Identifier): Cst[S] = Cst(newId) + } + case class App[T1 : Type, T2 : Type](f: Expr[Arrow[T1, T2]], arg: Expr[T1]) extends Expr[T2] { + val underlying: K.Application = K.Application(f.underlying, arg.underlying) + def substUnsafe(m: Map[Var[?], Expr[?]]): App[T1, T2] = App(f.substUnsafe(m), arg.substUnsafe(m)) + def freeVars: Set[Var[?]] = f.freeVars ++ arg.freeVars + def freeTermVars: Set[Var[T]] = f.freeTermVars ++ arg.freeTermVars + } + case class Abs[T1 : Type, T2 : Type](v: Var[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { + val underlying: K.Lambda = K.Lambda(v.underlying, body.underlying) + def substUnsafe(m: Map[Var[?], Expr[?]]): Abs[T1, T2] = Abs(v, body.substUnsafe(m - v)) + def freeVars: Set[Var[?]] = body.freeVars - v + def freeTermVars: Set[Var[T]] = body.freeTermVars.filterNot(_ == v) + } + + extension [T1 : Type, T2 : Type](f: Expr[Arrow[T1, T2]]) { + def apply(arg: Expr[T1]): Expr[T2] = App(f, arg) + } + + + val x = Var[T]("x") + val y: Expr[F] = Var("x") + val z: Expr[Arrow[T, F]] = Var("x") + z(x) + + + @showAsInfix + infix type |->[I, O] = (I, O) match { + case (Expr[T], Expr[F]) => Expr[Arrow[T, F]] + } + } + + object FirstTest { + import First._ + + val x1: Term = Var("x") + val y1: Formula = Var("y") + val z1: (Term |-> Formula) = Var("z") + } + + + + + object Third { + trait Expr { + val typ: K.Type + } + + + opaque type Term <: Expr = Expr + opaque type Formula <: Expr = Expr + opaque type |->[A, B] <: Expr = Expr + + sealed trait Type { + type Self <: Expr + val underlying: K.Type + val isExpr: (Expr =:= Self) + } + given (Term is Type) with + val underlying = K.Term + val isExpr = summon[Expr =:= Term] + given (Formula is Type) with + val underlying = K.Formula + val isExpr = summon[Expr =:= Formula] + given [A : Type, B : Type]: ((|->[A, B]) is Type) with + val underlying = K.Arrow(summon[Type{type Self = A}].underlying, summon[Type{type Self = B}].underlying) + val isExpr = summon[Expr =:= |->[A, B]] + + + case class Var(id: K.Identifier, typ: K.Type) extends Expr + object Var { + def apply[T: Type](id: String): Var & T = + val evT = summon[Type{type Self = T}] + (new Var(id, evT.underlying)).asInstanceOf + } + case class Cst(id: K.Identifier, typ: K.Type) extends Expr + case class App(f: Expr, arg: Expr, typ: K.Type) extends Expr + case class Abs(v: Var, body: Expr, typ: K.Type) extends Expr + + } + + + object Fourth { + + trait Expr[T <: Expr[T]] { + val typ: K.Type + } + + /* + opaque type Term <: Expr[?] = Expr[?] + opaque type Formula <: Expr[?] = Expr[?] + opaque type |->[A, B] <: Expr[?] = Expr[?] +*/ + + sealed trait Term extends Expr[Term] { + } + sealed trait Formula extends Expr[Formula] { + } + sealed trait |->[A, B] extends Expr[|->[A, B]] { + } + + sealed trait Type { + type Self <: Expr[?] + val underlying: K.Type + } + given (Term is Type) with + val underlying = K.Term + given (Formula is Type) with + val underlying = K.Formula + given [A : Type, B : Type]: ((|->[A, B]) is Type) with + val underlying = K.Arrow(summon[Type{type Self = A}].underlying, summon[Type{type Self = B}].underlying) + + case class Var[T<: Expr[T]](id: K.Identifier, typ: K.Type) extends Expr[T] + object Var { + def apply[T <: Expr[T] : Type](id: String): Var[T] = + val evT = summon[Type{type Self = T}] + (new Var(id, evT.underlying)).asInstanceOf + } + + + + } + + object FourthTest { + import Fourth._ + + val x: Var[Term] = Var("x") + val y: Var[Formula] = Var("y") + val z: Var[ |->[Term, Formula]] = Var("z") + } + + +} + + + + 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 821473e3..00000000 --- a/lisa-utils/src/main/scala/lisa/utils/parsing/Printer.scala +++ /dev/null @@ -1,60 +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) - - -} 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 ac25cba5..852c99ef 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -439,7 +439,7 @@ object ProofParser { case x:K.Variable => x case _ => throw new Exception(s"Expected a variable, but got $xl") val (y: K.Variable, phi: K.Expression) = convertToKernel(f) match { - case K.Exists(Lambda(x, phi)) => (x, phi) + case K.Exists(K.Lambda(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)) From 2b790afc561bc94c10b625ad01e62120067a314b Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Mon, 14 Oct 2024 17:12:58 +0200 Subject: [PATCH 10/92] Finished top-level syntax, including sequents. Next: Prooflib --- .../kernel/fol/OLEquivalenceChecker.scala | 88 ++--- .../main/scala/lisa/kernel/fol/Syntax.scala | 40 +- .../lisa/kernel/proof/RunningTheory.scala | 14 +- .../lisa/kernel/proof/SCProofChecker.scala | 168 ++++----- .../lisa/kernel/proof/SequentCalculus.scala | 2 +- .../main/scala/lisa/utils/KernelHelpers.scala | 40 +- .../main/scala/lisa/utils/Serialization.scala | 32 +- .../main/scala/lisa/utils/fol/Predef.scala | 99 +++++ .../main/scala/lisa/utils/fol/Sequents.scala | 235 ++++++++++++ .../main/scala/lisa/utils/fol/Syntax.scala | 352 +++++++++--------- 10 files changed, 709 insertions(+), 361 deletions(-) create mode 100644 lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala index b637ccb4..6f876973 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -46,7 +46,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { * returns true if the first argument implies the second by the laws of ortholattices. */ def isImplying(e1: Expression, e2: Expression): Boolean = { - require(e1.typ == Formula && e2.typ == Formula) + require(e1.sort == Formula && e2.sort == Formula) val nf1 = computeNormalForm(simplify(e1)) val nf2 = computeNormalForm(simplify(e2)) latticesLEQ(nf1, nf2) @@ -71,7 +71,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { private var totSimpleExpr = 0 sealed abstract class SimpleExpression { - val typ: Type + val sort: Sort val containsFormulas : Boolean val uniqueKey = totSimpleExpr @@ -105,47 +105,47 @@ private[fol] trait OLEquivalenceChecker extends Syntax { } } - case class SimpleVariable(id: Identifier, typ:Type, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = typ == Formula + case class SimpleVariable(id: Identifier, sort:Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula val size = 1 } - case class SimpleBoundVariable(no: Int, typ: Type, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = typ == Formula + case class SimpleBoundVariable(no: Int, sort: Sort, polarity: Boolean) extends SimpleExpression { + val containsFormulas: Boolean = sort == Formula val size = 1 } - case class SimpleConstant(id: Identifier, typ: Type, polarity: Boolean) extends SimpleExpression { - val containsFormulas: Boolean = typ == Formula + 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.typ, arg.typ) // Optimize after debugging - val typ = legalapp.get - val containsFormulas: Boolean = typ == Formula || f.containsFormulas || arg.containsFormulas + 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 typ = (v.typ -> body.typ) + 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 typ = Formula + 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 typ = Formula + val sort = Formula val size = body.size +1 } case class SimpleLiteral(polarity: Boolean) extends SimpleExpression { val containsFormulas: Boolean = true - val typ = Formula + val sort = Formula val size = 1 } case class SimpleEquality(left: SimpleExpression, right: SimpleExpression, polarity: Boolean) extends SimpleExpression { val containsFormulas: Boolean = true - val typ = Formula + val sort = Formula val size = left.size + right.size + 1 } @@ -158,10 +158,10 @@ private[fol] trait OLEquivalenceChecker extends Syntax { 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.typ == Formula => e.copy(polarity = !e.polarity) - case e: SimpleBoundVariable if e.typ == Formula => e.copy(polarity = !e.polarity) - case e: SimpleConstant if e.typ == Formula => e.copy(polarity = !e.polarity) - case e: SimpleApplication if e.typ == Formula => 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) @@ -183,9 +183,9 @@ private[fol] trait OLEquivalenceChecker extends Syntax { val f = equality(toExpressionAIG(left))(toExpressionAIG(right)) if (polarity) f else neg(f) case SimpleLiteral(polarity) => if (polarity) top else bot - case SimpleVariable(id, typ, polarity) => if (polarity) Variable(id, typ) else neg(Variable(id, typ)) - case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toFormulaAIG on a bound variable") - case SimpleConstant(id, typ, polarity) => if (polarity) Constant(id, typ) else neg(Constant(id, typ)) + 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) @@ -225,13 +225,13 @@ private[fol] trait OLEquivalenceChecker extends Syntax { case SimpleLiteral(polarity) => if (positive == polarity) top else bot - case SimpleVariable(id, typ, polarity) => - if (polarity == positive) Variable(id, typ) - else neg(Variable(id, typ)) - case SimpleBoundVariable(no, typ, polarity) => throw new Exception("This case should be unreachable. Can't call toExpressionNNF on a bound variable") - case SimpleConstant(id, typ, polarity) => - if (polarity == positive) Constant(id, typ) - else neg(Constant(id, typ)) + 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)) @@ -274,37 +274,37 @@ private[fol] trait OLEquivalenceChecker extends Syntax { case Lambda(v, body) => SimpleLambda(v, polarize(body, true)) case Constant(`top`, Formula) => SimpleLiteral(true) case Constant(`bot`, Formula) => SimpleLiteral(false) - case Constant(id, typ) => SimpleConstant(id, typ, polarity) - case Variable(id, typ) => SimpleVariable(id, typ, polarity) + case Constant(id, sort) => SimpleConstant(id, sort, polarity) + case Variable(id, sort) => SimpleVariable(id, sort, polarity) } - def toLocallyNameless(e: SimpleExpression, subst: Map[(Identifier, Type), Int], i: Int): SimpleExpression = e match { + def toLocallyNameless(e: SimpleExpression, subst: Map[(Identifier, Sort), Int], i: Int): SimpleExpression = e match { case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless(_, subst, i)), polarity) case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless(inner, subst + ((x, Term) -> i), i + 1), polarity) case e: SimpleLiteral => e case SimpleEquality(left, right, polarity) => SimpleEquality(toLocallyNameless(left, subst, i), toLocallyNameless(right, subst, i), polarity) case v: SimpleVariable => - if (subst.contains((v.id, v.typ))) SimpleBoundVariable(i - subst((v.id, v.typ)), v.typ, v.polarity) + 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(toLocallyNameless(arg1, subst, i), toLocallyNameless(arg2, subst, i), polarity) - case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless(inner, subst + ((x.id, x.typ) -> i), i + 1)) + case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless(inner, subst + ((x.id, x.sort) -> i), i + 1)) } - def fromLocallyNameless(e: SimpleExpression, subst: Map[Int, (Identifier, Type)], i: Int): SimpleExpression = e match { + 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, typ, polarity) => + case SimpleBoundVariable(no, sort, polarity) => val dist = i - no - if (subst.contains(dist)) {val (id, typ) = subst(dist); SimpleVariable(id, typ, polarity)} + 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.typ)), i + 1)) + 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), Map.empty, 0) @@ -330,11 +330,11 @@ private[fol] trait OLEquivalenceChecker extends Syntax { case SimpleApplication(f, arg, true) => SimpleApplication(computeNormalForm(f), computeNormalForm(arg), true) - case SimpleBoundVariable(no, typ, true) => e + case SimpleBoundVariable(no, sort, true) => e - case SimpleVariable(id, typ, true) => e + case SimpleVariable(id, sort, true) => e - case SimpleConstant(id, typ, true) => e + case SimpleConstant(id, sort, true) => e case SimpleEquality(left, right, true) => val l = computeNormalForm(left) @@ -419,7 +419,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { def latticesLEQ(e1: SimpleExpression, e2: SimpleExpression): Boolean = { - require(e1.typ == Formula && e2.typ == Formula) + require(e1.sort == Formula && e2.sort == Formula) if (e1.uniqueKey == e2.uniqueKey) true else e1.lessThanCached(e2) match { @@ -470,7 +470,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { def latticesEQ(e1: SimpleExpression, e2: SimpleExpression): Boolean = if (e1.uniqueKey == e2.uniqueKey) true else if (e1.containsFormulas && e2.containsFormulas) { - if (e1.typ == Formula) latticesLEQ(e1, e2) && latticesLEQ(e2, e1) + 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 diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala index aab6d544..578acbfb 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -32,32 +32,32 @@ private[fol] trait Syntax { - sealed trait Type { - def ->(to: Type): Arrow = Arrow(this, to) + sealed trait Sort { + def ->(to: Sort): Arrow = Arrow(this, to) val isFunctional: Boolean def isPredicate: Boolean = !isFunctional val depth: Int } - case object Term extends Type { + case object Term extends Sort { val isFunctional = true val depth = 0 } - case object Formula extends Type { + case object Formula extends Sort { val isFunctional = false val depth = 0 } - sealed case class Arrow(from: Type, to: Type) extends Type { + sealed case class Arrow(from: Sort, to: Sort) extends Sort { val isFunctional = to.isFunctional val depth = 1+to.depth } - def depth(t:Type): Int = t match { + def depth(t:Sort): Int = t match { case Arrow(a, b) => 1 + depth(b) case _ => 0 } - def legalApplication(typ1: Type, typ2: Type): Option[Type] = { + def legalApplication(typ1: Sort, typ2: Sort): Option[Sort] = { typ1 match { case Arrow(`typ2`, to) => Some(to) case _ => None @@ -73,13 +73,13 @@ private[fol] trait Syntax { } sealed trait Expression { - val typ: Type + 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) => f.unapplySeq(arg).map(fargs => fargs :+ arg) + case Application(f, arg) => unapplySeq(f).map(fargs => fargs :+ arg) case _ => None } @@ -100,23 +100,23 @@ private[fol] trait Syntax { } - case class Variable(id: Identifier, typ:Type) extends Expression { - val containsFormulas = typ == Formula + 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, typ: Type) extends Expression { - val containsFormulas = typ == Formula + 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.typ, arg.typ) + private val legalapp = legalApplication(f.sort, arg.sort) require(legalapp.isDefined, s"Application of $f to $arg is not legal") - val typ = legalapp.get - val containsFormulas = typ == Formula || f.containsFormulas || arg.containsFormulas + 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 @@ -125,7 +125,7 @@ private[fol] trait Syntax { case class Lambda(v: Variable, body: Expression) extends Expression { val containsFormulas = body.containsFormulas - val typ = (v.typ -> body.typ) + val sort = (v.sort -> body.sort) def freeVariables: Set[Variable] = body.freeVariables - v def constants: Set[Constant] = body.constants @@ -222,8 +222,8 @@ private[fol] trait Syntax { case v: Variable => m.get(v) match { case Some(r) => - if (r.typ == v.typ) r - else throw new IllegalArgumentException("Type mismatch in substitution: " + v + " -> " + r) + if (r.sort == v.sort) r + else throw new IllegalArgumentException("Sort mismatch in substitution: " + v + " -> " + r) case None => v } case c: Constant => c @@ -232,7 +232,7 @@ private[fol] trait Syntax { Lambda(v, substituteVariables(t, m - v)) } - def flatTypeParameters(t: Type): List[Type] = t match { + 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/proof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala index 5b247911..f8e1bbf4 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala @@ -84,9 +84,9 @@ class RunningTheory { def makeDefinition(cst: Constant, expression: Expression, vars: Seq[Variable]): RunningTheoryJudgement[this.Definition] = { - if (cst.typ.depth == vars.length) - if (flatTypeParameters(cst.typ) zip vars.map(_.typ) forall { case (a, b) => a == b }) - if (cst.typ == expression.typ) + 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) { @@ -175,7 +175,7 @@ class RunningTheory { case Theorem(name, proposition, _) => proposition case Axiom(name, ax) => Sequent(Set.empty, Set(ax)) case Definition(cst, e, vars) => - if (cst.typ.isPredicate){ + if (cst.sort.isPredicate){ val inner = iff(vars.foldLeft(cst: Expression)(_(_)))(vars.foldLeft(e)(_(_))) Sequent(Set(), Set(inner)) } else { @@ -193,7 +193,7 @@ class RunningTheory { * @return true if the axiom was added to the theory, false else. */ def addAxiom(name: String, f: Expression): Option[Axiom] = { - if (f.typ == Formula && belongsToTheory(f)) { + if (f.sort == Formula && belongsToTheory(f)) { val ax = Axiom(name, f) theoryAxioms.update(name, ax) Some(ax) @@ -279,12 +279,12 @@ class RunningTheory { /** * Verify if a given formula is an axiom of the theory */ - def isAxiom(f: Expression): Boolean = f.typ == Formula && 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: Expression): Option[Axiom] = if (f.typ == Formula) theoryAxioms.find(a => isSame(a._2.ax, f)).map(_._2) else None + 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. 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 65aee20d..f0df4e3d 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -51,8 +51,8 @@ object SCProofChecker { * Γ, φ |- φ, Δ */ case Hypothesis(Sequent(left, right), phi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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 φ") @@ -64,8 +64,8 @@ object SCProofChecker { * Γ, Σ |- Δ, Π */ case Cut(b, t1, t2, phi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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)) @@ -83,10 +83,10 @@ object SCProofChecker { * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ */ case LeftAnd(b, t1, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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 ( @@ -103,9 +103,9 @@ object SCProofChecker { * Γ, Σ, φ∨ψ |- Δ, Π */ case LeftOr(b, t, disjuncts) => - if (disjuncts.exists(phi => phi.typ != Formula)){ - val culprit = disjuncts.find(phi => phi.typ != Formula).get - SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) + 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 ( @@ -121,10 +121,10 @@ object SCProofChecker { * Γ, Σ, φ⇒ψ |- Δ, Π */ case LeftImplies(b, t1, t2, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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)) @@ -139,10 +139,10 @@ object SCProofChecker { * Γ, φ⇔ψ |- Δ Γ, φ⇔ψ |- Δ */ case LeftIff(b, t1, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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) @@ -164,8 +164,8 @@ object SCProofChecker { * Γ, ¬φ |- Δ */ case LeftNot(b, t1, phi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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)) @@ -181,12 +181,12 @@ object SCProofChecker { * Γ, ∀x. φ |- Δ */ case LeftForall(b, t1, phi, x, t) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) - else if (t.typ != Term) - SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + 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)) @@ -199,10 +199,10 @@ object SCProofChecker { * Γ, ∃x. φ|- Δ */ case LeftExists(b, t1, phi, x) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + 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))) @@ -218,9 +218,9 @@ object SCProofChecker { * Γ, Σ |- φ∧ψ, Π, Δ */ case RightAnd(b, t, cunjuncts) => - if (cunjuncts.exists(phi => phi.typ != Formula)){ - val culprit = cunjuncts.find(phi => phi.typ != Formula).get - SCInvalidProof(SCProof(step), Nil, "all φs must be a formula, but " + culprit + " is a " + culprit.typ) + 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 _))) @@ -239,10 +239,10 @@ object SCProofChecker { * Γ |- φ∨ψ, Δ Γ |- φ∨ψ, Δ */ case RightOr(b, t1, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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)) @@ -261,10 +261,10 @@ object SCProofChecker { * Γ |- φ⇒ψ, Δ */ case RightImplies(b, t1, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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)) @@ -279,10 +279,10 @@ object SCProofChecker { * Γ, Σ |- φ⇔ψ, Π, Δ */ case RightIff(b, t1, t2, phi, psi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (psi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "ψ must be a formula, but it is a " + phi.typ) + 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) @@ -303,8 +303,8 @@ object SCProofChecker { * Γ |- ¬φ, Δ */ case RightNot(b, t1, phi) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) + 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)) @@ -319,10 +319,10 @@ object SCProofChecker { * Γ |- ∀x. φ, Δ */ case RightForall(b, t1, phi, x) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) + 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))) @@ -336,12 +336,12 @@ object SCProofChecker { * Γ |- ∃x. φ, Δ */ case RightExists(b, t1, phi, x, t) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) - else if (t.typ != Term) - SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + 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)) @@ -356,12 +356,12 @@ object SCProofChecker { * */ case RightEpsilon(b, t1, phi, x, t) => - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (x.typ != Term) - SCInvalidProof(SCProof(step), Nil, "x must be a term variable, but it is a " + x.typ) - else if (t.typ != Term) - SCInvalidProof(SCProof(step), Nil, "t must be a term , but it is a " + t.typ) + 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)))) @@ -391,12 +391,12 @@ object SCProofChecker { */ case LeftBeta(b, t1, phi, lambda, t, x) => val Lambda(y, e) = lambda - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (y.typ != t.typ) - SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.typ + " and " + y.typ) - else if (e.typ != x.typ) - SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.typ + " and " + x.typ) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.sort + " and " + y.sort) + else if (e.sort != x.sort) + SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.sort + " and " + x.sort) else if (isSameSet(b.left, ref(t1).left)) { val redex = lambda(t) val normalized = substituteVariables(e, Map(y -> t)) @@ -417,12 +417,12 @@ object SCProofChecker { */ case RightBeta(b, t1, phi, lambda, t, x) => val Lambda(y, e) = lambda - if (phi.typ != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.typ) - else if (y.typ != t.typ) - SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.typ + " and " + y.typ) - else if (e.typ != x.typ) - SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.typ + " and " + x.typ) + if (phi.sort != Formula) + SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.sort + " and " + y.sort) + else if (e.sort != x.sort) + SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.sort + " and " + x.sort) else if (isSameSet(b.right, ref(t1).right)) { val redex = lambda(t) val normalized = substituteVariables(e, Map(y -> t)) @@ -476,9 +476,9 @@ object SCProofChecker { */ case LeftSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => val (phi_arg, phi_body) = lambdaPhi - if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else if (!s.typ.isFunctional) + else if (!s.sort.isFunctional) SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") else { val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) @@ -518,9 +518,9 @@ object SCProofChecker { */ case RightSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => val (phi_arg, phi_body) = lambdaPhi - if (s.typ != phi_arg.typ || t.typ != phi_arg.typ) + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else if (!s.typ.isFunctional) + else if (!s.sort.isFunctional) SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") else { val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) @@ -556,9 +556,9 @@ object SCProofChecker { */ case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => val (phi_arg, phi_body) = lambdaPhi - if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) + 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.typ.isPredicate) + 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)) @@ -598,9 +598,9 @@ object SCProofChecker { */ case RightSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => val (phi_arg, phi_body) = lambdaPhi - if (psi.typ != phi_arg.typ || tau.typ != phi_arg.typ) + 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.typ.isPredicate) + 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)) 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 58bcf989..bf679c6b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala @@ -22,7 +22,7 @@ object SequentCalculus { * @param right the right side of the sequent */ case class Sequent(left: Set[Expression], right: Set[Expression]){ - require(left.forall(_.typ == Formula) && right.forall(_.typ == Formula), "Sequent can only contain formulas") + require(left.forall(_.sort == Formula) && right.forall(_.sort == Formula), "Sequent can only contain formulas") } /** diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index cdc20f01..7d12e5dd 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -13,8 +13,8 @@ import scala.annotation.targetName */ object KernelHelpers { - def predicateType(arity: Int) = Range(0, arity).foldLeft(Formula: Type)((acc, _) => Term -> acc) - def functionType(arity: Int) = Range(0, arity).foldLeft(Term: Type)((acc, _) => Term -> acc) + 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 // @@ -53,7 +53,7 @@ object KernelHelpers { def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) @targetName("forallUnapply") def unapply(e: Expression): Option[(Variable, Expression)] = e match { - case forall(Lambda(x, inner)) => Some((x, inner)) + case Application(forall, Lambda(x, inner)) => Some((x, inner)) case _ => None } } @@ -62,7 +62,7 @@ object KernelHelpers { def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) @targetName("existsUnapply") def unapply(e: Expression): Option[(Variable, Expression)] = e match { - case exists(Lambda(x, inner)) => Some((x, inner)) + case Application(exists, Lambda(x, inner)) => Some((x, inner)) case _ => None } } @@ -71,7 +71,7 @@ object KernelHelpers { def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) @targetName("epsilonUnapply") def unapply(e: Expression): Option[(Variable, Expression)] = e match { - case epsilon(Lambda(x, inner)) => Some((x, inner)) + case Application(epsilon, Lambda(x, inner)) => Some((x, inner)) case _ => None } } @@ -126,15 +126,15 @@ object KernelHelpers { case epsilon(v, inner) => s"(epsilon(${v.repr}, ${inner.repr})" case Application(f, arg) => s"${f.repr}(${arg.repr})" - case Constant(id, typ) => id.toString + case Constant(id, sort) => id.toString case Lambda(v, body) => s"lambda(${v.repr}, ${body.repr})" - case Variable(id, typ) => id.toString + case Variable(id, sort) => id.toString def fullRepr: String = f match case Application(f, arg) => s"${f.fullRepr}(${arg.fullRepr})" - case Constant(id, typ) => s"cst(${id},${typ})" + case Constant(id, sort) => s"cst(${id},${sort})" case Lambda(v, body) => s"λ${v.fullRepr}.${body.fullRepr}" - case Variable(id, typ) => s"v(${id},${typ})" + case Variable(id, sort) => s"v(${id},${sort})" } /* Conversions */ @@ -324,14 +324,14 @@ object KernelHelpers { def reduceLambda(f: Lambda, t: Expression): Expression = substituteVariables(f.body, Map(f.v -> t)) // declare symbols easily: "val x = variable;" - def HOvariable(using name: sourcecode.Name)(typ: Type): Variable = Variable(name.value, typ) + 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 v(id: Identifier, typ:Type): Variable = Variable(id, typ) - def function(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Term: Type)((acc, _)=> Term -> acc)) + def v(id: Identifier, sort:Sort): Variable = Variable(id, sort) + 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: Type)((acc, _)=> Term -> acc)) - def connector(arity: Integer)(using name: sourcecode.Name): Variable = Variable(name.value, Range(0, arity).foldLeft(Formula: Type)((acc, _)=> Formula -> acc)) - def cst(id: Identifier, typ:Type): Constant = Constant(id, typ) + 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(id: Identifier, sort:Sort): Constant = Constant(id, sort) // Conversions from String to Identifier class InvalidIdentifierException(identifier: String, errorMessage: String) extends LisaException(errorMessage) { @@ -404,13 +404,13 @@ object KernelHelpers { * of the theorem to have more explicit writing and for sanity check. See also [[lisa.kernel.proof.RunningTheory.makePredicateDefinition]] */ def definition(symbol: String, expression: Expression): RunningTheoryJudgement[theory.Definition] = { - val label = Constant(symbol, expression.typ) + val label = Constant(symbol, expression.sort) val vars = expression.leadingVars() - if (vars.length == expression.typ.depth) then + if (vars.length == expression.sort.depth) then theory.makeDefinition(label, expression, vars) else var maxid = expression.maxVarId()-1 - val newvars = flatTypeParameters(expression.typ).drop(vars.length).map(t => {maxid+=1;Variable(Identifier("x", maxid), t)}) + val newvars = flatTypeParameters(expression.sort).drop(vars.length).map(t => {maxid+=1;Variable(Identifier("x", maxid), t)}) theory.makeDefinition(label, expression, vars ++ newvars) } @@ -427,7 +427,7 @@ object KernelHelpers { * @return The List of undefined symols */ def findUndefinedSymbols(phi: Expression): Set[Constant] = phi match { - case Variable(id, typ) => Set.empty + 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) @@ -449,7 +449,7 @@ object KernelHelpers { 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 => - s" Definition of symbol ${d.cst.id} : ${d.cst.typ} := ${d.expression}\n" + s" Definition of symbol ${d.cst.id} : ${d.cst.sort} := ${d.expression}\n" } } diff --git a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala index 2c40c790..f31567b0 100644 --- a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala +++ b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala @@ -44,26 +44,26 @@ object Serialization { type Line = Int - def typeToString(t: Type): String = + 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.typ) - def variableToSting(v: Variable): String = "var_" + v.id.name + "_" + v.id.no + "_" + typeToString(v.typ) + 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.typ)) + 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.typ)) + dos.writeUTF(typeToString(v.sort)) /* def formulaLabelToDOS(label: FormulaLabel, dos: DataOutputStream): Unit = @@ -114,12 +114,12 @@ object Serialization { treesDOS.writeByte(0) treesDOS.writeUTF(v.id.name) treesDOS.writeInt(v.id.no) - treesDOS.writeUTF(typeToString(v.typ)) + 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.typ)) + treesDOS.writeUTF(typeToString(c.sort)) case Lambda(v, inner) => treesDOS.writeByte(2) val vi = lineOfExpr(v) @@ -351,7 +351,7 @@ object Serialization { } - def typeFromString(s: String): (Type, String) = + 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 @@ -378,13 +378,13 @@ object Serialization { case 0 => val name = treesDIS.readUTF() val no = treesDIS.readInt() - val typ = treesDIS.readUTF() - Variable(Identifier(name, no), typeFromString(typ)._1) + val sort = treesDIS.readUTF() + Variable(Identifier(name, no), typeFromString(sort)._1) case 1 => val name = treesDIS.readUTF() val no = treesDIS.readInt() - val typ = treesDIS.readUTF() - Constant(Identifier(name, no), typeFromString(typ)._1) + val sort = treesDIS.readUTF() + Constant(Identifier(name, no), typeFromString(sort)._1) case 2 => val v = exprMap(treesDIS.readInt()) val body = exprMap(treesDIS.readInt()) @@ -560,8 +560,8 @@ object Serialization { case (obj, theory.Axiom(name, ax)) => "a" + obj + "$" + name case (obj, theory.Theorem(name, proposition, withSorry)) => "t" + obj + "$" + name case (obj, theory.Definition(label, expression, vars)) => - "d" + obj + "$" + label.id.name + "_" + label.id.no + "_" + typeToString(label.typ) //+ "__" + - //vars.size + vars.map(v => v.id.name + "_" + v.id.no + "_" + typeToString(v.typ)).mkString("__") + "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, proof, justNames) @@ -589,8 +589,8 @@ object Serialization { case 't' => theory.getTheorem(name).get case 'd' => - val Array(id, no, typ) = name.split("_") - val cst = Constant(Identifier(id, no.toInt), typeFromString(typ)._1) + val Array(id, no, sort) = name.split("_") + val cst = Constant(Identifier(id, no.toInt), typeFromString(sort)._1) theory.getDefinition(cst).get } if debug then 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 00000000..ad75cd26 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -0,0 +1,99 @@ +package lisa.fol + +import lisa.utils.K +import K.given + +trait Predef extends Syntax { + + + def variable[S](using IsSort[SortOf[S]])(id: K.Identifier): Var[SortOf[S]] = new Var(id) + def constant[S](using IsSort[SortOf[S]])(id: K.Identifier): Cst[SortOf[S]] = new Cst(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]]): Var[SortOf[S]] = new Var(name.value) + def constant[S](using name: sourcecode.Name, is: IsSort[SortOf[S]]): Cst[SortOf[S]] = new Cst(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) + + 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]("∧") + val /\ : and.type = and + val ∧ : and.type = and + + val or = constant[Formula >>: Formula >>: Formula]("∨") + val \/ : or.type = or + val ∨ : or.type = or + + val implies = constant[Formula >>: Formula >>: Formula]("⇒") + val ==> : implies.type = implies + + val iff = constant[Formula >>: Formula >>: Formula]("⇔") + val <=> : iff.type = iff + val ⇔ : iff.type = iff + + val forall = constant[(Term >>: Formula) >>: Formula]("∀") + val ∀ : forall.type = forall + + val exists = constant[(Term >>: Formula) >>: Formula]("∃") + val ∃ : exists.type = exists + + val epsilon = constant[(Term >>: Formula) >>: Term]("ε") + val ε : epsilon.type = epsilon + + val existsOne = constant[(Term >>: Formula) >>: Formula]("∃!") + 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(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) + } + + 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): Cst[?] = + new Cst[T](c.id)(using new Sort { type Self = T; val underlying = c.sort }) + + def asFrontVariable(v: K.Variable): Var[?] = + new Var[T](v.id)(using new Sort { type Self = T; val underlying = v.sort }) + + def asFrontApplication(a: K.Application): App[?, ?] = + new App[T, T](asFrontExpression(a.f).asInstanceOf, asFrontExpression(a.arg).asInstanceOf)( + using new Sort { type Self = T; val underlying = a.sort }) + + def asFrontLambda(l: K.Lambda): Abs[?, ?] = + new Abs[T, T](asFrontVariable(l.v).asInstanceOf, asFrontExpression(l.body).asInstanceOf)( + using new Sort { type Self = T; val underlying = l.sort }) + +} \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala new file mode 100644 index 00000000..d9450b00 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala @@ -0,0 +1,235 @@ +package lisa.fol + +//import lisa.kernel.proof.SequentCalculus.Sequent + +/* +import lisa.prooflib.BasicStepTactic +import lisa.prooflib.Library +import lisa.prooflib.ProofTacticLib.ProofTactic +*/ +import lisa.utils.K + +import scala.annotation.showAsInfix + +trait Sequents extends Predef { + /* + object SequentInstantiationRule extends ProofTactic + given ProofTactic = SequentInstantiationRule + */ + + 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 substituteUnsafe(m: Map[Var[?], Expr[?]]): Sequent = Sequent(left.map(_.substituteUnsafe(m)), right.map(_.substituteUnsafe(m))) + override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Sequent = + super.substituteWithCheck(m).asInstanceOf[Sequent] + override def substitute(pairs: SubstPair[?]*): Sequent = + super.substitute(pairs*).asInstanceOf[Sequent] + + def freeVars: Set[Var[?]] = left.flatMap(_.freeVars) ++ right.flatMap(_.freeVars) + def freeTermVars: Set[Var[T]] = left.flatMap(_.freeTermVars) ++ right.flatMap(_.freeTermVars) + def constants: Set[Cst[?]] = 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. + * Namely, if "that" is the result of the substitution, the proof should conclude with "that.underlying", + * using the assumption "this.underlying" at step index -1. + * + * @param map + * @return + */ + def instantiateWithProof(map: Map[Var[?], Expr[?]], 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)) + } + } + ) + (substituteUnsafe(map), instantiateWithProofLikeKernel(mConn, mPred, mTerm, 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) => + val t = args.head + val newf = 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 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 + (this.left |- newf).instantiateForallWithProof(args.tail, index + 3) match { + case (s, p) => (s, Seq(s0, s1, s2) ++ p) + } + + case _ => throw new IllegalArgumentException("Right side of sequent must be a single universally quantified formula") + } + + } + + /** + * Given 3 substitution maps like the kernel accepts, i.e. Substitution of Predicate Connector and Term schemas, do the substitution + * and produce the (one-step) kernel proof that the result is provable from the original sequent + * + * @param mCon The substitution of connector schemas + * @param mPred The substitution of predicate schemas + * @param mTerm The substitution of function schemas + * @return + */ + def instantiateWithProofLikeKernel( + mCon: Map[SchematicConnectorLabel[?], LambdaExpression[Formula, Formula, ?]], + mPred: Map[SchematicPredicateLabel[?] | VariableFormula, LambdaExpression[Term, Formula, ?]], + mTerm: Map[SchematicFunctionLabel[?] | Variable, LambdaExpression[Term, Term, ?]], + 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)) + } +*/ + + 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) + infix def ->>(f: Formula): Sequent = this.copy(right = this.right - f) + infix def ++<<(s1: Sequent): Sequent = this.copy(left = this.left ++ s1.left) + infix def --<<(s1: Sequent): Sequent = this.copy(left = this.left -- s1.left) + infix def ++>>(s1: Sequent): Sequent = this.copy(right = this.right ++ s1.right) + infix def -->>(s1: Sequent): Sequent = this.copy(right = this.right -- s1.right) + infix def ++(s1: Sequent): Sequent = this.copy(left = this.left ++ s1.left, right = this.right ++ s1.right) + infix def --(s1: Sequent): Sequent = this.copy(left = this.left -- s1.left, right = this.right -- s1.right) + + infix def removeLeft(f: Formula): Sequent = this.copy(left = this.left.filterNot(isSame(_, f))) + infix def removeRight(f: Formula): Sequent = this.copy(right = this.right.filterNot(isSame(_, f))) + infix def removeAllLeft(s1: Sequent): Sequent = this.copy(left = this.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2)))) + infix def removeAllLeft(s1: Set[Formula]): Sequent = this.copy(left = this.left.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAllRight(s1: Sequent): Sequent = this.copy(right = this.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) + infix def removeAllRight(s1: Set[Formula]): Sequent = this.copy(right = this.right.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) + infix def removeAll(s1: Sequent): Sequent = + this.copy(left = this.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2))), right = this.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) + + infix def addLeftIfNotExists(f: Formula): Sequent = if (this.left.exists(isSame(_, f))) this else (this +<< f) + infix def addRightIfNotExists(f: Formula): Sequent = if (this.right.exists(isSame(_, f))) this else (this +>> f) + infix def addAllLeftIfNotExists(s1: Sequent): Sequent = this ++<< s1.copy(left = s1.left.filterNot(e1 => this.left.exists(isSame(_, e1)))) + infix def addAllRightIfNotExists(s1: Sequent): Sequent = this ++>> s1.copy(right = s1.right.filterNot(e1 => this.right.exists(isSame(_, e1)))) + infix def addAllIfNotExists(s1: Sequent): Sequent = + this ++ s1.copy(left = s1.left.filterNot(e1 => this.left.exists(isSame(_, e1))), right = s1.right.filterNot(e1 => this.right.exists(isSame(_, e1)))) + + // OL shorthands + infix def +?(f: Formula): Sequent = this addRightIfNotExists f + infix def ->?(f: Formula): Sequent = this removeRight f + infix def ++?(s1: Sequent): Sequent = this addAllRightIfNotExists s1 + infix def -->?(s1: Sequent): Sequent = this removeAllRight s1 + infix def --?(s1: Sequent): Sequent = this removeAll s1 + infix def ++?(s1: Sequent): Sequent = this addAllIfNotExists s1 + + override def toString = + (if left.size == 0 then "" else if left.size == 1 then left.head.toString else "( " + left.mkString(", ") + " )") + + " ⊢ " + + (if right.size == 0 then "" else if right.size == 1 then right.head.toString else "( " + right.mkString(", ") + " )") + + } + + val emptySeq: Sequent = Sequent(Set.empty, Set.empty) + + given Conversion[Formula, Sequent] = f => Sequent(Set.empty, Set(f)) + + def isSame(e1: Expr[?], e2: Expr[?]): Boolean = { + e1.sort == e2.sort && K.isSame(e1.underlying, e2.underlying) + } + + def isSameSequent(sequent1: Sequent, sequent2: Sequent): Boolean = { + K.isSameSequent(sequent1.underlying, sequent2.underlying) + } + + /** + * returns true if the first argument implies the second by the laws of ortholattices. + */ + 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[Expr[?]], s2: Set[Expr[?]]): Boolean = { + K.isSubset(s1.map(_.underlying), s2.map(_.underlying)) + } + def isSameSet(s1: Set[Expr[?]], s2: Set[Expr[?]]): Boolean = + K.isSameSet(s1.map(_.underlying), s2.map(_.underlying)) + + def contains(s: Set[Expr[?]], f: Expr[?]): Boolean = { + K.contains(s.map(_.underlying), f.underlying) + } + + /** + * 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 + */ + trait FormulaSetConverter[T] { + def apply(t: T): Set[Formula] + } + + given FormulaSetConverter[Unit] with { + override def apply(u: Unit): Set[Formula] = 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 + } + + given formula_to_set[T <: Formula]: FormulaSetConverter[T] with { + override def apply(f: T): Set[Formula] = Set(f) + } + + given iterable_to_set[T <: Formula, I <: Iterable[T]]: FormulaSetConverter[I] with { + override def apply(s: I): Set[Formula] = s.toSet + } + + private def any2set[A, T <: A](any: T)(using c: FormulaSetConverter[T]): Set[Formula] = 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)) + infix def ⊢[B, T2 <: B](right: T2)(using FormulaSetConverter[T2]): Sequent = Sequent(any2set(left), any2set(right)) + } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 8da823ef..0539ac29 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -10,210 +10,224 @@ import scala.annotation.showAsInfix import scala.annotation.targetName trait Syntax { - - - - - - + type IsSort[T] = Sort{type Self = T} + - object First { - trait T - trait F - trait Arrow[A: Type, B: Type] + trait T + trait F + trait Arrow[A: Sort, B: Sort] - trait Type { - type Self - val underlying: K.Type - } - given given_TermType: (Type{type Self = T}) with - val underlying = K.Term - given given_FormulaType: (Type{type Self = F}) with - val underlying = K.Formula - given given_ArrowType[A : Type, B : Type]: (Type{type Self = Arrow[A, B]}) with - val underlying = K.Arrow(summon[Type{type Self = A}].underlying, summon[Type{type Self = B}].underlying) - - class SubstPair[T: Type] private (val _1: Var[T], val _2: Expr[T]) { - // def toTuple = (_1, _2) - } - object SubstPair { - def apply[T : Type](_1: Var[T], _2: Expr[T]) = new SubstPair(_1, _2) - } + 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 + } - given [T: Type]: Conversion[(Var[T], Expr[T]), SubstPair[T]] = s => SubstPair(s._1, s._2) + trait Sort { + type Self + val underlying: K.Sort + } + 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) + + class SubstPair[T: Sort] private (val _1: Var[T], val _2: Expr[T]) + object SubstPair { + def apply[T : Sort](_1: Var[T], _2: Expr[T]) = new SubstPair(_1, _2) + } + given [T: Sort]: Conversion[(Var[T], Expr[T]), SubstPair[T]] = s => SubstPair(s._1, s._2) - trait Expr[S : Type] { - val typ: K.Type = summon[Type{type Self = S}].underlying - def underlying: K.Expression - def substUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] - def substituteWithCheck(m: Map[Var[?], Expr[?]]): Expr[S] = { - if m.forall((k, v) => k.typ == v.typ) then - substUnsafe(m) - else - val culprit = m.find((k, v) => k.typ != v.typ).get - throw new IllegalArgumentException("Type mismatch in substitution: " + culprit._1 + " -> " + culprit._2) - } - def substitute(pairs: SubstPair[?]*): Expr[S] = - substituteWithCheck(pairs.view.map(s => (s._1, s._2)).toMap) - def freeVars: Set[Var[?]] - def freeTermVars: Set[Var[T]] + trait LisaObject { + def substituteUnsafe(m: Map[Var[?], Expr[?]]): LisaObject + def substituteWithCheck(m: Map[Var[?], 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[Var[?]] + def freeTermVars: Set[Var[T]] + def constants: Set[Cst[?]] + } + trait Expr[S: Sort] extends LisaObject { + val sort: K.Sort = summon[IsSort[S]].underlying + private val arity = K.flatTypeParameters(sort).size + def underlying: K.Expression + + def substituteUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] + override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Expr[S]] + override def substitute(pairs: SubstPair[?]*): LisaObject = + super.substitute(pairs: _*).asInstanceOf[Expr[S]] + + def unapply[T1, T2](e: Expr[Arrow[T1, T2]]): Option[Expr[T1]] = e match { + case App[T1, T2](f, arg) if f == this => Some(arg) + case _ => None + } + final def defaultMkString(args: Seq[Expr[?]]): String = s"$this(${args.map(a => s"(${a})")})" + final def defaultMkStringSeparated(args: Seq[Expr[?]]): String = s"(${defaultMkString(args)})" + var mkString: Seq[Expr[?]] => String = defaultMkString + var mkStringSeparated: Seq[Expr[?]] => String = defaultMkStringSeparated + } + + 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) + + - type Formula = Expr[F] - type Term = Expr[T] - - case class Var[S : Type](id: K.Identifier) extends Expr[S] { - val underlying: K.Variable = K.Variable(id, typ) - def substUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] = m.getOrElse(this, this).asInstanceOf[Expr[S]] - def freeVars: Set[Var[?]] = Set(this) - def freeTermVars: Set[Var[T]] = if typ == K.Term then Set(this.asInstanceOf) else Set.empty - def rename(newId: K.Identifier): Var[S] = Var(newId) - def freshRename(existing: Iterable[Expr[?]]): Var[S] = { - val newId = K.freshId(existing.flatMap(_.freeVars.map(_.id)), id) - Var(newId) - } - - def :=(replacement: Expr[S]) = SubstPair(this, replacement) - } - case class Cst[S : Type](id: K.Identifier) extends Expr[S] { - val underlying: K.Constant = K.Constant(id, typ) - def substUnsafe(m: Map[Var[?], Expr[?]]): Cst[S] = this - def freeVars: Set[Var[?]] = Set.empty - def freeTermVars: Set[Var[T]] = Set.empty - def rename(newId: K.Identifier): Cst[S] = Cst(newId) - } - case class App[T1 : Type, T2 : Type](f: Expr[Arrow[T1, T2]], arg: Expr[T1]) extends Expr[T2] { - val underlying: K.Application = K.Application(f.underlying, arg.underlying) - def substUnsafe(m: Map[Var[?], Expr[?]]): App[T1, T2] = App(f.substUnsafe(m), arg.substUnsafe(m)) - def freeVars: Set[Var[?]] = f.freeVars ++ arg.freeVars - def freeTermVars: Set[Var[T]] = f.freeTermVars ++ arg.freeTermVars - } - case class Abs[T1 : Type, T2 : Type](v: Var[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { - val underlying: K.Lambda = K.Lambda(v.underlying, body.underlying) - def substUnsafe(m: Map[Var[?], Expr[?]]): Abs[T1, T2] = Abs(v, body.substUnsafe(m - v)) - def freeVars: Set[Var[?]] = body.freeVars - v - def freeTermVars: Set[Var[T]] = body.freeTermVars.filterNot(_ == v) - } - - extension [T1 : Type, T2 : Type](f: Expr[Arrow[T1, T2]]) { - def apply(arg: Expr[T1]): Expr[T2] = App(f, arg) - } + def unfoldAllApp(e:Expr[?]): (Expr[?], List[Expr[?]]) = e match + case App(f, arg) => + val (f1, args) = unfoldAllApp(f) + (f1, arg :: args ) + case _ => (e, Nil) - val x = Var[T]("x") - val y: Expr[F] = Var("x") - val z: Expr[Arrow[T, F]] = Var("x") - z(x) - @showAsInfix - infix type |->[I, O] = (I, O) match { - case (Expr[T], Expr[F]) => Expr[Arrow[T, F]] + case class Var[S : Sort](id: K.Identifier) extends Expr[S] { + val underlying: K.Variable = K.Variable(id, sort) + def substituteUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] = m.getOrElse(this, this).asInstanceOf[Expr[S]] + override def substituteWithCheck(m: Map[Var[?], 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[Var[?]] = Set(this) + def freeTermVars: Set[Var[T]] = if sort == K.Term then Set(this.asInstanceOf) else Set.empty + def constants: Set[Cst[?]] = Set.empty + def rename(newId: K.Identifier): Var[S] = Var(newId) + def freshRename(existing: Iterable[Expr[?]]): Var[S] = { + val newId = K.freshId(existing.flatMap(_.freeVars.map(_.id)), id) + Var(newId) } + override def toString(): String = id.toString + def :=(replacement: Expr[S]) = SubstPair(this, replacement) } - object FirstTest { - import First._ - - val x1: Term = Var("x") - val y1: Formula = Var("y") - val z1: (Term |-> Formula) = Var("z") + object Var { + def apply(id: String, sort: K.Sort): Var[?] = Var(id)(using new Sort { type Self = T; val underlying = sort }) } + case class Cst[S : Sort](id: K.Identifier) extends Expr[S] { + private var infix: Boolean = false + def setInfix(): Unit = infix = true + val underlying: K.Constant = K.Constant(id, sort) + def substituteUnsafe(m: Map[Var[?], Expr[?]]): Cst[S] = this + override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Expr[S] = + super.substituteWithCheck(m).asInstanceOf[Cst[S]] + override def substitute(pairs: SubstPair[?]*): Cst[S] = + super.substitute(pairs: _*).asInstanceOf[Cst[S]] + def freeVars: Set[Var[?]] = Set.empty + def freeTermVars: Set[Var[T]] = Set.empty + def constants: Set[Cst[?]] = Set(this) + def rename(newId: K.Identifier): Cst[S] = Cst(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) + } + object Cst { + def apply(id: String, sort: K.Sort): Cst[?] = Cst(id)(using new Sort { type Self = T; val underlying = sort }) + } - object Third { - trait Expr { - val typ: K.Type - } - - - opaque type Term <: Expr = Expr - opaque type Formula <: Expr = Expr - opaque type |->[A, B] <: Expr = Expr + class Binder[T1: Sort, T2: Sort, T3: Sort](id: K.Identifier) extends Cst[Arrow[Arrow[T1, T2], T3]](id) { + def apply(v1: Var[T1], e: Expr[T2]): App[Arrow[T1, T2], T3] = App(this, Abs(v1, e)) + 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) + } + } - sealed trait Type { - type Self <: Expr - val underlying: K.Type - val isExpr: (Expr =:= Self) - } - given (Term is Type) with - val underlying = K.Term - val isExpr = summon[Expr =:= Term] - given (Formula is Type) with - val underlying = K.Formula - val isExpr = summon[Expr =:= Formula] - given [A : Type, B : Type]: ((|->[A, B]) is Type) with - val underlying = K.Arrow(summon[Type{type Self = A}].underlying, summon[Type{type Self = B}].underlying) - val isExpr = summon[Expr =:= |->[A, B]] - - - case class Var(id: K.Identifier, typ: K.Type) extends Expr - object Var { - def apply[T: Type](id: String): Var & T = - val evT = summon[Type{type Self = T}] - (new Var(id, evT.underlying)).asInstanceOf - } - case class Cst(id: K.Identifier, typ: K.Type) extends Expr - case class App(f: Expr, arg: Expr, typ: K.Type) extends Expr - case class Abs(v: Var, body: Expr, typ: K.Type) extends Expr + case class App[T1 : Sort, T2 : Sort](f: Expr[Arrow[T1, T2]], arg: Expr[T1]) extends Expr[T2] { + val underlying: K.Application = K.Application(f.underlying, arg.underlying) + def substituteUnsafe(m: Map[Var[?], Expr[?]]): App[T1, T2] = App[T1, T2](f.substituteUnsafe(m), arg.substituteUnsafe(m)) + override def substituteWithCheck(m: Map[Var[?], 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[Var[?]] = f.freeVars ++ arg.freeVars + def freeTermVars: Set[Var[T]] = f.freeTermVars ++ arg.freeTermVars + def constants: Set[Cst[?]] = f.constants ++ arg.constants + override def toString(): String = + val (f, args) = unfoldAllApp(this) + f.mkString(args) } + object App { + def apply(f: Expr[?], arg: Expr[?]): Expr[?] = + val rsort = K.legalApplication(f.sort, arg.sort) + rsort match + case Some(to) => + App(f.asInstanceOf, arg.asInstanceOf)(using new Sort { type Self = T; val underlying = to }, new Sort { type Self = T; val underlying = to }) + case None => throw new IllegalArgumentException(s"Cannot apply $f of sort ${f.sort} to $arg of sort ${arg.sort}") + } - object Fourth { - trait Expr[T <: Expr[T]] { - val typ: K.Type - } + case class Abs[T1 : Sort, T2 : Sort](v: Var[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { + val underlying: K.Lambda = K.Lambda(v.underlying, body.underlying) + def substituteUnsafe(m: Map[Var[?], Expr[?]]): Abs[T1, T2] = Abs(v, body.substituteUnsafe(m - v)) + override def substituteWithCheck(m: Map[Var[?], 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[Var[?]] = body.freeVars - v + def freeTermVars: Set[Var[T]] = body.freeTermVars.filterNot(_ == v) + def constants: Set[Cst[?]] = body.constants + override def toString(): String = s"Abs($v, $body)" + } - /* - opaque type Term <: Expr[?] = Expr[?] - opaque type Formula <: Expr[?] = Expr[?] - opaque type |->[A, B] <: Expr[?] = Expr[?] -*/ - sealed trait Term extends Expr[Term] { - } - sealed trait Formula extends Expr[Formula] { - } - sealed trait |->[A, B] extends Expr[|->[A, B]] { - } - - sealed trait Type { - type Self <: Expr[?] - val underlying: K.Type - } - given (Term is Type) with - val underlying = K.Term - given (Formula is Type) with - val underlying = K.Formula - given [A : Type, B : Type]: ((|->[A, B]) is Type) with - val underlying = K.Arrow(summon[Type{type Self = A}].underlying, summon[Type{type Self = B}].underlying) - - case class Var[T<: Expr[T]](id: K.Identifier, typ: K.Type) extends Expr[T] - object Var { - def apply[T <: Expr[T] : Type](id: String): Var[T] = - val evT = summon[Type{type Self = T}] - (new Var(id, evT.underlying)).asInstanceOf - } + extension [T1, T2](f: Expr[Arrow[T1, T2]]) { + def apply(using IsSort[T1], IsSort[T2])(arg: Expr[T1]): Expr[T2] = App(f, arg) + } - } - object FourthTest { - import Fourth._ + private val x = Var[T]("x") + private val y = Var[F]("x") + private val z: Expr[Arrow[T, F]] = Var("x") + z(x) - val x: Var[Term] = Var("x") - val y: Var[Formula] = Var("y") - val z: Var[ |->[Term, Formula]] = Var("z") - } } From b6ffb858ed0e4d67e9c4e5f8951be5a382897f63 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Tue, 15 Oct 2024 18:21:15 +0200 Subject: [PATCH 11/92] Mosly finished with prooflib, currently doing definitions. Remains BasicStepTactics for beta conversion. --- .../lisa/kernel/proof/SCProofChecker.scala | 87 +- .../main/scala/lisa/utils/LisaException.scala | 10 +- .../src/main/scala/lisa/utils/fol/FOL.scala | 5 + .../main/scala/lisa/utils/fol/Predef.scala | 24 +- .../main/scala/lisa/utils/fol/Sequents.scala | 75 +- .../main/scala/lisa/utils/fol/Syntax.scala | 117 +- .../scala/lisa/utils/prooflib/BasicMain.scala | 29 + .../lisa/utils/prooflib/BasicStepTactic.scala | 1419 +++++++++++++++++ .../scala/lisa/utils/prooflib/Exports.scala | 6 + .../scala/lisa/utils/prooflib/Library.scala | 106 ++ .../lisa/utils/prooflib/OutputManager.scala | 52 + .../utils/{ => prooflib}/ProofPrinter.scala | 11 +- .../lisa/utils/prooflib/ProofTacticLib.scala | 66 + .../lisa/utils/prooflib/ProofsHelpers.scala | 453 ++++++ .../utils/prooflib/SimpleDeducedSteps.scala | 350 ++++ .../lisa/utils/prooflib/WithTheorems.scala | 649 ++++++++ 16 files changed, 3288 insertions(+), 171 deletions(-) create mode 100644 lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala rename lisa-utils/src/main/scala/lisa/utils/{ => prooflib}/ProofPrinter.scala (98%) create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala 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 f0df4e3d..2a8ea906 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -478,15 +478,19 @@ object SCProofChecker { val (phi_arg, phi_body) = lambdaPhi if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) + else /*if (!s.sort.isFunctional) SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - else { + else */{ val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) val inner1 = vars.foldLeft(s)(_(_)) val inner2 = vars.foldLeft(t)(_(_)) - val sEqt = equality(inner1)(inner2) + val sEqt = + if (s.sort.isFunctional) + equality(inner1)(inner2) + else + iff(inner1)(inner2) val varss = vars.toSet if ( @@ -511,32 +515,36 @@ object SCProofChecker { else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } - /* - * Γ |- φ(s), Δ Σ |- s=t, Π - * --------------------------------- - * Γ, Σ |- φ(t), Δ, Π + /* + * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π + * -------------------------------- + * Γ, Σ φ(b) |- Δ, Π */ - case RightSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => + case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => val (phi_arg, phi_body) = lambdaPhi - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + 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 -> s)) - val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) + 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(s)(_(_)) - val inner2 = vars.foldLeft(t)(_(_)) - val sEqt = equality(inner1)(inner2) + 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(t1).right, b.right) && isSubset(ref(t2).right, b.right + sEqt) && - isSubset(b.right, ref(t1).right union ref(t2).right + phi_t_for_f) + isSubset(b.right, ref(t1).right union ref(t2).right) ) { - if (isSameSet(b.left, ref(t1).left union ref(t2).left)) { + if ( + 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) @@ -549,36 +557,33 @@ object SCProofChecker { else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } + /* - * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π - * -------------------------------- - * Γ, Σ φ(b) |- Δ, Π + * Γ |- φ(s), Δ Σ |- s=t, Π + * --------------------------------- + * Γ, Σ |- φ(t), Δ, Π */ - case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + case RightSubstEq(b, t1, t2, s, t, 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)") + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") 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 phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) + val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - val inner1 = vars.foldLeft(psi)(_(_)) - val inner2 = vars.foldLeft(tau)(_(_)) - val sEqt = iff(inner1)(inner2) + val inner1 = vars.foldLeft(s)(_(_)) + val inner2 = vars.foldLeft(t)(_(_)) + val sEqt = equality(inner1)(inner2) val varss = vars.toSet if ( - isSubset(ref(t1).right, b.right) && + 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) + isSubset(b.right, ref(t1).right union ref(t2).right + phi_t_for_f) ) { - if ( - 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 (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) @@ -591,6 +596,8 @@ object SCProofChecker { else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } + + /* * Γ |- φ[ψ/?p], Δ * --------------------- diff --git a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala index b3df33f7..bb529718 100644 --- a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala +++ b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala @@ -1,11 +1,11 @@ package lisa.utils -//import lisa.fol.FOL as F +import lisa.fol.FOL as F import lisa.kernel.fol.FOL import lisa.kernel.proof.RunningTheoryJudgement import lisa.kernel.proof.RunningTheoryJudgement.InvalidJustification import lisa.kernel.proof.SCProof -//import lisa.prooflib.Library +import lisa.prooflib.Library //import lisa.prooflib.ProofTacticLib.ProofTactic import lisa.utils.KernelHelpers.repr import lisa.utils.KernelHelpers.prettySCProof @@ -16,7 +16,7 @@ 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 @@ -61,10 +61,8 @@ 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/fol/FOL.scala b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala new file mode 100644 index 00000000..5d9e9bf7 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala @@ -0,0 +1,5 @@ +package lisa.fol + +object FOL extends Sequents { + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index ad75cd26..abf008c8 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -6,13 +6,13 @@ import K.given trait Predef extends Syntax { - def variable[S](using IsSort[SortOf[S]])(id: K.Identifier): Var[SortOf[S]] = new Var(id) - def constant[S](using IsSort[SortOf[S]])(id: K.Identifier): Cst[SortOf[S]] = new Cst(id) + 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]]): Var[SortOf[S]] = new Var(name.value) - def constant[S](using name: sourcecode.Name, is: IsSort[SortOf[S]]): Cst[SortOf[S]] = new Cst(name.value) + 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) @@ -52,16 +52,16 @@ trait Predef extends Syntax { val <=> : iff.type = iff val ⇔ : iff.type = iff - val forall = constant[(Term >>: Formula) >>: Formula]("∀") + val forall = binder[Term, Formula, Formula]("∀") val ∀ : forall.type = forall - val exists = constant[(Term >>: Formula) >>: Formula]("∃") + val exists = binder[Term, Formula, Formula]("∃") val ∃ : exists.type = exists - val epsilon = constant[(Term >>: Formula) >>: Term]("ε") + val epsilon = binder[Term, Formula, Term]("ε") val ε : epsilon.type = epsilon - val existsOne = constant[(Term >>: Formula) >>: Formula]("∃!") + val existsOne = binder[Term, Formula, Formula]("∃!") val ∃! : existsOne.type = existsOne @@ -82,11 +82,11 @@ trait Predef extends Syntax { case a: K.Application => asFrontApplication(a) case l: K.Lambda => asFrontLambda(l) - def asFrontConstant(c: K.Constant): Cst[?] = - new Cst[T](c.id)(using new Sort { type Self = T; val underlying = c.sort }) + def asFrontConstant(c: K.Constant): Constant[?] = + new Constant[T](c.id)(using new Sort { type Self = T; val underlying = c.sort }) - def asFrontVariable(v: K.Variable): Var[?] = - new Var[T](v.id)(using new Sort { type Self = T; val underlying = v.sort }) + def asFrontVariable(v: K.Variable): Variable[?] = + new Variable[T](v.id)(using new Sort { type Self = T; val underlying = v.sort }) def asFrontApplication(a: K.Application): App[?, ?] = new App[T, T](asFrontExpression(a.f).asInstanceOf, asFrontExpression(a.arg).asInstanceOf)( diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala index d9450b00..656fdb34 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala @@ -2,38 +2,37 @@ package lisa.fol //import lisa.kernel.proof.SequentCalculus.Sequent -/* + import lisa.prooflib.BasicStepTactic import lisa.prooflib.Library import lisa.prooflib.ProofTacticLib.ProofTactic -*/ + import lisa.utils.K import scala.annotation.showAsInfix trait Sequents extends Predef { - /* + object SequentInstantiationRule extends ProofTactic given ProofTactic = SequentInstantiationRule - */ 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 substituteUnsafe(m: Map[Var[?], Expr[?]]): Sequent = Sequent(left.map(_.substituteUnsafe(m)), right.map(_.substituteUnsafe(m))) - override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Sequent = + 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[Var[?]] = left.flatMap(_.freeVars) ++ right.flatMap(_.freeVars) - def freeTermVars: Set[Var[T]] = left.flatMap(_.freeTermVars) ++ right.flatMap(_.freeTermVars) - def constants: Set[Cst[?]] = left.flatMap(_.constants) ++ right.flatMap(_.constants) + 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. @@ -43,34 +42,19 @@ trait Sequents extends Predef { * @param map * @return */ - def instantiateWithProof(map: Map[Var[?], Expr[?]], 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)) - } - } - ) - (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 @ forall(x, f) => 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 @@ -93,30 +77,15 @@ trait Sequents extends 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) @@ -186,13 +155,13 @@ trait Sequents extends Predef { K.isImplyingSequent(sequent1.underlying, sequent2.underlying) } - def isSubset(s1: Set[Expr[?]], s2: Set[Expr[?]]): 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[Expr[?]], s2: Set[Expr[?]]): 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[Expr[?]], f: Expr[?]): 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 index 0539ac29..22a3114b 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -12,7 +12,6 @@ import scala.annotation.targetName trait Syntax { type IsSort[T] = Sort{type Self = T} - trait T trait F @@ -39,17 +38,19 @@ trait Syntax { given given_ArrowType[A : Sort as ta, B : Sort as tb]: (IsSort[Arrow[A, B]]) with val underlying = K.Arrow(ta.underlying, tb.underlying) - class SubstPair[T: Sort] private (val _1: Var[T], val _2: Expr[T]) + class SubstPair[T: Sort] private (val _1: Variable[T], val _2: Expr[T]) object SubstPair { - def apply[T : Sort](_1: Var[T], _2: Expr[T]) = new SubstPair(_1, _2) + def apply[T : Sort](_1: Variable[T], _2: Expr[T]) = new SubstPair(_1, _2) } - given [T: Sort]: Conversion[(Var[T], Expr[T]), SubstPair[T]] = s => SubstPair(s._1, s._2) + 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[T]] = s => SubstPair(s._1, s._2) trait LisaObject { - def substituteUnsafe(m: Map[Var[?], Expr[?]]): LisaObject - def substituteWithCheck(m: Map[Var[?], Expr[?]]): 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 @@ -59,19 +60,19 @@ trait Syntax { def substitute(pairs: SubstPair[?]*): LisaObject = substituteWithCheck(pairs.view.map(s => (s._1, s._2)).toMap) - def freeVars: Set[Var[?]] - def freeTermVars: Set[Var[T]] - def constants: Set[Cst[?]] + def freeVars: Set[Variable[?]] + def freeTermVars: Set[Variable[T]] + def constants: Set[Constant[?]] } trait Expr[S: Sort] extends LisaObject { val sort: K.Sort = summon[IsSort[S]].underlying private val arity = K.flatTypeParameters(sort).size def underlying: K.Expression - def substituteUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] - override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Expr[S] = + 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[?]*): LisaObject = + override def substitute(pairs: SubstPair[?]*): Expr[S] = super.substitute(pairs: _*).asInstanceOf[Expr[S]] def unapply[T1, T2](e: Expr[Arrow[T1, T2]]): Option[Expr[T1]] = e match { @@ -103,43 +104,43 @@ trait Syntax { - case class Var[S : Sort](id: K.Identifier) extends Expr[S] { + case class Variable[S : Sort](id: K.Identifier) extends Expr[S] { val underlying: K.Variable = K.Variable(id, sort) - def substituteUnsafe(m: Map[Var[?], Expr[?]]): Expr[S] = m.getOrElse(this, this).asInstanceOf[Expr[S]] - override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Expr[S] = + 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[Var[?]] = Set(this) - def freeTermVars: Set[Var[T]] = if sort == K.Term then Set(this.asInstanceOf) else Set.empty - def constants: Set[Cst[?]] = Set.empty - def rename(newId: K.Identifier): Var[S] = Var(newId) - def freshRename(existing: Iterable[Expr[?]]): Var[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) - Var(newId) + Variable(newId) } override def toString(): String = id.toString def :=(replacement: Expr[S]) = SubstPair(this, replacement) } - object Var { - def apply(id: String, sort: K.Sort): Var[?] = Var(id)(using new Sort { type Self = T; val underlying = sort }) + object Variable { + def unsafe(id: String, sort: K.Sort): Variable[?] = Variable(id)(using unsafeSortEvidence(sort)) } - case class Cst[S : Sort](id: K.Identifier) extends Expr[S] { + case class Constant[S : Sort](id: K.Identifier) extends Expr[S] { private var infix: Boolean = false def setInfix(): Unit = infix = true val underlying: K.Constant = K.Constant(id, sort) - def substituteUnsafe(m: Map[Var[?], Expr[?]]): Cst[S] = this - override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Expr[S] = - super.substituteWithCheck(m).asInstanceOf[Cst[S]] - override def substitute(pairs: SubstPair[?]*): Cst[S] = - super.substitute(pairs: _*).asInstanceOf[Cst[S]] - def freeVars: Set[Var[?]] = Set.empty - def freeTermVars: Set[Var[T]] = Set.empty - def constants: Set[Cst[?]] = Set(this) - def rename(newId: K.Identifier): Cst[S] = Cst(newId) + 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 @@ -157,12 +158,16 @@ trait Syntax { defaultMkStringSeparated(args) } - object Cst { - def apply(id: String, sort: K.Sort): Cst[?] = Cst(id)(using new Sort { type Self = T; val underlying = sort }) + 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 Cst[Arrow[Arrow[T1, T2], T3]](id) { - def apply(v1: Var[T1], e: Expr[T2]): App[Arrow[T1, T2], T3] = App(this, Abs(v1, e)) + 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)) + def unapply(e: Expr[?]): 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 { @@ -179,42 +184,47 @@ trait Syntax { case class App[T1 : Sort, T2 : Sort](f: Expr[Arrow[T1, T2]], arg: Expr[T1]) extends Expr[T2] { val underlying: K.Application = K.Application(f.underlying, arg.underlying) - def substituteUnsafe(m: Map[Var[?], Expr[?]]): App[T1, T2] = App[T1, T2](f.substituteUnsafe(m), arg.substituteUnsafe(m)) - override def substituteWithCheck(m: Map[Var[?], Expr[?]]): App[T1, T2] = + 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[Var[?]] = f.freeVars ++ arg.freeVars - def freeTermVars: Set[Var[T]] = f.freeTermVars ++ arg.freeTermVars - def constants: Set[Cst[?]] = f.constants ++ arg.constants + 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 apply(f: Expr[?], arg: Expr[?]): Expr[?] = + def unsafe(f: Expr[?], arg: Expr[?]): Expr[?] = val rsort = K.legalApplication(f.sort, arg.sort) rsort match case Some(to) => - App(f.asInstanceOf, arg.asInstanceOf)(using new Sort { type Self = T; val underlying = to }, new Sort { type Self = T; val underlying = to }) + App(f.asInstanceOf, arg.asInstanceOf)(using unsafeSortEvidence(to), unsafeSortEvidence(arg.sort)) case None => throw new IllegalArgumentException(s"Cannot apply $f of sort ${f.sort} to $arg of sort ${arg.sort}") } - case class Abs[T1 : Sort, T2 : Sort](v: Var[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { + case class Abs[T1 : Sort, T2 : Sort](v: Variable[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { val underlying: K.Lambda = K.Lambda(v.underlying, body.underlying) - def substituteUnsafe(m: Map[Var[?], Expr[?]]): Abs[T1, T2] = Abs(v, body.substituteUnsafe(m - v)) - override def substituteWithCheck(m: Map[Var[?], Expr[?]]): Abs[T1, T2] = + 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[Var[?]] = body.freeVars - v - def freeTermVars: Set[Var[T]] = body.freeTermVars.filterNot(_ == v) - def constants: Set[Cst[?]] = body.constants + 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[?] = + Abs(v.asInstanceOf, body.asInstanceOf)(using unsafeSortEvidence(v.sort), unsafeSortEvidence(body.sort)) + } + extension [T1, T2](f: Expr[Arrow[T1, T2]]) { def apply(using IsSort[T1], IsSort[T2])(arg: Expr[T1]): Expr[T2] = App(f, arg) @@ -222,10 +232,9 @@ trait Syntax { - - private val x = Var[T]("x") - private val y = Var[F]("x") - private val z: Expr[Arrow[T, F]] = Var("x") + private val x = Variable[T]("x") + private val y = Variable[F]("x") + private val z: Expr[Arrow[T, F]] = Variable("x") z(x) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala new file mode 100644 index 00000000..748f58d5 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicMain.scala @@ -0,0 +1,29 @@ +package lisa.prooflib + +import lisa.utils.Serialization.* + +trait BasicMain { + val library: Library + + private val realOutput: String => Unit = println + + val om: OutputManager = new OutputManager { + def finishOutput(exception: Exception): Nothing = { + log(exception) + main(Array[String]()) + sys.exit + } + val stringWriter: java.io.StringWriter = new java.io.StringWriter() + } + export om.output + + /** + * This specific implementation make sure that what is "shown" in theory files is only printed for the one we run, and not for the whole library. + */ + def main(args: Array[String]): Unit = { + realOutput(om.stringWriter.toString) + } + + given om.type = om + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala new file mode 100644 index 00000000..fc5a57ee --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -0,0 +1,1419 @@ +package lisa.prooflib +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.unification.UnificationUtils + +object BasicStepTactic { + + def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { + judgement match { + case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) + case j: proof.InvalidProofTactic => proof.InvalidProofTactic(s"Internal tactic call failed! $message\n${j.message}") + } + } + + object Hypothesis extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val intersectedPivot = botK.left.intersect(botK.right) + + if (intersectedPivot.isEmpty) + proof.InvalidProofTactic("A formula for input to Hypothesis could not be inferred from left and right side of the sequent.") + else + proof.ValidProofTactic(bot, Seq(K.Hypothesis(botK, intersectedPivot.head)), Seq()) + } + } + + object Rewrite extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + if (!K.isSameSequent(botK, proof.getSequent(premise).underlying)) + proof.InvalidProofTactic("The premise and the conclusion are not trivially equivalent.") + else + proof.ValidProofTactic(bot, Seq(K.Restate(botK, -1)), Seq(premise)) + } + } + + 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.top)) + proof.InvalidProofTactic("The desired conclusion is not a trivial tautology.") + else + proof.ValidProofTactic(bot, Seq(K.RestateTrue(botK)), Seq()) + } + } + + /** + *
+   *  Γ |- Δ, φ    φ, Σ |- Π
+   * ------------------------
+   *       Γ, Σ |- Δ, Π
+   * 
+ */ + object Cut extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + + if (!K.contains(leftSequent.right, phiK)) + proof.InvalidProofTactic("Right-hand side of first premise does not contain φ as claimed.") + else if (!K.contains(rightSequent.left, phiK)) + proof.InvalidProofTactic("Left-hand side of second premise does not contain φ as claimed.") + else if (!K.isSameSet(botK.left + phiK, leftSequent.left ++ rightSequent.left) || (leftSequent.left.contains(phiK) && !botK.left.contains(phiK))) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") + else if (!K.isSameSet(botK.right + phiK, leftSequent.right ++ rightSequent.right) || (rightSequent.right.contains(phiK) && !botK.right.contains(phiK))) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") + else + proof.ValidProofTactic(bot, Seq(K.Cut(botK, -1, -2, phiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + + lazy val cutSet = (((rightSequent --? bot).right |- ())).left + lazy val intersectedCutSet = rightSequent.left intersect leftSequent.right + + if (!cutSet.isEmpty) + if (cutSet.tail.isEmpty) + Cut.withParameters(cutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Inferred cut pivot is not a singleton set.") + else if (!intersectedCutSet.isEmpty && intersectedCutSet.tail.isEmpty) + // can still find a pivot + Cut.withParameters(intersectedCutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause.") + } + } + + // Left rules + /** + *
+   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
+   * --------------     or     --------------
+   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
+   * 
+ */ + object LeftAnd extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + 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.") + else if ( + !K.isSameSet(botK.left + phiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + psiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + phiK + psiK, premiseSequent.left + phiAndPsi) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftAnd(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.and, phi), psi) => + if (premiseSequent.left.contains(phi)) + LeftAnd.withParameters(phi, psi)(premise)(bot) + else + LeftAnd.withParameters(phi, psi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a conjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial LeftAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
+   * --------------------------------
+   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
+   * 
+ */ + object LeftOr extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(disjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + val botK = bot.underlying + val disjunctsK = disjuncts.map(_.underlying) + lazy val disjunction = K.multior(disjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != disjuncts.length) + proof.InvalidProofTactic(s"Premises and disjuncts expected to be equal in number, but ${premises.length} premises and ${disjuncts.length} disjuncts received.") + else if (!K.isSameSet(botK.right, premiseSequents.map(_.right).reduce(_ union _))) + proof.InvalidProofTactic("Right-hand side of conclusion is not the union of the right-hand sides of the premises.") + else if ( + premiseSequents.zip(disjunctsK).forall((sequent, disjunct) => K.isSubset(sequent.left, botK.left + disjunct)) // \forall i. premise_i.left \subset bot.left + phi_i + && !K.isSubset(botK.left, premiseSequents.map(_.left).reduce(_ union _) + disjunction) // bot.left \subseteq \bigcup premise_i.left + ) + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftOr(botK, Range(-1, -premises.length - 1, -1), disjunctsK)), premises.toSeq) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.left.diff(bot.left)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for LeftOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + LeftOr.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + } + } + + /** + *
+   *  Γ |- φ, Δ    Σ, ψ |- Π
+   * ------------------------
+   *    Γ, Σ, φ⇒ψ |- Δ, Π
+   * 
+ */ + object LeftImplies extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + 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.") + else if (!K.isSameSet(botK.left + psiK, leftSequent.left union rightSequent.left + implication)) + proof.InvalidProofTactic("Left-hand side of conclusion + ψ is not the union of left-hand sides of premises + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftImplies(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + lazy val pivotLeft = leftSequent.right.diff(bot.right) + lazy val pivotRight = rightSequent.left.diff(bot.left) + + if (pivotLeft.isEmpty) + if (F.isSubset(leftSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial left premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the first premises.") + else if (pivotRight.isEmpty) + if (F.isSubset(rightSequent.right, bot.right)) + unwrapTactic(Weakening(prem2)(bot))("Attempted weakening on trivial right premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the second premises.") + else if (pivotLeft.tail.isEmpty && pivotRight.tail.isEmpty) + LeftImplies.withParameters(pivotLeft.head, pivotRight.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as a pivot from the premises and conclusion, possible extraneous formulae in premises.") + } + } + + /** + *
+   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
+   * --------------    or     --------------------
+   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
+   * 
+ */ + object LeftIff extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + 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.") + else if ( + !K.isSameSet(botK.left + impLeft, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impRight, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impLeft + impRight, premiseSequent.left + implication) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ⇔ψ is not the same as left-hand side of conclusion + either φ⇒ψ, ψ⇒φ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftIff(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else + pivot.head match { + 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.") + } + } + } + + /** + *
+   *   Γ |- φ, Δ
+   * --------------
+   *   Γ, ¬φ |- Δ
+   * 
+ */ + object LeftNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + 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.") + else if (!K.isSameSet(botK.left, premiseSequent.left + negation)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftNot(botK, -1, phiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftNot failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") + + } + } + + /** + *
+   *   Γ, φ[t/x] |- Δ
+   * -------------------
+   *   Γ, ∀x. φ |- Δ
+   *
+   * 
+ */ + object LeftForall extends ProofTactic with ProofFactSequentTactic { + 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 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") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftForall(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left // .diff(botK.left) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + 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 + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case g @ F.forall(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + 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. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.left.diff(premiseSequent.left) + lazy val pivot = if (prepivot.isEmpty) bot.left else prepivot + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.forall(x, phi) => UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => + LeftForall.withParameters(phi, x, UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).get._2.getOrElse(x, 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. φ.") + } + } + + /** + *
+   *    Γ, φ |- Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ, ∃x φ|- Δ
+   *
+   * 
+ */ + object LeftExists extends ProofTactic with ProofFactSequentTactic { + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") + else if (!K.isSameSet(botK.left + phiK, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftExists(botK, -1, phiK, xK)), Seq(premise)) + } + + var debug = false + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExists failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case F.exists(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + 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.exists(x, phi) => LeftExists.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the quantified formula found.") + } + } + + /* + /** + *
+   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ, ∃!x. φ |- Δ
+   * 
+ */ + object LeftExistsOne extends ProofTactic with ProofFactSequentTactic { + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as left-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExistsOne failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi + 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 + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.BinderFormula(F.ExistsOne, x, phi) => LeftExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + } + } + + */ + + // Right rules + /** + *
+   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
+   * ------------------------------------
+   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
+   * 
+ */ + object RightAnd extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(conjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + lazy val botK = bot.underlying + lazy val conjunctsK = conjuncts.map(_.underlying) + lazy val conjunction = K.multiand(conjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != conjuncts.length) + proof.InvalidProofTactic(s"Premises and conjuncts expected to be equal in number, but ${premises.length} premises and ${conjuncts.length} conjuncts received.") + else if (!K.isSameSet(botK.left, premiseSequents.map(_.left).reduce(_ union _))) + proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + else if ( + premiseSequents.zip(conjunctsK).forall((sequent, conjunct) => K.isSubset(sequent.right, botK.right + conjunct)) // \forall i. premise_i.right \subset bot.right + phi_i + && !K.isSubset(botK.right, premiseSequents.map(_.right).reduce(_ union _) + conjunction) // bot.right \subseteq \bigcup premise_i.right + ) + proof.InvalidProofTactic("Right-hand side of conclusion + conjuncts is not the same as the union of the right-hand sides of the premises + φ∧ψ....") + else + proof.ValidProofTactic(bot, Seq(K.RightAnd(botK, Range(-1, -premises.length - 1, -1), conjunctsK)), premises) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.right.diff(bot.right)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for RightAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + RightAnd.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises +φ∧ψ.") + } + } + + /** + *
+   *   Γ |- φ, Δ               Γ |- φ, ψ, Δ
+   * --------------    or    ---------------
+   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
+   * 
+ */ + object RightOr extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + 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.") + else if ( + !K.isSameSet(botK.right + phiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + psiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + phiK + psiK, premiseSequent.right + phiAndPsi) + ) + proof.InvalidProofTactic("Right-hand side of premise + φ∧ψ is not the same as right-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.RightOr(botK, -1, phiK, psiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.or, phi), psi) => + if (premiseSequent.left.contains(phi)) + RightOr.withParameters(phi, psi)(premise)(bot) + else + RightOr.withParameters(psi, phi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a disjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ∧ψ is not the same as right-hand side of premise + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- ψ, Δ
+   * --------------
+   *  Γ |- φ⇒ψ, Δ
+   * 
+ */ + object RightImplies extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.right + psiK, premiseSequent.right + implication)) + proof.InvalidProofTactic("Right-hand side of conclusion + ψ is not the same as right-hand side of premise + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.RightImplies(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val leftPivot = premiseSequent.left.diff(bot.left) + lazy val rightPivot = premiseSequent.right.diff(bot.right) + + if ( + !leftPivot.isEmpty && leftPivot.tail.isEmpty && + !rightPivot.isEmpty && rightPivot.tail.isEmpty + ) + RightImplies.withParameters(leftPivot.head, rightPivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") + } + } + + /** + *
+   *  Γ |- φ⇒ψ, Δ    Σ |- ψ⇒φ, Π
+   * ----------------------------
+   *      Γ, Σ |- φ⇔ψ, Π, Δ
+   * 
+ */ + object RightIff extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + 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"[${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"[${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"[${f.repr}]") + .reduce(_ ++ ", " ++ _) + ) + else + proof.ValidProofTactic(bot, Seq(K.RightIff(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(prem1) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + pivot.head match { + 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 + proof.InvalidProofTactic("Right-hand side of conclusion + φ⇒ψ + ψ⇒φ is not the same as the union of the right-hand sides of the premises φ⇔ψ.") + } + } + + /** + *
+   *  Γ, φ |- Δ
+   * --------------
+   *   Γ |- ¬φ, Δ
+   * 
+ */ + object RightNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.right, premiseSequent.right + negation)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightNot(botK, -1, phiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + RightNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + + } + } + + /** + *
+   *    Γ |- φ, Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ |- ∀x. φ, Δ
+   * 
+ */ + object RightForall extends ProofTactic with ProofFactSequentTactic { + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else 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 + phiK, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∀x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightForall(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from the premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case F.forall(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + 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.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. φ.") + } + } + + /** + *
+   *   Γ |- φ[t/x], Δ
+   * -------------------
+   *  Γ |- ∃x. φ, Δ
+   *
+   * (ln-x stands for locally nameless x)
+   * 
+ */ + object RightExists extends ProofTactic with ProofFactSequentTactic { + 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 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") + else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.RightExists(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + 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 + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightExists failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case g @ F.exists(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + 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. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.right.diff(premiseSequent.right) + lazy val pivot = if (prepivot.isEmpty) bot.right else prepivot + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.exists(x, phi) => + UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => + RightExists.withParameters(phi, x, UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).get._2.getOrElse(x, 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) ⇔ φ, Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ|- ∃!x. φ,  Δ
+   * 
+ */ + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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)) + ) + ) + lazy val quantified = K.BinderFormula(K.ExistsOne, xK, phiK) + + 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 + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as right-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightExistsOne failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, 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.") + } + } 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.ExistsOne, x, phi) => RightExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially 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. φ.") + } + } + + */ + + // Structural rules + /** + *
+   *     Γ |- Δ
+   * --------------
+   *   Γ, Σ |- Δ, Π
+   * 
+ */ + object Weakening extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + + if (!F.isImplyingSequent(premiseSequent, bot)) + proof.InvalidProofTactic("Conclusion cannot be trivially derived from premise.") + else + proof.ValidProofTactic(bot, Seq(K.Weakening(bot.underlying, -1)), Seq(premise)) + } + } + + // Equality Rules + /** + *
+   *  Γ, s=s |- Δ
+   * --------------
+   *     Γ |- Δ
+   * 
+ */ + object LeftRefl extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val faK = fa.underlying + lazy val botK = bot.underlying + + if (!K.isSameSet(botK.left + faK, premiseSequent.left) || !premiseSequent.left.exists(_ == faK) || botK.left.exists(_ == faK)) + proof.InvalidProofTactic("Left-hand sides of the conclusion + φ is not the same as left-hand side of the premise.") + else 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 + faK match { + 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.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftRefl.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an equality as pivot from premise and conclusion.") + } + } + + /** + *
+   *
+   * --------------
+   *     |- s=s
+   * 
+ */ + object RightRefl extends ProofTactic with ProofSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val faK = fa.underlying + lazy val botK = bot.underlying + if (!botK.right.exists(_ == faK)) + proof.InvalidProofTactic("Right-hand side of conclusion does not contain φ.") + else + faK match { + 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.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (bot.right.isEmpty) proof.InvalidProofTactic("Right-hand side of conclusion does not contain an instance of reflexivity.") + else { + // go through conclusion to see if you can find an reflexive formula + 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.App(F.App(e, l), r) => + (F.equality) == (e) && l == r // termequality + case _ => false + } + ) + + pivot match { + case Some(phi) => RightRefl.withParameters(phi)(bot) + case _ => proof.InvalidProofTactic("Could not infer an equality as pivot from conclusion.") + } + + } + + } + } + + /** + *
+   *   Γ, φ(s) |- Δ     Σ |- s=t, Π     
+   * --------------------------------
+   *        Γ, Σ φ(t) |- Δ, Π
+   * 
+ */ + object LeftSubstEq extends ProofTactic { + + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + + + } + } + + /** + *
+   *  Γ |- φ(s), Δ     Σ |- s=t, Π
+   * ---------------------------------
+   *         Γ, Σ |- φ(t), Δ, Π
+   * 
+ */ + object RightSubstEq extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + } + + } + + /** + *
+   *           Γ, φ(a1,...an) |- Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   * 
+ */ + object LeftSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + /*{ + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.Iff(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + */ + + } + + /** + *
+   *           Γ |- φ(a1,...an), Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   * 
+ */ + object RightSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + /* + 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(_.underlying), 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.forall(s_arg, acc) } + } + + 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)) + } + */ + } + + /** + *
+   *           Γ |- Δ
+   * --------------------------
+   *  Γ[r(a)/?f] |- Δ[r(a)/?f]
+   * 
+ */ + object InstSchema extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof + )(map: Map[F.Variable[?], F.Expr[?]])(premise: proof.Fact): 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)) + } + } + object Subproof extends ProofTactic { + def apply(using proof: Library#Proof)(statement: Option[F.Sequent])(iProof: proof.InnerProof) = { + val bot: Option[F.Sequent] = statement + val botK: Option[K.Sequent] = statement map (_.underlying) + if (iProof.length == 0) throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + val judgement: proof.ProofTacticJudgement = { + 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: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}" + ) + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + 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) + if (iProof.length == 0) + throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + def judgement: proof.ProofTacticJudgement = { + 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: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}") + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + } + + // TODO make specific support for subproofs written inside tactics.kkkkkkk + + inline def TacticSubproof(using proof: Library#Proof)(inline computeProof: proof.InnerProof ?=> Unit): proof.ProofTacticJudgement = + val iProof: proof.InnerProof = new proof.InnerProof(None) + computeProof(using iProof) + SUBPROOF(using proof)(None)(iProof).judgement.asInstanceOf[proof.ProofTacticJudgement] + + object Sorry extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + proof.ValidProofTactic(bot, Seq(K.Sorry(bot.underlying)), Seq()) + } + } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala new file mode 100644 index 00000000..836d1cac --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/Exports.scala @@ -0,0 +1,6 @@ +package lisa.prooflib + +object Exports { + export BasicStepTactic.* + export lisa.prooflib.SimpleDeducedSteps.* +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala new file mode 100644 index 00000000..049b82bf --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala @@ -0,0 +1,106 @@ +package lisa.prooflib + +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.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import scala.collection.mutable.Stack as stack + +/** + * A class abstracting a [[lisa.kernel.proof.RunningTheory]] providing utility functions and a convenient syntax + * to write and use Theorems and Definitions. + * @param theory The inner RunningTheory + */ +abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.ProofsHelpers { + + val theory: RunningTheory + given library: this.type = this + given RunningTheory = theory + + export lisa.kernel.proof.SCProof + + val K = lisa.utils.K + val SC: SequentCalculus.type = K.SC + private[prooflib] val F = lisa.fol.FOL + import F.{given} + + var last: Option[JUSTIFICATION] = None + + // Options for files + private[prooflib] var _withCache: Boolean = false + def withCache(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: withCache option should be used before the first definition or theorem.")) + else _withCache = true + + private[prooflib] var _draft: Option[sourcecode.File] = None + def draft(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: draft option should be used before the first definition or theorem.")) + else _draft = Some(file) + def isDraft = _draft.nonEmpty + + 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.Constant[?]): Unit = + theory.addSymbol(s.underlying) + knownDefs.update(s, None) + + 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.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case Some(value) => value + } + + /** + * An alias to create a Theorem + */ + def makeTheorem(name: String, statement: K.Sequent, proof: K.SCProof, justifications: Seq[theory.Justification]): K.Judgement[theory.Theorem] = + theory.theorem(name, statement, proof, justifications) + + // 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 makeSimpleDefinition(symbol: String, expression: K.Expression): K.Judgement[theory.Definition] = + theory.definition(symbol, expression) + + + /** + * Prints a short representation of the given theorem or definition + */ + def show(using om: OutputManager)(thm: JUSTIFICATION) = { + if (thm.withSorry) om.output(thm.repr, Console.YELLOW) + else om.output(thm.repr, Console.GREEN) + } + + /** + * Prints a short representation of the last theorem or definition introduced + */ + def show(using om: OutputManager): Unit = last match { + case Some(value) => show(value) + case None => throw new NoSuchElementException("There is nothing to show: No theorem or definition has been proved yet.") + } + +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala new file mode 100644 index 00000000..64ab76b7 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala @@ -0,0 +1,52 @@ +package lisa.prooflib + +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import java.io.PrintWriter +import java.io.StringWriter + +abstract class OutputManager { + + given OutputManager = this + + def output(s: String): Unit = stringWriter.write(s + "\n") + def output(s: String, color: String): Unit = stringWriter.write(Console.RESET + color + s + "\n" + Console.RESET) + val stringWriter: StringWriter + + def finishOutput(exception: Exception): Nothing + + def lisaThrow(le: LisaException): Nothing = + le match { + case ule: UserLisaException => + ule.fixTrace() + output(ule.showError) + finishOutput(ule) + + case e: LisaException.InvalidKernelJustificationComputation => + e.proof match { + case Some(value) => output(lisa.utils.prooflib.ProofPrinter.prettyProof(value)) + case None => () + } + output(e.underlying.repr) + finishOutput(e) + + } + + def log(e: Exception): Unit = { + stringWriter.write("\n[" + Console.RED + "Error" + Console.RESET + "] ") + e.printStackTrace(PrintWriter(stringWriter)) + output(Console.RESET) + } + +} +object OutputManager { + def RED(s: String): String = Console.RED + s + Console.RESET + def GREEN(s: String): String = Console.GREEN + s + Console.RESET + def BLUE(s: String): String = Console.BLUE + s + Console.RESET + def YELLOW(s: String): String = Console.YELLOW + s + Console.RESET + def CYAN(s: String): String = Console.CYAN + s + Console.RESET + def MAGENTA(s: String): String = Console.MAGENTA + s + Console.RESET + + def WARNING(s: String): String = Console.YELLOW + "⚠ " + s + Console.RESET +} diff --git a/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala similarity index 98% rename from lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala rename to lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala index 0db2d8ae..96c95137 100644 --- a/lisa-utils/src/main/scala/lisa/utils/ProofPrinter.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala @@ -1,13 +1,12 @@ -package lisa.utils +package lisa.prooflib import lisa.kernel.proof.SCProofCheckerJudgement -//import lisa.prooflib.BasicStepTactic.SUBPROOF -//import lisa.prooflib.Library -//import lisa.prooflib.* +import lisa.prooflib.BasicStepTactic.SUBPROOF +import lisa.prooflib.Library +import lisa.prooflib.* import lisa.utils.* object ProofPrinter { -/* private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" @@ -126,5 +125,5 @@ object ProofPrinter { 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/utils/prooflib/ProofTacticLib.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala new file mode 100644 index 00000000..b9dc82cd --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala @@ -0,0 +1,66 @@ +package lisa.prooflib + +import lisa.fol.FOL as F +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.UserLisaException +import lisa.utils.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. + */ + trait ProofTactic { + val name: String = this.getClass.getName.split('$').last + given ProofTactic = this + + } + + trait OnlyProofTactic { + def apply(using lib: Library, proof: lib.Proof): proof.ProofTacticJudgement + } + + trait ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement + } + + trait ProofFactTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact): proof.ProofTacticJudgement + } + trait ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement + } + + class UnapplicableProofTactic(val tactic: ProofTactic, proof: Library#Proof, errorMessage: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { + override def fixTrace(): Unit = { + val start = getStackTrace.indexWhere(elem => { + !elem.getClassName.contains(tactic.name) + }) + 1 + setStackTrace(getStackTrace.take(start)) + } + + def 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() + Console.RED + proof.owningTheorem.prettyGoal + Console.RESET + "\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 + } + } + + class UnimplementedProof(val theorem: Library#THM)(using sourcecode.Line, sourcecode.File) extends UserLisaException("Unimplemented Theorem") { + def showError: String = s"Theorem ${theorem.name}" + } + case class UnexpectedProofTacticFailureException(failure: Library#Proof#InvalidProofTactic, errorMessage: String)(using sourcecode.Line, sourcecode.File) + 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:" + + 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 00000000..620adb94 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -0,0 +1,453 @@ +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.{_, 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 + } + } + + type ParamsOf[S] = S match { + case Arrow[s1, s2] => s1 *: ParamsOf[s2] + case _ => S *: EmptyTuple + } + + type FromParamList[S, R] = S match { + case EmptyTuple => R + case s *: ss => s >>: FromParamList[ss, R] + } + + class The(val out: Variable[T], val f: Formula)( + val just: JUSTIFICATION + ) + class definitionWithVars[S <: Tuple](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): Constant[?] = + Direct[N](name.value, line.value, file.value)(args, t.out, t.f, t.just).label + + inline infix def -->[R: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(expr: Expr[R]): Constant[FromParamList[S, R]] = + val res: Expr[FromParamList[S, R]] = args.toList.foldRight(expr: Expr[?])((v, acc) => Abs.unsafe(v: Variable[?], acc)).asInstanceOf[Expr[FromParamList[S, R]]] + DirectDefinition[FromParamList[S, R]](name.value, line.value, file.value)(res)(using F.unsafeSortEvidence(res.sort)).label + + } + //Tuple.Map[S, Variable] + + def DEF(): definitionWithVars[EmptyTuple] = new definitionWithVars[EmptyTuple](Seq()) + def DEF[S1](a: Variable[S1]): definitionWithVars[(S1 *: EmptyTuple)] = + new definitionWithVars(Seq(a)) + def DEF[S1, S2](a: Variable[S1], b: Variable[S2]): definitionWithVars[S1*:S2*:EmptyTuple] = + new definitionWithVars(Seq(a, b)) + def DEF[S1, S2, S3](a: Variable[S1], b: Variable[S2], c: Variable[S3]): definitionWithVars[S1*:S2*:S3*:EmptyTuple] = + new definitionWithVars(Seq(a, b, c)) + def DEF[S1, S2, S3, S4](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4]): definitionWithVars[S1*:S2*:S3*:S4*:EmptyTuple] = + new definitionWithVars(Seq(a, b, c, d)) + def DEF[S1, S2, S3, S4, S5](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4], e: Variable[S5]): definitionWithVars[S1*:S2*:S3*:S4*:S5*:EmptyTuple] = + new definitionWithVars(Seq(a, b, c, d, e)) + def DEF[S1, S2, S3, S4, S5, S6](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4], e: Variable[S5], f: Variable[S6]): definitionWithVars[S1*:S2*:S3*:S4*:S5*:S6*:EmptyTuple] = + new definitionWithVars(Seq(a, b, c, d, e, f)) + def DEF[S1, S2, S3, S4, S5, S6, S7](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4], e: Variable[S5], f: Variable[S6], g: Variable[S7]): definitionWithVars[S1*:S2*:S3*:S4*:S5*:S6*:S7*:EmptyTuple] = + new definitionWithVars(Seq(a, b, c, d, e, f, g)) + + + class DirectDefinition[S : Sort](using om: OutputManager)(val fullName: String, line: Int, file: String)(val expr: Expr[S]) extends DEFINITION(line, file) { + + lazy val vars: Seq[F.Variable[?]] = ??? + val arity = ??? + + lazy val label: Constant[S] = F.Constant(name) + + + val innerJustification: theory.Definition = { + import lisa.utils.K.{findUndefinedSymbols} + val uexpr = expr.underlying + val ucst = K.Constant(name, label.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 statement: F.Sequent = () |- Iff(label.applySeq(vars), lambda.body) + library.last = Some(this) + } + + + + + /** + * Allows to make definitions "by unique existance" of a function symbol + */ + class EpsilonDefinition[S, R](using om: OutputManager)(val fullName: String, line: Int, file: String)( + val vars: Seq[S.Variable[?]], // Tuple.Map[S, Variable], + val out: F.Variable[R], + 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 uvars = vars.map(_.underlyingLabel) + val ucst = K.ConstantFunctionLabel(name, vars.size) + val judgement = theory.makeFunctionDefinition(pr, Seq(j.innerJustification), ucst, out.underlyingLabel, K.LambdaTermFormula(uvars, 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(ucst)) { + om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) + } + if (!(underf.freeSchematicTermLabels.subsetOf(uvars.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 -- uvars.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) + } + } + + + + ///////////////////////// + // 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/prooflib/SimpleDeducedSteps.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala new file mode 100644 index 00000000..d9a2cd98 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala @@ -0,0 +1,350 @@ +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 = + unwrapTactic(RewriteTrue(bot))("Attempted true rewrite during tactic Restate failed.") + + // (proof.ProofStep | proof.OutsideFact | Int) is definitionally equal to proof.Fact, but for some reason + // scala compiler doesn't resolve the overload with a type alias, dependant type and implicit parameter + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + def from(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + } + + object Discharge extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(premise: proof.Fact): proof.ProofTacticJudgement = { + val ss = premises zip (premises map (e => proof.getSequent(e))) + val seqs = ss.map(_._2) + if (!seqs.forall(_.right.size == 1)) + return proof.InvalidProofTactic("When discharging this way, the discharged sequent must have only a single formula on the right handside.") + val seqAny = ss.find((_, s) => premise.statement.left.exists(f2 => F.isSame(s.right.head, f2))) + if (seqAny.isEmpty) + Restate.from(premise)(premise.statement) + else + TacticSubproof: ip ?=> + ss.foldLeft(premise: ip.Fact)((prem, discharge) => + val seq = discharge._2 + if prem.statement.left.exists(f => F.isSame(f, seq.right.head)) then + val goal = prem.statement - + * Γ ⊢ ∀x.ψ, Δ + * ------------------------- + * Γ |- ψ[t/x], Δ + * + * + * + * Returns a subproof containing the instantiation steps + */ + object InstantiateForall extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: F.Formula, t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val phiK = phi.underlying + val tK = t map (_.underlying) + val premiseSequent = proof.getSequent(premise) + val premiseSequentK = premiseSequent.underlying + if (!premiseSequent.right.contains(phi)) { + proof.InvalidProofTactic("Input formula was not found in the RHS of the premise sequent.") + } else { + val emptyProof = K.SCProof(IndexedSeq(), IndexedSeq(premiseSequentK)) + val j = proof.ValidProofTactic(bot, Seq(K.Restate(premiseSequentK, -1)), Seq(premise)) + val res = tK.foldLeft((emptyProof, phiK, j: proof.ProofTacticJudgement)) { case ((p, f, j), t) => + j match { + case proof.InvalidProofTactic(_) => (p, f, j) // propagate error + case proof.ValidProofTactic(_, _, _) => + // good state, continue instantiating + // 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)) + // instantiate the formula with input + val in = instantiateBinder(psi, 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 p2 = K.Cut(con, -1, 1, f) + + /** + * in = ψ[t/x] + * + * s1 = Γ ⊢ ∀x.ψ, Δ Premise + * con = Γ ⊢ ψ[t/x], Δ Result + * + * p0 = ψ[t/x] ⊢ ψ[t/x] Hypothesis + * p1 = ∀x.ψ ⊢ ψ[t/x] LeftForall p0 + * p2 = Γ ⊢ ψ[t/x], Δ Cut s1, p1 + */ + val newStep = K.SCSubproof(K.SCProof(IndexedSeq(p0, p1, p2), IndexedSeq(p.conclusion)), Seq(p.length - 1)) + ( + p withNewSteps IndexedSeq(newStep), + in, + j + ) + case _ => + (p, f, proof.InvalidProofTactic("Input formula is not universally quantified")) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _, _) => { + 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)}") + } + } + } + } + + def apply(using lib: Library, proof: lib.Proof)(t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val prem = proof.getSequent(premise) + if (prem.right.tail.isEmpty) { + // well formed + apply(using lib, proof)(prem.right.head, t*)(premise)(bot): proof.ProofTacticJudgement + } else proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + try { + val sp = TacticSubproof { + // lazy val premiseSequent = proof.getSequent(premise) + val s1 = lib.have(bot +<< bot.right.head) by Restate + lib.have(bot) by LeftForall(s1) + } + BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") + } catch { + case e: Exception => proof.InvalidProofTactic("Impossible to justify desired step with instantiation.") + } + + } + + } + /* + /** + * Performs a cut when the formula to be used as pivot for the cut is + * inside a conjunction, preserving the conjunction structure + * + *
+   *
+   * PartialCut(ϕ, ϕ ∧ ψ)(left, right) :
+   *
+   *     left: Γ ⊢ ϕ ∧ ψ, Δ      right: ϕ, Σ ⊢ γ1 , γ2, …, γn
+   * -----------------------------------------------------------
+   *            Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn
+   *
+   * 
+ */ + object PartialCut extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, conjunction: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val leftSequent = proof.getSequent(prem1) + val rightSequent = proof.getSequent(prem2) + + if (leftSequent.right.contains(conjunction)) { + + if (rightSequent.left.contains(phi)) { + // check conjunction matches with phi + conjunction match { + case K.ConnectorFormula(K.And, s: Seq[K.Formula]) => { + if (s.contains(phi)) { + // construct proof + + val psi: Seq[K.Formula] = s.filterNot(_ == phi) + val newConclusions: Set[K.Formula] = rightSequent.right.map((f: K.Formula) => K.ConnectorFormula(K.And, f +: psi)) + + val Sigma: Set[K.Formula] = rightSequent.left - phi + + val p0 = K.Weakening(rightSequent ++<< (psi |- ()), -2) + val p1 = K.RestateTrue(psi |- psi) + + // TODO: can be abstracted into a RightAndAll step + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(p0.bot, p1.bot)) + val proofRightAndAll = rightSequent.right.foldLeft(emptyProof) { case (p, gamma) => + p withNewSteps IndexedSeq(K.RightAnd(p.conclusion ->> gamma +>> K.ConnectorFormula(K.And, gamma +: psi), Seq(p.length - 1, -2), gamma +: psi)) + } + + val p2 = K.SCSubproof(proofRightAndAll, Seq(0, 1)) + val p3 = K.Restate(Sigma + conjunction |- newConclusions, 2) // sanity check and correct form + val p4 = K.Cut(bot, -1, 3, conjunction) + + /** + * newConclusions = ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn + * + * left = Γ ⊢ ϕ ∧ ψ, Δ Premise + * right = ϕ, Σ ⊢ γ1 , γ2, …, γn Premise + * + * p0 = ϕ, Σ, ψ ⊢ γ1 , γ2, …, γn Weakening on right + * p1 = ψ ⊢ ψ Hypothesis + * p2 = Subproof: + * 2.1 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , γ2, …, γn RightAnd on p0 and p1 with ψ ∧ γ1 + * 2.2 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , ψ ∧ γ2, …, γn RightAnd on 2.1 and p1 ψ ∧ γ2 + * ... + * 2.n = ϕ, Σ, ψ ⊢ ψ ∧ γ1, ψ ∧ γ2, …, ψ ∧ γn RightAnd on 2.(n-1) and p1 with ψ ∧ γn + * + * p3 = ϕ ∧ ψ, Σ ⊢ ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Rewrite on p2 (just to have a cleaner form) + * p2 = Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Cut on left, p1 with ϕ ∧ ψ + * + * p2 is the result + */ + + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3, p4), Seq(prem1, prem2)) + } else { + proof.InvalidProofTactic("Input conjunction does not contain the pivot.") + } + } + case _ => proof.InvalidProofTactic("Input not a conjunction.") + } + } else { + proof.InvalidProofTactic("Input pivot formula not found in right premise.") + } + } else { + proof.InvalidProofTactic("Input conjunction not found in first premise.") + } + } + } + + object destructRightAnd extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.LeftAnd(emptySeq +<< (a /\ b) +>> a, 0, a, b) + val p2 = K.Cut(conc ->> (a /\ b) ->> (b /\ a) +>> a, -1, 1, a /\ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2), Seq(prem)) + } + } + object destructRightOr extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val mat = conc.right.find(f => K.isSame(f, a \/ b)) + if (mat.nonEmpty) { + + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.Hypothesis(emptySeq +<< b +>> b, b) + + val p2 = K.LeftOr(emptySeq +<< (a \/ b) +>> a +>> b, Seq(0, 1), Seq(a, b)) + val p3 = K.Cut(conc ->> mat.get +>> a +>> b, -1, 2, a \/ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3), Seq(prem)) + } else { + proof.InvalidProofTactic("Premise does not contain the union of the given formulas") + } + + } + } + + object GeneralizeToForall extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val sequent = proof.getSequent(prem) + if (sequent.right.contains(phi)) { + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(sequent)) + val j = proof.ValidProofTactic(IndexedSeq(K.Restate(sequent, proof.length - 1)), Seq[proof.Fact]()) + + val res = t.foldRight(emptyProof: SCProof, phi: K.Formula, j: proof.ProofTacticJudgement) { case (x1, (p1: SCProof, phi1, j1)) => + j1 match { + case proof.InvalidProofTactic(_) => (p1, phi1, j1) + case proof.ValidProofTactic(_, _) => { + if (!p1.conclusion.right.contains(phi1)) + (p1, phi1, proof.InvalidProofTactic("Formula is not present in the lass sequent")) + + val proofStep = K.RightForall(p1.conclusion ->> phi1 +>> forall(x1, phi1), p1.length - 1, phi1, x1) + ( + p1 appended proofStep, + forall(x1, phi1), + j1 + ) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _) => proof.ValidProofTactic((res._1.steps appended K.Restate(bot, res._1.length - 1)), Seq(prem)) + } + + } else proof.InvalidProofTactic("RHS of premise sequent contains not phi") + + } + } + + object GeneralizeToForallNoForm extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (proof.getSequent(prem).right.tail.isEmpty) + GeneralizeToForall.apply(using lib, proof)(proof.getSequent(prem).right.head, t*)(prem)(bot): proof.ProofTacticJudgement + else + proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + } + + object ByCase extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val nphi = !phi + + val pa = proof.getSequent(prem1) + val pb = proof.getSequent(prem2) + val (leftAphi, leftBnphi) = (pa.left.find(K.isSame(_, phi)), pb.left.find(K.isSame(_, nphi))) + if (leftAphi.nonEmpty && leftBnphi.nonEmpty) { + val p2 = K.RightNot(pa -<< leftAphi.get +>> nphi, -1, phi) + val p3 = K.Cut(pa -<< leftAphi.get ++ (pb -<< leftBnphi.get), 0, -2, nphi) + val p4 = K.Restate(bot, 1) + proof.ValidProofTactic(IndexedSeq(p2, p3, p4), IndexedSeq(prem1, prem2)) // TODO: Check pa/pb orDer + + } else { + proof.InvalidProofTactic("Premises have not the right syntax") + } + } + } + */ +} diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala new file mode 100644 index 00000000..8173caa1 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala @@ -0,0 +1,649 @@ +package lisa.prooflib + +import lisa.kernel.proof.RunningTheory +import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.prooflib.ProofTacticLib.UnimplementedProof +import lisa.prooflib.* +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.LisaException +import lisa.utils.UserLisaException +import lisa.utils.UserLisaException.* + +import scala.annotation.nowarn +import scala.collection.mutable.Buffer as mBuf +import scala.collection.mutable.Map as mMap +import scala.collection.mutable.Stack as stack + +trait WithTheorems { + library: Library => + + /** + * The main builder for proofs. It is a mutable object that can be used to build a proof step by step. + * It is used either to construct a theorem/lemma ([[BaseProof]]) or to construct a subproof ([[InnerProof]]). + * We can add proof tactics to it producing intermediate results. In the end, obtain a [[K.SCProof]] from it. + * + * @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 + type Fact = ProofStep | InstantiatedFact | OutsideFact | Int + + /** + * A proven fact (from a previously proven step, a theorem or a definition) with specific instantiations of free variables. + * + * @param fact The base fact + * @param insts The instantiation of free variables + */ + case class InstantiatedFact( + fact: Fact, + insts: Seq[F.SubstPair[?] | F.Term] + ) { + 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 (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) + val (s2, p2) = if terms.isEmpty then (s1, p1) else s1.instantiateForallWithProof(terms, p1.length - 1) + (s2, p1 ++ p2) + } + + } + + val library: WithTheorems.this.type = WithTheorems.this + + private var steps: List[ProofStep] = Nil + private var imports: List[(OutsideFact, F.Sequent)] = Nil + private var instantiatedFacts: List[(InstantiatedFact, Int)] = Nil + private var assumptions: List[F.Formula] = assump + private var eliminations: List[(F.Formula, (Int, F.Sequent) => List[K.SCProofStep])] = Nil + + def cleanAssumptions: Unit = assumptions = Nil + + /** + * the theorem that is being proved (paritally, if subproof) by this proof. + * + * @return The theorem + */ + def owningTheorem: THM + + /** + * A proof step, containing a high level ProofTactic and the corresponding K.SCProofStep. If the tactic produce more than one + * step, they must be encapsulated in a subproof. Usually constructed with [[ValidProofTactic.validate]] + * + * @param judgement The result of the tactic + * @param scps The corresponding [[K.SCProofStep]] + * @param position The position of the step in the proof + */ + case class ProofStep private (judgement: ValidProofTactic, scps: K.SCProofStep, position: Int) { + val bot: F.Sequent = judgement.bot + def innerBot: K.Sequent = scps.bot + val host: Proof.this.type = Proof.this + + def tactic: ProofTactic = judgement.tactic + + } + private object ProofStep { // TODO + def newProofStep(judgement: ValidProofTactic): ProofStep = { + val ps = ProofStep( + judgement, + SC.SCSubproof( + K.SCProof(judgement.scps.toIndexedSeq, judgement.imports.map(f => sequentOfFact(f).underlying).toIndexedSeq), + judgement.imports.map(sequentAndIntOfFact(_)._2) + ), + steps.length + ) + addStep(ps) + ps + + } + } + + /** + * A proof step can be constructed from a succesfully executed tactic + */ + def newProofStep(judgement: ValidProofTactic): ProofStep = + ProofStep.newProofStep(judgement) + + private def addStep(ds: ProofStep): Unit = steps = ds :: steps + private def addImport(imp: OutsideFact, seq: F.Sequent): Unit = { + imports = (imp, seq) :: imports + } + + private def addInstantiatedFact(instFact: InstantiatedFact): Unit = { + val step = ValidProofTactic(instFact.result, instFact.proof, Seq(instFact.fact))(using F.SequentInstantiationRule) + newProofStep(step) + instantiatedFacts = (instFact, steps.length - 1) :: instantiatedFacts + } + + /** + * Add an assumption the the proof, i.e. a formula that is automatically on the left side of the sequent. + * + * @param f + */ + def addAssumption(f: F.Formula): Unit = { + if (!assumptions.contains(f)) assumptions = f :: assumptions + } + + def addElimination(f: F.Formula, elim: (Int, F.Sequent) => List[K.SCProofStep]): Unit = { + eliminations = (f, elim) :: eliminations + } + + def addDischarge(ji: Fact): Unit = { + val (s1, t1) = sequentAndIntOfFact(ji) + val f = s1.right.head + val fu = f.underlying + addElimination( + f, + (i, sequent) => + List( + SC.Cut((sequent.underlying -<< fu) ++ (s1.underlying ->> fu), t1, i, fu) + ) + ) + } + /* + 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 + + /** + * Favour using getSequent when applicable. + * @return The list of ValidatedSteps (containing a high level ProofTactic and the corresponding K.SCProofStep). + */ + def getSteps: List[ProofStep] = steps.reverse + + /** + * Favour using getSequent when applicable. + * @return The list of Imports validated in the formula, with their original justification. + */ + def getImports: List[(OutsideFact, F.Sequent)] = imports.reverse + + /** + * @return The list of formulas that are assumed for the reminder of the proof. + */ + def getAssumptions: List[F.Formula] = assumptions + + /** + * Produce the low level [[K.SCProof]] corresponding to the proof. Automatically eliminates any formula in the discharges that is still left of the sequent. + * + * @return + */ + def toSCProof: K.SCProof = { + import lisa.utils.KernelHelpers.{-<<, ->>} + val finalSteps = eliminations.foldLeft[(List[SC.SCProofStep], F.Sequent)]((steps.map(_.scps), steps.head.bot)) { (cumul_bot, f_elim) => + val (cumul, bot) = cumul_bot + val (f, elim) = f_elim + val i = cumul.size + val elimSteps = elim(i - 1, bot) + (elimSteps.foldLeft(cumul)((cumul2, step) => step :: cumul2), bot -<< f) + } + + val r = K.SCProof(finalSteps._1.reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + r + } + + def currentSCProof: K.SCProof = K.SCProof(steps.map(_.scps).reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + + /** + * For a fact, returns the sequent that the fact proove and the position of the fact in the proof. + * + * @param fact Any fact, possibly instantiated, belonging to the proof + * @return its proven sequent and position + */ + def sequentAndIntOfFact(fact: Fact): (F.Sequent, Int) = fact match { + case i: Int => + ( + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + }, + i + ) + case ds: ProofStep => (ds.bot, ds.position) + case instFact: InstantiatedFact => + val r = instantiatedFacts.find(instFact == _._1) + r match { + case Some(value) => (instFact.result, value._2) + case None => + addInstantiatedFact(instFact) + (instFact.result, steps.length - 1) + } + case of: OutsideFact @unchecked => + val r = imports.indexWhere(of == _._1) + if (r != -1) { + (imports(r)._2, r - imports.length) + } else { + val r2 = sequentOfOutsideFact(of) + addImport(of, r2) + (r2, -imports.length) + } + } + + def sequentOfFact(fact: Fact): F.Sequent = fact match { + case i: Int => + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + } + case ds: ProofStep => ds.bot + case instfact: InstantiatedFact => instfact.result + case of: OutsideFact @unchecked => + val r = imports.find(of == _._1) + if (r.nonEmpty) { + r.get._2 + } else { + sequentOfOutsideFact(of) + } + } + + def sequentOfOutsideFact(of: OutsideFact): F.Sequent + + def getSequent(f: Fact): F.Sequent = sequentOfFact(f) + def mostRecentStep: ProofStep = steps.head + + /** + * The number of steps in the proof. This is not the same as the number of steps in the corresponding [[K.SCProof]]. + * This also does not count the number of steps in the subproof. + * + * @return + */ + def length: Int = steps.length + + /** + * The set of symbols that can't be instantiated because they are free in an assumption. + */ + 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. + */ + def asOutsideFact(j: JUSTIFICATION): OutsideFact + + def depth: Int = + (this: @unchecked) match { + case p: Proof#InnerProof => 1 + p.parent.depth + case _: BaseProof => 0 + } + + /** + * Create a subproof inside the current proof. The subproof will have the same assumptions as the current proof. + * Can have a goal known in advance (usually for a user-written subproof) or not (usually for a tactic-generated subproof). + */ + def newInnerProof(possibleGoal: Option[F.Sequent]) = new InnerProof(possibleGoal) + final class InnerProof(val possibleGoal: Option[F.Sequent]) extends Proof(this.getAssumptions) { + val parent: Proof.this.type = Proof.this + val owningTheorem: THM = parent.owningTheorem + type OutsideFact = parent.Fact + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = parent.asOutsideFact(j) + + override def sequentOfOutsideFact(of: parent.Fact): F.Sequent = of match { + case j: JUSTIFICATION => j.statement + case ds: Proof#ProofStep => ds.bot + case _ => parent.sequentOfFact(of) + } + } + + /** + * Contains the result of a tactic computing a K.SCProofTactic. + * Can be successful or unsuccessful. + */ + sealed abstract class ProofTacticJudgement { + val tactic: ProofTactic + val proof: Proof = Proof.this + + /** + * Returns true if and only if the judgement is valid. + */ + def isValid: Boolean = this match { + case ValidProofTactic(_, _, _) => true + case InvalidProofTactic(_) => false + } + + def validate(line: sourcecode.Line, file: sourcecode.File): ProofStep = { + this match { + case vpt: ValidProofTactic => newProofStep(vpt) + case ipt: InvalidProofTactic => + val e = lisa.prooflib.ProofTacticLib.UnapplicableProofTactic(ipt.tactic, ipt.proof, ipt.message)(using line, file) + e.setStackTrace(ipt.stack) + throw e + } + } + } + + /** + * A Kernel Sequent Calculus proof step that has been correctly produced. + */ + case class ValidProofTactic(bot: lisa.fol.FOL.Sequent, scps: Seq[K.SCProofStep], imports: Seq[Fact])(using val tactic: ProofTactic) extends ProofTacticJudgement {} + + /** + * A proof step which led to an error when computing the corresponding K.Sequent Calculus proof step. + */ + case class InvalidProofTactic(message: String)(using val tactic: ProofTactic) extends ProofTacticJudgement { + 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 + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = j + + override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement + + def justifications: List[JUSTIFICATION] = getImports.map(_._1) + } + + /** + * Abstract class representing theorems, axioms and different kinds of definitions. Corresponds to a [[theory.Justification]]. + */ + sealed abstract class JUSTIFICATION { + + /** + * A pretty representation of the justification + */ + def repr: String + + /** + * The inner kernel justification + */ + def innerJustification: theory.Justification + + /** + * The sequent that the justification proves + */ + def statement: F.Sequent + + /** + * The complete name of the justification. Two justifications should never have the same full name. Typically, path is used to disambiguate. + */ + def fullName: String + + /** + * The short name of the justification (without the path). + */ + val name: String = fullName.split("\\.").last + + /** + * The "owning" object of the justification. Typically, the package/object in which it is defined. + */ + val owner = fullName.split("\\.").dropRight(1).mkString(".") + + /** + * Returns if the statement is unconditionaly proven or if it depends on some sorry step (including in the other justifications it relies on) + */ + def withSorry: Boolean = innerJustification match { + case thm: theory.Theorem => thm.withSorry + case d: theory.Definition => false + case ax: theory.Axiom => false + } + } + + /** + * A Justification, corresponding to [[K.Axiom]] + */ + class AXIOM(innerAxiom: theory.Axiom, val axiom: F.Formula, val fullName: String) extends JUSTIFICATION { + def innerJustification: theory.Axiom = innerAxiom + val statement: F.Sequent = F.Sequent(Set(), Set(axiom)) + if (statement.underlying != theory.sequentFromJustification(innerAxiom)) { + throw new InvalidAxiomException("The provided kernel axiom and desired statement don't match.", name, axiom, library) + } + def repr: String = s" Axiom $name := $axiom" + } + + /** + * Introduces a new axiom in the theory. + * + * @param fullName The name of the axiom, including the path. Usually fetched automatically by the compiler. + * @param axiom The axiomatized formula. + * @return + */ + def Axiom(using fullName: sourcecode.FullName)(axiom: F.Formula): AXIOM = { + val ax: Option[theory.Axiom] = theory.addAxiom(fullName.value, axiom.underlying) + ax match { + case None => throw new InvalidAxiomException("Not all symbols belong to the theory", fullName.value, axiom, library) + case Some(value) => AXIOM(value, axiom, fullName.value) + } + } + + /** + * A Justification, corresponding to [[K.FunctionDefinition]] or [[K.PredicateDefinition]] + */ + abstract class DEFINITION(line: Int, file: String) extends JUSTIFICATION { + val fullName: String + def repr: String = innerJustification.repr + + def label: F.Constant[?] + knownDefs.update(label, Some(this)) + + } + + /** + * A proven, reusable statement. A justification corresponding to [[K.Theorem]]. + */ + sealed abstract class THM extends JUSTIFICATION { + def repr: String = + s" Theorem ${name} := ${statement}${if (withSorry) " (!! Relies on Sorry)" else ""}" + + /** + * The underlying Kernel proof [[K.SCProof]], if it is still available. Proofs are not kept in memory for efficiency. + */ + def kernelProof: Option[K.SCProof] + + /** + * The high level [[Proof]], if one was used to obtain the theorem. If the theorem was not produced by such high level proof but directly by a low level one, this is None. + */ + def highProof: Option[BaseProof] + val innerJustification: theory.Theorem + + /** + * A pretty representation of the goal of the theorem + */ + def prettyGoal: String = statement.underlying.repr + } + object THM { + + /** + * Standard way to construct a theorem using a high level proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path. Usually fetched automatically by the compiler. + * @param line The line at which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param file The file in which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param computeProof The proof computation. The proof is built by adding proof steps to the proof object. The proof object is an impicit argument of computeProof, + * @see Context Functions in Scala + * @return + */ + def apply(using om: OutputManager)(statement: F.Sequent, fullName: String, line: Int, file: String, kind: TheoremKind)(computeProof: Proof ?=> Unit) = + THMFromProof(statement, fullName, line, file, kind)(computeProof) + + /** + * Constructs a "high level" theorem from an existing theorem in the + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param innerThm The inner theorem, coming from the kernel + * @param getProof If available, a way to compute the Kernel proof again. + */ + def fromKernel(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) = + THMFromKernel(statement, fullName, kind, innerThm, getProof) + + /** + * Construct a theorem (both in the kernel and high level) from a proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param getProof The kernel proof. + * @param justifs low level justifications used to justify the proof's imports + * @return + */ + def fromSCProof(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, getProof: () => K.SCProof, justifs: Seq[theory.Justification]): THM = + val proof = getProof() + theory.theorem(fullName, statement.underlying, proof, justifs) match { + case K.Judgement.ValidJustification(just) => + fromKernel(statement, fullName, kind, just.asInstanceOf, () => Some(getProof())) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The proof was rejected by LISA's logical kernel. ", + wrongJudgement, + None + ) + ) + } + + } + + /** + * A theorem that was produced from a kernel theorem and not from a high level proof. See [[THM.fromKernel]]. + * Those are typically theorems imported from another tool, or from serialization. + */ + class THMFromKernel(using om: OutputManager)(val statement: F.Sequent, val fullName: String, val kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) extends THM { + + val innerJustification: theory.Theorem = innerThm + assert(innerThm.name == fullName) + def kernelProof: Option[K.SCProof] = getProof() + def highProof: Option[BaseProof] = None + + val goal: F.Sequent = statement + + } + + /** + * A theorem that was produced from a high level proof. See [[THM.apply]]. + * Typical way to construct a theorem in the library, but serialization for example will produce a [[THMFromKernel]]. + */ + class THMFromProof(using om: OutputManager)(val statement: F.Sequent, val fullName: String, line: Int, file: String, val kind: TheoremKind)(computeProof: Proof ?=> Unit) extends THM { + + val goal: F.Sequent = statement + + val proof: BaseProof = new BaseProof(this) + def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) + def highProof: Option[BaseProof] = Some(proof) + + import lisa.utils.Serialization.* + val innerJustification: theory.Theorem = + if library._draft.nonEmpty && library._draft.get.value != file + then // if the draft option is activated, and the theorem is not in the file where the draft option is given, then we replace the proof by sorry + theory.theorem(name, goal.underlying, SCProof(SC.Sorry(goal.underlying)), IndexedSeq.empty) match { + case K.Judgement.ValidJustification(just) => + just + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + else if library._withCache then + oneThmFromFile("cache/" + name, library.theory) match { + case Some(thm) => thm // try to get the theorem from file + + case None => + val (thm, scp, justifs) = prove(computeProof) // if fail, prove it + thmsToFile("cache/" + name, theory, List((name, scp, justifs))) // and save it to the file + thm + } + else prove(computeProof)._1 + + library.last = Some(this) + + /** + * Construct the kernel theorem from the high level proof + */ + private def prove(computeProof: Proof ?=> Unit): (theory.Theorem, SCProof, List[(String, theory.Justification)]) = { + try { + computeProof(using proof) + } catch { + case e: UserLisaException => + om.lisaThrow(e) + } + + if (proof.length == 0) + om.lisaThrow(new UnimplementedProof(this)) + + val scp = proof.toSCProof + val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) + theory.theorem(name, goal.underlying, scp, justifs.map(_._2)) match { + case K.Judgement.ValidJustification(just) => + (just, scp, justifs) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + } + + } + + given thmConv: Conversion[library.THM, theory.Theorem] = _.innerJustification + + trait TheoremKind { + val kind2: String + + def apply(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(statement: F.Sequent)(computeProof: Proof ?=> Unit): THM = { + val thm = THM(statement, name.value, line.value, file.value, this)(computeProof) + if this == Theorem then show(thm) + thm + } + + } + + /** + * A "Theorem" kind of theorem, by opposition with a lemma or corollary. The difference is that theorem are always printed when a file defining one is run. + */ + object Theorem extends TheoremKind { val kind2: String = "Theorem" } + + /** + * Lemmas are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Lemma extends TheoremKind { val kind2: String = "Lemma" } + + /** + * Corollaries are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Corollary extends TheoremKind { val kind2: String = "Corollary" } + + /** + * Internal statements are internally produced theorems, for example as intermediate step in definitions. + */ + object InternalStatement extends TheoremKind { val kind2: String = "Internal, automatically produced" } + +} From ccbe7f082a7b0f4bbefa149eb93c1b14c7955ff1 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Wed, 16 Oct 2024 18:57:39 +0200 Subject: [PATCH 12/92] At this stage, compiles but many things left commented to track the bug. --- backup/backup2/prooflib/BasicMain.scala | 29 + backup/backup2/prooflib/BasicStepTactic.scala | 1481 +++++++++++++++++ backup/backup2/prooflib/Exports.scala | 6 + backup/backup2/prooflib/Library.scala | 106 ++ backup/backup2/prooflib/OutputManager.scala | 52 + backup/backup2/prooflib/ProofPrinter.scala | 129 ++ backup/backup2/prooflib/ProofTacticLib.scala | 66 + backup/backup2/prooflib/ProofsHelpers.scala | 356 ++++ .../backup2/prooflib/SimpleDeducedSteps.scala | 331 ++++ backup/backup2/prooflib/WithTheorems.scala | 649 ++++++++ build.sbt | 3 +- .../lisa/kernel/proof/SCProofChecker.scala | 4 +- .../lisa/automation/CongruenceSimp.scala | 172 ++ lisa-utils/src/main/scala/lisa/utils/K.scala | 1 - .../main/scala/lisa/utils/KernelHelpers.scala | 4 +- .../main/scala/lisa/utils/LisaException.scala | 5 +- .../main/scala/lisa/utils/Serialization.scala | 16 + .../src/main/scala/lisa/utils/fol/FOL.scala | 3 +- .../main/scala/lisa/utils/fol/Predef.scala | 35 + .../main/scala/lisa/utils/fol/Sequents.scala | 9 +- .../main/scala/lisa/utils/fol/Syntax.scala | 70 +- .../src/main/scala/lisa/utils/package.scala | 4 - .../lisa/utils/prooflib/BasicStepTactic.scala | 82 +- .../scala/lisa/utils/prooflib/Library.scala | 20 +- .../lisa/utils/prooflib/OutputManager.scala | 2 +- .../lisa/utils/prooflib/ProofPrinter.scala | 6 +- .../lisa/utils/prooflib/ProofTacticLib.scala | 9 +- .../lisa/utils/prooflib/ProofsHelpers.scala | 334 ++-- .../utils/prooflib/SimpleDeducedSteps.scala | 39 +- .../lisa/utils/prooflib/WithTheorems.scala | 40 +- .../utils/unification/UnificationUtils.scala | 618 +++++++ 31 files changed, 4344 insertions(+), 337 deletions(-) create mode 100644 backup/backup2/prooflib/BasicMain.scala create mode 100644 backup/backup2/prooflib/BasicStepTactic.scala create mode 100644 backup/backup2/prooflib/Exports.scala create mode 100644 backup/backup2/prooflib/Library.scala create mode 100644 backup/backup2/prooflib/OutputManager.scala create mode 100644 backup/backup2/prooflib/ProofPrinter.scala create mode 100644 backup/backup2/prooflib/ProofTacticLib.scala create mode 100644 backup/backup2/prooflib/ProofsHelpers.scala create mode 100644 backup/backup2/prooflib/SimpleDeducedSteps.scala create mode 100644 backup/backup2/prooflib/WithTheorems.scala create mode 100644 lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala delete mode 100644 lisa-utils/src/main/scala/lisa/utils/package.scala create mode 100644 lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala diff --git a/backup/backup2/prooflib/BasicMain.scala b/backup/backup2/prooflib/BasicMain.scala new file mode 100644 index 00000000..748f58d5 --- /dev/null +++ b/backup/backup2/prooflib/BasicMain.scala @@ -0,0 +1,29 @@ +package lisa.prooflib + +import lisa.utils.Serialization.* + +trait BasicMain { + val library: Library + + private val realOutput: String => Unit = println + + val om: OutputManager = new OutputManager { + def finishOutput(exception: Exception): Nothing = { + log(exception) + main(Array[String]()) + sys.exit + } + val stringWriter: java.io.StringWriter = new java.io.StringWriter() + } + export om.output + + /** + * This specific implementation make sure that what is "shown" in theory files is only printed for the one we run, and not for the whole library. + */ + def main(args: Array[String]): Unit = { + realOutput(om.stringWriter.toString) + } + + given om.type = om + +} diff --git a/backup/backup2/prooflib/BasicStepTactic.scala b/backup/backup2/prooflib/BasicStepTactic.scala new file mode 100644 index 00000000..7b4c3c2c --- /dev/null +++ b/backup/backup2/prooflib/BasicStepTactic.scala @@ -0,0 +1,1481 @@ +package lisa.prooflib +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.unification.UnificationUtils + +object BasicStepTactic { +/* + def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { + judgement match { + case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) + case j: proof.InvalidProofTactic => proof.InvalidProofTactic(s"Internal tactic call failed! $message\n${j.message}") + } + } + + object Hypothesis extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val intersectedPivot = botK.left.intersect(botK.right) + + if (intersectedPivot.isEmpty) + proof.InvalidProofTactic("A formula for input to Hypothesis could not be inferred from left and right side of the sequent.") + else + proof.ValidProofTactic(bot, Seq(K.Hypothesis(botK, intersectedPivot.head)), Seq()) + } + } + + object Rewrite extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + if (!K.isSameSequent(botK, proof.getSequent(premise).underlying)) + proof.InvalidProofTactic("The premise and the conclusion are not trivially equivalent.") + else + proof.ValidProofTactic(bot, Seq(K.Restate(botK, -1)), Seq(premise)) + } + } + + 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.top)) + proof.InvalidProofTactic("The desired conclusion is not a trivial tautology.") + else + proof.ValidProofTactic(bot, Seq(K.RestateTrue(botK)), Seq()) + } + } + + /** + *
+   *  Γ |- Δ, φ    φ, Σ |- Π
+   * ------------------------
+   *       Γ, Σ |- Δ, Π
+   * 
+ */ + object Cut extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + + if (!K.contains(leftSequent.right, phiK)) + proof.InvalidProofTactic("Right-hand side of first premise does not contain φ as claimed.") + else if (!K.contains(rightSequent.left, phiK)) + proof.InvalidProofTactic("Left-hand side of second premise does not contain φ as claimed.") + else if (!K.isSameSet(botK.left + phiK, leftSequent.left ++ rightSequent.left) || (leftSequent.left.contains(phiK) && !botK.left.contains(phiK))) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") + else if (!K.isSameSet(botK.right + phiK, leftSequent.right ++ rightSequent.right) || (rightSequent.right.contains(phiK) && !botK.right.contains(phiK))) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") + else + proof.ValidProofTactic(bot, Seq(K.Cut(botK, -1, -2, phiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + + lazy val cutSet = (((rightSequent --? bot).right |- ())).left + lazy val intersectedCutSet = rightSequent.left intersect leftSequent.right + + if (!cutSet.isEmpty) + if (cutSet.tail.isEmpty) + Cut.withParameters(cutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Inferred cut pivot is not a singleton set.") + else if (!intersectedCutSet.isEmpty && intersectedCutSet.tail.isEmpty) + // can still find a pivot + Cut.withParameters(intersectedCutSet.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause.") + } + } + + // Left rules + /** + *
+   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
+   * --------------     or     --------------
+   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
+   * 
+ */ + object LeftAnd extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + 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.") + else if ( + !K.isSameSet(botK.left + phiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + psiK, premiseSequent.left + phiAndPsi) && + !K.isSameSet(botK.left + phiK + psiK, premiseSequent.left + phiAndPsi) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftAnd(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.and, phi), psi) => + if (premiseSequent.left.contains(phi)) + LeftAnd.withParameters(phi, psi)(premise)(bot) + else + LeftAnd.withParameters(phi, psi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a conjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial LeftAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
+   * --------------------------------
+   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
+   * 
+ */ + object LeftOr extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(disjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + val botK = bot.underlying + val disjunctsK = disjuncts.map(_.underlying) + lazy val disjunction = K.multior(disjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != disjuncts.length) + proof.InvalidProofTactic(s"Premises and disjuncts expected to be equal in number, but ${premises.length} premises and ${disjuncts.length} disjuncts received.") + else if (!K.isSameSet(botK.right, premiseSequents.map(_.right).reduce(_ union _))) + proof.InvalidProofTactic("Right-hand side of conclusion is not the union of the right-hand sides of the premises.") + else if ( + premiseSequents.zip(disjunctsK).forall((sequent, disjunct) => K.isSubset(sequent.left, botK.left + disjunct)) // \forall i. premise_i.left \subset bot.left + phi_i + && !K.isSubset(botK.left, premiseSequents.map(_.left).reduce(_ union _) + disjunction) // bot.left \subseteq \bigcup premise_i.left + ) + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftOr(botK, Range(-1, -premises.length - 1, -1), disjunctsK)), premises.toSeq) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.left.diff(bot.left)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for LeftOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + LeftOr.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") + } + } + + /** + *
+   *  Γ |- φ, Δ    Σ, ψ |- Π
+   * ------------------------
+   *    Γ, Σ, φ⇒ψ |- Δ, Π
+   * 
+ */ + object LeftImplies extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + 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.") + else if (!K.isSameSet(botK.left + psiK, leftSequent.left union rightSequent.left + implication)) + proof.InvalidProofTactic("Left-hand side of conclusion + ψ is not the union of left-hand sides of premises + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftImplies(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1) + lazy val rightSequent = proof.getSequent(prem2) + lazy val pivotLeft = leftSequent.right.diff(bot.right) + lazy val pivotRight = rightSequent.left.diff(bot.left) + + if (pivotLeft.isEmpty) + if (F.isSubset(leftSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial left premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the first premises.") + else if (pivotRight.isEmpty) + if (F.isSubset(rightSequent.right, bot.right)) + unwrapTactic(Weakening(prem2)(bot))("Attempted weakening on trivial right premise for LeftImplies failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the second premises.") + else if (pivotLeft.tail.isEmpty && pivotRight.tail.isEmpty) + LeftImplies.withParameters(pivotLeft.head, pivotRight.head)(prem1, prem2)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as a pivot from the premises and conclusion, possible extraneous formulae in premises.") + } + } + + /** + *
+   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
+   * --------------    or     --------------------
+   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
+   * 
+ */ + object LeftIff extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + val psiK = psi.underlying + 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.") + else if ( + !K.isSameSet(botK.left + impLeft, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impRight, premiseSequent.left + implication) && + !K.isSameSet(botK.left + impLeft + impRight, premiseSequent.left + implication) + ) + proof.InvalidProofTactic("Left-hand side of premise + φ⇔ψ is not the same as left-hand side of conclusion + either φ⇒ψ, ψ⇒φ or both.") + else + proof.ValidProofTactic(bot, Seq(K.LeftIff(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else + pivot.head match { + 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.") + } + } + } + + /** + *
+   *   Γ |- φ, Δ
+   * --------------
+   *   Γ, ¬φ |- Δ
+   * 
+ */ + object LeftNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + val botK = bot.underlying + val phiK = phi.underlying + 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.") + else if (!K.isSameSet(botK.left, premiseSequent.left + negation)) + proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftNot(botK, -1, phiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftNot failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") + + } + } + + /** + *
+   *   Γ, φ[t/x] |- Δ
+   * -------------------
+   *   Γ, ∀x. φ |- Δ
+   *
+   * 
+ */ + object LeftForall extends ProofTactic with ProofFactSequentTactic { + 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 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") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftForall(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left // .diff(botK.left) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + 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 + proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case g @ F.forall(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + 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. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.left.diff(premiseSequent.left) + lazy val pivot = if (prepivot.isEmpty) bot.left else prepivot + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.forall(x, phi) => ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.forall(x, phi)) => + LeftForall.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, 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. φ.") + } + } + + /** + *
+   *    Γ, φ |- Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ, ∃x φ|- Δ
+   *
+   * 
+ */ + object LeftExists extends ProofTactic with ProofFactSequentTactic { + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.right, premiseSequent.right)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") + else if (!K.isSameSet(botK.left + phiK, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.LeftExists(botK, -1, phiK, xK)), Seq(premise)) + } + + var debug = false + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExists failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.left.find(f => + f match { + case F.exists(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + 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.exists(x, phi) => LeftExists.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the quantified formula found.") + } + } + + /* + /** + *
+   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ, ∃!x. φ |- Δ
+   * 
+ */ + object LeftExistsOne extends ProofTactic with ProofFactSequentTactic { + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) + proof.InvalidProofTactic("Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as left-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.LeftExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.left.diff(premiseSequent.left) + lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExistsOne failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi + 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 + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + else if (pivot.tail.isEmpty) + pivot.head match { + case F.BinderFormula(F.ExistsOne, x, phi) => LeftExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") + } + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") + } + } + + */ + + // Right rules + /** + *
+   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
+   * ------------------------------------
+   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
+   * 
+ */ + object RightAnd extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(conjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) + lazy val botK = bot.underlying + lazy val conjunctsK = conjuncts.map(_.underlying) + lazy val conjunction = K.multiand(conjunctsK) + + if (premises.length == 0) + proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (premises.length != conjuncts.length) + proof.InvalidProofTactic(s"Premises and conjuncts expected to be equal in number, but ${premises.length} premises and ${conjuncts.length} conjuncts received.") + else if (!K.isSameSet(botK.left, premiseSequents.map(_.left).reduce(_ union _))) + proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") + else if ( + premiseSequents.zip(conjunctsK).forall((sequent, conjunct) => K.isSubset(sequent.right, botK.right + conjunct)) // \forall i. premise_i.right \subset bot.right + phi_i + && !K.isSubset(botK.right, premiseSequents.map(_.right).reduce(_ union _) + conjunction) // bot.right \subseteq \bigcup premise_i.right + ) + proof.InvalidProofTactic("Right-hand side of conclusion + conjuncts is not the same as the union of the right-hand sides of the premises + φ∧ψ....") + else + proof.ValidProofTactic(bot, Seq(K.RightAnd(botK, Range(-1, -premises.length - 1, -1), conjunctsK)), premises) + } + + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequents = premises.map(proof.getSequent(_)) + lazy val pivots = premiseSequents.map(_.right.diff(bot.right)) + + if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") + else if (pivots.exists(_.isEmpty)) { + val emptyIndex = pivots.indexWhere(_.isEmpty) + if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) + unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for RightAnd failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the one of the premises.") + } else if (pivots.forall(_.tail.isEmpty)) + RightAnd.withParameters(pivots.map(_.head)*)(premises*)(bot) + else + // some extraneous formulae + proof.InvalidProofTactic("Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises +φ∧ψ.") + } + } + + /** + *
+   *   Γ |- φ, Δ               Γ |- φ, ψ, Δ
+   * --------------    or    ---------------
+   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
+   * 
+ */ + object RightOr extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + 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.") + else if ( + !K.isSameSet(botK.right + phiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + psiK, premiseSequent.right + phiAndPsi) && + !K.isSameSet(botK.right + phiK + psiK, premiseSequent.right + phiAndPsi) + ) + proof.InvalidProofTactic("Right-hand side of premise + φ∧ψ is not the same as right-hand side of conclusion + either φ, ψ or both.") + else + proof.ValidProofTactic(bot, Seq(K.RightOr(botK, -1, phiK, psiK)), Seq(premise)) + } + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + pivot.head match { + case F.App(F.App(F.or, phi), psi) => + if (premiseSequent.left.contains(phi)) + RightOr.withParameters(phi, psi)(premise)(bot) + else + RightOr.withParameters(psi, phi)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer a disjunction as pivot from premise and conclusion.") + } + else + // try a rewrite, if it works, go ahead with it, otherwise malformed + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightOr failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion + φ∧ψ is not the same as right-hand side of premise + either φ, ψ or both.") + } + } + + /** + *
+   *  Γ, φ |- ψ, Δ
+   * --------------
+   *  Γ |- φ⇒ψ, Δ
+   * 
+ */ + object RightImplies extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.right + psiK, premiseSequent.right + implication)) + proof.InvalidProofTactic("Right-hand side of conclusion + ψ is not the same as right-hand side of premise + φ⇒ψ.") + else + proof.ValidProofTactic(bot, Seq(K.RightImplies(botK, -1, phiK, psiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val leftPivot = premiseSequent.left.diff(bot.left) + lazy val rightPivot = premiseSequent.right.diff(bot.right) + + if ( + !leftPivot.isEmpty && leftPivot.tail.isEmpty && + !rightPivot.isEmpty && rightPivot.tail.isEmpty + ) + RightImplies.withParameters(leftPivot.head, rightPivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") + } + } + + /** + *
+   *  Γ |- φ⇒ψ, Δ    Σ |- ψ⇒φ, Π
+   * ----------------------------
+   *      Γ, Σ |- φ⇔ψ, Π, Δ
+   * 
+ */ + object RightIff extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val leftSequent = proof.getSequent(prem1).underlying + lazy val rightSequent = proof.getSequent(prem2).underlying + lazy val phiK = phi.underlying + lazy val psiK = psi.underlying + lazy val botK = bot.underlying + 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"[${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"[${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"[${f.repr}]") + .reduce(_ ++ ", " ++ _) + ) + else + proof.ValidProofTactic(bot, Seq(K.RightIff(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) + } + + def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(prem1) + lazy val pivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + pivot.head match { + 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 + proof.InvalidProofTactic("Right-hand side of conclusion + φ⇒ψ + ψ⇒φ is not the same as the union of the right-hand sides of the premises φ⇔ψ.") + } + } + + /** + *
+   *  Γ, φ |- Δ
+   * --------------
+   *   Γ |- ¬φ, Δ
+   * 
+ */ + object RightNot extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else if (!K.isSameSet(botK.right, premiseSequent.right + negation)) + proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise + ¬φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightNot(botK, -1, phiK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (pivot.isEmpty) + if (F.isSubset(premiseSequent.right, bot.right)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightIff failed.") + else + proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") + else if (pivot.tail.isEmpty) + RightNot.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") + + } + } + + /** + *
+   *    Γ |- φ, Δ
+   * ------------------- if x is not free in the resulting sequent
+   *  Γ |- ∀x. φ, Δ
+   * 
+ */ + object RightForall extends ProofTactic with ProofFactSequentTactic { + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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.") + else 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 + phiK, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∀x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightForall(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from the premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case F.forall(_, g) => F.isSame(g, in) + case _ => false + } + ) + + quantifiedPhi match { + 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.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. φ.") + } + } + + /** + *
+   *   Γ |- φ[t/x], Δ
+   * -------------------
+   *  Γ |- ∃x. φ, Δ
+   *
+   * (ln-x stands for locally nameless x)
+   * 
+ */ + object RightExists extends ProofTactic with ProofFactSequentTactic { + 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 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") + else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ") + else + proof.ValidProofTactic(bot, Seq(K.RightExists(botK, -1, phiK, xK, tK)), Seq(premise)) + } + + def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (!pivot.isEmpty) + if (pivot.tail.isEmpty) + pivot.head match { + 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 + proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") + else if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightExists failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + val quantifiedPhi: Option[F.Formula] = bot.right.find(f => + f match { + case g @ F.exists(v, e) => F.isSame(e.substitute(v := t), in) + case _ => false + } + ) + + quantifiedPhi match { + 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. φ.") + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val prepivot = bot.right.diff(premiseSequent.right) + lazy val pivot = if (prepivot.isEmpty) bot.right else prepivot + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (instantiatedPivot.isEmpty) + if (F.isSubset(premiseSequent.left, bot.left)) + unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightForall failed.") + else + proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") + else if (instantiatedPivot.tail.isEmpty) { + // go through conclusion to find a matching quantified formula + + val in: F.Formula = instantiatedPivot.head + + val quantifiedPhi: Option[F.Formula] = pivot.find(f => + f match { + case g @ F.exists(x, phi) => + ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + case _ => false + } + ) + + quantifiedPhi match { + case Some(F.exists(x, phi)) => + RightExists.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, 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) ⇔ φ, Δ
+   * ---------------------------- if y is not free in φ
+   *      Γ|- ∃!x. φ,  Δ
+   * 
+ */ + 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.underlying + lazy val phiK = phi.underlying + lazy val botK = bot.underlying + 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)) + ) + ) + lazy val quantified = K.BinderFormula(K.ExistsOne, xK, phiK) + + 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 + quantified)) + proof.InvalidProofTactic("Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as right-hand side of premise + ∃!x. φ.") + else + proof.ValidProofTactic(bot, Seq(K.RightExistsOne(botK, -1, phiK, xK)), Seq(premise)) + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = bot.right.diff(premiseSequent.right) + lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) + + if (pivot.isEmpty) + if (instantiatedPivot.isEmpty) + if (F.isSameSequent(premiseSequent, bot)) + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightExistsOne failed.") + else + proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") + else if (instantiatedPivot.tail.isEmpty) { + instantiatedPivot.head match { + // ∃_. ∀x. _ ⇔ φ == extract ==> x, 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.") + } + } 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.ExistsOne, x, phi) => RightExistsOne.withParameters(phi, x)(premise)(bot) + case _ => proof.InvalidProofTactic("Could not infer an existentially 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. φ.") + } + } + + */ + + object RightBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.left, premiseSequent.left)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.right + phi_redex, premiseSequent.right + phi_normalized) || K.isSameSet(botK.right + phi_normalized, premiseSequent.right + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Right-hand side of the conclusion + φ[λy.e]t/x must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Left-hand side of the conclusion must be the same as the left-hand side of the premise") + } + } + + object LeftBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.right, premiseSequent.right)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.left + phi_redex, premiseSequent.left + phi_normalized) || K.isSameSet(botK.left + phi_normalized, premiseSequent.left + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Left-hand side of the conclusion + φ[λy.e]t/x must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Right-hand side of the conclusion must be the same as the right-hand side of the premise") + } + } + + // Structural rules + /** + *
+   *     Γ |- Δ
+   * --------------
+   *   Γ, Σ |- Δ, Π
+   * 
+ */ + object Weakening extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + + if (!F.isImplyingSequent(premiseSequent, bot)) + proof.InvalidProofTactic("Conclusion cannot be trivially derived from premise.") + else + proof.ValidProofTactic(bot, Seq(K.Weakening(bot.underlying, -1)), Seq(premise)) + } + } + + // Equality Rules + /** + *
+   *  Γ, s=s |- Δ
+   * --------------
+   *     Γ |- Δ
+   * 
+ */ + object LeftRefl extends ProofTactic with ProofFactSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val faK = fa.underlying + lazy val botK = bot.underlying + + if (!K.isSameSet(botK.left + faK, premiseSequent.left) || !premiseSequent.left.exists(_ == faK) || botK.left.exists(_ == faK)) + proof.InvalidProofTactic("Left-hand sides of the conclusion + φ is not the same as left-hand side of the premise.") + else 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 + faK match { + 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.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise) + lazy val pivot = premiseSequent.left.diff(bot.left) + + if (!pivot.isEmpty && pivot.tail.isEmpty) + LeftRefl.withParameters(pivot.head)(premise)(bot) + else + proof.InvalidProofTactic("Could not infer an equality as pivot from premise and conclusion.") + } + } + + /** + *
+   *
+   * --------------
+   *     |- s=s
+   * 
+ */ + object RightRefl extends ProofTactic with ProofSequentTactic { + def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val faK = fa.underlying + lazy val botK = bot.underlying + if (!botK.right.exists(_ == faK)) + proof.InvalidProofTactic("Right-hand side of conclusion does not contain φ.") + else + faK match { + 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.") + case _ => proof.InvalidProofTactic("φ is not an equality.") + } + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (bot.right.isEmpty) proof.InvalidProofTactic("Right-hand side of conclusion does not contain an instance of reflexivity.") + else { + // go through conclusion to see if you can find an reflexive formula + 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.App(F.App(e, l), r) => + (F.equality) == (e) && l == r // termequality + case _ => false + } + ) + + pivot match { + case Some(phi) => RightRefl.withParameters(phi)(bot) + case _ => proof.InvalidProofTactic("Could not infer an equality as pivot from conclusion.") + } + + } + + } + } + + /** + *
+   *   Γ, φ(s) |- Δ     Σ |- s=t, Π     
+   * --------------------------------
+   *        Γ, Σ φ(t) |- Δ, Π
+   * 
+ */ + object LeftSubstEq extends ProofTactic { + + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + + + } + } + + /** + *
+   *  Γ |- φ(s), Δ     Σ |- s=t, Π
+   * ---------------------------------
+   *         Γ, Σ |- φ(t), Δ, Π
+   * 
+ */ + object RightSubstEq extends ProofTactic { + def withParametersSimple[T1](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + } + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Formula) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.equality(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + } + + } + + /** + *
+   *           Γ, φ(a1,...an) |- Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
+   * 
+ */ + object LeftSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + /*{ + lazy val premiseSequent1 = proof.getSequent(prem1).underlying + lazy val premiseSequent2 = proof.getSequent(prem2).underlying + lazy val botK = bot.underlying + lazy val sK = s.underlying + lazy val tK = t.underlying + lazy val varsK = vars.map(_.underlying) + val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) + val (phi_arg, phi_body) = lambdaPhiK + + if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) + return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") + else if (!s.sort.isFunctional) + return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") + val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) + val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) + + val inner1 = varsK.foldLeft(sK)(_(_)) + val inner2 = varsK.foldLeft(tK)(_(_)) + val sEqt = K.Iff(inner1)(inner2) + val varss = varsK.toSet + + if ( + K.isSubset(premiseSequent1.right, botK.right) && + K.isSubset(premiseSequent2.right, botK.right + sEqt) && + K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) + ) { + if ( + K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && + K.isSubset(premiseSequent2.left, botK.left) && + K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) + ) { + if ( + premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || + premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) + ) { + return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") + } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + } + else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") + } + else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") + */ + + } + + /** + *
+   *           Γ |- φ(a1,...an), Δ
+   * ----------------------------------------
+   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
+   * 
+ */ + object RightSubstIff extends ProofTactic { + def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + def withParameters(using lib: Library, proof: lib.Proof)( + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + + /* + 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(_.underlying), 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.forall(s_arg, acc) } + } + + 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)) + } + */ + } + + /** + *
+   *           Γ |- Δ
+   * --------------------------
+   *  Γ[r(a)/?f] |- Δ[r(a)/?f]
+   * 
+ */ + object InstSchema extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof + )(map: Map[F.Variable[?], F.Expr[?]])(premise: proof.Fact): 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)) + } + } + object Subproof extends ProofTactic { + def apply(using proof: Library#Proof)(statement: Option[F.Sequent])(iProof: proof.InnerProof) = { + val bot: Option[F.Sequent] = statement + val botK: Option[K.Sequent] = statement map (_.underlying) + if (iProof.length == 0) throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + val judgement: proof.ProofTacticJudgement = { + 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: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}" + ) + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + 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) + if (iProof.length == 0) + throw (new UnimplementedProof(proof.owningTheorem)) + val scproof: K.SCProof = iProof.toSCProof + + val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) + def judgement: proof.ProofTacticJudgement = { + 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: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}") + else + proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) + } + } + + // TODO make specific support for subproofs written inside tactics.kkkkkkk + + inline def TacticSubproof(using proof: Library#Proof)(inline computeProof: proof.InnerProof ?=> Unit): proof.ProofTacticJudgement = + val iProof: proof.InnerProof = new proof.InnerProof(None) + computeProof(using iProof) + SUBPROOF(using proof)(None)(iProof).judgement.asInstanceOf[proof.ProofTacticJudgement] + + object Sorry extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + proof.ValidProofTactic(bot, Seq(K.Sorry(bot.underlying)), Seq()) + } + } + +} diff --git a/backup/backup2/prooflib/Exports.scala b/backup/backup2/prooflib/Exports.scala new file mode 100644 index 00000000..836d1cac --- /dev/null +++ b/backup/backup2/prooflib/Exports.scala @@ -0,0 +1,6 @@ +package lisa.prooflib + +object Exports { + export BasicStepTactic.* + export lisa.prooflib.SimpleDeducedSteps.* +} diff --git a/backup/backup2/prooflib/Library.scala b/backup/backup2/prooflib/Library.scala new file mode 100644 index 00000000..049b82bf --- /dev/null +++ b/backup/backup2/prooflib/Library.scala @@ -0,0 +1,106 @@ +package lisa.prooflib + +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.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import scala.collection.mutable.Stack as stack + +/** + * A class abstracting a [[lisa.kernel.proof.RunningTheory]] providing utility functions and a convenient syntax + * to write and use Theorems and Definitions. + * @param theory The inner RunningTheory + */ +abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.ProofsHelpers { + + val theory: RunningTheory + given library: this.type = this + given RunningTheory = theory + + export lisa.kernel.proof.SCProof + + val K = lisa.utils.K + val SC: SequentCalculus.type = K.SC + private[prooflib] val F = lisa.fol.FOL + import F.{given} + + var last: Option[JUSTIFICATION] = None + + // Options for files + private[prooflib] var _withCache: Boolean = false + def withCache(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: withCache option should be used before the first definition or theorem.")) + else _withCache = true + + private[prooflib] var _draft: Option[sourcecode.File] = None + def draft(using file: sourcecode.File, om: OutputManager)(): Unit = + if last.nonEmpty then om.output(OutputManager.WARNING("Warning: draft option should be used before the first definition or theorem.")) + else _draft = Some(file) + def isDraft = _draft.nonEmpty + + 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.Constant[?]): Unit = + theory.addSymbol(s.underlying) + knownDefs.update(s, None) + + 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.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case Some(value) => value + } + + /** + * An alias to create a Theorem + */ + def makeTheorem(name: String, statement: K.Sequent, proof: K.SCProof, justifications: Seq[theory.Justification]): K.Judgement[theory.Theorem] = + theory.theorem(name, statement, proof, justifications) + + // 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 makeSimpleDefinition(symbol: String, expression: K.Expression): K.Judgement[theory.Definition] = + theory.definition(symbol, expression) + + + /** + * Prints a short representation of the given theorem or definition + */ + def show(using om: OutputManager)(thm: JUSTIFICATION) = { + if (thm.withSorry) om.output(thm.repr, Console.YELLOW) + else om.output(thm.repr, Console.GREEN) + } + + /** + * Prints a short representation of the last theorem or definition introduced + */ + def show(using om: OutputManager): Unit = last match { + case Some(value) => show(value) + case None => throw new NoSuchElementException("There is nothing to show: No theorem or definition has been proved yet.") + } + +} diff --git a/backup/backup2/prooflib/OutputManager.scala b/backup/backup2/prooflib/OutputManager.scala new file mode 100644 index 00000000..bc7442ef --- /dev/null +++ b/backup/backup2/prooflib/OutputManager.scala @@ -0,0 +1,52 @@ +package lisa.prooflib + +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.{_, given} + +import java.io.PrintWriter +import java.io.StringWriter + +abstract class OutputManager { + + given OutputManager = this + + def output(s: String): Unit = stringWriter.write(s + "\n") + def output(s: String, color: String): Unit = stringWriter.write(Console.RESET + color + s + "\n" + Console.RESET) + val stringWriter: StringWriter + + def finishOutput(exception: Exception): Nothing + + def lisaThrow(le: LisaException): Nothing = + le match { + case ule: UserLisaException => + ule.fixTrace() + output(ule.showError) + finishOutput(ule) + + case e: LisaException.InvalidKernelJustificationComputation => + e.proof match { + case Some(value) => output(lisa.prooflib.ProofPrinter.prettyProof(value)) + case None => () + } + output(e.underlying.repr) + finishOutput(e) + + } + + def log(e: Exception): Unit = { + stringWriter.write("\n[" + Console.RED + "Error" + Console.RESET + "] ") + e.printStackTrace(PrintWriter(stringWriter)) + output(Console.RESET) + } + +} +object OutputManager { + def RED(s: String): String = Console.RED + s + Console.RESET + def GREEN(s: String): String = Console.GREEN + s + Console.RESET + def BLUE(s: String): String = Console.BLUE + s + Console.RESET + def YELLOW(s: String): String = Console.YELLOW + s + Console.RESET + def CYAN(s: String): String = Console.CYAN + s + Console.RESET + def MAGENTA(s: String): String = Console.MAGENTA + s + Console.RESET + + def WARNING(s: String): String = Console.YELLOW + "⚠ " + s + Console.RESET +} diff --git a/backup/backup2/prooflib/ProofPrinter.scala b/backup/backup2/prooflib/ProofPrinter.scala new file mode 100644 index 00000000..96c95137 --- /dev/null +++ b/backup/backup2/prooflib/ProofPrinter.scala @@ -0,0 +1,129 @@ +package lisa.prooflib + +import lisa.kernel.proof.SCProofCheckerJudgement +import lisa.prooflib.BasicStepTactic.SUBPROOF +import lisa.prooflib.Library +import lisa.prooflib.* +import lisa.utils.* + +object ProofPrinter { + private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " + + private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" + + private def prettyProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { + def computeMaxNumberingLengths(proof: Library#Proof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { + val resultWithCurrent = result.updated( + level, + (Seq((proof.getSteps.size - 1).toString.length, result(level)) ++ (if (proof.getImports.nonEmpty) Seq((-proof.getImports.size).toString.length) else Seq.empty)).max + ) + proof.getSteps + .collect { case ps: proof.ProofStep if ps.tactic.isInstanceOf[SUBPROOF] => ps.tactic.asInstanceOf[SUBPROOF] } + .foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.iProof, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) + } + + val maxNumberingLengths = computeMaxNumberingLengths(printedProof, 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 prettyProofRecursive(printedProof: Library#Proof, level: Int, tree: IndexedSeq[Int], topMostIndices: IndexedSeq[Int]): Seq[(Boolean, String, String, String)] = { + val imports = printedProof.getImports + val steps = printedProof.getSteps + val printedImports = imports.zipWithIndex.reverse.flatMap { case (imp, i) => + val currentTree = tree :+ (-i - 1) + + val showErrorForLine: Boolean = error match { + case None => false + case Some((position, message)) => 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._2.toString() + ) + + Seq(pretty("Import", 0)) + } + printedImports ++ steps.zipWithIndex.flatMap { case (step, i) => + val currentTree = tree :+ i + val showErrorForLine: Boolean = error match { + case None => false + case Some((position, message)) => 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.toString() + ) + + step.tactic match { + case sp: SUBPROOF => + val topSteps: Seq[Int] = sp.premises.map((f: sp.proof.Fact) => sp.proof.sequentAndIntOfFact(f)._2) + pretty("Subproof", topSteps*) +: prettyProofRecursive(sp.iProof, level + 1, currentTree, (if (i == 0) topMostIndices else IndexedSeq.empty) :+ i) + case other => + val line = pretty(other.name) + Seq(line) + } + } + } + + val marker = "->" + + val lines = prettyProofRecursive(printedProof, 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 (error.isDefined) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix + full.mkString(" ") + } + ++ (error match { + case None => Nil + case Some((path, message)) => List(s"\nProof checker has reported an error at line ${path.mkString(".")}: $message") + }) + } + + def prettyFullProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { + printedProof match { + case proof: Library#Proof#InnerProof => + prettyFullProofLines(proof.parent, None) ++ prettyProofLines(proof, error).map(" " + _) + case proof: Library#BaseProof => + prettyProofLines(proof, None) + } + } + + def prettyProof(proof: Library#Proof): String = prettyFullProofLines(proof, None).mkString("\n") + def prettyProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyFullProofLines(proof, None).mkString("\n" + (" " * indent)) + + def prettyProof(proof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, error).mkString("\n") + def prettyProof(proof: Library#Proof, indent: Int, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, None).mkString("\n" + " " * indent) + + def prettySimpleProof(proof: Library#Proof): String = prettyProofLines(proof, None).mkString("\n") + def prettySimpleProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyProofLines(proof, None).mkString("\n" + (" " * indent)) + + 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/backup/backup2/prooflib/ProofTacticLib.scala b/backup/backup2/prooflib/ProofTacticLib.scala new file mode 100644 index 00000000..c107e724 --- /dev/null +++ b/backup/backup2/prooflib/ProofTacticLib.scala @@ -0,0 +1,66 @@ +package lisa.prooflib + +import lisa.fol.FOL as F +import lisa.prooflib.* +import lisa.utils.K +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. + */ + trait ProofTactic { + val name: String = this.getClass.getName.split('$').last + given ProofTactic = this + + } + + trait OnlyProofTactic { + def apply(using lib: Library, proof: lib.Proof): proof.ProofTacticJudgement + } + + trait ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement + } + + trait ProofFactTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact): proof.ProofTacticJudgement + } + trait ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement + } + + class UnapplicableProofTactic(val tactic: ProofTactic, proof: Library#Proof, errorMessage: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { + override def fixTrace(): Unit = { + val start = getStackTrace.indexWhere(elem => { + !elem.getClassName.contains(tactic.name) + }) + 1 + setStackTrace(getStackTrace.take(start)) + } + + def 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() + Console.RED + proof.owningTheorem.prettyGoal + Console.RESET + "\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 + } + } + + class UnimplementedProof(val theorem: Library#THM)(using sourcecode.Line, sourcecode.File) extends UserLisaException("Unimplemented Theorem") { + def showError: String = s"Theorem ${theorem.name}" + } + case class UnexpectedProofTacticFailureException(failure: Library#Proof#InvalidProofTactic, errorMessage: String)(using sourcecode.Line, sourcecode.File) + 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:" + + ProofPrinter.prettyProof(failure.proof) + } + +} diff --git a/backup/backup2/prooflib/ProofsHelpers.scala b/backup/backup2/prooflib/ProofsHelpers.scala new file mode 100644 index 00000000..d9fdfe59 --- /dev/null +++ b/backup/backup2/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 => ??? // TODO 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)) + ??? // TODO 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 om: OutputManager, name: sourcecode.FullName, 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 + () |- iff.#@(appliedCst).#@(right).asInstanceOf[Formula] + else + () |- equality.#@(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 RightBeta.withParameters(appliedCst === right, lam, vAbs, freshX) + 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(propCst) by BasicStepTactic.RightSubstEq.withParametersSimple[T](appliedCst, body, Seq(), (epsilonVar, inner))(j, lastStep) + ??? + } + + 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/backup/backup2/prooflib/SimpleDeducedSteps.scala b/backup/backup2/prooflib/SimpleDeducedSteps.scala new file mode 100644 index 00000000..2ce8f574 --- /dev/null +++ b/backup/backup2/prooflib/SimpleDeducedSteps.scala @@ -0,0 +1,331 @@ +package lisa.prooflib + +import lisa.fol.FOL as F +import lisa.prooflib.BasicStepTactic.* +import lisa.prooflib.ProofTacticLib.{_, given} +import lisa.prooflib.* +import lisa.utils.K +import lisa.utils.KernelHelpers.{_, given} + +object SimpleDeducedSteps { +/* + + object Restate extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(RewriteTrue(bot))("Attempted true rewrite during tactic Restate failed.") + + // (proof.ProofStep | proof.OutsideFact | Int) is definitionally equal to proof.Fact, but for some reason + // scala compiler doesn't resolve the overload with a type alias, dependant type and implicit parameter + + def apply(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + def from(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = + unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") + + } + + object Discharge extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(premise: proof.Fact): proof.ProofTacticJudgement = { + val ss = premises zip (premises map (e => proof.getSequent(e))) + val seqs = ss.map(_._2) + if (!seqs.forall(_.right.size == 1)) + return proof.InvalidProofTactic("When discharging this way, the discharged sequent must have only a single formula on the right handside.") + val seqAny = ss.find((_, s) => premise.statement.left.exists(f2 => F.isSame(s.right.head, f2))) + if (seqAny.isEmpty) + Restate.from(premise)(premise.statement) + else + TacticSubproof: ip ?=> + ss.foldLeft(premise: ip.Fact)((prem, discharge) => + val seq = discharge._2 + if prem.statement.left.exists(f => F.isSame(f, seq.right.head)) then + val goal = prem.statement - + * Γ ⊢ ∀x.ψ, Δ + * ------------------------- + * Γ |- ψ[t/x], Δ + * + * + * + * Returns a subproof containing the instantiation steps + */ + object InstantiateForall extends ProofTactic with ProofSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: F.Formula, t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val botK = bot.underlying + val phiK = phi.underlying + val tK = t map (_.underlying) + val premiseSequent = proof.getSequent(premise) + val premiseSequentK = premiseSequent.underlying + if (!premiseSequent.right.contains(phi)) { + proof.InvalidProofTactic("Input formula was not found in the RHS of the premise sequent.") + } else { + val emptyProof = K.SCProof(IndexedSeq(), IndexedSeq(premiseSequentK)) + val j = proof.ValidProofTactic(bot, Seq(K.Restate(premiseSequentK, -1)), Seq(premise)) + val res = tK.foldLeft((emptyProof, phiK, j: proof.ProofTacticJudgement)) { case ((p, f, j), t) => + j match { + case proof.InvalidProofTactic(_) => (p, f, j) // propagate error + case proof.ValidProofTactic(_, _, _) => + // good state, continue instantiating + // by construction the premise is well-formed + // verify the formula structure and instantiate + f match { + case psi @ K.Forall(K.Lambda(x, inner)) => + val tempVar = K.Variable(K.freshId(psi.freeVariables.map(_.id), x.id), K.Term) + // instantiate the formula with input + 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, K.substituteVariables(inner, Map(x -> tempVar)) , tempVar, t) + val p2 = K.Cut(con, -1, 1, f) + + /** + * in = ψ[t/x] + * + * s1 = Γ ⊢ ∀x.ψ, Δ Premise + * con = Γ ⊢ ψ[t/x], Δ Result + * + * p0 = ψ[t/x] ⊢ ψ[t/x] Hypothesis + * p1 = ∀x.ψ ⊢ ψ[t/x] LeftForall p0 + * p2 = Γ ⊢ ψ[t/x], Δ Cut s1, p1 + */ + val newStep = K.SCSubproof(K.SCProof(IndexedSeq(p0, p1, p2), IndexedSeq(p.conclusion)), Seq(p.length - 1)) + ( + p withNewSteps IndexedSeq(newStep), + in, + j + ) + case _ => + (p, f, proof.InvalidProofTactic("Input formula is not universally quantified")) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _, _) => { + 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${res._1.conclusion.repr}\ninstead of input sequent\n\t${botK.repr}") + } + } + } + } + + def apply(using lib: Library, proof: lib.Proof)(t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val prem = proof.getSequent(premise) + if (prem.right.tail.isEmpty) { + // well formed + apply(using lib, proof)(prem.right.head, t*)(premise)(bot): proof.ProofTacticJudgement + } else proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { + try { + val sp = TacticSubproof { + // lazy val premiseSequent = proof.getSequent(premise) + val s1 = lib.have(bot +<< bot.right.head) by Restate + lib.have(bot) by LeftForall(s1) + } + BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") + } catch { + case e: Exception => proof.InvalidProofTactic("Impossible to justify desired step with instantiation.") + } + + } + + } + + */ + + + /* + /** + * Performs a cut when the formula to be used as pivot for the cut is + * inside a conjunction, preserving the conjunction structure + * + *
+   *
+   * PartialCut(ϕ, ϕ ∧ ψ)(left, right) :
+   *
+   *     left: Γ ⊢ ϕ ∧ ψ, Δ      right: ϕ, Σ ⊢ γ1 , γ2, …, γn
+   * -----------------------------------------------------------
+   *            Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn
+   *
+   * 
+ */ + object PartialCut extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, conjunction: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val leftSequent = proof.getSequent(prem1) + val rightSequent = proof.getSequent(prem2) + + if (leftSequent.right.contains(conjunction)) { + + if (rightSequent.left.contains(phi)) { + // check conjunction matches with phi + conjunction match { + case K.ConnectorFormula(K.And, s: Seq[K.Formula]) => { + if (s.contains(phi)) { + // construct proof + + val psi: Seq[K.Formula] = s.filterNot(_ == phi) + val newConclusions: Set[K.Formula] = rightSequent.right.map((f: K.Formula) => K.ConnectorFormula(K.And, f +: psi)) + + val Sigma: Set[K.Formula] = rightSequent.left - phi + + val p0 = K.Weakening(rightSequent ++<< (psi |- ()), -2) + val p1 = K.RestateTrue(psi |- psi) + + // TODO: can be abstracted into a RightAndAll step + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(p0.bot, p1.bot)) + val proofRightAndAll = rightSequent.right.foldLeft(emptyProof) { case (p, gamma) => + p withNewSteps IndexedSeq(K.RightAnd(p.conclusion ->> gamma +>> K.ConnectorFormula(K.And, gamma +: psi), Seq(p.length - 1, -2), gamma +: psi)) + } + + val p2 = K.SCSubproof(proofRightAndAll, Seq(0, 1)) + val p3 = K.Restate(Sigma + conjunction |- newConclusions, 2) // sanity check and correct form + val p4 = K.Cut(bot, -1, 3, conjunction) + + /** + * newConclusions = ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn + * + * left = Γ ⊢ ϕ ∧ ψ, Δ Premise + * right = ϕ, Σ ⊢ γ1 , γ2, …, γn Premise + * + * p0 = ϕ, Σ, ψ ⊢ γ1 , γ2, …, γn Weakening on right + * p1 = ψ ⊢ ψ Hypothesis + * p2 = Subproof: + * 2.1 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , γ2, …, γn RightAnd on p0 and p1 with ψ ∧ γ1 + * 2.2 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , ψ ∧ γ2, …, γn RightAnd on 2.1 and p1 ψ ∧ γ2 + * ... + * 2.n = ϕ, Σ, ψ ⊢ ψ ∧ γ1, ψ ∧ γ2, …, ψ ∧ γn RightAnd on 2.(n-1) and p1 with ψ ∧ γn + * + * p3 = ϕ ∧ ψ, Σ ⊢ ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Rewrite on p2 (just to have a cleaner form) + * p2 = Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Cut on left, p1 with ϕ ∧ ψ + * + * p2 is the result + */ + + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3, p4), Seq(prem1, prem2)) + } else { + proof.InvalidProofTactic("Input conjunction does not contain the pivot.") + } + } + case _ => proof.InvalidProofTactic("Input not a conjunction.") + } + } else { + proof.InvalidProofTactic("Input pivot formula not found in right premise.") + } + } else { + proof.InvalidProofTactic("Input conjunction not found in first premise.") + } + } + } + + object destructRightAnd extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.LeftAnd(emptySeq +<< (a /\ b) +>> a, 0, a, b) + val p2 = K.Cut(conc ->> (a /\ b) ->> (b /\ a) +>> a, -1, 1, a /\ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2), Seq(prem)) + } + } + object destructRightOr extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val conc = proof.getSequent(prem) + val mat = conc.right.find(f => K.isSame(f, a \/ b)) + if (mat.nonEmpty) { + + val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) + val p1 = K.Hypothesis(emptySeq +<< b +>> b, b) + + val p2 = K.LeftOr(emptySeq +<< (a \/ b) +>> a +>> b, Seq(0, 1), Seq(a, b)) + val p3 = K.Cut(conc ->> mat.get +>> a +>> b, -1, 2, a \/ b) + proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3), Seq(prem)) + } else { + proof.InvalidProofTactic("Premise does not contain the union of the given formulas") + } + + } + } + + object GeneralizeToForall extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val sequent = proof.getSequent(prem) + if (sequent.right.contains(phi)) { + val emptyProof = SCProof(IndexedSeq(), IndexedSeq(sequent)) + val j = proof.ValidProofTactic(IndexedSeq(K.Restate(sequent, proof.length - 1)), Seq[proof.Fact]()) + + val res = t.foldRight(emptyProof: SCProof, phi: K.Formula, j: proof.ProofTacticJudgement) { case (x1, (p1: SCProof, phi1, j1)) => + j1 match { + case proof.InvalidProofTactic(_) => (p1, phi1, j1) + case proof.ValidProofTactic(_, _) => { + if (!p1.conclusion.right.contains(phi1)) + (p1, phi1, proof.InvalidProofTactic("Formula is not present in the lass sequent")) + + val proofStep = K.RightForall(p1.conclusion ->> phi1 +>> forall(x1, phi1), p1.length - 1, phi1, x1) + ( + p1 appended proofStep, + forall(x1, phi1), + j1 + ) + } + } + } + + res._3 match { + case proof.InvalidProofTactic(_) => res._3 + case proof.ValidProofTactic(_, _) => proof.ValidProofTactic((res._1.steps appended K.Restate(bot, res._1.length - 1)), Seq(prem)) + } + + } else proof.InvalidProofTactic("RHS of premise sequent contains not phi") + + } + } + + object GeneralizeToForallNoForm extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + if (proof.getSequent(prem).right.tail.isEmpty) + GeneralizeToForall.apply(using lib, proof)(proof.getSequent(prem).right.head, t*)(prem)(bot): proof.ProofTacticJudgement + else + proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") + } + + } + + object ByCase extends ProofTactic { + def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + val nphi = !phi + + val pa = proof.getSequent(prem1) + val pb = proof.getSequent(prem2) + val (leftAphi, leftBnphi) = (pa.left.find(K.isSame(_, phi)), pb.left.find(K.isSame(_, nphi))) + if (leftAphi.nonEmpty && leftBnphi.nonEmpty) { + val p2 = K.RightNot(pa -<< leftAphi.get +>> nphi, -1, phi) + val p3 = K.Cut(pa -<< leftAphi.get ++ (pb -<< leftBnphi.get), 0, -2, nphi) + val p4 = K.Restate(bot, 1) + proof.ValidProofTactic(IndexedSeq(p2, p3, p4), IndexedSeq(prem1, prem2)) // TODO: Check pa/pb orDer + + } else { + proof.InvalidProofTactic("Premises have not the right syntax") + } + } + } + */ +} diff --git a/backup/backup2/prooflib/WithTheorems.scala b/backup/backup2/prooflib/WithTheorems.scala new file mode 100644 index 00000000..d8f3e930 --- /dev/null +++ b/backup/backup2/prooflib/WithTheorems.scala @@ -0,0 +1,649 @@ +package lisa.prooflib + +import lisa.kernel.proof.RunningTheory +import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.prooflib.ProofTacticLib.UnimplementedProof +import lisa.prooflib.* +import lisa.utils.KernelHelpers.{_, given} +import lisa.utils.LisaException +import lisa.utils.UserLisaException +import lisa.utils.UserLisaException.* + +import scala.annotation.nowarn +import scala.collection.mutable.Buffer as mBuf +import scala.collection.mutable.Map as mMap +import scala.collection.mutable.Stack as stack + +trait WithTheorems { + library: Library => + + /** + * The main builder for proofs. It is a mutable object that can be used to build a proof step by step. + * It is used either to construct a theorem/lemma ([[BaseProof]]) or to construct a subproof ([[InnerProof]]). + * We can add proof tactics to it producing intermediate results. In the end, obtain a [[K.SCProof]] from it. + * + * @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 + type Fact = ProofStep | InstantiatedFact | OutsideFact | Int + + /** + * A proven fact (from a previously proven step, a theorem or a definition) with specific instantiations of free variables. + * + * @param fact The base fact + * @param insts The instantiation of free variables + */ + case class InstantiatedFact( + fact: Fact, + insts: Seq[F.SubstPair | F.Term] + ) { + 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 (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) + val (s2, p2) = if terms.isEmpty then (s1, p1) else s1.instantiateForallWithProof(terms, p1.length - 1) + (s2, p1 ++ p2) + } + + } + + val library: WithTheorems.this.type = WithTheorems.this + + private var steps: List[ProofStep] = Nil + private var imports: List[(OutsideFact, F.Sequent)] = Nil + private var instantiatedFacts: List[(InstantiatedFact, Int)] = Nil + private var assumptions: List[F.Formula] = assump + private var eliminations: List[(F.Formula, (Int, F.Sequent) => List[K.SCProofStep])] = Nil + + def cleanAssumptions: Unit = assumptions = Nil + + /** + * the theorem that is being proved (paritally, if subproof) by this proof. + * + * @return The theorem + */ + def owningTheorem: THM + + /** + * A proof step, containing a high level ProofTactic and the corresponding K.SCProofStep. If the tactic produce more than one + * step, they must be encapsulated in a subproof. Usually constructed with [[ValidProofTactic.validate]] + * + * @param judgement The result of the tactic + * @param scps The corresponding [[K.SCProofStep]] + * @param position The position of the step in the proof + */ + case class ProofStep private (judgement: ValidProofTactic, scps: K.SCProofStep, position: Int) { + val bot: F.Sequent = judgement.bot + def innerBot: K.Sequent = scps.bot + val host: Proof.this.type = Proof.this + + def tactic: ProofTactic = judgement.tactic + + } + private object ProofStep { // TODO + def newProofStep(judgement: ValidProofTactic): ProofStep = { + val ps = ProofStep( + judgement, + SC.SCSubproof( + K.SCProof(judgement.scps.toIndexedSeq, judgement.imports.map(f => sequentOfFact(f).underlying).toIndexedSeq), + judgement.imports.map(sequentAndIntOfFact(_)._2) + ), + steps.length + ) + addStep(ps) + ps + + } + } + + /** + * A proof step can be constructed from a succesfully executed tactic + */ + def newProofStep(judgement: ValidProofTactic): ProofStep = + ProofStep.newProofStep(judgement) + + private def addStep(ds: ProofStep): Unit = steps = ds :: steps + private def addImport(imp: OutsideFact, seq: F.Sequent): Unit = { + imports = (imp, seq) :: imports + } + + private def addInstantiatedFact(instFact: InstantiatedFact): Unit = { + val step = ValidProofTactic(instFact.result, instFact.proof, Seq(instFact.fact))(using F.SequentInstantiationRule) + newProofStep(step) + instantiatedFacts = (instFact, steps.length - 1) :: instantiatedFacts + } + + /** + * Add an assumption the the proof, i.e. a formula that is automatically on the left side of the sequent. + * + * @param f + */ + def addAssumption(f: F.Formula): Unit = { + if (!assumptions.contains(f)) assumptions = f :: assumptions + } + + def addElimination(f: F.Formula, elim: (Int, F.Sequent) => List[K.SCProofStep]): Unit = { + eliminations = (f, elim) :: eliminations + } + + def addDischarge(ji: Fact): Unit = { + val (s1, t1) = sequentAndIntOfFact(ji) + val f = s1.right.head + val fu = f.underlying + addElimination( + f, + (i, sequent) => + List( + SC.Cut((sequent.underlying -<< fu) ++ (s1.underlying ->> fu), t1, i, fu) + ) + ) + } + /* + 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 + + /** + * Favour using getSequent when applicable. + * @return The list of ValidatedSteps (containing a high level ProofTactic and the corresponding K.SCProofStep). + */ + def getSteps: List[ProofStep] = steps.reverse + + /** + * Favour using getSequent when applicable. + * @return The list of Imports validated in the formula, with their original justification. + */ + def getImports: List[(OutsideFact, F.Sequent)] = imports.reverse + + /** + * @return The list of formulas that are assumed for the reminder of the proof. + */ + def getAssumptions: List[F.Formula] = assumptions + + /** + * Produce the low level [[K.SCProof]] corresponding to the proof. Automatically eliminates any formula in the discharges that is still left of the sequent. + * + * @return + */ + def toSCProof: K.SCProof = { + import lisa.utils.KernelHelpers.{-<<, ->>} + val finalSteps = eliminations.foldLeft[(List[SC.SCProofStep], F.Sequent)]((steps.map(_.scps), steps.head.bot)) { (cumul_bot, f_elim) => + val (cumul, bot) = cumul_bot + val (f, elim) = f_elim + val i = cumul.size + val elimSteps = elim(i - 1, bot) + (elimSteps.foldLeft(cumul)((cumul2, step) => step :: cumul2), bot -<< f) + } + + val r = K.SCProof(finalSteps._1.reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + r + } + + def currentSCProof: K.SCProof = K.SCProof(steps.map(_.scps).reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) + + /** + * For a fact, returns the sequent that the fact proove and the position of the fact in the proof. + * + * @param fact Any fact, possibly instantiated, belonging to the proof + * @return its proven sequent and position + */ + def sequentAndIntOfFact(fact: Fact): (F.Sequent, Int) = fact match { + case i: Int => + ( + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + }, + i + ) + case ds: ProofStep => (ds.bot, ds.position) + case instFact: InstantiatedFact => + val r = instantiatedFacts.find(instFact == _._1) + r match { + case Some(value) => (instFact.result, value._2) + case None => + addInstantiatedFact(instFact) + (instFact.result, steps.length - 1) + } + case of: OutsideFact @unchecked => + val r = imports.indexWhere(of == _._1) + if (r != -1) { + (imports(r)._2, r - imports.length) + } else { + val r2 = sequentOfOutsideFact(of) + addImport(of, r2) + (r2, -imports.length) + } + } + + def sequentOfFact(fact: Fact): F.Sequent = fact match { + case i: Int => + if (i >= 0) + if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") + else steps(steps.length - i - 1).bot + else { + val i2 = -(i + 1) + if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") + else imports(imports.length + i)._2 + } + case ds: ProofStep => ds.bot + case instfact: InstantiatedFact => instfact.result + case of: OutsideFact @unchecked => + val r = imports.find(of == _._1) + if (r.nonEmpty) { + r.get._2 + } else { + sequentOfOutsideFact(of) + } + } + + def sequentOfOutsideFact(of: OutsideFact): F.Sequent + + def getSequent(f: Fact): F.Sequent = sequentOfFact(f) + def mostRecentStep: ProofStep = steps.head + + /** + * The number of steps in the proof. This is not the same as the number of steps in the corresponding [[K.SCProof]]. + * This also does not count the number of steps in the subproof. + * + * @return + */ + def length: Int = steps.length + + /** + * The set of symbols that can't be instantiated because they are free in an assumption. + */ + 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. + */ + def asOutsideFact(j: JUSTIFICATION): OutsideFact + + def depth: Int = + (this: @unchecked) match { + case p: Proof#InnerProof => 1 + p.parent.depth + case _: BaseProof => 0 + } + + /** + * Create a subproof inside the current proof. The subproof will have the same assumptions as the current proof. + * Can have a goal known in advance (usually for a user-written subproof) or not (usually for a tactic-generated subproof). + */ + def newInnerProof(possibleGoal: Option[F.Sequent]) = new InnerProof(possibleGoal) + final class InnerProof(val possibleGoal: Option[F.Sequent]) extends Proof(this.getAssumptions) { + val parent: Proof.this.type = Proof.this + val owningTheorem: THM = parent.owningTheorem + type OutsideFact = parent.Fact + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = parent.asOutsideFact(j) + + override def sequentOfOutsideFact(of: parent.Fact): F.Sequent = of match { + case j: JUSTIFICATION => j.statement + case ds: Proof#ProofStep => ds.bot + case _ => parent.sequentOfFact(of) + } + } + + /** + * Contains the result of a tactic computing a K.SCProofTactic. + * Can be successful or unsuccessful. + */ + sealed abstract class ProofTacticJudgement { + val tactic: ProofTactic + val proof: Proof = Proof.this + + /** + * Returns true if and only if the judgement is valid. + */ + def isValid: Boolean = this match { + case ValidProofTactic(_, _, _) => true + case InvalidProofTactic(_) => false + } + + def validate(line: sourcecode.Line, file: sourcecode.File): ProofStep = { + this match { + case vpt: ValidProofTactic => newProofStep(vpt) + case ipt: InvalidProofTactic => + val e = lisa.prooflib.ProofTacticLib.UnapplicableProofTactic(ipt.tactic, ipt.proof, ipt.message)(using line, file) + e.setStackTrace(ipt.stack) + throw e + } + } + } + + /** + * A Kernel Sequent Calculus proof step that has been correctly produced. + */ + case class ValidProofTactic(bot: lisa.fol.FOL.Sequent, scps: Seq[K.SCProofStep], imports: Seq[Fact])(using val tactic: ProofTactic) extends ProofTacticJudgement {} + + /** + * A proof step which led to an error when computing the corresponding K.Sequent Calculus proof step. + */ + case class InvalidProofTactic(message: String)(using val tactic: ProofTactic) extends ProofTacticJudgement { + 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 + override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = j + + override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement + + def justifications: List[JUSTIFICATION] = getImports.map(_._1) + } + + /** + * Abstract class representing theorems, axioms and different kinds of definitions. Corresponds to a [[theory.Justification]]. + */ + sealed abstract class JUSTIFICATION { + + /** + * A pretty representation of the justification + */ + def repr: String + + /** + * The inner kernel justification + */ + def innerJustification: theory.Justification + + /** + * The sequent that the justification proves + */ + def statement: F.Sequent + + /** + * The complete name of the justification. Two justifications should never have the same full name. Typically, path is used to disambiguate. + */ + def fullName: String + + /** + * The short name of the justification (without the path). + */ + val name: String = fullName.split("\\.").last + + /** + * The "owning" object of the justification. Typically, the package/object in which it is defined. + */ + val owner = fullName.split("\\.").dropRight(1).mkString(".") + + /** + * Returns if the statement is unconditionaly proven or if it depends on some sorry step (including in the other justifications it relies on) + */ + def withSorry: Boolean = innerJustification match { + case thm: theory.Theorem => thm.withSorry + case d: theory.Definition => false + case ax: theory.Axiom => false + } + } + + /** + * A Justification, corresponding to [[K.Axiom]] + */ + class AXIOM(innerAxiom: theory.Axiom, val axiom: F.Formula, val fullName: String) extends JUSTIFICATION { + def innerJustification: theory.Axiom = innerAxiom + val statement: F.Sequent = F.Sequent(Set(), Set(axiom)) + if (statement.underlying != theory.sequentFromJustification(innerAxiom)) { + throw new InvalidAxiomException("The provided kernel axiom and desired statement don't match.", name, axiom, library) + } + def repr: String = s" Axiom $name := $axiom" + } + + /** + * Introduces a new axiom in the theory. + * + * @param fullName The name of the axiom, including the path. Usually fetched automatically by the compiler. + * @param axiom The axiomatized formula. + * @return + */ + def Axiom(using fullName: sourcecode.FullName)(axiom: F.Formula): AXIOM = { + val ax: Option[theory.Axiom] = theory.addAxiom(fullName.value, axiom.underlying) + ax match { + case None => throw new InvalidAxiomException("Not all symbols belong to the theory", fullName.value, axiom, library) + case Some(value) => AXIOM(value, axiom, fullName.value) + } + } + + /** + * A Justification, corresponding to [[K.FunctionDefinition]] or [[K.PredicateDefinition]] + */ + abstract class DEFINITION(line: Int, file: String) extends JUSTIFICATION { + val fullName: String + def repr: String = innerJustification.repr + + def cst: F.Constant[?] + knownDefs.update(cst, Some(this)) + + } + + /** + * A proven, reusable statement. A justification corresponding to [[K.Theorem]]. + */ + sealed abstract class THM extends JUSTIFICATION { + def repr: String = + s" Theorem ${name} := ${statement}${if (withSorry) " (!! Relies on Sorry)" else ""}" + + /** + * The underlying Kernel proof [[K.SCProof]], if it is still available. Proofs are not kept in memory for efficiency. + */ + def kernelProof: Option[K.SCProof] + + /** + * The high level [[Proof]], if one was used to obtain the theorem. If the theorem was not produced by such high level proof but directly by a low level one, this is None. + */ + def highProof: Option[BaseProof] + val innerJustification: theory.Theorem + + /** + * A pretty representation of the goal of the theorem + */ + def prettyGoal: String = statement.underlying.repr + } + object THM { + + /** + * Standard way to construct a theorem using a high level proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path. Usually fetched automatically by the compiler. + * @param line The line at which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param file The file in which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param computeProof The proof computation. The proof is built by adding proof steps to the proof object. The proof object is an impicit argument of computeProof, + * @see Context Functions in Scala + * @return + */ + def apply(using om: OutputManager)(statement: F.Sequent, fullName: String, line: Int, file: String, kind: TheoremKind)(computeProof: Proof ?=> Unit) = + THMFromProof(statement, fullName, line, file, kind)(computeProof) + + /** + * Constructs a "high level" theorem from an existing theorem in the + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param innerThm The inner theorem, coming from the kernel + * @param getProof If available, a way to compute the Kernel proof again. + */ + def fromKernel(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) = + THMFromKernel(statement, fullName, kind, innerThm, getProof) + + /** + * Construct a theorem (both in the kernel and high level) from a proof. + * + * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] + * @param statement The statement of the theorem + * @param fullName The full name of the theorem, including the path/package. + * @param kind The kind of theorem (Theorem, Lemma, Corollary) + * @param getProof The kernel proof. + * @param justifs low level justifications used to justify the proof's imports + * @return + */ + def fromSCProof(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, getProof: () => K.SCProof, justifs: Seq[theory.Justification]): THM = + val proof = getProof() + theory.theorem(fullName, statement.underlying, proof, justifs) match { + case K.Judgement.ValidJustification(just) => + fromKernel(statement, fullName, kind, just.asInstanceOf, () => Some(getProof())) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The proof was rejected by LISA's logical kernel. ", + wrongJudgement, + None + ) + ) + } + + } + + /** + * A theorem that was produced from a kernel theorem and not from a high level proof. See [[THM.fromKernel]]. + * Those are typically theorems imported from another tool, or from serialization. + */ + class THMFromKernel(using om: OutputManager)(val statement: F.Sequent, val fullName: String, val kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) extends THM { + + val innerJustification: theory.Theorem = innerThm + assert(innerThm.name == fullName) + def kernelProof: Option[K.SCProof] = getProof() + def highProof: Option[BaseProof] = None + + val goal: F.Sequent = statement + + } + + /** + * A theorem that was produced from a high level proof. See [[THM.apply]]. + * Typical way to construct a theorem in the library, but serialization for example will produce a [[THMFromKernel]]. + */ + class THMFromProof(using om: OutputManager)(val statement: F.Sequent, val fullName: String, line: Int, file: String, val kind: TheoremKind)(computeProof: Proof ?=> Unit) extends THM { + + val goal: F.Sequent = statement + + val proof: BaseProof = new BaseProof(this) + def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) + def highProof: Option[BaseProof] = Some(proof) + + import lisa.utils.Serialization.* + val innerJustification: theory.Theorem = + if library._draft.nonEmpty && library._draft.get.value != file + then // if the draft option is activated, and the theorem is not in the file where the draft option is given, then we replace the proof by sorry + theory.theorem(name, goal.underlying, SCProof(SC.Sorry(goal.underlying)), IndexedSeq.empty) match { + case K.Judgement.ValidJustification(just) => + just + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + else if library._withCache then + oneThmFromFile("cache/" + name, library.theory) match { + case Some(thm) => thm // try to get the theorem from file + + case None => + val (thm, scp, justifs) = prove(computeProof) // if fail, prove it + thmsToFile("cache/" + name, theory, List((name, scp, justifs))) // and save it to the file + thm + } + else prove(computeProof)._1 + + library.last = Some(this) + + /** + * Construct the kernel theorem from the high level proof + */ + private def prove(computeProof: Proof ?=> Unit): (theory.Theorem, SCProof, List[(String, theory.Justification)]) = { + try { + computeProof(using proof) + } catch { + case e: UserLisaException => + om.lisaThrow(e) + } + + if (proof.length == 0) + om.lisaThrow(new UnimplementedProof(this)) + + val scp = proof.toSCProof + val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) + theory.theorem(name, goal.underlying, scp, justifs.map(_._2)) match { + case K.Judgement.ValidJustification(just) => + (just, scp, justifs) + case wrongJudgement: K.Judgement.InvalidJustification[?] => + om.lisaThrow( + LisaException.InvalidKernelJustificationComputation( + "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", + wrongJudgement, + Some(proof) + ) + ) + } + } + + } + + given thmConv: Conversion[library.THM, theory.Theorem] = _.innerJustification + + trait TheoremKind { + val kind2: String + + def apply(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(statement: F.Sequent)(computeProof: Proof ?=> Unit): THM = { + val thm = THM(statement, name.value, line.value, file.value, this)(computeProof) + if this == Theorem then show(thm) + thm + } + + } + + /** + * A "Theorem" kind of theorem, by opposition with a lemma or corollary. The difference is that theorem are always printed when a file defining one is run. + */ + object Theorem extends TheoremKind { val kind2: String = "Theorem" } + + /** + * Lemmas are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Lemma extends TheoremKind { val kind2: String = "Lemma" } + + /** + * Corollaries are like theorems, but are conceptually less importants and are not printed when a file defining one is run. + */ + object Corollary extends TheoremKind { val kind2: String = "Corollary" } + + /** + * Internal statements are internally produced theorems, for example as intermediate step in definitions. + */ + object InternalStatement extends TheoremKind { val kind2: String = "Internal, automatically produced" } + +} diff --git a/build.sbt b/build.sbt index 4a9f57e5..ceadfb1c 100644 --- a/build.sbt +++ b/build.sbt @@ -33,8 +33,7 @@ val commonSettings3 = commonSettings ++ Seq( "-language:implicitConversions", //"-rewrite", "-source", "3.4-migration", "-Wconf:msg=.*will never be selected.*:silent", - "-language:experimental.modularity", - "-source future" + "-language:experimental.modularity" ), javaOptions += "-Xmx10G", 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 2a8ea906..f41ae994 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -389,7 +389,7 @@ object SCProofChecker { * Γ |- φ[e[t/y]/x], Δ * */ - case LeftBeta(b, t1, phi, lambda, t, x) => + case RightBeta(b, t1, phi, lambda, t, x) => val Lambda(y, e) = lambda if (phi.sort != Formula) SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) @@ -415,7 +415,7 @@ object SCProofChecker { * Γ, φ[e[t/y]/x] |- Δ * */ - case RightBeta(b, t1, phi, lambda, t, x) => + case LeftBeta(b, t1, phi, lambda, t, x) => val Lambda(y, e) = lambda if (phi.sort != Formula) SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) 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 00000000..b6438378 --- /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.* +import lisa.utils.parsing.UnreachableException + + + + +/////////////////////////////// +///////// E-graph ///////////// +/////////////////////////////// + +import scala.collection.mutable + + + + + +class EGraphTermsSimp() { + + 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-utils/src/main/scala/lisa/utils/K.scala b/lisa-utils/src/main/scala/lisa/utils/K.scala index c75e7510..1980bc0f 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 ProofPrinter.* } diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 7d12e5dd..bc6a0582 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -579,9 +579,11 @@ object KernelHelpers { 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 Weakening(_, t1) => pretty("Weakening", t1) + case LeftBeta(_, t1, _, _, _, _) => pretty("Left β", t1) + case RightBeta(_, t1, _, _, _, _) => pretty("Right β", t1) case LeftRefl(_, t1, _) => pretty("L. Refl", t1) case RightRefl(_, _) => pretty("R. Refl") case LeftSubstEq(_, t1, t2, _, _, _, _) => pretty("L. SubstEq", t1, t2) diff --git a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala index bb529718..126397ff 100644 --- a/lisa-utils/src/main/scala/lisa/utils/LisaException.scala +++ b/lisa-utils/src/main/scala/lisa/utils/LisaException.scala @@ -6,7 +6,7 @@ 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 @@ -19,6 +19,7 @@ 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 @@ -40,12 +41,14 @@ 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 diff --git a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala index f31567b0..883b02a9 100644 --- a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala +++ b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala @@ -269,6 +269,22 @@ object Serialization { proofDOS.writeByte(weakening) sequentToProofDOS(bot) proofDOS.writeInt(t1) + case LeftBeta(bot, t1, phi, lambda, t, x) => + proofDOS.writeByte(leftBeta) + sequentToProofDOS(bot) + proofDOS.writeInt(t1) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(lambda)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeInt(lineOfExpr(x)) + case RightBeta(bot, t1, phi, lambda, t, x) => + proofDOS.writeByte(rightBeta) + sequentToProofDOS(bot) + proofDOS.writeInt(t1) + proofDOS.writeInt(lineOfExpr(phi)) + proofDOS.writeInt(lineOfExpr(lambda)) + proofDOS.writeInt(lineOfExpr(t)) + proofDOS.writeInt(lineOfExpr(x)) case LeftRefl(bot, t1, fa) => proofDOS.writeByte(leftRefl) sequentToProofDOS(bot) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala index 5d9e9bf7..84802045 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala @@ -1,5 +1,6 @@ package lisa.fol object FOL extends Sequents { - + export lisa.utils.K + export 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 index abf008c8..aa7f76ab 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -96,4 +96,39 @@ trait Predef extends Syntax { new Abs[T, T](asFrontVariable(l.v).asInstanceOf, asFrontExpression(l.body).asInstanceOf)( using new Sort { type Self = T; val underlying = l.sort }) + 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: Seq[K.Expression | Expr[?] | K.Identifier ], base: String): 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): 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, _)) + } + + + + + val f = variable[Formula >>: Term]("f") + val x: Expr[?] = variable[Term]("x") + val y: Expr[F] = variable[Formula]("x") + val g: Expr[Arrow[F, T]] = variable[Formula >>: Term]("g") + val h: Expr[?] = variable[Formula >>: Term]("g") + + } \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala index 656fdb34..0b9f7ea0 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala @@ -5,7 +5,7 @@ package lisa.fol import lisa.prooflib.BasicStepTactic import lisa.prooflib.Library -import lisa.prooflib.ProofTacticLib.ProofTactic +//import lisa.prooflib.ProofTacticLib.ProofTactic import lisa.utils.K @@ -13,8 +13,9 @@ import scala.annotation.showAsInfix trait Sequents extends Predef { - object SequentInstantiationRule extends ProofTactic - given ProofTactic = SequentInstantiationRule + + ??? // TODO object SequentInstantiationRule extends ProofTactic + ??? // TODO given ProofTactic = SequentInstantiationRule 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)) @@ -22,7 +23,7 @@ trait Sequents extends Predef { 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 = + override def substitute(pairs: SubstPair*): Sequent = super.substitute(pairs*).asInstanceOf[Sequent] def freeVars: Set[Variable[?]] = left.flatMap(_.freeVars) ++ right.flatMap(_.freeVars) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 22a3114b..63ad3933 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -23,6 +23,7 @@ trait Syntax { 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 } @@ -38,14 +39,20 @@ trait Syntax { given given_ArrowType[A : Sort as ta, B : Sort as tb]: (IsSort[Arrow[A, B]]) with val underlying = K.Arrow(ta.underlying, tb.underlying) - class SubstPair[T: Sort] private (val _1: Variable[T], val _2: Expr[T]) + 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[T : Sort](_1: Variable[T], _2: Expr[T]) = new SubstPair(_1, _2) + 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[T]] = s => SubstPair(s._1, s._2) + given [T: Sort]: Conversion[(Variable[T], Expr[T]), SubstPair{type S = T}] = s => SubstPair(s._1, s._2) trait LisaObject { @@ -57,7 +64,7 @@ trait Syntax { 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 = + def substitute(pairs: SubstPair*): LisaObject = substituteWithCheck(pairs.view.map(s => (s._1, s._2)).toMap) def freeVars: Set[Variable[?]] @@ -72,19 +79,38 @@ trait Syntax { 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]] + override def substitute(pairs: SubstPair*): Expr[S] = + super.substitute(pairs*).asInstanceOf[Expr[S]] - def unapply[T1, T2](e: Expr[Arrow[T1, T2]]): Option[Expr[T1]] = e match { - case App[T1, T2](f, arg) if f == this => Some(arg) + + def unapply[T1, T2](e: Expr[Arrow[T1, T2]]): Option[Expr[T1]] = (e: @unchecked) match { + case App[T1, T2](f, arg) if f == this => Some(arg) case _ => None } final def defaultMkString(args: Seq[Expr[?]]): String = s"$this(${args.map(a => s"(${a})")})" 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) } - + + 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] + case _ => Expr[?] + */ + + type RetExpr[T] = Expr[?] + class Multiapp(f: Expr[?]): def unapply (e: Expr[?]): Option[Seq[Expr[?]]] = def inner(e: Expr[?]): Option[List[Expr[?]]] = e match @@ -92,7 +118,11 @@ trait Syntax { 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 unfoldAllApp(e:Expr[?]): (Expr[?], List[Expr[?]]) = e match @@ -109,8 +139,8 @@ trait Syntax { 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]] + 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 @@ -135,8 +165,8 @@ trait Syntax { 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]] + 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) @@ -164,6 +194,7 @@ trait Syntax { 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[?]): 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 @@ -187,8 +218,8 @@ trait Syntax { 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]] + 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 @@ -212,8 +243,8 @@ trait Syntax { 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]] + 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 @@ -226,9 +257,6 @@ trait Syntax { } - extension [T1, T2](f: Expr[Arrow[T1, T2]]) { - def apply(using IsSort[T1], IsSort[T2])(arg: Expr[T1]): Expr[T2] = App(f, arg) - } 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 8919c80e..00000000 --- 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/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index fc5a57ee..80f2f998 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -4,11 +4,11 @@ import lisa.prooflib.ProofTacticLib.{_, given} import lisa.prooflib.* import lisa.utils.K import lisa.utils.KernelHelpers.{|- => `K|-`, _} -import lisa.utils.UserLisaException +//import lisa.utils.UserLisaException import lisa.utils.unification.UnificationUtils object BasicStepTactic { - +/* def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { judgement match { case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) @@ -401,14 +401,14 @@ object BasicStepTactic { val in: F.Formula = instantiatedPivot.head val quantifiedPhi: Option[F.Formula] = pivot.find(f => f match { - case g @ F.forall(x, phi) => UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined + case g @ F.forall(x, phi) => ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined case _ => false } ) quantifiedPhi match { case Some(F.forall(x, phi)) => - LeftForall.withParameters(phi, x, UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).get._2.getOrElse(x, x))(premise)(bot) + LeftForall.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, 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. φ.") @@ -908,14 +908,14 @@ object BasicStepTactic { val quantifiedPhi: Option[F.Formula] = pivot.find(f => f match { case g @ F.exists(x, phi) => - UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined + ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined case _ => false } ) quantifiedPhi match { case Some(F.exists(x, phi)) => - RightExists.withParameters(phi, x, UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).get._2.getOrElse(x, x))(premise)(bot) + RightExists.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, 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. φ.") @@ -988,6 +988,68 @@ object BasicStepTactic { */ + object RightBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.left, premiseSequent.left)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.right + phi_redex, premiseSequent.right + phi_normalized) || K.isSameSet(botK.right + phi_normalized, premiseSequent.right + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Right-hand side of the conclusion + φ[λy.e]t/x must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Left-hand side of the conclusion must be the same as the left-hand side of the premise") + } + } + + object LeftBeta extends ProofTactic { + def withParameters(using lib: Library, proof: lib.Proof) + (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + lazy val premiseSequent = proof.getSequent(premise).underlying + lazy val phiK = phi.underlying + lazy val lambdaK = lambda.underlying + lazy val tK = t.underlying + lazy val xK = x.underlying + lazy val botK = bot.underlying + lazy val y = lambda.v.underlying + lazy val e = lambda.body.underlying + if (phi.sort != K.Formula) + return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) + else if (y.sort != t.sort) + return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) + else if (e.sort != x.sort) + return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) + else if (K.isSameSet(botK.right, premiseSequent.right)) { + val redex = lambdaK(tK) + val normalized = K.substituteVariables(e, Map(y -> tK)) + val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) + val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) + if (K.isSameSet(botK.left + phi_redex, premiseSequent.left + phi_normalized) || K.isSameSet(botK.left + phi_normalized, premiseSequent.left + phi_redex)) + return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) + else + return proof.InvalidProofTactic("Left-hand side of the conclusion + φ[λy.e]t/x must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") + } else + return proof.InvalidProofTactic("Right-hand side of the conclusion must be the same as the right-hand side of the premise") + } + } + // Structural rules /** *
@@ -1166,14 +1228,14 @@ object BasicStepTactic {
    * 
*/ object RightSubstEq extends ProofTactic { - def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + def withParametersSimple[T1](using lib: Library, proof: lib.Proof)( + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) } def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) + s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Formula) )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { lazy val premiseSequent1 = proof.getSequent(prem1).underlying lazy val premiseSequent2 = proof.getSequent(prem2).underlying @@ -1384,7 +1446,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) @@ -1416,4 +1477,5 @@ object BasicStepTactic { } } +*/ } diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala index 049b82bf..229b5498 100644 --- a/lisa-utils/src/main/scala/lisa/utils/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} @@ -50,11 +50,11 @@ abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.Pro knownDefs.update(s, None) def getDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = knownDefs.get(label) match { - case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case None => ??? // TODO throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) case Some(value) => value } def getShortDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { - case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case None => ??? // TODO throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) case Some(value) => value } @@ -66,20 +66,6 @@ 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: */ diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala index 64ab76b7..24b9ff25 100644 --- a/lisa-utils/src/main/scala/lisa/utils/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.prooflib.ProofPrinter.prettyProof(value)) + case Some(value) => ??? // TODO output(lisa.prooflib.ProofPrinter.prettyProof(value)) case None => () } output(e.underlying.repr) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala index 96c95137..51f4196d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala @@ -1,12 +1,14 @@ package lisa.prooflib import lisa.kernel.proof.SCProofCheckerJudgement -import lisa.prooflib.BasicStepTactic.SUBPROOF +//import lisa.prooflib.BasicStepTactic.SUBPROOF import lisa.prooflib.Library import lisa.prooflib.* import lisa.utils.* object ProofPrinter { + + /* private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" @@ -125,5 +127,5 @@ object ProofPrinter { 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/utils/prooflib/ProofTacticLib.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala index b9dc82cd..c3613e2f 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala @@ -4,11 +4,12 @@ import lisa.fol.FOL as F import lisa.prooflib.* import lisa.utils.K import lisa.utils.UserLisaException -import lisa.utils.prooflib.ProofPrinter +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" + - ProofPrinter.prettyProof(proof, 2) + "\n" + + ??? // TODO 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:" + - ProofPrinter.prettyProof(failure.proof) + ??? // TODO 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 index 620adb94..b719db25 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -1,12 +1,12 @@ 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.K.Identifier import lisa.utils.LisaException import lisa.utils.UserLisaException import lisa.utils.{_, given} @@ -18,6 +18,7 @@ trait ProofsHelpers { 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 @@ -67,7 +68,7 @@ trait ProofsHelpers { 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)) + case fact: proof.Fact @unchecked => ??? // TODO HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) } /** @@ -87,6 +88,7 @@ trait ProofsHelpers { } } + /* /** * Assume the given formula in all future left hand-side of claimed sequents. @@ -101,7 +103,7 @@ trait ProofsHelpers { */ def assume(using proof: library.Proof)(fs: Formula*): proof.ProofStep = { fs.foreach(f => proof.addAssumption(f)) - have(() |- fs.toSet) by BasicStepTactic.Hypothesis + ??? // TODO have(() |- fs.toSet) by BasicStepTactic.Hypothesis } def thesis(using proof: library.Proof): Sequent = proof.possibleGoal.get @@ -119,13 +121,17 @@ trait ProofsHelpers { } extension (using proof: library.Proof)(fact: proof.Fact) { - infix def of(insts: (F.SubstPair[?] | F.Term)*): proof.InstantiatedFact = { + 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 // @@ -142,63 +148,55 @@ trait ProofsHelpers { } } - type ParamsOf[S] = S match { - case Arrow[s1, s2] => s1 *: ParamsOf[s2] - case _ => S *: EmptyTuple - } - type FromParamList[S, R] = S match { - case EmptyTuple => R - case s *: ss => s >>: FromParamList[ss, R] - } - - class The(val out: Variable[T], val f: Formula)( - val just: JUSTIFICATION - ) - class definitionWithVars[S <: Tuple](val args: Seq[Variable[?]]) { + 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 om: OutputManager, name: sourcecode.FullName, 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.")) - // 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): Constant[?] = - Direct[N](name.value, line.value, file.value)(args, t.out, t.f, t.just).label + - inline infix def -->[R: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(expr: Expr[R]): Constant[FromParamList[S, R]] = - val res: Expr[FromParamList[S, R]] = args.toList.foldRight(expr: Expr[?])((v, acc) => Abs.unsafe(v: Variable[?], acc)).asInstanceOf[Expr[FromParamList[S, R]]] - DirectDefinition[FromParamList[S, R]](name.value, line.value, file.value)(res)(using F.unsafeSortEvidence(res.sort)).label + 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) { - } - //Tuple.Map[S, Variable] - - def DEF(): definitionWithVars[EmptyTuple] = new definitionWithVars[EmptyTuple](Seq()) - def DEF[S1](a: Variable[S1]): definitionWithVars[(S1 *: EmptyTuple)] = - new definitionWithVars(Seq(a)) - def DEF[S1, S2](a: Variable[S1], b: Variable[S2]): definitionWithVars[S1*:S2*:EmptyTuple] = - new definitionWithVars(Seq(a, b)) - def DEF[S1, S2, S3](a: Variable[S1], b: Variable[S2], c: Variable[S3]): definitionWithVars[S1*:S2*:S3*:EmptyTuple] = - new definitionWithVars(Seq(a, b, c)) - def DEF[S1, S2, S3, S4](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4]): definitionWithVars[S1*:S2*:S3*:S4*:EmptyTuple] = - new definitionWithVars(Seq(a, b, c, d)) - def DEF[S1, S2, S3, S4, S5](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4], e: Variable[S5]): definitionWithVars[S1*:S2*:S3*:S4*:S5*:EmptyTuple] = - new definitionWithVars(Seq(a, b, c, d, e)) - def DEF[S1, S2, S3, S4, S5, S6](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4], e: Variable[S5], f: Variable[S6]): definitionWithVars[S1*:S2*:S3*:S4*:S5*:S6*:EmptyTuple] = - new definitionWithVars(Seq(a, b, c, d, e, f)) - def DEF[S1, S2, S3, S4, S5, S6, S7](a: Variable[S1], b: Variable[S2], c: Variable[S3], d: Variable[S4], e: Variable[S5], f: Variable[S6], g: Variable[S7]): definitionWithVars[S1*:S2*:S3*:S4*:S5*:S6*:S7*:EmptyTuple] = - new definitionWithVars(Seq(a, b, c, d, e, f, g)) - + val arity = vars.size - class DirectDefinition[S : Sort](using om: OutputManager)(val fullName: String, line: Int, file: String)(val expr: Expr[S]) extends DEFINITION(line, file) { + lazy val cst: Constant[S] = F.Constant(name) - lazy val vars: Seq[F.Variable[?]] = ??? - val arity = ??? - lazy val label: 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, label.sort) + val ucst = K.Constant(name, cst.sort) val uvars = vars.map(_.underlying) val judgement = theory.makeDefinition(ucst, uexpr, uvars) judgement match { @@ -234,149 +232,91 @@ trait ProofsHelpers { ) } } - - val statement: F.Sequent = () |- Iff(label.applySeq(vars), lambda.body) + val right = expr#@@(vars) + val statement = + if appliedCst.sort == K.Term then + () |- iff.#@(appliedCst).#@(right).asInstanceOf[Formula] + else + () |- equality.#@(appliedCst).#@(right).asInstanceOf[Formula] library.last = Some(this) } - + def dropAllLambdas(s: Expr[?]): Expr[?] = s match { + case Abs(v, body) => dropAllLambdas(body) + case _ => s + } /** - * Allows to make definitions "by unique existance" of a function symbol + * For a list of sequence of variables x, y, z, creates the term with lambdas: + * λx.(λy.(λz. base)) */ - class EpsilonDefinition[S, R](using om: OutputManager)(val fullName: String, line: Int, file: String)( - val vars: Seq[S.Variable[?]], // Tuple.Map[S, Variable], - val out: F.Variable[R], - 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 uvars = vars.map(_.underlyingLabel) - val ucst = K.ConstantFunctionLabel(name, vars.size) - val judgement = theory.makeFunctionDefinition(pr, Seq(j.innerJustification), ucst, out.underlyingLabel, K.LambdaTermFormula(uvars, 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(ucst)) { - om.lisaThrow(UserInvalidDefinitionException(name, s"The symbol ${name} has already been defined and can't be redefined.")) - } - if (!(underf.freeSchematicTermLabels.subsetOf(uvars.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 -- uvars.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 - ) - ) - } - } + 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) - // val label: ConstantTermLabel[N] - val statement: F.Sequent = - () |- F.Forall( - out, - Iff( - equality(label.applySeq(vars), out), - f - ) - ) + /** + * 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)) - library.last = Some(this) + /** + * 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 equality" of a function symbol + * Allows to make definitions "by unique existance" of a symbol. May need debugging */ - 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)) + 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 RightBeta.withParameters(appliedCst === right, lam, vAbs, freshX) + 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(propCst) by BasicStepTactic.RightSubstEq.withParametersSimple[T](appliedCst, body, Seq(), (epsilonVar, inner))(j, lastStep) + ??? + } - } + override def repr: String = + s" ${if (withSorry) " Sorry" else ""} Definition of symbol ${appliedCst} such that ${definingProp.statement})\n" - 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) - } } @@ -385,7 +325,7 @@ trait ProofsHelpers { // Local Definitions // ///////////////////////// - import lisa.utils.parsing.FOLPrinter.prettySCProof + import lisa.utils.K.prettySCProof import lisa.utils.KernelHelpers.apply /** @@ -394,7 +334,7 @@ trait ProofsHelpers { * @param proof * @param id */ - abstract class LocalyDefinedVariable(val proof: library.Proof, id: Identifier) extends Variable(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 @@ -403,40 +343,6 @@ trait ProofsHelpers { // 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. */ @@ -449,5 +355,5 @@ trait ProofsHelpers { checkProof(csc) throw Exception("Proof is not valid: " + message) } - +*/ } diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala index d9a2cd98..2ce8f574 100644 --- a/lisa-utils/src/main/scala/lisa/utils/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(K.Lambda(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/utils/prooflib/WithTheorems.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala index 8173caa1..b765886f 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala @@ -1,8 +1,8 @@ package lisa.prooflib import lisa.kernel.proof.RunningTheory -import lisa.prooflib.ProofTacticLib.ProofTactic -import lisa.prooflib.ProofTacticLib.UnimplementedProof +// import lisa.prooflib.ProofTacticLib.ProofTactic +// import lisa.prooflib.ProofTacticLib.UnimplementedProof import lisa.prooflib.* import lisa.utils.KernelHelpers.{_, given} import lisa.utils.LisaException @@ -25,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 @@ -38,13 +39,13 @@ trait WithTheorems { */ case class InstantiatedFact( fact: Fact, - insts: Seq[F.SubstPair[?] | F.Term] + insts: Seq[F.SubstPair | F.Term] ) { 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) + case sp: F.SubstPair => Right(sp) } val (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) @@ -145,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 @@ -343,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 @@ -356,7 +348,8 @@ trait WithTheorems { override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement - def justifications: List[JUSTIFICATION] = getImports.map(_._1) + def justifications: List[JUSTIFICATION] = ??? // TODO getImports.map(_._1) + */ } /** @@ -438,8 +431,8 @@ trait WithTheorems { val fullName: String def repr: String = innerJustification.repr - def label: F.Constant[?] - knownDefs.update(label, Some(this)) + def cst: F.Constant[?] + knownDefs.update(cst, Some(this)) } @@ -549,7 +542,7 @@ trait WithTheorems { val goal: F.Sequent = statement val proof: BaseProof = new BaseProof(this) - def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) + def kernelProof: Option[K.SCProof] = ??? // TODO Some(proof.toSCProof) def highProof: Option[BaseProof] = Some(proof) import lisa.utils.Serialization.* @@ -591,9 +584,9 @@ trait WithTheorems { case e: UserLisaException => om.lisaThrow(e) } - - if (proof.length == 0) - om.lisaThrow(new UnimplementedProof(this)) +/* + if ??? // TODO (proof.length == 0) + then ??? // TODO om.lisaThrow(new UnimplementedProof(this)) val scp = proof.toSCProof val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) @@ -608,7 +601,8 @@ trait WithTheorems { Some(proof) ) ) - } + }*/ + ??? } } diff --git a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala new file mode 100644 index 00000000..d73d8126 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -0,0 +1,618 @@ +package lisa.utils.unification + +import lisa.fol.FOL.{_, given} +//import lisa.fol.FOLHelpers.* + +//import lisa.kernel.fol.FOL.* +//import lisa.utils.KernelHelpers.{_, given} + +/** + * General utilities for unification, substitution, and rewriting + */ +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 + while (res.isEmpty && iter.hasNext) { + res = f(iter.next()) + } + res + } + + } + + /** + * All the information required for performing rewrites. + */ + case class RewriteContext( + freeFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + freeTermRules: Seq[(Term, Term)] = Seq.empty, + confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenFormulaVars: Set[VariableFormula] = Set.empty, + takenTermVars: Set[Variable] = Set.empty + ) { + private var lastID: Identifier = freshId((takenFormulaVars ++ takenTermVars).map(_.id), "@@rewriteVar@@") + + /** + * Generates a fresh identifier with an internal label `__rewriteVar__`. + * Mutates state. + * + * @return fresh identifier + */ + def freshIdentifier = { + lastID = freshId(Seq(lastID), "@@rewriteVar@@") + lastID + } + + def isFreeVariable(v: Variable) = !takenTermVars.contains(v) + 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 + */ + def updateTo(other: RewriteContext) = + lastID = if (other.lastID.no > lastID.no) other.lastID else lastID + } + + object RewriteContext { + def empty = RewriteContext() + } + + // substitutions + + type TermSubstitution = Map[Variable, Term] + val TermSubstitution = Map // don't abuse pls O.o + + type FormulaSubstitution = Map[VariableFormula, Formula] + 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. + */ + 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. + */ + private def matchTermRecursive(using context: RewriteContext)(reference: Term, template: Term, substitution: TermSubstitution): Option[TermSubstitution] = + if (reference == template) + Some(substitution) + else + template match { + case v @ Variable(id) if context.isFreeVariable(v) => + // different label but substitutable or already correctly set + if (reference != template && reference == substitution.getOrElse(v, reference)) Some(substitution + (v -> reference)) + // same and not already substituted to something else + else if (reference == template && reference == substitution.getOrElse(v, reference)) Some(substitution) + // unsat + else None + // {Constant, Schematic} FunctionLabel + case _ => + if (reference.label != template.label) None + else + (reference.args zip template.args).foldLeft(Option(substitution)) { + case (Some(subs), (r, t)) => matchTermRecursive(r, t, subs) + case (None, _) => None + } + } + + /** + * 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, + ): Option[(FormulaSubstitution, TermSubstitution)] = { + val context = RewriteContext( + takenTermVars = takenTermVariables.toSet, + takenFormulaVars = takenFormulaVariables.toSet + ) + matchFormulaRecursive(using context)(reference, template, FormulaSubstitution.empty, TermSubstitution.empty) + } + + /** + * 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. + */ + private def matchFormulaRecursive(using + context: RewriteContext + )(reference: Formula, template: Formula, formulaSubstitution: FormulaSubstitution, termSubstitution: TermSubstitution): Option[(FormulaSubstitution, TermSubstitution)] = { + if (isSame(reference, template)) + Some((formulaSubstitution, termSubstitution)) + else + (reference, template) match { + case (BinderFormula(labelR, boundR, innerR), BinderFormula(labelT, boundT, innerT)) if labelR == labelT => { + val freshVar = Variable(context.freshIdentifier) + + // add a safety substitution to make sure bound variable isn't substituted, and check instantiated bodies + val innerSubst = matchFormulaRecursive( + innerR.substitute(boundR := freshVar), + innerT.substitute(boundT := freshVar), + formulaSubstitution, + termSubstitution + (freshVar -> freshVar) // dummy substitution to make sure we don't attempt to match this as a variable + ) + + innerSubst match { + case None => innerSubst + case Some((sf, st)) => { + val cleanSubst = (sf, st - freshVar) // remove the dummy substitution we added + + // were any formula substitutions involving the bound variable required? + // if yes, not matchable + if (cleanSubst._1.exists((k, v) => v.freeVariables.contains(freshVar))) None + else Some(cleanSubst) + } + } + } + + case (AppliedConnector(labelR, argsR), AppliedConnector(labelT, argsT)) if labelR == labelT => + if (argsR.length != argsT.length) + // quick discard + None + else { + // recursively check inner formulas + val newSubstitution = (argsR zip argsT).foldLeft(Option(formulaSubstitution, termSubstitution)) { + case (Some(substs), (ref, temp)) => matchFormulaRecursive(ref, temp, substs._1, substs._2) + case (None, _) => None + } + newSubstitution + } + + case (_, template: VariableFormula) => + // can this variable be matched with the reference based on previously known or new substitutions? + if (reference == formulaSubstitution.getOrElse(template, reference)) Some(formulaSubstitution + (template -> reference), termSubstitution) + else if (template == reference && reference == formulaSubstitution.getOrElse(template, reference)) Some(formulaSubstitution, termSubstitution) + else None + + case (AppliedPredicate(labelR, argsR), AppliedPredicate(labelT, argsT)) if labelR == labelT => + if (argsR.length != argsT.length) + // quick discard + None + else { + // our arguments are terms, match them recursively + val newTermSubstitution = (argsR zip argsT).foldLeft(Option(termSubstitution)) { + case (Some(tSubst), (ref, temp)) => matchTermRecursive(ref, temp, tSubst) + case (None, _) => None + } + if (newTermSubstitution.isEmpty) None + else Some(formulaSubstitution, newTermSubstitution.get) + } + case _ => None + } + } + // 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))` + */ + 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)))` + */ + 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 + */ + case class TermRewriteLambda( + termVars: Seq[Variable] = Seq.empty, + termRules: Seq[(Variable, TermRule)] = Seq.empty, + body: Term + ) {} + + /** + * 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, + body: Formula + ) { + + /** + * **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**. + */ + def toLambdaFF: LambdaExpression[Formula, Formula, ?] = LambdaExpression(formulaRules.map(_._1), body, formulaRules.size) + } + + /** + * 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 + */ + 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]]. + */ + 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]]. + */ + 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 + */ + def getContextTerm( + first: Term, + second: Term, + freeTermRules: Seq[(Term, Term)], + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenTermVariables: Set[Variable] = Set.empty + ): Option[TermRewriteLambda] = { + val context = RewriteContext( + takenTermVars = takenTermVariables, + freeTermRules = freeTermRules, + confinedTermRules = confinedTermRules + ) + getContextRecursive(using context)(first, second) + } + + /** + * 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 = + context.confinedTermRules + .getFirst { case (l, r) => + val subst = canRewrite(using context)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + .orElse { + // free all variables for substitution + // matchTermRecursive does not generate any free variables + // so it cannot affect global state, so this is safe to do + val freeContext = context.copy(takenTermVars = Set.empty) + freeContext.freeTermRules.getFirst { case (l, r) => + val subst = canRewrite(using freeContext)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + } + + if (first == second) Some(TermRewriteLambda(body = first)) + else if (validSubstitution.isDefined) { + val newVar = Variable(context.freshIdentifier) + val body = newVar // newVar() + Some( + TermRewriteLambda( + Seq(newVar), + Seq(newVar -> validSubstitution.get), + body + ) + ) + } else if (first.label != second.label || first.args.length != second.args.length) None + else { + // recurse + // known: first.label == second.label + // first.args.length == second.args.length + // and first cannot be rewritten into second + val innerSubstitutions = (first.args zip second.args).map(arg => getContextRecursive(using context)(arg._1, arg._2)) + + if (innerSubstitutions.exists(_.isEmpty)) None + else { + val retrieved = innerSubstitutions.map(_.get) + val body = first.label.applySeq(retrieved.map(_.body)) + val lambda = + retrieved.foldLeft(TermRewriteLambda(body = body)) { case (currentLambda, nextLambda) => + TermRewriteLambda( + currentLambda.termVars ++ nextLambda.termVars, + currentLambda.termRules ++ nextLambda.termRules, + currentLambda.body + ) + } + Some(lambda) + } + } + } + + /** + * 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, + freeTermRules: Seq[(Term, Term)] = Seq.empty, + freeFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenTermVariables: Set[Variable] = Set.empty, + confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + takenFormulaVariables: Set[VariableFormula] = Set.empty + ): Option[FormulaRewriteLambda] = { + val context = RewriteContext( + takenTermVars = takenTermVariables, + takenFormulaVars = takenFormulaVariables, + freeTermRules = freeTermRules, + confinedTermRules = confinedTermRules, + freeFormulaRules = freeFormulaRules, + confinedFormulaRules = confinedFormulaRules + ) + getContextRecursive(using context)(first, second) + } + + def getContextFormulaSet( + first: Seq[Formula], + second: Seq[Formula], + freeTermRules: Seq[(Term, Term)], + freeFormulaRules: Seq[(Formula, Formula)], + confinedTermRules: Seq[(Term, Term)] = Seq.empty, + takenTermVariables: Set[Variable] = Set.empty, + confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, + takenFormulaVariables: Set[VariableFormula] = Set.empty + ): Option[Seq[FormulaRewriteLambda]] = { + val context = RewriteContext( + takenTermVars = takenTermVariables, + takenFormulaVars = takenFormulaVariables, + freeTermRules = freeTermRules, + confinedTermRules = confinedTermRules, + freeFormulaRules = freeFormulaRules, + confinedFormulaRules = confinedFormulaRules + ) + + val substSeq = first.map { f => + second.getFirst { s => + val newContext = context.copy() + val subst = getContextRecursive(using newContext)(f, s) + subst.foreach { _ => context.updateTo(newContext) } + subst + } + } + + // Seq[Option[_]] -> Option[Seq[_]] + substSeq.foldLeft(Option(Seq.empty[FormulaRewriteLambda]))((f, s) => f.flatMap(f1 => s.map(s1 => f1 :+ s1))) + } + + /** + * 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 = + context.confinedFormulaRules + .getFirst { (l: Formula, r: Formula) => + val subst = canRewrite(using context)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + .orElse { + // free all variables for substitution + // matchFormulaRecursive generates but does not expose any new variables + // It cannot affect global state, so this is safe to do + val freeContext = context.copy(takenTermVars = Set.empty) + freeContext.freeFormulaRules.getFirst { case (l, r) => + val subst = canRewrite(using freeContext)(first, second, (l, r)) + subst.map(s => ((l, r), s)) + } + } + + if (isSame(first, second)) Some(FormulaRewriteLambda(body = first)) + else if (validSubstitution.isDefined) { + val newVar = VariableFormula(context.freshIdentifier) + val body = newVar // newVar() + Some( + FormulaRewriteLambda( + Seq(), + Seq(newVar -> validSubstitution.get), + body + ) + ) + } // else if (first.label != second.label) None //Should not pass the next match anyway + else { + // recurse + // known: first.label == second.label + // and first cannot be rewritten into second + (first, second) match { + case (BinderFormula(labelF, boundF, innerF), BinderFormula(labelS, boundS, innerS)) => { + val freshVar = Variable(context.freshIdentifier) + val freeContext = context.copy(takenTermVars = context.takenTermVars + freshVar) + + // add a safety substitution to make sure bound variable isn't substituted, and check instantiated bodies + val innerSubst = getContextRecursive(using freeContext)( + innerF.substitute(boundF := freshVar), + innerS.substitute(boundS := freshVar) + ) + + context.updateTo(freeContext) + + innerSubst.map(s => s.copy(body = BinderFormula(labelF, freshVar, s.body))) + } + + case (AppliedConnector(labelF, argsF), AppliedConnector(labelS, argsS)) => + if (argsF.length != argsS.length) + // quick discard + None + else { + // recursively check inner formulas + val innerSubstitutions = (argsF zip argsS).map(arg => getContextRecursive(using context)(arg._1, arg._2)) + + if (innerSubstitutions.exists(_.isEmpty)) None + else { + val retrieved = innerSubstitutions.map(_.get) + val body = AppliedConnector(labelF, retrieved.map(_.body)) + val lambda = + retrieved.foldLeft(FormulaRewriteLambda(body = body)) { case (currentLambda, nextLambda) => + FormulaRewriteLambda( + currentLambda.termRules ++ nextLambda.termRules, + currentLambda.formulaRules ++ nextLambda.formulaRules, + currentLambda.body + ) + } + Some(lambda) + } + } + + case (AppliedPredicate(labelF, argsF), AppliedPredicate(labelS, argsS)) => + if (argsF.length != argsS.length) + // quick discard + None + else { + // our arguments are terms, get contexts from them recursively + val innerSubstitutions = (argsF zip argsS).map(arg => getContextRecursive(using context)(arg._1, arg._2)) + + if (innerSubstitutions.exists(_.isEmpty)) None + else { + val retrieved = innerSubstitutions.map(_.get) + val body = AppliedPredicate(labelF, retrieved.map(_.body)) + val lambda = + retrieved.foldLeft(FormulaRewriteLambda(body = body)) { case (currentLambda, nextLambda) => + FormulaRewriteLambda( + currentLambda.termRules ++ nextLambda.termRules, + currentLambda.formulaRules, + currentLambda.body + ) + } + Some(lambda) + } + } + case _ => None + } + } + } +*/ +} From 02665e8dcfbb630665a694ea5fe22e6ad4269add Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Thu, 17 Oct 2024 10:28:47 +0200 Subject: [PATCH 13/92] all of utils except for UnificationUtils ported. Next, tests. --- backup/backup2/prooflib/BasicStepTactic.scala | 4 +-- backup/backup2/prooflib/ProofsHelpers.scala | 4 +-- .../main/scala/lisa/utils/fol/Sequents.scala | 9 ++---- .../lisa/utils/prooflib/BasicStepTactic.scala | 4 +-- .../scala/lisa/utils/prooflib/Library.scala | 4 +-- .../lisa/utils/prooflib/OutputManager.scala | 2 +- .../lisa/utils/prooflib/ProofPrinter.scala | 5 +-- .../lisa/utils/prooflib/ProofTacticLib.scala | 8 ++--- .../lisa/utils/prooflib/ProofsHelpers.scala | 12 +++---- .../lisa/utils/prooflib/WithTheorems.scala | 32 +++++++++---------- 10 files changed, 37 insertions(+), 47 deletions(-) diff --git a/backup/backup2/prooflib/BasicStepTactic.scala b/backup/backup2/prooflib/BasicStepTactic.scala index 7b4c3c2c..fdda8257 100644 --- a/backup/backup2/prooflib/BasicStepTactic.scala +++ b/backup/backup2/prooflib/BasicStepTactic.scala @@ -401,7 +401,7 @@ object BasicStepTactic { val in: F.Formula = instantiatedPivot.head val quantifiedPhi: Option[F.Formula] = pivot.find(f => f match { - case g @ F.forall(x, phi) => ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + case g @ F.forall(x, phi) => UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined case _ => false } ) @@ -908,7 +908,7 @@ object BasicStepTactic { val quantifiedPhi: Option[F.Formula] = pivot.find(f => f match { case g @ F.exists(x, phi) => - ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined + UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined case _ => false } ) diff --git a/backup/backup2/prooflib/ProofsHelpers.scala b/backup/backup2/prooflib/ProofsHelpers.scala index d9fdfe59..46d75d7f 100644 --- a/backup/backup2/prooflib/ProofsHelpers.scala +++ b/backup/backup2/prooflib/ProofsHelpers.scala @@ -67,7 +67,7 @@ trait ProofsHelpers { 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 => ??? // TODO HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) + case fact: proof.Fact @unchecked => HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) } /** @@ -102,7 +102,7 @@ trait ProofsHelpers { */ def assume(using proof: library.Proof)(fs: Formula*): proof.ProofStep = { fs.foreach(f => proof.addAssumption(f)) - ??? // TODO have(() |- fs.toSet) by BasicStepTactic.Hypothesis + have(() |- fs.toSet) by BasicStepTactic.Hypothesis } def thesis(using proof: library.Proof): Sequent = proof.possibleGoal.get diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala index 0b9f7ea0..b09ed3b5 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala @@ -1,11 +1,8 @@ package lisa.fol -//import lisa.kernel.proof.SequentCalculus.Sequent - - import lisa.prooflib.BasicStepTactic import lisa.prooflib.Library -//import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.prooflib.ProofTacticLib.ProofTactic import lisa.utils.K @@ -14,8 +11,8 @@ import scala.annotation.showAsInfix trait Sequents extends Predef { - ??? // TODO object SequentInstantiationRule extends ProofTactic - ??? // TODO given ProofTactic = SequentInstantiationRule + object SequentInstantiationRule extends ProofTactic + given ProofTactic = SequentInstantiationRule 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)) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index 80f2f998..81204620 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -8,7 +8,7 @@ import lisa.utils.KernelHelpers.{|- => `K|-`, _} import lisa.utils.unification.UnificationUtils object BasicStepTactic { -/* + def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { judgement match { case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) @@ -1477,5 +1477,5 @@ object BasicStepTactic { } } -*/ + } diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala index 229b5498..0a677f30 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/Library.scala @@ -50,11 +50,11 @@ abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.Pro knownDefs.update(s, None) def getDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = knownDefs.get(label) match { - case None => ??? // TODO throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) case Some(value) => value } def getShortDefinition(label: F.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { - case None => ??? // TODO throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) + case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) case Some(value) => value } diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/OutputManager.scala index 24b9ff25..bc7442ef 100644 --- a/lisa-utils/src/main/scala/lisa/utils/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) => ??? // TODO output(lisa.prooflib.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/prooflib/ProofPrinter.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala index 51f4196d..09cc6d8d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofPrinter.scala @@ -1,14 +1,13 @@ package lisa.prooflib import lisa.kernel.proof.SCProofCheckerJudgement -//import lisa.prooflib.BasicStepTactic.SUBPROOF +import lisa.prooflib.BasicStepTactic.SUBPROOF import lisa.prooflib.Library import lisa.prooflib.* import lisa.utils.* object ProofPrinter { - /* private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" @@ -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/utils/prooflib/ProofTacticLib.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala index c3613e2f..c398c880 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofTacticLib.scala @@ -9,7 +9,7 @@ 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. */ @@ -47,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" + - ??? // TODO 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 @@ -61,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:" + - ??? // TODO 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 index b719db25..9e499d7d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -18,7 +18,6 @@ trait ProofsHelpers { 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 @@ -68,7 +67,7 @@ trait ProofsHelpers { 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 => ??? // TODO HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) + case fact: proof.Fact @unchecked => HaveSequent(proof.sequentOfFact(fact)).by(using proof, line, file)(Rewrite(using library, proof)(fact)) } /** @@ -103,7 +102,7 @@ trait ProofsHelpers { */ def assume(using proof: library.Proof)(fs: Formula*): proof.ProofStep = { fs.foreach(f => proof.addAssumption(f)) - ??? // TODO have(() |- fs.toSet) by BasicStepTactic.Hypothesis + have(() |- fs.toSet) by BasicStepTactic.Hypothesis } def thesis(using proof: library.Proof): Sequent = proof.possibleGoal.get @@ -128,10 +127,7 @@ trait ProofsHelpers { } def currentProof(using p: library.Proof): Library#Proof = p -*/ - -/* //////////////////////////////////////// // DSL for definitions and theorems // @@ -309,7 +305,7 @@ trait ProofsHelpers { 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 existsStep = ??? // have(propEpsilon) by // prop(body) val s3 = have(propCst) by BasicStepTactic.RightSubstEq.withParametersSimple[T](appliedCst, body, Seq(), (epsilonVar, inner))(j, lastStep) ??? } @@ -355,5 +351,5 @@ trait ProofsHelpers { checkProof(csc) throw Exception("Proof is not valid: " + message) } -*/ + } diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala index b765886f..bf0fed20 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/WithTheorems.scala @@ -1,8 +1,8 @@ package lisa.prooflib import lisa.kernel.proof.RunningTheory -// import lisa.prooflib.ProofTacticLib.ProofTactic -// import lisa.prooflib.ProofTacticLib.UnimplementedProof +import lisa.prooflib.ProofTacticLib.ProofTactic +import lisa.prooflib.ProofTacticLib.UnimplementedProof import lisa.prooflib.* import lisa.utils.KernelHelpers.{_, given} import lisa.utils.LisaException @@ -25,7 +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 @@ -43,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) @@ -333,14 +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 @@ -348,8 +348,8 @@ trait WithTheorems { override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement - def justifications: List[JUSTIFICATION] = ??? // TODO getImports.map(_._1) - */ + def justifications: List[JUSTIFICATION] = getImports.map(_._1) + } /** @@ -542,7 +542,7 @@ trait WithTheorems { val goal: F.Sequent = statement val proof: BaseProof = new BaseProof(this) - def kernelProof: Option[K.SCProof] = ??? // TODO Some(proof.toSCProof) + def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) def highProof: Option[BaseProof] = Some(proof) import lisa.utils.Serialization.* @@ -584,9 +584,9 @@ trait WithTheorems { case e: UserLisaException => om.lisaThrow(e) } -/* - if ??? // TODO (proof.length == 0) - then ??? // TODO om.lisaThrow(new UnimplementedProof(this)) + + if (proof.length == 0) + then om.lisaThrow(new UnimplementedProof(this)) val scp = proof.toSCProof val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) @@ -601,8 +601,8 @@ trait WithTheorems { Some(proof) ) ) - }*/ - ??? + } + } } From 22c3de61e18fec8219ac20bee42651bbbf7c58e3 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:14:36 +0200 Subject: [PATCH 14/92] Seal `fol.Expr` --- lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 63ad3933..9f6f259b 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -71,7 +71,7 @@ trait Syntax { def freeTermVars: Set[Variable[T]] def constants: Set[Constant[?]] } - trait Expr[S: Sort] extends LisaObject { + sealed trait Expr[S: Sort] extends LisaObject { val sort: K.Sort = summon[IsSort[S]].underlying private val arity = K.flatTypeParameters(sort).size def underlying: K.Expression From 9239dce43bac7b90ad6b1ac06003c7ab33b2a259 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:15:47 +0200 Subject: [PATCH 15/92] Basic matching --- .../utils/unification/UnificationUtils.scala | 52 ++++++++++++++++--- 1 file changed, 46 insertions(+), 6 deletions(-) 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 d73d8126..0511e88a 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -1,15 +1,55 @@ package lisa.utils.unification import lisa.fol.FOL.{_, given} -//import lisa.fol.FOLHelpers.* - -//import lisa.kernel.fol.FOL.* -//import lisa.utils.KernelHelpers.{_, given} /** * General utilities for unification, substitution, and rewriting */ -object UnificationUtils { +object UnificationUtils: + + class Substitution: + def apply[A](v: Variable[A]): Option[Expr[A]] = ??? + + def +[A](mapping: (Variable[A], Expr[A])): Substitution = ??? + + def contains[A](v: Variable[A]): Boolean = ??? + + object Substitution: + def empty: Substitution = ??? + + def matchExpr[A](expr: Expr[A], pattern: Expr[A], subst: Substitution = Substitution.empty): Option[Substitution] = + if expr eq pattern then + Some(subst) + else + (expr, pattern) match + case (v @ Variable(_), _) => + subst(v) match + case Some(e) => if 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 are 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)) + val inner = matchExpr( + fe.substitute(ve := freshVar), + fp.substitute(vp := freshVar), + subst + ) + // did the bound variable need to be assigned? + // if yes, fail + inner.filterNot(_.contains(freshVar)) + + case _ => None + +end UnificationUtils + +// object UnificationUtils { /* extension [A](seq: Seq[A]) { @@ -615,4 +655,4 @@ object UnificationUtils { } } */ -} +// } From 7a9e672b11c9207e1398bc8490fcff6c422a26d6 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:26:03 +0200 Subject: [PATCH 16/92] Document matchExpr --- .../lisa/utils/unification/UnificationUtils.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 0511e88a..34a2c828 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -17,6 +17,17 @@ object UnificationUtils: object Substitution: def empty: Substitution = ??? + /** + * 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. + * + * @param expr the reference term (to substitute in) + * @param pattern the pattern to match against + * @param subst partial substitution to match under + * @return substitution (Option) from variables to terms. `None` iff a + * substitution does not exist. + */ def matchExpr[A](expr: Expr[A], pattern: Expr[A], subst: Substitution = Substitution.empty): Option[Substitution] = if expr eq pattern then Some(subst) From a01ac264e1e90475efe499d17e4f04f543738ea8 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:26:19 +0200 Subject: [PATCH 17/92] Implement Substitution --- .../utils/unification/UnificationUtils.scala | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) 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 34a2c828..126f176e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -7,15 +7,32 @@ import lisa.fol.FOL.{_, given} */ object UnificationUtils: - class Substitution: - def apply[A](v: Variable[A]): Option[Expr[A]] = ??? - - def +[A](mapping: (Variable[A], Expr[A])): Substitution = ??? - - def contains[A](v: Variable[A]): Boolean = ??? + /** + * Immutable representation of a typed variable substitution. + * + * Types are discarded for storage but are guaranteed to be sound by + * construction. + * + * @param assignments mappings to initialize the substitution with + */ + class Substitution private (assignments: Map[Variable[?], Expr[?]]): + private val underlying: Map[Variable[?], Expr[?]] = assignments + + /** (Optionally) retrieves a variable's mapping */ + def apply[A](v: Variable[A]): Option[Expr[A]] = + underlying.get(v).map(_.asInstanceOf) + + /** Creates a new subtitution with a new mapping added */ + def +[A](mapping: (Variable[A], Expr[A])): Substitution = + Substitution(underlying + mapping) + + /** Checks whether a variable is assigned by this subtitution */ + def contains[A](v: Variable[A]): Boolean = + underlying.contains(v) object Substitution: - def empty: Substitution = ??? + /** The empty substitution */ + def empty: Substitution = Substitution(Map.empty) /** * Performs first-order matching for two terms. Returns a (most-general) From b5c8bd2fa8dce6a31c0545e265f12b1ef806a777 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:40:26 +0200 Subject: [PATCH 18/92] Define a rewrite context --- .../utils/unification/UnificationUtils.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 126f176e..7bd32b37 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -7,6 +7,24 @@ import lisa.fol.FOL.{_, given} */ object UnificationUtils: + /** + * 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[?]] + ): + def isFree[A](v: Variable[A]) = !isBound(v) + def isBound[A](v: Variable[A]) = boundVariables.contains(v) + def bind[A](v: Variable[A]) = this.copy(boundVariables = boundVariables + v) + + object RewriteContext: + def empty = RewriteContext(Set.empty) + def withBound(vars: Iterable[Variable[?]]) = + RewriteContext(vars.toSet) + /** * Immutable representation of a typed variable substitution. * From db8f973a28cfd5b937b7dc131a9b39cda14af47b Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:41:06 +0200 Subject: [PATCH 19/92] Use OL eq, correctly check whether subst respects bound vars --- .../utils/unification/UnificationUtils.scala | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) 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 7bd32b37..6523b13c 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -48,6 +48,16 @@ object UnificationUtils: def contains[A](v: Variable[A]): Boolean = underlying.contains(v) + /** + * Checks whether any susbtitution contains the given variable. Needed for + * verifying ill-formed substitutions containing bound variables. + * + * 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 = + underlying.values.exists(_.freeVars.contains(v)) + object Substitution: /** The empty substitution */ def empty: Substitution = Substitution(Map.empty) @@ -63,14 +73,21 @@ object UnificationUtils: * @return substitution (Option) from variables to terms. `None` iff a * substitution does not exist. */ - def matchExpr[A](expr: Expr[A], pattern: Expr[A], subst: Substitution = Substitution.empty): Option[Substitution] = - if expr eq pattern then + 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(_), _) => + case (v @ Variable(_), _) if ctx.isFree(v) => subst(v) match - case Some(e) => if e == pattern then Some(subst) else None + 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)) @@ -82,14 +99,11 @@ object UnificationUtils: case (Abs(ve, fe), Abs(vp, fp)) => val freshVar = ve.freshRename(Seq(fe, fp)) - val inner = matchExpr( + matchExpr(using ctx.bind(freshVar))( fe.substitute(ve := freshVar), fp.substitute(vp := freshVar), subst - ) - // did the bound variable need to be assigned? - // if yes, fail - inner.filterNot(_.contains(freshVar)) + ).filterNot(_.substitutes(freshVar)) case _ => None From c76eb215831232e67398a14af04cf08324d7c5f3 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:42:49 +0200 Subject: [PATCH 20/92] scalafmt --- .../utils/unification/UnificationUtils.scala | 408 +++++++++--------- 1 file changed, 208 insertions(+), 200 deletions(-) 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 6523b13c..17f29907 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -8,13 +8,13 @@ import lisa.fol.FOL.{_, given} object UnificationUtils: /** - * Context containing information and constraints pertaining to matching, - * unification, and rewriting. - * - * @param boundVariables variables in terms that cannot be substituted - */ + * 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[?]] + boundVariables: Set[Variable[?]] ): def isFree[A](v: Variable[A]) = !isBound(v) def isBound[A](v: Variable[A]) = boundVariables.contains(v) @@ -26,40 +26,48 @@ object UnificationUtils: RewriteContext(vars.toSet) /** - * Immutable representation of a typed variable substitution. - * - * Types are discarded for storage but are guaranteed to be sound by - * construction. - * - * @param assignments mappings to initialize the substitution with - */ + * Immutable representation of a typed variable substitution. + * + * Types are discarded for storage but are guaranteed to be sound by + * construction. + * + * @param assignments mappings to initialize the substitution with + */ class Substitution private (assignments: Map[Variable[?], Expr[?]]): private val underlying: Map[Variable[?], Expr[?]] = assignments - - /** (Optionally) retrieves a variable's mapping */ - def apply[A](v: Variable[A]): Option[Expr[A]] = + + /** + * (Optionally) retrieves a variable's mapping + */ + def apply[A](v: Variable[A]): Option[Expr[A]] = underlying.get(v).map(_.asInstanceOf) - /** Creates a new subtitution with a new mapping added */ - def +[A](mapping: (Variable[A], Expr[A])): Substitution = + /** + * Creates a new subtitution with a new mapping added + */ + def +[A](mapping: (Variable[A], Expr[A])): Substitution = Substitution(underlying + mapping) - /** Checks whether a variable is assigned by this subtitution */ - def contains[A](v: Variable[A]): Boolean = + /** + * Checks whether a variable is assigned by this subtitution + */ + def contains[A](v: Variable[A]): Boolean = underlying.contains(v) - /** + /** * Checks whether any susbtitution contains the given variable. Needed for * verifying ill-formed substitutions containing bound variables. * * Eg: if `v` is externally bound, then `x` and `f(v)` have no matcher under - * capture avoiding substitution. + * capture avoiding substitution. */ def substitutes[A](v: Variable[A]): Boolean = underlying.values.exists(_.freeVars.contains(v)) object Substitution: - /** The empty substitution */ + /** + * The empty substitution + */ def empty: Substitution = Substitution(Map.empty) /** @@ -82,9 +90,9 @@ object UnificationUtils: Some(subst) else (expr, pattern) match - case (v @ Variable(_), _) if ctx.isFree(v) => + case (v @ Variable(_), _) if ctx.isFree(v) => subst(v) match - case Some(e) => + 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 @@ -100,9 +108,9 @@ object UnificationUtils: case (Abs(ve, fe), Abs(vp, fp)) => val freshVar = ve.freshRename(Seq(fe, fp)) matchExpr(using ctx.bind(freshVar))( - fe.substitute(ve := freshVar), - fp.substitute(vp := freshVar), - subst + fe.substitute(ve := freshVar), + fp.substitute(vp := freshVar), + subst ).filterNot(_.substitutes(freshVar)) case _ => None @@ -114,13 +122,13 @@ end 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 - */ + * 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 @@ -133,8 +141,8 @@ end 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, @@ -146,11 +154,11 @@ end 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 @@ -160,11 +168,11 @@ end 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 } @@ -182,34 +190,34 @@ end 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 susbtitutions 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) @@ -233,20 +241,20 @@ end 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, @@ -260,17 +268,17 @@ end 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 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. + */ private def matchFormulaRecursive(using context: RewriteContext )(reference: Formula, template: Formula, formulaSubstitution: FormulaSubstitution, termSubstitution: TermSubstitution): Option[(FormulaSubstitution, TermSubstitution)] = { @@ -340,37 +348,37 @@ end 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, @@ -378,21 +386,21 @@ end 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, @@ -400,61 +408,61 @@ end 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, @@ -471,12 +479,12 @@ end 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 = @@ -533,25 +541,25 @@ end 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, @@ -606,12 +614,12 @@ end 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 = @@ -714,5 +722,5 @@ end UnificationUtils } } } -*/ + */ // } From 1eec81685ae188d15228ef4c8e46003dc5cfe638 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 18 Oct 2024 13:44:07 +0200 Subject: [PATCH 21/92] typo --- .../main/scala/lisa/utils/unification/UnificationUtils.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 17f29907..0acbd5fd 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -100,7 +100,7 @@ object UnificationUtils: // first encounter Some(subst + (v -> pattern)) case (App(fe, arge), App(fp, argp)) if fe.sort == fp.sort => - // the sort of fp are already runtime checked here; the sort of argp + // 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)) From aa4045d338fbfcd84cfcf745ea1081690a9dbda0 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Fri, 18 Oct 2024 17:03:27 +0200 Subject: [PATCH 22/92] lisa-utils tests compile --- .../main/scala/lisa/utils/KernelHelpers.scala | 28 +- .../src/main/scala/lisa/utils/fol/FOL.scala | 4 +- .../main/scala/lisa/utils/fol/Predef.scala | 10 +- .../main/scala/lisa/utils/fol/Syntax.scala | 9 - .../test/scala/lisa/ProofCheckerSuite.scala | 41 +-- .../test/scala/lisa/ProofTacticTestLib.scala | 5 +- .../test/scala/lisa/TestTheoryAxioms.scala | 20 +- .../test/scala/lisa/TestTheoryLibrary.scala | 13 +- .../lisa/kernel/EquivalenceCheckerTests.scala | 124 +++---- .../src/test/scala/lisa/kernel/FolTests.scala | 16 +- .../lisa/kernel/IncorrectProofsTests.scala | 15 +- .../test/scala/lisa/kernel/ProofTests.scala | 322 +++++++++++------- .../scala/lisa/kernel/SubstitutionTest.scala | 213 ++++-------- .../lisa/kernel/TheoriesHelpersTest.scala | 7 +- .../test/scala/lisa/utils/ParserTest.scala | 8 +- .../test/scala/lisa/utils/PrinterTest.scala | 165 ++++----- .../src/test/scala/lisa/utils/TestUtils.scala | 27 +- 17 files changed, 509 insertions(+), 518 deletions(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index bc6a0582..5b02367f 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -22,6 +22,10 @@ object KernelHelpers { /* Prefix syntax */ + extension (s: Sort) { + def >>:(t: Sort) : Sort = Arrow(t, s) + } + val Equality = equality val === = equality val ⊤ : Expression = top @@ -92,6 +96,7 @@ object KernelHelpers { } 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) @@ -199,7 +204,18 @@ object KernelHelpers { def fullRepr: String = s"${s.left.map(_.fullRepr).mkString(", ")} |- ${s.right.map(_.fullRepr).mkString(", ")}" } - // TODO: Should make less generic + def betaReduce(e: Expression): Expression = e match { + case Application(f, arg) => + val f1 = betaReduce(f) + val a2 = betaReduce(arg) + f1 match + case Lambda(v, body) => betaReduce(substituteVariables(body, Map(v -> a2))) + case _ => Application(f1, betaReduce(a2)) + case Lambda(v, inner) => + Lambda(v, betaReduce(inner)) + case _ => e + } + /** * Represents a converter of some object into a set. * @tparam S The type of elements in that set @@ -320,17 +336,25 @@ object KernelHelpers { 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 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 HOvariable(using name: sourcecode.Name)(sort: Sort): Variable = Variable(name.value, sort) def variable(using name: sourcecode.Name): Variable = Variable(name.value, Term) - def v(id: Identifier, sort:Sort): Variable = Variable(id, sort) 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 diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala index 84802045..dfc4d36e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/FOL.scala @@ -1,6 +1,6 @@ package lisa.fol object FOL extends Sequents { - export lisa.utils.K - export K.Identifier + //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 index aa7f76ab..6f89cbef 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -15,6 +15,7 @@ trait Predef extends Syntax { 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) + val equality = constant[Term >>: Term >>: Formula]("===") val === = equality @@ -122,13 +123,4 @@ trait Predef extends Syntax { } - - - val f = variable[Formula >>: Term]("f") - val x: Expr[?] = variable[Term]("x") - val y: Expr[F] = variable[Formula]("x") - val g: Expr[Arrow[F, T]] = variable[Formula >>: Term]("g") - val h: Expr[?] = variable[Formula >>: Term]("g") - - } \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 63ad3933..c8723166 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -258,15 +258,6 @@ trait Syntax { - - - private val x = Variable[T]("x") - private val y = Variable[F]("x") - private val z: Expr[Arrow[T, F]] = Variable("x") - z(x) - - - } diff --git a/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala b/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala index 5b14743b..0f11cba1 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 = { 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 ebecd9b3..3c6db36a 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 aeaa403d..259ad336 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 87b0cf73..c76e6a2a 100644 --- a/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala +++ b/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala @@ -7,19 +7,18 @@ 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 anotherFixed = constant[Term] addSymbol(p1) addSymbol(p2) addSymbol(f1) addSymbol(fixedElement) addSymbol(anotherFixed) - 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 diff --git a/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala b/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala index 81a39258..5034fc0e 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 = + def addDoubleNegations(p: Double)(random: Random)(f: Expression): Expression = { + def transform(f: Expression): Expression = if (random.nextDouble() < p) 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 index bc769dd8..5a986a39 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/FolTests.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/FolTests.scala @@ -7,7 +7,6 @@ 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 @@ -15,6 +14,7 @@ import scala.language.adhocExtensions import scala.util.Random class FolTests extends AnyFunSuite { + /* val predicateVerifier = SCProofChecker.checkSCProof @@ -23,27 +23,27 @@ class FolTests extends AnyFunSuite { else candidates(gen.between(0, candidates.length)) } - def termGenerator(maxDepth: Int, gen: Random = new Random()): Term = { + def termGenerator(maxDepth: Int, gen: Random = new Random()): Expression = { 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()) + Constant(name, Term) } else { val name = "" + ('v' to 'z')(gen.between(0, 5)) - VariableTerm(VariableLabel(name)) + Variable(name, Term) } } 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()) + Constant(name, Term) } else if (r == 1) { val name = "" + ('v' to 'z')(gen.between(0, 5)) - VariableTerm(VariableLabel(name)) + Variable(name, Term) } - if (r <= 3) Term(ConstantFunctionLabel(name, 1), Seq(termGenerator(maxDepth - 1, gen))) + else 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 @@ -116,4 +116,6 @@ class FolTests extends AnyFunSuite { 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 8a9ba63a..1abd0458 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, 1, 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, 1, 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, 1, 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, 1, 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, 1, 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 f86a1813..d1caca4b 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,56 +46,75 @@ 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, 1, x, y, Seq(), (sT, fp(sT))) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) } { 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))), + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t2 = LeftSubstEq( + Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), 0, - List((LambdaTermTerm(Seq(x), f(x)), LambdaTermTerm(Seq(x), g(x, x)))), - (Seq(f2), exists(x, fp(f2(x)))) + 1, + f, lambda(x, g(x, x)), + Seq(y), + (f2, exists(x, fp(f2(x)))) ) - 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 = LeftSubstEq( + val t3 = LeftBeta( Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), - 0, - List((LambdaTermTerm(Seq(y), f(y)), LambdaTermTerm(Seq(z), g(z, z)))), - (Seq(f2), exists(x, fp(f2(x)))) + 3, + exists(x, fp(y)), + lambda(x, g(x, x)), + x, + y ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) } { - 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))))), + val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t2 = LeftSubstEq( + Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y)))), - (Seq(g2), exists(x, forall(y, fp(g2(y, g(x, z)))))) + 1, + f, lambda(z, g(z, z)), + Seq(y), + (f2, exists(x, fp(f2(x)))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t3 = LeftBeta( + Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), + 3, + exists(x, fp(y)), + lambda(x, g(x, x)), + x, + y + ) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) } { 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))))), + val t1 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t2 = LeftSubstEq( + 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))(z, y)))) |- 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(g2), exists(x, forall(y, fp(g2(y, g2(x, z)))))) + 1, + g, lambda(Seq(y, z), g(z, y)), + Seq(y, z), + (g2, exists(x, forall(y, fp(g2(y, g(x, z)))))) ) assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) } { - 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)))))), + 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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t2 = LeftSubstEq( + 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))(z, y)))) |- 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))), (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))))))) + 1, + g, lambda(Seq(y, z), g(z, y)), + Seq(y, z), + (g2, exists(x, forall(y, fp(g2(y, g2(x, z)))))) ) assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) } @@ -105,145 +123,213 @@ class ProofTests extends AnyFunSuite { 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, 1, 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))), + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t2 = RightSubstEq( + Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(lambda(x, g(x, x))(x))), 0, - List((LambdaTermTerm(Seq(x), f(x)), LambdaTermTerm(Seq(x), g(x, x)))), - (Seq(f2), exists(x, fp(f2(x)))) + 1, + f, lambda(x, g(x, x)), + Seq(y), + (f2, exists(x, fp(f2(x)))) ) - 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( + val t3 = RightBeta( Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), - 0, - List((LambdaTermTerm(Seq(y), f(y)), LambdaTermTerm(Seq(z), g(z, z)))), - (Seq(f2), exists(x, fp(f2(x)))) + 3, + exists(x, fp(y)), + lambda(x, g(x, x)), + x, + y ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) } { - 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)))), + val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t2 = RightSubstEq( + Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(lambda(z, g(z, z))(x))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y)))), - (Seq(g2), exists(x, forall(y, fp(g2(y, g(x, z)))))) + 1, + f, lambda(z, g(z, z)), + Seq(y), + (f2, exists(x, fp(f2(x)))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t3 = RightBeta( + Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), + 3, + exists(x, fp(y)), + lambda(x, g(x, x)), + x, + y + ) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) } { 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)))), + val t1 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t2 = RightSubstEq( + 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))(z, y)))) |- exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(g(x, z), y)))), 0, - List((LambdaTermTerm(Seq(y, z), g(y, z)), LambdaTermTerm(Seq(y, z), g(z, y)))), - (Seq(g2), exists(x, forall(y, fp(g2(y, g2(x, z)))))) + 1, + g, lambda(Seq(y, z), g(z, y)), + Seq(y, z), + (g2, exists(x, forall(y, fp(g2(y, g(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) } { - 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)))), + 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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t2 = RightSubstEq( + 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))(z, y)))) |- exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(lambda(Seq(y, z), g(z, y))(x, z), 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))))))) + 1, + g, lambda(Seq(y, z), g(z, y)), + Seq(y, z), + (g2, exists(x, forall(y, fp(g2(y, g2(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) } - } - test("Verification of LeftSubstIff") { + + 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 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, 1, P(x), P(y), Seq(), (X, P(X))) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).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 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t2 = LeftSubstIff( + Set(exists(x, lambda(x, Q(x, 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))) + 1, + P, lambda(x, Q(x, x)), + Seq(y), + (P2, exists(x, P2(x))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t3 = LeftBeta( + Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), + 2, + exists(x, X), + lambda(x, Q(x, x)), + x, + X + ) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).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 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t2 = LeftSubstIff( + Set(exists(x, lambda(z, Q(z, z))(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), 0, - List((LambdaTermFormula(Seq(y), P(y)), LambdaTermFormula(Seq(z), Q(z, z)))), - (Seq(P2), exists(x, P2(x))) + 1, + P, lambda(z, Q(z, z)), + Seq(y), + (P2, exists(x, P2(x))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t3 = LeftBeta( + Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), + 2, + exists(x, X), + lambda(z, Q(z, z)), + x, + X + ) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).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)))), + val t1 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> Q(z, y)) + val t2 = LeftSubstIff( + Set(exists(x, forall(y, lambda(Seq(y, z), Q(z, y))(y, g(x, z)))), 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))))) + 1, + Q, lambda(Seq(y, z), Q(z, y)), + Seq(y, z), + (Q2, exists(x, forall(y, Q2(y, g(x, z))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).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 t1 = Hypothesis(P(x) <=> P(y) |- P(x) <=> P(y), P(x) <=> P(y)) + val t2 = RightSubstIff(Set(P(x), P(x) <=> P(y)) |- P(y), 0, 1, P(x), P(y), Seq(), (X, P(X))) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) } { 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)), + val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t2 = RightSubstIff( + Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, lambda(x, Q(x, x))(x)), 0, - List((LambdaTermFormula(Seq(x), P(x)), LambdaTermFormula(Seq(x), Q(x, x)))), - (Seq(P2), exists(x, P2(x))) + 1, + P, lambda(x, Q(x, x)), + Seq(y), + (P2, exists(x, P2(x))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t3 = RightBeta( + Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), + 2, + exists(x, X), + lambda(x, Q(x, x)), + x, + X + ) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) } { 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)), + val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t2 = RightSubstIff( + Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, lambda(z, Q(z, z))(x)), 0, - List((LambdaTermFormula(Seq(y), P(y)), LambdaTermFormula(Seq(z), Q(z, z)))), - (Seq(P2), exists(x, P2(x))) + 1, + P, lambda(z, Q(z, z)), + Seq(y), + (P2, exists(x, P2(x))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + val t3 = RightBeta( + Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), + 2, + exists(x, X), + lambda(z, Q(z, z)), + x, + X + ) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).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 = 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 t1 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> Q(z, y)) + val t2 = RightSubstIff( + Set(exists(x, forall(y, Q(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> Q(y, x)))) |- exists(x, lambda(Seq(y, z), Q(z, y))(g(x, z), y)), 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)))) + 1, + Q, lambda(Seq(y, z), Q(z, y)), + Seq(y, z), + (Q2, exists(x, forall(y, Q2(y, g(x, z))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1))).isValid) + assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) } } 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 b5e59ffc..18b835b0 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(substituteVariables(t, m.toMap) == betaReduce(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 988ead99..487b1a2f 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala @@ -17,11 +17,10 @@ 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("0", Term), Constant("1", 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 { diff --git a/lisa-utils/src/test/scala/lisa/utils/ParserTest.scala b/lisa-utils/src/test/scala/lisa/utils/ParserTest.scala index b6e20602..6774488b 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 8f213241..5db68146 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 243bd3c1..2f1cd58f 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 } From 6e069642f7a5980cdd688266c70fe6ced6d0ca1d Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Mon, 21 Oct 2024 01:59:41 +0200 Subject: [PATCH 23/92] Some tests pass. --- .../kernel/fol/OLEquivalenceChecker.scala | 35 ++-- .../main/scala/lisa/kernel/fol/Syntax.scala | 88 +++------- .../lisa/kernel/proof/SCProofChecker.scala | 57 +----- .../lisa/kernel/proof/SequentCalculus.scala | 18 +- .../main/scala/lisa/utils/KernelHelpers.scala | 30 ++-- .../main/scala/lisa/utils/Serialization.scala | 55 ++---- .../lisa/utils/prooflib/BasicStepTactic.scala | 68 +------- .../lisa/utils/prooflib/ProofsHelpers.scala | 2 +- .../test/scala/lisa/ProofCheckerSuite.scala | 2 +- .../lisa/kernel/IncorrectProofsTests.scala | 10 +- .../test/scala/lisa/kernel/ProofTests.scala | 163 +++++++++--------- .../scala/lisa/kernel/SubstitutionTest.scala | 2 +- 12 files changed, 171 insertions(+), 359 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala index 6f876973..8a15409b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -33,13 +33,10 @@ private[fol] trait OLEquivalenceChecker extends Syntax { @deprecated("Use isSame instead", "0.8") def isSameTerm(term1: Expression, term2: Expression): Boolean = isSame(term1, term2) def isSame(e1: Expression, e2: Expression): Boolean = { - if (e1.containsFormulas != e2.containsFormulas) false - else if (!e1.containsFormulas) e1 == e2 - else { - val nf1 = computeNormalForm(simplify(e1)) - val nf2 = computeNormalForm(simplify(e2)) - latticesEQ(nf1, nf2) - } + val nf1 = computeNormalForm(simplify(e1)) + val nf2 = computeNormalForm(simplify(e2)) + latticesEQ(nf1, nf2) + } /** @@ -469,17 +466,15 @@ private[fol] trait OLEquivalenceChecker extends Syntax { def latticesEQ(e1: SimpleExpression, e2: SimpleExpression): Boolean = if (e1.uniqueKey == e2.uniqueKey) true - else if (e1.containsFormulas && e2.containsFormulas) { - 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 - } - } else e1 == e2 + 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/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala index 578acbfb..57bef8d0 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -132,72 +132,6 @@ private[fol] trait Syntax { def allVariables: Set[Variable] = body.allVariables } - /* - object Equality { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`equality`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(arg1: Expression, arg2: Expression): Expression = equality(arg1)(arg2) - } - - object Neg { - def unapply (e: Expression): Option[Expression] = e match { - case Application(`neg`, arg) => Some(arg) - case _ => None - } - def apply(arg: Expression): Expression = neg(arg) - } - object Implies { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`implies`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(arg1: Expression, arg2: Expression): Expression = implies(arg1)(arg2) - } - object Iff { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`iff`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(arg1: Expression, arg2: Expression): Expression = iff(arg1)(arg2) - } - object And { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`and`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) - } - object Or { - def unapply (e: Expression): Option[(Expression, Expression)] = e match { - case Application(Application(`or`, arg1), arg2) => Some((arg1, arg2)) - case _ => None - } - def apply(args: Iterable[Expression]): Expression = args.reduceLeft(and(_)(_)) - } - object Forall { - def unapply (e: Expression): Option[(Variable, Expression)] = e match { - case Application(`forall`, Lambda(v, body)) => Some((v, body)) - case _ => None - } - def apply(v: Variable, body: Expression): Expression = forall(Lambda(v, body)) - } - object Exists { - def unapply (e: Expression): Option[(Variable, Expression)] = e match { - case Application(`exists`, Lambda(v, body)) => Some((v, body)) - case _ => None - } - def apply(v: Variable, body: Expression): Expression = exists(Lambda(v, body)) - } - object Epsilon { - def unapply (e: Expression): Option[(Variable, Expression)] = e match { - case Application(`epsilon`, Lambda(v, body)) => Some((v, body)) - case _ => None - } - def apply(v: Variable, body: Expression): Expression = epsilon(Lambda(v, body)) - } -*/ val equality = Constant(Identifier("="), Term -> (Term -> Formula)) val top = Constant(Identifier("⊤"), Formula) @@ -229,7 +163,13 @@ private[fol] trait Syntax { case c: Constant => c case Application(f, arg) => Application(substituteVariables(f, m), substituteVariables(arg, m)) case Lambda(v, t) => - Lambda(v, substituteVariables(t, m - v)) + 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 { @@ -237,4 +177,18 @@ private[fol] trait Syntax { case _ => List() } + def betaReduce(e: Expression): Expression = e match { + case Application(f, arg) => { + val f1 = betaReduce(f) + val a2 = betaReduce(arg) + f1 match { + case Lambda(v, body) => betaReduce(substituteVariables(body, Map(v -> a2))) + case _ => Application(f1, betaReduce(a2)) + } + } + case Lambda(v, inner) => + Lambda(v, betaReduce(inner)) + case _ => e + } + } 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 f41ae994..778903fe 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -381,33 +381,6 @@ 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 RightBeta(b, t1, phi, lambda, t, x) => - val Lambda(y, e) = lambda - if (phi.sort != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) - else if (y.sort != t.sort) - SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.sort + " and " + y.sort) - else if (e.sort != x.sort) - SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.sort + " and " + x.sort) - else if (isSameSet(b.left, ref(t1).left)) { - val redex = lambda(t) - val normalized = substituteVariables(e, Map(y -> t)) - val phi_redex = substituteVariables(phi, Map(x -> redex)) - val phi_normalized = substituteVariables(phi, Map(x -> normalized)) - if (isSameSet(b.right + phi_redex, ref(t1).right + phi_normalized) || isSameSet(b.right + phi_normalized, ref(t1).right + phi_redex)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of the conclusion + φ[(λy. e)t/x] must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") - } else SCInvalidProof(SCProof(step), Nil, "Left-hand sides or conclusion and premise must be the same.") - - /** *
            *    Γ, φ[(λy. e)t/x] |- Δ
@@ -415,24 +388,10 @@ object SCProofChecker {
            *     Γ, φ[e[t/y]/x] |- Δ
            * 
*/ - case LeftBeta(b, t1, phi, lambda, t, x) => - val Lambda(y, e) = lambda - if (phi.sort != Formula) - SCInvalidProof(SCProof(step), Nil, "φ must be a formula, but it is a " + phi.sort) - else if (y.sort != t.sort) - SCInvalidProof(SCProof(step), Nil, "t must have the same type as y, but they are " + t.sort + " and " + y.sort) - else if (e.sort != x.sort) - SCInvalidProof(SCProof(step), Nil, "e must have the same type as x, but they are " + e.sort + " and " + x.sort) - else if (isSameSet(b.right, ref(t1).right)) { - val redex = lambda(t) - val normalized = substituteVariables(e, Map(y -> t)) - val phi_redex = substituteVariables(phi, Map(x -> redex)) - val phi_normalized = substituteVariables(phi, Map(x -> normalized)) - if (isSameSet(b.left + phi_redex, ref(t1).left + phi_normalized) || isSameSet(b.left + phi_normalized, ref(t1).left + phi_redex)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of the conclusion + φ[(λy. e)t/x] must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") - } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides or conclusion and premise must be the same.") - + case Beta(b, t1) => + if (isSame(betaReduce(sequentToFormula(b)), betaReduce(sequentToFormula(ref(t1))))) { + SCValidProof(SCProof(step)) + } else SCInvalidProof(SCProof(step), Nil, "The conclusion is not beta-OL-equivalent to the premise.") // Equality Rules /* @@ -510,9 +469,9 @@ object SCProofChecker { 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, "Left-hand sides of the conclusion + φ(s) must be the same as left-hand side of the premises + φ(t).") } - else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premises must be the same as the right-hand side of the conclusion + s=t.") } /* @@ -591,9 +550,9 @@ object SCProofChecker { 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, "Left-hand sides of the conclusion the same as the left-hand sides of the premises.") } - else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premises + φ(t) must be the same as right-hand sides of the premises + φ(s) + s=t.") } 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 bf679c6b..6b79aaca 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala @@ -257,17 +257,9 @@ object SequentCalculus { * Γ |- φ[e[t/y]/x], Δ * */ - case class LeftBeta(bot: Sequent, t1: Int, phi: Expression, lambda: Lambda, t: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } + case class Beta(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } - /** - *
-   *    Γ, φ[(λy. e)t/x] |- Δ
-   * ---------------------------
-   *     Γ, φ[e[t/y]/x] |- Δ
-   * 
- */ - case class RightBeta(bot: Sequent, t1: Int, phi: Expression, lambda: Lambda, t: Expression, x: Variable) extends SCProofStep { val premises = Seq(t1) } // Equality Rules /** @@ -297,7 +289,7 @@ object SequentCalculus { * 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, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } + case class LeftSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1, t2) } /** *
@@ -307,7 +299,7 @@ object SequentCalculus {
    * 
* equals elements must have type ... -> ... -> Term */ - case class RightSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1) } + case class RightSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1, t2) } /** *
@@ -317,7 +309,7 @@ object SequentCalculus {
    * 
* equals elements must have type ... -> ... -> Formula */ - 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) } + 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) } /** *
@@ -327,7 +319,7 @@ object SequentCalculus {
    * 
* equals elements must have type ... -> ... -> Formula */ - 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) } + 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 diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 5b02367f..501f750b 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -23,7 +23,7 @@ object KernelHelpers { /* Prefix syntax */ extension (s: Sort) { - def >>:(t: Sort) : Sort = Arrow(t, s) + def >>:(t: Sort) : Sort = Arrow(s, t) } val Equality = equality @@ -52,12 +52,13 @@ object KernelHelpers { val ε = epsilon + extension (binder: forall.type) { @targetName("forallApply") def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) @targetName("forallUnapply") def unapply(e: Expression): Option[(Variable, Expression)] = e match { - case Application(forall, Lambda(x, inner)) => Some((x, inner)) + case Application(`forall`, Lambda(x, inner)) => Some((x, inner)) case _ => None } } @@ -66,7 +67,7 @@ object KernelHelpers { def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) @targetName("existsUnapply") def unapply(e: Expression): Option[(Variable, Expression)] = e match { - case Application(exists, Lambda(x, inner)) => Some((x, inner)) + case Application(`exists`, Lambda(x, inner)) => Some((x, inner)) case _ => None } } @@ -75,7 +76,7 @@ object KernelHelpers { def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) @targetName("epsilonUnapply") def unapply(e: Expression): Option[(Variable, Expression)] = e match { - case Application(epsilon, Lambda(x, inner)) => Some((x, inner)) + case Application(`epsilon`, Lambda(x, inner)) => Some((x, inner)) case _ => None } } @@ -204,18 +205,6 @@ object KernelHelpers { def fullRepr: String = s"${s.left.map(_.fullRepr).mkString(", ")} |- ${s.right.map(_.fullRepr).mkString(", ")}" } - def betaReduce(e: Expression): Expression = e match { - case Application(f, arg) => - val f1 = betaReduce(f) - val a2 = betaReduce(arg) - f1 match - case Lambda(v, body) => betaReduce(substituteVariables(body, Map(v -> a2))) - case _ => Application(f1, betaReduce(a2)) - case Lambda(v, inner) => - Lambda(v, betaReduce(inner)) - case _ => e - } - /** * Represents a converter of some object into a set. * @tparam S The type of elements in that set @@ -497,6 +486,12 @@ object KernelHelpers { } } + + extension (judg: SCProofCheckerJudgement) { + def repr: String = prettySCProof(judg) + } + + /** * output a readable representation of a proof. */ @@ -606,8 +601,7 @@ object KernelHelpers { case LeftImplies(_, t1, t2, _, _) => pretty("Left ⇒", t1, t2) case LeftIff(_, t1, _, _) => pretty("Left ⇔", t1) case Weakening(_, t1) => pretty("Weakening", t1) - case LeftBeta(_, t1, _, _, _, _) => pretty("Left β", t1) - case RightBeta(_, t1, _, _, _, _) => pretty("Right β", t1) + case Beta(_, t1) => pretty("Beta", t1) case LeftRefl(_, t1, _) => pretty("L. Refl", t1) case RightRefl(_, _) => pretty("R. Refl") case LeftSubstEq(_, t1, t2, _, _, _, _) => pretty("L. SubstEq", t1, t2) diff --git a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala index 883b02a9..555d5c6a 100644 --- a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala +++ b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala @@ -30,17 +30,16 @@ object Serialization { inline def rightExists: Byte = 18 inline def rightEpsilon: Byte = 19 inline def weakening: Byte = 20 - inline def leftBeta: Byte = 21 - inline def rightBeta: Byte = 22 - inline def leftRefl: Byte = 23 - inline def rightRefl: Byte = 24 - inline def leftSubstEq: Byte = 25 - inline def rightSubstEq: Byte = 26 - inline def leftSubstIff: Byte = 27 - inline def rightSubstIff: Byte = 28 - inline def instSchema: Byte = 29 - inline def scSubproof: Byte = 30 - inline def sorry: Byte = 31 + 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 leftSubstIff: Byte = 26 + inline def rightSubstIff: Byte = 27 + inline def instSchema: Byte = 28 + inline def scSubproof: Byte = 29 + inline def sorry: Byte = 30 type Line = Int @@ -269,22 +268,10 @@ object Serialization { proofDOS.writeByte(weakening) sequentToProofDOS(bot) proofDOS.writeInt(t1) - case LeftBeta(bot, t1, phi, lambda, t, x) => - proofDOS.writeByte(leftBeta) + case Beta(bot, t1) => + proofDOS.writeByte(beta) sequentToProofDOS(bot) proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfExpr(phi)) - proofDOS.writeInt(lineOfExpr(lambda)) - proofDOS.writeInt(lineOfExpr(t)) - proofDOS.writeInt(lineOfExpr(x)) - case RightBeta(bot, t1, phi, lambda, t, x) => - proofDOS.writeByte(rightBeta) - sequentToProofDOS(bot) - proofDOS.writeInt(t1) - proofDOS.writeInt(lineOfExpr(phi)) - proofDOS.writeInt(lineOfExpr(lambda)) - proofDOS.writeInt(lineOfExpr(t)) - proofDOS.writeInt(lineOfExpr(x)) case LeftRefl(bot, t1, fa) => proofDOS.writeByte(leftRefl) sequentToProofDOS(bot) @@ -477,22 +464,10 @@ object Serialization { exprMap(proofDIS.readInt()) ) else if (psType == weakening) Weakening(sequentFromProofDIS(), proofDIS.readInt()) - else if (psType == leftBeta) - LeftBeta(sequentFromProofDIS(), - proofDIS.readInt(), - exprMap(proofDIS.readInt()), - exprMap(proofDIS.readInt()).asInstanceOf[Lambda], - exprMap(proofDIS.readInt()), - exprMap(proofDIS.readInt()).asInstanceOf[Variable] + else if (psType == beta) + Beta(sequentFromProofDIS(), + proofDIS.readInt() ) - else if (psType == rightBeta) - RightBeta(sequentFromProofDIS(), - proofDIS.readInt(), - exprMap(proofDIS.readInt()), - exprMap(proofDIS.readInt()).asInstanceOf[Lambda], - exprMap(proofDIS.readInt()), - exprMap(proofDIS.readInt()).asInstanceOf[Variable] - ) else if (psType == leftRefl) LeftRefl(sequentFromProofDIS(), proofDIS.readInt(), exprMap(proofDIS.readInt())) else if (psType == rightRefl) RightRefl(sequentFromProofDIS(), exprMap(proofDIS.readInt())) else if (psType == leftSubstEq) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index 81204620..af342c83 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -988,65 +988,15 @@ object BasicStepTactic { */ - object RightBeta extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof) - (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val phiK = phi.underlying - lazy val lambdaK = lambda.underlying - lazy val tK = t.underlying - lazy val xK = x.underlying - lazy val botK = bot.underlying - lazy val y = lambda.v.underlying - lazy val e = lambda.body.underlying - if (phi.sort != K.Formula) - return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) - else if (y.sort != t.sort) - return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) - else if (e.sort != x.sort) - return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) - else if (K.isSameSet(botK.left, premiseSequent.left)) { - val redex = lambdaK(tK) - val normalized = K.substituteVariables(e, Map(y -> tK)) - val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) - val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) - if (K.isSameSet(botK.right + phi_redex, premiseSequent.right + phi_normalized) || K.isSameSet(botK.right + phi_normalized, premiseSequent.right + phi_redex)) - return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) - else - return proof.InvalidProofTactic("Right-hand side of the conclusion + φ[λy.e]t/x must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") - } else - return proof.InvalidProofTactic("Left-hand side of the conclusion must be the same as the left-hand side of the premise") - } - } - - object LeftBeta extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof) - (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val phiK = phi.underlying - lazy val lambdaK = lambda.underlying - lazy val tK = t.underlying - lazy val xK = x.underlying - lazy val botK = bot.underlying - lazy val y = lambda.v.underlying - lazy val e = lambda.body.underlying - if (phi.sort != K.Formula) - return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) - else if (y.sort != t.sort) - return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) - else if (e.sort != x.sort) - return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) - else if (K.isSameSet(botK.right, premiseSequent.right)) { - val redex = lambdaK(tK) - val normalized = K.substituteVariables(e, Map(y -> tK)) - val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) - val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) - if (K.isSameSet(botK.left + phi_redex, premiseSequent.left + phi_normalized) || K.isSameSet(botK.left + phi_normalized, premiseSequent.left + phi_redex)) - return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) - else - return proof.InvalidProofTactic("Left-hand side of the conclusion + φ[λy.e]t/x must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") - } else - return proof.InvalidProofTactic("Right-hand side of the conclusion must be the same as the right-hand side of the premise") + 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.betaReduce(K.sequentToFormula(botK)) + val red2 = K.betaReduce(K.sequentToFormula(proof.getSequent(premise).underlying)) + 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)) } } diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala index 9e499d7d..38d4f74e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -297,7 +297,7 @@ trait ProofsHelpers { 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 RightBeta.withParameters(appliedCst === right, lam, vAbs, freshX) + thenHave(appliedCst === instRight) by Beta case App(f, a: Variable[?]) => loop(expr, a :: leading) case _ => throw new Exception("Unreachable") } diff --git a/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala b/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala index 0f11cba1..01d567da 100644 --- a/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala +++ b/lisa-utils/src/test/scala/lisa/ProofCheckerSuite.scala @@ -39,7 +39,7 @@ abstract class ProofCheckerSuite extends AnyFunSuite { 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 '${incorrectProof.conclusion.repr}' was accepted by the proof checker)\nSequent: ${incorrectProof.conclusion}" diff --git a/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala b/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala index 1abd0458..8eae73ea 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala @@ -37,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, 1, x, z, Seq(), (y, x === y)) // wrong variable replaced + RightSubstEq(emptySeq +<< (x === y) +<< (x === z) +>> (z === y), 0, 0, 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, 1, x, z, Seq(), (x, x === y)) // missing hypothesis + RightSubstEq(emptySeq +<< (x === y) +>> (z === y), 0, 0, 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, 1, x, z, Seq(), (x, x === z)) // replacement mismatch + RightSubstEq(emptySeq +<< (x === y) +<< (x === z) +>> (z === y), 0, 0, 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, 1, x, z, Seq(), (y, x === y)) + LeftSubstEq(emptySeq +<< (z === y) +<< (x === z) +>> (x === y), 0, 0, x, z, Seq(), (y, x === y)) ), SCProof( Hypothesis(emptySeq +<< (f <=> g) +>> (f <=> g), f <=> g), - LeftSubstIff(emptySeq +<< (h <=> g) +<< (f <=> h) +>> (f <=> g), 0, 1, f, h, Seq(), (g, f <=> g)) + LeftSubstIff(emptySeq +<< (h <=> g) +<< (f <=> h) +>> (f <=> g), 0, 0, 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 d1caca4b..c80735e1 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala @@ -48,11 +48,12 @@ class ProofTests extends AnyFunSuite { val t0 = Hypothesis(fp(x) |- fp(x), fp(x)) val t1 = Hypothesis(x === y |- x === y, x === y) val t2 = LeftSubstEq(Set(fp(y), x === y) |- fp(x), 0, 1, x, y, Seq(), (sT, fp(sT))) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, judg.repr) } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) - val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(x, g(x, x))(y)) val t2 = LeftSubstEq( Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), 0, @@ -61,19 +62,16 @@ class ProofTests extends AnyFunSuite { Seq(y), (f2, exists(x, fp(f2(x)))) ) - val t3 = LeftBeta( + val t3 = Beta( Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), - 3, - exists(x, fp(y)), - lambda(x, g(x, x)), - x, - y + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))) + assert(judg.isValid, judg.repr) } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) - val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(x, g(x, x))(y)) val t2 = LeftSubstEq( Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), 0, @@ -82,41 +80,43 @@ class ProofTests extends AnyFunSuite { Seq(y), (f2, exists(x, fp(f2(x)))) ) - val t3 = LeftBeta( + val t3 = Beta( Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))), - 3, - exists(x, fp(y)), - lambda(x, g(x, x)), - x, - y + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))) + assert(judg.isValid, 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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t1 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y)(z)) val t2 = LeftSubstEq( - 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))(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) === g(z, y)))) |- exists(x, forall(y, fp(g(y, g(x, z))))), 0, 1, g, lambda(Seq(y, z), g(z, y)), Seq(y, z), (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, t2))) + assert(judg.isValid, 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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t1 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y, z)) val t2 = LeftSubstEq( - 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))(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) === g(z, y))) + ) |- exists(x, forall(y, fp(g(y, g(x, z))))), 0, 1, g, lambda(Seq(y, z), g(z, y)), Seq(y, z), (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, t2))) + assert(judg.isValid, judg.repr) } } @@ -129,7 +129,7 @@ class ProofTests extends AnyFunSuite { } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) - val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(x, g(x, x))(y)) val t2 = RightSubstEq( Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(lambda(x, g(x, x))(x))), 0, @@ -138,19 +138,16 @@ class ProofTests extends AnyFunSuite { Seq(y), (f2, exists(x, fp(f2(x)))) ) - val t3 = RightBeta( + val t3 = Beta( Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), - 3, - exists(x, fp(y)), - lambda(x, g(x, x)), - x, - y + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))) + assert(judg.isValid, judg.repr) } { val t0 = Hypothesis(exists(x, fp(f(x))) |- exists(x, fp(f(x))), exists(x, fp(f(x)))) - val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === g(y, y)) + val t1 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(z, g(z, z))(y)) val t2 = RightSubstEq( Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(lambda(z, g(z, z))(x))), 0, @@ -159,41 +156,46 @@ class ProofTests extends AnyFunSuite { Seq(y), (f2, exists(x, fp(f2(x)))) ) - val t3 = RightBeta( + val t3 = Beta( Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))), - 3, - exists(x, fp(y)), - lambda(x, g(x, x)), - x, - y + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))) + assert(judg.isValid, 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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t1 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y, z)) val t2 = RightSubstEq( - 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))(z, y)))) |- exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(g(x, z), y)))), + 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(lambda(Seq(y, z), g(z, y))(y, g(x, z))))), 0, 1, g, lambda(Seq(y, z), g(z, y)), Seq(y, z), (g2, exists(x, forall(y, fp(g2(y, g(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, 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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === g(z, y)) + val t1 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y, z)) val t2 = RightSubstEq( - 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))(z, y)))) |- exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(lambda(Seq(y, z), g(z, y))(x, z), y)))), + 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(lambda(Seq(y, z), g(z, y))(y, lambda(Seq(y, z), g(z, y))(x, z))))), 0, 1, g, lambda(Seq(y, z), g(z, y)), Seq(y, z), (g2, exists(x, forall(y, fp(g2(y, g2(x, z)))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, judg.repr) } } @@ -202,12 +204,13 @@ class ProofTests extends AnyFunSuite { { 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, 1, P(x), P(y), Seq(), (X, P(X))) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) + val t2 = LeftSubstIff(Set(P(y), P(x) <=> P(y)) |- P(x), 0, 1, P(x), P(y), Seq(), (X, X)) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, judg.repr) } { val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) - val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(x, Q(x, x))(y)) val t2 = LeftSubstIff( Set(exists(x, lambda(x, Q(x, x))(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), 0, @@ -216,19 +219,16 @@ class ProofTests extends AnyFunSuite { Seq(y), (P2, exists(x, P2(x))) ) - val t3 = LeftBeta( + val t3 = Beta( Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), - 2, - exists(x, X), - lambda(x, Q(x, x)), - x, - X + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))) + assert(judg.isValid, judg.repr) } { val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) - val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(z, Q(z, z))(y)) val t2 = LeftSubstIff( Set(exists(x, lambda(z, Q(z, z))(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), 0, @@ -237,19 +237,16 @@ class ProofTests extends AnyFunSuite { Seq(y), (P2, exists(x, P2(x))) ) - val t3 = LeftBeta( + val t3 = Beta( Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)), - 2, - exists(x, X), - lambda(z, Q(z, z)), - x, - X + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))) + assert(judg.isValid, 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 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> Q(z, y)) + val t1 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> lambda(Seq(y, z), Q(z, y))(y, z)) val t2 = LeftSubstIff( Set(exists(x, forall(y, lambda(Seq(y, z), Q(z, y))(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> Q(y, x)))) |- exists(x, forall(y, Q(y, g(x, z)))), 0, @@ -258,7 +255,8 @@ class ProofTests extends AnyFunSuite { Seq(y, z), (Q2, exists(x, forall(y, Q2(y, g(x, z))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, judg.repr) } } @@ -266,12 +264,12 @@ class ProofTests extends AnyFunSuite { { 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 = RightSubstIff(Set(P(x), P(x) <=> P(y)) |- P(y), 0, 1, P(x), P(y), Seq(), (X, P(X))) + val t2 = RightSubstIff(Set(P(x), P(x) <=> P(y)) |- P(y), 0, 1, P(x), P(y), Seq(), (X, X)) assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) } { val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) - val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(x, Q(x, x))(y)) val t2 = RightSubstIff( Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, lambda(x, Q(x, x))(x)), 0, @@ -280,19 +278,16 @@ class ProofTests extends AnyFunSuite { Seq(y), (P2, exists(x, P2(x))) ) - val t3 = RightBeta( + val t3 = Beta( Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), - 2, - exists(x, X), - lambda(x, Q(x, x)), - x, - X + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))) + assert(judg.isValid, judg.repr) } { val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x))) - val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> Q(y, y)) + val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(x, Q(x, x))(y)) val t2 = RightSubstIff( Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, lambda(z, Q(z, z))(x)), 0, @@ -301,28 +296,26 @@ class ProofTests extends AnyFunSuite { Seq(y), (P2, exists(x, P2(x))) ) - val t3 = RightBeta( + val t3 = Beta( Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)), - 2, - exists(x, X), - lambda(z, Q(z, z)), - x, - X + 2 ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, 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 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> Q(z, y)) + val t1 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> lambda(Seq(y, z), Q(z, y))(y, z)) val t2 = RightSubstIff( - Set(exists(x, forall(y, Q(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> Q(y, x)))) |- exists(x, lambda(Seq(y, z), Q(z, y))(g(x, z), y)), + Set(exists(x, forall(y, Q(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> Q(y, x)))) |- exists(x, forall(y, lambda(Seq(y, z), Q(z, y))(y, g(x, z)))), 0, 1, Q, lambda(Seq(y, z), Q(z, y)), Seq(y, z), (Q2, exists(x, forall(y, Q2(y, g(x, z))))) ) - assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid) + val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2))) + assert(judg.isValid, judg.repr) } } diff --git a/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala b/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala index 18b835b0..9127f49f 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/SubstitutionTest.scala @@ -43,7 +43,7 @@ class SubstitutionTest extends AnyFunSuite { private val e2 = connector(2) case class $(t: Expression, m: (Variable, Expression)*){ inline infix def _VS_(t2: Expression): Assertion = { - assert(substituteVariables(t, m.toMap) == betaReduce(t2), "\n - " + substituteVariables(t, m.toMap).repr + " didn't match " + t2.repr) + assert(isSame(betaReduce(substituteVariables(t, m.toMap)), t2), "\n - " + substituteVariables(t, m.toMap).repr + " didn't match " + t2.repr) } } test("First Order Substitutions") { From ee6d5b6cc78deb5b20335f13c04c4f7f642d98ee Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Mon, 21 Oct 2024 15:58:06 +0200 Subject: [PATCH 24/92] utils tests passing. Left to do: - BasicTacticTest - UnificationTest - printer and parsers test: Port to TPTP-based printer (longer term) --- .../kernel/fol/OLEquivalenceChecker.scala | 110 +++++++++++------- .../main/scala/lisa/kernel/fol/Syntax.scala | 3 + .../lisa/kernel/proof/SequentCalculus.scala | 14 ++- .../main/scala/lisa/utils/fol/Predef.scala | 2 +- .../main/scala/lisa/utils/fol/Syntax.scala | 12 +- .../test/scala/lisa/TestTheoryLibrary.scala | 10 +- .../lisa/kernel/EquivalenceCheckerTests.scala | 2 +- .../lisa/kernel/TheoriesHelpersTest.scala | 9 +- .../scala/lisa/utils/BasicTacticTest.scala | 38 +++--- 9 files changed, 125 insertions(+), 75 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala index 8a15409b..d03e9c16 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -82,8 +82,10 @@ private[fol] trait OLEquivalenceChecker extends Syntax { def getNNF_neg = NNF_neg private[OLEquivalenceChecker] var formulaAIG: Option[Expression] = None def getFormulaAIG = formulaAIG - private[OLEquivalenceChecker] var normalForm: Option[SimpleExpression] = if (containsFormulas) None else Some(this) + 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() @@ -243,50 +245,80 @@ private[fol] trait OLEquivalenceChecker extends Syntax { - def polarize(e: Expression, polarity:Boolean): SimpleExpression = 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 exists(Lambda(v, body)) => - SimpleForall(v.id, polarize(body, false), !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 Constant(`top`, Formula) => SimpleLiteral(true) - case Constant(`bot`, Formula) => SimpleLiteral(false) - case Constant(id, sort) => SimpleConstant(id, sort, polarity) - case Variable(id, sort) => SimpleVariable(id, sort, polarity) - } + 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 exists(Lambda(v, body)) => + SimpleForall(v.id, polarize(body, false), !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 Constant(`top`, Formula) => SimpleLiteral(true) + case Constant(`bot`, Formula) => SimpleLiteral(false) + 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, subst: Map[(Identifier, Sort), Int], i: Int): SimpleExpression = e match { - case SimpleAnd(children, polarity) => SimpleAnd(children.map(toLocallyNameless(_, subst, i)), polarity) - case SimpleForall(x, inner, polarity) => SimpleForall(x, toLocallyNameless(inner, subst + ((x, Term) -> i), i + 1), polarity) + 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(toLocallyNameless(left, subst, i), toLocallyNameless(right, subst, i), polarity) + 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(toLocallyNameless(arg1, subst, i), toLocallyNameless(arg2, subst, i), polarity) - case SimpleLambda(x, inner) => SimpleLambda(x, toLocallyNameless(inner, subst + ((x.id, x.sort) -> i), i + 1)) + 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 { @@ -304,7 +336,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { 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), Map.empty, 0) + def simplify(e: Expression): SimpleExpression = toLocallyNameless(polarize(e, true)) ////////////////////// diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala index 57bef8d0..161e4970 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -2,6 +2,7 @@ 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") @@ -73,6 +74,8 @@ private[fol] trait Syntax { } 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 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 6b79aaca..326770f4 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala @@ -28,7 +28,19 @@ object SequentCalculus { /** * Simple method that transforms a sequent to a logically equivalent formula. */ - def sequentToFormula(s: Sequent): Expression = implies(s.left.reduce(and(_)(_)))(s.right.reduce(or(_)(_))) + 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]]. diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index 6f89cbef..51df9506 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -17,7 +17,7 @@ trait Predef extends Syntax { (using IsSort[SortOf[S1]], IsSort[SortOf[S2]], IsSort[SortOf[S3]]): Binder[SortOf[S1], SortOf[S2], SortOf[S3]] = new Binder(name.value) - val equality = constant[Term >>: Term >>: Formula]("===") + val equality = constant[Term >>: Term >>: Formula]("=") val === = equality val = = equality diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 22f1e254..1f4b98e4 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -98,16 +98,18 @@ trait Syntax { 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})" + } 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] - case _ => Expr[?] - */ type RetExpr[T] = Expr[?] diff --git a/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala b/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala index c76e6a2a..f3446d04 100644 --- a/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala +++ b/lisa-utils/src/test/scala/lisa/TestTheoryLibrary.scala @@ -11,21 +11,25 @@ object TestTheoryLibrary extends Library { final val p2 = constant[Term >>: Formula] final val f1 = constant[Term >>: Term] final val fixedElement = constant[Term] - final val anotherFixed = constant[Term] + final val anotherElement = constant[Term] addSymbol(p1) addSymbol(p2) addSymbol(f1) addSymbol(fixedElement) - addSymbol(anotherFixed) + addSymbol(anotherElement) 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 5034fc0e..002c55ae 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/EquivalenceCheckerTests.scala @@ -190,7 +190,7 @@ class EquivalenceCheckerTests extends AnyFunSuite { } def addDoubleNegations(p: Double)(random: Random)(f: Expression): Expression = { def transform(f: Expression): Expression = - if (random.nextDouble() < p) neg(neg(transform(f))) + if (random.nextDouble() < p && f.sort == Formula) neg(neg(transform(f))) else f match { case Application(f, arg) => Application(transform(f), transform(arg)) diff --git a/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala b/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala index 487b1a2f..e265eab0 100644 --- a/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala +++ b/lisa-utils/src/test/scala/lisa/kernel/TheoriesHelpersTest.scala @@ -17,7 +17,7 @@ class TheoriesHelpersTest extends AnyFunSuite { export TestTheory.* test("theorem with incorrect statement") { - val (c0, c1) = (Constant("0", Term), Constant("1", Term)) + val (c0, c1) = (Constant("Z", Term), Constant("S", Term)) runningTestTheory.addSymbol(c0) runningTestTheory.addSymbol(c1) @@ -27,12 +27,15 @@ class TheoriesHelpersTest extends AnyFunSuite { 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 8d46bbcf..b63fb972 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 From 3ebc3a8d4840e13473c323368a3fafd2cb62226a Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Mon, 21 Oct 2024 18:11:37 +0200 Subject: [PATCH 25/92] ongoing work on tautology --- backup/backup2/prooflib/BasicMain.scala | 29 - backup/backup2/prooflib/BasicStepTactic.scala | 1481 ----------------- backup/backup2/prooflib/Exports.scala | 6 - backup/backup2/prooflib/Library.scala | 106 -- backup/backup2/prooflib/OutputManager.scala | 52 - backup/backup2/prooflib/ProofPrinter.scala | 129 -- backup/backup2/prooflib/ProofTacticLib.scala | 66 - backup/backup2/prooflib/ProofsHelpers.scala | 356 ---- .../backup2/prooflib/SimpleDeducedSteps.scala | 331 ---- backup/backup2/prooflib/WithTheorems.scala | 649 -------- backup/fol/Common.scala | 929 ----------- backup/fol/FOL.scala | 6 - backup/fol/FOLHelpers.scala | 138 -- backup/fol/Lambdas.scala | 100 -- backup/fol/Predef.scala | 78 - backup/fol/Sequents.scala | 249 --- backup/prooflib/BasicMain.scala | 29 - backup/prooflib/BasicStepTactic.scala | 1457 ---------------- backup/prooflib/Exports.scala | 6 - backup/prooflib/Library.scala | 123 -- backup/prooflib/OutputManager.scala | 52 - backup/prooflib/ProofTacticLib.scala | 66 - backup/prooflib/ProofsHelpers.scala | 441 ----- backup/prooflib/SimpleDeducedSteps.scala | 350 ---- backup/prooflib/WithTheorems.scala | 652 -------- backup/unification/UnificationUtils.scala | 619 ------- .../scala/lisa/automation/Tautology.scala | 113 +- .../src/test/scala/lisa/kernel/FolTests.scala | 121 -- 28 files changed, 34 insertions(+), 8700 deletions(-) delete mode 100644 backup/backup2/prooflib/BasicMain.scala delete mode 100644 backup/backup2/prooflib/BasicStepTactic.scala delete mode 100644 backup/backup2/prooflib/Exports.scala delete mode 100644 backup/backup2/prooflib/Library.scala delete mode 100644 backup/backup2/prooflib/OutputManager.scala delete mode 100644 backup/backup2/prooflib/ProofPrinter.scala delete mode 100644 backup/backup2/prooflib/ProofTacticLib.scala delete mode 100644 backup/backup2/prooflib/ProofsHelpers.scala delete mode 100644 backup/backup2/prooflib/SimpleDeducedSteps.scala delete mode 100644 backup/backup2/prooflib/WithTheorems.scala delete mode 100644 backup/fol/Common.scala delete mode 100644 backup/fol/FOL.scala delete mode 100644 backup/fol/FOLHelpers.scala delete mode 100644 backup/fol/Lambdas.scala delete mode 100644 backup/fol/Predef.scala delete mode 100644 backup/fol/Sequents.scala delete mode 100644 backup/prooflib/BasicMain.scala delete mode 100644 backup/prooflib/BasicStepTactic.scala delete mode 100644 backup/prooflib/Exports.scala delete mode 100644 backup/prooflib/Library.scala delete mode 100644 backup/prooflib/OutputManager.scala delete mode 100644 backup/prooflib/ProofTacticLib.scala delete mode 100644 backup/prooflib/ProofsHelpers.scala delete mode 100644 backup/prooflib/SimpleDeducedSteps.scala delete mode 100644 backup/prooflib/WithTheorems.scala delete mode 100644 backup/unification/UnificationUtils.scala delete mode 100644 lisa-utils/src/test/scala/lisa/kernel/FolTests.scala diff --git a/backup/backup2/prooflib/BasicMain.scala b/backup/backup2/prooflib/BasicMain.scala deleted file mode 100644 index 748f58d5..00000000 --- a/backup/backup2/prooflib/BasicMain.scala +++ /dev/null @@ -1,29 +0,0 @@ -package lisa.prooflib - -import lisa.utils.Serialization.* - -trait BasicMain { - val library: Library - - private val realOutput: String => Unit = println - - val om: OutputManager = new OutputManager { - def finishOutput(exception: Exception): Nothing = { - log(exception) - main(Array[String]()) - sys.exit - } - val stringWriter: java.io.StringWriter = new java.io.StringWriter() - } - export om.output - - /** - * This specific implementation make sure that what is "shown" in theory files is only printed for the one we run, and not for the whole library. - */ - def main(args: Array[String]): Unit = { - realOutput(om.stringWriter.toString) - } - - given om.type = om - -} diff --git a/backup/backup2/prooflib/BasicStepTactic.scala b/backup/backup2/prooflib/BasicStepTactic.scala deleted file mode 100644 index fdda8257..00000000 --- a/backup/backup2/prooflib/BasicStepTactic.scala +++ /dev/null @@ -1,1481 +0,0 @@ -package lisa.prooflib -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.unification.UnificationUtils - -object BasicStepTactic { -/* - def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { - judgement match { - case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) - case j: proof.InvalidProofTactic => proof.InvalidProofTactic(s"Internal tactic call failed! $message\n${j.message}") - } - } - - object Hypothesis extends ProofTactic with ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - val botK = bot.underlying - val intersectedPivot = botK.left.intersect(botK.right) - - if (intersectedPivot.isEmpty) - proof.InvalidProofTactic("A formula for input to Hypothesis could not be inferred from left and right side of the sequent.") - else - proof.ValidProofTactic(bot, Seq(K.Hypothesis(botK, intersectedPivot.head)), Seq()) - } - } - - object Rewrite extends ProofTactic with ProofFactSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val botK = bot.underlying - if (!K.isSameSequent(botK, proof.getSequent(premise).underlying)) - proof.InvalidProofTactic("The premise and the conclusion are not trivially equivalent.") - else - proof.ValidProofTactic(bot, Seq(K.Restate(botK, -1)), Seq(premise)) - } - } - - 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.top)) - proof.InvalidProofTactic("The desired conclusion is not a trivial tautology.") - else - proof.ValidProofTactic(bot, Seq(K.RestateTrue(botK)), Seq()) - } - } - - /** - *
-   *  Γ |- Δ, φ    φ, Σ |- Π
-   * ------------------------
-   *       Γ, Σ |- Δ, Π
-   * 
- */ - object Cut extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1).underlying - lazy val rightSequent = proof.getSequent(prem2).underlying - val botK = bot.underlying - val phiK = phi.underlying - - if (!K.contains(leftSequent.right, phiK)) - proof.InvalidProofTactic("Right-hand side of first premise does not contain φ as claimed.") - else if (!K.contains(rightSequent.left, phiK)) - proof.InvalidProofTactic("Left-hand side of second premise does not contain φ as claimed.") - else if (!K.isSameSet(botK.left + phiK, leftSequent.left ++ rightSequent.left) || (leftSequent.left.contains(phiK) && !botK.left.contains(phiK))) - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") - else if (!K.isSameSet(botK.right + phiK, leftSequent.right ++ rightSequent.right) || (rightSequent.right.contains(phiK) && !botK.right.contains(phiK))) - proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") - else - proof.ValidProofTactic(bot, Seq(K.Cut(botK, -1, -2, phiK)), Seq(prem1, prem2)) - } - - def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1) - lazy val rightSequent = proof.getSequent(prem2) - - lazy val cutSet = (((rightSequent --? bot).right |- ())).left - lazy val intersectedCutSet = rightSequent.left intersect leftSequent.right - - if (!cutSet.isEmpty) - if (cutSet.tail.isEmpty) - Cut.withParameters(cutSet.head)(prem1, prem2)(bot) - else - proof.InvalidProofTactic("Inferred cut pivot is not a singleton set.") - else if (!intersectedCutSet.isEmpty && intersectedCutSet.tail.isEmpty) - // can still find a pivot - Cut.withParameters(intersectedCutSet.head)(prem1, prem2)(bot) - else - proof.InvalidProofTactic("A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause.") - } - } - - // Left rules - /** - *
-   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
-   * --------------     or     --------------
-   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
-   * 
- */ - object LeftAnd extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - val botK = bot.underlying - val phiK = phi.underlying - val psiK = psi.underlying - 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.") - else if ( - !K.isSameSet(botK.left + phiK, premiseSequent.left + phiAndPsi) && - !K.isSameSet(botK.left + psiK, premiseSequent.left + phiAndPsi) && - !K.isSameSet(botK.left + phiK + psiK, premiseSequent.left + phiAndPsi) - ) - proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") - else - proof.ValidProofTactic(bot, Seq(K.LeftAnd(botK, -1, phiK, psiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - - if (!pivot.isEmpty && pivot.tail.isEmpty) - pivot.head match { - case F.App(F.App(F.and, phi), psi) => - if (premiseSequent.left.contains(phi)) - LeftAnd.withParameters(phi, psi)(premise)(bot) - else - LeftAnd.withParameters(phi, psi)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer a conjunction as pivot from premise and conclusion.") - } - else - // try a rewrite, if it works, go ahead with it, otherwise malformed - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial LeftAnd failed.") - else - proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") - } - } - - /** - *
-   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
-   * --------------------------------
-   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
-   * 
- */ - object LeftOr extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(disjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) - val botK = bot.underlying - val disjunctsK = disjuncts.map(_.underlying) - lazy val disjunction = K.multior(disjunctsK) - - if (premises.length == 0) - proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (premises.length != disjuncts.length) - proof.InvalidProofTactic(s"Premises and disjuncts expected to be equal in number, but ${premises.length} premises and ${disjuncts.length} disjuncts received.") - else if (!K.isSameSet(botK.right, premiseSequents.map(_.right).reduce(_ union _))) - proof.InvalidProofTactic("Right-hand side of conclusion is not the union of the right-hand sides of the premises.") - else if ( - premiseSequents.zip(disjunctsK).forall((sequent, disjunct) => K.isSubset(sequent.left, botK.left + disjunct)) // \forall i. premise_i.left \subset bot.left + phi_i - && !K.isSubset(botK.left, premiseSequents.map(_.left).reduce(_ union _) + disjunction) // bot.left \subseteq \bigcup premise_i.left - ) - proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftOr(botK, Range(-1, -premises.length - 1, -1), disjunctsK)), premises.toSeq) - } - - def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequents = premises.map(proof.getSequent(_)) - lazy val pivots = premiseSequents.map(_.left.diff(bot.left)) - - if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (pivots.exists(_.isEmpty)) { - val emptyIndex = pivots.indexWhere(_.isEmpty) - if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) - unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for LeftOr failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the one of the premises.") - } else if (pivots.forall(_.tail.isEmpty)) - LeftOr.withParameters(pivots.map(_.head)*)(premises*)(bot) - else - // some extraneous formulae - proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") - } - } - - /** - *
-   *  Γ |- φ, Δ    Σ, ψ |- Π
-   * ------------------------
-   *    Γ, Σ, φ⇒ψ |- Δ, Π
-   * 
- */ - object LeftImplies extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1).underlying - lazy val rightSequent = proof.getSequent(prem2).underlying - val botK = bot.underlying - val phiK = phi.underlying - val psiK = psi.underlying - 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.") - else if (!K.isSameSet(botK.left + psiK, leftSequent.left union rightSequent.left + implication)) - proof.InvalidProofTactic("Left-hand side of conclusion + ψ is not the union of left-hand sides of premises + φ⇒ψ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftImplies(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) - } - def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1) - lazy val rightSequent = proof.getSequent(prem2) - lazy val pivotLeft = leftSequent.right.diff(bot.right) - lazy val pivotRight = rightSequent.left.diff(bot.left) - - if (pivotLeft.isEmpty) - if (F.isSubset(leftSequent.left, bot.left)) - unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial left premise for LeftImplies failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the first premises.") - else if (pivotRight.isEmpty) - if (F.isSubset(rightSequent.right, bot.right)) - unwrapTactic(Weakening(prem2)(bot))("Attempted weakening on trivial right premise for LeftImplies failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the second premises.") - else if (pivotLeft.tail.isEmpty && pivotRight.tail.isEmpty) - LeftImplies.withParameters(pivotLeft.head, pivotRight.head)(prem1, prem2)(bot) - else - proof.InvalidProofTactic("Could not infer an implication as a pivot from the premises and conclusion, possible extraneous formulae in premises.") - } - } - - /** - *
-   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
-   * --------------    or     --------------------
-   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
-   * 
- */ - object LeftIff extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - val botK = bot.underlying - val phiK = phi.underlying - val psiK = psi.underlying - 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.") - else if ( - !K.isSameSet(botK.left + impLeft, premiseSequent.left + implication) && - !K.isSameSet(botK.left + impRight, premiseSequent.left + implication) && - !K.isSameSet(botK.left + impLeft + impRight, premiseSequent.left + implication) - ) - proof.InvalidProofTactic("Left-hand side of premise + φ⇔ψ is not the same as left-hand side of conclusion + either φ⇒ψ, ψ⇒φ or both.") - else - proof.ValidProofTactic(bot, Seq(K.LeftIff(botK, -1, phiK, psiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftIff failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else - pivot.head match { - 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.") - } - } - } - - /** - *
-   *   Γ |- φ, Δ
-   * --------------
-   *   Γ, ¬φ |- Δ
-   * 
- */ - object LeftNot extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - val botK = bot.underlying - val phiK = phi.underlying - 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.") - else if (!K.isSameSet(botK.left, premiseSequent.left + negation)) - proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise + ¬φ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftNot(botK, -1, phiK)), Seq(premise)) - } - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftNot failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") - else if (!pivot.isEmpty && pivot.tail.isEmpty) - LeftNot.withParameters(pivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") - - } - } - - /** - *
-   *   Γ, φ[t/x] |- Δ
-   * -------------------
-   *   Γ, ∀x. φ |- Δ
-   *
-   * 
- */ - object LeftForall extends ProofTactic with ProofFactSequentTactic { - 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 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") - else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) - proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ") - else - proof.ValidProofTactic(bot, Seq(K.LeftForall(botK, -1, phiK, xK, tK)), Seq(premise)) - } - - def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - lazy val instantiatedPivot = premiseSequent.left // .diff(botK.left) - - if (!pivot.isEmpty) - if (pivot.tail.isEmpty) - pivot.head match { - 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 - proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") - else if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = bot.left.find(f => - f match { - case g @ F.forall(v, e) => F.isSame(e.substitute(v := t), in) - case _ => false - } - ) - - quantifiedPhi match { - 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. φ.") - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val prepivot = bot.left.diff(premiseSequent.left) - lazy val pivot = if (prepivot.isEmpty) bot.left else prepivot - lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) - - if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = pivot.find(f => - f match { - case g @ F.forall(x, phi) => UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined - case _ => false - } - ) - - quantifiedPhi match { - case Some(F.forall(x, phi)) => - LeftForall.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, 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. φ.") - } - } - - /** - *
-   *    Γ, φ |- Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ, ∃x φ|- Δ
-   *
-   * 
- */ - object LeftExists extends ProofTactic with ProofFactSequentTactic { - 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.underlying - lazy val phiK = phi.underlying - lazy val botK = bot.underlying - 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.") - else if (!K.isSameSet(botK.right, premiseSequent.right)) - proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") - else if (!K.isSameSet(botK.left + phiK, premiseSequent.left + quantified)) - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ") - else - proof.ValidProofTactic(bot, Seq(K.LeftExists(botK, -1, phiK, xK)), Seq(premise)) - } - - var debug = false - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExists failed.") - else - proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") - else if (instantiatedPivot.tail.isEmpty) { - val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = bot.left.find(f => - f match { - case F.exists(_, g) => F.isSame(g, in) - case _ => false - } - ) - - quantifiedPhi match { - 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.exists(x, phi) => LeftExists.withParameters(phi, x)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") - } - else - proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the quantified formula found.") - } - } - - /* - /** - *
-   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
-   * ---------------------------- if y is not free in φ
-   *      Γ, ∃!x. φ |- Δ
-   * 
- */ - object LeftExistsOne extends ProofTactic with ProofFactSequentTactic { - 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.underlying - lazy val phiK = phi.underlying - lazy val botK = bot.underlying - 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.") - else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) - proof.InvalidProofTactic("Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as left-hand side of premise + ∃!x. φ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftExistsOne(botK, -1, phiK, xK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExistsOne failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - instantiatedPivot.head match { - // ∃_. ∀x. _ ⇔ φ == extract ==> x, phi - 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 - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") - else if (pivot.tail.isEmpty) - pivot.head match { - case F.BinderFormula(F.ExistsOne, x, phi) => LeftExistsOne.withParameters(phi, x)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") - } - else - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") - } - } - - */ - - // Right rules - /** - *
-   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
-   * ------------------------------------
-   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
-   * 
- */ - object RightAnd extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(conjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequents = premises.map(proof.getSequent(_).underlying) - lazy val botK = bot.underlying - lazy val conjunctsK = conjuncts.map(_.underlying) - lazy val conjunction = K.multiand(conjunctsK) - - if (premises.length == 0) - proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (premises.length != conjuncts.length) - proof.InvalidProofTactic(s"Premises and conjuncts expected to be equal in number, but ${premises.length} premises and ${conjuncts.length} conjuncts received.") - else if (!K.isSameSet(botK.left, premiseSequents.map(_.left).reduce(_ union _))) - proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") - else if ( - premiseSequents.zip(conjunctsK).forall((sequent, conjunct) => K.isSubset(sequent.right, botK.right + conjunct)) // \forall i. premise_i.right \subset bot.right + phi_i - && !K.isSubset(botK.right, premiseSequents.map(_.right).reduce(_ union _) + conjunction) // bot.right \subseteq \bigcup premise_i.right - ) - proof.InvalidProofTactic("Right-hand side of conclusion + conjuncts is not the same as the union of the right-hand sides of the premises + φ∧ψ....") - else - proof.ValidProofTactic(bot, Seq(K.RightAnd(botK, Range(-1, -premises.length - 1, -1), conjunctsK)), premises) - } - - def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequents = premises.map(proof.getSequent(_)) - lazy val pivots = premiseSequents.map(_.right.diff(bot.right)) - - if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (pivots.exists(_.isEmpty)) { - val emptyIndex = pivots.indexWhere(_.isEmpty) - if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) - unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for RightAnd failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the one of the premises.") - } else if (pivots.forall(_.tail.isEmpty)) - RightAnd.withParameters(pivots.map(_.head)*)(premises*)(bot) - else - // some extraneous formulae - proof.InvalidProofTactic("Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises +φ∧ψ.") - } - } - - /** - *
-   *   Γ |- φ, Δ               Γ |- φ, ψ, Δ
-   * --------------    or    ---------------
-   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
-   * 
- */ - object RightOr extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val phiK = phi.underlying - lazy val psiK = psi.underlying - lazy val botK = bot.underlying - 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.") - else if ( - !K.isSameSet(botK.right + phiK, premiseSequent.right + phiAndPsi) && - !K.isSameSet(botK.right + psiK, premiseSequent.right + phiAndPsi) && - !K.isSameSet(botK.right + phiK + psiK, premiseSequent.right + phiAndPsi) - ) - proof.InvalidProofTactic("Right-hand side of premise + φ∧ψ is not the same as right-hand side of conclusion + either φ, ψ or both.") - else - proof.ValidProofTactic(bot, Seq(K.RightOr(botK, -1, phiK, psiK)), Seq(premise)) - } - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - - if (!pivot.isEmpty && pivot.tail.isEmpty) - pivot.head match { - case F.App(F.App(F.or, phi), psi) => - if (premiseSequent.left.contains(phi)) - RightOr.withParameters(phi, psi)(premise)(bot) - else - RightOr.withParameters(psi, phi)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer a disjunction as pivot from premise and conclusion.") - } - else - // try a rewrite, if it works, go ahead with it, otherwise malformed - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightOr failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion + φ∧ψ is not the same as right-hand side of premise + either φ, ψ or both.") - } - } - - /** - *
-   *  Γ, φ |- ψ, Δ
-   * --------------
-   *  Γ |- φ⇒ψ, Δ
-   * 
- */ - object RightImplies extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val phiK = phi.underlying - lazy val psiK = psi.underlying - lazy val botK = bot.underlying - 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.") - else if (!K.isSameSet(botK.right + psiK, premiseSequent.right + implication)) - proof.InvalidProofTactic("Right-hand side of conclusion + ψ is not the same as right-hand side of premise + φ⇒ψ.") - else - proof.ValidProofTactic(bot, Seq(K.RightImplies(botK, -1, phiK, psiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val leftPivot = premiseSequent.left.diff(bot.left) - lazy val rightPivot = premiseSequent.right.diff(bot.right) - - if ( - !leftPivot.isEmpty && leftPivot.tail.isEmpty && - !rightPivot.isEmpty && rightPivot.tail.isEmpty - ) - RightImplies.withParameters(leftPivot.head, rightPivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") - } - } - - /** - *
-   *  Γ |- φ⇒ψ, Δ    Σ |- ψ⇒φ, Π
-   * ----------------------------
-   *      Γ, Σ |- φ⇔ψ, Π, Δ
-   * 
- */ - object RightIff extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1).underlying - lazy val rightSequent = proof.getSequent(prem2).underlying - lazy val phiK = phi.underlying - lazy val psiK = psi.underlying - lazy val botK = bot.underlying - 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"[${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"[${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"[${f.repr}]") - .reduce(_ ++ ", " ++ _) - ) - else - proof.ValidProofTactic(bot, Seq(K.RightIff(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) - } - - def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(prem1) - lazy val pivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial premise for RightIff failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") - else if (pivot.tail.isEmpty) - pivot.head match { - 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 - proof.InvalidProofTactic("Right-hand side of conclusion + φ⇒ψ + ψ⇒φ is not the same as the union of the right-hand sides of the premises φ⇔ψ.") - } - } - - /** - *
-   *  Γ, φ |- Δ
-   * --------------
-   *   Γ |- ¬φ, Δ
-   * 
- */ - object RightNot extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val phiK = phi.underlying - lazy val botK = bot.underlying - 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.") - else if (!K.isSameSet(botK.right, premiseSequent.right + negation)) - proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise + ¬φ.") - else - proof.ValidProofTactic(bot, Seq(K.RightNot(botK, -1, phiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightIff failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else if (pivot.tail.isEmpty) - RightNot.withParameters(pivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") - - } - } - - /** - *
-   *    Γ |- φ, Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ |- ∀x. φ, Δ
-   * 
- */ - object RightForall extends ProofTactic with ProofFactSequentTactic { - 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.underlying - lazy val phiK = phi.underlying - lazy val botK = bot.underlying - 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.") - else 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 + phiK, premiseSequent.right + quantified)) - proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∀x. φ.") - else - proof.ValidProofTactic(bot, Seq(K.RightForall(botK, -1, phiK, xK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightForall failed.") - else - proof.InvalidProofTactic("Could not infer a pivot from the premise and conclusion.") - else if (instantiatedPivot.tail.isEmpty) { - val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = bot.right.find(f => - f match { - case F.forall(_, g) => F.isSame(g, in) - case _ => false - } - ) - - quantifiedPhi match { - 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.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. φ.") - } - } - - /** - *
-   *   Γ |- φ[t/x], Δ
-   * -------------------
-   *  Γ |- ∃x. φ, Δ
-   *
-   * (ln-x stands for locally nameless x)
-   * 
- */ - object RightExists extends ProofTactic with ProofFactSequentTactic { - 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 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") - else if (!K.isSameSet(botK.right + instantiated, premiseSequent.right + quantified)) - proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ") - else - proof.ValidProofTactic(bot, Seq(K.RightExists(botK, -1, phiK, xK, tK)), Seq(premise)) - } - - def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - if (!pivot.isEmpty) - if (pivot.tail.isEmpty) - pivot.head match { - 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 - proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") - else if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightExists failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = bot.right.find(f => - f match { - case g @ F.exists(v, e) => F.isSame(e.substitute(v := t), in) - case _ => false - } - ) - - quantifiedPhi match { - 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. φ.") - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val prepivot = bot.right.diff(premiseSequent.right) - lazy val pivot = if (prepivot.isEmpty) bot.right else prepivot - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightForall failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - val in: F.Formula = instantiatedPivot.head - - val quantifiedPhi: Option[F.Formula] = pivot.find(f => - f match { - case g @ F.exists(x, phi) => - UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined - case _ => false - } - ) - - quantifiedPhi match { - case Some(F.exists(x, phi)) => - RightExists.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).get._2.getOrElse(x, 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) ⇔ φ, Δ
-   * ---------------------------- if y is not free in φ
-   *      Γ|- ∃!x. φ,  Δ
-   * 
- */ - 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.underlying - lazy val phiK = phi.underlying - lazy val botK = bot.underlying - 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)) - ) - ) - lazy val quantified = K.BinderFormula(K.ExistsOne, xK, phiK) - - 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 + quantified)) - proof.InvalidProofTactic("Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as right-hand side of premise + ∃!x. φ.") - else - proof.ValidProofTactic(bot, Seq(K.RightExistsOne(botK, -1, phiK, xK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightExistsOne failed.") - else - proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") - else if (instantiatedPivot.tail.isEmpty) { - instantiatedPivot.head match { - // ∃_. ∀x. _ ⇔ φ == extract ==> x, 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.") - } - } 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.ExistsOne, x, phi) => RightExistsOne.withParameters(phi, x)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer an existentially 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. φ.") - } - } - - */ - - object RightBeta extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof) - (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val phiK = phi.underlying - lazy val lambdaK = lambda.underlying - lazy val tK = t.underlying - lazy val xK = x.underlying - lazy val botK = bot.underlying - lazy val y = lambda.v.underlying - lazy val e = lambda.body.underlying - if (phi.sort != K.Formula) - return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) - else if (y.sort != t.sort) - return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) - else if (e.sort != x.sort) - return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) - else if (K.isSameSet(botK.left, premiseSequent.left)) { - val redex = lambdaK(tK) - val normalized = K.substituteVariables(e, Map(y -> tK)) - val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) - val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) - if (K.isSameSet(botK.right + phi_redex, premiseSequent.right + phi_normalized) || K.isSameSet(botK.right + phi_normalized, premiseSequent.right + phi_redex)) - return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) - else - return proof.InvalidProofTactic("Right-hand side of the conclusion + φ[λy.e]t/x must be the same as right-hand side of the premise + φ[e[t/y]/x] (or the opposite)") - } else - return proof.InvalidProofTactic("Left-hand side of the conclusion must be the same as the left-hand side of the premise") - } - } - - object LeftBeta extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof) - (phi: F.Formula, lambda: F.Abs[?, ?], t: F.Expr[?], x: F.Variable[?])(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val phiK = phi.underlying - lazy val lambdaK = lambda.underlying - lazy val tK = t.underlying - lazy val xK = x.underlying - lazy val botK = bot.underlying - lazy val y = lambda.v.underlying - lazy val e = lambda.body.underlying - if (phi.sort != K.Formula) - return proof.InvalidProofTactic("φ must be a formula, but it is a " + phi.sort) - else if (y.sort != t.sort) - return proof.InvalidProofTactic("y must have the same type as t, but they are " + y.sort + " and " + t.sort) - else if (e.sort != x.sort) - return proof.InvalidProofTactic("e must have the same type as x, but they are " + e.sort + " and " + x.sort) - else if (K.isSameSet(botK.right, premiseSequent.right)) { - val redex = lambdaK(tK) - val normalized = K.substituteVariables(e, Map(y -> tK)) - val phi_redex = K.substituteVariables(phiK, Map(xK -> redex)) - val phi_normalized = K.substituteVariables(phiK, Map(xK -> normalized)) - if (K.isSameSet(botK.left + phi_redex, premiseSequent.left + phi_normalized) || K.isSameSet(botK.left + phi_normalized, premiseSequent.left + phi_redex)) - return proof.ValidProofTactic(bot, Seq(K.LeftBeta(botK, -1, phiK, lambdaK, tK, xK)), Seq(premise)) - else - return proof.InvalidProofTactic("Left-hand side of the conclusion + φ[λy.e]t/x must be the same as left-hand side of the premise + φ[e[t/y]/x] (or the opposite)") - } else - return proof.InvalidProofTactic("Right-hand side of the conclusion must be the same as the right-hand side of the premise") - } - } - - // Structural rules - /** - *
-   *     Γ |- Δ
-   * --------------
-   *   Γ, Σ |- Δ, Π
-   * 
- */ - object Weakening extends ProofTactic with ProofFactSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - - if (!F.isImplyingSequent(premiseSequent, bot)) - proof.InvalidProofTactic("Conclusion cannot be trivially derived from premise.") - else - proof.ValidProofTactic(bot, Seq(K.Weakening(bot.underlying, -1)), Seq(premise)) - } - } - - // Equality Rules - /** - *
-   *  Γ, s=s |- Δ
-   * --------------
-   *     Γ |- Δ
-   * 
- */ - object LeftRefl extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val faK = fa.underlying - lazy val botK = bot.underlying - - if (!K.isSameSet(botK.left + faK, premiseSequent.left) || !premiseSequent.left.exists(_ == faK) || botK.left.exists(_ == faK)) - proof.InvalidProofTactic("Left-hand sides of the conclusion + φ is not the same as left-hand side of the premise.") - else 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 - faK match { - 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.") - case _ => proof.InvalidProofTactic("φ is not an equality.") - } - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.left.diff(bot.left) - - if (!pivot.isEmpty && pivot.tail.isEmpty) - LeftRefl.withParameters(pivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Could not infer an equality as pivot from premise and conclusion.") - } - } - - /** - *
-   *
-   * --------------
-   *     |- s=s
-   * 
- */ - object RightRefl extends ProofTactic with ProofSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val faK = fa.underlying - lazy val botK = bot.underlying - if (!botK.right.exists(_ == faK)) - proof.InvalidProofTactic("Right-hand side of conclusion does not contain φ.") - else - faK match { - 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.") - case _ => proof.InvalidProofTactic("φ is not an equality.") - } - } - - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - if (bot.right.isEmpty) proof.InvalidProofTactic("Right-hand side of conclusion does not contain an instance of reflexivity.") - else { - // go through conclusion to see if you can find an reflexive formula - 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.App(F.App(e, l), r) => - (F.equality) == (e) && l == r // termequality - case _ => false - } - ) - - pivot match { - case Some(phi) => RightRefl.withParameters(phi)(bot) - case _ => proof.InvalidProofTactic("Could not infer an equality as pivot from conclusion.") - } - - } - - } - } - - /** - *
-   *   Γ, φ(s) |- Δ     Σ |- s=t, Π     
-   * --------------------------------
-   *        Γ, Σ φ(t) |- Δ, Π
-   * 
- */ - object LeftSubstEq extends ProofTactic { - - def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - } - - def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent1 = proof.getSequent(prem1).underlying - lazy val premiseSequent2 = proof.getSequent(prem2).underlying - lazy val botK = bot.underlying - lazy val sK = s.underlying - lazy val tK = t.underlying - lazy val varsK = vars.map(_.underlying) - val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) - val (phi_arg, phi_body) = lambdaPhiK - - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) - val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) - - val inner1 = varsK.foldLeft(sK)(_(_)) - val inner2 = varsK.foldLeft(tK)(_(_)) - val sEqt = K.equality(inner1)(inner2) - val varss = varsK.toSet - - if ( - K.isSubset(premiseSequent1.right, botK.right) && - K.isSubset(premiseSequent2.right, botK.right + sEqt) && - K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) - ) { - if ( - K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && - K.isSubset(premiseSequent2.left, botK.left) && - K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) - ) { - if ( - premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || - premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) - ) { - return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") - } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) - } - else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") - } - else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") - - - } - } - - /** - *
-   *  Γ |- φ(s), Δ     Σ |- s=t, Π
-   * ---------------------------------
-   *         Γ, Σ |- φ(t), Δ, Π
-   * 
- */ - object RightSubstEq extends ProofTactic { - def withParametersSimple[T1](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - } - - def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Formula) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent1 = proof.getSequent(prem1).underlying - lazy val premiseSequent2 = proof.getSequent(prem2).underlying - lazy val botK = bot.underlying - lazy val sK = s.underlying - lazy val tK = t.underlying - lazy val varsK = vars.map(_.underlying) - val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) - val (phi_arg, phi_body) = lambdaPhiK - - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) - val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) - - val inner1 = varsK.foldLeft(sK)(_(_)) - val inner2 = varsK.foldLeft(tK)(_(_)) - val sEqt = K.equality(inner1)(inner2) - val varss = varsK.toSet - - if ( - K.isSubset(premiseSequent1.right, botK.right) && - K.isSubset(premiseSequent2.right, botK.right + sEqt) && - K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) - ) { - if ( - K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && - K.isSubset(premiseSequent2.left, botK.left) && - K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) - ) { - if ( - premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || - premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) - ) { - proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") - } else proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) - } - else proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") - } - else proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") - } - - } - - /** - *
-   *           Γ, φ(a1,...an) |- Δ
-   * ----------------------------------------
-   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
-   * 
- */ - object LeftSubstIff extends ProofTactic { - def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - - def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - /*{ - lazy val premiseSequent1 = proof.getSequent(prem1).underlying - lazy val premiseSequent2 = proof.getSequent(prem2).underlying - lazy val botK = bot.underlying - lazy val sK = s.underlying - lazy val tK = t.underlying - lazy val varsK = vars.map(_.underlying) - val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) - val (phi_arg, phi_body) = lambdaPhiK - - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) - val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) - - val inner1 = varsK.foldLeft(sK)(_(_)) - val inner2 = varsK.foldLeft(tK)(_(_)) - val sEqt = K.Iff(inner1)(inner2) - val varss = varsK.toSet - - if ( - K.isSubset(premiseSequent1.right, botK.right) && - K.isSubset(premiseSequent2.right, botK.right + sEqt) && - K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) - ) { - if ( - K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && - K.isSubset(premiseSequent2.left, botK.left) && - K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) - ) { - if ( - premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || - premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) - ) { - return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") - } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) - } - else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") - } - else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") - */ - - } - - /** - *
-   *           Γ |- φ(a1,...an), Δ
-   * ----------------------------------------
-   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
-   * 
- */ - object RightSubstIff extends ProofTactic { - def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - - def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - - /* - 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(_.underlying), 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.forall(s_arg, acc) } - } - - 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)) - } - */ - } - - /** - *
-   *           Γ |- Δ
-   * --------------------------
-   *  Γ[r(a)/?f] |- Δ[r(a)/?f]
-   * 
- */ - object InstSchema extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof - )(map: Map[F.Variable[?], F.Expr[?]])(premise: proof.Fact): 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)) - } - } - object Subproof extends ProofTactic { - def apply(using proof: Library#Proof)(statement: Option[F.Sequent])(iProof: proof.InnerProof) = { - val bot: Option[F.Sequent] = statement - val botK: Option[K.Sequent] = statement map (_.underlying) - if (iProof.length == 0) throw (new UnimplementedProof(proof.owningTheorem)) - val scproof: K.SCProof = iProof.toSCProof - val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) - val judgement: proof.ProofTacticJudgement = { - 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: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}" - ) - else - proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) - } - 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) - if (iProof.length == 0) - throw (new UnimplementedProof(proof.owningTheorem)) - val scproof: K.SCProof = iProof.toSCProof - - val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) - def judgement: proof.ProofTacticJudgement = { - 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: ${botK.get.repr}\n\tObtained: ${scproof.conclusion.repr}") - else - proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) - } - } - - // TODO make specific support for subproofs written inside tactics.kkkkkkk - - inline def TacticSubproof(using proof: Library#Proof)(inline computeProof: proof.InnerProof ?=> Unit): proof.ProofTacticJudgement = - val iProof: proof.InnerProof = new proof.InnerProof(None) - computeProof(using iProof) - SUBPROOF(using proof)(None)(iProof).judgement.asInstanceOf[proof.ProofTacticJudgement] - - object Sorry extends ProofTactic with ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - proof.ValidProofTactic(bot, Seq(K.Sorry(bot.underlying)), Seq()) - } - } - -} diff --git a/backup/backup2/prooflib/Exports.scala b/backup/backup2/prooflib/Exports.scala deleted file mode 100644 index 836d1cac..00000000 --- a/backup/backup2/prooflib/Exports.scala +++ /dev/null @@ -1,6 +0,0 @@ -package lisa.prooflib - -object Exports { - export BasicStepTactic.* - export lisa.prooflib.SimpleDeducedSteps.* -} diff --git a/backup/backup2/prooflib/Library.scala b/backup/backup2/prooflib/Library.scala deleted file mode 100644 index 049b82bf..00000000 --- a/backup/backup2/prooflib/Library.scala +++ /dev/null @@ -1,106 +0,0 @@ -package lisa.prooflib - -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.utils.KernelHelpers.{_, given} -import lisa.utils.{_, given} - -import scala.collection.mutable.Stack as stack - -/** - * A class abstracting a [[lisa.kernel.proof.RunningTheory]] providing utility functions and a convenient syntax - * to write and use Theorems and Definitions. - * @param theory The inner RunningTheory - */ -abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.ProofsHelpers { - - val theory: RunningTheory - given library: this.type = this - given RunningTheory = theory - - export lisa.kernel.proof.SCProof - - val K = lisa.utils.K - val SC: SequentCalculus.type = K.SC - private[prooflib] val F = lisa.fol.FOL - import F.{given} - - var last: Option[JUSTIFICATION] = None - - // Options for files - private[prooflib] var _withCache: Boolean = false - def withCache(using file: sourcecode.File, om: OutputManager)(): Unit = - if last.nonEmpty then om.output(OutputManager.WARNING("Warning: withCache option should be used before the first definition or theorem.")) - else _withCache = true - - private[prooflib] var _draft: Option[sourcecode.File] = None - def draft(using file: sourcecode.File, om: OutputManager)(): Unit = - if last.nonEmpty then om.output(OutputManager.WARNING("Warning: draft option should be used before the first definition or theorem.")) - else _draft = Some(file) - def isDraft = _draft.nonEmpty - - 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.Constant[?]): Unit = - theory.addSymbol(s.underlying) - knownDefs.update(s, None) - - 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.Constant[?]): Option[JUSTIFICATION] = shortDefs.get(label) match { - case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) - case Some(value) => value - } - - /** - * An alias to create a Theorem - */ - def makeTheorem(name: String, statement: K.Sequent, proof: K.SCProof, justifications: Seq[theory.Justification]): K.Judgement[theory.Theorem] = - theory.theorem(name, statement, proof, justifications) - - // 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 makeSimpleDefinition(symbol: String, expression: K.Expression): K.Judgement[theory.Definition] = - theory.definition(symbol, expression) - - - /** - * Prints a short representation of the given theorem or definition - */ - def show(using om: OutputManager)(thm: JUSTIFICATION) = { - if (thm.withSorry) om.output(thm.repr, Console.YELLOW) - else om.output(thm.repr, Console.GREEN) - } - - /** - * Prints a short representation of the last theorem or definition introduced - */ - def show(using om: OutputManager): Unit = last match { - case Some(value) => show(value) - case None => throw new NoSuchElementException("There is nothing to show: No theorem or definition has been proved yet.") - } - -} diff --git a/backup/backup2/prooflib/OutputManager.scala b/backup/backup2/prooflib/OutputManager.scala deleted file mode 100644 index bc7442ef..00000000 --- a/backup/backup2/prooflib/OutputManager.scala +++ /dev/null @@ -1,52 +0,0 @@ -package lisa.prooflib - -import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.{_, given} - -import java.io.PrintWriter -import java.io.StringWriter - -abstract class OutputManager { - - given OutputManager = this - - def output(s: String): Unit = stringWriter.write(s + "\n") - def output(s: String, color: String): Unit = stringWriter.write(Console.RESET + color + s + "\n" + Console.RESET) - val stringWriter: StringWriter - - def finishOutput(exception: Exception): Nothing - - def lisaThrow(le: LisaException): Nothing = - le match { - case ule: UserLisaException => - ule.fixTrace() - output(ule.showError) - finishOutput(ule) - - case e: LisaException.InvalidKernelJustificationComputation => - e.proof match { - case Some(value) => output(lisa.prooflib.ProofPrinter.prettyProof(value)) - case None => () - } - output(e.underlying.repr) - finishOutput(e) - - } - - def log(e: Exception): Unit = { - stringWriter.write("\n[" + Console.RED + "Error" + Console.RESET + "] ") - e.printStackTrace(PrintWriter(stringWriter)) - output(Console.RESET) - } - -} -object OutputManager { - def RED(s: String): String = Console.RED + s + Console.RESET - def GREEN(s: String): String = Console.GREEN + s + Console.RESET - def BLUE(s: String): String = Console.BLUE + s + Console.RESET - def YELLOW(s: String): String = Console.YELLOW + s + Console.RESET - def CYAN(s: String): String = Console.CYAN + s + Console.RESET - def MAGENTA(s: String): String = Console.MAGENTA + s + Console.RESET - - def WARNING(s: String): String = Console.YELLOW + "⚠ " + s + Console.RESET -} diff --git a/backup/backup2/prooflib/ProofPrinter.scala b/backup/backup2/prooflib/ProofPrinter.scala deleted file mode 100644 index 96c95137..00000000 --- a/backup/backup2/prooflib/ProofPrinter.scala +++ /dev/null @@ -1,129 +0,0 @@ -package lisa.prooflib - -import lisa.kernel.proof.SCProofCheckerJudgement -import lisa.prooflib.BasicStepTactic.SUBPROOF -import lisa.prooflib.Library -import lisa.prooflib.* -import lisa.utils.* - -object ProofPrinter { - private def spaceSeparator(compact: Boolean): String = if (compact) "" else " " - - private def commaSeparator(compact: Boolean, symbol: String = ","): String = s"$symbol${spaceSeparator(compact)}" - - private def prettyProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { - def computeMaxNumberingLengths(proof: Library#Proof, level: Int, result: IndexedSeq[Int]): IndexedSeq[Int] = { - val resultWithCurrent = result.updated( - level, - (Seq((proof.getSteps.size - 1).toString.length, result(level)) ++ (if (proof.getImports.nonEmpty) Seq((-proof.getImports.size).toString.length) else Seq.empty)).max - ) - proof.getSteps - .collect { case ps: proof.ProofStep if ps.tactic.isInstanceOf[SUBPROOF] => ps.tactic.asInstanceOf[SUBPROOF] } - .foldLeft(resultWithCurrent)((acc, sp) => computeMaxNumberingLengths(sp.iProof, level + 1, if (acc.size <= level + 1) acc :+ 0 else acc)) - } - - val maxNumberingLengths = computeMaxNumberingLengths(printedProof, 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 prettyProofRecursive(printedProof: Library#Proof, level: Int, tree: IndexedSeq[Int], topMostIndices: IndexedSeq[Int]): Seq[(Boolean, String, String, String)] = { - val imports = printedProof.getImports - val steps = printedProof.getSteps - val printedImports = imports.zipWithIndex.reverse.flatMap { case (imp, i) => - val currentTree = tree :+ (-i - 1) - - val showErrorForLine: Boolean = error match { - case None => false - case Some((position, message)) => 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._2.toString() - ) - - Seq(pretty("Import", 0)) - } - printedImports ++ steps.zipWithIndex.flatMap { case (step, i) => - val currentTree = tree :+ i - val showErrorForLine: Boolean = error match { - case None => false - case Some((position, message)) => 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.toString() - ) - - step.tactic match { - case sp: SUBPROOF => - val topSteps: Seq[Int] = sp.premises.map((f: sp.proof.Fact) => sp.proof.sequentAndIntOfFact(f)._2) - pretty("Subproof", topSteps*) +: prettyProofRecursive(sp.iProof, level + 1, currentTree, (if (i == 0) topMostIndices else IndexedSeq.empty) :+ i) - case other => - val line = pretty(other.name) - Seq(line) - } - } - } - - val marker = "->" - - val lines = prettyProofRecursive(printedProof, 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 (error.isDefined) (if (isMarked) marker else leftPadSpaces("", marker.length)) +: suffix else suffix - full.mkString(" ") - } - ++ (error match { - case None => Nil - case Some((path, message)) => List(s"\nProof checker has reported an error at line ${path.mkString(".")}: $message") - }) - } - - def prettyFullProofLines(printedProof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): Seq[String] = { - printedProof match { - case proof: Library#Proof#InnerProof => - prettyFullProofLines(proof.parent, None) ++ prettyProofLines(proof, error).map(" " + _) - case proof: Library#BaseProof => - prettyProofLines(proof, None) - } - } - - def prettyProof(proof: Library#Proof): String = prettyFullProofLines(proof, None).mkString("\n") - def prettyProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyFullProofLines(proof, None).mkString("\n" + (" " * indent)) - - def prettyProof(proof: Library#Proof, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, error).mkString("\n") - def prettyProof(proof: Library#Proof, indent: Int, error: Option[(IndexedSeq[Int], String)]): String = prettyFullProofLines(proof, None).mkString("\n" + " " * indent) - - def prettySimpleProof(proof: Library#Proof): String = prettyProofLines(proof, None).mkString("\n") - def prettySimpleProof(proof: Library#Proof, indent: Int): String = (" " * indent) + prettyProofLines(proof, None).mkString("\n" + (" " * indent)) - - 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/backup/backup2/prooflib/ProofTacticLib.scala b/backup/backup2/prooflib/ProofTacticLib.scala deleted file mode 100644 index c107e724..00000000 --- a/backup/backup2/prooflib/ProofTacticLib.scala +++ /dev/null @@ -1,66 +0,0 @@ -package lisa.prooflib - -import lisa.fol.FOL as F -import lisa.prooflib.* -import lisa.utils.K -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. - */ - trait ProofTactic { - val name: String = this.getClass.getName.split('$').last - given ProofTactic = this - - } - - trait OnlyProofTactic { - def apply(using lib: Library, proof: lib.Proof): proof.ProofTacticJudgement - } - - trait ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement - } - - trait ProofFactTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact): proof.ProofTacticJudgement - } - trait ProofFactSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement - } - - class UnapplicableProofTactic(val tactic: ProofTactic, proof: Library#Proof, errorMessage: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { - override def fixTrace(): Unit = { - val start = getStackTrace.indexWhere(elem => { - !elem.getClassName.contains(tactic.name) - }) + 1 - setStackTrace(getStackTrace.take(start)) - } - - def 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() - Console.RED + proof.owningTheorem.prettyGoal + Console.RESET + "\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 - } - } - - class UnimplementedProof(val theorem: Library#THM)(using sourcecode.Line, sourcecode.File) extends UserLisaException("Unimplemented Theorem") { - def showError: String = s"Theorem ${theorem.name}" - } - case class UnexpectedProofTacticFailureException(failure: Library#Proof#InvalidProofTactic, errorMessage: String)(using sourcecode.Line, sourcecode.File) - 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:" + - ProofPrinter.prettyProof(failure.proof) - } - -} diff --git a/backup/backup2/prooflib/ProofsHelpers.scala b/backup/backup2/prooflib/ProofsHelpers.scala deleted file mode 100644 index 46d75d7f..00000000 --- a/backup/backup2/prooflib/ProofsHelpers.scala +++ /dev/null @@ -1,356 +0,0 @@ -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 om: OutputManager, name: sourcecode.FullName, 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 - () |- iff.#@(appliedCst).#@(right).asInstanceOf[Formula] - else - () |- equality.#@(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 RightBeta.withParameters(appliedCst === right, lam, vAbs, freshX) - 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(propCst) by BasicStepTactic.RightSubstEq.withParametersSimple[T](appliedCst, body, Seq(), (epsilonVar, inner))(j, lastStep) - ??? - } - - 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/backup/backup2/prooflib/SimpleDeducedSteps.scala b/backup/backup2/prooflib/SimpleDeducedSteps.scala deleted file mode 100644 index 2ce8f574..00000000 --- a/backup/backup2/prooflib/SimpleDeducedSteps.scala +++ /dev/null @@ -1,331 +0,0 @@ -package lisa.prooflib - -import lisa.fol.FOL as F -import lisa.prooflib.BasicStepTactic.* -import lisa.prooflib.ProofTacticLib.{_, given} -import lisa.prooflib.* -import lisa.utils.K -import lisa.utils.KernelHelpers.{_, given} - -object SimpleDeducedSteps { -/* - - object Restate extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = - unwrapTactic(RewriteTrue(bot))("Attempted true rewrite during tactic Restate failed.") - - // (proof.ProofStep | proof.OutsideFact | Int) is definitionally equal to proof.Fact, but for some reason - // scala compiler doesn't resolve the overload with a type alias, dependant type and implicit parameter - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") - - def from(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") - - } - - object Discharge extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(premise: proof.Fact): proof.ProofTacticJudgement = { - val ss = premises zip (premises map (e => proof.getSequent(e))) - val seqs = ss.map(_._2) - if (!seqs.forall(_.right.size == 1)) - return proof.InvalidProofTactic("When discharging this way, the discharged sequent must have only a single formula on the right handside.") - val seqAny = ss.find((_, s) => premise.statement.left.exists(f2 => F.isSame(s.right.head, f2))) - if (seqAny.isEmpty) - Restate.from(premise)(premise.statement) - else - TacticSubproof: ip ?=> - ss.foldLeft(premise: ip.Fact)((prem, discharge) => - val seq = discharge._2 - if prem.statement.left.exists(f => F.isSame(f, seq.right.head)) then - val goal = prem.statement - - * Γ ⊢ ∀x.ψ, Δ - * ------------------------- - * Γ |- ψ[t/x], Δ - * - * - * - * Returns a subproof containing the instantiation steps - */ - object InstantiateForall extends ProofTactic with ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: F.Formula, t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val botK = bot.underlying - val phiK = phi.underlying - val tK = t map (_.underlying) - val premiseSequent = proof.getSequent(premise) - val premiseSequentK = premiseSequent.underlying - if (!premiseSequent.right.contains(phi)) { - proof.InvalidProofTactic("Input formula was not found in the RHS of the premise sequent.") - } else { - val emptyProof = K.SCProof(IndexedSeq(), IndexedSeq(premiseSequentK)) - val j = proof.ValidProofTactic(bot, Seq(K.Restate(premiseSequentK, -1)), Seq(premise)) - val res = tK.foldLeft((emptyProof, phiK, j: proof.ProofTacticJudgement)) { case ((p, f, j), t) => - j match { - case proof.InvalidProofTactic(_) => (p, f, j) // propagate error - case proof.ValidProofTactic(_, _, _) => - // good state, continue instantiating - // by construction the premise is well-formed - // verify the formula structure and instantiate - f match { - case psi @ K.Forall(K.Lambda(x, inner)) => - val tempVar = K.Variable(K.freshId(psi.freeVariables.map(_.id), x.id), K.Term) - // instantiate the formula with input - 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, K.substituteVariables(inner, Map(x -> tempVar)) , tempVar, t) - val p2 = K.Cut(con, -1, 1, f) - - /** - * in = ψ[t/x] - * - * s1 = Γ ⊢ ∀x.ψ, Δ Premise - * con = Γ ⊢ ψ[t/x], Δ Result - * - * p0 = ψ[t/x] ⊢ ψ[t/x] Hypothesis - * p1 = ∀x.ψ ⊢ ψ[t/x] LeftForall p0 - * p2 = Γ ⊢ ψ[t/x], Δ Cut s1, p1 - */ - val newStep = K.SCSubproof(K.SCProof(IndexedSeq(p0, p1, p2), IndexedSeq(p.conclusion)), Seq(p.length - 1)) - ( - p withNewSteps IndexedSeq(newStep), - in, - j - ) - case _ => - (p, f, proof.InvalidProofTactic("Input formula is not universally quantified")) - } - } - } - - res._3 match { - case proof.InvalidProofTactic(_) => res._3 - case proof.ValidProofTactic(_, _, _) => { - 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${res._1.conclusion.repr}\ninstead of input sequent\n\t${botK.repr}") - } - } - } - } - - def apply(using lib: Library, proof: lib.Proof)(t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val prem = proof.getSequent(premise) - if (prem.right.tail.isEmpty) { - // well formed - apply(using lib, proof)(prem.right.head, t*)(premise)(bot): proof.ProofTacticJudgement - } else proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") - } - - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - try { - val sp = TacticSubproof { - // lazy val premiseSequent = proof.getSequent(premise) - val s1 = lib.have(bot +<< bot.right.head) by Restate - lib.have(bot) by LeftForall(s1) - } - BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") - } catch { - case e: Exception => proof.InvalidProofTactic("Impossible to justify desired step with instantiation.") - } - - } - - } - - */ - - - /* - /** - * Performs a cut when the formula to be used as pivot for the cut is - * inside a conjunction, preserving the conjunction structure - * - *
-   *
-   * PartialCut(ϕ, ϕ ∧ ψ)(left, right) :
-   *
-   *     left: Γ ⊢ ϕ ∧ ψ, Δ      right: ϕ, Σ ⊢ γ1 , γ2, …, γn
-   * -----------------------------------------------------------
-   *            Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn
-   *
-   * 
- */ - object PartialCut extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, conjunction: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val leftSequent = proof.getSequent(prem1) - val rightSequent = proof.getSequent(prem2) - - if (leftSequent.right.contains(conjunction)) { - - if (rightSequent.left.contains(phi)) { - // check conjunction matches with phi - conjunction match { - case K.ConnectorFormula(K.And, s: Seq[K.Formula]) => { - if (s.contains(phi)) { - // construct proof - - val psi: Seq[K.Formula] = s.filterNot(_ == phi) - val newConclusions: Set[K.Formula] = rightSequent.right.map((f: K.Formula) => K.ConnectorFormula(K.And, f +: psi)) - - val Sigma: Set[K.Formula] = rightSequent.left - phi - - val p0 = K.Weakening(rightSequent ++<< (psi |- ()), -2) - val p1 = K.RestateTrue(psi |- psi) - - // TODO: can be abstracted into a RightAndAll step - val emptyProof = SCProof(IndexedSeq(), IndexedSeq(p0.bot, p1.bot)) - val proofRightAndAll = rightSequent.right.foldLeft(emptyProof) { case (p, gamma) => - p withNewSteps IndexedSeq(K.RightAnd(p.conclusion ->> gamma +>> K.ConnectorFormula(K.And, gamma +: psi), Seq(p.length - 1, -2), gamma +: psi)) - } - - val p2 = K.SCSubproof(proofRightAndAll, Seq(0, 1)) - val p3 = K.Restate(Sigma + conjunction |- newConclusions, 2) // sanity check and correct form - val p4 = K.Cut(bot, -1, 3, conjunction) - - /** - * newConclusions = ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn - * - * left = Γ ⊢ ϕ ∧ ψ, Δ Premise - * right = ϕ, Σ ⊢ γ1 , γ2, …, γn Premise - * - * p0 = ϕ, Σ, ψ ⊢ γ1 , γ2, …, γn Weakening on right - * p1 = ψ ⊢ ψ Hypothesis - * p2 = Subproof: - * 2.1 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , γ2, …, γn RightAnd on p0 and p1 with ψ ∧ γ1 - * 2.2 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , ψ ∧ γ2, …, γn RightAnd on 2.1 and p1 ψ ∧ γ2 - * ... - * 2.n = ϕ, Σ, ψ ⊢ ψ ∧ γ1, ψ ∧ γ2, …, ψ ∧ γn RightAnd on 2.(n-1) and p1 with ψ ∧ γn - * - * p3 = ϕ ∧ ψ, Σ ⊢ ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Rewrite on p2 (just to have a cleaner form) - * p2 = Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Cut on left, p1 with ϕ ∧ ψ - * - * p2 is the result - */ - - proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3, p4), Seq(prem1, prem2)) - } else { - proof.InvalidProofTactic("Input conjunction does not contain the pivot.") - } - } - case _ => proof.InvalidProofTactic("Input not a conjunction.") - } - } else { - proof.InvalidProofTactic("Input pivot formula not found in right premise.") - } - } else { - proof.InvalidProofTactic("Input conjunction not found in first premise.") - } - } - } - - object destructRightAnd extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val conc = proof.getSequent(prem) - val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) - val p1 = K.LeftAnd(emptySeq +<< (a /\ b) +>> a, 0, a, b) - val p2 = K.Cut(conc ->> (a /\ b) ->> (b /\ a) +>> a, -1, 1, a /\ b) - proof.ValidProofTactic(IndexedSeq(p0, p1, p2), Seq(prem)) - } - } - object destructRightOr extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val conc = proof.getSequent(prem) - val mat = conc.right.find(f => K.isSame(f, a \/ b)) - if (mat.nonEmpty) { - - val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) - val p1 = K.Hypothesis(emptySeq +<< b +>> b, b) - - val p2 = K.LeftOr(emptySeq +<< (a \/ b) +>> a +>> b, Seq(0, 1), Seq(a, b)) - val p3 = K.Cut(conc ->> mat.get +>> a +>> b, -1, 2, a \/ b) - proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3), Seq(prem)) - } else { - proof.InvalidProofTactic("Premise does not contain the union of the given formulas") - } - - } - } - - object GeneralizeToForall extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val sequent = proof.getSequent(prem) - if (sequent.right.contains(phi)) { - val emptyProof = SCProof(IndexedSeq(), IndexedSeq(sequent)) - val j = proof.ValidProofTactic(IndexedSeq(K.Restate(sequent, proof.length - 1)), Seq[proof.Fact]()) - - val res = t.foldRight(emptyProof: SCProof, phi: K.Formula, j: proof.ProofTacticJudgement) { case (x1, (p1: SCProof, phi1, j1)) => - j1 match { - case proof.InvalidProofTactic(_) => (p1, phi1, j1) - case proof.ValidProofTactic(_, _) => { - if (!p1.conclusion.right.contains(phi1)) - (p1, phi1, proof.InvalidProofTactic("Formula is not present in the lass sequent")) - - val proofStep = K.RightForall(p1.conclusion ->> phi1 +>> forall(x1, phi1), p1.length - 1, phi1, x1) - ( - p1 appended proofStep, - forall(x1, phi1), - j1 - ) - } - } - } - - res._3 match { - case proof.InvalidProofTactic(_) => res._3 - case proof.ValidProofTactic(_, _) => proof.ValidProofTactic((res._1.steps appended K.Restate(bot, res._1.length - 1)), Seq(prem)) - } - - } else proof.InvalidProofTactic("RHS of premise sequent contains not phi") - - } - } - - object GeneralizeToForallNoForm extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - if (proof.getSequent(prem).right.tail.isEmpty) - GeneralizeToForall.apply(using lib, proof)(proof.getSequent(prem).right.head, t*)(prem)(bot): proof.ProofTacticJudgement - else - proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") - } - - } - - object ByCase extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val nphi = !phi - - val pa = proof.getSequent(prem1) - val pb = proof.getSequent(prem2) - val (leftAphi, leftBnphi) = (pa.left.find(K.isSame(_, phi)), pb.left.find(K.isSame(_, nphi))) - if (leftAphi.nonEmpty && leftBnphi.nonEmpty) { - val p2 = K.RightNot(pa -<< leftAphi.get +>> nphi, -1, phi) - val p3 = K.Cut(pa -<< leftAphi.get ++ (pb -<< leftBnphi.get), 0, -2, nphi) - val p4 = K.Restate(bot, 1) - proof.ValidProofTactic(IndexedSeq(p2, p3, p4), IndexedSeq(prem1, prem2)) // TODO: Check pa/pb orDer - - } else { - proof.InvalidProofTactic("Premises have not the right syntax") - } - } - } - */ -} diff --git a/backup/backup2/prooflib/WithTheorems.scala b/backup/backup2/prooflib/WithTheorems.scala deleted file mode 100644 index d8f3e930..00000000 --- a/backup/backup2/prooflib/WithTheorems.scala +++ /dev/null @@ -1,649 +0,0 @@ -package lisa.prooflib - -import lisa.kernel.proof.RunningTheory -import lisa.prooflib.ProofTacticLib.ProofTactic -import lisa.prooflib.ProofTacticLib.UnimplementedProof -import lisa.prooflib.* -import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.LisaException -import lisa.utils.UserLisaException -import lisa.utils.UserLisaException.* - -import scala.annotation.nowarn -import scala.collection.mutable.Buffer as mBuf -import scala.collection.mutable.Map as mMap -import scala.collection.mutable.Stack as stack - -trait WithTheorems { - library: Library => - - /** - * The main builder for proofs. It is a mutable object that can be used to build a proof step by step. - * It is used either to construct a theorem/lemma ([[BaseProof]]) or to construct a subproof ([[InnerProof]]). - * We can add proof tactics to it producing intermediate results. In the end, obtain a [[K.SCProof]] from it. - * - * @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 - type Fact = ProofStep | InstantiatedFact | OutsideFact | Int - - /** - * A proven fact (from a previously proven step, a theorem or a definition) with specific instantiations of free variables. - * - * @param fact The base fact - * @param insts The instantiation of free variables - */ - case class InstantiatedFact( - fact: Fact, - insts: Seq[F.SubstPair | F.Term] - ) { - 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 (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) - val (s2, p2) = if terms.isEmpty then (s1, p1) else s1.instantiateForallWithProof(terms, p1.length - 1) - (s2, p1 ++ p2) - } - - } - - val library: WithTheorems.this.type = WithTheorems.this - - private var steps: List[ProofStep] = Nil - private var imports: List[(OutsideFact, F.Sequent)] = Nil - private var instantiatedFacts: List[(InstantiatedFact, Int)] = Nil - private var assumptions: List[F.Formula] = assump - private var eliminations: List[(F.Formula, (Int, F.Sequent) => List[K.SCProofStep])] = Nil - - def cleanAssumptions: Unit = assumptions = Nil - - /** - * the theorem that is being proved (paritally, if subproof) by this proof. - * - * @return The theorem - */ - def owningTheorem: THM - - /** - * A proof step, containing a high level ProofTactic and the corresponding K.SCProofStep. If the tactic produce more than one - * step, they must be encapsulated in a subproof. Usually constructed with [[ValidProofTactic.validate]] - * - * @param judgement The result of the tactic - * @param scps The corresponding [[K.SCProofStep]] - * @param position The position of the step in the proof - */ - case class ProofStep private (judgement: ValidProofTactic, scps: K.SCProofStep, position: Int) { - val bot: F.Sequent = judgement.bot - def innerBot: K.Sequent = scps.bot - val host: Proof.this.type = Proof.this - - def tactic: ProofTactic = judgement.tactic - - } - private object ProofStep { // TODO - def newProofStep(judgement: ValidProofTactic): ProofStep = { - val ps = ProofStep( - judgement, - SC.SCSubproof( - K.SCProof(judgement.scps.toIndexedSeq, judgement.imports.map(f => sequentOfFact(f).underlying).toIndexedSeq), - judgement.imports.map(sequentAndIntOfFact(_)._2) - ), - steps.length - ) - addStep(ps) - ps - - } - } - - /** - * A proof step can be constructed from a succesfully executed tactic - */ - def newProofStep(judgement: ValidProofTactic): ProofStep = - ProofStep.newProofStep(judgement) - - private def addStep(ds: ProofStep): Unit = steps = ds :: steps - private def addImport(imp: OutsideFact, seq: F.Sequent): Unit = { - imports = (imp, seq) :: imports - } - - private def addInstantiatedFact(instFact: InstantiatedFact): Unit = { - val step = ValidProofTactic(instFact.result, instFact.proof, Seq(instFact.fact))(using F.SequentInstantiationRule) - newProofStep(step) - instantiatedFacts = (instFact, steps.length - 1) :: instantiatedFacts - } - - /** - * Add an assumption the the proof, i.e. a formula that is automatically on the left side of the sequent. - * - * @param f - */ - def addAssumption(f: F.Formula): Unit = { - if (!assumptions.contains(f)) assumptions = f :: assumptions - } - - def addElimination(f: F.Formula, elim: (Int, F.Sequent) => List[K.SCProofStep]): Unit = { - eliminations = (f, elim) :: eliminations - } - - def addDischarge(ji: Fact): Unit = { - val (s1, t1) = sequentAndIntOfFact(ji) - val f = s1.right.head - val fu = f.underlying - addElimination( - f, - (i, sequent) => - List( - SC.Cut((sequent.underlying -<< fu) ++ (s1.underlying ->> fu), t1, i, fu) - ) - ) - } - /* - 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 - - /** - * Favour using getSequent when applicable. - * @return The list of ValidatedSteps (containing a high level ProofTactic and the corresponding K.SCProofStep). - */ - def getSteps: List[ProofStep] = steps.reverse - - /** - * Favour using getSequent when applicable. - * @return The list of Imports validated in the formula, with their original justification. - */ - def getImports: List[(OutsideFact, F.Sequent)] = imports.reverse - - /** - * @return The list of formulas that are assumed for the reminder of the proof. - */ - def getAssumptions: List[F.Formula] = assumptions - - /** - * Produce the low level [[K.SCProof]] corresponding to the proof. Automatically eliminates any formula in the discharges that is still left of the sequent. - * - * @return - */ - def toSCProof: K.SCProof = { - import lisa.utils.KernelHelpers.{-<<, ->>} - val finalSteps = eliminations.foldLeft[(List[SC.SCProofStep], F.Sequent)]((steps.map(_.scps), steps.head.bot)) { (cumul_bot, f_elim) => - val (cumul, bot) = cumul_bot - val (f, elim) = f_elim - val i = cumul.size - val elimSteps = elim(i - 1, bot) - (elimSteps.foldLeft(cumul)((cumul2, step) => step :: cumul2), bot -<< f) - } - - val r = K.SCProof(finalSteps._1.reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) - r - } - - def currentSCProof: K.SCProof = K.SCProof(steps.map(_.scps).reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) - - /** - * For a fact, returns the sequent that the fact proove and the position of the fact in the proof. - * - * @param fact Any fact, possibly instantiated, belonging to the proof - * @return its proven sequent and position - */ - def sequentAndIntOfFact(fact: Fact): (F.Sequent, Int) = fact match { - case i: Int => - ( - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(steps.length - i - 1).bot - else { - val i2 = -(i + 1) - if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") - else imports(imports.length + i)._2 - }, - i - ) - case ds: ProofStep => (ds.bot, ds.position) - case instFact: InstantiatedFact => - val r = instantiatedFacts.find(instFact == _._1) - r match { - case Some(value) => (instFact.result, value._2) - case None => - addInstantiatedFact(instFact) - (instFact.result, steps.length - 1) - } - case of: OutsideFact @unchecked => - val r = imports.indexWhere(of == _._1) - if (r != -1) { - (imports(r)._2, r - imports.length) - } else { - val r2 = sequentOfOutsideFact(of) - addImport(of, r2) - (r2, -imports.length) - } - } - - def sequentOfFact(fact: Fact): F.Sequent = fact match { - case i: Int => - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(steps.length - i - 1).bot - else { - val i2 = -(i + 1) - if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") - else imports(imports.length + i)._2 - } - case ds: ProofStep => ds.bot - case instfact: InstantiatedFact => instfact.result - case of: OutsideFact @unchecked => - val r = imports.find(of == _._1) - if (r.nonEmpty) { - r.get._2 - } else { - sequentOfOutsideFact(of) - } - } - - def sequentOfOutsideFact(of: OutsideFact): F.Sequent - - def getSequent(f: Fact): F.Sequent = sequentOfFact(f) - def mostRecentStep: ProofStep = steps.head - - /** - * The number of steps in the proof. This is not the same as the number of steps in the corresponding [[K.SCProof]]. - * This also does not count the number of steps in the subproof. - * - * @return - */ - def length: Int = steps.length - - /** - * The set of symbols that can't be instantiated because they are free in an assumption. - */ - 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. - */ - def asOutsideFact(j: JUSTIFICATION): OutsideFact - - def depth: Int = - (this: @unchecked) match { - case p: Proof#InnerProof => 1 + p.parent.depth - case _: BaseProof => 0 - } - - /** - * Create a subproof inside the current proof. The subproof will have the same assumptions as the current proof. - * Can have a goal known in advance (usually for a user-written subproof) or not (usually for a tactic-generated subproof). - */ - def newInnerProof(possibleGoal: Option[F.Sequent]) = new InnerProof(possibleGoal) - final class InnerProof(val possibleGoal: Option[F.Sequent]) extends Proof(this.getAssumptions) { - val parent: Proof.this.type = Proof.this - val owningTheorem: THM = parent.owningTheorem - type OutsideFact = parent.Fact - override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = parent.asOutsideFact(j) - - override def sequentOfOutsideFact(of: parent.Fact): F.Sequent = of match { - case j: JUSTIFICATION => j.statement - case ds: Proof#ProofStep => ds.bot - case _ => parent.sequentOfFact(of) - } - } - - /** - * Contains the result of a tactic computing a K.SCProofTactic. - * Can be successful or unsuccessful. - */ - sealed abstract class ProofTacticJudgement { - val tactic: ProofTactic - val proof: Proof = Proof.this - - /** - * Returns true if and only if the judgement is valid. - */ - def isValid: Boolean = this match { - case ValidProofTactic(_, _, _) => true - case InvalidProofTactic(_) => false - } - - def validate(line: sourcecode.Line, file: sourcecode.File): ProofStep = { - this match { - case vpt: ValidProofTactic => newProofStep(vpt) - case ipt: InvalidProofTactic => - val e = lisa.prooflib.ProofTacticLib.UnapplicableProofTactic(ipt.tactic, ipt.proof, ipt.message)(using line, file) - e.setStackTrace(ipt.stack) - throw e - } - } - } - - /** - * A Kernel Sequent Calculus proof step that has been correctly produced. - */ - case class ValidProofTactic(bot: lisa.fol.FOL.Sequent, scps: Seq[K.SCProofStep], imports: Seq[Fact])(using val tactic: ProofTactic) extends ProofTacticJudgement {} - - /** - * A proof step which led to an error when computing the corresponding K.Sequent Calculus proof step. - */ - case class InvalidProofTactic(message: String)(using val tactic: ProofTactic) extends ProofTacticJudgement { - 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 - override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = j - - override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement - - def justifications: List[JUSTIFICATION] = getImports.map(_._1) - } - - /** - * Abstract class representing theorems, axioms and different kinds of definitions. Corresponds to a [[theory.Justification]]. - */ - sealed abstract class JUSTIFICATION { - - /** - * A pretty representation of the justification - */ - def repr: String - - /** - * The inner kernel justification - */ - def innerJustification: theory.Justification - - /** - * The sequent that the justification proves - */ - def statement: F.Sequent - - /** - * The complete name of the justification. Two justifications should never have the same full name. Typically, path is used to disambiguate. - */ - def fullName: String - - /** - * The short name of the justification (without the path). - */ - val name: String = fullName.split("\\.").last - - /** - * The "owning" object of the justification. Typically, the package/object in which it is defined. - */ - val owner = fullName.split("\\.").dropRight(1).mkString(".") - - /** - * Returns if the statement is unconditionaly proven or if it depends on some sorry step (including in the other justifications it relies on) - */ - def withSorry: Boolean = innerJustification match { - case thm: theory.Theorem => thm.withSorry - case d: theory.Definition => false - case ax: theory.Axiom => false - } - } - - /** - * A Justification, corresponding to [[K.Axiom]] - */ - class AXIOM(innerAxiom: theory.Axiom, val axiom: F.Formula, val fullName: String) extends JUSTIFICATION { - def innerJustification: theory.Axiom = innerAxiom - val statement: F.Sequent = F.Sequent(Set(), Set(axiom)) - if (statement.underlying != theory.sequentFromJustification(innerAxiom)) { - throw new InvalidAxiomException("The provided kernel axiom and desired statement don't match.", name, axiom, library) - } - def repr: String = s" Axiom $name := $axiom" - } - - /** - * Introduces a new axiom in the theory. - * - * @param fullName The name of the axiom, including the path. Usually fetched automatically by the compiler. - * @param axiom The axiomatized formula. - * @return - */ - def Axiom(using fullName: sourcecode.FullName)(axiom: F.Formula): AXIOM = { - val ax: Option[theory.Axiom] = theory.addAxiom(fullName.value, axiom.underlying) - ax match { - case None => throw new InvalidAxiomException("Not all symbols belong to the theory", fullName.value, axiom, library) - case Some(value) => AXIOM(value, axiom, fullName.value) - } - } - - /** - * A Justification, corresponding to [[K.FunctionDefinition]] or [[K.PredicateDefinition]] - */ - abstract class DEFINITION(line: Int, file: String) extends JUSTIFICATION { - val fullName: String - def repr: String = innerJustification.repr - - def cst: F.Constant[?] - knownDefs.update(cst, Some(this)) - - } - - /** - * A proven, reusable statement. A justification corresponding to [[K.Theorem]]. - */ - sealed abstract class THM extends JUSTIFICATION { - def repr: String = - s" Theorem ${name} := ${statement}${if (withSorry) " (!! Relies on Sorry)" else ""}" - - /** - * The underlying Kernel proof [[K.SCProof]], if it is still available. Proofs are not kept in memory for efficiency. - */ - def kernelProof: Option[K.SCProof] - - /** - * The high level [[Proof]], if one was used to obtain the theorem. If the theorem was not produced by such high level proof but directly by a low level one, this is None. - */ - def highProof: Option[BaseProof] - val innerJustification: theory.Theorem - - /** - * A pretty representation of the goal of the theorem - */ - def prettyGoal: String = statement.underlying.repr - } - object THM { - - /** - * Standard way to construct a theorem using a high level proof. - * - * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] - * @param statement The statement of the theorem - * @param fullName The full name of the theorem, including the path. Usually fetched automatically by the compiler. - * @param line The line at which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting - * @param file The file in which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting - * @param kind The kind of theorem (Theorem, Lemma, Corollary) - * @param computeProof The proof computation. The proof is built by adding proof steps to the proof object. The proof object is an impicit argument of computeProof, - * @see Context Functions in Scala - * @return - */ - def apply(using om: OutputManager)(statement: F.Sequent, fullName: String, line: Int, file: String, kind: TheoremKind)(computeProof: Proof ?=> Unit) = - THMFromProof(statement, fullName, line, file, kind)(computeProof) - - /** - * Constructs a "high level" theorem from an existing theorem in the - * - * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] - * @param statement The statement of the theorem - * @param fullName The full name of the theorem, including the path/package. - * @param kind The kind of theorem (Theorem, Lemma, Corollary) - * @param innerThm The inner theorem, coming from the kernel - * @param getProof If available, a way to compute the Kernel proof again. - */ - def fromKernel(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) = - THMFromKernel(statement, fullName, kind, innerThm, getProof) - - /** - * Construct a theorem (both in the kernel and high level) from a proof. - * - * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] - * @param statement The statement of the theorem - * @param fullName The full name of the theorem, including the path/package. - * @param kind The kind of theorem (Theorem, Lemma, Corollary) - * @param getProof The kernel proof. - * @param justifs low level justifications used to justify the proof's imports - * @return - */ - def fromSCProof(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, getProof: () => K.SCProof, justifs: Seq[theory.Justification]): THM = - val proof = getProof() - theory.theorem(fullName, statement.underlying, proof, justifs) match { - case K.Judgement.ValidJustification(just) => - fromKernel(statement, fullName, kind, just.asInstanceOf, () => Some(getProof())) - case wrongJudgement: K.Judgement.InvalidJustification[?] => - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The proof was rejected by LISA's logical kernel. ", - wrongJudgement, - None - ) - ) - } - - } - - /** - * A theorem that was produced from a kernel theorem and not from a high level proof. See [[THM.fromKernel]]. - * Those are typically theorems imported from another tool, or from serialization. - */ - class THMFromKernel(using om: OutputManager)(val statement: F.Sequent, val fullName: String, val kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) extends THM { - - val innerJustification: theory.Theorem = innerThm - assert(innerThm.name == fullName) - def kernelProof: Option[K.SCProof] = getProof() - def highProof: Option[BaseProof] = None - - val goal: F.Sequent = statement - - } - - /** - * A theorem that was produced from a high level proof. See [[THM.apply]]. - * Typical way to construct a theorem in the library, but serialization for example will produce a [[THMFromKernel]]. - */ - class THMFromProof(using om: OutputManager)(val statement: F.Sequent, val fullName: String, line: Int, file: String, val kind: TheoremKind)(computeProof: Proof ?=> Unit) extends THM { - - val goal: F.Sequent = statement - - val proof: BaseProof = new BaseProof(this) - def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) - def highProof: Option[BaseProof] = Some(proof) - - import lisa.utils.Serialization.* - val innerJustification: theory.Theorem = - if library._draft.nonEmpty && library._draft.get.value != file - then // if the draft option is activated, and the theorem is not in the file where the draft option is given, then we replace the proof by sorry - theory.theorem(name, goal.underlying, SCProof(SC.Sorry(goal.underlying)), IndexedSeq.empty) match { - case K.Judgement.ValidJustification(just) => - just - case wrongJudgement: K.Judgement.InvalidJustification[?] => - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", - wrongJudgement, - Some(proof) - ) - ) - } - else if library._withCache then - oneThmFromFile("cache/" + name, library.theory) match { - case Some(thm) => thm // try to get the theorem from file - - case None => - val (thm, scp, justifs) = prove(computeProof) // if fail, prove it - thmsToFile("cache/" + name, theory, List((name, scp, justifs))) // and save it to the file - thm - } - else prove(computeProof)._1 - - library.last = Some(this) - - /** - * Construct the kernel theorem from the high level proof - */ - private def prove(computeProof: Proof ?=> Unit): (theory.Theorem, SCProof, List[(String, theory.Justification)]) = { - try { - computeProof(using proof) - } catch { - case e: UserLisaException => - om.lisaThrow(e) - } - - if (proof.length == 0) - om.lisaThrow(new UnimplementedProof(this)) - - val scp = proof.toSCProof - val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) - theory.theorem(name, goal.underlying, scp, justifs.map(_._2)) match { - case K.Judgement.ValidJustification(just) => - (just, scp, justifs) - case wrongJudgement: K.Judgement.InvalidJustification[?] => - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", - wrongJudgement, - Some(proof) - ) - ) - } - } - - } - - given thmConv: Conversion[library.THM, theory.Theorem] = _.innerJustification - - trait TheoremKind { - val kind2: String - - def apply(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(statement: F.Sequent)(computeProof: Proof ?=> Unit): THM = { - val thm = THM(statement, name.value, line.value, file.value, this)(computeProof) - if this == Theorem then show(thm) - thm - } - - } - - /** - * A "Theorem" kind of theorem, by opposition with a lemma or corollary. The difference is that theorem are always printed when a file defining one is run. - */ - object Theorem extends TheoremKind { val kind2: String = "Theorem" } - - /** - * Lemmas are like theorems, but are conceptually less importants and are not printed when a file defining one is run. - */ - object Lemma extends TheoremKind { val kind2: String = "Lemma" } - - /** - * Corollaries are like theorems, but are conceptually less importants and are not printed when a file defining one is run. - */ - object Corollary extends TheoremKind { val kind2: String = "Corollary" } - - /** - * Internal statements are internally produced theorems, for example as intermediate step in definitions. - */ - object InternalStatement extends TheoremKind { val kind2: String = "Internal, automatically produced" } - -} diff --git a/backup/fol/Common.scala b/backup/fol/Common.scala deleted file mode 100644 index ab09773d..00000000 --- a/backup/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/backup/fol/FOL.scala b/backup/fol/FOL.scala deleted file mode 100644 index d22d98c5..00000000 --- a/backup/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/backup/fol/FOLHelpers.scala b/backup/fol/FOLHelpers.scala deleted file mode 100644 index d9232d3b..00000000 --- a/backup/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/backup/fol/Lambdas.scala b/backup/fol/Lambdas.scala deleted file mode 100644 index 45d1f868..00000000 --- a/backup/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/backup/fol/Predef.scala b/backup/fol/Predef.scala deleted file mode 100644 index 5d53b6a9..00000000 --- a/backup/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/backup/fol/Sequents.scala b/backup/fol/Sequents.scala deleted file mode 100644 index 7c8b4f35..00000000 --- a/backup/fol/Sequents.scala +++ /dev/null @@ -1,249 +0,0 @@ -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 { - object SequentInstantiationRule extends ProofTactic - given ProofTactic = SequentInstantiationRule - - case class Sequent(left: Set[Formula], right: Set[Formula]) extends LisaObject[Sequent] with Absolute { - 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))) - - /*Ok for now but what when we have more*/ - /** - * Substitute schematic symbols inside this, and produces a kernel proof. - * Namely, if "that" is the result of the substitution, the proof should conclude with "that.underlying", - * using the assumption "this.underlying" at step index -1. - * - * @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 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) => - val t = args.head - val newf = 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 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 - (this.left |- newf).instantiateForallWithProof(args.tail, index + 3) match { - case (s, p) => (s, Seq(s0, s1, s2) ++ p) - } - - case _ => throw new IllegalArgumentException("Right side of sequent must be a single universally quantified formula") - } - - } - - /** - * Given 3 substitution maps like the kernel accepts, i.e. Substitution of Predicate Connector and Term schemas, do the substitution - * and produce the (one-step) kernel proof that the result is provable from the original sequent - * - * @param mCon The substitution of connector schemas - * @param mPred The substitution of predicate schemas - * @param mTerm The substitution of function schemas - * @return - */ - def instantiateWithProofLikeKernel( - mCon: Map[SchematicConnectorLabel[?], LambdaExpression[Formula, Formula, ?]], - mPred: Map[SchematicPredicateLabel[?] | VariableFormula, LambdaExpression[Term, Formula, ?]], - mTerm: Map[SchematicFunctionLabel[?] | Variable, LambdaExpression[Term, Term, ?]], - 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)) - } - - 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) - infix def ->>(f: Formula): Sequent = this.copy(right = this.right - f) - infix def ++<<(s1: Sequent): Sequent = this.copy(left = this.left ++ s1.left) - infix def --<<(s1: Sequent): Sequent = this.copy(left = this.left -- s1.left) - infix def ++>>(s1: Sequent): Sequent = this.copy(right = this.right ++ s1.right) - infix def -->>(s1: Sequent): Sequent = this.copy(right = this.right -- s1.right) - infix def ++(s1: Sequent): Sequent = this.copy(left = this.left ++ s1.left, right = this.right ++ s1.right) - infix def --(s1: Sequent): Sequent = this.copy(left = this.left -- s1.left, right = this.right -- s1.right) - - infix def removeLeft(f: Formula): Sequent = this.copy(left = this.left.filterNot(isSame(_, f))) - infix def removeRight(f: Formula): Sequent = this.copy(right = this.right.filterNot(isSame(_, f))) - infix def removeAllLeft(s1: Sequent): Sequent = this.copy(left = this.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2)))) - infix def removeAllLeft(s1: Set[Formula]): Sequent = this.copy(left = this.left.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) - infix def removeAllRight(s1: Sequent): Sequent = this.copy(right = this.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) - infix def removeAllRight(s1: Set[Formula]): Sequent = this.copy(right = this.right.filterNot(e1 => s1.exists(e2 => isSame(e1, e2)))) - infix def removeAll(s1: Sequent): Sequent = - this.copy(left = this.left.filterNot(e1 => s1.left.exists(e2 => isSame(e1, e2))), right = this.right.filterNot(e1 => s1.right.exists(e2 => isSame(e1, e2)))) - - infix def addLeftIfNotExists(f: Formula): Sequent = if (this.left.exists(isSame(_, f))) this else (this +<< f) - infix def addRightIfNotExists(f: Formula): Sequent = if (this.right.exists(isSame(_, f))) this else (this +>> f) - infix def addAllLeftIfNotExists(s1: Sequent): Sequent = this ++<< s1.copy(left = s1.left.filterNot(e1 => this.left.exists(isSame(_, e1)))) - infix def addAllRightIfNotExists(s1: Sequent): Sequent = this ++>> s1.copy(right = s1.right.filterNot(e1 => this.right.exists(isSame(_, e1)))) - infix def addAllIfNotExists(s1: Sequent): Sequent = - this ++ s1.copy(left = s1.left.filterNot(e1 => this.left.exists(isSame(_, e1))), right = s1.right.filterNot(e1 => this.right.exists(isSame(_, e1)))) - - // OL shorthands - infix def +?(f: Formula): Sequent = this addRightIfNotExists f - infix def ->?(f: Formula): Sequent = this removeRight f - infix def ++?(s1: Sequent): Sequent = this addAllRightIfNotExists s1 - infix def -->?(s1: Sequent): Sequent = this removeAllRight s1 - infix def --?(s1: Sequent): Sequent = this removeAll s1 - infix def ++?(s1: Sequent): Sequent = this addAllIfNotExists s1 - - override def toString = - (if left.size == 0 then "" else if left.size == 1 then left.head.toString else "( " + left.mkString(", ") + " )") + - " ⊢ " + - (if right.size == 0 then "" else if right.size == 1 then right.head.toString else "( " + right.mkString(", ") + " )") - - } - - val emptySeq: Sequent = Sequent(Set.empty, Set.empty) - - 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 isSameSequent(sequent1: Sequent, sequent2: Sequent): Boolean = { - K.isSameSequent(sequent1.underlying, sequent2.underlying) - } - - /** - * 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 isImplyingSequent(sequent1: Sequent, sequent2: Sequent): Boolean = { - K.isImplyingSequent(sequent1.underlying, sequent2.underlying) - } - - def isSubset(s1: Set[Formula], s2: Set[Formula]): Boolean = { - K.isSubset(s1.map(_.underlying), s2.map(_.underlying)) - } - def isSameSet(s1: Set[Formula], s2: Set[Formula]): Boolean = - K.isSameSet(s1.map(_.underlying), s2.map(_.underlying)) - - def contains(s: Set[Formula], f: Formula): Boolean = { - K.contains(s.map(_.underlying), f.underlying) - } - - /** - * 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 - */ - trait FormulaSetConverter[T] { - def apply(t: T): Set[Formula] - } - - given FormulaSetConverter[Unit] with { - override def apply(u: Unit): Set[Formula] = 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 - } - - given formula_to_set[T <: Formula]: FormulaSetConverter[T] with { - override def apply(f: T): Set[Formula] = Set(f) - } - - given iterable_to_set[T <: Formula, I <: Iterable[T]]: FormulaSetConverter[I] with { - override def apply(s: I): Set[Formula] = s.toSet - } - - private def any2set[A, T <: A](any: T)(using c: FormulaSetConverter[T]): Set[Formula] = 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)) - infix def ⊢[B, T2 <: B](right: T2)(using FormulaSetConverter[T2]): Sequent = Sequent(any2set(left), any2set(right)) - } - -} diff --git a/backup/prooflib/BasicMain.scala b/backup/prooflib/BasicMain.scala deleted file mode 100644 index 748f58d5..00000000 --- a/backup/prooflib/BasicMain.scala +++ /dev/null @@ -1,29 +0,0 @@ -package lisa.prooflib - -import lisa.utils.Serialization.* - -trait BasicMain { - val library: Library - - private val realOutput: String => Unit = println - - val om: OutputManager = new OutputManager { - def finishOutput(exception: Exception): Nothing = { - log(exception) - main(Array[String]()) - sys.exit - } - val stringWriter: java.io.StringWriter = new java.io.StringWriter() - } - export om.output - - /** - * This specific implementation make sure that what is "shown" in theory files is only printed for the one we run, and not for the whole library. - */ - def main(args: Array[String]): Unit = { - realOutput(om.stringWriter.toString) - } - - given om.type = om - -} diff --git a/backup/prooflib/BasicStepTactic.scala b/backup/prooflib/BasicStepTactic.scala deleted file mode 100644 index 899c51da..00000000 --- a/backup/prooflib/BasicStepTactic.scala +++ /dev/null @@ -1,1457 +0,0 @@ -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 - -object BasicStepTactic { - - def unwrapTactic(using lib: Library, proof: lib.Proof)(using tactic: ProofTactic)(judgement: proof.ProofTacticJudgement)(message: String): proof.ProofTacticJudgement = { - judgement match { - case j: proof.ValidProofTactic => proof.ValidProofTactic(j.bot, j.scps, j.imports) - case j: proof.InvalidProofTactic => proof.InvalidProofTactic(s"Internal tactic call failed! $message\n${j.message}") - } - } - - object Hypothesis extends ProofTactic with ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - val botK = bot.underlying - val intersectedPivot = botK.left.intersect(botK.right) - - if (intersectedPivot.isEmpty) - proof.InvalidProofTactic("A formula for input to Hypothesis could not be inferred from left and right side of the sequent.") - else - proof.ValidProofTactic(bot, Seq(K.Hypothesis(botK, intersectedPivot.head)), Seq()) - } - } - - object Rewrite extends ProofTactic with ProofFactSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val botK = bot.underlying - if (!K.isSameSequent(botK, proof.getSequent(premise).underlying)) - proof.InvalidProofTactic("The premise and the conclusion are not trivially equivalent.") - else - proof.ValidProofTactic(bot, Seq(K.Restate(botK, -1)), Seq(premise)) - } - } - - 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))) - proof.InvalidProofTactic("The desired conclusion is not a trivial tautology.") - else - proof.ValidProofTactic(bot, Seq(K.RestateTrue(botK)), Seq()) - } - } - - /** - *
-   *  Γ |- Δ, φ    φ, Σ |- Π
-   * ------------------------
-   *       Γ, Σ |- Δ, Π
-   * 
- */ - object Cut extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1).underlying - lazy val rightSequent = proof.getSequent(prem2).underlying - val botK = bot.underlying - val phiK = phi.underlying - - if (!K.contains(leftSequent.right, phiK)) - proof.InvalidProofTactic("Right-hand side of first premise does not contain φ as claimed.") - else if (!K.contains(rightSequent.left, phiK)) - proof.InvalidProofTactic("Left-hand side of second premise does not contain φ as claimed.") - else if (!K.isSameSet(botK.left + phiK, leftSequent.left ++ rightSequent.left) || (leftSequent.left.contains(phiK) && !botK.left.contains(phiK))) - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.") - else if (!K.isSameSet(botK.right + phiK, leftSequent.right ++ rightSequent.right) || (rightSequent.right.contains(phiK) && !botK.right.contains(phiK))) - proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.") - else - proof.ValidProofTactic(bot, Seq(K.Cut(botK, -1, -2, phiK)), Seq(prem1, prem2)) - } - - def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1) - lazy val rightSequent = proof.getSequent(prem2) - - lazy val cutSet = (((rightSequent --? bot).right |- ())).left - lazy val intersectedCutSet = rightSequent.left intersect leftSequent.right - - if (!cutSet.isEmpty) - if (cutSet.tail.isEmpty) - Cut.withParameters(cutSet.head)(prem1, prem2)(bot) - else - proof.InvalidProofTactic("Inferred cut pivot is not a singleton set.") - else if (!intersectedCutSet.isEmpty && intersectedCutSet.tail.isEmpty) - // can still find a pivot - Cut.withParameters(intersectedCutSet.head)(prem1, prem2)(bot) - else - proof.InvalidProofTactic("A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause.") - } - } - - // Left rules - /** - *
-   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
-   * --------------     or     --------------
-   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
-   * 
- */ - object LeftAnd extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - val botK = bot.underlying - val phiK = phi.underlying - val psiK = psi.underlying - lazy val phiAndPsi = K.ConnectorFormula(K.And, Seq(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.") - else if ( - !K.isSameSet(botK.left + phiK, premiseSequent.left + phiAndPsi) && - !K.isSameSet(botK.left + psiK, premiseSequent.left + phiAndPsi) && - !K.isSameSet(botK.left + phiK + psiK, premiseSequent.left + phiAndPsi) - ) - proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") - else - proof.ValidProofTactic(bot, Seq(K.LeftAnd(botK, -1, phiK, psiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - - if (!pivot.isEmpty && pivot.tail.isEmpty) - pivot.head match { - case F.AppliedConnector(F.And, Seq(phi, psi)) => - if (premiseSequent.left.contains(phi)) - LeftAnd.withParameters(phi, psi)(premise)(bot) - else - LeftAnd.withParameters(phi, psi)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer a conjunction as pivot from premise and conclusion.") - } - else - // try a rewrite, if it works, go ahead with it, otherwise malformed - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial LeftAnd failed.") - else - proof.InvalidProofTactic("Left-hand side of premise + φ∧ψ is not the same as left-hand side of conclusion + either φ, ψ or both.") - } - } - - /** - *
-   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
-   * --------------------------------
-   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
-   * 
- */ - object LeftOr extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(disjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - 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) - - if (premises.length == 0) - proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (premises.length != disjuncts.length) - proof.InvalidProofTactic(s"Premises and disjuncts expected to be equal in number, but ${premises.length} premises and ${disjuncts.length} disjuncts received.") - else if (!K.isSameSet(botK.right, premiseSequents.map(_.right).reduce(_ union _))) - proof.InvalidProofTactic("Right-hand side of conclusion is not the union of the right-hand sides of the premises.") - else if ( - premiseSequents.zip(disjunctsK).forall((sequent, disjunct) => K.isSubset(sequent.left, botK.left + disjunct)) // \forall i. premise_i.left \subset bot.left + phi_i - && !K.isSubset(botK.left, premiseSequents.map(_.left).reduce(_ union _) + disjunction) // bot.left \subseteq \bigcup premise_i.left - ) - proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftOr(botK, Range(-1, -premises.length - 1, -1), disjunctsK)), premises.toSeq) - } - - def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequents = premises.map(proof.getSequent(_)) - lazy val pivots = premiseSequents.map(_.left.diff(bot.left)) - - if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (pivots.exists(_.isEmpty)) { - val emptyIndex = pivots.indexWhere(_.isEmpty) - if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) - unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for LeftOr failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the one of the premises.") - } else if (pivots.forall(_.tail.isEmpty)) - LeftOr.withParameters(pivots.map(_.head)*)(premises*)(bot) - else - // some extraneous formulae - proof.InvalidProofTactic("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.") - } - } - - /** - *
-   *  Γ |- φ, Δ    Σ, ψ |- Π
-   * ------------------------
-   *    Γ, Σ, φ⇒ψ |- Δ, Π
-   * 
- */ - object LeftImplies extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1).underlying - lazy val rightSequent = proof.getSequent(prem2).underlying - val botK = bot.underlying - val phiK = phi.underlying - val psiK = psi.underlying - lazy val implication = K.ConnectorFormula(K.Implies, Seq(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.") - else if (!K.isSameSet(botK.left + psiK, leftSequent.left union rightSequent.left + implication)) - proof.InvalidProofTactic("Left-hand side of conclusion + ψ is not the union of left-hand sides of premises + φ⇒ψ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftImplies(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) - } - def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1) - lazy val rightSequent = proof.getSequent(prem2) - lazy val pivotLeft = leftSequent.right.diff(bot.right) - lazy val pivotRight = rightSequent.left.diff(bot.left) - - if (pivotLeft.isEmpty) - if (F.isSubset(leftSequent.left, bot.left)) - unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial left premise for LeftImplies failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the first premises.") - else if (pivotRight.isEmpty) - if (F.isSubset(rightSequent.right, bot.right)) - unwrapTactic(Weakening(prem2)(bot))("Attempted weakening on trivial right premise for LeftImplies failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the second premises.") - else if (pivotLeft.tail.isEmpty && pivotRight.tail.isEmpty) - LeftImplies.withParameters(pivotLeft.head, pivotRight.head)(prem1, prem2)(bot) - else - proof.InvalidProofTactic("Could not infer an implication as a pivot from the premises and conclusion, possible extraneous formulae in premises.") - } - } - - /** - *
-   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
-   * --------------    or     --------------------
-   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
-   * 
- */ - object LeftIff extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - 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)) - - if (!K.isSameSet(botK.right, premiseSequent.right)) - proof.InvalidProofTactic("Right-hand side of premise is not the same as right-hand side of conclusion.") - else if ( - !K.isSameSet(botK.left + impLeft, premiseSequent.left + implication) && - !K.isSameSet(botK.left + impRight, premiseSequent.left + implication) && - !K.isSameSet(botK.left + impLeft + impRight, premiseSequent.left + implication) - ) - proof.InvalidProofTactic("Left-hand side of premise + φ⇔ψ is not the same as left-hand side of conclusion + either φ⇒ψ, ψ⇒φ or both.") - else - proof.ValidProofTactic(bot, Seq(K.LeftIff(botK, -1, phiK, psiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftIff failed.") - else - 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 _ => proof.InvalidProofTactic("Could not infer a pivot implication from premise.") - } - } - } - - /** - *
-   *   Γ |- φ, Δ
-   * --------------
-   *   Γ, ¬φ |- Δ
-   * 
- */ - object LeftNot extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - val botK = bot.underlying - val phiK = phi.underlying - lazy val negation = K.ConnectorFormula(K.Neg, Seq(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.") - else if (!K.isSameSet(botK.left, premiseSequent.left + negation)) - proof.InvalidProofTactic("Left-hand side of conclusion is not the same as left-hand side of premise + ¬φ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftNot(botK, -1, phiK)), Seq(premise)) - } - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftNot failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") - else if (!pivot.isEmpty && pivot.tail.isEmpty) - LeftNot.withParameters(pivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise.") - - } - } - - /** - *
-   *   Γ, φ[t/x] |- Δ
-   * -------------------
-   *   Γ, ∀x. φ |- Δ
-   *
-   * 
- */ - 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 = { - 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 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()) - - if (!K.isSameSet(botK.right, premiseSequent.right)) - proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") - else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) - proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ") - else - proof.ValidProofTactic(bot, Seq(K.LeftForall(botK, -1, phiK, xK, tK)), Seq(premise)) - } - - def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - lazy val instantiatedPivot = premiseSequent.left // .diff(botK.left) - - 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 _ => proof.InvalidProofTactic("Could not infer a universally quantified pivot from premise and conclusion.") - } - else - proof.InvalidProofTactic("Left-hand side of conclusion + φ[t/x] is not the same as left-hand side of premise + ∀x. φ.") - else if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - 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 _ => false - } - ) - - quantifiedPhi match { - case Some(F.BinderFormula(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. φ.") - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val prepivot = bot.left.diff(premiseSequent.left) - lazy val pivot = if (prepivot.isEmpty) bot.left else prepivot - lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) - - if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for LeftForall failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - val in: F.Formula = instantiatedPivot.head - val quantifiedPhi: Option[F.Formula] = pivot.find(f => - f match { - case g @ F.BinderFormula(F.Forall, x, phi) => UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined - case _ => false - } - ) - - 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 _ => 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. φ.") - } - } - - /** - *
-   *    Γ, φ |- Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ, ∃x φ|- Δ
-   *
-   * 
- */ - 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 = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel - lazy val phiK = phi.underlying - lazy val botK = bot.underlying - lazy val quantified = K.BinderFormula(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.") - else if (!K.isSameSet(botK.right, premiseSequent.right)) - proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise") - else if (!K.isSameSet(botK.left + phiK, premiseSequent.left + quantified)) - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ") - else - proof.ValidProofTactic(bot, Seq(K.LeftExists(botK, -1, phiK, xK)), Seq(premise)) - } - - var debug = false - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExists failed.") - else - proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") - else if (instantiatedPivot.tail.isEmpty) { - 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 _ => false - } - ) - - quantifiedPhi match { - case Some(F.BinderFormula(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 _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") - } - else - proof.InvalidProofTactic("Ambigous application of LeftExists, multiple pivots corresponding to the quantified formula found.") - } - } - - /** - *
-   *  Γ, ∃y.∀x. (x=y) ⇔ φ |-  Δ
-   * ---------------------------- if y is not free in φ
-   *      Γ, ∃!x. φ |- Δ
-   * 
- */ - 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 = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel - 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) - - if (!K.isSameSet(botK.right, premiseSequent.right)) - proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise.") - else if (!K.isSameSet(botK.left + instantiated, premiseSequent.left + quantified)) - proof.InvalidProofTactic("Left-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as left-hand side of premise + ∃!x. φ.") - else - proof.ValidProofTactic(bot, Seq(K.LeftExistsOne(botK, -1, phiK, xK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.left.diff(premiseSequent.left) - lazy val instantiatedPivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for LeftExistsOne failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - 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 _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") - } - } else - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") - else if (pivot.tail.isEmpty) - pivot.head match { - case F.BinderFormula(F.ExistsOne, x, phi) => LeftExistsOne.withParameters(phi, x)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") - } - else - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise + ∃x. φ.") - } - } - - // Right rules - /** - *
-   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
-   * ------------------------------------
-   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
-   * 
- */ - object RightAnd extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(conjuncts: F.Formula*)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - 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) - - if (premises.length == 0) - proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (premises.length != conjuncts.length) - proof.InvalidProofTactic(s"Premises and conjuncts expected to be equal in number, but ${premises.length} premises and ${conjuncts.length} conjuncts received.") - else if (!K.isSameSet(botK.left, premiseSequents.map(_.left).reduce(_ union _))) - proof.InvalidProofTactic("Left-hand side of conclusion is not the union of the left-hand sides of the premises.") - else if ( - premiseSequents.zip(conjunctsK).forall((sequent, conjunct) => K.isSubset(sequent.right, botK.right + conjunct)) // \forall i. premise_i.right \subset bot.right + phi_i - && !K.isSubset(botK.right, premiseSequents.map(_.right).reduce(_ union _) + conjunction) // bot.right \subseteq \bigcup premise_i.right - ) - proof.InvalidProofTactic("Right-hand side of conclusion + conjuncts is not the same as the union of the right-hand sides of the premises + φ∧ψ....") - else - proof.ValidProofTactic(bot, Seq(K.RightAnd(botK, Range(-1, -premises.length - 1, -1), conjunctsK)), premises) - } - - def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequents = premises.map(proof.getSequent(_)) - lazy val pivots = premiseSequents.map(_.right.diff(bot.right)) - - if (premises.length == 0) proof.InvalidProofTactic(s"Premises expected, ${premises.length} received.") - else if (pivots.exists(_.isEmpty)) { - val emptyIndex = pivots.indexWhere(_.isEmpty) - if (F.isSubset(premiseSequents(emptyIndex).left, bot.left)) - unwrapTactic(Weakening(premises(emptyIndex))(bot))("Attempted weakening on trivial premise for RightAnd failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the one of the premises.") - } else if (pivots.forall(_.tail.isEmpty)) - RightAnd.withParameters(pivots.map(_.head)*)(premises*)(bot) - else - // some extraneous formulae - proof.InvalidProofTactic("Right-hand side of conclusion + φ + ψ is not the same as the union of the right-hand sides of the premises +φ∧ψ.") - } - } - - /** - *
-   *   Γ |- φ, Δ               Γ |- φ, ψ, Δ
-   * --------------    or    ---------------
-   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
-   * 
- */ - object RightOr extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - 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)) - - 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.") - else if ( - !K.isSameSet(botK.right + phiK, premiseSequent.right + phiAndPsi) && - !K.isSameSet(botK.right + psiK, premiseSequent.right + phiAndPsi) && - !K.isSameSet(botK.right + phiK + psiK, premiseSequent.right + phiAndPsi) - ) - proof.InvalidProofTactic("Right-hand side of premise + φ∧ψ is not the same as right-hand side of conclusion + either φ, ψ or both.") - else - proof.ValidProofTactic(bot, Seq(K.RightOr(botK, -1, phiK, psiK)), Seq(premise)) - } - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - - if (!pivot.isEmpty && pivot.tail.isEmpty) - pivot.head match { - case F.AppliedConnector(F.Or, Seq(phi, psi)) => - if (premiseSequent.left.contains(phi)) - RightOr.withParameters(phi, psi)(premise)(bot) - else - RightOr.withParameters(psi, phi)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer a disjunction as pivot from premise and conclusion.") - } - else - // try a rewrite, if it works, go ahead with it, otherwise malformed - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightOr failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion + φ∧ψ is not the same as right-hand side of premise + either φ, ψ or both.") - } - } - - /** - *
-   *  Γ, φ |- ψ, Δ
-   * --------------
-   *  Γ |- φ⇒ψ, Δ
-   * 
- */ - object RightImplies extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - 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)) - - 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.") - else if (!K.isSameSet(botK.right + psiK, premiseSequent.right + implication)) - proof.InvalidProofTactic("Right-hand side of conclusion + ψ is not the same as right-hand side of premise + φ⇒ψ.") - else - proof.ValidProofTactic(bot, Seq(K.RightImplies(botK, -1, phiK, psiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val leftPivot = premiseSequent.left.diff(bot.left) - lazy val rightPivot = premiseSequent.right.diff(bot.right) - - if ( - !leftPivot.isEmpty && leftPivot.tail.isEmpty && - !rightPivot.isEmpty && rightPivot.tail.isEmpty - ) - RightImplies.withParameters(leftPivot.head, rightPivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") - } - } - - /** - *
-   *  Γ |- φ⇒ψ, Δ    Σ |- ψ⇒φ, Π
-   * ----------------------------
-   *      Γ, Σ |- φ⇔ψ, Π, Δ
-   * 
- */ - object RightIff extends ProofTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula, psi: F.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val leftSequent = proof.getSequent(prem1).underlying - lazy val rightSequent = proof.getSequent(prem2).underlying - 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)) - - 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(_ ++ ", " ++ _) - ) - 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(_ ++ ", " ++ _) - ) - 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)}]") - .reduce(_ ++ ", " ++ _) - ) - else - proof.ValidProofTactic(bot, Seq(K.RightIff(botK, -1, -2, phiK, psiK)), Seq(prem1, prem2)) - } - - def apply(using lib: Library, proof: lib.Proof)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(prem1) - lazy val pivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(prem1)(bot))("Attempted weakening on trivial premise for RightIff failed.") - else - 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 _ => proof.InvalidProofTactic("Could not infer an implication as pivot from premise and conclusion.") - } - else - proof.InvalidProofTactic("Right-hand side of conclusion + φ⇒ψ + ψ⇒φ is not the same as the union of the right-hand sides of the premises φ⇔ψ.") - } - } - - /** - *
-   *  Γ, φ |- Δ
-   * --------------
-   *   Γ |- ¬φ, Δ
-   * 
- */ - object RightNot extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(phi: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - 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)) - - 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.") - else if (!K.isSameSet(botK.right, premiseSequent.right + negation)) - proof.InvalidProofTactic("Right-hand side of conclusion is not the same as right-hand side of premise + ¬φ.") - else - proof.ValidProofTactic(bot, Seq(K.RightNot(botK, -1, phiK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.left.diff(bot.left) - - if (pivot.isEmpty) - if (F.isSubset(premiseSequent.right, bot.right)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightIff failed.") - else - proof.InvalidProofTactic("Right-hand side of conclusion is not a superset of the premises.") - else if (pivot.tail.isEmpty) - RightNot.withParameters(pivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Left-hand side of conclusion + φ is not the same as left-hand side of premise.") - - } - } - - /** - *
-   *    Γ |- φ, Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ |- ∀x. φ, Δ
-   * 
- */ - 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 = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val xK = x.underlyingLabel - lazy val phiK = phi.underlying - lazy val botK = bot.underlying - lazy val quantified = K.BinderFormula(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.") - else 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 + phiK, premiseSequent.right + quantified)) - proof.InvalidProofTactic("Right-hand side of conclusion + φ is not the same as right-hand side of premise + ∀x. φ.") - else - proof.ValidProofTactic(bot, Seq(K.RightForall(botK, -1, phiK, xK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightForall failed.") - else - proof.InvalidProofTactic("Could not infer a pivot from the premise and conclusion.") - else if (instantiatedPivot.tail.isEmpty) { - 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 _ => false - } - ) - - quantifiedPhi match { - case Some(F.BinderFormula(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 _ => 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. φ.") - } - } - - /** - *
-   *   Γ |- φ[t/x], Δ
-   * -------------------
-   *  Γ |- ∃x. φ, Δ
-   *
-   * (ln-x stands for locally nameless x)
-   * 
- */ - 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 = { - 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 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()) - - 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 + quantified)) - proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ") - else - proof.ValidProofTactic(bot, Seq(K.RightExists(botK, -1, phiK, xK, tK)), Seq(premise)) - } - - def withParameters(using lib: Library, proof: lib.Proof)(t: F.Term)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - 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 _ => proof.InvalidProofTactic("Could not infer an existentially quantified pivot from premise and conclusion.") - } - else - proof.InvalidProofTactic("Right-hand side of conclusion + φ[t/x] is not the same as right-hand side of premise + ∃x. φ.") - else if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightExists failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - 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 _ => false - } - ) - - quantifiedPhi match { - case Some(F.BinderFormula(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. φ.") - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val prepivot = bot.right.diff(premiseSequent.right) - lazy val pivot = if (prepivot.isEmpty) bot.right else prepivot - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - if (instantiatedPivot.isEmpty) - if (F.isSubset(premiseSequent.left, bot.left)) - unwrapTactic(Weakening(premise)(bot))("Attempted weakening on trivial premise for RightForall failed.") - else - proof.InvalidProofTactic("Left-hand side of conclusion is not a superset of the premises.") - else if (instantiatedPivot.tail.isEmpty) { - // go through conclusion to find a matching quantified formula - - val in: F.Formula = instantiatedPivot.head - - val quantifiedPhi: Option[F.Formula] = pivot.find(f => - f match { - case g @ F.BinderFormula(F.Exists, x, phi) => - UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVariables - x)).isDefined - case _ => false - } - ) - - 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 _ => 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) ⇔ φ, Δ
-   * ---------------------------- if y is not free in φ
-   *      Γ|- ∃!x. φ,  Δ
-   * 
- */ - 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 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) - - 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 + quantified)) - proof.InvalidProofTactic("Right-hand side of conclusion + ∃y.∀x. (x=y) ⇔ φ is not the same as right-hand side of premise + ∃!x. φ.") - else - proof.ValidProofTactic(bot, Seq(K.RightExistsOne(botK, -1, phiK, xK)), Seq(premise)) - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = bot.right.diff(premiseSequent.right) - lazy val instantiatedPivot = premiseSequent.right.diff(bot.right) - - if (pivot.isEmpty) - if (instantiatedPivot.isEmpty) - if (F.isSameSequent(premiseSequent, bot)) - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite on trivial premise for RightExistsOne failed.") - else - proof.InvalidProofTactic("Could not infer a pivot from premise and conclusion.") - 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)))) => - RightExistsOne.withParameters(phi, x)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer an existentially 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.ExistsOne, x, phi) => RightExistsOne.withParameters(phi, x)(premise)(bot) - case _ => proof.InvalidProofTactic("Could not infer an existentially 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. φ.") - } - } - - // Structural rules - /** - *
-   *     Γ |- Δ
-   * --------------
-   *   Γ, Σ |- Δ, Π
-   * 
- */ - object Weakening extends ProofTactic with ProofFactSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - - if (!F.isImplyingSequent(premiseSequent, bot)) - proof.InvalidProofTactic("Conclusion cannot be trivially derived from premise.") - else - proof.ValidProofTactic(bot, Seq(K.Weakening(bot.underlying, -1)), Seq(premise)) - } - } - - // Equality Rules - /** - *
-   *  Γ, s=s |- Δ
-   * --------------
-   *     Γ |- Δ
-   * 
- */ - object LeftRefl extends ProofTactic with ProofFactSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise).underlying - lazy val faK = fa.underlying - lazy val botK = bot.underlying - - if (!K.isSameSet(botK.left + faK, premiseSequent.left) || !premiseSequent.left.exists(_ == faK) || botK.left.exists(_ == faK)) - proof.InvalidProofTactic("Left-hand sides of the conclusion + φ is not the same as left-hand side of the premise.") - else 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 - faK match { - case K.AtomicFormula(K.equality, Seq(left, right)) => - if (K.isSameTerm(left, right)) - proof.ValidProofTactic(bot, Seq(K.LeftRefl(botK, -1, faK)), Seq(premise)) - else - proof.InvalidProofTactic("φ is not an instance of reflexivity.") - case _ => proof.InvalidProofTactic("φ is not an equality.") - } - } - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent = proof.getSequent(premise) - lazy val pivot = premiseSequent.left.diff(bot.left) - - if (!pivot.isEmpty && pivot.tail.isEmpty) - LeftRefl.withParameters(pivot.head)(premise)(bot) - else - proof.InvalidProofTactic("Could not infer an equality as pivot from premise and conclusion.") - } - } - - /** - *
-   *
-   * --------------
-   *     |- s=s
-   * 
- */ - object RightRefl extends ProofTactic with ProofSequentTactic { - def withParameters(using lib: Library, proof: lib.Proof)(fa: F.Formula)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val faK = fa.underlying - lazy val botK = bot.underlying - if (!botK.right.exists(_ == faK)) - 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)) - proof.ValidProofTactic(bot, Seq(K.RightRefl(botK, faK)), Seq()) - else - proof.InvalidProofTactic("φ is not an instance of reflexivity.") - case _ => proof.InvalidProofTactic("φ is not an equality.") - } - } - - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - if (bot.right.isEmpty) proof.InvalidProofTactic("Right-hand side of conclusion does not contain an instance of reflexivity.") - else { - // go through conclusion to see if you can find an reflexive formula - 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)) => - (F.equality) == (e) && l == r // termequality - case _ => false - } - ) - - pivot match { - case Some(phi) => RightRefl.withParameters(phi)(bot) - case _ => proof.InvalidProofTactic("Could not infer an equality as pivot from conclusion.") - } - - } - - } - } - - /** - *
-   *           Γ, φ(s1,...,sn) |- Δ
-   * ----------------------------------------
-   *  Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ
-   * 
- */ - object LeftSubstEq extends ProofTactic { - - def withParametersSimple(using lib: Library, proof: lib.Proof)( - equals: List[(F.Term, F.Term)], - lambdaPhi: F.LambdaExpression[F.Term, 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) - } - - 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) - )(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) - - 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.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)) - } - } - - /** - *
-   *          Γ |- φ(s1,...,sn), Δ
-   * ----------------------------------------
-   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
-   * 
- */ - object RightSubstEq extends ProofTactic { - def withParametersSimple(using lib: Library, proof: lib.Proof)( - equals: List[(F.Term, F.Term)], - lambdaPhi: F.LambdaExpression[F.Term, 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) - } - - 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) - )(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) - - 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) - - 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 ++ 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)) - } - } - - /** - *
-   *           Γ |- Δ
-   * --------------------------
-   *  Γ[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)) - } - } - - /** - *
-   *           Γ |- Δ
-   * --------------------------
-   *  Γ[ψ(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)) - } - ) - - 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) = { - val bot: Option[F.Sequent] = statement - val botK: Option[K.Sequent] = statement map (_.underlying) - if (iProof.length == 0) throw (new UnimplementedProof(proof.owningTheorem)) - val scproof: K.SCProof = iProof.toSCProof - val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) - val judgement: proof.ProofTacticJudgement = { - 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)}" - ) - else - proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) - } - 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) - if (iProof.length == 0) - throw (new UnimplementedProof(proof.owningTheorem)) - val scproof: K.SCProof = iProof.toSCProof - - val premises: Seq[proof.Fact] = iProof.getImports.map(of => of._1) - def judgement: proof.ProofTacticJudgement = { - 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)}") - else - proof.ValidProofTactic(bot.get, scproof.steps :+ K.Restate(botK.get, scproof.length - 1), premises) - } - } - - // TODO make specific support for subproofs written inside tactics.kkkkkkk - - inline def TacticSubproof(using proof: Library#Proof)(inline computeProof: proof.InnerProof ?=> Unit): proof.ProofTacticJudgement = - val iProof: proof.InnerProof = new proof.InnerProof(None) - computeProof(using iProof) - SUBPROOF(using proof)(None)(iProof).judgement.asInstanceOf[proof.ProofTacticJudgement] - - object Sorry extends ProofTactic with ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - proof.ValidProofTactic(bot, Seq(K.Sorry(bot.underlying)), Seq()) - } - } - -} diff --git a/backup/prooflib/Exports.scala b/backup/prooflib/Exports.scala deleted file mode 100644 index 836d1cac..00000000 --- a/backup/prooflib/Exports.scala +++ /dev/null @@ -1,6 +0,0 @@ -package lisa.prooflib - -object Exports { - export BasicStepTactic.* - export lisa.prooflib.SimpleDeducedSteps.* -} diff --git a/backup/prooflib/Library.scala b/backup/prooflib/Library.scala deleted file mode 100644 index 9d64e278..00000000 --- a/backup/prooflib/Library.scala +++ /dev/null @@ -1,123 +0,0 @@ -package lisa.prooflib - -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.utils.KernelHelpers.{_, given} -import lisa.utils.{_, given} - -import scala.collection.mutable.Stack as stack - -/** - * A class abstracting a [[lisa.kernel.proof.RunningTheory]] providing utility functions and a convenient syntax - * to write and use Theorems and Definitions. - * @param theory The inner RunningTheory - */ -abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.ProofsHelpers { - - val theory: RunningTheory - given library: this.type = this - given RunningTheory = theory - - export lisa.kernel.proof.SCProof - - val K = lisa.utils.K - val SC: SequentCalculus.type = K.SC - private[prooflib] val F = lisa.fol.FOL - import F.{given} - - var last: Option[JUSTIFICATION] = None - - // Options for files - private[prooflib] var _withCache: Boolean = false - def withCache(using file: sourcecode.File, om: OutputManager)(): Unit = - if last.nonEmpty then om.output(OutputManager.WARNING("Warning: withCache option should be used before the first definition or theorem.")) - else _withCache = true - - private[prooflib] var _draft: Option[sourcecode.File] = None - def draft(using file: sourcecode.File, om: OutputManager)(): Unit = - if last.nonEmpty then om.output(OutputManager.WARNING("Warning: draft option should be used before the first definition or theorem.")) - 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 - - 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) - } - knownDefs.update(s, None) - } - - def getDefinition(label: F.ConstantLabel[?]): 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 { - case None => throw new UserLisaException.UndefinedSymbolException("Unknown symbol", label, this) - case Some(value) => value - } - - /** - * An alias to create a Theorem - */ - def makeTheorem(name: String, statement: K.Sequent, proof: K.SCProof, justifications: Seq[theory.Justification]): K.Judgement[theory.Theorem] = - theory.theorem(name, statement, proof, justifications) - - // 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) - } - - /** - * Prints a short representation of the given theorem or definition - */ - def show(using om: OutputManager)(thm: JUSTIFICATION) = { - if (thm.withSorry) om.output(thm.repr, Console.YELLOW) - else om.output(thm.repr, Console.GREEN) - } - - /** - * Prints a short representation of the last theorem or definition introduced - */ - def show(using om: OutputManager): Unit = last match { - case Some(value) => show(value) - case None => throw new NoSuchElementException("There is nothing to show: No theorem or definition has been proved yet.") - } - -} diff --git a/backup/prooflib/OutputManager.scala b/backup/prooflib/OutputManager.scala deleted file mode 100644 index ce3f84f8..00000000 --- a/backup/prooflib/OutputManager.scala +++ /dev/null @@ -1,52 +0,0 @@ -package lisa.prooflib - -import lisa.utils.KernelHelpers.{_, given} -import lisa.utils.{_, given} - -import java.io.PrintWriter -import java.io.StringWriter - -abstract class OutputManager { - - given OutputManager = this - - def output(s: String): Unit = stringWriter.write(s + "\n") - def output(s: String, color: String): Unit = stringWriter.write(Console.RESET + color + s + "\n" + Console.RESET) - val stringWriter: StringWriter - - def finishOutput(exception: Exception): Nothing - - def lisaThrow(le: LisaException): Nothing = - le match { - case ule: UserLisaException => - ule.fixTrace() - output(ule.showError) - finishOutput(ule) - - case e: LisaException.InvalidKernelJustificationComputation => - e.proof match { - case Some(value) => output(lisa.utils.ProofPrinter.prettyProof(value)) - case None => () - } - output(e.underlying.repr) - finishOutput(e) - - } - - def log(e: Exception): Unit = { - stringWriter.write("\n[" + Console.RED + "Error" + Console.RESET + "] ") - e.printStackTrace(PrintWriter(stringWriter)) - output(Console.RESET) - } - -} -object OutputManager { - def RED(s: String): String = Console.RED + s + Console.RESET - def GREEN(s: String): String = Console.GREEN + s + Console.RESET - def BLUE(s: String): String = Console.BLUE + s + Console.RESET - def YELLOW(s: String): String = Console.YELLOW + s + Console.RESET - def CYAN(s: String): String = Console.CYAN + s + Console.RESET - def MAGENTA(s: String): String = Console.MAGENTA + s + Console.RESET - - def WARNING(s: String): String = Console.YELLOW + "⚠ " + s + Console.RESET -} diff --git a/backup/prooflib/ProofTacticLib.scala b/backup/prooflib/ProofTacticLib.scala deleted file mode 100644 index fb108de4..00000000 --- a/backup/prooflib/ProofTacticLib.scala +++ /dev/null @@ -1,66 +0,0 @@ -package lisa.prooflib - -import lisa.fol.FOL as F -import lisa.prooflib.* -import lisa.utils.K -import lisa.utils.Printer -import lisa.utils.UserLisaException - -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. - */ - trait ProofTactic { - val name: String = this.getClass.getName.split('$').last - given ProofTactic = this - - } - - trait OnlyProofTactic { - def apply(using lib: Library, proof: lib.Proof): proof.ProofTacticJudgement - } - - trait ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement - } - - trait ProofFactTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact): proof.ProofTacticJudgement - } - trait ProofFactSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement - } - - class UnapplicableProofTactic(val tactic: ProofTactic, proof: Library#Proof, errorMessage: String)(using sourcecode.Line, sourcecode.File) extends UserLisaException(errorMessage) { - override def fixTrace(): Unit = { - val start = getStackTrace.indexWhere(elem => { - !elem.getClassName.contains(tactic.name) - }) + 1 - setStackTrace(getStackTrace.take(start)) - } - - def 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() - Console.RED + proof.owningTheorem.prettyGoal + Console.RESET + "\n" + - lisa.utils.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 - } - } - - class UnimplementedProof(val theorem: Library#THM)(using sourcecode.Line, sourcecode.File) extends UserLisaException("Unimplemented Theorem") { - def showError: String = s"Theorem ${theorem.name}" - } - case class UnexpectedProofTacticFailureException(failure: Library#Proof#InvalidProofTactic, errorMessage: String)(using sourcecode.Line, sourcecode.File) - 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) - } - -} diff --git a/backup/prooflib/ProofsHelpers.scala b/backup/prooflib/ProofsHelpers.scala deleted file mode 100644 index 77ef9744..00000000 --- a/backup/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.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/backup/prooflib/SimpleDeducedSteps.scala b/backup/prooflib/SimpleDeducedSteps.scala deleted file mode 100644 index d9a2cd98..00000000 --- a/backup/prooflib/SimpleDeducedSteps.scala +++ /dev/null @@ -1,350 +0,0 @@ -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 = - unwrapTactic(RewriteTrue(bot))("Attempted true rewrite during tactic Restate failed.") - - // (proof.ProofStep | proof.OutsideFact | Int) is definitionally equal to proof.Fact, but for some reason - // scala compiler doesn't resolve the overload with a type alias, dependant type and implicit parameter - - def apply(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") - - def from(using lib: Library, proof: lib.Proof)(premise: proof.ProofStep | proof.OutsideFact | Int | proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - unwrapTactic(Rewrite(premise)(bot))("Attempted rewrite during tactic Restate failed.") - - } - - object Discharge extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(premises: proof.Fact*)(premise: proof.Fact): proof.ProofTacticJudgement = { - val ss = premises zip (premises map (e => proof.getSequent(e))) - val seqs = ss.map(_._2) - if (!seqs.forall(_.right.size == 1)) - return proof.InvalidProofTactic("When discharging this way, the discharged sequent must have only a single formula on the right handside.") - val seqAny = ss.find((_, s) => premise.statement.left.exists(f2 => F.isSame(s.right.head, f2))) - if (seqAny.isEmpty) - Restate.from(premise)(premise.statement) - else - TacticSubproof: ip ?=> - ss.foldLeft(premise: ip.Fact)((prem, discharge) => - val seq = discharge._2 - if prem.statement.left.exists(f => F.isSame(f, seq.right.head)) then - val goal = prem.statement - - * Γ ⊢ ∀x.ψ, Δ - * ------------------------- - * Γ |- ψ[t/x], Δ - * - * - * - * Returns a subproof containing the instantiation steps - */ - object InstantiateForall extends ProofTactic with ProofSequentTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: F.Formula, t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val botK = bot.underlying - val phiK = phi.underlying - val tK = t map (_.underlying) - val premiseSequent = proof.getSequent(premise) - val premiseSequentK = premiseSequent.underlying - if (!premiseSequent.right.contains(phi)) { - proof.InvalidProofTactic("Input formula was not found in the RHS of the premise sequent.") - } else { - val emptyProof = K.SCProof(IndexedSeq(), IndexedSeq(premiseSequentK)) - val j = proof.ValidProofTactic(bot, Seq(K.Restate(premiseSequentK, -1)), Seq(premise)) - val res = tK.foldLeft((emptyProof, phiK, j: proof.ProofTacticJudgement)) { case ((p, f, j), t) => - j match { - case proof.InvalidProofTactic(_) => (p, f, j) // propagate error - case proof.ValidProofTactic(_, _, _) => - // good state, continue instantiating - // 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)) - // instantiate the formula with input - val in = instantiateBinder(psi, 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 p2 = K.Cut(con, -1, 1, f) - - /** - * in = ψ[t/x] - * - * s1 = Γ ⊢ ∀x.ψ, Δ Premise - * con = Γ ⊢ ψ[t/x], Δ Result - * - * p0 = ψ[t/x] ⊢ ψ[t/x] Hypothesis - * p1 = ∀x.ψ ⊢ ψ[t/x] LeftForall p0 - * p2 = Γ ⊢ ψ[t/x], Δ Cut s1, p1 - */ - val newStep = K.SCSubproof(K.SCProof(IndexedSeq(p0, p1, p2), IndexedSeq(p.conclusion)), Seq(p.length - 1)) - ( - p withNewSteps IndexedSeq(newStep), - in, - j - ) - case _ => - (p, f, proof.InvalidProofTactic("Input formula is not universally quantified")) - } - } - } - - res._3 match { - case proof.InvalidProofTactic(_) => res._3 - case proof.ValidProofTactic(_, _, _) => { - 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)}") - } - } - } - } - - def apply(using lib: Library, proof: lib.Proof)(t: F.Term*)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val prem = proof.getSequent(premise) - if (prem.right.tail.isEmpty) { - // well formed - apply(using lib, proof)(prem.right.head, t*)(premise)(bot): proof.ProofTacticJudgement - } else proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") - } - - def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = { - try { - val sp = TacticSubproof { - // lazy val premiseSequent = proof.getSequent(premise) - val s1 = lib.have(bot +<< bot.right.head) by Restate - lib.have(bot) by LeftForall(s1) - } - BasicStepTactic.unwrapTactic(sp)("Subproof substitution fail.") - } catch { - case e: Exception => proof.InvalidProofTactic("Impossible to justify desired step with instantiation.") - } - - } - - } - /* - /** - * Performs a cut when the formula to be used as pivot for the cut is - * inside a conjunction, preserving the conjunction structure - * - *
-   *
-   * PartialCut(ϕ, ϕ ∧ ψ)(left, right) :
-   *
-   *     left: Γ ⊢ ϕ ∧ ψ, Δ      right: ϕ, Σ ⊢ γ1 , γ2, …, γn
-   * -----------------------------------------------------------
-   *            Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn
-   *
-   * 
- */ - object PartialCut extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, conjunction: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val leftSequent = proof.getSequent(prem1) - val rightSequent = proof.getSequent(prem2) - - if (leftSequent.right.contains(conjunction)) { - - if (rightSequent.left.contains(phi)) { - // check conjunction matches with phi - conjunction match { - case K.ConnectorFormula(K.And, s: Seq[K.Formula]) => { - if (s.contains(phi)) { - // construct proof - - val psi: Seq[K.Formula] = s.filterNot(_ == phi) - val newConclusions: Set[K.Formula] = rightSequent.right.map((f: K.Formula) => K.ConnectorFormula(K.And, f +: psi)) - - val Sigma: Set[K.Formula] = rightSequent.left - phi - - val p0 = K.Weakening(rightSequent ++<< (psi |- ()), -2) - val p1 = K.RestateTrue(psi |- psi) - - // TODO: can be abstracted into a RightAndAll step - val emptyProof = SCProof(IndexedSeq(), IndexedSeq(p0.bot, p1.bot)) - val proofRightAndAll = rightSequent.right.foldLeft(emptyProof) { case (p, gamma) => - p withNewSteps IndexedSeq(K.RightAnd(p.conclusion ->> gamma +>> K.ConnectorFormula(K.And, gamma +: psi), Seq(p.length - 1, -2), gamma +: psi)) - } - - val p2 = K.SCSubproof(proofRightAndAll, Seq(0, 1)) - val p3 = K.Restate(Sigma + conjunction |- newConclusions, 2) // sanity check and correct form - val p4 = K.Cut(bot, -1, 3, conjunction) - - /** - * newConclusions = ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn - * - * left = Γ ⊢ ϕ ∧ ψ, Δ Premise - * right = ϕ, Σ ⊢ γ1 , γ2, …, γn Premise - * - * p0 = ϕ, Σ, ψ ⊢ γ1 , γ2, …, γn Weakening on right - * p1 = ψ ⊢ ψ Hypothesis - * p2 = Subproof: - * 2.1 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , γ2, …, γn RightAnd on p0 and p1 with ψ ∧ γ1 - * 2.2 = ϕ, Σ, ψ ⊢ ψ ∧ γ1 , ψ ∧ γ2, …, γn RightAnd on 2.1 and p1 ψ ∧ γ2 - * ... - * 2.n = ϕ, Σ, ψ ⊢ ψ ∧ γ1, ψ ∧ γ2, …, ψ ∧ γn RightAnd on 2.(n-1) and p1 with ψ ∧ γn - * - * p3 = ϕ ∧ ψ, Σ ⊢ ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Rewrite on p2 (just to have a cleaner form) - * p2 = Γ, Σ ⊢ Δ, ψ ∧ γ1, ψ ∧ γ2, … , ψ ∧ γn Cut on left, p1 with ϕ ∧ ψ - * - * p2 is the result - */ - - proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3, p4), Seq(prem1, prem2)) - } else { - proof.InvalidProofTactic("Input conjunction does not contain the pivot.") - } - } - case _ => proof.InvalidProofTactic("Input not a conjunction.") - } - } else { - proof.InvalidProofTactic("Input pivot formula not found in right premise.") - } - } else { - proof.InvalidProofTactic("Input conjunction not found in first premise.") - } - } - } - - object destructRightAnd extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val conc = proof.getSequent(prem) - val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) - val p1 = K.LeftAnd(emptySeq +<< (a /\ b) +>> a, 0, a, b) - val p2 = K.Cut(conc ->> (a /\ b) ->> (b /\ a) +>> a, -1, 1, a /\ b) - proof.ValidProofTactic(IndexedSeq(p0, p1, p2), Seq(prem)) - } - } - object destructRightOr extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(a: K.Formula, b: K.Formula)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val conc = proof.getSequent(prem) - val mat = conc.right.find(f => K.isSame(f, a \/ b)) - if (mat.nonEmpty) { - - val p0 = K.Hypothesis(emptySeq +<< a +>> a, a) - val p1 = K.Hypothesis(emptySeq +<< b +>> b, b) - - val p2 = K.LeftOr(emptySeq +<< (a \/ b) +>> a +>> b, Seq(0, 1), Seq(a, b)) - val p3 = K.Cut(conc ->> mat.get +>> a +>> b, -1, 2, a \/ b) - proof.ValidProofTactic(IndexedSeq(p0, p1, p2, p3), Seq(prem)) - } else { - proof.InvalidProofTactic("Premise does not contain the union of the given formulas") - } - - } - } - - object GeneralizeToForall extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula, t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val sequent = proof.getSequent(prem) - if (sequent.right.contains(phi)) { - val emptyProof = SCProof(IndexedSeq(), IndexedSeq(sequent)) - val j = proof.ValidProofTactic(IndexedSeq(K.Restate(sequent, proof.length - 1)), Seq[proof.Fact]()) - - val res = t.foldRight(emptyProof: SCProof, phi: K.Formula, j: proof.ProofTacticJudgement) { case (x1, (p1: SCProof, phi1, j1)) => - j1 match { - case proof.InvalidProofTactic(_) => (p1, phi1, j1) - case proof.ValidProofTactic(_, _) => { - if (!p1.conclusion.right.contains(phi1)) - (p1, phi1, proof.InvalidProofTactic("Formula is not present in the lass sequent")) - - val proofStep = K.RightForall(p1.conclusion ->> phi1 +>> forall(x1, phi1), p1.length - 1, phi1, x1) - ( - p1 appended proofStep, - forall(x1, phi1), - j1 - ) - } - } - } - - res._3 match { - case proof.InvalidProofTactic(_) => res._3 - case proof.ValidProofTactic(_, _) => proof.ValidProofTactic((res._1.steps appended K.Restate(bot, res._1.length - 1)), Seq(prem)) - } - - } else proof.InvalidProofTactic("RHS of premise sequent contains not phi") - - } - } - - object GeneralizeToForallNoForm extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(t: K.VariableLabel*)(prem: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - if (proof.getSequent(prem).right.tail.isEmpty) - GeneralizeToForall.apply(using lib, proof)(proof.getSequent(prem).right.head, t*)(prem)(bot): proof.ProofTacticJudgement - else - proof.InvalidProofTactic("RHS of premise sequent is not a singleton.") - } - - } - - object ByCase extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof)(phi: K.Formula)(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - val nphi = !phi - - val pa = proof.getSequent(prem1) - val pb = proof.getSequent(prem2) - val (leftAphi, leftBnphi) = (pa.left.find(K.isSame(_, phi)), pb.left.find(K.isSame(_, nphi))) - if (leftAphi.nonEmpty && leftBnphi.nonEmpty) { - val p2 = K.RightNot(pa -<< leftAphi.get +>> nphi, -1, phi) - val p3 = K.Cut(pa -<< leftAphi.get ++ (pb -<< leftBnphi.get), 0, -2, nphi) - val p4 = K.Restate(bot, 1) - proof.ValidProofTactic(IndexedSeq(p2, p3, p4), IndexedSeq(prem1, prem2)) // TODO: Check pa/pb orDer - - } else { - proof.InvalidProofTactic("Premises have not the right syntax") - } - } - } - */ -} diff --git a/backup/prooflib/WithTheorems.scala b/backup/prooflib/WithTheorems.scala deleted file mode 100644 index 628ef7ff..00000000 --- a/backup/prooflib/WithTheorems.scala +++ /dev/null @@ -1,652 +0,0 @@ -package lisa.prooflib - -import lisa.kernel.proof.RunningTheory -import lisa.prooflib.ProofTacticLib.ProofTactic -import lisa.prooflib.ProofTacticLib.UnimplementedProof -import lisa.prooflib.* -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 -import scala.collection.mutable.Map as mMap -import scala.collection.mutable.Stack as stack - -trait WithTheorems { - library: Library => - - /** - * The main builder for proofs. It is a mutable object that can be used to build a proof step by step. - * It is used either to construct a theorem/lemma ([[BaseProof]]) or to construct a subproof ([[InnerProof]]). - * We can add proof tactics to it producing intermediate results. In the end, obtain a [[K.SCProof]] from it. - * - * @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 - type Fact = ProofStep | InstantiatedFact | OutsideFact | Int - - /** - * A proven fact (from a previously proven step, a theorem or a definition) with specific instantiations of free variables. - * - * @param fact The base fact - * @param insts The instantiation of free variables - */ - case class InstantiatedFact( - fact: Fact, - insts: Seq[F.SubstPair | F.Term] - ) { - 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 (s1, p1) = if substPairs.isEmpty then (baseFormula, Seq()) else baseFormula.instantiateWithProof(substPairs.map(sp => (sp._1, sp._2)).toMap, -1) - val (s2, p2) = if terms.isEmpty then (s1, p1) else s1.instantiateForallWithProof(terms, p1.length - 1) - (s2, p1 ++ p2) - } - - } - - val library: WithTheorems.this.type = WithTheorems.this - - private var steps: List[ProofStep] = Nil - private var imports: List[(OutsideFact, F.Sequent)] = Nil - private var instantiatedFacts: List[(InstantiatedFact, Int)] = Nil - private var assumptions: List[F.Formula] = assump - private var eliminations: List[(F.Formula, (Int, F.Sequent) => List[K.SCProofStep])] = Nil - - def cleanAssumptions: Unit = assumptions = Nil - - /** - * the theorem that is being proved (paritally, if subproof) by this proof. - * - * @return The theorem - */ - def owningTheorem: THM - - /** - * A proof step, containing a high level ProofTactic and the corresponding K.SCProofStep. If the tactic produce more than one - * step, they must be encapsulated in a subproof. Usually constructed with [[ValidProofTactic.validate]] - * - * @param judgement The result of the tactic - * @param scps The corresponding [[K.SCProofStep]] - * @param position The position of the step in the proof - */ - case class ProofStep private (judgement: ValidProofTactic, scps: K.SCProofStep, position: Int) { - val bot: F.Sequent = judgement.bot - def innerBot: K.Sequent = scps.bot - val host: Proof.this.type = Proof.this - - def tactic: ProofTactic = judgement.tactic - - } - private object ProofStep { // TODO - def newProofStep(judgement: ValidProofTactic): ProofStep = { - val ps = ProofStep( - judgement, - SC.SCSubproof( - K.SCProof(judgement.scps.toIndexedSeq, judgement.imports.map(f => sequentOfFact(f).underlying).toIndexedSeq), - judgement.imports.map(sequentAndIntOfFact(_)._2) - ), - steps.length - ) - addStep(ps) - ps - - } - } - - /** - * A proof step can be constructed from a succesfully executed tactic - */ - def newProofStep(judgement: ValidProofTactic): ProofStep = - ProofStep.newProofStep(judgement) - - private def addStep(ds: ProofStep): Unit = steps = ds :: steps - private def addImport(imp: OutsideFact, seq: F.Sequent): Unit = { - imports = (imp, seq) :: imports - } - - private def addInstantiatedFact(instFact: InstantiatedFact): Unit = { - val step = ValidProofTactic(instFact.result, instFact.proof, Seq(instFact.fact))(using F.SequentInstantiationRule) - newProofStep(step) - instantiatedFacts = (instFact, steps.length - 1) :: instantiatedFacts - } - - /** - * Add an assumption the the proof, i.e. a formula that is automatically on the left side of the sequent. - * - * @param f - */ - def addAssumption(f: F.Formula): Unit = { - if (!assumptions.contains(f)) assumptions = f :: assumptions - } - - def addElimination(f: F.Formula, elim: (Int, F.Sequent) => List[K.SCProofStep]): Unit = { - eliminations = (f, elim) :: eliminations - } - - def addDischarge(ji: Fact): Unit = { - val (s1, t1) = sequentAndIntOfFact(ji) - val f = s1.right.head - val fu = f.underlying - addElimination( - f, - (i, sequent) => - List( - SC.Cut((sequent.underlying -<< fu) ++ (s1.underlying ->> fu), t1, i, fu) - ) - ) - } - /* - 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 - - /** - * Favour using getSequent when applicable. - * @return The list of ValidatedSteps (containing a high level ProofTactic and the corresponding K.SCProofStep). - */ - def getSteps: List[ProofStep] = steps.reverse - - /** - * Favour using getSequent when applicable. - * @return The list of Imports validated in the formula, with their original justification. - */ - def getImports: List[(OutsideFact, F.Sequent)] = imports.reverse - - /** - * @return The list of formulas that are assumed for the reminder of the proof. - */ - def getAssumptions: List[F.Formula] = assumptions - - /** - * Produce the low level [[K.SCProof]] corresponding to the proof. Automatically eliminates any formula in the discharges that is still left of the sequent. - * - * @return - */ - def toSCProof: K.SCProof = { - import lisa.utils.KernelHelpers.{-<<, ->>} - val finalSteps = eliminations.foldLeft[(List[SC.SCProofStep], F.Sequent)]((steps.map(_.scps), steps.head.bot)) { (cumul_bot, f_elim) => - val (cumul, bot) = cumul_bot - val (f, elim) = f_elim - val i = cumul.size - val elimSteps = elim(i - 1, bot) - (elimSteps.foldLeft(cumul)((cumul2, step) => step :: cumul2), bot -<< f) - } - - val r = K.SCProof(finalSteps._1.reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) - r - } - - def currentSCProof: K.SCProof = K.SCProof(steps.map(_.scps).reverse.toIndexedSeq, getImports.map(of => of._2.underlying).toIndexedSeq) - - /** - * For a fact, returns the sequent that the fact proove and the position of the fact in the proof. - * - * @param fact Any fact, possibly instantiated, belonging to the proof - * @return its proven sequent and position - */ - def sequentAndIntOfFact(fact: Fact): (F.Sequent, Int) = fact match { - case i: Int => - ( - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(steps.length - i - 1).bot - else { - val i2 = -(i + 1) - if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") - else imports(imports.length + i)._2 - }, - i - ) - case ds: ProofStep => (ds.bot, ds.position) - case instFact: InstantiatedFact => - val r = instantiatedFacts.find(instFact == _._1) - r match { - case Some(value) => (instFact.result, value._2) - case None => - addInstantiatedFact(instFact) - (instFact.result, steps.length - 1) - } - case of: OutsideFact @unchecked => - val r = imports.indexWhere(of == _._1) - if (r != -1) { - (imports(r)._2, r - imports.length) - } else { - val r2 = sequentOfOutsideFact(of) - addImport(of, r2) - (r2, -imports.length) - } - } - - def sequentOfFact(fact: Fact): F.Sequent = fact match { - case i: Int => - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(steps.length - i - 1).bot - else { - val i2 = -(i + 1) - if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") - else imports(imports.length + i)._2 - } - case ds: ProofStep => ds.bot - case instfact: InstantiatedFact => instfact.result - case of: OutsideFact @unchecked => - val r = imports.find(of == _._1) - if (r.nonEmpty) { - r.get._2 - } else { - sequentOfOutsideFact(of) - } - } - - def sequentOfOutsideFact(of: OutsideFact): F.Sequent - - def getSequent(f: Fact): F.Sequent = sequentOfFact(f) - def mostRecentStep: ProofStep = steps.head - - /** - * The number of steps in the proof. This is not the same as the number of steps in the corresponding [[K.SCProof]]. - * This also does not count the number of steps in the subproof. - * - * @return - */ - def length: Int = steps.length - - /** - * 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) - - /** - * Used to "lift" the type of a justification when the compiler can't infer it. - */ - def asOutsideFact(j: JUSTIFICATION): OutsideFact - - def depth: Int = - (this: @unchecked) match { - case p: Proof#InnerProof => 1 + p.parent.depth - case _: BaseProof => 0 - } - - /** - * Create a subproof inside the current proof. The subproof will have the same assumptions as the current proof. - * Can have a goal known in advance (usually for a user-written subproof) or not (usually for a tactic-generated subproof). - */ - def newInnerProof(possibleGoal: Option[F.Sequent]) = new InnerProof(possibleGoal) - final class InnerProof(val possibleGoal: Option[F.Sequent]) extends Proof(this.getAssumptions) { - val parent: Proof.this.type = Proof.this - val owningTheorem: THM = parent.owningTheorem - type OutsideFact = parent.Fact - override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = parent.asOutsideFact(j) - - override def sequentOfOutsideFact(of: parent.Fact): F.Sequent = of match { - case j: JUSTIFICATION => j.statement - case ds: Proof#ProofStep => ds.bot - case _ => parent.sequentOfFact(of) - } - } - - /** - * Contains the result of a tactic computing a K.SCProofTactic. - * Can be successful or unsuccessful. - */ - sealed abstract class ProofTacticJudgement { - val tactic: ProofTactic - val proof: Proof = Proof.this - - /** - * Returns true if and only if the judgement is valid. - */ - def isValid: Boolean = this match { - case ValidProofTactic(_, _, _) => true - case InvalidProofTactic(_) => false - } - - def validate(line: sourcecode.Line, file: sourcecode.File): ProofStep = { - this match { - case vpt: ValidProofTactic => newProofStep(vpt) - case ipt: InvalidProofTactic => - val e = lisa.prooflib.ProofTacticLib.UnapplicableProofTactic(ipt.tactic, ipt.proof, ipt.message)(using line, file) - e.setStackTrace(ipt.stack) - throw e - } - } - } - - /** - * A Kernel Sequent Calculus proof step that has been correctly produced. - */ - case class ValidProofTactic(bot: lisa.fol.FOL.Sequent, scps: Seq[K.SCProofStep], imports: Seq[Fact])(using val tactic: ProofTactic) extends ProofTacticJudgement {} - - /** - * A proof step which led to an error when computing the corresponding K.Sequent Calculus proof step. - */ - case class InvalidProofTactic(message: String)(using val tactic: ProofTactic) extends ProofTacticJudgement { - 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 - override inline def asOutsideFact(j: JUSTIFICATION): OutsideFact = j - - override def sequentOfOutsideFact(j: JUSTIFICATION): F.Sequent = j.statement - - def justifications: List[JUSTIFICATION] = getImports.map(_._1) - } - - /** - * Abstract class representing theorems, axioms and different kinds of definitions. Corresponds to a [[theory.Justification]]. - */ - sealed abstract class JUSTIFICATION { - - /** - * A pretty representation of the justification - */ - def repr: String - - /** - * The inner kernel justification - */ - def innerJustification: theory.Justification - - /** - * The sequent that the justification proves - */ - def statement: F.Sequent - - /** - * The complete name of the justification. Two justifications should never have the same full name. Typically, path is used to disambiguate. - */ - def fullName: String - - /** - * The short name of the justification (without the path). - */ - val name: String = fullName.split("\\.").last - - /** - * The "owning" object of the justification. Typically, the package/object in which it is defined. - */ - val owner = fullName.split("\\.").dropRight(1).mkString(".") - - /** - * Returns if the statement is unconditionaly proven or if it depends on some sorry step (including in the other justifications it relies on) - */ - def withSorry: Boolean = innerJustification match { - case thm: theory.Theorem => thm.withSorry - case fd: theory.FunctionDefinition => fd.withSorry - case pd: theory.PredicateDefinition => false - case ax: theory.Axiom => false - } - } - - /** - * A Justification, corresponding to [[K.Axiom]] - */ - class AXIOM(innerAxiom: theory.Axiom, val axiom: F.Formula, val fullName: String) extends JUSTIFICATION { - def innerJustification: theory.Axiom = innerAxiom - val statement: F.Sequent = F.Sequent(Set(), Set(axiom)) - if (statement.underlying != theory.sequentFromJustification(innerAxiom)) { - throw new InvalidAxiomException("The provided kernel axiom and desired statement don't match.", name, axiom, library) - } - def repr: String = s" Axiom $name := $axiom" - } - - /** - * Introduces a new axiom in the theory. - * - * @param fullName The name of the axiom, including the path. Usually fetched automatically by the compiler. - * @param axiom The axiomatized formula. - * @return - */ - def Axiom(using fullName: sourcecode.FullName)(axiom: F.Formula): AXIOM = { - val ax: Option[theory.Axiom] = theory.addAxiom(fullName.value, axiom.underlying) - ax match { - case None => throw new InvalidAxiomException("Not all symbols belong to the theory", fullName.value, axiom, library) - case Some(value) => AXIOM(value, axiom, fullName.value) - } - } - - /** - * A Justification, corresponding to [[K.FunctionDefinition]] or [[K.PredicateDefinition]] - */ - abstract class DEFINITION(line: Int, file: String) extends JUSTIFICATION { - val fullName: String - def repr: String = innerJustification.repr - - def label: F.ConstantLabel[?] - knownDefs.update(label, Some(this)) - - } - - /** - * A proven, reusable statement. A justification corresponding to [[K.Theorem]]. - */ - sealed abstract class THM extends JUSTIFICATION { - def repr: String = - s" Theorem ${name} := ${statement}${if (withSorry) " (!! Relies on Sorry)" else ""}" - - /** - * The underlying Kernel proof [[K.SCProof]], if it is still available. Proofs are not kept in memory for efficiency. - */ - def kernelProof: Option[K.SCProof] - - /** - * The high level [[Proof]], if one was used to obtain the theorem. If the theorem was not produced by such high level proof but directly by a low level one, this is None. - */ - def highProof: Option[BaseProof] - val innerJustification: theory.Theorem - - /** - * A pretty representation of the goal of the theorem - */ - def prettyGoal: String = lisa.utils.FOLPrinter.prettySequent(statement.underlying) - } - object THM { - - /** - * Standard way to construct a theorem using a high level proof. - * - * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] - * @param statement The statement of the theorem - * @param fullName The full name of the theorem, including the path. Usually fetched automatically by the compiler. - * @param line The line at which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting - * @param file The file in which the theorem is defined. Usually fetched automatically by the compiler. Used for error reporting - * @param kind The kind of theorem (Theorem, Lemma, Corollary) - * @param computeProof The proof computation. The proof is built by adding proof steps to the proof object. The proof object is an impicit argument of computeProof, - * @see Context Functions in Scala - * @return - */ - def apply(using om: OutputManager)(statement: F.Sequent, fullName: String, line: Int, file: String, kind: TheoremKind)(computeProof: Proof ?=> Unit) = - THMFromProof(statement, fullName, line, file, kind)(computeProof) - - /** - * Constructs a "high level" theorem from an existing theorem in the - * - * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] - * @param statement The statement of the theorem - * @param fullName The full name of the theorem, including the path/package. - * @param kind The kind of theorem (Theorem, Lemma, Corollary) - * @param innerThm The inner theorem, coming from the kernel - * @param getProof If available, a way to compute the Kernel proof again. - */ - def fromKernel(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) = - THMFromKernel(statement, fullName, kind, innerThm, getProof) - - /** - * Construct a theorem (both in the kernel and high level) from a proof. - * - * @param om The output manager, available in any file extending [[lisa.utils.BasicMain]] - * @param statement The statement of the theorem - * @param fullName The full name of the theorem, including the path/package. - * @param kind The kind of theorem (Theorem, Lemma, Corollary) - * @param getProof The kernel proof. - * @param justifs low level justifications used to justify the proof's imports - * @return - */ - def fromSCProof(using om: OutputManager)(statement: F.Sequent, fullName: String, kind: TheoremKind, getProof: () => K.SCProof, justifs: Seq[theory.Justification]): THM = - val proof = getProof() - theory.theorem(fullName, statement.underlying, proof, justifs) match { - case K.Judgement.ValidJustification(just) => - fromKernel(statement, fullName, kind, just.asInstanceOf, () => Some(getProof())) - case wrongJudgement: K.Judgement.InvalidJustification[?] => - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The proof was rejected by LISA's logical kernel. ", - wrongJudgement, - None - ) - ) - } - - } - - /** - * A theorem that was produced from a kernel theorem and not from a high level proof. See [[THM.fromKernel]]. - * Those are typically theorems imported from another tool, or from serialization. - */ - class THMFromKernel(using om: OutputManager)(val statement: F.Sequent, val fullName: String, val kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) extends THM { - - val innerJustification: theory.Theorem = innerThm - assert(innerThm.name == fullName) - def kernelProof: Option[K.SCProof] = getProof() - def highProof: Option[BaseProof] = None - - val goal: F.Sequent = statement - - } - - /** - * A theorem that was produced from a high level proof. See [[THM.apply]]. - * Typical way to construct a theorem in the library, but serialization for example will produce a [[THMFromKernel]]. - */ - class THMFromProof(using om: OutputManager)(val statement: F.Sequent, val fullName: String, line: Int, file: String, val kind: TheoremKind)(computeProof: Proof ?=> Unit) extends THM { - - val goal: F.Sequent = statement - - val proof: BaseProof = new BaseProof(this) - def kernelProof: Option[K.SCProof] = Some(proof.toSCProof) - def highProof: Option[BaseProof] = Some(proof) - - import lisa.utils.Serialization.* - val innerJustification: theory.Theorem = - if library._draft.nonEmpty && library._draft.get.value != file - then // if the draft option is activated, and the theorem is not in the file where the draft option is given, then we replace the proof by sorry - theory.theorem(name, goal.underlying, SCProof(SC.Sorry(goal.underlying)), IndexedSeq.empty) match { - case K.Judgement.ValidJustification(just) => - just - case wrongJudgement: K.Judgement.InvalidJustification[?] => - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", - wrongJudgement, - Some(proof) - ) - ) - } - else if library._withCache then - oneThmFromFile("cache/" + name, library.theory) match { - case Some(thm) => thm // try to get the theorem from file - - case None => - val (thm, scp, justifs) = prove(computeProof) // if fail, prove it - thmsToFile("cache/" + name, theory, List((name, scp, justifs))) // and save it to the file - thm - } - else prove(computeProof)._1 - - library.last = Some(this) - - /** - * Construct the kernel theorem from the high level proof - */ - private def prove(computeProof: Proof ?=> Unit): (theory.Theorem, SCProof, List[(String, theory.Justification)]) = { - try { - computeProof(using proof) - } catch { - case e: UserLisaException => - om.lisaThrow(e) - } - - if (proof.length == 0) - om.lisaThrow(new UnimplementedProof(this)) - - val scp = proof.toSCProof - val justifs = proof.getImports.map(e => (e._1.owner, e._1.innerJustification)) - theory.theorem(name, goal.underlying, scp, justifs.map(_._2)) match { - case K.Judgement.ValidJustification(just) => - (just, scp, justifs) - case wrongJudgement: K.Judgement.InvalidJustification[?] => - om.lisaThrow( - LisaException.InvalidKernelJustificationComputation( - "The final proof was rejected by LISA's logical kernel. This may be due to a faulty proof computation or lack of verification by a proof tactic.", - wrongJudgement, - Some(proof) - ) - ) - } - } - - } - - given thmConv: Conversion[library.THM, theory.Theorem] = _.innerJustification - - trait TheoremKind { - val kind2: String - - def apply(using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File)(statement: F.Sequent)(computeProof: Proof ?=> Unit): THM = { - val thm = THM(statement, name.value, line.value, file.value, this)(computeProof) - if this == Theorem then show(thm) - thm - } - - } - - /** - * A "Theorem" kind of theorem, by opposition with a lemma or corollary. The difference is that theorem are always printed when a file defining one is run. - */ - object Theorem extends TheoremKind { val kind2: String = "Theorem" } - - /** - * Lemmas are like theorems, but are conceptually less importants and are not printed when a file defining one is run. - */ - object Lemma extends TheoremKind { val kind2: String = "Lemma" } - - /** - * Corollaries are like theorems, but are conceptually less importants and are not printed when a file defining one is run. - */ - object Corollary extends TheoremKind { val kind2: String = "Corollary" } - - /** - * Internal statements are internally produced theorems, for example as intermediate step in definitions. - */ - object InternalStatement extends TheoremKind { val kind2: String = "Internal, automatically produced" } - -} diff --git a/backup/unification/UnificationUtils.scala b/backup/unification/UnificationUtils.scala deleted file mode 100644 index dcc762fa..00000000 --- a/backup/unification/UnificationUtils.scala +++ /dev/null @@ -1,619 +0,0 @@ -package lisa.utils.unification - -import lisa.fol.FOL.{_, given} -//import lisa.fol.FOLHelpers.* - -//import lisa.kernel.fol.FOL.* -//import lisa.utils.KernelHelpers.{_, given} - -/** - * General utilities for unification, substitution, and rewriting - */ -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 - while (res.isEmpty && iter.hasNext) { - res = f(iter.next()) - } - res - } - - } - - /** - * All the information required for performing rewrites. - */ - case class RewriteContext( - freeFormulaRules: Seq[(Formula, Formula)] = Seq.empty, - freeTermRules: Seq[(Term, Term)] = Seq.empty, - confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, - confinedTermRules: Seq[(Term, Term)] = Seq.empty, - takenFormulaVars: Set[VariableFormula] = Set.empty, - takenTermVars: Set[Variable] = Set.empty - ) { - private var lastID: Identifier = freshId((takenFormulaVars ++ takenTermVars).map(_.id), "@@rewriteVar@@") - - /** - * Generates a fresh identifier with an internal label `__rewriteVar__`. - * Mutates state. - * - * @return fresh identifier - */ - def freshIdentifier = { - lastID = freshId(Seq(lastID), "@@rewriteVar@@") - lastID - } - - def isFreeVariable(v: Variable) = !takenTermVars.contains(v) - 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 - */ - def updateTo(other: RewriteContext) = - lastID = if (other.lastID.no > lastID.no) other.lastID else lastID - } - - object RewriteContext { - def empty = RewriteContext() - } - - // substitutions - - type TermSubstitution = Map[Variable, Term] - val TermSubstitution = Map // don't abuse pls O.o - - type FormulaSubstitution = Map[VariableFormula, Formula] - 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. - */ - 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. - */ - private def matchTermRecursive(using context: RewriteContext)(reference: Term, template: Term, substitution: TermSubstitution): Option[TermSubstitution] = - if (reference == template) - Some(substitution) - else - template match { - case v @ Variable(id) if context.isFreeVariable(v) => - // different label but substitutable or already correctly set - if (reference != template && reference == substitution.getOrElse(v, reference)) Some(substitution + (v -> reference)) - // same and not already substituted to something else - else if (reference == template && reference == substitution.getOrElse(v, reference)) Some(substitution) - // unsat - else None - // {Constant, Schematic} FunctionLabel - case _ => - if (reference.label != template.label) None - else - (reference.args zip template.args).foldLeft(Option(substitution)) { - case (Some(subs), (r, t)) => matchTermRecursive(r, t, subs) - case (None, _) => None - } - } - - /** - * 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, - takenFormulaVars = takenFormulaVariables.toSet - ) - matchFormulaRecursive(using context)(reference, template, FormulaSubstitution.empty, TermSubstitution.empty) - } - - /** - * 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. - */ - private def matchFormulaRecursive(using - context: RewriteContext - )(reference: Formula, template: Formula, formulaSubstitution: FormulaSubstitution, termSubstitution: TermSubstitution): Option[(FormulaSubstitution, TermSubstitution)] = { - if (isSame(reference, template)) - Some((formulaSubstitution, termSubstitution)) - else - (reference, template) match { - case (BinderFormula(labelR, boundR, innerR), BinderFormula(labelT, boundT, innerT)) if labelR == labelT => { - val freshVar = Variable(context.freshIdentifier) - - // add a safety substitution to make sure bound variable isn't substituted, and check instantiated bodies - val innerSubst = matchFormulaRecursive( - innerR.substitute(boundR := freshVar), - innerT.substitute(boundT := freshVar), - formulaSubstitution, - termSubstitution + (freshVar -> freshVar) // dummy substitution to make sure we don't attempt to match this as a variable - ) - - innerSubst match { - case None => innerSubst - case Some((sf, st)) => { - val cleanSubst = (sf, st - freshVar) // remove the dummy substitution we added - - // were any formula substitutions involving the bound variable required? - // if yes, not matchable - if (cleanSubst._1.exists((k, v) => v.freeVariables.contains(freshVar))) None - else Some(cleanSubst) - } - } - } - - case (AppliedConnector(labelR, argsR), AppliedConnector(labelT, argsT)) if labelR == labelT => - if (argsR.length != argsT.length) - // quick discard - None - else { - // recursively check inner formulas - val newSubstitution = (argsR zip argsT).foldLeft(Option(formulaSubstitution, termSubstitution)) { - case (Some(substs), (ref, temp)) => matchFormulaRecursive(ref, temp, substs._1, substs._2) - case (None, _) => None - } - newSubstitution - } - - case (_, template: VariableFormula) => - // can this variable be matched with the reference based on previously known or new substitutions? - if (reference == formulaSubstitution.getOrElse(template, reference)) Some(formulaSubstitution + (template -> reference), termSubstitution) - else if (template == reference && reference == formulaSubstitution.getOrElse(template, reference)) Some(formulaSubstitution, termSubstitution) - else None - - case (AppliedPredicate(labelR, argsR), AppliedPredicate(labelT, argsT)) if labelR == labelT => - if (argsR.length != argsT.length) - // quick discard - None - else { - // our arguments are terms, match them recursively - val newTermSubstitution = (argsR zip argsT).foldLeft(Option(termSubstitution)) { - case (Some(tSubst), (ref, temp)) => matchTermRecursive(ref, temp, tSubst) - case (None, _) => None - } - if (newTermSubstitution.isEmpty) None - else Some(formulaSubstitution, newTermSubstitution.get) - } - case _ => None - } - } - // 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))` - */ - 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)))` - */ - 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 - */ - case class TermRewriteLambda( - termVars: Seq[Variable] = Seq.empty, - termRules: Seq[(Variable, TermRule)] = Seq.empty, - body: Term - ) {} - - /** - * 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, - body: Formula - ) { - - /** - * **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**. - */ - def toLambdaFF: LambdaExpression[Formula, Formula, ?] = LambdaExpression(formulaRules.map(_._1), body, formulaRules.size) - } - - /** - * 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 - */ - 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]]. - */ - 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]]. - */ - 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 - */ - def getContextTerm( - first: Term, - second: Term, - freeTermRules: Seq[(Term, Term)], - confinedTermRules: Seq[(Term, Term)] = Seq.empty, - takenTermVariables: Set[Variable] = Set.empty - ): Option[TermRewriteLambda] = { - val context = RewriteContext( - takenTermVars = takenTermVariables, - freeTermRules = freeTermRules, - confinedTermRules = confinedTermRules - ) - getContextRecursive(using context)(first, second) - } - - /** - * 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 = - context.confinedTermRules - .getFirst { case (l, r) => - val subst = canRewrite(using context)(first, second, (l, r)) - subst.map(s => ((l, r), s)) - } - .orElse { - // free all variables for substitution - // matchTermRecursive does not generate any free variables - // so it cannot affect global state, so this is safe to do - val freeContext = context.copy(takenTermVars = Set.empty) - freeContext.freeTermRules.getFirst { case (l, r) => - val subst = canRewrite(using freeContext)(first, second, (l, r)) - subst.map(s => ((l, r), s)) - } - } - - if (first == second) Some(TermRewriteLambda(body = first)) - else if (validSubstitution.isDefined) { - val newVar = Variable(context.freshIdentifier) - val body = newVar // newVar() - Some( - TermRewriteLambda( - Seq(newVar), - Seq(newVar -> validSubstitution.get), - body - ) - ) - } else if (first.label != second.label || first.args.length != second.args.length) None - else { - // recurse - // known: first.label == second.label - // first.args.length == second.args.length - // and first cannot be rewritten into second - val innerSubstitutions = (first.args zip second.args).map(arg => getContextRecursive(using context)(arg._1, arg._2)) - - if (innerSubstitutions.exists(_.isEmpty)) None - else { - val retrieved = innerSubstitutions.map(_.get) - val body = first.label.applySeq(retrieved.map(_.body)) - val lambda = - retrieved.foldLeft(TermRewriteLambda(body = body)) { case (currentLambda, nextLambda) => - TermRewriteLambda( - currentLambda.termVars ++ nextLambda.termVars, - currentLambda.termRules ++ nextLambda.termRules, - currentLambda.body - ) - } - Some(lambda) - } - } - } - - /** - * 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, - freeTermRules: Seq[(Term, Term)] = Seq.empty, - freeFormulaRules: Seq[(Formula, Formula)] = Seq.empty, - confinedTermRules: Seq[(Term, Term)] = Seq.empty, - takenTermVariables: Set[Variable] = Set.empty, - confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, - takenFormulaVariables: Set[VariableFormula] = Set.empty - ): Option[FormulaRewriteLambda] = { - val context = RewriteContext( - takenTermVars = takenTermVariables, - takenFormulaVars = takenFormulaVariables, - freeTermRules = freeTermRules, - confinedTermRules = confinedTermRules, - freeFormulaRules = freeFormulaRules, - confinedFormulaRules = confinedFormulaRules - ) - getContextRecursive(using context)(first, second) - } - - def getContextFormulaSet( - first: Seq[Formula], - second: Seq[Formula], - freeTermRules: Seq[(Term, Term)], - freeFormulaRules: Seq[(Formula, Formula)], - confinedTermRules: Seq[(Term, Term)] = Seq.empty, - takenTermVariables: Set[Variable] = Set.empty, - confinedFormulaRules: Seq[(Formula, Formula)] = Seq.empty, - takenFormulaVariables: Set[VariableFormula] = Set.empty - ): Option[Seq[FormulaRewriteLambda]] = { - val context = RewriteContext( - takenTermVars = takenTermVariables, - takenFormulaVars = takenFormulaVariables, - freeTermRules = freeTermRules, - confinedTermRules = confinedTermRules, - freeFormulaRules = freeFormulaRules, - confinedFormulaRules = confinedFormulaRules - ) - - val substSeq = first.map { f => - second.getFirst { s => - val newContext = context.copy() - val subst = getContextRecursive(using newContext)(f, s) - subst.foreach { _ => context.updateTo(newContext) } - subst - } - } - - // Seq[Option[_]] -> Option[Seq[_]] - substSeq.foldLeft(Option(Seq.empty[FormulaRewriteLambda]))((f, s) => f.flatMap(f1 => s.map(s1 => f1 :+ s1))) - } - - /** - * 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 = - context.confinedFormulaRules - .getFirst { (l: Formula, r: Formula) => - val subst = canRewrite(using context)(first, second, (l, r)) - subst.map(s => ((l, r), s)) - } - .orElse { - // free all variables for substitution - // matchFormulaRecursive generates but does not expose any new variables - // It cannot affect global state, so this is safe to do - val freeContext = context.copy(takenTermVars = Set.empty) - freeContext.freeFormulaRules.getFirst { case (l, r) => - val subst = canRewrite(using freeContext)(first, second, (l, r)) - subst.map(s => ((l, r), s)) - } - } - - if (isSame(first, second)) Some(FormulaRewriteLambda(body = first)) - else if (validSubstitution.isDefined) { - val newVar = VariableFormula(context.freshIdentifier) - val body = newVar // newVar() - Some( - FormulaRewriteLambda( - Seq(), - Seq(newVar -> validSubstitution.get), - body - ) - ) - } // else if (first.label != second.label) None //Should not pass the next match anyway - else { - // recurse - // known: first.label == second.label - // and first cannot be rewritten into second - (first, second) match { - case (BinderFormula(labelF, boundF, innerF), BinderFormula(labelS, boundS, innerS)) => { - val freshVar = Variable(context.freshIdentifier) - val freeContext = context.copy(takenTermVars = context.takenTermVars + freshVar) - - // add a safety substitution to make sure bound variable isn't substituted, and check instantiated bodies - val innerSubst = getContextRecursive(using freeContext)( - innerF.substitute(boundF := freshVar), - innerS.substitute(boundS := freshVar) - ) - - context.updateTo(freeContext) - - innerSubst.map(s => s.copy(body = BinderFormula(labelF, freshVar, s.body))) - } - - case (AppliedConnector(labelF, argsF), AppliedConnector(labelS, argsS)) => - if (argsF.length != argsS.length) - // quick discard - None - else { - // recursively check inner formulas - val innerSubstitutions = (argsF zip argsS).map(arg => getContextRecursive(using context)(arg._1, arg._2)) - - if (innerSubstitutions.exists(_.isEmpty)) None - else { - val retrieved = innerSubstitutions.map(_.get) - val body = AppliedConnector(labelF, retrieved.map(_.body)) - val lambda = - retrieved.foldLeft(FormulaRewriteLambda(body = body)) { case (currentLambda, nextLambda) => - FormulaRewriteLambda( - currentLambda.termRules ++ nextLambda.termRules, - currentLambda.formulaRules ++ nextLambda.formulaRules, - currentLambda.body - ) - } - Some(lambda) - } - } - - case (AppliedPredicate(labelF, argsF), AppliedPredicate(labelS, argsS)) => - if (argsF.length != argsS.length) - // quick discard - None - else { - // our arguments are terms, get contexts from them recursively - val innerSubstitutions = (argsF zip argsS).map(arg => getContextRecursive(using context)(arg._1, arg._2)) - - if (innerSubstitutions.exists(_.isEmpty)) None - else { - val retrieved = innerSubstitutions.map(_.get) - val body = AppliedPredicate(labelF, retrieved.map(_.body)) - val lambda = - retrieved.foldLeft(FormulaRewriteLambda(body = body)) { case (currentLambda, nextLambda) => - FormulaRewriteLambda( - currentLambda.termRules ++ nextLambda.termRules, - currentLambda.formulaRules, - currentLambda.body - ) - } - Some(lambda) - } - } - case _ => None - } - } - } - -} diff --git a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala index d1367fee..50cc782c 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,11 +123,11 @@ 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 @@ -145,69 +140,42 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque val seq1 = AugSequent((atom :: s.decisions._1, s.decisions._2), lambdaF(Seq(top()))) val proof1 = solveAugSequent(seq1, offset) + val hyp1 = RestateTrue(atom |- atom) 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 - 2, offset + proof1.length - 1, - List((LambdaTermFormula(Seq(), atom), LambdaTermFormula(Seq(), top()))), + atom, top, + Seq(), (lambdaF.vars, lambdaF.body) ) + val negatom = Neg(atom) val seq2 = AugSequent((s.decisions._1, atom :: s.decisions._2), lambdaF(Seq(bot()))) val proof2 = solveAugSequent(seq2, offset + proof1.length + 1) + val hyp2 = RestateTrue(negatom |- negatom) 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()))), + negatom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => Neg(f)) |- redF, + offset + proof1.length + proof2.length + 1 - 2, + offset + proof1.length + proof2.length + 1 - 1, + atom, bot, + Seq(), (lambdaF.vars, lambdaF.body) ) - 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) = { + private def findSubformula2(f: Expression, subs: Seq[(VariableFormulaLabel, Expression)]): (Expression, Boolean) = { val eq = subs.find(s => isSame(f, s._2)) if (eq.nonEmpty) (eq.get._1(), true) else @@ -233,21 +201,8 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque } } } - 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] = { + def findSubformula(f: Expression, subs: Seq[(VariableFormulaLabel, Expression)]): Option[LambdaFormulaFormula] = { val vars = subs.map(_._1) val r = findSubformula2(f, subs) if (r._2) Some(LambdaFormulaFormula(vars, r._1)) 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 5a986a39..00000000 --- a/lisa-utils/src/test/scala/lisa/kernel/FolTests.scala +++ /dev/null @@ -1,121 +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 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()): Expression = { - if (maxDepth <= 1) { - val r = gen.between(0, 3) - if (r == 0) { - val name = "" + ('a' to 'e')(gen.between(0, 5)) - Constant(name, Term) - } else { - val name = "" + ('v' to 'z')(gen.between(0, 5)) - Variable(name, Term) - } - } 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)) - Constant(name, Term) - } else if (r == 1) { - val name = "" + ('v' to 'z')(gen.between(0, 5)) - Variable(name, Term) - } - else 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)) - } - - */ -} From e4c3036ff7e23c6074b2a8880683924a4982179a Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Tue, 22 Oct 2024 19:11:09 +0200 Subject: [PATCH 26/92] Tautology and Tableaux compile. Experiments with unapply paterns in lisa.utils.fol. --- lisa-sets/src/main/scala/lisa/Main.scala | 2 +- .../scala/lisa/automation/Congruence.scala | 2 +- .../lisa/automation/CongruenceSimp.scala | 4 +- .../main/scala/lisa/automation/Tableau.scala | 324 +++++++++--------- .../scala/lisa/automation/Tautology.scala | 67 ++-- .../main/scala/lisa/utils/KernelHelpers.scala | 93 +++-- .../main/scala/lisa/utils/fol/Predef.scala | 17 + .../main/scala/lisa/utils/fol/Sequents.scala | 2 +- .../main/scala/lisa/utils/fol/Syntax.scala | 44 ++- .../scala/lisa/utils/tptp/KernelParser.scala | 2 +- .../scala/lisa/utils/tptp/ProofParser.scala | 16 +- 11 files changed, 318 insertions(+), 255 deletions(-) diff --git a/lisa-sets/src/main/scala/lisa/Main.scala b/lisa-sets/src/main/scala/lisa/Main.scala index c96ac042..8a49a8f6 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/automation/Congruence.scala b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala index 8c60de41..cd2faf06 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala @@ -4,7 +4,6 @@ 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 /** @@ -35,6 +34,7 @@ object Congruence extends ProofTactic with ProofSequentTactic { egraph.addAll(bot.right) bot.left.foreach{ + case equality(left, right) => ??? case (left === right) => egraph.merge(left, right) case (left <=> right) => egraph.merge(left, right) case _ => () diff --git a/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala index b6438378..e8860e96 100644 --- a/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala +++ b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala @@ -4,7 +4,6 @@ import lisa.prooflib.BasicStepTactic.* import lisa.prooflib.ProofTacticLib.* import lisa.prooflib.SimpleDeducedSteps.* import lisa.prooflib.* -import lisa.utils.parsing.UnreachableException @@ -20,6 +19,7 @@ import scala.collection.mutable class EGraphTermsSimp() { + /* val termParents = mutable.Map[Term, mutable.Set[AppliedFunctional]]() val termUF = new UnionFind[Term]() @@ -167,6 +167,6 @@ class EGraphTermsSimp() { } } } - +*/ } \ No newline at end of file diff --git a/lisa-sets/src/main/scala/lisa/automation/Tableau.scala b/lisa-sets/src/main/scala/lisa/automation/Tableau.scala index de6f6bcb..6302f59a 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 50cc782c..e4953ad7 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala @@ -127,85 +127,82 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque val bestAtom = findBestAtom(s.formula) val redF = reducedForm(s.formula) if (redF == top()) { - List(RestateTrue(s.decisions._1 ++ s.decisions._2.map((f: Expression) => 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 hyp1 = RestateTrue(atom |- atom) val subst1 = RightSubstIff( - atom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => Neg(f)) |- redF, + atom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, offset + proof1.length - 2, offset + proof1.length - 1, atom, top, Seq(), - (lambdaF.vars, lambdaF.body) + (MaRvIn, lambdaF) ) - val negatom = Neg(atom) - 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 hyp2 = RestateTrue(negatom |- negatom) val subst2 = RightSubstIff( - negatom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => Neg(f)) |- redF, + negatom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF, offset + proof1.length + proof2.length + 1 - 2, offset + proof1.length + proof2.length + 1 - 1, atom, bot, Seq(), - (lambdaF.vars, lambdaF.body) + (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) + 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(f: Expression, subs: Seq[(VariableFormulaLabel, Expression)]): (Expression, 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 findSubformula(f: Expression, subs: Seq[(VariableFormulaLabel, Expression)]): 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-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 501f750b..9c496abc 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -33,68 +33,87 @@ object KernelHelpers { 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 ∀ = forall - val Exists = exists val ∃ = exists - val Epsilon = epsilon val ε = epsilon - extension (binder: forall.type) { - @targetName("forallApply") - def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) - @targetName("forallUnapply") - def unapply(e: Expression): Option[(Variable, Expression)] = e match { + // UnapplyMethods + + object And : + def unapply(e: Expression): Option[(Expression, Expression)] = e match + case Application(Application(`and`, l), r) => Some((l, r)) + case _ => None + + object Or : + def unapply(e: Expression): Option[(Expression, Expression)] = e match + case Application(Application(`or`, l), r) => Some((l, r)) + case _ => None + + 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 + + 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 - } - } - extension (binder: exists.type) { - @targetName("existsApply") - def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) - @targetName("existsUnapply") - def unapply(e: Expression): Option[(Variable, Expression)] = e match { + + object Exists : + def unapply(e: Expression): Option[(Variable, Expression)] = e match case Application(`exists`, Lambda(x, inner)) => Some((x, inner)) case _ => None - } - } - extension (binder: epsilon.type) { - @targetName("epsilonApply") - def apply(bound: Variable, inner: Expression): Expression = binder(Lambda(bound, inner)) - @targetName("epsilonUnapply") - def unapply(e: Expression): Option[(Variable, Expression)] = e match { + + object Epsilon : + def unapply(e: Expression): Option[(Variable, Expression)] = e match case Application(`epsilon`, Lambda(x, inner)) => Some((x, inner)) 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 */ + 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 { + 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 _ => None inner(e).map(l => {val rev = l.reverse; (rev.head, rev.tail)}) - - } + + + + + 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: Expression) { def apply(args: Expression*): Expression = multiapply(f)(args) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index 51df9506..cc3f836d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -5,6 +5,23 @@ import K.given trait Predef extends Syntax { + private val e : Expr[F] = ??? + val eee : Term >>: Term >>: Formula = equality + val (x, y) = eee.unapplySeq[F](e).get + + def printt(t: Term): Unit = println(t) + + + def printv(v: Variable[?]): Unit = println(v.id) + e match { + case exists(x, f) => printv(x); print(f) + case ===[Arrow[T, F]](l) => print(l) + case ===[F](l, r) => print(l); print(r) + case #@[T, Arrow[T, F]](`===`, l) #@ r => printt(l: Term); printt(r) + case eee[F](l: Expr[T], r: Expr[T]) => print(l); print(r) + case (l === r) => print(l); print(r) + } + 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) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala index b09ed3b5..ff9fc5e1 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Sequents.scala @@ -48,7 +48,7 @@ trait Sequents extends Predef { 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: Formula = f.substitute(x := t) val s0 = K.Hypothesis((newf |- newf).underlying, newf.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 index 1f4b98e4..9c30e37f 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -13,9 +13,9 @@ trait Syntax { type IsSort[T] = Sort{type Self = T} - trait T - trait F - trait Arrow[A: Sort, B: Sort] + sealed trait T + sealed trait F + sealed trait Arrow[A: Sort, B: Sort] type Formula = Expr[F] type Term = Expr[T] @@ -55,6 +55,12 @@ trait Syntax { 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 = { @@ -82,11 +88,31 @@ trait Syntax { override def substitute(pairs: SubstPair*): Expr[S] = super.substitute(pairs*).asInstanceOf[Expr[S]] - - def unapply[T1, T2](e: Expr[Arrow[T1, T2]]): Option[Expr[T1]] = (e: @unchecked) match { + 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) + + + /* + @targetName("unapply2") + def unapply[T1, T2, T3](e: Expr[T3]): Option[(Expr[T1], Expr[T2])] = (e: @unchecked) match + case App[T2, T3](e1, a2) => e1 match + case App[T1, T2 >>: T3](f, a1) if f == this => Some((a1, a2)) + case _ => None + case _ => None + + @targetName("unapply1") + def unapply[T1, T2](e: Expr[T2]): Option[Expr[T1]] = (e: @unchecked) match { case App[T1, T2](f, arg) if f == this => Some(arg) case _ => None } + */ final def defaultMkString(args: Seq[Expr[?]]): String = s"$this(${args.map(a => s"(${a})")})" final def defaultMkStringSeparated(args: Seq[Expr[?]]): String = s"(${defaultMkString(args)})" var mkString: Seq[Expr[?]] => String = defaultMkString @@ -106,6 +132,11 @@ trait Syntax { 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) @@ -197,7 +228,7 @@ trait Syntax { 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[?]): Option[(Variable[T1], Expr[T2])] = e match { + 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 } @@ -260,6 +291,7 @@ trait Syntax { + } 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 5d4f1d31..5414be78 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/KernelParser.scala @@ -48,7 +48,7 @@ object KernelParser { } 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 { 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 852c99ef..4fbaf2c4 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -287,7 +287,7 @@ object ProofParser { 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 @@ -350,7 +350,7 @@ object ProofParser { 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 } } @@ -401,7 +401,7 @@ object ProofParser { 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 } } @@ -422,7 +422,7 @@ object ProofParser { 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.substituteVariables(K.Neg(phi), Map(y -> xl)), x), name)) + else Some((K.LeftExists(convertToKernel(sequent), numbermap(t1), K.substituteVariables(K.neg(phi), Map(y -> xl)), x), name)) case _ => None } } @@ -439,7 +439,7 @@ object ProofParser { case x:K.Variable => x case _ => throw new Exception(s"Expected a variable, but got $xl") val (y: K.Variable, phi: K.Expression) = convertToKernel(f) match { - case K.Exists(K.Lambda(x, phi)) => (x, phi) + 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)) @@ -457,7 +457,7 @@ object ProofParser { 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.Forall(K.Lambda(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)) @@ -474,10 +474,10 @@ object ProofParser { 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.Neg(K.Exists(K.Lambda(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.Neg(phi), x, t), name)) + Some((K.LeftForall(convertToKernel(sequent), numbermap(t1), K.neg(phi), x, t), name)) case _ => None } } From 248059c31959ef2f75863c580a6f4cc397fac546 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Wed, 23 Oct 2024 07:11:10 +0200 Subject: [PATCH 27/92] Change depth to not recalculate --- lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala index 161e4970..e685f80b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -52,10 +52,7 @@ private[fol] trait Syntax { val depth = 1+to.depth } - def depth(t:Sort): Int = t match { - case Arrow(a, b) => 1 + depth(b) - case _ => 0 - } + def depth(t:Sort): Int = t.depth def legalApplication(typ1: Sort, typ2: Sort): Option[Sort] = { From 1f671c4f57ffedbcf475398c6a2bc5c7be7a7cd2 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Wed, 23 Oct 2024 10:48:38 +0200 Subject: [PATCH 28/92] small corrections to subst steps in basic step tactics and kernel, ongoing portage of congruence. --- .../lisa/kernel/proof/SCProofChecker.scala | 10 +- .../scala/lisa/automation/Congruence.scala | 190 ++++++------------ .../lisa/automation/CongruenceSimp.scala | 2 +- .../lisa/automation/CongruenceTest.scala | 52 ++--- .../main/scala/lisa/utils/fol/Predef.scala | 17 -- .../lisa/utils/prooflib/BasicStepTactic.scala | 10 +- .../utils/prooflib/SimpleDeducedSteps.scala | 6 +- 7 files changed, 107 insertions(+), 180 deletions(-) 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 778903fe..8370634c 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -483,9 +483,9 @@ object SCProofChecker { 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) + else /*if (!psi.sort.isPredicate) SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") - else { + 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)) @@ -534,7 +534,11 @@ object SCProofChecker { val inner1 = vars.foldLeft(s)(_(_)) val inner2 = vars.foldLeft(t)(_(_)) - val sEqt = equality(inner1)(inner2) + val sEqt = + if (s.sort.isFunctional) + equality(inner1)(inner2) + else + iff(inner1)(inner2) val varss = vars.toSet if ( diff --git a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala index cd2faf06..4fc12747 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala @@ -5,6 +5,7 @@ import lisa.prooflib.ProofTacticLib.* import lisa.prooflib.SimpleDeducedSteps.* import lisa.prooflib.* import leo.datastructures.TPTP.CNF.AtomicFormula +import scala.annotation.targetName /** * This tactic tries to prove a sequent by congruence. @@ -29,12 +30,11 @@ object Congruence extends ProofTactic with ProofSequentTactic { def apply(using lib: Library, proof: lib.Proof)(bot: Sequent): proof.ProofTacticJudgement = TacticSubproof { import lib.* - val egraph = new EGraphTerms() + val egraph = new EGraphExpr() egraph.addAll(bot.left) egraph.addAll(bot.right) bot.left.foreach{ - case equality(left, right) => ??? case (left === right) => egraph.merge(left, right) case (left <=> right) => egraph.merge(left, right) case _ => () @@ -47,18 +47,20 @@ object Congruence extends ProofTactic with ProofSequentTactic { 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) + val a = variable[Formula] + val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate + have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParametersSimple(lf, rf, Seq(), (a, a))(base, eqstep) have(bot) by Cut(eq, lastStep) true else false } || bot.left.exists{ - case rf2 @ Neg(rf) if egraph.idEq(lf, rf)=> + 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) + val a = variable[Formula] + val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate + have((bot.left + (lf <=> rf)) |- (bot.right) ) by LeftSubstIff.withParametersSimple(lf, rf, Seq(), (a, !a))(base, eqstep) have(bot) by Cut(eq, lastStep) true case _ => false @@ -76,11 +78,12 @@ object Congruence extends ProofTactic with ProofSequentTactic { } then () else if bot.right.exists { rf => bot.right.exists{ - case lf2 @ Neg(lf) if egraph.idEq(lf, rf)=> + 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) + val a = variable[Formula] + val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate + have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParametersSimple(lf, rf, Seq(), (a, !a))(base, eqstep) have(bot) by Cut(eq, lastStep) true case _ => false @@ -238,36 +241,28 @@ class UnionFind[T] { 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[App[?, ?]]]() + 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(id: Expr[?]): Expr[?] = UF.find(id) - 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 ExprStep + case class CongruenceStep(between: (Expr[?], Expr[?])) extends ExprStep - 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[?]), ExprStep]() - 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[ExprStep]] = { + val steps = UF.explain(id1, id2) + steps.map(_.foldLeft((id1, List[ExprStep]())) { case ((prev, acc), step) => - termProofMap(step) match + proofMap(step) match case s @ TermExternal((l, r)) => if l == prev then (r, s :: acc) @@ -284,89 +279,38 @@ class EGraphTerms() { }._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 idEqT(id1: Term, id2: Term): Boolean = find(id1) == find(id2) + def idEqF(id1: Formula, id2: Formula): 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 node @ App(f, a) => App(find(f), find(t)) 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 + def add(node: Expr[?]): Expr[?] = + if UF.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 node @ App(f, a) => + add(f) + parents(find(f)).add(node) + add(a) + parents(find(a)).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 addAll(nodes: Iterable[Term|Formula]): Unit = nodes.foreach{ @@ -377,39 +321,35 @@ class EGraphTerms() { - 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(id1: Expr[?], id2: Expr[?]): 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]() + type Sig = Expr[?] | (Int, Int) + val termSigs = mutable.Map[Sig, Expr[?]]() + val codes = mutable.Map[Expr[?], Int]() - def canSig(node: Term): Sig = node match - case AppliedFunctional(label, args) => - (label, args.map(a => codes(find(a))).toList) + def canSig(node: Expr[?]): Sig = node match + case App(f, g) => (codes(find(f)), codes(find(g))) case _ => (node, List()) - protected def mergeWithStep(id1: Term, id2: Term, step: TermStep): Unit = { + protected def mergeWithStep(id1: Term, id2: Term, step: Step): Unit = { if find(id1) == find(id2) then () else - termProofMap((id1, id2)) = step - val parentsT1 = termParentsT(find(id1)) - val parentsF1 = termParentsF(find(id1)) + proofMap((id1, id2)) = step + val parentsT1 = parents(find(id1)) + val parentsF1 = parents(find(id1)) - val parentsT2 = termParentsT(find(id2)) - val parentsF2 = termParentsF(find(id2)) + val parentsT2 = parents(find(id2)) + val parentsF2 = parents(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) + UF.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)]() + var formWorklist = List[(Formula, Formula, ExprStep)]() + var termWorklist = List[(Term, Term, ExprStep)]() parentsT2.foreach { case pTerm: AppliedFunctional => @@ -429,23 +369,23 @@ class EGraphTerms() { else formulaSeen(canonicalPFormula) = pFormula } - termParentsT(newId) = termParentsT(id1) - termParentsT(newId).addAll(termParentsT(id2)) - termParentsF(newId) = formulaSeen.values.to(mutable.Set) + parents(newId) = parents(id1) + parents(newId).addAll(parents(id2)) + parents(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) } } - protected def mergeWithStep(id1: Formula, id2: Formula, step: FormulaStep): Unit = + protected def mergeWithStep(id1: Formula, id2: Formula, step: ExprStep): Unit = if find(id1) == find(id2) then () else - formulaProofMap((id1, id2)) = step - val newparents = formulaParents(find(id1)) ++ formulaParents(find(id2)) - formulaUF.union(id1, id2) + proofMap((id1, id2)) = step + val newparents = parents(find(id1)) ++ parents(find(id2)) + UF.union(id1, id2) val newId = find(id1) val formulaSeen = mutable.Map[Formula, AppliedConnector]() - var formWorklist = List[(Formula, Formula, FormulaStep)]() + var formWorklist = List[(Formula, Formula, ExprStep)]() newparents.foreach { case pFormula: AppliedConnector => @@ -457,7 +397,7 @@ class EGraphTerms() { else formulaSeen(canonicalPFormula) = pFormula } - formulaParents(newId) = formulaSeen.values.to(mutable.Set) + parents(newId) = formulaSeen.values.to(mutable.Set) formWorklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } diff --git a/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala index e8860e96..ec652748 100644 --- a/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala +++ b/lisa-sets/src/main/scala/lisa/automation/CongruenceSimp.scala @@ -18,7 +18,7 @@ import scala.collection.mutable -class EGraphTermsSimp() { +class EGraphExprSimp() { /* val termParents = mutable.Map[Term, mutable.Set[AppliedFunctional]]() diff --git a/lisa-sets/src/test/scala/lisa/automation/CongruenceTest.scala b/lisa-sets/src/test/scala/lisa/automation/CongruenceTest.scala index 34bc77ee..74c6e9ec 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-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index cc3f836d..51df9506 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -5,23 +5,6 @@ import K.given trait Predef extends Syntax { - private val e : Expr[F] = ??? - val eee : Term >>: Term >>: Formula = equality - val (x, y) = eee.unapplySeq[F](e).get - - def printt(t: Term): Unit = println(t) - - - def printv(v: Variable[?]): Unit = println(v.id) - e match { - case exists(x, f) => printv(x); print(f) - case ===[Arrow[T, F]](l) => print(l) - case ===[F](l, r) => print(l); print(r) - case #@[T, Arrow[T, F]](`===`, l) #@ r => printt(l: Term); printt(r) - case eee[F](l: Expr[T], r: Expr[T]) => print(l); print(r) - case (l === r) => print(l); print(r) - } - 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) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index af342c83..2f6b1d26 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -1116,7 +1116,7 @@ object BasicStepTactic { object LeftSubstEq extends ProofTactic { def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) } @@ -1241,7 +1241,7 @@ object BasicStepTactic { */ object LeftSubstIff extends ProofTactic { def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) @@ -1304,14 +1304,14 @@ object BasicStepTactic { */ object RightSubstIff extends ProofTactic { def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[2]) + s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + RightSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) def withParameters(using lib: Library, proof: lib.Proof)( s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + RightSubstEq.withParameters(s, t, vars, lambdaPhi.asInstanceOf)(prem1, prem2)(bot) /* def withParametersSimple(using lib: Library, proof: lib.Proof)( diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala index 2ce8f574..5cb6bebf 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/SimpleDeducedSteps.scala @@ -8,7 +8,7 @@ import lisa.utils.K import lisa.utils.KernelHelpers.{_, given} object SimpleDeducedSteps { -/* + object Restate extends ProofTactic with ProofSequentTactic with ProofFactSequentTactic { def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = @@ -83,7 +83,7 @@ object SimpleDeducedSteps { // by construction the premise is well-formed // verify the formula structure and instantiate f match { - case psi @ K.Forall(K.Lambda(x, inner)) => + 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 = K.substituteVariables(inner, Map(x -> t)) @@ -151,7 +151,7 @@ object SimpleDeducedSteps { } - */ + /* From f0935cf65c0b76d9247228b81a22e62a519b4dab Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Wed, 23 Oct 2024 20:37:50 +0200 Subject: [PATCH 29/92] Continued work on congruence actic. --- .../scala/lisa/automation/Congruence.scala | 131 +++++++----------- .../main/scala/lisa/utils/KernelHelpers.scala | 12 ++ .../main/scala/lisa/utils/fol/Predef.scala | 21 ++- .../main/scala/lisa/utils/fol/Syntax.scala | 23 +-- 4 files changed, 97 insertions(+), 90 deletions(-) diff --git a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala index 4fc12747..e0f7425c 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala @@ -249,31 +249,31 @@ class EGraphExpr() { - def find(id: Expr[?]): Expr[?] = UF.find(id) + def find[T](id: Expr[T]): Expr[T] = UF.find(id).asInstanceOf[Expr[T]] trait Step - case class ExternalStep(between: (Expr[?], Expr[?])) extends ExprStep - case class CongruenceStep(between: (Expr[?], Expr[?])) extends ExprStep + case class ExternalStep(between: (Expr[?], Expr[?])) extends Step + case class CongruenceStep(between: (Expr[?], Expr[?])) extends Step - val proofMap = mutable.Map[(Expr[?], Expr[?]), ExprStep]() + val proofMap = mutable.Map[(Expr[?], Expr[?]), Step]() - def explain(id1: Expr[?], id2: Expr[?]): Option[List[ExprStep]] = { + def explain(id1: Expr[?], id2: Expr[?]): Option[List[Step]] = { val steps = UF.explain(id1, id2) - steps.map(_.foldLeft((id1, List[ExprStep]())) { + steps.map(_.foldLeft((id1, List[Step]())) { case ((prev, acc), step) => proofMap(step) match - case s @ TermExternal((l, r)) => + 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) @@ -292,7 +292,7 @@ class EGraphExpr() { def canonicalize(node: Expr[?]): Expr[?] = node match - case node @ App(f, a) => App(find(f), find(t)) + case node @ App(f, a) => App.unsafe(find(f), find(a)) case _ => node @@ -331,95 +331,70 @@ class EGraphExpr() { def canSig(node: Expr[?]): Sig = node match case App(f, g) => (codes(find(f)), codes(find(g))) - case _ => (node, List()) + case _ => node - protected def mergeWithStep(id1: Term, id2: Term, step: Step): 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 proofMap((id1, id2)) = step - val parentsT1 = parents(find(id1)) - val parentsF1 = parents(find(id1)) - - val parentsT2 = parents(find(id2)) - val parentsF2 = parents(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)) - UF.union(id1, id2) - val newId = find(id1) - - val formulaSeen = mutable.Map[Formula, AppliedPredicate]() - var formWorklist = List[(Formula, Formula, ExprStep)]() - var termWorklist = List[(Term, Term, ExprStep)]() - - 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 - } - parents(newId) = parents(id1) - parents(newId).addAll(parents(id2)) - parents(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) } + 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 termSigs.contains(canonicalPExpr) then + val qExpr = termSigs(canonicalPExpr) + + worklist = (pExpr, qExpr, CongruenceStep((pExpr, qExpr))) :: worklist + else + termSigs(canonicalPExpr) = pExpr + } + parents(newId) = parents(big) + parents(newId).addAll(parents(small)) + worklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } } - protected def mergeWithStep(id1: Formula, id2: Formula, step: ExprStep): Unit = - if find(id1) == find(id2) then () - else - proofMap((id1, id2)) = step - val newparents = parents(find(id1)) ++ parents(find(id2)) - UF.union(id1, id2) - val newId = find(id1) - - val formulaSeen = mutable.Map[Formula, AppliedConnector]() - var formWorklist = List[(Formula, Formula, ExprStep)]() - - 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 - } - parents(newId) = formulaSeen.values.to(mutable.Set) - formWorklist.foreach { case (l, r, step) => mergeWithStep(l, r, step) } - def proveTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): proof.ProofTacticJudgement = - TacticSubproof { proveInnerTerm(id1, id2, base) } + def proveTerm[S](using lib: Library, proof: lib.Proof)(id1: Expr[S], id2:Expr[S], base: Sequent): proof.ProofTacticJudgement = + TacticSubproof { proveInner(id1, id2, base) } - def proveInnerTerm(using lib: Library, proof: lib.Proof)(id1: Term, id2:Term, base: Sequent): Unit = { + def proveInner[S](using lib: Library, proof: lib.Proof)(id1: Expr[S], id2:Expr[S], base: Sequent): Unit = { import lib.* val steps = explain(id1, id2) + val sort = id1.sort + val (eqOp, vars) = sort match + case Functional(argSorts) => + ??? + case Predicate(argSorts) => + ??? + 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 TermExternal((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 = freshVariable(id1) have(goalSequent) by RightSubstEq.withParametersSimple(List((l, r)), lambda(x, id1 === x))(lastStep) - case TermCongruence((l, r)) => + 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 diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 9c496abc..e8d4a6ba 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -114,6 +114,18 @@ object KernelHelpers { /* Infix syntax */ + 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) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index 51df9506..da1cfee9 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -90,12 +90,10 @@ trait Predef extends Syntax { new Variable[T](v.id)(using new Sort { type Self = T; val underlying = v.sort }) def asFrontApplication(a: K.Application): App[?, ?] = - new App[T, T](asFrontExpression(a.f).asInstanceOf, asFrontExpression(a.arg).asInstanceOf)( - using new Sort { type Self = T; val underlying = a.sort }) + new App(asFrontExpression(a.f).asInstanceOf, asFrontExpression(a.arg)) def asFrontLambda(l: K.Lambda): Abs[?, ?] = - new Abs[T, T](asFrontVariable(l.v).asInstanceOf, asFrontExpression(l.body).asInstanceOf)( - using new Sort { type Self = T; val underlying = l.sort }) + new Abs(asFrontVariable(l.v).asInstanceOf, asFrontExpression(l.body)) def greatestId(exprs: Seq[K.Expression | Expr[?] | K.Identifier ]): Int = exprs.view.flatMap({ @@ -123,4 +121,19 @@ trait Predef extends Syntax { } + 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 + + } \ No newline at end of file diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 9c30e37f..65edb2c3 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -77,8 +77,8 @@ trait Syntax { def freeTermVars: Set[Variable[T]] def constants: Set[Constant[?]] } - sealed trait Expr[S: Sort] extends LisaObject { - val sort: K.Sort = summon[IsSort[S]].underlying + sealed trait Expr[S] extends LisaObject { + val sort: K.Sort private val arity = K.flatTypeParameters(sort).size def underlying: K.Expression @@ -167,7 +167,8 @@ trait Syntax { - case class Variable[S : Sort](id: K.Identifier) extends Expr[S] { + 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] = @@ -191,7 +192,8 @@ trait Syntax { } - case class Constant[S : Sort](id: K.Identifier) extends Expr[S] { + 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 setInfix(): Unit = infix = true val underlying: K.Constant = K.Constant(id, sort) @@ -246,7 +248,11 @@ trait Syntax { } - case class App[T1 : Sort, T2 : Sort](f: Expr[Arrow[T1, T2]], arg: Expr[T1]) extends Expr[T2] { + 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] = @@ -266,12 +272,13 @@ trait Syntax { val rsort = K.legalApplication(f.sort, arg.sort) rsort match case Some(to) => - App(f.asInstanceOf, arg.asInstanceOf)(using unsafeSortEvidence(to), unsafeSortEvidence(arg.sort)) + 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 : Sort, T2 : Sort](v: Variable[T1], body: Expr[T2]) extends Expr[Arrow[T1, T2]] { + 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] = @@ -286,7 +293,7 @@ trait Syntax { object Abs { def unsafe(v: Variable[?], body: Expr[?]): Expr[?] = - Abs(v.asInstanceOf, body.asInstanceOf)(using unsafeSortEvidence(v.sort), unsafeSortEvidence(body.sort)) + Abs(v.asInstanceOf, body.asInstanceOf) } From 3bfce57652f38c2c61530bbf387c9c45f8ef595e Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Thu, 24 Oct 2024 00:23:10 +0200 Subject: [PATCH 30/92] Return substitution to first and second order only. --- .../main/scala/lisa/kernel/fol/Syntax.scala | 7 +- .../lisa/kernel/proof/SCProofChecker.scala | 213 ++++++------ .../lisa/kernel/proof/SequentCalculus.scala | 33 +- .../main/scala/lisa/utils/KernelHelpers.scala | 6 +- .../main/scala/lisa/utils/Serialization.scala | 96 ++--- .../lisa/utils/prooflib/BasicStepTactic.scala | 291 +++++----------- .../lisa/utils/prooflib/ProofsHelpers.scala | 3 +- .../lisa/kernel/IncorrectProofsTests.scala | 10 +- .../test/scala/lisa/kernel/ProofTests.scala | 328 ++++++++---------- 9 files changed, 395 insertions(+), 592 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala index e685f80b..98d3cc3b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -36,19 +36,22 @@ private[fol] trait Syntax { sealed trait Sort { def ->(to: Sort): Arrow = Arrow(this, to) val isFunctional: Boolean - def isPredicate: Boolean = !isFunctional + def 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 = to.isFunctional + val isFunctional = from == Term && to.isFunctional + val isPredicate = from == Term && to.isPredicate val depth = 1+to.depth } 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 8370634c..f2d17085 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -428,64 +428,103 @@ 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, t2, s, t, vars, lambdaPhi) => - val (phi_arg, phi_body) = lambdaPhi - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else /*if (!s.sort.isFunctional) - SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - else */{ - val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) - val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - - val inner1 = vars.foldLeft(s)(_(_)) - val inner2 = vars.foldLeft(t)(_(_)) - val sEqt = - if (s.sort.isFunctional) - equality(inner1)(inner2) - else - iff(inner1)(inner2) - val varss = vars.toSet + 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.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 = 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) => + 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 ( - 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, ref(t1).right)) if ( - 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 premises + φ(t).") + isSameSet(b.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || + isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) + ) + 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_) (or with s_ and t_ swapped)." + ) + else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") + } + + /* + * Γ |- φ(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 != 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.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 = 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) => + 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)) } } - else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premises must be the same as the right-hand side of the conclusion + s=t.") + + if (isSameSet(b.left, ref(t1).left ++ sEqT_es)) + if ( + 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 + 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.") } - /* - * Γ, φ(ψ) |- Δ Σ |- a⇔b, Π - * -------------------------------- - * Γ, Σ φ(b) |- Δ, Π + +/* + /* + * Γ |- φ[ψ/?p], Δ + * --------------------- + * Γ, ψ⇔τ |- φ[τ/?p], Δ */ - case LeftSubstIff(b, t1, t2, psi, tau, vars, lambdaPhi) => + 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) + else if (!psi.sort.isPredicate) SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") - else */{ + 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)) @@ -494,53 +533,6 @@ object SCProofChecker { val sEqt = iff(inner1)(inner2) val varss = vars.toSet - 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 ( - 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.") - } - - - /* - * Γ |- φ(s), Δ Σ |- s=t, Π - * --------------------------------- - * Γ, Σ |- φ(t), Δ, Π - */ - case RightSubstEq(b, t1, t2, s, t, vars, lambdaPhi) => - val (phi_arg, phi_body) = lambdaPhi - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - SCInvalidProof(SCProof(step), Nil, "The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - SCInvalidProof(SCProof(step), Nil, "Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - else { - val phi_s_for_f = substituteVariables(phi_body, Map(phi_arg -> s)) - val phi_t_for_f = substituteVariables(phi_body, Map(phi_arg -> t)) - - val inner1 = vars.foldLeft(s)(_(_)) - val inner2 = vars.foldLeft(t)(_(_)) - val sEqt = - if (s.sort.isFunctional) - equality(inner1)(inner2) - else - 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) && @@ -554,25 +546,23 @@ object SCProofChecker { 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 the same as the left-hand sides of the premises.") + 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 premises + φ(t) must be the same as right-hand sides of the premises + φ(s) + s=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, t2, psi, tau, vars, lambdaPhi) => + 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) + else /*if (!psi.sort.isPredicate) SCInvalidProof(SCProof(step), Nil, "Can only substitute predicate-like terms (with type Term -> ... -> Term -> Formula)") - else { + 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)) @@ -582,11 +572,15 @@ object SCProofChecker { val varss = vars.toSet if ( - isSubset(ref(t1).right, b.right + phi_s_for_f) && + isSubset(ref(t1).right, b.right) && isSubset(ref(t2).right, b.right + sEqt) && - isSubset(b.right, ref(t1).right union ref(t2).right + phi_t_for_f) + isSubset(b.right, ref(t1).right union ref(t2).right) ) { - if (isSameSet(b.left, ref(t1).left union ref(t2).left)) { + if ( + 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) @@ -598,8 +592,7 @@ object SCProofChecker { } else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") } - - +*/ /** *
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 326770f4..0d871d31 100644
--- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala
+++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SequentCalculus.scala
@@ -294,25 +294,38 @@ object SequentCalculus {
 
   /**
    * 
-   *  Γ, φ(s) |- Δ     Σ1 |- s=t, Π     
-   * ----------------------------------------
-   *             Γ, Σ φ(t) |- Δ, Π
+   *                     Γ, φ(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, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1, t2) } + case class LeftSubstEq(bot: Sequent, t1: Int, equals: Seq[(Expression, Expression)], lambdaPhi: (Seq[Variable], Expression)) extends SCProofStep { val premises = Seq(t1) } /** *
-   *  Γ |- φ(s), Δ     Σ1 |- s=t, Π
-   * ---------------------------------
-   *         Γ, Σ |- φ(t), Δ, Π
+   *                     Γ |- φ(s), Δ 
+   * ------------------------------------------------------
+   *     Γ, ∀x,...,z. (s x ... z)=(t x ... z) |- φ(t), Δ
    * 
* equals elements must have type ... -> ... -> Term */ - case class RightSubstEq(bot: Sequent, t1: Int, t2: Int, s: Expression, t: Expression, vars: Seq[Variable], lambdaPhi: (Variable, Expression)) extends SCProofStep { val premises = Seq(t1, t2) } + 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) + } + } + + /* /** *
    *   Γ, φ(ψ) |- Δ     Σ |- ψ⇔τ, Π     
@@ -332,7 +345,7 @@ object SequentCalculus {
    * equals elements must have type ... -> ... -> Formula
    */
   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(
diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
index e8d4a6ba..39d6993f 100644
--- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
@@ -635,10 +635,8 @@ object KernelHelpers {
               case Beta(_, t1) => pretty("Beta", t1)
               case LeftRefl(_, t1, _) => pretty("L. Refl", t1)
               case RightRefl(_, _) => pretty("R. Refl")
-              case LeftSubstEq(_, t1, t2, _, _, _, _) => pretty("L. SubstEq", t1, t2)
-              case RightSubstEq(_, t1, t2, _, _, _, _) => pretty("R. SubstEq", t1, t2)
-              case LeftSubstIff(_, t1, t2, _, _, _, _) => pretty("L. SubstIff", t1, t2)
-              case RightSubstIff(_, t1, t2, _, _, _, _) => pretty("R. SubstIff", t1, t2)
+              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")
diff --git a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala
index 555d5c6a..6cfca547 100644
--- a/lisa-utils/src/main/scala/lisa/utils/Serialization.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/Serialization.scala
@@ -35,11 +35,9 @@ object Serialization {
   inline def rightRefl: Byte = 23
   inline def leftSubstEq: Byte = 24
   inline def rightSubstEq: Byte = 25
-  inline def leftSubstIff: Byte = 26
-  inline def rightSubstIff: Byte = 27
-  inline def instSchema: Byte = 28
-  inline def scSubproof: Byte = 29
-  inline def sorry: Byte = 30
+  inline def instSchema: Byte = 26
+  inline def scSubproof: Byte = 27
+  inline def sorry: Byte = 28
 
   type Line = Int
 
@@ -281,49 +279,29 @@ object Serialization {
           proofDOS.writeByte(rightRefl)
           sequentToProofDOS(bot)
           proofDOS.writeInt(lineOfExpr(fa))
-        case LeftSubstEq(bot, t1, t2, s, t, vars, lambdaPhi) =>
+        case LeftSubstEq(bot, t1, equals, lambdaPhi) =>
           proofDOS.writeByte(leftSubstEq)
           sequentToProofDOS(bot)
           proofDOS.writeInt(t1)
-          proofDOS.writeInt(t2)
-          proofDOS.writeInt(lineOfExpr(s))
-          proofDOS.writeInt(lineOfExpr(t))
-          proofDOS.writeShort(vars.size)
-          vars.foreach(v => proofDOS.writeInt(lineOfExpr(v)))
-          proofDOS.writeInt(lineOfExpr(lambdaPhi._1))
-          proofDOS.writeInt(lineOfExpr(lambdaPhi._2))
-        case RightSubstEq(bot, t1, t2, s, t, vars, lambdaPhi) =>
-          proofDOS.writeByte(rightSubstEq)
-          sequentToProofDOS(bot)
-          proofDOS.writeInt(t1)
-          proofDOS.writeInt(t2)
-          proofDOS.writeInt(lineOfExpr(s))
-          proofDOS.writeInt(lineOfExpr(t))
-          proofDOS.writeShort(vars.size)
-          vars.foreach(v => proofDOS.writeInt(lineOfExpr(v)))
-          proofDOS.writeInt(lineOfExpr(lambdaPhi._1))
-          proofDOS.writeInt(lineOfExpr(lambdaPhi._2))
-        case LeftSubstIff(bot, t1, t2, s, t, vars, lambdaPhi) =>
-          proofDOS.writeByte(leftSubstIff)
-          sequentToProofDOS(bot)
-          proofDOS.writeInt(t1)
-          proofDOS.writeInt(t2)
-          proofDOS.writeInt(lineOfExpr(s))
-          proofDOS.writeInt(lineOfExpr(t))
-          proofDOS.writeShort(vars.size)
-          vars.foreach(v => proofDOS.writeInt(lineOfExpr(v)))
-          proofDOS.writeInt(lineOfExpr(lambdaPhi._1))
+          proofDOS.writeShort(equals.size)
+          equals.foreach(ltts =>
+            proofDOS.writeInt(lineOfExpr(ltts._1))
+            proofDOS.writeInt(lineOfExpr(ltts._2))
+          )
+          proofDOS.writeShort(lambdaPhi._1.size)
+          lambdaPhi._1.foreach(stl => proofDOS.writeInt(lineOfExpr(stl)))
           proofDOS.writeInt(lineOfExpr(lambdaPhi._2))
-        case RightSubstIff(bot, t1, t2, s, t, vars, lambdaPhi) =>
-          proofDOS.writeByte(rightSubstIff)
+        case RightSubstEq(bot, t1, equals, lambdaPhi) =>
+          proofDOS.writeByte(leftSubstEq)
           sequentToProofDOS(bot)
           proofDOS.writeInt(t1)
-          proofDOS.writeInt(t2)
-          proofDOS.writeInt(lineOfExpr(s))
-          proofDOS.writeInt(lineOfExpr(t))
-          proofDOS.writeShort(vars.size)
-          vars.foreach(v => proofDOS.writeInt(lineOfExpr(v)))
-          proofDOS.writeInt(lineOfExpr(lambdaPhi._1))
+          proofDOS.writeShort(equals.size)
+          equals.foreach(ltts =>
+            proofDOS.writeInt(lineOfExpr(ltts._1))
+            proofDOS.writeInt(lineOfExpr(ltts._2))
+          )
+          proofDOS.writeShort(lambdaPhi._1.size)
+          lambdaPhi._1.foreach(stl => proofDOS.writeInt(lineOfExpr(stl)))
           proofDOS.writeInt(lineOfExpr(lambdaPhi._2))
         case InstSchema(bot, t1, m) =>
           proofDOS.writeByte(instSchema)
@@ -474,41 +452,15 @@ object Serialization {
         LeftSubstEq(
           sequentFromProofDIS(),
           proofDIS.readInt(),
-          proofDIS.readInt(),
-          exprMap(proofDIS.readInt()),
-          exprMap(proofDIS.readInt()),
-          (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList,
-          (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(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(),
-          proofDIS.readInt(),
-          exprMap(proofDIS.readInt()),
-          exprMap(proofDIS.readInt()),
-          (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList,
-          (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(proofDIS.readInt()))
-        )
-      else if (psType == leftSubstIff)
-        LeftSubstIff(
-          sequentFromProofDIS(),
-          proofDIS.readInt(),
-          proofDIS.readInt(),
-          exprMap(proofDIS.readInt()),
-          exprMap(proofDIS.readInt()),
-          (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList,
-          (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(proofDIS.readInt()))
-        )
-      else if (psType == rightSubstIff)
-        RightSubstIff(
-          sequentFromProofDIS(),
-          proofDIS.readInt(),
-          proofDIS.readInt(),
-          exprMap(proofDIS.readInt()),
-          exprMap(proofDIS.readInt()),
-          (1 to proofDIS.readShort()).map(_ => exprMap(proofDIS.readInt()).asInstanceOf[Variable]).toList,
-          (exprMap(proofDIS.readInt()).asInstanceOf[Variable], exprMap(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(
diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
index 2f6b1d26..65885e45 100644
--- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
@@ -1114,62 +1114,56 @@ object BasicStepTactic {
    * 
*/ object LeftSubstEq extends ProofTactic { - - def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) + @deprecated("Use withParameters instead", "0.9") + def withParametersSimple(using lib: Library, proof: lib.Proof)( + equals: List[(F.Term, F.Term)], + lambdaPhi: (Seq[F.Variable[?]], F.Formula) + )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { + withParameters(equals, lambdaPhi)(premise)(bot) } def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent1 = proof.getSequent(prem1).underlying - lazy val premiseSequent2 = proof.getSequent(prem2).underlying + equals: List[(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 sK = s.underlying - lazy val tK = t.underlying - lazy val varsK = vars.map(_.underlying) - val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) - val (phi_arg, phi_body) = lambdaPhiK - - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) - val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) - - val inner1 = varsK.foldLeft(sK)(_(_)) - val inner2 = varsK.foldLeft(tK)(_(_)) - val sEqt = K.equality(inner1)(inner2) - val varss = varsK.toSet + lazy val equalsK = equals.map(p => (p._1.underlying, p._2.underlying)) + lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlying), lambdaPhi._2.underlying) - if ( - K.isSubset(premiseSequent1.right, botK.right) && - K.isSubset(premiseSequent2.right, botK.right + sEqt) && - K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) - ) { - if ( - K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && - K.isSubset(premiseSequent2.left, botK.left) && - K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) - ) { - if ( - premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || - premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) - ) { - return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") - } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) + val (s_es, t_es) = equalsK.unzip + val (phi_args, phi_body) = lambdaPhiK + 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) } } - else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") - } - else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") - + if (K.isSameSet(botK.right, premiseSequent.right)) + 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.") + } } } + /** *
    *  Γ |- φ(s), Δ     Σ |- s=t, Π
@@ -1178,186 +1172,59 @@ object BasicStepTactic {
    * 
*/ object RightSubstEq extends ProofTactic { - def withParametersSimple[T1](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - } - - def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Formula) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = { - lazy val premiseSequent1 = proof.getSequent(prem1).underlying - lazy val premiseSequent2 = proof.getSequent(prem2).underlying - lazy val botK = bot.underlying - lazy val sK = s.underlying - lazy val tK = t.underlying - lazy val varsK = vars.map(_.underlying) - val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) - val (phi_arg, phi_body) = lambdaPhiK - - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) - val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) - - val inner1 = varsK.foldLeft(sK)(_(_)) - val inner2 = varsK.foldLeft(tK)(_(_)) - val sEqt = K.equality(inner1)(inner2) - val varss = varsK.toSet - - if ( - K.isSubset(premiseSequent1.right, botK.right) && - K.isSubset(premiseSequent2.right, botK.right + sEqt) && - K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) - ) { - if ( - K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && - K.isSubset(premiseSequent2.left, botK.left) && - K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) - ) { - if ( - premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || - premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) - ) { - proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") - } else proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) - } - else proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") - } - else proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") - } - - } - - /** - *
-   *           Γ, φ(a1,...an) |- Δ
-   * ----------------------------------------
-   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
-   * 
- */ - object LeftSubstIff extends ProofTactic { - def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - - def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - LeftSubstEq.withParameters(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - /*{ - lazy val premiseSequent1 = proof.getSequent(prem1).underlying - lazy val premiseSequent2 = proof.getSequent(prem2).underlying - lazy val botK = bot.underlying - lazy val sK = s.underlying - lazy val tK = t.underlying - lazy val varsK = vars.map(_.underlying) - val lambdaPhiK = (lambdaPhi._1.underlying, lambdaPhi._2.underlying) - val (phi_arg, phi_body) = lambdaPhiK - - if (s.sort != phi_arg.sort || t.sort != phi_arg.sort) - return proof.InvalidProofTactic("The types of the variable of φ must be the same as the types of s and t.") - else if (!s.sort.isFunctional) - return proof.InvalidProofTactic("Can only substitute function-like terms (with type _ -> ... -> _ -> Term)") - val phi_s_for_f = K.substituteVariables(phi_body, Map(phi_arg -> sK)) - val phi_t_for_f = K.substituteVariables(phi_body, Map(phi_arg -> tK)) - - val inner1 = varsK.foldLeft(sK)(_(_)) - val inner2 = varsK.foldLeft(tK)(_(_)) - val sEqt = K.Iff(inner1)(inner2) - val varss = varsK.toSet - - if ( - K.isSubset(premiseSequent1.right, botK.right) && - K.isSubset(premiseSequent2.right, botK.right + sEqt) && - K.isSubset(botK.right, premiseSequent1.right union premiseSequent2.right) - ) { - if ( - K.isSubset(premiseSequent1.left, botK.left + phi_s_for_f) && - K.isSubset(premiseSequent2.left, botK.left) && - K.isSubset(botK.left, premiseSequent1.left union premiseSequent2.left + phi_t_for_f) - ) { - if ( - premiseSequent2.left.exists(f => f.freeVariables.intersect(varss).nonEmpty) || - premiseSequent2.right.exists(f => !K.isSame(f, sEqt) && f.freeVariables.intersect(varss).nonEmpty) - ) { - return proof.InvalidProofTactic("The variable x1...xn must not be free in the second premise other than as parameters of the equality.") - } else proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, -2, sK, tK, varsK, lambdaPhiK)), Seq(prem1, prem2)) - } - else return proof.InvalidProofTactic("Left-hand sides of the conclusion + φ(s_) must be the same as left-hand side of the premise + (s=t)_ + φ(t_).") - } - else return proof.InvalidProofTactic("Right-hand sides of the premise and the conclusion aren't the same.") - */ - - } - - /** - *
-   *           Γ |- φ(a1,...an), Δ
-   * ----------------------------------------
-   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
-   * 
- */ - object RightSubstIff extends ProofTactic { - def withParametersSimple[T1, T2](using lib: Library, proof: lib.Proof)( - s: F.Expr[T1], t: F.Expr[T1], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[T1], F.Expr[F.F]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - RightSubstEq.withParametersSimple(s, t, vars, lambdaPhi)(prem1, prem2)(bot) - - def withParameters(using lib: Library, proof: lib.Proof)( - s: F.Expr[?], t: F.Expr[?], vars: Seq[F.Variable[?]], lambdaPhi: (F.Variable[?], F.Expr[?]) - )(prem1: proof.Fact, prem2: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = - RightSubstEq.withParameters(s, t, vars, lambdaPhi.asInstanceOf)(prem1, prem2)(bot) - - /* + @deprecated("Use withParameters instead", "0.9") def withParametersSimple(using lib: Library, proof: lib.Proof)( - equals: List[(F.Formula, F.Formula)], - lambdaPhi: F.LambdaExpression[F.Formula, F.Formula, ?] + equals: List[(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.SchematicAtomicLabel[?]]], lambdaPhi.body))(premise)(bot) + withParameters(equals, lambdaPhi)(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) + equals: List[(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.underlyingLTF, p._2.underlyingLTF)) + lazy val equalsK = equals.map(p => (p._1.underlying, p._2.underlying)) lazy val lambdaPhiK = (lambdaPhi._1.map(_.underlying), lambdaPhi._2.underlying) - val (psi_s, tau_s) = equalsK.unzip + val (s_es, t_es) = 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.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.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)) + if (K.isSameSet(botK.right, premiseSequent.right ++ sEqT_es)) + if ( + K.isSameSet(botK.left + phi_t_for_f, premiseSequent.left + phi_s_for_f) || + K.isSameSet(botK.left + phi_s_for_f, premiseSequent.left + phi_t_for_f) + ) + proof.ValidProofTactic(bot, Seq(K.LeftSubstEq(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) + else + proof.InvalidProofTactic("ight-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.") + } } - */ } + + @deprecated("Use LeftSubstEq instead", "0.9") + val LeftSubstIff = LeftSubstEq + @deprecated("Use RightSubstEq instead", "0.9") + val RightSubstIff = RightSubstEq /** *
diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala
index 38d4f74e..f291c164 100644
--- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala
@@ -306,7 +306,8 @@ trait ProofsHelpers {
       val eqStep = lastStep // appliedCst === body
       // j is exists(x, prop(x))
       val existsStep = ??? // have(propEpsilon) by // prop(body)
-      val s3 = have(propCst) by BasicStepTactic.RightSubstEq.withParametersSimple[T](appliedCst, body, Seq(), (epsilonVar, inner))(j, lastStep)
+      val s3 = have(appliedCst === body |- propCst) by RightSubstEq.withParameters(List((appliedCst, body)), (List(epsilonVar), inner))(lastStep)
+      val s4 = have(propCst) by Cut(s3, j)
       ???
     }
 
diff --git a/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala b/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala
index 8eae73ea..bbdf8959 100644
--- a/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala
+++ b/lisa-utils/src/test/scala/lisa/kernel/IncorrectProofsTests.scala
@@ -37,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, 0, x, z, Seq(), (y, 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, 0, x, z, Seq(), (x, 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, 0, x, z, Seq(), (x, 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, 0, x, z, Seq(), (y, 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, 0, f, h, Seq(), (g, 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 c80735e1..0a9b5d7f 100644
--- a/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala
+++ b/lisa-utils/src/test/scala/lisa/kernel/ProofTests.scala
@@ -47,76 +47,125 @@ class ProofTests extends AnyFunSuite {
     {
       val t0 = Hypothesis(fp(x) |- fp(x), fp(x))
       val t1 = Hypothesis(x === y |- x === y, x === y)
-      val t2 = LeftSubstEq(Set(fp(y), x === y) |- fp(x), 0, 1, x, y, Seq(), (sT, fp(sT)))
+      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, judg.repr)
+      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 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(x, g(x, x))(y))
-      val t2 = LeftSubstEq(
-        Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))),
+      val t1 = LeftSubstEq(
+        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,
-        1,
-        f, lambda(x, g(x, x)),
-        Seq(y),
-        (f2, exists(x, fp(f2(x))))
+        Seq((f, lambda(x, g(x, x)))),
+        (Seq(f2), exists(x, fp(f2(x))))
       )
-      val t3 = Beta(
+      val t2 = Beta(
         Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))),
-        2
+        1
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(x, g(x, x))(y))
-      val t2 = LeftSubstEq(
-        Set(exists(x, fp(lambda(x, g(x, x))(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))),
+      val t1 = LeftSubstEq(
+        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,
-        1,
-        f, lambda(z, g(z, z)),
-        Seq(y),
-        (f2, exists(x, fp(f2(x))))
+        Seq((f, lambda(z, g(z, z)))),
+        (Seq(f2), exists(x, fp(f2(x))))
       )
-      val t3 = Beta(
+      val t2 = Beta(
         Set(exists(x, fp(g(x, x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(f(x))),
-        2
+        1
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y)(z))
-      val t2 = LeftSubstEq(
-        Set(exists(x, forall(y, fp(lambda(Seq(y, z), g(z, y))(y, g(x, z))))), forall(y, forall(z, g(y, z) === g(z, y)))) |- exists(x, forall(y, fp(g(y, g(x, z))))),
+      val t1 = LeftSubstEq(
+        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,
-        1,
-        g, lambda(Seq(y, z), g(z, y)),
-        Seq(y, z),
-        (g2, exists(x, forall(y, fp(g2(y, g(x, z))))))
+        Seq((g, lambda(Seq(y, z), g(z, y)))),
+        (Seq(g2), exists(x, forall(y, fp(g2(y, g(x, z))))))
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y, z))
-      val t2 = LeftSubstEq(
+      val t1 = LeftSubstEq(
         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) === g(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, z))))),
         0,
-        1,
-        g, lambda(Seq(y, z), g(z, y)),
-        Seq(y, z),
-        (g2, exists(x, forall(y, fp(g2(y, g2(x, z))))))
+        Seq((g, lambda(Seq(y, z), g(z, y)))),
+        (Seq(g2), exists(x, forall(y, fp(g2(y, g2(x, z))))))
+      )
+      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, judg.repr)
+      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(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((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)))))))
+      )
+      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)
     }
   }
 
@@ -124,198 +173,125 @@ class ProofTests extends AnyFunSuite {
     {
       val t0 = Hypothesis(fp(x) |- fp(x), fp(x))
       val t1 = Hypothesis(x === y |- x === y, x === y)
-      val t2 = RightSubstEq(Set(fp(x), x === y) |- fp(y), 0, 1, x, y, Seq(), (sT, fp(sT)))
+      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 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(x, g(x, x))(y))
-      val t2 = RightSubstEq(
-        Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(lambda(x, g(x, x))(x))),
+      val t1 = RightSubstEq(
+        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,
-        1,
-        f, lambda(x, g(x, x)),
-        Seq(y),
-        (f2, exists(x, fp(f2(x))))
+        Seq((f, lambda(x, g(x, x)))),
+        (Seq(f2), exists(x, fp(f2(x))))
       )
-      val t3 = Beta(
+      val t2 = Beta(
         Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))),
-        2
+        1
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, f(y) === g(y, y)) |- f(y) === lambda(z, g(z, z))(y))
-      val t2 = RightSubstEq(
-        Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(lambda(z, g(z, z))(x))),
+      val t1 = RightSubstEq(
+        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,
-        1,
-        f, lambda(z, g(z, z)),
-        Seq(y),
-        (f2, exists(x, fp(f2(x))))
+        Seq((f, lambda(z, g(z, z)))),
+        (Seq(f2), exists(x, fp(f2(x))))
       )
-      val t3 = Beta(
+      val t2 = Beta(
         Set(exists(x, fp(f(x))), forall(y, f(y) === g(y, y))) |- exists(x, fp(g(x, x))),
-        2
+        1
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y, z))
-      val t2 = RightSubstEq(
+      val t1 = RightSubstEq(
         Set(
           exists(x, forall(y, fp(g(y, g(x, z))))), 
-          forall(y, forall(z, g(y, z) === g(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, g(x, z))))),
         0,
-        1,
-        g, lambda(Seq(y, z), g(z, y)),
-        Seq(y, z),
-        (g2, exists(x, forall(y, fp(g2(y, g(x, z))))))
+        Seq((g, lambda(Seq(y, z), g(z, y)))),
+        (Seq(g2), exists(x, forall(y, fp(g2(y, g(x, z))))))
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, forall(z, g(y, z) === g(z, y))) |- g(y, z) === lambda(Seq(y, z), g(z, y))(y, z))
-      val t2 = RightSubstEq(
+      val t1 = RightSubstEq(
         Set(
           exists(x, forall(y, fp(g(y, g(x, z))))), 
-          forall(y, forall(z, g(y, z) === g(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, z))))),
         0,
-        1,
-        g, lambda(Seq(y, z), g(z, y)),
-        Seq(y, z),
-        (g2, exists(x, forall(y, fp(g2(y, g2(x, z))))))
+        Seq((g, lambda(Seq(y, z), g(z, y)))),
+        (Seq(g2), exists(x, forall(y, fp(g2(y, g2(x, z))))))
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2)))
-      assert(judg.isValid, judg.repr)
+      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1)))
+      assert(judg.isValid, "\n" + judg.repr)
     }
-  }
-
-
-  test ("Verification of LeftSubstIff") {
+    //
     {
       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, 1, P(x), P(y), Seq(), (X, X))
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2)))
-      assert(judg.isValid, judg.repr)
-    }
-    {
-      val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x)))
-      val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(x, Q(x, x))(y))
-      val t2 = LeftSubstIff(
-        Set(exists(x, lambda(x, Q(x, x))(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)),
-        0,
-        1,
-        P, lambda(x, Q(x, x)),
-        Seq(y),
-        (P2, exists(x, P2(x)))
-      )
-      val t3 = Beta(
-        Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)),
-        2
-      )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(z, Q(z, z))(y))
-      val t2 = LeftSubstIff(
-        Set(exists(x, lambda(z, Q(z, z))(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,
-        1,
-        P, lambda(z, Q(z, z)),
-        Seq(y),
-        (P2, exists(x, P2(x)))
-      )
-      val t3 = Beta(
-        Set(exists(x, Q(x, x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, P(x)),
-        2
+        Seq((P, lambda(x, Q(x, x)))),
+        (Seq(P2), exists(x, P2(x)))
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3)))
-      assert(judg.isValid, 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 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> lambda(Seq(y, z), Q(z, y))(y, z))
-      val t2 = LeftSubstIff(
-        Set(exists(x, forall(y, lambda(Seq(y, z), Q(z, y))(y, g(x, z)))), forall(x, forall(y, Q(x, y) <=> Q(y, x)))) |- exists(x, forall(y, Q(y, g(x, z)))),
-        0,
-        1,
-        Q, lambda(Seq(y, z), Q(z, y)),
-        Seq(y, z),
-        (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
       )
       val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2)))
-      assert(judg.isValid, judg.repr)
-    }
-  }
-
-  test("Verification of RightSubstIff") {
-    {
-      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 = RightSubstIff(Set(P(x), P(x) <=> P(y)) |- P(y), 0, 1, P(x), P(y), Seq(), (X, X))
-      assert(checkSCProof(SCProof(IndexedSeq(t0, t1, t2))).isValid)
+      assert(judg.isValid, "\n" + judg.repr)
     }
     {
       val t0 = Hypothesis(exists(x, P(x)) |- exists(x, P(x)), exists(x, P(x)))
-      val t1 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(x, Q(x, x))(y))
-      val t2 = RightSubstIff(
-        Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, lambda(x, Q(x, x))(x)),
+      val t1 = RightSubstIff(
+        Set(exists(x, P(x)), forall(y, P(y) <=> lambda(x, Q(x, x))(y))) |- exists(x, lambda(z, Q(z, z))(x)),
         0,
-        1,
-        P, lambda(x, Q(x, x)),
-        Seq(y),
-        (P2, exists(x, P2(x)))
+        Seq((P, lambda(z, Q(z, z)))),
+        (Seq(P2), exists(x, P2(x)))
       )
-      val t3 = Beta(
+      val t2 = Beta(
         Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)),
-        2
+        1
       )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2, t3)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, P(y) <=> Q(y, y)) |- P(y) <=> lambda(x, Q(x, x))(y))
-      val t2 = RightSubstIff(
-        Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, lambda(z, Q(z, z))(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, 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,
-        1,
-        P, lambda(z, Q(z, z)),
-        Seq(y),
-        (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)))))
       )
-      val t3 = Beta(
-        Set(exists(x, P(x)), forall(y, P(y) <=> Q(y, y))) |- exists(x, Q(x, x)),
-        2
-      )
-      val judg = checkSCProof(SCProof(IndexedSeq(t0, t1, t2)))
-      assert(judg.isValid, judg.repr)
+      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 = Sorry(forall(y, forall(z, Q(y, z) <=> Q(z, y))) |- Q(y, z) <=> lambda(Seq(y, z), Q(z, y))(y, z))
-      val t2 = 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, lambda(Seq(y, z), Q(z, y))(y, g(x, z)))),
+      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,
-        1,
-        Q, lambda(Seq(y, z), Q(z, y)),
-        Seq(y, z),
-        (Q2, exists(x, forall(y, Q2(y, g(x, 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)))))))
       )
+      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, judg.repr)
+      assert(judg.isValid, "\n" + judg.repr)
     }
   }
 

From 516ef5b5f60969e721c38fa4bc385a2cde6394ef Mon Sep 17 00:00:00 2001
From: SimonGuilloud 
Date: Thu, 24 Oct 2024 03:02:46 +0200
Subject: [PATCH 31/92] general improvements, update to congruence.

---
 .../main/scala/lisa/kernel/fol/Syntax.scala   |   2 +-
 .../lisa/kernel/proof/SCProofChecker.scala    |   4 +-
 .../scala/lisa/automation/Congruence.scala    | 287 +++++++++---------
 .../scala/lisa/automation/Tautology.scala     |  14 +-
 .../main/scala/lisa/utils/fol/Predef.scala    |  13 +-
 .../main/scala/lisa/utils/fol/Syntax.scala    |  19 +-
 6 files changed, 172 insertions(+), 167 deletions(-)

diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala
index 98d3cc3b..63f8b2c5 100644
--- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala
+++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala
@@ -36,7 +36,7 @@ private[fol] trait Syntax {
   sealed trait Sort {
     def ->(to: Sort): Arrow = Arrow(this, to)
     val isFunctional: Boolean
-    def isPredicate: Boolean
+    val isPredicate: Boolean
     val depth: Int 
   }
   case object Term extends Sort {
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 f2d17085..0b0d23f5 100644
--- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala
+++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala
@@ -441,14 +441,14 @@ object SCProofChecker {
             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.sort != arg.sort || t.sort != arg.sort })
+            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 = 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) => 
-                  val no = ((s.freeVariables ++ t.freeVariables).view.map(_.id.no) ++ Seq(0)).max+1
+                  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)(_(_))
diff --git a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala
index e0f7425c..c75a78c8 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 leo.datastructures.TPTP.CNF.AtomicFormula
-import scala.annotation.targetName
+import lisa.utils.K
+import leo.datastructures.TPTP.AnnotatedFormula.FormulaType
 
 /**
   * This tactic tries to prove a sequent by congruence.
@@ -27,82 +27,82 @@ import scala.annotation.targetName
   * 
   */
 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 EGraphExpr()
+    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.proveFormula(lf, rf, bot))
+          val a = variable[Formula]
+          val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
+          have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParameters(Seq((lf, rf)), (Seq(a), a))(base, eqstep)
+          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 = variable[Formula]
+          val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
+          have((bot.left + (lf <=> rf)) |- (bot.right) ) by LeftSubstIff.withParameters(Seq((lf, rf)), (Seq(a), !a))(base, eqstep)
+          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
       }
 
-      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 = variable[Formula]
-            val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
-            have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParametersSimple(lf, rf, Seq(), (a, a))(base, eqstep)
-            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 = variable[Formula]
-            val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
-            have((bot.left + (lf <=> rf)) |- (bot.right) ) by LeftSubstIff.withParametersSimple(lf, rf, Seq(), (a, !a))(base, eqstep)
-            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.proveFormula(lf, rf, bot))
+          val a = variable[Formula]
+          val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
+          have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParameters(Seq((lf, rf)), (Seq(a), !a))(base, eqstep)
+          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")
+  }
 
-      } 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 = variable[Formula]
-            val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
-            have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParametersSimple(lf, rf, Seq(), (a, !a))(base, eqstep)
-            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")
-    }
 
-  
 }
 
 
@@ -117,8 +117,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))
@@ -126,10 +126,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
@@ -145,8 +145,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)
@@ -188,9 +188,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())
@@ -223,27 +223,27 @@ 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 EGraphExpr() {
 
-  val parents = mutable.Map[Expr[?], mutable.Set[App[?, ?]]]()
+  val parents = mutable.Map[Expr[?], mutable.Set[Expr[?]]]()
   val UF = new UnionFind[Expr[?]]()
 
 
@@ -286,52 +286,54 @@ class EGraphExpr() {
     node
   }
 
-  def idEqT(id1: Term, id2: Term): Boolean = find(id1) == find(id2)
-  def idEqF(id1: Formula, id2: Formula): Boolean = find(id1) == find(id2)
+  def idEq(id1: Expr[?], id2: Expr[?]): Boolean = find(id1) == find(id2)
 
   
-
-  def canonicalize(node: Expr[?]): Expr[?] = node match
-    case node @ App(f, a) => App.unsafe(find(f), find(a))
+  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 UF.parent.contains(node) then return node
-    makeSingletonEClass(node)
-    codes(node) = codes.size
-    node match
-      case node @ App(f, a) =>
-        add(f)
-        parents(find(f)).add(node)
-        add(a)
-        parents(find(a)).add(node)
-      case _ => ()
-    termSigs(canSig(node)) = node
-    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: Expr[?], id2: Expr[?]): Unit = {
+  def merge[S](id1: Expr[S], id2: Expr[S]): Unit = {
     mergeWithStep(id1, id2, ExternalStep((id1, id2)))
   }
 
-  type Sig = Expr[?] | (Int, Int)
-  val termSigs = mutable.Map[Sig, Expr[?]]()
+  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 App(f, g) => (codes(find(f)), codes(find(g)))
-    case _ => node
+    case Multiapp(label, args) => 
+      (label, args.map(a => codes(find(a))).toList)
+    case _ => (node, List())
 
   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")
@@ -354,12 +356,12 @@ class EGraphExpr() {
 
     parents(small).foreach { pExpr =>
       val canonicalPExpr = canSig(pExpr) 
-      if termSigs.contains(canonicalPExpr) then 
-        val qExpr = termSigs(canonicalPExpr)
+      if mapSigs.contains(canonicalPExpr) then 
+        val qExpr = mapSigs(canonicalPExpr)
 
         worklist = (pExpr, qExpr, CongruenceStep((pExpr, qExpr))) :: worklist
       else
-        termSigs(canonicalPExpr) = pExpr
+        mapSigs(canonicalPExpr) = pExpr
     }
     parents(newId) = parents(big)
     parents(newId).addAll(parents(small))
@@ -367,36 +369,29 @@ class EGraphExpr() {
   }
 
 
+  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 proveTerm[S](using lib: Library, proof: lib.Proof)(id1: Expr[S], id2:Expr[S], base: Sequent): proof.ProofTacticJudgement = 
-    TacticSubproof { proveInner(id1, id2, base) }
 
-  def proveInner[S](using lib: Library, proof: lib.Proof)(id1: Expr[S], id2:Expr[S], 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)
-    val sort = id1.sort
-    val (eqOp, vars) = sort match
-      case Functional(argSorts) =>
-        ???
-      case Predicate(argSorts) =>
-        ???
-    
     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 ExternalStep((l, r)) => 
-            val goalSequent = base.left |- (base.right + (id1 === 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)
+              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
@@ -419,7 +414,7 @@ class EGraphExpr() {
                   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, (vars, l === Multiapply(labelr, children)))(lastStep)
                   steps.foreach { s =>
                     have(
                       if s._2.bot.left.contains(s._1) then lastStep.bot else lastStep.bot -<< s._1
@@ -434,7 +429,7 @@ class EGraphExpr() {
             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)
+              have(goalSequent +<< (l === r)) by RightSubstEq.withParameters(List((l, r)), lambda(x, id1 === x))(prev)
               have(goalSequent) by Cut(leqr, lastStep)
         }
     }
@@ -451,14 +446,14 @@ class EGraphExpr() {
       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
@@ -483,7 +478,7 @@ class EGraphExpr() {
                   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
@@ -511,7 +506,7 @@ class EGraphExpr() {
                   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
@@ -526,7 +521,7 @@ class EGraphExpr() {
             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)
         
         }
diff --git a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala
index e4953ad7..74f96ef1 100644
--- a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala
+++ b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala
@@ -140,26 +140,20 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque
 
       val seq1 = AugSequent((atom :: s.decisions._1, s.decisions._2), substituteVariables(lambdaF, Map(MaRvIn -> top)))
       val proof1 = solveAugSequent(seq1, offset)
-      val hyp1 = RestateTrue(atom |- atom)
       val subst1 = RightSubstIff(
         atom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF,
-        offset + proof1.length - 2,
         offset + proof1.length - 1,
-        atom, top,
-        Seq(),
-        (MaRvIn, lambdaF)
+        Seq((atom, top)),
+        (Seq(MaRvIn), lambdaF)
       )
       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 hyp2 = RestateTrue(negatom |- negatom)
       val subst2 = RightSubstIff(
         negatom :: s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- redF,
-        offset + proof1.length + proof2.length + 1 - 2,
         offset + proof1.length + proof2.length + 1 - 1,
-        atom, bot,
-        Seq(),
-        (MaRvIn, lambdaF)
+        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)
diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
index da1cfee9..2b832777 100644
--- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
@@ -102,7 +102,7 @@ trait Predef extends Syntax {
       case id: K.Identifier => Seq(id)
     }).map(_.no).max
 
-  def freshId(exprs: Seq[K.Expression | Expr[?] | K.Identifier ], base: String): K.Identifier = {
+  def freshId(exprs: Seq[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)
@@ -111,7 +111,7 @@ trait Predef extends Syntax {
     K.Identifier(base, i + 1)
   }
 
-  def nFreshIds(n: Int, exprs: Seq[K.Expression | Expr[?] | K.Identifier ], base: String): Seq[K.Identifier] = {
+  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)
@@ -136,4 +136,13 @@ trait Predef extends Syntax {
       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/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
index 65edb2c3..4ec67642 100644
--- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
@@ -142,7 +142,9 @@ trait Syntax {
     def apply(using IsSort[T1], IsSort[T2])(arg: Expr[T1]): Expr[T2] = App(f, arg)
 
 
-  type RetExpr[T] = Expr[?]
+  type RetExpr[T] <: Expr[?] = T match {
+    case Arrow[a, b] => Expr[b]
+  }
 
   class Multiapp(f: Expr[?]):
     def unapply (e: Expr[?]): Option[Seq[Expr[?]]] = 
@@ -155,14 +157,19 @@ trait Syntax {
   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[?]]) = e match
-    case App(f, arg) =>
-      val (f1, args) = unfoldAllApp(f)
-      (f1, arg :: args )
-    case _ => (e, Nil)
+
+  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)
 
 
 

From ea67c79928b5582bbf9547fd489600a1512ddb9d Mon Sep 17 00:00:00 2001
From: Sankalp Gambhir 
Date: Thu, 24 Oct 2024 08:38:22 +0200
Subject: [PATCH 32/92] Change memoized internals to be protected instead of
 private

---
 .../src/main/scala/lisa/utils/memoization/Memoized.scala    | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

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 43afb552..b010cabe 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

From 146c0743517f125ced9e456b24b1773361bdbaeb Mon Sep 17 00:00:00 2001
From: Sankalp Gambhir 
Date: Thu, 24 Oct 2024 08:38:52 +0200
Subject: [PATCH 33/92] Readd collectFirstDefined function, make
 utils.collection

---
 .../lisa/utils/collection/Extensions.scala    | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala

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 00000000..de2f894e
--- /dev/null
+++ b/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala
@@ -0,0 +1,24 @@
+package lisa.utils.collection
+
+object Extensions:
+
+  extension [A](seq: Iterable[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[T](f: A => Option[T]): Option[T] = {
+      var res: Option[T] = None
+      val iter = seq.iterator
+      while (res.isEmpty && iter.hasNext) {
+        res = f(iter.next())
+      }
+      res
+    }
+
+  }
+

From 2d04ee9519bce03b0ed4f4292be2fe6f9d368e8b Mon Sep 17 00:00:00 2001
From: Sankalp Gambhir 
Date: Thu, 24 Oct 2024 08:39:17 +0200
Subject: [PATCH 34/92] Make Substitution class more reliable

---
 .../utils/unification/UnificationUtils.scala  | 24 +++++++++++++------
 1 file changed, 17 insertions(+), 7 deletions(-)

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 0acbd5fd..a4d08602 100644
--- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala
@@ -27,32 +27,42 @@ object UnificationUtils:
 
   /**
    * 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 (assignments: Map[Variable[?], Expr[?]]):
-    private val underlying: Map[Variable[?], Expr[?]] = assignments
+  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]] =
-      underlying.get(v).map(_.asInstanceOf)
+      assignments.get(v).map(_.asInstanceOf)
 
     /**
      * Creates a new subtitution with a new mapping added
      */
     def +[A](mapping: (Variable[A], Expr[A])): Substitution =
-      Substitution(underlying + mapping)
+      val newfree = mapping._2.freeVars + mapping._1
+      Substitution(assignments + mapping, freeVariables ++ newfree)
 
     /**
      * Checks whether a variable is assigned by this subtitution
      */
     def contains[A](v: Variable[A]): Boolean =
-      underlying.contains(v)
+      assignments.contains(v)
 
     /**
      * Checks whether any susbtitution contains the given variable. Needed for
@@ -62,13 +72,13 @@ object UnificationUtils:
      * capture avoiding substitution.
      */
     def substitutes[A](v: Variable[A]): Boolean =
-      underlying.values.exists(_.freeVars.contains(v))
+      freeVariables(v)
 
   object Substitution:
     /**
      * The empty substitution
      */
-    def empty: Substitution = Substitution(Map.empty)
+    def empty: Substitution = Substitution(Map.empty, Set.empty)
 
   /**
    * Performs first-order matching for two terms. Returns a (most-general)

From 8917298a654bb89ca0d9125d98a71ea7eeae21b4 Mon Sep 17 00:00:00 2001
From: Sankalp Gambhir 
Date: Thu, 24 Oct 2024 08:39:39 +0200
Subject: [PATCH 35/92] Make LeftForall and RightExists use new matching

---
 .../lisa/utils/prooflib/BasicStepTactic.scala | 26 +++++++++++--------
 1 file changed, 15 insertions(+), 11 deletions(-)

diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
index 65885e45..2e0f3a08 100644
--- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
@@ -5,7 +5,8 @@ import lisa.prooflib.*
 import lisa.utils.K
 import lisa.utils.KernelHelpers.{|- => `K|-`, _}
 //import lisa.utils.UserLisaException
-import lisa.utils.unification.UnificationUtils
+import lisa.utils.unification.UnificationUtils.*
+import lisa.utils.collection.Extensions.*
 
 object BasicStepTactic {
 
@@ -399,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.forall(x, phi) => ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - 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.forall(x, phi)) =>
-            LeftForall.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - 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. φ.")
@@ -905,17 +908,18 @@ 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.exists(x, phi) =>
-              ??? // TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - x)).isDefined
-            case _ => false
+              val ctx = RewriteContext.withBound(phi.freeVars - x)
+              matchExpr(using ctx)(phi, in).map(f -> _)
+            case _ => None
           }
         )
 
         quantifiedPhi match {
-          case Some(F.exists(x, phi)) =>
-            RightExists.withParameters(phi, x, ??? /* TODO UnificationUtils.matchFormula(in, phi, takenTermVariables = (phi.freeVars - 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. φ.")

From 47c103586e57e66691dcfcabb4e8cd44a55e783e Mon Sep 17 00:00:00 2001
From: Simon Guilloud 
Date: Thu, 24 Oct 2024 12:03:23 +0200
Subject: [PATCH 36/92] Congruence.scala compiles

---
 .../scala/lisa/automation/Congruence.scala    | 62 +++++++++----------
 .../main/scala/lisa/utils/fol/Predef.scala    |  8 ++-
 .../lisa/utils/prooflib/BasicStepTactic.scala |  8 +--
 3 files changed, 42 insertions(+), 36 deletions(-)

diff --git a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala
index c75a78c8..98d915f7 100644
--- a/lisa-sets/src/main/scala/lisa/automation/Congruence.scala
+++ b/lisa-sets/src/main/scala/lisa/automation/Congruence.scala
@@ -46,10 +46,9 @@ object Congruence  extends ProofTactic with ProofSequentTactic {
       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 eq = have(egraph.proveExpr(lf, rf, bot))
           val a = variable[Formula]
-          val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
-          have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParameters(Seq((lf, rf)), (Seq(a), a))(base, eqstep)
+          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
@@ -57,20 +56,19 @@ object Congruence  extends ProofTactic with ProofSequentTactic {
       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 eq = have(egraph.proveExpr(lf, rf, bot))
           val a = variable[Formula]
-          val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
-          have((bot.left + (lf <=> rf)) |- (bot.right) ) by LeftSubstIff.withParameters(Seq((lf, rf)), (Seq(a), !a))(base, eqstep)
+          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.proveTerm(a, b, bot))
+          have(egraph.proveExpr(a, b, bot))
           true
         case !(a <=> b) if egraph.idEq(a, b) => 
-          have(egraph.proveFormula(a, b, bot))
+          have(egraph.proveExpr(a, b, bot))
           true
         case _ => false
       }
@@ -80,20 +78,19 @@ object Congruence  extends ProofTactic with ProofSequentTactic {
       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 eq = have(egraph.proveExpr(lf, rf, bot))
           val a = variable[Formula]
-          val eqstep = have((lf <=> rf) |- (lf <=> rf)) by Restate
-          have((bot.left + (lf <=> rf)) |- (bot.right) ) by RightSubstIff.withParameters(Seq((lf, rf)), (Seq(a), !a))(base, eqstep)
+          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.proveTerm(a, b, bot))
+          have(egraph.proveExpr(a, b, bot))
           true
         case (a <=> b) if egraph.idEq(a, b) =>
-          have(egraph.proveFormula(a, b, bot))
+          have(egraph.proveExpr(a, b, bot))
           true
         case _ => false
       }
@@ -388,33 +385,33 @@ class EGraphExpr() {
               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)
+              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 (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.withParameters(zip, (vars, l === Multiapply(labelr, 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
@@ -423,19 +420,21 @@ class EGraphExpr() {
                 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.withParameters(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 = {
@@ -471,7 +470,7 @@ class EGraphExpr() {
                       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
                     }
                   }
@@ -527,6 +526,7 @@ class EGraphExpr() {
         }
     }
   }
+    */
 
 
 }
\ No newline at end of file
diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
index 2b832777..8cbc033a 100644
--- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
@@ -15,6 +15,12 @@ trait Predef extends Syntax {
   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]("=")
@@ -102,7 +108,7 @@ trait Predef extends Syntax {
       case id: K.Identifier => Seq(id)
     }).map(_.no).max
 
-  def freshId(exprs: Seq[K.Expression | Expr[?] | K.Identifier ], base: String = "x"): K.Identifier = {
+  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)
diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
index 2e0f3a08..a8531927 100644
--- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
@@ -1120,14 +1120,14 @@ object BasicStepTactic {
   object LeftSubstEq extends ProofTactic {
     @deprecated("Use withParameters instead", "0.9")
     def withParametersSimple(using lib: Library, proof: lib.Proof)(
-        equals: List[(F.Term, F.Term)],
+        equals: Seq[(F.Term, F.Term)],
         lambdaPhi: (Seq[F.Variable[?]], F.Formula)
     )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = {
       withParameters(equals, lambdaPhi)(premise)(bot)
     }
 
     def withParameters(using lib: Library, proof: lib.Proof)(
-        equals: List[(F.Expr[?], F.Expr[?])],
+        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
@@ -1178,14 +1178,14 @@ object BasicStepTactic {
   object RightSubstEq extends ProofTactic {
     @deprecated("Use withParameters instead", "0.9")
     def withParametersSimple(using lib: Library, proof: lib.Proof)(
-        equals: List[(F.Term, F.Term)],
+        equals: Seq[(F.Term, F.Term)],
         lambdaPhi: (Seq[F.Variable[?]], F.Formula)
     )(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = {
       withParameters(equals, lambdaPhi)(premise)(bot)
     }
 
     def withParameters(using lib: Library, proof: lib.Proof)(
-        equals: List[(F.Expr[?], F.Expr[?])],
+        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

From 261a9cb6e159f4188ae603c6d9d8255f817a52d7 Mon Sep 17 00:00:00 2001
From: Simon Guilloud 
Date: Thu, 24 Oct 2024 12:41:47 +0200
Subject: [PATCH 37/92] update SetTheoryLibrary, small improvement to
 Syntax.scala unapply.

---
 .../main/scala/lisa/SetTheoryLibrary.scala    | 69 ++++++++++---------
 .../main/scala/lisa/utils/fol/Predef.scala    |  6 +-
 .../main/scala/lisa/utils/fol/Syntax.scala    | 16 +++--
 3 files changed, 50 insertions(+), 41 deletions(-)

diff --git a/lisa-sets/src/main/scala/lisa/SetTheoryLibrary.scala b/lisa-sets/src/main/scala/lisa/SetTheoryLibrary.scala
index addae4c8..c7862e66 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-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
index 8cbc033a..2ca482ea 100644
--- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
@@ -5,6 +5,8 @@ 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)
@@ -90,10 +92,10 @@ trait Predef extends Syntax {
     case l: K.Lambda => asFrontLambda(l)
 
   def asFrontConstant(c: K.Constant): Constant[?] = 
-    new Constant[T](c.id)(using new Sort { type Self = T; val underlying = c.sort })
+    new Constant[T](c.id)(using unsafeSortEvidence(c.sort))
 
   def asFrontVariable(v: K.Variable): Variable[?] =
-    new Variable[T](v.id)(using new Sort { type Self = T; val underlying = v.sort })
+    new Variable[T](v.id)(using unsafeSortEvidence(v.sort))
   
   def asFrontApplication(a: K.Application): App[?, ?] = 
     new App(asFrontExpression(a.f).asInstanceOf, asFrontExpression(a.arg))
diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
index 4ec67642..56d433a5 100644
--- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
@@ -8,14 +8,12 @@ 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}
 
-  sealed trait T
-  sealed trait F
-  sealed trait Arrow[A: Sort, B: Sort]
 
   type Formula = Expr[F]
   type Term = Expr[T]
@@ -28,10 +26,18 @@ trait Syntax {
     case Expr[t] => t
   }
 
-  trait Sort {
+  sealed trait Sort {
     type Self
     val underlying: K.Sort
   }
+
+
+  sealed trait T extends Sort
+  sealed trait F extends Sort
+  sealed trait Arrow[A: Sort, B: Sort] extends 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
@@ -88,7 +94,7 @@ trait Syntax {
     override def substitute(pairs: SubstPair*): Expr[S] = 
       super.substitute(pairs*).asInstanceOf[Expr[S]]
 
-    def unapplySeq[Target](e: Expr[Target]): Option[ArgsTo[S, Target]] = 
+    def unapplySeq[Target <: Sort](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]])

From f62d9cc1fac112300c3d0489b88dcb7ba4778322 Mon Sep 17 00:00:00 2001
From: Simon Guilloud 
Date: Thu, 24 Oct 2024 17:19:29 +0200
Subject: [PATCH 38/92] debugging pettern matching

---
 build.sbt                                     | 10 +++-
 .../scala/lisa/automation/CommonTactics.scala |  8 +--
 .../scala/lisa/automation/atp/Goeland.scala   |  4 +-
 .../main/scala/lisa/maths/Quantifiers.scala   | 28 +++++------
 .../lisa/maths/settheory/InductiveSets.scala  |  8 +--
 .../lisa/maths/settheory/SetTheory.scala      | 50 +++++++++----------
 .../lisa/examples/peano_example/Peano.scala   |  6 +--
 .../main/scala/lisa/utils/KernelHelpers.scala | 25 ----------
 .../main/scala/lisa/utils/fol/Predef.scala    | 29 +++++++++++
 .../main/scala/lisa/utils/fol/Syntax.scala    | 38 ++++++--------
 .../lisa/utils/prooflib/BasicStepTactic.scala | 11 ++--
 .../scala/lisa/utils/BasicTacticTest.scala    |  8 +--
 12 files changed, 116 insertions(+), 109 deletions(-)

diff --git a/build.sbt b/build.sbt
index ceadfb1c..8c94d2e2 100644
--- a/build.sbt
+++ b/build.sbt
@@ -16,7 +16,8 @@ ThisBuild / semanticdbEnabled := true
 ThisBuild / semanticdbVersion := scalafixSemanticdb.revision
 
 val scala2 = "2.13.8"
-val scala3 = "3.5.1"
+val scala3 = "3.5.2"
+//val scala3 = "3.6.2-RC1-bin-20241023-31e7359-NIGHTLY"
 val commonSettings = Seq(
   crossScalaVersions := Seq(scala3),
 
@@ -76,6 +77,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-sets/src/main/scala/lisa/automation/CommonTactics.scala b/lisa-sets/src/main/scala/lisa/automation/CommonTactics.scala
index 2d2b8219..db243327 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/atp/Goeland.scala b/lisa-sets/src/main/scala/lisa/automation/atp/Goeland.scala
index e8d50cf0..2493399e 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 1aeaf55a..bad4ab92 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 cbc135a1..768e9bd8 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 3439bad4..4908bc36 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/examples/peano_example/Peano.scala b/lisa-sets/src/test/scala/lisa/examples/peano_example/Peano.scala
index 2f01882f..c84112f7 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-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
index 39d6993f..08c2d3b4 100644
--- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
@@ -177,12 +177,6 @@ object KernelHelpers {
   /* Conversions */
 
   /*
-  given Conversion[TermLabel, Expression] = Expression(_, Seq())
-  given Conversion[Expression, 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[Expression, Sequent] = () |- _
@@ -278,25 +272,6 @@ object KernelHelpers {
     s.left.map(phi => substituteVariables(phi, m)) |- s.right.map(phi => substituteVariables(phi, m))
   }
 
-  /*
-  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))
-  }
-    */
-
   //////////////////////
   // SCProofs helpers //
   //////////////////////
diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
index 2ca482ea..fc870d91 100644
--- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala
@@ -4,6 +4,33 @@ import lisa.utils.K
 import K.given
 
 trait Predef extends Syntax {
+  //Current
+  val e: Formula = ???
+
+  def printt(t: Term) = println(t)
+  def printf(f: Formula) = println(f)
+
+  trait Test
+
+  
+  def validSubstitutionRule
+    (rule: (Test | Formula)): Unit =
+      rule match
+        // as formula
+        //case l === r => {printt(l); printt(r)}
+        case ===[F](l, r) => {printt(l); printt(r)}
+        //case === #@ l #@ r => {printt(l); printt(r)}
+        //case l <=> r => {printf(l); printf(r)}
+   
+
+  /*
+  e match
+      //case App(App(`===`, left), right) => ()
+      //case left === right => ()
+      //case ===(left, right) => ()
+      case ===[F](left, right) => ()
+      case _ => ()
+        */
 
   export K.{given_Conversion_String_Identifier, given_Conversion_Identifier_String}
 
@@ -153,4 +180,6 @@ trait Predef extends Syntax {
     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/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
index 56d433a5..5408dd1a 100644
--- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala
@@ -26,15 +26,15 @@ trait Syntax {
     case Expr[t] => t
   }
 
-  sealed trait Sort {
+  trait Sort {
     type Self
     val underlying: K.Sort
   }
 
 
-  sealed trait T extends Sort
-  sealed trait F extends Sort
-  sealed trait Arrow[A: Sort, B: Sort] extends 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
 
@@ -94,7 +94,7 @@ trait Syntax {
     override def substitute(pairs: SubstPair*): Expr[S] = 
       super.substitute(pairs*).asInstanceOf[Expr[S]]
 
-    def unapplySeq[Target <: Sort](e: Expr[Target]): Option[ArgsTo[S, Target]] = 
+    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]])
@@ -104,21 +104,6 @@ trait Syntax {
     @targetName("unapplySeq2")
     def unapplySeq(e: Expr[?]): Option[Seq[Expr[?]]] = Multiapp(this).unapply(e)
 
-
-    /*
-    @targetName("unapply2")
-    def unapply[T1, T2, T3](e: Expr[T3]): Option[(Expr[T1], Expr[T2])] = (e: @unchecked) match 
-      case App[T2, T3](e1, a2) => e1 match 
-        case App[T1, T2 >>: T3](f, a1) if f == this => Some((a1, a2))
-        case _ => None
-      case _ => None
-        
-    @targetName("unapply1")  
-    def unapply[T1, T2](e: Expr[T2]): Option[Expr[T1]] = (e: @unchecked) match {
-      case App[T1, T2](f, arg)  if f == this => Some(arg)
-      case _ => None
-    }
-      */
     final def defaultMkString(args: Seq[Expr[?]]): String = s"$this(${args.map(a => s"(${a})")})"
     final def defaultMkStringSeparated(args: Seq[Expr[?]]): String = s"(${defaultMkString(args)})"
     var mkString: Seq[Expr[?]] => String = defaultMkString
@@ -243,7 +228,7 @@ trait Syntax {
   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 {
+    def unapply(e: Expr[?]): 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
     }
@@ -304,10 +289,15 @@ trait Syntax {
     override def toString(): String = s"Abs($v, $body)"
   }
 
-  object Abs {
+  object Abs:
     def unsafe(v: Variable[?], body: Expr[?]): Expr[?] = 
-      Abs(v.asInstanceOf, body.asInstanceOf)
-  }
+      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/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
index a8531927..7eef072f 100644
--- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala
@@ -1238,14 +1238,19 @@ object BasicStepTactic {
    * 
*/ object InstSchema extends ProofTactic { - def apply(using lib: Library, proof: lib.Proof - )(map: Map[F.Variable[?], F.Expr[?]])(premise: proof.Fact): proof.ProofTacticJudgement = { + 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)) - } + + 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) + + + } object Subproof extends ProofTactic { def apply(using proof: Library#Proof)(statement: Option[F.Sequent])(iProof: proof.InnerProof) = { diff --git a/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala b/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala index b63fb972..ed9a3067 100644 --- a/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala +++ b/lisa-utils/src/test/scala/lisa/utils/BasicTacticTest.scala @@ -1426,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) @@ -1456,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) @@ -1492,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) } } */ From 343b8100195b2b527b8220acbb33589eadd3b7ad Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Fri, 25 Oct 2024 00:27:29 +0200 Subject: [PATCH 39/92] clean to sets2. clean to sets2. --- .../main/scala/lisa/utils/fol/Predef.scala | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index fc870d91..65d7c3c8 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -4,33 +4,6 @@ import lisa.utils.K import K.given trait Predef extends Syntax { - //Current - val e: Formula = ??? - - def printt(t: Term) = println(t) - def printf(f: Formula) = println(f) - - trait Test - - - def validSubstitutionRule - (rule: (Test | Formula)): Unit = - rule match - // as formula - //case l === r => {printt(l); printt(r)} - case ===[F](l, r) => {printt(l); printt(r)} - //case === #@ l #@ r => {printt(l); printt(r)} - //case l <=> r => {printf(l); printf(r)} - - - /* - e match - //case App(App(`===`, left), right) => () - //case left === right => () - //case ===(left, right) => () - case ===[F](left, right) => () - case _ => () - */ export K.{given_Conversion_String_Identifier, given_Conversion_Identifier_String} From 874c3ce3b351949a8ef1111e86d1e60955db580c Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Fri, 25 Oct 2024 01:23:11 +0200 Subject: [PATCH 40/92] fix bugs in equivalence checker and Tableau. --- .../scala/lisa/kernel/fol/OLEquivalenceChecker.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala index d03e9c16..ee6f3ae7 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -14,13 +14,14 @@ private[fol] trait OLEquivalenceChecker extends Syntax { res } - def reducedNNFForm(formula: Expression): Expression = { - val p = simplify(formula) + def reducedNNFForm(expr: Expression): Expression = { + 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) @@ -245,7 +246,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { - def polarize(e: Expression, polarity:Boolean): SimpleExpression = + def polarize(e: Expression, polarity:Boolean): SimpleExpression = { if (polarity & e.polarExpr.isDefined) { e.polarExpr.get } else if (!polarity & e.polarExpr.isDefined) { @@ -277,8 +278,8 @@ private[fol] trait OLEquivalenceChecker extends Syntax { case Application(f, arg) => SimpleApplication(polarize(f, true), polarize(arg, true), polarity) case Lambda(v, body) => SimpleLambda(v, polarize(body, true)) - case Constant(`top`, Formula) => SimpleLiteral(true) - case Constant(`bot`, Formula) => SimpleLiteral(false) + case `top` => SimpleLiteral(polarity) + case `bot` => SimpleLiteral(!polarity) case Constant(id, sort) => SimpleConstant(id, sort, polarity) case Variable(id, sort) => SimpleVariable(id, sort, polarity) } @@ -286,6 +287,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { else e.polarExpr = Some(getInversePolar(r)) r } + } def toLocallyNameless(e: SimpleExpression): SimpleExpression = e.namelessForm match { From d1472155272b4bf801b53fcd3c15d8c77c625c8d Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:44:10 +0200 Subject: [PATCH 41/92] Rewrite Substitution.Apply --- .../scala/lisa/automation/Substitution.scala | 1080 +++++++---------- 1 file changed, 453 insertions(+), 627 deletions(-) diff --git a/lisa-sets/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets/src/main/scala/lisa/automation/Substitution.scala index 87eab43c..af970d64 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 - 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.map(_.rule) + val rightRules = rightRewrites.map(_.rule) + + // 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 From 2c23d199a745030f71f3c3f893f1cc1c35e6eb49 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:44:29 +0200 Subject: [PATCH 42/92] Add toOptionSeq extension method --- .../lisa/utils/collection/Extensions.scala | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala b/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala index de2f894e..e0b38ee9 100644 --- a/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala +++ b/lisa-utils/src/main/scala/lisa/utils/collection/Extensions.scala @@ -1,9 +1,11 @@ package lisa.utils.collection -object Extensions: +import scala.collection.immutable.SeqOps +import scala.collection.immutable.VectorBuilder - extension [A](seq: Iterable[A]) { +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` @@ -11,14 +13,24 @@ object Extensions: * * @param f the function to evaluate */ - def collectFirstDefined[T](f: A => Option[T]): Option[T] = { - var res: Option[T] = None + 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()) - } + 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()) From 7e6e89a6dcb9a0a2b9f6861ef452482ba8eb41cf Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:44:44 +0200 Subject: [PATCH 43/92] Add andAll / orAll methods --- lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index 2ca482ea..ca63643d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -6,8 +6,7 @@ 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]]) @@ -73,8 +72,6 @@ trait Predef extends Syntax { val existsOne = binder[Term, Formula, Formula]("∃!") val ∃! : existsOne.type = existsOne - - extension (f: Formula) { def unary_! = neg(f) infix inline def ==>(g: Formula): Formula = implies(f)(g) @@ -85,6 +82,12 @@ trait Predef extends Syntax { 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) From c14b35f9e7f16f4a6cf884b085dfdad814114edb Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:45:00 +0200 Subject: [PATCH 44/92] Switch tactic inputs to use Seq instead of List --- .../src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index a8531927..cc837483 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -947,7 +947,7 @@ object BasicStepTactic { 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) From 792f904f0c8a65b6e9b4989e5423ecbd2e5d8873 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:45:43 +0200 Subject: [PATCH 45/92] Extend RewriteContext with rules --- .../utils/unification/UnificationUtils.scala | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) 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 a4d08602..608e84de 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -14,16 +14,57 @@ object UnificationUtils: * @param boundVariables variables in terms that cannot be substituted */ case class RewriteContext( - boundVariables: Set[Variable[?]] + boundVariables: Set[Variable[?]], + freeRules: Set[(Expr[?], Expr[?])], + confinedRules: Set[(Expr[?], Expr[?])], ): + /** + * 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) - def bind[A](v: Variable[A]) = this.copy(boundVariables = boundVariables + 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](l: Expr[A], r: Expr[A]) = + this.copy(freeRules = freeRules + (l -> r)) + + /** + * 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](l: Expr[A], r: Expr[A]) = + this.copy(confinedRules = confinedRules + (l -> r)) object RewriteContext: - def empty = RewriteContext(Set.empty) + /** + * 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.toSet) + RewriteContext(vars.toSet, Set.empty, Set.empty) /** * Immutable representation of a typed variable substitution. From d3a6d26573ba98169c88bbd8d06b7b4f0337c788 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:46:03 +0200 Subject: [PATCH 46/92] Better matching documentation and naming --- .../lisa/utils/unification/UnificationUtils.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 608e84de..79c79aad 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -93,20 +93,20 @@ object UnificationUtils: assignments.get(v).map(_.asInstanceOf) /** - * Creates a new subtitution with a new mapping added + * 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 subtitution + * Checks whether a variable is assigned by this substitution */ def contains[A](v: Variable[A]): Boolean = assignments.contains(v) /** - * Checks whether any susbtitution contains the given variable. Needed for + * Checks whether any substitution contains the given variable. Needed for * verifying ill-formed substitutions containing bound variables. * * Eg: if `v` is externally bound, then `x` and `f(v)` have no matcher under @@ -125,10 +125,13 @@ object UnificationUtils: * 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. */ @@ -158,7 +161,7 @@ object UnificationUtils: case (Abs(ve, fe), Abs(vp, fp)) => val freshVar = ve.freshRename(Seq(fe, fp)) - matchExpr(using ctx.bind(freshVar))( + matchExpr(using ctx.withBound(freshVar))( fe.substitute(ve := freshVar), fp.substitute(vp := freshVar), subst From 4e621fcf79fef73a5f68c8432f9fa6b65207a110 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:46:20 +0200 Subject: [PATCH 47/92] Rewrite method and classes scaffolds --- .../utils/unification/UnificationUtils.scala | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) 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 79c79aad..be6232f2 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -169,6 +169,42 @@ object UnificationUtils: 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 + def toFormula: Formula = ??? + + case class TermRewriteRule(l: Term, r: Term) extends RewriteRule: + type Base = T + def swap: TermRewriteRule = TermRewriteRule(r, l) + def source(using lib: Library, proof: lib.Proof): proof.Fact = + lib.have(l === r |- l === r) by SimpleDeducedSteps.Restate + + case class FormulaRewriteRule(l: Formula, r: Formula) extends RewriteRule: + type Base = F + def swap: FormulaRewriteRule = FormulaRewriteRule(r, l) + def source(using lib: Library, proof: lib.Proof): proof.Fact = + lib.have(l <=> r |- l <=> r) by SimpleDeducedSteps.Restate + + case class RewriteResult(valut: Int): + def toLeft: Formula = ??? + def toRight: Formula = ??? + def rule: RewriteRule = ??? + def lambda: (Seq[Variable[?]], Formula) = ??? + + def rewrite[A](using ctx: RewriteContext)(from: Expr[A], to: Expr[A]): Option[RewriteResult] = + ??? + end UnificationUtils // object UnificationUtils { From fc29f49ee43040a1592a5eb9b161716b004e7ffb Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:46:35 +0200 Subject: [PATCH 48/92] Minor corrections --- .../scala/lisa/utils/unification/UnificationUtils.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 be6232f2..fc750b59 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -1,6 +1,8 @@ package lisa.utils.unification import lisa.fol.FOL.{_, given} +import lisa.prooflib.Library +import lisa.prooflib.SimpleDeducedSteps /** * General utilities for unification, substitution, and rewriting @@ -85,7 +87,6 @@ object UnificationUtils: // freeVariables == assignments.keySet ++ assignments.values.flatMap(_.freeVars) // ) - /** * (Optionally) retrieves a variable's mapping */ @@ -304,7 +305,7 @@ end UnificationUtils * generation state * @param reference the reference terms * @param template the terms to match - * @param substitution currently accumulated susbtitutions to variables + * @param substitution currently accumulated substitutions to variables * @return substitution (Option) from variables to terms. `None` if a * substitution does not exist. */ @@ -364,8 +365,8 @@ end UnificationUtils * @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 + * @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. */ From 1ea1a11b73e8df6e5409a7740ac369b1424f6364 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 08:47:06 +0200 Subject: [PATCH 49/92] Upgrade to scala 3.5.2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ceadfb1c..2e80cb7a 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), From 1d1cbc300ec4193fe5c441538a9528803bfdd0e3 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Fri, 25 Oct 2024 12:09:38 +0200 Subject: [PATCH 50/92] add some @unchecked, git add new lisa-sets2 folder. --- lisa-sets2/src/main/scala/lisa/Main.scala | 38 + .../main/scala/lisa/SetTheoryLibrary.scala | 257 +++++ .../scala/lisa/automation/CommonTactics.scala | 262 +++++ .../scala/lisa/automation/Congruence.scala | 537 ++++++++++ .../scala/lisa/automation/Substitution.scala | 467 +++++++++ .../main/scala/lisa/automation/Tableau.scala | 491 ++++++++++ .../scala/lisa/automation/Tautology.scala | 203 ++++ .../main/scala/lisa/maths/Quantifiers.scala | 267 +++++ .../lisa/maths/settheory/SetTheory2.scala | 109 +++ .../lisa/automation/CongruenceTest.scala | 913 ++++++++++++++++++ .../scala/lisa/automation/TableauTest.scala | 158 +++ .../lisa/examples/peano_example/Peano.scala | 344 +++++++ .../peano_example/PeanoArithmetics.scala | 39 + .../PeanoArithmeticsLibrary.scala | 7 + .../lisa/proven/InitialProofsTests.scala | 20 + .../lisa/utilities/ComprehensionsTests.scala | 83 ++ .../lisa/utilities/SerializationTest.scala | 188 ++++ .../utilities/SubstitutionTacticTest.scala | 119 +++ .../test/scala/lisa/utilities/TestMain.scala | 16 + 19 files changed, 4518 insertions(+) create mode 100644 lisa-sets2/src/main/scala/lisa/Main.scala create mode 100644 lisa-sets2/src/main/scala/lisa/SetTheoryLibrary.scala create mode 100644 lisa-sets2/src/main/scala/lisa/automation/CommonTactics.scala create mode 100644 lisa-sets2/src/main/scala/lisa/automation/Congruence.scala create mode 100644 lisa-sets2/src/main/scala/lisa/automation/Substitution.scala create mode 100644 lisa-sets2/src/main/scala/lisa/automation/Tableau.scala create mode 100644 lisa-sets2/src/main/scala/lisa/automation/Tautology.scala create mode 100644 lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/SetTheory2.scala create mode 100644 lisa-sets2/src/test/scala/lisa/automation/CongruenceTest.scala create mode 100644 lisa-sets2/src/test/scala/lisa/automation/TableauTest.scala create mode 100644 lisa-sets2/src/test/scala/lisa/examples/peano_example/Peano.scala create mode 100644 lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmetics.scala create mode 100644 lisa-sets2/src/test/scala/lisa/examples/peano_example/PeanoArithmeticsLibrary.scala create mode 100644 lisa-sets2/src/test/scala/lisa/proven/InitialProofsTests.scala create mode 100644 lisa-sets2/src/test/scala/lisa/utilities/ComprehensionsTests.scala create mode 100644 lisa-sets2/src/test/scala/lisa/utilities/SerializationTest.scala create mode 100644 lisa-sets2/src/test/scala/lisa/utilities/SubstitutionTacticTest.scala create mode 100644 lisa-sets2/src/test/scala/lisa/utilities/TestMain.scala 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 00000000..3a56aea6 --- /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 00000000..c7862e66 --- /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 00000000..db243327 --- /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 { + /* + + /** + *
+   * Γ |- ∃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 00000000..905bad78 --- /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 00000000..3a9bb59c --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -0,0 +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.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} + +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) + val (l, r) = (erule.l, erule.r) + rule match + case f: Formula @unchecked => + (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 @unchecked => + (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: Expr[?] => 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[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.map(_.rule) + val rightRules = rightRewrites.map(_.rule) + + // 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-sets2/src/main/scala/lisa/automation/Tableau.scala b/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala new file mode 100644 index 00000000..62bc4030 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala @@ -0,0 +1,491 @@ +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 00000000..95c0ae80 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala @@ -0,0 +1,203 @@ +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(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) + case _ => throw new Exception("Unreachable case in findBestAtom") + } + 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 bestAtom = findBestAtom(s.formula) + val redF = reducedForm(s.formula) + if (redF == top()) { + 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, 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((s.decisions._1, atom :: s.decisions._2), substituteVariables(lambdaF, Map(MaRvIn -> top))) + 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(f: Expression, x: Variable, e: Expression, fv: Set[Variable]): (Expression, Boolean) = { + if (isSame(f, e)) (x, true) + else + f 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 (f, false) + case Lambda(v, inner) => + if (!fv.contains(v)) { + val induct = findSubformula2(inner, x, e, fv) + if (!induct._2) (f, false) + else (Lambda(v, induct._1), true) + } else { + 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 (Lambda(newv, induct._1), true) + } + case _ => (f, false) + } + } + + 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/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala new file mode 100644 index 00000000..80377273 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -0,0 +1,267 @@ +package lisa.maths + +import lisa.utils.Serialization.sorry +import lisa.prooflib.BasicStepTactic.Sorry +import lisa.utils.K.repr +/** + * Implements theorems about first-order logic. + */ +object Quantifiers extends lisa.Main { + + 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 + } + + /** + * 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 + } + + /** + * 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(thesis) by Restate + } + + /** + * 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) + } + +} 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 00000000..c9811f18 --- /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/test/scala/lisa/automation/CongruenceTest.scala b/lisa-sets2/src/test/scala/lisa/automation/CongruenceTest.scala new file mode 100644 index 00000000..74c6e9ec --- /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 00000000..69a8d250 --- /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 00000000..c84112f7 --- /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 00000000..e422b6f1 --- /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 00000000..3f9d423f --- /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 00000000..9800e4fe --- /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 00000000..7b7a67c3 --- /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 00000000..29826e76 --- /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 00000000..b69b8e4e --- /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 00000000..6d93e5d0 --- /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() + } + +} From cd05987a178736a330979bd56cc2b8c097dfb7b5 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Fri, 25 Oct 2024 12:21:05 +0200 Subject: [PATCH 51/92] reestablish a vompilation crash for tests. --- lisa-sets2/src/main/scala/lisa/automation/Substitution.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala index 3a9bb59c..fa28cbed 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -61,8 +61,8 @@ object Substitution: (rule: (proof.Fact | F.Formula)): Boolean = rule match // as formula - case f: Expr[?] => f match - case === #@ l #@ r => true + case f: Formula @ unchecked => f match + case ===(l, r) => true case <=> #@ l #@ r => true case _ => false // as a justification From d369a7e16af235271f97db648ce9c321782a7a7f Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Fri, 25 Oct 2024 12:30:05 +0200 Subject: [PATCH 52/92] small fix --- .../src/main/scala/lisa/automation/Congruence.scala | 12 ++++++------ .../main/scala/lisa/automation/Substitution.scala | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Congruence.scala b/lisa-sets2/src/main/scala/lisa/automation/Congruence.scala index 905bad78..75784aa2 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Congruence.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Congruence.scala @@ -37,8 +37,8 @@ object Congruence extends ProofTactic with ProofSequentTactic { egraph.addAll(bot.right) bot.left.foreach{ - case `===` #@ l #@ r => egraph.merge(l, r) - case `<=>` #@ l #@ r => egraph.merge(l, r) + case === #@ l #@ r => egraph.merge(l, r) + case <=> #@ l #@ r => egraph.merge(l, r) case _ => () } @@ -66,10 +66,10 @@ object Congruence extends ProofTactic with ProofSequentTactic { case _ => false } || { lf match - case neg #@ (`===` #@ a #@ b) if egraph.idEq(a, b) => + 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) => + case neg #@ (<=> #@ a #@ b) if egraph.idEq(a, b) => have(egraph.proveExpr(a, b, bot)) true case _ => false @@ -88,10 +88,10 @@ object Congruence extends ProofTactic with ProofSequentTactic { case _ => false } || { rf match - case (`===` #@ a #@ b) if egraph.idEq(a, b) => + case (=== #@ a #@ b) if egraph.idEq(a, b) => have(egraph.proveExpr(a, b, bot)) true - case (`<=>` #@ a #@ b) if egraph.idEq(a, b) => + case (<=> #@ a #@ b) if egraph.idEq(a, b) => have(egraph.proveExpr(a, b, bot)) true case _ => false diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala index fa28cbed..a1f6f066 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -28,8 +28,8 @@ object Substitution: (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 === #@ (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) /** @@ -61,8 +61,8 @@ object Substitution: (rule: (proof.Fact | F.Formula)): Boolean = rule match // as formula - case f: Formula @ unchecked => f match - case ===(l, r) => true + case f: Formula @unchecked => f match + case === #@ l #@ r => true case <=> #@ l #@ r => true case _ => false // as a justification From 929d8e4edc5f838e9d5409752914ccbd48fca1d6 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 14:38:32 +0200 Subject: [PATCH 53/92] Add new Vector backed order-preserving Set --- .../scala/lisa/utils/collection/VecSet.scala | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala 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 00000000..73fc5de2 --- /dev/null +++ b/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala @@ -0,0 +1,73 @@ +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 From 5ab7342f8d717cf90ce2cd185c2efac94e3acf8d Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Fri, 25 Oct 2024 14:39:12 +0200 Subject: [PATCH 54/92] Expand rewrite result types --- .../scala/lisa/automation/Substitution.scala | 4 +- .../utils/unification/UnificationUtils.scala | 63 ++++++++++++++----- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/lisa-sets/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets/src/main/scala/lisa/automation/Substitution.scala index af970d64..bf39f308 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Substitution.scala @@ -176,8 +176,8 @@ object Substitution: TacticSubproof: val leftRewrites = leftSubsts.get val rightRewrites = rightSubsts.get - val leftRules = leftRewrites.map(_.rule) - val rightRules = rightRewrites.map(_.rule) + val leftRules = leftRewrites.head.rules + val rightRules = rightRewrites.head.rules // instantiated discharges 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 fc750b59..d6b2e58e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -3,6 +3,8 @@ package lisa.utils.unification import lisa.fol.FOL.{_, given} import lisa.prooflib.Library import lisa.prooflib.SimpleDeducedSteps +import lisa.utils.KernelHelpers.freshId +import lisa.utils.memoization.memoized /** * General utilities for unification, substitution, and rewriting @@ -17,8 +19,8 @@ object UnificationUtils: */ case class RewriteContext( boundVariables: Set[Variable[?]], - freeRules: Set[(Expr[?], Expr[?])], - confinedRules: Set[(Expr[?], Expr[?])], + freeRules: Set[RewriteRule], + confinedRules: Set[RewriteRule], ): /** * Checks if a variable is free under this context. @@ -46,15 +48,38 @@ object UnificationUtils: * 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](l: Expr[A], r: Expr[A]) = - this.copy(freeRules = freeRules + (l -> r)) + def withFreeRule[A](rule: RewriteRule) = + this.copy(freeRules = freeRules + rule) /** * 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](l: Expr[A], r: Expr[A]) = - this.copy(confinedRules = confinedRules + (l -> r)) + def withConfinedRule[A](rule: RewriteRule) = + this.copy(confinedRules = confinedRules + rule) + + /** + * (Deterministic) variables assigned to rewrite rules. + * + * In place of generic holes in the presence of several rewrite rules, + * their representatives are used. + * + */ + def ruleRepresentatives = + allRules.map(representativeVariable) + + /** + * All rules (free + confined) in this context. + */ + def allRules: Seq[RewriteRule] = ??? // freeRules ++ confinedRules + + protected val representativeVariable = memoized(__representativeVariable) + + private def __representativeVariable(rule: RewriteRule): Variable[?] = + val id = ??? // freshRepr + rule match + case TermRewriteRule(_, _) => Variable[T](id) + case FormulaRewriteRule(_, _) => Variable[F](id) object RewriteContext: /** @@ -182,28 +207,34 @@ object UnificationUtils: /** * The trivial hypothesis step that can be used as a source for this rewrite */ - def source(using lib: Library, proof: lib.Proof): proof.Fact - def toFormula: Formula = ??? + 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 case class TermRewriteRule(l: Term, r: Term) extends RewriteRule: type Base = T def swap: TermRewriteRule = TermRewriteRule(r, l) - def source(using lib: Library, proof: lib.Proof): proof.Fact = - lib.have(l === r |- l === r) by SimpleDeducedSteps.Restate + def toFormula: Formula = l === r case class FormulaRewriteRule(l: Formula, r: Formula) extends RewriteRule: type Base = F def swap: FormulaRewriteRule = FormulaRewriteRule(r, l) - def source(using lib: Library, proof: lib.Proof): proof.Fact = - lib.have(l <=> r |- l <=> r) by SimpleDeducedSteps.Restate + def toFormula: Formula = l <=> r - case class RewriteResult(valut: Int): + case class RewriteResult[A](ctx: RewriteContext, context: Expr[A]): def toLeft: Formula = ??? def toRight: Formula = ??? - def rule: RewriteRule = ??? - def lambda: (Seq[Variable[?]], Formula) = ??? + def vars: Seq[Variable[?]] = ctx.ruleRepresentatives + def lambda: Expr[A] = context + def rules: Seq[RewriteRule] = ctx.allRules + + type FormulaRewriteResult = RewriteResult[F] - def rewrite[A](using ctx: RewriteContext)(from: Expr[A], to: Expr[A]): Option[RewriteResult] = + def rewrite[A](using ctx: RewriteContext)(from: Expr[A], to: Expr[A]): Option[RewriteResult[A]] = ??? end UnificationUtils From 5f6de320ba575f2f76f2ac132afad3e9b18a7254 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Fri, 25 Oct 2024 14:45:00 +0200 Subject: [PATCH 55/92] Improve pretty printing --- lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala | 5 +++++ lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala | 3 --- lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala | 4 +++- .../src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala index 80377273..718f66d4 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -46,6 +46,11 @@ object Quantifiers extends lisa.Main { 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. diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index 4f56f8fb..a9b5a576 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -69,9 +69,6 @@ trait Predef extends Syntax { val epsilon = binder[Term, Formula, Term]("ε") val ε : epsilon.type = epsilon - val existsOne = binder[Term, Formula, Formula]("∃!") - val ∃! : existsOne.type = existsOne - extension (f: Formula) { def unary_! = neg(f) infix inline def ==>(g: Formula): Formula = implies(f)(g) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 5408dd1a..21bf1756 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -104,7 +104,7 @@ trait Syntax { @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})")})" + 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 @@ -219,6 +219,8 @@ trait Syntax { 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 { diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala index f291c164..f9379538 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -154,7 +154,7 @@ trait ProofsHelpers { val r = inner(e) (r._1.reverse, r._2) - def DEF[S: Sort](using om: OutputManager, name: sourcecode.FullName, line: sourcecode.Line, file: sourcecode.File) + 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 From 131e1eacc6802bd4866f4e979853244416e5d953 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Sat, 26 Oct 2024 23:11:19 +0200 Subject: [PATCH 56/92] fixed dome bugs in tautology. --- .../scala/lisa/automation/Substitution.scala | 3 ++ .../scala/lisa/automation/Tautology.scala | 29 ++++++++++--------- .../main/scala/lisa/maths/Quantifiers.scala | 13 ++++++++- .../lisa/utils/prooflib/BasicStepTactic.scala | 2 +- .../lisa/utils/prooflib/ProofsHelpers.scala | 4 +-- .../utils/unification/UnificationUtils.scala | 1 + 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala index a1f6f066..71fdb391 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -19,6 +19,7 @@ import scala.collection.mutable.{Map as MMap} import F.{*, given} object Substitution: + /* /** * Extracts a raw substitution into a `RewriteRule`. @@ -230,6 +231,8 @@ object Substitution: end Apply + */ + // object applySubst extends ProofTactic { // private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala b/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala index 95c0ae80..723fcd39 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala @@ -108,11 +108,11 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque // 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(f: Expression, add: Expression => Unit): Unit = f match { + 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 f != top && f != bot => add(f) - case _ => throw new Exception("Unreachable case in findBestAtom") + 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) @@ -124,11 +124,12 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque // 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 bestAtom = findBestAtom(s.formula) val redF = reducedForm(s.formula) if (redF == top()) { List(RestateTrue(s.decisions._1 ++ s.decisions._2.map((f: Expression) => neg(f)) |- s.formula)) - } else if (bestAtom.isEmpty) { + } 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) @@ -169,29 +170,31 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) - private def findSubformula2(f: Expression, x: Variable, e: Expression, fv: Set[Variable]): (Expression, Boolean) = { - if (isSame(f, e)) (x, true) + private def findSubformula2(outer: Expression, x: Variable, e: Expression, fv: Set[Variable]): (Expression, Boolean) = { + if (isSame(outer, e)) (x, true) else - f match { + 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 (f, false) + else (outer, false) case Lambda(v, inner) => if (!fv.contains(v)) { val induct = findSubformula2(inner, x, e, fv) - if (!induct._2) (f, false) + if (!induct._2) (outer, false) else (Lambda(v, induct._1), true) } else { - val newv = Variable(freshId((f.freeVariables ++ fv).map(_.id), v.id), v.sort) + 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) (f, false) + if (!induct._2) (outer, false) else (Lambda(newv, induct._1), true) } - case _ => (f, false) + 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] = { diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala index 718f66d4..c94fadec 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -8,6 +8,8 @@ import lisa.utils.K.repr */ object Quantifiers extends lisa.Main { + private val X = variable[Formula] + private val Y = variable[Formula] private val x = variable[Term] private val y = variable[Term] private val z = variable[Term] @@ -63,9 +65,18 @@ object Quantifiers extends lisa.Main { 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 + thenHave(lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P) |- ∃(x, P(x))) by Beta + thenHave(( + lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P) <=> ∃!(x, P(x)), + ∃!(x, P(x)) + ) + |- ∃(x, P(x))) by LeftSubstEq + .withParameters(List((∃!(x, P(x)), lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P))), (Seq(X), X)) + have(thesis) by Tautology.from(lastStep, existsOne.definition) } + assert(false) + /** * Theorem --- Equality relation is transitive. */ diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index 01d4897a..ec0d710f 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -1154,7 +1154,7 @@ object BasicStepTactic { vars.foldLeft(base : K.Expression) { case (acc, s_arg) => K.forall(s_arg, acc) } } - if (K.isSameSet(botK.right, premiseSequent.right)) + 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) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala index f9379538..f50a6e7d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/ProofsHelpers.scala @@ -231,9 +231,9 @@ trait ProofsHelpers { val right = expr#@@(vars) val statement = if appliedCst.sort == K.Term then - () |- iff.#@(appliedCst).#@(right).asInstanceOf[Formula] + () |- (equality #@ appliedCst #@ right).asInstanceOf[Formula] else - () |- equality.#@(appliedCst).#@(right).asInstanceOf[Formula] + () |- (iff #@ appliedCst #@ right).asInstanceOf[Formula] library.last = Some(this) } 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 d6b2e58e..61ee6400 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -214,6 +214,7 @@ object UnificationUtils: * Reduce this rewrite rule to a formula representing the equivalence. */ def toFormula: Formula + case class TermRewriteRule(l: Term, r: Term) extends RewriteRule: type Base = T From fd7e312cb3352855de7b93e9c2c32e75c53ea9c6 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Sun, 27 Oct 2024 01:14:59 +0200 Subject: [PATCH 57/92] add eta-reduction, corect autologies and definitions. --- .../lisa/kernel/exfol/CommonDefinitions.scala | 57 -- .../kernel/exfol/EquivalenceChecker.scala | 810 ------------------ .../main/scala/lisa/kernel/exfol/FOL.scala | 10 - .../kernel/exfol/FormulaDefinitions.scala | 138 --- .../exfol/FormulaLabelDefinitions.scala | 112 --- .../lisa/kernel/exfol/Substitutions.scala | 244 ------ .../lisa/kernel/exfol/TermDefinitions.scala | 84 -- .../kernel/exfol/TermLabelDefinitions.scala | 52 -- .../scala/lisa/kernel/exproof/Judgement.scala | 76 -- .../lisa/kernel/exproof/RunningTheory.scala | 379 -------- .../scala/lisa/kernel/exproof/SCProof.scala | 97 --- .../lisa/kernel/exproof/SCProofChecker.scala | 567 ------------ .../lisa/kernel/exproof/SequentCalculus.scala | 348 -------- .../main/scala/lisa/kernel/fol/Syntax.scala | 1 + .../lisa/kernel/proof/RunningTheory.scala | 8 +- .../main/scala/lisa/maths/Quantifiers.scala | 13 +- 16 files changed, 12 insertions(+), 2984 deletions(-) delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala delete mode 100644 lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala deleted file mode 100644 index 679e7ec4..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/CommonDefinitions.scala +++ /dev/null @@ -1,57 +0,0 @@ -package lisa.kernel.exfol - -/** - * Definitions that are common to terms and formulas. - */ -private[exfol] 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/exfol/EquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala deleted file mode 100644 index 20d80c24..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/EquivalenceChecker.scala +++ /dev/null @@ -1,810 +0,0 @@ -package lisa.kernel.exfol - -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[exfol] 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/exfol/FOL.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala deleted file mode 100644 index 7a0cc33c..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/FOL.scala +++ /dev/null @@ -1,10 +0,0 @@ -package lisa.kernel.exfol - -/** - * The concrete implementation of first order logic. - * All its content can be imported using a single statement: - *
- * import lisa.fol.FOL._
- * 
- */ -object FOL extends FormulaDefinitions with EquivalenceChecker with Substitutions {} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala deleted file mode 100644 index 07c48a0d..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaDefinitions.scala +++ /dev/null @@ -1,138 +0,0 @@ -package lisa.kernel.exfol - -/** - * Definitions of formulas; analogous to [[TermDefinitions]]. - * Depends on [[FormulaLabelDefinitions]] and [[TermDefinitions]]. - */ -private[exfol] 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[exfol] 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/exfol/FormulaLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala deleted file mode 100644 index 59b38c2f..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/FormulaLabelDefinitions.scala +++ /dev/null @@ -1,112 +0,0 @@ -package lisa.kernel.exfol - -/** - * Definitions of formula labels. Analogous to [[TermLabelDefinitions]]. - */ -private[exfol] 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/exfol/Substitutions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala deleted file mode 100644 index 278682ab..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/Substitutions.scala +++ /dev/null @@ -1,244 +0,0 @@ -package lisa.kernel.exfol - -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/exfol/TermDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala deleted file mode 100644 index bf25f09f..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermDefinitions.scala +++ /dev/null @@ -1,84 +0,0 @@ -package lisa.kernel.exfol - -/** - * Definitions of terms; depends on [[TermLabelDefinitions]]. - */ -private[exfol] 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/exfol/TermLabelDefinitions.scala b/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala deleted file mode 100644 index 209820ce..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exfol/TermLabelDefinitions.scala +++ /dev/null @@ -1,52 +0,0 @@ -package lisa.kernel.exfol - -/** - * Definitions of term labels. - */ -private[exfol] 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/exproof/Judgement.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala deleted file mode 100644 index f7e774c6..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exproof/Judgement.scala +++ /dev/null @@ -1,76 +0,0 @@ -package lisa.kernel.exproof - -import lisa.kernel.exproof.RunningTheory - -/** - * The judgement (or verdict) of a proof checking procedure. - * Typically, see [[SCProofChecker.checkSingleSCStep]] and [[SCProofChecker.checkSCProof]]. - */ -sealed abstract class SCProofCheckerJudgement { - import SCProofCheckerJudgement._ - val proof: SCProof - - /** - * Whether this judgement is positive -- the proof is concluded to be valid; - * or negative -- the proof checker couldn't certify the validity of this proof. - * @return An instance of either [[SCValidProof]] or [[SCInvalidProof]] - */ - def isValid: Boolean = this match { - case _: SCValidProof => true - case _: SCInvalidProof => false - } -} - -object SCProofCheckerJudgement { - - /** - * A positive judgement. - */ - case class SCValidProof(proof: SCProof, val usesSorry: Boolean = false) extends SCProofCheckerJudgement - - /** - * A negative judgement. - * @param path The path of the error, expressed as indices - * @param message The error message that hints about the first error encountered - */ - case class SCInvalidProof(proof: SCProof, path: Seq[Int], message: String) extends SCProofCheckerJudgement -} - -/** - * The judgement (or verdict) of a running theory. - */ -sealed abstract class RunningTheoryJudgement[+J <: RunningTheory#Justification] { - import RunningTheoryJudgement._ - - /** - * Whether this judgement is positive -- the justification could be imported into the running theory; - * or negative -- the justification is not suitable to be imported in the theory. - * @return An instance of either [[ValidJustification]] or [[InvalidJustification]] - */ - def isValid: Boolean = this match { - case _: ValidJustification[_] => true - case _: InvalidJustification[_] => false - } - def get: J = this match { - case ValidJustification(just) => just - case InvalidJustification(message, error) => - throw InvalidJustificationException(message, error) - } -} - -object RunningTheoryJudgement { - - /** - * A positive judgement. - */ - case class ValidJustification[J <: RunningTheory#Justification](just: J) extends RunningTheoryJudgement[J] - - /** - * A negative judgement. - * @param error If the justification is rejected because the proof is wrong, will contain the error in the proof. - * @param message The error message that hints about the first error encountered - */ - case class InvalidJustification[J <: RunningTheory#Justification](message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends RunningTheoryJudgement[J] - - case class InvalidJustificationException(message: String, error: Option[SCProofCheckerJudgement.SCInvalidProof]) extends Exception(message) -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala deleted file mode 100644 index dc35bfaf..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exproof/RunningTheory.scala +++ /dev/null @@ -1,379 +0,0 @@ -package lisa.kernel.exproof - -import lisa.kernel.exfol.FOL._ -import lisa.kernel.exproof.RunningTheoryJudgement._ -import lisa.kernel.exproof.SequentCalculus._ - -import scala.collection.immutable.Set -import scala.collection.mutable.{Map => mMap} - -/** - * This class describes the theory, i.e. the context and language, in which theorems are proven. - * A theory is built from scratch by introducing axioms and symbols first, then by definitional extensions. - * The structure is one-way mutable: Once an axiom or definition has been introduced, it can't be removed. - * On the other hand, theorems proven before the theory is extended will still hold. - * A theorem only holds true within a specific theory. - * A theory is responsible to make sure that a symbol already defined or present in the language can't - * be redefined. If a theory needs to be extanded in two different ways, or if a theory and its extension need - * to coexist independently, they should be different instances of this class. - */ -class RunningTheory { - - /** - * A Justification is either a Theorem, an Axiom or a Definition - */ - sealed abstract class Justification - - /** - * A theorem encapsulate a sequent and assert that this sequent has been correctly proven and may be used safely in further proofs. - */ - sealed case class Theorem private[RunningTheory] (name: String, proposition: Sequent, withSorry: Boolean) extends Justification - - /** - * An axiom is any formula that is assumed and considered true within the theory. It can freely be used later in any proof. - */ - sealed case class Axiom private[RunningTheory] (name: String, ax: Formula) extends Justification - - /** - * A definition can be either a PredicateDefinition or a FunctionDefinition. - */ - sealed abstract class Definition extends Justification - - /** - * Define a predicate symbol as a shortcut for a formula. Example : P(x,y) := ∃!z. (x=y+z) - * - * @param label The name and arity of the new symbol - * @param 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 - - private[exproof] val theoryAxioms: mMap[String, Axiom] = mMap.empty - private[exproof] val theorems: mMap[String, Theorem] = mMap.empty - - private[exproof] val funDefinitions: mMap[ConstantFunctionLabel, Option[FunctionDefinition]] = mMap.empty - private[exproof] val predDefinitions: mMap[ConstantAtomicLabel, Option[PredicateDefinition]] = mMap(equality -> None, top -> None, bot -> None) - - private[exproof] val knownSymbols: mMap[Identifier, ConstantLabel] = mMap(equality.id -> equality) - - /** - * From a given proof, if it is true in the Running theory, add that theorem to the theory and returns it. - * The proof's imports must be justified by the list of justification, and the conclusion of the theorem - * can't contain symbols that do not belong to the theory. - * - * @param justifications The list of justifications of the proof's imports. - * @param proof The proof of the desired Theorem. - * @return A Theorem if the proof is correct, None else - */ - def makeTheorem(name: String, statement: Sequent, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = { - if (proof.conclusion == statement) proofToTheorem(name, proof, justifications) - else InvalidJustification("The proof does not prove the claimed statement", None) - } - - private def proofToTheorem(name: String, proof: SCProof, justifications: Seq[Justification]): RunningTheoryJudgement[this.Theorem] = - if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) - if (belongsToTheory(proof.conclusion)) { - val r = SCProofChecker.checkSCProof(proof) - r match { - case SCProofCheckerJudgement.SCValidProof(_, sorry) => - 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 - } - }) - val thm = Theorem(name, proof.conclusion, usesSorry) - theorems.update(name, thm) - ValidJustification(thm) - case r @ SCProofCheckerJudgement.SCInvalidProof(_, _, message) => - InvalidJustification("The given proof is incorrect: " + message, Some(r)) - } - } 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) - } - - /** - * Introduce a new definition of a function in the theory. The symbol must not already exist in the theory - * and the formula can't contain symbols that are not in the theory. The existence and uniqueness of an element - * satisfying the definition's formula must first be proven. This is easy if the formula behaves as a shortcut, - * for example f(x,y) = 3x+2y - * but is much more general. The proof's conclusion must be of the form: |- ∀args. ∃!out. phi - * - * @param proof The proof of existence and uniqueness - * @param justifications The justifications of the proof. - * @param label The desired label. - * @param expression The functional term defining the function symbol. - * @param out The variable representing the function's result in the formula - * @param proven A formula possibly stronger than `expression` that the proof proves. It is always correct if it is the same as "expression", but - * if `expression` is less strong, this allows to make underspecified definitions. - * @return A definition object if the parameters are correct, - */ - def makeFunctionDefinition( - proof: SCProof, - justifications: Seq[Justification], - label: ConstantFunctionLabel, - out: VariableLabel, - expression: LambdaTermFormula, - proven: Formula - ): RunningTheoryJudgement[this.FunctionDefinition] = { - val LambdaTermFormula(vars, body) = expression - if (vars.length == label.arity) { - if (belongsToTheory(body)) { - if (isAvailable(label)) { - if (body.freeSchematicTermLabels.subsetOf((vars appended out).toSet) && body.schematicFormulaLabels.isEmpty) { - if (proof.imports.forall(i => justifications.exists(j => isSameSequent(i, sequentFromJustification(j))))) { - val r = SCProofChecker.checkSCProof(proof) - r match { - case SCProofCheckerJudgement.SCValidProof(_, sorry) => - proof.conclusion match { - case Sequent(l, r) if l.isEmpty && r.size == 1 => - if (isImplying(proven, body)) { - val subst = BinderFormula(ExistsOne, out, proven) - if (isSame(r.head, subst)) { - 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 - } - }) - val newDef = FunctionDefinition(label, out, expression, usesSorry) - funDefinitions.update(label, Some(newDef)) - knownSymbols.update(label.id, label) - RunningTheoryJudgement.ValidJustification(newDef) - } else InvalidJustification("The proof is correct but its conclusion does not correspond to the claimed proven property.", None) - } else InvalidJustification("The proven property must be at least as strong as the desired definition, and it is not.", None) - - case _ => InvalidJustification("The conclusion of the proof must have an empty left hand side, and a single formula on the right hand side.", None) - } - case r @ SCProofCheckerJudgement.SCInvalidProof(_, path, message) => InvalidJustification("The given proof is incorrect: " + message, Some(r)) - } - } else InvalidJustification("Not all imports of the proof are correctly justified.", None) - } 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) - } 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)) - - } - - /** - * Add a new axiom to the Theory. For example, if the theory contains the language and theorems - * of Zermelo-Fraenkel Set Theory, this function may add the axiom of choice to it. - * If the axiom belongs to the language of the theory, adds it and return true. Else, returns false. - * - * @param f the new axiom to be added. - * @return true if the axiom was added to the theory, false else. - */ - def addAxiom(name: String, f: Formula): Option[Axiom] = { - if (belongsToTheory(f)) { - val ax = Axiom(name, f) - theoryAxioms.update(name, ax) - Some(ax) - } else None - } - - /** - * Add a new symbol to the theory, without providing a definition. An ad-hoc definition can be - * added via an axiom, typically if the desired object is not derivable in the base theory itself. - * For example, This function can add the empty set symbol to a theory, and then an axiom asserting - * 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) - } - } 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) - } - - /** - * Add all constant symbols in the sequent. Note that this can't be reversed and will prevent from giving them a definition later. - */ - def makeSequentBelongToTheory(s: Sequent): Unit = { - s.left.foreach(makeFormulaBelongToTheory) - s.right.foreach(makeFormulaBelongToTheory) - } - - /** - * 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. - * - * @param t The term 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) - } - - } - - /** - * Verify if a given sequent belongs to the language of the theory. - * - * @param s The sequent to check - * @return Weather s belongs to the specified language - */ - def belongsToTheory(s: Sequent): Boolean = - s.left.forall(belongsToTheory) && s.right.forall(belongsToTheory) - - /** - * Public accessor to the set of symbol currently in the theory's language. - * - * @return the set of symbol currently in the theory's language. - */ - def language(): List[(ConstantLabel, Option[Definition])] = funDefinitions.toList ++ predDefinitions.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) - } - - /** - * Check if a label is not already used in the theory. - * @return - */ - def isAvailable(label: ConstantLabel): Boolean = !knownSymbols.contains(label.id) - - /** - * Public accessor to the current set of axioms of the theory - * - * @return the current set of axioms of the theory - */ - def axiomsList(): Iterable[Axiom] = theoryAxioms.values - - /** - * Verify if a given formula is an axiom of the theory - */ - def isAxiom(f: Formula): Boolean = 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 - - /** - * Get the definition of the given label, if it is defined in the theory. - */ - def getDefinition(label: ConstantFunctionLabel): Option[FunctionDefinition] = funDefinitions.get(label).flatten - - /** - * Get the Axiom with the given name, if it exists in the theory. - */ - def getAxiom(name: String): Option[Axiom] = theoryAxioms.get(name) - - /** - * Get the Theorem with the given name, if it exists in the theory. - */ - def getTheorem(name: String): Option[Theorem] = theorems.get(name) - - /** - * 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) - } - -} -object RunningTheory { - - /** - * An empty theory suitable to reason about first order logic. - */ - def PredicateLogic: RunningTheory = new RunningTheory() -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala deleted file mode 100644 index 21846db3..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProof.scala +++ /dev/null @@ -1,97 +0,0 @@ -package lisa.kernel.exproof - -import lisa.kernel.exproof.SequentCalculus._ - -/** - * A SCPRoof (for Sequent Calculus Proof) is a (dependant) proof. While technically a proof is an Directed Acyclic Graph, - * here proofs are linearized and represented as a list of proof steps. - * Moreover, a proof can depend on some assumed, unproved, sequents specified in the second argument - * @param steps A list of Proof Steps that should form a valid proof. Each individual step should only refer to earlier - * proof steps as premisces. - * @param imports A list of assumed sequents that further steps may refer to. Imports are refered to using negative integers - * To refer to the first sequent of imports, use integer -1. - */ -case class SCProof(steps: IndexedSeq[SCProofStep], imports: IndexedSeq[Sequent] = IndexedSeq.empty) { - def numberedSteps: Seq[(SCProofStep, Int)] = steps.zipWithIndex - - /** - * Fetches the ith step of the proof. - * @param i the index - * @return a step - */ - def apply(i: Int): SCProofStep = { - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(i) - else throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - } - - /** - * Get the ith sequent of the proof. If the index is positive, give the bottom sequent of proof step number i. - * If the index is negative, return the (-i-1)th imported sequent. - * - * @param i The reference number of a sequent in the proof - * @return A sequent, either imported or reached during the proof. - */ - def getSequent(i: Int): Sequent = { - if (i >= 0) - if (i >= steps.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the steps Seq") - else steps(i).bot - else { - val i2 = -(i + 1) - if (i2 >= imports.length) throw new IndexOutOfBoundsException(s"index $i is out of bounds of the imports Seq") - else imports(i2) - } - } - - /** - * The length of the proof in terms of top-level steps, without including the imports. - */ - def length: Int = steps.length - - /** - * The total length of the proof in terms of proof-step, including steps in subproof, but excluding the imports. - */ - def totalLength: Int = steps.foldLeft(0)((i, s) => - i + (s match { - case s: SCSubproof => s.sp.totalLength + 1 - case _ => 1 - }) - ) - - /** - * The conclusion of the proof, namely the bottom sequent of the last proof step. - * Can be undefined if the proof is empty. - */ - def conclusion: Sequent = { - if (steps.isEmpty && imports.isEmpty) throw new NoSuchElementException("conclusion of an empty proof") - this.getSequent(length - 1) - } - - /** - * A helper method that creates a new proof with a new step appended at the end. - * @param newStep the new step to be added - * @return a new proof - */ - def appended(newStep: SCProofStep): SCProof = copy(steps = steps appended newStep) - - /** - * A helper method that creates a new proof with a sequence of new steps appended at the end. - * @param newSteps the sequence of steps to be added - * @return a new proof - */ - def withNewSteps(newSteps: IndexedSeq[SCProofStep]): SCProof = copy(steps = steps ++ newSteps) -} - -object SCProof { - - /** - * Instantiates a proof from an indexed list of proof steps. - * @param steps the steps of the proof - * @return the corresponding proof - */ - def apply(steps: SCProofStep*): SCProof = { - SCProof(steps.toIndexedSeq) - } - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala deleted file mode 100644 index 1dcb768a..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SCProofChecker.scala +++ /dev/null @@ -1,567 +0,0 @@ -package lisa.kernel.exproof - -import lisa.kernel.exfol.FOL._ -import lisa.kernel.exproof.SCProofCheckerJudgement._ -import lisa.kernel.exproof.SequentCalculus._ - -object SCProofChecker { - - /** - * This function verifies that a single SCProofStep is correctly applied. It verifies that the step only refers to sequents with a lower number, - * and that the type, premises and parameters of the proof step correspond to the claimed conclusion. - * - * @param no The number of the given proof step. Needed to vewrify that the proof step doesn't refer to posterior sequents. - * @param step The proof step whose correctness needs to be checked - * @param references A function that associates sequents to a range of positive and negative integers that the proof step may refer to. Typically, - * a proof's [[SCProof.getSequent]] function. - * @return A Judgement about the correctness of the proof step. - */ - def checkSingleSCStep(no: Int, step: SCProofStep, references: Int => Sequent, importsSize: Int): SCProofCheckerJudgement = { - val ref = references - val false_premise = step.premises.find(i => i >= no) - val false_premise2 = step.premises.find(i => i < -importsSize) - - val r: SCProofCheckerJudgement = - if (false_premise.nonEmpty) - SCInvalidProof(SCProof(step), Nil, s"Step no $no can't refer to higher number ${false_premise.get} as a premise.") - else if (false_premise2.nonEmpty) - SCInvalidProof(SCProof(step), Nil, s"A step can't refer to step ${false_premise2.get}, imports only contains ${importsSize} elements.") - else - step match { - /* - * Γ |- Δ - * ------------ - * Γ |- Δ - */ - case Restate(s, t1) => - if (isSameSequent(ref(t1), s)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The premise does not trivially imply the conclusion.") - - /* - * - * ------------ - * Γ |- Γ - */ - case RestateTrue(s) => - val truth = Sequent(Set(), Set(AtomicFormula(top, Nil))) - if (isSameSequent(s, truth)) SCValidProof(SCProof(step)) else SCInvalidProof(SCProof(step), Nil, s"The desired conclusion is not a trivial tautology") - /* - * - * -------------- - * Γ, φ |- φ, Δ - */ - case Hypothesis(Sequent(left, right), phi) => - 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 (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)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of first premise does not contain φ as claimed.") - else SCInvalidProof(SCProof(step), Nil, s"Left-hand side of second premise does not contain φ as claimed.") - else SCInvalidProof(SCProof(step), Nil, s"Right-hand side of conclusion + φ is not 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.") - - // Left rules - /* - * Γ, φ |- Δ Γ, φ, ψ |- Δ - * -------------- or ------------- - * Γ, φ∧ψ |- Δ Γ, φ∧ψ |- Δ - */ - case LeftAnd(b, t1, phi, psi) => - if (isSameSet(ref(t1).right, b.right)) { - val phiAndPsi = ConnectorFormula(And, Seq(phi, psi)) - if ( - isSameSet(b.left + phi, ref(t1).left + phiAndPsi) || - isSameSet(b.left + psi, ref(t1).left + phiAndPsi) || - isSameSet(b.left + phi + psi, ref(t1).left + phiAndPsi) - ) - 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 the premise and the conclusion must be the same.") - /* - * Γ, φ |- Δ Σ, ψ |- Π - * ------------------------ - * Γ, Σ, φ∨ψ |- Δ, Π - */ - case LeftOr(b, t, disjuncts) => - if (isSameSet(b.right, t.map(ref(_).right).fold(Set.empty)(_ union _))) { - val phiOrPsi = ConnectorFormula(Or, disjuncts) - 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.") - /* - * Γ |- φ, Δ Σ, ψ |- Π - * ------------------------ - * Γ, Σ, φ⇒ψ |- Δ, Π - */ - 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.") - /* - * Γ, φ⇒ψ |- Δ Γ, φ⇒ψ, ψ⇒φ |- Δ - * -------------- 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.") - - /* - * Γ |- φ, Δ - * -------------- - * Γ, ¬φ |- Δ - */ - 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 + ¬φ") - - /* - * Γ, φ[t/x] |- Δ - * ------------------- - * Γ, ∀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))) - 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") - - /* - * Γ, φ |- Δ - * ------------------- if x is not free in the resulting sequent - * Γ, ∃x. φ|- Δ - */ - case LeftExists(b, t1, phi, x) => - if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + phi, ref(t1).left + BinderFormula(Exists, x, phi))) - if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) - 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 - /* - * Γ |- φ, Δ Σ |- ψ, Π - * ------------------------ - * Γ, Σ |- φ∧ψ, Π, Δ - */ - 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.") - /* - * Γ |- φ, Δ Γ |- φ, ψ, Δ - * -------------- 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.") - /* - * Γ, φ |- ψ, Δ - * -------------- - * Γ |- φ⇒ψ, Δ - */ - 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.") - /* - * Γ |- φ⇒ψ, Δ Σ |- ψ⇒φ, Π - * ---------------------------- - * Γ, Σ |- φ⇔ψ, Π, Δ - */ - 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.") - /* - * Γ, φ |- Δ - * -------------- - * Γ |- ¬φ, Δ - */ - 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 x is not free in the resulting sequent - * Γ |- ∀x. φ, Δ - */ - case RightForall(b, t1, phi, x) => - if (isSameSet(b.left, ref(t1).left)) - if (isSameSet(b.right + phi, ref(t1).right + BinderFormula(Forall, x, phi))) - if ((b.left union b.right).forall(f => !f.freeVariables.contains(x))) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "The variable x must not be free in the resulting sequent.") - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of conclusion + φ 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.") - /* - * Γ |- φ[t/x], Δ - * ------------------- - * Γ |- ∃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))) - 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. φ,  Δ
-           * 
- */ - 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))) - 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") - - // Structural rules - /* - * Γ |- Δ - * -------------- - * Γ, Σ |- Δ - */ - case Weakening(b, t1) => - if (isImplyingSequent(ref(t1), b)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Conclusion cannot be trivially derived from premise.") - - // Equality Rules - /* - * Γ, s=s |- Δ - * -------------- - * Γ |- Δ - */ - case LeftRefl(b, t1, phi) => - phi match { - case AtomicFormula(`equality`, Seq(left, right)) => - if (isSameTerm(left, right)) - if (isSameSet(b.right, ref(t1).right)) - if (isSameSet(b.left + phi, ref(t1).left)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Left-hand sides of the conclusion + φ must be the same as left-hand side of the premise.") - else SCInvalidProof(SCProof(step), Nil, s"Right-hand sides of the premise and the conclusion aren't the same.") - else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") - case _ => SCInvalidProof(SCProof(step), Nil, "φ is not an equality") - } - - /* - * - * -------------- - * |- s=s - */ - case RightRefl(b, phi) => - phi match { - case AtomicFormula(`equality`, Seq(left, right)) => - if (isSameTerm(left, right)) - if (contains(b.right, phi)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, s"Right-Hand side of conclusion does not contain φ") - else SCInvalidProof(SCProof(step), Nil, s"φ is not an instance of reflexivity.") - case _ => SCInvalidProof(SCProof(step), Nil, s"φ is not an equality.") - } - - /* - * Γ, φ(s_) |- Δ - * --------------------- - * Γ, (s=t)_, φ(t_)|- Δ - */ - 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 }) - 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 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) } - } - - if (isSameSet(b.right, ref(t1).right)) - if ( - isSameSet(b.left + phi_t_for_f, ref(t1).left ++ sEqT_es + phi_s_for_f) || - isSameSet(b.left + phi_s_for_f, ref(t1).left ++ sEqT_es + phi_t_for_f) - ) - 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_) (or with s_ and t_ swapped)." - ) - else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") - } - - /* - * Γ |- φ(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. - 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_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 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) } - } - - if (isSameSet(ref(t1).left ++ sEqT_es, b.left)) - 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) - ) - 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 φ(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.") - } - - /* - * Γ, φ(ψ_) |- Δ - * --------------------- - * Γ, ψ⇔τ, φ(τ) |- Δ - */ - 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.") - 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) } - } - - 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)." - ) - else SCInvalidProof(SCProof(step), Nil, "Right-hand sides of the premise and the conclusion aren't the same.") - } - - /* - * Γ |- φ[ψ/?p], Δ - * --------------------- - * Γ, ψ⇔τ |- φ[τ/?p], Δ - */ - 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) } - } - - if (isSameSet(ref(t1).left ++ psiIffTau, b.left)) - if ( - isSameSet(b.right + phi_tau_for_q, ref(t1).right + phi_psi_for_q) || - isSameSet(b.right + phi_psi_for_q, ref(t1).right + phi_tau_for_q) - ) - 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.") - } - - - - /** - *
-           * Γ |- Δ
-           * --------------------------
-           * Γ[ψ/?p] |- Δ[ψ/?p]
-           * 
- */ - case InstSchema(bot, t1, mCon, mPred, mTerm) => - val expected = - (ref(t1).left.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm)), ref(t1).right.map(phi => instantiateSchemas(phi, mCon, mPred, mTerm))) - if (isSameSet(bot.left, expected._1)) - if (isSameSet(bot.right, expected._2)) - SCValidProof(SCProof(step)) - else SCInvalidProof(SCProof(step), Nil, "Right-hand side of premise instantiated with the given maps must be the same as right-hand side of conclusion.") - else SCInvalidProof(SCProof(step), Nil, "Left-hand side of premise instantiated with the given maps must be the same as left-hand side of conclusion.") - - case SCSubproof(sp, premises) => - if (premises.size == sp.imports.size) { - val invalid = premises.zipWithIndex.find { case (no, p) => !isSameSequent(ref(no), sp.imports(p)) } - if (invalid.isEmpty) { - checkSCProof(sp) - } else - SCInvalidProof( - SCProof(step), - Nil, - s"Premise number ${invalid.get._1} (refering to step ${invalid.get}) is not the same as import number ${invalid.get._1} of the subproof." - ) - } else SCInvalidProof(SCProof(step), Nil, "Number of premises and imports don't match: " + premises.size + " " + sp.imports.size) - - /* - * - * -------------- - * |- s=s - */ - case Sorry(b) => - SCValidProof(SCProof(step), usesSorry = true) - - } - r - } - - /** - * Verifies if a given pure SequentCalculus is conditionally correct, as the imported sequents are assumed. - * If the proof is not correct, the function will report the faulty line and a brief explanation. - * - * @param proof A SC proof to check - * @return SCValidProof(SCProof(step)) if the proof is correct, else SCInvalidProof with the path to the incorrect proof step - * and an explanation. - */ - def checkSCProof(proof: SCProof): SCProofCheckerJudgement = { - var isSorry = false - val possibleError = proof.steps.view.zipWithIndex - .map { case (step, no) => - checkSingleSCStep(no, step, (i: Int) => proof.getSequent(i), proof.imports.size) match { - case SCInvalidProof(_, path, message) => SCInvalidProof(proof, no +: path, message) - case SCValidProof(_, sorry) => - isSorry = isSorry || sorry - SCValidProof(proof, sorry) - } - } - .find(j => !j.isValid) - if (possibleError.isEmpty) SCValidProof(proof, isSorry) - else possibleError.get - } - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala b/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala deleted file mode 100644 index ef9cbd9b..00000000 --- a/lisa-kernel/src/main/scala/lisa/kernel/exproof/SequentCalculus.scala +++ /dev/null @@ -1,348 +0,0 @@ -package lisa.kernel.exproof - -import lisa.kernel.exfol.FOL._ - -/** - * The concrete implementation of sequent calculus (with equality). - * This file specifies the sequents and the allowed operations on them, the deduction rules of sequent calculus. - * It contains typical sequent calculus rules for FOL with equality as can be found in a text book, as well as a couple more for - * non-elementary symbols (⇔, ∃!) and rules for substituting equal terms or equivalent formulas. I also contains two structural rules, - * subproof and a dummy rewrite step. - * Further mathematical steps, such as introducing or using definitions, axioms or theorems are not part of the basic sequent calculus. - */ -object SequentCalculus { - - /** - * A sequent is an object that can contain two sets of formulas, [[left]] and [[right]]. - * The intended semantic is for the [[left]] formulas to be interpreted as a conjunction, while the [[right]] ones as a disjunction. - * Traditionally, sequents are represented by two lists of formulas. - * Since sequent calculus includes rules for permuting and weakening, it is in essence equivalent to sets. - * Seqs make verifying proof steps much easier, but proof construction much more verbose and proofs longer. - * @param left the left side of the sequent - * @param right the right side of the sequent - */ - case class Sequent(left: Set[Formula], right: Set[Formula]) - - /** - * Simple method that transforms a sequent to a logically equivalent formula. - */ - def sequentToFormula(s: Sequent): Formula = ConnectorFormula(Implies, List(ConnectorFormula(And, s.left.toSeq), ConnectorFormula(Or, s.right.toSeq))) - - /** - * Checks whether two sequents are equivalent, with respect to [[isSameTerm]]. - * - * @param l the first sequent - * @param r the second sequent - * @return see [[isSameTerm]] - */ - def isSameSequent(l: Sequent, r: Sequent): Boolean = isSame(sequentToFormula(l), sequentToFormula(r)) - - /** - * Checks whether a given sequent implies another, with respect to [[latticeLEQ]]. - * - * @param l the first sequent - * @param r the second sequent - * @return see [[latticeLEQ]] - */ - def isImplyingSequent(l: Sequent, r: Sequent): Boolean = isImplying(sequentToFormula(l), sequentToFormula(r)) - - /** - * The parent of all proof steps types. - * A proof step is a deduction rule of sequent calculus, with the sequents forming the prerequisite and conclusion. - * For easier linearisation of the proof, the prerequisite are represented with numbers showing the place in the proof of the sequent used. - */ - - /** - * The parent of all sequent calculus rules. - */ - sealed trait SCProofStep { - val bot: Sequent - val premises: Seq[Int] - } - - /** - *
-   *    Γ |- Δ
-   * ------------
-   *    Γ |- Δ  (OL rewrite)
-   * 
- */ - case class Restate(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *
-   * ------------
-   *    Γ |- Γ  (OL tautology)
-   * 
- */ - case class RestateTrue(bot: Sequent) extends SCProofStep { val premises = Seq() } - - /** - *
-   *
-   * --------------
-   *   Γ, φ |- φ, Δ
-   * 
- */ - case class Hypothesis(bot: Sequent, phi: Formula) extends SCProofStep { val premises = Seq() } - - /** - *
-   *  Γ |- Δ, φ    φ, Σ |- Π
-   * ------------------------
-   *       Γ, Σ |-Δ, Π
-   * 
- */ - case class Cut(bot: Sequent, t1: Int, t2: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } - - // Left rules - /** - *
-   *   Γ, φ |- Δ                Γ, φ, ψ |- Δ
-   * --------------     or     --------------
-   *  Γ, φ∧ψ |- Δ               Γ, φ∧ψ |- Δ
-   * 
- */ - case class LeftAnd(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ, φ |- Δ    Σ, ψ |- Π    ...
-   * --------------------------------
-   *    Γ, Σ, φ∨ψ∨... |- Δ, Π
-   * 
- */ - case class LeftOr(bot: Sequent, t: Seq[Int], disjuncts: Seq[Formula]) extends SCProofStep { val premises = t } - - /** - *
-   *  Γ |- φ, Δ    Σ, ψ |- Π
-   * ------------------------
-   *    Γ, Σ, φ⇒ψ |- Δ, Π
-   * 
- */ - case class LeftImplies(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } - - /** - *
-   *  Γ, φ⇒ψ |- Δ               Γ, φ⇒ψ, ψ⇒φ |- Δ
-   * --------------    or     --------------------
-   *  Γ, φ⇔ψ |- Δ                 Γ, φ⇔ψ |- Δ
-   * 
- */ - case class LeftIff(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *   Γ |- φ, Δ
-   * --------------
-   *   Γ, ¬φ |- Δ
-   * 
- */ - case class LeftNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *   Γ, φ[t/x] |- Δ
-   * -------------------
-   *  Γ, ∀ φ |- Δ
-   *
-   * 
- */ - case class LeftForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel, t: Term) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *    Γ, φ |- Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ, ∃x φ|- Δ
-   *
-   * 
- */ - 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) } - - // Right rules - /** - *
-   *  Γ |- φ, Δ    Σ |- ψ, Π     ...
-   * ------------------------------------
-   *    Γ, Σ |- φ∧ψ∧..., Π, Δ
-   * 
- */ - case class RightAnd(bot: Sequent, t: Seq[Int], cunjuncts: Seq[Formula]) extends SCProofStep { val premises = t } - - /** - *
-   *   Γ |- φ, Δ                Γ |- φ, ψ, Δ
-   * --------------    or    ---------------
-   *  Γ |- φ∨ψ, Δ              Γ |- φ∨ψ, Δ
-   * 
- */ - case class RightOr(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ, φ |- ψ, Δ
-   * --------------
-   *  Γ |- φ⇒ψ, Δ
-   * 
- */ - case class RightImplies(bot: Sequent, t1: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *  Γ |- a⇒ψ, Δ    Σ |- ψ⇒φ, Π
-   * ----------------------------
-   *      Γ, Σ |- φ⇔ψ, Π, Δ
-   * 
- */ - case class RightIff(bot: Sequent, t1: Int, t2: Int, phi: Formula, psi: Formula) extends SCProofStep { val premises = Seq(t1, t2) } - - /** - *
-   *  Γ, φ |- Δ
-   * --------------
-   *   Γ |- ¬φ, Δ
-   * 
- */ - case class RightNot(bot: Sequent, t1: Int, phi: Formula) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *    Γ |- φ, Δ
-   * ------------------- if x is not free in the resulting sequent
-   *  Γ |- ∀x. φ, Δ
-   * 
- */ - case class RightForall(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *   Γ |- φ[t/x], Δ
-   * -------------------
-   *  Γ |- ∃x. φ, Δ
-   *
-   * (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) } - - /** - *
-   *  Γ |- ∃y.∀x. (x=y) ⇔ φ, Δ
-   * ---------------------------- if y is not free in φ
-   *      Γ|- ∃!x. φ,  Δ
-   * 
- */ - case class RightExistsOne(bot: Sequent, t1: Int, phi: Formula, x: VariableLabel) extends SCProofStep { val premises = Seq(t1) } - - // Structural rule - /** - *
-   *     Γ |- Δ
-   * --------------
-   *   Γ, Σ |- Δ, Π
-   * 
- */ - case class Weakening(bot: Sequent, t1: Int) extends SCProofStep { val premises = Seq(t1) } - - // Equality Rules - /** - *
-   *  Γ, s=s |- Δ
-   * --------------
-   *     Γ |- Δ
-   * 
- */ - case class LeftRefl(bot: Sequent, t1: Int, fa: Formula) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *
-   * --------------
-   *     |- s=s
-   * 
- */ - case class RightRefl(bot: Sequent, fa: Formula) extends SCProofStep { val premises = Seq() } - - /** - *
-   *    Γ, φ(s1,...,sn) |- Δ
-   * ---------------------
-   *  Γ, s1=t1, ..., sn=tn, φ(t1,...tn) |- Δ
-   * 
- */ - case class LeftSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *    Γ |- φ(s1,...,sn), Δ
-   * ---------------------
-   *  Γ, s1=t1, ..., sn=tn |- φ(t1,...tn), Δ
-   * 
- */ - case class RightSubstEq(bot: Sequent, t1: Int, equals: List[(LambdaTermTerm, LambdaTermTerm)], lambdaPhi: (Seq[SchematicTermLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *    Γ, φ(a1,...an) |- Δ
-   * ---------------------
-   *  Γ, a1⇔b1, ..., an⇔bn, φ(b1,...bn) |- Δ
-   * 
- */ - case class LeftSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } - - /** - *
-   *    Γ |- φ(a1,...an), Δ
-   * ---------------------
-   *  Γ, a1⇔b1, ..., an⇔bn |- φ(b1,...bn), Δ
-   * 
- */ - - case class RightSubstIff(bot: Sequent, t1: Int, equals: List[(LambdaTermFormula, LambdaTermFormula)], lambdaPhi: (Seq[SchematicAtomicLabel], Formula)) extends SCProofStep { val premises = Seq(t1) } - - // Rule for schemas - - case class InstSchema( - bot: Sequent, - t1: Int, - mCon: Map[SchematicConnectorLabel, LambdaFormulaFormula], - mPred: Map[SchematicAtomicLabel, LambdaTermFormula], - mTerm: Map[SchematicTermLabel, LambdaTermTerm] - ) extends SCProofStep { val premises = Seq(t1) } - - // Proof Organisation rules - - /** - * Encapsulate a proof into a single step. The imports of the subproof correspond to the premisces of the step. - * @param sp The encapsulated subproof. - * @param premises The indices of steps on the outside proof that are equivalent to the import of the subproof. - * @param display A boolean value indicating whether the subproof needs to be expanded when printed. Should probably go and - * be replaced by encapsulation. - */ - case class SCSubproof(sp: SCProof, premises: Seq[Int] = Seq.empty) extends SCProofStep { - // premises is a list of ints similar to t1, t2... that verifies that imports of the subproof sp are justified by previous steps. - val bot: Sequent = sp.conclusion - } - - /** - *
-   *
-   * --------------
-   *   Γ  |- Δ
-   * 
- */ - case class Sorry(bot: Sequent) extends SCProofStep { val premises = Seq() } - -} diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala index 63f8b2c5..6a431ee3 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -189,6 +189,7 @@ private[fol] trait Syntax { case _ => Application(f1, betaReduce(a2)) } } + case Lambda(v, Application(f, arg)) if v == arg => f case Lambda(v, inner) => Lambda(v, betaReduce(inner)) case _ => e 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 f8e1bbf4..cbd500ca 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/RunningTheory.scala @@ -175,11 +175,13 @@ class RunningTheory { case Theorem(name, proposition, _) => proposition case Axiom(name, ax) => Sequent(Set.empty, Set(ax)) case Definition(cst, e, vars) => - if (cst.sort.isPredicate){ - val inner = iff(vars.foldLeft(cst: Expression)(_(_)))(vars.foldLeft(e)(_(_))) + 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(vars.foldLeft(cst: Expression)(_(_)))(vars.foldLeft(e)(_(_))) + val inner = equality(left)(right) Sequent(Set(), Set(inner)) } } diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala index c94fadec..ef35a226 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -10,6 +10,7 @@ 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] @@ -67,16 +68,14 @@ object Quantifiers extends lisa.Main { thenHave(∃(x, ∀(y, (x === y) <=> P(y))) |- ∃(x, P(x))) by LeftExists thenHave(lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P) |- ∃(x, P(x))) by Beta thenHave(( - lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P) <=> ∃!(x, P(x)), - ∃!(x, P(x)) + lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P) <=> ∃!(P), + ∃!(P) ) - |- ∃(x, P(x))) by LeftSubstEq - .withParameters(List((∃!(x, P(x)), lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P))), (Seq(X), X)) - have(thesis) by Tautology.from(lastStep, existsOne.definition) + |- ∃(x, P(x))) by LeftSubstEq.withParameters(List((∃!(P), lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P))), (Seq(X), X)) + have(∃!(P) |- ∃(x, P(x))) by Tautology.from(lastStep, existsOne.definition) + thenHave(thesis) by Beta } - assert(false) - /** * Theorem --- Equality relation is transitive. */ From 287ec1aa888029460a11a1cf757d3264ecbda8ee Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Sun, 27 Oct 2024 16:46:13 +0100 Subject: [PATCH 58/92] various fixes and improvements --- .../kernel/fol/OLEquivalenceChecker.scala | 20 +++++++++--- .../main/scala/lisa/kernel/fol/Syntax.scala | 31 +++++++++--------- .../lisa/kernel/proof/SCProofChecker.scala | 2 +- .../main/scala/lisa/automation/Tableau.scala | 2 -- .../main/scala/lisa/maths/Quantifiers.scala | 15 ++++----- .../main/scala/lisa/utils/KernelHelpers.scala | 32 +++++++++++++++++++ .../main/scala/lisa/utils/fol/Predef.scala | 8 ++--- .../main/scala/lisa/utils/fol/Syntax.scala | 4 ++- .../lisa/utils/prooflib/BasicStepTactic.scala | 14 ++++---- 9 files changed, 84 insertions(+), 44 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala index ee6f3ae7..36a8ec5b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -7,7 +7,8 @@ private[fol] trait OLEquivalenceChecker extends Syntax { def reducedForm(expr: Expression): Expression = { - val p = simplify(expr) + val bnf = expr.betaNormalForm + val p = simplify(bnf) val nf = computeNormalForm(p) val fln = fromLocallyNameless(nf, Map.empty, 0) val res = toExpressionAIG(fln) @@ -15,6 +16,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { } def reducedNNFForm(expr: Expression): Expression = { + val bnf = expr.betaNormalForm val p = simplify(expr) val nf = computeNormalForm(p) val fln = fromLocallyNameless(nf, Map.empty, 0) @@ -34,8 +36,8 @@ private[fol] trait OLEquivalenceChecker extends Syntax { @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)) - val nf2 = computeNormalForm(simplify(e2)) + val nf1 = computeNormalForm(simplify(e1.betaNormalForm)) + val nf2 = computeNormalForm(simplify(e2.betaNormalForm)) latticesEQ(nf1, nf2) } @@ -45,8 +47,8 @@ private[fol] trait OLEquivalenceChecker extends Syntax { */ def isImplying(e1: Expression, e2: Expression): Boolean = { require(e1.sort == Formula && e2.sort == Formula) - val nf1 = computeNormalForm(simplify(e1)) - val nf2 = computeNormalForm(simplify(e2)) + val nf1 = computeNormalForm(simplify(e1.betaNormalForm)) + val nf2 = computeNormalForm(simplify(e2.betaNormalForm)) latticesLEQ(nf1, nf2) } @@ -271,8 +273,16 @@ private[fol] trait OLEquivalenceChecker extends Syntax { 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) => diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala index 6a431ee3..4eba7dbc 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/Syntax.scala @@ -85,6 +85,22 @@ private[fol] trait Syntax { 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. @@ -180,19 +196,4 @@ private[fol] trait Syntax { case _ => List() } - def betaReduce(e: Expression): Expression = e match { - case Application(f, arg) => { - val f1 = betaReduce(f) - val a2 = betaReduce(arg) - f1 match { - case Lambda(v, body) => betaReduce(substituteVariables(body, Map(v -> a2))) - case _ => Application(f1, betaReduce(a2)) - } - } - case Lambda(v, Application(f, arg)) if v == arg => f - case Lambda(v, inner) => - Lambda(v, betaReduce(inner)) - case _ => e - } - } 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 0b0d23f5..a48a4f04 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -389,7 +389,7 @@ object SCProofChecker { *
*/ case Beta(b, t1) => - if (isSame(betaReduce(sequentToFormula(b)), betaReduce(sequentToFormula(ref(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.") diff --git a/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala b/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala index 62bc4030..894ebc59 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Tableau.scala @@ -64,12 +64,10 @@ object Tableau extends ProofTactic with ProofSequentTactic with ProofFactSequent 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)) diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala index ef35a226..d8f07370 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -66,16 +66,12 @@ object Quantifiers extends lisa.Main { 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(lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P) |- ∃(x, P(x))) by Beta - thenHave(( - lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P) <=> ∃!(P), - ∃!(P) - ) - |- ∃(x, P(x))) by LeftSubstEq.withParameters(List((∃!(P), lambda(P, ∃(x, ∀(y, (x === y) <=> P(y))))(P))), (Seq(X), X)) - have(∃!(P) |- ∃(x, P(x))) by Tautology.from(lastStep, existsOne.definition) - thenHave(thesis) by Beta + 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. */ @@ -188,6 +184,7 @@ object Quantifiers extends lisa.Main { have(thesis) by Tableau } + /* /** * Theorem --- Universal quantification of equivalence implies equivalence * of unique existential quantification. @@ -225,7 +222,6 @@ object Quantifiers extends lisa.Main { 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 } @@ -279,4 +275,5 @@ object Quantifiers extends lisa.Main { have(thesis) by Tautology.from(fwd, bwd) } +*/ } diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 08c2d3b4..c0e9434e 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -174,6 +174,38 @@ object KernelHelpers { case Variable(id, sort) => s"v(${id},${sort})" } + 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 */ /* diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index a9b5a576..86d62bfd 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -45,18 +45,18 @@ trait Predef extends Syntax { val ¬ : neg.type = neg val ! : neg.type = neg - val and = constant[Formula >>: Formula >>: Formula]("∧") + val and = constant[Formula >>: Formula >>: Formula]("∧").setInfix() val /\ : and.type = and val ∧ : and.type = and - val or = constant[Formula >>: Formula >>: Formula]("∨") + val or = constant[Formula >>: Formula >>: Formula]("∨").setInfix() val \/ : or.type = or val ∨ : or.type = or - val implies = constant[Formula >>: Formula >>: Formula]("⇒") + val implies = constant[Formula >>: Formula >>: Formula]("⇒").setInfix() val ==> : implies.type = implies - val iff = constant[Formula >>: Formula >>: Formula]("⇔") + val iff = constant[Formula >>: Formula >>: Formula]("⇔").setInfix() val <=> : iff.type = iff val ⇔ : iff.type = iff diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 21bf1756..05704ffc 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -193,7 +193,9 @@ trait Syntax { 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 setInfix(): Unit = infix = true + def setInfix(): 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] = diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index ec0d710f..091f71b8 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -995,8 +995,8 @@ object BasicStepTactic { 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.betaReduce(K.sequentToFormula(botK)) - val red2 = K.betaReduce(K.sequentToFormula(proof.getSequent(premise).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 @@ -1212,14 +1212,14 @@ object BasicStepTactic { vars.foldLeft(base : K.Expression) { case (acc, s_arg) => K.forall(s_arg, acc) } } - if (K.isSameSet(botK.right, premiseSequent.right ++ sEqT_es)) + if (K.isSameSet(botK.left, premiseSequent.left ++ sEqT_es)) if ( - K.isSameSet(botK.left + phi_t_for_f, premiseSequent.left + phi_s_for_f) || - K.isSameSet(botK.left + phi_s_for_f, premiseSequent.left + phi_t_for_f) + 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.LeftSubstEq(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) + proof.ValidProofTactic(bot, Seq(K.RightSubstEq(botK, -1, equalsK, lambdaPhiK)), Seq(premise)) else - proof.InvalidProofTactic("ight-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.") + 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.") } } From 2f0ae905f8641776332d8b5f3bbaf9669898fd9e Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Sun, 27 Oct 2024 17:28:48 +0100 Subject: [PATCH 59/92] rename setInfix in printInfix --- lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala | 8 ++++---- lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala index 86d62bfd..c967ccbc 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Predef.scala @@ -45,18 +45,18 @@ trait Predef extends Syntax { val ¬ : neg.type = neg val ! : neg.type = neg - val and = constant[Formula >>: Formula >>: Formula]("∧").setInfix() + val and = constant[Formula >>: Formula >>: Formula]("∧").printInfix() val /\ : and.type = and val ∧ : and.type = and - val or = constant[Formula >>: Formula >>: Formula]("∨").setInfix() + val or = constant[Formula >>: Formula >>: Formula]("∨").printInfix() val \/ : or.type = or val ∨ : or.type = or - val implies = constant[Formula >>: Formula >>: Formula]("⇒").setInfix() + val implies = constant[Formula >>: Formula >>: Formula]("⇒").printInfix() val ==> : implies.type = implies - val iff = constant[Formula >>: Formula >>: Formula]("⇔").setInfix() + val iff = constant[Formula >>: Formula >>: Formula]("⇔").printInfix() val <=> : iff.type = iff val ⇔ : iff.type = iff diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 05704ffc..63e28d3b 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -193,7 +193,7 @@ trait Syntax { 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 setInfix(): Constant[S] = + def printInfix(): Constant[S] = infix = true this val underlying: K.Constant = K.Constant(id, sort) From 5cde9a0deb15b5b82de65b03004ae930ad5cff87 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Sun, 27 Oct 2024 22:45:19 +0100 Subject: [PATCH 60/92] more bug fix and improvements --- lisa-sets2/src/main/scala/lisa/maths/Tests.scala | 16 ++++++++++++++++ .../main/scala/lisa/utils/KernelHelpers.scala | 8 +++++++- .../main/scala/lisa/utils/tptp/ProofParser.scala | 9 ++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 lisa-sets2/src/main/scala/lisa/maths/Tests.scala 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 00000000..b4e2d9d0 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/Tests.scala @@ -0,0 +1,16 @@ +package lisa.maths +import lisa.automation.atp.Goeland + +object Tests extends lisa.Main { + draft() + + val x = variable[Term] + val y = variable[Term] + val z = variable[Term] + val P = variable[Term >>: Formula] + + + 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-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index c0e9434e..1e0820b0 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -8,6 +8,7 @@ import lisa.kernel.proof.SequentCalculus.* import lisa.kernel.proof.* 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. */ @@ -101,8 +102,13 @@ object KernelHelpers { 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 - inner(e).map(l => {val rev = l.reverse; (rev.head, rev.tail)}) 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 4fbaf2c4..43d1ad66 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 @@ -107,11 +108,17 @@ object ProofParser { case K.iff(f1, f2) => FOF.BinaryFormula(FOF.<=>, formulaToFOFFormula(f1), formulaToFOFFormula(f2)) case K.forall(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.!, Seq(quoted("X" + v.id)), formulaToFOFFormula(f)) case K.exists(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.?, Seq(quoted("X" + v.id)), formulaToFOFFormula(f)) + case K.forall(p) => + val x = K.freshId(p.freeVariables.map(_.id), "x") + FOF.QuantifiedFormula(FOF.!, Seq(quoted("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(quoted("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") + case _ => throw new Exception("The expression is not purely first order: " + formula) } From 080acc237dff72c30b3037224a230a06afa9cfb8 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Mon, 28 Oct 2024 10:21:38 +0100 Subject: [PATCH 61/92] Fix substitution types --- .../scala/lisa/automation/Substitution.scala | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala index 71fdb391..f413f652 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -19,7 +19,6 @@ import scala.collection.mutable.{Map as MMap} import F.{*, given} object Substitution: - /* /** * Extracts a raw substitution into a `RewriteRule`. @@ -44,14 +43,13 @@ object Substitution: 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 @unchecked => - (source + (erule -> erule.source), ctx.withConfinedRule(l, r).withBound(f.freeVars)) + (source + (erule -> erule.source), ctx.withConfinedRule(erule).withBound(f.freeVars)) case j: lib.JUSTIFICATION => - (source + (erule -> j), ctx.withFreeRule(l, r)) + (source + (erule -> j), ctx.withFreeRule(erule)) case f: proof.Fact @unchecked => - (source + (erule -> f), ctx.withConfinedRule(l, r)) + (source + (erule -> f), ctx.withConfinedRule(erule)) /** * Checks if a raw substitution input can be used as a rewrite rule (is === or @@ -128,7 +126,7 @@ object Substitution: // 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: Set[Formula], target: Set[Formula]): Option[Seq[FormulaRewriteResult]] = base.iterator.map: formula => target.collectFirstDefined: target => rewrite(using ctx)(formula, target) @@ -177,8 +175,8 @@ object Substitution: TacticSubproof: val leftRewrites = leftSubsts.get val rightRewrites = rightSubsts.get - val leftRules = leftRewrites.map(_.rule) - val rightRules = rightRewrites.map(_.rule) + val leftRules = leftRewrites.head.rules + val rightRules = rightRewrites.head.rules // instantiated discharges @@ -194,8 +192,8 @@ object Substitution: 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)) + val leftVars = leftRewrites.head.vars + val leftLambda = andAll(leftRewrites.map(_.lambda)) 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) @@ -206,8 +204,8 @@ object Substitution: 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)) + val rightVars = rightRewrites.head.vars + val rightLambda = orAll(rightRewrites.map(_.lambda)) 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) @@ -231,8 +229,6 @@ object Substitution: end Apply - */ - // object applySubst extends ProofTactic { // private def condflat[T](s: Seq[(T, Boolean)]): (Seq[T], Boolean) = (s.map(_._1), s.exists(_._2)) From d6f61ea1817adfd74e12b3a299eeeb52a45fef11 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Mon, 28 Oct 2024 13:07:29 +0100 Subject: [PATCH 62/92] fix tptp printing and goeland tactic --- .../src/main/scala/lisa/utils/tptp/ProofParser.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 43d1ad66..1c4e5324 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -85,7 +85,7 @@ object ProofParser { def termToFOFTerm(term: K.Expression): FOF.Term = { term match { - case K.Variable(id, K.Term) => FOF.Variable(quoted("X" + id)) + 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)) @@ -106,14 +106,14 @@ object ProofParser { 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(quoted("X" + v.id)), formulaToFOFFormula(f)) - case K.exists(K.Lambda(v, f)) => FOF.QuantifiedFormula(FOF.?, Seq(quoted("X" + v.id)), formulaToFOFFormula(f)) + 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(quoted("X" + x)), formulaToFOFFormula(K.Application(p, K.Variable(x, K.Term)))) + 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(quoted("X" + x)), formulaToFOFFormula(K.Application(p, K.Variable(x, K.Term)))) + 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) => From 40d95bf9858275b51b3a0b6d867a526398344c83 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 29 Oct 2024 12:11:44 +0100 Subject: [PATCH 63/92] Specialize VecSet conversions to Seq and Vector --- lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala b/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala index 73fc5de2..4e74b7a0 100644 --- a/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala +++ b/lisa-utils/src/main/scala/lisa/utils/collection/VecSet.scala @@ -71,3 +71,6 @@ sealed class VecSet[A] private (protected val evec: Vector[A], protected val ese 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 From b17708dbaae3c19068d21e4410fc82d05bbfc491 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 29 Oct 2024 12:12:30 +0100 Subject: [PATCH 64/92] Fix rewriting to carry instantiations around properly --- .../utils/unification/UnificationUtils.scala | 154 +++++++++++++++--- 1 file changed, 133 insertions(+), 21 deletions(-) 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 61ee6400..6587ca42 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -3,14 +3,23 @@ package lisa.utils.unification import lisa.fol.FOL.{_, given} import lisa.prooflib.Library import lisa.prooflib.SimpleDeducedSteps +import lisa.utils.K import lisa.utils.KernelHelpers.freshId 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: + /** + * 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. @@ -57,27 +66,17 @@ object UnificationUtils: */ def withConfinedRule[A](rule: RewriteRule) = this.copy(confinedRules = confinedRules + rule) - - /** - * (Deterministic) variables assigned to rewrite rules. - * - * In place of generic holes in the presence of several rewrite rules, - * their representatives are used. - * - */ - def ruleRepresentatives = - allRules.map(representativeVariable) /** * All rules (free + confined) in this context. */ - def allRules: Seq[RewriteRule] = ??? // freeRules ++ confinedRules + def allRules: Set[RewriteRule] = freeRules ++ confinedRules - protected val representativeVariable = memoized(__representativeVariable) + val representativeVariable = memoized(__representativeVariable) - private def __representativeVariable(rule: RewriteRule): Variable[?] = + private def __representativeVariable(rule: InstantiatedRewriteRule): Variable[?] = val id = ??? // freshRepr - rule match + rule.rule match case TermRewriteRule(_, _) => Variable[T](id) case FormulaRewriteRule(_, _) => Variable[F](id) @@ -91,7 +90,7 @@ object UnificationUtils: * A rewrite context with the given variables considered bound. */ def withBound(vars: Iterable[Variable[?]]) = - RewriteContext(vars.toSet, Set.empty, Set.empty) + RewriteContext(vars.to(Set), Set.empty, Set.empty) /** * Immutable representation of a typed variable substitution. @@ -141,6 +140,9 @@ object UnificationUtils: 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 @@ -198,22 +200,32 @@ object UnificationUtils: 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: @@ -226,17 +238,117 @@ object UnificationUtils: def swap: FormulaRewriteRule = FormulaRewriteRule(r, l) def toFormula: Formula = l <=> r - case class RewriteResult[A](ctx: RewriteContext, context: Expr[A]): - def toLeft: Formula = ??? - def toRight: Formula = ??? - def vars: Seq[Variable[?]] = ctx.ruleRepresentatives + 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: Seq[RewriteRule] = ctx.allRules + 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 From 7bce89d0366c0f13f8760926447e1ef2f8f51725 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 29 Oct 2024 12:12:48 +0100 Subject: [PATCH 65/92] Fix proof reconstruction to use instantiated rewrite rules --- .../scala/lisa/automation/Substitution.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala index f413f652..a4bf9b02 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -17,6 +17,7 @@ import scala.annotation.nowarn import scala.collection.mutable.{Map as MMap} import F.{*, given} +import lisa.utils.collection.VecSet object Substitution: @@ -175,13 +176,13 @@ object Substitution: TacticSubproof: val leftRewrites = leftSubsts.get val rightRewrites = rightSubsts.get - val leftRules = leftRewrites.head.rules - val rightRules = rightRewrites.head.rules + val leftRules = leftRewrites.to(VecSet).flatMap(_.rules) + val rightRules = rightRewrites.to(VecSet).flatMap(_.rules) // instantiated discharges - val leftDischarges = leftRules.map(r => r -> sourceMap(r)) - val rightDischarges = rightRules.map(r => r -> sourceMap(r)) + 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 @@ -195,8 +196,8 @@ object Substitution: val leftVars = leftRewrites.head.vars val leftLambda = andAll(leftRewrites.map(_.lambda)) 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) + 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 @@ -207,8 +208,8 @@ object Substitution: val rightVars = rightRewrites.head.vars val rightLambda = orAll(rightRewrites.map(_.lambda)) 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) + 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 From 13a6946fca71bc786692cc83d5dd31d27a30cf6e Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Tue, 29 Oct 2024 16:40:36 +0100 Subject: [PATCH 66/92] Update proof parser to handle substitution step. Correct bug in equivalence checker. Other minor improvements. --- .../kernel/fol/OLEquivalenceChecker.scala | 2 +- .../lisa/kernel/proof/SCProofChecker.scala | 9 +- .../main/scala/lisa/maths/Quantifiers.scala | 2 + .../src/main/scala/lisa/maths/Tests.scala | 10 ++ .../main/scala/lisa/utils/KernelHelpers.scala | 7 +- .../scala/lisa/utils/tptp/ProofParser.scala | 111 +++++++++++++++++- 6 files changed, 134 insertions(+), 7 deletions(-) diff --git a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala index 36a8ec5b..9c61d71b 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/fol/OLEquivalenceChecker.scala @@ -472,7 +472,7 @@ private[fol] trait OLEquivalenceChecker extends Syntax { case (_, SimpleLiteral(true)) => true case (SimpleEquality(l1, r1, pol1), SimpleEquality(l2, r2, pol2)) => - pol1 == pol2 && latticesEQ(l1, l2) && latticesEQ(r1, r2) + 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)) 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 a48a4f04..a3415d30 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -495,19 +495,20 @@ object SCProofChecker { 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.left, ref(t1).left ++ sEqT_es)) if ( 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.") } diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala index d8f07370..c52ba4f9 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -3,6 +3,7 @@ package lisa.maths import lisa.utils.Serialization.sorry import lisa.prooflib.BasicStepTactic.Sorry import lisa.utils.K.repr +import lisa.automation.atp.Goeland /** * Implements theorems about first-order logic. */ @@ -38,6 +39,7 @@ object Quantifiers extends lisa.Main { ) { have(thesis) by Tableau } + draft() /** * Theorem --- A formula is equivalent to itself existentially quantified if diff --git a/lisa-sets2/src/main/scala/lisa/maths/Tests.scala b/lisa-sets2/src/main/scala/lisa/maths/Tests.scala index b4e2d9d0..4a9e6914 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Tests.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Tests.scala @@ -1,5 +1,10 @@ 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() @@ -8,6 +13,11 @@ object Tests extends lisa.Main { val y = variable[Term] val z = variable[Term] val P = variable[Term >>: Formula] + + //val ppp = ProofParser.reconstructProof(new File("goeland/testSubst.p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable) + + //checkProof(ppp) + val buveurs = Theorem(exists(x, P(x) ==> forall(y, P(y)))) { diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala index 1e0820b0..354d3f3a 100644 --- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala +++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala @@ -541,6 +541,10 @@ object KernelHelpers { */ 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("...") @@ -607,7 +611,8 @@ object KernelHelpers { val currentTree = tree :+ i val showErrorForLine = judgement match { case SCValidProof(_, _) => false - case SCInvalidProof(proof, position, _) => currentTree.startsWith(position) && currentTree.drop(position.size).forall(_ == 0) + 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(" ") 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 1c4e5324..ecac4e24 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -179,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 { @@ -202,6 +207,13 @@ object ProofParser { 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 { @@ -501,5 +513,102 @@ 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 + } + } + } } From 91ee02bfe8362d025b094629dbfae7c18b428489 Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Fri, 1 Nov 2024 15:01:56 +0100 Subject: [PATCH 67/92] add goeland files in sets2 --- .../scala/lisa/automation/atp/Goeland.scala | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala 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 00000000..2493399e --- /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 _ => 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 From f624c6a91357bb41f2ae16c807c9b8f9375e4741 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Sun, 3 Nov 2024 14:55:32 +0100 Subject: [PATCH 68/92] small improvements. --- .../src/main/scala/lisa/automation/Substitution.scala | 7 +++++++ lisa-sets2/src/main/scala/lisa/maths/Tests.scala | 6 ++++-- .../src/main/scala/lisa/utils/tptp/ProofParser.scala | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala index a4bf9b02..407a0849 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -230,6 +230,13 @@ object Substitution: end Apply + object Unfold extends ProofTactic: + def apply(using lib: Library, proof: lib.Proof)(definition: 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)) diff --git a/lisa-sets2/src/main/scala/lisa/maths/Tests.scala b/lisa-sets2/src/main/scala/lisa/maths/Tests.scala index 4a9e6914..9f1e75f0 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Tests.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Tests.scala @@ -14,13 +14,15 @@ object Tests extends lisa.Main { val z = variable[Term] val P = variable[Term >>: Formula] - //val ppp = ProofParser.reconstructProof(new File("goeland/testSubst.p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable) + val ppp = ProofParser.reconstructProof(new File("goeland/testEgg.p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable) - //checkProof(ppp) + 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-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala index ecac4e24..ecaac2d4 100644 --- a/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala +++ b/lisa-utils/src/main/scala/lisa/utils/tptp/ProofParser.scala @@ -545,7 +545,8 @@ object ProofParser { 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)) + Some((K.RightSubstEq(convertToKernel(sequent), numbermap(t1), Seq((s, t)), (Seq(x), fl)), name)) + case _ => None } } From 7c8aa45abf48bc6ea5f494ec3df362c2751c8f08 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 29 Oct 2024 17:06:02 +0100 Subject: [PATCH 69/92] Add ID counter --- .../utils/unification/UnificationUtils.scala | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) 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 6587ca42..2af048cf 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -4,7 +4,6 @@ import lisa.fol.FOL.{_, given} import lisa.prooflib.Library import lisa.prooflib.SimpleDeducedSteps import lisa.utils.K -import lisa.utils.KernelHelpers.freshId import lisa.utils.memoization.memoized import lisa.utils.collection.Extensions.* import lisa.utils.collection.{VecSet => Set} @@ -31,6 +30,10 @@ object UnificationUtils: 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. */ @@ -75,10 +78,12 @@ object UnificationUtils: val representativeVariable = memoized(__representativeVariable) private def __representativeVariable(rule: InstantiatedRewriteRule): Variable[?] = - val id = ??? // freshRepr + 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: /** @@ -92,6 +97,31 @@ object UnificationUtils: 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)).max + 1 + setIDCountTo(max) + /** * Immutable representation of a typed variable substitution. * From a497bb6ff58c872e5b93cd453096df9033a65d7f Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Mon, 4 Nov 2024 07:56:58 +0100 Subject: [PATCH 70/92] Fully qualify Definition for now --- lisa-sets2/src/main/scala/lisa/automation/Substitution.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala index 407a0849..29b81e0e 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Substitution.scala @@ -231,7 +231,7 @@ object Substitution: end Apply object Unfold extends ProofTactic: - def apply(using lib: Library, proof: lib.Proof)(definition: Definition)(premise: proof.Fact): proof.ProofTacticJudgement = + def apply(using lib: Library, proof: lib.Proof)(definition: lib.theory.Definition)(premise: proof.Fact): proof.ProofTacticJudgement = ??? From 7d2a95c47e13e780834c2976b1761839724c7d8e Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Mon, 4 Nov 2024 13:22:23 +0100 Subject: [PATCH 71/92] Add new WIP extensionality and upair theorems --- .../lisa/maths/settheory/Extensionality.scala | 49 +++++++++++++++++++ .../lisa/maths/settheory/UnorderedPair.scala | 36 ++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/UnorderedPair.scala 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 00000000..4501ad16 --- /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 implication = + proof.InstantiatedFact(implied, Seq(x := xe, y := ye)) + + have(premise ->> pivot +>> qpivot) by RightForall.withParameters(pivot, z)(premiseStep) + have(premise ->> pivot +>> 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/UnorderedPair.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/UnorderedPair.scala new file mode 100644 index 00000000..dc5f7544 --- /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 From 09de0ed4f1994c1549103ba56df536f41f9e13b1 Mon Sep 17 00:00:00 2001 From: SimonGuilloud Date: Mon, 4 Nov 2024 20:04:46 +0100 Subject: [PATCH 72/92] fix bug in Tautology --- lisa-sets2/src/main/scala/lisa/automation/Tautology.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala b/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala index 723fcd39..6627ec9f 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/Tautology.scala @@ -148,7 +148,7 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque (Seq(MaRvIn), lambdaF) ) val negatom = neg(atom) - val seq2 = AugSequent((s.decisions._1, atom :: s.decisions._2), substituteVariables(lambdaF, Map(MaRvIn -> top))) + 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, From 879d83d0c68fc20c7b332213666e55d594693701 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Mon, 4 Nov 2024 13:30:06 +0100 Subject: [PATCH 73/92] Only compute base sequent once in extensionality --- .../main/scala/lisa/maths/settheory/Extensionality.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala index 4501ad16..78ed0ae6 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Extensionality.scala @@ -40,10 +40,10 @@ object Extensionality extends lisa.Main: val pivot = z ∈ xe <=> z ∈ ye val qpivot = forall(z, pivot) val eq = xe === ye - val implication = - proof.InstantiatedFact(implied, Seq(x := xe, y := ye)) + val baseSequent = premise ->> pivot + val implication = proof.InstantiatedFact(implied, Seq(x := xe, y := ye)) - have(premise ->> pivot +>> qpivot) by RightForall.withParameters(pivot, z)(premiseStep) - have(premise ->> pivot +>> eq) by Cut.withParameters(qpivot)(lastStep, implication) + 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 From 24b9329731969ef681dc53c053a3eee895530867 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Mon, 4 Nov 2024 20:51:33 +0100 Subject: [PATCH 74/92] Save wip set theory library --- .../scala/lisa/maths/settheory/Pair.scala | 34 +++++++++++++++++++ .../lisa/maths/settheory/Singleton.scala | 30 ++++++++++++++++ .../lisa/maths/settheory/UnorderedPair.scala | 10 +++--- 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/Pair.scala create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala 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 00000000..0f2d6cdc --- /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/Singleton.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala new file mode 100644 index 00000000..876f2685 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala @@ -0,0 +1,30 @@ +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) + + show(singleton.definition) + + 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 index dc5f7544..e5518774 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/settheory/UnorderedPair.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/UnorderedPair.scala @@ -26,11 +26,11 @@ object UnorderedPair extends lisa.Main: 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)) + // 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 + // 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 From f16e268e7c358eb1acdd301fc4944c2b69edbc5a Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Mon, 4 Nov 2024 20:52:02 +0100 Subject: [PATCH 75/92] Add emptiness check for max id calculation in rewriting --- .../main/scala/lisa/utils/unification/UnificationUtils.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2af048cf..4f58765c 100644 --- a/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala +++ b/lisa-utils/src/main/scala/lisa/utils/unification/UnificationUtils.scala @@ -119,7 +119,7 @@ object UnificationUtils: 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)).max + 1 + val max = ctx.allRules.map(r => maxVarId(r.toFormula)).maxOption.getOrElse(0) + 1 setIDCountTo(max) /** From 794fcf8beee4017ffcdd1a20ebb500b8457309bf Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 08:15:17 +0100 Subject: [PATCH 76/92] Remove print in singleton --- lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala index 876f2685..cc31189e 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Singleton.scala @@ -20,8 +20,6 @@ object Singleton extends lisa.Main: */ def unary_~ = singleton(x) - show(singleton.definition) - val membership = Theorem( x ∈ ~x ): have(x ∈ (x <> x)) by Restate.from(UnorderedPair.firstMember of (y := x)) thenHave(thesis) by Substitution.Apply(singleton.definition) From ccc5e1745c76d1a159039415f18b8053f8abd200 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 08:15:28 +0100 Subject: [PATCH 77/92] Add comprehension --- .../lisa/maths/settheory/Comprehension.scala | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala 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 00000000..9d7ca397 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala @@ -0,0 +1,33 @@ +package lisa.maths.settheory + +import scala.annotation.targetName +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 From bb7ebb5ea512ada8320adf99d5cfe55cea48d5e2 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:05:19 +0100 Subject: [PATCH 78/92] Add simple list printer --- lisa-utils/src/main/scala/lisa/utils/Printing.scala | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 lisa-utils/src/main/scala/lisa/utils/Printing.scala 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 00000000..b6ba22fc --- /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) + From c0b391c1b72efa32a547bccc527d2e55d02e0128 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:05:42 +0100 Subject: [PATCH 79/92] Add quantifyAll tactic --- .../main/scala/lisa/maths/Quantifiers.scala | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala index c52ba4f9..45c860ae 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -4,6 +4,9 @@ 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. */ @@ -278,4 +281,64 @@ object Quantifiers extends lisa.Main { } */ + + + /** + * Quantify all variables in a formula on the right side of the premise sequent. + * + * Γ ⊢ φ, Δ + * -------------------------- x, y, ..., z do not appear in Γ + * Γ ⊢ ∀x.∀y. ... ∀z. φ, Δ + * + * @param lib + * @param proof + * @param premiseStep + * @param conclusion + * @return + */ + 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 + } From b3818cf33e80d24432c2455958318cd38348d397 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:05:59 +0100 Subject: [PATCH 80/92] Remove unused targetName import --- .../src/main/scala/lisa/maths/settheory/Comprehension.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala index 9d7ca397..669afce6 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Comprehension.scala @@ -1,6 +1,5 @@ package lisa.maths.settheory -import scala.annotation.targetName import lisa.automation.Substitution object Comprehension extends lisa.Main: From 2e5dcd3692ae8813b94561c453c14d649b5526e5 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:06:09 +0100 Subject: [PATCH 81/92] Add transitivity proof --- .../main/scala/lisa/maths/settheory/Equality.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/Equality.scala 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 00000000..4ac74b1c --- /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 From f9f64cf1c653b0682e7c9beebc19d6bfb352fa13 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:06:22 +0100 Subject: [PATCH 82/92] Add replacement definition --- .../lisa/maths/settheory/Replacement.scala | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala 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 00000000..2808bc69 --- /dev/null +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala @@ -0,0 +1,46 @@ +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 + + 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) + + have(∀(y, ∀(z, ((y === f(x)) /\ (z === f(x))) ==> (y === z)))) subproof: + have(((y === f(x)) /\ (z === f(x))) ==> (y === z)) by Weakening(Equality.transitivity of (x := y, y := f(x), z := z)) + thenHave(thesis) 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) + + // val definition: THM = Theorem( ∀(x, x ∈ s.map(f) <=> (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 Replacement \ No newline at end of file From 4f6f28d1b48eb8c4776aa4227fc3870324db5712 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:33:33 +0100 Subject: [PATCH 83/92] Add replacement proofs --- .../lisa/maths/settheory/Replacement.scala | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala index 2808bc69..e888afdd 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala @@ -23,6 +23,10 @@ object Replacement extends lisa.Main: // 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) @@ -36,11 +40,31 @@ object Replacement extends lisa.Main: have(thesis) by Cut(lastStep, conditional) - // val definition: THM = Theorem( ∀(x, x ∈ s.map(f) <=> (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) + /** + * 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 From 75f4c748ffd17a0ecaf2a49d815d679ae299e399 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:39:10 +0100 Subject: [PATCH 84/92] Add parenthesies to fix equality parsing --- .../lisa/maths/settheory/Replacement.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala index e888afdd..71a1f692 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala @@ -45,11 +45,11 @@ object Replacement extends lisa.Main: * * `∀(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 + 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) /** @@ -58,12 +58,12 @@ object Replacement extends lisa.Main: * `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 + 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(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) From ff40c939e81ab0a25a809e9cdab68ed183f11273 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:39:27 +0100 Subject: [PATCH 85/92] Clean up quantifyAll documentation --- lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala index 45c860ae..2cb2793e 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/Quantifiers.scala @@ -286,15 +286,11 @@ object Quantifiers extends lisa.Main { /** * Quantify all variables in a formula on the right side of the premise sequent. * + *
     *         Γ ⊢ φ, Δ
     * -------------------------- x, y, ..., z do not appear in Γ
     *  Γ ⊢ ∀x.∀y. ... ∀z. φ, Δ
-    *
-    * @param lib
-    * @param proof
-    * @param premiseStep
-    * @param conclusion
-    * @return
+    * 
*/ 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]]] = From d39c480d7cd3c83f89fa7a23e3f7703202887143 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:40:26 +0100 Subject: [PATCH 86/92] Add RightEpsilon scaffold --- .../src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index 091f71b8..fa5fc89d 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -992,6 +992,10 @@ object BasicStepTactic { */ + object RightEpsilon extends ProofTactic with ProofFactSequentTactic { + def apply(using lib: Library, proof: lib.Proof)(premise: proof.Fact)(bot: F.Sequent): proof.ProofTacticJudgement = ??? + } + 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 From 2a72d756cb64f2ef186ad308617c3c5d24275ef0 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 09:43:22 +0100 Subject: [PATCH 87/92] Remove subproof for uniformity --- .../src/main/scala/lisa/maths/settheory/Replacement.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala index 71a1f692..5bfa9d62 100644 --- a/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala +++ b/lisa-sets2/src/main/scala/lisa/maths/settheory/Replacement.scala @@ -31,9 +31,9 @@ object Replacement extends lisa.Main: 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) - have(∀(y, ∀(z, ((y === f(x)) /\ (z === f(x))) ==> (y === z)))) subproof: + val eqTautology = have(((y === f(x)) /\ (z === f(x))) ==> (y === z)) by Weakening(Equality.transitivity of (x := y, y := f(x), z := z)) - thenHave(thesis) by Quantifiers.quantifyAll + 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 From ef9c12cb2adc716d17e5995eff8996ce853db94f Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 10:00:16 +0100 Subject: [PATCH 88/92] Remove unnecessary condition in RightEpsilon docs --- .../src/main/scala/lisa/kernel/proof/SCProofChecker.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a3415d30..9d7b1c84 100644 --- a/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala +++ b/lisa-kernel/src/main/scala/lisa/kernel/proof/SCProofChecker.scala @@ -351,7 +351,7 @@ object SCProofChecker { /** *
            *       Γ |- φ[t/x], Δ
-           * -------------------------- if y is not free in φ
+           * --------------------------
            *     Γ|- φ[(εx. φ)/x], Δ
            * 
*/ From 87f5e45fa1e4f2f8cbf925722d43fca35c6ee223 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 10:00:32 +0100 Subject: [PATCH 89/92] Add RightEpsilon.withParameters --- .../lisa/utils/prooflib/BasicStepTactic.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index fa5fc89d..d7c629c1 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -992,7 +992,31 @@ object BasicStepTactic { */ + /** + *
+   *       Γ |- φ[t/x], Δ
+   * --------------------------
+   *     Γ|- φ[(εx. φ)/x], Δ
+   * 
+ */ object RightEpsilon extends ProofTactic with ProofFactSequentTactic { + 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 = ??? } From cb318dae0a3137c0e9889e08dffec05e90559205 Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 11:19:34 +0100 Subject: [PATCH 90/92] Clarify type in Binder.unapply --- lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala index 63e28d3b..1d717aac 100644 --- a/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala +++ b/lisa-utils/src/main/scala/lisa/utils/fol/Syntax.scala @@ -232,7 +232,7 @@ trait Syntax { 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[?]): Option[(Variable[T1], Expr[T2])] = e match { + 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 } From 4659dfe0a2d0e16af83c31a073a8cd9b93e3e06d Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 11:19:51 +0100 Subject: [PATCH 91/92] Parameter inference for RightEpsilon --- .../lisa/utils/prooflib/BasicStepTactic.scala | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala index d7c629c1..6644d84c 100644 --- a/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala +++ b/lisa-utils/src/main/scala/lisa/utils/prooflib/BasicStepTactic.scala @@ -998,8 +998,13 @@ object BasicStepTactic { * -------------------------- * Γ|- φ[(ε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 @@ -1017,7 +1022,42 @@ object BasicStepTactic { 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 = ??? + 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 { From ef31631f7352a017d222d3c16f8563399fe0ca8d Mon Sep 17 00:00:00 2001 From: Sankalp Gambhir Date: Tue, 5 Nov 2024 11:22:46 +0100 Subject: [PATCH 92/92] Change _ to null per compiler warning suggestion --- lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala b/lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala index 2493399e..dc829075 100644 --- a/lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala +++ b/lisa-sets2/src/main/scala/lisa/automation/atp/Goeland.scala @@ -85,7 +85,7 @@ object Goeland extends ProofTactic with ProofSequentTactic { val backMap = freevars.map{ case (x: K.Variable, xx: K.Variable) => xx -> x - case _ => throw new Exception("This should not happen") + case null => throw new Exception("This should not happen") } val r = problemToFile(foldername, filename, "question"+i, axioms, sequent, source) i += 1