diff --git a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
index c72300e20..8c606648b 100644
--- a/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
+++ b/lisa-utils/src/main/scala/lisa/utils/KernelHelpers.scala
@@ -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 */
diff --git a/src/main/scala/lisa/automation/kernel/CommonTactics.scala b/src/main/scala/lisa/automation/kernel/CommonTactics.scala
index 3ed8d5c18..ee401b17b 100644
--- a/src/main/scala/lisa/automation/kernel/CommonTactics.scala
+++ b/src/main/scala/lisa/automation/kernel/CommonTactics.scala
@@ -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 {
/**
@@ -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
@@ -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)
}
@@ -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.")
}
@@ -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.")
}
}
}
+
+ /**
+ *
+ * Γ |- a(0) === a(1) Γ' |- a(2) === a(3) ...
+ * --------------------------------------------
+ * Γ, Γ', ... |- a(i) === a(j)
+ *
+ * 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.")
+ }
+ }
+ }
+
+ /**
+ *
+ * Γ, φ |- Δ, Σ Γ, ¬φ |- Δ, Σ'
+ * -----------------------------
+ * Γ |- Δ
+ *
+ *
+ * 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)
+ }
+ }
+ }
}
diff --git a/src/main/scala/lisa/mathematics/FirstOrderLogic.scala b/src/main/scala/lisa/mathematics/FirstOrderLogic.scala
index 4b77cdbee..1c695fc1a 100644
--- a/src/main/scala/lisa/mathematics/FirstOrderLogic.scala
+++ b/src/main/scala/lisa/mathematics/FirstOrderLogic.scala
@@ -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.*
@@ -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.
*/
@@ -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)
+ }
}
diff --git a/src/main/scala/lisa/mathematics/GroupTheory.scala b/src/main/scala/lisa/mathematics/GroupTheory.scala
new file mode 100644
index 000000000..09d9c0715
--- /dev/null
+++ b/src/main/scala/lisa/mathematics/GroupTheory.scala
@@ -0,0 +1,1913 @@
+package lisa.mathematics
+
+import lisa.automation.kernel.CommonTactics.Cases
+import lisa.automation.kernel.CommonTactics.Definition
+import lisa.automation.kernel.CommonTactics.Equalities
+import lisa.automation.kernel.CommonTactics.ExistenceAndUniqueness
+import lisa.automation.kernel.OLPropositionalSolver.Tautology
+import lisa.automation.settheory.SetTheoryTactics.UniqueComprehension
+import lisa.kernel.proof.SequentCalculus.Hypothesis
+import lisa.mathematics.FirstOrderLogic.equalityTransitivity
+import lisa.mathematics.FirstOrderLogic.existsOneImpliesExists
+import lisa.mathematics.FirstOrderLogic.substitutionInUniquenessQuantifier
+import lisa.mathematics.SetTheory.*
+
+/**
+ * Group theory, developed following Chapter 2 of S. Lang "Undergraduate Algebra".
+ *
+ * Book : [[https://link.springer.com/book/10.1007/978-1-4684-9234-7]]
+ */
+object GroupTheory extends lisa.Main {
+ // Groups
+ private val G, H = variable
+
+ // Group laws
+ private val * = variable
+
+ // Group elements
+ private val a, b, c, d = variable
+ private val x, y, z = variable
+ private val t, u, v, w = variable
+
+ // Identity elements
+ private val e, f = variable
+
+ // Predicates
+ private val P, Q = predicate(1)
+
+ //
+ // 0. Notation
+ //
+
+ /**
+ * Defines the element that is uniquely given by the uniqueness theorem, or falls back to the error element if the
+ * assumptions of the theorem are not satisfied.
+ *
+ * This is useful in defining specific elements in groups, where their uniqueness (and existence) strongly rely
+ * on the assumption of the group structure.
+ */
+ def TheConditional(u: VariableLabel, f: Formula)(just: runningSetTheory.Theorem, defaultValue: Term = ∅): The = {
+ val seq = just.proposition
+
+ if (seq.left.isEmpty) {
+ The(u, f)(just)
+ } else {
+ val prem = if (seq.left.size == 1) seq.left.head else And(seq.left.toSeq: _*)
+ val completeDef = (prem ==> f) /\ (!prem ==> (u === defaultValue))
+ val substF = substituteVariables(completeDef, Map[VariableLabel, Term](u -> defaultValue), Seq())
+ val substDef = substituteVariables(completeDef, Map[VariableLabel, Term](u -> v), Seq())
+
+ val completeUniquenessTheorem = Lemma(
+ ∃!(u, completeDef)
+ ) {
+ val case1 = have(prem |- ∃!(u, completeDef)) subproof {
+ // We prove the equivalence f <=> completeDef so that we can substitute it in the uniqueness quantifier
+ val equiv = have(prem |- ∀(u, f <=> completeDef)) subproof {
+ have(f |- f) by Hypothesis
+ thenHave((prem, f) |- f) by Weakening
+ val left = thenHave(f |- (prem ==> f)) by Restate
+
+ have(prem |- prem) by Hypothesis
+ thenHave((prem, !prem) |- ()) by LeftNot
+ thenHave((prem, !prem) |- (u === defaultValue)) by Weakening
+ val right = thenHave(prem |- (!prem ==> (u === defaultValue))) by Restate
+
+ have((prem, f) |- completeDef) by RightAnd(left, right)
+ val forward = thenHave(prem |- f ==> completeDef) by Restate
+
+ have(completeDef |- completeDef) by Hypothesis
+ thenHave((prem, completeDef) |- completeDef) by Weakening
+ thenHave((prem, completeDef) |- f) by Tautology
+ val backward = thenHave(prem |- completeDef ==> f) by Restate
+
+ have(prem |- f <=> completeDef) by RightIff(forward, backward)
+ thenHave(thesis) by RightForall
+ }
+
+ val substitution = have((∃!(u, f), ∀(u, f <=> completeDef)) |- ∃!(u, completeDef)) by Restate.from(
+ substitutionInUniquenessQuantifier of (P -> lambda(u, f), Q -> lambda(u, completeDef))
+ )
+
+ val implication = have((prem, ∃!(u, f)) |- ∃!(u, completeDef)) by Cut(equiv, substitution)
+ val uniqueness = have(prem |- ∃!(u, f)) by Restate.from(just)
+ have(prem |- ∃!(u, completeDef)) by Cut(uniqueness, implication)
+ }
+
+ val case2 = have(!prem |- ∃!(u, completeDef)) subproof {
+ val existence = have(!prem |- ∃(u, completeDef)) subproof {
+ have(!prem |- !prem) by Hypothesis
+ thenHave((prem, !prem) |- ()) by LeftNot
+ thenHave((prem, !prem) |- substF) by Weakening
+ val left = thenHave(!prem |- (prem ==> substF)) by Restate
+
+ have(defaultValue === defaultValue) by RightRefl
+ thenHave(!prem |- defaultValue === defaultValue) by Weakening
+ val right = thenHave(!prem ==> (defaultValue === defaultValue)) by Restate
+
+ have(!prem |- (prem ==> substF) /\ (!prem ==> (defaultValue === defaultValue))) by RightAnd(left, right)
+ thenHave(thesis) by RightExists.withParameters(defaultValue)
+ }
+
+ val uniqueness = have((!prem, completeDef, substDef) |- (u === v)) subproof {
+ assume(!prem)
+ assume(completeDef)
+ assume(substDef)
+
+ val eq1 = have(u === defaultValue) by Tautology
+ val eq2 = have(defaultValue === v) by Tautology
+ val p = have((u === defaultValue) /\ (defaultValue === v)) by RightAnd(eq1, eq2)
+
+ val transitivity = equalityTransitivity of (x -> u, y -> defaultValue, z -> v)
+ have(thesis) by Cut(p, transitivity)
+ }
+
+ have(thesis) by ExistenceAndUniqueness(completeDef)(existence, uniqueness)
+ }
+
+ have(thesis) by Cases(case1, case2)
+ }
+
+ The(u, completeDef)(completeUniquenessTheorem)
+ }
+ }
+
+ //
+ // 1. Basic definitions and results
+ //
+
+ /**
+ * Binary operation --- `*` is a binary operation on `G` if it associates to each pair of elements of `G`
+ * a unique element in `G`. In other words, `*` is a function `G × G -> G`.
+ */
+ val binaryOperation = DEF(G, *) --> functionFrom(*, cartesianProduct(G, G), G)
+
+ /**
+ * Short-hand alias for `x * y`.
+ */
+ inline def op(x: Term, * : Term, y: Term) = app(*, pair(x, y))
+
+ /**
+ * Associativity --- `*` is associative (in `G`) if `(x * y) * z = x * (y * z)` for all `x, y, z` in `G`.
+ */
+ val associativityAxiom = DEF(G, *) -->
+ ∀(x, x ∈ G ==> ∀(y, y ∈ G ==> ∀(z, z ∈ G ==> (op(op(x, *, y), *, z) === op(x, *, op(y, *, z))))))
+
+ /**
+ * Neutral element --- We say that an element `e` in `G` is neutral if `e * x = x * e = x` for all `x` in `G`.
+ */
+ val isNeutral = DEF(e, G, *) --> (e ∈ G /\ ∀(x, (x ∈ G) ==> ((op(e, *, x) === x) /\ (op(x, *, e) === x))))
+
+ /**
+ * Identity existence --- There exists a neutral element `e` in `G`.
+ */
+ val identityExistence = DEF(G, *) --> ∃(e, isNeutral(e, G, *))
+
+ /**
+ * Inverse element --- `y` is called an inverse of `x` if `x * y = y * x = e`.
+ */
+ val isInverse = DEF(y, x, G, *) --> (y ∈ G) /\ isNeutral(op(x, *, y), G, *) /\ isNeutral(op(y, *, x), G, *)
+
+ /**
+ * Inverse existence --- For all `x` in G, there exists an element `y` in G such that `x * y = y * x = e`.
+ */
+ val inverseExistence = DEF(G, *) --> ∀(x, (x ∈ G) ==> ∃(y, isInverse(y, x, G, *)))
+
+ /**
+ * Group --- A group (G, *) is a set along with a law of composition `*`, satisfying [[associativityAxiom]], [[identityExistence]]
+ * and [[inverseExistence]].
+ */
+ val group = DEF(G, *) --> binaryOperation(G, *) /\ associativityAxiom(G, *) /\ identityExistence(G, *) /\ inverseExistence(G, *)
+
+ /**
+ * Commutativity --- `*` is said to be commutative on `G` if `x * y = y * x` for all `x, y ∈ G`.
+ */
+ val commutativityAxiom = DEF(G, *) --> ∀(x, x ∈ G ==> ∀(y, y ∈ G ==> (op(x, *, y) === op(y, *, x))))
+
+ /**
+ * Abelian group --- A group is said to be abelian (or commutative) if every element commutes,
+ * i.e. it satisfies [[commutativityAxiom]].
+ */
+ val abelianGroup = DEF(G, *) --> group(G, *) /\ commutativityAxiom(G, *)
+
+ /**
+ * Alias for abelian group.
+ */
+ val commutativeGroup = abelianGroup
+
+ /**
+ * Lemma --- For elements `x, y, z` in a group `(G, *)`, we have `(xy)z = x(yz)`.
+ *
+ * Practical reformulation of the [[associativityAxiom]].
+ */
+ val associativity = Lemma(
+ (group(G, *), x ∈ G, y ∈ G, z ∈ G) |- op(op(x, *, y), *, z) === op(x, *, op(y, *, z))
+ ) {
+ assume(group(G, *))
+
+ have(∀(x, x ∈ G ==> ∀(y, y ∈ G ==> ∀(z, z ∈ G ==> (op(op(x, *, y), *, z) === op(x, *, op(y, *, z))))))) by Tautology.from(
+ group.definition,
+ associativityAxiom.definition
+ )
+ thenHave(x ∈ G ==> ∀(y, y ∈ G ==> ∀(z, z ∈ G ==> (op(op(x, *, y), *, z) === op(x, *, op(y, *, z)))))) by InstantiateForall(x)
+ thenHave(x ∈ G |- ∀(y, y ∈ G ==> ∀(z, z ∈ G ==> (op(op(x, *, y), *, z) === op(x, *, op(y, *, z)))))) by Restate
+ thenHave(x ∈ G |- y ∈ G ==> ∀(z, z ∈ G ==> (op(op(x, *, y), *, z) === op(x, *, op(y, *, z))))) by InstantiateForall(y)
+ thenHave((x ∈ G, y ∈ G) |- ∀(z, z ∈ G ==> (op(op(x, *, y), *, z) === op(x, *, op(y, *, z))))) by Restate
+ thenHave((x ∈ G, y ∈ G) |- z ∈ G ==> (op(op(x, *, y), *, z) === op(x, *, op(y, *, z)))) by InstantiateForall(z)
+ thenHave((x ∈ G, y ∈ G, z ∈ G) |- (op(op(x, *, y), *, z) === op(x, *, op(y, *, z)))) by Restate
+ }
+
+ /**
+ * Lemma --- For elements `x, y` in an abelian group `(G, *)`, we have `xy = yx`.
+ *
+ * Practical reformulation of [[commutativityAxiom]].
+ */
+ val commutativity = Lemma(
+ (abelianGroup(G, *), x ∈ G, y ∈ G) |- op(x, *, y) === op(y, *, x)
+ ) {
+ assume(abelianGroup(G, *))
+
+ have(∀(x, x ∈ G ==> ∀(y, y ∈ G ==> (op(x, *, y) === op(y, *, x))))) by Tautology.from(
+ abelianGroup.definition,
+ commutativityAxiom.definition
+ )
+ thenHave(x ∈ G ==> ∀(y, y ∈ G ==> (op(x, *, y) === op(y, *, x)))) by InstantiateForall(x)
+ thenHave(x ∈ G |- ∀(y, y ∈ G ==> (op(x, *, y) === op(y, *, x)))) by Restate
+ thenHave(x ∈ G |- (y ∈ G ==> (op(x, *, y) === op(y, *, x)))) by InstantiateForall(y)
+ thenHave((x ∈ G, y ∈ G) |- ((op(x, *, y) === op(y, *, x)))) by Restate
+ }
+
+ /**
+ * Group operation is functional -- The group operation `*` is functional.
+ */
+ val groupOperationIsFunctional = Lemma(
+ group(G, *) |- functional(*)
+ ) {
+ have(thesis) by Tautology.from(
+ group.definition,
+ binaryOperation.definition,
+ functionFromImpliesFunctional of (f -> *, x -> cartesianProduct(G, G), y -> G)
+ )
+ }
+
+ /**
+ * Group operation domain -- The domain of a group law is the cartesian product of the group `G` with itself.
+ *
+ * Follows directly from the definition of `binaryRelation`.
+ */
+ val groupOperationDomain = Lemma(
+ group(G, *) |- relationDomain(*) === cartesianProduct(G, G)
+ ) {
+ have(thesis) by Tautology.from(
+ group.definition,
+ binaryOperation.definition,
+ functionFromImpliesDomainEq of (f -> *, x -> cartesianProduct(G, G), y -> G)
+ )
+ }
+
+ /**
+ * Lemma --- If `x` and `y` are two elements of the group, the pair `(x, y)` is in the relation domain of `*.
+ */
+ val groupPairInOperationDomain = Lemma(
+ (group(G, *), x ∈ G, y ∈ G) |- pair(x, y) ∈ relationDomain(*)
+ ) {
+ assume(group(G, *))
+ assume(x ∈ G)
+ assume(y ∈ G)
+
+ have(x ∈ G /\ y ∈ G) by Tautology
+ have(pair(x, y) ∈ cartesianProduct(G, G)) by Tautology.from(
+ lastStep,
+ pairInCartesianProduct of (a -> x, b -> y, x -> G, y -> G)
+ )
+ thenHave((relationDomain(*) === cartesianProduct(G, G)) |- pair(x, y) ∈ relationDomain(*)) by RightSubstEq(
+ List((relationDomain(*), cartesianProduct(G, G))),
+ lambda(z, pair(x, y) ∈ z)
+ )
+
+ have(thesis) by Cut(groupOperationDomain, lastStep)
+ }
+
+ /**
+ * Lemma --- If `x, y ∈ G`, then `x * y ∈ G`.
+ */
+ val groupIsClosedByProduct = Lemma(
+ (group(G, *), x ∈ G, y ∈ G) |- op(x, *, y) ∈ G
+ ) {
+ have(∀(t, (t ∈ relationRange(*)) <=> ∃(a, pair(a, t) ∈ *))) by Definition(relationRange, relationRangeUniqueness)(*)
+ val relationRangeDef = thenHave((op(x, *, y) ∈ relationRange(*)) <=> ∃(a, pair(a, op(x, *, y)) ∈ *)) by InstantiateForall(op(x, *, y))
+
+ val appDef = have(
+ (functional(*), pair(x, y) ∈ relationDomain(*)) |- pair(pair(x, y), op(x, *, y)) ∈ *
+ ) by Definition(app, functionApplicationUniqueness)(*, pair(x, y))
+
+ assume(group(G, *))
+ assume(x ∈ G)
+ assume(y ∈ G)
+
+ // Show that x * y is in relation range
+ have(pair(pair(x, y), op(x, *, y)) ∈ *) by Tautology.from(
+ appDef,
+ groupOperationIsFunctional,
+ groupPairInOperationDomain
+ )
+ thenHave(∃(a, pair(a, op(x, *, y)) ∈ *)) by RightExists
+
+ val productInRelationRange = have(op(x, *, y) ∈ relationRange(*)) by Tautology.from(lastStep, relationRangeDef)
+
+ // Conclude by [[functionImpliesRangeSubsetOfCodomain]]
+ have(∀(t, t ∈ relationRange(*) ==> t ∈ G)) by Tautology.from(
+ group.definition,
+ binaryOperation.definition,
+ functionImpliesRangeSubsetOfCodomain of (f -> *, x -> cartesianProduct(G, G), y -> G),
+ subset.definition of (x -> relationRange(*), y -> G)
+ )
+ thenHave(op(x, *, y) ∈ relationRange(*) ==> op(x, *, y) ∈ G) by InstantiateForall(op(x, *, y))
+ thenHave(op(x, *, y) ∈ relationRange(*) |- op(x, *, y) ∈ G) by Restate
+
+ have(thesis) by Cut(productInRelationRange, lastStep)
+ }
+
+ /**
+ * Identity uniqueness --- In a group (G, *), an identity element is unique, i.e. if both `e * x = x * e = x` and
+ * `f * x = x * f = x` for all `x`, then `e = f`.
+ *
+ * This justifies calling `e` the identity element.
+ */
+ val identityUniqueness = Theorem(
+ group(G, *) |- ∃!(e, isNeutral(e, G, *))
+ ) {
+ val existence = have(group(G, *) |- ∃(e, isNeutral(e, G, *))) by Tautology.from(group.definition, identityExistence.definition)
+
+ // We prove that if e and f are neutral elements then ef = f = e, where the first equality comes from e's left neutrality,
+ // and the second equality from f's right neutrality
+ val uniqueness = have((isNeutral(e, G, *), isNeutral(f, G, *)) |- (e === f)) subproof {
+ // We prove that neutral elements are elements of G, such that * can be applied.
+ val membership = have(isNeutral(e, G, *) |- e ∈ G) by Tautology.from(isNeutral.definition)
+
+ assume(isNeutral(e, G, *))
+ assume(isNeutral(f, G, *))
+
+ // 1. ef = f
+ have(∀(x, x ∈ G ==> ((op(e, *, x) === x) /\ (op(x, *, e) === x)))) by Tautology.from(isNeutral.definition)
+ thenHave(f ∈ G ==> ((op(e, *, f) === f) /\ (op(f, *, e) === f))) by InstantiateForall(f)
+ val neutrality = thenHave(f ∈ G |- ((op(e, *, f) === f) /\ (op(f, *, e) === f))) by Restate
+
+ have((op(e, *, f) === f) /\ (op(f, *, e) === f)) by Cut(membership of (e -> f), neutrality)
+ val firstEq = thenHave(op(e, *, f) === f) by Tautology
+
+ // 2. ef = e
+ have((op(f, *, e) === e) /\ (op(e, *, f) === e)) by Cut(membership of (e -> e), neutrality of (e -> f, f -> e))
+ val secondEq = thenHave(e === op(e, *, f)) by Tautology
+
+ // 3. Conclude by transitivity
+ have(e === f) by Equalities(firstEq, secondEq)
+ }
+
+ have(group(G, *) |- ∃!(e, isNeutral(e, G, *))) by ExistenceAndUniqueness(isNeutral(e, G, *))(existence, uniqueness)
+ }
+
+ /**
+ * Defines the identity element of `(G, *)`.
+ */
+ val identity = DEF(G, *) --> TheConditional(e, isNeutral(e, G, *))(identityUniqueness)
+
+ /**
+ * Lemma --- The identity element is neutral by definition.
+ */
+ private val identityIsNeutral = Lemma(
+ group(G, *) |- isNeutral(identity(G, *), G, *)
+ ) {
+ have(thesis) by Definition(identity, identityUniqueness)(G, *)
+ }
+
+ /**
+ * Lemma --- For any element `x` in a group `(G, *)`, we have `x * e = e * x = x`.
+ *
+ * Practical reformulation of [[identityIsNeutral]].
+ */
+ val identityNeutrality = Lemma(
+ (group(G, *), x ∈ G) |- (op(identity(G, *), *, x) === x) /\ (op(x, *, identity(G, *)) === x)
+ ) {
+ have(group(G, *) |- ∀(x, (x ∈ G) ==> ((op(identity(G, *), *, x) === x) /\ (op(x, *, identity(G, *)) === x)))) by Tautology.from(
+ identityIsNeutral,
+ isNeutral.definition of (e -> identity(G, *))
+ )
+ thenHave(group(G, *) |- (x ∈ G) ==> ((op(identity(G, *), *, x) === x) /\ (op(x, *, identity(G, *)) === x))) by InstantiateForall(x)
+ thenHave(thesis) by Restate
+ }
+
+ /**
+ * Theorem --- The identity element belongs to the group.
+ *
+ * This simple theorem has unexpected consequences, such as [[groupNonEmpty]].
+ */
+ val identityInGroup = Theorem(
+ group(G, *) |- identity(G, *) ∈ G
+ ) {
+ have(thesis) by Tautology.from(
+ identityIsNeutral,
+ isNeutral.definition of (e -> identity(G, *))
+ )
+ }
+
+ /**
+ * Theorem --- A group is non-empty.
+ *
+ * Direct corollary of [[identityInGroup]].
+ */
+ val groupNonEmpty = Theorem(
+ group(G, *) |- (G !== ∅)
+ ) {
+ have(thesis) by Cut(identityInGroup, setWithElementNonEmpty of (x -> G, y -> identity(G, *)))
+ }
+
+ /**
+ * Theorem --- The inverse of an element `x` (i.e. `y` such that `x * y = y * x = e`) in `G` is unique.
+ */
+ val inverseUniqueness = Theorem(
+ (group(G, *), x ∈ G) |- ∃!(y, isInverse(y, x, G, *))
+ ) {
+ have(group(G, *) |- ∀(x, x ∈ G ==> ∃(y, isInverse(y, x, G, *)))) by Tautology.from(group.definition, inverseExistence.definition)
+ thenHave(group(G, *) |- (x ∈ G ==> ∃(y, isInverse(y, x, G, *)))) by InstantiateForall(x)
+ val existence = thenHave((group(G, *), x ∈ G) |- ∃(y, isInverse(y, x, G, *))) by Restate
+
+ // Assume y and z are inverses of x.
+ // We prove the following chain of equalities:
+ // z === (yx)z === y(xz) === y
+ // where equalities come from
+ // 1. Left neutrality of yx
+ // 2. Associativity
+ // 3. Right neutrality of xz
+ val uniqueness = have((group(G, *), x ∈ G, isInverse(y, x, G, *), isInverse(z, x, G, *)) |- (y === z)) subproof {
+ val inverseMembership = have(isInverse(y, x, G, *) |- y ∈ G) by Tautology.from(isInverse.definition)
+
+ // 1. (yx)z = z
+ val leftNeutrality = have((group(G, *), x ∈ G, isInverse(y, x, G, *), z ∈ G) |- (op(op(y, *, x), *, z) === z)) subproof {
+ assume(group(G, *))
+ assume(x ∈ G)
+ assume(isInverse(y, x, G, *))
+ assume(z ∈ G)
+
+ have(∀(u, u ∈ G ==> ((op(op(y, *, x), *, u) === u) /\ (op(u, *, op(y, *, x)) === u)))) by Tautology.from(isInverse.definition, isNeutral.definition of (e -> op(y, *, x)))
+ thenHave(z ∈ G ==> ((op(op(y, *, x), *, z) === z) /\ (op(z, *, op(y, *, x)) === z))) by InstantiateForall(z)
+ thenHave(op(op(y, *, x), *, z) === z) by Tautology
+ }
+ val firstEq = have((group(G, *), x ∈ G, isInverse(y, x, G, *), isInverse(z, x, G, *)) |- op(op(y, *, x), *, z) === z) by Cut(inverseMembership of (y -> z), leftNeutrality)
+
+ // 2. (yx)z = y(xz)
+ val associativityCut = have((group(G, *), x ∈ G /\ y ∈ G /\ z ∈ G) |- (op(op(y, *, x), *, z) === op(y, *, op(x, *, z)))) by Restate.from(
+ associativity of (x -> y, y -> x)
+ )
+ val memberships = have((x ∈ G, isInverse(y, x, G, *), isInverse(z, x, G, *)) |- x ∈ G /\ y ∈ G /\ z ∈ G) by Tautology.from(inverseMembership of (y -> y), inverseMembership of (y -> z))
+ val secondEq = have((group(G, *), x ∈ G, isInverse(y, x, G, *), isInverse(z, x, G, *)) |- op(op(y, *, x), *, z) === op(y, *, op(x, *, z))) by Cut(memberships, associativityCut)
+
+ // 3. y(xz) = y
+ val rightNeutrality = have((group(G, *), x ∈ G, y ∈ G, isInverse(z, x, G, *)) |- (op(y, *, op(x, *, z)) === y)) subproof {
+ assume(group(G, *))
+ assume(x ∈ G)
+ assume(y ∈ G)
+ assume(isInverse(z, x, G, *))
+
+ have(∀(u, u ∈ G ==> ((op(op(x, *, z), *, u) === u) /\ (op(u, *, op(x, *, z)) === u)))) by Tautology.from(isInverse.definition of (y -> z), isNeutral.definition of (e -> op(x, *, z)))
+ thenHave(y ∈ G ==> ((op(op(x, *, z), *, y) === y) /\ (op(y, *, op(x, *, z)) === y))) by InstantiateForall(y)
+ thenHave(op(y, *, op(x, *, z)) === y) by Tautology
+ }
+ val thirdEq = have((group(G, *), x ∈ G, isInverse(y, x, G, *), isInverse(z, x, G, *)) |- op(y, *, op(x, *, z)) === y) by Cut(inverseMembership of (y -> y), rightNeutrality)
+
+ // 4. z = y
+ have((group(G, *), x ∈ G, isInverse(y, x, G, *), isInverse(z, x, G, *)) |- z === y) by Equalities(firstEq, secondEq, thirdEq)
+ }
+
+ have(thesis) by ExistenceAndUniqueness(isInverse(y, x, G, *))(existence, uniqueness)
+ }
+
+ /**
+ * Defines the inverse of an element `x` in a group `(G, *)`.
+ */
+ val inverse = DEF(x, G, *) --> TheConditional(y, isInverse(y, x, G, *))(inverseUniqueness)
+
+ /**
+ * Lemma --- The inverse of `x` is an inverse of `x` (by definition).
+ */
+ private val inverseIsInverse = Lemma(
+ (group(G, *), x ∈ G) |- isInverse(inverse(x, G, *), x, G, *)
+ ) {
+ have(thesis) by Definition(inverse, inverseUniqueness)(x, G, *)
+ }
+
+ /**
+ * Lemma --- The inverse element `y` of `x` is in `G`.
+ */
+ val inverseInGroup = Lemma(
+ (group(G, *), x ∈ G) |- inverse(x, G, *) ∈ G
+ ) {
+ have(thesis) by Tautology.from(
+ inverseIsInverse,
+ isInverse.definition of (y -> inverse(x, G, *))
+ )
+ }
+
+ /**
+ * Theorem --- For any element `x`, we have `x * inverse(x) = inverse(x) * x = e`.
+ */
+ val inverseCancellation = Theorem(
+ (group(G, *), x ∈ G) |- (op(x, *, inverse(x, G, *)) === identity(G, *)) /\ (op(inverse(x, G, *), *, x) === identity(G, *))
+ ) {
+ assume(group(G, *))
+
+ have(∀(y, (y === identity(G, *)) <=> isNeutral(y, G, *))) by Tautology.from(
+ identity.definition,
+ identityUniqueness
+ )
+ val idCharacterization = thenHave((y === identity(G, *)) <=> isNeutral(y, G, *)) by InstantiateForall(y)
+
+ assume(x ∈ G)
+ val inverseDef = have((inverse(x, G, *) ∈ G) /\ isNeutral(op(x, *, inverse(x, G, *)), G, *) /\ isNeutral(op(inverse(x, G, *), *, x), G, *)) by Tautology.from(
+ inverseIsInverse,
+ isInverse.definition of (y -> inverse(x, G, *))
+ )
+
+ val left = have(op(x, *, inverse(x, G, *)) === identity(G, *)) by Tautology.from(
+ inverseDef,
+ idCharacterization of (y -> op(x, *, inverse(x, G, *)))
+ )
+ val right = have(op(inverse(x, G, *), *, x) === identity(G, *)) by Tautology.from(
+ inverseDef,
+ idCharacterization of (y -> op(inverse(x, G, *), *, x))
+ )
+
+ have(thesis) by RightAnd(left, right)
+ }
+
+ /**
+ * Theorem --- `y` is the inverse of `x` iff `x` is the inverse of `y`
+ */
+ val inverseSymmetry = Theorem(
+ (group(G, *), x ∈ G, y ∈ G) |- (y === inverse(x, G, *)) <=> (x === inverse(y, G, *))
+ ) {
+ assume(group(G, *))
+
+ val inverseCharacterization = have(x ∈ G |- ((y === inverse(x, G, *)) <=> isInverse(y, x, G, *))) subproof {
+ have(x ∈ G |- ∀(y, (y === inverse(x, G, *)) <=> isInverse(y, x, G, *))) by Tautology.from(inverseUniqueness, inverse.definition)
+ thenHave(thesis) by InstantiateForall(y)
+ }
+
+ val forward = have(x ∈ G |- isInverse(y, x, G, *) ==> isInverse(x, y, G, *)) subproof {
+ assume(x ∈ G)
+ have(isInverse(y, x, G, *) |- y ∈ G /\ isNeutral(op(x, *, y), G, *) /\ isNeutral(op(y, *, x), G, *)) by Tautology.from(isInverse.definition)
+ thenHave(isInverse(y, x, G, *) |- isNeutral(op(y, *, x), G, *) /\ isNeutral(op(x, *, y), G, *)) by Tautology
+ thenHave(isInverse(y, x, G, *) |- x ∈ G /\ isNeutral(op(y, *, x), G, *) /\ isNeutral(op(x, *, y), G, *)) by Tautology
+
+ have(isInverse(y, x, G, *) |- isInverse(x, y, G, *)) by Tautology.from(lastStep, isInverse.definition of (y -> x, x -> y))
+ thenHave(thesis) by Restate
+ }
+
+ val backward = forward of (x -> y, y -> x)
+
+ have((x ∈ G, y ∈ G) |- isInverse(y, x, G, *) <=> isInverse(x, y, G, *)) by RightIff(forward, backward)
+
+ have(thesis) by Tautology.from(
+ inverseCharacterization,
+ lastStep,
+ inverseCharacterization of (x -> y, y -> x)
+ )
+ }
+
+ /**
+ * Involution of the inverse -- For all `x`, `inverse(inverse(x)) = x`.
+ *
+ * Direct corollary of [[inverseSymmetry]].
+ */
+ val inverseIsInvolutive = Theorem(
+ (group(G, *), x ∈ G) |- (inverse(inverse(x, G, *), G, *) === x)
+ ) {
+ have(thesis) by Tautology.from(
+ inverseSymmetry of (y -> inverse(x, G, *)),
+ inverseInGroup
+ )
+ }
+
+ /**
+ * Theorem --- In a group `(G, *)`, we have `xy = xz ==> y = z`.
+ */
+ val leftCancellation = Theorem(
+ (group(G, *), x ∈ G, y ∈ G, z ∈ G) |- (op(x, *, y) === op(x, *, z)) ==> (y === z)
+ ) {
+ val i = inverse(x, G, *)
+
+ // 1. Prove that i * (xy) = y and i * (xz) = z
+ val cancellation = have((group(G, *), x ∈ G, y ∈ G) |- op(i, *, op(x, *, y)) === y) subproof {
+ // (ix)y = i(xy)
+ val eq1 = have((group(G, *), x ∈ G, y ∈ G) |- op(op(i, *, x), *, y) === op(i, *, op(x, *, y))) by Cut(
+ inverseInGroup,
+ associativity of (x -> i, y -> x, z -> y)
+ )
+
+ // (ix)y = y
+ have((group(G, *), x ∈ G) |- ∀(y, (y ∈ G) ==> ((op(op(i, *, x), *, y) === y) /\ (op(y, *, op(i, *, x)) === y)))) by Tautology.from(
+ inverseIsInverse,
+ isInverse.definition of (y -> i),
+ isNeutral.definition of (e -> op(i, *, x))
+ )
+ thenHave((group(G, *), x ∈ G) |- (y ∈ G) ==> ((op(op(i, *, x), *, y) === y) /\ (op(y, *, op(i, *, x)) === y))) by InstantiateForall(y)
+ val eq2 = thenHave((group(G, *), x ∈ G, y ∈ G) |- op(op(i, *, x), *, y) === y) by Tautology
+
+ // i(xy) = y
+ have(thesis) by Equalities(eq1, eq2)
+ }
+
+ // 2. By substitution, xy = xz implies i(xy) = i(xz)
+ have(op(i, *, op(x, *, y)) === op(i, *, op(x, *, y))) by RightRefl
+ val substitution = thenHave(op(x, *, y) === op(x, *, z) |- op(i, *, op(x, *, y)) === op(i, *, op(x, *, z))) by RightSubstEq(
+ List((op(x, *, y), op(x, *, z))),
+ lambda(z, op(i, *, op(x, *, y)) === op(i, *, z))
+ )
+
+ // 3. Conclude that xy = xz ==> y === z
+ have((group(G, *), x ∈ G, y ∈ G, z ∈ G, op(x, *, y) === op(x, *, z)) |- y === z) by Equalities(cancellation, cancellation of (y -> z), substitution)
+ thenHave(thesis) by Restate
+ }
+
+ /**
+ * Theorem --- In a group `(G, *)`, we have `yx = zx ==> y = z`.
+ *
+ * Analoguous to [[leftCancellation]].
+ */
+ val rightCancellation = Theorem(
+ (group(G, *), x ∈ G, y ∈ G, z ∈ G) |- (op(y, *, x) === op(z, *, x)) ==> (y === z)
+ ) {
+ val i = inverse(x, G, *)
+
+ // 1. Prove that (yx)i = y and (zx)i = z
+ val cancellation = have((group(G, *), x ∈ G, y ∈ G) |- op(op(y, *, x), *, i) === y) subproof {
+ // (xy)i = y(xi)
+ val eq1 = have((group(G, *), x ∈ G, y ∈ G) |- op(op(y, *, x), *, i) === op(y, *, op(x, *, i))) by Cut(
+ inverseInGroup,
+ associativity of (x -> y, y -> x, z -> i)
+ )
+
+ // y(xi) = y
+ have((group(G, *), x ∈ G) |- ∀(y, (y ∈ G) ==> ((op(op(x, *, i), *, y) === y) /\ (op(y, *, op(x, *, i)) === y)))) by Tautology.from(
+ inverseIsInverse,
+ isInverse.definition of (y -> i),
+ isNeutral.definition of (e -> op(x, *, i))
+ )
+ thenHave((group(G, *), x ∈ G) |- (y ∈ G) ==> ((op(op(x, *, i), *, y) === y) /\ (op(y, *, op(x, *, i)) === y))) by InstantiateForall(y)
+ val eq2 = thenHave((group(G, *), x ∈ G, y ∈ G) |- op(y, *, op(x, *, i)) === y) by Tautology
+
+ // (yx)i = y
+ have(thesis) by Equalities(eq1, eq2)
+ }
+
+ // 2. By substitution, yx = zx implies (yx)i = (zx)i
+ have(op(op(y, *, x), *, i) === op(op(y, *, x), *, i)) by RightRefl
+ val substitution = thenHave(op(y, *, x) === op(z, *, x) |- op(op(y, *, x), *, i) === op(op(z, *, x), *, i)) by RightSubstEq(
+ List((op(y, *, x), op(z, *, x))),
+ lambda(z, op(op(y, *, x), *, i) === op(z, *, i))
+ )
+
+ // 3. Conclude that yx = zx ==> y === z
+ have((group(G, *), x ∈ G, y ∈ G, z ∈ G, op(y, *, x) === op(z, *, x)) |- y === z) by Equalities(cancellation, cancellation of (y -> z), substitution)
+ thenHave(thesis) by Restate
+ }
+
+ /**
+ * Theorem --- An element `x` of a group `(G, *)` is idempotent if and only if `x` is the identity element.
+ */
+ val identityIdempotence = Theorem(
+ (group(G, *), x ∈ G) |- (op(x, *, x) === x) <=> (x === identity(G, *))
+ ) {
+ assume(group(G, *))
+ assume(x ∈ G)
+
+ val neutralityEquality = have(op(identity(G, *), *, x) === x) by Tautology.from(identityNeutrality)
+
+ // Forward direction, using the equality x * x = x = e * x
+ // and concluding by right cancellation
+ have(op(x, *, x) === x |- x === identity(G, *)) subproof {
+ have(op(x, *, x) === x |- op(x, *, x) === x) by Hypothesis
+ have(op(x, *, x) === x |- op(x, *, x) === op(identity(G, *), *, x)) by Equalities(lastStep, neutralityEquality)
+ have((op(x, *, x) === x, identity(G, *) ∈ G) |- x === identity(G, *)) by Tautology.from(
+ lastStep,
+ rightCancellation of (x -> x, y -> x, z -> identity(G, *))
+ )
+ have(thesis) by Cut(identityInGroup, lastStep)
+ }
+ val forward = thenHave((op(x, *, x) === x) ==> (x === identity(G, *))) by Restate
+
+ have(x === identity(G, *) |- op(x, *, x) === x) by RightSubstEq(
+ List((x, identity(G, *))),
+ lambda(z, op(z, *, x) === x)
+ )(neutralityEquality)
+ val backward = thenHave((x === identity(G, *)) ==> (op(x, *, x) === x)) by Restate
+
+ have(thesis) by RightIff(forward, backward)
+ }
+
+ /**
+ * Theorem --- If `x * y = e` then `y = inverse(x)`.
+ *
+ * This also implies that `x = inverse(y)` by [[inverseSymmetry]].
+ */
+ val inverseTest = Theorem(
+ (group(G, *), x ∈ G, y ∈ G) |- (op(x, *, y) === identity(G, *)) ==> (y === inverse(x, G, *))
+ ) {
+ assume(group(G, *))
+ assume(x ∈ G)
+ assume(y ∈ G)
+
+ val e = identity(G, *)
+
+ // 1. e = x * inverse(x)
+ val eq1 = have(op(x, *, inverse(x, G, *)) === e) by Tautology.from(
+ identityInGroup,
+ inverseCancellation
+ )
+
+ // 2. x * y = x * inverse(x)
+ have((op(x, *, y) === e) |- (op(x, *, y) === e)) by Hypothesis
+ val eq2 = have((op(x, *, y) === e) |- op(x, *, y) === op(x, *, inverse(x, G, *))) by Equalities(eq1, lastStep)
+
+ // Conclude by left cancellation
+ have((op(x, *, y) === e, inverse(x, G, *) ∈ G) |- (y === inverse(x, G, *))) by Tautology.from(
+ lastStep,
+ leftCancellation of (z -> inverse(x, G, *))
+ )
+ have((op(x, *, y) === e) |- (y === inverse(x, G, *))) by Cut(inverseInGroup, lastStep)
+ thenHave(thesis) by Restate
+ }
+
+ /**
+ * Theorem --- The inverse of the identity element is itself.
+ */
+ val inverseOfIdentityIsIdentity = Theorem(
+ group(G, *) |- inverse(identity(G, *), G, *) === identity(G, *)
+ ) {
+ assume(group(G, *))
+
+ val e = identity(G, *)
+
+ have(x ∈ G |- ∀(y, (y === inverse(x, G, *)) <=> isInverse(y, x, G, *))) by Tautology.from(
+ inverseUniqueness,
+ inverse.definition
+ )
+ thenHave(x ∈ G |- (e === inverse(x, G, *)) <=> isInverse(e, x, G, *)) by InstantiateForall(e)
+ val characterization = have((e === inverse(e, G, *)) <=> isInverse(e, e, G, *)) by Cut(identityInGroup, lastStep of (x -> e))
+
+ // Prove that e is an inverse of e
+ val satisfaction = have(isInverse(e, e, G, *)) subproof {
+ val neutrality = have(op(e, *, e) === e) by Cut(identityInGroup, identityNeutrality of (x -> e))
+
+ have((op(e, *, e) === e) |- isNeutral(op(e, *, e), G, *)) by RightSubstEq(
+ List((op(e, *, e), e)),
+ lambda(z, isNeutral(z, G, *))
+ )(identityIsNeutral)
+
+ have(isNeutral(op(e, *, e), G, *)) by Cut(neutrality, lastStep)
+
+ have(e ∈ G /\ isNeutral(op(e, *, e), G, *) /\ isNeutral(op(e, *, e), G, *)) by RightAnd(identityInGroup, lastStep, lastStep)
+ have(thesis) by Tautology.from(lastStep, isInverse.definition of (x -> e, y -> e))
+ }
+
+ have(thesis) by Tautology.from(characterization, satisfaction)
+ }
+
+ // TODO Direct product group
+ // TODO Permutation group
+
+ //
+ // 2. Subgroups
+ //
+
+ // By convention, this will always refer to the restricted operation.
+ private val ★ = restrictedFunction(*, cartesianProduct(H, H))
+
+ /**
+ * Subgroup --- `H` is a subgroup of `(G, *)` if `H` is a subset of `G`, such that the restriction of `*` to `H` is
+ * a group law for `H`, i.e. `(H, *_H)` is a group.
+ *
+ * We denote `H <= G` for `H` a subgroup of `G`.
+ */
+ val subgroup = DEF(H, G, *) --> group(G, *) /\ subset(H, G) /\ group(H, restrictedFunction(*, cartesianProduct(H, H)))
+
+ /**
+ * Lemma --- A group is a subgroup of itself, i.e. the subgroup relationship is reflexive.
+ */
+ val groupIsSubgroupOfItself = Theorem(
+ group(G, *) |- subgroup(G, G, *)
+ ) {
+ val condition1 = have(group(G, *) |- group(G, *)) by Hypothesis
+ val condition2 = have(subset(G, G)) by Restate.from(subsetReflexivity of (x -> G))
+
+ // For condition 3, we need to substitute everything using the 3 following equalities:
+ // 1. restrictedFunction(*, relationDomain(*)) === * (restrictedFunctionCancellation)
+ // 2. relationDomain(*) === cartesianProduct(G, G) (groupOperationDomain)
+ // 3. restrictedFunction(*, cartesianProduct(G, G)) === * (derived from 1. and 2.)
+
+ val substitution = have((group(G, *), restrictedFunction(*, cartesianProduct(G, G)) === *) |- group(G, restrictedFunction(*, cartesianProduct(G, G)))) by RightSubstEq(
+ List((restrictedFunction(*, cartesianProduct(G, G)), *)),
+ lambda(z, group(G, z))
+ )(condition1)
+
+ val eq3 = have(group(G, *) |- restrictedFunction(*, cartesianProduct(G, G)) === *) subproof {
+ assume(group(G, *))
+ val eq1 = have(restrictedFunction(*, relationDomain(*)) === *) by Cut(
+ groupOperationIsFunctional,
+ restrictedFunctionCancellation of (f -> *)
+ )
+ thenHave((relationDomain(*) === cartesianProduct(G, G)) |- restrictedFunction(*, cartesianProduct(G, G)) === *) by RightSubstEq(
+ List((relationDomain(*), cartesianProduct(G, G))),
+ lambda(z, restrictedFunction(*, z) === *)
+ )
+
+ have(thesis) by Cut(groupOperationDomain, lastStep)
+ }
+
+ val condition3 = have(group(G, *) |- group(G, restrictedFunction(*, cartesianProduct(G, G)))) by Cut(eq3, substitution)
+
+ have(group(G, *) |- group(G, *) /\ subset(G, G) /\ group(G, restrictedFunction(*, cartesianProduct(G, G)))) by RightAnd(condition1, condition2, condition3)
+ have(thesis) by Tautology.from(lastStep, subgroup.definition of (G -> G, H -> G))
+ }
+
+ /**
+ * Proper subgroup --- `H` is a proper subgroup of `(G, *)` if `H` is a subgroup of `G` and `H != G`.
+ */
+ val properSubgroup = DEF(H, G, *) --> subgroup(H, G, *) /\ (H !== G)
+
+ /**
+ * Lemma --- If `x` and `y` are two elements of the subgroup `H` of `(G, *)`, the pair belongs to the relation domain
+ * of the parent group's operation `*`.
+ *
+ * Analogous to [[groupPairInOperationDomain]], except that the considered relation is different.
+ */
+ val subgroupPairInParentOperationDomain = Lemma(
+ (subgroup(H, G, *), x ∈ H, y ∈ H) |- pair(x, y) ∈ relationDomain(*)
+ ) {
+ assume(subgroup(H, G, *))
+ assume(x ∈ H)
+ assume(y ∈ H)
+
+ have(subset(H, G)) by Tautology.from(subgroup.definition)
+ have(∀(x, x ∈ H ==> x ∈ G)) by Tautology.from(lastStep, subset.definition of (x -> H, y -> G))
+ val subsetDef = thenHave(x ∈ H ==> x ∈ G) by InstantiateForall(x)
+
+ val left = have(x ∈ G) by Tautology.from(subsetDef)
+ val right = have(y ∈ G) by Tautology.from(subsetDef of (x -> y))
+
+ have(group(G, *)) by Tautology.from(subgroup.definition)
+
+ have(thesis) by Tautology.from(lastStep, left, right, groupPairInOperationDomain)
+ }
+
+ /**
+ * Theorem --- The subgroup operation is exactly the same as in the above group, i.e. if `(G, *)` is a group, `H` a
+ * subgroup of `G`, then for elements `x, y ∈ H` we have `x ★ y = x * y`, where `★ = *_H`.
+ */
+ val subgroupOperation = Theorem(
+ (subgroup(H, G, *), x ∈ H, y ∈ H) |- (op(x, ★, y) === op(x, *, y))
+ ) {
+ assume(subgroup(H, G, *))
+ val groupG = have(group(G, *)) by Tautology.from(subgroup.definition)
+ val groupH = have(group(H, ★)) by Tautology.from(subgroup.definition)
+
+ have((x ∈ H, y ∈ H) |- pair(x, y) ∈ relationDomain(★)) by Tautology.from(
+ groupH,
+ groupPairInOperationDomain of (G -> H, * -> ★)
+ )
+ have((functional(*), x ∈ H, y ∈ H) |- op(x, ★, y) === op(x, *, y)) by Cut(
+ lastStep,
+ restrictedFunctionApplication of (f -> *, d -> cartesianProduct(H, H), x -> pair(x, y))
+ )
+ have(thesis) by Tautology.from(
+ lastStep,
+ groupOperationIsFunctional,
+ groupG
+ )
+ }
+
+ /**
+ * Lemma --- If `H` is a subgroup of `G`, then `e_H ∈ G`.
+ */
+ val subgroupIdentityInParent = Lemma(
+ subgroup(H, G, *) |- identity(H, ★) ∈ G
+ ) {
+ val identityInH = have(subgroup(H, G, *) |- identity(H, ★) ∈ H) by Tautology.from(
+ subgroup.definition,
+ identityInGroup of (G -> H, * -> ★)
+ )
+
+ have(subgroup(H, G, *) |- ∀(x, x ∈ H ==> x ∈ G)) by Tautology.from(
+ subgroup.definition,
+ subset.definition of (x -> H, y -> G)
+ )
+ thenHave(subgroup(H, G, *) |- identity(H, ★) ∈ H ==> identity(H, ★) ∈ G) by InstantiateForall(identity(H, ★))
+ thenHave((subgroup(H, G, *), identity(H, ★) ∈ H) |- identity(H, ★) ∈ G) by Restate
+
+ have(thesis) by Cut(identityInH, lastStep)
+ }
+
+ /**
+ * Identity in subgroup --- The identity element `e_H` of a subgroup `H` of `G` is exactly the identity element `e_G` of
+ * the parent group `(G, *)`.
+ */
+ val subgroupIdentity = Theorem(
+ subgroup(H, G, *) |- identity(H, ★) === identity(G, *)
+ ) {
+ val e_G = identity(G, *)
+ val e_H = identity(H, ★)
+
+ val groupG = have(subgroup(H, G, *) |- group(G, *)) by Tautology.from(subgroup.definition)
+ val groupH = have(subgroup(H, G, *) |- group(H, ★)) by Tautology.from(subgroup.definition)
+
+ val subgroupIdentityInH = have(subgroup(H, G, *) |- identity(H, ★) ∈ H) by Tautology.from(
+ subgroup.definition,
+ identityInGroup of (G -> H, * -> ★)
+ )
+
+ // 1. e_H ★ e_H = e_H
+ val eq1 = have(subgroup(H, G, *) |- op(e_H, ★, e_H) === e_H) subproof {
+ have(group(H, ★) |- (op(e_H, ★, e_H) === e_H)) by Cut(
+ identityInGroup of (G -> H, * -> ★),
+ identityNeutrality of (G -> H, * -> ★, x -> e_H)
+ )
+
+ have(thesis) by Cut(groupH, lastStep)
+ }
+
+ // 2. e_H * e_H = e_H
+ have(subgroup(H, G, *) |- op(e_H, ★, e_H) === op(e_H, *, e_H)) by Cut(
+ subgroupIdentityInH,
+ subgroupOperation of (x -> e_H, y -> e_H)
+ )
+ val eq2 = have(subgroup(H, G, *) |- op(e_H, *, e_H) === e_H) by Equalities(eq1, lastStep)
+
+ // 3. e_G * e_H = e_H
+ val eq3 = have(subgroup(H, G, *) |- op(e_G, *, e_H) === e_H) subproof {
+ have((group(G, *), e_H ∈ G) |- op(e_G, *, e_H) === e_H) by Tautology.from(identityNeutrality of (x -> e_H))
+ have((subgroup(H, G, *), e_H ∈ G) |- op(e_G, *, e_H) === e_H) by Cut(groupG, lastStep)
+ have(thesis) by Cut(subgroupIdentityInParent, lastStep)
+ }
+
+ // Conclude by right cancellation
+ val eq4 = have(subgroup(H, G, *) |- op(e_H, *, e_H) === op(e_G, *, e_H)) by Equalities(eq2, eq3)
+ have((group(G, *), e_H ∈ G, e_G ∈ G, op(e_H, *, e_H) === op(e_G, *, e_H)) |- e_H === e_G) by Restate.from(
+ rightCancellation of (x -> e_H, y -> e_H, z -> e_G)
+ )
+ have((subgroup(H, G, *), e_H ∈ G, e_G ∈ G, op(e_H, *, e_H) === op(e_G, *, e_H)) |- e_H === e_G) by Cut(groupG, lastStep)
+ have((subgroup(H, G, *), e_H ∈ G, e_G ∈ G) |- e_H === e_G) by Cut(eq4, lastStep)
+
+ val finalStep = have((subgroup(H, G, *), e_G ∈ G) |- e_H === e_G) by Cut(subgroupIdentityInParent, lastStep)
+
+ have(subgroup(H, G, *) |- e_G ∈ G) by Cut(groupG, identityInGroup)
+ have(thesis) by Cut(lastStep, finalStep)
+ }
+
+ /**
+ * Theorem --- If `H` is a subgroup of `G`, then the inverse is the same as in the parent group.
+ */
+ val subgroupInverse = Theorem(
+ (subgroup(H, G, *), x ∈ H) |- inverse(x, H, ★) === inverse(x, G, *)
+ ) {
+ assume(subgroup(H, G, *))
+ assume(x ∈ H)
+
+ have(∀(x, (x ∈ H) ==> (x ∈ G))) by Tautology.from(
+ subgroup.definition,
+ subset.definition of (x -> H, y -> G)
+ )
+ val subsetDef = thenHave((x ∈ H) ==> (x ∈ G)) by InstantiateForall(x)
+ val xInG = thenHave(x ∈ G) by Tautology
+
+ val groupG = have(group(G, *)) by Tautology.from(subgroup.definition)
+ val groupH = have(group(H, ★)) by Tautology.from(subgroup.definition)
+
+ val eG = identity(G, *)
+ val eH = identity(H, ★)
+
+ val inverseHInH = have(inverse(x, H, ★) ∈ H) by Cut(groupH, inverseInGroup of (G -> H, * -> ★))
+ val inverseHInG = have(inverse(x, H, ★) ∈ G) by Tautology.from(inverseHInH, subsetDef of (x -> inverse(x, H, ★)))
+
+ // 1. x * inverse(x, H, ★) = e_H
+ have((inverse(x, H, ★) ∈ H) |- (op(x, ★, inverse(x, H, ★)) === eH)) by Tautology.from(
+ groupH,
+ inverseCancellation of (G -> H, * -> ★)
+ )
+ have((inverse(x, H, ★) ∈ H) |- (op(x, *, inverse(x, H, ★)) === eH)) by Equalities(
+ lastStep,
+ subgroupOperation of (y -> inverse(x, H, ★))
+ )
+
+ val eq1 = have(op(x, *, inverse(x, H, ★)) === eH) by Cut(inverseHInH, lastStep)
+
+ // 2. e_H = e_G
+ val eq2 = have(eH === eG) by Tautology.from(subgroupIdentity)
+
+ // 3. x * inverse(x, G, *) = e_G
+ val eq3 = have(op(x, *, inverse(x, G, *)) === eG) by Tautology.from(
+ groupG,
+ xInG,
+ inverseInGroup,
+ inverseCancellation
+ )
+
+ // 4. x * inverse(x, H, ★) === x * inverse(x, G, *)
+ have(op(x, *, inverse(x, H, ★)) === op(x, *, inverse(x, G, *))) by Equalities(eq1, eq2, eq3)
+
+ // Conclude by left cancellation
+ have(thesis) by Tautology.from(
+ lastStep,
+ groupG,
+ xInG,
+ inverseHInG,
+ inverseInGroup,
+ leftCancellation of (y -> inverse(x, H, ★), z -> inverse(x, G, *))
+ )
+ }
+
+ //
+ // 2.1 Main subgroup test
+ //
+ // We define several useful lemmas to attack this easy, but long theorem to formalize
+ //
+
+ private val nonEmpty = H !== ∅
+ private val closedByProducts = ∀(x, ∀(y, (x ∈ H /\ y ∈ H) ==> (op(x, *, y) ∈ H)))
+ private val closedByInverses = ∀(x, x ∈ H ==> (inverse(x, G, *) ∈ H))
+ private val subgroupConditions = nonEmpty /\ closedByProducts /\ closedByInverses
+
+ /**
+ * Lemma --- Reformulation of the subset definition.
+ */
+ private val subgroupConditionsSubset = Lemma(
+ (subset(H, G), x ∈ H) |- x ∈ G
+ ) {
+ assume(subset(H, G))
+ have(∀(x, x ∈ H ==> x ∈ G)) by Tautology.from(subset.definition of (x -> H, y -> G))
+ thenHave(x ∈ H ==> x ∈ G) by InstantiateForall(x)
+ thenHave(x ∈ H |- x ∈ G) by Restate
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply that `relationDomain(★) === cartesianProduct(H, H)`.
+ */
+ private val subgroupConditionsDomain = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions) |- relationDomain(★) === cartesianProduct(H, H)
+ ) {
+ val H2 = cartesianProduct(H, H)
+ val G2 = cartesianProduct(G, G)
+
+ assume(group(G, *))
+ assume(subset(H, G))
+ assume(subgroupConditions)
+
+ have(relationDomain(★) === (H2 ∩ relationDomain(*))) by Tautology.from(restrictedFunctionDomain of (f -> *, x -> H2))
+ thenHave((relationDomain(*) === G2) |- relationDomain(★) === (H2 ∩ G2)) by RightSubstEq(
+ List((relationDomain(*), G2)),
+ lambda(z, relationDomain(★) === (H2 ∩ z))
+ )
+ val eq1 = have(relationDomain(★) === (H2 ∩ G2)) by Cut(groupOperationDomain, lastStep)
+
+ // Prove that (H2 ∩ G2) = H2
+ have(subset(H2, G2)) by Tautology.from(subsetsCartesianProduct of (a -> H, b -> G, c -> H, d -> G))
+ val eq2 = have((H2 ∩ G2) === H2) by Cut(
+ lastStep,
+ setIntersectionSubset of (x -> H2, y -> G2)
+ )
+
+ have(thesis) by Equalities(eq1, eq2)
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply that `(x, y)` is in the relation domain of `★`.
+ *
+ * Analogous to [[groupPairInOperationDomain]].
+ */
+ private val subgroupConditionsPairInDomain = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions, x ∈ H, y ∈ H) |- pair(x, y) ∈ relationDomain(★)
+ ) {
+ assume(group(G, *))
+ assume(subset(H, G))
+ assume(subgroupConditions)
+ assume(x ∈ H)
+ assume(y ∈ H)
+
+ have(pair(x, y) ∈ cartesianProduct(H, H)) by Tautology.from(
+ pairInCartesianProduct of (a -> x, b -> y, x -> H, y -> H)
+ )
+ thenHave((relationDomain(★) === cartesianProduct(H, H)) |- pair(x, y) ∈ relationDomain(★)) by RightSubstEq(
+ List((relationDomain(★), cartesianProduct(H, H))),
+ lambda(z, pair(x, y) ∈ z)
+ )
+
+ have(thesis) by Cut(subgroupConditionsDomain, lastStep)
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply that `x ★ y = x * y`.
+ *
+ * Analogous to [[subgroupOperation]].
+ */
+ private val subgroupConditionsOperation = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions, x ∈ H, y ∈ H) |- op(x, ★, y) === op(x, *, y)
+ ) {
+ have(thesis) by Tautology.from(
+ subgroupConditionsPairInDomain,
+ groupOperationIsFunctional,
+ restrictedFunctionIsFunctional of (f -> *, x -> cartesianProduct(H, H)),
+ restrictedFunctionApplication of (f -> *, d -> cartesianProduct(H, H), x -> pair(x, y))
+ )
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply that `x ★ y ∈ H`.
+ */
+ private val subgroupConditionsProductClosure = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions, x ∈ H, y ∈ H) |- op(x, ★, y) ∈ H
+ ) {
+ assume(group(G, *))
+ assume(subset(H, G))
+ assume(subgroupConditions)
+
+ have(closedByProducts) by Tautology
+ thenHave(∀(y, (x ∈ H /\ y ∈ H) ==> (op(x, *, y) ∈ H))) by InstantiateForall(x)
+ thenHave((x ∈ H /\ y ∈ H) ==> (op(x, *, y) ∈ H)) by InstantiateForall(y)
+ thenHave((x ∈ H, y ∈ H) |- (op(x, *, y) ∈ H)) by Restate
+ thenHave((x ∈ H, y ∈ H, op(x, ★, y) === op(x, *, y)) |- (op(x, ★, y) ∈ H)) by RightSubstEq(
+ List((op(x, ★, y), op(x, *, y))),
+ lambda(z, z ∈ H)
+ )
+
+ have(thesis) by Cut(subgroupConditionsOperation, lastStep)
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply that `★` is a binary relation on `H`.
+ */
+ private val subgroupConditionsBinaryRelation = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions) |- binaryOperation(H, ★)
+ ) {
+ assume(group(G, *))
+ assume(subset(H, G))
+ assume(subgroupConditions)
+
+ val H2 = cartesianProduct(H, H)
+ val r = variable
+
+ have(∀(t, (t ∈ setOfFunctions(H2, H)) <=> (t ∈ powerSet(cartesianProduct(H2, H)) /\ functionalOver(t, H2)))) by Definition(setOfFunctions, setOfFunctionsUniqueness)(H2, H)
+ val setOfFunDef = thenHave((★ ∈ setOfFunctions(H2, H)) <=> (★ ∈ powerSet(cartesianProduct(H2, H)) /\ functionalOver(★, H2))) by InstantiateForall(★)
+
+ val fun = have(functional(★)) by Tautology.from(
+ groupOperationIsFunctional,
+ restrictedFunctionIsFunctional of (f -> *, x -> H2)
+ )
+ have(functional(★) /\ (relationDomain(★) === H2)) by RightAnd(fun, subgroupConditionsDomain)
+ val funOver = have(functionalOver(★, H2)) by Tautology.from(lastStep, functionalOver.definition of (f -> ★, x -> H2))
+
+ have(subset(★, cartesianProduct(relationDomain(★), relationRange(★)))) by Tautology.from(
+ fun,
+ functional.definition of (f -> ★),
+ relation.definition of (r -> ★),
+ relationImpliesRelationBetweenDomainAndRange of (r -> ★),
+ relationBetween.definition of (r -> ★, a -> relationDomain(★), b -> relationRange(★))
+ )
+ thenHave((relationDomain(★) === H2) |- subset(★, cartesianProduct(H2, relationRange(★)))) by RightSubstEq(
+ List((relationDomain(★), H2)),
+ lambda(z, subset(★, cartesianProduct(z, relationRange(★))))
+ )
+
+ val subsetDomRange = have(subset(★, cartesianProduct(H2, relationRange(★)))) by Cut(
+ subgroupConditionsDomain,
+ lastStep
+ )
+
+ // Prove that ★ is a subset of H2 x H
+ val left = have(subset(H2, H2)) by Tautology.from(subsetReflexivity of (x -> H2))
+ val right = have(subset(relationRange(★), H)) subproof {
+ // Use pullback to characterize t
+ val pullback = have(t ∈ relationRange(★) |- ∃(x, (x ∈ relationDomain(★)) /\ (app(★, x) === t))) by Tautology.from(
+ groupOperationIsFunctional,
+ restrictedFunctionIsFunctional of (f -> *, x -> H2),
+ inRangeImpliesPullbackExists of (f -> ★, z -> t)
+ )
+
+ have((x ∈ relationDomain(★)) <=> (x ∈ relationDomain(★))) by Restate
+ thenHave((relationDomain(★) === H2) |- (x ∈ relationDomain(★)) <=> (x ∈ H2)) by RightSubstEq(
+ List((relationDomain(★), H2)),
+ lambda(z, (x ∈ relationDomain(★)) <=> (x ∈ z))
+ )
+ val equiv1 = have((x ∈ relationDomain(★)) <=> (x ∈ H2)) by Cut(subgroupConditionsDomain, lastStep)
+ val equiv2 = have((x ∈ H2) <=> ∃(a, ∃(b, (x === pair(a, b)) /\ in(a, H) /\ in(b, H)))) by Tautology.from(
+ elemOfCartesianProduct of (t -> x, x -> H, y -> H)
+ )
+
+ // Use closure by products to show that app(★, x) ∈ H
+ have(closedByProducts) by Tautology
+ thenHave(∀(y, (a ∈ H /\ y ∈ H) ==> (op(a, *, y) ∈ H))) by InstantiateForall(a)
+ thenHave((a ∈ H /\ b ∈ H) ==> (op(a, *, b) ∈ H)) by InstantiateForall(b)
+ thenHave((a ∈ H, b ∈ H) |- (op(a, *, b) ∈ H)) by Restate
+ thenHave((a ∈ H, b ∈ H, op(a, ★, b) === op(a, *, b)) |- (op(a, ★, b) ∈ H)) by RightSubstEq(
+ List((op(a, ★, b), op(a, *, b))),
+ lambda(z, z ∈ H)
+ )
+
+ have((a ∈ H, b ∈ H) |- (op(a, ★, b) ∈ H)) by Cut(
+ subgroupConditionsOperation of (x -> a, y -> b),
+ lastStep
+ )
+ thenHave((x === pair(a, b), a ∈ H, b ∈ H) |- (app(★, x) ∈ H)) by RightSubstEq(
+ List((x, pair(a, b))),
+ lambda(z, app(★, z) ∈ H)
+ )
+ thenHave(((x === pair(a, b)) /\ a ∈ H /\ b ∈ H) |- (app(★, x) ∈ H)) by Restate
+ thenHave(∃(b, (x === pair(a, b)) /\ a ∈ H /\ b ∈ H) |- (app(★, x) ∈ H)) by LeftExists
+ thenHave(∃(a, ∃(b, (x === pair(a, b)) /\ a ∈ H /\ b ∈ H)) |- (app(★, x) ∈ H)) by LeftExists
+
+ have((x ∈ relationDomain(★)) |- (app(★, x) ∈ H)) by Tautology.from(lastStep, equiv1, equiv2)
+ thenHave((x ∈ relationDomain(★), app(★, x) === t) |- (t ∈ H)) by RightSubstEq(
+ List((app(★, x), t)),
+ lambda(z, z ∈ H)
+ )
+ thenHave((x ∈ relationDomain(★) /\ (app(★, x) === t)) |- (t ∈ H)) by Restate
+ thenHave(∃(x, x ∈ relationDomain(★) /\ (app(★, x) === t)) |- (t ∈ H)) by LeftExists
+
+ have(t ∈ relationRange(★) |- t ∈ H) by Cut(pullback, lastStep)
+ thenHave(t ∈ relationRange(★) ==> t ∈ H) by Restate
+ thenHave(∀(t, t ∈ relationRange(★) ==> t ∈ H)) by RightForall
+
+ have(thesis) by Tautology.from(lastStep, subset.definition of (x -> relationRange(★), y -> H))
+ }
+
+ have(subset(cartesianProduct(H2, relationRange(★)), cartesianProduct(H2, H))) by Tautology.from(
+ left,
+ right,
+ subsetsCartesianProduct of (a -> H2, b -> H2, c -> relationRange(★), d -> H)
+ )
+ have(subset(★, cartesianProduct(H2, H))) by Tautology.from(
+ lastStep,
+ subsetDomRange,
+ subsetTransitivity of (a -> ★, b -> cartesianProduct(H2, relationRange(★)), c -> cartesianProduct(H2, H))
+ )
+ have(★ ∈ powerSet(cartesianProduct(H2, H))) by Tautology.from(
+ lastStep,
+ powerSet.definition of (x -> ★, y -> cartesianProduct(H2, H))
+ )
+
+ have(★ ∈ powerSet(cartesianProduct(H2, H)) /\ functionalOver(★, H2)) by RightAnd(lastStep, funOver)
+
+ have(thesis) by Tautology.from(
+ lastStep,
+ setOfFunDef,
+ functionFrom.definition of (f -> ★, x -> H2, y -> H),
+ binaryOperation.definition of (G -> H, * -> ★)
+ )
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply associativity on `H`.
+ *
+ * This directly follows from associativity on `G` and [[subgroupConditionsOperation]].
+ */
+ private val subgroupConditionsAssociativity = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions) |- associativityAxiom(H, ★)
+ ) {
+ assume(group(G, *))
+ assume(subset(H, G))
+ assume(subgroupConditions)
+
+ have((x ∈ H, y ∈ H, z ∈ H) |- op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z))) subproof {
+ assume(x ∈ H)
+ assume(y ∈ H)
+ assume(z ∈ H)
+
+ have(op(op(x, *, y), *, z) === op(x, *, op(y, *, z))) by Tautology.from(
+ associativity,
+ subgroupConditionsSubset,
+ subgroupConditionsSubset of (x -> y),
+ subgroupConditionsSubset of (x -> z)
+ )
+ thenHave((op(x, ★, y) === op(x, *, y), op(y, ★, z) === op(y, *, z)) |- (op(op(x, ★, y), *, z) === op(x, *, op(y, ★, z)))) by RightSubstEq(
+ List((op(x, ★, y), op(x, *, y)), (op(y, ★, z), op(y, *, z))),
+ lambda(Seq(a, b), op(a, *, z) === op(x, *, b))
+ )
+
+ have(op(op(x, ★, y), *, z) === op(x, *, op(y, ★, z))) by Tautology.from(
+ lastStep,
+ subgroupConditionsOperation,
+ subgroupConditionsOperation of (x -> y, y -> z)
+ )
+ thenHave((op(op(x, ★, y), ★, z) === op(op(x, ★, y), *, z), op(x, ★, op(y, ★, z)) === op(x, *, op(y, ★, z))) |- (op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z)))) by RightSubstEq(
+ List((op(op(x, ★, y), ★, z), op(op(x, ★, y), *, z)), (op(x, ★, op(y, ★, z)), op(x, *, op(y, ★, z)))),
+ lambda(Seq(a, b), a === b)
+ )
+
+ have(op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z))) by Tautology.from(
+ lastStep,
+ subgroupConditionsOperation of (x -> op(x, ★, y), y -> z),
+ subgroupConditionsOperation of (x -> x, y -> op(y, ★, z)),
+ subgroupConditionsProductClosure,
+ subgroupConditionsProductClosure of (x -> y, y -> z)
+ )
+ }
+
+ // Reconstruct the axiom in its closed form
+ thenHave((x ∈ H, y ∈ H) |- (z ∈ H) ==> (op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z)))) by Restate
+ thenHave((x ∈ H, y ∈ H) |- ∀(z, (z ∈ H) ==> (op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z))))) by RightForall
+ thenHave((x ∈ H) |- (y ∈ H) ==> ∀(z, (z ∈ H) ==> (op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z))))) by Restate
+ thenHave((x ∈ H) |- ∀(y, (y ∈ H) ==> ∀(z, (z ∈ H) ==> (op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z)))))) by RightForall
+ thenHave((x ∈ H) ==> ∀(y, (y ∈ H) ==> ∀(z, (z ∈ H) ==> (op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z)))))) by Restate
+ thenHave(∀(x, (x ∈ H) ==> ∀(y, (y ∈ H) ==> ∀(z, (z ∈ H) ==> (op(op(x, ★, y), ★, z) === op(x, ★, op(y, ★, z))))))) by RightForall
+
+ have(thesis) by Tautology.from(lastStep, associativityAxiom.definition of (G -> H, * -> ★))
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply the existence of an identity element on `H`.
+ *
+ * We show in particular that identity(G, *) is neutral on `H`.
+ */
+ private val subgroupConditionsIdentityExistence = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions) |- identityExistence(H, ★)
+ ) {
+ assume(group(G, *))
+ assume(subset(H, G))
+ assume(subgroupConditions)
+
+ // We show that for an element x ∈ H:
+ // 1. inverse(x) ∈ H [[closedByInverses]]
+ // 2. x * inverse(x) ∈ H [[closedByProducts]]
+ // 3. x * inverse(x) = identity(G, *) [[inverseCancellation]]
+ // 4. identity(G, *) ∈ H Substitution of 3. in 2.
+ // 5. isNeutral(identity(G, *), H, ★) [[identityNeutrality]]
+ // 6. identityExistence(H, ★) [[identityExistence]]
+ // We finally conclude by [[nonEmpty]].
+
+ // 1. inverse(x) ∈ H
+ have(closedByInverses) by Tautology
+ thenHave(((x ∈ H) ==> (inverse(x, G, *) ∈ H))) by InstantiateForall(x)
+ val step1 = thenHave((x ∈ H) |- (inverse(x, G, *) ∈ H)) by Restate
+
+ // 2. x * inverse(x) ∈ H
+ have(closedByProducts) by Tautology
+ thenHave(∀(y, (x ∈ H /\ y ∈ H) ==> (op(x, *, y) ∈ H))) by InstantiateForall(x)
+ thenHave((x ∈ H /\ inverse(x, G, *) ∈ H) ==> (op(x, *, inverse(x, G, *)) ∈ H)) by InstantiateForall(inverse(x, G, *))
+ thenHave((x ∈ H, inverse(x, G, *) ∈ H) |- (op(x, *, inverse(x, G, *)) ∈ H)) by Restate
+
+ val step2 = have((x ∈ H) |- (op(x, *, inverse(x, G, *)) ∈ H)) by Cut(step1, lastStep)
+
+ // 3. x * inverse(x) = identity(G, *)
+ val step3 = have((x ∈ H) |- op(x, *, inverse(x, G, *)) === identity(G, *)) by Tautology.from(
+ subgroupConditionsSubset,
+ inverseCancellation
+ )
+
+ // 4. identity(G, *) ∈ H
+ have((x ∈ H, op(x, *, inverse(x, G, *)) === identity(G, *)) |- (identity(G, *) ∈ H)) by RightSubstEq(
+ List((op(x, *, inverse(x, G, *)), identity(G, *))),
+ lambda(z, z ∈ H)
+ )(step2)
+ val step4 = have((x ∈ H) |- (identity(G, *) ∈ H)) by Cut(step3, lastStep)
+
+ // 5. isNeutral(identity(G, *), H, ★)
+ have((x ∈ H) |- (op(identity(G, *), *, x) === x) /\ (op(x, *, identity(G, *)) === x)) by Tautology.from(
+ subgroupConditionsSubset,
+ identityNeutrality
+ )
+ thenHave(
+ (x ∈ H, op(identity(G, *), ★, x) === op(identity(G, *), *, x), op(x, ★, identity(G, *)) === op(x, *, identity(G, *))) |- (op(identity(G, *), ★, x) === x) /\ (op(x, ★, identity(G, *)) === x)
+ ) by RightSubstEq(
+ List((op(identity(G, *), ★, x), op(identity(G, *), *, x)), (op(x, ★, identity(G, *)), op(x, *, identity(G, *)))),
+ lambda(Seq(a, b), (a === x) /\ (b === x))
+ )
+
+ have(x ∈ H |- (op(identity(G, *), ★, x) === x) /\ (op(x, ★, identity(G, *)) === x)) by Tautology.from(
+ lastStep,
+ step4,
+ subgroupConditionsOperation of (x -> identity(G, *), y -> x),
+ subgroupConditionsOperation of (x -> x, y -> identity(G, *))
+ )
+
+ thenHave((x ∈ H) ==> (op(identity(G, *), ★, x) === x) /\ (op(x, ★, identity(G, *)) === x)) by Restate
+ thenHave(∀(x, (x ∈ H) ==> (op(identity(G, *), ★, x) === x) /\ (op(x, ★, identity(G, *)) === x))) by RightForall
+ val step5 = have((x ∈ H) |- isNeutral(identity(G, *), H, ★)) by Tautology.from(
+ lastStep,
+ step4,
+ isNeutral.definition of (e -> identity(G, *), G -> H, * -> ★)
+ )
+
+ // 6. identityExistence(H, ★)
+ thenHave((x ∈ H) |- ∃(e, isNeutral(e, H, ★))) by RightExists
+ val step6 = have((x ∈ H) |- identityExistence(H, ★)) by Tautology.from(lastStep, identityExistence.definition of (G -> H, * -> ★))
+
+ // Conclude by [[nonEmpty]]
+ thenHave(∃(x, x ∈ H) |- identityExistence(H, ★)) by LeftExists
+
+ have(thesis) by Tautology.from(lastStep, nonEmptySetHasElement of (x -> H))
+ }
+
+ /**
+ * Lemma --- The subgroup conditions imply that for all elements `x` in `H`, there exists an inverse in `H`.
+ */
+ private val subgroupConditionsInverseExistence = Lemma(
+ (group(G, *), subset(H, G), subgroupConditions) |- inverseExistence(H, ★)
+ ) {
+ assume(group(G, *))
+ assume(subset(H, G))
+ assume(subgroupConditions)
+
+ val i = inverse(x, G, *)
+
+ have(closedByInverses) by Tautology
+ thenHave(x ∈ H ==> i ∈ H) by InstantiateForall(x)
+ val inverseInH = thenHave(x ∈ H |- i ∈ H) by Restate
+
+ // Show that a neutral element of G is also neutral in H
+ val neutralityInheritance = have((e ∈ H, isNeutral(e, G, *)) |- isNeutral(e, H, ★)) subproof {
+ assume(isNeutral(e, G, *))
+ have(∀(x, (x ∈ G) ==> ((op(e, *, x) === x) /\ (op(x, *, e) === x)))) by Tautology.from(isNeutral.definition)
+ thenHave((x ∈ G) ==> ((op(e, *, x) === x) /\ (op(x, *, e) === x))) by InstantiateForall(x)
+ thenHave(x ∈ G |- (op(e, *, x) === x) /\ (op(x, *, e) === x)) by Restate
+
+ have(x ∈ H |- (op(e, *, x) === x) /\ (op(x, *, e) === x)) by Cut(subgroupConditionsSubset, lastStep)
+ thenHave((x ∈ H, op(e, ★, x) === op(e, *, x), op(x, ★, e) === op(x, *, e)) |- (op(e, ★, x) === x) /\ (op(x, ★, e) === x)) by RightSubstEq(
+ List((op(e, ★, x), op(e, *, x)), (op(x, ★, e), op(x, *, e))),
+ lambda(Seq(a, b), (a === x) /\ (b === x))
+ )
+
+ have((x ∈ H, e ∈ H) |- (op(e, ★, x) === x) /\ (op(x, ★, e) === x)) by Tautology.from(
+ lastStep,
+ subgroupConditionsOperation of (x -> e, y -> x),
+ subgroupConditionsOperation of (x -> x, y -> e)
+ )
+ thenHave(e ∈ H |- (x ∈ H) ==> (op(e, ★, x) === x) /\ (op(x, ★, e) === x)) by Restate
+ thenHave(e ∈ H |- ∀(x, (x ∈ H) ==> (op(e, ★, x) === x) /\ (op(x, ★, e) === x))) by RightForall
+
+ have(e ∈ H |- isNeutral(e, H, ★)) by Tautology.from(lastStep, isNeutral.definition of (G -> H, * -> ★))
+ }
+
+ // Show that i is neutral in H
+ have(x ∈ H |- isNeutral(op(x, *, i), G, *) /\ isNeutral(op(i, *, x), G, *)) by Tautology.from(
+ subgroupConditionsSubset,
+ inverseIsInverse,
+ isInverse.definition of (y -> inverse(x, G, *))
+ )
+ thenHave((x ∈ H, op(x, ★, i) === op(x, *, i), op(i, ★, x) === op(i, *, x)) |- isNeutral(op(x, ★, i), G, *) /\ isNeutral(op(i, ★, x), G, *)) by RightSubstEq(
+ List((op(x, ★, i), op(x, *, i)), (op(i, ★, x), op(i, *, x))),
+ lambda(Seq(a, b), isNeutral(a, G, *) /\ isNeutral(b, G, *))
+ )
+
+ have((x ∈ H, i ∈ H) |- isNeutral(op(x, ★, i), G, *) /\ isNeutral(op(i, ★, x), G, *)) by Tautology.from(
+ lastStep,
+ subgroupConditionsOperation of (x -> x, y -> i),
+ subgroupConditionsOperation of (x -> i, y -> x)
+ )
+
+ have((x ∈ H, i ∈ H) |- isNeutral(op(x, ★, i), H, ★) /\ isNeutral(op(i, ★, x), H, ★)) by Tautology.from(
+ lastStep,
+ neutralityInheritance of (e -> op(x, ★, i)),
+ neutralityInheritance of (e -> op(i, ★, x)),
+ subgroupConditionsProductClosure of (x -> x, y -> i),
+ subgroupConditionsProductClosure of (x -> i, y -> x)
+ )
+
+ have(x ∈ H |- (i ∈ H) /\ isNeutral(op(x, ★, i), H, ★) /\ isNeutral(op(i, ★, x), H, ★)) by Tautology.from(inverseInH, lastStep)
+ have(x ∈ H |- isInverse(i, x, H, ★)) by Tautology.from(lastStep, isInverse.definition of (y -> i, G -> H, * -> ★))
+ thenHave(x ∈ H |- ∃(y, isInverse(y, x, H, ★))) by RightExists
+ thenHave(x ∈ H ==> ∃(y, isInverse(y, x, H, ★))) by Restate
+ thenHave(∀(x, x ∈ H ==> ∃(y, isInverse(y, x, H, ★)))) by RightForall
+
+ have(thesis) by Tautology.from(lastStep, inverseExistence.definition of (G -> H, * -> ★))
+ }
+
+ /**
+ * Theorem (Main subgroup test) --- A subset `H ⊆ G` of a group `(G, *)` is a subgroup if and only if:
+ * 1. `H` is non-empty,
+ * 2. `H` is closed by products, and
+ * 3. `H` is closed by inversion.
+ *
+ * It is often easier to prove the 3 conditions independently than using the definition directly.
+ *
+ * Note that in the case where H is finite, conditions 1 and 2 are sufficient.
+ */
+ val subgroupTest = Theorem(
+ (group(G, *), subset(H, G)) |- (subgroup(H, G, *) <=> subgroupConditions)
+ ) {
+ assume(group(G, *))
+ assume(subset(H, G))
+
+ // The forward direction follow directly:
+ // 1. nonEmpty --> [[groupNonEmpty]]
+ // 2. closedByProducts --> [[subgroupOperation]] and [[groupIsClosedByProduct]]
+ // 3. closedByInverses --> [[subgroupInverse]] and [[inverseInGroup]]
+ have(subgroup(H, G, *) |- subgroupConditions) subproof {
+ assume(subgroup(H, G, *))
+ val groupH = have(group(H, ★)) by Tautology.from(subgroup.definition)
+
+ val condition1 = have(nonEmpty) by Cut(groupH, groupNonEmpty of (G -> H, * -> ★))
+
+ have((x ∈ H, y ∈ H) |- op(x, ★, y) ∈ H) by Cut(groupH, groupIsClosedByProduct of (G -> H, * -> ★))
+ thenHave((x ∈ H, y ∈ H, op(x, ★, y) === op(x, *, y)) |- op(x, *, y) ∈ H) by RightSubstEq(
+ List((op(x, ★, y), op(x, *, y))),
+ lambda(z, z ∈ H)
+ )
+
+ have((x ∈ H, y ∈ H) |- op(x, *, y) ∈ H) by Cut(subgroupOperation, lastStep)
+ thenHave((x ∈ H /\ y ∈ H) ==> (op(x, *, y) ∈ H)) by Restate
+ thenHave(∀(y, (x ∈ H /\ y ∈ H) ==> (op(x, *, y) ∈ H))) by RightForall
+ val condition2 = thenHave(closedByProducts) by RightForall
+
+ have((x ∈ H) |- (inverse(x, H, ★) ∈ H)) by Cut(groupH, inverseInGroup of (G -> H, * -> ★))
+ thenHave((x ∈ H, inverse(x, H, ★) === inverse(x, G, *)) |- inverse(x, G, *) ∈ H) by RightSubstEq(
+ List((inverse(x, H, ★), inverse(x, G, *))),
+ lambda(z, z ∈ H)
+ )
+
+ have((x ∈ H) |- (inverse(x, G, *) ∈ H)) by Cut(subgroupInverse, lastStep)
+ thenHave((x ∈ H) ==> (inverse(x, G, *) ∈ H)) by Restate
+ val condition3 = thenHave(closedByInverses) by RightForall
+
+ have(subgroupConditions) by RightAnd(condition1, condition2, condition3)
+ }
+ val forward = thenHave(subgroup(H, G, *) ==> subgroupConditions) by Restate
+
+ // For the backward direction, we must prove that the conditions make (H, ★) satisfy the axioms of a group:
+ // 1. Closure by products (i.e. ★'s codomain is H): [[closedByProducts]]
+ // 2. Associativity: follows from G's associativity
+ // 3. Identity existence: follows from [[nonEmpty]], [[closedByProducts]] and [[closedByInverses]]
+ // 4. Inverse existence: [[closedByInverse]]
+ //
+ // This direction is quite painful to prove. Each step is presented in its own lemma for easier legibility.
+ have(subgroupConditions |- subgroup(H, G, *)) subproof {
+ assume(subgroupConditions)
+ have(binaryOperation(H, ★) /\ associativityAxiom(H, ★) /\ identityExistence(H, ★) /\ inverseExistence(H, ★)) by RightAnd(
+ subgroupConditionsBinaryRelation,
+ subgroupConditionsAssociativity,
+ subgroupConditionsIdentityExistence,
+ subgroupConditionsInverseExistence
+ )
+ have(group(H, ★)) by Tautology.from(lastStep, group.definition of (G -> H, * -> ★))
+ thenHave(group(G, *) /\ subset(H, G) /\ group(H, ★)) by Tautology
+ have(thesis) by Tautology.from(lastStep, subgroup.definition)
+ }
+ val backward = thenHave(subgroupConditions ==> subgroup(H, G, *)) by Restate
+
+ have(thesis) by RightIff(forward, backward)
+ }
+
+ // TODO Trivial subgroup
+
+ //
+ // 3. Homomorphisms
+ //
+
+ // Extra group composition law
+ val ** = variable
+
+ /**
+ * Definition --- A group homomorphism is a mapping `f: G -> H` from structures `G` and `H` equipped with binary operations `*` and `★` respectively,
+ * such that for all `x, y ∈ G`, we have* `f(x * y) = f(x) ** f(y)`.
+ *
+ * In the following, "homomorphism" always stands for "group homomorphism", i.e. `(G, *)` and `(H, **)` are groups.
+ */
+ val homomorphism = DEF(f, G, *, H, **) --> group(G, *) /\ group(H, **) /\ functionFrom(f, G, H) /\ ∀(x, x ∈ G ==> ∀(y, y ∈ G ==> (app(f, op(x, *, y)) === op(app(f, x), **, app(f, y)))))
+
+ /**
+ * Lemma --- Practical reformulation of the homomorphism definition.
+ */
+ val homomorphismApplication = Lemma(
+ (homomorphism(f, G, *, H, **), x ∈ G, y ∈ G) |- app(f, op(x, *, y)) === op(app(f, x), **, app(f, y))
+ ) {
+ assume(homomorphism(f, G, *, H, **))
+ have(∀(x, x ∈ G ==> ∀(y, y ∈ G ==> (app(f, op(x, *, y)) === op(app(f, x), **, app(f, y)))))) by Tautology.from(homomorphism.definition)
+ thenHave(x ∈ G ==> ∀(y, y ∈ G ==> (app(f, op(x, *, y)) === op(app(f, x), **, app(f, y))))) by InstantiateForall(x)
+ thenHave((x ∈ G) |- ∀(y, y ∈ G ==> (app(f, op(x, *, y)) === op(app(f, x), **, app(f, y))))) by Restate
+ thenHave((x ∈ G) |- y ∈ G ==> (app(f, op(x, *, y)) === op(app(f, x), **, app(f, y)))) by InstantiateForall(y)
+ thenHave(thesis) by Restate
+ }
+
+ /**
+ * Lemma --- If `f` is a homomorphism, then `f(x) ∈ H` for all `x ∈ G`.
+ */
+ private val homomorphismAppInH = Lemma(
+ (homomorphism(f, G, *, H, **), x ∈ G) |- app(f, x) ∈ H
+ ) {
+ have(homomorphism(f, G, *, H, **) |- functionFrom(f, G, H)) by Tautology.from(homomorphism.definition)
+ have(thesis) by Cut(
+ lastStep,
+ functionAppInCodomain of (VariableLabel("t") -> x, VariableLabel("x") -> G, y -> H)
+ )
+ }
+
+ /**
+ * Theorem --- If `f` is a group-homomorphism between `G` and `H`, then `f(e_G) = e_H`.
+ */
+ val homomorphismMapsIdentityToIdentity = Theorem(
+ homomorphism(f, G, *, H, **) |- app(f, identity(G, *)) === identity(H, **)
+ ) {
+ val e = identity(G, *)
+
+ val groupG = have(homomorphism(f, G, *, H, **) |- group(G, *)) by Tautology.from(homomorphism.definition)
+ val groupH = have(homomorphism(f, G, *, H, **) |- group(H, **)) by Tautology.from(homomorphism.definition)
+
+ val identityInG = have(homomorphism(f, G, *, H, **) |- e ∈ G) by Cut(groupG, identityInGroup)
+ val appInH = have(homomorphism(f, G, *, H, **) |- app(f, e) ∈ H) by Cut(identityInG, homomorphismAppInH of (x -> e))
+
+ // 0. e * e = e (to apply substitution)
+ have(group(G, *) |- op(e, *, e) === e) by Cut(
+ identityInGroup,
+ identityIdempotence of (x -> e)
+ )
+ val eq0 = have(homomorphism(f, G, *, H, **) |- op(e, *, e) === e) by Cut(groupG, lastStep)
+
+ // 1. f(e * e) = f(e)
+ have(app(f, e) === app(f, e)) by RightRefl
+ thenHave(op(e, *, e) === e |- app(f, op(e, *, e)) === app(f, e)) by RightSubstEq(
+ List((op(e, *, e), e)),
+ lambda(z, app(f, z) === app(f, e))
+ )
+ val eq1 = have(homomorphism(f, G, *, H, **) |- app(f, op(e, *, e)) === app(f, e)) by Cut(eq0, lastStep)
+
+ // 2. f(e * e) = f(e) ** f(e)
+ val eq2 = have(homomorphism(f, G, *, H, **) |- app(f, op(e, *, e)) === op(app(f, e), **, app(f, e))) by Cut(
+ identityInG,
+ homomorphismApplication of (x -> e, y -> e)
+ )
+
+ // 3. f(e) ** f(e) = f(e)
+ val eq3 = have(homomorphism(f, G, *, H, **) |- op(app(f, e), **, app(f, e)) === app(f, e)) by Equalities(eq1, eq2)
+
+ // Conclude by idempotence
+ have((homomorphism(f, G, *, H, **), app(f, e) ∈ H) |- (op(app(f, e), **, app(f, e)) === app(f, e)) <=> (app(f, e) === identity(H, **))) by Cut(
+ groupH,
+ identityIdempotence of (x -> app(f, e), G -> H, * -> **)
+ )
+ have(homomorphism(f, G, *, H, **) |- (op(app(f, e), **, app(f, e)) === app(f, e)) <=> (app(f, e) === identity(H, **))) by Cut(
+ appInH,
+ lastStep
+ )
+
+ have(thesis) by Tautology.from(lastStep, eq3)
+ }
+
+ /**
+ * Theorem --- If `f: G -> H` is a group homomorphism, then `f(inverse(x, G, *)) = inverse(f(x), H, **)`.
+ */
+ val homomorphismMapsInverseToInverse = Theorem(
+ (homomorphism(f, G, *, H, **), x ∈ G) |- app(f, inverse(x, G, *)) === inverse(app(f, x), H, **)
+ ) {
+ assume(homomorphism(f, G, *, H, **))
+ assume(x ∈ G)
+
+ val groupG = have(group(G, *)) by Tautology.from(homomorphism.definition)
+ val groupH = have(group(H, **)) by Tautology.from(homomorphism.definition)
+
+ val eG = identity(G, *)
+ val eH = identity(H, **)
+ val i = inverse(x, G, *)
+ val iInG = have(i ∈ G) by Cut(groupG, inverseInGroup)
+
+ // 1. f(x * inverse(x)) = f(x) f(inverse(x))
+ val eq1 = have(app(f, op(x, *, i)) === op(app(f, x), **, app(f, i))) by Cut(
+ iInG,
+ homomorphismApplication of (y -> i)
+ )
+
+ // 2. f(x * inverse(x)) = f(e)
+ val cancellation = have(op(x, *, i) === eG) by Tautology.from(
+ groupG,
+ inverseCancellation
+ )
+
+ have(app(f, op(x, *, i)) === app(f, op(x, *, i))) by RightRefl
+ thenHave((op(x, *, i) === eG) |- (app(f, op(x, *, i)) === app(f, eG))) by RightSubstEq(
+ List((op(x, *, i), eG)),
+ lambda(z, app(f, op(x, *, i)) === app(f, z))
+ )
+
+ val eq2 = have(app(f, op(x, *, i)) === app(f, eG)) by Cut(cancellation, lastStep)
+
+ // 3. f(e) = e'
+ val eq3 = have(app(f, eG) === eH) by Tautology.from(homomorphismMapsIdentityToIdentity)
+
+ // 4. f(x)f(inverse(x)) = e'
+ val eq4 = have(op(app(f, x), **, app(f, i)) === eH) by Equalities(eq1, eq2, eq3)
+
+ // Conclude
+ val conclusion = have((app(f, i) ∈ H) |- (app(f, i) === inverse(app(f, x), H, **))) by Tautology.from(
+ groupH,
+ inverseTest of (G -> H, * -> **, x -> app(f, x), y -> app(f, i)),
+ eq4,
+ homomorphismAppInH
+ )
+ have(app(f, i) ∈ H) by Cut(iInG, homomorphismAppInH of (x -> i))
+
+ have(thesis) by Cut(lastStep, conclusion)
+ }
+
+ // TODO Homomorphism composition once we have function composition
+
+ /**
+ * Kernel uniqueness --- The kernel of a homomorphism is well-defined.
+ */
+ val kernelUniqueness = Theorem(
+ homomorphism(f, G, *, H, **) |- ∃!(z, ∀(t, (t ∈ z) <=> (t ∈ G /\ (app(f, t) === identity(H, **)))))
+ ) {
+ // We apply the comprehension axiom here.
+ // It might seem odd that the homomorphism assumption is not needed for the set to be defined,
+ // but remember that [[app]] and [[identity]] default to the empty set when the assumptions are not met.
+ // We add the assumption of `f` being a homomorphism to discard any value when the assumptions do not hold.
+ have(∃!(z, ∀(t, (t ∈ z) <=> (t ∈ G /\ (app(f, t) === identity(H, **)))))) by UniqueComprehension(
+ G,
+ lambda(Seq(t, G), app(f, t) === identity(H, **))
+ )
+ thenHave(thesis) by Weakening
+ }
+
+ /**
+ * Kernel --- The kernel of a homomorphism `f: G -> H` is the set of elements `t ∈ G` such that `f(t) = e_H`.
+ */
+ val kernel = DEF(f, G, *, H, **) --> TheConditional(z, ∀(t, (t ∈ z) <=> (t ∈ G /\ (app(f, t) === identity(H, **)))))(kernelUniqueness)
+
+ // Shortcut alias
+ private val K = kernel(f, G, *, H, **)
+
+ /**
+ * Lemma --- Reformulation of the kernel definition.
+ */
+ private val kernelDef = Lemma(
+ homomorphism(f, G, *, H, **) |- (x ∈ K) <=> (x ∈ G /\ (app(f, x) === identity(H, **)))
+ ) {
+ assume(homomorphism(f, G, *, H, **))
+ have(∀(t, (t ∈ K) <=> (t ∈ G /\ (app(f, t) === identity(H, **))))) by Definition(kernel, kernelUniqueness)(f, G, *, H, **)
+ thenHave(thesis) by InstantiateForall(x)
+ }
+
+ /**
+ * Lemma --- The kernel is closed by products, i.e. if `x, y ∈ K`, then `x * y ∈ K`.
+ */
+ val kernelIsClosedByProducts = Lemma(
+ (homomorphism(f, G, *, H, **), x ∈ K, y ∈ K) |- op(x, *, y) ∈ K
+ ) {
+ assume(homomorphism(f, G, *, H, **))
+ assume(x ∈ K)
+ assume(y ∈ K)
+
+ val elemInG = have(x ∈ G) by Tautology.from(kernelDef)
+
+ val groupG = have(group(G, *)) by Tautology.from(homomorphism.definition)
+ val groupH = have(group(H, **)) by Tautology.from(homomorphism.definition)
+
+ val e = identity(H, **)
+ val eInH = have(e ∈ H) by Cut(groupH, identityInGroup of (G -> H, * -> **))
+
+ // 1. f(x) ** f(y) = f(x * y)
+ val eq1 = have(app(f, op(x, *, y)) === op(app(f, x), **, app(f, y))) by Tautology.from(
+ homomorphismApplication,
+ elemInG,
+ elemInG of (x -> y)
+ )
+
+ // 2. f(x) ** f(y) = e ** e
+ val appValue = have(app(f, x) === e) by Tautology.from(kernelDef)
+ have(op(app(f, x), **, app(f, y)) === op(app(f, x), **, app(f, y))) by RightRefl
+ thenHave((app(f, x) === e, app(f, y) === e) |- op(app(f, x), **, app(f, y)) === op(e, **, e)) by RightSubstEq(
+ List((app(f, x), e), (app(f, y), e)),
+ lambda(Seq(a, b), op(app(f, x), **, app(f, y)) === op(a, **, b))
+ )
+
+ val eq2 = have(op(app(f, x), **, app(f, y)) === op(e, **, e)) by Tautology.from(
+ lastStep,
+ appValue,
+ appValue of (x -> y)
+ )
+
+ // 3. e ** e = e
+ val eq3 = have(op(e, **, e) === e) by Tautology.from(
+ identityNeutrality of (G -> H, * -> **, x -> e),
+ groupH,
+ eInH
+ )
+
+ // 4. f(x * y) = e
+ val eq4 = have(app(f, op(x, *, y)) === e) by Equalities(eq1, eq2, eq3)
+
+ // Conclude that x * y ∈ K
+ have(op(x, *, y) ∈ G) by Tautology.from(
+ groupG,
+ elemInG,
+ elemInG of (x -> y),
+ groupIsClosedByProduct
+ )
+
+ have(op(x, *, y) ∈ G /\ (app(f, op(x, *, y)) === e)) by RightAnd(lastStep, eq4)
+ have(thesis) by Tautology.from(lastStep, kernelDef of (x -> op(x, *, y)))
+ }
+
+ /**
+ * Lemma --- The kernel is closed by inversion, i.e. if `x ∈ K` then `inverse(x, G, *) ∈ K`.
+ */
+ val kernelIsClosedByInversion = Lemma(
+ (homomorphism(f, G, *, H, **), x ∈ K) |- inverse(x, G, *) ∈ K
+ ) {
+ assume(homomorphism(f, G, *, H, **))
+ assume(x ∈ K)
+
+ val groupG = have(group(G, *)) by Tautology.from(homomorphism.definition)
+ val groupH = have(group(H, **)) by Tautology.from(homomorphism.definition)
+ val elemInG = have(x ∈ G) by Tautology.from(kernelDef)
+
+ val e = identity(H, **)
+ val appValue = have(app(f, x) === e) by Tautology.from(kernelDef)
+
+ // 1. f(inverse(x)) = inverse(f(x)) = inverse(e)
+ have(app(f, inverse(x, G, *)) === inverse(app(f, x), H, **)) by Tautology.from(
+ homomorphismMapsInverseToInverse,
+ elemInG
+ )
+ thenHave((app(f, x) === e) |- (app(f, inverse(x, G, *)) === inverse(e, H, **))) by RightSubstEq(
+ List((app(f, x), e)),
+ lambda(z, app(f, inverse(x, G, *)) === inverse(z, H, **))
+ )
+
+ val eq1 = have(app(f, inverse(x, G, *)) === inverse(e, H, **)) by Cut(appValue, lastStep)
+
+ // 2. inverse(e) = e
+ val eq2 = have(inverse(e, H, **) === e) by Cut(groupH, inverseOfIdentityIsIdentity of (G -> H, * -> **))
+
+ // 3. Conclude
+ val eq3 = have(app(f, inverse(x, G, *)) === e) by Equalities(eq1, eq2)
+ have(inverse(x, G, *) ∈ G) by Tautology.from(
+ groupG,
+ elemInG,
+ inverseInGroup
+ )
+
+ have((inverse(x, G, *) ∈ G) /\ (app(f, inverse(x, G, *)) === e)) by RightAnd(lastStep, eq3)
+
+ have(thesis) by Tautology.from(lastStep, kernelDef of (x -> inverse(x, G, *)))
+ }
+
+ /**
+ * Theorem --- The kernel of a homomorphism `f: G -> H` is a subgroup of `G`.
+ */
+ val kernelIsSubgroup = Theorem(
+ homomorphism(f, G, *, H, **) |- subgroup(kernel(f, G, *, H, **), G, *)
+ ) {
+ assume(homomorphism(f, G, *, H, **))
+ val groupG = have(group(G, *)) by Tautology.from(homomorphism.definition)
+
+ // We show that the kernel satisfies all requirements of [[subgroupTest]]
+ have((x ∈ K) ==> (x ∈ G)) by Tautology.from(kernelDef)
+ thenHave(∀(x, x ∈ K ==> x ∈ G)) by RightForall
+ val kernelIsSubset = have(subset(K, G)) by Tautology.from(lastStep, subsetAxiom of (x -> K, y -> G))
+
+ // 1. kernel != ∅
+ have(identity(G, *) ∈ G) by Cut(groupG, identityInGroup)
+ have(identity(G, *) ∈ G /\ (app(f, identity(G, *)) === identity(H, **))) by RightAnd(
+ lastStep,
+ homomorphismMapsIdentityToIdentity
+ )
+ have(identity(G, *) ∈ K) by Tautology.from(
+ lastStep,
+ kernelDef of (x -> identity(G, *))
+ )
+ val condition1 = have(K !== ∅) by Cut(lastStep, setWithElementNonEmpty of (y -> identity(G, *), x -> K))
+
+ // 2. The kernel is closed by products
+ have((x ∈ K /\ y ∈ K) ==> op(x, *, y) ∈ K) by Restate.from(kernelIsClosedByProducts)
+ thenHave(∀(y, (x ∈ K /\ y ∈ K) ==> op(x, *, y) ∈ K)) by RightForall
+ val condition2 = thenHave(∀(x, ∀(y, (x ∈ K /\ y ∈ K) ==> op(x, *, y) ∈ K))) by RightForall
+
+ // 3. The kernel is closed by inversion
+ have((x ∈ K) ==> (inverse(x, G, *) ∈ K)) by Restate.from(kernelIsClosedByInversion)
+ val condition3 = thenHave(∀(x, (x ∈ K) ==> (inverse(x, G, *) ∈ K))) by RightForall
+
+ // Conclude
+ have((K !== ∅) /\ ∀(x, ∀(y, (x ∈ K /\ y ∈ K) ==> op(x, *, y) ∈ K)) /\ ∀(x, (x ∈ K) ==> (inverse(x, G, *) ∈ K))) by RightAnd(
+ condition1,
+ condition2,
+ condition3
+ )
+
+ have(subgroup(K, G, *)) by Tautology.from(
+ lastStep,
+ subgroupTest of (H -> K),
+ groupG,
+ kernelIsSubset
+ )
+ }
+
+ // TODO Kernel injectivity
+ // TODO Image is subgroup
+
+ /**
+ * Isomorphism --- An isomorphism `f: G -> H` is a bijective homomorphism.
+ *
+ * In some sense, isomorphic groups are equivalent, up to relabelling their elements.
+ */
+ val isomorphism = DEF(f, G, *, H, **) --> homomorphism(f, G, *, H, **) /\ bijective(f, G, H)
+
+ /**
+ * Automorphism --- An automorphism is an isomorphism from a group to itself.
+ */
+ val automorphism = DEF(f, G, *) --> isomorphism(f, G, *, G, *)
+}
diff --git a/src/main/scala/lisa/mathematics/SetTheory.scala b/src/main/scala/lisa/mathematics/SetTheory.scala
index 112576448..770c2f6f2 100644
--- a/src/main/scala/lisa/mathematics/SetTheory.scala
+++ b/src/main/scala/lisa/mathematics/SetTheory.scala
@@ -5,6 +5,7 @@ import lisa.automation.kernel.OLPropositionalSolver.Tautology
import lisa.automation.kernel.SimpleSimplifier.*
import lisa.automation.settheory.SetTheoryTactics.*
import lisa.mathematics.FirstOrderLogic.*
+import lisa.mathematics.FirstOrderLogic.existentialConjunctionWithClosedFormula
/**
* Set Theory Library
@@ -365,6 +366,20 @@ object SetTheory extends lisa.Main {
thenHave(∀(y, !in(y, x)) |- (x === ∅)) by Tautology
}
+ /**
+ * Theorem --- A non-empty set has an element.
+ *
+ * Contra-positive of [[setWithNoElementsIsEmpty]].
+ */
+ val nonEmptySetHasElement = Theorem(
+ !(x === ∅) |- ∃(y, in(y, x))
+ ) {
+ have((!(x === ∅), ∀(y, !in(y, x))) |- (x === ∅)) by Weakening(setWithNoElementsIsEmpty)
+ thenHave((!(x === ∅), ∀(y, !in(y, x))) |- ()) by LeftNot
+ thenHave((!(x === ∅)) |- ! ∀(y, !in(y, x))) by RightNot
+ thenHave(thesis) by Restate
+ }
+
/**
* Theorem --- The empty set is a subset of every set.
*
@@ -860,6 +875,25 @@ object SetTheory extends lisa.Main {
infix def ∩(y: Term) = setIntersection(x, y)
}
+ /**
+ * Theorem --- If `x ⊆ y` then `x ∩ y = x`.
+ */
+ val setIntersectionSubset = Theorem(
+ subset(x, y) |- (x ∩ y) === x
+ ) {
+ assume(subset(x, y))
+ have(∀(t, t ∈ x ==> t ∈ y)) by Tautology.from(subset.definition)
+ val subsetDef = thenHave(t ∈ x ==> t ∈ y) by InstantiateForall(t)
+
+ have(∀(t, (t ∈ (x ∩ y)) <=> (t ∈ x /\ t ∈ y))) by Definition(setIntersection, setIntersectionUniqueness)(x, y)
+ val intersectionDef = thenHave((t ∈ (x ∩ y)) <=> (t ∈ x /\ t ∈ y)) by InstantiateForall(t)
+
+ have((t ∈ (x ∩ y)) <=> (t ∈ x)) by Tautology.from(subsetDef, intersectionDef)
+ thenHave(∀(t, (t ∈ (x ∩ y)) <=> (t ∈ x))) by RightForall
+
+ have(thesis) by Tautology.from(lastStep, extensionalityAxiom of (x -> (x ∩ y), y -> x))
+ }
+
val unaryIntersectionUniqueness = Theorem(
∃!(z, ∀(t, in(t, z) <=> (exists(b, in(b, x)) /\ ∀(b, in(b, x) ==> in(t, b)))))
) {
@@ -1671,6 +1705,37 @@ object SetTheory extends lisa.Main {
have(thesis) by Tautology.from(lastStep, defUnfold)
}
+ /**
+ * Theorem --- If `a ⊆ b` and `c ⊆ d`, then `(a * c) ⊆ (b * d)`.
+ */
+ val subsetsCartesianProduct = Theorem(
+ (subset(a, b), subset(c, d)) |- subset(cartesianProduct(a, c), cartesianProduct(b, d))
+ ) {
+ have(subset(a, b) |- ∀(x, x ∈ a ==> x ∈ b)) by Tautology.from(subset.definition of (x -> a, y -> b))
+ val subsetDef = thenHave(subset(a, b) |- x ∈ a ==> x ∈ b) by InstantiateForall(x)
+
+ assume(subset(a, b))
+ assume(subset(c, d))
+ have(((t === pair(x, y)) /\ x ∈ a /\ y ∈ c) |- ((t === pair(x, y)) /\ x ∈ b /\ y ∈ d)) by Tautology.from(
+ subsetDef,
+ subsetDef of (a -> c, b -> d, x -> y)
+ )
+ thenHave(((t === pair(x, y)) /\ x ∈ a /\ y ∈ c) |- ∃(y, ((t === pair(x, y)) /\ x ∈ b /\ y ∈ d))) by RightExists
+ thenHave(((t === pair(x, y)) /\ x ∈ a /\ y ∈ c) |- ∃(x, ∃(y, ((t === pair(x, y)) /\ x ∈ b /\ y ∈ d)))) by RightExists
+ thenHave(∃(y, ((t === pair(x, y)) /\ x ∈ a /\ y ∈ c)) |- ∃(x, ∃(y, ((t === pair(x, y)) /\ x ∈ b /\ y ∈ d)))) by LeftExists
+ thenHave(∃(x, ∃(y, ((t === pair(x, y)) /\ x ∈ a /\ y ∈ c))) |- ∃(x, ∃(y, ((t === pair(x, y)) /\ x ∈ b /\ y ∈ d)))) by LeftExists
+ thenHave(∃(x, ∃(y, ((t === pair(x, y)) /\ x ∈ a /\ y ∈ c))) ==> ∃(x, ∃(y, ((t === pair(x, y)) /\ x ∈ b /\ y ∈ d)))) by Restate
+
+ have(t ∈ cartesianProduct(a, c) ==> t ∈ cartesianProduct(b, d)) by Tautology.from(
+ lastStep,
+ elemOfCartesianProduct of (x -> a, y -> c),
+ elemOfCartesianProduct of (x -> b, y -> d)
+ )
+ thenHave(∀(t, t ∈ cartesianProduct(a, c) ==> t ∈ cartesianProduct(b, d))) by RightForall
+
+ have(thesis) by Tautology.from(lastStep, subset.definition of (x -> cartesianProduct(a, c), y -> cartesianProduct(b, d)))
+ }
+
/**
* Theorem --- the union of two Cartesian products is a subset of the product of unions.
*
@@ -2399,6 +2464,119 @@ object SetTheory extends lisa.Main {
have(thesis) by Tautology.from(lastStep, pairMembership)
}
+ /**
+ * Lemma --- If `g` is the restriction of `f` to `x`, then `g` is a subset of `f`.
+ */
+ val restrictedFunctionIsSubset = Lemma(
+ subset(restrictedFunction(f, x), f)
+ ) {
+ have(∀(t, (t ∈ restrictedFunction(f, x)) <=> (t ∈ f /\ ∃(y, ∃(z, y ∈ x /\ (t === pair(y, z))))))) by Definition(restrictedFunction, restrictedFunctionUniqueness)(f, x)
+ thenHave((t ∈ restrictedFunction(f, x)) <=> (t ∈ f /\ ∃(y, ∃(z, y ∈ x /\ (t === pair(y, z)))))) by InstantiateForall(t)
+ thenHave(t ∈ restrictedFunction(f, x) ==> t ∈ f) by Tautology
+ thenHave(∀(t, t ∈ restrictedFunction(f, x) ==> t ∈ f)) by RightForall
+
+ have(thesis) by Tautology.from(
+ lastStep,
+ subset.definition of (x -> restrictedFunction(f, x), y -> f)
+ )
+ }
+
+ /**
+ * Lemma --- If `f` is a function, so is any restriction of `f`.
+ */
+ val restrictedFunctionIsFunctional = Lemma(
+ functional(f) |- functional(restrictedFunction(f, x))
+ ) {
+ val g = restrictedFunction(f, x)
+ assume(functional(f))
+
+ val functionalDef = have(relation(f) /\ ∀(x, ∃(y, in(pair(x, y), f)) ==> ∃!(y, in(pair(x, y), f)))) by Tautology.from(functional.definition)
+ thenHave(∀(x, ∃(y, in(pair(x, y), f)) ==> ∃!(y, in(pair(x, y), f)))) by Tautology
+ val fAppUniqueness = thenHave(∃(y, in(pair(t, y), f)) ==> ∃!(y, in(pair(t, y), f))) by InstantiateForall(t)
+ val fIsRelation = have(relation(f)) by Tautology.from(functionalDef)
+
+ val gIsRelation = have(relation(g)) subproof {
+ // Since f is a relation and g is a subset of f, g is also a relation by subset transitivity
+ have(relationBetween(f, a, b) |- subset(f, cartesianProduct(a, b))) by Tautology.from(relationBetween.definition of (r -> f))
+ have(relationBetween(f, a, b) |- subset(g, f) /\ subset(f, cartesianProduct(a, b))) by RightAnd(restrictedFunctionIsSubset, lastStep)
+ have(relationBetween(f, a, b) |- subset(g, cartesianProduct(a, b))) by Cut(lastStep, subsetTransitivity of (a -> g, b -> f, c -> cartesianProduct(a, b)))
+
+ // Reconstruct the definition
+ have(relationBetween(f, a, b) |- relationBetween(g, a, b)) by Tautology.from(lastStep, relationBetween.definition of (r -> g))
+ thenHave(relationBetween(f, a, b) |- ∃(b, relationBetween(g, a, b))) by RightExists
+ thenHave(relationBetween(f, a, b) |- ∃(a, ∃(b, relationBetween(g, a, b)))) by RightExists
+ thenHave(∃(b, relationBetween(f, a, b)) |- ∃(a, ∃(b, relationBetween(g, a, b)))) by LeftExists
+ thenHave(∃(a, ∃(b, relationBetween(f, a, b))) |- ∃(a, ∃(b, relationBetween(g, a, b)))) by LeftExists
+
+ have(relation(f) |- relation(g)) by Tautology.from(
+ lastStep,
+ relation.definition of (r -> f),
+ relation.definition of (r -> g)
+ )
+ have(thesis) by Cut(fIsRelation, lastStep)
+ }
+
+ val gAppUniqueness = have(∀(t, ∃(y, pair(t, y) ∈ g) ==> ∃!(y, pair(t, y) ∈ g))) subproof {
+ have((pair(t, a) ∈ restrictedFunction(f, x)) <=> (pair(t, a) ∈ f /\ in(t, x))) by Tautology.from(restrictedFunctionPairMembership)
+ val equiv = thenHave(∀(a, (pair(t, a) ∈ restrictedFunction(f, x)) <=> (pair(t, a) ∈ f /\ in(t, x)))) by RightForall
+
+ // Strategy:
+ // 1. ∃(a, (t, a) ∈ g)) <=> ∃(a, (t, a) ∈ f /\ t ∈ x) [[restrictedFunctionPairMembership]]
+ // 2. ∃(a, (t, a) ∈ f /\ t ∈ x) <=> ∃(a, (t, a) ∈ f) /\ t ∈ x [[existentialConjunctionWithClosedFormula]]
+ // 3. (∃(a, (t, a) ∈ f) /\ t ∈ x) ==> (∃!(a, (t, a) ∈ f) /\ t ∈ x) [[fAppUniqueness]]
+ // 4. (∃!(a, (t, a) ∈ f) /\ t ∈ x) <=> ∃!(a, (t, a) ∈ f /\ t ∈ x) [[uniqueExistentialConjunctionWithClosedFormula]]
+ // 5. ∃!(a, (t, a) ∈ f /\ t ∈ x) <=> ∃!(a, (t, a) ∈ g) [[uniqueExistentialEquivalenceDistribution]]
+ val p = formulaVariable
+
+ val step1 = have(∃(a, pair(t, a) ∈ g) <=> ∃(a, pair(t, a) ∈ f /\ t ∈ x)) by Cut(
+ equiv,
+ existentialEquivalenceDistribution of (
+ P -> lambda(a, pair(t, a) ∈ g),
+ Q -> lambda(a, pair(t, a) ∈ f /\ t ∈ x)
+ )
+ )
+
+ val step2 = have((∃(a, pair(t, a) ∈ f) /\ t ∈ x) <=> (∃(a, pair(t, a) ∈ f) /\ t ∈ x)) by Tautology.from(
+ existentialConjunctionWithClosedFormula of (
+ P -> lambda(a, pair(t, a) ∈ f),
+ p -> lambda(Seq(), t ∈ x)
+ )
+ )
+
+ val step3 = have((∃(a, pair(t, a) ∈ f) /\ t ∈ x) ==> (∃!(a, pair(t, a) ∈ f) /\ t ∈ x)) by Tautology.from(fAppUniqueness)
+
+ val step4 = have((∃!(a, pair(t, a) ∈ f) /\ t ∈ x) <=> ∃!(a, pair(t, a) ∈ f /\ t ∈ x)) by Tautology.from(
+ uniqueExistentialConjunctionWithClosedFormula of (
+ P -> lambda(a, pair(t, a) ∈ f),
+ p -> lambda(Seq(), t ∈ x)
+ )
+ )
+
+ val step5 = have(∃!(a, pair(t, a) ∈ f /\ t ∈ x) <=> ∃!(a, pair(t, a) ∈ g)) by Tautology.from(
+ equiv,
+ uniqueExistentialEquivalenceDistribution of (
+ P -> lambda(a, pair(t, a) ∈ f /\ t ∈ x),
+ Q -> lambda(a, pair(t, a) ∈ g)
+ )
+ )
+
+ have(∃(y, pair(t, y) ∈ g) ==> ∃!(y, pair(t, y) ∈ g)) by Tautology.from(
+ step1,
+ step2,
+ step3,
+ step4,
+ step5
+ )
+ thenHave(∀(t, ∃(y, pair(t, y) ∈ g) ==> ∃!(y, pair(t, y) ∈ g))) by RightForall
+ }
+
+ have(relation(g) /\ ∀(t, ∃(y, in(pair(t, y), g)) ==> ∃!(y, in(pair(t, y), g)))) by RightAnd(gIsRelation, gAppUniqueness)
+ have(functional(g)) by Tautology.from(
+ lastStep,
+ functional.definition of (f -> g)
+ )
+ }
+
/**
* Restricted function domain -- For a function `f`, the domain of `f_x` is `x ∩ relationDomain(f)`.
*/
@@ -2473,6 +2651,70 @@ object SetTheory extends lisa.Main {
have(thesis) by Tautology.from(domCharacterization, simplerCharacterization)
}
+ /**
+ * Theorem --- A restricted function coincides with the original function on its domain.
+ * In other words, if `g = restrictedFunction(f, d)`, `x ∈ d`, then `g(x) = f(x)`.
+ */
+ val restrictedFunctionApplication = Theorem {
+ val g = restrictedFunction(f, d)
+ (functional(f), x ∈ relationDomain(g)) |- app(g, x) === app(f, x)
+ } {
+ val g = restrictedFunction(f, d)
+ val p = pair(x, app(g, x))
+
+ // Show that x ∈ relationDomain(g) ==> x ∈ relationDomain(f)
+ have(∀(x, x ∈ (d ∩ relationDomain(f)) <=> (x ∈ d /\ x ∈ relationDomain(f)))) by Definition(setIntersection, setIntersectionUniqueness)(d, relationDomain(f))
+ thenHave(x ∈ (d ∩ relationDomain(f)) <=> (x ∈ d /\ x ∈ relationDomain(f))) by InstantiateForall(x)
+ thenHave(x ∈ (d ∩ relationDomain(f)) ==> x ∈ relationDomain(f)) by Tautology
+ thenHave(x ∈ (d ∩ relationDomain(f)) |- x ∈ relationDomain(f)) by Restate
+ thenHave((x ∈ relationDomain(g), relationDomain(g) === (d ∩ relationDomain(f))) |- x ∈ relationDomain(f)) by LeftSubstEq(
+ List((relationDomain(g), (d ∩ relationDomain(f)))),
+ lambda(z, x ∈ z)
+ )
+ val domainInclusion = have(x ∈ relationDomain(g) |- x ∈ relationDomain(f)) by Cut(
+ restrictedFunctionDomain of (x -> d),
+ lastStep
+ )
+
+ // Characterize app(f, x)
+ val characterization = have(
+ (app(g, x) === app(f, x)) <=> (((functional(f) /\ (x ∈ relationDomain(f))) ==> (p ∈ f)) /\
+ ((!functional(f) \/ (x ∉ relationDomain(f))) ==> (app(g, x) === ∅)))
+ ) by InstantiateForall(app(g, x))(app.definition)
+
+ // Use the definition of restricted functions
+ have(∀(t, t ∈ g ==> t ∈ f)) by Tautology.from(
+ restrictedFunctionIsSubset of (x -> d),
+ subset.definition of (x -> g, y -> f)
+ )
+ thenHave(p ∈ g ==> p ∈ f) by InstantiateForall(p)
+ val gSubsetF = thenHave(p ∈ g |- p ∈ f) by Restate
+
+ // Show that (x, app(g, x)) ∈ f
+ have((functional(g), x ∈ relationDomain(g)) |- p ∈ g) by Definition(app, functionApplicationUniqueness)(g, x)
+ have((functional(f), x ∈ relationDomain(g)) |- p ∈ g) by Cut(restrictedFunctionIsFunctional of (x -> d), lastStep)
+ val membership = have((functional(f), x ∈ relationDomain(g)) |- p ∈ f) by Cut(lastStep, gSubsetF)
+
+ // Reconstruct the definition and conclude by the characterization
+ val premises = have((functional(f), x ∈ relationDomain(g)) |- (functional(f) /\ (x ∈ relationDomain(f)))) by Tautology.from(domainInclusion)
+ val pos = have((functional(f), x ∈ relationDomain(g)) |- ((functional(f) /\ (x ∈ relationDomain(f))) ==> (p ∈ f))) by Tautology.from(
+ premises,
+ membership
+ )
+
+ have((functional(f), x ∈ relationDomain(g), !(functional(f) /\ (x ∈ relationDomain(f)))) |- ()) by LeftNot(premises)
+ thenHave((functional(f), x ∈ relationDomain(g), !functional(f) \/ (x ∉ relationDomain(f))) |- ()) by Restate
+ thenHave((functional(f), x ∈ relationDomain(g), !functional(f) \/ (x ∉ relationDomain(f))) |- (app(g, x) === ∅)) by Weakening
+ val neg = thenHave((functional(f), x ∈ relationDomain(g)) |- (!functional(f) \/ (x ∉ relationDomain(f)) ==> (app(g, x) === ∅))) by Restate
+
+ have(
+ (functional(f), x ∈ relationDomain(g)) |- (((functional(f) /\ (x ∈ relationDomain(f))) ==> (p ∈ f)) /\
+ ((!functional(f) \/ (x ∉ relationDomain(f))) ==> (app(g, x) === ∅)))
+ ) by RightAnd(pos, neg)
+
+ have(thesis) by Tautology.from(lastStep, characterization)
+ }
+
/**
* Restricted function cancellation --- Restricting a function to its relation domain does nothing.
*/
@@ -2882,6 +3124,40 @@ object SetTheory extends lisa.Main {
have(thesis) by Cut(surjfunc, funceq)
}
+ /**
+ * Lemma --- If `f: x -> y` is a function and `z ∈ x`, then `f(z) ∈ y`.
+ */
+ val functionAppInCodomain = Lemma(
+ (functionFrom(f, x, y), in(t, x)) |- in(app(f, t), y)
+ ) {
+ have((functional(f), in(t, relationDomain(f))) |- in(pair(t, app(f, t)), f)) by Definition(app, functionApplicationUniqueness)(f, t)
+ have((functionFrom(f, x, y), in(t, relationDomain(f))) |- in(pair(t, app(f, t)), f)) by Cut(functionFromImpliesFunctional, lastStep)
+ thenHave((functionFrom(f, x, y), relationDomain(f) === x, in(t, x)) |- in(pair(t, app(f, t)), f)) by LeftSubstEq(
+ List((relationDomain(f), x)),
+ lambda(z, in(t, z))
+ )
+ val appDef = have((functionFrom(f, x, y), in(t, x)) |- in(pair(t, app(f, t)), f)) by Cut(functionFromImpliesDomainEq, lastStep)
+
+ have(∀(t, in(t, setOfFunctions(x, y)) <=> (in(t, powerSet(cartesianProduct(x, y))) /\ functionalOver(t, x)))) by Definition(setOfFunctions, setOfFunctionsUniqueness)(x, y)
+ thenHave(in(f, setOfFunctions(x, y)) <=> (in(f, powerSet(cartesianProduct(x, y))) /\ functionalOver(f, x))) by InstantiateForall(f)
+
+ have(functionFrom(f, x, y) |- ∀(t, in(t, f) ==> in(t, cartesianProduct(x, y)))) by Tautology.from(
+ lastStep,
+ functionFrom.definition,
+ powerSet.definition of (x -> f, y -> cartesianProduct(x, y)),
+ subset.definition of (x -> f, y -> cartesianProduct(x, y))
+ )
+ thenHave(functionFrom(f, x, y) |- in(pair(t, app(f, t)), f) ==> in(pair(t, app(f, t)), cartesianProduct(x, y))) by InstantiateForall(pair(t, app(f, t)))
+ thenHave((functionFrom(f, x, y), in(pair(t, app(f, t)), f)) |- in(pair(t, app(f, t)), cartesianProduct(x, y))) by Restate
+
+ have((functionFrom(f, x, y), in(pair(t, app(f, t)), f)) |- in(app(f, t), y)) by Tautology.from(
+ lastStep,
+ pairInCartesianProduct of (a -> t, b -> app(f, t))
+ )
+
+ have(thesis) by Cut(appDef, lastStep)
+ }
+
/**
* Theorem --- Cantor's Theorem
*
diff --git a/src/main/scala/lisa/settheory/SetTheoryLibrary.scala b/src/main/scala/lisa/settheory/SetTheoryLibrary.scala
index 4dc84632c..b4a814444 100644
--- a/src/main/scala/lisa/settheory/SetTheoryLibrary.scala
+++ b/src/main/scala/lisa/settheory/SetTheoryLibrary.scala
@@ -15,6 +15,7 @@ object SetTheoryLibrary extends Library with SetTheoryTGAxioms {
val ∅ : Term = emptySet()
extension (t: Term) {
infix def ∈(u: Term): Formula = PredicateFormula(in, Seq(t, u))
+ infix def ∉(u: Term): Formula = !(t ∈ u)
}
}