diff --git a/lisa-examples/src/main/scala/Example.scala b/lisa-examples/src/main/scala/Example.scala index 28b78f657614e16d12f4ef651bbc70500be17582..74465308c6ad2e9142ed75d100bc04b37058124e 100644 --- a/lisa-examples/src/main/scala/Example.scala +++ b/lisa-examples/src/main/scala/Example.scala @@ -9,10 +9,8 @@ object Example extends lisa.Main { // Simple proof with LISA's DSL val fixedPointDoubleApplication = Theorem(∀(x, P(x) ==> P(f(x))) |- P(x) ==> P(f(f(x)))) { - assume(∀(x, P(x) ==> P(f(x)))) - val step1 = have(P(x) ==> P(f(x))) by InstantiateForall - val step2 = have(P(f(x)) ==> P(f(f(x)))) by InstantiateForall - have(thesis) by Tautology.from(step1, step2) + val a1 = assume(∀(x, P(x) ==> P(f(x)))) + have(thesis) by Tautology.from(a1 of x, a1 of f(x)) } // Example of set theoretic development diff --git a/lisa-examples/src/main/scala/Lattices.scala b/lisa-examples/src/main/scala/Lattices.scala new file mode 100644 index 0000000000000000000000000000000000000000..5a467b30b4571d07b26dec4561dbdb6bb1bb60a4 --- /dev/null +++ b/lisa-examples/src/main/scala/Lattices.scala @@ -0,0 +1,186 @@ +object Lattices extends lisa.Main { + // We introduce the signature of lattices + val <= = ConstantPredicateLabel.infix("<=", 2) + addSymbol(<=) + val u = ConstantFunctionLabel.infix("u", 2) // join (union for sets, disjunction in boolean algebras) + addSymbol(u) + val n = ConstantFunctionLabel.infix("n", 2) // meet (interestion for sets, conjunction in boolean algebras) + addSymbol(n) + + // Enables infix notation + extension (left: Term) { + def <=(right: Term): Formula = Lattices.<=.applyUnsafe(Seq(left, right)) + def u(right: Term): Term = Lattices.u.applyUnsafe(Seq(left, right)) + def n(right: Term): Term = Lattices.n.applyUnsafe(Seq(left, right)) + } + + // We now states the axioms of lattices + + val x = variable + val y = variable + val z = variable + + val reflexivity = Axiom(x <= x) + val antisymmetry = Axiom(((x <= y) /\ (y <= x)) ==> (x === y)) + val transitivity = Axiom(((x <= y) /\ (y <= z)) ==> (x <= z)) + val lub = Axiom(((x <= z) /\ (y <= z)) <=> ((x u y) <= z)) + val glb = Axiom(((z <= x) /\ (z <= y)) <=> (z <= (x n y))) + + // Let's prove some properties ! + + val joinLowerBound = Theorem((x <= (x u y)) /\ (y <= (x u y))) { + have(thesis) by Tautology.from(lub of (z := (x u y)), reflexivity of (x := (x u y))) + } + + + val meetUpperBound = Theorem(((x n y) <= x) /\ ((x n y) <= y)) { + sorry + } + val joinCommutative = Theorem((x u y) === (y u x)) { + val s1 = have((x u y) <= (y u x)) by Tautology.from(lub of (z := (y u x)), joinLowerBound of (x := y, y := x)) + have(thesis) by Tautology.from(s1, s1 of (x := y, y := x), antisymmetry of (x := x u y, y := y u x)) + } + + + val meetCommutative = Theorem((x n y) === (y n x)) { + sorry + } + val joinAbsorption = Theorem((x <= y) |- (x u y) === y) { + sorry + } + + val meetAbsorption = Theorem((x <= y) |- (x n y) === x) { + sorry + } + + val joinAssociative = Theorem((x u (y u z)) === ((x u y) u z)) { + sorry + } + + // Tedious, isn't it + // Can we automate this? + // Yes, we can! + + import lisa.prooflib.ProofTacticLib.ProofTactic + import lisa.prooflib.Library + + object Whitman extends ProofTactic { + def solve(using lib: library.type, proof: lib.Proof)(goal: Sequent): proof.ProofTacticJudgement = { + TacticSubproof { // Starting the proof of `goal` + + if goal.left.nonEmpty || goal.right.size != 1 then proof.InvalidProofTactic("Whitman can only be applied to solve goals of the form () |- s <= t") + else + goal.right.head match { + case <=(left: Term, right: Term) => { + // We have different cases to consider + (left, right) match { + // 1. left is a join. In that case, lub gives us the decomposition + case (u(a: Term, b: Term), _) => + val s1 = solve(a <= right) + val s2 = solve(b <= right) + if s1.isValid & s2.isValid then + have(left <= right) by Tautology.from(lub of (x := a, y := b, z := right), have(s1), have(s2)) + else return proof.InvalidProofTactic("The inequality is not true in general ") + + // 2. right is a meet. In that case, glb gives us the decomposition + case (_, n(a: Term, b: Term)) => + ??? + + // 3. left is a meet, right is a join. In that case, we try all pairs. + case (n(a: Term, b: Term), u(c: Term, d: Term)) => + val result = LazyList(a, b) + .map { e => (e, solve(e <= right)) } + .find { _._2.isValid } + .map { case (e, step) => + have(left <= right) by Tautology.from( + have(step), + meetUpperBound of (x := a, y := b), + transitivity of (x := left, y := e, z := right) + ) + } orElse { + LazyList(c, d) + .map { e => (e, solve(left <= e)) } + .find { _._2.isValid } + .map { case (e, step) => + have(left <= right) by Tautology.from( + have(step), + joinLowerBound of (x := c, y := d), + transitivity of (x := left, y := e, z := right) + ) + } + } + if result.isEmpty then return proof.InvalidProofTactic("The inequality is not true in general 3") + + + // 4. left is a meet, right is a variable or unknown term. + case (n(a: Term, b: Term), _) => + val result = LazyList(a, b) + .map { e => (e, solve(e <= right)) } + .find { _._2.isValid } + .map { case (e, step) => + have(left <= right) by Tautology.from( + have(step), + meetUpperBound of (x := a, y := b), + transitivity of (x := left, y := e, z := right) + ) + + } + if result.isEmpty then return proof.InvalidProofTactic("The inequality is not true in general") + + // 5. left is a variable or unknown term, right is a join. + case (_, u(c: Term, d: Term)) => + ??? + + // 6. left and right are variables or unknown terms. + case _ => + if !(left == right) then return proof.InvalidProofTactic("The inequality is not true in general 6") + else ??? + + } + } + + case ===(left: Term, right: Term) => + val s1 = solve(left <= right) + val s2 = solve(right <= left) + if !(s1.isValid && s2.isValid) then return proof.InvalidProofTactic("The equality is not true in general") + else + have(left === right) by Tautology.from( + have(s1), + have(s2), + antisymmetry of (x := left, y := right) + ) + case _ => return proof.InvalidProofTactic("Whitman can only be applied to solve goals of the form () |- s <= t") + } + } + } + } + + + // uncomment when the tactic is implemented + + /* + val test1 = Theorem(x <= x) { + have(thesis) by Whitman.solve + } + val test2 = Theorem(x <= (x u y)) { + have(thesis) by Whitman.solve + } + val test3 = Theorem((y n x) <= x) { + have(thesis) by Whitman.solve + } + val test4 = Theorem((x n y) <= (y u z)) { + have(thesis) by Whitman.solve + } + val idempotence = Theorem((x u x) === x) { + have(thesis) by Whitman.solve + } + val meetAssociative = Theorem((x n (y n z)) === ((x n y) n z)) { + have(thesis) by Whitman.solve + } + val semiDistributivity = Theorem((x u (y n z)) <= ((x u y) n (x u z))) { + have(thesis) by Whitman.solve + } + */ + + +} diff --git a/lisa-examples/src/main/scala/Test.scala b/lisa-examples/src/main/scala/Test.scala index b65c3c6c2856115278ef9fd926b786a5f706b0a9..026a3761a4610362f2a41b69b7a4c45ea5a536db 100644 --- a/lisa-examples/src/main/scala/Test.scala +++ b/lisa-examples/src/main/scala/Test.scala @@ -25,7 +25,6 @@ object Test extends lisa.Main { val goal = E(f(f(g(a))), g(f(f(a)))) - val thm = Theorem((assump1, assump2, assump3) |- goal) { have(thesis) by Tableau } @@ -43,7 +42,7 @@ object Test extends lisa.Main { } val thm3 = Theorem(∀(y, ∀(x, E(x, y))) |- E(f(x), y) /\ E(x, h(x, y))) { - val s1 = assume (∀(y, ∀(x, E(x, y)))) + val s1 = assume(∀(y, ∀(x, E(x, y)))) val s2 = have(∀(x, E(x, y))) by Restate.from(s1 of y) have(thesis) by Tautology.from(s2 of f(x), s2 of (x, y := h(x, y))) diff --git a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala index 476df8a588f235fb31935088ec0d932710bffe17..d1367feebb32cd151a00958f8b7ec191a42b53bb 100644 --- a/lisa-sets/src/main/scala/lisa/automation/Tautology.scala +++ b/lisa-sets/src/main/scala/lisa/automation/Tautology.scala @@ -48,7 +48,8 @@ object Tautology extends ProofTactic with ProofSequentTactic with ProofFactSeque val subpr = SCSubproof(value) val stepsList = premsFormulas.foldLeft[List[SCProofStep]](List(subpr))((prev: List[SCProofStep], cur) => { val ((prem, form), position) = cur - Cut(prev.head.bot -<< form, position, initProof.length + prev.length - 1, form) :: prev + if prev.head.bot.left.contains(form) then Cut(prev.head.bot -<< form, position, initProof.length + prev.length - 1, form) :: prev + else prev }) val steps = (initProof ++ stepsList.reverse).toIndexedSeq proof.ValidProofTactic(bot, steps, premises) diff --git a/lisa-utils/src/main/scala/lisa/fol/Common.scala b/lisa-utils/src/main/scala/lisa/fol/Common.scala index 356b95d845b0c0a1407103759dee0fd0007a695e..cb1ca1eeb7290babfdc96545e97397f989bebc1b 100644 --- a/lisa-utils/src/main/scala/lisa/fol/Common.scala +++ b/lisa-utils/src/main/scala/lisa/fol/Common.scala @@ -377,7 +377,7 @@ trait Common { def rename(newid: Identifier): ConstantFunctionLabel[N] = ConstantFunctionLabel(newid, arity) def freshRename(taken: Iterable[Identifier]): ConstantFunctionLabel[N] = rename(K.freshId(taken, id)) override def toString(): String = id - def mkString(args: Seq[Term]): String = if (infix) (args(0).toString() + " " + toString() + " " + args(1).toString()) else toString() + "(" + args.mkString(", ") + ")" + def mkString(args: Seq[Term]): String = if (infix) (args(0).toStringSeparated() + " " + toString() + " " + args(1).toStringSeparated()) else toString() + "(" + args.mkString(", ") + ")" override def mkStringSeparated(args: Seq[Term]): String = if (infix) "(" + mkString(args) + ")" else mkString(args) } object ConstantFunctionLabel { @@ -398,7 +398,7 @@ trait Common { def freeSchematicLabels: Set[SchematicLabel[?]] = label.freeSchematicLabels ++ args.flatMap(_.freeSchematicLabels) def allSchematicLabels: Set[SchematicLabel[?]] = label.allSchematicLabels ++ args.flatMap(_.allSchematicLabels) override def toString: String = label.mkString(args) - override def toStringSeparated(): String = label.mkString(args) + override def toStringSeparated(): String = label.mkStringSeparated(args) } ////////////////////////////////////// @@ -610,7 +610,7 @@ trait Common { def rename(newid: Identifier): ConstantPredicateLabel[N] = ConstantPredicateLabel(newid, arity) def freshRename(taken: Iterable[Identifier]): ConstantPredicateLabel[N] = rename(K.freshId(taken, id)) override def toString(): String = id - def mkString(args: Seq[Term]): String = if (infix) (args(0).toString() + " " + toString() + " " + args(1).toString()) else toString() + "(" + args.mkString(", ") + ")" + def mkString(args: Seq[Term]): String = if (infix) (args(0).toStringSeparated() + " " + toString() + " " + args(1).toStringSeparated()) else toString() + "(" + args.mkString(", ") + ")" override def mkStringSeparated(args: Seq[Term]): String = if (infix) "(" + mkString(args) + ")" else mkString(args) } object ConstantPredicateLabel { @@ -632,7 +632,7 @@ trait Common { def allSchematicLabels: Set[SchematicLabel[?]] = label.allSchematicLabels ++ args.toSeq.flatMap(_.allSchematicLabels) override def toString: String = label.mkString(args) - override def toStringSeparated(): String = label.mkString(args) + override def toStringSeparated(): String = label.mkStringSeparated(args) } //////////////// diff --git a/lisa-utils/src/main/scala/lisa/fol/Predef.scala b/lisa-utils/src/main/scala/lisa/fol/Predef.scala index c5cd8d3a7ab827e88796cb542cdfb31d62ad372f..5d53b6a92040ae45e08e614127cedb8ee3886c98 100644 --- a/lisa-utils/src/main/scala/lisa/fol/Predef.scala +++ b/lisa-utils/src/main/scala/lisa/fol/Predef.scala @@ -4,7 +4,7 @@ import lisa.utils.K trait Predef extends Common { - val equality: ConstantPredicateLabel[2] = ConstantPredicateLabel[2](K.Identifier("="), 2) + val equality: ConstantPredicateLabel[2] = ConstantPredicateLabel.infix(K.Identifier("="), 2) val === = equality val = = equality diff --git a/lisa-utils/src/main/scala/lisa/fol/Sequents.scala b/lisa-utils/src/main/scala/lisa/fol/Sequents.scala index bd6bc96696d4b4f178c4d6936a53c1965f557dc9..1f13bf8498f0eb2b309d746687cc6e6f88070e8a 100644 --- a/lisa-utils/src/main/scala/lisa/fol/Sequents.scala +++ b/lisa-utils/src/main/scala/lisa/fol/Sequents.scala @@ -168,9 +168,9 @@ trait Sequents extends Common with lisa.fol.Lambdas with Predef { infix def ++?(s1: Sequent): Sequent = this addAllIfNotExists s1 override def toString = - (if left.size == 1 then left.head.toString else "( " + left.mkString(", ") + " )") + + (if left.size == 0 then "" else if left.size == 1 then left.head.toString else "( " + left.mkString(", ") + " )") + " ⊢ " + - (if right.size == 1 then right.head.toString else "( " + right.mkString(", ") + " )") + (if right.size == 0 then "" else if right.size == 1 then right.head.toString else "( " + right.mkString(", ") + " )") } diff --git a/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala b/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala index 49349ce973650803707afa2be9ccd63a321cef3a..45cab01d15b300afb2e0e438eb659db769966ad4 100644 --- a/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala +++ b/lisa-utils/src/main/scala/lisa/prooflib/WithTheorems.scala @@ -451,6 +451,8 @@ trait WithTheorems { * 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. @@ -533,7 +535,6 @@ trait WithTheorems { */ class THMFromKernel(using om: OutputManager)(val statement: F.Sequent, val fullName: String, val kind: TheoremKind, innerThm: theory.Theorem, getProof: () => Option[K.SCProof]) extends THM { - def repr: String = innerJustification.repr val innerJustification: theory.Theorem = innerThm assert(innerThm.name == fullName) def kernelProof: Option[K.SCProof] = getProof() @@ -567,7 +568,6 @@ trait WithTheorems { thm } else prove(computeProof)._1 - def repr: String = innerJustification.repr library.last = Some(this) private def prove(computeProof: Proof ?=> Unit): (theory.Theorem, SCProof, List[(String, theory.Justification)]) = {