/-
Copyright (c) 2018 Johan Commelin All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Chris Hughes, Kevin Buzzard
-/
import algebra.hom.group
import algebra.group.units
/-!
# Monoid homomorphisms and units

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

This file allows to lift monoid homomorphisms to group homomorphisms of their units subgroups. It
also contains unrelated results about `units` that depend on `monoid_hom`.

## Main declarations

* `units.map`: Turn an homomorphism from `α` to `β` monoids into an homomorphism from `αˣ` to `βˣ`.
* `monoid_hom.to_hom_units`: Turn an homomorphism from a group `α` to `β` into an homomorphism from
  `α` to `βˣ`.

## TODO

The results that don't mention homomorphisms should be proved (earlier?) in a different file and be
used to golf the basic `group` lemmas.
-/

open function

universes u v w

@[to_additive] lemma group.is_unit {G} [group G] (g : G) : is_unit g :=
⟨⟨g, g⁻¹, mul_inv_self g, inv_mul_self g⟩, rfl⟩

section monoid_hom_class

/-- If two homomorphisms from a division monoid to a monoid are equal at a unit `x`, then they are
equal at `x⁻¹`. -/
@[to_additive "If two homomorphisms from a subtraction monoid to an additive monoid are equal at an
additive unit `x`, then they are equal at `-x`."]
lemma is_unit.eq_on_inv {F G N} [division_monoid G] [monoid N] [monoid_hom_class F G N] {x : G}
  (hx : is_unit x) (f g : F) (h : f x = g x) : f x⁻¹ = g x⁻¹ :=
left_inv_eq_right_inv (map_mul_eq_one f hx.inv_mul_cancel) $
  h.symm ▸ map_mul_eq_one g $ hx.mul_inv_cancel

/-- If two homomorphism from a group to a monoid are equal at `x`, then they are equal at `x⁻¹`. -/
@[to_additive "If two homomorphism from an additive group to an additive monoid are equal at `x`,
then they are equal at `-x`." ]
lemma eq_on_inv {F G M} [group G] [monoid M] [monoid_hom_class F G M] (f g : F) {x : G}
  (h : f x = g x) : f x⁻¹ = g x⁻¹ :=
(group.is_unit x).eq_on_inv f g h

end monoid_hom_class

namespace units
variables {α : Type*} {M : Type u} {N : Type v} {P : Type w} [monoid M] [monoid N] [monoid P]

/-- The group homomorphism on units induced by a `monoid_hom`. -/
@[to_additive "The `add_group` homomorphism on `add_unit`s induced by an `add_monoid_hom`."]
def map (f : M →* N) : Mˣ →* Nˣ :=
monoid_hom.mk'
  (λ u, ⟨f u.val, f u.inv,
                  by rw [← f.map_mul, u.val_inv, f.map_one],
                  by rw [← f.map_mul, u.inv_val, f.map_one]⟩)
  (λ x y, ext (f.map_mul x y))

@[simp, to_additive] lemma coe_map (f : M →* N) (x : Mˣ) : ↑(map f x) = f x := rfl

@[simp, to_additive] lemma coe_map_inv (f : M →* N) (u : Mˣ) :
  ↑(map f u)⁻¹ = f ↑u⁻¹ :=
rfl

@[simp, to_additive]
lemma map_comp (f : M →* N) (g : N →* P) : map (g.comp f) = (map g).comp (map f) := rfl

variables (M)
@[simp, to_additive] lemma map_id : map (monoid_hom.id M) = monoid_hom.id Mˣ :=
by ext; refl

/-- Coercion `Mˣ → M` as a monoid homomorphism. -/
@[to_additive "Coercion `add_units M → M` as an add_monoid homomorphism."]
def coe_hom : Mˣ →* M := ⟨coe, coe_one, coe_mul⟩

variable {M}

@[simp, to_additive] lemma coe_hom_apply (x : Mˣ) : coe_hom M x = ↑x := rfl

@[simp, norm_cast, to_additive]
lemma coe_pow (u : Mˣ) (n : ℕ) : ((u ^ n : Mˣ) : M) = u ^ n :=
(units.coe_hom M).map_pow u n

section division_monoid
variables [division_monoid α]

@[simp, norm_cast, to_additive] lemma coe_div : ∀ u₁ u₂ : αˣ, ↑(u₁ / u₂) = (u₁ / u₂ : α) :=
(units.coe_hom α).map_div

@[simp, norm_cast, to_additive] lemma coe_zpow : ∀ (u : αˣ) (n : ℤ), ((u ^ n : αˣ) : α) = u ^ n :=
(units.coe_hom α).map_zpow

@[field_simps] lemma _root_.divp_eq_div (a : α) (u : αˣ) : a /ₚ u = a / u :=
by rw [div_eq_mul_inv, divp, u.coe_inv]

@[simp, to_additive]
lemma _root_.map_units_inv {F : Type*} [monoid_hom_class F M α] (f : F) (u : units M) :
  f ↑u⁻¹ = (f u)⁻¹ :=
((f : M →* α).comp (units.coe_hom M)).map_inv u

end division_monoid

/-- If a map `g : M → Nˣ` agrees with a homomorphism `f : M →* N`, then
this map is a monoid homomorphism too. -/
@[to_additive "If a map `g : M → add_units N` agrees with a homomorphism `f : M →+ N`, then this map
is an add_monoid homomorphism too."]
def lift_right (f : M →* N) (g : M → Nˣ) (h : ∀ x, ↑(g x) = f x) :
  M →* Nˣ :=
{ to_fun := g,
  map_one' := units.ext $ (h 1).symm ▸ f.map_one,
  map_mul' := λ x y, units.ext $ by simp only [h, coe_mul, f.map_mul] }

@[simp, to_additive] lemma coe_lift_right {f : M →* N} {g : M → Nˣ}
  (h : ∀ x, ↑(g x) = f x) (x) : (lift_right f g h x : N) = f x := h x

@[simp, to_additive] lemma mul_lift_right_inv {f : M →* N} {g : M → Nˣ}
  (h : ∀ x, ↑(g x) = f x) (x) : f x * ↑(lift_right f g h x)⁻¹ = 1 :=
by rw [units.mul_inv_eq_iff_eq_mul, one_mul, coe_lift_right]

@[simp, to_additive] lemma lift_right_inv_mul {f : M →* N} {g : M → Nˣ}
  (h : ∀ x, ↑(g x) = f x) (x) : ↑(lift_right f g h x)⁻¹ * f x = 1 :=
by rw [units.inv_mul_eq_iff_eq_mul, mul_one, coe_lift_right]

end units

namespace monoid_hom

/-- If `f` is a homomorphism from a group `G` to a monoid `M`,
then its image lies in the units of `M`,
and `f.to_hom_units` is the corresponding monoid homomorphism from `G` to `Mˣ`. -/
@[to_additive "If `f` is a homomorphism from an additive group `G` to an additive monoid `M`,
then its image lies in the `add_units` of `M`,
and `f.to_hom_units` is the corresponding homomorphism from `G` to `add_units M`."]
def to_hom_units {G M : Type*} [group G] [monoid M] (f : G →* M) : G →* Mˣ :=
units.lift_right f
  (λ g, ⟨f g, f g⁻¹, map_mul_eq_one f (mul_inv_self _), map_mul_eq_one f (inv_mul_self _)⟩)
  (λ g, rfl)

@[simp, to_additive]
lemma coe_to_hom_units {G M : Type*} [group G] [monoid M] (f : G →* M) (g : G) :
  (f.to_hom_units g : M) = f g :=
rfl

end monoid_hom

namespace is_unit
variables {F G α M N : Type*}

section monoid
variables [monoid M] [monoid N]

@[to_additive] lemma map [monoid_hom_class F M N] (f : F) {x : M} (h : is_unit x) : is_unit (f x) :=
by rcases h with ⟨y, rfl⟩; exact (units.map (f : M →* N) y).is_unit

@[to_additive] lemma of_left_inverse [monoid_hom_class F M N] [monoid_hom_class G N M]
  {f : F} {x : M} (g : G) (hfg : function.left_inverse g f) (h : is_unit (f x)) :
  is_unit x :=
by simpa only [hfg x] using h.map g

@[to_additive] lemma _root_.is_unit_map_of_left_inverse
  [monoid_hom_class F M N] [monoid_hom_class G N M]
  {f : F} {x : M} (g : G) (hfg : function.left_inverse g f) :
  is_unit (f x) ↔ is_unit x :=
⟨of_left_inverse g hfg, map _⟩

/-- If a homomorphism `f : M →* N` sends each element to an `is_unit`, then it can be lifted
to `f : M →* Nˣ`. See also `units.lift_right` for a computable version. -/
@[to_additive "If a homomorphism `f : M →+ N` sends each element to an `is_add_unit`, then it can be
lifted to `f : M →+ add_units N`. See also `add_units.lift_right` for a computable version."]
noncomputable def lift_right (f : M →* N) (hf : ∀ x, is_unit (f x)) : M →* Nˣ :=
units.lift_right f (λ x, (hf x).unit) $ λ x, rfl

@[to_additive] lemma coe_lift_right (f : M →* N) (hf : ∀ x, is_unit (f x)) (x) :
  (is_unit.lift_right f hf x : N) = f x := rfl

@[simp, to_additive] lemma mul_lift_right_inv (f : M →* N) (h : ∀ x, is_unit (f x)) (x) :
  f x * ↑(is_unit.lift_right f h x)⁻¹ = 1 :=
units.mul_lift_right_inv (λ y, rfl) x

@[simp, to_additive] lemma lift_right_inv_mul (f : M →* N) (h : ∀ x, is_unit (f x)) (x) :
  ↑(is_unit.lift_right f h x)⁻¹ * f x = 1 :=
units.lift_right_inv_mul (λ y, rfl) x

end monoid

section division_monoid
variables [division_monoid α] {a b c : α}

/-- The element of the group of units, corresponding to an element of a monoid which is a unit. As
opposed to `is_unit.unit`, the inverse is computable and comes from the inversion on `α`. This is
useful to transfer properties of inversion in `units α` to `α`. See also `to_units`. -/
@[to_additive "The element of the additive group of additive units, corresponding to an element of
an additive monoid which is an additive unit. As opposed to `is_add_unit.add_unit`, the negation is
computable and comes from the negation on `α`. This is useful to transfer properties of negation in
`add_units α` to `α`. See also `to_add_units`.", simps]
def unit' (h : is_unit a) : αˣ := ⟨a, a⁻¹, h.mul_inv_cancel, h.inv_mul_cancel⟩

@[simp, to_additive] protected lemma mul_inv_cancel_left (h : is_unit a) : ∀ b, a * (a⁻¹ * b) = b :=
h.unit'.mul_inv_cancel_left

@[simp, to_additive] protected lemma inv_mul_cancel_left (h : is_unit a) : ∀ b, a⁻¹ * (a * b) = b :=
h.unit'.inv_mul_cancel_left

@[simp, to_additive] protected lemma mul_inv_cancel_right (h : is_unit b) (a : α) :
  a * b * b⁻¹ = a :=
h.unit'.mul_inv_cancel_right _

@[simp, to_additive] protected lemma inv_mul_cancel_right (h : is_unit b) (a : α) :
  a * b⁻¹ * b = a :=
h.unit'.inv_mul_cancel_right _

@[to_additive] protected lemma div_self (h : is_unit a) : a / a = 1 :=
by rw [div_eq_mul_inv, h.mul_inv_cancel]

@[to_additive] protected lemma eq_mul_inv_iff_mul_eq (h : is_unit c) : a = b * c⁻¹ ↔ a * c = b :=
h.unit'.eq_mul_inv_iff_mul_eq

@[to_additive] protected lemma eq_inv_mul_iff_mul_eq (h : is_unit b) : a = b⁻¹ * c ↔ b * a = c :=
h.unit'.eq_inv_mul_iff_mul_eq

@[to_additive] protected lemma inv_mul_eq_iff_eq_mul (h : is_unit a) : a⁻¹ * b = c ↔ b = a * c :=
h.unit'.inv_mul_eq_iff_eq_mul

@[to_additive] protected lemma mul_inv_eq_iff_eq_mul (h : is_unit b) : a * b⁻¹ = c ↔ a = c * b :=
h.unit'.mul_inv_eq_iff_eq_mul

@[to_additive] protected lemma mul_inv_eq_one (h : is_unit b) : a * b⁻¹ = 1 ↔ a = b :=
@units.mul_inv_eq_one _ _ h.unit' _

@[to_additive] protected lemma inv_mul_eq_one (h : is_unit a) : a⁻¹ * b = 1 ↔ a = b :=
@units.inv_mul_eq_one _ _ h.unit' _

@[to_additive] protected lemma mul_eq_one_iff_eq_inv (h : is_unit b) : a * b = 1 ↔ a = b⁻¹ :=
@units.mul_eq_one_iff_eq_inv _ _ h.unit' _

@[to_additive] protected lemma mul_eq_one_iff_inv_eq (h : is_unit a) : a * b = 1 ↔ a⁻¹ = b :=
@units.mul_eq_one_iff_inv_eq _ _ h.unit' _

@[simp, to_additive] protected lemma div_mul_cancel (h : is_unit b) (a : α) : a / b * b = a :=
by rw [div_eq_mul_inv, h.inv_mul_cancel_right]

@[simp, to_additive] protected lemma mul_div_cancel (h : is_unit b) (a : α) : a * b / b = a :=
by rw [div_eq_mul_inv, h.mul_inv_cancel_right]

@[to_additive] protected lemma mul_one_div_cancel (h : is_unit a) : a * (1 / a) = 1 := by simp [h]
@[to_additive] protected lemma one_div_mul_cancel (h : is_unit a) : (1 / a) * a = 1 := by simp [h]

@[to_additive] lemma inv : is_unit a → is_unit a⁻¹ :=
by { rintro ⟨u, rfl⟩, rw ←units.coe_inv, exact units.is_unit _ }

@[to_additive] lemma div (ha : is_unit a) (hb : is_unit b) : is_unit (a / b) :=
by { rw div_eq_mul_inv, exact ha.mul hb.inv }

@[to_additive] protected lemma div_left_inj (h : is_unit c) : a / c = b / c ↔ a = b :=
by { simp_rw div_eq_mul_inv, exact units.mul_left_inj h.inv.unit' }

@[to_additive] protected lemma div_eq_iff (h : is_unit b) : a / b = c ↔ a = c * b :=
by rw [div_eq_mul_inv, h.mul_inv_eq_iff_eq_mul]

@[to_additive] protected lemma eq_div_iff (h : is_unit c) : a = b / c ↔ a * c = b :=
by rw [div_eq_mul_inv, h.eq_mul_inv_iff_mul_eq]

@[to_additive] protected lemma div_eq_of_eq_mul (h : is_unit b) : a = c * b → a / b = c :=
h.div_eq_iff.2

@[to_additive] protected

@[to_additive] protected lemma div_eq_one_iff_eq (h : is_unit b) : a / b = 1 ↔ a = b :=
⟨eq_of_div_eq_one, λ hab, hab.symm ▸ h.div_self⟩

@[to_additive] protected lemma div_mul_left (h : is_unit b) : b / (a * b) = 1 / a :=
by rw [div_eq_mul_inv, mul_inv_rev, h.mul_inv_cancel_left, one_div]

@[to_additive] protected lemma mul_div_mul_right (h : is_unit c) (a b : α) :
  (a * c) / (b * c) = a / b :=
by simp only [div_eq_mul_inv, mul_inv_rev, mul_assoc, h.mul_inv_cancel_left]

@[to_additive] protected lemma mul_mul_div (a : α) (h : is_unit b) : a * b * (1 / b) = a :=
by simp [h]

end division_monoid

section division_comm_monoid
variables [division_comm_monoid α] {a b c d : α}

@[to_additive] protected lemma div_mul_right (h : is_unit a) (b : α) : a / (a * b) = 1 / b :=
by rw [mul_comm, h.div_mul_left]

@[to_additive] protected lemma mul_div_cancel_left (h : is_unit a) (b : α) : a * b / a = b :=
by rw [mul_comm, h.mul_div_cancel]

@[to_additive] protected lemma mul_div_cancel' (h : is_unit a) (b : α) : a * (b / a) = b :=
by rw [mul_comm, h.div_mul_cancel]

@[to_additive] protected lemma mul_div_mul_left (h : is_unit c) (a b : α) :
  (c * a) / (c * b) = a / b :=
by rw [mul_comm c, mul_comm c, h.mul_div_mul_right]

@[to_additive] protected lemma mul_eq_mul_of_div_eq_div (hb : is_unit b) (hd : is_unit d) (a c : α)
  (h : a / b = c / d) : a * d = c * b :=
by rw [←mul_one a, ←hb.div_self, ←mul_comm_div, h, div_mul_eq_mul_div, hd.div_mul_cancel]

@[to_additive] protected lemma div_eq_div_iff (hb : is_unit b) (hd : is_unit d) :
  a / b = c / d ↔ a * d = c * b :=
by rw [←(hb.mul hd).mul_left_inj, ←mul_assoc, hb.div_mul_cancel, ←mul_assoc, mul_right_comm,
  hd.div_mul_cancel]

@[to_additive] protected lemma div_div_cancel (h : is_unit a) : a / (a / b) = b :=
by rw [div_div_eq_mul_div, h.mul_div_cancel_left]

@[to_additive] protected lemma div_div_cancel_left (h : is_unit a) :
  a / b / a = b⁻¹ :=
by rw [div_eq_mul_inv, div_eq_mul_inv, mul_right_comm, h.mul_inv_cancel, one_mul]

end division_comm_monoid
end is_unit


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

! This file was ported from Lean 3 source module measure_theory.function.lp_order
! leanprover-community/mathlib commit 7f5bf383ec988aa0ea700583c6449332003722a7
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Normed.Order.Lattice
import Mathbin.MeasureTheory.Function.LpSpace

/-!
# Order related properties of Lp spaces

### Results

- `Lp E p μ` is an `ordered_add_comm_group` when `E` is a `normed_lattice_add_comm_group`.

### TODO

- move definitions of `Lp.pos_part` and `Lp.neg_part` to this file, and define them as
  `has_pos_part.pos` and `has_pos_part.neg` given by the lattice structure.

-/

open TopologicalSpace MeasureTheory LatticeOrderedCommGroup

open ENNReal

variable {α E : Type _} {m : MeasurableSpace α} {μ : Measure α} {p : ℝ≥0∞}

namespace MeasureTheory

namespace Lp

section Order

variable [NormedLatticeAddCommGroup E]

theorem coeFn_le (f g : lp E p μ) : f ≤ᵐ[μ] g ↔ f ≤ g := by
  rw [← Subtype.coe_le_coe, ← ae_eq_fun.coe_fn_le, ← coeFn_coeBase, ← coeFn_coeBase]
#align measure_theory.Lp.coe_fn_le MeasureTheory.lp.coeFn_le

theorem coeFn_nonneg (f : lp E p μ) : 0 ≤ᵐ[μ] f ↔ 0 ≤ f :=
  by
  rw [← coe_fn_le]
  have h0 := Lp.coe_fn_zero E p μ
  constructor <;> intro h <;> filter_upwards [h, h0]with _ _ h2
  · rwa [h2]
  · rwa [← h2]
#align measure_theory.Lp.coe_fn_nonneg MeasureTheory.lp.coeFn_nonneg

instance : CovariantClass (lp E p μ) (lp E p μ) (· + ·) (· ≤ ·) :=
  by
  refine' ⟨fun f g₁ g₂ hg₁₂ => _⟩
  rw [← coe_fn_le] at hg₁₂⊢
  filter_upwards [coe_fn_add f g₁, coe_fn_add f g₂, hg₁₂]with _ h1 h2 h3
  rw [h1, h2, Pi.add_apply, Pi.add_apply]
  exact add_le_add le_rfl h3

instance : OrderedAddCommGroup (lp E p μ) :=
  { Subtype.partialOrder _, AddSubgroup.toAddCommGroup _ with
    add_le_add_left := fun f g => add_le_add_left }

theorem MeasureTheory.Memℒp.sup {f g : α → E} (hf : Memℒp f p μ) (hg : Memℒp g p μ) :
    Memℒp (f ⊔ g) p μ :=
  Memℒp.mono' (hf.norm.add hg.norm) (hf.1.sup hg.1)
    (Filter.eventually_of_forall fun x => norm_sup_le_add (f x) (g x))
#align measure_theory.mem_ℒp.sup MeasureTheory.Memℒp.sup

theorem MeasureTheory.Memℒp.inf {f g : α → E} (hf : Memℒp f p μ) (hg : Memℒp g p μ) :
    Memℒp (f ⊓ g) p μ :=
  Memℒp.mono' (hf.norm.add hg.norm) (hf.1.inf hg.1)
    (Filter.eventually_of_forall fun x => norm_inf_le_add (f x) (g x))
#align measure_theory.mem_ℒp.inf MeasureTheory.Memℒp.inf

theorem MeasureTheory.Memℒp.abs {f : α → E} (hf : Memℒp f p μ) : Memℒp (|f|) p μ :=
  hf.sup hf.neg
#align measure_theory.mem_ℒp.abs MeasureTheory.Memℒp.abs

instance : Lattice (lp E p μ) :=
  Subtype.lattice
    (fun f g hf hg => by
      rw [mem_Lp_iff_mem_ℒp] at *
      exact (mem_ℒp_congr_ae (ae_eq_fun.coe_fn_sup _ _)).mpr (hf.sup hg))
    fun f g hf hg => by
    rw [mem_Lp_iff_mem_ℒp] at *
    exact (mem_ℒp_congr_ae (ae_eq_fun.coe_fn_inf _ _)).mpr (hf.inf hg)

theorem coeFn_sup (f g : lp E p μ) : ⇑(f ⊔ g) =ᵐ[μ] ⇑f ⊔ ⇑g :=
  AeEqFun.coeFn_sup _ _
#align measure_theory.Lp.coe_fn_sup MeasureTheory.lp.coeFn_sup

theorem coeFn_inf (f g : lp E p μ) : ⇑(f ⊓ g) =ᵐ[μ] ⇑f ⊓ ⇑g :=
  AeEqFun.coeFn_inf _ _
#align measure_theory.Lp.coe_fn_inf MeasureTheory.lp.coeFn_inf

theorem coeFn_abs (f : lp E p μ) : ⇑(|f|) =ᵐ[μ] fun x => |f x| :=
  AeEqFun.coeFn_abs _
#align measure_theory.Lp.coe_fn_abs MeasureTheory.lp.coeFn_abs

noncomputable instance [Fact (1 ≤ p)] : NormedLatticeAddCommGroup (lp E p μ) :=
  { lp.lattice,
    lp.normedAddCommGroup with
    add_le_add_left := fun f g => add_le_add_left
    solid := fun f g hfg => by
      rw [← coe_fn_le] at hfg
      simp_rw [Lp.norm_def, ENNReal.toReal_le_toReal (Lp.snorm_ne_top f) (Lp.snorm_ne_top g)]
      refine' snorm_mono_ae _
      filter_upwards [hfg, Lp.coe_fn_abs f, Lp.coe_fn_abs g]with x hx hxf hxg
      rw [hxf, hxg] at hx
      exact solid hx }

end Order

end Lp

end MeasureTheory


import data.multiset
import data.list_extra

namespace multiset
open multiset

variables {α : Type*}

def all_prop (p : α → Prop) (s : multiset α) : Prop :=
quot.lift_on s (list.all_prop p) (λ l₁ l₂ e, by {change list.perm l₁ l₂ at e,
 apply propext,
 apply @list.perm_induction_on α
  (λ l₁ l₂, list.all_prop p l₁ ↔ list.all_prop p l₂) l₁ l₂ e,
 {rw[iff_self],trivial},
 {intros x l₁ l₂ h,simp only[],intro h',rw[list.all_prop,list.all_prop,h']},
 {intros x y l₁ l₂ h,simp only [],intro h',
  repeat {rw[list.all_prop]},rw[← and_assoc,and_comm (p y),and_assoc,h'],},
 {intros l₁ l₂ l₃ h₁₂ h₂₃,simp only[],intros e₁₂ e₂₃,exact e₁₂.trans e₂₃}
})

lemma all_prop_coe (p : α → Prop) (l : list α) :
 all_prop p l = list.all_prop p l := rfl

lemma all_prop_iff {p : α → Prop} {s : multiset α} :
 all_prop p s ↔ ∀ a, a ∈ s → p a :=
begin
 rcases s,
 have : quot.mk setoid.r s = (s : multiset α) := rfl, rw[this],
 rw[all_prop_coe,list.all_prop_iff],
 split; intros h a; replace h := h a; rw[mem_coe] at *; exact h,
end

lemma pairwise_zero {α : Type*} (p : α → α → Prop) :
 pairwise p 0 := ⟨list.nil,⟨rfl,list.pairwise.nil⟩⟩

variable [decidable_eq α]

lemma eq_iff_count {s t : multiset α} :
 s = t ↔ ∀ a, count a s = count a t :=
begin
 split,
 {intros e a,rw[e]},
 {intro h,apply le_antisymm;rw[le_iff_count]; intro a; rw[h a],}
end

lemma eq_repeat_count (s : multiset α) :
 s.dedup.bind (λ a, repeat a (count a s)) = s :=
begin
 rw[eq_iff_count],intro a,
 rw[count_bind],
 let s₁ := s.dedup,
 let m₁ := s₁.card,
 let c := (λ b, count a (repeat b (count b s))),
 change (s₁.map c).sum = count a s,
 by_cases h : a ∈ s,
 {have h₁ : a ∈ s₁ := mem_dedup.mpr h,
  let s₂ : multiset α := s₁.erase a,
  let s₃ : multiset α := (cons a s₂),
  have : s₁ = (cons a s₂) := (cons_erase h₁).symm,
  rw[this,map_cons,sum_cons],
  have : a ∉ s₂ := λ h,
   ((nodup.mem_erase_iff s.nodup_dedup).mp h).left rfl,
  have : s₂.map c = repeat 0 (s₂.map c).card :=
   by {apply eq_repeat.mpr ⟨rfl,_⟩,intros m hm,
       rcases (mem_map.mp hm) with ⟨b,⟨h₁,h₂⟩⟩,
       rw[← h₂,count_eq_zero],intro ha,
       rw[← eq_of_mem_repeat ha] at h₁,
       exact this h₁,
   },
  rw[this,sum_repeat,smul_eq_mul,mul_zero,add_zero],
  dsimp[c],rw[count_repeat,if_pos (rfl : a = a)],
 },
 {rw[count_eq_zero.mpr h],
  have : s₁.map c = repeat 0 (s₁.map c).card :=
   by {apply eq_repeat.mpr ⟨rfl,_⟩,intros m hm,
       rcases (mem_map.mp hm) with ⟨b,⟨h₁,h₂⟩⟩,
       rw[← h₂,count_eq_zero],intro ha,
       rw[eq_of_mem_repeat ha] at h,
       exact h (mem_dedup.mp h₁),
   },
  rw[this,sum_repeat,smul_eq_mul,mul_zero],
 }
end

end multiset


/-
Copyright (c) 2021 Paula Neeley. All rights reserved.
Author: Paula Neeley
Following the textbook "Dynamic Epistemic Logic" by
Hans van Ditmarsch, Wiebe van der Hoek, and Barteld Kooi
-/

import del.syntax.syntaxDEL del.syntax.syntaxlemmasPADEL
import del.semantics.translationfunction

variables {agents : Type}
open prfPA
open PAlemma

lemma iff_iff_imp_iff {Γ : ctxPA agents} {φ ψ χ θ : formPA agents} :
  prfPA Γ (φ ↔ χ) → prfPA Γ (ψ ↔ θ) → prfPA Γ ((φ ⊃ ψ) ↔ (χ ⊃ θ)) :=
begin
intros h1 h2,
exact (mp (mp pl4 (imp_switch (cut1 (cut (mp pl6 h1) likemp) (mp pl5 h2))))
  (imp_switch (cut1 (cut (mp pl5 h1) likemp) (mp pl6 h2))))
end

lemma iff_k_dist {Γ : ctxPA agents} {φ ψ : formPA agents} {a : agents} :
  prfPA Γ (φ ↔ ψ) → prfPA Γ (K a φ ↔ K a ψ) :=
begin
intro h1,
exact (mp (mp pl4 (mp kdist (nec (mp pl5 h1)))) (mp kdist (nec (mp pl6 h1)))),
end

lemma update_iff1 {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ (φ ↔ χ) → prfPA Γ (U φ ψ ↔ (φ ⊃ ψ)) → prfPA Γ (U φ ψ ↔ (χ ⊃ ψ)) :=
begin
intros h1 h2,
exact (mp (mp pl4 (cut2 (mp pl6 h1) (mp pl5 h2))) (cut (mp hs2 (mp pl5 h1)) (mp pl6 h2)))
end

lemma update_iff2 {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ ((U φ ψ) ↔ to_PA (translate (U φ ψ))) → prfPA Γ ((U φ χ) ↔ to_PA (translate (U φ χ))) →
  prfPA Γ ((U φ (ψ & χ) ↔ (U φ ψ & U φ χ))) →
  prfPA Γ ((U φ (ψ & χ) ↔ to_PA (translate (U φ ψ)) & to_PA (translate (U φ χ)))) :=
begin
intros h1 h2 h3,
exact (mp (mp pl4 (cut (mp pl5 h3) (imp_and_and_imp
  (mp (mp pl4 (mp pl5 h1)) (mp pl5 h2))))) (cut (imp_and_and_imp
  (mp (mp pl4 (mp pl6 h1)) (mp pl6 h2))) (mp pl6 h3)))
end

lemma update_iff3 {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ (U φ ψ ↔ to_PA (translate (U φ ψ))) →
  prfPA Γ (U φ χ ↔ to_PA (translate (U φ χ))) →
  prfPA Γ (U φ (ψ ⊃ χ) ↔ (U φ ψ ⊃ U φ χ)) →
  prfPA Γ
    (U φ (ψ ⊃ χ) ↔ to_PA (translate (U φ ψ)) ⊃ to_PA (translate (U φ χ))) :=
begin
intros h1 h2 h3,
exact mp (mp pl4 (cut2 (mp pl6 h1) (cut1 (mp pl5 h3) (mp pl5 h2))))
  (cut (cut2 (mp pl5 h1) (mp hs1 (mp pl6 h2))) (mp pl6 h3))
end

lemma update_iff4 {Γ : ctxPA agents} {φ ψ : formPA agents} {a : agents} :
  prfPA Γ ((U φ (K a ψ) ↔ (φ ⊃ K a (U φ ψ)))) →
  prfPA Γ ((φ ⊃ K a (U φ ψ)) ↔ to_PA (translate (φ ⊃ K a (U φ ψ)))) →
  prfPA Γ (U φ (K a ψ) ↔ (to_PA (translate φ) ⊃ K a (to_PA (translate (U φ ψ))))) :=
begin
simp at *,
intros h1 h2,
exact mp (mp pl4 (cut (mp pl5 h1) (mp pl5 h2))) (cut (mp pl6 h2) (mp pl6 h1))
end

lemma update_iff5 {Γ : ctxPA agents} {φ ψ χ : formPA agents} :
  prfPA Γ ((U φ (U ψ χ)) ↔ (U (φ & U φ ψ) χ)) →
  prfPA Γ ((U (φ & U φ ψ) χ) ↔ to_PA (translate (U (φ & U φ ψ) χ))) →
  prfPA Γ ((U φ (U ψ χ)) ↔ to_PA (translate (U (φ & U φ ψ) χ))) :=
begin
simp at *,
intros h1 h2,
exact mp (mp pl4 (cut (mp pl5 h1) (mp pl5 h2))) (cut (mp pl6 h2) (mp pl6 h1))
end


/-
Making some ring definitions in a lean
-/

import tactic

namespace myring

-- There are many many types of rings, might be annoying to add them all

-- my rings!
class ring (R : Type) extends has_add R, has_zero R, has_inv R, has_mul R, has_one R, has_neg R:=
(add_comm   : ∀ (a b : R), a + b = b + a)
(add_ass    : ∀ (a b c : R), (a + b) + c = a + (b + c))
(zero_add   : ∀ (a : R), 0 + a = a)
(add_zero   : ∀ (a : R), a + 0 = a)                         --prefer
(inv_add    : ∀ (a : R), (-a) + a = 0)
(add_inv    : ∀ (a : R), a + (-a) = 0)                      --prefer
(mul_ass    : ∀ (a b c : R), (a * b) * c = a * (b * c))
(left_dist  : ∀ (a b c : R), a * (b + c) = a * b + a * c)
(right_dist : ∀ (a b c : R), (a + b) * c = a * c + b * c)

/-
class comm_ring (R : Type) [ring R] :=
(M1 : ∀ (a b : R), a * b = b * a)

class unity_ring (R : Type) [ring R] :=
(M3l : ∀ (a : R), 1 * a = a)
(M3r : ∀ (a : R), a * 1 = a)

class no_zero_div_ring (R : Type) [ring R] :=
(Z : ∀ (a b : R), a * b = 0 → a = 0 ∨ b = 0)
-/

namespace ring

variables {R : Type} [ring R]

-- I am so bad at this so I will do the most stupid of exercises
lemma add_is_comm {a b : R} : a + b = b + a :=
begin
  rw add_comm,
end

lemma add_is_comm' {a b : R} : a + b = b + a := by rw add_comm a b

lemma add_inv_add {a : R} : a + (-a) + a = a :=
begin
    rw add_inv,
    rw zero_add,
end

lemma add_inv_add' {a : R} : a + (-a) + a = a :=
begin
    rw [add_inv,zero_add],
end

lemma add_inv_add'' {a : R} : a + (-a) + a = a := by rw [add_inv,zero_add]
-- I think this is the shortest I can do

-- maybe with my little simp?

attribute [simp] add_inv zero_add

lemma add_inv_add''' {a : R} : a + (-a) + a = a := by simp
-- ok that is good

-- I wonder if we can just use right inverses/zeros instead of both
-- I think we definitely can, eg:
lemma add_inv_add'''' {a : R} : a + (-a) + a = a :=
begin
    rw add_ass,
    rw inv_add,
    rw add_zero,
end

lemma add_inv_add''''' {a : R} : a + (-a) + a = a := by rw [add_ass, inv_add, add_zero]
-- ok

--can simpy do it?
attribute [simp] add_ass inv_add add_zero
lemma add_inv_add'''''' {a : R} : a + (-a) + a = a := by simp
-- ok, nice one

lemma many_adders {a : R} : a + (-a) + (-a) + a + (-a) + (-a) + a + a = 0 := by simp
-- ok

lemma zero_add1 {a : R} : 0 + a + 0 + a = a + a :=
begin
    rw add_zero,
    rw add_comm,
    rw ← add_ass,
    rw add_zero,
    /-
    I had a bit of trouble being able to move around my cursor
    I could use simp, but being able to place my finger somewhere would be great
    Asking on zulip!
    -/
end

lemma add_left_cancel (a b c : R) (h: a + b = a + c) : b = c :=
begin
calc b = 0 + b          : by rw zero_add
...    = ((-a) + a) + b : by rw inv_add
...    = (-a) + (a + b) : by rw add_ass
...    = (-a) + (a + c) : by rw h
...    = ((-a) + a) + c : by rw add_ass
...    = 0 + c          : by rw inv_add
...    = c              : by rw zero_add
end

-- can our simp do it?
attribute [simp] inv_add add_ass zero_add

-- first let's see where it stops doing its thing
lemma add_left_cancel' (a b c : R) (h: a + b = a + c) : b = c :=
begin
    rw ← zero_add b,
    rw ← inv_add a,
    rw add_ass,
    rw h,
    rw ← add_ass,
    rw inv_add,
    rw zero_add,
end

lemma add_left_cancel'' {a b c : R} (h: a + b = a + c) : b = c :=
begin
    rw ← zero_add b,
    rw ← inv_add a,
    rw add_ass,
    rw h,
    rw ← add_ass,
    rw inv_add,
    simp,
end

lemma add_left_cancel''' {a b c : R} (h: a + b = a + c) : b = c :=
begin
    rw ← zero_add b,
    rw ← inv_add a,
    rw add_ass,
    rw h,
    rw ← add_ass,
    simp,
end

-- nope so it can't do association, quite bad
lemma add_left_cancel'''' {a b c : R} (h: a + b = a + c) : b = c :=
begin
    rw ← zero_add b,
    rw ← inv_add a,
    rw add_ass,
    rw h,
    sorry
    --simp,
end

attribute [simp] inv_add add_ass zero_add add_comm

lemma add_left_cancel''''' {a b c : R} (h: a + b = a + c) : b = c :=
begin
    rw ← zero_add b,
    rw ← inv_add a,
    rw add_ass,
    rw h,
    simp [← add_ass],
end

lemma add_left_cancel'''''' {a b c : R} (h: a + b = a + c) : b = c :=
begin
    rw [←zero_add b, ←inv_add a, add_ass, h],
    simp [← add_ass],
end

--technically it's quicker like this
lemma add_left_cancel''''''' {a b c : R} (h: a + b = a + c) : b = c :=
    by rw [←zero_add b, ←inv_add a, add_ass, h, ←add_ass, inv_add, zero_add]

-- anyways, what's this all about:
lemma is_this_true {a : R} : a + a = 2 * a :=
begin
    -- why did changing input from {} to () let me use add_left_cancel?
    --rw ← add_zero (2*a), interesting not needed!
    apply add_left_cancel (-a),
    rw [←add_ass, inv_add, zero_add, add_comm],
    -- shit we need to show that -1*a is -a
    sorry
end

lemma right_multiply {a b c : R} (h: a = b) : a*c = b*c :=
begin
    sorry --how do I get the property of the = sign?
end

theorem zero_multiply (a : R) : 0 * a = 0 :=
begin
    sorry
end

theorem multiply_zero (a : R) : a * 0 = 0 :=
begin
    sorry
end

end ring

end myring


import tactic

-- Section 7.1: Enumerated Types
namespace s71

  inductive weekday : Type
  | sunday : weekday
  | monday : weekday
  | tuesday : weekday
  | wednesday : weekday
  | thursday : weekday
  | friday : weekday
  | saturday : weekday
  #check weekday.sunday
  #check weekday.monday

  open weekday

  #check sunday
  #check monday

  -- rec on

  def number_of_day (d : weekday) : ℕ :=
  weekday.rec_on d 1 2 3 4 5 6 7

  #reduce number_of_day weekday.sunday
  #reduce number_of_day weekday.monday
  #reduce number_of_day weekday.tuesday

  -- Less general, cases on
  def number_of_day2 (d : weekday) : ℕ :=
  weekday.cases_on d 1 2 3 4 5 6 7

  -- Group functions associated with datatype
  namespace weekday
    @[reducible]
    private def cases_on := @weekday.cases_on

    def number_of_day (d : weekday) : nat :=
    cases_on d 1 2 3 4 5 6 7
  end weekday

  #reduce weekday.number_of_day weekday.sunday

  open weekday (renaming cases_on → cases_on)

  #reduce number_of_day sunday
  #check cases_on

  namespace weekday
    def next (d : weekday) : weekday :=
    weekday.cases_on d monday tuesday wednesday thursday friday
      saturday sunday

    def previous (d : weekday) : weekday :=
    weekday.cases_on d saturday sunday monday tuesday wednesday
      thursday friday

    #reduce next (next tuesday)
    #reduce next (previous tuesday)

    example : next (previous tuesday) = tuesday := rfl
  end weekday

  -- Prove an invariant
  open weekday (renaming cases_on → cases_on)

    theorem next_previous (d: weekday) :
      next (previous d) = d :=
    weekday.cases_on d
      (show next (previous sunday) = sunday, from rfl)
      (show next (previous monday) = monday, from rfl)
      (show next (previous tuesday) = tuesday, from rfl)
      (show next (previous wednesday) = wednesday, from rfl)
      (show next (previous thursday) = thursday, from rfl)
      (show next (previous friday) = friday, from rfl)
      (show next (previous saturday) = saturday, from rfl)

  --more succinct
    theorem next_previous2 (d: weekday) :
      next (previous d) = d :=
    weekday.cases_on d rfl rfl rfl rfl rfl rfl rfl

  -- Use parallel combinator ; in tactic proof
  theorem next_previous3 (d: weekday) :
      next (previous d) = d :=
    by apply weekday.cases_on d; refl

  -- standard library enum types
  namespace hidden
  inductive empty : Type

  inductive unit : Type
  | star : unit

  inductive bool : Type
  | ff : bool
  | tt : bool
  end hidden

end s71

-- Section 7.2: Constructors with Arguments
namespace s72
  universes u v

  inductive prodd (α : Type u) (β : Type v)
  | mk : α → β → prodd

  inductive summ (α : Type u) (β : Type v)
  | inl {} : α → summ
  | inr {} : β → summ

  def fst {α : Type u} {β : Type v} (p : prodd α β) : α :=
  prodd.rec_on p (λ a b, a)

  def snd {α : Type u} {β : Type v} (p : α × β) : β :=
  prod.rec_on p (λ a b, b)

  def prod_example (p : bool × ℕ) : ℕ :=
  prod.rec_on p (λ b n, cond b (2 * n) (2 * n + 1))

  #reduce prod_example (tt, 3)
  #reduce prod_example (ff, 3)

  def sum_example (s : ℕ ⊕ ℕ) : ℕ :=
  sum.cases_on s (λ n, 2 * n) (λ n, 2 * n + 1)

  #reduce sum_example (sum.inl 3)
  #reduce sum_example (sum.inr 3)

  -- simultaneously introduces the inductive type, prod, its constructor, mk, the usual eliminators (rec and rec_on), as well as the projections, fst and snd, as defined above.
  structure prod3 (α β : Type) :=
  mk :: (fst : α) (snd : β)

  structure color := (red : nat) (green : nat) (blue : nat)
  def yellow := color.mk 255 255 0
  #reduce color.red yellow

  structure Semigroup :=
  (carrier : Type u)
  (mul : carrier → carrier → carrier)
  (mul_assoc : ∀ a b c, mul (mul a b) c = mul a (mul b c))

  inductive sigma {α : Type u} (β : α → Type v)
  | dpair : Π a : α, β a → sigma

  inductive option (α : Type u)
  | none {} : option
  | some    : α → option

  inductive inhabited (α : Type u)
  | mk : α → inhabited

end s72

-- Section 7.3: Inductively defined propositions
namespace s73
  inductive false : Prop

  inductive true : Prop
  | intro : true

  inductive and (a b : Prop) : Prop
  | intro : a → b → and

  inductive or (a b : Prop) : Prop
  | intro_left  : a → or
  | intro_right : b → or

  universe u
  inductive Exists {α : Type u} (p : α → Prop) : Prop
  | intro : ∀ (a : α), p a → Exists

  def exists.intro := @Exists.intro

  -- false, true, and, + or are perfectly analogous to the definitions of empty, unit, prod, and sum.
  --difference is that the first group yields elements of Prop, and the second yields elements of Type u

  --  sort of a hybrid between ∃ x : α, P and Σ x : α, P.

  inductive subtype {α : Type u} (p : α → Prop)
  | mk : Π x : α, p x → subtype

  section
  structure subtype2 {α : Sort u} (p : α → Prop) :=
  (val : α) (property : p val)
  variables {α : Type u} (p : α → Prop)

  #check subtype2 p
  #check { x : α // p x}
  end

end s73

-- Section 7.4: Defining the nats
namespace s74
  inductive nat : Type
  | zero : nat
  | succ : nat → nat
  #check @nat.rec_on -- 'motive C', major premise n, and two minor premises

  namespace nat
    def add (m n : nat) : nat :=
    nat.rec_on n m (λ _ add_m_n, succ add_m_n)

    -- try it out
    #reduce add (succ zero) (succ (succ zero))

    instance : has_zero nat := has_zero.mk zero
    instance : has_add nat := has_add.mk add

    theorem add_zero (m : nat) : m + 0 = m := rfl
    theorem add_succ (m n : nat) : m + succ n = succ (m + n) := rfl

    theorem zero_add (n : nat) : 0 + n = n :=
    nat.rec_on n
      (show 0 + zero = zero, from rfl)
      (assume n,
        assume ih : 0 + n = n,
        show 0 + succ n = succ n, from
          calc
            0 + succ n = succ (0 + n) : rfl
              ... = succ n : by rw ih)

    theorem zero_add' (n : nat) : 0 + n = n :=
    nat.rec_on n rfl (λ n ih, by rw [add_succ, ih])

    theorem zero_add''(n : nat) : 0 + n = n :=
    nat.rec_on n rfl (λ n ih, by simp only [add_succ, ih])

    theorem add_assoc (m n k : nat) : m + n + k = m + (n + k) :=
    nat.rec_on k
      (show m + n + 0 = m + (n + 0), from rfl)
      (assume k,
        assume ih : m + n + k = m + (n + k),
        show m + n + succ k = m + (n + succ k), from
          calc
            m + n + succ k = succ (m + n + k) : rfl
              ... = succ (m + (n + k)) : by rw ih
              ... = m + succ (n + k) : rfl
              ... = m + (n + succ k) : rfl)

    theorem add_assoc' (m n k : nat) : m + n + k = m + (n + k) :=
    nat.rec_on k rfl (λ k ih, by simp only [add_succ, ih])

    theorem succ_add (m n : nat) : succ m + n = succ (m + n) :=
    nat.rec_on n
      (show succ m + 0 = succ (m + 0), from rfl)
      (assume n,
        assume ih : succ m + n = succ (m + n),
        show succ m + succ n = succ (m + succ n), from
          calc
            succ m + succ n = succ (succ m + n) : rfl
              ... = succ (succ (m + n)) : by rw ih
              ... = succ (m + succ n) : rfl)

    theorem succ_add' (m n : nat) : succ m + n = succ (m + n) :=
    nat.rec_on n rfl (λ n ih, by simp only [add_succ, ih])

  theorem add_comm (m n : nat) : m + n = n + m :=
  nat.rec_on n
    (show m + 0 = 0 + m, by rw [nat.zero_add, nat.add_zero])
    (assume n,
      assume ih : m + n = n + m,
      calc
        m + succ n = succ (m + n) : rfl
          ... = succ (n + m) : by rw ih
          ... = succ n + m : by rw succ_add)

  end nat

end s74

-- Section 7.5: Other recursive datatypes
namespace s75
  universe u
  inductive list (α : Type u)
  | nil {} : list
  | cons : α → list → list

  namespace list

    variable {α : Type}

    notation h :: t  := cons h t

    def append (s t : list α) : list α :=
    list.rec t (λ x l u, x::u) s

    notation s ++ t := append s t

    theorem nil_append (t : list α) : nil ++ t = t := rfl

    theorem cons_append (x : α) (s t : list α) :
      x::s ++ t = x::(s ++ t) := rfl

    notation `[` l:(foldr `,` (h t, cons h t) nil) `]` := l

    section
      open nat
      #check [1, 2, 3, 4, 5]
      #check ([1, 2, 3, 4, 5] : list int)
    end

  end list

  inductive binary_tree
  | leaf : binary_tree
  | node : binary_tree → binary_tree → binary_tree

  inductive cbtree
  | leaf : cbtree
  | sup : (ℕ → cbtree) → cbtree

  namespace cbtree

  def succ (t : cbtree) : cbtree :=
  sup (λ n, t)

  def omega : cbtree :=
  sup (λ n, nat.rec_on n leaf (λ n t, succ t))

  end cbtree

end s75

-- Section 7.6: Tactics for inductive types
namespace s76
  open nat
  variable p : ℕ → Prop

  example (hz : p 0) (hs : ∀ n, p (succ n)) : ∀ n, p n :=
  begin
    intro n,
    cases n,
    { exact hz },  -- goal is p 0
    apply hs       -- goal is a : ℕ ⊢ p (succ a)
  end

  example (n : ℕ) (h : n ≠ 0) : succ (pred n) = n :=
  begin
    cases n with m,
    -- first goal: h : 0 ≠ 0 ⊢ succ (pred 0) = 0
      { apply (absurd rfl h) },
    -- second goal: h : succ m ≠ 0 ⊢ succ (pred (succ a)) = succ a
    reflexivity
  end

  def f37 (n : ℕ) : ℕ :=
  begin
    cases n, exact 3, exact 7
  end

  example : f37 0 = 3 := rfl
  example : f37 5 = 7 := rfl
  universe u

  def tuple (α : Type u) (n : ℕ) :=
    { l : list α // list.length l = n }

  variables {α : Type u} {n : ℕ}

  def f {n : ℕ} (t : tuple α n) : ℕ :=
  begin
    cases n, exact 3, exact 7
  end
  def myList : list ℕ := 0 :: 1 :: 2 :: list.nil
  def my_tuple : tuple ℕ 3 :=  ⟨myList, rfl⟩
  def my_tuple2 : tuple ℕ 0 :=  ⟨list.nil, rfl⟩

  example : f my_tuple = 7 := rfl
  example : f my_tuple2 = 3 := rfl

  inductive foo : Type
  | bar1 : ℕ → ℕ → foo
  | bar2 : ℕ → ℕ → ℕ → foo

  def silly (x : foo) : ℕ :=
  begin
    cases x with a b c d e,
    exact b,    -- a, b    are in the context
    exact e     -- c, d, e are in the context
  end

  --Difficult to keep track of which variables are associated w/ which case
  -- Same as above but better syntax
  def silly' (x : foo) : ℕ :=
  begin
    cases x,
      case foo.bar2 : c d e -- Can do in any order
        { exact e },
      case foo.bar1 : a b
        { exact b }
  end

  --Cases on an arbitrary expr
  open nat
  variable natprop : ℕ → Prop

  example (hz : natprop 0) (hs : ∀ n, natprop (succ n)) (m k : ℕ) :
    natprop (m + 3 * k) :=
  begin
    cases (m + 3 * k),
    { exact hz },  -- goal is natprop 0
    apply hs       -- goal is a : ℕ ⊢ natprop (succ a)
  end

  -- above is equivalent to this

  example (hz : natprop 0) (hs : ∀ n, natprop (succ n)) (m k : ℕ) :
    natprop (m + 3 * k) :=
  begin
    generalize : (m + 3 * k) = n,
    cases n,
    { exact hz },  -- goal is natprop 0
    apply hs       -- goal is a : ℕ ⊢ natprop (succ a)
  end

  example (p : Prop) (m n : ℕ)
    (h₁ : m < n → p) (h₂ : m ≥ n → p) : p :=
  begin
    cases lt_or_ge m n with hlt hge,
    { exact h₁ hlt },
    exact h₂ hge
  end

  #check nat.sub_self

  example (m n : ℕ) : m - n = 0 ∨ m ≠ n :=
  begin
    cases decidable.em (m = n) with heq hne,
    { rw heq,
      left, exact nat.sub_self n },
    right, exact hne
  end

  def f' (m k : ℕ) : ℕ :=
  begin
    cases m - k, exact 3, exact 7
  end

  example : f' 5 7 = 3 := rfl
  example : f' 10 2 = 7 := rfl

  -- Induction instead of 'cases'
  theorem zero_add (n : ℕ) : 0 + n = n :=
  begin
    induction n with n ih,
      refl,
    rw [add_succ, ih]
  end

  theorem zero_add' (n : ℕ) : 0 + n = n :=
  begin
    induction n,
    case zero : { refl },
    case succ : n ih { rw [add_succ, ih]}
  end

  theorem succ_add (m n : ℕ) : succ m + n = succ (m + n) :=
  begin
    induction n,
    case zero : { refl },
    case succ : n ih { rw [add_succ, ih] }
  end

  theorem add_comm (m n : ℕ) : m + n = n + m :=
  begin
    induction n,
    case zero : { rw zero_add, refl },
    case succ : n ih { rw [add_succ, ih, succ_add] }
  end

  theorem zero_add'' (n : ℕ) : 0 + n = n :=
  by induction n; simp only [*, add_zero, add_succ]

  theorem succ_add' (m n : ℕ) : succ m + n = succ (m + n) :=
  by induction n; simp only [*, add_zero, add_succ]

  theorem add_comm' (m n : ℕ) : m + n = n + m :=
  by induction n;
      simp only [*, add_zero, add_succ, succ_add, zero_add]

  theorem add_assoc (m n k : ℕ) : m + n + k = m + (n + k) :=
  by induction k; simp only [*, add_zero, add_succ]

  -- Injectivity of the IDT constructors
  example (m n k : ℕ) (h : succ (succ m) = succ (succ n)) :
    n + k = m + k :=
  begin
    injection h   with h',
    injection h' with h'',
    rw h''
  end

  example (m n k : ℕ) (h : succ (succ m) = succ (succ n)) :
    n + k = m + k :=
  begin
    injections with h' h'',
    rw h''
  end

  example (m n k : ℕ) (h : succ (succ m) = succ (succ n)) :
    n + k = m + k :=
  by injections; simp *

  example (m n : ℕ) (h : succ m = 0) : n = n + 7 :=
  by injections

  example (m n : ℕ) (h : succ m = 0) : n = n + 7 :=
  by contradiction

  example (h : 7 = 4) : false :=
  by injections

end s76

-- Section 7.7: inductive families
namespace s77

  universe u
  open nat
  inductive vector (α : Type u) : nat → Type u
  | nil {}                              : vector zero
  | cons {n : ℕ} (a : α) (v : vector n) : vector (succ n)

  inductive eq {α : Sort u} (a : α) : α → Prop
  | refl : eq a

  @[elab_as_eliminator]
  theorem subst {α : Type u} {a b : α} {p : α → Prop}
    (h₁ : eq a b) (h₂ : p a) : p b :=
  eq.rec h₂ h₁

  #check subst

  theorem symm {α : Type u} {a b : α} (h : eq a b) : eq b a :=
  subst h (eq.refl a)

end s77

-- Section 7.9: mutual and nested inductive types
namespace s79
  mutual inductive even, odd
  with even : ℕ → Prop
  | even_zero : even 0
  | even_succ : ∀ n, odd n → even (n + 1)
  with odd : ℕ → Prop
  | odd_succ : ∀ n, even n → odd (n + 1)

  universe u

  mutual inductive tree, list_tree (α : Type u)
  with tree : Type u
  | node : α → list_tree → tree
  with list_tree : Type u
  | nil {} : list_tree
  | cons    : tree → list_tree → list_tree

  -- this is much more convenient, does the same thing under the hood (allowing us to use list theorems)
  inductive tree' (α : Type u)
  | mk : α → list tree' → tree'

end s79

-- Exercises
namespace s7x

-- 1
--define multiplication, the predecessor function, truncated subtraction, and exponentiation
  namespace nat
    def mult (m n : ℕ) : ℕ :=
    nat.rec_on n 0 (λ _ add_m_n, m + add_m_n)
    #reduce mult 6 4

    def pred (n : nat) : nat :=
    nat.rec_on n 0 (λ n pred_n, n)
    #reduce pred 21

    def tsub (m n : nat) : nat :=
    nat.rec_on n m (λ n tsub_m_n, pred tsub_m_n)
    #reduce tsub 21 4

    def exp (m n : nat) : nat :=
    nat.rec_on n 1 (λ n exp_m_n, mult m exp_m_n)
    #reduce exp 3 4

  end nat

--2a

variable {α : Type}
open list

--lemma append_nil (t : list α) : t ++ nil = t := list.append_nil

example  (s t : list α) : length (s ++ t) = length s + length t :=
  list.rec_on t
    (show length (s ++ nil) = length s + length nil, by
    calc
      length (s ++ nil) = length s : by rw append_nil
        ... = length s + length nil : by refl)
    (assume x : α,
    assume y : list α ,
    assume ih : length (s ++ y) = length (s) + length(y),
    show length (s ++ x::y) = length (s) + length(x::y), by
    calc
      length (s ++ x::y) = length (s ++ y) + 1 : by simp
      ... = length (s) + length(x::y): by simp )

example (t : list α) : list.length (list.reverse t) = list.length t := sorry

example (t : list α) : list.reverse (list.reverse t) = t := sorry

-- 2b implement the following
theorem append_nil (t : list α) : t ++ list.nil = t :=
  list.rec_on t
  (show nil ++ nil = nil, by refl)
  (assume x : α,
   assume y : list α,
   assume ih : y ++ nil = y,
   show x::y ++ nil = x::y , by {injections; simp})

theorem append_assoc (r s t : list α) :
r ++ s ++ t = r ++ (s ++ t) := sorry

--3
--4

--5

theorem trans {α : Type u} {a b c : α}
  (h₁ : eq a b) (h₂ : eq b c) : eq a c :=
sorry

theorem congr {α β : Type u} {a b : α} (f : α → β)
  (h : eq a b) : eq (f a) (f b) :=
sorry

end s7x


/-
Copyright (c) 2018 Robert Y. Lewis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Robert Y. Lewis, Chris Hughes
-/
import algebra.associated
import algebra.big_operators.basic
import ring_theory.valuation.basic
import data.nat.factorization

/-!
# Multiplicity of a divisor

For a commutative monoid, this file introduces the notion of multiplicity of a divisor and proves
several basic results on it.

## Main definitions

* `multiplicity a b`: for two elements `a` and `b` of a commutative monoid returns the largest
  number `n` such that `a ^ n ∣ b` or infinity, written `⊤`, if `a ^ n ∣ b` for all natural numbers
  `n`.
* `multiplicity.finite a b`: a predicate denoting that the multiplicity of `a` in `b` is finite.
-/

variables {α : Type*}

open nat part
open_locale big_operators

/-- `multiplicity a b` returns the largest natural number `n` such that
  `a ^ n ∣ b`, as an `enat` or natural with infinity. If `∀ n, a ^ n ∣ b`,
  then it returns `⊤`-/
def multiplicity [comm_monoid α] [decidable_rel ((∣) : α → α → Prop)] (a b : α) : enat :=
enat.find $ λ n, ¬a ^ (n + 1) ∣ b

namespace multiplicity

section comm_monoid
variables [comm_monoid α]

/-- `multiplicity.finite a b` indicates that the multiplicity of `a` in `b` is finite. -/
@[reducible] def finite (a b : α) : Prop := ∃ n : ℕ, ¬a ^ (n + 1) ∣ b

lemma finite_iff_dom [decidable_rel ((∣) : α → α → Prop)] {a b : α} :
  finite a b ↔ (multiplicity a b).dom := iff.rfl

lemma finite_def {a b : α} : finite a b ↔ ∃ n : ℕ, ¬a ^ (n + 1) ∣ b := iff.rfl

@[norm_cast]
theorem int.coe_nat_multiplicity (a b : ℕ) :
    multiplicity (a : ℤ) (b : ℤ) = multiplicity a b :=
begin
  apply part.ext',
  { repeat { rw [← finite_iff_dom, finite_def] },
    norm_cast },
  { intros h1 h2,
    apply _root_.le_antisymm; { apply nat.find_mono, norm_cast, simp } }
end

lemma not_finite_iff_forall {a b : α} : (¬ finite a b) ↔ ∀ n : ℕ, a ^ n ∣ b :=
⟨λ h n, nat.cases_on n (by { rw pow_zero, exact one_dvd _ }) (by simpa [finite, not_not] using h),
  by simp [finite, multiplicity, not_not]; tauto⟩

lemma not_unit_of_finite {a b : α} (h : finite a b) : ¬is_unit a :=
let ⟨n, hn⟩ := h in mt (is_unit_iff_forall_dvd.1 ∘ is_unit.pow (n + 1)) $
λ h, hn (h b)

lemma finite_of_finite_mul_left {a b c : α} : finite a (b * c) → finite a c :=
λ ⟨n, hn⟩, ⟨n, λ h, hn (h.trans (by simp [mul_pow]))⟩

lemma finite_of_finite_mul_right {a b c : α} : finite a (b * c) → finite a b :=
by rw mul_comm; exact finite_of_finite_mul_left

variable [decidable_rel ((∣) : α → α → Prop)]

lemma pow_dvd_of_le_multiplicity {a b : α} {k : ℕ} : (k : enat) ≤ multiplicity a b → a ^ k ∣ b :=
by { rw ← enat.some_eq_coe, exact
nat.cases_on k (λ _, by { rw pow_zero, exact one_dvd _ })
  (λ k ⟨h₁, h₂⟩, by_contradiction (λ hk, (nat.find_min _ (lt_of_succ_le (h₂ ⟨k, hk⟩)) hk))) }

lemma pow_multiplicity_dvd {a b : α} (h : finite a b) : a ^ get (multiplicity a b) h ∣ b :=
pow_dvd_of_le_multiplicity (by rw enat.coe_get)

lemma is_greatest  {a b : α} {m : ℕ} (hm : multiplicity a b < m) : ¬a ^ m ∣ b :=
λ h, by rw [enat.lt_coe_iff] at hm; exact nat.find_spec hm.fst ((pow_dvd_pow _ hm.snd).trans h)

lemma is_greatest' {a b : α} {m : ℕ} (h : finite a b) (hm : get (multiplicity a b) h < m) :
  ¬a ^ m ∣ b :=
is_greatest (by rwa [← enat.coe_lt_coe, enat.coe_get] at hm)

lemma pos_of_dvd {a b : α} (hfin : finite a b) (hdiv : a ∣ b) : 0 < (multiplicity a b).get hfin :=
begin
  refine zero_lt_iff.2 (λ h, _),
  simpa [hdiv] using (is_greatest' hfin (lt_one_iff.mpr h)),
end

lemma unique {a b : α} {k : ℕ} (hk : a ^ k ∣ b) (hsucc : ¬a ^ (k + 1) ∣ b) :
  (k : enat) = multiplicity a b :=
le_antisymm (le_of_not_gt (λ hk', is_greatest hk' hk)) $
  have finite a b, from ⟨k, hsucc⟩,
  by { rw [enat.le_coe_iff], exact ⟨this, nat.find_min' _ hsucc⟩ }

lemma unique' {a b : α} {k : ℕ} (hk : a ^ k ∣ b) (hsucc : ¬ a ^ (k + 1) ∣ b) :
  k = get (multiplicity a b) ⟨k, hsucc⟩ :=
by rw [← enat.coe_inj, enat.coe_get, unique hk hsucc]

lemma pow_dvd_iff_le_multiplicity {a b : α}
  {k : ℕ} : a ^ k ∣ b ↔ (k : enat) ≤ multiplicity a b :=
⟨le_multiplicity_of_pow_dvd, pow_dvd_of_le_multiplicity⟩

lemma multiplicity_lt_iff_neg_dvd {a b : α} {k : ℕ} :
  multiplicity a b < (k : enat) ↔ ¬ a ^ k ∣ b :=
by { rw [pow_dvd_iff_le_multiplicity, not_le] }

lemma eq_coe_iff {a b : α} {n : ℕ} :
  multiplicity a b = (n : enat) ↔ a ^ n ∣ b ∧ ¬a ^ (n + 1) ∣ b :=
begin
  rw [← enat.some_eq_coe],
  exact ⟨λ h, let ⟨h₁, h₂⟩ := eq_some_iff.1 h in
      h₂ ▸ ⟨pow_multiplicity_dvd _, is_greatest
        (by { rw [enat.lt_coe_iff], exact ⟨h₁, lt_succ_self _⟩ })⟩,
    λ h, eq_some_iff.2 ⟨⟨n, h.2⟩, eq.symm $ unique' h.1 h.2⟩⟩
end

lemma eq_top_iff {a b : α} :
  multiplicity a b = ⊤ ↔ ∀ n : ℕ, a ^ n ∣ b :=
(enat.find_eq_top_iff _).trans $
by { simp only [not_not],
     exact ⟨λ h n, nat.cases_on n (by { rw pow_zero, exact one_dvd _}) (λ n, h _), λ h n, h _⟩ }

@[simp] lemma is_unit_left {a : α} (b : α) (ha : is_unit a) : multiplicity a b = ⊤ :=
eq_top_iff.2 (λ _, is_unit_iff_forall_dvd.1 (ha.pow _) _)

lemma is_unit_right {a b : α} (ha : ¬is_unit a) (hb : is_unit b) :
  multiplicity a b = 0 :=
eq_coe_iff.2 ⟨show a ^ 0 ∣ b, by simp only [pow_zero, one_dvd],
  by { rw pow_one, exact λ h, mt (is_unit_of_dvd_unit h) ha hb }⟩

@[simp] lemma one_left (b : α) : multiplicity 1 b = ⊤ := is_unit_left b is_unit_one

lemma one_right {a : α} (ha : ¬is_unit a) : multiplicity a 1 = 0 := is_unit_right ha is_unit_one

@[simp] lemma get_one_right {a : α} (ha : finite a 1) : get (multiplicity a 1) ha = 0 :=
begin
  rw [enat.get_eq_iff_eq_coe, eq_coe_iff, pow_zero],
  simpa [is_unit_iff_dvd_one.symm] using not_unit_of_finite ha,
end

@[simp] lemma unit_left (a : α) (u : αˣ) : multiplicity (u : α) a = ⊤ :=
is_unit_left a u.is_unit

lemma unit_right {a : α} (ha : ¬is_unit a) (u : αˣ) : multiplicity a u = 0 :=
is_unit_right ha u.is_unit

lemma multiplicity_eq_zero_of_not_dvd {a b : α} (ha : ¬a ∣ b) : multiplicity a b = 0 :=
by { rw [← nat.cast_zero, eq_coe_iff], simpa }

lemma eq_top_iff_not_finite {a b : α} : multiplicity a b = ⊤ ↔ ¬ finite a b :=
part.eq_none_iff'

lemma ne_top_iff_finite {a b : α} : multiplicity a b ≠ ⊤ ↔ finite a b :=
by rw [ne.def, eq_top_iff_not_finite, not_not]

lemma lt_top_iff_finite {a b : α} : multiplicity a b < ⊤ ↔ finite a b :=
by rw [lt_top_iff_ne_top, ne_top_iff_finite]

lemma exists_eq_pow_mul_and_not_dvd {a b : α} (hfin : finite a b) :
  ∃ (c : α), b = a ^ ((multiplicity a b).get hfin) * c ∧ ¬ a ∣ c :=
begin
  obtain ⟨c, hc⟩ := multiplicity.pow_multiplicity_dvd hfin,
  refine ⟨c, hc, _⟩,
  rintro ⟨k, hk⟩,
  rw [hk, ← mul_assoc, ← pow_succ'] at hc,
  have h₁ : a ^ ((multiplicity a b).get hfin + 1) ∣ b := ⟨k, hc⟩,
  exact (multiplicity.eq_coe_iff.1 (by simp)).2 h₁,
end

open_locale classical

lemma multiplicity_le_multiplicity_iff {a b c d : α} : multiplicity a b ≤ multiplicity c d ↔
  (∀ n : ℕ, a ^ n ∣ b → c ^ n ∣ d) :=
⟨λ h n hab, (pow_dvd_of_le_multiplicity (le_trans (le_multiplicity_of_pow_dvd hab) h)),
  λ h, if hab : finite a b
    then by rw [← enat.coe_get (finite_iff_dom.1 hab)];
      exact le_multiplicity_of_pow_dvd (h _ (pow_multiplicity_dvd _))
    else
    have ∀ n : ℕ, c ^ n ∣ d, from λ n, h n (not_finite_iff_forall.1 hab _),
    by rw [eq_top_iff_not_finite.2 hab, eq_top_iff_not_finite.2
      (not_finite_iff_forall.2 this)]⟩

lemma multiplicity_le_multiplicity_of_dvd_left {a b c : α} (hdvd : a ∣ b) :
  multiplicity b c ≤ multiplicity a c :=
multiplicity_le_multiplicity_iff.2 $ λ n h, (pow_dvd_pow_of_dvd hdvd n).trans h

lemma eq_of_associated_left {a b c : α} (h : associated a b) :
  multiplicity b c = multiplicity a c :=
le_antisymm (multiplicity_le_multiplicity_of_dvd_left h.dvd)
  (multiplicity_le_multiplicity_of_dvd_left h.symm.dvd)

lemma multiplicity_le_multiplicity_of_dvd_right {a b c : α} (h : b ∣ c) :
  multiplicity a b ≤ multiplicity a c :=
multiplicity_le_multiplicity_iff.2 $ λ n hb, hb.trans h

lemma eq_of_associated_right {a b c : α} (h : associated b c) :
  multiplicity a b = multiplicity a c :=
le_antisymm (multiplicity_le_multiplicity_of_dvd_right h.dvd)
  (multiplicity_le_multiplicity_of_dvd_right h.symm.dvd)

lemma dvd_of_multiplicity_pos {a b : α} (h : (0 : enat) < multiplicity a b) : a ∣ b :=
begin
  rw ← pow_one a,
  apply pow_dvd_of_le_multiplicity,
  simpa only [nat.cast_one, enat.pos_iff_one_le] using h
end

lemma dvd_iff_multiplicity_pos {a b : α} : (0 : enat) < multiplicity a b ↔ a ∣ b :=
⟨dvd_of_multiplicity_pos,
  λ hdvd, lt_of_le_of_ne (zero_le _) (λ heq, is_greatest
    (show multiplicity a b < ↑1,
      by simpa only [heq, nat.cast_zero] using enat.coe_lt_coe.mpr zero_lt_one)
    (by rwa pow_one a))⟩

lemma finite_nat_iff {a b : ℕ} : finite a b ↔ (a ≠ 1 ∧ 0 < b) :=
begin
  rw [← not_iff_not, not_finite_iff_forall, not_and_distrib, ne.def,
    not_not, not_lt, nat.le_zero_iff],
  exact ⟨λ h, or_iff_not_imp_right.2 (λ hb,
    have ha : a ≠ 0, from λ ha, by simpa [ha] using h 1,
    by_contradiction (λ ha1 : a ≠ 1,
      have ha_gt_one : 1 < a, from
        lt_of_not_ge (λ ha', by { clear h, revert ha ha1, dec_trivial! }),
      not_lt_of_ge (le_of_dvd (nat.pos_of_ne_zero hb) (h b))
          (lt_pow_self ha_gt_one b))),
    λ h, by cases h; simp *⟩
end

alias dvd_iff_multiplicity_pos ↔ _ has_dvd.dvd.multiplicity_pos

end comm_monoid

section comm_monoid_with_zero

variable [comm_monoid_with_zero α]

lemma ne_zero_of_finite {a b : α} (h : finite a b) : b ≠ 0 :=
let ⟨n, hn⟩ := h in λ hb, by simpa [hb] using hn

variable [decidable_rel ((∣) : α → α → Prop)]

@[simp] protected lemma zero (a : α) : multiplicity a 0 = ⊤ :=
part.eq_none_iff.2 (λ n ⟨⟨k, hk⟩, _⟩, hk (dvd_zero _))

@[simp] lemma multiplicity_zero_eq_zero_of_ne_zero (a : α) (ha : a ≠ 0) : multiplicity 0 a = 0 :=
begin
  apply multiplicity.multiplicity_eq_zero_of_not_dvd,
  rwa zero_dvd_iff,
end

end comm_monoid_with_zero

section comm_semiring

variables [comm_semiring α] [decidable_rel ((∣) : α → α → Prop)]

lemma min_le_multiplicity_add {p a b : α} :
  min (multiplicity p a) (multiplicity p b) ≤ multiplicity p (a + b) :=
(le_total (multiplicity p a) (multiplicity p b)).elim
  (λ h, by rw [min_eq_left h, multiplicity_le_multiplicity_iff];
    exact λ n hn, dvd_add hn (multiplicity_le_multiplicity_iff.1 h n hn))
  (λ h, by rw [min_eq_right h, multiplicity_le_multiplicity_iff];
    exact λ n hn, dvd_add (multiplicity_le_multiplicity_iff.1 h n hn) hn)

end comm_semiring

section comm_ring

variables [comm_ring α] [decidable_rel ((∣) : α → α → Prop)]

open_locale classical

@[simp] protected lemma neg (a b : α) : multiplicity a (-b) = multiplicity a b :=
part.ext' (by simp only [multiplicity, enat.find, dvd_neg])
  (λ h₁ h₂, enat.coe_inj.1 (by rw [enat.coe_get]; exact
    eq.symm (unique ((dvd_neg _ _).2 (pow_multiplicity_dvd _))
      (mt (dvd_neg _ _).1 (is_greatest' _ (lt_succ_self _))))))

lemma multiplicity_add_of_gt {p a b : α} (h : multiplicity p b < multiplicity p a) :
  multiplicity p (a + b) = multiplicity p b :=
begin
  apply le_antisymm,
  { apply enat.le_of_lt_add_one,
    cases enat.ne_top_iff.mp (enat.ne_top_of_lt h) with k hk,
    rw [hk], rw_mod_cast [multiplicity_lt_iff_neg_dvd], intro h_dvd,
    rw [← dvd_add_iff_right] at h_dvd,
    apply multiplicity.is_greatest _ h_dvd, rw [hk], apply_mod_cast nat.lt_succ_self,
    rw [pow_dvd_iff_le_multiplicity, nat.cast_add, ← hk, nat.cast_one],
    exact enat.add_one_le_of_lt h },
  { convert min_le_multiplicity_add, rw [min_eq_right (le_of_lt h)] }
end

lemma multiplicity_sub_of_gt {p a b : α} (h : multiplicity p b < multiplicity p a) :
  multiplicity p (a - b) = multiplicity p b :=
by { rw [sub_eq_add_neg, multiplicity_add_of_gt]; rwa [multiplicity.neg] }

lemma multiplicity_add_eq_min {p a b : α} (h : multiplicity p a ≠ multiplicity p b) :
  multiplicity p (a + b) = min (multiplicity p a) (multiplicity p b) :=
begin
  rcases lt_trichotomy (multiplicity p a) (multiplicity p b) with hab|hab|hab,
  { rw [add_comm, multiplicity_add_of_gt hab, min_eq_left], exact le_of_lt hab },
  { contradiction },
  { rw [multiplicity_add_of_gt hab, min_eq_right], exact le_of_lt hab},
end

end comm_ring

section cancel_comm_monoid_with_zero

variables [cancel_comm_monoid_with_zero α]

lemma finite_mul_aux {p : α} (hp : prime p) : ∀ {n m : ℕ} {a b : α},
  ¬p ^ (n + 1) ∣ a → ¬p ^ (m + 1) ∣ b → ¬p ^ (n + m + 1) ∣ a * b
| n m := λ a b ha hb ⟨s, hs⟩,
  have p ∣ a * b, from ⟨p ^ (n + m) * s,
    by simp [hs, pow_add, mul_comm, mul_assoc, mul_left_comm]⟩,
  (hp.2.2 a b this).elim
    (λ ⟨x, hx⟩, have hn0 : 0 < n,
        from nat.pos_of_ne_zero (λ hn0, by clear _fun_match _fun_match; simpa [hx, hn0] using ha),
      have wf : (n - 1) < n, from tsub_lt_self hn0 dec_trivial,
      have hpx : ¬ p ^ (n - 1 + 1) ∣ x,
        from λ ⟨y, hy⟩, ha (hx.symm ▸ ⟨y, mul_right_cancel₀ hp.1
          $ by rw [tsub_add_cancel_of_le (succ_le_of_lt hn0)] at hy;
            simp [hy, pow_add, mul_comm, mul_assoc, mul_left_comm]⟩),
      have 1 ≤ n + m, from le_trans hn0 (nat.le_add_right n m),
      finite_mul_aux hpx hb ⟨s, mul_right_cancel₀ hp.1 begin
          rw [tsub_add_eq_add_tsub (succ_le_of_lt hn0), tsub_add_cancel_of_le this],
          clear _fun_match _fun_match finite_mul_aux,
          simp [*, mul_comm, mul_assoc, mul_left_comm, pow_add] at *
        end⟩)
    (λ ⟨x, hx⟩, have hm0 : 0 < m,
        from nat.pos_of_ne_zero (λ hm0, by clear _fun_match _fun_match; simpa [hx, hm0] using hb),
      have wf : (m - 1) < m, from tsub_lt_self hm0 dec_trivial,
      have hpx : ¬ p ^ (m - 1 + 1) ∣ x,
        from λ ⟨y, hy⟩, hb (hx.symm ▸ ⟨y, mul_right_cancel₀ hp.1
          $ by rw [tsub_add_cancel_of_le (succ_le_of_lt hm0)] at hy;
            simp [hy, pow_add, mul_comm, mul_assoc, mul_left_comm]⟩),
      finite_mul_aux ha hpx ⟨s, mul_right_cancel₀ hp.1 begin
          rw [add_assoc, tsub_add_cancel_of_le (succ_le_of_lt hm0)],
          clear _fun_match _fun_match finite_mul_aux,
          simp [*, mul_comm, mul_assoc, mul_left_comm, pow_add] at *
        end⟩)

lemma finite_mul {p a b : α} (hp : prime p) : finite p a → finite p b → finite p (a * b) :=
λ ⟨n, hn⟩ ⟨m, hm⟩, ⟨n + m, finite_mul_aux hp hn hm⟩

lemma finite_mul_iff {p a b : α} (hp : prime p) : finite p (a * b) ↔ finite p a ∧ finite p b :=
⟨λ h, ⟨finite_of_finite_mul_right h, finite_of_finite_mul_left h⟩,
  λ h, finite_mul hp h.1 h.2⟩

lemma finite_pow {p a : α} (hp : prime p) : Π {k : ℕ} (ha : finite p a), finite p (a ^ k)
| 0     ha := ⟨0, by simp [mt is_unit_iff_dvd_one.2 hp.2.1]⟩
| (k+1) ha := by rw [pow_succ]; exact finite_mul hp ha (finite_pow ha)

variable [decidable_rel ((∣) : α → α → Prop)]

@[simp] lemma multiplicity_self {a : α} (ha : ¬is_unit a) (ha0 : a ≠ 0) :
  multiplicity a a = 1 :=
by { rw ← nat.cast_one, exact
eq_coe_iff.2 ⟨by simp, λ ⟨b, hb⟩, ha (is_unit_iff_dvd_one.2
  ⟨b, mul_left_cancel₀ ha0 $ by { clear _fun_match,
    simpa [pow_succ, mul_assoc] using hb }⟩)⟩ }

@[simp] lemma get_multiplicity_self {a : α} (ha : finite a a) :
  get (multiplicity a a) ha = 1 :=
enat.get_eq_iff_eq_coe.2 (eq_coe_iff.2
  ⟨by simp, λ ⟨b, hb⟩,
    by rw [← mul_one a, pow_add, pow_one, mul_assoc, mul_assoc,
        mul_right_inj' (ne_zero_of_finite ha)] at hb;
      exact mt is_unit_iff_dvd_one.2 (not_unit_of_finite ha)
        ⟨b, by clear _fun_match; simp * at *⟩⟩)

protected lemma mul' {p a b : α} (hp : prime p)
  (h : (multiplicity p (a * b)).dom) :
  get (multiplicity p (a * b)) h =
  get (multiplicity p a) ((finite_mul_iff hp).1 h).1 +
  get (multiplicity p b) ((finite_mul_iff hp).1 h).2 :=
have hdiva : p ^ get (multiplicity p a) ((finite_mul_iff hp).1 h).1 ∣ a,
  from pow_multiplicity_dvd _,
have hdivb : p ^ get (multiplicity p b) ((finite_mul_iff hp).1 h).2 ∣ b,
  from pow_multiplicity_dvd _,
have hpoweq : p ^ (get (multiplicity p a) ((finite_mul_iff hp).1 h).1 +
    get (multiplicity p b) ((finite_mul_iff hp).1 h).2) =
    p ^ get (multiplicity p a) ((finite_mul_iff hp).1 h).1 *
    p ^ get (multiplicity p b) ((finite_mul_iff hp).1 h).2,
  by simp [pow_add],
have hdiv : p ^ (get (multiplicity p a) ((finite_mul_iff hp).1 h).1 +
    get (multiplicity p b) ((finite_mul_iff hp).1 h).2) ∣ a * b,
  by rw [hpoweq]; apply mul_dvd_mul; assumption,
have hsucc : ¬p ^ ((get (multiplicity p a) ((finite_mul_iff hp).1 h).1 +
    get (multiplicity p b) ((finite_mul_iff hp).1 h).2) + 1) ∣ a * b,
  from λ h, by exact
    not_or (is_greatest' _ (lt_succ_self _)) (is_greatest' _ (lt_succ_self _))
      (_root_.succ_dvd_or_succ_dvd_of_succ_sum_dvd_mul hp hdiva hdivb h),
by rw [← enat.coe_inj, enat.coe_get, eq_coe_iff];
  exact ⟨hdiv, hsucc⟩

open_locale classical

protected lemma mul {p a b : α} (hp : prime p) :
  multiplicity p (a * b) = multiplicity p a + multiplicity p b :=
if h : finite p a ∧ finite p b then
by rw [← enat.coe_get (finite_iff_dom.1 h.1), ← enat.coe_get (finite_iff_dom.1 h.2),
  ← enat.coe_get (finite_iff_dom.1 (finite_mul hp h.1 h.2)),
    ← nat.cast_add, enat.coe_inj, multiplicity.mul' hp]; refl
else begin
  rw [eq_top_iff_not_finite.2 (mt (finite_mul_iff hp).1 h)],
  cases not_and_distrib.1 h with h h;
    simp [eq_top_iff_not_finite.2 h]
end

lemma finset.prod {β : Type*} {p : α} (hp : prime p) (s : finset β) (f : β → α) :
  multiplicity p (∏ x in s, f x) = ∑ x in s, multiplicity p (f x) :=
begin
  classical,
  induction s using finset.induction with a s has ih h,
  { simp only [finset.sum_empty, finset.prod_empty],
    convert one_right hp.not_unit },
  { simp [has, ← ih],
    convert multiplicity.mul hp }
end

protected lemma pow' {p a : α} (hp : prime p) (ha : finite p a) : ∀ {k : ℕ},
  get (multiplicity p (a ^ k)) (finite_pow hp ha) = k * get (multiplicity p a) ha
| 0     := by simp [one_right hp.not_unit]
| (k+1) := have multiplicity p (a ^ (k + 1)) = multiplicity p (a * a ^ k), by rw pow_succ,
    by rw [get_eq_get_of_eq _ _ this, multiplicity.mul' hp, pow', add_mul, one_mul, add_comm]

lemma pow {p a : α} (hp : prime p) : ∀ {k : ℕ},
  multiplicity p (a ^ k) = k • (multiplicity p a)
| 0        := by simp [one_right hp.not_unit]
| (succ k) := by simp [pow_succ, succ_nsmul, pow, multiplicity.mul hp]

lemma multiplicity_pow_self {p : α} (h0 : p ≠ 0) (hu : ¬ is_unit p) (n : ℕ) :
  multiplicity p (p ^ n) = n :=
by { rw [eq_coe_iff], use dvd_rfl, rw [pow_dvd_pow_iff h0 hu], apply nat.not_succ_le_self }

lemma multiplicity_pow_self_of_prime {p : α} (hp : prime p) (n : ℕ) :
  multiplicity p (p ^ n) = n :=
multiplicity_pow_self hp.ne_zero hp.not_unit n

end cancel_comm_monoid_with_zero

section valuation

variables {R : Type*} [comm_ring R] [is_domain R] {p : R}
  [decidable_rel (has_dvd.dvd : R → R → Prop)]

/-- `multiplicity` of a prime inan integral domain as an additive valuation to `enat`. -/
noncomputable def add_valuation (hp : prime p) : add_valuation R enat :=
add_valuation.of (multiplicity p) (multiplicity.zero _) (one_right hp.not_unit)
  (λ _ _, min_le_multiplicity_add) (λ a b, multiplicity.mul hp)

@[simp]
lemma add_valuation_apply {hp : prime p} {r : R} : add_valuation hp r = multiplicity p r := rfl

end valuation

end multiplicity

section nat
open multiplicity

lemma multiplicity_eq_zero_of_coprime {p a b : ℕ} (hp : p ≠ 1)
  (hle : multiplicity p a ≤ multiplicity p b)
  (hab : nat.coprime a b) : multiplicity p a = 0 :=
begin
  rw [multiplicity_le_multiplicity_iff] at hle,
  rw [← nonpos_iff_eq_zero, ← not_lt, enat.pos_iff_one_le, ← nat.cast_one,
    ← pow_dvd_iff_le_multiplicity],
  assume h,
  have := nat.dvd_gcd h (hle _ h),
  rw [coprime.gcd_eq_one hab, nat.dvd_one, pow_one] at this,
  exact hp this
end

lemma multiplicity_eq_factorization {n p : ℕ} (pp : p.prime) (hn : n ≠ 0) :
  multiplicity p n = n.factorization p :=
multiplicity.eq_coe_iff.mpr ⟨pow_factorization_dvd n p, pow_succ_factorization_not_dvd hn pp⟩

end nat


import Math.CategoryTheory.NatTrans
import Math.Data.Equiv

namespace CategoryTheory

open Category

variable [Category α] [Category β]

structure Adjunction (F : Functor α β) (G : Functor β α) where
  equiv : ∀ (X : α) (Y : β), hom (F.obj X) Y ≃ hom X (G.obj Y)
  natural : ∀ (f : hom X X') (g : hom Y Y') (h : hom (F.obj X') Y), G.map g ∘ (equiv _ _).map h ∘ f = (equiv _ _).map (g ∘ h ∘ F.map f)

infix:80 " ⊣ " => Adjunction

namespace Adjunction

variable {F : Functor α β} {G : Functor β α}
variable (adj : F ⊣ G)

def tr  (f : hom (F.obj X) Y) := (adj.equiv _ _).map f
def tr' (g : hom X (G.obj Y)) := (adj.equiv _ _).inv g

@[simp]
theorem tr_tr'_inv (g : hom X (G.obj Y)) : adj.tr (adj.tr' g) = g := by unfold tr', tr; simp;

@[simp]
theorem tr'_tr_inv (f : hom (F.obj X) Y) : adj.tr' (adj.tr f) = f := by unfold tr', tr; simp;

@[simp]
theorem natural₁ (f : hom X X') (h : hom (F.obj X') Y) : adj.tr h ∘ f = adj.tr (h ∘ F.map f) := by {
  have q := adj.natural f (𝟙 Y) h; simp at q; exact q;
}

@[simp]
theorem natural₂ (g : hom Y Y') (h : hom (F.obj X) Y) : G.map g ∘ adj.tr h = adj.tr (g ∘ h) := by {
  have q := adj.natural (𝟙 X) g h; simp at q; exact q;
}

theorem natural' (f : hom X X') (g : hom Y Y') (h : hom X' (G.obj Y)) : g ∘ (adj.tr' h ∘ F.map f) = adj.tr' (G.map g ∘ h ∘ f) := by {
  have q : adj.tr' (adj.tr (g ∘ adj.tr' h ∘ F.map f)) = adj.tr' (adj.tr (adj.tr' (G.map g ∘ h ∘ f))) := by {
    rw [← adj.natural₂, ← adj.natural₁];
    simp;
  }
  simp at q;
  exact q;
}

@[simp]
theorem natural'₁ (f : hom X X') (h : hom X' (G.obj Y)) : adj.tr' h ∘ F.map f = adj.tr' (h ∘ f) := by {
  have q := adj.natural' f (𝟙 Y) h; simp at q; exact q;
}

@[simp]
theorem natural'₂ (g : hom Y Y') (h : hom X (G.obj Y)) : g ∘ (adj.tr' h) = adj.tr' (G.map g ∘ h) := by {
  have q := natural' adj (𝟙 X) g h; simp at q; exact q;
}

end Adjunction

end CategoryTheory


-- Basics about analytic functions (general field case)

import analysis.analytic.basic
import analysis.analytic.composition
import analysis.analytic.linear
import analysis.calculus.fderiv_analytic
import analysis.calculus.formal_multilinear_series
import analysis.complex.cauchy_integral
import data.complex.basic
import data.finset.basic
import data.real.basic
import data.real.ennreal
import data.real.nnreal
import data.real.pi.bounds
import data.set.basic
import topology.basic

import bounds
import multilinear
import simple
import tactics
import topology

open complex (abs exp I log)
open filter (at_top)
open metric (ball closed_ball sphere is_open_ball)
open linear_order (min)
open_locale real nnreal ennreal topological_space
noncomputable theory

variables {𝕜 : Type} [nontrivially_normed_field 𝕜]
variables {E : Type} [normed_add_comm_group E] [normed_space 𝕜 E] [complete_space E]
variables {F : Type} [normed_add_comm_group F] [normed_space 𝕜 F] [complete_space F]

-- A function is entire iff it is analytic everywhere
def entire (𝕜 : Type) {E F : Type}
    [nontrivially_normed_field 𝕜] [normed_add_comm_group E] [normed_space 𝕜 E] [normed_add_comm_group F] [normed_space 𝕜 F]
    (f : E → F) := ∀ z, analytic_at 𝕜 f z

-- Entire functions are analytic on all sets
lemma entire.analytic_on {f : E → F} {s : set E} (e : entire 𝕜 f) : analytic_on 𝕜 f s := λ x xs, e x

-- Infinite radius of convergence implies entire
lemma radius_inf_to_entire {f : E → F} (p : formal_multilinear_series 𝕜 E F) (z : E)
    : has_fpower_series_on_ball f p z ∞ → entire 𝕜 f := begin
  intros h w,
  refine has_fpower_series_on_ball.analytic_at_of_mem h _,
  rw emetric.mem_ball, exact edist_lt_top w z
end

-- Analytic functions have derivatives
lemma analytic_at.has_deriv_at {f : 𝕜 → E} {z : 𝕜}
    : analytic_at 𝕜 f z → has_deriv_at f (deriv f z) z := begin
  intro a,
  have dwa : differentiable_within_at 𝕜 f set.univ z := analytic_at.differentiable_within_at a,
  refine (dwa.differentiable_at _).has_deriv_at,
  exact is_open.mem_nhds is_open_univ (set.mem_univ z),
end

-- Zero is entire
theorem entire.zero : entire 𝕜 (λ _ : E, (0 : F)) := (0 : E →L[𝕜] F).analytic_at

-- id is entire
theorem entire.id : entire 𝕜 (λ x : E, x) := (continuous_linear_map.id 𝕜 E).analytic_at

-- Finite sums of analytic functions are analytic
theorem analytic_at.sum {f : ℕ → E → F} {c : E}
    (h : ∀ n, analytic_at 𝕜 (f n) c) (N : finset ℕ) : analytic_at 𝕜 (λ z, N.sum (λ n, f n z)) c := begin
  induction N using finset.induction with a B aB hB, {
    simp, exact entire.zero c
  }, {
    simp_rw finset.sum_insert aB,
    apply analytic_at.add,
    exact h a,
    exact hB
  },
end
theorem analytic.sum {f : ℕ → E → F} {s : set E}
    (h : ∀ n, analytic_on 𝕜 (f n) s) (N : finset ℕ) : analytic_on 𝕜 (λ z, N.sum (λ n, f n z)) s :=
  λ z zs, analytic_at.sum (λ n, h n z zs) N

-- Power series terms are analytic
theorem change_origin.analytic_at (p : formal_multilinear_series 𝕜 E F) (rp : p.radius > 0) (n : ℕ)
    : analytic_at 𝕜 (λ x, p.change_origin x n) 0 :=
  (formal_multilinear_series.has_fpower_series_on_ball_change_origin p n rp).analytic_at

-- analytic_on is monotonic in the set
lemma analytic_on.mono {f : E → F} {sa sb : set E} (fa : analytic_on 𝕜 f sb) (s : sa ⊆ sb)
    : analytic_on 𝕜 f sa := λ x xa, fa x (s xa)

-- Analytic at a point means analytic in a small ball
theorem analytic_at.ball {f : E → F} {z : E}
    : analytic_at 𝕜 f z → ∃ (r : ℝ), r > 0 ∧ analytic_on 𝕜 f (ball z r) := begin
  intro a,
  rcases a with ⟨p,r,h⟩,
  by_cases ri : r = ∞, {
    existsi (1 : ℝ),
    exact ⟨by norm_num, λ z zs, has_fpower_series_on_ball.analytic_on h z (by {rw ri, simp})⟩
  }, {
    existsi r.to_real,
    constructor, {
      exact ennreal.to_real_pos (ne_of_gt h.r_pos) ri
    }, {
      intros z zs,
      refine has_fpower_series_on_ball.analytic_on h z _,
      simp at zs ⊢,
      have rr := ennreal.of_real_to_real ri,
      rw [←rr, edist_lt_of_real], assumption
    }
  }
end

-- Analytic at a point means analytic in a small closed ball
theorem analytic_at.cball {f : E → F} {z : E}
    : analytic_at 𝕜 f z → ∃ (r : ℝ), r > 0 ∧ analytic_on 𝕜 f (closed_ball z r) := begin
  intro a,
  rcases analytic_at.ball a with ⟨r, rp, ao⟩,
  existsi r/2,
  constructor, {
    bound
  }, {
    intros z zs,
    refine ao z _,
    simp at ⊢ zs,
    exact lt_of_le_of_lt zs (by bound)
  }
end

-- analytic_on depends only on values on s (→ version)
lemma analytic_on.congr {f g : E → F} {s : set E}
    (fa : analytic_on 𝕜 f s) (o : is_open s) (fg : ∀ z, z ∈ s → f z = g z)
    : analytic_on 𝕜 g s := begin
  intros z zs,
  rcases metric.is_open_iff.mp o z zs with ⟨r0,r0p,r0s⟩,
  rcases fa z zs with ⟨p,r1,fp⟩,
  existsi p, existsi (min (ennreal.of_real r0) r1),
  refine { r_le := trans (min_le_right _ _) fp.r_le,
           r_pos := lt_min_iff.mpr ⟨ennreal.of_real_pos.mpr r0p,fp.r_pos⟩,
           has_sum := _ },
  intros w wr, simp at wr,
  specialize fg (z + w) _, {
    apply r0s,
    simp, exact wr.left
  }, {
    rw ←fg, refine fp.has_sum _,
    simp, exact wr.right
  },
end

-- analytic_on depends only on values on s (↔ version)
lemma analytic_on_congr {f g : E → F} {s : set E}
    (o : is_open s) (fg : ∀ z, z ∈ s → f z = g z)
    : analytic_on 𝕜 f s ↔ analytic_on 𝕜 g s :=
  ⟨λ fa, fa.congr o fg, λ ga, ga.congr o (λ z zs, (fg z zs).symm)⟩

-- analytic_at depends only on values near c (→ version)
lemma analytic_at.congr {f g : E → F} {x : E}
    (fa : analytic_at 𝕜 f x) (h : f =ᶠ[nhds x] g) : analytic_at 𝕜 g x := begin
  rcases metric.eventually_nhds_iff_ball.mp h with ⟨e,ep,es⟩,
  rcases fa.ball with ⟨r,rp,fr⟩,
  have fer : analytic_on 𝕜 f (ball x (min e r)) := fr.mono (metric.ball_subset_ball (by bound)),
  have es' : ∀ y, y ∈ ball x (min e r) → f y = g y :=
    λ y yer, es y (metric.ball_subset_ball (by bound) yer),
  exact fer.congr is_open_ball es' x (metric.mem_ball_self (by bound)),
end

-- analytic_at depends only on values near c (↔ version)
lemma analytic_at_congr {f g : E → F} {x : E} (h : f =ᶠ[nhds x] g) : analytic_at 𝕜 f x ↔ analytic_at 𝕜 g x :=
  ⟨λ fa, fa.congr h, λ ga, ga.congr h.symm⟩


/-
Copyright (c) 2022 Vincent Beffara. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Vincent Beffara

! This file was ported from Lean 3 source module analysis.complex.locally_uniform_limit
! leanprover-community/mathlib commit fe44cd36149e675eb5dec87acc7e8f1d6568e081
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Complex.RemovableSingularity
import Mathbin.Analysis.Calculus.Series

/-!
# Locally uniform limits of holomorphic functions

This file gathers some results about locally uniform limits of holomorphic functions on an open
subset of the complex plane.

## Main results

* `tendsto_locally_uniformly_on.differentiable_on`: A locally uniform limit of holomorphic functions
  is holomorphic.
* `tendsto_locally_uniformly_on.deriv`: Locally uniform convergence implies locally uniform
  convergence of the derivatives to the derivative of the limit.
-/

open Set Metric MeasureTheory Filter Complex intervalIntegral

open Real Topology

variable {E ι : Type _} [NormedAddCommGroup E] [NormedSpace ℂ E] [CompleteSpace E] {U K : Set ℂ}
  {z : ℂ} {M r δ : ℝ} {φ : Filter ι} {F : ι → ℂ → E} {f g : ℂ → E}

namespace Complex

section Cderiv

/-- A circle integral which coincides with `deriv f z` whenever one can apply the Cauchy formula for
the derivative. It is useful in the proof that locally uniform limits of holomorphic functions are
holomorphic, because it depends continuously on `f` for the uniform topology. -/
noncomputable def cderiv (r : ℝ) (f : ℂ → E) (z : ℂ) : E :=
  (2 * π * I : ℂ)⁻¹ • ∮ w in C(z, r), ((w - z) ^ 2)⁻¹ • f w
#align complex.cderiv Complex.cderiv

theorem cderiv_eq_deriv (hU : IsOpen U) (hf : DifferentiableOn ℂ f U) (hr : 0 < r)
    (hzr : closedBall z r ⊆ U) : cderiv r f z = deriv f z :=
  two_pi_i_inv_smul_circleIntegral_sub_sq_inv_smul_of_differentiable hU hzr hf (mem_ball_self hr)
#align complex.cderiv_eq_deriv Complex.cderiv_eq_deriv

theorem norm_cderiv_le (hr : 0 < r) (hf : ∀ w ∈ sphere z r, ‖f w‖ ≤ M) : ‖cderiv r f z‖ ≤ M / r :=
  by
  have hM : 0 ≤ M :=
    by
    obtain ⟨w, hw⟩ : (sphere z r).Nonempty := normed_space.sphere_nonempty.mpr hr.le
    exact (norm_nonneg _).trans (hf w hw)
  have h1 : ∀ w ∈ sphere z r, ‖((w - z) ^ 2)⁻¹ • f w‖ ≤ M / r ^ 2 :=
    by
    intro w hw
    simp only [mem_sphere_iff_norm, norm_eq_abs] at hw
    simp only [norm_smul, inv_mul_eq_div, hw, norm_eq_abs, map_inv₀, Complex.abs_pow]
    exact div_le_div hM (hf w hw) (sq_pos_of_pos hr) le_rfl
  have h2 := circleIntegral.norm_integral_le_of_norm_le_const hr.le h1
  simp only [cderiv, norm_smul]
  refine' (mul_le_mul le_rfl h2 (norm_nonneg _) (norm_nonneg _)).trans (le_of_eq _)
  field_simp [_root_.abs_of_nonneg real.pi_pos.le, real.pi_pos.ne.symm, hr.ne.symm]
  ring
#align complex.norm_cderiv_le Complex.norm_cderiv_le

theorem cderiv_sub (hr : 0 < r) (hf : ContinuousOn f (sphere z r))
    (hg : ContinuousOn g (sphere z r)) : cderiv r (f - g) z = cderiv r f z - cderiv r g z :=
  by
  have h1 : ContinuousOn (fun w : ℂ => ((w - z) ^ 2)⁻¹) (sphere z r) :=
    by
    refine' ((continuous_id'.sub continuous_const).pow 2).ContinuousOn.inv₀ fun w hw h => hr.ne _
    rwa [mem_sphere_iff_norm, sq_eq_zero_iff.mp h, norm_zero] at hw
  simp_rw [cderiv, ← smul_sub]
  congr 1
  simpa only [Pi.sub_apply, smul_sub] using
    circleIntegral.integral_sub ((h1.smul hf).CircleIntegrable hr.le)
      ((h1.smul hg).CircleIntegrable hr.le)
#align complex.cderiv_sub Complex.cderiv_sub

theorem norm_cderiv_lt (hr : 0 < r) (hfM : ∀ w ∈ sphere z r, ‖f w‖ < M)
    (hf : ContinuousOn f (sphere z r)) : ‖cderiv r f z‖ < M / r :=
  by
  obtain ⟨L, hL1, hL2⟩ : ∃ L < M, ∀ w ∈ sphere z r, ‖f w‖ ≤ L :=
    by
    have e1 : (sphere z r).Nonempty := normed_space.sphere_nonempty.mpr hr.le
    have e2 : ContinuousOn (fun w => ‖f w‖) (sphere z r) := continuous_norm.comp_continuous_on hf
    obtain ⟨x, hx, hx'⟩ := (isCompact_sphere z r).exists_forall_ge e1 e2
    exact ⟨‖f x‖, hfM x hx, hx'⟩
  exact (norm_cderiv_le hr hL2).trans_lt ((div_lt_div_right hr).mpr hL1)
#align complex.norm_cderiv_lt Complex.norm_cderiv_lt

theorem norm_cderiv_sub_lt (hr : 0 < r) (hfg : ∀ w ∈ sphere z r, ‖f w - g w‖ < M)
    (hf : ContinuousOn f (sphere z r)) (hg : ContinuousOn g (sphere z r)) :
    ‖cderiv r f z - cderiv r g z‖ < M / r :=
  cderiv_sub hr hf hg ▸ norm_cderiv_lt hr hfg (hf.sub hg)
#align complex.norm_cderiv_sub_lt Complex.norm_cderiv_sub_lt

theorem TendstoUniformlyOn.cderiv (hF : TendstoUniformlyOn F f φ (cthickening δ K)) (hδ : 0 < δ)
    (hFn : ∀ᶠ n in φ, ContinuousOn (F n) (cthickening δ K)) :
    TendstoUniformlyOn (cderiv δ ∘ F) (cderiv δ f) φ K :=
  by
  by_cases φ = ⊥
  · simp only [h, TendstoUniformlyOn, eventually_bot, imp_true_iff]
  haveI : φ.ne_bot := ne_bot_iff.2 h
  have e1 : ContinuousOn f (cthickening δ K) := TendstoUniformlyOn.continuousOn hF hFn
  rw [tendsto_uniformly_on_iff] at hF⊢
  rintro ε hε
  filter_upwards [hF (ε * δ) (mul_pos hε hδ), hFn]with n h h' z hz
  simp_rw [dist_eq_norm] at h⊢
  have e2 : ∀ w ∈ sphere z δ, ‖f w - F n w‖ < ε * δ := fun w hw1 =>
    h w (closed_ball_subset_cthickening hz δ (sphere_subset_closed_ball hw1))
  have e3 := sphere_subset_closed_ball.trans (closed_ball_subset_cthickening hz δ)
  have hf : ContinuousOn f (sphere z δ) :=
    e1.mono (sphere_subset_closed_ball.trans (closed_ball_subset_cthickening hz δ))
  simpa only [mul_div_cancel _ hδ.ne.symm] using norm_cderiv_sub_lt hδ e2 hf (h'.mono e3)
#align complex.tendsto_uniformly_on.cderiv Complex.TendstoUniformlyOn.cderiv

end Cderiv

section Weierstrass

theorem tendstoUniformlyOn_deriv_of_cthickening_subset (hf : TendstoLocallyUniformlyOn F f φ U)
    (hF : ∀ᶠ n in φ, DifferentiableOn ℂ (F n) U) {δ : ℝ} (hδ : 0 < δ) (hK : IsCompact K)
    (hU : IsOpen U) (hKU : cthickening δ K ⊆ U) : TendstoUniformlyOn (deriv ∘ F) (cderiv δ f) φ K :=
  by
  have h1 : ∀ᶠ n in φ, ContinuousOn (F n) (cthickening δ K) := by
    filter_upwards [hF]with n h using h.continuous_on.mono hKU
  have h2 : IsCompact (cthickening δ K) :=
    is_compact_of_is_closed_bounded is_closed_cthickening hK.bounded.cthickening
  have h3 : TendstoUniformlyOn F f φ (cthickening δ K) :=
    (tendstoLocallyUniformlyOn_iff_forall_isCompact hU).mp hf (cthickening δ K) hKU h2
  apply (h3.cderiv hδ h1).congr
  filter_upwards [hF]with n h z hz
  exact cderiv_eq_deriv hU h hδ ((closed_ball_subset_cthickening hz δ).trans hKU)
#align complex.tendsto_uniformly_on_deriv_of_cthickening_subset Complex.tendstoUniformlyOn_deriv_of_cthickening_subset

theorem exists_cthickening_tendstoUniformlyOn (hf : TendstoLocallyUniformlyOn F f φ U)
    (hF : ∀ᶠ n in φ, DifferentiableOn ℂ (F n) U) (hK : IsCompact K) (hU : IsOpen U) (hKU : K ⊆ U) :
    ∃ δ > 0, cthickening δ K ⊆ U ∧ TendstoUniformlyOn (deriv ∘ F) (cderiv δ f) φ K :=
  by
  obtain ⟨δ, hδ, hKδ⟩ := hK.exists_cthickening_subset_open hU hKU
  exact ⟨δ, hδ, hKδ, tendsto_uniformly_on_deriv_of_cthickening_subset hf hF hδ hK hU hKδ⟩
#align complex.exists_cthickening_tendsto_uniformly_on Complex.exists_cthickening_tendstoUniformlyOn

/-- A locally uniform limit of holomorphic functions on an open domain of the complex plane is
holomorphic (the derivatives converge locally uniformly to that of the limit, which is proved
as `tendsto_locally_uniformly_on.deriv`). -/
theorem TendstoLocallyUniformlyOn.differentiableOn [φ.ne_bot]
    (hf : TendstoLocallyUniformlyOn F f φ U) (hF : ∀ᶠ n in φ, DifferentiableOn ℂ (F n) U)
    (hU : IsOpen U) : DifferentiableOn ℂ f U :=
  by
  rintro x hx
  obtain ⟨K, ⟨hKx, hK⟩, hKU⟩ := (compact_basis_nhds x).mem_iff.mp (hU.mem_nhds hx)
  obtain ⟨δ, hδ, -, h1⟩ := exists_cthickening_tendsto_uniformly_on hf hF hK hU hKU
  have h2 : interior K ⊆ U := interior_subset.trans hKU
  have h3 : ∀ᶠ n in φ, DifferentiableOn ℂ (F n) (interior K)
  filter_upwards [hF]with n h using h.mono h2
  have h4 : TendstoLocallyUniformlyOn F f φ (interior K) := hf.mono h2
  have h5 : TendstoLocallyUniformlyOn (deriv ∘ F) (cderiv δ f) φ (interior K) :=
    h1.tendsto_locally_uniformly_on.mono interior_subset
  have h6 : ∀ x ∈ interior K, HasDerivAt f (cderiv δ f x) x := fun x h =>
    hasDerivAt_of_tendsto_locally_uniformly_on' isOpen_interior h5 h3 (fun _ => h4.tendsto_at) h
  have h7 : DifferentiableOn ℂ f (interior K) := fun x hx =>
    (h6 x hx).DifferentiableAt.DifferentiableWithinAt
  exact (h7.differentiable_at (interior_mem_nhds.mpr hKx)).DifferentiableWithinAt
#align tendsto_locally_uniformly_on.differentiable_on TendstoLocallyUniformlyOn.differentiableOn

theorem TendstoLocallyUniformlyOn.deriv (hf : TendstoLocallyUniformlyOn F f φ U)
    (hF : ∀ᶠ n in φ, DifferentiableOn ℂ (F n) U) (hU : IsOpen U) :
    TendstoLocallyUniformlyOn (deriv ∘ F) (deriv f) φ U :=
  by
  rw [tendstoLocallyUniformlyOn_iff_forall_isCompact hU]
  by_cases φ = ⊥
  · simp only [h, TendstoUniformlyOn, eventually_bot, imp_true_iff]
  haveI : φ.ne_bot := ne_bot_iff.2 h
  rintro K hKU hK
  obtain ⟨δ, hδ, hK4, h⟩ := exists_cthickening_tendsto_uniformly_on hf hF hK hU hKU
  refine' h.congr_right fun z hz => cderiv_eq_deriv hU (hf.differentiable_on hF hU) hδ _
  exact (closed_ball_subset_cthickening hz δ).trans hK4
#align tendsto_locally_uniformly_on.deriv TendstoLocallyUniformlyOn.deriv

end Weierstrass

section Tsums

/-- If the terms in the sum `∑' (i : ι), F i` are uniformly bounded on `U` by a
summable function, and each term in the sum is differentiable on `U`, then so is the sum. -/
theorem differentiableOn_tsum_of_summable_norm {u : ι → ℝ} (hu : Summable u)
    (hf : ∀ i : ι, DifferentiableOn ℂ (F i) U) (hU : IsOpen U)
    (hF_le : ∀ (i : ι) (w : ℂ), w ∈ U → ‖F i w‖ ≤ u i) :
    DifferentiableOn ℂ (fun w : ℂ => ∑' i : ι, F i w) U := by
  classical
    have hc := (tendstoUniformlyOn_tsum hu hF_le).TendstoLocallyUniformlyOn
    refine' hc.differentiable_on (eventually_of_forall fun s => _) hU
    exact DifferentiableOn.sum fun i hi => hf i
#align complex.differentiable_on_tsum_of_summable_norm Complex.differentiableOn_tsum_of_summable_norm

/-- If the terms in the sum `∑' (i : ι), F i` are uniformly bounded on `U` by a
summable function, then the sum of `deriv F i` at a point in `U` is the derivative of the
sum. -/
theorem hasSum_deriv_of_summable_norm {u : ι → ℝ} (hu : Summable u)
    (hf : ∀ i : ι, DifferentiableOn ℂ (F i) U) (hU : IsOpen U)
    (hF_le : ∀ (i : ι) (w : ℂ), w ∈ U → ‖F i w‖ ≤ u i) (hz : z ∈ U) :
    HasSum (fun i : ι => deriv (F i) z) (deriv (fun w : ℂ => ∑' i : ι, F i w) z) :=
  by
  rw [HasSum]
  have hc := (tendstoUniformlyOn_tsum hu hF_le).TendstoLocallyUniformlyOn
  convert(hc.deriv (eventually_of_forall fun s => DifferentiableOn.sum fun i hi => hf i)
          hU).tendsto_at
      hz using
    1
  ext1 s
  exact (deriv_sum fun i hi => (hf i).DifferentiableAt (hU.mem_nhds hz)).symm
#align complex.has_sum_deriv_of_summable_norm Complex.hasSum_deriv_of_summable_norm

end Tsums

end Complex