From 572ed12432d034ea55fb9080855f907513dc5565 Mon Sep 17 00:00:00 2001
From: Sankalp Gambhir <sankalp.gambhir47@gmail.com>
Date: Tue, 1 Nov 2022 14:07:34 +0100
Subject: [PATCH] Proof checking at higher abstraction level - Left
 propositional rules

---
 .../lisa/utils/tactics/BasicStepTactic.scala  | 315 ++++++++++++++----
 1 file changed, 241 insertions(+), 74 deletions(-)

diff --git a/lisa-utils/src/main/scala/lisa/utils/tactics/BasicStepTactic.scala b/lisa-utils/src/main/scala/lisa/utils/tactics/BasicStepTactic.scala
index 9c02f590..59f81b59 100644
--- a/lisa-utils/src/main/scala/lisa/utils/tactics/BasicStepTactic.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/tactics/BasicStepTactic.scala
@@ -12,13 +12,34 @@ import lisa.utils.tactics.ProofStepLib.{_, given}
 object BasicStepTactic {
 
   case object Hypothesis extends ProofStepWithoutBotNorPrem(0) {
-    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement =
-      SC.Hypothesis(bot, bot.left.intersect(bot.right).head)
+    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
+      val intersectedPivot = bot.left.intersect(bot.right)
+
+      if (!intersectedPivot.isEmpty)
+        SC.Hypothesis(bot, intersectedPivot.head)
+      else
+        ProofStepJudgement.InvalidProofStep(
+          this.asProofStepWithoutBot(premises).asProofStep(bot),
+          "A formula for input to hypothesis could not be inferred from left and right side of sequent."
+        )
+    }
   }
 
   case object Rewrite extends ProofStepWithoutBotNorPrem(1) {
-    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement =
-      SC.Rewrite(bot, premises(0))
+    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
+      if (premises.length != 1)
+        ProofStepJudgement.InvalidProofStep(
+          this.asProofStepWithoutBot(premises).asProofStep(bot),
+          s"One premise expected, ${premises.length} received."
+        )
+      else if (SC.isSameSequent(bot, currentProof.getSequent(premises(0))))
+        SC.Rewrite(bot, premises(0))
+      else
+        ProofStepJudgement.InvalidProofStep(
+          this.asProofStepWithoutBot(premises).asProofStep(bot),
+          "The premise and the conclusion are not trivially equivalent."
+        )
+    }
   }
 
   /**
@@ -29,30 +50,54 @@ object BasicStepTactic {
    * </pre>
    */
   case class Cut(phi: Formula) extends ProofStepWithoutBotNorPrem(2) {
-    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement =
-      SC.Cut(bot, premises(0), premises(1), phi)
+    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val leftSequent = currentProof.getSequent(premises(0))
+      lazy val rightSequent = currentProof.getSequent(premises(1))
+
+      if (premises.length != 2)
+        invalid(s"Two premises expected, ${premises.length} received.")
+      else if (!contains(leftSequent.right, phi))
+        invalid("Right-hand side of first premise does not contain φ as claimed.")
+      else if (!contains(rightSequent.left, phi))
+        invalid("Left-hand side of second premise does not contain φ as claimed.")
+      else if (!isSameSet(bot.left, leftSequent.left ++ rightSequent.left.filterNot(isSame(_, phi))))
+        invalid("Left-hand side of conclusion + φ is not the union of the left-hand sides of the premises.")
+      else if (!isSameSet(bot.right, leftSequent.right.filterNot(isSame(_, phi)) ++ rightSequent.right))
+        invalid("Right-hand side of conclusion + φ is not the union of the right-hand sides of the premises.")
+      else
+        SC.Cut(bot, premises(0), premises(1), phi)
+    }
   }
 
   case object CutWithoutFormula extends ProofStepWithoutBotNorPrem(2) {
     def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
-      val leftSequent = currentProof.getSequent(premises(0))
-      val rightSequent = currentProof.getSequent(premises(1))
-      val cutSet = rightSequent.left.diff(bot.left) ++ leftSequent.right.diff(bot.right)
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val leftSequent = currentProof.getSequent(premises(0))
+      lazy val rightSequent = currentProof.getSequent(premises(1))
+      lazy val cutSet = rightSequent.left.diff(bot.left) ++ leftSequent.right.diff(bot.right)
       lazy val intersectedCutSet = rightSequent.left & leftSequent.right
 
-      if (!cutSet.isEmpty)
-        if (cutSet.tail.isEmpty) {
-          SC.Cut(bot, premises(0), premises(1), cutSet.head)
-        } else
-          ProofStepJudgement.InvalidProofStep(this.asProofStepWithoutBot(premises).asProofStep(bot), "Inferred cut pivot is not a singleton set.")
+      if (premises.length != 2)
+        invalid(s"Two premises expected, ${premises.length} received.")
+      else if (!cutSet.isEmpty)
+        if (cutSet.tail.isEmpty)
+          Cut(cutSet.head).asSCProof(bot, premises, currentProof)
+        else
+          invalid("Inferred cut pivot is not a singleton set.")
       else if (!intersectedCutSet.isEmpty && intersectedCutSet.tail.isEmpty)
         // can still find a pivot
-        SC.Cut(bot, premises(0), premises(1), intersectedCutSet.head)
+        Cut(intersectedCutSet.head).asSCProof(bot, premises, currentProof)
       else
-        ProofStepJudgement.InvalidProofStep(
-          this.asProofStepWithoutBot(premises).asProofStep(bot),
-          "A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause."
-        )
+        invalid("A consistent cut pivot cannot be inferred from the premises. Possibly a missing or extraneous clause.")
     }
   }
 
