Zulip Chat Archive
Stream: maths
Topic: graph theory
Jakob von Raumer (Jul 07 2021 at 08:06):
Have there been any useful attempts to make a good library on graph theory? Any students of yours, @Kevin Buzzard ? There's a student here who's considering formalising some planar graph theory as part of their bachelor's thesis.
Johan Commelin (Jul 07 2021 at 08:09):
@Jakob von Raumer There's a #graph theory stream!
Johan Commelin (Jul 07 2021 at 08:10):
There's been quite some activity in fact, see also https://github.com/leanprover-community/mathlib/tree/master/src/combinatorics
Kevin Buzzard (Jul 07 2021 at 08:11):
funnily enough I was thinking about this yesterday, and basically I have no idea what "good" means. It seems to me that graph theory is taught very early on to CS students, with applications to networks, algorithms etc. We don't have a graph theory course in the maths department at Imperial, and the one I sat through as an UG in Cambridge comprised a hotch-potch of unrelated results like Hall's Marriage theorem and the 5 colour theorem (and even the 7 colour theorem for maps on a torus) with no underlying story and no other courses relying on the results (mathematically, this is often the case in combinatorics-based courses, they tend to teach general principles as opposed to important theorems like the fundamental theorem of Galois theory which is used everywhere). So I am probably not the best person to ask! @Bhavik Mehta what does a "good" graph theory library contain?
Jakob von Raumer (Jul 07 2021 at 08:15):
Ah, thank you! Forgot to check the list of streams :)
Jakob von Raumer (Jul 07 2021 at 08:16):
@Kevin Buzzard The graph theory course at KIT at the math dept contains, if my memory serves me right, a lot of Ramsey theory style results
Kevin Buzzard (Jul 07 2021 at 08:17):
I think we probably had one lecture on "R(3,3)=6, proof by induction that R(m,n) is finite" and that was it for Ramsey theory.
Bhavik Mehta (Jul 07 2021 at 13:33):
Kevin Buzzard said:
funnily enough I was thinking about this yesterday, and basically I have no idea what "good" means. It seems to me that graph theory is taught very early on to CS students, with applications to networks, algorithms etc. We don't have a graph theory course in the maths department at Imperial, and the one I sat through as an UG in Cambridge comprised a hotch-potch of unrelated results like Hall's Marriage theorem and the 5 colour theorem (and even the 7 colour theorem for maps on a torus) with no underlying story and no other courses relying on the results (mathematically, this is often the case in combinatorics-based courses, they tend to teach general principles as opposed to important theorems like the fundamental theorem of Galois theory which is used everywhere). So I am probably not the best person to ask! Bhavik Mehta what does a "good" graph theory library contain?
I agree it's a tricky question. The two main things I'd like to see in a "good" library are definitions (of course with API) of all the basic concepts of graphs: planarity, colouring, extremal number, diameter etc with the standard results about them eg Brooks' theorem, Hall's theorem; and a collection of tools in order to make proving new results easy, for example results like Erdos-Stone, Szemeredi's regularity lemma and a clean API for probabilistic graph theory and expander graphs.
Bhavik Mehta (Jul 07 2021 at 13:35):
Some of these are much harder than others, but I think the resulting library has the same goal as any other maths library: to provide enough definitions and results to make proving "actual" graph theory results (ie active research things) convenient.
Bhavik Mehta (Jul 07 2021 at 13:41):
As for the existing Cambridge graph theory courses, my undergrad graph theory notes are here: https://github.com/b-mehta/maths-notes/blob/master/ii/mich/graph_theory.pdf, you can basically see a summary from the contents page. The Ramsey theory covered was basically what Kevin says, plus a proof of multicolour and infinite Ramsey; but there was also a Part III (MSc-level) Ramsey theory course syllabus here which went into more detail, @David Wärn is proving some of the combinatorial results from it eg Hales-Jewett and Rado's theorem. There was also a Part III Extremal Graph Theory course which was more in-depth, there are some notes from a 2017 version of this course here: https://dec41.user.srcf.net/notes/III_M/extremal_graph_theory.pdf
Hunter Monroe (Jul 07 2021 at 14:19):
@Bhavik Mehta do you mind if I update your Ramsey Theory files to the latest mathlib and submit?
Bhavik Mehta (Jul 07 2021 at 14:22):
Feel free to make the PRs, but the main reason I haven't is that I'm not certain they're ready for PR yet, there's the whole crec business and it's pretty ugly... That said, the actual result is (I think) formulated nicely so maybe it's not hugely important that the proof is ugly
Patrick Massot (Jul 07 2021 at 14:50):
Kevin Buzzard said:
We don't have a graph theory course in the maths department at Imperial, and the one I sat through as an UG in Cambridge comprised a hotch-potch of unrelated results like Hall's Marriage theorem and the 5 colour theorem (and even the 7 colour theorem for maps on a torus) with no underlying story and no other courses relying on the results (mathematically, this is often the case in combinatorics-based courses, they tend to teach general principles as opposed to important theorems like the fundamental theorem of Galois theory which is used everywhere).
In France a standard undergrad math curriculum features no graph theory at all. This is why graph theory doesn't appear at all on our undergrad math page. Some students may still encounter graphs in some random optional first year course on recreational mathematics.
Arthur Paulino (Oct 25 2021 at 15:43):
Question: why is simple_graph
under combinatorics
? Would it fit better under data
?
Idea: data
> graph
> [simple_graph
, directed_graph
, weighted_graph
etc]
What do you think?
Dima Pasechnik (Oct 25 2021 at 16:55):
normally speaking, "graphs" of "data (science)" are rather different from graphs as combinatorial structures.
Yaël Dillies (Oct 25 2021 at 16:57):
It would make me even sadder that there's so little under combinatorics
:sad:
Scott Morrison (Oct 25 2021 at 22:08):
The data
is more of a historical hodge podge of things people didn't find better homes for at the time. :-) I think in the long term we would like to rearrange things so data
comes to mean "data structures" in the programming sense. I would encourage moving anything "mathmetical" out of data
, rather than the reverse.
Scott Morrison (Oct 25 2021 at 22:09):
Indeed, after the transition to Lean4 I think we're hoping for a separate "standard library" that comes before mathlib, and perhaps we will actually aspire to emptying the data
directory: everything about data structures per se will be in the standard library, and whatever remains can be categorised by mathematical topic in mathlib.
Arthur Paulino (Oct 25 2021 at 22:10):
@Kyle Miller thanks for the return! I think we can move on to this thread now
Arthur Paulino (Oct 25 2021 at 22:16):
Looking at how far that branch already is, I thought of the following subareas:
- shortest path theorems
- minimum cut
- maximum flow
- arriving at minimum cut and maximum flow duality
Of course, you might have a clearer idea on the next steps since you've been doing such a fine job on this branch for quite a while already. I want to try something but I'm all ears.
Kyle Miller (Oct 25 2021 at 22:17):
Regarding the location of graphs in the library -- given your topic about complexity theory, maybe you're interested in formalizing algorithms about graphs? I sort of think about the combinatorics
graphs as being like the abstract interface (in the Java sense), where they don't have a concrete implementation. The data of a simple_graph
is just a relation without any promise of computability (unless you have [decidable_rel G.adj]
). You'd probably want reasonably efficient ways to represent a simple_graph
for questions about complexity, and those might more on the data
side of things.
Arthur Paulino (Oct 25 2021 at 22:24):
Hm, I guess I'd like to follow the flow of proving theorems with definitions without having to implement the mechanics of the algorithms just yet. Where do you see the simple_graph
project moving towards?
Kyle Miller (Oct 25 2021 at 22:25):
Those all seem like interesting things to work on! Maybe for now feel free to use the walks_and_trees
branch, adding some new files.
Arthur Paulino (Oct 25 2021 at 22:28):
Alright, just wanted to know. I will try a few things locally first before requesting the permission to write on branches. I wanna make sure I can lift some weights beforehand.
Kyle Miller (Oct 25 2021 at 22:29):
For simple_graph
, one goal has been trying to implement all the basic stuff from the first few pages of Bollobas's textbook on graph theory, and trying to do so with an eye toward avoiding too much code duplication between, for example, simple graphs, multigraphs, labeled multigraphs, etc.
Kyle Miller (Oct 25 2021 at 22:36):
For flows of a simple graph, this might be a reasonable starting definition:
structure preflow {V : Type*} (G : simple_graph V) (α : Type*) [has_zero α] [has_neg α] :=
(weight : V → V → α)
(adj_of_nonzero : ∀ v w, weight v w ≠ 0 → G.adj v w)
(neg : ∀ v w, -weight v w = weight w v)
This supports having -flows for any abelian group . It's just missing conditions about what ∑ (w : V), weight v w
is.
Arthur Paulino (Oct 25 2021 at 22:40):
Hm, maybe talking about shortest paths wrt non-weighted graphs would be a closer next step?
Arthur Paulino (Oct 25 2021 at 22:41):
Not sure if such theorems are very (re)usable tho.
Kyle Miller (Oct 25 2021 at 22:48):
Maybe directed graphs would be better for things about flows, but here's a definition for flows in an undirected graph:
import combinatorics.simple_graph.basic
import algebra.big_operators.basic
import data.real.basic
open_locale big_operators
structure flow {V : Type*} [fintype V]
(G : simple_graph V) (cap : sym2 V → ℝ) (s t : V) :=
(weight : V → V → ℝ)
(adj_of_ne : ∀ v w, weight v w ≠ 0 → G.adj v w)
(neg : ∀ v w, -weight v w = weight w v)
(conserve : ∀ v, v ≠ s → v ≠ t → ∑ (w : V), weight v w = 0)
(cap_constraint : ∀ v w, abs (weight v w) ≤ cap ⟦(v, w)⟧)
Each edge has a capacity, and the flow for that edge can go in either direction so long as it's within the capacity.
This is also an option, allowing the capacity to be different for flows in each direction:
structure flow {V : Type*} [fintype V]
(G : simple_graph V) (cap : V → V → ℝ) (s t : V) :=
(weight : V → V → ℝ)
(for_edges : ∀ v w, weight v w ≠ 0 → G.adj v w)
(neg : ∀ v w, -weight v w = weight w v)
(conserve : ∀ v, v ≠ s → v ≠ t → ∑ (w : V), weight v w = 0)
(cap_constraint : ∀ v w, weight v w ≤ cap v w)
Kyle Miller (Oct 25 2021 at 22:49):
Arthur Paulino said:
Not sure if such theorems are very (re)usable tho.
If anything, you'd probably create useful supporting lemmas along the way.
Arthur Paulino (Oct 25 2021 at 22:51):
Now I understood what you meant by "avoiding too much code duplication". You're using the simple_graph
structure to extend onto other structures
Arthur Paulino (Oct 25 2021 at 22:59):
I will let you know of any updates :+1:
Kyle Miller (Oct 25 2021 at 23:29):
It might also be nice splitting up the definition into the "relative 1-cycle" part (i.e., the part having to do with Kirkhoff's law for currents where certain nodes are allowed to be current sources/sinks) and the capacity part. This can also be generalized to infinite graphs, so long as every vertex has only finitely many neighbors (i.e., it's locally finite).
/-- A relative 1-cycle with coefficients in an abelian group. -/
structure rel_cycle {V : Type*} (G : simple_graph V) [G.locally_finite] (W : set V)
(α : Type*) [add_comm_group α] :=
(weight : V → V → α)
(adj_if : ∀ v w, weight v w ≠ 0 → G.adj v w)
(neg : ∀ v w, -weight v w = weight w v)
(conserve : ∀ v, ¬v ∈ W → ∑ w in G.neighbor_finset v, weight v w = 0)
/-- A real-valued relative 1-cycle with bounded weights with only two boundary vertices. -/
structure flow {V : Type*} (G : simple_graph V) [G.locally_finite]
(cap : V → V → ℝ) (s t : V) extends rel_cycle G {s, t} ℝ :=
(cap_constraint : ∀ v w, weight v w ≤ cap v w)
Kyle Miller (Oct 25 2021 at 23:32):
1-cycles themselves form an abelian group, the rank of which is an important invariant for graphs (it's the minimum number of edges you have to remove to get a forest).
Yakov Pechersky (Oct 26 2021 at 12:08):
Also, just because git status says your branch is up to date doesn't mean you don't have changes. By that I mean unsaved changes. Edited unsaved files earlier in the import tree still cause recompilation of themselves and dependent files.
Yakov Pechersky (Oct 26 2021 at 12:08):
If they are open in your vscode and your extension settings are the default ones.
Arthur Paulino (Oct 26 2021 at 13:42):
@Kyle Miller Lean is not accepting append_support''
nor append_support'
on my machine (connectivity.lean
lines 462 to 488).
lemma append_support'' {u v w : V} (p : G.walk u v) (p' : G.walk v w) :
((p.append p').support : multiset V) = {u} + p.support.tail + p'.support.tail :=
begin
rw [append_support, ←multiset.coe_add],
congr' 1,
simp only [multiset.singleton_eq_singleton, multiset.cons_coe,
zero_add, multiset.coe_eq_coe, multiset.cons_add],
rw [←support_eq],
end
lemma append_support' [decidable_eq V] {u v w : V} (p : G.walk u v) (p' : G.walk v w) :
((p.append p').support : multiset V) = p.support + p'.support - {v} :=
begin
rw append_support,
cases p',
{ simp only [nil_support, list.tail_cons, list.append_nil],
convert_to _ = (p.support + ([v] - {v}) : multiset V),
{ erw multiset.add_sub_cancel,
simp, },
{ nth_rewrite 0 ←add_zero (p.support : multiset V),
rw add_left_cancel_iff,
simp, }, },
{ simp_rw [cons_support, list.tail_cons, ←multiset.cons_coe, ←multiset.singleton_add],
rw [add_comm, add_assoc, add_comm],
erw multiset.add_sub_cancel,
rw [←multiset.coe_add, add_comm], },
end
On the former, it's failing to apply simp
and on the later the second simp
ends up with unsolved goals. Was this under your radar?
Arthur Paulino (Oct 26 2021 at 16:12):
Okay I fixed a few things. I wanna push to a different branch before merging so you can see my modifications first because these are my first changes to mathlib files.
Arthur Paulino (Oct 26 2021 at 16:33):
https://github.com/leanprover-community/mathlib/pull/9983 A PR to merge my branch into yours.
You said that I could push commits directly to your branch if I added new files. But in this case I made changes to files that you wrote so I'm more comfortable opening a PR into your branch this time around (I don't want to mess things up).
Kyle Miller (Oct 26 2021 at 16:46):
For those kinds of changes (fixing proofs, changing names, and the like), feel free to commit straight to the branch. (And thanks for finding a way to simplify one of the proofs! I wouldn't mind if you did that for more of the proofs; many still have things like non-terminal simps.)
I haven't seen using PRs targeting non-master branches before for mathlib, and I'm not sure how it will interact with the existing PR due to my lack of experience. Will squash-and-merge effectively just add a single commit to the walks_and_trees
branch?
Yaël Dillies (Oct 26 2021 at 16:47):
Yeah, such PR are fine but pretty rare. People usually rather go through a PR-like process behind the scenes.
Arthur Paulino (Oct 26 2021 at 16:51):
Okay since you've already reviewed the changes and approved them (on this chat), I'm gonna accept the PR and merge my branch into yours
Arthur Paulino (Oct 26 2021 at 17:42):
Kyle Miller said:
I haven't seen using PRs targeting non-master branches before for mathlib, and I'm not sure how it will interact with the existing PR due to my lack of experience. Will squash-and-merge effectively just add a single commit to the
walks_and_trees
branch?
Yeah that's precisely it. It's just a way of coordinating collaboration so you know exactly what I'm changing instead of having to read it on a tight git diff
terminal interface
Arthur Paulino (Oct 26 2021 at 17:45):
But sure, I can be more straightforward and commit directly on your branch :+1:
Kyle Miller (Oct 26 2021 at 17:47):
(I don't mind reviewing your changes if you prefer PRing.)
Arthur Paulino (Oct 26 2021 at 18:13):
Interesting, I've just found out about this GitHub feature: compare
.
If you go to https://github.com/leanprover-community/mathlib/compare you can pick two branches as if you wanted to merge one into the other and the diff will be shown in a PR-styled way. Example: comparing walks_and_trees
with master
:
https://github.com/leanprover-community/mathlib/compare/master...walks_and_trees
But it doesn't allow commentaries etc, which make sense
Arthur Paulino (Oct 26 2021 at 18:14):
Kyle Miller said:
(I don't mind reviewing your changes if you prefer PRing.)
Thanks :big_smile:
Yeah I feel safer that way
Arthur Paulino (Oct 26 2021 at 22:52):
According to this definition, trees and spanning trees are the same for us, right?
/-- A *tree* is a connected acyclic graph. -/
def is_tree : Prop := G.connected ∧ G.is_acyclic
Kyle Miller (Oct 26 2021 at 23:02):
A spanning tree is a G' : subgraph G
that satisfies G'.is_spanning
and G'.coe.is_tree
(docs#simple_graph.subgraph.coe to turn the subgraph into a graph.) It's probably more convenient to use G'.spanning_coe.is_tree
, which doesn't introduce a subtype for the vertices.
Another way to formalize spanning trees, probably even more convenient, is as a G' : simple_graph V
such that G' ≤ G
and G'.is_tree
. The type simple_graph V
has a boolean algebra structure from comparing edge sets, and G' ≤ G
means G'
is a spanning subgraph of G
.
Arthur Paulino (Oct 26 2021 at 23:16):
Ah, wait, I got confused. The concept of "Spanning tree" is that we can compute the spanning tree of a connected graph
Kyle Miller (Oct 26 2021 at 23:23):
This (or some variation on it) would be nice to have:
theorem exists_spanning_tree (G : simple_graph V) (h : G.connected) :
∃ G', G' ≤ G ∧ G'.is_tree :=
sorry
Kyle Miller (Oct 26 2021 at 23:25):
This is a noncomputable version, allowing for V
to be infinite. The argument that comes to mind uses Zorn's lemma applied to acyclic graphs G' ≤ G
.
Arthur Paulino (Oct 26 2021 at 23:27):
Nice. I'm trying to figure out a way to state that the number of edges of a finite tree is equal to the number of vertices minus 1
Arthur Paulino (Oct 26 2021 at 23:28):
But I'm not even being able to write it as a lemma and have Lean accept it
Arthur Paulino (Oct 26 2021 at 23:30):
This is not the right lemma, but I'm getting an error.
lemma asd : G.is_tree → G.edge_finset.card = 1 := sorry
The error is:
failed to synthesize type class instance for
V : Type u,
G : simple_graph V,
_inst_1 : decidable_eq V,
ᾰ : G.is_tree
⊢ fintype V
Kyle Miller (Oct 26 2021 at 23:30):
That's saying you don't have the assumption that V
is finite. Add [fintype V]
as an argument to asd
.
Kyle Miller (Oct 26 2021 at 23:32):
I guess you need [decidable_eq V] [fintype V] [decidable_rel G.adj]
to use G.edge_finset
.
Kyle Miller (Oct 26 2021 at 23:34):
(This should be changed actually. G.edge_finset
should just need [fintype G.edge_set]
. Or maybe it should be removed, since all it does is make it a little easier to write G.edge_set.to_finset
.)
Arthur Paulino (Oct 26 2021 at 23:36):
[fintype V] [decidable_rel G.adj]
did the job, thanks!
Question: I see that some proofs use assumptions introduced with {...}
and others use [...]
. What's the difference?
Kyle Miller (Oct 26 2021 at 23:37):
Kyle Miller (Oct 26 2021 at 23:38):
https://leanprover.github.io/reference/expressions.html#implicit-arguments
Bryan Gin-ge Chen (Oct 26 2021 at 23:39):
Chapter 10 of #tpil is a good reference on how type classes (related to the square brackets) work in Lean 3.
Arthur Paulino (Oct 26 2021 at 23:41):
lemma asd [fintype G.edge_set]: G.is_tree → G.edge_set.to_finset.card = 1 := sorry
is accepted :+1:
Arthur Paulino (Oct 26 2021 at 23:42):
I will get back to the introduction of spanning trees once I figure this one out. Now I need to express the cardinality of V
. Let me struggle a little
Kyle Miller (Oct 26 2021 at 23:44):
Kyle Miller (Oct 26 2021 at 23:44):
Arthur Paulino (Oct 27 2021 at 01:53):
Is this too astray?
open fintype
lemma is_tree_then_card_edges_le_card_vertices
[fintype G.edge_set] [fintype V] : G.is_tree → card G.edge_set < card V :=
begin
sorry
end
lemma is_tree_then_not_card_edges_le_card_vertices_minus_one
[fintype G.edge_set] [fintype V] [nonempty V]: G.is_tree → ¬ card G.edge_set < card V - 1 :=
begin
sorry
end
/-
the strategy is to use the lemmas above and nat.eq_of_lt_succ_of_not_lt in order to
achieve equality
-/
lemma is_tree_then_card_edges_eq_card_vertices_minus_one
[fintype G.edge_set] [fintype V] [nonempty V] : G.is_tree → card G.edge_set = card V - 1 :=
begin
intro h,
have hle := is_tree_then_card_edges_le_card_vertices _ h,
have hnle := is_tree_then_not_card_edges_le_card_vertices_minus_one _ h,
sorry
end
Arthur Paulino (Oct 27 2021 at 01:55):
Maybe an induction on the number of vertices works better
Kyle Miller (Oct 27 2021 at 02:23):
It would be possible to prove it using an induction principle for trees (though I'm not sure exactly how I'd want to formulate it).
Another option is more direct. Given a tree, choose an arbitrary root . Let . For every , there is a unique path from to (see tree_path
); let denote the first edge in this path. This function is surjective (given an edge, you should be able to use is_rootward_antisymm
to show it's the image of one of its endpoints) and it is injective (same lemma or thereabouts). Since this is a bijection, we get an equality of the cardinalities of the edge set and of .
Arthur Paulino (Oct 27 2021 at 02:28):
Will take a closer look at your approach tomorrow. I'm feeling a bit fried after some hours trying to translate my thoughts into Lean.
I've started this draft PR, also including some adjustments you requested on the previous PR that I couldn't see in time:
https://github.com/leanprover-community/mathlib/pull/9993
Kyle Miller (Oct 27 2021 at 04:16):
The coercions here are a bit hairy, but maybe you can try filling in the sorries (assuming I set things up right):
def next_edge : ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), G.incidence_set v
| v w h walk.nil := (h rfl).elim
| v w h (@walk.cons _ _ _ u _ hvw _) := ⟨⟦(v, u)⟧, hvw, sym2.mk_has_mem _ _⟩
lemma is_tree_then_card_edges_eq_card_vertices_minus_one
[fintype G.edge_set] [fintype V] [nonempty V] (h : G.is_tree) :
card G.edge_set = card V - 1 :=
begin
have root := classical.arbitrary V,
rw ←set.card_ne_eq root,
let f : {v | v ≠ root} → G.edge_set,
{ intro v,
let a := next_edge (v : V) root v.property (G.tree_path h v root : G.walk v root),
-- convert the term of incidence_set to an edge_set; there's probably a nicer way
exact ⟨_, a.property.1⟩, },
have fprop : ∀ (v : V) (hv : v ≠ root), ↑(f ⟨v, hv⟩) ∈ G.incidence_set v,
{ intros v hv,
dsimp [f],
generalize : next_edge _ _ _ _ = e,
exact e.property, },
have finj : function.injective f,
{ sorry, },
have fsurj : function.surjective f,
{ sorry, },
exact (card_of_bijective ⟨finj, fsurj⟩).symm,
end
(Small warning: I'm not sure how much more code is needed to fill them in.)
Arthur Paulino (Oct 27 2021 at 11:38):
It's not accepting v
as an input of next_edge
:
type mismatch at application
next_edge ↑v
term
↑v
has type
V : Type u
but is expected to have type
simple_graph ?m_1 : Type ?
Johan Commelin (Oct 27 2021 at 11:40):
Do you need next_edge _ v
? With an extra underscore?
Johan Commelin (Oct 27 2021 at 11:40):
Maybe simple_graph _
shouldn't be an explicit argument of next_edge
?
Arthur Paulino (Oct 27 2021 at 11:44):
The extra underscore before v
worked, but I don't understand what you meant by simple_graph _
being an argument of next_edge
Kevin Buzzard (Oct 27 2021 at 11:44):
@Arthur Paulino if you read the error message you can see it says "you know where you put v? I was expecting you to put a simple graph", and if you hover over next_edge
you can see all the inputs it expects and in what order, so you can confirm that indeed the first input to that function is supposed to be a simple graph
Kevin Buzzard (Oct 27 2021 at 11:46):
Do you understand about () and {} inputs to functions? (Edit -- and [] inputs) That's what Johan is talking about
Johan Commelin (Oct 27 2021 at 11:46):
@Arthur Paulino I guess you have variables (foobar : simple_graph V)
somewhere above the definition of next_edge
. I don't know much about the graph theory library, but maybe that should be [simple_graph V]
.
Arthur Paulino (Oct 27 2021 at 11:49):
I'm gonna do some reading on that subject
Kevin Buzzard (Oct 27 2021 at 11:49):
The _ means "Lean please work out yourself what this input should be"
Kevin Buzzard (Oct 27 2021 at 11:49):
But a more cunning choice of brackets will mean that you don't even need to put the _
Kevin Buzzard (Oct 27 2021 at 11:52):
Inputs in () brackets are supplied by the user, unless the user gives lean _ which puts the ball back into its court. Inputs in {} brackets are supplied by lean's unification system so the user skips them, and inputs in [] brackets are supplied by lean's type class inference system so the user skips them too.
Kevin Buzzard (Oct 27 2021 at 11:53):
Right now your error was giving lean a vertex when it wanted a simple graph, the suggested fix was to give it _ but another fix would be to change the type of bracket
Arthur Paulino (Oct 27 2021 at 12:03):
Right now your error was giving lean a vertex when it wanted a simple graph
If that's the error, then I think it's not just about the brackets. There's a logical error because we want V
to be the vertices of a simple_graph G
. So elements of V
, such as v
, shouldn't be of type simple_graph
Kevin Buzzard (Oct 27 2021 at 12:56):
You can read the error, and it is what it is. I am not suggesting that v should have type simple_graph! I am however suggesting that Lean wanted something of type simple_graph where you put a v, and this is of your own making (because of your choice of brackets).
Kevin Buzzard (Oct 27 2021 at 12:57):
If you post a #mwe with the error I can try and explain my point more clearly (apart from the fact that I'm about to go to a meeting)
Arthur Paulino (Oct 27 2021 at 13:09):
Yeah I copied/pasted Kyle's code and now I'm trying to catch up with his line of thought :sweat_smile:. I'm still crawling in Lean so I don't think I can spot a MWE that manifests this error.
But you're not the first one that mentions the possibility of improving the way that the assumptions are denoted (implicitly/explicitly) so that's definitely something to take into consideration as an improvement.
Kevin Buzzard (Oct 27 2021 at 13:45):
I'm asking if you could simply paste your fully (almost) working file with the error, so I don't have to go back over the entire thread and cut and paste all the parts together.
Arthur Paulino (Oct 27 2021 at 13:51):
Let me try
Arthur Paulino (Oct 27 2021 at 13:55):
import combinatorics.simple_graph.basic
variables {V : Type u} (G : simple_graph V)
/-- A walk is a sequence of adjacent vertices. For vertices `u v : V`,
the type `walk u v` consists of all walks starting at `u` and ending at `v`.
We say that a walk *visits* the vertices it contains. The set of vertices a
walk visits is `simple_graph.walk.support`. -/
@[derive decidable_eq]
inductive walk : V → V → Type u
| nil {u : V} : walk u u
| cons {u v w: V} (h : G.adj u v) (p : walk v w) : walk u w
attribute [refl] walk.nil
def next_edge : ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), G.incidence_set v
| v w h walk.nil := (h rfl).elim
| v w h (@walk.cons _ _ _ u _ hvw _) := ⟨⟦(v, u)⟧, hvw, sym2.mk_has_mem _ _⟩
lemma is_tree.card_edges_eq_card_vertices_sub_one'
[fintype G.edge_set] [fintype V] [nonempty V] (h : G.is_tree) :
card G.edge_set = card V - 1 :=
begin
have root := classical.arbitrary V,
rw ←set.card_ne_eq root,
let f : {v | v ≠ root} → G.edge_set,
{ intro v,
let a := next_edge (v : V) root v.property (G.tree_path h v root : G.walk v root),
-- convert the term of incidence_set to an edge_set; there's probably a nicer way
exact ⟨_, a.property.1⟩, },
have fprop : ∀ (v : V) (hv : v ≠ root), ↑(f ⟨v, hv⟩) ∈ G.incidence_set v,
{ intros v hv,
dsimp [f],
generalize : next_edge _ _ _ _ = e,
exact e.property, },
have finj : function.injective f,
{ sorry, },
have fsurj : function.surjective f,
{ sorry, },
exact (card_of_bijective ⟨finj, fsurj⟩).symm,
end
Kevin Buzzard (Oct 27 2021 at 13:57):
This file doesn't work for me.
Kevin Buzzard (Oct 27 2021 at 13:57):
What branch of mathlib are you on? And there are universe issues.
Arthur Paulino (Oct 27 2021 at 13:59):
The branch is called more_on_trees
. I've just pushed a commit. The file is src/combinatorics/simple_graph/connectivity.lean
Arthur Paulino (Oct 27 2021 at 13:59):
The lemma starts at line 1251
Kevin Buzzard (Oct 27 2021 at 14:01):
There are errors on that branch for me, in files imported by the file you're working on. This can cause unexpected issues.
Kevin Buzzard (Oct 27 2021 at 14:02):
But despite these issues I think I've seen enough to be able to say something coherent.
Kevin Buzzard (Oct 27 2021 at 14:06):
Unfortunately I can't get next_edge
compiling either on your branch or on master.
Kevin Buzzard (Oct 27 2021 at 14:08):
But the definition mentions G
and G
is a variable which depends on V
, so the actual type of next_edge
will be something like next_edge : \forall {V : Type u} (G : simple_graph V), ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), G.incidence_set
Arthur Paulino (Oct 27 2021 at 14:08):
Yeah Kyle introduced the definition of next_edge
just today, here on Zulip
Kevin Buzzard (Oct 27 2021 at 14:09):
And the reason that V is in {} brackets and G is in () brackets is because of the variables {V : Type u} (G : simple_graph V)
line
Kevin Buzzard (Oct 27 2021 at 14:09):
The brackets for the next_edge
variables correspond to the brackets used in the variables
command earlier.
Kevin Buzzard (Oct 27 2021 at 14:10):
So the first user-provided input for next_edge
needs to be a term of type simple_graph V
Kevin Buzzard (Oct 27 2021 at 14:10):
and then the second and third inputs are two terms of type V etc etc
Kevin Buzzard (Oct 27 2021 at 14:12):
And so the error you get is correct, because of the choice of brackets in the variables
line.
Kevin Buzzard (Oct 27 2021 at 14:14):
Johan suggested making them []
but it seems that simple_graph
is a structure not a class, which means that the designers have left open the possibility that you might want to consider more than one structure of a simple graph on a fixed vertex type V
. As a result the type class inference system does not know about simple_graph
so []
brackets won't work.
Kevin Buzzard (Oct 27 2021 at 14:19):
The other possibility is that you change (G : simple_graph V)
into {G : simple_graph V}
and in this case you'll be asking the unification system to fill in the variable for you. This could well work, because now my guess for what next_edge
will want to eat is first two elements v,w of V, then a proof that v!=w, and finally a term p
of type G.walk v w
, and once Lean sees this term it will be able to look at its type and guess what G
was, as the term G
is mentioned in the type of p
, and this is exactly the sort of thing which the unification system does. So I suspect that changing (G : simple_graph V)
to {G : simple_graph V}
on the variables line (or, if this variables line is on line 10 of the code and you're looking at line 1000, writing a new line variable {G}
just above the definition of next_edge
) will be another way of fixing the error you were seeing (other than adding in the _
).
Arthur Paulino (Oct 27 2021 at 14:33):
It worked :D
I'm learning the syntax slowly. For instance, it's not clear to me why the first parameter of next_edge
is a simple_graph
.
When I read
def next_edge : ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), G.incidence_set v
| v w h walk.nil := (h rfl).elim
| v w h (@walk.cons _ _ _ u _ hvw _) := ⟨⟦(v, u)⟧, hvw, sym2.mk_has_mem _ _⟩
just by checking the matches I see four inputs (like v w h walk.nil
)
Kevin Buzzard (Oct 27 2021 at 14:33):
Yes but you also see V and G, right? Where are they coming from?
Kevin Buzzard (Oct 27 2021 at 14:33):
That's what variables
do for you -- they are automatically added as inputs to any function which mentions them by name.
Kevin Buzzard (Oct 27 2021 at 14:34):
They're not there when the function is defined, but when you use the function you have to supply them
Kevin Buzzard (Oct 27 2021 at 14:35):
The |
magic is supplying them, but if you do #check @next_edge
just after the definition, or hover over it in a place where it's used in a theorem, you'll see the extra inputs.
Arthur Paulino (Oct 27 2021 at 14:37):
Will Lean4 work like this too?
Kevin Buzzard (Oct 27 2021 at 14:38):
Yup
Arthur Paulino (Oct 27 2021 at 14:40):
Okay then I better get used to it. I think it's a bit hard on the reader but I see how it speeds up writing
Kyle Miller (Oct 27 2021 at 16:32):
Sorry for the confusion I've seemed to cause with the code I posted. I had written it at the end of src/combinatorics/simple_graph/connectivity.lean
in the walks_and_trees
branch right before the final end simple_graph
, so the full context was variables {V : Type u} {G : simple_graph V} [decidable_eq V]
in the simple_graph
namespace.
Kyle Miller (Oct 27 2021 at 16:38):
It looks like errors in imports are from someone helpfully merging master to bring the branch up to date, but this introduced some small errors. (@Arthur Paulino if you want to fix them, in subgraph.lean
look for the couple cases of sym :=
and replace them with symm :=
. I think that's it.)
Arthur Paulino (Oct 27 2021 at 16:57):
Nice. I had replaced some occurrences of sym
by symm
but I seem to have overlooked two of them
Anupam Nayak (Oct 27 2021 at 17:33):
Declaring variables
actually makes it easier for the reader too, if used properly. If you're working on something where you have 10 theorems, all about some 3 or 4 variables, then having to read the same arguments in each theorem is cumbersome. Separating out the common variables makes it clear to the reader that the next few theorems are all about these variables.
Even in maths we do this a lot when we say "let X, Y be topological spaces, f a continuous function from X to Y, yada yada, theorem 1... theorem n"
Yaël Dillies (Oct 27 2021 at 17:37):
That someone being me :exhausted:
Arthur Paulino (Oct 27 2021 at 19:00):
@Kyle Miller I was also trying to make this definition a bit clearer:
def next_edge : ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), G.incidence_set v
| v w h walk.nil := (h rfl).elim
| v w h (@walk.cons _ _ _ u _ hvw _) := ⟨⟦(v, u)⟧, hvw, sym2.mk_has_mem _ _⟩
Do you think it's worth the effort?
Arthur Paulino (Oct 27 2021 at 19:01):
What does mem
stand for?
Kyle Miller (Oct 27 2021 at 19:06):
next_edge
would be better if G
were an explicit argument, but it doesn't make the definition clearer. I'm not sure it could be made simpler.
By doubling the amount of code, you can split off the proof of incidence from the edge the function outputs:
def next_edge (G : simple_graph V) : ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), sym2 V
| v w h walk.nil := (h rfl).elim
| v w h (@walk.cons _ _ _ u _ hvw _) := ⟦(v, u)⟧
lemma next_edge_mem_incidence_set (v w : V) (h : v ≠ w) (p : G.walk v w) :
G.next_edge v w h p ∈ G.incidence_set v :=
begin
cases p,
{ exact (h rfl).elim },
{ exact ⟨by assumption, sym2.mk_has_mem _ _⟩, }
end
Kyle Miller (Oct 27 2021 at 19:07):
mem
is used in a name for ∈
Arthur Paulino (Oct 27 2021 at 20:05):
this is the current diff: https://github.com/leanprover-community/mathlib/pull/9993/files
Arthur Paulino (Oct 27 2021 at 21:31):
If I have e: ↥(G.edge_set)
, how to get the vertices that compose e
?
Yaël Dillies (Oct 27 2021 at 21:38):
e.1
gives you an edge. From there it's a good exercise to define what you want. There are several ways to do it.
Kevin Buzzard (Oct 27 2021 at 21:39):
I guess e.val
will have type sym2 V
so look at the API for sym2
by right clicking on it
Kevin Buzzard (Oct 27 2021 at 21:41):
oh hmm it's a quotient. You could use quotient.out
I guess
Yaël Dillies (Oct 27 2021 at 21:42):
If you don't find what you want, don't worry but do tell us. The API for sym2
is currently crap and we have plans to improve it.
Arthur Paulino (Oct 27 2021 at 21:48):
I think it's better to explain the strategy I'd apply if I were to prove it in a undergrad course, then I can use some help on translating my intent to Lean.
This is my current state:
V: Type u
G: simple_graph V
_inst_1: decidable_eq V
_inst_2: fintype ↥(G.edge_set)
_inst_3: fintype V
_inst_4: nonempty V
h: G.is_tree
root: V
f: ↥{v : V | v ≠ root} → ↥(G.edge_set) := λ (v : ↥{v : V | v ≠ root}), ⟨↑(next_edge ↑v root _ ↑(G.tree_path h ↑v root)), _⟩
fprop: ∀ (v : V) (hv : v ≠ root), ↑(f ⟨v, hv⟩) ∈ G.incidence_set v
finj: function.injective f
e: ↥(G.edge_set)
⊢ ∃ (a : ↥{v : V | v ≠ root}), f a = e
That is, I need to prove that given an edge e
of a tree G
and a root vertex root
, I can retrieve a
as the furthest vertex that composes e
Arthur Paulino (Oct 27 2021 at 21:51):
f
is a function that returns the last edge on a path from root
to a given vertex
Arthur Paulino (Oct 27 2021 at 21:57):
My intuition is to take e1
and e2
as the vertices that compose e
and apply f
to them. Either f(e1)
or f(e2)
will result in e
Arthur Paulino (Oct 27 2021 at 22:00):
The proof wouldn't be complete because I'd need to prove that last sentence. My struggle is to translate my thoughts into Lean
Kyle Miller (Oct 27 2021 at 22:01):
Yaël Dillies said:
The API for
sym2
is currently crap and we have plans to improve it.
Where's this sentiment coming from? It certainly has everything necessary for this (though of course things can always be improved.)
Yaël Dillies (Oct 27 2021 at 22:01):
Doing SRL :stuck_out_tongue_closed_eyes:
Yaël Dillies (Oct 27 2021 at 22:02):
Bhavik identified two stupid lemmas which made our life much easier. But we still ended up not using sym2
anymore (well, except for the intrinsically simple_graph
part).
Kyle Miller (Oct 27 2021 at 22:07):
@Arthur Paulino One way to do this is this incantation: cases e with e he, refine quotient.ind (λ p, _) e, cases p with v w,
Kyle Miller (Oct 27 2021 at 22:07):
Oh, wait, that doesn't generalize a variable correctly.
Kyle Miller (Oct 27 2021 at 22:08):
Maybe cases e with e he, induction e using quotient.ind, cases e with v w,
Kyle Miller (Oct 27 2021 at 22:10):
I could have sworn there was a sym2.ind
that we added, but I guess it was just sym2.lift
Yaël Dillies (Oct 27 2021 at 22:11):
We have sym2.ind
in branch#szemeredi
Arthur Paulino (Oct 27 2021 at 22:24):
Now I need to take the furthest one (between v
and w
) from root
Arthur Paulino (Oct 27 2021 at 22:30):
I'm gonna spend a few hours here: https://leanprover.github.io/reference/tactics.html
Bryan Gin-ge Chen (Oct 27 2021 at 22:49):
Some info on that page might be out of date. I suspect you'd be better off reading the corresponding chapter of TPiL and referring to our tactic docs as needed.
Kyle Miller (Oct 27 2021 at 22:51):
Here's sort of what I was imagining for surjectivity:
def next_edge (G : simple_graph V) : ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), G.incidence_set v
| v w h walk.nil := (h rfl).elim
| v w h (@walk.cons _ _ _ u _ hvw _) := ⟨⟦(v, u)⟧, hvw, sym2.mk_has_mem _ _⟩
lemma nonempty_path_not_loop {v : V} {e : sym2 V} (p : G.path v v)
(h : e ∈ walk.edges (p : G.walk v v)) : false :=
begin
cases p with p hp,
cases p,
{ exact h, },
{ cases hp,
simpa using hp_support_nodup, },
end
lemma eq_next_edge_if_mem_path {u v w : V}
(hne : u ≠ v) (hinc : ⟦(u, w)⟧ ∈ G.incidence_set u)
(p : G.path u v) (h : ⟦(u, w)⟧ ∈ (p : G.walk u v).edges) :
G.next_edge u v hne p = ⟨⟦(u, w)⟧, hinc⟩ :=
begin
cases p with p hp,
cases p,
{ exact (hne rfl).elim },
{ cases hp,
simp at hp_support_nodup,
simp only [next_edge, subtype.mk_eq_mk, subtype.coe_mk],
congr,
simp only [list.mem_cons_iff, subtype.coe_mk, simple_graph.walk.cons_edges, sym2.eq_iff] at h,
cases h,
{ obtain (⟨_,rfl⟩|⟨rfl,rfl⟩) := h; refl, },
{ have h := walk.mem_support_of_mem_edges _ h,
exact (hp_support_nodup.1 h).elim, }, },
end
lemma is_tree_then_card_edges_eq_card_vertices_minus_one
[fintype G.edge_set] [fintype V] [nonempty V] (h : G.is_tree) :
card G.edge_set = card V - 1 :=
begin
have root := classical.arbitrary V,
rw ←set.card_ne_eq root,
let f : {v | v ≠ root} → G.edge_set := λ v,
⟨G.next_edge v root v.property (G.tree_path h v root),
G.incidence_set_subset _ (subtype.mem _)⟩,
have fprop : ∀ (v : V) (hv : v ≠ root), ↑(f ⟨v, hv⟩) ∈ G.incidence_set v,
{ intros v hv,
dsimp only [f, subtype.coe_mk],
apply subtype.mem, },
have finj : function.injective f,
{ sorry, },
have fsurj : function.surjective f,
{ intro e,
cases e with e he,
induction e using quotient.ind,
cases e with v w,
cases is_rootward_or_reverse h root he with hr hr,
{ use v,
rintro rfl, -- goal is equivalently v ≠ root; substitute v for root
dsimp only [is_rootward] at hr,
exact nonempty_path_not_loop _ hr.2,
cases hr,
simp only [f],
erw eq_next_edge_if_mem_path _ ⟨he, _⟩ _ hr_right; simp, },
{ sorry, -- do the same thing again. use wlog?
}, },
exact (card_of_bijective ⟨finj, fsurj⟩).symm,
end
Kyle Miller (Oct 27 2021 at 22:52):
With the right lemmas, this could be a lot cleaner. I'm also not sure if going through is_rootward_or_reverse
and using the definition of is_rootward
is the best way, but at least it works.
Arthur Paulino (Oct 27 2021 at 22:58):
eq_next_edge_if_mem_path
is not being accepted
Arthur Paulino (Oct 27 2021 at 22:58):
invalid field notation, function 'simple_graph.next_edge' does not have explicit argument with type (simple_graph ...)
Kyle Miller (Oct 27 2021 at 22:58):
Note that I changed the definition of next_edge
to include G
as an explicit argument
Arthur Paulino (Oct 27 2021 at 22:59):
Oh, ok
Now it failed to instantiate goal with v
on the use
tactic
Arthur Paulino (Oct 27 2021 at 23:00):
brb
Arthur Paulino (Oct 28 2021 at 00:36):
Alright, had to do some tweaks, but I was able to finish up surjection:
def next_edge (G : simple_graph V) : ∀ (v w : V) (h : v ≠ w) (p : G.walk v w), G.incidence_set v
| v w h walk.nil := (h rfl).elim
| v w h (@walk.cons _ _ _ u _ hvw _) := ⟨⟦(v, u)⟧, hvw, sym2.mk_has_mem _ _⟩
lemma nonempty_path_not_loop {v : V} {e : sym2 V} (p : G.path v v)
(h : e ∈ walk.edges (p : G.walk v v)) : false :=
begin
cases p with p hp,
cases p,
{ exact h, },
{ cases hp,
simpa using hp_support_nodup, },
end
lemma eq_next_edge_if_mem_path {u v w : V}
(hne : u ≠ v) (hinc : ⟦(u, w)⟧ ∈ G.incidence_set u)
(p : G.path u v) (h : ⟦(u, w)⟧ ∈ (p : G.walk u v).edges) :
G.next_edge u v hne p = ⟨⟦(u, w)⟧, hinc⟩ :=
begin
cases p with p hp,
cases p,
{ exact (hne rfl).elim },
{ cases hp,
simp at hp_support_nodup,
simp only [next_edge, subtype.mk_eq_mk, subtype.coe_mk],
congr,
simp only [list.mem_cons_iff, subtype.coe_mk, simple_graph.walk.cons_edges, sym2.eq_iff] at h,
cases h,
{ obtain (⟨_,rfl⟩|⟨rfl,rfl⟩) := h; refl, },
{ have h := walk.mem_support_of_mem_edges _ h,
exact (hp_support_nodup.1 h).elim, }, },
end
lemma is_tree.card_edges_eq_card_vertices_sub_one
[fintype G.edge_set] [fintype V] [nonempty V] (h : G.is_tree) :
card G.edge_set = card V - 1 :=
begin
have root := classical.arbitrary V,
rw ←set.card_ne_eq root,
let f : {v | v ≠ root} → G.edge_set := λ v,
⟨G.next_edge (v : V) root v.property (G.tree_path h v root : G.walk v root),
G.incidence_set_subset _ (subtype.mem _)⟩,
have fprop : ∀ (v : V) (hv : v ≠ root), ↑(f ⟨v, hv⟩) ∈ G.incidence_set v,
{ intros v hv,
dsimp only [f, subtype.coe_mk],
apply subtype.mem, },
have finj : function.injective f,
{ intros v₁ v₂,
sorry, },
have fsurj : function.surjective f,
{ intro e,
cases e with e he,
induction e using quotient.ind,
cases e with e₁ e₂,
cases is_rootward_or_reverse h root he with hr hr,
{ use e₁,
rintro rfl,
dsimp only [is_rootward] at hr,
exact nonempty_path_not_loop _ hr.2,
cases hr,
have key := eq_next_edge_if_mem_path _ _ _ hr_right,
{ simp only [f],
erw key,
simp [sym2.eq_swap]},
{ simpa [sym2.eq_swap]}, },
{ use e₂,
rintro rfl,
dsimp only [is_rootward] at hr,
exact nonempty_path_not_loop _ hr.2,
cases hr,
have key := eq_next_edge_if_mem_path _ _ _ hr_right,
{ simp only [f],
erw key,
simp [sym2.eq_swap], },
{ simpa [he]}, }, },
exact (card_of_bijective ⟨finj, fsurj⟩).symm,
end
Kyle Miller (Oct 28 2021 at 00:41):
Nice. By the way, I modified my comment shortly after posting it with some minor proof simplifications in the surjection proof
Kyle Miller (Oct 28 2021 at 00:44):
Any reason you changed it to cases e with e₁ e₂
? This splits e
into two vertices, so naming-wise either v w
or v₁ v₂
seem potentially better.
Arthur Paulino (Oct 28 2021 at 00:46):
hm, I wanted to use something different than v
. Maybe w
suits better
Arthur Paulino (Oct 28 2021 at 00:46):
or u₁ u₂
?
Arthur Paulino (Oct 28 2021 at 00:51):
I'm gonna try to use your optimized solution to prove it for the other vertex
Kyle Miller (Oct 28 2021 at 00:56):
There's also wlog
since the two cases are basically the same except for permuting the vertices, but it gives a very slow proof:
have fsurj : function.surjective f,
{ intro e,
cases e with e he,
induction e using quotient.ind,
cases e with u₁ u₂,
wlog := is_rootward_or_reverse h root he using [u₁ u₂],
{ use u₁,
rintro rfl,
dsimp only [is_rootward] at case,
exact nonempty_path_not_loop _ case.2,
cases case,
simp only [f],
erw eq_next_edge_if_mem_path _ ⟨he, _⟩ _ case_right; simp, },
obtain ⟨p, hp⟩ := this (G.symm he),
use p,
simp [hp, sym2.eq_swap], },
Arthur Paulino (Oct 28 2021 at 00:57):
Slow in terms of processing? Like it's more time consuming?
Arthur Paulino (Oct 28 2021 at 01:05):
have fsurj : function.surjective f,
{ intro e,
cases e with e he,
induction e using quotient.ind,
cases e with u₁ u₂,
cases is_rootward_or_reverse h root he with hr hr,
{ use u₁,
rintro rfl,
dsimp only [is_rootward] at hr,
exact nonempty_path_not_loop _ hr.2,
cases hr,
simp only [f],
erw eq_next_edge_if_mem_path _ ⟨he, _⟩ _ hr_right; simp [he]},
{ use u₂,
rintro rfl,
dsimp only [is_rootward] at hr,
exact nonempty_path_not_loop _ hr.2,
cases hr,
simp only [f],
erw eq_next_edge_if_mem_path _ ⟨_ , _⟩ _ hr_right; simp [he, sym2.eq_swap], }, },
Arthur Paulino (Oct 28 2021 at 01:06):
okay I'm gonna push this version to my branch
Arthur Paulino (Oct 28 2021 at 12:06):
@Kyle Miller I was thinking yesterday about the function f
you defined inside the proof and I realized that f
being (or not) surjective and injective is closely related with the definition of trees. That is, if f
is not surjective then there's a pair of vertices that are not reachable from the root. And if if
is not injective, then there is a cycle in G
. Do you think this approach could be clearer?
For the injection, at least, I'd want to try a proof by contradiction by finding a cycle. What do you think?
Arthur Paulino (Oct 28 2021 at 17:12):
Branch updated!
https://github.com/leanprover-community/mathlib/pull/9993
Please let me know what you think :rocket:
Kyle Miller (Oct 28 2021 at 17:29):
That approach would definitely work (for non-injectivity, it would be nice if there were a way to construct a cycle from f
somewhat directly, by iteratively applying it to the opposite vertex across the output edge, but that would take some more work to develop).
Something I forgot to mention is that is_rootward_or_reverse
and is_rootward_antisymm
do these kinds of cycle argument already, and I figured it might save some work to use them. However, it would be a good exercise to not use them, for both you and for the library (to help see what's missing).
Arthur Paulino (Oct 28 2021 at 17:47):
Yeah I'm trying as hard as I can to translate the logic inside my mind into Lean. The proof by cycle construction seems to be going okay, unless I've set myself for some very hard intermediate proof that I can't see right now. From your perspective, do you see a trap like that in my code? I mean, is any of those sorry
s that I inserted super hard for my current level?
Arthur Paulino (Oct 28 2021 at 17:58):
Okay, 1 sorry
down. 1 sorry
left
Kyle Miller (Oct 28 2021 at 18:19):
What you're doing seems doable (if perhaps all a bit of a trial by fire :smile: but you seem to be fine with that!).
I wanted to check my suggestion to use is_rootward_antisymm
actually panned out, so I went ahead and proved injectivity with it. It'd be better if you kept going with your approach, so don't let this spoiler-tagged-code stop you.
Here's a utility lemma that may or may not be helpful to you:
lemma next_edge_mem_edges (G : simple_graph V) (v w : V) (h : v ≠ w) (p : G.walk v w) :
(G.next_edge v w h p : sym2 V) ∈ p.edges :=
begin
cases p with p hp,
{ exact (h rfl).elim },
{ simp only [next_edge, list.mem_cons_iff, walk.cons_edges, subtype.coe_mk],
left,
refl,},
end
Kyle Miller (Oct 28 2021 at 18:22):
Now that next_edge
exists, it would probably be better to redefine next_edge
and is_rootward
so there's less of an impedance mismatch. Too much of the proof seems to be moving data around to put things into or out of a form that works for the definition of is_rootward
.
Kyle Miller (Oct 28 2021 at 18:29):
(This part of the branch is much more experimental -- I'd wanted to see whether there was already enough stuff to be able to come up with some definition for is_rootward
that satisfied lemmas is_rootward_antisymm
and is_rootward_or_reverse
.)
Kyle Miller (Oct 28 2021 at 18:36):
Another way this argument could have been organized is to define two functions parent child : G.edge_set -> V
when given a root. These functions give the structure of the rooted tree, and e = ⟦(parent e, child e)⟧
for all e (modulo coercions). Then you'd show that child
is injective, and its image is the set of non-root vertices. Taking cardinalities, you get the result.
Arthur Paulino (Oct 28 2021 at 20:45):
Hm, I got stuck on that part. I was able to build a circuit but I'm not seeing how I can prove that the graph is not acyclic
Kyle Miller (Oct 28 2021 at 20:57):
Maybe the proof of is_rootward_or_reverse
might have some hint. walk.to_path
is a way to turn a walk into a path, so that's a way to get a cycle from a circuit.
Arthur Paulino (Oct 28 2021 at 21:22):
I got the cycle but I don't know how to say "here's the cycle, G is not acyclic"
Arthur Paulino (Oct 28 2021 at 21:25):
have has_cycle : ¬G.is_acyclic,
let cycle := (walk.to_path circuit : G.walk u₁ u₁),
Kyle Miller (Oct 28 2021 at 21:28):
Oh, I led you down a bad path, sorry. That defines the empty path -- walk.to_path
will delete everything since there's only one path from a vertex to itself.
Kyle Miller (Oct 28 2021 at 21:30):
It might be easier applying lemma is_acyclic_iff : G.is_acyclic ↔ ∀ (v w : V) (p q : G.path v w), p = q
. If you intro
your goal, you get a G.is_acyclic
hypothesis, which you can rw with this lemma.
Kyle Miller (Oct 28 2021 at 21:31):
Then you can specialize
it to the two paths you built your circuit from. I'm not sure if this is the best way, but you should be able to get that either u1 or u2 is equal to root, which is a contradiction.
Arthur Paulino (Oct 28 2021 at 23:37):
I'm not being able to break down g
u₁_root_u₂: G.walk u₁ u₂ := ↑(G.tree_path h u₁ root).append ↑(G.tree_path h u₂ root).reverse
u₁_u₂: G.path (u₁, u₂).fst (u₁, u₂).snd := path.singleton e_prop
u₂_u₁: G.path u₂ u₁
circuit: G.walk u₁ u₁ := u₁_root_u₂.append ↑u₂_u₁
g: u₁_root_u₂.to_path = u₁_u₂
⊢ false
Arthur Paulino (Oct 28 2021 at 23:42):
This is so close!
Arthur Paulino (Oct 29 2021 at 00:34):
Okay I need another cue :sweat_smile:
after this one I'm gonna go to the very basics. I'm sure I will understand a lot more
Arthur Paulino (Oct 29 2021 at 12:57):
Ah, I actually found an issue with my plan. Because of the way circuits are defined, a circuit can be something like u -> v -> w -> v -> u
Arthur Paulino (Oct 29 2021 at 12:57):
Which doesn't even contain a cycle
Arthur Paulino (Oct 29 2021 at 13:01):
@Kyle Miller feel free to hit the merge button if you think everything is okay:
https://github.com/leanprover-community/mathlib/pull/9993/files
Arthur Paulino (Oct 29 2021 at 13:01):
@Kyle Miller feel free to hit the merge button if you think everything is okay:
https://github.com/leanprover-community/mathlib/pull/9993/files
Arthur Paulino (Oct 29 2021 at 16:43):
There's something strange about our branches. A lot of things break when I merge master
into them :thinking:
Yaël Dillies (Oct 29 2021 at 16:48):
What kind of stuff? Some of it might might my fault.
Arthur Paulino (Oct 29 2021 at 16:50):
Hm, we can go slowly. First, simple_graph/adj_matrix
is not working on more_on_trees
Arthur Paulino (Oct 29 2021 at 16:58):
It doesn't work on walks_and_trees
either. But this PR (walks_and_trees
into master
) is open and without broken tests:
https://github.com/leanprover-community/mathlib/pull/8737
Arthur Paulino (Oct 29 2021 at 17:00):
more_on_trees
contains fixes for walks_and_trees
. So if we can get more_on_trees
it to work, this PR will fix walks_and_trees
Arthur Paulino (Oct 29 2021 at 17:01):
Long story short, can you help me fix more_on_walks
? :smile:
Yaël Dillies (Oct 29 2021 at 17:01):
Let's have a look!
Yaël Dillies (Oct 29 2021 at 17:01):
Wait 10-15min, I'm onto something else right now.
Arthur Paulino (Oct 29 2021 at 17:05):
Np, I'm investigating these merges a little further
Arthur Paulino (Oct 29 2021 at 17:11):
2 months ago this line was added. but nowadays, R
cannot be found anymore
Arthur Paulino (Oct 29 2021 at 17:14):
Back then, this was the line that introduced R
(a semiring
)
Yaël Dillies (Oct 29 2021 at 17:18):
Don't worry too much, this happens all the time. You write something, then someone comes along and changes the variables
and bem your branch broke.
Arthur Paulino (Oct 29 2021 at 17:19):
But... how come can they merge something if their changes break other parts of the code?
Arthur Paulino (Oct 29 2021 at 17:19):
Shouldn't the build test fail?
Yury G. Kudryashov (Oct 29 2021 at 17:21):
CI ensures that master
branch still compiles. It doesn't care about other branches.
Yury G. Kudryashov (Oct 29 2021 at 17:22):
So, if you work on a feature in a branch and merge master, then quite possibly your code will break.
Arthur Paulino (Oct 29 2021 at 17:23):
Ah, so it's really a matter of other branches becoming old/deprecated with time
Yaël Dillies (Oct 29 2021 at 17:50):
@Arthur Paulino, did you push all your changes? I can try to bump now.
Arthur Paulino (Oct 29 2021 at 17:51):
Yeap, more_on_trees
is up to date on GitHub too
Arthur Paulino (Oct 29 2021 at 17:52):
after those fixes on adj_matrix.lean
I'm gonna try to merge master
into it
Yaël Dillies (Oct 29 2021 at 17:53):
I'm doing that now.
Arthur Paulino (Oct 29 2021 at 17:53):
Thanks!
Kyle Miller (Oct 29 2021 at 17:57):
If the way the PR to another branch works is that it does a squash merge, won't that make it very difficult to see what Arthur's changes were? Maybe the destination branch should be updated to master, too?
Yaël Dillies (Oct 29 2021 at 17:58):
Yeah, the best thing would have been to fix those problems while in walks_and_trees
Arthur Paulino (Oct 29 2021 at 18:00):
There's the "blame" visualization mode on GitHub. We can use that if we want to see who did what
Kyle Miller (Oct 29 2021 at 18:01):
Since it's a squash, I think everything that changes from merging master will be blamed on you, Arthur
Arthur Paulino (Oct 29 2021 at 18:02):
Oh, you mean on master
Arthur Paulino (Oct 29 2021 at 18:02):
On master it's gonna be blamed on you because I opened a PR into your branch
Kyle Miller (Oct 29 2021 at 18:03):
I mean on walks_and_trees
. I like having somewhat nice commit histories on branches so that I can track what's changed while working.
Kyle Miller (Oct 29 2021 at 18:04):
If your PR is what updates walks_and_trees
to master, then there will be this massive commit that mixes what you did with everything that's happened to mathlib, and it will list you as the author. (This doesn't matter for the history of master
, since it will all be squashed again.)
Arthur Paulino (Oct 29 2021 at 18:05):
Makes sense. I should have started doing those fixes on walks_and_trees
Kyle Miller (Oct 29 2021 at 18:05):
I suspect all that needs to be done is to make sure to merge master
on walks_and_trees
, then merge walks_and_trees
into more_on_trees
, and then when the PR is merged, things will be OK.
Arthur Paulino (Oct 29 2021 at 18:06):
I can cherry pick changes from Yael and make small pushes on your branch if he's already started the merging process
Arthur Paulino (Oct 29 2021 at 18:07):
@Yaël Dillies have you?
Kyle Miller (Oct 29 2021 at 18:07):
I don't think you need to do that -- this merging plan should be sufficient, and it shouldn't create any conflicts.
Yaël Dillies (Oct 29 2021 at 18:07):
I'm doing exactly what Kyle just described.
Arthur Paulino (Oct 29 2021 at 18:08):
Perfect
Arthur Paulino (Oct 29 2021 at 18:12):
Next time I do fixes, I'm gonna do on the branch I branched out from and then merge it into mine afterwards
Kyle Miller (Oct 29 2021 at 18:19):
Perhaps a surprising thing about git is that every commit is actually a complete snapshot of the entire repository (along with pointers to the commits it's based on). All the diffs you see in the UI are just computed on the fly from these complete snapshots. I'm just trying to make sure there's a commit on walks_and_trees
that already has a post-merge-master
state so that when more_on_trees
is merged, the diff that git computes will make more sense.
Yaël Dillies (Oct 29 2021 at 18:22):
I've fixed connectivity
and adj_matrix
. Is there any file file you noticed that was broken?
Yaël Dillies (Oct 29 2021 at 18:27):
All fixed!
Kyle Miller (Oct 29 2021 at 18:28):
You really threaded the needle there with all of your comma removal, somehow avoiding a merge conflict. :clap:
Kyle Miller (Oct 29 2021 at 18:30):
Do you know why adj_matrix_pow_apply_eq_card_walk
needs semiring
now?
Yaël Dillies (Oct 29 2021 at 18:30):
Yeah, I was expecting some horrendous thing going on!
Yaël Dillies (Oct 29 2021 at 18:30):
What did it need before?
Yaël Dillies (Oct 29 2021 at 18:30):
I suspect that what happened is that a variables [semiring R]
got removed.
Kyle Miller (Oct 29 2021 at 18:31):
Oh right, adj_matrix
was generalized to def adj_matrix [has_zero α] [has_one α]
Kyle Miller (Oct 29 2021 at 18:38):
Thanks @Arthur Paulino for getting this to happen
lemma is_tree.card_edges_eq_card_vertices_sub_one
[fintype G.edge_set] [fintype V] [nonempty V] (h : G.is_tree) :
card G.edge_set = card V - 1
Arthur Paulino (Oct 29 2021 at 18:44):
I kicked it off, but you were the one actually paving the way :D
Arthur Paulino (Oct 29 2021 at 18:45):
Thanks a lot Yaël for the merges!
Kyle Miller (Oct 29 2021 at 18:46):
Hopefully eventually we'll have the matrix-tree theorem, too, which at its core relates any minor of the Laplacian matrix to the set of all tree-like functions f : {v | v ≠ root} → G.edge_set
.
Yaël Dillies (Oct 29 2021 at 18:52):
This sounds like it should say
lemma is_tree.card_edges [fintype V] (h : G.is_tree) :
G.edge_finset.card = card V - 1 :=
Yaël Dillies (Oct 29 2021 at 18:53):
[fintype G.edge_set]
should come from [fintype V]
and [nonempty V]
is useless.
Kyle Miller (Oct 29 2021 at 18:55):
I sort of want to abolish G.edge_finset
since it's just G.edge_set.to_finset
.
The reason [fintype G.edge_set]
is there is because, while it is derived from [fintype V]
, it's better to have it take an arbitrary proof of finiteness rather than requiring it to take the one derived from [fintype V]
.
Yaël Dillies (Oct 29 2021 at 18:56):
Ah yeah because it shows up in the statement.
Kyle Miller (Oct 29 2021 at 18:56):
It's true that nonempty V
is not necessary because of truncating subtraction.
Yaël Dillies (Oct 29 2021 at 18:56):
I strongly disagree to removing edge_finset
. It's very useful but I think it could be redefined.
Yaël Dillies (Oct 29 2021 at 18:58):
It's actually just a matter of changing.to_finset
with univ.filter
becaus you assume a fintype anyway.
Kyle Miller (Oct 29 2021 at 18:59):
edge_finset
definitely should be redefined -- right now it requires [fintype V]
which is very restrictive. The definition is using the instance that says [fintype G.edge_set]
from [fintype V]
.
Arthur Paulino (Oct 29 2021 at 19:01):
This line requires [nonempty V]
, doesn't it?
have root := classical.arbitrary V,
Yaël Dillies (Oct 29 2021 at 19:02):
In SRL, we talk a lot about edge_finset
(even if to be fair it's hidden under one definition so never actually see it).
Yaël Dillies (Oct 29 2021 at 19:02):
Arthur Paulino said:
This line requires
[nonempty V]
, doesn't it?
have root := classical.arbitrary V,
My point is that the final statement doesn't require it. so you can case-split on is_empty V
/nonempty V
, prove the trivial stuff in the first case, and go on with your existing proof in the second case.
Yaël Dillies (Oct 29 2021 at 19:03):
And for SRGs, Alena definitely needed edge_finset
.
Kyle Miller (Oct 29 2021 at 19:04):
I think the reason edge_finset
exists is that we didn't realize we could write G.edge_set.to_finset
, judging by its definition. It also makes summations over the edge set look nicer, but it's mostly cosmetic.
Yaël Dillies (Oct 29 2021 at 19:04):
Maybe we can change the assumptions to [fintype (G.neighbors_set u)] [fintype (G.neighbors_set v)]
?
Kyle Miller (Oct 29 2021 at 19:05):
Here's a question: is the empty graph a tree? Right now the definition allows empty graphs to be trees, but that doesn't seem right.
Kyle Miller (Oct 29 2021 at 19:06):
@Arthur Paulino A good exercise would be to remove the [nonempty V]
assumption, if you want to do that.
Arthur Paulino (Oct 29 2021 at 19:07):
Sure I'm gonna try it
Kyle Miller (Oct 29 2021 at 19:09):
Yaël Dillies (Oct 29 2021 at 19:10):
Is there thing akin to docs#tactic.nontriviality which would automatically perform the cache update and the goal discharge?
Kyle Miller (Oct 29 2021 at 19:10):
You'll also have to figure out how to show the cardinality of the edge set is 0 if V
is empty. Maybe write that as a separate lemma, like
lemma simple_graph.edge_set_empty_of_empty [is_empty V] : ¬G.edge_set.nonempty := sorry
Arthur Paulino (Oct 29 2021 at 19:14):
Yaël Dillies said:
Is there thing akin to docs#tactic.nontriviality which would automatically perform the cache update and the goal discharge?
Do you mean this one? https://leanprover-community.github.io/mathlib_docs/logic/nontrivial.html#tactic.interactive.nontriviality
Kyle Miller (Oct 29 2021 at 19:14):
@Yaël Dillies I think casesI
would work?
Arthur Paulino (Oct 29 2021 at 19:19):
@Kyle Miller do you agree with this name change?
This sounds like it should say
lemma is_tree.card_edges [fintype V] (h : G.is_tree) :
G.edge_finset.card = card V - 1 :=
Arthur Paulino (Oct 29 2021 at 19:20):
To me it sounds more like the name of a function that would return the cardinality of the set of edges of a tree :thinking:
But of course, my experience in this subject is super short
Mauricio Collares (Oct 29 2021 at 19:22):
Kyle Miller said:
Here's a question: is the empty graph a tree? Right now the definition allows empty graphs to be trees, but that doesn't seem right.
Of course this is debatable, but typically empty sets are not connected. For the graph case, this means the empty graph is not a tree (but it is a forest with zero trees, of course).
Arthur Paulino (Oct 29 2021 at 19:27):
Within my (shallow) understanding, making use of a subtraction that truncates at zero to prove such lemma for empty trees sounds a bit hacky. Because, semantically, the right side of the equality (n - 1
) doesn't make sense to me
Arthur Paulino (Oct 29 2021 at 19:36):
Unless, of course, we have a reason (besides pure generality) to make it true for empty trees as well
Kyle Miller (Oct 29 2021 at 19:42):
is_tree.card_edge_finset
would be a better name than is_tree.card_edges
. I agree removing the nonempty V
assumption is hacky, but if it's only a few extra lines in the proof it's nice when you can eliminate assumptions.
Kyle Miller (Oct 29 2021 at 19:43):
It also might not be a hack if G.is_tree
comes with the fact that V
is nonempty, for instance by modifying G.is_connected
to include this hypothesis.
Arthur Paulino (Oct 29 2021 at 19:49):
Okay that makes sense to me. I'm gonna try and see if I can do it
Arthur Paulino (Oct 30 2021 at 04:36):
@Kyle Miller It took longer than I expected, but I got it to work!
https://github.com/leanprover-community/mathlib/pull/10053
Arthur Paulino (Oct 30 2021 at 04:44):
Also @Yaël Dillies :point_up:
Arthur Paulino (Oct 30 2021 at 11:21):
Hey @Yury G. Kudryashov, after your proposed changes this was the only proof I couldn't adjust:
lemma is_tree_iff : G.is_tree ↔ nonempty V ∧ ∀ (v w : V), ∃!(p : G.walk v w), p.is_path :=
begin
simp only [is_tree, is_acyclic_iff],
split,
{ rintro ⟨hc, hu, hne⟩,
use hne,
intros v w,
let q := (hc v w).some.to_path,
use q,
simp only [true_and, path.path_is_path],
intros p hp,
specialize hu v w ⟨p, hp⟩ q,
rw ←hu,
refl, },
{ rintro ⟨hne, h⟩,
refine ⟨_, _, hne⟩,
{ intros v w,
obtain ⟨p, hp⟩ := h v w,
use p, },
{ rintros v w ⟨p, hp⟩ ⟨q, hq⟩,
simp only [p, hp, q, hq],
exact unique_of_exists_unique (h v w) hp hq, }, },
end
Arthur Paulino (Oct 30 2021 at 11:22):
It's failing to apply the very first simp
Eric Rodriguez (Oct 30 2021 at 11:42):
the issue is that is_tree is now a structure, so it's not definitionally equal to the ands from before
Eric Rodriguez (Oct 30 2021 at 11:47):
here's a working version:
lemma is_tree_iff : G.is_tree ↔ nonempty V ∧ ∀ (v w : V), ∃!(p : G.walk v w), p.is_path :=
begin
split,
{ rintro ⟨hc, hu, hne⟩,
rw is_acyclic_iff at hu,
refine ⟨hne, λ v w, _⟩,
let q := (hc v w).some.to_path,
use q,
simp only [true_and, path.path_is_path],
intros p hp,
specialize hu v w ⟨p, hp⟩ q,
rw ←hu,
refl, },
{ rintro ⟨hne, h⟩,
refine ⟨_, _, hne⟩,
{ intros v w,
obtain ⟨p, hp⟩ := h v w,
use p, },
{ rw is_acyclic_iff,
rintros v w ⟨p, hp⟩ ⟨q, hq⟩,
simp only [p, hp, q, hq],
exact unique_of_exists_unique (h v w) hp hq, }, },
end
note that I changed the use/intros
into a refine
, which is a very nice tactic. it's also strictly stronger than split
! (although annoyingly, it gets confused when you try to do a rintro
in it... so for example refine ⟨λ ⟨hc, hu, hne⟩, _, λ ⟨hne, h⟩, _⟩
doesn't work, but if it did it'd be the sameas the split
and the two rintro
s you do after
Arthur Paulino (Oct 30 2021 at 11:50):
Thank you very much!
Arthur Paulino (Oct 30 2021 at 11:52):
Another thing... The lint tests are failing. I tried to fix those but I couldn't figure it out.
Eric Rodriguez (Oct 30 2021 at 12:06):
don't worry about them, they're not in your part of the code
Eric Rodriguez (Oct 30 2021 at 12:07):
also, I know you've put in all this work on this, but it may be worth keeping the "weaker" empty option available (as mathematically boring as it is); I think this is common in mathlib, with things like docs#is_preconnected (so maybe is_pretree
?) because a lot of results end up holding for the weaker class anyways
Arthur Paulino (Oct 30 2021 at 17:14):
Hm, I'd rather leave it for later decisions since we're not sure whether we need that generalization starting from a definition of "preconnectedness" in the context of graphs
Arthur Paulino (Oct 30 2021 at 17:23):
I'd say time will tell. Let's see how the need evolves
Arthur Paulino (Oct 30 2021 at 17:34):
Plus I'm not so sure how I'd go about that generalization with the definition of "pretrees". I gave it a little go and I ended up having to redefine a lot of things in terms of "pretree", like pretree_path
and pretree_dist
, which seemed very odd to me :thinking:
Arthur Paulino (Oct 31 2021 at 17:15):
What if we just allow empty trees and add the nonempty restrictions on lemmas that require such? In other words, simply ignore my most recent PR?
Kevin Buzzard (Oct 31 2021 at 22:53):
I think the idea of letting "pre-trees" be forests such that there's a path between any two vertices is fine, and then just define a tree to be a nonempty pretree.
Kevin Buzzard (Oct 31 2021 at 22:53):
there are a bunch of standard theorems about primes which are true for preprimes (i.e. primes and 1), e.g. p divides ab implies p divides a or p divides b
Kyle Miller (Oct 31 2021 at 23:04):
Right now, a forest is just an acyclic graph (and it has no definition of its own since G.is_acyclic
exists), and a tree is a connected acyclic graph. The definition of G.is_connected
is that there's a walk between every pair of vertices. If anything, it should be G.is_connected
that should require that there be at least one vertex.
The way this probably should work is the definition of connected be changed to "there exists a vertex v such that for every vertex w there is a walk from v to w."
Kyle Miller (Oct 31 2021 at 23:05):
This is the same as saying that the quotient of V
by the G.walk
relation is a singleton.
Arthur Paulino (Oct 31 2021 at 23:21):
But then how would we structure it? Have the definition of trees that requires connectivity and define a forest as an union of trees?
Kyle Miller (Oct 31 2021 at 23:23):
I'm suggesting that the definition of connectivity be changed, and leave is_tree
alone. A forest is just an acyclic graph, so there's nothing to change there.
Kyle Miller (Oct 31 2021 at 23:25):
Unfortunately, this makes the different theorems for what G.is_tree
is equivalent to to be more awkward, but maybe they don't have to be equivalences.
Arthur Paulino (Oct 31 2021 at 23:27):
Today I merged master into walks_and_trees
again and solved some lint errors
Arthur Paulino (Nov 01 2021 at 01:39):
@Kyle Miller Is this what you mean?
https://github.com/leanprover-community/mathlib/pull/10082
Arthur Paulino (Nov 01 2021 at 01:41):
Line 1244 is not working and I don't know how to proceed there. Probably related to what you've said about dropping the equivalence
Kyle Miller (Nov 01 2021 at 01:42):
Kyle Miller said:
The way this probably should work is the definition of connected be changed to "there exists a vertex v such that for every vertex w there is a walk from v to w."
def connected : Prop := ∃ (u : V), ∀ (v : V), G.reachable u v
Kyle Miller (Nov 01 2021 at 01:45):
and then something like
lemma connected_iff_nonempty_and_pairwise_reachable :
G.connected ↔ nonempty V ∧ ∀ (u v : V), G.reachable u v :=
begin
sorry
end
Kyle Miller (Nov 01 2021 at 01:50):
I haven't given much thought to the effects of this, though. The nonempty V ∧ ∀ (u v : V), G.reachable u v
definition is potentially nicer (note that in the definition you gave, nonempty V
is inside the forall).
Arthur Paulino (Nov 01 2021 at 02:16):
Oops, I better remove it from the forall.
Arthur Paulino (Nov 01 2021 at 02:36):
The definition I gave is more "symmetrical", I think
Arthur Paulino (Nov 01 2021 at 02:36):
And it changes less things
Arthur Paulino (Nov 01 2021 at 02:59):
Okay, I got some progress there
Scott Morrison (Nov 01 2021 at 05:25):
I think we already have at least 2 or 3 pairs of definitions is_preconnected
and is_connected
in mathlib, where is_connected
is just is_preconnected + nonempty
. You should do that too!
Arthur Paulino (Nov 01 2021 at 12:05):
Got the definition of preconnected
in :+1:
@Kyle Miller I wasn't able to prove connected_of_edge_connected
but everything else is working. Feel free to take over branch change-connectivity-def
before accepting this PR (which merges change-connectivity-def
into yours)
Arthur Paulino (Nov 01 2021 at 12:22):
And I'm closing this PR since we're going for the change of definition of connected
instead
Arthur Paulino (Nov 01 2021 at 19:20):
@Kyle Miller I've updated https://github.com/leanprover-community/mathlib/pull/10082 with your ideas but I couldn't make connected_of_edge_connected
work
Arthur Paulino (Nov 01 2021 at 19:21):
(the proof is written in a way that I can't understand at the moment)
Yakov Pechersky (Nov 01 2021 at 19:22):
Do you remember what the definition of connected
used to be before?
Arthur Paulino (Nov 01 2021 at 19:23):
Yeah
def connected : Prop := ∀ (u v : V), G.reachable u v
Yakov Pechersky (Nov 01 2021 at 19:24):
It was an and
. So I used and.imp_right
, since connected
was preconnected AND nontrivial V
and edge_connected _ _ _ _
was connected _
, which was _ AND nontrivial V
. So the right hand side of the assumption implies the right hand side of the goal. That's the and.imp_right
shortcut.
Yakov Pechersky (Nov 01 2021 at 19:24):
Not exactly, that's what preconnected
is.
Yakov Pechersky (Nov 01 2021 at 19:25):
Now that connected
is a structure, and not a plain and
conjunction, you can't use and.elim_right
anymore.
Yakov Pechersky (Nov 01 2021 at 19:29):
With the new definition, here's a place to start:
lemma connected_of_edge_connected {k : ℕ} (hk : 0 < k) (h : G.edge_connected k) : G.connected :=
{ preconnected := _,
nonempty := _ }
Arthur Paulino (Nov 01 2021 at 19:29):
I see, but I think it's the writing style (I've very new to Lean). So far I've only been able to understand steps explicitly separated by commas
Kyle Miller (Nov 01 2021 at 19:33):
I think the and.imp_right
proof might be more obfuscated than just terse, so don't feel bad about not being able to follow it.
Arthur Paulino (Nov 01 2021 at 19:36):
begin
split,
rw preconnected,
intros u v,
rw reachable,
end
leaves me at
V: Type u
G: simple_graph V
k: ℕ
hk: 0 < k
h: G.edge_connected k
uv: V
⊢ nonempty (G.walk u v)
V: Type u
G: simple_graph V
k: ℕ
hk: 0 < k
h: G.edge_connected k
⊢ nonempty V
Kyle Miller (Nov 01 2021 at 19:37):
I'm having a hard time modifying the one-liner, but here's away to modify the original one:
lemma preconnected_of_edge_connected {k : ℕ} (hk : 0 < k) (h : G.edge_connected k) :
G.preconnected :=
begin
intros v w,
have h' := (h ∅ (by simp) (by simp [hk])).preconnected v w,
simp only [finset.coe_empty, subgraph.delete_edges_of_empty] at h',
cases h',
exact ⟨h'.map (subgraph.map_spanning_top _)⟩,
end
Have another lemma nonempty_of_edge_connected
, then put them together for connected_of_edge_connected
.
Yakov Pechersky (Nov 01 2021 at 19:37):
lemma connected_of_edge_connected {k : ℕ} (hk : 0 < k) (h : G.edge_connected k) : G.connected :=
let C' := h ∅ (by simp) (by simp [hk]) in
{ preconnected := by simpa using C'.preconnected,
nonempty := C'.nonempty }
Yakov Pechersky (Nov 01 2021 at 19:38):
If you want to reuse the old proof. Really, there should be a connected.antimono
or however you name it, saying that if a "supergraph" is connected, so is an induced subgraph.
Yakov Pechersky (Nov 01 2021 at 19:40):
The original proof was pretty terse too =), I just followed the style
Arthur Paulino (Nov 01 2021 at 19:41):
Alright, PR updated. I'm gonna spend a few hours tomorrow figuring out what you guys wrote. But feel free to merge it into walks_and_trees
if you see fit :+1:
https://github.com/leanprover-community/mathlib/pull/10082
Or, of course, just push commits directly to change-connectivity-def
Yakov Pechersky (Nov 01 2021 at 19:41):
Right now I'm having issues extracting the reachable
Prop out of the quot. I'm clumsy with that API. If I have the following state, what's the right quot
API?
V: Type u
G: simple_graph V
_inst_1: unique G.connected_component
h: nonempty V
v: V
w: V
this: G.connected_component_of v = G.connected_component_of w
⊢ G.reachable v w
Kyle Miller (Nov 01 2021 at 19:42):
(preconnected_of_edge_connected
is now unnecessary given Yakov's connected_of_edge_connected
)
Kyle Miller (Nov 01 2021 at 19:47):
@Yakov Pechersky I'm not sure if you can apply it directly, but docs#quotient.eq should be a way to do something with this
given reachable_is_equivalence
/reachable_setoid
Yakov Pechersky (Nov 01 2021 at 19:49):
Ah, something with docs#quot.exact maybe?
Kyle Miller (Nov 01 2021 at 19:51):
The nice thing about the quotient
ones (like docs#quotient.exact) is that they give it in terms of the original relation, rather than eqv_gen
. Maybe this is a hole in the quot
api? having variants for when you can supply an explicit setoid
?
Yakov Pechersky (Nov 01 2021 at 19:52):
Right, but even quotient.*
ask for the setoid to be an implicit arg
Kyle Miller (Nov 01 2021 at 19:52):
example [unique G.connected_component] [h : nonempty V] (v w : V)
(this: G.connected_component_of v = G.connected_component_of w) :
G.reachable v w :=
@quotient.exact _ G.reachable_setoid _ _ this
Kyle Miller (Nov 01 2021 at 19:53):
quotient
seems to be designed around having a canonical setoid instance for a given type, but there are times when you want potentially many.
Kyle Miller (Nov 01 2021 at 19:54):
It would be nice if there were a quot.exact'
or something that explicitly took this setoid
instance, and then quotient.exact
could be defined in terms of it.
Yakov Pechersky (Nov 01 2021 at 19:56):
@[simp] lemma equivalence.eqv_gen_iff {α : Type*} {r : α → α → Prop} (h : equivalence r) {x y : α} :
eqv_gen r x y ↔ r x y :=
begin
refine ⟨λ H, _, eqv_gen.rel _ _⟩,
induction H,
{ assumption },
{ exact h.left _ },
{ exact h.right.left ‹_› },
{ exact h.right.right ‹_› ‹_› }
end
lemma connected_component_of.reachable_congr {v w : V}
(h : G.connected_component_of v = G.connected_component_of w) : G.reachable v w :=
by simpa [reachable_is_equivalence] using quot.exact _ h
Kyle Miller (Nov 01 2021 at 19:56):
setoid
might not be right since it bundles the relation -- rather, if quot.exact'
took an equivalence
proof.
Yakov Pechersky (Nov 01 2021 at 19:58):
Which gives the following too:
@[reducible]
def quot.is_empty {α : Type*} (p : α → α → Prop) [is_empty α] :
is_empty (quot p) :=
⟨λ a, quot.induction_on a (λ a', is_empty_elim a')⟩
instance connected_component.is_empty_of_is_empty [is_empty V] :
is_empty G.connected_component :=
quot.is_empty _
lemma connected_of_unique [unique G.connected_component] : G.connected :=
begin
casesI is_empty_or_nonempty V,
{ exact is_empty_elim (default G.connected_component) },
{ exact ⟨λ _ _, connected_component_of.reachable_congr (subsingleton.elim _ _), ‹_›⟩ }
end
Kyle Miller (Nov 01 2021 at 20:03):
If eqv_gen
is really the best interface to quot
given an equivalence relation, I'd suggest
lemma _root_.eqv_gen.of_eqv {α : Type*} {r : α → α → Prop} (h : equivalence r) {x y : α}
(H : eqv_gen r x y) : r x y :=
begin
induction H,
{ assumption },
{ exact h.left _ },
{ exact h.right.left ‹_› },
{ exact h.right.right ‹_› ‹_› }
end
lemma connected_component_of.reachable_congr {v w : V}
(h : G.connected_component_of v = G.connected_component_of w) : G.reachable v w :=
(quot.exact _ h).of_eqv G.reachable_is_equivalence
If namespaces worked out, it would also be nice with flipped arguments, so that the proof is G.reachable_is_equivalence.of_eqv_gen (quot.exact _ h)
Kyle Miller (Nov 01 2021 at 20:04):
eqv_gen_iff
would be nice to have, too
Kevin Buzzard (Nov 01 2021 at 21:55):
Arthur Paulino said:
begin split, rw preconnected, intros u v, rw reachable, end
leaves me at
V: Type u G: simple_graph V k: ℕ hk: 0 < k h: G.edge_connected k u v: V ⊢ nonempty (G.walk u v) V: Type u G: simple_graph V k: ℕ hk: 0 < k h: G.edge_connected k ⊢ nonempty V
@Arthur Paulino maybe you don't need this now, but here's how to do that nonempty
goal from earlier: if you look at the definition of nonempty
then you can see that the constructor is called nonempty.intro
, so exact nonempty.intro u
should do it.
Arthur Paulino (Nov 02 2021 at 14:36):
@Kevin Buzzard makes sense, thanks!
Arthur Paulino (Nov 02 2021 at 14:37):
@Kyle Miller do you think the PR needs more adjustments before merging?
Arthur Paulino (Nov 03 2021 at 00:49):
/-- The list of edges in a walk are all edges of the graph.
It is written in this form to avoid unsightly coercions. -/
lemma edges_subset_edge_set {u v : V} (p : G.walk u v) {e : sym2 V}
(h : e ∈ p.edges) : e ∈ G.edge_set
Hm, this isn't really what we mean, is it? Wouldn't it be more like "Every edge in a walk is an edge of the graph"?
Kyle Miller (Nov 03 2021 at 00:56):
Are you talking about the comment? Less weird is "Every edge in a walk's edge list is an edge of the graph." The point of this comment is to remind the reader why it's not p.edges ⊆ G.edge_set
, and that's because p.edges : list (sym2 V)
rather than set (sym2 V)
.
Arthur Paulino (Nov 03 2021 at 00:57):
Yeah, about the comment
Kyle Miller (Nov 03 2021 at 01:00):
(Please feel free to clarify that comment in the source. You can delete it and start over.)
Arthur Paulino (Nov 04 2021 at 20:28):
Hi! I'm trying to prove the lemma that says that the existence of a perfect matching implies that the cardinality of the vertices set is even. I've come to this point:
V: Type u
G: simple_graph V
_inst_1: fintype V
M: G.matching
_inst_2: fintype ↥(M.edges)
h: ∀ (v : V), ∃ (e : sym2 V), e ∈ M.edges ∧ v ∈ e
⊢ card V = 2 * card ↥(M.edges)
Yaël Dillies (Nov 04 2021 at 20:31):
I'd advise you to go by double counting the finset (univ : V × sym2 V).filter (λ ve, ve.1 ∈ ve.2)
Johan Commelin (Nov 04 2021 at 20:32):
Can you define an equiv V ≃ Σ e : M.edges, {v : V // v ∈ e}
?
Yaël Dillies (Nov 04 2021 at 20:33):
Yeah, that's another way. Not sure it goes smoothly, though.
Arthur Paulino (Nov 04 2021 at 20:36):
I thought about double counting too because a vertex is in exactly one edge and an edge has exactly two vertices ( verytypical informal proof)
Johan Commelin (Nov 04 2021 at 20:39):
I suggest choose e he1 he2 using h
as next tactic step.
Kyle Miller (Nov 04 2021 at 20:43):
This should follow from the degree-sum formula, right? Perfect matchings are certain kinds of subgraphs (this coercion is not implemented) and then you apply docs#simple_graph.sum_degrees_eq_twice_card_edges and use the fact that all the degrees are 1 for this subgraph.
Kyle Miller (Nov 04 2021 at 20:45):
If you want to do it yourself, I'd suggest using the map V -> sym2 V
for the perfect matching that gets the edge associated to a vertex, show the image is M.edges
, and show the map is 2-1 onto each element of M.edges
.
Kyle Miller (Nov 04 2021 at 20:47):
(Somewhere I shared a link to a branch that contains more things about perfect matchings, when @Yaël Dillies asked very recently. I think it has this V -> sym2 V
map.)
Kyle Miller (Nov 04 2021 at 20:53):
Matchings were added to mathlib before subgraphs were. It might be reasonable to redesign them as a graph M : simple_graph V
satisfying M \leq G
such that whenever M.adj v w
and M.adj v w'
then w = w'
. We could define simple_graph.support
of a graph to be {v : V | \exists w, G.adj v w}
, and then a perfect matching is an M
such that M.support = set.univ
.
Yaël Dillies (Nov 04 2021 at 20:54):
Do you think we even want a standalone definition if we have k-factors?
Kyle Miller (Nov 04 2021 at 20:55):
If there are k-factors, then it would be good to have an interface for 1-factors something like this
Kyle Miller (Nov 04 2021 at 20:56):
though it would instead actually be terms of G.subgraph
instead of terms of simple_graph V
that are subgraphs.
Kyle Miller (Nov 04 2021 at 20:58):
Maybe make sure k-factors work for non-locally-finite graphs; the definition of matchings I gave doesn't depend on that assumption.
Kyle Miller (Nov 04 2021 at 21:10):
@Arthur Paulino if you want a relatively small thing to try PRing to mathlib itself, it could be defining the support of a simple_graph
, which I don't think exists already. Maybe its definition could be def simple_graph.support (G : simple_graph V) : set V := rel.image G.adj set.univ
, and then you'd have a simp lemma simple_graph.mem_support
that says a vertex v
is an element of G.support
iff there exists a vertex w
with G.adj v w
. One other useful lemma (a corollary) would be that if G.adj v w
for (v w : V)
then v
is an element of G.support
.
Arthur Paulino (Nov 04 2021 at 21:14):
Yeah, all of a sudden this matching lemma became a redesigning task which I'm not sure I can handle without bugging too many people :sweat_smile:
Arthur Paulino (Nov 04 2021 at 21:16):
At least not yet
Johan Commelin (Nov 04 2021 at 21:16):
What is left? That goal state that you posted above?
Johan Commelin (Nov 04 2021 at 21:16):
That shouldn't require any redesigning.
Johan Commelin (Nov 04 2021 at 21:17):
With that choose
line that I posted, it should be quite straightforward to build the equiv.
Johan Commelin (Nov 04 2021 at 21:18):
And then you can use fintype.card_congr
and fintype.card_sigma
. By then you should be in pretty good shape.
Yaël Dillies (Nov 04 2021 at 21:30):
Arthur Paulino said:
Yeah, all of a sudden this matching lemma became a redesigning task which I'm not sure I can handle without bugging too many people :sweat_smile:
Don't worry! I happen to need k-factors myself.
Arthur Paulino (Nov 04 2021 at 23:09):
@Kyle Miller I was able to prove the lemma with:
@[simp] lemma mem_support {v : V} : v ∈ G.support ↔ ∃ w, G.adj v w :=
begin
simp only [support, rel.image],
split,
{ intro h,
choose w h using h,
use w,
cases h with _ h,
rw adj_comm at h,
use h, },
{ intro h,
choose w h using h,
use w,
simp [h, adj_comm], }
end
But I notice some repeated structure for each split. Is it possible to optimize this proof?
Arthur Paulino (Nov 04 2021 at 23:14):
better:
@[simp] lemma mem_support {v : V} : v ∈ G.support ↔ ∃ w, G.adj v w :=
begin
simp only [support, rel.image, adj_comm],
split,
{ intro h,
choose w h using h,
use w,
cases h with _ h,
use h, },
{ intro h,
choose w h using h,
use w,
simp [h], }
end
Kyle Miller (Nov 04 2021 at 23:14):
I think you might be able to do it in one line with rw [support, rel.mem_image, adj_comm]
Kyle Miller (Nov 04 2021 at 23:14):
(maybe simp only
instead of rw
)
Kyle Miller (Nov 04 2021 at 23:15):
Oh, you'll need set.mem_univ
and maybe a couple others.
Arthur Paulino (Nov 04 2021 at 23:17):
Is it slower to add many arguments to simp only
?
Another question, what's the difference between simp [a, b, ...]
and simp only [a, b, c, ...]
?
Kyle Miller (Nov 04 2021 at 23:19):
Without only
, it additionally uses all lemmas marked with the @[simp]
attribute. https://leanprover-community.github.io/extras/simp.html
Arthur Paulino (Nov 04 2021 at 23:21):
About the redesign you mentioned, isn't it a bit strange to define a matching as a subgraph? I mean, it's a subgraph that always contain all the vertices of the full graph
Arthur Paulino (Nov 04 2021 at 23:23):
I think it's more common in the literature to understand a matching as a subset of edges
Kyle Miller (Nov 04 2021 at 23:24):
This theorem, by the way, is strangely resisting squeeze_simp
and simp?
Arthur Paulino (Nov 04 2021 at 23:26):
What do you mean?
Kyle Miller (Nov 04 2021 at 23:28):
And regarding formalization vs literature: combinatorics is a very intuitive subject, so things tend to be very informal because the gaps can be filled in easily. But a computer needs everything to be represented very precisely. It seems like many of the ways we might use matchings are very subgraph-like, so we may as well just represent them as subgraphs, without needing to create coercions back and forth between different representations.
Yael seems to be on it to define what k-factors are, and he was thinking of defining them as being subgraphs.
Kyle Miller (Nov 04 2021 at 23:29):
Arthur Paulino said:
What do you mean?
Ok:
@[simp] lemma mem_support {v : V} : v ∈ G.support ↔ ∃ w, G.adj v w :=
begin
simp [support, rel.mem_image, adj_comm, set.mem_univ],
end
Replacing simp
with squeeze_simp
suggests this simp only
:
@[simp] lemma mem_support {v : V} : v ∈ G.support ↔ ∃ w, G.adj v w :=
begin
simp only [support, rel.mem_image, adj_comm, set.mem_univ, set.mem_univ, iff_self, exists_true_left],
-- goal not closed
end
Arthur Paulino (Nov 04 2021 at 23:29):
Yeah as soon as I said it, I realized that spanning trees also have this characteristic of containing all vertices of the bigger graph and we might as well define them as subgraphs
Arthur Paulino (Nov 04 2021 at 23:31):
Wow that's indeed a 1-liner
Kyle Miller (Nov 04 2021 at 23:31):
There's a complexity already with simple graphs that there are two notions of a subgraph: G.subgraph
for arbitrary ones and simple_graph V
for spanning ones (where we take only those that are ).
Arthur Paulino (Nov 04 2021 at 23:32):
@[simp] lemma mem_support {v : V} : v ∈ G.support ↔ ∃ w, G.adj v w :=
by simp [support, rel.image, adj_comm]
closed the goal here
Kyle Miller (Nov 04 2021 at 23:34):
docs#simple_graph.subgraph.spanning_coe is an attempt to connect up with this latter notion, and I expect to use G.support
at some point for restricting a graph to a G.subgraph
by removing degree-0 vertices.
Kyle Miller (Nov 04 2021 at 23:36):
This would be a little better:
@[simp] lemma mem_support {v : V} : v ∈ G.support ↔ ∃ w, G.adj v w :=
by simp [support, rel.mem_image, adj_comm]
It's better to use the "API" for definitions rather than unfolding them. The point of mem_support
is to define the API for G.support
so future users don't have to unfold it.
Kyle Miller (Nov 04 2021 at 23:37):
Actually, I'm not sure this should be a @[simp]
lemma. It doesn't hurt, but it's unclear to me right now whether it will help simplify things. I guess leave it unless anyone has a reason it shouldn't be one.
Arthur Paulino (Nov 04 2021 at 23:38):
I can add it without @[simp]
and then you can make it so if you see fit later
Yakov Pechersky (Nov 04 2021 at 23:39):
You might have a support_mono
lemma that says G <= G' -> G.support <= G'.support
, right?
Arthur Paulino (Nov 04 2021 at 23:41):
lemme try to get that in
Yakov Pechersky (Nov 04 2021 at 23:42):
Something something forgetful functor to Set
Arthur Paulino (Nov 04 2021 at 23:44):
type mismatch at application
G ≤ G'
term
G'
has type
simple_graph W : Type v
but is expected to have type
simple_graph V : Type u
G'
wasn't defined as a subgraph of G
here :thinking:
Arthur Paulino (Nov 04 2021 at 23:45):
This should go inside subgraph
right?
Kyle Miller (Nov 05 2021 at 00:03):
This would be for (G G' : simple_graph V)
Kyle Miller (Nov 05 2021 at 00:03):
There's an ordering on simple graphs with the same vertex type based on edge containment
Kyle Miller (Nov 05 2021 at 00:13):
@Arthur Paulino For your proof earlier, you don't need to use choose
, but you can rather use cases
, rcases
, or obtain
since the goal is a proposition. choose
is mainly useful for when you need to "actually" get a value out of an existential
Yaël Dillies (Nov 05 2021 at 00:15):
I would even add when you need to swap an exists and a forall.
Arthur Paulino (Nov 05 2021 at 00:35):
Got this one
lemma support_mono (H: simple_graph V) : H ≤ G → H.support ≤ G.support :=
begin
intros h v hv,
simp only [←is_subgraph_eq_le, is_subgraph] at h,
rw mem_support at hv,
choose w hw using hv,
use w,
simp [hw, h, adj_comm],
end
Kyle Miller (Nov 05 2021 at 00:47):
Two things, other than that looks good (though of course someone will golf it, it's inevitable): make sure to put a space after H
, and please swap the roles of H
and G
in the lemma statement -- if you #check @support_mono
you'll see that the arguments are (G H : simple_graph V)
. Another option is to edit your lemma so it says (H G : simple_graph V)
since this will override the variables
Kyle Miller (Nov 05 2021 at 00:48):
I forgot a third thing: put (h : H ≤ G)
as an argument before the colon, since you're intro
ing it anyway.
Arthur Paulino (Nov 05 2021 at 00:51):
like this?
lemma support_mono (G' : simple_graph V) (h : G ≤ G') : G.support ≤ G'.support :=
begin
intros v hv,
rw [←is_subgraph_eq_le, is_subgraph] at h,
rw mem_support at hv,
cases hv with w hw,
use w,
simp [hw, h, adj_comm],
end
Mario Carneiro (Nov 05 2021 at 00:53):
probably G
and G'
should be implicit
Mario Carneiro (Nov 05 2021 at 00:54):
also space before after the <-
although enforcement on that rule is pretty low
Kyle Miller (Nov 05 2021 at 00:54):
@Arthur Paulino so use {G G' : simple_graph V}
as the argument instead
Kyle Miller (Nov 05 2021 at 00:55):
What rule's that about <-
?
Mario Carneiro (Nov 05 2021 at 00:55):
i.e. rw [← is_subgraph_eq_le, is_subgraph] at h,
Arthur Paulino (Nov 05 2021 at 00:56):
done: https://github.com/leanprover-community/mathlib/pull/10176
Arthur Paulino (Nov 05 2021 at 02:31):
PR updated :+1:
Arthur Paulino (Nov 05 2021 at 16:30):
I'm attempting to formalize vertex coloring. do you guys think this is a good design?
structure vertex_coloring :=
(color : V → ℕ)
(valid : ∀ (v w : V), G.adj v w → color v ≠ color w)
def vertex_colors (C : G.vertex_coloring) := ...
Yaël Dillies (Nov 05 2021 at 16:30):
We already thought about it quite a lot.
Mario Carneiro (Nov 05 2021 at 16:30):
I would make the codomain of color
a parameter
Yaël Dillies (Nov 05 2021 at 16:31):
@David Wärn used them without explicitly defining them for docs#combinatorics.line.exists_mono_in_high_dimension
Yaël Dillies (Nov 05 2021 at 16:31):
and @Alena Gusakov has a definition on a branch
Yaël Dillies (Nov 05 2021 at 16:32):
Either way, you'll want to use docs#pairwise
Mario Carneiro (Nov 05 2021 at 16:32):
@Yaël Dillies I don't think your example is a vertex coloring in @Arthur Paulino 's sense, Hales-jewett does not have any constraints on colors of adjacent vertices
Yaël Dillies (Nov 05 2021 at 16:33):
No, it doesn't. This is more of a contextual idea.
Mario Carneiro (Nov 05 2021 at 16:33):
it does reveal some ambiguity of the terminology though
Arthur Paulino (Nov 05 2021 at 16:35):
@Mario Carneiro do you mean like this?
structure vertex_coloring (color_type : Type u) :=
(color : V → color_type)
(valid : ∀ (v w : V), G.adj v w → color v ≠ color w)
def vertex_colors (color_type : Type u) (C : G.vertex_coloring color_type) := ...
Mario Carneiro (Nov 05 2021 at 16:35):
yes
Arthur Paulino (Nov 05 2021 at 16:35):
That was my first go but I found it a bit annoying to carry color_type
everywhere. What can I do about it?
Yaël Dillies (Nov 05 2021 at 16:36):
Call it α
:stuck_out_tongue:
Arthur Paulino (Nov 05 2021 at 16:36):
Ah, then explain in a docstring
Mario Carneiro (Nov 05 2021 at 16:37):
The docstring of vertex_coloring
needs to explain it anyway
Arthur Paulino (Nov 05 2021 at 16:37):
@Yaël Dillies how do you mean about pairwise
?
Mario Carneiro (Nov 05 2021 at 16:37):
but it's also useful to be able to constrain the type to other things; even in the finite graph situation you might want to take the codomain to be fin k
to get a k-coloring
Mario Carneiro (Nov 05 2021 at 16:38):
and in the infinite graph situation you might want it to be some other cardinal
Mario Carneiro (Nov 05 2021 at 16:39):
or even just use regular type operations on it like A ⊕ B
when joining two graphs with disjoint colors
Yaël Dillies (Nov 05 2021 at 16:40):
Eh, it doesn't turn out that great.
Yaël Dillies (Nov 05 2021 at 16:41):
I was thinking of something like ∀ a, is_antichain G.adj (color '⁻¹ {a})
Mario Carneiro (Nov 05 2021 at 16:43):
or your "colors" could be other mathematical objects relevant to an application. This is the reason why docs#matrix lets you use arbitrary index types even though we usually just think of the fin n
case
Kyle Miller (Nov 05 2021 at 16:47):
Mario Carneiro said:
it does reveal some ambiguity of the terminology though
These tend to be called "proper" colorings when there can be confusion.
@Arthur Paulino Another design is to consider homomorphisms to complete_graph α
, which gives you a lot of power
Kyle Miller (Nov 05 2021 at 16:49):
abbreviation proper_coloring (α : Type*) := G →g complete_graph α
Kyle Miller (Nov 05 2021 at 16:49):
This is how @Aaron Anderson suggested doing it.
Arthur Paulino (Nov 05 2021 at 16:57):
I was thinking about defining vertex_coloring
and edge_coloring
Arthur Paulino (Nov 05 2021 at 16:58):
Do you think it would be okay to prove the homomorphism after having that pair of more bare-bones definitions?
Kyle Miller (Nov 05 2021 at 17:08):
I think you should start with the homomorphism and turn the fields from the struct into a definition and a lemma. The first is from applying the homomorphism, and the second is what it means to be a homomorphism, so it should be 2-4 lines total
Kyle Miller (Nov 05 2021 at 17:09):
It takes a bit of definition unfolding to see it, but this homomorphism definition is basically identical to the struct you defined
Arthur Paulino (Nov 05 2021 at 17:12):
Alright, I'm gonna try to dig this approach
Kyle Miller (Nov 05 2021 at 17:52):
(@Yaël Dillies With your k-factors work, one thing to think about is how to go back and forth between edge colorings and coverings by disjoint maximal 1-factors (i.e., perfect matchings).)
Arthur Paulino (Nov 05 2021 at 18:37):
How do I say this?
lemma proper_coloring_is_valid (α : Type*) (C : G.proper_coloring α) :
∀ (v w : V), G.adj v w → ¬(C.adj v w) := sorry
Arthur Paulino (Nov 05 2021 at 18:38):
invalid field notation, 'adj' is not a valid "field" because environment does not contain 'simple_graph.proper_coloring.adj'
C
which has type
G.proper_coloring α
Kyle Miller (Nov 05 2021 at 18:40):
variables {V : Type u} (G : simple_graph V)
abbreviation proper_coloring (α : Type*) := G →g complete_graph α
variables {G} {α : Type*} (C : G.proper_coloring α)
def proper_coloring.color (v : V) : α := C v
lemma proper_coloring.valid (v w : V) (h : G.adj v w) :
C.color v ≠ C.color w :=
C.map_rel h
Kyle Miller (Nov 05 2021 at 18:43):
Also,
def proper_coloring.mk (f : V → α) (h : ∀ {v w : V}, G.adj v w → f v ≠ f w) :
G.proper_coloring α := ⟨f, @h⟩
for a constructor that looks recognizable.
Kyle Miller (Nov 05 2021 at 18:50):
One of the reasons for having this be a homomorphism is that if you have an injective map a -> b
, then there is an induced graph homomorphism complete_graph a ->g complete_graph b
(not written, but easy), which you can compose with a proper a-coloring to get a proper b-coloring (so you can extend the set of colors using already existing machinery).
Kyle Miller (Nov 05 2021 at 18:51):
Or, if you have a graph homomorphism G ->g G'
and a proper a-coloring of G'
, then you can compose the homomorphisms to get a proper a-coloring of G
. This can be used to show that subgraphs of a proper a-colorable graph are a-colorable.
Arthur Paulino (Nov 05 2021 at 19:06):
You can see a lot further than I can. I will do my best to catch up
Yaël Dillies (Nov 05 2021 at 19:10):
(Yaël Dillies With your k-factors work, one thing to think about is how to go back and forth between edge colorings and coverings by disjoint maximal 1-factors (i.e., perfect matchings).)
Does that mean you want k-factors to be subgraphs?
Kyle Miller (Nov 05 2021 at 19:19):
@Arthur Paulino Something I'm looking forward to is having the fact that if every finite subgraph of a graph has a proper a-coloring, then (noncomputably) the graph has a proper a-coloring. This can be done using the idea for how docs#finset.all_card_le_bUnion_card_iff_exists_injective is proved from docs#finset.all_card_le_bUnion_card_iff_exists_injective'
I guess there's a more general result, which is if G'
is a finite graph and if every finite subgraph of G
has a homomorphism to G'
, then there is a homomorphism from G
to G'
.
Kyle Miller (Nov 05 2021 at 19:22):
@Yaël Dillies How were you thinking about formalizing them?
Arthur Paulino (Nov 05 2021 at 19:28):
Kyle Miller said:
Arthur Paulino Something I'm looking forward to is having the fact that if every finite subgraph of a graph has a proper a-coloring, then (noncomputably) the graph has a proper a-coloring. This can be done using the idea for how docs#finset.all_card_le_bUnion_card_iff_exists_injective is proved from docs#finset.all_card_le_bUnion_card_iff_exists_injective'
Isn't that kind of trivial from our current definition of subgraph since a graph can be a subgraph of itself?
structure subgraph {V : Type u} (G : simple_graph V) :=
(verts : set V)
(adj : V → V → Prop)
(adj_sub : ∀ {v w : V}, adj v w → G.adj v w)
(edge_vert : ∀ {v w : V}, adj v w → v ∈ verts)
(symm : symmetric adj . obviously)
Kyle Miller (Nov 05 2021 at 19:31):
The graph can be infinite!
Arthur Paulino (Nov 05 2021 at 19:32):
Oh okay then, now that's interesting :rofl:
Arthur Paulino (Nov 05 2021 at 19:35):
I'm still in the process of struggling a bit with syntax. I will be chewing the idea of proper_coloring
that you suggested
Arthur Paulino (Nov 05 2021 at 19:36):
def proper_coloring.mk (f : V → α) (h : ∀ {v w : V}, G.adj v w → f v ≠ f w) :
G.proper_coloring α := ⟨f, @h⟩
what does @h
mean?
Kevin Buzzard (Nov 05 2021 at 19:37):
it turns the {} brackets into () ones
Kyle Miller (Nov 05 2021 at 19:38):
The problem is that with just h
, lean will fill in the v
and w
arguments with metavariables that it tries to solve for, but the second slot for the constructor is expecting a function that still takes those as arguments.
Kevin Buzzard (Nov 05 2021 at 19:38):
If you try it without the @ then presumably you get some error saying "I was expecting you to give me a function which starts by consuming two elements of V but this function h appears to start by eating a term of type G.adj _ _
"
Kyle Miller (Nov 05 2021 at 19:40):
I guess there's really not a need for these to be implicit arguments for this h
argument -- I was just following the types for rel_iso.mk
, which is what those angle brackets actually mean in this context.
Kyle Miller (Nov 05 2021 at 19:47):
This is a kind of fun thing you can do with proper_coloring.mk
, if you like pattern matching:
abbreviation proper_coloring (α : Type*) := G →g complete_graph α
variables {G} {α : Type*} (C : G.proper_coloring α)
@[pattern]
def proper_coloring.mk (f : V → α) (h : ∀ {v w : V}, G.adj v w → f v ≠ f w) :
G.proper_coloring α := ⟨f, @h⟩
def proper_coloring.color : Π (C : G.proper_coloring α), V → α
| (proper_coloring.mk f valid) := f
def proper_coloring.valid : Π (C : G.proper_coloring α) {v w : V} (h : G.adj v w), C.color v ≠ C.color w
| (proper_coloring.mk f valid) v w h := valid h
(I'm not suggesting you implement color
and valid
this way. It's just an example of what @[pattern]
can do and why it might be good to keep the arguments toh
as implicit.)
Arthur Paulino (Nov 05 2021 at 20:03):
It's still not clear to me what mk
is doing. It's a function that takes a coloring function and a hypothesis that this coloring function is a proper one and returns... what?
Kyle Miller (Nov 05 2021 at 20:04):
It's returning an "actual" proper coloring
Kyle Miller (Nov 05 2021 at 20:05):
which we defined to be a kind of graph homomorphism
Kyle Miller (Nov 05 2021 at 20:06):
Try looking at #check @vertex_coloring.mk
for your original definition:
structure vertex_coloring (color_type : Type u) :=
(color : V → color_type)
(valid : ∀ (v w : V), G.adj v w → color v ≠ color w)
Every structure defines a .mk
function (unless you override what it's called), which is the constructor.
Kyle Miller (Nov 05 2021 at 20:13):
An idea here is that what structure
does is have Lean create a new type from scratch with all the desired properties -- all these properties are what the type is. What we can instead do is take a pre-existing type and implementing the same properties, so this type now is the structure defined by vertex_coloring
. (There's one difference though, which is that record syntax doesn't work quite as well if you don't use structure
directly.)
Kyle Miller (Nov 05 2021 at 20:14):
The way dot notation works also helps keep up the illusion. We can write C.color
and it will look for proper_coloring.color C
first.
Arthur Paulino (Nov 05 2021 at 21:39):
The question I asked here was because I'm trying to arrive at a definition of the chromatic number. I did this:
open fintype
def proper_coloring.colors : set α := set.range C.color
def proper_coloring.card [fintype C.colors] : ℕ := card C.colors
But I'm very unsure this will have good consequences
Arthur Paulino (Nov 05 2021 at 21:41):
Maybe I should focus on some lemmas first
Kyle Miller (Nov 05 2021 at 21:48):
It's easier to just use fintype.card α
here, then later use some idea of restricting a coloring. It's ok if not all colors are used, since there's a smaller α
where it does use all the colors.
Arthur Paulino (Nov 05 2021 at 21:51):
Maybe some trick similar to the rel.image
from earlier?
Kyle Miller (Nov 05 2021 at 21:54):
A TODO in subgraph.lean
says "Images of graph homomorphisms as subgraphs." If we had that implemented, then you could do C.image
to get a subgraph, the vertex set of which is the set of used colors.
Kyle Miller (Nov 05 2021 at 21:55):
(The edges in the images represent colors that show up on endpoints of some edge in the original graph. If the image isn't a complete graph, you can merge colors whenever they're not adjacent, for example.)
Arthur Paulino (Nov 05 2021 at 23:27):
Got this so far:
import combinatorics.simple_graph.basic
universe u
namespace simple_graph
variables {V : Type u} (G : simple_graph V)
abbreviation proper_coloring (α : Type*) := G →g complete_graph α
variables {G} {α : Type*} (C : G.proper_coloring α)
def proper_coloring.color (v : V) : α := C v
lemma proper_coloring.valid (v w : V) (h : G.adj v w) : C.color v ≠ C.color w :=
C.map_rel h
def proper_coloring.mk (color : V → α) (h : ∀ {v w : V}, G.adj v w → color v ≠ color w) :
G.proper_coloring α := ⟨color, @h⟩
def proper_coloring.card [fintype α] : (G.proper_coloring α) → ℕ := fintype.card α
def proper_coloring.is_minimal [fintype α] : Prop :=
∀ C' : (G.proper_coloring α), C.card ≤ C'.card
end simple_graph
Yaël Dillies (Nov 05 2021 at 23:31):
Kyle Miller said:
Yaël Dillies How were you thinking about formalizing them?
I was thinking of defining them as graphs. But making them subgraphs does sound smart
Arthur Paulino (Nov 06 2021 at 00:14):
I found this: https://github.com/leanprover-community/mathlib/blob/simple_graph_coloring/src/combinatorics/simple_graph/coloring.lean
Kyle Miller (Nov 06 2021 at 00:18):
Maybe this would work:
import combinatorics.simple_graph.basic
import data.nat.lattice
universe u
namespace simple_graph
variables {V : Type u} (G : simple_graph V)
abbreviation proper_coloring (α : Type*) := G →g complete_graph α
variables {G} {α : Type*} (C : G.proper_coloring α)
def proper_coloring.color (v : V) : α := C v
lemma proper_coloring.valid (v w : V) (h : G.adj v w) : C.color v ≠ C.color w :=
C.map_rel h
@[pattern]
def proper_coloring.mk (color : V → α) (h : ∀ {v w : V}, G.adj v w → color v ≠ color w) :
G.proper_coloring α := ⟨color, @h⟩
/-- Whether a graph can be colored by at most `n` colors. -/
def colorable (G : simple_graph V) (n : ℕ) : Prop :=
∃ (α : Type*) [fintype α] (C : G.proper_coloring α),
by exactI fintype.card α ≤ n -- the "by exactI" trick is a way to get Lean to notice the `fintype` instance
/-- If `G` isn't colorable with finitely many colors, this will be 0. -/
noncomputable
def chromatic_number (G : simple_graph V) := Inf { n : ℕ | G.colorable n }
end simple_graph
Kyle Miller (Nov 06 2021 at 00:20):
The definition of colorable
is "There exists a finite color type of size at most n
for which there is a proper coloring of G
." The by exactI
is a bit unfortunate; try removing it to see the error it's avoiding.
Kyle Miller (Nov 06 2021 at 00:24):
By the way, here's some code from a while back about graphs and colorings (from before mathlib had anything about simple graphs at all): https://github.com/kmill/lean-graphcoloring/blob/master/src/graph.lean
Kyle Miller (Nov 06 2021 at 00:26):
Arthur Paulino said:
I found this: https://github.com/leanprover-community/mathlib/blob/simple_graph_coloring/src/combinatorics/simple_graph/coloring.lean
This is the one that got more developed: https://github.com/leanprover-community/mathlib/blob/simple_graph_matching/src/combinatorics/simple_graph/coloring.lean
It has stuff about homomorphisms, but colorings weren't homomorphisms themselves.
Kyle Miller (Nov 06 2021 at 00:28):
It also has this interesting "dual" version of colorings, which an indexed family of subsets of vertices (one subset per color), which allows you to work with partial colorings since not every vertex needs a color:
/-- This is an indexed family of disjoint subsets of the vertex type of `G` such that vertices in
the same set are not adjacent. This is similar to `subgraph.coloring`, however not every vertex
needs to be given a color. -/
@[ext]
structure coloring' {V : Type u} (G : simple_graph V) (β : Type v) :=
(color_set : β → set V)
(disjoint : ∀ (c c' : β), c ≠ c' → color_set c ∩ color_set c' = ∅)
(valid : ∀ (c : β) (v v' : V), v ∈ color_set c → v' ∈ color_set c → ¬G.adj v v')
Arthur Paulino (Nov 06 2021 at 00:46):
I found the idea of β-colorable
(would be α-colorable
in our case) pretty smart, which constrains the coloring by the elements of the codomain
Joanna Choules (Nov 06 2021 at 01:19):
Kyle Miller said:
I guess there's a more general result, which is if
G'
is a finite graph and if every finite subgraph ofG
has a homomorphism toG'
, then there is a homomorphism fromG
toG'
.
Do you happen to know if this is already being worked on currently? I thought I would try the proof myself out of curiosity, and I've been making reasonable headway, but I'm conscious of not duplicating effort.
Kyle Miller (Nov 06 2021 at 01:25):
@Joanna Choules I'm not aware of anyone working on it. (Are you using docs#nonempty_sections_of_fintype_inverse_system? or do you have another approach for the compactness argument?)
Kyle Miller (Nov 06 2021 at 01:40):
@Arthur Paulino This is a proof that that definition of colorability I gave is "reasonable" (in that it is equivalent to something that's more concrete):
/-- Whether a graph can be colored by at most `n` colors. -/
def colorable (G : simple_graph V) (n : ℕ) : Prop :=
∃ (α : Type*) [fintype α] (C : G.proper_coloring α),
by exactI fintype.card α ≤ n -- the "by exactI" trick is a way to get Lean to notice the `fintype` instance
def complete_graph.of_embedding {α β : Type*} (f : α ↪ β) : complete_graph α ↪g complete_graph β :=
{ to_fun := f,
inj' := f.inj',
map_rel_iff' := by simp }
lemma colorable_if_nonempty_fin_coloring (G : simple_graph V) (n : ℕ) :
G.colorable n ↔ nonempty (G.proper_coloring (fin n)) :=
begin
split,
{ rintro ⟨α, αf, C, h⟩,
tactic.unfreeze_local_instances,
let f := (fintype.equiv_fin α).to_embedding.trans (fin.cast_le h).to_embedding,
exact ⟨(complete_graph.of_embedding f).to_hom.comp C⟩, },
{ rintro ⟨C⟩,
exact ⟨ulift (fin n), by apply_instance,
(complete_graph.of_embedding equiv.ulift.symm.to_embedding).to_hom.comp C, by simp⟩, },
end
There are some universe variable issues that the proof needs to deal with, and it needs tactic.unfreeze_local_instances
to get Lean to notice the fintype
instance, but other than these technicalities the proof amounts to composing some graph homomorphisms.
Arthur Paulino (Nov 06 2021 at 01:44):
it's not accepting this line: let f := (fintype.equiv_fin α).to_embedding.trans (fin.cast_le h).to_embedding,
Arthur Paulino (Nov 06 2021 at 01:44):
type mismatch at application
(fintype.equiv_fin α).to_embedding.trans (fin.cast_le h).to_embedding
term
(fin.cast_le h).to_embedding
has type
fin C.card ↪ fin n : Type
but is expected to have type
fin (fintype.card α) ↪ ?m_1 : Sort (max 1 (imax 1 ?))
Arthur Paulino (Nov 06 2021 at 01:46):
Something to do with this definition that I added:
def proper_coloring.card [fintype α] : (G.proper_coloring α) → ℕ := fintype.card α
Joanna Choules (Nov 06 2021 at 01:52):
@Kyle Miller I plan to use the inverse system approach, yeah. So far I've mainly been working on the scaffolding (establishing a directed_order
, defining the system functor), but I think I know how I want the main thread of the proof to go through.
Arthur Paulino (Nov 06 2021 at 01:58):
@Kyle Miller do you think this can be useful?
lemma chromatic_number_is_minimal [fintype α] (h : C.is_minimal) :
chromatic_number G = C.card :=
begin
sorry
end
Arthur Paulino (Nov 06 2021 at 01:59):
def proper_coloring.is_minimal [fintype α] : Prop :=
∀ C' : (G.proper_coloring α), C.card ≤ C'.card
Kyle Miller (Nov 06 2021 at 02:03):
Is C.card
defined to be card (set.range C.color)
? I thought I saw you define it to be card α
, and this theorem wouldn't be true for that. (Maybe name the set C.support
and make the theorem be about card C.support
?) There will be some decidable
complaints from lean, so maybe add open_locale classical
to avoid this for now.
Arthur Paulino (Nov 06 2021 at 02:04):
This is the definition:
def proper_coloring.card [fintype α] : (G.proper_coloring α) → ℕ := fintype.card α
Kyle Miller (Nov 06 2021 at 02:07):
Yeah, that's giving you the number of colors, not the number of used colors.
Arthur Paulino (Nov 06 2021 at 02:08):
Ah, right
Kyle Miller (Nov 06 2021 at 02:09):
I think these are true:
def proper_coloring.chromatic_number_le [fintype α] (C : G.proper_coloring α) :
G.chromatic_number ≤ fintype.card α :=
sorry
def proper_coloring.zero_le_chromatic_number [nonempty V] [fintype α] (C : G.proper_coloring α) :
0 < G.chromatic_number :=
sorry
def proper_coloring.chromatic_number_minimal [fintype α] (C : G.proper_coloring α)
(h : ∀ (C' : G.proper_coloring α), set.range C'.color = set.univ) :
G.chromatic_number = fintype.card α :=
sorry
Arthur Paulino (Nov 06 2021 at 02:11):
Why do you use def
for these instead?
Arthur Paulino (Nov 06 2021 at 02:34):
@Kyle Miller I've uploaded everything to the branch graph-coloring-homomorphism
Kyle Miller (Nov 06 2021 at 02:35):
That was a mistake -- they were supposed to be lemma
s
Kyle Miller (Nov 06 2021 at 02:42):
This is potentially a useful variation:
lemma colorable_if_nonempty_fin_coloring' (G : simple_graph V) (n : ℕ) :
G.colorable n ↔ ∃ (C : G.proper_coloring ℕ), ∀ v, C.color v < n :=
begin
rw colorable_if_nonempty_fin_coloring,
split,
{ rintro ⟨C⟩,
let f := complete_graph.of_embedding (fin.coe_embedding n).to_embedding,
use f.to_hom.comp C,
cases C with color valid,
intro v,
exact fin.is_lt (color v), },
{ rintro ⟨C, Cf⟩,
refine ⟨⟨λ v, ⟨C.color v, Cf v⟩, _⟩⟩,
rintro v w hvw,
simp only [complete_graph_eq_top, top_adj, subtype.mk_eq_mk, ne.def],
exact C.valid v w hvw, },
end
Arthur Paulino (Nov 06 2021 at 02:43):
I'm gonna include it in the branch too
Arthur Paulino (Nov 06 2021 at 02:45):
Arthur Paulino (Nov 06 2021 at 02:45):
I'm gonna try to pick up that other task to redesign matchings as subgraphs. I think it's simpler :sweat_smile:
Kevin Buzzard (Nov 06 2021 at 07:43):
I'm quite happy for this discussion to be going on here but isn't there an entire graph theory stream where you can sort your ideas into different threads which perhaps will be helpful for later reference? Perhaps @Joanna Choules and @Arthur Paulino don't know about it -- it's hidden by default. It would be really great to see some more progress in this area, I think there's plenty to be done and I think we now understand basic design decisions (which held up graph theory for so long) much better so there's now a chance for the area to grow
Joanna Choules (Nov 06 2021 at 09:37):
Yeah, I just wanted to do a quick status check before ploughing on. If I run into any implementation questions I'll thread them separately.
Arthur Paulino (Nov 07 2021 at 14:23):
@Kevin Buzzard How to create different threads inside a stream? Do you mean we can have the following hierarchy:
maths > graph theory > coloring
?
Reid Barton (Nov 07 2021 at 14:29):
There is a separate stream (same level as maths
), see #graph theory
Arthur Paulino (Nov 08 2021 at 15:20):
Posting here too since not everyone has subscribed to #graph theory
It's open for reviews: https://github.com/leanprover-community/mathlib/pull/10210
Last updated: Dec 20 2023 at 11:08 UTC