├── .github └── workflows │ ├── ci.yml │ └── update-lean.yml ├── .gitignore ├── LICENSE ├── README.md ├── _posts └── 2022-01-09-Graph-Coloring-Basics.md ├── git-hooks └── pre-commit ├── lean └── 2022-01-09-Graph-Coloring-Basics.lean ├── lean2md.py └── leanpkg.toml /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | release: 7 | types: [published] 8 | schedule: 9 | # Daily at 6:11 10 | - cron: '11 6 * * *' 11 | 12 | 13 | jobs: 14 | ci: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build via lean 20 | uses: leanprover-contrib/lean-build-action@master 21 | -------------------------------------------------------------------------------- /.github/workflows/update-lean.yml: -------------------------------------------------------------------------------- 1 | name: Autoupdate Lean 2 | 3 | on: 4 | schedule: 5 | - cron: '0 2 * * *' 6 | 7 | jobs: 8 | upgrade_lean: 9 | runs-on: ubuntu-latest 10 | name: Bump Lean and dependency versions 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Upgrade lean and dependencies 14 | uses: leanprover-contrib/lean-upgrade-action@master 15 | with: 16 | repo: ${{ github.repository }} 17 | access-token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _target/ 2 | leanpkg.path 3 | *.olean 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Lean Community Authors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lean for the Inept Mathematician 2 | 3 | This repository contains source files for a number of articles or posts aimed at explaining bite-sized mathematical concepts with the help of the [Lean Theorem Prover](https://leanprover.github.io/) and [mathlib](https://github.com/leanprover-community/mathlib/), its library of mathematics. 4 | 5 | The authors of these posts are themselves not mathematicians (so corrections or pointers are most welcome!). 6 | We aim to learn ourselves, and in the process, perhaps explain a thing or two about mathlib to any who find it useful. 7 | 8 | *The name, self-deprecatingly (for those of us who choose to apply it to ourselves), is an homage to the wonderful Lean for the Curious Mathematician event held annually by the Lean community for the last few years.* 9 | 10 | ## Contributing 11 | 12 | You can place Lean code in the `lean` directory and run `$ python lean2md.py lean _posts` to turn your code into a post. Lean docstrings will turn into regular text and everything else will be encapsulated inside code blocks. 13 | 14 | This repository is also a Lean project that has `mathlib` as dependency. You 15 | can get cached `olean` files by doing `leanproject get-mathlib-cache`. 16 | 17 | # Configure Git hooks 18 | This repository contains a pre-commit hook to automatically convert Lean source files to Markdown files on commit. If you want to use this hook, run 19 | ``` 20 | $ git config core.hooksPath ./git-hooks 21 | $ chmod +x ./git-hooks/pre-commit 22 | ``` 23 | 24 | Otherwise, you can run `python3 lean2md.py lean _posts` to convert Lean files to Markdown. 25 | -------------------------------------------------------------------------------- /_posts/2022-01-09-Graph-Coloring-Basics.md: -------------------------------------------------------------------------------- 1 | layout: post 2 | title: "Graph Coloring Basics" 3 | date: 2022-01-09 4 | categories: graph 5 | 6 | Prelude: 7 | 8 | * [Graph homomorphism](https://en.wikipedia.org/wiki/Graph_homomorphism) 9 | * [NP-hardness](https://en.wikipedia.org/wiki/NP-hardness) 10 | 11 | # Graph Coloring Basics 12 | 13 | In Graph Theory, finding a proper coloring of a graph consists of attributing "colors" 14 | to its vertices in a way that adjacent vertices have different colors. 15 | 16 | [In Lean](https://github.com/leanprover-community/mathlib/pull/10287), a coloring (short 17 | for "proper coloring") is represented as a homomorphism from the graph being colored to 18 | the complete graph of available colors. Let's seen an example of how this works. 19 | 20 | Suppose we have a graph `G` that we want to color with three colors: `c₁`, `c₂` and `c₃`. 21 | 22 | ``` 23 | G Complete graph of colors 24 | D-------A c₁ 25 | | | \ / \ 26 | | | C c₂---c₃ 27 | | | / 28 | E-------B 29 | ``` 30 | 31 | The following mapping can be seen as a coloring of `G`: 32 | 33 | ``` 34 | A → c₁ 35 | B → c₂ 36 | C → c₃ 37 | D → c₂ 38 | E → c₃ 39 | ``` 40 | 41 | Now note that this mapping is in fact a homomorphism. And you may wonder, why does such 42 | a homomorphism represent a coloring? 43 | 44 | Informally, consider two adjacent vertices in G. Since our mapping is a homomorphism, 45 | those vertices must be mapped to adjacent vertices in the complete graph of colors. 46 | And such adjacent vertices represent different colors. 47 | 48 | You can check Mathlib's formalization of the above [here](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.coloring.valid). 49 | 50 | Let's play with the API a bit. 51 | 52 | ```lean 53 | import combinatorics.simple_graph.coloring 54 | import tactic.derive_fintype 55 | ``` 56 | 57 | Deriving `decidable_eq` and `fintype` will allow us to prove some results 58 | with `dec_trivial`. 59 | 60 | ```lean 61 | @[derive [decidable_eq, fintype]] 62 | inductive verts : Type 63 | | A | B | C | D | E 64 | 65 | open verts 66 | 67 | def edges : list (verts × verts) := 68 | [ (A, B), (B, C), (A, C), (A, D), (D, E), (B, E) ] 69 | ``` 70 | 71 | This function tells whether two vertices are adjacent. 72 | 73 | ```lean 74 | def adj (v w : verts) : bool := edges.mem (v, w) || edges.mem (w, v) 75 | ``` 76 | 77 | The `simple_graph` API requires a `Prop` for adjacency. So we need to coerce 78 | our `bool` function to a `Prop` one: 79 | 80 | ```lean 81 | def adj_prop (v w : verts) : Prop := adj v w 82 | ``` 83 | 84 | The following proofs are required to construct a `simple_graph`: 85 | 86 | ```lean 87 | lemma adj_symmetric : ∀ (x y : verts), adj x y → adj y x := dec_trivial 88 | lemma adj_loopless : ∀ (x : verts), ¬adj x x := dec_trivial 89 | 90 | def G : simple_graph verts := ⟨adj_prop, adj_symmetric, adj_loopless⟩ 91 | ``` 92 | 93 | Now we can color `G`. 94 | 95 | ```lean 96 | @[derive decidable_eq] 97 | inductive colors : Type 98 | | c₁ | c₂ | c₃ 99 | 100 | open colors 101 | 102 | def color_fn : verts → colors 103 | | A := c₁ 104 | | B := c₂ 105 | | C := c₃ 106 | | D := c₂ 107 | | E := c₃ 108 | ``` 109 | 110 | We must prove our coloring is proper, which `dec_trivial` is able to do easily for 111 | our finite graph. 112 | 113 | ```lean 114 | lemma color_fn_is_valid : ∀ (v w : verts), adj v w → color_fn v ≠ color_fn w := 115 | dec_trivial 116 | 117 | def my_coloring : G.coloring colors := ⟨color_fn, color_fn_is_valid⟩ 118 | ``` 119 | 120 | Notice that `color_fn_is_valid` wouldn't have been accepted by Lean if `color_fn` 121 | weren't in fact a proper coloring function. 122 | 123 | ## The real challenge 124 | 125 | Coloring graphs would be a trivial task if we could use as many colors as we wanted. 126 | Just color each vertex with a different color! This is done in [Mathlib](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.self_coloring) 127 | by coloring vertices using themselves as colors. 128 | 129 | However, the interesting question is: what is the **minimum** number of colors required 130 | to color a certain graph? This quantity is called *chromatic number* and finding it 131 | for any given graph is an NP-Hard problem, although Mathlib does provide proofs for some 132 | known results: 133 | 134 | * If `G` is a subgraph of `G'`, the chromatic number of `G'` is at least the 135 | chromatic number of `G` 136 | 137 | [`simple_graph.chromatic_number_le_of_le_colorable`](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.chromatic_number_le_of_le_colorable) 138 | 139 | * The chromatic number of a complete graph is the number of vertices it has: 140 | 141 | [`simple_graph.chromatic_number_complete_graph`](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.chromatic_number_complete_graph) 142 | 143 | * The chromatic number of a complete bipartite graph is 2: 144 | 145 | [`simple_graph.complete_bipartite_graph.chromatic_number`](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.complete_bipartite_graph.chromatic_number) 146 | 147 | ## Conclusion 148 | 149 | Graph coloring is an important problem in Computer Science, with 150 | [applications](https://en.wikipedia.org/wiki/Graph_coloring#Applications) in different 151 | areas of study. This is an ongoing field of research and finding lower/upper bounds for 152 | the chromatic number, as well as potent coloring heuristics for graphs of certain shapes, 153 | is of immense value for the academic community and for the industry in general. 154 | -------------------------------------------------------------------------------- /git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 lean2md.py lean _posts 3 | git add _posts 4 | -------------------------------------------------------------------------------- /lean/2022-01-09-Graph-Coloring-Basics.lean: -------------------------------------------------------------------------------- 1 | /- 2 | layout: post 3 | title: "Graph Coloring Basics" 4 | date: 2022-01-09 5 | categories: graph 6 | 7 | Prelude: 8 | 9 | * [Graph homomorphism](https://en.wikipedia.org/wiki/Graph_homomorphism) 10 | * [NP-hardness](https://en.wikipedia.org/wiki/NP-hardness) 11 | 12 | # Graph Coloring Basics 13 | 14 | In Graph Theory, finding a proper coloring of a graph consists of attributing "colors" 15 | to its vertices in a way that adjacent vertices have different colors. 16 | 17 | [In Lean](https://github.com/leanprover-community/mathlib/pull/10287), a coloring (short 18 | for "proper coloring") is represented as a homomorphism from the graph being colored to 19 | the complete graph of available colors. Let's seen an example of how this works. 20 | 21 | Suppose we have a graph `G` that we want to color with three colors: `c₁`, `c₂` and `c₃`. 22 | 23 | ``` 24 | G Complete graph of colors 25 | D-------A c₁ 26 | | | \ / \ 27 | | | C c₂---c₃ 28 | | | / 29 | E-------B 30 | ``` 31 | 32 | The following mapping can be seen as a coloring of `G`: 33 | 34 | ``` 35 | A → c₁ 36 | B → c₂ 37 | C → c₃ 38 | D → c₂ 39 | E → c₃ 40 | ``` 41 | 42 | Now note that this mapping is in fact a homomorphism. And you may wonder, why does such 43 | a homomorphism represent a coloring? 44 | 45 | Informally, consider two adjacent vertices in G. Since our mapping is a homomorphism, 46 | those vertices must be mapped to adjacent vertices in the complete graph of colors. 47 | And such adjacent vertices represent different colors. 48 | 49 | You can check Mathlib's formalization of the above [here](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.coloring.valid). 50 | 51 | Let's play with the API a bit. 52 | -/ 53 | 54 | import combinatorics.simple_graph.coloring 55 | import tactic.derive_fintype 56 | 57 | /- 58 | Deriving `decidable_eq` and `fintype` will allow us to prove some results 59 | with `dec_trivial`. 60 | -/ 61 | 62 | @[derive [decidable_eq, fintype]] 63 | inductive verts : Type 64 | | A | B | C | D | E 65 | 66 | open verts 67 | 68 | def edges : list (verts × verts) := 69 | [ (A, B), (B, C), (A, C), (A, D), (D, E), (B, E) ] 70 | 71 | /- This function tells whether two vertices are adjacent. -/ 72 | 73 | def adj (v w : verts) : bool := edges.mem (v, w) || edges.mem (w, v) 74 | 75 | /- 76 | The `simple_graph` API requires a `Prop` for adjacency. So we need to coerce 77 | our `bool` function to a `Prop` one: 78 | -/ 79 | 80 | def adj_prop (v w : verts) : Prop := adj v w 81 | 82 | /- The following proofs are required to construct a `simple_graph`: -/ 83 | 84 | lemma adj_symmetric : ∀ (x y : verts), adj x y → adj y x := dec_trivial 85 | lemma adj_loopless : ∀ (x : verts), ¬adj x x := dec_trivial 86 | 87 | def G : simple_graph verts := ⟨adj_prop, adj_symmetric, adj_loopless⟩ 88 | 89 | /- Now we can color `G`.-/ 90 | 91 | @[derive decidable_eq] 92 | inductive colors : Type 93 | | c₁ | c₂ | c₃ 94 | 95 | open colors 96 | 97 | def color_fn : verts → colors 98 | | A := c₁ 99 | | B := c₂ 100 | | C := c₃ 101 | | D := c₂ 102 | | E := c₃ 103 | 104 | /- 105 | We must prove our coloring is proper, which `dec_trivial` is able to do easily for 106 | our finite graph. 107 | -/ 108 | 109 | lemma color_fn_is_valid : ∀ (v w : verts), adj v w → color_fn v ≠ color_fn w := 110 | dec_trivial 111 | 112 | def my_coloring : G.coloring colors := ⟨color_fn, color_fn_is_valid⟩ 113 | 114 | /- 115 | Notice that `color_fn_is_valid` wouldn't have been accepted by Lean if `color_fn` 116 | weren't in fact a proper coloring function. 117 | 118 | ## The real challenge 119 | 120 | Coloring graphs would be a trivial task if we could use as many colors as we wanted. 121 | Just color each vertex with a different color! This is done in [Mathlib](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.self_coloring) 122 | by coloring vertices using themselves as colors. 123 | 124 | However, the interesting question is: what is the **minimum** number of colors required 125 | to color a certain graph? This quantity is called *chromatic number* and finding it 126 | for any given graph is an NP-Hard problem, although Mathlib does provide proofs for some 127 | known results: 128 | 129 | * If `G` is a subgraph of `G'`, the chromatic number of `G'` is at least the 130 | chromatic number of `G` 131 | 132 | [`simple_graph.chromatic_number_le_of_le_colorable`](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.chromatic_number_le_of_le_colorable) 133 | 134 | * The chromatic number of a complete graph is the number of vertices it has: 135 | 136 | [`simple_graph.chromatic_number_complete_graph`](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.chromatic_number_complete_graph) 137 | 138 | * The chromatic number of a complete bipartite graph is 2: 139 | 140 | [`simple_graph.complete_bipartite_graph.chromatic_number`](https://leanprover-community.github.io/mathlib_docs/combinatorics/simple_graph/coloring.html#simple_graph.complete_bipartite_graph.chromatic_number) 141 | 142 | ## Conclusion 143 | 144 | Graph coloring is an important problem in Computer Science, with 145 | [applications](https://en.wikipedia.org/wiki/Graph_coloring#Applications) in different 146 | areas of study. This is an ongoing field of research and finding lower/upper bounds for 147 | the chromatic number, as well as potent coloring heuristics for graphs of certain shapes, 148 | is of immense value for the academic community and for the industry in general. 149 | -/ 150 | -------------------------------------------------------------------------------- /lean2md.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | def build_blocks(lines): 5 | reading_lean_code = True 6 | blocks = [] 7 | content = "" 8 | for i, line_n in enumerate(lines): 9 | line = line_n.split("\n")[0] 10 | if line.startswith("/-"): 11 | if not reading_lean_code: 12 | raise RuntimeError( 13 | "Nested lean commentary sections not allowed in:\n" + 14 | " line {}: {}".format(str(i + 1), line) 15 | ) 16 | blocks.append({"content" : content.strip(), "is_code" : True}) 17 | reading_lean_code = False 18 | content = line.split("/-!" if line.startswith("/-!") else "/-")[-1] 19 | if line.endswith("-/"): 20 | reading_lean_code = True 21 | content = content.split("-/")[0] 22 | blocks.append({"content" : content.strip(), "is_code" : False}) 23 | content = "" 24 | elif line.endswith("-/"): 25 | content += line.split("-/")[0] 26 | reading_lean_code = True 27 | blocks.append({"content" : content.strip(), "is_code" : False}) 28 | content = "" 29 | else: 30 | content += line + "\n" 31 | if content != "": 32 | blocks.append({"content" : content.strip(), "is_code" : True}) 33 | return blocks 34 | 35 | def merge_blocks(blocks): 36 | res = "" 37 | for block in blocks: 38 | if block["content"] == "": 39 | continue 40 | if block["is_code"]: 41 | res += "```lean\n" + block["content"] + "\n```\n\n" 42 | else: 43 | res += block["content"] + "\n\n" 44 | return res.strip() + "\n" 45 | 46 | 47 | def lean_file_2_md(filename): 48 | lines = open(filename, "r").readlines() 49 | blocks = build_blocks(lines) 50 | return merge_blocks(blocks) 51 | 52 | if __name__ == "__main__": 53 | if len(sys.argv) != 3: 54 | raise RuntimeError( 55 | "Usage: python {} ".format(sys.argv[0]) 56 | ) 57 | src = sys.argv[1] 58 | if not src.endswith("/"): 59 | src += "/" 60 | tgt = sys.argv[2] 61 | if not tgt.endswith("/"): 62 | tgt += "/" 63 | for filename in os.listdir(src): 64 | if not filename.endswith(".lean"): 65 | continue 66 | tgt_filename = tgt + filename.split("/")[-1].split(".lean")[0] + ".md" 67 | f = open(tgt_filename, "w") 68 | f.write(lean_file_2_md(src + filename)) 69 | f.close() 70 | -------------------------------------------------------------------------------- /leanpkg.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lean-for-the-inept-mathematician" 3 | version = "0.1.0" 4 | lean_version = "leanprover-community/lean:3.46.0" 5 | path = "lean" 6 | 7 | [dependencies] 8 | mathlib = {git = "https://github.com/leanprover-community/mathlib", rev = "5ef6426143338092cd126421361e6cb22be01913"} 9 | --------------------------------------------------------------------------------