open classical

variable (r : Prop)
variable (α : Type)
variables (p q : α → Prop)
variable a : α
variable w : α

example (α : Type) (p q : α → Prop) : (∀ x : α, p x ∧ q x) ↔ (∀ x : α, p x) ∧ (∀ x : α, q x) :=
  iff.intro
  (fun h : ∀ x : α, p x ∧ q x ,
    and.intro
      (fun z : α , show p z ,from and.left (h z))
      (fun z : α , show q z ,from and.right (h z))
  )
  (fun h : (∀ x : α, p x) ∧ (∀ x : α, q x) ,
    (fun z : α ,
      and.intro ( and.left h z ) ( and.right h z)
    )
  )

example : (∀ x: α, p x → q x) → (∀ x: α, p x) → (∀ x: α, q x) :=
  (fun h1 : ∀ x: α, p x → q x ,
    (fun h2 : ∀ x: α, p x ,
      (fun z: α ,  h1 z (h2 z)
      )
    )
  )

example : (∀ x : α, p x) ∨ (∀ x : α, q x) → ∀ x : α, p x ∨ q x :=
  (fun h :(∀ x : α, p x) ∨ (∀ x : α, q x) ,
    or.elim
      h
      (fun h1 : (∀ x : α, p x) , (fun z : α , or.intro_left (q z) (h1 z) ) )
      (fun h2 : (∀ x : α, q x) , (fun z : α , or.intro_right (p z) (h2 z) ) )
  )

example : α → ( (∀ x : α, r) ↔ r) :=
  (fun a : α ,
    show (∀ x : α, r) ↔ r, from
    iff.intro
        (fun h1 : (∀ x : α, r),
            show r, from
            h1 a
        )
        (fun h2 : r,
            show (∀ x : α, r), from
            (fun b : α, h2)
        )
  )

example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r :=
  iff.intro
    (fun h : (∀ x, p x ∨ r),
      or.elim
        (em r)
        (fun hr : r,
          or.intro_right (∀ x, p x) hr
        )
        (fun hnr : ¬r,
          have h2 : (∀ z, p z) :=
            (fun z : α,
            have h1 : (p z ∨ r) := (h z),
            or.elim
              h1
              (fun hpz : (p z), hpz)
              (fun hr : r, absurd hr hnr)
            ),
          or.intro_left r h2
        )
    )
    (fun h : (∀ x, p x) ∨ r,
        (fun z : α,
          or.elim
            h
            (fun hl : (∀ x, p x), or.intro_left r (hl z))
            (fun hr : r, or.intro_right (p z) hr)
        )
    )

example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
  iff.intro
      (fun h : (∀ x, r → p x),
        (fun hr : r,
          (fun z : α,
            (h z) hr
          )
        )
      )
      (fun h : r → ∀ x, p x,
        (fun z : α,
          (fun hr : r,
            h hr z
          )
        )
      )

example : (∃ x : α, r) → r :=
  (fun h : (∃ x : α, r),
    exists.elim
      h
      (fun w : α,
        (fun hr : r,
          hr
        )
      )
  )

example : r → (∃ x : α, r) :=
  (fun hr : r,
    exists.intro a hr
  )

example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
  iff.intro
    (fun h :(∃ x, p x ∧ r),
      exists.elim
        h
        (fun w,
          (fun hw : (p w ∧ r),
            have hr : r :=  and.right hw,
            have hl : (∃ x, p x) := exists.intro w (and.left hw),
            and.intro hl hr
          )
        )
    )
    (fun h : ((∃ x, p x) ∧ r),
      exists.elim
        (and.left h)
        (fun w,
          (fun hw: (p w),
            exists.intro w (and.intro hw (and.right h))
          )
        )
    )

example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
  iff.intro
    (fun h : (∃ x, p x ∨ q x),
      exists.elim
        h
        (fun w,
          (fun hw : (p w ∨ q w),
            or.elim
              hw
              (fun hpw : (p w),
                have hepx : (∃ x, p x) := exists.intro w hpw,
                or.intro_left (∃ x, q x) hepx
              )
              (fun hqw : (q w),
                have heqx : (∃ x, q x) := exists.intro w hqw,
                or.intro_right (∃ x, p x) heqx
              )
          )
        )
    )
    (fun h : (∃ x, p x) ∨ (∃ x, q x),
      or.elim
        h
        (fun hpx : (∃ x, p x),
          exists.elim
            hpx
            (fun w,
              (fun hw : p w,
                have h_pw_or_qw : (p w) ∨ (q w) := or.intro_left (q w) hw,
                exists.intro
                  w
                  h_pw_or_qw
              )
            )
        )
        (fun hqx : (∃ x, q x),
          exists.elim
            hqx
            (fun w,
              (fun hw : q w,
                have h_pw_or_qw : (p w) ∨ (q w) := or.intro_right (p w) hw,
                exists.intro
                  w
                  h_pw_or_qw
              )
            )
        )
    )

theorem forall_px_eq_not_exist_not_px  {α : Type} {p : α → Prop} : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
  iff.intro
    (fun h : (∀ x, p x),
      ( fun hx : (∃ x, ¬ p x),
        exists.elim
          hx
          (fun w,
            have hpw : p w := h w,
            (fun hw :  ¬ (p w),
              absurd hpw hw
            )
          )
      )
    )
    (fun h : ¬ (∃ x, ¬ p x),
      (fun z : α,
        by_contradiction
          (fun hnpz : ¬ p z,
            absurd
              (exists.intro z hnpz)
              h
          )
      )
    )

theorem not_exists_p_x_imp_forall_not_p_x {α : Type} {p : α → Prop} :  ¬ (∃ x, p x) -> (∀ x, ¬ p x) :=
  (fun h : ¬ (∃ x, p x),
    (fun z,
      (fun hpz : p z,
        have h3 :  (∃ x, p x) :=
          exists.intro
            z
            hpz,
        h h3
      )
    )
  )

theorem dne {p : Prop} (h : ¬¬p) : p :=
  by_cases
    (fun h1 : p , h1)
    (fun h1 : ¬p , absurd h1 h)

example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
  iff.intro
  (fun h: (∃ x, p x) ,
      by_contradiction
        (fun h2 : ¬¬ (∀ x, ¬ p x),
          have h1 : (∀ x, ¬ p x) := dne h2,
          exists.elim
            h
            (fun w,
              (fun hw : (p w),
                absurd hw (h1 w)
              )
            )
        )
  )
  (fun h : ¬ (∀ x, ¬ p x),
    by_contradiction
      (fun h1:  ¬ (∃ x, p x),
        have h2 : (∀ x, ¬ p x) := not_exists_p_x_imp_forall_not_p_x h1,
        absurd h2 h
      )
  )

