5. Teori Bilangan Elementer

Dalam bab ini, kami menunjukkan cara memformalkan beberapa hasil dasar dalam teori bilangan. Ketika kita beralih ke materi matematika yang lebih substantif, bukti-buktinya akan semakin panjang dan rumit, dengan bertumpu pada keterampilan yang telah Anda kuasai.

5.1. Akar Irasional

Mari kita mulai dengan fakta yang telah dikenal oleh bangsa Yunani kuno, yaitu bahwa akar kuadrat dari 2 merupakan bilangan irasional. Jika kita mengandaikan sebaliknya, kita dapat menulis \(\sqrt{2} = a / b\) sebagai pecahan dalam bentuk paling sederhana. Menguadratkan kedua ruas menghasilkan \(a^2 = 2 b^2\), yang menyiratkan bahwa \(a\) genap. Jika kita menulis \(a = 2c\), kita memperoleh \(4c^2 = 2 b^2\) dan dengan demikian \(b^2 = 2 c^2\). Ini menyiratkan bahwa \(b\) juga genap, bertentangan dengan asumsi bahwa \(a / b\) telah disederhanakan ke bentuk paling sederhana.

Pernyataan bahwa \(a / b\) merupakan pecahan dalam bentuk paling sederhana berarti bahwa \(a\) dan \(b\) tidak memiliki faktor persekutuan, atau dengan kata lain, keduanya saling prima. Mathlib mendefinisikan predikat Nat.Coprime m n sebagai Nat.gcd m n = 1. Dengan notasi proyeksi anonim Lean, jika s dan t adalah ekspresi bertipe Nat, kita dapat menulis s.Coprime t sebagai pengganti Nat.Coprime s t, dan hal serupa berlaku untuk Nat.gcd. Seperti biasa, Lean sering kali menguraikan definisi Nat.Coprime secara otomatis ketika diperlukan, tetapi kita juga dapat melakukannya secara manual dengan menulis ulang atau menyederhanakan menggunakan pengidentifikasi Nat.Coprime. Taktik norm_num cukup cerdas untuk menghitung nilai konkret.

#print Nat.Coprime

example (m n : Nat) (h : m.Coprime n) : m.gcd n = 1 :=
  h

example (m n : Nat) (h : m.Coprime n) : m.gcd n = 1 := by
  rw [Nat.Coprime] at h
  exact h

example : Nat.Coprime 12 7 := by norm_num

example : Nat.gcd 12 8 = 4 := by norm_num

Kita telah menjumpai fungsi gcd di Bab 2.4. Tersedia pula versi gcd untuk bilangan bulat; nanti kita akan kembali membahas hubungan antara berbagai sistem bilangan. Bahkan tersedia fungsi gcd generik serta gagasan generik tentang Prime dan Coprime yang bermakna pada kelas umum struktur aljabar. Dalam bab berikutnya, kita akan memahami cara Lean menangani tingkat keumuman ini. Sementara itu, dalam bagian ini kita akan membatasi perhatian pada bilangan asli.

Kita juga memerlukan gagasan bilangan prima, Nat.Prime. Teorema Nat.prime_def_lt memberikan salah satu karakterisasi yang sudah dikenal, sedangkan Nat.Prime.eq_one_or_self_of_dvd memberikan karakterisasi lainnya.

#check Nat.prime_def_lt

example (p : ) (prime_p : Nat.Prime p) : 2  p   m : , m < p  m  p  m = 1 := by
  rwa [Nat.prime_def_lt] at prime_p

#check Nat.Prime.eq_one_or_self_of_dvd

example (p : ) (prime_p : Nat.Prime p) :  m : , m  p  m = 1  m = p :=
  prime_p.eq_one_or_self_of_dvd

example : Nat.Prime 17 := by norm_num

-- sering digunakan
example : Nat.Prime 2 :=
  Nat.prime_two

example : Nat.Prime 3 :=
  Nat.prime_three

Pada bilangan asli, bilangan prima memiliki sifat bahwa ia tidak dapat ditulis sebagai hasil kali faktor-faktor nontrivial. Dalam konteks matematika yang lebih luas, elemen gelanggang dengan sifat ini disebut tak tereduksi. Suatu elemen gelanggang disebut prima jika setiap kali ia membagi suatu hasil kali, ia membagi salah satu faktornya. Salah satu sifat penting bilangan asli ialah bahwa dalam konteks ini kedua gagasan tersebut berimpit, yang menghasilkan teorema Nat.Prime.dvd_mul.

Kita dapat menggunakan fakta ini untuk menetapkan sifat kunci dalam argumen di atas: jika kuadrat suatu bilangan adalah genap, maka bilangan tersebut juga genap. Mathlib mendefinisikan predikat Even dalam Algebra.Group.Even, tetapi karena alasan yang akan menjadi jelas di bawah ini, kita cukup menggunakan 2 m untuk menyatakan bahwa m genap.

#check Nat.Prime.dvd_mul
#check Nat.Prime.dvd_mul Nat.prime_two
#check Nat.prime_two.dvd_mul

theorem even_of_even_sqr {m : } (h : 2  m ^ 2) : 2  m := by
  rw [pow_two, Nat.prime_two.dvd_mul] at h
  cases h <;> assumption

example {m : } (h : 2  m ^ 2) : 2  m :=
  Nat.Prime.dvd_of_dvd_pow Nat.prime_two h

Seiring melanjutkan pembahasan, Anda perlu mahir menemukan fakta-fakta yang diperlukan. Ingatlah bahwa jika Anda dapat menebak awalan suatu nama dan telah mengimpor pustaka yang relevan, Anda dapat menggunakan pelengkapan tab (kadang-kadang dengan ctrl-tab) untuk menemukan hal yang dicari. Anda dapat menggunakan ctrl-click pada pengidentifikasi mana pun untuk melompat ke berkas tempat pengidentifikasi tersebut didefinisikan, sehingga Anda dapat menelusuri definisi dan teorema di sekitarnya. Anda juga dapat memakai mesin pencari pada laman web komunitas Lean, dan jika semua cara lain tidak berhasil, jangan ragu bertanya di Zulip.

