/-
Copyright (c) 2021 Heather Macbeth. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Heather Macbeth

! This file was ported from Lean 3 source module analysis.normed_space.affine_isometry
! leanprover-community/mathlib commit 78261225eb5cedc61c5c74ecb44e5b385d13b733
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.NormedSpace.LinearIsometry
import Mathbin.Analysis.Normed.Group.AddTorsor
import Mathbin.Analysis.NormedSpace.Basic
import Mathbin.LinearAlgebra.AffineSpace.Restrict
import Mathbin.LinearAlgebra.AffineSpace.MidpointZero

/-!
# Affine isometries

In this file we define `affine_isometry 𝕜 P P₂` to be an affine isometric embedding of normed
add-torsors `P` into `P₂` over normed `𝕜`-spaces and `affine_isometry_equiv` to be an affine
isometric equivalence between `P` and `P₂`.

We also prove basic lemmas and provide convenience constructors.  The choice of these lemmas and
constructors is closely modelled on those for the `linear_isometry` and `affine_map` theories.

Since many elementary properties don't require `‖x‖ = 0 → x = 0` we initially set up the theory for
`seminormed_add_comm_group` and specialize to `normed_add_comm_group` only when needed.

## Notation

We introduce the notation `P →ᵃⁱ[𝕜] P₂` for `affine_isometry 𝕜 P P₂`, and `P ≃ᵃⁱ[𝕜] P₂` for
`affine_isometry_equiv 𝕜 P P₂`.  In contrast with the notation `→ₗᵢ` for linear isometries, `≃ᵢ`
for isometric equivalences, etc., the "i" here is a superscript.  This is for aesthetic reasons to
match the superscript "a" (note that in mathlib `→ᵃ` is an affine map, since `→ₐ` has been taken by
algebra-homomorphisms.)

-/

open Function Set

variable (𝕜 : Type _) {V V₁ V₂ V₃ V₄ : Type _} {P₁ : Type _} (P P₂ : Type _) {P₃ P₄ : Type _}
  [NormedField 𝕜] [SeminormedAddCommGroup V] [SeminormedAddCommGroup V₁] [SeminormedAddCommGroup V₂]
  [SeminormedAddCommGroup V₃] [SeminormedAddCommGroup V₄] [NormedSpace 𝕜 V] [NormedSpace 𝕜 V₁]
  [NormedSpace 𝕜 V₂] [NormedSpace 𝕜 V₃] [NormedSpace 𝕜 V₄] [PseudoMetricSpace P] [MetricSpace P₁]
  [PseudoMetricSpace P₂] [PseudoMetricSpace P₃] [PseudoMetricSpace P₄] [NormedAddTorsor V P]
  [NormedAddTorsor V₁ P₁] [NormedAddTorsor V₂ P₂] [NormedAddTorsor V₃ P₃] [NormedAddTorsor V₄ P₄]

include V V₂

/-- An `𝕜`-affine isometric embedding of one normed add-torsor over a normed `𝕜`-space into
another. -/
structure AffineIsometry extends P →ᵃ[𝕜] P₂ where
  norm_map : ∀ x : V, ‖linear x‖ = ‖x‖
#align affine_isometry AffineIsometry

omit V V₂

variable {𝕜 P P₂}

-- mathport name: «expr →ᵃⁱ[ ] »
notation:25 -- `→ᵃᵢ` would be more consistent with the linear isometry notation, but it is uglier
P " →ᵃⁱ[" 𝕜:25 "] " P₂:0 => AffineIsometry 𝕜 P P₂

namespace AffineIsometry

variable (f : P →ᵃⁱ[𝕜] P₂)

/-- The underlying linear map of an affine isometry is in fact a linear isometry. -/
protected def linearIsometry : V →ₗᵢ[𝕜] V₂ :=
  { f.linear with norm_map' := f.norm_map }
#align affine_isometry.linear_isometry AffineIsometry.linearIsometry

@[simp]
theorem linear_eq_linearIsometry : f.linear = f.LinearIsometry.toLinearMap :=
  by
  ext
  rfl
#align affine_isometry.linear_eq_linear_isometry AffineIsometry.linear_eq_linearIsometry

include V V₂

instance : CoeFun (P →ᵃⁱ[𝕜] P₂) fun _ => P → P₂ :=
  ⟨fun f => f.toFun⟩

omit V V₂

@[simp]
theorem coe_toAffineMap : ⇑f.toAffineMap = f :=
  rfl
#align affine_isometry.coe_to_affine_map AffineIsometry.coe_toAffineMap

include V V₂

theorem toAffineMap_injective : Injective (toAffineMap : (P →ᵃⁱ[𝕜] P₂) → P →ᵃ[𝕜] P₂)
  | ⟨f, _⟩, ⟨g, _⟩, rfl => rfl
#align affine_isometry.to_affine_map_injective AffineIsometry.toAffineMap_injective

theorem coeFn_injective : @Injective (P →ᵃⁱ[𝕜] P₂) (P → P₂) coeFn :=
  AffineMap.coeFn_injective.comp toAffineMap_injective
#align affine_isometry.coe_fn_injective AffineIsometry.coeFn_injective

@[ext]
theorem ext {f g : P →ᵃⁱ[𝕜] P₂} (h : ∀ x, f x = g x) : f = g :=
  coeFn_injective <| funext h
#align affine_isometry.ext AffineIsometry.ext

omit V V₂

end AffineIsometry

namespace LinearIsometry

variable (f : V →ₗᵢ[𝕜] V₂)

/-- Reinterpret a linear isometry as an affine isometry. -/
def toAffineIsometry : V →ᵃⁱ[𝕜] V₂ :=
  { f.toLinearMap.toAffineMap with norm_map := f.norm_map }
#align linear_isometry.to_affine_isometry LinearIsometry.toAffineIsometry

@[simp]
theorem coe_toAffineIsometry : ⇑(f.toAffineIsometry : V →ᵃⁱ[𝕜] V₂) = f :=
  rfl
#align linear_isometry.coe_to_affine_isometry LinearIsometry.coe_toAffineIsometry

@[simp]
theorem toAffineIsometry_linearIsometry : f.toAffineIsometry.LinearIsometry = f :=
  by
  ext
  rfl
#align linear_isometry.to_affine_isometry_linear_isometry LinearIsometry.toAffineIsometry_linearIsometry

-- somewhat arbitrary choice of simp direction
@[simp]
theorem toAffineIsometry_toAffineMap : f.toAffineIsometry.toAffineMap = f.toLinearMap.toAffineMap :=
  rfl
#align linear_isometry.to_affine_isometry_to_affine_map LinearIsometry.toAffineIsometry_toAffineMap

end LinearIsometry

namespace AffineIsometry

variable (f : P →ᵃⁱ[𝕜] P₂) (f₁ : P₁ →ᵃⁱ[𝕜] P₂)

@[simp]
theorem map_vadd (p : P) (v : V) : f (v +ᵥ p) = f.LinearIsometry v +ᵥ f p :=
  f.toAffineMap.map_vadd p v
#align affine_isometry.map_vadd AffineIsometry.map_vadd

@[simp]
theorem map_vsub (p1 p2 : P) : f.LinearIsometry (p1 -ᵥ p2) = f p1 -ᵥ f p2 :=
  f.toAffineMap.linearMap_vsub p1 p2
#align affine_isometry.map_vsub AffineIsometry.map_vsub

@[simp]
theorem dist_map (x y : P) : dist (f x) (f y) = dist x y := by
  rw [dist_eq_norm_vsub V₂, dist_eq_norm_vsub V, ← map_vsub, f.linear_isometry.norm_map]
#align affine_isometry.dist_map AffineIsometry.dist_map

@[simp]
theorem nndist_map (x y : P) : nndist (f x) (f y) = nndist x y := by simp [nndist_dist]
#align affine_isometry.nndist_map AffineIsometry.nndist_map

@[simp]
theorem edist_map (x y : P) : edist (f x) (f y) = edist x y := by simp [edist_dist]
#align affine_isometry.edist_map AffineIsometry.edist_map

protected theorem isometry : Isometry f :=
  f.edist_map
#align affine_isometry.isometry AffineIsometry.isometry

protected theorem injective : Injective f₁ :=
  f₁.Isometry.Injective
#align affine_isometry.injective AffineIsometry.injective

@[simp]
theorem map_eq_iff {x y : P₁} : f₁ x = f₁ y ↔ x = y :=
  f₁.Injective.eq_iff
#align affine_isometry.map_eq_iff AffineIsometry.map_eq_iff

theorem map_ne {x y : P₁} (h : x ≠ y) : f₁ x ≠ f₁ y :=
  f₁.Injective.Ne h
#align affine_isometry.map_ne AffineIsometry.map_ne

protected theorem lipschitz : LipschitzWith 1 f :=
  f.Isometry.lipschitz
#align affine_isometry.lipschitz AffineIsometry.lipschitz

protected theorem antilipschitz : AntilipschitzWith 1 f :=
  f.Isometry.antilipschitz
#align affine_isometry.antilipschitz AffineIsometry.antilipschitz

@[continuity]
protected theorem continuous : Continuous f :=
  f.Isometry.Continuous
#align affine_isometry.continuous AffineIsometry.continuous

theorem ediam_image (s : Set P) : EMetric.diam (f '' s) = EMetric.diam s :=
  f.Isometry.ediam_image s
#align affine_isometry.ediam_image AffineIsometry.ediam_image

theorem ediam_range : EMetric.diam (range f) = EMetric.diam (univ : Set P) :=
  f.Isometry.ediam_range
#align affine_isometry.ediam_range AffineIsometry.ediam_range

theorem diam_image (s : Set P) : Metric.diam (f '' s) = Metric.diam s :=
  f.Isometry.diam_image s
#align affine_isometry.diam_image AffineIsometry.diam_image

theorem diam_range : Metric.diam (range f) = Metric.diam (univ : Set P) :=
  f.Isometry.diam_range
#align affine_isometry.diam_range AffineIsometry.diam_range

@[simp]
theorem comp_continuous_iff {α : Type _} [TopologicalSpace α] {g : α → P} :
    Continuous (f ∘ g) ↔ Continuous g :=
  f.Isometry.comp_continuous_iff
#align affine_isometry.comp_continuous_iff AffineIsometry.comp_continuous_iff

include V

/-- The identity affine isometry. -/
def id : P →ᵃⁱ[𝕜] P :=
  ⟨AffineMap.id 𝕜 P, fun x => rfl⟩
#align affine_isometry.id AffineIsometry.id

@[simp]
theorem coe_id : ⇑(id : P →ᵃⁱ[𝕜] P) = id :=
  rfl
#align affine_isometry.coe_id AffineIsometry.coe_id

@[simp]
theorem id_apply (x : P) : (AffineIsometry.id : P →ᵃⁱ[𝕜] P) x = x :=
  rfl
#align affine_isometry.id_apply AffineIsometry.id_apply

@[simp]
theorem id_toAffineMap : (id.toAffineMap : P →ᵃ[𝕜] P) = AffineMap.id 𝕜 P :=
  rfl
#align affine_isometry.id_to_affine_map AffineIsometry.id_toAffineMap

instance : Inhabited (P →ᵃⁱ[𝕜] P) :=
  ⟨id⟩

include V₂ V₃

/-- Composition of affine isometries. -/
def comp (g : P₂ →ᵃⁱ[𝕜] P₃) (f : P →ᵃⁱ[𝕜] P₂) : P →ᵃⁱ[𝕜] P₃ :=
  ⟨g.toAffineMap.comp f.toAffineMap, fun x => (g.norm_map _).trans (f.norm_map _)⟩
#align affine_isometry.comp AffineIsometry.comp

@[simp]
theorem coe_comp (g : P₂ →ᵃⁱ[𝕜] P₃) (f : P →ᵃⁱ[𝕜] P₂) : ⇑(g.comp f) = g ∘ f :=
  rfl
#align affine_isometry.coe_comp AffineIsometry.coe_comp

omit V V₂ V₃

@[simp]
theorem id_comp : (id : P₂ →ᵃⁱ[𝕜] P₂).comp f = f :=
  ext fun x => rfl
#align affine_isometry.id_comp AffineIsometry.id_comp

@[simp]
theorem comp_id : f.comp id = f :=
  ext fun x => rfl
#align affine_isometry.comp_id AffineIsometry.comp_id

include V V₂ V₃ V₄

theorem comp_assoc (f : P₃ →ᵃⁱ[𝕜] P₄) (g : P₂ →ᵃⁱ[𝕜] P₃) (h : P →ᵃⁱ[𝕜] P₂) :
    (f.comp g).comp h = f.comp (g.comp h) :=
  rfl
#align affine_isometry.comp_assoc AffineIsometry.comp_assoc

omit V₂ V₃ V₄

instance : Monoid (P →ᵃⁱ[𝕜] P) where
  one := id
  mul := comp
  mul_assoc := comp_assoc
  one_mul := id_comp
  mul_one := comp_id

@[simp]
theorem coe_one : ⇑(1 : P →ᵃⁱ[𝕜] P) = id :=
  rfl
#align affine_isometry.coe_one AffineIsometry.coe_one

@[simp]
theorem coe_mul (f g : P →ᵃⁱ[𝕜] P) : ⇑(f * g) = f ∘ g :=
  rfl
#align affine_isometry.coe_mul AffineIsometry.coe_mul

end AffineIsometry

namespace AffineSubspace

include V

/-- `affine_subspace.subtype` as an `affine_isometry`. -/
def subtypeₐᵢ (s : AffineSubspace 𝕜 P) [Nonempty s] : s →ᵃⁱ[𝕜] P :=
  { s.Subtype with norm_map := s.direction.subtypeₗᵢ.norm_map }
#align affine_subspace.subtypeₐᵢ AffineSubspace.subtypeₐᵢ

theorem subtypeₐᵢ_linear (s : AffineSubspace 𝕜 P) [Nonempty s] :
    s.subtypeₐᵢ.linear = s.direction.Subtype :=
  rfl
#align affine_subspace.subtypeₐᵢ_linear AffineSubspace.subtypeₐᵢ_linear

@[simp]
theorem subtypeₐᵢ_linearIsometry (s : AffineSubspace 𝕜 P) [Nonempty s] :
    s.subtypeₐᵢ.LinearIsometry = s.direction.subtypeₗᵢ :=
  rfl
#align affine_subspace.subtypeₐᵢ_linear_isometry AffineSubspace.subtypeₐᵢ_linearIsometry

@[simp]
theorem coe_subtypeₐᵢ (s : AffineSubspace 𝕜 P) [Nonempty s] : ⇑s.subtypeₐᵢ = s.Subtype :=
  rfl
#align affine_subspace.coe_subtypeₐᵢ AffineSubspace.coe_subtypeₐᵢ

@[simp]
theorem subtypeₐᵢ_toAffineMap (s : AffineSubspace 𝕜 P) [Nonempty s] :
    s.subtypeₐᵢ.toAffineMap = s.Subtype :=
  rfl
#align affine_subspace.subtypeₐᵢ_to_affine_map AffineSubspace.subtypeₐᵢ_toAffineMap

end AffineSubspace

variable (𝕜 P P₂)

include V V₂

/-- A affine isometric equivalence between two normed vector spaces. -/
structure AffineIsometryEquiv extends P ≃ᵃ[𝕜] P₂ where
  norm_map : ∀ x, ‖linear x‖ = ‖x‖
#align affine_isometry_equiv AffineIsometryEquiv

variable {𝕜 P P₂}

omit V V₂

-- mathport name: «expr ≃ᵃⁱ[ ] »
notation:25
  -- `≃ᵃᵢ` would be more consistent with the linear isometry equiv notation, but it is uglier
P " ≃ᵃⁱ[" 𝕜:25 "] " P₂:0 => AffineIsometryEquiv 𝕜 P P₂

namespace AffineIsometryEquiv

variable (e : P ≃ᵃⁱ[𝕜] P₂)

/-- The underlying linear equiv of an affine isometry equiv is in fact a linear isometry equiv. -/
protected def linearIsometryEquiv : V ≃ₗᵢ[𝕜] V₂ :=
  { e.linear with norm_map' := e.norm_map }
#align affine_isometry_equiv.linear_isometry_equiv AffineIsometryEquiv.linearIsometryEquiv

@[simp]
theorem linear_eq_linear_isometry : e.linear = e.LinearIsometryEquiv.toLinearEquiv :=
  by
  ext
  rfl
#align affine_isometry_equiv.linear_eq_linear_isometry AffineIsometryEquiv.linear_eq_linear_isometry

include V V₂

instance : CoeFun (P ≃ᵃⁱ[𝕜] P₂) fun _ => P → P₂ :=
  ⟨fun f => f.toFun⟩

@[simp]
theorem coe_mk (e : P ≃ᵃ[𝕜] P₂) (he : ∀ x, ‖e.linear x‖ = ‖x‖) : ⇑(mk e he) = e :=
  rfl
#align affine_isometry_equiv.coe_mk AffineIsometryEquiv.coe_mk

@[simp]
theorem coe_toAffineEquiv (e : P ≃ᵃⁱ[𝕜] P₂) : ⇑e.toAffineEquiv = e :=
  rfl
#align affine_isometry_equiv.coe_to_affine_equiv AffineIsometryEquiv.coe_toAffineEquiv

theorem toAffineEquiv_injective : Injective (toAffineEquiv : (P ≃ᵃⁱ[𝕜] P₂) → P ≃ᵃ[𝕜] P₂)
  | ⟨e, _⟩, ⟨_, _⟩, rfl => rfl
#align affine_isometry_equiv.to_affine_equiv_injective AffineIsometryEquiv.toAffineEquiv_injective

@[ext]
theorem ext {e e' : P ≃ᵃⁱ[𝕜] P₂} (h : ∀ x, e x = e' x) : e = e' :=
  toAffineEquiv_injective <| AffineEquiv.ext h
#align affine_isometry_equiv.ext AffineIsometryEquiv.ext

omit V V₂

/-- Reinterpret a `affine_isometry_equiv` as a `affine_isometry`. -/
def toAffineIsometry : P →ᵃⁱ[𝕜] P₂ :=
  ⟨e.1.toAffineMap, e.2⟩
#align affine_isometry_equiv.to_affine_isometry AffineIsometryEquiv.toAffineIsometry

@[simp]
theorem coe_toAffineIsometry : ⇑e.toAffineIsometry = e :=
  rfl
#align affine_isometry_equiv.coe_to_affine_isometry AffineIsometryEquiv.coe_toAffineIsometry

/-- Construct an affine isometry equivalence by verifying the relation between the map and its
linear part at one base point. Namely, this function takes a map `e : P₁ → P₂`, a linear isometry
equivalence `e' : V₁ ≃ᵢₗ[k] V₂`, and a point `p` such that for any other point `p'` we have
`e p' = e' (p' -ᵥ p) +ᵥ e p`. -/
def mk' (e : P₁ → P₂) (e' : V₁ ≃ₗᵢ[𝕜] V₂) (p : P₁) (h : ∀ p' : P₁, e p' = e' (p' -ᵥ p) +ᵥ e p) :
    P₁ ≃ᵃⁱ[𝕜] P₂ :=
  { AffineEquiv.mk' e e'.toLinearEquiv p h with norm_map := e'.norm_map }
#align affine_isometry_equiv.mk' AffineIsometryEquiv.mk'

@[simp]
theorem coe_mk' (e : P₁ → P₂) (e' : V₁ ≃ₗᵢ[𝕜] V₂) (p h) : ⇑(mk' e e' p h) = e :=
  rfl