example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
  iff.intro
    (fun h : ¬ ∃ x, p x,
        not_exists_p_x_imp_forall_not_p_x h
    )
    (fun h : ∀ x, ¬ p x,
        (fun h2 : ∃ x, p x,
          exists.elim
            h2
            (fun w,
              (fun hw: p w,
                absurd hw (h w)
              )
            )
        )
    )

theorem not_forall_iff_not_exists {α : Type} {p : α → Prop} : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
  iff.intro
    (fun h : ¬ ∀ x, p x,
      by_contradiction
        (fun h_tofalsify : ¬ (∃ x, ¬ p x),
          have h2 : ∀ x, p x := (iff.elim_right forall_px_eq_not_exist_not_px) h_tofalsify,
          absurd h2 h
        )
      )
    (fun h : ∃ x, ¬ p x,
        exists.elim
          h
          (fun w ,
            (fun hnw :  ¬ p w,
              (fun  hallp : ∀ x, p x,
                absurd (hallp w) hnw
              )
            )
          )
    )

example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
  iff.intro
    (fun h : (∀ x, p x → r),
      (fun h2 : ∃ x, p x,
        exists.elim
          h2
          (fun w,
            (fun hw: p w,
              (h w) hw
            )
          )
      )
    )
    (fun h : (∃ x, p x) → r,
      (fun z : α,
        (fun hpz : p z,
          h(
            exists.intro
            z
            hpz
          )
        )
      )
    )

example : (∃ x, p x → r) ↔ (∀ x, p x) → r :=
  iff.intro
    (fun h : ∃ x, p x → r,
        exists.elim
          h
          (fun w ,
            (fun hw : p w → r,
              (fun h2 : ∀ x, p x,
                hw (h2 w)
              )
            )
          )
    )
    (fun h : (∀ x, p x) → r,
      by_cases
        (fun h1 : (∀ x, p x),
          have hr : r := h h1,
          exists.intro
            a
            (fun hp : p a,
              hr
            )
        )
        (fun hn1 : ¬ (∀ x, p x),
          have h3 : (∃ x, ¬ p x) := (iff.elim_left not_forall_iff_not_exists) hn1,
          exists.elim
            h3
            (fun w ,
              (fun hw : ¬ p w,
                exists.intro
                  w
                  (fun hpw : p w,
                    absurd hpw hw
                  )
              )
            )
        )
    )

example : (∃ x, r → p x) ↔ (r → ∃ x, p x) :=
  iff.intro
    (fun h : (∃ x, r → p x),
        exists.elim
          h
          (fun w,
            (fun hw : r -> p w ,
              (fun hr : r,
                exists.intro
                  w
                  (hw hr)
              )
            )
          )
    )
    (fun h : (r → ∃ x, p x),
      by_cases
        (fun hr : r,
          have h2 : ∃ x, p x := h hr,
          exists.elim
            h2
            (fun w,
              (fun hw : p w,
                exists.intro
                  w
                  (fun hr : r, hw)
              )
            )
        )
        (fun hnr : ¬ r,
          exists.intro
            a
            (fun hr : r,
              absurd hr hnr
            )
        )
    )


/-
Copyright (c) 2022 Kyle Miller. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kyle Miller, Vincent Beffara
-/
import combinatorics.simple_graph.connectivity
import data.nat.lattice

/-!
# Graph metric

This module defines the `simple_graph.dist` function, which takes
pairs of vertices to the length of the shortest walk between them.

## Main definitions

- `simple_graph.dist` is the graph metric.

## Todo

- Provide an additional computable version of `simple_graph.dist`
  for when `G` is connected.

- Evaluate `nat` vs `enat` for the codomain of `dist`, or potentially
  having an additional `edist` when the objects under consideration are
  disconnected graphs.

- When directed graphs exist, a directed notion of distance,
  likely `enat`-valued.

## Tags

graph metric, distance

-/

namespace simple_graph
variables {V : Type*} (G : simple_graph V)

/-! ## Metric -/

/-- The distance between two vertices is the length of the shortest walk between them.
If no such walk exists, this uses the junk value of `0`. -/
noncomputable
def dist (u v : V) : ℕ := Inf (set.range (walk.length : G.walk u v → ℕ))

variables {G}

protected
lemma reachable.exists_walk_of_dist {u v : V} (hr : G.reachable u v) :
  ∃ (p : G.walk u v), p.length = G.dist u v :=
nat.Inf_mem (set.range_nonempty_iff_nonempty.mpr hr)

protected
lemma connected.exists_walk_of_dist (hconn : G.connected) (u v : V) :
  ∃ (p : G.walk u v), p.length = G.dist u v :=
(hconn u v).exists_walk_of_dist

lemma dist_le {u v : V} (p : G.walk u v) : G.dist u v ≤ p.length := nat.Inf_le ⟨p, rfl⟩

@[simp]
lemma dist_eq_zero_iff_eq_or_not_reachable {u v : V} : G.dist u v = 0 ↔ u = v ∨ ¬ G.reachable u v :=
by simp [dist, nat.Inf_eq_zero, reachable]

lemma dist_self {v : V} : dist G v v = 0 := by simp

protected
lemma reachable.dist_eq_zero_iff {u v : V} (hr : G.reachable u v) :
  G.dist u v = 0 ↔ u = v := by simp [hr]

protected
lemma reachable.pos_dist_of_ne {u v : V} (h : G.reachable u v) (hne : u ≠ v) : 0 < G.dist u v :=
nat.pos_of_ne_zero (by simp [h, hne])

protected
lemma connected.dist_eq_zero_iff (hconn : G.connected) {u v : V} :
  G.dist u v = 0 ↔ u = v := by simp [hconn u v]

protected
lemma connected.pos_dist_of_ne {u v : V} (hconn : G.connected) (hne : u ≠ v) : 0 < G.dist u v :=
nat.pos_of_ne_zero (by simp [hconn.dist_eq_zero_iff, hne])

lemma dist_eq_zero_of_not_reachable {u v : V} (h : ¬ G.reachable u v) : G.dist u v = 0 :=
by simp [h]