example (a b c : Nat) (h : a * b = a * c) (h' : a  0) : b = c :=
  -- apply? menyarankan yang berikut:
  (mul_right_inj' h').mp h

Inti bukti kita bahwa akar kuadrat dari dua bersifat irasional terkandung dalam teorema berikut. Cobalah lengkapi sketsa buktinya dengan menggunakan even_of_even_sqr dan teorema Nat.dvd_gcd.

example {m n : } (coprime_mn : m.Coprime n) : m ^ 2  2 * n ^ 2 := by
  intro sqr_eq
  have : 2  m := by
    sorry
  obtain k, meq := dvd_iff_exists_eq_mul_left.mp this
  have : 2 * (2 * k ^ 2) = 2 * n ^ 2 := by
    rw [ sqr_eq, meq]
    ring
  have : 2 * k ^ 2 = n ^ 2 :=
    sorry
  have : 2  n := by
    sorry
  have : 2  m.gcd n := by
    sorry
  have : 2  1 := by
    sorry
  norm_num at this

Sebenarnya, dengan sangat sedikit perubahan, kita dapat mengganti 2 dengan sembarang bilangan prima. Cobalah pada contoh berikutnya. Di akhir bukti, Anda perlu menurunkan kontradiksi dari p 1. Anda dapat menggunakan Nat.Prime.two_le, yang menyatakan bahwa setiap bilangan prima lebih besar dari atau sama dengan dua, serta Nat.le_of_dvd.

example {m n p : } (coprime_mn : m.Coprime n) (prime_p : p.Prime) : m ^ 2  p * n ^ 2 := by
  sorry

Mari kita pertimbangkan pendekatan lain. Berikut bukti singkat bahwa jika \(p\) prima, maka \(m^2 \ne p n^2\): jika kita mengasumsikan \(m^2 = p n^2\) dan meninjau faktorisasi prima \(m\) dan \(n\), maka jumlah kemunculan \(p\) di ruas kiri persamaan genap dan jumlah kemunculannya di ruas kanan ganjil, suatu kontradiksi. Perhatikan bahwa argumen ini mengharuskan \(n\), dan karenanya \(m\), tidak sama dengan nol. Formalisasi di bawah ini menegaskan bahwa asumsi tersebut sudah memadai.

Teorema faktorisasi tunggal menyatakan bahwa setiap bilangan asli selain nol dapat ditulis secara unik sebagai hasil kali bilangan-bilangan prima. Mathlib memuat versi formal teorema ini melalui fungsi Nat.primeFactorsList, yang mengembalikan daftar faktor prima suatu bilangan dalam urutan tak menurun. Pustaka membuktikan bahwa semua elemen Nat.primeFactorsList n bersifat prima, bahwa setiap n yang lebih besar dari nol sama dengan hasil kali faktor-faktornya, dan bahwa jika n sama dengan hasil kali daftar bilangan prima lainnya, maka daftar tersebut merupakan permutasi dari Nat.primeFactorsList n.

#check Nat.primeFactorsList
#check Nat.prime_of_mem_primeFactorsList
#check Nat.prod_primeFactorsList
#check Nat.primeFactorsList_unique

Anda dapat menelusuri teorema-teorema ini dan teorema lain di sekitarnya, meskipun kita belum membahas keanggotaan daftar, hasil kali, atau permutasi. Kita tidak memerlukan semua itu untuk keperluan saat ini. Sebagai gantinya, kita akan menggunakan fakta bahwa Mathlib memiliki fungsi Nat.factorization, yang merepresentasikan data yang sama sebagai sebuah fungsi. Secara khusus, Nat.factorization n p, yang juga dapat kita tulis sebagai n.factorization p, mengembalikan multiplisitas p dalam faktorisasi prima n. Kita akan menggunakan tiga fakta berikut.

theorem factorization_mul' {m n : } (mnez : m  0) (nnez : n  0) (p : ) :
    (m * n).factorization p = m.factorization p + n.factorization p := by
  rw [Nat.factorization_mul mnez nnez]
  rfl

theorem factorization_pow' (n k p : ) :
    (n ^ k).factorization p = k * n.factorization p := by
  rw [Nat.factorization_pow]
  rfl

theorem Nat.Prime.factorization' {p : } (prime_p : p.Prime) :
    p.factorization p = 1 := by
  rw [prime_p.factorization]
  simp

Sebenarnya, n.factorization didefinisikan di Lean sebagai fungsi dengan dukungan berhingga, yang menjelaskan notasi tak biasa yang akan Anda lihat ketika menelusuri bukti-bukti di atas. Jangan khawatirkan hal ini sekarang. Untuk keperluan kita di sini, kita dapat menggunakan ketiga teorema di atas sebagai kotak hitam.

Contoh berikut menunjukkan bahwa penyederhana cukup cerdas untuk mengganti n^2 0 dengan n 0. Taktik simpa hanya memanggil simp yang diikuti dengan assumption.

Cobalah gunakan identitas-identitas di atas untuk melengkapi bagian bukti yang masih kosong.

example {m n p : } (nnz : n  0) (prime_p : p.Prime) : m ^ 2  p * n ^ 2 := by
  intro sqr_eq
  have nsqr_nez : n ^ 2  0 := by simpa
  have eq1 : Nat.factorization (m ^ 2) p = 2 * m.factorization p := by
    sorry
  have eq2 : (p * n ^ 2).factorization p = 2 * n.factorization p + 1 := by
    sorry
  have : 2 * m.factorization p % 2 = (2 * n.factorization p + 1) % 2 := by
    rw [ eq1, sqr_eq, eq2]
  rw [add_comm, Nat.add_mul_mod_self_left, Nat.mul_mod_right] at this
  norm_num at this

Salah satu hal menarik tentang bukti ini adalah bahwa bukti tersebut juga dapat digeneralisasi. Tidak ada yang istimewa tentang 2; dengan sedikit perubahan, bukti ini menunjukkan bahwa setiap kali kita menulis m^k = r * n^k, multiplisitas setiap bilangan prima p di dalam r harus merupakan kelipatan k.

Untuk menggunakan factorization_mul' pada r * n^k, kita perlu mengetahui bahwa r tidak sama dengan nol. Namun, ketika r sama dengan nol, teorema di bawah ini bersifat trivial dan mudah dibuktikan oleh penyederhana. Karena itu, bukti dijalankan dengan pemisahan kasus. Baris rcases r with _ | r mengganti sasaran dengan dua versi: satu versi yang mengganti r dengan 0, dan versi lain yang mengganti r dengan r + 1. Dalam kasus kedua, kita dapat menggunakan teorema r.succ_ne_zero, yang menetapkan r + 1 0 (succ merupakan singkatan dari successor atau penerus).

Perhatikan pula bahwa baris yang dimulai dengan have npow_nz : memberikan term bukti singkat untuk n^k 0. Untuk memahami cara kerjanya, cobalah menggantinya dengan bukti taktik, lalu pikirkan bagaimana taktik-taktik tersebut mendeskripsikan term bukti.

Cobalah lengkapi bagian-bagian bukti di bawah ini yang masih kosong. Di bagian paling akhir, Anda dapat menggunakan Nat.dvd_sub dan Nat.dvd_mul_right untuk menyelesaikannya.

Perhatikan bahwa contoh ini tidak mengasumsikan p prima, tetapi kesimpulannya bersifat trivial ketika p tidak prima karena r.factorization p menurut definisi bernilai nol, dan bukti tersebut tetap berlaku dalam semua kasus.

example {m n k r : } (nnz : n  0) (pow_eq : m ^ k = r * n ^ k) {p : } :
    k  r.factorization p := by
  rcases r with _ | r
  · simp
  have npow_nz : n ^ k  0 := fun npowz  nnz (eq_zero_of_pow_eq_zero npowz)
  have eq1 : (m ^ k).factorization p = k * m.factorization p := by
    sorry
  have eq2 : ((r + 1) * n ^ k).factorization p =
      k * n.factorization p + (r + 1).factorization p := by
    sorry
  have : r.succ.factorization p = k * m.factorization p - k * n.factorization p := by
    rw [ eq1, pow_eq, eq2, add_comm, Nat.add_sub_cancel]
  rw [this]
  sorry

Ada beberapa cara yang mungkin kita tempuh untuk menyempurnakan hasil-hasil ini. Pertama-tama, bukti bahwa akar kuadrat dari dua bersifat irasional seharusnya benar-benar membahas akar kuadrat dari dua, yang dapat dipahami sebagai bilangan real atau kompleks. Pernyataan bahwa bilangan itu irasional juga seharusnya membahas bilangan rasional, yakni bahwa tidak ada bilangan rasional yang sama dengannya. Selain itu, kita seharusnya memperluas teorema-teorema dalam bagian ini ke bilangan bulat. Meskipun secara matematis jelas bahwa jika akar kuadrat dari dua dapat ditulis sebagai hasil bagi dua bilangan bulat, maka ia juga dapat ditulis sebagai hasil bagi dua bilangan asli, pembuktian formalnya memerlukan sejumlah usaha.

Di Mathlib, bilangan asli, bilangan bulat, bilangan rasional, bilangan real, dan bilangan kompleks direpresentasikan oleh tipe data yang terpisah. Membatasi perhatian pada masing-masing domain sering kali membantu: kita akan melihat bahwa induksi pada bilangan asli mudah dilakukan, dan penalaran tentang keterbagian bilangan bulat paling mudah ketika bilangan real tidak ikut terlibat. Namun, menjembatani berbagai domain tersebut cukup merepotkan, dan kita harus menghadapinya. Kita akan kembali ke persoalan ini nanti dalam bab ini.

Kita juga patut berharap dapat memperkuat kesimpulan teorema terakhir untuk menyatakan bahwa bilangan r merupakan pangkat ke-k, karena akar ke-k-nya diperoleh dengan mengalikan setiap bilangan prima yang membagi r, masing-masing dipangkatkan dengan multiplisitasnya di r setelah dibagi k. Untuk melakukan hal itu, kita memerlukan sarana yang lebih baik untuk menalar tentang hasil kali dan jumlah pada himpunan berhingga, yang juga akan kita bahas kembali nanti.

Sebenarnya, semua hasil dalam bagian ini telah dibuktikan di Mathlib dengan tingkat keumuman yang jauh lebih tinggi, dalam Data.Real.Irrational. Gagasan multiplicity didefinisikan untuk sembarang monoid komutatif, dan nilainya berupa bilangan asli yang diperluas, enat, yang menambahkan nilai tak hingga pada bilangan asli. Dalam bab berikutnya, kita akan mulai mengembangkan sarana untuk memahami cara Lean mendukung tingkat keumuman semacam ini.

5.2. Induksi dan Rekursi

Himpunan bilangan asli \(\mathbb{N} = \{ 0, 1, 2, \ldots \}\) bukan hanya penting secara fundamental, melainkan juga berperan sentral dalam membangun objek matematika baru. Fondasi Lean memungkinkan kita mendeklarasikan tipe induktif, yaitu tipe yang dibangkitkan secara induktif oleh suatu daftar konstruktor. Di Lean, bilangan asli dideklarasikan sebagai berikut.

inductive Nat where
  | zero : Nat
  | succ (n : Nat) : Nat

Anda dapat menemukan deklarasi ini di pustaka dengan menulis #check Nat, lalu menggunakan ctrl-click pada pengidentifikasi Nat. Perintah tersebut menetapkan bahwa Nat adalah tipe data yang dibangkitkan secara bebas dan induktif oleh dua konstruktor zero : Nat dan succ : Nat Nat. Tentu saja, pustaka memperkenalkan notasi dan 0 masing-masing untuk Nat dan zero. (Lambang bilangan diterjemahkan menjadi representasi biner, tetapi kita belum perlu memikirkan rinciannya sekarang.)

Bagi matematikawan dalam praktik, kata "bebas" berarti bahwa tipe Nat memiliki elemen zero dan fungsi penerus injektif succ yang citranya tidak memuat zero.

example (n : Nat) : n.succ  Nat.zero :=
  Nat.succ_ne_zero n

example (m n : Nat) (h : m.succ = n.succ) : m = n :=
  Nat.succ.inj h

Bagi matematikawan dalam praktik, kata "induktif" berarti bahwa bilangan asli dilengkapi dengan prinsip pembuktian melalui induksi dan prinsip pendefinisian melalui rekursi. Bagian ini akan menunjukkan cara menggunakan kedua prinsip tersebut.

Berikut contoh definisi rekursif untuk fungsi faktorial.

def fac :   
  | 0 => 1
  | n + 1 => (n + 1) * fac n

Sintaks ini memerlukan sedikit pembiasaan. Perhatikan bahwa tidak ada := pada baris pertama. Dua baris berikutnya memberikan kasus dasar dan langkah induktif untuk definisi rekursif tersebut. Persamaan-persamaan ini berlaku secara definisional, tetapi juga dapat digunakan secara manual dengan memberikan fac sebagai argumen kepada simp atau rw.

example : fac 0 = 1 :=
  rfl

example : fac 0 = 1 := by
  rw [fac]

example : fac 0 = 1 := by
  simp [fac]

example (n : ) : fac (n + 1) = (n + 1) * fac n :=
  rfl

example (n : ) : fac (n + 1) = (n + 1) * fac n := by
  rw [fac]

example (n : ) : fac (n + 1) = (n + 1) * fac n := by
  simp [fac]

Sebenarnya, fungsi faktorial telah didefinisikan di Mathlib sebagai Nat.factorial. Sekali lagi, Anda dapat melompat ke definisinya dengan mengetik #check Nat.factorial dan menggunakan ctrl-click. Untuk keperluan ilustrasi, kita akan tetap menggunakan fac dalam contoh-contoh. Anotasi @[simp] sebelum definisi Nat.factorial menetapkan bahwa persamaan definisinya harus ditambahkan ke basis data identitas yang digunakan penyederhana secara bawaan.

Prinsip induksi menyatakan bahwa kita dapat membuktikan pernyataan umum tentang bilangan asli dengan membuktikan bahwa pernyataan tersebut berlaku untuk 0 dan bahwa setiap kali pernyataan itu berlaku untuk bilangan asli \(n\), ia juga berlaku untuk \(n + 1\). Karena itu, baris induction' n with n ih dalam bukti di bawah ini menghasilkan dua sasaran: pada sasaran pertama kita perlu membuktikan 0 < fac 0, sedangkan pada sasaran kedua kita memperoleh asumsi tambahan ih : 0 < fac n dan harus membuktikan 0 < fac (n + 1). Frasa with n ih digunakan untuk menamai variabel dan asumsi hipotesis induksi, dan Anda bebas memilih nama apa pun untuk keduanya.

theorem fac_pos (n : ) : 0 < fac n := by
  induction' n with n ih
  · rw [fac]
    exact zero_lt_one
  rw [fac]
  exact mul_pos n.succ_pos ih

Taktik induction' cukup cerdas untuk menyertakan hipotesis yang bergantung pada variabel induksi sebagai bagian dari hipotesis induksi. Telusurilah contoh berikut untuk melihat apa yang terjadi.

theorem dvd_fac {i n : } (ipos : 0 < i) (ile : i  n) : i  fac n := by
  induction' n with n ih
  · exact absurd ipos (not_lt_of_ge ile)
  rw [fac]
  rcases Nat.of_le_succ ile with h | h
  · apply dvd_mul_of_dvd_right (ih h)
  rw [h]
  apply dvd_mul_right

Contoh berikut memberikan batas bawah kasar untuk fungsi faktorial. Ternyata lebih mudah memulainya dengan pembuktian berdasarkan kasus, sehingga sisa bukti dimulai dari kasus \(n = 1\). Cobalah lengkapi argumennya dengan pembuktian melalui induksi menggunakan pow_succ atau pow_succ'.

theorem pow_two_le_fac (n : ) : 2 ^ (n - 1)  fac n := by
  rcases n with _ | n
  · simp [fac]
  sorry

Induksi sering digunakan untuk membuktikan identitas yang melibatkan jumlah dan hasil kali berhingga. Mathlib mendefinisikan ekspresi Finset.sum s f, dengan s : Finset α sebagai himpunan berhingga beranggotakan elemen bertipe α dan f sebagai fungsi yang didefinisikan pada α. Kodomain f dapat berupa tipe apa pun yang mendukung operasi penjumlahan komutatif dan asosiatif dengan elemen nol. Jika Anda mengimpor Algebra.BigOperators.Ring dan menjalankan perintah open BigOperators, Anda dapat menggunakan notasi yang lebih intuitif x s, f x. Tentu saja, tersedia operasi dan notasi analog untuk hasil kali berhingga.

Kita akan membahas tipe Finset dan operasi-operasi yang didukungnya dalam bagian berikutnya, lalu kembali membahasnya dalam bab selanjutnya. Untuk sekarang, kita hanya akan menggunakan Finset.range n, yaitu himpunan berhingga bilangan asli yang lebih kecil dari n.

variable {α : Type*} (s : Finset ) (f :   ) (n : )

#check Finset.sum s f
#check Finset.prod s f

open BigOperators
open Finset

example : s.sum f =  x  s, f x :=
  rfl

example : s.prod f =  x  s, f x :=
  rfl

example : (range n).sum f =  x  range n, f x :=
  rfl

example : (range n).prod f =  x  range n, f x :=
  rfl

Fakta Finset.sum_range_zero dan Finset.sum_range_succ memberikan deskripsi rekursif untuk penjumlahan hingga \(n\), dan hal serupa berlaku untuk hasil kali.

example (f :   ) :  x  range 0, f x = 0 :=
  Finset.sum_range_zero f

example (f :   ) (n : ) :  x  range n.succ, f x =  x  range n, f x + f n :=
  Finset.sum_range_succ f n

example (f :   ) :  x  range 0, f x = 1 :=
  Finset.prod_range_zero f

example (f :   ) (n : ) :  x  range n.succ, f x = ( x  range n, f x) * f n :=
  Finset.prod_range_succ f n

Identitas pertama dalam setiap pasangan berlaku secara definisional; artinya, Anda dapat mengganti buktinya dengan rfl.

Pernyataan berikut menyatakan fungsi faktorial yang kita definisikan sebagai suatu hasil kali.

example (n : ) : fac n =  i  range n, (i + 1) := by
  induction' n with n ih
  · simp [fac]
  simp [fac, ih, prod_range_succ, mul_comm]

Fakta bahwa kita menyertakan mul_comm sebagai kaidah penyederhanaan perlu diberi penjelasan. Menyederhanakan dengan identitas x * y = y * x tampaknya berbahaya, karena biasanya hal itu akan berulang tanpa henti. Penyederhana Lean cukup cerdas untuk mengenali masalah tersebut dan hanya menerapkan kaidah ketika term yang dihasilkan memiliki nilai lebih kecil dalam suatu pengurutan term yang tetap tetapi arbitrer. Contoh berikut menunjukkan bahwa penyederhanaan menggunakan tiga kaidah mul_assoc, mul_comm, dan mul_left_comm berhasil mengenali hasil kali yang sama hingga perbedaan penempatan tanda kurung dan urutan variabel.

example (a b c d e f : ) : a * (b * c * f * (d * e)) = d * (a * f * e) * (c * b) := by
  simp [mul_comm, mul_left_comm]

Secara kasar, kaidah-kaidah tersebut bekerja dengan mendorong tanda kurung ke kanan, lalu mengurutkan ulang ekspresi pada kedua ruas hingga keduanya mengikuti urutan kanonik yang sama. Menyederhanakan dengan kaidah-kaidah ini, serta kaidah yang bersesuaian untuk penjumlahan, merupakan kiat yang berguna.

Kembali ke identitas penjumlahan, kami menyarankan Anda menelusuri bukti berikut bahwa jumlah bilangan asli sampai dengan \(n\) adalah \(n (n + 1) / 2\). Langkah pertama bukti tersebut menghilangkan penyebut. Cara ini umumnya berguna ketika memformalkan identitas, karena perhitungan dengan pembagian biasanya memiliki syarat samping. (Demikian pula, sebaiknya hindari pengurangan pada bilangan asli jika memungkinkan.)

theorem sum_id (n : ) :  i  range (n + 1), i = n * (n + 1) / 2 := by
  symm; apply Nat.div_eq_of_eq_mul_right (by norm_num : 0 < 2)
  induction' n with n ih
  · simp
  rw [Finset.sum_range_succ, mul_add 2,  ih]
  ring

Kami menganjurkan Anda membuktikan identitas analog untuk jumlah kuadrat, serta identitas lain yang dapat ditemukan di web.

theorem sum_sqr (n : ) :  i  range (n + 1), i ^ 2 = n * (n + 1) * (2 * n + 1) / 6 := by
  sorry

Dalam pustaka inti Lean, penjumlahan dan perkalian sendiri didefinisikan menggunakan definisi rekursif, dan sifat-sifat dasarnya ditetapkan melalui induksi. Jika Anda senang memikirkan topik-topik fondasional semacam itu, Anda mungkin akan menikmati penelusuran bukti komutativitas dan asosiativitas perkalian dan penjumlahan, serta distributivitas perkalian terhadap penjumlahan. Anda dapat melakukannya pada suatu salinan tipe bilangan asli dengan mengikuti kerangka di bawah ini. Perhatikan bahwa kita dapat menggunakan taktik induction bersama MyNat; Lean cukup cerdas untuk mengetahui bahwa ia harus menggunakan prinsip induksi yang relevan (yang tentu saja sama dengan prinsip untuk Nat).

Kami membantu Anda memulai dengan komutativitas penjumlahan. Pedoman yang baik ialah bahwa karena penjumlahan dan perkalian didefinisikan secara rekursif pada argumen kedua, umumnya menguntungkan untuk melakukan pembuktian melalui induksi pada variabel yang muncul pada posisi tersebut. Menentukan variabel yang akan digunakan dalam bukti asosiativitas sedikit lebih rumit.

Menulis berbagai hal tanpa notasi yang lazim untuk nol, satu, penjumlahan, dan perkalian dapat membingungkan. Nanti kita akan mempelajari cara mendefinisikan notasi semacam itu. Bekerja dalam namespace MyNat berarti kita dapat menulis zero dan succ alih-alih MyNat.zero dan MyNat.succ, dan penafsiran nama-nama tersebut didahulukan daripada penafsiran lainnya. Di luar namespace, nama lengkap add yang didefinisikan di bawah ini, misalnya, adalah MyNat.add.

Jika Anda benar-benar menikmati hal semacam ini, cobalah mendefinisikan pengurangan terpotong dan perpangkatan, lalu buktikan pula beberapa sifatnya. Ingatlah bahwa pengurangan terpotong berhenti pada nol. Untuk mendefinisikannya, sebaiknya definisikan fungsi pendahulu, pred, yang mengurangi satu dari setiap bilangan bukan nol dan mempertahankan nol. Fungsi pred dapat didefinisikan dengan rekursi sederhana.

inductive MyNat where
  | zero : MyNat
  | succ : MyNat  MyNat

namespace MyNat

def add : MyNat  MyNat  MyNat
  | x, zero => x
  | x, succ y => succ (add x y)

def mul : MyNat  MyNat  MyNat
  | _, zero => zero
  | x, succ y => add (mul x y) x

theorem zero_add (n : MyNat) : add zero n = n := by
  induction' n with n ih
  · rfl
  rw [add, ih]

theorem succ_add (m n : MyNat) : add (succ m) n = succ (add m n) := by
  induction' n with n ih
  · rfl
  rw [add, ih]
  rfl

theorem add_comm (m n : MyNat) : add m n = add n m := by
  induction' n with n ih
  · rw [zero_add]
    rfl
  rw [add, succ_add, ih]

theorem add_assoc (m n k : MyNat) : add (add m n) k = add m (add n k) := by
  sorry
theorem mul_add (m n k : MyNat) : mul m (add n k) = add (mul m n) (mul m k) := by
  sorry
theorem zero_mul (n : MyNat) : mul zero n = zero := by
  sorry
theorem succ_mul (m n : MyNat) : mul (succ m) n = add (mul m n) n := by
  sorry
theorem mul_comm (m n : MyNat) : mul m n = mul n m := by
  sorry
end MyNat

5.3. Bilangan Prima yang Tak Berhingga Banyaknya

Mari lanjutkan penelusuran kita tentang induksi dan rekursi dengan satu lagi hasil matematika klasik: bukti bahwa bilangan prima tak berhingga banyaknya. Salah satu cara merumuskannya ialah dengan menyatakan bahwa untuk setiap bilangan asli \(n\), terdapat bilangan prima yang lebih besar dari \(n\). Untuk membuktikannya, misalkan \(p\) adalah sembarang faktor prima dari \(n! + 1\). Jika \(p\) lebih kecil dari atau sama dengan \(n\), maka ia membagi \(n!\). Karena ia juga membagi \(n! + 1\), ia membagi 1, suatu kontradiksi. Jadi, \(p\) lebih besar dari \(n\).

Untuk memformalkan bukti tersebut, kita perlu menunjukkan bahwa setiap bilangan yang lebih besar dari atau sama dengan 2 memiliki faktor prima. Untuk itu, kita perlu menunjukkan bahwa setiap bilangan asli yang tidak sama dengan 0 maupun 1 lebih besar dari atau sama dengan 2. Hal ini membawa kita pada salah satu keunikan formalisasi: sering kali justru pernyataan trivial semacam inilah yang paling merepotkan untuk diformalkan. Di sini kita membahas beberapa cara untuk melakukannya.

Sebagai permulaan, kita dapat menggunakan taktik cases dan fakta bahwa fungsi penerus mempertahankan pengurutan pada bilangan asli.

theorem two_le {m : } (h0 : m  0) (h1 : m  1) : 2  m := by
  cases m; contradiction
  case succ m =>
    cases m; contradiction
    repeat apply Nat.succ_le_succ
    apply zero_le

Strategi lain ialah menggunakan taktik interval_cases, yang secara otomatis memecah sasaran menjadi beberapa kasus ketika variabel yang dimaksud berada dalam suatu interval bilangan asli atau bilangan bulat. Ingatlah bahwa Anda dapat mengarahkan kursor ke taktik tersebut untuk melihat dokumentasinya.

example {m : } (h0 : m  0) (h1 : m  1) : 2  m := by
  by_contra h
  push_neg at h
  interval_cases m <;> contradiction

Ingat bahwa titik koma setelah interval_cases m berarti taktik berikutnya diterapkan pada setiap kasus yang dihasilkannya. Pilihan lain adalah menggunakan taktik decide, yang mencoba menemukan prosedur keputusan untuk menyelesaikan masalah. Lean mengetahui bahwa nilai kebenaran pernyataan yang diawali kuantor terbatas x, x < n ... atau x, x < n ... dapat diputuskan dengan memutuskan setiap instans yang jumlahnya berhingga.

example {m : } (h0 : m  0) (h1 : m  1) : 2  m := by
  by_contra h
  push_neg at h
  revert h0 h1
  revert h m
  decide

Setelah memiliki teorema two_le, mari mulai dengan menunjukkan bahwa setiap bilangan asli yang lebih besar dari atau sama dengan dua memiliki pembagi prima. Mathlib memiliki fungsi Nat.minFac yang mengembalikan pembagi prima terkecil, tetapi demi mempelajari bagian baru dari pustaka, kita tidak akan menggunakannya dan akan membuktikan teorema tersebut secara langsung.

Di sini, induksi biasa tidak memadai. Kita ingin menggunakan induksi kuat, yang memungkinkan kita membuktikan bahwa setiap bilangan asli \(n\) memiliki sifat \(P\) dengan menunjukkan bahwa untuk setiap bilangan \(n\), jika \(P\) berlaku untuk semua nilai yang lebih kecil dari \(n\), maka ia juga berlaku pada \(n\). Di Lean, prinsip ini disebut Nat.strong_induction_on, dan kita dapat menggunakan kata kunci using untuk meminta taktik induksi menggunakannya. Perhatikan bahwa dengan cara ini tidak ada kasus dasar; kasus tersebut sudah tercakup dalam langkah induksi umum.

Argumennya sederhana. Dengan mengasumsikan \(n ≥ 2\), jika \(n\) prima, bukti selesai. Jika tidak, maka menurut salah satu karakterisasi bilangan prima, ia memiliki faktor nontrivial, \(m\), dan kita dapat menerapkan hipotesis induksi pada faktor tersebut. Telusurilah bukti berikut untuk melihat bagaimana argumen ini berlangsung.

theorem exists_prime_factor {n : Nat} (h : 2  n) :  p : Nat, p.Prime  p  n := by
  by_cases np : n.Prime
  · use n, np
  induction' n using Nat.strong_induction_on with n ih
  rw [Nat.prime_def_lt] at np
  push_neg at np
  rcases np h with m, mltn, mdvdn, mne1
  have : m  0 := by
    intro mz
    rw [mz, zero_dvd_iff] at mdvdn
    linarith
  have mgt2 : 2  m := two_le this mne1
  by_cases mp : m.Prime
  · use m, mp
  · rcases ih m mltn mgt2 mp with p, pp, pdvd
    use p, pp
    apply pdvd.trans mdvdn

Sekarang kita dapat membuktikan rumusan teorema berikut. Cobalah lengkapi sketsanya. Anda dapat menggunakan Nat.factorial_pos, Nat.dvd_factorial, dan Nat.dvd_sub'.

theorem primes_infinite :  n,  p > n, Nat.Prime p := by
  intro n
  have : 2  Nat.factorial n + 1 := by
    sorry
  rcases exists_prime_factor this with p, pp, pdvd
  refine p, ?_, pp
  show p > n
  by_contra ple
  push_neg at ple
  have : p  Nat.factorial n := by
    sorry
  have : p  1 := by
    sorry
  show False
  sorry

Mari kita pertimbangkan variasi bukti di atas. Alih-alih menggunakan fungsi faktorial, misalkan kita diberi himpunan berhingga \(\{ p_1, \ldots, p_n \}\) dan meninjau suatu faktor prima dari \(\prod_{i = 1}^n p_i + 1\). Faktor prima tersebut harus berbeda dari setiap \(p_i\), yang menunjukkan bahwa tidak ada himpunan berhingga yang memuat semua bilangan prima.

Untuk memformalkan argumen ini, kita perlu menalar tentang himpunan berhingga. Di Lean, untuk sembarang tipe α, tipe Finset α merepresentasikan himpunan berhingga beranggotakan elemen bertipe α. Penalaran komputasional tentang himpunan berhingga memerlukan prosedur untuk menguji kesamaan pada α; karena itulah cuplikan di bawah ini menyertakan asumsi [DecidableEq α]. Untuk tipe data konkret seperti , , dan , asumsi tersebut dipenuhi secara otomatis. Ketika menalar tentang bilangan real, asumsi itu dapat dipenuhi dengan menggunakan logika klasik dan melepaskan interpretasi komputasional.

Kita menggunakan perintah open Finset agar dapat memakai nama yang lebih pendek untuk teorema-teorema yang relevan. Tidak seperti pada himpunan, kebanyakan ekuivalensi yang melibatkan himpunan berhingga tidak berlaku secara definisional, sehingga perlu dijabarkan secara manual menggunakan ekuivalensi seperti Finset.subset_iff, Finset.mem_union, Finset.mem_inter, dan Finset.mem_sdiff. Taktik ext tetap dapat digunakan untuk menunjukkan bahwa dua himpunan berhingga sama dengan menunjukkan bahwa setiap elemen salah satunya juga berada dalam himpunan lainnya.

open Finset

section
variable {α : Type*} [DecidableEq α] (r s t : Finset α)

example : r  (s  t)  r  s  r  t := by
  rw [subset_iff]
  intro x
  rw [mem_inter, mem_union, mem_union, mem_inter, mem_inter]
  tauto

example : r  (s  t)  r  s  r  t := by
  simp [subset_iff]
  intro x
  tauto

example : r  s  r  t  r  (s  t) := by
  simp [subset_iff]
  intro x
  tauto

example : r  s  r  t = r  (s  t) := by
  ext x
  simp
  tauto

end

Kita telah menggunakan kiat baru: taktik tauto (serta versi yang diperkuat, tauto!, yang menggunakan logika klasik) dapat digunakan untuk menuntaskan tautologi proposisional. Cobalah gunakan metode-metode ini untuk membuktikan dua contoh di bawah ini.

example : (r  s)  (r  t) = r  s  t := by
  sorry
example : (r \ s) \ t = r \ (s  t) := by
  sorry

Teorema Finset.dvd_prod_of_mem menyatakan bahwa jika n merupakan elemen himpunan berhingga s, maka n membagi i s, i.

example (s : Finset ) (n : ) (h : n  s) : n   i  s, i :=
  Finset.dvd_prod_of_mem _ h

Kita juga perlu mengetahui bahwa kebalikannya berlaku ketika n prima dan s merupakan himpunan bilangan prima. Untuk menunjukkannya, kita memerlukan lema berikut, yang seharusnya dapat Anda buktikan menggunakan teorema Nat.Prime.eq_one_or_self_of_dvd.

theorem _root_.Nat.Prime.eq_of_dvd_of_prime {p q : }
      (prime_p : Nat.Prime p) (prime_q : Nat.Prime q) (h : p  q) :
    p = q := by
  sorry

Kita dapat menggunakan lema ini untuk menunjukkan bahwa jika bilangan prima p membagi hasil kali suatu himpunan berhingga bilangan prima, maka ia sama dengan salah satu anggotanya. Mathlib menyediakan prinsip induksi yang berguna pada himpunan berhingga: untuk menunjukkan bahwa suatu sifat berlaku pada sembarang himpunan berhingga s, tunjukkan bahwa sifat tersebut berlaku pada himpunan kosong, lalu tunjukkan bahwa sifat itu tetap berlaku ketika kita menambahkan satu elemen baru a s. Prinsip ini dikenal sebagai Finset.induction_on. Ketika meminta taktik induksi menggunakannya, kita juga dapat menentukan nama a dan s, nama untuk asumsi a s dalam langkah induktif, serta nama hipotesis induksi. Ekspresi Finset.insert a s menyatakan gabungan s dengan himpunan tunggal yang berisi a. Identitas Finset.prod_empty dan Finset.prod_insert kemudian memberikan kaidah penulisan ulang yang relevan untuk hasil kali. Dalam bukti di bawah ini, simp pertama menerapkan Finset.prod_empty. Telusurilah awal bukti untuk melihat induksi tersebut terurai, lalu selesaikan buktinya.

theorem mem_of_dvd_prod_primes {s : Finset } {p : } (prime_p : p.Prime) :
    ( n  s, Nat.Prime n)  (p   n  s, n)  p  s := by
  intro h₀ h₁
  induction' s using Finset.induction_on with a s ans ih
  · simp at h₁
    linarith [prime_p.two_le]
  simp [Finset.prod_insert ans, prime_p.dvd_mul] at h₀ h₁
  rw [mem_insert]
  sorry

Kita memerlukan satu sifat terakhir dari himpunan berhingga. Diberikan himpunan s : Set α dan predikat P pada α, dalam Bab 4 kita menulis { x s | P x } untuk himpunan elemen s yang memenuhi P. Diberikan s : Finset α, gagasan analognya ditulis s.filter P.

example (s : Finset ) (x : ) : x  s.filter Nat.Prime  x  s  x.Prime :=
  mem_filter

Sekarang kita membuktikan rumusan alternatif dari pernyataan bahwa bilangan prima tak berhingga banyaknya: untuk setiap s : Finset , terdapat bilangan prima p yang bukan elemen s. Untuk memperoleh kontradiksi, kita mengasumsikan bahwa semua bilangan prima berada di s, lalu menyaringnya menjadi himpunan s' yang memuat semua bilangan prima dan tidak memuat unsur lain. Mengambil hasil kali himpunan itu, menambahkan satu, lalu mencari faktor prima dari hasilnya menghasilkan kontradiksi yang kita cari. Cobalah lengkapi sketsa di bawah ini. Anda dapat menggunakan Finset.prod_pos dalam bukti untuk have pertama.

theorem primes_infinite' :  s : Finset Nat,  p, Nat.Prime p  p  s := by
  intro s
  by_contra h
  push_neg at h
  set s' := s.filter Nat.Prime with s'_def
  have mem_s' :  {n : }, n  s'  n.Prime := by
    intro n
    simp [s'_def]
    apply h
  have : 2  ( i  s', i) + 1 := by
    sorry
  rcases exists_prime_factor this with p, pp, pdvd
  have : p   i  s', i := by
    sorry
  have : p  1 := by
    convert Nat.dvd_sub pdvd this
    simp
  show False
  sorry

Dengan demikian, kita telah melihat dua cara untuk menyatakan bahwa bilangan prima tak berhingga banyaknya: menyatakan bahwa bilangan-bilangan tersebut tidak dibatasi oleh n mana pun, dan menyatakan bahwa bilangan-bilangan tersebut tidak termuat dalam himpunan berhingga s mana pun. Dua bukti di bawah ini menunjukkan bahwa kedua rumusan tersebut ekuivalen. Dalam bukti kedua, untuk membentuk s.filter Q, kita harus mengasumsikan adanya prosedur untuk memutuskan apakah Q berlaku. Lean mengetahui bahwa prosedur semacam itu tersedia untuk Nat.Prime. Secara umum, jika kita menggunakan logika klasik dengan menulis open Classical, kita tidak memerlukan asumsi tersebut.

Di Mathlib, Finset.sup s f menyatakan supremum nilai f x ketika x berkisar pada anggota s; hasilnya 0 ketika s kosong dan kodomain f adalah . Dalam bukti pertama, kita menggunakan s.sup id, dengan id sebagai fungsi identitas, untuk merujuk pada nilai maksimum di s.

theorem bounded_of_ex_finset (Q :   Prop) :
    ( s : Finset ,  k, Q k  k  s)   n,  k, Q k  k < n := by
  rintro s, hs
  use s.sup id + 1
  intro k Qk
  apply Nat.lt_succ_of_le
  show id k  s.sup id
  apply le_sup (hs k Qk)

theorem ex_finset_of_bounded (Q :   Prop) [DecidablePred Q] :
    ( n,  k, Q k  k  n)   s : Finset ,  k, Q k  k  s := by
  rintro n, hn
  use (range (n + 1)).filter Q
  intro k
  simpa using hn k

Sedikit variasi pada bukti kedua kita bahwa bilangan prima tak berhingga banyaknya menunjukkan bahwa bilangan prima yang kongruen dengan 3 modulo 4 juga tak berhingga banyaknya. Argumennya sebagai berikut. Pertama, perhatikan bahwa jika hasil kali dua bilangan \(m\) dan \(n\) sama dengan 3 modulo 4, maka salah satu dari kedua bilangan tersebut kongruen dengan 3 modulo 4. Keduanya harus ganjil, dan jika keduanya kongruen dengan 1 modulo 4, hasil kalinya juga demikian. Kita dapat menggunakan pengamatan ini untuk menunjukkan bahwa jika suatu bilangan yang lebih besar dari 2 kongruen dengan 3 modulo 4, maka bilangan tersebut memiliki pembagi prima yang juga kongruen dengan 3 modulo 4.

Sekarang misalkan hanya terdapat berhingga banyak bilangan prima yang kongruen dengan 3 modulo 4, katakanlah \(p_1, \ldots, p_k\). Tanpa mengurangi keumuman, kita dapat mengasumsikan \(p_1 = 3\). Perhatikan bilangan \(4 \prod_{i = 2}^k p_i + 3\). Mudah dilihat bahwa bilangan ini kongruen dengan 3 modulo 4, sehingga ia memiliki faktor prima \(p\) yang kongruen dengan 3 modulo 4. Tidak mungkin \(p = 3\); karena \(p\) membagi \(4 \prod_{i = 2}^k p_i + 3\), jika \(p\) sama dengan 3, maka ia juga membagi \(\prod_{i = 2}^k p_i\), yang menyiratkan bahwa \(p\) sama dengan salah satu \(p_i\) untuk \(i = 2, \ldots, k\); padahal 3 telah kita keluarkan dari daftar ini. Jadi, \(p\) harus merupakan salah satu elemen \(p_i\) yang lain. Namun dalam hal itu, \(p\) membagi \(4 \prod_{i = 2}^k p_i\) dan karenanya membagi 3, bertentangan dengan fakta bahwa bilangan itu bukan 3.

Di Lean, notasi n % m, yang dibaca "n modulo m", menyatakan sisa pembagian n oleh m.

example : 27 % 4 = 3 := by norm_num

Dengan demikian, kita dapat menyatakan "n kongruen dengan 3 modulo 4" sebagai n % 4 = 3. Contoh dan teorema berikut merangkum fakta-fakta tentang fungsi ini yang akan kita gunakan di bawah. Teorema bernama pertama memberikan ilustrasi lain tentang penalaran berdasarkan sejumlah kecil kasus. Dalam teorema bernama kedua, ingat bahwa titik koma berarti blok taktik berikutnya diterapkan pada semua sasaran yang dibuat oleh taktik sebelumnya.

example (n : ) : (4 * n + 3) % 4 = 3 := by
  rw [add_comm, Nat.add_mul_mod_self_left]

theorem mod_4_eq_3_or_mod_4_eq_3 {m n : } (h : m * n % 4 = 3) : m % 4 = 3  n % 4 = 3 := by
  revert h
  rw [Nat.mul_mod]
  have : m % 4 < 4 := Nat.mod_lt m (by norm_num)
  interval_cases m % 4 <;> simp [-Nat.mul_mod_mod]
  have : n % 4 < 4 := Nat.mod_lt n (by norm_num)
  interval_cases n % 4 <;> simp

theorem two_le_of_mod_4_eq_3 {n : } (h : n % 4 = 3) : 2  n := by
  apply two_le <;>
    · intro neq
      rw [neq] at h
      norm_num at h

Kita juga memerlukan fakta berikut, yang menyatakan bahwa jika m merupakan pembagi nontrivial dari n, maka n / m juga demikian. Cobalah lengkapi buktinya menggunakan Nat.div_dvd_of_dvd dan Nat.div_lt_self.

theorem aux {m n : } (h₀ : m  n) (h₁ : 2  m) (h₂ : m < n) : n / m  n  n / m < n := by
  sorry

Sekarang satukan semua bagiannya untuk membuktikan bahwa setiap bilangan yang kongruen dengan 3 modulo 4 memiliki pembagi prima dengan sifat yang sama.

theorem exists_prime_factor_mod_4_eq_3 {n : Nat} (h : n % 4 = 3) :
     p : Nat, p.Prime  p  n  p % 4 = 3 := by
  by_cases np : n.Prime
  · use n
  induction' n using Nat.strong_induction_on with n ih
  rw [Nat.prime_def_lt] at np
  push_neg at np
  rcases np (two_le_of_mod_4_eq_3 h) with m, mltn, mdvdn, mne1
  have mge2 : 2  m := by
    apply two_le _ mne1
    intro mz
    rw [mz, zero_dvd_iff] at mdvdn
    linarith
  have neq : m * (n / m) = n := Nat.mul_div_cancel' mdvdn
  have : m % 4 = 3  n / m % 4 = 3 := by
    apply mod_4_eq_3_or_mod_4_eq_3
    rw [neq, h]
  rcases this with h1 | h1
  . sorry
  . sorry

Kita sudah mendekati akhir. Diberikan himpunan s beranggotakan bilangan prima, kita perlu membahas hasil penghapusan 3 dari himpunan tersebut jika 3 memang ada di dalamnya. Fungsi Finset.erase menangani hal itu.

example (m n : ) (s : Finset ) (h : m  erase s n) : m  n  m  s := by
  rwa [mem_erase] at h

example (m n : ) (s : Finset ) (h : m  erase s n) : m  n  m  s := by
  simp at h
  assumption

Sekarang kita siap membuktikan bahwa bilangan prima yang kongruen dengan 3 modulo 4 tak berhingga banyaknya. Lengkapi bagian-bagian yang masih kosong di bawah ini. Dalam prosesnya, solusi kita menggunakan Nat.dvd_add_iff_left dan Nat.dvd_sub'.

theorem primes_mod_4_eq_3_infinite :  n,  p > n, Nat.Prime p  p % 4 = 3 := by
  by_contra h
  push_neg at h
  rcases h with n, hn
  have :  s : Finset Nat,  p : , p.Prime  p % 4 = 3  p  s := by
    apply ex_finset_of_bounded
    use n
    contrapose! hn
    rcases hn with p, pp, p4⟩, pltn
    exact p, pltn, pp, p4
  rcases this with s, hs
  have h₁ : ((4 *  i  erase s 3, i) + 3) % 4 = 3 := by
    sorry
  rcases exists_prime_factor_mod_4_eq_3 h₁ with p, pp, pdvd, p4eq
  have ps : p  s := by
    sorry
  have pne3 : p  3 := by
    sorry
  have : p  4 *  i  erase s 3, i := by
    sorry
  have : p  3 := by
    sorry
  have : p = 3 := by
    sorry
  contradiction

Jika Anda berhasil menyelesaikan bukti tersebut, selamat! Ini merupakan pencapaian formalisasi yang sungguh berarti.

5.4. Induksi Lebih Lanjut

Dalam Bab 5.2, kita telah melihat cara mendefinisikan fungsi faktorial melalui rekursi pada bilangan asli.

def fac :   
  | 0 => 1
  | n + 1 => (n + 1) * fac n

Kita juga telah melihat cara membuktikan teorema menggunakan taktik induction'.

theorem fac_pos (n : ) : 0 < fac n := by
  induction' n with n ih
  · rw [fac]
    exact zero_lt_one
  rw [fac]
  exact mul_pos n.succ_pos ih

Taktik induction (tanpa tanda prima) memungkinkan sintaks yang lebih terstruktur.

example (n : ) : 0 < fac n := by
  induction n
  case zero =>
    rw [fac]
    exact zero_lt_one
  case succ n ih =>
    rw [fac]
    exact mul_pos n.succ_pos ih

example (n : ) : 0 < fac n := by
  induction n with
  | zero =>
    rw [fac]
    exact zero_lt_one
  | succ n ih =>
    rw [fac]
    exact mul_pos n.succ_pos ih

Seperti biasa, Anda dapat mengarahkan kursor ke kata kunci induction untuk membaca dokumentasinya. Nama kasus, zero dan succ, diambil dari definisi tipe . Perhatikan bahwa kasus succ memungkinkan Anda memilih nama apa pun untuk variabel induksi dan hipotesis induksi, yang di sini bernama n dan ih. Anda bahkan dapat membuktikan teorema dengan notasi yang sama seperti saat mendefinisikan fungsi rekursif.

theorem fac_pos' :  n, 0 < fac n
  | 0 => by
    rw [fac]
    exact zero_lt_one
  | n + 1 => by
    rw [fac]
    exact mul_pos n.succ_pos (fac_pos' n)

Perhatikan pula tidak adanya := dan n setelah titik dua, tidak adanya kata kunci by dalam setiap kasus, serta adanya pemanggilan induktif fac_pos' n. Seolah-olah teorema tersebut merupakan fungsi rekursif dari n, dan pada langkah induktif kita melakukan pemanggilan rekursif.

Gaya pendefinisian ini sangat fleksibel. Para perancang Lean telah menyediakan sarana yang canggih untuk mendefinisikan fungsi rekursif, dan sarana ini juga mencakup pembuktian melalui induksi. Sebagai contoh, kita dapat mendefinisikan fungsi Fibonacci dengan beberapa kasus dasar.

@[simp] def fib :   
  | 0 => 0
  | 1 => 1
  | n + 2 => fib n + fib (n + 1)

Anotasi @[simp] berarti bahwa penyederhana akan menggunakan persamaan definisinya. Anda juga dapat menerapkannya dengan menulis rw [fib]. Di bawah ini, akan berguna jika kasus n + 2 diberi nama.

theorem fib_add_two (n : ) : fib (n + 2) = fib n + fib (n + 1) := rfl

example (n : ) : fib (n + 2) = fib n + fib (n + 1) := by rw [fib]

Dengan notasi Lean untuk fungsi rekursif, Anda dapat menjalankan pembuktian melalui induksi pada bilangan asli yang mencerminkan definisi rekursif fib. Contoh berikut memberikan rumus eksplisit untuk bilangan Fibonacci ke-n dalam bentuk rasio emas, φ, dan konjugatnya, φ'. Kita harus memberi tahu Lean bahwa definisi kita tidak diharapkan menghasilkan kode karena operasi aritmetika pada bilangan real tidak dapat dikomputasi.

Kita akan menggunakan taktik grind untuk melakukan komputasi dengan memintanya memakai definisi phi dan phi', serta asumsi induktif fib_eq n dan fib_eq (n+1).

noncomputable section

def phi  :  := (1 + 5) / 2
def phi' :  := (1 - 5) / 2

theorem fib_eq :  n, fib n = (phi^n - phi'^n) / 5
  | 0   => by simp
  | 1   => by unfold fib; grind [phi, phi']
  | n+2 => by unfold fib; simp [fib_eq n, fib_eq (n+1), phi, phi']; grind

end

Pembuktian induksi yang melibatkan fungsi Fibonacci tidak harus berbentuk demikian. Di bawah ini kita mereproduksi bukti Mathlib bahwa bilangan Fibonacci yang berurutan saling prima.

theorem fib_coprime_fib_succ (n : ) : Nat.Coprime (fib n) (fib (n + 1)) := by
  induction n with
  | zero => simp
  | succ n ih =>
    simp only [fib, Nat.coprime_add_self_right]
    exact ih.symm

Dengan interpretasi komputasional Lean, kita dapat mengevaluasi bilangan Fibonacci.

#eval fib 6
#eval List.range 20 |>.map fib

Implementasi langsung fib tidak efisien secara komputasional. Bahkan, waktu jalannya eksponensial terhadap argumennya. (Cobalah pikirkan alasannya.) Di Lean, kita dapat mengimplementasikan versi rekursif ekor berikut, yang waktu jalannya linear terhadap n, lalu membuktikan bahwa versi tersebut menghitung fungsi yang sama.

def fib' (n : Nat) : Nat :=
  aux n 0 1
where aux
  | 0,   x, _ => x
  | n+1, x, y => aux n y (x + y)

theorem fib'.aux_eq (m n : ) : fib'.aux n (fib m) (fib (m + 1)) = fib (n + m) := by
  induction n generalizing m with
  | zero => simp [fib'.aux]
  | succ n ih => rw [fib'.aux, fib_add_two, ih, add_assoc, add_comm 1]

theorem fib'_eq_fib : fib' = fib := by
  ext n
  erw [fib', fib'.aux_eq 0 n]; rfl

#eval fib' 10000

Perhatikan kata kunci generalizing dalam bukti fib'.aux_eq. Kata kunci ini menyisipkan m di depan hipotesis induksi, sehingga dalam langkah induktif, m dapat mengambil nilai yang berbeda. Anda dapat menelusuri bukti tersebut dan memeriksa bahwa dalam kasus ini, kuantornya perlu diinstansiasi dengan m + 1 pada langkah induktif.

Perhatikan pula penggunaan erw (singkatan dari extended rewrite atau penulisan ulang diperluas) alih-alih rw. Hal ini diperlukan karena untuk menulis ulang sasaran fib'.aux_eq, fib 0 dan fib 1 harus direduksi masing-masing menjadi 0 dan 1. Taktik erw lebih agresif daripada rw dalam menguraikan definisi agar sesuai dengan parameter. Cara ini tidak selalu baik; dalam beberapa kasus ia dapat membuang banyak waktu, jadi gunakan erw secukupnya.

Berikut contoh lain penggunaan kata kunci generalizing dalam pembuktian identitas lain yang terdapat di Mathlib. Bukti informal identitas tersebut dapat ditemukan di sini. Kami memberikan dua varian bukti formalnya.

theorem fib_add (m n : ) : fib (m + n + 1) = fib m * fib n + fib (m + 1) * fib (n + 1) := by
  induction n generalizing m with
  | zero => simp
  | succ n ih =>
    specialize ih (m + 1)
    rw [add_assoc m 1 n, add_comm 1 n] at ih
    simp only [fib_add_two, ih]
    ring

theorem fib_add' :  m n, fib (m + n + 1) = fib m * fib n + fib (m + 1) * fib (n + 1)
  | _, 0     => by simp
  | m, n + 1 => by
    have := fib_add' (m + 1) n
    rw [add_assoc m 1 n, add_comm 1 n] at this
    simp only [fib_add_two, this]
    ring

Sebagai latihan, gunakan fib_add untuk membuktikan pernyataan berikut.

example (n : ): (fib n) ^ 2 + (fib (n + 1)) ^ 2 = fib (2 * n + 1) := by
  sorry

Mekanisme Lean untuk mendefinisikan fungsi rekursif cukup fleksibel untuk mengizinkan sembarang pemanggilan rekursif, asalkan kompleksitas argumennya menurun menurut suatu ukuran yang menjamin terminasi. Dalam contoh berikut, kita menunjukkan bahwa setiap bilangan asli n 1 memiliki pembagi prima, dengan menggunakan fakta bahwa jika n bukan nol dan tidak prima, ia memiliki pembagi yang lebih kecil. (Anda dapat memeriksa bahwa Mathlib memiliki teorema bernama sama dalam namespace Nat, meskipun buktinya berbeda dari bukti yang kita berikan di sini.)

#check (@Nat.not_prime_iff_exists_dvd_lt :
   {n : }, 2  n  (¬Nat.Prime n   m, m  n  2  m  m < n))

theorem ne_one_iff_exists_prime_dvd :  {n}, n  1   p : , p.Prime  p  n
  | 0 => by simpa using Exists.intro 2 Nat.prime_two
  | 1 => by simp [Nat.not_prime_one]
  | n + 2 => by
    have hn : n + 2  1 := by omega
    simp only [Ne, not_false_iff, true_iff, hn]
    by_cases h : Nat.Prime (n + 2)
    · use n + 2, h
    · have : 2  n + 2 := by omega
      rw [Nat.not_prime_iff_exists_dvd_lt this] at h
      rcases h with m, mdvdn, mge2, -
      have : m  1 := by omega
      rw [ne_one_iff_exists_prime_dvd] at this
      rcases this with p, primep, pdvdm
      use p, primep
      exact pdvdm.trans mdvdn

Baris rw [ne_one_iff_exists_prime_dvd] at this menyerupai sulap: kita menggunakan teorema yang sedang dibuktikan di dalam buktinya sendiri. Hal ini dapat bekerja karena pemanggilan induktif diinstansiasi pada m, kasus saat ini adalah n + 2, dan konteks memuat m < n + 2. Lean dapat menemukan hipotesis tersebut dan menggunakannya untuk menunjukkan bahwa pemanggilan rekursif ini berakhir. Lean cukup pandai menentukan apa yang menurun; dalam kasus ini, pilihan n dalam pernyataan teorema dan relasi kurang-dari sudah jelas. Dalam kasus yang lebih rumit, Lean menyediakan mekanisme untuk memberikan informasi ini secara eksplisit. Lihat bagian tentang rekursi dengan jaminan terminasi dalam Manual Referensi Lean.

Kadang-kadang, dalam sebuah bukti, Anda perlu memisahkan kasus berdasarkan apakah bilangan asli n merupakan nol atau penerus, tanpa memerlukan hipotesis induksi dalam kasus penerus. Untuk itu, Anda dapat menggunakan taktik cases dan rcases.

theorem zero_lt_of_mul_eq_one (m n : ) : n * m = 1  0 < n  0 < m := by
  cases n <;> cases m <;> simp

example (m n : ) : n*m = 1  0 < n  0 < m := by
  rcases m with (_ | m); simp
  rcases n with (_ | n) <;> simp

Ini merupakan kiat yang berguna. Sering kali Anda memiliki teorema tentang bilangan asli n yang kasus nolnya mudah. Jika Anda memisahkan kasus pada n dan segera menyelesaikan kasus nol, sasaran asli akan tersisa dengan n diganti oleh n + 1.