/-
Copyright (c) 2018 Kenny Lau. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Kenny Lau, Michael Howes
-/
import data.finite.card
import group_theory.commutator
import group_theory.finiteness

/-!
# The abelianization of a group

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

This file defines the commutator and the abelianization of a group. It furthermore prepares for the
result that the abelianization is left adjoint to the forgetful functor from abelian groups to
groups, which can be found in `algebra/category/Group/adjunctions`.

## Main definitions

* `commutator`: defines the commutator of a group `G` as a subgroup of `G`.
* `abelianization`: defines the abelianization of a group `G` as the quotient of a group by its
  commutator subgroup.
* `abelianization.map`: lifts a group homomorphism to a homomorphism between the abelianizations
* `mul_equiv.abelianization_congr`: Equivalent groups have equivalent abelianizations

-/

universes u v w

-- Let G be a group.
variables (G : Type u) [group G]

/-- The commutator subgroup of a group G is the normal subgroup
  generated by the commutators [p,q]=`p*q*p⁻¹*q⁻¹`. -/
@[derive subgroup.normal]
def commutator : subgroup G :=
⁅(⊤ : subgroup G), ⊤⁆

lemma commutator_def : commutator G = ⁅(⊤ : subgroup G), ⊤⁆ := rfl

lemma commutator_eq_closure : commutator G = subgroup.closure (commutator_set G) :=
by simp [commutator, subgroup.commutator_def, commutator_set]

lemma commutator_eq_normal_closure :
  commutator G = subgroup.normal_closure (commutator_set G) :=
