I translated my friend Cody Roux’s Lean proof https://github.com/teorth/equational_theories/pull/708 that the axioms of Sheffer stroke imply it is a boolean algebra into knuckledragger, my low barrier python proof assistant. Pretty much it is a close one to one translation. I have not even really attempted to understand the proofs at all and was running quite unthinkingly.

Mentally speaking, translating between the lean notation and from the names commut1 and commut2 to sup_comm and inf_comm (a name change I shouldn’t have done) was super confusing as trivial as it sounds. All told I think it took me about 3 hours.

Not to toot my own (and Z3’s) horn, but I was pretty impressed with Knuckledragger’s performance here. A pure algebraic question like this is about as in it’s wheelhouse as it comes though. A nice thing is that eventually Cody’s proofs looked a little less declarative, but I could just dump the lemmas he used in kd.lemma and that papered over it.

I also used ChatGPT to help me. https://chatgpt.com/share/6776116b-c4d4-8008-b3d0-d73c401feb05 It sometimes was good, sometimes not. Of course ChatGPT doesn’t know much if anything about knuckledragger, so I did the first bits to get it started. I am curious if another LLM would do better. I did not use the fancy new o1 model.

I gave it a giant dump of Cody’s file, and then the first bits of my translation. As with my experience translation between programming languages it was so so. It worked better going lemma by lemma. I hope as the LLMs trawl my repo, they might get better at this. The fancy new OpenAI interactive editing mode might help, but I don’t really know how to use it.

I also have github copilot and was hoping it having the nearby proofs and access to cody’s file below would work well. It again was so-so. Sometimes copilot fills out a whole line, but there’s often something a bit off.

One interesting thing was that the LLM invented a small feature I didn’t have. It tried to applied universally quantified proof objects to arguments to instantiate them, but I had never made that particular __call__ operator overloading. I added it in, both because I think it’s kind of cute, but also so I didn’t have to fix the LLM output when it did that. ChatGPT seemed quite confused about the different capabilities of kd.Lemma and kd.Calc. Maybe there’s something there that I should unify them more. The differences exist because Calc is much simpler and both have organically evolved. There is a possibility that the mistakes or assumptions ChatGPT make might be the same as a user who is not me. Kind of interesting.

# The following knuckledragger python proof is derived from the lean proofs below
from kdrag.all import *

simp = []
Stroke = smt.DeclareSort("Stroke")
x,y,z,a,b,d = smt.Consts("x y z a b d", Stroke)
nand = smt.Function("nand", Stroke, Stroke, Stroke)
kd.notation.or_.register(Stroke, nand)

# prime is "not"
invert = kd.define("inv", [x],  x | x)
kd.notation.invert.register(Stroke, invert)

sh1 = kd.axiom(smt.ForAll([a], ~(~a) == a))
sh2 = kd.axiom(smt.ForAll([a,b],  a | (b | ~b) == ~a))
sh3 = kd.axiom(smt.ForAll([a,b,d], ~(a | (b | d)) == (~b | a) | (~d | a)))

c = kd.Calc([a,b], a | b)
c.eq(~(~(a | b)), by=[sh1])
c.eq(~(~(a | (~ (~b)))),by=sh1)
c.eq(~~(~~b | a),by=[sh3, invert.defn])
c.eq(b | a, by=[sh1])
commut = c.qed()

c = kd.Calc([a,b], a | ~a)
c.eq(~~(a | ~a), by=sh1)
c.eq(~((a | ~a) | (b | ~b)),by=sh2)
c.eq(~((b | ~b) | (a | ~a)),by=commut)
c.eq(~~(b | ~b),by=sh2)
c.eq(b | ~b, by=sh1)
all_bot = c.qed()



inf = kd.define("inf", [a,b], ~a | ~b)
sup = kd.define("sup", [a,b], ~(a | b))

bot = kd.define("bot", [], a | ~a)
top = kd.define("top", [], ~bot)

inf_comm = kd.lemma(smt.ForAll([a,b], inf(a,b) == inf(b,a)), by=[inf.defn, commut])
sup_comm = kd.lemma(smt.ForAll([a,b], sup(a,b) == sup(b,a)), by=[sup.defn, commut])

