/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir, Jean Lo, Calle Sönne, Benjamin Davidson

! This file was ported from Lean 3 source module analysis.special_functions.trigonometric.arctan
! leanprover-community/mathlib commit f2ce6086713c78a7f880485f7917ea547a215982
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.SpecialFunctions.Trigonometric.Complex

/-!
# The `arctan` function.

Inequalities, derivatives,
and `real.tan` as a `local_homeomorph` between `(-(π / 2), π / 2)` and the whole line.
-/

noncomputable section

namespace Real

open Set Filter

open Topology Real

theorem tan_add {x y : ℝ}
    (h :
      ((∀ k : ℤ, x ≠ (2 * k + 1) * π / 2) ∧ ∀ l : ℤ, y ≠ (2 * l + 1) * π / 2) ∨
        (∃ k : ℤ, x = (2 * k + 1) * π / 2) ∧ ∃ l : ℤ, y = (2 * l + 1) * π / 2) :
    tan (x + y) = (tan x + tan y) / (1 - tan x * tan y) := by
  simpa only [← Complex.ofReal_inj, Complex.ofReal_sub, Complex.ofReal_add, Complex.ofReal_div,
    Complex.ofReal_mul, Complex.ofReal_tan] using
    @Complex.tan_add (x : ℂ) (y : ℂ) (by convert h <;> norm_cast)
#align real.tan_add Real.tan_add

theorem tan_add' {x y : ℝ}
    (h : (∀ k : ℤ, x ≠ (2 * k + 1) * π / 2) ∧ ∀ l : ℤ, y ≠ (2 * l + 1) * π / 2) :
    tan (x + y) = (tan x + tan y) / (1 - tan x * tan y) :=
  tan_add (Or.inl h)
#align real.tan_add' Real.tan_add'

theorem tan_two_mul {x : ℝ} : tan (2 * x) = 2 * tan x / (1 - tan x ^ 2) := by
  simpa only [← Complex.ofReal_inj, Complex.ofReal_sub, Complex.ofReal_div, Complex.ofReal_pow,
    Complex.ofReal_mul, Complex.ofReal_tan, Complex.ofReal_bit0, Complex.ofReal_one] using
    Complex.tan_two_mul
#align real.tan_two_mul Real.tan_two_mul

theorem tan_ne_zero_iff {θ : ℝ} : tan θ ≠ 0 ↔ ∀ k : ℤ, θ ≠ k * π / 2 := by
  rw [← Complex.ofReal_ne_zero, Complex.ofReal_tan, Complex.tan_ne_zero_iff] <;> norm_cast
#align real.tan_ne_zero_iff Real.tan_ne_zero_iff

theorem tan_eq_zero_iff {θ : ℝ} : tan θ = 0 ↔ ∃ k : ℤ, θ = k * π / 2 := by
  rw [← not_iff_not, not_exists, ← Ne, tan_ne_zero_iff]
#align real.tan_eq_zero_iff Real.tan_eq_zero_iff

theorem tan_int_mul_pi_div_two (n : ℤ) : tan (n * π / 2) = 0 :=
  tan_eq_zero_iff.mpr (by use n)
#align real.tan_int_mul_pi_div_two Real.tan_int_mul_pi_div_two

theorem continuousOn_tan : ContinuousOn tan { x | cos x ≠ 0 } :=
  by
  suffices ContinuousOn (fun x => sin x / cos x) { x | cos x ≠ 0 }
    by
    have h_eq : (fun x => sin x / cos x) = tan :=
      by
      ext1 x
      rw [tan_eq_sin_div_cos]
    rwa [h_eq] at this
  exact continuous_on_sin.div continuous_on_cos fun x => id
#align real.continuous_on_tan Real.continuousOn_tan

@[continuity]
theorem continuous_tan : Continuous fun x : { x | cos x ≠ 0 } => tan x :=
  continuousOn_iff_continuous_restrict.1 continuousOn_tan
#align real.continuous_tan Real.continuous_tan

theorem continuousOn_tan_Ioo : ContinuousOn tan (Ioo (-(π / 2)) (π / 2)) :=
  by
  refine' ContinuousOn.mono continuous_on_tan fun x => _
  simp only [and_imp, mem_Ioo, mem_set_of_eq, Ne.def]
  rw [cos_eq_zero_iff]
  rintro hx_gt hx_lt ⟨r, hxr_eq⟩
  cases le_or_lt 0 r
  · rw [lt_iff_not_ge] at hx_lt
    refine' hx_lt _
    rw [hxr_eq, ← one_mul (π / 2), mul_div_assoc, ge_iff_le, mul_le_mul_right (half_pos pi_pos)]
    simp [h]
  · rw [lt_iff_not_ge] at hx_gt
    refine' hx_gt _
    rw [hxr_eq, ← one_mul (π / 2), mul_div_assoc, ge_iff_le, neg_mul_eq_neg_mul,
      mul_le_mul_right (half_pos pi_pos)]
    have hr_le : r ≤ -1 := by rwa [Int.lt_iff_add_one_le, ← le_neg_iff_add_nonpos_right] at h
    rw [← le_sub_iff_add_le, mul_comm, ← le_div_iff]
    · norm_num
      rw [← Int.cast_one, ← Int.cast_neg]
      norm_cast
      exact hr_le
    · exact zero_lt_two
#align real.continuous_on_tan_Ioo Real.continuousOn_tan_Ioo

theorem surjOn_tan : SurjOn tan (Ioo (-(π / 2)) (π / 2)) univ :=
  have := neg_lt_self pi_div_two_pos
  continuousOn_tan_Ioo.surjOn_of_tendsto (nonempty_Ioo.2 this)
    (by simp [tendsto_tan_neg_pi_div_two, this]) (by simp [tendsto_tan_pi_div_two, this])
#align real.surj_on_tan Real.surjOn_tan

theorem tan_surjective : Function.Surjective tan := fun x => surjOn_tan.subset_range trivial
#align real.tan_surjective Real.tan_surjective

theorem image_tan_Ioo : tan '' Ioo (-(π / 2)) (π / 2) = univ :=
  univ_subset_iff.1 surjOn_tan
#align real.image_tan_Ioo Real.image_tan_Ioo

/-- `real.tan` as an `order_iso` between `(-(π / 2), π / 2)` and `ℝ`. -/
def tanOrderIso : Ioo (-(π / 2)) (π / 2) ≃o ℝ :=
  (strictMonoOn_tan.OrderIso _ _).trans <|
    (OrderIso.setCongr _ _ image_tan_Ioo).trans OrderIso.Set.univ
#align real.tan_order_iso Real.tanOrderIso

/-- Inverse of the `tan` function, returns values in the range `-π / 2 < arctan x` and
`arctan x < π / 2` -/
@[pp_nodot]
noncomputable def arctan (x : ℝ) : ℝ :=
  tanOrderIso.symm x
#align real.arctan Real.arctan

@[simp]
theorem tan_arctan (x : ℝ) : tan (arctan x) = x :=
  tanOrderIso.apply_symm_apply x
#align real.tan_arctan Real.tan_arctan

theorem arctan_mem_Ioo (x : ℝ) : arctan x ∈ Ioo (-(π / 2)) (π / 2) :=
  Subtype.coe_prop _
#align real.arctan_mem_Ioo Real.arctan_mem_Ioo

@[simp]
theorem range_arctan : range arctan = Ioo (-(π / 2)) (π / 2) :=
  ((EquivLike.surjective _).range_comp _).trans Subtype.range_coe
#align real.range_arctan Real.range_arctan

theorem arctan_tan {x : ℝ} (hx₁ : -(π / 2) < x) (hx₂ : x < π / 2) : arctan (tan x) = x :=
  Subtype.ext_iff.1 <| tanOrderIso.symm_apply_apply ⟨x, hx₁, hx₂⟩
#align real.arctan_tan Real.arctan_tan

theorem cos_arctan_pos (x : ℝ) : 0 < cos (arctan x) :=
  cos_pos_of_mem_Ioo <| arctan_mem_Ioo x
#align real.cos_arctan_pos Real.cos_arctan_pos

