From 26e3b86eef628f87aa94845491887d2e75f163bf Mon Sep 17 00:00:00 2001 From: Simon Guilloud Date: Tue, 8 Oct 2024 18:25:56 +0200 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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) ) ) - }*/ - ??? + } + } }