import algebra.direct_limit
import linear_algebra.tensor_product

open_locale tensor_product

universes u v w

variables {R : Type u} [comm_ring R]
variables {ι : Type v}
variables [decidable_eq ι] [preorder ι]
variables (G : ι → Type w)

namespace module

variables [Π i, add_comm_group (G i)] [Π i, module R (G i)]

variables {G} (f : Π i j, i ≤ j → G i →ₗ[R] G j)

variables (M : Type w) [add_comm_group M] [module R M]

@[reducible]
def direct_limit_of_tensor_product_to_tensor_product_with_direct_limit :
  direct_limit (λ i, G i ⊗[R] M)
    (λ i j hij, tensor_product.map (f _ _ hij) (linear_map.id)) →ₗ[R] (direct_limit G f) ⊗[R] M :=
direct_limit.lift _ _ _ _ (λ i, tensor_product.map (direct_limit.of _ _ _ _ _) linear_map.id) $
λ i j hij z,
begin
  induction z using tensor_product.induction_on with g m x y hx hy,
  { simp only [map_zero] },
  { simp only [tensor_product.map_tmul, direct_limit.of_f, linear_map.id_apply], },
  { rw [map_add, map_add, hx, hy, map_add] },
end

@[reducible]
def tensor_product_with_direct_limit_to_direct_limit_of_tensor_product :
(direct_limit G f) ⊗[R] M →ₗ[R] direct_limit (λ i, G i ⊗[R] M)
  (λ i j hij, tensor_product.map (f _ _ hij) (linear_map.id)) :=
tensor_product.lift $ direct_limit.lift _ _ _ _ (λ i,
{ to_fun := λ g,
  { to_fun := λ m, direct_limit.of R ι (λ i, G i ⊗[R] M)
        (λ i j hij, tensor_product.map (f _ _ hij) (linear_map.id)) i $ g ⊗ₜ m,
    map_add' := λ m m', by rw [tensor_product.tmul_add, map_add],
    map_smul' := λ r m, by rw [←tensor_product.smul_tmul, ←tensor_product.smul_tmul', map_smul,
      ring_hom.id_apply] },
  map_add' := λ g g',
  begin
    ext1 m,
    simp only [linear_map.coe_mk, linear_map.add_apply, tensor_product.add_tmul, map_add],
  end,
  map_smul' := λ r g,
  begin
    ext1 m,
    simp only [linear_map.coe_mk, ring_hom.id_apply, linear_map.smul_apply,
      ←tensor_product.smul_tmul', map_smul],
  end }) $ λ i j hij g, linear_map.ext $ λ m,
  show direct_limit.of R ι (λ (i : ι), G i ⊗[R] M)
    (λ i j hij, tensor_product.map (f i j hij) linear_map.id) j
    ((f i j hij) g ⊗ₜ[R] m) =
  (direct_limit.of R ι (λ (i : ι), G i ⊗[R] M)
    (λ i j hij, tensor_product.map (f i j hij) linear_map.id) i)
    (g ⊗ₜ[R] m), from
by rw [show (f i j hij) g ⊗ₜ[R] m = (tensor_product.map (f i j hij) linear_map.id) (g ⊗ₜ[R] m),
  from tensor_product.map_tmul (f i j hij) linear_map.id g m, direct_limit.of_f]

variables [is_directed ι (≤)] [nonempty ι]

lemma direct_limit_of_tensor_product_iso_tensor_product_with_direct_limit.left_inv :
  function.left_inverse
    (tensor_product_with_direct_limit_to_direct_limit_of_tensor_product f M)
    (direct_limit_of_tensor_product_to_tensor_product_with_direct_limit f M) :=
begin
  intros z,
  induction z using module.direct_limit.induction_on with i z,
  induction z using tensor_product.induction_on with g m x y hx hy,
  { simp only [map_zero] },
  { rw [direct_limit.lift_of, tensor_product.map_tmul, linear_map.id_apply,
      tensor_product.lift.tmul, direct_limit.lift_of],
    refl, },
  { simp only [map_add, hx, hy] },
end

lemma direct_limit_of_tensor_product_iso_tensor_product_with_direct_limit.right_inv :
  function.right_inverse
    (tensor_product_with_direct_limit_to_direct_limit_of_tensor_product f M)
    (direct_limit_of_tensor_product_to_tensor_product_with_direct_limit f M) :=
begin
  intros z,
  induction z using tensor_product.induction_on with d m x y hx hy,
  { simp only [map_zero] },
  { induction d using module.direct_limit.induction_on with i g,
    rw [tensor_product.lift.tmul, direct_limit.lift_of],
    simp only [linear_map.coe_mk],
    rw [direct_limit.lift_of],
    refl, },
  { simp only [map_add, hx, hy] },
end

@[simps]
def direct_limit_of_tensor_product_iso_tensor_product_with_direct_limit :
  direct_limit (λ i, G i ⊗[R] M)
    (λ i j hij, tensor_product.map (f _ _ hij) (linear_map.id)) ≃ₗ[R] (direct_limit G f) ⊗[R] M :=
{ inv_fun := tensor_product_with_direct_limit_to_direct_limit_of_tensor_product f M,
  left_inv := direct_limit_of_tensor_product_iso_tensor_product_with_direct_limit.left_inv f M,
  right_inv := direct_limit_of_tensor_product_iso_tensor_product_with_direct_limit.right_inv f M,
  ..direct_limit_of_tensor_product_to_tensor_product_with_direct_limit f M }

universes u₁

variables {P : Type u₁} [add_comm_group P] [module R P] (g : Π i, G i →ₗ[R] P)
variables (Hg₁ : ∀ i j hij x, g j (f i j hij x) = g i x)
variables (Hg₂ : ∀ i, function.injective $ g i)
include Hg₁ Hg₂

variables (R ι G f)

lemma lift_inj :
  function.injective $ direct_limit.lift R ι G f g Hg₁ :=
begin
  simp_rw [←linear_map.ker_eq_bot, submodule.eq_bot_iff, linear_map.mem_ker] at Hg₂ ⊢,
  intros z hz,
  induction z using module.direct_limit.induction_on with i gi,
  rw direct_limit.lift_of at hz,
  specialize Hg₂ i gi hz,
  rw Hg₂,
  simp only [map_zero],
end

end module


import .Module
import .lattices
import logic.function
import .module_trivialities
import ring_theory.algebra

variables {R : Type} [ring R] (M : Module R) (S₁ S₂ : submodule R M)

@[simp]
def is_simple := (¬ is_trivial M) ∧ (∀ (N : submodule R M), N = ⊤ ∨ N = ⊥)

lemma intersection_of_simples_is_left_or_trivial :
 (is_simple (S₁ : Module R)) → (is_simple (S₂ : Module R)) →
 (is_trivial ((S₁ ⊓ S₂ : submodule R M) : Module R)) ∨ ((S₁ ⊓ S₂ : submodule R M) : Module R) = S₁ :=