#align affine_isometry_equiv.coe_mk' AffineIsometryEquiv.coe_mk'

@[simp]
theorem linearIsometryEquiv_mk' (e : P₁ → P₂) (e' : V₁ ≃ₗᵢ[𝕜] V₂) (p h) :
    (mk' e e' p h).LinearIsometryEquiv = e' := by
  ext
  rfl
#align affine_isometry_equiv.linear_isometry_equiv_mk' AffineIsometryEquiv.linearIsometryEquiv_mk'

end AffineIsometryEquiv

namespace LinearIsometryEquiv

variable (e : V ≃ₗᵢ[𝕜] V₂)

/-- Reinterpret a linear isometry equiv as an affine isometry equiv. -/
def toAffineIsometryEquiv : V ≃ᵃⁱ[𝕜] V₂ :=
  { e.toLinearEquiv.toAffineEquiv with norm_map := e.norm_map }
#align linear_isometry_equiv.to_affine_isometry_equiv LinearIsometryEquiv.toAffineIsometryEquiv

@[simp]
theorem coe_toAffineIsometryEquiv : ⇑(e.toAffineIsometryEquiv : V ≃ᵃⁱ[𝕜] V₂) = e :=
  rfl
#align linear_isometry_equiv.coe_to_affine_isometry_equiv LinearIsometryEquiv.coe_toAffineIsometryEquiv

@[simp]
theorem toAffineIsometryEquiv_linearIsometryEquiv :
    e.toAffineIsometryEquiv.LinearIsometryEquiv = e :=
  by
  ext
  rfl
#align linear_isometry_equiv.to_affine_isometry_equiv_linear_isometry_equiv LinearIsometryEquiv.toAffineIsometryEquiv_linearIsometryEquiv

-- somewhat arbitrary choice of simp direction
@[simp]
theorem toAffineIsometryEquiv_toAffineEquiv :
    e.toAffineIsometryEquiv.toAffineEquiv = e.toLinearEquiv.toAffineEquiv :=
  rfl
#align linear_isometry_equiv.to_affine_isometry_equiv_to_affine_equiv LinearIsometryEquiv.toAffineIsometryEquiv_toAffineEquiv

-- somewhat arbitrary choice of simp direction
@[simp]
theorem toAffineIsometryEquiv_toAffineIsometry :
    e.toAffineIsometryEquiv.toAffineIsometry = e.toLinearIsometry.toAffineIsometry :=
  rfl
#align linear_isometry_equiv.to_affine_isometry_equiv_to_affine_isometry LinearIsometryEquiv.toAffineIsometryEquiv_toAffineIsometry

end LinearIsometryEquiv

namespace AffineIsometryEquiv

variable (e : P ≃ᵃⁱ[𝕜] P₂)

protected theorem isometry : Isometry e :=
  e.toAffineIsometry.Isometry
#align affine_isometry_equiv.isometry AffineIsometryEquiv.isometry

/-- Reinterpret a `affine_isometry_equiv` as an `isometry_equiv`. -/
def toIsometryEquiv : P ≃ᵢ P₂ :=
  ⟨e.toAffineEquiv.toEquiv, e.Isometry⟩
#align affine_isometry_equiv.to_isometry_equiv AffineIsometryEquiv.toIsometryEquiv

@[simp]
theorem coe_toIsometryEquiv : ⇑e.toIsometryEquiv = e :=
  rfl
#align affine_isometry_equiv.coe_to_isometry_equiv AffineIsometryEquiv.coe_toIsometryEquiv

include V V₂

theorem range_eq_univ (e : P ≃ᵃⁱ[𝕜] P₂) : Set.range e = Set.univ :=
  by
  rw [← coe_to_isometry_equiv]
  exact IsometryEquiv.range_eq_univ _
#align affine_isometry_equiv.range_eq_univ AffineIsometryEquiv.range_eq_univ

omit V V₂

/-- Reinterpret a `affine_isometry_equiv` as an `homeomorph`. -/
def toHomeomorph : P ≃ₜ P₂ :=
  e.toIsometryEquiv.toHomeomorph
#align affine_isometry_equiv.to_homeomorph AffineIsometryEquiv.toHomeomorph

@[simp]
theorem coe_toHomeomorph : ⇑e.toHomeomorph = e :=
  rfl
#align affine_isometry_equiv.coe_to_homeomorph AffineIsometryEquiv.coe_toHomeomorph

protected theorem continuous : Continuous e :=
  e.Isometry.Continuous
#align affine_isometry_equiv.continuous AffineIsometryEquiv.continuous

protected theorem continuousAt {x} : ContinuousAt e x :=
  e.Continuous.ContinuousAt
#align affine_isometry_equiv.continuous_at AffineIsometryEquiv.continuousAt

protected theorem continuousOn {s} : ContinuousOn e s :=
  e.Continuous.ContinuousOn
#align affine_isometry_equiv.continuous_on AffineIsometryEquiv.continuousOn

protected theorem continuousWithinAt {s x} : ContinuousWithinAt e s x :=
  e.Continuous.ContinuousWithinAt
#align affine_isometry_equiv.continuous_within_at AffineIsometryEquiv.continuousWithinAt

variable (𝕜 P)

include V

/-- Identity map as a `affine_isometry_equiv`. -/
def refl : P ≃ᵃⁱ[𝕜] P :=
  ⟨AffineEquiv.refl 𝕜 P, fun x => rfl⟩
#align affine_isometry_equiv.refl AffineIsometryEquiv.refl

variable {𝕜 P}

instance : Inhabited (P ≃ᵃⁱ[𝕜] P) :=
  ⟨refl 𝕜 P⟩

@[simp]
theorem coe_refl : ⇑(refl 𝕜 P) = id :=
  rfl
#align affine_isometry_equiv.coe_refl AffineIsometryEquiv.coe_refl

@[simp]
theorem toAffineEquiv_refl : (refl 𝕜 P).toAffineEquiv = AffineEquiv.refl 𝕜 P :=
  rfl
#align affine_isometry_equiv.to_affine_equiv_refl AffineIsometryEquiv.toAffineEquiv_refl

@[simp]
theorem toIsometryEquiv_refl : (refl 𝕜 P).toIsometryEquiv = IsometryEquiv.refl P :=
  rfl
#align affine_isometry_equiv.to_isometry_equiv_refl AffineIsometryEquiv.toIsometryEquiv_refl

@[simp]
theorem toHomeomorph_refl : (refl 𝕜 P).toHomeomorph = Homeomorph.refl P :=
  rfl
#align affine_isometry_equiv.to_homeomorph_refl AffineIsometryEquiv.toHomeomorph_refl

omit V

/-- The inverse `affine_isometry_equiv`. -/
def symm : P₂ ≃ᵃⁱ[𝕜] P :=
  { e.toAffineEquiv.symm with norm_map := e.LinearIsometryEquiv.symm.norm_map }
#align affine_isometry_equiv.symm AffineIsometryEquiv.symm

@[simp]
theorem apply_symm_apply (x : P₂) : e (e.symm x) = x :=
  e.toAffineEquiv.apply_symm_apply x
#align affine_isometry_equiv.apply_symm_apply AffineIsometryEquiv.apply_symm_apply

@[simp]
theorem symm_apply_apply (x : P) : e.symm (e x) = x :=
  e.toAffineEquiv.symm_apply_apply x
#align affine_isometry_equiv.symm_apply_apply AffineIsometryEquiv.symm_apply_apply

@[simp]
theorem symm_symm : e.symm.symm = e :=
  ext fun x => rfl
#align affine_isometry_equiv.symm_symm AffineIsometryEquiv.symm_symm

@[simp]
theorem toAffineEquiv_symm : e.toAffineEquiv.symm = e.symm.toAffineEquiv :=
  rfl