ident1 = kd.lemma(smt.ForAll([a], sup(a,bot) == a), by=[sup.defn, bot.defn, sh1, sh2])
ident2 = kd.lemma(smt.ForAll([a], inf(a,top) == a), by=[inf.defn, top.defn, bot.defn, sh1, sh2])


distrib1 = kd.lemma(smt.ForAll([a,b,d], sup(a, inf(b,d)) == inf(sup(a,b), sup(a,d))), by=[inf.defn, sup.defn, sh1, sh3, commut])
distrib2 = kd.lemma(smt.ForAll([a,b,d], inf(a, sup(b,d)) == sup(inf(a,b), inf(a,d))), by=[inf.defn, sup.defn, sh1, sh3, commut])

compl1 = kd.lemma(smt.ForAll([a], sup(a, ~a) == top), by=[sup.defn, top.defn, bot.defn, sh1, sh2, all_bot])
compl2 = kd.lemma(smt.ForAll([a], inf(a, ~a) == bot), by=[inf.defn, bot.defn, sh1, sh2, all_bot])

simp += [ident1, ident2, compl1, compl2]

c = kd.Calc([a], sup(a, top))
c.eq(inf(sup(a,top), top), by=ident2)
c.eq(inf(top, sup(a,top)), by=inf_comm)
c.eq(inf(sup(a, ~a), sup(a,top)), by=compl1)
c.eq(sup(a, inf(~a, top)), by=distrib1)
c.eq(sup(a, ~a), by=ident2)
c.eq(top, by=compl1)
bound1 = c.qed()

# this is where I first started talking to chatgpt

c = kd.Calc([a], inf(a, bot))
c.eq(sup(inf(a,bot), bot), by=ident1)
c.eq(sup(bot, inf(a,bot)), by=sup_comm)
c.eq(sup(inf(a, ~a), inf(a,bot)), by=compl2)
c.eq(inf(a, sup(~a, bot)), by=distrib2)
c.eq(inf(a, ~a), by=ident1)
c.eq(bot, by=compl2)
bound2 = c.qed()

c = kd.Calc([a, b], sup(a, inf(a, b)))
c.eq(sup(inf(a, top), inf(a, b)), by=ident2)         
c.eq(inf(a, sup(top, b)), by=distrib2)            
c.eq(inf(a, sup(b, top)), by=sup_comm) 
c.eq(inf(a, top), by=bound1)                      
c.eq(a, by=ident2)                                 
absorb1 = c.qed()

c = kd.Calc([a, b], inf(a, sup(a, b)))
c.eq(inf(sup(a, bot), sup(a, b)), by=ident1)  
c.eq(sup(a, inf(bot, b)), by=distrib1)  
c.eq(sup(a, inf(b, bot)), by=inf_comm)       
c.eq(sup(a, bot), by=bound2)                    
c.eq(a, by=ident1)                            
absorb2 = c.qed()

c = kd.Calc([a], a)
c.eq(inf(a, sup(a, bot)), by=absorb2)  # Rewrite using absorb2: a ⊓ (a ⊔ bot) = a
c.eq(inf(a, a), by=ident1)                     # Simplify sup(a, bot) = a using ident1
idemp2 = c.qed()

simp += [bound1, bound2, absorb1, absorb2, idemp2]

l = kd.Lemma(kd.QForAll([a,b], sup(a,b) == top, inf(a,b) == bot, b == ~a))
_a, _b = l.intros()
l.intros()
l.symm()
l.eq(inf(_b,top), by=[ident2])
l.eq(inf(_b, sup(_a, ~_a)), by=[compl1])
l.eq(sup(inf(_b,_a), inf(_b, ~_a)), by=[distrib2])
l.eq(sup(inf(_a, _b), inf(~_a, _b)), by=[inf_comm])
l.eq(sup(bot, inf(~_a, _b)))
l.eq(sup(inf(_a, ~_a), inf(~_a, _b)), by=[compl2])
l.eq(sup(inf(~_a, _a), inf(~_a, _b)), by=[inf_comm])
l.eq(inf(~_a, sup(_a, _b)), by=[distrib2])
l.eq(inf(~_a, top))
l.eq(~_a, by=[ident2])
l.auto()
inv = l.qed()

l = kd.Lemma(kd.QForAll([a], ~~a == a))
_a = l.intros()
l.symm()
l.apply(inv)
l.auto(by=[inf_comm, sup_comm, compl1, compl2])
dne = l.qed()