by simp [commutator, subgroup.commutator_def', commutator_set]

instance commutator_characteristic : (commutator G).characteristic :=
subgroup.commutator_characteristic ⊤ ⊤

instance [finite (commutator_set G)] : group.fg (commutator G) :=
begin
  rw commutator_eq_closure,
  apply group.closure_finite_fg,
end

lemma rank_commutator_le_card [finite (commutator_set G)] :
  group.rank (commutator G) ≤ nat.card (commutator_set G) :=
begin
  rw subgroup.rank_congr (commutator_eq_closure G),
  apply subgroup.rank_closure_finite_le_nat_card,
end

lemma commutator_centralizer_commutator_le_center :
  ⁅(commutator G).centralizer, (commutator G).centralizer⁆ ≤ subgroup.center G :=
begin
  rw [←subgroup.centralizer_top, ←subgroup.commutator_eq_bot_iff_le_centralizer],
  suffices : ⁅⁅⊤, (commutator G).centralizer⁆, (commutator G).centralizer⁆ = ⊥,
  { refine subgroup.commutator_commutator_eq_bot_of_rotate _ this,
    rwa subgroup.commutator_comm (commutator G).centralizer },
  rw [subgroup.commutator_comm, subgroup.commutator_eq_bot_iff_le_centralizer],
  exact set.centralizer_subset (subgroup.commutator_mono le_top le_top),
end

/-- The abelianization of G is the quotient of G by its commutator subgroup. -/
def abelianization : Type u :=
G ⧸ (commutator G)

namespace abelianization

local attribute [instance] quotient_group.left_rel

instance : comm_group (abelianization G) :=
{ mul_comm := λ x y, quotient.induction_on₂' x y $ λ a b, quotient.sound' $
    quotient_group.left_rel_apply.mpr $
    subgroup.subset_closure ⟨b⁻¹, subgroup.mem_top b⁻¹, a⁻¹, subgroup.mem_top a⁻¹, by group⟩,
.. quotient_group.quotient.group _ }

instance : inhabited (abelianization G) := ⟨1⟩

instance [fintype G] [decidable_pred (∈ commutator G)] :
  fintype (abelianization G) :=
quotient_group.fintype (commutator G)

instance [finite G] : finite (abelianization G) :=
quotient.finite _

variable {G}

/-- `of` is the canonical projection from G to its abelianization. -/
def of : G →* abelianization G :=
{ to_fun := quotient_group.mk,
  map_one' := rfl,
  map_mul' := λ x y, rfl }

@[simp] lemma mk_eq_of (a : G) : quot.mk _ a = of a := rfl

section lift
-- So far we have built Gᵃᵇ and proved it's an abelian group.
-- Furthremore we defined the canonical projection `of : G → Gᵃᵇ`

-- Let `A` be an abelian group and let `f` be a group homomorphism from `G` to `A`.
variables {A : Type v} [comm_group A] (f : G →* A)

lemma commutator_subset_ker : commutator G ≤ f.ker :=
begin
  rw [commutator_eq_closure, subgroup.closure_le],
  rintros x ⟨p, q, rfl⟩,
  simp [monoid_hom.mem_ker, mul_right_comm (f p) (f q), commutator_element_def],
end

/-- If `f : G → A` is a group homomorphism to an abelian group, then `lift f` is the unique map from
  the abelianization of a `G` to `A` that factors through `f`. -/
def lift : (G →* A) ≃ (abelianization G →* A) :=
{ to_fun := λ f, quotient_group.lift _ f (λ x h, f.mem_ker.2 $ commutator_subset_ker _ h),
  inv_fun := λ F, F.comp of,
  left_inv := λ f, monoid_hom.ext $ λ x, rfl,
  right_inv := λ F, monoid_hom.ext $ λ x, quotient_group.induction_on x $ λ z, rfl }

@[simp] lemma lift.of (x : G) : lift f (of x) = f x :=
rfl

theorem lift.unique
  (φ : abelianization G →* A)
  -- hφ : φ agrees with f on the image of G in Gᵃᵇ
  (hφ : ∀ (x : G), φ (of x) = f x)
  {x : abelianization G} :
  φ x = lift f x :=
quotient_group.induction_on x hφ

@[simp] lemma lift_of : lift of = monoid_hom.id (abelianization G) :=
lift.apply_symm_apply $ monoid_hom.id _

end lift

variables {A : Type v} [monoid A]

/-- See note [partially-applied ext lemmas]. -/
@[ext]
theorem hom_ext (φ ψ : abelianization G →* A)
  (h : φ.comp of = ψ.comp of) : φ = ψ :=
monoid_hom.ext $ λ x, quotient_group.induction_on x $ monoid_hom.congr_fun h

section map

variables {H : Type v} [group H] (f : G →* H)

/-- The map operation of the `abelianization` functor -/
def map : abelianization G →* abelianization H := lift (of.comp f)

@[simp]
lemma map_of (x : G) : map f (of x) = of (f x) := rfl

@[simp]
lemma map_id : map (monoid_hom.id G) = monoid_hom.id (abelianization G) := hom_ext _ _ rfl

@[simp]
lemma map_comp {I : Type w} [group I] (g : H →* I) :
  (map g).comp (map f) = map (g.comp f) := hom_ext _ _ rfl

@[simp]
lemma map_map_apply {I : Type w} [group I] {g : H →* I} {x : abelianization G}:
  map g (map f x) = map (g.comp f) x := monoid_hom.congr_fun (map_comp _ _) x

end map

end abelianization

section abelianization_congr

variables {G} {H : Type v} [group H] (e : G ≃* H)

/-- Equivalent groups have equivalent abelianizations -/
def mul_equiv.abelianization_congr : abelianization G ≃* abelianization H :=
{ to_fun := abelianization.map e.to_monoid_hom,
  inv_fun := abelianization.map e.symm.to_monoid_hom,
  left_inv := by { rintros ⟨a⟩, simp },
  right_inv := by { rintros ⟨a⟩, simp },
  map_mul' := monoid_hom.map_mul _ }

@[simp]
lemma abelianization_congr_of (x : G) :
  (e.abelianization_congr) (abelianization.of x) = abelianization.of (e x) := rfl

@[simp]
lemma abelianization_congr_refl :
  (mul_equiv.refl G).abelianization_congr = mul_equiv.refl (abelianization G) :=
mul_equiv.to_monoid_hom_injective abelianization.lift_of

@[simp]
lemma abelianization_congr_symm  :
  e.abelianization_congr.symm = e.symm.abelianization_congr := rfl

@[simp]
lemma abelianization_congr_trans {I : Type v} [group I] (e₂ : H ≃* I) :
  e.abelianization_congr.trans e₂.abelianization_congr = (e.trans e₂).abelianization_congr :=
mul_equiv.to_monoid_hom_injective (abelianization.hom_ext _ _ rfl)

end abelianization_congr

/-- An Abelian group is equivalent to its own abelianization. -/
@[simps] def abelianization.equiv_of_comm {H : Type*} [comm_group H] :
  H ≃* abelianization H :=
{ to_fun    := abelianization.of,
  inv_fun   := abelianization.lift (monoid_hom.id H),
  left_inv  := λ a, rfl,
  right_inv := by { rintros ⟨a⟩, refl, },
  .. abelianization.of }

section commutator_representatives

open subgroup

/-- Representatives `(g₁, g₂) : G × G` of commutator_set `⁅g₁, g₂⁆ ∈ G`. -/
def commutator_representatives : set (G × G) :=
set.range (λ g : commutator_set G, (g.2.some, g.2.some_spec.some))

instance [finite (commutator_set G)] : finite (commutator_representatives G) :=
set.finite_coe_iff.mpr (set.finite_range _)

/-- Subgroup generated by representatives `g₁ g₂ : G` of commutators `⁅g₁, g₂⁆ ∈ G`. -/
def closure_commutator_representatives : subgroup G :=
closure (prod.fst '' commutator_representatives G ∪ prod.snd '' commutator_representatives G)

instance closure_commutator_representatives_fg [finite (commutator_set G)] :
  group.fg (closure_commutator_representatives G) :=
group.closure_finite_fg _

lemma rank_closure_commutator_representations_le [finite (commutator_set G)] :
  group.rank (closure_commutator_representatives G) ≤ 2 * nat.card (commutator_set G) :=
begin
  rw two_mul,
  exact (subgroup.rank_closure_finite_le_nat_card _).trans ((set.card_union_le _ _).trans
    (add_le_add ((finite.card_image_le _).trans (finite.card_range_le _))
    ((finite.card_image_le _).trans (finite.card_range_le _ )))),
end

lemma image_commutator_set_closure_commutator_representatives :
  (closure_commutator_representatives G).subtype ''
    (commutator_set (closure_commutator_representatives G)) = commutator_set G :=
begin
  apply set.subset.antisymm,
  { rintros - ⟨-, ⟨g₁, g₂, rfl⟩, rfl⟩,
    exact ⟨g₁, g₂, rfl⟩ },
  { exact λ g hg, ⟨_,
      ⟨⟨_, subset_closure (or.inl ⟨_, ⟨⟨g, hg⟩, rfl⟩, rfl⟩)⟩,
       ⟨_, subset_closure (or.inr ⟨_, ⟨⟨g, hg⟩, rfl⟩, rfl⟩)⟩,
       rfl⟩,
      hg.some_spec.some_spec⟩ },
end

lemma card_commutator_set_closure_commutator_representatives :
  nat.card (commutator_set (closure_commutator_representatives G)) = nat.card (commutator_set G) :=
begin
  rw ← image_commutator_set_closure_commutator_representatives G,
  exact nat.card_congr (equiv.set.image _ _ (subtype_injective _)),
end

lemma card_commutator_closure_commutator_representatives :
  nat.card (commutator (closure_commutator_representatives G)) = nat.card (commutator G) :=
begin
  rw [commutator_eq_closure G, ←image_commutator_set_closure_commutator_representatives,
      ←monoid_hom.map_closure, ←commutator_eq_closure],
  exact nat.card_congr (equiv.set.image _ _ (subtype_injective _)),
end

instance [finite (commutator_set G)] :
  finite (commutator_set (closure_commutator_representatives G)) :=
begin
  apply nat.finite_of_card_ne_zero,
  rw card_commutator_set_closure_commutator_representatives,
  exact finite.card_pos.ne',
end

end commutator_representatives


/-
Copyright (c) 2019 Neil Strickland. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Neil Strickland
-/

import algebra.big_operators.multiset.basic
import data.pnat.prime
import data.nat.factors
import data.multiset.sort

/-!
# Prime factors of nonzero naturals

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

This file defines the factorization of a nonzero natural number `n` as a multiset of primes,
the multiplicity of `p` in this factors multiset being the p-adic valuation of `n`.

## Main declarations

* `prime_multiset`: Type of multisets of prime numbers.
* `factor_multiset n`: Multiset of prime factors of `n`.
-/

/-- The type of multisets of prime numbers.  Unique factorization
 gives an equivalence between this set and ℕ+, as we will formalize
 below. -/
@[derive [inhabited, canonically_ordered_add_monoid, distrib_lattice,
  semilattice_sup, order_bot, has_sub, has_ordered_sub]]
def prime_multiset := multiset nat.primes

namespace prime_multiset

-- `@[derive]` doesn't work for `meta` instances
meta instance : has_repr prime_multiset := by delta prime_multiset; apply_instance

/-- The multiset consisting of a single prime -/
def of_prime (p : nat.primes) : prime_multiset := ({p} : multiset nat.primes)

theorem card_of_prime (p : nat.primes) : multiset.card (of_prime p) = 1 := rfl

/-- We can forget the primality property and regard a multiset
 of primes as just a multiset of positive integers, or a multiset
 of natural numbers.  In the opposite direction, if we have a
 multiset of positive integers or natural numbers, together with
 a proof that all the elements are prime, then we can regard it
 as a multiset of primes.  The next block of results records
 obvious properties of these coercions.
-/
def to_nat_multiset : prime_multiset → multiset ℕ :=
λ v, v.map (λ p, (p : ℕ))

instance coe_nat : has_coe prime_multiset (multiset ℕ) := ⟨to_nat_multiset⟩

/-- `prime_multiset.coe`, the coercion from a multiset of primes to a multiset of
naturals, promoted to an `add_monoid_hom`. -/
def coe_nat_monoid_hom : prime_multiset →+ multiset ℕ :=
{ to_fun := coe,
  .. multiset.map_add_monoid_hom coe }

@[simp] lemma coe_coe_nat_monoid_hom :
  (coe_nat_monoid_hom : prime_multiset → multiset ℕ) = coe := rfl

theorem coe_nat_injective : function.injective (coe : prime_multiset → multiset ℕ) :=
multiset.map_injective nat.primes.coe_nat_injective

theorem coe_nat_of_prime (p : nat.primes) :
((of_prime p) : multiset ℕ) = {p} := rfl

theorem coe_nat_prime (v : prime_multiset)
(p : ℕ) (h : p ∈ (v : multiset ℕ)) : p.prime :=
by { rcases multiset.mem_map.mp h with ⟨⟨p', hp'⟩, ⟨h_mem, h_eq⟩⟩,
     exact h_eq ▸ hp' }

/-- Converts a `prime_multiset` to a `multiset ℕ+`. -/
def to_pnat_multiset : prime_multiset → multiset ℕ+ :=
λ v, v.map (λ p, (p : ℕ+))

instance coe_pnat : has_coe prime_multiset (multiset ℕ+) := ⟨to_pnat_multiset⟩

/-- `coe_pnat`, the coercion from a multiset of primes to a multiset of positive
naturals, regarded as an `add_monoid_hom`. -/
def coe_pnat_monoid_hom : prime_multiset →+ multiset ℕ+ :=
{ to_fun := coe,
  .. multiset.map_add_monoid_hom coe }

@[simp] lemma coe_coe_pnat_monoid_hom :
  (coe_pnat_monoid_hom : prime_multiset → multiset ℕ+) = coe := rfl

theorem coe_pnat_injective : function.injective (coe : prime_multiset → multiset ℕ+) :=
multiset.map_injective nat.primes.coe_pnat_injective

theorem coe_pnat_of_prime (p : nat.primes) :
  ((of_prime p) : multiset ℕ+) = {(p : ℕ+)} := rfl

theorem coe_pnat_prime (v : prime_multiset)
  (p : ℕ+) (h : p ∈ (v : multiset ℕ+)) : p.prime :=
by { rcases multiset.mem_map.mp h with ⟨⟨p', hp'⟩, ⟨h_mem, h_eq⟩⟩,
     exact h_eq ▸ hp' }

instance coe_multiset_pnat_nat : has_coe (multiset ℕ+) (multiset ℕ) :=
⟨λ v, v.map (λ n, (n : ℕ))⟩

theorem coe_pnat_nat (v : prime_multiset) :
  ((v : (multiset ℕ+)) : (multiset ℕ)) = (v : multiset ℕ) :=
by { change (v.map (coe : nat.primes → ℕ+)).map subtype.val = v.map subtype.val,
     rw [multiset.map_map], congr }

/-- The product of a `prime_multiset`, as a `ℕ+`. -/
def prod (v : prime_multiset) : ℕ+ := (v : multiset pnat).prod

theorem coe_prod (v : prime_multiset) : (v.prod : ℕ) = (v : multiset ℕ).prod :=
begin
  let h : (v.prod : ℕ) = ((v.map coe).map coe).prod :=
    (pnat.coe_monoid_hom.map_multiset_prod v.to_pnat_multiset),
  rw [multiset.map_map] at h,
  have : (coe : ℕ+ → ℕ) ∘ (coe : nat.primes → ℕ+) = coe := funext (λ p, rfl),
  rw[this] at h, exact h,
end

theorem prod_of_prime (p : nat.primes) : (of_prime p).prod = (p : ℕ+) :=
multiset.prod_singleton _

/-- If a `multiset ℕ` consists only of primes, it can be recast as a `prime_multiset`. -/
def of_nat_multiset
  (v : multiset ℕ) (h : ∀ (p : ℕ), p ∈ v → p.prime) : prime_multiset :=
@multiset.pmap ℕ nat.primes nat.prime (λ p hp, ⟨p, hp⟩) v h

theorem to_of_nat_multiset (v : multiset ℕ) (h) :
  ((of_nat_multiset v h) : multiset ℕ) = v :=
begin
  unfold_coes,
  dsimp [of_nat_multiset, to_nat_multiset],
  have : (λ (p : ℕ) (h : p.prime), ((⟨p, h⟩ : nat.primes) : ℕ)) = (λ p h, id p) :=
    by {funext p h, refl},
  rw [multiset.map_pmap, this, multiset.pmap_eq_map, multiset.map_id]
end

theorem prod_of_nat_multiset (v : multiset ℕ) (h) :
  ((of_nat_multiset v h).prod : ℕ) = (v.prod : ℕ) :=
by rw[coe_prod, to_of_nat_multiset]

/-- If a `multiset ℕ+` consists only of primes, it can be recast as a `prime_multiset`. -/
def of_pnat_multiset
  (v : multiset ℕ+) (h : ∀ (p : ℕ+), p ∈ v → p.prime) : prime_multiset :=
@multiset.pmap ℕ+ nat.primes pnat.prime (λ p hp, ⟨(p : ℕ), hp⟩) v h

theorem to_of_pnat_multiset (v : multiset ℕ+) (h) :
 ((of_pnat_multiset v h) : multiset ℕ+) = v :=
begin
  unfold_coes, dsimp[of_pnat_multiset, to_pnat_multiset],
  have : (λ (p : ℕ+) (h : p.prime), ((coe : nat.primes → ℕ+) ⟨p, h⟩)) = (λ p h, id p) :=
    by {funext p h, apply subtype.eq, refl},
  rw[multiset.map_pmap, this, multiset.pmap_eq_map, multiset.map_id]
end

theorem prod_of_pnat_multiset (v : multiset ℕ+) (h) :
 ((of_pnat_multiset v h).prod : ℕ+) = v.prod :=
by { dsimp [prod], rw [to_of_pnat_multiset] }

/-- Lists can be coerced to multisets; here we have some results
about how this interacts with our constructions on multisets. -/
def of_nat_list (l : list ℕ) (h : ∀ (p : ℕ), p ∈ l → p.prime) : prime_multiset :=
of_nat_multiset (l : multiset ℕ) h

theorem prod_of_nat_list (l : list ℕ) (h) : ((of_nat_list l h).prod : ℕ) = l.prod :=
by { have := prod_of_nat_multiset (l : multiset ℕ) h,
     rw [multiset.coe_prod] at this, exact this }

/-- If a `list ℕ+` consists only of primes, it can be recast as a `prime_multiset` with
the coercion from lists to multisets. -/
def of_pnat_list (l : list ℕ+) (h : ∀ (p : ℕ+), p ∈ l → p.prime) : prime_multiset :=
of_pnat_multiset (l : multiset ℕ+) h

theorem prod_of_pnat_list (l : list ℕ+) (h) : (of_pnat_list l h).prod = l.prod :=
by { have := prod_of_pnat_multiset (l : multiset ℕ+) h,
     rw [multiset.coe_prod] at this, exact this }

/-- The product map gives a homomorphism from the additive monoid
of multisets to the multiplicative monoid ℕ+. -/
theorem prod_zero : (0 : prime_multiset).prod = 1 :=
by { dsimp [prod], exact multiset.prod_zero }

theorem prod_add (u v : prime_multiset) : (u + v).prod = u.prod * v.prod :=
begin
  change (coe_pnat_monoid_hom (u + v)).prod = _,
  rw coe_pnat_monoid_hom.map_add,
  exact multiset.prod_add _ _,
end

end prime_multiset

namespace pnat

/-- The prime factors of n, regarded as a multiset -/
def factor_multiset (n : ℕ+) : prime_multiset :=
prime_multiset.of_nat_list (nat.factors n) (@nat.prime_of_mem_factors n)

/-- The product of the factors is the original number -/
theorem prod_factor_multiset (n : ℕ+) : (factor_multiset n).prod = n :=
eq $ by { dsimp [factor_multiset],
          rw [prime_multiset.prod_of_nat_list],
          exact nat.prod_factors n.ne_zero }

theorem coe_nat_factor_multiset (n : ℕ+) :
  ((factor_multiset n) : (multiset ℕ)) = ((nat.factors n) : multiset ℕ) :=
prime_multiset.to_of_nat_multiset (nat.factors n) (@nat.prime_of_mem_factors n)

end pnat

namespace prime_multiset

/-- If we start with a multiset of primes, take the product and
 then factor it, we get back the original multiset. -/
theorem factor_multiset_prod (v : prime_multiset) :
  v.prod.factor_multiset = v :=
begin
  apply prime_multiset.coe_nat_injective,
  rw [v.prod.coe_nat_factor_multiset, prime_multiset.coe_prod],
  rcases v with ⟨l⟩,
  unfold_coes,
  dsimp [prime_multiset.to_nat_multiset],
  rw [multiset.coe_prod],
  let l' := l.map (coe : nat.primes → ℕ),
  have : ∀ (p : ℕ), p ∈ l' → p.prime :=
    λ p hp, by {rcases list.mem_map.mp hp with ⟨⟨p', hp'⟩, ⟨h_mem, h_eq⟩⟩,
                exact h_eq ▸ hp'},
  exact multiset.coe_eq_coe.mpr (@nat.factors_unique _ l' rfl this).symm,
end

end prime_multiset

namespace pnat

/-- Positive integers biject with multisets of primes. -/
def factor_multiset_equiv : ℕ+ ≃ prime_multiset :=
{ to_fun    := factor_multiset,
  inv_fun   := prime_multiset.prod,
  left_inv  := prod_factor_multiset,
  right_inv := prime_multiset.factor_multiset_prod }

/-- Factoring gives a homomorphism from the multiplicative
 monoid ℕ+ to the additive monoid of multisets. -/
theorem factor_multiset_one : factor_multiset 1 = 0 :=
by simp [factor_multiset, prime_multiset.of_nat_list, prime_multiset.of_nat_multiset]

theorem factor_multiset_mul (n m : ℕ+) :
  factor_multiset (n * m) = (factor_multiset n) + (factor_multiset m) :=
begin
  let u := factor_multiset n,
  let v := factor_multiset m,
  have : n = u.prod := (prod_factor_multiset n).symm, rw[this],
  have : m = v.prod := (prod_factor_multiset m).symm, rw[this],
  rw[← prime_multiset.prod_add],
  repeat {rw[prime_multiset.factor_multiset_prod]},
end

theorem factor_multiset_pow (n : ℕ+) (m : ℕ) :
  factor_multiset (n ^ m) = m • (factor_multiset n) :=
begin
  let u := factor_multiset n,
  have : n = u.prod := (prod_factor_multiset n).symm,
  rw[this, ← prime_multiset.prod_smul],
  repeat {rw[prime_multiset.factor_multiset_prod]},
end

/-- Factoring a prime gives the corresponding one-element multiset. -/
theorem factor_multiset_of_prime (p : nat.primes) :
  (p : ℕ+).factor_multiset = prime_multiset.of_prime p :=
begin
  apply factor_multiset_equiv.symm.injective,
  change (p : ℕ+).factor_multiset.prod = (prime_multiset.of_prime p).prod,
  rw[(p : ℕ+).prod_factor_multiset, prime_multiset.prod_of_prime],
end

/-- We now have four different results that all encode the
 idea that inequality of multisets corresponds to divisibility
 of positive integers. -/
theorem factor_multiset_le_iff {m n : ℕ+} :
  factor_multiset m ≤ factor_multiset n ↔ m ∣ n :=
begin
  split,
  { intro h,
    rw [← prod_factor_multiset m, ← prod_factor_multiset m],
    apply dvd.intro (n.factor_multiset - m.factor_multiset).prod,
    rw [← prime_multiset.prod_add, prime_multiset.factor_multiset_prod,
        add_tsub_cancel_of_le h, prod_factor_multiset] },
  { intro  h,
    rw [← mul_div_exact h, factor_multiset_mul],
    exact le_self_add }
end

theorem factor_multiset_le_iff' {m : ℕ+} {v : prime_multiset}:
 factor_multiset m ≤ v ↔ m ∣ v.prod :=
by { let h := @factor_multiset_le_iff m v.prod,
     rw [v.factor_multiset_prod] at h, exact h }

end pnat

namespace prime_multiset

theorem prod_dvd_iff {u v : prime_multiset} : u.prod ∣ v.prod ↔ u ≤ v :=
by { let h := @pnat.factor_multiset_le_iff' u.prod v,
     rw [u.factor_multiset_prod] at h, exact h.symm }

theorem prod_dvd_iff' {u : prime_multiset} {n : ℕ+} : u.prod ∣ n ↔ u ≤ n.factor_multiset :=
by { let h := @prod_dvd_iff u n.factor_multiset,
     rw [n.prod_factor_multiset] at h, exact h }

end prime_multiset

namespace pnat

/-- The gcd and lcm operations on positive integers correspond
 to the inf and sup operations on multisets. -/
theorem factor_multiset_gcd (m n : ℕ+) :
 factor_multiset (gcd m n) = (factor_multiset m) ⊓ (factor_multiset n) :=
begin
  apply le_antisymm,
  { apply le_inf_iff.mpr; split; apply factor_multiset_le_iff.mpr,
    exact gcd_dvd_left m n, exact gcd_dvd_right m n},
  { rw[← prime_multiset.prod_dvd_iff, prod_factor_multiset],
    apply dvd_gcd; rw[prime_multiset.prod_dvd_iff'],
    exact inf_le_left, exact inf_le_right}
end

theorem factor_multiset_lcm (m n : ℕ+) :
 factor_multiset (lcm m n) = (factor_multiset m) ⊔ (factor_multiset n) :=
begin
  apply le_antisymm,
  { rw[← prime_multiset.prod_dvd_iff, prod_factor_multiset],
    apply lcm_dvd; rw[← factor_multiset_le_iff'],
    exact le_sup_left, exact le_sup_right},
  { apply sup_le_iff.mpr; split; apply factor_multiset_le_iff.mpr,
    exact dvd_lcm_left m n, exact dvd_lcm_right m n },
end

/-- The number of occurrences of p in the factor multiset of m
 is the same as the p-adic valuation of m. -/
theorem count_factor_multiset (m : ℕ+) (p : nat.primes) (k : ℕ) :
 (p : ℕ+) ^ k ∣ m ↔ k ≤ m.factor_multiset.count p :=
begin
  intros,
  rw [multiset.le_count_iff_replicate_le],
  rw [← factor_multiset_le_iff, factor_multiset_pow, factor_multiset_of_prime],
  congr' 2,
  apply multiset.eq_replicate.mpr,
  split,
  { rw [multiset.card_nsmul, prime_multiset.card_of_prime, mul_one] },
  { intros q h, rw [prime_multiset.of_prime, multiset.nsmul_singleton _ k] at h,
    exact multiset.eq_of_mem_replicate h }
end

end pnat

namespace prime_multiset

theorem prod_inf (u v : prime_multiset) :
 (u ⊓ v).prod = pnat.gcd u.prod v.prod :=
begin
  let n := u.prod,
  let m := v.prod,
  change (u ⊓ v).prod = pnat.gcd n m,
  have : u = n.factor_multiset := u.factor_multiset_prod.symm, rw [this],
  have : v = m.factor_multiset := v.factor_multiset_prod.symm, rw [this],
  rw [← pnat.factor_multiset_gcd n m, pnat.prod_factor_multiset]
end

theorem prod_sup (u v : prime_multiset) :
 (u ⊔ v).prod = pnat.lcm u.prod v.prod :=
begin
  let n := u.prod,
  let m := v.prod,
  change (u ⊔ v).prod = pnat.lcm n m,
  have : u = n.factor_multiset := u.factor_multiset_prod.symm, rw [this],
  have : v = m.factor_multiset := v.factor_multiset_prod.symm, rw [this],
  rw[← pnat.factor_multiset_lcm n m, pnat.prod_factor_multiset]
end

end prime_multiset


import Smt

def Nat.max' (x y : Nat) : Nat := if x ≤ y then y else x

theorem Nat.not_le_of_reverse_le {m n : Nat} : ¬ m ≤ n → n ≤ m := fun hn =>
  match Nat.le_total m n with
  | Or.inl h => absurd h hn
  | Or.inr h => h

theorem Nat.max'_ge : ∀ x y : Nat, x ≤ max' x y ∧ y ≤ max' x y := by
  intro x y
  smt
  by_cases h : x ≤ y <;> simp [max', h]
  apply not_le_of_reverse_le h

theorem Nat.max'_ge' : ∀ x y : Nat, x ≤ max' x y ∧ y ≤ max' x y := by
  intro x y
  smt [max']
  by_cases h : x ≤ y <;> simp [max', h]
  apply not_le_of_reverse_le h


/-
Copyright (c) 2023 María Inés de Frutos-Fernández. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: María Inés de Frutos-Fernández
-/

import order.liminf_limsup
import topology.instances.nnreal

/-!
# Limsup

We prove some auxiliary results about limsups, infis, and suprs.

## Main Results

* `ennreal.le_infi_mul_infi` : if `f g : ι → ennreal` take real values, and
  `∀ (i j : ι), a ≤ f i * g j`, then `a ≤ infi f * infi g`.
* `ennreal.infi_mul_le_mul_infi` : if `u v : ι → ennreal` take real values and are antitone, then
  `infi (u * v) ≤ infi u * infi v`.
* `ennreal.limsup_mul_le` : if `u v : ℕ → ℝ≥0∞` are bounded above by real numbers, then
  `filter.limsup (u * v) at_top ≤ filter.limsup u at_top * filter.limsup v at_top`.
* `real.limsup_mul_le` : If `u v : ℕ → ℝ` are nonnegative and bounded above, then
  `filter.limsup (u * v) at_top ≤ filter.limsup u at_top * filter.limsup v at_top `.

## Tags

limsup, real, nnreal, ennreal
-/

noncomputable theory

namespace filter

/-- If `u : β → α` is nonnegative and `is_bounded_under has_le.le f u`, then `0 ≤ limsup u f`. -/
lemma limsup_nonneg_of_nonneg {α β : Type*} [has_zero α] [conditionally_complete_linear_order α]
  {f : filter β} [hf_ne_bot : f.ne_bot] {u : β → α}
  (hfu : is_bounded_under has_le.le f u) (h : 0 ≤ u) : 0 ≤ limsup u f :=
le_limsup_of_frequently_le (frequently_of_forall h) hfu

/-- If `filter.limsup u at_top ≤ x`, then for all `ε > 0`, eventually we have `u a < x + ε`.  -/
lemma eventually_lt_add_pos_of_limsup_le {α : Type*} [preorder α] {x : ℝ} {u : α → ℝ}
  (hu_bdd : is_bounded_under has_le.le at_top u) (hu : filter.limsup u at_top ≤ x)
  {ε : ℝ} (hε : 0 < ε) : ∀ᶠ (a : α) in at_top, u a < x + ε :=
eventually_lt_of_limsup_lt (lt_of_le_of_lt hu (lt_add_of_pos_right x hε)) hu_bdd

/-- If `filter.limsup u at_top ≤ x`, then for all `ε > 0`, there exists a positive natural
  number `n` such that `u n < x + ε`.  -/
lemma exists_lt_of_limsup_le {x : ℝ} {u : ℕ → ℝ} (hu_bdd : is_bounded_under has_le.le at_top u)
  (hu : filter.limsup u at_top ≤ x) {ε : ℝ} (hε : 0 < ε) :
  ∃ n : pnat, u n < x + ε :=
begin
  have h : ∀ᶠ (a : ℕ) in at_top, u a < x + ε := eventually_lt_add_pos_of_limsup_le hu_bdd hu hε,
  simp only [eventually_at_top, ge_iff_le] at h,
  obtain ⟨n, hn⟩ := h,
  exact ⟨⟨n + 1, nat.succ_pos _⟩, hn (n + 1) (nat.le_succ _)⟩,
end

end filter

open filter
open_locale topological_space nnreal ennreal

lemma bdd_above.is_bounded_under {α : Type*} [preorder α] {u : α → ℝ}
  (hu_bdd : bdd_above (set.range u)) : is_bounded_under has_le.le at_top u :=
begin
  obtain ⟨b, hb⟩ := hu_bdd,
  use b,
  simp only [mem_upper_bounds, set.mem_range, forall_exists_index,
    forall_apply_eq_imp_iff'] at hb,
  exact eventually_map.mpr (eventually_of_forall hb)
end

namespace nnreal

lemma coe_limsup {u : ℕ → ℝ} (hu : 0 ≤ u) :
  limsup u at_top = (((limsup (λ n, (⟨u n, hu n⟩ : ℝ≥0)) at_top) : ℝ≥0) : ℝ) :=
begin
  simp only [limsup_eq],
  norm_cast,
  apply congr_arg,
  ext x,
  simp only [set.mem_set_of_eq, set.mem_image],
  refine ⟨λ hx, _, λ hx, _⟩,
  { have hx' := hx,
    simp only [eventually_at_top, ge_iff_le] at hx',
    obtain ⟨N, hN⟩ := hx',
    have hx0 : 0 ≤ x := le_trans (hu N) (hN N (le_refl _)),
    exact ⟨⟨x, hx0⟩, hx, rfl⟩, },
  { obtain ⟨y, hy, hyx⟩ := hx,
    simp_rw [← nnreal.coe_le_coe, nnreal.coe_mk, hyx] at hy,
    exact hy }
end

/-- If `u : ℕ → ℝ` is bounded above an nonnegative, it is also bounded above when regarded as
  a function to `ℝ≥0`. -/
lemma bdd_above' {u : ℕ → ℝ} (hu0 : 0 ≤ u) (hu_bdd: bdd_above (set.range u)) :
  bdd_above (set.range (λ (n : ℕ), (⟨u n, hu0 n⟩ : ℝ≥0))) :=
begin
  obtain ⟨B, hB⟩ := hu_bdd,
  simp only [mem_upper_bounds, set.mem_range, forall_exists_index, forall_apply_eq_imp_iff'] at hB,
  have hB0 : 0 ≤ B := le_trans (hu0 0) (hB 0),
  use (⟨B, hB0⟩ : ℝ≥0),
  simp only [mem_upper_bounds, set.mem_range, forall_exists_index, subtype.forall,
    subtype.mk_le_mk],
  rintros x - n hn,
  rw ← hn,
  exact hB n,
end

lemma eventually_le_of_bdd_above' {u : ℕ → ℝ≥0} (hu : bdd_above (set.range u)) :
  {a : ℝ≥0 | ∀ᶠ (n : ℕ) in at_top, u n ≤ a}.nonempty :=
begin
  obtain ⟨B, hB⟩ := hu,
  simp only [mem_upper_bounds, set.mem_range, forall_exists_index, forall_apply_eq_imp_iff']
    at hB,
  exact ⟨B, eventually_of_forall hB⟩,
end

end nnreal

namespace ennreal

/-- If `f g : ι → ℝ≥0∞` take real values, and `∀ (i j : ι), a ≤ f i * g j`, then
  `a ≤ infi f * infi g`. -/
lemma le_infi_mul_infi {ι : Sort*} [hι : nonempty ι] {a : ℝ≥0∞} {f g : ι → ℝ≥0∞}
  (hf : ∀ x, f x ≠ ⊤) (hg : ∀ x, g x ≠ ⊤) (H : ∀ (i j : ι), a ≤ f i * g j) :
  a ≤ infi f * infi g :=
begin
  have hg' : infi g ≠ ⊤,
  { rw [ne.def, infi_eq_top, not_forall], exact ⟨hι.some, hg hι.some⟩ },
  rw infi_mul hg',
  refine le_infi _,
  intros i,
  rw mul_infi (hf i),
  exact le_infi (H i),
  { apply_instance },
  { apply_instance },
end

/-- If `u v : ι → ℝ≥0∞` take real values and are antitone, then `infi (u * v) ≤ infi u * infi v`. -/
lemma infi_mul_le_mul_infi {u v : ℕ → ℝ≥0∞} (hu_top : ∀ x, u x ≠ ⊤) (hu : antitone u)
  (hv_top : ∀ x, v x ≠ ⊤) (hv : antitone v) : infi (u * v) ≤ infi u * infi v :=
begin
  rw infi_le_iff,
  intros b hb,
  apply le_infi_mul_infi hu_top hv_top,
  intros m n,
  exact le_trans (hb (max m n)) (mul_le_mul (hu (le_max_left _ _)) (hv (le_max_right _ _))),
end

lemma supr_tail_seq (u : ℕ → ℝ≥0∞) (n : ℕ) :
  (⨆ (k : ℕ) (x : n ≤ k), u k) = ⨆ (k : { k : ℕ // n ≤ k}), u k :=
by rw supr_subtype; refl

lemma le_supr_prop (u : ℕ → ℝ≥0∞) {n k : ℕ} (hnk : n ≤ k) :
  u k ≤ ⨆ (k : ℕ) (x : n ≤ k), u k :=
begin
  refine le_supr_of_le k _,
  rw csupr_pos hnk,
  exact le_refl _,
end

/-- The function sending `n : ℕ` to `⨆ (k : ℕ) (x : n ≤ k), u k` is antitone. -/
lemma antitone.supr {u : ℕ → ℝ≥0∞} :
  antitone (λ (n : ℕ), ⨆ (k : ℕ) (x : n ≤ k), u k) :=
begin
  apply antitone_nat_of_succ_le _,
  intros n,
  rw [supr₂_le_iff],
  intros k hk,
  exact le_supr_prop u (le_trans (nat.le_succ n) hk),
end

/-- If `u : ℕ → ℝ≥0∞` is bounded above by a real number, then its `supr` is finite. -/
lemma supr_le_top_of_bdd_above {u : ℕ → ℝ≥0∞} {B : ℝ≥0} (hu : ∀ x, u x ≤ B) (n : ℕ):
  (⨆ (k : ℕ) (x : n ≤ k), u k) ≠ ⊤ :=
begin
  have h_le : (⨆ (k : ℕ) (x : n ≤ k), u k) ≤ B,
  { rw supr_tail_seq,
    exact supr_le (λ m, hu m), },
  exact ne_top_of_le_ne_top coe_ne_top h_le
end

/-- If `u v : ℕ → ℝ≥0∞` are bounded above by real numbers, then
  `filter.limsup (u * v) at_top ≤ filter.limsup u at_top * filter.limsup v at_top`. -/
lemma limsup_mul_le {u v : ℕ → ℝ≥0∞} {Bu Bv : ℝ≥0} (hu : ∀ x, u x ≤ Bu) (hv : ∀ x, v x ≤ Bv) :
  filter.limsup (u * v) at_top ≤ filter.limsup u at_top * filter.limsup v at_top :=
begin
  have h_le : (⨅ (n : ℕ), ⨆ (i : ℕ) (x : n ≤ i), u i * v i) ≤
    (⨅ (n : ℕ), (⨆ (i : ℕ) (x : n ≤ i), u i) *(⨆ (j : ℕ) (x : n ≤ j), v j)),
  { refine infi_mono _,
    intros n,
    apply supr_le _,
    intros k,
    apply supr_le _,
    intros hk,
    exact mul_le_mul (le_supr_prop u hk) (le_supr_prop v hk), },
  simp only [filter.limsup_eq_infi_supr_of_nat, ge_iff_le, pi.mul_apply],
  exact le_trans h_le (infi_mul_le_mul_infi (supr_le_top_of_bdd_above hu) antitone.supr
    (supr_le_top_of_bdd_above hv) antitone.supr),
end

lemma coe_limsup {u : ℕ → ℝ≥0} (hu : bdd_above (set.range u)) :
  (((limsup u at_top) : ℝ≥0) : ℝ≥0∞) = limsup (λ n, (u n : ℝ≥0∞)) at_top :=
begin
  simp only [limsup_eq],
  rw [coe_Inf (nnreal.eventually_le_of_bdd_above' hu), Inf_eq_infi],
  simp only [eventually_at_top, ge_iff_le, set.mem_set_of_eq, infi_exists],
  { apply le_antisymm,
    { apply le_infi₂ _,
      intros x n,
      apply le_infi _,
      intro h,
      cases x,
      { simp only [none_eq_top, le_top], },
      { simp only [some_eq_coe, coe_le_coe] at h,
        exact infi₂_le_of_le x n (infi_le_of_le h (le_refl _)) }},
    { apply le_infi₂ _,
      intros x n,
      apply le_infi _,
      intro h,
      refine infi₂_le_of_le x n _,
      simp_rw coe_le_coe,
      exact infi_le_of_le h (le_refl _) }},
end

lemma coe_limsup' {u : ℕ → ℝ} (hu : bdd_above (set.range u)) (hu0 : 0 ≤ u) :
  (limsup (λ n, ((coe : ℝ≥0 → ℝ≥0∞) (⟨u n, hu0 n⟩ : ℝ≥0))) at_top) =
  (coe : ℝ≥0 → ℝ≥0∞) (⟨limsup u at_top, limsup_nonneg_of_nonneg hu.is_bounded_under hu0⟩ : ℝ≥0) :=
by rw [← ennreal.coe_limsup (nnreal.bdd_above' hu0 hu), ennreal.coe_eq_coe, ← nnreal.coe_eq,
  subtype.coe_mk, nnreal.coe_limsup]

end ennreal

namespace real

/-- If `u v : ℕ → ℝ` are nonnegative and bounded above, then `u * v` is bounded above. -/
lemma range_bdd_above_mul {u v : ℕ → ℝ} (hu : bdd_above (set.range u)) (hu0 : 0 ≤ u)
   (hv : bdd_above (set.range v)) (hv0 : 0 ≤ v) :  bdd_above (set.range (u * v)) :=
begin
  obtain ⟨bu, hbu⟩ := hu,
  obtain ⟨bv, hbv⟩ := hv,
  use bu*bv,
  simp only [mem_upper_bounds, set.mem_range, pi.mul_apply, forall_exists_index,
    forall_apply_eq_imp_iff'] at hbu hbv ⊢,
  intros n,
  exact mul_le_mul (hbu n) (hbv n) (hv0 n) (le_trans (hu0 n) (hbu n)),
end

/-- If `u v : ℕ → ℝ` are nonnegative and bounded above, then
  `filter.limsup (u * v) at_top ≤ filter.limsup u at_top * filter.limsup v at_top `.-/
lemma limsup_mul_le {u v : ℕ → ℝ} (hu_bdd : bdd_above (set.range u)) (hu0 : 0 ≤ u)
  (hv_bdd : bdd_above (set.range v)) (hv0 : 0 ≤ v) :
  filter.limsup (u * v) at_top ≤ filter.limsup u at_top * filter.limsup v at_top :=
begin
  have h_bdd : bdd_above (set.range (u * v)),
  { exact range_bdd_above_mul hu_bdd hu0 hv_bdd hv0 },
  have hc : ∀ n : ℕ, (⟨u n * v n, (mul_nonneg (hu0 n) (hv0 n))⟩ : ℝ≥0) = ⟨u n, hu0 n⟩*⟨v n, hv0 n⟩,
  { intro n, simp only [nonneg.mk_mul_mk], },
  rw [← nnreal.coe_mk _ (limsup_nonneg_of_nonneg h_bdd.is_bounded_under (mul_nonneg hu0 hv0)),
    ← nnreal.coe_mk _ (limsup_nonneg_of_nonneg hu_bdd.is_bounded_under hu0),
    ← nnreal.coe_mk _ (limsup_nonneg_of_nonneg hv_bdd.is_bounded_under hv0),
    ← nnreal.coe_mul, nnreal.coe_le_coe, ← ennreal.coe_le_coe, ennreal.coe_mul],
  simp only [← ennreal.coe_limsup', pi.mul_apply, hc, ennreal.coe_mul],
  obtain ⟨Bu, hBu⟩ := hu_bdd,
  obtain ⟨Bv, hBv⟩ := hv_bdd,
  simp only [mem_upper_bounds, set.mem_range, forall_exists_index, forall_apply_eq_imp_iff']
    at hBu hBv,
  have hBu_0 : 0 ≤ Bu := le_trans (hu0 0) (hBu 0),
  have hBu' : ∀ (n : ℕ), (⟨u n, hu0 n⟩ : ℝ≥0)  ≤ (⟨Bu, hBu_0⟩ : ℝ≥0),
  { simp only [← nnreal.coe_le_coe, nnreal.coe_mk], exact hBu },
  have hBv_0 : 0 ≤ Bv := le_trans (hv0 0) (hBv 0),
  have hBv' : ∀ (n : ℕ), (⟨v n, hv0 n⟩ : ℝ≥0) ≤ (⟨Bv, hBv_0⟩ : ℝ≥0),
  { simp only [← nnreal.coe_le_coe, nnreal.coe_mk], exact hBv },
  simp_rw ← ennreal.coe_le_coe at hBu' hBv',
  exact ennreal.limsup_mul_le hBu' hBv',
end

-- Alternative proof of limsup_mul_le
lemma limsup_mul_le' {u v : ℕ → ℝ} (hu_bdd : bdd_above (set.range u)) (hu0 : 0 ≤ u)
  (hv_bdd : bdd_above (set.range v)) (hv0 : 0 ≤ v) :
  filter.limsup (u * v) at_top ≤ filter.limsup u at_top * filter.limsup v at_top :=
begin
  have h_bdd : bdd_above (set.range (u * v)),
  { exact range_bdd_above_mul hu_bdd hu0 hv_bdd hv0 },
  have hc : ∀ n : ℕ, (⟨u n * v n, (mul_nonneg (hu0 n) (hv0 n))⟩ : ℝ≥0) = ⟨u n, hu0 n⟩*⟨v n, hv0 n⟩,
  { intro n, simp only [nonneg.mk_mul_mk], },
  rw [nnreal.coe_limsup (mul_nonneg hu0 hv0), nnreal.coe_limsup  hu0, nnreal.coe_limsup hv0,
    ← nnreal.coe_mul, nnreal.coe_le_coe, ← ennreal.coe_le_coe, ennreal.coe_mul,
    ennreal.coe_limsup (nnreal.bdd_above' _ h_bdd),
    ennreal.coe_limsup (nnreal.bdd_above' hu0 hu_bdd),
    ennreal.coe_limsup (nnreal.bdd_above' hv0 hv_bdd)],

  simp only [pi.mul_apply, hc, ennreal.coe_mul],
  obtain ⟨Bu, hBu⟩ := hu_bdd,
  obtain ⟨Bv, hBv⟩ := hv_bdd,
  simp only [mem_upper_bounds, set.mem_range, forall_exists_index, forall_apply_eq_imp_iff']
    at hBu hBv,
  have hBu_0 : 0 ≤ Bu := le_trans (hu0 0) (hBu 0),
  have hBu' : ∀ (n : ℕ), (⟨u n, hu0 n⟩ : ℝ≥0)  ≤ (⟨Bu, hBu_0⟩ : ℝ≥0),
  { simp only [← nnreal.coe_le_coe, nnreal.coe_mk], exact hBu },
  have hBv_0 : 0 ≤ Bv := le_trans (hv0 0) (hBv 0),
  have hBv' : ∀ (n : ℕ), (⟨v n, hv0 n⟩ : ℝ≥0) ≤ (⟨Bv, hBv_0⟩ : ℝ≥0),
  { simp only [← nnreal.coe_le_coe, nnreal.coe_mk], exact hBv },

  simp_rw ← ennreal.coe_le_coe at hBu' hBv',
  exact ennreal.limsup_mul_le hBu' hBv',
end

end real