#align affine_isometry_equiv.to_affine_equiv_symm AffineIsometryEquiv.toAffineEquiv_symm

@[simp]
theorem toIsometryEquiv_symm : e.toIsometryEquiv.symm = e.symm.toIsometryEquiv :=
  rfl
#align affine_isometry_equiv.to_isometry_equiv_symm AffineIsometryEquiv.toIsometryEquiv_symm

@[simp]
theorem toHomeomorph_symm : e.toHomeomorph.symm = e.symm.toHomeomorph :=
  rfl
#align affine_isometry_equiv.to_homeomorph_symm AffineIsometryEquiv.toHomeomorph_symm

include V₃

/-- Composition of `affine_isometry_equiv`s as a `affine_isometry_equiv`. -/
def trans (e' : P₂ ≃ᵃⁱ[𝕜] P₃) : P ≃ᵃⁱ[𝕜] P₃ :=
  ⟨e.toAffineEquiv.trans e'.toAffineEquiv, fun x => (e'.norm_map _).trans (e.norm_map _)⟩
#align affine_isometry_equiv.trans AffineIsometryEquiv.trans

include V V₂

@[simp]
theorem coe_trans (e₁ : P ≃ᵃⁱ[𝕜] P₂) (e₂ : P₂ ≃ᵃⁱ[𝕜] P₃) : ⇑(e₁.trans e₂) = e₂ ∘ e₁ :=
  rfl
#align affine_isometry_equiv.coe_trans AffineIsometryEquiv.coe_trans

omit V V₂ V₃

@[simp]
theorem trans_refl : e.trans (refl 𝕜 P₂) = e :=
  ext fun x => rfl
#align affine_isometry_equiv.trans_refl AffineIsometryEquiv.trans_refl

@[simp]
theorem refl_trans : (refl 𝕜 P).trans e = e :=
  ext fun x => rfl
#align affine_isometry_equiv.refl_trans AffineIsometryEquiv.refl_trans

@[simp]
theorem self_trans_symm : e.trans e.symm = refl 𝕜 P :=
  ext e.symm_apply_apply
#align affine_isometry_equiv.self_trans_symm AffineIsometryEquiv.self_trans_symm

@[simp]
theorem symm_trans_self : e.symm.trans e = refl 𝕜 P₂ :=
  ext e.apply_symm_apply
#align affine_isometry_equiv.symm_trans_self AffineIsometryEquiv.symm_trans_self

include V V₂ V₃

@[simp]
theorem coe_symm_trans (e₁ : P ≃ᵃⁱ[𝕜] P₂) (e₂ : P₂ ≃ᵃⁱ[𝕜] P₃) :
    ⇑(e₁.trans e₂).symm = e₁.symm ∘ e₂.symm :=
  rfl
#align affine_isometry_equiv.coe_symm_trans AffineIsometryEquiv.coe_symm_trans

include V₄

