Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group Theory #151

Open
wants to merge 78 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
6092e12
Add group definitions
dhalilov Feb 27, 2023
032ee85
Correct bounded quantifiers semantics
dhalilov Feb 28, 2023
e8c816c
Proof of identity uniqueness
dhalilov Feb 28, 2023
b65230c
Implement ExistenceAndUniqueness tactic
dhalilov Mar 3, 2023
3f0bc81
Fix ExistenceAndUniqueness's premises in subproof
dhalilov Mar 13, 2023
689e008
Prove uniqueness of inverse element
dhalilov Mar 13, 2023
4b0e036
Add existential quantifiers substitutions
dhalilov Mar 15, 2023
53a808a
Create Cases tactic
dhalilov Mar 15, 2023
07e9510
Create conditional definitions
dhalilov Mar 15, 2023
62ece1d
Fix TheConditional cut
dhalilov Mar 16, 2023
4ae4f18
Define identity element
dhalilov Mar 16, 2023
d6b7524
Define inverse element
dhalilov Mar 16, 2023
552d685
Merge github.com:epfl-lara/lisa
dhalilov Mar 18, 2023
59f5657
Fix missing predicates
dhalilov Mar 20, 2023
a2f738c
Simplify proof of identity uniqueness
dhalilov Mar 21, 2023
3e6151a
Fix ExistenceAndUniqueness variable inference
dhalilov Mar 21, 2023
5fca597
Use TacticSubproof
dhalilov Mar 21, 2023
70e959c
Create isInverse and rewrite inverse uniqueness proof
dhalilov Mar 21, 2023
2a0cb72
Switch to infix notation for op
dhalilov Mar 22, 2023
fe62a0e
Prove symmetry of inverse
dhalilov Mar 22, 2023
19055b8
Prove involution of inverse
dhalilov Mar 24, 2023
522273b
Optimize imports
dhalilov Mar 24, 2023
f205f09
Modularize proof of involution of inverse
dhalilov Mar 24, 2023
dd4b657
Add minor comments tweaks
dhalilov Mar 24, 2023
c4b1eb9
Merge remote-tracking branch 'fork/main'
dhalilov Mar 31, 2023
05d6a60
Add not equal sign
dhalilov Mar 31, 2023
974fa1d
Remove bounded quantifiers and use Unicode ∈ symbol
dhalilov Apr 16, 2023
7bc75f4
Create Definition tactic
dhalilov Apr 22, 2023
0ab6024
Merge with epfl-lara/lisa
dhalilov Apr 23, 2023
647389b
Fix merged FunctionDefinition unapply in Definition tactic
dhalilov Apr 24, 2023
ac30619
Prove restricted function domain
dhalilov Apr 24, 2023
652af81
Rename binaryRelation to binaryFunction
dhalilov Apr 27, 2023
4565ec6
Fix inverseUniqueness proof assumptions bug
dhalilov Apr 30, 2023
a8c6dc6
Add subgroup definitions and prove subgroup operation
dhalilov Apr 30, 2023
7248910
Simply GroupTheory tactics
dhalilov Apr 30, 2023
189d9fb
Merge remote-tracking branch 'fork/main'
dhalilov Apr 30, 2023
5650fcd
Revert "Simply GroupTheory tactics"
dhalilov Apr 30, 2023
2e023c3
Fix merge substituteVariable bug
dhalilov Apr 30, 2023
52e8f62
Prove group is non empty
dhalilov May 4, 2023
c645f57
Prove group is subgroup of itself
dhalilov May 5, 2023
188d1c0
Prove restrictedFunctionCancellation
dhalilov May 15, 2023
80e1449
Fix groupIsSubgroupOfItself proof
dhalilov May 15, 2023
c37431b
scala{fix, fmt}
dhalilov May 15, 2023
2ffe0d7
Create associativity lemma
dhalilov May 15, 2023
ae454ad
Simplify proofs
dhalilov May 16, 2023
e3ee43d
Migrate tactics to CommonTactics
dhalilov May 27, 2023
59534af
Create Equalities tactic
dhalilov May 30, 2023
61d2b7b
Merge remote-tracking branch 'fork/main'
dhalilov May 30, 2023
1479852
Add assumptions to subproof checking (#160)
sankalpgambhir May 2, 2023
f1da07e
Fix Equalities assumption bug
dhalilov May 31, 2023
4110189
Simplify equality proofs
dhalilov May 31, 2023
5fa02b6
Prove left cancellation law
dhalilov May 31, 2023
6987290
Create inverse lemmas
dhalilov May 31, 2023
ca4492d
Prove right cancellation law
dhalilov May 31, 2023
3e4126a
Merge branch 'main' of github.com:dhalilov/lisa
dhalilov May 31, 2023
cfa74c8
Prove subgroup identity
dhalilov Jun 3, 2023
90c2972
Rename binaryFunction to binaryOperation
dhalilov Jun 3, 2023
e28b304
Add neutral element lemmas
dhalilov Jun 4, 2023
23508ad
Prove neutral element idempotence
dhalilov Jun 4, 2023
6c8b441
Add homomorphisms
dhalilov Jun 5, 2023
a70cbd3
Prove homomorphism maps e_G to e_H
dhalilov Jun 5, 2023
51b47b5
Clean GroupTheory and add abelian group definition
dhalilov Jun 6, 2023
5764ce7
Define kernel of homomorphism
dhalilov Jun 6, 2023
2c5f2e7
Fix ExistenceAndUniqueness RHS
dhalilov Jun 9, 2023
79d45ab
Simplify inverse symmetry proof
dhalilov Jun 11, 2023
9582b7b
Prove inverse cancellation
dhalilov Jun 11, 2023
3b705cc
Prove more inverse theorems
dhalilov Jun 11, 2023
37bbace
Prove that kernel is a subgroup
dhalilov Jun 11, 2023
a389de7
Define isomorphisms and automorphisms
dhalilov Jun 11, 2023
d935f4e
Prove subgroup inverse
dhalilov Jun 11, 2023
c2b71ab
Prove restricted functions application theorem
dhalilov Jun 15, 2023
f68d2df
Rename subgroup operation
dhalilov Jun 15, 2023
450985d
Prove main subgroup test theorem
dhalilov Jun 16, 2023
ad952d6
scala{fix, fmt}
dhalilov Jun 16, 2023
6ae0b30
Merge branch 'main' into main
dhalilov Jun 16, 2023
b7e306e
Merge branch 'main' into main
SimonGuilloud Jul 26, 2023
5a8ac7f
Merge branch 'main' into main
SimonGuilloud Jul 26, 2023
3a091fd
Merge branch 'main' into main
SimonGuilloud Jul 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ object KernelHelpers {
extension (t: Term) {
infix def ===(u: Term): Formula = PredicateFormula(equality, Seq(t, u))
infix def =(u: Term): Formula = PredicateFormula(equality, Seq(t, u))

infix def !==(u: Term): Formula = !(t === u)

infix def ≠(u: Term): Formula = !(t === u)
}

/* Conversions */
Expand Down
204 changes: 196 additions & 8 deletions src/main/scala/lisa/automation/kernel/CommonTactics.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import lisa.prooflib.BasicStepTactic.*
import lisa.prooflib.ProofTacticLib.{_, given}
import lisa.prooflib.SimpleDeducedSteps.*
import lisa.prooflib.*
import lisa.utils.FOLPrinter
import lisa.utils.KernelHelpers.{_, given}

import scala.collection.immutable.Queue
import scala.collection.mutable.{Map => MutableMap}
import scala.collection.mutable.{Set => MutableSet}

object CommonTactics {

/**
Expand Down Expand Up @@ -57,6 +62,7 @@ object CommonTactics {
proof.InvalidProofTactic("Could not infer correct right pivots.")
} else {
val gammaPrime = uniquenessSeq.left.filter(f => !isSame(f, phi) && !isSame(f, substPhi))
val deltaPrime = uniquenessSeq.right.filter(f => !isSame(f, (x === y)) && !isSame(f, (y === x)))

TacticSubproof {
// There's got to be a better way of importing have/thenHave/assume methods
Expand All @@ -72,13 +78,13 @@ object CommonTactics {
lib.assume(f)
}

val backward = lib.have(phi |- (substPhi ==> (x === y))) by Restate.from(uniqueness)
val backward = lib.have(phi |- (deltaPrime + (substPhi ==> (x === y)))) by Restate.from(uniqueness)

lib.have(phi |- ((x === y) <=> substPhi)) by RightIff(forward, backward)
lib.thenHave(phi |- ∀(y, (x === y) <=> substPhi)) by RightForall
lib.thenHave(phi |- ∃(x, ∀(y, (x === y) <=> substPhi))) by RightExists
lib.thenHave(∃(x, phi) |- ∃(x, ∀(y, (x === y) <=> substPhi))) by LeftExists
lib.thenHave(∃(x, phi) |- ∃!(x, phi)) by RightExistsOne
lib.have(phi |- (deltaPrime + ((x === y) <=> substPhi))) by RightIff(forward, backward)
lib.thenHave(phi |- (deltaPrime + ∀(y, (x === y) <=> substPhi))) by RightForall
lib.thenHave(phi |- (deltaPrime + ∃(x, ∀(y, (x === y) <=> substPhi)))) by RightExists
lib.thenHave(∃(x, phi) |- (deltaPrime + ∃(x, ∀(y, (x === y) <=> substPhi)))) by LeftExists
lib.thenHave(∃(x, phi) |- (deltaPrime + ∃!(x, phi))) by RightExistsOne

lib.have(bot) by Cut(existence, lib.lastStep)
}
Expand Down Expand Up @@ -231,10 +237,11 @@ object CommonTactics {
FOL.ConnectorFormula(FOL.Implies, Seq(a, f)),
FOL.ConnectorFormula(FOL.Implies, Seq(b, g))
)
) if isSame(FOL.Neg(a), b) =>
) if isSame(FOL.Neg(a), b) => {
if (isSame(a, phi)) FOL.LambdaTermFormula(Seq(y), f)
else if (isSame(b, phi)) FOL.LambdaTermFormula(Seq(y), g)
else return proof.InvalidProofTactic("Condition of definition is not satisfied.")
}

case _ => return proof.InvalidProofTactic("Definition is not conditional.")
}
Expand All @@ -250,11 +257,192 @@ object CommonTactics {
lib.thenHave((f(xs) === f(xs)) <=> P(Seq(f(xs)))) by InstFunSchema(Map(y -> f(xs)))
lib.thenHave(P(Seq(f(xs)))) by Restate
lib.thenHave(phi ==> Q(Seq(f(xs)))) by Tautology
lib.thenHave(phi |- Q(Seq(f(xs)))) by Restate
lib.thenHave(bot) by Restate
}

case _ => proof.InvalidProofTactic("Could not get definition of function.")
}
}
}

/**
* <pre>
* Γ |- a(0) === a(1) Γ' |- a(2) === a(3) ...
* --------------------------------------------
* Γ, Γ', ... |- a(i) === a(j)
* </pre>
* Proves any equality induced transitively by the equalities of the premises.
*
* Internally, we construct an undirected graph, where sides of an equality are represented by vertices, and
* an edge between two terms `a` and `b` means that some premise proves `a === b` (or equivalently `b === a`).
* We also keep the premise from which the equality stems as a label of the edge to construct the final antecedent of
* the bottom sequent.
*
* We can see that an equality `a === b` is provable from the premises if and only if `a` is reachable from `b`.
* We thus run Breadth-First Search (BFS) on the graph starting from `a` to find the smallest solution (in terms of
* sequent calculus steps), if any.
*/
object Equalities extends ProofTactic {
def apply(using lib: Library, proof: lib.Proof)(equalities: proof.Fact*)(bot: Sequent): proof.ProofTacticJudgement = {
// Construct the graph as an adjacency list for O(1) equality checks
val graph = MutableMap[FOL.Term, List[FOL.Term]]()
val premises = MutableMap[(FOL.Term, FOL.Term), proof.Fact]()

// Use a variable to avoid non-local returns
// This is because the below loop is rewritten using maps and filters under the hood
var error: Option[proof.InvalidProofTactic] = None
for (premise <- equalities; f <- proof.getSequent(premise).right) {
f match {
case FOL.PredicateFormula(FOL.`equality`, Seq(x: FOL.Term, y: FOL.Term)) =>
if (error.isEmpty) {
// In case of conflicts, it would be too costly in the general case to find which premise is appropriate
// We simply throw an error to indicate that something is wrong with the premises
if (premises.contains((x, y)) && premises((x, y)) != premise) {
// TODO Indicate which premises lead to the error
error = Some(proof.InvalidProofTactic(s"Equality ${FOLPrinter.prettyTerm(x)} === ${FOLPrinter.prettyTerm(y)} was proven in two different premises."))
} else {
graph(x) = y :: graph.getOrElse(x, Nil)
graph(y) = x :: graph.getOrElse(y, Nil)
premises += ((x, y) -> premise)
premises += ((y, x) -> premise)
}
}
case _ =>
if (error.isEmpty) {
error = Some(proof.InvalidProofTactic("Right-hand side of premises should only contain equalities."))
}
}
}

if (error.nonEmpty) {
return error.get
}

if (bot.right.size != 1) {
return proof.InvalidProofTactic(s"Right-hand side of bottom sequent expected exactly 1 formula, got ${bot.right.size}")
}

bot.right.head match {
case FOL.PredicateFormula(FOL.`equality`, Seq(x: FOL.Term, y: FOL.Term)) =>
// Optimization in the trivial case x === x
if (isSameTerm(x, y)) {
return TacticSubproof {
lib.have(x === y) by RightRefl
if (bot.left.nonEmpty) {
lib.thenHave(bot) by Weakening
}
}
}

// Run BFS on the graph
var Q = Queue[FOL.Term](x)
val explored = MutableSet[FOL.Term](x)
val parent = MutableMap[FOL.Term, FOL.Term]()
while (Q.nonEmpty) {
val (v, newQ) = Q.dequeue
Q = newQ
if (v == y) {
lazy val traversal: LazyList[FOL.Term] = y #:: traversal.map(parent)
val path = (traversal.tail.takeWhile(_ != x) :+ x).toList // Path from y (excluded) to x (included)

def getEqTerms(using lib: Library, proof: lib.Proof)(eq: proof.Fact): (FOL.Term, FOL.Term) =
proof.getSequent(eq).right.head match {
case FOL.PredicateFormula(`equality`, Seq(a, b)) => (a, b)
case _ => throw IllegalArgumentException("Not an equality.")
}

def order(using lib: Library, proof: lib.Proof)(eq: proof.Fact, first: FOL.Term, second: FOL.Term): proof.Fact = {
val seq = proof.getSequent(eq)
seq.right.head match {
case FOL.PredicateFormula(`equality`, Seq(`first`, `second`)) => eq
case FOL.PredicateFormula(`equality`, Seq(`second`, `first`)) => {
val u = variable
lib.have(first === first) by RightRefl
lib.thenHave(second === first |- first === second) by RightSubstEq(List((second, first)), lambda(u, first === u))
lib.have(seq.left |- first === second) by Cut(eq, lib.lastStep)
}
case _ => throw IllegalArgumentException("First or last is not present in the given equality.")
}
}

return TacticSubproof { (innerProof: proof.InnerProof) ?=>
val initialStep = order(premises(y -> path.head), path.head, y)
val u = variable

path
.zip(path.tail)
.foldLeft(initialStep)((leftEq, vars) => {
val leftSeq = innerProof.getSequent(leftEq)
val (a, b) = vars
val premiseEq = premises(vars)
val premiseSeq = proof.getSequent(premiseEq)

// Apply equality transitivity on (a === y) /\ (a === b) to get (b === y)
// TODO Watch for issue #161, as assumptions will break this step
lib.have((leftSeq.left + premiseSeq.right.head) |- b === y) by RightSubstEq(
List(getEqTerms(premiseEq)),
lambda(u, u === y)
)(leftEq)
val seq = (leftSeq.left ++ premiseSeq.left) |- b === y
val eq = lib.have(seq) by Cut(premiseEq, lib.lastStep)

eq
})
}
}

for (w <- graph(v) if !explored.contains(w)) {
explored += w
parent(w) = v
Q = Q.enqueue(w)
}
}
proof.InvalidProofTactic("Equality is not provable from the premises.")

case _ => proof.InvalidProofTactic("Right-hand side of bottom sequent should be of the form x === y.")
}
}
}

/**
* <pre>
* Γ, φ |- Δ, Σ Γ, ¬φ |- Δ, Σ'
* -----------------------------
* Γ |- Δ
* </pre>
*
* TODO: Extending the tactic to more general pivots
*/
object Cases extends ProofTactic {
def withParameters(using lib: Library, proof: lib.Proof, om: OutputManager)(phi: Formula)(pos: proof.Fact, neg: proof.Fact)(bot: Sequent): proof.ProofTacticJudgement = {
val seqPos = proof.getSequent(pos)
val seqNeg = proof.getSequent(neg)

if (
!(contains(seqPos.left, phi) && contains(seqNeg.left, !phi) && !contains(seqNeg.left, phi)) &&
!(contains(seqPos.left, !phi) && contains(seqNeg.left, phi) && !contains(seqNeg.left, !phi))
) {
proof.InvalidProofTactic("The given sequent do not contain φ or ¬φ.")
} else {
val gamma = bot.left
TacticSubproof {
val excludedMiddle = lib.have(phi \/ !phi) by Tautology
val toCut = lib.have((gamma + (phi \/ !phi)) |- bot.right) by LeftOr(pos, neg)

lib.have(bot) by Cut(excludedMiddle, toCut)
}
}
}

def apply(using lib: Library, proof: lib.Proof, om: OutputManager)(pos: proof.Fact, neg: proof.Fact)(bot: Sequent): proof.ProofTacticJudgement = {
val seqPos = proof.getSequent(pos)
val pivot = seqPos.left -- bot.left

if (pivot.size != 1) {
proof.InvalidProofTactic("Could not infer correct formula φ.")
} else {
Cases.withParameters(pivot.head)(pos, neg)(bot)
}
}
}
}
79 changes: 79 additions & 0 deletions src/main/scala/lisa/mathematics/FirstOrderLogic.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package lisa.mathematics

import lisa.automation.kernel.CommonTactics.Cases
import lisa.automation.kernel.OLPropositionalSolver.Tautology
import lisa.automation.kernel.SimplePropositionalSolver.*
import lisa.automation.kernel.SimpleSimplifier.*
Expand Down Expand Up @@ -70,6 +71,46 @@ object FirstOrderLogic extends lisa.Main {
thenHave(thesis) by Restate
}

/**
* Theorem -- If `P` and `Q` are equivalent, then `∃(x, P(x))` implies `∃(x, Q(x))`.
*/
val substitutionInExistenceQuantifier = Theorem(
(∃(x, P(x)), ∀(y, P(y) <=> Q(y))) |- ∃(x, Q(x))
) {
have(P(x) |- P(x)) by Hypothesis
val substitution = thenHave((P(x), P(x) <=> Q(x)) |- Q(x)) by RightSubstIff(List((P(x), Q(x))), lambda(p, p))

have(∀(y, P(y) <=> Q(y)) |- ∀(y, P(y) <=> Q(y))) by Hypothesis
val equiv = thenHave(∀(y, P(y) <=> Q(y)) |- P(x) <=> Q(x)) by InstantiateForall(x)

have((P(x), ∀(y, P(y) <=> Q(y))) |- Q(x)) by Cut(equiv, substitution)
thenHave((P(x), ∀(y, P(y) <=> Q(y))) |- ∃(x, Q(x))) by RightExists
thenHave(thesis) by LeftExists
}

/**
* Theorem -- If `P` and `Q` are equivalent, then `∃!(x, P(x))` implies `∃!(x, Q(x))`.
*/
val substitutionInUniquenessQuantifier = Theorem(
(∃!(x, P(x)), ∀(y, P(y) <=> Q(y))) |- ∃!(x, Q(x))
) {
have((x === y) <=> P(y) |- (x === y) <=> P(y)) by Hypothesis
thenHave(∀(y, (x === y) <=> P(y)) |- (x === y) <=> P(y)) by LeftForall
val substitution = thenHave((∀(y, (x === y) <=> P(y)), P(y) <=> Q(y)) |- (x === y) <=> Q(y)) by RightSubstIff(
List((P(y), Q(y))),
lambda(p, (x === y) <=> p)
)

have(∀(y, P(y) <=> Q(y)) |- ∀(y, P(y) <=> Q(y))) by Hypothesis
val equiv = thenHave(∀(y, P(y) <=> Q(y)) |- P(y) <=> Q(y)) by InstantiateForall(y)

have((∀(y, (x === y) <=> P(y)), ∀(y, P(y) <=> Q(y))) |- (x === y) <=> Q(y)) by Cut(equiv, substitution)
thenHave((∀(y, (x === y) <=> P(y)), ∀(y, P(y) <=> Q(y))) |- ∀(y, (x === y) <=> Q(y))) by RightForall
thenHave((∀(y, (x === y) <=> P(y)), ∀(y, P(y) <=> Q(y))) |- ∃(x, ∀(y, (x === y) <=> Q(y)))) by RightExists
thenHave((∃(x, ∀(y, (x === y) <=> P(y))), ∀(y, P(y) <=> Q(y))) |- ∃(x, ∀(y, (x === y) <=> Q(y)))) by LeftExists
thenHave(thesis) by Restate
}

/**
* Theorem --- Equality relation is transitive.
*/
Expand Down Expand Up @@ -354,4 +395,42 @@ object FirstOrderLogic extends lisa.Main {
thenHave(thesis) by Restate
}

/**
* Theorem --- Unique existential quantifier distributes fully with a closed formula.
*/
val uniqueExistentialConjunctionWithClosedFormula = Theorem(
existsOne(x, P(x) /\ p()) <=> (existsOne(x, P(x)) /\ p())
) {
val pos = have(p() |- existsOne(x, P(x) /\ p()) <=> (existsOne(x, P(x)) /\ p())) subproof {
have(p() |- (P(x) /\ p()) <=> P(x)) by Tautology
thenHave(p() |- forall(x, (P(x) /\ p()) <=> P(x))) by RightForall

have(p() |- existsOne(x, P(x) /\ p()) <=> (existsOne(x, P(x)))) by Cut(
lastStep,
uniqueExistentialEquivalenceDistribution of (
P -> lambda(x, P(x) /\ p()),
Q -> lambda(x, P(x))
)
)
thenHave(thesis) by Tautology
}

val neg = have(!p() |- existsOne(x, P(x) /\ p()) <=> (existsOne(x, P(x)) /\ p())) subproof {
have((!p(), (x === x) <=> (P(x) /\ p())) |- p()) by Tautology
thenHave((!p(), forall(y, (x === y) <=> (P(y) /\ p()))) |- p()) by LeftForall
thenHave((!p(), exists(x, forall(y, (x === y) <=> (P(y) /\ p())))) |- p()) by LeftExists
thenHave((!p(), existsOne(x, P(x) /\ p())) |- p()) by LeftExistsOne
thenHave((!p(), existsOne(x, P(x) /\ p())) |- ()) by LeftNot
thenHave((!p(), existsOne(x, P(x) /\ p())) |- existsOne(x, P(x)) /\ p()) by Weakening
val forward = thenHave(!p() |- existsOne(x, P(x) /\ p()) ==> (existsOne(x, P(x)) /\ p())) by Restate

have((!p(), existsOne(x, P(x)) /\ p()) |- ()) by Tautology
thenHave((!p(), existsOne(x, P(x)) /\ p()) |- existsOne(x, P(x) /\ p())) by Weakening
val backward = thenHave(!p() |- existsOne(x, P(x)) /\ p() ==> existsOne(x, P(x) /\ p())) by Restate

have(thesis) by RightIff(forward, backward)
}

have(thesis) by Cases(pos, neg)
}
}
Loading