/-
Copyright (c) 2017 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl

! This file was ported from Lean 3 source module data.prod.basic
! leanprover-community/mathlib commit bd9851ca476957ea4549eb19b40e7b5ade9428cc
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Tactic.Basic
import Mathbin.Logic.Function.Basic

/-!
# Extra facts about `prod`

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

This file defines `prod.swap : α × β → β × α` and proves various simple lemmas about `prod`.
-/

variable {α : Type _} {β : Type _} {γ : Type _} {δ : Type _}

/- warning: prod_map -> Prod_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} (f : α -> γ) (g : β -> δ) (p : Prod.{u1, u2} α β), Eq.{max (succ u3) (succ u4)} (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g p) (Prod.mk.{u3, u4} γ δ (f (Prod.fst.{u1, u2} α β p)) (g (Prod.snd.{u1, u2} α β p)))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u2}} {δ : Type.{u1}} (f : α -> γ) (g : β -> δ) (p : Prod.{u4, u3} α β), Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} γ δ) (Prod.map.{u4, u2, u3, u1} α γ β δ f g p) (Prod.mk.{u2, u1} γ δ (f (Prod.fst.{u4, u3} α β p)) (g (Prod.snd.{u4, u3} α β p)))
Case conversion may be inaccurate. Consider using '#align prod_map Prod_mapₓ'. -/
@[simp]
theorem Prod_map (f : α → γ) (g : β → δ) (p : α × β) : Prod.map f g p = (f p.1, g p.2) :=
  rfl
#align prod_map Prod_map

namespace Prod

/- warning: prod.forall -> Prod.forall is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : (Prod.{u1, u2} α β) -> Prop}, Iff (forall (x : Prod.{u1, u2} α β), p x) (forall (a : α) (b : β), p (Prod.mk.{u1, u2} α β a b))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : (Prod.{u2, u1} α β) -> Prop}, Iff (forall (x : Prod.{u2, u1} α β), p x) (forall (a : α) (b : β), p (Prod.mk.{u2, u1} α β a b))
Case conversion may be inaccurate. Consider using '#align prod.forall Prod.forallₓ'. -/
@[simp]
theorem forall {p : α × β → Prop} : (∀ x, p x) ↔ ∀ a b, p (a, b) :=
  ⟨fun h a b => h (a, b), fun h ⟨a, b⟩ => h a b⟩
#align prod.forall Prod.forall

/- warning: prod.exists -> Prod.exists is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : (Prod.{u1, u2} α β) -> Prop}, Iff (Exists.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (fun (x : Prod.{u1, u2} α β) => p x)) (Exists.{succ u1} α (fun (a : α) => Exists.{succ u2} β (fun (b : β) => p (Prod.mk.{u1, u2} α β a b))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : (Prod.{u2, u1} α β) -> Prop}, Iff (Exists.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (fun (x : Prod.{u2, u1} α β) => p x)) (Exists.{succ u2} α (fun (a : α) => Exists.{succ u1} β (fun (b : β) => p (Prod.mk.{u2, u1} α β a b))))
Case conversion may be inaccurate. Consider using '#align prod.exists Prod.existsₓ'. -/
@[simp]
theorem exists {p : α × β → Prop} : (∃ x, p x) ↔ ∃ a b, p (a, b) :=
  ⟨fun ⟨⟨a, b⟩, h⟩ => ⟨a, b, h⟩, fun ⟨a, b, h⟩ => ⟨⟨a, b⟩, h⟩⟩
#align prod.exists Prod.exists

/- warning: prod.forall' -> Prod.forall' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : α -> β -> Prop}, Iff (forall (x : Prod.{u1, u2} α β), p (Prod.fst.{u1, u2} α β x) (Prod.snd.{u1, u2} α β x)) (forall (a : α) (b : β), p a b)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : α -> β -> Prop}, Iff (forall (x : Prod.{u2, u1} α β), p (Prod.fst.{u2, u1} α β x) (Prod.snd.{u2, u1} α β x)) (forall (a : α) (b : β), p a b)
Case conversion may be inaccurate. Consider using '#align prod.forall' Prod.forall'ₓ'. -/
theorem forall' {p : α → β → Prop} : (∀ x : α × β, p x.1 x.2) ↔ ∀ a b, p a b :=
  Prod.forall
#align prod.forall' Prod.forall'

/- warning: prod.exists' -> Prod.exists' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : α -> β -> Prop}, Iff (Exists.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (fun (x : Prod.{u1, u2} α β) => p (Prod.fst.{u1, u2} α β x) (Prod.snd.{u1, u2} α β x))) (Exists.{succ u1} α (fun (a : α) => Exists.{succ u2} β (fun (b : β) => p a b)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : α -> β -> Prop}, Iff (Exists.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (fun (x : Prod.{u2, u1} α β) => p (Prod.fst.{u2, u1} α β x) (Prod.snd.{u2, u1} α β x))) (Exists.{succ u2} α (fun (a : α) => Exists.{succ u1} β (fun (b : β) => p a b)))
Case conversion may be inaccurate. Consider using '#align prod.exists' Prod.exists'ₓ'. -/
theorem exists' {p : α → β → Prop} : (∃ x : α × β, p x.1 x.2) ↔ ∃ a b, p a b :=
  Prod.exists
#align prod.exists' Prod.exists'

#print Prod.snd_comp_mk /-
@[simp]
theorem snd_comp_mk (x : α) : Prod.snd ∘ (Prod.mk x : β → α × β) = id :=
  rfl
#align prod.snd_comp_mk Prod.snd_comp_mk
-/

/- warning: prod.fst_comp_mk -> Prod.fst_comp_mk is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (x : α), Eq.{max (succ u2) (succ u1)} (β -> α) (Function.comp.{succ u2, max (succ u1) (succ u2), succ u1} β (Prod.{u1, u2} α β) α (Prod.fst.{u1, u2} α β) (Prod.mk.{u1, u2} α β x)) (Function.const.{succ u1, succ u2} α β x)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (x : α), Eq.{max (succ u2) (succ u1)} (β -> α) (Function.comp.{succ u1, max (succ u1) (succ u2), succ u2} β (Prod.{u2, u1} α β) α (Prod.fst.{u2, u1} α β) (Prod.mk.{u2, u1} α β x)) (Function.const.{succ u2, succ u1} α β x)
Case conversion may be inaccurate. Consider using '#align prod.fst_comp_mk Prod.fst_comp_mkₓ'. -/
@[simp]
theorem fst_comp_mk (x : α) : Prod.fst ∘ (Prod.mk x : β → α × β) = Function.const β x :=
  rfl
#align prod.fst_comp_mk Prod.fst_comp_mk

/- warning: prod.map_mk -> Prod.map_mk is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} (f : α -> γ) (g : β -> δ) (a : α) (b : β), Eq.{max (succ u3) (succ u4)} (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g (Prod.mk.{u1, u2} α β a b)) (Prod.mk.{u3, u4} γ δ (f a) (g b))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {γ : Type.{u4}} {δ : Type.{u3}} (f : α -> γ) (g : β -> δ) (a : α) (b : β), Eq.{max (succ u4) (succ u3)} (Prod.{u4, u3} γ δ) (Prod.map.{u2, u4, u1, u3} α γ β δ f g (Prod.mk.{u2, u1} α β a b)) (Prod.mk.{u4, u3} γ δ (f a) (g b))
Case conversion may be inaccurate. Consider using '#align prod.map_mk Prod.map_mkₓ'. -/
@[simp]
theorem map_mk (f : α → γ) (g : β → δ) (a : α) (b : β) : map f g (a, b) = (f a, g b) :=
  rfl
#align prod.map_mk Prod.map_mk

/- warning: prod.map_fst -> Prod.map_fst is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} (f : α -> γ) (g : β -> δ) (p : Prod.{u1, u2} α β), Eq.{succ u3} γ (Prod.fst.{u3, u4} γ δ (Prod.map.{u1, u3, u2, u4} α γ β δ f g p)) (f (Prod.fst.{u1, u2} α β p))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u2}} {δ : Type.{u1}} (f : α -> γ) (g : β -> δ) (p : Prod.{u4, u3} α β), Eq.{succ u2} γ (Prod.fst.{u2, u1} γ δ (Prod.map.{u4, u2, u3, u1} α γ β δ f g p)) (f (Prod.fst.{u4, u3} α β p))
Case conversion may be inaccurate. Consider using '#align prod.map_fst Prod.map_fstₓ'. -/
theorem map_fst (f : α → γ) (g : β → δ) (p : α × β) : (map f g p).1 = f p.1 :=
  rfl
#align prod.map_fst Prod.map_fst

/- warning: prod.map_snd -> Prod.map_snd is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} (f : α -> γ) (g : β -> δ) (p : Prod.{u1, u2} α β), Eq.{succ u4} δ (Prod.snd.{u3, u4} γ δ (Prod.map.{u1, u3, u2, u4} α γ β δ f g p)) (g (Prod.snd.{u1, u2} α β p))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u1}} {δ : Type.{u2}} (f : α -> γ) (g : β -> δ) (p : Prod.{u4, u3} α β), Eq.{succ u2} δ (Prod.snd.{u1, u2} γ δ (Prod.map.{u4, u1, u3, u2} α γ β δ f g p)) (g (Prod.snd.{u4, u3} α β p))
Case conversion may be inaccurate. Consider using '#align prod.map_snd Prod.map_sndₓ'. -/
theorem map_snd (f : α → γ) (g : β → δ) (p : α × β) : (map f g p).2 = g p.2 :=
  rfl
#align prod.map_snd Prod.map_snd

/- warning: prod.map_fst' -> Prod.map_fst' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} (f : α -> γ) (g : β -> δ), Eq.{max (max (succ u1) (succ u2)) (succ u3)} ((Prod.{u1, u2} α β) -> γ) (Function.comp.{max (succ u1) (succ u2), max (succ u3) (succ u4), succ u3} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) γ (Prod.fst.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g)) (Function.comp.{max (succ u1) (succ u2), succ u1, succ u3} (Prod.{u1, u2} α β) α γ f (Prod.fst.{u1, u2} α β))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u2}} {δ : Type.{u1}} (f : α -> γ) (g : β -> δ), Eq.{max (max (succ u4) (succ u3)) (succ u2)} ((Prod.{u4, u3} α β) -> γ) (Function.comp.{max (succ u3) (succ u4), max (succ u1) (succ u2), succ u2} (Prod.{u4, u3} α β) (Prod.{u2, u1} γ δ) γ (Prod.fst.{u2, u1} γ δ) (Prod.map.{u4, u2, u3, u1} α γ β δ f g)) (Function.comp.{max (succ u3) (succ u4), succ u4, succ u2} (Prod.{u4, u3} α β) α γ f (Prod.fst.{u4, u3} α β))
Case conversion may be inaccurate. Consider using '#align prod.map_fst' Prod.map_fst'ₓ'. -/
theorem map_fst' (f : α → γ) (g : β → δ) : Prod.fst ∘ map f g = f ∘ Prod.fst :=
  funext <| map_fst f g
#align prod.map_fst' Prod.map_fst'

/- warning: prod.map_snd' -> Prod.map_snd' is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} (f : α -> γ) (g : β -> δ), Eq.{max (max (succ u1) (succ u2)) (succ u4)} ((Prod.{u1, u2} α β) -> δ) (Function.comp.{max (succ u1) (succ u2), max (succ u3) (succ u4), succ u4} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) δ (Prod.snd.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g)) (Function.comp.{max (succ u1) (succ u2), succ u2, succ u4} (Prod.{u1, u2} α β) β δ g (Prod.snd.{u1, u2} α β))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u1}} {δ : Type.{u2}} (f : α -> γ) (g : β -> δ), Eq.{max (max (succ u4) (succ u3)) (succ u2)} ((Prod.{u4, u3} α β) -> δ) (Function.comp.{max (succ u3) (succ u4), max (succ u2) (succ u1), succ u2} (Prod.{u4, u3} α β) (Prod.{u1, u2} γ δ) δ (Prod.snd.{u1, u2} γ δ) (Prod.map.{u4, u1, u3, u2} α γ β δ f g)) (Function.comp.{max (succ u3) (succ u4), succ u3, succ u2} (Prod.{u4, u3} α β) β δ g (Prod.snd.{u4, u3} α β))
Case conversion may be inaccurate. Consider using '#align prod.map_snd' Prod.map_snd'ₓ'. -/
theorem map_snd' (f : α → γ) (g : β → δ) : Prod.snd ∘ map f g = g ∘ Prod.snd :=
  funext <| map_snd f g
#align prod.map_snd' Prod.map_snd'

/- warning: prod.map_comp_map -> Prod.map_comp_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {ε : Type.{u5}} {ζ : Type.{u6}} (f : α -> β) (f' : γ -> δ) (g : β -> ε) (g' : δ -> ζ), Eq.{max (max (succ u1) (succ u3)) (succ u5) (succ u6)} ((Prod.{u1, u3} α γ) -> (Prod.{u5, u6} ε ζ)) (Function.comp.{max (succ u1) (succ u3), max (succ u2) (succ u4), max (succ u5) (succ u6)} (Prod.{u1, u3} α γ) (Prod.{u2, u4} β δ) (Prod.{u5, u6} ε ζ) (Prod.map.{u2, u5, u4, u6} β ε δ ζ g g') (Prod.map.{u1, u2, u3, u4} α β γ δ f f')) (Prod.map.{u1, u5, u3, u6} α ε γ ζ (Function.comp.{succ u1, succ u2, succ u5} α β ε g f) (Function.comp.{succ u3, succ u4, succ u6} γ δ ζ g' f'))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u1}} {γ : Type.{u3}} {δ : Type.{u2}} {ε : Type.{u6}} {ζ : Type.{u5}} (f : α -> β) (f' : γ -> δ) (g : β -> ε) (g' : δ -> ζ), Eq.{max (max (max (succ u4) (succ u3)) (succ u6)) (succ u5)} ((Prod.{u4, u3} α γ) -> (Prod.{u6, u5} ε ζ)) (Function.comp.{max (succ u3) (succ u4), max (succ u2) (succ u1), max (succ u5) (succ u6)} (Prod.{u4, u3} α γ) (Prod.{u1, u2} β δ) (Prod.{u6, u5} ε ζ) (Prod.map.{u1, u6, u2, u5} β ε δ ζ g g') (Prod.map.{u4, u1, u3, u2} α β γ δ f f')) (Prod.map.{u4, u6, u3, u5} α ε γ ζ (Function.comp.{succ u4, succ u1, succ u6} α β ε g f) (Function.comp.{succ u3, succ u2, succ u5} γ δ ζ g' f'))
Case conversion may be inaccurate. Consider using '#align prod.map_comp_map Prod.map_comp_mapₓ'. -/
/-- Composing a `prod.map` with another `prod.map` is equal to
a single `prod.map` of composed functions.
-/
theorem map_comp_map {ε ζ : Type _} (f : α → β) (f' : γ → δ) (g : β → ε) (g' : δ → ζ) :
    Prod.map g g' ∘ Prod.map f f' = Prod.map (g ∘ f) (g' ∘ f') :=
  rfl
