open classical

variables p q r s : Prop

-- commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p := begin
    apply iff.intro,
    intro h,
    split,
        apply and.right,
        exact h,
        apply and.left,
        exact h,
    intro h,
    split,
        apply and.right,
        exact h,
    apply and.left,
    exact h,
end

example : p ∨ q ↔ q ∨ p := begin
    apply iff.intro,
    intro h,
    cases h with hp hq,
    right,
    exact hp,
    left,
    exact hq,
    intro h,
    cases h with hq hp,
    right,
    exact hq,
    left,
    exact hp,
end

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) := begin
    apply iff.intro,
    intro h,
    apply and.intro,
    apply and.left,
    apply and.left,
    exact h,
    apply and.intro,
    exact (and.right (and.left h)),
    exact (and.right h),
    intro h,
    split,
    split,
    exact (and.left h),
    exact (and.left (and.right h)),
    exact (and.right (and.right h))
end

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) := begin
    split,
    intro h,
    cases h with a b,
    cases a with c d,
    left, assumption,
    right, left, assumption,
    right, right, assumption,
    intro h,
    cases h with a b,
    left, left, assumption,
    cases b with c d,
    left, right, assumption,
    right, assumption
end

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) := begin
    split,
    intros,
    have h : q ∨ r, from and.right a,
    cases h with hq hr,
    left,
    split,
    exact (and.left a),
    assumption,
    right,
    split,
    exact (and.left a),
    assumption,
    intros,
    cases a,
    split,
    exact (and.left a),
    left,
    exact (and.right a),
    split,
    exact (and.left a),
    right,
    exact (and.right a),
end

example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) := begin
    split,
    intros,
    split,
    cases a with b c,
    apply or.inl,
    assumption,
    apply or.inr,
    apply and.left,
    assumption,
    cases a with b c,
    apply or.inl,
    assumption,
    exact or.inr (and.right c),
    intro a,
    have h : p ∨ q,
    from and.left a,
    have i : p ∨ r,
    from and.right a,
    cases h with b c,
    exact or.inl b,
    cases i with d e,
    exact or.inl d,
    exact or.inr (and.intro c e)
end

-- other properties
example : (p → (q → r)) ↔ (p ∧ q → r) := begin
    split,
    intros h i,
    simp *,
    intros h i j,
    simp *
end

example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) := begin
    split,
    show ((p ∨ q) → r) → (p → r) ∧ (q → r), begin
        intro a,
        split,
        intro b,
        simp *,
        intro b,
        simp *,
    end,
    show (p → r) ∧ (q → r) → ((p ∨ q) → r), begin
        intros a b,
        have al : p → r,
        from and.left a,
        have ar : q → r,
        from and.right a,
        exact or.elim b al ar,
    end
end

example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := sorry
example : ¬p ∨ ¬q → ¬(p ∧ q) := sorry
example : ¬(p ∧ ¬p) := sorry
example : p ∧ ¬q → ¬(p → q) := sorry
example : ¬p → (p → q) := sorry
example : (¬p ∨ q) → (p → q) := sorry
example : p ∨ false ↔ p := sorry
example : p ∧ false ↔ false := sorry
example : ¬(p ↔ ¬p) := sorry
example : (p → q) → (¬q → ¬p) := sorry

-- these require classical reasoning
open classical

example : (p → r ∨ s) → ((p → r) ∨ (p → s)) := sorry
example : ¬(p ∧ q) → ¬p ∨ ¬q := sorry
example : ¬(p → q) → p ∧ ¬q := sorry
example : (p → q) → (¬p ∨ q) := sorry
example : (¬q → ¬p) → (p → q) := sorry
example : p ∨ ¬p := begin
    exact em p
end

example : (((p → q) → p) → p) :=
    λ a : (p → q) → p,
    begin
        have h : p ∨ ¬ p, from em p,
        cases h with hl hr,
        show p,
            from hl,
        show p,
            from a (λb : p, false.elim (hr b))
    end


import MyNat
import MyNat.proposition_world

open MyNat

example (P Q : Prop) (p : P) (q : Q) : P ∧ Q := by
  constructor
  exact p
  exact q

lemma and_symm (P Q : Prop) : P ∧ Q → Q ∧ P := by
  intro h
  cases h with
  | intro p q =>
    constructor
    exact q
    exact p

lemma and_trans (P Q R : Prop) : P ∧ Q → Q ∧ R → P ∧ R := by
  intro h1 h2
  cases h1 with
  | intro p q =>
  cases h2 with
  | intro q r =>
  constructor
  exact p
  exact r

lemma iff_trans (P Q R : Prop) : (P ↔ Q) → (Q ↔ R) → (P ↔ R) := by
  intro h1 h2
  cases h1 with
  | intro pq qp =>
  cases h2 with
  | intro qr rq =>
  constructor
  intro p
  exact (qr (pq p))
  intro r
  exact (qp (rq r))

example (P Q R : Prop) : (P ↔ Q) → (Q ↔ R) → (P ↔ R) := by
  intro hpq hqr
  constructor
  intro p
  apply (Iff.mp hqr)
  apply (Iff.mp hpq)
  exact p
  intro r
  rewrite [hpq, hqr]
  exact r

example (P Q : Prop) : Q → (P ∨ Q) := by
 intro q
 exact (Or.inr q)

lemma or_symm (P Q : Prop) : P ∨ Q -> Q ∨ P := by
  intro pq
  exact (
    Or.elim
    pq
    (Or.inr)
    (Or.inl)
  )

lemma alexs_discovery (P Q R : Prop) :
  P → (P ∨ Q) ∧ (P ∨ R) := by
  intro p
  exact ⟨Or.inl p, Or.inl p⟩

lemma and_or_distrib_left (P Q R : Prop) :
  P ∧ (Q ∨ R) ↔ (P ∧ Q) ∨ (P ∧ R) := by
  constructor
  intro p_and_qr
  let p := (p_and_qr.left)
  let qr := (p_and_qr.right)
  exact (
    Or.elim qr
    (fun q => Or.inl (And.intro p q))
    (fun r => Or.inr (And.intro p r))
  )
  intro p_and_q_or_p_and_r
  exact (
    Or.elim p_and_q_or_p_and_r
    (fun pq =>
      And.intro pq.left (Or.inl pq.right)
    )
    (fun pr =>
      And.intro pr.left (Or.inr pr.right)
    )
  )

lemma contra (P Q : Prop) : (P ∧ ¬ P) → Q := by
  intro pandnotp
  let p := pandnotp.left
  let notp := pandnotp.right
  -- rewrite [not_iff_imp_false] at notp
  -- let false := (notp p)
  -- exact False.elim false
  exact (absurd p notp)

lemma contrapositive2 (P Q : Prop) :
  (¬Q → ¬P) → (P → Q) := by
  intro h p
  by_cases p : P
  case inl p' =>
    . by_cases q : Q
      . case inl => exact q
      . case inr => exact absurd p (h q)
  case inr p' =>
    . by_cases q : Q
      . case _ => exact q
      . case inr => exact absurd p' p

lemma full_contrapositive (P Q : Prop) :
  (¬Q → ¬P) ↔ (P → Q) := by
  constructor
  exact contrapositive2 P Q
  exact contrapositive  P Q


/- LoVe Demo 2: Tactical Proofs -/

import .love01_definitions_and_lemma_statements_demo

namespace LoVe

/- Tactic Mode -/

lemma fst_of_two_props :
  ∀a b : Prop, a → b → a :=
begin
  intros a b,
  intros ha hb,
  apply ha
end

lemma fst_of_two_props₂ (a b : Prop) (ha : a) (hb : b) :
  a :=
begin
  apply ha
end

/- Basic Tactics -/

lemma prop_comp (a b c : Prop) (hab : a → b) (hbc : b → c) :
  a → c :=
begin
  intro ha,
  apply hbc,
  apply hab,
  exact ha
end

lemma prop_comp₂ (a b c : Prop) (hab : a → b) (hbc : b → c) :
  a → c :=
begin
  intro,
  apply hbc,
  apply hab,
  assumption
end

lemma α_example {α β : Type} (f : α → β) :
  (λx, f x) = (λy, f y) :=
by refl

lemma β_example {α β : Type} (f : α → β) (a : α) :
  (λx, f x) a = f a :=
by refl

def double (n : ℕ) : ℕ :=
n + n

lemma δ_example (m : ℕ) :
  double m = m + m :=
by refl

lemma ζ_example :
  (let n : ℕ := 2 in n + n) = 4 :=
by refl

lemma η_example {α β : Type} (f : α → β) :
  (λx, f x) = f :=
by refl

lemma ι_example {α β : Type} (a : α) (b : β) :
  prod.fst (a, b) = a :=
by refl

lemma nat_exists_double_iden :
  ∃n : ℕ, double n = n :=
begin
  use 0,
  refl
end

/- Proofs about Logical Connectives and Quantifiers -/

-- introduction rules
#check true.intro
#check not.intro
#check and.intro
#check or.intro_left
#check or.intro_right
#check iff.intro
#check exists.intro

-- elimination rules
#check false.elim
#check and.elim_left
#check and.elim_right
#check or.elim
#check iff.elim_left
#check iff.elim_right
#check exists.elim

-- definition of `¬` and related lemmas
#print not
#check classical.em
#check classical.by_contradiction

lemma and_swap (a b : Prop) :
  a ∧ b → b ∧ a :=
begin
  intro hab,
  apply and.intro,
  apply and.elim_right,
  exact hab,
  apply and.elim_left,
  exact hab
end

lemma and_swap₂ :
  ∀a b : Prop, a ∧ b → b ∧ a :=
begin
  intros a b hab,
  apply and.intro,
  { exact and.elim_right hab },
  { exact and.elim_left hab }
end

lemma or_swap (a b : Prop) :
  a ∨ b → b ∨ a :=
begin
  intros hab,
  apply or.elim hab,
  { intros ha,
    exact or.intro_right _ ha },
  { intros hb,
    exact or.intro_left _ hb }
end

lemma modus_ponens (a b : Prop) :
  (a → b) → a → b :=
begin
  intros hab ha,
  apply hab,
  exact ha
end

lemma modus_ponens₂ (a b : Prop) (hab : a → b) (hp : a) :
  b :=
begin
  apply hab,
  assumption
end

lemma proof_of_negation (a : Prop) :
  a → ¬¬ a :=
begin
  intro ha,
  apply not.intro,
  intro hna,
  apply hna,
  exact ha
end

lemma proof_of_negation₂ (a : Prop) :
  a → ¬¬ a :=
begin
  intros ha hna,
  apply hna,
  exact ha
end

lemma proof_by_contradiction (a : Prop) :
  ¬¬ a → a :=
begin
  intro hnna,
  apply classical.by_contradiction,
  exact hnna
end

lemma nat_exists_double_iden₂ :
  ∃n : ℕ, double n = n :=
begin
  apply exists.intro 0,
  refl
end

/- Rewriting Tactics -/

lemma proof_of_negation₃ (a : Prop) :
  a → ¬¬ a :=
begin
  dunfold not,
  intro ha,
  apply not.intro,
  intro hna,
  apply hna,
  exact ha
end

/- Proofs about Natural Numbers -/

lemma add_zero (n : ℕ) :
  add 0 n = n :=
begin
  induction n,
  { refl },
  { simp [add, n_ih] }
end

lemma add_zero₂ (n : ℕ) :
  add 0 n = n :=
begin
  induction n,
  case nat.zero {
    refl },
  case nat.succ : m ih {
    simp [add, ih] }
end

lemma add_zero₃ (n : ℕ) :
  add 0 n = n :=
by induction n; simp [add, *]

lemma add_succ (m n : ℕ) :
  add (nat.succ m) n = nat.succ (add m n) :=
begin
  induction n,
  case nat.zero {
    refl },
  case nat.succ : m ih {
    simp [add, ih] }
end

lemma add_comm (m n : ℕ) :
  add m n = add n m :=