l = kd.Lemma(kd.QForAll([a,b], ~a == ~b,  a == b))
_a, _b = l.intros()
l.intros()
l.have(~~_a == ~~_b)
l.rw(dne, at=-1)
l.auto(by=dne(_a))
inv_elim = l.qed()

l = kd.Lemma(kd.QForAll([a,b], sup(a, ~b) == top, inf(a, ~b) == bot, a == b))
_a, _b = l.intros()
l.intros()
l.have(~_b == ~_a, by=[inv])
l.apply(inv_elim)
l.auto()
cancel = l.qed()

c = kd.Calc([a, b], sup(a, sup(~a, b)))
c.eq(inf(sup(a, sup(~a, b)), top), by=ident2)          # Rewrite: a ⊔ (¬a ⊔ b) = (a ⊔ (¬a ⊔ b)) ⊓ u
c.eq(inf(top, sup(a, sup(~a, b))), by=inf_comm)         # Commute: u ⊓ (a ⊔ (¬a ⊔ b)) -> (a ⊔ (¬a ⊔ b)) ⊓ u
c.eq(inf(sup(a, ~a), sup(a, sup(~a, b))), by=compl1)   # Replace a ⊔ ¬a = u
c.eq(sup(a, inf(~a, sup(~a, b))), by=distrib1)         # Distribute sup over inf
c.eq(sup(a, ~a), by=[absorb2, compl1])                # Simplify: ¬a ⊓ (¬a ⊔ b) = ¬a
c.eq(top, by=compl1)                                   # Simplify a ⊔ ¬a = u
A1 = c.qed()


#c = kd.Calc([a,b], inf(a, sup(~a, b)) == bot)
c = kd.Calc([a, b], inf(a, inf(~a, b)))
c.eq(sup(inf(a, inf(~a, b)), bot), by=ident1)          # Rewrite: a ⊓ (¬a ⊓ b) = (a ⊓ (¬a ⊓ b)) ⊔ z
c.eq(sup(bot, inf(a, inf(~a, b))), by=sup_comm)         # Commute: z ⊔ (a ⊓ (¬a ⊓ b)) -> (a ⊓ (¬a ⊓ b)) ⊔ z
c.eq(sup(inf(a, ~a), inf(a, inf(~a, b))), by=compl2)   # Replace a ⊓ ¬a = z
c.eq(inf(a, sup(~a, inf(~a, b))), by=distrib2)         # Distribute inf over sup
c.eq(inf(a, ~a), by=[absorb1, compl2])                # Simplify: ¬a ⊔ (¬a ⊓ b) = ¬a
c.eq(bot, by=compl2)                                   # Simplify a ⊓ ¬a = z
A2 = c.qed()

simp += [A1, A2]

c = kd.Lemma(smt.ForAll([a, b], ~sup(a, b) == inf(~a, ~b)))
_a, _b = c.intros()
c.symm()
c.apply(cancel) # Hmm. apply should already apply split?
c.split()

# First case
c.rw(dne)
c.rw(sup_comm)
c.rw(distrib1)
#c
c.auto(by=[dne(_a), dne(_b), A1, inf_comm, sup_comm, ident2])

#c.rw(distrib2)
#from kdrag.solvers import VampireSolver, TweeSolver, EProverTHFSolver


c.auto(by=[A1, A2, inf_comm, distrib2, sup_comm, ident1] + simp)
dm1 = c.qed()

l = kd.Lemma(smt.ForAll([a,b], ~inf(a, b) == sup(~a, ~b)))
_a, _b = l.intros()
l.symm()
l.apply(cancel)
l.split()

l.auto(by=[A1, A2, inf_comm, distrib2, sup_comm, ident1] + simp)
l.auto(by=[dne(_a), dne(_b), A2, inf_comm, distrib2, sup_comm, ident1] + simp)
dm2 = l.qed()


simp += [dm1, dm2]

D1 = kd.lemma(smt.ForAll([a,b,d], sup(sup(a, sup(b, d)), ~a) == top), by=[sup_comm, ident2, distrib1, compl1] + simp)

E1 = kd.lemma(smt.ForAll([a,b,d], inf(b, sup(a, sup(b,d))) == b),  by=[distrib2, absorb2, sup_comm, absorb1])

