Theory Undirected_Graph_Theory.Bipartite_Graphs
theory Bipartite_Graphs imports Undirected_Graph_Walks
begin
section ‹Bipartite Graphs ›
text ‹An introductory library for reasoning on bipartite graphs.›
subsection ‹Bipartite Set Up ›
text ‹All "edges", i.e. pairs, between any two sets ›
definition all_bi_edges :: "'a set ⇒ 'a set ⇒ 'a edge set" where
"all_bi_edges X Y ≡ mk_edge ` (X × Y)"
lemma all_bi_edges_alt:
assumes "X ∩ Y = {}"
shows "all_bi_edges X Y = {e . card e = 2 ∧ e ∩ X ≠ {} ∧ e ∩ Y ≠ {}}"
unfolding all_bi_edges_def
proof (intro subset_antisym subsetI)
fix e assume "e ∈ mk_edge ` (X × Y)"
then obtain v1 v2 where "e = { v1, v2}" and "v1 ∈ X" and "v2 ∈ Y"
by auto
then show "e ∈ {e. card e = 2 ∧ e ∩ X ≠ {} ∧ e ∩ Y ≠ {}}" using assms
using card_2_iff by blast
next
fix e' assume assm: "e' ∈ {e. card e = 2 ∧ e ∩ X ≠ {} ∧ e ∩ Y ≠ {}}"
then obtain v1 where v1in: "v1 ∈ e'" and "v1 ∈ X"
by blast
moreover obtain v2 where v2in: "v2 ∈ e'" and "v2 ∈ Y" using assm by blast
then have ne: "v1 ≠ v2"
using assms calculation(2) by blast
have "card e' = 2" using assm by blast
have "{v1, v2} ⊆ e'" using v1in v2in by blast
then have "e' = {v1, v2}" using assm v1in v2in
by (metis (no_types, opaque_lifting) ‹card e' = 2› card_2_iff' insertCI ne subsetI subset_antisym)
then show "e' ∈ mk_edge ` (X × Y)"
by (simp add: ‹v2 ∈ Y› calculation(2) in_mk_edge_img)
qed
lemma all_bi_edges_alt2: "all_bi_edges X Y = {{x, y} | x y. x ∈ X ∧ y ∈ Y }"
unfolding all_bi_edges_def
proof (intro subset_antisym subsetI)
fix x assume "x ∈ mk_edge ` (X × Y)"
then obtain a b where "(a, b) ∈ (X × Y)" and xeq: "x = mk_edge (a, b) " by blast
then show "x ∈ {{x, y} |x y. x ∈ X ∧ y ∈ Y}"
by auto
next
fix x assume "x ∈ {{x, y} |x y. x ∈ X ∧ y ∈ Y}"
then obtain a b where xeq: "x = {a, b}" and "a ∈ X" and "b ∈ Y"
by blast
then have "(a, b) ∈ (X × Y)" by auto
then show "x ∈ mk_edge ` (X × Y)" using in_mk_edge_img xeq by metis
qed
lemma all_bi_edges_wf: "e ∈ all_bi_edges X Y ⟹ e ⊆ X ∪ Y"
by (auto simp add: all_bi_edges_alt2)
lemma all_bi_edges_2: "X ∩ Y = {} ⟹ e ∈ all_bi_edges X Y ⟹ card e = 2"
using card_2_iff by (auto simp add: all_bi_edges_alt2)
lemma all_bi_edges_main: "X ∩ Y = {} ⟹ all_bi_edges X Y ⊆ all_edges (X ∪ Y)"
unfolding all_edges_def using all_bi_edges_wf all_bi_edges_2 by blast
lemma all_bi_edges_finite: "finite X ⟹ finite Y ⟹ finite (all_bi_edges X Y)"
by (simp add: all_bi_edges_def)
lemma all_bi_edges_not_ssX: "X ∩ Y = {} ⟹ e ∈ all_bi_edges X Y ⟹ ¬ e ⊆ X"
by (auto simp add: all_bi_edges_alt)
lemma all_bi_edges_sym: "all_bi_edges X Y = all_bi_edges Y X"
by (auto simp add: all_bi_edges_alt2)
lemma all_bi_edges_not_ssY: "X ∩ Y = {} ⟹ e ∈ all_bi_edges X Y ⟹ ¬ e ⊆ Y"
by (auto simp add: all_bi_edges_alt)
lemma card_all_bi_edges:
assumes "finite X" "finite Y"
assumes "X ∩ Y = {}"
shows "card (all_bi_edges X Y) = card X * card Y"
proof -
have "card (all_bi_edges X Y) = card (X × Y)"
unfolding all_bi_edges_def using inj_on_mk_edge assms card_image by blast
thus ?thesis using card_cartesian_product by auto
qed
lemma (in sgraph) all_edges_between_bi_subset: "mk_edge ` all_edges_between X Y ⊆ all_bi_edges X Y"
by (auto simp: all_edges_between_def all_bi_edges_def)
subsection ‹ Bipartite Graph Locale ›
text ‹For reasoning purposes, it is useful to explicitly label the two sets of vertices as X and Y.
These are parameters in the locale›
locale bipartite_graph = graph_system +
fixes X Y :: "'a set"
assumes partition: "partition_on V {X, Y}"
assumes ne: "X ≠ Y"
assumes edge_betw: "e ∈ E ⟹ e ∈ all_bi_edges X Y"
begin
lemma part_intersect_empty: "X ∩ Y = {}"
using partition_onD2 partition disjointD ne
by blast
lemma X_not_empty: "X ≠ {}"
using partition partition_onD3 by auto
lemma Y_not_empty: "Y ≠ {}"
using partition partition_onD3 by auto
lemma XY_union: "X ∪ Y = V"
using partition partition_onD1 by auto
lemma card_edges_two: "e ∈ E ⟹ card e = 2"
using edge_betw all_bi_edges_alt part_intersect_empty by auto
lemma partitions_ss: "X ⊆ V" "Y ⊆ V"
using XY_union by auto
end
text ‹ By definition, we say an edge must be between X and Y, i.e. contains two vertices ›
sublocale bipartite_graph ⊆ sgraph
using card_edges_two by (unfold_locales)
context bipartite_graph
begin
abbreviation "density ≡ edge_density X Y"
lemma bipartite_sym: "bipartite_graph V E Y X"
using partition ne edge_betw all_bi_edges_sym
by (unfold_locales) (auto simp add: insert_commute)
lemma X_verts_not_adj:
assumes "x1 ∈ X" "x2 ∈ X"
shows "¬ vert_adj x1 x2"
proof (rule ccontr, simp add: vert_adj_def)
assume "{x1, x2} ∈ E"
then have "¬ {x1, x2} ⊆ X"
using all_bi_edges_not_ssX edge_betw part_intersect_empty by auto
then show False using assms by auto
qed
lemma Y_verts_not_adj:
assumes "y1 ∈ Y" "y2 ∈ Y"
shows "¬ vert_adj y1 y2"
proof -
interpret sym: bipartite_graph V E Y X using bipartite_sym by simp
show ?thesis using sym.X_verts_not_adj
by (simp add: assms(1) assms(2))
qed
lemma X_vert_adj_Y: "x ∈X ⟹ vert_adj x y ⟹ y ∈ Y"
using X_verts_not_adj XY_union vert_adj_imp_inV by blast
lemma Y_vert_adj_X: "y ∈Y ⟹ vert_adj y x ⟹ x ∈ X"
using Y_verts_not_adj XY_union vert_adj_imp_inV by blast
lemma neighbors_ss_eq_neighborhoodX: "v ∈ X ⟹ neighborhood v = neighbors_ss v Y"
unfolding neighborhood_def neighbors_ss_def
by(auto simp add: X_vert_adj_Y vert_adj_imp_inV)
lemma neighbors_ss_eq_neighborhoodY: "v ∈ Y ⟹ neighborhood v = neighbors_ss v X"
unfolding neighborhood_def neighbors_ss_def
by(auto simp add: Y_vert_adj_X vert_adj_imp_inV)
lemma neighborhood_subset_oppX: "v ∈ X ⟹ neighborhood v ⊆ Y"
using neighbors_ss_eq_neighborhoodX neighbors_ss_def by auto
lemma neighborhood_subset_oppY: "v ∈ Y ⟹ neighborhood v ⊆ X"
using neighbors_ss_eq_neighborhoodY neighbors_ss_def by auto
lemma degree_neighbors_ssX: "v ∈ X ⟹ degree v = card (neighbors_ss v Y)"
using neighbors_ss_eq_neighborhoodX alt_deg_neighborhood by auto
lemma degree_neighbors_ssY: "v ∈ Y ⟹ degree v = card (neighbors_ss v X)"
using neighbors_ss_eq_neighborhoodY alt_deg_neighborhood by auto
definition is_bicomplete:: "bool" where
"is_bicomplete ≡ E = all_bi_edges X Y"
lemma edge_betw_indiv:
assumes "e ∈ E"
obtains x y where "x ∈ X ∧ y ∈ Y ∧ e = {x, y}"
proof -
have "e ∈ {{x, y} | x y. x ∈ X ∧ y ∈ Y }"
using edge_betw all_bi_edges_alt2 assms by blast
thus ?thesis
using that by auto
qed
lemma edges_between_equals_edge_set: "mk_edge ` (all_edges_between X Y) = E"
by (simp add: all_edges_between_set, intro subset_antisym subsetI, auto) (metis edge_betw_indiv)
text ‹ Lemmas for reasoning on walks and paths in a bipartite graph ›
lemma walk_alternates:
assumes "is_walk w"
assumes "Suc i < length w" "i ≥ 0"
shows "w ! i ∈ X ⟷ w ! (i + 1) ∈ Y"
proof -
have "{w ! i, w ! (i +1)} ∈ E" using is_walk_index assms by auto
then show ?thesis
using X_vert_adj_Y not_vert_adj Y_vert_adj_X vert_adj_sym by blast
qed
text ‹A useful reasoning pattern to mimic "wlog" statements for properties that are symmetric
is to interpret the symmetric bipartite graph and then directly apply the lemma proven earlier›
lemma walk_alternates_sym:
assumes "is_walk w"
assumes "Suc i < length w" "i ≥ 0"
shows "w ! i ∈ Y ⟷ w ! (i + 1) ∈ X"
proof -
interpret sym: bipartite_graph V E Y X using bipartite_sym by simp
show ?thesis using sym.walk_alternates assms by simp
qed
lemma walk_length_even:
assumes "is_walk w"
assumes "hd w ∈ X" and "last w ∈ X"
shows "even (walk_length w)"
using assms
proof (induct "length w" arbitrary: w rule: nat_induct2)
case 0
then show ?case by (auto simp add: is_walk_def)
next
case 1
then have "walk_length w = 0" using walk_length_conv by auto
then show ?case by simp
next
case (step n)
then show ?case proof (cases "n = 0")
case True
then have "length w = 2" using step by simp
then have "hd w ∈ X ⟹ last w ∈ Y" using walk_alternates hd_conv_nth last_conv_nth
by (metis add_0 add_diff_cancel_right' less_2_cases_iff list.size(3) nat_1_add_1 step.prems(1)
zero_le zero_neq_numeral)
then show ?thesis
using part_intersect_empty step.prems(2) step.prems(3) by blast
next
case False
have IH: "(⋀w. n = length w ⟹ is_walk w ⟹ hd w ∈ X ⟹ last w ∈ X ⟹ even (walk_length w))"
using step by simp
obtain w1 w2 where weq: "w = w1@w2" and w1: "w1 = take n w" and w2: "w2 = drop n w"
by simp
then have ne: "w1 ≠ []" using False is_walk_not_empty2 step.prems(1) by fastforce
then have w1_walk: "is_walk w1" using w1 is_walk_take False
by (metis nat_le_linear neq0_conv step.prems(1) take_all)
have hdw1: "hd w1 ∈ X" using step ne weq by auto
then have w1n: "length w1 = n" using step length_take w1 by auto
then have "length w2 = 2" using step length_drop
by (simp add: w2)
have "last w = w ! (n + 1)" using step last_conv_nth is_walk_not_empty
by (metis add.left_commute diff_add_inverse nat_1_add_1)
then have "w ! n ∈ Y" using step by (simp add: walk_alternates_sym)
then have "w ! (n - 1) ∈ X" using False walk_alternates step by simp
then have "last w1 ∈ X" using step last_conv_nth[of w1] ne w1n
by (metis last_list_update list_update_id take_update_swap w1)
then have "even (walk_length w1)" using w1_walk w1n hdw1 IH[of w1] by simp
then have "even (walk_length w1 + 2)" by simp
then show ?thesis using walk_length_conv weq step
by (simp add: False w1n)
qed
qed
lemma walk_length_even_sym:
assumes "is_walk w"
assumes "hd w ∈ Y"
assumes "last w ∈ Y"
shows "even (walk_length w)"
proof -
interpret sym: bipartite_graph V E Y X using bipartite_sym by simp
show ?thesis using sym.walk_length_even assms by auto
qed
lemma walk_length_odd:
assumes "is_walk w"
assumes "hd w ∈ X" and "last w ∈ Y"
shows "odd (walk_length w)"
using assms
proof (cases "length w ≥ 2")
case True
then have hdin: "hd (tl w) ∈ Y" using walk_alternates hd_conv_nth
by (metis (mono_tags, lifting) Suc_1 Suc_less_eq2 assms(1) assms(2) is_walk_not_empty2 is_walk_tl
le_neq_implies_less le_numeral_extra(3) length_greater_0_conv less_Suc_eq nth_tl
numeral_1_eq_Suc_0 numerals(1) plus_nat.add_0)
have w: "is_walk (tl w)" using assms True is_walk_tl by auto
have last: "last (tl w) ∈ Y" using assms(3) by (simp add: is_walk_not_empty last_tl w)
then have ev: "even (walk_length (tl w))" using hdin w walk_length_even_sym[of "tl w"] by auto
then have "walk_length w = walk_length (tl w) + 1" using True walk_length_conv by auto
then show ?thesis using ev by simp
next
case False
have "length w ≠ 0" using is_walk_not_empty assms by simp
then have "length w = 1" using False by linarith
then have "hd w = last w"
using ‹length w ≠ 0› hd_conv_nth last_conv_nth by fastforce
then have "hd w ∈ X ⟹ last w ∉ Y" using part_intersect_empty by auto
then show ?thesis using assms by simp
qed
lemma walk_length_odd_sym:
assumes "is_walk w"
assumes "hd w ∈ Y" and "last w ∈ X"
shows "odd (walk_length w)"
proof -
interpret sym: bipartite_graph V E Y X using bipartite_sym by simp
show ?thesis using assms sym.walk_length_odd by simp
qed
lemma walk_length_even_iff:
assumes "is_walk w"
shows "even (walk_length w) ⟷ (hd w ∈ X ∧ last w ∈ X) ∨ (hd w ∈ Y ∧ last w ∈ Y)"
proof (intro iffI)
assume ev: "even (walk_length w)"
show "hd w ∈ X ∧ last w ∈ X ∨ hd w ∈ Y ∧ last w ∈ Y"
proof (rule ccontr)
assume "¬ ((hd w ∈ X ∧ last w ∈ X) ∨ (hd w ∈ Y ∧ last w ∈ Y))"
then have "(hd w ∉ X ∨ last w ∉ X) ∧ (hd w ∉ Y ∨ last w ∉ Y)" by simp
then have "(hd w ∈ Y ∨ last w ∈ Y) ∧ (hd w ∈ X ∨ last w ∈ X)" using part_intersect_empty
using XY_union assms is_walk_wf_hd is_walk_wf_last by auto
then have split: "(hd w ∈ X ∧ last w ∈ Y) ∨ (hd w ∈ Y ∧ last w ∈ X)"
using part_intersect_empty by auto
have o1: "(hd w ∈ X ∧ last w ∈ Y) ⟹ odd (walk_length w)" using walk_length_odd assms by auto
have "(hd w ∈ Y ∧ last w ∈ X) ⟹ odd (walk_length w)" using walk_length_odd_sym assms by auto
then show False using split ev o1 by auto
qed
next
show "(hd w ∈ X ∧ last w ∈ X) ∨ (hd w ∈ Y ∧ last w ∈ Y) ⟹ even (walk_length w)"
using walk_length_even walk_length_even_sym assms by auto
qed
lemma walk_length_odd_iff:
assumes "is_walk w"
shows "odd (walk_length w) ⟷ (hd w ∈ X ∧ last w ∈ Y) ∨ (hd w ∈ Y ∧ last w ∈ X)"
proof (intro iffI)
assume o: "odd (walk_length w)"
show "(hd w ∈ X ∧ last w ∈ Y) ∨ (hd w ∈ Y ∧ last w ∈ X)"
proof (rule ccontr)
assume "¬ ((hd w ∈ X ∧ last w ∈ Y) ∨ (hd w ∈ Y ∧ last w ∈ X))"
then have "(hd w ∉ X ∨ last w ∉ Y) ∧ (hd w ∉ Y ∨ last w ∉ X)" by simp
then have "(hd w ∈ Y ∨ last w ∈ X) ∧ (hd w ∈ X ∨ last w ∈ Y)" using part_intersect_empty
using XY_union assms is_walk_wf_hd is_walk_wf_last by auto
then have split: "(hd w ∈ X ∧ last w ∈ X) ∨ (hd w ∈ Y ∧ last w ∈ Y)"
using part_intersect_empty by auto
have e1: "(hd w ∈ X ∧ last w ∈ X) ⟹ even (walk_length w)" using walk_length_even assms by auto
have "(hd w ∈ Y ∧ last w ∈ Y) ⟹ even (walk_length w)" using walk_length_even_sym assms by auto
then show False using split o e1 by auto
qed
next
show "(hd w ∈ X ∧ last w ∈ Y) ∨ (hd w ∈ Y ∧ last w ∈ X) ⟹ odd (walk_length w)"
using walk_length_odd walk_length_odd_sym assms by auto
qed
text ‹ Classic basic theorem that a bipartite graph must not have any cycles with an odd length ›
lemma no_odd_cycles:
assumes "is_walk w"
assumes "odd (walk_length w)"
shows "¬ is_cycle w"
proof -
have "(hd w ∈ X ∧ last w ∈ Y) ∨ (hd w ∈ Y ∧ last w ∈ X)" using assms walk_length_odd_iff by auto
then have "hd w ≠ last w" using part_intersect_empty by auto
thus ?thesis using is_cycle_def is_closed_walk_def by simp
qed
end
text ‹ A few properties rely on cardinality definitions that require the vertex sets to be finite ›
locale fin_bipartite_graph = bipartite_graph + fin_graph_system
begin
lemma fin_bipartite_sym: "fin_bipartite_graph V E Y X"
by (intro_locales) (simp add: bipartite_sym bipartite_graph.axioms(2))
lemma partitions_finite: "finite X" "finite Y"
using partitions_ss finite_subset finV by auto
lemma card_edges_between_set: "card (all_edges_between X Y) = card E"
proof -
have "card (all_edges_between X Y) = card (mk_edge ` (all_edges_between X Y))"
using inj_on_mk_edge using partitions_finite card_image
by (metis inj_on_mk_edge part_intersect_empty)
then show ?thesis by (simp add: edges_between_equals_edge_set)
qed
lemma density_simp: "density = card (E) / ((card X) * (card Y))"
unfolding edge_density_def using card_edges_between_set by auto
lemma edge_size_degree_sumY: "card E = (∑y ∈ Y . degree y)"
proof -
have "(∑y ∈ Y . degree y) = (∑y ∈ Y . card(neighbors_ss y X))"
using degree_neighbors_ssY by (simp)
also have "... = card (all_edges_between X Y)"
using card_all_edges_betw_neighbor
by (metis card_all_edges_between_commute partitions_finite(1) partitions_finite(2))
finally show ?thesis
by (simp add: card_edges_between_set)
qed
lemma edge_size_degree_sumX: "card E = (∑y ∈ X . degree y)"
proof -
interpret sym: fin_bipartite_graph V E Y X
using fin_bipartite_sym by simp
show ?thesis using sym.edge_size_degree_sumY by simp
qed
end
end