begin
  induction n,
  case nat.zero {
    simp [add, add_zero] },
  case nat.succ : m ih {
    simp [add, add_succ, ih] }
end

lemma add_assoc (l m n : ℕ) :
  add (add l m) n = add l (add m n) :=
begin
  induction n,
  case nat.zero {
    refl },
  case nat.succ : m ih {
    simp [add, ih] }
end

-- type classes (useful for `ac_refl` below)
instance : is_commutative ℕ add := ⟨add_comm⟩
instance : is_associative ℕ add := ⟨add_assoc⟩

lemma mul_add (l m n : ℕ) :
  mul l (add m n) = add (mul l m) (mul l n) :=
begin
  induction n,
  case nat.zero {
    refl },
  case nat.succ : m ih {
    simp [add, mul, ih],
    ac_refl }
end

/- Management Tactics -/

lemma cleanup_example (a b c : Prop) (ha : a) (hb : b)
  (hab : a → b) (hbc : b → c) :
  c :=
begin
  revert a b c ha hb hab hbc,
  intros x y z hx hy hxy hyz,
  clear hx hxy x,
  apply hyz,
  clear hyz z,
  rename hy h,
  exact h
end

end LoVe


import .satisfiability
import .rules_of_reasoning

/-
CS2102 Exam #2: Propositional Logic
-/

/-
#1. Define a predicate function, is_satisfiable: pExp → bool
that returns true iff the given proposition is satisfiable.
Hint: Model your answer on our definition of is_valid. You
may need to define one or more helper functions. You should
test your solution, but we will only grade your definition.
Please write test cases in a separate file. Do not submit
that file.
-/

-- Answer here

/-
#2. Clearly the propositions in rules_of_reasoning.lean that
are valid are also satisfiable. But what about the following?
Apply your satisfiability predicate function to decide whether
or not each of the following formulae is satisfiable or not.
Write an "#eval is_satisfiable e" command for each expression.

A. The "fallacies" in that file (the ones that aren't valid).

B. The proposition, pFalse.

C. The following propositions:

1. P ∧ ¬ P
2. P ∨ ¬ P
3. = (¬x1 ∨ x2) ∧ (¬x2 ∨ x3 ∨ ¬x4) ∧ (x1 ∨ x2 ∨ x3 ∨ ¬x4)

Note that for 3. you will have to define four new variables.
Call them x1, x2, x3, and x4.
-/

-- Answers here

/-
3. In the previous problems, you defined a satisfiability
predicate function that returns true or false depending on
whether a given formula is satisfiable or not. Often we will
want to know not only whether there exists a solution but an
actual example of a solution, if there is one. Define a function
called sat_solver : pExp → option (var → bool) that returns a
satisfying interpretation (as "some interpretation") if there
is one, or "none" otherwise. Hint: Model your solution on our
validity checker. First compute the list of interpretations for
a given expression, e, then reduce that list to a value of type
option (var → bool). Evaluate e under each interpretation in
the list until either (A) you find one, call it i, for which e
evaluates to true, or until you reach the empty list, empty
handed.
-/

-- Answer here

/-
4. Define a predicate function, is_unsatisfiable : pExp → bool,
that takes a propositional logic formula, e, and returns true
iff e is unsatisfiable. Hint: Easy. Build on what you have.
-/

-- Answer here

/-
5. Given a propositional logic expression, e, and an incorrect
claim that it's valid, we often want to produce a counter-example
to that claim. Such a counter example is an interpretation under
which the expression is not true. Equivalently, a counterexample
is an interpretation under which the *negation* of the expression
*is* true.

Define a function, counterexample : pExp → option (var → bool),
that takes any expression e and returns either a counterexample
(as "some interpretation") or "none" if there isn't one. Hint:
given e, try to find a model for the expression, ¬ e.
-/

-- Answer here

/-
6. Give an English language example for every valid rule of
reasoning in rules_of_reasoning.lean, and also give an English
language counter-example for each fallacy.

For example, for the rule, P >> Q >> P ∧ Q, you could say,
"If the ball is red then if (in addition to that) the box
is blue, then (in this context) the ball is red AND the box
is blue." It will be easiest is you re-use the same words
for P, Q, and R, in all of your examples. E.g., P means "the
ball is red", Q means "the box is blue", etc.

Copy the contents of rules_of_reasoning.lean into this comment
block and under each rule give your English language sentence.
-/


import analysis.special_functions.log.basic

open real
variables (a b c d e f: ℝ)

#check (le_refl : ∀ a : ℝ, a ≤ a)
#check (le_trans : a ≤ b → b ≤ c → a ≤ c)

variables (h : a ≤ b) (h' : b ≤ c)

#check (le_refl : ∀ a : real, a ≤ a)
#check (le_refl a : a ≤ a)
#check (le_trans : a ≤ b → b ≤ c → a ≤ c)
#check (le_trans h : b ≤ c → a ≤ c)
#check (le_trans h h' : a ≤ c)

example (x y z : ℝ) (h₀ : x ≤ y) (h₁ : y ≤ z) : x ≤ z :=
begin
  apply le_trans,
  exact h₀,
  exact h₁,
end

example (x y z : ℝ) (h₀ : x ≤ y) (h₁ : y ≤ z) : x ≤ z :=
begin
  apply le_trans h₀,
  exact h₁,
end

example (x : ℝ) : x ≤ x :=
  by apply le_refl

#check (le_refl  : ∀ a, a ≤ a)
#check (le_trans : a ≤ b → b ≤ c → a ≤ c)
#check (lt_of_le_of_lt : a ≤ b → b < c → a < c)
#check (lt_of_lt_of_le : a < b → b ≤ c → a < c)
#check (lt_trans : a < b → b < c → a < c)

example (h₀ : a ≤ b) (h₁ : b < c) (h₂ : c ≤ d) (h₃ : d < e) :
  a < e :=
begin
  apply lt_of_le_of_lt h₀,
  apply lt_trans h₁,
  apply lt_of_le_of_lt h₂ h₃,
end

example (h₀ : a ≤ b) (h₁ : b < c) (h₂ : c ≤ d) (h₃ : d < e) :
  a < e := by linarith

example (h : 1 ≤ a) (h' : b ≤ c) :
    2 + a + exp b ≤ 3 * a + exp c :=
  by linarith [exp_le_exp.mpr h']

#check (exp_le_exp : exp a ≤ exp b ↔ a ≤ b)
#check (exp_lt_exp : exp a < exp b ↔ a < b)
#check (log_le_log : 0 < a → 0 < b → (log a ≤ log b ↔ a ≤ b))
#check (log_lt_log : 0 < a → a < b → log a < log b)
#check (add_le_add : a ≤ b → c ≤ d → a + c ≤ b + d)
#check (add_le_add_left : a ≤ b → ∀ c, c + a ≤ c + b)
#check (add_le_add_right : a ≤ b → ∀ c, a + c ≤ b + c)
#check (add_lt_add_of_le_of_lt : a ≤ b → c < d → a + c < b + d)
#check (add_lt_add_of_lt_of_le : a < b → c ≤ d → a + c < b + d)
#check (add_lt_add_left : a < b → ∀ c, c + a < c + b)
#check (add_lt_add_right : a < b → ∀ c, a + c < b + c)
#check (add_nonneg : 0 ≤ a → 0 ≤ b → 0 ≤ a + b)
#check (add_pos : 0 < a → 0 < b → 0 < a + b)
#check (add_pos_of_pos_of_nonneg : 0 < a → 0 ≤ b → 0 < a + b)
#check (exp_pos : ∀ a, 0 < exp a)

example (h₀ : a ≤ b) (h₁ : c < d) : a + exp c + e < b + exp d + e :=
begin
  apply add_lt_add_right,
  apply add_lt_add_of_le_of_lt,
  { exact h₀},
  { exact exp_lt_exp.mpr h₁}
end

example (h₀ : d ≤ e) : c + exp (a + d) ≤ c + exp (a + e) :=
begin
  apply add_le_add_left,
  apply exp_le_exp.mpr,
  apply add_le_add_left h₀,
end

example : (0 : ℝ) < 1 :=
by norm_num

example (h : a ≤ b) : log (1 + exp a) ≤ log (1 + exp b) :=
begin
  have h₀ : 0 < 1 + exp a,
  { apply add_pos,
    norm_num,
    apply exp_pos, },
  have h₁ : 0 < 1 + exp b,
  { apply add_pos,
    norm_num,
    apply exp_pos, },
  apply (log_le_log h₀ h₁).mpr,
  apply add_le_add_left,
  apply exp_le_exp.mpr h,
end

example : 0 ≤ a^2 :=
begin
  exact sq_nonneg a,
end

example (h : a ≤ b) : c - exp b ≤ c - exp a :=
begin
  apply add_le_add_left,
  apply neg_le_neg,
  exact exp_le_exp.mpr h,
end

example : 2*a*b ≤ a^2 + b^2 :=
begin
  have : 0 ≤ a^2 - 2*a*b + b^2,
  calc
    a^2 - 2*a*b + b^2 = (a - b)^2 : by ring
    ... ≥ 0 : by apply sq_nonneg,
  calc
    2*a*b
        = 2*a*b + 0 : by ring
    ... ≤ 2*a*b + (a^2 - 2*a*b + b^2) : add_le_add (le_refl _) this
    ... = a^2 + b^2 : by ring,
end

example : |a*b| ≤ (a^2 + b^2) / 2 :=
begin
  have : 0 ≤ a^2 - 2*a*b + b^2,
  calc
    a^2 - 2*a*b + b^2 = (a - b)^2 : by ring
    ... ≥ 0 : by apply sq_nonneg,
  have h₀ : a*b ≤ (a^2 + b^2) / 2,
    by linarith,

  have : 0 ≤ a^2 + 2*a*b + b^2,
  calc
    a^2 + 2*a*b + b^2 = (a + b)^2 : by ring
    ... ≥ 0 : by apply sq_nonneg,
  have h₁ : -(a*b) ≤ (a^2 + b^2) / 2,
    by linarith,

  apply abs_le'.mpr,
  constructor,
  exact h₀,
  exact h₁,
end

#check abs_le'.mpr


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

! This file was ported from Lean 3 source module data.nat.multiplicity
! leanprover-community/mathlib commit 290a7ba01fbcab1b64757bdaa270d28f4dcede35
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.BigOperators.Intervals
import Mathbin.Algebra.GeomSum
import Mathbin.Data.Nat.Bitwise
import Mathbin.Data.Nat.Log
import Mathbin.Data.Nat.Parity
import Mathbin.Data.Nat.Prime
import Mathbin.RingTheory.Multiplicity

/-!
# Natural number multiplicity

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

This file contains lemmas about the multiplicity function (the maximum prime power dividing a
number) when applied to naturals, in particular calculating it for factorials and binomial
coefficients.

## Multiplicity calculations

* `nat.multiplicity_factorial`: Legendre's Theorem. The multiplicity of `p` in `n!` is
  `n/p + ... + n/p^b` for any `b` such that `n/p^(b + 1) = 0`.
* `nat.multiplicity_factorial_mul`: The multiplicity of `p` in `(p * n)!` is `n` more than that of
  `n!`.
* `nat.multiplicity_choose`: The multiplicity of `p` in `n.choose k` is the number of carries when
  `k` and`n - k` are added in base `p`.

## Other declarations

* `nat.multiplicity_eq_card_pow_dvd`: The multiplicity of `m` in `n` is the number of positive
  natural numbers `i` such that `m ^ i` divides `n`.
* `nat.multiplicity_two_factorial_lt`: The multiplicity of `2` in `n!` is strictly less than `n`.
* `nat.prime.multiplicity_something`: Specialization of `multiplicity.something` to a prime in the
  naturals. Avoids having to provide `p ≠ 1` and other trivialities, along with translating between
  `prime` and `nat.prime`.

## Tags

Legendre, p-adic
-/

open Finset Nat multiplicity

open BigOperators Nat

namespace Nat

/- warning: nat.multiplicity_eq_card_pow_dvd -> Nat.multiplicity_eq_card_pow_dvd is a dubious translation:
lean 3 declaration is
  forall {m : Nat} {n : Nat} {b : Nat}, (Ne.{1} Nat m (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) -> (LT.lt.{0} Nat Nat.hasLt (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))) n) -> (LT.lt.{0} Nat Nat.hasLt (Nat.log m n) b) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) m n) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) (Finset.card.{0} Nat (Finset.filter.{0} Nat (fun (i : Nat) => Dvd.Dvd.{0} Nat Nat.hasDvd (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) m i) n) (fun (a : Nat) => Nat.decidableDvd (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) m a) n) (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring))) Nat.locallyFiniteOrder (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) b)))))
