/-
Copyright (c) 2022 Yakov Pechersky. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yakov Pechersky, Floris van Doorn
-/
import data.pnat.basic

/-!
# Explicit least witnesses to existentials on positive natural numbers

Implemented via calling out to `nat.find`.

-/

namespace pnat

variables {p q : ℕ+ → Prop} [decidable_pred p] [decidable_pred q] (h : ∃ n, p n)

instance decidable_pred_exists_nat :
  decidable_pred (λ n' : ℕ, ∃ (n : ℕ+) (hn : n' = n), p n) := λ n',
decidable_of_iff' (∃ (h : 0 < n'), p ⟨n', h⟩) $ subtype.exists.trans $
  by simp_rw [subtype.coe_mk, @exists_comm (_ < _) (_ = _), exists_prop, exists_eq_left']

include h

/-- The `pnat` version of `nat.find_x` -/
protected def find_x : {n // p n ∧ ∀ m : ℕ+, m < n → ¬p m} :=
begin
  have : ∃ (n' : ℕ) (n : ℕ+) (hn' : n' = n), p n, from exists.elim h (λ n hn, ⟨n, n, rfl, hn⟩),
  have n := nat.find_x this,
  refine ⟨⟨n, _⟩, _, λ m hm pm, _⟩,
  { obtain ⟨n', hn', -⟩ := n.prop.1,
    rw hn',
    exact n'.prop },
  { obtain ⟨n', hn', pn'⟩ := n.prop.1,
    simpa [hn', subtype.coe_eta] using pn' },
  { exact n.prop.2 m hm ⟨m, rfl, pm⟩ }
end

/--
If `p` is a (decidable) predicate on `ℕ+` and `hp : ∃ (n : ℕ+), p n` is a proof that
there exists some positive natural number satisfying `p`, then `pnat.find hp` is the
smallest positive natural number satisfying `p`. Note that `pnat.find` is protected,
meaning that you can't just write `find`, even if the `pnat` namespace is open.

The API for `pnat.find` is:

* `pnat.find_spec` is the proof that `pnat.find hp` satisfies `p`.
* `pnat.find_min` is the proof that if `m < pnat.find hp` then `m` does not satisfy `p`.
* `pnat.find_min'` is the proof that if `m` does satisfy `p` then `pnat.find hp ≤ m`.
-/
protected def find : ℕ+ :=
pnat.find_x h

protected theorem find_spec : p (pnat.find h) :=
(pnat.find_x h).prop.left

protected theorem find_min : ∀ {m : ℕ+}, m < pnat.find h → ¬p m :=
(pnat.find_x h).prop.right

protected theorem find_min' {m : ℕ+} (hm : p m) : pnat.find h ≤ m :=
le_of_not_lt (λ l, pnat.find_min h l hm)

variables {n m : ℕ+}

lemma find_eq_iff : pnat.find h = m ↔ p m ∧ ∀ n < m, ¬ p n :=
begin
  split,
  { rintro rfl, exact ⟨pnat.find_spec h, λ _, pnat.find_min h⟩ },
  { rintro ⟨hm, hlt⟩,
    exact le_antisymm (pnat.find_min' h hm) (not_lt.1 $ imp_not_comm.1 (hlt _) $ pnat.find_spec h) }
end

@[simp] lemma find_lt_iff (n : ℕ+) : pnat.find h < n ↔ ∃ m < n, p m :=
⟨λ h2, ⟨pnat.find h, h2, pnat.find_spec h⟩, λ ⟨m, hmn, hm⟩, (pnat.find_min' h hm).trans_lt hmn⟩

@[simp] lemma find_le_iff (n : ℕ+) : pnat.find h ≤ n ↔ ∃ m ≤ n, p m :=
by simp only [exists_prop, ← lt_add_one_iff, find_lt_iff]

@[simp] lemma le_find_iff (n : ℕ+) : n ≤ pnat.find h ↔ ∀ m < n, ¬ p m :=
by simp_rw [← not_lt, find_lt_iff, not_exists]

@[simp] lemma lt_find_iff (n : ℕ+) : n < pnat.find h ↔ ∀ m ≤ n, ¬ p m :=
by simp only [← add_one_le_iff, le_find_iff, add_le_add_iff_right]

@[simp] lemma find_eq_one : pnat.find h = 1 ↔ p 1 :=
by simp [find_eq_iff]

@[simp] lemma one_le_find : 1 < pnat.find h ↔ ¬ p 1 :=
not_iff_not.mp $ by simp

theorem find_mono (h : ∀ n, q n → p n)
  {hp : ∃ n, p n} {hq : ∃ n, q n} :
  pnat.find hp ≤ pnat.find hq :=
pnat.find_min' _ (h _ (pnat.find_spec hq))

lemma find_le {h : ∃ n, p n} (hn : p n) : pnat.find h ≤ n :=
(pnat.find_le_iff _ _).2 ⟨n, le_rfl, hn⟩

lemma find_comp_succ (h : ∃ n, p n) (h₂ : ∃ n, p (n + 1)) (h1 : ¬ p 1) :
  pnat.find h = pnat.find h₂ + 1 :=
begin
  refine (find_eq_iff _).2 ⟨pnat.find_spec h₂, λ n, pnat.rec_on n _ _⟩,
  { simp [h1] },
  intros m IH hm,
  simp only [add_lt_add_iff_right, lt_find_iff] at hm,
  exact hm _ le_rfl
end

end pnat


-- Propiedades_asociativa_y_conmutativa.lean
-- Propiedades asociativa y conmutativa del producto de los reales.
-- José A. Alonso Jiménez
-- Sevilla, 12 de agosto de 2020
-- ---------------------------------------------------------------------

-- En esta relación se presentan distintas pruebas con Lean de una
-- igualdad con productos de números reales. La primera es por
-- reescritura usando las propiedades asociativa y conmutativa, La
-- segunda es con encadenamiento de ecuaciones. Las restantes son
-- automáticas.

-- ---------------------------------------------------------------------
-- Ejercicio. Sean a, b y c números reales. Demostrar que
--    (a * b) * c = b * (a * c)
-- ----------------------------------------------------------------------

import data.real.basic

variables (a b c : ℝ)

-- 1ª demostración (hacia atrás con rw)
-- ====================================

example : (a * b) * c = b * (a * c) :=
begin
  rw mul_comm a b,
  rw mul_assoc,
end

-- Prueba:
/-
  a b c : ℝ
  ⊢ (a * b) * c = b * (a * c)
rw mul_comm a b,
  ⊢ (b * a) * c = b * (a * c)
rw mul_assoc,
  no goals
-/

-- Comentarios:
-- + Se han usado los lemas
--   + mul_comm : ∀ (a b : ℝ), a * b = b * a
--   + mul_assoc : ∀ (a b c : ℝ), a * b * c = a * (b * c)

-- 2ª demostración (encadenamiento de igualdades)
-- ==============================================

example : (a * b) * c = b * (a * c) :=
begin
  calc (a * b) * c = (b * a) * c : by rw mul_comm a b
              ...  = b * (a * c) : by rw mul_assoc,
end

-- 3ª demostración (automática con linarith)
-- =========================================

example : (a * b) * c = b * (a * c) :=
by linarith

-- 4ª demostración (automática con finish)
-- =======================================

example : (a * b) * c = b * (a * c) :=
by finish

-- 5ª demostración (automática con ring)
-- =====================================

example : (a * b) * c = b * (a * c) :=
by ring

-- Comentarios:
-- + La táctica ring demuestra la conclusión normalizando las
--   expresiones con las reglas de los anillos.


/-
Define the abstract and concrete syntax and semantics of simple arithmetic expressions, to
include variables.
-/

inductive avar : Type
| mk (n : nat)

def a_state := avar → nat

inductive aexp : Type
| lit_expr (n : nat)
| var_expr (a : avar)
| add_expr (e1 e2 : aexp)
| mul_expr (e1 e2 : aexp)

open aexp

def aeval : aexp → a_state → nat
| (lit_expr n) st := n
| (var_expr v) st :=  st v
| (add_expr e1 e2) st := (aeval e1 st) + (aeval e2 st)
| (mul_expr e1 e2) st := (aeval e1 st) * (aeval e2 st)

notation `[` n `]` := lit_expr n
notation `[` v `]` := var_expr v
notation e1 + e2 := add_expr e1 e2
notation e1 * e2 := mul_expr e1 e2

def st0 := λ (v : avar), 0
example : aeval ([3] + [5]) st0 = 8 := rfl


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

/-!
# The valuation at infinity on k(t)
For a field `k`, the valuation at infinity on the function field `k(t)` is the nonarchimedean
valuation on `k(t)` with uniformizer `1/t`. Explicitly, if `f/g ∈ k(t)` is a nonzero quotient of
polynomials, its valuation at infinity is `multiplicative.of_add(degree(f) - degree(g))`.

## Main definitions
- `infty_valuation` : The valuation at infinity on `k(t)`.
- `kt_infty` : The completion `k((t⁻¹))` of `k(t)` with respect to `infty_valuation`.

## Implementation notes
The code in this file has already been incorporated to mathlib and can be found in the file
`number_theory/function_field.lean`. Note that `kt_infty` is called `Fqt_infty` there for
consistency with the file's notation, and that some of the names of definitions and lemmas have been
modified. We keep this version of the code here so that this branch is a complete reference for the
article "Formalizing the Rings of Adèles of a Global Field".

## Tags
function field, valuation
-/

noncomputable theory

open_locale classical

variables (k : Type) [field k]
/-- The valuation at infinity is the nonarchimedean valuation on `k(t)` with uniformizer `1/t`. -/
def infty_valuation_def (r : ratfunc k) : with_zero (multiplicative ℤ) :=
if (r = 0) then 0 else (multiplicative.of_add ((r.num.nat_degree : ℤ) - r.denom.nat_degree))

lemma infty_valuation.map_zero' : infty_valuation_def k 0 = 0 :=
by { rw [infty_valuation_def, if_pos], refl, }

lemma infty_valuation.map_one' : infty_valuation_def k 1 = 1 :=
begin
  rw [infty_valuation_def, if_neg (zero_ne_one.symm : (1 : ratfunc k) ≠ 0)],
  simp only [polynomial.nat_degree_one, ratfunc.num_one, int.coe_nat_zero, sub_zero,
    ratfunc.denom_one, of_add_zero, with_zero.coe_one],
end

lemma infty_valuation.map_mul' (x y : ratfunc k) :
  infty_valuation_def k (x * y) = infty_valuation_def k x * infty_valuation_def k y :=
begin
  rw [infty_valuation_def, infty_valuation_def, infty_valuation_def],
  by_cases hx : x = 0,
  { rw [hx, zero_mul, if_pos (eq.refl _), zero_mul] },
  { by_cases hy : y = 0,
    { rw [hy, mul_zero, if_pos (eq.refl _), mul_zero] },
    { rw [if_neg hx, if_neg hy, if_neg (mul_ne_zero hx hy), ← with_zero.coe_mul,
        with_zero.coe_inj, ← of_add_add],
      apply congr_arg,
      rw [add_sub, sub_add, sub_sub_assoc_swap, sub_sub, sub_eq_sub_iff_add_eq_add],
      norm_cast,
      rw [← polynomial.nat_degree_mul x.denom_ne_zero y.denom_ne_zero,
        ← polynomial.nat_degree_mul (ratfunc.num_ne_zero (mul_ne_zero hx hy))
          (mul_ne_zero x.denom_ne_zero y.denom_ne_zero),
        ← polynomial.nat_degree_mul (ratfunc.num_ne_zero hx) (ratfunc.num_ne_zero hy),
        ← polynomial.nat_degree_mul (mul_ne_zero (ratfunc.num_ne_zero hx) (ratfunc.num_ne_zero hy))
          (x * y).denom_ne_zero, ratfunc.num_denom_mul],}}
end

variable {k}
/-- Equivalent fractions have the same valuation -/
lemma infty_valuation_well_defined {r₁ r₂ s₁ s₂ : polynomial k} (hr₁ : r₁ ≠ 0) (hs₁ : s₁ ≠ 0)
  (hr₂ : r₂ ≠ 0) (hs₂ : s₂ ≠ 0) (h_eq : r₁*s₂ = r₂*s₁) :
  (r₁.nat_degree : ℤ) - s₁.nat_degree = (r₂.nat_degree : ℤ) - s₂.nat_degree :=
begin
  rw sub_eq_sub_iff_add_eq_add,
  norm_cast,
  rw [← polynomial.nat_degree_mul hr₁ hs₂, ← polynomial.nat_degree_mul hr₂ hs₁, h_eq],
end

lemma ratfunc.num_add_ne_zero {x y : ratfunc k} (hxy : x + y ≠ 0) :
  x.num * y.denom + x.denom * y.num ≠ 0 :=
begin
  intro h_zero,
  have h := ratfunc.num_denom_add x y,
  rw [h_zero, zero_mul] at h,
  exact (mul_ne_zero (ratfunc.num_ne_zero hxy) (mul_ne_zero x.denom_ne_zero y.denom_ne_zero)) h,
end

lemma infty_valuation_add_rw {x y : ratfunc k} (hxy : x + y ≠ 0) :
  ((x + y).num.nat_degree : ℤ) - ((x + y).denom.nat_degree)  =
  ((x.num) * y.denom + (x.denom) * y.num).nat_degree - ((x.denom) * y.denom).nat_degree :=
infty_valuation_well_defined (ratfunc.num_ne_zero hxy) ((x + y).denom_ne_zero)
    (ratfunc.num_add_ne_zero hxy) (mul_ne_zero x.denom_ne_zero y.denom_ne_zero)
    (ratfunc.num_denom_add x y)

lemma infty_valuation_rw {x : ratfunc k} (hx : x ≠ 0) {s : polynomial k} (hs : s ≠ 0):
  (x.num.nat_degree : ℤ) - (x.denom.nat_degree)  =
  ((x.num)*s).nat_degree - (s*(x.denom)).nat_degree :=
begin
  apply infty_valuation_well_defined (ratfunc.num_ne_zero hx) x.denom_ne_zero
    (mul_ne_zero (ratfunc.num_ne_zero hx) hs) (mul_ne_zero hs x.denom_ne_zero),
  rw mul_assoc,
end

variable (k)
lemma infty_valuation.map_add' (x y : ratfunc k) :
  infty_valuation_def k (x + y) ≤ max (infty_valuation_def k x) (infty_valuation_def k y) :=
begin
  by_cases hx : x = 0,
    { rw [hx, zero_add],
      conv_rhs {rw [infty_valuation_def, if_pos (eq.refl _)]},
      rw max_eq_right (with_zero.zero_le (infty_valuation_def k y)),
      exact le_refl _, },
    { by_cases hy : y = 0,
        { rw [hy, add_zero],
          conv_rhs {rw [max_comm, infty_valuation_def, if_pos (eq.refl _)]},
          rw max_eq_right (with_zero.zero_le (infty_valuation_def k x)),
          exact le_refl _ },
        { by_cases hxy : x + y = 0,
          { rw [infty_valuation_def, if_pos hxy], exact zero_le',},
          { rw [infty_valuation_def, infty_valuation_def, infty_valuation_def, if_neg hx,
              if_neg hy, if_neg hxy, infty_valuation_add_rw hxy,
              infty_valuation_rw hx y.denom_ne_zero, mul_comm y.denom,
              infty_valuation_rw hy x.denom_ne_zero, le_max_iff, with_zero.coe_le_coe, of_add_le,
              with_zero.coe_le_coe, of_add_le, sub_le_sub_iff_right, int.coe_nat_le,
              sub_le_sub_iff_right, int.coe_nat_le, ← le_max_iff, mul_comm y.num],
            exact polynomial.nat_degree_add_le _ _, }}},
end

/-- The valuation at infinity on `k(t)`. -/
def infty_valuation  : valuation (ratfunc k) (with_zero (multiplicative ℤ)) :=
{ to_fun    := infty_valuation_def k,
  map_zero' := infty_valuation.map_zero' k,
  map_one'  := infty_valuation.map_one' k,
  map_mul'  := infty_valuation.map_mul' k,
  map_add_le_max'  := infty_valuation.map_add' k }

/-- The valued field `k(t)` with the valuation at infinity. -/
def infty_valued_kt : valued (ratfunc k) (with_zero (multiplicative ℤ)) :=
⟨infty_valuation k⟩

lemma infty_valued_kt.def {x : ratfunc k} :
  @valued.v (ratfunc k) _ _ _ (infty_valued_kt k) (x) = infty_valuation_def k x := rfl

/-- The topology structure on `k(t)` induced by the valuation at infinity. -/
def tsq' : topological_space (ratfunc k) :=
@valued.topological_space (ratfunc k) _ _ _ (infty_valued_kt k)

lemma tdrq' : @topological_division_ring (ratfunc k) _ (tsq' k) :=
@valued.topological_division_ring (ratfunc k) _ _ _ (infty_valued_kt k)

lemma trq' : @topological_ring (ratfunc k) (tsq' k) _ := infer_instance

lemma tgq' : @topological_add_group (ratfunc k) (tsq' k) _ := infer_instance

/-- The uniform structure on `k(t)` induced by the valuation at infinity. -/
def usq' : uniform_space (ratfunc k) :=
@topological_add_group.to_uniform_space (ratfunc k) _ (tsq' k) _

lemma ugq' : @uniform_add_group (ratfunc k) (usq' k) _ :=
@topological_add_group_is_uniform (ratfunc k) _ (tsq' k) _

lemma cfq' : @completable_top_field (ratfunc k) _ (usq' k) :=
@valued.completable (ratfunc k) _ _ _ (infty_valued_kt k)

lemma ssq' : @separated_space (ratfunc k) (usq' k) :=
@valued_ring.separated (ratfunc k) _ _ _ (infty_valued_kt k)

/-- The completion `k((t⁻¹))`  of `k(t)` with respect to the valuation at infinity. -/
def kt_infty := @uniform_space.completion (ratfunc k) (usq' k)

instance : field (kt_infty k) :=
@field_completion (ratfunc k) _ (usq' k) (tdrq' k) _ (ugq' k)

/-- The valuation at infinity on `k(t)` extends to a valuation on `kt_infty`. -/
instance valued_kt_infty : valued (kt_infty k) (with_zero (multiplicative ℤ)):=
⟨@valued.extension_valuation (ratfunc k) _ _ _ (infty_valued_kt k)⟩

lemma valued_kt_infty.def {x : kt_infty k} :
  valued.v (x) = @valued.extension (ratfunc k) _ _ _ (infty_valued_kt k) x := rfl

instance tsq : topological_space (kt_infty k) :=
@valued.topological_space (kt_infty k) _ _ _ (valued_kt_infty k)

instance tdrq : @topological_division_ring (kt_infty k) _ (tsq k) :=
@valued.topological_division_ring (kt_infty k) _ _ _(valued_kt_infty k)

instance trq : @topological_ring (kt_infty k) (tsq k) _ := (tdrq k).to_topological_ring

instance tgq : @topological_add_group (kt_infty k) (tsq k) _ :=
@topological_ring.to_topological_add_group (kt_infty k) _ (tsq k) (trq k)

instance usq : uniform_space (kt_infty k) :=
@topological_add_group.to_uniform_space (kt_infty k) _ (tsq k) (tgq k)

instance ugq : @uniform_add_group (kt_infty k) (usq k) _ :=
@topological_add_group_is_uniform (kt_infty k) _ (tsq k) (tgq k)

instance : inhabited (kt_infty k) := ⟨(0 : kt_infty k)⟩


import algebra.ring

#check add_comm
#check zero_add
#check add_left_neg

namespace my_ring

variables {R : Type*} [ring R]

theorem add_zero (a : R) : a + 0 = a :=
begin
 rw add_comm,
 rw zero_add,
end

-- A shorter proof
example (a : R) : a + 0 = a :=
by rw [add_comm, zero_add]
  /- Multiple rewrites can be combined using the notation
     rw [t_1, ..., t_n], which is just shorthand for rewrite
     t_1, ..., rewrite t_n. -/

theorem add_right_neg (a : R) : a + -a = 0 :=
begin
  sorry,
end

end my_ring

#check add_zero
#check add_right_neg


import data.real.basic
import game.functions.bothInjective game.functions.bothSurjective
open function

/-
# Chapter 6 : Functions

## Level 3

Be sure to make use of the results in the previous two levels.
-/

/- Lemma
If $f : X \to Y$ and $g : Y \to Z$ are both bijective functions, then
the function resulting from their composition is also bijective.
-/
theorem both_bijective
    (X Y Z : set ℝ) (f : X → Y) (g : Y → Z) :
    bijective f ∧ bijective g → bijective (g ∘ f) :=
begin
    -- Since $f$ and $g$ are bijective, they are also both injective and surjective.
    rintro ⟨⟨hfi, hfs⟩, hgi, hgs⟩,
    split,
    -- Since $f$ and $g$ are injective, $g ∘ f$ is injective by a previous result.
    apply both_injective,
    split,
    repeat {assumption},
    -- Similarly, since $f$ and $g$ are surjective, $g ∘ f$ is surjective.
    apply both_surjective,
    split,
    -- Hence, since $g ∘ f$ is injective and surjective, $g ∘ f$ is bijective.
    repeat {assumption}, done
end


theorem mul_pos (a b : mynat) : a ≠ 0 → b ≠ 0 → a * b ≠ 0 :=
begin
cases a,
rw zero_mul,
intros h1 h2,
apply h1,
cases b,
rw mul_zero,
intros h1 h2,
apply h2,
intros h1 h2 h3,
rw mul_succ at h3,
rw add_succ at h3,
exact succ_ne_zero (succ a * b + a) h3,
end


import game.limits.Blockus_Time -- hide
import game.sets.L01defs -- hide
import game.sup_inf.GLBprop_if_LUBprop -- hide
import game.limits.bounded_if_convergent -- hide
import data.real.basic -- hide
import tactic.linarith -- hide
import game.limits.seq_limitProd -- hide
import game.limits.lim_recip -- hide
import game.limits.Mulv2 -- hide

namespace xena -- hide
/-
# Chapter 7 : Limits

## Level 13

Prove the quotient property of limits.

In this proof, you may find that you will have to
compare equal functions. For this, you will want to use
"funext", which basically says that if two functions
have equal parts, then they are equal.

Good luck.
-/

local notation `|`x`|` := abs x

lemma lim_quo (a : ℕ → ℝ) (b : ℕ → ℝ) (L  R  : ℝ)
    (ha : is_limit a L) (hb : is_limit b R) (hbnz : ∀ n : ℕ, b n ≠ 0) (hr : R ≠ 0):
    is_limit ( λ n, (a n) / (b n) ) (L / R) :=
    begin

       have L1 := lim_recip b R hr hb hbnz,
       set c := (λn , 1 / b n),
       have L2 := lim_mul a c L (1/R) ha L1,
       have L3 : L * (1 / R) = L / R, ring, rw L3 at L2,
       have L4 : (λn , a n * c n) = (λn, a n / b n),
       funext, have F : c n = 1 / b n, refl,
       rw F, symmetry, exact div_eq_mul_one_div (a n) (b n),
       rw L4 at L2, exact L2,

    end

end xena -- hide


import tactic -- hide
open function nat -- hide

/-
## Some more on `apply`

In the following example, `h` eats a number $x$ and a proof of the fact that $1\leq x$, and gives
a proof of the fact that $1\leq x^2$. So if we `apply h`, *Lean* can figure out that $x$ must be set to
$2$ (we could just as well type `apply h 2` to help him), but then it will want a proof of the fact that $1\leq 2$. In this case, *Lean* has in its library
a proof of
this fact, called `one_le_two`, which we can `apply` after `h`.
-/

/- Lemma : no-side-bar
Knowing that for all x, if $1\leq x$ then $1 ≤ x^2$, we can prove that $1 ≤ 2^2$.
-/
lemma l7 (h : ∀ x, 1 ≤ x → 1 ≤ x^2) : 1 ≤ 2^2:=
begin
  apply h,
  apply one_le_two,

end


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

! This file was ported from Lean 3 source module data.polynomial.coeff
! leanprover-community/mathlib commit fa256f00ce018e7b40e1dc756e403c86680bf448
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Polynomial.Basic
import Mathlib.Data.Finset.NatAntidiagonal
import Mathlib.Data.Nat.Choose.Sum

/-!
# Theory of univariate polynomials

The theorems include formulas for computing coefficients, such as
`coeff_add`, `coeff_sum`, `coeff_mul`

-/

set_option linter.uppercaseLean3 false

noncomputable section

open Finsupp Finset AddMonoidAlgebra

open BigOperators Polynomial

namespace Polynomial

universe u v

variable {R : Type u} {S : Type v} {a b : R} {n m : ℕ}

variable [Semiring R] {p q r : R[X]}

section Coeff

theorem coeff_one (n : ℕ) : coeff (1 : R[X]) n = if 0 = n then 1 else 0 :=
  coeff_monomial
#align polynomial.coeff_one Polynomial.coeff_one

@[simp]
theorem coeff_add (p q : R[X]) (n : ℕ) : coeff (p + q) n = coeff p n + coeff q n := by
  rcases p with ⟨⟩
  rcases q with ⟨⟩
  simp_rw [← ofFinsupp_add, coeff]
  exact Finsupp.add_apply _ _ _
#align polynomial.coeff_add Polynomial.coeff_add

set_option linter.deprecated false in
@[simp]
theorem coeff_bit0 (p : R[X]) (n : ℕ) : coeff (bit0 p) n = bit0 (coeff p n) := by simp [bit0]
#align polynomial.coeff_bit0 Polynomial.coeff_bit0

@[simp]
theorem coeff_smul [Monoid S] [DistribMulAction S R] (r : S) (p : R[X]) (n : ℕ) :
    coeff (r • p) n = r • coeff p n := by
  rcases p with ⟨⟩
  simp_rw [← ofFinsupp_smul, coeff]
  exact Finsupp.smul_apply _ _ _
#align polynomial.coeff_smul Polynomial.coeff_smul

theorem support_smul [Monoid S] [DistribMulAction S R] (r : S) (p : R[X]) :
    support (r • p) ⊆ support p := by
  intro i hi
  simp [mem_support_iff] at hi⊢
  contrapose! hi
  simp [hi]
#align polynomial.support_smul Polynomial.support_smul

/-- `Polynomial.sum` as a linear map. -/
@[simps]
def lsum {R A M : Type _} [Semiring R] [Semiring A] [AddCommMonoid M] [Module R A] [Module R M]
    (f : ℕ → A →ₗ[R] M) : A[X] →ₗ[R] M
    where
  toFun p := p.sum fun n r => f n r
  map_add' p q := sum_add_index p q _ (fun n => (f n).map_zero) fun n _ _ => (f n).map_add _ _
  map_smul' c p := by
    -- Porting note: `dsimp only []` is required for beta reduction.
    dsimp only []
    rw [sum_eq_of_subset _ (fun n r => f n r) (fun n => (f n).map_zero) _ (support_smul c p)]
    simp only [sum_def, Finset.smul_sum, coeff_smul, LinearMap.map_smul, RingHom.id_apply]
#align polynomial.lsum Polynomial.lsum
#align polynomial.lsum_apply Polynomial.lsum_apply

variable (R)

/-- The nth coefficient, as a linear map. -/
def lcoeff (n : ℕ) : R[X] →ₗ[R] R where
  toFun p := coeff p n
  map_add' p q := coeff_add p q n
  map_smul' r p := coeff_smul r p n
#align polynomial.lcoeff Polynomial.lcoeff

variable {R}

@[simp]
theorem lcoeff_apply (n : ℕ) (f : R[X]) : lcoeff R n f = coeff f n :=
  rfl
#align polynomial.lcoeff_apply Polynomial.lcoeff_apply

@[simp]
theorem finset_sum_coeff {ι : Type _} (s : Finset ι) (f : ι → R[X]) (n : ℕ) :
    coeff (∑ b in s, f b) n = ∑ b in s, coeff (f b) n :=
  (lcoeff R n).map_sum
#align polynomial.finset_sum_coeff Polynomial.finset_sum_coeff

theorem coeff_sum [Semiring S] (n : ℕ) (f : ℕ → R → S[X]) :
    coeff (p.sum f) n = p.sum fun a b => coeff (f a b) n := by
  rcases p with ⟨⟩
  -- Porting note: Was `simp [Polynomial.sum, support, coeff]`.
  simp [Polynomial.sum, support_ofFinsupp, coeff_ofFinsupp]
#align polynomial.coeff_sum Polynomial.coeff_sum

/-- Decomposes the coefficient of the product `p * q` as a sum
over `Nat.antidiagonal`. A version which sums over `range (n + 1)` can be obtained
by using `Finset.Nat.sum_antidiagonal_eq_sum_range_succ`. -/
theorem coeff_mul (p q : R[X]) (n : ℕ) :
    coeff (p * q) n = ∑ x in Nat.antidiagonal n, coeff p x.1 * coeff q x.2 := by
  rcases p with ⟨p⟩; rcases q with ⟨q⟩
  simp_rw [← ofFinsupp_mul, coeff]
  exact AddMonoidAlgebra.mul_apply_antidiagonal p q n _ Nat.mem_antidiagonal
#align polynomial.coeff_mul Polynomial.coeff_mul

@[simp]
theorem mul_coeff_zero (p q : R[X]) : coeff (p * q) 0 = coeff p 0 * coeff q 0 := by simp [coeff_mul]
#align polynomial.mul_coeff_zero Polynomial.mul_coeff_zero

/-- `constantCoeff p` returns the constant term of the polynomial `p`,
  defined as `coeff p 0`. This is a ring homomorphism. -/
@[simps]
def constantCoeff : R[X] →+* R where
  toFun p := coeff p 0
  map_one' := coeff_one_zero
  map_mul' := mul_coeff_zero
  map_zero' := coeff_zero 0
  map_add' p q := coeff_add p q 0
#align polynomial.constant_coeff Polynomial.constantCoeff
#align polynomial.constant_coeff_apply Polynomial.constantCoeff_apply

theorem isUnit_C {x : R} : IsUnit (C x) ↔ IsUnit x :=
  ⟨fun h => (congr_arg IsUnit coeff_C_zero).mp (h.map <| @constantCoeff R _), fun h => h.map C⟩
#align polynomial.is_unit_C Polynomial.isUnit_C

theorem coeff_mul_X_zero (p : R[X]) : coeff (p * X) 0 = 0 := by simp
#align polynomial.coeff_mul_X_zero Polynomial.coeff_mul_X_zero

theorem coeff_X_mul_zero (p : R[X]) : coeff (X * p) 0 = 0 := by simp
#align polynomial.coeff_X_mul_zero Polynomial.coeff_X_mul_zero

theorem coeff_C_mul_X_pow (x : R) (k n : ℕ) :
    coeff (C x * X ^ k : R[X]) n = if n = k then x else 0 := by
  rw [C_mul_X_pow_eq_monomial, coeff_monomial]
  congr 1
  simp [eq_comm]
#align polynomial.coeff_C_mul_X_pow Polynomial.coeff_C_mul_X_pow

theorem coeff_C_mul_X (x : R) (n : ℕ) : coeff (C x * X : R[X]) n = if n = 1 then x else 0 := by
  rw [← pow_one X, coeff_C_mul_X_pow]
#align polynomial.coeff_C_mul_X Polynomial.coeff_C_mul_X

@[simp]
theorem coeff_C_mul (p : R[X]) : coeff (C a * p) n = a * coeff p n := by
  rcases p with ⟨p⟩
  simp_rw [← monomial_zero_left, ← ofFinsupp_single, ← ofFinsupp_mul, coeff]
  exact AddMonoidAlgebra.single_zero_mul_apply p a n
#align polynomial.coeff_C_mul Polynomial.coeff_C_mul

theorem C_mul' (a : R) (f : R[X]) : C a * f = a • f := by
  ext
  rw [coeff_C_mul, coeff_smul, smul_eq_mul]
#align polynomial.C_mul' Polynomial.C_mul'

@[simp]
theorem coeff_mul_C (p : R[X]) (n : ℕ) (a : R) : coeff (p * C a) n = coeff p n * a := by
  rcases p with ⟨p⟩
  simp_rw [← monomial_zero_left, ← ofFinsupp_single, ← ofFinsupp_mul, coeff]
  exact AddMonoidAlgebra.mul_single_zero_apply p a n
#align polynomial.coeff_mul_C Polynomial.coeff_mul_C

theorem coeff_X_pow (k n : ℕ) : coeff (X ^ k : R[X]) n = if n = k then 1 else 0 := by
  simp only [one_mul, RingHom.map_one, ← coeff_C_mul_X_pow]
#align polynomial.coeff_X_pow Polynomial.coeff_X_pow

@[simp]
theorem coeff_X_pow_self (n : ℕ) : coeff (X ^ n : R[X]) n = 1 := by simp [coeff_X_pow]
#align polynomial.coeff_X_pow_self Polynomial.coeff_X_pow_self

section Fewnomials

open Finset

theorem support_binomial {k m : ℕ} (hkm : k ≠ m) {x y : R} (hx : x ≠ 0) (hy : y ≠ 0) :
    support (C x * X ^ k + C y * X ^ m) = {k, m} := by
  apply subset_antisymm (support_binomial' k m x y)
  simp_rw [insert_subset, singleton_subset_iff, mem_support_iff, coeff_add, coeff_C_mul,
    coeff_X_pow_self, mul_one, coeff_X_pow, if_neg hkm, if_neg hkm.symm, mul_zero, zero_add,
    add_zero, Ne.def, hx, hy]
#align polynomial.support_binomial Polynomial.support_binomial

theorem support_trinomial {k m n : ℕ} (hkm : k < m) (hmn : m < n) {x y z : R} (hx : x ≠ 0)
    (hy : y ≠ 0) (hz : z ≠ 0) :
    support (C x * X ^ k + C y * X ^ m + C z * X ^ n) = {k, m, n} := by
  apply subset_antisymm (support_trinomial' k m n x y z)
  simp_rw [insert_subset, singleton_subset_iff, mem_support_iff, coeff_add, coeff_C_mul,
    coeff_X_pow_self, mul_one, coeff_X_pow, if_neg hkm.ne, if_neg hkm.ne', if_neg hmn.ne,
    if_neg hmn.ne', if_neg (hkm.trans hmn).ne, if_neg (hkm.trans hmn).ne', mul_zero, add_zero,
    zero_add, Ne.def, hx, hy, hz]
#align polynomial.support_trinomial Polynomial.support_trinomial

theorem card_support_binomial {k m : ℕ} (h : k ≠ m) {x y : R} (hx : x ≠ 0) (hy : y ≠ 0) :
    card (support (C x * X ^ k + C y * X ^ m)) = 2 := by
  rw [support_binomial h hx hy, card_insert_of_not_mem (mt mem_singleton.mp h), card_singleton]
#align polynomial.card_support_binomial Polynomial.card_support_binomial

theorem card_support_trinomial {k m n : ℕ} (hkm : k < m) (hmn : m < n) {x y z : R} (hx : x ≠ 0)
    (hy : y ≠ 0) (hz : z ≠ 0) : card (support (C x * X ^ k + C y * X ^ m + C z * X ^ n)) = 3 := by
  rw [support_trinomial hkm hmn hx hy hz,
    card_insert_of_not_mem
      (mt mem_insert.mp (not_or_of_not hkm.ne (mt mem_singleton.mp (hkm.trans hmn).ne))),
    card_insert_of_not_mem (mt mem_singleton.mp hmn.ne), card_singleton]
#align polynomial.card_support_trinomial Polynomial.card_support_trinomial

end Fewnomials

@[simp]
theorem coeff_mul_X_pow (p : R[X]) (n d : ℕ) :
    coeff (p * Polynomial.X ^ n) (d + n) = coeff p d := by
  rw [coeff_mul, sum_eq_single (d, n), coeff_X_pow, if_pos rfl, mul_one]
  · rintro ⟨i, j⟩ h1 h2
    rw [coeff_X_pow, if_neg, mul_zero]
    rintro rfl
    apply h2
    rw [Nat.mem_antidiagonal, add_right_cancel_iff] at h1
    subst h1
    rfl
  · exact fun h1 => (h1 (Nat.mem_antidiagonal.2 rfl)).elim
#align polynomial.coeff_mul_X_pow Polynomial.coeff_mul_X_pow

@[simp]
theorem coeff_X_pow_mul (p : R[X]) (n d : ℕ) : coeff (Polynomial.X ^ n * p) (d + n) = coeff p d :=
  by rw [(commute_X_pow p n).eq, coeff_mul_X_pow]
#align polynomial.coeff_X_pow_mul Polynomial.coeff_X_pow_mul

theorem coeff_mul_X_pow' (p : R[X]) (n d : ℕ) :
    (p * X ^ n).coeff d = ite (n ≤ d) (p.coeff (d - n)) 0 := by
  split_ifs with h
  · rw [← tsub_add_cancel_of_le h, coeff_mul_X_pow, add_tsub_cancel_right]
  · refine' (coeff_mul _ _ _).trans (Finset.sum_eq_zero fun x hx => _)
    rw [coeff_X_pow, if_neg, mul_zero]
    exact ((le_of_add_le_right (Finset.Nat.mem_antidiagonal.mp hx).le).trans_lt <| not_le.mp h).ne
#align polynomial.coeff_mul_X_pow' Polynomial.coeff_mul_X_pow'

theorem coeff_X_pow_mul' (p : R[X]) (n d : ℕ) :
    (X ^ n * p).coeff d = ite (n ≤ d) (p.coeff (d - n)) 0 := by
  rw [(commute_X_pow p n).eq, coeff_mul_X_pow']
#align polynomial.coeff_X_pow_mul' Polynomial.coeff_X_pow_mul'

@[simp]
theorem coeff_mul_X (p : R[X]) (n : ℕ) : coeff (p * X) (n + 1) = coeff p n := by
  simpa only [pow_one] using coeff_mul_X_pow p 1 n
#align polynomial.coeff_mul_X Polynomial.coeff_mul_X

@[simp]
theorem coeff_X_mul (p : R[X]) (n : ℕ) : coeff (X * p) (n + 1) = coeff p n := by
  rw [(commute_X p).eq, coeff_mul_X]
#align polynomial.coeff_X_mul Polynomial.coeff_X_mul

theorem coeff_mul_monomial (p : R[X]) (n d : ℕ) (r : R) :
    coeff (p * monomial n r) (d + n) = coeff p d * r := by
  rw [← C_mul_X_pow_eq_monomial, ← X_pow_mul, ← mul_assoc, coeff_mul_C, coeff_mul_X_pow]
#align polynomial.coeff_mul_monomial Polynomial.coeff_mul_monomial

theorem coeff_monomial_mul (p : R[X]) (n d : ℕ) (r : R) :
    coeff (monomial n r * p) (d + n) = r * coeff p d := by
  rw [← C_mul_X_pow_eq_monomial, mul_assoc, coeff_C_mul, X_pow_mul, coeff_mul_X_pow]
#align polynomial.coeff_monomial_mul Polynomial.coeff_monomial_mul

-- This can already be proved by `simp`.
theorem coeff_mul_monomial_zero (p : R[X]) (d : ℕ) (r : R) :
    coeff (p * monomial 0 r) d = coeff p d * r :=
  coeff_mul_monomial p 0 d r
#align polynomial.coeff_mul_monomial_zero Polynomial.coeff_mul_monomial_zero

-- This can already be proved by `simp`.
theorem coeff_monomial_zero_mul (p : R[X]) (d : ℕ) (r : R) :
    coeff (monomial 0 r * p) d = r * coeff p d :=
  coeff_monomial_mul p 0 d r
#align polynomial.coeff_monomial_zero_mul Polynomial.coeff_monomial_zero_mul

theorem mul_X_pow_eq_zero {p : R[X]} {n : ℕ} (H : p * X ^ n = 0) : p = 0 :=
  ext fun k => (coeff_mul_X_pow p n k).symm.trans <| ext_iff.1 H (k + n)
#align polynomial.mul_X_pow_eq_zero Polynomial.mul_X_pow_eq_zero

theorem mul_X_pow_injective (n : ℕ) : Function.Injective fun P : R[X] => X ^ n * P := by
  intro P Q hPQ
  simp only at hPQ
  ext i
  rw [← coeff_X_pow_mul P n i, hPQ, coeff_X_pow_mul Q n i]
#align polynomial.mul_X_pow_injective Polynomial.mul_X_pow_injective

theorem mul_X_injective : Function.Injective fun P : R[X] => X * P :=
  pow_one (X : R[X]) ▸ mul_X_pow_injective 1
#align polynomial.mul_X_injective Polynomial.mul_X_injective

theorem coeff_X_add_C_pow (r : R) (n k : ℕ) :
    ((X + C r) ^ n).coeff k = r ^ (n - k) * (n.choose k : R) := by
  rw [(commute_X (C r : R[X])).add_pow, ← lcoeff_apply, LinearMap.map_sum]
  simp only [one_pow, mul_one, lcoeff_apply, ← C_eq_nat_cast, ← C_pow, coeff_mul_C, Nat.cast_id]
  rw [Finset.sum_eq_single k, coeff_X_pow_self, one_mul]
  · intro _ _ h
    simp [coeff_X_pow, h.symm]
  · simp only [coeff_X_pow_self, one_mul, not_lt, Finset.mem_range]
    intro h
    rw [Nat.choose_eq_zero_of_lt h, Nat.cast_zero, mul_zero]
#align polynomial.coeff_X_add_C_pow Polynomial.coeff_X_add_C_pow

theorem coeff_one_add_X_pow (R : Type _) [Semiring R] (n k : ℕ) :
    ((1 + X) ^ n).coeff k = (n.choose k : R) := by rw [add_comm _ X, coeff_X_add_one_pow]
#align polynomial.coeff_one_add_X_pow Polynomial.coeff_one_add_X_pow

theorem C_dvd_iff_dvd_coeff (r : R) (φ : R[X]) : C r ∣ φ ↔ ∀ i, r ∣ φ.coeff i := by
  constructor
  · rintro ⟨φ, rfl⟩ c
    rw [coeff_C_mul]
    apply dvd_mul_right
  · intro h
    choose c hc using h
    classical
      let c' : ℕ → R := fun i => if i ∈ φ.support then c i else 0
      let ψ : R[X] := ∑ i in φ.support, monomial i (c' i)
      use ψ
      ext i
      simp only [coeff_C_mul, mem_support_iff, coeff_monomial, finset_sum_coeff,
        Finset.sum_ite_eq']
      split_ifs with hi
      · rw [hc]
      · rw [Classical.not_not] at hi
        rwa [mul_zero]
#align polynomial.C_dvd_iff_dvd_coeff Polynomial.C_dvd_iff_dvd_coeff

set_option linter.deprecated false in
theorem coeff_bit0_mul (P Q : R[X]) (n : ℕ) : coeff (bit0 P * Q) n = 2 * coeff (P * Q) n := by
  -- Porting note: `two_mul` is required.
  simp [bit0, add_mul, two_mul]
#align polynomial.coeff_bit0_mul Polynomial.coeff_bit0_mul

set_option linter.deprecated false in
theorem coeff_bit1_mul (P Q : R[X]) (n : ℕ) :
    coeff (bit1 P * Q) n = 2 * coeff (P * Q) n + coeff Q n := by
  simp [bit1, add_mul, coeff_bit0_mul]
#align polynomial.coeff_bit1_mul Polynomial.coeff_bit1_mul

theorem smul_eq_C_mul (a : R) : a • p = C a * p := by simp [ext_iff]
#align polynomial.smul_eq_C_mul Polynomial.smul_eq_C_mul

theorem update_eq_add_sub_coeff {R : Type _} [Ring R] (p : R[X]) (n : ℕ) (a : R) :
    p.update n a = p + Polynomial.C (a - p.coeff n) * Polynomial.X ^ n := by
  ext
  rw [coeff_update_apply, coeff_add, coeff_C_mul_X_pow]
  split_ifs with h <;> simp [h]
#align polynomial.update_eq_add_sub_coeff Polynomial.update_eq_add_sub_coeff

end Coeff

section cast

@[simp]
theorem nat_cast_coeff_zero {n : ℕ} {R : Type _} [Semiring R] : (n : R[X]).coeff 0 = n := by
  induction' n with n ih
  · simp
  · simp [ih]
#align polynomial.nat_cast_coeff_zero Polynomial.nat_cast_coeff_zero

@[norm_cast] -- @[simp] -- Porting note: simp can prove this
theorem nat_cast_inj {m n : ℕ} {R : Type _} [Semiring R] [CharZero R] :
    (↑m : R[X]) = ↑n ↔ m = n := by
  constructor
  · intro h
    apply_fun fun p => p.coeff 0  at h
    simpa using h
  · rintro rfl
    rfl
#align polynomial.nat_cast_inj Polynomial.nat_cast_inj

@[simp]
theorem int_cast_coeff_zero {i : ℤ} {R : Type _} [Ring R] : (i : R[X]).coeff 0 = i := by
  cases i <;> simp
#align polynomial.int_cast_coeff_zero Polynomial.int_cast_coeff_zero

@[norm_cast] -- @[simp] -- Porting note: simp can prove this
theorem int_cast_inj {m n : ℤ} {R : Type _} [Ring R] [CharZero R] : (↑m : R[X]) = ↑n ↔ m = n := by
  constructor
  · intro h
    apply_fun fun p => p.coeff 0  at h
    simpa using h
  · rintro rfl
    rfl
#align polynomial.int_cast_inj Polynomial.int_cast_inj

end cast

instance charZero [CharZero R] : CharZero R[X] where cast_injective _x _y := nat_cast_inj.mp
#align polynomial.char_zero Polynomial.charZero

end Polynomial


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Jens Wagemaker
-/
import data.polynomial.algebra_map
import data.polynomial.degree.lemmas
import data.polynomial.monic

/-!
# Theory of monic polynomials

We define `integral_normalization`, which relate arbitrary polynomials to monic ones.
-/

open_locale big_operators polynomial

namespace polynomial
universes u v y
variables {R : Type u} {S : Type v} {a b : R} {m n : ℕ} {ι : Type y}

section integral_normalization

section semiring
variables [semiring R]

/-- If `f : R[X]` is a nonzero polynomial with root `z`, `integral_normalization f` is
a monic polynomial with root `leading_coeff f * z`.

Moreover, `integral_normalization 0 = 0`.
-/
noncomputable def integral_normalization (f : R[X]) : R[X] :=
∑ i in f.support, monomial i (if f.degree = i then 1 else
  coeff f i * f.leading_coeff ^ (f.nat_degree - 1 - i))

@[simp] lemma integral_normalization_zero :
  integral_normalization (0 : R[X]) = 0 :=
by simp [integral_normalization]

lemma integral_normalization_coeff {f : R[X]} {i : ℕ} :
  (integral_normalization f).coeff i =
    if f.degree = i then 1 else coeff f i * f.leading_coeff ^ (f.nat_degree - 1 - i) :=
have f.coeff i = 0 → f.degree ≠ i, from λ hc hd, coeff_ne_zero_of_eq_degree hd hc,
by simp [integral_normalization, coeff_monomial, this, mem_support_iff] {contextual := tt}

lemma integral_normalization_support {f : R[X]} :
  (integral_normalization f).support ⊆ f.support :=
by { intro, simp [integral_normalization, coeff_monomial, mem_support_iff] {contextual := tt} }

lemma integral_normalization_coeff_degree {f : R[X]} {i : ℕ} (hi : f.degree = i) :
  (integral_normalization f).coeff i = 1 :=
by rw [integral_normalization_coeff, if_pos hi]

lemma integral_normalization_coeff_nat_degree {f : R[X]} (hf : f ≠ 0) :
  (integral_normalization f).coeff (nat_degree f) = 1 :=
integral_normalization_coeff_degree (degree_eq_nat_degree hf)

lemma integral_normalization_coeff_ne_degree {f : R[X]} {i : ℕ} (hi : f.degree ≠ i) :
  coeff (integral_normalization f) i = coeff f i * f.leading_coeff ^ (f.nat_degree - 1 - i) :=
by rw [integral_normalization_coeff, if_neg hi]

lemma integral_normalization_coeff_ne_nat_degree
  {f : R[X]} {i : ℕ} (hi : i ≠ nat_degree f) :
  coeff (integral_normalization f) i = coeff f i * f.leading_coeff ^ (f.nat_degree - 1 - i) :=
integral_normalization_coeff_ne_degree (degree_ne_of_nat_degree_ne hi.symm)

lemma monic_integral_normalization {f : R[X]} (hf : f ≠ 0) :
  monic (integral_normalization f) :=
monic_of_degree_le f.nat_degree
  (finset.sup_le $ λ i h, with_bot.coe_le_coe.2 $
    le_nat_degree_of_mem_supp i $ integral_normalization_support h)
  (integral_normalization_coeff_nat_degree hf)

end semiring

section is_domain
variables [ring R] [is_domain R]

@[simp] lemma support_integral_normalization {f : R[X]} :
  (integral_normalization f).support = f.support :=
begin
  by_cases hf : f = 0, { simp [hf] },
  ext i,
  refine ⟨λ h, integral_normalization_support h, _⟩,
  simp only [integral_normalization_coeff, mem_support_iff],
  intro hfi,
  split_ifs with hi; simp [hfi, hi, pow_ne_zero _ (leading_coeff_ne_zero.mpr hf)]
end
end is_domain

section is_domain
variables [comm_ring R] [is_domain R]
variables [comm_ring S]

lemma integral_normalization_eval₂_eq_zero {p : R[X]} (f : R →+* S)
  {z : S} (hz : eval₂ f z p = 0) (inj : ∀ (x : R), f x = 0 → x = 0) :
  eval₂ f (z * f p.leading_coeff) (integral_normalization p) = 0 :=
calc eval₂ f (z * f p.leading_coeff) (integral_normalization p)
    = p.support.attach.sum
        (λ i, f (coeff (integral_normalization p) i.1 * p.leading_coeff ^ i.1) * z ^ i.1) :
      by { rw [eval₂, sum_def, support_integral_normalization],
           simp only [mul_comm z, mul_pow, mul_assoc, ring_hom.map_pow, ring_hom.map_mul],
           exact finset.sum_attach.symm }
... = p.support.attach.sum
        (λ i, f (coeff p i.1 * p.leading_coeff ^ (nat_degree p - 1)) * z ^ i.1) :
      begin
        by_cases hp : p = 0, { simp [hp] },
        have one_le_deg : 1 ≤ nat_degree p :=
          nat.succ_le_of_lt (nat_degree_pos_of_eval₂_root hp f hz inj),
        congr' with i,
        congr' 2,
        by_cases hi : i.1 = nat_degree p,
        { rw [hi, integral_normalization_coeff_degree, one_mul, leading_coeff, ←pow_succ,
              tsub_add_cancel_of_le one_le_deg],
          exact degree_eq_nat_degree hp },
        { have : i.1 ≤ p.nat_degree - 1 := nat.le_pred_of_lt (lt_of_le_of_ne
            (le_nat_degree_of_ne_zero (mem_support_iff.mp i.2)) hi),
          rw [integral_normalization_coeff_ne_nat_degree hi, mul_assoc, ←pow_add,
              tsub_add_cancel_of_le this] }
      end
... = f p.leading_coeff ^ (nat_degree p - 1) * eval₂ f z p :
      by { simp_rw [eval₂, sum_def, λ i, mul_comm (coeff p i), ring_hom.map_mul,
                    ring_hom.map_pow, mul_assoc, ←finset.mul_sum],
           congr' 1,
           exact @finset.sum_attach _ _ p.support _ (λ i, f (p.coeff i) * z ^ i) }
... = 0 : by rw [hz, _root_.mul_zero]

end is_domain

end integral_normalization

end polynomial


/-
Copyright (c) 2020 Johan Commelin. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johan Commelin, Robert Y. Lewis

! This file was ported from Lean 3 source module data.mv_polynomial.expand
! leanprover-community/mathlib commit 5da451b4c96b4c2e122c0325a7fce17d62ee46c6
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.MvPolynomial.Monad

/-!
## Expand multivariate polynomials

Given a multivariate polynomial `φ`, one may replace every occurence of `X i` by `X i ^ n`,
for some natural number `n`.
This operation is called `mv_polynomial.expand` and it is an algebra homomorphism.

### Main declaration

* `mv_polynomial.expand`: expand a polynomial by a factor of p, so `∑ aₙ xⁿ` becomes `∑ aₙ xⁿᵖ`.
-/

open BigOperators

namespace MvPolynomial

variable {σ τ R S : Type _} [CommSemiring R] [CommSemiring S]

/-- Expand the polynomial by a factor of p, so `∑ aₙ xⁿ` becomes `∑ aₙ xⁿᵖ`.

See also `polynomial.expand`. -/
noncomputable def expand (p : ℕ) : MvPolynomial σ R →ₐ[R] MvPolynomial σ R :=
  { (eval₂Hom C fun i => X i ^ p : MvPolynomial σ R →+* MvPolynomial σ R) with
    commutes' := fun r => eval₂Hom_C _ _ _ }
#align mv_polynomial.expand MvPolynomial.expand

@[simp]
theorem expand_c (p : ℕ) (r : R) : expand p (C r : MvPolynomial σ R) = C r :=
  eval₂Hom_C _ _ _
#align mv_polynomial.expand_C MvPolynomial.expand_c

@[simp]
theorem expand_x (p : ℕ) (i : σ) : expand p (X i : MvPolynomial σ R) = X i ^ p :=
  eval₂Hom_X' _ _ _
#align mv_polynomial.expand_X MvPolynomial.expand_x

@[simp]
theorem expand_monomial (p : ℕ) (d : σ →₀ ℕ) (r : R) :
    expand p (monomial d r) = C r * ∏ i in d.support, (X i ^ p) ^ d i :=
  bind₁_monomial _ _ _
#align mv_polynomial.expand_monomial MvPolynomial.expand_monomial

theorem expand_one_apply (f : MvPolynomial σ R) : expand 1 f = f := by
  simp only [expand, bind₁_X_left, AlgHom.id_apply, RingHom.toFun_eq_coe, eval₂_hom_C_left,
    AlgHom.coe_toRingHom, pow_one, AlgHom.coe_mks]
#align mv_polynomial.expand_one_apply MvPolynomial.expand_one_apply

@[simp]
theorem expand_one : expand 1 = AlgHom.id R (MvPolynomial σ R) :=
  by
  ext1 f
  rw [expand_one_apply, AlgHom.id_apply]
#align mv_polynomial.expand_one MvPolynomial.expand_one

theorem expand_comp_bind₁ (p : ℕ) (f : σ → MvPolynomial τ R) :
    (expand p).comp (bind₁ f) = bind₁ fun i => expand p (f i) :=
  by
  apply alg_hom_ext
  intro i
  simp only [AlgHom.comp_apply, bind₁_X_right]
#align mv_polynomial.expand_comp_bind₁ MvPolynomial.expand_comp_bind₁

theorem expand_bind₁ (p : ℕ) (f : σ → MvPolynomial τ R) (φ : MvPolynomial σ R) :
    expand p (bind₁ f φ) = bind₁ (fun i => expand p (f i)) φ := by
  rw [← AlgHom.comp_apply, expand_comp_bind₁]
#align mv_polynomial.expand_bind₁ MvPolynomial.expand_bind₁

@[simp]
theorem map_expand (f : R →+* S) (p : ℕ) (φ : MvPolynomial σ R) :
    map f (expand p φ) = expand p (map f φ) := by simp [expand, map_bind₁]
#align mv_polynomial.map_expand MvPolynomial.map_expand

@[simp]
theorem rename_expand (f : σ → τ) (p : ℕ) (φ : MvPolynomial σ R) :
    rename f (expand p φ) = expand p (rename f φ) := by simp [expand, bind₁_rename, rename_bind₁]
#align mv_polynomial.rename_expand MvPolynomial.rename_expand

@[simp]
theorem rename_comp_expand (f : σ → τ) (p : ℕ) :
    (rename f).comp (expand p) =
      (expand p).comp (rename f : MvPolynomial σ R →ₐ[R] MvPolynomial τ R) :=
  by
  ext1 φ
  simp only [rename_expand, AlgHom.comp_apply]
#align mv_polynomial.rename_comp_expand MvPolynomial.rename_comp_expand

end MvPolynomial


/-
The "exact" and "apply" tactics construct proofs
of the current goal.

The exact tactic is meant to be given a *complete*
proof term, with no remaining holes (placeholders)
that remain to be filled in.

The apply tactic will accept a complete proof term,
and so can be used anywhere "exact" is used, but it
is reall meant to accept proof terms with holes that
remain to be filled in with actual values. These
holes are generally arguments that need to be provided
to some inference rule, or proof of an implication or
generalization, for it to construct a final, complete
proof.

Let's see some simple examples. (We start by stating
that P Q and R should be treated as variables of type
Prop).
-/

variables P Q R : Prop  -- assume these are props

/-
Now recall the inference rule for ∧.

(p : Q) (q : Q) ⊢ (pq : P ∧ Q) [and.intro]

In other words, and.intro is a procedure
that *takes* two arguments, here called p
and q, and *constructs* a proof of P ∧ Q.
-/

-- an example using exact
example : P → Q → P ∧ Q :=
begin
  assume (p : P) (q :Q),
  exact (and.intro p q),
  -- QED!
end

-- an example using apply
example : P → Q → P ∧ Q :=
begin
  assume (p : P) (q :Q),
  apply (and.intro _ _),
  -- The _'s become subgoals
  exact p,
  exact q,
  -- This is top-down structured decomposition
end

-- you can leave out the _ _ by the way
example : P → Q → P ∧ Q :=
begin
  assume (p : P) (q :Q),
  apply (and.intro),      -- _ _ left out
  exact p,                -- now we fill holes
  exact q,
end

-- You can even provide *some* arguments!

-- you can leave out the _ _ by the way
example : P → Q → P ∧ Q :=
begin
  assume (p : P) (q :Q),
  apply (and.intro _ q),      -- _ _ left out
  exact p,                -- now we fill holes
end

example : P ↔ Q :=
begin
  apply iff.intro _ _,
end


import set_theory.pgame

universe u

/-!
# Basic definitions about who has a winning stratergy

We define `G.p_position`, `G.n_position`, `G.l_position` and `G.r_position`
for a pgame `G`, which means the second, first, left and right players
have a winning stratergy respectivly.
These are defined by inequalities which can be unfolded with, `pgame.lt_def`
and `pgame.le_def`.
-/

namespace pgame

local infix ` ≈ ` := pgame.equiv

/-- The player who goes first loses -/
def p_position (G : pgame) : Prop := G ≤ 0 ∧ 0 ≤ G

/-- The player who goes first wins -/
def n_position (G : pgame) : Prop := 0 < G ∧ G < 0

/-- The left player can always win -/
def l_position (G : pgame) : Prop := 0 < G ∧ 0 ≤ G

/-- The right player can always win -/
def r_position (G : pgame) : Prop := G ≤ 0 ∧ G < 0

theorem zero_p_postition : p_position 0 := by tidy
theorem one_l_postition : l_position 1 :=
begin
    split,
    rw lt_def_le,
    tidy
end
theorem star_n_postition : n_position star := ⟨ zero_lt_star, star_lt_zero ⟩
theorem omega_l_postition : l_position omega :=
begin
  split,
    rw lt_def_le,
    left,
    use 0,
  tidy
end

lemma position_cases (G : pgame) : G.l_position ∨ G.r_position ∨ G.p_position ∨ G.n_position :=
begin
  classical,
  by_cases hpos : 0 < G;
  by_cases hneg : G < 0;
  { try { rw not_lt at hpos },
    try { rw not_lt at hneg },
    try { left, exact ⟨ hpos, hneg ⟩ },
    try { right, left, exact ⟨ hpos, hneg ⟩ },
    try { right, right, left, exact ⟨ hpos, hneg ⟩ },
    try { right, right, right, exact ⟨ hpos, hneg ⟩ } }
end

lemma p_position_is_zero {G : pgame} : G.p_position ↔ G ≈ 0 := by refl

lemma p_position_of_equiv {G H : pgame} (h : G ≈ H) : G.p_position → H.p_position :=
λ hGp, ⟨ le_of_equiv_of_le h.symm hGp.1, le_of_le_of_equiv hGp.2 h ⟩
lemma n_position_of_equiv {G H : pgame} (h : G ≈ H) : G.n_position → H.n_position :=
λ hGn, ⟨ lt_of_lt_of_equiv hGn.1 h, lt_of_equiv_of_lt h.symm hGn.2 ⟩
lemma l_position_of_equiv {G H : pgame} (h : G ≈ H) : G.l_position → H.l_position :=
λ hGl, ⟨ lt_of_lt_of_equiv hGl.1 h, le_of_le_of_equiv hGl.2 h ⟩
lemma r_position_of_equiv {G H : pgame} (h : G ≈ H) : G.r_position → H.r_position :=
λ hGr, ⟨ le_of_equiv_of_le h.symm hGr.1, lt_of_equiv_of_lt h.symm hGr.2 ⟩

lemma p_position_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.p_position ↔ H.p_position :=
⟨ p_position_of_equiv h, p_position_of_equiv h.symm ⟩
lemma n_position_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.n_position ↔ H.n_position :=
⟨ n_position_of_equiv h, n_position_of_equiv h.symm ⟩
lemma l_position_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.l_position ↔ H.l_position :=
⟨ l_position_of_equiv h, l_position_of_equiv h.symm ⟩
lemma r_position_of_equiv_iff {G H : pgame} (h : G ≈ H) : G.r_position ↔ H.r_position :=
⟨ r_position_of_equiv h, r_position_of_equiv h.symm ⟩

end pgame


-- BOTH:
import data.real.basic
import data.nat.prime

/- TEXT:
.. _conjunction_and_biimplication:

Conjunction and Bi-implication
------------------------------

.. index:: split, tactics ; split

You have already seen that the conjunction symbol, ``∧``,
is used to express "and."
The ``split`` tactic allows you to prove a statement of
the form ``A ∧ B``
by proving ``A`` and then proving ``B``.
TEXT. -/
-- QUOTE:
example {x y : ℝ} (h₀ : x ≤ y) (h₁ : ¬ y ≤ x) : x ≤ y ∧ x ≠ y :=
begin
  split,
  { assumption },
  intro h,
  apply h₁,
  rw h
end
-- QUOTE.

/- TEXT:
.. index:: assumption, tactics ; assumption

In this example, the ``assumption`` tactic
tells Lean to find an assumption that will solve the goal.
Notice that the final ``rw`` finishes the goal by
applying the reflexivity of ``≤``.
The following are alternative ways of carrying out
the previous examples using the anonymous constructor
angle brackets.
The first is a slick proof-term version of the
previous proof,
which drops into tactic mode at the keyword ``by``.
TEXT. -/
-- QUOTE:
example {x y : ℝ} (h₀ : x ≤ y) (h₁ : ¬ y ≤ x) : x ≤ y ∧ x ≠ y :=
⟨h₀, λ h, h₁ (by rw h)⟩

example {x y : ℝ} (h₀ : x ≤ y) (h₁ : ¬ y ≤ x) : x ≤ y ∧ x ≠ y :=
begin
  have h : x ≠ y,
  { contrapose! h₁,
    rw h₁ },
  exact ⟨h₀, h⟩
end
-- QUOTE.

/- TEXT:
*Using* a conjunction instead of proving one involves unpacking the proofs of the
two parts.
You can use the ``cases`` tactic for that,
as well as ``rcases``, ``rintros``, or a pattern-matching lambda,
all in a manner similar to the way they are used with
the existential quantifier.
TEXT. -/
-- QUOTE:
example {x y : ℝ} (h : x ≤ y ∧ x ≠ y) : ¬ y ≤ x :=
begin
  cases h with h₀ h₁,
  contrapose! h₁,
  exact le_antisymm h₀ h₁
end

example {x y : ℝ} : x ≤ y ∧ x ≠ y → ¬ y ≤ x :=
begin
  rintros ⟨h₀, h₁⟩ h',
  exact h₁ (le_antisymm h₀ h')
end

example {x y : ℝ} : x ≤ y ∧ x ≠ y → ¬ y ≤ x :=
λ ⟨h₀, h₁⟩ h', h₁ (le_antisymm h₀ h')
-- QUOTE.

/- TEXT:
In contrast to using an existential quantifier,
you can also extract proofs of the two components
of a hypothesis ``h : A ∧ B``
by writing ``h.left`` and ``h.right``,
or, equivalently, ``h.1`` and ``h.2``.
TEXT. -/
-- QUOTE:
example {x y : ℝ} (h : x ≤ y ∧ x ≠ y) : ¬ y ≤ x :=
begin
  intro h',
  apply h.right,
  exact le_antisymm h.left h'
end

example {x y : ℝ} (h : x ≤ y ∧ x ≠ y) : ¬ y ≤ x :=
λ h', h.right (le_antisymm h.left h')
-- QUOTE.

/- TEXT:
Try using these techniques to come up with various ways of proving of the following:
TEXT. -/
-- QUOTE:
example {m n : ℕ} (h : m ∣ n ∧ m ≠ n) :
  m ∣ n ∧ ¬ n ∣ m :=
sorry
-- QUOTE.

-- SOLUTIONS:
example {m n : ℕ} (h : m ∣ n ∧ m ≠ n) :
  m ∣ n ∧ ¬ n ∣ m :=
begin
  cases h with h0 h1,
  split,
  { exact h0 },
  intro h2,
  apply h1,
  apply nat.dvd_antisymm h0 h2,
end

/- TEXT:
You can nest uses of ``∃`` and ``∧``
with anonymous constructors, ``rintros``, and ``rcases``.
TEXT. -/
-- QUOTE:
example : ∃ x : ℝ, 2 < x ∧ x < 4 :=
⟨5/2, by norm_num, by norm_num⟩

example (x y : ℝ) : (∃ z : ℝ, x < z ∧ z < y) → x < y :=
begin
  rintros ⟨z, xltz, zlty⟩,
  exact lt_trans xltz zlty
end

example (x y : ℝ) : (∃ z : ℝ, x < z ∧ z < y) → x < y :=
λ ⟨z, xltz, zlty⟩, lt_trans xltz zlty
-- QUOTE.

/- TEXT:
You can also use the ``use`` tactic:
TEXT. -/
-- QUOTE:
example : ∃ x : ℝ, 2 < x ∧ x < 4 :=
begin
  use 5 / 2,
  split; norm_num
end

example : ∃ m n : ℕ,
  4 < m ∧ m < n ∧ n < 10 ∧ nat.prime m ∧ nat.prime n :=
begin
  use [5, 7],
  norm_num
end

example {x y : ℝ} : x ≤ y ∧ x ≠ y → x ≤ y ∧ ¬ y ≤ x :=
begin
  rintros ⟨h₀, h₁⟩,
  use [h₀, λ h', h₁ (le_antisymm h₀ h')]
end
-- QUOTE.

/- TEXT:
In the first example, the semicolon after the ``split`` command tells Lean to use the
``norm_num`` tactic on both of the goals that result.

In Lean, ``A ↔ B`` is *not* defined to be ``(A → B) ∧ (B → A)``,
but it could have been,
and it behaves roughly the same way.
You have already seen that you can write ``h.mp`` and ``h.mpr``
or ``h.1`` and ``h.2`` for the two directions of ``h : A ↔ B``.
You can also use ``cases`` and friends.
To prove an if-and-only-if statement,
you can uses ``split`` or angle brackets,
just as you would if you were proving a conjunction.
TEXT. -/
-- QUOTE:
example {x y : ℝ} (h : x ≤ y) : ¬ y ≤ x ↔ x ≠ y :=
begin
  split,
  { contrapose!,
    rintro rfl,
    reflexivity },
  contrapose!,
  exact le_antisymm h
end

example {x y : ℝ} (h : x ≤ y) : ¬ y ≤ x ↔ x ≠ y :=
⟨λ h₀ h₁, h₀ (by rw h₁), λ h₀ h₁, h₀ (le_antisymm h h₁)⟩
-- QUOTE.

/- TEXT:
The last proof term is inscrutable. Remember that you can
use underscores while writing an expression like that to
see what Lean expects.

Try out the various techniques and gadgets you have just seen
in order to prove the following:
TEXT. -/
-- QUOTE:
example {x y : ℝ} : x ≤ y ∧ ¬ y ≤ x ↔ x ≤ y ∧ x ≠ y :=
sorry
-- QUOTE.

-- SOLUTIONS:
example {x y : ℝ} : x ≤ y ∧ ¬ y ≤ x ↔ x ≤ y ∧ x ≠ y :=
begin
  split,
  { rintros ⟨h0, h1⟩,
    split,
    { exact h0 },
    intro h2,
    apply h1,
    rw h2 },
  rintros ⟨h0, h1⟩,
  split,
  { exact h0 },
  intro h2,
  apply h1,
  apply le_antisymm h0 h2
end

/- TEXT:
For a more interesting exercise, show that for any
two real numbers ``x`` and ``y``,
``x^2 + y^2 = 0`` if and only if ``x = 0`` and ``y = 0``.
We suggest proving an auxiliary lemma using
``linarith``, ``pow_two_nonneg``, and ``pow_eq_zero``.
TEXT. -/
-- QUOTE:
theorem aux {x y : ℝ} (h : x^2 + y^2 = 0) : x = 0 :=
begin
  have h' : x^2 = 0,
  { sorry },
  exact pow_eq_zero h'
end

example (x y : ℝ) : x^2 + y^2 = 0 ↔ x = 0 ∧ y = 0 :=
sorry
-- QUOTE.

-- SOLUTIONS:
theorem auxαα {x y : ℝ} (h : x^2 + y^2 = 0) : x = 0 :=
begin
  have h' : x^2 = 0,
  { linarith [pow_two_nonneg x, pow_two_nonneg y] },
  exact pow_eq_zero h'
end

example (x y : ℝ) : x^2 + y^2 = 0 ↔ x = 0 ∧ y = 0 :=
begin
  split,
  { intro h,
    split,
    { exact aux h },
    rw add_comm at h,
    exact aux h },
  rintros ⟨rfl, rfl⟩,
  norm_num
end

/- TEXT:
In Lean, bi-implication leads a double-life.
You can treat it like a conjunction and use its two
parts separately.
But Lean also knows that it is a reflexive, symmetric,
and transitive relation between propositions,
and you can also use it with ``calc`` and ``rw``.
It is often convenient to rewrite a statement to
an equivalent one.
In the next example, we use ``abs_lt`` to
replace an expression of the form ``abs x < y``
by the equivalent expression ``- y < x ∧ x < y``,
and in the one after that we use ``nat.dvd_gcd_iff``
to replace an expression of the form ``m ∣ nat.gcd n k`` by the equivalent expression ``m ∣ n ∧ m ∣ k``.
TEXT. -/
section

-- QUOTE:
example (x y : ℝ) : abs (x + 3) < 5 → -8 < x ∧ x < 2 :=
begin
  rw abs_lt,
  intro h,
  split; linarith
end

example : 3 ∣ nat.gcd 6 15 :=
begin
  rw nat.dvd_gcd_iff,
  split; norm_num
end
-- QUOTE.

end

/- TEXT:
See if you can use ``rw`` with the theorem below
to provide a short proof that negation is not a
nondecreasing function. (Note that ``push_neg`` won't
unfold definitions for you, so the ``rw monotone`` in
the proof of the theorem is needed.)
BOTH: -/
-- QUOTE:
theorem not_monotone_iff {f : ℝ → ℝ}:
  ¬ monotone f ↔ ∃ x y, x ≤ y ∧ f x > f y :=
by { rw monotone, push_neg }

-- EXAMPLES:
example : ¬ monotone (λ x : ℝ, -x) :=
sorry
-- QUOTE.

-- SOLUTIONS:
example : ¬ monotone (λ x : ℝ, -x) :=
begin
  rw not_monotone_iff,
  use [0, 1],
  norm_num
end

/- TEXT:
The remaining exercises in this section are designed
to give you some more practice with conjunction and
bi-implication. Remember that a *partial order* is a
binary relation that is transitive, reflexive, and
antisymmetric.
An even weaker notion sometimes arises:
a *preorder* is just a reflexive, transitive relation.
For any pre-order ``≤``,
Lean axiomatizes the associated strict pre-order by
``a < b ↔ a ≤ b ∧ ¬ b ≤ a``.
Show that if ``≤`` is a partial order,
then ``a < b`` is equivalent to ``a ≤ b ∧ a ≠ b``:
TEXT. -/
-- BOTH:
section
-- QUOTE:
variables {α : Type*} [partial_order α]
variables a b : α

-- EXAMPLES:
example : a < b ↔ a ≤ b ∧ a ≠ b :=
begin
  rw lt_iff_le_not_le,
  sorry
end
-- QUOTE.

-- SOLUTIONS:
example : a < b ↔ a ≤ b ∧ a ≠ b :=
begin
  rw lt_iff_le_not_le,
  split,
  { rintros ⟨h0, h1⟩,
    split,
    { exact h0 },
    intro h2,
    apply h1,
    rw h2 },
  rintros ⟨h0, h1⟩,
  split,
  { exact h0 },
  intro h2,
  apply h1,
  apply le_antisymm h0 h2
end

-- BOTH:
end

/- TEXT:
.. index:: simp, tactics ; simp

Beyond logical operations, you should not need
anything more than ``le_refl`` and ``le_antisymm``.
Then show that even in the case where ``≤``
is only assumed to be a preorder,
we can prove that the strict order is irreflexive
and transitive.
You do not need anything more than ``le_refl`` and ``le_trans``.
In the second example,
for convenience, we use the simplifier rather than ``rw``
to express ``<`` in terms of ``≤`` and ``¬``.
We will come back to the simplifier later,
but here we are only relying on the fact that it will
use the indicated lemma repeatedly, even if it needs
to be instantiated to different values.
TEXT. -/
-- BOTH:
section
-- QUOTE:
variables {α : Type*} [preorder α]
variables a b c : α

-- EXAMPLES:
example : ¬ a < a :=
begin
  rw lt_iff_le_not_le,
  sorry
end

example : a < b → b < c → a < c :=
begin
  simp only [lt_iff_le_not_le],
  sorry
end
-- QUOTE.

-- SOLUTIONS:
example : ¬ a < a :=
begin
  rw lt_iff_le_not_le,
  rintros ⟨h0, h1⟩,
  exact h1 h0
end

example : a < b → b < c → a < c :=
begin
  simp only [lt_iff_le_not_le],
  rintros ⟨h0, h1⟩ ⟨h2, h3⟩,
  split,
  { apply le_trans h0 h2 },
  intro h4,
  apply h1,
  apply le_trans h2 h4
end

-- BOTH:
end


import data.nat.prime
import data.rat.basic
import data.real.basic
import tactic

-- could use:
-- (univ : set X)
-- to have X be recognized as a subset of X
-- X has Type u for some universe u; it doesn't have type set X

theorem sqrt_two_irrational {a b : ℕ} (co : nat.gcd a b = 1) : a^2 ≠ 2 * b^2 :=
begin
    intro h,
    have h1 : 2 ∣ a^2, simp [h],
    have h2 : 2 ∣ a, from nat.prime.dvd_of_dvd_pow nat.prime_two h1,
    cases h2 with c aeq,
    have h3 : 2 * ( 2 * c^2) = 2 * b^2,
        by simp [eq.symm h, aeq];
           simp [nat.pow_succ, mul_comm, mul_assoc, mul_left_comm],
    have h4 : 2 * c^2 = b^2,
        from nat.eq_of_mul_eq_mul_left dec_trivial h3,
    have h5 : 2 ∣ b^2, by simp [eq.symm h4],
    have hb : 2 ∣ b, from nat.prime.dvd_of_dvd_pow nat.prime_two h5,
    have ha : 2 ∣ a, from nat.prime.dvd_of_dvd_pow nat.prime_two h1,
    have h6 : 2 ∣ nat.gcd a b, from nat.dvd_gcd ha hb,
    have habs : 2 ∣ (1 : ℕ), by
        {rw co at h6, exact h6},
    have h7 : ¬ 2 ∣ 1, exact dec_trivial,
    exact h7 habs, done
end

-- Some simple preliminary results. Not really needed, but they make the proof shorter
lemma rat_times_rat (r : ℚ) : r * r * ↑(r.denom) ^ 2 = ↑(r.num) ^ 2 :=
begin
    have h1 := @rat.mul_denom_eq_num r,
    rw pow_two,
    rw mul_assoc, rw ← mul_assoc r r.denom r.denom,
    rw h1, rw ← mul_assoc, rw mul_comm, rw ← mul_assoc,
    rw mul_comm ↑r.denom r, rw h1, rw pow_two, done
end
#check nat.coprime.pow
#check nat.coprime.pow_left
#check nat.coprime.dvd_of_dvd_mul_left
#check nat.coprime.coprime_dvd_left

theorem rational_not_sqrt_two : ¬ ∃ r : ℚ, r ^ 2 = (2:ℚ)  :=
begin
    intro h,
    cases h with r H,
    let num := r.num, set den := r.denom with hden,
    --explicitly build the hypothetical rational number r
    have hr := @rat.num_denom r,
    rw ← hr at H, -- use it in the main assumption
    -- now we can figure out some properties of r.num and r.denom
    -- first off, the denominator is not zero; this is encoded in r.
    have hdenom := r.pos,  -- the denom is actually positive
    have hdne : r.denom ≠ 0, linarith, -- so it is non zero; linarith can handle that
    set n := int.nat_abs num with hn1,
    have hn : (n ^2 : ℤ) = num ^ 2,
        norm_cast, rw ← int.nat_abs_pow_two num, rw ← int.coe_nat_pow,
    have G : (2:ℚ) * (r.denom ^2) = (r.num ^ 2),
        rw ← H, norm_cast, simp,
        rw pow_two r, simp * at *,
        exact rat_times_rat r,
    norm_cast at G,
    rw ← hn at G,
    have g1 : nat.gcd n den = 1,
        have g11 := r.cop,
        unfold nat.coprime at g11,
        have g12 := nat.coprime.pow_left 2 g11,
        have g13 := nat.coprime.coprime_mul_left g12,
        rw ← hn1 at g13, exact g13,
    have E := sqrt_two_irrational g1,
    have g2 := G.symm,
    norm_cast at g2,
    done
end

example (a b : ℝ) : a = b → a^2 = b^2 := by library_search

-- some extra stuff on rationals from Zulip
--import data.rat.basic tactic

lemma rat_id01 {a b : ℤ} (hb0 : 0 < b) (h : nat.coprime a.nat_abs b.nat_abs) :
  (a / b : ℚ).num = a ∧ ((a / b : ℚ).denom : ℤ) = b :=
begin
  lift b to ℕ using le_of_lt hb0,
  norm_cast at hb0 h,
  rw [← rat.mk_eq_div, ← rat.mk_pnat_eq a b hb0, rat.mk_pnat_num, rat.mk_pnat_denom,
    pnat.mk_coe, h.gcd_eq_one, int.coe_nat_one, int.div_one, nat.div_one],
  split; refl
end

--import data.rat.basic tactic

lemma rat_id02 {a b : ℤ} (hb0 : 0 < b) (h : nat.coprime a.nat_abs b.nat_abs) :
  (a / b : ℚ).num = a ∧ ((a / b : ℚ).denom : ℤ) = b :=
begin
  lift b to ℕ using le_of_lt hb0,
  norm_cast at hb0 h,
  rw [← rat.mk_eq_div, ← rat.mk_pnat_eq a b hb0],
  split; simp [rat.mk_pnat_num, rat.mk_pnat_denom, h.gcd_eq_one]
end


import Lean4Axiomatic.Rational.Impl.Fraction.Addition
import Lean4Axiomatic.Rational.Multiplication

namespace Lean4Axiomatic.Rational.Impl.Fraction

open Logic (AP)
open Signed (Positive)

variable {ℕ : Type} [Natural ℕ]
variable {ℤ : Type} [Integer (ℕ := ℕ) ℤ]

/-! ## Fraction multiplication -/

/-- Multiplication of fractions. -/
def mul : Fraction ℤ → Fraction ℤ → Fraction ℤ
| p//q, r//s => (p * r)//(q * s)

instance multiplication_ops : Multiplication.Ops (Fraction ℤ) := {
  mul := mul
}

/--
Multiplication of integer fractions is consistent with its equivalent on
integers.

**Property intuition**: This must be true if we want integers to be represented
as integer fractions.

**Proof intuition**: Expand the definition of multiplication and use integer
algebra on the numerator and denominator.
-/
theorem mul_compat_from_integer
    {a b : ℤ} : from_integer (a * b) ≃ from_integer a * from_integer b
    := by
  show (a * b)//1 ≃ a//1 * b//1
  have : a//1 * b//1 ≃ (a * b)//1 := calc
    a//1 * b//1      ≃ _ := eqv_refl
    (a * b)//(1 * 1) ≃ _ := substD AA.identL
    (a * b)//1       ≃ _ := eqv_refl
  exact eqv_symm this

/--
Multiplication of integer fractions is commutative.

**Property intuition**: We'd expect this to be true due to the viewpoint that
fractions are scaled integers.

**Proof intuition**: Expand the definition of multiplication and use integer
algebra on the numerator and denominator.
-/
theorem mul_comm {p q : Fraction ℤ} : p * q ≃ q * p := by
  revert p; intro (pn//pd); revert q; intro (qn//qd)
  show pn//pd * qn//qd ≃ qn//qd * pn//pd
  calc
    pn//pd * qn//qd      ≃ _ := eqv_refl
    (pn * qn)//(pd * qd) ≃ _ := substN AA.comm
    (qn * pn)//(pd * qd) ≃ _ := substD AA.comm
    (qn * pn)//(qd * pd) ≃ _ := eqv_refl
    qn//qd * pn//pd      ≃ _ := eqv_refl

/--
Replacing the left operand in a product of fractions with an equivalent value
gives an equivalent result.

**Property intuition**: This must be true for multiplication on fractions to be
a valid function.

**Proof intuition**: Expand all definitions in the hypotheses and goal until
equivalences involving only integers are reached. Show the goal equivalence
using algebra and the equivalence from the `p₁ ≃ p₂` hypothesis.
-/
theorem mul_substL {p₁ p₂ q : Fraction ℤ} : p₁ ≃ p₂ → p₁ * q ≃ p₂ * q := by
  revert p₁; intro (p₁n//p₁d); revert p₂; intro (p₂n//p₂d)
  revert q; intro (qn//qd)
  intro (_ : p₁n//p₁d ≃ p₂n//p₂d)
  show p₁n//p₁d * qn//qd ≃ p₂n//p₂d * qn//qd
  show (p₁n * qn)//(p₁d * qd) ≃ (p₂n * qn)//(p₂d * qd)
  show (p₁n * qn) * (p₂d * qd) ≃ (p₂n * qn) * (p₁d * qd)
  have : p₁n * p₂d ≃ p₂n * p₁d := ‹p₁n//p₁d ≃ p₂n//p₂d›
  calc
    (p₁n * qn) * (p₂d * qd) ≃ _ := AA.expr_xxfxxff_lr_swap_rl
    (p₁n * p₂d) * (qn * qd) ≃ _ := AA.substL ‹p₁n * p₂d ≃ p₂n * p₁d›
    (p₂n * p₁d) * (qn * qd) ≃ _ := AA.expr_xxfxxff_lr_swap_rl
    (p₂n * qn) * (p₁d * qd) ≃ _ := Rel.refl

/--
Replacing the right operand in a product of fractions with an equivalent value
gives an equivalent result.

**Property intuition**: This must be true for multiplication on fractions to be
a valid function.

**Proof intuition**: Flip the product around using commutativity, perform left
substitution, then flip it back.
-/
theorem mul_substR {p q₁ q₂ : Fraction ℤ} : q₁ ≃ q₂ → p * q₁ ≃ p * q₂ := by
  intro (_ : q₁ ≃ q₂)
  show p * q₁ ≃ p * q₂
  calc
    p * q₁ ≃ _ := mul_comm
    q₁ * p ≃ _ := mul_substL ‹q₁ ≃ q₂›
    q₂ * p ≃ _ := mul_comm
    p * q₂ ≃ _ := eqv_refl

/--
Fraction multiplication is associative.

**Property intuition**: We'd expect this to be true due to the viewpoint that
fractions are scaled integers.

**Proof intuition**: Evaluate all multiplications until a single fraction is
obtained. Associativity on its numerator and denominator gives the result.
-/
theorem mul_assoc {p q r : Fraction ℤ} : (p * q) * r ≃ p * (q * r) := by
  revert p; intro (pn//pd); revert q; intro (qn//qd); revert r; intro (rn//rd)
  show (pn//pd * qn//qd) * rn//rd ≃ pn//pd * (qn//qd * rn//rd)
  calc
    (pn//pd * qn//qd) * rn//rd         ≃ _ := eqv_refl
    (pn * qn)//(pd * qd) * rn//rd      ≃ _ := eqv_refl
    ((pn * qn) * rn)//((pd * qd) * rd) ≃ _ := substN AA.assoc
    (pn * (qn * rn))//((pd * qd) * rd) ≃ _ := substD AA.assoc
    (pn * (qn * rn))//(pd * (qd * rd)) ≃ _ := eqv_refl
    pn//pd * (qn * rn)//(qd * rd)      ≃ _ := eqv_refl
    pn//pd * (qn//qd * rn//rd)         ≃ _ := eqv_refl

/--
One is the left multiplicative identity for fractions.

**Property intuition**: We'd expect this to be true due to the viewpoint that
fractions are scaled integers.

**Proof intuition**: Evaluate the multiplication to obtain a single fraction.
Use the integer multiplicative identity on its numerator and denominator.
-/
theorem mul_identL {p : Fraction ℤ} : 1 * p ≃ p := by
  revert p; intro (pn//pd)
  show 1 * pn//pd ≃ pn//pd
  calc
    1 * pn//pd         ≃ _ := eqv_refl
    1//1 * pn//pd      ≃ _ := eqv_refl
    (1 * pn)//(1 * pd) ≃ _ := substN AA.identL
    pn//(1 * pd)       ≃ _ := substD AA.identL
    pn//pd             ≃ _ := eqv_refl

/--
One is the right multiplicative identity for fractions.

**Property intuition**: We'd expect this to be true due to the viewpoint that
fractions are scaled integers.

**Proof intuition**: Follows from left identity via commutativity.
-/
theorem mul_identR {p : Fraction ℤ} : p * 1 ≃ p :=
  eqv_trans mul_comm mul_identL

/--
A common factor on the left of the numerator and denominator can be removed.

**Property and proof intuition**: A fraction of products, in the numerator and
denominator, is equivalent to a product of fractions of the factors. If the two
factors on the left of the numerator and denominator are the same, then the
corresponding fraction factor is equivalent to one, and doesn't contribute to
the result.
-/
theorem cancelL
    {a b c : ℤ} [AP (Positive a)] [AP (Positive c)] : (a * b)//(a * c) ≃ b//c
    := calc
  (a * b)//(a * c) ≃ _ := eqv_refl
  a//a * b//c      ≃ _ := mul_substL (eqv_one_iff_numer_eqv_denom.mpr Rel.refl)
  1 * b//c         ≃ _ := mul_identL
  b//c             ≃ _ := eqv_refl

/--
A common factor on the right of the numerator and denominator can be removed.

**Property and proof intuition**: This follows from left-cancellation and
commutativity.
-/
theorem cancelR
    {a b c : ℤ} [AP (Positive a)] [AP (Positive c)] : (b * a)//(c * a) ≃ b//c
    := calc
  (b * a)//(c * a) ≃ _ := substN AA.comm
  (a * b)//(c * a) ≃ _ := substD AA.comm
  (a * b)//(a * c) ≃ _ := cancelL
  b//c             ≃ _ := eqv_refl

/--
Addition of fractions with the same denominator can be accomplished by adding
their numerators.

**Property intuition**: The numerators are at the same "scale" because the
denominators are the same, so they can be added as integers.

**Proof intuition**: Evaluate the addition, then pull out the common factor of
`d` in the numerator using integer distributivity. With a factor of `d` in the
numerator and denominator, the fraction is the result of multiplication by
`d//d`, which is `1`. So the common factor can be removed, achieving the goal.
-/
theorem add_eqv_denominators
    {a b d : ℤ} [AP (Positive d)] : a//d + b//d ≃ (a + b)//d
    := calc
  a//d + b//d
    ≃ _ := eqv_refl
  (a * d + d * b)//(d * d)
    ≃ _ := substN (AA.substR AA.comm)
  (a * d + b * d)//(d * d)
    ≃ _ := substN (Rel.symm AA.distribR)
  ((a + b) * d)//(d * d)
    ≃ _ := cancelR
  (a + b)//d
    ≃ _ := eqv_refl

/--
Fraction multiplication (on the left) distributes over fraction addition.

**Property intuition**: We'd expect this to be true due to the viewpoint that
fractions are scaled integers.

**Proof intuition**: Evaluate the addition and multiplication of the left-hand
side to produce a single fraction. Use integer distributivity to make the
numerator a sum. Split the fraction into a sum of fractions with the same
denominator. Cancel common factors and separate each term into a product of the
input fractions.
-/
theorem mul_distribL {p q r : Fraction ℤ} : p * (q + r) ≃ p * q + p * r := by
  revert p; intro (pn//pd); revert q; intro (qn//qd); revert r; intro (rn//rd)
  show pn//pd * (qn//qd + rn//rd) ≃ pn//pd * qn//qd + pn//pd * rn//rd
  -- For some unknown reason this is needed to prevent a compile error
  have pos_mul_denom_prq : AP (Positive (pd * (rd * qd))) := inferInstance
  calc
    pn//pd * (qn//qd + rn//rd)
      ≃ _ := eqv_refl
    pn//pd * (qn * rd + qd * rn)//(qd * rd)
      ≃ _ := eqv_refl
    (pn * (qn * rd + qd * rn))//(pd * (qd * rd))
      ≃ _ := substN AA.distribL
    (pn * (qn * rd) + pn * (qd * rn))//(pd * (qd * rd))
      ≃ _ := eqv_symm add_eqv_denominators
    (pn * (qn * rd))//(pd * (qd * rd)) + (pn * (qd * rn))//(pd * (qd * rd))
      ≃ _ := add_substL (substN (Rel.symm AA.assoc))
    ((pn * qn) * rd)//(pd * (qd * rd)) + (pn * (qd * rn))//(pd * (qd * rd))
      ≃ _ := add_substL (substD (Rel.symm AA.assoc))
    ((pn * qn) * rd)//((pd * qd) * rd) + (pn * (qd * rn))//(pd * (qd * rd))
      ≃ _ := add_substL cancelR
    (pn * qn)//(pd * qd) + (pn * (qd * rn))//(pd * (qd * rd))
      ≃ _ := add_substR (substN (AA.substR AA.comm))
    (pn * qn)//(pd * qd) + (pn * (rn * qd))//(pd * (qd * rd))
      ≃ _ := add_substR (substD (pb₂ := pos_mul_denom_prq) (AA.substR AA.comm))
    (pn * qn)//(pd * qd) + (pn * (rn * qd))//(pd * (rd * qd))
      ≃ _ := add_substR (substN (Rel.symm AA.assoc))
    (pn * qn)//(pd * qd) + ((pn * rn) * qd)//(pd * (rd * qd))
      ≃ _ := add_substR (substD (Rel.symm AA.assoc))
    (pn * qn)//(pd * qd) + ((pn * rn) * qd)//((pd * rd) * qd)
      ≃ _ := add_substR cancelR
    (pn * qn)//(pd * qd) + (pn * rn)//(pd * rd)
      ≃ _ := eqv_refl
    pn//pd * qn//qd + (pn * rn)//(pd * rd)
      ≃ _ := eqv_refl
    pn//pd * qn//qd + pn//pd * rn//rd
      ≃ _ := eqv_refl

/--
Fraction multiplication (on the right) distributes over fraction addition.

**Property intuition**: We'd expect this to be true due to the viewpoint that
fractions are scaled integers.

**Proof intuition**: Follows from left-distributivity and commutativity of
addition and multiplication.
-/
theorem mul_distribR {p q r : Fraction ℤ} : (q + r) * p ≃ q * p + r * p := calc
  (q + r) * p   ≃ _ := mul_comm
  p * (q + r)   ≃ _ := mul_distribL
  p * q + p * r ≃ _ := add_substL mul_comm
  q * p + p * r ≃ _ := add_substR mul_comm
  q * p + r * p ≃ _ := eqv_refl

instance multiplication_props : Multiplication.Props (Fraction ℤ) := {
  mul_substL := mul_substL
  mul_substR := mul_substR
  mul_compat_from_integer := mul_compat_from_integer
  mul_comm := mul_comm
  mul_assoc := mul_assoc
  mul_identL := mul_identL
  mul_identR := mul_identR
  mul_distribL := mul_distribL
  mul_distribR := mul_distribR
}

instance multiplication : Multiplication (Fraction ℤ) := {
  toOps := multiplication_ops
  toProps := multiplication_props
}

end Lean4Axiomatic.Rational.Impl.Fraction


/-
Copyright (c) 2022 Anne Baanen. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Anne Baanen, Alex J. Best
-/

import linear_algebra.determinant
import linear_algebra.free_module.finite.basic

/-!
# Determinants in free (finite) modules

Quite a lot of our results on determinants (that you might know in vector spaces) will work for all
free (finite) modules over any commutative ring.

## Main results

 * `linear_map.det_zero''`: The determinant of the constant zero map is zero, in a finite free
   nontrivial module.
-/

@[simp] lemma linear_map.det_zero'' {R M : Type*} [comm_ring R] [add_comm_group M] [module R M]
  [module.free R M] [module.finite R M] [nontrivial M] :
  linear_map.det (0 : M →ₗ[R] M) = 0 :=
begin
  letI : nonempty (module.free.choose_basis_index R M) :=
    (module.free.choose_basis R M).index_nonempty,
  nontriviality R,
  exact linear_map.det_zero' (module.free.choose_basis R M)
end


import linear_algebra.basic
import linear_algebra.basis
open linear_map
open is_basis
universe variables u v w
namespace classical_basis
set_option trace.simplify.rewrite true
variables {G : Type u} {R : Type v} [group G] [comm_ring R]
variables {X : Type w} [fintype X] [decidable_eq X]
/-!
    Definition of classical basis of `X → R`.
    `ε x = λ y, if x = y then 1 else 0`.
-/
def ε {R : Type v}[comm_ring R]{X : Type w}(x : X) [fintype X] [decidable_eq X] :
        (X → R) := (λ y : X, if x = y then 1 else 0)
variable (x : X)
#check ε x x     -- premier probleme est-ce que je dois mettre X et R ?
                    -- C'est lourd

lemma epsilon_eq (x : X) : ε x x = (1 : R) :=   --- un meilleur nom !
begin
    unfold ε,simp,
end
/-!
    On va integrer les sommes pour obtenir une formule du style
    pour f : X → R , f = ∑ λ x, f x * (ε  x) !
    C'est la décomposition dans la base ε !
-/
@[simp]lemma epsilon_ne ( x y : X)(HYP : ¬ y = x) : ε x y = (0 : R) := begin
    unfold ε, split_ifs, rw h at HYP, trivial,
    exact rfl,
end
@[simp]lemma smul_ite (φ : X → R) ( y : X) : (λ (x : X), φ x • (ε x y)) =  (λ x : X, if y = x then φ x else 0) := begin
    funext,
    split_ifs,
        rw h, rw epsilon_eq, exact mul_one (φ x),
        rw epsilon_ne x y h,
        exact mul_zero (φ x),
end
notation `Σ` := finset.sum finset.univ
lemma gen (φ : X → R) : φ  = Σ (λ (x : X), φ  x  • ε  x) :=
begin
    funext y,
    rw finset.sum_apply,
    --change _ = Σ (λ x : X, φ x •  ε x y),
    erw smul_ite,
    erw finset.sum_ite_eq,
    split_ifs,exact rfl,
    have R : y ∈ finset.univ, exact finset.mem_univ y, trivial,
end

#check is_basis R ε
@[simp]lemma  test  (g : X → R)(s : finset X)(y ∈  s) :
        finset.sum s (λ i : X, g i • (ε  i : X → R) ) y = finset.sum s (λ i : X, (g i • ε i) y) :=
begin exact finset.sum_apply (λ (i : X), R) (λ (i : X), g i • ε i) y,

end
@[simp]lemma classical_basis : is_basis R (λ x : X, (ε  x : X → R)) :=  --- c'est un peu chiant
begin
    split,
    rw linear_independent_iff', intros s, intros φ, intros hyp, intros x, intros hyp_x,
    rw function.funext_iff at hyp, specialize hyp x,
    rw finset.sum_apply (λ (i : X), R) _ _ at hyp,
    change finset.sum s (λ y : X, φ y • (ε y x : R)) = 0   at hyp,
    rw smul_ite at hyp,
    erw finset.sum_ite_eq at hyp, split_ifs at hyp,assumption,
    rw eq_top_iff, rw submodule.le_def',
    intros φ, intros,
    rw gen φ,
    let p := (submodule.span R (set.range (λ (x : X), ε x))),
    apply submodule.sum_mem p,
    intros x, intros hyp,
    apply submodule.smul_mem p,
    have r : ε x ∈ set.range (λ (t : X), ε t),
        rw set.mem_range,
        use x,
    have rr :  set.range (λ (t : X), ε t) ⊆ ↑p,
        exact submodule.subset_span ,
    have rrr : ε x ∈ ↑p,
        exact set.mem_of_mem_of_subset r rr,
    exact rrr,
end
end classical_basis


import Mathlib.Logic.Function.Basic
import Mathlib.Tactic.Linarith
import Mathlib.Tactic.Zify
import Playground.Data.GeneralDotNotation

section
  namespace Function
  noncomputable def right_inverse_of_surjective {f : α → β} (h : Surjective f)
    : β → α :=
    λ y => (h y).choose

  theorem right_inverse.is_RightInverse (h : Surjective f)
    : RightInverse (right_inverse_of_surjective h) f :=
    λ y => (h y).choose_spec

  theorem right_inverse.is_injective (h : Surjective f)
    : Injective (right_inverse_of_surjective h) :=
    (is_RightInverse h).injective

  end Function
end

section
  open Function

  def finMapExcluding : (a : Fin (n + 1)) → { x : Fin (n + 1) // x ≠ a } → Fin n
    | ⟨a, a_lt⟩, ⟨⟨i, i_lt⟩, i_prop⟩ =>
      have : i ≠ a := λ c => i_prop (Fin.eq_of_val_eq c)
      if hi : i ≤ a then
        have := lt_of_le_of_ne hi this
        { val := i, isLt := by linarith }
      else
        have : i > 0 := by linarith
        { val := i - 1, isLt := by zify [this]; linarith }

  theorem finMapExcluding_injective : ∀ a : Fin (n + 1), (finMapExcluding a).Injective
    | ⟨a, a_lt⟩, ⟨⟨i, i_lt⟩, i_prop⟩, ⟨⟨j, j_lt⟩, j_prop⟩, (h : dite .. = dite ..) =>
      if hi : i ≤ a then
        if hj : j ≤ a then by
          simp [hi, hj] at h
          subst h; rfl
        else by
          simp [hi, hj] at h
          have : j > 0 := by linarith
          zify [this] at h
          have : i = a := by linarith
          subst this; contradiction
      else
        if hj : j ≤ a then by
          simp [hi, hj] at h
          have : i > 0 := by linarith
          zify [this] at h
          have : j + 1 = i := by linarith
          have : j = a := by linarith
          subst this; contradiction
        else by
          simp [hi, hj] at h
          have i_pos : i > 0 := by linarith
          have j_pos : j > 0 := by linarith
          zify [i_pos, j_pos] at h
          have : i = j := by linarith
          subst this; rfl

  theorem le_of_injective {f : Fin n → Fin m} (hf : Injective f) : n ≤ m :=
    match n, m with
    | 0, _ => Nat.zero_le _
    | _ + 1, 0 => (Nat.not_lt_zero _ (f ⟨0, Nat.zero_lt_succ _⟩).isLt).elim
    | n + 1, m + 1 =>
      let a := f { val := n, isLt := by linarith }
      let g₁ : Fin n → { x : Fin (m + 1) // x ≠ a } :=
        λ { val := i, isLt := i_lt_n } => {
          val := f { val := i, isLt := by linarith }
          property := λ c => Nat.ne_of_lt i_lt_n $ Fin.val_eq_of_eq $ hf c
        }
      have hg₁ : Injective g₁ := λ i j h =>
        have := Fin.val_eq_of_eq $ hf $ congrArg Subtype.val h
        Fin.eq_of_val_eq this
      let hg₂ := finMapExcluding_injective a
      Nat.succ_le_succ <| le_of_injective <| Injective.comp hg₂ hg₁

  theorem ge_of_surjective {f : Fin n → Fin m} (hf : Surjective f) : n ≥ m :=
    right_inverse.is_injective hf |> le_of_injective

  theorem eq_of_bijective {f : Fin n → Fin m} (hf : Bijective f) : n = m :=
    (le_of_injective hf.injective)·le_antisymm (ge_of_surjective hf.surjective)

example (a b : ℕ) (h : a ≤ b) (g : b ≤ a) : a = b := by
  -- linarith
  sorry
end

section
  open Function

  universe u
  variable (type : Type u)

  def has_card (n : Nat) := ∃ f : Fin n → type, Bijective f

  def is_finite := ∃ n, type·has_card n

  -- def Inhabited.has_card (type : Inhabited (Type u)) (n : Nat) := ∃ f : Fin n → type.default, bijective f
  -- def Inhabited.is_finite (type : Inhabited (Type u)) := ∃ n, type.has_card n

  noncomputable def card : Option Nat :=
    let this := Classical.propDecidable
    if h : type·is_finite then some h.choose
    else none

  theorem has_card_unique (hn : type·has_card n) (hm : type·has_card m)
    : n = m :=
    let ⟨fn, hfn⟩ := hn
    let ⟨fm, hfm⟩ := hm
    sorry

end


import Mathlib.Algebra.Group.Defs
import Mathlib.Logic.Basic

section AddCommSemigroup_lemmas

variable {A : Type u} [AddCommSemigroup A]

lemma add_left_comm (a b c : A) : a + (b + c) = b + (a + c) :=
by rw [← add_assoc, add_comm a, add_assoc]

lemma add_right_comm (a b c : A) : a + b + c = a + c + b :=
by rw [add_assoc, add_comm b, add_assoc]

theorem add_add_add_comm (a b c d : A) : (a + b) +(c + d) = (a + c) + (b + d) :=
by simp [add_left_comm, add_assoc]

end AddCommSemigroup_lemmas

section CommSemigroup_lemmas

variable {M : Type u} [CommSemigroup M]

theorem mul_mul_mul_comm (a b c d : M) : (a * b) * (c * d) = (a * c) * (b * d) :=
by simp [mul_assoc, mul_left_comm]

end CommSemigroup_lemmas

section AddLeftCancelMonoid_lemmas
-- too lazy to do mul versions and right versions

variable {A : Type u} [AddMonoid A] [IsAddLeftCancel A] {a b : A}

lemma add_right_eq_self : a + b = a ↔ b = 0 :=
by rw [←add_left_cancel_iff (c := 0), add_zero]

lemma self_eq_add_right : a = a + b ↔ b = 0 :=
by rw [←add_left_cancel_iff (c := 0), add_zero, eq_comm]

end AddLeftCancelMonoid_lemmas


-- Exercise 1

section ex1

variables p q r : Prop

-- commutativity of ∧ and ∨
example : p ∧ q ↔ q ∧ p :=
have beep : p ∧ q → q ∧ p, from λ p_and_q : p ∧ q, and.intro p_and_q.2 p_and_q.1,
have boop : q ∧ p → p ∧ q, from λ q_and_p : q ∧ p, and.intro q_and_p.2 q_and_p.1,
show p ∧ q ↔ q ∧ p, from iff.intro beep boop

example : p ∨ q ↔ q ∨ p :=
have beep : p ∨ q → q ∨ p, from λ p_or_q : p ∨ q,
    or.elim
        p_or_q
        (or.intro_right q)
        (or.intro_left p),
have boop : q ∨ p → p ∨ q, from λ q_or_p : q ∨ p,
    or.elim
        q_or_p
        (or.intro_right p)
        (or.intro_left q),
show p ∨ q ↔ q ∨ p, from iff.intro beep boop

-- associativity of ∧ and ∨
example : (p ∧ q) ∧ r ↔ p ∧ (q ∧ r) :=
have group_left : (p ∧ q) ∧ r → p ∧ (q ∧ r), from (
    λ (left_grouped: (p ∧ q) ∧ r),
    and.intro
        left_grouped.elim_left.elim_left
        (and.intro left_grouped.elim_left.elim_right left_grouped.elim_right)
),
have group_right : p ∧ (q ∧ r) → (p ∧ q) ∧ r, from (
    λ (right_grouped: p ∧ (q ∧ r)),
    and.intro
        (and.intro right_grouped.elim_left right_grouped.elim_right.elim_left)
        right_grouped.elim_right.elim_right
),
show (p ∧ q) ∧ r ↔ p ∧ (q ∧ r), from iff.intro group_left group_right

example : (p ∨ q) ∨ r ↔ p ∨ (q ∨ r) :=
have flattened_left : (p ∨ q) ∨ r → p ∨ q ∨ r, from (
    λ grouped, or.elim grouped
        (λ group,
            or.elim group
                (λ (hp : p), or.inl hp)
                (λ (hq : q), or.inr (or.inl hq))
            )
        (λ (hr : r), or.inr (or.inr hr))
),
have unflattened_left : p ∨ q ∨ r → (p ∨ q) ∨ r, from (
    λ flat, or.elim flat
        (λ (hp : p), or.inl (or.inl hp))
        (λ (hqr : q ∨ r), or.elim hqr
            (λ (hq : q), or.inl (or.inr hq))
            (λ (hr : r), or.inr hr)
        )
),
have flattened_right : p ∨ (q ∨ r) → p ∨ q ∨ r, from λ a, a,
have unflattened_right : p ∨ q ∨ r → p ∨ (q ∨ r), from λ a, a,
have left_flattens : (p ∨ q) ∨ r ↔ p ∨ q ∨ r, from iff.intro flattened_left unflattened_left,
have right_flattens : p ∨ (q ∨ r) ↔ p ∨ q ∨ r, from iff.intro flattened_right unflattened_right,
show (p ∨ q) ∨ r ↔ p ∨ (q ∨ r), from iff.trans left_flattens right_flattens

-- distributivity
example : p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r) :=
have expansion : p ∧ (q ∨ r) → (p ∧ q) ∨ (p ∧ r), from (
    λ factorized,
    let (hp : p) := factorized.left in
    let (q_or_r_hypothesis : q ∨ r) := factorized.right in
    let (first_branch : q → (p ∧ q) ∨ (p ∧ r)) := λ hq,  or.intro_left (p ∧ r) (and.intro hp hq) in
    let (second_branch : r → p ∧ q ∨ p ∧ r) := λ hr, or.intro_right (p ∧ q) (and.intro hp hr) in
    or.elim
        q_or_r_hypothesis
        first_branch
        second_branch
),
have factorization : (p ∧ q) ∨ (p ∧ r) → p ∧ (q ∨ r), from (
    λ expanded,
    let (hp : p) := or.elim expanded and.left and.left in
    let (extract_q : (p ∧ q) → (q ∨ r)) := λ p_and_q, or.intro_left r (and.right p_and_q) in
    let (extract_r : (p ∧ r) → (q ∨ r)) := λ p_and_r, or.intro_right q (and.right p_and_r) in
    let (q_or_r_hypothesis : q ∨ r) := or.elim expanded extract_q extract_r in
    and.intro hp q_or_r_hypothesis
),
show p ∧ (q ∨ r) ↔ (p ∧ q) ∨ (p ∧ r), from iff.intro expansion factorization

example : p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r) :=
have expansion : p ∨ (q ∧ r) → (p ∨ q) ∧ (p ∨ r), from (
    λ factorized,
    let (p_or_q : (p ∨ q)) := or.elim factorized (or.intro_left q) (λ conj, or.intro_right p conj.left) in
    let (p_or_r : (p ∨ r)) := or.elim factorized (or.intro_left r) (λ conj, or.intro_right p conj.right) in
    and.intro p_or_q p_or_r
),
have factorization : (p ∨ q) ∧ (p ∨ r) → p ∨ (q ∧ r), from (
    λ expanded,
    let (p_case : p → p ∨ (q ∧ r)) := λ (hp : p), or.intro_left (q ∧ r) hp in
    let (q_and_r_case : (q ∧ r) → p ∨ (q ∧ r)) := λ (q_and_r : q ∧ r), or.intro_right p q_and_r in
    let split := λ (p_or_q : p ∨ q) (p_or_r : p ∨ r),
        or.elim p_or_q p_case (λ hq, or.elim p_or_r p_case (λ hr, q_and_r_case (and.intro hq hr))) in
    let (analyzed : p ∨ (q ∧ r)) := and.elim expanded split in
    or.elim analyzed p_case q_and_r_case
),
show p ∨ (q ∧ r) ↔ (p ∨ q) ∧ (p ∨ r), from iff.intro expansion factorization

-- other properties
example : (p → (q → r)) ↔ (p ∧ q → r) :=
have implication : (p → (q → r)) → (p ∧ q → r), from λ p_to_q_to_r, λ p_and_q, p_to_q_to_r p_and_q.left p_and_q.right,
have reversed :  (p ∧ q → r) → (p → (q → r)), from λ p_and_q_to_r, λ p, λ q, p_and_q_to_r (and.intro p q),
show (p → (q → r)) ↔ (p ∧ q → r), from iff.intro implication reversed

example : ((p ∨ q) → r) ↔ (p → r) ∧ (q → r) :=
have expansion : ((p ∨ q) → r) → (p → r) ∧ (q → r), from (
    λ p_or_q_to_r,
        and.intro
            (λ hp, let p_or_q := or.intro_left q hp in p_or_q_to_r p_or_q)
            (λ hq, let p_or_q := or.intro_right p hq in p_or_q_to_r p_or_q)
),
have factorization : (p → r) ∧ (q → r) → ((p ∨ q) → r), from (
    λ p_to_r_and_q_to_r, λ p_or_q, and.elim p_to_r_and_q_to_r (λ p_to_r q_to_r, or.elim p_or_q p_to_r q_to_r)
),
show ((p ∨ q) → r) ↔ (p → r) ∧ (q → r), from iff.intro expansion factorization

-- de morgan disjunction
def de_morgan_disjunction : ¬(p ∨ q) ↔ ¬p ∧ ¬q :=
have left_to_right : ¬(p ∨ q) → ¬p ∧ ¬q, from (
    assume not_p_or_q,
    and.intro
        (λ hnotp, not_p_or_q (or.inl hnotp))
        (λ hnotq, not_p_or_q (or.inr hnotq))
),
have right_to_left : ¬p ∧ ¬q → ¬(p ∨ q), from (
    λ not_p_and_not_q, not.intro (λ p_or_q, or.elim p_or_q (λ hp, not_p_and_not_q.left hp) (λ hq, not_p_and_not_q.right hq))
),
show ¬(p ∨ q) ↔ ¬p ∧ ¬q, from iff.intro left_to_right right_to_left

example : ¬(p ∨ q) ↔ ¬p ∧ ¬q := @de_morgan_disjunction p q

example : ¬p ∨ ¬q → ¬(p ∧ q) := λ not_p_or_not_q,
    not.intro (
        λ p_and_q,
        and.elim p_and_q (λ hp hq, or.elim not_p_or_not_q (λ not_p, not_p hp) (λ not_q, not_q hq))
    )

example : ¬(p ∧ ¬p) := λ conjunction, absurd conjunction.1 conjunction.2

example : p ∧ ¬q → ¬(p → q) :=
assume (p_and_not_q : p ∧ ¬q),
have hp : p, from p_and_not_q.left,
have nq : ¬q, from p_and_not_q.right,
show ¬(p → q), from not.intro (λ p_to_q, absurd (p_to_q hp) nq)

example : ¬p → (p → q) := λ (np : ¬p), λ (hp : p), absurd hp np

example : (¬p ∨ q) → (p → q) :=
λ not_p_or_q,
λ hp,
or.elim not_p_or_q (λ not_p, absurd hp not_p) (λ hq, hq)

example : p ∨ false ↔ p :=
have p_or_false_implies_p : (p ∨ false) → p, from (
    λ p_or_false, or.elim p_or_false (λ hp, hp) false.elim
),
have p_implies_p_or_false : p → (p ∨ false), from (
    λ hp, or.intro_left false hp
),
show p ∨ false ↔ p, from iff.intro p_or_false_implies_p p_implies_p_or_false

example : p ∧ false ↔ false :=
have p_and_false_implies_false : (p ∧ false) → false, from and.right,
have false_implies_p_and_false : false → (p ∧ false), from false.elim,
show p ∧ false ↔ false, from iff.intro p_and_false_implies_false false_implies_p_and_false

example : ¬(p ↔ ¬p) := @not.intro (p ↔ ¬p) (
    assume p_iff_not_p : p ↔ ¬p,
    have hnp : ¬p, from (
        assume hp : p,
        have not_p : ¬p, from iff.elim_left p_iff_not_p hp,
        show false, from absurd hp not_p
    ),
    have hp : p, from iff.elim_right p_iff_not_p hnp,
    show false, from absurd hp hnp
)

example : (p → q) → (¬q → ¬p) :=
λ (hpq : p → q), λ (nq : ¬q), not.intro (λ hp, absurd (hpq hp) nq)

end ex1

-- Exercize 2
section ex2
    open classical

    variables p q r s : Prop

    example : (p → r ∨ s) → ((p → r) ∨ (p → s)) :=
    assume p_to_r_or_s : p → r ∨ s,
    -- assume hp : p,
    -- have r_or_s : r ∨ s, from p_to_r_or_s hp,
    sorry

    -- de morgan conjunction
    example : ¬(p ∧ q) → ¬p ∨ ¬q :=
    assume npandq : ¬(p ∧ q),
    by_contradiction (
        assume hno : ¬(¬p ∨ ¬q),
        have transformed : Π p₁ q₁, ¬(¬p₁ ∨ ¬q₁) → (¬p₁ ∧ ¬q₁), from λ l, iff.elim_left de_morgan_disjunction l
        -- have hdm : ¬p ∧ ¬q, from (iff.elim_left de_morgan_disjunction hno),
        sorry
    )

    example : ¬(p → q) → p ∧ ¬q :=
    assume h : ¬(p → q),
    sorry

    example : (p → q) → (¬p ∨ q) :=
    assume p_implies_q : p → q,
    sorry

    example : (¬q → ¬p) → (p → q) :=
    assume not_q_to_not_p : ¬q → ¬p,
    assume hp : p,
    sorry

    example : p ∨ ¬p := @em p

    example : (((p → q) → p) → p) :=
    assume p_implies_q_implies_p : ((p → q) → p),
    sorry

end ex2


/-
Copyright (c) 2020 Bhavik Mehta. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Bhavik Mehta
-/
import order.basic
import order.preorder_hom
import order.galois_connection
import tactic.monotonicity

/-!
# Closure operators on a partial order

We define (bundled) closure operators on a partial order as an monotone (increasing), extensive
(inflationary) and idempotent function.
We define closed elements for the operator as elements which are fixed by it.

Note that there is close connection to Galois connections and Galois insertions: every closure
operator induces a Galois insertion (from the set of closed elements to the underlying type), and
every Galois connection induces a closure operator (namely the composition). In particular,
a Galois insertion can be seen as a general case of a closure operator, where the inclusion is given
by coercion, see `closure_operator.gi`.

## References

* https://en.wikipedia.org/wiki/Closure_operator#Closure_operators_on_partially_ordered_sets

-/
universe u

variables (α : Type u) [partial_order α]

/--
A closure operator on the partial order `α` is a monotone function which is extensive (every `x`
is less than its closure) and idempotent.
-/
structure closure_operator extends α →ₘ α :=
(le_closure' : ∀ x, x ≤ to_fun x)
(idempotent' : ∀ x, to_fun (to_fun x) = to_fun x)

instance : has_coe_to_fun (closure_operator α) :=
{ F := _, coe := λ c, c.to_fun }

/-- See Note [custom simps projection] -/
def closure_operator.simps.apply (f : closure_operator α) : α → α := f

initialize_simps_projections closure_operator (to_preorder_hom_to_fun → apply, -to_preorder_hom)

namespace closure_operator

/-- The identity function as a closure operator. -/
@[simps]
def id : closure_operator α :=
{ to_fun := λ x, x,
  monotone' := λ _ _ h, h,
  le_closure' := λ _, le_refl _,
  idempotent' := λ _, rfl }

instance : inhabited (closure_operator α) := ⟨id α⟩

variables {α} (c : closure_operator α)

@[ext] lemma ext :
  ∀ (c₁ c₂ : closure_operator α), (c₁ : α → α) = (c₂ : α → α) → c₁ = c₂
| ⟨⟨c₁, _⟩, _, _⟩ ⟨⟨c₂, _⟩, _, _⟩ h := by { congr, exact h }

/-- Constructor for a closure operator using the weaker idempotency axiom: `f (f x) ≤ f x`. -/
@[simps]
def mk' (f : α → α) (hf₁ : monotone f) (hf₂ : ∀ x, x ≤ f x) (hf₃ : ∀ x, f (f x) ≤ f x) :
  closure_operator α :=
{ to_fun := f,
  monotone' := hf₁,
  le_closure' := hf₂,
  idempotent' := λ x, le_antisymm (hf₃ x) (hf₁ (hf₂ x)) }

@[mono]

lemma le_closure_iff (x y : α) : x ≤ c y ↔ c x ≤ c y :=
⟨λ h, c.idempotent y ▸ c.monotone h, λ h, le_trans (c.le_closure x) h⟩

lemma closure_top {α : Type u} [order_top α] (c : closure_operator α) : c ⊤ = ⊤ :=
le_antisymm le_top (c.le_closure _)

lemma closure_inter_le {α : Type u} [semilattice_inf α] (c : closure_operator α) (x y : α) :
  c (x ⊓ y) ≤ c x ⊓ c y :=
c.monotone.map_inf_le _ _

lemma closure_union_closure_le {α : Type u} [semilattice_sup α] (c : closure_operator α) (x y : α) :
  c x ⊔ c y ≤ c (x ⊔ y) :=
c.monotone.le_map_sup _ _

/-- An element `x` is closed for the closure operator `c` if it is a fixed point for it. -/
def closed : set α := λ x, c x = x

lemma mem_closed_iff (x : α) : x ∈ c.closed ↔ c x = x := iff.rfl
lemma mem_closed_iff_closure_le (x : α) : x ∈ c.closed ↔ c x ≤ x :=
⟨le_of_eq, λ h, le_antisymm h (c.le_closure x)⟩
lemma closure_eq_self_of_mem_closed {x : α} (h : x ∈ c.closed) : c x = x := h

@[simp] lemma closure_is_closed (x : α) : c x ∈ c.closed := c.idempotent x

/-- The set of closed elements for `c` is exactly its range. -/
lemma closed_eq_range_close : c.closed = set.range c :=
set.ext $ λ x, ⟨λ h, ⟨x, h⟩, by { rintro ⟨y, rfl⟩, apply c.idempotent }⟩

/-- Send an `x` to an element of the set of closed elements (by taking the closure). -/
def to_closed (x : α) : c.closed := ⟨c x, c.closure_is_closed x⟩

lemma top_mem_closed {α : Type u} [order_top α] (c : closure_operator α) : ⊤ ∈ c.closed :=
c.closure_top

lemma closure_le_closed_iff_le {x y : α} (hy : c.closed y) : x ≤ y ↔ c x ≤ y :=
by rw [← c.closure_eq_self_of_mem_closed hy, le_closure_iff]

/-- The set of closed elements has a Galois insertion to the underlying type. -/
def gi : galois_insertion c.to_closed coe :=
{ choice := λ x hx, ⟨x, le_antisymm hx (c.le_closure x)⟩,
  gc := λ x y, (c.closure_le_closed_iff_le y.2).symm,
  le_l_u := λ x, c.le_closure _,
  choice_eq := λ x hx, le_antisymm (c.le_closure x) hx }

end closure_operator

variables {α} (c : closure_operator α)

/--
Every Galois connection induces a closure operator given by the composition. This is the partial
order version of the statement that every adjunction induces a monad.
-/
@[simps]
def galois_connection.closure_operator {β : Type u} [preorder β]
  {l : α → β} {u : β → α} (gc : galois_connection l u) :
  closure_operator α :=
{ to_fun := λ x, u (l x),
  monotone' := λ x y h, gc.monotone_u (gc.monotone_l h),
  le_closure' := gc.le_u_l,
  idempotent' := λ x, le_antisymm (gc.monotone_u (gc.l_u_le _)) (gc.le_u_l _) }

/--
The Galois insertion associated to a closure operator can be used to reconstruct the closure
operator.

Note that the inverse in the opposite direction does not hold in general.
-/
@[simp]
lemma closure_operator_gi_self : c.gi.gc.closure_operator = c :=
by { ext x, refl }