E2 = kd.lemma(smt.ForAll([a,b,d], sup(b, inf(a, inf(b,d))) == b),  by=[distrib1, absorb1, inf_comm, absorb2])

c = kd.Calc([a, b, d], sup(sup(a, sup(b, d)), ~b))
c.eq(sup(~b, sup(a, sup(b, d))), by=sup_comm)                    # Commute: (a ⊔ (b ⊔ c)) ⊔ bᶜ -> bᶜ ⊔ (a ⊔ (b ⊔ c))
c.eq(inf(top, sup(~b, sup(a, sup(b, d)))), by=[inf_comm] + simp)          # Rewrite: u ⊓ (bᶜ ⊔ (a ⊔ (b ⊔ c)))
c.eq(inf(sup(b, ~b), sup(~b, sup(a, sup(b, d)))), by=simp)      # Substitute u = b ⊔ bᶜ
c.eq(inf(sup(~b, b), sup(~b, sup(a, sup(b, d)))), by=sup_comm)   # Commute: (b ⊔ bᶜ) -> (bᶜ ⊔ b)
c.eq(sup(~b, inf(b, sup(a, sup(b, d)))), by=distrib1)           # Distribute inf over sup
c.eq(sup(~b, b), by=E1)                                         # Simplify using E₁: b ⊓ (a ⊔ (b ⊔ c)) = b
c.eq(top, by=[sup_comm] + simp)                                           # Substitute: bᶜ ⊔ b = u
F1 = c.qed()


c = kd.Calc([a, b, d], sup(sup(a, sup(b, d)), ~d))
c.eq(sup(sup(a, sup(d, b)), ~d), by=sup_comm)  # Apply commutativity to reorder (b ⊔ c) -> (c ⊔ b)
                                  # Apply F₁: (a ⊔ (c ⊔ b)) ⊔ cᶜ = u
c.eq(top, by=F1)                  # Substitute: cᶜ ⊔ (a ⊔ (c ⊔ b)) = u
G1 = c.qed()

c = smt.Const("c", Stroke)

d = kd.Calc([a, b, c], inf(~sup(sup(a, b), c), a))
d.eq(inf(a, inf(inf(~a, ~b), ~c)), by=simp+[inf_comm])  
d.eq(sup(inf(a, inf(inf(~a, ~b), ~c)), bot), by=ident1)               # Rewrite using identity
d.eq(sup(bot, inf(a, inf(inf(~a, ~b), ~c))), by=sup_comm)             # Commute bot ⊔ (...) -> (...) ⊔ bot
d.eq(sup(inf(a, ~a), inf(a, inf(inf(~a, ~b), ~c))), by=compl2)        # Substitute a ⊓ ¬a = z
d.eq(inf(a, sup(~a, inf(inf(~a, ~b), ~c))), by=distrib2)              # Distribute inf over sup
d.eq(inf(a, sup(~a, inf(~c, inf(~a, ~b)))), by=inf_comm)              # Reorder terms: (~b ⊓ (~a ⊓ ~c)) -> (~a ⊓ (~c ⊓ ~b))
d.eq(inf(a, ~a), by=E2)                                               # Apply E₂: ¬a ⊔ (¬a ⊓ (¬c ⊓ ¬b)) = ¬a
d.eq(bot, by=compl2)                                                  # Simplify a ⊓ ¬a = z
H1 = d.qed()

d = kd.Calc([a, b, c], inf(~sup(sup(a, b), c), b))
d.eq(inf(~sup(sup(b, a), c), b), by=sup_comm)  # Reorder: (a ⊔ b ⊔ c) -> (b ⊔ a ⊔ c)
d.eq(bot, by=H1)                                    # Apply \( H₁ \): \( \neg (b \lor a \lor c) \land b = z \)
I1 = d.qed()


d = kd.Calc([a, b, c], inf(~sup(sup(a, b), c), c))

J1 = kd.lemma(smt.ForAll([a,b,c], inf(~sup(sup(a, b), c), c) == bot), by=[sup_comm, inf_comm] + simp)

l = kd.Lemma(smt.ForAll([a,b,c], sup(a,sup(b,c)) == sup(sup(a,b),c)))
_a, _b, _c = l.intros()
l.apply(cancel)
l.split()

l.auto(by=[distrib1, D1, F1, G1] + simp)
l.auto(by=[distrib2, inf_comm, H1, I1, J1] + simp)
assoc1 = l.qed()