#align prod.map_comp_map Prod.map_comp_map

/- warning: prod.map_map -> Prod.map_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {ε : Type.{u5}} {ζ : Type.{u6}} (f : α -> β) (f' : γ -> δ) (g : β -> ε) (g' : δ -> ζ) (x : Prod.{u1, u3} α γ), Eq.{max (succ u5) (succ u6)} (Prod.{u5, u6} ε ζ) (Prod.map.{u2, u5, u4, u6} β ε δ ζ g g' (Prod.map.{u1, u2, u3, u4} α β γ δ f f' x)) (Prod.map.{u1, u5, u3, u6} α ε γ ζ (Function.comp.{succ u1, succ u2, succ u5} α β ε g f) (Function.comp.{succ u3, succ u4, succ u6} γ δ ζ g' f') x)
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {ε : Type.{u6}} {ζ : Type.{u5}} (f : α -> β) (f' : γ -> δ) (g : β -> ε) (g' : δ -> ζ) (x : Prod.{u4, u3} α γ), Eq.{max (succ u6) (succ u5)} (Prod.{u6, u5} ε ζ) (Prod.map.{u2, u6, u1, u5} β ε δ ζ g g' (Prod.map.{u4, u2, u3, u1} α β γ δ f f' x)) (Prod.map.{u4, u6, u3, u5} α ε γ ζ (Function.comp.{succ u4, succ u2, succ u6} α β ε g f) (Function.comp.{succ u3, succ u1, succ u5} γ δ ζ g' f') x)
Case conversion may be inaccurate. Consider using '#align prod.map_map Prod.map_mapₓ'. -/
/-- Composing a `prod.map` with another `prod.map` is equal to
a single `prod.map` of composed functions, fully applied.
-/
theorem map_map {ε ζ : Type _} (f : α → β) (f' : γ → δ) (g : β → ε) (g' : δ → ζ) (x : α × γ) :
    Prod.map g g' (Prod.map f f' x) = Prod.map (g ∘ f) (g' ∘ f') x :=
  rfl
#align prod.map_map Prod.map_map

variable {a a₁ a₂ : α} {b b₁ b₂ : β}

/- warning: prod.mk.inj_iff -> Prod.mk.inj_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {a₁ : α} {a₂ : α} {b₁ : β} {b₂ : β}, Iff (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.mk.{u1, u2} α β a₁ b₁) (Prod.mk.{u1, u2} α β a₂ b₂)) (And (Eq.{succ u1} α a₁ a₂) (Eq.{succ u2} β b₁ b₂))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {a₁ : α} {a₂ : α} {b₁ : β} {b₂ : β}, Iff (Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (Prod.mk.{u2, u1} α β a₁ b₁) (Prod.mk.{u2, u1} α β a₂ b₂)) (And (Eq.{succ u2} α a₁ a₂) (Eq.{succ u1} β b₁ b₂))
Case conversion may be inaccurate. Consider using '#align prod.mk.inj_iff Prod.mk.inj_iffₓ'. -/
@[simp]
theorem mk.inj_iff : (a₁, b₁) = (a₂, b₂) ↔ a₁ = a₂ ∧ b₁ = b₂ :=
  ⟨Prod.mk.inj, by cc⟩
#align prod.mk.inj_iff Prod.mk.inj_iff

/- warning: prod.mk.inj_left -> Prod.mk.inj_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (a : α), Function.Injective.{succ u2, max (succ u1) (succ u2)} β (Prod.{u1, u2} α β) (Prod.mk.{u1, u2} α β a)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (a : α), Function.Injective.{succ u1, max (succ u2) (succ u1)} β (Prod.{u2, u1} α β) (Prod.mk.{u2, u1} α β a)
Case conversion may be inaccurate. Consider using '#align prod.mk.inj_left Prod.mk.inj_leftₓ'. -/
theorem mk.inj_left {α β : Type _} (a : α) : Function.Injective (Prod.mk a : β → α × β) :=
  by
  intro b₁ b₂ h
  simpa only [true_and_iff, Prod.mk.inj_iff, eq_self_iff_true] using h
#align prod.mk.inj_left Prod.mk.inj_left

/- warning: prod.mk.inj_right -> Prod.mk.inj_right is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (b : β), Function.Injective.{succ u1, max (succ u1) (succ u2)} α (Prod.{u1, u2} α β) (fun (a : α) => Prod.mk.{u1, u2} α β a b)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (b : β), Function.Injective.{succ u2, max (succ u2) (succ u1)} α (Prod.{u2, u1} α β) (fun (a : α) => Prod.mk.{u2, u1} α β a b)
Case conversion may be inaccurate. Consider using '#align prod.mk.inj_right Prod.mk.inj_rightₓ'. -/
theorem mk.inj_right {α β : Type _} (b : β) :
    Function.Injective (fun a => Prod.mk a b : α → α × β) :=
  by
  intro b₁ b₂ h
  · simpa only [and_true_iff, eq_self_iff_true, mk.inj_iff] using h
#align prod.mk.inj_right Prod.mk.inj_right

/- warning: prod.mk_inj_left -> Prod.mk_inj_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {a : α} {b₁ : β} {b₂ : β}, Iff (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.mk.{u1, u2} α β a b₁) (Prod.mk.{u1, u2} α β a b₂)) (Eq.{succ u2} β b₁ b₂)
but is expected to have type
  forall {α : Type.{u2}} {β : α} {a : Type.{u1}} {b₁ : a} {b₂ : a}, Iff (Eq.{max (succ u1) (succ u2)} (Prod.{u2, u1} α a) (Prod.mk.{u2, u1} α a β b₁) (Prod.mk.{u2, u1} α a β b₂)) (Eq.{succ u1} a b₁ b₂)
Case conversion may be inaccurate. Consider using '#align prod.mk_inj_left Prod.mk_inj_leftₓ'. -/
theorem mk_inj_left : (a, b₁) = (a, b₂) ↔ b₁ = b₂ :=
  (mk.inj_left _).eq_iff
#align prod.mk_inj_left Prod.mk_inj_left

/- warning: prod.mk_inj_right -> Prod.mk_inj_right is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {a₁ : α} {a₂ : α} {b : β}, Iff (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.mk.{u1, u2} α β a₁ b) (Prod.mk.{u1, u2} α β a₂ b)) (Eq.{succ u1} α a₁ a₂)
but is expected to have type
  forall {α : Type.{u2}} {β : α} {a₁ : Type.{u1}} {a₂ : a₁} {b : α}, Iff (Eq.{max (succ u1) (succ u2)} (Prod.{u2, u1} α a₁) (Prod.mk.{u2, u1} α a₁ β a₂) (Prod.mk.{u2, u1} α a₁ b a₂)) (Eq.{succ u2} α β b)
Case conversion may be inaccurate. Consider using '#align prod.mk_inj_right Prod.mk_inj_rightₓ'. -/
theorem mk_inj_right : (a₁, b) = (a₂, b) ↔ a₁ = a₂ :=
  (mk.inj_right _).eq_iff
#align prod.mk_inj_right Prod.mk_inj_right

/- warning: prod.ext_iff -> Prod.ext_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β} {q : Prod.{u1, u2} α β}, Iff (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) p q) (And (Eq.{succ u1} α (Prod.fst.{u1, u2} α β p) (Prod.fst.{u1, u2} α β q)) (Eq.{succ u2} β (Prod.snd.{u1, u2} α β p) (Prod.snd.{u1, u2} α β q)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β} {q : Prod.{u2, u1} α β}, Iff (Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) p q) (And (Eq.{succ u2} α (Prod.fst.{u2, u1} α β p) (Prod.fst.{u2, u1} α β q)) (Eq.{succ u1} β (Prod.snd.{u2, u1} α β p) (Prod.snd.{u2, u1} α β q)))
Case conversion may be inaccurate. Consider using '#align prod.ext_iff Prod.ext_iffₓ'. -/
theorem ext_iff {p q : α × β} : p = q ↔ p.1 = q.1 ∧ p.2 = q.2 := by
  rw [← @mk.eta _ _ p, ← @mk.eta _ _ q, mk.inj_iff]
#align prod.ext_iff Prod.ext_iff

/- warning: prod.ext -> Prod.ext is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β} {q : Prod.{u1, u2} α β}, (Eq.{succ u1} α (Prod.fst.{u1, u2} α β p) (Prod.fst.{u1, u2} α β q)) -> (Eq.{succ u2} β (Prod.snd.{u1, u2} α β p) (Prod.snd.{u1, u2} α β q)) -> (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) p q)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β} {q : Prod.{u2, u1} α β}, (Eq.{succ u2} α (Prod.fst.{u2, u1} α β p) (Prod.fst.{u2, u1} α β q)) -> (Eq.{succ u1} β (Prod.snd.{u2, u1} α β p) (Prod.snd.{u2, u1} α β q)) -> (Eq.{max (succ u1) (succ u2)} (Prod.{u2, u1} α β) p q)
Case conversion may be inaccurate. Consider using '#align prod.ext Prod.extₓ'. -/
@[ext]
theorem ext {α β} {p q : α × β} (h₁ : p.1 = q.1) (h₂ : p.2 = q.2) : p = q :=
  ext_iff.2 ⟨h₁, h₂⟩
#align prod.ext Prod.ext

/- warning: prod.map_def -> Prod.map_def is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {f : α -> γ} {g : β -> δ}, Eq.{max (max (succ u1) (succ u2)) (succ u3) (succ u4)} ((Prod.{u1, u2} α β) -> (Prod.{u3, u4} γ δ)) (Prod.map.{u1, u3, u2, u4} α γ β δ f g) (fun (p : Prod.{u1, u2} α β) => Prod.mk.{u3, u4} γ δ (f (Prod.fst.{u1, u2} α β p)) (g (Prod.snd.{u1, u2} α β p)))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u2}} {δ : Type.{u1}} {f : α -> γ} {g : β -> δ}, Eq.{max (max (max (succ u4) (succ u3)) (succ u2)) (succ u1)} ((Prod.{u4, u3} α β) -> (Prod.{u2, u1} γ δ)) (Prod.map.{u4, u2, u3, u1} α γ β δ f g) (fun (p : Prod.{u4, u3} α β) => Prod.mk.{u2, u1} γ δ (f (Prod.fst.{u4, u3} α β p)) (g (Prod.snd.{u4, u3} α β p)))
Case conversion may be inaccurate. Consider using '#align prod.map_def Prod.map_defₓ'. -/
theorem map_def {f : α → γ} {g : β → δ} : Prod.map f g = fun p : α × β => (f p.1, g p.2) :=
  funext fun p => ext (map_fst f g p) (map_snd f g p)
#align prod.map_def Prod.map_def

/- warning: prod.id_prod -> Prod.id_prod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Eq.{max (succ u1) (succ u2)} ((Prod.{u1, u2} α β) -> (Prod.{u1, u2} α β)) (fun (p : Prod.{u1, u2} α β) => Prod.mk.{u1, u2} α β (Prod.fst.{u1, u2} α β p) (Prod.snd.{u1, u2} α β p)) (id.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Eq.{max (succ u2) (succ u1)} ((Prod.{u2, u1} α β) -> (Prod.{u2, u1} α β)) (fun (p : Prod.{u2, u1} α β) => Prod.mk.{u2, u1} α β (Prod.fst.{u2, u1} α β p) (Prod.snd.{u2, u1} α β p)) (id.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β))
Case conversion may be inaccurate. Consider using '#align prod.id_prod Prod.id_prodₓ'. -/
theorem id_prod : (fun p : α × β => (p.1, p.2)) = id :=
  funext fun ⟨a, b⟩ => rfl
#align prod.id_prod Prod.id_prod

/- warning: prod.map_id -> Prod.map_id is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Eq.{max (succ u1) (succ u2)} ((Prod.{u1, u2} α β) -> (Prod.{u1, u2} α β)) (Prod.map.{u1, u1, u2, u2} α α β β (id.{succ u1} α) (id.{succ u2} β)) (id.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Eq.{max (succ u2) (succ u1)} ((Prod.{u2, u1} α β) -> (Prod.{u2, u1} α β)) (Prod.map.{u2, u2, u1, u1} α α β β (id.{succ u2} α) (id.{succ u1} β)) (id.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β))
Case conversion may be inaccurate. Consider using '#align prod.map_id Prod.map_idₓ'. -/
theorem map_id : Prod.map (@id α) (@id β) = id :=
  id_prod
#align prod.map_id Prod.map_id

#print Prod.fst_surjective /-
theorem fst_surjective [h : Nonempty β] : Function.Surjective (@fst α β) := fun x =>
  h.elim fun y => ⟨⟨x, y⟩, rfl⟩
#align prod.fst_surjective Prod.fst_surjective
-/

/- warning: prod.snd_surjective -> Prod.snd_surjective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [h : Nonempty.{succ u1} α], Function.Surjective.{max (succ u1) (succ u2), succ u2} (Prod.{u1, u2} α β) β (Prod.snd.{u1, u2} α β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [h : Nonempty.{succ u2} α], Function.Surjective.{max (succ u2) (succ u1), succ u1} (Prod.{u2, u1} α β) β (Prod.snd.{u2, u1} α β)
Case conversion may be inaccurate. Consider using '#align prod.snd_surjective Prod.snd_surjectiveₓ'. -/
theorem snd_surjective [h : Nonempty α] : Function.Surjective (@snd α β) := fun y =>
  h.elim fun x => ⟨⟨x, y⟩, rfl⟩
#align prod.snd_surjective Prod.snd_surjective

#print Prod.fst_injective /-
theorem fst_injective [Subsingleton β] : Function.Injective (@fst α β) := fun x y h =>
  ext h (Subsingleton.elim _ _)
#align prod.fst_injective Prod.fst_injective
-/

/- warning: prod.snd_injective -> Prod.snd_injective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Subsingleton.{succ u1} α], Function.Injective.{max (succ u1) (succ u2), succ u2} (Prod.{u1, u2} α β) β (Prod.snd.{u1, u2} α β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Subsingleton.{succ u2} α], Function.Injective.{max (succ u2) (succ u1), succ u1} (Prod.{u2, u1} α β) β (Prod.snd.{u2, u1} α β)
Case conversion may be inaccurate. Consider using '#align prod.snd_injective Prod.snd_injectiveₓ'. -/
theorem snd_injective [Subsingleton α] : Function.Injective (@snd α β) := fun x y h =>
  ext (Subsingleton.elim _ _) h
