├── lean-toolchain ├── MA4N1 ├── Init.lean ├── Pisa │ ├── Pisa.md │ ├── generalizations.lean │ ├── nilpotent_matrices.lean │ ├── matrici_nilpotenti.lean │ ├── nilpotent_exercises_no_sols.lean │ └── nilpotent_exercises.lean ├── help_me.lean ├── L13_deriv_pathologies_no_sols.lean ├── L16_setoids_week_end_no_sols.lean ├── L05_limits_no_sols.lean ├── L16_setoids_week_end.lean ├── L13_deriv_pathologies.lean ├── L00_intro.lean ├── L05_limits.lean ├── L07_calculations.lean ├── L05_graphs_no_sols.lean ├── Matrices.lean ├── L08_Ri_hard_no_sols.lean ├── L12_pathologies.lean ├── L01_polynomials_no_sols.lean ├── L02_generalizations.lean ├── L09_noncomputable_IsSquare.lean ├── L08_Ri_easy_no_sols.lean ├── L17_navigating_Mathlib.lean ├── L08_Ri_hard.lean ├── L05_graphs.lean ├── L11_autoImplicits.lean ├── L15_setoids.lean ├── L01_polynomials.lean ├── L14_in_implicit_explicit.lean ├── L08_Ri_easy.lean ├── L03_groups_no_sols.lean ├── L03_groups.lean ├── L06_typeclasses.lean ├── L10_dvd_induction_no_sols.lean └── L04_definitions.lean ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitpod.yml ├── .vscode └── settings.json ├── .gitignore ├── lakefile.lean ├── non_finishing_aesop.md ├── README.md ├── LICENSE ├── scripts.sh ├── MA4N1.lean ├── .oldDocker └── gitpod │ └── Dockerfile ├── toc.md ├── todo.md └── lake-manifest.json /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.24.0-rc1 2 | -------------------------------------------------------------------------------- /MA4N1/Init.lean: -------------------------------------------------------------------------------- 1 | import Mathlib.Tactic.Linter.UnusedTacticExtension 2 | 3 | #allow_unused_tactic! Lean.Parser.Tactic.done 4 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/devcontainers/base:jammy 2 | 3 | USER vscode 4 | WORKDIR /home/vscode 5 | 6 | RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none 7 | 8 | ENV PATH="/home/vscode/.elan/bin:${PATH}" 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mathlib4 dev container", 3 | 4 | "build": { 5 | "dockerfile": "Dockerfile" 6 | }, 7 | 8 | "onCreateCommand": "lake exe cache get!", 9 | 10 | "customizations": { 11 | "vscode" : { 12 | "extensions" : [ "leanprover.lean4" ] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | # This is run when starting a Gitpod workspace on this repository 2 | 3 | image: leanprovercommunity/gitpod4 4 | 5 | vscode: 6 | extensions: 7 | - leanprover.lean4 # install the Lean 4 VS Code extension 8 | 9 | tasks: 10 | - init: | 11 | elan self update 12 | lake exe cache get 13 | lake build 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "spellright.language": [ 3 | "en_US" 4 | ], 5 | "spellright.documentTypes": [ 6 | "markdown", 7 | "latex", 8 | "plaintext", 9 | "lean4" 10 | ], 11 | "spellright.parserByClass": { 12 | "lean4": { 13 | "parser": "code", 14 | "comment_start": "/-", 15 | "comment_end": "-/", 16 | "comment_line": "--" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS leaves these files everywhere: 2 | .DS_Store 3 | # Created by `lake exe cache get` if no home directory is available 4 | /.cache 5 | # Prior to v4.3.0-rc2 lake stored files in these locations. 6 | # We'll leave them in the `.gitignore` for a while for users switching between toolchains. 7 | /build/ 8 | /lake-packages/ 9 | /lakefile.olean 10 | # After v4.3.0-rc2 lake stores its files here: 11 | /.lake/ 12 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | package «mA4N1» where 5 | leanOptions := #[ 6 | ⟨`weak.lang.lemmaCmd, true⟩ 7 | ] 8 | -- add any package configuration options here 9 | moreServerOptions := #[ 10 | ⟨`pp.unicode.fun, true⟩, -- pretty-prints `fun a ↦ b` 11 | ⟨`autoImplicit, false⟩, 12 | ⟨`weak.lang.lemmaCmd, true⟩ 13 | ] 14 | 15 | require mathlib from git 16 | "https://github.com/leanprover-community/mathlib4.git" 17 | 18 | @[default_target] 19 | lean_lib «MA4N1» { 20 | -- add any library configuration options here 21 | roots := #[`MA4N1] 22 | } 23 | -------------------------------------------------------------------------------- /MA4N1/Pisa/Pisa.md: -------------------------------------------------------------------------------- 1 | # Formalizzazioni a Pisa 2 | 3 | | Data | Titolo | 4 | | - | - | 5 | | Lunedì 11 dicembre 2023, 16-18, | [Matrici nilpotenti], [Esercizi] | 6 | | Martedì 12 dicembre 2023, 11-13, | [Computability] | 7 | | Mercoledì 13 dicembre 2023, 16.30-17.30, | [Mathematics, automation, theorem proving], Colloquio | 8 | 9 | [Matrici nilpotenti]: matrici_nilpotenti.lean 10 | [Esercizi]: nilpotent_exercises_no_sols.lean 11 | [Computability]: ../L09_noncomputable_IsSquare.lean 12 | [Mathematics, automation, theorem proving]: https://adomani.github.io/Syllabus/2023_Pisa_Maths_Autom_Thm_Proving.pdf 13 | -------------------------------------------------------------------------------- /non_finishing_aesop.md: -------------------------------------------------------------------------------- 1 | # Workaround for finding some information about non-finishing `aesop` 2 | 3 | [Source](https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/non-finishing.20.60aesop.3F.60/near/404962767) 4 | 5 | ```lean 6 | import Mathlib.Tactic 7 | import Aesop 8 | 9 | -- add the following line to your files 10 | @[aesop 1% unsafe apply] def sorryeh {A} : A := sorry 11 | 12 | example {n : ℕ} (h : (n = 0 ∧ False) ∨ n = 1) : False := by 13 | aesop? 14 | sorry 15 | ``` 16 | 17 | This makes `aesop` try, as a last resource, `sorryeh`. 18 | Since `sorryeh` produces a term of *anything*, it will close all goals. 19 | 20 | The `1% unsafe` is a way of communicating to `aesop` that it should really only use this when all else fails. 21 | 22 | The likely conclusion is that `aesop` will close all goals, either because it is really successful, 23 | or because, after doing its thing, it will apply `sorryeh`. 24 | 25 | Thus, `aesop?` will always print something! 26 | 27 | And, if the last step was `apply sorryeh`, we simply erase it and we should be where we wanted to be! 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [MA4N1 Theorem Proving with Lean](https://adomani.github.io/Syllabus/MA4N1/toc) 2 | 3 | This is the repository for the module [MA4N1 Theorem Proving with Lean](https://adomani.github.io/Syllabus/MA4N1/toc) at the University of Warwick. 4 | 5 | Here, you will find the Lean files used during lectures, some exercises and various complements. 6 | 7 | This is a Lean project with `Mathlib` as a dependency. 8 | You may want to set-up something like this for your group project. 9 | You can find instructions on how to do this at [this webpage](https://adomani.github.io/Syllabus/MA4N1/instructions_for_new_project). 10 | For systems other than Unix, take a look at the [Mathlib projects webpage](https://leanprover-community.github.io/install/project.html#creating-a-lean-project). 11 | 12 | --- 13 | 14 | [Back to the `Theorem Proving with Lean` webpage](https://adomani.github.io/Syllabus/MA4N1/toc) 15 | 16 | [Back to Moodle](https://moodle.warwick.ac.uk/course/view.php?id=71736#section-0) 17 | 18 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/adomani/MA4N1_Theorem_proving_with_Lean) 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Damiano Testa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MA4N1/help_me.lean: -------------------------------------------------------------------------------- 1 | import Batteries.Tactic.Lemma 2 | 3 | open Lean Elab Command Meta Tactic 4 | 5 | set_option hygiene false in 6 | elab tk:"help_me!" : command => liftTermElabM do 7 | if (← getEnv).contains `TPwL.re_add then 8 | logInfo "What should `lemma im_add` be?\nYou may need 10 lemmas analogous to `re_add`!" 9 | else 10 | let stx ← `(command| @[simp] lemma re_add {a b : Ri} : (a + b).re = a.re + b.re := sorry ) 11 | TryThis.addSuggestion tk stx 12 | 13 | elab tk:"hint_inverse" : tactic => do 14 | TryThis.addSuggestion tk (← `(tactic| rintro ⟨d⟩ )) 15 | 16 | elab "help_all" : command => liftTermElabM do 17 | TryThis.addSuggestion (← getRef) "@[simp] lemma re_zero : (0 : Ri).re = 0 := rfl 18 | @[simp] lemma im_zero : (0 : Ri).im = 0 := rfl 19 | @[simp] lemma re_one : (1 : Ri).re = 1 := rfl 20 | @[simp] lemma im_one : (1 : Ri).im = 0 := rfl 21 | 22 | @[simp] lemma re_add {a b : Ri} : (a + b).re = a.re + b.re := rfl 23 | @[simp] lemma im_add {a b : Ri} : (a + b).im = a.im + b.im := rfl 24 | @[simp] lemma re_mul {a b : Ri} : (a * b).re = a.re * b.re - a.im * b.im := rfl 25 | @[simp] lemma im_mul {a b : Ri} : (a * b).im = a.re * b.im + a.im * b.re := rfl 26 | 27 | @[simp] lemma re_inv {a : Ri} : (a⁻¹).re = a.re / (a.re ^ 2 + a.im ^ 2) := rfl 28 | @[simp] lemma im_inv {a : Ri} : (a⁻¹).im = - a.im / (a.re ^ 2 + a.im ^ 2) := rfl" 29 | -------------------------------------------------------------------------------- /scripts.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | exercify () { 4 | sed ' 5 | # /:= by$/, /^ done$/ {/:= by$/!{/^ done$/!{/^ /d;};};} 6 | # /^ [ ·]*have.* := by$/{d;} 7 | # s=^\( *\)done$=\1sorry\n&= 8 | /:= by$/, /^ *done$/ {/:= by$/!{/^ *done$/!{/^ /d;};};} 9 | /^ *have.* := by$/{d;} 10 | s=^\( *\)done=\1sorry\n&= 11 | ' "${1}" 12 | } 13 | 14 | exme () { 15 | newPth="${1/.lean/_no_sols.lean}" 16 | echo ${newPth} 17 | exercify "${1}" > "${newPth}" 18 | } 19 | 20 | mkNew () { 21 | local tl="$(ls MA4N1/L* | sed -n 's=MA4N1/L0*\([0-9][0-9]*\)_.*=\1=p' | tail -1)" 22 | tl="$(printf '%02d' "$(( tl + 1 ))")" 23 | local file="$( printf 'MA4N1/L%s_%s.lean' "${tl}" "${1// /_}" )" 24 | if [ -f "${file}" ]; then 25 | lcyan $'File' ; printf ' %s ' "${file}" ; lcyan $'already exists!\n' 26 | else 27 | { brown $'save to file '; printf '%s\n' "${file}" ; } >&2 28 | printf $'import MA4N1.Init\nimport Mathlib.Tactic\n\nnamespace TPwL\n\n\n\nend TPwL\n' > "${file}" 29 | fi 30 | } 31 | 32 | genToc () { 33 | printf '# Table of Contents\n\n' 34 | local fil 35 | for fil in MA4N1/*.lean 36 | do 37 | printf '[%s](%s) (%s)\n\n' "$( sed -n '/^# /{s=^# *==p;q}' "${fil}" )" "${fil}" "${fil}" 38 | done | 39 | grep -v "easy\|week_end\|no_sols\|\[\]" 40 | } 41 | 42 | alias toc='( croot ; genToc | sed -z "s=\n\n*=\n\n=g" > toc.md )' 43 | -------------------------------------------------------------------------------- /MA4N1.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.BreatherWeek 2 | import MA4N1.Init 3 | import MA4N1.L00_intro 4 | import MA4N1.L01_polynomials 5 | import MA4N1.L01_polynomials_no_sols 6 | import MA4N1.L02_generalizations 7 | import MA4N1.L03_groups 8 | import MA4N1.L03_groups_no_sols 9 | import MA4N1.L04_definitions 10 | import MA4N1.L05_graphs 11 | import MA4N1.L05_graphs_no_sols 12 | import MA4N1.L05_limits 13 | import MA4N1.L05_limits_no_sols 14 | import MA4N1.L06_typeclasses 15 | import MA4N1.L07_calculations 16 | import MA4N1.L08_Ri_easy 17 | import MA4N1.L08_Ri_easy_no_sols 18 | import MA4N1.L08_Ri_hard 19 | import MA4N1.L08_Ri_hard_no_sols 20 | import MA4N1.L09_noncomputable_IsSquare 21 | import MA4N1.L10_dvd_induction 22 | import MA4N1.L10_dvd_induction_no_sols 23 | import MA4N1.L11_autoImplicits 24 | import MA4N1.L12_pathologies 25 | import MA4N1.L13_deriv_pathologies 26 | import MA4N1.L13_deriv_pathologies_no_sols 27 | import MA4N1.L14_in_implicit_explicit 28 | import MA4N1.L15_setoids 29 | import MA4N1.L16_setoids_week_end 30 | import MA4N1.L16_setoids_week_end_no_sols 31 | import MA4N1.L17_navigating_Mathlib 32 | import MA4N1.L18_finiteness 33 | import MA4N1.Matrices 34 | import MA4N1.Pisa.generalizations 35 | import MA4N1.Pisa.matrici_nilpotenti 36 | import MA4N1.Pisa.nilpotent_exercises 37 | import MA4N1.Pisa.nilpotent_exercises_no_sols 38 | import MA4N1.Pisa.nilpotent_matrices 39 | import MA4N1.help_me 40 | -------------------------------------------------------------------------------- /.oldDocker/gitpod/Dockerfile: -------------------------------------------------------------------------------- 1 | # This is the Dockerfile for `leanprovercommunity/mathlib:gitpod`. 2 | 3 | # gitpod doesn't support multiple FROM statements, (or rather, you can't copy from one to another) 4 | # so we just install everything in one go 5 | FROM ubuntu:jammy 6 | 7 | USER root 8 | 9 | RUN apt-get update && apt-get install sudo git curl git bash-completion python3 -y && apt-get clean 10 | 11 | RUN useradd -l -u 33333 -G sudo -md /home/gitpod -s /bin/bash -p gitpod gitpod \ 12 | # passwordless sudo for users in the 'sudo' group 13 | && sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers 14 | USER gitpod 15 | WORKDIR /home/gitpod 16 | 17 | SHELL ["/bin/bash", "-c"] 18 | 19 | # gitpod bash prompt 20 | RUN { echo && echo "PS1='\[\033[01;32m\]\u\[\033[00m\] \[\033[01;34m\]\w\[\033[00m\]\$(__git_ps1 \" (%s)\") $ '" ; } >> .bashrc 21 | 22 | # install elan 23 | RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none 24 | 25 | # install whichever toolchain mathlib is currently using 26 | RUN . ~/.profile && elan toolchain install $(curl https://raw.githubusercontent.com/leanprover-community/mathlib4/master/lean-toolchain) 27 | 28 | ENV PATH="/home/gitpod/.local/bin:/home/gitpod/.elan/bin:${PATH}" 29 | 30 | # fix the infoview when the container is used on gitpod: 31 | ENV VSCODE_API_VERSION="1.50.0" 32 | 33 | # ssh to github once to bypass the unknown fingerprint warning 34 | RUN ssh -o StrictHostKeyChecking=no github.com || true 35 | 36 | # run sudo once to suppress usage info 37 | RUN sudo echo finished 38 | -------------------------------------------------------------------------------- /toc.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | [Preliminaries](MA4N1/L01_polynomials.lean) (MA4N1/L01_polynomials.lean) 4 | 5 | [Generalizations, automation, `exact?`, `simp`, tactics](MA4N1/L02_generalizations.lean) (MA4N1/L02_generalizations.lean) 6 | 7 | [Groups](MA4N1/L03_groups.lean) (MA4N1/L03_groups.lean) 8 | 9 | [Developing an "API" around new definitions](MA4N1/L04_definitions.lean) (MA4N1/L04_definitions.lean) 10 | 11 | [Graphs in `Mathlib`](MA4N1/L05_graphs.lean) (MA4N1/L05_graphs.lean) 12 | 13 | [Continuity in `Mathlib`](MA4N1/L05_limits.lean) (MA4N1/L05_limits.lean) 14 | 15 | [Definitions, Structures and Typeclasses](MA4N1/L06_typeclasses.lean) (MA4N1/L06_typeclasses.lean) 16 | 17 | [Performing calculations in Lean](MA4N1/L07_calculations.lean) (MA4N1/L07_calculations.lean) 18 | 19 | [Defining the complex numbers](MA4N1/L08_Ri_hard.lean) (MA4N1/L08_Ri_hard.lean) 20 | 21 | [A short introduction to `noncomputable` and `Decidable`](MA4N1/L09_noncomputable_IsSquare.lean) (MA4N1/L09_noncomputable_IsSquare.lean) 22 | 23 | [Divisibility as an excuse to see more tactics](MA4N1/L10_dvd_induction.lean) (MA4N1/L10_dvd_induction.lean) 24 | 25 | [Setting `autoImplicit` true or false](MA4N1/L11_autoImplicits.lean) (MA4N1/L11_autoImplicits.lean) 26 | 27 | [Pathologies](MA4N1/L12_pathologies.lean) (MA4N1/L12_pathologies.lean) 28 | 29 | [Pathologies of `deriv`](MA4N1/L13_deriv_pathologies.lean) (MA4N1/L13_deriv_pathologies.lean) 30 | 31 | [The `in` modifier](MA4N1/L14_in_implicit_explicit.lean) (MA4N1/L14_in_implicit_explicit.lean) 32 | 33 | [`Setoid`s and equivalence relations](MA4N1/L15_setoids.lean) (MA4N1/L15_setoids.lean) 34 | 35 | [Finding lemmas in `Mathlib`](MA4N1/L17_navigating_Mathlib.lean) (MA4N1/L17_navigating_Mathlib.lean) 36 | 37 | [Finiteness in Mathlib](MA4N1/L18_finiteness.lean) (MA4N1/L18_finiteness.lean) 38 | 39 | [Matrices in Lean](MA4N1/Matrices.lean) (MA4N1/Matrices.lean) 40 | 41 | -------------------------------------------------------------------------------- /MA4N1/L13_deriv_pathologies_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Analysis.Calculus.LocalExtr.Basic 3 | 4 | namespace TPwL_deriv_pathologies_no_sols 5 | 6 | /-! 7 | # Pathologies of `deriv` 8 | 9 | This file shows some of the quirks and oddities appearing in `Lean/Mathlib`. 10 | 11 | To get some practice, we use `deriv` and `derivWithin: 12 | -/ 13 | 14 | #check deriv 15 | #check derivWithin 16 | 17 | /-- The real function that is `0` before `0` and `1` starting from `0`. -/ 18 | noncomputable 19 | def step (r : ℝ) : ℝ := if r < 0 then 0 else 1 20 | 21 | /-! 22 | As mentioned in the lecture, we are going to show that the derivative of the 23 | function `step` is identically `0`. 24 | This exploits the "pathological" definition of setting to `0` the `deriv`ative 25 | at `p` of a function that is not differentiable at `p`. 26 | 27 | Due to how the function `step` is defined, you may find it convenient to use 28 | that it is constant on `{x : ℝ | x < 0}` and also on `{x : ℝ | x < 0}`. 29 | 30 | You may want to use that the derivative of a *globally* constant function 31 | coincides with the derivative of `step` on an appropriate open set. 32 | Lemmas that relate values of possibly different functions at certain arguments 33 | are typically referred to as "congruence" lemmas. 34 | In this case, you may look at `derivWithin_congr`. 35 | -/ 36 | 37 | -- useful lemmas: `derivWithin_of_isOpen, deriv_const, derivWithin_congr` 38 | theorem derivWithin_step_of_neg {r : ℝ} (h0 : r < 0) : 39 | derivWithin step {x | x ≠ 0} r = 0 := by 40 | sorry 41 | done 42 | 43 | -- copy-paste the previous proof and fix? 44 | theorem derivWithin_step_of_pos {r : ℝ} (h0 : 0 < r) : 45 | derivWithin step {x | x ≠ 0} r = 0 := by 46 | sorry 47 | done 48 | 49 | -- tactic `by_cases` and then use the previous results. 50 | theorem derivWithin_step_of_ne_zero {r : ℝ} (h0 : r ≠ 0) : 51 | derivWithin step {x | x ≠ 0} r = 0 := by 52 | sorry 53 | done 54 | 55 | -- Use that `{x : ℝ | x ≠ 0}` is open. 56 | theorem deriv_step_of_ne_zero {r : ℝ} (h0 : r ≠ 0) : 57 | deriv step r = 0 := by 58 | sorry 59 | done 60 | 61 | -- funny consequence of the pathologies: `IsLocalMax.deriv_eq_zero` 62 | -- also useful `IsMaxOn.isLocalMax` 63 | theorem deriv_step_zero : deriv step 0 = 0 := by 64 | sorry 65 | done 66 | 67 | -- `eq_or_ne` may be useful here 68 | example : deriv step = 0 := by 69 | sorry 70 | done 71 | 72 | end TPwL_deriv_pathologies_no_sols 73 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | * Fix MA4N1_2023 file, so that it builds 2 | * Check branch `typeclasses` and de-duplicate 3 | * L04_definitions: L157-174 4 | * L06_typeclass: L201-218 5 | 6 | 7 | Add troubleshooting for `exact?` producing invalid code? 8 | 9 | /-- We found a contradiction using an *implicit* natural number `n`. -/ 10 | theorem implicitContra {n : Nat} : False := sorry 11 | 12 | example (n : Nat) : False := by 13 | -- exact? says exact implicitContra 14 | -- but it does not work! 15 | -- This does: notice the explicit passing of the implicit argument `n`. 16 | exact implicitContra (n := n) 17 | 18 | /-- We found a contradiction using an *explicit* integer `n`. -/ 19 | theorem explicitContra (n : Int) : False := sorry 20 | 21 | example (n : Int) : False := by 22 | exact? 23 | 24 | /-! 25 | ## A short explanation of why `exact?` produced code that is not working. 26 | 27 | Internally, uses `apply` with lemmas that look like they might be useful. 28 | These lemmas may leave side-goals that `exact?` tries to solve with `solve_by_elim`. 29 | 30 | In the example above, `apply implicitContra`, leaves a goal of `⊢ Nat` that 31 | `solve_by_elim` can solve, since *it* uses the local context and 32 | is aware of the `n : Nat` assumption. 33 | 34 | However, when `exact?` reconstructs the "one-line `exact ???`", 35 | it only tries to fill in the *explicit* inputs of the lemma that works, 36 | so it does not provide the (in this case obvious) implicit argument. 37 | 38 | In the second case, `apply` finds `explicitContra` 39 | (notice that now we are dealing with integers, since otherwise `exact?` would pick 40 | up again `implicitContra`, as it appears earlier!). 41 | Once the side-goal `n : Int` is solved, `exact?` is faced with providing an 42 | `exact explicitContra` output. 43 | This now requires passing an argument to `explicitContra` and `exact?` provides one. 44 | 45 | *Conclusion*. 46 | Sometimes, when `exact?` returns code that is not working, it is bug. 47 | 48 | More often, though, when `exact?` returns code that is not working, 49 | it is an indication that something else went wrong earlier! 50 | 51 | In the example above, the issue was created by the fact that the value `n : Nat` in 52 | `implicitContra` could not be inferred from the later expressions: 53 | neither the remaining assumptions of the lemma (there were none!), nor the conclusion 54 | (`False`) mention `n`. 55 | In this case, you should seriously think whether you really want to leave `n` implicit 56 | or make it explicit. 57 | -/ 58 | -------------------------------------------------------------------------------- /MA4N1/Pisa/generalizations.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace Pisa 5 | 6 | open Function Polynomial NNReal 7 | 8 | namespace Nat 9 | 10 | /- 11 | # Generalizations, automation, `exact?`, `simp`, tactics 12 | 13 | As it happens, someone comes along and says: 14 | 15 | "I just learned a cool fact! A polynomial with coefficients in `ℕ` is monotone!" 16 | 17 | Let's formalize this result! 18 | 19 | Let's also think about what it really means... 20 | 21 | Surely they intended to say that viewing a polynomial with coefficients in `ℕ` 22 | as a function `ℕ → ℕ`, we obtain a monotone function. 23 | -/ 24 | 25 | variable (f : ℕ[X]) -- `f` is a polynomial with coefficients in `ℕ` 26 | (P : ℕ[X] → Prop) -- `P` is a property of polynomials: `P f` may be 27 | -- true or false 28 | 29 | example : Monotone (fun n ↦ f.eval n) := by 30 | refine Polynomial.induction_on' f ?_ ?_ 31 | · intros f g hf hg 32 | simp only [eval_add] 33 | exact? says exact Monotone.add hf hg 34 | · intros n a x y xy 35 | simp only [eval_monomial] 36 | gcongr 37 | done 38 | 39 | end Nat 40 | 41 | /- 42 | Now that we proved it for `ℕ`, let's generalize to `ℝ≥0`. 43 | 44 | Copy-paste the above, change `ℕ` to `ℝ≥0` and fix the issues. 45 | -/ 46 | namespace nnreal 47 | 48 | variable (f : ℝ≥0[X]) -- `f` is a polynomial with coefficients in `ℝ≥0` 49 | (P : ℝ≥0[X] → Prop) -- `P` is a property of polynomials: `P f` may be 50 | -- true or false 51 | 52 | end nnreal 53 | 54 | /-! 55 | What happens if we copy-paste the above and change `ℕ` to `ℝ`? 56 | -/ 57 | namespace real 58 | 59 | variable (f : ℝ[X]) -- `f` is a polynomial with coefficients in `ℝ` 60 | (P : ℝ[X] → Prop) -- `P` is a property of polynomials: `P f` may be 61 | -- true or false 62 | 63 | end real 64 | 65 | /-! 66 | How can we further generalize this? 67 | -/ 68 | namespace general 69 | 70 | variable {R} [Semiring R] 71 | variable (f : R[X]) -- `f` is a polynomial with coefficients in `R` 72 | (P : R[X] → Prop) -- `P` is a property of polynomials: `P f` may be 73 | -- true or false 74 | 75 | end general 76 | 77 | /- 78 | Finally, let's confirm that the more general result proves to the special cases that we know. 79 | -/ 80 | 81 | example (f : ℕ[X]) : Monotone (fun n ↦ f.eval n) := by 82 | sorry 83 | done 84 | 85 | example (f : ℝ≥0[X]) : Monotone (fun n ↦ f.eval n) := by 86 | sorry 87 | done 88 | 89 | -- --> Semiring --> Comm --> Ordered --> Canonically 90 | 91 | end Pisa 92 | -------------------------------------------------------------------------------- /MA4N1/L16_setoids_week_end_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Batteries.Tactic.Lemma 3 | import Mathlib.Tactic 4 | import MA4N1.help_me 5 | 6 | namespace TPwL_setoids_week_end_no_sols 7 | 8 | /-! 9 | # `Setoid`s and equivalence relations 10 | -/ 11 | 12 | -- A left-over lemma from the lecture on Tuesday 13 | lemma two_dvd_sub_one_iff (d : ℤ) : 2 ∣ d - 1 ↔ ¬ 2 ∣ d := by 14 | sorry 15 | done 16 | 17 | /-- `Week` is the finite Type with exactly 7 constructors, one for each day of the week. -/ 18 | inductive Week 19 | | Mon 20 | | Tue 21 | | Wed 22 | | Thu 23 | | Fri 24 | | Sat 25 | | Sun 26 | 27 | /-- `week_end? d` is the predicate on a day of the `Week`, 28 | answering the question "Is `d` part of the week-end?" -/ 29 | def week_end? : Week → Bool 30 | -- the "anonymous dot-notation": 31 | -- Lean is expecting a term of type `Week`, so `.Sat` gets parsed as `Week.Sat` 32 | | .Sat => true 33 | | .Sun => true 34 | -- every other day of the `Week` returns `false` 35 | | _ => false 36 | 37 | instance Week_setoid : Setoid Week where 38 | r x y := week_end? x = week_end? y 39 | iseqv := by 40 | sorry 41 | done 42 | 43 | -- If you like an equivalent but more obfuscated version of the instance above, here it is! 44 | /- 45 | instance : Setoid Week where 46 | r := (week_end? · = week_end? ·) 47 | iseqv := { refl := fun _ => rfl 48 | symm := fun {_ _} => .symm 49 | trans := fun {_ _ _} => .trans } 50 | -/ 51 | 52 | namespace Week 53 | 54 | @[simp] 55 | lemma sat_sun : (⟦Sat⟧ : Quotient Week_setoid) = ⟦Sun⟧ := by 56 | sorry 57 | done 58 | 59 | example : (⟦Sat⟧ : Quotient Week_setoid) ≠ ⟦Mon⟧ := by 60 | sorry 61 | done 62 | 63 | example : (⟦Sat⟧ : Quotient Week_setoid) ≠ ⟦Mon⟧ := by 64 | sorry 65 | done 66 | 67 | lemma equiv_class_of_Sunday (d : Week) : (⟦Sun⟧ : Quotient Week_setoid) = ⟦d⟧ ↔ 68 | d ∈ ({Sat, Sun} : Set _) := by 69 | sorry 70 | done 71 | 72 | lemma equiv_class_of_Monday (d : Week) : (⟦Mon⟧ : Quotient Week_setoid) = ⟦d⟧ ↔ 73 | d ∈ ({Mon, Tue, Wed, Thu, Fri} : Set _) := by 74 | sorry 75 | done 76 | 77 | example : Quotient Week_setoid ≃ Bool where 78 | -- the function from the quotient to `Bool` is the `Quotient.lift` of the function `week_end?`. 79 | -- in maths, it is more common to say that `week_end?` "descends" to the quotient. 80 | toFun := Quotient.lift week_end? (fun a b => id) 81 | -- the function from `Bool` to the quotient assigns `true` to `⟦.Sun⟧` and `false` to `⟦.Mon⟧` 82 | invFun b := if b then ⟦Sun⟧ else ⟦Mon⟧ 83 | -- it is now up to you to prove that these two functions are inverses of each other! 84 | -- the tactic `hint_inverse` will give you a hint on how to start the proof! 85 | left_inv := by 86 | sorry 87 | done 88 | right_inv := by 89 | sorry 90 | done 91 | 92 | end Week 93 | 94 | end TPwL_setoids_week_end_no_sols 95 | -------------------------------------------------------------------------------- /MA4N1/L05_limits_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Order.Filter.Basic 3 | import Mathlib.Data.Real.Archimedean 4 | 5 | namespace TPwL_limits_no_sols 6 | 7 | /-! 8 | # Continuity in `Mathlib` 9 | 10 | As I mentioned in the lectures, continuity and limits are defined in a way 11 | that differs quite a bit to what you might be used. 12 | This is done via `filter`s. 13 | 14 | Nevertheless, the "standard" definition of continuity is available in `Mathlib` 15 | and we will use the standard one, not the one using filters. 16 | 17 | First, in case you are curious, `Filter.Tendsto` is `Mathlib`s way of talking about limits. 18 | -/ 19 | #check Filter.Tendsto 20 | /-! 21 | Moving on, let's roll out our "standard" definition. 22 | -/ 23 | 24 | /-- The limit of a sequence of real numbers. -/ 25 | def limit (f : ℕ → ℝ) (a : ℝ) : Prop := ∀ ε > 0, ∃ N, ∀ n, N ≤ n → |f n - a| < ε 26 | 27 | /-! 28 | Note that there is an existential (`∃`) in the definition of a limit. 29 | To provide a witness of the existential, you can use the tactic `use`. 30 | 31 | Here is an example of the `use` tactic. 32 | Use ``use [your choice of a natural number bigger than `n`]``! 33 | -/ 34 | example {n : ℕ} : ∃ m, n < m := by 35 | sorry 36 | done 37 | 38 | example {a : ℝ} : limit (fun n => a) a := by 39 | sorry 40 | done 41 | 42 | -- Hint: on an existential `H : ∃ x, P x`, the tactic `cases H` gives you access to an `x` and 43 | -- the fact that it satisfies the proposition `P`. 44 | -- Remember to wait for the lightbulb to fill in the syntax for `cases`! 45 | -- Also, keep in mind that you have a "hidden" `∃` in the local context. 46 | example {a b : ℝ} (f : ℕ → ℝ) (h : limit f a) : limit (fun n => f n + b) (a + b) := by 47 | sorry 48 | done 49 | 50 | -- Hint: check what `half_pos` is! 51 | example {a b : ℝ} (f g : ℕ → ℝ) (hf : limit f a) (hg : limit g b) : 52 | limit (fun n => f n + g n) (a + b) := by 53 | sorry 54 | done 55 | 56 | /- 57 | What follows can be *very* challenging: do not despair if you get stuck! 58 | 59 | In the proposed solution, I used 60 | * `Int.natAbs`, a function that takes an integer and returns its absolute value as a natural number; 61 | * `Int.floor`, a function that takes a real number and returns its floor as an integer number; 62 | * `le_trans`, a proposition that takes the proof of two inequalities of the form `a ≤ b` and `b ≤ c` 63 | as input and returns a proof of the inequality `a ≤ c`; 64 | 65 | and various other tricks, including 66 | * `LT.lt.le` converting a strict inequality `a < b` into a weak one `a ≤ b`; 67 | * explicitly using double type-ascriptions to layer the steps in going from `ℕ`, to `ℤ`, to `ℝ`; 68 | * ... 69 | 70 | Part of the challenge is for you to find a much simpler proof of `no_limit_id` than the one that I propose! 71 | -/ 72 | 73 | lemma aux1 (n : ℤ) : n ≤ Int.natAbs n := by 74 | sorry 75 | done 76 | 77 | lemma aux (a : ℝ) : -1 ≤ (Int.natAbs ⌊a⌋) - a := by 78 | sorry 79 | done 80 | 81 | lemma no_limit_id {a : ℝ} : ¬ limit (fun n => n) a := by 82 | sorry 83 | done 84 | 85 | end TPwL_limits_no_sols 86 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": "1.1.0", 2 | "packagesDir": ".lake/packages", 3 | "packages": 4 | [{"url": "https://github.com/leanprover-community/mathlib4.git", 5 | "type": "git", 6 | "subDir": null, 7 | "scope": "", 8 | "rev": "586e9c386274d740624bd33abe9a67f7365a92fc", 9 | "name": "mathlib", 10 | "manifestFile": "lake-manifest.json", 11 | "inputRev": null, 12 | "inherited": false, 13 | "configFile": "lakefile.lean"}, 14 | {"url": "https://github.com/leanprover-community/plausible", 15 | "type": "git", 16 | "subDir": null, 17 | "scope": "leanprover-community", 18 | "rev": "9f492660e9837df43fd885a2ad05c520da9ff9f5", 19 | "name": "plausible", 20 | "manifestFile": "lake-manifest.json", 21 | "inputRev": "main", 22 | "inherited": true, 23 | "configFile": "lakefile.toml"}, 24 | {"url": "https://github.com/leanprover-community/LeanSearchClient", 25 | "type": "git", 26 | "subDir": null, 27 | "scope": "leanprover-community", 28 | "rev": "99657ad92e23804e279f77ea6dbdeebaa1317b98", 29 | "name": "LeanSearchClient", 30 | "manifestFile": "lake-manifest.json", 31 | "inputRev": "main", 32 | "inherited": true, 33 | "configFile": "lakefile.toml"}, 34 | {"url": "https://github.com/leanprover-community/import-graph", 35 | "type": "git", 36 | "subDir": null, 37 | "scope": "leanprover-community", 38 | "rev": "90f3b0f429411beeeb29bbc248d799c18a2d520d", 39 | "name": "importGraph", 40 | "manifestFile": "lake-manifest.json", 41 | "inputRev": "main", 42 | "inherited": true, 43 | "configFile": "lakefile.toml"}, 44 | {"url": "https://github.com/leanprover-community/ProofWidgets4", 45 | "type": "git", 46 | "subDir": null, 47 | "scope": "leanprover-community", 48 | "rev": "556caed0eadb7901e068131d1be208dd907d07a2", 49 | "name": "proofwidgets", 50 | "manifestFile": "lake-manifest.json", 51 | "inputRev": "v0.0.74", 52 | "inherited": true, 53 | "configFile": "lakefile.lean"}, 54 | {"url": "https://github.com/leanprover-community/aesop", 55 | "type": "git", 56 | "subDir": null, 57 | "scope": "leanprover-community", 58 | "rev": "9e8de5716b162ec8983a89711a186d13ff871c22", 59 | "name": "aesop", 60 | "manifestFile": "lake-manifest.json", 61 | "inputRev": "master", 62 | "inherited": true, 63 | "configFile": "lakefile.toml"}, 64 | {"url": "https://github.com/leanprover-community/quote4", 65 | "type": "git", 66 | "subDir": null, 67 | "scope": "leanprover-community", 68 | "rev": "2e582a44b0150db152bff1c8484eb557fb5340da", 69 | "name": "Qq", 70 | "manifestFile": "lake-manifest.json", 71 | "inputRev": "master", 72 | "inherited": true, 73 | "configFile": "lakefile.toml"}, 74 | {"url": "https://github.com/leanprover-community/batteries", 75 | "type": "git", 76 | "subDir": null, 77 | "scope": "leanprover-community", 78 | "rev": "6f7d05f4955eb5af9799d828b697251bb2c9c0b8", 79 | "name": "batteries", 80 | "manifestFile": "lake-manifest.json", 81 | "inputRev": "main", 82 | "inherited": true, 83 | "configFile": "lakefile.toml"}, 84 | {"url": "https://github.com/leanprover/lean4-cli", 85 | "type": "git", 86 | "subDir": null, 87 | "scope": "leanprover", 88 | "rev": "b62fd39acc32da6fb8bb160c82d1bbc3cb3c186e", 89 | "name": "Cli", 90 | "manifestFile": "lake-manifest.json", 91 | "inputRev": "main", 92 | "inherited": true, 93 | "configFile": "lakefile.toml"}], 94 | "name": "mA4N1", 95 | "lakeDir": ".lake"} 96 | -------------------------------------------------------------------------------- /MA4N1/Pisa/nilpotent_matrices.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic.Recall 3 | import Mathlib.LinearAlgebra.Matrix.Charpoly.Coeff 4 | 5 | section Origin_of_the_question 6 | 7 | /-! 8 | [Source](https://leanprover.zulipchat.com/#narrow/stream/217875-Is-there-code-for-X.3F/topic/Nilpotent.20implies.20trace.20zero/near/381540803) 9 | -/ 10 | 11 | variable (R : Type _) [CommRing R] {n : Type _} [DecidableEq n] [Fintype n] 12 | 13 | /-! This is a question asked on the [Lean Zulip chat](https://leanprover.zulipchat.com/). -/ 14 | 15 | -- I don't suppose anyone has a proof of this lying around: 16 | -- Fairly sure `IsReduced` suffices (at least in commutative case) but 17 | -- I'll settle for a proof over a field. 18 | example [IsReduced R] {A : Matrix n n R} (h : IsNilpotent A) : 19 | A.trace = 0 := sorry 20 | 21 | /-! 22 | The question is very precise, but it leaves a few lingering follow-up questions. 23 | 24 | * Is the statement true? 25 | * Can the hypothesis `IsReduced R` be removed? 26 | * Can `CommRing R` be weakened to `Ring R`? Or even `Semiring R`? 27 | 28 | Possible first reactions. 29 | 30 | * Over a field, the result is true: the trace is the sum of the eigenvalues and 31 | all the eigenvalues of a nilpotent matrix are `0`. 32 | * Over an integral domain -- also, since an integral domain embeds in its field of fractions. 33 | * Nilpotent elements are clearly an issue: if `ε ∈ R` is non-zero and nilpotent, 34 | then the `1 × 1` matrix `(ε)` has trace that is nonzero! 35 | 36 | What if we weaken the statement to `IsNilpotent A.trace`? 37 | Since the question assumes `IsReduced R`, the trace being nilpotent is the same as the trace being `0`. 38 | But, now the counterexample with a ring containing nilpotents no longer contradicts this statement! 39 | -/ 40 | 41 | -- Could maybe this be true? Notice that `IsReduced` no longer appears and 42 | -- the conclusion is that the trace is *nilpotent*, as opposed to `0`. 43 | -- The ring is still a `CommRing`. 44 | example {A : Matrix n n R} (h : IsNilpotent A) : 45 | IsNilpotent A.trace := sorry 46 | 47 | /-! 48 | # Enter the main tool 49 | 50 | About a month before this question had been asked, this result had arrived into `Mathlib`: 51 | -/ 52 | 53 | #check Polynomial.isUnit_iff_coeff_isUnit_isNilpotent 54 | 55 | /-! 56 | How about this? 57 | 58 | Assume that `A ^ N = 0`. 59 | 60 | Start with the identities 61 | 62 | `I = I - (tA) ^ (N + 1)` 63 | ` = (I - tA)(I + tA + ... + (tA) ^ N)`. 64 | 65 | Compute determinants on both sides and use that the determinant of a product is the product of the determinants. 66 | 67 | Deduce that the determinant of `(I - tA)` is an invertible polynomial. 68 | Therefore all its coefficients of positive degree are nilpotents. 69 | 70 | Is this right? If only I had a proof assistant at hand... 71 | 72 | The rest of this file develops the tools that should allow you to formalize the above proof 73 | in the following hour! 74 | -/ 75 | 76 | end Origin_of_the_question 77 | 78 | section CommRing 79 | 80 | variable {R : Type*} [CommRing R] {n : Type*} [DecidableEq n] [Fintype n] 81 | 82 | open Polynomial 83 | 84 | recall Matrix.charpolyRev (M : Matrix n n R) := det (1 - (X : R[X]) • M.map C) 85 | 86 | namespace Matrix 87 | 88 | variable (M : Matrix n n R) 89 | 90 | example (hM : IsNilpotent M) : IsUnit (charpolyRev M) := by 91 | sorry 92 | done 93 | 94 | end Matrix 95 | 96 | end CommRing 97 | 98 | /-! 99 | # Extra credit 100 | 101 | Can you weaken `CommRing R` to `Ring R`? 102 | -/ 103 | 104 | variable {R : Type*} [Ring R] {n : Type*} [DecidableEq n] [Fintype n] (M : Matrix n n R) 105 | open Matrix 106 | 107 | example (hM : IsNilpotent M) : IsNilpotent M.trace := sorry 108 | -------------------------------------------------------------------------------- /MA4N1/Pisa/matrici_nilpotenti.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic.Recall 3 | import Mathlib.LinearAlgebra.Matrix.Charpoly.Coeff 4 | 5 | section Origine_della_domanda 6 | 7 | /-! 8 | [Fonte](https://leanprover.zulipchat.com/#narrow/stream/217875-Is-there-code-for-X.3F/topic/Nilpotent.20implies.20trace.20zero/near/381540803) 9 | -/ 10 | 11 | variable (R : Type _) [CommRing R] {n : Type _} [DecidableEq n] [Fintype n] 12 | 13 | /-! Questa è una domanda che è apparsa sulla [chat di Zulip su Lean](https://leanprover.zulipchat.com/). -/ 14 | 15 | -- I don't suppose anyone has a proof of this lying around: 16 | -- Fairly sure `IsReduced` suffices (at least in commutative case) but 17 | -- I'll settle for a proof over a field. 18 | example [IsReduced R] {M : Matrix n n R} (h : IsNilpotent M) : 19 | M.trace = 0 := sorry 20 | 21 | /-! 22 | La domanda è molto precisa, ma lascia qualche domanda irrisolta. 23 | 24 | * È vero il risultato? 25 | * Si può eliminare l'ipotesi `IsReduced R`? 26 | * Si può indebolire `CommRing R` a `Ring R`? O perfino a `Semiring R`? 27 | 28 | Possibili reazioni iniziali. 29 | 30 | * Su un camp, il risultato è vero: la traccia è la somma degli autovalori e 31 | tutti gli autovalori di una matrice nilpotente sono `0`. 32 | * Su un dominio di integrità, stesso risultato, poiché un dominio si immerge nel suo campo delle frazioni. 33 | * La presenza di elementi nilpotenti è chiaramente un problema: se `ε ∈ R` è non nullo e nilpotente, 34 | allora la matrice `1 × 1` `(ε)` ha traccia che non è nulla! 35 | 36 | E se cambiamo la conclusione a `IsNilpotent M.trace`? 37 | Poiché la domanda assume `IsReduced R`, se la traccia è nilpotente allora è automaticamente nulla. 38 | E inoltre, il controesempio con un anello con nilpotenti non è in contraddizione immediata con l'enunciato! 39 | 40 | Può essere che il risultato qui sotto sia vero? 41 | Osserviamo che `IsReduced` non è più un'ipotesi e 42 | la conclusione è che la traccia è *nilpotente*, invece di essere nulla. 43 | L'anello continua a essere un `CommRing` (ovvero, è *commutativo*). 44 | -/ 45 | example {M : Matrix n n R} (h : IsNilpotent M) : 46 | IsNilpotent M.trace := sorry 47 | 48 | /-! 49 | # Entra il risultato principale 50 | 51 | Circa un mese prima che la domanda qui sopra fosse proposta, questo risultato era arrivato in `Mathlib`: 52 | -/ 53 | 54 | #check Polynomial.isUnit_iff_coeff_isUnit_isNilpotent 55 | 56 | /-! 57 | Proviamo così. 58 | 59 | Assumiamo che `M` sia nilpotente: `M ^ N = 0`. 60 | 61 | Iniziamo con le identità 62 | 63 | `I = I - (tM) ^ (N + 1)` 64 | ` = (I - tM)(I + tM + ... + (tM) ^ N)`. 65 | 66 | Calcoliamo i determinanti dei due lati e usiamo che il determinante di un prodotto è il prodotto 67 | dei determinanti. 68 | 69 | Deduciamo che il determinante di `(I - tM)` è un polinomio invertibile. 70 | Pertanto, tutti i suoi coefficienti di grado positivo sono nilpotenti. 71 | -/ 72 | 73 | end Origine_della_domanda 74 | 75 | section CommRing 76 | 77 | variable {R : Type*} [CommRing R] {n : Type*} [DecidableEq n] [Fintype n] 78 | 79 | open Polynomial 80 | 81 | recall Matrix.charpolyRev (M : Matrix n n R) := det (1 - (X : R[X]) • M.map C) 82 | 83 | namespace Matrix 84 | 85 | variable (M : Matrix n n R) 86 | 87 | -- Il risultato che dimostreremo nel file di esercizi. 88 | example {N : ℕ} (hM : M ^ N = 0) {n : ℕ} (hn : n ≠ 0) : IsNilpotent ((charpolyRev M).coeff n) := by 89 | sorry 90 | done 91 | 92 | end Matrix 93 | 94 | end CommRing 95 | 96 | /-! 97 | # Extra credit 98 | 99 | Possiamo indebolire `CommRing R` a `Ring R`? 100 | -/ 101 | 102 | variable {R : Type*} [Ring R] {n : Type*} [DecidableEq n] [Fintype n] (M : Matrix n n R) 103 | open Matrix 104 | 105 | example (hM : IsNilpotent M) : IsNilpotent M.trace := sorry 106 | -------------------------------------------------------------------------------- /MA4N1/L16_setoids_week_end.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Batteries.Tactic.Lemma 3 | import Mathlib.Tactic 4 | import MA4N1.help_me 5 | 6 | namespace TPwL_setoids_week_end 7 | 8 | /-! 9 | # `Setoid`s and equivalence relations 10 | -/ 11 | 12 | -- A left-over lemma from the lecture on Tuesday 13 | lemma two_dvd_sub_one_iff (d : ℤ) : 2 ∣ d - 1 ↔ ¬ 2 ∣ d := by 14 | constructor <;> intro h 15 | · omega 16 | · omega 17 | done 18 | 19 | /-- `Week` is the finite Type with exactly 7 constructors, one for each day of the week. -/ 20 | inductive Week 21 | | Mon 22 | | Tue 23 | | Wed 24 | | Thu 25 | | Fri 26 | | Sat 27 | | Sun 28 | 29 | /-- `week_end? d` is the predicate on a day of the `Week`, 30 | answering the question "Is `d` part of the week-end?" -/ 31 | def week_end? : Week → Bool 32 | -- the "anonymous dot-notation": 33 | -- Lean is expecting a term of type `Week`, so `.Sat` gets parsed as `Week.Sat` 34 | | .Sat => true 35 | | .Sun => true 36 | -- every other day of the `Week` returns `false` 37 | | _ => false 38 | 39 | instance Week_setoid : Setoid Week where 40 | r x y := week_end? x = week_end? y 41 | iseqv := by 42 | constructor 43 | · exact? says exact fun x => rfl 44 | · exact? says exact fun {x y} a => id a.symm 45 | · intros x y z xy yz 46 | exact xy.trans yz 47 | done 48 | 49 | -- If you like an equivalent but more obfuscated version of the instance above, here it is! 50 | /- 51 | instance : Setoid Week where 52 | r := (week_end? · = week_end? ·) 53 | iseqv := { refl := fun _ => rfl 54 | symm := fun {_ _} => .symm 55 | trans := fun {_ _ _} => .trans } 56 | -/ 57 | 58 | namespace Week 59 | 60 | @[simp] 61 | lemma sat_sun : (⟦Sat⟧ : Quotient Week_setoid) = ⟦Sun⟧ := by 62 | exact Quotient.eq.mpr rfl 63 | done 64 | 65 | example : (⟦Sat⟧ : Quotient Week_setoid) ≠ ⟦Mon⟧ := by 66 | intro h 67 | simp only [Quotient.eq] at h 68 | cases h 69 | done 70 | 71 | example : (⟦Sat⟧ : Quotient Week_setoid) ≠ ⟦Mon⟧ := by 72 | simp only [ne_eq, Quotient.eq] 73 | rintro ⟨⟩ 74 | done 75 | 76 | lemma equiv_class_of_Sunday (d : Week) : (⟦Sun⟧ : Quotient Week_setoid) = ⟦d⟧ ↔ 77 | d ∈ ({Sat, Sun} : Set _) := by 78 | -- `rcases d with _ | _ | _ | _ | _ | _ | _ <;>` also works instead of `induction` 79 | induction d <;> 80 | simp <;> 81 | rintro ⟨⟩ 82 | done 83 | 84 | lemma equiv_class_of_Monday (d : Week) : (⟦Mon⟧ : Quotient Week_setoid) = ⟦d⟧ ↔ 85 | d ∈ ({Mon, Tue, Wed, Thu, Fri} : Set _) := by 86 | -- `rcases d with _ | _ | _ | _ | _ | _ | _ <;>` also works instead of `induction` 87 | induction d <;> 88 | simp <;> 89 | (try rfl) <;> 90 | rintro ⟨⟩ 91 | done 92 | 93 | example : Quotient Week_setoid ≃ Bool where 94 | -- the function from the quotient to `Bool` is the `Quotient.lift` of the function `week_end?`. 95 | -- in maths, it is more common to say that `week_end?` "descends" to the quotient. 96 | toFun := Quotient.lift week_end? (fun a b => id) 97 | -- the function from `Bool` to the quotient assigns `true` to `⟦.Sun⟧` and `false` to `⟦.Mon⟧` 98 | invFun b := if b then ⟦Sun⟧ else ⟦Mon⟧ 99 | -- it is now up to you to prove that these two functions are inverses of each other! 100 | -- the tactic `hint_inverse` will give you a hint on how to start the proof! 101 | left_inv := by 102 | rintro ⟨d⟩ 103 | dsimp only 104 | split_ifs with h 105 | · apply d.equiv_class_of_Sunday.mpr 106 | induction d <;> cases h <;> simp 107 | · simp only [Bool.not_eq_true] at h 108 | apply d.equiv_class_of_Monday.mpr 109 | induction d <;> cases h <;> simp 110 | done 111 | right_inv := by 112 | rintro (x|x) -- split into `true`/`false` 113 | · simp [week_end?] 114 | · simp [week_end?] 115 | done 116 | 117 | end Week 118 | 119 | end TPwL_setoids_week_end 120 | -------------------------------------------------------------------------------- /MA4N1/L13_deriv_pathologies.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Analysis.Calculus.LocalExtr.Basic 3 | 4 | namespace TPwL_deriv_pathologies 5 | 6 | /-! 7 | # Pathologies of `deriv` 8 | 9 | This file shows some of the quirks and oddities appearing in `Lean/Mathlib`. 10 | 11 | To get some practice, we use `deriv` and `derivWithin: 12 | -/ 13 | 14 | #check deriv 15 | #check derivWithin 16 | 17 | /-- The real function that is `0` before `0` and `1` starting from `0`. -/ 18 | noncomputable 19 | def step (r : ℝ) : ℝ := if r < 0 then 0 else 1 20 | 21 | /-! 22 | As mentioned in the lecture, we are going to show that the derivative of the 23 | function `step` is identically `0`. 24 | This exploits the "pathological" definition of setting to `0` the `deriv`ative 25 | at `p` of a function that is not differentiable at `p`. 26 | 27 | Due to how the function `step` is defined, you may find it convenient to use 28 | that it is constant on `{x : ℝ | x < 0}` and also on `{x : ℝ | x < 0}`. 29 | 30 | You may want to use that the derivative of a *globally* constant function 31 | coincides with the derivative of `step` on an appropriate open set. 32 | Lemmas that relate values of possibly different functions at certain arguments 33 | are typically referred to as "congruence" lemmas. 34 | In this case, you may look at `derivWithin_congr`. 35 | -/ 36 | 37 | -- useful lemmas: `derivWithin_of_isOpen, deriv_const, derivWithin_congr` 38 | theorem derivWithin_step_of_neg {r : ℝ} (h0 : r < 0) : 39 | derivWithin step {x | x ≠ 0} r = 0 := by 40 | have : derivWithin (fun _ : ℝ => (0 : ℝ)) {x | x < 0} r = 0 := by 41 | rw [derivWithin_of_isOpen] 42 | · exact deriv_const r 0 43 | · exact isOpen_Iio 44 | · exact h0 45 | conv_rhs => rw [← this] 46 | rw [derivWithin_of_isOpen, ← derivWithin_of_isOpen (s := {x : ℝ | x < 0})] 47 | · unfold step 48 | rw [derivWithin_congr] 49 | · intros x hx 50 | aesop 51 | · aesop 52 | · exact isOpen_gt' 0 53 | · exact h0 54 | · exact isOpen_ne 55 | · exact h0.ne 56 | done 57 | 58 | -- copy-paste the previous proof and fix? 59 | theorem derivWithin_step_of_pos {r : ℝ} (h0 : 0 < r) : 60 | derivWithin step {x | x ≠ 0} r = 0 := by 61 | have : derivWithin (fun _ : ℝ => (1 : ℝ)) {x | 0 < x} r = 0 := by 62 | rw [derivWithin_of_isOpen] 63 | · exact deriv_const r 1 64 | · exact isOpen_Ioi 65 | · exact h0 66 | conv_rhs => rw [← this] 67 | rw [derivWithin_of_isOpen, ← derivWithin_of_isOpen (s := {x : ℝ | 0 < x})] 68 | · unfold step 69 | rw [derivWithin_congr] 70 | · intros x hx 71 | simp_all 72 | linarith -- overkill, `exact hx.le` would have sufficed 73 | · simp only [ite_eq_right_iff, zero_ne_one] 74 | intro c 75 | linarith 76 | · exact isOpen_lt' 0 77 | · exact h0 78 | · exact isOpen_ne 79 | · exact h0.ne' 80 | done 81 | 82 | -- tactic `by_cases` and then use the previous results. 83 | theorem derivWithin_step_of_ne_zero {r : ℝ} (h0 : r ≠ 0) : 84 | derivWithin step {x | x ≠ 0} r = 0 := by 85 | by_cases r0 : r < 0 86 | · exact derivWithin_step_of_neg r0 87 | · apply derivWithin_step_of_pos 88 | -- the following was the first result of `apply?` 89 | refine lt_of_le_of_ne' ?_ h0 90 | exact? says exact not_lt.mp r0 91 | done 92 | 93 | -- Use that `{x : ℝ | x ≠ 0}` is open. 94 | theorem deriv_step_of_ne_zero {r : ℝ} (h0 : r ≠ 0) : 95 | deriv step r = 0 := by 96 | rw [← derivWithin_of_isOpen (s := {x | x ≠ 0})] 97 | · exact? says exact derivWithin_step_of_ne_zero h0 98 | · exact? says exact isOpen_ne 99 | · exact? says exact h0 100 | done 101 | 102 | -- funny consequence of the pathologies: `IsLocalMax.deriv_eq_zero` 103 | -- also useful `IsMaxOn.isLocalMax` 104 | theorem deriv_step_zero : deriv step 0 = 0 := by 105 | apply IsLocalMax.deriv_eq_zero 106 | apply IsMaxOn.isLocalMax (s := Set.univ) 107 | · intros x _ 108 | unfold step 109 | aesop 110 | · simp 111 | done 112 | 113 | -- `eq_or_ne` may be useful here 114 | example : deriv step = 0 := by 115 | ext r 116 | simp only [Pi.zero_apply] 117 | rcases eq_or_ne r 0 with rfl | h0 118 | · exact? says exact deriv_step_zero 119 | · exact? says exact deriv_step_of_ne_zero h0 120 | done 121 | 122 | end TPwL_deriv_pathologies 123 | -------------------------------------------------------------------------------- /MA4N1/L00_intro.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_intro 5 | 6 | #check Bool -- Bool : Type 7 | 8 | #check Prop -- Prop : Type 9 | #check Sort -- Prop : Type 10 | 11 | #check Type -- Type : Type 1 12 | #check Sort 1 -- Type : Type 1 13 | 14 | #check Type 1 -- Type 1 : Type 2 15 | #check Sort 2 -- Type 1 : Type 2 16 | /- and so on. -/ 17 | 18 | #check and -- and (x y : Bool) : Bool 19 | #check or -- or (x y : Bool) : Bool 20 | #check And -- And (a b : Prop) : Prop 21 | #check Or -- Or (a b : Prop) : Prop 22 | 23 | #check (· ∧ ·) -- fun x1 x2 => x1 ∧ x2 : Prop → Prop → Prop 24 | 25 | /- Easy template for a `lemma` 26 | 27 | lemma name_of_lemma assumptions : statement := by 28 | tactics ... 29 | done 30 | 31 | * `lemma` instructs Lean that we are stating a lemma, 32 | * `name_of_lemma` assigns a name to the lemma that we are about to state, 33 | * `assumptions` is a (finite) sequence, possibly empty, of statements like 34 | `(n : ℕ) {z : ℤ} (h : n + n = n + 1)`, 35 | * `statement` is what we actually want to prove, e.g. `n = 1` or `∃ k : ℕ, n = 2 * k` 36 | or whatever we want to prove, 37 | * `tactics` are the steps that we communicate to Lean to guide it through the proof. 38 | 39 | If we do not want to re-use our result, then we may not want to even give it a name. 40 | In that case, we can use `example`. 41 | The syntax is similar to the one of `lemma`, 42 | except that we begin with `example` and we cannot give it a name: 43 | 44 | example assumptions : statement := by 45 | tactics ... 46 | done 47 | 48 | _A comment about `done`_ 49 | Technically, writing `done` at the end of a proof is not needed. 50 | However, since Lean does a fair amount of second-guessing about what you might be trying to say, 51 | informing it explicitly where the proof should finish helps it to produce more meaningful error messages. 52 | In turn, producing better error messages means that you will find it easier to find your mistakes! 53 | -/ 54 | 55 | example (p q : Prop) (hpq : p ∧ q) : q ∧ p := by 56 | sorry 57 | ---- this lemma exists and `exact?` finds it for us 58 | --constructor -- splits the `and` in the goal into two separate goals 59 | --· cases hpq -- place the cursor at the end of `hpq`, wait for 💡 to appear and click on it! 60 | -- assumption 61 | --· sorry -- left to you! 62 | done 63 | 64 | example (p q : Prop) (hpq : p → q) (hp : p) : q := by 65 | sorry 66 | --apply hpq 67 | --assumption 68 | done 69 | 70 | example (p q r : Prop) (hpq : p → q) (hqr : q → r) (hp : p) : r := by 71 | sorry 72 | --apply hqr 73 | --apply hpq 74 | --assumption 75 | ---- "alternative" proofs 76 | --apply hqr (hpq hp) 77 | --exact hqr (hpq hp) 78 | --solve_by_elim 79 | --exact? 80 | done 81 | 82 | /- 83 | Often, proof assistants make us aware of concepts that we were using unconsciously. 84 | One of these may be the different role that *assumption* and *conclusions* play in a proof. 85 | For instance, if one of your *assumptions* is `a + b = 0 ∧ a - b = 0`, 86 | then we should have access to both statements `a + b = 0` and `a - b = 0`. 87 | However, if our *conclusion* is `a + b = 0 ∧ a - b = 0`, 88 | then, effectively, we have to prove two equalities 89 | * `a + b = 0`, and 90 | * `a - b = 0`. 91 | 92 | You can think of the conjunction `and` among the *assumptions* as giving you two assumptions 93 | within the same proof. 94 | You can think of the conjunction `and` among the *conclusions* as requiring you to give 95 | two *separate* proofs with the same assumptions. 96 | 97 | Exercise. 98 | What changes if we replace `and` with `or` in the previous discussion? 99 | 100 | Consequently, the tactics that we use may be different, based on whether "similar looking" 101 | statements are assumptions or conclusions. 102 | 103 | For instance, when we have an `and` assumption, we can use the `cases` tactic. 104 | When we have an `and` conclusion, we can use the `constructor` tactic. 105 | -/ 106 | 107 | example {n : ℕ} (h : n + n = n + 1) : n = 1 := by 108 | sorry 109 | ---- this lemma (or rather a more general version of it) exists and `exact?` finds it for us 110 | --have := add_right_inj (G := ℕ) 111 | ---- apply this.mp -- fails, try to understand the error! 112 | --apply (this ?_).mp 113 | ---- look at the state now 114 | --· exact h 115 | ---- where did the other goal go? 116 | done 117 | 118 | lemma zero_eq_zero : 0 = 0 := by 119 | rfl 120 | done 121 | 122 | lemma zero_ne_one : 0 ≠ 1 := by 123 | exact? 124 | done 125 | -------------------------------------------------------------------------------- /MA4N1/L05_limits.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Order.Filter.Basic 3 | import Mathlib.Data.Real.Archimedean 4 | 5 | namespace TPwL_limits 6 | 7 | /-! 8 | # Continuity in `Mathlib` 9 | 10 | As I mentioned in the lectures, continuity and limits are defined in a way 11 | that differs quite a bit to what you might be used. 12 | This is done via `filter`s. 13 | 14 | Nevertheless, the "standard" definition of continuity is available in `Mathlib` 15 | and we will use the standard one, not the one using filters. 16 | 17 | First, in case you are curious, `Filter.Tendsto` is `Mathlib`s way of talking about limits. 18 | -/ 19 | #check Filter.Tendsto 20 | /-! 21 | Moving on, let's roll out our "standard" definition. 22 | -/ 23 | 24 | /-- The limit of a sequence of real numbers. -/ 25 | def limit (f : ℕ → ℝ) (a : ℝ) : Prop := ∀ ε > 0, ∃ N, ∀ n, N ≤ n → |f n - a| < ε 26 | 27 | /-! 28 | Note that there is an existential (`∃`) in the definition of a limit. 29 | To provide a witness of the existential, you can use the tactic `use`. 30 | 31 | Here is an example of the `use` tactic. 32 | Use ``use [your choice of a natural number bigger than `n`]``! 33 | -/ 34 | example {n : ℕ} : ∃ m, n < m := by 35 | use n + 1 36 | exact? 37 | done 38 | 39 | example {a : ℝ} : limit (fun _n => a) a := by 40 | unfold limit 41 | intros ε h 42 | use 0 43 | simp [h] 44 | done 45 | 46 | -- Hint: on an existential `H : ∃ x, P x`, the tactic `cases H` gives you access to an `x` and 47 | -- the fact that it satisfies the proposition `P`. 48 | -- Remember to wait for the lightbulb to fill in the syntax for `cases`! 49 | -- Also, keep in mind that you have a "hidden" `∃` in the local context. 50 | example {a b : ℝ} (f : ℕ → ℝ) (h : limit f a) : limit (fun n => f n + b) (a + b) := by 51 | unfold limit 52 | intros ε hε 53 | cases h ε hε with 54 | | intro N h1 => 55 | use N 56 | intro n Nn 57 | norm_num 58 | apply h1 _ Nn 59 | done 60 | 61 | -- Hint: check what `half_pos` is! 62 | example {a b : ℝ} (f g : ℕ → ℝ) (hf : limit f a) (hg : limit g b) : 63 | limit (fun n => f n + g n) (a + b) := by 64 | unfold limit 65 | intros ε hε 66 | cases hf (ε / 2) (half_pos hε) with 67 | | intro M h1 => 68 | cases hg (ε / 2) (half_pos hε) with 69 | | intro N h2 => 70 | use max M N 71 | intro n MNn 72 | dsimp 73 | rw [add_sub_add_comm] 74 | apply lt_of_le_of_lt (abs_add_le (f n - a) (g n - b)) 75 | refine lt_of_lt_of_le (add_lt_add (h1 n ?_) (h2 n ?_)) ?_ 76 | · exact le_of_max_le_left MNn -- `exact?` works 77 | · exact le_of_max_le_right MNn -- `exact?` works 78 | · norm_num 79 | done 80 | 81 | /- 82 | What follows can be *very* challenging: do not despair if you get stuck! 83 | 84 | In the proposed solution, I used 85 | * `Int.natAbs`, a function that takes an integer and returns its absolute value as a natural number; 86 | * `Int.floor`, a function that takes a real number and returns its floor as an integer number; 87 | * `le_trans`, a propositon that takes the proof of two inequalities of the form `a ≤ b` and `b ≤ c` 88 | as input and returns a proof of the inequality `a ≤ c`; 89 | 90 | and various other tricks, including 91 | * `LT.lt.le` converting a strict inequality `a < b` into a weak one `a ≤ b`; 92 | * explicitly using double type-ascriptions to layer the steps in going from `ℕ`, to `ℤ`, to `ℝ`; 93 | * ... 94 | 95 | Part of the challenge is for you to find a much simpler proof of `no_limit_id` than the one that I propose! 96 | -/ 97 | 98 | lemma aux1 (n : ℤ) : n ≤ Int.natAbs n := by 99 | exact? 100 | done 101 | 102 | lemma aux (a : ℝ) : -1 ≤ (Int.natAbs ⌊a⌋) - a := by 103 | rw [neg_le_sub_iff_le_add] 104 | apply le_trans (b := (⌊a⌋ : ℝ) + 1) 105 | · apply (Int.lt_floor_add_one a).le 106 | · simp 107 | apply le_trans (b := (⌊a⌋ : ℝ)) 108 | · exact Eq.le rfl 109 | · have := aux1 ⌊a⌋ 110 | norm_cast 111 | exact? 112 | done 113 | 114 | lemma no_limit_id {a : ℝ} : ¬ limit (fun n => n) a := by 115 | unfold limit 116 | simp only [gt_iff_lt, not_forall, not_exists, not_lt, exists_prop] 117 | use 1 118 | simp 119 | intro n 120 | use (FloorRing.floor a).natAbs + 2 + n 121 | norm_num 122 | rw [← neg_add_eq_sub, ← add_assoc, ← add_assoc, neg_add_eq_sub] 123 | rw [le_abs] 124 | apply Or.inl 125 | have : (-1 : ℝ) + 2 + 0 ≤ ↑(Int.natAbs ⌊a⌋) - a + 2 + ↑n := by 126 | apply add_le_add 127 | · apply add_le_add 128 | · exact aux a 129 | · exact Eq.le rfl -- `exact?` finds this 130 | · exact Nat.cast_nonneg n -- `exact?` finds this 131 | norm_num at this 132 | assumption 133 | done 134 | 135 | end TPwL_limits 136 | -------------------------------------------------------------------------------- /MA4N1/L07_calculations.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_calculations 5 | 6 | /-! 7 | 8 | # Performing calculations in Lean 9 | 10 | Very likely, you will find yourself having to perform some calculation while working on your project. 11 | Here we go over some of the tools and tactics available to support this part of the proof. 12 | 13 | ## `calc` blocks 14 | 15 | The first tool that we introduce is `calc`. 16 | This usually creates very readable code for chaining together long sequences of 17 | * equalities (`=`); 18 | * weak inequalities (`≤`); 19 | * strict inequalities (`<`); 20 | * and more! 21 | 22 | First, let's see the syntax. 23 | If the goal has the shape `a ≤ b` (or also `a < b` or `a = b`), then we can write 24 | ```lean 25 | calc a ≤ c := by sorry 26 | c ≤ d := by sorry 27 | ... 28 | z ≤ b := by sorry 29 | ``` 30 | and Lean will chain the proofs together for us. 31 | The syntax is a little fiddly and it may take a little time to get used to. 32 | Here are some pointers. 33 | * Unless your expressions are very short, I suggest leaving `calc` on the previous line and 34 | beginning the following line with `a ≤ c := ...`. 35 | * If all LHS begin with the same indentation, then you are good! 36 | * Most of the times, you do not need to repeat the LHS, since Lean will know that it is the same 37 | as the previous RHS. 38 | * Most of the times, you can leave out the final RHS, since Lean will assume that it is the final term. 39 | 40 | The "most of the times" above may really not apply in certain situations! 41 | 42 | Let's begin with some simple examples. 43 | -/ 44 | 45 | -- First, one to get the syntax working. 46 | example : 0 < 10 := by calc 47 | _ < 1 := by norm_num 48 | _ ≤ 2 := by norm_num -- indent me more to see an error message 49 | _ = 1 + 1 := by norm_num 50 | _ ≤ 10 := by norm_num 51 | 52 | -- annoying: can we use `calc`? 53 | example {n : ℕ} {x : ℝ} (h : 1 ≤ x) : n ≤ n * x := by 54 | rw [← mul_one n] 55 | /- 56 | calc 57 | _ = ↑(n * 1) * (1 : ℝ) := by rw [mul_one (M := ℝ)] 58 | _ ≤ _ := by apply mul_le_mul le_rfl h zero_le_one <| Nat.cast_nonneg _ 59 | --/ 60 | sorry 61 | done 62 | 63 | -- `calc` can help dealing with "casts" 64 | example : (1 : ℝ) ≤ 3 := by 65 | sorry 66 | done 67 | 68 | -- you can nest the uses of `calc` 69 | example {n : ℕ} {x : ℝ} (h : 1 ≤ x) : n + 1 ≤ n * x + 3 := by calc 70 | (_ : ℝ) ≤ n * 1 + 1 := by rw [mul_one] 71 | _ ≤ n * x + 1 := add_le_add_right (mul_le_mul rfl.le h zero_le_one n.cast_nonneg) .. 72 | _ ≤ _ := by 73 | apply add_le_add_left 74 | calc 75 | (1 : ℝ) = ((1 : ℕ) : ℝ) := by exact Nat.cast_one.symm 76 | _ ≤ ((3 : ℕ) : ℝ) := by refine Nat.cast_le.mpr ?_; exact Nat.le_three_of_sqrt_eq_one rfl 77 | _ ≤ _ := by exact Eq.le rfl 78 | 79 | -- try replacing the `<` by `≤` or doing other changes and see how Lean reacts. 80 | example : (0 : ℝ) < 10 := by calc 81 | (_ : ℝ) < 1 := by norm_num 82 | _ ≤ 2 := by norm_num 83 | _ = 1 + 1 := by norm_num 84 | _ ≤ 10 := by norm_num 85 | 86 | /-! 87 | 88 | # The Conversion Tactic Mode `conv` 89 | 90 | `conv` allows you to "zoom in" on parts of an expression and perform various operations on it. 91 | A typical usage is to do targeted rewrites, but there are several other possibilities available. 92 | 93 | Hovering over `conv`, the doc-string contains some information and the link 94 | 95 | https://lean-lang.org/theorem_proving_in_lean4/conv.html 96 | 97 | extensive documentation. 98 | -/ 99 | 100 | -- annoying: can we use `conv`? 101 | example {n : ℕ} {x : ℝ} (h : 1 ≤ x) : n ≤ n * x := by 102 | conv => congr; rw [← mul_one n] 103 | sorry 104 | done 105 | 106 | example {f g : ℕ → ℝ} (hf : ∀ n, f n = 5) (hg : ∀ n, g n = 6) : 107 | ∀ ε > 0, ∃ N ≥ 0, ∀ n ≥ N, |f n + g n - 11| < ε := by 108 | -- rw [hf, hg] -- does not work, even adding `intros` before it 109 | conv in |_| => 110 | rw [hf, hg] 111 | norm_num 112 | intros _ h 113 | simp [h] 114 | done 115 | 116 | /-! 117 | 118 | # Generalized congruence: `gcongr` 119 | 120 | When faced with an equality involving several terms, and a lot of common expressions, 121 | `congr` can help to focus on the different-looking parts. 122 | 123 | `gcongr` is similar, but works on more than just equality, notably with `≤` and `<`. 124 | 125 | -/ 126 | 127 | example {a b c : ℝ} (ha : 1 ≤ a) (hb : 0 ≤ b) (hc : 0 ≤ c) : 128 | a + b + c ≤ a * a + 3 * b + 4 * c := by 129 | -- we can do this using a `calc` block, going sequentially through 130 | -- `_ ≤ a + b + 4 * c` 131 | -- `_ ≤ a + 3 * b + 4 * c` 132 | -- `_ ≤ a * a + 3 * b + 4 * c` 133 | -- however, the situation where you want to estimate two sides of an (in)equality is fairly common 134 | -- and the two sides can be estimated "term-wise". 135 | -- the `gcongr` tactic helps with this! 136 | gcongr 137 | · apply le_mul_of_le_mul_of_nonneg_left ?_ ha 138 | · apply zero_le_one.trans ha 139 | · rw [mul_one] 140 | · linarith 141 | · linarith 142 | done 143 | 144 | end TPwL_calculations 145 | -------------------------------------------------------------------------------- /MA4N1/L05_graphs_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Combinatorics.SimpleGraph.Hasse 3 | import Mathlib.Combinatorics.SimpleGraph.Basic 4 | import Mathlib.Data.PNat.Prime 5 | 6 | namespace TPwL_graphs 7 | 8 | /- 9 | # Graphs in `Mathlib` 10 | 11 | In maths, you may have seen the definition of a graph as a pair `(V, E)` 12 | consisting of 13 | * a set a `V`, whose elements are called vertices; 14 | * a set `E ⊂ Sym^2 V` of unordered pairs of distinct vertices of `V`. 15 | 16 | There are various "flavours" or variations, where you may allow edges between 17 | a vertex and itself, or where you may allow multiple edges connecting the same 18 | pair of vertices. 19 | 20 | We will only look at the notion `SimpleGraph` from `Mathlib`, which corresponds 21 | to what are called simple, undirected, loopless graphs: no edges between a vertex 22 | and itself, at most one edge between any pair of vertices, edges are *un*ordered 23 | pairs. 24 | 25 | In `Mathlib`, all this data is encoded in the structure `SimpleGraph`, associated 26 | with a Type `V` and containing as fields 27 | * the relation `SimpleGraph.Adj` on `V` -- the adjacency relation, where two vertices 28 | are in relation if they correspond to adjacent vertices; 29 | * a proof that the relation is symmetric -- corresponding to the fact that our graphs 30 | are undirected; 31 | * a proof that the relation is irreflexive -- corresponding to the fact that our graphs 32 | are loopless. 33 | 34 | In case you have seen the notion of graph elsewhere, you may want to take some time 35 | to convince yourself that this version really is equivalent to the one with which 36 | you may be familiar! 37 | 38 | There is quite a bit of machinery developed to convert smoothly between edges 39 | simply represented as terms of `V` that are in relation and actual "concrete" 40 | unordered pairs. 41 | The relevant Type of unordered pairs is called `Sym2` and it has a fairly extensive 42 | API, allowing to go back and forth between vertices in relation and pairs. 43 | Also, `SimpleGraph.edgeSet` is... the edge set of a graph! 44 | It is a subset of `Sym2 V`. 45 | 46 | We still did not see this, but the Type of subsets of a given Type `V` is 47 | denoted by `Set V` is `Mathlib`. 48 | So, writing `U : Set V` means "`U` is a subset of `V`". 49 | 50 | The notation for an edge, viewed as an unordered pair is `s(a, b)`. 51 | The `s` is part of the syntax: this is a `s`ymmetric pair. 52 | In fact, separating `s` and `(` by a space, yields a syntax error. 53 | -/ 54 | 55 | -- Hover over these notions, to see the internal documentation. 56 | -- Go to definition if you want to see the implementation. 57 | #check SimpleGraph 58 | #check Sym2 59 | #check SimpleGraph.edgeSet 60 | 61 | /- 62 | `Mathlib` already has the definition of some graphs: 63 | -/ 64 | #check SimpleGraph.completeGraph 65 | #check SimpleGraph.emptyGraph 66 | #check completeBipartiteGraph 67 | #check SimpleGraph.pathGraph 68 | #check SimpleGraph.hasse -- graph associated to an order 69 | 70 | open SimpleGraph 71 | 72 | -- Hint: try `simp` and see if you can understand what is going on! 73 | lemma completeGraph_mem_edgeSet_of_ne (V : Type) {a b : V} (h : a ≠ b) : 74 | s(a, b) ∈ edgeSet (completeGraph V) := by 75 | sorry 76 | done 77 | /- 78 | _Aside: `DefEq` and its abuse._ 79 | Just using `exact?` suggests `exact h`. 80 | While of course this works, it is probably considered a "bad" proof, 81 | or what is sometimes called "`DefEq` abuse". 82 | `DefEq` is `Def`initional `Eq`uality and it is a very very strict form of equality. 83 | Proving that `(a, b)` is an edge of the complete graph, since `a` is different from `b` 84 | is exploiting the actual implementation of the graph, rather than its properties. 85 | If someone decided to implement the notion of `completeGraph` with a 86 | different (e.g. non-`DefEq` by still equivalent) property, *ideally* the only proofs 87 | that should be rewritten are the ones of the "API" surrounding `completeGraph`. 88 | From this point of view, the `simp [h]` proof should still work, while the proof 89 | `exact h` is more brittle and likelier to no longer work. 90 | -/ 91 | 92 | example {V : Type} {G : SimpleGraph V} {a b : V} (h : s(a, b) ∈ G.edgeSet) : 93 | s(b, a) ∈ G.edgeSet := by 94 | sorry 95 | done 96 | 97 | /-! 98 | We now define a new graph. 99 | -/ 100 | 101 | /-- 102 | the `divisibilityGraph`: this is a graph with vertices the natural numbers 103 | whose edges consist of pairs of distinct numbers one of which divides the other. 104 | -/ 105 | -- Most of this is auto-generated: if instead of `where` you type `:=[new_line]_`, 106 | -- wait for the lightbulb to appear and click on it, then you can auto-complete 107 | -- using `Generate a skeleton for the structure under construction`. 108 | def divisibilityGraph : SimpleGraph ℕ where 109 | -- if you notice, we use `Adj a b := ...` instead of `Adj := fun a b => ...` 110 | -- this is a convenient feature to improve readability, but both mean the same 111 | Adj a b := (a ≠ b) ∧ ((a ∣ b) ∨ (b ∣ a)) 112 | -- use `dsimp at *` to get a better view of the assumptions/goal 113 | symm a b h := by 114 | sorry 115 | done 116 | loopless a ha := by 117 | sorry 118 | done 119 | 120 | example : ¬ s(3, 5) ∈ divisibilityGraph.edgeSet := by 121 | sorry 122 | done 123 | 124 | example : s(3, 6) ∈ divisibilityGraph.edgeSet := by 125 | sorry 126 | done 127 | 128 | -- Try this for a potential challenge! 129 | example {p q : ℕ} (hp : p.Prime) (hq : q.Prime) : 130 | ¬ s(p, q) ∈ divisibilityGraph.edgeSet := by 131 | sorry 132 | done 133 | 134 | end TPwL_graphs 135 | -------------------------------------------------------------------------------- /MA4N1/Matrices.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | /-! 5 | # Matrices in Lean 6 | 7 | As you know, the entries of a matrix are indexed by two natural numbers, 8 | representing the rows and columns of the matrix. 9 | 10 | In `Mathlib`, the formalisation of matrices starts with a function that takes 11 | two "indices" and returns the corresponding entry of the matrix. 12 | Thus, we should first of all decide where the entries live. 13 | If we allow all natural numbers, then we are going to get (doubly) infinite matrices -- 14 | this may not be so good! 15 | 16 | So, instead, we can start with two finite sets and 17 | use these finite sets to index the entries of our matrix. 18 | 19 | But... how do we get a finite set? 20 | Of course, in many ways! 21 | 22 | A convenient one is to use `Fin`. 23 | `Fin` takes a natural number `n` and returns `Fin n`, the finite set `{0, 1,..., n-1}`. 24 | -/ 25 | 26 | open Matrix 27 | 28 | example {n : ℕ} : Nat.card (Fin n) = n := by 29 | simp 30 | -- `Nat.card` is the cardinality of a set, as a natural number. Hover over it to see the documentation. 31 | 32 | -- A way of writing the 2×2 identity matrix (this ends up not being the most straightforward way!) 33 | example : (fun i j : Fin 2 => if i = j then 1 else 0 : Matrix (Fin 2) (Fin 2) ℝ) = Matrix.one.one := by 34 | -- read below for some details on these tactics 35 | ext i j 36 | fin_cases i <;> fin_cases j <;> simp <;> rfl 37 | /- 38 | The proof above is useful since it displays some of the automation that exists. 39 | Also, something along those lines would have to happen if you wanted to prove some facts about your own 40 | kind of matrix for which there are no lemmas yet. 41 | 42 | Tactics used in the proof 43 | * `ext` is short for extensionality. 44 | If the goal is something that can be proved by checking it separately on all elements of "something", 45 | then `ext` introduces those variables and asks you to prove the result for the introduced elements. 46 | In this case, an identity between matrices holds if it holds entrywise. 47 | `ext` takes, optionally, the names of the new variables that it introduces. 48 | * `fin_cases ` takes as input a variable that Lean can verify ranges in a finite set. 49 | It creates as many goals as the number of elements of this set and in each it specializes the variable to 50 | one of its possibilities. 51 | In this case, `i` and `j` are in `Fin 2`, so Lean replaces each by `0` or `1`. 52 | * `<;>` is not really a tactic, but a "tactic combinator": it tells Lean to run the tactic the follows 53 | `<;>` on all the goals created by the tactic that precedes it. 54 | In this case, we want to have all possibilities of `i` and `j` instantiated as `0` or `1`. 55 | * `simp` you should know: simplify and hope for the best! 56 | * `rfl` is short for reflexivity. 57 | This is basically telling Lean to check that the goal is "just true" or "true by definition". 58 | Equality is somewhat hard, since the computer has a very very strict notion of what counts as equal. 59 | Mathematicians tend to have a very loose notion of equality and will happily identify very very different notions. 60 | `rfl` is a way for the computer to accept a little more than its very strict notion. 61 | In this case, the goals that `rfl` proves are 62 | (recall that the rows and columns of our matrices are indexed by `0` and `1`) 63 | * `1 = One.one 0 0` -- the `(0,0)` entry of the matrix `One.one` (the identity matrix) is `1`, and 64 | * `0 = One.one 0 1` -- the `(0,1)` entry of the matrix `One.one` (the identity matrix) is `0`. 65 | 66 | Note that `ext; simp` is a fairly common pattern of proof for results that hold elementwise. 67 | In this case, we are simply inserting the "case-split" `fin_cases`, since the arguments for different entries 68 | depend on the actual entries. 69 | -/ 70 | 71 | -- Better to use the dedicated notation! 72 | example : !![1, 0; 0, 1] = 1 := by exact one_fin_two.symm -- found with `exact?` 73 | 74 | -- Also, let's make `one_fin_two` into a `simp` lemma. 75 | attribute [simp] one_fin_two 76 | 77 | /- 78 | Hopefully the notation is self-explanatory: 79 | we can provide a matrix to Lean by using the following syntax 80 | * the matrix entries are contained in a `!![...]` block; 81 | * rows are inserted by comma-separated `,` entries; 82 | * consecutive rows are separated by a colon `;`. 83 | 84 | The syntax allows rectangular (i.e. not necessarily square) matrices. 85 | For legibility (especially if we have bigger matrices), we could write the `2 × 2` identity matrix as 86 | -/ 87 | #check !![1, 0; 88 | 0, 1] 89 | /- 90 | although, admittedly, for such a small matrix, the single-line expression might be clearer. 91 | -/ 92 | 93 | -- let's prove a not fully trivial result about matrices! 94 | example {n : ℕ} : !![1, 1; 0, 1] ^ n = !![1, n; 0, 1] := by 95 | induction n with 96 | | zero => simp 97 | | succ n ih => 98 | rw [pow_succ', ih] 99 | simp 100 | done 101 | 102 | /- 103 | The `!![...]` notation is useful for writing "explicit" matrices, 104 | but if you have a "generic" `m × n` matrix it will not help. 105 | In fact, how *do* we write a generic `m × n`? 106 | -/ 107 | 108 | variable {m n : Type} (M : Matrix m n ℝ) 109 | /- 110 | `m` and `n` are *arbitrary* sets, `M` is a real-valued matrix 111 | whose rows are indexed by `m` and 112 | whose columns are indexed by `n`. 113 | 114 | This is maybe not so common: rows and columns are often *finite*... here we go! 115 | -/ 116 | variable [Fintype m] [Fintype n] 117 | /- 118 | Finiteness is a property of the set that is expressed by a typeclass. 119 | In fact, by several typeclasses, but `Fintype` may be the one that is most useful for us. 120 | -/ 121 | -------------------------------------------------------------------------------- /MA4N1/L08_Ri_hard_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import MA4N1.help_me 4 | 5 | namespace TPwL_Ri_hard_no_sols 6 | 7 | /-! 8 | 9 | # Defining the complex numbers 10 | 11 | In this exercise sheet, you will prove that the complex numbers form a field. 12 | 13 | Some parts of the argument are tricky: I will give *very* few spoilers to begin with, 14 | so that you have a chance to try it out for yourself. 15 | However, if you want to have some hints, do skip ahead, since there are lots of pointers below! 16 | 17 | ## The setup 18 | 19 | We define our version of the complex numbers, and we call it `Ri` (i.e. `ℝ` with `i`). 20 | Thus, `Ri` is simply a pair of real numbers, like `point` from the lectures. 21 | -/ 22 | 23 | @[ext] -- notice the `ext` attribute: we've taken this onboard! 24 | structure Ri : Type where 25 | re : ℝ -- we suggestively called the two fields `re` and `im`, 26 | im : ℝ -- to convey our expectation. 27 | 28 | /-! 29 | We are going to define the "data" fields of a `Field` before we prove that `Ri` is a field. 30 | While this is not strictly necessary, it turns out to be a *huge* advantage to have the 31 | definitions before-hand: this is related to some of the spoilers for this file! 32 | 33 | In fact, we begin by showing that `Ri` is a `CommRing`, leaving the proof that it is a field for later. 34 | Thus, we define `Add`, `Mul`, `Zero`, `One` and `Neg`. 35 | -/ 36 | 37 | instance : Add Ri where add a b := ⟨a.re + b.re, a.im + b.im⟩ 38 | instance : Mul Ri where mul a b := ⟨a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re⟩ 39 | instance : Neg Ri where neg a := ⟨- a.re, -a.im⟩ 40 | instance : Zero Ri where zero := ⟨0, 0⟩ 41 | instance : One Ri where one := ⟨1, 0⟩ 42 | 43 | /-! 44 | _Warning._ 45 | Proving the instance below with just what appears above is going to be *very* hard! 46 | I still encourage you to try, so that you can see first-hand what it feels like to have 47 | some missing key API lemmas! 48 | 49 | Once you have had enough, try to think of what lemmas would be helpful. 50 | If nothing comes to mind, type `help_me!` for a hint! 51 | If you are still unsure, `help_all` will give you all the API lemmas. 52 | 53 | We are now (not) ready to prove that `Ri` is a `CommRing`. 54 | 55 | The "data" fields 56 | * `add := (· + ·)` 57 | * `mul := (· * ·)` 58 | * `neg := (- ·)` 59 | * `zero := 0` 60 | * `one := 1` 61 | 62 | have already been provided above, so we no longer need to give them here. 63 | -/ 64 | instance : CommRing Ri where 65 | add_assoc := by 66 | sorry 67 | done 68 | zero_add := by 69 | sorry 70 | done 71 | add_zero := by 72 | sorry 73 | done 74 | add_comm := by 75 | sorry 76 | done 77 | left_distrib := by 78 | sorry 79 | done 80 | right_distrib := by 81 | sorry 82 | done 83 | zero_mul := by 84 | sorry 85 | done 86 | mul_zero := by 87 | sorry 88 | done 89 | mul_assoc := by 90 | sorry 91 | done 92 | one_mul := by 93 | sorry 94 | done 95 | mul_one := by 96 | sorry 97 | done 98 | neg_add_cancel := by 99 | sorry 100 | done 101 | mul_comm := by 102 | sorry 103 | done 104 | nsmul := nsmulRec 105 | zsmul := zsmulRec 106 | 107 | /-! 108 | 109 | ## Dealing with inverses 110 | 111 | Now that we proved that `Ri` is a `CommRing`, let's conclude by showing that it is in fact a `Field`! 112 | 113 | As before, we provide the only remaining "data" field, `Inv`, separately. 114 | Notice that Lean wants us to label `Inv` as `noncomputable`. 115 | This is not a big deal: we want to prove theorems about `Ri`, not "computing" anything with it! 116 | (If you are curious, comment out `noncomputable` to see what Lean says: 117 | ultimately, the "noncomputability" is a consequence of the fact that the real numbers are non-computable. 118 | This only means that there is no algorithm to decide if two real numbers are equal or not -- hardly surprising!) 119 | -/ 120 | 121 | noncomputable 122 | instance : Inv Ri where 123 | inv a := ⟨a.re / (a.re ^ 2 + a.im ^ 2), - a.im / (a.re ^ 2 + a.im ^ 2)⟩ 124 | 125 | -- Hint: you may find this lemma useful! 126 | #check add_eq_zero_iff_of_nonneg 127 | 128 | lemma add_square_eq_zero {a b : ℝ} (ha : a ^ 2 + b ^ 2 = 0) : 129 | a = 0 ∧ b = 0 := by 130 | sorry 131 | done 132 | 133 | /-! 134 | Here you may find the tactic `contrapose!` useful! 135 | Read the documentation below to see what it does, in case it is not clear from the name! 136 | -/ 137 | 138 | #help tactic contrapose 139 | 140 | lemma add_square_ne_zero {a : Ri} (ha : a ≠ 0) : 141 | a.re ^ 2 + a.im ^ 2 ≠ 0 := by 142 | sorry 143 | done 144 | 145 | /-! 146 | Hint: there is a tactic that I have not yet mentioned, but that I found useful for proving this instance. 147 | The tactic is called `apply_fun` (see below for the documentation of the tactic). 148 | The way in which I used it, is to generate an equality between the real parts of two equal real numbers. 149 | The real numbers in question were equal "by contradiction" and `apply_fun` allowed me to exploit 150 | results about the real numbers to close a goal. 151 | -/ 152 | 153 | #help tactic apply_fun 154 | 155 | -- uncomment the next line for a small hint 156 | --help_me! 157 | 158 | -- uncomment the next line for full hints 159 | --help_all 160 | 161 | noncomputable 162 | instance : Field Ri where 163 | exists_pair_ne := by 164 | sorry 165 | done 166 | mul_inv_cancel a ha := by 167 | sorry 168 | done 169 | inv_zero := by 170 | sorry 171 | done 172 | nnqsmul := _ -- as usual, an implementation detail: hover for some info 173 | qsmul := _ -- as usual, an implementation detail: hover for some info 174 | 175 | end TPwL_Ri_hard_no_sols 176 | -------------------------------------------------------------------------------- /MA4N1/L12_pathologies.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import Mathlib.Analysis.Calculus.LocalExtr.Basic 4 | 5 | namespace TPwL_pathologies 6 | 7 | /-! 8 | # Pathologies 9 | 10 | This file shows some of the quirks and oddities appearing in `Lean/Mathlib`. 11 | 12 | ## Every function is "total" 13 | 14 | What this means is that you are not allowed to leave a function undefined on 15 | some (non-empty) subset of its domain. 16 | 17 | Of course, you could imagine that you simply make sure that the domain of every function 18 | is exactly the subset where your function makes sense. 19 | While this is in theory possible, it is often better to work on the whole "natural" domain 20 | and simply return (carefully chosen) arbitrary values on inputs where you would normally 21 | not define your function. 22 | 23 | Here are some simple examples. 24 | -/ 25 | 26 | #eval 0 - 1 27 | example : 0 - 1 = 0 := by exact rfl 28 | /-! 29 | Lean happily tells us that `0 - 1` equals `0`. 30 | -/ 31 | 32 | #eval (1 : ℚ) / 0 33 | example : (1 : ℚ) / 0 = 0 := by exact div_zero 1 34 | /-! 35 | Lean is just happily telling us that 36 | * division by zero is correct; 37 | * and that `1 / 0` has value `0`, in fact. 38 | -/ 39 | 40 | example {q : ℚ} : q / 0 = 0 := by 41 | exact div_zero q 42 | done 43 | 44 | /- 45 | First, let's see a "high-level" explanation: 46 | * we want to define `(· / ·) : ℚ → ℚ → ℚ`; 47 | * we know what to do when the second input is non-zero; 48 | * in set-theory, we simply say "let's not define division when the denominator is `0`". 49 | 50 | A more "low-level" explanation is that 51 | 52 | **there is a cost to every definition that you make.** 53 | 54 | Thus, if you want the denominator input to your division function to be non-zero, 55 | you are going to have to roll your sleeves up and define the "Type of non-zero rational numbers". 56 | After you have defined this Type, you will have to start proving some theorems about it -- 57 | these theorems will likely be complete trivialities, but you will have to devote time to doing that. 58 | You will also have to relate "non-zero rational numbers" to "rational numbers that could be zero". 59 | 60 | Of course, this is something that *can* be done, and you can certainly find situations in `Mathlib` 61 | where this is the preferred route. 62 | 63 | However, there are also other situations where it is simply much more convenient to work with 64 | `junk values`: you define your function everywhere, trying to make your life simpler. 65 | Naturally, for the results that are "really" interesting, some extra assumption will show up. 66 | Nevertheless, as a rule of thumb, the more you can hold on making these assumptions, 67 | the easier it will be to use your results, because you will not have to provide these assumptions 68 | every time you use your lemmas. 69 | -/ 70 | 71 | -- The `Std` division on `ℚ`. 72 | example : (2 : ℚ) / 1 = 2 := by 73 | exact div_one 2 74 | done 75 | 76 | -- Let's roll our own 77 | 78 | /-- `myDiv p q h` is the result of division of `p` by `q` with the assumption `h` that `q` is non-zero. -/ 79 | def myDiv (p q : ℚ) (h : q ≠ 0) : ℚ := p / q 80 | 81 | 82 | /-- 83 | error: failed to synthesize 84 | OfNat (1 ≠ 0 → ℚ) 2 85 | numerals are polymorphic in Lean, but the numeral `2` cannot be used in a context where the expected type is 86 | 1 ≠ 0 → ℚ 87 | due to the absence of the instance above 88 | 89 | Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command. 90 | --- 91 | error: unsolved goals 92 | ⊢ myDiv 2 1 = 2 93 | -/ 94 | #guard_msgs in 95 | example : myDiv 2 1 = 2 := by 96 | done 97 | 98 | example : myDiv 2 1 (by exact one_ne_zero) = 2 := by 99 | unfold myDiv 100 | exact div_one 2 101 | done 102 | 103 | example : (1 : ℚ) / 2 + 1 / 2 = 1 := by 104 | exact add_halves 1 105 | done 106 | 107 | example : myDiv 1 2 (by exact two_ne_zero) + myDiv 1 2 (by exact two_ne_zero) = 1 := by 108 | unfold myDiv 109 | exact add_halves 1 110 | done 111 | 112 | /-- 113 | error: unsolved goals 114 | ⊢ 0 ≠ 0 115 | --- 116 | error: unsolved goals 117 | ⊢ myDiv 1 0 ⋯ = 0 118 | -/ 119 | #guard_msgs in 120 | example : myDiv 1 0 (by 121 | _ 122 | ) = 0 := by 123 | done 124 | 125 | -- Finally, an example of a result where you cannot get away with the expected non-zero assumption 126 | 127 | /-- 128 | error: `exact?` could not close the goal. Try `apply?` to see partial suggestions. 129 | -/ 130 | #guard_msgs in 131 | example {q : ℚ} : q / q = 1 := by exact? 132 | -- `exact?` does not find this result. 133 | -- For a good reason: it is not true! 134 | example : (0 : ℚ) / 0 = 0 := by exact? 135 | 136 | -- So, let's try again! 137 | --[fill in your guess!] 138 | 139 | /-! 140 | # Conclusion 141 | 142 | It is important to know how everything is defined! 143 | 144 | Lean will help checking that what you formalised is correct. 145 | It will not check that what you formalised agrees with your intuitive idea! 146 | 147 | However, with Lean it is extremely easy to recurse into every definition and see how every 148 | object is defined from "first principles". 149 | In "informal" maths, this is not always straightforward, not only because you may not have access 150 | to some sources, but also because 151 | * sometimes/often definitions can be context-dependent, 152 | * there may be conflicting conventions in the literature, 153 | * there may be implicit assumptions that may not be clear to someone who is just starting, 154 | * ... 155 | 156 | These are some of the reasons why formalization is useful! 157 | -/ 158 | 159 | noncomputable 160 | def step (r : ℝ) : ℝ := if r < 0 then 0 else 1 161 | 162 | #check deriv 163 | 164 | -- The proof of this example appears in the file for the support class for Week 7 165 | example : deriv step = 0 := by 166 | sorry 167 | done 168 | 169 | end TPwL_pathologies 170 | -------------------------------------------------------------------------------- /MA4N1/L01_polynomials_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_polynomials_no_sols 5 | 6 | section Presentation 7 | /-! 8 | # Preliminaries 9 | 10 | The exercises cover several different notions: 11 | * `Polynomial` rings `R[X]`; 12 | * `natDegree`s of `Polynomial`s. 13 | 14 | I will certainly not have time to talk about all of the above, but you are of course more than 15 | welcome to explore on your own and to ask lots of questions! 16 | 17 | ## `Polynomial` 18 | 19 | The structure `Polynomial` takes a (Semi)`Ring` as input and returns... 20 | the `Mathlib` formalization of polynomials! 21 | -/ 22 | 23 | section Polynomials 24 | 25 | variable {R : Type*} [Semiring R] {r : R} 26 | 27 | #check Polynomial R 28 | #guard_msgs(drop error) in 29 | #check R[X] 30 | 31 | open Polynomial 32 | 33 | #check R[X] 34 | #guard_msgs(drop error) in 35 | #check R[Y] 36 | 37 | -- ## Basic constructors 38 | 39 | -- ### `C` -- the constants 40 | -- the extended name is `Polynomial.C` 41 | #check C r 42 | 43 | example {s : R} : C (r * s) = C r * C s := by 44 | exact? 45 | 46 | -- ### `X` -- the variable 47 | -- the extended name is `Polynomial.X` 48 | #check (X : R[X]) 49 | #check X 50 | 51 | -- ### `monomial` -- the... monomials 52 | -- we are not actually going to use them 53 | #check monomial 3 r 54 | 55 | example {n : ℕ} : C r * X ^ n = monomial n r := by 56 | exact? 57 | 58 | example : ((X + C 1) ^ 2 : R[X]) = X ^ 2 + 2 * X + 1 := by 59 | rw [sq, mul_add, add_mul, add_mul, ← sq, add_assoc, add_assoc] 60 | simp -- clears the `C`s 61 | congr 1 -- matches the common parts of the expressions 62 | rw [← add_assoc, two_mul] 63 | 64 | example : ((X + C r) ^ 2 : R[X]) = X ^ 2 + 2 * C r * X + C r ^ 2 := by 65 | rw [sq, mul_add, add_mul, add_mul, ← sq, add_assoc, add_assoc, X_mul_C] 66 | congr 1 -- matches the common parts of the expressions 67 | rw [← add_assoc, two_mul, ← add_mul, sq] 68 | 69 | variable {S} [CommSemiring S] in 70 | example : ((X + 1) ^ 2 : S[X]) = X ^ 2 + 2 * X + 1 := by 71 | ring 72 | 73 | variable {S} [CommSemiring S] in 74 | example : ((X + C 1) ^ 2 : S[X]) = X ^ 2 + C 2 * X + C 1 := by 75 | simp? 76 | ring 77 | congr 78 | 79 | #check natDegree 80 | 81 | -- Lean may not always have enough information to fill in typeclass arguments 82 | #guard_msgs(drop error) in 83 | example : natDegree 1 = 0 := by 84 | exact? 85 | 86 | #guard_msgs(drop error) in 87 | example : natDegree (C r * X + C 1) = 1 := by 88 | exact? -- we are missing a hypothesis! 89 | 90 | -- prove using `natDegree_add_eq_left_of_natDegree_lt` 91 | example [Nontrivial R] : natDegree (X + C 1) = 1 := by 92 | rw [natDegree_add_eq_left_of_natDegree_lt] 93 | exact? 94 | simp? 95 | 96 | -- One thing that could be useful for some of the exercises. 97 | -- The evaluation of polynomials in `R[X]` at a fixed polynomial `p` is a ring homomorphism 98 | -- `R[X] →+* R[X]`. 99 | -- This is called `Polynomial.aeval` in `Mathlib`. 100 | 101 | noncomputable 102 | example {R} [CommRing R] (p : R[X]) : R[X] →+* R[X] := 103 | (aeval p).toRingHom 104 | 105 | /- 106 | ### Pitfall: disappearing `C`s 107 | 108 | The exact shape of a lemma in `Mathlib` is what makes it applicable or not in any given situation. 109 | On the one hand, not all combinations of lemmas with/without `C` in statements about `Polynomial`s 110 | are available. 111 | On the other hand, `simp` will try to remove `C`s in your expressions, if it can. 112 | This means that `exact?` might have found a lemma *before* applying `simp` and may fail afterwards: 113 | -/ 114 | example [Nontrivial R] : natDegree (X + C 1) = 1 := by 115 | --simp --uncomment this `simp` and `exact?` fails 116 | exact? 117 | 118 | end Polynomials 119 | 120 | end Presentation 121 | 122 | section Exercises 123 | /- 124 | # Exercises 125 | -/ 126 | 127 | open Polynomial 128 | 129 | variable {R : Type*} [CommRing R] 130 | /-! 131 | Polynomials in Mathlib are denoted by the familiar notation `R[X]`. 132 | This notation is available because of the line `open Polynomial` just inside this section. 133 | Without `open Polynomial`, the notation is `Polynomial R`. 134 | 135 | Note that the `R` in `R[X]` is a `CommRing` and you can replace it by whatever (Semi)ring you want. 136 | The `[X]` part is hard-coded: it instructs Lean to consider polynomials in one variable over `R`. 137 | For instance, `#check R[Y]` yields an `unknown identifier 'Y'` error. 138 | 139 | Of course, the name of the variable in `R[X]` is `X`, so the notation is internally consistent, 140 | but you do not get the option of changing it, at least not easily! 141 | 142 | Also, the "obvious" inclusion `R ↪ R[X]` is denoted by `C` (for `C`onstants). 143 | The full name is `Polynomial.C`, but we are inside `open Polynomial`, so `C` suffices. 144 | 145 | Thus, `X ^ 3 + C 3 * X - C 2` represents the polynomial that you might write in TeX as 146 | $x ^ 3 + 3 x - 2$. 147 | -/ 148 | 149 | -- The following exercises get you familiar with `natDegree`s of polynomials. 150 | section natDegree 151 | 152 | example : natDegree (X + 1 : ℤ[X]) = 1 := by 153 | sorry 154 | done 155 | 156 | example : natDegree (C 0 * X ^ 2 + C 3 * X : ℤ[X]) = 1 := by 157 | sorry 158 | done 159 | 160 | example (h2 : (2 : R) = 0) (h3 : (3 : R) = 0) : (0 : R) = 1 := by 161 | sorry 162 | done 163 | 164 | lemma aux [Nontrivial R] (h2 : (2 : R) ≠ 0) : 165 | natDegree (C 4 * X ^ 2 : R[X]) < natDegree (C 2 * X ^ 3 : R[X]) := by 166 | sorry 167 | done 168 | 169 | /-- Proof without automation -- I had prepared this before tactic `compute_degree` was merged. -/ 170 | example : natDegree (C 2 * X ^ 3 + C 4 * X ^ 2 + 1 : R[X]) ∈ ({0, 3} : Set ℕ) := by 171 | sorry 172 | done 173 | 174 | /-- Proof with more automation -- works now that `compute_degree` is merged. -/ 175 | example : natDegree (C 2 * X ^ 3 + C 4 * X ^ 2 + 1 : R[X]) ∈ ({0, 3} : Set ℕ) := by 176 | sorry 177 | done 178 | 179 | end natDegree 180 | 181 | end Exercises 182 | 183 | end TPwL_polynomials_no_sols 184 | -------------------------------------------------------------------------------- /MA4N1/L02_generalizations.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | /- 3 | 4 | # Generalizations, automation, `exact?`, `simp`, tactics 5 | 6 | As it happens, someone comes along and says: 7 | 8 | "I just learned a cool fact! A polynomial with coefficients in `ℕ` is monotone!" 9 | 10 | Let's formalize this result! 11 | 12 | Let's also think about what it really means... 13 | 14 | Surely they intended to say that viewing a polynomial with coefficients in `ℕ` 15 | as a function `ℕ → ℕ`, we obtain a monotone function. 16 | 17 | -/ 18 | 19 | /- 20 | ### import 21 | 22 | tells Lean to learn basic mathematical facts 23 | -/ 24 | import Mathlib.Tactic 25 | 26 | /- 27 | ### namespace 28 | 29 | means that if we construct something and we call it `X` 30 | its real name is going to be `TPwL.X`. 31 | useful to avoid name-clashes with pre-existing objects. 32 | -/ 33 | 34 | namespace TPwL_generalizations 35 | 36 | /- 37 | ### open 38 | 39 | `open whatever` instructs Lean that when we refer 40 | to `X`, it should look for `X` or `whatever.X`. 41 | 42 | `namespace`s are ubiquitous, thus `open` allows us to avoid 43 | constantly writing `Function.[...]` or `Polynomial.[...]`. 44 | -/ 45 | open Function Polynomial NNReal 46 | 47 | namespace Nat 48 | 49 | /- 50 | ### variables 51 | 52 | `variables (x : X)` means that, from now on 53 | (within the current `section`/`namespace`/...), if we write `x` 54 | and Lean does not already know what `x` means, then it tries 55 | to see if `(x : X)` works and uses it. 56 | 57 | Useful to avoid repetitions in a group of results that have 58 | common assumptions and notation. 59 | -/ 60 | variable (f : ℕ[X]) -- `f` is a polynomial with coefficients in `ℕ` 61 | (P : ℕ[X] → Prop) -- `P` is a property of polynomials: `P f` may be 62 | -- true or false 63 | 64 | /- 65 | ### Digression on `Prop` 66 | 67 | `Prop` is the "generic Type of propositions". Most of the times, you can 68 | think of this as `True/False`. 69 | (The Type of "actual" `true/false` is called `Bool` -- note the difference 70 | in capitalization: `true : Bool` while `True : Prop`. 71 | This will be only superficially relevant.) 72 | 73 | Thus, when we write `P : ℕ[X] → Prop` we are introducing a function `P` 74 | that takes a polynomial with coefficients in `ℕ` and returns `True` 75 | or `False`. For instance, "being monic" could be one such function. 76 | Also, "the leading coefficient of `f` equals the first decimal digit 77 | of the `deg f`-th odd perfect number, if it exists, and `1` otherwise". 78 | -/ 79 | 80 | /- 81 | ### theorem/lemma 82 | 83 | Presumably, you already know about this. 84 | 85 | The syntax is 86 | `theorem : := ` 87 | 88 | * is the identifier that we can then use to refer to it. 89 | It is like a `\label` in laTex. 90 | 91 | * is where we list the assumptions that we make. 92 | For instance `(a : ℕ)` or `[CommGroup G]` or `(f : ℕ → ℝ)` or 93 | `(Goldbach: ∀ n : ℕ, ∃ p q, Prime p ∧ Prime q ∧ p + q = n)`. 94 | 95 | Bonus: to "see" Type-inference at work, look at the outputs of 96 | ```lean 97 | #check ∀ n, ∃ p q, Prime p ∧ Prime q ∧ p + q = n 98 | #check ∀ n : ℕ, ∃ p q, Prime p ∧ Prime q ∧ p + q = n 99 | ``` 100 | 101 | * is the actual proof term. 102 | Usually, this is a sequence of tactics inside a `by ... done` block. 103 | -/ 104 | theorem my_induction 105 | (P_zero : P 0) 106 | (P_add : ∀ p q, P p → P q → P (p + q)) 107 | (P_X_pow : ∀ n : ℕ, P (X ^ n)) : 108 | P f := by 109 | -- hover over `Polynomial.induction_on'` 110 | refine Polynomial.induction_on' f ?_ ?_ 111 | · -- `exact?` reports `exact P_add` 112 | exact P_add 113 | · intros n a 114 | simp [← C_mul_X_pow_eq_monomial] -- replace `monomial n a` with `a * X ^ n` 115 | -- proceed by induction on `a`, call 116 | induction a with 117 | -- base case: `a = 0` 118 | | zero => 119 | -- `simp?` to get some insight 120 | -- `rwa [CharP.cast_eq_zero, zero_mul]` also works 121 | simp [P_zero] 122 | -- * `a` the variable in the induction step 123 | -- * `ha` the inductive hypothesis 124 | -- induction step: `a → a + 1` 125 | -- `a.succ` stands for `Nat.succ a`: the `succ`essor function applied to `a` 126 | -- Lean "prefers" `Nat.succ` since it is one of the two "constructors" for `ℕ`. 127 | | succ a ha => 128 | simp [add_mul] 129 | apply P_add _ _ ha (P_X_pow _) 130 | done 131 | 132 | /- 133 | ### example 134 | 135 | The same (almost) as `theorem`, except that we cannot assign it a name. 136 | -/ 137 | example : Monotone (fun n ↦ f.eval n) := by 138 | apply my_induction f _ 139 | · -- show that the `0`-polynomial is monotone 140 | simp 141 | exact? 142 | -- we can compact this to `simp [monotone_const]` or more explicitly `simp only [eval_zero, monotone_const]` 143 | · -- if two polynomials are monotone, then so is their sum 144 | intros f g hf hg 145 | -- use that the sum of two monotone functions is monotone 146 | -- we can find the name of the lemma using auto-completion (Ctrl-Space) and guessing 147 | convert Monotone.add hf hg 148 | simp 149 | · -- show that monomials are monotone 150 | intros 151 | simp 152 | apply Monotone.pow_const 153 | apply monotone_id 154 | done 155 | 156 | end Nat 157 | 158 | /- 159 | Now that we proved it for `ℕ`, let's generalize to `ℝ≥0`. 160 | 161 | Copy-paste the above, change `ℕ` to `ℝ≥0` and fix the issues. 162 | -/ 163 | namespace nnreal 164 | 165 | end nnreal 166 | 167 | #lint 168 | 169 | /- 170 | Now that we proved it for `ℕ` and for `ℝ≥0`, let's generalize further. 171 | 172 | Copy-paste the above and look for a common generalization of `ℕ` and `ℝ≥0`. 173 | -/ 174 | 175 | namespace next 176 | 177 | -- --> Semiring --> Comm --> Ordered --> Canonically 178 | 179 | end next 180 | 181 | /- 182 | Finally, let's confirm that the more general result proves to the special cases that we know. 183 | -/ 184 | 185 | example (f : ℕ[X]) : Monotone (fun n ↦ f.eval n) := by 186 | sorry 187 | done 188 | 189 | example (f : ℝ≥0[X]) : Monotone (fun n ↦ f.eval n) := by 190 | sorry 191 | done 192 | 193 | end TPwL_generalizations 194 | -------------------------------------------------------------------------------- /MA4N1/L09_noncomputable_IsSquare.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_noncomputable_IsSquare 5 | 6 | /-! 7 | # A short introduction to `noncomputable` and `Decidable` 8 | 9 | The concepts of `noncomputable` and of `Decidable` are somewhat related. 10 | Further players that are involved in this area are `Classical` and the 11 | difference between `Prop` and `Bool`. 12 | Here, we try to give a light touch introduction to these topics, by giving 13 | one example of a `noncomputable` definition and one of a `Decidable` instance. 14 | You will see that `Prop` vs `Bool` also makes an appearance. 15 | To avoid saying *nothing* about `Classical`, let's simply point out that, 16 | within `open Classical` *every* `Prop` automatically acquires a `Decidable` 17 | instance and can therefore be turned into a `Bool`. 18 | 19 | ## `noncomputable` 20 | 21 | Sometimes, when using Lean, you may receive a warning that some definition should be marked as 22 | `noncomputable`. 23 | 24 | For example, Lean tells us that the definition `Real_inv` below should be `noncomputable`: 25 | if in the code below, you comment out the `noncomputable`, Lean will give the following error: 26 | 27 | failed to compile definition, consider marking it as 'noncomputable' because it depends on 28 | 'Real.instInv', and it does not have executable code 29 | -/ 30 | noncomputable 31 | def Real_inv (a : ℝ) : ℝ := a⁻¹ 32 | 33 | /-! 34 | Let's investigate this a little more. 35 | 36 | First, not all operations on `ℝ` are `noncomputable`: 37 | -/ 38 | section computable_Reals 39 | variable (a b : ℝ) 40 | 41 | def Real_add : ℝ := a + b 42 | def Real_sub : ℝ := a - b 43 | def Real_mul : ℝ := a * b 44 | end computable_Reals 45 | 46 | /-! 47 | What makes `Real_inv` "special"? Or, rather, `noncomputable`? 48 | 49 | Digging deeper into the definition, the culprit turns out to be the way in which `a⁻¹` is defined. 50 | In order to "compute" `a⁻¹`, we should know whether `a` is `0` or not. 51 | (Recall, that since functions in Lean must return a value for *every* input, 52 | there is a convention that `0⁻¹ = 0`.) 53 | 54 | On the one hand, if `a` is non-zero, then for any Cauchy sequence converging to `a`, 55 | eventually the terms will be non-zero and the sequence of "inverses of non-zero terms" is Cauchy 56 | and converges to `a⁻¹`. 57 | (Given the convention on `0⁻¹ = 0`, we do not even have to worry about inverting/omitting the terms 58 | of the Cauchy sequence that are equal to `0`.) 59 | 60 | So far so good. 61 | 62 | On the other hand, if `a` is `0`, then the sequence constantly equal to `0` is a Cauchy sequence 63 | converging to `a⁻¹ = 0⁻¹ = 0`. 64 | So... where is the issue? 65 | 66 | As you may have guessed, it is the `if ... then ... else ...` split. 67 | The `if`-clause asks us to decide whether the real number `a` is zero or not. 68 | *This* is an undecidable problem: no algorithm exists to test this property. 69 | 70 | Of course, in some cases, we may know that `a` is `0`, but the general question is undecidable. 71 | 72 | Let's test this. 73 | -/ 74 | noncomputable -- comment this and Lean complains 75 | def Real_is_zero (a : ℝ) : Bool := a = 0 76 | 77 | -- No need to mark as `noncomputable`, since this is a `Prop`, not a `Bool`. 78 | def Real_is_zero_Prop (a : ℝ) := a = 0 79 | 80 | /-! 81 | Hopefully, this clarified some possibly murky points. 82 | 83 | You see that there is a subtle interaction between computability, `Prop`s and `Bool`s. 84 | 85 | ## `Decidable` 86 | 87 | `Decidable` is a typeclass that certain `Prop`ositions have. 88 | Proving a `Decidable` instance on `(p : Prop)` means that 89 | * either you produce a proof of `p`, 90 | * or you produce a proof of the negation `¬ p` of `p`. 91 | 92 | If you take a look at the constructors for `Decidable`, you see that there exactly two: 93 | * `Decidable.isFalse` obtained by providing a proof of `¬ p`; 94 | * `Decidable.isTrue` obtained by providing a proof of `p`. 95 | -/ 96 | #print Decidable 97 | 98 | /-! 99 | Let's provide a `Decidable` instant to the predicate `IsSquare` on natural numbers. 100 | 101 | Since there is a rich infrastructure of results available to provide `Decidable` instances, 102 | we take advantage of the fact that Lean will be able to infer a `Decidable` instance on 103 | `IsSquare` if we can prove that `IsSquare` is equivalent to another `Prop` for which Lean 104 | already has a `Decidable` instance. 105 | 106 | Thus, we begin by proving a lemma showing that `IsSquare` is equivalent to a different 107 | (but equivalent!) `Prop`osition. 108 | -/ 109 | 110 | theorem IsSquare_iff_mul_self {m : ℕ} : IsSquare m ↔ Nat.sqrt m * Nat.sqrt m = m := by 111 | rw [← Nat.exists_mul_self m, IsSquare] 112 | -- `rw` is not able to change the order of one of the equalities, since it does 113 | -- not "enter" the binders: the `∃` prevents `rw` from working in this case. 114 | -- `simp_rw [x]` is similar to `simp only [x]` and *does* enter binders. 115 | simp_rw [eq_comm] 116 | done 117 | 118 | -- The following examples can be proved using the result above. 119 | -- `norm_num` does not work before the `rw` and neither does `decide`. 120 | example : IsSquare 36 := by 121 | --decide -- failed to synthesize `Decidable (IsSquare 36)` 122 | rw [IsSquare_iff_mul_self] 123 | norm_num 124 | done 125 | 126 | example : ¬ IsSquare 20 := by 127 | --decide -- failed to synthesize `Decidable ¬IsSquare 20` 128 | rw [IsSquare_iff_mul_self] 129 | norm_num 130 | done 131 | 132 | -- Let's provide now the `Decidable` instance. 133 | -- `decidable_of_iff'` takes as input a `Prop` that already has a `Decidable` instance 134 | -- and a proof of the equivalence of the "`Decidable`" `Prop` and the not-yet-`Decidable` 135 | -- one and "transports" the decidability. 136 | instance {m : ℕ} : Decidable (IsSquare m) := 137 | decidable_of_iff' _ IsSquare_iff_mul_self 138 | 139 | -- The two examples above can now be proved using `decide`. 140 | -- May get fixed soon: https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Nat.2Esqrt.20no.20longer.20reduces.20definitionally 141 | example : IsSquare 36 := by 142 | decide 143 | done 144 | 145 | example : ¬ IsSquare 20 := by 146 | decide 147 | done 148 | 149 | end TPwL_noncomputable_IsSquare 150 | -------------------------------------------------------------------------------- /MA4N1/L08_Ri_easy_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Batteries.Tactic.Lemma 3 | import Mathlib.Tactic 4 | import MA4N1.help_me 5 | 6 | namespace TPwL_Ri_easy_no_sols 7 | 8 | /-! 9 | 10 | # Defining the complex numbers 11 | 12 | In this exercise sheet, you will prove that the complex numbers form a field. 13 | 14 | Some parts of the argument are tricky: I will give *very* few spoilers to begin with, 15 | so that you have a chance to try it out for yourself. 16 | However, if you want to have some hints, do skip ahead, since there are lots of pointers below! 17 | 18 | ## The setup 19 | 20 | We define our version of the complex numbers, and we call it `Ri` (i.e. `ℝ` with `i`). 21 | Thus, `Ri` is simply a pair of real numbers, like `point` from the lectures. 22 | -/ 23 | 24 | @[ext] -- notice the `ext` attribute: we've taken this onboard! 25 | structure Ri : Type where 26 | re : ℝ -- we suggestively called the two fields `re` and `im`, 27 | im : ℝ -- to convey our expectation. 28 | 29 | /-! 30 | We are going to define the "data" fields of a `Field` before we prove that `Ri` is a field. 31 | While this is not strictly necessary, it turns out to be a *huge* advantage to have the 32 | definitions before-hand: this is related to some of the spoilers for this file! 33 | 34 | In fact, we begin by showing that `Ri` is a `CommRing`, leaving the proof that it is a field for later. 35 | Thus, we define `Add`, `Mul`, `Zero`, `One` and `Neg`. 36 | -/ 37 | 38 | instance : Add Ri where add a b := ⟨a.re + b.re, a.im + b.im⟩ 39 | instance : Mul Ri where mul a b := ⟨a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re⟩ 40 | instance : Neg Ri where neg a := ⟨- a.re, -a.im⟩ 41 | instance : Zero Ri where zero := ⟨0, 0⟩ 42 | instance : One Ri where one := ⟨1, 0⟩ 43 | 44 | /-! 45 | The following 10 lemmas have virtually no mathematical context and their proof is `rfl`: 46 | this is Lean's way of saying that they follow from the definitions in a very strong sense. 47 | 48 | However, stating them and giving them the `simp` tag means that Lean will apply them 49 | whenever we call `simp`: if you tried proving that `Ri` is a `CommRing` without seeing these 50 | lemmas, you will notice a great difference before and after! 51 | -/ 52 | @[simp] lemma re_add {a b : Ri} : (a + b).re = a.re + b.re := rfl 53 | @[simp] lemma im_add {a b : Ri} : (a + b).im = a.im + b.im := rfl 54 | 55 | @[simp] lemma re_mul {a b : Ri} : (a * b).re = a.re * b.re - a.im * b.im := rfl 56 | @[simp] lemma im_mul {a b : Ri} : (a * b).im = a.re * b.im + a.im * b.re := rfl 57 | 58 | @[simp] lemma re_neg {a : Ri} : (- a).re = - a.re := rfl 59 | @[simp] lemma im_neg {a : Ri} : (- a).im = - a.im := rfl 60 | 61 | @[simp] lemma re_zero : (0 : Ri).re = 0 := rfl 62 | @[simp] lemma im_zero : (0 : Ri).im = 0 := rfl 63 | 64 | @[simp] lemma re_one : (1 : Ri).re = 1 := rfl 65 | @[simp] lemma im_one : (1 : Ri).im = 0 := rfl 66 | 67 | /-! 68 | We are now ready to prove that `Ri` is a `CommRing`. 69 | 70 | The "data" fields 71 | * `add := (· + ·)` 72 | * `mul := (· * ·)` 73 | * `neg := (- ·)` 74 | * `zero := 0` 75 | * `one := 1` 76 | 77 | have already been provided above, so we no longer need to give them here. 78 | -/ 79 | instance : CommRing Ri where 80 | add_assoc := by sorry 81 | zero_add := by sorry 82 | add_zero := by sorry 83 | add_comm := by sorry 84 | left_distrib := by sorry 85 | right_distrib := by sorry 86 | zero_mul := by sorry 87 | mul_zero := by sorry 88 | mul_assoc := by sorry 89 | one_mul := by sorry 90 | mul_one := by sorry 91 | neg_add_cancel := by sorry 92 | mul_comm := by sorry 93 | nsmul := nsmulRec 94 | zsmul := zsmulRec 95 | /-! 96 | 97 | ## Dealing with inverses 98 | 99 | Now that we proved that `Ri` is a `CommRing`, let's conclude by showing that it is in fact a `Field`! 100 | 101 | As before, we provide the only remaining "data" field, `Inv`, separately. 102 | Notice that Lean wants us to label `Inv` as `noncomputable`. 103 | This is not a big deal: we want to prove theorems about `Ri`, not "computing" anything with it! 104 | (If you are curious, comment out `noncomputable` to see what Lean says: 105 | ultimately, the "noncomputability" is a consequence of the fact that the real numbers are non-computable. 106 | This only means that there is no algorithm to decide if two real numbers are equal or not -- hardly surprising!) 107 | -/ 108 | 109 | noncomputable 110 | instance : Inv Ri where 111 | inv a := ⟨a.re / (a.re ^ 2 + a.im ^ 2), - a.im / (a.re ^ 2 + a.im ^ 2)⟩ 112 | 113 | /-! 114 | We learned our lesson: let's prove our silly `rfl` lemmas about `Inv.inv`. 115 | -/ 116 | 117 | @[simp] lemma re_inv {a : Ri} : (a⁻¹).re = a.re / (a.re ^ 2 + a.im ^ 2) := rfl 118 | @[simp] lemma im_inv {a : Ri} : (a⁻¹).im = - a.im / (a.re ^ 2 + a.im ^ 2) := rfl 119 | 120 | -- Hint: you may find this lemma useful! 121 | #check add_eq_zero_iff_of_nonneg 122 | 123 | lemma add_square_eq_zero {a b : ℝ} (ha : a ^ 2 + b ^ 2 = 0) : 124 | a = 0 ∧ b = 0 := by 125 | sorry 126 | done 127 | 128 | /-! 129 | Here you may find the tactic `contrapose!` useful! 130 | Read the documentation below to see what it does, in case it is not clear from the name! 131 | -/ 132 | 133 | #help tactic contrapose 134 | 135 | lemma add_square_ne_zero {a : Ri} (ha : a ≠ 0) : 136 | a.re ^ 2 + a.im ^ 2 ≠ 0 := by 137 | sorry 138 | done 139 | 140 | /-! 141 | Hint: there is a tactic that I have not yet mentioned, but that I found useful for proving this instance. 142 | The tactic is called `apply_fun` (see below for the documentation of the tactic). 143 | The way in which I used it, is to generate an equality between the real parts of two equal real numbers. 144 | The real numbers in question were equal "by contradiction" and `apply_fun` allowed me to exploit 145 | results about the real numbers to close a goal. 146 | -/ 147 | 148 | #help tactic apply_fun 149 | 150 | noncomputable 151 | instance : Field Ri where 152 | exists_pair_ne := by 153 | sorry 154 | done 155 | mul_inv_cancel a ha := by 156 | sorry 157 | done 158 | inv_zero := by 159 | sorry 160 | done 161 | nnqsmul := _ -- as usual, an implementation detail: hover for some info 162 | qsmul := _ -- as usual, an implementation detail: hover for some info 163 | 164 | end TPwL_Ri_easy_no_sols 165 | -------------------------------------------------------------------------------- /MA4N1/L17_navigating_Mathlib.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import Mathlib.Algebra.Order.Chebyshev 4 | import Mathlib.MeasureTheory.Integral.IntervalIntegral.FundThmCalculus 5 | 6 | namespace TPwL_navigating_Mathlib 7 | 8 | /-! 9 | # Finding lemmas in `Mathlib` 10 | 11 | This file covers some of the techniques that I use to find results in a part of `Mathlib` 12 | that I do not already know. 13 | 14 | It is a series of tips and tricks that may work in some situation! 15 | 16 | # Get familiar with `Mathlib`s naming convention! 17 | 18 | [Mathlib's naming convention](https://leanprover-community.github.io/contribute/naming.html) 19 | is *very* useful for finding what are the names of theorems. 20 | It takes a while to get used to it, but it is definitely worth it! 21 | 22 | The general principle is that the name should describe the *syntax* of the statement. 23 | 24 | To first approximation, ignore upper/lower-casing and imagine that every capital letter 25 | really stands for `_` followed by the corresponding lower-case letter. 26 | There is information encoded in the casing, but in my opinion, it is mostly a distraction 27 | than an added value. 28 | -/ 29 | 30 | --#check Nat.succ_eq_add_one 31 | --#check Continuous.add 32 | --#check Polynomial.natDegree_add_le 33 | 34 | /-! 35 | Also, the general rule is that if the statement has a LHS and a RHS, then the RHS should be 36 | simpler than the LHS. 37 | A common assumption is also that the RHS should be the "obvious simplification" of the RHS. 38 | -/ 39 | --#check true_and 40 | --#check false_and 41 | --#check map_add 42 | 43 | /-! 44 | Some parts of the name follow some convention. 45 | For instance, `of` usually introduces an assumption. 46 | -/ 47 | 48 | --#check lt_of_le_of_lt 49 | 50 | /-! 51 | # Use autocompletion (Ctrl-Space) to guess the name of a lemma 52 | 53 | If you have an idea of how the name of the lemma should start, type the beginning and then 54 | press `Ctrl-Space`. 55 | You will get an auto-complete dialog that will suggest how to fill in the rest of the name. 56 | It also gives useful type information for the statement, to fine-tune the search. 57 | 58 | After getting more familiar with the naming convention, this is my method of choice, 59 | and certainly the first attempt that I make at finding a lemma name. 60 | Honestly, the success rate of this method greatly increases with time, 61 | so it may not feel very efficient at the beginning, but it should improve! 62 | 63 | # Produce a *M*inimal *W*orking *E*xample first... 64 | 65 | Producing a [#mwe](https://leanprover-community.github.io/mwe.html) is a very good way to 66 | isolate what the issue is and removing unnecessary complications. 67 | 68 | Also, once you have a #mwe, you can easily share it with others and they will be able 69 | to see quickly what the problem is and may be able to help. 70 | Even better, the process of producing a #mwe often suggests to *you* what the next step should be! 71 | 72 | # ... and see if automation helps! 73 | 74 | And even if that is not the case, maybe `exact?` will prove the result for you? 75 | Or `simp`? `aesop`? `hint`? 76 | (`hint` requires a more recent version of `Mathlib` than the one on which this project is based. 77 | I might update `Mathlib` soon, but wanted to avoid doing it during the term, to avoid having 78 | to revisit the proofs that may brake with the update.) 79 | 80 | # `have` inside a proof and/or `extract_goal` 81 | 82 | If you have an idea of what the next step is, and it would require using several assumptions 83 | from the local context, then there are a couple of options. 84 | 85 | 1. You could add a `have new_hyp : := by sorry` and try to fill in the `sorry` 86 | by some combination of the above tactics. 87 | 2. You could use `extract_goal` to create a standalone goal with the current goal and 88 | its assumptions, to try to produce a #mwe and reduce to the above cases. 89 | 90 | # [Loogle](https://loogle.lean-lang.org/) 91 | 92 | Loogle is a search engine that allows you to look for lemmas in `Mathlib` using various filters. 93 | You can restrict to lemmas that contain 94 | * certain `Type`s, 95 | * certain names, 96 | * an implication, 97 | * and so on. 98 | 99 | You can also mix such conditions and there is an extensive syntax for specifying exactly 100 | what you are looking for. 101 | 102 | It takes some time getting used to, but it can be very powerful! 103 | 104 | For instance, say that we are looking for 105 | -/ 106 | #check sq_sum_le_card_mul_sum_sq 107 | /-! 108 | We might try 109 | `Finset.sum, _ ^ _, LE.le` 110 | `Finset.sum, _ ^ _, ⊢ _ ≤ _` 111 | 112 | and then maybe 113 | 114 | `Finset.sum, Finset.card, _ ^ _, LE.le` 115 | 116 | eventually refine to 117 | 118 | `(Finset.sum _ _) ^ 2`. 119 | 120 | # [Moogle](https://www.moogle.ai/) 121 | 122 | This is an AI-based search that takes "natural language" as input. 123 | Suppose that we are still trying to look for `sq_sum_le_card_mul_sum_sq`. 124 | We could try 125 | 126 | `the sum of the squares is less than or equal to the square of the sum`. 127 | 128 | # Look at the source code 129 | 130 | This is actually something that I do regularly. 131 | It is very easy to get to a reasonable place in the library using the "Go to definition" 132 | menu, or `Ctrl-click`. 133 | Once you are in a relevant file, scroll around for lemmas that look similar to the one 134 | that you want, if you can find them. 135 | Chances are, you will find inspiration to help you get unstuck! 136 | -/ 137 | 138 | -- What theorem is this? 139 | --#check Continuous.deriv_integral 140 | --#check intervalIntegral.fderiv_integral 141 | 142 | /-! 143 | # Global search inside the `.lean` files 144 | 145 | Since the naming convention takes into account the *syntax* of the statement, 146 | looking for "fundamental theorem of calculus" as a lemma name may not be productive. 147 | However, the documentation should include this information, at least for "well-known" 148 | results. 149 | In such cases, a global search among the `mathlib` files can be useful. 150 | 151 | Here is an example of what you can do with a Unix-like command-line. 152 | 153 | `grep "undamental.*calculus" $( find lake-packages/mathlib/ -name '*.lean' )` 154 | 155 | # Ask! 156 | 157 | If you have a reasonable #mwe, the above did not help, then feel free to ask me or 158 | ask a question on the `Mathlib` [Zulip chat](https://leanprover.zulipchat.com/). 159 | 160 | -/ 161 | 162 | end TPwL_navigating_Mathlib 163 | -------------------------------------------------------------------------------- /MA4N1/L08_Ri_hard.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import MA4N1.help_me 4 | 5 | namespace TPwL_Ri_hard 6 | 7 | /-! 8 | 9 | # Defining the complex numbers 10 | 11 | In this exercise sheet, you will prove that the complex numbers form a field. 12 | 13 | Some parts of the argument are tricky: I will give *very* few spoilers to begin with, 14 | so that you have a chance to try it out for yourself. 15 | However, if you want to have some hints, do skip ahead, since there are lots of pointers below! 16 | 17 | ## The setup 18 | 19 | We define our version of the complex numbers, and we call it `Ri` (i.e. `ℝ` with `i`). 20 | Thus, `Ri` is simply a pair of real numbers, like `point` from the lectures. 21 | -/ 22 | 23 | @[ext] -- notice the `ext` attribute: we've taken this onboard! 24 | structure Ri : Type where 25 | re : ℝ -- we suggestively called the two fields `re` and `im`, 26 | im : ℝ -- to convey our expectation. 27 | 28 | /-! 29 | We are going to define the "data" fields of a `Field` before we prove that `Ri` is a field. 30 | While this is not strictly necessary, it turns out to be a *huge* advantage to have the 31 | definitions before-hand: this is related to some of the spoilers for this file! 32 | 33 | In fact, we begin by showing that `Ri` is a `CommRing`, leaving the proof that it is a field for later. 34 | Thus, we define `Add`, `Mul`, `Zero`, `One` and `Neg`. 35 | -/ 36 | 37 | instance : Add Ri where add a b := ⟨a.re + b.re, a.im + b.im⟩ 38 | instance : Mul Ri where mul a b := ⟨a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re⟩ 39 | instance : Neg Ri where neg a := ⟨- a.re, -a.im⟩ 40 | instance : Zero Ri where zero := ⟨0, 0⟩ 41 | instance : One Ri where one := ⟨1, 0⟩ 42 | 43 | /-! 44 | _Warning._ 45 | Proving the instance below with just what appears above is going to be *very* hard! 46 | I still encourage you to try, so that you can see first-hand what it feels like to have 47 | some missing key API lemmas! 48 | 49 | Once you have had enough, try to think of what lemmas would be helpful. 50 | If nothing comes to mind, type `help_me!` for a hint! 51 | If you are still unsure, `help_all` will give you all the API lemmas. 52 | 53 | We are now (not) ready to prove that `Ri` is a `CommRing`. 54 | 55 | The "data" fields 56 | * `add := (· + ·)` 57 | * `mul := (· * ·)` 58 | * `neg := (- ·)` 59 | * `zero := 0` 60 | * `one := 1` 61 | 62 | have already been provided above, so we no longer need to give them here. 63 | -/ 64 | instance : CommRing Ri where 65 | add_assoc := by 66 | sorry 67 | done 68 | zero_add := by 69 | sorry 70 | done 71 | add_zero := by 72 | sorry 73 | done 74 | add_comm := by 75 | sorry 76 | done 77 | left_distrib := by 78 | sorry 79 | done 80 | right_distrib := by 81 | sorry 82 | done 83 | zero_mul := by 84 | sorry 85 | done 86 | mul_zero := by 87 | sorry 88 | done 89 | mul_assoc := by 90 | sorry 91 | done 92 | one_mul := by 93 | sorry 94 | done 95 | mul_one := by 96 | sorry 97 | done 98 | neg_add_cancel := by 99 | sorry 100 | done 101 | mul_comm := by 102 | sorry 103 | done 104 | nsmul := nsmulRec 105 | zsmul := zsmulRec 106 | 107 | /-! 108 | 109 | ## Dealing with inverses 110 | 111 | Now that we proved that `Ri` is a `CommRing`, let's conclude by showing that it is in fact a `Field`! 112 | 113 | As before, we provide the only remaining "data" field, `Inv`, separately. 114 | Notice that Lean wants us to label `Inv` as `noncomputable`. 115 | This is not a big deal: we want to prove theorems about `Ri`, not "computing" anything with it! 116 | (If you are curious, comment out `noncomputable` to see what Lean says: 117 | ultimately, the "noncomputability" is a consequence of the fact that the real numbers are non-computable. 118 | This only means that there is no algorithm to decide if two real numbers are equal or not -- hardly surprising!) 119 | -/ 120 | 121 | noncomputable 122 | instance : Inv Ri where 123 | inv a := ⟨a.re / (a.re ^ 2 + a.im ^ 2), - a.im / (a.re ^ 2 + a.im ^ 2)⟩ 124 | 125 | -- Hint: you may find this lemma useful! 126 | #check add_eq_zero_iff_of_nonneg 127 | 128 | lemma add_square_eq_zero {a b : ℝ} (ha : a ^ 2 + b ^ 2 = 0) : 129 | a = 0 ∧ b = 0 := by 130 | -- `rwa` means "try `assumption` after the rewrite" 131 | rwa [← sq_eq_zero_iff, ← sq_eq_zero_iff (a := b), ← add_eq_zero_iff_of_nonneg] <;> 132 | exact sq_nonneg _ 133 | done 134 | 135 | /-! 136 | Here you may find the tactic `contrapose!` useful! 137 | Read the documentation below to see what it does, in case it is not clear from the name! 138 | -/ 139 | 140 | #help tactic contrapose 141 | 142 | lemma add_square_ne_zero {a : Ri} (ha : a ≠ 0) : 143 | a.re ^ 2 + a.im ^ 2 ≠ 0 := by 144 | contrapose! ha 145 | ext 146 | · exact (add_square_eq_zero ha).left 147 | · exact (add_square_eq_zero ha).right 148 | done 149 | 150 | /-! 151 | Hint: there is a tactic that I have not yet mentioned, but that I found useful for proving this instance. 152 | The tactic is called `apply_fun` (see below for the documentation of the tactic). 153 | The way in which I used it, is to generate an equality between the real parts of two equal real numbers. 154 | The real numbers in question were equal "by contradiction" and `apply_fun` allowed me to exploit 155 | results about the real numbers to close a goal. 156 | -/ 157 | 158 | #help tactic apply_fun 159 | 160 | help_all 161 | 162 | noncomputable 163 | instance : Field Ri where 164 | exists_pair_ne := by 165 | use 0 166 | use 1 167 | intro h 168 | apply_fun Ri.re at h 169 | simp? at h says simp only [re_zero, re_one, zero_ne_one] at h 170 | done 171 | mul_inv_cancel a ha := by 172 | ext 173 | · simp? says simp only [re_mul, re_inv, im_inv, re_one] 174 | rw [← mul_div_assoc, ← mul_div_assoc, div_sub_div, div_eq_one_iff_eq] 175 | · ring 176 | all_goals 177 | · simp? says simp only [ne_eq, mul_eq_zero, or_self] 178 | exact add_square_ne_zero ha 179 | · simp 180 | rw [← mul_div_assoc, ← mul_div_assoc, div_add_div, div_eq_zero_iff] 181 | · ring_nf; simp 182 | all_goals exact add_square_ne_zero ha 183 | done 184 | inv_zero := by 185 | ext <;> simp 186 | done 187 | nnqsmul := _ -- as usual, an implementation detail: hover for some info 188 | qsmul := _ -- as usual, an implementation detail: hover for some info 189 | 190 | end TPwL_Ri_hard 191 | -------------------------------------------------------------------------------- /MA4N1/L05_graphs.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Combinatorics.SimpleGraph.Hasse 3 | import Mathlib.Combinatorics.SimpleGraph.Basic 4 | import Mathlib.Data.PNat.Prime 5 | 6 | namespace TPwL_graphs 7 | 8 | /- 9 | # Graphs in `Mathlib` 10 | 11 | In maths, you may have seen the definition of a graph as a pair `(V, E)` 12 | consisting of 13 | * a set a `V`, whose elements are called vertices; 14 | * a set `E ⊂ Sym^2 V` of unordered pairs of distinct vertices of `V`. 15 | 16 | There are various "flavours" or variations, where you may allow edges between 17 | a vertex and itself, or where you may allow multiple edges connecting the same 18 | pair of vertices. 19 | 20 | We will only look at the notion `SimpleGraph` from `Mathlib`, which corresponds 21 | to what are called simple, undirected, loopless graphs: no edges between a vertex 22 | and itself, at most one edge between any pair of vertices, edges are *un*ordered 23 | pairs. 24 | 25 | In `Mathlib`, all this data is encoded in the structure `SimpleGraph`, associated 26 | with a Type `V` and containing as fields 27 | * the relation `SimpleGraph.Adj` on `V` -- the adjacency relation, where two vertices 28 | are in relation if they correspond to adjacent vertices; 29 | * a proof that the relation is symmetric -- corresponding to the fact that our graphs 30 | are undirected; 31 | * a proof that the relation is irreflexive -- corresponding to the fact that our graphs 32 | are loopless. 33 | 34 | In case you have seen the notion of graph elsewhere, you may want to take some time 35 | to convince yourself that this version really is equivalent to the one with which 36 | you may be familiar! 37 | 38 | There is quite a bit of machinery developed to convert smoothly between edges 39 | simply represented as terms of `V` that are in relation and actual "concrete" 40 | unordered pairs. 41 | The relevant Type of unordered pairs is called `Sym2` and it has a fairly extensive 42 | API, allowing to go back and forth between vertices in relation and pairs. 43 | Also, `SimpleGraph.edgeSet` is... the edge set of a graph! 44 | It is a subset of `Sym2 V`. 45 | 46 | We still did not see this, but the Type of subsets of a given Type `V` is 47 | denoted by `Set V` is `Mathlib`. 48 | So, writing `U : Set V` means "`U` is a subset of `V`". 49 | 50 | The notation for an edge, viewed as an unordered pair is `s(a, b)`. 51 | The `s` is part of the syntax: this is a `s`ymmetric pair. 52 | In fact, separating `s` and `(` by a space, yields a syntax error. 53 | -/ 54 | 55 | -- Hover over these notions, to see the internal documentation. 56 | -- Go to definition if you want to see the implementation. 57 | #check SimpleGraph 58 | #check Sym2 59 | #check SimpleGraph.edgeSet 60 | 61 | /- 62 | `Mathlib` already has the definition of some graphs: 63 | -/ 64 | #check SimpleGraph.completeGraph 65 | #check SimpleGraph.emptyGraph 66 | #check completeBipartiteGraph 67 | #check SimpleGraph.pathGraph 68 | #check SimpleGraph.hasse -- graph associated to an order 69 | 70 | open SimpleGraph 71 | 72 | -- Hint: try `simp` and see if you can understand what is going on! 73 | lemma completeGraph_mem_edgeSet_of_ne (V : Type) {a b : V} (h : a ≠ b) : 74 | s(a, b) ∈ edgeSet (completeGraph V) := by 75 | simp [h] -- `simp` leaves `¬ a = b` as a goal 76 | done 77 | /- 78 | _Aside: `DefEq` and its abuse._ 79 | Just using `exact?` suggests `exact h`. 80 | While of course this works, it is probably considered a "bad" proof, 81 | or what is sometimes called "`DefEq` abuse". 82 | `DefEq` is `Def`initional `Eq`uality and it is a very very strict form of equality. 83 | Proving that `(a, b)` is an edge of the complete graph, since `a` is different from `b` 84 | is exploiting the actual implementation of the graph, rather than its properties. 85 | If someone decided to implement the notion of `completeGraph` with a 86 | different (e.g. non-`DefEq` by still equivalent) property, *ideally* the only proofs 87 | that should be rewritten are the ones of the "API" surrounding `completeGraph`. 88 | From this point of view, the `simp [h]` proof should still work, while the proof 89 | `exact h` is more brittle and likelier to no longer work. 90 | -/ 91 | 92 | example {V : Type} {G : SimpleGraph V} {a b : V} (h : s(a, b) ∈ G.edgeSet) : 93 | s(b, a) ∈ G.edgeSet := by 94 | apply G.symm -- `G.symm` is dot-notation: it stands for SimpleGraph.symm G 95 | exact h 96 | -- or, combining the two `exact G.symm h` 97 | done 98 | 99 | /-! 100 | We now define a new graph. 101 | -/ 102 | 103 | /-- 104 | the `divisibilityGraph`: this is a graph with vertices the natural numbers 105 | whose edges consist of pairs of distinct numbers one of which divides the other. 106 | -/ 107 | -- Most of this is auto-generated: if instead of `where` you type `:=[new_line]_`, 108 | -- wait for the lightbulb to appear and click on it, then you can auto-complete 109 | -- using `Generate a skeleton for the structure under construction`. 110 | def divisibilityGraph : SimpleGraph ℕ where 111 | -- if you notice, we use `Adj a b := ...` instead of `Adj := fun a b => ...` 112 | -- this is a convenient feature to improve readability, but both mean the same 113 | Adj a b := (a ≠ b) ∧ ((a ∣ b) ∨ (b ∣ a)) 114 | -- use `dsimp at *` to get a better view of the assumptions/goal 115 | symm a b h := by 116 | dsimp at * 117 | cases h with 118 | | intro left right => 119 | constructor 120 | · exact? 121 | · exact? 122 | done 123 | loopless a ha := by 124 | dsimp at ha 125 | cases ha with 126 | | intro left right => 127 | exact? 128 | done 129 | 130 | example : ¬ s(3, 5) ∈ divisibilityGraph.edgeSet := by 131 | simp 132 | unfold Adj 133 | unfold divisibilityGraph 134 | omega 135 | done 136 | 137 | example : s(3, 6) ∈ divisibilityGraph.edgeSet := by 138 | simp 139 | unfold Adj 140 | unfold divisibilityGraph 141 | omega 142 | done 143 | 144 | -- Try this for a potential challenge! 145 | example {p q : ℕ} (hp : p.Prime) (hq : q.Prime) : 146 | ¬ s(p, q) ∈ divisibilityGraph.edgeSet := by 147 | simp 148 | unfold Adj 149 | unfold divisibilityGraph 150 | simp 151 | intro pq 152 | constructor <;> rw [Nat.prime_dvd_prime_iff_eq] <;> try assumption 153 | exact? 154 | -- if you want a more verbose and clearer proof, this also works 155 | --constructor 156 | --· rw [Nat.prime_dvd_prime_iff_eq] <;> assumption 157 | --· rw [Nat.prime_dvd_prime_iff_eq] 158 | -- · exact? 159 | -- · assumption 160 | -- · assumption 161 | done 162 | 163 | end TPwL_graphs 164 | -------------------------------------------------------------------------------- /MA4N1/L11_autoImplicits.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import Mathlib.Combinatorics.SimpleGraph.Basic 4 | 5 | namespace TPwL_autoImplicits 6 | 7 | /-! 8 | # Setting `autoImplicit` true or false 9 | 10 | Lean's default set-up involves a feature called `autoImplicit`. 11 | This means that if you start typing code in a fresh `Lean` file, 12 | `Lean` will try hard to make sense of what you are doing, 13 | assuming *you* know exactly what you are doing! 14 | 15 | Click on this link for an quick explanation 16 | 17 | https://live.lean-lang.org/#code=--set_option%20autoImplicit%20false%0A--set_option%20relaxedAutoImplicit%20false%0A%0A%23check%20Nat.succ%0A%0Aexample%20%3A%20Nat.succ%20n%20%E2%89%A0%200%20%3A%3D%20by%0A%20%20sorry%0A%20%20done%0A%0Aexample%20%3A%20Nat.succ%20n_longName%20%E2%89%A0%200%20%3A%3D%20by%0A%20%20sorry%0A%20%20done%0A%0Adef%20mySucc%20(n%20%3A%20%E2%84%95)%20%3A%3D%20n%20%2B%201%0A%0A%23eval%20mySucc%203 18 | 19 | The conclusion is that 20 | * `set_option autoImplicit false` makes sure that `Lean` interprets as 21 | syntax errors all typos; 22 | * `set_option relaxedAutoImplicit false` makes sure that `Lean` interprets as 23 | syntax errors all typos, except the *one-letter ones*. 24 | 25 | My view on `autoImplicit`s is mixed: 26 | * on the one hand, it is convenient to not have to introduce each symbol, 27 | and it feels good automation when `Lean` "gets it"; 28 | * on the other hand, it is *very* easy to actually have typos when you are 29 | writing code and not receiving feedback about this, since `Lean` interpreted 30 | your typo as an implicitly defined variable can be annoying. 31 | 32 | For this reason, you may have noticed that, in most of the live-coding that happened 33 | in the lectures, I ended up writing `set_option autoImplicit false` near the beginning 34 | of the file. 35 | 36 | ## Change `autoImplicit` defaults 37 | 38 | Over the week-end, I have changed the default for this project: 39 | `autoImplicit`s are now disabled by default on `MA4N1`. 40 | 41 | *Warning.* 42 | `Lean` projects will likely have `autoImplicit`s on by default. 43 | 44 | In order to change the `autoImplicit` setting for a whole project, 45 | you should edit `lakefile.lean`. 46 | 47 | Find the part of the code beginning with 48 | 49 | `package «my_project» where [...]` 50 | 51 | or 52 | `package «my_project» := { [...]` 53 | 54 | and convert it to 55 | ```lean 56 | package «my_project» where 57 | -- add any package configuration options here 58 | moreServerOptions := #[⟨`autoImplicit, false⟩] 59 | ``` 60 | 61 | Notice that `set_option [whatever]` are *scoped*: 62 | if you only want to have some `autoImplicit` behaviour in some part of the code, 63 | but not another, this would work. 64 | -/ 65 | 66 | section autoImplicit_on 67 | 68 | set_option autoImplicit true 69 | 70 | -- useful `autoImplicit`: "gets" what `n` means 71 | example : Nat.succ n ≠ 0 := by 72 | exact? says exact Nat.succ_ne_zero n 73 | done 74 | 75 | -- useful `autoImplicit`: "gets" what `V` means 76 | variable (G1 : SimpleGraph V) 77 | 78 | #check G1 79 | #check G1.Adj 80 | 81 | -- unhelpful `autoImplicit`: misinterprets a typo 82 | variable (G2 : simpleGraph) 83 | 84 | -- and creates a weird environment with misleading errors. 85 | #check G2 86 | 87 | /-- 88 | error: Invalid field notation: Type is not of the form `C ...` where C is a constant 89 | G2 90 | has type 91 | simpleGraph 92 | -/ 93 | #guard_msgs in 94 | #check G2.Adj 95 | 96 | /-- 97 | error: Application type mismatch: The argument 98 | G2 99 | has type 100 | simpleGraph 101 | but is expected to have type 102 | SimpleGraph ?m.3 103 | in the application 104 | SimpleGraph.Adj G2 105 | --- 106 | info: sorry.Adj : ?m.3 → ?m.3 → Prop 107 | -/ 108 | #guard_msgs in 109 | #check SimpleGraph.Adj G2 110 | 111 | end autoImplicit_on 112 | 113 | section what_is_n? 114 | -- uncomment this `variable` to resolve the `unknown identifier 'n'` error below. 115 | --variable {n : ℕ} 116 | 117 | example : Nat.succ n ≠ 0 := by 118 | exact Nat.succ_ne_zero n 119 | done 120 | 121 | end what_is_n? 122 | 123 | /-! 124 | 125 | Another potential issue is capitalization. 126 | 127 | Lean is very much aware of capitalization! 128 | Names that differ only in their capitalization are considered *different* by Lean. 129 | 130 | In the old `Lean 3` days, the convention for almost every name was that they would 131 | be written in all lower-case letters and replacing spaces (` `) by underscores (`_`). 132 | 133 | Conventions changed in `Lean 4`, so now you see a big variation on upper/lower-case, 134 | spaces and underscores in declaration names. 135 | There are some [naming conventions](https://leanprover-community.github.io/contribute/naming.html), 136 | but the rules are not completely straightforward and the (deprecated) habit from `Lean 3` is hard 137 | to loose. 138 | 139 | -/ 140 | 141 | open scoped Pointwise 142 | 143 | lemma Nat.Prime.divisors_mul (n : ℕ) {p : ℕ} (hp : Nat.Prime p) : 144 | Nat.divisors (p * n) = Nat.divisors p * Nat.divisors n := by 145 | ext a 146 | rw [Finset.mem_mul] 147 | -- the `says` combinator is essentially just a way of making the code more readable 148 | -- and maintainable. For example `tac? says tac [...]` means: 149 | -- * we used the tactic `tac?` that produces some output like a `Try this`; 150 | -- * the output of `Try this` is `tac [...]`. 151 | -- This has several benefits: 152 | -- * the output of `tac?` may be very long and cluttered, but the call `tac?` 153 | -- itself may be very clear 154 | -- * the execution of `tac [...]` may be much faster than `tac` or `tac?` 155 | -- * if we change a lemma in the output of `tac?`, but `tac?` still works, 156 | -- we only have to re-run `tac?`, instead of having to find out what had 157 | -- generated `tac [...]`. 158 | simp? [dvd_mul, Nat.dvd_prime hp, hp.divisors] says 159 | simp only [Nat.mem_divisors, dvd_mul, Nat.dvd_prime hp, exists_and_left, exists_eq_or_imp, 160 | one_mul, exists_eq_right', exists_eq_left, ne_eq, mul_eq_zero, not_or, hp.divisors, 161 | Finset.mem_insert, Finset.mem_singleton, exists_eq_right] 162 | -- `aesop` can finish the proof from here 163 | -- below is a proof "by hand": let's see how we might discover it. 164 | constructor <;> intro h 165 | · rcases h with ⟨h | ⟨h, hh, rfl⟩, j⟩ 166 | · simp [h, j] 167 | · simp [hh, j] 168 | · rcases h with ⟨h, j⟩ | ⟨b, ⟨bn, n0⟩, rfl⟩ 169 | · simp [h, j] 170 | exact? says exact Nat.Prime.ne_zero hp 171 | · simp [bn, n0, Nat.Prime.ne_zero hp] 172 | done 173 | 174 | end TPwL_autoImplicits 175 | -------------------------------------------------------------------------------- /MA4N1/L15_setoids.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib--.Tactic 3 | 4 | namespace TPwL_setoids 5 | 6 | /-! 7 | # `Setoid`s and equivalence relations 8 | 9 | Many constructions in mathematics are obtained by "passing to the quotient". 10 | The "quotient" is usually an implicit way of referring to some equivalence relation 11 | that we are using to identify different elements of some set. 12 | 13 | The following `Setoid` is copied from `Core` Lean. 14 | -/ 15 | 16 | #check Setoid 17 | 18 | /-- 19 | A setoid is a type with a distinguished equivalence relation, denoted `≈`. 20 | This is mainly used as input to the `Quotient` type constructor. 21 | 22 | Note that I copied it here, but with a *lower-case* `S`! 23 | This is so that it does not mess up the in-built one that we use later. 24 | -/ 25 | class setoid (α : Sort*) where 26 | /-- `x ≈ y` is the distinguished equivalence relation of a setoid. -/ 27 | r : α → α → Prop 28 | /-- The relation `x ≈ y` is an equivalence relation. -/ 29 | iseqv : Equivalence r 30 | 31 | /-! 32 | 33 | Thus, a `Setoid` is simply 34 | * a Type `α`; 35 | * a relation called `Setoid.r` on `a`; 36 | * a proof `iseqv` that `r` is an equivalence relation. 37 | 38 | Notice that it is a `class`. 39 | This means that we can use the typeclass system to take over for us, when we want. 40 | 41 | This also means that we should have some idea of how to set things up correctly! 42 | 43 | Here is an easy example: 44 | every Type is a `Setoid` with respect to the identity equivalence relation. 45 | -/ 46 | 47 | def Setoid_eq (α : Type*) : Setoid α where 48 | r x y := x = y 49 | iseqv := by 50 | --exact? says exact eq_equivalence -- works 51 | constructor 52 | · exact? says exact fun x => rfl 53 | · exact? says exact fun {x y} a => id a.symm 54 | · intros x y z 55 | exact Eq.trans 56 | done 57 | 58 | section 59 | 60 | /-- A copy of the natural numbers, with none of the instances already registered. -/ 61 | def myN := ℕ 62 | 63 | variable {a b : myN} 64 | 65 | /- We record a `Setoid` instance on `myN`, determined by the equality relation. -/ 66 | instance Nat_setoid : Setoid myN := Setoid_eq ℕ 67 | 68 | /-! 69 | Having a Setoid gives us two convenient notations. 70 | 71 | First, the notation `a ≈ b` for `HasEquiv.Equiv`. 72 | 73 | This is a very primitive notion: simply a Type with a relation on it. 74 | The relation is called `Equiv`, but it is simply a relation. 75 | Of course, the most likely intended use-case is when `HasEquiv.Equiv` is an equivalence relation. 76 | -/ 77 | #check a ≈ b 78 | 79 | #print HasEquiv 80 | #check HasEquiv.Equiv 81 | 82 | -- A `HasEquiv` is just any relation, no extra requirement. 83 | example : HasEquiv Nat where 84 | Equiv a _b := a = 0 85 | 86 | /-! 87 | Second, the notation `⟦x⟧` for `Quotient s`, if `s : Setoid X` is available. 88 | -/ 89 | 90 | #check (⟦a⟧ : Quotient Nat_setoid) 91 | 92 | variable {α : Type} 93 | 94 | variable (r : α → α → Prop) in 95 | -- Good for a relation that is not required to be an equivalence relation 96 | #check Quot r 97 | #print Quot 98 | 99 | -- Better, when the relation is an equivalence relation 100 | -- Takes a `Setoid` an input. 101 | #check Quotient 102 | 103 | variable [s : Setoid α] in 104 | #check Quotient s 105 | 106 | example : a ≈ b ↔ Setoid.r a b := by 107 | exact? says exact Iff.rfl 108 | done 109 | 110 | example : (⟦a⟧ : Quotient Nat_setoid) = ⟦b⟧ ↔ a ≈ b := by 111 | exact? says exact Quotient.eq 112 | done 113 | 114 | #check Quotient.eq' 115 | #check Quot.sound 116 | 117 | instance parity_setoid : Setoid ℤ where 118 | r x y := 2 ∣ x - y 119 | iseqv := { 120 | refl := by 121 | intros 122 | exact? says exact Int.ModEq.dvd rfl 123 | symm := by 124 | intros x y 125 | exact dvd_sub_comm.mp 126 | done 127 | trans := by 128 | intros x y z 129 | convert dvd_add using 2 130 | · simp 131 | · infer_instance 132 | · infer_instance 133 | done 134 | } 135 | 136 | -- Let's record that `parity_setoid` satisfies `IsSymm`, which is the typeclass analogue of 137 | -- the `symm` field from above. 138 | instance : IsSymm ℤ parity_setoid where 139 | symm _a _b hab := Setoid.symm' parity_setoid hab 140 | 141 | -- `≃` is the symbol for an `Equiv 142 | #print Equiv 143 | 144 | /-! 145 | An `Equiv` is a structure with two input types `α` and `β` and 4 fields: 146 | * `toFun` -- a function `α → β`; 147 | * `invFun` -- a function `β → α`; 148 | * `left_inv` -- a proof that `invFun` is a left-inverse to `toFun`; 149 | * `right_inv` -- a proof that `invFun` is a right-inverse to `toFun`. 150 | -/ 151 | 152 | lemma equiv_class_of_zero (d : ℤ) : 153 | (⟦0⟧ : Quotient parity_setoid) = ⟦d⟧ ↔ 2 ∣ d := by 154 | simp only [Quotient.eq] 155 | conv_rhs => rw [← sub_zero d] 156 | exact comm 157 | done 158 | 159 | lemma equiv_class_of_one (d : ℤ) : 160 | (⟦1⟧ : Quotient parity_setoid) = ⟦d⟧ ↔ 2 ∣ d - 1 := by 161 | simpa only [Quotient.eq] using comm 162 | done 163 | 164 | -- This lemma is part of the Friday support class 165 | lemma two_dvd_sub_one_iff (d : ℤ) : 2 ∣ d - 1 ↔ ¬ 2 ∣ d := by 166 | sorry 167 | done 168 | 169 | /-! 170 | The next `example` is an excuse to highlight the use of `Quotient.lift` 171 | -/ 172 | 173 | #check Quotient.lift 174 | 175 | /-! 176 | `Quotient.lift` is the result that says that a function on the quotient "is the same" 177 | as a function on the original set that is constant on equivalence classes. 178 | 179 | In passing, note that the (data-carrying) fields `toFun` and `invFun` have a strong impact 180 | on how easy it is to prove the subsequent results. 181 | In particular, I stated the lemmas `equiv_class_of_zero, equiv_class_of_one` and 182 | `two_dvd_sub_one_iff` to get the proofs working. 183 | 184 | Note also how `decide_eq_decide` in `toFun` taints the proofs. 185 | -/ 186 | 187 | example : Quotient parity_setoid ≃ Bool where 188 | -- the function from the quotient to `Bool` is the `Quotient.lift` of the function 189 | -- "Does 2 divide `x`?". 190 | -- in maths, it is more common to say that the function "descends" to the quotient. 191 | toFun := Quotient.lift (fun x => 2 ∣ x) (fun x y xy => decide_eq_decide.mpr (Int.dvd_iff_dvd_of_dvd_sub xy)) 192 | -- the function from `Bool` to the quotient assigns `true` to `⟦0⟧` and `false` to `⟦1⟧` 193 | invFun b := if b then ⟦0⟧ else ⟦1⟧ 194 | left_inv := by 195 | rintro ⟨d⟩ 196 | dsimp only 197 | split_ifs with h 198 | · apply (equiv_class_of_zero d).mpr 199 | convert h 200 | simp 201 | · apply (equiv_class_of_one d).mpr 202 | apply (two_dvd_sub_one_iff d).mpr 203 | convert h 204 | simp 205 | done 206 | right_inv := by 207 | rintro (x|x) -- split into `true`/`false` 208 | · simp 209 | · simp 210 | done 211 | 212 | end 213 | 214 | end TPwL_setoids 215 | -------------------------------------------------------------------------------- /MA4N1/L01_polynomials.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_polynomials 5 | 6 | section Presentation 7 | /-! 8 | # Preliminaries 9 | 10 | The exercises cover several different notions: 11 | * `Polynomial` rings `R[X]`; 12 | * `natDegree`s of `Polynomial`s. 13 | 14 | I will certainly not have time to talk about all of the above, but you are of course more than 15 | welcome to explore on your own and to ask lots of questions! 16 | 17 | ## `Polynomial` 18 | 19 | The structure `Polynomial` takes a (Semi)`Ring` as input and returns... 20 | the `Mathlib` formalization of polynomials! 21 | -/ 22 | 23 | section Polynomials 24 | 25 | variable {R : Type*} [Semiring R] {r : R} 26 | 27 | #check Polynomial R 28 | #guard_msgs(drop error) in 29 | #check R[X] 30 | 31 | open Polynomial 32 | 33 | #check R[X] 34 | #guard_msgs(drop error) in 35 | #check R[Y] 36 | 37 | -- ## Basic constructors 38 | 39 | -- ### `C` -- the constants 40 | -- the extended name is `Polynomial.C` 41 | #check C r 42 | 43 | example {s : R} : C (r * s) = C r * C s := by 44 | exact? 45 | 46 | -- ### `X` -- the variable 47 | -- the extended name is `Polynomial.X` 48 | #check (X : R[X]) 49 | #check X 50 | 51 | -- ### `monomial` -- the... monomials 52 | -- we are not actually going to use them 53 | #check monomial 3 r 54 | 55 | example {n : ℕ} : C r * X ^ n = monomial n r := by 56 | exact? 57 | 58 | example : ((X + C 1) ^ 2 : R[X]) = X ^ 2 + 2 * X + 1 := by 59 | rw [sq, mul_add, add_mul, add_mul, ← sq, add_assoc, add_assoc] 60 | simp -- clears the `C`s 61 | congr 1 -- matches the common parts of the expressions 62 | rw [← add_assoc, two_mul] 63 | 64 | example : ((X + C r) ^ 2 : R[X]) = X ^ 2 + 2 * C r * X + C r ^ 2 := by 65 | rw [sq, mul_add, add_mul, add_mul, ← sq, add_assoc, add_assoc, X_mul_C] 66 | congr 1 -- matches the common parts of the expressions 67 | rw [← add_assoc, two_mul, ← add_mul, sq] 68 | 69 | variable {S} [CommSemiring S] in 70 | example : ((X + 1) ^ 2 : S[X]) = X ^ 2 + 2 * X + 1 := by 71 | ring 72 | 73 | variable {S} [CommSemiring S] in 74 | example : ((X + C 1) ^ 2 : S[X]) = X ^ 2 + C 2 * X + C 1 := by 75 | simp? 76 | ring 77 | congr 78 | 79 | #check natDegree 80 | 81 | -- Lean may not always have enough information to fill in typeclass arguments 82 | #guard_msgs(drop error) in 83 | example : natDegree 1 = 0 := by 84 | exact? 85 | 86 | #guard_msgs(drop error) in 87 | example : natDegree (C r * X + C 1) = 1 := by 88 | exact? -- we are missing a hypothesis! 89 | 90 | -- prove using `natDegree_add_eq_left_of_natDegree_lt` 91 | example [Nontrivial R] : natDegree (X + C 1) = 1 := by 92 | rw [natDegree_add_eq_left_of_natDegree_lt] 93 | exact? 94 | simp? 95 | 96 | -- One thing that could be useful for some of the exercises. 97 | -- The evaluation of polynomials in `R[X]` at a fixed polynomial `p` is a ring homomorphism 98 | -- `R[X] →+* R[X]`. 99 | -- This is called `Polynomial.aeval` in `Mathlib`. 100 | 101 | noncomputable 102 | example {R} [CommRing R] (p : R[X]) : R[X] →+* R[X] := 103 | (aeval p).toRingHom 104 | 105 | /- 106 | ### Pitfall: disappearing `C`s 107 | 108 | The exact shape of a lemma in `Mathlib` is what makes it applicable or not in any given situation. 109 | On the one hand, not all combinations of lemmas with/without `C` in statements about `Polynomial`s 110 | are available. 111 | On the other hand, `simp` will try to remove `C`s in your expressions, if it can. 112 | This means that `exact?` might have found a lemma *before* applying `simp` and may fail afterwards: 113 | -/ 114 | example [Nontrivial R] : natDegree (X + C 1) = 1 := by 115 | --simp --uncomment this `simp` and `exact?` fails 116 | exact? 117 | 118 | end Polynomials 119 | 120 | end Presentation 121 | 122 | section Exercises 123 | /- 124 | # Exercises 125 | -/ 126 | 127 | open Polynomial 128 | 129 | variable {R : Type*} [CommRing R] 130 | /-! 131 | Polynomials in Mathlib are denoted by the familiar notation `R[X]`. 132 | This notation is available because of the line `open Polynomial` just inside this section. 133 | Without `open Polynomial`, the notation is `Polynomial R`. 134 | 135 | Note that the `R` in `R[X]` is a `CommRing` and you can replace it by whatever (Semi)ring you want. 136 | The `[X]` part is hard-coded: it instructs Lean to consider polynomials in one variable over `R`. 137 | For instance, `#check R[Y]` yields an `unknown identifier 'Y'` error. 138 | 139 | Of course, the name of the variable in `R[X]` is `X`, so the notation is internally consistent, 140 | but you do not get the option of changing it, at least not easily! 141 | 142 | Also, the "obvious" inclusion `R ↪ R[X]` is denoted by `C` (for `C`onstants). 143 | The full name is `Polynomial.C`, but we are inside `open Polynomial`, so `C` suffices. 144 | 145 | Thus, `X ^ 3 + C 3 * X - C 2` represents the polynomial that you might write in TeX as 146 | $x ^ 3 + 3 x - 2$. 147 | -/ 148 | 149 | -- The following exercises get you familiar with `natDegree`s of polynomials. 150 | section natDegree 151 | 152 | example : natDegree (X + 1 : ℤ[X]) = 1 := by 153 | rw [natDegree_add_eq_left_of_natDegree_lt] <;> 154 | simp 155 | 156 | example : natDegree (C 0 * X ^ 2 + C 3 * X : ℤ[X]) = 1 := by 157 | rw [natDegree_add_eq_right_of_natDegree_lt] 158 | · apply natDegree_C_mul_X 159 | norm_num 160 | · simp 161 | 162 | example (h2 : (2 : R) = 0) (h3 : (3 : R) = 0) : (0 : R) = 1 := by 163 | have : (3 : R) - 2 = 1 := by norm_num 164 | rw [← this, h2, h3, sub_zero] 165 | 166 | lemma aux [Nontrivial R] (h2 : (2 : R) ≠ 0) : 167 | natDegree (C 4 * X ^ 2 : R[X]) < natDegree (C 2 * X ^ 3 : R[X]) := by 168 | rw [natDegree_C_mul_X_pow 3] 169 | · refine (natDegree_C_mul_X_pow_le (4 : R) 2).trans_lt ?_ 170 | norm_num 171 | · assumption 172 | 173 | /-- Proof without automation -- I had prepared this before tactic `compute_degree` was merged. -/ 174 | example : natDegree (C 2 * X ^ 3 + C 4 * X ^ 2 + 1 : R[X]) ∈ ({0, 3} : Set ℕ) := by 175 | nontriviality R 176 | by_cases h2 : (2 : R) = 0 177 | · have h22 : (4 : R) = 2 * 2 := by norm_num 178 | simp [h22, h2] 179 | · simp only [Set.mem_singleton_iff, Set.mem_insert_iff] 180 | right 181 | rwa [natDegree_add_eq_left_of_natDegree_lt, natDegree_add_eq_left_of_natDegree_lt, 182 | natDegree_C_mul_X_pow] 183 | · exact aux h2 184 | · rw [natDegree_add_eq_left_of_natDegree_lt, natDegree_C_mul_X_pow] 185 | · simp only [natDegree_one, zero_lt_three] 186 | · assumption 187 | · exact aux h2 188 | 189 | /-- Proof with more automation -- works now that `compute_degree` is merged. -/ 190 | example : natDegree (C 2 * X ^ 3 + C 4 * X ^ 2 + 1 : R[X]) ∈ ({0, 3} : Set ℕ) := by 191 | nontriviality R 192 | by_cases h2 : (2 : R) = 0 193 | · have h22 : (4 : R) = 2 * 2 := by norm_num 194 | simp [h22, h2] 195 | · simp only [Set.mem_singleton_iff, Set.mem_insert_iff] 196 | right 197 | compute_degree! 198 | 199 | end natDegree 200 | 201 | end Exercises 202 | 203 | end TPwL_polynomials 204 | -------------------------------------------------------------------------------- /MA4N1/L14_in_implicit_explicit.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_in_implicit_explicit 5 | 6 | /-! 7 | # The `in` modifier 8 | 9 | Several commands in Lean can be followed by an `in`. 10 | From a strictly mathematical point of view, this is just a convenience and can be ignored. 11 | However, as we are effectively writing code to check a mathematical proof, 12 | it can be useful to know about this, to make the code sturdier and cleaner. 13 | 14 | ## Scope 15 | 16 | Many commands in Lean modify the environment. 17 | For instance, `variable (n : ℕ)` introduces a generic natural number `n` in *all subsequent* 18 | `lemma`s/`theorem`s/`example`s. 19 | 20 | *All subsequent* really means "all until it doesn't anymore"! 21 | This brings us to the concept of "scope". 22 | There are various ways of influencing the scope of a command, but typically, any command 23 | contained within a `section Title [...] end Title` block is "in scope" until the following `end` 24 | and "out of scope" before being declared and after the following `end`. 25 | -/ 26 | 27 | section There_is_n_here 28 | 29 | -- `n` has not yet been declared 30 | /-- error: Unknown identifier `n` -/ 31 | #guard_msgs in 32 | #check n -- unknown identifier 'n' 33 | 34 | variable (n : ℕ) 35 | 36 | -- `n` has been declared and is in scope 37 | #check n -- `n : ℕ` 38 | 39 | end There_is_n_here 40 | 41 | -- `n` has been declared, but is out of scope 42 | /-- error: Unknown identifier `n` -/ 43 | #guard_msgs in 44 | #check n -- unknown identifier 'n' 45 | 46 | /- 47 | 48 | Other examples of commands to which the previous discussion applies are 49 | * `open` (to open a `namespace/notation`); 50 | * `open scoped` (to specifically open a `notation`, but not also the corresponding `namespace`); 51 | * `set_option [option] true/false` (we saw the `set_option autoImplicit false`). 52 | 53 | There is a command that helps us figure out what is in scope at any given location and what is not. 54 | 55 | ## `#where` 56 | 57 | The `#where` command tells you 58 | * what `namespace`s are `open`, 59 | * what `variable`s are declared, 60 | * what `option`s have been set, 61 | 62 | and so on. 63 | -/ 64 | variable {R : Type*} [Ring R] (r : R) 65 | 66 | #where 67 | 68 | /- 69 | Sometimes, you would like to limit the effect of one a command just to the following declaration. 70 | 71 | For instance, you may be proving lemmas about one `Ring` and then you have one extra result to involves two. 72 | Rather than having the second `Ring` polluting your environment, you can do this. 73 | -/ 74 | 75 | example : 2 • r = r + r := by 76 | exact two_nsmul r 77 | done 78 | 79 | #where 80 | 81 | variable {S : Type*} [Ring S] (f : R →+* S) in -- <--- note the `in` here! 82 | example : f 0 = 0 := by 83 | exact RingHom.map_zero f 84 | done 85 | 86 | #where 87 | 88 | /- 89 | 90 | # Implicit vs explicit assumption in `lemma/theorem` 91 | 92 | When stating a `lemma/theorem`, you have the option of declaring assumptions as implicit or explicit. 93 | 94 | The proof of the corresponding result is completely oblivious to this choice. 95 | However, when you then want to apply the result, the choice that you made will be *very* important. 96 | 97 | Here is an example. 98 | -/ 99 | 100 | -- it does not show, but the choice is `a` implicit, `a ≠ 0` explicit. 101 | #print div_self 102 | 103 | -- the following two lemmas differ in their name and whether `a` is explicit or not. 104 | lemma div_ee (a : ℚ) (ha : a ≠ 0) : a / a = 1 := div_self ha 105 | lemma div_ie {a : ℚ} (ha : a ≠ 0) : a / a = 1 := div_self ha 106 | 107 | example (a b : ℚ) (ha : a ≠ 0) (hb : b ≠ 0) : a / a = b / b := by 108 | -- two explicit 109 | -- fails 110 | --apply (div_ee ?_ ?_).trans 111 | 112 | -- works 113 | --apply (div_ee _ ?_).trans 114 | 115 | -- works 116 | --apply (div_ee a ?_).trans 117 | 118 | -- works 119 | --refine (div_ee ?_ ?_).trans ?_ 120 | 121 | -- one implicit, one explicit 122 | -- works 123 | --apply (div_ie ?_).trans 124 | 125 | -- works 126 | -- refine (div_ie ?_).trans ?_ 127 | 128 | refine (div_ie ?_).trans (div_ie ?_).symm <;> 129 | assumption 130 | done 131 | 132 | /-! 133 | 134 | In the proofs above we used some notation that you may not have seen before. 135 | When applying a previous result, you may want to 136 | * pass it hypotheses that you have in context -- 137 | the `a` in `div_ee a ?_`; 138 | * create goals for some hypotheses -- 139 | the `?_` in `div_ee a ?_`; 140 | * ask Lean to fill those positions automatically -- 141 | the `_` in `div_ee _ ?_`. 142 | 143 | These variously annotated underscores are often referred to as *holes*. 144 | Some tactic allow not to mention holes, like `apply`. 145 | -/ 146 | 147 | example (a : ℚ) (ha : a ≠ 0) : a / a = 1 := by 148 | apply div_self -- also `apply div_self _` works 149 | assumption 150 | done 151 | 152 | /-! 153 | Other tactics require all holes to be explicitly assigned. 154 | -/ 155 | 156 | example (a : ℚ) (ha : a ≠ 0) : a / a = 1 := by 157 | -- fails 158 | --refine div_ee 159 | 160 | -- fails 161 | --refine div_ee _ 162 | 163 | -- fails 164 | --refine div_ee _ _ 165 | 166 | -- works 167 | --refine div_ee _ ?_ 168 | 169 | -- works 170 | --refine div_ee ?_ ?_ 171 | 172 | -- fails 173 | --refine div_ie 174 | 175 | -- works 176 | --refine div_ie ?_ ; assumption 177 | 178 | -- works 179 | refine div_self ha 180 | done 181 | 182 | /-! 183 | 184 | ## How to choose between implicit vs explicit? 185 | 186 | There are not universal rules. 187 | Also, the worst that can happen is that when you apply the lemma, you will have to 188 | * either fill in lots of underscores for arguments that Lean could have figured out; 189 | * or pass explicitly arguments that were marked as implicit using, for instance, 190 | `apply div_ie (a := 3)`. 191 | 192 | It will not prevent you from proving what you want, 193 | it may just make the process of formalization a little less automatic. 194 | 195 | However, this is a principle that often applies. 196 | 197 | *If a hypothesis/variable can be deduce from a later hypothesis, chances are that making it implicit is a good choice.* 198 | 199 | In our example of `div_ee` vs `div_ie`, the hypotheses are 200 | * `a : ℚ`; 201 | * `ha : a ≠ 0`. 202 | 203 | If, in whatever way, Lean figures out what `ha` should be, 204 | it will have already figured out what `a` had to be. 205 | Since eventually we need to pass enough information to Lean to fill in all arguments, 206 | we may not need to pass it `a`, as `ha` will imply it. 207 | 208 | Thus, *minimalistically*, we can hope that marking `a` as implicit is a good choice. 209 | 210 | This is what `Mathlib` does in this case. 211 | 212 | Lean also uses the *goal* to try to infer arguments, so an argument that can be reconstructed by looking at the goal 213 | can in principle also be left implicit. 214 | -/ 215 | 216 | #check add_comm 217 | #check eq_div_iff_mul_eq' 218 | 219 | /- 220 | There could be subtle reasons why you may want to overrule this principle. 221 | -/ 222 | 223 | #check add_zero 224 | 225 | end TPwL_in_implicit_explicit 226 | -------------------------------------------------------------------------------- /MA4N1/L08_Ri_easy.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Batteries.Tactic.Lemma 3 | import Mathlib.Tactic 4 | import MA4N1.help_me 5 | 6 | namespace TPwL_Ri_easy 7 | 8 | /-! 9 | 10 | # Defining the complex numbers 11 | 12 | In this exercise sheet, you will prove that the complex numbers form a field. 13 | 14 | Some parts of the argument are tricky: I will give *very* few spoilers to begin with, 15 | so that you have a chance to try it out for yourself. 16 | However, if you want to have some hints, do skip ahead, since there are lots of pointers below! 17 | 18 | ## The setup 19 | 20 | We define our version of the complex numbers, and we call it `Ri` (i.e. `ℝ` with `i`). 21 | Thus, `Ri` is simply a pair of real numbers, like `point` from the lectures. 22 | -/ 23 | 24 | @[ext] -- notice the `ext` attribute: we've taken this onboard! 25 | structure Ri : Type where 26 | re : ℝ -- we suggestively called the two fields `re` and `im`, 27 | im : ℝ -- to convey our expectation. 28 | 29 | /-! 30 | We are going to define the "data" fields of a `Field` before we prove that `Ri` is a field. 31 | While this is not strictly necessary, it turns out to be a *huge* advantage to have the 32 | definitions before-hand: this is related to some of the spoilers for this file! 33 | 34 | In fact, we begin by showing that `Ri` is a `CommRing`, leaving the proof that it is a field for later. 35 | Thus, we define `Add`, `Mul`, `Zero`, `One` and `Neg`. 36 | -/ 37 | 38 | instance : Add Ri where add a b := ⟨a.re + b.re, a.im + b.im⟩ 39 | instance : Mul Ri where mul a b := ⟨a.re * b.re - a.im * b.im, a.re * b.im + a.im * b.re⟩ 40 | instance : Neg Ri where neg a := ⟨- a.re, -a.im⟩ 41 | instance : Zero Ri where zero := ⟨0, 0⟩ 42 | instance : One Ri where one := ⟨1, 0⟩ 43 | 44 | /-! 45 | The following 10 lemmas have virtually no mathematical context and their proof is `rfl`: 46 | this is Lean's way of saying that they follow from the definitions in a very strong sense. 47 | 48 | However, stating them and giving them the `simp` tag means that Lean will apply them 49 | whenever we call `simp`: if you tried proving that `Ri` is a `CommRing` without seeing these 50 | lemmas, you will notice a great difference before and after! 51 | -/ 52 | @[simp] lemma re_add {a b : Ri} : (a + b).re = a.re + b.re := rfl 53 | @[simp] lemma im_add {a b : Ri} : (a + b).im = a.im + b.im := rfl 54 | 55 | @[simp] lemma re_mul {a b : Ri} : (a * b).re = a.re * b.re - a.im * b.im := rfl 56 | @[simp] lemma im_mul {a b : Ri} : (a * b).im = a.re * b.im + a.im * b.re := rfl 57 | 58 | @[simp] lemma re_neg {a : Ri} : (- a).re = - a.re := rfl 59 | @[simp] lemma im_neg {a : Ri} : (- a).im = - a.im := rfl 60 | 61 | @[simp] lemma re_zero : (0 : Ri).re = 0 := rfl 62 | @[simp] lemma im_zero : (0 : Ri).im = 0 := rfl 63 | 64 | @[simp] lemma re_one : (1 : Ri).re = 1 := rfl 65 | @[simp] lemma im_one : (1 : Ri).im = 0 := rfl 66 | 67 | /-! 68 | The following command is a way of defining a new tactic. 69 | This is a completely straightforward one that chains together some other tactics. 70 | -/ 71 | 72 | /-- 73 | `solve` does `intros` followed by `ext`. 74 | After that, it calls `simp` and `ring` on all goals, 75 | but only if the combination of the two closes all remaining goals. 76 | It turns out that this simple-minded tactic solves all the `Prop`-fields 77 | of `CommRing Ri`. 78 | -/ 79 | macro "solve" : tactic => `(tactic| intros <;> ext <;> try ( simp <;> ring ; done )) 80 | 81 | /-! 82 | We are now ready to prove that `Ri` is a `CommRing`. 83 | 84 | The "data" fields 85 | * `add := (· + ·)` 86 | * `mul := (· * ·)` 87 | * `neg := (- ·)` 88 | * `zero := 0` 89 | * `one := 1` 90 | 91 | have already been provided above, so we no longer need to give them here. 92 | -/ 93 | instance : CommRing Ri where 94 | add_assoc := by solve 95 | zero_add := by solve 96 | add_zero := by solve 97 | add_comm := by solve 98 | left_distrib := by solve 99 | right_distrib := by solve 100 | zero_mul := by solve 101 | mul_zero := by solve 102 | mul_assoc := by solve 103 | one_mul := by solve 104 | mul_one := by solve 105 | neg_add_cancel := by solve 106 | mul_comm := by solve 107 | nsmul := nsmulRec 108 | zsmul := zsmulRec 109 | 110 | /-! 111 | 112 | ## Dealing with inverses 113 | 114 | Now that we proved that `Ri` is a `CommRing`, let's conclude by showing that it is in fact a `Field`! 115 | 116 | As before, we provide the only remaining "data" field, `Inv`, separately. 117 | Notice that Lean wants us to label `Inv` as `noncomputable`. 118 | This is not a big deal: we want to prove theorems about `Ri`, not "computing" anything with it! 119 | (If you are curious, comment out `noncomputable` to see what Lean says: 120 | ultimately, the "noncomputability" is a consequence of the fact that the real numbers are non-computable. 121 | This only means that there is no algorithm to decide if two real numbers are equal or not -- hardly surprising!) 122 | -/ 123 | 124 | noncomputable 125 | instance : Inv Ri where 126 | inv a := ⟨a.re / (a.re ^ 2 + a.im ^ 2), - a.im / (a.re ^ 2 + a.im ^ 2)⟩ 127 | 128 | /-! 129 | We learned our lesson: let's prove our silly `rfl` lemmas about `Inv.inv`. 130 | -/ 131 | 132 | @[simp] lemma re_inv {a : Ri} : (a⁻¹).re = a.re / (a.re ^ 2 + a.im ^ 2) := rfl 133 | @[simp] lemma im_inv {a : Ri} : (a⁻¹).im = - a.im / (a.re ^ 2 + a.im ^ 2) := rfl 134 | 135 | -- Hint: you may find this lemma useful! 136 | #check add_eq_zero_iff_of_nonneg 137 | 138 | lemma add_square_eq_zero {a b : ℝ} (ha : a ^ 2 + b ^ 2 = 0) : 139 | a = 0 ∧ b = 0 := by 140 | -- `rwa` means "try `assumption` after the rewrite" 141 | rwa [← sq_eq_zero_iff, ← sq_eq_zero_iff (a := b), ← add_eq_zero_iff_of_nonneg] <;> 142 | exact sq_nonneg _ 143 | done 144 | 145 | /-! 146 | Here you may find the tactic `contrapose!` useful! 147 | Read the documentation below to see what it does, in case it is not clear from the name! 148 | -/ 149 | 150 | #help tactic contrapose 151 | 152 | lemma add_square_ne_zero {a : Ri} (ha : a ≠ 0) : 153 | a.re ^ 2 + a.im ^ 2 ≠ 0 := by 154 | contrapose! ha 155 | ext 156 | · exact (add_square_eq_zero ha).left 157 | · exact (add_square_eq_zero ha).right 158 | done 159 | 160 | /-! 161 | Hint: there is a tactic that I have not yet mentioned, but that I found useful for proving this instance. 162 | The tactic is called `apply_fun` (see below for the documentation of the tactic). 163 | The way in which I used it, is to generate an equality between the real parts of two equal real numbers. 164 | The real numbers in question were equal "by contradiction" and `apply_fun` allowed me to exploit 165 | results about the real numbers to close a goal. 166 | -/ 167 | 168 | #help tactic apply_fun 169 | 170 | noncomputable 171 | instance : Field Ri where 172 | exists_pair_ne := by 173 | use 0 174 | use 1 175 | intro h 176 | apply_fun Ri.re at h 177 | simp? at h says simp only [re_zero, re_one, zero_ne_one] at h 178 | done 179 | mul_inv_cancel a ha := by 180 | ext 181 | · simp? says simp only [re_mul, re_inv, im_inv, re_one] 182 | rw [← mul_div_assoc, ← mul_div_assoc, div_sub_div, div_eq_one_iff_eq] 183 | · ring 184 | all_goals 185 | · simp? says simp only [ne_eq, mul_eq_zero, or_self] 186 | exact add_square_ne_zero ha 187 | · simp 188 | rw [← mul_div_assoc, ← mul_div_assoc, div_add_div, div_eq_zero_iff] 189 | · ring_nf; simp 190 | all_goals exact add_square_ne_zero ha 191 | done 192 | inv_zero := by solve 193 | nnqsmul := _ -- as usual, an implementation detail: hover for some info 194 | qsmul := _ -- as usual, an implementation detail: hover for some info 195 | 196 | end TPwL_Ri_easy 197 | -------------------------------------------------------------------------------- /MA4N1/Pisa/nilpotent_exercises_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import Mathlib.LinearAlgebra.Matrix.Charpoly.Coeff 4 | 5 | open Matrix 6 | 7 | /-! 8 | # Esercizi sulle matrici nilpotenti 9 | 10 | In questo file, suggerisce una dimostrazione del risultato seguente: 11 | 12 | Se la matrice `M` è nilpotente, allora tutti i coefficienti del polinomio 13 | `det (1 - M * X)` sono nilpotenti, tranne il coefficiente di `X ^ 0`. 14 | 15 | In particolare, la traccia della matrice `M` è nilpotente. 16 | -/ 17 | 18 | variable {R n : Type*} [CommRing R] [DecidableEq n] [Fintype n] 19 | 20 | open Matrix in 21 | example (M : Matrix n n R) {N : ℕ} (hM : M ^ N = 0) {n : ℕ} (hn : n ≠ 0) : 22 | IsNilpotent ((charpolyRev M).coeff n) := by 23 | sorry 24 | done 25 | 26 | /-! 27 | # Preliminari 28 | 29 | Iniziamo con qualche lemma semplice, per imparare un po' come interagire con matrici, 30 | determinanti e altre cose. 31 | 32 | ### Alcune identità in anelli 33 | 34 | `Mathlib` contiene già molte identità di base tra elementi di anelli. 35 | 36 | Cominciamo con una identità facile. 37 | -/ 38 | example (x y : R) (n : ℕ) : x - y ∣ x ^ n - y ^ n := by 39 | sorry 40 | done 41 | 42 | /-! 43 | Tuttavia, il risultato qui sopra non è quello che vogliamo! 44 | 45 | Nella nostra applicazione, l'anello `R` sarà realmente l'anello delle matrici a coefficienti in `R`. 46 | Il risultato di prima non lo possiamo usare, perché assume che `R` è commutativo. 47 | 48 | La versione non-commutativa, in cui assumiamo che `x` e `y` commutano è anche già in `Mathlib`. 49 | 50 | In questo esempio, `x` e `y` sono elementi di una anello qualsiasi 51 | -/ 52 | example {A : Type*} [Ring A] {x y : A} (h : Commute x y) (n : ℕ) : 53 | x - y ∣ x ^ n - y ^ n := by 54 | sorry 55 | done 56 | 57 | /-! 58 | ### Matrici 59 | 60 | Fate attenzione alle coercizioni (`↑`) (coercions) che facciamo inserire a Lean con le 61 | ascrizioni (type-ascriptions) esplicite. 62 | -/ 63 | example (a b : ℕ) : ((a * b : ℕ) : Matrix n n R) = (a * b) := by 64 | sorry 65 | done 66 | 67 | /-! Le matrici sono costruite a partire dalle funzioni con due argomenti. 68 | La tattica `ext` può essere utile per dimostrare il risultato seguente. 69 | -/ 70 | example : (fun i j => if i = j then 1 else 0 : Matrix (Fin 2) (Fin 2) R) = 71 | (1 : Matrix (Fin 2) (Fin 2) R) := by 72 | sorry 73 | done 74 | /-! 75 | _Nota._ 76 | Questa non è la maniera più diretta di definire la matrice identità: nell'esercizio 77 | successivo vediamo una maniera migliore. 78 | -/ 79 | example : !![1, 0; 0, 1] = 1 := by 80 | sorry 81 | done 82 | 83 | /-! 84 | ### Determinanti 85 | 86 | Nella nostra dimostrazione, usiamo anche qualche determinante. 87 | 88 | Il determinante di una matrice quadrata è `Matrix.det` 89 | (poiché abbiamo scritto `open Matrix` più sopra, possiamo riferirci con `det` a `Matrix.det`). 90 | 91 | Come ci possiamo aspettare, `det` è una funzione che associa a una matrice a coefficienti in `R` 92 | un elemento di `R`. 93 | -/ 94 | #check det 95 | 96 | example : det (1 : Matrix n n R) = 1 := by 97 | sorry 98 | done 99 | 100 | /-! 101 | Per il prossimo esempio, può essere utile usare `det_smul`. 102 | -/ 103 | #check det_smul 104 | 105 | example : det (2 : Matrix n n R) = 2 ^ (Fintype.card n) := by 106 | sorry 107 | done 108 | 109 | /-! 110 | ### Unità 111 | 112 | Non useremo quasi nulla sulle unità, ma appariranno nell'equivalenza 113 | 114 | `i coefficienti non-costanti di un polinomio sono nilpotenti ↔ il polinomio è invertibile` 115 | 116 | Il comando `#print` qui sotto non mostra un'informazione importante, che però possiamo 117 | ottenere mettendo il cursore sulla `u` in `∃ u` nell'infoview. 118 | -/ 119 | #print IsUnit 120 | /-! 121 | Il tipo di `u` è `Mˣ = Units M`, il tipo delle unità di `M`. 122 | -/ 123 | #print Units 124 | 125 | /-! 126 | Quindi, `IsUnit` è semplicemente il predicato su `R` che decide se un elemento `a` ammette o meno 127 | un inverso destro e sinistro; ovvero se `a` "è" o meno una unità. 128 | -/ 129 | 130 | /-- In un anello, un elemento che divide `1` è una unità. -/ 131 | example {a : R} (h : a ∣ 1) : IsUnit a := by 132 | sorry 133 | done 134 | 135 | /-! 136 | Assume that `M ^ N = 0`. 137 | 138 | Start with the identities 139 | 140 | `I = I - (tM) ^ (N + 1)` 141 | ` = (I - tM)(I + tM + ... + (tM) ^ N)`. 142 | 143 | Compute determinants on both sides and use that the determinant of a product is the product of the determinants. 144 | 145 | Deduce that the determinant of `(I - tM)` is an invertible polynomial. 146 | Therefore all its coefficients of positive degree are nilpotents. 147 | 148 | The rest of this file develops the tools that should allow you to formalize the above proof 149 | in the following hour! 150 | -/ 151 | 152 | section CommRing 153 | 154 | variable {R : Type*} [CommRing R] {n : Type*} [DecidableEq n] [Fintype n] 155 | 156 | open Polynomial 157 | 158 | recall Matrix.charpolyRev (M : Matrix n n R) := det (1 - (X : R[X]) • M.map C) 159 | 160 | namespace Matrix 161 | 162 | variable (M : Matrix n n R) 163 | 164 | -- why did I not find this lemma already? 165 | theorem map_pow (N : ℕ) : (M ^ N).map C = M.map C ^ N := by 166 | sorry 167 | done 168 | 169 | /-! 170 | Per la dimostrazione di `isUnit_charpolyRev` ho usato (tra le altre) le due tattiche 171 | `apply_fun` e `conv`. 172 | 173 | `apply_fun f` è utile per applicare la stessa funzione `f` ai due lati dell'uguaglianza nel 174 | risultato principale. 175 | Funziona anche con `apply_fun at h`, quando vogliamo applicare `f` ai due lati dell'ipotesi `h`. 176 | La tattica cerca (un po') di trovare l'ipotesi che `f` è iniettiva. 177 | Se non riesce a dimostrare che `f` è iniettiva, produce anche un side-goal `⊢ f.Injective`. 178 | 179 | Qui sotto potete leggere la documentazione di `apply_fun`. 180 | -/ 181 | #help tactic apply_fun 182 | 183 | /-! 184 | L'altra tattica è `conv`. 185 | La sintassi e l'uso sono molto estesi, ma una forma comune è di usare `conv_lhs` o `conv_rhs` 186 | quando il goal è un'uguaglianza. 187 | Per esempio, `conv_lhs => rw [one_mul]` permette di riscrivere `one_mul` solo a sinistra 188 | dell'uguale e non anche a destra. 189 | 190 | Come prima, se volete più informazioni, la documentazione di `conv` la potete leggere qui sotto. 191 | -/ 192 | 193 | #help tactic conv 194 | 195 | /-! 196 | Questo è il risultato principale: il polinomio caratteristico "rovesciato" di una matrice 197 | nilpotente è un'unità. 198 | -/ 199 | theorem isUnit_charpolyRev {N : ℕ} (hM : M ^ N = 0) : IsUnit (charpolyRev M) := by 200 | sorry 201 | done 202 | 203 | /-! 204 | Per concludere e collegare il risultato precedente al fatto che i coefficienti del 205 | polinomio caratteristico rovesciato sono nilpotenti usiamo 206 | `Polynomial.isUnit_iff_coeff_isUnit_isNilpotent`. 207 | -/ 208 | 209 | #check Polynomial.isUnit_iff_coeff_isUnit_isNilpotent 210 | 211 | example {N : ℕ} (hM : M ^ N = 0) {n : ℕ} (hn : n ≠ 0) : IsNilpotent ((charpolyRev M).coeff n) := by 212 | sorry 213 | done 214 | 215 | end Matrix 216 | 217 | end CommRing 218 | 219 | /-! 220 | # Extra credit 221 | 222 | Se avete finito presto gli esercizi, ecco un'ultima domanda: 223 | se siamo interessati solo alla traccia della matrice, si può sostituire 224 | l'ipotesi `CommRing R` con `Ring R`? 225 | 226 | Ovvero, se una matrice a coefficienti in un anello *non-necessariamente commutativo* 227 | è nilpotente, è vero che la traccia della matrice è nilpotente? 228 | 229 | Qui sotto, trovate la formalizzazione dell'enunciato. 230 | -/ 231 | 232 | variable {R : Type*} [Ring R] {n : Type*} [DecidableEq n] [Fintype n] (M : Matrix n n R) 233 | open Matrix 234 | 235 | -- Dimostrare o trovare un controesempio. 236 | theorem Matrix.isNilpotent_trace_of_isNilpotent' (hM : IsNilpotent M) : 237 | IsNilpotent M.trace := sorry 238 | -------------------------------------------------------------------------------- /MA4N1/L03_groups_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_groups_no_sols 5 | 6 | /-! 7 | # Groups 8 | 9 | Working with the implementation of groups in Mathlib is very similar to what we already did with polynomials. 10 | After all, polynomials have two operations and, with respect to each, they share a large common set of axioms 11 | with the axioms for a group. 12 | 13 | The main difference between multiplication (or addition) in semirings and groups is the existence of inverses/opposites. 14 | We will come back to this in a moment. 15 | 16 | One possibly surprising choice in Mathlib is that there are two (mathematically equivalent) definitions of groups, 17 | depending on whether the operation is denoted by `*` or by `+`: 18 | * the groups with multiplication as the operation are called `Group` in Mathlib; 19 | * the groups with addition as the operation are called `AddGroup` in Mathlib. 20 | 21 | My experience is that I got used pretty quickly to this and there are automated ways of converting results about 22 | "multiplicative" groups to results about "additive" groups. 23 | 24 | The takeaway is that, unless you have some good reason for wanting to work with an additive group, 25 | stick with multiplicative notation. 26 | 27 | Back to inverses (or opposites -- I will concentrate on multiplicative notation from now on). 28 | 29 | ## `⁻¹` or `/`? 30 | 31 | There are (at least) two ways of talking about inverses of elements: 32 | -/ 33 | 34 | -- Let `G` be a (multiplicative) group and let `g, h` be elements of `G` 35 | variable {G : Type} [Group G] {g h : G} 36 | 37 | -- good, we can multiply elements of a group 38 | #check g * h 39 | 40 | -- we can take inverses of elements of a group 41 | #check g⁻¹ 42 | 43 | -- and we can form more complicated expressions 44 | #check g ^ 2 * h ^ (- 4 : ℤ) * g 45 | 46 | /-! 47 | All of the above is about `⁻¹`, i.e. `Inv`. 48 | -/ 49 | #check Inv 50 | 51 | -- There is also `Div`... 52 | #check g / h 53 | 54 | -- Mathematically is not great-looking, but let's check that it is what we expect 55 | example : g / h = g * h⁻¹ := by 56 | exact? 57 | 58 | /- 59 | Ok, so that is really the definition of division. 60 | 61 | Finally, the main property of inverses: 62 | -/ 63 | example : g * g⁻¹ = 1 := by 64 | exact? 65 | 66 | /- 67 | Two comments. 68 | * Can you guess how is the lemma stating the identity `g⁻¹ * g = 1` is called? 69 | * Did you see that we simply used `1` and Lean was happy with it? 70 | -/ 71 | 72 | /-! 73 | # Exercises 74 | -/ 75 | 76 | variable (a b c : G) 77 | 78 | example : (a⁻¹)⁻¹ = a := by 79 | sorry 80 | done 81 | 82 | lemma aux : a * (b * c) * a⁻¹ = (a * b * a⁻¹) * (a * c * a⁻¹) := by 83 | sorry 84 | done 85 | 86 | example : a⁻¹ * (b * c) * a = (a⁻¹ * b * a) * (a⁻¹ * c * a) := by 87 | sorry 88 | done 89 | 90 | -- Did you use `aux` in the previous example? 91 | -- If so, find a proof that does not use it. 92 | -- If not, then use it now! 93 | example : a⁻¹ * (b * c) * a = (a⁻¹ * b * a) * (a⁻¹ * c * a) := by 94 | sorry 95 | done 96 | 97 | /- 98 | For elements of groups, we may be interested in computing their order. 99 | Mathlib calls the order `orderOf g` (and `addOrderOf a` in the case of an additive group). 100 | -/ 101 | 102 | -- In fact, the order is defined for a `Monoid`, not only for groups. Oh, well. 103 | #check orderOf 104 | 105 | -- feel free to free-style this, but read on if you want hints! 106 | example : addOrderOf (2 : ZMod 5) = 5 := by 107 | sorry 108 | done 109 | 110 | /- 111 | In the example above, you may be in the following situation: 112 | * you (hopefully) know the meaning of `orderOf/addOrderOf` in maths; 113 | * you are confronted with the Mathlib implementation of that definition. 114 | 115 | How do we proceed? 116 | 117 | First, we could "Go to definition" and check if `Mathlib`s definition happens to be 118 | directly mirroring the one that we already know: 119 | -/ 120 | 121 | #print orderOf 122 | /- 123 | def orderOf.{u_1} : {G : Type u_1} → [inst : Monoid G] → G → ℕ := 124 | fun {G} [Monoid G] x => Function.minimalPeriod (fun x_1 => x * x_1) 1 125 | 126 | Ok, that probably did not go according to plan. 127 | Even cleaning the output above for readability: 128 | 129 | def orderOf : the inputs are a `Monoid` `G` and an element of `G`; the output is in `ℕ` 130 | The processed definition is: 131 | if `x ∈ G`, then `orderOf x` is the "`Function.minimalPeriod`" of the function 132 | `G → G` given by `y => x * y`. 133 | 134 | We can probably guess what `minimaPeriod` is, 135 | we can probably convince ourselves that this coincides with our "standard" definition. 136 | 137 | However, whoever wrote this definition did it because it "worked better for Lean", 138 | but surely they must have wanted the definition that we expect! 139 | 140 | This means that we should look for a lemma the proves that whatever the actual 141 | `Mathlib` implementation is, it is equivalent to the "mathematical" one. 142 | 143 | Such lemmas are often named `_def`, `_iff`, or something like this. 144 | -/ 145 | 146 | #check orderOf_eq_iff 147 | /- 148 | orderOf_eq_iff.{u_1} {G : Type u_1} [inst✝ : Monoid G] {x : G} {n : ℕ} (h : 0 < n) : 149 | orderOf x = n ↔ x ^ n = 1 ∧ ∀ (m : ℕ), m < n → 0 < m → x ^ m ≠ 1 150 | 151 | **Much better!** 152 | Longer, but better! 153 | Hopefully, you recognise the assertion now: 154 | assuming the `n` is strictly positive, 155 | the statement `orderOf x = n` is equivalent to the conditions 156 | * `x ^ n = 1` 157 | * for all `m` strictly between `0` and `n`, `x ^ m` is not `1`. 158 | 159 | I hope that you recognise this! 160 | 161 | Thus, presumably when you are getting familiar with `orderOf`, you will begin by 162 | using `rw [orderOf_eq_iff]` and continue working with the definition that you know. 163 | (Note that `rw` works not only with equalities, but also with iffs.) 164 | 165 | *Aside.* 166 | Probably, in the long run, you may appreciate the *actual* implementation and you will 167 | be able to take advantage also of that, but, thanks for the equivalence, 168 | in theory there is no need. 169 | 170 | One more twist: what replaces the power in `x ^ n = 1` in the case of an `AddGroup`? 171 | Let's find out! 172 | -/ 173 | 174 | #check addOrderOf_eq_iff 175 | /- 176 | addOrderOf_eq_iff.{u_1} {G : Type u_1} [inst✝ : AddMonoid G] {x : G} {n : ℕ} (h : 0 < n) : 177 | addOrderOf x = n ↔ n • x = 0 ∧ ∀ (m : ℕ), m < n → 0 < m → m • x ≠ 0 178 | 179 | So, `x ^ n = 1` became `n • x = 0`. 180 | That may look more or less reasonable, except, possibly for `•`: 181 | this is the "scalar product" operation. 182 | In this case, it allows you to multiply a natural number `n` and an element of a generic 183 | `AddGroup` `x`. 184 | Indeed, it is just like the power, but for iterated addition. 185 | In easy cases, using `simp` may clear this. 186 | If you find yourself having to actually work with `•`, note that its name is 187 | `nsmul` (`s`calar `mul`tiplication by `n`atural numbers -- there is also `smul`). 188 | Thus, you may look for lemmas involving `nsmul` to help out -- but this will not 189 | be necessary in this exercise sheet! 190 | 191 | Let's go back to proving the result that we stated above. 192 | -/ 193 | 194 | example : addOrderOf (2 : ZMod 5) = 5 := by 195 | sorry 196 | done 197 | 198 | example : addOrderOf (3 : ZMod 6) = 2 := by 199 | sorry 200 | done 201 | 202 | /- 203 | Finally, a small quirk: 204 | `orderOf/addOrderOf` returns a natural number for *every* element of every (`Add`)`Group`. 205 | Also for the elements that may not have finite order. 206 | What is the order of an element that does not have finite order? 207 | Well, it must be a natural number and `Mathlib`s convention is that it is `0`. 208 | 209 | In this case, a relevant lemma is `addOrderOf_eq_zero_iff'`. 210 | 211 | Here is an example. 212 | -/ 213 | 214 | example : addOrderOf (1 : ℤ) = 0 := by 215 | sorry 216 | done 217 | 218 | end TPwL_groups_no_sols 219 | -------------------------------------------------------------------------------- /MA4N1/L03_groups.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_groups 5 | 6 | /-! 7 | # Groups 8 | 9 | Working with the implementation of groups in Mathlib is very similar to what we already did with polynomials. 10 | After all, polynomials have two operations and, with respect to each, they share a large common set of axioms 11 | with the axioms for a group. 12 | 13 | The main difference between multiplication (or addition) in semirings and groups is the existence of inverses/opposites. 14 | We will come back to this in a moment. 15 | 16 | One possibly surprising choice in Mathlib is that there are two (mathematically equivalent) definitions of groups, 17 | depending on whether the operation is denoted by `*` or by `+`: 18 | * the groups with multiplication as the operation are called `Group` in Mathlib; 19 | * the groups with addition as the operation are called `AddGroup` in Mathlib. 20 | 21 | My experience is that I got used pretty quickly to this and there are automated ways of converting results about 22 | "multiplicative" groups to results about "additive" groups. 23 | 24 | The takeaway is that, unless you have some good reason for wanting to work with an additive group, 25 | stick with multiplicative notation. 26 | 27 | Back to inverses (or opposites -- I will concentrate on multiplicative notation from now on). 28 | 29 | ## `⁻¹` or `/`? 30 | 31 | There are (at least) two ways of talking about inverses of elements: 32 | -/ 33 | 34 | -- Let `G` be a (multiplicative) group and let `g, h` be elements of `G` 35 | variable {G : Type} [Group G] {g h : G} 36 | 37 | -- good, we can multiply elements of a group 38 | #check g * h 39 | 40 | -- we can take inverses of elements of a group 41 | #check g⁻¹ 42 | 43 | -- and we can form more complicated expressions 44 | #check g ^ 2 * h ^ (- 4 : ℤ) * g 45 | 46 | /-! 47 | All of the above is about `⁻¹`, i.e. `Inv`. 48 | -/ 49 | #check Inv 50 | 51 | -- There is also `Div`... 52 | #check g / h 53 | 54 | -- Mathematically is not great-looking, but let's check that it is what we expect 55 | example : g / h = g * h⁻¹ := by 56 | exact? 57 | 58 | /- 59 | Ok, so that is really the definition of division. 60 | 61 | Finally, the main property of inverses: 62 | -/ 63 | example : g * g⁻¹ = 1 := by 64 | exact? 65 | 66 | /- 67 | Two comments. 68 | * Can you guess how is the lemma stating the identity `g⁻¹ * g = 1` is called? 69 | * Did you see that we simply used `1` and Lean was happy with it? 70 | -/ 71 | 72 | /-! 73 | # Exercises 74 | -/ 75 | 76 | variable (a b c : G) 77 | 78 | example : (a⁻¹)⁻¹ = a := by 79 | exact inv_inv a 80 | done 81 | 82 | lemma aux : a * (b * c) * a⁻¹ = (a * b * a⁻¹) * (a * c * a⁻¹) := by 83 | exact conj_mul.symm 84 | done 85 | 86 | example : a⁻¹ * (b * c) * a = (a⁻¹ * b * a) * (a⁻¹ * c * a) := by 87 | -- the "boring" way 88 | simp [← mul_assoc] 89 | done 90 | 91 | example : a⁻¹ * (b * c) * a = (a⁻¹ * b * a) * (a⁻¹ * c * a) := by 92 | -- the "recycle the lemma we already proved" way 93 | convert aux a⁻¹ b c <;> 94 | simp 95 | done 96 | 97 | /- 98 | For elements of groups, we may be interested in computing their order. 99 | Mathlib calls the order `orderOf g` (and `addOrderOf a` in the case of an additive group). 100 | -/ 101 | 102 | -- In fact, the order is defined for a `Monoid`, not only for groups. Oh, well. 103 | #check orderOf 104 | 105 | -- feel free to free-style this, but read on if you want hints! 106 | example : addOrderOf (2 : ZMod 5) = 5 := by 107 | sorry 108 | done 109 | 110 | /- 111 | In the example above, you may be in the following situation: 112 | * you (hopefully) know the meaning of `orderOf/addOrderOf` in maths; 113 | * you are confronted with the Mathlib implementation of that definition. 114 | 115 | How do we proceed? 116 | 117 | First, we could "Go to definition" and check if `Mathlib`s definition happens to be 118 | directly mirroring the one that we already know: 119 | -/ 120 | 121 | #print orderOf 122 | /- 123 | def orderOf.{u_1} : {G : Type u_1} → [inst : Monoid G] → G → ℕ := 124 | fun {G} [Monoid G] x => Function.minimalPeriod (fun x_1 => x * x_1) 1 125 | 126 | Ok, that probably did not go according to plan. 127 | Even cleaning the output above for readability: 128 | 129 | def orderOf : the inputs are a `Monoid` `G` and an element of `G`; the output is in `ℕ` 130 | The processed definition is: 131 | if `x ∈ G`, then `orderOf x` is the "`Function.minimalPeriod`" of the function 132 | `G → G` given by `y => x * y`. 133 | 134 | We can probably guess what `minimaPeriod` is, 135 | we can probably convince ourselves that this coincides with our "standard" definition. 136 | 137 | However, whoever wrote this definition did it because it "worked better for Lean", 138 | but surely they must have wanted the definition that we expect! 139 | 140 | This means that we should look for a lemma the proves that whatever the actual 141 | `Mathlib` implementation is, it is equivalent to the "mathematical" one. 142 | 143 | Such lemmas are often named `_def`, `_iff`, or something like this. 144 | -/ 145 | 146 | #check orderOf_eq_iff 147 | /- 148 | orderOf_eq_iff.{u_1} {G : Type u_1} [inst✝ : Monoid G] {x : G} {n : ℕ} (h : 0 < n) : 149 | orderOf x = n ↔ x ^ n = 1 ∧ ∀ (m : ℕ), m < n → 0 < m → x ^ m ≠ 1 150 | 151 | **Much better!** 152 | Longer, but better! 153 | Hopefully, you recognise the assertion now: 154 | assuming the `n` is strictly positive, 155 | the statement `orderOf x = n` is equivalent to the conditions 156 | * `x ^ n = 1` 157 | * for all `m` strictly between `0` and `n`, `x ^ m` is not `1`. 158 | 159 | I hope that you recognise this! 160 | 161 | Thus, presumably when you are getting familiar with `orderOf`, you will begin by 162 | using `rw [orderOf_eq_iff]` and continue working with the definition that you know. 163 | (Note that `rw` works not only with equalities, but also with iffs.) 164 | 165 | *Aside.* 166 | Probably, in the long run, you may appreciate the *actual* implementation and you will 167 | be able to take advantage also of that, but, thanks for the equivalence, 168 | in theory there is no need. 169 | 170 | One more twist: what replaces the power in `x ^ n = 1` in the case of an `AddGroup`? 171 | Let's find out! 172 | -/ 173 | 174 | #check addOrderOf_eq_iff 175 | /- 176 | addOrderOf_eq_iff.{u_1} {G : Type u_1} [inst✝ : AddMonoid G] {x : G} {n : ℕ} (h : 0 < n) : 177 | addOrderOf x = n ↔ n • x = 0 ∧ ∀ (m : ℕ), m < n → 0 < m → m • x ≠ 0 178 | 179 | So, `x ^ n = 1` became `n • x = 0`. 180 | That may look more or less reasonable, except, possibly for `•`: 181 | this is the "scalar product" operation. 182 | In this case, it allows you to multiply a natural number `n` and an element of a generic 183 | `AddGroup` `x`. 184 | Indeed, it is just like the power, but for iterated addition. 185 | In easy cases, using `simp` may clear this. 186 | If you find yourself having to actually work with `•`, note that its name is 187 | `nsmul` (`s`calar `mul`tiplication by `n`atural numbers -- there is also `smul`). 188 | Thus, you may look for lemmas involving `nsmul` to help out -- but this will not 189 | be necessary in this exercise sheet! 190 | 191 | Let's go back to proving the result that we stated above. 192 | -/ 193 | 194 | example : addOrderOf (2 : ZMod 5) = 5 := by 195 | rw [addOrderOf_eq_iff] 196 | · simp 197 | reduce_mod_char 198 | rw [true_and] 199 | intros m hm5 hm0 200 | interval_cases m <;> decide 201 | · omega 202 | done 203 | 204 | example : addOrderOf (3 : ZMod 6) = 2 := by 205 | rw [addOrderOf_eq_iff] 206 | · simp 207 | reduce_mod_char 208 | rw [true_and] 209 | intros m hm5 hm0 210 | interval_cases m 211 | decide 212 | · omega 213 | done 214 | 215 | /- 216 | Finally, a small quirk: 217 | `orderOf/addOrderOf` returns a natural number for *every* element of every (`Add`)`Group`. 218 | Also for the elements that may not have finite order. 219 | What is the order of an element that does not have finite order? 220 | Well, it must be a natural number and `Mathlib`s convention is that it is `0`. 221 | 222 | In this case, a relevant lemma is `addOrderOf_eq_zero_iff'`. 223 | 224 | Here is an example. 225 | -/ 226 | 227 | example : addOrderOf (1 : ℤ) = 0 := by 228 | rw [addOrderOf_eq_zero_iff'] 229 | intros n n0 230 | simp 231 | exact Nat.pos_iff_ne_zero.mp n0 232 | done 233 | 234 | end TPwL_groups 235 | -------------------------------------------------------------------------------- /MA4N1/Pisa/nilpotent_exercises.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import Mathlib.LinearAlgebra.Matrix.Charpoly.Coeff 4 | 5 | open Matrix 6 | 7 | /-! 8 | # Esercizi sulle matrici nilpotenti 9 | 10 | In questo file, suggerisce una dimostrazione del risultato seguente: 11 | 12 | Se la matrice `M` è nilpotente, allora tutti i coefficienti del polinomio 13 | `det (1 - M * X)` sono nilpotenti, tranne il coefficiente di `X ^ 0`. 14 | 15 | In particolare, la traccia della matrice `M` è nilpotente. 16 | -/ 17 | 18 | variable {R n : Type*} [CommRing R] [DecidableEq n] [Fintype n] 19 | 20 | open Matrix in 21 | example (M : Matrix n n R) {N : ℕ} (hM : M ^ N = 0) {n : ℕ} (hn : n ≠ 0) : 22 | IsNilpotent ((charpolyRev M).coeff n) := by 23 | sorry 24 | done 25 | 26 | /-! 27 | # Preliminari 28 | 29 | Iniziamo con qualche lemma semplice, per imparare un po' come interagire con matrici, 30 | determinanti e altre cose. 31 | 32 | ### Alcune identità in anelli 33 | 34 | `Mathlib` contiene già molte identità di base tra elementi di anelli. 35 | 36 | Cominciamo con una identità facile. 37 | -/ 38 | example (x y : R) (n : ℕ) : x - y ∣ x ^ n - y ^ n := by 39 | exact? says exact sub_dvd_pow_sub_pow x y n 40 | done 41 | 42 | /-! 43 | Tuttavia, il risultato qui sopra non è quello che vogliamo! 44 | 45 | Nella nostra applicazione, l'anello `R` sarà realmente l'anello delle matrici a coefficienti in `R`. 46 | Il risultato di prima non lo possiamo usare, perché assume che `R` è commutativo. 47 | 48 | La versione non-commutativa, in cui assumiamo che `x` e `y` commutano è anche già in `Mathlib`. 49 | 50 | In questo esempio, `x` e `y` sono elementi di una anello qualsiasi 51 | -/ 52 | example {A : Type*} [Ring A] {x y : A} (h : Commute x y) (n : ℕ) : 53 | x - y ∣ x ^ n - y ^ n := by 54 | exact? says exact Commute.sub_dvd_pow_sub_pow h n 55 | done 56 | 57 | /-! 58 | ### Matrici 59 | 60 | Fate attenzione alle coercizioni (`↑`) (coercions) che facciamo inserire a Lean con le 61 | ascrizioni (type-ascriptions) esplicite. 62 | -/ 63 | example (a b : ℕ) : ((a * b : ℕ) : Matrix n n R) = (a * b) := by 64 | exact? says exact Nat.cast_mul a b 65 | done 66 | 67 | /-! Le matrici sono costruite a partire dalle funzioni con due argomenti. 68 | La tattica `ext` può essere utile per dimostrare il risultato seguente. 69 | -/ 70 | example : (fun i j => if i = j then 1 else 0 : Matrix (Fin 2) (Fin 2) R) = 71 | (1 : Matrix (Fin 2) (Fin 2) R) := by 72 | ext i j 73 | split_ifs with h 74 | · rw [h, one_apply_eq] 75 | · exact? says exact (one_apply_ne h).symm 76 | done 77 | /-! 78 | _Nota._ 79 | Questa non è la maniera più diretta di definire la matrice identità: nell'esercizio 80 | successivo vediamo una maniera migliore. 81 | -/ 82 | example : !![1, 0; 0, 1] = 1 := by 83 | rw [one_fin_two] 84 | done 85 | 86 | /-! 87 | ### Determinanti 88 | 89 | Nella nostra dimostrazione, usiamo anche qualche determinante. 90 | 91 | Il determinante di una matrice quadrata è `Matrix.det` 92 | (poiché abbiamo scritto `open Matrix` più sopra, possiamo riferirci con `det` a `Matrix.det`). 93 | 94 | Come ci possiamo aspettare, `det` è una funzione che associa a una matrice a coefficienti in `R` 95 | un elemento di `R`. 96 | -/ 97 | #check det 98 | 99 | example : det (1 : Matrix n n R) = 1 := by 100 | exact? says exact det_one 101 | done 102 | 103 | /-! 104 | Per il prossimo esempio, può essere utile usare `det_smul`. 105 | -/ 106 | #check det_smul 107 | 108 | example : det (2 : Matrix n n R) = 2 ^ (Fintype.card n) := by 109 | convert det_smul 1 (2 : R) 110 | · rw [← Algebra.algebraMap_eq_smul_one] 111 | rfl 112 | · rw [det_one, mul_one] 113 | done 114 | 115 | /-! 116 | ### Unità 117 | 118 | Non useremo quasi nulla sulle unità, ma appariranno nell'equivalenza 119 | 120 | `i coefficienti non-costanti di un polinomio sono nilpotenti ↔ il polinomio è invertibile` 121 | 122 | Il comando `#print` qui sotto non mostra un'informazione importante, che però possiamo 123 | ottenere mettendo il cursore sulla `u` in `∃ u` nell'infoview. 124 | -/ 125 | #print IsUnit 126 | /-! 127 | Il tipo di `u` è `Mˣ = Units M`, il tipo delle unità di `M`. 128 | -/ 129 | #print Units 130 | 131 | /-! 132 | Quindi, `IsUnit` è semplicemente il predicato su `R` che decide se un elemento `a` ammette o meno 133 | un inverso destro e sinistro; ovvero se `a` "è" o meno una unità. 134 | -/ 135 | 136 | /-- In un anello, un elemento che divide `1` è una unità. -/ 137 | example {a : R} (h : a ∣ 1) : IsUnit a := by 138 | exact? says exact isUnit_of_dvd_one h 139 | done 140 | 141 | /-! 142 | Assume that `M ^ N = 0`. 143 | 144 | Start with the identities 145 | 146 | `I = I - (tM) ^ (N + 1)` 147 | ` = (I - tM)(I + tM + ... + (tM) ^ N)`. 148 | 149 | Compute determinants on both sides and use that the determinant of a product is the product of the determinants. 150 | 151 | Deduce that the determinant of `(I - tM)` is an invertible polynomial. 152 | Therefore all its coefficients of positive degree are nilpotents. 153 | 154 | The rest of this file develops the tools that should allow you to formalize the above proof 155 | in the following hour! 156 | -/ 157 | 158 | section CommRing 159 | 160 | variable {R : Type*} [CommRing R] {n : Type*} [DecidableEq n] [Fintype n] 161 | 162 | open Polynomial 163 | 164 | recall Matrix.charpolyRev (M : Matrix n n R) := det (1 - (X : R[X]) • M.map C) 165 | 166 | namespace Matrix 167 | 168 | variable (M : Matrix n n R) 169 | 170 | -- why did I not find this lemma already? 171 | theorem map_pow (N : ℕ) : (M ^ N).map C = M.map C ^ N := by 172 | induction N with 173 | | zero => simp 174 | | succ N => simp [pow_succ, *] 175 | done 176 | 177 | /-! 178 | Per la dimostrazione di `isUnit_charpolyRev` ho usato (tra le altre) le due tattiche 179 | `apply_fun` e `conv`. 180 | 181 | `apply_fun f` è utile per applicare la stessa funzione `f` ai due lati dell'uguaglianza nel 182 | risultato principale. 183 | Funziona anche con `apply_fun at h`, quando vogliamo applicare `f` ai due lati dell'ipotesi `h`. 184 | La tattica cerca (un po') di trovare l'ipotesi che `f` è iniettiva. 185 | Se non riesce a dimostrare che `f` è iniettiva, produce anche un side-goal `⊢ f.Injective`. 186 | 187 | Qui sotto potete leggere la documentazione di `apply_fun`. 188 | -/ 189 | #help tactic apply_fun 190 | 191 | /-! 192 | L'altra tattica è `conv`. 193 | La sintassi e l'uso sono molto estesi, ma una forma comune è di usare `conv_lhs` o `conv_rhs` 194 | quando il goal è un'uguaglianza. 195 | Per esempio, `conv_lhs => rw [one_mul]` permette di riscrivere `one_mul` solo a sinistra 196 | dell'uguale e non anche a destra. 197 | 198 | Come prima, se volete più informazioni, la documentazione di `conv` la potete leggere qui sotto. 199 | -/ 200 | 201 | #help tactic conv 202 | 203 | /-! 204 | Questo è il risultato principale: il polinomio caratteristico "rovesciato" di una matrice 205 | nilpotente è un'unità. 206 | -/ 207 | theorem isUnit_charpolyRev {N : ℕ} (hM : M ^ N = 0) : IsUnit (charpolyRev M) := by 208 | apply isUnit_of_dvd_one 209 | have : 1 = 1 - ((X : R[X]) • M.map C) ^ N := by 210 | simp [smul_pow, ← map_pow, hM] 211 | apply_fun det at this 212 | rw [det_one] at this 213 | rw [this] 214 | obtain ⟨A, h⟩ : 1 - (X : R[X]) • M.map C ∣ 1 - ((X : R[X]) • M.map C) ^ N := by 215 | conv_rhs => rw [← one_pow N] 216 | exact Commute.sub_dvd_pow_sub_pow (by simp) N 217 | rw [h] 218 | simp [charpolyRev] 219 | done 220 | 221 | /-! 222 | Per concludere e collegare il risultato precedente al fatto che i coefficienti del 223 | polinomio caratteristico rovesciato sono nilpotenti usiamo 224 | `Polynomial.isUnit_iff_coeff_isUnit_isNilpotent`. 225 | -/ 226 | 227 | #check Polynomial.isUnit_iff_coeff_isUnit_isNilpotent 228 | 229 | example {N : ℕ} (hM : M ^ N = 0) {n : ℕ} (hn : n ≠ 0) : IsNilpotent ((charpolyRev M).coeff n) := by 230 | -- invece di `have` e `rcases`, li potremmo combinare in `obtain ⟨-, h⟩`. 231 | have : IsUnit (M.charpolyRev.coeff 0) ∧ 232 | ∀ (i : ℕ), i ≠ 0 → IsNilpotent (coeff (M.charpolyRev) i) := by 233 | exact (Polynomial.isUnit_iff_coeff_isUnit_isNilpotent).mp (isUnit_charpolyRev M hM) 234 | rcases this with ⟨-, h⟩ 235 | apply h _ hn 236 | done 237 | 238 | end Matrix 239 | 240 | end CommRing 241 | 242 | /-! 243 | # Extra credit 244 | 245 | Se avete finito presto gli esercizi, ecco un'ultima domanda: 246 | se siamo interessati solo alla traccia della matrice, si può sostituire 247 | l'ipotesi `CommRing R` con `Ring R`? 248 | 249 | Ovvero, se una matrice a coefficienti in un anello *non-necessariamente commutativo* 250 | è nilpotente, è vero che la traccia della matrice è nilpotente? 251 | 252 | Qui sotto, trovate la formalizzazione dell'enunciato. 253 | -/ 254 | 255 | variable {R : Type*} [Ring R] {n : Type*} [DecidableEq n] [Fintype n] (M : Matrix n n R) 256 | open Matrix 257 | 258 | -- Dimostrare o trovare un controesempio. 259 | theorem Matrix.isNilpotent_trace_of_isNilpotent' (hM : IsNilpotent M) : 260 | IsNilpotent M.trace := sorry 261 | -------------------------------------------------------------------------------- /MA4N1/L06_typeclasses.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | import Mathlib.Combinatorics.SimpleGraph.Basic 4 | 5 | namespace TPwL_typeclasses 6 | 7 | /-! 8 | 9 | # Definitions, Structures and Typeclasses 10 | 11 | In mathematics, you often package together various different notions into a single one. 12 | Here are a few examples: 13 | * a basis of a vector space; 14 | * the degree of a polynomial; 15 | * whether a number is prime; 16 | * a group, vector space, ring, field, algebra,...; 17 | * whether a function `f : G → H` between groups is a group homomorphism; 18 | * a zero-divisor in a ring; 19 | 20 | and so on. 21 | 22 | From a mathematical point of view, we would probably not have any problems calling all of 23 | the items above "definitions". 24 | 25 | However, when formalising these notions, we may exploit different parts of Lean's internal mechanism 26 | to guide it and ultimately simplify our following efforts. 27 | 28 | Here we look at the 3 keywords 29 | * `def`; 30 | * `structure`; 31 | * `class`; 32 | 33 | that Lean has to generate new definitions. 34 | 35 | Knowing which one to use when normally means asking yourself "what is the expected usage of this notion". 36 | 37 | Here is a somewhat heuristic description of the differences between the keywords and 38 | the contexts in which they may be used. 39 | 40 | ## `def` 41 | 42 | A `def`inition in Lean is usually a "single" property, a function or a "statement". 43 | For example, the degree of a polynomial is a function that takes a polynomial and returns a natural number. 44 | This is a definition in `Mathlib`: 45 | -/ 46 | #check Polynomial.natDegree -- immediately followed by more definitions 47 | #check Polynomial.leadingCoeff 48 | #check Polynomial.Monic 49 | 50 | #check Nat.Prime -- slightly unusual, since `Irreducible` is a structure 51 | #check Nat.minFac 52 | 53 | /- 54 | 55 | ## `structure` 56 | 57 | This is typically a "collection" of properties that we would like to treat as a single "bundle". 58 | For example 59 | -/ 60 | #check Module.Basis -- a basis of a vector space 61 | #check MonoidHom -- a homomorphism of monoids (e.g. of groups!) 62 | #check Units -- the units in a monoid (e.g. in a ring) 63 | #check SimpleGraph -- a simple, undirected, loopless graph 64 | 65 | /- 66 | 67 | ## `class` 68 | 69 | This is very similar to structure, except that we would expect at most one such structure to be present on a given Type. 70 | -/ 71 | 72 | #check Group 73 | #check Ring 74 | #check Field 75 | #check NoZeroDivisors 76 | #check TopologicalSpace 77 | /- 78 | 79 | Among `def`s, `structure`s and `class`es, the `class`es are the ones that are trickier. 80 | Technically, a `class` is simply a `structure` that is visible to the typeclass system. 81 | The typeclass system is the part of Lean that takes care of handling, synthesizing and discharging assumptions that appear inside square brackets (such as `[CommRing R] [Algebra R A]`,...). 82 | 83 | Thus, when Lean sees `class`, it created a structure, but also adds the corresponding structure to its database of `class`es. 84 | It will then look up at this database for figuring out that `Field` implies `AddCommGroup`, for instance. 85 | To create an implication among typeclasses, you should prove that some `class` is a consequence of others (e.g. as before that `AddCommGroup` follows from `Field`). 86 | You do this, by "proving" an `instance`. 87 | In some sense, `class`es are the vertices and `instance`s are the edges in the "typeclass graph". 88 | Lean uses this information in the background to simplify our formalisation. 89 | -/ 90 | @[ext] --we will see later what this does! 91 | structure point where 92 | x : ℝ 93 | y : ℝ 94 | 95 | /- 96 | Let's tell Lean that we want to be able to add two points, using component-wise addition. 97 | This means that we will register an `Add` instance on `point`. 98 | -/ 99 | 100 | variable (p q : point) in 101 | /-- 102 | error: failed to synthesize 103 | HAdd point point ?m.3 104 | 105 | Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command. 106 | -/ 107 | #guard_msgs in 108 | #check p + q 109 | 110 | instance : Add point where 111 | add p q := { 112 | x := p.x + q.x 113 | y := p.y + q.y 114 | } 115 | 116 | variable (p q : point) in 117 | #check p + q 118 | 119 | /-! 120 | We want to inform Lean that `point` has the structure of an (additive) commutative group, 121 | where the operations are the "obvious" ones (component-wise addition). 122 | 123 | Note that in our informal description above of the "commutative group structure", we reference 124 | the *operations* and say that `point` is a group with respect to those operations. 125 | This brings up the distinction between "data" and "`Prop`": 126 | * the operations are "data"; 127 | * the properties that these operations satisfy are "`Prop`". 128 | 129 | This distinction is equally important in formal and informal mathematics, 130 | though it is more *explicit* in formal maths. 131 | 132 | Informal mathematics sometimes blurs the distinction between the two. 133 | For instance, when we say that `point` is a group with with respect to the usual operations, 134 | we are being very implicit about what is the `zero` of the group operation. 135 | After all, the general theory guarantees that a group has a unique zero, so, technically, 136 | we do not have to provide one. 137 | However, it turns out that for formalization it is more convenient to be as explicit as possible 138 | about what the unit of our operation is (and more generally, about everything!). 139 | 140 | In this case, we isolate the definition of the zero element and of the opposite. 141 | The reason for isolating these two fields has more to do with a Lean-language detail, than anything 142 | mathematical. 143 | 144 | Note that both `zero` and `neg` are "data", and that there is more "data" in the 145 | additive commutative group instance on `point`. 146 | -/ 147 | 148 | instance : Zero point where zero := {x := 0, y := 0} 149 | -- the above instance can also be written as 150 | -- `instance : Zero point := ⟨{x := 0, y := 0}⟩` 151 | -- or even as 152 | -- `instance : Zero point := ⟨⟨0, 0⟩⟩` 153 | 154 | instance : Neg point where neg a := ⟨-a.x, -a.y⟩ 155 | 156 | instance : AddCommGroup point where 157 | add a b := a + b 158 | add_assoc a b c := by ext <;> apply add_assoc 159 | zero_add a := by ext <;> apply zero_add 160 | add_zero a := by ext <;> apply add_zero 161 | nsmul := nsmulRec -- do not worry about this: hover for info and ask if you want more! 162 | zsmul := zsmulRec -- do not worry about this: hover for info and ask if you want more! 163 | neg_add_cancel a := by ext <;> apply neg_add_cancel 164 | add_comm a b := by ext <;> apply add_comm 165 | 166 | /- 167 | From now, Lean knows that the `structure` that we called `point` is an additive commutative group. 168 | In particular, all the lemmas in the library that apply to `AddCommGroup` also apply to `point`! 169 | -/ 170 | 171 | example {p q r : point} : r + p + q - r = p + q := by 172 | abel -- tries to solve goals in abelian groups (additive or multiplicative) 173 | done 174 | 175 | /- 176 | `Mathlib` has a very intricate hierarchy of structures and typeclasses linking them: 177 | to get an impression, think about how many "structures" there are between a "bare" Type and 178 | and `Field`. 179 | 180 | The graphs in [this post](https://leanprover.zulipchat.com/#narrow/stream/287929-mathlib4/topic/instance.20graphs/near/378851049) 181 | were the actual instance graphs in `Mathlib` a few months ago. 182 | 183 | While the process of adding typeclass assumptions to `structure`s is as easy as adding 184 | hypotheses in square brackets, there are a couple of issues to keep in mind. 185 | 186 | ### `extends` vs assumption 187 | 188 | A `structure` can `extend` a typeclass or it can take the typeclass as an assumption. 189 | Here is the difference. 190 | -/ 191 | 192 | section right_and_wrong_structures 193 | 194 | -- structure `A` expects to find already the typeclass `Add` on `α` 195 | structure A (α : Type) [Add α] where 196 | 197 | /-- 198 | error: failed to synthesize 199 | Add α 200 | 201 | Hint: Additional diagnostic information may be available using the `set_option diagnostics true` command. 202 | -/ 203 | #guard_msgs in 204 | variable {α : Type} (h : A α) -- fails 205 | variable {α : Type} [Add α] (h : A α) -- works 206 | 207 | -- structure `B` will add the typeclass `Add` on `α` 208 | structure B (α : Type) extends Add α where 209 | 210 | variable {α : Type} (h : B α) -- works 211 | 212 | end right_and_wrong_structures 213 | 214 | /- 215 | ### Putting a typeclass assumption twice on the same type 216 | 217 | This mistake leads to confusing errors. 218 | It happens when you accidentally (or unknowingly) put two different assumptions on a Type that 219 | imply the same typeclass. 220 | -/ 221 | 222 | example {α : Type} /- 223 | [Add α] --/ 224 | [CommRing α] (a b : α) : 225 | a + b - b = a := by 226 | exact? says exact Eq.symm (eq_sub_of_add_eq rfl) 227 | done 228 | 229 | class group (G : Type) where 230 | id : G 231 | mul : G → G → G 232 | inv : G → G 233 | mul_assoc : ∀ a b c : G, mul (mul a b) c = mul a (mul b c) 234 | mul_inv : ∀ g, mul g (inv g) = id 235 | -- and so on 236 | 237 | end TPwL_typeclasses 238 | -------------------------------------------------------------------------------- /MA4N1/L10_dvd_induction_no_sols.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_dvd_induction_no_sols 5 | 6 | /-! 7 | 8 | # Divisibility as an excuse to see more tactics 9 | 10 | This exercise sheet proves some results on divisibility among natural numbers. 11 | 12 | Before going into the actual exercises, I want to introduce the tactic `rcases`. 13 | It is personally one of my favourite tactics, although it took me a while to 14 | appreciate how useful it can be! 15 | You would be surprised by how far you can get by with just `refine/apply` and `rcases`! 16 | 17 | ## Digression on `rcases` 18 | 19 | The `rcases` tactic is the recursive version of `cases`. 20 | It has lot's of features, but in its most basic form it allows us to avoid long chains 21 | of `cases x with ...`. 22 | Here is a brief summary of some of its features. 23 | 24 | If we have an assumption `h : p ∧ q` in our local context, then `rcases h with ⟨hp, hq⟩` 25 | "deconstructs" the `And` and leaves the two hypotheses `hp : p, hq : q` in its place. 26 | 27 | If we have an assumption `h : p ∨ q` in our local context, then `rcases h with hp | hq` 28 | "deconstructs" the `Or` and produces two goals, one containing the hypothesis `hp : p`, 29 | the other containing `hq : q`. 30 | 31 | These deconstructions can be combined recursively. 32 | -/ 33 | 34 | example {m n : ℕ} (h : (m = 0 ∧ n = 1) ∨ (m = 0 ∧ n = 2)) : m = 0 := by 35 | rcases h with ⟨m0, _n1⟩ | ⟨m0, _n2⟩ <;> 36 | exact m0 37 | done -- do experiment with the code in this proof! 38 | 39 | /-! 40 | ... and it can be used on `inductive` types as well: 41 | -/ 42 | example {n : ℕ} : (n = 0) ∨ (n = 1) ∨ (n = 2) ∨ (3 ≤ n) := by 43 | -- the underscores represent, in succession, 44 | -- `Nat.zero`, `Nat.succ Nat.zero`, `Nat.succ (Nat.succ Nat.zero)` and the rest, that is 45 | -- `0`, `1`, `2`, `n + 1 + 1 + 1` 46 | rcases n with _ | _ | _ | _ <;> 47 | omega -- `omega` is a tactic for solving linear problems with natural and integer coefficients 48 | done -- do experiment with the code in this proof! 49 | 50 | /-! 51 | One more feature: if destructuring you encounter an equality where one of the two 52 | sides is a variable and the other side does not contain the variable, 53 | you can write `rfl` in the corresponding `rcases` slot and `rcases` replaces 54 | all occurrences of that variable by its value. 55 | -/ 56 | 57 | example {n : ℕ} (h : (n = 0) ∨ (n = 1) ∨ (n = 2) ∨ (n = 3) ∨ (n = 4)) : n ≤ 4 := by 58 | rcases h with rfl | rfl | rfl | rfl | rfl <;> 59 | repeat apply Nat.succ_le_succ 60 | all_goals exact Nat.zero_le _ 61 | done -- do experiment with the code in this proof! 62 | 63 | /-! 64 | Take a look at the documentation for `rcases` to see more features. 65 | -/ 66 | 67 | #help tactic rcases 68 | 69 | /-! 70 | Now that the aside is over, let's go to divisibility. 71 | 72 | First, we begin with a warm-up working with divisors. 73 | The symbol for "divides" is `∣`, that is `\mid`. 74 | The symbol for "such that" that appears in the definition of a set is `|` -- they look the same, but are not the same! 75 | In general, you can hover over any symbol and you should get information about how to type it. 76 | 77 | In the following example, the tactic `interval_cases` can be useful. 78 | If `a` is a variable in context, then calling `interval_cases a` looks for upper and lower bounds for `a` and 79 | returns a goal for each possible value of `a`. 80 | Note that if `a : ℕ`, then the lower bound of `0` is always available, 81 | so you only need to have in context an upper bound of the form `a ≤ [some number]`. 82 | However, if there is a better lower bound than `0`, then `interval_cases a` will use it. 83 | 84 | You can take a look at the documentation for `interval_cases` here: 85 | -/ 86 | #help tactic interval_cases 87 | 88 | /-! 89 | Another tactic that might be useful is `simp_all`. 90 | This tactic essentially applies `simp` everywhere recursively, until it makes no further progress. 91 | -/ 92 | #help tactic simp_all 93 | 94 | /- 95 | Finally, remember the `ext` tactic: to show that certain equalities hold, 96 | it suffices to show that the two sides have the same elements. 97 | You can also use `ext a` to name the "common" element that the tactic extracts. 98 | -/ 99 | 100 | example : {a : ℕ | a ∣ 6} = {1, 2, 3, 6} := by 101 | sorry 102 | done 103 | 104 | /-! 105 | The proof above probably looks more complicated than it "should". 106 | Even compressing it a little, it feels clunky: 107 | -/ 108 | 109 | example : {a : ℕ | a ∣ 6} = {1, 2, 3, 6} := by 110 | ext a 111 | simp only [Set.mem_setOf_eq, Set.mem_insert_iff, Set.mem_singleton_iff] 112 | constructor 113 | · intro h 114 | have := Nat.le_of_dvd (Nat.succ_pos 5) h 115 | interval_cases a <;> omega 116 | · rintro (rfl|rfl|rfl|rfl) <;> omega 117 | 118 | /-! 119 | In some sense, our intuition is correct: we have made our life hard, by 120 | formulating a statement about finite sets, without letting Lean know that this was the case. 121 | 122 | The advantage of working with finite sets is that we can sometimes outsource proofs 123 | to explicit enumerations that Lean performs automatically. 124 | We can exploit this automation via the `decide` tactic. 125 | 126 | However, for the `decide` tactic to work, we should set ourselves up by 127 | having explicitly finite sets, and several algorithms that, behind the scenes, 128 | will take care of the appropriate enumerations. 129 | 130 | The automation behind `decide` is driven by `Decidable` instances. 131 | For most of you, `Decidabl`ity will likely play a marginal (or inexistent) role. 132 | But for some, it may be more important. 133 | 134 | Here is a more automated approach to the example above. 135 | 136 | First, we observe that the set of divisors of a natural number is already available in Lean: 137 | -/ 138 | 139 | #check Nat.divisors 140 | 141 | /-! 142 | Even better: `Nat.divisors n` is not just a set, but a `Finset`. 143 | `Finset` is one of the ways that Mathlib has of talking about finite sets. 144 | 145 | Here is how we can revisit the example above: 146 | -/ 147 | 148 | example : Nat.divisors 6 = {1, 2, 3, 6} := by decide 149 | 150 | /-! 151 | The first thing to note is that the proof is painless! 152 | 153 | The second thing to note is that the statement is not exactly the same as what it was before. 154 | 155 | Earlier, we were proving an equality of sets. 156 | The fact that these sets were finite helped us in the proof (at the core, the proof was a case split), 157 | but we never made Lean directly aware of this fact. 158 | 159 | The latest version of the example is an equality of `Finset`s. 160 | This means that someone has already taken care of verifying that 161 | * `Nat.divisors n` yields a finite set (and they also took care of dealing with the case `Nat.divisors 0`); 162 | * the notation `{1, 2, 3, 6}` can ambiguously mean `Set` or `Finset`, depending on context -- it meant `Set ℕ` 163 | earlier, it means `Finset ℕ` now. 164 | 165 | Of course, the fact that this proof became painless is simply a reflection that the work is hiding somewhere else. 166 | In this case, the work is making sure that Lean can really verify these proofs by enumeration, following 167 | around the `Decidable` instances and working with `Finset`s. 168 | 169 | Note that working with `Finset`s can be annoying! 170 | 171 | Let's now prove an "induction principle" for natural numbers that works on divisibility. 172 | 173 | You may find `Nat.strongRecOn` useful. 174 | -/ 175 | #check Nat.strongRecOn 176 | 177 | lemma dvd_induction {P : ℕ → Prop} (n : ℕ) 178 | (P0 : P 0) 179 | (P1 : P 1) 180 | (P_mul : ∀ {p a}, Nat.Prime p → a ≠ 0 → a ≠ 1 → P a → P (p * a)) 181 | (P_prime : ∀ {p}, Nat.Prime p → P p) : 182 | P n := by 183 | sorry 184 | done 185 | 186 | /-! 187 | This is something that may appear unfamiliar. 188 | 189 | First, `open scoped Pointwise` means that we can access the features that are associated 190 | with `Pointwise`. 191 | Typical features include special notation, syntax, instances, and so on. 192 | The reason they are only available inside an `open scoped` is that they may conflict with 193 | some more common conventions, and you would not want to have the "unusual" instances affect 194 | the "usual" ones all the time. 195 | 196 | In the specific case of `Pointwise`, the main feature that we are after is the possibility 197 | of multiplying `Finset`s. 198 | 199 | The product of two `Finset`s is defined as the set of all pairwise products of one element 200 | from each `Finset`. 201 | 202 | For example, 203 | -/ 204 | open scoped Pointwise 205 | 206 | example : ({0, 1, 2} : Finset ℕ) * {3, 4} = {0, 3, 4, 6, 8} := by 207 | decide 208 | done -- if you are curious, change the numbers and get some practice! 209 | 210 | /-! 211 | Our main goal will be to show that the (Fin)set of divisors of a product of two 212 | natural numbers is the product of the sets of divisors of each factor. 213 | If you never thought about multiplying sets of numbers, think about why the statement 214 | is true and then get on with the proof! 215 | 216 | In my proof, I conclude by using the `aesop` tactic. 217 | This tactic is a proof-search tactic and is *very* useful! 218 | In fact, the reason for not mentioning it earlier was precisely to get you 219 | to learn how to do something on your own, so that when `aesop` fails, 220 | you are not necessarily stuck! 221 | -/ 222 | 223 | #help tactic aesop 224 | 225 | lemma _root_.Nat.Prime.divisors_mul (n : ℕ) {p : ℕ} (hp : Nat.Prime p) : 226 | Nat.divisors (p * n) = Nat.divisors p * Nat.divisors n := by 227 | sorry 228 | done 229 | 230 | /-! 231 | Our main result: the divisors of a product are the product of the divisors. 232 | -/ 233 | 234 | example {m n : ℕ} : Nat.divisors m * Nat.divisors n = Nat.divisors (m * n) := by 235 | sorry 236 | done 237 | 238 | end TPwL_dvd_induction_no_sols 239 | -------------------------------------------------------------------------------- /MA4N1/L04_definitions.lean: -------------------------------------------------------------------------------- 1 | import MA4N1.Init 2 | import Mathlib.Tactic 3 | 4 | namespace TPwL_definitions 5 | 6 | /-! 7 | 8 | # Developing an "API" around new definitions 9 | 10 | First of all, `API` stands for `A`pplication `P`rogramming `I`nterface. 11 | In the context of formalisation of mathematics, 12 | it generically refers to (typically very trivial) results about a new concept that you have introduced. 13 | 14 | For example, in the API for `Group` I would expect to find the statements that 15 | * multiplying any element `g` of a group by the identity element returns the element `g`; 16 | * multiplication is associative; 17 | * multiplying a group element by its inverse gives the identity; 18 | * the inverse of the inverse of an element is the element itself: 19 | 20 | and so on. 21 | Indeed, what we saw during the past lectures on `And, Or, Polynomial, natDegree, Group, orderOf` 22 | was essentially the basic API results and then a little bit of extra theory that you could develop. 23 | 24 | *Every* new definition in Lean should come with a (possibly small) API. 25 | One important difference between informal and formal mathematics is that in informal mathematics 26 | it is often easy to blur the distinction between genuinely different (and equivalent!) definitions. 27 | Sometimes, even non-equivalent definitions may be used, depending on authors or context. 28 | 29 | For example, you may have encountered "the" definition of the real numbers as 30 | * Dedekind cuts of rational numbers; 31 | * Cauchy sequences of rational numbers, up to equivalence; 32 | * completion of the rational numbers, with respect to the absolute value; 33 | * a complete, linearly ordered, Archimedean field; 34 | * ... 35 | 36 | When *formalising* the notion of real numbers, it is good practice to *pick one* definition and 37 | prove that it is equivalent to all the others. 38 | Once that is done, for a *user* of the formalisation, it should be irrelevant what the definition 39 | *actually* is: they should have direct access to all the equivalent forms "from the API". 40 | 41 | Ideally, the *user* should not need to know what the formal definition of `ℝ` is, but should be able to 42 | convert between all the "usual" definitions by using lemmas in the library. 43 | 44 | You saw some of this when you proved results about `orderOf` for groups. 45 | The exercises were careful in *hiding* the actual definition, and making you go through the 46 | property that you are familiar with: 47 | the order is the smallest positive exponent of an element that gives the identity. 48 | 49 | Let me emphasize: 50 | *this is not `Mathlib`'s definition of order*! 51 | But it is equivalent to it, and this is good enough. 52 | 53 | Let's see this in practice, by developing a little API around the definition of the absolute value. 54 | -/ 55 | def Abs (z : ℤ) : ℤ := if z < 0 then - z else z 56 | /- 57 | Note the syntax of `def`: it is very similar to the one of `theorem`. 58 | We are defining a new concept, `Abs` (or rather `TPwL.Abs`, since we are inside the `TPwL`-namespace). 59 | It takes an integer `z` as input and returns an integer. 60 | The integer `Abs z` is defined by a case-split: 61 | you can probably guess what the `if ... then ... else ...` syntax means! 62 | -/ 63 | 64 | #check Abs 3 65 | #check Abs (- 3) 66 | 67 | #eval Abs (- 3) 68 | 69 | lemma abs_eq_max (z : ℤ) : Abs z = max z (-z) := by 70 | -- since we proved *nothing* about `Abs`, this had better follow from the actual definition we gave! 71 | -- we access this by using `unfold`: 72 | unfold Abs 73 | split_ifs with h 74 | · -- here we would like to access the "API" around `max` 75 | -- we know that the `max` is equal to its second argument in this case, so... 76 | symm -- swaps the two sides of an equality: Lean is *very* pedantic 77 | apply max_eq_right 78 | linarith -- solves for us (in)-equalities that involve variables appearing linearly, more or less 79 | -- alternatively, this is how we could prove the `z ≤ -z` inequality: 80 | -- first, we use `h` and transitivity to reduce to proving that `0 ≤ -z` 81 | -- we actually end up using `h.le : z ≤ 0` and then transitivity 82 | -- note that `h.le.trans` exploits dot-notation twice: it stands for 83 | -- `LE.le.trans (LT.lt.le h)`, and uses that 84 | -- * the type of `h` begins with `LT.lt (it is a strict inequality); 85 | -- * the type of `LT.lt.le h = h.le` is `z ≤ 0` and it begins with `LE.le` (it is an inequality). 86 | --refine h.le.trans ?_ 87 | -- the next `have` statement is there simply to make the following `exact?` succeed: 88 | -- we should guess that `h.le` should be the "right" assumption to close the goal. 89 | --have := h.le 90 | --exact? 91 | · simp at h -- to convert the `¬ z < 0` to `0 ≤ z`. using `simp? at h` tells you the name of the lemma 92 | symm 93 | apply max_eq_left 94 | exact? 95 | done 96 | 97 | lemma abs_zero : Abs 0 = 0 := by 98 | unfold Abs -- this is not even needed in this case: `rfl` works directly 99 | split_ifs 100 | · exact rfl 101 | · exact rfl 102 | 103 | -- following the approach from before, we can prove this result by `unfold`ing and splitting if's 104 | lemma abs_neg (z : ℤ) : Abs (- z) = Abs z := by 105 | unfold Abs 106 | split_ifs with h k <;> linarith 107 | done 108 | 109 | -- however... we may not need to do that! 110 | example (z : ℤ) : Abs (- z) = Abs z := by 111 | rw [abs_eq_max, abs_eq_max, neg_neg, max_comm] 112 | done 113 | 114 | /- 115 | In fact, if the library already contains `max` and a well-developed API for it, 116 | it may make sense to *define* `Abs` in terms of `max` and then develop the API 117 | for `Abs` by relying on the one for `max` (and probably diverging from it, once 118 | we get into more juicy facts). 119 | 120 | Let's see how that would work out! 121 | -/ 122 | 123 | def Abs' (z : ℤ) : ℤ := max z (-z) 124 | 125 | lemma abs'_eq_max (z : ℤ) : Abs' z = max z (-z) := by 126 | rfl -- this is just the definition! 127 | done 128 | 129 | lemma abs'_neg (z : ℤ) : Abs' (- z) = Abs' z := by 130 | unfold Abs' 131 | rw [neg_neg, max_comm] 132 | done 133 | 134 | lemma abs'_eq_abs (z : ℤ) : Abs' z = if z < 0 then - z else z := by 135 | unfold Abs' 136 | split_ifs with h 137 | · simp 138 | exact Int.le_of_lt h 139 | · simp at h ⊢ 140 | assumption 141 | done 142 | 143 | /- 144 | This should feel like a much smoother experience! 145 | 146 | The main reason is that we could rely on the API for `max`, rather than on "hard" automation, 147 | provided for instance by `linarith`. 148 | 149 | When formalising a new definition, try to keep as close as possible to definitions that already exist. 150 | If you think that you have to make a big "leap", chances are that it will be easier to first define some 151 | intermediate notion and then continue with what you want. 152 | 153 | Lean will try hard to connect your "new" definition to the ones that it already knows about, 154 | and it will be an easier experience for you to teach it new trick in small, incremental baby steps! 155 | 156 | # Instances 157 | 158 | Sometimes, the "definition" that you want to make is to endow some "object" with some "structure": 159 | for instance you may want to define the component-wise addition on pairs of integers and 160 | prove that what you defined is an (additive) abelian group. 161 | 162 | Lean has a special mechanism to keep track of such "definitions" which, for instance, automatically allows 163 | you to use `+` as a notation for the operation. 164 | Another great feature is that if your newly defined `+`-operation actually induces an abelian group 165 | (i.e. you proved this!), then Lean will already know that therefore it is also an `AddMonoid` and you will 166 | be able to use the lemmas that `Mathlib` has about `AddMonoid`s. 167 | 168 | This mechanism is called the *typeclass system* and it is mostly painless to use. 169 | When assuming such a hypothesis (e.g. you want to prove a result about groups or topological spaces), 170 | then these assumptions are written inside square bracket (e.g. `[Group G]` or `[TopologicalSpace X]`). 171 | If you want a new object `N` that you defined to be registered by Lean as a `Group N`, then you should 172 | provide an `instance` of `Group` to `N`. 173 | 174 | Here is an example. 175 | We introduce the notion of an additive group to the pairs of integers. 176 | -/ 177 | 178 | #check add_sub_cancel_left 179 | 180 | #synth AddCommMonoid (ℤ × ℤ) 181 | #check add_sub_cancel_left (0 : ℤ × ℤ) 182 | 183 | @[ext] 184 | structure point where 185 | x : ℤ 186 | y : ℤ 187 | 188 | #check point 189 | #print point 190 | #print point.x 191 | #print point.y 192 | 193 | -- `inductive` is another way of generating definitions. 194 | -- i will not spend much time on it today, but there is *a lot* that can be said about them! 195 | -- if it turns out to be relevant for your projects, I will prepare a lecture about it. 196 | inductive nat 197 | | zero : nat 198 | | succ (n : nat) : nat 199 | 200 | -- Let's try with a definition of `even` 201 | def even (n : Nat) : Prop := match n with 202 | | 0 => True 203 | | n + 1 => ¬ even n 204 | 205 | #check Nat 206 | 207 | example : even 2 := by 208 | unfold even 209 | unfold even 210 | unfold even 211 | trivial 212 | done 213 | 214 | example : ¬ even 3 := by 215 | unfold even 216 | unfold even 217 | unfold even 218 | unfold even 219 | trivial 220 | done 221 | 222 | example {n : ℕ} (h : even n) : even (n + 2) := by 223 | unfold even 224 | unfold even 225 | simpa 226 | done 227 | 228 | -- a little unwieldy 229 | example : even 8 := by 230 | unfold even 231 | unfold even 232 | unfold even 233 | unfold even 234 | unfold even 235 | unfold even 236 | unfold even 237 | unfold even 238 | unfold even 239 | trivial 240 | done 241 | 242 | -- `repeat` can be useful 243 | example : even 8 := by 244 | repeat unfold even 245 | trivial 246 | done 247 | 248 | example (n : ℕ) : even (2 * n) := by 249 | induction n with 250 | | zero => simp? -- `simp` is stuck, since it does not know that `even 0` is `True` 251 | -- let's teach it! 252 | exact trivial -- this line is what we would not like to have to use 253 | | succ n ih => sorry 254 | done 255 | 256 | -- Let's try with a different definition of `even` 257 | def even' (n : Nat) : Prop := match n with 258 | | 0 => True 259 | | 1 => False 260 | | n + 2 => even' n 261 | 262 | example : even' 8 := by 263 | unfold even' 264 | unfold even' 265 | unfold even' 266 | unfold even' 267 | unfold even' 268 | trivial -- move me up! 269 | 270 | end TPwL_definitions 271 | --------------------------------------------------------------------------------