theorem trans_assoc (ePP₂ : P ≃ᵃⁱ[𝕜] P₂) (eP₂G : P₂ ≃ᵃⁱ[𝕜] P₃) (eGG' : P₃ ≃ᵃⁱ[𝕜] P₄) :
    ePP₂.trans (eP₂G.trans eGG') = (ePP₂.trans eP₂G).trans eGG' :=
  rfl
#align affine_isometry_equiv.trans_assoc AffineIsometryEquiv.trans_assoc

omit V₂ V₃ V₄

/-- The group of affine isometries of a `normed_add_torsor`, `P`. -/
instance : Group (P ≃ᵃⁱ[𝕜] P) where
  mul e₁ e₂ := e₂.trans e₁
  one := refl _ _
  inv := symm
  one_mul := trans_refl
  mul_one := refl_trans
  mul_assoc _ _ _ := trans_assoc _ _ _
  mul_left_inv := self_trans_symm

@[simp]
theorem coe_one : ⇑(1 : P ≃ᵃⁱ[𝕜] P) = id :=
  rfl
#align affine_isometry_equiv.coe_one AffineIsometryEquiv.coe_one

@[simp]
theorem coe_mul (e e' : P ≃ᵃⁱ[𝕜] P) : ⇑(e * e') = e ∘ e' :=
  rfl
#align affine_isometry_equiv.coe_mul AffineIsometryEquiv.coe_mul

@[simp]
theorem coe_inv (e : P ≃ᵃⁱ[𝕜] P) : ⇑e⁻¹ = e.symm :=
  rfl
#align affine_isometry_equiv.coe_inv AffineIsometryEquiv.coe_inv

omit V

@[simp]
theorem map_vadd (p : P) (v : V) : e (v +ᵥ p) = e.LinearIsometryEquiv v +ᵥ e p :=
  e.toAffineIsometry.map_vadd p v
#align affine_isometry_equiv.map_vadd AffineIsometryEquiv.map_vadd

@[simp]
theorem map_vsub (p1 p2 : P) : e.LinearIsometryEquiv (p1 -ᵥ p2) = e p1 -ᵥ e p2 :=
  e.toAffineIsometry.map_vsub p1 p2
#align affine_isometry_equiv.map_vsub AffineIsometryEquiv.map_vsub

@[simp]
theorem dist_map (x y : P) : dist (e x) (e y) = dist x y :=
  e.toAffineIsometry.dist_map x y
#align affine_isometry_equiv.dist_map AffineIsometryEquiv.dist_map

@[simp]
theorem edist_map (x y : P) : edist (e x) (e y) = edist x y :=
  e.toAffineIsometry.edist_map x y
#align affine_isometry_equiv.edist_map AffineIsometryEquiv.edist_map

protected theorem bijective : Bijective e :=
  e.1.Bijective
#align affine_isometry_equiv.bijective AffineIsometryEquiv.bijective

protected theorem injective : Injective e :=
  e.1.Injective
#align affine_isometry_equiv.injective AffineIsometryEquiv.injective

protected theorem surjective : Surjective e :=
  e.1.Surjective
#align affine_isometry_equiv.surjective AffineIsometryEquiv.surjective

@[simp]
theorem map_eq_iff {x y : P} : e x = e y ↔ x = y :=
  e.Injective.eq_iff
#align affine_isometry_equiv.map_eq_iff AffineIsometryEquiv.map_eq_iff

theorem map_ne {x y : P} (h : x ≠ y) : e x ≠ e y :=
  e.Injective.Ne h
#align affine_isometry_equiv.map_ne AffineIsometryEquiv.map_ne

protected theorem lipschitz : LipschitzWith 1 e :=
  e.Isometry.lipschitz
#align affine_isometry_equiv.lipschitz AffineIsometryEquiv.lipschitz

protected theorem antilipschitz : AntilipschitzWith 1 e :=
  e.Isometry.antilipschitz
#align affine_isometry_equiv.antilipschitz AffineIsometryEquiv.antilipschitz

@[simp]
theorem ediam_image (s : Set P) : EMetric.diam (e '' s) = EMetric.diam s :=
  e.Isometry.ediam_image s
#align affine_isometry_equiv.ediam_image AffineIsometryEquiv.ediam_image

@[simp]
theorem diam_image (s : Set P) : Metric.diam (e '' s) = Metric.diam s :=
  e.Isometry.diam_image s
#align affine_isometry_equiv.diam_image AffineIsometryEquiv.diam_image

variable {α : Type _} [TopologicalSpace α]

@[simp]
theorem comp_continuousOn_iff {f : α → P} {s : Set α} : ContinuousOn (e ∘ f) s ↔ ContinuousOn f s :=
  e.Isometry.comp_continuousOn_iff
#align affine_isometry_equiv.comp_continuous_on_iff AffineIsometryEquiv.comp_continuousOn_iff

@[simp]
theorem comp_continuous_iff {f : α → P} : Continuous (e ∘ f) ↔ Continuous f :=
  e.Isometry.comp_continuous_iff
#align affine_isometry_equiv.comp_continuous_iff AffineIsometryEquiv.comp_continuous_iff

section Constructions

variable (𝕜)

/-- The map `v ↦ v +ᵥ p` as an affine isometric equivalence between `V` and `P`. -/
def vaddConst (p : P) : V ≃ᵃⁱ[𝕜] P :=
  { AffineEquiv.vaddConst 𝕜 p with norm_map := fun x => rfl }
#align affine_isometry_equiv.vadd_const AffineIsometryEquiv.vaddConst

variable {𝕜}

include V

@[simp]
theorem coe_vaddConst (p : P) : ⇑(vaddConst 𝕜 p) = fun v => v +ᵥ p :=
  rfl
#align affine_isometry_equiv.coe_vadd_const AffineIsometryEquiv.coe_vaddConst

@[simp]
theorem coe_vaddConst_symm (p : P) : ⇑(vaddConst 𝕜 p).symm = fun p' => p' -ᵥ p :=
  rfl
#align affine_isometry_equiv.coe_vadd_const_symm AffineIsometryEquiv.coe_vaddConst_symm

@[simp]
theorem vaddConst_toAffineEquiv (p : P) :
    (vaddConst 𝕜 p).toAffineEquiv = AffineEquiv.vaddConst 𝕜 p :=
  rfl
#align affine_isometry_equiv.vadd_const_to_affine_equiv AffineIsometryEquiv.vaddConst_toAffineEquiv

omit V

variable (𝕜)

/-- `p' ↦ p -ᵥ p'` as an affine isometric equivalence. -/
def constVsub (p : P) : P ≃ᵃⁱ[𝕜] V :=
  { AffineEquiv.constVSub 𝕜 p with norm_map := norm_neg }
#align affine_isometry_equiv.const_vsub AffineIsometryEquiv.constVsub

variable {𝕜}

include V

@[simp]
theorem coe_constVsub (p : P) : ⇑(constVsub 𝕜 p) = (· -ᵥ ·) p :=
  rfl
#align affine_isometry_equiv.coe_const_vsub AffineIsometryEquiv.coe_constVsub

@[simp]
theorem symm_constVsub (p : P) :
    (constVsub 𝕜 p).symm =
      (LinearIsometryEquiv.neg 𝕜).toAffineIsometryEquiv.trans (vaddConst 𝕜 p) :=
  by
  ext
  rfl
#align affine_isometry_equiv.symm_const_vsub AffineIsometryEquiv.symm_constVsub

omit V

variable (𝕜 P)

/-- Translation by `v` (that is, the map `p ↦ v +ᵥ p`) as an affine isometric automorphism of `P`.
-/
def constVadd (v : V) : P ≃ᵃⁱ[𝕜] P :=
  { AffineEquiv.constVAdd 𝕜 P v with norm_map := fun x => rfl }
#align affine_isometry_equiv.const_vadd AffineIsometryEquiv.constVadd

variable {𝕜 P}

@[simp]
theorem coe_constVadd (v : V) : ⇑(constVadd 𝕜 P v : P ≃ᵃⁱ[𝕜] P) = (· +ᵥ ·) v :=
  rfl
#align affine_isometry_equiv.coe_const_vadd AffineIsometryEquiv.coe_constVadd

@[simp]
theorem constVadd_zero : constVadd 𝕜 P (0 : V) = refl 𝕜 P :=
  ext <| zero_vadd V
#align affine_isometry_equiv.const_vadd_zero AffineIsometryEquiv.constVadd_zero

include 𝕜 V

/-- The map `g` from `V` to `V₂` corresponding to a map `f` from `P` to `P₂`, at a base point `p`,
is an isometry if `f` is one. -/
theorem vadd_vsub {f : P → P₂} (hf : Isometry f) {p : P} {g : V → V₂}
    (hg : ∀ v, g v = f (v +ᵥ p) -ᵥ f p) : Isometry g :=
  by
  convert(vadd_const 𝕜 (f p)).symm.Isometry.comp (hf.comp (vadd_const 𝕜 p).Isometry)
  exact funext hg
#align affine_isometry_equiv.vadd_vsub AffineIsometryEquiv.vadd_vsub

omit 𝕜

variable (𝕜)

/-- Point reflection in `x` as an affine isometric automorphism. -/
def pointReflection (x : P) : P ≃ᵃⁱ[𝕜] P :=
  (constVsub 𝕜 x).trans (vaddConst 𝕜 x)
#align affine_isometry_equiv.point_reflection AffineIsometryEquiv.pointReflection

variable {𝕜}

theorem pointReflection_apply (x y : P) : (pointReflection 𝕜 x) y = x -ᵥ y +ᵥ x :=
  rfl
#align affine_isometry_equiv.point_reflection_apply AffineIsometryEquiv.pointReflection_apply

@[simp]
theorem pointReflection_toAffineEquiv (x : P) :
    (pointReflection 𝕜 x).toAffineEquiv = AffineEquiv.pointReflection 𝕜 x :=
  rfl
#align affine_isometry_equiv.point_reflection_to_affine_equiv AffineIsometryEquiv.pointReflection_toAffineEquiv

@[simp]
theorem pointReflection_self (x : P) : pointReflection 𝕜 x x = x :=
  AffineEquiv.pointReflection_self 𝕜 x
#align affine_isometry_equiv.point_reflection_self AffineIsometryEquiv.pointReflection_self

theorem pointReflection_involutive (x : P) : Function.Involutive (pointReflection 𝕜 x) :=
  Equiv.pointReflection_involutive x
#align affine_isometry_equiv.point_reflection_involutive AffineIsometryEquiv.pointReflection_involutive

@[simp]
theorem pointReflection_symm (x : P) : (pointReflection 𝕜 x).symm = pointReflection 𝕜 x :=
  toAffineEquiv_injective <| AffineEquiv.pointReflection_symm 𝕜 x
#align affine_isometry_equiv.point_reflection_symm AffineIsometryEquiv.pointReflection_symm

@[simp]
theorem dist_pointReflection_fixed (x y : P) : dist (pointReflection 𝕜 x y) x = dist y x := by
  rw [← (point_reflection 𝕜 x).dist_map y x, point_reflection_self]
#align affine_isometry_equiv.dist_point_reflection_fixed AffineIsometryEquiv.dist_pointReflection_fixed

theorem dist_pointReflection_self' (x y : P) : dist (pointReflection 𝕜 x y) y = ‖bit0 (x -ᵥ y)‖ :=
  by rw [point_reflection_apply, dist_eq_norm_vsub V, vadd_vsub_assoc, bit0]
#align affine_isometry_equiv.dist_point_reflection_self' AffineIsometryEquiv.dist_pointReflection_self'

theorem dist_pointReflection_self (x y : P) :
    dist (pointReflection 𝕜 x y) y = ‖(2 : 𝕜)‖ * dist x y := by
  rw [dist_point_reflection_self', ← two_smul' 𝕜 (x -ᵥ y), norm_smul, ← dist_eq_norm_vsub V]
#align affine_isometry_equiv.dist_point_reflection_self AffineIsometryEquiv.dist_pointReflection_self

theorem pointReflection_fixed_iff [Invertible (2 : 𝕜)] {x y : P} :
    pointReflection 𝕜 x y = y ↔ y = x :=
  AffineEquiv.pointReflection_fixed_iff_of_module 𝕜
#align affine_isometry_equiv.point_reflection_fixed_iff AffineIsometryEquiv.pointReflection_fixed_iff

variable [NormedSpace ℝ V]

theorem dist_pointReflection_self_real (x y : P) : dist (pointReflection ℝ x y) y = 2 * dist x y :=
  by rw [dist_point_reflection_self, Real.norm_two]
#align affine_isometry_equiv.dist_point_reflection_self_real AffineIsometryEquiv.dist_pointReflection_self_real

@[simp]
theorem pointReflection_midpoint_left (x y : P) : pointReflection ℝ (midpoint ℝ x y) x = y :=
  AffineEquiv.pointReflection_midpoint_left x y
#align affine_isometry_equiv.point_reflection_midpoint_left AffineIsometryEquiv.pointReflection_midpoint_left

@[simp]
theorem pointReflection_midpoint_right (x y : P) : pointReflection ℝ (midpoint ℝ x y) y = x :=
  AffineEquiv.pointReflection_midpoint_right x y
#align affine_isometry_equiv.point_reflection_midpoint_right AffineIsometryEquiv.pointReflection_midpoint_right

end Constructions

end AffineIsometryEquiv

include V V₂

/-- If `f` is an affine map, then its linear part is continuous iff `f` is continuous. -/
theorem AffineMap.continuous_linear_iff {f : P →ᵃ[𝕜] P₂} : Continuous f.linear ↔ Continuous f :=
  by
  inhabit P
  have :
    (f.linear : V → V₂) =
      (AffineIsometryEquiv.vaddConst 𝕜 <| f default).toHomeomorph.symm ∘
        f ∘ (AffineIsometryEquiv.vaddConst 𝕜 default).toHomeomorph :=
    by
    ext v
    simp
  rw [this]
  simp only [Homeomorph.comp_continuous_iff, Homeomorph.comp_continuous_iff']
#align affine_map.continuous_linear_iff AffineMap.continuous_linear_iff

/-- If `f` is an affine map, then its linear part is an open map iff `f` is an open map. -/
theorem AffineMap.isOpenMap_linear_iff {f : P →ᵃ[𝕜] P₂} : IsOpenMap f.linear ↔ IsOpenMap f :=
  by
  inhabit P
  have :
    (f.linear : V → V₂) =
      (AffineIsometryEquiv.vaddConst 𝕜 <| f default).toHomeomorph.symm ∘
        f ∘ (AffineIsometryEquiv.vaddConst 𝕜 default).toHomeomorph :=
    by
    ext v
    simp
  rw [this]
  simp only [Homeomorph.comp_isOpenMap_iff, Homeomorph.comp_isOpenMap_iff']
#align affine_map.is_open_map_linear_iff AffineMap.isOpenMap_linear_iff

attribute [local instance, local nolint fails_quickly] AffineSubspace.nonempty_map

include V₁

omit V

namespace AffineSubspace

/-- An affine subspace is isomorphic to its image under an injective affine map.
This is the affine version of `submodule.equiv_map_of_injective`.
-/
@[simps]
noncomputable def equivMapOfInjective (E : AffineSubspace 𝕜 P₁) [Nonempty E] (φ : P₁ →ᵃ[𝕜] P₂)
    (hφ : Function.Injective φ) : E ≃ᵃ[𝕜] E.map φ :=
  {
    Equiv.Set.image _ (E : Set P₁)
      hφ with
    linear :=
      (E.direction.equivMapOfInjective φ.linear (φ.linear_injective_iff.mpr hφ)).trans
        (LinearEquiv.ofEq _ _ (AffineSubspace.map_direction _ _).symm)
    map_vadd' := fun p v => Subtype.ext <| φ.map_vadd p v }
#align affine_subspace.equiv_map_of_injective AffineSubspace.equivMapOfInjective

/-- Restricts an affine isometry to an affine isometry equivalence between a nonempty affine
subspace `E` and its image.

This is an isometry version of `affine_subspace.equiv_map`, having a stronger premise and a stronger
conclusion.
-/
noncomputable def isometryEquivMap (φ : P₁ →ᵃⁱ[𝕜] P₂) (E : AffineSubspace 𝕜 P₁) [Nonempty E] :
    E ≃ᵃⁱ[𝕜] E.map φ.toAffineMap :=
  ⟨E.equivMapOfInjective φ.toAffineMap φ.Injective, fun _ => φ.norm_map _⟩
#align affine_subspace.isometry_equiv_map AffineSubspace.isometryEquivMap

@[simp]
theorem isometryEquivMap.apply_symm_apply {E : AffineSubspace 𝕜 P₁} [Nonempty E] {φ : P₁ →ᵃⁱ[𝕜] P₂}
    (x : E.map φ.toAffineMap) : φ ((E.isometryEquivMap φ).symm x) = x :=
  congr_arg coe <| (E.isometryEquivMap φ).apply_symm_apply _
#align affine_subspace.isometry_equiv_map.apply_symm_apply AffineSubspace.isometryEquivMap.apply_symm_apply

@[simp]
theorem isometryEquivMap.coe_apply (φ : P₁ →ᵃⁱ[𝕜] P₂) (E : AffineSubspace 𝕜 P₁) [Nonempty E]
    (g : E) : ↑(E.isometryEquivMap φ g) = φ g :=
  rfl
#align affine_subspace.isometry_equiv_map.coe_apply AffineSubspace.isometryEquivMap.coe_apply

@[simp]
theorem isometryEquivMap.toAffineMap_eq (φ : P₁ →ᵃⁱ[𝕜] P₂) (E : AffineSubspace 𝕜 P₁) [Nonempty E] :
    (E.isometryEquivMap φ).toAffineMap = E.equivMapOfInjective φ.toAffineMap φ.Injective :=
  rfl
#align affine_subspace.isometry_equiv_map.to_affine_map_eq AffineSubspace.isometryEquivMap.toAffineMap_eq

end AffineSubspace


import Rings.Rings
import Rings.Fields
import Rings.ToMathlib.list
import Rings.ToMathlib.nat
import Rings.ToMathlib.fol
import Rings.ToMathlib.mv_polynomial
import Rings.RealizeThings
import algebra.big_operators.finprod
import data.finset.basic

namespace AxGroth

noncomputable theory

universe u

open fol
open Rings

/-- composition by coe : fin d → ℕ -/
def monom_of_bd_monom {n d : ℕ} :
  (fin n → fin d) ↪ fin n → ℕ :=
⟨
  λ f, coe ∘ f ,
  λ f g h,
  begin
    funext i,
    have h' := congr_fun h i,
    apply fin.coe_injective h',
  end
⟩

/-- converts map from fin n to same map with finite support -/
def finsupp_of_fin_dom {A : Type*} [has_zero A] {n : ℕ} :
  (fin n → A) → fin n →₀ A :=
finsupp.equiv_fun_on_fintype.inv_fun

def finsupp_of_fin_dom_emb {A : Type*} [has_zero A] {n : ℕ} :
  (fin n → A) ↪ (fin n →₀ A) :=
equiv.to_embedding finsupp.equiv_fun_on_fintype.symm

@[simp] lemma to_fun_finsupp_of_fin_dom {A : Type*} [has_zero A] {n : ℕ}
  (f : fin n → A) : (finsupp_of_fin_dom f).to_fun = f :=
begin
  simp only [finsupp_of_fin_dom],
  funext k,
  simpa,
end

@[simp] lemma finsupp_of_fin_dom_to_fun {A : Type*} [has_zero A] {n : ℕ}
  (f : fin n →₀ A) : finsupp_of_fin_dom f.to_fun = f :=
begin
  simp only [finsupp_of_fin_dom],
  apply finsupp.ext,
  intro k,
  simpa,
end

lemma to_fun_finsupp_of_fin_dom_emb {A : Type*} [has_zero A] {n : ℕ}
  (f : fin n → A) : (finsupp_of_fin_dom_emb f).to_fun = f :=
begin
  simp only [finsupp_of_fin_dom_emb],
  exact to_fun_finsupp_of_fin_dom f,
end

/-- {f : fin n → ℕ | ∀ i, f i < d.succ }-/
@[simp] def n_var_bd_monom (n d : ℕ) : finset (fin n → ℕ) :=
finset.map (@monom_of_bd_monom n d.succ)
finset.univ

-- then convert these to maps f : fin n → ℕ
/-- { f : fin n → ℕ | sum f ≤ d } -/
@[simp] def monom_deg_le_finset (n d : ℕ) : finset (fin n → ℕ) :=
finset.filter
  (λ f : fin n → ℕ, (finsupp_of_fin_dom f).support.sum f ≤ d)
  (n_var_bd_monom n d)

/-- list of all n-variable monomials of degree ≤ d -/
def monom_deg_le (n d : ℕ) : list (fin n → ℕ) :=
(monom_deg_le_finset n d).to_list

/-- counts all n-variable monomials of degree ≤ d -/
-- def monom_deg_le_length (n d : ℕ) : ℕ :=
-- list.length $ monom_deg_le n d

/-- lists all n-variable monomials of degree ≤ d as finsupp maps-/
@[simp] def monom_deg_le₀ (n d : ℕ) : list (fin n →₀ ℕ) :=
list.map (finsupp_of_fin_dom) $ monom_deg_le n d

-- no restriction on degree
/-- takes a polynomial in n variables and gives a list of its coefficients-/
@[simp] def coeffs_list_of_mv_polynomial
  {K : Type*} [comm_semiring K] {n : ℕ} (d : ℕ)
  (p : mv_polynomial (fin n) K) : list K :=
list.map (λ m, mv_polynomial.coeff m p) (monom_deg_le₀ n d)

/-- there is always a monomial of degree ≤ d,
  namely the constant polynomial 0 -/
lemma monom_deg_le_length_ne_zero (n d : ℕ) :
  (monom_deg_le n d).length ≠ 0 :=
begin
  simp only [monom_deg_le,
    finset.length_to_list, finset.card_map,
    monom_deg_le_finset],
  apply finset.card_ne_zero_of_mem _,
  {exact λ i, 0},
  {
    simp only [finset.mem_univ, finset.sum_const_zero,
      finset.mem_filter, n_var_bd_monom, and_true, finset.mem_map,
      zero_le', exists_true_left],
    exact ⟨ (λ _, 0), by simp,
    (begin
      simp only [monom_of_bd_monom],
      funext,
      refl,
    end)⟩,
  },
end

lemma zero_lt_monom_deg_le_length (n d : ℕ) :
  0 < (monom_deg_le n d).length :=
  nat.pos_of_ne_zero (monom_deg_le_length_ne_zero n d)

lemma monom_deg_le₀_nth_le {n d k : ℕ}
  (hk : k < (monom_deg_le n d).length) :
  (monom_deg_le₀ n d).nth_le k (by simp [hk]) =
  finsupp_of_fin_dom_emb ((monom_deg_le n d).nth_le k hk) :=
begin
  simp only [list.length_map, list.length,
    list.nth_le, monom_deg_le₀, list.map, finsupp_of_fin_dom_emb,
    finsupp_of_fin_dom],
  simp,
end

lemma index_of_monom_deg_le_lt_length {n d : ℕ} (f : fin n → ℕ) :
  list.index_of' f (monom_deg_le_finset n d).to_list
  < (monom_deg_le n d).length :=
list.index_of'_lt_length (zero_lt_monom_deg_le_length n d)

lemma index_of_monom_deg_le₀_lt_length {n d : ℕ} (f : fin n → ℕ) :
  list.index_of' f (monom_deg_le_finset n d).to_list
  < (monom_deg_le₀ n d).length :=
begin
  rw monom_deg_le₀,
  rw list.length_map,
  apply list.index_of'_lt_length (zero_lt_monom_deg_le_length n d),
end

/-- the bound hndc is enough in poly_indexed_by_monoms -/
lemma poly_indexed_by_monoms_aux0 (n d s c : ℕ)
  (hndc : (monom_deg_le n d).length + s ≤ c) (f : fin n → ℕ) :
  list.index_of' f (monom_deg_le n d) + s < c :=
begin
  apply nat.lt_of_lt_of_le _ hndc,
  apply nat.add_lt_add_right,
  apply list.index_of'_lt_length,
  apply nat.pos_of_ne_zero (monom_deg_le_length_ne_zero n d),
end

/-- if i ∈ fin n and n + p ≤ c then i + p < c -/
lemma fin_add_lt_of_add_le (n p c : ℕ) (hnpc : n + p ≤ c) (i : fin n) :
(i : ℕ) + p < c :=
begin
  apply nat.lt_of_lt_of_le _ hnpc,
  apply nat.add_lt_add_right i.2,
end

/-- ∑_{f ∈ monom_deg_le n d} xⱼ₊ₛ ∏ {0 ≤ i < n} xᵢ₊ₚᶠ⁽ⁱ⁾ in "context c"
 where j is the index of f in monom_deg_le n d
 the context should at least include the variables xⱼ₊ₛ -- this is hndc
 the context should at least include the variables xᵢ₊ₚ -- this is hpc -/
@[simp] def poly_indexed_by_monoms (n d s p c : ℕ)
  (hndsc : (monom_deg_le n d).length + s ≤ c)
  (hnpc : n + p ≤ c) :
  bounded_ring_term c :=
-- sum indexed by the n-variable monom of degree < d
list.sumr
(list.map
  (λ f : (fin n → ℕ),
    let
      x_js : bounded_ring_term c :=
      x_ ⟨(list.index_of' f (monom_deg_le n d) + s) ,
      poly_indexed_by_monoms_aux0 n d s c hndsc f ⟩,
      x_ip (i : fin n) : bounded_ring_term c :=
      x_ ⟨ (i : ℕ) + p , fin_add_lt_of_add_le n p c hnpc i ⟩
    in
    x_js * (n.non_comm_prod $ λ i, npow_rec (f i) (x_ip i))
    )
  (monom_deg_le n d)
)

lemma realize_poly_indexed_by_monoms
  {A : Type*} [comm_ring A] {n d s p c : ℕ}
  (hndsc : (monom_deg_le n d).length + s ≤ c)
  (hnpc : n + p ≤ c)
  {xs : dvector (struc_to_ring_struc.Structure A) c}  :
  realize_bounded_term xs
    (poly_indexed_by_monoms n d s p c hndsc hnpc) dvector.nil
  =
  list.sumr
  (list.map
    (λ f,
    (dvector.nth xs (list.index_of' f (monom_deg_le n d) + s)
      (poly_indexed_by_monoms_aux0 n d s c hndsc f))
    *
    (n.non_comm_prod $ λ i,
    ((dvector.nth xs (i + p) (fin_add_lt_of_add_le n p c hnpc i)) ^ (f i) ))
    )
  (monom_deg_le n d)
  ) :=
begin
  simp only [poly_indexed_by_monoms],
  rw realize_ring_term.sumr,
  rw ← list.comp_map,
  congr,
  funext f,
  simp only [realize_ring_term.add_zero_hom, function.comp_app],
  simp only [struc_to_ring_struc.func_map, fin.val_eq_coe, dvector.last,
    struc_to_ring_struc.binaries_map, realize_bounded_term,
    ring_signature.mul, dvector.nth],
  congr,
  rw realize_ring_term.nat_non_comm_prod,
  congr,
  funext i,
  rw realize_ring_term.pow,
  simp,
end

lemma inj_formula_aux0 {n d : ℕ} {j : fin n} :
  (monom_deg_le n d).length +
    (j * ((monom_deg_le n d).length) + n + n)
  ≤
  n * (monom_deg_le n d).length + n + n :=
begin
  let c := n * ((monom_deg_le n d).length) + n + n,
  let monom := (monom_deg_le n d).length,
  repeat {rw ← nat.add_assoc, apply nat.add_le_add_right},
  cases nat.exists_eq_succ_of_ne_zero (ne_zero_of_lt j.2) with k hk,
  have hrw : n * (monom_deg_le n d).length = (monom_deg_le n d).length + k * (monom_deg_le n d).length,
  {
    rw hk,
    rw nat.succ_mul,
    rw nat.add_comm,
  },
  rw hrw,
  apply nat.add_le_add_left,
  apply nat.mul_le_mul_of_nonneg_right,
  apply nat.le_of_lt_succ,
  rw ← hk,
  exact j.2,
end

lemma inj_formula_aux1 {n d : ℕ} :
  n + 0
  ≤
  n * (monom_deg_le n d).length + n + n :=
begin
  rw add_comm,
  apply nat.add_le_add_right,
  apply nat.zero_le,
end

lemma inj_formula_aux2 {n d : ℕ} :
  n + n
  ≤
  n * (monom_deg_le n d).length + n + n :=
begin
  rw nat.add_assoc,
  apply nat.le_add_left,
end

lemma inj_formula_aux3 {n d : ℕ} {i : fin n} :
  (i : ℕ)
  <
  n * (monom_deg_le n d).length + n + n :=
begin
  apply nat.lt_of_lt_of_le i.2,
  apply nat.le_add_left,
end

lemma inj_formula_aux4 {n d : ℕ} {i : fin n} :
  (i : ℕ) + n < n * (monom_deg_le n d).length + n + n :=
begin
  rw nat.add_assoc,
  apply nat.lt_add_left,
  apply nat.add_lt_add_right,
  exact i.2,
end

-- in the context of having n polynomials pⱼ indexed by
-- their monomial coefficients,
-- if for all xᵢ and all yᵢ, every polynomial satisfies pⱼ xᵢ = pⱼ yᵢ
-- then each xᵢ = yᵢ.
-- This says the polynomial map formed by the pⱼs is injective
/-- Injectivity of polynomial maps stated model-theoretically-/
def inj_formula (n d : ℕ) :
  bounded_ring_formula (n * (monom_deg_le n d).length) :=
  let monom := (monom_deg_le n d).length in
-- for all pairs in the domain x₋ ∈ Kⁿ and ...
bd_alls' n _
$
-- ... y₋ ∈ Kⁿ
bd_alls' n _
$
-- if at each pⱼ
(bd_big_and n
-- pⱼ xᵢ = pⱼ yᵢ
  (λ j,
    (poly_indexed_by_monoms n d (j * monom + n + n) n _ -- note n
      inj_formula_aux0 inj_formula_aux2)
    ≃
    (poly_indexed_by_monoms n d (j * monom + n + n) 0 _ -- note 0
      inj_formula_aux0 inj_formula_aux1)
  )
)
-- then
⟹
-- at each 0 ≤ i < n,
(bd_big_and n $ λ i,
-- xᵢ = yᵢ (where yᵢ is written as xᵢ₊ₙ₊₁)
  x_ ⟨ i , inj_formula_aux3 ⟩ ≃ x_ (⟨ i + n , inj_formula_aux4 ⟩)
)

-- in the context of having n polynomials pⱼ indexed by
-- their monomial coefficients,
-- for all z₋ ∈ Kⁿ, there exists x₋ ∈ Kⁿ such that each zⱼ = pⱼ x₋
-- This says the polynomial map formed by the pⱼs is surjective
/-- Surjectivity of polynomial maps stated model-theoretically-/
def surj_formula (n d : ℕ) :
  bounded_ring_formula (n * (monom_deg_le n d).length) :=
let monom := (monom_deg_le n d).length in
-- for all x₋ ∈ Kⁿ in the codomain
bd_alls' n _
$
-- there exists y₋ ∈ Kⁿ in the domain such that
bd_exs' n _
$
-- at each 0 ≤ j < n
bd_big_and n
$
-- pⱼ y₋ = xⱼ
λ j,
  poly_indexed_by_monoms n d (j * monom + n + n) 0 _
    inj_formula_aux0 inj_formula_aux1
  ≃
  x_ ⟨ j + n , inj_formula_aux4 ⟩

/-- Ax-Grothendieck stated model-theoretically -/
def Ax_Groth_formula (n d : ℕ) : sentence ring_signature :=
-- quantify over n many (n-variable polynomials) called ps;
-- i.e. the data of a polynomial map
-- by quantifying over (n * monom_of_bounded_degree) monomial coefficients
bd_alls (n * ((monom_deg_le n d).length))
-- if the polynomial function is injective then it is surjective
$ inj_formula n d ⟹ surj_formula n d

/-- the data of a polynomial map consists of n polynomials in n variables -/
@[simp] def poly_map_data (K : Type*) [comm_semiring K] (n : ℕ) : Type* :=
fin n → mv_polynomial (fin n) K

/-- a polynomial map evaluates those polynomials that it consists of -/
def poly_map {K : Type*} [comm_semiring K] {n : ℕ} :
  poly_map_data K n → (fin n → K) → (fin n → K) :=
λ ps as k, mv_polynomial.eval as (ps k)

/-- The main result: algebraically closed fields of characteristic zero
   satisfy Ax-Grothendieck formula -/
lemma realize_Ax_Groth_formula_of_char_zero {K : Type*} [field K] [is_alg_closed K]
  (h0 : char_zero K) (n d : ℕ) :
  struc_to_ring_struc.Structure K ⊨ Ax_Groth_formula n d :=
sorry

section semiring

variables {A : Type*} [comm_semiring A] {n : ℕ}

/-- takes a polynomial map
  (preferably of total deg < d) and gives list of coeffs of each polynomial -/
@[simp] def poly_map_data.coeffs_list
  {n : ℕ} (d : ℕ) (ps : poly_map_data A n) : list A :=
list.join (list.of_fn (λ i : fin n, coeffs_list_of_mv_polynomial d (ps i)))

/- Writes polynomial map into a dvector of its coefficients -/
def poly_map_data.coeffs_dvector
  {n : ℕ} (d : ℕ) (ps : poly_map_data A n) :
  dvector A (poly_map_data.coeffs_list d ps).length :=
dvector.of_list (poly_map_data.coeffs_list d ps)

/-- the number of coefficients of mv_polynomial = number of monomials -/
lemma coeffs_list_length_eq_monom_deg_le_length {d : ℕ}
  (p : mv_polynomial (fin n) A) :
  (coeffs_list_of_mv_polynomial d p).length
  =
  (monom_deg_le n d).length := by simp

/-- lemma for matching up lengths of contexts for mv_polynomials -/
lemma variable_bound_equal {n : ℕ} (d : ℕ) (ps : poly_map_data A n) :
  (n * (monom_deg_le n d).length)
  =
  (poly_map_data.coeffs_list d ps).length :=
begin
  simp only [poly_map_data.coeffs_list, list.length_join],
  rw list.sum_map_length_of_fn_const_length,
  intro i,
  apply coeffs_list_length_eq_monom_deg_le_length,
end

/-- Writes polynomial map into a dvector of its coefficients
  (with a replaced variable context) -/
def poly_map_data.coeffs_dvector' {n : ℕ} (d : ℕ)
  (ps : poly_map_data A n) :
  dvector A (n * (monom_deg_le n d).length) :=
dvector.cast (symm (variable_bound_equal d ps))
  (poly_map_data.coeffs_dvector d ps)

end semiring

-- ⇑(mv_polynomial.eval (λ (i : fin n), ys.reverse.nth ↑i _)) (ps i) =
--     realize_bounded_term (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps)))
--      (poly_indexed_by_monoms n d (↑i * (monom_deg_le_finset n d).to_list.length + n + n) 0
--          (n * (monom_deg_le n d).length + n + n)
--          _
--          _)
--       dvector.nil

-- {A : Type*} [comm_ring A] {n d s p c : ℕ}
--   (hndsc : (monom_deg_le n d).length + s ≤ c)
--   (hnpc : n + p ≤ c)
--   {xs : dvector (struc_to_ring_struc.Structure A) c}  :
--   realize_bounded_term xs
--     (poly_indexed_by_monoms hndsc hnpc) dvector.nil
--   =
--

section realize_poly_map_data_coeffs_xs_and_ys

variables {A : Type*} [comm_ring A] {n d : ℕ}
  (p : mv_polynomial (fin n) A)

/-- the support of a polynomial of degree ≤ d is contained in the set of monomials of degree ≤ d -/
lemma support_sub_monom_deg_le_finset (hdeg : p.total_degree ≤ d) :
  p.support ⊆ finset.map finsupp_of_fin_dom_emb (monom_deg_le_finset n d) :=
begin
  intros f hf,
  simp only [true_and, exists_prop, finset.mem_univ,
    finset.mem_map, monom_deg_le_finset, finset.mem_filter],
  exact ⟨ f.to_fun,
  begin
    have hf_img_lt : ∀ i : fin n, f i < d.succ,
    {
      intro i,
      rw nat.lt_succ_iff,
      apply le_trans _ hdeg,
      exact mv_polynomial.support_le_total_degree i f hf,
    },
    refine ⟨ ⟨ _ , _ ⟩ , _ ⟩ ,
    {
      simp only [n_var_bd_monom, monom_of_bd_monom, finset.mem_map,
        finset.mem_univ],
      refine ⟨ λ k, ⟨ f k, hf_img_lt k⟩, _ , _ ⟩,
      {simp},
      {funext, simpa},
    },
    {
      apply le_trans _ hdeg,
      rw finsupp_of_fin_dom_to_fun,
      apply finset.le_sup hf,
    },
    {exact finsupp_of_fin_dom_to_fun f},
  end ⟩,
end

/-- the support union its complement (in monom_deg_le_finset) is equal to the whole thing-/
lemma support_union_compl_eq_monom_deg_le_finset
  [decidable_eq (mv_polynomial (fin n) A)] (hdeg : p.total_degree ≤ d) :
  p.support ∪
    (finset.map finsupp_of_fin_dom_emb (monom_deg_le_finset n d)
      \ p.support) =
  finset.map finsupp_of_fin_dom_emb (monom_deg_le_finset n d) :=
finset.union_sdiff_of_subset $ support_sub_monom_deg_le_finset _ hdeg

lemma mv_polynomial_sum_eq_finset_map_monom_deg_le_finset_sum
  [decidable_eq (mv_polynomial (fin n) A)]
  (hdeg : p.total_degree ≤ d)
  (as : (fin n →₀ ℕ) → A)
  :
  p.support.sum (λ (f : fin n →₀ ℕ), mv_polynomial.coeff f p * as f)
  =
  (finset.map finsupp_of_fin_dom_emb (monom_deg_le_finset n d)).sum
  (λ (f : fin n →₀ ℕ), mv_polynomial.coeff f p * as f)
  :=
begin
  rw ← support_union_compl_eq_monom_deg_le_finset p hdeg,
  rw finset.sum_union,
  {
    set LHS :=
      p.support.sum (λ (f : fin n →₀ ℕ), mv_polynomial.coeff f p * as f)
      with hLHS,
    rw ← add_zero LHS,
    rw ← hLHS,
    congr,
    symmetry,
    apply finset.sum_eq_zero,
    intros f hf,
    rw finset.mem_sdiff at hf,
    cases hf with hfl hfr,
    unfold mv_polynomial.coeff,
    rw finsupp.not_mem_support_iff.1 hfr,
    exact zero_mul _,
  },
  {exact disjoint_sdiff_self_right},
end

variables (ps : poly_map_data A n)
  (hdeg : ∀ (i : fin n), (ps i).total_degree ≤ d)
  (xs ys : dvector ↥(struc_to_ring_struc.Structure A) n)

lemma realize_poly_map_data_coeffs_xs_aux_prod
  (f : fin n → ℕ) :
  n.non_comm_prod
  (λ (i : fin n),
    (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps))).nth
    (i + n) inj_formula_aux4 ^ f i) =
  finset.univ.prod (λ (i : fin n), xs.nth i i.2 ^ f i) :=
begin
  rw nat.non_comm_prod_eq_prod,
  congr,
  funext i,
  rw dvector.nth_append_big
    (nat.le_add_left n _) inj_formula_aux4,
  simp only [nat.add_sub_cancel i n],
  rw dvector.nth_append_small,
end

lemma realize_poly_map_data_coeffs_ys_aux_prod
  (f : fin n → ℕ) :
  n.non_comm_prod
  (λ (i : fin n),
    (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps))).nth
    (i + 0) inj_formula_aux3 ^ f i) =
  finset.univ.prod (λ (i : fin n), ys.nth i i.2 ^ f i) :=
begin
  rw nat.non_comm_prod_eq_prod,
  congr,
  funext i,
  simp only [add_zero],
  rw ← @dvector.nth_append_small _ _ _ ys,
end

lemma poly_map_data.coeffs_dvector'_nth_aux0
  (j : fin n) (f : fin n → ℕ) :
  list.index_of' f (monom_deg_le_finset n d).to_list
    + ↑j * (monom_deg_le_finset n d).to_list.length
  <
    n * (monom_deg_le n d).length :=
begin
  have h1 : (1 + ↑j) * (monom_deg_le n d).length ≤ n * (monom_deg_le n d).length,
  {
    rw [mul_le_mul_right (zero_lt_monom_deg_le_length _ _),
      nat.one_add, nat.succ_le_iff],
    exact j.2,
  },
  apply lt_of_lt_of_le _ h1,
  rw add_mul,
  apply add_lt_add_right,
  rw one_mul,
  apply list.index_of'_lt_length (zero_lt_monom_deg_le_length _ _),
end

lemma poly_map_data.coeffs_dvector'_nth_aux1
  {ps : poly_map_data A n} :
  ∀ (i : fin n),
  ((λ (i : fin n), coeffs_list_of_mv_polynomial d (ps i)) i).length
  = (monom_deg_le n d).length :=
λ i, coeffs_list_length_eq_monom_deg_le_length (ps i)

lemma poly_map_data.coeffs_dvector'_nth_aux2
  (j : fin n) (f : fin n → ℕ)
  (hf : f ∈ monom_deg_le n d) :
  finsupp_of_fin_dom_emb.to_fun f
  =
  (monom_deg_le₀ n d).nth_le
    (list.index_of' f (monom_deg_le_finset n d).to_list)
    (index_of_monom_deg_le₀_lt_length f) :=
begin
  rw monom_deg_le₀_nth_le (index_of_monom_deg_le_lt_length f),
  apply congr_arg,
  simp only,
  delta monom_deg_le,
  have h := @list.index_of'_nth_le _ _
    f (monom_deg_le_finset n d).to_list hf,
  funext i,
  rw ← congr_fun h i,
  congr,
end

lemma poly_map_data.coeffs_dvector'_nth
  {A : Type*} [comm_ring A] {n d : ℕ}
  {ps : poly_map_data A n} (j : fin n) (f : fin n → ℕ)
  (hf : f ∈ monom_deg_le_finset n d) :
  (ps j).to_fun (finsupp_of_fin_dom_emb f) =
    (poly_map_data.coeffs_dvector' d ps).nth
      (list.index_of' f (monom_deg_le_finset n d).to_list +
        ↑j * (monom_deg_le n d).length)
      (poly_map_data.coeffs_dvector'_nth_aux0 j f) :=
begin
  rw [poly_map_data.coeffs_dvector', dvector.nth_cast,
    poly_map_data.coeffs_dvector, dvector.nth_of_list],
  delta poly_map_data.coeffs_list,
  unfold_coes,
  rw list.nth_le_join_of_fn_const_length
    (λ (i : fin n), coeffs_list_of_mv_polynomial d (ps i))
    poly_map_data.coeffs_dvector'_nth_aux1
    j.2
    (index_of_monom_deg_le_lt_length _),
  have h :
    ((λ (i : fin n), coeffs_list_of_mv_polynomial d (ps i)) ⟨ j.1 , j.2⟩)
    =
    coeffs_list_of_mv_polynomial d (ps ⟨ j.1 , j.2 ⟩),
  {refl},
  simp only [h],
  delta coeffs_list_of_mv_polynomial,
  delta mv_polynomial.coeff,
  rw list.nth_le_map,
  have hj : (⟨ j.1, j.2 ⟩ : fin n) = j := fin.eta j j.2,
  rw [hj],
  apply congr_arg,
  have hf' : f ∈ (monom_deg_le_finset n d).to_list :=
  (finset.mem_to_list _).2 hf,
  exact poly_map_data.coeffs_dvector'_nth_aux2 j f hf',
  -- {exact index_of_monom_deg_le₀_lt_length _},
end

lemma realize_poly_map_data_coeffs_xs
  [decidable_eq (mv_polynomial (fin n) A)]
  (hdeg : ∀ (i : fin n), (ps i).total_degree ≤ d) (j : fin n) :
  mv_polynomial.eval (λ (i : fin n), xs.nth i i.2) (ps j)
  =
  list.sumr (list.map
    (λ (f : fin n → ℕ),
       (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps))).nth
         (list.index_of' f (monom_deg_le_finset n d).to_list +
            (j * (monom_deg_le_finset n d).to_list.length + n + n))
         (poly_indexed_by_monoms_aux0 n d _ _ inj_formula_aux0 f)
         *
         (n.non_comm_prod
           (λ (i : fin n),
             (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps))).nth
             (i + n) inj_formula_aux4 ^ f i))
         )
    (monom_deg_le n d))
  :=
begin
  rw mv_polynomial.eval_eq',
  rw mv_polynomial_sum_eq_finset_map_monom_deg_le_finset_sum (ps j) (hdeg j),
  rw finset.sum_map,
  rw list.sumr_eq_sum,
  delta monom_deg_le,
  rw finset.sum_to_list,
  apply finset.sum_congr rfl,
  intros f hf,
  rw realize_poly_map_data_coeffs_xs_aux_prod ps xs ys f,
  congr,
  rw mv_polynomial.coeff,
  rw dvector.nth_append_big,
  {
    rw dvector.nth_append_big,
    {
      simp only [add_assoc],
      simp only [symm (add_assoc (list.index_of' f (monom_deg_le_finset n d).to_list)
        (j * (monom_deg_le_finset n d).to_list.length ) (n + n))],
      simp only [symm (add_assoc (list.index_of' f (monom_deg_le_finset n d).to_list
        + j * (monom_deg_le_finset n d).to_list.length) n n)],
      simp only [nat.add_sub_cancel],
      exact poly_map_data.coeffs_dvector'_nth _ f hf,
    },
    {
      rw ← add_assoc,
      rw nat.add_sub_cancel,
      rw ← add_assoc,
      apply nat.le_add_left,
    },
  },
  {
    rw ← add_assoc,
    apply nat.le_add_left,
  },
end

lemma realize_poly_map_data_coeffs_ys
  [decidable_eq (mv_polynomial (fin n) ↥(struc_to_ring_struc.Structure A))]
  (hdeg : ∀ (i : fin n), (ps i).total_degree ≤ d) (j : fin n) :
  mv_polynomial.eval (λ (i : fin n), ys.nth i i.2) (ps j)
  =
  list.sumr (list.map
    (λ (f : fin n → ℕ),
      (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps))).nth
        (list.index_of' f (monom_deg_le_finset n d).to_list +
           (j * (monom_deg_le_finset n d).to_list.length + n + n))
        (poly_indexed_by_monoms_aux0 n d _ _ inj_formula_aux0 f)
        *
        (n.non_comm_prod
          (λ (i : fin n),
            (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps))).nth
            (i + 0) inj_formula_aux3 ^ f i))
        )
    (monom_deg_le_finset n d).to_list)
  :=
