-- Boilerplate:
import algebra.algebra.basic
import algebra.big_operators.basic
import data.equiv.ring
namespace examples
open_locale big_operators

/-
The `is_ring_hom` predicate stated `f} preserves the ring operations `*`, `+`, `1` and `0`.
Instances were available for the common operations, except composition:
-/
-- Adapted from `deprecated/group.lean:77`
class is_monoid_hom {M N : Type*} [monoid M] [monoid N] (f : M → N) : Prop :=
(map_mul : ∀ x y : M, f (x * y) = f x * f y)
(map_one : f 1 = 1)

-- Adapted from `deprecated/ring.lean:64`
class is_ring_hom {R S : Type*} [semiring R] [semiring S] (f : R → S)
  extends is_monoid_hom f :=
(map_add : ∀ x y : R, f (x + y) = f x + f y)
(map_zero : f 0 = 0)

-- Adapted from `deprecated/ring.lean:93`
instance id.is_ring_hom (R : Type*) [semiring R] :
  is_ring_hom (id : R → R) :=
{ map_mul := λ _ _, rfl,
  map_one := rfl,
  map_add := λ _ _, rfl,
  map_zero := rfl }

-- Adapted from `deprecated/ring.lean:97`
lemma comp.is_ring_hom {R S T : Type*} (f : R → S) (g : S → T)
  [semiring R] [semiring S] [semiring T] [hf : is_ring_hom f] [hg : is_ring_hom g] :
  is_ring_hom (g ∘ f) :=
{ map_add := λ x y, by simp [hf.map_add]; rw hg.map_add; refl,
  map_mul := λ x y, by simp [hf.map_mul]; rw hg.map_mul; refl,
  map_one := by simp [hf.map_one]; exact hg.map_one,
  map_zero := by simp [hf.map_zero]; exact hg.map_zero }

/-!--------------
Bundled morphisms
----------------/

namespace bundled
/-
For these reasons, \mathlib was refactored to prefer bundled morphisms:
-/
-- Adapted from `algebra/group/hom.lean:248`
structure monoid_hom (M N : Type*) [monoid M] [monoid N] :=
(to_fun : M → N)
(map_mul : ∀ x y, to_fun (x * y) = to_fun x * to_fun y)
(map_one : to_fun 1 = 1)

-- Adapted from `algebra/ring/basic.lean:329`
structure ring_hom (R S : Type*) [semiring R] [semiring S]
  extends monoid_hom R S :=
{map_add : ∀ x y, to_fun (x + y) = to_fun x + to_fun y}
(map_zero : to_fun 0 = 0)

-- Adapted from `algebra/group/hom.lean:406`
instance monoid_hom.has_coe_to_fun (M N : Type*) [monoid M] [monoid N] :
  has_coe_to_fun (monoid_hom M N) (λ _, M → N) :=
{ coe := monoid_hom.to_fun }

-- Adapted from `algebra/group/hom.lean:661`
def monoid_hom.id (M : Type*) [monoid M] : monoid_hom M M :=
{ to_fun := id,
  map_one := rfl,
  map_mul := λ _ _, rfl }

-- Adapted from `algebra/group/hom.lean:688`
def monoid_hom.comp {M N O : Type*} [monoid M] [monoid N] [monoid O]
  (f : monoid_hom M N) (g : monoid_hom N O) : monoid_hom M O :=
{ to_fun := g ∘ f,
  map_one := by simp [coe_fn, has_coe_to_fun.coe, monoid_hom.has_coe_to_fun, monoid_hom.map_one],
  map_mul := by simp [coe_fn, has_coe_to_fun.coe, monoid_hom.has_coe_to_fun, monoid_hom.map_mul] }

/-
[T]he additive group endomorphism of a ring given by multiplying by a constant \lstinline{c}:
-/
-- Adapted from `deprecated/group.lean:71`
class is_add_monoid_hom {M N : Type*} [add_monoid M] [add_monoid N] (f : M → N) : Prop :=
(map_add : ∀ x y : M, f (x + y) = f x + f y)
(map_zero : f 0 = 0)

-- Adapted from `deprecated/group.lean:166`
instance mul.is_add_monoid_hom {R : Type*} [ring R] (c : R) :
  is_add_monoid_hom ((*) c) :=
{ map_zero := mul_zero c,
  map_add := mul_add c }

-- Adapted from `algebra/ring/basic.lean:303`
def add_monoid_hom.mul_left {R : Type*} [ring R] (c : R) :
  add_monoid_hom R R :=
{ to_fun := (*) c,
  map_zero' := mul_zero c,
  map_add' := mul_add c }
end bundled

#check monoid_hom.map_prod

/-
Thus \mathlib ended up with many copies of lemmas such as `map_prod`:
-/
-- Adapted from `algebra/big_operators/basic.lean:111`
lemma monoid_hom.map_prod {ι M N : Type*} (s : finset ι) (f : ι → M) [comm_monoid M] [comm_monoid N]
  (g : monoid_hom M N) :
  g (∏ i in s, f i) = ∏ i in s, g (f i) :=
by simp only [finset.prod_eq_multiset_prod, g.map_multiset_prod, multiset.map_map]

-- Adapted from `algebra/big_operators/basic.lean:144`
lemma ring_hom.map_prod {ι R S : Type*} (s : finset ι) (f : ι → R) [comm_semiring R] [comm_semiring S]
  (g : ring_hom R S) :
  g (∏ i in s, f i) = ∏ i in s, g (f i) :=
monoid_hom.map_prod s f g.to_monoid_hom

-- Adapted from `algebra/big_operators/basic.lean:116`
lemma mul_equiv.map_prod {ι M N : Type*} (s : finset ι) (f : ι → M) [comm_monoid M] [comm_monoid N]
  (g : mul_equiv M N) :
  g (∏ i in s, f i) = ∏ i in s, g (f i) :=
monoid_hom.map_prod s f g.to_monoid_hom

-- Adapted from `data/equiv/ring.lean:386`
lemma ring_equiv.map_prod {ι R S : Type*} (s : finset ι) (f : ι → R) [comm_semiring R] [comm_semiring S]
  (g : ring_equiv R S) :
  g (∏ i in s, f i) = ∏ i in s, g (f i) :=
monoid_hom.map_prod s f g.to_monoid_hom

-- Adapted from `algebra/algebra/basic.lean:704`
lemma alg_hom.map_prod {ι R A B : Type*} (s : finset ι) (f : ι → A) [comm_semiring R]
  [comm_semiring A] [comm_semiring B] [algebra R A] [algebra R B]
  (g : alg_hom R A B) :
  g (∏ i in s, f i) = ∏ i in s, g (f i) :=
ring_hom.map_prod s f g.to_ring_hom

-- Adapted from `algebra/algebra/basic.lean:1142`
lemma alg_equiv.map_prod {ι R A B : Type*} (s : finset ι) (f : ι → A) [comm_semiring R]
  [comm_semiring A] [comm_semiring B] [algebra R A] [algebra R B]
  (g : alg_equiv R A B) :
  g (∏ i in s, f i) = ∏ i in s, g (f i) :=
g.to_alg_hom.map_prod f s

/-!-------------
Morphism classes
---------------/

/-
My first step in introducing this interface was a typeclass `fun_like` for *types* of bundled (dependent) functions,
based on Eric Wieser's `set_like` class for types of bundled subobjects. (https://github.com/leanprover-community/mathlib/pull/6768)
-/
-- Adapted from `library/init/coe.lean:54`
class has_coe_to_fun' (F : Type*) (α : out_param (F → Type*)) :=
(coe : Π x : F, α x)

-- Adapted from `data/fun_like/basic.lean:127`
class fun_like (F : Type*)
  (α : out_param Type*) (β : out_param (α → Type*))
  extends has_coe_to_fun F (λ _, Π a : α, β a) :=
(coe_injective' : function.injective to_has_coe_to_fun.coe)

-- A typical instance looks like:
instance monoid_hom.fun_like {M N : Type*} [monoid M] [monoid N] :
  fun_like (monoid_hom M N) M (λ _, N) :=
{ coe := monoid_hom.to_fun,
  coe_injective' := λ f g h, by { cases f, cases g, congr' } }
/-
The next step in addressing the duplication is to introduce a class for
the bundled morphism types that coerce to `monoid_hom`:
-/
-- Adapted from `algebra/group/hom.lean:260`
class monoid_hom_class (F : Type*) (M N : out_param Type*)
  [monoid M] [monoid N] extends fun_like F M (λ _, N) :=
(map_one : ∀ (f : F), f 1 = 1)
(map_mul : ∀ (f : F) (x y : M), f (x * y) = f x * f y)

-- Adapted from `algebra/group/hom.lean:265`
instance monoid_hom.monoid_hom_class {M N : Type*} [monoid M] [monoid N] :
  monoid_hom_class (monoid_hom M N) M N :=
{ coe := monoid_hom.to_fun,
  coe_injective' := λ f g h, by { cases f, cases g, congr' },
  map_one := monoid_hom.map_one',
  map_mul := monoid_hom.map_mul' }

/-
The types such as \lstinline{ring_hom} extending \lstinline{monoid_hom} should receive a \lstinline{monoid_hom_class} instance,
which we can do by subclassing \lstinline{monoid_hom_class} and instantiating the subclass:
-/
-- Adapted from `algebra/ring/basic.lean:354`
class ring_hom_class (F : Type*) (R S : out_param Type*)
  [semiring R] [semiring S]
  extends monoid_hom_class F R S :=
(map_zero : ∀ (f : F), f 0 = 0)
(map_add : ∀ (f : F) (x y : R), f (x + y) = f x + f y)

-- Adapted from `algebra/ring/basic.lean:382`
instance ring_hom.ring_hom_class {R S : Type*} [semiring R] [semiring S] :
  ring_hom_class (ring_hom R S) R S :=
{ coe := ring_hom.to_fun,
  coe_injective' := λ f g h, by { cases f, cases g, congr' },
  map_mul := ring_hom.map_mul',
  map_one := ring_hom.map_one',
  map_add := ring_hom.map_add',
  map_zero := ring_hom.map_zero' }

/-
Now lemmas can be made generic by parametrising over all the types of bundled morphisms,
reducing the multiplicative amount of lemmas to an additive amount:
each extension of \lstinline{monoid_hom} should get a \lstinline{monoid_hom_class} instance,
and each operation preserved by \lstinline{monoid_hom}s should get a lemma taking a \lstinline{monoid_hom_class} parameter.
-/
lemma map_prod {ι M N : Type*} (s : finset ι) (f : ι → M) [comm_monoid M] [comm_monoid N]
  {G : Type*} [monoid_hom_class G M N] (g : G) :
  g (∏ i in s, f i) = ∏ i in s, g (f i) :=
begin
  letI := classical.dec_eq ι,
  refine finset.induction_on s _ _,
  { simp [monoid_hom_class.map_one] },
  { intros i s hi ih,
    simp [finset.prod_insert hi, monoid_hom_class.map_mul, ih] },
end

end examples


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

/-!
# Left and right limits

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

We define the (strict) left and right limits of a function.

* `left_lim f x` is the strict left limit of `f` at `x` (using `f x` as a garbage value if `x`
  is isolated to its left).
* `right_lim f x` is the strict right limit of `f` at `x` (using `f x` as a garbage value if `x`
  is isolated to its right).

We develop a comprehensive API for monotone functions. Notably,

* `monotone.continuous_at_iff_left_lim_eq_right_lim` states that a monotone function is continuous
  at a point if and only if its left and right limits coincide.
* `monotone.countable_not_continuous_at` asserts that a monotone function taking values in a
  second-countable space has at most countably many discontinuity points.

We also port the API to antitone functions.

## TODO

Prove corresponding stronger results for strict_mono and strict_anti functions.
-/

open set filter
open_locale topology

section

variables {α β : Type*} [linear_order α] [topological_space β]

/-- Let `f : α → β` be a function from a linear order `α` to a topological_space `β`, and
let `a : α`. The limit strictly to the left of `f` at `a`, denoted with `left_lim f a`, is defined
by using the order topology on `α`. If `a` is isolated to its left or the function has no left
limit, we use `f a` instead to guarantee a good behavior in most cases. -/
@[irreducible] noncomputable def function.left_lim (f : α → β) (a : α) : β :=
begin
  classical,
  haveI : nonempty β := ⟨f a⟩,
  letI : topological_space α := preorder.topology α,
  exact if (𝓝[<] a = ⊥) ∨ ¬(∃ y, tendsto f (𝓝[<] a) (𝓝 y)) then f a
    else lim (𝓝[<] a) f
end

/-- Let `f : α → β` be a function from a linear order `α` to a topological_space `β`, and
let `a : α`. The limit strictly to the right of `f` at `a`, denoted with `right_lim f a`, is defined
by using the order topology on `α`. If `a` is isolated to its right or the function has no right
limit, , we use `f a` instead to guarantee a good behavior in most cases. -/
noncomputable def function.right_lim (f : α → β) (a : α) : β :=
@function.left_lim αᵒᵈ β  _ _ f a

open function

lemma left_lim_eq_of_tendsto
  [hα : topological_space α] [h'α : order_topology α] [t2_space β]
  {f : α → β} {a : α} {y : β} (h : 𝓝[<] a ≠ ⊥) (h' : tendsto f (𝓝[<] a) (𝓝 y)) :
  left_lim f a = y :=
begin
  have h'' : ∃ y, tendsto f (𝓝[<] a) (𝓝 y) := ⟨y, h'⟩,
  rw [h'α.topology_eq_generate_intervals] at h h' h'',
  simp only [left_lim, h, h'', not_true, or_self, if_false],
  haveI := ne_bot_iff.2 h,
  exact h'.lim_eq,
end

lemma left_lim_eq_of_eq_bot [hα : topological_space α] [h'α : order_topology α]
  (f : α → β) {a : α} (h : 𝓝[<] a = ⊥) :
  left_lim f a = f a :=
begin
  rw [h'α.topology_eq_generate_intervals] at h,
  simp [left_lim, ite_eq_left_iff, h],
end

end

open function

namespace monotone

variables {α β : Type*} [linear_order α] [conditionally_complete_linear_order β]
[topological_space β] [order_topology β] {f : α → β} (hf : monotone f) {x y : α}
include hf

lemma left_lim_eq_Sup [topological_space α] [order_topology α] (h : 𝓝[<] x ≠ ⊥) :
  left_lim f x = Sup (f '' (Iio x)) :=
left_lim_eq_of_tendsto h (hf.tendsto_nhds_within_Iio x)

lemma left_lim_le (h : x ≤ y) : left_lim f x ≤ f y :=
begin
  letI : topological_space α := preorder.topology α,
  haveI : order_topology α := ⟨rfl⟩,
  rcases eq_or_ne (𝓝[<] x) ⊥ with h'|h',
  { simpa [left_lim, h'] using hf h },
  haveI A : ne_bot (𝓝[<] x) := ne_bot_iff.2 h',
  rw left_lim_eq_Sup hf h',
  refine cSup_le _ _,
  { simp only [nonempty_image_iff],
    exact (forall_mem_nonempty_iff_ne_bot.2 A) _ self_mem_nhds_within },
  { simp only [mem_image, mem_Iio, forall_exists_index, and_imp, forall_apply_eq_imp_iff₂],
    assume z hz,
    exact hf (hz.le.trans h) },
end

lemma le_left_lim (h : x < y) : f x ≤ left_lim f y :=
begin
  letI : topological_space α := preorder.topology α,
  haveI : order_topology α := ⟨rfl⟩,
  rcases eq_or_ne (𝓝[<] y) ⊥ with h'|h',
  { rw left_lim_eq_of_eq_bot _ h', exact hf h.le },
  rw left_lim_eq_Sup hf h',
  refine le_cSup ⟨f y, _⟩ (mem_image_of_mem _ h),
  simp only [upper_bounds, mem_image, mem_Iio, forall_exists_index, and_imp,
    forall_apply_eq_imp_iff₂, mem_set_of_eq],
  assume z hz,
  exact hf hz.le
end

@[mono] protected lemma left_lim : monotone (left_lim f) :=
begin
  assume x y h,
  rcases eq_or_lt_of_le h with rfl|hxy,
  { exact le_rfl },
  { exact (hf.left_lim_le le_rfl).trans (hf.le_left_lim hxy) }
end

lemma le_right_lim (h : x ≤ y) : f x ≤ right_lim f y :=
hf.dual.left_lim_le h

lemma right_lim_le (h : x < y) : right_lim f x ≤ f y :=
hf.dual.le_left_lim h

@[mono] protected lemma right_lim : monotone (right_lim f) :=
λ x y h, hf.dual.left_lim h

lemma left_lim_le_right_lim (h : x ≤ y) : left_lim f x ≤ right_lim f y :=
(hf.left_lim_le le_rfl).trans (hf.le_right_lim h)

lemma right_lim_le_left_lim (h : x < y) : right_lim f x ≤ left_lim f y :=
begin
  letI : topological_space α := preorder.topology α,
  haveI : order_topology α := ⟨rfl⟩,
  rcases eq_or_ne (𝓝[<] y) ⊥ with h'|h',
  { simp [left_lim, h'],
    exact right_lim_le hf h },
  obtain ⟨a, ⟨xa, ay⟩⟩ : (Ioo x y).nonempty :=
    forall_mem_nonempty_iff_ne_bot.2 (ne_bot_iff.2 h') (Ioo x y)
      (Ioo_mem_nhds_within_Iio ⟨h, le_refl _⟩),
  calc right_lim f x ≤ f a : hf.right_lim_le xa
  ... ≤ left_lim f y : hf.le_left_lim ay
end

variables [topological_space α] [order_topology α]

lemma tendsto_left_lim (x : α) : tendsto f (𝓝[<] x) (𝓝 (left_lim f x)) :=
begin
  rcases eq_or_ne (𝓝[<] x) ⊥ with h'|h',
  { simp [h'] },
  rw left_lim_eq_Sup hf h',
  exact hf.tendsto_nhds_within_Iio x
end

lemma tendsto_left_lim_within (x : α) : tendsto f (𝓝[<] x) (𝓝[≤] (left_lim f x)) :=
begin
  apply tendsto_nhds_within_of_tendsto_nhds_of_eventually_within f (hf.tendsto_left_lim x),
  filter_upwards [self_mem_nhds_within] with y hy using hf.le_left_lim hy,
end

lemma tendsto_right_lim (x : α) :
  tendsto f (𝓝[>] x) (𝓝 (right_lim f x)) :=
hf.dual.tendsto_left_lim x

lemma tendsto_right_lim_within (x : α) :
  tendsto f (𝓝[>] x) (𝓝[≥] (right_lim f x)) :=
hf.dual.tendsto_left_lim_within x

/-- A monotone function is continuous to the left at a point if and only if its left limit
coincides with the value of the function. -/
lemma continuous_within_at_Iio_iff_left_lim_eq  :
  continuous_within_at f (Iio x) x ↔ left_lim f x = f x :=
begin
  rcases eq_or_ne (𝓝[<] x) ⊥ with h'|h',
  { simp [left_lim_eq_of_eq_bot f h', continuous_within_at, h'] },
  haveI : (𝓝[Iio x] x).ne_bot := ne_bot_iff.2 h',
  refine ⟨λ h, tendsto_nhds_unique (hf.tendsto_left_lim x) h.tendsto, λ h, _⟩,
  have := hf.tendsto_left_lim x,
  rwa h at this,
end

/-- A monotone function is continuous to the right at a point if and only if its right limit
coincides with the value of the function. -/
lemma continuous_within_at_Ioi_iff_right_lim_eq :
  continuous_within_at f (Ioi x) x ↔ right_lim f x = f x :=
hf.dual.continuous_within_at_Iio_iff_left_lim_eq

/-- A monotone function is continuous at a point if and only if its left and right limits
coincide. -/
lemma continuous_at_iff_left_lim_eq_right_lim :
  continuous_at f x ↔ left_lim f x = right_lim f x :=
begin
  refine ⟨λ h, _, λ h, _⟩,
  { have A : left_lim f x = f x,
      from (hf.continuous_within_at_Iio_iff_left_lim_eq).1 h.continuous_within_at,
    have B : right_lim f x = f x,
      from (hf.continuous_within_at_Ioi_iff_right_lim_eq).1 h.continuous_within_at,
    exact A.trans B.symm },
  { have h' : left_lim f x = f x,
    { apply le_antisymm (left_lim_le hf (le_refl _)),
      rw h,
      exact le_right_lim hf (le_refl _) },
    refine continuous_at_iff_continuous_left'_right'.2 ⟨_, _⟩,
    { exact hf.continuous_within_at_Iio_iff_left_lim_eq.2 h' },
    { rw h at h',
      exact hf.continuous_within_at_Ioi_iff_right_lim_eq.2 h' } },
end

/-- In a second countable space, the set of points where a monotone function is not right-continuous
is at most countable. Superseded by `countable_not_continuous_at` which gives the two-sided
version. -/
lemma countable_not_continuous_within_at_Ioi [topological_space.second_countable_topology β] :
  set.countable {x | ¬(continuous_within_at f (Ioi x) x)} :=
begin
  /- If `f` is not continuous on the right at `x`, there is an interval `(f x, z x)` which is not
  reached by `f`. This gives a family of disjoint open intervals in `β`. Such a family can only
  be countable as `β` is second-countable. -/
  nontriviality α,
  let s := {x | ¬(continuous_within_at f (Ioi x) x)},
  have : ∀ x, x ∈ s → ∃ z, f x < z ∧ ∀ y, x < y → z ≤ f y,
  { rintros x (hx : ¬(continuous_within_at f (Ioi x) x)),
    contrapose! hx,
    refine tendsto_order.2 ⟨λ m hm, _, λ u hu, _⟩,
    { filter_upwards [self_mem_nhds_within] with y hy using hm.trans_le (hf (le_of_lt hy)) },
    rcases hx u hu with ⟨v, xv, fvu⟩,
    have : Ioo x v ∈ 𝓝[>] x, from Ioo_mem_nhds_within_Ioi ⟨le_refl _, xv⟩,
    filter_upwards [this] with y hy,
    apply (hf hy.2.le).trans_lt fvu },
  -- choose `z x` such that `f` does not take the values in `(f x, z x)`.
  choose! z hz using this,
  have I : inj_on f s,
  { apply strict_mono_on.inj_on,
    assume x hx y hy hxy,
    calc f x < z x : (hz x hx).1
    ... ≤ f y : (hz x hx).2 y hxy },
  -- show that `f s` is countable by arguing that a disjoint family of disjoint open intervals
  -- (the intervals `(f x, z x)`) is at most countable.
  have fs_count : (f '' s).countable,
  { have A : (f '' s).pairwise_disjoint (λ x, Ioo x (z (inv_fun_on f s x))),
    { rintros _ ⟨u, us, rfl⟩ _ ⟨v, vs, rfl⟩ huv,
      wlog hle : u ≤ v generalizing u v,
      { exact (this v vs u us huv.symm (le_of_not_le hle)).symm },
      have hlt : u < v, from hle.lt_of_ne (ne_of_apply_ne _ huv),
      apply disjoint_iff_forall_ne.2,
      rintros a ha b hb rfl,
      simp only [I.left_inv_on_inv_fun_on us, I.left_inv_on_inv_fun_on vs] at ha hb,
      exact lt_irrefl _ ((ha.2.trans_le ((hz u us).2 v hlt)).trans hb.1) },
    apply set.pairwise_disjoint.countable_of_Ioo A,
    rintros _ ⟨y, ys, rfl⟩,
    simpa only [I.left_inv_on_inv_fun_on ys] using (hz y ys).1 },
  exact maps_to.countable_of_inj_on (maps_to_image f s) I fs_count,
end

/-- In a second countable space, the set of points where a monotone function is not left-continuous
is at most countable. Superseded by `countable_not_continuous_at` which gives the two-sided
version. -/
lemma countable_not_continuous_within_at_Iio [topological_space.second_countable_topology β] :
  set.countable {x | ¬(continuous_within_at f (Iio x) x)} :=
hf.dual.countable_not_continuous_within_at_Ioi

/-- In a second countable space, the set of points where a monotone function is not continuous
is at most countable. -/
lemma countable_not_continuous_at [topological_space.second_countable_topology β] :
  set.countable {x | ¬(continuous_at f x)} :=
begin
  apply (hf.countable_not_continuous_within_at_Ioi.union
         hf.countable_not_continuous_within_at_Iio).mono _,
  refine compl_subset_compl.1 _,
  simp only [compl_union],
  rintros x ⟨hx, h'x⟩,
  simp only [mem_set_of_eq, not_not, mem_compl_iff] at hx h'x ⊢,
  exact continuous_at_iff_continuous_left'_right'.2 ⟨h'x, hx⟩
end

end monotone

namespace antitone

variables {α β : Type*} [linear_order α] [conditionally_complete_linear_order β]
[topological_space β] [order_topology β] {f : α → β} (hf : antitone f) {x y : α}
include hf

lemma le_left_lim (h : x ≤ y) : f y ≤ left_lim f x :=
hf.dual_right.left_lim_le h

lemma left_lim_le (h : x < y) : left_lim f y ≤ f x :=
hf.dual_right.le_left_lim h

@[mono] protected lemma left_lim : antitone (left_lim f) :=
hf.dual_right.left_lim

lemma right_lim_le (h : x ≤ y) : right_lim f y ≤ f x :=
hf.dual_right.le_right_lim h

lemma le_right_lim (h : x < y) : f y ≤ right_lim f x :=
hf.dual_right.right_lim_le h

@[mono] protected lemma right_lim : antitone (right_lim f) :=
hf.dual_right.right_lim

lemma right_lim_le_left_lim (h : x ≤ y) : right_lim f y ≤ left_lim f x :=
hf.dual_right.left_lim_le_right_lim h

lemma left_lim_le_right_lim (h : x < y) : left_lim f y ≤ right_lim f x :=
hf.dual_right.right_lim_le_left_lim h

variables [topological_space α] [order_topology α]

lemma tendsto_left_lim (x : α) : tendsto f (𝓝[<] x) (𝓝 (left_lim f x)) :=
hf.dual_right.tendsto_left_lim x

lemma tendsto_left_lim_within (x : α) : tendsto f (𝓝[<] x) (𝓝[≥] (left_lim f x)) :=
hf.dual_right.tendsto_left_lim_within x

lemma tendsto_right_lim (x : α) :
  tendsto f (𝓝[>] x) (𝓝 (right_lim f x)) :=
hf.dual_right.tendsto_right_lim x

lemma tendsto_right_lim_within (x : α) :
  tendsto f (𝓝[>] x) (𝓝[≤] (right_lim f x)) :=
hf.dual_right.tendsto_right_lim_within x

/-- An antitone function is continuous to the left at a point if and only if its left limit
coincides with the value of the function. -/
lemma continuous_within_at_Iio_iff_left_lim_eq  :
  continuous_within_at f (Iio x) x ↔ left_lim f x = f x :=
hf.dual_right.continuous_within_at_Iio_iff_left_lim_eq

/-- An antitone function is continuous to the right at a point if and only if its right limit
coincides with the value of the function. -/
lemma continuous_within_at_Ioi_iff_right_lim_eq :
  continuous_within_at f (Ioi x) x ↔ right_lim f x = f x :=
hf.dual_right.continuous_within_at_Ioi_iff_right_lim_eq

/-- An antitone function is continuous at a point if and only if its left and right limits
coincide. -/
lemma continuous_at_iff_left_lim_eq_right_lim :
  continuous_at f x ↔ left_lim f x = right_lim f x :=
hf.dual_right.continuous_at_iff_left_lim_eq_right_lim

/-- In a second countable space, the set of points where an antitone function is not continuous
is at most countable. -/
lemma countable_not_continuous_at [topological_space.second_countable_topology β] :
  set.countable {x | ¬(continuous_at f x)} :=
hf.dual_right.countable_not_continuous_at

end antitone


/-
Copyright (c) 2021 Thomas Browning. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Thomas Browning, Jireh Loreaux

! This file was ported from Lean 3 source module group_theory.subsemigroup.centralizer
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.GroupTheory.Subsemigroup.Center
import Mathbin.Algebra.GroupWithZero.Units.Lemmas

/-!
# Centralizers of magmas and semigroups

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

## Main definitions

* `set.centralizer`: the centralizer of a subset of a magma
* `subsemigroup.centralizer`: the centralizer of a subset of a semigroup
* `set.add_centralizer`: the centralizer of a subset of an additive magma
* `add_subsemigroup.centralizer`: the centralizer of a subset of an additive semigroup

We provide `monoid.centralizer`, `add_monoid.centralizer`, `subgroup.centralizer`, and
`add_subgroup.centralizer` in other files.
-/

variable {M : Type _} {S T : Set M}

namespace Set

variable (S)

#print Set.centralizer /-
/-- The centralizer of a subset of a magma. -/
@[to_additive add_centralizer " The centralizer of a subset of an additive magma. "]
def centralizer [Mul M] : Set M :=
  { c | ∀ m ∈ S, m * c = c * m }
#align set.centralizer Set.centralizer
#align set.add_centralizer Set.addCentralizer
-/

variable {S}

#print Set.mem_centralizer_iff /-
@[to_additive mem_add_centralizer]
theorem mem_centralizer_iff [Mul M] {c : M} : c ∈ centralizer S ↔ ∀ m ∈ S, m * c = c * m :=
  Iff.rfl
#align set.mem_centralizer_iff Set.mem_centralizer_iff
#align set.mem_add_centralizer Set.mem_add_centralizer
-/

#print Set.decidableMemCentralizer /-
@[to_additive decidable_mem_add_centralizer]
instance decidableMemCentralizer [Mul M] [∀ a : M, Decidable <| ∀ b ∈ S, b * a = a * b] :
    DecidablePred (· ∈ centralizer S) := fun _ => decidable_of_iff' _ mem_centralizer_iff
#align set.decidable_mem_centralizer Set.decidableMemCentralizer
#align set.decidable_mem_add_centralizer Set.decidableMemAddCentralizer
-/

variable (S)

/- warning: set.one_mem_centralizer -> Set.one_mem_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : MulOneClass.{u1} M], Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (OfNat.ofNat.{u1} M 1 (OfNat.mk.{u1} M 1 (One.one.{u1} M (MulOneClass.toHasOne.{u1} M _inst_1)))) (Set.centralizer.{u1} M S (MulOneClass.toHasMul.{u1} M _inst_1))
but is expected to have type
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : MulOneClass.{u1} M], Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (OfNat.ofNat.{u1} M 1 (One.toOfNat1.{u1} M (MulOneClass.toOne.{u1} M _inst_1))) (Set.centralizer.{u1} M S (MulOneClass.toMul.{u1} M _inst_1))
Case conversion may be inaccurate. Consider using '#align set.one_mem_centralizer Set.one_mem_centralizerₓ'. -/
@[simp, to_additive zero_mem_add_centralizer]
theorem one_mem_centralizer [MulOneClass M] : (1 : M) ∈ centralizer S := by
  simp [mem_centralizer_iff]
#align set.one_mem_centralizer Set.one_mem_centralizer
#align set.zero_mem_add_centralizer Set.zero_mem_add_centralizer

/- warning: set.zero_mem_centralizer -> Set.zero_mem_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : MulZeroClass.{u1} M], Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (OfNat.ofNat.{u1} M 0 (OfNat.mk.{u1} M 0 (Zero.zero.{u1} M (MulZeroClass.toHasZero.{u1} M _inst_1)))) (Set.centralizer.{u1} M S (MulZeroClass.toHasMul.{u1} M _inst_1))
but is expected to have type
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : MulZeroClass.{u1} M], Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (OfNat.ofNat.{u1} M 0 (Zero.toOfNat0.{u1} M (MulZeroClass.toZero.{u1} M _inst_1))) (Set.centralizer.{u1} M S (MulZeroClass.toMul.{u1} M _inst_1))
Case conversion may be inaccurate. Consider using '#align set.zero_mem_centralizer Set.zero_mem_centralizerₓ'. -/
@[simp]
theorem zero_mem_centralizer [MulZeroClass M] : (0 : M) ∈ centralizer S := by
  simp [mem_centralizer_iff]
#align set.zero_mem_centralizer Set.zero_mem_centralizer

variable {S} {a b : M}

/- warning: set.mul_mem_centralizer -> Set.mul_mem_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : Semigroup.{u1} M], (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) a (Set.centralizer.{u1} M S (Semigroup.toHasMul.{u1} M _inst_1))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) b (Set.centralizer.{u1} M S (Semigroup.toHasMul.{u1} M _inst_1))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) a b) (Set.centralizer.{u1} M S (Semigroup.toHasMul.{u1} M _inst_1)))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : Semigroup.{u1} M], (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) a (Set.centralizer.{u1} M S (Semigroup.toMul.{u1} M _inst_1))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) b (Set.centralizer.{u1} M S (Semigroup.toMul.{u1} M _inst_1))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toMul.{u1} M _inst_1)) a b) (Set.centralizer.{u1} M S (Semigroup.toMul.{u1} M _inst_1)))
Case conversion may be inaccurate. Consider using '#align set.mul_mem_centralizer Set.mul_mem_centralizerₓ'. -/
@[simp, to_additive add_mem_add_centralizer]
theorem mul_mem_centralizer [Semigroup M] (ha : a ∈ centralizer S) (hb : b ∈ centralizer S) :
    a * b ∈ centralizer S := fun g hg => by
  rw [mul_assoc, ← hb g hg, ← mul_assoc, ha g hg, mul_assoc]
#align set.mul_mem_centralizer Set.mul_mem_centralizer
#align set.add_mem_add_centralizer Set.add_mem_add_centralizer

/- warning: set.inv_mem_centralizer -> Set.inv_mem_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} [_inst_1 : Group.{u1} M], (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) a (Set.centralizer.{u1} M S (MulOneClass.toHasMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1)))))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (Inv.inv.{u1} M (DivInvMonoid.toHasInv.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1)) a) (Set.centralizer.{u1} M S (MulOneClass.toHasMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1))))))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} [_inst_1 : Group.{u1} M], (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) a (Set.centralizer.{u1} M S (MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1)))))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (Inv.inv.{u1} M (InvOneClass.toInv.{u1} M (DivInvOneMonoid.toInvOneClass.{u1} M (DivisionMonoid.toDivInvOneMonoid.{u1} M (Group.toDivisionMonoid.{u1} M _inst_1)))) a) (Set.centralizer.{u1} M S (MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1))))))
Case conversion may be inaccurate. Consider using '#align set.inv_mem_centralizer Set.inv_mem_centralizerₓ'. -/
@[simp, to_additive neg_mem_add_centralizer]
theorem inv_mem_centralizer [Group M] (ha : a ∈ centralizer S) : a⁻¹ ∈ centralizer S := fun g hg =>
  by rw [mul_inv_eq_iff_eq_mul, mul_assoc, eq_inv_mul_iff_mul_eq, ha g hg]
#align set.inv_mem_centralizer Set.inv_mem_centralizer
#align set.neg_mem_add_centralizer Set.neg_mem_add_centralizer

/- warning: set.add_mem_centralizer -> Set.add_mem_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : Distrib.{u1} M], (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) a (Set.centralizer.{u1} M S (Distrib.toHasMul.{u1} M _inst_1))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) b (Set.centralizer.{u1} M S (Distrib.toHasMul.{u1} M _inst_1))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (HAdd.hAdd.{u1, u1, u1} M M M (instHAdd.{u1} M (Distrib.toHasAdd.{u1} M _inst_1)) a b) (Set.centralizer.{u1} M S (Distrib.toHasMul.{u1} M _inst_1)))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : Distrib.{u1} M], (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) a (Set.centralizer.{u1} M S (Distrib.toMul.{u1} M _inst_1))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) b (Set.centralizer.{u1} M S (Distrib.toMul.{u1} M _inst_1))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (HAdd.hAdd.{u1, u1, u1} M M M (instHAdd.{u1} M (Distrib.toAdd.{u1} M _inst_1)) a b) (Set.centralizer.{u1} M S (Distrib.toMul.{u1} M _inst_1)))
Case conversion may be inaccurate. Consider using '#align set.add_mem_centralizer Set.add_mem_centralizerₓ'. -/
@[simp]
theorem add_mem_centralizer [Distrib M] (ha : a ∈ centralizer S) (hb : b ∈ centralizer S) :
    a + b ∈ centralizer S := fun c hc => by rw [add_mul, mul_add, ha c hc, hb c hc]
#align set.add_mem_centralizer Set.add_mem_centralizer

/- warning: set.neg_mem_centralizer -> Set.neg_mem_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} [_inst_1 : Mul.{u1} M] [_inst_2 : HasDistribNeg.{u1} M _inst_1], (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) a (Set.centralizer.{u1} M S _inst_1)) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (Neg.neg.{u1} M (InvolutiveNeg.toHasNeg.{u1} M (HasDistribNeg.toHasInvolutiveNeg.{u1} M _inst_1 _inst_2)) a) (Set.centralizer.{u1} M S _inst_1))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} [_inst_1 : Mul.{u1} M] [_inst_2 : HasDistribNeg.{u1} M _inst_1], (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) a (Set.centralizer.{u1} M S _inst_1)) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (Neg.neg.{u1} M (InvolutiveNeg.toNeg.{u1} M (HasDistribNeg.toInvolutiveNeg.{u1} M _inst_1 _inst_2)) a) (Set.centralizer.{u1} M S _inst_1))
Case conversion may be inaccurate. Consider using '#align set.neg_mem_centralizer Set.neg_mem_centralizerₓ'. -/
@[simp]
theorem neg_mem_centralizer [Mul M] [HasDistribNeg M] (ha : a ∈ centralizer S) :
    -a ∈ centralizer S := fun c hc => by rw [mul_neg, ha c hc, neg_mul]
#align set.neg_mem_centralizer Set.neg_mem_centralizer

/- warning: set.inv_mem_centralizer₀ -> Set.inv_mem_centralizer₀ is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} [_inst_1 : GroupWithZero.{u1} M], (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) a (Set.centralizer.{u1} M S (MulZeroClass.toHasMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1)))))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (Inv.inv.{u1} M (DivInvMonoid.toHasInv.{u1} M (GroupWithZero.toDivInvMonoid.{u1} M _inst_1)) a) (Set.centralizer.{u1} M S (MulZeroClass.toHasMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1))))))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} [_inst_1 : GroupWithZero.{u1} M], (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) a (Set.centralizer.{u1} M S (MulZeroClass.toMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1)))))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (Inv.inv.{u1} M (GroupWithZero.toInv.{u1} M _inst_1) a) (Set.centralizer.{u1} M S (MulZeroClass.toMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1))))))
Case conversion may be inaccurate. Consider using '#align set.inv_mem_centralizer₀ Set.inv_mem_centralizer₀ₓ'. -/
@[simp]
theorem inv_mem_centralizer₀ [GroupWithZero M] (ha : a ∈ centralizer S) : a⁻¹ ∈ centralizer S :=
  (eq_or_ne a 0).elim
    (fun h => by
      rw [h, inv_zero]
      exact zero_mem_centralizer S)
    fun ha0 c hc => by
    rw [mul_inv_eq_iff_eq_mul₀ ha0, mul_assoc, eq_inv_mul_iff_mul_eq₀ ha0, ha c hc]
#align set.inv_mem_centralizer₀ Set.inv_mem_centralizer₀

/- warning: set.div_mem_centralizer -> Set.div_mem_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : Group.{u1} M], (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) a (Set.centralizer.{u1} M S (MulOneClass.toHasMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1)))))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) b (Set.centralizer.{u1} M S (MulOneClass.toHasMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1)))))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (HDiv.hDiv.{u1, u1, u1} M M M (instHDiv.{u1} M (DivInvMonoid.toHasDiv.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1))) a b) (Set.centralizer.{u1} M S (MulOneClass.toHasMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1))))))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : Group.{u1} M], (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) a (Set.centralizer.{u1} M S (MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1)))))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) b (Set.centralizer.{u1} M S (MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1)))))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (HDiv.hDiv.{u1, u1, u1} M M M (instHDiv.{u1} M (DivInvMonoid.toDiv.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1))) a b) (Set.centralizer.{u1} M S (MulOneClass.toMul.{u1} M (Monoid.toMulOneClass.{u1} M (DivInvMonoid.toMonoid.{u1} M (Group.toDivInvMonoid.{u1} M _inst_1))))))
Case conversion may be inaccurate. Consider using '#align set.div_mem_centralizer Set.div_mem_centralizerₓ'. -/
@[simp, to_additive sub_mem_add_centralizer]
theorem div_mem_centralizer [Group M] (ha : a ∈ centralizer S) (hb : b ∈ centralizer S) :
    a / b ∈ centralizer S := by
  rw [div_eq_mul_inv]
  exact mul_mem_centralizer ha (inv_mem_centralizer hb)
#align set.div_mem_centralizer Set.div_mem_centralizer
#align set.sub_mem_add_centralizer Set.sub_mem_add_centralizer

/- warning: set.div_mem_centralizer₀ -> Set.div_mem_centralizer₀ is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : GroupWithZero.{u1} M], (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) a (Set.centralizer.{u1} M S (MulZeroClass.toHasMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1)))))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) b (Set.centralizer.{u1} M S (MulZeroClass.toHasMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1)))))) -> (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) (HDiv.hDiv.{u1, u1, u1} M M M (instHDiv.{u1} M (DivInvMonoid.toHasDiv.{u1} M (GroupWithZero.toDivInvMonoid.{u1} M _inst_1))) a b) (Set.centralizer.{u1} M S (MulZeroClass.toHasMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1))))))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {a : M} {b : M} [_inst_1 : GroupWithZero.{u1} M], (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) a (Set.centralizer.{u1} M S (MulZeroClass.toMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1)))))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) b (Set.centralizer.{u1} M S (MulZeroClass.toMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1)))))) -> (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) (HDiv.hDiv.{u1, u1, u1} M M M (instHDiv.{u1} M (GroupWithZero.toDiv.{u1} M _inst_1)) a b) (Set.centralizer.{u1} M S (MulZeroClass.toMul.{u1} M (MulZeroOneClass.toMulZeroClass.{u1} M (MonoidWithZero.toMulZeroOneClass.{u1} M (GroupWithZero.toMonoidWithZero.{u1} M _inst_1))))))
Case conversion may be inaccurate. Consider using '#align set.div_mem_centralizer₀ Set.div_mem_centralizer₀ₓ'. -/
@[simp]
theorem div_mem_centralizer₀ [GroupWithZero M] (ha : a ∈ centralizer S) (hb : b ∈ centralizer S) :
    a / b ∈ centralizer S := by
  rw [div_eq_mul_inv]
  exact mul_mem_centralizer ha (inv_mem_centralizer₀ hb)
#align set.div_mem_centralizer₀ Set.div_mem_centralizer₀

#print Set.centralizer_subset /-
@[to_additive add_centralizer_subset]
theorem centralizer_subset [Mul M] (h : S ⊆ T) : centralizer T ⊆ centralizer S := fun t ht s hs =>
  ht s (h hs)
#align set.centralizer_subset Set.centralizer_subset
#align set.add_centralizer_subset Set.add_centralizer_subset
-/

variable (M)

#print Set.centralizer_univ /-
@[simp, to_additive add_centralizer_univ]
theorem centralizer_univ [Mul M] : centralizer univ = center M :=
  Subset.antisymm (fun a ha b => ha b (Set.mem_univ b)) fun a ha b hb => ha b
#align set.centralizer_univ Set.centralizer_univ
#align set.add_centralizer_univ Set.add_centralizer_univ
-/

variable {M} (S)

/- warning: set.centralizer_eq_univ -> Set.centralizer_eq_univ is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : CommSemigroup.{u1} M], Eq.{succ u1} (Set.{u1} M) (Set.centralizer.{u1} M S (Semigroup.toHasMul.{u1} M (CommSemigroup.toSemigroup.{u1} M _inst_1))) (Set.univ.{u1} M)
but is expected to have type
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : CommSemigroup.{u1} M], Eq.{succ u1} (Set.{u1} M) (Set.centralizer.{u1} M S (Semigroup.toMul.{u1} M (CommSemigroup.toSemigroup.{u1} M _inst_1))) (Set.univ.{u1} M)
Case conversion may be inaccurate. Consider using '#align set.centralizer_eq_univ Set.centralizer_eq_univₓ'. -/
@[simp, to_additive add_centralizer_eq_univ]
theorem centralizer_eq_univ [CommSemigroup M] : centralizer S = univ :=
  Subset.antisymm (subset_univ _) fun x hx y hy => mul_comm y x
#align set.centralizer_eq_univ Set.centralizer_eq_univ
#align set.add_centralizer_eq_univ Set.add_centralizer_eq_univ

end Set

namespace Subsemigroup

section

variable {M} [Semigroup M] (S)

/- warning: subsemigroup.centralizer -> Subsemigroup.centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}}, (Set.{u1} M) -> (forall [_inst_1 : Semigroup.{u1} M], Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1))
but is expected to have type
  forall {M : Type.{u1}}, (Set.{u1} M) -> (forall [_inst_1 : Semigroup.{u1} M], Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1))
Case conversion may be inaccurate. Consider using '#align subsemigroup.centralizer Subsemigroup.centralizerₓ'. -/
/-- The centralizer of a subset of a semigroup `M`. -/
@[to_additive "The centralizer of a subset of an additive semigroup."]
def centralizer : Subsemigroup M where
  carrier := S.centralizer
  mul_mem' a b := Set.mul_mem_centralizer
#align subsemigroup.centralizer Subsemigroup.centralizer
#align add_subsemigroup.centralizer AddSubsemigroup.centralizer

/- warning: subsemigroup.coe_centralizer -> Subsemigroup.coe_centralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : Semigroup.{u1} M], Eq.{succ u1} (Set.{u1} M) ((fun (a : Type.{u1}) (b : Type.{u1}) [self : HasLiftT.{succ u1, succ u1} a b] => self.0) (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (Set.{u1} M) (HasLiftT.mk.{succ u1, succ u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (Set.{u1} M) (CoeTCₓ.coe.{succ u1, succ u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (Set.{u1} M) (SetLike.Set.hasCoeT.{u1, u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) M (Subsemigroup.setLike.{u1} M (Semigroup.toHasMul.{u1} M _inst_1))))) (Subsemigroup.centralizer.{u1} M S _inst_1)) (Set.centralizer.{u1} M S (Semigroup.toHasMul.{u1} M _inst_1))
but is expected to have type
  forall {M : Type.{u1}} (S : Set.{u1} M) [_inst_1 : Semigroup.{u1} M], Eq.{succ u1} (Set.{u1} M) (SetLike.coe.{u1, u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) M (Subsemigroup.instSetLikeSubsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (Subsemigroup.centralizer.{u1} M S _inst_1)) (Set.centralizer.{u1} M S (Semigroup.toMul.{u1} M _inst_1))
Case conversion may be inaccurate. Consider using '#align subsemigroup.coe_centralizer Subsemigroup.coe_centralizerₓ'. -/
@[simp, norm_cast, to_additive]
theorem coe_centralizer : ↑(centralizer S) = S.centralizer :=
  rfl
#align subsemigroup.coe_centralizer Subsemigroup.coe_centralizer
#align add_subsemigroup.coe_centralizer AddSubsemigroup.coe_centralizer

variable {S}

/- warning: subsemigroup.mem_centralizer_iff -> Subsemigroup.mem_centralizer_iff is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} [_inst_1 : Semigroup.{u1} M] {z : M}, Iff (Membership.Mem.{u1, u1} M (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (SetLike.hasMem.{u1, u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) M (Subsemigroup.setLike.{u1} M (Semigroup.toHasMul.{u1} M _inst_1))) z (Subsemigroup.centralizer.{u1} M S _inst_1)) (forall (g : M), (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) g S) -> (Eq.{succ u1} M (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) g z) (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) z g)))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} [_inst_1 : Semigroup.{u1} M] {z : M}, Iff (Membership.mem.{u1, u1} M (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (SetLike.instMembership.{u1, u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) M (Subsemigroup.instSetLikeSubsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1))) z (Subsemigroup.centralizer.{u1} M S _inst_1)) (forall (g : M), (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) g S) -> (Eq.{succ u1} M (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toMul.{u1} M _inst_1)) g z) (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toMul.{u1} M _inst_1)) z g)))
Case conversion may be inaccurate. Consider using '#align subsemigroup.mem_centralizer_iff Subsemigroup.mem_centralizer_iffₓ'. -/
@[to_additive]
theorem mem_centralizer_iff {z : M} : z ∈ centralizer S ↔ ∀ g ∈ S, g * z = z * g :=
  Iff.rfl
#align subsemigroup.mem_centralizer_iff Subsemigroup.mem_centralizer_iff
#align add_subsemigroup.mem_centralizer_iff AddSubsemigroup.mem_centralizer_iff

/- warning: subsemigroup.decidable_mem_centralizer -> Subsemigroup.decidableMemCentralizer is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} [_inst_1 : Semigroup.{u1} M] (a : M) [_inst_2 : Decidable (forall (b : M), (Membership.Mem.{u1, u1} M (Set.{u1} M) (Set.hasMem.{u1} M) b S) -> (Eq.{succ u1} M (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) b a) (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) a b)))], Decidable (Membership.Mem.{u1, u1} M (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (SetLike.hasMem.{u1, u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) M (Subsemigroup.setLike.{u1} M (Semigroup.toHasMul.{u1} M _inst_1))) a (Subsemigroup.centralizer.{u1} M S _inst_1))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} [_inst_1 : Semigroup.{u1} M] (a : M) [_inst_2 : Decidable (forall (b : M), (Membership.mem.{u1, u1} M (Set.{u1} M) (Set.instMembershipSet.{u1} M) b S) -> (Eq.{succ u1} M (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toMul.{u1} M _inst_1)) b a) (HMul.hMul.{u1, u1, u1} M M M (instHMul.{u1} M (Semigroup.toMul.{u1} M _inst_1)) a b)))], Decidable (Membership.mem.{u1, u1} M (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (SetLike.instMembership.{u1, u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) M (Subsemigroup.instSetLikeSubsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1))) a (Subsemigroup.centralizer.{u1} M S _inst_1))
Case conversion may be inaccurate. Consider using '#align subsemigroup.decidable_mem_centralizer Subsemigroup.decidableMemCentralizerₓ'. -/
@[to_additive]
instance decidableMemCentralizer (a) [Decidable <| ∀ b ∈ S, b * a = a * b] :
    Decidable (a ∈ centralizer S) :=
  decidable_of_iff' _ mem_centralizer_iff
#align subsemigroup.decidable_mem_centralizer Subsemigroup.decidableMemCentralizer
#align add_subsemigroup.decidable_mem_centralizer AddSubsemigroup.decidableMemCentralizer

/- warning: subsemigroup.centralizer_le -> Subsemigroup.centralizer_le is a dubious translation:
lean 3 declaration is
  forall {M : Type.{u1}} {S : Set.{u1} M} {T : Set.{u1} M} [_inst_1 : Semigroup.{u1} M], (HasSubset.Subset.{u1} (Set.{u1} M) (Set.hasSubset.{u1} M) S T) -> (LE.le.{u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (Preorder.toLE.{u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (PartialOrder.toPreorder.{u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (SetLike.partialOrder.{u1, u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) M (Subsemigroup.setLike.{u1} M (Semigroup.toHasMul.{u1} M _inst_1))))) (Subsemigroup.centralizer.{u1} M T _inst_1) (Subsemigroup.centralizer.{u1} M S _inst_1))
but is expected to have type
  forall {M : Type.{u1}} {S : Set.{u1} M} {T : Set.{u1} M} [_inst_1 : Semigroup.{u1} M], (HasSubset.Subset.{u1} (Set.{u1} M) (Set.instHasSubsetSet.{u1} M) S T) -> (LE.le.{u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (Preorder.toLE.{u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (PartialOrder.toPreorder.{u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (CompleteSemilatticeInf.toPartialOrder.{u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (CompleteLattice.toCompleteSemilatticeInf.{u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (Subsemigroup.instCompleteLatticeSubsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)))))) (Subsemigroup.centralizer.{u1} M T _inst_1) (Subsemigroup.centralizer.{u1} M S _inst_1))
Case conversion may be inaccurate. Consider using '#align subsemigroup.centralizer_le Subsemigroup.centralizer_leₓ'. -/
@[to_additive]
theorem centralizer_le (h : S ⊆ T) : centralizer T ≤ centralizer S :=
  Set.centralizer_subset h
#align subsemigroup.centralizer_le Subsemigroup.centralizer_le
#align add_subsemigroup.centralizer_le AddSubsemigroup.centralizer_le

variable (M)

/- warning: subsemigroup.centralizer_univ -> Subsemigroup.centralizer_univ is a dubious translation:
lean 3 declaration is
  forall (M : Type.{u1}) [_inst_1 : Semigroup.{u1} M], Eq.{succ u1} (Subsemigroup.{u1} M (Semigroup.toHasMul.{u1} M _inst_1)) (Subsemigroup.centralizer.{u1} M (Set.univ.{u1} M) _inst_1) (Subsemigroup.center.{u1} M _inst_1)
but is expected to have type
  forall (M : Type.{u1}) [_inst_1 : Semigroup.{u1} M], Eq.{succ u1} (Subsemigroup.{u1} M (Semigroup.toMul.{u1} M _inst_1)) (Subsemigroup.centralizer.{u1} M (Set.univ.{u1} M) _inst_1) (Subsemigroup.center.{u1} M _inst_1)
Case conversion may be inaccurate. Consider using '#align subsemigroup.centralizer_univ Subsemigroup.centralizer_univₓ'. -/
@[simp, to_additive]
theorem centralizer_univ : centralizer Set.univ = center M :=
  SetLike.ext' (Set.centralizer_univ M)
#align subsemigroup.centralizer_univ Subsemigroup.centralizer_univ
#align add_subsemigroup.centralizer_univ AddSubsemigroup.centralizer_univ

end

end Subsemigroup

-- Guard against import creep
assert_not_exists Finset


/-
Copyright (c) 2019 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Johan Commelin
-/
import data.mv_polynomial.equiv
import data.mv_polynomial.comm_ring
import deprecated.ring
import logic.equiv.functor
import ring_theory.free_ring

/-!
# Free commutative rings

The theory of the free commutative ring generated by a type `α`.
It is isomorphic to the polynomial ring over ℤ with variables
in `α`

## Main definitions

* `free_comm_ring α`     : the free commutative ring on a type α
* `lift (f : α → R)` : the ring hom `free_comm_ring α →+* R` induced by functoriality from `f`.
* `map (f : α → β)`      : the ring hom `free_comm_ring α →*+ free_comm_ring β` induced by
                           functoriality from f.

## Main results

`free_comm_ring` has functorial properties (it is an adjoint to the forgetful functor).
In this file we have:

* `of : α → free_comm_ring α`
* `lift (f : α → R) : free_comm_ring α →+* R`
* `map (f : α → β) : free_comm_ring α →+* free_comm_ring β`

* `free_comm_ring_equiv_mv_polynomial_int : free_comm_ring α ≃+* mv_polynomial α ℤ` :
    `free_comm_ring α` is isomorphic to a polynomial ring.

## Implementation notes

`free_comm_ring α` is implemented not using `mv_polynomial` but
directly as the free abelian group on `multiset α`, the type
of monomials in this free commutative ring.

## Tags

free commutative ring, free ring
-/

noncomputable theory
open_locale classical polynomial

universes u v

variables (α : Type u)

/-- `free_comm_ring α` is the free commutative ring on the type `α`. -/
@[derive [comm_ring, inhabited]]
def free_comm_ring (α : Type u) : Type u :=
free_abelian_group $ multiplicative $ multiset α

namespace free_comm_ring

variables {α}

/-- The canonical map from `α` to the free commutative ring on `α`. -/
def of (x : α) : free_comm_ring α :=
free_abelian_group.of $ multiplicative.of_add ({x} : multiset α)

lemma of_injective : function.injective (of : α → free_comm_ring α) :=
free_abelian_group.of_injective.comp (λ x y,
  (multiset.coe_eq_coe.trans list.singleton_perm_singleton).mp)

@[elab_as_eliminator] protected lemma induction_on
  {C : free_comm_ring α → Prop} (z : free_comm_ring α)
  (hn1 : C (-1)) (hb : ∀ b, C (of b))
  (ha : ∀ x y, C x → C y → C (x + y))
  (hm : ∀ x y, C x → C y → C (x * y)) : C z :=
have hn : ∀ x, C x → C (-x), from λ x ih, neg_one_mul x ▸ hm _ _ hn1 ih,
have h1 : C 1, from neg_neg (1 : free_comm_ring α) ▸ hn _ hn1,
free_abelian_group.induction_on z
  (add_left_neg (1 : free_comm_ring α) ▸ ha _ _ hn1 h1)
  (λ m, multiset.induction_on m h1 $ λ a m ih, hm _ _ (hb a) ih)
  (λ m ih, hn _ ih)
  ha
section lift

variables {R : Type v} [comm_ring R] (f : α → R)

/-- A helper to implement `lift`. This is essentially `free_comm_monoid.lift`, but this does not
currently exist. -/
private def lift_to_multiset : (α → R) ≃ (multiplicative (multiset α) →* R) :=
{ to_fun := λ f,
  { to_fun := λ s, (s.to_add.map f).prod,
    map_mul' := λ x y, calc _ = multiset.prod ((multiset.map f x) + (multiset.map f y)) :
                                    by {congr' 1, exact multiset.map_add _ _ _}
                          ... = _ : multiset.prod_add _ _,
    map_one' := rfl},
  inv_fun := λ F x, F (multiplicative.of_add ({x} : multiset α)),
  left_inv := λ f, funext $ λ x, show (multiset.map f {x}).prod = _, by simp,
  right_inv := λ F, monoid_hom.ext $ λ x,
    let F' := F.to_additive'', x' := x.to_add in show (multiset.map (λ a, F' {a}) x').sum = F' x',
    begin
      rw [←multiset.map_map, ←add_monoid_hom.map_multiset_sum],
      exact F.congr_arg (multiset.sum_map_singleton x'),
    end }

/-- Lift a map `α → R` to a additive group homomorphism `free_comm_ring α → R`.
For a version producing a bundled homomorphism, see `lift_hom`. -/
def lift : (α → R) ≃ (free_comm_ring α →+* R) :=
equiv.trans lift_to_multiset free_abelian_group.lift_monoid

@[simp] lemma lift_of (x : α) : lift f (of x) = f x :=
(free_abelian_group.lift.of _ _).trans $ mul_one _

@[simp] lemma lift_comp_of (f : free_comm_ring α →+* R) : lift (f ∘ of) = f :=
ring_hom.ext $ λ x, free_comm_ring.induction_on x
  (by rw [ring_hom.map_neg, ring_hom.map_one, f.map_neg, f.map_one])
  (lift_of _)
  (λ x y ihx ihy, by rw [ring_hom.map_add, f.map_add, ihx, ihy])
  (λ x y ihx ihy, by rw [ring_hom.map_mul, f.map_mul, ihx, ihy])

@[ext]
lemma hom_ext ⦃f g : free_comm_ring α →+* R⦄ (h : ∀ x, f (of x) = g (of x)) :
  f = g :=
lift.symm.injective (funext h)

end lift

variables {β : Type v} (f : α → β)

/-- A map `f : α → β` produces a ring homomorphism `free_comm_ring α →+* free_comm_ring β`. -/
def map : free_comm_ring α →+* free_comm_ring β :=
lift $ of ∘ f

@[simp]
lemma map_of (x : α) : map f (of x) = of (f x) := lift_of _ _

/-- `is_supported x s` means that all monomials showing up in `x` have variables in `s`. -/
def is_supported (x : free_comm_ring α) (s : set α) : Prop :=
x ∈ subring.closure (of '' s)

section is_supported
variables {x y : free_comm_ring α} {s t : set α}

theorem is_supported_upwards (hs : is_supported x s) (hst : s ⊆ t) :
  is_supported x t :=
subring.closure_mono (set.monotone_image hst) hs

theorem is_supported_add (hxs : is_supported x s) (hys : is_supported y s) :
  is_supported (x + y) s :=
subring.add_mem _ hxs hys

theorem is_supported_neg (hxs : is_supported x s) :
  is_supported (-x) s :=
subring.neg_mem _ hxs

theorem is_supported_sub (hxs : is_supported x s) (hys : is_supported y s) :
  is_supported (x - y) s :=
subring.sub_mem _ hxs hys

theorem is_supported_mul (hxs : is_supported x s) (hys : is_supported y s) :
  is_supported (x * y) s :=
subring.mul_mem _ hxs hys

theorem is_supported_zero : is_supported 0 s :=
subring.zero_mem _

theorem is_supported_one : is_supported 1 s :=
subring.one_mem _

theorem is_supported_int {i : ℤ} {s : set α} : is_supported ↑i s :=
int.induction_on i is_supported_zero
  (λ i hi, by rw [int.cast_add, int.cast_one]; exact is_supported_add hi is_supported_one)
  (λ i hi, by rw [int.cast_sub, int.cast_one]; exact is_supported_sub hi is_supported_one)

end is_supported

/-- The restriction map from `free_comm_ring α` to `free_comm_ring s` where `s : set α`, defined
  by sending all variables not in `s` to zero. -/
def restriction (s : set α) [decidable_pred (∈ s)] : free_comm_ring α →+* free_comm_ring s :=
lift (λ p, if H : p ∈ s then of (⟨p, H⟩ : s) else 0)

section restriction
variables (s : set α) [decidable_pred (∈ s)] (x y : free_comm_ring α)
@[simp] lemma restriction_of (p) :
  restriction s (of p) = if H : p ∈ s then of ⟨p, H⟩ else 0 := lift_of _ _

end restriction

theorem is_supported_of {p} {s : set α} : is_supported (of p) s ↔ p ∈ s :=
suffices is_supported (of p) s → p ∈ s, from ⟨this, λ hps, subring.subset_closure ⟨p, hps, rfl⟩⟩,
assume hps : is_supported (of p) s, begin
  haveI := classical.dec_pred s,
  have : ∀ x, is_supported x s →
    ∃ (n : ℤ), lift (λ a, if a ∈ s then (0 : ℤ[X]) else polynomial.X) x = n,
  { intros x hx, refine subring.in_closure.rec_on hx _ _ _ _,
    { use 1, rw [ring_hom.map_one], norm_cast },
    { use -1, rw [ring_hom.map_neg, ring_hom.map_one], norm_cast },
    { rintros _ ⟨z, hzs, rfl⟩ _ _, use 0, rw [ring_hom.map_mul, lift_of, if_pos hzs, zero_mul],
      norm_cast },
    { rintros x y ⟨q, hq⟩ ⟨r, hr⟩, refine ⟨q+r, _⟩, rw [ring_hom.map_add, hq, hr], norm_cast } },
  specialize this (of p) hps, rw [lift_of] at this, split_ifs at this, { exact h },
  exfalso, apply ne.symm int.zero_ne_one,
  rcases this with ⟨w, H⟩, rw ←polynomial.C_eq_int_cast at H,
  have : polynomial.X.coeff 1 = (polynomial.C ↑w).coeff 1, by rw H,
  rwa [polynomial.coeff_C, if_neg (one_ne_zero : 1 ≠ 0), polynomial.coeff_X, if_pos rfl] at this
end

theorem map_subtype_val_restriction {x} (s : set α) [decidable_pred (∈ s)]
  (hxs : is_supported x s) :
  map (subtype.val : s → α) (restriction s x) = x :=
begin
  refine subring.in_closure.rec_on hxs _ _ _ _,
  { rw ring_hom.map_one, refl },
  { rw [ring_hom.map_neg, ring_hom.map_neg, ring_hom.map_one], refl },
  { rintros _ ⟨p, hps, rfl⟩ n ih,
    rw [ring_hom.map_mul, restriction_of, dif_pos hps, ring_hom.map_mul, map_of, ih] },
  { intros x y ihx ihy, rw [ring_hom.map_add, ring_hom.map_add, ihx, ihy] }
end

theorem exists_finite_support (x : free_comm_ring α) :
  ∃ s : set α, set.finite s ∧ is_supported x s :=
free_comm_ring.induction_on x
  ⟨∅, set.finite_empty, is_supported_neg is_supported_one⟩
  (λ p, ⟨{p}, set.finite_singleton p, is_supported_of.2 $ set.mem_singleton _⟩)
  (λ x y ⟨s, hfs, hxs⟩ ⟨t, hft, hxt⟩, ⟨s ∪ t, hfs.union hft, is_supported_add
    (is_supported_upwards hxs $ set.subset_union_left s t)
    (is_supported_upwards hxt $ set.subset_union_right s t)⟩)
  (λ x y ⟨s, hfs, hxs⟩ ⟨t, hft, hxt⟩, ⟨s ∪ t, hfs.union hft, is_supported_mul
    (is_supported_upwards hxs $ set.subset_union_left s t)
    (is_supported_upwards hxt $ set.subset_union_right s t)⟩)

theorem exists_finset_support (x : free_comm_ring α) : ∃ s : finset α, is_supported x ↑s :=
let ⟨s, hfs, hxs⟩ := exists_finite_support x in ⟨hfs.to_finset, by rwa set.finite.coe_to_finset⟩

end free_comm_ring

namespace free_ring
open function
variable (α)

/-- The canonical ring homomorphism from the free ring generated by `α` to the free commutative ring
    generated by `α`. -/
def to_free_comm_ring {α} : free_ring α →+* free_comm_ring α :=
free_ring.lift free_comm_ring.of

instance : has_coe (free_ring α) (free_comm_ring α) := ⟨to_free_comm_ring⟩

/-- The natural map `free_ring α → free_comm_ring α`, as a `ring_hom`. -/
def coe_ring_hom : free_ring α →+* free_comm_ring α := to_free_comm_ring

@[simp, norm_cast] protected lemma coe_zero : ↑(0 : free_ring α) = (0 : free_comm_ring α) := rfl
@[simp, norm_cast] protected lemma coe_one : ↑(1 : free_ring α) = (1 : free_comm_ring α) := rfl

variable {α}

@[simp] protected lemma coe_of (a : α) : ↑(free_ring.of a) = free_comm_ring.of a :=
free_ring.lift_of _ _
@[simp, norm_cast] protected lemma coe_neg (x : free_ring α) : ↑(-x) = -(x : free_comm_ring α) :=
(free_ring.lift _).map_neg _
@[simp, norm_cast] protected lemma coe_add (x y : free_ring α) :
  ↑(x + y) = (x : free_comm_ring α) + y :=
(free_ring.lift _).map_add _ _
@[simp, norm_cast] protected lemma coe_sub (x y : free_ring α) :
  ↑(x - y) = (x : free_comm_ring α) - y :=
(free_ring.lift _).map_sub _ _
@[simp, norm_cast] protected lemma coe_mul (x y : free_ring α) :
  ↑(x * y) = (x : free_comm_ring α) * y :=
(free_ring.lift _).map_mul _ _

variable (α)

protected lemma coe_surjective : surjective (coe : free_ring α → free_comm_ring α) :=
λ x,
begin
  apply free_comm_ring.induction_on x,
  { use -1, refl },
  { intro x, use free_ring.of x, refl },
  { rintros _ _ ⟨x, rfl⟩ ⟨y, rfl⟩, use x + y, exact (free_ring.lift _).map_add _ _ },
  { rintros _ _ ⟨x, rfl⟩ ⟨y, rfl⟩, use x * y, exact (free_ring.lift _).map_mul _ _ }
end

lemma coe_eq :
  (coe : free_ring α → free_comm_ring α) =
  @functor.map free_abelian_group _ _ _ (λ (l : list α), (l : multiset α)) :=
funext $ λ x, free_abelian_group.lift.unique _ _ $ λ L,
by { simp_rw [free_abelian_group.lift.of, (∘)], exact free_monoid.rec_on L rfl
(λ hd tl ih, by { rw [(free_monoid.lift _).map_mul, free_monoid.lift_eval_of, ih], refl }) }

-- FIXME This was in `deprecated.ring`, but only used here.
-- It would be good to inline it into the next construction.
/-- Interpret an equivalence `f : R ≃ S` as a ring equivalence `R ≃+* S`. -/
def of' {R S : Type*} [ring R] [ring S] (e : R ≃ S) (he : is_ring_hom e) : R ≃+* S :=
{ .. e,
  .. monoid_hom.of he.to_is_semiring_hom.to_is_monoid_hom,
  .. add_monoid_hom.of he.to_is_semiring_hom.to_is_add_monoid_hom }

/-- If α has size at most 1 then the natural map from the free ring on `α` to the
    free commutative ring on `α` is an isomorphism of rings. -/
def subsingleton_equiv_free_comm_ring [subsingleton α] :
  free_ring α ≃+* free_comm_ring α :=
@of' (free_ring α) (free_comm_ring α) _ _
  (functor.map_equiv free_abelian_group (multiset.subsingleton_equiv α)) $
  begin
    delta functor.map_equiv,
    rw congr_arg is_ring_hom _,
    work_on_goal 3 { symmetry, exact coe_eq α },
    exact (coe_ring_hom _).to_is_ring_hom,
  end

instance [subsingleton α] : comm_ring (free_ring α) :=
{ mul_comm := λ x y,
  by { rw [← (subsingleton_equiv_free_comm_ring α).symm_apply_apply (y * x),
        ((subsingleton_equiv_free_comm_ring α)).map_mul,
        mul_comm,
        ← ((subsingleton_equiv_free_comm_ring α)).map_mul,
        (subsingleton_equiv_free_comm_ring α).symm_apply_apply], },
  .. free_ring.ring α }

end free_ring

/-- The free commutative ring on `α` is isomorphic to the polynomial ring over ℤ with
    variables in `α` -/
def free_comm_ring_equiv_mv_polynomial_int :
  free_comm_ring α ≃+* mv_polynomial α ℤ :=
ring_equiv.of_hom_inv
  (free_comm_ring.lift $ λ a, mv_polynomial.X a)
  (mv_polynomial.eval₂_hom (int.cast_ring_hom (free_comm_ring α)) free_comm_ring.of)
  (by { ext, simp })
  (by ext; simp )

/-- The free commutative ring on the empty type is isomorphic to `ℤ`. -/
def free_comm_ring_pempty_equiv_int : free_comm_ring pempty.{u+1} ≃+* ℤ :=
ring_equiv.trans (free_comm_ring_equiv_mv_polynomial_int _)
  (mv_polynomial.is_empty_ring_equiv _ pempty)

/-- The free commutative ring on a type with one term is isomorphic to `ℤ[X]`. -/
def free_comm_ring_punit_equiv_polynomial_int : free_comm_ring punit.{u+1} ≃+* polynomial ℤ :=
(free_comm_ring_equiv_mv_polynomial_int _).trans (mv_polynomial.punit_alg_equiv ℤ).to_ring_equiv

open free_ring

/-- The free ring on the empty type is isomorphic to `ℤ`. -/
def free_ring_pempty_equiv_int : free_ring pempty.{u+1} ≃+* ℤ :=
ring_equiv.trans (subsingleton_equiv_free_comm_ring _) free_comm_ring_pempty_equiv_int

/-- The free ring on a type with one term is isomorphic to `ℤ[X]`. -/
def free_ring_punit_equiv_polynomial_int : free_ring punit.{u+1} ≃+* polynomial ℤ :=
ring_equiv.trans (subsingleton_equiv_free_comm_ring _) free_comm_ring_punit_equiv_polynomial_int


/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Gabriel Ebner

! This file was ported from Lean 3 source module data.int.cast.basic
! leanprover-community/mathlib commit 70d50ecfd4900dd6d328da39ab7ebd516abe4025
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Int.Cast.Defs
import Mathlib.Algebra.Group.Basic

/-!
# Cast of integers (additional theorems)

This file proves additional properties about the *canonical* homomorphism from
the integers into an additive group with a one (`Int.cast`).

There is also `Data.Int.Cast.Lemmas`,
which includes lemmas stated in terms of algebraic homomorphisms,
and results involving the order structure of `ℤ`.

By contrast, this file's only import beyond `Data.Int.Cast.Defs` is `Algebra.Group.Basic`.
-/

universe u

namespace Nat

variable {R : Type u} [AddGroupWithOne R]

@[simp, norm_cast]
theorem cast_sub {m n} (h : m ≤ n) : ((n - m : ℕ) : R) = n - m :=
  eq_sub_of_add_eq <| by rw [← cast_add, Nat.sub_add_cancel h]
#align nat.cast_sub Nat.cast_subₓ
-- `HasLiftT` appeared in the type signature

@[simp, norm_cast]
theorem cast_pred : ∀ {n}, 0 < n → ((n - 1 : ℕ) : R) = n - 1
  | 0, h => by cases h
  | n + 1, _ => by rw [cast_succ, add_sub_cancel]; rfl
#align nat.cast_pred Nat.cast_pred

end Nat

open Nat

namespace Int

variable {R : Type u} [AddGroupWithOne R]

@[simp, norm_cast]

@[simp, norm_cast]
theorem cast_zero : ((0 : ℤ) : R) = 0 :=
  (AddGroupWithOne.intCast_ofNat 0).trans Nat.cast_zero
#align int.cast_zero Int.cast_zeroₓ
-- type had `HasLiftT`

@[simp high, nolint simpNF, norm_cast] -- this lemma competes with `Int.ofNat_eq_cast` to come later
theorem cast_ofNat (n : ℕ) : ((n : ℤ) : R) = n :=
  AddGroupWithOne.intCast_ofNat _
#align int.cast_coe_nat Int.cast_ofNatₓ
-- expected `n` to be implicit, and `HasLiftT`
#align int.cast_of_nat Int.cast_ofNatₓ

@[simp, norm_cast]
theorem int_cast_ofNat (n : ℕ) [n.AtLeastTwo] :
    ((OfNat.ofNat n : ℤ) : R) = OfNat.ofNat n := by
  simpa only [OfNat.ofNat] using AddGroupWithOne.intCast_ofNat (R := R) n

@[simp, norm_cast]
theorem cast_one : ((1 : ℤ) : R) = 1 := by
  erw [cast_ofNat, Nat.cast_one]
#align int.cast_one Int.cast_oneₓ
-- type had `HasLiftT`

@[simp, norm_cast]
theorem cast_neg : ∀ n, ((-n : ℤ) : R) = -n
  | (0 : ℕ) => by erw [cast_zero, neg_zero]
  | (n + 1 : ℕ) => by erw [cast_ofNat, cast_negSucc]
  | -[n+1] => by erw [cast_ofNat, cast_negSucc, neg_neg]
#align int.cast_neg Int.cast_negₓ
-- type had `HasLiftT`

@[simp, norm_cast]
theorem cast_subNatNat (m n) : ((Int.subNatNat m n : ℤ) : R) = m - n := by
  unfold subNatNat
  cases e : n - m
  · simp only [ofNat_eq_coe]
    simp [e, Nat.le_of_sub_eq_zero e]
  · rw [cast_negSucc, Nat.add_one, ← e, Nat.cast_sub <| _root_.le_of_lt <| Nat.lt_of_sub_eq_succ e,
      neg_sub]
#align int.cast_sub_nat_nat Int.cast_subNatNatₓ
-- type had `HasLiftT`

#align int.neg_of_nat_eq Int.negOfNat_eq

@[simp]
theorem cast_negOfNat (n : ℕ) : ((negOfNat n : ℤ) : R) = -n := by simp [Int.cast_neg, negOfNat_eq]
#align int.cast_neg_of_nat Int.cast_negOfNat

@[simp, norm_cast]
theorem cast_add : ∀ m n, ((m + n : ℤ) : R) = m + n
  | (m : ℕ), (n : ℕ) => by simp [← Int.ofNat_add, Nat.cast_add]
  | (m : ℕ), -[n+1] => by erw [cast_subNatNat, cast_ofNat, cast_negSucc, sub_eq_add_neg]
  | -[m+1], (n : ℕ) => by
    erw [cast_subNatNat, cast_ofNat, cast_negSucc, sub_eq_iff_eq_add, add_assoc,
      eq_neg_add_iff_add_eq, ← Nat.cast_add, ← Nat.cast_add, Nat.add_comm]
  | -[m+1], -[n+1] =>
    show (-[m + n + 1+1] : R) = _ by
      rw [cast_negSucc, cast_negSucc, cast_negSucc, ← neg_add_rev, ← Nat.cast_add,
        Nat.add_right_comm m n 1, Nat.add_assoc, Nat.add_comm]
#align int.cast_add Int.cast_addₓ
-- type had `HasLiftT`

@[simp, norm_cast]
theorem cast_sub (m n) : ((m - n : ℤ) : R) = m - n := by
  simp [Int.sub_eq_add_neg, sub_eq_add_neg, Int.cast_neg, Int.cast_add]
#align int.cast_sub Int.cast_subₓ
-- type had `HasLiftT`

section deprecated
set_option linter.deprecated false

@[norm_cast, deprecated]
theorem ofNat_bit0 (n : ℕ) : (↑(bit0 n) : ℤ) = bit0 ↑n :=
  rfl
#align int.coe_nat_bit0 Int.ofNat_bit0

@[norm_cast, deprecated]
theorem ofNat_bit1 (n : ℕ) : (↑(bit1 n) : ℤ) = bit1 ↑n :=
  rfl
#align int.coe_nat_bit1 Int.ofNat_bit1

@[norm_cast, deprecated]
theorem cast_bit0 (n : ℤ) : ((bit0 n : ℤ) : R) = bit0 (n : R) :=
  Int.cast_add _ _
#align int.cast_bit0 Int.cast_bit0

@[norm_cast, deprecated]
theorem cast_bit1 (n : ℤ) : ((bit1 n : ℤ) : R) = bit1 (n : R) :=
  by rw [bit1, Int.cast_add, Int.cast_one, cast_bit0]; rfl
#align int.cast_bit1 Int.cast_bit1

end deprecated

theorem cast_two : ((2 : ℤ) : R) = 2 :=
  show (((2 : ℕ) : ℤ) : R) = ((2 : ℕ) : R) by rw [cast_ofNat, Nat.cast_ofNat]
#align int.cast_two Int.cast_two

theorem cast_three : ((3 : ℤ) : R) = 3 :=
  show (((3 : ℕ) : ℤ) : R) = ((3 : ℕ) : R) by rw [cast_ofNat, Nat.cast_ofNat]
#align int.cast_three Int.cast_three

theorem cast_four : ((4 : ℤ) : R) = 4 :=
  show (((4 : ℕ) : ℤ) : R) = ((4 : ℕ) : R) by rw [cast_ofNat, Nat.cast_ofNat]
#align int.cast_four Int.cast_four

end Int


import data.real.basic
import .affnK

abbreviation K := ℚ
def dim := 3

def v1 : vec_n K dim := mk_vec_n _ _ ⟨[4,5,6], rfl⟩
#eval v1.coords
def v2 : vec_n K dim := v1 + v1
def v3 := 5•v1 - v2

def p1 : pt_n K dim := mk_pt_n _ _  ⟨[1,2,3], rfl⟩

#eval p1.coords

def p2 : pt_n K dim := v2 +ᵥ p1

#eval p2.coords

def p3 := p1 -ᵥ p2

#eval p3.coords

/-
Code from 6/14 meeting:
-/

/-
Build an n-D affine space from n 1-D spaces
-/
abbreviation affnK (n : nat) := fin n → (affine_space (vec K) (pt K))

/-
-/
example : nat := 3

def a1k : affnK ℚ 1 := λ i, by apply_instance
def a2k : affnK ℚ 2 := λ i, by apply_instance
def a3k : affnK ℚ 3 := λ i, by apply_instance

#check a1k
#check a2k
#check a3k

def foo {n1 n2 : nat} (a1 : affnK K n1) (a2 : affnK K n2) : affnK K (n1 + n2) :=
  λ i : fin (n1 + n2), if i.val < n1 then a1 ⟨ i.1, sorry ⟩ else a2 ⟨ i.1 - n1, sorry ⟩

def a5k := foo ℚ (a2k) (a3k)

#check (a5k)

def a10k := foo ℚ (a5k) (a5k)
#check a10k


/-
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.normed_field
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
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)

theorem closure_ball [normed_space ℝ E] (x : E) {r : ℝ} (hr : r ≠ 0) :
  closure (ball x r) = closed_ball x r :=
begin
  refine set.subset.antisymm closure_ball_subset_closed_ball (λ y hy, _),
  have : continuous_within_at (λ c : ℝ, c • (y - x) + x) (set.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 set.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) ⊆ set.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 (set.Icc (-1:ℝ) 1) :=
      interior_mono this (preimage_interior_subset_interior_preimage hfc hf1),
    contrapose h1,
    simp },
  intros c hc,
  rw [set.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 }

variables (α)

lemma ne_neg_of_mem_sphere [char_zero α] {r : ℝ} (hr : r ≠ 0) (x : sphere (0:E) r) : x ≠ - x :=
λ h, ne_zero_of_mem_sphere hr x ((self_eq_neg α _).mp (by { conv_lhs {rw h}, simp }))

lemma ne_neg_of_mem_unit_sphere [char_zero α] (x : sphere (0:E) 1) : x ≠ - x :=
ne_neg_of_mem_sphere α one_ne_zero x

variables {α}

open normed_field

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

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 (set.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 an algebra endowed with a norm for which the
embedding of `𝕜` in `𝕜'` is an isometry. -/
class normed_algebra (𝕜 : Type*) (𝕜' : Type*) [normed_field 𝕜] [semi_normed_ring 𝕜']
  extends algebra 𝕜 𝕜' :=