but is expected to have type
  forall {m : Nat} {n : Nat} {b : Nat}, (Ne.{1} Nat m (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) -> (LT.lt.{0} Nat instLTNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)) n) -> (LT.lt.{0} Nat instLTNat (Nat.log m n) b) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) m n) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) (Finset.card.{0} Nat (Finset.filter.{0} Nat (fun (i : Nat) => Dvd.dvd.{0} Nat Nat.instDvdNat (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) m i) n) (fun (a : Nat) => Nat.decidable_dvd (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) m a) n) (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring)) instLocallyFiniteOrderNatToPreorderToPartialOrderStrictOrderedSemiring (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) b)))))
Case conversion may be inaccurate. Consider using '#align nat.multiplicity_eq_card_pow_dvd Nat.multiplicity_eq_card_pow_dvdₓ'. -/
/-- The multiplicity of `m` in `n` is the number of positive natural numbers `i` such that `m ^ i`
divides `n`. This set is expressed by filtering `Ico 1 b` where `b` is any bound greater than
`log m n`. -/
theorem multiplicity_eq_card_pow_dvd {m n b : ℕ} (hm : m ≠ 1) (hn : 0 < n) (hb : log m n < b) :
    multiplicity m n = ↑((Finset.Ico 1 b).filterₓ fun i => m ^ i ∣ n).card :=
  calc
    multiplicity m n = ↑(Ico 1 <| (multiplicity m n).get (finite_nat_iff.2 ⟨hm, hn⟩) + 1).card := by
      simp
    _ = ↑((Finset.Ico 1 b).filterₓ fun i => m ^ i ∣ n).card :=
      congr_arg coe <|
        congr_arg card <|
          Finset.ext fun i =>
            by
            rw [mem_filter, mem_Ico, mem_Ico, lt_succ_iff, ← @PartENat.coe_le_coe i,
              PartENat.natCast_get, ← pow_dvd_iff_le_multiplicity, and_right_comm]
            refine' (and_iff_left_of_imp fun h => lt_of_le_of_lt _ hb).symm
            cases m
            · rw [zero_pow, zero_dvd_iff] at h
              exacts[(hn.ne' h.2).elim, h.1]
            exact
              le_log_of_pow_le (one_lt_iff_ne_zero_and_ne_one.2 ⟨m.succ_ne_zero, hm⟩)
                (le_of_dvd hn h.2)

#align nat.multiplicity_eq_card_pow_dvd Nat.multiplicity_eq_card_pow_dvd

namespace Prime

/- warning: nat.prime.multiplicity_one -> Nat.Prime.multiplicity_one is a dubious translation:
lean 3 declaration is
  forall {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) (OfNat.ofNat.{0} PartENat 0 (OfNat.mk.{0} PartENat 0 (Zero.zero.{0} PartENat PartENat.hasZero))))
but is expected to have type
  forall {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) (OfNat.ofNat.{0} PartENat 0 (Zero.toOfNat0.{0} PartENat PartENat.instZeroPartENat)))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_one Nat.Prime.multiplicity_oneₓ'. -/
theorem multiplicity_one {p : ℕ} (hp : p.Prime) : multiplicity p 1 = 0 :=
  multiplicity.one_right hp.Prime.not_unit
#align nat.prime.multiplicity_one Nat.Prime.multiplicity_one

/- warning: nat.prime.multiplicity_mul -> Nat.Prime.multiplicity_mul is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {m : Nat} {n : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) m n)) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.hasAdd) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p m) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p n)))
but is expected to have type
  forall {p : Nat} {m : Nat} {n : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) m n)) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.instAddPartENat) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p m) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p n)))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_mul Nat.Prime.multiplicity_mulₓ'. -/
theorem multiplicity_mul {p m n : ℕ} (hp : p.Prime) :
    multiplicity p (m * n) = multiplicity p m + multiplicity p n :=
  multiplicity.mul hp.Prime
#align nat.prime.multiplicity_mul Nat.Prime.multiplicity_mul

/- warning: nat.prime.multiplicity_pow -> Nat.Prime.multiplicity_pow is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {m : Nat} {n : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) m n)) (SMul.smul.{0, 0} Nat PartENat (AddMonoid.SMul.{0} PartENat (AddMonoidWithOne.toAddMonoid.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))) n (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p m)))
but is expected to have type
  forall {p : Nat} {m : Nat} {n : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) m n)) (HSMul.hSMul.{0, 0, 0} Nat PartENat PartENat (instHSMul.{0, 0} Nat PartENat (AddMonoid.SMul.{0} PartENat (AddMonoidWithOne.toAddMonoid.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)))) n (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p m)))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_pow Nat.Prime.multiplicity_powₓ'. -/
theorem multiplicity_pow {p m n : ℕ} (hp : p.Prime) :
    multiplicity p (m ^ n) = n • multiplicity p m :=
  multiplicity.pow hp.Prime
#align nat.prime.multiplicity_pow Nat.Prime.multiplicity_pow

/- warning: nat.prime.multiplicity_self -> Nat.Prime.multiplicity_self is a dubious translation:
lean 3 declaration is
  forall {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p p) (OfNat.ofNat.{0} PartENat 1 (OfNat.mk.{0} PartENat 1 (One.one.{0} PartENat PartENat.hasOne))))
but is expected to have type
  forall {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p p) (OfNat.ofNat.{0} PartENat 1 (One.toOfNat1.{0} PartENat PartENat.instOnePartENat)))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_self Nat.Prime.multiplicity_selfₓ'. -/
theorem multiplicity_self {p : ℕ} (hp : p.Prime) : multiplicity p p = 1 :=
  multiplicity_self hp.Prime.not_unit hp.NeZero
#align nat.prime.multiplicity_self Nat.Prime.multiplicity_self

/- warning: nat.prime.multiplicity_pow_self -> Nat.Prime.multiplicity_pow_self is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {n : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p n)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) n))
but is expected to have type
  forall {p : Nat} {n : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p n)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) n))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_pow_self Nat.Prime.multiplicity_pow_selfₓ'. -/
theorem multiplicity_pow_self {p n : ℕ} (hp : p.Prime) : multiplicity p (p ^ n) = n :=
  multiplicity_pow_self hp.NeZero hp.Prime.not_unit n
#align nat.prime.multiplicity_pow_self Nat.Prime.multiplicity_pow_self

/- warning: nat.prime.multiplicity_factorial -> Nat.Prime.multiplicity_factorial is a dubious translation:
lean 3 declaration is
  forall {p : Nat}, (Nat.Prime p) -> (forall {n : Nat} {b : Nat}, (LT.lt.{0} Nat Nat.hasLt (Nat.log p n) b) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.factorial n)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring))) Nat.locallyFiniteOrder (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.hasDiv) n (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i))))))
but is expected to have type
  forall {p : Nat}, (Nat.Prime p) -> (forall {n : Nat} {b : Nat}, (LT.lt.{0} Nat instLTNat (Nat.log p n) b) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.factorial n)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring)) instLocallyFiniteOrderNatToPreorderToPartialOrderStrictOrderedSemiring (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.instDivNat) n (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i))))))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_factorial Nat.Prime.multiplicity_factorialₓ'. -/
/-- **Legendre's Theorem**

The multiplicity of a prime in `n!` is the sum of the quotients `n / p ^ i`. This sum is expressed
over the finset `Ico 1 b` where `b` is any bound greater than `log p n`. -/
theorem multiplicity_factorial {p : ℕ} (hp : p.Prime) :
    ∀ {n b : ℕ}, log p n < b → multiplicity p n ! = (∑ i in Ico 1 b, n / p ^ i : ℕ)
  | 0, b, hb => by simp [Ico, hp.multiplicity_one]
  | n + 1, b, hb =>
    calc
      multiplicity p (n + 1)! = multiplicity p n ! + multiplicity p (n + 1) := by
        rw [factorial_succ, hp.multiplicity_mul, add_comm]
      _ =
          (∑ i in Ico 1 b, n / p ^ i : ℕ) +
            ((Finset.Ico 1 b).filterₓ fun i => p ^ i ∣ n + 1).card :=
        by
        rw [multiplicity_factorial ((log_mono_right <| le_succ _).trans_lt hb), ←
          multiplicity_eq_card_pow_dvd hp.ne_one (succ_pos _) hb]
      _ = (∑ i in Ico 1 b, n / p ^ i + if p ^ i ∣ n + 1 then 1 else 0 : ℕ) :=
        by
        rw [sum_add_distrib, sum_boole]
        simp
      _ = (∑ i in Ico 1 b, (n + 1) / p ^ i : ℕ) :=
        congr_arg coe <| Finset.sum_congr rfl fun _ _ => (succ_div _ _).symm

#align nat.prime.multiplicity_factorial Nat.Prime.multiplicity_factorial

/- warning: nat.prime.multiplicity_factorial_mul_succ -> Nat.Prime.multiplicity_factorial_mul_succ is a dubious translation:
lean 3 declaration is
  forall {n : Nat} {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.factorial (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) p (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))))))) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.hasAdd) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.hasAdd) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.factorial (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) p n))) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) n (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))) (OfNat.ofNat.{0} PartENat 1 (OfNat.mk.{0} PartENat 1 (One.one.{0} PartENat PartENat.hasOne)))))
but is expected to have type
  forall {n : Nat} {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.factorial (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) p (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)))))) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.instAddPartENat) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.instAddPartENat) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.factorial (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) p n))) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) n (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))) (OfNat.ofNat.{0} PartENat 1 (One.toOfNat1.{0} PartENat PartENat.instOnePartENat))))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_factorial_mul_succ Nat.Prime.multiplicity_factorial_mul_succₓ'. -/
/-- The multiplicity of `p` in `(p * (n + 1))!` is one more than the sum
  of the multiplicities of `p` in `(p * n)!` and `n + 1`. -/
theorem multiplicity_factorial_mul_succ {n p : ℕ} (hp : p.Prime) :
    multiplicity p (p * (n + 1))! = multiplicity p (p * n)! + multiplicity p (n + 1) + 1 :=
  by
  have hp' := hp.prime
  have h0 : 2 ≤ p := hp.two_le
  have h1 : 1 ≤ p * n + 1 := Nat.le_add_left _ _
  have h2 : p * n + 1 ≤ p * (n + 1)
  linarith
  have h3 : p * n + 1 ≤ p * (n + 1) + 1
  linarith
  have hm : multiplicity p (p * n)! ≠ ⊤ :=
    by
    rw [Ne.def, eq_top_iff_not_finite, Classical.not_not, finite_nat_iff]
    exact ⟨hp.ne_one, factorial_pos _⟩
  revert hm
  have h4 : ∀ m ∈ Ico (p * n + 1) (p * (n + 1)), multiplicity p m = 0 :=
    by
    intro m hm
    rw [multiplicity_eq_zero, ← not_dvd_iff_between_consec_multiples _ hp.pos]
    rw [mem_Ico] at hm
    exact ⟨n, lt_of_succ_le hm.1, hm.2⟩
  simp_rw [← prod_Ico_id_eq_factorial, multiplicity.Finset.prod hp', ← sum_Ico_consecutive _ h1 h3,
    add_assoc]
  intro h
  rw [PartENat.add_left_cancel_iff h, sum_Ico_succ_top h2, multiplicity.mul hp',
    hp.multiplicity_self, sum_congr rfl h4, sum_const_zero, zero_add, add_comm (1 : PartENat)]