simp += [assoc1]

assoc2 = kd.lemma(smt.ForAll([a,b,c], inf(a, inf(b,c)) == inf(inf(a,b),c)), by=[inv_elim] + simp)

le = kd.define("le", [a,b], a == inf(b,a))
kd.notation.le.register(Stroke, le)

le_trans = kd.lemma(kd.QForAll([a,b,c], a <= b, b <= c, a <= c), by=[assoc2, le.defn])

le_antisym = kd.lemma(kd.QForAll([a,b], a <= b, b <= a, a == b), by=[le.defn, inf_comm])


c = kd.Calc([a, b, d], inf(~sup(a, sup(b, d)), a))

# Step-by-step proof
c.eq(inf(a, inf(~a, inf(~b, ~d))), by=simp+[inf_comm])                          # Substitute: ¬(a ⊔ b) = ¬a ⊓ ¬b
c.eq(sup(inf(a, inf(~a, inf(~b, ~d))), bot), by=ident1)               # Rewrite using identity
c.eq(sup(bot, inf(a, inf(~a, inf(~b, ~d)))), by=sup_comm)             # Commute bot ⊔ (...) -> (...) ⊔ bot
c.eq(sup(inf(a, ~a), inf(a, inf(~a, inf(~b, ~d)))), by=compl2)        # Substitute a ⊓ ¬a = z
c.eq(inf(a, sup(~a, inf(~a, inf(~b, ~d)))), by=distrib2)              # Distribute inf over sup
c.eq(inf(a, sup(~a, inf(~d, inf(~a, ~b)))), by=inf_comm)              
c.eq(inf(a, ~a), by=E2)                                               # Apply E₂: ¬a ⊔ (¬c ⊓ (¬a ⊓ ¬b)) = ¬a
c.eq(bot, by=compl2)                                                  # Simplify a ⊓ ¬a = z

# Conclude the proof
H1 = c.qed()

Here is cody’s proof for comparison

import equational_theories.Magma
import Mathlib.Order.BooleanAlgebra

section ShefferLaws

-- Taking notations from https://www.cs.unm.edu/~mccune/papers/basax/v12.pdf and
-- the original Sheffer paper, "A set of five independent postulates for Boolean algebras,
-- with application to logical constants"
class Stroke (α : Type*) where
  stroke : α  α  α

infix:80 " | " => Stroke.stroke

def prime {α} [Stroke α] (a : α) := a | a

postfix:max " ′ " => prime

-- The three laws axiomatize the Sheffer stroke, or NAND, entirely.
-- The remainder of this file is dedicated to that proof.
class ShefferAlgebra (α : Type*) extends Stroke α where
-- We need to assume α is nonempty
elt : α
sh :  a : α, a′′ = a
sh :  a b : α, a | (b | b) = a
sh :  a b c : α, (a | (b | c)) = (b | a) | (c | a)

variable {α : Type*}

variable [ShefferAlgebra α]

open ShefferAlgebra

lemma commut (a b : α) : a | b = b | a := by
  calc
    a | b = (a | b)′′ := by rw [sh]
    _     = (a | (b′′))′′ := by rw [sh b]
    _     = (b′′ | a)′′ := by conv => left; arg 1; arg 1; simp [prime]
                              conv => left; arg 1; rw [sh]
                              rfl
    _     = b | a := by repeat rw [sh]

lemma all_bot (a b : α) : a | a = b | b := by
  calc
    a | a = (a | a)′′ := by rw [sh]
    _      = ((a | a) | (b | b)) := by rw [sh]
    _      = ((b | b) | (a | a)) := by rw [commut]
    _      = (b | b)′′ := by rw [sh]
    _      = b | b     := by rw [sh]

instance : Min α where
  min a b := (a | b)

lemma inf (a b : α) : a  b = (a | b) := rfl

instance : Max α where
 max a b := (a | b)

lemma sup (a b : α) : a  b = (a | b) := rfl

instance : HasCompl α where
  compl a := a

lemma comple (a : α) : a = a := rfl

def z : α := elt | elt

def u : α := z

lemma commut (a b : α) : a  b = b  a := by simp only [sup, commut]

lemma commut (a b : α) : a  b = b  a := by simp only [inf, commut]

@[simp]
lemma ident (a : α) : a  z = a := by simp only [sup, z, sh, sh]