#align prod.snd_injective Prod.snd_injective

#print Prod.swap /-
/-- Swap the factors of a product. `swap (a, b) = (b, a)` -/
def swap : α × β → β × α := fun p => (p.2, p.1)
#align prod.swap Prod.swap
-/

/- warning: prod.swap_swap -> Prod.swap_swap is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (x : Prod.{u1, u2} α β), Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.swap.{u2, u1} β α (Prod.swap.{u1, u2} α β x)) x
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (x : Prod.{u2, u1} α β), Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (Prod.swap.{u1, u2} β α (Prod.swap.{u2, u1} α β x)) x
Case conversion may be inaccurate. Consider using '#align prod.swap_swap Prod.swap_swapₓ'. -/
@[simp]
theorem swap_swap : ∀ x : α × β, swap (swap x) = x
  | ⟨a, b⟩ => rfl
#align prod.swap_swap Prod.swap_swap

/- warning: prod.fst_swap -> Prod.fst_swap is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β}, Eq.{succ u2} β (Prod.fst.{u2, u1} β α (Prod.swap.{u1, u2} α β p)) (Prod.snd.{u1, u2} α β p)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β}, Eq.{succ u1} β (Prod.fst.{u1, u2} β α (Prod.swap.{u2, u1} α β p)) (Prod.snd.{u2, u1} α β p)
Case conversion may be inaccurate. Consider using '#align prod.fst_swap Prod.fst_swapₓ'. -/
@[simp]
theorem fst_swap {p : α × β} : (swap p).1 = p.2 :=
  rfl
#align prod.fst_swap Prod.fst_swap

/- warning: prod.snd_swap -> Prod.snd_swap is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β}, Eq.{succ u1} α (Prod.snd.{u2, u1} β α (Prod.swap.{u1, u2} α β p)) (Prod.fst.{u1, u2} α β p)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β}, Eq.{succ u2} α (Prod.snd.{u1, u2} β α (Prod.swap.{u2, u1} α β p)) (Prod.fst.{u2, u1} α β p)
Case conversion may be inaccurate. Consider using '#align prod.snd_swap Prod.snd_swapₓ'. -/
@[simp]
theorem snd_swap {p : α × β} : (swap p).2 = p.1 :=
  rfl
#align prod.snd_swap Prod.snd_swap

/- warning: prod.swap_prod_mk -> Prod.swap_prod_mk is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {a : α} {b : β}, Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} β α) (Prod.swap.{u1, u2} α β (Prod.mk.{u1, u2} α β a b)) (Prod.mk.{u2, u1} β α b a)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {a : α} {b : β}, Eq.{max (succ u2) (succ u1)} (Prod.{u1, u2} β α) (Prod.swap.{u2, u1} α β (Prod.mk.{u2, u1} α β a b)) (Prod.mk.{u1, u2} β α b a)
Case conversion may be inaccurate. Consider using '#align prod.swap_prod_mk Prod.swap_prod_mkₓ'. -/
@[simp]
theorem swap_prod_mk {a : α} {b : β} : swap (a, b) = (b, a) :=
  rfl
#align prod.swap_prod_mk Prod.swap_prod_mk

/- warning: prod.swap_swap_eq -> Prod.swap_swap_eq is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Eq.{max (succ u1) (succ u2)} ((Prod.{u1, u2} α β) -> (Prod.{u1, u2} α β)) (Function.comp.{max (succ u1) (succ u2), max (succ u2) (succ u1), max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.{u2, u1} β α) (Prod.{u1, u2} α β) (Prod.swap.{u2, u1} β α) (Prod.swap.{u1, u2} α β)) (id.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Eq.{max (succ u2) (succ u1)} ((Prod.{u2, u1} α β) -> (Prod.{u2, u1} α β)) (Function.comp.{max (succ u1) (succ u2), max (succ u2) (succ u1), max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (Prod.{u1, u2} β α) (Prod.{u2, u1} α β) (Prod.swap.{u1, u2} β α) (Prod.swap.{u2, u1} α β)) (id.{max (succ u1) (succ u2)} (Prod.{u2, u1} α β))
Case conversion may be inaccurate. Consider using '#align prod.swap_swap_eq Prod.swap_swap_eqₓ'. -/
@[simp]
theorem swap_swap_eq : swap ∘ swap = @id (α × β) :=
  funext swap_swap
#align prod.swap_swap_eq Prod.swap_swap_eq

/- warning: prod.swap_left_inverse -> Prod.swap_leftInverse is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Function.LeftInverse.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (Prod.{u2, u1} β α) (Prod.{u1, u2} α β) (Prod.swap.{u1, u2} α β) (Prod.swap.{u2, u1} β α)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Function.LeftInverse.{max (succ u2) (succ u1), max (succ u2) (succ u1)} (Prod.{u1, u2} β α) (Prod.{u2, u1} α β) (Prod.swap.{u2, u1} α β) (Prod.swap.{u1, u2} β α)
Case conversion may be inaccurate. Consider using '#align prod.swap_left_inverse Prod.swap_leftInverseₓ'. -/
@[simp]
theorem swap_leftInverse : Function.LeftInverse (@swap α β) swap :=
  swap_swap
#align prod.swap_left_inverse Prod.swap_leftInverse

/- warning: prod.swap_right_inverse -> Prod.swap_rightInverse is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Function.RightInverse.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (Prod.{u2, u1} β α) (Prod.{u1, u2} α β) (Prod.swap.{u1, u2} α β) (Prod.swap.{u2, u1} β α)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Function.RightInverse.{max (succ u2) (succ u1), max (succ u2) (succ u1)} (Prod.{u1, u2} β α) (Prod.{u2, u1} α β) (Prod.swap.{u2, u1} α β) (Prod.swap.{u1, u2} β α)
Case conversion may be inaccurate. Consider using '#align prod.swap_right_inverse Prod.swap_rightInverseₓ'. -/
@[simp]
theorem swap_rightInverse : Function.RightInverse (@swap α β) swap :=
  swap_swap
#align prod.swap_right_inverse Prod.swap_rightInverse

/- warning: prod.swap_injective -> Prod.swap_injective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Function.Injective.{max (succ u1) (succ u2), max (succ u2) (succ u1)} (Prod.{u1, u2} α β) (Prod.{u2, u1} β α) (Prod.swap.{u1, u2} α β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Function.Injective.{max (succ u2) (succ u1), max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (Prod.{u1, u2} β α) (Prod.swap.{u2, u1} α β)
Case conversion may be inaccurate. Consider using '#align prod.swap_injective Prod.swap_injectiveₓ'. -/
theorem swap_injective : Function.Injective (@swap α β) :=
  swap_leftInverse.Injective
#align prod.swap_injective Prod.swap_injective

/- warning: prod.swap_surjective -> Prod.swap_surjective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Function.Surjective.{max (succ u1) (succ u2), max (succ u2) (succ u1)} (Prod.{u1, u2} α β) (Prod.{u2, u1} β α) (Prod.swap.{u1, u2} α β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Function.Surjective.{max (succ u2) (succ u1), max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (Prod.{u1, u2} β α) (Prod.swap.{u2, u1} α β)
Case conversion may be inaccurate. Consider using '#align prod.swap_surjective Prod.swap_surjectiveₓ'. -/
theorem swap_surjective : Function.Surjective (@swap α β) :=
  swap_leftInverse.Surjective
#align prod.swap_surjective Prod.swap_surjective

/- warning: prod.swap_bijective -> Prod.swap_bijective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}}, Function.Bijective.{max (succ u1) (succ u2), max (succ u2) (succ u1)} (Prod.{u1, u2} α β) (Prod.{u2, u1} β α) (Prod.swap.{u1, u2} α β)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}}, Function.Bijective.{max (succ u2) (succ u1), max (succ u2) (succ u1)} (Prod.{u2, u1} α β) (Prod.{u1, u2} β α) (Prod.swap.{u2, u1} α β)
Case conversion may be inaccurate. Consider using '#align prod.swap_bijective Prod.swap_bijectiveₓ'. -/
theorem swap_bijective : Function.Bijective (@swap α β) :=
  ⟨swap_injective, swap_surjective⟩
#align prod.swap_bijective Prod.swap_bijective

/- warning: prod.swap_inj -> Prod.swap_inj is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β} {q : Prod.{u1, u2} α β}, Iff (Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} β α) (Prod.swap.{u1, u2} α β p) (Prod.swap.{u1, u2} α β q)) (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) p q)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β} {q : Prod.{u2, u1} α β}, Iff (Eq.{max (succ u2) (succ u1)} (Prod.{u1, u2} β α) (Prod.swap.{u2, u1} α β p) (Prod.swap.{u2, u1} α β q)) (Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) p q)
Case conversion may be inaccurate. Consider using '#align prod.swap_inj Prod.swap_injₓ'. -/
@[simp]
theorem swap_inj {p q : α × β} : swap p = swap q ↔ p = q :=
  swap_injective.eq_iff
#align prod.swap_inj Prod.swap_inj

/- warning: prod.eq_iff_fst_eq_snd_eq -> Prod.eq_iff_fst_eq_snd_eq is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β} {q : Prod.{u1, u2} α β}, Iff (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) p q) (And (Eq.{succ u1} α (Prod.fst.{u1, u2} α β p) (Prod.fst.{u1, u2} α β q)) (Eq.{succ u2} β (Prod.snd.{u1, u2} α β p) (Prod.snd.{u1, u2} α β q)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β} {q : Prod.{u2, u1} α β}, Iff (Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) p q) (And (Eq.{succ u2} α (Prod.fst.{u2, u1} α β p) (Prod.fst.{u2, u1} α β q)) (Eq.{succ u1} β (Prod.snd.{u2, u1} α β p) (Prod.snd.{u2, u1} α β q)))
Case conversion may be inaccurate. Consider using '#align prod.eq_iff_fst_eq_snd_eq Prod.eq_iff_fst_eq_snd_eqₓ'. -/
theorem eq_iff_fst_eq_snd_eq : ∀ {p q : α × β}, p = q ↔ p.1 = q.1 ∧ p.2 = q.2
  | ⟨p₁, p₂⟩, ⟨q₁, q₂⟩ => by simp
#align prod.eq_iff_fst_eq_snd_eq Prod.eq_iff_fst_eq_snd_eq

/- warning: prod.fst_eq_iff -> Prod.fst_eq_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β} {x : α}, Iff (Eq.{succ u1} α (Prod.fst.{u1, u2} α β p) x) (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) p (Prod.mk.{u1, u2} α β x (Prod.snd.{u1, u2} α β p)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β} {x : α}, Iff (Eq.{succ u2} α (Prod.fst.{u2, u1} α β p) x) (Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) p (Prod.mk.{u2, u1} α β x (Prod.snd.{u2, u1} α β p)))
Case conversion may be inaccurate. Consider using '#align prod.fst_eq_iff Prod.fst_eq_iffₓ'. -/
theorem fst_eq_iff : ∀ {p : α × β} {x : α}, p.1 = x ↔ p = (x, p.2)
  | ⟨a, b⟩, x => by simp
#align prod.fst_eq_iff Prod.fst_eq_iff

/- warning: prod.snd_eq_iff -> Prod.snd_eq_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {p : Prod.{u1, u2} α β} {x : β}, Iff (Eq.{succ u2} β (Prod.snd.{u1, u2} α β p) x) (Eq.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) p (Prod.mk.{u1, u2} α β (Prod.fst.{u1, u2} α β p) x))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {p : Prod.{u2, u1} α β} {x : β}, Iff (Eq.{succ u1} β (Prod.snd.{u2, u1} α β p) x) (Eq.{max (succ u2) (succ u1)} (Prod.{u2, u1} α β) p (Prod.mk.{u2, u1} α β (Prod.fst.{u2, u1} α β p) x))
Case conversion may be inaccurate. Consider using '#align prod.snd_eq_iff Prod.snd_eq_iffₓ'. -/
theorem snd_eq_iff : ∀ {p : α × β} {x : β}, p.2 = x ↔ p = (p.1, x)
  | ⟨a, b⟩, x => by simp
#align prod.snd_eq_iff Prod.snd_eq_iff

variable {r : α → α → Prop} {s : β → β → Prop} {x y : α × β}