#align nat.prime.multiplicity_factorial_mul_succ Nat.Prime.multiplicity_factorial_mul_succ

/- warning: nat.prime.multiplicity_factorial_mul -> Nat.Prime.multiplicity_factorial_mul is a dubious translation:
lean 3 declaration is
  forall {n : Nat} {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.factorial (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat Nat.hasMul) p n))) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.hasAdd) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.factorial n)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) n)))
but is expected to have type
  forall {n : Nat} {p : Nat}, (Nat.Prime p) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.factorial (HMul.hMul.{0, 0, 0} Nat Nat Nat (instHMul.{0} Nat instMulNat) p n))) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.instAddPartENat) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.factorial n)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) n)))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_factorial_mul Nat.Prime.multiplicity_factorial_mulₓ'. -/
/-- The multiplicity of `p` in `(p * n)!` is `n` more than that of `n!`. -/
theorem multiplicity_factorial_mul {n p : ℕ} (hp : p.Prime) :
    multiplicity p (p * n)! = multiplicity p n ! + n :=
  by
  induction' n with n ih
  · simp
  · simp only [succ_eq_add_one, multiplicity.mul, hp, hp.prime, ih, multiplicity_factorial_mul_succ,
      ← add_assoc, Nat.cast_one, Nat.cast_add, factorial_succ]
    congr 1
    rw [add_comm, add_assoc]
#align nat.prime.multiplicity_factorial_mul Nat.Prime.multiplicity_factorial_mul

#print Nat.Prime.pow_dvd_factorial_iff /-
/-- A prime power divides `n!` iff it is at most the sum of the quotients `n / p ^ i`.
  This sum is expressed over the set `Ico 1 b` where `b` is any bound greater than `log p n` -/
theorem pow_dvd_factorial_iff {p : ℕ} {n r b : ℕ} (hp : p.Prime) (hbn : log p n < b) :
    p ^ r ∣ n ! ↔ r ≤ ∑ i in Ico 1 b, n / p ^ i := by
  rw [← PartENat.coe_le_coe, ← hp.multiplicity_factorial hbn, ← pow_dvd_iff_le_multiplicity]
#align nat.prime.pow_dvd_factorial_iff Nat.Prime.pow_dvd_factorial_iff
-/

/- warning: nat.prime.multiplicity_factorial_le_div_pred -> Nat.Prime.multiplicity_factorial_le_div_pred is a dubious translation:
lean 3 declaration is
  forall {p : Nat}, (Nat.Prime p) -> (forall (n : Nat), LE.le.{0} PartENat PartENat.hasLe (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.factorial n)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) (HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.hasDiv) n (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) p (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))))))
but is expected to have type
  forall {p : Nat}, (Nat.Prime p) -> (forall (n : Nat), LE.le.{0} PartENat PartENat.instLEPartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.factorial n)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) (HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.instDivNat) n (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) p (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))))))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_factorial_le_div_pred Nat.Prime.multiplicity_factorial_le_div_predₓ'. -/
theorem multiplicity_factorial_le_div_pred {p : ℕ} (hp : p.Prime) (n : ℕ) :
    multiplicity p n ! ≤ (n / (p - 1) : ℕ) :=
  by
  rw [hp.multiplicity_factorial (lt_succ_self _), PartENat.coe_le_coe]
  exact Nat.geom_sum_Ico_le hp.two_le _ _
#align nat.prime.multiplicity_factorial_le_div_pred Nat.Prime.multiplicity_factorial_le_div_pred

/- warning: nat.prime.multiplicity_choose_aux -> Nat.Prime.multiplicity_choose_aux is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {n : Nat} {b : Nat} {k : Nat}, (Nat.Prime p) -> (LE.le.{0} Nat Nat.hasLe k n) -> (Eq.{1} Nat (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring))) Nat.locallyFiniteOrder (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.hasDiv) n (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i))) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring))) Nat.locallyFiniteOrder (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.hasDiv) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i))) (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring))) Nat.locallyFiniteOrder (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.hasDiv) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i)))) (Finset.card.{0} Nat (Finset.filter.{0} Nat (fun (i : Nat) => LE.le.{0} Nat Nat.hasLe (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i)))) (fun (a : Nat) => Nat.decidableLe (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p a) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p a)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p a)))) (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring))) Nat.locallyFiniteOrder (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) b)))))
but is expected to have type
  forall {p : Nat} {n : Nat} {b : Nat} {k : Nat}, (Nat.Prime p) -> (LE.le.{0} Nat instLENat k n) -> (Eq.{1} Nat (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring)) instLocallyFiniteOrderNatToPreorderToPartialOrderStrictOrderedSemiring (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.instDivNat) n (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i))) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring)) instLocallyFiniteOrderNatToPreorderToPartialOrderStrictOrderedSemiring (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.instDivNat) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i))) (Finset.sum.{0, 0} Nat Nat Nat.addCommMonoid (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring)) instLocallyFiniteOrderNatToPreorderToPartialOrderStrictOrderedSemiring (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) b) (fun (i : Nat) => HDiv.hDiv.{0, 0, 0} Nat Nat Nat (instHDiv.{0} Nat Nat.instDivNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i)))) (Finset.card.{0} Nat (Finset.filter.{0} Nat (fun (i : Nat) => LE.le.{0} Nat instLENat (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i)))) (fun (a : Nat) => Nat.decLe (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p a) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p a)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p a)))) (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring)) instLocallyFiniteOrderNatToPreorderToPartialOrderStrictOrderedSemiring (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) b)))))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_choose_aux Nat.Prime.multiplicity_choose_auxₓ'. -/
theorem multiplicity_choose_aux {p n b k : ℕ} (hp : p.Prime) (hkn : k ≤ n) :
    (∑ i in Finset.Ico 1 b, n / p ^ i) =
      ((∑ i in Finset.Ico 1 b, k / p ^ i) + ∑ i in Finset.Ico 1 b, (n - k) / p ^ i) +
        ((Finset.Ico 1 b).filterₓ fun i => p ^ i ≤ k % p ^ i + (n - k) % p ^ i).card :=
  calc
    (∑ i in Finset.Ico 1 b, n / p ^ i) = ∑ i in Finset.Ico 1 b, (k + (n - k)) / p ^ i := by
      simp only [add_tsub_cancel_of_le hkn]
    _ =
        ∑ i in Finset.Ico 1 b,
          k / p ^ i + (n - k) / p ^ i + if p ^ i ≤ k % p ^ i + (n - k) % p ^ i then 1 else 0 :=
      by simp only [Nat.add_div (pow_pos hp.pos _)]
    _ = _ := by simp [sum_add_distrib, sum_boole]

#align nat.prime.multiplicity_choose_aux Nat.Prime.multiplicity_choose_aux

/- warning: nat.prime.multiplicity_choose -> Nat.Prime.multiplicity_choose is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {n : Nat} {k : Nat} {b : Nat}, (Nat.Prime p) -> (LE.le.{0} Nat Nat.hasLe k n) -> (LT.lt.{0} Nat Nat.hasLt (Nat.log p n) b) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.choose n k)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) (Finset.card.{0} Nat (Finset.filter.{0} Nat (fun (i : Nat) => LE.le.{0} Nat Nat.hasLe (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p i)))) (fun (a : Nat) => Nat.decidableLe (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p a) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat Nat.hasAdd) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p a)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.hasMod) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p a)))) (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring))) Nat.locallyFiniteOrder (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne))) b)))))
but is expected to have type
  forall {p : Nat} {n : Nat} {k : Nat} {b : Nat}, (Nat.Prime p) -> (LE.le.{0} Nat instLENat k n) -> (LT.lt.{0} Nat instLTNat (Nat.log p n) b) -> (Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.choose n k)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) (Finset.card.{0} Nat (Finset.filter.{0} Nat (fun (i : Nat) => LE.le.{0} Nat instLENat (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p i)))) (fun (a : Nat) => Nat.decLe (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p a) (HAdd.hAdd.{0, 0, 0} Nat Nat Nat (instHAdd.{0} Nat instAddNat) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p a)) (HMod.hMod.{0, 0, 0} Nat Nat Nat (instHMod.{0} Nat Nat.instModNat) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n k) (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p a)))) (Finset.Ico.{0} Nat (PartialOrder.toPreorder.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring)) instLocallyFiniteOrderNatToPreorderToPartialOrderStrictOrderedSemiring (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1)) b)))))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_choose Nat.Prime.multiplicity_chooseₓ'. -/
/-- The multiplicity of `p` in `choose n k` is the number of carries when `k` and `n - k`
  are added in base `p`. The set is expressed by filtering `Ico 1 b` where `b`
  is any bound greater than `log p n`. -/
theorem multiplicity_choose {p n k b : ℕ} (hp : p.Prime) (hkn : k ≤ n) (hnb : log p n < b) :
    multiplicity p (choose n k) =
      ((Ico 1 b).filterₓ fun i => p ^ i ≤ k % p ^ i + (n - k) % p ^ i).card :=
  have h₁ :
    multiplicity p (choose n k) + multiplicity p (k ! * (n - k)!) =
      ((Finset.Ico 1 b).filterₓ fun i => p ^ i ≤ k % p ^ i + (n - k) % p ^ i).card +
        multiplicity p (k ! * (n - k)!) :=
    by
    rw [← hp.multiplicity_mul, ← mul_assoc, choose_mul_factorial_mul_factorial hkn,
      hp.multiplicity_factorial hnb, hp.multiplicity_mul,
      hp.multiplicity_factorial ((log_mono_right hkn).trans_lt hnb),
      hp.multiplicity_factorial (lt_of_le_of_lt (log_mono_right tsub_le_self) hnb),
      multiplicity_choose_aux hp hkn]
    simp [add_comm]
  (PartENat.add_right_cancel_iff
        (PartENat.ne_top_iff_dom.2 <|
          finite_nat_iff.2
            ⟨ne_of_gt hp.one_lt, mul_pos (factorial_pos k) (factorial_pos (n - k))⟩)).1
    h₁
#align nat.prime.multiplicity_choose Nat.Prime.multiplicity_choose

/- warning: nat.prime.multiplicity_le_multiplicity_choose_add -> Nat.Prime.multiplicity_le_multiplicity_choose_add is a dubious translation:
lean 3 declaration is
  forall {p : Nat}, (Nat.Prime p) -> (forall (n : Nat) (k : Nat), LE.le.{0} PartENat PartENat.hasLe (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p n) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.hasAdd) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.choose n k)) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p k)))
but is expected to have type
  forall {p : Nat}, (Nat.Prime p) -> (forall (n : Nat) (k : Nat), LE.le.{0} PartENat PartENat.instLEPartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p n) (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.instAddPartENat) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.choose n k)) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p k)))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_le_multiplicity_choose_add Nat.Prime.multiplicity_le_multiplicity_choose_addₓ'. -/
/-- A lower bound on the multiplicity of `p` in `choose n k`. -/
theorem multiplicity_le_multiplicity_choose_add {p : ℕ} (hp : p.Prime) :
    ∀ n k : ℕ, multiplicity p n ≤ multiplicity p (choose n k) + multiplicity p k
  | _, 0 => by simp
  | 0, _ + 1 => by simp
  | n + 1, k + 1 => by
    rw [← hp.multiplicity_mul]
    refine' multiplicity_le_multiplicity_of_dvd_right _
    rw [← succ_mul_choose_eq]
    exact dvd_mul_right _ _
#align nat.prime.multiplicity_le_multiplicity_choose_add Nat.Prime.multiplicity_le_multiplicity_choose_add

variable {p n k : ℕ}

