import Analysis.Set.Function
import Analysis.Set.Lattice

/--
  A filter on `α` is a set of sets of `α` containing `α` itself, closed under
  supersets and intersection.
  NB. This definition of filters does not require `∅ ∉ sets`. This is done so
  we can create a lattice structure. `∅ ∉ sets` should be included as a
  seperate proposition in lemmas.
-/
structure Filter (α : Type u) where
  sets                   : Set (Set α)
  univ_sets              : Set.univ ∈ sets
  sets_of_superset {x y} : x ∈ sets → x ⊆ y → y ∈ sets
  inter_sets       {x y} : x ∈ sets → y ∈ sets → x ∩ y ∈ sets

-- I'm going to follow
-- https://web.archive.org/web/20071009170540/http://www.efnet-math.org/~david/mathematics/filters.pdf

-- First, we will find a way to generate filters for any given set of sets of α
-- To achieve this, we consider that the intersection of a collection of filters
-- is also a filter, so therefore, a filter can be generated form a set of sets
-- by taking the intersection of all filters containing this set, i.e. if `S` is
-- type `set (set α)`, then the filter generated by `S` is
-- ⋂ { F : filter α | S ⊆ F.sets }

namespace Filter

open Set

instance : Coe (Filter α) (Set (Set α)) := ⟨λ F => F.sets⟩

instance : Mem (Set α) (Filter α) := ⟨λ x F => x ∈ (F : Set (Set α))⟩

instance : LE (Filter α) := ⟨λ F G => (G : Set (Set α)) ≤ F⟩

/-! ### Basics -/

theorem memIff {F : Filter α} {s : Set α} : s ∈ F.sets ↔ s ∈ F := Iff.rfl

theorem LERefl (F : Filter α) : F ≤ F :=
  λ _ hs => hs

theorem eq {F G : Filter α} (h : F.sets = G.sets) : F = G := by
  cases F; cases G; subst h; rfl

theorem eqIff {F G : Filter α} : F = G ↔ F.sets = G.sets :=
Iff.intro (λ h => h ▸ rfl) eq

theorem ext {F G : Filter α} (h : ∀ s, s ∈ F ↔ s ∈ G) : F = G :=
  eq <| Set.ext h

/-- The intersection of a collection of filters is a filter. -/
def Inf (𝒞 : Set (Filter α)) : Filter α :=
{ sets := ⋂ F ∈ 𝒞, F
  univ_sets := λ F hF => F.univ_sets
  sets_of_superset := λ hx hxy F hF => F.sets_of_superset (hx F hF) hxy
  inter_sets := λ hx hy F hF => F.inter_sets (hx F hF) (hy F hF) }

instance : hasInf (Filter α) := ⟨Filter.Inf⟩

-- With that we can now define the filter generated by an arbitary set of sets

/-- The filter generated from `S`, a set of sets of `α` is the Inf of all filters
  containing `S` -/
def generatedFrom (S : Set (Set α)) : Filter α :=
  Inf { F : Filter α | S ⊆ F }

-- The method above generates the smallest filter that contains `S : set (set α)`
-- On the other hand, we can generate a filter using `s : set α` be letting the
-- filter be all supersets of `s`, this is called `principal s`

/-- The principal filter of a set `s` is set set of all sets larger than `s`. -/
def principal (s : Set α) : Filter α :=
{ sets := { t | s ⊆ t }
  univ_sets := Subset.subsetUniv
  sets_of_superset := λ hx hxy => Subset.trans hx hxy
  inter_sets := λ hx hy => subsetInter hx hy }

prefix:100 "𝓟 " => principal

theorem selfMemPrincipal (s : Set α) : s ∈ 𝓟 s := Subset.refl

theorem memPrincipalIff (s t : Set α) : s ∈ 𝓟 t ↔ t ⊆ s := Iff.rfl

variable {S : Set (Set α)}

theorem leGeneratedFrom (s : Set (Set α)) : s ⊆ generatedFrom s :=
  λ _ hs _ hF => hF _ hs

-- Straightaway, we see that if `∅ ∈ S`, then `generatedFrom S` is the powerset of `α`
theorem generatedFromEmpty (hS : ∅ ∈ S) (s : Set α) : s ∈ generatedFrom S :=
  (generatedFrom S).sets_of_superset (leGeneratedFrom _ _ hS) (Subset.empty s)

-- We don't want to consider filters with only the set `univ` and so, we
-- introduce the `neBot` class

/-- The smallest filter is the filter containing only the set `univ`. -/
def top : Filter α :=
{ sets := λ s => univ = s
  univ_sets := rfl
  sets_of_superset := λ hx hy => Subset.univSubsetIff.1 <| hx ▸ hy
  inter_sets := λ hx hy => Eq.symm <| hx ▸ hy ▸ interSelf }

def bot : Filter α :=
{ sets := λ _ => True
  univ_sets := trivial
  sets_of_superset := λ _ _ => trivial
  inter_sets := λ _ _ => trivial }

theorem memBot (s : Set α) : s ∈ bot := trivial

theorem eqBotIff (F : Filter α) : F = bot ↔ ∅ ∈ F :=
  Iff.intro (λ h => h ▸ memBot ∅)
    (λ h => Filter.ext <| λ s => Iff.intro (λ _ => memBot _)
    (λ _ => sets_of_superset _ h (Subset.empty _)))

/-- A filter is `neBot` if it is not equal to `Filter.bot`-/
class neBot (F : Filter α) where
  ne_bot : F ≠ bot

/-- Let `F` be a `ne_bot` filter on `α`, `F` is an ultra filter if for all
  `S : set α`, `S ∈ F` or `Sᶜ ∈ F` -/
class Ultra (F : Filter α) where
  ne_bot : neBot F
  mem_or_compl_mem {S : Set α} : S ∈ F ∨ Sᶜ ∈ F

-- The ultra filter theorem states that for all `F : filter α`, there exists
-- some ultra filter `𝕌`, `F ⊆ 𝕌`.

-- The proof of this follows from Zorn's lemma.
-- Let `F` be a filter on `α`, We have the filters of `α` that contain `F` form
-- a poset. Let `𝒞` be a chain (a totaly ordered set) within this set, then by
-- Zorn's lemma, `𝒞` has at least one maximum element. Thus, by checking this
-- maximum element is indeed an ultra filter, we have found a ultra filter
-- containing `F`.

-- We won't try proving it anytime soon
-- In Lean 3 mathlib its known as `exists_maximal_of_chains_bounded`

-- theorem existsUltraGe (F : Filter α) [neBot F] :

def map (f : α → β) (F : Filter α) : Filter β :=
{ sets := preimage (preimage f) F
  univ_sets := F.univ_sets
  sets_of_superset := λ hx hxy => F.sets_of_superset hx <| preimageMono f hxy
  inter_sets := λ hx hy => F.inter_sets hx hy }

theorem memMap {f : α → β} {F : Filter α} (t : Set β) :
  t ∈ F.map f ↔ (preimage f) t ∈ F := Iff.rfl

/-! ### Convergence -/

/-- A neighbourhood of `x` is the principal filter of the singleton set `{x}`-/
def neighbourhood (x : α) : Filter α := 𝓟 {x}

notation:100 "𝓝 " x => 𝓟 {x}

theorem memNeighbourhoodIff (x : α) (s : Set α) : (s ∈ 𝓝 x) ↔ x ∈ s :=
Iff.intro (λ h => (memPrincipalIff s {x}).1 h x <| memSelfSingleton _)
  (λ h => (𝓟 {x}).sets_of_superset (selfMemPrincipal {x}) (Subset.singletonIff.1 h))

def eventually (p : α → Prop) (F : Filter α) := p ∈ F

theorem eventually.ext {F G : Filter α} (h : ∀ p, eventually p F ↔ eventually p G) :
  F = G :=
Filter.ext h

theorem eventually.filter_mono {F G : Filter α} (h : F ≤ G) {p : α → Prop}
  (hG : eventually p G) : eventually p F := h _ hG

/-- A filter `l₁` tendsto another filter `l₂` along some function `f` if the
map of `l₁` along `f` is smaller than `l₂`. -/
def tendsto (f : α → β) (l₁ : Filter α) (l₂ : Filter β) := l₁.map f ≤ l₂

theorem tendstoDef {f : α → β} {l₁ : Filter α} {l₂ : Filter β} :
  tendsto f l₁ l₂ ↔ ∀ s ∈ l₂, s.preimage f ∈ l₁ := Iff.rfl

-- Notation for the limit of a filter to a point
notation:100 F " ⟶ " x => tendsto id F (𝓝 x)

theorem tendstoNeighberhoodIff {x : α} {F : Filter α} : (F ⟶ x) ↔ F ≤ 𝓝 x :=
Iff.rfl

theorem tendstoIffEventually {f : α → β} {l₁ : Filter α} {l₂ : Filter β} :
  tendsto f l₁ l₂ ↔ ∀ {p : β → Prop} (hp : eventually p l₂), eventually (p ∘ f) l₁ :=
Iff.rfl

theorem tendstoRefl (F : Filter α) : tendsto id F F := LERefl F

def filterImage (f : α → β) (F : Filter α) : Filter β :=
  generatedFrom <| Set.image (λ s : Set α => s.image f) F