/- warning: prod.lex_def -> Prod.lex_def is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (r : α -> α -> Prop) (s : β -> β -> Prop) {p : Prod.{u1, u2} α β} {q : Prod.{u1, u2} α β}, Iff (Prod.Lex.{u1, u2} α β r s p q) (Or (r (Prod.fst.{u1, u2} α β p) (Prod.fst.{u1, u2} α β q)) (And (Eq.{succ u1} α (Prod.fst.{u1, u2} α β p) (Prod.fst.{u1, u2} α β q)) (s (Prod.snd.{u1, u2} α β p) (Prod.snd.{u1, u2} α β q))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (r : α -> α -> Prop) (s : β -> β -> Prop) {p : Prod.{u2, u1} α β} {q : Prod.{u2, u1} α β}, Iff (Prod.Lex.{u2, u1} α β r s p q) (Or (r (Prod.fst.{u2, u1} α β p) (Prod.fst.{u2, u1} α β q)) (And (Eq.{succ u2} α (Prod.fst.{u2, u1} α β p) (Prod.fst.{u2, u1} α β q)) (s (Prod.snd.{u2, u1} α β p) (Prod.snd.{u2, u1} α β q))))
Case conversion may be inaccurate. Consider using '#align prod.lex_def Prod.lex_defₓ'. -/
theorem lex_def (r : α → α → Prop) (s : β → β → Prop) {p q : α × β} :
    Prod.Lex r s p q ↔ r p.1 q.1 ∨ p.1 = q.1 ∧ s p.2 q.2 :=
  ⟨fun h => by cases h <;> simp [*], fun h =>
    match p, q, h with
    | (a, b), (c, d), Or.inl h => Lex.left _ _ h
    | (a, b), (c, d), Or.inr ⟨e, h⟩ => by change a = c at e <;> subst e <;> exact lex.right _ h⟩
#align prod.lex_def Prod.lex_def

/- warning: prod.lex_iff -> Prod.lex_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {r : α -> α -> Prop} {s : β -> β -> Prop} {x : Prod.{u1, u2} α β} {y : Prod.{u1, u2} α β}, Iff (Prod.Lex.{u1, u2} α β r s x y) (Or (r (Prod.fst.{u1, u2} α β x) (Prod.fst.{u1, u2} α β y)) (And (Eq.{succ u1} α (Prod.fst.{u1, u2} α β x) (Prod.fst.{u1, u2} α β y)) (s (Prod.snd.{u1, u2} α β x) (Prod.snd.{u1, u2} α β y))))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {r : α -> α -> Prop} {s : β -> β -> Prop} {x : Prod.{u2, u1} α β} {y : Prod.{u2, u1} α β}, Iff (Prod.Lex.{u2, u1} α β r s x y) (Or (r (Prod.fst.{u2, u1} α β x) (Prod.fst.{u2, u1} α β y)) (And (Eq.{succ u2} α (Prod.fst.{u2, u1} α β x) (Prod.fst.{u2, u1} α β y)) (s (Prod.snd.{u2, u1} α β x) (Prod.snd.{u2, u1} α β y))))
Case conversion may be inaccurate. Consider using '#align prod.lex_iff Prod.lex_iffₓ'. -/
theorem lex_iff : Lex r s x y ↔ r x.1 y.1 ∨ x.1 = y.1 ∧ s x.2 y.2 :=
  lex_def _ _
#align prod.lex_iff Prod.lex_iff

#print Prod.Lex.decidable /-
instance Lex.decidable [DecidableEq α] (r : α → α → Prop) (s : β → β → Prop) [DecidableRel r]
    [DecidableRel s] : DecidableRel (Prod.Lex r s) := fun p q =>
  decidable_of_decidable_of_iff (by infer_instance) (lex_def r s).symm
#align prod.lex.decidable Prod.Lex.decidable
-/

/- warning: prod.lex.refl_left -> Prod.Lex.refl_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} (r : α -> α -> Prop) (s : β -> β -> Prop) [_inst_1 : IsRefl.{u1} α r] (x : Prod.{u1, u2} α β), Prod.Lex.{u1, u2} α β r s x x
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} (r : α -> α -> Prop) (s : β -> β -> Prop) [_inst_1 : IsRefl.{u2} α r] (x : Prod.{u2, u1} α β), Prod.Lex.{u2, u1} α β r s x x
Case conversion may be inaccurate. Consider using '#align prod.lex.refl_left Prod.Lex.refl_leftₓ'. -/
@[refl]
theorem Lex.refl_left (r : α → α → Prop) (s : β → β → Prop) [IsRefl α r] : ∀ x, Prod.Lex r s x x
  | (x₁, x₂) => Lex.left _ _ (refl _)
#align prod.lex.refl_left Prod.Lex.refl_left

instance isRefl_left {r : α → α → Prop} {s : β → β → Prop} [IsRefl α r] :
    IsRefl (α × β) (Lex r s) :=
  ⟨Lex.refl_left _ _⟩
#align prod.is_refl_left Prod.isRefl_left

#print Prod.Lex.refl_right /-
@[refl]
theorem Lex.refl_right (r : α → α → Prop) (s : β → β → Prop) [IsRefl β s] : ∀ x, Prod.Lex r s x x
  | (x₁, x₂) => Lex.right _ (refl _)
#align prod.lex.refl_right Prod.Lex.refl_right
-/

instance isRefl_right {r : α → α → Prop} {s : β → β → Prop} [IsRefl β s] :
    IsRefl (α × β) (Lex r s) :=
  ⟨Lex.refl_right _ _⟩
#align prod.is_refl_right Prod.isRefl_right

#print Prod.isIrrefl /-
instance isIrrefl [IsIrrefl α r] [IsIrrefl β s] : IsIrrefl (α × β) (Lex r s) :=
  ⟨by rintro ⟨i, a⟩ (⟨_, _, h⟩ | ⟨_, h⟩) <;> exact irrefl _ h⟩
#align prod.is_irrefl Prod.isIrrefl
-/

/- warning: prod.lex.trans -> Prod.Lex.trans is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {r : α -> α -> Prop} {s : β -> β -> Prop} [_inst_1 : IsTrans.{u1} α r] [_inst_2 : IsTrans.{u2} β s] {x : Prod.{u1, u2} α β} {y : Prod.{u1, u2} α β} {z : Prod.{u1, u2} α β}, (Prod.Lex.{u1, u2} α β r s x y) -> (Prod.Lex.{u1, u2} α β r s y z) -> (Prod.Lex.{u1, u2} α β r s x z)
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {r : α -> α -> Prop} {s : β -> β -> Prop} [_inst_1 : IsTrans.{u2} α r] [_inst_2 : IsTrans.{u1} β s] {x : Prod.{u2, u1} α β} {y : Prod.{u2, u1} α β} {z : Prod.{u2, u1} α β}, (Prod.Lex.{u2, u1} α β r s x y) -> (Prod.Lex.{u2, u1} α β r s y z) -> (Prod.Lex.{u2, u1} α β r s x z)
Case conversion may be inaccurate. Consider using '#align prod.lex.trans Prod.Lex.transₓ'. -/
@[trans]
theorem Lex.trans {r : α → α → Prop} {s : β → β → Prop} [IsTrans α r] [IsTrans β s] :
    ∀ {x y z : α × β}, Prod.Lex r s x y → Prod.Lex r s y z → Prod.Lex r s x z
  | (x₁, x₂), (y₁, y₂), (z₁, z₂), lex.left _ _ hxy₁, lex.left _ _ hyz₁ =>
    Lex.left _ _ (trans hxy₁ hyz₁)
  | (x₁, x₂), (y₁, y₂), (z₁, z₂), lex.left _ _ hxy₁, lex.right _ hyz₂ => Lex.left _ _ hxy₁
  | (x₁, x₂), (y₁, y₂), (z₁, z₂), lex.right _ _, lex.left _ _ hyz₁ => Lex.left _ _ hyz₁
  | (x₁, x₂), (y₁, y₂), (z₁, z₂), lex.right _ hxy₂, lex.right _ hyz₂ =>
    Lex.right _ (trans hxy₂ hyz₂)
#align prod.lex.trans Prod.Lex.trans

instance {r : α → α → Prop} {s : β → β → Prop} [IsTrans α r] [IsTrans β s] :
    IsTrans (α × β) (Lex r s) :=
  ⟨fun _ _ _ => Lex.trans⟩

instance {r : α → α → Prop} {s : β → β → Prop} [IsStrictOrder α r] [IsAntisymm β s] :
    IsAntisymm (α × β) (Lex r s) :=
  ⟨fun x₁ x₂ h₁₂ h₂₁ =>
    match x₁, x₂, h₁₂, h₂₁ with
    | (a₁, b₁), (a₂, b₂), lex.left _ _ hr₁, lex.left _ _ hr₂ => (irrefl a₁ (trans hr₁ hr₂)).elim
    | (a₁, b₁), (a₂, b₂), lex.left _ _ hr₁, lex.right _ _ => (irrefl _ hr₁).elim
    | (a₁, b₁), (a₂, b₂), lex.right _ _, lex.left _ _ hr₂ => (irrefl _ hr₂).elim
    | (a₁, b₁), (a₂, b₂), lex.right _ hs₁, lex.right _ hs₂ => antisymm hs₁ hs₂ ▸ rfl⟩

#print Prod.isTotal_left /-
instance isTotal_left {r : α → α → Prop} {s : β → β → Prop} [IsTotal α r] :
    IsTotal (α × β) (Lex r s) :=
  ⟨fun ⟨a₁, b₁⟩ ⟨a₂, b₂⟩ => (IsTotal.total a₁ a₂).imp (Lex.left _ _) (Lex.left _ _)⟩
#align prod.is_total_left Prod.isTotal_left
-/

#print Prod.isTotal_right /-
instance isTotal_right {r : α → α → Prop} {s : β → β → Prop} [IsTrichotomous α r] [IsTotal β s] :
    IsTotal (α × β) (Lex r s) :=
  ⟨fun ⟨i, a⟩ ⟨j, b⟩ => by
    obtain hij | rfl | hji := trichotomous_of r i j
    · exact Or.inl (lex.left _ _ hij)
    · exact (total_of s a b).imp (lex.right _) (lex.right _)
    · exact Or.inr (lex.left _ _ hji)⟩
#align prod.is_total_right Prod.isTotal_right
-/

instance isTrichotomous [IsTrichotomous α r] [IsTrichotomous β s] :
    IsTrichotomous (α × β) (Lex r s) :=
  ⟨fun ⟨i, a⟩ ⟨j, b⟩ => by
    obtain hij | rfl | hji := trichotomous_of r i j
    · exact Or.inl (lex.left _ _ hij)
    · exact (trichotomous_of s a b).imp3 (lex.right _) (congr_arg _) (lex.right _)
    · exact Or.inr (Or.inr <| lex.left _ _ hji)⟩
#align prod.is_trichotomous Prod.isTrichotomous

end Prod

open Prod

namespace Function

variable {f : α → γ} {g : β → δ} {f₁ : α → β} {g₁ : γ → δ} {f₂ : β → α} {g₂ : δ → γ}

/- warning: function.injective.prod_map -> Function.Injective.Prod_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {f : α -> γ} {g : β -> δ}, (Function.Injective.{succ u1, succ u3} α γ f) -> (Function.Injective.{succ u2, succ u4} β δ g) -> (Function.Injective.{max (succ u1) (succ u2), max (succ u3) (succ u4)} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {f : α -> γ} {g : β -> δ}, (Function.Injective.{succ u4, succ u3} α γ f) -> (Function.Injective.{succ u2, succ u1} β δ g) -> (Function.Injective.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u4, u2} α β) (Prod.{u3, u1} γ δ) (Prod.map.{u4, u3, u2, u1} α γ β δ f g))
Case conversion may be inaccurate. Consider using '#align function.injective.prod_map Function.Injective.Prod_mapₓ'. -/
theorem Injective.Prod_map (hf : Injective f) (hg : Injective g) : Injective (map f g) :=
  fun x y h => ext (hf (ext_iff.1 h).1) (hg <| (ext_iff.1 h).2)
#align function.injective.prod_map Function.Injective.Prod_map

/- warning: function.surjective.prod_map -> Function.Surjective.Prod_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {f : α -> γ} {g : β -> δ}, (Function.Surjective.{succ u1, succ u3} α γ f) -> (Function.Surjective.{succ u2, succ u4} β δ g) -> (Function.Surjective.{max (succ u1) (succ u2), max (succ u3) (succ u4)} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {f : α -> γ} {g : β -> δ}, (Function.Surjective.{succ u4, succ u3} α γ f) -> (Function.Surjective.{succ u2, succ u1} β δ g) -> (Function.Surjective.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u4, u2} α β) (Prod.{u3, u1} γ δ) (Prod.map.{u4, u3, u2, u1} α γ β δ f g))
Case conversion may be inaccurate. Consider using '#align function.surjective.prod_map Function.Surjective.Prod_mapₓ'. -/
theorem Surjective.Prod_map (hf : Surjective f) (hg : Surjective g) : Surjective (map f g) :=
  fun p =>
  let ⟨x, hx⟩ := hf p.1
  let ⟨y, hy⟩ := hg p.2
  ⟨(x, y), Prod.ext hx hy⟩
#align function.surjective.prod_map Function.Surjective.Prod_map

/- warning: function.bijective.prod_map -> Function.Bijective.Prod_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {f : α -> γ} {g : β -> δ}, (Function.Bijective.{succ u1, succ u3} α γ f) -> (Function.Bijective.{succ u2, succ u4} β δ g) -> (Function.Bijective.{max (succ u1) (succ u2), max (succ u3) (succ u4)} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u1}} {f : α -> γ} {g : β -> δ}, (Function.Bijective.{succ u4, succ u3} α γ f) -> (Function.Bijective.{succ u2, succ u1} β δ g) -> (Function.Bijective.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u4, u2} α β) (Prod.{u3, u1} γ δ) (Prod.map.{u4, u3, u2, u1} α γ β δ f g))
Case conversion may be inaccurate. Consider using '#align function.bijective.prod_map Function.Bijective.Prod_mapₓ'. -/
theorem Bijective.Prod_map (hf : Bijective f) (hg : Bijective g) : Bijective (map f g) :=
  ⟨hf.1.Prod_map hg.1, hf.2.Prod_map hg.2⟩
#align function.bijective.prod_map Function.Bijective.Prod_map

/- warning: function.left_inverse.prod_map -> Function.LeftInverse.Prod_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, (Function.LeftInverse.{succ u2, succ u1} β α f₁ f₂) -> (Function.LeftInverse.{succ u4, succ u3} δ γ g₁ g₂) -> (Function.LeftInverse.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u2, u4} β δ) (Prod.{u1, u3} α γ) (Prod.map.{u1, u2, u3, u4} α β γ δ f₁ g₁) (Prod.map.{u2, u1, u4, u3} β α δ γ f₂ g₂))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u4}} {γ : Type.{u1}} {δ : Type.{u2}} {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, (Function.LeftInverse.{succ u4, succ u3} β α f₁ f₂) -> (Function.LeftInverse.{succ u2, succ u1} δ γ g₁ g₂) -> (Function.LeftInverse.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u4, u2} β δ) (Prod.{u3, u1} α γ) (Prod.map.{u3, u4, u1, u2} α β γ δ f₁ g₁) (Prod.map.{u4, u3, u2, u1} β α δ γ f₂ g₂))
Case conversion may be inaccurate. Consider using '#align function.left_inverse.prod_map Function.LeftInverse.Prod_mapₓ'. -/
theorem LeftInverse.Prod_map (hf : LeftInverse f₁ f₂) (hg : LeftInverse g₁ g₂) :
    LeftInverse (map f₁ g₁) (map f₂ g₂) := fun a => by
  rw [Prod.map_map, hf.comp_eq_id, hg.comp_eq_id, map_id, id]
#align function.left_inverse.prod_map Function.LeftInverse.Prod_map

