Skip to content

Commit

Permalink
Support for Goéland and SC-TPTP (#211)
Browse files Browse the repository at this point in the history
This adds support for Goéland as a proof tactic, and for parsing of proofs in SC-TPTP format.
  • Loading branch information
SimonGuilloud authored Apr 8, 2024
1 parent 73bc04b commit 0e2b6b7
Show file tree
Hide file tree
Showing 16 changed files with 801 additions and 122 deletions.
8 changes: 5 additions & 3 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# Change List

## 2024-03-02
Support for reconstructing proofs from file in SC-TPTP format. Support and inclusion of Goeland as a tactic. Doc in the manual.

## 2024-03-02
Addition of a tactic that proves a sequent by applying a generic theorem on facts provided by the user. It can be used via `by Apply(thm).on(fact1, fact2,...)`. Replaces cases where using `Tautology` was overkill.

## 2024-02-05
The "draft()" option can now be used at the start of a file to skip checking proofs of theorem outside this file during development.


## 2024-01-02
The "of" keyword can now be used to instantiate quantified variables in facts whose right hand side is a single universaly quantified formula. This can be combined freely with instantiation of ffree schematic symbols. Manual and tests update.

The "of" keyword can now be used to instantiate quantified variables in facts whose right hand side is a single universaly quantified formula. This can be combined freely with instantiation of free schematic symbols. Manual and tests update.

## 2023-12-31
Expanded the Substitution rules to allow substitution under quantifiers when a statement of the form $\forall x. f(x) = g(x)$ is given.
Expand Down
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def githubProject(repo: String, commitHash: String) = RootProject(uri(s"$repo#$c
lazy val scallion = githubProject("https://github.com/sankalpgambhir/scallion.git", "6434e21bd08872cf547c8f0efb67c963bfdf4190")

lazy val silex = githubProject("https://github.com/epfl-lara/silex.git", "fc07a8670a5fa8ea2dd5649a00424710274a5d18")
lazy val customTstpParser = githubProject("https://github.com/SimonGuilloud/scala-tptp-parser.git", "eae9c1b7a9546f74779d77ff50fa6e8a1654cfa0")

scallion/scalacOptions ~= (_.filterNot(Set("-Wvalue-discard")))
silex/scalacOptions ~= (_.filterNot(Set("-Wvalue-discard")))
Expand Down Expand Up @@ -92,7 +93,8 @@ lazy val utils = Project(
.dependsOn(kernel)
.dependsOn(silex)
.dependsOn(scallion % "compile->compile")
.settings(libraryDependencies += "io.github.leoprover" % "scala-tptp-parser_2.13" % "1.4")
.dependsOn(customTstpParser)
//.settings(libraryDependencies += "io.github.leoprover" % "scala-tptp-parser_2.13" % "1.4")

ThisBuild / assemblyMergeStrategy := {
case PathList("module-info.class") => MergeStrategy.discard
Expand Down
10 changes: 10 additions & 0 deletions lisa-examples/src/main/scala/Example.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import lisa.automation.Substitution.{ApplyRules as Substitute}
import lisa.automation.Tableau
import lisa.automation.atp.Goeland

object Example extends lisa.Main {

Expand Down Expand Up @@ -54,6 +56,14 @@ object Example extends lisa.Main {
)
}

val buveurs = Theorem(exists(x, P(x) ==> forall(y, P(y)))) {
have(thesis) by Tableau
}

val buveurs2 = Theorem(exists(x, P(x) ==> forall(y, P(y)))) {
have(thesis) by Goeland("goeland/Example.buveurs2_sol")
}

/*
import lisa.mathematics.settheory.SetTheory.*
Expand Down
19 changes: 4 additions & 15 deletions lisa-examples/src/main/scala/Lattices.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
object Lattices extends lisa.Main {



val x = variable
val P = predicate[1]
val f = function[1]
Expand All @@ -11,11 +9,6 @@ object Lattices extends lisa.Main {
) {
sorry
}






// We introduce the signature of lattices
val <= = ConstantPredicateLabel.infix("<=", 2)
Expand Down Expand Up @@ -49,7 +42,6 @@ object Lattices extends lisa.Main {
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
}
Expand All @@ -58,7 +50,6 @@ object Lattices extends lisa.Main {
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
}
Expand Down Expand Up @@ -97,7 +88,6 @@ object Lattices extends lisa.Main {

// 2. right is a meet. In that case, glb gives us the decomposition
case (_, n(a: Term, b: Term)) =>

val s1 = solve(left <= a)
val s2 = solve(left <= b)
if s1.isValid & s2.isValid then have(left <= right) by Tautology.from(glb of (x := a, y := b, z := left), have(s1), have(s2))
Expand Down Expand Up @@ -131,7 +121,7 @@ object Lattices extends lisa.Main {
// 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)) }
.map { e => (e, solve(e <= right)) }
.find { _._2.isValid }
.map { case (e, step) =>
have(left <= right) by Tautology.from(
Expand Down Expand Up @@ -180,7 +170,6 @@ object Lattices extends lisa.Main {
}
}


// uncomment when the tactic is implemented

/*
Expand All @@ -205,6 +194,6 @@ object Lattices extends lisa.Main {
val semiDistributivity = Theorem((x u (y n z)) <= ((x u y) n (x u z))) {
have(thesis) by Whitman.solve
}
*/
}
*/

}
57 changes: 0 additions & 57 deletions lisa-examples/src/main/scala/Test.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private[fol] trait TermLabelDefinitions extends CommonDefinitions {
* @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)
require(arity >= 1 && arity < MaxArity, "Trying to define SchemaFunctionLabel with arity " + arity + " for symbol " + id.name + "_" + id.no)
}

/**
Expand Down
119 changes: 119 additions & 0 deletions lisa-sets/src/main/scala/lisa/automation/atp/Goeland.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package lisa.automation.atp
import lisa.utils.tptp.*
import ProofParser.*
import KernelParser.*

import lisa.prooflib.ProofTacticLib.*
import lisa.fol.FOL as F
import lisa.prooflib.Library
import lisa.utils.K

import java.io.*
import sys.process._
import scala.io.Source
import scala.util.{Try, Success, Failure}
import lisa.prooflib.OutputManager

/**
* Goéland is an automated theorem prover. This tactic calls the Goéland prover to solve the current sequent.
* Goéland is only available on Linux yet, but proofs generated by Goéland should be kept in the library for future use.
* To ensure that proofs are published and can be replayed in any system, proofs from an ATPcan only be generated in draft mode.
* When in non-draft mode, the proof file should be given as an argument to the tactic (the exact file is provided by Lisa upon run without draft mode).
*/
object Goeland extends ProofTactic with ProofSequentTactic {
private var i : Int = 0

val goelandExec = "../bin/goeland_linux_release"

class OsNotSupportedException(msg: String) extends Exception(msg)

val foldername = "goeland/"

/**
* Fetch a proof of a sequent that was previously proven by Goéland.
* The file must be in SC-TPTP format.
*/
def apply(using lib: Library, proof: lib.Proof)(file:String)(bot: F.Sequent): proof.ProofTacticJudgement = {
val outputname = proof.owningTheorem.fullName+"_sol"
try {
val scproof = reconstructProof(new File(foldername+outputname+".p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable)
proof.ValidProofTactic(bot, scproof.steps, Seq())
} catch {
case e: FileNotFoundException =>
throw FileNotFoundException("The file "+foldername+outputname+".p was not found. To produce a proof, use `by Goeland`. ")
case e => throw e
}
}


/**
* Solve a sequent using the Goéland automated theorem prover.
* At the moment, this option is only available on Linux system.
* The proof is generated and saved in a file in the `Goeland` folder.
*/
def apply(using lib: Library, proof: lib.Proof)(bot: F.Sequent): proof.ProofTacticJudgement = {


solve(Seq(), bot, proof.owningTheorem.fullName, lib.isDraft) match {
case Success(value) => proof.ValidProofTactic(bot, value.steps, Seq())
case Failure(e) => e match
case e: FileNotFoundException => throw new Exception("For compatibility reasons, external provers can't be called in non-draft mode" +
" unless all proofs have already been generated and be available in static files. You can enable draft mode by adding `draft()` at the top of your working file.")
case e: OsNotSupportedException => throw e
case e =>
throw e
}
}

inline def solve(axioms: Seq[F.Sequent], sequent: F.Sequent, source: String, generateProofs : Boolean): Try[K.SCProof] =
solveK(axioms.map(_.underlying), sequent.underlying, source, generateProofs)


/**
* Solve a sequent using the Goéland automated theorem prover, and return the kernel proof.
* At the moment, this option is only available on Linux systems.
*/
def solveK(using line: sourcecode.Line, file: sourcecode.File)(axioms: Seq[K.Sequent], sequent: K.Sequent, source:String, generateProofs : Boolean): Try[K.SCProof] = {
val filename = source
val outputname = source+"_sol"
val directory = File(foldername)
if (directory != null) && !directory.exists() then directory.mkdirs()

val freevars = (sequent.left.flatMap(_.freeVariables) ++ sequent.right.flatMap(_.freeVariables) ).toSet.map(x => x -> K.Term(K.VariableLabel(K.Identifier("X"+x.id.name, x.id.no)), Seq())).toMap

val backMap = freevars.map{
case (x: K.VariableLabel, K.Term(xx: K.VariableLabel, _)) => xx -> K.LambdaTermTerm(Seq(), K.Term(x, Seq()))
case _ => throw new Exception("This should not happen")
}
val r = problemToFile(foldername, filename, "question"+i, axioms, sequent, source)
i += 1

if generateProofs then
val OS = System.getProperty("os.name")
if OS.contains("nix") || OS.contains("nux") || OS.contains("aix") then
val ret = s"chmod u+x \"$goelandExec\"".!
val cmd = (s"$goelandExec -otptp -wlogs -no_id -quoted_pred -proof_file=$foldername$outputname $foldername$filename.p")
val res = try {
cmd.!!
} catch {
case e: Exception =>
throw e
}
val proof = reconstructProof(new File(foldername+outputname+".p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable)
Success(proof)
else if OS.contains("win") then
Failure(OsNotSupportedException("The Goeland automated theorem prover is not yet supported on Windows."))
else
Failure(OsNotSupportedException("The Goeland automated theorem prover is only supported on Linux for now."))
else
if File(foldername+outputname+".p").exists() then
val proof = reconstructProof(new File(foldername+outputname+".p"))(using ProofParser.mapAtom, ProofParser.mapTerm, ProofParser.mapVariable)
println(OutputManager.WARNING(s"WARNING: in ${file.value}:$line, For compatibility reasons, replace `by Goeland` with `by Goeland(\"$foldername$outputname\")`."))
Success(proof)

else Failure(Exception("For compatibility reasons, external provers can't be called in non-draft mode. You can enable draft mode by adding `draft()` at the top of your working file."))


}

}
1 change: 1 addition & 0 deletions lisa-utils/src/main/scala/lisa/prooflib/Library.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ abstract class Library extends lisa.prooflib.WithTheorems with lisa.prooflib.Pro
def draft(using file: sourcecode.File, om: OutputManager)(): Unit =
if last.nonEmpty then om.output(OutputManager.WARNING("Warning: draft option should be used before the first definition or theorem."))
else _draft = Some(file)
def isDraft = _draft.nonEmpty

val knownDefs: scala.collection.mutable.Map[F.ConstantLabel[?], Option[JUSTIFICATION]] = scala.collection.mutable.Map.empty

Expand Down
24 changes: 15 additions & 9 deletions lisa-utils/src/main/scala/lisa/utils/tptp/Example.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package lisa.utils.tptp

import lisa.utils.parsing.FOLParser.*
import lisa.utils.tptp.KernelParser.annotatedFormulaToKernel
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 KernelParser.{mapAtom, mapTerm, mapVariable}

object Example {

val prettyFormula = lisa.utils.parsing.FOLParser.printFormula
Expand All @@ -27,11 +29,11 @@ object Example {
)

println("\n---Individual Fetched Formulas---")
axioms.foreach(a => println(prettyFormula(parseToKernel(a))))
println(prettyFormula(parseToKernel(conjecture)))
axioms.foreach(a => println(prettyFormula(parseToKernel(a)(using mapAtom, mapTerm, mapVariable))))
println(prettyFormula(parseToKernel(conjecture)(using mapAtom, mapTerm, mapVariable)))

println("\n---Annotated Formulas---")
anStatements.map(annotatedFormulaToKernel).foreach(printAnnotatedFormula)
anStatements.map(annotatedStatementToKernel(_)(using mapAtom, mapTerm, mapVariable)).foreach(f => printAnnotatedStatement(f))

println("\n---Problems---")

Expand All @@ -55,16 +57,20 @@ object Example {
}

// Utility
def printAnnotatedFormula(a: AnnotatedFormula): Unit = {
if (a.role == "axiom") println("Given " + a.name + ": " + prettyFormula(a.formula))
else if (a.role == "conjecture") println("Prove " + a.name + ": " + prettyFormula(a.formula))
else println(a.role + " " + a.name + ": " + prettyFormula(a.formula))
def printAnnotatedStatement(a: AnnotatedStatement): Unit = {
val prettyStatement = a match {
case f: AnnotatedFormula => prettyFormula(f.formula)
case s: AnnotatedSequent => prettySequent(s.sequent)
}
if (a.role == "axiom") println("Given " + a.name + ": " + prettyStatement)
else if (a.role == "conjecture") println("Prove " + a.name + ": " + prettyStatement)
else println(a.role + " " + a.name + ": " + prettyStatement)
}

def printProblem(p: Problem): Unit = {
println("Problem: " + p.name + " (" + p.domain + ") ---")
println("Status: " + p.status)
println("SPC: " + p.spc.mkString(", "))
p.formulas.foreach(printAnnotatedFormula)
p.formulas.foreach(printAnnotatedStatement)
}
}
Loading

0 comments on commit 0e2b6b7

Please sign in to comment.