theorem memFilterImageIff {f : α → β} {F : Filter α} (V : Set β) :
  V ∈ map f F ↔ ∃ U ∈ F, U.image f ⊆ V := by
  apply Iff.intro
  { intro h;
    apply Exists.intro (preimage f V);
    apply And.intro h;
    intro x hx;
    rw [memImage] at hx;
    exact hx.2.2 ▸ hx.2.1 }
  { intro h;
    let ⟨U, hU, hU'⟩ := h;
    rw [memMap];
    exact F.sets_of_superset hU λ x hx => hU' _ <| Exists.intro x ⟨hx, rfl⟩ }

#exit

variables {Y : Type*} [topological_space Y]

lemma nhds_subset_filter_of_tendsto {x : X} {F : filter X}
  (hF : tendsto id F (nhds x)) : (nhds x : set (set X)) ⊆ F :=
begin
  intros s hs,
  have := tendsto_def.1 hF _ hs,
  rwa preimage_id at this
end

/-- A map between topological spaces `f : X → Y` is continuous at some `x : X`
  if for all `F : filter X` that tends to `x`, `map F` tends to `f(x)` -/
theorem continuous_of_filter_tendsto {x : X} (f : X → Y)
  (hF : ∀ F : filter X, tendsto id F (nhds x) →
    tendsto id (map f F) (nhds (f x))) : continuous_at f x :=
λ _ hU, tendsto_def.1 (hF _ $ nhds_tendsto x) _ hU

/-- If `f : X → Y` is a continuous map between topological spaces, then for all
  `F : filter X` that tends to `x`, `map F` tends to `f(x)` -/
theorem filter_tendsto_of_continuous {x : X} {F : filter X} (f : X → Y)
  (hf : continuous_at f x) (hF : tendsto id F (nhds x)) :
  tendsto id (map f F) (nhds (f x)) :=
begin
  rw tendsto_def at *, intros U hU,
  exact nhds_subset_filter_of_tendsto hF (hf hU),
end

/-! ### Product Filters -/

/- Given two filters `F` and `G` on the topological spaces `X` and `Y` respectively,
  we define the the product filter `F × G` as a filter on the product space `X × Y`
  such that
  `prod F G := F.comap prod.fst ⊓ G.comap prod.snd`
  where `prod.fst = (a, b) ↦ a`, `prod.snd = (a, b) ↦ b` and
  `(C : filter β).comap (f : α → β)` is the filter generated by the set of preimages
  of sets contained in `C`, i.e. `(C.comap f).sets = generate { f⁻¹(s) | s ∈ C }`. -/

-- We borrow the notation of product filters from mathlib
localized "infix ` ×ᶠ `:60 := filter.prod" in filter

-- Write some theorems here maybe?
-- TODO : make the natural projection : filter (X × Y) → filter X

/-! ### Compactness -/

variables {C : Type*} [topological_space C] [compact_space C]
/- In mathlib a compact space is a topological space that satisfy the
  `is_compact` proposition where
  `def is_compact (s : set α) := ∀ ⦃f⦄ [ne_bot f], f ≤ 𝓟 s → ∃ a ∈ s, cluster_pt a f`
  (`cluset_pt a f` means a is a limit point of f) -/

end Filter


/-
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, Mario Carneiro, Kevin Buzzard, Yury Kudryashov, Eric Wieser
-/
import linear_algebra.basic

/-!
# Pi types of modules

This file defines constructors for linear maps whose domains or codomains are pi types.

It contains theorems relating these to each other, as well as to `linear_map.ker`.

## Main definitions

- pi types in the codomain:
  - `linear_map.pi`
  - `linear_map.single`
- pi types in the domain:
  - `linear_map.proj`
- `linear_map.diag`

-/

universes u v w x y z u' v' w' y'
variables {R : Type u} {K : Type u'} {M : Type v} {V : Type v'} {M₂ : Type w} {V₂ : Type w'}
variables {M₃ : Type y} {V₃ : Type y'} {M₄ : Type z} {ι : Type x}

open function submodule
open_locale big_operators

namespace linear_map

universe i
variables [semiring R] [add_comm_monoid M₂] [module R M₂] [add_comm_monoid M₃] [module R M₃]
{φ : ι → Type i} [∀i, add_comm_monoid (φ i)] [∀i, module R (φ i)]

/-- `pi` construction for linear functions. From a family of linear functions it produces a linear
function into a family of modules. -/
def pi (f : Πi, M₂ →ₗ[R] φ i) : M₂ →ₗ[R] (Πi, φ i) :=
⟨λc i, f i c, λ c d, funext $ λ i, (f i).map_add _ _, λ c d, funext $ λ i, (f i).map_smul _ _⟩

@[simp] lemma pi_apply (f : Πi, M₂ →ₗ[R] φ i) (c : M₂) (i : ι) :
  pi f c i = f i c := rfl

lemma ker_pi (f : Πi, M₂ →ₗ[R] φ i) : ker (pi f) = (⨅i:ι, ker (f i)) :=
by ext c; simp [funext_iff]; refl

lemma pi_eq_zero (f : Πi, M₂ →ₗ[R] φ i) : pi f = 0 ↔ (∀i, f i = 0) :=
by simp only [linear_map.ext_iff, pi_apply, funext_iff]; exact ⟨λh a b, h b a, λh a b, h b a⟩

lemma pi_zero : pi (λi, 0 : Πi, M₂ →ₗ[R] φ i) = 0 :=
by ext; refl

lemma pi_comp (f : Πi, M₂ →ₗ[R] φ i) (g : M₃ →ₗ[R] M₂) : (pi f).comp g = pi (λi, (f i).comp g) :=
rfl

/-- The projections from a family of modules are linear maps. -/
def proj (i : ι) : (Πi, φ i) →ₗ[R] φ i :=
⟨ λa, a i, assume f g, rfl, assume c f, rfl ⟩

@[simp] lemma coe_proj (i : ι) : ⇑(proj i : (Πi, φ i) →ₗ[R] φ i) = function.eval i := rfl

lemma proj_apply (i : ι) (b : Πi, φ i) : (proj i : (Πi, φ i) →ₗ[R] φ i) b = b i := rfl

lemma proj_pi (f : Πi, M₂ →ₗ[R] φ i) (i : ι) : (proj i).comp (pi f) = f i :=
ext $ assume c, rfl

lemma infi_ker_proj : (⨅i, ker (proj i) : submodule R (Πi, φ i)) = ⊥ :=
bot_unique $ set_like.le_def.2 $ assume a h,
begin
  simp only [mem_infi, mem_ker, proj_apply] at h,
  exact (mem_bot _).2 (funext $ assume i, h i)
end

lemma apply_single [add_comm_monoid M] [module R M] [decidable_eq ι]
  (f : Π i, φ i →ₗ[R] M) (i j : ι) (x : φ i) :
  f j (pi.single i x j) = pi.single i (f i x) j :=
pi.apply_single (λ i, f i) (λ i, (f i).map_zero) _ _ _

/-- The `linear_map` version of `add_monoid_hom.single` and `pi.single`. -/
def single [decidable_eq ι] (i : ι) : φ i →ₗ[R] (Πi, φ i) :=
{ to_fun := pi.single i,
  map_smul' := pi.single_smul i,
  .. add_monoid_hom.single φ i}

@[simp] lemma coe_single [decidable_eq ι] (i : ι) :
  ⇑(single i : φ i →ₗ[R] (Π i, φ i)) = pi.single i := rfl

variables (R φ)

/-- The linear equivalence between linear functions on a finite product of modules and
families of functions on these modules. See note [bundled maps over different rings]. -/
@[simps] def lsum (S) [add_comm_monoid M] [module R M] [fintype ι] [decidable_eq ι]
  [semiring S] [module S M]  [smul_comm_class R S M] :
  (Π i, φ i →ₗ[R] M) ≃ₗ[S] ((Π i, φ i) →ₗ[R] M) :=
{ to_fun := λ f, ∑ i : ι, (f i).comp (proj i),
  inv_fun := λ f i, f.comp (single i),
  map_add' := λ f g, by simp only [pi.add_apply, add_comp, finset.sum_add_distrib],
  map_smul' := λ c f, by simp only [pi.smul_apply, smul_comp, finset.smul_sum],
  left_inv := λ f, by { ext i x, simp [apply_single] },
  right_inv := λ f,
    begin
      ext,
      suffices : f (∑ j, pi.single j (x j)) = f x, by simpa [apply_single],
      rw finset.univ_sum_single
    end }

variables {R φ}

section ext

variables [fintype ι] [decidable_eq ι] [add_comm_monoid M] [module R M]
  {f g : (Π i, φ i) →ₗ[R] M}

lemma pi_ext (h : ∀ i x, f (pi.single i x) = g (pi.single i x)) :
  f = g :=
to_add_monoid_hom_injective $ add_monoid_hom.functions_ext _ _ _ h

lemma pi_ext_iff : f = g ↔ ∀ i x, f (pi.single i x) = g (pi.single i x) :=
⟨λ h i x, h ▸ rfl, pi_ext⟩

/-- This is used as the ext lemma instead of `linear_map.pi_ext` for reasons explained in
note [partially-applied ext lemmas]. -/
@[ext] lemma pi_ext' (h : ∀ i, f.comp (single i) = g.comp (single i)) : f = g :=
begin
  refine pi_ext (λ i x, _),
  convert linear_map.congr_fun (h i) x
end

lemma pi_ext'_iff : f = g ↔ ∀ i, f.comp (single i) = g.comp (single i) :=
⟨λ h i, h ▸ rfl, pi_ext'⟩

end ext

section
variables (R φ)

/-- If `I` and `J` are disjoint index sets, the product of the kernels of the `J`th projections of
`φ` is linearly equivalent to the product over `I`. -/
def infi_ker_proj_equiv {I J : set ι} [decidable_pred (λi, i ∈ I)]
  (hd : disjoint I J) (hu : set.univ ⊆ I ∪ J) :
  (⨅i ∈ J, ker (proj i) : submodule R (Πi, φ i)) ≃ₗ[R] (Πi:I, φ i) :=
begin
  refine linear_equiv.of_linear
    (pi $ λi, (proj (i:ι)).comp (submodule.subtype _))
    (cod_restrict _ (pi $ λi, if h : i ∈ I then proj (⟨i, h⟩ : I) else 0) _) _ _,
  { assume b,
    simp only [mem_infi, mem_ker, funext_iff, proj_apply, pi_apply],
    assume j hjJ,
    have : j ∉ I := assume hjI, hd ⟨hjI, hjJ⟩,
    rw [dif_neg this, zero_apply] },
  { simp only [pi_comp, comp_assoc, subtype_comp_cod_restrict, proj_pi, dif_pos, subtype.coe_prop],
    ext b ⟨j, hj⟩, refl },
  { ext1 ⟨b, hb⟩,
    apply subtype.ext,
    ext j,
    have hb : ∀i ∈ J, b i = 0,
    { simpa only [mem_infi, mem_ker, proj_apply] using (mem_infi _).1 hb },
    simp only [comp_apply, pi_apply, id_apply, proj_apply, subtype_apply, cod_restrict_apply],
    split_ifs,
    { refl },
    { exact (hb _ $ (hu trivial).resolve_left h).symm } }
end
end

section
variable [decidable_eq ι]

/-- `diag i j` is the identity map if `i = j`. Otherwise it is the constant 0 map. -/
def diag (i j : ι) : φ i →ₗ[R] φ j :=
@function.update ι (λj, φ i →ₗ[R] φ j) _ 0 i id j

lemma update_apply (f : Πi, M₂ →ₗ[R] φ i) (c : M₂) (i j : ι) (b : M₂ →ₗ[R] φ i) :
  (update f i b j) c = update (λi, f i c) i (b c) j :=
begin
  by_cases j = i,
  { rw [h, update_same, update_same] },
  { rw [update_noteq h, update_noteq h] }
end

end

end linear_map

namespace submodule

variables [semiring R] {φ : ι → Type*} [∀ i, add_comm_monoid (φ i)] [∀ i, module R (φ i)]

open linear_map

/-- A version of `set.pi` for submodules. Given an index set `I` and a family of submodules
`p : Π i, submodule R (φ i)`, `pi I s` is the submodule of dependent functions `f : Π i, φ i`
such that `f i` belongs to `p a` whenever `i ∈ I`. -/
def pi (I : set ι) (p : Π i, submodule R (φ i)) : submodule R (Π i, φ i) :=
{ carrier := set.pi I (λ i, p i),
  zero_mem' := λ i hi, (p i).zero_mem,
  add_mem' := λ x y hx hy i hi, (p i).add_mem (hx i hi) (hy i hi),
  smul_mem' := λ c x hx i hi, (p i).smul_mem c (hx i hi) }

variables {I : set ι} {p : Π i, submodule R (φ i)} {x : Π i, φ i}

@[simp] lemma mem_pi : x ∈ pi I p ↔ ∀ i ∈ I, x i ∈ p i := iff.rfl

@[simp, norm_cast] lemma coe_pi : (pi I p : set (Π i, φ i)) = set.pi I (λ i, p i) := rfl

lemma binfi_comap_proj : (⨅ i ∈ I, comap (proj i) (p i)) = pi I p :=
by { ext x, simp }

lemma infi_comap_proj : (⨅ i, comap (proj i) (p i)) = pi set.univ p :=
by { ext x, simp }

lemma supr_map_single [decidable_eq ι] [fintype ι] :
  (⨆ i, map (linear_map.single i) (p i)) = pi set.univ p :=
begin
  refine (supr_le $ λ i, _).antisymm _,
  { rintro _ ⟨x, hx : x ∈ p i, rfl⟩ j -,
    rcases em (j = i) with rfl|hj; simp * },
  { intros x hx,
    rw [← finset.univ_sum_single x],
    exact sum_mem_supr (λ i, mem_map_of_mem (hx i trivial)) }
end

end submodule

namespace linear_equiv

variables [semiring R] {φ ψ : ι → Type*} [∀i, add_comm_monoid (φ i)] [∀i, module R (φ i)]
  [∀i, add_comm_monoid (ψ i)] [∀i, module R (ψ i)]

/-- Combine a family of linear equivalences into a linear equivalence of `pi`-types. -/
@[simps] def pi (e : Π i, φ i ≃ₗ[R] ψ i) : (Π i, φ i) ≃ₗ[R] (Π i, ψ i) :=
{ to_fun := λ f i, e i (f i),
  inv_fun := λ f i, (e i).symm (f i),
  map_add' := λ f g, by { ext, simp },
  map_smul' := λ c f, by { ext, simp },
  left_inv := λ f, by { ext, simp },
  right_inv := λ f, by { ext, simp } }

variables (ι R M) (S : Type*) [fintype ι] [decidable_eq ι] [semiring S]
  [add_comm_monoid M] [module R M] [module S M] [smul_comm_class R S M]

/-- Linear equivalence between linear functions `Rⁿ → M` and `Mⁿ`. The spaces `Rⁿ` and `Mⁿ`
are represented as `ι → R` and `ι → M`, respectively, where `ι` is a finite type.

This as an `S`-linear equivalence, under the assumption that `S` acts on `M` commuting with `R`.
When `R` is commutative, we can take this to be the usual action with `S = R`.
Otherwise, `S = ℕ` shows that the equivalence is additive.
See note [bundled maps over different rings]. -/
def pi_ring : ((ι → R) →ₗ[R] M) ≃ₗ[S] (ι → M) :=
(linear_map.lsum R (λ i : ι, R) S).symm.trans
  (pi $ λ i, linear_map.ring_lmap_equiv_self R M S)

variables {ι R M}

@[simp] lemma pi_ring_apply (f : (ι → R) →ₗ[R] M) (i : ι) :
  pi_ring R M ι S f i = f (pi.single i 1) :=
rfl

@[simp] lemma pi_ring_symm_apply (f : ι → M) (g : ι → R) :
  (pi_ring R M ι S).symm f g = ∑ i, g i • f i :=
by simp [pi_ring, linear_map.lsum]

end linear_equiv


import data.vector

universe variables u

namespace vector

variable {α : Type u}
variable {n : ℕ}

local infix `++`:65 := vector.append

@[simp]
theorem to_list_repeat (x : α) : to_list (repeat x n) = list.repeat x n :=
begin
  induction n,
  all_goals { simp [repeat] }
end

theorem repeat_succ_to_append {α : Type} (x : α) (n : ℕ)
  : repeat x (nat.succ n) = repeat x n ++ cons x nil :=
begin
  induction n with n ind,
  { apply vector.eq,
    simp [repeat]
  },
  {
    have h := congr_arg to_list ind,
    simp [vector.repeat] at h,
    apply vector.eq,
    simp [vector.repeat],
    rw h,
  },
end

end vector


/-
Copyright (c) 2021 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies
-/
import data.set.pairwise

/-!
# Antichains

This file defines antichains. An antichain is a set where any two distinct elements are not related.
If the relation is `(≤)`, this corresponds to incomparability and usual order antichains. If the
relation is `G.adj` for `G : simple_graph α`, this corresponds to independent sets of `G`.

## Definitions

* `is_antichain r s`: Any two elements of `s : set α` are unrelated by `r : α → α → Prop`.
* `is_strong_antichain r s`: Any two elements of `s : set α` are not related by `r : α → α → Prop`
  to a common element.
* `is_antichain.mk r s`: Turns `s` into an antichain by keeping only the "maximal" elements.
-/

open function set

variables {α β : Type*} {r r₁ r₂ : α → α → Prop} {r' : β → β → Prop} {s t : set α} {a : α}

protected lemma symmetric.compl (h : symmetric r) : symmetric rᶜ := λ x y hr hr', hr $ h hr'

/-- An antichain is a set such that no two distinct elements are related. -/
def is_antichain (r : α → α → Prop) (s : set α) : Prop := s.pairwise rᶜ

namespace is_antichain

protected lemma subset (hs : is_antichain r s) (h : t ⊆ s) : is_antichain r t := hs.mono h

lemma mono (hs : is_antichain r₁ s) (h : r₂ ≤ r₁) : is_antichain r₂ s := hs.mono' $ compl_le_compl h

lemma mono_on (hs : is_antichain r₁ s) (h : s.pairwise (λ ⦃a b⦄, r₂ a b → r₁ a b)) :
  is_antichain r₂ s :=
hs.imp_on $ h.imp $ λ a b h h₁ h₂, h₁ $ h h₂

protected lemma eq (hs : is_antichain r s) {a b : α} (ha : a ∈ s) (hb : b ∈ s) (h : r a b) :
  a = b :=
hs.eq ha hb $ not_not_intro h

protected lemma eq' (hs : is_antichain r s) {a b : α} (ha : a ∈ s) (hb : b ∈ s) (h : r b a) :
  a = b :=
(hs.eq hb ha h).symm

protected lemma is_antisymm (h : is_antichain r univ) : is_antisymm α r :=
⟨λ a b ha _, h.eq trivial trivial ha⟩

protected lemma subsingleton [is_trichotomous α r] (h : is_antichain r s) : s.subsingleton :=
begin
  rintro a ha b hb,
  obtain hab | hab | hab := trichotomous_of r a b,
  { exact h.eq ha hb hab },
  { exact hab },
  { exact h.eq' ha hb hab }
end

protected lemma flip (hs : is_antichain r s) : is_antichain (flip r) s :=
λ a ha b hb h, hs hb ha h.symm

lemma swap (hs : is_antichain r s) : is_antichain (swap r) s := hs.flip

lemma image (hs : is_antichain r s) (f : α → β) (h : ∀ ⦃a b⦄, r' (f a) (f b) → r a b) :
  is_antichain r' (f '' s) :=
begin
  rintro _ ⟨b, hb, rfl⟩ _ ⟨c, hc, rfl⟩ hbc hr,
  exact hs hb hc (ne_of_apply_ne _ hbc) (h hr),
end

lemma preimage (hs : is_antichain r s) {f : β → α} (hf : injective f)
  (h : ∀ ⦃a b⦄, r' a b → r (f a) (f b)) :
  is_antichain r' (f ⁻¹' s) :=
λ b hb c hc hbc hr, hs hb hc (hf.ne hbc) $ h hr

lemma _root_.is_antichain_insert :
  is_antichain r (insert a s) ↔ is_antichain r s ∧ ∀ ⦃b⦄, b ∈ s → a ≠ b → ¬ r a b ∧ ¬ r b a :=
set.pairwise_insert

protected lemma insert (hs : is_antichain r s) (hl : ∀ ⦃b⦄, b ∈ s → a ≠ b → ¬ r b a)
  (hr : ∀ ⦃b⦄, b ∈ s → a ≠ b → ¬ r a b) :
  is_antichain r (insert a s) :=
is_antichain_insert.2 ⟨hs, λ b hb hab, ⟨hr hb hab, hl hb hab⟩⟩

lemma _root_.is_antichain_insert_of_symmetric (hr : symmetric r) :
  is_antichain r (insert a s) ↔ is_antichain r s ∧ ∀ ⦃b⦄, b ∈ s → a ≠ b → ¬ r a b :=
pairwise_insert_of_symmetric hr.compl

lemma insert_of_symmetric (hs : is_antichain r s) (hr : symmetric r)
  (h : ∀ ⦃b⦄, b ∈ s → a ≠ b → ¬ r a b) :
  is_antichain r (insert a s) :=
(is_antichain_insert_of_symmetric hr).2 ⟨hs, h⟩

lemma image_rel_embedding (hs : is_antichain r s) (φ : r ↪r r') : is_antichain r' (φ '' s) :=
begin
  intros b hb b' hb' h₁ h₂,
  rw set.mem_image at hb hb',
  obtain ⟨⟨a,has,rfl⟩,⟨a',has',rfl⟩⟩ := ⟨hb,hb'⟩,
  exact hs has has' (λ haa', h₁ (haa'.subst (by refl))) (φ.map_rel_iff.mp h₂),
end

lemma preimage_rel_embedding {t : set β} (ht : is_antichain r' t) (φ : r ↪r r') :
  is_antichain r (φ ⁻¹' t) :=
λ a ha a' ha' hne hle, ht ha ha' (λ h, hne (φ.injective h)) (φ.map_rel_iff.mpr hle)

lemma image_rel_iso (hs : is_antichain r s) (φ : r ≃r r') : is_antichain r' (φ '' s) :=
hs.image_rel_embedding φ

lemma preimage_rel_iso {t : set β} (hs : is_antichain r' t) (φ : r ≃r r') :
  is_antichain r (φ ⁻¹' t) :=
hs.preimage_rel_embedding φ

lemma image_rel_embedding_iff {φ : r ↪r r'} : is_antichain r' (φ '' s) ↔ is_antichain r s :=
⟨λ h, (φ.injective.preimage_image s).subst (h.preimage_rel_embedding φ),
  λ h, h.image_rel_embedding φ⟩

lemma image_rel_iso_iff {φ : r ≃r r'} : is_antichain r' (φ '' s) ↔ is_antichain r s :=
  @image_rel_embedding_iff _ _ _ _ _ (φ : r ↪r r')

lemma image_embedding [has_le α] [has_le β] (hs : is_antichain (≤) s) (φ : α ↪o β) :
  is_antichain (≤) (φ '' s) :=
image_rel_embedding hs _

lemma preimage_embedding [has_le α] [has_le β] {t : set β} (ht : is_antichain (≤) t) (φ : α ↪o β) :
  is_antichain (≤) (φ ⁻¹' t) :=
preimage_rel_embedding ht _

lemma image_embedding_iff [has_le α] [has_le β] {φ : α ↪o β} :
  is_antichain (≤) (φ '' s) ↔ is_antichain (≤) s :=
image_rel_embedding_iff

lemma image_iso [has_le α] [has_le β] (hs : is_antichain (≤) s) (φ : α ≃o β) :
  is_antichain (≤) (φ '' s) :=
image_rel_embedding hs _

lemma image_iso_iff [has_le α] [has_le β] {φ : α ≃o β} :
  is_antichain (≤) (φ '' s) ↔ is_antichain (≤) s :=
image_rel_embedding_iff

lemma preimage_iso [has_le α] [has_le β] {t : set β} (ht : is_antichain (≤) t) (φ : α ≃o β) :
  is_antichain (≤) (φ ⁻¹' t) :=
preimage_rel_embedding ht _

lemma preimage_iso_iff [has_le α] [has_le β] {t : set β} {φ : α ≃o β} :
  is_antichain (≤) (φ ⁻¹' t) ↔ is_antichain (≤) t :=
⟨λ h, (φ.image_preimage t).subst (h.image_iso φ), λ h, h.preimage_iso _⟩

lemma to_dual [has_le α] (hs : is_antichain (≤) s) : @is_antichain αᵒᵈ (≤) s :=
λ a ha b hb hab, hs hb ha hab.symm

lemma to_dual_iff [has_le α] : is_antichain (≤) s ↔ @is_antichain αᵒᵈ (≤) s := ⟨to_dual, to_dual⟩

lemma image_compl [boolean_algebra α] (hs : is_antichain (≤) s) :
  is_antichain (≤) (compl '' s) :=
(hs.image_embedding (order_iso.compl α).to_order_embedding).flip

lemma preimage_compl [boolean_algebra α] (hs : is_antichain (≤) s) :
  is_antichain (≤) (compl ⁻¹' s) :=
λ a ha a' ha' hne hle, hs ha' ha (λ h, hne (compl_inj_iff.mp h.symm)) (compl_le_compl hle)

end is_antichain

lemma is_antichain_singleton (a : α) (r : α → α → Prop) : is_antichain r {a} :=
pairwise_singleton _ _

lemma set.subsingleton.is_antichain (hs : s.subsingleton) (r : α → α → Prop) : is_antichain r s :=
hs.pairwise _

section preorder
variables [preorder α]

lemma is_antichain_and_least_iff : is_antichain (≤) s ∧ is_least s a ↔ s = {a} :=
⟨λ h, eq_singleton_iff_unique_mem.2 ⟨h.2.1, λ b hb, h.1.eq' hb h.2.1 (h.2.2 hb)⟩,
  by { rintro rfl, exact ⟨is_antichain_singleton _ _, is_least_singleton⟩ }⟩

lemma is_antichain_and_greatest_iff : is_antichain (≤) s ∧ is_greatest s a ↔ s = {a} :=
⟨λ h, eq_singleton_iff_unique_mem.2 ⟨h.2.1, λ b hb, h.1.eq hb h.2.1 (h.2.2 hb)⟩,
  by { rintro rfl, exact ⟨is_antichain_singleton _ _, is_greatest_singleton⟩ }⟩

lemma is_antichain.least_iff (hs : is_antichain (≤) s) : is_least s a ↔ s = {a} :=
(and_iff_right hs).symm.trans is_antichain_and_least_iff

lemma is_antichain.greatest_iff (hs : is_antichain (≤) s) : is_greatest s a ↔ s = {a} :=
(and_iff_right hs).symm.trans is_antichain_and_greatest_iff

lemma is_least.antichain_iff (hs : is_least s a) : is_antichain (≤) s ↔ s = {a} :=
(and_iff_left hs).symm.trans is_antichain_and_least_iff

lemma is_greatest.antichain_iff (hs : is_greatest s a) : is_antichain (≤) s ↔ s = {a} :=
(and_iff_left hs).symm.trans is_antichain_and_greatest_iff

lemma is_antichain.bot_mem_iff [order_bot α] (hs : is_antichain (≤) s) : ⊥ ∈ s ↔ s = {⊥} :=
is_least_bot_iff.symm.trans hs.least_iff

lemma is_antichain.top_mem_iff [order_top α] (hs : is_antichain (≤) s) : ⊤ ∈ s ↔ s = {⊤} :=
is_greatest_top_iff.symm.trans hs.greatest_iff

end preorder

/-! ### Strong antichains -/

/-- An strong (upward) antichain is a set such that no two distinct elements are related to a common
element. -/
def is_strong_antichain (r : α → α → Prop) (s : set α) : Prop :=
s.pairwise $ λ a b, ∀ c, ¬ r a c ∨ ¬ r b c

namespace is_strong_antichain

protected lemma subset (hs : is_strong_antichain r s) (h : t ⊆ s) : is_strong_antichain r t :=
hs.mono h

lemma mono (hs : is_strong_antichain r₁ s) (h : r₂ ≤ r₁) : is_strong_antichain r₂ s :=
hs.mono' $ λ a b hab c, (hab c).imp (compl_le_compl h _ _) (compl_le_compl h _ _)

lemma eq (hs : is_strong_antichain r s) {a b c : α} (ha : a ∈ s) (hb : b ∈ s) (hac : r a c)
  (hbc : r b c) :
  a = b :=
hs.eq ha hb $ λ h, false.elim $ (h c).elim (not_not_intro hac) (not_not_intro hbc)

protected lemma is_antichain [is_refl α r] (h : is_strong_antichain r s) : is_antichain r s :=
h.imp $ λ a b hab, (hab b).resolve_right (not_not_intro $ refl _)

protected lemma subsingleton [is_directed α r] (h : is_strong_antichain r s) : s.subsingleton :=
λ a ha b hb, let ⟨c, hac, hbc⟩ := directed_of r a b in h.eq ha hb hac hbc

protected lemma flip [is_symm α r] (hs : is_strong_antichain r s) :
  is_strong_antichain (flip r) s :=
λ a ha b hb h c, (hs ha hb h c).imp (mt $ symm_of r) (mt $ symm_of r)

lemma swap [is_symm α r] (hs : is_strong_antichain r s) : is_strong_antichain (swap r) s := hs.flip

lemma image (hs : is_strong_antichain r s) {f : α → β} (hf : surjective f)
  (h : ∀ a b, r' (f a) (f b) → r a b) :
  is_strong_antichain r' (f '' s) :=
begin
  rintro _ ⟨a, ha, rfl⟩ _ ⟨b, hb, rfl⟩ hab c,
  obtain ⟨c, rfl⟩ := hf c,
  exact (hs ha hb (ne_of_apply_ne _ hab) _).imp (mt $ h _ _) (mt $ h _ _),
end

lemma preimage (hs : is_strong_antichain r s) {f : β → α} (hf : injective f)
  (h : ∀ a b, r' a b → r (f a) (f b)) :
  is_strong_antichain r' (f ⁻¹' s) :=
λ a ha b hb hab c, (hs ha hb (hf.ne hab) _).imp (mt $ h _ _) (mt $ h _ _)

lemma _root_.is_strong_antichain_insert :
  is_strong_antichain r (insert a s) ↔ is_strong_antichain r s ∧
    ∀ ⦃b⦄, b ∈ s → a ≠ b → ∀ c, ¬ r a c ∨ ¬ r b c :=
set.pairwise_insert_of_symmetric $ λ a b h c, (h c).symm

protected lemma insert (hs : is_strong_antichain r s)
  (h : ∀ ⦃b⦄, b ∈ s → a ≠ b → ∀ c, ¬ r a c ∨ ¬ r b c) :
  is_strong_antichain r (insert a s) :=
is_strong_antichain_insert.2 ⟨hs, h⟩

end is_strong_antichain

lemma set.subsingleton.is_strong_antichain (hs : s.subsingleton) (r : α → α → Prop) :
  is_strong_antichain r s :=
hs.pairwise _


/-
Copyright (c) 2020 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Aaron Anderson.
-/

import order.conditionally_complete_lattice
import order.lattice
import tactic.pi_instances

/-
# Divisibility Lattices

## Notations

## Implementation Notes
Trying to make a standard typeclass for structures with a sensibly defined dvd relation, as well as gcd, coprime, etc.
Experimenting with type tags, and

## References

-/

set_option old_structure_cmd true

open_locale classical

section divisibility_lattice

variable (α : Type)
/--A canonically ordered additive monoid which is also a lattice-/
class add_lattice extends lattice α, canonically_ordered_add_monoid α

/--A preorder with the symbol | -/
class dvd_preorder extends has_dvd α :=
(dvd_refl : ∀ a : α, a ∣ a)
(dvd_trans : ∀ a b c : α, a ∣ b → b ∣ c → a ∣ c)

/--A partial order with the symbol | -/
class dvd_order extends dvd_preorder α :=
(dvd_antisymm : ∀ a b : α, a ∣ b → b ∣ a → a = b)

/--A multiplicative analog of canonically_ordered_add_monoid-/
class dvd_monoid extends comm_monoid α, has_dvd α :=
(dvd_iff_exists_mul : ∀ a b : α, a ∣ b ↔ ∃ c : α, b = a * c)

class dvd_lattice extends dvd_monoid α :=
(gcd : α → α → α)
(inf_le_left : ∀ (a b : α), gcd a b ∣ a)
(inf_le_right : ∀ (a b : α), gcd a b ∣ b)
(le_inf : ∀ (a b c : α), a ∣ b → a ∣ c → a ∣ gcd b c)
(lcm : α → α → α)
(dvd_lcm_left : ∀ (a b : α), a ∣ lcm a b)
(dvd_lcm_right : ∀ (a b : α), b ∣ lcm a b)
(lcm_dvd : ∀ (a b c : α), a ∣ c → b ∣ c → lcm a b ∣ c)
(coprime : α → α → Prop)
(coprime_iff_gcd_one : ∀ (a b : α), coprime a b ↔ gcd a b = 1)

@[instance]
instance dvd_monoid.dvd_preorder [dvd_monoid α] : dvd_preorder α :=
begin
  refine dvd_preorder.mk has_dvd.dvd _ _; simp_rw dvd_monoid.dvd_iff_exists_mul; intros,
  { existsi (1 : α), simp },
  { cases a_1 with d hd, cases a_2 with e he, existsi d * e, rw [← mul_assoc, he, hd] }
end

@[instance]
instance additive.has_le [dvd_preorder α] : has_le (additive α) :=
{ le := λ x y, has_dvd.dvd x.to_mul y.to_mul }

@[instance]
instance multiplicative.has_dvd [has_le α] :
  has_dvd (multiplicative α) :=
{ dvd := λ x y, has_le.le x.to_add y.to_add }

@[instance]
instance multiplicative.dvd_preorder [preorder α] :
  dvd_preorder (multiplicative α) :=
{ dvd_refl := by { unfold has_dvd.dvd, apply le_refl },
  dvd_trans := by { unfold has_dvd.dvd, apply le_trans },
  ..multiplicative.has_dvd α }

@[instance]
instance additive.preorder [dvd_preorder α] :
  preorder (additive α) :=
{ le_refl := dvd_preorder.dvd_refl,
  le_trans := dvd_preorder.dvd_trans,
  ..additive.has_le α }

@[instance]
instance multiplicative.dvd_order [partial_order α] :
  dvd_order (multiplicative α) :=
{ dvd_antisymm := by { unfold has_dvd.dvd, apply le_antisymm },
  ..multiplicative.dvd_preorder α }

/--
@[instance]
instance dvd_monoid.canonically_ordered_add_monoid [dvd_monoid α] [dvd_order α] :
  canonically_ordered_add_monoid (additive α) :=
{ lt_of_add_lt_add_left := by {  },
  le_iff_exists_add     := by { unfold has_add.add, apply dvd_monoid.dvd_iff_exists_mul },
  add_le_add_left       := by { unfold has_add.add, rw le_iff_exists_add, },
  bot                   := 0,
  bot_le                := by { unfold has_le.le,  },
  ..additive.add_comm_monoid,
  ..additive.partial_order α }
  -/

@[instance]
instance additive.partial_order [dvd_order α] :
  partial_order (additive α) :=
{ le_antisymm := dvd_order.dvd_antisymm,
  ..additive.preorder α }

end divisibility_lattice

section order_iso_lattice

variables (α β: Type)

structure order_iso' [preorder α] [preorder β] :=
(to_equiv : α ≃ β)
(le_iff_le : ∀ a₁ a₂ : α, to_equiv.to_fun a₁ ≤ to_equiv.to_fun a₂ ↔ a₁ ≤ a₂ )

variables {α β}

def order_iso'.to_fun [preorder α] [preorder β] (f : order_iso' α β) : α → β := f.to_equiv.to_fun

instance [preorder α] [preorder β] : has_coe_to_fun (order_iso' α β) := ⟨ _, order_iso'.to_fun ⟩

lemma order_iso'.coe_eq_equiv_to_fun [preorder α] [preorder β] (f : order_iso' α β) :
  (f : α → β) = f.to_equiv.to_fun := rfl

lemma order_iso'.coe_monotone [partial_order α] [partial_order β] (f : order_iso' α β) :
  monotone f := by { unfold monotone, simp_rw [f.coe_eq_equiv_to_fun, f.le_iff_le], simp }

def order_iso'.inv_fun [preorder α] [preorder β] (f : order_iso' α β) : β → α := f.to_equiv.inv_fun

lemma order_iso'.monotone_inv_fun [partial_order α] [partial_order β] (f : order_iso' α β) :
  monotone f.inv_fun :=
begin
  unfold monotone, unfold order_iso'.inv_fun,
  intros, rw [← f.le_iff_le, f.to_equiv.right_inv, f.to_equiv.right_inv], apply a_1
end

variables [lattice α] [lattice β] {f : order_iso' α β}

lemma order_iso'.inf_map {a₁ a₂ : α} : f (a₁ ⊓ a₂) = f (a₁) ⊓ f (a₂) :=
begin
  apply lattice.le_antisymm,
  { apply lattice.le_inf; refine f.coe_monotone _, apply inf_le_left, apply inf_le_right },
  { rw ← f.to_equiv.right_inv f a₁ ⊓ f a₂, rw f.le_iff_le, },
end

end order_iso_lattice


/-
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 Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.linear_algebra.basic
import Mathlib.algebra.algebra.basic
import Mathlib.tactic.omega.default
import Mathlib.data.fintype.sort
import Mathlib.PostPort

universes u v w u' l v₁ v₂ u_1 v₃ v' u_2 u_3 u_5 u_6 u_7 u_4

namespace Mathlib

/-!
# Multilinear maps

We define multilinear maps as maps from `Π(i : ι), M₁ i` to `M₂` which are linear in each
coordinate. Here, `M₁ i` and `M₂` are modules over a ring `R`, and `ι` is an arbitrary type
(although some statements will require it to be a fintype). This space, denoted by
`multilinear_map R M₁ M₂`, inherits a module structure by pointwise addition and multiplication.

## Main definitions

* `multilinear_map R M₁ M₂` is the space of multilinear maps from `Π(i : ι), M₁ i` to `M₂`.
* `f.map_smul` is the multiplicativity of the multilinear map `f` along each coordinate.
* `f.map_add` is the additivity of the multilinear map `f` along each coordinate.
* `f.map_smul_univ` expresses the multiplicativity of `f` over all coordinates at the same time,
  writing `f (λi, c i • m i)` as `(∏ i, c i) • f m`.
* `f.map_add_univ` expresses the additivity of `f` over all coordinates at the same time, writing
  `f (m + m')` as the sum over all subsets `s` of `ι` of `f (s.piecewise m m')`.
* `f.map_sum` expresses `f (Σ_{j₁} g₁ j₁, ..., Σ_{jₙ} gₙ jₙ)` as the sum of
  `f (g₁ (r 1), ..., gₙ (r n))` where `r` ranges over all possible functions.

We also register isomorphisms corresponding to currying or uncurrying variables, transforming a
multilinear function `f` on `n+1` variables into a linear function taking values in multilinear
functions in `n` variables, and into a multilinear function in `n` variables taking values in linear
functions. These operations are called `f.curry_left` and `f.curry_right` respectively
(with inverses `f.uncurry_left` and `f.uncurry_right`). These operations induce linear equivalences
between spaces of multilinear functions in `n+1` variables and spaces of linear functions into
multilinear functions in `n` variables (resp. multilinear functions in `n` variables taking values
in linear functions), called respectively `multilinear_curry_left_equiv` and
`multilinear_curry_right_equiv`.

## Implementation notes

Expressing that a map is linear along the `i`-th coordinate when all other coordinates are fixed
can be done in two (equivalent) different ways:
* fixing a vector `m : Π(j : ι - i), M₁ j.val`, and then choosing separately the `i`-th coordinate
* fixing a vector `m : Πj, M₁ j`, and then modifying its `i`-th coordinate
The second way is more artificial as the value of `m` at `i` is not relevant, but it has the
advantage of avoiding subtype inclusion issues. This is the definition we use, based on
`function.update` that allows to change the value of `m` at `i`.
-/

/-- Multilinear maps over the ring `R`, from `Πi, M₁ i` to `M₂` where `M₁ i` and `M₂` are modules
over `R`. -/
structure multilinear_map (R : Type u) {ι : Type u'} (M₁ : ι → Type v) (M₂ : Type w) [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂]
    where
  to_fun : ((i : ι) → M₁ i) → M₂
  map_add' :
    ∀ (m : (i : ι) → M₁ i) (i : ι) (x y : M₁ i),
      to_fun (function.update m i (x + y)) =
        to_fun (function.update m i x) + to_fun (function.update m i y)
  map_smul' :
    ∀ (m : (i : ι) → M₁ i) (i : ι) (c : R) (x : M₁ i),
      to_fun (function.update m i (c • x)) = c • to_fun (function.update m i x)

namespace multilinear_map

protected instance has_coe_to_fun {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : has_coe_to_fun (multilinear_map R M₁ M₂) :=
  has_coe_to_fun.mk (fun (x : multilinear_map R M₁ M₂) => ((i : ι) → M₁ i) → M₂) to_fun

@[simp] theorem to_fun_eq_coe {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂) :
    to_fun f = ⇑f :=
  rfl

@[simp] theorem coe_mk {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : ((i : ι) → M₁ i) → M₂)
    (h₁ :
      ∀ (m : (i : ι) → M₁ i) (i : ι) (x y : M₁ i),
        f (function.update m i (x + y)) = f (function.update m i x) + f (function.update m i y))
    (h₂ :
      ∀ (m : (i : ι) → M₁ i) (i : ι) (c : R) (x : M₁ i),
        f (function.update m i (c • x)) = c • f (function.update m i x)) :
    ⇑(mk f h₁ h₂) = f :=
  rfl

theorem congr_fun {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {f : multilinear_map R M₁ M₂}
    {g : multilinear_map R M₁ M₂} (h : f = g) (x : (i : ι) → M₁ i) : coe_fn f x = coe_fn g x :=
  congr_arg (fun (h : multilinear_map R M₁ M₂) => coe_fn h x) h

theorem congr_arg {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    {x : (i : ι) → M₁ i} {y : (i : ι) → M₁ i} (h : x = y) : coe_fn f x = coe_fn f y :=
  congr_arg (fun (x : (i : ι) → M₁ i) => coe_fn f x) h

theorem coe_inj {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {f : multilinear_map R M₁ M₂}
    {g : multilinear_map R M₁ M₂} (h : ⇑f = ⇑g) : f = g :=
  sorry

theorem ext {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {f : multilinear_map R M₁ M₂}
    {f' : multilinear_map R M₁ M₂} (H : ∀ (x : (i : ι) → M₁ i), coe_fn f x = coe_fn f' x) :
    f = f' :=
  coe_inj (funext H)

theorem ext_iff {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {f : multilinear_map R M₁ M₂}
    {g : multilinear_map R M₁ M₂} : f = g ↔ ∀ (x : (i : ι) → M₁ i), coe_fn f x = coe_fn g x :=
  { mp := fun (h : f = g) (x : (i : ι) → M₁ i) => h ▸ rfl,
    mpr := fun (h : ∀ (x : (i : ι) → M₁ i), coe_fn f x = coe_fn g x) => ext h }

@[simp] theorem map_add {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) (i : ι) (x : M₁ i) (y : M₁ i) :
    coe_fn f (function.update m i (x + y)) =
        coe_fn f (function.update m i x) + coe_fn f (function.update m i y) :=
  map_add' f m i x y

@[simp] theorem map_smul {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) (i : ι) (c : R) (x : M₁ i) :
    coe_fn f (function.update m i (c • x)) = c • coe_fn f (function.update m i x) :=
  map_smul' f m i c x

theorem map_coord_zero {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    {m : (i : ι) → M₁ i} (i : ι) (h : m i = 0) : coe_fn f m = 0 :=
  sorry

@[simp] theorem map_update_zero {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) (i : ι) : coe_fn f (function.update m i 0) = 0 :=
  map_coord_zero f i (function.update_same i 0 m)

@[simp] theorem map_zero {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂) [Nonempty ι] :
    coe_fn f 0 = 0 :=
  Exists.dcases_on (set.exists_mem_of_nonempty ι)
    fun (i : ι) (h : i ∈ set.univ) => map_coord_zero f i rfl

protected instance has_add {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : Add (multilinear_map R M₁ M₂) :=
  { add :=
      fun (f f' : multilinear_map R M₁ M₂) =>
        mk (fun (x : (i : ι) → M₁ i) => coe_fn f x + coe_fn f' x) sorry sorry }

@[simp] theorem add_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (f' : multilinear_map R M₁ M₂) (m : (i : ι) → M₁ i) :
    coe_fn (f + f') m = coe_fn f m + coe_fn f' m :=
  rfl

protected instance has_zero {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : HasZero (multilinear_map R M₁ M₂) :=
  { zero := mk (fun (_x : (i : ι) → M₁ i) => 0) sorry sorry }

protected instance inhabited {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : Inhabited (multilinear_map R M₁ M₂) :=
  { default := 0 }

@[simp] theorem zero_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (m : (i : ι) → M₁ i) : coe_fn 0 m = 0 :=
  rfl

protected instance add_comm_monoid {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : add_comm_monoid (multilinear_map R M₁ M₂) :=
  add_comm_monoid.mk Add.add sorry 0 sorry sorry sorry

@[simp] theorem sum_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {α : Type u_1}
    (f : α → multilinear_map R M₁ M₂) (m : (i : ι) → M₁ i) {s : finset α} :
    coe_fn (finset.sum s fun (a : α) => f a) m = finset.sum s fun (a : α) => coe_fn (f a) m :=
  sorry

/-- If `f` is a multilinear map, then `f.to_linear_map m i` is the linear map obtained by fixing all
coordinates but `i` equal to those of `m`, and varying the `i`-th coordinate. -/
def to_linear_map {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) (i : ι) : linear_map R (M₁ i) M₂ :=
  linear_map.mk (fun (x : M₁ i) => coe_fn f (function.update m i x)) sorry sorry

/-- The cartesian product of two multilinear maps, as a multilinear map. -/
def prod {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} {M₃ : Type v₃} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] [add_comm_monoid M₃]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] [semimodule R M₃]
    (f : multilinear_map R M₁ M₂) (g : multilinear_map R M₁ M₃) : multilinear_map R M₁ (M₂ × M₃) :=
  mk (fun (m : (i : ι) → M₁ i) => (coe_fn f m, coe_fn g m)) sorry sorry

/-- Given a multilinear map `f` on `n` variables (parameterized by `fin n`) and a subset `s` of `k`
of these variables, one gets a new multilinear map on `fin k` by varying these variables, and fixing
the other ones equal to a given value `z`. It is denoted by `f.restr s hk z`, where `hk` is a
proof that the cardinality of `s` is `k`. The implicit identification between `fin k` and `s` that
we use is the canonical (increasing) bijection. -/
def restr {R : Type u} {M₂ : Type v₂} {M' : Type v'} [semiring R] [add_comm_monoid M₂]
    [add_comm_monoid M'] [semimodule R M₂] [semimodule R M'] {k : ℕ} {n : ℕ}
    (f : multilinear_map R (fun (i : fin n) => M') M₂) (s : finset (fin n)) (hk : finset.card s = k)
    (z : M') : multilinear_map R (fun (i : fin k) => M') M₂ :=
  mk
    (fun (v : fin k → M') =>
      coe_fn f
        fun (j : fin n) =>
          dite (j ∈ s)
            (fun (h : j ∈ s) =>
              v
                (coe_fn (order_iso.symm (finset.order_iso_of_fin s hk))
                  { val := j, property := h }))
            fun (h : ¬j ∈ s) => z)
    sorry sorry

/-- In the specific case of 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. -/
theorem cons_add {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂)
    (m : (i : fin n) → M (fin.succ i)) (x : M 0) (y : M 0) :
    coe_fn f (fin.cons (x + y) m) = coe_fn f (fin.cons x m) + coe_fn f (fin.cons y m) :=
  sorry

/-- In the specific case of 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. -/
theorem cons_smul {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂)
    (m : (i : fin n) → M (fin.succ i)) (c : R) (x : M 0) :
    coe_fn f (fin.cons (c • x) m) = c • coe_fn f (fin.cons x m) :=
  sorry

/-- In the specific case of multilinear maps on spaces indexed by `fin (n+1)`, where one can build
an element of `Π(i : fin (n+1)), M i` using `snoc`, one can express directly the additivity of a
multilinear map along the first variable. -/
theorem snoc_add {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂)
    (m : (i : fin n) → M (coe_fn fin.cast_succ i)) (x : M (fin.last n)) (y : M (fin.last n)) :
    coe_fn f (fin.snoc m (x + y)) = coe_fn f (fin.snoc m x) + coe_fn f (fin.snoc m y) :=
  sorry

/-- In the specific case of 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. -/
theorem snoc_smul {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂)
    (m : (i : fin n) → M (coe_fn fin.cast_succ i)) (c : R) (x : M (fin.last n)) :
    coe_fn f (fin.snoc m (c • x)) = c • coe_fn f (fin.snoc m x) :=
  sorry

/-- If `g` is a multilinear map and `f` is a collection of linear maps,
then `g (f₁ m₁, ..., fₙ mₙ)` is again a multilinear map, that we call
`g.comp_linear_map f`. -/
def comp_linear_map {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {M₁' : ι → Type u_1}
    [(i : ι) → add_comm_monoid (M₁' i)] [(i : ι) → semimodule R (M₁' i)]
    (g : multilinear_map R M₁' M₂) (f : (i : ι) → linear_map R (M₁ i) (M₁' i)) :
    multilinear_map R M₁ M₂ :=
  mk (fun (m : (i : ι) → M₁ i) => coe_fn g fun (i : ι) => coe_fn (f i) (m i)) sorry sorry

@[simp] theorem comp_linear_map_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {M₁' : ι → Type u_1}
    [(i : ι) → add_comm_monoid (M₁' i)] [(i : ι) → semimodule R (M₁' i)]
    (g : multilinear_map R M₁' M₂) (f : (i : ι) → linear_map R (M₁ i) (M₁' i))
    (m : (i : ι) → M₁ i) :
    coe_fn (comp_linear_map g f) m = coe_fn g fun (i : ι) => coe_fn (f i) (m i) :=
  rfl

/-- If one adds to a vector `m'` another vector `m`, but only for coordinates in a finset `t`, then
the image under a multilinear map `f` is the sum of `f (s.piecewise m m')` along all subsets `s` of
`t`. This is mainly an auxiliary statement to prove the result when `t = univ`, given in
`map_add_univ`, although it can be useful in its own right as it does not require the index set `ι`
to be finite.-/
theorem map_piecewise_add {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) (m' : (i : ι) → M₁ i) (t : finset ι) :
    coe_fn f (finset.piecewise t (m + m') m') =
        finset.sum (finset.powerset t) fun (s : finset ι) => coe_fn f (finset.piecewise s m m') :=
  sorry

/-- Additivity of a 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`. -/
theorem map_add_univ {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂) [fintype ι]
    (m : (i : ι) → M₁ i) (m' : (i : ι) → M₁ i) :
    coe_fn f (m + m') =
        finset.sum finset.univ fun (s : finset ι) => coe_fn f (finset.piecewise s m m') :=
  sorry

/-- If `f` is 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. Here, we give an auxiliary statement tailored for an inductive proof. Use instead
`map_sum_finset`. -/
theorem map_sum_finset_aux {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    {α : ι → Type u_1} (g : (i : ι) → α i → M₁ i) (A : (i : ι) → finset (α i)) [fintype ι] {n : ℕ}
    (h : (finset.sum finset.univ fun (i : ι) => finset.card (A i)) = n) :
    (coe_fn f fun (i : ι) => finset.sum (A i) fun (j : α i) => g i j) =
        finset.sum (fintype.pi_finset A)
          fun (r : (a : ι) → α a) => coe_fn f fun (i : ι) => g i (r i) :=
  sorry

/-- If `f` is 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. -/
theorem map_sum_finset {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    {α : ι → Type u_1} (g : (i : ι) → α i → M₁ i) (A : (i : ι) → finset (α i)) [fintype ι] :
    (coe_fn f fun (i : ι) => finset.sum (A i) fun (j : α i) => g i j) =
        finset.sum (fintype.pi_finset A)
          fun (r : (a : ι) → α a) => coe_fn f fun (i : ι) => g i (r i) :=
  map_sum_finset_aux f (fun (i : ι) (j : α i) => g i j) (fun (i : ι) => A i) rfl

/-- If `f` is 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. -/
theorem map_sum {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    {α : ι → Type u_1} (g : (i : ι) → α i → M₁ i) [fintype ι] [(i : ι) → fintype (α i)] :
    (coe_fn f fun (i : ι) => finset.sum finset.univ fun (j : α i) => g i j) =
        finset.sum finset.univ fun (r : (i : ι) → α i) => coe_fn f fun (i : ι) => g i (r i) :=
  map_sum_finset f g fun (i : ι) => finset.univ

theorem map_update_sum {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂) {α : Type u_1}
    (t : finset α) (i : ι) (g : α → M₁ i) (m : (i : ι) → M₁ i) :
    coe_fn f (function.update m i (finset.sum t fun (a : α) => g a)) =
        finset.sum t fun (a : α) => coe_fn f (function.update m i (g a)) :=
  sorry

/-- Reinterpret an `A`-multilinear map as an `R`-multilinear map, if `A` is an algebra over `R`
and their actions on all involved semimodules agree with the action of `R` on `A`. -/
def restrict_scalars (R : Type u) {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {A : Type u_1} [semiring A] [has_scalar R A]
    [(i : ι) → semimodule A (M₁ i)] [semimodule A M₂] [∀ (i : ι), is_scalar_tower R A (M₁ i)]
    [is_scalar_tower R A M₂] (f : multilinear_map A M₁ M₂) : multilinear_map R M₁ M₂ :=
  mk ⇑f sorry sorry

@[simp] theorem coe_restrict_scalars (R : Type u) {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] {A : Type u_1} [semiring A] [has_scalar R A]
    [(i : ι) → semimodule A (M₁ i)] [semimodule A M₂] [∀ (i : ι), is_scalar_tower R A (M₁ i)]
    [is_scalar_tower R A M₂] (f : multilinear_map A M₁ M₂) : ⇑(restrict_scalars R f) = ⇑f :=
  rfl

/-- Transfer the arguments to a map along an equivalence between argument indices.

The naming is derived from `finsupp.dom_congr`, noting that here the permutation applies to the
domain of the domain. -/
@[simp] theorem dom_dom_congr_apply {R : Type u} {M₂ : Type v₂} {M₃ : Type v₃} [semiring R]
    [add_comm_monoid M₂] [add_comm_monoid M₃] [semimodule R M₂] [semimodule R M₃] {ι₁ : Type u_1}
    {ι₂ : Type u_2} [DecidableEq ι₁] [DecidableEq ι₂] (σ : ι₁ ≃ ι₂)
    (m : multilinear_map R (fun (i : ι₁) => M₂) M₃) (v : ι₂ → M₂) :
    coe_fn (dom_dom_congr σ m) v = coe_fn m fun (i : ι₁) => v (coe_fn σ i) :=
  Eq.refl (coe_fn (dom_dom_congr σ m) v)

theorem dom_dom_congr_trans {R : Type u} {M₂ : Type v₂} {M₃ : Type v₃} [semiring R]
    [add_comm_monoid M₂] [add_comm_monoid M₃] [semimodule R M₂] [semimodule R M₃] {ι₁ : Type u_1}
    {ι₂ : Type u_2} {ι₃ : Type u_3} [DecidableEq ι₁] [DecidableEq ι₂] [DecidableEq ι₃]
    (σ₁ : ι₁ ≃ ι₂) (σ₂ : ι₂ ≃ ι₃) (m : multilinear_map R (fun (i : ι₁) => M₂) M₃) :
    dom_dom_congr (equiv.trans σ₁ σ₂) m = dom_dom_congr σ₂ (dom_dom_congr σ₁ m) :=
  rfl

theorem dom_dom_congr_mul {R : Type u} {M₂ : Type v₂} {M₃ : Type v₃} [semiring R]
    [add_comm_monoid M₂] [add_comm_monoid M₃] [semimodule R M₂] [semimodule R M₃] {ι₁ : Type u_1}
    [DecidableEq ι₁] (σ₁ : equiv.perm ι₁) (σ₂ : equiv.perm ι₁)
    (m : multilinear_map R (fun (i : ι₁) => M₂) M₃) :
    dom_dom_congr (σ₂ * σ₁) m = dom_dom_congr σ₂ (dom_dom_congr σ₁ m) :=
  rfl

/-- `multilinear_map.dom_dom_congr` as an equivalence.

This is declared separately because it does not work with dot notation. -/
def dom_dom_congr_equiv {R : Type u} {M₂ : Type v₂} {M₃ : Type v₃} [semiring R] [add_comm_monoid M₂]
    [add_comm_monoid M₃] [semimodule R M₂] [semimodule R M₃] {ι₁ : Type u_1} {ι₂ : Type u_2}
    [DecidableEq ι₁] [DecidableEq ι₂] (σ : ι₁ ≃ ι₂) :
    multilinear_map R (fun (i : ι₁) => M₂) M₃ ≃+ multilinear_map R (fun (i : ι₂) => M₂) M₃ :=
  add_equiv.mk (dom_dom_congr σ) (dom_dom_congr (equiv.symm σ)) sorry sorry sorry

end multilinear_map

namespace linear_map

/-- Composing a multilinear map with a linear map gives again a multilinear map. -/
def comp_multilinear_map {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} {M₃ : Type v₃}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [add_comm_monoid M₃] [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] [semimodule R M₃]
    (g : linear_map R M₂ M₃) (f : multilinear_map R M₁ M₂) : multilinear_map R M₁ M₃ :=
  multilinear_map.mk (⇑g ∘ ⇑f) sorry sorry

@[simp] theorem coe_comp_multilinear_map {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁}
    {M₂ : Type v₂} {M₃ : Type v₃} [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)]
    [add_comm_monoid M₂] [add_comm_monoid M₃] [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂]
    [semimodule R M₃] (g : linear_map R M₂ M₃) (f : multilinear_map R M₁ M₂) :
    ⇑(comp_multilinear_map g f) = ⇑g ∘ ⇑f :=
  rfl

theorem comp_multilinear_map_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    {M₃ : Type v₃} [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)]
    [add_comm_monoid M₂] [add_comm_monoid M₃] [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂]
    [semimodule R M₃] (g : linear_map R M₂ M₃) (f : multilinear_map R M₁ M₂) (m : (i : ι) → M₁ i) :
    coe_fn (comp_multilinear_map g f) m = coe_fn g (coe_fn f m) :=
  rfl

@[simp] theorem comp_multilinear_map_dom_dom_congr {R : Type u} {M₂ : Type v₂} {M₃ : Type v₃}
    {M' : Type v'} [semiring R] [add_comm_monoid M₂] [add_comm_monoid M₃] [add_comm_monoid M']
    [semimodule R M₂] [semimodule R M₃] [semimodule R M'] {ι₁ : Type u_1} {ι₂ : Type u_2}
    [DecidableEq ι₁] [DecidableEq ι₂] (σ : ι₁ ≃ ι₂) (g : linear_map R M₂ M₃)
    (f : multilinear_map R (fun (i : ι₁) => M') M₂) :
    multilinear_map.dom_dom_congr σ (comp_multilinear_map g f) =
        comp_multilinear_map g (multilinear_map.dom_dom_congr σ f) :=
  sorry

end linear_map

namespace multilinear_map

/-- If one multiplies by `c i` the coordinates in a finset `s`, then the image under a multilinear
map is multiplied by `∏ i in s, c i`. This is mainly an auxiliary statement to prove the result when
`s = univ`, given in `map_smul_univ`, although it can be useful in its own right as it does not
require the index set `ι` to be finite. -/
theorem map_piecewise_smul {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [comm_semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂) (c : ι → R)
    (m : (i : ι) → M₁ i) (s : finset ι) :
    coe_fn f (finset.piecewise s (fun (i : ι) => c i • m i) m) =
        (finset.prod s fun (i : ι) => c i) • coe_fn f m :=
  sorry

/-- Multiplicativity of a multilinear map along all coordinates at the same time,
writing `f (λi, c i • m i)` as `(∏ i, c i) • f m`. -/
theorem map_smul_univ {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [comm_semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂) [fintype ι]
    (c : ι → R) (m : (i : ι) → M₁ i) :
    (coe_fn f fun (i : ι) => c i • m i) =
        (finset.prod finset.univ fun (i : ι) => c i) • coe_fn f m :=
  sorry

protected instance has_scalar {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] {R' : Type u_1} {A : Type u_2}
    [monoid R'] [semiring A] [(i : ι) → semimodule A (M₁ i)] [distrib_mul_action R' M₂]
    [semimodule A M₂] [smul_comm_class A R' M₂] : has_scalar R' (multilinear_map A M₁ M₂) :=
  has_scalar.mk
    fun (c : R') (f : multilinear_map A M₁ M₂) =>
      mk (fun (m : (i : ι) → M₁ i) => c • coe_fn f m) sorry sorry

@[simp] theorem smul_apply {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] {R' : Type u_1} {A : Type u_2}
    [monoid R'] [semiring A] [(i : ι) → semimodule A (M₁ i)] [distrib_mul_action R' M₂]
    [semimodule A M₂] [smul_comm_class A R' M₂] (f : multilinear_map A M₁ M₂) (c : R')
    (m : (i : ι) → M₁ i) : coe_fn (c • f) m = c • coe_fn f m :=
  rfl

protected instance distrib_mul_action {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] {R' : Type u_1}
    {A : Type u_2} [monoid R'] [semiring A] [(i : ι) → semimodule A (M₁ i)]
    [distrib_mul_action R' M₂] [semimodule A M₂] [smul_comm_class A R' M₂] :
    distrib_mul_action R' (multilinear_map A M₁ M₂) :=
  distrib_mul_action.mk sorry sorry

/-- The space of multilinear maps over an algebra over `R` is a module over `R`, for the pointwise
addition and scalar multiplication. -/
protected instance semimodule {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] {R' : Type u_1} {A : Type u_2}
    [semiring R'] [semiring A] [(i : ι) → semimodule A (M₁ i)] [semimodule R' M₂] [semimodule A M₂]
    [smul_comm_class A R' M₂] : semimodule R' (multilinear_map A M₁ M₂) :=
  semimodule.mk sorry sorry

/-- Given two multilinear maps `(ι₁ → N) → N₁` and `(ι₂ → N) → N₂`, this produces the map
`(ι₁ ⊕ ι₂ → N) → N₁ ⊗ N₂` by taking the coproduct of the domain and the tensor product
of the codomain.

This can be thought of as combining `equiv.sum_arrow_equiv_prod_arrow.symm` with
`tensor_product.map`, noting that the two operations can't be separated as the intermediate result
is not a `multilinear_map`.

While this can be generalized to work for dependent `Π i : ι₁, N'₁ i` instead of `ι₁ → N`, doing so
introduces `sum.elim N'₁ N'₂` types in the result which are difficult to work with and not defeq
to the simple case defined here. See [this zulip thread](
https://leanprover.zulipchat.com/#narrow/stream/217875-Is-there.20code.20for.20X.3F/topic/Instances.20on.20.60sum.2Eelim.20A.20B.20i.60/near/218484619).
-/
def dom_coprod {R : Type u} [comm_semiring R] {ι₁ : Type u_1} {ι₂ : Type u_2} [DecidableEq ι₁]
    [DecidableEq ι₂] {N₁ : Type u_5} [add_comm_monoid N₁] [semimodule R N₁] {N₂ : Type u_6}
    [add_comm_monoid N₂] [semimodule R N₂] {N : Type u_7} [add_comm_monoid N] [semimodule R N]
    (a : multilinear_map R (fun (_x : ι₁) => N) N₁)
    (b : multilinear_map R (fun (_x : ι₂) => N) N₂) :
    multilinear_map R (fun (_x : ι₁ ⊕ ι₂) => N) (tensor_product R N₁ N₂) :=
  mk
    (fun (v : ι₁ ⊕ ι₂ → N) =>
      tensor_product.tmul R (coe_fn a fun (i : ι₁) => v (sum.inl i))
        (coe_fn b fun (i : ι₂) => v (sum.inr i)))
    sorry sorry

/-- A more bundled version of `multilinear_map.dom_coprod` that maps
`((ι₁ → N) → N₁) ⊗ ((ι₂ → N) → N₂)` to `(ι₁ ⊕ ι₂ → N) → N₁ ⊗ N₂`. -/
def dom_coprod' {R : Type u} [comm_semiring R] {ι₁ : Type u_1} {ι₂ : Type u_2} [DecidableEq ι₁]
    [DecidableEq ι₂] {N₁ : Type u_5} [add_comm_monoid N₁] [semimodule R N₁] {N₂ : Type u_6}
    [add_comm_monoid N₂] [semimodule R N₂] {N : Type u_7} [add_comm_monoid N] [semimodule R N] :
    linear_map R
        (tensor_product R (multilinear_map R (fun (_x : ι₁) => N) N₁)
          (multilinear_map R (fun (_x : ι₂) => N) N₂))
        (multilinear_map R (fun (_x : ι₁ ⊕ ι₂) => N) (tensor_product R N₁ N₂)) :=
  tensor_product.lift (linear_map.mk₂ R dom_coprod sorry sorry sorry sorry)

@[simp] theorem dom_coprod'_apply {R : Type u} [comm_semiring R] {ι₁ : Type u_1} {ι₂ : Type u_2}
    [DecidableEq ι₁] [DecidableEq ι₂] {N₁ : Type u_5} [add_comm_monoid N₁] [semimodule R N₁]
    {N₂ : Type u_6} [add_comm_monoid N₂] [semimodule R N₂] {N : Type u_7} [add_comm_monoid N]
    [semimodule R N] (a : multilinear_map R (fun (_x : ι₁) => N) N₁)
    (b : multilinear_map R (fun (_x : ι₂) => N) N₂) :
    coe_fn dom_coprod' (tensor_product.tmul R a b) = dom_coprod a b :=
  rfl

/-- When passed an `equiv.sum_congr`, `multilinear_map.dom_dom_congr` distributes over
`multilinear_map.dom_coprod`. -/
theorem dom_coprod_dom_dom_congr_sum_congr {R : Type u} [comm_semiring R] {ι₁ : Type u_1}
    {ι₂ : Type u_2} {ι₃ : Type u_3} {ι₄ : Type u_4} [DecidableEq ι₁] [DecidableEq ι₂]
    [DecidableEq ι₃] [DecidableEq ι₄] {N₁ : Type u_5} [add_comm_monoid N₁] [semimodule R N₁]
    {N₂ : Type u_6} [add_comm_monoid N₂] [semimodule R N₂] {N : Type u_7} [add_comm_monoid N]
    [semimodule R N] (a : multilinear_map R (fun (_x : ι₁) => N) N₁)
    (b : multilinear_map R (fun (_x : ι₂) => N) N₂) (σa : ι₁ ≃ ι₃) (σb : ι₂ ≃ ι₄) :
    dom_dom_congr (equiv.sum_congr σa σb) (dom_coprod a b) =
        dom_coprod (dom_dom_congr σa a) (dom_dom_congr σb b) :=
  rfl

/-- Given an `R`-algebra `A`, `mk_pi_algebra` is the multilinear map on `A^ι` associating
to `m` the product of all the `m i`.

See also `multilinear_map.mk_pi_algebra_fin` for a version that works with a non-commutative
algebra `A` but requires `ι = fin n`. -/
protected def mk_pi_algebra (R : Type u) (ι : Type u') [DecidableEq ι] [comm_semiring R]
    (A : Type u_1) [comm_semiring A] [algebra R A] [fintype ι] :
    multilinear_map R (fun (i : ι) => A) A :=
  mk (fun (m : ι → A) => finset.prod finset.univ fun (i : ι) => m i) sorry sorry

@[simp] theorem mk_pi_algebra_apply {R : Type u} {ι : Type u'} [DecidableEq ι] [comm_semiring R]
    {A : Type u_1} [comm_semiring A] [algebra R A] [fintype ι] (m : ι → A) :
    coe_fn (multilinear_map.mk_pi_algebra R ι A) m = finset.prod finset.univ fun (i : ι) => m i :=
  rfl

/-- Given an `R`-algebra `A`, `mk_pi_algebra_fin` is the multilinear map on `A^n` associating
to `m` the product of all the `m i`.

See also `multilinear_map.mk_pi_algebra` for a version that assumes `[comm_semiring A]` but works
for `A^ι` with any finite type `ι`. -/
protected def mk_pi_algebra_fin (R : Type u) (n : ℕ) [comm_semiring R] (A : Type u_1) [semiring A]
    [algebra R A] : multilinear_map R (fun (i : fin n) => A) A :=
  mk (fun (m : fin n → A) => list.prod (list.of_fn m)) sorry sorry

@[simp] theorem mk_pi_algebra_fin_apply {R : Type u} {n : ℕ} [comm_semiring R] {A : Type u_1}
    [semiring A] [algebra R A] (m : fin n → A) :
    coe_fn (multilinear_map.mk_pi_algebra_fin R n A) m = list.prod (list.of_fn m) :=
  rfl

theorem mk_pi_algebra_fin_apply_const {R : Type u} {n : ℕ} [comm_semiring R] {A : Type u_1}
    [semiring A] [algebra R A] (a : A) :
    (coe_fn (multilinear_map.mk_pi_algebra_fin R n A) fun (_x : fin n) => a) = a ^ n :=
  sorry

/-- Given an `R`-multilinear map `f` taking values in `R`, `f.smul_right z` is the map
sending `m` to `f m • z`. -/
def smul_right {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [comm_semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ R) (z : M₂) :
    multilinear_map R M₁ M₂ :=
  linear_map.comp_multilinear_map (linear_map.smul_right linear_map.id z) f

@[simp] theorem smul_right_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [comm_semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ R) (z : M₂)
    (m : (i : ι) → M₁ i) : coe_fn (smul_right f z) m = coe_fn f m • z :=
  rfl

/-- The canonical multilinear map on `R^ι` when `ι` is finite, associating to `m` the product of
all the `m i` (multiplied by a fixed reference element `z` in the target module). See also
`mk_pi_algebra` for a more general version. -/
protected def mk_pi_ring (R : Type u) (ι : Type u') {M₂ : Type v₂} [DecidableEq ι] [comm_semiring R]
    [add_comm_monoid M₂] [semimodule R M₂] [fintype ι] (z : M₂) :
    multilinear_map R (fun (i : ι) => R) M₂ :=
  smul_right (multilinear_map.mk_pi_algebra R ι R) z

@[simp] theorem mk_pi_ring_apply {R : Type u} {ι : Type u'} {M₂ : Type v₂} [DecidableEq ι]
    [comm_semiring R] [add_comm_monoid M₂] [semimodule R M₂] [fintype ι] (z : M₂) (m : ι → R) :
    coe_fn (multilinear_map.mk_pi_ring R ι z) m =
        (finset.prod finset.univ fun (i : ι) => m i) • z :=
  rfl

theorem mk_pi_ring_apply_one_eq_self {R : Type u} {ι : Type u'} {M₂ : Type v₂} [DecidableEq ι]
    [comm_semiring R] [add_comm_monoid M₂] [semimodule R M₂] [fintype ι]
    (f : multilinear_map R (fun (i : ι) => R) M₂) :
    multilinear_map.mk_pi_ring R ι (coe_fn f fun (i : ι) => 1) = f :=
  sorry

protected instance has_neg {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_group M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : Neg (multilinear_map R M₁ M₂) :=
  { neg :=
      fun (f : multilinear_map R M₁ M₂) =>
        mk (fun (m : (i : ι) → M₁ i) => -coe_fn f m) sorry sorry }

@[simp] theorem neg_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_group M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) : coe_fn (-f) m = -coe_fn f m :=
  rfl

protected instance has_sub {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_group M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : Sub (multilinear_map R M₁ M₂) :=
  { sub :=
      fun (f g : multilinear_map R M₁ M₂) =>
        mk (fun (m : (i : ι) → M₁ i) => coe_fn f m - coe_fn g m) sorry sorry }

@[simp] theorem sub_apply {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_group M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (g : multilinear_map R M₁ M₂) (m : (i : ι) → M₁ i) :
    coe_fn (f - g) m = coe_fn f m - coe_fn g m :=
  rfl

protected instance add_comm_group {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂}
    [DecidableEq ι] [semiring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_group M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] : add_comm_group (multilinear_map R M₁ M₂) :=
  add_comm_group.mk Add.add sorry 0 sorry sorry Neg.neg Sub.sub sorry sorry

@[simp] theorem map_neg {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_group (M₁ i)] [add_comm_group M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) (i : ι) (x : M₁ i) :
    coe_fn f (function.update m i (-x)) = -coe_fn f (function.update m i x) :=
  sorry

@[simp] theorem map_sub {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [semiring R] [(i : ι) → add_comm_group (M₁ i)] [add_comm_group M₂]
    [(i : ι) → semimodule R (M₁ i)] [semimodule R M₂] (f : multilinear_map R M₁ M₂)
    (m : (i : ι) → M₁ i) (i : ι) (x : M₁ i) (y : M₁ i) :
    coe_fn f (function.update m i (x - y)) =
        coe_fn f (function.update m i x) - coe_fn f (function.update m i y) :=
  sorry

/-- When `ι` is finite, multilinear maps on `R^ι` with values in `M₂` are in bijection with `M₂`,
as such a multilinear map is completely determined by its value on the constant vector made of ones.
We register this bijection as a linear equivalence in `multilinear_map.pi_ring_equiv`. -/
protected def pi_ring_equiv {R : Type u} {ι : Type u'} {M₂ : Type v₂} [DecidableEq ι]
    [comm_semiring R] [add_comm_group M₂] [semimodule R M₂] [fintype ι] :
    linear_equiv R M₂ (multilinear_map R (fun (i : ι) => R) M₂) :=
  linear_equiv.mk (fun (z : M₂) => multilinear_map.mk_pi_ring R ι z) sorry sorry
    (fun (f : multilinear_map R (fun (i : ι) => R) M₂) => coe_fn f fun (i : ι) => 1) sorry sorry

end multilinear_map

/-!
### Currying

We associate to a multilinear map in `n+1` variables (i.e., based on `fin n.succ`) two
curried functions, named `f.curry_left` (which is a linear map on `E 0` taking values
in multilinear maps in `n` variables) and `f.curry_right` (wich is a multilinear map in `n`
variables taking values in linear maps on `E 0`). In both constructions, the variable that is
singled out is `0`, to take advantage of the operations `cons` and `tail` on `fin n`.
The inverse operations are called `uncurry_left` and `uncurry_right`.

We also register linear equiv versions of these correspondences, in
`multilinear_curry_left_equiv` and `multilinear_curry_right_equiv`.
-/

/-! #### Left currying -/

/-- Given a linear map `f` from `M 0` to multilinear maps on `n` variables,
construct the corresponding multilinear map on `n+1` variables obtained by concatenating
the variables, given by `m ↦ f (m 0) (tail m)`-/
def linear_map.uncurry_left {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂}
    [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂]
    (f : linear_map R (M 0) (multilinear_map R (fun (i : fin n) => M (fin.succ i)) M₂)) :
    multilinear_map R M M₂ :=
  multilinear_map.mk
    (fun (m : (i : fin (Nat.succ n)) → M i) => coe_fn (coe_fn f (m 0)) (fin.tail m)) sorry sorry

@[simp] theorem linear_map.uncurry_left_apply {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v}
    {M₂ : Type v₂} [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)]
    [add_comm_monoid M₂] [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂]
    (f : linear_map R (M 0) (multilinear_map R (fun (i : fin n) => M (fin.succ i)) M₂))
    (m : (i : fin (Nat.succ n)) → M i) :
    coe_fn (linear_map.uncurry_left f) m = coe_fn (coe_fn f (m 0)) (fin.tail m) :=
  rfl

/-- Given a multilinear map `f` in `n+1` variables, split the first variable to obtain
a linear map into multilinear maps in `n` variables, given by `x ↦ (m ↦ f (cons x m))`. -/
def multilinear_map.curry_left {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂}
    [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂) :
    linear_map R (M 0) (multilinear_map R (fun (i : fin n) => M (fin.succ i)) M₂) :=
  linear_map.mk
    (fun (x : M 0) =>
      multilinear_map.mk (fun (m : (i : fin n) → M (fin.succ i)) => coe_fn f (fin.cons x m)) sorry
        sorry)
    sorry sorry

@[simp] theorem multilinear_map.curry_left_apply {R : Type u} {n : ℕ}
    {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [comm_semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂)
    (x : M 0) (m : (i : fin n) → M (fin.succ i)) :
    coe_fn (coe_fn (multilinear_map.curry_left f) x) m = coe_fn f (fin.cons x m) :=
  rfl

@[simp] theorem linear_map.curry_uncurry_left {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v}
    {M₂ : Type v₂} [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)]
    [add_comm_monoid M₂] [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂]
    (f : linear_map R (M 0) (multilinear_map R (fun (i : fin n) => M (fin.succ i)) M₂)) :
    multilinear_map.curry_left (linear_map.uncurry_left f) = f :=
  sorry

@[simp] theorem multilinear_map.uncurry_curry_left {R : Type u} {n : ℕ}
    {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [comm_semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂) :
    linear_map.uncurry_left (multilinear_map.curry_left f) = f :=
  sorry

/-- The space of multilinear maps on `Π(i : fin (n+1)), M i` is canonically isomorphic to
the space of linear maps from `M 0` to the space of multilinear maps on
`Π(i : fin n), M i.succ `, by separating the first variable. We register this isomorphism as a
linear isomorphism in `multilinear_curry_left_equiv R M M₂`.

The direct and inverse maps are given by `f.uncurry_left` and `f.curry_left`. Use these
unless you need the full framework of linear equivs. -/
def multilinear_curry_left_equiv (R : Type u) {n : ℕ} (M : fin (Nat.succ n) → Type v) (M₂ : Type v₂)
    [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] :
    linear_equiv R (linear_map R (M 0) (multilinear_map R (fun (i : fin n) => M (fin.succ i)) M₂))
        (multilinear_map R M M₂) :=
  linear_equiv.mk linear_map.uncurry_left sorry sorry multilinear_map.curry_left
    linear_map.curry_uncurry_left sorry

/-! #### Right currying -/

/-- Given a multilinear map `f` in `n` variables to the space of linear maps from `M (last n)` to
`M₂`, construct the corresponding multilinear map on `n+1` variables obtained by concatenating
the variables, given by `m ↦ f (init m) (m (last n))`-/
def multilinear_map.uncurry_right {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v}
    {M₂ : Type v₂} [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)]
    [add_comm_monoid M₂] [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂]
    (f :
      multilinear_map R (fun (i : fin n) => M (coe_fn fin.cast_succ i))
        (linear_map R (M (fin.last n)) M₂)) :
    multilinear_map R M M₂ :=
  multilinear_map.mk
    (fun (m : (i : fin (Nat.succ n)) → M i) => coe_fn (coe_fn f (fin.init m)) (m (fin.last n)))
    sorry sorry

@[simp] theorem multilinear_map.uncurry_right_apply {R : Type u} {n : ℕ}
    {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [comm_semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂]
    (f :
      multilinear_map R (fun (i : fin n) => M (coe_fn fin.cast_succ i))
        (linear_map R (M (fin.last n)) M₂))
    (m : (i : fin (Nat.succ n)) → M i) :
    coe_fn (multilinear_map.uncurry_right f) m = coe_fn (coe_fn f (fin.init m)) (m (fin.last n)) :=
  rfl

/-- Given a multilinear map `f` in `n+1` variables, split the last variable to obtain
a multilinear map in `n` variables taking values in linear maps from `M (last n)` to `M₂`, given by
`m ↦ (x ↦ f (snoc m x))`. -/
def multilinear_map.curry_right {R : Type u} {n : ℕ} {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂}
    [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂) :
    multilinear_map R (fun (i : fin n) => M (coe_fn fin.cast_succ i))
        (linear_map R (M (fin.last n)) M₂) :=
  multilinear_map.mk
    (fun (m : (i : fin n) → M (coe_fn fin.cast_succ i)) =>
      linear_map.mk (fun (x : M (fin.last n)) => coe_fn f (fin.snoc m x)) sorry sorry)
    sorry sorry

@[simp] theorem multilinear_map.curry_right_apply {R : Type u} {n : ℕ}
    {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [comm_semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂)
    (m : (i : fin n) → M (coe_fn fin.cast_succ i)) (x : M (fin.last n)) :
    coe_fn (coe_fn (multilinear_map.curry_right f) m) x = coe_fn f (fin.snoc m x) :=
  rfl

@[simp] theorem multilinear_map.curry_uncurry_right {R : Type u} {n : ℕ}
    {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [comm_semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂]
    (f :
      multilinear_map R (fun (i : fin n) => M (coe_fn fin.cast_succ i))
        (linear_map R (M (fin.last n)) M₂)) :
    multilinear_map.curry_right (multilinear_map.uncurry_right f) = f :=
  sorry

@[simp] theorem multilinear_map.uncurry_curry_right {R : Type u} {n : ℕ}
    {M : fin (Nat.succ n) → Type v} {M₂ : Type v₂} [comm_semiring R]
    [(i : fin (Nat.succ n)) → add_comm_monoid (M i)] [add_comm_monoid M₂]
    [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] (f : multilinear_map R M M₂) :
    multilinear_map.uncurry_right (multilinear_map.curry_right f) = f :=
  sorry

/-- The space of multilinear maps on `Π(i : fin (n+1)), M i` is canonically isomorphic to
the space of linear maps from the space of multilinear maps on `Π(i : fin n), M i.cast_succ` to the
space of linear maps on `M (last n)`, by separating the last variable. We register this isomorphism
as a linear isomorphism in `multilinear_curry_right_equiv R M M₂`.

The direct and inverse maps are given by `f.uncurry_right` and `f.curry_right`. Use these
unless you need the full framework of linear equivs. -/
def multilinear_curry_right_equiv (R : Type u) {n : ℕ} (M : fin (Nat.succ n) → Type v)
    (M₂ : Type v₂) [comm_semiring R] [(i : fin (Nat.succ n)) → add_comm_monoid (M i)]
    [add_comm_monoid M₂] [(i : fin (Nat.succ n)) → semimodule R (M i)] [semimodule R M₂] :
    linear_equiv R
        (multilinear_map R (fun (i : fin n) => M (coe_fn fin.cast_succ i))
          (linear_map R (M (fin.last n)) M₂))
        (multilinear_map R M M₂) :=
  linear_equiv.mk multilinear_map.uncurry_right sorry sorry multilinear_map.curry_right
    multilinear_map.curry_uncurry_right sorry

namespace multilinear_map

/-- The pushforward of an indexed collection of submodule `p i ⊆ M₁ i` by `f : M₁ → M₂`.

Note that this is not a submodule - it is not closed under addition. -/
def map {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι] [ring R]
    [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] [(i : ι) → semimodule R (M₁ i)]
    [semimodule R M₂] [Nonempty ι] (f : multilinear_map R M₁ M₂)
    (p : (i : ι) → submodule R (M₁ i)) : sub_mul_action R M₂ :=
  sub_mul_action.mk (⇑f '' set_of fun (v : (i : ι) → M₁ i) => ∀ (i : ι), v i ∈ p i) sorry

/-- The map is always nonempty. This lemma is needed to apply `sub_mul_action.zero_mem`. -/
theorem map_nonempty {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι]
    [ring R] [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] [(i : ι) → semimodule R (M₁ i)]
    [semimodule R M₂] [Nonempty ι] (f : multilinear_map R M₁ M₂)
    (p : (i : ι) → submodule R (M₁ i)) : set.nonempty ↑(map f p) :=
  Exists.intro (coe_fn f 0)
    (Exists.intro 0 { left := fun (i : ι) => submodule.zero_mem (p i), right := rfl })

/-- The range of a multilinear map, closed under scalar multiplication. -/
def range {R : Type u} {ι : Type u'} {M₁ : ι → Type v₁} {M₂ : Type v₂} [DecidableEq ι] [ring R]
    [(i : ι) → add_comm_monoid (M₁ i)] [add_comm_monoid M₂] [(i : ι) → semimodule R (M₁ i)]
    [semimodule R M₂] [Nonempty ι] (f : multilinear_map R M₁ M₂) : sub_mul_action R M₂ :=
  map f fun (i : ι) => ⊤

end Mathlib