/- warning: function.right_inverse.prod_map -> Function.RightInverse.Prod_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, (Function.RightInverse.{succ u2, succ u1} β α f₁ f₂) -> (Function.RightInverse.{succ u4, succ u3} δ γ g₁ g₂) -> (Function.RightInverse.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u2, u4} β δ) (Prod.{u1, u3} α γ) (Prod.map.{u1, u2, u3, u4} α β γ δ f₁ g₁) (Prod.map.{u2, u1, u4, u3} β α δ γ f₂ g₂))
but is expected to have type
  forall {α : Type.{u3}} {β : Type.{u4}} {γ : Type.{u1}} {δ : Type.{u2}} {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, (Function.RightInverse.{succ u4, succ u3} β α f₁ f₂) -> (Function.RightInverse.{succ u2, succ u1} δ γ g₁ g₂) -> (Function.RightInverse.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u4, u2} β δ) (Prod.{u3, u1} α γ) (Prod.map.{u3, u4, u1, u2} α β γ δ f₁ g₁) (Prod.map.{u4, u3, u2, u1} β α δ γ f₂ g₂))
Case conversion may be inaccurate. Consider using '#align function.right_inverse.prod_map Function.RightInverse.Prod_mapₓ'. -/
theorem RightInverse.Prod_map :
    RightInverse f₁ f₂ → RightInverse g₁ g₂ → RightInverse (map f₁ g₁) (map f₂ g₂) :=
  LeftInverse.Prod_map
#align function.right_inverse.prod_map Function.RightInverse.Prod_map

/- warning: function.involutive.prod_map -> Function.Involutive.Prod_map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> α} {g : β -> β}, (Function.Involutive.{succ u1} α f) -> (Function.Involutive.{succ u2} β g) -> (Function.Involutive.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.map.{u1, u1, u2, u2} α α β β f g))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {f : α -> α} {g : β -> β}, (Function.Involutive.{succ u2} α f) -> (Function.Involutive.{succ u1} β g) -> (Function.Involutive.{max (succ u1) (succ u2)} (Prod.{u2, u1} α β) (Prod.map.{u2, u2, u1, u1} α α β β f g))
Case conversion may be inaccurate. Consider using '#align function.involutive.prod_map Function.Involutive.Prod_mapₓ'. -/
theorem Involutive.Prod_map {f : α → α} {g : β → β} :
    Involutive f → Involutive g → Involutive (map f g) :=
  LeftInverse.Prod_map
#align function.involutive.prod_map Function.Involutive.Prod_map

end Function

namespace Prod

open Function

/- warning: prod.map_injective -> Prod.map_injective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} [_inst_1 : Nonempty.{succ u1} α] [_inst_2 : Nonempty.{succ u2} β] {f : α -> γ} {g : β -> δ}, Iff (Function.Injective.{max (succ u1) (succ u2), max (succ u3) (succ u4)} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g)) (And (Function.Injective.{succ u1, succ u3} α γ f) (Function.Injective.{succ u2, succ u4} β δ g))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u1}} {δ : Type.{u2}} [_inst_1 : Nonempty.{succ u4} α] [_inst_2 : Nonempty.{succ u3} β] {f : α -> γ} {g : β -> δ}, Iff (Function.Injective.{max (succ u3) (succ u4), max (succ u2) (succ u1)} (Prod.{u4, u3} α β) (Prod.{u1, u2} γ δ) (Prod.map.{u4, u1, u3, u2} α γ β δ f g)) (And (Function.Injective.{succ u4, succ u1} α γ f) (Function.Injective.{succ u3, succ u2} β δ g))
Case conversion may be inaccurate. Consider using '#align prod.map_injective Prod.map_injectiveₓ'. -/
@[simp]
theorem map_injective [Nonempty α] [Nonempty β] {f : α → γ} {g : β → δ} :
    Injective (map f g) ↔ Injective f ∧ Injective g :=
  ⟨fun h =>
    ⟨fun a₁ a₂ ha => by
      inhabit β
      injection
        @h (a₁, default) (a₂, default) (congr_arg (fun c : γ => Prod.mk c (g default)) ha : _),
      fun b₁ b₂ hb => by
      inhabit α
      injection @h (default, b₁) (default, b₂) (congr_arg (Prod.mk (f default)) hb : _)⟩,
    fun h => h.1.Prod_map h.2⟩
#align prod.map_injective Prod.map_injective

/- warning: prod.map_surjective -> Prod.map_surjective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} [_inst_1 : Nonempty.{succ u3} γ] [_inst_2 : Nonempty.{succ u4} δ] {f : α -> γ} {g : β -> δ}, Iff (Function.Surjective.{max (succ u1) (succ u2), max (succ u3) (succ u4)} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g)) (And (Function.Surjective.{succ u1, succ u3} α γ f) (Function.Surjective.{succ u2, succ u4} β δ g))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u4}} {δ : Type.{u3}} [_inst_1 : Nonempty.{succ u4} γ] [_inst_2 : Nonempty.{succ u3} δ] {f : α -> γ} {g : β -> δ}, Iff (Function.Surjective.{max (succ u2) (succ u1), max (succ u3) (succ u4)} (Prod.{u1, u2} α β) (Prod.{u4, u3} γ δ) (Prod.map.{u1, u4, u2, u3} α γ β δ f g)) (And (Function.Surjective.{succ u1, succ u4} α γ f) (Function.Surjective.{succ u2, succ u3} β δ g))
Case conversion may be inaccurate. Consider using '#align prod.map_surjective Prod.map_surjectiveₓ'. -/
@[simp]
theorem map_surjective [Nonempty γ] [Nonempty δ] {f : α → γ} {g : β → δ} :
    Surjective (map f g) ↔ Surjective f ∧ Surjective g :=
  ⟨fun h =>
    ⟨fun c => by
      inhabit δ
      obtain ⟨⟨a, b⟩, h⟩ := h (c, default)
      exact ⟨a, congr_arg Prod.fst h⟩, fun d =>
      by
      inhabit γ
      obtain ⟨⟨a, b⟩, h⟩ := h (default, d)
      exact ⟨b, congr_arg Prod.snd h⟩⟩,
    fun h => h.1.Prod_map h.2⟩
#align prod.map_surjective Prod.map_surjective

/- warning: prod.map_bijective -> Prod.map_bijective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} [_inst_1 : Nonempty.{succ u1} α] [_inst_2 : Nonempty.{succ u2} β] {f : α -> γ} {g : β -> δ}, Iff (Function.Bijective.{max (succ u1) (succ u2), max (succ u3) (succ u4)} (Prod.{u1, u2} α β) (Prod.{u3, u4} γ δ) (Prod.map.{u1, u3, u2, u4} α γ β δ f g)) (And (Function.Bijective.{succ u1, succ u3} α γ f) (Function.Bijective.{succ u2, succ u4} β δ g))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u3}} {γ : Type.{u1}} {δ : Type.{u2}} [_inst_1 : Nonempty.{succ u4} α] [_inst_2 : Nonempty.{succ u3} β] {f : α -> γ} {g : β -> δ}, Iff (Function.Bijective.{max (succ u3) (succ u4), max (succ u2) (succ u1)} (Prod.{u4, u3} α β) (Prod.{u1, u2} γ δ) (Prod.map.{u4, u1, u3, u2} α γ β δ f g)) (And (Function.Bijective.{succ u4, succ u1} α γ f) (Function.Bijective.{succ u3, succ u2} β δ g))
Case conversion may be inaccurate. Consider using '#align prod.map_bijective Prod.map_bijectiveₓ'. -/
@[simp]
theorem map_bijective [Nonempty α] [Nonempty β] {f : α → γ} {g : β → δ} :
    Bijective (map f g) ↔ Bijective f ∧ Bijective g :=
  by
  haveI := Nonempty.map f ‹_›
  haveI := Nonempty.map g ‹_›
  exact (map_injective.and map_surjective).trans (and_and_and_comm _ _ _ _)
#align prod.map_bijective Prod.map_bijective

/- warning: prod.map_left_inverse -> Prod.map_leftInverse is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} [_inst_1 : Nonempty.{succ u2} β] [_inst_2 : Nonempty.{succ u4} δ] {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, Iff (Function.LeftInverse.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u2, u4} β δ) (Prod.{u1, u3} α γ) (Prod.map.{u1, u2, u3, u4} α β γ δ f₁ g₁) (Prod.map.{u2, u1, u4, u3} β α δ γ f₂ g₂)) (And (Function.LeftInverse.{succ u2, succ u1} β α f₁ f₂) (Function.LeftInverse.{succ u4, succ u3} δ γ g₁ g₂))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u4}} {γ : Type.{u2}} {δ : Type.{u3}} [_inst_1 : Nonempty.{succ u4} β] [_inst_2 : Nonempty.{succ u3} δ] {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, Iff (Function.LeftInverse.{max (succ u3) (succ u4), max (succ u2) (succ u1)} (Prod.{u4, u3} β δ) (Prod.{u1, u2} α γ) (Prod.map.{u1, u4, u2, u3} α β γ δ f₁ g₁) (Prod.map.{u4, u1, u3, u2} β α δ γ f₂ g₂)) (And (Function.LeftInverse.{succ u4, succ u1} β α f₁ f₂) (Function.LeftInverse.{succ u3, succ u2} δ γ g₁ g₂))
Case conversion may be inaccurate. Consider using '#align prod.map_left_inverse Prod.map_leftInverseₓ'. -/
@[simp]
theorem map_leftInverse [Nonempty β] [Nonempty δ] {f₁ : α → β} {g₁ : γ → δ} {f₂ : β → α}
    {g₂ : δ → γ} : LeftInverse (map f₁ g₁) (map f₂ g₂) ↔ LeftInverse f₁ f₂ ∧ LeftInverse g₁ g₂ :=
  ⟨fun h =>
    ⟨fun b => by
      inhabit δ
      exact congr_arg Prod.fst (h (b, default)), fun d =>
      by
      inhabit β
      exact congr_arg Prod.snd (h (default, d))⟩,
    fun h => h.1.Prod_map h.2⟩
#align prod.map_left_inverse Prod.map_leftInverse

/- warning: prod.map_right_inverse -> Prod.map_rightInverse is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {γ : Type.{u3}} {δ : Type.{u4}} [_inst_1 : Nonempty.{succ u1} α] [_inst_2 : Nonempty.{succ u3} γ] {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, Iff (Function.RightInverse.{max (succ u2) (succ u4), max (succ u1) (succ u3)} (Prod.{u2, u4} β δ) (Prod.{u1, u3} α γ) (Prod.map.{u1, u2, u3, u4} α β γ δ f₁ g₁) (Prod.map.{u2, u1, u4, u3} β α δ γ f₂ g₂)) (And (Function.RightInverse.{succ u2, succ u1} β α f₁ f₂) (Function.RightInverse.{succ u4, succ u3} δ γ g₁ g₂))
but is expected to have type
  forall {α : Type.{u4}} {β : Type.{u1}} {γ : Type.{u3}} {δ : Type.{u2}} [_inst_1 : Nonempty.{succ u4} α] [_inst_2 : Nonempty.{succ u3} γ] {f₁ : α -> β} {g₁ : γ -> δ} {f₂ : β -> α} {g₂ : δ -> γ}, Iff (Function.RightInverse.{max (succ u2) (succ u1), max (succ u3) (succ u4)} (Prod.{u1, u2} β δ) (Prod.{u4, u3} α γ) (Prod.map.{u4, u1, u3, u2} α β γ δ f₁ g₁) (Prod.map.{u1, u4, u2, u3} β α δ γ f₂ g₂)) (And (Function.RightInverse.{succ u1, succ u4} β α f₁ f₂) (Function.RightInverse.{succ u2, succ u3} δ γ g₁ g₂))
Case conversion may be inaccurate. Consider using '#align prod.map_right_inverse Prod.map_rightInverseₓ'. -/
@[simp]
theorem map_rightInverse [Nonempty α] [Nonempty γ] {f₁ : α → β} {g₁ : γ → δ} {f₂ : β → α}
    {g₂ : δ → γ} : RightInverse (map f₁ g₁) (map f₂ g₂) ↔ RightInverse f₁ f₂ ∧ RightInverse g₁ g₂ :=
  map_leftInverse
#align prod.map_right_inverse Prod.map_rightInverse

/- warning: prod.map_involutive -> Prod.map_involutive is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Nonempty.{succ u1} α] [_inst_2 : Nonempty.{succ u2} β] {f : α -> α} {g : β -> β}, Iff (Function.Involutive.{max (succ u1) (succ u2)} (Prod.{u1, u2} α β) (Prod.map.{u1, u1, u2, u2} α α β β f g)) (And (Function.Involutive.{succ u1} α f) (Function.Involutive.{succ u2} β g))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} [_inst_1 : Nonempty.{succ u2} α] [_inst_2 : Nonempty.{succ u1} β] {f : α -> α} {g : β -> β}, Iff (Function.Involutive.{max (succ u1) (succ u2)} (Prod.{u2, u1} α β) (Prod.map.{u2, u2, u1, u1} α α β β f g)) (And (Function.Involutive.{succ u2} α f) (Function.Involutive.{succ u1} β g))
Case conversion may be inaccurate. Consider using '#align prod.map_involutive Prod.map_involutiveₓ'. -/
@[simp]
theorem map_involutive [Nonempty α] [Nonempty β] {f : α → α} {g : β → β} :
    Involutive (map f g) ↔ Involutive f ∧ Involutive g :=
  map_leftInverse
#align prod.map_involutive Prod.map_involutive

end Prod


/-
Copyright (c) 2022 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser
-/

import linear_algebra.exterior_algebra.basic
import linear_algebra.clifford_algebra.fold
import linear_algebra.clifford_algebra.conjugation

/-!
# Contraction in Clifford Algebras

This file contains some of the results from [grinberg_clifford_2016][].
The key result is `clifford_algebra.equiv_exterior`.

## Main definitions

* `clifford_algebra.contract_left`: contract a multivector by a `module.dual R M` on the left.
* `clifford_algebra.contract_right`: contract a multivector by a `module.dual R M` on the right.
* `clifford_algebra.change_form`: convert between two algebras of different quadratic form, sending
  vectors to vectors. The difference of the quadratic forms must be a bilinear form.
* `clifford_algebra.equiv_exterior`: in characteristic not-two, the `clifford_algebra Q` is
  isomorphic as a module to the exterior algebra.

## Implementation notes

This file somewhat follows [grinberg_clifford_2016][], although we are missing some of the induction
principles needed to prove many of the results. Here, we avoid the quotient-based approach described
in [grinberg_clifford_2016][], instead directly constructing our objects using the universal
property.