@[simp]
lemma ident (a : α) : a  u = a := by simp only [inf, u, z, sh, sh]

lemma distrib (a b c : α) : a  (b  c) = (a  b)  (a  c) := by
  simp only [sup, inf, sh, sh, commut]

lemma distrib (a b c : α) : a  (b  c) = (a  b)  (a  c) := by
  simp only [sup, inf, sh]
  conv => left; exact (sh _).symm
  rw [sh]
  exact congr rfl (congr (congr rfl (commut ..)) (commut ..))

@[simp]
lemma compl (a : α) : a  a = u := by
  simp only [sup, u, z, comple]
  exact congr rfl (all_bot a elt)

@[simp]
lemma compl (a  : α) : a  a = z := by
  simp only [inf, z, u, comple, sh, commut]
  exact all_bot a elt

@[simp]
lemma bound (a : α) : a  u = u := by
  calc
    a  u = (a  u)  u        := by rw [ident]
    _     = u  (a  u)        := by rw [commut]
    _     = (a  a)  (a  u) := by rw [compl]
    _     = a  (a  u)       := by rw [distrib]
    _     = a  a             := by rw [ident]
    _     = u                  := compl a

@[simp]
lemma bound (a : α) : a  z = z := by
  calc
    a  z = (a  z)  z        := by rw [ident]
    _     = z  (a  z)        := by rw [commut]
    _     = (a  a)  (a  z) := by rw [compl]
    _     = a  (a  z)       := by rw [distrib]
    _     = a  a             := by rw [ident]
    _     = z                  := compl a

@[simp] -- This simp is a little overeager.
lemma absorb (a b : α) : a  (a  b) = a := by
  calc
    a  (a  b) = (a  u)  (a  b) := by rw [ident]
    _           = a  (u  b)       := by rw [distrib]
    _           = a  (b  u)       := by conv => lhs; rw [commut]
    _           = a  u             := by rw [bound]
    _           = a                 := ident a

-- it would be nice to have a "dualization" tactic. This might be some work though.
@[simp] -- This simp is a little overeager.
lemma absorb (a b : α) : a  (a  b) = a := by
  calc
    a  (a  b) = (a  z)  (a  b) := by rw [ident]
    _           = a  (z  b)       := by rw [distrib]
    _           = a  (b  z)       := by conv => lhs; rw [commut]
    _           = a  z             := by rw [bound]
    _           = a                 := ident a

@[simp]
lemma idemp (a : α) : a  a = a := by
  symm
  calc
    a = a  (a  z) := Eq.symm (absorb a z)
    _ = a  a       := by rw [ident]

