Lean Prover

Hey, welcome back to my blog! It’s been a minute since I last posted, but I’m back with something I’ve been pretty excited about lately. Today, I’m writing about the Lean theorem prover — it’s become pretty relevant with some recent changes in my life, and I thought it’d be cool to share. I recently started contributing to a project called Project Numina, where we formalize a wide range of math competition problems, from AMC to AIME level. The long-term goal is to eventually tackle IMO problems as well. It’s been an awesome opportunity to sharpen my skills and dive deeper into Lean. In this post, I’ll show you some of the neat things you can do with the Lean theorem prover and share a few resources that helped me learn Lean 4.

The first cool thing I want to highlight is formalizing mathematical proofs in Lean. You’ve seen how geometric theorems and propositions can be proven step-by-step, starting from a set of basic axioms. In Lean, you can do exactly the same — except with a high degree of precision and automation. By formalizing these proofs, we ensure that every logical step is verified and there’s no room for error, making the entire process not just rigorous, but also reproducible.

Let's look at an example. In the set of real numbers, or more specifically, in the group of real numbers under the binary operation of addition, we know that for every $x$, there exists a $y$ such that $x + y = 0$. A standard pencil-and-paper proof goes like this: Let $x \in \mathbb{R}$ be arbitrary. Set $y = -x$. Then $y \in \mathbb{R}$, and we have $x + y = 0$. Hence, for every $x$, there exists a $y$ such that $x + y = 0$. In Lean 4, we can formalize this proof as follows:


import Mathlib.Data.Real.Basic

theorem forall_x_exists_y_add_x_eq_zero : ∀ x : ℝ, ∃ y : ℝ, x + y = 0 := by
  intro x
  use -x
  ring 

This proof is an example of a tactic proof in Lean 4. The intro tactic introduces an arbitrary $x\in \mathbb{R}$. The use tactic gives a witness to $x + y = 0$ which is $y = -x$. Finally, the ring tactic finishes the proof by showing $x + (-x) = 0$. There are various tactics tailored for different purposes. I will discuss them in a future post.

Now you might be wondering what Mathlib.Data.Real.Basic stands for. Mathlib is Lean's library of various axiomatizations of theories and different proofs of theorems. Importing Mathlib.Data.Real.Basic gives us the basic facts about the real numbers to work with. Let's look at an example.


import Mathlib

theorem sum_of_evens_is_even {a b : ℕ} (ha : Even a) (hb : Even b) : Even (a + b) := by
  exact Even.add ha hb

This theorem says that if $a$ and $b$ are even numbers in $\mathbb{N}$, then $a + b$ is an even number as well. The clauses ha : Even b and hb : Even b say that ha and ha are witnesses of the facts that $a$ is an even number and $b$ is an even number respectively. Notice that we have used a Mathlib theorem directly which is Even.add that says the sum of two even numbers is even. The exact tactic finishes the proof by taking the witness ha and hb.

Here are some problems that I formalized for Project Numina.


import Mathlib
set_option exponentiation.threshold 5000


/-Let f be a function  with the following properties
(i) f(1) = 1 and
(ii) f(2n)= n ⬝ f(n) for any positive integer n.
What is the value of f(2^100)?
(A)1 (B)2^99 (C)2^100 (D)2^4950 (E)2^9999
-/

theorem algebra_95206  {f : ℕ → ℕ}
(h1 : f 1 = 1) (h2 : ∀ n > 0, f (2 * n) = n * f n) : f (2 ^ 100) = 2 ^ 4950 := by

have h : ∀ n : ℕ, f (2^n) = 2^((n-1) * n / 2) := by
    intro n
    induction n with
    | zero =>
        -- Base case: f(2^0) = f(1) = 1
        rw [pow_zero]
        --a^0 =1 for all a
        exact h1
        -- uses h1
    | succ n ih =>
        -- inductive step
        calc
        f (2 ^ (n + 1))
          = f (2 * 2 ^ n) := by
                rw [pow_succ, mul_comm]
        _ = (2 ^ n) * f (2 ^ n) := by
                apply h2
                --uses the assumption h2
                apply pow_pos
                --shows 2>0
                norm_num
                --normalizes numerical expressions
        _ = (2 ^ n) * (2 ^ ((n - 1) * n / 2)) := by
                rw [ih]
                --uses the inductive hypothesis
        _ = 2 ^ (n + ((n - 1) * n / 2)) := by
                ring_nf
                --simplifying using ring_nf
        _ = 2 ^ (((n + 1 - 1) * (n + 1)) / 2) := by
                      congr
                      --ensures both sides are structurally same
                      symm
                      nth_rewrite 1 [mul_comm]
                      --apply tactic to 1st occurrence etc
                      nth_rewrite 2 [mul_comm]
                      nth_rewrite 2 [add_comm]
                      apply Nat.div_eq_of_eq_mul_right
                      --expresses division in form of multiplication
                      simp
                      --simplifies expression
                      rw [mul_add]
                      --expands product over addition
                      cases n with
                      --case analysis
                      | zero => simp
                      | succ n =>
                        simp
                        have : 2 * ((n + 1) * n / 2) = (n+1) * n := by
                                nth_rewrite 1 [mul_comm]
                                --Swap multiplication order in first occurrence
                                refine Nat.div_two_mul_two_of_even ?_
                                --property that division by 2 is valid when even
                                refine Nat.even_mul.mpr ?_
                                --`(n + 1) * n` is even
                                by_cases H : Even n
                                --either `n` is even or it is odd
                                · tauto
                                · left
                                  exact Nat.even_add_one.mpr H
                                  --if n is odd then n+1 is even
                        rw [this]
                        --rewrite using proven equality
                        linarith
                        --finishes goal