/- warning: nat.prime.multiplicity_choose_prime_pow_add_multiplicity -> Nat.Prime.multiplicity_choose_prime_pow_add_multiplicity is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {n : Nat} {k : Nat}, (Nat.Prime p) -> (LE.le.{0} Nat Nat.hasLe k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p n)) -> (Ne.{1} Nat k (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) -> (Eq.{1} PartENat (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.hasAdd) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.choose (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p n) k)) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p k)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) n))
but is expected to have type
  forall {p : Nat} {n : Nat} {k : Nat}, (Nat.Prime p) -> (LE.le.{0} Nat instLENat k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p n)) -> (Ne.{1} Nat k (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) -> (Eq.{1} PartENat (HAdd.hAdd.{0, 0, 0} PartENat PartENat PartENat (instHAdd.{0} PartENat PartENat.instAddPartENat) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.choose (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p n) k)) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p k)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) n))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_choose_prime_pow_add_multiplicity Nat.Prime.multiplicity_choose_prime_pow_add_multiplicityₓ'. -/
theorem multiplicity_choose_prime_pow_add_multiplicity (hp : p.Prime) (hkn : k ≤ p ^ n)
    (hk0 : k ≠ 0) : multiplicity p (choose (p ^ n) k) + multiplicity p k = n :=
  le_antisymm
    (by
      have hdisj :
        Disjoint ((Ico 1 n.succ).filterₓ fun i => p ^ i ≤ k % p ^ i + (p ^ n - k) % p ^ i)
          ((Ico 1 n.succ).filterₓ fun i => p ^ i ∣ k) :=
        by
        simp (config := { contextual := true }) [disjoint_right, *, dvd_iff_mod_eq_zero,
          Nat.mod_lt _ (pow_pos hp.pos _)]
      rw [multiplicity_choose hp hkn (lt_succ_self _),
        multiplicity_eq_card_pow_dvd (ne_of_gt hp.one_lt) hk0.bot_lt
          (lt_succ_of_le (log_mono_right hkn)),
        ← Nat.cast_add, PartENat.coe_le_coe, log_pow hp.one_lt, ← card_disjoint_union hdisj,
        filter_union_right]
      have filter_le_Ico := (Ico 1 n.succ).card_filter_le _
      rwa [card_Ico 1 n.succ] at filter_le_Ico)
    (by rw [← hp.multiplicity_pow_self] <;> exact multiplicity_le_multiplicity_choose_add hp _ _)
#align nat.prime.multiplicity_choose_prime_pow_add_multiplicity Nat.Prime.multiplicity_choose_prime_pow_add_multiplicity

/- warning: nat.prime.multiplicity_choose_prime_pow -> Nat.Prime.multiplicity_choose_prime_pow is a dubious translation:
lean 3 declaration is
  forall {p : Nat} {n : Nat} {k : Nat} (hp : Nat.Prime p), (LE.le.{0} Nat Nat.hasLe k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p n)) -> (forall (hk0 : Ne.{1} Nat k (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))), Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p (Nat.choose (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat (Monoid.Pow.{0} Nat Nat.monoid)) p n) k)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat Nat.hasSub) n (Part.get.{0} Nat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) p k) (Iff.mpr (multiplicity.Finite.{0} Nat Nat.monoid p k) (And (Ne.{1} Nat p (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) (LT.lt.{0} Nat Nat.hasLt (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))) k)) (multiplicity.finite_nat_iff p k) (And.intro (Ne.{1} Nat p (OfNat.ofNat.{0} Nat 1 (OfNat.mk.{0} Nat 1 (One.one.{0} Nat Nat.hasOne)))) (LT.lt.{0} Nat Nat.hasLt (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero))) k) (Nat.Prime.ne_one p hp) (Ne.bot_lt.{0} Nat (OrderedCancelAddCommMonoid.toPartialOrder.{0} Nat (StrictOrderedSemiring.toOrderedCancelAddCommMonoid.{0} Nat Nat.strictOrderedSemiring)) Nat.orderBot k hk0)))))))
but is expected to have type
  forall {p : Nat} {n : Nat} {k : Nat} (hp : Nat.Prime p), (LE.le.{0} Nat instLENat k (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p n)) -> (forall (hk0 : Ne.{1} Nat k (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))), Eq.{1} PartENat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p (Nat.choose (HPow.hPow.{0, 0, 0} Nat Nat Nat (instHPow.{0, 0} Nat Nat instPowNat) p n) k)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) (HSub.hSub.{0, 0, 0} Nat Nat Nat (instHSub.{0} Nat instSubNat) n (Part.get.{0} Nat (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) p k) (Iff.mpr (multiplicity.Finite.{0} Nat Nat.monoid p k) (And (Ne.{1} Nat p (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) (LT.lt.{0} Nat instLTNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)) k)) (multiplicity.finite_nat_iff p k) (And.intro (Ne.{1} Nat p (OfNat.ofNat.{0} Nat 1 (instOfNatNat 1))) (LT.lt.{0} Nat instLTNat (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0)) k) (Nat.Prime.ne_one p hp) (Ne.bot_lt.{0} Nat (StrictOrderedSemiring.toPartialOrder.{0} Nat Nat.strictOrderedSemiring) Nat.orderBot k hk0)))))))
Case conversion may be inaccurate. Consider using '#align nat.prime.multiplicity_choose_prime_pow Nat.Prime.multiplicity_choose_prime_powₓ'. -/
theorem multiplicity_choose_prime_pow {p n k : ℕ} (hp : p.Prime) (hkn : k ≤ p ^ n) (hk0 : k ≠ 0) :
    multiplicity p (choose (p ^ n) k) =
      ↑(n - (multiplicity p k).get (finite_nat_iff.2 ⟨hp.ne_one, hk0.bot_lt⟩)) :=
  PartENat.eq_natCast_sub_of_add_eq_natCast <|
    multiplicity_choose_prime_pow_add_multiplicity hp hkn hk0
#align nat.prime.multiplicity_choose_prime_pow Nat.Prime.multiplicity_choose_prime_pow

#print Nat.Prime.dvd_choose_pow /-
theorem dvd_choose_pow (hp : Prime p) (hk : k ≠ 0) (hkp : k ≠ p ^ n) : p ∣ (p ^ n).choose k :=
  by
  obtain hkp | hkp := hkp.symm.lt_or_lt
  · simp [choose_eq_zero_of_lt hkp]
  refine' multiplicity_ne_zero.1 fun h => hkp.not_le <| Nat.le_of_dvd hk.bot_lt _
  have H := hp.multiplicity_choose_prime_pow_add_multiplicity hkp.le hk
  rw [h, zero_add, eq_coe_iff] at H
  exact H.1
#align nat.prime.dvd_choose_pow Nat.Prime.dvd_choose_pow
-/

#print Nat.Prime.dvd_choose_pow_iff /-
theorem dvd_choose_pow_iff (hp : Prime p) : p ∣ (p ^ n).choose k ↔ k ≠ 0 ∧ k ≠ p ^ n := by
  refine' ⟨fun h => ⟨_, _⟩, fun h => dvd_choose_pow hp h.1 h.2⟩ <;> rintro rfl <;>
    simpa [hp.ne_one] using h
#align nat.prime.dvd_choose_pow_iff Nat.Prime.dvd_choose_pow_iff
-/

end Prime

/- warning: nat.multiplicity_two_factorial_lt -> Nat.multiplicity_two_factorial_lt is a dubious translation:
lean 3 declaration is
  forall {n : Nat}, (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) -> (LT.lt.{0} PartENat (Preorder.toLT.{0} PartENat (PartialOrder.toPreorder.{0} PartENat PartENat.partialOrder)) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidableDvd a b) (OfNat.ofNat.{0} Nat 2 (OfNat.mk.{0} Nat 2 (bit0.{0} Nat Nat.hasAdd (One.one.{0} Nat Nat.hasOne)))) (Nat.factorial n)) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat PartENat (HasLiftT.mk.{1, 1} Nat PartENat (CoeTCₓ.coe.{1, 1} Nat PartENat (Nat.castCoe.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.addCommMonoidWithOne))))) n))
but is expected to have type
  forall {n : Nat}, (Ne.{1} Nat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) -> (LT.lt.{0} PartENat (Preorder.toLT.{0} PartENat (PartialOrder.toPreorder.{0} PartENat PartENat.partialOrder)) (multiplicity.{0} Nat Nat.monoid (fun (a : Nat) (b : Nat) => Nat.decidable_dvd a b) (OfNat.ofNat.{0} Nat 2 (instOfNatNat 2)) (Nat.factorial n)) (Nat.cast.{0} PartENat (AddMonoidWithOne.toNatCast.{0} PartENat (AddCommMonoidWithOne.toAddMonoidWithOne.{0} PartENat PartENat.instAddCommMonoidWithOnePartENat)) n))
Case conversion may be inaccurate. Consider using '#align nat.multiplicity_two_factorial_lt Nat.multiplicity_two_factorial_ltₓ'. -/
theorem multiplicity_two_factorial_lt : ∀ {n : ℕ} (h : n ≠ 0), multiplicity 2 n ! < n :=
  by
  have h2 := prime_two.prime
  refine' binary_rec _ _
  · contradiction
  · intro b n ih h
    by_cases hn : n = 0
    · subst hn
      simp at h
      simp [h, one_right h2.not_unit]
    have : multiplicity 2 (2 * n)! < (2 * n : ℕ) :=
      by
      rw [prime_two.multiplicity_factorial_mul]
      refine' (PartENat.add_lt_add_right (ih hn) (PartENat.natCast_ne_top _)).trans_le _
      rw [two_mul]
      norm_cast
    cases b
    · simpa [bit0_eq_two_mul n]
    · suffices multiplicity 2 (2 * n + 1) + multiplicity 2 (2 * n)! < ↑(2 * n) + 1 by
        simpa [succ_eq_add_one, multiplicity.mul, h2, prime_two, Nat.bit1_eq_succ_bit0,
          bit0_eq_two_mul n]
      rw [multiplicity_eq_zero.2 (two_not_dvd_two_mul_add_one n), zero_add]
      refine' this.trans _
      exact_mod_cast lt_succ_self _
#align nat.multiplicity_two_factorial_lt Nat.multiplicity_two_factorial_lt

end Nat


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

-/

import data.fintype.basic
import data.nat.interval

namespace combinatorics

/- @latex: defn_intervals -/
def Ico (n m : ℕ) : finset ℕ := (finset.Ico n m)
def Icc (n m : ℕ) : finset ℕ := Ico n m.succ
def Ioo (n m : ℕ) : finset ℕ := Ico n.succ m
def Ioc (n m : ℕ) : finset ℕ := Ico n.succ m.succ

lemma mem_Ico { n m k : ℕ } : k ∈ (Ico n m) ↔ (n ≤ k ∧ k < m) :=
 finset.mem_Ico

lemma mem_Icc { n m k : ℕ } : k ∈ (Icc n m) ↔ (n ≤ k ∧ k ≤ m) :=
by { rw[Icc, ← @nat.lt_succ_iff k m], exact finset.mem_Ico }

lemma mem_Ioo { n m k : ℕ } : k ∈ (Ioo n m) ↔ (n < k ∧ k < m) :=
by { rw[Ioo, ← @nat.succ_le_iff n k], exact finset.mem_Ico }

lemma mem_Ioc { n m k : ℕ } : k ∈ (Ioc n m) ↔ (n < k ∧ k ≤ m) :=
by { rw[Ioc, ← @nat.lt_succ_iff k m, ← @nat.succ_le_iff n k],
      exact finset.mem_Ico }

lemma Ico_card (n m : ℕ) : (Ico n m).card = m - n := nat.card_Ico n m

lemma Icc_card (n m : ℕ) : (Icc n m).card = m.succ - n :=
by { rw[Icc, Ico_card] }

lemma Ioo_card (n m : ℕ) : (Ioo n m).card = (m - n).pred :=
by { rw[Ioo, Ico_card, nat.sub_succ] }

lemma Ioc_card (n m : ℕ) : (Ioc n m).card = m - n :=
by { rw[Ioc, Ico_card, nat.succ_sub_succ] }