lemma inv (a a' : α) : a  a' = u  a  a' = z  a' = a := by
  intro h h
  calc
    a' = a'  u               := Eq.symm (ident a')
    _  = a'  (a  a)        := by rw [compl]
    _  = (a'  a)  (a'  a) := by rw [distrib]
    _  = (a'  a)  (a  a') := by conv => left; right; exact commut a' a
    _  = (a  a')  (a  a') := by conv => left; left; exact commut a' a
    _  = z  (a  a')        := by rw [h]
    _  = (a  a)  (a  a') := by rw [compl]
    _  = (a  a)  (a  a') := by conv => left; left; exact commut a a
    _  = a  (a  a')        := by rw [distrib]
    _  = a  u               := by rw [h]
    _  = a                   := ident a

lemma dne (a : α) : aᶜᶜ = a := by
  symm
  apply inv
  . rw [commut]; exact compl a
  . rw [commut]; exact compl a

lemma inv_elim (a b : α) : a = b  a = b := by
  intro h
  have h' : aᶜᶜ = bᶜᶜ := congrArg compl h
  simp only [dne] at h'
  trivial

lemma cancel (a b : α) : a  b = u  a  b = z  a = b := by
  intro h h
  have h : b = a := by apply inv <;> trivial
  apply inv_elim; symm; trivial

@[simp]
lemma A (a b : α) : a  (a  b) = u := by
  calc
    a  (a  b) = (a  (a  b))  u := by rw [ident]
    _            = u  (a  (a  b)) := by rw [commut]
    _            = (a  a)  (a  (a  b)) := by rw [compl]
    _            = a  (a  (a  b)) := by rw [distrib]
    _            = a  a := by rw [absorb, compl]
    _            = u := by rw [compl]

@[simp]
lemma A (a b : α) : a  (a  b) = z := by
  calc
    a  (a  b) = (a  (a  b))  z := by rw [ident]
    _            = z  (a  (a  b)) := by rw [commut]
    _            = (a  a)  (a  (a  b)) := by rw [compl]
    _            = a  (a  (a  b)) := by rw [distrib]
    _            = a  a := by rw [absorb, compl]
    _            = z := by rw [compl]


@[simp]
lemma dm (a b : α) : (a  b) = a  b := by
  symm
  apply cancel <;> simp [dne]
  . rw [commut, distrib]
    conv => left; left; rw [commut]; right; left; exact Eq.symm (dne a)
    rw [A, commut, ident, commut]
    conv => left; right; rw [commut]; left; exact Eq.symm (dne b)
    simp
  . rw [distrib]
    conv => left; left; rw [commut]
    rw [A, commut, ident]
    rw [commut]; conv => left; right; rw [commut]
    simp

@[simp]
lemma dm (a b : α) : (a  b) = a  b := by
  symm
  apply cancel <;> simp [dne]
  . rw [commut, distrib]
    conv => left; left; rw [commut]; right; rw [commut]
    rw [A, commut, ident]
    rw [commut]
    rw [A]
  . rw [commut, distrib]
    conv => left; left; rw [commut]; right; left; exact Eq.symm (dne a)
    rw [A, commut, ident]
    rw [commut]; conv => left; right; rw [commut]; left; exact Eq.symm (dne b)
    rw [A]

lemma D (a b c : α) : (a  (b  c))  a = u := by
  rw [commut]
  conv => left; right; left; exact Eq.symm (dne a)
  simp

lemma E (a b c : α) : b  (a  (b  c)) = b := by rw [distrib, absorb, commut, absorb]

lemma E (a b c : α) : b  (a  (b  c)) = b := by rw [distrib, absorb, commut, absorb]

lemma F (a b c : α) : (a  (b  c))  b = u := by
  calc
    (a  (b  c))  b = b  (a  (b  c)) := by rw [commut]
    _                  = u  (b  (a  (b  c))) := by rw [commut]; simp
    _                  = (b  b)  (b  (a  (b  c))) := by simp
    _                  = (b  b)  (b  (a  (b  c))) := by rw [commut]
    _                  = b  (b  (a  (b  c))) := by rw [distrib]
    _                  = b  b := by rw [E]
    _                  = u := by rw [commut]; simp

lemma G (a b c : α) : (a  (b  c))  c = u := by
  conv => left; left; right; rw [commut]
  apply F

lemma H (a b c : α) : (a  b  c)  a = z := by
  simp; rw [commut]
  calc
    a  (a  b  c) = (a  (a  b  c))  z := by rw [ident]
    _                  = z  (a  (a  b  c)) := by rw [commut]
    _                  = (a  a)  (a  (a  b  c)) := by rw [compl]
    _                  = a  (a  (a  b  c)) := by rw [distrib]
    _                  = a  (a  (c  (a  b))) := by conv => left; right; right; rw [commut]
    _                  = a  a := by rw [E]
    _                  = z := by rw [compl]

lemma I (a b c : α) : (a  b  c)  b = z := by
  conv => left; left; arg 1; left; rw [commut]
  exact H ..

lemma J (a b c : α) : (a  b  c)  c = z := by
  simp
  rw [commut]
  conv => left; right; rw [commut]
  simp

-- Incredibly, these are derivable
@[simp]
lemma assoc (a b c : α) : a  (b  c) = (a  b)  c := by
  apply cancel; simp
  . calc
      (a  (b  c))  (a  b  c) = ((a  (b  c))  (a  b))  ((a  (b  c))  c) := by rw [distrib]
      _ = ((a  (b  c)  a)  ((a  (b  c)  b)))  ((a  (b  c))  c) := by rw [distrib]
      _ = (u  u)  u := by rw [D, F, G]
      _ = u := by simp
  . rw [commut]
    rw [distrib]; rw [distrib]
    calc
      (a  b  c)  a  ((a  b  c)  b  (a  b  c)  c) = z  (z  z) := by rw [H, I, J]
      _ = z := by repeat rw [ident]

-- We don't try to dualize the proof here, that's too painful, we apply de Morgan liberally
@[simp]
lemma assoc (a b c : α) : a  (b  c) = (a  b)  c := by
  apply inv_elim
  simp

instance ShefferLE : LE α :=  λ a b  a = b  a 

lemma Sheffer.le_refl (a : α) : a  a := by simp [ShefferLE]

lemma Sheffer.le_trans (a b c : α) : a  b  b  c  a  c := by
  rw [ShefferLE]
  intro h h
  calc
    a = b  a       := h
    _ = (c  b)  a := by conv => lhs; rw [h]
    _ = c  (b  a) := Eq.symm (assoc c b a)
    _ = c  a       := by rw [ h]

lemma Sheffer.le_antisymm (a b : α) : a  b  b  a  a = b := by
  simp [ShefferLE]
  intro h h
  calc
    a = b  a := h
    _ = a  b := commut ..
    _ = b     := id (Eq.symm h)


instance ShefferToBooleanAlg : BooleanAlgebra α where
  sup := (.  .)
  le_refl := fun a  Sheffer.le_refl a
  le_trans := fun a b c a_1 a_2  Sheffer.le_trans a b c a_1 a_2
  le_antisymm := Sheffer.le_antisymm
  le_sup_left := by
    intro a b
    simp only [ShefferLE, Sup.sup]
    rw [commut]
    exact Eq.symm (absorb a b)
  le_sup_right := by
    intro a b
    simp only [ShefferLE]
    rw [commut, commut]
    exact Eq.symm (absorb b a)
  sup_le := by
    intro a b c
    simp only [ShefferLE]
    intro h h
    calc
      a  b = (c  a)  b       := by conv => left; left; rw [h]
      _     = (c  a)  (c  b) := by conv => left; right; rw [h]
      _     = c  (a  b)       := Eq.symm (distrib ..)
  inf := (.  .)
  inf_le_left := by
    intro a b; simp [ShefferLE]
  inf_le_right := by
    intro a b; simp only [ShefferLE]
    calc
      a  b = a  (b  b) := by rw [idemp]
      _     = a  b  b   := assoc a b b
      _     = b  a  b   := by conv => left; left; rw [commut]
      _     = b  (a  b) := Eq.symm (assoc ..)
  le_inf := by
    intro a b c h h
    simp only [ShefferLE]
    calc
      a = b  a       := h
      _ = b  (c  a) := by conv => left; right; rw [h]
      _ = b  c  a  := by exact assoc b c a
  le_sup_inf := by intro a b c; simp; rw [distrib]; exact Sheffer.le_refl ..
  top := u
  bot := z
  inf_compl_le_bot := by intro a; simp only [compl]; exact Sheffer.le_refl ..
  top_le_sup_compl := by intro a; simp only [compl]; exact Sheffer.le_refl ..
  le_top := by intro a; simp only [ShefferLE]; rw [commut]; exact Eq.symm (ident a)
  bot_le := by intro a; simp [ShefferLE]

end ShefferLaws

section BooleanToSheffer

variable {α : Type*}
variable [BooleanAlgebra α]

-- This is intentionally not an instance to avoid creating an instance cycle
-- Boolean Algebra -> Sheffer Algebra -> Boolean Algebra.
def BooleanAlgToSheffer : ShefferAlgebra α where
  stroke x y := (x  y)
  elt := 
  sh := by intro a; simp [prime]
  sh := by intro a b; simp [prime]
  sh := by intro a b c; simp [prime]; rw [inf_sup_left]
            conv => left; left; rw [inf_comm]
            conv => left; right; rw [inf_comm]

end BooleanToSheffer

Bits and Bobbles

I need to line profile this thing. It’s zippy enough for now, but I’m curious where I’m losing time.

A huge punt in knuckledragger is that I don’t have a notion of typeclass or algebraic hierarchy. I’ve prototyped that a bunch and not liked any of it. Too verbose, too arcane, kind of fishy, non pythonic. I had a latest idea I was kind of intrigued by “declclass”

I should read that mccune et all paper. Seems neat https://www.cs.unm.edu/~mccune/papers/basax/v12.pdf

using a regular list for domain specific simp is ok. I should probably make it so rewrite accepts failure and multiple axioms.

Actually chatgpt’s mistakes are kind of interesting. I could make my proof objects have a call notation to apply them to arguments Is there any other notation that might be nice?