@@ -76,33 +121,57 @@ object BasicStepTactic {
    * </pre>
    */
   case class LeftAnd(phi: Formula, psi: Formula) extends ProofStepWithoutBotNorPrem(1) {
-    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement =
-      SC.LeftAnd(bot, premises(0), phi, psi)
+    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val premiseSequent = currentProof.getSequent(premises(0))
+      lazy val phiAndPsi = ConnectorFormula(And, Seq(phi, psi))
+
+      if (premises.length != 1)
+        invalid(s"One premise expected, ${premises.length} received.")
+      else if (!isSameSet(bot.right, premiseSequent.right))
+        invalid("Right-hand sides of the premise and the conclusion must be the same.")
+      else if (
+        !isSameSet(bot.left + phi, premiseSequent.left + phiAndPsi) &&
+        !isSameSet(bot.left + psi, premiseSequent.left + phiAndPsi) &&
+        !isSameSet(bot.left + phi + psi, premiseSequent.left + phiAndPsi)
+      )
+        invalid("Left-hand side of conclusion + φ∧ψ must be same as left-hand side of premise + either φ, ψ or both.")
+      else
+        SC.LeftAnd(bot, premises(0), phi, psi)
+    }
   }
 
   case object LeftAndWithoutFormula extends ProofStepWithoutBotNorPrem(1) {
     def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
-      val premiseSequent = currentProof.getSequent(premises(0))
-      val pivot = bot.left.diff(premiseSequent.left)
-
-      if (!pivot.isEmpty && pivot.tail.isEmpty)
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+      
+      lazy val premiseSequent = currentProof.getSequent(premises(0))
+      lazy val pivot = bot.left.diff(premiseSequent.left)
+
+      if (premises.length != 1)
+        invalid(s"One premise expected, ${premises.length} received.")
+      else if (!pivot.isEmpty && pivot.tail.isEmpty)
         pivot.head match {
           case ConnectorFormula(And, Seq(phi, psi)) =>
             if (premiseSequent.left.contains(phi))
-              SC.LeftAnd(bot, premises(0), phi, psi)
+              LeftAnd(phi, psi).asSCProof(bot, premises, currentProof)
             else
-              SC.LeftAnd(bot, premises(0), psi, phi)
-          case _ => ProofStepJudgement.InvalidProofStep(this.asProofStepWithoutBot(premises).asProofStep(bot), "Could not infer a conjunction as pivot from premise and conclusion.")
+              LeftAnd(psi, phi).asSCProof(bot, premises, currentProof)
+          case _ => invalid("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 (SC.isSameSequent(premiseSequent, bot))
-        SC.Rewrite(bot, premises(0))
-      else
-        ProofStepJudgement.InvalidProofStep(
-          this.asProofStepWithoutBot(premises).asProofStep(bot),
-          "Left-hand side of conclusion + φ∧ψ must be same as left-hand side of premise + either φ, ψ or both."
-        )
+        // try a rewrite, if it works, go ahead with it, otherwise malformed
+        if (SC.isSameSequent(premiseSequent, bot))
+          Rewrite.asSCProof(bot, premises, currentProof)
+        else
+          invalid("Left-hand side of conclusion + φ∧ψ must be same as left-hand side of premise + either φ, ψ or both.")
     }
   }
 
@@ -125,24 +194,44 @@ object BasicStepTactic {
    */
   case class LeftOr(disjuncts: Seq[Formula]) extends ProofStepWithoutBotNorPrem(-1) {
     def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
-      SC.LeftOr(bot, premises, disjuncts)
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val premiseSequents = premises.map(currentProof.getSequent(_))
+      lazy val disjunction = ConnectorFormula(Or, disjuncts)
+      
+
+      if (premises.length == 0)
+        invalid(s"Premises expected, ${premises.length} received.")
+      else if (!isSameSet(bot.right, premiseSequents.map(_.right).reduce(_ union _)))
+        invalid("Right-hand side of conclusion is not the union of the right-hand sides of the premises.")
+      else if (!isSameSet(disjuncts.foldLeft(bot.left)(_ + _), premiseSequents.map(_.left).reduce(_ union _) + disjunction))
+        invalid("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.")
+      else
+        SC.LeftOr(bot, premises, disjuncts)
     }
   }
   case class LeftOrWithoutFormula() extends ProofStepWithoutBotNorPrem(-1) {
     def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
-      val premiseSequents = premises.map(currentProof.getSequent(_))
-      val pivots = premiseSequents.map(_.left.diff(bot.left))
-
-      if (pivots.exists(_.isEmpty))
-        SC.Weakening(bot, premises(pivots.indexWhere(_.isEmpty)))
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val premiseSequents = premises.map(currentProof.getSequent(_))
+      lazy val pivots = premiseSequents.map(_.left.diff(bot.left))
+
+      if (premises.length == 0)
+        invalid(s"Premises expected, ${premises.length} received.")
+      else if (pivots.exists(_.isEmpty))
+        Weakening.asSCProof(bot, Seq(premises(pivots.indexWhere(_.isEmpty))), currentProof)
       else if (pivots.forall(_.tail.isEmpty))
-        SC.LeftOr(bot, premises, pivots.map(_.head))
+        LeftOr(pivots.map(_.head)).asSCProof(bot, premises, currentProof)
       else
         // some extraneous formulae
-        ProofStepJudgement.InvalidProofStep(
-          this.asProofStepWithoutBot(premises).asProofStep(bot),
-          "Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ."
-        )
+        invalid("Left-hand side of conclusion + disjuncts is not the same as the union of the left-hand sides of the premises + φ∨ψ.")
     }
   }
 