end combinatorics


/-
Copyright (c) 2021 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics

/-!

# Logic in Lean, example sheet 6 : "or" (∨`)

We learn about how to manipulate `P ∨ Q` in Lean.

## Tactics

You'll need to know about the tactics from the previous sheets,
and also the following tactics

* `left` and `right`
* `cases` (new functionality)

-/

-- Throughout this sheet, `P`, `Q`, `R` and `S` will denote propositions.

variables (P Q R S : Prop)

example : P → P ∨ Q :=
begin
  sorry
end

example : Q → P ∨ Q :=
begin
  sorry,
end

example : P ∨ Q → (P → R) → (Q → R) → R :=
begin
  sorry
end

-- symmetry of `or`
example : P ∨ Q → Q ∨ P :=
begin
  sorry
end

-- associativity of `or`
example : (P ∨ Q) ∨ R ↔ P ∨ (Q ∨ R) :=
begin
  sorry,
end

example : (P → R) → (Q → S) → P ∨ Q → R ∨ S :=
begin
  sorry,
end

example : (P → Q) → P ∨ R → Q ∨ R :=
begin
  sorry,
end

example : (P ↔ R) → (Q ↔ S) → (P ∨ Q ↔ R ∨ S) :=
begin
  sorry,
end

-- de Morgan's laws
example : ¬ (P ∨ Q) ↔ ¬ P ∧ ¬ Q :=
begin
  sorry
end

example : ¬ (P ∧ Q) ↔ ¬ P ∨ ¬ Q :=
begin
  sorry
end


/-
Copyright (c) 2021 Lu-Ming Zhang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Lu-Ming Zhang

! This file was ported from Lean 3 source module linear_algebra.matrix.circulant
! leanprover-community/mathlib commit 806bbb0132ba63b93d5edbe4789ea226f8329979
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.LinearAlgebra.Matrix.Symmetric

/-!
# Circulant matrices

This file contains the definition and basic results about circulant matrices.
Given a vector `v : n → α` indexed by a type that is endowed with subtraction,
`matrix.circulant v` is the matrix whose `(i, j)`th entry is `v (i - j)`.

## Main results

- `matrix.circulant`: the circulant matrix generated by a given vector `v : n → α`.
- `matrix.circulant_mul`: the product of two circulant matrices `circulant v` and `circulant w` is
                          the circulant matrix generated by `mul_vec (circulant v) w`.
- `matrix.circulant_mul_comm`: multiplication of circulant matrices commutes when the elements do.

## Implementation notes

`matrix.fin.foo` is the `fin n` version of `matrix.foo`.
Namely, the index type of the circulant matrices in discussion is `fin n`.

## Tags

circulant, matrix
-/

variable {α β m n R : Type _}

namespace Matrix

open Function

open Matrix BigOperators

/-- Given the condition `[has_sub n]` and a vector `v : n → α`,
    we define `circulant v` to be the circulant matrix generated by `v` of type `matrix n n α`.
    The `(i,j)`th entry is defined to be `v (i - j)`. -/
@[simp]
def circulant [Sub n] (v : n → α) : Matrix n n α
  | i, j => v (i - j)
#align matrix.circulant Matrix.circulant

theorem circulant_col_zero_eq [AddGroup n] (v : n → α) (i : n) : circulant v i 0 = v i :=
  congr_arg v (sub_zero _)
#align matrix.circulant_col_zero_eq Matrix.circulant_col_zero_eq

theorem circulant_injective [AddGroup n] : Injective (circulant : (n → α) → Matrix n n α) :=
  by
  intro v w h
  ext k
  rw [← circulant_col_zero_eq v, ← circulant_col_zero_eq w, h]
#align matrix.circulant_injective Matrix.circulant_injective

theorem Fin.circulant_injective : ∀ n, Injective fun v : Fin n → α => circulant v
  | 0 => by decide
  | n + 1 => circulant_injective
#align matrix.fin.circulant_injective Matrix.Fin.circulant_injective

@[simp]
theorem circulant_inj [AddGroup n] {v w : n → α} : circulant v = circulant w ↔ v = w :=
  circulant_injective.eq_iff
#align matrix.circulant_inj Matrix.circulant_inj

@[simp]
theorem Fin.circulant_inj {n} {v w : Fin n → α} : circulant v = circulant w ↔ v = w :=
  (Fin.circulant_injective n).eq_iff
#align matrix.fin.circulant_inj Matrix.Fin.circulant_inj

theorem transpose_circulant [AddGroup n] (v : n → α) : (circulant v)ᵀ = circulant fun i => v (-i) :=
  by ext <;> simp
#align matrix.transpose_circulant Matrix.transpose_circulant

theorem conjTranspose_circulant [Star α] [AddGroup n] (v : n → α) :
    (circulant v)ᴴ = circulant (star fun i => v (-i)) := by ext <;> simp
#align matrix.conj_transpose_circulant Matrix.conjTranspose_circulant

theorem Fin.transpose_circulant : ∀ {n} (v : Fin n → α), (circulant v)ᵀ = circulant fun i => v (-i)
  | 0 => by decide
  | n + 1 => transpose_circulant
#align matrix.fin.transpose_circulant Matrix.Fin.transpose_circulant

theorem Fin.conjTranspose_circulant [Star α] :
    ∀ {n} (v : Fin n → α), (circulant v)ᴴ = circulant (star fun i => v (-i))
  | 0 => by decide
  | n + 1 => conjTranspose_circulant
#align matrix.fin.conj_transpose_circulant Matrix.Fin.conjTranspose_circulant

theorem map_circulant [Sub n] (v : n → α) (f : α → β) :
    (circulant v).map f = circulant fun i => f (v i) :=
  ext fun _ _ => rfl
#align matrix.map_circulant Matrix.map_circulant

theorem circulant_neg [Neg α] [Sub n] (v : n → α) : circulant (-v) = -circulant v :=
  ext fun _ _ => rfl
#align matrix.circulant_neg Matrix.circulant_neg

@[simp]
theorem circulant_zero (α n) [Zero α] [Sub n] : circulant 0 = (0 : Matrix n n α) :=
  ext fun _ _ => rfl
#align matrix.circulant_zero Matrix.circulant_zero

theorem circulant_add [Add α] [Sub n] (v w : n → α) :
    circulant (v + w) = circulant v + circulant w :=
  ext fun _ _ => rfl
#align matrix.circulant_add Matrix.circulant_add

theorem circulant_sub [Sub α] [Sub n] (v w : n → α) :
    circulant (v - w) = circulant v - circulant w :=
  ext fun _ _ => rfl
#align matrix.circulant_sub Matrix.circulant_sub

/-- The product of two circulant matrices `circulant v` and `circulant w` is
    the circulant matrix generated by `mul_vec (circulant v) w`. -/
theorem circulant_mul [Semiring α] [Fintype n] [AddGroup n] (v w : n → α) :
    circulant v ⬝ circulant w = circulant (mulVec (circulant v) w) :=
  by
  ext (i j)
  simp only [mul_apply, mul_vec, circulant, dot_product]
  refine' Fintype.sum_equiv (Equiv.subRight j) _ _ _
  intro x
  simp only [Equiv.subRight_apply, sub_sub_sub_cancel_right]
#align matrix.circulant_mul Matrix.circulant_mul

theorem Fin.circulant_mul [Semiring α] :
    ∀ {n} (v w : Fin n → α), circulant v ⬝ circulant w = circulant (mulVec (circulant v) w)
  | 0 => by decide
  | n + 1 => circulant_mul
#align matrix.fin.circulant_mul Matrix.Fin.circulant_mul

/-- Multiplication of circulant matrices commutes when the elements do. -/
theorem circulant_mul_comm [CommSemigroup α] [AddCommMonoid α] [Fintype n] [AddCommGroup n]
    (v w : n → α) : circulant v ⬝ circulant w = circulant w ⬝ circulant v :=
  by
  ext (i j)
  simp only [mul_apply, circulant, mul_comm]
  refine' Fintype.sum_equiv ((Equiv.subLeft i).trans (Equiv.addRight j)) _ _ _
  intro x
  congr 2
  · simp
  · simp only [Equiv.coe_addRight, Function.comp_apply, Equiv.coe_trans, Equiv.subLeft_apply]
    abel
#align matrix.circulant_mul_comm Matrix.circulant_mul_comm

theorem Fin.circulant_mul_comm [CommSemigroup α] [AddCommMonoid α] :
    ∀ {n} (v w : Fin n → α), circulant v ⬝ circulant w = circulant w ⬝ circulant v
  | 0 => by decide
  | n + 1 => circulant_mul_comm
#align matrix.fin.circulant_mul_comm Matrix.Fin.circulant_mul_comm

/-- `k • circulant v` is another circulant matrix `circulant (k • v)`. -/
theorem circulant_smul [Sub n] [SMul R α] (k : R) (v : n → α) :
    circulant (k • v) = k • circulant v := by ext <;> simp
#align matrix.circulant_smul Matrix.circulant_smul

@[simp]
theorem circulant_single_one (α n) [Zero α] [One α] [DecidableEq n] [AddGroup n] :
    circulant (Pi.single 0 1 : n → α) = (1 : Matrix n n α) :=
  by
  ext (i j)
  simp [one_apply, Pi.single_apply, sub_eq_zero]
#align matrix.circulant_single_one Matrix.circulant_single_one

@[simp]
theorem circulant_single (n) [Semiring α] [DecidableEq n] [AddGroup n] [Fintype n] (a : α) :
    circulant (Pi.single 0 a : n → α) = scalar n a :=
  by
  ext (i j)
  simp [Pi.single_apply, one_apply, sub_eq_zero]
#align matrix.circulant_single Matrix.circulant_single

/-- Note we use `↑i = 0` instead of `i = 0` as `fin 0` has no `0`.
This means that we cannot state this with `pi.single` as we did with `matrix.circulant_single`. -/
theorem Fin.circulant_ite (α) [Zero α] [One α] :
    ∀ n, circulant (fun i => ite (↑i = 0) 1 0 : Fin n → α) = 1
  | 0 => by decide
  | n + 1 => by
    rw [← circulant_single_one]
    congr with j
    simp only [Pi.single_apply, Fin.ext_iff]
    congr
#align matrix.fin.circulant_ite Matrix.Fin.circulant_ite

/-- A circulant of `v` is symmetric iff `v` equals its reverse. -/
theorem circulant_isSymm_iff [AddGroup n] {v : n → α} : (circulant v).IsSymm ↔ ∀ i, v (-i) = v i :=
  by rw [IsSymm, transpose_circulant, circulant_inj, funext_iff]
#align matrix.circulant_is_symm_iff Matrix.circulant_isSymm_iff

theorem Fin.circulant_isSymm_iff : ∀ {n} {v : Fin n → α}, (circulant v).IsSymm ↔ ∀ i, v (-i) = v i
  | 0 => fun v => by simp [is_symm.ext_iff, IsEmpty.forall_iff]
  | n + 1 => fun v => circulant_isSymm_iff
#align matrix.fin.circulant_is_symm_iff Matrix.Fin.circulant_isSymm_iff

/-- If `circulant v` is symmetric, `∀ i j : I, v (- i) = v i`. -/
theorem circulant_isSymm_apply [AddGroup n] {v : n → α} (h : (circulant v).IsSymm) (i : n) :
    v (-i) = v i :=
  circulant_isSymm_iff.1 h i
#align matrix.circulant_is_symm_apply Matrix.circulant_isSymm_apply

theorem Fin.circulant_isSymm_apply {n} {v : Fin n → α} (h : (circulant v).IsSymm) (i : Fin n) :
    v (-i) = v i :=
  Fin.circulant_isSymm_iff.1 h i
#align matrix.fin.circulant_is_symm_apply Matrix.Fin.circulant_isSymm_apply

end Matrix