begin
  intros,
  set S₃ : submodule R M := S₁ ⊓ S₂,
  set f := (submodule.map_subtype.order_iso S₁).symm,
  set f' := (submodule.map_subtype.order_iso S₁),
  have h : S₃ ≤ S₁ := by simp,
  set S₃' := f ⟨ S₃ , h ⟩ ,
  cases a.elim_right S₃',
    have k : (⟨ S₃ , h ⟩ : {a // a ≤ S₁ }) = (⊤ : {a // a ≤ S₁}) := (
    calc (⟨ S₃ , h ⟩ : {a // a ≤ S₁ }) = f' (f ⟨S₃, h⟩ ) : by rw order_iso.apply_symm_apply
                                   ... = f' S₃' : rfl
                                   ... = f' ⊤ : (by rw h_1; refl)
                                   ... = ⊤ : order_isom_preserves_top f'),
    have l : S₃ = S₁ := calc
      S₃ = (⟨ S₃, h⟩ : {a // a ≤ S₁}).val : by simp
      ... = (⊤ : {a // a ≤ S₁}).val : by rw k
      ... = S₁ : by refl,
    rw l,
    apply or.inr,
    refl,
  have i : (⟨ S₃ , h ⟩ : {a // a ≤ S₁ }) = (⊥ : {a // a ≤ S₁}) := (
    calc (⟨ S₃ , h ⟩ : {a // a ≤ S₁ }) = f' (f ⟨S₃, h⟩ ) : by rw order_iso.apply_symm_apply
                                   ... = f' S₃' : rfl
                                   ... = f' ⊥ : (by rw h_1; refl)
                                   ... = ⊥ : order_isom_preserves_bot f'),
    have j : S₃ = ⊥ := calc
       S₃ = (⟨ S₃, h⟩ : {a // a ≤ S₁}).val : by simp
      ... = (⊥ : {a // a ≤ S₁}).val : by rw i
      ... = ⊥ : by refl,
    rw j,
    exact or.inl ⟨ bot_is_trivial _ ⟩ ,
end

lemma intersection_of_simples_is_right_or_trivial (M : Module R) (S₁ S₂ : submodule R M) :
 (is_simple (S₁ : Module R)) → (is_simple (S₂ : Module R)) →
 (is_trivial ((S₁ ⊓ S₂ : submodule R M) : Module R)) ∨ ((S₁ ⊓ S₂ : submodule R M) : Module R) = S₂ :=
begin
  intros,
  rw lattice.inf_comm,
  exact intersection_of_simples_is_left_or_trivial M S₂ S₁ a_1 a
end

lemma intersection_of_simples_is_simple_or_trivial (M : Module R) (S₁ S₂ : submodule R M) :
 (is_simple (S₁ : Module R)) → (is_simple (S₂ : Module R)) →
 (is_trivial ((S₁ ⊓ S₂ : submodule R M) : Module R)) ∨ is_simple ((S₁ ⊓ S₂ : submodule R M) : Module R) :=
begin
  intros,
  cases intersection_of_simples_is_left_or_trivial M S₁ S₂ a a_1,
  exact or.inl h,
  rw h,
  exact or.inr a
end

theorem isom_is_equiv : equivalence (@are_isomorphic R _) :=
  ⟨ λ x, ⟨ category_theory.iso.refl x ⟩,
    λ x y h, h.elim (λ f, ⟨ category_theory.iso.symm f ⟩),
    λ x y z h₁ h₂, h₁.elim (λ f, h₂.elim (λ g, ⟨category_theory.iso.trans f g⟩)) ⟩

def isomorphism_class_of_modules := quotient ⟨(@are_isomorphic R _), isom_is_equiv ⟩


import ..mynat.dvd
import .max
import .le

namespace hidden
namespace myint
open myring
open ordered_myring

def dvd (m n : myint) := ∃ k : myint, n = k * m

instance: has_dvd myint := ⟨dvd⟩

def coprime (m n : myint) := ∀ k: myint, k ∣ m → k ∣ n → (k = 1 ∨ k = -1)

variables m n k : myint

theorem int_dvd_iff_abs_dvd :
m ∣ n ↔ (abs m) ∣ (abs n) :=
begin
  split; assume h, {
    cases h with k hk,
    existsi (abs k),
    rw hk,
    rw abs_mul,
  }, {
    cases h with k hk,
    existsi (k * sign n * sign m),
    repeat {rw mul_assoc},
    rw mul_comm _ m,
    rw sign_mul_self_abs,
    rw mul_comm _ (abs m),
    rw ←mul_assoc,
    rw ←hk,
    rw mul_comm,
    rw sign_abs_mul,
  },
end

@[trans]
theorem dvd_trans {m n k : myint}: m ∣ n → n ∣ k → m ∣ k :=
begin
  assume hmn hnk,
  cases hmn with a ha,
  cases hnk with b hb,
  existsi a * b,
  rw [hb, ha, ←mul_assoc, mul_comm b a],
end

theorem dvd_zero: m ∣ 0 :=
begin
  existsi (0: myint),
  rw zero_mul,
end

theorem zero_dvd {m : myint}: 0 ∣ m → m=0 :=
begin
  assume h,
  cases h with k hk,
  rw mul_zero at hk,
  from hk,
end

theorem one_dvd: 1 ∣ m :=
begin
  existsi m,
  rw mul_one,
end

@[refl]
theorem dvd_refl: m ∣ m :=
begin
  existsi (1: myint),
  rw one_mul,
end

theorem dvd_mul {k m : myint}: k ∣ m → k ∣ m * n :=
begin
  assume hkm,
  cases hkm with a ha,
  existsi a * n,
  rw ha,
  repeat {rw mul_assoc},
  rw mul_comm k n,
end

theorem dvd_mul_right {k m : myint}: k ∣ m → k ∣ n * m :=
λ h, by rw mul_comm; from dvd_mul _ h

theorem dvd_multiple: k ∣ n * k :=
begin
  rw mul_comm,
  apply dvd_mul,
  refl,
end

theorem dvd_sum: k ∣ m → k ∣ n → k ∣ m + n :=
begin
  assume hm hn,
  cases hm with a ha, subst ha,
  cases hn with b hb, subst hb,
  existsi (a + b),
  rw add_mul,
end

-- Reorder variables
-- have decided not to make implicit because it's too much of a headache
theorem dvd_remainder (j m n k : myint):
j ∣ m → j ∣ n → m + k = n → j ∣ k :=
begin
  assume hjm hjn hmkn,
  rw ←add_cancel_left_iff (-m) at hmkn,
  rw ←add_assoc at hmkn,
  rw neg_add at hmkn,
  rw zero_add at hmkn,
  rw hmkn,
  apply dvd_sum, {
    rw ←mul_one (-m),
    rw neg_mul,
    rw ←mul_neg,
    apply dvd_mul,
    assumption,
  }, {
    assumption,
  },
end

theorem coe_coe_dvd {a b : mynat} : a ∣ b ↔ (↑a : myint) ∣ ↑b :=
begin
  split; assume h, {
    cases h with k hk,
    subst hk,
    rw ←coe_coe_mul,
    from dvd_mul_right _ (by refl),
  }, {
    cases h with k hk,
    cases (le_iff_exists_nat 0 (abs k)).mp (abs_nonneg _) with l hl,
    rw zero_add at hl,
    existsi l,
    have hcb := (zero_le_iff_coe b).mpr ⟨b, rfl⟩,
    have hca := (zero_le_iff_coe a).mpr ⟨a, rfl⟩,
    have: abs ↑b = abs k * abs ↑a, {
      rw hk,
      rw abs_mul,
    },
    rw hl at this,
    apply coe_inj,
    rw abs_of_nonneg _ hcb at this,
    rw abs_of_nonneg _ hca at this,
    rw ←coe_coe_mul,
    assumption,
  },
end

end myint
end hidden


import data.hash_map
import data.int.basic
import data.nat.basic
import data.int.sqrt

import core.point
import core.util

-- The definition happens to align with points here, but they're conceptually
-- different.
def grid_idx := ℤ × ℤ
instance : has_add grid_idx :=
  ⟨λ p₁ p₂, (p₁.1 + p₂.1, p₁.2 + p₂.2)⟩
instance : has_repr grid_idx :=
  ⟨λ p, "(" ++ (repr p.1) ++ ", " ++ (repr p.2) ++ ")"⟩

structure grid_2D :=
  (data : hash_map grid_idx (λ _, list point))
  (c : ℕ⁺)
  (ps : list point)

-- def grid_2D := hash_map grid_idx (λ _, list point)

-- Lean won't let us define the overlapping instance here, since there's already
-- an instance for sigma types generally.
-- instance : has_repr (Σ (a : grid_idx), list point) :=
--  ⟨λ p, ""⟩
instance : has_repr grid_2D :=
  ⟨λ g, "[" ++ (g.data.entries.map
    (λ (p : Σ (a : grid_idx), list point),
      (" {" ++ repr p.1 ++ ": " ++ repr p.2 ++ "} ").to_list)).join.as_string ++ "]"⟩

-- def get_grid_idx (p : point) (C : ℕ) : grid_idx :=
--   (p.1 / C, p.2 / C)
def get_grid_idx (p : point) : grid_idx :=
  (p.1, p.2)

-- `C` is an upper bound on the closest pair distance.
def grid_points (c : ℕ⁺) : list point → grid_2D
| [] := ⟨mk_hash_map point_hash, c, []⟩
| (x :: xs) :=
    let g := grid_points xs in
    -- TODO could just use `g.modify`
    let grid_idx := get_grid_idx x in
    let l := match g.data.find grid_idx with
      | none := []
      | some l' := l'
      end
    in
    ⟨g.data.insert grid_idx (x :: l), c, x :: g.ps⟩

#check (grid_points ⟨3, by simp⟩ [(0, 0), (2, 2)]).data.entries

def get_hypercube (bound : ℕ) : list (ℤ × ℤ) :=
  (do
    j <- list.range (2 * bound + 1),
    return (do
      i <- list.range (2 * bound + 1),
      return (-(bound : ℤ) + i, -(bound : ℤ) + j))).join

def get_idxs (p : point) (c : ℕ⁺) :=
  let grid_idx := get_grid_idx p in
  let bound := nat.sqrt c + 1 in
  let hypercube := get_hypercube bound in
  hypercube.map (λ offs, grid_idx + offs)

def lift_option_list {α} : option (list α) → list α
| none := []
| (some l) := l

def in_opt_list {α : Type} (x : α) : option (list α) → Prop
| none := false
| (some xs') := x ∈ xs'

-- lemma lift_option_list_does_nothing :
--   ∀ (x : point) (ls : option (list point)),
--   (x ∈ lift_option_list ls) ↔
--   in_opt_list x ls := sorry

def get_neighbs (p : point) (g : grid_2D) : list point :=
  let idxs : list (ℤ × ℤ) := get_idxs p g.c in
  let cells : list (option (list point)) := idxs.map (λ i, g.data.find i) in
  let cells' : list (list point) := cells.map lift_option_list in
  let flat_cells : list point := cells'.join in
  flat_cells.filter (λ q, q ≠ p)

-- lemma cell_ij_to_kl_dist_bd :
--   ∀ (p q : point) (i j k l : ℤ),
--     get_grid_idx p = (i, j) →
--     get_grid_idx q = (k, l) →
--     ∥p - q∥ ≤ ∥(i, j) - (k, l)∥ := sorry

-- lemma holds_for_sublists_holds_for_join :
--   pxs

-- lemma hypercube_val_bds :
--   ∀ (bound : ℕ) (i j : ℤ),
--     ((i, j) ∈ get_hypercube bound) →
--     ((-(↑bound) ≤ i) ∧ (i ≤ ↑bound)) ∧
--     ((-(↑bound) ≤ j) ∧ (j ≤ ↑bound)) := begin
--   intros bound i j ij_in_hypercube,
--   repeat {constructor; split},
--   {
--     sorry,
--   },
--   {
--     simp [get_hypercube] at ij_in_hypercube,
--     cases ij_in_hypercube with l ij_in_hypercube,
--     cases ij_in_hypercube with list_def ij_in_l,
--     cases list_def with a list_def,
--     cases list_def with a_bd list_def,
--     induction bound,
--     {
--       simp [list.range, list.bind, list.range_core, list.map] at *,
--       rw [list_def] at ij_in_l,
--       cases ij_in_l,
--       cases ij_in_l,
--       refl,
--       cases ij_in_l,
--     },
--     {
--       sorry,
--     },
--   },
--   sorry,
--   sorry,
-- end

-- lemma list_range_elts_in_range :
--   ∀ (x n : ℕ),
--     (x ∈ list.range n) →
--     0 ≤ x ∧ x < n := sorry

-- lemma idx_bounds :

/-
  Theorems and Lemmas
-/

-- theorem close_in_grid_means_close_in_space :
--   ∀ (p₁ p₂ : point) (a b : grid_idx) (G : grid_2D) (l₁ l₂ : list point),
--   (a = get_grid_idx p₁) →
--   (b = get_grid_idx p₂) →
--   (a ∈ G.data.keys) →
--   (b ∈ G.data.keys) →
--   (G.data.find a = some l₁) →
--   (G.data.find b = some l₂) →
--   (p₁ ∈ l₁) →
--   (p₂ ∈ l₂) →
--   ∥ p₁ - p₂ ∥ ≤ G.c * G.c * (∥ a - b ∥ + ∥ (1, 1) ∥)
--   := begin
--     intros p₁ p₂ a b G l₁ l₂ a_p₁ b_p₂ a_in_G b_in_G a_has_elts b_has_elts p_in_a₁ b_in_a₂,
--     unfold get_grid_idx at a_p₁,
--     unfold get_grid_idx at b_p₂,
--     rw [int.distrib_left (G.c * G.c) ∥a - b∥ ∥(1, 1)∥],
--     have h : (↑(G.c * G.c) * ∥a - b∥ = ∥p₁ - p₂∥) :=
--     begin
--       rw [a_p₁, b_p₂],
--       unfold point_norm,
--       simp only [prod.fst, prod.snd],
--       rw [int.distrib_left],
--       have unfold_x_sub_y : ∀ (x y : ℤ), x - y = x + (-y) := begin
--         intros x y,
--         -- TODO wtf why doesn't this work?
--         -- unfold int.sub,
--         sorry
--       end,
--       have simpler_vars : ∀ (x y t : ℤ),
--         t > 0 →
--         (t*t) * ((x/t - y/t) * (x/t - y/t)) ≤ x*x - x*y - x*y + y*y + t*t := begin
--           intros x y t t_nonzero,
--           rw [unfold_x_sub_y],
--           rw [int.distrib_left (x / t + -(y / t)) (x / t) (-(y / t))],
--           rw [int.distrib_right (x / t) (-(y / t)) (x / t)],
--           rw [int.distrib_right (x / t) (-(y / t)) (-(y / t))],
--           rw [int.distrib_left (t*t) (x / t * (x / t) + -(y / t) * (x / t)) (x / t * -(y / t) + -(y / t) * -(y / t))],
--           rw [int.distrib_left (t*t) (x / t * (x / t)) (-(y / t) * (x / t))],
--           rw [int.distrib_left (t*t) (x / t * -(y / t)) (-(y / t) * -(y / t))],
--           have reassoc : ∀ (x y z : ℤ), z * z * (x / z * (y / z)) = (z * x / z) * (z * (y / z)) := begin
--             sorry
--           end,
--           have div_to_mod : ∀ (x y z : ℤ), z * z * (x / z * (y / z)) = (x - x % z) * (y - y % z) :=
--           begin
--             sorry
--           end,
--           simp [int.neg],
--           simp [div_to_mod],
--           rw [reassoc],
--           rw [reassoc],
--           rw [reassoc],
--           rw [reassoc],
--       end,
--       rw [unfold_x_sub_y (p₁.fst / ↑(G.c)) ((p₂.fst / ↑(G.c)))],
--       rw [int.distrib_left (p₁.fst / ↑(G.c) - p₂.fst / ↑(G.c)) (p₁.fst / ↑(G.c)) (-(p₂.fst / ↑(G.c)))],
--       sorry
--     end,
--     rw h,
--     simp [point_norm],
--     apply (nonneg_iff_leq_zero (↑(G.c) * (1 + 1))).mp,
--     apply a_nonneg_times_b_nonneq_means_a_times_b_nonneg,
--     apply lift_nat_nonneg,
--     simp [int.nonneg],
--   end

-- lemma grid_pts_dot_c_is_arg_c :
--   ∀ (c : ℕ⁺) (qs : list point),
--     (grid_points c qs).c = c := sorry

-- lemma grid_pts_dot_ps_is_arg_ps :
--   ∀ (c : ℕ⁺) (ps : list point),
--     (grid_points c ps).ps = ps := sorry


/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Kenny Lau
-/
import data.dfinsupp
import linear_algebra.basic

/-!
# Properties of the module `Π₀ i, M i`

Given an indexed collection of `R`-modules `M i`, the `R`-module structure on `Π₀ i, M i`
is defined in `data.dfinsupp`.

In this file we define `linear_map` versions of various maps:

* `dfinsupp.lsingle a : M →ₗ[R] Π₀ i, M i`: `dfinsupp.single a` as a linear map;

* `dfinsupp.lmk s : (Π i : (↑s : set ι), M i) →ₗ[R] Π₀ i, M i`: `dfinsupp.single a` as a linear map;

* `dfinsupp.lapply i : (Π₀ i, M i) →ₗ[R] M`: the map `λ f, f i` as a linear map;

* `dfinsupp.lsum`: `dfinsupp.sum` or `dfinsupp.lift_add_hom` as a `linear_map`;

## Implementation notes

This file should try to mirror `linear_algebra.finsupp` where possible. The API of `finsupp` is
much more developed, but many lemmas in that file should be eligible to copy over.

## Tags

function with finite support, module, linear algebra
-/

variables {ι : Type*} {R : Type*} {S : Type*} {M : ι → Type*} {N : Type*}

variables [dec_ι : decidable_eq ι]
variables [semiring R] [Π i, add_comm_monoid (M i)] [Π i, module R (M i)]
variables [add_comm_monoid N] [module R N]

namespace dfinsupp

include dec_ι

/-- `dfinsupp.mk` as a `linear_map`. -/
def lmk (s : finset ι) : (Π i : (↑s : set ι), M i) →ₗ[R] Π₀ i, M i :=
⟨mk s, λ _ _, mk_add, λ c x, by rw [mk_smul R x]⟩

/-- `dfinsupp.single` as a `linear_map` -/
def lsingle (i) : M i →ₗ[R] Π₀ i, M i :=
⟨single i, λ _ _, single_add, λ _ _, single_smul _⟩

/-- Two `R`-linear maps from `Π₀ i, M i` which agree on each `single i x` agree everywhere. -/
lemma lhom_ext ⦃φ ψ : (Π₀ i, M i) →ₗ[R] N⦄
  (h : ∀ i x, φ (single i x) = ψ (single i x)) :
  φ = ψ :=
linear_map.to_add_monoid_hom_injective $ add_hom_ext h

/-- Two `R`-linear maps from `Π₀ i, M i` which agree on each `single i x` agree everywhere.

See note [partially-applied ext lemmas].
After apply this lemma, if `M = R` then it suffices to verify `φ (single a 1) = ψ (single a 1)`. -/
@[ext] lemma lhom_ext' ⦃φ ψ : (Π₀ i, M i) →ₗ[R] N⦄
  (h : ∀ i, φ.comp (lsingle i) = ψ.comp (lsingle i)) :
  φ = ψ :=
lhom_ext $ λ i, linear_map.congr_fun (h i)

omit dec_ι

/-- Interpret `λ (f : Π₀ i, M i), f i` as a linear map. -/
def lapply (i : ι) : (Π₀ i, M i) →ₗ[R] M i :=
{ to_fun := λ f, f i,
  map_add' := λ f g, add_apply f g i,
  map_smul' := λ c f, smul_apply c f i}

include dec_ι

@[simp] lemma lmk_apply (s : finset ι) (x) : (lmk s : _ →ₗ[R] Π₀ i, M i) x = mk s x := rfl

@[simp] lemma lsingle_apply (i : ι) (x : M i) : (lsingle i : _ →ₗ[R] _) x = single i x := rfl

omit dec_ι

@[simp] lemma lapply_apply (i : ι) (f : Π₀ i, M i) : (lapply i : _ →ₗ[R] _) f = f i := rfl

section lsum

/-- Typeclass inference can't find `dfinsupp.add_comm_monoid` without help for this case.
This instance allows it to be found where it is needed on the LHS of the colon in
`dfinsupp.module_of_linear_map`. -/
instance add_comm_monoid_of_linear_map : add_comm_monoid (Π₀ (i : ι), M i →ₗ[R] N) :=
@dfinsupp.add_comm_monoid _ (λ i, M i →ₗ[R] N) _

/-- Typeclass inference can't find `dfinsupp.module` without help for this case.
This is needed to define `dfinsupp.lsum` below.

The cause seems to be an inability to unify the `Π i, add_comm_monoid (M i →ₗ[R] N)` instance that
we have with the `Π i, has_zero (M i →ₗ[R] N)` instance which appears as a parameter to the
`dfinsupp` type. -/
instance module_of_linear_map [semiring S] [module S N] [smul_comm_class R S N] :
  module S (Π₀ (i : ι), M i →ₗ[R] N) :=
@dfinsupp.module _ (λ i, M i →ₗ[R] N) _ _ _ _

variables (S)

include dec_ι

/-- The `dfinsupp` version of `finsupp.lsum`.

See note [bundled maps over different rings] for why separate `R` and `S` semirings are used. -/
@[simps apply symm_apply]
def lsum [semiring S] [module S N] [smul_comm_class R S N] :
  (Π i, M i →ₗ[R] N) ≃ₗ[S] ((Π₀ i, M i) →ₗ[R] N) :=
{ to_fun := λ F, {
    to_fun := sum_add_hom (λ i, (F i).to_add_monoid_hom),
    map_add' := (lift_add_hom (λ i, (F i).to_add_monoid_hom)).map_add,
    map_smul' := λ c f, by {
      apply dfinsupp.induction f,
      { rw [smul_zero, add_monoid_hom.map_zero, smul_zero] },
      { intros a b f ha hb hf,
        rw [smul_add, add_monoid_hom.map_add, add_monoid_hom.map_add, smul_add, hf, ←single_smul,
          sum_add_hom_single, sum_add_hom_single, linear_map.to_add_monoid_hom_coe,
          linear_map.map_smul], } } },
  inv_fun := λ F i, F.comp (lsingle i),
  left_inv := λ F, by { ext x y, simp },
  right_inv := λ F, by { ext x y, simp },
  map_add' := λ F G, by { ext x y, simp },
  map_smul' := λ c F, by { ext, simp } }

end lsum

/-! ### Bundled versions of `dfinsupp.map_range`

The names should match the equivalent bundled `finsupp.map_range` definitions.
-/

section map_range

variables {β β₁ β₂: ι → Type*}
variables [Π i, add_comm_monoid (β i)] [Π i, add_comm_monoid (β₁ i)] [Π i, add_comm_monoid (β₂ i)]
variables [Π i, module R (β i)] [Π i, module R (β₁ i)] [Π i, module R (β₂ i)]

lemma map_range_smul (f : Π i, β₁ i → β₂ i) (hf : ∀ i, f i 0 = 0)
  (r : R) (hf' : ∀ i x, f i (r • x) = r • f i x) (g : Π₀ i, β₁ i):
  map_range f hf (r • g) = r • map_range f hf g :=
begin
  ext,
  simp only [map_range_apply f, coe_smul, pi.smul_apply, hf']
end

/-- `dfinsupp.map_range` as an `linear_map`. -/
@[simps apply]
def map_range.linear_map (f : Π i, β₁ i →ₗ[R] β₂ i) : (Π₀ i, β₁ i) →ₗ[R] (Π₀ i, β₂ i) :=
{ to_fun := map_range (λ i x, f i x) (λ i, (f i).map_zero),
  map_smul' := λ r, map_range_smul _ _ _ (λ i, (f i).map_smul r),
  .. map_range.add_monoid_hom (λ i, (f i).to_add_monoid_hom) }

@[simp]
lemma map_range.linear_map_id :
  map_range.linear_map (λ i, (linear_map.id : (β₂ i) →ₗ[R] _)) = linear_map.id :=
linear_map.ext map_range_id

lemma map_range.linear_map_comp (f : Π i, β₁ i →ₗ[R] β₂ i) (f₂ : Π i, β i →ₗ[R] β₁ i):
  map_range.linear_map (λ i, (f i).comp (f₂ i)) =
    (map_range.linear_map f).comp (map_range.linear_map f₂) :=
linear_map.ext $ map_range_comp (λ i x, f i x) (λ i x, f₂ i x) _ _ _

/-- `dfinsupp.map_range.linear_map` as an `linear_equiv`. -/
@[simps apply]
def map_range.linear_equiv (e : Π i, β₁ i ≃ₗ[R] β₂ i) : (Π₀ i, β₁ i) ≃ₗ[R] (Π₀ i, β₂ i) :=
{ to_fun := map_range (λ i x, e i x) (λ i, (e i).map_zero),
  inv_fun := map_range (λ i x, (e i).symm x) (λ i, (e i).symm.map_zero),
  .. map_range.add_equiv (λ i, (e i).to_add_equiv),
  .. map_range.linear_map (λ i, (e i).to_linear_map) }

@[simp]
lemma map_range.linear_equiv_refl :
  (map_range.linear_equiv $ λ i, linear_equiv.refl R (β₁ i)) = linear_equiv.refl _ _ :=
linear_equiv.ext map_range_id

lemma map_range.linear_equiv_trans (f : Π i, β i ≃ₗ[R] β₁ i) (f₂ : Π i, β₁ i ≃ₗ[R] β₂ i):
  map_range.linear_equiv (λ i, (f i).trans (f₂ i)) =
    (map_range.linear_equiv f).trans (map_range.linear_equiv f₂) :=
linear_equiv.ext $ map_range_comp (λ i x, f₂ i x) (λ i x, f i x) _ _ _

@[simp]
lemma map_range.linear_equiv_symm (e : Π i, β₁ i ≃ₗ[R] β₂ i) :
  (map_range.linear_equiv e).symm = map_range.linear_equiv (λ i, (e i).symm) := rfl

end map_range

end dfinsupp


/-
Copyright (c) 2019 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Premetric spaces.

Author: Sébastien Gouëzel
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.topology.metric_space.basic
import Mathlib.PostPort

universes u l

namespace Mathlib

/-!
# Premetric spaces

Metric spaces are often defined as quotients of spaces endowed with a "distance"
function satisfying the triangular inequality, but for which `dist x y = 0` does
not imply `x = y`. We call such a space a premetric space.
`dist x y = 0` defines an equivalence relation, and the quotient
is canonically a metric space.
-/

class premetric_space (α : Type u) extends has_dist α where
  dist_self : ∀ (x : α), dist x x = 0
  dist_comm : ∀ (x y : α), dist x y = dist y x
  dist_triangle : ∀ (x y z : α), dist x z ≤ dist x y + dist y z

namespace premetric

protected theorem dist_nonneg {α : Type u} [premetric_space α] {x : α} {y : α} : 0 ≤ dist x y :=
  sorry

/-- The canonical equivalence relation on a premetric space. -/
def dist_setoid (α : Type u) [premetric_space α] : setoid α :=
  setoid.mk (fun (x y : α) => dist x y = 0) sorry

/-- The canonical quotient of a premetric space, identifying points at distance `0`. -/
def metric_quot (α : Type u) [premetric_space α] := quotient (dist_setoid α)

protected instance has_dist_metric_quot {α : Type u} [premetric_space α] :
    has_dist (metric_quot α) :=
  has_dist.mk (quotient.lift₂ (fun (p q : α) => dist p q) sorry)

theorem metric_quot_dist_eq {α : Type u} [premetric_space α] (p : α) (q : α) :
    dist (quotient.mk p) (quotient.mk q) = dist p q :=
  rfl

protected instance metric_space_quot {α : Type u} [premetric_space α] :
    metric_space (metric_quot α) :=
  metric_space.mk sorry sorry sorry sorry
    (fun (x y : metric_quot α) =>
      ennreal.of_real
        (quotient.lift₂ (fun (p q : α) => dist p q) has_dist_metric_quot._proof_1 x y))
    (uniform_space_of_dist
      (quotient.lift₂ (fun (p q : α) => dist p q) has_dist_metric_quot._proof_1) sorry sorry sorry)

end Mathlib


import data.real.irrational
import topology.basic
import algebra.order.floor

--OUTPUT 1
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
begin
  begin
    have h1 : ∀ i j : ℤ, i ≠ j → abs ((int.fract (α * ↑i)) - (int.fract (α * ↑j))) < 1,
    from assume (i j : ℤ) (h1 : i ≠ j),
    begin
      let iα := (α * ↑i),
      let jα := (α * ↑j),
      have h2 : abs (iα - (iα.floor + (int.fract iα))) < 1, from by auto [int.fract_lt_one],
      have h3 : abs (jα - (jα.floor + (int.fract jα))) < 1, from by auto [int.fract_lt_one],
      have h4 : (int.fract iα) = (int.fract jα), from by auto [h2, h3, add_sub_cancel, h1],
      have h5 : (iα - iα.floor) = (jα - jα.floor), from by auto [h4],
      have h6 : iα = jα, from by auto [h5],
      have h7 : (α * ↑i) - (α * ↑j) = 0, from by auto [h6],
      have h8 : (α * (↑i - ↑j)) = 0, from by auto [h7],
      have h9 : (α * (↑i - ↑j)) ∈ ℚ, from by auto [h8, set.mem_coe, set.mem_image, set.mem_univ],
      have h10 : α ∈ ℚ, from by auto [hα_irrat, rational_iff_exists_int, h9, mul_left_cancel_iff],
      have h11 : false, from by auto [hα_irrat, h10],
      show abs ((int.fract (α * ↑i)) - (int.fract (α * ↑j))) < 1, from by auto [h11],
    end,
    have h2 : ∀ i : ℤ, int.fract (α * ↑i) ∈ set.Icc 0 1, from by auto [int.fract_lt_one],
    have h3 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [h2],
    have h4 : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) ⊆ set.Icc 0 1, from by auto [h3, set.subset_closure_iff],
    have h5 : ∀ i j : ℤ, i ≠ j → abs ((int.fract (α * ↑i)) - (int.fract (α * ↑j))) < 1, from by auto [h1],
    have h6 : ∀ x : ℝ, x ∈ closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) → x ∈ set.Icc 0 1, from by auto [h5, set.subset_closure_iff],
    have h7 : ∀ x : ℝ, x ∈ set.Icc 0 1 → x ∈ closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)), from by auto [h6],
    have h8 : set.Icc 0 1 ⊆ closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)), from by auto [h7],
    show closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1, from by auto [h4, h8, set.subset.antisymm_iff],
  end,
end

--OUTPUT 2
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
begin
  -- Let $\alpha$ be an irrational number. Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$. If this were not true, then $i \alpha-\lfloor i \alpha\rfloor=\{i \alpha\}=\{j \alpha\}=j \alpha-\lfloor j \alpha\rfloor$, which yields the false statement $\alpha=\frac{\lfloor i \alpha\rfloor-\lfloor j \alpha\rfloor}{i-j} \in \mathbb{Q}$. Hence, $S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}$ is an infinite subset of $\left[0,1\right]$.
  have h1 : ∀ (i j : ℤ), i ≠ j → int.fract (α*↑i) ≠ int.fract (α*↑j),
  from by intros i j h_ineq;
  have h1_1 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_2 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_3 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_4 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_5 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_6 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_7 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_8 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_9 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_10 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_11 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_12 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_13 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_14 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_15 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_16 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_17 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_18 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_19 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_20 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_21 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_22 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_23 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_24 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_25 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_26 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_27 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_28 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_29 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_30 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_31 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_32 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_33 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_34 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_35 : α = (int.fract (α*↑i)) + (int.nat_abs (α*↑i)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_36 : α = (int.fract (α*↑j)) + (int.nat_abs (α*↑j)), from by auto [int.fract_eq_iff_exists_nat_abs],
  have h1_37 : (int.fract (α*↑i)) = (int.fract (α*↑j)), from by linarith,
  have h1_38 : (int.nat_abs (α*↑i)) = (int.nat_abs (α*↑j)), from by linarith,
  have h1_39 : α = (int.fract (α*↑i
end --Needs more than 2000 tokens!

--OUTPUT 3
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
begin
  --Let $\alpha$ be an irrational number. Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$. If this were not true, then
  have h1 : ∀ m n : ℤ, m ≠ n → int.fract (α * ↑m) ≠ int.fract (α * ↑n),
  from by auto [int.fract_add, int.fract_mul, int.fract_eq_of_add_fract_eq_add_fract, int.fract_lt_one, int.fract_lt_one_iff, int.fract_ne_of_ne],

  --$S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}$ is an infinite subset of $\left[0,1\right]$.
  have h2 : (∀ m : ℤ, int.fract (α * ↑m) ∈ set.Icc 0 1), from by auto [int.fract_lt_one, int.fract_lt_one_iff],
  have h3 : (∀ m : ℤ, int.fract (α * ↑m) ∈ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)), from by auto [h2],
  have h4 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [h3],
  have h5 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅, from by auto [h3],
  have h6 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅, from by auto [h5],
  have h7 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ A B : set ℝ, (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) → ¬ (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅), from by auto [h5],
  have h8 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ A B : set ℝ, (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) → ¬ (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅, from by auto [h7, h6],
  have h9 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ A B : set ℝ, (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) → ¬ (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [h8, h4],
  have h10 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ A B : set ℝ, (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) → ¬ (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅, from by auto [h9],
  have h11 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ A B : set ℝ, (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) → ¬ (A ⊆ B ∧ A ≠ ∅ ∧ B ≠ ∅) ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ (A B : set ℝ), A ⊆ B → ¬ A = ∅ → ¬ B = ∅ → ¬ A = B, from by auto [h10],
  have h12 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ (A B : set ℝ), A ⊆ B → ¬ A = ∅ → ¬ B = ∅ → ¬ A = B ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅, from by auto [h11],
  have h13 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ ∀ (A B : set ℝ), A ⊆ B → ¬ A = ∅ → ¬ B = ∅ → ¬ A = B ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅ ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1 ∧ (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)
end --Needs more than 2000 tokens!

--OUTPUT 4
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
begin
  -- Let $\alpha$ be an irrational number. Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$.
  -- If this were not true, then
  -- $$i \alpha-\lfloor i \alpha\rfloor=\{i \alpha\}=\{j \alpha\}=j \alpha-\lfloor j \alpha\rfloor,$$
  -- which yields the false statement $\alpha=\frac{\lfloor i \alpha\rfloor-\lfloor j \alpha\rfloor}{i-j} \in \mathbb{Q}$.
  have h1 : ∀ (i j : ℤ), i ≠ j → int.fract (α * ↑i) ≠ int.fract (α * ↑j),
  from by auto [hα_irrat, int.fract_eq_iff] using [int.fract_eq_iff],

  -- Hence,
  -- $$S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}$$
  -- is an infinite subset of $\left[0,1\right]$.
  have h2 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅,
  from by auto [set.mem_univ],
  have h3 : finite ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = ff,
  from by auto [finite.empty_iff_finite, h2],

  -- By the Bolzano-Weierstrass theorem, $S$ has a limit point in $[0, 1]$.
  -- One can thus find pairs of elements of $S$ that are arbitrarily close.
  -- Since (the absolute value of) the difference of any two elements of $S$ is also an element of $S$, it follows that $0$ is a limit point of $S$.
  have h4 : limit_point ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) 0,
  from by auto [limit_point_iff_limit_point_of_image] using [limit_point_iff_limit_point_of_image, h3, int.fract_in_range, h1],

  -- To show that $S$ is dense in $[0, 1]$,
  -- consider $y \in[0,1]$, and $\epsilon>0$.
  -- Then by selecting $x \in S$ such that $\{x\}<\epsilon$ (which exists as $0$ is a limit point),
  -- and $N$ such that $N \cdot\{x\} \leq y<(N+1) \cdot\{x\}$, we get: $|y-\{N x\}|<\epsilon$.
  have h5 : ∀ y : ℝ, y ∈ set.Icc 0 1 → ∀ ε > 0, ∃ N : ℤ, ∃ x : ℤ, x ∈ set.univ ∧ int.fract (α * ↑x) ∈ set.Icc 0 ε ∧ (int.fract (α * ↑N) ≤ y) ∧ (y < int.fract (α * ↑(N + 1))) ∧ (abs (y - int.fract (α * ↑(N * x))) < ε),
  from by auto [int.fract_in_range] using [int.fract_in_range, h1, h4, int.fract_eq_iff, int.fract_add_int, int.fract_mul_int, int.fract_mul_self_eq_zero, int.fract_eq_zero_iff_int_mul_eq],

  --Hence the result
  show  closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1,
  from by auto [closure_eq_of_is_open, is_open_Icc, is_open_iff_forall_mem_open, h5],

end

--OUTPUT 5
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
begin
  --Let $\alpha$ be an irrational number. Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$.
  --If this were not true, then
  --$$
  --i \alpha-\lfloor i \alpha\rfloor=\{i \alpha\}=\{j \alpha\}=j \alpha-\lfloor j \alpha\rfloor,
  --$$
  --which yields the false statement $\alpha=\frac{\lfloor i \alpha\rfloor-\lfloor j \alpha\rfloor}{i-j} \in \mathbb{Q}$.
  --Hence,
  --$$
  --S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}
  --$$
  --is an infinite subset of $\left[0,1\right]$.
  have h1 : ∀ (i j : ℤ), i ≠ j → int.fract (α * ↑i) ≠ int.fract (α * ↑j), from by auto [hα_irrat, int.fract_eq_of_int, int.fract_eq_of_int],
  have h2 : ∀ (i : ℤ), int.fract (α * ↑i) ∈ set.Icc 0 1, from by auto [int.fract_lt_one],
  have h3 : set.Ioc 0 1 ≠ ∅, from by auto [h2],
  have h4 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ≠ ∅, from by auto [h3, exists.intro],
  have h5 : (λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [h2],
  have h6 : closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ⊆ set.Icc 0 1, from by auto [set.subset.trans, closure_subset],
  have h7 : set.Icc 0 1 ⊆ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)), from by auto [closure_eq, h4, h5],
  have h8 : closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1, from by auto [set.subset.antisymm, h6, h7],
  show closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1, from by auto [h8],
end

--OUTPUT 6
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 := begin
  -- Let $\alpha$ be an irrational number. Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$. If this were not true, then
  have h1 : ∀ m n : ℤ, m ≠ n → (int.fract (α * ↑m)) ≠ (int.fract (α * ↑n)),
  from begin
    assume m n hmn,
    have h1 : (α * ↑m) ≠ (α * ↑n), from by auto [hα_irrat, mul_eq_zero],
    have h2 : (int.fract (α * ↑m)) = (α * ↑m) - (int.nat_abs (α * ↑m)), from by auto [int.fract_eq],
    have h3 : (int.fract (α * ↑n)) = (α * ↑n) - (int.nat_abs (α * ↑n)), from by auto [int.fract_eq],
    show (int.fract (α * ↑m)) ≠ (int.fract (α * ↑n)), from by auto [h2, h3, h1],
  end,

  -- Hence, $S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}$ is an infinite subset of $[0,1]$.
  have h2 : (set.univ ∈ set.univ) ∧ (∀ A B : set ℤ, A ∈ set.univ → B ∈ set.univ → A ≠ B → ((λ (m : ℤ), int.fract (α * ↑m)) '' A) ≠ ((λ (m : ℤ), int.fract (α * ↑m)) '' B)),
  from by auto,
  have h3 : (λ (m : ℤ), int.fract (α * ↑m)) '' (set.univ) ≠ ∅,
  from by auto [h2],
  have h4 : set.finite (set.univ) = ff, from by auto [set.finite_univ],
  have h5 : set.finite ((λ (m : ℤ), int.fract (α * ↑m)) '' set.univ) = ff,
  from by auto [set.finite_image],
  have h6 : set.infinite ((λ (m : ℤ), int.fract (α * ↑m)) '' set.univ),
  from by auto [h4, h5, set.infinite_iff_nonempty_and_injective, h3, h2],

  -- By the Bolzano-Weierstrass theorem, $S$ has a limit point in $[0, 1]$. One can thus find pairs of elements of $S$ that are arbitrarily close. Since (the absolute value of) the difference of any two elements of $S$ is also an element of $S$, it follows that $0$ is a limit point of $S$.
  have h7 : ∀ (a : ℝ), ¬(a = 0) → ∃ (ε : ℝ), ε > 0 ∧ ∀ (x : ℝ), x ≠ 0 → x ∉ set.Icc (a - ε) (a + ε),
  from by auto [not_forall],
  have h8 : ∀ (ε : ℝ), ε > 0 → ∃ (x : ℝ), x ∉ set.Icc (-ε) ε,
  from begin
    assume ε hε,
    have h1 : (0 : ℝ) ≠ (ε : ℝ), from by auto [lt_iff_not_ge] using [hε],
    cases h7 ε h1 with ε1 h9,
    have h10 : (ε1 : ℝ) > 0, from by auto [h9],
    have h11 : ε1 ∈ set.Icc (-ε) ε, from by auto [set.mem_Icc],
    have h12 : ε1 ∉ set.Icc (-ε) ε, from by auto [h9, h10, h11],
    show ∃ (x : ℝ), x ∉ set.Icc (-ε) ε, from by auto [h12],
  end,
  have h9 : closure ((λ m : ℤ, int.fract (α * ↑m)) '' set.univ) ≠ set.Icc 0 1,
  from by auto [not_forall, h8],

  -- To show that $S$ is dense in $[0, 1]$, consider $y \in[0,1]$, and $\epsilon>0$. Then by selecting $x \in S$ such that $\{x\}<\epsilon$ (which exists as $0$ is a limit point), and $N$ such that $N \cdot\{x\} \leq y<(N+1) \cdot\{x\}$, we get: $|y-\{N x\}|<\epsilon$.
  have h10 : ∀ (y : ℝ), y ∈ set.Icc 0 1 → ∀ (ε : ℝ), ε > 0 → ∃ (x n : ℤ), (int.fract (α * ↑x)) ∈ (λ (m : ℤ), int.fract (α * ↑m)) '' set.univ ∧ (int.fract (α * ↑x)) < ε ∧ (int.nat_abs (α * ↑n)) ≤ y ∧ y < (int.nat_abs (α * ↑n)) + (int.fract (α * ↑x)),
  from begin
    assume y hy ε hε,
    have h1 : ∀ (n : ℤ), (int.fract (α * ↑n)) ∈ set.Icc 0 1, from by auto [int.fract_lt_one],
    have h2 : ∀ (n : ℤ), (int.fract (α * ↑n)) ∈ (λ (m : ℤ), int.fract (α * ↑m)) '' set.univ, from by auto [set.mem_image],
    have h3 : ∀ (n : ℤ), (int.fract (α * ↑n)) ≠ 0, from by auto [int.fract_ne_zero],
    have h4 : ∀ (n : ℤ), (int.fract (α * ↑n)) > 0, from by auto [lt_iff_le_and_ne, int.fract_pos],
    have h5 : ∀ (n : ℤ), (int.nat_abs (α * ↑n)) ≥ 0, from by auto [int.nat_abs_nonneg],
    have h6 : ∀ (n : ℤ), (int.nat_abs (α * ↑n)) < (int.nat_abs (α * ↑n)) + (int.fract (α * ↑n)), from by auto [int.nat_abs_lt_add_one_iff],
    have h7 : ∀ (n : ℤ), (int.nat_abs (α * ↑n)) < (int.nat_abs (α * ↑n)) + (int.fract (α * ↑n)), from by auto [int.nat_abs_lt_add_one_iff],
    have h8 : ∀ (n : ℤ), (int.nat_abs (α * ↑n)) < (int.nat_abs (α * ↑n)) + (int.fract (α * ↑n)), from by auto [int.nat_abs_lt_add_one_iff],
    have h9 : ∀ (n : ℤ), (int.nat_abs (α * ↑n)) < (int.nat_abs (α * ↑n)) + (int.fract (α * ↑n)), from by auto [int.nat_abs_lt_add_one_iff],
    have h10 : ∀ (n : ℤ), (int.nat_abs (α * ↑n)) < (int.nat_abs (α * ↑n)) + (int.fract (α * ↑n)), from by auto [int.nat_abs_lt_add_one_iff],
    have h11 : ∀ (n : ℤ), (int.fract (α *
end --Needs more than 2000 tokens!

--OUTPUT 7
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
begin

  --$\alpha$ is an irrational number.
  assume (α : ℝ) (hα_irrat : irrational α),

  --Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$.
  have h1 : ∀ (i j : ℤ), (i ≠ j) → (int.fract (α * ↑i) ≠ int.fract (α * ↑j)),
  from begin
    assume (i j : ℤ) (h1 : i ≠ j),
    have h2 : (int.fract (α * ↑i)) - (int.fract (α * ↑j)) = (α * ↑i) - (int.fract (α * ↑i)) - ((α * ↑j) - (int.fract (α * ↑j))), by ring,
    have h3 : (α * ↑i) - (int.fract (α * ↑i)) = (α * ↑j) - (int.fract (α * ↑j)),
    from by auto [h2, sub_eq_add_neg, int.fract_mul_self_eq, int.fract_add_self_eq, int.fract_mul_self_eq, int.fract_add_self_eq, int.fract_mul_self_eq, int.fract_add_self_eq],
    have h4 : α = (int.fract (α * ↑i) - int.fract (α * ↑j)) / (i - j),
    from by auto [sub_eq_add_neg, int.fract_mul_self_eq, int.fract_add_self_eq, int.fract_mul_self_eq, int.fract_add_self_eq],
    show (int.fract (α * ↑i)) ≠ (int.fract (α * ↑j)),
    from by auto [h1, h3, h4, hα_irrat, quotient.exists_rep],
  end,

  --Hence, $S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}$ is an infinite subset of $\left[0,1\right]$.
  have h2 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h3 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h4 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h5 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h6 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h7 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h8 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h9 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h10 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h11 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h12 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h13 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h14 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h15 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h16 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h17 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h18 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h19 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h20 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h21 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h22 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h23 : (λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ) ⊆ set.Icc 0 1, from by auto [int.fract_nonneg, int.fract_lt_one],
  have h24 : (λ m : ℤ, int.
end --Needs more than 2000 tokens!

--OUTPUT 8
theorem irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
begin
  -- Let $\alpha$ be an irrational number.
  -- Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$.
  -- If this were not true, then
  -- $$
  -- i \alpha-\lfloor i \alpha\rfloor=\{i \alpha\}=\{j \alpha\}=j \alpha-\lfloor j \alpha\rfloor,
  -- $$
  -- which yields the false statement $\alpha=\frac{\lfloor i \alpha\rfloor-\lfloor j \alpha\rfloor}{i-j} \in \mathbb{Q}$.
  have h1 : ∀ (i j : ℤ), i ≠ j → (int.fract (α * ↑i)) ≠ (int.fract (α * ↑j)),
  from begin
    -- We consider two cases, when $i=j$ and when $i\neq j$.
    assume (i j : ℤ) (h3 : i ≠ j),
    -- In the first case, we assume $i=j$.
    assume h4 : (int.fract (α * ↑i)) = (int.fract (α * ↑j)),
    -- Then we have a contradiction, as $i\neq j$.
    have h5 : i = j, from by auto [h4],
    have h6 : false, from by auto [h3, h5],
    show (int.fract (α * ↑i)) ≠ (int.fract (α * ↑j)), from by auto [h6],
  end,

  -- Hence,
  -- $$
  -- S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}
  -- $$
  -- is an infinite subset of $\left[0,1\right]$.
  have h7 : nonempty ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)), from by auto [h1],

  -- By the Bolzano-Weierstrass theorem, $S$ has a limit point in $[0, 1]$.
  have h8 : ∃ x, x ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)), from by auto using [h7, set.finite_univ],

  -- One can thus find pairs of elements of $S$ that are arbitrarily close.
  have h9 : ∀ ε > 0, ∃ x y, x ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ y ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ abs (x - y) < ε,
  from begin
    assume (ε : ℝ) (h10 : ε > 0),
    cases h8 with x h11,
    cases h11 ε h10 with N h12,
    cases h12 N with y h13,
    have h14 : y ∈ _, from by auto [set.mem_univ, set.mem_image, set.mem_closure, set.subset_univ],
    use x,
    use y,
    have h15 : abs (x - y) < ε, from by auto [h13],
    show x ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ y ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ abs (x - y) < ε, from by auto [h11, h14, h15],
  end,

  -- Since (the absolute value of) the difference of any two elements of $S$ is also an element of $S$, it follows that $0$ is a limit point of $S$.
  have h16 : ∀ ε > 0, ∃ (y : ℝ), y ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ abs y < ε,
  from begin
    assume (ε : ℝ) (h17 : ε > 0),
    cases h9 ε h17 with x h18,
    cases h18 with y h19,
    cases h19 with h20 h21,
    cases h21 with h22 h23,
    have h24 : abs (0 - y) < ε, from by auto [h23],
    use y,
    show y ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ abs y < ε, from by auto [h20, h24],
  end,

  -- To show that $S$ is dense in $[0, 1]$, consider $y \in[0,1]$, and $\epsilon>0$.
  have h25 : ∀ (y : ℝ), y ∈ Icc 0 1 → ∀ ε > 0, ∃ (x : ℝ), x ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ abs (y - x) < ε,
  from begin
    assume (y : ℝ) (h26 : y ∈ Icc 0 1) (ε : ℝ) (h27 : ε > 0),
    cases h16 ε h27 with x h28,
    cases h28 with h29 h30,

    -- Then by selecting $x \in S$ such that $\{x\}<\epsilon$ (which exists as $0$ is a limit point), and $N$ such that $N \cdot\{x\} \leq y<(N+1) \cdot\{x\}$, we get: $|y-\{N x\}|<\epsilon$.
    have h31 : ∃ (m : ℤ), ((m : ℝ) * x) ≤ y ∧ y < ((m + 1) : ℝ) * x,
    from by auto [int.coe_nat_lt, int.coe_nat_le, int.coe_nat_pos, int.coe_nat_add, int.coe_nat_mul, int.fract_lt_one],
    cases h31 with m h32,
    cases h32 with h33 h34,
    have h35 : abs (y - int.fract (↑m * x)) < ε, from by auto [h26, h30, h33, h34],
    use int.fract (↑m * x),
    show int.fract (↑m * x) ∈ closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)) ∧ abs (y - int.fract (↑m * x)) < ε, from by auto [h29, h35],
  end,

  -- Let $A := closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ))$.
  let A := closure ((λ (m : ℤ), int.fract (α * ↑m)) '' (@set.univ ℤ)),

  -- Then $A \subseteq \{0, 1\}$
  have h36 : A ⊆ {0, 1}, from by auto [set.mem_closure, set.mem_univ, set.mem_image, int.fract_lt_one, set.mem_singleton],

  -- Let $B := \{0, 1\}$.
  let B := {0, 1},

  -- Then $A = B$
  have h37 : A = B, from by auto [set.ext, set.mem_singleton, h36],

  -- Then $A = \{0, 1\}$
  have h38 : A = {0, 1}, from by auto [h37],

  -- Then $A$ is dense in $\{0, 1\}$
  have h39
end --Needs more than 2000 tokens!

/- FEW SHOT PROMPTS TO CODEX(START)
/--`theorem`
Power Set is Closed under Intersection
Let $S$ be a set.

Let $\powerset S$ be the power set of $S$.

Then:
:$\forall A, B \in \powerset S: A \cap B \in \powerset S$
`proof`
Let $A, B \in \powerset S$.

Then by the definition of power set, $A \subseteq S$ and $B \subseteq S$.

From Intersection is Subset we have that $A \cap B \subseteq A$.

It follows from Subset Relation is Transitive that $A \cap B \subseteq S$.

Thus $A \cap B \in \powerset S$ and closure is proved.
{{qed}}
-/
theorem power_set_intersection_closed {α : Type*} (S : set α) : ∀ A B ∈ 𝒫 S, (A ∩ B) ∈ 𝒫 S :=
begin
  -- $A$ and $B$ are sets. $A$ and $B$ belong to power set of $S$
  assume (A : set α) (hA : A ∈ 𝒫 S) (B : set α) (hB : B ∈ 𝒫 S),
  -- Then $A ⊆ S$ and $B ⊆ S$, by power set definition
  have h1 : (A ⊆ S) ∧ (B ⊆ S), from by auto [set.subset_of_mem_powerset, set.subset_of_mem_powerset],
  -- Then $(A ∩ B) ⊆ A$, by intersection of set is a subset
  have h2 : (A ∩ B) ⊆ A, from by auto [set.inter_subset_left],
  -- Then $(A ∩ B) ⊆ S$, by subset relation is transitive
  have h3 : (A ∩ B) ⊆ S, from by auto [set.subset.trans],
  -- Hence $(A ∩ B) ∈  𝒫 S$, by power set definition
  show (A ∩ B) ∈  𝒫 S, from by auto [set.mem_powerset],
end

/--`theorem`
Square of Sum
 :$\forall x, y \in \R: \paren {x + y}^2 = x^2 + 2 x y + y^2$
`proof`
Follows from the distribution of multiplication over addition:

{{begin-eqn}}
{{eqn | l = \left({x + y}\right)^2
      | r = \left({x + y}\right) \cdot \left({x + y}\right)
}}
{{eqn | r = x \cdot \left({x + y}\right) + y \cdot \left({x + y}\right)
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x \cdot x + x \cdot y + y \cdot x + y \cdot y
      | c = Real Multiplication Distributes over Addition
}}
{{eqn | r = x^2 + 2xy + y^2
      | c =
}}
{{end-eqn}}
{{qed}}
-/
theorem square_of_sum (x y : ℝ) : (x + y)^2 = (x^2 + 2*x*y + y^2) :=
begin
  -- expand the power
  calc (x + y)^2 = (x+y)*(x+y) : by auto [sq]
  -- distributive property of multiplication over addition gives:
  ... = x*(x+y) + y*(x+y) : by auto [add_mul]
  -- applying the above property further gives:
  ... = x*x + x*y + y*x + y*y : by auto [mul_comm, add_mul] using [ring]
  -- rearranging the terms using commutativity and adding gives:
  ... = x^2 + 2*x*y + y^2 : by auto [sq, mul_comm] using [ring]
end

/--`theorem`
Identity of Group is Unique
Let $\struct {G, \circ}$ be a group. Then there is a unique identity element $e \in G$.
`proof`
From Group has Latin Square Property, there exists a unique $x \in G$ such that:
:$a x = b$

and there exists a unique $y \in G$ such that:
:$y a = b$

Setting $b = a$, this becomes:

There exists a unique $x \in G$ such that:
:$a x = a$

and there exists a unique $y \in G$ such that:
:$y a = a$

These $x$ and $y$ are both $e$, by definition of identity element.
{{qed}}
-/
theorem group_identity_unique {G : Type*} [group G] : ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a :=
begin
  -- Group has Latin Square Property
  have h1 : ∀ a b : G, ∃! x : G, a * x = b, from by auto using [use (a⁻¹ * b)],
  have h2 : ∀ a b : G, ∃! y : G, y * a = b, from by auto using [use b * a⁻¹],

  -- Setting $b = a$, this becomes:
  have h3 : ∀ a : G, ∃! x : G, a * x = a, from by auto [h1],
  have h4 : ∀ a : G, ∃! y : G, y * a = a, from by auto [h2],

  -- These $x$ and $y$ are both $(1 : G)$, by definition of identity element
  have h5 : ∀ a : G, classical.some (h3 a).exists = (1 : G), from by auto [exists_unique.unique, h3, classical.some_spec, exists_unique.exists, mul_one],
  have h6 : ∀ a : G, classical.some (h4 a).exists = (1 : G), from by auto [exists_unique.unique, h4, classical.some_spec, exists_unique.exists, one_mul],

  show ∃! e : G, ∀ a : G, e * a = a ∧ a * e = a, from by auto [h3, h4, exists_unique.unique, classical.some_spec, exists_unique.exists] using [use (1 : G)],
end

/--`theorem`
Squeeze Theorem for Real Numbers
Let $\sequence {x_n}$, $\sequence {y_n}$ and $\sequence {z_n}$ be sequences in $\R$.

Let $\sequence {y_n}$ and $\sequence {z_n}$ both be convergent to the following limit:
:$\ds \lim_{n \mathop \to \infty} y_n = l, \lim_{n \mathop \to \infty} z_n = l$

Suppose that:
:$\forall n \in \N: y_n \le x_n \le z_n$

Then:
:$x_n \to l$ as $n \to \infty$
that is:
:$\ds \lim_{n \mathop \to \infty} x_n = l$

`proof`
From Negative of Absolute Value:
:$\size {x - l} < \epsilon \iff l - \epsilon < x < l + \epsilon$

Let $\epsilon > 0$.

We need to prove that:
:$\exists N: \forall n > N: \size {x_n - l} < \epsilon$

As $\ds \lim_{n \mathop \to \infty} y_n = l$ we know that:
:$\exists N_1: \forall n > N_1: \size {y_n - l} < \epsilon$

As $\ds \lim_{n \mathop \to \infty} z_n = l$ we know that:
:$\exists N_2: \forall n > N_2: \size {z_n - l} < \epsilon$

Let $N = \max \set {N_1, N_2}$.

Then if $n > N$, it follows that $n > N_1$ and $n > N_2$.

So:
:$\forall n > N: l - \epsilon < y_n < l + \epsilon$
:$\forall n > N: l - \epsilon < z_n < l + \epsilon$

But:
:$\forall n \in \N: y_n \le x_n \le z_n$

So:
:$\forall n > N: l - \epsilon < y_n \le x_n \le z_n < l + \epsilon$

and so:
:$\forall n > N: l - \epsilon < x_n < l + \epsilon$

So:
:$\forall n > N: \size {x_n - l} < \epsilon$

Hence the result.
{{qed}}

-/
theorem squeeze_theorem_real_numbers (x y z : ℕ → ℝ) (l : ℝ) :
let seq_limit : (ℕ → ℝ) → ℝ → Prop :=  λ (u : ℕ → ℝ) (l : ℝ), ∀ ε > 0, ∃ N, ∀ n > N, |u n - l| < ε in
 seq_limit y l → seq_limit z l →  (∀ n : ℕ, (y n) ≤ (x n) ∧ (x n) ≤ (z n)) → seq_limit x l :=
begin
  assume seq_limit (h2 : seq_limit y l) (h3 : seq_limit z l) (h4 : ∀ (n : ℕ), y n ≤ x n ∧ x n ≤ z n) (ε),

  --From Negative of Absolute Value: $\size {x - l} < \epsilon \iff l - \epsilon < x < l + \epsilon$
  have h5 : ∀ x, |x - l| < ε ↔ (((l - ε) < x) ∧ (x < (l + ε))),
  from by auto [abs_sub_lt_iff] using [linarith],

  --Let $\epsilon > 0$.
  assume (h7 : ε > 0),

  --As $\ds \lim_{n \mathop \to \infty} y_n = l$ we know that $\exists N_1: \forall n > N_1: \size {y_n - l} < \epsilon$
  cases h2 ε h7 with N1 h8,

  --As $\ds \lim_{n \mathop \to \infty} z_n = l$ we know that $\exists N_2: \forall n > N_2: \size {z_n - l} < \epsilon$
  cases h3 ε h7 with N2 h9,

  --Let $N = \max \set {N_1, N_2}$.
  let N := max N1 N2,
  use N,

  --Then if $n > N$, it follows that $n > N_1$ and $n > N_2$.
  have h10 : ∀ n > N, n > N1 ∧ n > N2 := by auto [lt_of_le_of_lt, le_max_left, le_max_right],

  --$\forall n > N: l - \epsilon < y_n < l + \epsilon$
  --$\forall n > N: l - \epsilon < z_n < l + \epsilon$
  --$\forall n \in \N: y_n \le x_n \le z_n$
  --So $\forall n > N: l - \epsilon < y_n \le x_n \le z_n < l + \epsilon$
  have h11 : ∀ n > N, (((l - ε) < (y n)) ∧ ((y n) ≤ (x n))) ∧ (((x n) ≤ (z n)) ∧ ((z n) < l+ε)),
  from by auto [h8, h10, h5, h9],

  --$\forall n > N: l - \epsilon < x_n < l + \epsilon$
  have h15 : ∀ n > N, ((l - ε) < (x n)) ∧ ((x n) < (l+ε)),
  from by auto [h11] using [linarith],

  --So $\forall n > N: \size {x_n - l} < \epsilon$
  --Hence the result
  show  ∀ (n : ℕ), n > N → |x n - l| < ε,
  from by auto [h5, h15],

end

/--`theorem`
Density of irrational orbit
The fractional parts of the integer multiples of an irrational number form a dense subset of the unit interval
`proof`
Let $\alpha$ be an irrational number. Then for distinct $i, j \in \mathbb{Z}$, we must have $\{i \alpha\} \neq\{j \alpha\}$. If this were not true, then
$$
i \alpha-\lfloor i \alpha\rfloor=\{i \alpha\}=\{j \alpha\}=j \alpha-\lfloor j \alpha\rfloor,
$$
which yields the false statement $\alpha=\frac{\lfloor i \alpha\rfloor-\lfloor j \alpha\rfloor}{i-j} \in \mathbb{Q}$. Hence,
$$
S:=\{\{i \alpha\} \mid i \in \mathbb{Z}\}
$$
is an infinite subset of $\left[0,1\right]$.

By the Bolzano-Weierstrass theorem, $S$ has a limit point in $[0, 1]$. One can thus find pairs of elements of $S$ that are arbitrarily close. Since (the absolute value of) the difference of any two elements of $S$ is also an element of $S$, it follows that $0$ is a limit point of $S$.

To show that $S$ is dense in $[0, 1]$, consider $y \in[0,1]$, and $\epsilon>0$. Then by selecting $x \in S$ such that $\{x\}<\epsilon$ (which exists as $0$ is a limit point), and $N$ such that $N \cdot\{x\} \leq y<(N+1) \cdot\{x\}$, we get: $|y-\{N x\}|<\epsilon$.

QED
-/
theorem  irrational_orbit_dense {α : ℝ} (hα_irrat : irrational α) : closure ((λ m : ℤ, int.fract (α * ↑m)) '' (@set.univ ℤ)) = set.Icc 0 1 :=
FEW SHOT PROMPTS TO CODEX(END)-/


/-
Copyright (c) 2018 Sébastien Gouëzel. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Sébastien Gouëzel
-/
import analysis.normed_space.lp_space
import topology.compacts

/-!
# The Kuratowski embedding

Any separable metric space can be embedded isometrically in `ℓ^∞(ℝ)`.
-/

noncomputable theory

open set metric topological_space
open_locale ennreal
local notation `ℓ_infty_ℝ`:= lp (λ n : ℕ, ℝ) ∞

universes u v w
variables {α : Type u} {β : Type v} {γ : Type w}

namespace Kuratowski_embedding

/-! ### Any separable metric space can be embedded isometrically in ℓ^∞(ℝ) -/

variables {f g : ℓ_infty_ℝ} {n : ℕ} {C : ℝ} [metric_space α] (x : ℕ → α) (a b : α)

/-- A metric space can be embedded in `l^∞(ℝ)` via the distances to points in
a fixed countable set, if this set is dense. This map is given in `Kuratowski_embedding`,
without density assumptions. -/
def embedding_of_subset : ℓ_infty_ℝ :=
⟨ λ n, dist a (x n) - dist (x 0) (x n),
  begin
    apply mem_ℓp_infty,
    use dist a (x 0),
    rintros - ⟨n, rfl⟩,
    exact abs_dist_sub_le _ _ _
  end ⟩

lemma embedding_of_subset_coe : embedding_of_subset x a n = dist a (x n) - dist (x 0) (x n) := rfl

/-- The embedding map is always a semi-contraction. -/
lemma embedding_of_subset_dist_le (a b : α) :
  dist (embedding_of_subset x a) (embedding_of_subset x b) ≤ dist a b :=
begin
  refine lp.norm_le_of_forall_le dist_nonneg (λn, _),
  simp only [lp.coe_fn_sub, pi.sub_apply, embedding_of_subset_coe, real.dist_eq],
  convert abs_dist_sub_le a b (x n) using 2,
  ring
end

/-- When the reference set is dense, the embedding map is an isometry on its image. -/
lemma embedding_of_subset_isometry (H : dense_range x) : isometry (embedding_of_subset x) :=
begin
  refine isometry_emetric_iff_metric.2 (λa b, _),
  refine (embedding_of_subset_dist_le x a b).antisymm (le_of_forall_pos_le_add (λe epos, _)),
  /- First step: find n with dist a (x n) < e -/
  rcases metric.mem_closure_range_iff.1 (H a) (e/2) (half_pos epos) with ⟨n, hn⟩,
  /- Second step: use the norm control at index n to conclude -/
  have C : dist b (x n) - dist a (x n) = embedding_of_subset x b n - embedding_of_subset x a n :=
    by { simp only [embedding_of_subset_coe, sub_sub_sub_cancel_right] },
  have := calc
    dist a b ≤ dist a (x n) + dist (x n) b : dist_triangle _ _ _
    ...    = 2 * dist a (x n) + (dist b (x n) - dist a (x n)) : by { simp [dist_comm], ring }
    ...    ≤ 2 * dist a (x n) + |dist b (x n) - dist a (x n)| :
      by apply_rules [add_le_add_left, le_abs_self]
    ...    ≤ 2 * (e/2) + |embedding_of_subset x b n - embedding_of_subset x a n| :
      begin rw C, apply_rules [add_le_add, mul_le_mul_of_nonneg_left, hn.le, le_refl], norm_num end
    ...    ≤ 2 * (e/2) + dist (embedding_of_subset x b) (embedding_of_subset x a) :
    begin
      have : |embedding_of_subset x b n - embedding_of_subset x a n|
        ≤ dist (embedding_of_subset x b) (embedding_of_subset x a),
      { simpa [dist_eq_norm] using lp.norm_apply_le_norm ennreal.top_ne_zero
          (embedding_of_subset x b - embedding_of_subset x a) n },
      nlinarith,
    end
    ...    = dist (embedding_of_subset x b) (embedding_of_subset x a) + e : by ring,
  simpa [dist_comm] using this
end

/-- Every separable metric space embeds isometrically in `ℓ_infty_ℝ`. -/
theorem exists_isometric_embedding (α : Type u) [metric_space α] [separable_space α] :
  ∃(f : α → ℓ_infty_ℝ), isometry f :=
begin
  cases (univ : set α).eq_empty_or_nonempty with h h,
  { use (λ_, 0), assume x, exact absurd h (nonempty.ne_empty ⟨x, mem_univ x⟩) },
  { /- We construct a map x : ℕ → α with dense image -/
    rcases h with ⟨basepoint⟩,
    haveI : inhabited α := ⟨basepoint⟩,
    have : ∃s:set α, countable s ∧ dense s := exists_countable_dense α,
    rcases this with ⟨S, ⟨S_countable, S_dense⟩⟩,
    rcases countable_iff_exists_surjective.1 S_countable with ⟨x, x_range⟩,
    /- Use embedding_of_subset to construct the desired isometry -/
    exact ⟨embedding_of_subset x, embedding_of_subset_isometry x (S_dense.mono x_range)⟩ }
end
end Kuratowski_embedding

open topological_space Kuratowski_embedding

/-- The Kuratowski embedding is an isometric embedding of a separable metric space in `ℓ^∞(ℝ)`. -/
def Kuratowski_embedding (α : Type u) [metric_space α] [separable_space α] : α → ℓ_infty_ℝ :=
  classical.some (Kuratowski_embedding.exists_isometric_embedding α)

/-- The Kuratowski embedding is an isometry. -/
protected lemma Kuratowski_embedding.isometry (α : Type u) [metric_space α] [separable_space α] :
  isometry (Kuratowski_embedding α) :=
classical.some_spec (exists_isometric_embedding α)

/-- Version of the Kuratowski embedding for nonempty compacts -/
def nonempty_compacts.Kuratowski_embedding (α : Type u) [metric_space α] [compact_space α]
  [nonempty α] :
  nonempty_compacts ℓ_infty_ℝ :=
⟨range (Kuratowski_embedding α), range_nonempty _,
  is_compact_range (Kuratowski_embedding.isometry α).continuous⟩


import data.quot
import group_theory.subgroup.basic
import group_theory.quotient_group
import group_theory.specific_groups.cyclic
import tactic

variables {G : Type*} [group G]

variable (G)
-- Extra practice: rolling out my own definition of the center of a subgroup
def center : subgroup G :=
{ carrier := {g : G | ∀ (x : G), g * x = x * g},
  one_mem' := λ x, by group,
  mul_mem' := λ a b ha hb x, by conv_lhs {rw [ha b, mul_assoc, hb (a * x), ha x, mul_assoc]},
  inv_mem' := λ g hg x, begin
    apply (@mul_left_cancel G _ g _ _),
    apply (@mul_right_cancel G _ _ g _),
    simp [hg (x * g⁻¹)],
  end }

variable {G}
-- I'll use Lean's definition of a normal subgroup for convenience
instance center_normal : (center G).normal :=
⟨λ n hn g x, begin
  conv_lhs {rw [←hn g]},
  conv_rhs {rw [←hn g]},
  simp [hn x],
end⟩

-- Back to the original problem: if G/Z(G) is cyclic then G is abelian
-- Here I'll use the `subgroup.center` provided in the library instead of my own
theorem comm_of_quot_center_cyclic (hG : is_cyclic (G ⧸ subgroup.center G)) :
  ∀ (a b : G), a * b = b * a :=
begin
  intros a b,
  obtain ⟨gbar, h_gbar⟩ := hG.exists_generator,
  let g := quotient.out' gbar,
  simp_rw subgroup.mem_zpowers_iff at h_gbar,

  have h1 : ∀ (x : G), ∃ (y ∈ subgroup.center G) (k : ℤ), x = g ^ k * y := by {
    intro x,
    obtain ⟨k, hk⟩ := h_gbar x,
    have hh1 : ↑(g ^ k) = gbar ^ k := by {
      rw quotient_group.coe_zpow,
      congr,
      rw ←quotient.out_eq' gbar, refl
    },
    rw [←hh1, quotient_group.eq_iff_div_mem] at hk,

    have hh2 : x / g ^ k ∈ subgroup.center G := by rwa [←inv_div, inv_mem_iff],
    use x / g ^ k, split,
      assumption,
    use k,
    simp [hh2 (g ^ k)],
  },

  obtain ⟨ya, ha1, ka, ha2⟩ := h1 a,
  obtain ⟨yb, hb1, kb, hb2⟩ := h1 b,
  rw [ha2, hb2],
  calc g ^ ka * ya * (g ^ kb * yb) = g ^ ka * (ya * g ^ kb) * yb : by group
  ... = g ^ ka * (g ^ kb * ya) * yb : by rw ha1 _
  ... = g ^ ka * g ^ kb * (ya * yb) : by group
  ... = g ^ kb * g ^ ka * (yb * ya) : by {rw [ha1 _], group}
  ... = g ^ kb * (g ^ ka * yb) * ya : by group
  ... = g ^ kb * (yb * g ^ ka) * ya : by rw hb1 _
  ... = g ^ kb * yb * (g ^ ka * ya) : by group
end


/-
Copyright (c) 2020 Anatole Dedecker. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anatole Dedecker

! This file was ported from Lean 3 source module analysis.asymptotics.asymptotic_equivalent
! 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.Asymptotics.Asymptotics
import Mathbin.Analysis.Normed.Order.Basic

/-!
# Asymptotic equivalence

In this file, we define the relation `is_equivalent l u v`, which means that `u-v` is little o of
`v` along the filter `l`.

Unlike `is_[oO]` relations, this one requires `u` and `v` to have the same codomain `β`. While the
definition only requires `β` to be a `normed_add_comm_group`, most interesting properties require it
to be a `normed_field`.

## Notations

We introduce the notation `u ~[l] v := is_equivalent l u v`, which you can use by opening the
`asymptotics` locale.

## Main results

If `β` is a `normed_add_comm_group` :

- `_ ~[l] _` is an equivalence relation
- Equivalent statements for `u ~[l] const _ c` :
  - If `c ≠ 0`, this is true iff `tendsto u l (𝓝 c)` (see `is_equivalent_const_iff_tendsto`)
  - For `c = 0`, this is true iff `u =ᶠ[l] 0` (see `is_equivalent_zero_iff_eventually_zero`)

If `β` is a `normed_field` :

- Alternative characterization of the relation (see `is_equivalent_iff_exists_eq_mul`) :

  `u ~[l] v ↔ ∃ (φ : α → β) (hφ : tendsto φ l (𝓝 1)), u =ᶠ[l] φ * v`

- Provided some non-vanishing hypothesis, this can be seen as `u ~[l] v ↔ tendsto (u/v) l (𝓝 1)`
  (see `is_equivalent_iff_tendsto_one`)
- For any constant `c`, `u ~[l] v` implies `tendsto u l (𝓝 c) ↔ tendsto v l (𝓝 c)`
  (see `is_equivalent.tendsto_nhds_iff`)
- `*` and `/` are compatible with `_ ~[l] _` (see `is_equivalent.mul` and `is_equivalent.div`)

If `β` is a `normed_linear_ordered_field` :

- If `u ~[l] v`, we have `tendsto u l at_top ↔ tendsto v l at_top`
  (see `is_equivalent.tendsto_at_top_iff`)

## Implementation Notes

Note that `is_equivalent` takes the parameters `(l : filter α) (u v : α → β)` in that order.
This is to enable `calc` support, as `calc` requires that the last two explicit arguments are `u v`.

-/

namespace Asymptotics

open Filter Function

open Topology

section NormedAddCommGroup

variable {α β : Type _} [NormedAddCommGroup β]

/-- Two functions `u` and `v` are said to be asymptotically equivalent along a filter `l` when
    `u x - v x = o(v x)` as x converges along `l`. -/
def IsEquivalent (l : Filter α) (u v : α → β) :=
  (u - v) =o[l] v
#align asymptotics.is_equivalent Asymptotics.IsEquivalent

-- mathport name: asymptotics.is_equivalent
scoped notation:50 u " ~[" l:50 "] " v:50 => Asymptotics.IsEquivalent l u v

variable {u v w : α → β} {l : Filter α}

theorem IsEquivalent.isOCat (h : u ~[l] v) : (u - v) =o[l] v :=
  h
#align asymptotics.is_equivalent.is_o Asymptotics.IsEquivalent.isOCat

theorem IsEquivalent.isO (h : u ~[l] v) : u =O[l] v :=
  (IsO.congr_of_sub h.IsO.symm).mp (isO_refl _ _)
#align asymptotics.is_equivalent.is_O Asymptotics.IsEquivalent.isO

theorem IsEquivalent.isO_symm (h : u ~[l] v) : v =O[l] u :=
  by
  convert h.is_o.right_is_O_add
  ext
  simp
#align asymptotics.is_equivalent.is_O_symm Asymptotics.IsEquivalent.isO_symm

@[refl]
theorem IsEquivalent.refl : u ~[l] u :=
  by
  rw [is_equivalent, sub_self]
  exact is_o_zero _ _
#align asymptotics.is_equivalent.refl Asymptotics.IsEquivalent.refl

@[symm]
theorem IsEquivalent.symm (h : u ~[l] v) : v ~[l] u :=
  (h.IsOCat.trans_isO h.isO_symm).symm
#align asymptotics.is_equivalent.symm Asymptotics.IsEquivalent.symm

@[trans]
theorem IsEquivalent.trans {l : Filter α} {u v w : α → β} (huv : u ~[l] v) (hvw : v ~[l] w) :
    u ~[l] w :=
  (huv.IsOCat.trans_isO hvw.IsO).triangle hvw.IsOCat
#align asymptotics.is_equivalent.trans Asymptotics.IsEquivalent.trans

theorem IsEquivalent.congr_left {u v w : α → β} {l : Filter α} (huv : u ~[l] v) (huw : u =ᶠ[l] w) :
    w ~[l] v :=
  huv.congr' (huw.sub (EventuallyEq.refl _ _)) (EventuallyEq.refl _ _)
#align asymptotics.is_equivalent.congr_left Asymptotics.IsEquivalent.congr_left

theorem IsEquivalent.congr_right {u v w : α → β} {l : Filter α} (huv : u ~[l] v) (hvw : v =ᶠ[l] w) :
    u ~[l] w :=
  (huv.symm.congr_left hvw).symm
#align asymptotics.is_equivalent.congr_right Asymptotics.IsEquivalent.congr_right

theorem isEquivalent_zero_iff_eventually_zero : u ~[l] 0 ↔ u =ᶠ[l] 0 :=
  by
  rw [is_equivalent, sub_zero]
  exact is_o_zero_right_iff
#align asymptotics.is_equivalent_zero_iff_eventually_zero Asymptotics.isEquivalent_zero_iff_eventually_zero

theorem isEquivalent_zero_iff_isO_zero : u ~[l] 0 ↔ u =O[l] (0 : α → β) :=
  by
  refine' ⟨is_equivalent.is_O, fun h => _⟩
  rw [is_equivalent_zero_iff_eventually_zero, eventually_eq_iff_exists_mem]
  exact ⟨{ x : α | u x = 0 }, is_O_zero_right_iff.mp h, fun x hx => hx⟩
#align asymptotics.is_equivalent_zero_iff_is_O_zero Asymptotics.isEquivalent_zero_iff_isO_zero

theorem isEquivalent_const_iff_tendsto {c : β} (h : c ≠ 0) : u ~[l] const _ c ↔ Tendsto u l (𝓝 c) :=
  by
  rw [is_equivalent, is_o_const_iff h]
  constructor <;> intro h <;>
          [·
            have := h.sub tendsto_const_nhds
            rw [zero_sub (-c)] at this,
          · have := h.sub tendsto_const_nhds
            rw [← sub_self c]] <;>
        convert this <;>
      try ext <;>
    simp
#align asymptotics.is_equivalent_const_iff_tendsto Asymptotics.isEquivalent_const_iff_tendsto

theorem IsEquivalent.tendsto_const {c : β} (hu : u ~[l] const _ c) : Tendsto u l (𝓝 c) :=
  by
  rcases em <| c = 0 with ⟨rfl, h⟩
  · exact (tendsto_congr' <| is_equivalent_zero_iff_eventually_zero.mp hu).mpr tendsto_const_nhds
  · exact (is_equivalent_const_iff_tendsto h).mp hu
#align asymptotics.is_equivalent.tendsto_const Asymptotics.IsEquivalent.tendsto_const

theorem IsEquivalent.tendsto_nhds {c : β} (huv : u ~[l] v) (hu : Tendsto u l (𝓝 c)) :
    Tendsto v l (𝓝 c) := by
  by_cases h : c = 0
  · subst c
    rw [← is_o_one_iff ℝ] at hu⊢
    simpa using (huv.symm.is_o.trans hu).add hu
  · rw [← is_equivalent_const_iff_tendsto h] at hu⊢
    exact huv.symm.trans hu
#align asymptotics.is_equivalent.tendsto_nhds Asymptotics.IsEquivalent.tendsto_nhds

theorem IsEquivalent.tendsto_nhds_iff {c : β} (huv : u ~[l] v) :
    Tendsto u l (𝓝 c) ↔ Tendsto v l (𝓝 c) :=
  ⟨huv.tendsto_nhds, huv.symm.tendsto_nhds⟩
#align asymptotics.is_equivalent.tendsto_nhds_iff Asymptotics.IsEquivalent.tendsto_nhds_iff

theorem IsEquivalent.add_isOCat (huv : u ~[l] v) (hwv : w =o[l] v) : u + w ~[l] v := by
  simpa only [is_equivalent, add_sub_right_comm] using huv.add hwv
#align asymptotics.is_equivalent.add_is_o Asymptotics.IsEquivalent.add_isOCat

theorem IsEquivalent.sub_isOCat (huv : u ~[l] v) (hwv : w =o[l] v) : u - w ~[l] v := by
  simpa only [sub_eq_add_neg] using huv.add_is_o hwv.neg_left
#align asymptotics.is_equivalent.sub_is_o Asymptotics.IsEquivalent.sub_isOCat

theorem IsOCat.add_isEquivalent (hu : u =o[l] w) (hv : v ~[l] w) : u + v ~[l] w :=
  add_comm v u ▸ hv.add_isOCat hu
#align asymptotics.is_o.add_is_equivalent Asymptotics.IsOCat.add_isEquivalent

theorem IsOCat.isEquivalent (huv : (u - v) =o[l] v) : u ~[l] v :=
  huv
#align asymptotics.is_o.is_equivalent Asymptotics.IsOCat.isEquivalent

theorem IsEquivalent.neg (huv : u ~[l] v) : (fun x => -u x) ~[l] fun x => -v x :=
  by
  rw [is_equivalent]
  convert huv.is_o.neg_left.neg_right
  ext
  simp
#align asymptotics.is_equivalent.neg Asymptotics.IsEquivalent.neg

end NormedAddCommGroup

open Asymptotics

section NormedField

variable {α β : Type _} [NormedField β] {t u v w : α → β} {l : Filter α}

theorem isEquivalent_iff_exists_eq_mul :
    u ~[l] v ↔ ∃ (φ : α → β)(hφ : Tendsto φ l (𝓝 1)), u =ᶠ[l] φ * v :=
  by
  rw [is_equivalent, is_o_iff_exists_eq_mul]
  constructor <;> rintro ⟨φ, hφ, h⟩ <;> [use φ + 1, use φ - 1] <;> constructor
  · conv in 𝓝 _ => rw [← zero_add (1 : β)]
    exact hφ.add tendsto_const_nhds
  · convert h.add (eventually_eq.refl l v) <;> ext <;> simp [add_mul]
  · conv in 𝓝 _ => rw [← sub_self (1 : β)]
    exact hφ.sub tendsto_const_nhds
  · convert h.sub (eventually_eq.refl l v) <;> ext <;> simp [sub_mul]
#align asymptotics.is_equivalent_iff_exists_eq_mul Asymptotics.isEquivalent_iff_exists_eq_mul

theorem IsEquivalent.exists_eq_mul (huv : u ~[l] v) :
    ∃ (φ : α → β)(hφ : Tendsto φ l (𝓝 1)), u =ᶠ[l] φ * v :=
  isEquivalent_iff_exists_eq_mul.mp huv
#align asymptotics.is_equivalent.exists_eq_mul Asymptotics.IsEquivalent.exists_eq_mul

theorem isEquivalent_of_tendsto_one (hz : ∀ᶠ x in l, v x = 0 → u x = 0)
    (huv : Tendsto (u / v) l (𝓝 1)) : u ~[l] v :=
  by
  rw [is_equivalent_iff_exists_eq_mul]
  refine' ⟨u / v, huv, hz.mono fun x hz' => (div_mul_cancel_of_imp hz').symm⟩
#align asymptotics.is_equivalent_of_tendsto_one Asymptotics.isEquivalent_of_tendsto_one

theorem isEquivalent_of_tendsto_one' (hz : ∀ x, v x = 0 → u x = 0) (huv : Tendsto (u / v) l (𝓝 1)) :
    u ~[l] v :=
  isEquivalent_of_tendsto_one (eventually_of_forall hz) huv
#align asymptotics.is_equivalent_of_tendsto_one' Asymptotics.isEquivalent_of_tendsto_one'

theorem isEquivalent_iff_tendsto_one (hz : ∀ᶠ x in l, v x ≠ 0) :
    u ~[l] v ↔ Tendsto (u / v) l (𝓝 1) := by
  constructor
  · intro hequiv
    have := hequiv.is_o.tendsto_div_nhds_zero
    simp only [Pi.sub_apply, sub_div] at this
    have key : tendsto (fun x => v x / v x) l (𝓝 1) :=
      (tendsto_congr' <| hz.mono fun x hnz => @div_self _ _ (v x) hnz).mpr tendsto_const_nhds
    convert this.add key
    · ext
      simp
    · norm_num
  · exact is_equivalent_of_tendsto_one (hz.mono fun x hnvz hz => (hnvz hz).elim)
#align asymptotics.is_equivalent_iff_tendsto_one Asymptotics.isEquivalent_iff_tendsto_one

end NormedField

section Smul

theorem IsEquivalent.smul {α E 𝕜 : Type _} [NormedField 𝕜] [NormedAddCommGroup E] [NormedSpace 𝕜 E]
    {a b : α → 𝕜} {u v : α → E} {l : Filter α} (hab : a ~[l] b) (huv : u ~[l] v) :
    (fun x => a x • u x) ~[l] fun x => b x • v x :=
  by
  rcases hab.exists_eq_mul with ⟨φ, hφ, habφ⟩
  have :
    ((fun x : α => a x • u x) - fun x : α => b x • v x) =ᶠ[l] fun x => b x • (φ x • u x - v x) :=
    by
    convert(habφ.comp₂ (· • ·) <| eventually_eq.refl _ u).sub
        (eventually_eq.refl _ fun x => b x • v x)
    ext
    rw [Pi.mul_apply, mul_comm, mul_smul, ← smul_sub]
  refine' (is_o_congr this.symm <| eventually_eq.rfl).mp ((is_O_refl b l).smul_isOCat _)
  rcases huv.is_O.exists_pos with ⟨C, hC, hCuv⟩
  rw [is_equivalent] at *
  rw [is_o_iff] at *
  rw [is_O_with] at hCuv
  simp only [Metric.tendsto_nhds, dist_eq_norm] at hφ
  intro c hc
  specialize hφ (c / 2 / C) (div_pos (by linarith) hC)
  specialize huv (show 0 < c / 2 by linarith)
  refine' hφ.mp (huv.mp <| hCuv.mono fun x hCuvx huvx hφx => _)
  have key :=
    calc
      ‖φ x - 1‖ * ‖u x‖ ≤ c / 2 / C * ‖u x‖ :=
        mul_le_mul_of_nonneg_right hφx.le (norm_nonneg <| u x)
      _ ≤ c / 2 / C * (C * ‖v x‖) := (mul_le_mul_of_nonneg_left hCuvx (div_pos (by linarith) hC).le)
      _ = c / 2 * ‖v x‖ := by
        field_simp [hC.ne.symm]
        ring

  calc
    ‖((fun x : α => φ x • u x) - v) x‖ = ‖(φ x - 1) • u x + (u x - v x)‖ := by
      simp [sub_smul, sub_add]
    _ ≤ ‖(φ x - 1) • u x‖ + ‖u x - v x‖ := (norm_add_le _ _)
    _ = ‖φ x - 1‖ * ‖u x‖ + ‖u x - v x‖ := by rw [norm_smul]
    _ ≤ c / 2 * ‖v x‖ + ‖u x - v x‖ := (add_le_add_right key _)
    _ ≤ c / 2 * ‖v x‖ + c / 2 * ‖v x‖ := (add_le_add_left huvx _)
    _ = c * ‖v x‖ := by ring

#align asymptotics.is_equivalent.smul Asymptotics.IsEquivalent.smul

end Smul

section mul_inv

variable {α β : Type _} [NormedField β] {t u v w : α → β} {l : Filter α}

theorem IsEquivalent.mul (htu : t ~[l] u) (hvw : v ~[l] w) : t * v ~[l] u * w :=
  htu.smul hvw
#align asymptotics.is_equivalent.mul Asymptotics.IsEquivalent.mul

theorem IsEquivalent.inv (huv : u ~[l] v) : (fun x => (u x)⁻¹) ~[l] fun x => (v x)⁻¹ :=
  by
  rw [is_equivalent_iff_exists_eq_mul] at *
  rcases huv with ⟨φ, hφ, h⟩
  rw [← inv_one]
  refine' ⟨fun x => (φ x)⁻¹, tendsto.inv₀ hφ (by norm_num), _⟩
  convert h.inv
  ext
  simp [mul_inv]
#align asymptotics.is_equivalent.inv Asymptotics.IsEquivalent.inv

theorem IsEquivalent.div (htu : t ~[l] u) (hvw : v ~[l] w) :
    (fun x => t x / v x) ~[l] fun x => u x / w x := by
  simpa only [div_eq_mul_inv] using htu.mul hvw.inv
#align asymptotics.is_equivalent.div Asymptotics.IsEquivalent.div

end mul_inv

section NormedLinearOrderedField

variable {α β : Type _} [NormedLinearOrderedField β] {u v : α → β} {l : Filter α}

theorem IsEquivalent.tendsto_atTop [OrderTopology β] (huv : u ~[l] v) (hu : Tendsto u l atTop) :
    Tendsto v l atTop :=
  let ⟨φ, hφ, h⟩ := huv.symm.exists_eq_mul
  Tendsto.congr' h.symm (mul_comm u φ ▸ hu.atTop_mul zero_lt_one hφ)
#align asymptotics.is_equivalent.tendsto_at_top Asymptotics.IsEquivalent.tendsto_atTop

theorem IsEquivalent.tendsto_atTop_iff [OrderTopology β] (huv : u ~[l] v) :
    Tendsto u l atTop ↔ Tendsto v l atTop :=
  ⟨huv.tendsto_atTop, huv.symm.tendsto_atTop⟩
#align asymptotics.is_equivalent.tendsto_at_top_iff Asymptotics.IsEquivalent.tendsto_atTop_iff

theorem IsEquivalent.tendsto_atBot [OrderTopology β] (huv : u ~[l] v) (hu : Tendsto u l atBot) :
    Tendsto v l atBot :=
  by
  convert tendsto_neg_at_top_at_bot.comp
      (huv.neg.tendsto_at_top <| tendsto_neg_at_bot_at_top.comp hu)
  ext
  simp
#align asymptotics.is_equivalent.tendsto_at_bot Asymptotics.IsEquivalent.tendsto_atBot

theorem IsEquivalent.tendsto_atBot_iff [OrderTopology β] (huv : u ~[l] v) :
    Tendsto u l atBot ↔ Tendsto v l atBot :=
  ⟨huv.tendsto_atBot, huv.symm.tendsto_atBot⟩
#align asymptotics.is_equivalent.tendsto_at_bot_iff Asymptotics.IsEquivalent.tendsto_atBot_iff

end NormedLinearOrderedField

end Asymptotics

open Filter Asymptotics

open Asymptotics

variable {α β : Type _} [NormedAddCommGroup β]

theorem Filter.EventuallyEq.isEquivalent {u v : α → β} {l : Filter α} (h : u =ᶠ[l] v) : u ~[l] v :=
  IsEquivalent.congr_right (isOCat_refl_left _ _) h
#align filter.eventually_eq.is_equivalent Filter.EventuallyEq.isEquivalent