├── .gitignore ├── lean-toolchain ├── BrownCs22 ├── Demos │ ├── Readme.lean │ ├── Lecture02.lean │ ├── Lecture05.lean │ ├── SetIdentities.lean │ ├── Lecture03.lean │ ├── Lecture13.lean │ ├── Lecture21.lean │ ├── Lecture07.lean │ ├── Lecture06.lean │ ├── QuickReference.lean │ ├── Lecture08.lean │ ├── Lecture04.lean │ └── Lecture17.lean ├── Library │ ├── ModEq │ │ ├── Defs.lean │ │ └── Lemmas.lean │ ├── Defs.lean │ ├── ModSubst │ │ ├── Basic.lean │ │ └── ModRel.lean │ ├── Tactics.lean │ └── TruthTables.lean ├── Homework │ ├── Hw1.lean │ ├── Hw3.lean │ ├── Hw9.lean │ ├── Hw4.lean │ ├── Hw2.lean │ ├── Hw8.lean │ └── Hw7.lean └── Exercises │ ├── LogicIdentities.lean │ └── LogicIdentitiesSolutions.lean ├── Main.lean ├── BrownCs22.lean ├── .vscode └── settings.json ├── scripts ├── reset-all └── pull-updates ├── lakefile.lean ├── .gitpod.yml ├── lake-manifest.json ├── .docker └── gitpod │ └── Dockerfile └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /lake-packages -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:nightly-2023-02-10 -------------------------------------------------------------------------------- /BrownCs22/Demos/Readme.lean: -------------------------------------------------------------------------------- 1 | /- 2 | 3 | Nothing to see here yet! 4 | 5 | -/ -------------------------------------------------------------------------------- /Main.lean: -------------------------------------------------------------------------------- 1 | /- 2 | Please do not edit this file! 3 | -/ 4 | 5 | import BrownCs22 6 | -------------------------------------------------------------------------------- /BrownCs22.lean: -------------------------------------------------------------------------------- 1 | /- 2 | 3 | Nothing to see here yet! Please do not edit this file. 4 | 5 | -/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.minimap.enabled": false, 3 | "editor.insertSpaces": true, 4 | "editor.tabSize": 2, 5 | "editor.semanticTokenColorCustomizations": { 6 | "[Gitpod Light]": {"rules": {"other": "#335f66", "variable": "#7f197f"}} 7 | } 8 | } -------------------------------------------------------------------------------- /BrownCs22/Library/ModEq/Defs.lean: -------------------------------------------------------------------------------- 1 | /- Copyright (c) Heather Macbeth, 2023. All rights reserved. -/ 2 | -- import Math2001.Library.Division 3 | import Mathlib.Data.Int.Basic 4 | 5 | /-- Two integers are congruent modulo `n`, if their difference is a multiple of `n`. -/ 6 | def BrownCs22.Int.ModEq (n a b : ℤ) : Prop := n ∣ a - b 7 | 8 | notation:50 a " ≡ " b " [ZMOD " n "]" => BrownCs22.Int.ModEq n a b 9 | -------------------------------------------------------------------------------- /scripts/reset-all: -------------------------------------------------------------------------------- 1 | echo "Executing this script will erase any changes you have made to official course files." 2 | echo "It will not erase any files that you have created yourself." 3 | echo "Note to students using git: any TRACKED files you have created and committed WILL BE ERASED." 4 | read -p "Are you sure you wish to continue? (y/n) " -n 1 -r 5 | echo 6 | if [[ ! $REPLY =~ ^[Yy]$ ]] 7 | then 8 | exit 1 9 | fi 10 | 11 | git fetch origin 12 | git reset --hard origin/main 13 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | open Lake DSL 3 | 4 | 5 | require mathlib from git "https://github.com/leanprover-community/mathlib4" @ "e7e49c8a41239716cb79fa187a6bf66e7d82d710" 6 | require autograder from git "https://github.com/robertylewis/cs22-lean-autograder" @ "1c6119111649e9c18594be3b3722836025a96e86" 7 | 8 | package «brown-cs22» { 9 | -- add package configuration options here 10 | } 11 | 12 | lean_lib BrownCs22 { 13 | -- add library configuration options here 14 | } 15 | 16 | @[default_target] 17 | lean_exe «brown-cs22» { 18 | root := `Main 19 | } 20 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .docker/gitpod/Dockerfile 3 | 4 | vscode: 5 | extensions: 6 | - leanprover.lean4 7 | 8 | tasks: 9 | 10 | # These commands get entered because of clip-style chomping in the YAML processor 11 | - init: | 12 | lake exe cache get 13 | curl https://cs.brown.edu/~rlewis13/save-to-firestore-0.13.1.vsix -o save-to-firestore-0.13.1.vsix 14 | 15 | # Append to .bashrc so that every new shell can find our source control scripts in the path 16 | command: | 17 | echo 'export PATH=${PATH}:/workspace/CS22-Lean-2023/scripts' >> ${HOME}/.bashrc 18 | export PATH=${PATH}:/workspace/CS22-Lean-2023/scripts 19 | 20 | -------------------------------------------------------------------------------- /BrownCs22/Library/Defs.lean: -------------------------------------------------------------------------------- 1 | import Mathlib.Data.Nat.Basic 2 | import Mathlib.Data.Nat.Prime 3 | 4 | namespace BrownCs22 5 | namespace Nat 6 | 7 | def isOdd (n : ℕ) : Prop := 8 | ∃ k : ℕ, n = 2 * k + 1 9 | 10 | lemma quotient_remainder {a b c : ℕ} (h : a % b = c) : ∃ q, a = q*b + c := by 11 | use a/b 12 | rw [← h, mul_comm, Nat.div_add_mod] 13 | 14 | 15 | end Nat 16 | 17 | 18 | lemma Set.inter_union_cancel_left {α : Type u} {s t : Set α} : 19 | (s ∩ t) ∪ s = s := by simp 20 | 21 | lemma Set.inter_union_cancel_right {α : Type u} {s t : Set α} : 22 | (s ∩ t) ∪ t = t := by simp 23 | 24 | namespace Int 25 | 26 | -- def ModEq (n a b : ℤ) : Prop := n ∣ a - b 27 | 28 | 29 | 30 | 31 | -- notation:50 a " ≡ " b " [ZMOD " n "]" => Int.ModEq n a b 32 | 33 | end Int 34 | 35 | def totient (n : ℕ) : ℕ := ((List.range n).filter n.coprime).length 36 | 37 | 38 | end BrownCs22 39 | 40 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": 4, 2 | "packagesDir": "lake-packages", 3 | "packages": 4 | [{"git": 5 | {"url": "https://github.com/leanprover-community/mathlib4", 6 | "subDir?": null, 7 | "rev": "e7e49c8a41239716cb79fa187a6bf66e7d82d710", 8 | "name": "mathlib", 9 | "inputRev?": "e7e49c8a41239716cb79fa187a6bf66e7d82d710"}}, 10 | {"git": 11 | {"url": "https://github.com/gebner/quote4", 12 | "subDir?": null, 13 | "rev": "7ac99aa3fac487bec1d5860e751b99c7418298cf", 14 | "name": "Qq", 15 | "inputRev?": "master"}}, 16 | {"git": 17 | {"url": "https://github.com/JLimperg/aesop", 18 | "subDir?": null, 19 | "rev": "ba61f7fec6174d8c7d2796457da5a8d0b0da44c6", 20 | "name": "aesop", 21 | "inputRev?": "master"}}, 22 | {"git": 23 | {"url": "https://github.com/robertylewis/cs22-lean-autograder", 24 | "subDir?": null, 25 | "rev": "1c6119111649e9c18594be3b3722836025a96e86", 26 | "name": "autograder", 27 | "inputRev?": "1c6119111649e9c18594be3b3722836025a96e86"}}, 28 | {"git": 29 | {"url": "https://github.com/leanprover/std4", 30 | "subDir?": null, 31 | "rev": "de7e2a79905a3f87cad1ad5bf57045206f9738c7", 32 | "name": "std", 33 | "inputRev?": "main"}}]} 34 | -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture02.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Defs 2 | 3 | namespace Demo1 4 | open BrownCs22.Nat 5 | 6 | /- 7 | 8 | Welcome to Lean! 9 | 10 | We'll get a more detailed introduction later. 11 | For now, meet our new friend, the `#check` command. 12 | 13 | `#check ` tells us what kind of thing `` is. 14 | 15 | -/ 16 | 17 | #check 1 18 | 19 | #check 5 20 | 21 | /- 22 | 23 | Read `1 : ℕ` as saying, "`1` is a natural number." 24 | 25 | -/ 26 | 27 | #check 2 + 3 = 5 28 | 29 | #check 1 + 1 = 3 30 | 31 | /- 32 | 33 | How could I translate 34 | "every action has an equal but opposite reaction"? 35 | 36 | ... 37 | 38 | -/ 39 | 40 | /- 41 | 42 | But we can write things with quantifiers (`∀` and `∃`): 43 | 44 | -/ 45 | 46 | -- 47 | #check ∃ n : ℕ, n^2 % 10 = 6 48 | 49 | #check ∀ n : ℕ, Prime (n^2 + n + 41) 50 | 51 | 52 | /- 53 | 54 | `Prime` is a *predicate*. 55 | So is `isOdd`. 56 | -/ 57 | 58 | #check isOdd 59 | 60 | #check isOdd 5 61 | 62 | #check isOdd 10 63 | 64 | 65 | def P (n : ℕ) : Prop := 66 | sorry -- `n` is a perfect square 67 | 68 | 69 | #check P 22 70 | 71 | #check ∀ n : ℕ, P n 72 | 73 | #check ∃ n : ℕ, P n 74 | 75 | variable (n : ℕ) 76 | #check P (n + 1) 77 | 78 | -- #check P (n) + 1 79 | 80 | 81 | 82 | 83 | end Demo1 -------------------------------------------------------------------------------- /BrownCs22/Library/ModSubst/Basic.lean: -------------------------------------------------------------------------------- 1 | /- 2 | Copyright (c) 2023 Heather Macbeth. All rights reserved. 3 | Released under Apache 2.0 license as described in the file LICENSE. 4 | Authors: Heather Macbeth 5 | -/ 6 | import Mathlib.Tactic.SolveByElim 7 | 8 | register_label_attr ineq_rules 9 | 10 | register_label_attr ineq_extra 11 | 12 | register_label_attr mod_rules 13 | 14 | register_label_attr mod_extra 15 | 16 | register_label_attr iff_rules 17 | 18 | syntax (name := RelSyntax) "mod_subst" " [" term,* "] " : tactic 19 | syntax (name := ExtraSyntax) "extra" : tactic 20 | 21 | open Lean Mathlib Tactic 22 | 23 | def RelConfig : SolveByElim.Config := 24 | { transparency := .instances 25 | -- On applying a lemma or hypothesis successfully, don't backtrack 26 | failAtMaxDepth := false 27 | maxDepth := 50 } 28 | 29 | def Lean.MVarId.Rel (attr : Name) (add : List Term) (m : MessageData) 30 | (disch : MVarId → MetaM (Option (List MVarId)) := fun _ => pure none) 31 | (proc : List MVarId → List MVarId → MetaM (Option (List MVarId)) := fun _ _ => pure none) 32 | (g : MVarId) : 33 | MetaM (List MVarId) := do 34 | let cfg : SolveByElim.Config := { RelConfig with discharge := disch, proc := proc } 35 | let [] ← SolveByElim.solveByElim.processSyntax cfg true false add [] #[mkIdent attr] [g] 36 | | throwError m 37 | return [] 38 | -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture05.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | /- 4 | 5 | Wrapping up Lecture 4: here's how we can work with *implications* and *negations*. 6 | 7 | 8 | if we know `hpq : p → q`, and our goal is to prove `q`, 9 | then `apply hpq` will change the goal to `p`. 10 | 11 | -/ 12 | 13 | example : p → (p → q) → q := by 14 | intro hp 15 | intro hpq 16 | apply hpq 17 | assumption 18 | 19 | /- 20 | 21 | Alternatively, if we know `hpq : p → q` and `hp : p`, 22 | we can "reason forward" by writing `have hq : q := hpq hp`. 23 | This adds a new hypothesis `hq : q` without changing the goal. 24 | This syntax is a little clunky, but we'll use it in a more general setting later. 25 | 26 | -/ 27 | 28 | example : p → (p → q) → q ∧ p := by 29 | intro hp 30 | intro hpq 31 | have hq : q := hpq hp 32 | split_goal 33 | { assumption } 34 | { assumption } 35 | 36 | 37 | 38 | 39 | 40 | /- 41 | If you *know* a negation: use it to find a contradiction. 42 | 43 | -/ 44 | 45 | variable (p q r : Prop) 46 | 47 | example : (¬ p ∧ (p ∨ q)) → q := by 48 | intro hand 49 | eliminate hand with hnp hpq 50 | eliminate hpq with hp hq 51 | { contradiction } 52 | { assumption } 53 | 54 | /- 55 | If you want to *prove* a negation: assume the statement is true, 56 | and find a contradiction. 57 | -/ 58 | 59 | example : ¬ (p ∧ ¬ p) := by 60 | intro hpnp 61 | eliminate hpnp with hp hnp 62 | contradiction 63 | -------------------------------------------------------------------------------- /BrownCs22/Demos/SetIdentities.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Defs 2 | open Set BrownCs22.Set 3 | 4 | /- 5 | 6 | At 7 | you can find a list of set identities, also called "rewrite rules." 8 | Each rule on this list has a name in Lean! The names appear below, 9 | following the order on the reference sheet. 10 | 11 | -/ 12 | 13 | -- commutative laws 14 | #check union_comm 15 | #check inter_comm 16 | 17 | -- associative laws 18 | #check union_assoc 19 | #check inter_assoc 20 | 21 | -- distributive laws 22 | #check union_inter_distrib_left 23 | #check union_inter_distrib_right 24 | #check inter_union_distrib_left 25 | #check inter_union_distrib_right 26 | 27 | -- identity laws 28 | #check union_empty 29 | #check inter_univ 30 | 31 | -- complement laws 32 | #check union_compl_self 33 | #check inter_compl_self 34 | 35 | -- double complement law 36 | #check compl_compl 37 | 38 | -- idempotent laws 39 | #check union_self 40 | #check inter_self 41 | 42 | -- universal bound laws 43 | #check union_univ 44 | #check inter_empty 45 | 46 | -- de morgan's laws 47 | #check compl_union 48 | #check compl_inter 49 | 50 | -- absorption laws 51 | #check union_inter_cancel_left 52 | #check union_inter_cancel_right 53 | #check inter_union_cancel_left 54 | #check inter_union_cancel_right 55 | 56 | -- complements of universe and empty 57 | #check compl_univ 58 | #check compl_empty 59 | 60 | -- set difference law 61 | #check diff_eq -------------------------------------------------------------------------------- /.docker/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 python3-pip -y && apt-get clean 10 | 11 | RUN pip3 install requests 12 | 13 | RUN useradd -l -u 33333 -G sudo -md /home/gitpod -s /bin/bash -p gitpod gitpod \ 14 | # passwordless sudo for users in the 'sudo' group 15 | && sed -i.bkp -e 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers 16 | USER gitpod 17 | WORKDIR /home/gitpod 18 | 19 | SHELL ["/bin/bash", "-c"] 20 | 21 | # gitpod bash prompt 22 | RUN { echo && echo "PS1='\[\033[01;32m\]\u\[\033[00m\] \[\033[01;34m\]\w\[\033[00m\]\$(__git_ps1 \" (%s)\") $ '" ; } >> .bashrc 23 | 24 | # install elan 25 | RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none 26 | 27 | # install whichever toolchain mathlib is currently using 28 | RUN . ~/.profile && elan toolchain install $(curl https://raw.githubusercontent.com/leanprover-community/mathlib4/master/lean-toolchain) 29 | 30 | ENV PATH="/home/gitpod/.local/bin:/home/gitpod/.elan/bin:${PATH}" 31 | 32 | # fix the infoview when the container is used on gitpod: 33 | ENV VSCODE_API_VERSION="1.50.0" 34 | 35 | # ssh to github once to bypass the unknown fingerprint warning 36 | RUN ssh -o StrictHostKeyChecking=no github.com || true 37 | 38 | # run sudo once to suppress usage info 39 | RUN sudo echo finished 40 | -------------------------------------------------------------------------------- /scripts/pull-updates: -------------------------------------------------------------------------------- 1 | # Stores id of HEAD commit 2 | HC=$(git rev-parse HEAD) 3 | 4 | # Just in case user has env variable with this name, very unlikely 5 | unset -v STASHED 6 | 7 | # Stash changes - branch executes iff output did not contain "No local...", i.e. iff a stash was actually made 8 | if ! [[ $(git stash push -u) == *"No local changes to save"* ]]; then 9 | STASHED= 10 | fi 11 | 12 | function handle_merge_conflict () { 13 | 14 | # Reset index and working tree to their original state (before stash) 15 | git reset --hard $HC >/dev/null 16 | 17 | if [[ -v STASHED ]] ; then 18 | git stash pop >/dev/null 19 | fi 20 | 21 | echo 22 | echo "We cannot get the latest changes to the class materials, because you have local changes that conflict with them." 23 | echo "The conflicting files should be visible in the trace message above, after the word CONFLICT." 24 | echo 'To erase your local changes, run the command `reset-all`.' # uses single quotes to avoid backtick tomfoolery 25 | echo "(Warning: this cannot be undone.)" 26 | echo "Remember that you should only be modifying files in your personal working directories." 27 | exit 1 28 | } 29 | 30 | # See if automatic merge of upstream commits fails 31 | # Since we stashed, failure means the student is using git on their own and committed changes to class files 32 | if ! git pull --no-rebase; then 33 | handle_merge_conflict 34 | fi 35 | 36 | # Pull was successful, attempt to pop from stash if necessary 37 | if [[ -v STASHED ]] && ! git stash pop; then 38 | handle_merge_conflict 39 | fi 40 | 41 | # lean magic (I don't get this tbh) 42 | lake exe cache get 43 | -------------------------------------------------------------------------------- /BrownCs22/Library/ModSubst/ModRel.lean: -------------------------------------------------------------------------------- 1 | /- 2 | Copyright (c) 2023 Heather Macbeth. All rights reserved. 3 | Released under Apache 2.0 license as described in the file LICENSE. 4 | Authors: Heather Macbeth 5 | -/ 6 | import BrownCs22.Library.ModEq.Lemmas 7 | import BrownCs22.Library.ModSubst.Basic 8 | 9 | open Lean Elab Tactic 10 | 11 | -- syntax (name := ModRelSyntax) "mod_subst" " [" term,* "] " : tactic 12 | 13 | elab_rules : tactic | `(tactic| mod_subst [$t,*]) => do 14 | liftMetaTactic <| 15 | Lean.MVarId.Rel `mod_rules t.getElems.toList 16 | "cannot prove this by 'substituting' the listed relationships" 17 | 18 | -- macro_rules | `(tactic| mod_subst [$t,*]) => `(tactic| mod_rel [$t,*]) 19 | 20 | syntax (name := ModExtraSyntax) "mod_extra" : tactic 21 | 22 | elab_rules : tactic | `(tactic| mod_extra) => do 23 | liftMetaTactic <| 24 | Lean.MVarId.Rel `mod_extra [] 25 | "the two sides don't differ by a neutral quantity for the relation" 26 | 27 | macro_rules | `(tactic| extra) => `(tactic| mod_extra) 28 | 29 | attribute [mod_rules] 30 | BrownCs22.Int.ModEq.refl 31 | -- hopefully, the order here prioritizes `add_right` and `add_left` over `add` 32 | BrownCs22.Int.ModEq.add_right BrownCs22.Int.ModEq.add_left BrownCs22.Int.ModEq.add 33 | BrownCs22.Int.ModEq.sub_right BrownCs22.Int.ModEq.sub_left BrownCs22.Int.ModEq.sub 34 | BrownCs22.Int.ModEq.mul_right BrownCs22.Int.ModEq.mul_left BrownCs22.Int.ModEq.mul 35 | BrownCs22.Int.ModEq.neg BrownCs22.Int.ModEq.pow 36 | 37 | -- attribute [mod_extra] 38 | -- Int.modEq_fac_zero Int.modEq_fac_zero' Int.modEq_zero_fac Int.modEq_zero_fac' 39 | -- Int.modEq_add_fac_self Int.modEq_add_fac_self' Int.modEq_add_fac_self'' Int.modEq_add_fac_self''' 40 | -- Int.modEq_sub_fac_self Int.modEq_sub_fac_self' Int.modEq_sub_fac_self'' Int.modEq_sub_fac_self''' 41 | -- Int.modEq_add_fac_self_symm Int.modEq_add_fac_self_symm' Int.modEq_add_fac_self_symm'' Int.modEq_add_fac_self_symm''' 42 | -- Int.modEq_sub_fac_self_symm Int.modEq_sub_fac_self_symm' Int.modEq_sub_fac_self_symm'' Int.modEq_sub_fac_self_symm''' 43 | -- Int.ModEq.add_right Int.ModEq.add_left 44 | -- Int.ModEq.sub_right Int.ModEq.sub_left 45 | -- Int.ModEq.refl 46 | -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture03.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Defs 2 | import BrownCs22.Library.TruthTables 3 | 4 | /- 5 | 6 | Lean is a language that we will be using in CS22 this year. 7 | 8 | If you're in this class, you've most likely used a programming language before. 9 | Lean is a programming language too. But we'll be using it for a different reason: 10 | Lean lets us state *propositions*, write *proofs* of these propositions, and *check* 11 | automatically that these proofs are correct. 12 | 13 | Some basic Lean syntax: 14 | 15 | * Block comments, like this one, are written as /- ... -/. 16 | * Line comments begin with -- 17 | * If a file imports other files, this appears at the very top of the file. 18 | You shouldn't change these imports! 19 | 20 | 21 | Remember from Lecture 2, 22 | the `#check` command asks Lean to tell us "what kind of thing" something is. 23 | (This will be very useful for us!) 24 | 25 | For example, `1` is a natural number (`ℕ`), 26 | and `1 + 1 = 2` is a proposition (`Prop`). 27 | 28 | -/ 29 | 30 | #check 1 31 | 32 | #check 1 + 1 = 2 33 | 34 | 35 | /- 36 | 37 | We've just introduced *propositional formulas*, 38 | which are built out of *atoms* and *connectives*. 39 | 40 | In normal math, it's common for us to introduce some atoms by writing 41 | "let p, q, and r be propositions". 42 | 43 | In Lean, we write: 44 | 45 | -/ 46 | 47 | variable (p q r : Prop) 48 | 49 | #check p 50 | #check p ∧ q 51 | #check p ∧ q → r 52 | #check p ∨ q ∨ p ∧ r ∧ ¬ (p ∧ q ∧ r) 53 | 54 | /- 55 | 56 | A few things to note here. 57 | 58 | * In the third `#check` above, if you hover over the output in the infoview, 59 | you can see how this formula is parenthesized! 60 | 61 | * Those unicode symbols are input using \ . To write the third line I typed 62 | `p \and q \to r`. But there are lots of variants. 63 | They usually match the LaTeX command. 64 | You can hover over a symbol to see how it was typed. Here's a useful list: 65 | 66 | * `∧`: and, wedge 67 | * `∨`: or, vee 68 | * `¬`: not, neg 69 | * `→`: to, imp, rightarrow 70 | * `↔`: iff 71 | * `ℕ`: N, nat 72 | * `ℤ`: Z, int 73 | * `∀`: all, forall 74 | * `∃`: ex, exist 75 | 76 | Try it out: write down some propositional formulas. 77 | If you want more atoms than `p`, `q`, and `r`, you can write a new line 78 | `variable (s t u : Prop)` (using whatever letters you would like). 79 | 80 | -/ 81 | 82 | 83 | 84 | #truth_table p ∧ q ∨ r 85 | -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture13.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | /- 4 | 5 | # Induction in Lean 6 | 7 | There are tactics corresponding to the induction principles we saw in class. 8 | Think about it: how many goals should each induction principle produce? 9 | What hypotheses do we have in each goal? 10 | 11 | 12 | 13 | * `basic_induction` is the normal, one-step, starting-from-0 induction: 14 | 15 | -/ 16 | 17 | example (P : ℕ → Prop) : ∀ n, P n := by 18 | basic_induction 19 | { sorry } -- show `P 0` 20 | { intro n 21 | intro hn 22 | sorry } -- assuming `P n`, show `P (n + 1)` 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | /- 32 | 33 | * `induction_from_starting_point`: again, as the name implies, this lets 34 | us start a normal induction proof from a value besides 0. 35 | 36 | -/ 37 | 38 | example (P : ℕ → Prop) : ∀ n ≥ 4, P n := by 39 | induction_from_starting_point 40 | { sorry } -- prove `P 4` 41 | { intro n 42 | intro hn4 43 | intro hpn 44 | sorry } -- assuming `4 ≤ n` and `P n`, show `P (n + 1)` 45 | 46 | 47 | 48 | 49 | /- 50 | 51 | Here's an example we did in class, typed into Lean! 52 | 53 | -/ 54 | 55 | example : ∀ n ≥ 5, 2^n > n^2 := by 56 | induction_from_starting_point 57 | { numbers } 58 | { intro n 59 | intro hn 60 | intro ih 61 | have hle : 2*n + 1 ≤ n^2 62 | { nlinarith } 63 | calc (n+1)^2 = n^2 + 2*n + 1 := by linarith 64 | _ ≤ n^2 + n^2 := by linarith 65 | _ < 2^n + 2^n := by linarith 66 | _ = 2^n * 2 := by linarith 67 | _ = 2 ^ (n + 1) := by rw [pow_add, pow_one] 68 | } 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | /- 80 | 81 | * `strong_induction` is, as the name implies, an induction principle 82 | that lets us assume `P` holds of *all* smaller numbers. 83 | Why is there no base case here? 84 | 85 | -/ 86 | 87 | example (P : ℕ → Prop) : ∀ n, P n := by 88 | strong_induction 89 | intro n 90 | intro hn 91 | sorry -- assuming `∀ (m : ℕ), m < n → P m`, show `P n` 92 | 93 | 94 | /- 95 | 96 | There's no base case because if we can prove the strong induction step 97 | for every `n`, it must hold for `0`, which implies `P 0`: 98 | 99 | -/ 100 | 101 | example (P : ℕ → Prop) (hn : ∀ (n : ℕ), (∀ (m : ℕ), m < n → P m) → P n) : 102 | P 0 := by 103 | have hn0 : (∀ (m : ℕ), (m < 0 → P m)) → P 0 := hn 0 104 | apply hn0 105 | intro m hm 106 | contradiction 107 | 108 | -------------------------------------------------------------------------------- /BrownCs22/Library/Tactics.lean: -------------------------------------------------------------------------------- 1 | import Lean.Elab 2 | import Mathlib.Tactic.LeftRight 3 | import Mathlib.Tactic.Cases 4 | import Mathlib.Tactic.ApplyFun 5 | import Mathlib.Tactic.Existsi 6 | import Mathlib.Tactic.NormNum 7 | import Mathlib.Tactic.Positivity 8 | import Mathlib.Tactic.Linarith 9 | import Mathlib.Tactic.Polyrith 10 | 11 | open Lean hiding Rat mkRat 12 | open Meta Elab Tactic Lean.Parser.Tactic 13 | 14 | elab (name := split_goal) "split_goal " : tactic => withMainContext do 15 | let tgt := (← instantiateMVars (← whnfR (← getMainTarget))) 16 | match tgt.and?, tgt.iff? with 17 | | .none, .none => throwError "split_goal only applies to ∧ and ↔ goals" 18 | | _, _ => evalConstructor default 19 | 20 | 21 | open private getElimNameInfo in evalCases in 22 | elab (name := eliminate) "eliminate " tgts:(casesTarget,+) usingArg:((" using " ident)?) 23 | withArg:((" with " (colGt binderIdent)+)?) : tactic => focus do 24 | let targets ← elabCasesTargets tgts.1.getSepArgs 25 | let g ← getMainGoal 26 | g.withContext do 27 | let elimInfo ← getElimNameInfo usingArg targets (induction := false) 28 | let targets ← addImplicitTargets elimInfo targets 29 | let result ← withRef tgts <| ElimApp.mkElimApp elimInfo targets (← g.getTag) 30 | let elimArgs := result.elimApp.getAppArgs 31 | let targets ← elimInfo.targetsPos.mapM (instantiateMVars elimArgs[·]!) 32 | let motive := elimArgs[elimInfo.motivePos]! 33 | let g ← generalizeTargetsEq g (← inferType motive) targets 34 | let (targetsNew, g) ← g.introN targets.size 35 | g.withContext do 36 | ElimApp.setMotiveArg g motive.mvarId! targetsNew 37 | g.assign result.elimApp 38 | let subgoals ← ElimApp.evalNames elimInfo result.alts withArg 39 | (numEqs := targets.size) (toClear := targetsNew) 40 | setGoals subgoals.toList 41 | 42 | macro "reflexivity" : tactic => `(tactic |rfl) 43 | 44 | section 45 | 46 | open Lean.Meta Qq Lean.Elab Term 47 | open Lean.Parser.Tactic Mathlib.Meta.NormNum 48 | 49 | /-- 50 | Normalize numerical expressions. Supports the operations `+` `-` `*` `/` `⁻¹` and `^` 51 | over numerical types such as `ℕ`, `ℤ`, `ℚ`, `ℝ`, `ℂ` and some general algebraic types, 52 | and can prove goals of the form `A = B`, `A ≠ B`, `A < B` and `A ≤ B`, where `A` and `B` are 53 | numerical expressions. 54 | -/ 55 | elab (name := numbers) "numbers" loc:(location ?) : tactic => 56 | elabNormNum mkNullNode loc (simpOnly := true) (useSimp := false) 57 | end 58 | 59 | macro "set_simplify" : tactic => `(tactic | simp only [Set.mem_union, Set.mem_compl_iff, Set.mem_inter_iff, Set.mem_diff] at *) 60 | 61 | macro "linarith" : tactic => `(tactic| first | ring1 | linarith) 62 | 63 | @[elab_as_elim] 64 | lemma Nat.induction {P : ℕ → Prop} : P 0 → (∀ n, P n → P (n+1)) → (∀ n, P n) := 65 | Nat.rec 66 | 67 | macro "basic_induction" : tactic => 68 | `(tactic| apply Nat.induction) 69 | 70 | macro "strong_induction" : tactic => 71 | `(tactic| (intro n; refine Nat.strong_induction_on n ?_; clear n)) 72 | 73 | 74 | macro "induction_from_starting_point" : tactic => 75 | `(tactic| apply Nat.le_induction) 76 | 77 | 78 | macro "strong_induction" : tactic => 79 | `(tactic| (intro n; refine Nat.strong_induction_on n ?_; clear n)) 80 | 81 | 82 | -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture21.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Defs 2 | import BrownCs22.Library.Tactics 3 | import Mathlib.Data.Nat.ModEq 4 | import Mathlib.Data.Int.GCD 5 | 6 | open BrownCs22 7 | 8 | 9 | /- 10 | 11 | Let's check computationally that our RSA algorithm works. 12 | 13 | -/ 14 | 15 | -- given a public key and a modulus `n`, we can encrypt a message. 16 | def rsa_encrypt (public_key : ℕ) (n : ℕ) (message : ℕ) : ℕ := 17 | (message ^ public_key) % n 18 | 19 | -- given a private key and a modulus `n`, we can decrypt a message. 20 | def rsa_decrypt (private_key : ℕ) (n : ℕ) (encrypted_message : ℕ) : ℕ := 21 | (encrypted_message ^ private_key) % n 22 | 23 | 24 | 25 | -- let's choose `n` to be the product of two primes. 26 | def p := 113 27 | def q := 37 28 | def n := p * q 29 | 30 | #eval Nat.Prime p 31 | #eval Nat.Prime q 32 | 33 | -- We choose our public key that is relatively prime to `(p - 1)*(q - 1)`. 34 | def public_key := 13 35 | 36 | #eval Nat.gcd public_key ((p - 1)*(q - 1)) 37 | 38 | 39 | -- Now we need an inverse to the public key mod `(p - 1)*(q - 1)`. 40 | -- We get this from the extended Euclidean algorithm. 41 | #eval Nat.xgcd public_key ((p - 1)*(q - 1)) 42 | 43 | def private_key := 1861 44 | 45 | -- double check it's an inverse 46 | #eval private_key * public_key % ((p - 1)*(q - 1)) 47 | 48 | 49 | 50 | 51 | -- Okay! Let's choose a message. 52 | def message := 1034 53 | 54 | def encrypted_message := rsa_encrypt public_key n message 55 | 56 | #eval encrypted_message 57 | 58 | 59 | def decrypted_message := rsa_decrypt private_key n encrypted_message 60 | 61 | -- encrypting and decrypting the message produces the same output! 62 | #eval decrypted_message 63 | 64 | 65 | 66 | 67 | -- We can state, and (mostly) prove, the partial correctness theorem from class! 68 | theorem rsa_correct 69 | (p q : ℕ) (public_key private_key : ℕ) (message : ℕ) 70 | (hp : Prime p) (hq : Prime q) 71 | (h_pub_pri : public_key * private_key ≡ 1 [MOD (p - 1)*(q - 1)]) 72 | (h_msg : message < p*q) 73 | (h_rel_prime : Nat.gcd msg (p*q) = 1) : 74 | 75 | rsa_decrypt private_key (p*q) 76 | (rsa_encrypt public_key (p*q) message) 77 | = message := 78 | by 79 | dsimp [rsa_decrypt, rsa_encrypt] 80 | 81 | have h_lin_combo : ∃ k, public_key * private_key = 1 + (p - 1)*(q - 1)*k := 82 | sorry 83 | 84 | eliminate h_lin_combo with k hk 85 | 86 | calc 87 | (message ^ public_key % (p * q)) ^ private_key % (p * q) 88 | = (message ^ public_key) ^ private_key % (p * q) := by rw [← Nat.pow_mod] 89 | _ = message ^ (public_key * private_key) % (p * q) := by rw [pow_mul] 90 | _ = message ^ (1 + (p - 1)*(q - 1)*k) % (p * q) := by rw [hk] 91 | _ = message ^ (1 + totient (p*q)*k) % (p * q) := by sorry 92 | _ = (message * (message ^ totient (p * q))^k) % (p * q) := by rw [pow_add, pow_one, pow_mul] 93 | _ = (message % (p * q)) * ((message ^ totient (p * q))^k % (p * q)) % (p * q) 94 | := by rw [Nat.mul_mod] 95 | _ = (message % (p * q)) * 1 % (p * q) := by sorry 96 | _ = message % (p * q) := by rw [mul_one, Nat.mod_mod] 97 | _ = message := by rw [Nat.mod_eq_of_lt h_msg] 98 | -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture07.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.Defs 3 | 4 | /- 5 | 6 | # First order logic in Lean 7 | 8 | We were a little vague in class about what function symbols, predicate symbols, 9 | etc. we were going to allow in our formulas. 10 | In Lean, we need to be more precise. 11 | 12 | Symbols in Lean must have *types*: 13 | a predicate holds on `ℕ`, or on `ℤ`, or on some arbitrary type `T`, 14 | but not on all at once. 15 | 16 | We can introduce a new predicate symbol that takes one natural number argument 17 | as follows: 18 | 19 | -/ 20 | 21 | variable (P : ℕ → Prop) 22 | 23 | #check P 3 24 | 25 | /- 26 | 27 | Notice that we don't need to write parentheses around the argument. 28 | We can, though: 29 | 30 | -/ 31 | 32 | #check P (3) 33 | 34 | /- 35 | 36 | If we want a predicate symbol that takes two arguments: 37 | 38 | -/ 39 | 40 | variable (R : ℕ → ℕ → Prop) 41 | 42 | #check R 3 4 43 | 44 | -- this doesn't work! 45 | -- #check R (3, 4) 46 | 47 | /- 48 | 49 | Finally, Lean has lots of predicates and function symbols built in. 50 | Want to use `+`, `*`, `=`, all that familiar stuff? It's here already. 51 | 52 | -/ 53 | 54 | 55 | /- 56 | 57 | ## Universal introduction 58 | 59 | 60 | We said that to prove `∀ x : T, ...` goals, we got to introduce a new 61 | `x : T`. The tactic for this proof rule is familiar. The pattern here 62 | sounds a lot like implication: move something from the goal to the context. 63 | 64 | We use the tactic `intro` again! 65 | 66 | The `reflexivity` tactic will prove things equal to themselves. 67 | This is a very basic property of `=`. 68 | 69 | -/ 70 | 71 | example : ∀ x : ℤ, x = x := by 72 | intro x 73 | reflexivity 74 | 75 | 76 | -- Every even integer greater than two is the sum of two primes. 77 | example : ∀ n : ℕ, Even n ∧ (n > 2) → ∃p q : ℕ, Prime p ∧ Prime q ∧ (n = p + q) := by 78 | intro n 79 | intro h_and 80 | sorry 81 | 82 | 83 | /- 84 | 85 | ## Universal elimination 86 | 87 | If we know `hall : ∀ x : T, H x`, we can conclude `H t` for any `t : T`. 88 | We again do this just like implication elimination: 89 | `have ht : H t := hall t`. 90 | 91 | We can also use `apply`. 92 | 93 | 94 | Note: I've started this example with the hypothesis `hxp` "pre-intro'ed." 95 | Putting it left of the `:` means it is in my context at the beginning. 96 | This saves us a line. 97 | 98 | -/ 99 | 100 | example (hxp : ∀ x : ℕ, P x) : P 5 ∧ P 6 := by 101 | split_goal 102 | 103 | -- method 1: using `have` 104 | { have hP5 : P 5 := hxp 5 105 | assumption } 106 | 107 | -- method 2: using apply 108 | { apply hxp } 109 | 110 | 111 | 112 | /- 113 | 114 | ## Existential introduction 115 | 116 | To prove an `exists` goal, we provide a witness. 117 | The tactic for giving a witness is `existsi` (for "exists introduction," creatively). 118 | 119 | The tactic `numbers` proves silly obvious facts about explicit numbers. 120 | (It doesn't know anything about variables, like `x`.) 121 | 122 | -/ 123 | 124 | example : ∃ x : ℕ, x > 10 := by 125 | existsi 1000 126 | numbers 127 | 128 | 129 | /- 130 | 131 | ## Existential elimination 132 | 133 | This will be familiar again! 134 | To use an `exists` hypothesis, we can use the good old `eliminate` tactic 135 | to get a witness and a hypothesis about that witness. 136 | 137 | -/ 138 | 139 | example (hex : ∃ x : ℕ, P x) : 0 = 0 := by 140 | eliminate hex with k hPk 141 | numbers 142 | 143 | example (hex : ∃ x : ℕ, P x) (hall : ∀ x : ℕ, P x → P (x + 1)) : 144 | ∃ x : ℕ, P x ∧ P (x + 1) := by 145 | eliminate hex with k hPk 146 | have hk : P k → P (k + 1) := hall k 147 | have hPk1 : P (k + 1) := hk hPk 148 | existsi k 149 | split_goal 150 | { assumption } 151 | { assumption } 152 | -------------------------------------------------------------------------------- /BrownCs22/Homework/Hw1.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | /- 4 | 5 | # Welcome to the Lean section of HW1! 6 | 7 | Some general guidelines, before we get started. 8 | 9 | * When you're doing Lean homework assignments, including this one, 10 | do *not* edit any of the `import` statements above the 11 | opening comment. This will most likely break our autograder. 12 | 13 | * Speaking of: when you submit this on Gradescope, you'll 14 | upload it to a separate assignment than the PDF with your other 15 | solutions. That's because we can autograde Lean, but not normal 16 | math! In the future, we hope to let you upload both files to the 17 | same assignment. But autograders are tricky and we haven't 18 | figured it out yet. 19 | 20 | * Your goal in this assignment is to replace the `sorry`s in each 21 | part with completed proofs. If you can't finish a problem and 22 | want to submit the assignment half-complete, that's fine -- 23 | but try to "finish" the proof by putting in more `sorry`s. 24 | 25 | For example, if the original problem looks like this: 26 | -/ 27 | 28 | theorem example_1 (p : Prop) : p → p → p := by 29 | sorry 30 | 31 | /- 32 | and you work on it, but get stuck at this stage and can't finish: 33 | 34 | theorem example_1 (p : Prop) : p → p → p := by 35 | intro hp1 36 | intro hp2 37 | 38 | then you should "finish" it by writing `sorry` again: 39 | -/ 40 | 41 | theorem example_1' (p : Prop) : p → p → p := by 42 | intro hp1 43 | intro hp2 44 | sorry 45 | 46 | /- 47 | Notice that, with the `sorry`, `example_1` is highlighted in yellow. 48 | Without the sorry, there is a red error message. 49 | It's safest to submit a file with no red error messages, 50 | to avoid any unexpected autograder failures. 51 | The autograder will give you points if all of the warnings and errors are gone. 52 | 53 | Let's get started! 54 | -/ 55 | 56 | variable (p q r s : Prop) 57 | 58 | /- 59 | 60 | ## Problem 1 61 | 62 | Fill in the `sorry` in the proof below. 63 | The elimination and introduction rules we talked about in Lecture 4 64 | will be very helpful here! 65 | 66 | 67 | -/ 68 | 69 | /- 3 points -/ 70 | theorem problem_1 : (p ∧ q ∧ r) → (p ∧ r) := by 71 | sorry 72 | 73 | 74 | 75 | /- 76 | 77 | ## Problem 2 78 | 79 | Consider what the theorem below is saying! 80 | "If the truth of `p` implies the falsity of `q`, 81 | then `p` and `q` cannot both be true." 82 | 83 | Does that seem like a reasonable statement? 84 | What do the truth tables of `p → ¬ q` and `¬ (p ∧ q)` look like? 85 | (You do not need to write an answer, but think about it for a moment. 86 | Try the `#truth_table` command if you want.) 87 | 88 | 89 | Again, your task is to fill in the `sorry` below to prove this statement. 90 | 91 | -/ 92 | 93 | /- 3 points -/ 94 | theorem problem_2 : (p → ¬ q) → ¬ (p ∧ q) := by 95 | sorry 96 | 97 | 98 | /- 99 | ## Problem 3 100 | 101 | This one's a little tricky! Let's reason through it in natural language. 102 | 103 | We want to prove that, if we know `((p ∨ q) ∧ (p → r) ∧ (q → s))`, 104 | then we know `r ∨ s`. 105 | So suppose that we know `((p ∨ q) ∧ (p → r) ∧ (q → s))`. 106 | Our goal is to show that `r ∨ s` follows. 107 | 108 | From that long statement, we know three facts: `p ∨ q`, `p → r`, and `q → s`. 109 | We'll reason by cases on `p ∨ q`. 110 | 111 | First, if we know `p`, then we know `r`, because `p → r`. 112 | And if we know `r` then we know `r ∨ s`. 113 | 114 | Second, if we know `q`, then we know `s`, because `q → s`. 115 | And if we know `s` then we know `r ∨ s`. 116 | 117 | That completes our proof! 118 | 119 | Your task: translate this argument to Lean. 120 | 121 | -/ 122 | 123 | /- 4 points -/ 124 | theorem problem_3 : ((p ∨ q) ∧ (p → r) ∧ (q → s)) → (r ∨ s) := by 125 | sorry -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture06.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.Defs 3 | 4 | /- 5 | 6 | (Note: we'll actually cover this in lecture 7, so it's duplicated 7 | in `Lecture07.lean`.) 8 | 9 | # First order logic in Lean 10 | 11 | We were a little vague in class about what function symbols, predicate symbols, 12 | etc. we were going to allow in our formulas. 13 | In Lean, we need to be more precise. 14 | 15 | Symbols in Lean must have *types*: 16 | a predicate holds on `ℕ`, or on `ℤ`, or on some arbitrary type `T`, 17 | but not on all at once. 18 | 19 | We can introduce a new predicate symbol that takes one natural number argument 20 | as follows: 21 | 22 | -/ 23 | 24 | variable (P : ℕ → Prop) 25 | 26 | #check P 3 27 | 28 | /- 29 | 30 | Notice that we don't need to write parentheses around the argument. 31 | We can, though: 32 | 33 | -/ 34 | 35 | #check P (3) 36 | 37 | /- 38 | 39 | If we want a predicate symbol that takes two arguments: 40 | 41 | -/ 42 | 43 | variable (R : ℕ → ℕ → Prop) 44 | 45 | #check R 3 4 46 | 47 | -- this doesn't work! 48 | -- #check R (3, 4) 49 | 50 | /- 51 | 52 | Finally, Lean has lots of predicates and function symbols built in. 53 | Want to use `+`, `*`, `=`, all that familiar stuff? It's here already. 54 | 55 | -/ 56 | 57 | 58 | /- 59 | 60 | ## Universal introduction 61 | 62 | 63 | We said that to prove `∀ x : T, ...` goals, we got to introduce a new 64 | `x : T`. The tactic for this proof rule is familiar. The pattern here 65 | sounds a lot like implication: move something from the goal to the context. 66 | 67 | We use the tactic `intro` again! 68 | 69 | The `reflexivity` tactic will prove things equal to themselves. 70 | This is a very basic property of `=`. 71 | 72 | -/ 73 | 74 | example : ∀ x : ℤ, x = x := by 75 | intro x 76 | reflexivity 77 | 78 | 79 | -- Every even integer greater than two is the sum of two primes. 80 | example : ∀ n : ℕ, Even n ∧ (n > 2) → ∃p q : ℕ, Prime p ∧ Prime q ∧ (n = p + q) := by 81 | intro n 82 | intro h_and 83 | sorry 84 | 85 | 86 | /- 87 | 88 | ## Universal elimination 89 | 90 | If we know `hall : ∀ x : T, H x`, we can conclude `H t` for any `t : T`. 91 | We again do this just like implication elimination: 92 | `have ht : H t := hall t`. 93 | 94 | We can also use `apply`. 95 | 96 | 97 | Note: I've started this example with the hypothesis `hxp` "pre-intro'ed." 98 | Putting it left of the `:` means it is in my context at the beginning. 99 | This saves us a line. 100 | 101 | -/ 102 | 103 | example (hxp : ∀ x : ℕ, P x) : P 5 ∧ P 6 := by 104 | split_goal 105 | 106 | -- method 1: using `have` 107 | { have hP5 : P 5 := hxp 5 108 | assumption } 109 | 110 | -- method 2: using apply 111 | { apply hxp } 112 | 113 | 114 | 115 | /- 116 | 117 | ## Existential introduction 118 | 119 | To prove an `exists` goal, we provide a witness. 120 | The tactic for giving a witness is `existsi` (for "exists introduction," creatively). 121 | 122 | The tactic `numbers` proves silly obvious facts about explicit numbers. 123 | (It doesn't know anything about variables, like `x`.) 124 | 125 | -/ 126 | 127 | example : ∃ x : ℕ, x > 10 := by 128 | existsi 1000 129 | numbers 130 | 131 | 132 | /- 133 | 134 | ## Existential elimination 135 | 136 | This will be familiar again! 137 | To use an `exists` hypothesis, we can use the good old `eliminate` tactic 138 | to get a witness and a hypothesis about that witness. 139 | 140 | -/ 141 | 142 | example (hex : ∃ x : ℕ, P x) : 0 = 0 := by 143 | eliminate hex with k hPk 144 | numbers 145 | 146 | example (hex : ∃ x : ℕ, P x) (hall : ∀ x : ℕ, P x → P (x + 1)) : 147 | ∃ x : ℕ, P x ∧ P (x + 1) := by 148 | eliminate hex with k hPk 149 | have hk : P k → P (k + 1) := hall k 150 | have hPk1 : P (k + 1) := hk hPk 151 | existsi k 152 | split_goal 153 | { assumption } 154 | { assumption } 155 | -------------------------------------------------------------------------------- /BrownCs22/Demos/QuickReference.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.TruthTables 3 | 4 | /- 5 | 6 | In this file, we'll keep a running lecture-by-lecture list 7 | of the Lean syntax and tactics that we've learned. 8 | 9 | -/ 10 | 11 | 12 | 13 | /- 14 | 15 | ## Lecture 2 16 | 17 | `#check ` tells us what kind of thing `` is. 18 | 19 | -/ 20 | 21 | #check 0 22 | #check 1 + 1 = 3 23 | 24 | 25 | /- 26 | 27 | ## Lecture 3 28 | 29 | `variable (p q r : Prop)` introduces the atomic propositions `p`, `q`, `r`. 30 | (If we enclose them in `section` ... `end`, their scope is limited.) 31 | 32 | -/ 33 | 34 | section 35 | 36 | variable (p q r : Prop) 37 | 38 | #check p ∧ q → r 39 | 40 | end 41 | 42 | /- 43 | 44 | `#truth_table p ∧ q ∨ r` prints a truth table for the given proposition. 45 | 46 | -/ 47 | 48 | #truth_table p ∧ q 49 | 50 | 51 | /- 52 | 53 | ## Lecture 4 54 | 55 | We begin proofs by writing 56 | 57 | `example : p → p := by` or 58 | `theorem my_theorem_name : p → p := by`. 59 | 60 | Following lines should be indented two spaces. 61 | 62 | -/ 63 | 64 | example : p → p := by 65 | intro hp 66 | assumption 67 | 68 | /- 69 | 70 | The new tactics (proof rules) we saw: 71 | 72 | * `sorry`: proves anything! (Cheating!) 73 | 74 | * `split_goal`: turn a goal of `p ∧ q` into two goals `p` and `q`, 75 | and a goal of `p ↔ q` into two goals `p → q` and `q → p`. 76 | 77 | * `left` and `right`: turn a goal of `p ∨ q` into a goal of `p` 78 | or a goal of `q`, respectively. 79 | 80 | * `intro h`: turn a goal of `p → q` into a goal of `q`, 81 | with an extra hypothesis `h : p`. 82 | 83 | * `assumption`: if a hypothesis `h` matches the goal, will solve the goal. 84 | 85 | * `eliminate h with h1 h2`: when a hypothesis `h : p ∧ q`, 86 | replaces `h` with two hypotheses `h1 : p` and `h2 : q`. 87 | When a hypothesis `h : p ∨ q`, creates two goals, one with 88 | a hypothesis `h1 : p` and one with a hypothesis `h2 : q`. 89 | 90 | * `apply h`: if `h : p → q` and the goal is `q`, changes the goal to `p`. 91 | Read this as "to show `q`, it suffices to show `p`". 92 | We'll use this later in a slightly different context. 93 | 94 | * `have h : q := hpq hp`: if `hpq : p → q` and `hp : p` (the lhs of the → matches), 95 | creates a new hypothesis `h : q`. 96 | Read this as "I know `p → q` and I know `p`, so I know `q`." 97 | 98 | * `contradiction`: If you have both a proposition and its negation in your context, 99 | will prove any goal. 100 | 101 | -/ 102 | 103 | 104 | /- 105 | 106 | ## Lecture 6 107 | 108 | Some more new tactics, or new behavior for old tactics: 109 | 110 | * `intro t`: turn a goal of `∀ x : T, P x` into a goal of `P t`, 111 | with a new term `t : T` in the context. 112 | 113 | * `have h : P t := hP t`: if `hP : ∀ x : T, P x` and `t : T`, 114 | creates a new hypothesis `h : P t`. 115 | Read this as "I know every `T` has property `P`, and `t` is a `T`, 116 | so `t` must have property `P`. 117 | 118 | * `existsi t`: turns a goal of `∃ x : T, G x` into a goal of `G t`. 119 | This is the tactic we use to provide a witness. 120 | 121 | * `eliminate h with t ht`: when a hypothesis `h : ∃ x : T, H x`, 122 | replaces `h` with a new witness `t` and the hypothesis `ht : H t`. 123 | You should always use a fresh name for `t`. 124 | 125 | * `reflexivity`: proves equality goals where the LHS and RHS are the same, 126 | like `x = x` and `f y = f y`. 127 | 128 | -/ 129 | 130 | 131 | /- 132 | 133 | ## Homework 2 134 | 135 | * `positivity`: if your goal is to show something is positive or nonnegative 136 | (like `0 < x`, `x ≥ 0`, `0 ≤ a^2`, ...) and this "obviously" follows from 137 | your hypotheses, write `positivity` to finish that goal. This tactic knows 138 | basic facts like "natural numbers are nonnegative" and "the square of a 139 | positive number is positive." It does not know complicated arithmetic. 140 | 141 | * `numbers`: If your goal is to show an arithmetic statement about numerals, 142 | like `5 + 5 = 10` or `1000 < 50000000`, `numbers` will close the goal. 143 | It's basically a calculator! 144 | 145 | * `linarith`: stands for "linear arithmetic." (If you don't know this term, 146 | don't worry.) `linarith` does similar things to `positivity` and `numbers`, 147 | but it can do some simple arithmetic, and use hypotheses. 148 | For instance, if you know `h1 : x < 10` and `h2 : x + y < 20`, `linarith` 149 | can prove the goal `3*x + 2*y < 50`. 150 | 151 | -/ -------------------------------------------------------------------------------- /BrownCs22/Exercises/LogicIdentities.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | /- 4 | 5 | In this file, we'll prove some of the identities from 6 | https://brown-cs22.github.io/resources/math-resources/logic.pdf 7 | 8 | Note that these identities are stated with the symbol `≡`, 9 | meaning "the formula on the left is equivalent to the formula on the right." 10 | In Lean, we can use iff (bi-implication, `↔`) to mean the same thing. 11 | 12 | Remember the tactics we learned in Lecture 4. 13 | We'll see a bit of new Lean syntax along the way. 14 | 15 | Also remember that it doesn't matter what we name our hypotheses. 16 | In our examples, we use patterns like `hp` for the hypothesis `p`, 17 | `hnpq` for the hypothesis `¬ p ∧ q` (hnpq for "hypothesis not p q"), etc. 18 | But this is just a custom. You can choose any names you want. 19 | As they say, the hardest problem in computer science is naming variables. 20 | 21 | Your task: fill in all the `sorry`s with proofs! 22 | 23 | -/ 24 | 25 | variable (p q r : Prop) 26 | 27 | -- ## Commutative Laws 28 | 29 | -- We'll do the first for you, as an example. 30 | -- Notice two things. 31 | -- One, must of these problems will start with `split_goal`, 32 | -- to turn the `iff` goal into two `implies` goals. 33 | -- Two, we structure our proof by putting each *subproof* in `{...}`. 34 | -- This isn't necessary but it helps with organization! 35 | example : p ∧ q ↔ q ∧ p := by 36 | split_goal 37 | { intro hpq 38 | eliminate hpq with hp hq 39 | split_goal 40 | assumption 41 | assumption } 42 | { intro hqp 43 | eliminate hqp with hq hp 44 | split_goal 45 | assumption 46 | assumption } 47 | 48 | example : p ∨ q ↔ q ∨ p := by 49 | sorry 50 | 51 | -- ## Associative laws 52 | 53 | -- Notice that Lean doesn't print parentheses unless it needs to. 54 | example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := by 55 | sorry 56 | 57 | 58 | -- This is getting a little repetitive. 59 | -- To mix it up, we'll only do one direction of the iff this time. 60 | -- You won't start with `split_goal`, but instead, ... what? 61 | example : (p ∨ q) ∨ r → p ∨ (q ∨ r) := by 62 | sorry 63 | 64 | -- ## Distributive laws 65 | 66 | example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by 67 | sorry 68 | 69 | -- Again, let's just do one direction. 70 | -- When we're proving an implication `→`, we'll always start with `intro`. 71 | -- This is such a common pattern that Lean gives us special syntax for it: 72 | -- we can do the intro "in advance," before we start the proof, 73 | -- by naming the hypothesis to the left of the `:`. 74 | -- Notice that at the beginning of our proof, we already have the 75 | -- hypothesis `hpqr` in our context. 76 | example (hpqr : p ∨ (q ∧ r)) : (p ∨ q) ∧ (p ∨ r) := by 77 | sorry 78 | 79 | 80 | -- ## Identity laws 81 | 82 | /- 83 | 84 | We'll do a few more together. 85 | 86 | Let's sneak a new tactic in. 87 | To prove a goal `True`, you can use the tactic `trivial`. 88 | This is a sensible proof rule: trivially, you can always show `True` is true! 89 | -/ 90 | 91 | example : p ∧ True ↔ p := by 92 | split_goal 93 | { intro hpT 94 | eliminate hpT 95 | assumption } 96 | { intro hpT 97 | split_goal 98 | assumption 99 | trivial } 100 | 101 | -- Before, we saw `contradiction` would work if we had hypotheses 102 | -- `p` and `¬ p` at the same time. 103 | -- It also works if we have a hypothesis `False`! 104 | example : p ∨ False ↔ p := by 105 | split_goal 106 | { intro hpF 107 | eliminate hpF with hp hF 108 | assumption 109 | contradiction } 110 | { intro hp 111 | left 112 | assumption } 113 | 114 | /- 115 | 116 | ## Negation laws 117 | 118 | If you're following along with the list of identities, you'll see that 119 | we've come to some laws like `(p ∨ ¬ p) ↔ True`. 120 | 121 | Unfortunately, we don't have the tools to prove these in Lean yet. 122 | 123 | But, you can do the **Idempotent laws** and **Universal bound laws**. 124 | Try stating and proving these yourself! 125 | 126 | -/ 127 | 128 | 129 | 130 | 131 | /- 132 | 133 | ## De Morgan's Laws 134 | 135 | These can be a bit of a challenge! Like the negation laws, 136 | a few of the implications here will require some new tools. 137 | But try out these directions. 138 | 139 | Remember that to prove a negation `¬ X`, you can use a *proof by contradiction*: 140 | add a hypothesis `h : X` using `intro h`, and then show a contradiction. 141 | 142 | -/ 143 | 144 | example (hnpnq : ¬ p ∨ ¬ q) : ¬(p ∧ q) := by 145 | sorry 146 | 147 | example (hnpnq : ¬ p ∧ ¬ q) : ¬ (p ∨ q) := by 148 | sorry 149 | 150 | 151 | /- 152 | 153 | That's it for the moment! 154 | Try the **Absorption laws** and **Negation of True and False** 155 | on your own, if you want to. Skip the definition laws for now. 156 | 157 | -/ -------------------------------------------------------------------------------- /BrownCs22/Library/ModEq/Lemmas.lean: -------------------------------------------------------------------------------- 1 | /- Copyright (c) Heather Macbeth, 2022. All rights reserved. -/ 2 | import BrownCs22.Library.ModEq.Defs 3 | import BrownCs22.Library.Tactics 4 | -- import Mathlib.Data.Int.ModEq 5 | 6 | namespace BrownCs22 7 | 8 | instance (n) : DecidableRel (Int.ModEq n) := λ a b => 9 | if h : Int.mod (a - b) n = 0 then Decidable.isTrue (Int.dvd_of_mod_eq_zero h) else Decidable.isFalse (λ h' => h (Int.mod_eq_zero_of_dvd h')) 10 | 11 | 12 | protected theorem Int.ModEq.refl (a : ℤ) : a ≡ a [ZMOD n] := ⟨0, by ring⟩ 13 | 14 | protected theorem Int.ModEq.add (h1 : a ≡ b [ZMOD n]) (h2 : c ≡ d [ZMOD n]) : 15 | a + c ≡ b + d [ZMOD n] := by 16 | obtain ⟨x, hx⟩ := h1 17 | obtain ⟨y, hy⟩ := h2 18 | exact ⟨x + y, by linear_combination hx + hy⟩ 19 | 20 | protected theorem Int.ModEq.add_left (h : a ≡ b [ZMOD n]) : c + a ≡ c + b [ZMOD n] := 21 | (Int.ModEq.refl _).add h 22 | 23 | protected theorem Int.ModEq.add_right (h : a ≡ b [ZMOD n]) : a + c ≡ b + c [ZMOD n] := 24 | h.add (Int.ModEq.refl _) 25 | 26 | protected theorem Int.ModEq.sub (h1 : a ≡ b [ZMOD n]) (h2 : c ≡ d [ZMOD n]) : 27 | a - c ≡ b - d [ZMOD n] := by 28 | obtain ⟨x, hx⟩ := h1 29 | obtain ⟨y, hy⟩ := h2 30 | exact ⟨x - y, by linear_combination hx - hy⟩ 31 | 32 | protected theorem Int.ModEq.sub_left (h : a ≡ b [ZMOD n]) : c - a ≡ c - b [ZMOD n] := 33 | (Int.ModEq.refl _).sub h 34 | 35 | protected theorem Int.ModEq.sub_right (h : a ≡ b [ZMOD n]) : a - c ≡ b - c [ZMOD n] := 36 | h.sub (Int.ModEq.refl _) 37 | 38 | protected theorem Int.ModEq.neg (h1 : a ≡ b [ZMOD n]) : -a ≡ -b [ZMOD n] := by 39 | obtain ⟨x, hx⟩ := h1 40 | exact ⟨-x, by linear_combination -hx⟩ 41 | 42 | protected theorem Int.ModEq.mul (h1 : a ≡ b [ZMOD n]) (h2 : c ≡ d [ZMOD n]) : 43 | a * c ≡ b * d [ZMOD n] := by 44 | obtain ⟨x, hx⟩ := h1 45 | obtain ⟨y, hy⟩ := h2 46 | exact ⟨x * c + b * y, by linear_combination c * hx + b * hy⟩ 47 | 48 | protected theorem Int.ModEq.mul_left (h : a ≡ b [ZMOD n]) : c * a ≡ c * b [ZMOD n] := 49 | (Int.ModEq.refl _).mul h 50 | 51 | protected theorem Int.ModEq.mul_right (h : a ≡ b [ZMOD n]) : a * c ≡ b * c [ZMOD n] := 52 | h.mul (Int.ModEq.refl _) 53 | 54 | protected theorem Int.ModEq.pow (k : ℕ) (h : a ≡ b [ZMOD n]) : a ^ k ≡ b ^ k [ZMOD n] := by 55 | induction k 56 | case zero => exact Int.ModEq.refl _ 57 | case succ k hk => exact Int.ModEq.mul hk h 58 | 59 | protected theorem Int.ModEq.symm (h : a ≡ b [ZMOD n]) : b ≡ a [ZMOD n] := by 60 | obtain ⟨x, hx⟩ := h 61 | exact ⟨-x, by linear_combination - hx⟩ 62 | 63 | @[trans] 64 | protected theorem Int.ModEq.trans (h1 : a ≡ b [ZMOD n]) (h2 : b ≡ c [ZMOD n]) : a ≡ c [ZMOD n] := by 65 | obtain ⟨x, hx⟩ := h1 66 | obtain ⟨y, hy⟩ := h2 67 | exact ⟨x + y, by linear_combination hx + hy⟩ 68 | 69 | instance : IsTrans ℤ (Int.ModEq n) where 70 | trans := @Int.ModEq.trans n 71 | 72 | theorem Int.modEq_fac_zero : n * t ≡ 0 [ZMOD n] := ⟨t, by ring⟩ 73 | theorem Int.modEq_fac_zero' : t * n ≡ 0 [ZMOD n] := ⟨t, by ring⟩ 74 | theorem Int.modEq_zero_fac : 0 ≡ n * t [ZMOD n] := ⟨-t, by ring⟩ 75 | theorem Int.modEq_zero_fac' : 0 ≡ t * n [ZMOD n] := ⟨-t, by ring⟩ 76 | theorem Int.modEq_add_fac_self : a + n * t ≡ a [ZMOD n] := ⟨t, by ring⟩ 77 | theorem Int.modEq_add_fac_self' : n * t + a ≡ a [ZMOD n] := ⟨t, by ring⟩ 78 | theorem Int.modEq_add_fac_self'' : a + t * n ≡ a [ZMOD n] := ⟨t, by ring⟩ 79 | theorem Int.modEq_add_fac_self''' : t * n + a ≡ a [ZMOD n] := ⟨t, by ring⟩ 80 | theorem Int.modEq_sub_fac_self : a - n * t ≡ a [ZMOD n] := ⟨-t, by ring⟩ 81 | theorem Int.modEq_sub_fac_self' : n * t - a ≡ -a [ZMOD n] := ⟨t, by ring⟩ 82 | theorem Int.modEq_sub_fac_self'' : a - t * n ≡ a [ZMOD n] := ⟨-t, by ring⟩ 83 | theorem Int.modEq_sub_fac_self''' : t * n - a ≡ -a [ZMOD n] := ⟨t, by ring⟩ 84 | theorem Int.modEq_add_fac_self_symm : a ≡ a + n * t [ZMOD n] := ⟨-t, by ring⟩ 85 | theorem Int.modEq_add_fac_self_symm' : a ≡ n * t + a [ZMOD n] := ⟨-t, by ring⟩ 86 | theorem Int.modEq_add_fac_self_symm'' : a ≡ a + t * n [ZMOD n] := ⟨-t, by ring⟩ 87 | theorem Int.modEq_add_fac_self_symm''' : a ≡ t * n + a [ZMOD n] := ⟨-t, by ring⟩ 88 | theorem Int.modEq_sub_fac_self_symm : a ≡ a - n * t [ZMOD n] := ⟨t, by ring⟩ 89 | theorem Int.modEq_sub_fac_self_symm' : -a ≡ n * t - a [ZMOD n] := ⟨-t, by ring⟩ 90 | theorem Int.modEq_sub_fac_self_symm'' : a ≡ a - t * n [ZMOD n] := ⟨t, by ring⟩ 91 | theorem Int.modEq_sub_fac_self_symm''' : -a ≡ t * n - a [ZMOD n] := ⟨-t, by ring⟩ 92 | 93 | -- theorem Int.existsUnique_modEq_lt (a : ℤ) {b : ℤ} (hb : 0 < b) : 94 | -- ∃! r : ℤ, 0 ≤ r ∧ r < b ∧ a ≡ r [ZMOD b] := by 95 | -- obtain ⟨r, ⟨rpos, rlt, q, hq⟩, hr2⟩ := a.existsUnique_quotient_remainder b hb 96 | -- refine ⟨r, ⟨rpos, rlt, q, ?_⟩, ?_⟩ <;> dsimp at * 97 | -- . linear_combination hq 98 | -- rintro r' ⟨rpos', rlt', q', hq'⟩ 99 | -- refine hr2 r' ⟨rpos', rlt', q', ?_⟩ 100 | -- linear_combination hq' 101 | -------------------------------------------------------------------------------- /BrownCs22/Homework/Hw3.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.Defs 3 | 4 | -- don't change these lines 5 | namespace HW3 6 | open Set BrownCs22.Set 7 | set_option linter.unusedVariables false 8 | variable (U : Type) 9 | variable (A B : Set U) 10 | 11 | /- 12 | 13 | # Welcome to the Lean section of HW3! 14 | 15 | In these problems, we're going to get a little practice formally proving 16 | set equalities. We've seen two techniques for doing this on paper: 17 | 18 | * With the set-element method, we argue that `A = B` by showing that 19 | `A ⊆ B` and `B ⊆ A`. 20 | * With the algebraic method, we can prove that sets are equal by rewriting 21 | one or both sides with algebraic identities like `(Aᶜ)ᶜ = A`, until we 22 | have identical expressions. 23 | 24 | We can do both of these in Lean. In this assignment you'll prove two 25 | set equalities, one with each method. 26 | 27 | **Notes**: 28 | 29 | * Remember that in Lean, we write "the complement of A" as `Aᶜ` instead 30 | of using a bar over the letter. (Diacritics are hard in a text editor!) 31 | 32 | * You can type `⊆` using `\sub`. 33 | 34 | * You can type `ᶜ` using `\compl` or `\^c`. 35 | 36 | -/ 37 | 38 | 39 | /- 40 | 41 | ## Problem 1: the set-element method 42 | 43 | We saw two important tactics in lecture for set-element proofs in Lean: 44 | 45 | * `ext x`: given a goal `A = B` where `A` and `B` are sets, changes the goal 46 | to showing `x ∈ A ↔ x ∈ B` for an arbitrary element `x`. The name comes 47 | from "extensionality," the property that two sets are equal if they have 48 | the same elements. 49 | 50 | * `set_simplify`: unfolds the "logic" of a set membership proposition. 51 | For instance, `x ∈ A ∩ B` simplifies to `x ∈ A ∧ x ∈ B`. 52 | `x ∈ A \ C` simplifies to `x ∈ A ∧ ¬(x ∈ C)`. 53 | Calling `set_simplify` will simplify the goal and all hypotheses. 54 | 55 | Use these techniques to prove the following. 56 | Starting with `ext x` is probably a good move! 57 | Then think about the last few homeworks; how do you prove an `↔` goal? 58 | 59 | -/ 60 | 61 | /- 4 points -/ 62 | theorem problem_1 : (A ∩ B) ∪ A = A := by 63 | sorry 64 | 65 | 66 | /- 67 | 68 | ## Problem 2: the algebraic method 69 | 70 | What you just proved is sometimes called an "absorption law," since the 71 | intersection `A ∩ B` gets "absorbed" into the bigger set `A`. 72 | This is an example of a useful rewrite rule: if we ever see the pattern 73 | `X ∪ (X ∩ Y)` in a proposition, we can replace it with `X`, since we know 74 | that these sets are the same. 75 | 76 | As we saw in Lecture 8, the `rewrite` tactic lets us do this in Lean. 77 | Here's an example of using the above rule, and another useful rewrite rule: 78 | 79 | -/ 80 | 81 | 82 | example : A ∩ ((Aᶜ ∩ B) ∪ Aᶜ) = ∅ := by 83 | rewrite [inter_union_cancel_left] 84 | rewrite [inter_compl_self] 85 | reflexivity 86 | 87 | /- 88 | 89 | The name Lean gives to our identity from problem 1 is 90 | `inter_union_cancel_left`. We also used `inter_compl_self`, which says 91 | `A ∩ Aᶜ = ∅`. Hover over the name in the proof above to see this statement! 92 | 93 | The final tactic, `reflexivity`, tells Lean that we are done: we can close 94 | any goal of the form `P ↔ P`. 95 | 96 | The `rewrite` tactic will use the identity "left to right": 97 | it will look for the pattern on the left hand side of the identity, 98 | and replace it with the right hand side. For example, `inter_compl_self` 99 | says that `s ∩ sᶜ = ∅`, so it replaces `s ∩ sᶜ` with `∅`. 100 | Sometimes you can use a rule in reverse direction, right to left, 101 | using the symbol `←` (typed `\l` or `\<-`). For example: 102 | 103 | -/ 104 | 105 | example : Aᶜ ∩ Aᶜ = Aᶜ := by 106 | rewrite [← compl_union] -- we have used de morgan's law "backward", 107 | -- changing `Aᶜ ∩ Aᶜ` to `(A ∪ A)ᶜ`. 108 | rewrite [union_self] 109 | reflexivity 110 | 111 | /- 112 | But this won't work for every identity! Think about `inter_compl_self`. 113 | Backward, this would say that if you see the pattern `∅`, you can replace 114 | it with `s ∩ sᶜ`. But how would Lean know what set `s` you wanted to use? 115 | It could be anything! If you see some funny symbols like `?m1000`, 116 | this is probably what's going on. 117 | 118 | You may or may not need to use rules backwards in the following problem. 119 | 120 | 121 | In recitation, you saw (or will see) a list of set identities: 122 | 123 | All the identities on this list are available as rewrite rules in Lean, 124 | listed in the file `BrownCs22/Demos/SetIdentities.lean`. 125 | 126 | Use these rewrite rules to complete the following proof. Your proof should 127 | have the same structure as the example above: 128 | a sequence of rewrites, followed by `reflexivity`. 129 | 130 | It might help to plan out your steps on paper! 131 | 132 | -/ 133 | 134 | /- 4 points -/ 135 | theorem problem_2 : (Aᶜ \ B)ᶜ = A ∪ B := by 136 | sorry 137 | 138 | 139 | 140 | 141 | 142 | end HW3 -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture08.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | variable (U : Type) 4 | 5 | /- 6 | 7 | # Sets in Lean 8 | 9 | Today we'll talk about sets in Lean! 10 | 11 | Remember that, when we take the *complement* of a set `A`, 12 | we have to do this relative to some "universe" of objects. 13 | Otherwise, the set of things not in `A` would be gigantic. 14 | 15 | Lean enforces this even more strongly. Every set must be 16 | relative to some universe. `A : set ℕ` means that 17 | "`A` is a set of natural numbers," i.e., the universe is `ℕ`. 18 | Similarly, `A : set U` means that the elements of `A` come 19 | from some generic kind of objects that we call `U`. 20 | 21 | 22 | ## The set-element method 23 | 24 | To prove that two sets are equal, one technique we saw was 25 | to transform the equality into a universal statement about 26 | membership : `A = B` means `∀ x : U, x ∈ A ↔ x ∈ B`. 27 | The tactic for doing this is called `ext`, for "extensionality." 28 | You give it a name for the variable `x`. 29 | 30 | ## Some new useful tactics 31 | 32 | * `ext x`: if your goal is to show that two sets `A` and `B` 33 | are equal, `ext x` changes the goal to showing that 34 | `x ∈ A ↔ x ∈ B`. Remember, `split_goal` will then turn an `↔` 35 | statement into two implications! 36 | 37 | * `set_simplify`: this one is fun! If you have any "set membership" 38 | statements, like `x ∈ Aᶜ`, in your hypothesis or goals, 39 | `set_simplify` will turn these statements into "logic." 40 | For example, `x ∈ Aᶜ` will become `¬ (x ∈ A)`. 41 | `x ∈ A ∩ B` will become `x ∈ A ∧ x ∈ B`. 42 | 43 | * `rewrite`: stands for "rewrite." `rewrite` will let us substitute patterns 44 | in place of equivalent or equal patterns. A good example here is 45 | De Morgan's laws, which are logical equivalences. If we are 46 | ever looking to prove a goal of the form `¬ (p ∧ q)`, 47 | `rewrite [not_and_or]` will use De Morgan's law to change the goal to 48 | `¬p ∨ ¬q`. The word `not_and_or` is the name Lean gives to this form 49 | of De Morgan's law. 50 | 51 | There are lots of laws (patterns, equivalences) that we can use with 52 | `rewrite`. For now, we'll just use a few. More information about `rewrite` 53 | and a longer list of rules will come soon to the quick reference. 54 | 55 | You can figure out what a rule states using `#check`: 56 | 57 | -/ 58 | 59 | #check not_and_or -- De Morgan 1 60 | #check not_or -- De Morgan 2 61 | #check not_not -- Double negation elimination 62 | #check or_comm -- Changes the order of an "or" 63 | #check and_comm -- Changes the order of and "and" 64 | 65 | 66 | /- 67 | 68 | Finally, some useful keyboard shortcuts: 69 | 70 | * `∩`: \inter, \cap 71 | * `∪`: \union, \cup 72 | * `ᶜ`: We use this for "complement". \^c 73 | 74 | -/ 75 | 76 | 77 | /- 78 | 79 | Here's a proof using the set-element method: 80 | 81 | -/ 82 | 83 | example (A B : Set U) : (A ∩ B)ᶜ = Aᶜ ∪ Bᶜ := by 84 | ext x 85 | split_goal 86 | { intro hx 87 | set_simplify 88 | rewrite [not_and_or] at hx 89 | assumption } 90 | { intro hx 91 | set_simplify 92 | rewrite [not_and_or] 93 | assumption } 94 | 95 | 96 | 97 | /- 98 | 99 | Here's another. This one uses `rw` much more heavily! 100 | 101 | The final goal has the form `p ↔ p`. The `reflexivity` tactic, 102 | which we've seen once or twice already, closes goals like this. 103 | `↔`, `=`, and some other relations are called *reflexive* because 104 | for any `x`, `x ↔ x`, `x = x`, etc. are true. 105 | 106 | -/ 107 | 108 | example (A B : Set U) : (A \ B)ᶜ = B ∪ Aᶜ := by 109 | ext x 110 | set_simplify 111 | rewrite [not_and_or] 112 | rewrite [not_not] 113 | rewrite [or_comm] 114 | reflexivity 115 | 116 | 117 | 118 | 119 | 120 | 121 | /- 122 | 123 | ## Bonus 124 | 125 | We mentioned "De Morgan for quantifiers" in lecture. 126 | Here's a proof of one direction of this. 127 | The other direction is trickier: we need a new proof technique for it. 128 | 129 | -/ 130 | 131 | 132 | example (P : ℕ → Prop) : (¬ ∃ x, P x) ↔ (∀ x, ¬ P x) := by 133 | split_goal -- To prove an ↔, prove both directions. 134 | 135 | { intro hne -- Suppose `¬ ∃ x, P x`. 136 | intro x -- Take an arbitrary `x`, 137 | intro hx -- and suppose for the sake of contradiction that `P x` holds. 138 | have hex : ∃ x, P x -- We then know that `∃ x, P x` holds, 139 | { existsi x -- since `x` is a witness. 140 | assumption } 141 | contradiction } -- This contradicts our original hypothesis. 142 | 143 | { intro hall -- For the other direction, suppose `∀ x, ¬ P x`. 144 | intro hex -- Suppose for the sake of contradiction `∃ x, P x`. 145 | eliminate hex with w hw -- Then there is a witness `w` such that `P w` holds. 146 | have hnw : ¬ P w := hall w -- But, from our first hypothesis, `¬ P w` also holds. 147 | contradiction } -- Contradiction. 148 | 149 | /- 150 | 151 | There are even rewrite rules for "De Morgan for quantifiers": 152 | 153 | -/ 154 | 155 | #check not_forall 156 | #check not_exists -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Brown CS22 Spring 2023: Lean project 2 | 3 | ## Directions for setting up GitPod 4 | 5 | Before following these directions, 6 | you will need to sign up for a [GitHub account](https://github.com/) 7 | if you don't have one already. 8 | This will be useful throughout the rest of your career at Brown CS. 9 | We strongly recommend picking a professional and identifiable username. 10 | For example, I'm [robertylewis](https://github.com/robertylewis). 11 | 12 | There are scattered reports that this might not work on some versions of Safari. 13 | If you run into trouble, try it in Chrome or Firefox before reporting to us. 14 | 15 | * Click on [this link](https://gitpod.io/#https://github.com/brown-cs22/CS22-Lean-2023). 16 | * This will send you to Gitpod. 17 | Log in with your GitHub account. 18 | * If you are given a choice to open in your browser or locally, 19 | choose the browser option. 20 | * It will take a minute, but eventually, 21 | you'll see a VSCode interface in your browser. 22 | You should see: 23 | * A panel `Terminal` on the bottom. 24 | There will be some code running here at first. 25 | Let it finish before you do anything else; 26 | it's done when the last line is 27 | `gitpod /workspace/leanclass (main) $`. 28 | * A panel `Explorer` on the left that lets you browse directories and files. 29 | * A text editor panel in the middle. 30 | * A panel on the right, `Lean Infoview`. 31 | * Go back to the [Gitpod home page](https://gitpod.io/workspaces). 32 | You'll see your new workspace there. 33 | Use the dropdown menu ⋮ to: 34 | * Rename the workspace to something recognizable, like "Brown CS0220 Class Repository." 35 | * Pin the workspace. This is important: 36 | if you don't pin it and go two weeks without opening it, Gitpod will delete it! 37 | 38 | You're all set up. 39 | In the future, you should bookmark your workspace URL and access it that way, 40 | or use the Gitpod home page. 41 | The link above will create a new workspace each time you click it. 42 | (This is why we recommend renaming your main workspace, to distinguish it from new ones.) 43 | 44 | If at any point your workspace becomes unusable 45 | and you think you need a fresh start, 46 | you can click on the original link to get a new copy of the course workspace. 47 | 48 | ## Directions for Setting up Logging 49 | 50 | 1. Click on Extensions (or `Ctrl + Shift + X`) in the VSCode interface. 51 | 52 | Click on Extensions 53 | 54 | 55 | 2. Click the `...` and select `Install from VSIX`. 56 | 57 | Select Ellipsis 58 | 59 | 60 | 3. Enter the following path and hit `OK` : `/workspace/CS22-Lean-2023/save-to-firestore-0.13.1.vsix` 61 | 62 | 4. If prompted, restart VSCode. 63 | 64 | Enabling logging is an optional feature that can greatly benefit our understanding of students' issues with assignments, leading to improvements for future students. This logging is completely anonymous, and is carried out only when a file is *saved*. The information logged is: 65 | 66 | - The version of Lean being used 67 | - The name and contents of the file being worked on. 68 | - An anonymous user-id, which is persistent, but not related to the users' identity. 69 | - The timestamp at which the log was generated. 70 | 71 | 72 | ## Directions for updating 73 | 74 | We will push more lecture demos and homework assignments to this project throughout the semester. 75 | To pull them into your Gitpod instance, follow these directions: 76 | 77 | * Open the terminal in Gitpod, if it is not already open. 78 | * Run the command `scripts/pull-updates`. 79 | 80 | We will try not to let this happen, but occasionally, we might change files that you have edited yourself. 81 | The `pull-updates` script should notice this and not overwrite your changes. 82 | But if there are conflicts, you may have to reset your work. 83 | (Feel free to copy your changes to another file if you want.) 84 | Running the command `scripts/reset-all` and then `pull-updates` again should clean things up. 85 | 86 | ## git: the fine print 87 | 88 | This is a GitHub repository, 89 | and your workspace will interact with our course materials using git. 90 | We do *not* expect you to have any experience or knowledge of git 91 | beyond having a GitHub account. 92 | If you do know how to use git and would like to use proper version control in your workspace, 93 | you are welcome to, but our course staff is not responsible for helping! 94 | We document the setup here for your reference. 95 | 96 | * We will try our best not to modify files in the `BrownCs22/Demos` directory after we add them. 97 | This should minimize merge conflicts if you edit files there. 98 | * Official course materials, 99 | including lecture demos and homeworks, 100 | will be pushed to the `main` branch of this repository. 101 | You will have to pull these changes to your workspace. 102 | * The `pull-updates` script in your workspace 103 | will `git stash` any uncommitted changes you have, 104 | pull our updates, 105 | and `git stash pop` your changes back. 106 | If your changes conflict with ours, 107 | it will leave your project unmodified and print an error message. 108 | This script assumes you have not made any commits of your own; 109 | if you have, you're on your own! 110 | * The `reset-all` script resets all tracked files to the most recent commit. 111 | -------------------------------------------------------------------------------- /BrownCs22/Homework/Hw9.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.Defs 3 | 4 | 5 | /- 6 | 7 | # Strong Induction (Mindbender) 8 | 9 | You've seen `basic_induction`, now get ready for `strong_induction`! This is an 10 | alternative induction tactic that can be used on induction proofs where using 11 | `basic_induction` would be awkward and confusing. 12 | 13 | Informally: using basic induction, in our inductive step, we get to assume `P(n)` 14 | and need to prove `P(n+1)`. Using strong induction, We get to assume more in the 15 | inductive step: we assume `P(0)`, `P(1)`, ..., `P(n)`, and need to prove `P(n+1)`. 16 | Generically, we can say that our induction hypothesis is `∀ k, k ≤ n → P(k)`: 17 | "`P` holds for all `k` between 0 and `n` inclusive." 18 | 19 | A slight variant of this lets us get rid of the base case -- how useful! 20 | We assume `∀ k, k < n → P(k)` and prove `P(n)`. 21 | We talked about this briefly in class way back when, on Feb 22. 22 | 23 | 24 | 25 | Now in Lean! 26 | 27 | Recall that `basic_induction` transforms a goal of `∀ (x : ℕ), P(x)` into two new 28 | goals: `P(0)` and `∀ (x : ℕ), P(x) → P(x+1)`. 29 | 30 | On the other hand, `strong_induction` only creates one new goal -- it uses the 31 | variant version of strong induction. However, the transformed goal can look quite 32 | confusing in Lean, so we're going to walk through it step by step. 33 | 34 | -/ 35 | 36 | variable (P : Nat → Prop) 37 | 38 | example : ∀ (x : ℕ), P x := by 39 | strong_induction 40 | sorry 41 | 42 | /- 43 | 44 | The example above shows that the `strong_induction` tactic replaces our goal with: 45 | 46 | `∀ (n : ℕ), (∀ (m : ℕ), m < n → P m) → P n` 47 | 48 | Seeing this, it's natural to ask two questions: 49 | 1. Why would proving this let us prove our goal? 50 | 2. What does this even mean? 51 | 52 | To try to answer these questions, let's isolate the middle part of that expression: 53 | 54 | `(∀ (m : ℕ), m < n → P m)` 55 | 56 | In English, we can read that proposition as, "`P` is satisfied for all natural numbers 57 | less than `n`." Let's extract this proposition into a named predicate `SatisfiedBelow`, 58 | to hopefully make things clearer. 59 | 60 | `SatisfiedBelow n ↔ (∀ (m : ℕ), m < n → P m)` 61 | 62 | So `SatisfiedBelow n` is true iff `P` is satisfied for all natural numbers below `n`. 63 | 64 | Using that named predicate, we can rewrite the goal given to us by `strong_induction`: 65 | 66 | `∀ (n : ℕ), SatisfiedBelow n → P n` 67 | 68 | In this form, it's a lot easier to recognize the goal as the definition of strong 69 | induction we discussed in class. This is saying that for all `n`, if `P` is true 70 | for all natural numbers below `n`, then `P` is true for `n` as well. And since 71 | `SatisfiedBelow n` is *vacuously true* when `n = 0`, proving this goal will 72 | automatically prove `P 0`, and therefore also `P 1`, and `P 2`, and so on. 73 | 74 | 75 | 76 | # The Problem 77 | 78 | We're going to use the `strong_induction` tactic to prove something we discussed 79 | in class - that all natural numbers 8 or larger can be made using coins of value 80 | 3 and 5. 81 | 82 | Here's the predicate `P` we'll be working with: 83 | 84 | -/ 85 | 86 | def from_three_five (n : ℕ) : Prop := 87 | ∃ (x y : ℕ), n = 3 * x + 5 * y 88 | 89 | /- 90 | 91 | You'll need these three lemmas - they shouldn't require more than 2 lines each to prove. 92 | 93 | -/ 94 | 95 | /- 1 point -/ 96 | lemma ftf8 : from_three_five 8 := by 97 | sorry 98 | 99 | /- 1 point -/ 100 | lemma ftf9 : from_three_five 9 := by 101 | sorry 102 | 103 | /- 1 point -/ 104 | lemma ftf10 : from_three_five 10 := by 105 | sorry 106 | 107 | /- 108 | 109 | Now for the tricky parts. This next lemma is a proof that if `n` is between 8 and 110 | 10, inclusive, then it can be made from 3 and 5. This follows from the three lemmas 111 | above, but it will be useful to get it into this form. Your main tool in this proof 112 | will be the Mathlib lemma `lt_or_le`, which is a proof that for any natural numbers 113 | `x` and `y`, either `x < y`, or `y ≤ x`. (It actually works for more than just natural 114 | numbers, but we won't go into that.) 115 | 116 | -/ 117 | 118 | #check lt_or_le 119 | 120 | /- 1 points -/ 121 | lemma ftf_of_ge_8_le_10 (n : ℕ) : 8 ≤ n ∧ n ≤ 10 → from_three_five n := by 122 | intro h 123 | eliminate (lt_or_le n 9) with htri9 htri9 124 | { 125 | sorry 126 | } 127 | { 128 | eliminate (lt_or_le n 10) with htri10 htri10 129 | { 130 | sorry 131 | } 132 | { 133 | sorry 134 | } 135 | } 136 | 137 | /- 138 | 139 | Two more helper lemmas, and then we finish this once and for all. These two are 140 | *almost* the same - the first says that if `n` can be made from 3 and 5, so can `n+3`. 141 | The second says that if `n-3` can be made from 3 and 5, so can `n`. Consider: 142 | why aren't these *exactly* equivalent? And why does the second lemma require the 143 | extra hypothesis `3 ≤ n`? 144 | 145 | -/ 146 | 147 | /- 1 point -/ 148 | lemma ftf_add_3_of_ftf_self (n : ℕ) : 149 | from_three_five n → from_three_five (n+3) := by 150 | sorry 151 | 152 | -- You may find the Mathlib lemma below to be useful. 153 | #check Nat.sub_add_comm 154 | 155 | /- 1 point -/ 156 | lemma ftf_of_ftf_sub_3 (n : ℕ) : 157 | 3 ≤ n → from_three_five (n-3) → from_three_five n := by 158 | intro h_3_le_n hftf 159 | have : (n-3) + 3 = n := by { 160 | sorry 161 | } 162 | sorry 163 | 164 | /- 165 | 166 | You may find the Mathlib lemmas below useful. Also, `linarith` is your friend :) 167 | 168 | -/ 169 | 170 | #check Nat.sub_lt_self 171 | #check Nat.le_sub_of_add_le 172 | 173 | /- 2 points -/ 174 | lemma all_from_three_five : ∀ (n : ℕ), 8 ≤ n → from_three_five n := by 175 | strong_induction 176 | intro n hind h 177 | eliminate (lt_or_le n 11) with htri htri 178 | { 179 | sorry 180 | } 181 | { 182 | sorry 183 | } -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture04.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | namespace Lecture04 4 | 5 | /- 6 | 7 | Recall from lecture: 8 | * A *proof state* is a sequence of goals, 9 | each with an associated list of hypotheses. 10 | * We can manipulate a proof state by applying *proof rules*. 11 | Proof rules change the goals and/or hypotheses. 12 | * The objective of proving is to apply proof rules 13 | until there are no remaining goals. 14 | 15 | * *introduction rules* tell us how to show goals of certain shapes. 16 | * *elimination rules* tell us how to use hypotheses of certain shapes. 17 | 18 | -/ 19 | 20 | -- let `p, q, r` be propositions. 21 | variable (p q r : Prop) 22 | 23 | /- 24 | 25 | To start a proof, we write `example :` or `theorem theorem_name :`, 26 | followed by the proposition we are trying to prove, 27 | followed by `:= by`. 28 | 29 | What follows after that is a list of proof rules ("tactics"). 30 | If we put the cursor at the end of any line, we see the current proof state 31 | after applying that rule. 32 | 33 | The lines above the `⊢` symbol are our *context*, i.e. our hypotheses. 34 | The line after the `⊢` is our current goal. 35 | 36 | -/ 37 | 38 | example : p → p := by 39 | intro hp 40 | assumption 41 | 42 | /- 43 | Here are some useful tactics, corresponding to the intro rules we've seen. 44 | 45 | * and introduction: `split_goal` 46 | * or introduction: `left`, `right` 47 | * implication intro: `intro h` (you get to name the new hypothesis) 48 | * iff intro: `split_goal` 49 | * atom: `assumption` (if your goal matches a hypothesis) 50 | 51 | * `sorry` proves the goal automatically, no matter what it is. 52 | This is cheating! :) 53 | -/ 54 | 55 | example : p → q → p ∧ q := by 56 | intro hp 57 | intro hq 58 | split_goal 59 | { assumption } -- when we have multiple goals, we sometimes put each subproof in {...} 60 | { assumption } -- but this is mainly for style! 61 | 62 | example : p → p ∨ q := by 63 | intro hp 64 | left 65 | assumption 66 | 67 | -- try it yourself: 68 | 69 | example : q → p ∨ q := by 70 | sorry 71 | 72 | example : (p ∧ q) → (p ↔ q) := by 73 | sorry 74 | 75 | 76 | 77 | /- 78 | *Elimination rules* tell us how we can use hypotheses. To start: 79 | 80 | * and elimination: `eliminate h with h1 h2`, 81 | when `h : p ∧ q` is in the context. Creates new hypotheses 82 | `h1 : p` and `h2 : q`. 83 | * or elimination: `eliminate h with h1 h2`, 84 | when `h : p ∨ q` is in the context. Creates new goals, 85 | one with `h1 : p` in the context and one with `h2 : q` in the context. 86 | 87 | -/ 88 | 89 | example : p ∧ q → q ∧ p := by 90 | intro hpq 91 | eliminate hpq with hp hq 92 | split_goal 93 | . assumption 94 | . assumption 95 | 96 | example : (p ∨ q) → (p ∨ r) ∨ (q ∨ r) := by 97 | intro hpq 98 | eliminate hpq with hp hq 99 | { left 100 | left 101 | assumption } 102 | { right 103 | left 104 | assumption } 105 | 106 | /- 107 | 108 | Using an iff is easy: `eliminate` will turn it into two "imply"s. 109 | 110 | -/ 111 | 112 | example : (p ↔ q) → (p → q) := by 113 | intro h_iff 114 | eliminate h_iff with hpq hqp 115 | assumption 116 | 117 | /- 118 | 119 | Using an "imply" is a little trickier: 120 | if we know `hpq : p → q`, and our goal is to prove `q`, 121 | then `apply hpq` will change the goal to `p`. 122 | 123 | -/ 124 | 125 | example : p → (p → q) → q := by 126 | intro hp 127 | intro hpq 128 | apply hpq 129 | assumption 130 | 131 | /- 132 | 133 | Alternatively, if we know `hpq : p → q` and `hp : p`, 134 | we can "reason forward" by writing `have hq : q := hpq hp`. 135 | This adds a new hypothesis `hq : q` without changing the goal. 136 | This syntax is a little clunky, but we'll use it in a more general setting later. 137 | 138 | -/ 139 | 140 | example : p → (p → q) → q ∧ p := by 141 | intro hp 142 | intro hpq 143 | have hq : q := hpq hp 144 | split_goal 145 | { assumption } 146 | { assumption } 147 | 148 | 149 | 150 | 151 | 152 | 153 | /- Here's a little logic puzzle. 154 | 155 | (h1) Alan likes acorns, and either Betty likes begonias or Carl likes cacti. 156 | 157 | (h2) If Betty likes begonias, then Alan doesn’t like acorns. 158 | 159 | (h3) If Carl likes cacti, then Betty likes begonias. 160 | 161 | Show that these hypotheses are contradictory. 162 | 163 | -/ 164 | 165 | variable (al_ac : Prop) -- the proposition "Alan likes acorns" 166 | variable (betty_beg : Prop) -- the proposition "Betty likes begonias" 167 | variable (carl_cac : Prop) -- the proposition "Carl likes cacti" 168 | 169 | 170 | theorem these_are_contradictory 171 | (h1 : al_ac ∧ (betty_beg ∨ carl_cac)) 172 | (h2 : betty_beg → ¬ al_ac) 173 | (h3 : carl_cac → betty_beg) : 174 | False := by 175 | eliminate h1 with h4 h5 -- from h1, we know 176 | -- (h4) Alan likes acorns and 177 | -- (h5) either Betty likes begonias or Carl likes cacti. 178 | eliminate h5 with h6 h7 -- let's reason by cases on h5. 179 | 180 | { have h8 : ¬ al_ac := h2 h6 -- First, suppose Betty likes begonias. 181 | -- Then, by modus ponens with h2, Alan does not like acorns. 182 | contradiction } -- This contradicts h4! 183 | 184 | { have h9 : betty_beg := h3 h7 -- Now suppose Carl likes cacti. 185 | -- By modus ponens with h3, Betty likes begonias. 186 | have h10 : ¬ al_ac := h2 h9 -- But now again by modus ponens with h2, Alan does not like acorns. 187 | contradiction } -- Another contradiction! 188 | -- We've finished all our goals. 189 | 190 | 191 | /- 192 | 193 | You've just seen the missing connective. 194 | We skipped rules for negation introduction and elimination before. 195 | 196 | This example just showed how to *use* a negation: 197 | if we have hypotheses `hp : p` and `hnp : ¬ p`, then 198 | `contradiction` will prove any goal. 199 | 200 | How to introduce a negation? This uses a technique called 201 | "proof by contradiction", which we'll motivate in class soon. 202 | 203 | -/ 204 | 205 | 206 | 207 | 208 | end Lecture04 -------------------------------------------------------------------------------- /BrownCs22/Homework/Hw4.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.Defs 3 | import Mathlib.Data.Real.Basic 4 | import Mathlib.Tactic.Polyrith 5 | 6 | -- don't change these lines 7 | namespace HW4 8 | open Dvd Function 9 | set_option linter.unusedVariables false 10 | 11 | /- 12 | 13 | # Welcome to the Lean section of HW4! 14 | 15 | Today we'll explore a little bit about functions and relations in Lean. 16 | 17 | In particular, we'll think about the *logical form* 18 | of some of the function and relation properties we saw, 19 | like injectivity and surjectivity. 20 | 21 | 22 | ## Defining functions in Lean 23 | 24 | Lean, believe it or not, is a programming language like Python, Pyret, 25 | ReasonML, or whatever language you're familiar with. 26 | (Of the Brown intro course languages, it's probably closest to ReasonML.) 27 | 28 | This means we can define functions and evaluate them. 29 | The below defines a function named `my_quadratic` that takes as input 30 | an integer `x` and produces another integer. 31 | -/ 32 | 33 | def my_quadratic (x : ℤ) : ℤ := 34 | 4*x^2 + 55*x - 10 35 | 36 | #check my_quadratic 37 | 38 | /- 39 | We can evaluate it by using `#eval`. 40 | 41 | Unlike many languages like Python, we don't need parentheses 42 | if the argument is a single integer -- 43 | we *can* write `my_quadratic(12)`, but we don't need to. 44 | If we want to evaluate it on a more complicated expression, 45 | we do need parentheses around the argument. 46 | -/ 47 | 48 | #eval my_quadratic 12 49 | #eval my_quadratic (10 + 2) 50 | 51 | /- 52 | 53 | What makes Lean more exciting than these other languages? 54 | Not only can we define functions, but we can state *properties* 55 | of these functions, and *prove* that these properties hold. 56 | 57 | How convenient: we just learned about a lot of function 58 | and relation properties in class :) 59 | 60 | 61 | A side note: in class, we said that functions and relations were 62 | sets of ordered pairs, defined by their *graph*: 63 | `f(x) = y` if and only if `(x, y) ∈ G(f)`. 64 | But we also (in lecture 9) mentioned that it was a *choice* of ours 65 | to take sets as the "primary" concept and to define functions and relations 66 | in terms of sets. Lean, like most programming languages, 67 | takes the opposite approach: functions are a "primary" concept, and 68 | sets are defined in terms of functions. 69 | 70 | Confusing? Don't worry about it too much: if we're interested in properties 71 | of functions and relations, and in using these ideas, it doesn't really 72 | matter how they're defined. It matters what they can do. And Lean functions 73 | can do exactly the same things as our "sets of pairs" functions from class. 74 | 75 | 76 | 77 | 78 | ## Problems 1 and 2 79 | 80 | We'll work with some pretty simple functions today. 81 | Here's one, a *linear* function on the rational numbers, called `f`. 82 | 83 | (We call `f` linear because its graph is a straight line, or equivalently, 84 | because it doesn't multiply variables together. 85 | `g x = x^2` would not be linear.) 86 | 87 | -/ 88 | 89 | def f (x : ℚ) : ℚ := 2*x + 6 90 | 91 | #eval f 5 92 | #eval f (1/3) 93 | 94 | /- 95 | 96 | Let's prove that `f` is injective and surjective. 97 | 98 | Some tactics to remember: 99 | 100 | * `dsimp` unfolds definitions. You can `dsimp [Injective]` 101 | to expand what it means for `f` to be injective. 102 | (This is probably a good place to start!) 103 | You can `dsimp [f]` to change `f x` into `2*x + 6`. 104 | 105 | * `linarith` will do basic arithmetic for you, using facts 106 | that appear in your hypotheses. 107 | In particular, `linarith` will reason about linear functions. 108 | It will often get confused, or fail to prove things, 109 | if it sees variables multiplied together. 110 | 111 | -/ 112 | 113 | /- 2 points -/ 114 | theorem problem_1 : Injective f := by 115 | sorry 116 | 117 | /- 2 points -/ 118 | theorem problem_2 : Surjective f := by 119 | sorry 120 | 121 | 122 | /- 123 | 124 | ## Problem 3 125 | 126 | We can talk about relations in Lean as well. 127 | Again, the relations we see will be mostly mathematical, 128 | but there are lots of interesting relations on programming data types: 129 | for instance, the relation `is_prefix_of` on strings or lists. 130 | 131 | Here's a mathematical relation on `ℤ`: `R a b` holds when `a` divides `b`. 132 | -/ 133 | 134 | def R (a b : ℤ) : Prop := 135 | a ∣ b 136 | 137 | /- 138 | 139 | When we talked about relation properties in class, we described them 140 | in terms of "arrows" going out of and into the domain and codomain. 141 | But we also saw the logical form of these properties. Lean, unsurprisingly, 142 | knows about these logical forms. To show that `R` is reflexive, 143 | we'll need to prove `∀ x : ℤ, R x x`: 144 | "every integer is related to itself by `R`." 145 | 146 | Let's show that this relation is reflexive. Like before, 147 | `dsimp [Reflexive]` and `dsimp [R]` might be useful! 148 | 149 | 150 | -/ 151 | 152 | /- 2 points -/ 153 | theorem problem_3 : Reflexive R := by 154 | sorry 155 | 156 | /- 157 | 158 | ## Problem 4 159 | 160 | Finally, let's also show that `R` is transitive. 161 | (Why did we skip "symmetric"? Think about that one for a sec!) 162 | 163 | At some point in your proof, you might end up with a statement that 164 | looks like it could be solved by `linarith`, but can't. 165 | First, check that the statement should really be provable. 166 | If you're convinced it is -- are there variables multiplied together? 167 | 168 | There's another tactic (actually, two tactics) that can help out. 169 | 170 | `polyrith` is a very expensive version of `linarith` that only works 171 | on equalities, but handles nonlinear expressions. 172 | Because it can sometimes be slow, when it succeeds, 173 | it prints a message in the tactic state to the right. 174 | The message suggests to try using a tactic called `linear_combination`. 175 | Copy and paste this message, replacing your call to `polyrith`, 176 | and it should magically work. 177 | 178 | (We'll talk more in class about linear combinations very soon -- 179 | this is the same concept showing up in a different context.) 180 | 181 | -/ 182 | 183 | /- 3 points -/ 184 | theorem problem_4 : Transitive R := by 185 | sorry 186 | 187 | 188 | 189 | end HW4 -------------------------------------------------------------------------------- /BrownCs22/Exercises/LogicIdentitiesSolutions.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | /- 4 | 5 | In this file, we'll prove some of the identities from 6 | https://brown-cs22.github.io/resources/math-resources/logic.pdf 7 | 8 | Note that these identities are stated with the symbol `≡`, 9 | meaning "the formula on the left is equivalent to the formula on the right." 10 | In Lean, we can use iff (bi-implication, `↔`) to mean the same thing. 11 | 12 | Remember the tactics we learned in Lecture 4. 13 | We'll see a bit of new Lean syntax along the way. 14 | 15 | Also remember that it doesn't matter what we name our hypotheses. 16 | In our examples, we use patterns like `hp` for the hypothesis `p`, 17 | `hnpq` for the hypothesis `¬ p ∧ q` (hnpq for "hypothesis not p q"), etc. 18 | But this is just a custom. You can choose any names you want. 19 | As they say, the hardest problem in computer science is naming variables. 20 | 21 | Your task: fill in all the `sorry`s with proofs! 22 | 23 | -/ 24 | 25 | variable (p q r : Prop) 26 | 27 | -- ## Commutative Laws 28 | 29 | -- We'll do the first for you, as an example. 30 | -- Notice two things. 31 | -- One, must of these problems will start with `split_goal`, 32 | -- to turn the `iff` goal into two `implies` goals. 33 | -- Two, we structure our proof by putting each *subproof* in `{...}`. 34 | -- This isn't necessary but it helps with organization! 35 | example : p ∧ q ↔ q ∧ p := by 36 | split_goal 37 | { intro hpq 38 | eliminate hpq with hp hq 39 | split_goal 40 | assumption 41 | assumption } 42 | { intro hqp 43 | eliminate hqp with hq hp 44 | split_goal 45 | assumption 46 | assumption } 47 | 48 | example : p ∨ q ↔ q ∨ p := by 49 | split_goal 50 | { intro hpq 51 | eliminate hpq with hp hq 52 | { right 53 | assumption } 54 | { left 55 | assumption } } 56 | { intro hqp 57 | eliminate hqp with hq hp 58 | { right 59 | assumption } 60 | { left 61 | assumption } } 62 | 63 | 64 | -- ## Associative laws 65 | 66 | -- Notice that Lean doesn't print parentheses unless it needs to. 67 | example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := by 68 | split_goal 69 | { intro hpqr 70 | eliminate hpqr with hpq hr 71 | eliminate hpq with hp hq 72 | split_goal 73 | assumption 74 | split_goal 75 | assumption 76 | assumption } 77 | { intro hpqr 78 | eliminate hpqr with hp hqr 79 | eliminate hqr with hq hr 80 | split_goal 81 | split_goal 82 | assumption 83 | assumption 84 | assumption } 85 | 86 | -- This is getting a little repetitive. 87 | -- To mix it up, we'll only do one direction of the iff this time. 88 | -- You won't start with `split_goal`, but instead, ... what? 89 | example : (p ∨ q) ∨ r → p ∨ (q ∨ r) := by 90 | intro hpqr 91 | eliminate hpqr with hpq hr 92 | { eliminate hpq with hp hq 93 | { left 94 | assumption } 95 | { right 96 | left 97 | assumption } } 98 | { right 99 | right 100 | assumption } 101 | 102 | -- ## Distributive laws 103 | 104 | -- warning, this one is kind of long! 105 | example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := by 106 | split_goal 107 | { intro hpqr 108 | eliminate hpqr with hp hqr 109 | eliminate hqr with hq hr 110 | { left 111 | split_goal 112 | assumption 113 | assumption } 114 | { right 115 | split_goal 116 | assumption 117 | assumption } } 118 | { intro hpqpr 119 | eliminate hpqpr with hpq hpr 120 | { eliminate hpq with hp hq 121 | split_goal 122 | assumption 123 | left 124 | assumption } 125 | { eliminate hpr with hp hr 126 | split_goal 127 | assumption 128 | right 129 | assumption } } 130 | 131 | -- Again, let's just do one direction. 132 | -- When we're proving an implication `→`, we'll always start with `intro`. 133 | -- This is such a common pattern that Lean gives us special syntax for it: 134 | -- we can do the intro "in advance," before we start the proof, 135 | -- by naming the hypothesis to the left of the `:`. 136 | -- Notice that at the beginning of our proof, we already have the 137 | -- hypothesis `hpqr` in our context. 138 | example (hpqr : p ∨ (q ∧ r)) : (p ∨ q) ∧ (p ∨ r) := by 139 | eliminate hpqr with hp hqr 140 | { split_goal 141 | { left 142 | assumption } 143 | { left 144 | assumption } } 145 | { eliminate hqr with hq hr 146 | split_goal 147 | { right 148 | assumption } 149 | { right 150 | assumption } } 151 | 152 | 153 | -- ## Identity laws 154 | 155 | /- 156 | 157 | We'll do a few more together. 158 | 159 | Let's sneak a new tactic in. 160 | To prove a goal `True`, you can use the tactic `trivial`. 161 | This is a sensible proof rule: trivially, you can always show `True` is true! 162 | -/ 163 | 164 | example : p ∧ True ↔ p := by 165 | split_goal 166 | { intro hpT 167 | eliminate hpT 168 | assumption } 169 | { intro hpT 170 | split_goal 171 | assumption 172 | trivial } 173 | 174 | -- Before, we saw `contradiction` would work if we had hypotheses 175 | -- `p` and `¬ p` at the same time. 176 | -- It also works if we have a hypothesis `False`! 177 | example : p ∨ False ↔ p := by 178 | split_goal 179 | { intro hpF 180 | eliminate hpF with hp hF 181 | assumption 182 | contradiction } 183 | { intro hp 184 | left 185 | assumption } 186 | 187 | /- 188 | 189 | ## Negation laws 190 | 191 | If you're following along with the list of identities, you'll see that 192 | we've come to some laws like `(p ∨ ¬ p) ↔ True`. 193 | 194 | Unfortunately, we don't have the tools to prove these in Lean yet. 195 | 196 | But, you can do the **Idempotent laws** and **Universal bound laws**. 197 | Try stating and proving these yourself! 198 | 199 | -/ 200 | 201 | 202 | 203 | 204 | /- 205 | 206 | ## De Morgan's Laws 207 | 208 | These can be a bit of a challenge! Like the negation laws, 209 | a few of the implications here will require some new tools. 210 | But try out these directions. 211 | 212 | Remember that to prove a negation `¬ X`, you can use a *proof by contradiction*: 213 | add a hypothesis `h : X` using `intro h`, and then show a contradiction. 214 | 215 | -/ 216 | 217 | example (hnpnq : ¬ p ∨ ¬ q) : ¬(p ∧ q) := by 218 | intro hpq 219 | eliminate hpq with hp hq 220 | eliminate hnpnq with hnp hnq 221 | contradiction 222 | contradiction 223 | 224 | example (hnpnq : ¬ p ∧ ¬ q) : ¬ (p ∨ q) := by 225 | intro hpq 226 | eliminate hnpnq with hnp hnq 227 | eliminate hpq with hp hq 228 | contradiction 229 | contradiction 230 | 231 | 232 | /- 233 | 234 | That's it for the moment! 235 | Try the **Absorption laws** and **Negation of True and False** 236 | on your own, if you want to. Skip the definition laws for now. 237 | 238 | -/ -------------------------------------------------------------------------------- /BrownCs22/Demos/Lecture17.lean: -------------------------------------------------------------------------------- 1 | import Mathlib.Data.Nat.Basic 2 | import BrownCs22.Library.Tactics 3 | import BrownCs22.Library.ModSubst.ModRel 4 | import BrownCs22.Library.Defs 5 | 6 | set_option linter.unusedVariables false 7 | 8 | open BrownCs22 9 | 10 | /- 11 | 12 | # Lecture 17 Lean notes 13 | 14 | We've been using Lean (on and off) in homeworks. 15 | The plan for today is to get a bit more context about *why* 16 | people are interested in tools like this. 17 | 18 | 19 | ## GCD in Lean 20 | 21 | First of all: we've mentioned before that Lean is in fact 22 | a programming language itself. Here's the `gcd` function 23 | we've been discussing, implementing Euclid's algorithm. 24 | 25 | (We could implement the extended Euclidean algorithm, the "pulverizer", 26 | but let's keep it simple for now.) 27 | 28 | -/ 29 | 30 | def gcd (a b : ℕ) : ℕ := 31 | if b < a then 32 | gcd b a 33 | else if h : a = 0 then 34 | b 35 | else 36 | have hlt : b % a < a := Nat.mod_lt _ (by positivity) 37 | gcd (b%a) a 38 | termination_by gcd a b => a 39 | 40 | 41 | #eval gcd 55 60 42 | #eval gcd 22 3 43 | 44 | /- 45 | 46 | This looks more or less like our slides version, with a couple funny things. 47 | First of all: there are `have` statements and hypothesis names `h : ` in the 48 | body of our program! 49 | Second of all, what's the `termination_by` at the end? 50 | 51 | 52 | Lean is a very particular programming language. 53 | It does not support functions that fail to terminate, or that raise 54 | error messages. In other words: *we can only define total functions!* 55 | 56 | The statements we write in the body of our program help Lean decide 57 | that the program does, in fact, terminate. 58 | (Actually: it *proves* that the program terminates, automatically.) 59 | 60 | 61 | Proving about programs, cool. It's here that proof assistants like Lean 62 | really shine. Most proofs can't be done automatically, but... 63 | 64 | Here's a proof that the output of `gcd` always divides both the inputs. 65 | (We won't talk through the details in class.) 66 | -/ 67 | 68 | 69 | theorem gcd_divides : ∀ a b : ℕ, gcd a b ∣ a ∧ gcd a b ∣ b := by 70 | -- We proceed by strong induction on `a`, 71 | -- with predicate `∀ b : ℕ, gcd a b ∣ a ∧ gcd a b ∣ b`. 72 | strong_induction 73 | 74 | -- Introduce the induction hypothesis into the context. 75 | intro a 76 | intro ih 77 | intro b 78 | 79 | -- Expand the definition of `gcd`. 80 | unfold gcd 81 | 82 | -- Let's reason by cases on whether `b < a`. 83 | by_cases hba : b < a 84 | 85 | -- If we know `b < a`, we can simplify our `gcd` program (we know which branch of the `if` statement we take) 86 | { simp [hba] 87 | 88 | -- We can instantiate the induction hypothesis to match the goal. 89 | have ih2 : gcd b a ∣ b ∧ gcd b a ∣ a := ih b hba a 90 | rw [and_comm] 91 | assumption } 92 | 93 | -- Otherwise, we know `¬ b < a`. 94 | { simp [hba] 95 | 96 | -- Reason by cases again, on whether `a = 0`. 97 | by_cases ha : a = 0 98 | 99 | -- If `a = 0`, our proof is easy: `0 ∣ 0 ∧ 0 ∣ b` is trivial. 100 | { simp [ha] } 101 | 102 | -- Otherwise, we can again simplify our program knowing we're in the "else" branch. 103 | { simp [ha] 104 | 105 | -- We can again instantiate our induction hypothesis. 106 | have ih3 : gcd (b % a) a ∣ b % a ∧ gcd (b % a) a ∣ a := 107 | ih (b % a) (Nat.mod_lt _ (by positivity)) a 108 | eliminate ih3 with hl hr 109 | 110 | -- We need to prove two "divides" claims. 111 | split_goal 112 | 113 | -- The first comes directly from our induction hypothesis. 114 | { assumption } 115 | 116 | -- Otherwise, let's do some arithmetic, expanding the definition of `∣`. 117 | { dsimp [Dvd.dvd] at hl hr ⊢ 118 | eliminate hl with k hk 119 | eliminate hr with m hm 120 | 121 | -- We apply the qhotient-remainder theorem on `b % a`, 122 | -- which in Lean is named `Nat.div_add_mod`. 123 | have qr := Nat.div_add_mod b a 124 | 125 | -- We can then compute a witness for divisibility... 126 | existsi (b/a)*m + k 127 | 128 | -- and do a (slightly ugly) calculation, with the help of polyrith. 129 | -- (Don't worry about the details here too much.) 130 | zify at qr hm hk ⊢ 131 | linear_combination hk + (b / a : ℤ) * hm - qr } } } 132 | 133 | 134 | 135 | /- 136 | 137 | A lot of real-world programs -- encryptors or decryptors, for instance -- 138 | depend on a subroutine that computes gcds. 139 | A lot of these programs are safety-critical, in that bugs in this programs 140 | could have severe social, political, or economic consequences. 141 | 142 | Imagine if they were formally verified.... 143 | 144 | -/ 145 | 146 | 147 | 148 | open BrownCs22.Int Dvd 149 | 150 | /- 151 | 152 | ## Modular arithmetic in Lean 153 | 154 | On Friday we introduced the notion of "equivalence modulo `n`". 155 | Two integers are equivalent if they are separated by a multiple of `n`. 156 | 157 | -/ 158 | 159 | #check ModEq 160 | 161 | #eval 4 ≡ 14 [ZMOD 5] 162 | #eval 23 ≡ 11 [ZMOD 12] 163 | #eval 22 ≡ 11 [ZMOD 12] 164 | 165 | 166 | /- 167 | We can *prove* modular equivalences, using tools we've used before. 168 | -/ 169 | 170 | example : 4 ≡ 14 [ZMOD 5] := by 171 | dsimp [ModEq] 172 | dsimp [dvd] 173 | existsi -2 174 | numbers 175 | 176 | /- 177 | But if Lean can evaluate facts like this, why can't it prove them automatically? 178 | -/ 179 | 180 | example : 4 ≡ 14 [ZMOD 5] := by 181 | decide 182 | 183 | 184 | /- 185 | Ultimately, we're not interested so much in a bare calculator. 186 | We want to prove things about equivalences. 187 | 188 | Here's a theorem that says, if `a ≡ b`, we can add equivalent values 189 | to each side. (We used this in Friday's lecture...) 190 | -/ 191 | 192 | theorem Int.ModEq.add {n a b c d : ℤ} 193 | (h1 : a ≡ b [ZMOD n]) (h2 : c ≡ d [ZMOD n]) : 194 | a + c ≡ b + d [ZMOD n] := by 195 | dsimp [ModEq, dvd] at * 196 | eliminate h1 with x hx 197 | eliminate h2 with y hy 198 | existsi x + y 199 | calc 200 | a + c - (b + d) = a - b + (c - d) := by linarith 201 | _ = n * x + n * y := by rw [hx, hy] 202 | _ = n * (x + y) := by linarith 203 | 204 | 205 | 206 | /- 207 | We ended the day on Friday thinking about *division*. 208 | If I know `a ≡ b`, do I know `a/c ≡ b/c`? 209 | Certainly not if `c` doesn't divide `a` or `b`. 210 | But what if it does? Could we prove this theorem? 211 | -/ 212 | 213 | example {a b c n : ℤ} 214 | (hca : c ∣ a) (hcb : c ∣ b) (hab : a ≡ b [ZMOD n]) : 215 | a/c ≡ b/c [ZMOD n] := by 216 | sorry 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | /- 226 | After some thought and experimentation: *no!* 227 | Let `a = 10`, `b = 22`, `c = 2`, `n = 4`. 228 | -/ 229 | 230 | example : 2 ∣ 10 := by decide 231 | example : 2 ∣ 22 := by decide 232 | example : 10 ≡ 22 [ZMOD 4] := by decide 233 | example : 10/2 ≡ 22/2 [ZMOD 4] := by sorry 234 | 235 | #eval 10/2 ≡ 22/2 [ZMOD 4] 236 | 237 | 238 | 239 | 240 | /- 241 | 242 | So, we can't necessarily "cancel" across both sides of a modular equivalence. 243 | But we can simplify addition, multiplication, exponents, ... 244 | 245 | -/ 246 | 247 | 248 | example (a b : ℤ) (ha : a ≡ 4 [ZMOD 5]) (hb : b ≡ 3 [ZMOD 5]) : 249 | a * b + b ^ 3 + 3 ≡ 2 [ZMOD 5] := 250 | calc 251 | a * b + b ^ 3 + 3 ≡ 4 * b + b ^ 3 + 3 [ZMOD 5] := by mod_subst [ha] 252 | _ ≡ 4 * 3 + 3 ^ 3 + 3 [ZMOD 5] := by mod_subst [hb] 253 | _ = 2 + 5 * 8 := by numbers 254 | _ ≡ 2 [ZMOD 5] := by decide 255 | 256 | -------------------------------------------------------------------------------- /BrownCs22/Library/TruthTables.lean: -------------------------------------------------------------------------------- 1 | import Std.Data.List.Basic 2 | import Lean 3 | 4 | open Lean Widget 5 | 6 | inductive BExpr 7 | | const : Bool → BExpr 8 | | var : String → BExpr 9 | | and : BExpr → BExpr → BExpr 10 | | or : BExpr → BExpr → BExpr 11 | | implies : BExpr → BExpr → BExpr 12 | | not : BExpr → BExpr 13 | | iff : BExpr → BExpr → BExpr 14 | deriving Repr, DecidableEq, Inhabited 15 | 16 | instance : ToString BExpr := 17 | let rec toString 18 | | .const true => "⊤" 19 | | .const false => "⊥" 20 | | .var x => x 21 | | .and p q => "(" ++ toString p ++ " ∧ " ++ toString q ++ ")" 22 | | .or p q => "(" ++ toString p ++ " ∨ " ++ toString q ++ ")" 23 | | .implies p q => "(" ++ toString p ++ " → " ++ toString q ++ ")" 24 | | .not p => "¬" ++ toString p 25 | | .iff p q => "(" ++ toString p ++ " ↔ " ++ toString q ++ ")" 26 | ⟨toString⟩ 27 | 28 | def getVars : BExpr → List String 29 | | .var x => [x] 30 | | .and p q | .or p q | .implies p q | .iff p q => getVars p ++ getVars q 31 | | .not p => getVars p 32 | | .const _ => [] 33 | 34 | def subst : BExpr → String → Bool → BExpr 35 | | .var x, s, b => if x = s then .const b else .var x 36 | | .and p q, s, b => .and (subst p s b) (subst q s b) 37 | | .or p q, s, b => .or (subst p s b) (subst q s b) 38 | | .implies p q, s, b => .implies (subst p s b) (subst q s b) 39 | | .iff p q, s, b => .iff (subst p s b) (subst q s b) 40 | | .not p, s, b => .not (subst p s b) 41 | | .const v, _, _ => .const v 42 | 43 | def substAll (asgns : List (String × Bool)) (e : BExpr) : BExpr := 44 | asgns.foldl (λ | acc, (v, b) => subst acc v b) e 45 | 46 | def eval : BExpr → Option Bool 47 | | .const b => some b 48 | | .and p q => do (← eval p) && (← eval q) 49 | | .or p q => do (← eval p) || (← eval q) 50 | | .implies p q => do (not (← eval p)) || (← eval q) 51 | | .not p => do not (← eval p) 52 | | .var _ => none 53 | | .iff p q => do (← eval p) == (← eval q) 54 | 55 | def generateSubExprs : BExpr → List BExpr 56 | | .const _ => [] -- we don't need to examine truth vals of ⊤/⊥ 57 | | .var x => [.var x] 58 | | e@(.or p q) | e@(.and p q) | e@(.implies p q) | e@(.iff p q) => 59 | generateSubExprs p ++ generateSubExprs q ++ [e] 60 | | .not p => generateSubExprs p ++ [.not p] 61 | 62 | def permuteVarVals : List String → List (List (String × Bool)) := λ vs => 63 | let count := 2 ^ vs.length 64 | let rec helper : Nat → List (List (String × Bool)) 65 | | 0 => [] 66 | | .succ n => 67 | vs.mapIdx (λ (i : Nat) (v : String) => 68 | (v, decide $ ((count - n.succ) >>> i) % 2 = 0)) 69 | :: helper n 70 | helper count 71 | 72 | def List.uniqueAux {α} [DecidableEq α] : List α → List α → List α 73 | | [], acc => acc.reverse 74 | | x :: xs, acc => if x ∈ acc then uniqueAux xs acc else uniqueAux xs (x :: acc) 75 | 76 | def List.unique {α} [DecidableEq α] (xs : List α) := uniqueAux xs [] 77 | 78 | def prefixVars : List BExpr → List BExpr × List BExpr 79 | | [] => ([], []) 80 | | e@(.var _) :: tt => 81 | let (restV, restC) := prefixVars tt 82 | (e::restV, restC) 83 | | e :: tt => 84 | let (restV, restC) := prefixVars tt 85 | (restV, e::restC) 86 | 87 | -- TODO: don't use imperative things that can crash and burn (`get!`) 88 | def truthTable (e : BExpr) : List (List (String × Bool)) := 89 | let vars := getVars e 90 | let (BVars, VExps) := prefixVars ((generateSubExprs e).unique) 91 | let subBExprs := BVars.append VExps 92 | let allAsgns := permuteVarVals vars.unique 93 | allAsgns.map (λ asgns => 94 | subBExprs 95 | |> List.map (λ e => (toString e, e)) 96 | |> List.map (λ | (s, e) => (s, substAll asgns e)) 97 | |> List.map (λ | (s, e) => (s, (eval e).get!)) 98 | ) 99 | 100 | def htmlOfTable : List (List (String × Bool)) → String := 101 | λ t => 102 | -- Hacky workaround 103 | if h1 : t = [] then "" else 104 | "" 106 | ++ "" 107 | ++ List.foldl (λ acc p => acc ++ "") "" (t.head h1) 109 | ++ "" 110 | ++ List.foldl (λ acc r => 111 | acc ++ "" 112 | ++ r.foldl (λ acc p => 113 | let txt := toString p.2 114 | let bg := if txt = "true" then "darkgreen" else "darkred" 115 | acc ++ "") "" ++ "") "" t 117 | ++ "
" 108 | ++ toString p.1 ++ "
" ++ toString p.2 ++ "
" 118 | 119 | def mkTableWidget (t : List (List (String × Bool))) : 120 | UserWidgetDefinition where 121 | name := "Truth Table" 122 | javascript := " 123 | import * as React from 'react'; 124 | export default function(props) { 125 | return React.createElement('div', {dangerouslySetInnerHTML: {__html: '" 126 | ++ htmlOfTable t 127 | ++ "'}}) 128 | }" 129 | 130 | def null := Lean.Json.null 131 | 132 | syntax (name := truthTableCommand) "#truth_table" term : command 133 | 134 | partial def bExprOfPropTerm : 135 | TSyntax `term → Elab.Command.CommandElabM (TSyntax `term) 136 | | `(($P)) => bExprOfPropTerm P 137 | | `($P ∧ $Q) => do `(.and ($(← bExprOfPropTerm P)) ($(← bExprOfPropTerm Q))) 138 | | `($P ∨ $Q) => do `(.or ($(← bExprOfPropTerm P)) ($(← bExprOfPropTerm Q))) 139 | | `($P → $Q) => do `(.implies ($(← bExprOfPropTerm P)) ($(← bExprOfPropTerm Q))) 140 | | `($P ↔ $Q) => do `(.iff ($(← bExprOfPropTerm P)) ($(← bExprOfPropTerm Q))) 141 | | `(¬ $P) => do `(.not ($(← bExprOfPropTerm P))) 142 | | `(True) => do `(.const true) 143 | | `(False) => do `(.const false) 144 | -- We don't need this, but its breaking things may indicate a Lean bug 145 | -- | `(¬ ($P)) => do `(.not ($(← bExprOfPropTerm P))) 146 | | p => 147 | let vnm := p.raw.getId.toString 148 | 149 | match p.raw.isIdent with 150 | | false => throwError ("Illegal Expression " ++ (toString p.raw)) 151 | | true => `(.var $(Syntax.mkStrLit vnm)) 152 | 153 | -- TODO: This approach should be more robust, but it doesn't appear that Lean 4 154 | -- currently supports antiquotations of `Expr`s out of the box, and adding the 155 | -- `quote4` dependency could create even more headaches. 156 | -- partial def bExprOfPropTerm : Expr → Elab.TermElabM (TSyntax `term) 157 | -- | .app nm e' => do 158 | -- -- This is inadequate -- `nm` might actually be an application itself 159 | -- -- (consider `Or (And P Q) R`) 160 | -- let isNot : Bool ← Meta.isExprDefEq nm (.const `Not [.succ .zero]) 161 | -- if isNot 162 | -- then `(BExpr.not ($(← bExprOfPropTerm e'))) 163 | -- else 164 | -- let isAnd : Bool ← Meta.isExprDefEq nm (.const `And [.succ .zero]) 165 | -- sorry 166 | -- | _ => sorry 167 | 168 | @[command_elab «truthTableCommand»] private unsafe def elabTableWidget : 169 | Elab.Command.CommandElab := 170 | open Lean Lean.Elab Command Term in λ 171 | | stx@`(#truth_table $prop) => do 172 | let ident ← mkFreshIdent stx 173 | let decl := Lean.Syntax.getId ident 174 | let ident := mkIdent decl 175 | 176 | -- let tbStx : Lean.TSyntax `term ← Lean.Elab.Command.runTermElabM (λ _ => 177 | -- do bExprOfPropTerm (← Lean.Elab.Term.elabType prop)) 178 | 179 | elabDeclaration (← 180 | `(@[widget] def $ident := 181 | mkTableWidget (truthTable $(← bExprOfPropTerm prop))) 182 | -- `(@[widget] def $ident := mkTableWidget (truthTable $tbStx))) 183 | ) 184 | 185 | let null_stx ← `(Json.null) 186 | let props : Json ← runTermElabM fun _ => 187 | Term.evalTerm Json (mkConst ``Json) null_stx 188 | saveWidgetInfo decl props stx 189 | | _ => throwUnsupportedSyntax 190 | 191 | variable (P Q R S : Prop) 192 | 193 | #truth_table P ∧ True 194 | #truth_table (((P)) ∨ Q) ∧ (¬Q ∨ ¬((R))) → S 195 | #truth_table P ∨ (P → Q) 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /BrownCs22/Homework/Hw2.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | 3 | namespace HW2 4 | 5 | set_option linter.unusedVariables false 6 | open Dvd 7 | 8 | /- 9 | 10 | # Welcome to the Lean section of HW2! 11 | 12 | In this assignment, we're going to move beyond random letters. 13 | Enough of those `p`, `q`, `r` problems. 14 | 15 | Some things to keep in mind: 16 | 17 | * The file `BrownCs22/Demos/QuickReference.lean` has some brief notes on 18 | which tactics we've seen and what they do. Check it out! 19 | 20 | * There's also an FAQ thread on Ed. Check there before making a new post 21 | if you run into issues. 22 | 23 | * You can hover over any fancy character in VSCode to see how to type it. 24 | 25 | * Remember, the autograder will not be happy if your submission has any 26 | *red* error messages. If you can't finish a question, you can replace your work 27 | with `sorry`. You won't get points on that question but the autograder will run. 28 | 29 | 30 | Our goal today: let's prove some interesting things about numbers! 31 | We're looking a little bit ahead, preparing for the number theory 32 | section of this class. In the process, we'll get familiar with the proof rules 33 | for the *quantifiers* `∀` and `∃`. 34 | 35 | (If you don't remember those rules, check out the Lecture 6 notes and the 36 | quick reference.) 37 | 38 | 39 | ## A few helpful tactics 40 | 41 | In addition to the proof rules for quantifiers, these tactics may be useful 42 | to finish your proofs. Depending on how you do your proofs, you may not 43 | need all of these. 44 | 45 | * `positivity`: if your goal is to show something is positive or nonnegative 46 | (like `0 < x`, `x ≥ 0`, `0 ≤ a^2`, ...) and this "obviously" follows from 47 | your hypotheses, write `positivity` to finish that goal. This tactic knows 48 | basic facts like "natural numbers are nonnegative" and "the square of a 49 | positive number is positive." It does not know complicated arithmetic. 50 | 51 | * `numbers`: If your goal is to show an arithmetic statement about numerals, 52 | like `5 + 5 = 10` or `1000 < 50000000`, `numbers` will close the goal. 53 | It's basically a calculator! 54 | 55 | * `linarith`: stands for "linear arithmetic." (If you don't know this term, 56 | don't worry.) `linarith` does similar things to `positivity` and `numbers`, 57 | but it can do some simple arithmetic, and use hypotheses. 58 | For instance, if you know `h1 : x < 10` and `h2 : x + y < 20`, `linarith` 59 | can prove the goal `3*x + 2*y < 50`. 60 | 61 | A good strategy: if you have a goal with no variables in it (only numbers), 62 | try `numbers`. If it's a comparison between some variables and 0, try 63 | `positivity`. Otherwise, try `linarith`. 64 | 65 | -/ 66 | 67 | 68 | /- 69 | 70 | ## Problem 1 71 | 72 | Here's some practice with quantifiers. Read the statement of this question 73 | out loud to yourself -- what is it stating? 74 | 75 | Remember `ℕ = {0, 1, 2, ...}`, the natural numbers. 76 | 77 | -/ 78 | 79 | /- 3 points -/ 80 | theorem problem_1 : ∃ n : ℕ, ∀ x : ℕ, n ≤ x := by 81 | sorry 82 | 83 | 84 | 85 | 86 | 87 | /- 88 | 89 | ## Problems 2-3 90 | 91 | We say that a natural number `x : ℕ` *divides* a natural number `y : ℕ`, 92 | written `x ∣ y`, if there exists `c : ℕ` such that `y = x * c`. 93 | 94 | This is an existential claim in disguise! Written in logic, we can say 95 | `x ∣ y` is defined to mean `∃ c : ℕ, y = x * c`. 96 | 97 | If you see a statement like `x ∣ y` in your goal or in a hypothesis, 98 | you can use the tactic `dsimp [dvd]` to change it to the equivalent existential. 99 | This stands for "definition simplify". 100 | (In the future, we'll use `dsimp` to unfold more than just division.) 101 | 102 | For example: 103 | -/ 104 | 105 | example (x : ℕ) : x ∣ 10 → x ∣ 10 := by 106 | intro hx10 107 | dsimp [dvd] -- change the goal to an existential 108 | dsimp [dvd] at hx10 -- change the hypothesis hx10 to an existential 109 | assumption 110 | 111 | /- 112 | 113 | We don't *need* to use `dsimp`. 114 | If we want to prove a "divides" statement, we use `existsi` directly, 115 | just like for the existential statement. 116 | If we want to use a "divides" hypotheses, we can `eliminate` it directly, 117 | again just like for an existential. 118 | But it should never hurt to use `dsimp` if you want to. 119 | 120 | 121 | First, practice an introduction: 122 | 123 | -/ 124 | 125 | /- 1 point -/ 126 | theorem problem_2 : 5 ∣ 15 := by 127 | sorry 128 | 129 | 130 | /- 131 | 132 | Let's use this definition of divides to prove that any divisor of 10 133 | is also a divisor of 100. 134 | 135 | -/ 136 | 137 | /- 3 points -/ 138 | theorem problem_3 : ∀ x : ℕ, x ∣ 10 → x ∣ 100 := by 139 | sorry 140 | 141 | 142 | 143 | 144 | 145 | /- 146 | 147 | ## Problem 4 148 | 149 | This time, you're going to practice a forall elimination! 150 | Remember the syntax for this: 151 | if you have a hypothesis `h : ∀ x : ℕ, MyProperty1 x ∧ MyProperty2 x`, 152 | you could write `have h2 : MyProperty1 100 ∧ MyProperty2 100 := h 100`, 153 | or even `have h4 : MyProperty1 my_var ∧ MyProperty2 my_var := h my_var`, 154 | if you have a variable named `my_var`. 155 | We say that we have *instantiated* the universal statement 156 | with `100` and `my_var`, respectively, "plugging in" these values for `x`. 157 | 158 | 159 | 160 | Your creative step in this problem is to decide how to instantiate `h`. 161 | `h` says a certain proposition is true for every `x`. 162 | Which value of `x` is useful to you? 163 | 164 | Try to reason through this problem on paper before solving it in Lean. 165 | There's a hint at the bottom of this file. 166 | 167 | Notice that we've already introduced the hypothesis `h` for you in this problem. 168 | No need to start with `intro`. 169 | 170 | 171 | -/ 172 | 173 | /- 3 points -/ 174 | theorem problem_4 (a b : ℤ) (h : ∀ x, 2*a ≤ x ∨ x ≤ 2*b) : a ≤ b := by 175 | sorry 176 | 177 | 178 | 179 | 180 | /- 181 | 182 | ## Bonus challenge 183 | 184 | This one isn't for points, and doesn't have to do with numbers. 185 | But it's a fun logic puzzle. 186 | Try it if you're enjoying Lean and want a challenge. 187 | But don't feel bad if you can't figure it out! 188 | This problem relates to the infamous "barber paradox" (look it up!). 189 | 190 | A hint: the `have` syntax we used for modus ponens is very general. 191 | You can use this whenever you want, to create a new fact in your context. 192 | But you can't create a fact without justifying it. 193 | If you write, for instance, `have h_new_hyp : p ∧ q` 194 | (*without* a `:=` at the end, which we used for modus ponens), 195 | you will see that a new goal appears. First Lean wants you to prove `p ∧ q`. 196 | Then it wants you to return to your original goal, with a new hypothesis 197 | `h_new_hyp : p ∧ q` in your context. 198 | 199 | An example: 200 | 201 | -/ 202 | 203 | 204 | example (p q : Prop) (hp : p) (hq : q) : True := by 205 | have hpq : p ∧ q -- after this line, the goal becomes to show `p ∧ q` 206 | { split_goal -- use brackets to focus on the first goal 207 | assumption 208 | assumption } 209 | -- at this point in the proof, we have a new hypothesis `hpq : p ∧ q`. 210 | -- but this was just a silly example so we don't need to use it 211 | trivial 212 | 213 | 214 | 215 | /- Informally, this is like "reasoning forward". From things you already know, 216 | you're deriving a new thing, and then using that new thing later on. 217 | 218 | This technique is very helpful to finish this problem, when you look at 219 | the proof state and feel stuck. Try puzzling through it: what new thing(s) 220 | can you state and use? 221 | 222 | -/ 223 | 224 | 225 | 226 | theorem bonus_challenge (p : Prop) : ¬ (p ↔ ¬ p) := by 227 | sorry 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | /- 236 | 237 | ## Problem 4 hint 238 | 239 | Just thinking informally, not in Lean: 240 | if `2*a ≤ a + b`, then `a ≤ b`, 241 | since we can subtract `a` from both sides. 242 | 243 | -/ 244 | 245 | 246 | end HW2 -------------------------------------------------------------------------------- /BrownCs22/Homework/Hw8.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.Defs 3 | import Mathlib.Mathport.Syntax 4 | import Mathlib.Data.Nat.Parity 5 | 6 | set_option pp.notation true 7 | set_option pp.explicit false 8 | set_option pp.fullNames false 9 | set_option pp.instances false 10 | set_option pp.instanceTypes false 11 | set_option pp.instantiateMVars false 12 | set_option pp.privateNames false 13 | set_option pp.universes false 14 | set_option linter.unusedVariables false 15 | 16 | 17 | 18 | /- 19 | 20 | # Combinatorics! 21 | 22 | In this homework, we'll be looking at some combinatorics problems formalized in 23 | Lean. In doing so, we'll get to see how the same function can be defined in several 24 | different ways in Lean, and how our choice of definition can affect the proofs we 25 | write about that function. 26 | 27 | Our proofs will also expose the many unspoken assumptions that often go unnoticed 28 | when proving theorems in informal math. Those assumptions can lead to incorrect 29 | proofs, if we use those theorems in situations where the assumptions are false! 30 | 31 | As with last homework, much of this file is a demo. The problems for you to solve 32 | start around line 275. 33 | 34 | 35 | 36 | 37 | 38 | # How to define "n choose k" or, how to choose "choose" 39 | 40 | Before we get to the function definitions, we need to go over some new notation. 41 | 42 | In Lean, we calculate factorials with the `Nat.factorial` function. But that becomes 43 | annoying to type out (and to read) when you're working with tons of factorial 44 | expressions. It would be nice if we could use the exclamation point notation ordinarily 45 | used in written math, where `n! = Nat.factorial n`. 46 | 47 | But unfortunately, `!` is a somewhat special character in Lean (it means `not`), 48 | so we had to get SUPER creative. So, for this homework only, we've introduced a 49 | bit of special notation: 50 | 51 | -/ 52 | 53 | notation:max n "¡" => Nat.factorial n 54 | 55 | #eval 6¡ 56 | 57 | /- 58 | 59 | We're using the UPSIDE-DOWN exclamation point! (¿?¡!?¿) You can type this character 60 | by entering a backslash `\` and then a normal exclamation point. 61 | 62 | Now, how should we define the combination, or "choose", function? 63 | 64 | We could just directly steal the definition we used in lecture: 65 | 66 | -/ 67 | 68 | def ChooseAttempt1 (n k : ℕ) := n¡ / (k¡ * (n-k)¡) 69 | 70 | /- 71 | 72 | But what does this actually mean? In lecture, we were implicitly working under the 73 | assumption that `k` and `n` are natural numbers, and we've formalized that. But 74 | we were also working under the assumption that `k ≤ n`, and that is no longer the 75 | case here - our `ChooseAttempt1` function generalizes over all natural numbers. 76 | 77 | That's a good thing, since we would like to be able to say `2 Choose 3 = 0`, for 78 | a couple reasons. Firstly, it's impossible to choose 3 elements from a set of 2, 79 | so it makes sense to say there are 0 ways of doing so. Secondly, this is required 80 | to make Pascal's Rule (https://en.wikipedia.org/wiki/Pascal%27s_rule) work nicely. 81 | 82 | (Consider applying Pascal's Rule when `n = k = 3`.) 83 | 84 | However, this also means we have to deal with the thorny question of what `(n-k)¡` 85 | means when `n < k`. The factorial function doesn't make much sense on negative 86 | numbers, so it's a good thing the subtraction function on natural numbers bottoms 87 | out at 0. 88 | 89 | Well, when `n < k`, then `(n-k)¡ = 0¡ = 1`, and `n¡ < k¡`, and division of natural 90 | numbers rounds down, so `ChooseAttempt1 n k = n¡ / (k¡ * 1) = n¡ / k¡ = 0`. 91 | 92 | So this actually seems to work! 93 | 94 | Except, wait, we lied. `n < k` USUALLY implies `n¡ < k¡`, with one exception: 95 | when `n = 0` and `k = 1`. 96 | 97 | -/ 98 | 99 | #eval ChooseAttempt1 0 1 -- WRONG 100 | 101 | /- 102 | 103 | So we need to amend our definition. Let's try again: 104 | 105 | -/ 106 | 107 | def ChooseAttempt2 (n k : ℕ) := 108 | match n, k with 109 | | 0, _ => 0 110 | | n, k => n¡ / (k¡ * (n-k)¡) 111 | 112 | /- 113 | 114 | This syntax means roughly the following: 115 | "First check if `n = 0`. In this case return 0 (regardless of what `k` is). 116 | Otherwise, return `n¡ / (k¡ * (n-k)¡)`." 117 | 118 | This works, right? Nope. 119 | 120 | -/ 121 | 122 | #eval ChooseAttempt2 0 0 -- WRONG 123 | 124 | /- 125 | 126 | It can seem funny to ask how many ways there are to choose 0 elements 127 | from a set of 0. But another way of saying this is, how many 0-element 128 | subsets of the empty set are there? There's one: the empty set itself! 129 | So `0 choose 0` should be 1. 130 | 131 | Last try. 132 | 133 | -/ 134 | 135 | @[reducible] -- ignore this first line 136 | def Choose (n k : ℕ) := 137 | match n, k with 138 | | _, 0 => 1 139 | | 0, _ => 0 140 | | n, k => n¡ / (k¡ * (n-k)¡) 141 | 142 | /- 143 | 144 | Finally, this works. Notice how many assumptions, and how much nuance, went undetected 145 | in this one little function. Notice also how this definition is NOT recursive - 146 | as a result, our proofs about it will not use induction. 147 | 148 | We're introducing notation for this function as well. 149 | 150 | `(n C k)` means `Choose n k`. The parentheses, and the spaces around the C, are 151 | required. To use `simp` to break down a `Choose` expression, you still have to 152 | refer to it by its proper name, e.g. `simp [Choose]`. 153 | 154 | -/ 155 | 156 | notation:max "(" n " C " k ")" => Choose n k 157 | 158 | #eval (4 C 2) 159 | 160 | /- 161 | 162 | Let's prove some stuff! You are free to use the theorems below however you want 163 | in the following problems. You don't need to understand our Lean proofs of them. 164 | 165 | -/ 166 | 167 | -- Every natural number is either 0, or the successor of another natural number. 168 | lemma eq_zero_or_exists_succ (n : ℕ) : 169 | n = 0 ∨ ∃ (n0 : ℕ), n = n0 + 1 := by 170 | have hnzp : n = 0 ∨ n > 0 := Nat.eq_zero_or_pos n 171 | eliminate hnzp 172 | -- the previous two lines could have been written as one, `eliminate Nat.eq_zero_or_pos n`. 173 | {left; assumption} 174 | { 175 | right 176 | eliminate (Nat.exists_eq_succ_of_ne_zero (by linarith)) with w 177 | existsi w 178 | assumption 179 | } 180 | 181 | -- If a number is more than another number, it must be a successor of some number. 182 | lemma exists_succ_of_gt {x n : ℕ} : 183 | x < n → ∃ (n0 : ℕ), n = n0 + 1 := by 184 | intro h 185 | have hnzp : n = 0 ∨ ∃ n0, n = n0 + 1 := eq_zero_or_exists_succ n 186 | eliminate hnzp 187 | -- again, the previous two lines could have been written as `eliminate eq_zero_or_exists_succ n`. 188 | linarith 189 | assumption 190 | 191 | -- Any pair of natural numbers is equal or isn't. 192 | lemma nat_eq_or_ne (n k : ℕ) : n = k ∨ n ≠ k := by 193 | exact Decidable.eq_or_ne n k 194 | 195 | -- Every natural number is either equal to zero or not. 196 | lemma eq_zero_or_ne_zero (n : ℕ) : n = 0 ∨ n ≠ 0 := by 197 | exact nat_eq_or_ne n 0 198 | 199 | -- As long as `n` and `k` are both positive, `Choose n k` is 200 | -- equivalent to `n¡ / (k¡ * (n-k)¡)` 201 | @[simp] lemma Choose_expansion_of_pos_n_k (n k : ℕ) : 202 | 0 < n → 0 < k → Choose n k = n¡ / (k¡ * (n-k)¡) := by 203 | intro hn hk 204 | rewrite [Nat.pos_iff_ne_zero] at * 205 | simp [Choose] 206 | 207 | -- So `simp` will automatically clean up annoying bits of arithmetic that might 208 | -- pop up in your problems. You shouldn't need to call these manually. 209 | @[simp] lemma _112 : 1 + 1 = 2 := by numbers 210 | @[simp] lemma _x112 (x : ℕ) : x + 1 + 1 = x + 2 := by rw [add_assoc]; 211 | @[simp] lemma _x11x (x : ℕ) : x + 1 - 1 = x := by simp 212 | @[simp] lemma _011 : 0 + 1 = 1 := by simp 213 | @[simp] lemma _s01 : Nat.succ 0 = 1 := by simp 214 | @[simp] lemma Choose_zero_eq_one : ∀ n, (n C 0) = 1 := by simp [Choose] 215 | 216 | 217 | 218 | /- 219 | 220 | # Problem 1 221 | 222 | Remember that you won't be able to break down an expression like `(n C k)` until 223 | you know more about `n` or `k` (you can't break it down without knowing which arm 224 | of the `Choose` function will match!) So, one trick that's helpful in most of 225 | these proofs is breaking the problem into cases - for example, one case where 226 | `n = 0`, and one case where `∃ n0, n = n0 + 1`. It will occasionally be necessary 227 | to nest these cases. 228 | 229 | If you're confused, take a closer look at the provided helper theorems, and make 230 | sure you remember how to use existence and disjunction proofs. Also make sure you 231 | know how to do the proof on paper, first. 232 | 233 | For some of the trickier proofs, we've provided some of the proof structure for you. 234 | 235 | If you're feeling particularly adventurous, you are also free to use any library 236 | theorems you find in Mathlib4 237 | (https://leanprover-community.github.io/mathlib4_docs), but you may find the following 238 | theorems particularly useful. These are the only outside theorems our solutions use. 239 | 240 | One last pro tip: if you have a proof `h` in your context that you want to simplify, 241 | you can call `simp` like this: `simp at h`. 242 | 243 | -/ 244 | 245 | #check Nat.mul_div_cancel 246 | #check Nat.factorial_pos 247 | #check Nat.factorial_succ 248 | #check Nat.div_self 249 | #check Nat.mul_pos 250 | #check Nat.ne_of_lt 251 | #check Nat.div_eq_zero 252 | #check Nat.eq_zero_or_pos 253 | #check Nat.factorial_lt 254 | #check Nat.sub_sub_self 255 | #check Nat.sub_eq_zero_iff_le 256 | #check Nat.sub_add_comm 257 | 258 | #check Ne.symm 259 | #check lt_mul_of_lt_of_one_le 260 | #check lt_of_not_le 261 | #check mul_comm 262 | 263 | 264 | -- We do this one for you as a demo: 265 | @[simp] lemma Choose_one_eq_self (n : ℕ) : 266 | (n C 1) = n := by 267 | have hnzp : n = 0 ∨ ∃ n0, n = n0 + 1 := eq_zero_or_exists_succ n 268 | eliminate hnzp with h h 269 | -- again, the previous two lines could have been written as `eliminate eq_zero_or_exists_succ n with h h`. 270 | -- we'll use the condensed form from now on. 271 | {simp [h]} 272 | { 273 | eliminate h with n0 h 274 | have : n ≠ 0 := by linarith 275 | simp [Choose] 276 | rewrite [h, Nat.factorial_succ] 277 | simp 278 | rewrite [Nat.mul_div_cancel] 279 | {linarith} 280 | {apply Nat.factorial_pos} 281 | } 282 | 283 | 284 | -- Remember that `simp` can expand an expression `(n C k)` to the full factorial 285 | -- fraction as long as it knows `n ≠ 0` and `k ≠ 0`, i.e., as long as you have 286 | -- proofs of `n ≠ 0` and `k ≠ 0` in your context. 287 | 288 | /- 2 points -/ 289 | lemma Choose_self_eq_one (n : ℕ) : 290 | (n C n) = 1 := by 291 | sorry 292 | 293 | 294 | 295 | attribute [simp] Choose_self_eq_one 296 | 297 | -- You don't have to do this one on your own -- we're going to talk through it 298 | -- together in lecture on Friday, April 14! 299 | -- But you can use it in your proof below. 300 | lemma binomial_symmetry_helper (n k : ℕ) : 301 | n ≠ 0 → k ≠ 0 → (n - k) ≠ 0 → (n C k) = (n C n-k) := by 302 | intros hn hk hnk 303 | simp [Choose] 304 | rewrite [mul_comm] 305 | rewrite [Nat.sub_sub_self] 306 | { reflexivity } 307 | { have : k < n 308 | { apply lt_of_not_le 309 | rewrite [← Nat.sub_eq_zero_iff_le] 310 | assumption } 311 | { linarith } 312 | } 313 | 314 | -- This next proof is basically about hunting down and proving all the possible 315 | -- edge cases, and fiddling with arithmetic until you're in a position to call 316 | -- `binomial_symmetry_helper`. 317 | -- We've structured the proof for you; you should fill in *all* the `sorry`s. 318 | 319 | -- Hint: you'll probably have to do a few case splits on whether a natural number 320 | -- is 0 or a successor, like `eliminate (eq_zero_or_ne_zero n) with hn hn`. 321 | 322 | -- Also, consider: why would this be false without the `k ≤ n` hypothesis? Where 323 | -- would the proof go wrong? 324 | 325 | /- 2 points -/ 326 | theorem binomial_symmetry (n k : ℕ) : 327 | k ≤ n → (n C k) = (n C n-k) := by 328 | intro hk_le_n 329 | eliminate (eq_zero_or_ne_zero n) with hn hn 330 | { 331 | sorry 332 | } 333 | { 334 | eliminate (eq_zero_or_ne_zero k) with hk hk 335 | {sorry} 336 | { 337 | eliminate (eq_zero_or_ne_zero (n - k)) with hnk hnk 338 | { 339 | sorry 340 | } 341 | { 342 | apply binomial_symmetry_helper 343 | {sorry} 344 | {sorry} 345 | {sorry} 346 | } 347 | } 348 | } 349 | 350 | 351 | 352 | /- 353 | 354 | # Alternative definition 355 | 356 | Now we're going to explore an alternative way of defining the combination function, 357 | using Pascal's Rule. This way, Pascal's Rule is true *by definition*, which is pretty 358 | handy. This is the definition used by Mathlib! 359 | 360 | -/ 361 | 362 | @[reducible] def AltChoose (n k : ℕ) := 363 | match n, k with 364 | | _, 0 => 1 365 | | 0, _ => 0 366 | | n+1, k+1 => AltChoose n (k+1) + AltChoose n k 367 | 368 | /- 369 | 370 | Unlike our previous definition, `Choose`, this one is recursive. As a result, ALL 371 | of our proofs in this section will need to be by induction. It's also a much 372 | simpler definition in some ways - it doesn't use the factorial or division functions, 373 | both of which can be very tricky to work with. So these proofs have the potential 374 | to be much shorter. 375 | 376 | However, it's not as immediately obvious that this definition does indeed calculate 377 | the combination function we're aiming for. Also, proving its equivalence to our 378 | previous `Choose` definition is surprisingly difficult, and beyond the scope of 379 | this class. You should still be able to convince yourself that they're the same, 380 | though (hint: induct on `n` and apply Pascal's Rule). 381 | 382 | These kinds of trade-offs are inevitable in math, and it's good to be aware of them 383 | when writing your own definitions. 384 | 385 | 386 | 387 | 388 | 389 | # Problem 2 390 | 391 | Now let's try proving the same theorems with this alternative definition. For your 392 | convenience, we've again introduced some notation to avoid writing out `AltChoose` 393 | every time. It just uses an `A` instead of a `C`. 394 | 395 | Remember, you'll probably want to use induction! 396 | 397 | -/ 398 | 399 | 400 | notation:max "(" n " A " k ")" => AltChoose n k 401 | 402 | @[simp] lemma AltChoose_zero_eq_one : ∀ n, (n A 0) = 1 := by simp [AltChoose] 403 | 404 | /- 2 points -/ 405 | lemma AltChoose_one_eq_self : 406 | ∀ n, (n A 1) = n := by 407 | sorry 408 | 409 | 410 | attribute [simp] AltChoose_one_eq_self 411 | 412 | -- In this problem, you might find the following lemma useful again: 413 | #check exists_succ_of_gt 414 | 415 | 416 | /- 417 | 418 | In this problem, it can be tricky to think about what the induction predicate is! 419 | We're doing induction on `n`, with the predicate `∀ k, n < k → (n A k) = 0`. 420 | Take a look at what our induction hypothesis `ih` looks like in the inductive step. 421 | 422 | -/ 423 | 424 | /- 2 points -/ 425 | lemma AltChoose_zero_of_k_gt_n : 426 | ∀ n k, n < k → (n A k) = 0 := by 427 | basic_induction 428 | { 429 | sorry 430 | } 431 | { 432 | intro n 433 | intro ih 434 | sorry 435 | } 436 | 437 | 438 | 439 | attribute [simp] AltChoose_zero_of_k_gt_n 440 | 441 | 442 | 443 | 444 | 445 | /- 2 points -/ 446 | lemma AltChoose_self_eq_one : 447 | ∀ n, (n A n) = 1 := by 448 | sorry 449 | 450 | 451 | 452 | 453 | attribute [simp] AltChoose_self_eq_one 454 | 455 | /- 456 | 457 | # Bonus! 458 | 459 | The AltChoose version of the proof of binomial symmetry is surprisingly difficult! 460 | 461 | It can be done using only the tools you've been taught, but it's LONG, and it requires 462 | a lot of fiddling around to get the arithmetic right. 463 | 464 | Try it out if you're feeling confident... 👁️👄👁️ 465 | (It's hard to set up our Lean autograder to give bonus points. 466 | Shoot Rob an email if you finish this problem!) 467 | 468 | Hint: you may find the theorems below quite useful. 469 | 470 | -/ 471 | 472 | #check nat_eq_or_ne 473 | #check Nat.lt_of_le_of_ne 474 | #check eq_zero_or_exists_succ 475 | #check Nat.sub_add_comm 476 | #check add_comm 477 | #check congr_arg 478 | #check congr_arg₂ 479 | #check Nat.sub_add_eq 480 | 481 | /- 0 points -/ 482 | theorem alt_binomial_symmetry : 483 | ∀ n k, k ≤ n → (n A k) = (n A n-k) := by 484 | sorry -------------------------------------------------------------------------------- /BrownCs22/Homework/Hw7.lean: -------------------------------------------------------------------------------- 1 | import BrownCs22.Library.Tactics 2 | import BrownCs22.Library.Defs 3 | import Mathlib.Tactic.LibrarySearch 4 | 5 | set_option pp.notation true 6 | set_option linter.unusedVariables false 7 | namespace Hw7 8 | 9 | 10 | /- 11 | 12 | # Application and Induction 13 | 14 | In this homework, we're going to introduce you to two of the most powerful and 15 | commonly-used tactics in Lean, `simp` and `apply`. 16 | 17 | (We're all simps for `simp`!) 18 | 19 | We'll also be giving you a gentle introduction to induction proofs in Lean, using 20 | the tactic `basic_induction`, which inducts over all natural numbers, starting from 0. 21 | 22 | This homework is quite long, but don't panic! Most of it is reading and worked 23 | examples. There's only a few actual problems for you to do. 24 | 25 | 26 | 27 | 28 | 29 | ## Simplification 30 | 31 | The `simp` tactic, like `dsimp`, `simp`lifies expressions. However, `dsimp` only 32 | knows about *definitions*. For instance, if we have a function defined like this... 33 | 34 | -/ 35 | 36 | def f (n : ℕ) : ℕ := 37 | match n with 38 | | 0 => 0 39 | | x + 1 => (f x) + 1 40 | 41 | /- 42 | 43 | ... then `dsimp` could simplify `f 0` to `0`, and simplify `f 1` to `1`, and so on. 44 | 45 | (Note that `f` is defined *recursively*: `f 2 = (f 1) + 1 = ((f 0) + 1) + 1 = 0 + 1 + 1 = 2`. 46 | 47 | But what about `f x`? Without knowing the actual value of `x`, `dsimp` doesn't know 48 | how to expand the definition of `f`. It's not smart enough to know that `f x` is always 49 | equivalent to `x`, it just knows how to mechanically unwrap definitions. 50 | 51 | `simp`, on the other hand, knows a LOT of ways to simplify expressions, including 52 | everything `dsimp` can do. However, unlike `dsimp`, it doesn't automatically figure 53 | out simplification rules from definitions. Someone has to manually write the code 54 | that tells `simp` how to simplify things. We haven't done that here, so it won't 55 | help us with the `f` example. 56 | 57 | The good news is that `simp` comes built-in with a lot of useful simplification 58 | rules that can make proof-writing much easier. Whenever you find yourself looking 59 | at your proof goal and thinking, "ugh, OBVIously this is true, why can't Lean just 60 | accept it already???", and `dsimp` isn't doing anything, you may want to try `simp`. 61 | This is especially convenient for when you have annoying bits of arithmetic trapped 62 | deep inside an expression where `numbers` and `linarith` can't do anything about them. 63 | 64 | Here's a demo: 65 | 66 | -/ 67 | 68 | example : Nat.gcd (0 + a) a = a := by 69 | -- numbers -- nope doesn't work 70 | -- linarith -- nope 71 | -- dsimp -- still nothing 72 | simp -- hooray, we're done! 73 | 74 | 75 | /- 76 | 77 | ## The `apply` tactic 78 | 79 | So far, we've given you two main ways of using existing theorems in a new proof: 80 | `eliminate` and `have`. And they're not going away! They're very useful, and the 81 | best tool for the job in many, many proofs. But, both techniques have their drawbacks. 82 | 83 | `eliminate` will only let you use an existing theorem if it is either: 84 | 1. a conjunction (an AND statement, like `(2 + 2 = 4) ∧ (3 = 3)`), 85 | 2. a disjunction (an OR statement, like `(2 = 3) ∨ (3 = 3)`), 86 | 3. or an existential (a statement like `∃ x, x + 1 = 2`). 87 | 88 | But many theorems do not fit into any of these boxes. 89 | 90 | `have`, on the other hand, is quite general, but sometimes awkward to use. Let's 91 | say we wanted to prove a proposition `P`, and we have an existing theorem, called 92 | `helper`, that says `A → B → P`. So, if we have proofs of `A` and `B`, then by 93 | modus ponens, we can conclude `P` from `helper`. 94 | 95 | How would we write this in Lean, using `have`? Well, it would look something like 96 | this, where the stuff in brackets is a placeholder for some actual proof: 97 | 98 | `have proof_of_A : A := [... proof of A ...]` 99 | `have proof_of_B : B := [... proof of B ...]` 100 | `have goal : P := helper proof_of_A proof_of_B` 101 | 102 | This works! But it could be much simpler. We had to give explicit names to our 103 | subproofs of `A` and `B`, even though we might not use them later. We also had 104 | to manually invoke `helper` at the end, and make sure we passed in `proof_of_A` 105 | and `proof_of_B` in the right order. Pretty easy here, but getting the order right 106 | for helper theorems with a dozen premises gets tedious! 107 | 108 | The core issue here is that, once we have `helper`, WE DON'T CARE ABOUT PROVING 109 | `P` DIRECTLY ANYMORE. We just care about proving `A` and `B` - then getting `P` 110 | from `helper` is easy. But, if we only use `have`, then Lean thinks our goal is 111 | always just `P`, which means it won't help us out in proving `A` and `B`. 112 | 113 | This is where `apply` comes in! Here's the same proof of `P`, written using `apply`: 114 | 115 | `apply helper` 116 | `[... proof of A ...]` 117 | `[... proof of B ...]` 118 | 119 | Much simpler, right? Here's what's happening. The `apply` tactic expects to be given 120 | a proof of some implication. Here, we gave it `helper`, which is a double implication, 121 | so that works. That first line, `apply helper`, tells Lean to look at the current 122 | goal (`P`) and the conclusion of the given theorem (`helper` is a proof of `A → B → P`, 123 | so the conclusion is `P`). They match, so Lean knows we'll be done as soon as we prove 124 | `A` and prove `B`. So it removes the goal of `P`, and replaces it with two new, 125 | simpler goals: `A` and then `B`. This nicely cleans up our context and breaks down 126 | the problem, and means Lean will have a better idea of what we're trying to do, 127 | so it will be better at filling in the details. 128 | 129 | Enough explaining, let's look at some examples. 130 | 131 | -/ 132 | 133 | -- This is a lot like the example we just looked at, except `A` and `B` are given to us 134 | example (A B P : Prop) : A → B → (A → B → P) → P := by 135 | intros a b habp -- We want to just have `P` as our goal, so we can use `apply` 136 | apply habp -- `habp` has conclusion `P` which matches. It creates two goals 137 | { assumption } -- Our first new goal is `A`, and we already have proof in context 138 | { assumption } -- Our second goal is to prove `B`, and done! 139 | 140 | /- 141 | 142 | Now let's try a more "real" example. Our `helper` theorem this time will be the 143 | beautifully-named `Nat.mul_lt_mul_of_pos_left`, which comes from Lean's library. It says 144 | that if `a` is less than `b`, then `ka` is less than `kb` as long as `k` isn't 0. 145 | You can `#check Nat.mul_lt_mul_of_pos_left` or hover over it in the example below 146 | to see its formal statement. 147 | 148 | We could just use `numbers` here, but I'm using `apply` for the sake of demonstration. 149 | Unfortunately, it's hard to think of simple examples for which no one's made a powerful 150 | tactic that can do everything for us :'( 151 | 152 | -/ 153 | 154 | example : 1337 * 1003 < 1337 * 2005 := by 155 | -- Lean sees that the conclusion `k * n < k * m` matches our goal `1337 * 1003 < 1337 * 2005`, 156 | -- and very nicely infers the correct values for `k`, `n`, and `m`. 157 | apply Nat.mul_lt_mul_of_pos_left 158 | { numbers } -- proving 1003 < 2005 159 | { numbers } -- proving 1337 > 0 160 | 161 | /- 162 | 163 | You may be confused since `Nat.mul_lt_mul_of_pos_left` doesn't really look 164 | like an implication - see the #check statement below. It LOOKS like a universal 165 | quantification. What's going on there? 166 | 167 | -/ 168 | 169 | #check Nat.mul_lt_mul_of_pos_left 170 | 171 | /- 172 | 173 | Well, we probably won't go into this much in class, but to Lean, implications and 174 | universal quantifications are basically the same thing. But you don't need to worry 175 | about this much. The one thing that might get confusing is that, if Lean can't automatically 176 | figure out how to assign the quantified variable, it'll ask you to provide it. If 177 | your Infoview shows a goal of `ℕ` or `ℚ`, or something else that doesn't look like 178 | a proposition, that's what's happening. It's asking you to provide a value for the 179 | quantified variable. You may also see symbols like `?a` elsewhere in the context - 180 | those are placeholders waiting for that missing value. 181 | 182 | If you need to provide a missing value to continue the proof, but it's not showing 183 | up as your first goal, you can use the `case` tactic to focus that goal. The value 184 | you provide will then fill in for the weird `?a` variables. To demonstrate this, 185 | we're going to prove that `1 < 3` in a very silly and roundabout way. Try placing 186 | your cursor at each step in the proof to see how the context and goals are changing. 187 | 188 | -/ 189 | 190 | example : 1 < 3 := by 191 | -- This tells Lean we're going to prove 1 < 3 via transitivity. In other words, 192 | -- we're going to pick a value `x`, show 1 < `x`, and show `x` < 3. We're going 193 | -- to choose `x = 2` for this (really it's our only option). Lean will pick a 194 | -- weird-looking variable name instead of `x`, but I'll stuck with just `x` 195 | -- cuz it looks nicer. 196 | apply lt_trans 197 | 198 | -- Uh oh! Lean next wants us to prove 1 < `x`, but we haven't even chosen a value 199 | -- for `x` yet! How are we supposed to prove anything about it? Notice that the 200 | -- third goal is just `ℕ` - that's where we actually tell Lean what we want `x` 201 | -- to be. 202 | 203 | -- To fix this, let's move that third goal to the front. See how it's labeled 204 | -- `case b` in the Infoview? To move it up, we use the `case` tactic, like this: 205 | case b => 206 | -- We can use the `exact` tactic to provide missing values. 207 | -- It doesn't have to be a literal value, though - if we had `x : ℕ` and 208 | -- `y : ℕ` in our context, we could have used `x + y` here. 209 | exact 2 210 | 211 | 212 | -- Now our remaining goals are filled out, and we can finish the proof. 213 | { numbers } 214 | { numbers } 215 | 216 | /- 217 | 218 | So far, all our examples have applied theorems that are double-implications, but 219 | that was just to match our initial explanation. You can `apply` single implications, 220 | or quintuple implications, or whatever. 221 | 222 | In this next example, notice that we use both `apply` and `have`! This was a case 223 | where using both tactics made for a much easier proof. However, it's still much more 224 | complicated than the previous examples, so we recommend stepping through it line-by-line, 225 | and observing how the Infoview changes with each step. 226 | 227 | -/ 228 | 229 | example (a : ℕ) : (a ∣ a+1) → a = 1 := by 230 | intro h 231 | 232 | -- I found this theorem in the library! If we can prove `a` divides 1, we'll be done. 233 | apply Nat.eq_one_of_dvd_one 234 | 235 | -- I use `have` here because I want to rewrite `1` as `(a + 1) - a`, so that the 236 | -- conclusion of `Nat.dvd_sub` will match properly. That will require calling 237 | -- `rewrite` with a named proof of `1 = (a + 1) - a`. 238 | have rewrite_helper : 1 = (a + 1) - a 239 | { -- Since I didn't supply a proof of rewrite_helper, Lean makes it a goal for us 240 | -- to fill in. 241 | 242 | -- To change the goal from `1 = (a + 1) - a` to `(a + 1) - a = 1`. This lets me 243 | -- apply the next theorem, `Nat.add_sub_cancel_left`. 244 | apply Eq.symm 245 | 246 | -- This finishes the proof of rewrite_helper 247 | apply Nat.add_sub_cancel_left } 248 | 249 | -- Finally I can use `Nat.dvd_sub`. Now we just have to prove the premises, which 250 | -- are: `a ≤ a + 1`, `a ∣ a + 1`, and `a ∣ a`. We already have the tools to do all three! 251 | rewrite [rewrite_helper] 252 | apply Nat.dvd_sub 253 | 254 | { linarith } 255 | { assumption } 256 | 257 | -- The "divides" operator is reflexive, and Lean knows that. 258 | { reflexivity } 259 | 260 | 261 | 262 | /- 263 | 264 | ## Problems 1 and 2 265 | 266 | Now it's your turn! For each problem below, we've noted some theorems from 267 | Mathlib that we recommend `apply`ing. You'll then have to prove the resulting 268 | subgoals. 269 | 270 | Try putting your cursor after the #check statements to see what each theorem does. 271 | 272 | -/ 273 | 274 | -- Note: in Mathlib theorems, `lt` stands for Less Than, and `le` stands for 275 | -- Less than or Equal to. Learning the abbreviations makes theorem names just 276 | -- a *little* bit easier to read. 277 | #check lt_of_lt_of_le 278 | #check Nat.add_lt_add_left 279 | #check Nat.add_le_add_right 280 | 281 | -- Hint: show a + c < a + d, and a + d ≤ b + d 282 | -- Hint: use lt_of_lt_of_le, Nat.add_lt_add_left, and Nat.add_le_add_right 283 | -- Hint: you may need the `case` tactic - 284 | 285 | /- 2 points -/ 286 | lemma problem_1 (a b c d : ℕ) : 287 | a ≤ b → 288 | c < d → 289 | a + c < b + d := by 290 | 291 | sorry 292 | 293 | #check Nat.mul_le_mul 294 | #check Nat.le_of_lt 295 | 296 | -- Hint: use `problem_1`, `Nat.mul_le_mul`, and `Nat.le_of_lt` 297 | 298 | /- 2 points -/ 299 | lemma problem_2 (a b c d e f : ℕ) : 300 | a < b → 301 | c < d → 302 | e < f → 303 | a * c + e < b * d + f := by 304 | 305 | sorry 306 | 307 | 308 | 309 | /- 310 | 311 | ## Induction 312 | 313 | If you've gotten comfortable with the induction problems presented in class and in 314 | homework, then the way Lean deals with induction should feel fairly natural. Well, 315 | as natural as Lean ever feels. 316 | 317 | 318 | With that out of the way, let's look at `basic_induction`. To demonstrate, we're 319 | going to look at one of the first induction problems we saw in class - the formula 320 | for triangular numbers. 321 | 322 | Remember, the `n`th triangular number is `1 + 2 + 3 + ... + n` 323 | 324 | And we proved in class that this is equal to `(n * (n + 1)) / 2` 325 | 326 | First, we need to define them the first way, below. 327 | Note that this is again a recursive definition! 328 | 329 | -/ 330 | 331 | def tri_nums (n : ℕ) := 332 | match n with 333 | | 0 => 0 334 | | n + 1 => (tri_nums n) + (n + 1) 335 | 336 | /- 337 | 338 | Now let's prove that the closed-form formula is equivalent! To simplify things, 339 | we're actually going to prove that `2 * (tri_nums n) = n * (n + 1)`. This just 340 | gets rid of the division by 2. 341 | 342 | Finally we get to see `basic_induction` in action. This tactic can be applied 343 | whenever our proof goal is a universal quantification over natural numbers, so 344 | it has to look like `∀ (n : ℕ), P(n)` for some predicate `P`. 345 | 346 | To prove the goal, `basic_induction` gives us two new goals. First, we'll need 347 | to prove `P(0)`. Then, we'll need to prove that `∀ (n : ℕ), P(n) → P(n + 1)`. 348 | This is just like the induction problems you've already seen! 349 | 350 | -/ 351 | 352 | lemma tri_formula : ∀ (n : ℕ), 2 * (tri_nums n) = n * (n + 1) := by 353 | basic_induction 354 | { 355 | -- Prove that the zeroth triangular number is zero. Well, that's true just from 356 | -- how we defined tri_nums, so let's use dsimp 357 | dsimp [tri_nums] 358 | } 359 | { 360 | -- Now for the inductive case! 361 | intros n h 362 | dsimp [tri_nums] 363 | simp 364 | rewrite [Nat.left_distrib, h] 365 | linarith 366 | } 367 | 368 | 369 | 370 | /- 371 | 372 | ## Problem 3 373 | 374 | Now you try! This first problem is a lot like the last one, except we only use odd 375 | numbers: 376 | 377 | 1 = 1 378 | 1 + 3 = 4 379 | 1 + 3 + 5 = 9 380 | 1 + 3 + 5 + 7 = 16 381 | 382 | We're asking you to prove that this pattern always gives you the `n`th square number. 383 | 384 | -/ 385 | 386 | def recursive_square (n : ℕ) := 387 | match n with 388 | | 0 => 0 389 | | n + 1 => recursive_square n + (2 * n + 1) 390 | 391 | /- 2 points -/ 392 | lemma square_formula : ∀ (n : ℕ), recursive_square n = n * n := by 393 | sorry 394 | 395 | 396 | /- 397 | 398 | ## Parity and Problem 4 399 | 400 | For more induction practice, we're going to build up some foundational theorems 401 | about parity (whether a number is even or odd). 402 | 403 | -/ 404 | 405 | -- A natural number is even iff it is a multiple of 2 406 | def MyEven (n: ℕ) : Prop := ∃ (k : ℕ), 2*k = n 407 | 408 | -- A natural number is odd iff it is one more than a multiple of 2 409 | def MyOdd (n: ℕ) : Prop := ∃ (k : ℕ), 2*k+1 = n 410 | 411 | /- 412 | 413 | Please note that the above definitions, by themselves, leave very much to be proved! 414 | 415 | For instance, it will not be trivial to prove that all numbers are either even or 416 | odd, and not both. 417 | 418 | We're also giving you these theorems, which you are free to use however you want: 419 | 420 | -/ 421 | 422 | lemma zero_is_even : MyEven 0 := by 423 | dsimp [MyEven] 424 | existsi 0 425 | numbers 426 | 427 | lemma one_is_odd : MyOdd 1 := by 428 | dsimp [MyOdd] 429 | existsi 0 430 | numbers 431 | 432 | lemma zero_isnt_odd : ¬(MyOdd 0) := by 433 | dsimp [MyOdd] 434 | intro h 435 | eliminate h with x h 436 | linarith 437 | 438 | lemma one_isnt_even : ¬(MyEven 1) := by 439 | dsimp [MyEven] 440 | intro h 441 | eliminate h with x h 442 | rewrite [@Nat.mul_eq_one_iff] at h 443 | linarith 444 | 445 | lemma even_after_odd (n : ℕ) : MyOdd n → MyEven (n+1) := by 446 | dsimp [MyEven, MyOdd] 447 | intro h 448 | eliminate h with x h 449 | existsi x + 1 450 | linarith 451 | 452 | lemma odd_after_even (n : ℕ) : MyEven n → MyOdd (n+1) := by 453 | dsimp [MyEven, MyOdd] 454 | intro h 455 | eliminate h with x h 456 | existsi x 457 | linarith 458 | 459 | lemma even_before_odd : ∀ (n : ℕ), MyOdd (n+1) → MyEven n := by 460 | intro n 461 | dsimp [MyEven, MyOdd] 462 | intro h 463 | eliminate h with x hx 464 | existsi x 465 | linarith 466 | 467 | lemma odd_before_even : ∀ (n : ℕ), MyEven (n+1) → MyOdd n := by 468 | intro n 469 | dsimp [MyEven, MyOdd] 470 | intro h 471 | eliminate h with x hx 472 | cases x with 473 | | zero => {apply False.elim; simp at hx; linarith} 474 | | succ x => {existsi x; linarith} 475 | 476 | #check not_and 477 | #check not_or 478 | 479 | /- 480 | 481 | # Problem 4 482 | 483 | You may find `apply` to be very useful here! 484 | 485 | Hint: at some point in `not_both`, you might have a negation hypothesis 486 | `hn : ¬ P` and a goal of `False`. 487 | Remember that we can prove `False` using the `contradiction` tactic 488 | if we have hypotheses `hn : ¬ P` and `h : P`. 489 | So a good move here would be `have h : P`! 490 | Then you'll need to go ahead and prove `P`. 491 | 492 | -/ 493 | 494 | /- 2 points -/ 495 | theorem even_or_odd : ∀ (n : ℕ), MyEven n ∨ MyOdd n := by 496 | sorry 497 | 498 | /- 2 points -/ 499 | theorem not_both : ∀ (n : ℕ), ¬(MyEven n ∧ MyOdd n) := by 500 | sorry 501 | 502 | 503 | 504 | 505 | 506 | end Hw7 --------------------------------------------------------------------------------