/-
Copyright (c) 2016 Jeremy Avigad. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jeremy Avigad, Leonardo de Moura, Mario Carneiro, Johannes Hölzl
-/
import algebra.order.monoid.lemmas
import order.bounded_order

/-!
# Ordered monoids

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

This file provides the definitions of ordered monoids.

-/

set_option old_structure_cmd true
open function

universe u
variables {α : Type u} {β : Type*}

/-- An ordered commutative monoid is a commutative monoid
with a partial order such that `a ≤ b → c * a ≤ c * b` (multiplication is monotone)
-/
@[protect_proj, ancestor comm_monoid partial_order]
class ordered_comm_monoid (α : Type*) extends comm_monoid α, partial_order α :=
(mul_le_mul_left       : ∀ a b : α, a ≤ b → ∀ c : α, c * a ≤ c * b)

/-- An ordered (additive) commutative monoid is a commutative monoid
  with a partial order such that `a ≤ b → c + a ≤ c + b` (addition is monotone)
-/
@[protect_proj, ancestor add_comm_monoid partial_order]
class ordered_add_comm_monoid (α : Type*) extends add_comm_monoid α, partial_order α :=
(add_le_add_left       : ∀ a b : α, a ≤ b → ∀ c : α, c + a ≤ c + b)

attribute [to_additive] ordered_comm_monoid

section ordered_instances

@[to_additive]
instance ordered_comm_monoid.to_covariant_class_left (M : Type*) [ordered_comm_monoid M] :
  covariant_class M M (*) (≤) :=
{ elim := λ a b c bc, ordered_comm_monoid.mul_le_mul_left _ _ bc a }