Note that [grinberg_clifford_2016][] concludes that its contents are not novel, and are in fact just
a rehash of parts of [bourbaki2007][]; we should at some point consider swapping our references to
refer to the latter.

Within this file, we use the local notation
* `x ⌊ d` for `contract_right x d`
* `d ⌋ x` for `contract_left d x`

-/

universes u1 u2 u3

variables {R : Type u1} [comm_ring R]
variables {M : Type u2} [add_comm_group M] [module R M]
variables (Q : quadratic_form R M)

namespace clifford_algebra

section contract_left

variables (d d' : module.dual R M)

/-- Auxiliary construction for `clifford_algebra.contract_left` -/
@[simps]
def contract_left_aux (d : module.dual R M) :
  M →ₗ[R] clifford_algebra Q × clifford_algebra Q →ₗ[R] clifford_algebra Q :=
begin
  have v_mul := (algebra.lmul R (clifford_algebra Q)).to_linear_map ∘ₗ (ι Q),
  exact d.smul_right (linear_map.fst _ (clifford_algebra Q) (clifford_algebra Q)) -
        v_mul.compl₂ (linear_map.snd _ (clifford_algebra Q) _),
end

lemma contract_left_aux_contract_left_aux (v : M) (x : clifford_algebra Q)
  (fx : clifford_algebra Q) :
  contract_left_aux Q d v (ι Q v * x, contract_left_aux Q d v (x, fx)) = Q v • fx :=
begin
  simp only [contract_left_aux_apply_apply],
  rw [mul_sub, ←mul_assoc, ι_sq_scalar, ←algebra.smul_def, ←sub_add, mul_smul_comm, sub_self,
    zero_add],
end

variables {Q}

/-- Contract an element of the clifford algebra with an element `d : module.dual R M` from the left.

Note that $v ⌋ x$ is spelt `contract_left (Q.associated v) x`.

This includes [grinberg_clifford_2016][] Theorem 10.75 -/
def contract_left : module.dual R M →ₗ[R] clifford_algebra Q →ₗ[R] clifford_algebra Q :=
{ to_fun := λ d, foldr' Q (contract_left_aux Q d) (contract_left_aux_contract_left_aux Q d) 0,
  map_add' := λ d₁ d₂, linear_map.ext $ λ x, begin
    rw linear_map.add_apply,
    induction x using clifford_algebra.left_induction with r x y hx hy m x hx,
    { simp_rw [foldr'_algebra_map, smul_zero, zero_add] },
    { rw [map_add, map_add, map_add, add_add_add_comm, hx, hy] },
    { rw [foldr'_ι_mul, foldr'_ι_mul, foldr'_ι_mul, hx],
      dsimp only [contract_left_aux_apply_apply],
      rw [sub_add_sub_comm, mul_add, linear_map.add_apply, add_smul] }
  end,
  map_smul' := λ c d, linear_map.ext $ λ x,  begin
    rw [linear_map.smul_apply, ring_hom.id_apply],
    induction x using clifford_algebra.left_induction with r x y hx hy m x hx,
    { simp_rw [foldr'_algebra_map, smul_zero] },
    { rw [map_add, map_add, smul_add, hx, hy] },
    { rw [foldr'_ι_mul, foldr'_ι_mul, hx],
      dsimp only [contract_left_aux_apply_apply],
      rw [linear_map.smul_apply, smul_assoc, mul_smul_comm, smul_sub], }
  end }

/-- Contract an element of the clifford algebra with an element `d : module.dual R M` from the
right.

Note that $x ⌊ v$ is spelt `contract_right x (Q.associated v)`.

This includes [grinberg_clifford_2016][] Theorem 16.75 -/
def contract_right : clifford_algebra Q →ₗ[R] module.dual R M →ₗ[R] clifford_algebra Q :=
linear_map.flip (linear_map.compl₂ (linear_map.compr₂ contract_left reverse) reverse)

lemma contract_right_eq (x : clifford_algebra Q) :
  contract_right x d = reverse (contract_left d $ reverse x) := rfl

local infix `⌋`:70 := contract_left
local infix `⌊`:70 := contract_right

/-- This is [grinberg_clifford_2016][] Theorem 6  -/
lemma contract_left_ι_mul (a : M) (b : clifford_algebra Q) :
  d ⌋ (ι Q a * b) = d a • b - ι Q a * (d ⌋ b) :=
foldr'_ι_mul _ _ _ _ _ _

/-- This is [grinberg_clifford_2016][] Theorem 12  -/
lemma contract_right_mul_ι (a : M) (b : clifford_algebra Q) :
  (b * ι Q a) ⌊ d = d a • b - (b ⌊ d) * ι Q a :=
by rw [contract_right_eq, reverse.map_mul, reverse_ι, contract_left_ι_mul, map_sub, map_smul,
    reverse_reverse, reverse.map_mul, reverse_ι, contract_right_eq]

lemma contract_left_algebra_map_mul (r : R) (b : clifford_algebra Q) :
  d ⌋ (algebra_map _ _ r * b) = algebra_map _ _ r * (d ⌋ b) :=
by rw [←algebra.smul_def, map_smul, algebra.smul_def]

lemma contract_left_mul_algebra_map (a : clifford_algebra Q) (r : R) :
  d ⌋ (a * algebra_map _ _ r) = (d ⌋ a) * algebra_map _ _ r :=
by rw [←algebra.commutes, contract_left_algebra_map_mul, algebra.commutes]

lemma contract_right_algebra_map_mul (r : R) (b : clifford_algebra Q) :
  (algebra_map _ _ r * b) ⌊ d = algebra_map _ _ r * (b ⌊ d) :=
by rw [←algebra.smul_def, linear_map.map_smul₂, algebra.smul_def]

lemma contract_right_mul_algebra_map (a : clifford_algebra Q) (r : R) :
  (a * algebra_map _ _ r) ⌊ d = (a ⌊ d) * algebra_map _ _ r :=
by rw [←algebra.commutes, contract_right_algebra_map_mul, algebra.commutes]

variables (Q)

@[simp] lemma contract_left_ι (x : M) : d ⌋ ι Q x = algebra_map R _ (d x) :=
(foldr'_ι _ _ _ _ _).trans $
  by simp_rw [contract_left_aux_apply_apply, mul_zero, sub_zero, algebra.algebra_map_eq_smul_one]

@[simp] lemma contract_right_ι (x : M) : ι Q x ⌊ d = algebra_map R _ (d x) :=
by rw [contract_right_eq, reverse_ι, contract_left_ι, reverse.commutes]

@[simp] lemma contract_left_algebra_map (r : R) :
  d ⌋ (algebra_map R (clifford_algebra Q) r) = 0 :=
(foldr'_algebra_map _ _ _ _ _).trans $ smul_zero _

@[simp]

@[simp] lemma contract_left_one : d ⌋ (1 : clifford_algebra Q) = 0 :=
by simpa only [map_one] using contract_left_algebra_map Q d 1

@[simp] lemma contract_right_one : (1 : clifford_algebra Q) ⌊ d = 0 :=
by simpa only [map_one] using contract_right_algebra_map Q d 1

variables {Q}

/-- This is [grinberg_clifford_2016][] Theorem 7 -/
lemma contract_left_contract_left (x : clifford_algebra Q) :
  d ⌋ (d ⌋ x) = 0 :=
begin
  induction x using clifford_algebra.left_induction with r x y hx hy m x hx,
  { simp_rw [contract_left_algebra_map, map_zero] },
  { rw [map_add, map_add, hx, hy, add_zero] },
  { rw [contract_left_ι_mul, map_sub, contract_left_ι_mul, hx, linear_map.map_smul, mul_zero,
      sub_zero, sub_self], }
end

/-- This is [grinberg_clifford_2016][] Theorem 13 -/
lemma contract_right_contract_right (x : clifford_algebra Q) :
  (x ⌊ d) ⌊ d = 0 :=
by rw [contract_right_eq, contract_right_eq, reverse_reverse, contract_left_contract_left,
  map_zero]

/-- This is [grinberg_clifford_2016][] Theorem 8 -/
lemma contract_left_comm (x : clifford_algebra Q) : d ⌋ (d' ⌋ x) = -(d' ⌋ (d ⌋ x)) :=
begin
  induction x using clifford_algebra.left_induction with r x y hx hy m x hx,
  { simp_rw [contract_left_algebra_map, map_zero, neg_zero] },
  { rw [map_add, map_add, map_add, map_add, hx, hy, neg_add] },
  { simp only [contract_left_ι_mul, map_sub, linear_map.map_smul],
    rw [neg_sub, sub_sub_eq_add_sub, hx, mul_neg, ←sub_eq_add_neg] }
end

/-- This is [grinberg_clifford_2016][] Theorem 14 -/
lemma contract_right_comm (x : clifford_algebra Q) : (x ⌊ d) ⌊ d' = -((x ⌊ d') ⌊ d) :=
by rw [contract_right_eq, contract_right_eq, contract_right_eq, contract_right_eq,
  reverse_reverse, reverse_reverse, contract_left_comm, map_neg]

/- TODO:
lemma contract_right_contract_left (x : clifford_algebra Q) : (d ⌋ x) ⌊ d' = d ⌋ (x ⌊ d') :=
-/

end contract_left

local infix `⌋`:70 := contract_left
local infix `⌊`:70 := contract_right

/-- Auxiliary construction for `clifford_algebra.change_form` -/
@[simps]
def change_form_aux (B : bilin_form R M) : M →ₗ[R] clifford_algebra Q →ₗ[R] clifford_algebra Q :=
begin
  have v_mul := (algebra.lmul R (clifford_algebra Q)).to_linear_map ∘ₗ ι Q,
  exact v_mul - (contract_left ∘ₗ B.to_lin) ,
end

lemma change_form_aux_change_form_aux (B : bilin_form R M) (v : M) (x : clifford_algebra Q) :
  change_form_aux Q B v (change_form_aux Q B v x) = (Q v - B v v) • x :=
begin
  simp only [change_form_aux_apply_apply],
  rw [mul_sub, ←mul_assoc, ι_sq_scalar, map_sub, contract_left_ι_mul, ←sub_add, sub_sub_sub_comm,
    ←algebra.smul_def, bilin_form.to_lin_apply, sub_self, sub_zero, contract_left_contract_left,
    add_zero, sub_smul],
end

variables {Q}

variables {Q' Q'' : quadratic_form R M} {B B' : bilin_form R M}
variables (h : B.to_quadratic_form = Q' - Q) (h' : B'.to_quadratic_form = Q'' - Q')

/-- Convert between two algebras of different quadratic form, sending vector to vectors, scalars to
scalars, and adjusting products by a contraction term.

This is $\lambda_B$ from [bourbaki2007][] $9 Lemma 2. -/
def change_form (h : B.to_quadratic_form = Q' - Q) :
  clifford_algebra Q →ₗ[R] clifford_algebra Q' :=
foldr Q (change_form_aux Q' B) (λ m x, (change_form_aux_change_form_aux Q' B m x).trans $
  begin
    dsimp [←bilin_form.to_quadratic_form_apply],
    rw [h, quadratic_form.sub_apply, sub_sub_cancel],
  end) 1

/-- Auxiliary lemma used as an argument to `clifford_algebra.change_form` -/
lemma change_form.zero_proof : (0 : bilin_form R M).to_quadratic_form = Q - Q :=
(sub_self _).symm

/-- Auxiliary lemma used as an argument to `clifford_algebra.change_form` -/
lemma change_form.add_proof : (B + B').to_quadratic_form = Q'' - Q :=
(congr_arg2 (+) h h').trans $ sub_add_sub_cancel' _ _ _

/-- Auxiliary lemma used as an argument to `clifford_algebra.change_form` -/
lemma change_form.neg_proof : (-B).to_quadratic_form = Q - Q' :=
(congr_arg has_neg.neg h).trans $ neg_sub _ _

lemma change_form.associated_neg_proof [invertible (2 : R)] :
  (-Q).associated.to_quadratic_form = 0 - Q :=
by simp [quadratic_form.to_quadratic_form_associated]

@[simp]
lemma change_form_algebra_map (r : R) : change_form h (algebra_map R _ r) = algebra_map R _ r :=
(foldr_algebra_map _ _ _ _ _).trans $ eq.symm $ algebra.algebra_map_eq_smul_one r

@[simp] lemma change_form_one : change_form h (1 : clifford_algebra Q) = 1 :=
by simpa using change_form_algebra_map h (1 : R)

@[simp]
lemma change_form_ι (m : M) : change_form h (ι _ m) = ι _ m :=
(foldr_ι _ _ _ _ _).trans $ eq.symm $
  by rw [change_form_aux_apply_apply, mul_one, contract_left_one, sub_zero]

lemma change_form_ι_mul (m : M) (x : clifford_algebra Q) :
  change_form h (ι _ m * x) = ι _ m * change_form h x - bilin_form.to_lin B m ⌋ change_form h x :=
(foldr_mul _ _ _ _ _ _).trans $ begin rw foldr_ι, refl, end

lemma change_form_ι_mul_ι (m₁ m₂ : M) :
  change_form h (ι _ m₁ * ι _ m₂) = ι _ m₁ * ι _ m₂ - algebra_map _ _ (B m₁ m₂) :=
by rw [change_form_ι_mul, change_form_ι, contract_left_ι, bilin_form.to_lin_apply]

/-- Theorem 23 of [grinberg_clifford_2016][] -/
lemma change_form_contract_left (d : module.dual R M) (x : clifford_algebra Q) :
  change_form h (d ⌋ x) = d ⌋ change_form h x :=
begin
  induction x using clifford_algebra.left_induction with r x y hx hy m x hx,
  { simp only [contract_left_algebra_map, change_form_algebra_map, map_zero] },
  { rw [map_add, map_add, map_add, map_add, hx, hy] },
  { simp only [contract_left_ι_mul, change_form_ι_mul, map_sub, linear_map.map_smul],
    rw [←hx, contract_left_comm, ←sub_add, sub_neg_eq_add, ←hx] }
end

lemma change_form_self_apply (x : clifford_algebra Q) :
  change_form (change_form.zero_proof) x = x :=
begin
  induction x using clifford_algebra.left_induction with r x y hx hy m x hx,
  { simp_rw [change_form_algebra_map] },
  { rw [map_add, hx, hy] },
  { rw [change_form_ι_mul, hx, map_zero, linear_map.zero_apply, map_zero, linear_map.zero_apply,
        sub_zero] }
end

@[simp]
lemma change_form_self  :
  change_form change_form.zero_proof = (linear_map.id : clifford_algebra Q →ₗ[R] _) :=
linear_map.ext $ change_form_self_apply

/-- This is [bourbaki2007][] $9 Lemma 3. -/
lemma change_form_change_form (x : clifford_algebra Q) :
  change_form h' (change_form h x) = change_form (change_form.add_proof h h') x :=
begin
  induction x using clifford_algebra.left_induction with r x y hx hy m x hx,
  { simp_rw [change_form_algebra_map] },
  { rw [map_add, map_add, map_add, hx, hy] },
  { rw [change_form_ι_mul, map_sub, change_form_ι_mul, change_form_ι_mul, hx, sub_sub, map_add,
    linear_map.add_apply, map_add, linear_map.add_apply, change_form_contract_left, hx,
    add_comm (_ : clifford_algebra Q'')] }
end

lemma change_form_comp_change_form :
  (change_form h').comp (change_form h) = change_form (change_form.add_proof h h') :=
linear_map.ext $ change_form_change_form _ _

/-- Any two algebras whose quadratic forms differ by a bilinear form are isomorphic as modules.

This is $\bar \lambda_B$ from [bourbaki2007][] $9 Proposition 3. -/
@[simps apply]
def change_form_equiv : clifford_algebra Q ≃ₗ[R] clifford_algebra Q' :=
{ to_fun := change_form h,
  inv_fun := change_form (change_form.neg_proof h),
  left_inv := λ x, (change_form_change_form _ _ x).trans $
    by simp_rw [add_right_neg, change_form_self_apply],
  right_inv := λ x, (change_form_change_form _ _ x).trans $
    by simp_rw [add_left_neg, change_form_self_apply],
  ..change_form h }

@[simp]
lemma change_form_equiv_symm :
  (change_form_equiv h).symm = change_form_equiv (change_form.neg_proof h) :=
linear_equiv.ext $ λ x, (rfl : change_form _ x = change_form _ x)

variables (Q)

/-- The module isomorphism to the exterior algebra.

Note that this holds more generally when `Q` is divisible by two, rather than only when `1` is
divisible by two; but that would be more awkward to use. -/
@[simp]
def equiv_exterior [invertible (2 : R)] : clifford_algebra Q ≃ₗ[R] exterior_algebra R M :=
change_form_equiv change_form.associated_neg_proof

end clifford_algebra


/-
Copyright (c) 2020 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import topology.algebra.module.basic
import linear_algebra.multilinear.basic

/-!
# Continuous multilinear maps

We define continuous multilinear maps as maps from `Π(i : ι), M₁ i` to `M₂` which are multilinear
and continuous, by extending the space of multilinear maps with a continuity assumption.
Here, `M₁ i` and `M₂` are modules over a ring `R`, and `ι` is an arbitrary type, and all these
spaces are also topological spaces.

## Main definitions

* `continuous_multilinear_map R M₁ M₂` is the space of continuous multilinear maps from
  `Π(i : ι), M₁ i` to `M₂`. We show that it is an `R`-module.

## Implementation notes

We mostly follow the API of multilinear maps.

## Notation

We introduce the notation `M [×n]→L[R] M'` for the space of continuous `n`-multilinear maps from
`M^n` to `M'`. This is a particular case of the general notion (where we allow varying dependent
types as the arguments of our continuous multilinear maps), but arguably the most important one,
especially when defining iterated derivatives.
-/

open function fin set
open_locale big_operators

universes u v w w₁ w₁' w₂ w₃ w₄
variables {R : Type u} {ι : Type v} {n : ℕ} {M : fin n.succ → Type w} {M₁ : ι → Type w₁}
  {M₁' : ι → Type w₁'} {M₂ : Type w₂} {M₃ : Type w₃} {M₄ : Type w₄}

/-- Continuous multilinear maps over the ring `R`, from `Πi, M₁ i` to `M₂` where `M₁ i` and `M₂`
are modules over `R` with a topological structure. In applications, there will be compatibility
conditions between the algebraic and the topological structures, but this is not needed for the
definition. -/
structure continuous_multilinear_map (R : Type u) {ι : Type v} (M₁ : ι → Type w₁) (M₂ : Type w₂)
  [semiring R] [∀i, add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
  [∀i, module R (M₁ i)] [module R M₂] [∀i, topological_space (M₁ i)] [topological_space M₂]
  extends multilinear_map R M₁ M₂ :=
(cont : continuous to_fun)

notation M `[×`:25 n `]→L[`:25 R `] ` M' := continuous_multilinear_map R (λ (i : fin n), M) M'

namespace continuous_multilinear_map

section semiring

variables [semiring R]
[Πi, add_comm_monoid (M i)] [Πi, add_comm_monoid (M₁ i)] [Πi, add_comm_monoid (M₁' i)]
  [add_comm_monoid M₂] [add_comm_monoid M₃] [add_comm_monoid M₄] [Π i, module R (M i)]
  [Π i, module R (M₁ i)]  [Π i, module R (M₁' i)] [module R M₂]
  [module R M₃] [module R M₄]
  [Π i, topological_space (M i)] [Π i, topological_space (M₁ i)] [Π i, topological_space (M₁' i)]
  [topological_space M₂] [topological_space M₃] [topological_space M₄]
(f f' : continuous_multilinear_map R M₁ M₂)

instance : has_coe_to_fun (continuous_multilinear_map R M₁ M₂) (λ _, (Π i, M₁ i) → M₂) :=
⟨λ f, f.to_fun⟩

/-- See Note [custom simps projection]. We need to specify this projection explicitly in this case,
  because it is a composition of multiple projections. -/
def simps.apply (L₁ : continuous_multilinear_map R M₁ M₂) (v : Π i, M₁ i) : M₂ := L₁ v

initialize_simps_projections continuous_multilinear_map
  (-to_multilinear_map, to_multilinear_map_to_fun → apply)

@[continuity] lemma coe_continuous : continuous (f : (Π i, M₁ i) → M₂) := f.cont

@[simp] lemma coe_coe : (f.to_multilinear_map : (Π i, M₁ i) → M₂) = f := rfl

theorem to_multilinear_map_inj :
  function.injective (continuous_multilinear_map.to_multilinear_map :
    continuous_multilinear_map R M₁ M₂ → multilinear_map R M₁ M₂)
| ⟨f, hf⟩ ⟨g, hg⟩ rfl := rfl

@[ext] theorem ext {f f' : continuous_multilinear_map R M₁ M₂} (H : ∀ x, f x = f' x) : f = f' :=
to_multilinear_map_inj $ multilinear_map.ext H

theorem ext_iff {f f' : continuous_multilinear_map R M₁ M₂} : f = f' ↔ ∀ x, f x = f' x :=
by rw [← to_multilinear_map_inj.eq_iff, multilinear_map.ext_iff]; refl

@[simp] lemma map_add [decidable_eq ι] (m : Πi, M₁ i) (i : ι) (x y : M₁ i) :
  f (update m i (x + y)) = f (update m i x) + f (update m i y) :=
f.map_add' m i x y

@[simp] lemma map_smul [decidable_eq ι] (m : Πi, M₁ i) (i : ι) (c : R) (x : M₁ i) :
  f (update m i (c • x)) = c • f (update m i x) :=
f.map_smul' m i c x

lemma map_coord_zero {m : Πi, M₁ i} (i : ι) (h : m i = 0) : f m = 0 :=
f.to_multilinear_map.map_coord_zero i h

@[simp] lemma map_zero [nonempty ι] : f 0 = 0 :=
f.to_multilinear_map.map_zero

instance : has_zero (continuous_multilinear_map R M₁ M₂) :=
⟨{ cont := continuous_const, ..(0 : multilinear_map R M₁ M₂) }⟩

instance : inhabited (continuous_multilinear_map R M₁ M₂) := ⟨0⟩

@[simp] lemma zero_apply (m : Πi, M₁ i) : (0 : continuous_multilinear_map R M₁ M₂) m = 0 := rfl

@[simp] lemma to_multilinear_map_zero :
  (0 : continuous_multilinear_map R M₁ M₂).to_multilinear_map = 0 :=
rfl
section has_smul

variables {R' R'' A : Type*} [monoid R'] [monoid R''] [semiring A]
  [Π i, module A (M₁ i)] [module A M₂]
  [distrib_mul_action R' M₂] [has_continuous_const_smul R' M₂] [smul_comm_class A R' M₂]
  [distrib_mul_action R'' M₂] [has_continuous_const_smul R'' M₂] [smul_comm_class A R'' M₂]

instance : has_smul R' (continuous_multilinear_map A M₁ M₂) :=
⟨λ c f, { cont := f.cont.const_smul c, .. c • f.to_multilinear_map }⟩

@[simp] lemma smul_apply (f : continuous_multilinear_map A M₁ M₂) (c : R') (m : Πi, M₁ i) :
  (c • f) m = c • f m := rfl

@[simp] lemma to_multilinear_map_smul (c : R') (f : continuous_multilinear_map A M₁ M₂) :
  (c • f).to_multilinear_map = c • f.to_multilinear_map :=
rfl

instance [smul_comm_class R' R'' M₂] :
  smul_comm_class R' R'' (continuous_multilinear_map A M₁ M₂) :=
⟨λ c₁ c₂ f, ext $ λ x, smul_comm _ _ _⟩

instance [has_smul R' R''] [is_scalar_tower R' R'' M₂] :
  is_scalar_tower R' R'' (continuous_multilinear_map A M₁ M₂) :=
⟨λ c₁ c₂ f, ext $ λ x, smul_assoc _ _ _⟩

instance [distrib_mul_action R'ᵐᵒᵖ M₂] [is_central_scalar R' M₂] :
  is_central_scalar R' (continuous_multilinear_map A M₁ M₂) :=
⟨λ c₁ f, ext $ λ x, op_smul_eq_smul _ _⟩

instance : mul_action R' (continuous_multilinear_map A M₁ M₂) :=
function.injective.mul_action to_multilinear_map to_multilinear_map_inj (λ _ _, rfl)

end has_smul

section has_continuous_add
variable [has_continuous_add M₂]

instance : has_add (continuous_multilinear_map R M₁ M₂) :=
⟨λ f f', ⟨f.to_multilinear_map + f'.to_multilinear_map, f.cont.add f'.cont⟩⟩

@[simp] lemma add_apply (m : Πi, M₁ i) : (f + f') m = f m + f' m := rfl

@[simp] lemma to_multilinear_map_add (f g : continuous_multilinear_map R M₁ M₂) :
  (f + g).to_multilinear_map = f.to_multilinear_map + g.to_multilinear_map :=
rfl

instance add_comm_monoid : add_comm_monoid (continuous_multilinear_map R M₁ M₂) :=
to_multilinear_map_inj.add_comm_monoid _ rfl (λ _ _, rfl) (λ _ _, rfl)

/-- Evaluation of a `continuous_multilinear_map` at a vector as an `add_monoid_hom`. -/
def apply_add_hom (m : Π i, M₁ i) : continuous_multilinear_map R M₁ M₂ →+ M₂ :=
⟨λ f, f m, rfl, λ _ _, rfl⟩

@[simp] lemma sum_apply {α : Type*} (f : α → continuous_multilinear_map R M₁ M₂)
  (m : Πi, M₁ i) {s : finset α} : (∑ a in s, f a) m = ∑ a in s, f a m :=
(apply_add_hom m).map_sum f s

end has_continuous_add

/-- If `f` is a continuous multilinear map, then `f.to_continuous_linear_map m i` is the continuous
linear map obtained by fixing all coordinates but `i` equal to those of `m`, and varying the
`i`-th coordinate. -/
def to_continuous_linear_map [decidable_eq ι] (m : Πi, M₁ i) (i : ι) : M₁ i →L[R] M₂ :=
{ cont := f.cont.comp (continuous_const.update i continuous_id),
  .. f.to_multilinear_map.to_linear_map m i }

/-- The cartesian product of two continuous multilinear maps, as a continuous multilinear map. -/
def prod (f : continuous_multilinear_map R M₁ M₂) (g : continuous_multilinear_map R M₁ M₃) :
  continuous_multilinear_map R M₁ (M₂ × M₃) :=
{ cont := f.cont.prod_mk g.cont,
  .. f.to_multilinear_map.prod g.to_multilinear_map }

@[simp] lemma prod_apply
  (f : continuous_multilinear_map R M₁ M₂) (g : continuous_multilinear_map R M₁ M₃) (m : Πi, M₁ i) :
  (f.prod g) m = (f m, g m) := rfl

/-- Combine a family of continuous multilinear maps with the same domain and codomains `M' i` into a
continuous multilinear map taking values in the space of functions `Π i, M' i`. -/
def pi {ι' : Type*} {M' : ι' → Type*} [Π i, add_comm_monoid (M' i)] [Π i, topological_space (M' i)]
  [Π i, module R (M' i)] (f : Π i, continuous_multilinear_map R M₁ (M' i)) :
  continuous_multilinear_map R M₁ (Π i, M' i) :=
{ cont := continuous_pi $ λ i, (f i).coe_continuous,
  to_multilinear_map := multilinear_map.pi (λ i, (f i).to_multilinear_map) }

@[simp] lemma coe_pi {ι' : Type*} {M' : ι' → Type*} [Π i, add_comm_monoid (M' i)]
  [Π i, topological_space (M' i)] [Π i, module R (M' i)]
  (f : Π i, continuous_multilinear_map R M₁ (M' i)) :
  ⇑(pi f) = λ m j, f j m :=
rfl

lemma pi_apply {ι' : Type*} {M' : ι' → Type*} [Π i, add_comm_monoid (M' i)]
  [Π i, topological_space (M' i)] [Π i, module R (M' i)]
  (f : Π i, continuous_multilinear_map R M₁ (M' i)) (m : Π i, M₁ i) (j : ι') :
  pi f m j = f j m :=
rfl

section
variables (R M₂)

/-- The evaluation map from `ι → M₂` to `M₂` is multilinear at a given `i` when `ι` is subsingleton.
-/
@[simps to_multilinear_map apply]
def of_subsingleton [subsingleton ι] (i' : ι) : continuous_multilinear_map R (λ _ : ι, M₂) M₂ :=
{ to_multilinear_map := multilinear_map.of_subsingleton R _ i',
  cont := continuous_apply _ }

variables (M₁) {M₂}

/-- The constant map is multilinear when `ι` is empty. -/
@[simps to_multilinear_map apply]
def const_of_is_empty [is_empty ι] (m : M₂) : continuous_multilinear_map R M₁ M₂ :=
{ to_multilinear_map := multilinear_map.const_of_is_empty R _ m,
  cont := continuous_const }

end

/-- If `g` is continuous multilinear and `f` is a collection of continuous linear maps,
then `g (f₁ m₁, ..., fₙ mₙ)` is again a continuous multilinear map, that we call
`g.comp_continuous_linear_map f`. -/
def comp_continuous_linear_map
  (g : continuous_multilinear_map R M₁' M₄) (f : Π i : ι, M₁ i →L[R] M₁' i) :
  continuous_multilinear_map R M₁ M₄ :=
{ cont := g.cont.comp $ continuous_pi $ λj, (f j).cont.comp $ continuous_apply _,
  .. g.to_multilinear_map.comp_linear_map (λ i, (f i).to_linear_map) }

@[simp] lemma comp_continuous_linear_map_apply (g : continuous_multilinear_map R M₁' M₄)
  (f : Π i : ι, M₁ i →L[R] M₁' i) (m : Π i, M₁ i) :
  g.comp_continuous_linear_map f m = g (λ i, f i $ m i) :=
rfl

/-- Composing a continuous multilinear map with a continuous linear map gives again a
continuous multilinear map. -/
def _root_.continuous_linear_map.comp_continuous_multilinear_map
  (g : M₂ →L[R] M₃) (f : continuous_multilinear_map R M₁ M₂) :
  continuous_multilinear_map R M₁ M₃ :=
{ cont := g.cont.comp f.cont,
  .. g.to_linear_map.comp_multilinear_map f.to_multilinear_map }

@[simp] lemma _root_.continuous_linear_map.comp_continuous_multilinear_map_coe (g : M₂ →L[R] M₃)
  (f : continuous_multilinear_map R M₁ M₂) :
  ((g.comp_continuous_multilinear_map f) : (Πi, M₁ i) → M₃) =
  (g : M₂ → M₃) ∘ (f : (Πi, M₁ i) → M₂) :=
by { ext m, refl }

/-- `continuous_multilinear_map.pi` as an `equiv`. -/
@[simps]
def pi_equiv {ι' : Type*} {M' : ι' → Type*} [Π i, add_comm_monoid (M' i)]
  [Π i, topological_space (M' i)] [Π i, module R (M' i)] :
  (Π i, continuous_multilinear_map R M₁ (M' i)) ≃
  continuous_multilinear_map R M₁ (Π i, M' i) :=
{ to_fun := continuous_multilinear_map.pi,
  inv_fun := λ f i, (continuous_linear_map.proj i : _ →L[R] M' i).comp_continuous_multilinear_map f,
  left_inv := λ f, by { ext, refl },
  right_inv := λ f, by { ext, refl } }

/-- In the specific case of continuous multilinear maps on spaces indexed by `fin (n+1)`, where one
can build an element of `Π(i : fin (n+1)), M i` using `cons`, one can express directly the
additivity of a multilinear map along the first variable. -/
lemma cons_add (f : continuous_multilinear_map R M M₂) (m : Π(i : fin n), M i.succ) (x y : M 0) :
  f (cons (x+y) m) = f (cons x m) + f (cons y m) :=
f.to_multilinear_map.cons_add m x y

/-- In the specific case of continuous multilinear maps on spaces indexed by `fin (n+1)`, where one
can build an element of `Π(i : fin (n+1)), M i` using `cons`, one can express directly the
multiplicativity of a multilinear map along the first variable. -/
lemma cons_smul
  (f : continuous_multilinear_map R M M₂) (m : Π(i : fin n), M i.succ) (c : R) (x : M 0) :
  f (cons (c • x) m) = c • f (cons x m) :=
f.to_multilinear_map.cons_smul m c x

lemma map_piecewise_add [decidable_eq ι] (m m' : Πi, M₁ i) (t : finset ι) :
  f (t.piecewise (m + m') m') = ∑ s in t.powerset, f (s.piecewise m m') :=
f.to_multilinear_map.map_piecewise_add _ _ _

/-- Additivity of a continuous multilinear map along all coordinates at the same time,
writing `f (m + m')` as the sum  of `f (s.piecewise m m')` over all sets `s`. -/
lemma map_add_univ [decidable_eq ι] [fintype ι] (m m' : Πi, M₁ i) :
  f (m + m') = ∑ s : finset ι, f (s.piecewise m m') :=
f.to_multilinear_map.map_add_univ _ _

section apply_sum

open fintype finset

variables {α : ι → Type*} [fintype ι] (g : Π i, α i → M₁ i) (A : Π i, finset (α i))

/-- If `f` is continuous multilinear, then `f (Σ_{j₁ ∈ A₁} g₁ j₁, ..., Σ_{jₙ ∈ Aₙ} gₙ jₙ)` is the
sum of `f (g₁ (r 1), ..., gₙ (r n))` where `r` ranges over all functions with `r 1 ∈ A₁`, ...,
`r n ∈ Aₙ`. This follows from multilinearity by expanding successively with respect to each
coordinate. -/

lemma map_sum_finset [decidable_eq ι] :
  f (λ i, ∑ j in A i, g i j) = ∑ r in pi_finset A, f (λ i, g i (r i)) :=
f.to_multilinear_map.map_sum_finset _ _

/-- If `f` is continuous multilinear, then `f (Σ_{j₁} g₁ j₁, ..., Σ_{jₙ} gₙ jₙ)` is the sum of
`f (g₁ (r 1), ..., gₙ (r n))` where `r` ranges over all functions `r`. This follows from
multilinearity by expanding successively with respect to each coordinate. -/
lemma map_sum [decidable_eq ι] [∀ i, fintype (α i)] :
  f (λ i, ∑ j, g i j) = ∑ r : Π i, α i, f (λ i, g i (r i)) :=
f.to_multilinear_map.map_sum _

end apply_sum

section restrict_scalar

variables (R) {A : Type*} [semiring A] [has_smul R A] [Π (i : ι), module A (M₁ i)]
  [module A M₂] [∀ i, is_scalar_tower R A (M₁ i)] [is_scalar_tower R A M₂]

/-- Reinterpret an `A`-multilinear map as an `R`-multilinear map, if `A` is an algebra over `R`
and their actions on all involved modules agree with the action of `R` on `A`. -/
def restrict_scalars (f : continuous_multilinear_map A M₁ M₂) :
  continuous_multilinear_map R M₁ M₂ :=
{ to_multilinear_map := f.to_multilinear_map.restrict_scalars R,
  cont := f.cont }

@[simp] lemma coe_restrict_scalars (f : continuous_multilinear_map A M₁ M₂) :
  ⇑(f.restrict_scalars R) = f := rfl

end restrict_scalar

end semiring

section ring

variables [ring R] [∀i, add_comm_group (M₁ i)] [add_comm_group M₂]
[∀i, module R (M₁ i)] [module R M₂] [∀i, topological_space (M₁ i)] [topological_space M₂]
(f f' : continuous_multilinear_map R M₁ M₂)

@[simp] lemma map_sub [decidable_eq ι] (m : Πi, M₁ i) (i : ι) (x y : M₁ i) :
  f (update m i (x - y)) = f (update m i x) - f (update m i y) :=
f.to_multilinear_map.map_sub _ _ _ _

section topological_add_group
variable [topological_add_group M₂]

instance : has_neg (continuous_multilinear_map R M₁ M₂) :=
⟨λ f, {cont := f.cont.neg, ..(-f.to_multilinear_map)}⟩

@[simp] lemma neg_apply (m : Πi, M₁ i) : (-f) m = - (f m) := rfl

instance : has_sub (continuous_multilinear_map R M₁ M₂) :=
⟨λ f g, { cont := f.cont.sub g.cont, .. (f.to_multilinear_map - g.to_multilinear_map) }⟩

@[simp] lemma sub_apply (m : Πi, M₁ i) : (f - f') m = f m - f' m := rfl

instance : add_comm_group (continuous_multilinear_map R M₁ M₂) :=
to_multilinear_map_inj.add_comm_group _
  rfl (λ _ _, rfl) (λ _, rfl) (λ _ _, rfl) (λ _ _, rfl) (λ _ _, rfl)

end topological_add_group

end ring

section comm_semiring

variables [comm_semiring R]
[∀i, add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
[∀i, module R (M₁ i)] [module R M₂]
[∀i, topological_space (M₁ i)] [topological_space M₂]
(f : continuous_multilinear_map R M₁ M₂)

lemma map_piecewise_smul [decidable_eq ι] (c : ι → R) (m : Πi, M₁ i) (s : finset ι) :
  f (s.piecewise (λ i, c i • m i) m) = (∏ i in s, c i) • f m :=
f.to_multilinear_map.map_piecewise_smul _ _ _

/-- Multiplicativity of a continuous multilinear map along all coordinates at the same time,
writing `f (λ i, c i • m i)` as `(∏ i, c i) • f m`. -/
lemma map_smul_univ [fintype ι] (c : ι → R) (m : Πi, M₁ i) :
  f (λ i, c i • m i) = (∏ i, c i) • f m :=
f.to_multilinear_map.map_smul_univ _ _

end comm_semiring

section distrib_mul_action

variables {R' R'' A : Type*} [monoid R'] [monoid R''] [semiring A]
  [Π i, add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
  [Π i, topological_space (M₁ i)] [topological_space M₂]
  [Π i, module A (M₁ i)] [module A M₂]
  [distrib_mul_action R' M₂] [has_continuous_const_smul R' M₂] [smul_comm_class A R' M₂]
  [distrib_mul_action R'' M₂] [has_continuous_const_smul R'' M₂] [smul_comm_class A R'' M₂]

instance [has_continuous_add M₂] : distrib_mul_action R' (continuous_multilinear_map A M₁ M₂) :=
function.injective.distrib_mul_action
  ⟨to_multilinear_map, to_multilinear_map_zero, to_multilinear_map_add⟩
  to_multilinear_map_inj (λ _ _, rfl)

end distrib_mul_action

section module

variables {R' A : Type*} [semiring R'] [semiring A]
  [Π i, add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
  [Π i, topological_space (M₁ i)] [topological_space M₂] [has_continuous_add M₂]
  [Π i, module A (M₁ i)] [module A M₂]
  [module R' M₂] [has_continuous_const_smul R' M₂] [smul_comm_class A R' M₂]

/-- The space of continuous multilinear maps over an algebra over `R` is a module over `R`, for the
pointwise addition and scalar multiplication. -/
instance : module R' (continuous_multilinear_map A M₁ M₂) :=
function.injective.module _ ⟨to_multilinear_map, to_multilinear_map_zero, to_multilinear_map_add⟩
  to_multilinear_map_inj (λ _ _, rfl)

/-- Linear map version of the map `to_multilinear_map` associating to a continuous multilinear map
the corresponding multilinear map. -/
@[simps] def to_multilinear_map_linear :
  continuous_multilinear_map A M₁ M₂ →ₗ[R'] multilinear_map A M₁ M₂ :=
{ to_fun    := to_multilinear_map,
  map_add'  := to_multilinear_map_add,
  map_smul' := to_multilinear_map_smul }

/-- `continuous_multilinear_map.pi` as a `linear_equiv`. -/
@[simps {simp_rhs := tt}]
def pi_linear_equiv {ι' : Type*} {M' : ι' → Type*}
  [Π i, add_comm_monoid (M' i)] [Π i, topological_space (M' i)] [∀ i, has_continuous_add (M' i)]
  [Π i, module R' (M' i)] [Π i, module A (M' i)] [∀ i, smul_comm_class A R' (M' i)]
  [Π i, has_continuous_const_smul R' (M' i)] :
  (Π i, continuous_multilinear_map A M₁ (M' i)) ≃ₗ[R']
    continuous_multilinear_map A M₁ (Π i, M' i) :=
{ map_add' := λ x y, rfl,
  map_smul' := λ c x, rfl,
  .. pi_equiv }

end module

section comm_algebra

variables (R ι) (A : Type*) [fintype ι] [comm_semiring R] [comm_semiring A] [algebra R A]
  [topological_space A] [has_continuous_mul A]

/-- The continuous multilinear map on `A^ι`, where `A` is a normed commutative algebra
over `𝕜`, associating to `m` the product of all the `m i`.

See also `continuous_multilinear_map.mk_pi_algebra_fin`. -/
protected def mk_pi_algebra : continuous_multilinear_map R (λ i : ι, A) A :=
{ cont := continuous_finset_prod _ $ λ i hi, continuous_apply _,
  to_multilinear_map := multilinear_map.mk_pi_algebra R ι A}

@[simp] lemma mk_pi_algebra_apply (m : ι → A) :
  continuous_multilinear_map.mk_pi_algebra R ι A m = ∏ i, m i :=
rfl

end comm_algebra

section algebra

variables (R n) (A : Type*) [comm_semiring R] [semiring A] [algebra R A]
  [topological_space A] [has_continuous_mul A]

/-- The continuous multilinear map on `A^n`, where `A` is a normed algebra over `𝕜`, associating to
`m` the product of all the `m i`.

See also: `continuous_multilinear_map.mk_pi_algebra`. -/
protected def mk_pi_algebra_fin : A [×n]→L[R] A :=
{ cont := begin
    change continuous (λ m, (list.of_fn m).prod),
    simp_rw list.of_fn_eq_map,
    exact continuous_list_prod _ (λ i hi, continuous_apply _),
  end,
  to_multilinear_map := multilinear_map.mk_pi_algebra_fin R n A}

variables {R n A}

@[simp] lemma mk_pi_algebra_fin_apply (m : fin n → A) :
  continuous_multilinear_map.mk_pi_algebra_fin R n A m = (list.of_fn m).prod :=
rfl

end algebra

section smul_right

variables [comm_semiring R] [Π i, add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
  [Π i, module R (M₁ i)] [module R M₂] [topological_space R] [Π i, topological_space (M₁ i)]
  [topological_space M₂] [has_continuous_smul R M₂] (f : continuous_multilinear_map R M₁ R) (z : M₂)

/-- Given a continuous `R`-multilinear map `f` taking values in `R`, `f.smul_right z` is the
continuous multilinear map sending `m` to `f m • z`. -/
@[simps to_multilinear_map apply] def smul_right : continuous_multilinear_map R M₁ M₂ :=
{ to_multilinear_map := f.to_multilinear_map.smul_right z,
  cont := f.cont.smul continuous_const }

end smul_right

end continuous_multilinear_map