begin
  rw mv_polynomial.eval_eq',
  -- rw ← realize_poly_map_data_coeffs_ys_aux_prod ps xs ys,
  rw mv_polynomial_sum_eq_finset_map_monom_deg_le_finset_sum (ps j) (hdeg j),
  rw finset.sum_map,
  delta monom_deg_le,
  rw list.sumr_eq_sum,
  rw finset.sum_to_list,
  apply finset.sum_congr rfl,
  intros f hf,
  rw realize_poly_map_data_coeffs_ys_aux_prod ps xs ys f,
  congr,
  rw mv_polynomial.coeff,
  rw dvector.nth_append_big,
  {
    rw dvector.nth_append_big,
    {
      simp only [add_assoc],
      simp only [symm (add_assoc (list.index_of' f (monom_deg_le_finset n d).to_list)
        (j * (monom_deg_le_finset n d).to_list.length ) (n + n))],
      simp only [symm (add_assoc (list.index_of' f (monom_deg_le_finset n d).to_list
        + j * (monom_deg_le_finset n d).to_list.length) n n)],
      simp only [nat.add_sub_cancel],
      exact poly_map_data.coeffs_dvector'_nth _ f hf,
    },
    {
      rw ← add_assoc,
      rw nat.add_sub_cancel,
      rw ← add_assoc,
      apply nat.le_add_left,
    },
  },
  {
    rw ← add_assoc,
    apply nat.le_add_left,
  },
end

end realize_poly_map_data_coeffs_xs_and_ys

section max_total_deg

variables {K : Type*} [comm_semiring K]

@[simp] def max_total_deg :
  Π {n m : ℕ}, (fin n → mv_polynomial (fin m) K) → ℕ
| 0 _ ps := 0
| (n + 1) _ ps :=
  max (max_total_deg (λ (i : fin n), ps i)) (mv_polynomial.total_degree (ps n))

def total_deg_le_max_total_deg :
  Π {n m : ℕ} (ps : fin n → mv_polynomial (fin m) K) (i : fin n),
  mv_polynomial.total_degree (ps i) ≤ max_total_deg ps
| 0 _ ps i := fin_zero_elim i
| (n + 1) _ ps i :=
begin
  simp only [max_total_deg],
  rw le_max_iff,
  cases fin.lt_or_eq_fin i with h,
  {
    left,
    have hlt : (i : ℕ) < n,
    {
      rw fin.lt_coe_iff_val_lt i (nat.lt_succ_self _),
      exact h,
    },
    have hind :=
    @total_deg_le_max_total_deg n _ (λ j, ps j.val.cast) ⟨ i , hlt ⟩,
    simp only [fin.coe_eq_cast_succ, fin.cast_succ_mk, fin.eta] at hind,
    rw ← fin.coe_coe_eq_self i,
    exact hind,
  },
  {
    right,
    rw h,
  },
end

end max_total_deg

section alg_closed_field
open classical
local attribute [instance] prop_decidable

variables {K : Type*} [field K] [is_alg_closed K]

lemma Ax_Groth_inj_aux
  {n d : ℕ}
  (ps : poly_map_data K n)
  (hdeg : ∀ (i : fin n), (ps i).total_degree ≤ d)
  (hinj : function.injective (poly_map ps))
  : @realize_bounded_formula _ (struc_to_ring_struc.Structure K)
    _ _ (@poly_map_data.coeffs_dvector' K _ n d ps)
    (inj_formula n d) dvector.nil :=
begin
  let xs0 := poly_map_data.coeffs_dvector' d ps,
  -- open up the definition of inj_formula and what it means to realize it
  simp only [inj_formula,
    realize_bounded_formula_bd_alls',
    realize_bounded_formula_bd_alls',
    realize_bounded_formula_imp,
    realize_bounded_formula_bd_big_and],
  intros xs ys, -- n tuples in the domain
  -- we are showing that ps xs = ps yx implies xs = ys
  intro hImage, -- assume the images are all equal (expressed model theoretically)
  -- we must translate this to the images are equal
  -- (expressed algebraically / in the ring)
  have himage : poly_map ps (λ i, dvector.nth xs i i.2)
               = poly_map ps (λ i, dvector.nth ys i i.2),
  {
    funext j, -- for each i < n (... the tuples at i are equal)
    simp only [poly_map],
    have hImagei := hImage j,
    simp only [realize_bounded_formula,
      monom_deg_le, realize_poly_indexed_by_monoms] at hImagei,
    convert hImagei,
    {rw realize_poly_map_data_coeffs_xs ps xs ys hdeg j, refl },
    {rw realize_poly_map_data_coeffs_ys ps xs ys hdeg j, refl },
  },
  -- by injectivity of poly_map ps we have the preimages are equal (pointwise)
  intro i, -- for each input (... they are equal)
  have hpreimage : dvector.nth xs i i.2 = dvector.nth ys i i.2,
  {apply congr_fun (hinj himage) i},
  simp only [realize_bounded_formula,
    realize_bounded_term,
    @dvector.nth_append_small _ _ _ ys _ i i.2,
    dvector.nth_append_big (nat.le_add_left _ _),
    nat.add_sub_cancel,
    @dvector.nth_append_small _ _ _ xs _ i i.2,
    hpreimage],
end

lemma realize_surj_formula_iff_surjective
  {n d : ℕ}
  (ps : poly_map_data K n)
  (hdeg : ∀ (i : fin n), (ps i).total_degree ≤ d) :
  (@realize_bounded_formula _ (struc_to_ring_struc.Structure K)
    _ _ (@poly_map_data.coeffs_dvector' K _ n d ps)
    (surj_formula n d) dvector.nil)
  ↔
  function.surjective (poly_map ps)
  :=
begin
  simp only [surj_formula,
    realize_bounded_formula_bd_alls',
    realize_bounded_formula_bd_exs',
    realize_bounded_formula_bd_big_and,
    realize_bounded_formula],
  split,
{
  intros hSurj xs, -- for any n tuple xs in the codomain
  obtain ⟨ ys , hys ⟩ := hSurj (dvector.of_fn xs),
  use (dvector.nth' ys), -- there exists an n tuple ys in the domain
  funext k,
  have hysk := hys k,
  have hrw0 : realize_bounded_term
    (ys.append ((dvector.of_fn xs).append (poly_map_data.coeffs_dvector' d ps)))
    x_⟨ k + n , inj_formula_aux4 ⟩ dvector.nil = xs k,
  {
    simp only [realize_bounded_term, symm (fin.val_eq_coe _)],
    rw dvector.nth_append_big (nat.le_add_left _ _) _,
    rw dvector.nth_append_small,
    { simp only [nat.add_sub_cancel,
      dvector.nth_of_fn, fin.val_eq_coe, fin.eta] },
    { rw nat.add_sub_cancel, exact k.2 },
  },
  rw hrw0 at hysk,
  simp only [monom_deg_le, realize_poly_indexed_by_monoms] at hysk,
  have hrw1 := realize_poly_map_data_coeffs_ys ps (dvector.of_fn xs) ys hdeg k,
  simpa only [symm (eq.trans hrw1 hysk), fin.val_eq_coe],
 },
 {
   intros hsurj xs,
   obtain ⟨ ys , hys ⟩ := hsurj xs.nth',
   use dvector.of_fn ys,
   intro k,
   simp only [monom_deg_le, realize_poly_indexed_by_monoms],
   have hrw1 :=
     symm (realize_poly_map_data_coeffs_ys ps xs (dvector.of_fn ys) hdeg k),
   convert hrw1,
   simp only [realize_bounded_term, symm (fin.val_eq_coe _)],
   rw dvector.nth_append_big (nat.le_add_left _ _) _,
   rw dvector.nth_append_small,
   {
     have hysk := congr_fun hys k,
     dsimp only [poly_map, dvector.nth'] at hysk, -- remove
     simp only [nat.add_sub_cancel,
       dvector.nth_of_fn, fin.val_eq_coe, fin.eta, hysk],
   },
   { rw nat.add_sub_cancel, exact k.2 },
 },
end

lemma Ax_Groth_aux
  (h0 : char_zero K) {n d : ℕ}
  (ps : poly_map_data K n)
  (hdeg : ∀ (i : fin n), mv_polynomial.total_degree (ps i) ≤ d)
  (hinj : function.injective (poly_map ps)) :
  function.surjective (poly_map ps) :=
begin
  let coeffs := poly_map_data.coeffs_dvector' d ps,
  have hAG := realize_Ax_Groth_formula_of_char_zero h0 n d,
  simp only [realize_sentence_bd_alls, Ax_Groth_formula] at hAG,
  -- injective -> realize inj_formula
  have hInj : @realize_bounded_formula _ (struc_to_ring_struc.Structure K) _ _
    (poly_map_data.coeffs_dvector' d ps) (inj_formula n d) dvector.nil,
  {exact Ax_Groth_inj_aux ps hdeg hinj},
  rw ← realize_surj_formula_iff_surjective ps hdeg,
  -- apply realize_Ax_Groth to ps, i.e. apply hAG to its coefficients
  exact hAG coeffs hInj,
end

theorem Ax_Groth
  (h0 : char_zero K) {n : ℕ}
  (ps : poly_map_data K n) (hinj : function.injective (poly_map ps)) :
  function.surjective (poly_map ps) :=
Ax_Groth_aux h0 ps (total_deg_le_max_total_deg ps) hinj

end alg_closed_field
end AxGroth


-- import .misc data.list.basic data.vector

variables {α β : Type}

def arity (α β : Type) : nat → Type
| 0     := β
| (n+1) := α → arity n

namespace arity

def app_list [inhabited β] : ∀ {k}, arity α β k → list α → β
| 0 f []          := f
| 0 f (_::_)      := @inhabited.default _ _
| (k+1) f []      := @inhabited.default _ _
| (k+1) f (a::as) := app_list (f a) as

#exit

def set [inhabited β] : ∀ {k} m, (arity α β k) → arity α β m
| 0     0     f := f
| (k+1) 0     f := @inhabited.default β _
| 0 (m+1)     f := λ a, f.set m
| (k+1) (m+1) f := λ a, (f a).set m

lemma set_eq_self [inhabited β] :
  ∀ k (f : arity α β k), f.set k = f
| 0 f     := rfl
| (k+1) f :=
  begin
    simp [set], rw function.funext_iff,
    intro a, rw (set_eq_self k)
  end

def app [inhabited β] : ∀ {k}, (arity α β k) → list α → β
| 0     f []      := f
| 0     f (_::_)  := f
| (k+1) f []      := @inhabited.default _ _
| (k+1) f (a::as) := app (f a) as

#exit
  def app : ∀ {n}, (arity α β n) → ∀ (as : list α), as.length = n → β
  | 0 f _ _ := f
  | (n+1) f [] h := by {exfalso, cases h}
  | (n+1) f (a::as) h :=
     app (f a) as (by {cases h, refl})

  --def app_arity_if (b : β) (mf : Σ m, arity α β m) (as: list α) : β :=
  --dite (mf.fst = as.length)
  --  (λ h, app_arity mf.snd as h.symm) (λ _, b)
--
--  lemma app_arity_if_eq (b1 b2 : β) (nf mg) (as1 as2 : list α) :
--  b1 = b2 → nf = mg → as1 = as2 →
--  app_arity_if b1 nf as1 = app_arity_if b2 mg as2 :=
--  begin intros h1 h2 h3, rw [h1, h2, h3] end
--
  def sum_app : (Σ n, arity α β n × vector α n) → β :=
  λ x, app x.snd.fst x.snd.snd.val x.snd.snd.property

  def sum_app_eq_app :
  ∀ {n} {f :arity α β n} {as : list α} {h : as.length = n},
   sum_app (sigma.mk n ⟨f, (subtype.mk as h)⟩) = app f as h :=
  λ n f as h, refl _

  lemma app_eq_app {α β : Type} {n m : ℕ} (f : arity α β n) (g : arity α β m)
    (as1 as2 : list α) (h1 : list.length as1 = n) (h2 : list.length as2 = m)
    (heq1 : n = m) (heq2 : @eq.rec _ _ _ f m heq1 = g)
    (heq3 : as1 = as2) : app f as1 h1 = app g as2 h2 :=
  begin
    repeat {rw [sum_app_eq_app.symm]},
    apply congr_arg _ (sigma.eq _ _), apply heq1,
    rw prod.eq_iff_fst_eq_snd_eq, simp, constructor,
    subst heq1, simp at *, apply heq2,
    subst heq1, simp at *, apply heq3
  end

end arity


/-
Copyright (c) 2020 Markus Himmel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Markus Himmel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.category.Module.basic
import Mathlib.PostPort

universes u v u_1

namespace Mathlib

/-!
# The concrete (co)kernels in the category of modules are (co)kernels in the categorical sense.
-/

namespace Module

/-- The kernel cone induced by the concrete kernel. -/
def kernel_cone {R : Type u} [ring R] {M : Module R} {N : Module R} (f : M ⟶ N) : category_theory.limits.kernel_fork f :=
  category_theory.limits.kernel_fork.of_ι (as_hom (submodule.subtype (linear_map.ker f))) sorry

/-- The kernel of a linear map is a kernel in the categorical sense. -/
def kernel_is_limit {R : Type u} [ring R] {M : Module R} {N : Module R} (f : M ⟶ N) : category_theory.limits.is_limit (kernel_cone f) :=
  category_theory.limits.fork.is_limit.mk (kernel_cone f)
    (fun (s : category_theory.limits.fork f 0) =>
      linear_map.cod_restrict (linear_map.ker f) (category_theory.limits.fork.ι s) sorry)
    sorry sorry

/-- The cokernel cocone induced by the projection onto the quotient. -/
def cokernel_cocone {R : Type u} [ring R] {M : Module R} {N : Module R} (f : M ⟶ N) : category_theory.limits.cokernel_cofork f :=
  category_theory.limits.cokernel_cofork.of_π (as_hom (submodule.mkq (linear_map.range f))) sorry

/-- The projection onto the quotient is a cokernel in the categorical sense. -/
def cokernel_is_colimit {R : Type u} [ring R] {M : Module R} {N : Module R} (f : M ⟶ N) : category_theory.limits.is_colimit (cokernel_cocone f) :=
  category_theory.limits.cofork.is_colimit.mk (cokernel_cocone f)
    (fun (s : category_theory.limits.cofork f 0) =>
      submodule.liftq (linear_map.range f) (category_theory.limits.cofork.π s) sorry)
    sorry sorry

/-- The category of R-modules has kernels, given by the inclusion of the kernel submodule. -/
theorem has_kernels_Module {R : Type u} [ring R] : category_theory.limits.has_kernels (Module R) :=
  category_theory.limits.has_kernels.mk
    fun (X Y : Module R) (f : X ⟶ Y) =>
      category_theory.limits.has_limit.mk (category_theory.limits.limit_cone.mk (kernel_cone f) (kernel_is_limit f))

/-- The category or R-modules has cokernels, given by the projection onto the quotient. -/
theorem has_cokernels_Module {R : Type u} [ring R] : category_theory.limits.has_cokernels (Module R) := sorry


import .inter
import ..constructions.direct_sum
import ..equiv
import algebra.big_operators.finprod

open_locale classical
open_locale big_operators

open matroid set equiv

section partitionable

variables {ι E : Type*} [finite E] [finite ι] {M : ι → matroid E} {I A X : set E}

/-- A set is partitionable with respect to a collection of matroids on `E` if it admits a partition
  into sets that are independent in these matroids  -/
-- def partitionable (M : ι → matroid E) (I : set E) : Prop :=
--   ∃ f : I → ι, ∀ i, (M i).indep (I ∩ (coe '' (f⁻¹' {i})))

def partitionable (M : ι → matroid E) (I : set E) : Prop :=
  ∃ (f : ι → set E), (I = ⋃ i, f i) ∧ (∀ i, (M i).indep (f i)) ∧ pairwise (disjoint on f)

lemma partitionable_iff_is_Union :
  partitionable M I ↔ ∃ (f : ι → set E), (I = ⋃ i, f i) ∧ (∀ i, (M i).indep (f i)) :=
begin
  refine ⟨λ ⟨f, h, hf, h'⟩, ⟨f, h, hf⟩, _⟩,
  rintro ⟨f,rfl,hf⟩,
  have hinv : ∀ (e : ⋃ (i : ι), f i), ∃ i, (e : E) ∈ f i,
  { rintro ⟨e,he⟩, exact mem_Union.mp he},

  choose g hg using hinv,
  have hss : ∀ i, coe '' (g ⁻¹' {i}) ⊆ f i,
  { rintro i x ⟨y,hy,rfl⟩,
    rw [mem_preimage, mem_singleton_iff] at hy, subst hy,
    exact hg y},

  refine ⟨λ i, coe '' (g ⁻¹' {i}), subset_antisymm _ _, λ i, (hf i).subset (hss i), _⟩,
  { refine Union_subset (λ i x hx, mem_Union_of_mem (g ⟨x, mem_Union_of_mem _ hx⟩) _),
    simp only [mem_Union, mem_image, mem_preimage, mem_singleton_iff, set_coe.exists,
      subtype.coe_mk, exists_and_distrib_right, exists_eq_right, eq_self_iff_true, exists_prop,
      and_true],
    exact ⟨_, hx⟩},
  { exact Union_subset (λ i, subset_Union_of_subset i (hss i))},
  rintro i j hij s (h1 : s ⊆ _) (h2 : s ⊆ _) i his,
  have h1' := h1 his,
  have h2' := h2 his,
  simp only [mem_Union, mem_image, mem_preimage, mem_singleton_iff, set_coe.exists,
    subtype.coe_mk, exists_and_distrib_right, exists_eq_right] at h1' h2',
  obtain ⟨⟨i₁,hi₁⟩,rfl⟩ := h1',
  obtain ⟨⟨i₂,hi₂⟩,rfl⟩ := h2',
  exfalso,
  simpa using hij,
end

lemma partitionable_ub {M : ι → matroid E} {I : set E} (X : set E) (h : partitionable M I) :
  I.ncard ≤ ∑ᶠ i, (M i).r X + Xᶜ.ncard :=
begin
  rw [←ncard_inter_add_ncard_diff_eq_ncard I X],
  refine add_le_add _
    (ncard_le_of_subset (by {rw compl_eq_univ_diff, exact diff_subset_diff_left (subset_univ _)})),

  obtain ⟨J, rfl, hJ, -⟩ :=  h,
  rw [Union_inter],
  refine (ncard_Union_le _ (λ _, to_finite _)).trans _,
  refine finsum_le_finsum (to_finite _) (to_finite _) (λ i, _),
  rw [←((hJ i).inter_right _).r],
  apply r_inter_right_le_r,
end

theorem matroid_union (M : ι → matroid E) :
  ∃ I X, partitionable M I ∧ I.ncard = ∑ᶠ i, (M i).r X + Xᶜ.ncard :=
begin
  suffices h : ∃ I X : set E, partitionable M I ∧ ∑ᶠ i, (M i).r Xᶜ + X.ncard ≤ I.ncard,
  { obtain ⟨I, X, hI, hle⟩ := h,
    refine ⟨I, Xᶜ, hI, _⟩,
    rw compl_compl,
    refine hle.antisymm' _,
    have := (partitionable_ub Xᶜ hI),
    rwa compl_compl at this},

  set M₁ := (direct_sum M).congr_equiv (sigma_equiv_prod ι E) with hM₁,
  set M₂ := (partition_matroid (λ (e : E), ι) 1).congr_equiv
    ((sigma_equiv_prod E ι).trans (prod_comm _ _)) with hM₂,

  obtain ⟨I,X, hI₁,hI₂, hIX, hF⟩ := exists_common_ind_with_flat_right M₁ M₂,

  simp only [congr_equiv_apply_flat, equiv.coe_trans, coe_prod_comm, preimage_compl,
    partition_one_flat_iff] at hF,

  simp_rw [← disjoint_compl_left_iff_subset, compl_compl, disjoint_compl_left_iff_subset] at hF,

  refine ⟨prod.snd '' I, prod.snd '' Xᶜ, _, _⟩,
  { rw partitionable_iff_is_Union,
    refine ⟨λ i, prod.snd '' (I ∩ (prod.fst ⁻¹' {i})), _, λ i, _⟩,
    { rw [←image_Union, ←inter_Union, ←bUnion_univ, bUnion_preimage_singleton, preimage_univ,
      inter_univ]},
    simp only [congr_equiv_apply_indep, direct_sum_indep_iff] at hI₁,
    convert hI₁ i, ext, simp},

  have hI : (prod.snd '' I).ncard = I.ncard,
  { refine ncard_image_of_inj_on _,
    rintro ⟨i₁,e⟩ h₁ ⟨i₂,e'⟩ h₂ (rfl : e = e'),
    simp only [prod.mk.inj_iff, eq_self_iff_true, and_true],
    simp only [congr_equiv_apply_indep, equiv.coe_trans, coe_prod_comm, partition_matroid_indep_iff,
      pi.one_apply, ncard_le_one_iff, mem_inter_iff, mem_preimage, function.comp_app,
      sigma_equiv_prod_apply, prod.swap_prod_mk, mem_singleton_iff, and_imp, sigma.forall] at hI₂,
    simpa using (@hI₂ e e i₂ ⟨e,i₁⟩ h₂ rfl h₁ rfl).symm},

  rw hI,
  refine le_of_le_of_eq (add_le_add (le_of_eq _) _) hIX.symm,

  { simp only [congr_equiv_apply_r, direct_sum_r_eq],
    convert rfl, ext i, convert rfl, ext e,
    simp only [mem_compl_iff, mem_image, prod.exists, exists_eq_right, not_exists_not,
      mem_preimage, sigma_equiv_prod_apply],
    refine ⟨λ h, h _, λ hie x, _⟩,
    obtain (hdj | hss) := hF e,
    { exact (@disjoint.ne_of_mem _ _ _ hdj ⟨e,i⟩ ⟨e,i⟩ (by simpa) rfl rfl).elim},
    exact @hss ⟨e,x⟩ rfl},

  simp only [congr_equiv_apply_r, equiv.coe_trans, coe_prod_comm, preimage_compl,
    partition_matroid_r_eq, pi.one_apply, ←finsum_mem_one],

  nth_rewrite_rhs 0 ←finsum_mem_univ,
  refine (finsum_mem_le_finsum_mem (to_finite _) (to_finite _) _).trans
    (finsum_le_finsum_of_subset (subset_univ (prod.snd '' Xᶜ)) (to_finite _)),

  rintro i ⟨⟨x1,x2⟩,hx, rfl⟩,
  simp only [mem_inter_iff, mem_compl_iff, mem_preimage, function.comp_app,
    sigma_equiv_prod_apply, prod.swap_prod_mk, mem_singleton_iff, le_min_iff, le_refl, true_and],

  conv_rhs {congr, funext, rw finsum_eq_if,  },

  rw ←finsum_mem_univ,

  refine le_trans _ (mem_le_finsum (mem_univ ((sigma_equiv_prod _ _).symm ⟨x2,x1⟩))
    (to_finite _)),

  rw [sigma_equiv_prod_symm_apply, if_pos],
  simp only [eq_self_iff_true, and_true],
  rwa mem_compl_iff at hx,
end

end partitionable

section partitionable_sets

variables {ι E : Type*} [finite E] [finite ι] {M : ι → matroid E} {I A X S : set E}

lemma partitionable_ub_subset (hX : I ⊆ S) (h : partitionable M I) :
  I.ncard ≤ ∑ᶠ i, (M i).r X + (S \ X).ncard :=
begin
  rw [←ncard_inter_add_ncard_diff_eq_ncard I X],
   refine add_le_add _ (ncard_le_of_subset (diff_subset_diff_left hX) (to_finite _)),
  obtain ⟨J, rfl, hJ⟩ := partitionable_iff_is_Union.mp h,
  rw [Union_inter],
  refine (ncard_Union_le _ (λ _, to_finite _)).trans _,
  refine finsum_le_finsum (to_finite _) (to_finite _) (λ i, _),
  rw [←((hJ i).inter_right _).r],
  apply r_inter_right_le_r,
end

/-- A version of matroid union that is relative to a subset. -/
theorem matroid_union_subset (M : ι → matroid E) (S : set E):
  ∃ (I ⊆ S) (X ⊆ S), partitionable M I ∧ I.ncard = ∑ᶠ i, (M i).r X + (S \ X).ncard :=
begin
  suffices : ∃ (I ⊆ S) (X ⊆ S), partitionable M I ∧ ∑ᶠ i, (M i).r X + (S \ X).ncard ≤ I.ncard,
  { obtain ⟨I, hIS, X, hXS, hI, h⟩ := this,
    exact ⟨I, hIS, X, hXS, hI, (partitionable_ub_subset hIS hI).antisymm h⟩},
  obtain ⟨I, X, hpart, hcard⟩ := matroid_union (λ i, M i ‖ S),
  use I,

  obtain ⟨J, rfl, hJ⟩ := partitionable_iff_is_Union.mp hpart,
  simp only [indep_lrestrict_iff] at hJ,
  simp_rw [lrestrict_r] at hcard,

  refine ⟨Union_subset (λ i, (hJ i).2 ), X ∩ S, inter_subset_right _ _, _, _⟩,
  { rw partitionable_iff_is_Union,
    exact ⟨_,rfl,λi, (hJ i).1⟩ },

  simp only [hcard, diff_inter_self_eq_diff, add_le_add_iff_left, compl_eq_univ_diff],
  exact ncard_le_of_subset (diff_subset_diff_left (subset_univ _)),
end

noncomputable def max_partitionable_set (M : ι → matroid E) (X : set E) : ℕ :=
  ⨅ A, ∑ᶠ i, (M i).r A + (X \ A).ncard

-- noncomputable def partitionable_sets_matroid (M : ι → matroid E) :
--   matroid E :=
-- matroid_of_r (max_partitionable_set M)
-- (λ X, by {convert cinfi_le' _ (∅ : set E), simp})
-- (λ X Y hXY, le_cinfi (λ Z, (cinfi_le' _ Z).trans
--   (add_le_add_left (ncard_le_of_subset (diff_subset_diff_left hXY) (to_finite _)) _)))
-- (begin
--   simp only [max_partitionable_set],
--   intros X Y,
-- end )

-- lemma ncard_max_partitionable_subset_eq (M : ι → matroid E)

def partitionable_sets_matroid (M : ι → matroid E) :
  matroid E :=
matroid_of_indep (partitionable M)
⟨∅, partitionable_iff_is_Union.mpr ⟨λ i, ∅, by simp, λ _, empty_indep _⟩⟩
(begin
  simp_rw partitionable_iff_is_Union,
  rintro I J hIJ ⟨s, rfl, hi⟩,
  refine ⟨λ i, (s i) ∩ I, _, λ i, (hi i).subset (inter_subset_left _ _)⟩,
  rwa [←Union_inter, eq_comm, inter_eq_right_iff_subset],
end )
(begin
  --simp_rw partitionable_iff_is_Union,
  rintro I J ⟨I, rfl, hI, hIdj⟩ ⟨J, rfl, hJ, hJdj⟩ hlt,
  by_contra' h',

  have hss : (⋃ i, J i) ⊆ ⋃ i, (M i).cl (I i),
  { rintro e ⟨J₀,⟨k,rfl⟩,(hek : e ∈ J k)⟩,
    obtain (⟨I₀,⟨i,rfl⟩,(hei : e ∈ I i)⟩ | hne) := em (e ∈ ⋃ i, I i),
    { exact mem_Union_of_mem i (((M i).subset_cl _) hei)},
    by_contra h'',
    apply h' e (mem_Union_of_mem _ hek) hne,
    rw partitionable_iff_is_Union,
    refine ⟨I.update k (insert e (I k)), _, _⟩,
    { simp only [subset_antisymm_iff, insert_subset,mem_Union, Union_subset_iff,
        function.update_apply],
      refine ⟨⟨⟨k,by simp⟩,λ i, subset_Union_of_subset i _⟩,λ i, _⟩,
      { split_ifs, subst h, simp, },
      split_ifs,
      { subst h, exact insert_subset_insert (subset_Union _ _)},
      exact (subset_Union _ _).trans (subset_insert _ _)},
    intro i,
    simp_rw function.update_apply,
    split_ifs,
    { subst h,
      have hei := not_mem_subset (subset_Union _ i) h'',
      exact ((hI i).not_mem_cl_iff.mp hei).2},
    apply hI},

      -- refine ⟨⟨k, by simp⟩ , λ i, subset_Union_of_subset i _⟩,
      -- split_ifs,
      -- { subst h, simp},

    -- by_contra h'', rw [not_mem_iff] at h'',

  -- have h_ex : ∃ i, (I i).ncard < (J i).ncard,
  -- { by_contra' h,
  --   simp_rw [←finsum_mem_one, finsum_mem_Union hIdj (λ i, to_finite _),
  --     finsum_mem_Union hJdj (λ i, to_finite _), finsum_mem_one] at hlt,
  --   exact hlt.not_le (finsum_le_finsum (to_finite _) (to_finite _) h)},
  -- obtain ⟨i, hi⟩ := h_ex,
  -- obtain ⟨e ,heJ, heI, he⟩ := (hI i).augment (hJ i) hi,
  -- refine ⟨e, mem_Union_of_mem _ heJ, _ , _⟩,
  -- { rintro ⟨x, ⟨⟨j,rfl⟩,h⟩⟩,simp at h,   },

end)

end partitionable_sets


import data.set.basic
import tactic.tidy
import lib.attempt

variables (R A : Type)
variables (𝕍 : set R → set A) (𝕀 : set A → set R)

open set

-- 𝕍 𝕀 𝕍 = 𝕍 for a contravariant Galois connection
-- for example the one between R=k[X₁,X₂,…,Xₙ] and A=𝔸ⁿ
-- in the theory of algebraic varieties

example
  (𝕍_antimono : ∀ J₁ J₂ : set R, J₁ ⊆ J₂ → 𝕍 J₂ ⊆ 𝕍 J₁)
  (𝕀_antimono : ∀ W₁ W₂ : set A, W₁ ⊆ W₂ → 𝕀 W₂ ⊆ 𝕀 W₁)
  (galoi : ∀ J : set R, ∀ W : set A, J ⊆ 𝕀 W ↔ W ⊆ 𝕍 J) :
  ∀ J : set R, 𝕍 (𝕀 (𝕍 J)) = 𝕍 J :=
attempt begin
  simp only [subset_def, mem_def] at *,
  delta set at *,
  intro J,
  apply funext,
  intro a,
  apply propext,
  split,
  { intro h₁,
    apply 𝕍_antimono,
    intros r h₂,
    sorry },
  { sorry }
end $
attempt begin
  have galoi₁ := λ J W, (galoi J W).1,
  have galoi₂ := λ J W, (galoi J W).2,
  intro J,
  apply subset.antisymm,
  { apply 𝕍_antimono,
    apply galoi₂,
    apply subset.refl },
  { apply galoi₁,
    apply subset.refl }
end $
attempt begin
  have galoi₁ := λ J W, (galoi J W).1,
  have galoi₂ := λ J W, (galoi J W).2,
  intro J,
  apply subset.antisymm,
  solve_by_elim [subset.refl],
  solve_by_elim [subset.refl],
end $
begin
  intro J,
  apply subset.antisymm,
  { apply 𝕍_antimono,
    rw galoi },
  { rw ←galoi }
end