/- This instance can be proven with `by apply_instance`.  However, `with_bot ℕ` does not
pick up a `covariant_class M M (function.swap (*)) (≤)` instance without it (see PR #7940). -/
@[to_additive]
instance ordered_comm_monoid.to_covariant_class_right (M : Type*) [ordered_comm_monoid M] :
  covariant_class M M (swap (*)) (≤) :=
covariant_swap_mul_le_of_covariant_mul_le M

/- This is not an instance, to avoid creating a loop in the type-class system: in a
`left_cancel_semigroup` with a `partial_order`, assuming `covariant_class M M (*) (≤)` implies
`covariant_class M M (*) (<)`, see `left_cancel_semigroup.covariant_mul_lt_of_covariant_mul_le`. -/
@[to_additive] lemma has_mul.to_covariant_class_left
  (M : Type*) [has_mul M] [partial_order M] [covariant_class M M (*) (<)] :
  covariant_class M M (*) (≤) :=
⟨covariant_le_of_covariant_lt _ _ _ covariant_class.elim⟩

/- This is not an instance, to avoid creating a loop in the type-class system: in a
`right_cancel_semigroup` with a `partial_order`, assuming `covariant_class M M (swap (*)) (<)`
implies `covariant_class M M (swap (*)) (≤)`, see
`right_cancel_semigroup.covariant_swap_mul_lt_of_covariant_swap_mul_le`. -/
@[to_additive] lemma has_mul.to_covariant_class_right
  (M : Type*) [has_mul M] [partial_order M] [covariant_class M M (swap (*)) (<)] :
  covariant_class M M (swap (*)) (≤) :=
⟨covariant_le_of_covariant_lt _ _ _ covariant_class.elim⟩

end ordered_instances

lemma bit0_pos [ordered_add_comm_monoid α] {a : α} (h : 0 < a) : 0 < bit0 a :=
add_pos' h h

/-- A linearly ordered additive commutative monoid. -/
@[protect_proj, ancestor linear_order ordered_add_comm_monoid]
class linear_ordered_add_comm_monoid (α : Type*)
  extends linear_order α, ordered_add_comm_monoid α.

/-- A linearly ordered commutative monoid. -/
@[protect_proj, ancestor linear_order ordered_comm_monoid, to_additive]
class linear_ordered_comm_monoid (α : Type*)
  extends linear_order α, ordered_comm_monoid α.

/-- A linearly ordered commutative monoid with an additively absorbing `⊤` element.
  Instances should include number systems with an infinite element adjoined.` -/
@[protect_proj, ancestor linear_ordered_add_comm_monoid has_top]
class linear_ordered_add_comm_monoid_with_top (α : Type*)
  extends linear_ordered_add_comm_monoid α, has_top α :=
(le_top : ∀ x : α, x ≤ ⊤)
(top_add' : ∀ x : α, ⊤ + x = ⊤)

@[priority 100] -- see Note [lower instance priority]
instance linear_ordered_add_comm_monoid_with_top.to_order_top (α : Type u)
  [h : linear_ordered_add_comm_monoid_with_top α] : order_top α :=
{ ..h }

section linear_ordered_add_comm_monoid_with_top
variables [linear_ordered_add_comm_monoid_with_top α] {a b : α}

@[simp]
lemma top_add (a : α) : ⊤ + a = ⊤ := linear_ordered_add_comm_monoid_with_top.top_add' a

@[simp]
lemma add_top (a : α) : a + ⊤ = ⊤ :=
trans (add_comm _ _) (top_add _)

end linear_ordered_add_comm_monoid_with_top


/-
Copyright (c) 2020 Aaron Anderson. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Aaron Anderson
-/
import data.polynomial.degree.definitions

/-!
# Cancel the leading terms of two polynomials

## Definition

* `cancel_leads p q`: the polynomial formed by multiplying `p` and `q` by monomials so that they
  have the same leading term, and then subtracting.

## Main Results
The degree of `cancel_leads` is less than that of the larger of the two polynomials being cancelled.
Thus it is useful for induction or minimal-degree arguments.
-/
namespace polynomial
noncomputable theory
open_locale polynomial

variables {R : Type*}

section comm_ring
variables [ring R] (p q : R[X])

/-- `cancel_leads p q` is formed by multiplying `p` and `q` by monomials so that they
  have the same leading term, and then subtracting. -/
def cancel_leads : R[X] :=
C p.leading_coeff * X ^ (p.nat_degree - q.nat_degree) * q -
C q.leading_coeff * X ^ (q.nat_degree - p.nat_degree) * p

variables {p q}

@[simp] lemma neg_cancel_leads : - p.cancel_leads q = q.cancel_leads p := neg_sub _ _

end comm_ring

section comm_ring
variables [comm_ring R] {p q : R[X]}

lemma dvd_cancel_leads_of_dvd_of_dvd {r : R[X]} (pq : p ∣ q) (pr : p ∣ r) :
  p ∣ q.cancel_leads r :=
dvd_sub (pr.trans (dvd.intro_left _ rfl)) (pq.trans (dvd.intro_left _ rfl))

end comm_ring

lemma nat_degree_cancel_leads_lt_of_nat_degree_le_nat_degree [comm_ring R] [is_domain R]
  {p q : R[X]} (h : p.nat_degree ≤ q.nat_degree) (hq : 0 < q.nat_degree) :
  (p.cancel_leads q).nat_degree < q.nat_degree :=
begin
  by_cases hp : p = 0,
  { convert hq,
    simp [hp, cancel_leads], },
  rw [cancel_leads, sub_eq_add_neg, tsub_eq_zero_iff_le.mpr h, pow_zero, mul_one],
  by_cases h0 :
      C p.leading_coeff * q + -(C q.leading_coeff * X ^ (q.nat_degree - p.nat_degree) * p) = 0,
  { convert hq,
    simp only [h0, nat_degree_zero], },
  have hq0 : ¬ q = 0,
  { contrapose! hq,
    simp [hq] },
  apply lt_of_le_of_ne,
  { rw [← with_bot.coe_le_coe, ← degree_eq_nat_degree h0, ← degree_eq_nat_degree hq0],
    apply le_trans (degree_add_le _ _),
    rw ← leading_coeff_eq_zero at hp hq0,
    simp only [max_le_iff, degree_C hp, degree_C hq0, le_refl q.degree, true_and, nat.cast_with_bot,
      nsmul_one, degree_neg, degree_mul, zero_add, degree_X, degree_pow],
    rw leading_coeff_eq_zero at hp hq0,
    rw [degree_eq_nat_degree hp, degree_eq_nat_degree hq0, ← with_bot.coe_add, with_bot.coe_le_coe,
      tsub_add_cancel_of_le h], },
  { contrapose! h0,
    rw [← leading_coeff_eq_zero, leading_coeff, h0, mul_assoc, mul_comm _ p,
      ← tsub_add_cancel_of_le h, add_comm _ p.nat_degree],
    simp only [coeff_mul_X_pow, coeff_neg, coeff_C_mul, add_tsub_cancel_left, coeff_add],
    rw [add_comm p.nat_degree, tsub_add_cancel_of_le h, ← leading_coeff, ← leading_coeff,
      mul_comm _ q.leading_coeff, ← sub_eq_add_neg, ← mul_sub, sub_self, mul_zero] }
end

end polynomial


/-
Copyright (c) 2021 Eric Wieser. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Eric Wieser, Jireh Loreaux
-/
import algebra.ring.defs
import group_theory.subsemigroup.operations

/-!
# Centers of magmas and semigroups

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

## Main definitions

* `set.center`: the center of a magma
* `subsemigroup.center`: the center of a semigroup
* `set.add_center`: the center of an additive magma
* `add_subsemigroup.center`: the center of an additive semigroup

We provide `submonoid.center`, `add_submonoid.center`, `subgroup.center`, `add_subgroup.center`,
`subsemiring.center`, and `subring.center` in other files.
-/

variables {M : Type*}

namespace set

variables (M)

/-- The center of a magma. -/
@[to_additive add_center /-" The center of an additive magma. "-/]
def center [has_mul M] : set M := {z | ∀ m, m * z = z * m}

@[to_additive mem_add_center]
lemma mem_center_iff [has_mul M] {z : M} : z ∈ center M ↔ ∀ g, g * z = z * g := iff.rfl

instance decidable_mem_center [has_mul M]  [∀ a : M, decidable $ ∀ b : M, b * a = a * b] :
  decidable_pred (∈ center M) :=
λ _, decidable_of_iff' _ (mem_center_iff M)

@[simp, to_additive zero_mem_add_center]
lemma one_mem_center [mul_one_class M] : (1 : M) ∈ set.center M := by simp [mem_center_iff]

@[simp]
lemma zero_mem_center [mul_zero_class M] : (0 : M) ∈ set.center M := by simp [mem_center_iff]

variables {M}

@[simp, to_additive add_mem_add_center]
lemma mul_mem_center [semigroup M] {a b : M}
  (ha : a ∈ set.center M) (hb : b ∈ set.center M) : a * b ∈ set.center M :=
λ g, by rw [mul_assoc, ←hb g, ← mul_assoc, ha g, mul_assoc]

@[simp, to_additive neg_mem_add_center]
lemma inv_mem_center [group M] {a : M} (ha : a ∈ set.center M) : a⁻¹ ∈ set.center M :=
λ g, by rw [← inv_inj, mul_inv_rev, inv_inv, ← ha, mul_inv_rev, inv_inv]

@[simp]
lemma add_mem_center [distrib M] {a b : M}
  (ha : a ∈ set.center M) (hb : b ∈ set.center M) : a + b ∈ set.center M :=
λ c, by rw [add_mul, mul_add, ha c, hb c]

@[simp]
lemma neg_mem_center [ring M] {a : M} (ha : a ∈ set.center M) : -a ∈ set.center M :=
λ c, by rw [←neg_mul_comm, ha (-c), neg_mul_comm]

@[to_additive subset_add_center_add_units]
lemma subset_center_units [monoid M] :
  (coe : Mˣ → M) ⁻¹' center M ⊆ set.center Mˣ :=
λ a ha b, units.ext $ ha _

lemma center_units_subset [group_with_zero M] :
  set.center Mˣ ⊆ (coe : Mˣ → M) ⁻¹' center M :=
λ a ha b, begin
  obtain rfl | hb := eq_or_ne b 0,
  { rw [zero_mul, mul_zero], },
  { exact units.ext_iff.mp (ha (units.mk0 _ hb)) }
end

/-- In a group with zero, the center of the units is the preimage of the center. -/
lemma center_units_eq [group_with_zero M] :
  set.center Mˣ = (coe : Mˣ → M) ⁻¹' center M :=
subset.antisymm center_units_subset subset_center_units

@[simp]
lemma inv_mem_center₀ [group_with_zero M] {a : M} (ha : a ∈ set.center M) : a⁻¹ ∈ set.center M :=
begin
  obtain rfl | ha0 := eq_or_ne a 0,
  { rw inv_zero, exact zero_mem_center M },
  rcases is_unit.mk0 _ ha0 with ⟨a, rfl⟩,
  rw ←units.coe_inv,
  exact center_units_subset (inv_mem_center (subset_center_units ha)),
end

@[simp, to_additive sub_mem_add_center]
lemma div_mem_center [group M] {a b : M} (ha : a ∈ set.center M) (hb : b ∈ set.center M) :
  a / b ∈ set.center M :=
begin
  rw [div_eq_mul_inv],
  exact mul_mem_center ha (inv_mem_center hb),
end

@[simp]
lemma div_mem_center₀ [group_with_zero M] {a b : M} (ha : a ∈ set.center M)
  (hb : b ∈ set.center M) : a / b ∈ set.center M :=
begin
  rw div_eq_mul_inv,
  exact mul_mem_center ha (inv_mem_center₀ hb),
end

variables (M)

@[simp, to_additive add_center_eq_univ]
lemma center_eq_univ [comm_semigroup M] : center M = set.univ :=
subset.antisymm (subset_univ _) $ λ x _ y, mul_comm y x

end set

namespace subsemigroup
section
variables (M) [semigroup M]

/-- The center of a semigroup `M` is the set of elements that commute with everything in `M` -/
@[to_additive "The center of a semigroup `M` is the set of elements that commute with everything in
`M`"]
def center : subsemigroup M :=
{ carrier := set.center M,
  mul_mem' := λ a b, set.mul_mem_center }

@[to_additive] lemma coe_center : ↑(center M) = set.center M := rfl

variables {M}

@[to_additive] lemma mem_center_iff {z : M} : z ∈ center M ↔ ∀ g, g * z = z * g := iff.rfl

@[to_additive]
instance decidable_mem_center (a) [decidable $ ∀ b : M, b * a = a * b] :
  decidable (a ∈ center M) :=
decidable_of_iff' _ mem_center_iff

/-- The center of a semigroup is commutative. -/
@[to_additive "The center of an additive semigroup is commutative."]
instance : comm_semigroup (center M) :=
{ mul_comm := λ a b, subtype.ext $ b.prop _,
  .. mul_mem_class.to_semigroup (center M) }

end

section
variables (M) [comm_semigroup M]

@[to_additive, simp] lemma center_eq_top : center M = ⊤ :=
set_like.coe_injective (set.center_eq_univ M)

end

end subsemigroup

-- Guard against import creep
assert_not_exists finset


/-
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.
Author : María Inés de Frutos-Fernández
-/

import data.real.basic -- Importamos los números reales.

/-!
Algunos de estos ejemplos se han adaptado de la charla de estructuras y classes de LftCM 2022:
<https://icerm.brown.edu/video_archive/?play=2897>
-/

noncomputable theory

/-! # Clases

Referencia : Theorem Proving in Lean.

Cualquier estructura en Lean se puede marcar como una *clase de tipos*.
Podemos declarar *instancias* de una clase.
Cuando el elaborador está buscando un elemento de una clase, puede consultar una tabla con las
instancias declaradas para encontrar un elemento apropiado.
-/

/-! ## Variables -/

--En Lean, podemos usar distintos tipos de variables
variable (r : ℝ) -- variable explícita.
variable {n : ℕ} -- variable implícita. Para argumentos que se pueden inferir a partir de otros.
variable (G : Type*)
variable [group G] -- argumentos entre `[ ]` son inferidos por el mecanismo de inferencia de clases.

/- Las variables que acabamos de declarar estarán visibles hasta el final del fichero. No es posible
tener dos variables visibles con el mismo nombre.

Podemos modificar este comportamiento utilizando secciones:
-/
section ejemplo
variable (a : ℕ)
--variable (a : ℝ) -- da error
end ejemplo

variable (a : ℕ)

-- Ejemplos
lemma nat.add_pos (n m : ℕ) (hm : 0 < m) :
  0 < n + m :=
sorry

lemma nat.add_pos' (n : ℕ) {m : ℕ} (hm : 0 < m) :
  0 < n + m :=
sorry

example (n m : ℕ) (hm : 0 < m) :
  0 < m + n :=
begin
  rw add_comm, -- ¿por qué funciona esto?
  sorry
  --exact nat.add_pos n m hm,
  --exact nat.add_pos n _ hm, -- Lean puede deducir m a partir de hm
  --exact nat.add_pos' n hm -- con la segunda variante, la barra _ no es necesara.
end

/- ¿Por qué hemos podido utilizar `add_comm` en el ejemplo anterior?
  `add_comm` es un teorema sobre semigrupos aditivos conmutativos.
  Lean ha encontrado automáticamente la estructura de semigrupo de `ℕ`, mediante un proceso
  llamado *inferencia de clases*. -/

/- Cualquier estructura en Lean se puede marcar como una *clase de tipos*. Por ejemplo,
  `add_group` es la clase de grupos aditivos. -/
--#check add_group

/- Podemos declarar *instancias* de una clase. Por ejemplo, la estructura de grupo aditivo de `ℤ`
está registrada en la instancia `int.add_group`. -/
--#check int.add_group

/-Cuando el elaborador está buscando un elemento de una clase, puede consultar una tabla con las
instancias declaradas para encontrar un elemento apropiado. -/
--#check add_comm -- tiene un argumento [add_comm_semigroup G]

/- En Lean utilizamos clases para registrar estructuras algebraicas, topológicas, analíticas, ... -/

-- Ejemplo: Podemos crear una clase para indicar que un tipo es no vacío.
class no_vacio (A : Type) : Prop :=
(has_val : ∃ x : A, true)

instance : no_vacio ℤ :=
{ has_val := ⟨0, trivial⟩ }

instance {A B : Type} [ha : no_vacio A] [hb : no_vacio B] :
  no_vacio (A × B) :=
begin
  cases ha.has_val with a _,
  cases hb.has_val with b _,
  apply no_vacio.mk,
  use (a, b)
end

-- Ejemplo
instance producto_de_grupos {G H : Type*} [group G] [group H] : group (G × H) := infer_instance


import .love02_backward_proofs_exercise_sheet

/-! # LoVe Homework 2: Backward Proofs

Homework must be done individually. -/

set_option pp.beta true
set_option pp.generalized_field_notation false

namespace LoVe

namespace backward_proofs

/-! ## Question 1 (4 points): Connectives and Quantifiers

1.1 (3 points). Complete the following proofs using basic tactics such as
`intro`, `apply`, and `exact`. -/

lemma B (a b c : Prop) :
  (a → b) → (c → a) → c → b :=
sorry

lemma S (a b c : Prop) :
  (a → b → c) → (a → b) → a → c :=
sorry

lemma more_nonsense (a b c d : Prop) :
  ((a → b) → c → d) → c → b → d :=
sorry

lemma even_more_nonsense (a b c : Prop) :
  (a → b) → (a → c) → a → b → c :=
sorry

/-! 1.2 (1 point). Prove the following lemma using basic tactics. -/

lemma weak_peirce (a b : Prop) :
  ((((a → b) → a) → a) → b) → b :=
sorry

/-! ## Question 2 (5 points): Logical Connectives

2.1 (1 point). Prove the following property about double negation using basic
tactics.

Hints:

* Keep in mind that `¬ a` is the same as `a → false`. You can start by
  invoking `rw not_def` four times if this helps you.

* You will need to apply the elimination rule for `false` at a key point in the
  proof. -/

lemma herman (a : Prop) :
  ¬¬ (¬¬ a → a) :=
sorry

/-! 2.2 (2 points). Prove the missing link in our chain of classical axiom
implications.

Hints:

* One way to find the definitions of `double_negation` and `excluded_middle`
  quickly is to

  1. hold the Control (on Linux and Windows) or Command (on macOS) key pressed;
  2. move the cursor to the identifier `double_negation` or `excluded_middle`;
  3. click the identifier.

* You can use `rw double_negation` to unfold the definition of
  `double_negation`, and similarly for the other definitions.

* You will need to apply the double negation hypothesis for `a ∨ ¬ a`. You will
  also need the left and right introduction rules for `∨` at some point. -/

#check double_negation
#check excluded_middle

lemma em_of_dn :
  double_negation → excluded_middle :=
sorry

/-! 2.3 (2 points). We have proved three of the six possible implications between
`excluded_middle`, `peirce`, and `double_negation`. State and prove the
three missing implications, exploiting the three theorems we already have. -/

#check peirce_of_em
#check dn_of_peirce
#check em_of_dn

/-! 2.3 (2 points). We have proved three of the six possible implications
between `excluded_middle`, `peirce`, and `double_negation`. State and prove the
three missing implications, exploiting the three theorems we already have. -/

#check peirce_of_em
#check dn_of_peirce
#check em_of_dn

-- enter your solution here

end backward_proofs

end LoVe


import .lub_prodSets

namespace xena -- hide

/-
# Chapter 3 : Sup and Inf

## Level 10
-/

-- main result in lemma sup_mem_prod_of_sets
-- hide
--def mem_prod_sets (A : set ℝ) (B : set ℝ) := { x : ℝ | ∃ y ∈ A, ∃ z ∈ B, x = y * z}

/-
Intermediary result `zero_in_prod` proved in sets_level08.

Intermediary result `mem_prod_sets_lub_proof` in previous level.
-/

/- Lemma
For two non-empty sets of reals $A$ and $B$, it is not in general true that
$$ \textrm{sup} (A \cdot B) = \textrm{sup} (A) \cdot \textrm{sup}(B)$$
where $A \cdot B$ is defined pointwise as above.
-/
lemma sup_mem_prod_of_sets : ¬ ( ∀ (A B : set ℝ) (a b : ℝ),
  A.nonempty ∧ B.nonempty → bdd_below A ∧ bdd_below B →
  is_lub A a ∧ is_lub B b →
  is_lub (mem_prod_sets A B) (a * b) ) :=
begin
  intro H,
  -- do an example with A = [-2,-1], B = [0,3]
  set A1 : set ℝ := set.Icc (-2:ℝ) (-1:ℝ) with hA,
  set B1 : set ℝ := set.Icc (0:ℝ) (3:ℝ) with hB,
  set a : ℝ := (-1:ℝ) with ha,
  set b : ℝ := (3 : ℝ) with hb,
  have G := H A1 B1,
  have h1A : A1.nonempty, simp, norm_num,
  have h1B : B1.nonempty, simp, norm_num,
  have F := G a b (and.intro h1A h1B),
  have h11 : ((-2:ℝ) ≤ -1), norm_num,
  have h21 : (0:ℝ) ≤ (3:ℝ), norm_num,
  have h2A : bdd_below A1,
    -- use the definition in bounds.lean
    have h12 := is_glb_Icc h11,
    cases h12 with hh hhh,
    existsi (-2:ℝ), exact hh,
  have h2B : bdd_below B1,
    have h22 := is_glb_Icc h21,
    cases h22 with hh hhh,
    existsi (0:ℝ), exact hh,
  have E := F (and.intro h2A h2B),
  have h1 : is_lub A1 a,
    exact is_lub_Icc h11,
  have h2 : is_lub B1 b,
    exact is_lub_Icc h21,
  have D := E (and.intro h1 h2),
  rw ha at h1, rw hb at h2, rw ha at D, rw hb at D,
  have E : is_lub (mem_prod_sets A1 B1) 0,
    exact mem_prod_sets_lub_proof,
  have E1 := is_lub.unique D E,
  linarith, done
end

end xena -- hide