theorem cos_sq_arctan (x : ℝ) : cos (arctan x) ^ 2 = 1 / (1 + x ^ 2) := by
  rw [one_div, ← inv_one_add_tan_sq (cos_arctan_pos x).ne', tan_arctan]
#align real.cos_sq_arctan Real.cos_sq_arctan

theorem sin_arctan (x : ℝ) : sin (arctan x) = x / sqrt (1 + x ^ 2) := by
  rw [← tan_div_sqrt_one_add_tan_sq (cos_arctan_pos x), tan_arctan]
#align real.sin_arctan Real.sin_arctan

theorem cos_arctan (x : ℝ) : cos (arctan x) = 1 / sqrt (1 + x ^ 2) := by
  rw [one_div, ← inv_sqrt_one_add_tan_sq (cos_arctan_pos x), tan_arctan]
#align real.cos_arctan Real.cos_arctan

theorem arctan_lt_pi_div_two (x : ℝ) : arctan x < π / 2 :=
  (arctan_mem_Ioo x).2
#align real.arctan_lt_pi_div_two Real.arctan_lt_pi_div_two

theorem neg_pi_div_two_lt_arctan (x : ℝ) : -(π / 2) < arctan x :=
  (arctan_mem_Ioo x).1
#align real.neg_pi_div_two_lt_arctan Real.neg_pi_div_two_lt_arctan

theorem arctan_eq_arcsin (x : ℝ) : arctan x = arcsin (x / sqrt (1 + x ^ 2)) :=
  Eq.symm <| arcsin_eq_of_sin_eq (sin_arctan x) (mem_Icc_of_Ioo <| arctan_mem_Ioo x)
#align real.arctan_eq_arcsin Real.arctan_eq_arcsin

theorem arcsin_eq_arctan {x : ℝ} (h : x ∈ Ioo (-(1 : ℝ)) 1) :
    arcsin x = arctan (x / sqrt (1 - x ^ 2)) := by
  rw [arctan_eq_arcsin, div_pow, sq_sqrt, one_add_div, div_div, ← sqrt_mul, mul_div_cancel',
      sub_add_cancel, sqrt_one, div_one] <;>
    nlinarith [h.1, h.2]
#align real.arcsin_eq_arctan Real.arcsin_eq_arctan

@[simp]
theorem arctan_zero : arctan 0 = 0 := by simp [arctan_eq_arcsin]
#align real.arctan_zero Real.arctan_zero

theorem arctan_eq_of_tan_eq {x y : ℝ} (h : tan x = y) (hx : x ∈ Ioo (-(π / 2)) (π / 2)) :
    arctan y = x :=
  injOn_tan (arctan_mem_Ioo _) hx (by rw [tan_arctan, h])
#align real.arctan_eq_of_tan_eq Real.arctan_eq_of_tan_eq

@[simp]
theorem arctan_one : arctan 1 = π / 4 :=
  arctan_eq_of_tan_eq tan_pi_div_four <| by constructor <;> linarith [pi_pos]
#align real.arctan_one Real.arctan_one

@[simp]
theorem arctan_neg (x : ℝ) : arctan (-x) = -arctan x := by simp [arctan_eq_arcsin, neg_div]
#align real.arctan_neg Real.arctan_neg

theorem arctan_eq_arccos {x : ℝ} (h : 0 ≤ x) : arctan x = arccos (sqrt (1 + x ^ 2))⁻¹ :=
  by
  rw [arctan_eq_arcsin, arccos_eq_arcsin]; swap; · exact inv_nonneg.2 (sqrt_nonneg _)
  congr 1
  rw [← sqrt_inv, sq_sqrt, ← one_div, one_sub_div, add_sub_cancel', sqrt_div, sqrt_sq h]
  all_goals positivity
#align real.arctan_eq_arccos Real.arctan_eq_arccos

-- The junk values for `arccos` and `sqrt` make this true even for `1 < x`.
theorem arccos_eq_arctan {x : ℝ} (h : 0 < x) : arccos x = arctan (sqrt (1 - x ^ 2) / x) :=
  by
  rw [arccos, eq_comm]
  refine' arctan_eq_of_tan_eq _ ⟨_, _⟩
  · rw [tan_pi_div_two_sub, tan_arcsin, inv_div]
  · linarith only [arcsin_le_pi_div_two x, pi_pos]
  · linarith only [arcsin_pos.2 h]
#align real.arccos_eq_arctan Real.arccos_eq_arctan

@[continuity]
theorem continuous_arctan : Continuous arctan :=
  continuous_subtype_val.comp tanOrderIso.toHomeomorph.continuous_invFun
#align real.continuous_arctan Real.continuous_arctan

theorem continuousAt_arctan {x : ℝ} : ContinuousAt arctan x :=
  continuous_arctan.ContinuousAt
#align real.continuous_at_arctan Real.continuousAt_arctan

/-- `real.tan` as a `local_homeomorph` between `(-(π / 2), π / 2)` and the whole line. -/
def tanLocalHomeomorph : LocalHomeomorph ℝ ℝ
    where
  toFun := tan
  invFun := arctan
  source := Ioo (-(π / 2)) (π / 2)
  target := univ
  map_source' := mapsTo_univ _ _
  map_target' y hy := arctan_mem_Ioo y
  left_inv' x hx := arctan_tan hx.1 hx.2
  right_inv' y hy := tan_arctan y
  open_source := isOpen_Ioo
  open_target := isOpen_univ
  continuous_toFun := continuousOn_tan_Ioo
  continuous_invFun := continuous_arctan.ContinuousOn
#align real.tan_local_homeomorph Real.tanLocalHomeomorph

@[simp]
theorem coe_tanLocalHomeomorph : ⇑tanLocalHomeomorph = tan :=
  rfl
#align real.coe_tan_local_homeomorph Real.coe_tanLocalHomeomorph

@[simp]
theorem coe_tanLocalHomeomorph_symm : ⇑tanLocalHomeomorph.symm = arctan :=
  rfl
#align real.coe_tan_local_homeomorph_symm Real.coe_tanLocalHomeomorph_symm

end Real


/-
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

Relator for functions, pairs, sums, and lists.
-/

import logic.basic

namespace relator
universes u₁ u₂ v₁ v₂

/- TODO(johoelzl):
 * should we introduce relators of datatypes as recursive function or as inductive
predicate? For now we stick to the recursor approach.
 * relation lift for datatypes, Π, Σ, set, and subtype types
 * proof composition and identity laws
 * implement method to derive relators from datatype
-/

section
variables {α : Sort u₁} {β : Sort u₂} {γ : Sort v₁} {δ : Sort v₂}
variables (R : α → β → Prop) (S : γ → δ → Prop)

def lift_fun (f : α → γ) (g : β → δ) : Prop :=
∀⦃a b⦄, R a b → S (f a) (g b)

infixr ⇒ := lift_fun

end

section
variables {α : Type u₁} {β : Type u₂} (R : α → β → Prop)

def right_total : Prop := ∀ b, ∃ a, R a b
def left_total : Prop := ∀ a, ∃ b, R a b
def bi_total : Prop := left_total R ∧ right_total R

def left_unique : Prop := ∀ ⦃a b c⦄, R a c → R b c → a = b
def right_unique : Prop := ∀ ⦃a b c⦄, R a b → R a c → b = c
def bi_unique : Prop := left_unique R ∧ right_unique R

variable {R}

lemma right_total.rel_forall (h : right_total R) :
  ((R ⇒ implies) ⇒ implies) (λp, ∀i, p i) (λq, ∀i, q i) :=
assume p q Hrel H b, exists.elim (h b) (assume a Rab, Hrel Rab (H _))

lemma left_total.rel_exists (h : left_total R) :
  ((R ⇒ implies) ⇒ implies) (λp, ∃i, p i) (λq, ∃i, q i) :=
assume p q Hrel ⟨a, pa⟩, (h a).imp $ λ b Rab, Hrel Rab pa

lemma bi_total.rel_forall (h : bi_total R) :
  ((R ⇒ iff) ⇒ iff) (λp, ∀i, p i) (λq, ∀i, q i) :=
assume p q Hrel,
  ⟨assume H b, exists.elim (h.right b) (assume a Rab, (Hrel Rab).mp (H _)),
    assume H a, exists.elim (h.left a) (assume b Rab, (Hrel Rab).mpr (H _))⟩

lemma bi_total.rel_exists (h : bi_total R) : ((R ⇒ iff) ⇒ iff) (λp, ∃i, p i) (λq, ∃i, q i) :=
assume p q Hrel,
  ⟨assume ⟨a, pa⟩, (h.left a).imp $ λ b Rab, (Hrel Rab).1 pa,
    assume ⟨b, qb⟩, (h.right b).imp $ λ a Rab, (Hrel Rab).2 qb⟩

lemma left_unique_of_rel_eq {eq' : β → β → Prop} (he : (R ⇒ (R ⇒ iff)) eq eq') : left_unique R :=
λ a b c (ac : R a c) (bc : R b c), (he ac bc).mpr ((he bc bc).mp rfl)

end

lemma rel_imp : (iff ⇒ (iff  ⇒ iff)) implies implies :=
assume p q h r s l, imp_congr h l

lemma rel_not : (iff ⇒ iff) not not :=
assume p q h, not_congr h

lemma bi_total_eq {α : Type u₁} : relator.bi_total (@eq α) :=
{ left := λ a, ⟨a, rfl⟩, right := λ a, ⟨a, rfl⟩ }

variables {α : Type*} {β : Type*} {γ : Type*} {δ : Type*}
variables {r : α → β → Prop} {p : β → γ → Prop} {q : γ → δ → Prop}

lemma left_unique.flip (h : left_unique r) : right_unique (flip r) :=
λ a b c h₁ h₂, h h₁ h₂

lemma rel_and : ((↔) ⇒ (↔) ⇒ (↔)) (∧) (∧) :=
assume a b h₁ c d h₂, and_congr h₁ h₂

lemma rel_or : ((↔) ⇒ (↔) ⇒ (↔)) (∨) (∨) :=
assume a b h₁ c d h₂, or_congr h₁ h₂

lemma rel_iff : ((↔) ⇒ (↔) ⇒ (↔)) (↔) (↔) :=
assume a b h₁ c d h₂, iff_congr h₁ h₂

lemma rel_eq {r : α → β → Prop} (hr : bi_unique r) : (r ⇒ r ⇒ (↔)) (=) (=) :=
assume a b h₁ c d h₂,
iff.intro
  begin intro h, subst h, exact hr.right h₁ h₂ end
  begin intro h, subst h, exact hr.left h₁ h₂ end

end relator


import .topological_space

namespace hidden

namespace topological_space

variables {α β γ: Type}

open classical

local attribute [instance] classical.prop_decidable

def is_continuous
(X: topological_space α) (Y: topological_space β)
(f: α → β): Prop :=
∀ V : myset β, is_open Y V → is_open X (myset.inverse_image f V)

def is_open_map (X: topological_space α) (Y: topological_space β) (f: α → β): Prop :=
∀ U: myset α, is_open X U → is_open Y (myset.image f U)

-- is this right ???
structure is_homeomorphism
(X: topological_space α) (Y: topological_space β)
(f: α → β) (g: β → α): Prop :=
(f_continuous: is_continuous X Y f)
(g_continuous: is_continuous Y X g)
(right_inv: f ∘ g = id)
(left_inv: g ∘ f = id)

def homeomorphic (X: topological_space α) (Y: topological_space β): Prop :=
∃ (f: α → β) (g: β → α),
is_homeomorphism X Y f g

theorem identity_continuous (X : topological_space α):
is_continuous X X id :=
begin
  intro V,
  assume hoV,
  have: V = myset.inverse_image id V := rfl,
  rw ←this,
  assumption,
end

theorem constant_continuous
(X: topological_space α) (Y: topological_space β)
(y: β):
is_continuous X Y (λ x, y) :=
begin
  intro V,
  assume hoV,
  by_cases hy: y ∈ V, {
    have: (myset.inverse_image (λ (x : α), y) V) = myset.univ,  {
      apply myset.setext,
      intro x,
      split; assume h, {
        trivial,
      }, {
        from hy,
      },
    },
    rw this,
    from X.univ_open,
  }, {
    have: (myset.inverse_image (λ (x : α), y) V) = ∅, {
      apply myset.setext,
      intro x,
      split; assume h, {
        trivial,
      }, {
        exfalso, from h,
      },
    },
    rw this,
    from X.empty_open,
  },
end

theorem inclusion_continuous
(X : topological_space α) (Y: myset α):
is_continuous (subspace_topology X Y) X subtype.val :=
begin
  intro V,
  assume hoV,
  existsi V,
  split, {
    assumption,
  }, {
    refl,
  },
end

theorem s_inclusion_continuous
(X: topological_space α)
(Y: myset α) (Z: myset α) (hYZ: Y ⊆ Z):
is_continuous
  (X.subspace_topology Y) (X.subspace_topology Z)
  (λ y, ⟨y.val, hYZ y y.property⟩) :=
begin
  intro U,
  assume hUo,
  cases hUo with V hV,
  existsi V,
  split, {
    from hV.left,
  }, {
    apply myset.setext,
    intro x,
    split; assume hx, {
      unfold myset.subtype_restriction,
      rw hV.right at hx,
      from hx,
    }, {
      rw hV.right,
      from hx,
    },
  },
end

theorem continuous_to_image
(X : topological_space α) (Y : topological_space β)
(f: α → β):
is_continuous X Y f →
is_continuous X (subspace_topology Y (myset.imageu f))
  (myset.function_restrict_to_image f) :=
begin
  assume hfc,
  intro U,
  assume hUo,
  cases hUo with V hV,
  cases hV with hVo hVU,
  have: (myset.inverse_image (myset.function_restrict_to_image f) U)
        = myset.inverse_image f V, {
    rw hVU,
    refl,
  },
  rw this,
  from hfc V hVo,
end

theorem composition_continuous
(X: topological_space α) (Y: topological_space β)
(Z: topological_space γ)
(f: α → β) (g: β → γ)
(hfc: is_continuous X Y f)
(hgc: is_continuous Y Z g):
is_continuous X Z (g ∘ f) :=
begin
  intro U,
  assume hUo,
  rw myset.inverse_image_composition,
  apply hfc,
  apply hgc,
  assumption,
end

theorem restriction_continuous
(X : topological_space α) (Y: topological_space β) (X': myset α)
(f: α → β) (hfc: is_continuous X Y f):
is_continuous (subspace_topology X X') Y (λ x, f x) :=
begin
  have: ((λ x, f x): (subtype X') → β) = f ∘ ((λ x, x): (subtype X' → α)), {
    apply funext,
    intro x,
    refl,
  },
  rw this,
  from composition_continuous (subspace_topology X X') X Y _ f
    (inclusion_continuous X X') hfc,
end

-- couldn't quite figure out if this is a composition
-- of things we already know about
theorem restrict_set_to_image_continuous
(X : topological_space α) (Y : topological_space β)
(f: α → β) (hfc: is_continuous X Y f) (U: myset α):
(X.subspace_topology U).is_continuous (Y.subspace_topology (myset.image f U))
    (myset.function_restrict_to_set_image f U) :=
begin
  intro V',
  assume hV'o,
  cases hV'o with V hV,
  existsi myset.inverse_image f V,
  split, {
    from hfc _ hV.left,
  }, {
    rw hV.right,
    refl, -- god bless america
  },
end

theorem base_continuous
(X: topological_space α)
(B : myset (myset β)) (hB : is_base B)
(f: α → β):
is_continuous X (space_from_base B hB) f ↔
(∀ W: myset β, B W → X.is_open (myset.inverse_image f W)) :=
begin
  split, {
    assume hcf,
    intro W,
    assume hWB,
    apply hcf,
    intro x,
    assume hxW,
    existsi W,
    split, {
      assumption,
    }, split, {
      assumption,
    }, {
      intro x,
      assume hWx,
      assumption,
    },
  }, {
    assume hWpre,
    intro U,
    assume hUo,
    have := X.open_union_open,
    rw open_iff_neighbourhood_forall,
    intro x,
    assume hxpreU,
    cases hUo (f x) hxpreU with V hV,
    existsi myset.inverse_image f V,
    split, {
      apply hWpre,
      from hV.left,
    }, {
      from hV.right.left,
    }, {
      intro x',
      assume hx',
      apply hV.right.right,
      assumption,
    },
  },
end

theorem projection_continuous
(X : topological_space α) (Y: topological_space β):
is_continuous (product_topology X Y) X prod.fst :=
begin
  intro U,
  assume hUo,
  intro x,
  assume hx,
  existsi U × myset.univ,
  split, {
    existsi U,
    existsi myset.univ,
    split, {
      refl,
    }, {
      assumption,
    }, {
      from Y.univ_open,
    },
  }, split, {
    split, {
      from hx,
    }, {
      trivial,
    },
  }, {
    intro y,
    assume hy,
    from hy.left,
  },
end

theorem swap_continuous
(X : topological_space α) (Y: topological_space β):
is_continuous (product_topology X Y) (product_topology Y X)
  (λ x, (x.snd, x.fst)) :=
begin
  unfold product_topology,
  rw base_continuous,
  intro W,
  assume hWb,
  intro x,
  assume hx,
  cases hWb with U hWb,
  cases hWb with V hWVU,
  existsi V × U,
  split, {
    existsi V,
    existsi U,
    split, refl,
    from hWVU.open_right,
    from hWVU.open_left,
  }, split, {
    rw hWVU.eq at hx,
    from and.comm.mp hx,
  }, {
    intro y,
    assume hy,
    rw hWVU.eq,
    from and.comm.mp hy,
  },
end

theorem projection_2_continuous
(X : topological_space α) (Y: topological_space β):
is_continuous (product_topology X Y) Y prod.snd :=
begin
  have: (prod.snd: α × β → β) = prod.fst ∘ (λ x, (x.snd, x.fst)), {
    apply funext,
    intro x,
    refl,
  },
  rw this,
  apply composition_continuous (product_topology X Y) (product_topology Y X) Y,
  from swap_continuous X Y,
  from projection_continuous Y X,
end

theorem continuous_iff_components_continuous
(X : topological_space α) (Y: topological_space β)
(Z: topological_space γ) (f: α → β × γ):
is_continuous X (product_topology Y Z) f ↔
(is_continuous X Y (prod.fst ∘ f) ∧
 is_continuous X Z (prod.snd ∘ f)) :=
begin
  split, {
    assume hcf,
    split, {
      apply composition_continuous X (product_topology Y Z) Y,
      assumption,
      from projection_continuous Y Z,
    }, {
      apply composition_continuous X (product_topology Y Z) Z,
      assumption,
      from projection_2_continuous Y Z,
    },
  }, {
    assume hc,
    cases hc with hc1 hc2,
    unfold product_topology,
    rw base_continuous,
    intro W,
    assume hW,
    cases hW with U hW,
    cases hW with V hW,
    have: (myset.inverse_image f W)
        = myset.inverse_image (prod.fst ∘ f) U ∩
          myset.inverse_image (prod.snd ∘ f) V, {
      apply myset.setext,
      intro x,
      rw hW.eq,
      refl,
    },
    rw this,
    apply X.open_intersection_open, {
      apply hc1,
      from hW.open_left,
    }, {
      apply hc2,
      from hW.open_right,
    },
  },
end

theorem continuous_iff_closed_preimage
(X: topological_space α) (Y: topological_space β)
(f: α → β):
is_continuous X Y f ↔
(∀ V : myset β, Y.is_closed V → X.is_closed (myset.inverse_image f V)) :=
begin
  split, {
    assume hfc,
    intro V,
    assume hVcl,
    change X.is_open (myset.inverse_image f V.compl),
    apply hfc,
    from hVcl,
  }, {
    assume hfc',
    intro U,
    assume hUo,
    rw ←myset.compl_compl U,
    change X.is_closed (myset.inverse_image f U.compl),
    apply hfc',
    unfold is_closed,
    rw myset.compl_compl,
    assumption,
  },
end

-- question 8 from M&T sheet 1
theorem continuous_iff_closure_subset
(X: topological_space α) (Y: topological_space β)
(f: α → β):
is_continuous X Y f ↔
∀ A: myset α, myset.image f (X.closure A) ⊆ Y.closure (myset.image f A) :=
begin
  split, {
    assume hfc,
    intro A,
    -- transitivity myset.image f (myset.inverse_image f (Y.closure (myset.image f A))),
    -- wtf is its problem
    -- I think it has something to do with metavariables depending on each other
    apply myset.subset_trans _
        (myset.image f (myset.inverse_image f (Y.closure (myset.image f A))))
        (Y.closure (myset.image f A)), {
      apply myset.image_subset,
      -- should be inferable
      apply closure_minimal_closed_superset X A (myset.inverse_image f (Y.closure (myset.image f A))), {
        unfold closure,
        rw myset.inverse_image_sIntersection,
        apply closed_intersection_closed,
        intro B,
        assume hB,
        cases hB with B' hB',
        rw ←hB'.right,
        rw continuous_iff_closed_preimage at hfc,
        apply hfc,
        from hB'.left.left,
      }, {
        -- transitivity (myset.inverse_image f (myset.image f A)).
        apply myset.subset_trans _
            (myset.inverse_image f (myset.image f A))
            (myset.inverse_image f (Y.closure (myset.image f A))), {
          apply myset.inverse_image_of_image,
        }, {
          -- should be inferable
          apply myset.inverse_image_subset f (myset.image f A) ((Y.closure (myset.image f A))),
          apply self_subset_closure,
        },
      },
    }, {
      -- should be inferable
      apply myset.image_of_inverse_image f (Y.closure (myset.image f A)),
    },
  }, {
    assume hclosure_subset,
    rw continuous_iff_closed_preimage,
    intro A,
    assume hAc,
    rw ←closure_idem_iff_closed,
    apply myset.setext_subs, {
      transitivity myset.inverse_image f (myset.image f (X.closure (myset.inverse_image f A))), {
        apply myset.inverse_image_of_image,
      }, {
        apply myset.inverse_image_subset,
        apply myset.subset_trans _ _ A (hclosure_subset _),
        conv {
          to_rhs,
          rw ←(closure_idem_iff_closed Y A).mpr hAc,
        },
        apply closure_subset,
        apply myset.image_of_inverse_image,
      },
    }, {
      apply self_subset_closure,
    },
  },
end

theorem surjective_image_dense
(X: topological_space α) (Y: topological_space β)
(A: myset α) (hAd: X.is_dense A)
(f: α → β) (hfc: is_continuous X Y f)
(hsurj: function.surjective f):
Y.is_dense (myset.image f A) :=
begin
  apply myset.setext_subs, {
    from λ _ _, trivial,
  }, {
    intro y,
    assume hyt, clear hyt,
    apply (continuous_iff_closure_subset X Y f).mp hfc,
    unfold is_dense at hAd,
    rw hAd,
    cases hsurj y with x hx,
    rw ←hx,
    existsi x,
    split, {
      trivial,
    }, {
      refl,
    },
  },
end

theorem hausdorff_dense_determinism
(X: topological_space α) (Y: topological_space β)
(haus: is_hausdorff Y)
(A: myset α) (hAd: X.is_dense A)
(f g: α → β) (hfc: is_continuous X Y f) (hgc: is_continuous X Y g)
(hagree: ∀ a: α, a ∈ A → f a = g a):
f = g :=
begin
  have hagreecl: X.is_closed {x | f x = g x}, {
    have: is_continuous X (product_topology Y Y) (λ x, (f x, g x)), {
      rw continuous_iff_components_continuous,
      split, {
        from hfc,
      }, {
        from hgc,
      },
    },
    rw continuous_iff_closed_preimage at this,
    apply this {yy | yy.fst = yy.snd},
    rw hausdorff_iff_diagonal_closed at haus,
    from haus,
  },
  rw ←closure_idem_iff_closed at hagreecl,
  have heverywhere: {x : α | f x = g x} = myset.univ, {
    apply myset.setext_subs, {
      from λ _ _, trivial,
    }, {
      transitivity X.closure A, {
        unfold is_dense at hAd,
        rw hAd,
      }, {
        rw ←hagreecl,
        apply closure_subset,
        intro a,
        assume ha,
        from hagree a ha,
      },
    },
  },
  apply funext,
  intro x,
  change x ∈ {x : α | f x = g x},
  rw heverywhere,
  trivial,
end

theorem gluing_lemma
(X: topological_space α) (Y: topological_space β)
(U V: myset α) (hUc: X.is_closed U) (hVc: X.is_closed V)
(hUVcov: U ∪ V = myset.univ) (f: α → β):
is_continuous (subspace_topology X U) Y (λ x, f x) →
is_continuous (subspace_topology X V) Y (λ x, f x) →
is_continuous X Y f :=
begin
  repeat {rw continuous_iff_closed_preimage},
  assume hfUc hfVc,
  intro W,
  assume hWcl,
  cases hfUc W hWcl with U' hU',
  cases hfVc W hWcl with V' hV',
  have:
      (myset.inverse_image f W) =
        (myset.subtype_unrestriction U
          (@myset.inverse_image (subtype U) _ (λ x, f x) W)) ∪
        (myset.subtype_unrestriction V
          (@myset.inverse_image (subtype V) _ (λ x, f x) W)), {
    apply myset.setext,
    intro x,
    split, {
      assume hfWx,
      have: x ∈ U ∪ V, {
        rw hUVcov,
        trivial,
      },
      cases this with hU hV, {
        left,
        existsi hU,
        from hfWx,
      }, {
        right,
        existsi hV,
        from hfWx,
      },
    }, {
      assume hUVx,
      cases hUVx with hU hV, {
        cases hU with hxU hU,
        from hU,
      }, {
        cases hV with hxV hV,
        from hV,
      },
    },
  },
  rw this,
  apply X.closed_union_closed, {
    apply closed_in_closed X U hUc,
    apply hfUc,
    assumption,
  }, {
    apply closed_in_closed X V hVc,
    apply hfVc,
    assumption,
  },
end

theorem homeomorphism_refl
(X: topological_space α):
is_homeomorphism X X id id :=
begin
  split, {
    from identity_continuous X,
  }, {
    from identity_continuous X,
  }, {
    -- don't question it. I have no idea why this doesn't need
    -- extensionality
    refl,
  }, {
    refl,
  },
end

theorem homeomorphism_symm
(X: topological_space α) (Y: topological_space β)
(f: α → β) (g: β → α) (h_omeom: is_homeomorphism X Y f g):
is_homeomorphism Y X g f :=
begin
  cases h_omeom,
  split; assumption,
end

theorem homeomorphism_trans
(X: topological_space α) (Y: topological_space β)
(Z: topological_space γ) (f: α → β) (g: β → α)
(f': β → γ) (g': γ → β)
(hfghom: is_homeomorphism X Y f g)
(hfg'hom: is_homeomorphism Y Z f' g'):
is_homeomorphism X Z (f' ∘ f) (g ∘ g') :=
begin
  cases hfghom,
  cases hfg'hom,
  split, {
    apply composition_continuous _ Y; assumption,
  }, {
    apply composition_continuous _ Y; assumption,
  }, {
    change f' ∘ (f ∘ g) ∘ g' = id,
    rw hfghom_right_inv,
    change f' ∘ g' = id,
    assumption,
  }, {
    change g ∘ (g' ∘ f') ∘ f = id,
    rw hfg'hom_left_inv,
    change g ∘ f = id,
    assumption,
  },
end

theorem homeomorphism_open
(X: topological_space α) (Y: topological_space β)
(f: α → β) (g: β → α) (h_omeom: is_homeomorphism X Y f g):
is_open_map X Y f :=
begin
  intro U,
  assume hUo,
  -- candidate for myset theorem?
  have: myset.image f U = myset.inverse_image g U, {
    apply myset.setext,
    intro x,
    split; assume h, {
      cases h with y hy,
      cases hy with hyU hxfy,
      rw ←hxfy,
      change id y ∈ U at hyU,
      rw ←h_omeom.left_inv at hyU,
      from hyU,
    }, {
      existsi g x,
      split, {
        from h,
      }, {
        change (f ∘ g) x = x,
        rw h_omeom.right_inv,
        refl,
      },
    },
  },
  rw this,
  apply h_omeom.g_continuous,
  assumption,
end

theorem swap_homeomorphism
(X : topological_space α) (Y: topological_space β):
is_homeomorphism (product_topology X Y) (product_topology Y X)
  (λ x, (x.snd, x.fst))
  (λ x', (x'.snd, x'.fst)) :=
begin
  split, {
    apply swap_continuous,
  }, {
    apply swap_continuous,
  }, {
    apply funext,
    intro x,
    cases x,
    refl,
  }, {
    apply funext,
    intro x',
    cases x',
    refl,
  },
end

theorem composition_open
(X: topological_space α) (Y: topological_space β) (Z: topological_space γ)
(f: α → β) (g: β → γ) (hfo: is_open_map X Y f) (hgo: is_open_map Y Z g):
is_open_map X Z (g ∘ f) :=
begin
  intro U,
  assume hUo,
  rw myset.image_composition,
  apply hgo,
  apply hfo,
  assumption,
end

theorem base_open
(B : myset (myset α)) (hB: is_base B)
(Y: topological_space β) (f: α → β):
is_open_map (space_from_base B hB) Y f ↔
(∀ W: myset α, W ∈ B → Y.is_open (myset.image f W)) :=
begin
  split; assume h, {
    intro W,
    assume hWB,
    apply h,
    apply basis_set_open,
    assumption,
  }, {
    intro U,
    assume hUo,
    rw open_iff_neighbourhood_forall,
    intro x,
    assume hximf,
    cases hximf with y hy,
    cases hUo y hy.left with V hV,
    existsi (myset.image f V),
    split, {
      apply h,
      from hV.left,
    }, {
      existsi y,
      split, {
        from hV.right.left,
      }, {
        from hy.right,
      },
    }, {
      apply myset.image_subset,
      from hV.right.right,
    },
  },
end

theorem projection_open
(X : topological_space α) (Y: topological_space β):
is_open_map (product_topology X Y) X prod.fst :=
begin
  unfold product_topology,
  rw base_open,
  intro W,
  assume hWB,
  cases hWB with U hWB,
  cases hWB with V hWUV,
  -- so we don't have to worry about V being empty or not
  rw open_iff_neighbourhood_forall,
  intro x,
  assume hxW,
  existsi U,
  split, {
    from hWUV.open_left,
  }, {
    cases hxW with y hy,
    rw ←hy.right,
    rw hWUV.eq at hy,
    from hy.left.left,
  }, {
    intro y,
    assume hUy,
    rw hWUV.eq,
    cases hxW with z hz,
    existsi (⟨y, z.snd⟩: α × β),
    split, {
      split, {
        from hUy,
      }, {
        rw hWUV.eq at hz,
        from hz.left.right,
      },
    }, {
      refl,
    },
  },
end

theorem projection_2_open
(X : topological_space α) (Y: topological_space β):
is_open_map (product_topology X Y) Y prod.snd :=
begin
  have :=
    composition_open
      (product_topology X Y) (product_topology Y X)
      Y (λ x: α × β, ⟨x.snd, x.fst⟩) prod.fst
      (homeomorphism_open _ _ _ _
        (swap_homeomorphism _ _))
      (projection_open _ _),
  have hrw: (prod.fst ∘ λ (x : α × β), (x.snd, x.fst)) = prod.snd, {
    apply funext,
    intro x,
    refl,
  },
  rw ←hrw,
  assumption,
end

theorem to_indiscrete_continuous (X: topological_space α) (f: α → β):
is_continuous X (indiscrete_topology β) f :=
begin
  intro U,
  assume hUo,
  cases hUo with hU hU; rw hU, {
    from X.empty_open,
  }, {
    from X.univ_open,
  },
end

theorem to_discrete_open (X: topological_space α) (f: α → β):
is_open_map X (discrete_topology β) f :=
begin
  intro U,
  assume hUo,
  trivial,
end

theorem from_discrete_continuous (Y: topological_space β) (f: α → β):
is_continuous (discrete_topology α) Y f :=
begin
  intro U,
  assume hUo,
  trivial,
end

theorem from_indiscrete_open_to_image (Y: topological_space β) (f: α → β):
is_open_map (indiscrete_topology α) (subspace_topology Y (myset.imageu f))
  (myset.function_restrict_to_image f) :=
begin
  intro U,
  assume hUo,
  cases hUo with hU hU, {
    rw hU,
    -- weird function signature, probably my fault
    rw myset.image_empty (myset.function_restrict_to_image f) ∅ rfl,
    apply topological_space.empty_open,
  }, {
    rw hU,
    rw myset.to_image_surjective,
    apply topological_space.univ_open,
  },
end

theorem univ_homeomorphism
(X : topological_space α):
is_homeomorphism X (X.subspace_topology myset.univ)
  (λ x, ⟨x, trivial⟩) subtype.val :=
begin
  split, {
    -- is this a theorem already? idc anymore
    intro U,
    assume hUo,
    cases hUo with V hV,
    rw hV.right,
    from hV.left,
  }, {
    apply inclusion_continuous,
  }, {
    apply funext,
    intro x,
    apply subtype.eq,
    refl,
  }, {
    refl,
  },
end

end topological_space

end hidden


import Rings.Rings
import Rings.Fields
import Rings.ToMathlib.list
import Rings.ToMathlib.nat

namespace AxGroth

noncomputable theory

universe u

open fol
open Rings

def list_image_append {A : Type u} [decidable_eq A] {n} :
  A → list (fin n → A) → list (fin n.succ → A) :=
λ x, list.map (λ xs k, dite (↑k < n) (λ h, xs ⟨ k , h ⟩) (λ _ , x))

/-- lists all n-variable monomials of degree d -/
@[simp] def n_var_monomials_of_deg : Π (n d : ℕ), list (fin n → ℕ)
| 0 d := [fin_zero_elim]
| 1 d := [λ k, d]
| (n + 2) 0 := [λ k, 0]
| (n + 2) (d + 1) := list.join $ list.of_fn $
  λ i : fin (d + 2), list_image_append i (n_var_monomials_of_deg (n + 1) (d + 1 - i))

/-- lists all n-variable monomials of degree ≤ d -/
@[simp] def n_var_monomials_of_deg_le (n d : ℕ) : list (fin n → ℕ) :=
nat.natlist d.succ (λ d', n_var_monomials_of_deg n d')

-- counts all n-variable monomials of degree ≤ d
@[simp] def n_var_monomials_of_deg_le_length (n d : ℕ) : ℕ :=
list.length $ n_var_monomials_of_deg_le n d

def finsupp_to_nat_of_fin_to_nat {n : ℕ} (p : fin n → ℕ) :
  fin n →₀ ℕ :=
⟨ {k ∈ finset.fin_range n | p k ≠ 0 } , p ,
begin
  intro a,
  split,
  {intro ha, simpa using ha},
  {intro ha, simpa using ha}
end ⟩

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

-- no restriction on degree
/-- takes a polynomial in n.succ 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) (n_var_monomials_of_deg_le₀ n d)

section prop_decidable

local attribute [instance] classical.prop_decidable

namespace list

def fin_index_of {α : Type u} (a : α) :
  Π (l : list α), (a ∈ l) → fin (list.length l)
| [] h :=
begin
  exfalso,
  rw ← list.mem_nil_iff a,
  apply h,
end
| (hd :: l) h := ⟨ list.index_of a (hd :: l) ,
begin rw list.index_of_lt_length, apply h end ⟩

-- λ a f, ⟨ list.index_of a f , (by rw list.index_of_lt_length; simp) ⟩

end list

lemma poly_indexed_by_monos_aux (n d s c : ℕ)
  (hndc : (n_var_monomials_of_deg_lt n d).length + s < c) (f : fin n → ℕ) :
  list.index_of f (n_var_monomials_of_deg_lt n d) + s < c :=
begin
  apply nat.lt_of_le_of_lt _ hndc,
  apply nat.add_le_add_right list.index_of_le_length,
end

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 ∈ n_var_monomials_of_deg n d} xⱼ₊ₛ ∏ {0 ≤ i < n} xᵢ₊ₚᶠ⁽ⁱ⁾ in "context c"
-- where j is the index of f in n_var_monomials_of_deg 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_monos (n d s p c : ℕ)
  (hndc : (n_var_monomials_of_deg_lt n d).length + s < c)
  (hnpc : n + p ≤ c) :
  bounded_ring_term c :=
-- sum indexed by the n-variable monomials of degree < d
list.sumr
(list.mapr
  (λ f : (fin n → ℕ),
    let
      x_js : bounded_ring_term c :=
      x_ ⟨(list.index_of f (n_var_monomials_of_deg_lt n d) + s) ,
      poly_indexed_by_monos_aux n d s c hndc 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 * (nat.prod n $ λ i, (x_ip i) ^ (f i) )
    )
  (n_var_monomials_of_deg_lt n d)
)

#exit
-- -- ∑ {f ∈ n_var_monomials_of_deg n.succ d} x₍ⱼ₊ₛ₎ ∏ {0 ≤ i < n} x₍ᵢ₊ₚ₎ᶠ⁽ⁱ⁾ in "context c"
-- -- where j is the index of f in n_var_monomials_of_deg n.succ d
-- @[simp] def poly_indexed_by_monos (n d s p c : ℕ) (h : 0 < c) :
--   bounded_ring_term c :=
-- begin
--   rw ← nat.succ_pred_eq_of_pos h,
--   apply poly_indexed_by_monos' n d s p,
-- end

-- NOTE s = 2 * n.succ
-- NOTE c = n.succ * n_var_monomials_of_deg_lt n.succ d + 2 * n.succ

end prop_decidable

lemma inj_formula_aux {n d : ℕ} :
  0 < n.succ * n_var_monomials_of_deg_lt_length n d + n.succ + n.succ :=
begin
  apply nat.lt_add_left 0 (n.succ) _ ,
  simp,
end

-- in the context of having n.succ 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
def inj_formula (n d : ℕ) :
  bounded_ring_formula (n.succ * (n_var_monomials_of_deg_lt_length n d)) :=
let c := n.succ * (n_var_monomials_of_deg_lt_length n d) + 2 * n.succ,
    monom := n_var_monomials_of_deg_lt_length n d in
-- for all pairs in the domain x₋ ∈ Kⁿ⁺¹ and ...
bd_alls' n.succ _
$
-- ... y₋ ∈ Kⁿ⁺¹
bd_alls' n.succ _
$
-- if at each pⱼ
(bd_big_and n.succ
-- pⱼ xᵢ = pⱼ yᵢ
  (λ j,
    (poly_indexed_by_monos n.succ d (2 * n.succ + j * monom) 0 _)
    ≃
    (poly_indexed_by_monos n.succ d (2 * n.succ + j * monom) (n.succ) _)
  )
)
-- then
⟹
-- at each 0 ≤ i < n.succ,
(bd_big_and n.succ $ λ i,
-- xᵢ = yᵢ (where yᵢ is written as xᵢ₊ₙ₊₁)
  x_ i ≃ x_ (i + n.succ)
)

def surj_formula (n d : ℕ) :
  bounded_ring_formula (n.succ * n_var_monomials_of_deg_lt_length n d) :=
let monom := n_var_monomials_of_deg_lt_length n d in
-- for all z₋ ∈ Kⁿ⁺¹ in the codomain
bd_alls' n.succ _
$
-- there exists x₋ ∈ Kⁿ⁺¹ in the domain such that
bd_exs' n.succ _
$
-- at each 0 ≤ j < n.succ
bd_big_and n.succ
$
-- zⱼ = pⱼ x₋
λ j, x_ j ≃ poly_indexed_by_monos n.succ d (n.succ + n.succ + j * monom) 0 _

def Ax_Groth_formula (n d : ℕ) : sentence ring_signature :=
-- quantify over (n.succ) many (n.succ-variable polynomials) called ps;
-- i.e. the data of a polynomial map
-- by quantifying over (n.succ * monomials_of_bounded_degree) monomial coefficients
bd_alls (n.succ * (n_var_monomials_of_deg_lt_length n d))
-- if the polynomial function is injective then it is surjective
$ inj_formula n d ⟹ surj_formula n d

@[simp] def poly_map_data (K : Type) [comm_semiring K] (n : ℕ) : Type :=
fin n → mv_polynomial (fin n) K

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)

lemma realize_Ax_Groth_formula {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

------------
def dvector.of_list {A : Type} : Π (as : list A), dvector A (list.length as)
| list.nil := []
| (list.cons a as) := dvector.cons a (dvector.of_list as)

-- #check list.length_map

-- def coeffs_dvector_of_mv_polynomial {K : Type} [comm_semiring K] {n d : ℕ}
--   (p : mv_polynomial (fin n.succ) K) (hdeg : mv_polynomial.total_degree p < d) :
--   dvector K (list.length (coeffs_list_of_mv_polynomial d p)) :=
-- dvector.of_list (coeffs_list_of_mv_polynomial d p)
-- ------------

-- #check @list.of_fn
-- #eval list.join (list.cons (list.cons 0 list.nil) (list.cons (list.cons 1 list.nil) list.nil))

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.succ) : list A :=
list.join (list.of_fn (λ i : fin n.succ, 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.succ) :
  dvector A (poly_map_data.coeffs_list d ps).length :=
dvector.of_list (poly_map_data.coeffs_list d ps)

lemma list.map_length_of_fn_const {α} {m : ℕ} (f : fin n → list α)
  (h : ∀ i : fin n, (f i).length = m) :
  (list.map list.length (list.of_fn f)).sum = n * m :=
begin
 rw list.map_of_fn,
 rw list.sum_of_fn,
 have h' : (λ (i : fin n), (list.length ∘ f) i) = λ (i : fin n), m,
 {funext, exact h i},
 rw h',
 simp,
end

lemma coeffs_list_of_mv_polynomial_const {d : ℕ}
  (ps : poly_map_data A n.succ) (i : fin n.succ) :
  (coeffs_list_of_mv_polynomial d (ps i)).length = n_var_monomials_of_deg_lt_length n d :=
by simp

lemma variable_bound_equal {n : ℕ} (d : ℕ) (ps : poly_map_data A n.succ) :
  (n.succ * n_var_monomials_of_deg_lt_length n d) = (poly_map_data.coeffs_list d ps).length :=
begin
  simp only [poly_map_data.coeffs_list, list.length_join],
  rw list.map_length_of_fn_const _ (coeffs_list_of_mv_polynomial_const ps),
end

/- Writes polynomial map into a dvector of its coefficients (with a better variable context) -/
lemma poly_map_data.coeffs_dvector' {n : ℕ} (d : ℕ)
  (ps : poly_map_data A n.succ) :
  dvector A (n.succ * n_var_monomials_of_deg_lt_length n d) :=
begin
  have xs0 := poly_map_data.coeffs_dvector d ps,
  rw ← variable_bound_equal d ps at xs0,
  exact xs0,
end

open mv_polynomial

-- (X₀ + 1 , X₁ + 1)
-- def example_poly_map : poly_map_data ℤ 2 := λ k, X k + 1

end semiring

-- -- move to ToMathlib
-- def bd_pis {A : Type u} : Π (n : ℕ) (P : (fin n → A) → Prop), Prop
-- | nat.zero P := P fin_zero_elim
-- | (nat.succ n) P := Π (a : A), bd_pis n $ λ as, P $ fin.cons a as

-- lemma realize_bounded_formula_bd_big_and' {L} {S : Structure L} {n m : ℕ}
--   {v : dvector S n} (f : fin m.succ → bounded_formula L n) :
--   realize_bounded_formula v (bd_big_and m.succ f) dvector.nil
--   ↔
--   (realize_bounded_formula v (bd_big_and m (λ k, f k)) dvector.nil
--   ∧
--   realize_bounded_formula v (f m) dvector.nil) :=
-- begin
--   simp only [bd_big_and, realize_bounded_formula_and],
-- end

@[simp] lemma realize_bounded_formula_bd_big_and {L} {S : Structure L} {n : ℕ}
  {v : dvector S n} : Π {m : ℕ} (f : fin m → bounded_formula L n),
  realize_bounded_formula v (bd_big_and m f) dvector.nil
  ↔
  (Π k : fin m, realize_bounded_formula v (f k) dvector.nil)
| nat.zero f :=
begin
  simp only [bd_big_and, realize_bounded_formula_not,
    realize_bounded_formula, true_iff, not_false_iff],
  exact fin_zero_elim,
end
| (nat.succ m) f :=
begin
  simp only [bd_big_and, realize_bounded_formula_and],
  split,
  {
    intros hf k,
    rw realize_bounded_formula_bd_big_and at hf,
    cases fin.lt_or_eq_fin k with hk,
    -- by_cases hk : (k : ℕ) < m,
    {
      have h :=  hf.1 ⟨ k , _ ⟩,
      {simpa using h},
      {
        rw fin.lt_coe_iff_val_lt,
        exact hk,
        exact lt_add_one m,
      },
    },
    {rw h, exact hf.2}
  },
  {
    intro hf,
    split,
    {
      rw realize_bounded_formula_bd_big_and,
      intro k,
      apply hf k,
    },
    {exact hf m}
  },
end

-- #check list.sum

@[simp] lemma realize_bounded_term_list_sumr
  {A : Structure ring_signature} {c : ℕ} {xs : dvector A c} :
  Π {l : list (bounded_ring_term c)},
  realize_bounded_term xs (list.sumr l) dvector.nil
  =
  list.sumr (list.mapr (λ t, realize_bounded_term xs t dvector.nil) l)
| list.nil := by simp
| (list.cons t ts) :=
begin
  simp only [list.mapr, models_ring_theory_to_comm_ring.realize_add,
    ring_signature.add, list.sumr, realize_bounded_term],
  rw realize_bounded_term_list_sumr,
end

-- list.sumr
-- (list.mapr
--   (λ f : (fin n.succ → ℕ),
--     (x_ (list.index_of f (n_var_monomials_of_deg_lt n d) + s))
--     *
--     (nat.prod n.succ $ λ i, (x_ (i + p)) ^ (f i) )
--     )
--   (n_var_monomials_of_deg_lt n d)
-- )

def realize_add_zero_hom {A : Type*} [comm_ring A]
  {c : ℕ} (xs : dvector (struc_to_ring_struc.Structure A) c):
  add_zero_hom (bounded_ring_term c) A :=
⟨ λ t, realize_bounded_term xs t dvector.nil ,
  models_ring_theory_to_comm_ring.realize_zero ,
  λ t s, models_ring_theory_to_comm_ring.realize_add ⟩

lemma realize_sumr {A : Type*} [comm_ring A]
  {c : ℕ} (xs : dvector (struc_to_ring_struc.Structure A) c)
  {ts : list (bounded_ring_term c)} :
  realize_bounded_term xs (ts).sumr dvector.nil
  =
  (list.mapr (realize_add_zero_hom xs).to_fun ts).sumr :=
begin
  rw ← list.mapr_sumr (realize_add_zero_hom xs) ts,
  refl,
end

lemma realize_nat_prod {A : Type*} [comm_ring A]
  {c : ℕ} (xs : dvector (struc_to_ring_struc.Structure A) c)
  (ts : ℕ → bounded_ring_term c) : Π n,
  realize_bounded_term xs (nat.prod n ts) dvector.nil
  =
  nat.prod n (λ n, realize_bounded_term xs (ts n) dvector.nil)
| nat.zero :=
begin
  simp only [nat.prod],
  refl,
end
| (nat.succ n) :=
begin
  simp only [nat.prod, struc_to_ring_struc.func_map, dvector.last, struc_to_ring_struc.binaries_map, realize_bounded_term,
  ring_signature.mul, dvector.nth],
  rw realize_nat_prod n,
end

lemma realize_pow {A : Type*} [comm_ring A]
  {c : ℕ} (xs : dvector (struc_to_ring_struc.Structure A) c)
  (t : bounded_ring_term c) : Π (n : ℕ),
  realize_bounded_term xs (t ^ n) dvector.nil
  =
  (realize_bounded_term xs t dvector.nil) ^ n
| nat.zero := by
  simpa only [nat.nat_zero_eq_zero, ring_signature.pow_zero,
    realize_bounded_term, models_ring_theory_to_comm_ring.realize_one,
    ring_signature.one, models_ring_theory_to_comm_ring.realize_one]
| (nat.succ n) :=
begin
  simp only [struc_to_ring_struc.func_map, dvector.last, ring_signature.pow_succ, struc_to_ring_struc.binaries_map,
  realize_bounded_term, ring_signature.mul, dvector.nth],
  rw realize_pow n,
  refl,
end

lemma realize_poly_indexed_by_monos
  {A : Type*} [comm_ring A] {n d s p c : ℕ}
  (hcf : Π (f : fin n.succ → ℕ), list.index_of f (n_var_monomials_of_deg_lt n d) + s < c.succ)
  (hci : Π (i : ℕ), i + p < c.succ)
  {xs : dvector (struc_to_ring_struc.Structure A) c.succ}  :
  realize_bounded_term xs (poly_indexed_by_monos n d s p c) dvector.nil
  =
  list.sumr
  (list.mapr
    (λ f,
    (dvector.nth xs (list.index_of f (n_var_monomials_of_deg_lt n d) + s) (hcf f))
    *
    (nat.prod n.succ $ λ i, ((dvector.nth xs (i + p) (hci i)) ^ (f i) ))
    )
  (n_var_monomials_of_deg_lt n d)
  ) :=
begin
  simp only [poly_indexed_by_monos],
  rw realize_sumr,
  rw ← list.comp_mapr,
  congr,
  funext f,
  simp only [realize_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,
  {
    norm_cast,
    simp only [fin.val_eq_coe, fin.coe_of_nat_eq_mod],
    rw ← nat.mod_eq_of_lt (hcf f),
    congr,
  },
  {
    rw realize_nat_prod,
    congr,
    funext i,
    rw realize_pow,
    simp,
    norm_cast,
    congr,
    simp only [fin.val_eq_coe, fin.coe_of_nat_eq_mod],
    rw nat.mod_eq_of_lt (hci i),
  },
end

lemma realize_poly_map_data_coeffs_xs
  {A : Type*} [comm_ring A] {n d : ℕ}
  (ps : poly_map_data A n.succ)
  (hdeg : ∀ (i : fin n.succ), (ps i).total_degree < d)
  (xs ys : dvector ↥(struc_to_ring_struc.Structure A) n.succ)
  (i : fin n.succ)
  :
  realize_bounded_term (ys.append (xs.append (poly_map_data.coeffs_dvector' d ps)))
      (poly_indexed_by_monos n.succ d
        (2 * n.succ + ↑i * (d.natlist (n_var_monomials_of_deg n.succ)).length)
        0
        (n.succ * n_var_monomials_of_deg_lt_length n d + n.succ + n))
      dvector.nil
  =
  mv_polynomial.eval (λ (i : fin n.succ), xs.nth i i.2) (ps i) :=
begin
  rw realize_poly_indexed_by_monos,
  { sorry },
  {
    intro f, -- f is a monomial
    simp only [n_var_monomials_of_deg_lt],
    sorry,
  },
  {
    sorry
  },
end

lemma Ax_Groth_inj_aux {K : Type} [field K] [is_alg_closed K]
  (h0 : char_zero K)
  {n d : ℕ}
  (ps : poly_map_data K n.succ)
  (hdeg : ∀ (i : fin n.succ), (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
  simp only [inj_formula],
  rw realize_bounded_formula_bd_alls',
  intro xs, -- an n tuple in the domain
  rw realize_bounded_formula_bd_alls',
  intro ys, -- an n tuple in the domain
  simp only [realize_bounded_formula_imp],
  -- we are showing that ps xs = ps yx implies xs = ys
  -- assume the images are all equal (expressed model theoretically)
  intro hImage,
  rw realize_bounded_formula_bd_big_and,
  rw realize_bounded_formula_bd_big_and at hImage,
  -- 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 i, -- for each i < n (... the tuples at i are equal)
    simp only [poly_map],
    have hImagei := hImage i,
    simp only [n_var_monomials_of_deg_lt_length, realize_bounded_formula,
      n_var_monomials_of_deg_lt] at hImagei,
    convert hImagei,
    sorry,
    sorry,
  },
  intro k, -- for each input (... they are equal)
  -- ... they are equal
  {sorry},
end

-- realize_bounded_term (ys.append (xs.append xs0))
      -- (poly_indexed_by_monos n.succ d (2 * n.succ + ↑i * (nat.natlist d (n_var_monomials_of_deg n.succ)).length) 0
      --    (n.succ * n_var_monomials_of_deg_lt_length n d + n.succ + n.succ)
      --    inj_formula_aux)
      -- dvector.nil
      --
-- ⇑(mv_polynomial.eval (λ (i : fin n.succ), xs.nth ↑i _)) (ps i)

lemma Ax_Groth_aux {K : Type} [field K] [is_alg_closed K]
  (h0 : char_zero K) {n d : ℕ}
  (ps : poly_map_data K n.succ) (hdeg : ∀ (i : fin n.succ), mv_polynomial.total_degree (ps i) < d)
  (hinj : function.injective (poly_map ps)) :
  function.surjective (poly_map ps) :=
begin
  let xs0 := poly_map_data.coeffs_dvector' d ps,
  have hAG := realize_Ax_Groth_formula h0 n d,
  simp only [realize_sentence_bd_alls,
    Ax_Groth_formula, realize_bounded_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 h0 ps hdeg hinj},
  -- apply realize_Ax_Groth to ps, i.e. apply hAG to its coefficients
  have hSurj := hAG xs0 hInj,
  sorry,
end

@[simp] def max_total_deg {K : Type} [comm_semiring K] :
  Π {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 {K : Type} [comm_semiring K] :
  Π {n m : ℕ} (ps : fin n → mv_polynomial (fin m) K) (i : fin n),
  mv_polynomial.total_degree (ps i) < max_total_deg ps + 1
| 0 _ ps i := fin_zero_elim i
| (n + 1) _ ps i :=
begin
  rw nat.lt_add_one_iff,
  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 :=
    nat.le_of_lt_succ (@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

theorem Ax_Groth {K : Type} [field K] [is_alg_closed K]
  (h0 : char_zero K) {n : ℕ}
  (ps : poly_map_data K n.succ) (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 AxGroth


/-
Copyright (c) 2018 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot, Johannes Hölzl
-/
import analysis.normed.field.basic
import analysis.normed.group.infinite_sum
import data.matrix.basic
import topology.sequences

/-!
# Normed spaces

In this file we define (semi)normed spaces and algebras. We also prove some theorems
about these definitions.
-/

variables {α : Type*} {β : Type*} {γ : Type*} {ι : Type*}

noncomputable theory
open filter metric function set
open_locale topological_space big_operators nnreal ennreal uniformity pointwise

section semi_normed_group

section prio
set_option extends_priority 920
-- Here, we set a rather high priority for the instance `[normed_space α β] : module α β`
-- to take precedence over `semiring.to_module` as this leads to instance paths with better
-- unification properties.
/-- A normed space over a normed field is a vector space endowed with a norm which satisfies the
equality `∥c • x∥ = ∥c∥ ∥x∥`. We require only `∥c • x∥ ≤ ∥c∥ ∥x∥` in the definition, then prove
`∥c • x∥ = ∥c∥ ∥x∥` in `norm_smul`.

Note that since this requires `semi_normed_group` and not `normed_group`, this typeclass can be
used for "semi normed spaces" too, just as `module` can be used for "semi modules". -/
class normed_space (α : Type*) (β : Type*) [normed_field α] [semi_normed_group β]
  extends module α β :=
(norm_smul_le : ∀ (a:α) (b:β), ∥a • b∥ ≤ ∥a∥ * ∥b∥)
end prio

variables [normed_field α] [semi_normed_group β]

@[priority 100] -- see Note [lower instance priority]
instance normed_space.has_bounded_smul [normed_space α β] : has_bounded_smul α β :=
{ dist_smul_pair' := λ x y₁ y₂,
    by simpa [dist_eq_norm, smul_sub] using normed_space.norm_smul_le x (y₁ - y₂),
  dist_pair_smul' := λ x₁ x₂ y,
    by simpa [dist_eq_norm, sub_smul] using normed_space.norm_smul_le (x₁ - x₂) y }

instance normed_field.to_normed_space : normed_space α α :=
{ norm_smul_le := λ a b, le_of_eq (norm_mul a b) }

lemma norm_smul [normed_space α β] (s : α) (x : β) : ∥s • x∥ = ∥s∥ * ∥x∥ :=
begin
  by_cases h : s = 0,
  { simp [h] },
  { refine le_antisymm (normed_space.norm_smul_le s x) _,
    calc ∥s∥ * ∥x∥ = ∥s∥ * ∥s⁻¹ • s • x∥     : by rw [inv_smul_smul₀ h]
               ... ≤ ∥s∥ * (∥s⁻¹∥ * ∥s • x∥) :
      mul_le_mul_of_nonneg_left (normed_space.norm_smul_le _ _) (norm_nonneg _)
               ... = ∥s • x∥                 :
      by rw [norm_inv, ← mul_assoc, mul_inv_cancel (mt norm_eq_zero.1 h), one_mul] }
end

@[simp] lemma abs_norm_eq_norm (z : β) : |∥z∥| = ∥z∥ :=
  (abs_eq (norm_nonneg z)).mpr (or.inl rfl)

lemma inv_norm_smul_mem_closed_unit_ball [normed_space ℝ β] (x : β) :
  ∥x∥⁻¹ • x ∈ closed_ball (0 : β) 1 :=
by simp only [mem_closed_ball_zero_iff, norm_smul, norm_inv, norm_norm, ← div_eq_inv_mul,
  div_self_le_one]

lemma dist_smul [normed_space α β] (s : α) (x y : β) : dist (s • x) (s • y) = ∥s∥ * dist x y :=
by simp only [dist_eq_norm, (norm_smul _ _).symm, smul_sub]

lemma nnnorm_smul [normed_space α β] (s : α) (x : β) : ∥s • x∥₊ = ∥s∥₊ * ∥x∥₊ :=
nnreal.eq $ norm_smul s x

lemma nndist_smul [normed_space α β] (s : α) (x y : β) :
  nndist (s • x) (s • y) = ∥s∥₊ * nndist x y :=
nnreal.eq $ dist_smul s x y

lemma lipschitz_with_smul [normed_space α β] (s : α) : lipschitz_with ∥s∥₊ ((•) s : β → β) :=
lipschitz_with_iff_dist_le_mul.2 $ λ x y, by rw [dist_smul, coe_nnnorm]

lemma norm_smul_of_nonneg [normed_space ℝ β] {t : ℝ} (ht : 0 ≤ t) (x : β) :
  ∥t • x∥ = t * ∥x∥ := by rw [norm_smul, real.norm_eq_abs, abs_of_nonneg ht]

variables {E : Type*} [semi_normed_group E] [normed_space α E]
variables {F : Type*} [semi_normed_group F] [normed_space α F]

theorem eventually_nhds_norm_smul_sub_lt (c : α) (x : E) {ε : ℝ} (h : 0 < ε) :
  ∀ᶠ y in 𝓝 x, ∥c • (y - x)∥ < ε :=
have tendsto (λ y, ∥c • (y - x)∥) (𝓝 x) (𝓝 0),
  from ((continuous_id.sub continuous_const).const_smul _).norm.tendsto' _ _ (by simp),
this.eventually (gt_mem_nhds h)

lemma filter.tendsto.zero_smul_is_bounded_under_le {f : ι → α} {g : ι → E} {l : filter ι}
  (hf : tendsto f l (𝓝 0)) (hg : is_bounded_under (≤) l (norm ∘ g)) :
  tendsto (λ x, f x • g x) l (𝓝 0) :=
hf.op_zero_is_bounded_under_le hg (•) (λ x y, (norm_smul x y).le)

lemma filter.is_bounded_under.smul_tendsto_zero {f : ι → α} {g : ι → E} {l : filter ι}
  (hf : is_bounded_under (≤) l (norm ∘ f)) (hg : tendsto g l (𝓝 0)) :
  tendsto (λ x, f x • g x) l (𝓝 0) :=
hg.op_zero_is_bounded_under_le hf (flip (•)) (λ x y, ((norm_smul y x).trans (mul_comm _ _)).le)

theorem closure_ball [normed_space ℝ E] (x : E) {r : ℝ} (hr : r ≠ 0) :
  closure (ball x r) = closed_ball x r :=
begin
  refine subset.antisymm closure_ball_subset_closed_ball (λ y hy, _),
  have : continuous_within_at (λ c : ℝ, c • (y - x) + x) (Ico 0 1) 1 :=
    ((continuous_id.smul continuous_const).add continuous_const).continuous_within_at,
  convert this.mem_closure _ _,
  { rw [one_smul, sub_add_cancel] },
  { simp [closure_Ico (@zero_ne_one ℝ _ _), zero_le_one] },
  { rintros c ⟨hc0, hc1⟩,
    rw [mem_ball, dist_eq_norm, add_sub_cancel, norm_smul, real.norm_eq_abs,
      abs_of_nonneg hc0, mul_comm, ← mul_one r],
    rw [mem_closed_ball, dist_eq_norm] at hy,
    replace hr : 0 < r, from ((norm_nonneg _).trans hy).lt_of_ne hr.symm,
    apply mul_lt_mul'; assumption }
end

theorem frontier_ball [normed_space ℝ E] (x : E) {r : ℝ} (hr : r ≠ 0) :
  frontier (ball x r) = sphere x r :=
begin
  rw [frontier, closure_ball x hr, is_open_ball.interior_eq],
  ext x, exact (@eq_iff_le_not_lt ℝ _ _ _).symm
end

theorem interior_closed_ball [normed_space ℝ E] (x : E) {r : ℝ} (hr : r ≠ 0) :
  interior (closed_ball x r) = ball x r :=
begin
  cases hr.lt_or_lt with hr hr,
  { rw [closed_ball_eq_empty.2 hr, ball_eq_empty.2 hr.le, interior_empty] },
  refine subset.antisymm _ ball_subset_interior_closed_ball,
  intros y hy,
  rcases (mem_closed_ball.1 $ interior_subset hy).lt_or_eq with hr|rfl, { exact hr },
  set f : ℝ → E := λ c : ℝ, c • (y - x) + x,
  suffices : f ⁻¹' closed_ball x (dist y x) ⊆ Icc (-1) 1,
  { have hfc : continuous f := (continuous_id.smul continuous_const).add continuous_const,
    have hf1 : (1:ℝ) ∈ f ⁻¹' (interior (closed_ball x $ dist y x)), by simpa [f],
    have h1 : (1:ℝ) ∈ interior (Icc (-1:ℝ) 1) :=
      interior_mono this (preimage_interior_subset_interior_preimage hfc hf1),
    contrapose h1,
    simp },
  intros c hc,
  rw [mem_Icc, ← abs_le, ← real.norm_eq_abs, ← mul_le_mul_right hr],
  simpa [f, dist_eq_norm, norm_smul] using hc
end

theorem frontier_closed_ball [normed_space ℝ E] (x : E) {r : ℝ} (hr : r ≠ 0) :
  frontier (closed_ball x r) = sphere x r :=
by rw [frontier, closure_closed_ball, interior_closed_ball x hr,
  closed_ball_diff_ball]

/-- A (semi) normed real vector space is homeomorphic to the unit ball in the same space.
This homeomorphism sends `x : E` to `(1 + ∥x∥)⁻¹ • x`.

In many cases the actual implementation is not important, so we don't mark the projection lemmas
`homeomorph_unit_ball_apply_coe` and `homeomorph_unit_ball_symm_apply` as `@[simp]`. -/
@[simps { attrs := [] }]
def homeomorph_unit_ball {E : Type*} [semi_normed_group E] [normed_space ℝ E] :
  E ≃ₜ ball (0 : E) 1 :=
{ to_fun := λ x, ⟨(1 + ∥x∥)⁻¹ • x, begin
    have : ∥x∥ < |1 + ∥x∥| := (lt_one_add _).trans_le (le_abs_self _),
    rwa [mem_ball_zero_iff, norm_smul, real.norm_eq_abs, abs_inv, ← div_eq_inv_mul,
      div_lt_one ((norm_nonneg x).trans_lt this)],
  end⟩,
  inv_fun := λ x, (1 - ∥(x : E)∥)⁻¹ • (x : E),
  left_inv := λ x,
    begin
      have : 0 < 1 + ∥x∥ := (norm_nonneg x).trans_lt (lt_one_add _),
      field_simp [this.ne', abs_of_pos this, norm_smul, smul_smul, real.norm_eq_abs, abs_div]
    end,
  right_inv := λ x, subtype.ext
    begin
      have : 0 < 1 - ∥(x : E)∥ := sub_pos.2 (mem_ball_zero_iff.1 x.2),
      field_simp [norm_smul, smul_smul, real.norm_eq_abs, abs_div, abs_of_pos this, this.ne']
    end,
  continuous_to_fun := continuous_subtype_mk _ $
    ((continuous_const.add continuous_norm).inv₀
      (λ x, ((norm_nonneg x).trans_lt (lt_one_add _)).ne')).smul continuous_id,
  continuous_inv_fun := continuous.smul
    ((continuous_const.sub continuous_subtype_coe.norm).inv₀ $
      λ x, (sub_pos.2 $ mem_ball_zero_iff.1 x.2).ne') continuous_subtype_coe }

open normed_field

instance : normed_space α (ulift E) :=
{ norm_smul_le := λ s x, (normed_space.norm_smul_le s x.down : _),
  ..ulift.normed_group,
  ..ulift.module' }

/-- The product of two normed spaces is a normed space, with the sup norm. -/
instance prod.normed_space : normed_space α (E × F) :=
{ norm_smul_le := λ s x, le_of_eq $ by simp [prod.norm_def, norm_smul, mul_max_of_nonneg],
  ..prod.normed_group,
  ..prod.module }

/-- The product of finitely many normed spaces is a normed space, with the sup norm. -/
instance pi.normed_space {E : ι → Type*} [fintype ι] [∀i, semi_normed_group (E i)]
  [∀i, normed_space α (E i)] : normed_space α (Πi, E i) :=
{ norm_smul_le := λ a f, le_of_eq $
    show (↑(finset.sup finset.univ (λ (b : ι), ∥a • f b∥₊)) : ℝ) =
      ∥a∥₊ * ↑(finset.sup finset.univ (λ (b : ι), ∥f b∥₊)),
    by simp only [(nnreal.coe_mul _ _).symm, nnreal.mul_finset_sup, nnnorm_smul] }

/-- A subspace of a normed space is also a normed space, with the restriction of the norm. -/
instance submodule.normed_space {𝕜 R : Type*} [has_scalar 𝕜 R] [normed_field 𝕜] [ring R]
  {E : Type*} [semi_normed_group E] [normed_space 𝕜 E] [module R E]
  [is_scalar_tower 𝕜 R E] (s : submodule R E) :
  normed_space 𝕜 s :=
{ norm_smul_le := λc x, le_of_eq $ norm_smul c (x : E) }

/-- If there is a scalar `c` with `∥c∥>1`, then any element with nonzero norm can be
moved by scalar multiplication to any shell of width `∥c∥`. Also recap information on the norm of
the rescaling element that shows up in applications. -/
lemma rescale_to_shell_semi_normed {c : α} (hc : 1 < ∥c∥) {ε : ℝ} (εpos : 0 < ε) {x : E}
  (hx : ∥x∥ ≠ 0) : ∃d:α, d ≠ 0 ∧ ∥d • x∥ < ε ∧ (ε/∥c∥ ≤ ∥d • x∥) ∧ (∥d∥⁻¹ ≤ ε⁻¹ * ∥c∥ * ∥x∥) :=
begin
  have xεpos : 0 < ∥x∥/ε := div_pos ((ne.symm hx).le_iff_lt.1 (norm_nonneg x)) εpos,
  rcases exists_mem_Ico_zpow xεpos hc with ⟨n, hn⟩,
  have cpos : 0 < ∥c∥ := lt_trans (zero_lt_one : (0 :ℝ) < 1) hc,
  have cnpos : 0 < ∥c^(n+1)∥ := by { rw norm_zpow, exact lt_trans xεpos hn.2 },
  refine ⟨(c^(n+1))⁻¹, _, _, _, _⟩,
  show (c ^ (n + 1))⁻¹  ≠ 0,
    by rwa [ne.def, inv_eq_zero, ← ne.def, ← norm_pos_iff],
  show ∥(c ^ (n + 1))⁻¹ • x∥ < ε,
  { rw [norm_smul, norm_inv, ← div_eq_inv_mul, div_lt_iff cnpos, mul_comm, norm_zpow],
    exact (div_lt_iff εpos).1 (hn.2) },
  show ε / ∥c∥ ≤ ∥(c ^ (n + 1))⁻¹ • x∥,
  { rw [div_le_iff cpos, norm_smul, norm_inv, norm_zpow, zpow_add₀ (ne_of_gt cpos),
        zpow_one, mul_inv_rev, mul_comm, ← mul_assoc, ← mul_assoc, mul_inv_cancel (ne_of_gt cpos),
        one_mul, ← div_eq_inv_mul, le_div_iff (zpow_pos_of_pos cpos _), mul_comm],
    exact (le_div_iff εpos).1 hn.1 },
  show ∥(c ^ (n + 1))⁻¹∥⁻¹ ≤ ε⁻¹ * ∥c∥ * ∥x∥,
  { have : ε⁻¹ * ∥c∥ * ∥x∥ = ε⁻¹ * ∥x∥ * ∥c∥, by ring,
    rw [norm_inv, inv_inv, norm_zpow, zpow_add₀ (ne_of_gt cpos), zpow_one, this, ← div_eq_inv_mul],
    exact mul_le_mul_of_nonneg_right hn.1 (norm_nonneg _) }
end

end semi_normed_group

section normed_group

variables [normed_field α]
variables {E : Type*} [normed_group E] [normed_space α E]
variables {F : Type*} [normed_group F] [normed_space α F]

open normed_field

/-- While this may appear identical to `normed_space.to_module`, it contains an implicit argument
involving `normed_group.to_semi_normed_group` that typeclass inference has trouble inferring.

Specifically, the following instance cannot be found without this `normed_space.to_module'`:
```lean
example
  (𝕜 ι : Type*) (E : ι → Type*)
  [normed_field 𝕜] [Π i, normed_group (E i)] [Π i, normed_space 𝕜 (E i)] :
  Π i, module 𝕜 (E i) := by apply_instance
```

[This Zulip thread](https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/Typeclass.20resolution.20under.20binders/near/245151099)
gives some more context. -/
@[priority 100]
instance normed_space.to_module' : module α F := normed_space.to_module

section surj

variables (E) [normed_space ℝ E] [nontrivial E]

lemma exists_norm_eq {c : ℝ} (hc : 0 ≤ c) : ∃ x : E, ∥x∥ = c :=
begin
  rcases exists_ne (0 : E) with ⟨x, hx⟩,
  rw ← norm_ne_zero_iff at hx,
  use c • ∥x∥⁻¹ • x,
  simp [norm_smul, real.norm_of_nonneg hc, hx]
end

@[simp] lemma range_norm : range (norm : E → ℝ) = Ici 0 :=
subset.antisymm (range_subset_iff.2 norm_nonneg) (λ _, exists_norm_eq E)

lemma nnnorm_surjective : surjective (nnnorm : E → ℝ≥0) :=
λ c, (exists_norm_eq E c.coe_nonneg).imp $ λ x h, nnreal.eq h

@[simp] lemma range_nnnorm : range (nnnorm : E → ℝ≥0) = univ :=
(nnnorm_surjective E).range_eq

end surj

theorem interior_closed_ball' [normed_space ℝ E] [nontrivial E] (x : E) (r : ℝ) :
  interior (closed_ball x r) = ball x r :=
begin
  rcases eq_or_ne r 0 with rfl|hr,
  { rw [closed_ball_zero, ball_zero, interior_singleton] },
  { exact interior_closed_ball x hr }
end

theorem frontier_closed_ball' [normed_space ℝ E] [nontrivial E] (x : E) (r : ℝ) :
  frontier (closed_ball x r) = sphere x r :=
by rw [frontier, closure_closed_ball, interior_closed_ball' x r, closed_ball_diff_ball]

variables {α}

/-- If there is a scalar `c` with `∥c∥>1`, then any element can be moved by scalar multiplication to
any shell of width `∥c∥`. Also recap information on the norm of the rescaling element that shows
up in applications. -/
lemma rescale_to_shell {c : α} (hc : 1 < ∥c∥) {ε : ℝ} (εpos : 0 < ε) {x : E} (hx : x ≠ 0) :
  ∃d:α, d ≠ 0 ∧ ∥d • x∥ < ε ∧ (ε/∥c∥ ≤ ∥d • x∥) ∧ (∥d∥⁻¹ ≤ ε⁻¹ * ∥c∥ * ∥x∥) :=
rescale_to_shell_semi_normed hc εpos (ne_of_lt (norm_pos_iff.2 hx)).symm

end normed_group

section normed_space_nondiscrete

variables (𝕜 E : Type*) [nondiscrete_normed_field 𝕜] [normed_group E] [normed_space 𝕜 E]
  [nontrivial E]

include 𝕜

/-- If `E` is a nontrivial normed space over a nondiscrete normed field `𝕜`, then `E` is unbounded:
for any `c : ℝ`, there exists a vector `x : E` with norm strictly greater than `c`. -/
lemma normed_space.exists_lt_norm (c : ℝ) : ∃ x : E, c < ∥x∥ :=
begin
  rcases exists_ne (0 : E) with ⟨x, hx⟩,
  rcases normed_field.exists_lt_norm 𝕜 (c / ∥x∥) with ⟨r, hr⟩,
  use r • x,
  rwa [norm_smul, ← div_lt_iff],
  rwa norm_pos_iff
end

protected lemma normed_space.unbounded_univ : ¬bounded (univ : set E) :=
λ h, let ⟨R, hR⟩ := bounded_iff_forall_norm_le.1 h, ⟨x, hx⟩ := normed_space.exists_lt_norm 𝕜 E R
in hx.not_le (hR x trivial)

/-- A normed vector space over a nondiscrete normed field is a noncompact space. This cannot be
an instance because in order to apply it, Lean would have to search for `normed_space 𝕜 E` with
unknown `𝕜`. We register this as an instance in two cases: `𝕜 = E` and `𝕜 = ℝ`. -/
protected lemma normed_space.noncompact_space : noncompact_space E :=
⟨λ h, normed_space.unbounded_univ 𝕜 _ h.bounded⟩

@[priority 100]
instance nondiscrete_normed_field.noncompact_space : noncompact_space 𝕜 :=
normed_space.noncompact_space 𝕜 𝕜

omit 𝕜

@[priority 100]
instance real_normed_space.noncompact_space [normed_space ℝ E] : noncompact_space E :=
normed_space.noncompact_space ℝ E

end normed_space_nondiscrete

section normed_algebra

/-- A normed algebra `𝕜'` over `𝕜` is normed module that is also an algebra.

See the implementation notes for `algebra` for a discussion about non-unital algebras. Following
the strategy there, a non-unital *normed* algebra can be written as:
```lean
variables [normed_field 𝕜] [non_unital_semi_normed_ring 𝕜']
variables [normed_module 𝕜 𝕜'] [smul_comm_class 𝕜 𝕜' 𝕜'] [is_scalar_tower 𝕜 𝕜' 𝕜']
```
-/
class normed_algebra (𝕜 : Type*) (𝕜' : Type*) [normed_field 𝕜] [semi_normed_ring 𝕜']
  extends algebra 𝕜 𝕜' :=
(norm_smul_le : ∀ (r : 𝕜) (x : 𝕜'), ∥r • x∥ ≤ ∥r∥ * ∥x∥)

variables {𝕜 : Type*} (𝕜' : Type*) [normed_field 𝕜] [semi_normed_ring 𝕜'] [normed_algebra 𝕜 𝕜']

@[priority 100]
instance normed_algebra.to_normed_space : normed_space 𝕜 𝕜' :=
{ norm_smul_le := normed_algebra.norm_smul_le }

/-- While this may appear identical to `normed_algebra.to_normed_space`, it contains an implicit
argument involving `normed_ring.to_semi_normed_ring` that typeclass inference has trouble inferring.

Specifically, the following instance cannot be found without this `normed_space.to_module'`:
```lean
example
  (𝕜 ι : Type*) (E : ι → Type*)
  [normed_field 𝕜] [Π i, normed_ring (E i)] [Π i, normed_algebra 𝕜 (E i)] :
  Π i, module 𝕜 (E i) := by apply_instance
```

See `normed_space.to_module'` for a similar situation. -/
@[priority 100]
instance normed_algebra.to_normed_space' {𝕜'} [normed_ring 𝕜'] [normed_algebra 𝕜 𝕜'] :
  normed_space 𝕜 𝕜' := by apply_instance

lemma norm_algebra_map (x : 𝕜) : ∥algebra_map 𝕜 𝕜' x∥ = ∥x∥ * ∥(1 : 𝕜')∥ :=
begin
  rw algebra.algebra_map_eq_smul_one,
  exact norm_smul _ _,
end

lemma nnnorm_algebra_map (x : 𝕜) : ∥algebra_map 𝕜 𝕜' x∥₊ = ∥x∥₊ * ∥(1 : 𝕜')∥₊ :=
subtype.ext $ norm_algebra_map 𝕜' x

@[simp] lemma norm_algebra_map' [norm_one_class 𝕜'] (x : 𝕜) : ∥algebra_map 𝕜 𝕜' x∥ = ∥x∥ :=
by rw [norm_algebra_map, norm_one, mul_one]

@[simp] lemma nnnorm_algebra_map' [norm_one_class 𝕜'] (x : 𝕜) : ∥algebra_map 𝕜 𝕜' x∥₊ = ∥x∥₊ :=
subtype.ext $ norm_algebra_map' _ _

variables (𝕜 𝕜')

/-- In a normed algebra, the inclusion of the base field in the extended field is an isometry. -/
lemma algebra_map_isometry [norm_one_class 𝕜'] : isometry (algebra_map 𝕜 𝕜') :=
begin
  refine isometry_emetric_iff_metric.2 (λx y, _),
  rw [dist_eq_norm, dist_eq_norm, ← ring_hom.map_sub, norm_algebra_map'],
end

/-- The inclusion of the base field in a normed algebra as a continuous linear map. -/
@[simps]
def algebra_map_clm : 𝕜 →L[𝕜] 𝕜' :=
{ to_fun := algebra_map 𝕜 𝕜',
  map_add' := (algebra_map 𝕜 𝕜').map_add,
  map_smul' := λ r x, by rw [algebra.id.smul_eq_mul, map_mul, ring_hom.id_apply, algebra.smul_def],
  cont :=
    have lipschitz_with ∥(1 : 𝕜')∥₊ (algebra_map 𝕜 𝕜') := λ x y, begin
      rw [edist_eq_coe_nnnorm_sub, edist_eq_coe_nnnorm_sub, ←map_sub, ←ennreal.coe_mul,
        ennreal.coe_le_coe, mul_comm],
      exact (nnnorm_algebra_map _ _).le,
    end, this.continuous }

lemma algebra_map_clm_coe :
  (algebra_map_clm 𝕜 𝕜' : 𝕜 → 𝕜') = (algebra_map 𝕜 𝕜' : 𝕜 → 𝕜') := rfl

lemma algebra_map_clm_to_linear_map :
  (algebra_map_clm 𝕜 𝕜').to_linear_map = algebra.linear_map 𝕜 𝕜' := rfl

instance normed_algebra.id : normed_algebra 𝕜 𝕜 :=
{ .. normed_field.to_normed_space,
  .. algebra.id 𝕜}

/-- Any normed characteristic-zero division ring that is a normed_algebra over the reals is also a
normed algebra over the rationals.

Phrased another way, if `𝕜` is a normed algebra over the reals, then `algebra_rat` respects that
norm. -/
instance normed_algebra_rat {𝕜} [normed_division_ring 𝕜] [char_zero 𝕜] [normed_algebra ℝ 𝕜] :
  normed_algebra ℚ 𝕜 :=
{ norm_smul_le := λ q x,
    by rw [←smul_one_smul ℝ q x, rat.smul_one_eq_coe, norm_smul, rat.norm_cast_real], }

instance punit.normed_algebra : normed_algebra 𝕜 punit :=
{ norm_smul_le := λ q x, by simp only [punit.norm_eq_zero, mul_zero] }

instance : normed_algebra 𝕜 (ulift 𝕜') :=
{ ..ulift.normed_space }

/-- The product of two normed algebras is a normed algebra, with the sup norm. -/
instance prod.normed_algebra {E F : Type*} [semi_normed_ring E] [semi_normed_ring F]
  [normed_algebra 𝕜 E] [normed_algebra 𝕜 F] :
  normed_algebra 𝕜 (E × F) :=
{ ..prod.normed_space }

/-- The product of finitely many normed algebras is a normed algebra, with the sup norm. -/
instance pi.normed_algebra {E : ι → Type*} [fintype ι]
  [Π i, semi_normed_ring (E i)] [Π i, normed_algebra 𝕜 (E i)] :
  normed_algebra 𝕜 (Π i, E i) :=
{ .. pi.normed_space,
  .. pi.algebra _ E }

end normed_algebra

section restrict_scalars

variables (𝕜 : Type*) (𝕜' : Type*) [normed_field 𝕜] [normed_field 𝕜'] [normed_algebra 𝕜 𝕜']
(E : Type*) [semi_normed_group E] [normed_space 𝕜' E]

instance {𝕜 : Type*} {𝕜' : Type*} {E : Type*} [I : semi_normed_group E] :
  semi_normed_group (restrict_scalars 𝕜 𝕜' E) := I

instance {𝕜 : Type*} {𝕜' : Type*} {E : Type*} [I : normed_group E] :
  normed_group (restrict_scalars 𝕜 𝕜' E) := I

/-- If `E` is a normed space over `𝕜'` and `𝕜` is a normed algebra over `𝕜'`, then
`restrict_scalars.module` is additionally a `normed_space`. -/
instance : normed_space 𝕜 (restrict_scalars 𝕜 𝕜' E) :=
{ norm_smul_le := λ c x, (normed_space.norm_smul_le (algebra_map 𝕜 𝕜' c) (_ : E)).trans_eq $
    by rw norm_algebra_map',
  ..restrict_scalars.module 𝕜 𝕜' E }

/--
The action of the original normed_field on `restrict_scalars 𝕜 𝕜' E`.
This is not an instance as it would be contrary to the purpose of `restrict_scalars`.
-/
-- If you think you need this, consider instead reproducing `restrict_scalars.lsmul`
-- appropriately modified here.
def module.restrict_scalars.normed_space_orig {𝕜 : Type*} {𝕜' : Type*} {E : Type*}
  [normed_field 𝕜'] [semi_normed_group E] [I : normed_space 𝕜' E] :
  normed_space 𝕜' (restrict_scalars 𝕜 𝕜' E) := I

/-- Warning: This declaration should be used judiciously.
Please consider using `is_scalar_tower` and/or `restrict_scalars 𝕜 𝕜' E` instead.

This definition allows the `restrict_scalars.normed_space` instance to be put directly on `E`
rather on `restrict_scalars 𝕜 𝕜' E`. This would be a very bad instance; both because `𝕜'` cannot be
inferred, and because it is likely to create instance diamonds.
-/
def normed_space.restrict_scalars : normed_space 𝕜 E :=
restrict_scalars.normed_space _ 𝕜' _

end restrict_scalars


/-
Copyright (c) 2020 Frédéric Dupuis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Frédéric Dupuis

! This file was ported from Lean 3 source module topology.algebra.affine
! leanprover-community/mathlib commit 717c073262cd9d59b1a1dcda7e8ab570c5b63370
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.LinearAlgebra.AffineSpace.AffineMap
import Mathlib.Topology.Algebra.Group.Basic
import Mathlib.Topology.Algebra.MulAction

/-!
# Topological properties of affine spaces and maps

For now, this contains only a few facts regarding the continuity of affine maps in the special
case when the point space and vector space are the same.

TODO: Deal with the case where the point spaces are different from the vector spaces. Note that
we do have some results in this direction under the assumption that the topologies are induced by
(semi)norms.
-/

namespace AffineMap

variable {R E F : Type _}

variable [AddCommGroup E] [TopologicalSpace E]

variable [AddCommGroup F] [TopologicalSpace F] [TopologicalAddGroup F]

section Ring

variable [Ring R] [Module R E] [Module R F]

/-- An affine map is continuous iff its underlying linear map is continuous. See also
`AffineMap.continuous_linear_iff`. -/
theorem continuous_iff {f : E →ᵃ[R] F} : Continuous f ↔ Continuous f.linear := by
  constructor
  · intro hc
    rw [decomp' f]
    exact hc.sub continuous_const
  · intro hc
    rw [decomp f]
    exact hc.add continuous_const
#align affine_map.continuous_iff AffineMap.continuous_iff

/-- The line map is continuous. -/
@[continuity]
theorem lineMap_continuous [TopologicalSpace R] [ContinuousSMul R F] {p v : F} :
    Continuous (lineMap p v : R →ᵃ[R] F) :=
  continuous_iff.mpr <|
    (continuous_id.smul continuous_const).add <| @continuous_const _ _ _ _ (0 : F)
#align affine_map.line_map_continuous AffineMap.lineMap_continuous

end Ring

section CommRing

variable [CommRing R] [Module R F] [ContinuousConstSMul R F]

@[continuity]
theorem homothety_continuous (x : F) (t : R) : Continuous <| homothety x t := by
  suffices ⇑(homothety x t) = fun y => t • (y - x) + x
    by
    rw [this]
    exact ((continuous_id.sub continuous_const).const_smul _).add continuous_const
    -- Porting note: proof was `by continuity`
  ext y
  simp [homothety_apply]
#align affine_map.homothety_continuous AffineMap.homothety_continuous

end CommRing

section Field

variable [Field R] [Module R F] [ContinuousConstSMul R F]

theorem homothety_isOpenMap (x : F) (t : R) (ht : t ≠ 0) : IsOpenMap <| homothety x t := by
  apply IsOpenMap.of_inverse (homothety_continuous x t⁻¹) <;> intro e <;>
    simp [← AffineMap.comp_apply, ← homothety_mul, ht]
#align affine_map.homothety_is_open_map AffineMap.homothety_isOpenMap

end Field

end AffineMap