exact h 100

import Mathlib

/- 10.2. Given numbers $a, b, c$. It is known that for any $x$ the inequality

$$

a x^{2}+b x+c \geqslant b x^{2}+c x+a \geqslant c x^{2}+a x+b

$$

holds. Prove that $a=b=c$. -/

theorem inequalities_143807 (a b c : ℝ)
(h : ∀ x : ℝ, a * x^2 + b * x + c ≥ b * x^2 + c * x + a ∧ b * x^2 + c * x + a ≥ c * x^2 + a * x + b) :
    a = b ∧ b = c := by
    --Using `x = 0` we get two inequalities `c ≥ a` and `a ≥ b`.
    have h0 := h 0
    rcases h0 with ⟨h1, h2⟩
    have hcgea : c ≥ a := by linarith [h1]
    have hageb : a ≥ b := by linarith [h2]
    --Using `x = -1` we get the inequality `a ≥ c`.
    have hneg1 := h (-1)
    rcases hneg1 with ⟨h3, h4⟩
    have h3_1 : a - b + c ≥ b - c + a := by linarith [h3]
    have h4_1 : b - c + a ≥ c - a + b := by linarith [h4]
    have h4_1_1 : a ≥ c := by linarith [h4_1]
    --We get `a = c` using `c ≥ a` and `a ≥ c`.
    have haeqc : a = c := by
      apply le_antisymm
      exact hcgea
      exact h4_1_1
    --We are almost done. We get `b ≥ c` using `x = -2`.
    --Then we use the fact `a = c` to end the proof.
    have hneg2 := h (-2)
    rcases hneg2 with ⟨h5, h6⟩
    have h5_1 : 4 * a - 2 * b + c ≥ 4 * b - 2 * c + a := by linarith [h5]
    have h6_1 : 4 * b - 2 * c + a ≥ 4 * c - 2 * a + b := by linarith [h6]
    have h6_1_1 : 3 * b  - 6 * c + 3 * a ≥ 0 := by linarith [h6_1]
    rw [haeqc] at h6_1_1
    have h6_1_2 : 3 * b ≥ 3 * c := by linarith [h6_1_1]
    have hcleb : c ≤ b := by linarith [h6_1_2]
    have hblec : b ≤ c := by
      rw [haeqc] at hageb
      exact hageb
    have hbeqc : b = c := by exact le_antisymm hblec hcleb
    rw [←haeqc] at hcleb
    have haeqb : a = b := by exact le_antisymm hcleb hageb
    exact ⟨haeqb, hbeqc⟩

I was introduced to the Lean theorem prover using a nice and fun game called the Natural Number Game. It's a great way to learn some Lean syntax and various useful tactics. There are more fun games about set theory and logic as well. You can find them here.

After familiarizing myself with the basics of Lean, I used the free book Mathematics in Lean. It's a nice online book that introduced me to do mathematics in Lean 4. The other free online book is Theorem Proving in Lean. I gained a lot by reading these two books in parallel. In addition to these books, I followed Kevin Buzzard's Formalizing Mathematics course as well.

Just as we use set theory as a foundation to build mathematics, we can also use type theory for the same purpose. Lean is built on a type theory known as dependent type theory. In this blog post, I won’t go into types or dependent types—but it’s useful to know that dependent types are the foundation of how Lean works.

On the meta-side of Lean, there are communities working on creating tactics and that'll help automate mathematical proofs. This adds to the list of benefits of Lean being an interactive theorem prover. Such tactics will allow users to write clear and concise proofs. Here is a nice talk by Jason Rute discussing the meta aspects of Lean.

As I mentioned before, Mathlib is Lean's library of tactics and theorems. Anybody can contribute to Mathlib by proving theorems and communicating them to the Lean community. Another great resource is Lean community's Zulip Chat, where we can ask questions and get help from the experts.

In the next post, I’ll explore some powerful tactics commonly used in Lean for proving theorems. See you soon!