(norm_algebra_map_eq : ∀x:𝕜, ∥algebra_map 𝕜 𝕜' x∥ = ∥x∥)

@[simp] lemma norm_algebra_map_eq {𝕜 : Type*} (𝕜' : Type*) [normed_field 𝕜] [semi_normed_ring 𝕜']
  [h : normed_algebra 𝕜 𝕜'] (x : 𝕜) : ∥algebra_map 𝕜 𝕜' x∥ = ∥x∥ :=
normed_algebra.norm_algebra_map_eq _

/-- In a normed algebra, the inclusion of the base field in the extended field is an isometry. -/
lemma algebra_map_isometry (𝕜 : Type*) (𝕜' : Type*) [normed_field 𝕜] [semi_normed_ring 𝕜']
  [normed_algebra 𝕜 𝕜'] : 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_eq],
end

variables (𝕜 : Type*) (𝕜' : Type*) [normed_field 𝕜]

/-- The inclusion of the base field in a normed algebra as a continuous linear map. -/
@[simps]
def algebra_map_clm [semi_normed_ring 𝕜'] [normed_algebra 𝕜 𝕜'] : 𝕜 →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 := (algebra_map_isometry 𝕜 𝕜').continuous }

lemma algebra_map_clm_coe [semi_normed_ring 𝕜'] [normed_algebra 𝕜 𝕜'] :
  (algebra_map_clm 𝕜 𝕜' : 𝕜 → 𝕜') = (algebra_map 𝕜 𝕜' : 𝕜 → 𝕜') := rfl

lemma algebra_map_clm_to_linear_map [semi_normed_ring 𝕜'] [normed_algebra 𝕜 𝕜'] :
  (algebra_map_clm 𝕜 𝕜').to_linear_map = algebra.linear_map 𝕜 𝕜' := rfl

@[priority 100]
instance normed_algebra.to_normed_space [semi_normed_ring 𝕜'] [h : normed_algebra 𝕜 𝕜'] :
  normed_space 𝕜 𝕜' :=
{ norm_smul_le := λ s x, calc
    ∥s • x∥ = ∥((algebra_map 𝕜 𝕜') s) * x∥ : by { rw h.smul_def', refl }
    ... ≤ ∥algebra_map 𝕜 𝕜' s∥ * ∥x∥ : semi_normed_ring.norm_mul _ _
    ... = ∥s∥ * ∥x∥ : by rw norm_algebra_map_eq,
  ..h }

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

instance normed_algebra.id : normed_algebra 𝕜 𝕜 :=
{ norm_algebra_map_eq := by simp,
  .. algebra.id 𝕜}

variables (𝕜') [semi_normed_ring 𝕜'] [normed_algebra 𝕜 𝕜']
include 𝕜

lemma normed_algebra.norm_one : ∥(1:𝕜')∥ = 1 :=
by simpa using (norm_algebra_map_eq 𝕜' (1:𝕜))

lemma normed_algebra.norm_one_class : norm_one_class 𝕜' :=
⟨normed_algebra.norm_one 𝕜 𝕜'⟩

lemma normed_algebra.zero_ne_one : (0:𝕜') ≠ 1 :=
begin
  refine (ne_zero_of_norm_ne_zero _).symm,
  rw normed_algebra.norm_one 𝕜 𝕜', norm_num,
end

lemma normed_algebra.nontrivial : nontrivial 𝕜' :=
⟨⟨0, 1, normed_algebra.zero_ne_one 𝕜 𝕜'⟩⟩

end normed_algebra

section restrict_scalars

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

/-- Warning: This declaration should be used judiciously.
Please consider using `is_scalar_tower` instead.

`𝕜`-normed space structure induced by a `𝕜'`-normed space structure when `𝕜'` is a
normed algebra over `𝕜`. Not registered as an instance as `𝕜'` can not be inferred.

The type synonym `restrict_scalars 𝕜 𝕜' E` will be endowed with this instance by default.
-/
def normed_space.restrict_scalars : normed_space 𝕜 E :=
{ norm_smul_le := λc x, le_of_eq $ begin
    change ∥(algebra_map 𝕜 𝕜' c) • x∥ = ∥c∥ * ∥x∥,
    simp [norm_smul]
  end,
  ..restrict_scalars.module 𝕜 𝕜' 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

instance 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

instance : normed_space 𝕜 (restrict_scalars 𝕜 𝕜' E) :=
(normed_space.restrict_scalars 𝕜 𝕜' E : normed_space 𝕜 E)

end restrict_scalars


import Lean

def f : Nat → Bool
  | 0 => true
  | n + 1 => (match n with
    | 0 => true
    | _ + 1 => true) && f n

def g (i j : Nat) : Nat :=
  if i < 5 then 0 else
  match j with
  | Nat.zero => 1
  | Nat.succ j => g i j

def h (i j : Nat) : Nat :=
  i +
    match j with
    | Nat.zero => 1
    | Nat.succ j => h i j

def r (i j : Nat) : Nat :=
  i +
    match j with
    | Nat.zero => 1
    | Nat.succ j =>
      i * match j with
          | Nat.zero => 2
          | Nat.succ j => r i j

def s (i j : Nat) : Nat :=
  let z :=
    match j with
    | Nat.zero => i
    | Nat.succ j => s i j
  z + z

example : f 0 = true :=
  rfl

example : f (i+1) = ((match i with | 0 => true | _ + 1 => true) && f i) :=
  rfl

example : g i (j + 1) = if i < 5 then 0 else g i j :=
  rfl

example : g i 0 = if i < 5 then 0 else 1 :=
  rfl

example : h i 0 = i + 1 :=
  rfl

example : h i (j + 1) = i + h i j :=
  rfl

example : r i 0 = i + 1 :=
  rfl

example : r i 1 = i + i * 2 :=
  rfl

example : r i (j + 2) = i + i * r i j :=
  rfl

example : s i (j + 1) = let z := s i j; z + z :=
  rfl

open Lean
open Lean.Meta
open Lean.Elab
open Lean.Elab.Command
elab "unfold " e:term : command =>
  runTermElabM fun xs => do
    let e ← instantiateMVars (← Term.withSynthesize <| Term.elabTerm e none)
    match (← unfoldDefinition? e) with
    | some e => logInfo m!"{e}"
    | none => throwError ""

section end

variable (i j : Nat)

unfold f 0
unfold f (i+1)
unfold g i 0
unfold g i (j+1)
unfold h i 0
unfold h i (j + 1)
unfold r i 0
unfold r i 1
unfold r i (j + 2)
unfold r i j.succ.succ
unfold s i (j + 1)


/-
Copyright (c) 2018 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl, Jens Wagemaker, Anne Baanen
-/

import algebra.associated
import algebra.big_operators.finsupp

/-!
# Products of associated, prime, and irreducible elements.

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

This file contains some theorems relating definitions in `algebra.associated`
and products of multisets, finsets, and finsupps.

-/

variables {α β γ δ : Type*}

-- the same local notation used in `algebra.associated`
local infix ` ~ᵤ ` : 50 := associated
open_locale big_operators

namespace prime

variables [comm_monoid_with_zero α] {p : α} (hp : prime p)

lemma exists_mem_multiset_dvd {s : multiset α} :
  p ∣ s.prod → ∃ a ∈ s, p ∣ a :=
multiset.induction_on s (λ h, (hp.not_dvd_one h).elim) $
λ a s ih h,
  have p ∣ a * s.prod, by simpa using h,
  match hp.dvd_or_dvd this with
  | or.inl h := ⟨a, multiset.mem_cons_self a s, h⟩
  | or.inr h := let ⟨a, has, h⟩ := ih h in ⟨a, multiset.mem_cons_of_mem has, h⟩
  end

include hp

lemma exists_mem_multiset_map_dvd {s : multiset β} {f : β → α} :
  p ∣ (s.map f).prod → ∃ a ∈ s, p ∣ f a :=
λ h, by simpa only [exists_prop, multiset.mem_map, exists_exists_and_eq_and]
  using hp.exists_mem_multiset_dvd h

lemma exists_mem_finset_dvd {s : finset β} {f : β → α} :
  p ∣ s.prod f → ∃ i ∈ s, p ∣ f i :=
hp.exists_mem_multiset_map_dvd

end prime

lemma exists_associated_mem_of_dvd_prod [cancel_comm_monoid_with_zero α] {p : α}
  (hp : prime p) {s : multiset α} : (∀ r ∈ s, prime r) → p ∣ s.prod → ∃ q ∈ s, p ~ᵤ q :=
multiset.induction_on s (by simp [mt is_unit_iff_dvd_one.2 hp.not_unit])
  (λ a s ih hs hps, begin
    rw [multiset.prod_cons] at hps,
    cases hp.dvd_or_dvd hps with h h,
    { have hap := hs a (multiset.mem_cons.2 (or.inl rfl)),
      exact ⟨a, multiset.mem_cons_self a _, hp.associated_of_dvd hap h⟩ },
    { rcases ih (λ r hr, hs _ (multiset.mem_cons.2 (or.inr hr))) h with ⟨q, hq₁, hq₂⟩,
      exact ⟨q, multiset.mem_cons.2 (or.inr hq₁), hq₂⟩ }
  end)

lemma multiset.prod_primes_dvd
  [cancel_comm_monoid_with_zero α] [Π a : α, decidable_pred (associated a)]
  {s : multiset α} (n : α) (h : ∀ a ∈ s, prime a) (div : ∀ a ∈ s, a ∣ n)
  (uniq : ∀ a, s.countp (associated a) ≤ 1) :
  s.prod ∣ n :=
begin
  induction s using multiset.induction_on with a s induct n primes divs generalizing n,
  { simp only [multiset.prod_zero, one_dvd] },
  { rw multiset.prod_cons,
    obtain ⟨k, rfl⟩ : a ∣ n := div a (multiset.mem_cons_self a s),
    apply mul_dvd_mul_left a,
    refine induct
      (λ a ha, h a (multiset.mem_cons_of_mem ha))
      (λ a, (multiset.countp_le_of_le _ (multiset.le_cons_self _ _)).trans (uniq a))
      k (λ b b_in_s, _),
    { have b_div_n := div b (multiset.mem_cons_of_mem b_in_s),
      have a_prime := h a (multiset.mem_cons_self a s),
      have b_prime := h b (multiset.mem_cons_of_mem b_in_s),
      refine (b_prime.dvd_or_dvd b_div_n).resolve_left (λ b_div_a, _),
      have assoc := b_prime.associated_of_dvd a_prime b_div_a,
      have := uniq a,
      rw [multiset.countp_cons_of_pos _ (associated.refl _), nat.succ_le_succ_iff, ←not_lt,
        multiset.countp_pos] at this,
      exact this ⟨b, b_in_s, assoc.symm⟩ } }
end

lemma finset.prod_primes_dvd
  [cancel_comm_monoid_with_zero α] [unique αˣ]
  {s : finset α} (n : α) (h : ∀ a ∈ s, prime a) (div : ∀ a ∈ s, a ∣ n) :
  (∏ p in s, p) ∣ n :=
begin
  classical,
  exact multiset.prod_primes_dvd n
    (by simpa only [multiset.map_id', finset.mem_def] using h)
    (by simpa only [multiset.map_id', finset.mem_def] using div)
    (by simp only [multiset.map_id', associated_eq_eq, multiset.countp_eq_card_filter,
        ←multiset.count_eq_card_filter_eq, ←multiset.nodup_iff_count_le_one, s.nodup]),
end

namespace associates

section comm_monoid

variables [comm_monoid α]

theorem prod_mk {p : multiset α} : (p.map associates.mk).prod = associates.mk p.prod :=
multiset.induction_on p (by simp) $ λ a s ih, by simp [ih, associates.mk_mul_mk]

theorem finset_prod_mk {p : finset β} {f : β → α} :
  ∏ i in p, associates.mk (f i) = associates.mk (∏ i in p, f i) :=
by rw [finset.prod_eq_multiset_prod, ← multiset.map_map, prod_mk, ← finset.prod_eq_multiset_prod]

theorem rel_associated_iff_map_eq_map {p q : multiset α} :
  multiset.rel associated p q ↔ p.map associates.mk = q.map associates.mk :=
by { rw [← multiset.rel_eq, multiset.rel_map], simp only [mk_eq_mk_iff_associated] }

theorem prod_eq_one_iff {p : multiset (associates α)} :
  p.prod = 1 ↔ (∀a ∈ p, (a:associates α) = 1) :=
multiset.induction_on p
  (by simp)
  (by simp [mul_eq_one_iff, or_imp_distrib, forall_and_distrib] {contextual := tt})

theorem prod_le_prod {p q : multiset (associates α)} (h : p ≤ q) : p.prod ≤ q.prod :=
begin
  haveI := classical.dec_eq (associates α),
  haveI := classical.dec_eq α,
  suffices : p.prod ≤ (p + (q - p)).prod, { rwa [add_tsub_cancel_of_le h] at this },
  suffices : p.prod * 1 ≤ p.prod * (q - p).prod, { simpa },
  exact mul_mono (le_refl p.prod) one_le
end

end comm_monoid

section cancel_comm_monoid_with_zero

variables [cancel_comm_monoid_with_zero α]

lemma exists_mem_multiset_le_of_prime {s : multiset (associates α)} {p : associates α}
  (hp : prime p) :
  p ≤ s.prod → ∃a∈s, p ≤ a :=
multiset.induction_on s (assume ⟨d, eq⟩, (hp.ne_one (mul_eq_one_iff.1 eq.symm).1).elim) $
assume a s ih h,
  have p ≤ a * s.prod, by simpa using h,
  match prime.le_or_le hp this with
  | or.inl h := ⟨a, multiset.mem_cons_self a s, h⟩
  | or.inr h := let ⟨a, has, h⟩ := ih h in ⟨a, multiset.mem_cons_of_mem has, h⟩
  end

end cancel_comm_monoid_with_zero

end associates

namespace multiset

lemma prod_ne_zero_of_prime [cancel_comm_monoid_with_zero α] [nontrivial α]
 (s : multiset α) (h : ∀ x ∈ s, prime x) : s.prod ≠ 0 :=
multiset.prod_ne_zero (λ h0, prime.ne_zero (h 0 h0) rfl)

end multiset

open finset finsupp

section comm_monoid_with_zero

variables {M : Type*} [comm_monoid_with_zero M]

lemma prime.dvd_finset_prod_iff {S : finset α} {p : M} (pp : prime p) (g : α → M) :
  p ∣ S.prod g ↔ ∃ a ∈ S, p ∣ g a :=
⟨pp.exists_mem_finset_dvd, λ ⟨a, ha1, ha2⟩, dvd_trans ha2 (dvd_prod_of_mem g ha1)⟩

lemma prime.dvd_finsupp_prod_iff  {f: α →₀ M} {g : α → M → ℕ} {p : ℕ} (pp : prime p) :
  p ∣ f.prod g ↔ ∃ a ∈ f.support, p ∣ g a (f a) :=
prime.dvd_finset_prod_iff pp _

end comm_monoid_with_zero