lemma nonempty_of_pos_dist {u v : V} (h : 0 < G.dist u v) :
  (set.univ : set (G.walk u v)).nonempty :=
by simpa [set.range_nonempty_iff_nonempty, set.nonempty_iff_univ_nonempty]
     using nat.nonempty_of_pos_Inf h

protected
lemma connected.dist_triangle (hconn : G.connected) {u v w : V} :
  G.dist u w ≤ G.dist u v + G.dist v w :=
begin
  obtain ⟨p, hp⟩ := hconn.exists_walk_of_dist u v,
  obtain ⟨q, hq⟩ := hconn.exists_walk_of_dist v w,
  rw [← hp, ← hq, ← walk.length_append],
  apply dist_le,
end

private

lemma dist_comm {u v : V} : G.dist u v = G.dist v u :=
begin
  by_cases h : G.reachable u v,
  { apply le_antisymm (dist_comm_aux h) (dist_comm_aux h.symm), },
  { have h' : ¬ G.reachable v u := λ h', absurd h'.symm h,
    simp [h, h', dist_eq_zero_of_not_reachable], },
end

end simple_graph


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Mario Carneiro, Johan Commelin, Amelia Livingston, Anne Baanen

! This file was ported from Lean 3 source module ring_theory.localization.fraction_ring
! leanprover-community/mathlib commit 831c494092374cfe9f50591ed0ac81a25efc5b86
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Algebra.Algebra.Tower
import Mathlib.RingTheory.Localization.Basic

/-!
# Fraction ring / fraction field Frac(R) as localization

## Main definitions

 * `IsFractionRing R K` expresses that `K` is a field of fractions of `R`, as an abbreviation of
   `IsLocalization (NonZeroDivisors R) K`

## Main results

 * `IsFractionRing.field`: a definition (not an instance) stating the localization of an integral
   domain `R` at `R \ {0}` is a field
 * `Rat.isFractionRing` is an instance stating `ℚ` is the field of fractions of `ℤ`

## Implementation notes

See `RingTheory/Localization/Basic.lean` for a design overview.

## Tags
localization, ring localization, commutative ring localization, characteristic predicate,
commutative ring, field of fractions
-/

variable (R : Type _) [CommRing R] {M : Submonoid R} (S : Type _) [CommRing S]

variable [Algebra R S] {P : Type _} [CommRing P]

variable {A : Type _} [CommRing A] [IsDomain A] (K : Type _)

-- TODO: should this extend `Algebra` instead of assuming it?
/-- `IsFractionRing R K` states `K` is the field of fractions of an integral domain `R`. -/
abbrev IsFractionRing [CommRing K] [Algebra R K] :=
  IsLocalization (nonZeroDivisors R) K
#align is_fraction_ring IsFractionRing

/-- The cast from `Int` to `Rat` as a `FractionRing`. -/
instance Rat.isFractionRing : IsFractionRing ℤ ℚ where
  map_units' := by
    rintro ⟨x, hx⟩
    rw [mem_nonZeroDivisors_iff_ne_zero] at hx
    simpa only [eq_intCast, isUnit_iff_ne_zero, Int.cast_eq_zero, Ne.def, Subtype.coe_mk] using hx
  surj':= by
    rintro ⟨n, d, hd, h⟩
    refine' ⟨⟨n, ⟨d, _⟩⟩, Rat.mul_den_eq_num⟩
    rw [mem_nonZeroDivisors_iff_ne_zero, Int.coe_nat_ne_zero_iff_pos]
    exact Nat.zero_lt_of_ne_zero hd
  eq_iff_exists' := by
    intro x y
    rw [eq_intCast, eq_intCast, Int.cast_inj]
    apply Iff.intro
    · rintro rfl
      use 1
    · rintro ⟨⟨c, hc⟩, h⟩
      apply mul_left_cancel₀ _ h
      rwa [mem_nonZeroDivisors_iff_ne_zero] at hc
#align rat.is_fraction_ring Rat.isFractionRing

namespace IsFractionRing

open IsLocalization

variable {R K}

section CommRing

variable [CommRing K] [Algebra R K] [IsFractionRing R K] [Algebra A K] [IsFractionRing A K]

theorem to_map_eq_zero_iff {x : R} : algebraMap R K x = 0 ↔ x = 0 :=
  IsLocalization.to_map_eq_zero_iff _ le_rfl
#align is_fraction_ring.to_map_eq_zero_iff IsFractionRing.to_map_eq_zero_iff

variable (R K)

protected theorem injective : Function.Injective (algebraMap R K) :=
  IsLocalization.injective _ (le_of_eq rfl)
#align is_fraction_ring.injective IsFractionRing.injective

variable {R K}

@[norm_cast, simp]
-- Porting note: using `↑` didn't work, so I needed to explicitly put in the cast myself
theorem coe_inj {a b : R} : (Algebra.cast a : K) = Algebra.cast b ↔ a = b :=
  (IsFractionRing.injective R K).eq_iff
#align is_fraction_ring.coe_inj IsFractionRing.coe_inj

instance (priority := 100) [NoZeroDivisors K] : NoZeroSMulDivisors R K :=
  NoZeroSMulDivisors.of_algebraMap_injective <| IsFractionRing.injective R K

protected theorem to_map_ne_zero_of_mem_nonZeroDivisors [Nontrivial R] {x : R}
    (hx : x ∈ nonZeroDivisors R) : algebraMap R K x ≠ 0 :=
  IsLocalization.to_map_ne_zero_of_mem_nonZeroDivisors _ le_rfl hx
#align is_fraction_ring.to_map_ne_zero_of_mem_non_zero_divisors IsFractionRing.to_map_ne_zero_of_mem_nonZeroDivisors

variable (A)

/-- A `CommRing` `K` which is the localization of an integral domain `R` at `R - {0}` is an
integral domain. -/
protected theorem isDomain : IsDomain K :=
  isDomain_of_le_nonZeroDivisors _ (le_refl (nonZeroDivisors A))
#align is_fraction_ring.is_domain IsFractionRing.isDomain

attribute [local instance] Classical.decEq

/-- The inverse of an element in the field of fractions of an integral domain. -/
-- Porting note: Had to replace `irreducible_def` with the `@[irreducible]` attribute.
@[irreducible] protected noncomputable def inv (z : K) : K :=
  if h : z = 0 then 0
  else
    mk' K ↑(sec (nonZeroDivisors A) z).2
      ⟨(sec _ z).1,
        mem_nonZeroDivisors_iff_ne_zero.2 fun h0 =>
          h <| eq_zero_of_fst_eq_zero (sec_spec (nonZeroDivisors A) z) h0⟩
#align is_fraction_ring.inv IsFractionRing.inv

protected theorem mul_inv_cancel (x : K) (hx : x ≠ 0) : x * IsFractionRing.inv A x = 1 := by
  rw [IsFractionRing.inv, dif_neg hx, ←
    IsUnit.mul_left_inj
      (map_units K
        ⟨(sec _ x).1,
          mem_nonZeroDivisors_iff_ne_zero.2 fun h0 =>
            hx <| eq_zero_of_fst_eq_zero (sec_spec (nonZeroDivisors A) x) h0⟩),
    one_mul, mul_assoc]
  rw [mk'_spec, ← eq_mk'_iff_mul_eq]
  exact (mk'_sec _ x).symm
#align is_fraction_ring.mul_inv_cancel IsFractionRing.mul_inv_cancel

/-- A `CommRing` `K` which is the localization of an integral domain `R` at `R - {0}` is a field.
See note [reducible non-instances]. -/
@[reducible]
noncomputable def toField : Field K :=
  { IsFractionRing.isDomain A, inferInstanceAs (CommRing K) with
    inv := IsFractionRing.inv A
    mul_inv_cancel := IsFractionRing.mul_inv_cancel A
    inv_zero := by
      change IsFractionRing.inv A (0 : K) = 0
      rw [IsFractionRing.inv]
      exact dif_pos rfl }
#align is_fraction_ring.to_field IsFractionRing.toField

end CommRing

variable {B : Type _} [CommRing B] [IsDomain B] [Field K] {L : Type _} [Field L] [Algebra A K]
  [IsFractionRing A K] {g : A →+* L}

theorem mk'_mk_eq_div {r s} (hs : s ∈ nonZeroDivisors A) :
    mk' K r ⟨s, hs⟩ = algebraMap A K r / algebraMap A K s :=
  mk'_eq_iff_eq_mul.2 <|
    (div_mul_cancel (algebraMap A K r)
        (IsFractionRing.to_map_ne_zero_of_mem_nonZeroDivisors hs)).symm
#align is_fraction_ring.mk'_mk_eq_div IsFractionRing.mk'_mk_eq_div

@[simp]
theorem mk'_eq_div {r} (s : nonZeroDivisors A) : mk' K r s = algebraMap A K r / algebraMap A K s :=
  mk'_mk_eq_div s.2
#align is_fraction_ring.mk'_eq_div IsFractionRing.mk'_eq_div

theorem div_surjective (z : K) :
    ∃ (x y : A)(hy : y ∈ nonZeroDivisors A), algebraMap _ _ x / algebraMap _ _ y = z :=
  let ⟨x, ⟨y, hy⟩, h⟩ := mk'_surjective (nonZeroDivisors A) z
  ⟨x, y, hy, by rwa [mk'_eq_div] at h⟩
#align is_fraction_ring.div_surjective IsFractionRing.div_surjective

theorem isUnit_map_of_injective (hg : Function.Injective g) (y : nonZeroDivisors A) :
    IsUnit (g y) :=
  IsUnit.mk0 (g y) <|
    show g.toMonoidWithZeroHom y ≠ 0 from map_ne_zero_of_mem_nonZeroDivisors g hg y.2
#align is_fraction_ring.is_unit_map_of_injective IsFractionRing.isUnit_map_of_injective

@[simp]
theorem mk'_eq_zero_iff_eq_zero [Algebra R K] [IsFractionRing R K] {x : R} {y : nonZeroDivisors R} :
    mk' K x y = 0 ↔ x = 0 := by
  refine' ⟨fun hxy => _, fun h => by rw [h, mk'_zero]⟩
  · simp_rw [mk'_eq_zero_iff, mul_left_coe_nonZeroDivisors_eq_zero_iff] at hxy
    exact (exists_const _).mp hxy
#align is_fraction_ring.mk'_eq_zero_iff_eq_zero IsFractionRing.mk'_eq_zero_iff_eq_zero

theorem mk'_eq_one_iff_eq {x : A} {y : nonZeroDivisors A} : mk' K x y = 1 ↔ x = y := by
  refine' ⟨_, fun hxy => by rw [hxy, mk'_self']⟩
  · intro hxy
    have hy : (algebraMap A K) ↑y ≠ (0 : K) :=
      IsFractionRing.to_map_ne_zero_of_mem_nonZeroDivisors y.property
    rw [IsFractionRing.mk'_eq_div, div_eq_one_iff_eq hy] at hxy
    exact IsFractionRing.injective A K hxy
#align is_fraction_ring.mk'_eq_one_iff_eq IsFractionRing.mk'_eq_one_iff_eq

open Function

/-- Given an integral domain `A` with field of fractions `K`,
and an injective ring hom `g : A →+* L` where `L` is a field, we get a
field hom sending `z : K` to `g x * (g y)⁻¹`, where `(x, y) : A × (NonZeroDivisors A)` are
such that `z = f x * (f y)⁻¹`. -/
noncomputable def lift (hg : Injective g) : K →+* L :=
  IsLocalization.lift fun y : nonZeroDivisors A => isUnit_map_of_injective hg y
#align is_fraction_ring.lift IsFractionRing.lift

/-- Given an integral domain `A` with field of fractions `K`,
and an injective ring hom `g : A →+* L` where `L` is a field,
the field hom induced from `K` to `L` maps `x` to `g x` for all
`x : A`. -/
@[simp]
theorem lift_algebraMap (hg : Injective g) (x) : lift hg (algebraMap A K x) = g x :=
  lift_eq _ _
#align is_fraction_ring.lift_algebra_map IsFractionRing.lift_algebraMap

/-- Given an integral domain `A` with field of fractions `K`,
and an injective ring hom `g : A →+* L` where `L` is a field,
field hom induced from `K` to `L` maps `f x / f y` to `g x / g y` for all
`x : A, y ∈ NonZeroDivisors A`. -/
theorem lift_mk' (hg : Injective g) (x) (y : nonZeroDivisors A) : lift hg (mk' K x y) = g x / g y :=
  by simp only [mk'_eq_div, map_div₀, lift_algebraMap]
#align is_fraction_ring.lift_mk' IsFractionRing.lift_mk'

/-- Given integral domains `A, B` with fields of fractions `K`, `L`
and an injective ring hom `j : A →+* B`, we get a field hom
sending `z : K` to `g (j x) * (g (j y))⁻¹`, where `(x, y) : A × (NonZeroDivisors A)` are
such that `z = f x * (f y)⁻¹`. -/
noncomputable def map {A B K L : Type _} [CommRing A] [CommRing B] [IsDomain B] [CommRing K]
    [Algebra A K] [IsFractionRing A K] [CommRing L] [Algebra B L] [IsFractionRing B L] {j : A →+* B}
    (hj : Injective j) : K →+* L :=
  IsLocalization.map L j
    (show nonZeroDivisors A ≤ (nonZeroDivisors B).comap j from
      nonZeroDivisors_le_comap_nonZeroDivisors_of_injective j hj)
#align is_fraction_ring.map IsFractionRing.map

/-- Given integral domains `A, B` and localization maps to their fields of fractions
`f : A →+* K, g : B →+* L`, an isomorphism `j : A ≃+* B` induces an isomorphism of
fields of fractions `K ≃+* L`. -/
noncomputable def fieldEquivOfRingEquiv [Algebra B L] [IsFractionRing B L] (h : A ≃+* B) :
    K ≃+* L :=
  ringEquivOfRingEquiv K L h
    (by
      ext b
      show b ∈ h.toEquiv '' _ ↔ _
      erw [h.toEquiv.image_eq_preimage, Set.preimage, Set.mem_setOf_eq,
        mem_nonZeroDivisors_iff_ne_zero, mem_nonZeroDivisors_iff_ne_zero]
      exact h.symm.map_ne_zero_iff)
#align is_fraction_ring.field_equiv_of_ring_equiv IsFractionRing.fieldEquivOfRingEquiv

protected theorem nontrivial (R S : Type _) [CommRing R] [Nontrivial R] [CommRing S] [Algebra R S]
    [IsFractionRing R S] : Nontrivial S := by
  apply nontrivial_of_ne
  intro h
  apply @zero_ne_one R
  exact
    IsLocalization.injective S (le_of_eq rfl)
      (((algebraMap R S).map_zero.trans h).trans (algebraMap R S).map_one.symm)
#align is_fraction_ring.nontrivial IsFractionRing.nontrivial

end IsFractionRing

variable (A)

/-- The fraction ring of a commutative ring `R` as a quotient type.

We instantiate this definition as generally as possible, and assume that the
commutative ring `R` is an integral domain only when this is needed for proving.
-/
@[reducible]
def FractionRing :=
  Localization (nonZeroDivisors R)
#align fraction_ring FractionRing

namespace FractionRing

instance unique [Subsingleton R] : Unique (FractionRing R) :=
  Localization.instUniqueLocalizationToCommMonoid
#align fraction_ring.unique FractionRing.unique

instance [Nontrivial R] : Nontrivial (FractionRing R) :=
  ⟨⟨(algebraMap R _) 0, (algebraMap _ _) 1, fun H =>
      zero_ne_one (IsLocalization.injective _ le_rfl H)⟩⟩

/-- Porting note: if the fields of this instance are explicitly defined as they were
in mathlib3, the last instance in this file suffers a TC timeout -/
noncomputable instance : Field (FractionRing A) := IsFractionRing.toField A

@[simp]
theorem mk_eq_div {r s} :
    (Localization.mk r s : FractionRing A) =
      (algebraMap _ _ r / algebraMap A _ s : FractionRing A) :=
  by rw [Localization.mk_eq_mk', IsFractionRing.mk'_eq_div]
#align fraction_ring.mk_eq_div FractionRing.mk_eq_div

noncomputable instance [IsDomain R] [Field K] [Algebra R K] [NoZeroSMulDivisors R K] :
    Algebra (FractionRing R) K :=
  RingHom.toAlgebra (IsFractionRing.lift (NoZeroSMulDivisors.algebraMap_injective R _))

-- Porting note: had to fill in the `_` by hand for this instance
instance [IsDomain R] [Field K] [Algebra R K] [NoZeroSMulDivisors R K] :
    IsScalarTower R (FractionRing R) K :=
  IsScalarTower.of_algebraMap_eq fun x =>
    (IsFractionRing.lift_algebraMap (NoZeroSMulDivisors.algebraMap_injective R K ) x).symm

/-- Given an integral domain `A` and a localization map to a field of fractions
`f : A →+* K`, we get an `A`-isomorphism between the field of fractions of `A` as a quotient
type and `K`. -/
noncomputable def algEquiv (K : Type _) [Field K] [Algebra A K] [IsFractionRing A K] :
    FractionRing A ≃ₐ[A] K :=
  Localization.algEquiv (nonZeroDivisors A) K
#align fraction_ring.alg_equiv FractionRing.algEquiv

instance [Algebra R A] [NoZeroSMulDivisors R A] : NoZeroSMulDivisors R (FractionRing A) := by
  apply NoZeroSMulDivisors.of_algebraMap_injective
  rw [IsScalarTower.algebraMap_eq R A]
  apply Function.Injective.comp (NoZeroSMulDivisors.algebraMap_injective A (FractionRing A))
    (NoZeroSMulDivisors.algebraMap_injective R A)

end FractionRing


/-
Copyright (c) 2021 Praneeth Kolichala. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Praneeth Kolichala
-/
import topology.homotopy.basic
import topology.constructions
import topology.homotopy.path
import category_theory.groupoid
import topology.homotopy.fundamental_groupoid
import topology.category.Top.limits
import category_theory.limits.preserves.shapes.products

/-!
# Product of homotopies

In this file, we introduce definitions for the product of
homotopies. We show that the products of relative homotopies
are still relative homotopies. Finally, we specialize to the case
of path homotopies, and provide the definition for the product of path classes.
We show various lemmas associated with these products, such as the fact that
path products commute with path composition, and that projection is the inverse
of products.

## Definitions
### General homotopies
- `continuous_map.homotopy.pi homotopies`: Let f and g be a family of functions
  indexed on I, such that for each i ∈ I, fᵢ and gᵢ are maps from A to Xᵢ.
  Let `homotopies` be a family of homotopies from fᵢ to gᵢ for each i.
  Then `homotopy.pi homotopies` is the canonical homotopy
  from ∏ f to ∏ g, where ∏ f is the product map from A to Πi, Xᵢ,
  and similarly for ∏ g.

- `continuous_map.homotopy_rel.pi homotopies`: Same as `continuous_map.homotopy.pi`, but
  all homotopies are done relative to some set S ⊆ A.

- `continuous_map.homotopy.prod F G` is the product of homotopies F and G,
   where F is a homotopy between f₀ and f₁, G is a homotopy between g₀ and g₁.
   The result F × G is a homotopy between (f₀ × g₀) and (f₁ × g₁).
   Again, all homotopies are done relative to S.

- `continuous_map.homotopy_rel.prod F G`: Same as `continuous_map.homotopy.prod`, but
  all homotopies are done relative to some set S ⊆ A.

### Path products
- `path.homotopic.pi` The product of a family of path classes, where a path class is an equivalence
  class of paths up to path homotopy.

- `path.homotopic.prod` The product of two path classes.

## Fundamental groupoid preserves products
  - `fundamental_groupoid_functor.pi_iso` An isomorphism between Π i, (π Xᵢ) and π (Πi, Xᵢ), whose
    inverse is precisely the product of the maps π (Π i, Xᵢ) → π (Xᵢ), each induced by
    the projection in `Top` Π i, Xᵢ → Xᵢ.

  - `fundamental_groupoid_functor.prod_iso` An isomorphism between πX × πY and π (X × Y), whose
    inverse is precisely the product of the maps π (X × Y) → πX and π (X × Y) → Y, each induced by
    the projections X × Y → X and X × Y → Y

  - `fundamental_groupoid_functor.preserves_product` A proof that the fundamental groupoid functor
    preserves all products.
-/

noncomputable theory

namespace continuous_map
open continuous_map

section pi

variables {I : Type*} {X : I → Type*} [∀i, topological_space (X i)]
  {A : Type*} [topological_space A]
  {f g : Π i, C(A, X i)} {S : set A}

/-- The product homotopy of `homotopies` between functions `f` and `g` -/
@[simps]
def homotopy.pi (homotopies : Π i, homotopy (f i) (g i)) :
        homotopy (pi f) (pi g) :=
{ to_fun := λ t i, homotopies i t,
  to_fun_zero := by { intro t, ext i, simp only [pi_eval, homotopy.apply_zero], },
  to_fun_one := by { intro t, ext i, simp only [pi_eval, homotopy.apply_one], } }

/-- The relative product homotopy of `homotopies` between functions `f` and `g` -/
@[simps]
def homotopy_rel.pi (homotopies : Π i : I, homotopy_rel (f i) (g i) S) :
  homotopy_rel (pi f) (pi g) S :=
{ prop' :=
  begin
    intros t x hx,
    dsimp only [coe_mk, pi_eval, to_fun_eq_coe, homotopy_with.coe_to_continuous_map],
    simp only [function.funext_iff, ← forall_and_distrib],
    intro i,
    exact (homotopies i).prop' t x hx,
  end,
  ..(homotopy.pi (λ i, (homotopies i).to_homotopy)), }

end pi

section prod

variables {α β : Type*} [topological_space α] [topological_space β]
  {A : Type*} [topological_space A]
  {f₀ f₁ : C(A, α)} {g₀ g₁ : C(A, β)} {S : set A}

/-- The product of homotopies `F` and `G`,
  where `F` takes `f₀` to `f₁`  and `G` takes `g₀` to `g₁` -/
@[simps]
def homotopy.prod (F : homotopy f₀ f₁) (G : homotopy g₀ g₁) :
  homotopy (prod_mk f₀ g₀) (prod_mk f₁ g₁) :=
{ to_fun := λ t, (F t, G t),
  to_fun_zero := by { intro, simp only [prod_eval, homotopy.apply_zero], },
  to_fun_one := by { intro, simp only [prod_eval, homotopy.apply_one], } }

/-- The relative product of homotopies `F` and `G`,
  where `F` takes `f₀` to `f₁`  and `G` takes `g₀` to `g₁` -/
@[simps]
def homotopy_rel.prod (F : homotopy_rel f₀ f₁ S) (G : homotopy_rel g₀ g₁ S) :
  homotopy_rel (prod_mk f₀ g₀) (prod_mk f₁ g₁) S :=
{ prop' :=
  begin
    intros t x hx,
    have hF := F.prop' t x hx,
    have hG := G.prop' t x hx,
    simp only [coe_mk, prod_eval, prod.mk.inj_iff, homotopy.prod] at hF hG ⊢,
    exact ⟨⟨hF.1, hG.1⟩, ⟨hF.2, hG.2⟩⟩,
  end,
  ..(homotopy.prod F.to_homotopy G.to_homotopy) }

end prod
end continuous_map

namespace path.homotopic
local attribute [instance] path.homotopic.setoid
local infix ` ⬝ `:70 := quotient.comp

section pi

variables {ι : Type*} {X : ι → Type*} [∀ i, topological_space (X i)]
  {as bs cs : Π i, X i}

/-- The product of a family of path homotopies. This is just a specialization of `homotopy_rel` -/
def pi_homotopy (γ₀ γ₁ : Π i, path (as i) (bs i)) (H : ∀ i, path.homotopy (γ₀ i) (γ₁ i)) :
  path.homotopy (path.pi γ₀) (path.pi γ₁) := continuous_map.homotopy_rel.pi H

/-- The product of a family of path homotopy classes -/
def pi (γ : Π i, path.homotopic.quotient (as i) (bs i)) : path.homotopic.quotient as bs :=
(quotient.map path.pi
  (λ x y hxy, nonempty.map (pi_homotopy x y) (classical.nonempty_pi.mpr hxy)))
  (quotient.choice γ)

lemma pi_lift (γ : Π i, path (as i) (bs i)) : path.homotopic.pi (λ i, ⟦γ i⟧) = ⟦path.pi γ⟧ :=
by { unfold pi, simp, }

/-- Composition and products commute.
  This is `path.trans_pi_eq_pi_trans` descended to path homotopy classes -/
lemma comp_pi_eq_pi_comp
  (γ₀ : Π i, path.homotopic.quotient (as i) (bs i))
  (γ₁ : Π i, path.homotopic.quotient (bs i) (cs i)) :
  pi γ₀ ⬝ pi γ₁ = pi (λ i, γ₀ i ⬝ γ₁ i) :=
begin
  apply quotient.induction_on_pi γ₁,
  apply quotient.induction_on_pi γ₀,
  intros,
  simp only [pi_lift],
  rw [← path.homotopic.comp_lift,
      path.trans_pi_eq_pi_trans,
      ← pi_lift],
  refl,
end

/-- Abbreviation for projection onto the ith coordinate -/
@[reducible]
def proj (i : ι) (p : path.homotopic.quotient as bs) : path.homotopic.quotient (as i) (bs i) :=
p.map_fn ⟨_, continuous_apply i⟩

/-- Lemmas showing projection is the inverse of pi -/
@[simp] lemma proj_pi (i : ι) (paths : Π i, path.homotopic.quotient (as i) (bs i)) :
  proj i (pi paths) = paths i :=
begin
  apply quotient.induction_on_pi paths,
  intro, unfold proj,
  rw [pi_lift, ← path.homotopic.map_lift],
  congr, ext, refl,
end

@[simp] lemma pi_proj (p : path.homotopic.quotient as bs) : pi (λ i, proj i p) = p :=
begin
  apply quotient.induction_on p,
  intro, unfold proj,
  simp_rw ← path.homotopic.map_lift,
  rw pi_lift,
  congr, ext, refl,
end

end pi

section prod

variables {α β : Type*} [topological_space α] [topological_space β]
  {a₁ a₂ a₃ : α} {b₁ b₂ b₃ : β}
  {p₁ p₁' : path a₁ a₂} {p₂ p₂' : path b₁ b₂}
  (q₁ : path.homotopic.quotient a₁ a₂) (q₂ : path.homotopic.quotient b₁ b₂)

/-- The product of homotopies h₁ and h₂.
    This is `homotopy_rel.prod` specialized for path homotopies. -/
def prod_homotopy (h₁ : path.homotopy p₁ p₁') (h₂ : path.homotopy p₂ p₂') :
  path.homotopy (p₁.prod p₂) (p₁'.prod p₂') := continuous_map.homotopy_rel.prod h₁ h₂

/-- The product of path classes q₁ and q₂. This is `path.prod` descended to the quotient -/
def prod (q₁ : path.homotopic.quotient a₁ a₂) (q₂ : path.homotopic.quotient b₁ b₂) :
  path.homotopic.quotient (a₁, b₁) (a₂, b₂) :=
quotient.map₂ path.prod (λ p₁ p₁' h₁ p₂ p₂' h₂, nonempty.map2 prod_homotopy h₁ h₂) q₁ q₂

variables (p₁ p₁' p₂ p₂')
lemma prod_lift : prod ⟦p₁⟧ ⟦p₂⟧ = ⟦p₁.prod p₂⟧ := rfl

variables (r₁ : path.homotopic.quotient a₂ a₃) (r₂ : path.homotopic.quotient b₂ b₃)
/-- Products commute with path composition.
    This is `trans_prod_eq_prod_trans` descended to the quotient.-/
lemma comp_prod_eq_prod_comp : (prod q₁ q₂) ⬝ (prod r₁ r₂) = prod (q₁ ⬝ r₁) (q₂ ⬝ r₂) :=
begin
  apply quotient.induction_on₂ q₁ q₂,
  apply quotient.induction_on₂ r₁ r₂,
  intros,
  simp only [prod_lift, ← path.homotopic.comp_lift, path.trans_prod_eq_prod_trans],
end

variables {c₁ c₂ : α × β}

/-- Abbreviation for projection onto the left coordinate of a path class -/
@[reducible]
def proj_left (p : path.homotopic.quotient c₁ c₂) : path.homotopic.quotient c₁.1 c₂.1 :=
p.map_fn ⟨_, continuous_fst⟩

/-- Abbreviation for projection onto the right coordinate of a path class -/
@[reducible]
def proj_right (p : path.homotopic.quotient c₁ c₂) : path.homotopic.quotient c₁.2 c₂.2 :=
p.map_fn ⟨_, continuous_snd⟩

/-- Lemmas showing projection is the inverse of product -/
@[simp] lemma proj_left_prod : proj_left (prod q₁ q₂) = q₁ :=
begin
  apply quotient.induction_on₂ q₁ q₂,
  intros p₁ p₂,
  unfold proj_left,
  rw [prod_lift, ← path.homotopic.map_lift],
  congr, ext, refl,
end

@[simp] lemma proj_right_prod : proj_right (prod q₁ q₂) = q₂ :=
begin
  apply quotient.induction_on₂ q₁ q₂,
  intros p₁ p₂,
  unfold proj_right,
  rw [prod_lift, ← path.homotopic.map_lift],
  congr, ext, refl,
end

@[simp] lemma prod_proj_left_proj_right (p : path.homotopic.quotient (a₁, b₁) (a₂, b₂))
  : prod (proj_left p) (proj_right p) = p :=
begin
  apply quotient.induction_on p,
  intro p',
  unfold proj_left, unfold proj_right,
  simp only [← path.homotopic.map_lift, prod_lift],
  congr, ext; refl,
end

end prod

end path.homotopic

namespace fundamental_groupoid_functor

open_locale fundamental_groupoid

universes u

section pi

variables {I : Type u} (X : I → Top.{u})

/--
The projection map Π i, X i → X i induces a map π(Π i, X i) ⟶ π(X i).
-/
def proj (i : I) : (πₓ (Top.of (Π i, X i))).α ⥤ (πₓ (X i)).α := πₘ ⟨_, continuous_apply i⟩

/-- The projection map is precisely path.homotopic.proj interpreted as a functor -/
@[simp] lemma proj_map (i : I) (x₀ x₁ : (πₓ (Top.of (Π i, X i))).α) (p : x₀ ⟶ x₁) :
  (proj X i).map p = (@path.homotopic.proj _ _ _ _ _ i p) := rfl

/--
The map taking the pi product of a family of fundamental groupoids to the fundamental
groupoid of the pi product. This is actually an isomorphism (see `pi_iso`)
-/
@[simps]
def pi_to_pi_Top : (Π i, (πₓ (X i)).α) ⥤ (πₓ (Top.of (Π i, X i))).α :=
{ obj := λ g, g,
  map := λ v₁ v₂ p, path.homotopic.pi p,
  map_id' :=
  begin
    intro x,
    change path.homotopic.pi (λ i, 𝟙 (x i)) = _,
    simp only [fundamental_groupoid.id_eq_path_refl, path.homotopic.pi_lift],
    refl,
  end,
  map_comp' := λ x y z f g, (path.homotopic.comp_pi_eq_pi_comp f g).symm, }

/--
Shows `pi_to_pi_Top` is an isomorphism, whose inverse is precisely the pi product
of the induced projections. This shows that `fundamental_groupoid_functor` preserves products.
-/
@[simps]
def pi_iso : category_theory.Groupoid.of (Π i : I, (πₓ (X i)).α) ≅ (πₓ (Top.of (Π i, X i))) :=
{ hom := pi_to_pi_Top X,
  inv := category_theory.functor.pi' (proj X),
  hom_inv_id' :=
  begin
    change pi_to_pi_Top X ⋙ (category_theory.functor.pi' (proj X)) = 𝟭 _,
    apply category_theory.functor.ext; intros,
    { ext, simp, }, { refl, },
  end,
  inv_hom_id' :=
  begin
    change (category_theory.functor.pi' (proj X)) ⋙ pi_to_pi_Top X = 𝟭 _,
    apply category_theory.functor.ext; intros,
    { suffices : path.homotopic.pi ((category_theory.functor.pi' (proj X)).map f) = f, { simpa, },
      change (category_theory.functor.pi' (proj X)).map f
        with λ i, (category_theory.functor.pi' (proj X)).map f i,
      simp, }, { refl, }
  end }

section preserves
open category_theory

/-- Equivalence between the categories of cones over the objects `π Xᵢ` written in two ways -/
def cone_discrete_comp : limits.cone (discrete.functor X ⋙ π) ≌
  limits.cone (discrete.functor (λ i, πₓ (X i))) :=
limits.cones.postcompose_equivalence (discrete.comp_nat_iso_discrete X π)

lemma cone_discrete_comp_obj_map_cone :
  (cone_discrete_comp X).functor.obj ((π).map_cone (Top.pi_fan X))
  = limits.fan.mk (πₓ (Top.of (Π i, X i))) (proj X) := rfl

/-- This is `pi_iso.inv` as a cone morphism (in fact, isomorphism) -/
def pi_Top_to_pi_cone : (limits.fan.mk (πₓ (Top.of (Π i, X i))) (proj X)) ⟶
  Groupoid.pi_limit_fan (λ i : I, (πₓ (X i))) := { hom := category_theory.functor.pi' (proj X) }

instance : is_iso (pi_Top_to_pi_cone X) :=
begin
  haveI : is_iso (pi_Top_to_pi_cone X).hom := (infer_instance : is_iso (pi_iso X).inv),
  exact limits.cones.cone_iso_of_hom_iso (pi_Top_to_pi_cone X),
end

/-- The fundamental groupoid functor preserves products -/
def preserves_product : limits.preserves_limit (discrete.functor X) π :=
begin
  apply limits.preserves_limit_of_preserves_limit_cone (Top.pi_fan_is_limit X),
  apply (limits.is_limit.of_cone_equiv (cone_discrete_comp X)).to_fun,
  simp only [cone_discrete_comp_obj_map_cone],
  apply limits.is_limit.of_iso_limit _ (as_iso (pi_Top_to_pi_cone X)).symm,
  exact (Groupoid.pi_limit_cone _).is_limit,
end

end preserves

end pi

section prod

variables (A B : Top.{u})

/-- The induced map of the left projection map X × Y → X -/
def proj_left : (πₓ (Top.of (A × B))).α ⥤ (πₓ A).α := πₘ ⟨_, continuous_fst⟩

/-- The induced map of the right projection map X × Y → Y -/
def proj_right : (πₓ (Top.of (A × B))).α ⥤ (πₓ B).α := πₘ ⟨_, continuous_snd⟩

@[simp] lemma proj_left_map (x₀ x₁ : (πₓ (Top.of (A × B))).α) (p : x₀ ⟶ x₁) :
  (proj_left A B).map p = path.homotopic.proj_left p := rfl

@[simp] lemma proj_right_map (x₀ x₁ : (πₓ (Top.of (A × B))).α) (p : x₀ ⟶ x₁) :
  (proj_right A B).map p = path.homotopic.proj_right p := rfl

/--
The map taking the product of two fundamental groupoids to the fundamental groupoid of the product
of the two topological spaces. This is in fact an isomorphism (see `prod_iso`).
-/
@[simps]
def prod_to_prod_Top : (πₓ A).α × (πₓ B).α ⥤ (πₓ (Top.of (A × B))).α :=
{ obj := λ g, g,
  map := λ x y p, match x, y, p with
    | (x₀, x₁), (y₀, y₁), (p₀, p₁) := path.homotopic.prod p₀ p₁
  end,
  map_id' :=
  begin
    rintro ⟨x₀, x₁⟩,
    simp only [category_theory.prod_id, fundamental_groupoid.id_eq_path_refl],
    unfold_aux, rw path.homotopic.prod_lift, refl,
  end,
  map_comp' := λ x y z f g, match x, y, z, f, g with
    | (x₀, x₁), (y₀, y₁), (z₀, z₁), (f₀, f₁), (g₀, g₁) :=
    (path.homotopic.comp_prod_eq_prod_comp f₀ f₁ g₀ g₁).symm
  end }

/--
Shows `prod_to_prod_Top` is an isomorphism, whose inverse is precisely the product
of the induced left and right projections.
-/
@[simps]
def prod_iso : category_theory.Groupoid.of ((πₓ A).α × (πₓ B).α) ≅ (πₓ (Top.of (A × B))) :=
{ hom := prod_to_prod_Top A B,
  inv := (proj_left A B).prod' (proj_right A B),
  hom_inv_id' :=
  begin
    change prod_to_prod_Top A B ⋙ ((proj_left A B).prod' (proj_right A B)) = 𝟭 _,
    apply category_theory.functor.hext, { intros, ext; simp; refl, },
    rintros ⟨x₀, x₁⟩ ⟨y₀, y₁⟩ ⟨f₀, f₁⟩,
    have := and.intro (path.homotopic.proj_left_prod f₀ f₁) (path.homotopic.proj_right_prod f₀ f₁),
    simpa,
  end,
  inv_hom_id' :=
  begin
    change ((proj_left A B).prod' (proj_right A B)) ⋙ prod_to_prod_Top A B = 𝟭 _,
    apply category_theory.functor.hext, { intros, ext; simp; refl, },
    rintros ⟨x₀, x₁⟩ ⟨y₀, y₁⟩ f,
    have := path.homotopic.prod_proj_left_proj_right f,
    simpa,
  end }

end prod

end fundamental_groupoid_functor