@@ -164,23 +253,48 @@ object BasicStepTactic {
    * </pre>
    */
   case class LeftImplies(phi: Formula, psi: Formula) extends ProofStepWithoutBotNorPrem(2) {
-    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement =
-      SC.LeftImplies(bot, premises(0), premises(1), phi, psi)
+    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val leftSequent = currentProof.getSequent(premises(0))
+      lazy val rightSequent = currentProof.getSequent(premises(1))
+      lazy val implication = ConnectorFormula(Implies, Seq(phi, psi))
+      
+
+      if (premises.length != 2)
+        invalid(s"Two premises expected, ${premises.length} received.")
+      else if (!isSameSet(bot.right + phi, leftSequent.right union rightSequent.right))
+        invalid("Right-hand side of conclusion + φ must be identical to union of right-hand sides of premises.")
+      else if (!isSameSet(bot.left + psi, leftSequent.left union rightSequent.left + implication))
+        invalid("Left-hand side of conclusion + ψ must be identical to union of left-hand sides of premises + φ→ψ.")
+      else
+        SC.LeftImplies(bot, premises(0), premises(1), phi, psi)
+    }
   }
 
   case object LeftImpliesWithoutFormula extends ProofStepWithoutBotNorPrem(2) {
     def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
-      val leftSequent = currentProof.getSequent(premises(0))
-      val rightSequent = currentProof.getSequent(premises(1))
-      val pivotLeft = leftSequent.right.diff(bot.right)
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val leftSequent = currentProof.getSequent(premises(0))
+      lazy val rightSequent = currentProof.getSequent(premises(1))
+      lazy val pivotLeft = leftSequent.right.diff(bot.right)
       lazy val pivotRight = rightSequent.left.diff(bot.left)
 
-      if (pivotLeft.isEmpty)
-        SC.Weakening(bot, premises(0))
+      if (premises.length != 2)
+        invalid(s"Two premises expected, ${premises.length} received.")
+      else if (pivotLeft.isEmpty)
+        Weakening.asSCProof(bot, premises, currentProof)
       else if (pivotRight.isEmpty)
-        SC.Weakening(bot, premises(1))
+        Weakening.asSCProof(bot, premises, currentProof)
       else if (pivotLeft.tail.isEmpty && pivotRight.tail.isEmpty)
-        SC.LeftImplies(bot, premises(0), premises(1), pivotLeft.head, pivotRight.head)
+        LeftImplies(pivotLeft.head, pivotRight.head).asSCProof(bot, premises, currentProof)
       else
         ProofStepJudgement.InvalidProofStep(
           this.asProofStepWithoutBot(premises).asProofStep(bot),
@@ -207,21 +321,51 @@ object BasicStepTactic {
    * </pre>
    */
   case class LeftIff(phi: Formula, psi: Formula) extends ProofStepWithoutBotNorPrem(1) {
-    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement =
-      SC.LeftIff(bot, premises(0), phi, psi)
+    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val premiseSequent = currentProof.getSequent(premises(0))
+      lazy val implication = ConnectorFormula(Iff, Seq(phi, psi))
+      lazy val impLeft = ConnectorFormula(Implies, Seq(phi, psi))
+      lazy val impRight = ConnectorFormula(Implies, Seq(psi, phi))
+      
+
+      if (premises.length != 1)
+        invalid(s"One premise expected, ${premises.length} received.")
+      else if (!isSameSet(bot.right, premiseSequent.right))
+        invalid("Right-hand sides of premise and conclusion must be the same.")
+      else if (
+                !isSameSet(bot.left + impLeft, premiseSequent.left + implication) &&
+                !isSameSet(bot.left + impRight, premiseSequent.left + implication) &&
+                !isSameSet(bot.left + impLeft + impRight, premiseSequent.left + implication)
+        )
+        invalid("Left-hand side of conclusion + φ↔ψ must be same as left-hand side of premise + either φ→ψ, ψ→φ or both.")
+      else
+        SC.LeftIff(bot, premises(0), phi, psi)
+    }
   }
 
   case class LeftIffWithoutFormula() extends ProofStepWithoutBotNorPrem(1) {
     def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
-      val premiseSequent = currentProof.getSequent(premises(0))
-      val pivot = premiseSequent.left.diff(bot.left)
-
-      if (pivot.isEmpty)
-        SC.Weakening(bot, premises(0))
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val premiseSequent = currentProof.getSequent(premises(0))
+      lazy val pivot = premiseSequent.left.diff(bot.left)
+
+      if (premises.length != 1)
+        invalid(s"One premise expected, ${premises.length} received.")
+      else if (pivot.isEmpty)
+        Weakening.asSCProof(bot, premises, currentProof)
       else
         pivot.head match {
-          case ConnectorFormula(Implies, Seq(phi, psi)) => SC.LeftIff(bot, premises(0), phi, psi)
-          case _ => ProofStepJudgement.InvalidProofStep(this.asProofStepWithoutBot(premises).asProofStep(bot), "Could not infer a pivot implication from premise.")
+          case ConnectorFormula(Implies, Seq(phi, psi)) => LeftIff(phi, psi).asSCProof(bot, premises, currentProof)
+          case _ => invalid("Could not infer a pivot implication from premise.")
         }
     }
   }
@@ -244,21 +388,44 @@ object BasicStepTactic {
    * </pre>
    */
   case class LeftNot(phi: Formula) extends ProofStepWithoutBotNorPrem(1) {
-    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement =
-      SC.LeftNot(bot, premises(0), phi)
+    def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val premiseSequent = currentProof.getSequent(premises(0))
+      lazy val negation = ConnectorFormula(Neg, Seq(phi))
+      
+      if (premises.length != 1)
+        invalid(s"One premise expected, ${premises.length} received.")
+      else if (!isSameSet(bot.right + phi, premiseSequent.right))
+        invalid("Right-hand side of conclusion + φ must be the same as right-hand side of premise.")
+      else if (!isSameSet(bot.left, premiseSequent.left + negation))
+        invalid("Left-hand side of conclusion must be the same as left-hand side of premise + ¬φ.")
+      else
+        SC.LeftNot(bot, premises(0), phi)
+    }
   }
 
   case class LeftNotWithoutFormula() extends ProofStepWithoutBotNorPrem(1) {
     def asSCProof(bot: Sequent, premises: Seq[Int], currentProof: Library#Proof): ProofStepJudgement = {
-      val premiseSequent = currentProof.getSequent(premises(0))
-      val pivot = premiseSequent.right.diff(bot.right)
-
-      if (pivot.isEmpty)
-        SC.Weakening(bot, premises(0))
+      def invalid(msg: String) = ProofStepJudgement.InvalidProofStep(
+                                    this.asProofStepWithoutBot(premises).asProofStep(bot),
+                                    msg
+                                  )
+
+      lazy val premiseSequent = currentProof.getSequent(premises(0))
+      lazy val pivot = premiseSequent.right.diff(bot.right)
+
+      if (premises.length != 1)
+        invalid(s"One premise expected, ${premises.length} received.")
+      else if (pivot.isEmpty)
+        Weakening.asSCProof(bot, premises, currentProof)
       else if (pivot.tail.isEmpty)
-        SC.LeftNot(bot, premises(0), pivot.head)
+        LeftNot(pivot.head).asSCProof(bot, premises, currentProof)
       else
-        ProofStepJudgement.InvalidProofStep(this.asProofStepWithoutBot(premises).asProofStep(bot), "Right-hand side of conclusion + φ must be the same as right-hand side of premise.")
+        invalid("Right-hand side of conclusion + φ must be the same as right-hand side of premise.")
 
     }
   }
-- 
GitLab